/*--------------------------------------------------------------------------*/
/* ALBERTA:  an Adaptive multi Level finite element toolbox using           */
/*           Bisectioning refinement and Error control by Residual          */
/*           Techniques for scientific Applications                         */
/*--------------------------------------------------------------------------*/
/*                                                                          */
/* file:     parametric_2d.c                                                */
/*                                                                          */
/*                                                                          */
/* description: Support for parametric elements in 2D                       */
/*--------------------------------------------------------------------------*/
/*                                                                          */
/*  authors:   Daniel Koester                                               */
/*             Institut fuer Mathematik                                     */
/*             Universitaet Augsburg                                        */
/*             Universitaetsstr. 14                                         */
/*             D-86159 Augsburg, Germany                                    */
/*                                                                          */
/*             Claus-Justus Heine                                           */
/*             Numerische Mathematik fuer Hoechstleistungsrechner           */
/*             IANS / Universitaet Stuttgart                                */
/*             Pfaffenwaldring 57                                           */
/*             70569 Stuttgart, Germany                                     */
/*                                                                          */
/*  http://www.alberta-fem.de                                               */
/*                                                                          */
/*  (c) by D. Koester (2005),                                               */
/*         C.-J. Heine (2006-2012).                                         */
/*--------------------------------------------------------------------------*/

#ifdef MESH_DIM
# undef MESH_DIM
#endif
#define MESH_DIM 2

#ifdef N_BAS_MAX
# undef N_BAS_MAX
#endif
#define N_BAS_MAX N_BAS_LAGRANGE(LAGRANGE_DEG_MAX, MESH_DIM)

static const REAL_B mid_lambda_2d = INIT_BARY_2D(0.5, 0.5, 0.0);
static const REAL_B bary2_2d[N_BAS_LAG_2D(2)] = {
  INIT_BARY_2D(1.0, 0.0, 0.0),
  INIT_BARY_2D(0.0, 1.0, 0.0),
  INIT_BARY_2D(0.0, 0.0, 1.0),
  INIT_BARY_2D(0.0, 0.5, 0.5),
  INIT_BARY_2D(0.5, 0.0, 0.5),
  INIT_BARY_2D(0.5, 0.5, 0.0)
};

/*--------------------------------------------------------------------------*/
/* Functions for affine elements as parametric elements. (suffix 1_2d)      */
/*--------------------------------------------------------------------------*/

static bool param_init_element1_2d(const EL_INFO *el_info,
				   const PARAMETRIC *parametric)
{
  LAGRANGE_PARAM_DATA *data = (LAGRANGE_PARAM_DATA *)parametric->data;
  DOF_REAL_D_VEC *coords = data->coords;
  int node_v, n0_v, i;
  EL *el = el_info->el;
  EL_INFO *mod_el_info = (EL_INFO *)el_info; /* modifyable pointer reference */

  data->el = el_info->el;

  node_v = el_info->mesh->node[VERTEX];
  n0_v   = coords->fe_space->admin->n0_dof[VERTEX];
    
  if (!parametric->use_reference_mesh) {
    data->local_coords = mod_el_info->coord;
    mod_el_info->fill_flag |= FILL_COORDS;
  } else {
    data->local_coords = data->param_local_coords;
  }
  for (i = 0; i < N_VERTICES_2D; i++) {
    COPY_DOW(coords->vec[el->dof[node_v+i][n0_v]], data->local_coords[i]);
  }

  return false;
}

static void det1_2d(const EL_INFO *el_info, const QUAD *quad, int N,
		    const REAL_B lambda[], REAL dets[])
{
  REAL det;
  int  n;

  det = el_det_2d(el_info);

  if (quad) {
    N = quad->n_points;
  }

  for (n = 0; n < N; n++) {
    dets[n] = det;
  }
 
  return;
}

static void grd_lambda1_2d(const EL_INFO *el_info, const QUAD *quad,
			   int N, const REAL_B lambda[],
			   REAL_BD grd_lam[], REAL_BDD D2_lam[], REAL dets[])
{
  int i, n;

  dets[0] = el_grd_lambda_2d(el_info, grd_lam[0]);

  if (quad) {
    N = quad->n_points;
  }
  
  for (n = 1; n < N; n++) {
    for (i = 0; i < N_LAMBDA_2D; i++) {
      COPY_DOW(grd_lam[0][i], grd_lam[n][i]);
    }
    for (; i < N_LAMBDA_MAX; i++) {
      SET_DOW(0.0, grd_lam[n][i]);
    }
    if (dets) {
      dets[n] = dets[0];
    }
  }

  if (D2_lam) {
    for (n = 0; n < N; n++) {
      for (i = 0; i < N_LAMBDA_MAX;i++) {
	MSET_DOW(0.0, D2_lam[n][i]);
      }
    }
  }

  return;
}

static void
grd_world1_2d(const EL_INFO *el_info, const QUAD *quad,
	      int N, const REAL_B lambda[],
	      REAL_BD grd_Xtr[], REAL_BDB D2_Xtr[], REAL_BDBB D3_Xtr[])
{
  int i;

  if (quad) {
    N = quad->n_points;
  }

  for (i = 0; i < N_LAMBDA_2D; i++) {
    COPY_DOW(el_info->coord[i], grd_Xtr[0][i]);
  }
  for (; i < N_LAMBDA_MAX; i++) {
    SET_DOW(0.0, grd_Xtr[0][i]);
  }
  
  memcpy(grd_Xtr+1, grd_Xtr[0], (N-1)*sizeof(REAL_BDB));

  if (D2_Xtr) {
    memset(D2_Xtr, 0, N*sizeof(REAL_BBD));
  }

  if (D3_Xtr) {
    memset(D3_Xtr, 0, N*sizeof(REAL_BDBB));
  }
}

/* Compute the co-normal for WALL at the barycentric coordinates
 * specified by LAMBDA. LAMBDA are 2d barycentric coordinates,
 * i.e. lambda[wall] == 0.0.
 */
static void
wall_normal1_2d(const EL_INFO *el_info, int wall,
		const QUAD *quad, int n, const REAL_B lambda[],
		REAL_D normals[], REAL_DB grd_normals[], REAL_DBB D2_normals[],
		REAL dets[])
{
  int i;

  if (quad) {
    n = quad->n_points;
  }

  if (grd_normals) {
    memset(grd_normals, 0, n*sizeof(REAL_DB));
  }

  if (D2_normals) {
    memset(D2_normals, 0, n*sizeof(REAL_DBB));
  }

  if (normals) {
    REAL detsbuffer[n];

    if (!dets) {
      dets = detsbuffer;
    }
    dets[0] = get_wall_normal_2d(el_info, wall, normals[0]);

    for (i = 1; i < n; i++) {
      dets[i] = dets[0];
      COPY_DOW(normals[0], normals[i]);
    }
  } else {
    dets[0] = get_wall_normal_2d(el_info, wall, NULL);
    for (i = 1; i < n; i++) {
      dets[i] = dets[0];
    }
  }
}

/****************************************************************************/
/* fill_coords1_2d(data): initialize the DOF_REAL_D_VEC coords containing   */
/* the position data of the parametric elements (coordinates of vertices in */
/* this case).                                                              */
/****************************************************************************/

static void fill_coords1_2d(LAGRANGE_PARAM_DATA *data)
{
  DOF_REAL_D_VEC  *coords = data->coords;
  NODE_PROJECTION *n_proj = data->n_proj;
  bool            selective = n_proj != NULL;
  const NODE_PROJECTION *act_proj;
  FLAGS           fill_flag = CALL_LEAF_EL|FILL_COORDS|FILL_PROJECTION;
  MESH            *mesh;
  const DOF_ADMIN *admin;
  const BAS_FCTS  *bas_fcts;
  int             i;
  DOF             dof[N_VERTICES_2D];

  mesh     = coords->fe_space->mesh;
  admin    = coords->fe_space->admin;
  bas_fcts = coords->fe_space->bas_fcts;

  TRAVERSE_FIRST(mesh, -1, fill_flag|FILL_BOUND) {

    GET_DOF_INDICES(bas_fcts, el_info->el, admin, dof);

    for (i = 0; i < N_VERTICES_2D; i++) {
      REAL *vec = coords->vec[dof[i]];

      COPY_DOW(el_info->coord[i], vec);

      /* Look for a projection function that applies to vertex[i]. */
      /* Apply this projection if found.                           */

      if (!selective || n_proj->func) {
	act_proj = wall_proj(el_info, (i+1) % N_WALLS_2D);
	if (act_proj == NULL) {
	  act_proj = wall_proj(el_info, (i+2) % N_WALLS_2D);
	}
	if (!act_proj) {
	  act_proj = wall_proj(el_info, -1);
	}
	if (act_proj && act_proj->func && (!selective || act_proj == n_proj)){
	  act_proj->func(vec, el_info, bary2_2d[i]);
	}
      }
    }
  } TRAVERSE_NEXT();
}


/****************************************************************************/
/* refine_interpol1_2d(drdv,list,n): update coords vector during refinement.*/
/****************************************************************************/

static void refine_interpol1_2d(DOF_REAL_D_VEC *drdv, RC_LIST_EL *list, int n)
{
  /*FUNCNAME("refine_interpol1_2d");*/
  MESH                *mesh    = drdv->fe_space->mesh;
  LAGRANGE_PARAM_DATA *data    = (LAGRANGE_PARAM_DATA *)mesh->parametric->data;
  REAL_D              *vec     = drdv->vec;
  NODE_PROJECTION     *n_proj  = data->n_proj;
  bool                selective = n_proj != NULL;
  const NODE_PROJECTION *act_proj;
  EL                  *el;
  DOF                 dof_new, dof0, dof1;
  int                 n0, j;

  n0 = drdv->fe_space->admin->n0_dof[VERTEX];
  el = list->el_info.el;

  dof0 = el->dof[0][n0];           /* 1st endpoint of refinement edge */
  dof1 = el->dof[1][n0];           /* 2nd endpoint of refinement edge */
  dof_new = el->child[0]->dof[2][n0];   /* vertex[2] is newest vertex */

  for (j = 0; j < DIM_OF_WORLD; j++)
    vec[dof_new][j] = 0.5*(vec[dof0][j] + vec[dof1][j]);

  act_proj = list->el_info.active_projection;
  
  if (act_proj && act_proj->func && (!selective || n_proj == act_proj)) {
    act_proj->func(vec[dof_new], &list->el_info, mid_lambda_2d);
    _AI_refine_update_bbox(mesh, vec[dof_new]);
  }
}

static void vertex_coordsY_2d(EL_INFO *el_info)
{
  PARAMETRIC          *parametric = el_info->mesh->parametric;
  LAGRANGE_PARAM_DATA *data       = (LAGRANGE_PARAM_DATA *)parametric->data;
  DOF_REAL_D_VEC      *coords     = data->coords;
  EL                  *el         = el_info->el;
  int node_v, n0_v, i;

  node_v = el_info->mesh->node[VERTEX];
  n0_v   = coords->fe_space->admin->n0_dof[VERTEX];
    
  el_info->fill_flag |= FILL_COORDS;
  for (i = 0; i < N_VERTICES_2D; i++) {
    COPY_DOW(coords->vec[el->dof[node_v+i][n0_v]], el_info->coord[i]);
  }
}

/*--------------------------------------------------------------------------*/
/* Common functions for higher order elements. (suffix Y_2d)                */
/*--------------------------------------------------------------------------*/

static bool param_init_elementY_2d(const EL_INFO *el_info,
				   const PARAMETRIC *parametric)
{
  LAGRANGE_PARAM_DATA *data    = (LAGRANGE_PARAM_DATA *)parametric->data;
  DOF_PTR_VEC         *edge_pr = data->edge_projections;
  DOF_REAL_D_VEC      *coords  = data->coords;
  EL                  *el      = el_info->el;
  int i;

  if (data->el != el) {

    data->el = el;

    if (data->strategy == PARAM_ALL) {
      /* full-featured parametric element */
      const BAS_FCTS *bas_fcts = data->coords->fe_space->bas_fcts;

      bas_fcts->get_real_d_vec(data->local_coords, el, coords);
      
      return true;
    } else {
      int node_e = el_info->mesh->node[EDGE];
      int n0_edge_pr = edge_pr->fe_space->admin->n0_dof[EDGE];

      /* An element is affine iff none of its edges has suffered a
       * projection.
       */
      data->i_am_affine = true;
      for (i = 0; i < N_EDGES_2D; i++) {
	if (edge_pr->vec[el->dof[node_e+i][n0_edge_pr]] != NULL) {
	  /* full-featured parametric element */
	  const BAS_FCTS *bas_fcts = data->coords->fe_space->bas_fcts;

	  data->i_am_affine = false;
	  data->local_coords = data->param_local_coords;
	  bas_fcts->get_real_d_vec(data->local_coords, el, coords);
	  
	  return true;
	}
      }
      if (parametric->use_reference_mesh) {
	const BAS_FCTS *bas_fcts = data->coords->fe_space->bas_fcts;
	data->local_coords = data->param_local_coords;
	bas_fcts->get_real_d_vec(data->local_coords, el, coords);
      }
    }
  }
  
  if (!parametric->use_reference_mesh) {
    EL_INFO *mod_el_info = (EL_INFO *)el_info;
    if (data->i_am_affine) {
      /* Need only the vertices. */
      int node_v, n0_v;

      node_v = el_info->mesh->node[VERTEX];
      n0_v   = coords->fe_space->admin->n0_dof[VERTEX];
    
      data->local_coords = (REAL_D *)el_info->coord;
      mod_el_info->fill_flag |= FILL_COORDS;
      for (i = 0; i < N_VERTICES_2D; i++) {
	COPY_DOW(coords->vec[el->dof[node_v+i][n0_v]], data->local_coords[i]);
      }
    } else {
      mod_el_info->fill_flag &= ~FILL_COORDS;
    }
  }

  return !data->i_am_affine;
}

/* Much of the parametric stuff is actually dimension independent,
 * therefore a bunch of functions shared between (at least)
 * parametric_2d.c and parametric_3d.c is implemented in the following
 * header-file:
 */
#include "parametric_intern.h"

/* Compute the co-normal for WALL at a given quadrature point. Helper
 * routine for wall_normalY_2d().
 *
 * On request we compute also the barycentric gradient of the normal,
 * this is needed by vector-valued basis function which point into
 * normal direction.
 */
static inline
REAL wall_normal_iq_2d(REAL_D *const coords,
		       REAL DD[][MESH_DIM],
		       REAL dDD[][MESH_DIM][MESH_DIM],
		       REAL ddDD[][MESH_DIM][MESH_DIM][MESH_DIM],
		       int n_bas, int wall,
		       REAL_D normal, REAL_DB grd_normal, REAL_DBB D2_normal)
{
  REAL w[MESH_DIM], g[MESH_DIM][MESH_DIM], ncoeff[MESH_DIM], detg, det;
  REAL_D e[MESH_DIM];
  int alpha;
  
  /* Make the stuff a little bit more similar to the 3d case */
  detg = Dt_and_DtD_2d(coords, DD, n_bas, e, g);
  
  for (alpha = 0; alpha < MESH_DIM; alpha++) {
    w[alpha] = g[alpha][0] - g[alpha][1];
  }

  /* The coefficient vector of the normal must be orthogonal to the
   * coefficient vector w of (e0 - e1) w.r.t. to g, so the following
   * should be the outer normal (at least the orientation of the
   * normal is determined, plese FIXME if it should be the inner
   * normal ... at least it has a defined sign).
   */
  ncoeff[0] = -w[1];
  ncoeff[1] = +w[0];

  AXPBY_DOW(ncoeff[0], e[0], ncoeff[1], e[1], normal);

  /* The length of "normal" is sqrt(det g)*|wall|, as in the 3d
   * case. We correct this later. By construction "normal" is the
   * outer normal to the simplex.
   *
   * We can now proceed as in the 3d case and compute the Jacobian of
   * the normal field.
   */
  if (grd_normal || D2_normal) {
    REAL_D De[MESH_DIM][MESH_DIM];
    REAL   Dg[MESH_DIM][MESH_DIM][MESH_DIM];
    REAL   Dncoeff[MESH_DIM][MESH_DIM];
    REAL   nuDnu[MESH_DIM];
    REAL_D grd_tmp[MESH_DIM], grd_loc[MESH_DIM];
    REAL   inv_nrm2, inv_norm;
    int i, beta, gamma;
    
    dDt_and_dDtD_2d(coords, dDD, e, n_bas, De, Dg);
    
    /* normal = (g_10 - g11)*e0 - (g00 -g01) * e1 */

    /* First part: differentiate the basis vector of the tangent space */
    for (alpha = 0; alpha < MESH_DIM; alpha++) {
      AXPBY_DOW(ncoeff[0], De[alpha][0], ncoeff[1], De[alpha][1],
		grd_tmp[alpha]);
    }
    
    /* second part: differentiate the coefficients. */
    for (alpha = 0; alpha < MESH_DIM; alpha++) {
      Dncoeff[alpha][0] = Dg[alpha][1][0]-Dg[alpha][1][1];
      Dncoeff[alpha][1] = Dg[alpha][0][1]-Dg[alpha][0][0];
      AXPY_DOW(Dncoeff[alpha][0], e[0], grd_tmp[alpha]);
      AXPY_DOW(Dncoeff[alpha][1], e[1], grd_tmp[alpha]);
    }
   
    /* Now it remains to take the normalization into account, and to
     * re-order the barycentric co-ordinates s.t. they match ordering
     * of the bulk element.
     */
    inv_nrm2 =
      grd_normalize_2d(grd_loc, nuDnu, (const REAL_D *)grd_tmp, normal);
    inv_norm = sqrt(inv_nrm2);

    if (grd_normal) {
      /* Sort the stuff s.t. it uses the proper order of the
       * barycentric co-ordinates
       */
      for (i = 0; i < DIM_OF_WORLD; i++) {
	/* constant extension along \lambda_wall co-ordinate lines. */
	grd_normal[i][wall] = 0.0;
	for (alpha = 0; alpha < MESH_DIM; alpha++) {
	  grd_normal[i][(wall+alpha+1) % N_LAMBDA(MESH_DIM)] =
	    inv_norm * grd_loc[alpha][i];
	}
      }
    }

    if (D2_normal) {
      REAL_D       D2e[MESH_DIM][MESH_DIM][MESH_DIM];
      REAL_DDDD_2D D2g;
      REAL_D       D2_tmp[MESH_DIM][MESH_DIM], D2_loc[MESH_DIM][MESH_DIM];

      ddDt_and_ddDtD_2d(coords, ddDD, e, De, Dg, n_bas, D2e, D2g);

      /* First part: take the second derivatives of the tangent vectors */
      for (alpha = 0; alpha < MESH_DIM; alpha++) {
	for (gamma = 0; gamma < MESH_DIM; gamma++) {
	  AXPY_DOW(ncoeff[gamma], D2e[alpha][alpha][gamma],
		   D2_tmp[alpha][alpha]);
	}
	for (beta = alpha+1; beta < MESH_DIM; beta++) {
	  for (gamma = 0; gamma < MESH_DIM; gamma++) {
	    AXPY_DOW(ncoeff[gamma], D2e[alpha][beta][gamma],
		     D2_tmp[alpha][beta]);
	  }
	}
      }

      /* Second part: first derivatives of coefficients, with first
       * derivatives of the tangent vectors.
       */
      for (alpha = 0; alpha < MESH_DIM; alpha++) {
	for (gamma = 0; gamma < MESH_DIM; gamma++) {
	  AXPY_DOW(2.0*Dncoeff[alpha][gamma], De[alpha][gamma],
		   D2_tmp[alpha][beta]);
	}
	for (beta = alpha+1; beta < MESH_DIM; beta++) {
	  for (gamma = 0; gamma < MESH_DIM; gamma++) {
	    AXPY_DOW(Dncoeff[alpha][gamma], De[beta][gamma],
		     D2_tmp[alpha][beta]);
	    AXPY_DOW(Dncoeff[beta][gamma], De[alpha][gamma],
		     D2_tmp[alpha][beta]);
	  }
	}
      }

      /* Third part: second derivatives of coefficients, with values
       * of the tangent vectors.
       */
      for (alpha = 0; alpha < MESH_DIM; alpha++) {
	for (beta = alpha+1; beta < MESH_DIM; beta++) {
	  AXPY_DOW(D2g[alpha][beta][1][0] - D2g[alpha][beta][1][1],
		   e[0], D2_tmp[alpha][beta]);
	  AXPY_DOW(D2g[alpha][beta][0][1] - D2g[alpha][beta][0][0],
		   e[1], D2_tmp[alpha][beta]);
	}
      }

      /* Now we have computed the second derivatives of the non-unit
       * normals. We have to take the normalization into account.
       */
      D2_normalize_2d(D2_loc, normal,
		      (const REAL_D *)grd_tmp,
		      (const REAL_D (*)[MESH_DIM])D2_tmp, inv_nrm2, nuDnu);
      
      /* Sort the stuff s.t. it uses the proper order of the
       * barycentric co-ordinates
       */
      for (i = 0; i < DIM_OF_WORLD; i++) {
	D2_normal[i][wall][wall] = 0.0;
	for (alpha = 0; alpha < MESH_DIM; alpha++) {
	  int a = (wall + alpha + 1) % N_LAMBDA(MESH_DIM);
	  D2_normal[i][a][a] = inv_norm * D2_loc[alpha][alpha][i];
	  D2_normal[i][wall][a] = D2_normal[i][a][wall] = 0.0;
	  for (beta = alpha + 1; beta < MESH_DIM; beta++) {
	    int b = (wall + beta + 1) % N_LAMBDA(MESH_DIM);

	    D2_normal[i][a][b] =
	      D2_normal[i][b][a] = inv_norm * D2_loc[alpha][beta][i];
	  }
	}
      }
    }
  }

  /* Now scale the normal s.t. it reflects the magnitude of the line element */
  SCAL_DOW(1.0/sqrt(detg), normal);
  
#if 1
  /* In principle the orientation should be fixed ... */
  if (SCP_DOW(e[1], normal) <= 0.0) {
    if (ALBERTA_DEBUG) {
      WARNING("Wrong orientation?\n");
    }
    SCAL_DOW(-1.0, normal);
  }
#endif

  det = NORM_DOW(normal);

  /* cH: shouldn't the following test the relative size of det w.r.t.,
   * e.g. the edge length?
   */
  TEST_EXIT(det > 1.e-30, "face det = 0 on face %d.\n", wall);

  return det;
}

/*--------------------------------------------------------------------------*/
/* Functions for quadratic elements. (suffix 2_2d)                          */
/*--------------------------------------------------------------------------*/

static void refine_interpol2_2d(DOF_REAL_D_VEC *drdv, RC_LIST_EL *list, int n)
{
  /*FUNCNAME("refine_interpol2_2d");*/
  MESH                *mesh     = drdv->fe_space->mesh;
  LAGRANGE_PARAM_DATA *data     = (LAGRANGE_PARAM_DATA *)mesh->parametric->data;
  const DOF_ADMIN     *admin    = drdv->fe_space->admin;
  const BAS_FCTS      *bas_fcts = drdv->fe_space->bas_fcts;
  DOF_PTR_VEC         *edge_pr  = data->edge_projections;
  REAL_D              *vec      = drdv->vec;
  const EL_INFO       *el_info;
  EL                  *el;
  int                 node_v, node_e, n0_v, n0_e, i, j, i_child;
  DOF                 cdof, cdof_e[N_EDGES_2D], cdof_v[N_VERTICES_2D], pdof;
  REAL                *x[3];
  NODE_PROJECTION     *n_proj;
  bool                selective;
  const NODE_PROJECTION *act_proj;
  int                 n0_edge_pr = -1;
  DOF                 cdof_edge_pr[3] = { -1, };
  static const REAL_B lambda[3] = {INIT_BARY_2D(0.25, 0.25, 0.5),
				   INIT_BARY_2D(0.75, 0.25, 0.0),
				   INIT_BARY_2D(0.25, 0.75, 0.0) };

  el_info   = &list->el_info;
  el        = el_info->el;
  n_proj    = data->n_proj;
  selective = n_proj != NULL;

  node_v = mesh->node[VERTEX];        
  node_e = mesh->node[EDGE]; 
  n0_v   = admin->n0_dof[VERTEX];
  n0_e   = admin->n0_dof[EDGE];

/****************************************************************************/
/* Step 1: Now we need to determine three points: The midpoint on the edge  */
/* between the two children and the two midpoints of the children along the */
/* current refinement edge. These three points correspond to the three new  */
/* DOFS that were created.                                                  */
/* If strategy is PARAM_ALL or PARAM_CURVED_CHILDS we use the (nonlinear)   */
/* barycentric coords. For strategy PARAM_STRAIGHT_CHILD we use geometric   */
/* midpoints.                                                               */
/****************************************************************************/
  x[0] = vec[el->child[0]->dof[node_e+1][n0_e]];
  x[1] = vec[el->child[0]->dof[node_e+0][n0_e]];
  x[2] = vec[el->child[1]->dof[node_e+1][n0_e]];

  if (data->strategy != PARAM_STRAIGHT_CHILDS) {
    /* Just call the default interpolation routine */
    bas_fcts->real_d_refine_inter(drdv, list, n);
  } else {
    cdof_e[2] = el->dof[node_e+2][n0_e];
    for (i = 0; i < N_EDGES_2D; i++)
      cdof_v[i] = el->dof[node_v+i][n0_v];

    for (i = 0; i < DIM_OF_WORLD; i++) {
      x[0][i] = 0.5 * (vec[cdof_v[2]][i] + vec[cdof_e[2]][i]);
      x[1][i] = 0.5 * (vec[cdof_v[0]][i] + vec[cdof_e[2]][i]);
      x[2][i] = 0.5 * (vec[cdof_v[1]][i] + vec[cdof_e[2]][i]);
    }
  }
    
/****************************************************************************/
/* Step 2: We check if any projections need to be done on these three       */
/* points. While doing this, we keep track of projected coordinates.        */
/****************************************************************************/
  if (edge_pr) {
    n0_edge_pr = edge_pr->fe_space->admin->n0_dof[EDGE];

    cdof_edge_pr[0] = el->child[0]->dof[node_e+1][n0_edge_pr];
    cdof_edge_pr[1] = el->child[0]->dof[node_e+0][n0_edge_pr];
    cdof_edge_pr[2] = el->child[1]->dof[node_e+1][n0_edge_pr];

    edge_pr->vec[cdof_edge_pr[0]] =
      edge_pr->vec[cdof_edge_pr[1]] =
      edge_pr->vec[cdof_edge_pr[2]] = NULL;

    act_proj = wall_proj(el_info, -1);
    if (act_proj && (!selective || act_proj == n_proj)) {
      edge_pr->vec[cdof_edge_pr[0]] = (void *)act_proj;
    }
    act_proj = el_info->active_projection;
    if (act_proj && (!selective || act_proj == n_proj)) {
      edge_pr->vec[cdof_edge_pr[1]] = (void *)act_proj;
      edge_pr->vec[cdof_edge_pr[2]] = (void *)act_proj;
    }
  }

  act_proj = wall_proj(el_info, -1);
  if (act_proj && act_proj->func && (!selective || act_proj == n_proj)) {
    act_proj->func(x[0], el_info, lambda[0]);
    _AI_refine_update_bbox(mesh, x[0]);
  }
  act_proj = el_info->active_projection;
  if (act_proj && act_proj->func && (!selective || act_proj == n_proj)) {
    act_proj->func(x[1], el_info, lambda[1]);
    _AI_refine_update_bbox(mesh, x[1]);
    act_proj->func(x[2], el_info, lambda[2]);
    _AI_refine_update_bbox(mesh, x[2]);
  }

/****************************************************************************/
/* Step 3: We hand down the data corresponding to the parent edge vertex    */
/* in the refinement edge (its DOF could be deleted!).                      */
/****************************************************************************/
  pdof = el->dof[node_e+2][n0_e];
  cdof = el->child[0]->dof[node_v+2][n0_v];
  COPY_DOW(vec[pdof], vec[cdof]);

/****************************************************************************/
/* Step 4: Correct the children, if not all elements are to be parametric.  */
/* If the parent element was already affine, then the children will already */
/* have the correct coordinates.                                            */
/****************************************************************************/
  if (edge_pr) {
    int parent_affine = true;
    
    for (j = 0; j < N_EDGES_2D; j++) {
      DOF edge_dof = el->dof[node_e+j][n0_edge_pr];
	
      if (edge_pr->vec[edge_dof] != NULL) {
	parent_affine = false;
	break;
      }
    }

    if (!parent_affine) for (i_child = 0; i_child < 2; i_child++) {
      EL *child = el->child[i_child];
      int child_affine = true;
      
      for (j = 0; j < N_EDGES_2D; j++) {
	DOF edge_dof = child->dof[node_e+j][n0_edge_pr];
	
	if (edge_pr->vec[edge_dof] != NULL) {
	  child_affine = false;
	  break;
	}
      }
      if (child_affine) {
	for (j = 0; j < N_EDGES_2D; j++) {
	  cdof_e[j] = child->dof[node_e+j][n0_e];
	  cdof_v[j] = child->dof[node_v+j][n0_v];
	}
	
	for (j = 0; j < DIM_OF_WORLD; j++) {
	  vec[cdof_e[0]][j] = 0.5*(vec[cdof_v[1]][j] + vec[cdof_v[2]][j]);
	  vec[cdof_e[1]][j] = 0.5*(vec[cdof_v[0]][j] + vec[cdof_v[2]][j]);
	  vec[cdof_e[2]][j] = 0.5*(vec[cdof_v[0]][j] + vec[cdof_v[1]][j]);
	}
      }
    }
  }

/*--------------------------------------------------------------------------*/
/*---  Take care of the neighbour element.                               ---*/
/*--------------------------------------------------------------------------*/
  if (n > 1) {
    el_info = &list[1].el_info;
    el      = el_info->el;

/****************************************************************************/
/* Step 1: Determine the location of the midpoint on the edge between the   */
/* two children.                                                            */
/****************************************************************************/
    x[0] = vec[el->child[0]->dof[node_e+1][n0_e]];

    if (data->strategy == PARAM_STRAIGHT_CHILDS) {
      cdof_e[2] = el->dof[node_e+2][n0_e];
      cdof_v[2] = el->dof[node_v+2][n0_v];

      AXPBY_DOW(0.5, vec[cdof_v[2]], 0.5, vec[cdof_e[2]], x[0]);
    }

/****************************************************************************/
/* Step 2: We check if any projections need to be done on this point.       */
/* While doing this, we keep track of projected coordinates.                */
/****************************************************************************/

    if (edge_pr) {
      cdof_edge_pr[0] = el->child[0]->dof[node_e+1][n0_edge_pr];
      edge_pr->vec[cdof_edge_pr[0]] = NULL;

      act_proj = wall_proj(el_info, -1);
      if (act_proj && (!selective || act_proj == n_proj)) {
	cdof_edge_pr[0] = el->child[0]->dof[node_e+1][n0_edge_pr];
	edge_pr->vec[cdof_edge_pr[0]] = (void *)act_proj;
      }
    }

    act_proj = wall_proj(el_info, -1);
    if (act_proj && act_proj->func && (!selective || act_proj == n_proj)) {
      act_proj->func(x[0], el_info, lambda[0]);
      _AI_refine_update_bbox(mesh, x[0]);
    }

/****************************************************************************/
/* Step 3: Correct the children.                                            */
/***************************************************************************/
    if (edge_pr) {
      int parent_affine = true;
    
      for (j = 0; j < N_EDGES_2D; j++) {
	DOF edge_dof = el->dof[node_e+j][n0_edge_pr];
	
	if (edge_pr->vec[edge_dof] != NULL) {
	  parent_affine = false;
	  break;
	}
      }
      if (!parent_affine) for (i_child = 0; i_child < 2; i_child++) {
	EL *child = el->child[i_child];
	int child_affine = true;
	
	for (j = 0; j < N_EDGES_2D; j++) {
	  DOF edge_dof = child->dof[node_e+j][n0_edge_pr];
	
	  if (edge_pr->vec[edge_dof] != NULL) {
	    child_affine = false;
	    break;
	  }
	}
	if (child_affine) {
	  for (j = 0; j < N_EDGES_2D; j++) {
	    cdof_e[j] = child->dof[node_e+j][n0_e];
	    cdof_v[j] = child->dof[node_v+j][n0_v];
	  }
	  
	  for (j = 0; j < DIM_OF_WORLD; j++) {
	    vec[cdof_e[0]][j] = 0.5*(vec[cdof_v[1]][j] + vec[cdof_v[2]][j]);
	    vec[cdof_e[1]][j] = 0.5*(vec[cdof_v[0]][j] + vec[cdof_v[2]][j]);
	    vec[cdof_e[2]][j] = 0.5*(vec[cdof_v[0]][j] + vec[cdof_v[1]][j]);
	  }
	}
      }
    }
  }

  return;
}

static void coarse_interpol2_2d(DOF_REAL_D_VEC *drdv, RC_LIST_EL *list, int n)
{
  FUNCNAME("coarse_interpol2_2d");
  EL                  *el;
  REAL_D              *vec = NULL;
  int                 node_v, node_e, n0_v, n0_e, j;
  DOF                 cdof, pdof;
  const MESH          *mesh = drdv->fe_space->mesh;
  LAGRANGE_PARAM_DATA *data = (LAGRANGE_PARAM_DATA *)mesh->parametric->data;
  DOF_PTR_VEC         *edge_pr = data->edge_projections;

  GET_DOF_VEC(vec, drdv);
  el = list->el_info.el;

  node_v = mesh->node[VERTEX];        
  node_e = mesh->node[EDGE]; 
  n0_v   = drdv->fe_space->admin->n0_dof[VERTEX];
  n0_e   = drdv->fe_space->admin->n0_dof[EDGE];

/****************************************************************************/
/*  copy values at refinement vertex to the parent refinement edge.         */
/****************************************************************************/

  cdof = el->child[0]->dof[node_v+2][n0_v];      /* newest vertex is dim */
  pdof = el->dof[node_e+2][n0_e];

  for (j = 0; j < DIM_OF_WORLD; j++)
    vec[pdof][j] = vec[cdof][j];

  if (edge_pr) {
    int n0_edge_pr = edge_pr->fe_space->admin->n0_dof[EDGE];
    DOF pdof       = el->dof[node_e+2][n0_edge_pr];
    DOF cdof       = el->child[0]->dof[node_e+0][n0_edge_pr];

    edge_pr->vec[pdof] = edge_pr->vec[cdof];
  }

  return;
}

static void fill_coords2_2d(LAGRANGE_PARAM_DATA *data)
{
  DOF_REAL_D_VEC  *coords   = data->coords;
  MESH            *mesh     = coords->fe_space->mesh;
  const DOF_ADMIN *admin    = coords->fe_space->admin;
  const BAS_FCTS  *bas_fcts = coords->fe_space->bas_fcts;
  DOF_PTR_VEC     *edge_pr  = data->edge_projections;
  NODE_PROJECTION *n_proj   = data->n_proj;
  bool            selective = n_proj != NULL;
  const NODE_PROJECTION *act_proj;
  FLAGS           fill_flag = CALL_LEAF_EL|FILL_COORDS|FILL_PROJECTION;
  DOF             dof[bas_fcts->n_bas_fcts];
  int             i, node_e = -1, n0_edge_pr = -1;
  DOF             dof_edge_pr = -1;

  if (edge_pr) {
    node_e     = mesh->node[EDGE];
    n0_edge_pr = edge_pr->fe_space->admin->n0_dof[EDGE];
  }

  TRAVERSE_FIRST(mesh, -1, fill_flag|FILL_BOUND) {
    REAL *vec, *vec0, *vec1;

    GET_DOF_INDICES(bas_fcts, el_info->el, admin, dof);

    for (i = 0; i < N_VERTICES_2D; i++) {
      vec = coords->vec[dof[i]];

      COPY_DOW(el_info->coord[i], vec);

      /* Look for a projection function that applies to vertex[i].
       * Apply this projection if found.
       */

      if (!selective || n_proj->func) {
	act_proj = wall_proj(el_info, (i+1) % N_WALLS_2D);

	if (!act_proj) {
	  act_proj = wall_proj(el_info, (i+2) % N_WALLS_2D);
	}
	if (!act_proj) {
	  act_proj = wall_proj(el_info, -1);
	}
	if (act_proj && act_proj->func && (!selective || act_proj == n_proj)) {
	  act_proj->func(vec, el_info, bary2_2d[i]);
	}
      }
    }

    for (i = 0; i < N_EDGES_2D; i++) {
      const int *voe = vertex_of_edge_2d[i];

      vec = coords->vec[dof[N_VERTICES_2D+i]];
      vec0 = coords->vec[dof[voe[0]]];
      vec1 = coords->vec[dof[voe[1]]];

      AXPBY_DOW(0.5, vec0, 0.5, vec1, vec);

      /* Look for a projection function that applies to edge[i].   */
      /* Apply this projection if found.                           */

      if (edge_pr) {
	dof_edge_pr = el_info->el->dof[node_e+i][n0_edge_pr];
	edge_pr->vec[dof_edge_pr] = NULL;

	act_proj = wall_proj(el_info, i);
	if (!act_proj) {
	  act_proj = wall_proj(el_info, -1);
	}
	if (act_proj && (!selective || act_proj == n_proj)) {
	  edge_pr->vec[dof_edge_pr] = (void *)act_proj;
	}
      }

      if (!selective || n_proj->func) {
	act_proj = wall_proj(el_info, i);

	if (!act_proj) {
	  act_proj = wall_proj(el_info, -1);
	}
	if (act_proj && act_proj->func && (!selective  || act_proj == n_proj)) {
	  act_proj->func(vec, el_info, bary2_2d[N_VERTICES_2D + i]);
	}
      }
    }
  } TRAVERSE_NEXT();
}

/* In order to obtain optimal error estimates for higher-order
 * boundary approximations we need to be careful with the choice of
 * the interior interpolation points. We do this only for strategy !=
 * PARAM_ALL.
 *
 * We follow the procedure described in: M. Lenoir, Optimal
 * Isoparametric Finite Elements and Error Estimates for Domains
 * Involving Curved Bounaries, SIAM JNA 23(3) 1986.
 *
 * The procedure probably only makes sense when one and only one edge
 * is projected, but we try to be a little bit more general here.
 *
 * This function assumes that center nodes are ordered this way:
 *
 * - the nodes are ordered in rows, row 0 corresponds to the smallest
 *   value of lambda[2].
 */
static void adjust_center_nodesY_2d(const DOF *dof, const REAL_B *nodes,
				    REAL_D *vec, NODE_PROJECTION **edge_pr_loc,
				    int n_e, int n_c)
{
  int i, j, n_edge_pr;
  REAL scale;

  n_edge_pr = 0;
  for (i = 0; i < N_WALLS_2D; i++) {
    if (edge_pr_loc[i] != NULL) { /* This edge is curved */
      n_edge_pr++;
    }
  }
  scale = 1.0/(REAL)n_edge_pr;

  for (i = 0; i < N_WALLS_2D; i++) {
    if (edge_pr_loc[i] != NULL) { /* This edge is curved */
      REAL_B lambda;
      REAL_D d;
      int v1, v2;	      
	      
      lambda[i] = 0.0;
      v1 = vertex_of_edge_2d[i][0];
      v2 = vertex_of_edge_2d[i][1];
	      
      for (j = 0; j < n_c; j++) {
	int ldof = N_VERTICES_2D+N_EDGES_2D*n_e+j;
	int e_ldof;
		
	lambda[v1] = 1.0 - nodes[ldof][v2];
	lambda[v2] = nodes[ldof][v2];

	/* This is actually a Lagrange-node on the wall, determine its
	 * number.
	 */
	e_ldof  = (int)(lambda[v2]*(REAL)(n_e + 1) + 0.5) - 1;
	e_ldof += N_VERTICES_2D + n_e*i;
	AXPBY_DOW(lambda[v1], vec[dof[v1]],
		  lambda[v2], vec[dof[v2]], d);
	AXPY_DOW(-1.0, vec[dof[e_ldof]], d);
	AXPY_DOW(-scale*0.5*nodes[ldof][v1]/lambda[v1], d, vec[dof[ldof]]);
		
	/* Symmetrize a little bit */
	lambda[v1] = nodes[ldof][v1];
	lambda[v2] = 1.0 - nodes[ldof][v1];

	/* This is actually a Lagrange-node on the wall,
	 * determine its number.
	 */
	e_ldof  = n_e - 1 - (int)(lambda[v1]*(REAL)(n_e + 1) + 0.5) + 1;
	e_ldof += N_VERTICES_2D + n_e*i;
	AXPBY_DOW(lambda[v1], vec[dof[v1]],
		  lambda[v2], vec[dof[v2]], d);
	AXPY_DOW(-1.0, vec[dof[e_ldof]], d);
	AXPY_DOW(-scale*0.5*nodes[ldof][v2]/lambda[v2], d, vec[dof[ldof]]);
      }
    }
  }
}

static const REAL_B *const*child_nodes_2d(int degree)
{
  static const REAL_B child_loc[2][N_VERTICES_2D] = {
    { INIT_BARY_2D(0.0, 0.0, 1.0),
      INIT_BARY_2D(1.0, 0.0, 0.0),
      INIT_BARY_2D(0.5, 0.5, 0.0) },
    { INIT_BARY_2D(0.0, 1.0, 0.0),
      INIT_BARY_2D(0.0, 0.0, 1.0),
      INIT_BARY_2D(0.5, 0.5, 0.0) },
  };
  static REAL_B *(*child_nodes)[2];  
  static int degree_max;
  int i, j, k, d;

  if (!child_nodes) {
    child_nodes = (REAL_B *(*)[2])MEM_ALLOC(2*(degree+1), REAL_B *);
  } else if (degree > degree_max) {
    child_nodes = (REAL_B *(*)[2])
      MEM_REALLOC(child_nodes, 3*(degree_max+1), 2*(degree+1), REAL_B *);
  }

  /* we generate all child nodes up to degree_max */
  if (degree_max < degree) {
    for (d = MAX(degree_max, 1); d <= degree; d++) {
      const BAS_FCTS *bas_fcts = get_lagrange(MESH_DIM, d);    
      const REAL_B *nodes = LAGRANGE_NODES(bas_fcts);
      int n_bas_fcts;

      n_bas_fcts = (d+1)*(d+2)/2;
      child_nodes[d][0] = MEM_ALLOC(n_bas_fcts, REAL_B);
      child_nodes[d][1] = MEM_ALLOC(n_bas_fcts, REAL_B);
      
      for (i = 0; i < n_bas_fcts; i++) {
	for (j = 0; j < 2; j++) {
	  AXEY_BAR(MESH_DIM, nodes[i][0], child_loc[j][0],
		   child_nodes[d][j][i]);
	  for (k = 1; k < N_VERTICES_2D; k++) {
	    AXPY_BAR(MESH_DIM, nodes[i][k], child_loc[j][k],
		     child_nodes[d][j][i]);
	  }
	}
      }
    }
    degree_max = degree;
  }
  return (const REAL_B *const*)child_nodes[degree];
}

static void refine_interpolY_2d(DOF_REAL_D_VEC *drdv, RC_LIST_EL *list, int n)
{
  const FE_SPACE      *fe_space = drdv->fe_space;
  MESH                *mesh = fe_space->mesh;
  const BAS_FCTS      *bas_fcts = fe_space->bas_fcts;
  const DOF_ADMIN     *admin = fe_space->admin;
  LAGRANGE_PARAM_DATA *data = (LAGRANGE_PARAM_DATA *)mesh->parametric->data;
  PARAM_STRATEGY      strategy = data->strategy;
  DOF_PTR_VEC         *edge_pr = data->edge_projections;
  NODE_PROJECTION     *n_proj = data->n_proj;
  bool                selective = n_proj != NULL;
  const NODE_PROJECTION *act_proj;
  REAL_D              *vec = drdv->vec;
  int                 node_v, node_e, n0_v, n_c, n_e;
  const EL_INFO       *el_info;  
  EL                  *el;
  const REAL_B        *nodes = LAGRANGE_NODES(bas_fcts);
  const REAL_B        *const*child_nodes;
  int                 ref_edge_untouched = false;
  int                 n0_edge_pr = -1;
  int                 i, j, elem, i_child;

  TEST_EXIT(bas_fcts->degree <= DEGREE_MAX,
	    "degree %d unsupported, only up to degree %d.\n",
	    bas_fcts->degree, DEGREE_MAX);

  child_nodes = child_nodes_2d(bas_fcts->degree);

  node_e = mesh->node[EDGE];
  node_v = mesh->node[VERTEX];
  n0_v   = admin->n0_dof[VERTEX];
  n_e    = admin->n_dof[EDGE];
  n_c    = admin->n_dof[CENTER];

  if (edge_pr) {
    n0_edge_pr = edge_pr->fe_space->admin->n0_dof[EDGE];
  }

  /* If strategy != PARAM_STRAIGHT_CHILDS first call the default
   * interpolation routine for the coordinates. Why should we
   * duplicate work. Otherwise we interpolate between the vertices
   * according to the Lagrange-nodes.
   */
  if (strategy != PARAM_STRAIGHT_CHILDS) {
    bas_fcts->real_d_refine_inter(drdv, list, n);
  }

  for (elem = 0; elem < n; elem++) {
    el_info = &list[elem].el_info;
    el = el_info->el;

    for (i_child = 0; i_child < 2; i_child++) {
      const REAL_B *cnodes;
      DOF dof[N_BAS_MAX];

      cnodes = child_nodes[i_child];
	
      /* Get the DOF-indices on the child */
      GET_DOF_INDICES(bas_fcts, el->child[i_child], admin, dof);

      /* handle the refinement edge, once only */
      if (elem == 0) {
	/* <<< refinement edge stuff */

	if (i_child == 0) {

	  /* Compute coordinates if not already done */
	  if (strategy == PARAM_STRAIGHT_CHILDS) {
	    /* vertex 2 coordinate */
	    AXPBY_DOW(0.5, vec[el->dof[node_v+0][n0_v]],
		      0.5, vec[el->dof[node_v+1][n0_v]], vec[dof[2]]);
	  }
	  
	  /* Update edge_projections and do the necessary
	   * projections. The children just inherit the
	   * edge_projections value from the parent.
	   */
	  if (edge_pr) {
	    DOF pdof = el->dof[node_e+2][n0_edge_pr];
	    ref_edge_untouched = edge_pr->vec[pdof] == NULL;
	  }

	  act_proj = list->el_info.active_projection;
	  
	  if (i_child == 0 &&
	      !ref_edge_untouched &&
	      act_proj && act_proj->func &&
	      (!selective || act_proj == n_proj)) {
	    /* Project the midpoint of the refinement edge */
	    act_proj->func(vec[dof[2]], el_info, mid_lambda_2d);
	    _AI_refine_update_bbox(mesh, vec[dof[2]]);
	  }
	}
	
	/* Compute coordinates if not already done. Do this after
	 * (potentially) projecting the refinement edge.
	 */
	if (strategy == PARAM_STRAIGHT_CHILDS) {
	  for (i = 0; i < n_e; i++) {
	    int local_dof = N_VERTICES_2D+i_child*n_e+i;
	  
	    AXPBY_DOW(nodes[local_dof][1-i_child], vec[dof[1-i_child]],
		      nodes[local_dof][2], vec[dof[2]],
		      vec[dof[local_dof]]);
	  }
	}
	
	/* Apply the necessary projections */
	act_proj = list->el_info.active_projection;

	if (!ref_edge_untouched &&
	    act_proj && (!selective || n_proj == act_proj)) {
	  if (act_proj->func) {
	    /* Project all other edge DOFs */
	    for (i = 0; i < n_e; i++) {
	      int ldof = N_VERTICES_2D+i_child*n_e+i;

	      act_proj->func(vec[dof[ldof]], el_info, cnodes[ldof]);
	      _AI_refine_update_bbox(mesh, vec[dof[ldof]]);
	    }
	  }
	  
	  if (edge_pr) {
	    DOF edge_pr_dof = el->child[i_child]->dof[node_e+i_child][n0_edge_pr];
	    edge_pr->vec[edge_pr_dof] = (void *)act_proj;
	  }
	} else if (strategy != PARAM_ALL) {
	  DOF edge_pr_dof = el->child[i_child]->dof[node_e+i_child][n0_edge_pr];
	  edge_pr->vec[edge_pr_dof] = NULL;
	}
	/* >>> */
      }

      /* handle the common edge between the two children, do this only
       * once for each parent
       */
      if (i_child == 0 /* && n_e */) {
	/* <<< interior edge  */

	/* Compute coordinates if not already done */
	if (data->strategy == PARAM_STRAIGHT_CHILDS) {
	  for (i = 0; i < n_e; i++) {
	    int local_dof = N_VERTICES_2D+(1-i_child)*n_e+i;
	  
	    AXPBY_DOW(nodes[local_dof][i_child], vec[dof[i_child]],
		      nodes[local_dof][2], vec[dof[2]],
		      vec[dof[local_dof]]);
	  }
	}
	
	act_proj = wall_proj(el_info, -1);
	if (!ref_edge_untouched &&
	    act_proj && (!selective || n_proj == act_proj)) {
	  if (act_proj->func) {
	    for (i = 0; i < n_e; i++) {
	      int local_dof = N_VERTICES_2D+(1-i_child)*n_e+i;
	    
	      act_proj->func(vec[dof[local_dof]], el_info, cnodes[local_dof]);
	      _AI_refine_update_bbox(mesh, vec[dof[local_dof]]);
	    }
	  }

	  if (edge_pr) {
	    DOF edge_pr_dof = el->child[0]->dof[node_e+(1-i_child)][n0_edge_pr];
	    edge_pr->vec[edge_pr_dof] = (void *)act_proj;
	  }
	} else if (edge_pr) {
	  DOF edge_pr_dof = el->child[0]->dof[node_e+(1-i_child)][n0_edge_pr];
	  edge_pr->vec[edge_pr_dof] = NULL;
	}

	/* >>> */
      }

      /* then the interior DOFs, if any */
      if (n_c) {
	if (data->strategy == PARAM_STRAIGHT_CHILDS) {
	  NODE_PROJECTION *edge_pr_loc[N_EDGES_2D];

	  /* First compute default coordinates if not already done */
	  for (i = 0; i < n_c; i++) {
	    int local_dof = N_VERTICES_2D+N_EDGES_2D*n_e+i;

	    AXEY_DOW(nodes[local_dof][0], vec[dof[0]], vec[dof[local_dof]]);
	    for (j = 1; j < N_VERTICES_2D; j++) {
	      AXPY_DOW(nodes[local_dof][j], vec[dof[j]], vec[dof[local_dof]]);
	    }
	  }

	  /* Then add a correction which guarantees that the higher
	   * derivative of the transformation to the reference element
	   * vanish fast enough.
	   */
	  for (i = 0; i < N_EDGES_2D; i++) {
	    edge_pr_loc[i] =
	      edge_pr->vec[el->child[i_child]->dof[node_e+i][n0_edge_pr]];
	  }
	  
	  adjust_center_nodesY_2d(dof, nodes, vec, edge_pr_loc, n_e, n_c);
	} /* !PARAM_ALL */
	
	act_proj = wall_proj(el_info, -1);

	if (!ref_edge_untouched &&
	    act_proj && act_proj->func && (!selective || n_proj == act_proj)){
	  for (i = 0; i < n_c; i++) {
	    int local_dof = N_VERTICES_2D+N_EDGES_2D*n_e+i;	    
	    act_proj->func(vec[dof[local_dof]], el_info, cnodes[local_dof]);
	  }
	}
      }
    } /* child loop */
  } /* element loop */

  /* Co-ordinates are already correct when the parent was affine
   * or with strategy == PARAM_STRAIGHT_CHILDS.
   *
   * We have to adjust all edge and center DOFs. Even though some of
   * those DOFs were not projected, they were (potentially)
   * interpolated by the default real_d_refine_inter(). The effects
   * are undone here.
   *
   * We have to do the correction after doing anything else.
   */
  if (ref_edge_untouched && data->strategy == PARAM_CURVED_CHILDS) {

    for (elem = 0; elem < n; elem++) {
      int parent_affine = true;
      
      el_info = &list[elem].el_info;
      el = el_info->el;

      for (j = 0; j < N_EDGES_2D; j++) {
	DOF edge_dof = el->dof[node_e+j][n0_edge_pr];
	
	if (edge_pr->vec[edge_dof]) {
	  parent_affine = false;
	  break;
	}
      }

      if (!parent_affine) {
	for (i_child = 0; i_child < 2; i_child++) {
	  EL *child = el->child[i_child];
	  int child_affine = true;

	  /* The child is untouched iff all edges are untouched. */
	  for (j = 0; j < N_EDGES_2D; j++) {
	    DOF edge_dof = child->dof[node_e+j][n0_edge_pr];
	
	    if (edge_pr->vec[edge_dof]) {
	      child_affine = false;
	      break;
	    }
	  }
	  if (child_affine) {
	    DOF dof[N_BAS_MAX];

	    /* Get the DOF-indices on the child */
	    GET_DOF_INDICES(bas_fcts, child, admin, dof);

	    /* Adjust all new co-ordinates. */
	    if (n_e > 0) {
	      for (i = 0; i < N_EDGES_2D; i++) {
		const int *voe = vertex_of_edge_2d[i];
		REAL *vec0, *vec1;

		vec0 = vec[dof[voe[0]]];
		vec1 = vec[dof[voe[1]]];
		for (j = 0; j < n_e; j++) {
		  int ldof = N_VERTICES_2D + i*n_e + j;
		  
		  AXPBY_DOW(nodes[ldof][voe[0]], vec0,
			    nodes[ldof][voe[1]], vec1,
			    vec[dof[ldof]]);
		}
	      }
	    }
	    for (i = 0; i < n_c; i++) {
	      int ldof = N_VERTICES_2D + N_EDGES_2D*n_e + i;

	      AXEY_DOW(nodes[ldof][0], vec[dof[0]], vec[dof[ldof]]);
	      for (j = 1; j < N_VERTICES_2D; j++) {
		AXPY_DOW(nodes[ldof][j], vec[dof[j]], vec[dof[ldof]]);
	      }
	    }
	  }
	} /* child loop */
      } /* I am not affine */
    } /* element loop */
  } /* child correction necessary */
}

static void coarse_interpolY_2d(DOF_REAL_D_VEC *drdv, RC_LIST_EL *list, int n)
{
  const FE_SPACE      *fe_space = drdv->fe_space;
  const MESH          *mesh = fe_space->mesh;
  const BAS_FCTS      *bas_fcts = fe_space->bas_fcts;
  LAGRANGE_PARAM_DATA *data = (LAGRANGE_PARAM_DATA *)mesh->parametric->data;
  DOF_PTR_VEC         *edge_pr = data->edge_projections;

  /* first call the default interpolation routine for the coordinates */
  bas_fcts->real_d_coarse_inter(drdv, list, n);
  
  /* then take care of edge_projections */
  if (edge_pr) {
    int n0_edge_pr = edge_pr->fe_space->admin->n0_dof[EDGE];
    EL  *el        = list->el_info.el;
    int node_e     = mesh->node[EDGE];
    DOF pdof       = el->dof[node_e+2][n0_edge_pr];
    DOF cdof       = el->child[0]->dof[node_e+0][n0_edge_pr];

    edge_pr->vec[pdof] = edge_pr->vec[cdof];

    /* BIG FAT TODO: center nodes have to be re-adjusted after
     * coarsening
     */
  }

  return;
}

static void fill_coordsY_2d(LAGRANGE_PARAM_DATA *data)
{
  DOF_REAL_D_VEC  *coords   = data->coords;
  DOF_PTR_VEC     *edge_pr  = data->edge_projections;
  NODE_PROJECTION *n_proj   = data->n_proj;
  bool            selective = n_proj != NULL;
  const NODE_PROJECTION *act_proj;
  FLAGS           fill_flag = CALL_LEAF_EL|FILL_COORDS|FILL_PROJECTION;
  MESH            *mesh  = coords->fe_space->mesh;
  const DOF_ADMIN *admin = coords->fe_space->admin;
  const BAS_FCTS  *bas_fcts = coords->fe_space->bas_fcts;
  int             i, j, k, n_e, n_c, node_e = -1, n0_edge_pr = -1;
  const REAL_B    *nodes = LAGRANGE_NODES(bas_fcts);

  n_e    = admin->n_dof[EDGE];
  n_c    = admin->n_dof[CENTER];

  if (edge_pr) {
    node_e = mesh->node[EDGE];
    n0_edge_pr = edge_pr->fe_space->admin->n0_dof[EDGE];
  }
  
  TRAVERSE_FIRST(mesh, -1, fill_flag|FILL_BOUND) {
    DOF   dof[N_BAS_MAX];
    REAL  *vec, *vec0, *vec1;

    GET_DOF_INDICES(bas_fcts, el_info->el, admin, dof);

    for (i = 0; i < N_VERTICES_2D; i++) {
      vec = coords->vec[dof[i]];

      COPY_DOW(el_info->coord[i], vec);

      /* Look for a projection function that applies to vertex[i].
       * Apply this projection if found.
       */
      if (!selective || n_proj->func) {
	act_proj = wall_proj(el_info, (i+1) % N_WALLS_2D);

	if (!act_proj) {
	  act_proj = wall_proj(el_info, (i+2) % N_WALLS_2D);
	}
	if (!act_proj) {
	  act_proj = wall_proj(el_info, -1);
	}

	if (act_proj && act_proj->func && (!selective || act_proj == n_proj)) {
	  act_proj->func(vec, el_info, nodes[i]);
	}
      }
    }

    if (true /* n_e */) {
      for (i = 0; i < N_EDGES_2D; i++) {
	const int *voe = vertex_of_edge_2d[i];

	vec0 = coords->vec[dof[voe[0]]];
	vec1 = coords->vec[dof[voe[1]]];

	act_proj = wall_proj(el_info, i);
	if (!act_proj) {
	  act_proj = wall_proj(el_info, -1);
	}

	if (edge_pr) {
	  DOF edge_dof = el_info->el->dof[node_e+i][n0_edge_pr];
	  edge_pr->vec[edge_dof] =
	    act_proj && (!selective || act_proj == n_proj)
            ? (void *)act_proj : NULL;
	}

	for (j = 0; j < n_e; j++) {
	  int local_dof = N_VERTICES_2D + i*n_e + j;

	  vec = coords->vec[dof[local_dof]];

	  AXPBY_DOW(nodes[local_dof][voe[0]], vec0,
		    nodes[local_dof][voe[1]], vec1,
		    vec);

	  if (act_proj && act_proj->func &&
	      (!selective || act_proj == n_proj)) {
	    act_proj->func(vec, el_info, nodes[local_dof]);
	  }
	}
      }
      
      if (n_c) {
	act_proj = wall_proj(el_info, -1);
	
	for (j = 0; j < n_c; j++) {
	  int local_dof = N_VERTICES_2D + N_EDGES_2D*n_e + j;

	  vec = coords->vec[dof[local_dof]];

	  AXEY_DOW(nodes[local_dof][0], coords->vec[dof[0]], vec);
	  for (k = 1; k < N_VERTICES_2D; k++) {
	    AXPY_DOW(nodes[local_dof][k], coords->vec[dof[k]], vec);
	  }
	}
	
	if (data->strategy != PARAM_ALL) {
	  /* Add a correction which guarantees that the 2nd
	   * derivative of the transformation to the reference element
	   * vanishes fast enough.
	   */
	  NODE_PROJECTION *edge_pr_loc[N_EDGES_2D];
	  
	  for (i = 0; i < N_EDGES_2D; i++) {
	    edge_pr_loc[i] =
	      edge_pr->vec[el_info->el->dof[node_e+i][n0_edge_pr]];
	  }
	  
	  adjust_center_nodesY_2d(dof,
				  nodes, coords->vec, edge_pr_loc, n_e, n_c);
	}
	
	for (j = 0; j < n_c; j++) {
	  int local_dof = N_VERTICES_2D + N_EDGES_2D*n_e + j;

	  vec = coords->vec[dof[local_dof]];

	  if (act_proj && act_proj->func &&
	      (!selective || act_proj == n_proj)) {
	    act_proj->func(vec, el_info, nodes[local_dof]);
	  }
	}	
      }
    }
  } TRAVERSE_NEXT();

  return;
}

#if DIM_MAX > 2
static void
slave_refine_interpolY_2d(DOF_REAL_D_VEC *s_coords, RC_LIST_EL *list, int n)
{
  const FE_SPACE      *fe_space     = s_coords->fe_space;
  MESH                *slave        = fe_space->mesh;
  const BAS_FCTS      *s_bfcts      = fe_space->bas_fcts;
  const DOF_ADMIN     *s_admin      = fe_space->admin;
  PARAMETRIC          *s_parametric = slave->parametric;
  LAGRANGE_PARAM_DATA *s_data       = (LAGRANGE_PARAM_DATA *)s_parametric->data;
  MESH                *master       = get_master(slave);
  DOF_REAL_D_VEC      *m_coords     =
    ((LAGRANGE_PARAM_DATA *)master->parametric->data)->coords;
  const BAS_FCTS      *m_bfcts      = m_coords->fe_space->bas_fcts;
  const DOF_ADMIN     *m_admin      = m_coords->fe_space->admin;
  DOF_PTR_VEC         *s_edge_pr;
  DOF_PTR_VEC         *m_edge_pr = NULL;
  const EL_INFO *el_info;
  EL *m_el, *s_el, *s_child, *m_child;
  int wall, orientation, type;
  int p_wall, p_orientation, p_type;
  int node_e = -1;
  int n_c, n_e;
  int m_node_e = -1;
  int m_n_e = -1;
  int n0_edge_pr = -1, m_n0_edge_pr = -1;
  int s_edge, m_edge, s_dof, m_dof, s_dof_loc, m_dof_loc;
  int elem, i_child, i, m_i_child;
  DOF s_dofs[s_bfcts->n_bas_fcts];
  DOF m_dofs[m_bfcts->n_bas_fcts];
  const int *trace_map;

  if ((s_edge_pr = s_data->edge_projections)) {
    node_e       = slave->node[EDGE];
    m_node_e     = master->node[EDGE];
    n0_edge_pr   = s_edge_pr->fe_space->admin->n0_dof[EDGE];
    m_edge_pr    =
      ((LAGRANGE_PARAM_DATA *)master->parametric->data)->edge_projections;
    m_n0_edge_pr = m_edge_pr->fe_space->admin->n0_dof[EDGE];
    m_n_e        = m_admin->n_dof[EDGE];
  }

  n_e = s_admin->n_dof[EDGE];
  n_c = s_admin->n_dof[CENTER];

  for (elem = 0; elem < n; elem++) {

    el_info = &list[elem].el_info;
    s_el    = el_info->el;

    /* slave -> master binding has not been set up yet, so duplicate
     * the logic from submesh_3d.c here, given the master element,
     * orientation and type
     */
    m_el          = el_info->master.el;
    p_wall        = el_info->master.opp_vertex;
    p_orientation = el_info->master.orientation;
    p_type        = el_info->master.el_type;

    for (i_child = 0; i_child < 2; i_child++) {

      if (p_wall == 2) {
	m_i_child = p_orientation > 0 ? i_child : 1 - i_child;
      } else {
	m_i_child = p_orientation < 0 ? i_child : 1 - i_child;
      }

      s_child = s_el->child[i_child];
      m_child = m_el->child[m_i_child];

      wall        = child_face_3d[p_type][p_wall][m_i_child];
      orientation = p_orientation*child_orientation_3d[p_type][m_i_child];
      type        = (p_type + 1) % 3;
      trace_map = m_bfcts->trace_dof_map[type > 0][orientation < 0][wall];

      GET_DOF_INDICES(s_bfcts, s_child, s_admin, s_dofs);
      GET_DOF_INDICES(m_bfcts, m_child, m_admin, m_dofs);

      if (i_child == 0) {
	/* New vertex */
	s_dof     = s_dofs[2];
	m_dof     = m_dofs[trace_map[2]];
	COPY_DOW(m_coords->vec[m_dof], s_coords->vec[s_dof]);
	
	/* common wall between the two children */
	s_edge    = 1-i_child;
	for (i = 0; i < n_e; i++) {
	  s_dof_loc = N_VERTICES_2D + s_edge*n_e + i;
	  m_dof_loc = trace_map[s_dof_loc];
	  s_dof     = s_dofs[s_dof_loc];
	  m_dof     = m_dofs[m_dof_loc];
	  COPY_DOW(m_coords->vec[m_dof], s_coords->vec[s_dof]);

	  if (s_edge_pr) {
	    s_dof  = s_child->dof[node_e + s_edge][n0_edge_pr];
	    m_edge = (m_dof_loc - N_VERTICES_3D) / m_n_e;
	    m_dof  = m_child->dof[m_node_e + m_edge][m_n0_edge_pr];
	    s_edge_pr->vec[s_dof] = m_edge_pr->vec[m_dof];
	  }
	}
      }

      if (elem == 0) {
	/* wall to neighbour (or boundary) */
	s_edge    = i_child;
	for (i = 0; i < n_e; i++) {
	  s_dof_loc = N_VERTICES_2D + s_edge*n_e + i;
	  m_dof_loc = trace_map[s_dof_loc];
	  s_dof     = s_dofs[s_dof_loc];
	  m_dof     = m_dofs[m_dof_loc];
	  COPY_DOW(m_coords->vec[m_dof], s_coords->vec[s_dof]);

	  if (s_edge_pr) {
	    s_dof  = s_child->dof[node_e + s_edge][n0_edge_pr];
	    m_edge = (m_dof_loc - N_VERTICES_3D) / m_n_e;
	    m_dof  = m_child->dof[m_node_e + m_edge][m_n0_edge_pr];
	    s_edge_pr->vec[s_dof] = m_edge_pr->vec[m_dof];
	  }
	}
      }

      /* interior DOFs (face-dofs on the master) */
      for (i = 0; i < n_c; i++) {
	s_dof_loc = N_VERTICES_2D + N_EDGES_2D*n_e + i;
	m_dof_loc = trace_map[s_dof_loc];
	s_dof     = s_dofs[s_dof_loc];
	m_dof     = m_dofs[m_dof_loc];
	COPY_DOW(m_coords->vec[m_dof], s_coords->vec[s_dof]);
      }
    }
  }
  
  return;
}

static void slave_fill_coordsY_2d(LAGRANGE_PARAM_DATA *s_data)
{
  DOF_REAL_D_VEC      *s_coords     = s_data->coords;
  DOF_PTR_VEC         *s_edge_pr    = s_data->edge_projections;
  MESH                *slave        = s_coords->fe_space->mesh;
  const BAS_FCTS      *s_bfcts      = s_coords->fe_space->bas_fcts;
  const DOF_ADMIN     *s_admin      = s_coords->fe_space->admin;
  MESH                *master       = get_master(slave);
  PARAMETRIC          *m_parametric = master->parametric;
  LAGRANGE_PARAM_DATA *m_data       = (LAGRANGE_PARAM_DATA *)m_parametric->data;
  DOF_REAL_D_VEC      *m_coords     = m_data->coords;
  DOF_PTR_VEC         *m_edge_pr    = m_data->edge_projections;
  const BAS_FCTS      *m_bfcts      = m_coords->fe_space->bas_fcts;
  const DOF_ADMIN     *m_admin      = m_coords->fe_space->admin;
  int                 n0_edge_pr    =
    s_edge_pr ? s_edge_pr->fe_space->admin->n0_dof[EDGE] : -1;
  int                 m_n0_edge_pr  =
    m_edge_pr ? m_edge_pr->fe_space->admin->n0_dof[EDGE] : -1;
  EL *m_el, *s_el;
  int  wall, orientation, type;
  int node_e;
  int m_node_e;
  int m_n_e, n_e;
  int s_edge, m_edge, s_dof, m_dof, s_dof_loc, m_dof_loc, i;
  const int *trace_map;
  DOF m_dofs[m_bfcts->n_bas_fcts];
  DOF s_dofs[s_bfcts->n_bas_fcts];

  n_e      = s_admin->n_dof[EDGE];
  node_e   = slave->node[EDGE];

  m_n_e    = m_admin->n_dof[EDGE];
  m_node_e = master->node[EDGE];

  TRAVERSE_FIRST(slave, -1, CALL_LEAF_EL|FILL_MASTER_INFO) {

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

    trace_map = m_bfcts->trace_dof_map[type > 0][orientation < 0][wall];

    GET_DOF_INDICES(s_bfcts, s_el, s_admin, s_dofs);
    GET_DOF_INDICES(m_bfcts, m_el, m_admin, m_dofs);

    for (i = 0; i < s_data->n_local_coords; i++) {
      COPY_DOW(m_coords->vec[m_dofs[trace_map[i]]], s_coords->vec[s_dofs[i]]);
    }
      
    if (s_edge_pr) {
      for (s_edge = 0; s_edge < N_EDGES_2D; s_edge++) {
	/* first determine the matching edge on the master */
	s_dof_loc = N_VERTICES_2D + s_edge*n_e;
	m_dof_loc = trace_map[s_dof_loc];
	m_edge = (m_dof_loc - N_VERTICES_3D) / m_n_e;
	
	/* Copy over the projeted edges status */
	s_dof = s_el->dof[node_e+s_edge][n0_edge_pr];
	m_dof = m_el->dof[m_node_e+m_edge][m_n0_edge_pr];
	s_edge_pr->vec[s_dof] = m_edge_pr->vec[m_dof];
      }
    }
  } TRAVERSE_NEXT();

  return;
}
#endif

static const PARAMETRIC lagrange_parametric1_2d = {
  "2D Lagrange parametric elements of degree 1",
  false,  /* not_all */
  false,  /* use_reference_mesh */
  param_init_element1_2d,
  vertex_coordsY_2d,
  param_coord_to_world,
  param_world_to_coord,
  det1_2d,
  grd_lambda1_2d,
  NULL,
  wall_normal1_2d,
  NULL       /* data */
};

static const PARAMETRIC lagrange_parametric2_2d = {
  "2D Lagrange parametric elements of degree 2",
  false,  /* not_all */
  false,  /* use_reference_mesh */
  param_init_elementY_2d,
  vertex_coordsY_2d,
  param_coord_to_world,
  param_world_to_coord,
  detY_2d,
  grd_lambdaY_2d,
  grd_worldY_2d,
  wall_normalY_2d,
  NULL       /* data */
};

static const PARAMETRIC lagrange_parametricY_2d = {
  "2D Lagrange parametric elements of generic degree",
  false,  /* not_all */
  false,  /* use_reference_mesh */
  param_init_elementY_2d,
  vertex_coordsY_2d,
  param_coord_to_world,
  param_world_to_coord,
  detY_2d,
  grd_lambdaY_2d,
  grd_worldY_2d,
  wall_normalY_2d,
  NULL       /* data */
};
