/*---------------------------------------------------------------------------*/
/* 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:     macro.c                                                         */
/*                                                                           */
/* description: reading of macro triangulations; file includes               */
/*              1d/macro_1d.c, 2d/macro_2d.c, 3d/macro_3d.c which contain    */
/*              the dimension dependent parts.                               */ 
/*                                                                           */
/*---------------------------------------------------------------------------*/
/*                                                                           */
/*  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                                     */
/*                                                                           */
/*             Daniel Koester                                                */
/*             Institut fuer Mathematik                                      */
/*             Albert-Ludwigs-Universitaet Freiburg                          */
/*             Hermann-Herder-Str. 10                                        */
/*             D-79104 Freiburg                                              */
/*                                                                           */
/*             Claus-Justus Heine                                            */
/*             Abteilung fuer Angewandte Mathematik                           */
/*             Universitaet Freiburg                                          */
/*             Hermann-Herder-Strasse 10                                      */
/*             79104 Freiburg, Germany                                       */
/*                                                                           */
/*  http://www.alberta-fem.de                                               */
/*                                                                           */
/*  (c) by A. Schmidt and K.G. Siebert (1996-2003),                          */
/*         C.-J. Heine (2006), D. Koester (2004-2007).                       */
/*                                                                           */
/*---------------------------------------------------------------------------*/

#include "alberta_intern.h"
#include "alberta.h"

#define VERT_IND(dim, i, j) ((i)*N_VERTICES(dim)+(j))
#define NEIGH_IND(dim, i, j) ((i)*N_NEIGH(dim)+(j))

/*--------------------------------------------------------------------------*/
/* opp_vertex() checks whether the vertex/edge/face with vertices           */
/* test[0],.., test[dim-1] is part of mel's boundary. It returns the        */
/* opposite vertex if true else -1.                                         */
/*--------------------------------------------------------------------------*/

static S_CHAR opp_vertex(int dim, int *mel_vert, int *test, S_CHAR *mapping)
{
  int      i, j, nv = 0, ov = 0;

  for (i = 0; i < N_VERTICES(dim); i++) {
    if (nv < i-1)  return(-1);

    for (j = 0; j < dim; j++) {
      if (mel_vert[i] == test[j]) {
/*--------------------------------------------------------------------------*/
/* i is a common vertex                                                     */
/*--------------------------------------------------------------------------*/
	if (mapping)
	  mapping[j] = i;
	ov += i;
	nv++;
	break;
      }
    }

  }
  if (nv != dim) return(-1);
/*--------------------------------------------------------------------------*/
/*  the opposite vertex is DIM! - (sum of indices of common vertices)       */
/*--------------------------------------------------------------------------*/
  if (dim == 1)
    return(1-ov);
  else if(dim == 2)
    return(3-ov);
  else
    return(6-ov);
}

/*--------------------------------------------------------------------------*/
/*  compute_neigh_fast() is an algorithm meant to speed up the task of      */
/*  computing neighbours. It does not use an N^2-algorithm.                 */
/*  The idea is to link vertices to elements sharing them to make the       */
/*  search for neighbours more efficient -  at the cost of some additional  */
/*  temporary memory usage.                                 Daniel Koester  */
/*--------------------------------------------------------------------------*/

void compute_neigh_fast(MACRO_DATA *data)
{
  FUNCNAME("compute_neigh_fast");
  int      dim = data->dim;
  int      i, j, k, l, wt, index, vertices[DIM_MAX] = { 0, }, info=0;
  int      neigh_found = false;

  struct vert2elem {
    struct vert2elem *next;
    int mel;
  };

  typedef struct vert2elem VERT2ELEM;

  VERT2ELEM *buffer, *buffer2;
  VERT2ELEM **list = MEM_CALLOC(data->n_total_vertices, VERT2ELEM *);

  if (!data->neigh)
    data->neigh = MEM_ALLOC(data->n_macro_elements*N_NEIGH(dim), int);

  if (!data->opp_vertex)
    data->opp_vertex = MEM_ALLOC(data->n_macro_elements*N_NEIGH(dim), int);

/*--------------------------------------------------------------------------*/
/* first initialize elements  (-2 as "undefined")                           */
/*--------------------------------------------------------------------------*/

  for (i = 0; i < data->n_macro_elements; i++)
    for (j = 0; j < N_NEIGH(dim); j++)
      data->neigh[NEIGH_IND(dim, i, j)] = -2;

/*--------------------------------------------------------------------------*/
/* fill the array "list" of linked lists                                    */
/*--------------------------------------------------------------------------*/

  for(i = 0; i < data->n_macro_elements; i++) {
    for(j = 0; j < N_VERTICES(dim); j++) {
      buffer = list[(index=data->mel_vertices[VERT_IND(dim, i, j)])];
      list[index] = MEM_ALLOC(1, VERT2ELEM);
      list[index]->next = buffer;
      list[index]->mel = i;
    }
  }

/*--------------------------------------------------------------------------*/
/* here comes the actual checking...                                        */
/*--------------------------------------------------------------------------*/

  for (i = 0; i < data->n_macro_elements; i++)
  {
    INFO(info, 4, "Current element %d\n", i);
    INFO(info, 6, "with vertices: ");

    for(j = 0; j < N_VERTICES(dim); j++)
      PRINT_INFO(info, 6, "%d ", data->mel_vertices[VERT_IND(dim, i, j)]);
    PRINT_INFO(info, 6, "\n");

    for (j = 0; j < N_NEIGH(dim); j++)
    {
      if (data->neigh[NEIGH_IND(dim, i, j)] == -2)
      {
	INFO(info, 8, "looking for neighbour no %d\n", j);

	/* If this face (j) has a wall-transformation attached to it,
	 * then we must actually check whether the transformed indices
	 * belong to the neighbour.
	 */
	if (data->n_wall_vtx_trafos &&
	    (wt = data->el_wall_vtx_trafos[NEIGH_IND(dim, i, j)])) {
	  if (wt > 0) {
	    for (k = 0; k < N_VERTICES(dim-1); k++) {
	      index =
		data->mel_vertices[VERT_IND(dim, i, (j+k+1)%N_VERTICES(dim))];
	      for (l = 0; l < N_VERTICES(dim-1); l++) {
		if (data->wall_vtx_trafos[wt-1][l][0] == index) {
		  vertices[k] = data->wall_vtx_trafos[wt-1][l][1];
		}
	      }
	    }
	  } else {
	    for (k = 0; k < N_VERTICES(dim-1); k++) {
	      index =
		data->mel_vertices[VERT_IND(dim, i, (j+k+1)%N_VERTICES(dim))];
	      for (l = 0; l < N_VERTICES(dim-1); l++) {
		if (data->wall_vtx_trafos[-wt-1][l][1] == index) {
		  vertices[k] = data->wall_vtx_trafos[-wt-1][l][0];
		}
	      }
	    }
	  }
	} else {
	  for (k = 0; k < N_VERTICES(dim-1); k++)
	    vertices[k] =
	      data->mel_vertices[VERT_IND(dim, i, (j+k+1)%N_VERTICES(dim))];
	}

        buffer = list[vertices[0]];

	neigh_found = false;

        while(buffer) {
          if(buffer->mel != i) {
            if ((l = opp_vertex(dim, data->mel_vertices+buffer->mel*N_VERTICES(dim), vertices, NULL)) != -1) {
	      TEST_EXIT(!neigh_found,
			"Found two neighbours on wall %d of macro el %d!\n",
			j, i);
              data->neigh[NEIGH_IND(dim, i, j)] = buffer->mel;
              data->neigh[NEIGH_IND(dim, buffer->mel, l)] = i;
	      data->opp_vertex[NEIGH_IND(dim, i, j)] = l;
              data->opp_vertex[NEIGH_IND(dim, buffer->mel, l)] = j;
              INFO(info, 8, "found element %d as neighbour...\n", buffer->mel);
	      neigh_found = true;
	    }
	  }

          buffer = buffer->next;
	}

	if(!neigh_found) {
          INFO(info, 8,
	       "no neighbour %d of element %d found: Assuming a boundary...\n",
	       j, i);

          data->neigh[NEIGH_IND(dim, i, j)] = -1;
	}
      }
    }
  }

/*--------------------------------------------------------------------------*/
/* now is the time to clean up                                              */
/*--------------------------------------------------------------------------*/


  for(i = 0; i < data->n_total_vertices; i++) {
    buffer = list[i];

    while(buffer) {
      buffer2 = buffer->next;
      MEM_FREE(buffer, 1, VERT2ELEM);

      buffer = buffer2;
    }
  }

  MEM_FREE(list, data->n_total_vertices, VERT2ELEM *);

  return;
}


/* Sets the boundary of all edges without neigbour to boundary type
 * TYPE.  if overwrite==true, then overwrite old boundary information,
 * otherwise keep it, and only set type TYPE for previously
 * unclassified boundary segments.
 */
void default_boundary(MACRO_DATA *data, U_CHAR type, bool overwrite)
{
  int      i, dim = data->dim;

  if(!data->boundary) {
    data->boundary =
      MEM_CALLOC(data->n_macro_elements*N_NEIGH(dim), BNDRY_TYPE);
  }

  for (i = 0; i < data->n_macro_elements * N_NEIGH(dim); i++) {
    if (data->neigh[i] < 0 &&
	(overwrite || data->boundary[i] == INTERIOR)) {
      data->boundary[i] = type;
    }
  }
}

/* Transform the global representation of the wall-transformation to a
 * local per-element representation (i.e. using the local numbering of
 * the vertices of an element and its peridic neighbour).
 */
void _AI_compute_element_wall_transformations(MACRO_DATA *data)
{
  int i, j, wtv, v, match, notmatched = 0, dim = data->dim;
  
  memset(data->el_wall_vtx_trafos, 0,
	 sizeof(int)*N_WALLS(dim)*data->n_macro_elements);

  for (i = 0; i < data->n_macro_elements; i++) {
    for (j = 0; j < data->n_wall_vtx_trafos; j++) {
      match = 0;
      for (v = 0; v < N_VERTICES(dim); v++) {
	for (wtv = 0; wtv < N_VERTICES(dim-1); wtv++) {
	  if (data->mel_vertices[i*N_VERTICES(dim)+v]
	      ==
	      data->wall_vtx_trafos[j][wtv][0]) {
	    break;
	  }
	}
	if (wtv < N_VERTICES(dim-1)) {
	  match++;
	} else {
	  notmatched = v;
	}
      }
      if (match == N_VERTICES(dim-1)) { /* wall-trafo j is for us */
	data->el_wall_vtx_trafos[i*N_WALLS(dim)+notmatched] = j+1;
      } else { /* test the inverse of this wall transformation */
	match = 0;
	for (v = 0; v < N_VERTICES(dim); v++) {
	  for (wtv = 0; wtv < N_VERTICES(dim-1); wtv++) {
	    if (data->mel_vertices[i*N_VERTICES(dim)+v]
		==
		data->wall_vtx_trafos[j][wtv][1]) {
	      break;
	    }
	  }
	  if (wtv < N_VERTICES(dim-1)) {
	    match++;
	  } else {
	    notmatched = v;
	  }
	}
	if (match == N_VERTICES(dim-1)) { /* (wall-trafo j)^{-1} is for us */
	  data->el_wall_vtx_trafos[i*N_WALLS(dim)+notmatched] = -(j+1);
	}
      }
    }
  }
}

#include "macro_1d.c"
#if DIM_MAX > 1
#include "macro_2d.c"
#if DIM_MAX > 2
#include "macro_3d.c"
#endif
#endif


/*--------------------------------------------------------------------------*/
/* read data->neigh into mel[].neigh[]                                      */
/* fill opp_vertex values and do a check on neighbour relations             */
/*--------------------------------------------------------------------------*/

static void fill_neigh_info(MACRO_EL *mel, const MACRO_DATA *data)
{
  FUNCNAME("check_neigh_info");
  MACRO_EL *neigh;
  int      i, j, k, l, index, dim = data->dim;

  for (i = 0; i < data->n_macro_elements; i++) {
    for (j = 0; j < N_NEIGH(dim); j++) {
      mel[i].neigh[j] =
	((index = data->neigh[NEIGH_IND(dim, i, j)]) >= 0) ? (mel+index) : NULL;
    }
  }

  for (i = 0; i < data->n_macro_elements; i++) {
    for (j = 0; j < N_NEIGH(dim); j++) {
      for (l = 0; l < N_VERTICES(dim-1); l++) {
	mel[i].neigh_vertices[j][l] = -1;
      }
      if ((neigh = mel[i].neigh[j])) {
	if (data->n_wall_vtx_trafos) {
	  /* Strange things can happen with periodic meshes :) */
	  int vertices[N_VERTICES(DIM_MAX-1)] = { 0, };
	  int wt;

	  if ((wt = data->el_wall_vtx_trafos[NEIGH_IND(dim, i, j)])) {
	    if (wt > 0) {
	      for (k = 0; k < N_VERTICES(dim-1); k++) {
		index =	data->mel_vertices[VERT_IND(dim, i, (j+k+1)%(dim+1))];
		for (l = 0; l < N_VERTICES(dim-1); l++) {
		  if (data->wall_vtx_trafos[wt-1][l][0] == index) {
		    vertices[k] = data->wall_vtx_trafos[wt-1][l][1];
		  }
		}
	      }
	    } else {
	      for (k = 0; k < N_VERTICES(dim-1); k++) {
		index =	data->mel_vertices[VERT_IND(dim, i, (j+k+1)%(dim+1))];
		for (l = 0; l < N_VERTICES(dim-1); l++) {
		  if (data->wall_vtx_trafos[-wt-1][l][1] == index) {
		    vertices[k] = data->wall_vtx_trafos[-wt-1][l][0];
		  }
		}
	      }
	    }
	  } else {
	    for (k = 0; k < N_VERTICES(dim-1); k++) {
	      vertices[k] =
		data->mel_vertices[VERT_IND(dim, i, (j+k+1)%(dim+1))];
	    }
	  }
	  k = opp_vertex(dim,
			 data->mel_vertices+neigh->index*N_VERTICES(dim),
			 vertices, wt ? mel[i].neigh_vertices[j] : NULL);
	  TEST_EXIT(k >= 0 && neigh->neigh[k] == mel+i,
		    "el %d is no neighbour of neighbour %d!\n",
		    mel[i].index, neigh->index);
	} else {
	  for (k = 0; k < N_NEIGH(dim); k++)
	    if (neigh->neigh[k] == mel+i)  break;

	  TEST_EXIT(k<N_NEIGH(dim), "el %d is no neighbour of neighbour %d!\n",
		    mel[i].index, neigh->index);

	}
	TEST_EXIT(data->opp_vertex == NULL ||
		  data->opp_vertex[i*N_NEIGH(dim)+j] == k,
		  "Inconsistent computations of opp_vertex!\n");
	mel[i].opp_vertex[j] = k;
      } else {
        mel[i].opp_vertex[j] = -1;
      }
    }
  }

  return;
}


/*--------------------------------------------------------------------------*/
/* domain size                                                              */
/*--------------------------------------------------------------------------*/

static void calculate_size(MESH *mesh, const MACRO_DATA *data)
{
  int         i, j;

  for (j = 0; j < DIM_OF_WORLD; j++)
  {
    mesh->bbox[0][j] = data->coords[0][j];
    mesh->bbox[1][j] = data->coords[0][j];
  }

  for (i = 0; i < mesh->n_vertices; i++)
  {
    for (j = 0; j < DIM_OF_WORLD; j++)
    {
      mesh->bbox[0][j] = MIN(mesh->bbox[0][j], data->coords[i][j]);
      mesh->bbox[1][j] = MAX(mesh->bbox[1][j], data->coords[i][j]);
    }
  }

  AXPBY_DOW(1.0, mesh->bbox[1], -1.0, mesh->bbox[0], mesh->diam);

  return;
}

/* read_comment(file): consume the current line starting at the first
 * occurence of '#'
 */
static void read_comment(FILE *file)
{
  int c;

  for (;;) {
    while (isspace(c = fgetc(file)));
    if (c == '#') {
      int waste = fscanf(file, "%*[^\r\n]"); /* <- Could this possibly work? */
      (void)waste;
    } else {
      ungetc(c, file);
      return;
    }
  }
}

/*--------------------------------------------------------------------------*/
/*  read_indices()  reads dim+1 indices from  file  into  id[0-dim],        */
/*    returns true if dim+1 inputs arguments could be read successfully by  */
/*    fscanf(), else false                                                  */
/*--------------------------------------------------------------------------*/

static int read_indices(int dim, FILE *file, int id[])
{
  int      i;

  for (i = 0; i <= dim; i++) {
    read_comment(file);
    if (fscanf(file, "%d", id+i) != 1)
      return false;
  }
  return true;
}

enum {
  KEY_DIM = 0,
  KEY_DOW,
  KEY_NV,
  KEY_NEL,
  KEY_V_COORD,
  KEY_EL_VERT,
  KEY_EL_BND,
  KEY_EL_NEIGH,
  KEY_EL_TYPE,
  KEY_N_WALL_VTX_TRAFOS,
  KEY_WALL_VTX_TRAFOS,
  KEY_N_WALL_TRAFOS,
  KEY_WALL_TRAFOS,
  KEY_EL_WALL_TRAFOS,
  N_KEYS
};

#define N_MIN_KEYS  6
static const char *keys[N_KEYS] = {
  "DIM",                 /*  0  */
  "DIM_OF_WORLD",        /*  1  */
  "number of vertices",  /*  2  */
  "number of elements",  /*  3  */
  "vertex coordinates",  /*  4  */
  "element vertices",    /*  5  */
  "element boundaries",  /*  6  */
  "element neighbours",  /*  7  */
  "element type",        /*  8  */
  "number of wall vertex transformations", /* 9 */
  "wall vertex transformations",/* 10 */
  "number of wall transformations", /* 11 */
  "wall transformations",/* 12  */
  "element wall transformations", /* 13 */
};

static int get_key_no(const char *key)
{
  int     i;

  for (i = 0; i < N_KEYS; i++)
    if (!strcmp(keys[i], key))  return(i);

  return(-1);
}

static const char *read_key(const char *line)
{
  static char  key[100];
  char         *k = key;

  while(isspace(*line)) line++;
  while((*k++ = *line++) != ':');
  *--k = '\0';

  return((const char *) key);
}

/*--------------------------------------------------------------------------*/
/*  read_macro_data():                                                      */
/*    read macro triangulation from ascii file in ALBERTA format            */
/*    fills macro_data structure                                            */
/*    called by read_macro()                                                */
/*--------------------------------------------------------------------------*/

static MACRO_DATA *read_macro_data(const char *filename)
{
  FUNCNAME("read_macro_data");
  FILE       *file;
  MACRO_DATA *macro_data = NULL;
  int        dim, dow, nv, ne, nwt, nwvt, i, j, k, ind[N_VERTICES_MAX];
  REAL       dbl;
  char       name[128], line[256];
  int        line_no, n_keys, i_key, sort_key[N_KEYS];
  int        nv_key, ne_key, nwvt_key, nwt_key;
  int        key_def[N_KEYS] = {0, 0, 0, 0, 0, 0, 0, 0, 0};
  int        blah;
  const char *key;

  TEST_EXIT(filename, "no file specified; filename NULL pointer\n");
  TEST_EXIT(strlen(filename) < (unsigned int) 127,
	    "can only handle filenames up to 127 characters\n");

  TEST_EXIT((file=fopen(filename, "r")), "cannot open file %s\n", filename);
  strncpy(name, filename, 127);

/*--------------------------------------------------------------------------*/
/*  looking for all keys in the macro file ...                              */
/*--------------------------------------------------------------------------*/

  line_no = n_keys = 0;
  while (fgets(line, sizeof(line), file))
  {
    line_no++;
    if (*line == '#') continue;
    if (!strchr(line, ':'))  continue;
    key = read_key(line);
    i_key = get_key_no(key);
    TEST_EXIT(i_key >= 0,
	      "file %s: must not contain key %s on line %d\n",
	      name, key, line_no);
    TEST_EXIT(!key_def[i_key],
	      "file %s: key %s defined second time on line %d\n", name, key, line_no);

    sort_key[n_keys++] = i_key;
    key_def[i_key] = true;
  }

  for (i_key = 0; i_key < N_MIN_KEYS; i_key++)
  {
    for (j = 0; j < n_keys; j++)
      if (sort_key[j] == i_key)  break;
    TEST_EXIT(j < n_keys,
	      "file %s: You do not have specified data for %s in %s\n",
	      name, keys[i_key]);

    for (j = 0; j < n_keys; j++)
      if (sort_key[j] == KEY_NV)  break;
    nv_key = j;
    for (j = 0; j < n_keys; j++)
      if (sort_key[j] == KEY_NEL)  break;
    ne_key = j;
    for (j = 0; j < n_keys; j++)
      if (sort_key[j] == KEY_N_WALL_VTX_TRAFOS)  break;
    nwvt_key = j;
    for (j = 0; j < n_keys; j++)
      if (sort_key[j] == KEY_N_WALL_TRAFOS)  break;
    nwt_key = j;

    switch(i_key)
    {
    case KEY_DIM:
    case KEY_DOW:
      TEST_EXIT(sort_key[i_key] < 2,
		"file %s: You have to specify DIM or DIM_OF_WORLD before all other data\n", name);
      break;
    case KEY_V_COORD:
      TEST_EXIT(nv_key < i_key,
		"file %s: Before reading data for %s, you have to specify the %s\n",
		name, keys[KEY_V_COORD], keys[KEY_NV]);
      break;
    case KEY_EL_VERT:
      TEST_EXIT(nv_key < i_key  &&  ne_key < i_key,
		"file %s: Before reading data for %s, "
		"you have to specify the %s and %s\n",
		name, keys[KEY_EL_VERT], keys[KEY_NEL], keys[KEY_NV]);
    case KEY_EL_BND:
    case KEY_EL_NEIGH:
    case KEY_EL_TYPE:
      TEST_EXIT(ne_key < i_key,
		"file %s: Before reading data for %s, "
		"you have to specify the %s\n",
		name, keys[i_key], keys[KEY_NEL]);
      break;
    case KEY_N_WALL_VTX_TRAFOS:
      break;
    case KEY_WALL_VTX_TRAFOS:
      TEST_EXIT(nwvt_key < i_key,
		"file %s: Before reading data for %s, "
		"you have to specify the %s\n",
		name, keys[i_key], keys[KEY_N_WALL_VTX_TRAFOS]);
      break;
    case KEY_N_WALL_TRAFOS:
      break;
    case KEY_WALL_TRAFOS:
      TEST_EXIT(nwt_key < i_key,
		"file %s: Before reading data for %s, "
		"you have to specify the %s\n",
		name, keys[i_key], keys[KEY_N_WALL_TRAFOS]);
    case KEY_EL_WALL_TRAFOS:
      TEST_EXIT(ne_key < i_key,
		"file %s: Before reading data for %s, "
		"you have to specify the %s\n",
		name, keys[i_key], keys[KEY_NEL]);
      break;
    }
  }

  for (i_key = 0; i_key < N_KEYS; i_key++)
    key_def[i_key] = false;

/*--------------------------------------------------------------------------*/
/*  and now, reading data ...                                               */
/*--------------------------------------------------------------------------*/

  rewind(file);

  for (i_key = 0; i_key < n_keys; i_key++) {
    read_comment(file);

    switch(sort_key[i_key]) {
    case KEY_DIM:
      TEST_EXIT(fscanf(file, "%*s %d", &dim) == 1,
		"file %s: cannot read DIM correctly\n", name);
      TEST_EXIT(dim <= DIM_MAX,
		"file %s: dimension = %d > DIM_MAX = %d\n",
		name, dim, DIM_MAX);
      key_def[KEY_DIM] = true;
      break;
    case KEY_DOW:
      TEST_EXIT(fscanf(file, "%*s %d", &dow) == 1,
		"file %s: cannot read DIM_OF_WORLD correctly\n", name);
      TEST_EXIT(dow == DIM_OF_WORLD,
		"file %s: dimension of world = %d != DIM_OF_WORLD = %d\n",
		name, dow, DIM_OF_WORLD);

      key_def[KEY_DOW] = true;
      break;
    case KEY_NV:
      TEST_EXIT(fscanf(file, "%*s %*s %*s %d", &nv) == 1,
		"file %s: cannot read number of vertices correctly\n", name);
      TEST_EXIT(nv > 0,
		"file %s: number of vertices = %d must be bigger than 0\n", name, nv);

      key_def[KEY_NV] = true;

      if(key_def[KEY_NEL])
        macro_data = alloc_macro_data(dim, nv, ne);

      break;
    case KEY_NEL:
      TEST_EXIT(fscanf(file, "%*s %*s %*s %d", &ne) == 1,
		"file %s: cannot read number of elements correctly\n", name);
      TEST_EXIT(ne > 0,
		"file %s: number of elements = %d must be bigger than 0\n", name, ne);

      key_def[KEY_NEL] = true;

      if(key_def[KEY_NV])
        macro_data = alloc_macro_data(dim, nv, ne);

      break;
    case KEY_V_COORD:
      blah = fscanf(file, "%*s %*s");
      (void)blah;
      for (i = 0; i < nv; i++) {
	for (j = 0; j < DIM_OF_WORLD; j++) {
	  read_comment(file);
	  TEST_EXIT(fscanf(file, "%lf", &dbl) == 1,
		    "file %s: error while reading coordinates, check file\n",
		    name);

	  macro_data->coords[i][j] = dbl;
	}
      }

      key_def[KEY_V_COORD] = true;
      break;
    case KEY_EL_VERT:
      blah = fscanf(file, "%*s %*s");
      (void)blah;
/*--------------------------------------------------------------------------*/
/* global index of vertices for each single element                         */
/*--------------------------------------------------------------------------*/

      for (i = 0; i < ne; i++) {
	TEST_EXIT(read_indices(dim, file, ind),
		  "file %s: cannot read vertex indices of element %d\n",
		  name, i);

	for (j = 0; j < N_VERTICES(dim); j++)
	  macro_data->mel_vertices[VERT_IND(dim, i, j)] = ind[j];
      }

      key_def[KEY_EL_VERT] = true;
      break;
    case KEY_EL_BND:
      blah = fscanf(file, "%*s %*s");
      (void)blah;

      if(dim == 0)
	ERROR_EXIT("Boundary types do not make sense in 0d!\n");
      else {
/*--------------------------------------------------------------------------*/
/* read boundary type of each vertex/edge/face (in 1d/2d/3d)                */
/*--------------------------------------------------------------------------*/
	macro_data->boundary = MEM_ALLOC(ne*N_NEIGH(dim), BNDRY_TYPE);

	for (i = 0; i < ne; i++) {
	  TEST_EXIT(read_indices(dim, file, ind),
		    "file %s: cannot read boundary types of element %d\n", name, i);

	  for(j = 0; j < N_NEIGH(dim); j++)
	    macro_data->boundary[NEIGH_IND(dim, i, j)] = (BNDRY_TYPE)ind[j];
	}
      }

      key_def[KEY_EL_BND] = true;
      break;
    case KEY_EL_NEIGH:
      blah = fscanf(file, "%*s %*s");
      (void)blah;

      if(dim == 0)
	ERROR_EXIT("Neighbour indices do not make sense in 0d!\n");
      else {
/*--------------------------------------------------------------------------*/
/* read neighbour indices:                                                  */
/*--------------------------------------------------------------------------*/
	macro_data->neigh = MEM_ALLOC(ne*N_NEIGH(dim), int);

	for (i = 0; i < ne; i++) {
	  TEST_EXIT(read_indices(dim, file, ind),
		    "file %s: cannot read neighbour info of element %d\n", name, i);

	  for(j = 0; j < N_NEIGH(dim); j++)
	    macro_data->neigh[NEIGH_IND(dim, i, j)] = ind[j];
	}
      }

      key_def[KEY_EL_NEIGH] = true;
      break;
    case KEY_EL_TYPE:
      blah = fscanf(file, "%*s %*s");
      (void)blah;
/*--------------------------------------------------------------------------*/
/* el_type is handled just like bound and neigh above                       */
/*--------------------------------------------------------------------------*/

      if(dim < 3) {
	WARNING("File %s: element type only used in 3d; will ignore data for el_type\n", name);
      }
#if DIM_MAX > 2
      else {
	macro_data->el_type = MEM_ALLOC(ne, U_CHAR);

	for (i = 0; i < ne; i++) {
	  read_comment(file);
	  TEST_EXIT(fscanf(file, "%d", &j) == 1,
		    "file %s: cannot read el_type of element %d\n", name, i);

	  macro_data->el_type[i] = (U_CHAR) j;
	}
      }
#endif
      key_def[KEY_EL_TYPE] = true;
      break;
    case KEY_N_WALL_VTX_TRAFOS:
      TEST_EXIT(fscanf(file, "%*s %*s %*s %*s %*s %d", &nwvt) == 1,
		"file %s: cannot read number of "
		"wall vertex transformations correctly\n",
		name);
      TEST_EXIT(nwvt > 0,
		"file %s: number of wall transformations = %d must be bigger than 0\n",
		name, nwvt);

      macro_data->n_wall_vtx_trafos = nwvt;
      macro_data->wall_vtx_trafos = (int (*)[N_VERTICES(DIM_MAX-1)][2])
	MEM_ALLOC(nwvt*sizeof(*macro_data->wall_vtx_trafos), char);
      macro_data->el_wall_vtx_trafos = MEM_ALLOC(ne*N_WALLS(dim), int);

      key_def[KEY_N_WALL_VTX_TRAFOS] = true;
      break;
    case KEY_WALL_VTX_TRAFOS:
      blah = fscanf(file, "%*s %*s %*s");
      (void)blah;      
      for (i = 0; i < nwvt; i++) {
	TEST_EXIT(read_indices(dim*2-1, file, ind),
		  "file %s: cannot read wall transformation %d\n", name, i);
	for (j = 0; j < N_VERTICES(dim-1); j++) {
	  macro_data->wall_vtx_trafos[i][j][0] = ind[2*j];
	  macro_data->wall_vtx_trafos[i][j][1] = ind[2*j+1];
	}
      }
      key_def[KEY_WALL_VTX_TRAFOS] = true;
      break;
    case KEY_N_WALL_TRAFOS:
      TEST_EXIT(fscanf(file, "%*s %*s %*s %*s %d", &nwt) == 1,
		"file %s: cannot read number of "
		"wall transformations correctly\n",
		name);
      TEST_EXIT(nwt > 0,
		"file %s: number of wall transformations = %d must be bigger than 0\n",
		name, nwt);

      macro_data->n_wall_trafos = nwt;
      macro_data->wall_trafos = MEM_ALLOC(nwt, AFF_TRAFO);
      key_def[KEY_N_WALL_TRAFOS] = true;
      break;
    case KEY_WALL_TRAFOS:
      blah = fscanf(file, "%*s %*s");
      (void)blah;
      for (i = 0; i < nwt; i++) {
	for (j = 0; j < (DIM_OF_WORLD+1); j++) {
	  for (k = 0; k < (DIM_OF_WORLD+1); k++) {
	    read_comment(file);
	    TEST_EXIT(fscanf(file, "%lf", &dbl) == 1,
		      "file %s: error while reading wall transformation\n",
		      name);
	    if (j < DIM_OF_WORLD) {
	      if (k < DIM_OF_WORLD) {
		macro_data->wall_trafos[i].M[j][k] = dbl;
	      } else {
		macro_data->wall_trafos[i].t[j] = dbl;
	      }
	    }
	    /* discard the last line (projective co-ordinates) */
	  }
	}
      }
      key_def[KEY_WALL_TRAFOS] = true;
      break;
    case KEY_EL_WALL_TRAFOS:
      blah = fscanf(file, "%*s %*s %*s");
      (void)blah;

      if(dim == 0) {
	ERROR_EXIT("Wall transformations do not make sense in 0d!\n");
      } else {
	macro_data->el_wall_trafos = MEM_ALLOC(ne*N_NEIGH(dim), int);

	for (i = 0; i < ne; i++) {
	  TEST_EXIT(read_indices(dim, file, ind),
		    "file %s: "
		    "cannot read the wall transformations for element %d\n",
		    name, i);

	  for(j = 0; j < N_NEIGH(dim); j++) {
	    macro_data->el_wall_trafos[NEIGH_IND(dim, i, j)] = ind[j];
	  }
	}
      }
      key_def[KEY_EL_WALL_TRAFOS] = true;
      break;
      break;
    }
  }
  fclose(file);

  return(macro_data);
}

/*--------------------------------------------------------------------------*/
/* read macro triangulation from file "filename" into macro_data data in    */
/* native binary format                                                     */
/*--------------------------------------------------------------------------*/

static MACRO_DATA *read_macro_data_bin(const char *name)
{
  FUNCNAME("read_macro_data_bin");
  FILE       *file;
  MACRO_DATA *macro_data;
  int        dim, i, length, nv, ne;
  char       *s;
  char       record_written;
  int        blah;

  TEST_EXIT(file = fopen(name, "rb"), "cannot open file %s\n", name);

  length = MAX(strlen(ALBERTA_VERSION)+1, 21);
  s = MEM_ALLOC(length, char);

  blah = fread(s, sizeof(char), length, file);
  (void)blah;
  TEST_EXIT(!strncmp(s, "ALBERTA", 6), "file %s: unknown file id:\"%s\"\n", name, s);

  MEM_FREE(s, length, char);

  blah = fread(&i, sizeof(int), 1, file);
  (void)blah;
  TEST_EXIT(i == sizeof(REAL), "file %s: wrong sizeof(REAL) %d\n", name, i);

  blah = fread(&dim, sizeof(int), 1, file);
  (void)blah;
  TEST_EXIT(dim <= DIM_OF_WORLD,
	    "file %s: dimension = %d > DIM_MAX = %d\n",
	    name, dim, DIM_MAX);

  blah = fread(&i, sizeof(int), 1, file);
  (void)blah;
  TEST_EXIT(i == DIM_OF_WORLD,
	    "file %s: dimension of world = %d != DIM_OF_WORLD = %d\n",
	    name, i, DIM_OF_WORLD);

  blah = fread(&nv, sizeof(int), 1, file);
  (void)blah;
  TEST_EXIT(nv > 0,
	    "file %s: number of vertices = %d must be bigger than 0\n", name, nv);

  blah = fread(&ne, sizeof(int), 1, file);
  (void)blah;
  TEST_EXIT(ne > 0,
	    "file %s: number of elements = %d must be bigger than 0\n", name, ne);

  macro_data = alloc_macro_data(dim, nv, ne);

  blah = fread(macro_data->coords, sizeof(REAL_D), nv, file);
  blah = fread(
    macro_data->mel_vertices, sizeof(int), N_VERTICES(dim) * ne, file);

  blah = fread(&record_written, sizeof(char), 1, file);
  if(record_written)
  {
    macro_data->boundary = MEM_ALLOC(ne*N_NEIGH(dim), BNDRY_TYPE);
    blah = fread(
      macro_data->boundary, sizeof(BNDRY_TYPE), N_NEIGH(dim) * ne, file);
  }

  blah = fread(&record_written, sizeof(char), 1, file);
  if(record_written) {
    macro_data->neigh = MEM_ALLOC(ne*N_NEIGH(dim), int);
    blah = fread(macro_data->neigh, sizeof(int), N_NEIGH(dim) * ne, file);
  }

#if DIM_MAX > 2
  if(dim == 3) {
    int nonsense;
    (void)nonsense;
    nonsense = fread(&record_written, sizeof(char), 1, file);
    if (record_written) {
      macro_data->el_type = MEM_ALLOC(ne, U_CHAR);
      nonsense = fread(macro_data->el_type, sizeof(U_CHAR), ne, file);
    }
  }
#endif

  s = MEM_ALLOC(5, char);

  TEST_EXIT(fread(s, sizeof(char), 4, file) == 4,
	    "file %s: problem while reading FILE END MARK\n", name);

  TEST_EXIT(!strncmp(s, "EOF.", 4), "file %s: no FILE END MARK\n", name);
  MEM_FREE(s, 5, char);

  fclose(file);

  return(macro_data);
}


/*--------------------------------------------------------------------------*/
/* Some routines needed for interaction with xdr-files                      */
/* WARNING: These will need to be adapted if ALBERTA data types REAL, REAL_D*/
/* , etc. change!                                                            */
/*--------------------------------------------------------------------------*/

static int xdr_dim = 0;

static bool_t xdr_REAL(XDR *xdr, REAL *rp)
{
  return (xdr_double(xdr, rp));
}

ALBERTA_DEFUNUSED(static bool_t xdr_U_CHAR(XDR *xdr, U_CHAR *ucp))
{
  return (xdr_u_char(xdr, ucp));
}

static bool_t xdr_S_CHAR(XDR *xdr, S_CHAR *cp)
{
  return (xdr_char(xdr,(char *)cp));
}

static bool_t xdr_REAL_D(XDR *xdr, REAL_D *dp)
{
  return (xdr_vector(xdr, (char *)dp, DIM_OF_WORLD, sizeof(REAL), (xdrproc_t) xdr_REAL));
}

#if __APPLE_CC__
# define _XDR_PTR_T_ void *
#else
# define _XDR_PTR_T_ char *
#endif

static int read_xdr_file(_XDR_PTR_T_ xdr_file, _XDR_PTR_T_ buffer, int size)
{
  return fread(buffer, (size_t)size, 1, (FILE *)xdr_file) == 1 ? size : 0;
}

static int write_xdr_file(_XDR_PTR_T_ xdr_file, _XDR_PTR_T_ buffer, int size)
{
  return fwrite(buffer, (size_t)size, 1, (FILE *)xdr_file) == 1 ? size : 0;
}


static XDR *xdr_open_file(const char *filename, enum xdr_op mode)
{
  XDR *xdr;
  FILE *xdr_file;

  if (!(xdr = MEM_ALLOC(1, XDR)))
  {
    ERROR("can't allocate memory for xdr pointer.\n");

    return NULL;
  }

  if ((xdr_file = fopen(filename, mode == XDR_DECODE ? "r": "w")))
  {
    xdrrec_create(xdr, 65536, 65536, (caddr_t) xdr_file,
                  read_xdr_file, write_xdr_file);

    xdr->x_op = mode;
    xdr->x_public = (caddr_t)xdr_file;

    if (mode == XDR_DECODE)
      xdrrec_skiprecord(xdr);

    return xdr;
  }
  else
  {
    ERROR("error opening xdr file.\n");

    MEM_FREE(xdr, 1, XDR);

    return NULL;
  }
}


static int xdr_close_file(XDR *xdr)
{
  if (!xdr)
  {
    ERROR("NULL xdr pointer.\n");
    return 0;
  }

  if (xdr->x_op == XDR_ENCODE)
    xdrrec_endofrecord(xdr, 1);

  if (fclose((FILE *) xdr->x_public))
    ERROR("error closing file.\n");

  xdr_destroy(xdr);

  MEM_FREE(xdr, 1, XDR);

  return 1;
}

/*--------------------------------------------------------------------------*/
/*  read_macro_data_xdr():                                                  */
/*    read macro triangulation from file in xdr-format                      */
/*    fills macro_data structure                                            */
/*    called by ....?                                                       */
/*--------------------------------------------------------------------------*/

static MACRO_DATA *read_macro_data_xdr(const char *name)
{
  FUNCNAME("read_macro_data_xdr");
  XDR        *xdrp;
  MACRO_DATA *macro_data;
  int        length, dow, nv, ne, size;
  char       *s;
  bool_t     record_written;
  caddr_t    array_loc;


  TEST_EXIT(name, "no file specified; filename NULL pointer\n");

  if (!(xdrp = xdr_open_file(name, XDR_DECODE)))
    ERROR_EXIT("cannot open file %s\n", name);

  length = MAX(strlen(ALBERTA_VERSION)+1, 21);     /* length with terminating \0 */
  s = MEM_ALLOC(length, char);

  TEST_EXIT(xdr_string(xdrp, &s, length), "file %s: could not read file id\n", name);
  TEST_EXIT(!strncmp(s, "ALBERTA", 6), "file %s: unknown file id: \"%s\"\n", name, s);

  MEM_FREE(s, length, char);

  TEST_EXIT(xdr_int(xdrp, &xdr_dim),
	    "file %s: could not read dimension correctly\n", name);
  TEST_EXIT(xdr_dim <= DIM_MAX,
	    "file %s: dimension = %d > DIM_MAX = %d\n",
	    name, xdr_dim, DIM_MAX);


  TEST_EXIT(xdr_int(xdrp, &dow), "file %s: could not read dimension of world correctly\n", name);
  TEST_EXIT(dow == DIM_OF_WORLD,
	    "file %s: dimension of world = %d != DIM_OF_WORLD = %d\n",
	    name, dow, DIM_OF_WORLD);

  TEST_EXIT(xdr_int(xdrp, &nv),
	    "file %s: cannot read number of vertices correctly\n", name);
  TEST_EXIT(nv > 0,
	    "file %s: number of vertices = %d must be bigger than 0\n", name, nv);

  TEST_EXIT(xdr_int(xdrp, &ne),
	    "file %s: cannot read number of elements correctly\n", name);
  TEST_EXIT(ne > 0,
	    "file %s: number of elements = %d must be bigger than 0\n", name, ne);

  macro_data = alloc_macro_data(xdr_dim, nv, ne);

  array_loc=(caddr_t) macro_data->coords;
  TEST_EXIT(xdr_array(xdrp, &array_loc, (u_int *) &nv, (u_int) nv, sizeof(REAL_D), (xdrproc_t) xdr_REAL_D),
	    "file %s: error while reading coordinates, check file\n", name);

  array_loc=(caddr_t) macro_data->mel_vertices;
  size = ne * N_VERTICES(xdr_dim);
  TEST_EXIT(xdr_array(xdrp, &array_loc, (u_int *) &size, (u_int) size,
		      sizeof(int), (xdrproc_t) xdr_int),
	    "file %s: cannot read vertex indices\n", name);

  TEST_EXIT(xdr_bool(xdrp, &record_written),
	    "file %s: could not determine whether to allocate memory for boundaries\n", name);
  if(record_written)
  {
    macro_data->boundary = MEM_ALLOC(ne*N_NEIGH(xdr_dim), BNDRY_TYPE);

    array_loc=(caddr_t) macro_data->boundary;
    size = ne * N_NEIGH(xdr_dim);
    TEST_EXIT(xdr_array(xdrp, &array_loc, (u_int *) &size, (u_int) size,
			sizeof(BNDRY_TYPE), (xdrproc_t) xdr_S_CHAR),
	      "file %s: could not read boundary types\n", name);
  }

  TEST_EXIT(xdr_bool(xdrp, &record_written),
	    "file %s: could not determine whether to allocate memory for neighbours\n", name);
  if(record_written)
  {
    macro_data->neigh = MEM_ALLOC(ne*N_NEIGH(xdr_dim), int);

    array_loc=(caddr_t) macro_data->neigh;
    size = ne * N_NEIGH(xdr_dim);
    TEST_EXIT(xdr_array(xdrp, &array_loc, (u_int *) &size, (u_int) size,
			sizeof(int), (xdrproc_t) xdr_int),
	      "file %s: could not read neighbor info\n", name);
  }

#if DIM_MAX > 2
  if(xdr_dim == 3) {
    TEST_EXIT(xdr_bool(xdrp, &record_written),
	      "file %s: could not determine whether to allocate memory for element types\n", name);
    if(record_written)
    {
      macro_data->el_type = MEM_ALLOC(ne, U_CHAR);

      array_loc=(caddr_t) macro_data->el_type;
      TEST_EXIT(xdr_array(xdrp, &array_loc, (u_int *) &ne, (u_int) ne, sizeof(U_CHAR), (xdrproc_t) xdr_U_CHAR),
		"file %s: cannot read element types\n", name);
    }
  }
#endif

  xdr_close_file(xdrp);

  return(macro_data);
}

/*--------------------------------------------------------------------------*/
/* supported file types for macro data files:                               */
/*--------------------------------------------------------------------------*/

typedef enum {ascii_format, binary_format, xdr_format} macro_format;

/*--------------------------------------------------------------------------*/
/* read macro triangulation from file "filename"                            */
/*--------------------------------------------------------------------------*/

static MACRO_DATA *read_macro_master(const char *filename,
				     macro_format format)
{
  FUNCNAME("read_macro_master");
  MACRO_DATA *macro_data = NULL;
  char       filenew[1024];

  TEST_EXIT(filename, "no file specified; filename NULL pointer\n");

  switch (format) {
  case ascii_format:
    macro_data = read_macro_data(filename);
    break;
  case binary_format:
    macro_data = read_macro_data_bin(filename);
    break;
  case xdr_format:
    macro_data = read_macro_data_xdr(filename);
  }

  if (macro_data->n_wall_vtx_trafos > 0)
    _AI_compute_element_wall_transformations(macro_data);
  if (!macro_data->neigh && macro_data->dim > 0)
    compute_neigh_fast(macro_data);
  if (!macro_data->boundary && macro_data->dim > 0) {
    default_boundary(macro_data, DIRICHLET /* default boundary type */, true);
  }

  snprintf(filenew, 1024, "%s.new", filename);
  macro_test(macro_data, filenew);

  return macro_data;
}


static void cleanup_write_macro(MACRO_DATA *data, DOF_INT_VEC *dof_vert_ind,
				TRAVERSE_STACK *stack)
{

  if (data) free_macro_data(data);
  free_dof_int_vec(dof_vert_ind);
  if (stack)
    free_traverse_stack(stack);

  return;
}


/*--------------------------------------------------------------------------*/
/* mesh2macro_data(): counterpart to macro_data2mesh below. This routine    */
/* converts the information stored in the leaf elements of mesh to the raw  */
/* data type MACRO_DATA.                                                    */
/*--------------------------------------------------------------------------*/

MACRO_DATA *mesh2macro_data(MESH *mesh)
{
  FUNCNAME("mesh2macro_data");
  MACRO_DATA      *data;
  FLAGS           fill_flag;
  PARAMETRIC      *parametric;
  const DOF_ADMIN *admin;
  const FE_SPACE  *fe_space;
  const int       n_dof[N_NODE_TYPES] = {1, 0, 0, 0};
  DOF_INT_VEC     *dof_vert_ind;
  int             dim = mesh->dim, n0, ne, nv, i, *vert_ind = NULL;
#if DIM_MAX > 2
  U_CHAR          write_el_type;
#endif
  static const REAL_B vertex_bary[N_VERTICES_LIMIT] = {
    INIT_BARY_3D(1.0, 0.0, 0.0, 0.0),
    INIT_BARY_3D(0.0, 1.0, 0.0, 0.0),
    INIT_BARY_3D(0.0, 0.0, 1.0, 0.0),
    INIT_BARY_3D(0.0, 0.0, 0.0, 1.0)
  };

  /* we get a real vertex-only admin; this way it is easy to
   * manipulate, e.g., per-vertex data, and if we re-generate a mesh
   * from this macro data then the new mesh will have the same vertex
   * ordering as the vertex admin.
   */
  dof_compress(mesh);

  fe_space = get_dof_space(mesh, "mesh2macro_data", n_dof, ADM_FLAGS_DFLT);
  admin = fe_space->admin;

  n0 = admin->n0_dof[VERTEX];

  parametric = mesh->parametric;

  dof_vert_ind = get_dof_int_vec("vertex indices", fe_space);
  GET_DOF_VEC(vert_ind, dof_vert_ind);
  FOR_ALL_DOFS(admin, vert_ind[dof] = -1);

  data = alloc_macro_data(dim, mesh->n_vertices, mesh->n_elements);

  nv = ne = 0;
#if DIM_MAX > 2
  write_el_type = false;
#endif

/*--------------------------------------------------------------------------*/
/* The first pass counts elements and vertices, checks these against the    */
/* entries of mesh->n_elements, mesh->n_vertices, and fills data->coords.   */
/* A check on whether an element has nonzero el_type is also done.          */
/*--------------------------------------------------------------------------*/
  TRAVERSE_FIRST(mesh, -1,
		 CALL_LEAF_EL|FILL_COORDS|FILL_NEIGH|FILL_NON_PERIODIC) {

    if (parametric) {
      if (parametric->init_element(el_info, parametric)) {
	/* really parametric, otherwise el_info->coord already
	 * contains the coordinates. */
	parametric->coord_to_world(el_info, NULL, N_VERTICES(dim),
				   vertex_bary, (REAL_D *)el_info->coord);
      }
    }

    for (i = 0; i < N_VERTICES(dim); i++) {
      if (vert_ind[el_info->el->dof[i][n0]] == -1) {
/*--------------------------------------------------------------------------*/
/* assign a global index to each vertex                                     */
/*--------------------------------------------------------------------------*/
        vert_ind[el_info->el->dof[i][n0]] = el_info->el->dof[i][n0];

	COPY_DOW(el_info->coord[i], data->coords[el_info->el->dof[i][n0]]);

        nv++;

        if(nv > mesh->n_vertices)
	{
          cleanup_write_macro(data, dof_vert_ind, stack);
	  free_fe_space(fe_space);
          ERROR("mesh %s: n_vertices (==%d) is too small! Writing aborted\n",
                mesh->name, mesh->n_vertices);
          return NULL;
        }
      }
    }

    ne++;

    if(ne > mesh->n_elements) {
      cleanup_write_macro(data, dof_vert_ind, stack);
      ERROR("mesh %s: n_elements (==%d) is too small! Writing aborted\n",
            mesh->name, mesh->n_elements);
      return(NULL);
    }
#if DIM_MAX > 2
    if(dim == 3 && el_info->el_type) write_el_type = true;
#endif
  } TRAVERSE_NEXT();

  if(ne < mesh->n_elements) {
    cleanup_write_macro(data, dof_vert_ind, NULL);
    free_fe_space(fe_space);
    ERROR("mesh %s: n_elements (==%d) is too large: only %d leaf elements counted -- writing aborted\n", mesh->name, mesh->n_elements, ne);
    return(NULL);
  }

  if(nv < mesh->n_vertices) {
    cleanup_write_macro(data, dof_vert_ind, NULL);
    free_fe_space(fe_space);
    ERROR("mesh %s: n_vertices (==%d) is too large: only %d vertices counted --  allocation of macro data aborted\n", mesh->name, mesh->n_vertices, nv);
    return(NULL);
  }

  if(dim > 0) {
    data->boundary = MEM_ALLOC(ne*N_NEIGH(dim), BNDRY_TYPE);
  }

#if DIM_MAX > 2
  if(write_el_type) {
    data->el_type = MEM_ALLOC(ne, U_CHAR);
  }
#endif

  if (mesh->is_periodic) {
    data->el_wall_vtx_trafos = MEM_ALLOC(ne*N_WALLS(dim), int);

    if (mesh->n_wall_trafos) {
      data->n_wall_trafos = mesh->n_wall_trafos / 2;
      data->wall_trafos = MEM_ALLOC(data->n_wall_trafos, AFF_TRAFO);
      TEST_EXIT(mesh->n_wall_trafos % 2 == 0,
		"odd number of wall-transformations????");
      for (i = 0; i < data->n_wall_trafos; i++) {
	data->wall_trafos[i] = *mesh->wall_trafos[2*i];
      }
      data->el_wall_trafos =
	MEM_CALLOC(N_WALLS(dim) * data->n_macro_elements, int);
    }
  }

#if ALBERTA_DEBUG
  char *mel_comment = MEM_CALLOC(ne*80, char);
  data->mel_comment = MEM_CALLOC(ne, char *);
#endif

  ne = 0;

  /* The second pass assigns mel_vertices, boundary, and if necessary el_type
   *
   * We use FILL_NON_PERIODIC to recover boundary information
   * potentially attached to periodic walls. The neighbourhood
   * connectivity of the mesh is computed via compute_neigh_fast()
   * later, so FILL_NON_PERIODIC does not hurt here.
   */
  fill_flag =
    CALL_LEAF_EL|FILL_COORDS|FILL_MACRO_WALLS|FILL_NEIGH|FILL_NON_PERIODIC;
  TRAVERSE_FIRST(mesh, -1, fill_flag) {

   for (i = 0; i < N_VERTICES(dim); i++)
      data->mel_vertices[VERT_IND(dim, ne, i)] =
	vert_ind[el_info->el->dof[i][n0]];

    if(dim > 0) {
      for (i = 0; i < N_NEIGH(dim); i++) {
	data->boundary[NEIGH_IND(dim, ne, i)] = wall_bound(el_info, i);
      }
    }

#if DIM_MAX > 2
    if(write_el_type) data->el_type[ne] = el_info->el_type;
#endif

    if (mesh->is_periodic) {
      int w, v;


      for (w = 0; w < N_WALLS(dim); w++) {
	EL *neigh = el_info->neigh[w], *el = el_info->el;

	if (neigh) {
	  const int *wall_ind;
	  const int *wall_ind_n;
	  int ov = el_info->opp_vertex[w];

	  wall_ind =
	    sorted_wall_vertices(dim, w, wall_orientation(dim, el,  w));
	  wall_ind_n =
	    sorted_wall_vertices(dim, ov, wall_orientation(dim, neigh, ov));

	  /* If this is a periodic wall, then _ALL_ vertices must be
	   * different from the neighbour vertices, so we just need to
	   * check one.
	   */
	  if (el->dof[wall_ind[0]][n0] != neigh->dof[wall_ind_n[0]][n0]) {
	    /* Now construct the combinatorical wall vertex transformations.
	     */
	    int nwt = data->n_wall_vtx_trafos++;

	    if (nwt % 100 == 0) {
	      data->wall_vtx_trafos = (int (*)[N_VERTICES(DIM_MAX-1)][2])
		MEM_REALLOC(data->wall_vtx_trafos,
			    nwt*sizeof(*data->wall_vtx_trafos),
			    (nwt+100)*sizeof(*data->wall_vtx_trafos),
			    char);
	    }
	    for (v = 0; v < N_VERTICES(dim-1); v++) {
	      data->wall_vtx_trafos[nwt][v][0] =
		vert_ind[el->dof[wall_ind[v]][n0]];
	      data->wall_vtx_trafos[nwt][v][1] =
		vert_ind[neigh->dof[wall_ind_n[v]][n0]];
	    }
	    data->el_wall_vtx_trafos[ne*N_WALLS(dim)+w] = nwt + 1;

	    if (data->el_wall_trafos) {
	      int mwall = el_info->macro_wall[w];
	      AFF_TRAFO *wt = el_info->macro_el->wall_trafo[mwall];
	      int i;

	      for (i = 0; i < mesh->n_wall_trafos; i++) {
		if (wt == mesh->wall_trafos[i]) {
		  break;
		}
	      }
	      DEBUG_TEST_EXIT(i < mesh->n_wall_trafos, "Unknown wall-trafo?!");
	      if (i % 2 == 0) {
		data->el_wall_trafos[ne*N_WALLS(dim)+w] = +(i / 2 + 1);
	      } else {
		data->el_wall_trafos[ne*N_WALLS(dim)+w] = -(i / 2 + 1);
	      }
	    }

	  } else {
	    data->el_wall_vtx_trafos[ne*N_WALLS(dim)+w] = 0;
	  }
	} else {
	  data->el_wall_vtx_trafos[ne*N_WALLS(dim)+w] = 0;
	}
      }
    }

#if ALBERTA_DEBUG
    data->mel_comment[ne] = mel_comment + ne*80;
    snprintf(data->mel_comment[ne], 80, " Id: %d; Level: %d;",
             el_info->el->index, el_info->level);
#endif
 
    ++ne;
  } TRAVERSE_NEXT();

/*--------------------------------------------------------------------------*/
/* Finally, we compute neighbour information. This seems to be the easiest  */
/* solution, since neighbour information in ALBERTA is only available as    */
/* pointers.                                                                */
/*--------------------------------------------------------------------------*/

  if(dim > 0) {
    compute_neigh_fast(data);
  }

  /* Cleanup the wall-transformations (need only half of them) */
  if (mesh->is_periodic) {
    int neigh, oppv, w;
    int nwt = 0;
    int (*old_vtx_wts)[N_VERTICES(DIM_MAX-1)][2] = data->wall_vtx_trafos;

    data->wall_vtx_trafos = (int (*)[N_VERTICES(DIM_MAX-1)][2])
      MEM_ALLOC(data->n_wall_vtx_trafos/2*sizeof(*data->wall_vtx_trafos), char);
    for (ne = 0; ne < data->n_macro_elements; ne++) {
      for (w = 0; w < N_WALLS(dim); w++) {
	if (data->el_wall_vtx_trafos[ne*N_WALLS(dim)+w] > 0) {
	  memcpy(data->wall_vtx_trafos[nwt],
		 old_vtx_wts[data->el_wall_vtx_trafos[ne*N_WALLS(dim)+w]-1],
		 sizeof(*data->wall_vtx_trafos));
	  data->el_wall_vtx_trafos[ne*N_WALLS(dim)+w] = nwt+1;
	  neigh = data->neigh[ne*N_NEIGH(dim)+w];
	  oppv = data->opp_vertex[ne*N_NEIGH(dim)+w];
	  data->el_wall_vtx_trafos[neigh*N_WALLS(dim)+oppv] = -(nwt+1);
	  ++nwt;
	}
      }
    }
    TEST_EXIT(data->n_wall_vtx_trafos == 2*nwt,
	      "Wall transformation do not seem to be reflexive!\n");
    MEM_FREE(old_vtx_wts, 
	     (data->n_wall_vtx_trafos + 100 - 1) / 100 * 100, char);
    data->n_wall_vtx_trafos = nwt;
  }

  cleanup_write_macro(NULL, dof_vert_ind, NULL);

  free_fe_space(fe_space);

  return data;
}


/*--------------------------------------------------------------------------*/
/* write_macro() writes the current mesh (at the level of leaf elements) as */
/* a macro triangulation to the specified file                              */
/*--------------------------------------------------------------------------*/

static int write_macro_master(MESH *mesh, const char *filename,
			      macro_format format)
{
  FUNCNAME("write_macro_master");
  int         result = 0; /* make gcc happy */
  MACRO_DATA  *data;

  if (!filename)
  {
    ERROR("no filename specified, filename is NULL pointer\n");
    return(0);
  }

  if (!mesh)
  {
    ERROR("no mesh specified, mesh is NULL pointer\n");
    return(0);
  }

  if(!(data = mesh2macro_data(mesh))) {
    ERROR("Could not convert mesh to a macro data structure!\n");
    return(0);
  }

  switch(format)
  {
  case ascii_format:
    result=write_macro_data(data, filename);
    break;
  case binary_format:
    result=write_macro_data_bin(data, filename);
    break;
  case xdr_format:
    result=write_macro_data_xdr(data, filename);
  }

  free_macro_data(data);

  return(result);
}


/*--------------------------------------------------------------------------*/
/* These routines are available to the user:                                */
/*--------------------------------------------------------------------------*/

/*--------------------------------------------------------------------------*/
/*  initialize and clear macro data structures                              */
/*--------------------------------------------------------------------------*/

MACRO_DATA *alloc_macro_data(int dim, int nv, int ne)
{
  FUNCNAME("alloc_macro_data");
  MACRO_DATA *data = MEM_CALLOC(1, MACRO_DATA);

  data->dim              = dim;
  data->n_total_vertices = nv;
  data->n_macro_elements = ne;

  data->coords       = MEM_ALLOC(nv, REAL_D);

  data->mel_vertices = MEM_ALLOC(ne*N_VERTICES(dim), int);

  return data;
}

void free_macro_data(MACRO_DATA *data)
{
  int dim = data->dim;
  int ne = data->n_macro_elements;
  int nv = data->n_total_vertices;

  MEM_FREE(data->coords, nv, REAL_D);

  MEM_FREE(data->mel_vertices, ne*N_VERTICES(dim), int);

  if (data->neigh) MEM_FREE(data->neigh, ne*N_NEIGH(dim), int);
  if (data->opp_vertex) MEM_FREE(data->opp_vertex, ne*N_NEIGH(dim), int);
  if (data->boundary) MEM_FREE(data->boundary, ne*N_NEIGH(dim), BNDRY_TYPE);
#if DIM_MAX > 2
  if (dim == 3 && data->el_type) MEM_FREE(data->el_type, ne, U_CHAR);
#endif
  if (data->wall_vtx_trafos) {
    MEM_FREE(data->wall_vtx_trafos,
	     data->n_wall_vtx_trafos*sizeof(*data->wall_vtx_trafos),
	     char);
  }
  if (data->el_wall_vtx_trafos) {
    MEM_FREE(data->el_wall_vtx_trafos, ne*N_WALLS(dim), int);
  }
  if (data->wall_trafos) {
    MEM_FREE(data->wall_trafos, data->n_wall_trafos, AFF_TRAFO);
  }
  if (data->el_wall_trafos) {
    MEM_FREE(data->el_wall_trafos, ne*N_WALLS(dim), int);
  }

#if ALBERTA_DEBUG
  if (data->mel_comment) {
    MEM_FREE(data->mel_comment[0], ne * 80, char);
    MEM_FREE(data->mel_comment, ne, char *);
  }
#endif

  MEM_FREE(data, 1, MACRO_DATA);

  return;
}

MACRO_DATA *read_macro(const char *filename)
{
  return read_macro_master(filename, ascii_format);
}

MACRO_DATA *read_macro_bin(const char *filename)
{
  return read_macro_master(filename, binary_format);
}

MACRO_DATA *read_macro_xdr(const char *filename)
{
  return read_macro_master(filename, xdr_format);
}


bool write_macro(MESH *mesh, const char *filename)
{
  return(write_macro_master(mesh, filename, ascii_format));
}

bool write_macro_bin(MESH *mesh, const char *filename)
{
  return(write_macro_master(mesh, filename, binary_format));
}

bool write_macro_xdr(MESH *mesh, const char *filename)
{
  return(write_macro_master(mesh, filename, xdr_format));
}

/*--------------------------------------------------------------------------*/
/* write raw macro triangulation in "data" to "filename" in standard ALBERTA*/
/* key format                                                               */
/*--------------------------------------------------------------------------*/

bool write_macro_data(MACRO_DATA *data, const char *filename)
{
  FUNCNAME("write_macro_data");
  FILE    *macro_file;
  int     i, j, dim = data->dim;

  if (!(macro_file = fopen(filename, "w")))
  {
    ERROR("could not open file %s for writing\n", filename);
    return false;
  }

  fprintf(macro_file, "%s: %d\n", keys[KEY_DIM], dim);
  fprintf(macro_file, "%s: %d\n\n", keys[KEY_DOW], DIM_OF_WORLD);

  fprintf(macro_file, "%s: %d\n", keys[KEY_NV], data->n_total_vertices);
  fprintf(macro_file, "%s: %d\n\n", keys[KEY_NEL], data->n_macro_elements);

  fprintf(macro_file, "%s:\n", keys[KEY_V_COORD]);
  for(i = 0; i < data->n_total_vertices; i++)
    for (j = 0; j < DIM_OF_WORLD; j++)
      fprintf(macro_file, "%23.16e%s", data->coords[i][j],
	      j < DIM_OF_WORLD-1 ? " " : "\n");

  fprintf(macro_file, "\n%s:\n", keys[KEY_EL_VERT]);
  for(i = 0; i < data->n_macro_elements; i++) {
    for (j = 0; j < N_VERTICES(dim); j++) {
      fprintf(macro_file, " %5d", data->mel_vertices[VERT_IND(dim, i, j)]);
    }
#if ALBERTA_DEBUG
    if (data->mel_comment) {
      fprintf(macro_file, " # %s", data->mel_comment[i]);
    }
#endif
    fprintf(macro_file, "\n");
  }
  

  if(data->boundary) {
    fprintf(macro_file, "\n%s:\n", keys[KEY_EL_BND]);
    for(i = 0; i < data->n_macro_elements; i++)
      for (j = 0; j < N_NEIGH(dim); j++)
	fprintf(macro_file, "%4d%s", data->boundary[NEIGH_IND(dim, i, j)],
		j < N_NEIGH(dim)-1 ? " " : "\n");
  }

  if(data->neigh) {
    fprintf(macro_file, "\n%s:\n", keys[KEY_EL_NEIGH]);
    for(i = 0; i < data->n_macro_elements; i++)
      for (j = 0; j < N_NEIGH(dim); j++)
	fprintf(macro_file, "%4d%s", data->neigh[NEIGH_IND(dim, i, j)],
		j < N_NEIGH(dim)-1 ? " " : "\n");
  }

#if DIM_MAX > 2
  if (dim == 3 && data->el_type)
  {
    fprintf(macro_file, "\n%s:\n", keys[KEY_EL_TYPE]);
    for(i = 0; i < data->n_macro_elements; i++)
      fprintf(macro_file, "%d%s", data->el_type[i],  ((i+1)%20) ? " ": "\n");
  }
#endif

  if (data->n_wall_trafos) {
    int wt;
    
    fprintf(macro_file, "\n%s: %d\n",
	    keys[KEY_N_WALL_TRAFOS], data->n_wall_trafos);
    if (data->el_wall_trafos) {
      fprintf(macro_file, "\n%s:\n", keys[KEY_EL_WALL_TRAFOS]);
      for(i = 0; i < data->n_macro_elements; i++) {
	for (j = 0; j < N_WALLS(dim); j++) {
	  fprintf(macro_file, "%4d%s",
		  data->el_wall_trafos[NEIGH_IND(dim, i, j)],
		  j < N_NEIGH(dim)-1 ? " " : "\n");
	}
      }
    }
    fprintf(macro_file, "\n%s:\n", keys[KEY_WALL_TRAFOS]);
    for (wt = 0; wt < data->n_wall_trafos; wt++) {
      fprintf(macro_file, "# wall transformation #%d\n", i);
      for (i = 0; i < DIM_OF_WORLD; i++) {
	for (j = 0; j < DIM_OF_WORLD; j++) {
	  fprintf(macro_file, "%23.16e ", data->wall_trafos[wt].M[i][j]);
	}
	fprintf(macro_file, "%23.16e\n", data->wall_trafos[wt].t[i]);
      }
      fprintf(macro_file, "0 0 0 1\n");
    }
  }

  if (data->n_wall_vtx_trafos) {
    fprintf(macro_file, "\n%s: %d\n",
	    keys[KEY_N_WALL_VTX_TRAFOS], data->n_wall_vtx_trafos);
    fprintf(macro_file, "\n%s:\n", keys[KEY_WALL_VTX_TRAFOS]);
    for (i = 0; i < data->n_wall_vtx_trafos; i++) {      
      fprintf(macro_file, "# wall vertex transformation #%d\n", i);
      for (j = 0; j < N_VERTICES(dim-1); j++) {
	fprintf(macro_file, "%4d %4d\n",
		data->wall_vtx_trafos[i][j][0], data->wall_vtx_trafos[i][j][1]);
      }
    }
  }

  fprintf(macro_file, "\n");

  fclose(macro_file);

  INFO(2, 2, "wrote macro file %s\n", filename);

  return true;
}

/*--------------------------------------------------------------------------*/
/* write raw macro triangulation in "data" to "filename" in native binary   */
/* format                                                                   */
/*--------------------------------------------------------------------------*/

bool write_macro_data_bin(MACRO_DATA *data, const char *filename)
{
  FUNCNAME("write_macro_data_bin");
  FILE *file;
  int  i, dim = data->dim;
  char record_written=1;
  char record_not_written=0;
  int blah;

  if(!data) {
    ERROR("no data - no file created\n");
    return 0;
  }

  if (!(file = fopen(filename, "wb"))) {
    ERROR("cannot open file %s\n", filename);
    return 0;
  }

  blah = fwrite(ALBERTA_VERSION, sizeof(char), strlen(ALBERTA_VERSION)+1, file);

  i = sizeof(REAL);
  blah = fwrite(&i, sizeof(int), 1, file);

  blah = fwrite(&dim, sizeof(int), 1, file);

  i = DIM_OF_WORLD;
  blah = fwrite(&i, sizeof(int), 1, file);

  blah = fwrite(&(data->n_total_vertices), sizeof(int), 1, file);
  blah = fwrite(&(data->n_macro_elements), sizeof(int), 1, file);

  blah = fwrite(data->coords, sizeof(REAL_D), data->n_total_vertices, file);
  blah = fwrite(data->mel_vertices, sizeof(int),
		N_VERTICES(dim) * data->n_macro_elements, file);

  if(data->boundary) {
    blah = fwrite(&record_written, sizeof(char), 1, file);
    blah = fwrite(data->boundary, sizeof(BNDRY_TYPE),
		  N_NEIGH(dim) * data->n_macro_elements, file);
  } else {
    blah = fwrite(&record_not_written, sizeof(char), 1, file);
  }

  if (data->neigh) {
    blah = fwrite(&record_written, sizeof(char), 1, file);
    blah = fwrite(data->neigh, sizeof(int),
		  N_NEIGH(dim) * data->n_macro_elements, file);
  } else {
    blah = fwrite(&record_not_written, sizeof(char), 1, file);
  }

#if DIM_MAX > 2
  if (dim == 3 && data->el_type) {
    blah = fwrite(&record_written, sizeof(char), 1, file);
    blah = fwrite(data->el_type, sizeof(U_CHAR), data->n_macro_elements, file);
  } else {
#endif
    blah = fwrite(&record_not_written, sizeof(char), 1, file);
#if DIM_MAX > 2
  }
#endif

  blah = fwrite("EOF.", sizeof(char), 4, file);
  blah = fclose(file);

  INFO(2, 2, "wrote macro binary-file %s\n", filename);

  (void)blah;
  
  return 1;
}

/*--------------------------------------------------------------------------*/
/* write raw macro triangulation in "data" to "filename" in xdr format      */
/*--------------------------------------------------------------------------*/

bool write_macro_data_xdr(MACRO_DATA *data, const char *filename)
{
  FUNCNAME("write_macro_data_xdr");
  XDR    *xdrp;
  int    i, length;
  char   *s;
  bool_t record_written=1;
  bool_t record_not_written=0;

  caddr_t array_loc;

  if(!data)
  {
    ERROR("no data - no file created\n");
    return(0);
  }

  if (!(xdrp = xdr_open_file(filename, XDR_ENCODE)))
  {
    ERROR("cannot open file %s\n", filename);
    return(0);
  }

  length = MAX(strlen(ALBERTA_VERSION) + 1, 5);  /* length with terminating \0 */
  s=MEM_ALLOC(length, char);
  strcpy(s, ALBERTA_VERSION);
  xdr_string(xdrp, &s, length);
  MEM_FREE(s, length, char);

  xdr_dim = data->dim;

  xdr_int(xdrp, &xdr_dim);

  i = DIM_OF_WORLD;
  xdr_int(xdrp, &i);

  xdr_int(xdrp, &(data->n_total_vertices));
  xdr_int(xdrp, &(data->n_macro_elements));

  array_loc=(caddr_t) data->coords;
  xdr_array(xdrp, &array_loc, (u_int *) &(data->n_total_vertices),
	    (u_int) data->n_total_vertices, sizeof(REAL_D),
	    (xdrproc_t) xdr_REAL_D);

  array_loc=(caddr_t) data->mel_vertices;
  xdr_array(xdrp, &array_loc, (u_int *) &(data->n_macro_elements),
	    (u_int) data->n_macro_elements * N_VERTICES(xdr_dim), sizeof(int),
	    (xdrproc_t) xdr_int);

  if(data->boundary) {
    xdr_bool(xdrp, &record_written);
    array_loc=(caddr_t) data->boundary;
    xdr_array(xdrp, &array_loc, (u_int *) &(data->n_macro_elements),
	      (u_int) data->n_macro_elements * N_NEIGH(xdr_dim),
	      sizeof(BNDRY_TYPE), (xdrproc_t) xdr_S_CHAR);
  }
  else xdr_bool(xdrp, &record_not_written);

  if(data->neigh) {
    xdr_bool(xdrp, &record_written);
    array_loc=(caddr_t) data->neigh;
    xdr_array(xdrp, &array_loc, (u_int *) &(data->n_macro_elements),
	      (u_int) data->n_macro_elements * N_NEIGH(xdr_dim), sizeof(int),
	      (xdrproc_t) xdr_int);
  }
  else xdr_bool(xdrp, &record_not_written);

#if DIM_MAX > 2
  if (xdr_dim == 3 && data->el_type) {
    xdr_bool(xdrp, &record_written);
    array_loc=(caddr_t) data->el_type;
    xdr_array(xdrp, &array_loc, (u_int *) &(data->n_macro_elements),
	      (u_int) data->n_macro_elements, sizeof(U_CHAR),
	      (xdrproc_t) xdr_U_CHAR);
  }
  else
#endif
    xdr_bool(xdrp, &record_not_written);

  xdr_close_file(xdrp);

  INFO(2, 2, "wrote macro xdr-file %s\n", filename);

  return(1);
}

static int find_matching_wall(MESH *mesh,
			      int cur_mel, const AFF_TRAFO *wt, int wall,
			      bool strict);

/* Transform the geomtric wall transformations specified by
 * init_wall_trafos() into topological wall transformations acting on
 * the vertex numbers of the elements. Check for consistency if the
 * macro triangulation specified combinatorical wall transformations
 * explicitly.
 *
 * This function is called _AFTER_ fill_neigh_info() and has to take
 * care of all additional neighbourhood relationships introduced by
 * periodic boundaries.
 *
 * This stuff looks a little bit ugly, but as the application has to
 * specifiy geometric wall-transformations anyway we can as well
 * derive the combinatorical transformations from the geometrical
 * ones.
 *
 * Return 0 if no face transformations apply, return 1 if at least one
 * is found and everything is consistent, return -1 if at least one is
 * found but the macro-triangulation is not compatible. If "strict"
 * bail out in this case.
 */
static bool
init_wall_transformations(MESH *mesh,
			  AFF_TRAFO *(*init_wall_trafos)(MESH *mesh,
							 MACRO_EL *mel,
							 int wall),
			  bool strict)
{
  FUNCNAME("init_wall_transformations");
  MACRO_EL *mel = mesh->macro_els;
  int dim = mesh->dim;
  int i, j, w;
  bool have_wall_trafos = false;
  AFF_TRAFO *cur_wt;

  if (init_wall_trafos) {
    AFF_TRAFO **wall_trafos, *wt_neigh;
    int n_wall_trafos;
    
    for (i = 0; i < mesh->n_macro_el; i++) {
      for (w = 0; w < N_WALLS(dim); w++) {
	cur_wt = mel[i].wall_trafo[w] = init_wall_trafos(mesh, mel + i, w);
	if (cur_wt) {
	  if (mesh->wall_trafos == NULL) {
	    mesh->wall_trafos =
	      MEM_ALLOC(mesh->n_macro_el * N_WALLS(dim), AFF_TRAFO *);
	    mesh->n_wall_trafos = 0;
	    have_wall_trafos = true;
	  }
	  for (j = 0; j < mesh->n_wall_trafos; j++) {
	    if (mesh->wall_trafos[j] == cur_wt) {
	      break;
	    }
	  }
	  if (j == mesh->n_wall_trafos) {
	    ((AFF_TRAFO **)mesh->wall_trafos)[mesh->n_wall_trafos++] = cur_wt;
	  }
	  switch (find_matching_wall(mesh, i, cur_wt, w, strict)) {
	  case  1:
	    break; /* success */
	  case -1: /* error */
	    if (strict) {
	      ERROR_EXIT(
		"Wall transformation identifies vertices in same element.\n");
	    } else {
	      WARNING(
		"Wall transformation identifies vertices in same element.\n");
	      have_wall_trafos = false;
	    }
	    break;
	  case  0: /* error */
	    if (strict) {
	      ERROR_EXIT(
		"Wall transformation does not seem to map walls to walls.\n");
	    } else {
	      WARNING(
		"Wall transformation does not seem to map walls to walls.\n");
	      have_wall_trafos = false;
	    }
	    break;
	  }
	}
      }
    }

    TEST_EXIT(mesh->n_wall_trafos > 0, "No wall transformations apply???\n");

    /* Make sure that the wall-transformations are grouped in pairs A,
     * A^{-1}
     */
    
    /* The fancy allocation size is just to keep the memory allocation
     * book-keeping right; mesh_free() only frees mesh->wall_trafos.
     */
    wall_trafos = (AFF_TRAFO **)
      MEM_CALLOC(mesh->n_wall_trafos*(sizeof(AFF_TRAFO *) + sizeof(AFF_TRAFO)),
		 char);
    n_wall_trafos = 0;
    if (have_wall_trafos) {
      /* Use the neighbourhood information */
      for (i = 0; i < mesh->n_macro_el; i++) {
	for (w = 0; w < N_WALLS(dim); w++) {
	  if ((cur_wt = mel[i].wall_trafo[w]) != NULL) {
	    for (j = 0; j < n_wall_trafos; j++) {
	      if (wall_trafos[j] == cur_wt) {
		break;
	      }
	    }
	    if (j == n_wall_trafos) {
	      wt_neigh = mel[i].neigh[w]->wall_trafo[mel[i].opp_vertex[w]];
	      wall_trafos[n_wall_trafos++] = cur_wt;
	      wall_trafos[n_wall_trafos++] = wt_neigh;
	    }
	  }
	}
      }
    } else {
      /* Cannot use the neighbourhood information, invert the
       * transformation and do it the "hard way".
       */
      i = 0;
      do {
	cur_wt = mesh->wall_trafos[i];
	((AFF_TRAFO **)mesh->wall_trafos)[i] = NULL;
	i++;
	for (j = 0; j < n_wall_trafos; j++) {
	  if (wall_trafos[j] == cur_wt) {
	    break;
	  }
	}
	if (j == n_wall_trafos) {
	  /* a new one, search for the inverse */
	  AFF_TRAFO cur_inv, *inv_wt = NULL;
	  REAL dist;
	  int k;
	  
	  INVAFF_DOW(cur_wt, &cur_inv);
	  for (k = 0; k < mesh->n_wall_trafos; k++) {
	    if (mesh->wall_trafos[k] == NULL) {
	      continue;
	    }
	    inv_wt = mesh->wall_trafos[k];
	    dist  = MDST2_DOW((const REAL_D *)cur_inv.M,
			      (const REAL_D *)inv_wt->M);
	    dist += DST2_DOW(cur_inv.t, inv_wt->t);
	    if (dist < 1e-24) {
	      break;
	    }
	  }
	  TEST_EXIT(k < mesh->n_wall_trafos,
		    "Wall transformation without _distinct_ inverse. "
		    "Involutions are not supported, sorry.\n");
	  wall_trafos[n_wall_trafos++] = cur_wt;
	  wall_trafos[n_wall_trafos++] = inv_wt;
	}
      } while (n_wall_trafos < mesh->n_wall_trafos);
    }
    /* strict or not strict: the following must hold. */
    TEST_EXIT(mesh->n_wall_trafos == n_wall_trafos,
	      "Data inconsistency: mesh->n_wall_trafos = %d, counted %d.\n",
	      mesh->n_wall_trafos, n_wall_trafos);
    MEM_FREE(mesh->wall_trafos, mesh->n_wall_trafos, AFF_TRAFO *);
    mesh->wall_trafos = wall_trafos;
  } else {
    have_wall_trafos = true;
    for (i = 0; i < mesh->n_macro_el; i++) {
      for (w = 0; w < N_WALLS(dim); w++) {
	if ((cur_wt = mel[i].wall_trafo[w]) != NULL) {
	  switch (find_matching_wall(mesh, i, cur_wt, w, strict)) {
	  case 1: /* success */
	    break;
	  case 0: /* error, not found */
	    if (strict) {
	      ERROR_EXIT(
		"Wall transformation does not seem to map walls to walls.\n");
	    } else {
	      WARNING(
		"Wall transformation does not seem to map walls to walls.\n");
	      have_wall_trafos = false;
	    }
	    break;
	  case -1: /* error, identification inside same elmenet */
	    if (strict) {
	      ERROR_EXIT(
		"Wall transformation identifies vertices in same element.\n");
	    } else {
	      WARNING(
		"Wall transformation identifies vertices in same element.\n");
	      have_wall_trafos = false;
	    }
	    break;
	  }
	} else {
	  int wtno;
	  
	  for (wtno = 0; wtno < mesh->n_wall_trafos; wtno++) {
	    cur_wt = mesh->wall_trafos[wtno];
	    switch (find_matching_wall(mesh, i, cur_wt, w, strict)) {
	    case 1: /* success */
	      mel[i].wall_trafo[w] = cur_wt;
 	      wtno = mesh->n_wall_trafos; /* break out of loop */
	      break;
	    case -1: /* error, identification inside same elmenet */
	      if (strict) {
		ERROR_EXIT(
		  "Wall transformation identifies vertices in same element.\n");
	      } else {
		WARNING(
		  "Wall transformation identifies vertices in same element.\n");
		have_wall_trafos = false;
		wtno = mesh->n_wall_trafos; /* break out of loop */
	      }
	      break;
	    }
	  }
	}
      }
    }
  }

  return have_wall_trafos;
}

/* Return:
 *
 * -1 if WT maps a vertex to another vertex in the same element (error case).
 *  0 if no matching wall in another element can be found.
 *  1 if a matching wall in another element can be found.
 */
static int find_matching_wall(MESH *mesh,
			      int cur_mel, const AFF_TRAFO *wt, int wall,
			      bool strict)
{
  FUNCNAME("find_matching_wall");
  MACRO_EL *mel = mesh->macro_els;
  int dim = mesh->dim;
  int j, k, l, wn, v;
  S_CHAR vmap[N_VERTICES(DIM_MAX-1)] = { 0, };
  REAL_D wall_img[N_VERTICES(DIM_MAX-1)] = { { 0.0, }, };
  REAL   wall_vnrm[N_VERTICES(DIM_MAX-1)] = { 0.0, };

  /* Compute the image of the wall-vertices under the action
   * of the _inverse_ wall transformation.
   */
  for (j = 0; j < N_VERTICES(dim-1); j++) {
    v = (wall + j + 1) % N_VERTICES(dim);
    AFFINV_DOW(wt, *mel[cur_mel].coord[v], wall_img[j]);
    wall_vnrm[j] = DIST_DOW(*mel[cur_mel].coord[wall], *mel[cur_mel].coord[v]);
    if (DIST_DOW(wall_img[j], *mel[cur_mel].coord[wall])
	<=
	1e-12*wall_vnrm[j]) {
      return -1; /* Maps to a vertex in the same element */
    }
  }
  
  /* Now search for the matching wall */
  for (j = 0; j < mesh->n_macro_el; j++) {
    if (cur_mel == j) {
      continue; /* must not map to the same element */
    }
    for (wn = 0; wn < N_WALLS(dim); wn++) {
      if (mel[j].neigh[wn] && mel[j].neigh[wn] != mel+cur_mel) {
	continue;
      }
      for (l = 0; l < N_VERTICES(dim-1); l++) {
	vmap[l] = -1;
      }
      for (k = 0; k < N_VERTICES(dim-1); k++) {
	v = (wn + k + 1) % N_VERTICES(dim);
	for (l = 0; l < N_VERTICES(dim-1); l++) {
	  if (vmap[l] == -1 &&
	      DIST_DOW(wall_img[l], *mel[j].coord[v])
	      <=
	      1e-12*wall_vnrm[l]) { /* we have a match */
	    vmap[l] = v;
	    break;
	  }
	}
	if (l == N_VERTICES(dim-1)) { /* does not match */
	  break;
	}
      }
      if (k == N_VERTICES(dim-1)) { /* found it */
	break;
      }
	  }
    if (wn < N_WALLS(dim)) { /* found a matching wall */
      
      if (mel[cur_mel].neigh[wall] == NULL) {
	/* Add neighbourhood and opp-vertex specification. */
	mel[cur_mel].neigh[wall]      = mel+j;
	mel[cur_mel].opp_vertex[wall] = wn;
      } else if (!(mel[cur_mel].neigh[wall] == mel+j &&
		   mel[cur_mel].opp_vertex[wall] == wn)) {
	if (strict) {
	  ERROR_EXIT("Wall transformation violates mesh connectivity.\n");
	} else {
	  WARNING("Wall transformation violates mesh connectivity.\n");
	  return false;
	}
      }

      if (mel[j].neigh[wn] == NULL) {
	mel[j].neigh[wn]      = mel+cur_mel;
	mel[j].opp_vertex[wn] = wall;
      } else if (!(mel[j].neigh[wn] == mel+cur_mel &&
		   mel[j].opp_vertex[wn] == wall)) {
	if (strict) {
	  ERROR_EXIT("Wall transformation violates mesh connectivity.\n");
	} else {
	  WARNING("Wall transformation violates mesh connectivity.\n");
	  return false;
	}
      }
      
      if (mel[cur_mel].neigh_vertices[wall][0] != -1) {
	/* already have a periodic mapping for this wall, so
	 * check for consistency
	 */
	for (k = 0; k < N_VERTICES(dim-1); k++) {
	  if (!(mel[cur_mel].neigh_vertices[wall][k] == vmap[k])) {
	    if (strict) {
	      ERROR_EXIT("Geometric and topological wall transformations "
			 "do not match.\n");
	    } else {
	      WARNING("Geometric and topological wall transformations "
		      "do not match.\n");
	      return false;
	    }
	  }
	}
      } else {
	/* Fill the neigh_vertices information */
	for (k = 0; k < N_VERTICES(dim-1); k++) {
	  mel[cur_mel].neigh_vertices[wall][k] = vmap[k];
	}
      }
      break;
    }
  }

  /* Return "true" if we found a matching wall, otherwise "false". */
  return j < mesh->n_macro_el;
}

static bool check_wall_transformations(MESH *mesh, bool strict)
{
  FUNCNAME("check_wall_transformations");
  MACRO_EL *mel = mesh->macro_els;
  int dim = mesh->dim;
  int i, j, k, w, wn, v, nv, nnv, wnv;
  bool need_refine = false;

  /* We do not allow wall transformations to map a vertex of a simplex
   * to another vertex on the same simplex; check this. Also check for
   * reflexivity.
   */
  for (i = 0; i < mesh->n_macro_el; i++) {
    for (w = 0; w < N_WALLS(dim); w++) {
      if (mel[i].neigh_vertices[w][0] != -1) {
	MACRO_EL *mel_n = mel[i].neigh[w];
	wn = mel[i].opp_vertex[w];
	
	if ((mel_n = mel[i].neigh[w]) == NULL) {
	  if (strict) {
	    ERROR_EXIT("Wall transformation, but no neighour.\n");
	  } else {
	    WARNING("Wall transformation, but no neighour.\n");
	    need_refine = true;
	    continue;
	  }
	}
	for (j = 0; j < N_VERTICES(dim-1); j++ ) {
	  REAL_D *ncoord;

	  v = (w + j + 1) % N_VERTICES(dim);
	  nv = mel[i].neigh_vertices[w][j];
	  if (nv < wn) {
	    wnv = nv + N_VERTICES(dim) - wn - 1;
	  } else {
	    wnv = nv - wn - 1;
	  }
	  nnv = mel_n->neigh_vertices[wn][wnv];
	  TEST_EXIT(v == nnv,
		    "Wall transformations are not inverse to each other.\n");

	  ncoord = mel_n->coord[nv];
	  for (k = 0; k < N_VERTICES(dim); k++) {
	    if (!(ncoord != mel[i].coord[k])) {
	      if (strict) {
		ERROR_EXIT("Vertices must not be mapped to vertices of "
			   "the same element.\n");
	      } else {
		WARNING("Vertices must not be mapped to vertices of "
			"the same element.\n");
		need_refine = true;
	      }
	    }
	  }
	}
      }
    }
  }

  return !need_refine;
}

/* Try to refine the mesh s.t. that the
 * no-vertex-maps-to-the-same-element property is fulfilled. We do so
 * by using global refinement ATM. More sophisticated algorithms could
 * be implemented.
 *
 * We only support the case when the periodic structure in induced by
 * geometric face-transformations, simply because the case of pure
 * combinatorical face-transformations is more complicated and I'm not
 * in the mood to implement that right now.
 */
static void
try_resolve_periodic_walls(MESH *mesh, const MACRO_DATA *data,
			   NODE_PROJECTION *(*init_node_proj)(
			     MESH *, MACRO_EL *, int),
			   AFF_TRAFO *(*init_wall_trafos)(
			     MESH *, MACRO_EL *, int wall))
{
  FUNCNAME("try_resolve_periodic_walls");
  MACRO_DATA np_data[1]; /* not-periodic clone of "data" */
  MACRO_DATA *p_data;    /* macro-data generated from globally refined mesh */
  int dim = mesh->dim;
  MESH *np_mesh, *p_mesh, swap;
  int i, j, el, nwt;

  *np_data = *data;
  np_data->n_wall_trafos      = 0;
  np_data->wall_trafos        = NULL;
  np_data->el_wall_trafos     = NULL;
  np_data->n_wall_vtx_trafos  = 0;
  np_data->wall_vtx_trafos    = NULL;
  np_data->el_wall_vtx_trafos = NULL;

  /* Generate a new mesh and refine it globally s.t. each edge is
   * bisected at least once. If the issue can be resolved at all by
   * refinement, then this will hack it.
   */
  np_mesh =
    _AI_get_mesh(dim, "temporary periodic mesh", np_data, init_node_proj, NULL,
		 true /* strict_periodic */);
  global_refine(np_mesh, np_mesh->dim, FILL_NOTHING);
  p_data = mesh2macro_data(np_mesh);

  p_data->wall_trafos    = MEM_ALLOC(mesh->n_wall_trafos/2, AFF_TRAFO);
  p_data->n_wall_trafos  = mesh->n_wall_trafos/2;
  p_data->el_wall_trafos =
    MEM_CALLOC(p_data->n_macro_elements*N_WALLS(dim), int);
  for (i = 0; i < p_data->n_wall_trafos; i++) {
    p_data->wall_trafos[i] = *mesh->wall_trafos[2*i];
  }

  /* Now we have to generate the missing periodic information. We
   * exploit the fact that mesh2macro_data() enumerates the macro
   * elements in the order of an ordinary leaf-element traversal.
   *
   * We also exploit the fact that the macro-elements of mesh and
   * np_mesh occur in the same order.
   *
   * The periodic mesh was carrying geometric wall-transformations,
   * and these are still available, we simply have to collect them.
   */
  el = 0;
  TRAVERSE_FIRST(np_mesh, -1, CALL_LEAF_EL|FILL_MACRO_WALLS) {
    MACRO_EL *p_mel;
    int mi;
    AFF_TRAFO *wt;
    
    mi = el_info->macro_el->index;
    p_mel = &mesh->macro_els[mi];
    
    for (i = 0; i < N_WALLS(dim); i++) {
      int mwall = el_info->macro_wall[i];
      if (mwall < 0) {
	continue;
      }
      if ((wt = p_mel->wall_trafo[mwall]) == NULL) {
	continue;
      }
      for (j = 0;
	   j < mesh->n_wall_trafos && mesh->wall_trafos[j] != wt;
	   j++);
      DEBUG_TEST_EXIT(j < mesh->n_wall_trafos,
		      "Wall transformation not found.");
      if (j % 2 != 0) {
	j = -j/2-1;
      } else {
	j =  j/2+1;
      }
      p_data->el_wall_trafos[NEIGH_IND(dim, el, i)] = j;
    }
    
    el++;
    
  } TRAVERSE_NEXT();

  p_mesh = _AI_get_mesh(dim, mesh->name, p_data, NULL, NULL, true /* strict */);

  /* It remains to copy over the node-projections from the old mesh,
   * and to adjust the pointers to the face transformations in case
   * the application came along with its own init_wall_trafos() hook.
   */
  el = 0;
  TRAVERSE_FIRST(np_mesh, -1, CALL_LEAF_EL|FILL_MACRO_WALLS) {
    
    p_mesh->macro_els[el].projection[0] = el_info->macro_el->projection[0];

    for (i = 0; i < N_WALLS(dim); i++) {
      int mwall = el_info->macro_wall[i];
      if (mwall < 0) {
	continue;
      }
      p_mesh->macro_els[el].projection[i+1] =
	el_info->macro_el->projection[mwall];
    }
    
    el++;
  } TRAVERSE_NEXT();

  if (init_wall_trafos) {
    for (el = 0; el < p_mesh->n_macro_el; el++) {
      MACRO_EL *mel = &p_mesh->macro_els[el];
      
      for (i = 0; i < N_WALLS(dim); i++) {
	if ((nwt = p_data->el_wall_trafos[NEIGH_IND(dim, el, i)]) > 0) {
	  nwt--;
	  mel->wall_trafo[i] = mesh->wall_trafos[2*nwt];
	} else if (nwt < 0) {
	  nwt = - nwt - 1;
	  mel->wall_trafo[i] = mesh->wall_trafos[2*nwt+1];
	}
      }
    }
    memcpy((AFF_TRAFO **)p_mesh->wall_trafos, mesh->wall_trafos,
	   mesh->n_wall_trafos * sizeof(AFF_TRAFO *));
  }

  free_mesh(np_mesh);
  free_macro_data(p_data);

  swap = *mesh;
  *mesh = *p_mesh;
  *p_mesh = swap;
  
  free_mesh(p_mesh);
}

/* Compute the boundary classification for lower-dimensional
 * sub-simplices (vertices and edges (3d)) on the macro level. If
 * COUNT == TRUE, then also update the vertex, edge, face numbers in
 * MESH, otherwise just fill in the boundary information.
 */
void _AI_fill_bound_info(MESH *mesh,
			 int *mel_vertices, int nv, int ne, bool count)
{
  FUNCNAME("_AI_fill_bound_info");

  switch (mesh->dim) {
  case 1:
    fill_bound_info_1d(mesh, mel_vertices, nv, ne);
    /*fill_mel_orientation_1d(mesh->macro_els, mesh->n_macro_el);*/
    break;
#if DIM_MAX > 1
  case 2:
    fill_bound_info_2d(mesh, mel_vertices, nv, ne);
    if (count) {
      count_edges_2d(mesh);
    }
    /*fill_mel_orientation_2d(mesh->macro_els, mesh->n_macro_el);*/
    break;
#if DIM_MAX > 2
  case 3:
    fill_bound_info_3d(mesh, mel_vertices, nv, ne);
    fill_more_bound_info_3d(mesh, mel_vertices, ne, count);
    /*fill_mel_orientation_3d(mesh->macro_els, mesh->n_macro_el);*/
    break;
#endif
#endif
  default:
    ERROR_EXIT("Illegal dimension %d!\n", mesh->dim);
  }
}

static void init_node_projections(MESH *mesh,
				  NODE_PROJECTION *(*init_node_proj)(
				    MESH *, MACRO_EL *, int))
{
  int i;
#if DIM_MAX > 1
  int j, dim = mesh->dim;
#endif
  MACRO_EL *mel = mesh->macro_els;
  
  /****************************************************************************
   * Call the user-defined new vertex projection assignment routine
   * "init_node_proj".
   ***************************************************************************/
  if (init_node_proj) {
    for (i = 0; i < mesh->n_macro_el; i++) {
      mel[i].projection[0] = init_node_proj(mesh, mel + i, 0);

#if DIM_MAX > 1
      if(dim == 2)
	for(j = 1; j < N_NEIGH_2D + 1; j++)
	  mel[i].projection[j] = init_node_proj(mesh, mel + i, j);
#if DIM_MAX > 2
      else if(dim == 3)
	for(j = 1; j < N_NEIGH_3D + 1; j++)
	  mel[i].projection[j] = init_node_proj(mesh, mel + i, j);
#endif
#endif

#define REALLY_DONT_USE_THIS_CODE true
#if !REALLY_DONT_USE_THIS_CODE
      /* No point in doing this at the moment, DK. */
      /* cH: I think we really should do this. Shouldn't we? FIXME! */
      /* cH: Update: there is no point in doing this _at_ _all_, the
       * code below breaks periodic meshes.
       */
/****************************************************************************/
/* If necessary, copy projections to neighbour elements.                    */
/****************************************************************************/
      for (j = 0; j < N_NEIGH(dim); j++) {
	NODE_PROJECTION *tmp_proj;
	MACRO_EL *neigh;

	if ((neigh = mel[i].neigh[j]) != NULL) {
	  int k;
	  
	  tmp_proj = mel[i].projection[0];

	  if (mel[i].projection[j+1])
	    tmp_proj = mel[i].projection[j+1];

	  /* Search for the correct subsimplex on the neighbour element. */
	  for (k = 0; k < N_NEIGH(dim); k++)
	    if (neigh->neigh[k] == mel + i) break;

	  neigh->projection[k+1] = tmp_proj;
	}
      }
#endif
    }
  }
}

/***************************************************************************/
/*  macro_data2mesh():                                                     */
/*  copy macro data to the MESH structure "mesh" provided:                 */
/*  1) set most entries in "mesh"                                          */
/*  2) allocate macro elements and link them to "mesh"                     */
/*  3) calculate macro element orientation for 3D                          */
/*  4) calculate the mesh size for "mesh->bbox"                            */
/*  5) Initialize slave meshes                                             */
/*                                                                         */
/*  the entire MACRO_DATA structure can be freed after use!                */
/***************************************************************************/

void
macro_data2mesh(MESH *mesh, const MACRO_DATA *data,
		NODE_PROJECTION *(*init_node_proj)(MESH *, MACRO_EL *, int),
		AFF_TRAFO *(*init_wall_trafos)(MESH *, MACRO_EL *, int wall))
{
  _AI_macro_data2mesh(mesh, data, init_node_proj, init_wall_trafos, false);
}

void
_AI_macro_data2mesh(MESH *mesh, const MACRO_DATA *data,
		    NODE_PROJECTION *(*init_node_proj)(MESH *, MACRO_EL *, int),
		    AFF_TRAFO *(*init_wall_trafos)(
		      MESH *, MACRO_EL *, int wall),
		    bool strict_periodic)
{
  FUNCNAME("macro_data2mesh");
  int      i, j, dim = data->dim;
  MACRO_EL *mel;
  REAL_D   *newcoords;

  TEST_EXIT(mesh, "no mesh, mesh is NULL pointer!\n");

  mesh->dim = dim;

  mesh->n_elements =
    mesh->n_hier_elements =
    mesh->n_macro_el = data->n_macro_elements;
  mesh->n_vertices = data->n_total_vertices;

  mel = mesh->macro_els = MEM_CALLOC(data->n_macro_elements, MACRO_EL);

  newcoords = MEM_ALLOC(data->n_total_vertices, REAL_D);

  for(i = 0; i < data->n_total_vertices; i++)
    COPY_DOW(data->coords[i], newcoords[i]);

  ((MESH_MEM_INFO *)mesh->mem_info)->count = data->n_total_vertices;
  ((MESH_MEM_INFO *)mesh->mem_info)->coords = newcoords;

  for(i = 0; i < data->n_macro_elements; i++) {
    mel[i].el = get_element(mesh);

    mel[i].index = i;
#if ALBERTA_DEBUG
    mel[i].el->index = i;
#endif

    for(j = 0; j < N_VERTICES(dim); j++)
      mel[i].coord[j] = &newcoords[data->mel_vertices[VERT_IND(dim, i, j)]];

#if DIM_MAX >= 3
    if(dim == 3) {
      mel[i].el_type = data->el_type ? data->el_type[i] : 0;
      mel[i].orientation = AI_get_orientation_3d(mel + i);
    }
#endif
  }

  if (mesh->parametric)
    WARNING("mesh->bbox may not be computed correctly, "
	    "problems with graphical output may occur\n");
  /* else */ /* should still be better than nothing */
  calculate_size(mesh, data);

  if(dim > 0) {
    TEST_EXIT(data->neigh != NULL,
	      "Neighbour information must be present!\n");
    TEST_EXIT(data->boundary != NULL,
	      "Boundary information must be present!\n");

    fill_neigh_info(mel, data);

    /* Initialize periodic boundaries before filling the boundary
     * information. However, we need basic information about
     * wall-boundaries because init_wall_trafos() will probably need
     * such information to do its job. Therefore we fill the
     * wall-boundary information (without checking) at this point.
     *
     * We also compute the number of vertex orbits (per_n_vertices)
     * here.
     *
     * Note: the application may choose to override the
     * wall-transformations specified in the macro triangulation;
     * init_wall_trafos() takes precedence, at any case.
     *
     * For simplicity we store the wall-transformation _AND_ their
     * inverses.
     */
    if (data->n_wall_trafos && init_wall_trafos == NULL) {
      int wt;
      
      mesh->n_wall_trafos = 2*data->n_wall_trafos;
      mesh->wall_trafos   = MEM_ALLOC(mesh->n_wall_trafos, AFF_TRAFO *);
      ((AFF_TRAFO **)mesh->wall_trafos)[0] =
	MEM_ALLOC(mesh->n_wall_trafos, AFF_TRAFO);
      for (wt = 0; wt < data->n_wall_trafos; wt++) {
	((AFF_TRAFO **)mesh->wall_trafos)[2*wt] = mesh->wall_trafos[0]+2*wt;
	*mesh->wall_trafos[2*wt] = data->wall_trafos[wt];
	((AFF_TRAFO **)mesh->wall_trafos)[2*wt+1] = mesh->wall_trafos[0]+2*wt+1;
	INVAFF_DOW(&data->wall_trafos[wt], mesh->wall_trafos[2*wt+1]);
      }
      mesh->is_periodic = true;
      if (data->el_wall_trafos) {
	/* Even the mapping to the macro element walls was
	 * specified. Use it. In this case the mapping of faces to
	 * faces must be consistent and we will _NOT_ attempt to
	 * resolve conflicts by global refinement.
	 */
	for (i = 0; i < mesh->n_macro_el; i++) {
	  for (j = 0; j < N_WALLS(dim); j++) {
	    int wt;
	    if ((wt = data->el_wall_trafos[NEIGH_IND(dim, i, j)]) > 0) {
	      mel[i].wall_trafo[j] = mesh->wall_trafos[2*(wt - 1)];
	    } else if (wt < 0) {
	      mel[i].wall_trafo[j] = mesh->wall_trafos[-2*(wt + 1) + 1];
	    }
	  }
	}
      }
    }
    if (init_wall_trafos || mesh->n_wall_trafos > 0) {
      int (*wall_vtx_trafos)[N_VERTICES(DIM_MAX-1)][2];
      int nwt;

      for (i = 0; i < mesh->n_macro_el; i++) {
	for (j = 0; j < N_NEIGH(dim); j++) {
	  mel[i].wall_bound[j] = data->boundary[NEIGH_IND(dim, i, j)];
	}
      }
      mesh->is_periodic = true;
      if (!init_wall_transformations(mesh, init_wall_trafos, strict_periodic) ||
	  !check_wall_transformations(mesh, strict_periodic)) {
	if (strict_periodic) {
	  ERROR_EXIT("No non-trivial wall-transformation, or "
		     "incompatible macro-triangulation.\n");
	} else {
	  WARNING("Trying to resolve periodic "
		  "boundaries by global refinement.\n");
	}
	try_resolve_periodic_walls(mesh, data,
				   init_node_proj, init_wall_trafos);
	return;
      }
	
      nwt = _AI_compute_macro_wall_trafos(mesh, &wall_vtx_trafos);
      mesh->per_n_vertices = mesh->n_vertices;
      (void)_AI_wall_trafo_vertex_orbits(dim, wall_vtx_trafos, nwt,
					 NULL, &mesh->per_n_vertices);
      MEM_FREE(wall_vtx_trafos, nwt*N_VERTICES(DIM_MAX-1)*2, int);
    } else {
      mesh->is_periodic = data->n_wall_vtx_trafos > 0;
      if (mesh->is_periodic) {
	mesh->per_n_vertices = mesh->n_vertices;
	(void)_AI_wall_trafo_vertex_orbits(dim, data->wall_vtx_trafos,
					   data->n_wall_vtx_trafos,
					   0, &mesh->per_n_vertices);
      }
    }

    for(i = 0; i < data->n_macro_elements; i++) {
      for(j = 0; j < N_WALLS(dim); j++) {
	mesh->macro_els[i].wall_bound[j] = data->boundary[NEIGH_IND(dim, i, j)];
      }
    }

    _AI_fill_bound_info(mesh,
			data->mel_vertices, mesh->n_vertices, mesh->n_elements,
			true);
    
  }

  init_node_projections(mesh, init_node_proj);

}

void macro_test(MACRO_DATA *data, const char *new_name)
{
  FUNCNAME("macro_test");

  switch(data->dim) {
  case 0:
    break;
  case 1:
    macro_test_1d(data, new_name);
    break;
#if DIM_MAX > 1
  case 2:
    macro_test_2d(data, new_name);
    break;
#endif
#if DIM_MAX > 2
  case 3:
    macro_test_3d(data, new_name);
    break;
#endif
  default:
    ERROR_EXIT("Illegal dim == %d!\n", data->dim);
  }

  /* We do not allow mapping a vertex of a simplex to another vertex
   * on the same element. Otherwise we would have to cope with
   * pathological cases which can occur only with the unrefined macro
   * triangulation (almost only ...). The benefit is that we can use
   * the global numbering of the vertices (by means of a vertex
   * DOF_ADMIN) to determine things like orientation etc.
   */
  if (data->n_wall_vtx_trafos) {
    int el, wt, v, w, wv;
    int dim = data->dim;

    for (el = 0; el < data->n_macro_elements; ++el) {
      for (w = 0; w < N_WALLS(dim); w++) {
	if ((wt = data->el_wall_vtx_trafos[N_WALLS(dim)*el+w]) != 0) {
	  int img;
	  if (wt > 0) {
	    --wt;
	    img = 1;
	  } else {
	    wt = -wt-1;
	    img = 0;
	  }
	  for (wv = 0; wv < N_VERTICES(dim-1); wv++) {
	    int vertex_image = data->wall_vtx_trafos[wt][wv][img];
	    for (v = 0; v < N_VERTICES(dim); v++) {
	      TEST_EXIT(vertex_image
			!= data->mel_vertices[N_VERTICES(dim)*el+v],
			"ERROR: Unsupported feature in the context of periodic "
			"meshes: The walls of elements may not be mapped onto "
			"another wall on the same element; you have to refine "
			"your macro triangulation. Element nr: %d, "
			"wall trafo: %d, vertex (src/dst): %d/%d\n",
			el, wt,
			data->wall_vtx_trafos[wt][wv][1-img],
			vertex_image);
	    }
	  }
	}
      }
    }
  }
}
