/*--------------------------------------------------------------------------*/
/* ALBERTA:  an Adaptive multi Level finite element toolbox using           */
/*           Bisectioning refinement and Error control by Residual          */
/*           Techniques for scientific Applications                         */
/*--------------------------------------------------------------------------*/
/*                                                                          */
/* file:     submesh.c                                                      */
/*                                                                          */
/*                                                                          */
/* description: Support for master/slave meshes                             */
/*                                                                          */
/*--------------------------------------------------------------------------*/
/*                                                                          */
/*  authors:   Daniel Koester                                               */
/*             Institut fuer Mathematik                                     */
/*             Universitaet Augsburg                                        */
/*             Universitaetsstr. 14                                         */
/*             D-86159 Augsburg, Germany                                    */
/*                                                                          */
/*             Claus-Justus Heine                                           */
/*             Abteilung fuer Angewandte Mathematik                         */
/*             Universitaet Freiburg                                        */
/*             Hermann-Herder-Strasse 10                                    */
/*             79104 Freiburg, Germany                                      */
/*                                                                          */
/*  http://www.alberta-fem.de                                               */
/*                                                                          */
/*  (c) by D. Koester (2004),                                               */
/*         C.-J. Heine (2006-2009).                                         */
/*--------------------------------------------------------------------------*/

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

#include "submesh_1d.c"
#if DIM_MAX > 1
#include "submesh_2d.c"
#if DIM_MAX > 2
#include "submesh_3d.c"
#endif
#endif

static inline
void bind_one_mel(MESH *master, MACRO_EL *m_mel, int wall, DOF_PTR_VEC *s_dpv,
		  MESH *slave, MACRO_EL *s_mel, DOF_PTR_VEC *m_dpv)  
{
  int m_dim = master->dim;
  
  /* Here we take care of node projection function transfer. */
  if (m_dim > 1) {
    if (m_mel->projection[wall+1])
      s_mel->projection[0] = m_mel->projection[wall+1];
    else
      s_mel->projection[0] = m_mel->projection[0];
  }

  if (m_dim == 1)
    join_elements_recursive_1d(master, slave,
			       m_dpv->fe_space->admin,
			       s_dpv->fe_space->admin,
			       m_dpv, s_dpv,
			       wall, m_mel->el, s_mel->el);
#if DIM_MAX > 1
  else if (m_dim == 2)
    join_elements_recursive_2d(master, slave,
			       m_dpv->fe_space->admin,
			       s_dpv->fe_space->admin,
			       m_dpv, s_dpv,
			       wall, m_mel->el, s_mel->el);
#endif
#if DIM_MAX >= 3
  else if (m_dim == 3)
    join_elements_recursive_3d(master, slave,
			       m_dpv->fe_space->admin,
			       s_dpv->fe_space->admin,
			       m_dpv, s_dpv,
			       wall, m_mel->el, s_mel->el,
			       m_mel->orientation, m_mel->el_type);
#endif
}

/* Bind a given mesh (e.g. read from disk by read_mesh) to a master
 * mesh. SLAVE and MASTER should be compatible, otherwise the routine
 * will catch an error.
 */
void bind_submesh(MESH *master,
		  MESH *slave,
		  bool (*binding_method)(MESH *master, MACRO_EL *el,
					 int wall, void *data),
		  void *data)
{
  FUNCNAME("bind_submesh");
  MESH_MEM_INFO   *s_info, *m_info;
  MACRO_EL        *m_mel, *s_mel;
  const DOF_ADMIN *admin = NULL;
  const FE_SPACE  *m_space, *s_space;
  DOF_PTR_VEC     *m_dpv, *s_dpv;
  int              n_dof[N_NODE_TYPES] = { 0, };
  int              i, j, n, m_dim;

/****************************************************************************/
/* Do the checks for obvious user errors.                                   */
/****************************************************************************/

  TEST_EXIT(master, "No master mesh given!\n");
  TEST_EXIT(master->dim > 0, "Master mesh has dim == 0!\n");
  m_dim = master->dim;

/****************************************************************************/
/* Set the mem_info components in master and slave.                         */
/****************************************************************************/

  s_info = (MESH_MEM_INFO *)slave->mem_info;
  m_info = (MESH_MEM_INFO *)master->mem_info;

  m_info->slaves = MEM_REALLOC(m_info->slaves,
			       m_info->n_slaves,
			       m_info->n_slaves + 1,
			       MESH *);

  m_info->slaves[m_info->n_slaves] = slave;

  m_info->n_slaves++;

  slave->trace_id = m_info->next_trace_id++;

  s_info->master         = master;

/* Check for the correct DOF_ADMINs for slave and master.                    */

  n_dof[CENTER] = 1;

  for (i = 0; i < slave->n_dof_admin; i++) {
    admin = slave->dof_admin[i];
    for (j = 0; j < N_NODE_TYPES; j++) {
      if (admin->n_dof[j] != n_dof[j]) goto bad_admin;
    }
    if (!(admin->flags == ADM_PRESERVE_COARSE_DOFS)) goto bad_admin;
    break;
  bad_admin:
    admin = NULL;
  }
  TEST_EXIT(admin, "Slave mesh does not seem to have had a master!\n");
  s_space = get_dof_space(slave, "Center FE_SPACE", n_dof,
			  ADM_PRESERVE_COARSE_DOFS);

  n_dof[CENTER] = 0;
  switch(m_dim) {
  case 1:
    n_dof[VERTEX] = 1;
    break;
  case 2:
    n_dof[EDGE] = 1;
    break;
  case 3:
    n_dof[FACE] = 1;
    break;
  }

  for (i = 0; i < master->n_dof_admin; i++) {
    admin = master->dof_admin[i];
    for (j = 0; j < N_NODE_TYPES; j++) {
      if (admin->n_dof[j] != n_dof[j]) goto bad_admin2;
    }
    if (!(admin->flags == ADM_PRESERVE_COARSE_DOFS)) goto bad_admin2;
    break;
  bad_admin2:
    admin = NULL;
  }
  TEST_EXIT(admin,"Given master mesh does not seem to have had slaves!\n");
  m_space = get_dof_space(master, "Wall FE_SPACE", n_dof,
			  ADM_PRESERVE_COARSE_DOFS);

/* Allocate element pointer vectors.                                         */

  s_dpv = get_dof_ptr_vec("Slave - master pointers", s_space);
  s_info->master_binding  = s_dpv;

  m_dpv = get_dof_ptr_vec("Master - slave pointers", m_space);
  s_info->slave_binding   = m_dpv;

  switch(m_dim) {
  case 1:
    m_dpv->refine_interpol = master_interpol_1d;
    m_dpv->coarse_restrict = master_restrict_1d;
    break;
#if DIM_MAX > 1
  case 2:
    m_dpv->refine_interpol = master_interpol_2d;
    m_dpv->coarse_restrict = master_restrict_2d;
    break;
#if DIM_MAX >= 3
  case 3:
    m_dpv->refine_interpol = master_interpol_3d;
    m_dpv->coarse_restrict = master_restrict_3d;
#endif
#endif
  }

/****************************************************************************/
/* Set the element pointer vec entries to the correct values.               */
/* This assumes that slave macro elements were allocated in the order given */
/* by the loop below.                                                       */
/****************************************************************************/

  FOR_ALL_DOFS(s_dpv->fe_space->admin, s_dpv->vec[dof] = NULL);
  FOR_ALL_DOFS(m_dpv->fe_space->admin, m_dpv->vec[dof] = NULL);

  if (binding_method == NULL) {
    /* We assume here that the two meshes are already chained on the
     * macro element level.
     */
    for (n = 0; n < slave->n_macro_el; n++) {
      s_mel = slave->macro_els + n;
      m_mel = s_mel->master.macro_el;
      i     = s_mel->master.opp_vertex;
      TEST_EXIT(m_mel != NULL,
		"Meshes are not chained on the macro-element level.\n");
      TEST_EXIT(i >= 0,
		"Garbled slave->master binding (macro-element level).\n");
      
      bind_one_mel(master, m_mel, i, s_dpv, slave, s_mel, m_dpv);
    }
  } else {
    MACRO_EL *s_mel     = slave->macro_els;
    MACRO_EL *s_mel_end = s_mel + slave->n_macro_el;
    for (n = 0; n < master->n_macro_el; n++) {
      m_mel = master->macro_els + n;

      for (i = 0; i < N_NEIGH(m_dim); i++)
	if (binding_method(master, m_mel, i, data)) {
	  TEST_EXIT(s_mel < s_mel_end,
		    "Ran out of slave macro elements... Wrong meshes?\n");

	  bind_one_mel(master, m_mel, i, s_dpv, slave, s_mel, m_dpv);

	  /* Set links for FILL_MASTER_INFO during mesh-traversal */
	  s_mel->master.macro_el   = m_mel;
	  s_mel->master.opp_vertex = i;
	
	  s_mel++;
	}
    }
  }

  free_fe_space(s_space);
  free_fe_space(m_space);
}

/****************************************************************************/
/* read_submesh_gen(read_xdr, master, slave_filename, binding_method,       */
/*                  init_boundary):                                         */
/* Read a slave mesh from file "slave_name" and bind it to "master". Assumes*/
/* that master and slave were written at the same time. Available for native*/
/* and XDR formats. Return a pointer to the slave mesh.                     */
/****************************************************************************/

static
MESH *read_submesh_gen(int read_xdr, MESH *master,
		       const char *slave_filename,
		       bool (*binding_method)(MESH *master, MACRO_EL *el,
					      int wall, void *data),
		       void *data)
{
  FUNCNAME("read_submesh_gen");
  MESH            *slave = NULL;

/****************************************************************************/
/* Do the checks for obvious user errors.                                   */
/****************************************************************************/

  TEST_EXIT(master, "No master mesh given!\n");
  TEST_EXIT(master->dim > 0, "Master mesh has dim == 0!\n");
  TEST_EXIT(slave_filename, "No filename for the slave mesh given!\n");
  TEST_EXIT(binding_method, "No binding method given!\n");

/****************************************************************************/
/* Read the mesh from the file. Do not use the time value pointer.          */
/****************************************************************************/
  if (read_xdr)
    slave = read_mesh_xdr(slave_filename, NULL, NULL, NULL);
  else
    slave = read_mesh(slave_filename, NULL, NULL, NULL);

  bind_submesh(master, slave, binding_method, data);

  return slave;
}

/****************************************************************************/
/* Interface to the maintainers                                             */
/****************************************************************************/

/****************************************************************************/
/* AI_check_slavery(master): Do a consistency check for all submeshes.      */
/****************************************************************************/

void AI_check_slavery(MESH *master)
{
  FUNCNAME("AI_check_slavery");
  MESH_MEM_INFO   *m_mem_info, *s_mem_info;
  int              i, k, n_slaves, slave_el_count;
  MESH            *slave;
  DOF_PTR_VEC     *m_dpv, *s_dpv;
  TRAVERSE_STACK  *stack;
  const DOF_ADMIN *m_admin, *s_admin;
  const EL_INFO   *m_el_info, *s_el_info;
  const EL        *m_el, *s_el;

  if (!master) {
    MSG("No mesh provided!\n");
    return;
  }
  TEST_EXIT(m_mem_info = (MESH_MEM_INFO *)(master->mem_info),
    "No memory management present for \"%s\"!\n", master->name);
  n_slaves = m_mem_info->n_slaves;

  if (!n_slaves) {
    INFO(4,4,"Mesh \"%d\" has no slaves.\n", master->name);
    return;
  }

  stack = get_traverse_stack();

/****************************************************************************/
/* Run over all slave meshes.                                               */
/****************************************************************************/
  for (k = 0; k < n_slaves; k++) {
    slave = m_mem_info->slaves[k];
    TEST_EXIT(slave,"Slave mesh no. %d not found!\n", k);
    INFO(6,6,"Analysing slave \"%s\"...\n", slave->name);

    TEST_EXIT(slave->dim + 1 == master->dim,
	      "Bad dimension of slave!\n");

    TEST_EXIT(s_mem_info = (MESH_MEM_INFO *)(slave->mem_info),
	      "No memory management present for slave!\n");

    TEST_EXIT(s_mem_info->master == master,
	      "Wrong mem_info->master pointer on slave!\n");
    TEST_EXIT(m_dpv = s_mem_info->slave_binding,
	      "No binding vector to slave present!\n");
    TEST_EXIT(s_dpv = s_mem_info->master_binding,
	      "No binding vector to master present!\n");

    INFO(8,8,"Slave mesh has %d subslaves.\n", s_mem_info->n_slaves);

    m_admin = m_dpv->fe_space->admin;
    s_admin = s_dpv->fe_space->admin;

    INFO(10,10,"Current master leaf elements:\n");
    m_el_info = traverse_first(stack, master, -1, CALL_LEAF_EL);
    while(m_el_info) {
      INFO(10,10,"%d\n", INDEX(m_el_info->el));
      m_el_info = traverse_next(stack, m_el_info);
    }

    INFO(10,10,"Current slave leaf elements:\n");
    s_el_info = traverse_first(stack, slave, -1, CALL_LEAF_EL);
    while(s_el_info) {
      INFO(10,10,"%d\n", INDEX(s_el_info->el));
      s_el_info = traverse_next(stack, s_el_info);
    }

/****************************************************************************/
/* Run over the slave mesh and check correspondance to master.              */
/****************************************************************************/
    slave_el_count = 0;
    s_el_info = traverse_first(stack, slave, -1, CALL_EVERY_EL_PREORDER);
    while(s_el_info) {
      slave_el_count++;
      s_el = s_el_info->el;
      INFO(10,10,"Analysing slave el %d...\n", INDEX(s_el));
      if (!IS_LEAF_EL(s_el))
	INFO(10,10,"(Child elements: %d, %d)\n", INDEX(s_el->child[0]),
		    INDEX(s_el->child[1]));
      TEST_EXIT(m_el = (EL *) s_dpv->vec[s_el->dof[slave->node[CENTER]]
					  [s_admin->n0_dof[CENTER]]],
"Slave element %d does not point to a master element!\n", INDEX(s_el));
      INFO(10,10,"slave el %d points to master el %d\n",
		  INDEX(s_el), INDEX(m_el));

      for (i = 0; i < N_NEIGH(master->dim); i++) {
	if (master->dim == 2) {
	 if (s_el == (EL *)m_dpv->vec[m_el->dof[master->node[EDGE]+i]
				     [m_admin->n0_dof[EDGE]]])
	   break;
	}
	else
	  if (s_el == (EL *)m_dpv->vec[m_el->dof[master->node[FACE]+i]
				      [m_admin->n0_dof[FACE]]])
	    break;
      }
      TEST_EXIT(i < N_NEIGH(master->dim),
	"Master element %d does not point back to slave element %d!\n",
	 INDEX(m_el), INDEX(s_el));

      s_el_info = traverse_next(stack, s_el_info);
    }
    if (slave_el_count < slave->n_hier_elements)
      ERROR_EXIT("slave element count == %d < %d == slave->n_elements!\n",
		 slave_el_count, slave->n_elements);
    if (slave_el_count > slave->n_hier_elements)
      ERROR_EXIT("slave element count == %d > %d == slave->n_elements!\n",
		 slave_el_count, slave->n_elements);

/****************************************************************************/
/* Run over the current master mesh and check correspondance to slave.      */
/****************************************************************************/
    m_el_info = traverse_first(stack, master, -1,
			       CALL_EVERY_EL_PREORDER|FILL_ORIENTATION);
    while(m_el_info) {
      m_el = m_el_info->el;
      INFO(10,10,"Analysing master el %d...\n", INDEX(m_el));
      if (!IS_LEAF_EL(m_el))
	INFO(10,10,"(Child elements: %d, %d)\n", INDEX(m_el->child[0]),
		    INDEX(m_el->child[1]));

      for (i = 0; i < N_NEIGH(master->dim); i++) {
	if (master->dim == 2)
	  s_el = (EL *)m_dpv->vec[m_el->dof[master->node[EDGE]+i]
				  [m_admin->n0_dof[EDGE]]];
	else
	  s_el = (EL *)m_dpv->vec[m_el->dof[master->node[FACE]+i]
				  [m_admin->n0_dof[FACE]]];
	if (s_el) {
	  INFO(10,10,"master el %d, subsimplex %d, points to slave el %d\n",
		      INDEX(m_el), i, INDEX(s_el));

	  if (IS_LEAF_EL(m_el)) {
	    TEST_EXIT(m_el == (EL *)s_dpv->vec[s_el->dof[slave->node[CENTER]]
						[s_admin->n0_dof[CENTER]]],
		"Slave element %d does not point back to master element %d!\n",
		      INDEX(s_el), INDEX(m_el));
	  }
	}
      }

      m_el_info = traverse_next(stack, m_el_info);
    }
  }

  INFO(4,4,"No errors found.\n");
  free_traverse_stack(stack);

  return;
}

/****************************************************************************/
/* Interface to the user                                                    */
/****************************************************************************/


/****************************************************************************/
/* get_submesh(master, name, init_leaf_data, binding_method):               */
/* The main allocation routine for getting slave meshes.                    */
/****************************************************************************/

MESH *get_submesh(MESH *master, const char *name,
		  bool (*binding_method)(MESH *master,
					 MACRO_EL *el, int wall,
					 void *data),
		  void *data)
{
  FUNCNAME("get_submesh");
  MESH *slave = NULL;

/****************************************************************************/
/* Do the checks for obvious user errors.                                   */
/****************************************************************************/

  TEST_EXIT(master,"No master mesh specified!\n");

  TEST_EXIT(master->dim > 0, "Does not make sense for dim 0 master meshes!\n");

  TEST_EXIT(binding_method, "Parameter 'binding_method' must be nonzero!\n");

/****************************************************************************/
/* Do the dimension dependent stuff for the slave.                          */
/****************************************************************************/

  if (master->dim == 1)
    slave = get_submesh_1d(master, name, binding_method, data);
#if DIM_MAX > 1
  else if (master->dim == 2)
    slave = get_submesh_2d(master, name, binding_method, data);
#if DIM_MAX > 2
  else
    slave = get_submesh_3d(master, name, binding_method, data);
#endif
#endif

  slave->trace_id = ((MESH_MEM_INFO *)master->mem_info)->next_trace_id++;

/****************************************************************************/
/* Turn the submesh into a parametric mesh if necessary.                    */
/****************************************************************************/
  if (master->parametric) {
    master->parametric->inherit_parametric(slave);
  }

  return slave;
}

/****************************************************************************/
/* get_bndry_submesh(master, name):                                         */
/* Convert the entire boundary into a submesh.                              */
/****************************************************************************/
static bool bndry_binding_method(MESH *master,
				 MACRO_EL *mel, int wall, void *data)
{
  if (!mel->neigh[wall]) {
    return true;
  } else {
    return false;
  }
}

MESH *get_bndry_submesh(MESH *master, const char *name)
{
  return get_submesh(master, name, bndry_binding_method, NULL);
}

MESH *read_bndry_submesh(MESH *master, const char *slave_filename)
{
  return read_submesh(master, slave_filename, bndry_binding_method, NULL);
}

MESH *read_bndry_submesh_xdr(MESH *master, const char *slave_filename)
{
  return read_submesh_xdr(master, slave_filename, bndry_binding_method, NULL);
}

/****************************************************************************/
/* get_bndry_submesh_by_type(master, name, type):                           */
/* Convert boundary facets of the given type into a submesh.                */
/****************************************************************************/
static bool bndry_binding_method_by_type(MESH *master,
					 MACRO_EL *mel, int wall, void *data)
{
  BNDRY_TYPE type = *(BNDRY_TYPE *)data;

  if (mel->wall_bound[wall] == type) {
    return true;
  } else {
    return false;
  }
}

MESH *get_bndry_submesh_by_type(MESH *master, const char *name, BNDRY_TYPE type)
{
  return get_submesh(master,
		     name, bndry_binding_method_by_type, (void *)&type);
}

MESH *read_bndry_submesh_by_type(MESH *master,
				 const char *slave_filename, BNDRY_TYPE type)
{
  union 
  {
    BNDRY_TYPE type;
    void       *data;
  } voidptype;

  voidptype.type = type;
  
  return read_submesh(master, slave_filename,
		      bndry_binding_method_by_type,
		      voidptype.data);
}

MESH *read_bndry_submesh_by_type_xdr(MESH *master,
				     const char *slave_filename,
				     BNDRY_TYPE type)
{
  union 
  {
    BNDRY_TYPE type;
    void       *data;
  } voidptype;

  voidptype.type = type;
  
  return read_submesh_xdr(master, slave_filename,
			  bndry_binding_method_by_type,
			  voidptype.data);
}

/******************************************************************************
 * get_bndry_submesh_by_type(master, name, segment):
 *
 * Convert the boundary segments mentioned in the bit-mask segment
 * into a trace-mesh.
 ******************************************************************************/
static bool bndry_binding_method_by_segment(MESH *master,
					    MACRO_EL *mel, int wall, void *data)
{
  const FLAGS *segment = (const FLAGS *)data;

  if (BNDRY_FLAGS_IS_AT_BNDRY(segment, mel->wall_bound[wall])) {
    return true;
  } else {
    return false;
  }
}

MESH *get_bndry_submesh_by_segment(MESH *master,
				   const char *name, const BNDRY_FLAGS segment)
{
  return get_submesh(master, name,
		     bndry_binding_method_by_segment, (void *)segment);
}

MESH *read_bndry_submesh_by_segment(MESH *master,
				    const char *slave_filename,
				    const BNDRY_FLAGS segment)
{
  return read_submesh(master, slave_filename,
		      bndry_binding_method_by_segment, (void *)segment);
}

MESH *read_bndry_submesh_by_segment_xdr(MESH *master,
					const char *slave_filename,
					const BNDRY_FLAGS segment)
{
  return read_submesh_xdr(master, slave_filename,
			  bndry_binding_method_by_segment, (void *)segment);
}

/****************************************************************************/
/* unchain_submesh(slave): This routine destroys the ties between a slave   */
/* mesh and its master mesh. It DOES NOT destroy the slave mesh! That has to*/
/* be done by another call to free_mesh(). After this call, refinement and  */
/* coarsening of master and slave are independent, and there is no further  */
/* tie between the two meshes.                                              */
/****************************************************************************/

void unchain_submesh(MESH *slave)
{
  FUNCNAME("unchain_submesh");
  MESH           *master;
  MESH_MEM_INFO  *master_info, *slave_info;
  int             i;

/****************************************************************************/
/* Do the checks for obvious user errors.                                   */
/****************************************************************************/

  if (!slave) {
    ERROR("No slave mesh specified!\n");
    return;
  }

  slave_info = (MESH_MEM_INFO *)slave->mem_info;

  if (!(master = slave_info->master)) {
    ERROR("This mesh is not a slave mesh!\n");
    return;
  }

/****************************************************************************/
/* Look for the slave and the master mesh->mem_info.                        */
/****************************************************************************/

  master_info = (MESH_MEM_INFO *)master->mem_info;

  for (i = 0; i < master_info->n_slaves; i++)
    if (master_info->slaves[i] == slave)
      break;
  TEST_EXIT(i < master_info->n_slaves,
    "Could not find the slave mesh in slave vector!\n");

/****************************************************************************/
/* Call the parametric unchain-hook if necessary                            */
/****************************************************************************/
  if (slave->parametric && slave->parametric->unchain_parametric)
    slave->parametric->unchain_parametric(slave);

/****************************************************************************/
/* Resize the slaves vector on the master mesh->mem_info                    */
/****************************************************************************/

  for (; i < master_info->n_slaves - 1; i++)
    master_info->slaves[i] = master_info->slaves[i+1];

  if (master_info->n_slaves > 1)
    master_info->slaves = MEM_REALLOC(master_info->slaves,
				      master_info->n_slaves,
				      master_info->n_slaves - 1,
				      MESH *);
  else {
    MEM_FREE(master_info->slaves, 1, MESH *);

    master_info->slaves = NULL;
  }
  master_info->n_slaves--;

/****************************************************************************/
/* Free the DOF_PTR_VECs and fe_spaces                                      */
/****************************************************************************/

  free_dof_ptr_vec(slave_info->master_binding);
  free_dof_ptr_vec(slave_info->slave_binding);

/****************************************************************************/
/* Set the necessary pointers to NULL, so that this mesh is now free.        */
/****************************************************************************/

  slave_info->master         = NULL;
  slave_info->master_binding = NULL;
  slave_info->slave_binding  = NULL;

  slave->trace_id = -1;

  return;
}


MESH *read_submesh(MESH *master,
		   const char *slave_filename,
		   bool (*binding_method)(MESH *master,
					  MACRO_EL *el, int wall,
					  void *data),
		   void *data)
{
  return read_submesh_gen(false, master, slave_filename, binding_method, data);
}

MESH *read_submesh_xdr(MESH *master,
		       const char *slave_filename,
		       bool (*binding_method)(MESH *master,
					      MACRO_EL *el, int wall,
					      void *data),
		       void *data)
{
  return read_submesh_gen(true, master, slave_filename, binding_method, data);
}

/* Return a pointer to an existing trace-mesh with ID if this is
 * available, NULL otherwise.
 */
MESH *lookup_submesh_by_id(MESH *mesh, int id)
{
  MESH_MEM_INFO *mem_info = (MESH_MEM_INFO *)mesh->mem_info;
  int i;
  
  for (i = 0; i < mem_info->n_slaves; i++) {
    if (mem_info->slaves[i]->trace_id == id) {
      return mem_info->slaves[i];
    }
  }
  return NULL;
 }

MESH *lookup_submesh_by_name(MESH *mesh, const char *name)
{
  MESH_MEM_INFO *mem_info = (MESH_MEM_INFO *)mesh->mem_info;
  int i;
  
  for (i = 0; i < mem_info->n_slaves; i++) {
    if (mem_info->slaves[i]->name == NULL) {
      continue;
    }
    if (strcmp(mem_info->slaves[i]->name, name) == 0) {
      return mem_info->slaves[i];
    }
  }
  return NULL;
}

/* Search for an existing trace mesh conforming to BINDING_METHOD(). */
MESH *lookup_submesh_by_binding(MESH *master, 
				bool (*binding_method)(MESH *master,
						       MACRO_EL *el, int wall,
						       void *data),
				void *data)
{
  MESH_MEM_INFO *m_info = (MESH_MEM_INFO *)master->mem_info;  
  int m_dim = master->dim;
  int k;
  
  for (k = 0; k < m_info->n_slaves; k++) {
    MESH *slave         = m_info->slaves[k];
    MACRO_EL *s_mel     = slave->macro_els;
    MACRO_EL *s_mel_end = s_mel + slave->n_macro_el;
    int n;
    bool ok = true;

    for (n = 0; ok && n < master->n_macro_el; ++n) {
      MACRO_EL *m_mel = master->macro_els + n;
      int i;
      for (i = 0; i < N_NEIGH(m_dim); i++) {
	if (binding_method(master, m_mel, i, data)) {
	  if (s_mel >= s_mel_end ||
	      s_mel->master.macro_el != m_mel ||
	      s_mel->master.opp_vertex != i) {
	    ok = false;
	  }
	  s_mel++;
	}
      }
    }
    if (ok == true && s_mel == s_mel_end) {
      /* At this point all slave macro element have found their
       * matching master element, and no (master, wall) subject to
       * binding_method() is without slave.
       */
      return slave;
    }
  }
  return NULL;
}

MESH *lookup_bndry_submesh_by_type(MESH *master, BNDRY_TYPE type)
{
  return lookup_submesh_by_binding(master,
				   bndry_binding_method_by_type, (void *)&type);
}

MESH *lookup_bndry_submesh_by_segment(MESH *master, const BNDRY_FLAGS segment)
{
  return lookup_submesh_by_binding(master,
				   bndry_binding_method_by_segment,
				   (void *)segment);
}

MESH *lookup_bndry_submesh(MESH *master)
{
  return lookup_submesh_by_binding(master, bndry_binding_method, NULL);
}

#if 0
/****************************************************************************/
/* trace_dof_real_vec[_d](slave_vec, master_vec):                           */
/* A routine for transporting the values of "master_vec" at DOFs along the  */
/* interface to a slave mesh to "slave_vec". This could be described as a   */
/* discrete trace operation.                                                */
/****************************************************************************/

void trace_dof_real_vec_old(DOF_REAL_VEC *slave_vec, DOF_REAL_VEC *master_vec)
{
  FUNCNAME("trace_dof_real_vec");

  MESH             *slave = NULL, *master = NULL;
  TRAVERSE_STACK   *m_stack = get_traverse_stack();
  EL_INFO          s_el_info = { NULL, };
  DOF_PTR_VEC      *s_dpv, *m_dpv;
  const DOF_ADMIN  *s_admin, *s_ptr_admin, *m_ptr_admin;
  const BAS_FCTS   *s_bfcts, *m_bfcts;
  int              s_n_bas_fcts, m_dim;

/****************************************************************************/
/* Check for user errors.                                                   */
/****************************************************************************/
  TEST_EXIT(slave_vec,"No slave DOF_REAL_VEC given!\n");
  TEST_EXIT(m_vec = master_vec,"No master DOF_REAL_VEC given!\n");

/****************************************************************************/
/* Set the necessary pointers and allocate two TRAVERSE_STACKS.             */
/****************************************************************************/
  slave   = slave_vec->fe_space->mesh;
  master  = master_vec->fe_space->mesh;

  TEST_EXIT(((MESH_MEM_INFO *)slave->mem_info)->master == master,
    "Master and slave vectors do not seem to fit together!\n");

  m_dim   = master->dim;

  s_dpv   = ((MESH_MEM_INFO *)slave->mem_info)->master_binding;
  m_dpv   = ((MESH_MEM_INFO *)slave->mem_info)->slave_binding;

  s_admin     = slave_vec->fe_space->admin;
  s_bfcts     = slave_vec->fe_space->bas_fcts;
  m_bfcts     = master_vec->fe_space->bas_fcts;
  s_ptr_admin = s_dpv->fe_space->admin;
  m_ptr_admin = m_dpv->fe_space->admin;

  s_n_bas_fcts = s_bfcts->n_bas_fcts;

  TEST_EXIT(s_bfcts->interpol,
    "Interpolation routine not present for slave vector!\n");

  TEST_EXIT(s_bfcts->get_dof_indices,
    "'get_dof_indices' routine not present for slave vector!\n");

  TEST_EXIT(get_local_m_vec = master_vec->fe_space->bas_fcts->get_real_vec,
    "'get_real_vec' routine not present for master vector!\n");

  m_el_info      = traverse_first(m_stack, master, -1,
				  CALL_LEAF_EL|FILL_ORIENTATION);
  s_el_info.mesh = slave;

  {
    int i;
    REAL s_local_vec[s_n_bas_fcts];
    DOF  s_local_dofs[s_n_bas_fcts];

    while(m_el_info) {

      if (INIT_ELEMENT(m_el_info, m_bfcts) == INIT_EL_TAG_NULL) {
	continue;
      }

      for (m_subsimplex = 0; m_subsimplex < N_NEIGH(m_dim); m_subsimplex++) {
/****************************************************************************/
/* Look for a slave element on edge/face m_subsimplex.                     */
/****************************************************************************/
	switch(m_dim) {
	case 1:
	  if ((s_el_info.el = (EL *)
	     m_dpv->vec[m_el_info->el->dof[master->node[VERTEX] + m_subsimplex]
			[m_ptr_admin->n0_dof[VERTEX]]]))
	  break;
	case 2:
	  if ((s_el_info.el = (EL *)
	     m_dpv->vec[m_el_info->el->dof[master->node[EDGE] + m_subsimplex]
			[m_ptr_admin->n0_dof[EDGE]]]))
	    goto found_slave_element;
	  break;
	case 3:
	  if ((s_el_info.el = (EL *)
	     m_dpv->vec[m_el_info->el->dof[master->node[FACE] + m_subsimplex]
			[m_ptr_admin->n0_dof[FACE]]]))
	    goto found_slave_element;
	  break;
	}
	continue;

      found_slave_element:
/****************************************************************************/
/* Interpolate the master vector into s_local_vec and copy these values     */
/* to the correct position in slave_vec->vec.                               */
/****************************************************************************/
	GET_DOF_INDICES(s_bfcts, s_el_info.el, s_admin, s_local_dofs);
	INTERPOL(s_bfcts,
		 &s_el_info, 0, NULL, NULL, trace_loc, NULL, s_local_vec);

	for (i = 0; i < s_n_bas_fcts; i++)
	  slave_vec->vec[s_local_dofs[i]] = s_local_vec[i];
      }

/****************************************************************************/
/* Advance the slave stack.                                                 */
/****************************************************************************/
      m_el_info = traverse_next(m_stack, m_el_info);
    }
  }


/****************************************************************************/
/* Clean up.                                                                */
/****************************************************************************/
  free_traverse_stack(m_stack);

  return;
}


void trace_dof_real_d_vec_old(DOF_REAL_D_VEC *slave_vec,
			      DOF_REAL_D_VEC *master_vec)
{
  FUNCNAME("trace_dof_real_d_vec");

  MESH             *slave = NULL, *master = NULL;
  TRAVERSE_STACK   *m_stack = get_traverse_stack();
  EL_INFO          s_el_info = { NULL, };
  DOF_PTR_VEC      *s_dpv, *m_dpv;
  const DOF_ADMIN  *s_admin, *s_ptr_admin, *m_ptr_admin;
  const BAS_FCTS   *s_bfcts, *m_bfcts;
  int              s_n_bas_fcts, m_dim;

/****************************************************************************/
/* Check for user errors.                                                   */
/****************************************************************************/
  TEST_EXIT(slave_vec,"No slave DOF_REAL_D_VEC given!\n");
  TEST_EXIT(m_vec_d = master_vec,"No master DOF_REAL_D_VEC given!\n");

/****************************************************************************/
/* Set the necessary pointers and allocate two TRAVERSE_STACKS.             */
/****************************************************************************/
  slave   = slave_vec->fe_space->mesh;
  master  = master_vec->fe_space->mesh;

  TEST_EXIT(((MESH_MEM_INFO *)slave->mem_info)->master == master,
    "Master and slave vectors do not seem to fit together!\n");

  m_dim   = master->dim;

  s_dpv   = ((MESH_MEM_INFO *)slave->mem_info)->master_binding;
  m_dpv   = ((MESH_MEM_INFO *)slave->mem_info)->slave_binding;

  s_admin     = slave_vec->fe_space->admin;
  s_bfcts     = slave_vec->fe_space->bas_fcts;
  s_bfcts     = master_vec->fe_space->bas_fcts;
  m_bfcts     = master_vec->fe_space->bas_fcts;
  s_ptr_admin = s_dpv->fe_space->admin;
  m_ptr_admin = m_dpv->fe_space->admin;

  s_n_bas_fcts = slave_vec->fe_space->bas_fcts->n_bas_fcts;

  TEST_EXIT(s_bfcts->interpol_d,
    "Interpolation routine not present for slave vector!\n");

  TEST_EXIT(s_bfcts->get_dof_indices,
    "'get_dof_indices' routine not present for slave vector!\n");

  TEST_EXIT(get_local_m_vec_d = master_vec->fe_space->bas_fcts->get_real_d_vec,
    "'get_real_d_vec' routine not present for master vector!\n");

  m_el_info      = traverse_first(m_stack, master, -1,
				  CALL_LEAF_EL|FILL_ORIENTATION);
  s_el_info.mesh = slave;

  {
    int i;
    REAL_D s_local_vec_d[s_n_bas_fcts];
    DOF    s_local_dofs[s_n_bas_fcts];

    while(m_el_info) {

      if (INIT_ELEMENT(m_el_info, m_bfcts) == INIT_EL_TAG_NULL) {
	continue;
      }

      for (m_subsimplex = 0; m_subsimplex < N_NEIGH(m_dim); m_subsimplex++) {
/****************************************************************************/
/* Look for a slave element on edge/face m_subsimplex.                      */
/****************************************************************************/
	switch(m_dim) {
	case 1:
	  if ((s_el_info.el = (EL *)
	     m_dpv->vec[m_el_info->el->dof[master->node[VERTEX] + m_subsimplex]
			[m_ptr_admin->n0_dof[VERTEX]]]))
	  break;
	case 2:
	  if ((s_el_info.el = (EL *)
	     m_dpv->vec[m_el_info->el->dof[master->node[EDGE] + m_subsimplex]
			[m_ptr_admin->n0_dof[EDGE]]]))
	    goto found_slave_element;
	  break;
	case 3:
	  if ((s_el_info.el = (EL *)
	     m_dpv->vec[m_el_info->el->dof[master->node[FACE] + m_subsimplex]
			[m_ptr_admin->n0_dof[FACE]]]))
	    goto found_slave_element;
	  break;
	}
	continue;

      found_slave_element:
/****************************************************************************/
/* Interpolate the master vector into s_local_vec and copy these values     */
/* to the correct position in slave_vec->vec.                               */
/****************************************************************************/
	GET_DOF_INDICES(s_bfcts, s_el_info.el, s_admin, s_local_dofs);
	INTERPOL_D(s_bfcts,
		   &s_el_info, 0, NULL, NULL, trace_loc_d, NULL, s_local_vec_d);

	for (i = 0; i < s_n_bas_fcts; i++)
	  COPY_DOW(s_local_vec_d[i], slave_vec->vec[s_local_dofs[i]]);
      }

/****************************************************************************/
/* Advance the slave stack.                                                 */
/****************************************************************************/
      m_el_info = traverse_next(m_stack, m_el_info);
    }
  }

/****************************************************************************/
/* Clean up.                                                                */
/****************************************************************************/
  free_traverse_stack(m_stack);

  return;
}

#endif

/****************************************************************************/
/* get_slave_dof_mapping(m_fe_space, s_map): Fill s_map on the slave mesh   */
/* with the corresponding DOFs of m_fe_space. This is only implemented for  */
/* Lagrange FE spaces. Master and slave fe_spaces must have the same degree.*/
/*                                                                          */
/* s_map is not updated during mesh or DOF (dof_compress(master)) changes!! */
/****************************************************************************/

void get_slave_dof_mapping(const FE_SPACE *m_fe_space, DOF_INT_VEC *s_map)
{
  FUNCNAME("get_slave_dof_mapping");
  MESH            *master;
  MESH            *slave;
  const FE_SPACE  *s_fe_space;
  const BAS_FCTS  *m_bfcts, *s_bfcts;
  const DOF_ADMIN *m_admin, *s_admin;
  DOF_PTR_VEC     *m_dpv, *s_dpv;
  TRAVERSE_STACK  *m_stack = get_traverse_stack();
  const EL_INFO   *m_el_info;
  DOF             *m_local_dofs, *s_local_dofs;
  int             m_dim, m_n_dofs, s_n_dofs, m_subsimplex;
  int             m_n0, s_n0, m_n, s_n;
  const EL        *s_el, *m_el;
  FLAGS           which_elements;

/****************************************************************************/
/* Check for user errors.                                                   */
/****************************************************************************/
  TEST_EXIT(m_fe_space,"No master FE_SPACE given!\n");
  TEST_EXIT(s_map,"No DOF_INT_VEC s_map given!\n");

  s_fe_space = s_map->fe_space;

  TEST_EXIT(s_fe_space,"No slave FE_SPACE found!\n");

  m_admin = m_fe_space->admin;
  s_admin = s_fe_space->admin;

  m_bfcts = m_fe_space->bas_fcts;
  s_bfcts = s_fe_space->bas_fcts;

  TEST_EXIT(m_bfcts,
	    "Sorry, only implemented for FE_SPACEs derived from local "
	    "basis functions\n");
  
  TEST_EXIT(s_bfcts == m_bfcts->trace_bas_fcts,
	    "s_map->fe_space->bas_fcts != m_bfcts->trace_bas_fcts.\n");

  master = m_fe_space->mesh;
  slave  = s_fe_space->mesh;
  m_dim  = master->dim;

  TEST_EXIT(((MESH_MEM_INFO *)slave->mem_info)->master == master,
    "Master and slave meshes do not seem to belong together!\n");
  TEST_EXIT(strstr(m_fe_space->bas_fcts->name, "lagrange")
	    && strstr(s_fe_space->bas_fcts->name, "lagrange"),
    "Sorry, only implemented for Lagrange Finite Elements!\n");

  TEST_EXIT(m_admin->flags == s_admin->flags, "different flag values!\n");
  if (s_admin->flags & ADM_PRESERVE_COARSE_DOFS)
    which_elements = CALL_EVERY_EL_PREORDER;
  else
    which_elements = CALL_LEAF_EL;

/****************************************************************************/
/* Initialize values.                                                       */
/****************************************************************************/

  /* Mark everything as unused. */
  FOR_ALL_DOFS(s_admin, s_map->vec[dof] = -1);

  m_dpv             = ((MESH_MEM_INFO *)slave->mem_info)->slave_binding;
  s_dpv             = ((MESH_MEM_INFO *)slave->mem_info)->master_binding;

  s_n0              = s_dpv->fe_space->admin->n0_dof[CENTER];
  s_n               = slave->node[CENTER];

  m_n_dofs = m_fe_space->bas_fcts->n_bas_fcts;
  s_n_dofs = s_fe_space->bas_fcts->n_bas_fcts;

  m_local_dofs = MEM_ALLOC(m_n_dofs, DOF);
  s_local_dofs = MEM_ALLOC(s_n_dofs, DOF);

  switch(m_dim) {
  case 1:
    m_n0 = m_dpv->fe_space->admin->n0_dof[VERTEX];
    m_n  = master->node[VERTEX];

    for (m_el_info = traverse_first(m_stack, master, -1, which_elements);
	m_el_info;
	m_el_info = traverse_next(m_stack, m_el_info)) {

      if (INIT_ELEMENT(m_el_info, m_bfcts) == INIT_EL_TAG_NULL) {
	continue;
      }

      m_el = m_el_info->el;
      GET_DOF_INDICES(m_bfcts, m_el, m_admin, m_local_dofs);

      for (m_subsimplex = 0; m_subsimplex < N_NEIGH_1D; m_subsimplex++) {
	s_el = (EL *)m_dpv->vec[m_el->dof[m_n + m_subsimplex][m_n0]];
	if (s_el && m_el==(EL *)s_dpv->vec[s_el->dof[s_n][s_n0]]) {
	  GET_DOF_INDICES(s_bfcts, s_el, s_admin, s_local_dofs);
	  s_map->vec[s_local_dofs[0]] = m_local_dofs[m_subsimplex];
	}
      }
    }
    break;
#if DIM_MAX > 1
  case 2:
    m_n0 = m_dpv->fe_space->admin->n0_dof[EDGE];
    m_n = master->node[EDGE];

    for (m_el_info = traverse_first(m_stack, master, -1, which_elements);
	m_el_info;
	m_el_info = traverse_next(m_stack, m_el_info)) {

      if (INIT_ELEMENT(m_el_info, m_bfcts) == INIT_EL_TAG_NULL) {
	continue;
      }

      m_el = m_el_info->el;
      GET_DOF_INDICES(m_bfcts, m_el, m_admin, m_local_dofs);

      for (m_subsimplex = 0; m_subsimplex < N_NEIGH_2D; m_subsimplex++) {
	int i;

	s_el = (EL *)m_dpv->vec[m_el->dof[m_n + m_subsimplex][m_n0]];
	if (s_el && m_el==(EL *)s_dpv->vec[s_el->dof[s_n][s_n0]]) {
	  GET_DOF_INDICES(s_bfcts, s_el, s_admin, s_local_dofs);

	  for (i = 0; i < s_n_dofs; i++)
	    s_map->vec[s_local_dofs[i]] =
	      m_local_dofs[m_bfcts->trace_dof_map[0][0][m_subsimplex][i]];
	}
      }
    }
    break;
#if DIM_MAX > 2
  case 3:
    m_n0 = m_dpv->fe_space->admin->n0_dof[FACE];
    m_n = master->node[FACE];

    for (m_el_info = traverse_first(m_stack, master, -1,
				   which_elements|FILL_ORIENTATION);
	m_el_info;
	m_el_info = traverse_next(m_stack, m_el_info)) {
      int type, i;

      if (INIT_ELEMENT(m_el_info, m_bfcts) == INIT_EL_TAG_NULL) {
	continue;
      }

      m_el = m_el_info->el;
      GET_DOF_INDICES(m_bfcts, m_el, m_admin, m_local_dofs);

      if (m_el_info->el_type == 0)
	type = 0;
      else
	type = 1;

      for (m_subsimplex = 0; m_subsimplex < N_NEIGH_3D; m_subsimplex++) {
	s_el = (EL *)m_dpv->vec[m_el->dof[m_n + m_subsimplex][m_n0]];
	if (s_el && m_el==(EL *)s_dpv->vec[s_el->dof[s_n][s_n0]]) {
	  GET_DOF_INDICES(s_bfcts, s_el, s_admin, s_local_dofs);

	  for (i = 0; i < s_n_dofs; i++) {
	    if (m_el_info->orientation > 0)
	      s_map->vec[s_local_dofs[i]] =
		m_local_dofs[
		  m_bfcts->trace_dof_map[type][0][m_subsimplex][i]];
	    else
	      s_map->vec[s_local_dofs[i]] =
		m_local_dofs[
		  m_bfcts->trace_dof_map[type][1][m_subsimplex][i]];
	  }
	}
      }
    }
    break;
#endif
#endif
  default:
    ERROR_EXIT("Illegal dimension!\n");
  }

/****************************************************************************/
/* Clean up and return.                                                     */
/****************************************************************************/
  free_traverse_stack(m_stack);
  MEM_FREE(m_local_dofs, m_n_dofs, DOF);
  MEM_FREE(s_local_dofs, s_n_dofs, DOF);

  return;
}

/****************************************************************************/
/* get_master(slave): Return the master of slave if present.                */
/****************************************************************************/

MESH *get_master(MESH *slave)
{
  return ((MESH_MEM_INFO *)slave->mem_info)->master;
}

const EL *get_slave_el(const EL *el, int wall, MESH *trace_mesh)
{
  MESH_MEM_INFO *trace_minfo = trace_mesh->mem_info;
  MESH *mesh = get_master(trace_mesh);
  const DOF_PTR_VEC *trace_binding = trace_minfo->slave_binding;
  int n0, node;

  switch (trace_mesh->dim) {
  case 2: node = FACE; break;
  case 1: node = EDGE; break;
  case 0: node = VERTEX; break;
  default: node = -1; break;
  }
  
  trace_binding = ((MESH_MEM_INFO *)trace_mesh->mem_info)->slave_binding;
  n0   = trace_binding->fe_space->admin->n0_dof[node];
  node = mesh->node[node];

  return (const EL *)trace_binding->vec[el->dof[node+wall][n0]];
}

void fill_slave_el_info(EL_INFO *slv_el_info,
			const EL_INFO *el_info, int wall, MESH *trace_mesh)
{
  const EL *slv_el = get_slave_el(el_info->el, wall, trace_mesh);
  int dim = trace_mesh->dim;

  slv_el_info->fill_flag = FILL_NOTHING;
  slv_el_info->mesh      = trace_mesh;

  slv_el_info->macro_el  = NULL; /* FIXME */
  slv_el_info->el        = (EL *)slv_el;
  slv_el_info->parent    = NULL /* could do better here */;
  
  slv_el_info->master.el = el_info->el;
  slv_el_info->master.opp_vertex = wall;
  slv_el_info->master.el_type = el_info->el_type;
  slv_el_info->master.orientation = el_info->orientation;
  slv_el_info->fill_flag |= FILL_MASTER_INFO;

  slv_el_info->el_geom_cache.fill_flag  = 0U;
  slv_el_info->el_geom_cache.current_el = slv_el_info->el;

  if (el_info->fill_flag & FILL_COORDS) {
    if (dim == 2) {
#if DIM_MAX > 2
      int mst_t, mst_o, v;

      mst_t = el_info->el_type;
      mst_o = el_info->orientation;
	
      for (v = 0; v < N_VERTICES_3D; v++) {
	int sv = slave_numbering_3d[!!mst_t][mst_o < 0][wall][v];
	if (sv >= 0) {
	  COPY_DOW(el_info->coord[v], slv_el_info->coord[sv]);
	}
      }
#endif
    } else {
      int v;
      for (v = 0; v < N_VERTICES(dim); v++) {
	int mv = (wall + v) % N_VERTICES(dim+1);
	COPY_DOW(el_info->coord[mv], slv_el_info->coord[v]);
      }
    }
    COPY_DOW(el_info->coord[wall], slv_el_info->master.opp_coord);
    slv_el_info->fill_flag |= FILL_COORDS;
  }
  if (el_info->fill_flag & FILL_NEIGH) {
    slv_el_info->mst_neigh.el = el_info->neigh[wall];
    if (el_info->neigh[wall]) {
      slv_el_info->mst_neigh.opp_vertex = el_info->opp_vertex[wall];
      slv_el_info->mst_neigh.el_type = 0;
      slv_el_info->mst_neigh.orientation = 1;
      if (el_info->fill_flag & FILL_OPP_COORDS) {
	COPY_DOW(el_info->opp_coord[wall], slv_el_info->mst_neigh.opp_coord);
      }
    }
    slv_el_info->fill_flag |= FILL_MASTER_NEIGH;
  }
}

/* el_info has to refer to a trace element, lambda are trace
 * coordinates.
 */
void trace_to_bulk_coords_2d(REAL_B result,
			     const REAL_B lambda,
			     const EL_INFO *el_info)
{
#if DIM_MAX > 2
  int mst_ov = el_info->master.opp_vertex;
  int mst_t  = el_info->master.el_type;
  int mst_o  = el_info->master.orientation;    
  int v;

  for (v = 0; v < N_LAMBDA_3D; v++) {
    int sv = slave_numbering_3d[!!mst_t][mst_o < 0][mst_ov][v];
    if (sv >= 0) {
      result[v] = lambda[sv];
    }
  }
  result[mst_ov] = 0.0;

#else
  FUNCNAME("trace_to_bulk_coords_2d");
  ERROR_EXIT("This must not happen.\n");
#endif
}

/* el_info has to refer to a trace element, lambda are bulk
 * coordinates.
 */
void bulk_to_trace_coords_2d(REAL_B result,
			     const REAL_B lambda,
			     const EL_INFO *el_info)
{
  FUNCNAME("bulk_to_bulk_coords_2d");
#if DIM_MAX > 2
  int mst_ov = el_info->master.opp_vertex;
  int mst_t  = el_info->master.el_type;
  int mst_o  = el_info->master.orientation;    
  int v;

  DEBUG_TEST_EXIT(lambda[mst_ov] == 0.0,
                  "This bulk coordinate does not live on a face.");

  for (v = 0; v < N_LAMBDA_3D; v++) {
    int sv = slave_numbering_3d[!!mst_t][mst_o < 0][mst_ov][v];
    if (sv >= 0) {
      result[sv] = lambda[v];
    }
  }

  for (v = N_LAMBDA_2D; v < N_LAMBDA_MAX; v++) {
    result[v] = 0.0;
  }
#else
  ERROR_EXIT("This must not happen.\n");
#endif
}

void trace_to_bulk_coords_1d(REAL_B result,
			     const REAL_B lambda,
			     const EL_INFO *el_info)
{
#if DIM_MAX > 1
  int mst_ov = el_info->master.opp_vertex;
  int v;
  for (v = 0; v < N_VERTICES_1D; v++) {
    int mv = (mst_ov + v + 1) % N_VERTICES_2D;
    result[mv] = lambda[v];
  }
  result[mst_ov] = 0.0;
  for (v = N_LAMBDA_2D; v < N_LAMBDA_MAX; v++) {
    result[v] = 0.0;
  }
#else
  FUNCNAME("trace_to_bulk_coords_1d");
  ERROR_EXIT("This must not happen.\n");
#endif
}

void bulk_to_trace_coords_1d(REAL_B result,
			     const REAL_B lambda,
			     const EL_INFO *el_info)
{
  FUNCNAME("bulk_to_trace_coords_1d");
#if DIM_MAX > 1
  int mst_ov = el_info->master.opp_vertex;
  int v;

  DEBUG_TEST_EXIT(lambda[mst_ov] == 0.0,
                  "This bulk coordinate does not live on a face.");

  for (v = 0; v < N_VERTICES_1D; v++) {
    int mv = (mst_ov + v + 1) % N_VERTICES_2D;
    result[v] = lambda[mv];
  }

  for (v = N_LAMBDA_1D; v < N_LAMBDA_MAX; v++) {
    result[v] = 0.0;
  }
#else
  ERROR_EXIT("This must not happen.\n");
#endif
}

void trace_to_bulk_coords_0d(REAL_B result,
			     const REAL_B lambda,
			     const EL_INFO *el_info)
{
#if DIM_MAX > 0
  int mst_ov = el_info->master.opp_vertex;
  int v;

  result[mst_ov]     = 0.0;
  result[1 - mst_ov] = 1.0;
  for (v = N_LAMBDA_1D; v < N_LAMBDA_MAX; v++) {
    result[v] = 0.0;
  }
#else
  FUNCNAME("trace_to_bulk_coords_0d");
  ERROR_EXIT("This must not happen.\n");
#endif
}

void bulk_to_trace_coords_0d(REAL_B result,
			     const REAL_B lambda,
			     const EL_INFO *el_info)
{
  FUNCNAME("bulk_to_trace_coords_0d");
#if DIM_MAX > 0
  int v;

  DEBUG_TEST_EXIT(lambda[el_info->master.opp_vertex] == 0.0,
                  "This bulk coordinate does not live on a face.");

  result[0] = 1.0;

  for (v = N_LAMBDA_1D; v < N_LAMBDA_MAX; v++) {
    result[v] = 0.0;
  }
#else
  ERROR_EXIT("This must not happen.\n");
#endif
}

/* Construct an EL_INFO structure for the master element attached to
 * the given slave element. This requires FILL_MASTER_INFO to be set
 * in the fill-flags of el_info.
 *
 * The master element will have its el_type and orientation set. If
 * the slave EL_INFO has co-ordinate information, then also the master
 * EL_INFO will be equipped with co-ordinate information.
 */
void fill_master_el_info(EL_INFO *mst_el_info,
			 const EL_INFO *el_info,
			 FLAGS fill_flags)
{
  FUNCNAME("fill_master_el_info");
  int dim = el_info->mesh->dim;
  int mst_ov, mst_t = 0, mst_o = 0;

  DEBUG_TEST_EXIT(el_info->fill_flag & FILL_MASTER_INFO,
		  "Master element link not present in "
		  "slave element descriptor.\n");

  memset(mst_el_info, 0, sizeof(*mst_el_info));

  mst_el_info->fill_flag = FILL_NOTHING;
  mst_el_info->mesh      = get_master(el_info->mesh);

  mst_el_info->macro_el  = el_info->macro_el->master.macro_el;
  mst_el_info->el        = el_info->master.el;
  mst_el_info->parent    = NULL /* could do better here */;

  mst_el_info->el_geom_cache.fill_flag  = 0U;
  mst_el_info->el_geom_cache.current_el = mst_el_info->el;
  
  mst_ov = el_info->master.opp_vertex;

#if DIM_MAX > 2
  if (dim == 2) {
    mst_t = el_info->master.el_type;
    mst_o = el_info->master.orientation;
    
    mst_el_info->fill_flag |= FILL_ORIENTATION;

    if (fill_flags & FILL_COORDS) {
      int v;
      for (v = 0; v < N_VERTICES_3D; v++) {
	int sv = slave_numbering_3d[!!mst_t][mst_o < 0][mst_ov][v];
	if (sv >= 0) {
	  COPY_DOW(el_info->coord[sv], mst_el_info->coord[v]);
	}
      }
    }
    if (fill_flags & FILL_BOUND) {
      int v, e;
      for (v = 0; v < N_VERTICES_3D; v++) {
	int sv = slave_numbering_3d[!!mst_t][mst_o < 0][mst_ov][v];
	if (sv >= 0) {
	  BNDRY_FLAGS_CPY(mst_el_info->vertex_bound[v],
			  el_info->vertex_bound[sv]);
	} else {
	  BNDRY_FLAGS_INIT(mst_el_info->vertex_bound[v]);
	}
      }
      for (e = 0; e < N_EDGES_3D; e++) {
	BNDRY_FLAGS_INIT(mst_el_info->edge_bound[e]);
      }
      for (e = 0; e < N_EDGES_2D; e++) {
	int mst_edge = master_edge_3d[!!mst_t][mst_o < 0][mst_ov][e];
	BNDRY_FLAGS_CPY(mst_el_info->edge_bound[mst_edge],
			el_info->edge_bound[e]);
      }
      mst_el_info->face_bound[mst_ov] = el_info->face_bound[0];
      mst_el_info->wall_bound[mst_ov] =
	el_info->macro_el->master.macro_el->wall_bound[
	  el_info->macro_el->master.opp_vertex];

      mst_el_info->fill_flag |= FILL_BOUND; /* not quite correct, but so what */
    }
  } else
#endif
  {
    if (fill_flags & FILL_COORDS) {
      int v;
      for (v = 0; v < N_VERTICES(dim); v++) {
	int mv = (mst_ov + v + 1) % N_VERTICES(dim+1);
	COPY_DOW(el_info->coord[v], mst_el_info->coord[mv]);
      }
    }
    if (fill_flags & FILL_BOUND) {
      int v;
      for (v = 0; v < N_VERTICES(dim); v++) {
	int mv = (mst_ov + v + 1) % N_VERTICES(dim+1);
	BNDRY_FLAGS_CPY(mst_el_info->vertex_bound[mv],
			el_info->vertex_bound[v]);
      }
      BNDRY_FLAGS_INIT(mst_el_info->vertex_bound[mst_ov]);
      if (dim == 1) {
	BNDRY_FLAGS_CPY(mst_el_info->edge_bound[mst_ov],
			el_info->edge_bound[0]);
      }
      mst_el_info->wall_bound[mst_ov] =
	el_info->macro_el->master.macro_el->wall_bound[
	  el_info->macro_el->master.opp_vertex];
      mst_el_info->fill_flag |= FILL_BOUND; /* not quite correct, but so what */
    }
  }

  if (fill_flags & FILL_NEIGH) {
    mst_el_info->neigh[mst_ov] = el_info->mst_neigh.el;
    mst_el_info->opp_vertex[mst_ov] = el_info->mst_neigh.opp_vertex;
    mst_el_info->fill_flag |= FILL_NEIGH;
    if (fill_flags & FILL_OPP_COORDS) {
      COPY_DOW(el_info->mst_neigh.opp_coord, mst_el_info->opp_coord[mst_ov]);
      mst_el_info->fill_flag |= FILL_OPP_COORDS;
    }
  }

  mst_el_info->el_type      = mst_t;
  mst_el_info->orientation  = mst_o;

  if (fill_flags & FILL_COORDS) {
    mst_el_info->fill_flag |= FILL_COORDS;
    COPY_DOW(el_info->master.opp_coord, mst_el_info->coord[mst_ov]);
  }
}

/* Compute the local mapping between global master DOFs and local
 * slave DOFs, return value is an array with the global DOFs w.r.t. to
 * the master for the local basis functions on the given slave
 * element.
 */
const EL_DOF_VEC *get_master_dof_indices(EL_DOF_VEC *rvec,
					 const EL_INFO *s_el_info,
					 const FE_SPACE *m_fe_space)
{
  FUNCNAME("get_master_dof_indices");
  static EL_DOF_VEC *rvec_space;
  const BAS_FCTS *m_bfcts = m_fe_space->bas_fcts;
  DOF m_dofs[m_bfcts->n_bas_fcts];
  const EL       *m_el;
  const FE_SPACE *m_fe_chain;
  int wall;
  int orientation;
  int type;
  int sbf;

  DEBUG_TEST_EXIT(s_el_info->fill_flag & FILL_MASTER_INFO,
		  "slave->master link not set in EL_INFO.\n");

  m_el        = s_el_info->master.el;
  wall        = s_el_info->master.opp_vertex;
  orientation = s_el_info->master.orientation;
  type        = s_el_info->master.el_type;

  if (INIT_ELEMENT_NEEDED(m_bfcts)) {
    EL_INFO m_el_info = { 0 };

    fill_master_el_info(&m_el_info, s_el_info, m_bfcts->fill_flags);

    if (INIT_ELEMENT(&m_el_info, m_bfcts) == INIT_EL_TAG_NONE) {
      return NULL;
    }
  }

  if (rvec == NULL) {
    if (rvec_space) {
      free_el_dof_vec(rvec_space);
    }
    rvec = rvec_space = get_el_dof_vec(m_bfcts);
  }

  GET_DOF_INDICES(m_bfcts, m_el, m_fe_space->admin, m_dofs);
  rvec->n_components = m_bfcts->n_trace_bas_fcts[wall];
  for (sbf = 0; sbf < rvec->n_components; sbf++) {
    rvec->vec[sbf] =
      m_dofs[m_bfcts->trace_dof_map[type > 0][orientation < 0][wall][sbf]];
  }
  CHAIN_FOREACH(m_fe_chain, m_fe_space, const FE_SPACE) {
    DOF m_dofs[m_fe_chain->bas_fcts->n_bas_fcts];
    
    rvec = CHAIN_NEXT(rvec, EL_DOF_VEC);

    m_bfcts = m_fe_chain->bas_fcts;
    GET_DOF_INDICES(m_bfcts, m_el, m_fe_chain->admin, m_dofs);
    rvec->n_components = m_bfcts->n_trace_bas_fcts[wall];
    for (sbf = 0; sbf < rvec->n_components; sbf++) {
      rvec->vec[sbf] =
	m_dofs[m_bfcts->trace_dof_map[type > 0][orientation < 0][wall][sbf]];
    }
  }
  rvec = CHAIN_NEXT(rvec, EL_DOF_VEC);

  return rvec;
}

/* Compute the boundary classification for all local slave DOFs
 * relative to the master mesh.
 */
const EL_BNDRY_VEC *get_master_bound(EL_BNDRY_VEC *rvec,
				     const EL_INFO *s_el_info,
				     const BAS_FCTS *m_bas_fcts)
{
  FUNCNAME("get_master_dof_indices");
  static EL_BNDRY_VEC *rvec_space;
  BNDRY_FLAGS m_bound[m_bas_fcts->n_bas_fcts_max];
  EL_INFO m_el_info = { 0 };
  const BAS_FCTS *m_bfcts_chain;
  int wall, o, t;
  int sbf;

  DEBUG_TEST_EXIT(s_el_info->fill_flag & FILL_MASTER_INFO,
		  "slave->master link not set in EL_INFO.\n");

  wall = s_el_info->master.opp_vertex;
  o    = s_el_info->master.orientation < 0;
  t    = s_el_info->master.el_type > 0;

  fill_master_el_info(&m_el_info, s_el_info, m_bas_fcts->fill_flags|FILL_BOUND);
  if (INIT_ELEMENT(&m_el_info, m_bas_fcts) == INIT_EL_TAG_NONE) {
    return NULL;
  }

  if (rvec == NULL) {
    if (rvec_space) {
      free_el_bndry_vec(rvec_space);
    }
    rvec = rvec_space = get_el_bndry_vec(m_bas_fcts);
  }

  GET_BOUND(m_bas_fcts,& m_el_info, m_bound);
  rvec->n_components = m_bas_fcts->n_trace_bas_fcts[wall];
  for (sbf = 0; sbf < rvec->n_components; sbf++) {
    BNDRY_FLAGS_CPY(rvec->vec[sbf],
		    m_bound[m_bas_fcts->trace_dof_map[t][o][wall][sbf]]);
  }
  CHAIN_FOREACH(m_bfcts_chain, m_bas_fcts, const BAS_FCTS) {
    BNDRY_FLAGS m_bound[m_bfcts_chain->n_bas_fcts];
    
    rvec = CHAIN_NEXT(rvec, EL_BNDRY_VEC);

    GET_BOUND(m_bfcts_chain, &m_el_info, m_bound);
    rvec->n_components = m_bfcts_chain->n_trace_bas_fcts[wall];
    for (sbf = 0; sbf < rvec->n_components; sbf++) {
      BNDRY_FLAGS_CPY(rvec->vec[sbf],
		      m_bound[m_bfcts_chain->trace_dof_map[t][o][wall][sbf]]);
    }
  }
  rvec = CHAIN_NEXT(rvec, EL_BNDRY_VEC);

  return rvec;
}

#undef EQ_COPY
#define EQ_COPY(from, to) (to) = (from)

/* A C "template" to compute the trace of a generic DOF_VEC object,
 * copyinsn is called via copyinsn(from, to).
 */
#define DEFUN_TRACE_DOF_VEC(TYPE, typename, COPYINSN)			\
void trace_##typename##_vec(DOF_##TYPE##_VEC *svec,			\
			    const DOF_##TYPE##_VEC *mvec)		\
{									\
  FUNCNAME("trace_"#typename);						\
  const BAS_FCTS  *sbfcts = svec->fe_space->bas_fcts;			\
  const BAS_FCTS  *mbfcts = mvec->fe_space->bas_fcts;			\
  const DOF_ADMIN *sadmin = svec->fe_space->admin;			\
  const DOF *s_dofs;							\
  const EL_DOF_VEC *sm_dofs;						\
  int sbf;								\
									\
  TEST_EXIT(sbfcts == mbfcts->trace_bas_fcts,				\
	    "svec->fe_space->bas_fcts"					\
	    " != "							\
	    "mvec->fe_space->bas_fcts->trace_bas_fcts!\n");		\
									\
  TRAVERSE_FIRST(svec->fe_space->mesh, -1, CALL_LEAF_EL|FILL_MASTER_INFO) { \
									\
    if (INIT_ELEMENT(el_info, sbfcts) == INIT_EL_TAG_NULL) {		\
      continue;								\
    }									\
									\
    sm_dofs = get_master_dof_indices(NULL, el_info, mvec->fe_space);	\
    if (sm_dofs) {							\
      CHAIN_DO(sm_dofs, EL_DOF_VEC) {					\
									\
	s_dofs = GET_DOF_INDICES(sbfcts, el_info->el, sadmin, NULL)->vec; \
									\
	for (sbf = 0; sbf < sbfcts->n_bas_fcts; sbf++) {		\
	  COPYINSN(mvec->vec[sm_dofs->vec[sbf]], svec->vec[s_dofs[sbf]]); \
	}								\
	mvec = CHAIN_NEXT(mvec, DOF_##TYPE##_VEC);			\
	svec = CHAIN_NEXT(svec, DOF_##TYPE##_VEC);			\
      } CHAIN_WHILE(sm_dofs, EL_DOF_VEC);				\
    }									\
  } TRAVERSE_NEXT();							\
}

/* Traces for all kinds of DOF-vectors. :) */
DEFUN_TRACE_DOF_VEC(REAL, dof_real, EQ_COPY)
DEFUN_TRACE_DOF_VEC(REAL_D, dof_real_d, COPY_DOW)
DEFUN_TRACE_DOF_VEC(INT, dof_int, EQ_COPY)
DEFUN_TRACE_DOF_VEC(DOF, dof_dof, EQ_COPY)
DEFUN_TRACE_DOF_VEC(DOF, int_dof, EQ_COPY)
DEFUN_TRACE_DOF_VEC(UCHAR, dof_uchar, EQ_COPY)
DEFUN_TRACE_DOF_VEC(SCHAR, dof_schar, EQ_COPY)
DEFUN_TRACE_DOF_VEC(PTR, dof_ptr, EQ_COPY)

void trace_dof_real_vec_d(DOF_REAL_VEC_D *svec, const DOF_REAL_VEC_D *mvec)
{
  FUNCNAME("trace_dof_real_vec_d");
  const BAS_FCTS  *sbfcts = svec->fe_space->bas_fcts;
  const BAS_FCTS  *mbfcts = mvec->fe_space->bas_fcts;
  const DOF_ADMIN *sadmin = svec->fe_space->admin;
  const DOF *s_dofs;
  const EL_DOF_VEC *sm_dofs;
  int sbf;

  TEST_EXIT(sbfcts == mbfcts->trace_bas_fcts,
	    "svec->fe_space->bas_fcts"
	    " != "
	    "mvec->fe_space->bas_fcts->trace_bas_fcts!\n");

  TRAVERSE_FIRST(svec->fe_space->mesh, -1, CALL_LEAF_EL|FILL_MASTER_INFO) {

    if (INIT_ELEMENT(el_info, sbfcts) == INIT_EL_TAG_NULL) {
      continue;
    }

    sm_dofs = get_master_dof_indices(NULL, el_info, mvec->fe_space);
    if (sm_dofs) {
      CHAIN_DO(sm_dofs, EL_DOF_VEC) {

	s_dofs = GET_DOF_INDICES(sbfcts, el_info->el, sadmin, NULL)->vec;

	if (mvec->stride == 1) {
	  for (sbf = 0; sbf < sbfcts->n_bas_fcts; sbf++) {
	    svec->vec[s_dofs[sbf]] = mvec->vec[sm_dofs->vec[sbf]];
	  }
	} else {
	  DOF_REAL_D_VEC *svd = (DOF_REAL_D_VEC *)svec;
	  DOF_REAL_D_VEC *mvd = (DOF_REAL_D_VEC *)mvec;
	  for (sbf = 0; sbf < sbfcts->n_bas_fcts; sbf++) {
	    COPY_DOW(mvd->vec[sm_dofs->vec[sbf]], svd->vec[s_dofs[sbf]]);
	  }
	}
	mvec = CHAIN_NEXT(mvec, DOF_REAL_VEC_D);
	svec = CHAIN_NEXT(svec, DOF_REAL_VEC_D);
      } CHAIN_WHILE(sm_dofs, EL_DOF_VEC);
    }
  } TRAVERSE_NEXT();
}

/* What follows are some convenience functions to add element matrix
 * computed on slave meshes to master matrices. Do not use them. It is
 * overkill to derive a slave mesh for the sole purpose of assembling
 * boundary integral contributions.
 *
 * Except when either column of row space live on the trace-mesh
 * alone, e.g. for weak slip-conditions.
 */

/* Update a DOF_MATRIX living on a master mesh, using an
 * EL_MATRIX_INFO object living on a slave mesh.
 */
void update_master_matrix(DOF_MATRIX *m_dof_matrix,
			  const EL_MATRIX_INFO *s_minfo,
			  MatrixTranspose transpose)
{
  FUNCNAME("update_master_matrix");
  MESH *slave, *row_mesh, *col_mesh;
  const FE_SPACE *s_row_fe_space, *s_col_fe_space;
  const BAS_FCTS *s_col_bfcts, *m_row_bfcts;
  const DOF_ADMIN *s_row_admin;
  bool use_get_bound;
  EL_DOF_VEC *row_dof, *col_dof;
  EL_SCHAR_VEC *bound = NULL;
  EL_BNDRY_VEC *bndry_bits = NULL;
  FLAGS fill_flag;

  TEST_EXIT(s_minfo, "no EL_MATRIX_INFO\n");
  TEST_EXIT(s_minfo->el_matrix_fct, "no el_matrix_fct in EL_MATRIX_INFO\n");
  TEST_EXIT(m_dof_matrix, "no DOF_MATRIX\n");

  slave = s_minfo->row_fe_space->mesh;

  BNDRY_FLAGS_CPY(m_dof_matrix->dirichlet_bndry, s_minfo->dirichlet_bndry);
  
  s_col_fe_space = NULL;
  if (transpose == NoTranspose) {
    s_row_fe_space = s_minfo->row_fe_space;
    if (s_minfo->col_fe_space && s_minfo->col_fe_space != s_row_fe_space) {
      s_col_fe_space = s_minfo->col_fe_space;
    }
  } else {
    if (s_minfo->col_fe_space &&
	s_minfo->col_fe_space != s_minfo->row_fe_space) {
      s_row_fe_space = s_minfo->col_fe_space;
      s_col_fe_space = s_minfo->row_fe_space;
    } else {
      s_row_fe_space = s_minfo->col_fe_space;
    }
  }
  
  s_row_admin = s_row_fe_space->admin;

  if (s_col_fe_space) {
    s_col_bfcts = s_col_fe_space->bas_fcts;
  } else {
    s_col_bfcts = NULL;
  }

  use_get_bound = !BNDRY_FLAGS_IS_INTERIOR(m_dof_matrix->dirichlet_bndry);
  if (use_get_bound) {
    fill_flag = s_minfo->fill_flag|FILL_BOUND;
    if (slave->is_periodic && !(s_row_admin->flags & ADM_PERIODIC)) {
      fill_flag |= FILL_NON_PERIODIC;
    }
  } else {
    fill_flag = s_minfo->fill_flag;
  }

  s_minfo->el_matrix_fct(NULL, s_minfo->fill_info);

  m_row_bfcts = m_dof_matrix->row_fe_space->bas_fcts;
  row_dof = get_el_dof_vec(m_row_bfcts);
  if (use_get_bound) {
    bound      = get_el_schar_vec(m_row_bfcts);
    bndry_bits = get_el_bndry_vec(m_row_bfcts);
  }
    
  if (s_col_bfcts) {
    col_dof = get_el_dof_vec(m_dof_matrix->col_fe_space->bas_fcts);
  } else {
    col_dof = row_dof;
  }

  row_mesh = m_dof_matrix->row_fe_space->mesh;
  if (m_dof_matrix->col_fe_space != NULL) {
    col_mesh = m_dof_matrix->col_fe_space->mesh;
  } else {
    col_mesh = row_mesh;
  }

  if (row_mesh != slave && col_mesh != slave) {
    TRAVERSE_FIRST(slave, -1, fill_flag) {
      const EL_MATRIX *mat;
      
      if ((mat = s_minfo->el_matrix_fct(el_info, s_minfo->fill_info)) == NULL) {
	continue;
      }
      
      get_master_dof_indices(row_dof, el_info, m_dof_matrix->row_fe_space);
      if (s_col_bfcts) {
	get_master_dof_indices(col_dof, el_info, m_dof_matrix->col_fe_space);
      }
      if (use_get_bound) {
	get_master_bound(bndry_bits, el_info, m_row_bfcts);
	dirichlet_map(bound, bndry_bits, m_dof_matrix->dirichlet_bndry);
      }
      add_element_matrix(m_dof_matrix, s_minfo->factor, mat, transpose,
			 row_dof, col_dof, use_get_bound ? bound : NULL);
    } TRAVERSE_NEXT();
  } else if (row_mesh != slave) {
    TRAVERSE_FIRST(slave, -1, fill_flag) {
      const EL_MATRIX *mat;
      
      if ((mat = s_minfo->el_matrix_fct(el_info, s_minfo->fill_info)) == NULL) {
	continue;
      }
      
      get_master_dof_indices(row_dof, el_info, m_dof_matrix->row_fe_space);
      if (s_col_bfcts) {
	get_dof_indices(col_dof, s_col_fe_space, el_info->el);
      }
      if (use_get_bound) {
	get_master_bound(bndry_bits, el_info, m_row_bfcts);
	dirichlet_map(bound, bndry_bits, m_dof_matrix->dirichlet_bndry);
      }
      add_element_matrix(m_dof_matrix, s_minfo->factor, mat, transpose,
			 row_dof, col_dof, use_get_bound ? bound : NULL);
    } TRAVERSE_NEXT();
  } else if (col_mesh != slave) {
    TRAVERSE_FIRST(slave, -1, fill_flag) {
      const EL_MATRIX *mat;
      
      if ((mat = s_minfo->el_matrix_fct(el_info, s_minfo->fill_info)) == NULL) {
	continue;
      }
   
      get_dof_indices(row_dof, m_dof_matrix->row_fe_space, el_info->el);
      if (s_col_bfcts) {
	get_master_dof_indices(col_dof, el_info, m_dof_matrix->col_fe_space);
      }
      if (use_get_bound) {
	get_master_bound(bndry_bits, el_info, m_row_bfcts);
	dirichlet_map(bound, bndry_bits, m_dof_matrix->dirichlet_bndry);
      }
      add_element_matrix(m_dof_matrix, s_minfo->factor, mat, transpose,
			 row_dof, col_dof, use_get_bound ? bound : NULL);
    } TRAVERSE_NEXT();
  }  

  free_el_dof_vec(row_dof);
  if (s_col_bfcts) {
    free_el_dof_vec(col_dof);
  }

  if (use_get_bound) {
    free_el_schar_vec(bound);
    free_el_bndry_vec(bndry_bits);
  }
}

void update_master_real_vec(DOF_REAL_VEC *m_drv, const EL_VEC_INFO *s_vec_info)
{
  FUNCNAME("update_master_real_vec");
  MESH            *slave;
  const DOF_ADMIN *admin;
  bool use_get_bound;
  EL_DOF_VEC   *dof;
  EL_SCHAR_VEC *bound;
  const EL_BNDRY_VEC *bndry_bits;
  FLAGS fill_flag;

  TEST_EXIT(s_vec_info,"no EL_VEC_INFO\n");
  TEST_EXIT(s_vec_info->el_vec_fct,"no el_vec_fct in EL_VEC_INFO\n");
  TEST_EXIT(m_drv,"no DOF_REAL_VEC\n");

  slave = s_vec_info->fe_space->mesh;
  admin = s_vec_info->fe_space->admin;

  use_get_bound = !BNDRY_FLAGS_IS_INTERIOR(s_vec_info->dirichlet_bndry);
  if (use_get_bound) {
    fill_flag = s_vec_info->fill_flag|FILL_BOUND;
    if (slave->is_periodic && !(admin->flags & ADM_PERIODIC)) {
      fill_flag |= FILL_NON_PERIODIC;
    }
  } else {
    fill_flag = s_vec_info->fill_flag;
  }

  s_vec_info->el_vec_fct(NULL, s_vec_info->fill_info);

  dof = get_el_dof_vec(m_drv->fe_space->bas_fcts);
  bound = get_el_schar_vec(m_drv->fe_space->bas_fcts);

  TRAVERSE_FIRST(slave, -1, fill_flag) {
    const EL_REAL_VEC *vec;

    vec = s_vec_info->el_vec_fct(el_info, s_vec_info->fill_info);
    if (vec == NULL) {
      continue;
    }

    get_master_dof_indices(dof, el_info, m_drv->fe_space);
    if (use_get_bound) {
      bndry_bits = get_master_bound(NULL, el_info, m_drv->fe_space->bas_fcts);
      dirichlet_map(bound, bndry_bits, s_vec_info->dirichlet_bndry);
    }
    add_element_vec(m_drv, s_vec_info->factor, vec, dof,
		    use_get_bound ? bound : NULL);
  } TRAVERSE_NEXT();

  free_el_dof_vec(dof);
  free_el_schar_vec(bound);
}

void update_master_real_d_vec(DOF_REAL_D_VEC *m_drdv,
			      const EL_VEC_D_INFO *s_vec_info)
{
  FUNCNAME("update_master_real_d_vec");
  MESH            *slave;
  const BAS_FCTS  *bfcts;
  const DOF_ADMIN *admin;
  bool use_get_bound;
  EL_DOF_VEC   *dof;
  EL_SCHAR_VEC *bound;
  EL_BNDRY_VEC *bndry_bits;
  FLAGS fill_flag;

  TEST_EXIT(s_vec_info,"no EL_VEC_D_INFO\n");
  TEST_EXIT(s_vec_info->el_vec_fct,"no el_vec_fct in EL_VEC_D_INFO\n");
  TEST_EXIT(m_drdv,"no DOF_REAL_D_VEC\n");

  slave = s_vec_info->fe_space->mesh;
  bfcts = s_vec_info->fe_space->bas_fcts;
  admin = s_vec_info->fe_space->admin;

  use_get_bound = !BNDRY_FLAGS_IS_INTERIOR(s_vec_info->dirichlet_bndry);
  if (use_get_bound) {
    fill_flag = s_vec_info->fill_flag|FILL_BOUND;
    if (slave->is_periodic && !(admin->flags & ADM_PERIODIC)) {
      fill_flag |= FILL_NON_PERIODIC;
    }
  } else {
    fill_flag = s_vec_info->fill_flag;
  }

  s_vec_info->el_vec_fct(NULL, s_vec_info->fill_info);

  dof = get_el_dof_vec(m_drdv->fe_space->bas_fcts);
  bound = get_el_schar_vec(m_drdv->fe_space->bas_fcts);

  TRAVERSE_FIRST(slave, -1, fill_flag) {
    const EL_REAL_D_VEC *vec;

    vec = s_vec_info->el_vec_fct(el_info, s_vec_info->fill_info);
    if (vec == NULL) {
      continue;
    }

    get_master_dof_indices(dof, el_info, m_drdv->fe_space);
    if (use_get_bound) {
      bndry_bits = get_bound(NULL, bfcts, el_info);
      dirichlet_map(bound, bndry_bits, s_vec_info->dirichlet_bndry);
    }
    add_element_d_vec(m_drdv, s_vec_info->factor, vec, dof,
		      use_get_bound ? bound : NULL);
  } TRAVERSE_NEXT();

  free_el_dof_vec(dof);
  free_el_schar_vec(bound);
}
