/*--------------------------------------------------------------------------*/
/* 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_3d.c                                                    */
/*                                                                          */
/*                                                                          */
/* description:  special routines for getting new FE_SPACEs, 3D-Version     */
/*                                                                          */
/*--------------------------------------------------------------------------*/
/*                                                                          */
/*  authors:   Daniel Koester                                               */
/*             Institut fuer Mathematik                                     */
/*             Universitaet Augsburg                                        */
/*             Universitaetsstr. 14                                         */
/*             D-86159 Augsburg, Germany                                    */
/*                                                                          */
/*             Claus-Justus Heine                                           */
/*             Abteilung für Angewandte Mathematik                          */
/*             Universität Freiburg                                         */
/*             Hermann-Herder-Straße 10                                     */
/*             79104 Freiburg, Germany                                      */
/*                                                                          */
/*  (c) by D. Koester (2005),                                               */
/*         C.-J. Heine (2006).                                              */
/*--------------------------------------------------------------------------*/

/****************************************************************************/
/* LOGICAL_EL_3D:                                                           */
/* This structure defines a MESH element at any point in the hierarchy in   */
/* terms of global indices for vertices, edges, faces, 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.                  */
/*                                                                          */
/* neigh_v[face][loc_idx] specifies face-transformations across periodic    */
/* faces (walls): neigh_v[face][loc_idx] gives the global vertex number     */
/* on the neighbour element the vertex with local number                    */
/* (face + 1 + loc_idx) % N_VERTICES_3D is mapped to.                       */
/****************************************************************************/

typedef struct logical_el_3d LOGICAL_EL_3D;

struct logical_el_3d
{
  int    parent;
  int    child[2];
  int    neigh[N_NEIGH_3D];
  int    opp_v[N_NEIGH_3D];
  int    vertex[N_VERTICES_3D];
  int    edge[N_EDGES_3D];
  int    face[N_FACES_3D];
  int    neigh_v[N_NEIGH_3D][N_VERTICES_2D];
  U_CHAR el_type;
  EL     *el;
};

/****************************************************************************/
/* next_el[edge][direction]:                                                */
/* Gives the neighbour to move to when looping around an edge.              */
/* Needed in macro_edge_loop_3d() and hier_edge_loop_3d().                  */
/****************************************************************************/

static const int next_el[6][2] = {{3,2},{1,3},{1,2},{0,3},{0,2},{0,1}};


/****************************************************************************/
/* fill_logical_el_rec_3d(l_els, el, current_el, index,                     */
/*                        current_level, max_level):                        */
/* 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_3d(LOGICAL_EL_3D *l_els, EL *el,
				   int current_el, int *index, 
				   int current_level, int *max_level)
{
  /* FUNCNAME("fill_logical_el_rec_3d"); */
  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]].el_type  = (l_els[current_el].el_type + 1)%3;
      l_els[child_ind[i_child]].parent   = current_el;
    }

    *index += 2;

    /* Now recurse to the two children.                                     */

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

  return;
}


/****************************************************************************/
/* fill_child_vertices_3d(l_els, el): During descend_tree_3d() we need to   */
/* fill as much vertex information as possible. This routine takes care of  */
/* the task.                                                                */
/****************************************************************************/

static void fill_child_vertices_3d(LOGICAL_EL_3D *l_els, int el)
{
  FUNCNAME("fill_child_vertices_3d");
  int child[2];

  child[0] = l_els[el].child[0];
  child[1] = l_els[el].child[1];

  DEBUG_TEST_EXIT(child[0] > -1, "Did not find child 0 of %d!\n", el);
  DEBUG_TEST_EXIT(child[1] > -1, "Did not find child 1 of %d!\n", el);

  l_els[child[0]].vertex[0] = l_els[el].vertex[0];
  l_els[child[0]].vertex[1] = l_els[el].vertex[2];
  l_els[child[0]].vertex[2] = l_els[el].vertex[3];

  l_els[child[1]].vertex[0] = l_els[el].vertex[1];

  if (l_els[el].el_type == 0) {
    l_els[child[1]].vertex[1] = l_els[el].vertex[3];
    l_els[child[1]].vertex[2] = l_els[el].vertex[2];
  } else {
    l_els[child[1]].vertex[1] = l_els[el].vertex[2];
    l_els[child[1]].vertex[2] = l_els[el].vertex[3];
  }

  /* Possibly pass down face-transformations across periodic walls
   * down to the children. Quite a mess. FIXME.
   */
  if (l_els[el].neigh_v[0][0] != -1) {
    l_els[child[1]].neigh_v[3][0] = l_els[el].neigh_v[0][0];
    if (l_els[el].el_type == 0) {
      l_els[child[1]].neigh_v[3][1] = l_els[el].neigh_v[0][2];
      l_els[child[1]].neigh_v[3][2] = l_els[el].neigh_v[0][1];
    } else {
      l_els[child[1]].neigh_v[3][1] = l_els[el].neigh_v[0][1];
      l_els[child[1]].neigh_v[3][2] = l_els[el].neigh_v[0][2];
    }
  }
  if (l_els[el].neigh_v[1][0] != -1) {
    l_els[child[0]].neigh_v[3][0] = l_els[el].neigh_v[1][2];
    l_els[child[0]].neigh_v[3][1] = l_els[el].neigh_v[1][0];
    l_els[child[0]].neigh_v[3][2] = l_els[el].neigh_v[1][1];
  }
  if (l_els[el].neigh_v[2][0] != -1) {
    l_els[child[0]].neigh_v[1][0] = l_els[el].neigh_v[2][0];
    l_els[child[0]].neigh_v[1][2] = l_els[el].neigh_v[2][1];
    if (l_els[el].el_type == 0) {
      l_els[child[1]].neigh_v[2][1] = l_els[el].neigh_v[2][2];
      l_els[child[1]].neigh_v[2][2] = l_els[el].neigh_v[2][0];
    } else {
      l_els[child[1]].neigh_v[1][0] = l_els[el].neigh_v[2][0];
      l_els[child[1]].neigh_v[1][2] = l_els[el].neigh_v[2][2];
    }
  }
  if (l_els[el].neigh_v[3][0] != -1) {
    l_els[child[0]].neigh_v[2][1] = l_els[el].neigh_v[3][0];
    l_els[child[0]].neigh_v[2][2] = l_els[el].neigh_v[3][2];
    if (l_els[el].el_type == 0) {
      l_els[child[1]].neigh_v[1][0] = l_els[el].neigh_v[3][2];
      l_els[child[1]].neigh_v[1][2] = l_els[el].neigh_v[3][1];
    } else {
      l_els[child[1]].neigh_v[2][1] = l_els[el].neigh_v[3][1];
      l_els[child[1]].neigh_v[2][2] = l_els[el].neigh_v[3][2];
    }
  }

  return;
}


/****************************************************************************/
/* descend_tree_3d(l_els, orig_el, in_el, n, out_els, out_adjc):            */
/* While working on edges we wish to make sure that we are using a neighbour*/
/* element whose current refinement edge corresponds to the refinement edge */
/* of "el". In 3D this involves going downwards up to three elements in     */
/* the tree. We are then guaranteed to have correct edges lined up if the   */
/* triangulation is conforming.                                             */
/* We also need to examine if the 0-vertex on neigh corresponds to the      */
/* 0-vertex of el. This means adjc=0. Otherwise we set adjc=1.              */
/****************************************************************************/

static void descend_tree_3d(LOGICAL_EL_3D *l_els,
			    int orig_el, int in_el, int in_neigh,
			    int *n, int *out_els, int *out_adjc)
{
  FUNCNAME("descend_tree_3d");
  int    i, vertex[2], in_vertex[N_VERTICES_3D];
  U_CHAR in_type = l_els[in_el].el_type;

  DEBUG_TEST_EXIT(orig_el != in_el,
	      "Why is orig_el == in_el == %d???\n", orig_el);

  if (in_neigh < 0) {
    vertex[0] = l_els[orig_el].vertex[0];
    vertex[1] = l_els[orig_el].vertex[1];
  } else {
    /* take the face-transformations on periodic walls into
     * account. in_neigh is either 2 or 3.
     */
    vertex[0] = l_els[orig_el].neigh_v[in_neigh][3-in_neigh];
    vertex[1] = l_els[orig_el].neigh_v[in_neigh][3-in_neigh+1];
  }

  DEBUG_TEST_EXIT(vertex[0] > -1,
	      "Oops, unknown vertex 0 on element %d!\n", orig_el);
  DEBUG_TEST_EXIT(vertex[1] > -1,
	      "Oops, unknown vertex 1 on element %d!\n", orig_el);

  /* If necessary, fill vertex information on in_el from the
   * parent. This is necessary in a few exotic cases!
   */

  if(l_els[in_el].parent > -1)
    fill_child_vertices_3d(l_els, l_els[in_el].parent);

  for(i = 0; i < N_VERTICES_3D; i++) {
    in_vertex[i] = l_els[in_el].vertex[i];

    DEBUG_TEST_EXIT(in_vertex[i] > -1,
		"Oops, unknown vertex %d on element %d!\n", i, in_el);
  }

  *n = -1;
  if((vertex[0] == in_vertex[0] && vertex[1] == in_vertex[1]) ||
     (vertex[0] == in_vertex[1] && vertex[1] == in_vertex[0])) {
    *n = 1;

    out_els[0] = in_el;
  }
  else {
    if((vertex[0] == in_vertex[0] && vertex[1] == in_vertex[2]) ||
       (vertex[0] == in_vertex[2] && vertex[1] == in_vertex[0])) {
      *n = 1;

      /* Pass down as much vertex information as possible.                  */
      fill_child_vertices_3d(l_els, in_el);

      out_els[0] = l_els[in_el].child[0];
    }
    else if((vertex[0] == in_vertex[0] && vertex[1] == in_vertex[3]) ||
	    (vertex[0] == in_vertex[3] && vertex[1] == in_vertex[0])) {
      *n = 1;

      fill_child_vertices_3d(l_els, in_el);
      out_els[0] = l_els[in_el].child[0];

      fill_child_vertices_3d(l_els, out_els[0]);
      out_els[0] = l_els[out_els[0]].child[0];
    } 
    else if((vertex[0] == in_vertex[1] && vertex[1] == in_vertex[2]) ||
	    (vertex[0] == in_vertex[2] && vertex[1] == in_vertex[1])) {
      *n = 1;

      fill_child_vertices_3d(l_els, in_el);
      out_els[0] = l_els[in_el].child[1];

      if(in_type == 0) {
	fill_child_vertices_3d(l_els, out_els[0]);
	out_els[0] = l_els[out_els[0]].child[0];
      }
    }
    else if((vertex[0] == in_vertex[1] && vertex[1] == in_vertex[3]) ||
	    (vertex[0] == in_vertex[3] && vertex[1] == in_vertex[1])) {
      *n = 1;

      fill_child_vertices_3d(l_els, in_el);
      out_els[0] = l_els[in_el].child[1];

      if(in_type != 0) {
	fill_child_vertices_3d(l_els, out_els[0]);
	out_els[0] = l_els[out_els[0]].child[0];
      }
    }
    else if((vertex[0] == in_vertex[2] && vertex[1] == in_vertex[3]) ||
	    (vertex[0] == in_vertex[3] && vertex[1] == in_vertex[2])) {
      *n = 2;

      fill_child_vertices_3d(l_els, in_el);
      out_els[0] = l_els[in_el].child[0];
      out_els[1] = l_els[in_el].child[1];

      fill_child_vertices_3d(l_els, out_els[0]);
      fill_child_vertices_3d(l_els, out_els[1]);
      out_els[0] = l_els[out_els[0]].child[1];
      out_els[1] = l_els[out_els[1]].child[1];

      if(in_type == 2) {
	fill_child_vertices_3d(l_els, out_els[0]);
	fill_child_vertices_3d(l_els, out_els[1]);
	out_els[0] = l_els[out_els[0]].child[0];
	out_els[1] = l_els[out_els[1]].child[0];
      }
    }
  }

  /* Calculate adjacencies. */
  for(i = 0; i < *n; i++) {
    if(l_els[out_els[i]].vertex[0] == vertex[0])
      out_adjc[i] = 0;
    else if (l_els[out_els[i]].vertex[0] == vertex[1])
      out_adjc[i] = 1;
    else
      ERROR_EXIT("Nonconforming triangulation!\n");
  }

  return;
}

/****************************************************************************/
/* hier_edge_loop_3d(l_els, el, new_vertex, new_edge)                       */
/* A routine to loop around the refinement edge of "el". We must set the new*/
/* vertex and new edges on all child elements of the elements the loop runs */
/* through. Face and neighbour information is not assigned by this routine. */
/*   We must take care of the adjacency of the new edges relative to the    */
/* starting element "el". (VERTEX 0 - VERTEX 0 or VERTEX 0 - VERTEX 1 on the*/
/* refinement edge).                                                        */
/*   Looping around an edge is ugly since several cases are possible in 3D. */
/* Simple case: We start at "el" and manage to run around completely until  */
/* we are back at el. Ugly case: We encounter a boundary. Then we have to   */
/* start again and loop in the other direction until we hit another         */
/* boundary.                                                                */
/****************************************************************************/

static void hier_edge_loop_3d(LOGICAL_EL_3D *l_els, int el,
			      int new_vertex, int *new_edge)
{
  FUNCNAME("hier_edge_loop_3d");
  int vertex[2], vertices[N_VERTICES_3D];
  int i, j, k, old_el, neigh, opp_v, edge_no = 0, wall;
  int n, sub_neigh[2], adjc[2];
  int child[2];

  DEBUG_TEST_EXIT(l_els[el].vertex[0] > -1,
	      "Oops, unknown vertex 0 on element %d!\n", el);
  DEBUG_TEST_EXIT(l_els[el].vertex[1] > -1,
	      "Oops, unknown vertex 1 on element %d!\n", el);

  for (i = 0; i < 2; i++)
    vertex[i] = l_els[el].vertex[i];
  
/*--------------------------------------------------------------------------*/
/*  first look for all neighbours in one direction until a boundary is      */
/*  reached :-( or we are back to el :-)                                    */
/*--------------------------------------------------------------------------*/

  edge_no = 0;
  old_el  = el;

  wall    = next_el[edge_no][0];
  neigh   = l_els[el].neigh[wall];
  opp_v   = l_els[el].opp_v[wall];
  /* do not loop across periodic walls */
  if (neigh > -1 && l_els[el].face[wall] != l_els[neigh].face[opp_v]) {
    neigh = -1;
  }

  while (neigh > -1 && neigh != el) {
    for (j = 0; j < N_VERTICES_3D; j++)
      vertices[j] = l_els[neigh].vertex[j];
    for (j = 0; j < N_VERTICES_3D; j++)
      if (vertices[j] == vertex[0]) break;
    for (k = 0; k < N_VERTICES_3D; k++)
      if (vertices[k] == vertex[1]) break;

    DEBUG_TEST_EXIT(j < N_VERTICES_3D && k < N_VERTICES_3D,
		"Did not find the edge again!\n");

    edge_no = edge_of_vertices_3d[j][k];

    descend_tree_3d(l_els, el, neigh, -1, &n, sub_neigh, adjc);

    for (i = 0; i < n; i++) {
      child[0] = l_els[sub_neigh[i]].child[0];
      child[1] = l_els[sub_neigh[i]].child[1];

      /* Here we assign the necessary information: New vertices and edges.  */
      if (new_vertex > -1)
	l_els[child[0]].vertex[3] = l_els[child[1]].vertex[3] = new_vertex;

      if (new_edge[0] > -1) {
	l_els[child[adjc[i]]].edge[2] = new_edge[0];
	l_els[child[1-adjc[i]]].edge[2] = new_edge[1];
      }
    }

    if (sub_neigh[0] == el || (n == 2 && sub_neigh[1] == el)) {
      /* This can happen if the neighbour-pointer of EL points one
       * level up. In this case the neighbourhood information is not
       * transitive and we would generate an infinite loop here.
       */
      neigh = el;
      break;
    }

    if ((wall = next_el[edge_no][0]) == opp_v) {
      wall = next_el[edge_no][1];
    }
    old_el = neigh;
    opp_v = l_els[neigh].opp_v[wall];
    neigh = l_els[neigh].neigh[wall];
    /* do not loop across periodic walls */
    if (neigh > -1 && l_els[old_el].face[wall] != l_els[neigh].face[opp_v]) {
      neigh = -1;
    }
  }

  if (neigh < 0) {
/*--------------------------------------------------------------------------*/
/*  while looping around the edge the domain's boundary was reached. Now,   */
/*  loop in the other direction to the domain's boundary		    */
/*--------------------------------------------------------------------------*/

    edge_no = 0;
    old_el  = el;
    wall    = next_el[edge_no][1];
    neigh   = l_els[el].neigh[wall];
    opp_v   = l_els[el].opp_v[wall];
    /* do not loop across periodic walls */
    if (neigh > -1 && l_els[el].face[wall] != l_els[neigh].face[opp_v]) {
      neigh = -1;
    }

    while (neigh > -1) {
      for (j = 0; j < N_VERTICES_3D; j++)
	vertices[j] = l_els[neigh].vertex[j];
      for (j = 0; j < N_VERTICES_3D; j++)
	if (vertices[j] == vertex[0]) break;
      for (k = 0; k < N_VERTICES_3D; k++)
	if (vertices[k] == vertex[1]) break;

      DEBUG_TEST_EXIT(j < N_VERTICES_3D && k < N_VERTICES_3D,
		  "Did not find the edge again!\n");

      edge_no = edge_of_vertices_3d[j][k];
    
      descend_tree_3d(l_els, el, neigh, -1, &n, sub_neigh, adjc);

      for(i = 0; i < n; i++) {
	child[0] = l_els[sub_neigh[i]].child[0];
	child[1] = l_els[sub_neigh[i]].child[1];
	
	/* Here we assign the necessary information: New vertices and edges.*/
	if(new_vertex > -1)
	  l_els[child[0]].vertex[3] = l_els[child[1]].vertex[3] = new_vertex;
	
	if(new_edge[0] > -1) {
	  l_els[child[adjc[i]]].edge[2] = new_edge[0];
	  l_els[child[1-adjc[i]]].edge[2] = new_edge[1];
	}
      }

      if (sub_neigh[0] == el || (n == 2 && sub_neigh[1] == el)) {
	/* This can happen if the neighbour-pointer of EL points one
	 * level up. In this case the neighbourhood information is not
	 * transitive and we would generate an infinite loop here.
	 */
	neigh = el;
	break;
      }

      if ((wall = next_el[edge_no][0]) == opp_v) {
	wall = next_el[edge_no][1];
      }
      
      old_el = neigh;
      opp_v  = l_els[neigh].opp_v[wall];
      neigh  = l_els[neigh].neigh[wall];
      /* do not loop across periodic walls */
      if (neigh > -1 && l_els[old_el].face[wall] != l_els[neigh].face[opp_v]) {
	neigh = -1;
      }
    }
  }

  return;
}


/****************************************************************************/
/* fill_connectivity_rec_3d(l_els, current_el, v_index, e_index, f_index,   */
/*                          current_level, desired_level):                  */
/* This routine is the workhorse for filling a LOGICAL_EL array. It sets    */
/* vertex, edge, face, and neighbour indices on the children of             */
/* "current_el". It is not a simple recursion, but is called for each       */
/* refinement level (see below). Only thus are we guaranteed to have correct*/
/* neighbourhood information which is vital for setting edge, face, 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_3d(LOGICAL_EL_3D *l_els, int current_el,
				     int *v_index, int *e_index, int *f_index,
				     int current_level, int desired_level)
{
  FUNCNAME("fill_connectivity_rec_3d");
  int    i_child, child_ind[2], *opp_v, neigh, adjc;
  int    i, j, neigh_child[2], new_vertex = -1, new_edge[2] = {-1,-1};
  int    i_neigh, j_neigh, sub_opp_v, sub_neigh, sub_type;
  int    el_type = (int)l_els[current_el].el_type;


  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) {

      /* Fill opposite vertex information.                                  */
      opp_v = l_els[current_el].opp_v;

      /* Fill face information. Two faces are handed down from parents.     */
      l_els[child_ind[0]].face[3] = l_els[current_el].face[1];
      l_els[child_ind[1]].face[3] = l_els[current_el].face[0];

      /* One face is allocated where the two children of "current_el" meet. */
      l_els[child_ind[0]].face[0] =
	l_els[child_ind[1]].face[0] = *f_index;
      
      *f_index += 1;

      /* Four remaining faces are shared with exterior neighbours and might */
      /* already have been set by these neighbours.                         */
      if (l_els[child_ind[0]].face[1] < 0) {
	l_els[child_ind[0]].face[n_child_face_3d[el_type][0][0]] = *f_index;
	l_els[child_ind[1]].face[n_child_face_3d[el_type][1][0]] = *f_index +1;
	
	*f_index += 2;
      }
      if (l_els[child_ind[0]].face[2] < 0) {
	l_els[child_ind[0]].face[n_child_face_3d[el_type][0][1]] = *f_index;
	l_els[child_ind[1]].face[n_child_face_3d[el_type][1][1]] = *f_index +1;
	
	*f_index += 2;
      }
      
      /* Fill edge information. Five edges of "current_el" are passed to    */
      /* children -- all edges except the refinement edge. Take care of     */
      /* different local indices depending on el_type.                      */
      
      l_els[child_ind[0]].edge[0] = l_els[current_el].edge[1];
      l_els[child_ind[0]].edge[1] = l_els[current_el].edge[2];
      l_els[child_ind[0]].edge[3] = l_els[current_el].edge[5];
      
      l_els[child_ind[1]].edge[3] = l_els[current_el].edge[5];
      if (el_type == 0) {
	l_els[child_ind[1]].edge[0] = l_els[current_el].edge[4];
	l_els[child_ind[1]].edge[1] = l_els[current_el].edge[3];
      } else {
	l_els[child_ind[1]].edge[0] = l_els[current_el].edge[3];
	l_els[child_ind[1]].edge[1] = l_els[current_el].edge[4];
      }

      /* Two subedges of the refinement edge are allocated. These might also*/
      /* have been allocated by neighbour elements and passed around by     */
      /* hier_edge_loop_3d().                                               */

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

	new_edge[0] = *e_index;
	new_edge[1] = *e_index + 1;
	*e_index += 2;
      }

      /* Four edges are shared with neighbour elements. We spread this      */
      /* information below.                                                 */
      
      if(l_els[child_ind[0]].edge[5] < 0) {
	l_els[child_ind[0]].edge[5] = *e_index;
	
	if(el_type == 0)
	  l_els[child_ind[1]].edge[4] = *e_index;
	else
	  l_els[child_ind[1]].edge[5] = *e_index;
	
	*e_index += 1;
      }
      if(l_els[child_ind[0]].edge[4] < 0) {
	l_els[child_ind[0]].edge[4] = *e_index;
	
	if(el_type == 0)
	  l_els[child_ind[1]].edge[5] = *e_index;
	else
	  l_els[child_ind[1]].edge[4] = *e_index;
	
	*e_index += 1;
      }
      
      /* Fill vertex information. All "current_el" vertices are passed
       * to children. Also pass down the wall-transformations down to
       * the children.
       */
      fill_child_vertices_3d(l_els, current_el);

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

      new_vertex = -1;
      if(l_els[child_ind[0]].vertex[3] < 0) {
	l_els[child_ind[0]].vertex[3] =
	  l_els[child_ind[1]].vertex[3] = *v_index;
	
	new_vertex = *v_index;
	*v_index += 1;
      }
      
      /* Set neighbourhood information among children of "current_el".      */
      
      l_els[child_ind[0]].neigh[0] = child_ind[1];
      l_els[child_ind[1]].neigh[0] = child_ind[0];
      l_els[child_ind[0]].opp_v[0] =
	l_els[child_ind[1]].opp_v[0] = 0;

      /* Now spread neighbourhood info, edges, and faces to the direct      */
      /* neighbours. The subedges of the "current_el" refinement edge and   */
      /* the new vertex are special: we must loop around the refinement edge*/
      /* to set these.                                                      */
      
      for(i = 0; i < N_NEIGH_3D; i++) {
	int is_periodic;

	neigh = l_els[current_el].neigh[i];
	
	if (neigh < 0) {
	  continue;
	}

	/* If current_el and neigh are periodic neighbours (i.e. they
	 * do not share a wall) we do not spread edge and face
	 * indices to the neighbour children.
	 */
	is_periodic = l_els[current_el].face[i] != l_els[neigh].face[opp_v[i]];

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

	      l_els[child_ind[1-i]].opp_v[3] = 3;
	      l_els[neigh_child[1-opp_v[i]]].opp_v[3] = 3;

	      if (!is_periodic) {
		l_els[neigh_child[1-opp_v[i]]].face[3] = 
		  l_els[child_ind[1-i]].face[3];
	      }
	    } 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 3 contains the refinement edge.
	       */
	      l_els[child_ind[1-i]].neigh[3] = neigh;
	      l_els[child_ind[1-i]].opp_v[3] = opp_v[i];
	    }
	  }
	} else /*if (l_els[neigh].child[0] > -1) neighbour must have childs */ {
	  /* These are faces which contain the refinement edge. We
	   * might need to descend one level on the neighbouring
	   * elements to find child elements corresponding to child
	   * elements of "current_el". The remaining code was adapted
	   * from refine_3d.c
	   */

	  descend_tree_3d(l_els, current_el, neigh, is_periodic ? i : -1,
			  &j, &sub_neigh, &adjc);

	  DEBUG_TEST_EXIT(j == 1, "Found two sub elements!\n");

	  if(sub_neigh == neigh) 
	    sub_opp_v = opp_v[i];
	  else {
	    DEBUG_TEST_EXIT((l_els[neigh].child[0]==sub_neigh) ||
			    (l_els[neigh].child[1]==sub_neigh), 
			    "Oops, nonconforming triangulation!\n");
	    
	    sub_opp_v = 3;
	  }

	  sub_type = (int)l_els[sub_neigh].el_type;
	    
	  neigh_child[0] = l_els[sub_neigh].child[0];
	  neigh_child[1] = l_els[sub_neigh].child[1];
	    
	  for (i_child = 0; i_child < 2; i_child++) {
	    j = adjacent_child_3d[adjc][i_child];
	    
	    i_neigh = n_child_face_3d[el_type][i_child][i-2];
	    j_neigh = n_child_face_3d[sub_type][j][sub_opp_v-2];
	      
	    l_els[child_ind[i_child]].neigh[i_neigh] = neigh_child[j];
	    l_els[neigh_child[j]].neigh[j_neigh] = child_ind[i_child];

	    l_els[child_ind[i_child]].opp_v[i_neigh] = j_neigh;
	    l_els[neigh_child[j]].opp_v[j_neigh] = i_neigh;
	      
	    if (!is_periodic) {
	      /* do not share edge/face numbers across periodic walls */
	      l_els[neigh_child[j]].edge[n_child_edge_3d[sub_type][j][sub_opp_v-2]] =
		l_els[child_ind[i_child]].edge[n_child_edge_3d[el_type][i_child][i-2]];
	      
	      l_els[neigh_child[j]].face[j_neigh] =
		l_els[child_ind[i_child]].face[i_neigh];
	    } else {
	      /* insert the new vertex into the face-transformations
	       * of the children of the neighbour. Because the
	       * neighbour _has_ been refined we know that the new
	       * vertex also has local number 3 on the children of the
	       * neighbour, this implies it has index 1 (j_neigh == 1)
	       * or 0 (j_neigh == 2) w.r.t. to the face-transformation
	       * stored in neigh_v (cyclic indexing of the vertices on
	       * the face).
	       */
	      l_els[neigh_child[j]].neigh_v[j_neigh][j_neigh % 2] =
		new_vertex >= 0 ? new_vertex : l_els[child_ind[0]].vertex[3];  
	    }
	  }
	}
      }

      /* Now that all this is done we loop around the refinement edge.      */

      if(new_vertex > -1 || new_edge[0] > -1)
	hier_edge_loop_3d(l_els, current_el, new_vertex, new_edge);
    }

    /* Now recurse to the two children.                                     */
    if(current_level < desired_level) {
      fill_connectivity_rec_3d(l_els, child_ind[0], v_index, e_index, f_index,
			       current_level + 1, desired_level);
      fill_connectivity_rec_3d(l_els, child_ind[1], v_index, e_index, f_index,
			       current_level + 1, desired_level);
    }
  }
  
  return;
}


/****************************************************************************/
/* macro_edge_loop_3d(l_els, macro_els, el, el_edge_no, e_index):           */
/* We loop around the given edge using the same principle as above. Here we */
/* are on a macro triangulation and thus only set the l_els[].edge[] entry. */
/****************************************************************************/

static void macro_edge_loop_3d(LOGICAL_EL_3D *l_els, MACRO_EL *macro_els,
			       int el, int el_edge_no, int e_index)
{
  FUNCNAME("macro_edge_loop_3d"); 
  int              vertex[2], vertices[N_VERTICES_3D];
  int              j, k, neigh, opp_v, edge_no, wall;

  edge_no = el_edge_no;

  for (j = 0; j < 2; j++)
    vertex[j] = l_els[el].vertex[vertex_of_edge_3d[edge_no][j]];
  
/*--------------------------------------------------------------------------*/
/*  first look for all neighbours in one direction until a boundary is      */
/*  reached :-( or we are back to el :-)                                    */
/*--------------------------------------------------------------------------*/

  wall = next_el[edge_no][0];
  opp_v = l_els[el].opp_v[wall];
  neigh = l_els[el].neigh[wall];
  /* Do not loop across periodic walls */
  if (neigh > -1 && l_els[el].face[wall] != l_els[neigh].face[opp_v]) {
    neigh = -1;
  }

  while (neigh > -1 && neigh != el) {
    for (j = 0; j < N_VERTICES_3D; j++)
      vertices[j] = l_els[neigh].vertex[j];
    for (j = 0; j < N_VERTICES_3D; j++)
      if (vertices[j] == vertex[0]) break;
    for (k = 0; k < N_VERTICES_3D; k++)
      if (vertices[k] == vertex[1]) break;

    DEBUG_TEST_EXIT(j < N_VERTICES_3D && k < N_VERTICES_3D,
		"Did not find the edge again!\n");

    edge_no = edge_of_vertices_3d[j][k];

    l_els[neigh].edge[edge_no] = e_index;

    if ((wall = next_el[edge_no][0]) == opp_v) {
      wall = next_el[edge_no][1];
    }
    opp_v = l_els[neigh].opp_v[wall];
    /* do not loop across periodic walls */
    if (l_els[neigh].neigh[wall] > -1 &&
	l_els[neigh].face[wall] == l_els[l_els[neigh].neigh[wall]].face[opp_v])
      neigh = l_els[neigh].neigh[wall];
    else
      neigh = -1;
  }

  if (neigh < 0) {
/*--------------------------------------------------------------------------*/
/*  while looping around the edge the domain's boundary was reached. Now,   */
/*  loop in the other direction to the domain's boundary		    */
/*--------------------------------------------------------------------------*/
    edge_no = el_edge_no;

    wall = next_el[edge_no][1];
    opp_v = l_els[el].opp_v[wall];
    neigh = l_els[el].neigh[wall];
    /* do not loop across periodic walls */
    if (neigh > -1 && l_els[el].face[wall] != l_els[neigh].face[opp_v]) {
      neigh = -1;
    }

    while (neigh > -1) {
      for (j = 0; j < N_VERTICES_3D; j++)
	vertices[j] = l_els[neigh].vertex[j];
      for (j = 0; j < N_VERTICES_3D; j++)
	if (vertices[j] == vertex[0]) break;
      for (k = 0; k < N_VERTICES_3D; k++)
	if (vertices[k] == vertex[1]) break;

      DEBUG_TEST_EXIT(j < N_VERTICES_3D && k < N_VERTICES_3D,
		  "Did not find the edge again!\n");

      edge_no = edge_of_vertices_3d[j][k];

      l_els[neigh].edge[edge_no] = e_index;

      if ((wall = next_el[edge_no][0]) == opp_v) {
	wall = next_el[edge_no][1];
      }
      opp_v = l_els[neigh].opp_v[wall];
      /* do not loop across periodic walls */
      if (l_els[neigh].neigh[wall] > -1 &&
	  l_els[neigh].face[wall]
	  ==
	  l_els[l_els[neigh].neigh[wall]].face[opp_v]) {
	neigh = l_els[neigh].neigh[wall];
      } else {
	neigh = -1;
      }
    }
  }

  return;
}


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

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

  l_els = MEM_ALLOC(n_els, LOGICAL_EL_3D);

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

  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_NEIGH_3D; j++) {
	if(mel->neigh[j]) {
	  l_els[i].neigh[j] = mel->neigh[j]->index;
	  l_els[i].opp_v[j]  = mel->opp_vertex[j];
	} else {
	  l_els[i].neigh[j] = -1;
	  l_els[i].opp_v[j]  = -1;
	}
      }
      
      for(j = 0; j < N_VERTICES_3D; j++)
	l_els[i].vertex[j] = (REAL_D *)(mel->coord[j]) - coords;
      
      l_els[i].el      = mel->el;
      l_els[i].el_type = mel->el_type;
    } else {
      for(j = 0; j < N_NEIGH_3D; j++) {
	l_els[i].neigh[j]        =
	  l_els[i].opp_v[j]      =
	  l_els[i].neigh_v[j][0] = -1;
      }
      
      for(j = 0; j < N_VERTICES_3D; j++)
	l_els[i].vertex[j] = -1;
    }

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

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

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

  /* Note: to be able to identify periodic walls we first set the face
   * information. Periodic walls are then identified by differing face
   * numbers, i.e. we have hit a periodic boundary if
   *
   * l_els[el].face[j] !=
   * l_els[l_els[el].neigh[j]].face[l_els[el].opp_v[j]]
   */

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

    /* Possibly fill in the face transformations, in terms of global
     * vertex indices.
     */
    for (j = 0; j < N_NEIGH_3D; j++) {
      if ((neigh = l_els[i].neigh[j]) > -1) {
	if (mel->neigh_vertices[j][0] > -1) {
	  l_els[i].neigh_v[j][0] =
	    l_els[neigh].vertex[mel->neigh_vertices[j][0]];
	  l_els[i].neigh_v[j][1] =
	    l_els[neigh].vertex[mel->neigh_vertices[j][1]];
	  l_els[i].neigh_v[j][2] =
	    l_els[neigh].vertex[mel->neigh_vertices[j][2]];
	} else {
	  l_els[i].neigh_v[j][0] = -1;
	}
      }
    }

    for (j = 0; j < N_FACES_3D; j++) {
      if (l_els[i].face[j] < 0) {
	l_els[i].face[j]  = f_index;
	
	neigh = l_els[i].neigh[j];
	if (neigh > -1 && l_els[i].neigh_v[j][0] < 0) {
	  opp_v = l_els[i].opp_v[j];
	  l_els[neigh].face[opp_v] = f_index;
	}

	f_index++;
      }
    }
  }
  
  for(i = 0; i < mesh->n_macro_el; i++) {
    for(j = 0; j < N_EDGES_3D; j++) {
      if(l_els[i].edge[j] < 0) {
	l_els[i].edge[j]  = e_index;

	macro_edge_loop_3d(l_els, mesh->macro_els, i, j, 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;
  }
  if (n_macro_edges) {
    *n_macro_edges = e_index;
  }

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

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

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

  for(i = 1; i <= max_level; i++)
    for(j = 0; j < mesh->n_macro_el; j++)
      fill_connectivity_rec_3d(l_els, j, &v_index, &e_index, &f_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_3D; 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_3D; j++)
      DEBUG_TEST_EXIT(l_els[i].edge[j] > -1,
		  "Error while checking element %d, edge %d==-1!\n", i, j);
    for(j = 0; j < N_FACES_3D; j++)
      DEBUG_TEST_EXIT(l_els[i].face[j] > -1,
		  "Error while checking element %d, face %d==-1!\n", i, j);
  }
#endif

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

  return l_els;
}

/* Here we assign each orbit of the action of the group of
 * wall-transformations on the set of edges and the set of vertices a
 * unique number and compute the mapping vertex/edge -> orbit. This
 * enables us to properly identify DOFs across periodic walls.
 */
static void compute_periodic_orbits_3d(MESH *mesh,
				       LOGICAL_EL_3D *l_els,
				       int n_hier_elements,
				       int n_vertices,
				       int n_hier_edges,
				       int n_macro_vertices, int n_macro_edges,
				       int change_dofs[N_NODE_TYPES],
				       int *wt_v_orbit_map,
				       int *n_wt_v_orbits,
				       int *wt_e_orbit_map,
				       int *n_wt_e_orbits)
{
  int (*wall_trafos)[N_VERTICES(DIM_MAX-1)][2];
  int edges[n_macro_edges][2];
  int el, le, ge;
  int nwt;
  int i, w;
  int (*hier_edge_orbits)[3]; /* Two more edge- and one vertex-orbit
			       * is (potentially) generated by each
			       * edge orbit.
			       */
  int heo_allocated; /* size of memory allocate for hier_edge_orbits */
  int n_v_orbs = 0, n_e_orbs;
  int per_n_vertices;

  /* first compute a representation of the edges of the macro
   * triangulation as pairs of vertices. Maybe move this to
   * fill_logical_els_3d(), where it could be done more efficiently.
   */
  for (el = 0; el < mesh->n_macro_el; el++) {
    for (le = 0; le < N_EDGES_3D; le++) {
      ge = l_els[el].edge[le];
      edges[ge][0] = l_els[el].vertex[vertex_of_edge_3d[le][0]];
      edges[ge][1] = l_els[el].vertex[vertex_of_edge_3d[le][1]];
    }
  }
  
  /* Compute the generators of the group of wall transformations */
  nwt = _AI_compute_macro_wall_trafos(mesh, &wall_trafos);

  /* Compute the vertex/edge -> orbit mapping for the macro
   * triangulation
   */
  if (change_dofs[VERTEX]) {
    for (i = 0; i < n_vertices; i++) {
      wt_v_orbit_map[i] = -1;
    }
    per_n_vertices = n_macro_vertices;
    n_v_orbs =
      _AI_wall_trafo_vertex_orbits(mesh->dim, wall_trafos, nwt,
				   wt_v_orbit_map, &per_n_vertices);
    
  }
  
  for (i = 0; i < n_hier_edges; i++) {
    wt_e_orbit_map[i] = -1;
  }

  n_e_orbs =
    _AI_wall_trafo_edge_orbits(wall_trafos, nwt,
			       wt_e_orbit_map, edges, n_macro_edges);
  
  MEM_FREE(wall_trafos, nwt*sizeof(*wall_trafos), char);

  /* Then compute the edge/vertex -> orbit mapping for the remaining
   * vertices/edges. The point is that each "edge orbit" potentially
   * generates two more edge orbits and one additional vertex orbit
   * (for the center of the edge).
   *
   * We allocate too much memory, but so what.
   */
  hier_edge_orbits = (int (*)[3])MEM_ALLOC(3 * (n_e_orbs + 100), int);
  heo_allocated = n_e_orbs + 100;
  for (i = 0; i < heo_allocated; i++) {
    hier_edge_orbits[i][0] = -1;
  }

  /* Now traverse the mesh and compute how the hierarchical
   * vertices/edges map to edge/vertex orbits. This exploits the
   * fact that the ordering of elements in l_els still reflects the
   * binary tree structure of the mesh; we will never reach an
   * element unless we have alread hit its parent.
   */
  for (el = 0; el < n_hier_elements; el++) {
    int orb, neigh;
      
    if (l_els[el].child[0] < 0) {
      continue;
    }
      
    /* Edge number 0 is bisected, the new edges on the children have
     * local number 2
     */
    ge = l_els[el].edge[0];
    if ((orb = wt_e_orbit_map[ge]) == -1) { /* This edge is a fix point */
      continue;
    }

    /* We have at most 4 new edge orbits, two generated by a periodic
     * edge (edge 0), and 1 for each periodic wall (wall 2 or 3)
     */
    if (n_e_orbs + 4 > heo_allocated) {
      hier_edge_orbits = (int (*)[3])MEM_REALLOC(hier_edge_orbits,
						 3*heo_allocated,
						 3*(heo_allocated+100), int);
      for (i = heo_allocated; i < heo_allocated; i++) {
	hier_edge_orbits[i][0] = -1;
      }
      heo_allocated += 100;
    }

    if (hier_edge_orbits[orb][0] == -1) {
      /* Generate new orbit numbers if this edge orbit has not done
       * so before.
       */
      hier_edge_orbits[orb][0] = n_e_orbs;
      hier_edge_orbits[n_e_orbs++][0] = -1;
      hier_edge_orbits[orb][1] = n_e_orbs;
      hier_edge_orbits[n_e_orbs++][0] = -1;
      hier_edge_orbits[orb][2] = n_v_orbs ++;
    }

    if (change_dofs[VERTEX]) {
      /* Assign the new vertex the proper orbit number */
      wt_v_orbit_map[l_els[l_els[el].child[0]].vertex[3]] =
	hier_edge_orbits[orb][2];
    }

    /* For edges we have to take into account that the orientations
     * of neighbouring edges need not match; we use the global
     * vertex numbers to cope with this: the edge which shares the
     * smaller global vertex number with the parent edge is assigned
     * hier_edge_orbits[orb][0], the other one ...[1]
     */
    if (l_els[el].vertex[0] < l_els[el].vertex[1]) {
      wt_e_orbit_map[l_els[l_els[el].child[0]].edge[2]] =
	hier_edge_orbits[orb][0];
      wt_e_orbit_map[l_els[l_els[el].child[1]].edge[2]] =
	hier_edge_orbits[orb][1];
    } else {
      wt_e_orbit_map[l_els[l_els[el].child[0]].edge[2]] =
	hier_edge_orbits[orb][1];
      wt_e_orbit_map[l_els[l_els[el].child[1]].edge[2]] =
	hier_edge_orbits[orb][0];
    }

    /* There are still more two-point orbits; at least one of the
     * walls containing edge 0 is also mapped to another wall.
     *
     * So, if wall number 2 of the parent is subject to a wall
     * transformation, then edge 5 of child 1 will also be mapped to
     * the respective "periodic edge", if wall 3 of the parent is
     * subject to a wall transformation, then edge 4 of the child 0
     * will be mapped also.
     */
    for (w = 2; w <= 3; w++) {
      if ((neigh = l_els[el].neigh[w])) {
	int opp_v = l_els[el].opp_v[w];
	int neigh_face = l_els[neigh].face[opp_v];
	if (neigh_face != l_els[el].face[w]) {
	  wt_e_orbit_map[l_els[l_els[el].child[0]].edge[7-w]] = n_e_orbs;
	  /* The opp_v value on the neighbour must be either 2 or 3
	   */
	  if (opp_v == 2) {
	    wt_e_orbit_map[l_els[l_els[neigh].child[0]].edge[5]] =
	      n_e_orbs;
	  } else {
	    wt_e_orbit_map[l_els[l_els[neigh].child[0]].edge[4]] =
	      n_e_orbs;
	  }
	  hier_edge_orbits[n_e_orbs++][0] = -1;
	}
      }
    }
  }

  /* This should be it ... */
  MEM_FREE(hier_edge_orbits, 3 * heo_allocated, int);

  *n_wt_e_orbits = n_e_orbs;
  if (change_dofs[VERTEX]) {
    *n_wt_v_orbits = n_v_orbs;
  }
}

/****************************************************************************/
/* adjust_dofs_and_dof_ptrs_3d(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_3d(MESH *mesh, DOF_ADMIN *new_admin,
					int old_n_node_el,
					int *old_n_dof, int *old_node)
{
  FUNCNAME("adjust_dofs_and_dof_ptrs_3d");
  DOF            **old_dof_ptr;
  int            i, n, n_elements = 0, n_hier_elements;
  int            n_vertices, n_edges = 0, n_hier_edges;
  int            n_faces = 0, n_hier_faces, node;
  int            change_dofs[N_NODE_TYPES] = {0};
  EL             *el;
  TRAVERSE_STACK *stack = get_traverse_stack();
  const EL_INFO  *el_info = NULL;
  LOGICAL_EL_3D  *l_els = NULL;
  DOF            **new_vertex_dofs = NULL;
  DOF            **new_edge_dofs = NULL;
  DOF            **new_face_dofs = NULL;
  int            is_periodic;
  /* For periodic meshes we need the orbits of the vertices of the
   * macro-triangulation
   */
  int            n_macro_vertices = 0;
  int            *wt_v_orbit_map = NULL;
  int            n_wt_v_orbits = 0;
  DOF            **vertex_twin_dofs = NULL;
  /* And in 3d we need also the orbits of the edges of the
   * macro-triangulation
   */
  int            n_macro_edges = 0;
  int            *wt_e_orbit_map = NULL;
  int            n_wt_e_orbits = 0;
  DOF            **edge_twin_dofs = NULL;
  /* counts for the number of sub-simplexes on the periodic mesh */
  int            per_n_vertices = 0;
  int            per_n_edges    = 0;
  int            per_n_faces    = 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_3D; 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_3D; i++)
	  el->dof[mesh->node[EDGE] + i] = old_dof_ptr[old_node[EDGE] + i];
      if(old_n_dof[FACE])
	for(i = 0; i < N_FACES_3D; i++)
	  el->dof[mesh->node[FACE] + i] = old_dof_ptr[old_node[FACE] + 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. */

  for(n = 0; n < N_NODE_TYPES; n++)
    if(mesh->n_dof[n] > old_n_dof[n])
      change_dofs[n] = 1;

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

  l_els = fill_logical_els_3d(mesh, &n_hier_elements, 
			      &n_vertices, &n_hier_edges, &n_hier_faces,
			      &n_macro_vertices,
			      &n_macro_edges);

  /* 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 *);
  if(change_dofs[FACE])
    new_face_dofs = MEM_CALLOC(n_hier_faces, DOF *);

  if (is_periodic && (change_dofs[VERTEX] || change_dofs[EDGE])) {

    if (change_dofs[VERTEX]) {
      wt_v_orbit_map = MEM_ALLOC(n_vertices, int);
    }
    wt_e_orbit_map = MEM_ALLOC(n_hier_edges, int);

    compute_periodic_orbits_3d(mesh, l_els,
			       n_hier_elements, n_vertices, n_hier_edges,
			       n_macro_vertices, n_macro_edges,
			       change_dofs,
			       wt_v_orbit_map, &n_wt_v_orbits,
			       wt_e_orbit_map, &n_wt_e_orbits);

    edge_twin_dofs = MEM_CALLOC(n_wt_e_orbits, DOF *);
    if (change_dofs[VERTEX]) {
      vertex_twin_dofs = MEM_CALLOC(n_wt_v_orbits, DOF *);
    }
  }

  /* First pass: Change all DOFs on the leaf level. FACE and 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_3D; i++) {
	  int vertex = l_els[n].vertex[i];

	  if(!new_vertex_dofs[vertex]) {
	    DOF *twin_dof = NULL;
	    int orb_num = -1;
	    
	    if (is_periodic) {
	      if ((orb_num = wt_v_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_3D; i++) {
	  int edge = l_els[n].edge[i];
	  
	  if(!new_edge_dofs[edge]) {
	    DOF *twin_dof = NULL;
	    int orb_num = -1;
	    
	    n_edges++;

	    if (is_periodic) {
	      if ((orb_num = wt_e_orbit_map[edge]) >= 0) {
		twin_dof = edge_twin_dofs[orb_num];
		if (twin_dof) {
		  --per_n_edges;
		}
	      }		
	    }

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

	    if (orb_num >= 0 && !twin_dof) {
	      edge_twin_dofs[orb_num] = new_edge_dofs[edge];
	    }
	  }
	  
	  el->dof[node + i] = new_edge_dofs[edge];
	}
      }

      if(change_dofs[FACE]) {
	node = mesh->node[FACE];
	
	for(i = 0; i < N_FACES_3D; i++) {
	  int face = l_els[n].face[i];

	  if(!new_face_dofs[face]) {
	    DOF *twin_dof = NULL;
	    
	    n_faces++;

	    if (is_periodic) {
	      int neigh;

	      neigh = l_els[n].neigh[i];
	      if (neigh >= 0) {
		int neigh_face = l_els[neigh].face[l_els[n].opp_v[i]];

		if (neigh_face != face) {
		  twin_dof = new_face_dofs[neigh_face];
		  if (twin_dof) {
		    --per_n_faces;
		  }
		}
	      }
	    }

	    new_face_dofs[face] = 
	      transfer_dofs(mesh, new_admin, el->dof[node + i], FACE,
			    false, twin_dof);
	  }
	  
	  el->dof[node + i] = new_face_dofs[face];
	}
      }
    }
  }

  /* Second pass: Change all DOFs which were not yet set. These will be
   * coarse DOFs in the case of EDGE/FACE/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];

	/* All VERTEX DOFs must have been set on the leaf level.            */
	for(i = 0; i < N_VERTICES_3D; i++) {
	  DEBUG_TEST_EXIT(new_vertex_dofs[l_els[n].vertex[i]],
		      "Why is this VERTEX DOF not set on leaf level?\n");
	  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_3D; i++) {
	  int edge = l_els[n].edge[i];

	  if(!new_edge_dofs[edge]) {
	    DOF *twin_dof = NULL;
	    int orb_num = -1;
	  
	    if (is_periodic) {
	      if ((orb_num = wt_e_orbit_map[edge]) >= 0) {
		twin_dof = edge_twin_dofs[orb_num];
	      }		
	    }
	    
	    new_edge_dofs[edge] = 
	      transfer_dofs(mesh, new_admin, el->dof[node + i], EDGE,
			    true, twin_dof);

	    if (orb_num >= 0 && !twin_dof) {
	      edge_twin_dofs[orb_num] = new_edge_dofs[edge];
	    }
	  }

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

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

	for(i = 0; i < N_FACES_3D; i++) {
	  int face = l_els[n].face[i];
	  
	  if(!new_face_dofs[face]) {
	    DOF *twin_dof = NULL;
	    
	    if (is_periodic) {
	      int neigh;

	      neigh = l_els[n].neigh[i];
	      if (neigh >= 0) {
		int neigh_face = l_els[neigh].face[l_els[n].opp_v[i]];

		if (neigh_face != face) {
		  twin_dof = new_face_dofs[neigh_face];
		}
	      }
	    }
	    
	    new_face_dofs[face] = 
	      transfer_dofs(mesh, new_admin, el->dof[node + i], FACE,
			    true, twin_dof);
	  }

	  el->dof[node + i] = new_face_dofs[face];
	}
      }
    }
  }

  /*  A few checks and settings.                                            */

  TEST_EXIT(n_elements == mesh->n_elements,
	    "Did not count correct number of "
	    "leaf elements in mesh: %d / %d (mesh/counted)!\n",
	    mesh->n_elements, 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 (mesh/counted)!\n",
	      mesh->n_vertices, 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 (mesh/counted)!\n",
		mesh->n_edges, n_edges);
    } else {
      mesh->n_edges = n_edges;
    }
  }
  
  if(change_dofs[FACE]) {
    if(mesh->n_faces > -1) {
      TEST_EXIT(n_faces == mesh->n_faces,
		"Did not count correct number of "
		"leaf faces in mesh: %d / %d (mesh/counted)!\n",
		mesh->n_faces, n_faces);
    } else {
      mesh->n_faces = n_faces;
    }
  }
  
  if (is_periodic) {
    per_n_vertices += n_vertices;
    per_n_edges    += n_edges;
    per_n_faces    += n_faces;

    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 "
		  "periodic vertices in mesh: %d / %d (mesh/counted)!\n",
		  mesh->per_n_vertices, 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 "
		  "periodic leaf edges in mesh: %d / %d (mesh/counted)!\n",
		  mesh->per_n_edges, per_n_edges);
      } else {
	mesh->per_n_edges = per_n_edges;
      }
    }
  
    if(change_dofs[FACE]) {
      if(mesh->per_n_faces > -1) {
	TEST_EXIT(per_n_faces == mesh->per_n_faces,
		  "Did not count correct number of "
		  "periodic leaf faces in mesh: %d / %d (mesh/counted)!\n",
		  mesh->per_n_faces, per_n_faces);
      } else {
	mesh->per_n_faces = per_n_faces;
      }
    }
  }

  /*  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(new_face_dofs)
    MEM_FREE(new_face_dofs, n_hier_faces, DOF *);
  if(l_els)
    MEM_FREE(l_els, n_elements, LOGICAL_EL_3D);

  if (is_periodic) {
    if (wt_e_orbit_map) {
      MEM_FREE(wt_e_orbit_map, n_hier_edges, int);
    }
    if (wt_v_orbit_map) {
      MEM_FREE(wt_v_orbit_map, n_vertices, int);
    }
    if (edge_twin_dofs) {
      MEM_FREE(edge_twin_dofs, n_wt_e_orbits, DOF *);
    }
    if (vertex_twin_dofs) {
      MEM_FREE(vertex_twin_dofs, n_wt_v_orbits, DOF *);
    }
  }
  

  free_traverse_stack(stack);
  
  return;
}


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

static void fill_missing_dofs_3d(MESH *mesh)
{
  FUNCNAME("fill_missing_dofs_3d");
  int              i, n, n_elements, n_vertices, n_edges, n_faces, node;
  EL              *el;
  LOGICAL_EL_3D   *l_els = NULL;
  DOF            **new_edge_dofs = NULL, **new_face_dofs = NULL;
  
  if(!mesh->n_dof[CENTER] && !mesh->n_dof[EDGE] && !mesh->n_dof[FACE])
    return;

  l_els = fill_logical_els_3d(mesh,
			      &n_elements, &n_vertices, &n_edges, &n_faces,
			      NULL, NULL);

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

  if(mesh->n_dof[FACE])
    new_face_dofs = MEM_CALLOC(n_faces, DOF *);


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

  for(n = 0; n < n_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, NULL, CENTER, false, false);
    }

    if(mesh->n_dof[EDGE]) {
      node = mesh->node[EDGE];
      for(i = 0; i < N_EDGES_3D; i++)
	if(!el->dof[node + i]) {
	  DEBUG_TEST_EXIT(el->child[0], "No edge DOFs on a LEAF element??\n");

	  if(!new_edge_dofs[l_els[n].edge[i]])
	    new_edge_dofs[l_els[n].edge[i]] = 
	      transfer_dofs(mesh, NULL, NULL, EDGE, false, false);
	  
	  el->dof[node + i] = new_edge_dofs[l_els[n].edge[i]];
	}
    }

    if(mesh->n_dof[FACE]) {
      node = mesh->node[FACE];
      for(i = 0; i < N_FACES_3D; i++)
	if(!el->dof[node + i]) {
	  DEBUG_TEST_EXIT(el->child[0], "No face DOFs on a LEAF element??\n");

	  if(!new_face_dofs[l_els[n].face[i]])
	    new_face_dofs[l_els[n].face[i]] = 
	      transfer_dofs(mesh, NULL, NULL, FACE, false, false);
	  
	  el->dof[node + i] = new_face_dofs[l_els[n].face[i]];
	}
    }
  }

  /*  Clean up operations.                                                  */

  if(new_edge_dofs)
    MEM_FREE(new_edge_dofs, n_edges, DOF *);
  if(new_face_dofs)
    MEM_FREE(new_face_dofs, n_faces, DOF *);
  MEM_FREE(l_els, n_elements, LOGICAL_EL_3D);

  return;
}
