/**@file 
 *
 * Raviart-Thomas elements of lowest order.
 *
 *      author:  Claus-Justus Heine
 *               Abteilung fuer Angewandte Mathematik                     
 *               Albert-Ludwigs-Universitaet Freiburg
 *               Hermann-Herder-Str. 10
 *               79104 Freiburg i.Br.
 *               Germany
 *               claus@mathematik.uni-freiburg.de
 *
 * Copyright (C) by Claus-Justus Heine (2008).
 *
 */

#if HAVE_CONFIG_H
# include "config.h"
#endif

#include <alberta.h>

#include "albas.h"

/* Using the existent frame-work Raviart-Thomas (and other
 * vector-valued basis functions which need to be transformed by the
 * Piola-transformation) could be implemented using a _locally_
 * degenerated ansatz-space.
 *
 * Lowest order Raviart-Thomas:
 *
 * \span { x - P_0, x - P_1, x - P_2 }
 *
 * On the reference element:
 *
 * { \lambda - e_0, \lambda - e_1, \lambda - e-2 }
 *
 * Piola-transformation:
 *
 * 1/det (P_0P_1P_2) (\lambda - e_j)
 * =
 * 1/det (\sum \lambda_iP_i - P_j)
 *
 * Example for j=0:
 *
 * \phi_0 = 1/det(\lambda_1 P_1 + \lambda_2 P_2 + (\lambda_0-1) P_0)
 *        = 1/det(\lambda_1 (P_1 - P_0) + \lambda_2 (P_2 - P_0)
 *
 * It would now be possible to break \phi_0 into the two summands
 *
 * \phi_0^1 = 1/det (P_1 - P_0) * \lambda_1
 * \phi_0^2 = 1/det (P_2 - P_0) * \lambda_2
 *
 * The broken-apart basis functions have the structure supported by
 * ALBERTA: a scalar factor which is independent from the geometry of
 * the element times a vector valued factor which depends on the
 * geometry of the element.
 *
 * So in 2d the local ansatz space would look like
 *
 * { \phi_i^1, \phi_i^2 | i = 0, 1, 2 }
 *
 * where the get_dof_indices(), get_real_vec() etc. hooks would have
 * to make sure that \phi_i^1 and \phi_i^2 actually share the same
 * global DOF-index.
 *
 * Another possibility would have been to add a "transformation type"
 * to the basis functions structure. IMHO this would not have been
 * more efficient.
 *
 * Now someone could go ahead and implement this stuff ...
 *
 */

typedef struct rt_data
{
  REAL_D          edges[N_WALLS_MAX][DIM_MAX]; /* scaled and oriented */
  int             orientation[N_WALLS_MAX];    /* the orietation relative to the neighbour */
  const EL_INFO   *cur_el_info;
  const EL        *cur_el;
  const WALL_QUAD *wquad; /* interpolation */
  INIT_EL_TAG_CTX tag_ctx; /**< Tag context for caching purposes. */
} RT_DATA;

/******************************************************************************
 *
 * \phi_0 = 1/det * (  \lambda_1 (P_1 - P_0)
 *                   + \lambda_2 (P_2 - P_0)
 *                   + \lambda_3 (P_3 - P_0))
 *
 */

static REAL phi_0_1(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  return lambda[1];
}

static const REAL *grd_phi_0_1(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  static const REAL_B grd = INIT_BARY_MAX(0.0, 1.0, 0.0, 0.0);

  return grd;
}

static const REAL_B *D2_phi_0_1(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  static const REAL_BB D2;

  return (const REAL_B *)D2;
}

static const REAL *phi_d_0_1(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  RT_DATA *data = (RT_DATA *)bfcts->ext_data;
  
  return data->edges[0][0];
}

#if DIM_MAX > 1
static REAL phi_0_2(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  return lambda[2];
}

static const REAL *grd_phi_0_2(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  static const REAL_B grd = INIT_BARY_MAX(0.0, 0.0, 1.0, 0.0);

  return grd;
}

static const REAL_B *D2_phi_0_2(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  static const REAL_BB D2;

  return (const REAL_B *)D2;
}

static const REAL *phi_d_0_2(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  RT_DATA *data = (RT_DATA *)bfcts->ext_data;
  
  return data->edges[0][1];
}
#endif

#if DIM_MAX > 2
static REAL phi_0_3(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  return lambda[3];
}

static const REAL *grd_phi_0_3(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  static const REAL_B grd = INIT_BARY_MAX(0.0, 0.0, 0.0, 1.0);

  return grd;
}

static const REAL_B *D2_phi_0_3(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  static const REAL_BB D2;

  return (const REAL_B *)D2;
}

static const REAL *phi_d_0_3(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  RT_DATA *data = (RT_DATA *)bfcts->ext_data;
  
  return data->edges[0][2];
}
#endif

/******************************************************************************
 *
 * \phi_1 = 1/det * (  \lambda_0 (P_0 - P_1)
 *                   + \lambda_2 (P_2 - P_1)
 *                   + \lambda_3 (P_3 - P_1))
 *
 */

static REAL phi_1_1(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  return lambda[0];
}

static const REAL *grd_phi_1_1(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  static const REAL_B grd = INIT_BARY_MAX(1.0, 0.0, 0.0, 0.0);

  return grd;
}

static const REAL_B *D2_phi_1_1(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  static const REAL_BB D2;

  return (const REAL_B *)D2;
}

static const REAL *phi_d_1_1(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  RT_DATA *data = (RT_DATA *)bfcts->ext_data;
  
  return data->edges[1][0];
}

#if DIM_MAX > 1
static REAL phi_1_2(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  return lambda[2];
}

static const REAL *grd_phi_1_2(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  static const REAL_B grd = INIT_BARY_MAX(0.0, 0.0, 1.0, 0.0);

  return grd;
}

static const REAL_B *D2_phi_1_2(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  static const REAL_BB D2;

  return (const REAL_B *)D2;
}

static const REAL *phi_d_1_2(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  RT_DATA *data = (RT_DATA *)bfcts->ext_data;
  
  return data->edges[1][1];
}
#endif

#if DIM_MAX > 2
static REAL phi_1_3(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  return lambda[3];
}

static const REAL *grd_phi_1_3(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  static const REAL_B grd = INIT_BARY_MAX(0.0, 0.0, 0.0, 1.0);

  return grd;
}

static const REAL_B *D2_phi_1_3(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  static const REAL_BB D2;

  return (const REAL_B *)D2;
}

static const REAL *phi_d_1_3(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  RT_DATA *data = (RT_DATA *)bfcts->ext_data;
  
  return data->edges[1][2];
}
#endif

#if DIM_MAX > 1
/******************************************************************************
 *
 * \phi_2 = 1/det * (  \lambda_0 (P_0 - P_2)
 *                   + \lambda_1 (P_1 - P_2)
 *                   + \lambda_3 (P_3 - P_2))
 *
 */

static REAL phi_2_1(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  return lambda[0];
}

static const REAL *grd_phi_2_1(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  static const REAL_B grd = INIT_BARY_MAX(1.0, 0.0, 0.0, 0.0);

  return grd;
}

static const REAL_B *D2_phi_2_1(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  static const REAL_BB D2;

  return (const REAL_B *)D2;
}

static const REAL *phi_d_2_1(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  RT_DATA *data = (RT_DATA *)bfcts->ext_data;
  
  return data->edges[2][0];
}

static REAL phi_2_2(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  return lambda[1];
}

static const REAL *grd_phi_2_2(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  static const REAL_B grd = INIT_BARY_MAX(0.0, 1.0, 0.0, 0.0);

  return grd;
}

static const REAL_B *D2_phi_2_2(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  static const REAL_BB D2;

  return (const REAL_B *)D2;
}

static const REAL *phi_d_2_2(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  RT_DATA *data = (RT_DATA *)bfcts->ext_data;
  
  return data->edges[2][1];
}

# if DIM_MAX > 2
static REAL phi_2_3(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  return lambda[3];
}

static const REAL *grd_phi_2_3(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  static const REAL_B grd = INIT_BARY_MAX(0.0, 0.0, 0.0, 1.0);

  return grd;
}

static const REAL_B *D2_phi_2_3(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  static const REAL_BB D2;

  return (const REAL_B *)D2;
}

static const REAL *phi_d_2_3(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  RT_DATA *data = (RT_DATA *)bfcts->ext_data;
  
  return data->edges[2][2];
}
# endif

#endif /* DIM_MAX > 1 */

#if DIM_MAX > 2
/******************************************************************************
 *
 * \phi_3 = 1/det * (  \lambda_0 (P_0 - P_3)
 *                   + \lambda_1 (P_1 - P_3)
 *                   + \lambda_2 (P_2 - P_3))
 *
 */

static REAL phi_3_1(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  return lambda[0];
}

static const REAL *grd_phi_3_1(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  static const REAL_B grd = INIT_BARY_MAX(1.0, 0.0, 0.0, 0.0);

  return grd;
}

static const REAL_B *D2_phi_3_1(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  static const REAL_BB D2;

  return (const REAL_B *)D2;
}

static const REAL *phi_d_3_1(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  RT_DATA *data = (RT_DATA *)bfcts->ext_data;
  
  return data->edges[3][0];
}

static REAL phi_3_2(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  return lambda[1];
}

static const REAL *grd_phi_3_2(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  static const REAL_B grd = INIT_BARY_MAX(0.0, 1.0, 0.0, 0.0);

  return grd;
}

static const REAL_B *D2_phi_3_2(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  static const REAL_BB D2;

  return (const REAL_B *)D2;
}

static const REAL *phi_d_3_2(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  RT_DATA *data = (RT_DATA *)bfcts->ext_data;
  
  return data->edges[3][1];
}

static REAL phi_3_3(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  return lambda[2];
}

static const REAL *grd_phi_3_3(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  static const REAL_B grd = INIT_BARY_MAX(0.0, 0.0, 1.0, 0.0);

  return grd;
}

static const REAL_B *D2_phi_3_3(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  static const REAL_BB D2;

  return (const REAL_B *)D2;
}

static const REAL *phi_d_3_3(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  RT_DATA *data = (RT_DATA *)bfcts->ext_data;
  
  return data->edges[3][2];
}

#endif /* DIM_MAX > 2 */

#undef DEF_EL_VEC_RT
#define DEF_EL_VEC_RT(type, name)					\
  DEF_EL_VEC_CONST(type, name, DIM_MAX*N_WALLS_MAX, DIM_MAX*N_WALLS_MAX)

#undef DEFUN_GET_EL_VEC_RT
#define DEFUN_GET_EL_VEC_RT(dim, name, type, admin, body, ...)		\
  static const EL_##type##_VEC *					\
  rt_get_##name(type##_VEC_TYPE *vec, const EL *el, __VA_ARGS__)	\
  {									\
    FUNCNAME("wall_bubble_get_"#name);					\
    static DEF_EL_VEC_RT(type, rvec_space);				\
    type##_VEC_TYPE *rvec = vec ? vec : rvec_space->vec;		\
    int n0, node, wall, node_num, ibas, i;				\
    DOF **dofptr = el->dof, dof;					\
									\
    DEBUG_TEST_EXIT(true, "");						\
									\
    switch (dim) {							\
    case 0: node_num = VERTEX; break;					\
    case 1: node_num = VERTEX; break;					\
    case 2: node_num = EDGE; break;					\
    case 3: node_num = FACE; break;					\
    default:								\
      node_num = -1;							\
      ERROR_EXIT("Unsupport dimension: %d\n", dim);			\
      break;								\
    }									\
    node = (admin)->mesh->node[node_num];				\
    n0   = (admin)->n0_dof[node_num];					\
    for (ibas = wall = 0; wall < N_WALLS(dim); wall++) {		\
      dof = dofptr[node+wall][n0];					\
      for (i = 0; i < dim; i++) {					\
	body; ibas++;							\
      }									\
    }									\
									\
    if (vec) {								\
      return NULL;							\
    } else {								\
      rvec_space->n_components = dim*N_WALLS(dim);			\
      return rvec_space;						\
    }									\
  }									\
  struct _AI_semicolon_dummy

#undef DEFUN_GET_EL_DOF_VEC_RT
#define DEFUN_GET_EL_DOF_VEC_RT(name, type, ASSIGN)			\
  DEFUN_GET_EL_VEC_RT(dv->fe_space->admin->mesh->dim,			\
		      _##name##_vec, type, dv->fe_space->admin,		\
		      ASSIGN(dv->vec[dof], rvec[ibas]),			\
		      const DOF_##type##_VEC *dv);			\
  static const EL_##type##_VEC *					\
  rt_get_##name##_vec(type##_VEC_TYPE *vec, const EL *el,		\
		      const DOF_##type##_VEC *dv)			\
  {									\
    EL_##type##_VEC *vec_loc = dv->vec_loc;				\
    									\
    if (vec != NULL || vec_loc == NULL) {				\
      return rt_get__##name##_vec(vec, el, dv);				\
    } else {								\
      rt_get__##name##_vec(vec_loc->vec, el, dv);			\
      return vec_loc;							\
    }									\
  }									\
  struct _AI_semicolon_dummy
  

#undef COPY_EQ
#define COPY_EQ(a, b) (b) = (a)

/*******************************************************************************
 *  functions for combining basisfunctions with coefficients
 ******************************************************************************/

DEFUN_GET_EL_VEC_RT(thisptr->dim, dof_indices, DOF, admin,
		    rvec[ibas] = dof,
		    const DOF_ADMIN *admin, const BAS_FCTS *thisptr);

static const EL_BNDRY_VEC *
rt_get_bound_1d(BNDRY_FLAGS *vec,
		const EL_INFO *el_info, const BAS_FCTS *thisptr)
{
  static DEF_EL_VEC_RT(BNDRY, rvec_space);
  BNDRY_FLAGS *rvec = vec ? vec : rvec_space->vec;
  int w;

  for (w = 0; w < N_WALLS_1D; w++) {
    BNDRY_FLAGS_CPY(rvec[2*w], el_info->vertex_bound[1-w]);
    BNDRY_FLAGS_CPY(rvec[2*w+1], el_info->vertex_bound[1-w]);
  }

  return vec ? NULL : rvec_space;
}

#if DIM_MAX > 1
static const EL_BNDRY_VEC *
rt_get_bound_2d(BNDRY_FLAGS *vec,
		const EL_INFO *el_info, const BAS_FCTS *thisptr)
{
  static DEF_EL_VEC_RT(BNDRY, rvec_space);
  BNDRY_FLAGS *rvec = vec ? vec : rvec_space->vec;
  int w;

  for (w = 0; w < N_WALLS_2D; w++) {
    BNDRY_FLAGS_CPY(rvec[2*w], el_info->edge_bound[w]);
    BNDRY_FLAGS_CPY(rvec[2*w+1], el_info->edge_bound[w]);
  }

  return vec ? NULL : rvec_space;
}
#endif

#if DIM_MAX > 2
static const EL_BNDRY_VEC *
rt_get_bound_3d(BNDRY_FLAGS *vec,
		const EL_INFO *el_info, const BAS_FCTS *thisptr)
{
  static DEF_EL_VEC_RT(BNDRY, rvec_space);
  BNDRY_FLAGS *rvec = vec ? vec : rvec_space->vec;
  int w;

  for (w = 0; w < N_WALLS_3D; w++) {
    BNDRY_FLAGS_INIT(rvec[2*w]);
    BNDRY_FLAGS_SET(rvec[2*w], el_info->face_bound[w]);
    BNDRY_FLAGS_INIT(rvec[2*w+1]);
    BNDRY_FLAGS_SET(rvec[2*w+1], el_info->face_bound[w]);
  }

  return vec ? NULL : rvec_space;
}
#endif

/*******************************************************************************
 * function for accessing a local DOF_INT_VEC
 ******************************************************************************/

DEFUN_GET_EL_DOF_VEC_RT(int, INT, COPY_EQ);

/*******************************************************************************
 * function for accessing a local DOF_REAL_VEC            
 ******************************************************************************/

DEFUN_GET_EL_DOF_VEC_RT(real, REAL, COPY_EQ);

/*******************************************************************************
 * function for accessing a local DOF_REAL_D_VEC          
 ******************************************************************************/

DEFUN_GET_EL_DOF_VEC_RT(real_d, REAL_D, COPY_DOW);

/*******************************************************************************
 * function for accessing a local DOF_REAL_VEC_D
 ******************************************************************************/

static const EL_REAL_VEC_D *
rt_get_real_vec_d(REAL result[],
			    const EL *el, const DOF_REAL_VEC_D *dv)
{
  return (const EL_REAL_VEC_D *)
    rt_get_real_vec(result, el, (const DOF_REAL_VEC *)dv);
}

/*******************************************************************************
 * function for accessing a local DOF_SCHAR_VEC           
 ******************************************************************************/

DEFUN_GET_EL_DOF_VEC_RT(schar, SCHAR, COPY_EQ);

/*******************************************************************************
 * function for accessing a local DOF_UCHAR_VEC           
 ******************************************************************************/

DEFUN_GET_EL_DOF_VEC_RT(uchar, UCHAR, COPY_EQ);

/*******************************************************************************
 * function for accessing a local DOF_PTR_VEC             
 ******************************************************************************/

DEFUN_GET_EL_DOF_VEC_RT(ptr, PTR, COPY_EQ);

/*******************************************************************************
 * function for accessing a local DOF_REAL_DD_VEC             
 ******************************************************************************/

#define MYMCOPY(a, b) MCOPY_DOW((const REAL_D *)(a), (b))
DEFUN_GET_EL_DOF_VEC_RT(real_dd, REAL_DD, MYMCOPY);

/*******************************************************************************
 * functions for interpolation:
 ******************************************************************************/

static REAL rt_wall_interpol(RT_DATA *data,
			     const EL_INFO *el_info, int w,
			     LOC_FCT_D_AT_QP f, void *f_data)
{
  const QUAD *quad;
  REAL val;
  REAL_D fval;
  int iq;
  const EL_GEOM_CACHE *elgc =
    fill_el_geom_cache(el_info, FILL_EL_WALL_NORMAL(w)|FILL_EL_WALL_DET(w));

  quad = &data->wquad->quad[w];
  val = 0.0;
  for (iq = 0; iq < quad->n_points; iq++) {
    val +=
      quad->w[iq]
      *
      SCP_DOW(f(fval, el_info, quad, iq, f_data), elgc->wall_normal[w]);
  }
  val *= elgc->wall_det[w];
  return data->orientation[w] < 0 ? -val : val;
}

static void rt_interpol_dow(EL_REAL_VEC_D *vec,
			    const EL_INFO *el_info, int wall,
			    int no, const int *b_no,
			    LOC_FCT_D_AT_QP f, void *f_data,
			    const BAS_FCTS *thisptr)
{
  RT_DATA *data = (RT_DATA *)thisptr->ext_data;
  REAL val;
  int w, i, j, dim = thisptr->dim;
  
  if (wall != -1) {
    val = rt_wall_interpol(data, el_info, wall, f, f_data);
    for (i = 0; i < N_VERTICES(dim-1); ++i) {
      vec->vec[wall*N_VERTICES(dim-1) + i] = val;
    }
  } else {
    if (b_no == NULL) {
      for (w = 0; w < N_WALLS(thisptr->dim); w++) {
	val = rt_wall_interpol(data, el_info, w, f, f_data);
	for (i = 0; i < N_VERTICES(dim-1); i++) {
	  vec->vec[w*N_VERTICES(dim-1) + i] = val;
	}
      }
    } else {
      for (j = 0; j < no; j++) {
	w = b_no[j]/N_VERTICES(dim-1);
	val = rt_wall_interpol(data, el_info, w, f, f_data);
	for (i = 0; i < N_VERTICES(dim-1); i++) {
	  vec->vec[w*N_VERTICES(dim-1) + i] = val;
	}
      }
    }
  }
}

static void rt_real_refine_inter_d(DOF_REAL_VEC_D *drv,
					     RC_LIST_EL *rclist,
					     int n)
{
  const BAS_FCTS *self = drv->fe_space->bas_fcts;
  int node, n0, ichild;
  DOF cdof, pdof;
  EL *el;

  switch (self->dim) {
  case 1:
    node = drv->fe_space->admin->mesh->node[VERTEX];
    n0   = drv->fe_space->admin->n0_dof[VERTEX];
    el = rclist->el_info.el;
      
    for (ichild = 0; ichild < 2; ichild++) {
      pdof = el->dof[node+ichild][n0];
      cdof = el->child[ichild]->dof[node+ichild][n0];
      drv->vec[cdof] = drv->vec[pdof];
      cdof = el->child[ichild]->dof[node+1-ichild][n0];
      drv->vec[cdof] = 0.0;
    }
    break;
#if DIM_MAX > 1
  case 2: {
    REAL value;

    node = drv->fe_space->admin->mesh->node[EDGE];
    n0   = drv->fe_space->admin->n0_dof[EDGE];

    el = rclist->el_info.el;
    cdof = el->child[0]->dof[node+1][n0];
    drv->vec[cdof] = 0.0; /* interior edge */
    pdof = el->dof[node+2][n0];
    value = 0.5 * drv->vec[pdof];
    for (ichild = 0; ichild < 2; ichild++) {
      cdof = el->child[ichild]->dof[node+ichild][n0];
      drv->vec[cdof] = value;
    }
    if (n > 1) {
      el = rclist[1].el_info.el;
      cdof = el->child[0]->dof[node+1][n0];
      drv->vec[cdof] = 0.0; /* interior edge */
    }
    break;
  }
#endif
#if DIM_MAX > 2
  case 3: {
    REAL value;
    int i, j;
    
    node = drv->fe_space->admin->mesh->node[FACE];
    n0   = drv->fe_space->admin->n0_dof[FACE];
    for (i = 0; i < n; i++) {
      el = rclist[i].el_info.el;

      /* Interior wall */
      cdof = el->child[0]->dof[node+0][n0];
      drv->vec[cdof] = 0.0;
      /* all types, child 0:
       * wall 1 of child is on wall 2 of parent
       * wall 2 of child is on wall 3 of parent
       *
       * type 0, child 1:
       * wall 1 of child is on wall 3 of parent
       * wall 2 of child is on wall 2 of parent
       *
       * type 1, 2, child 1:
       * wall 1 of child is on wall 2 of parent
       * wall 2 of child is on wall 3 of parent
       */
      for (j = 2; j < 4; j++) {
	pdof = el->dof[node+j][n0];
	value = 0.5 * drv->vec[pdof];
	cdof = el->child[0]->dof[node+j-1][n0];
	drv->vec[cdof] = value;
	if (rclist[i].el_info.el_type > 0) {
	  cdof = el->child[1]->dof[node+j-1][n0];
	  drv->vec[cdof] = value;
	} else {
	  cdof = el->child[1]->dof[node+3-j+1][n0];
	  drv->vec[cdof] = value;
	}
      }
    }
    break;
  }
#endif
  default:
    break;
  }
}

static void rt_real_coarse_inter_d(DOF_REAL_VEC_D *drv,
					     RC_LIST_EL *rclist,
					     int n)
{
  const BAS_FCTS *self = drv->fe_space->bas_fcts;
  int node, n0, ichild;
  DOF cdof, pdof;
  EL *el;

  switch (self->dim) {
  case 1:
    node = drv->fe_space->admin->mesh->node[VERTEX];
    n0   = drv->fe_space->admin->n0_dof[VERTEX];
    el = rclist->el_info.el;
      
    for (ichild = 0; ichild < 2; ichild++) {
      pdof = el->dof[node+ichild][n0];
      cdof = el->child[ichild]->dof[node+ichild][n0];
      drv->vec[pdof] = drv->vec[cdof];
    }
    break;
#if DIM_MAX > 1
  case 2: {
    REAL value;

    node = drv->fe_space->admin->mesh->node[EDGE];
    n0   = drv->fe_space->admin->n0_dof[EDGE];

    el = rclist->el_info.el;
    pdof = el->dof[node+2][n0];
    value = 0.0;
    for (ichild = 0; ichild < 2; ichild++) {
      cdof = el->child[ichild]->dof[node+ichild][n0];
      value += drv->vec[cdof];
    }
    drv->vec[pdof] = value;
    break;
  }
#endif
#if DIM_MAX > 2
  case 3: {
    REAL value;
    int i, j;
    
    node = drv->fe_space->admin->mesh->node[FACE];
    n0   = drv->fe_space->admin->n0_dof[FACE];
    for (i = 0; i < n; i++) {
      el = rclist[i].el_info.el;
      /* all types, child 0:
       * wall 1 of child is on wall 2 of parent
       * wall 2 of child is on wall 3 of parent
       *
       * type 0, child 1:
       * wall 1 of child is on wall 3 of parent
       * wall 2 of child is on wall 2 of parent
       *
       * type 1, 2, child 1:
       * wall 1 of child is on wall 2 of parent
       * wall 2 of child is on wall 3 of parent
       */
      for (j = 2; j < 4; j++) {
	pdof = el->dof[node+j][n0];
	value = 0.0;
	cdof = el->child[0]->dof[node+j-1][n0];
	value += drv->vec[cdof];
	if (rclist[i].el_info.el_type > 0) {
	  cdof = el->child[1]->dof[node+j-1][n0];
	  value += drv->vec[cdof];
	} else {
	  cdof = el->child[1]->dof[node+3-j+1][n0];
	  value += drv->vec[cdof];
	}
	drv->vec[pdof] = value;
      }
    }
    break;
  }
#endif
  default:
    break;
  }
}

static void rt_real_coarse_restr_d(DOF_REAL_VEC_D *drv,
					     RC_LIST_EL *rclist,
					     int n)
{
  FUNCNAME("rt_real_coarse_restr_d");

  WARNING("Not implemented.\n");
}

/*******************************************************************************
 * per-element initialization:
 ******************************************************************************/

static
INIT_EL_TAG rt_init_element(const EL_INFO *el_info, void *thisptr)
{
  FUNCNAME("rt_init_element");
  BAS_FCTS *self = (BAS_FCTS *)thisptr;
  RT_DATA *data = (RT_DATA *)self->ext_data;
  MESH *mesh;
  REAL factor;
  int w, dim, e;
  const EL_GEOM_CACHE *elgc;
  
  if (el_info == NULL) {
    self->dir_pw_const = true;
    data->cur_el = NULL;
    INIT_EL_TAG_CTX_DFLT(&data->tag_ctx);
    return INIT_EL_TAG_CTX_TAG(&data->tag_ctx);
  }
  if (data->cur_el == el_info->el && data->cur_el_info == el_info) {
    return INIT_EL_TAG_CTX_TAG(&data->tag_ctx);    
  }
  data->cur_el      = el_info->el;
  data->cur_el_info = el_info;

  TEST_EXIT(el_info->fill_flag & FILL_NEIGH,
	    "Sorry, currently this implementation of wall-bubbles needs the "
	    "fill-flag FILL_NEIGH all the time.\n");

  mesh = el_info->mesh;
  dim = mesh->dim;

  elgc = fill_el_geom_cache(el_info, FILL_EL_DET);

  if (mesh->parametric) {
    ERROR_EXIT("Not yet implemented for parametric meshes.\n");
  }

  factor = 1.0 / elgc->det;  
  for (w = 0; w < N_WALLS(dim); w++) {
    const EL *neigh;
    REAL signedfactor = factor;
    data->orientation[w] = 1;

    if ((neigh = el_info->neigh[w])) {
      int ov          = el_info->opp_vertex[w];
      
      if (el_info->el->dof[w][VERTEX] > neigh->dof[ov][VERTEX]) {
	signedfactor = -signedfactor;
        data->orientation[w] = -1;
      }
    }

    for (e = 0; e < N_VERTICES(dim-1); e++) {
      /* vertex_of_wall_3d reflects the proper ordering, this is NO typo. */
      AXPBY_DOW(1.0, el_info->coord[VERTEX_OF_WALL_3D(w)[e]],
		-1.0, el_info->coord[w],
		data->edges[w][e]);
      SCAL_DOW(signedfactor, data->edges[w][e]);
    }
  }

  return INIT_EL_TAG_CTX_TAG(&data->tag_ctx);
}

/*******************************************************************************
 * constructor for the beast:
 ******************************************************************************/

/* Still missing: we need to define the trace space. The trace space on
 * a given wall is the direct sum of a lower-dimensional RT space +
 * the basis function with non-vanishing normal component on that wall.
 */

static const BAS_FCT rt_phi[DIM_MAX+1][N_WALLS_MAX*DIM_MAX] = {
  { NULL, /* 0 dim */ },
#if DIM_MAX >= 1
  { phi_0_1,
    phi_1_1 },
#endif
#if DIM_MAX >= 2
  { phi_0_1, phi_0_2,
    phi_1_1, phi_1_2,
    phi_2_1, phi_2_2 },
#endif
#if DIM_MAX >= 3
  { phi_0_1, phi_0_2, phi_0_3,
    phi_1_1, phi_1_2, phi_1_3,
    phi_2_1, phi_2_2, phi_2_3,
    phi_3_1, phi_3_2, phi_3_3 },
#endif
};

static const GRD_BAS_FCT rt_grd_phi[DIM_MAX+1][N_WALLS_MAX*DIM_MAX] = {
  { NULL, /* 0 dim */ },
#if DIM_MAX >= 1
  { grd_phi_0_1,
    grd_phi_1_1, },
#endif
#if DIM_MAX >= 2
  { grd_phi_0_1, grd_phi_0_2,
    grd_phi_1_1, grd_phi_1_2,
    grd_phi_2_1, grd_phi_2_2 },
#endif
#if DIM_MAX >= 3
  { grd_phi_0_1, grd_phi_0_2, grd_phi_0_3,
    grd_phi_1_1, grd_phi_1_2, grd_phi_1_3,
    grd_phi_2_1, grd_phi_2_2, grd_phi_2_3,
    grd_phi_3_1, grd_phi_3_2, grd_phi_3_3 },
#endif
};

static const D2_BAS_FCT rt_D2_phi[DIM_MAX+1][N_WALLS_MAX*DIM_MAX] = {
  { NULL, /* 0 dim */ },
#if DIM_MAX >= 1
  { D2_phi_0_1,
    D2_phi_1_1, },
#endif
#if DIM_MAX >= 2
  { D2_phi_0_1, D2_phi_0_2,
    D2_phi_1_1, D2_phi_1_2,
    D2_phi_2_1, D2_phi_2_2 },
#endif
#if DIM_MAX >= 3
  { D2_phi_0_1, D2_phi_0_2, D2_phi_0_3,
    D2_phi_1_1, D2_phi_1_2, D2_phi_1_3,
    D2_phi_2_1, D2_phi_2_2, D2_phi_2_3,
    D2_phi_3_1, D2_phi_3_2, D2_phi_3_3 },
#endif
};

static const BAS_FCT_D rt_phi_d[DIM_MAX+1][N_WALLS_MAX*DIM_MAX] = {
  { NULL, /* 0 dim */ },
#if DIM_MAX >= 1
  { phi_d_0_1,
    phi_d_1_1, },
#endif
#if DIM_MAX >= 2
  { phi_d_0_1, phi_d_0_2,
    phi_d_1_1, phi_d_1_2,
    phi_d_2_1, phi_d_2_2 },
#endif
#if DIM_MAX >= 3
  { phi_d_0_1, phi_d_0_2, phi_d_0_3,
    phi_d_1_1, phi_d_1_2, phi_d_1_3,
    phi_d_2_1, phi_d_2_2, phi_d_2_3,
    phi_d_3_1, phi_d_3_2, phi_d_3_3 },
#endif
};

static const int
trace_mapping_rt[DIM_MAX+1][N_WALLS_MAX][N_WALLS_MAX*DIM_MAX] = {
#if DIM_MAX > 0
  [1] = { { 1 }, { 0 } },
#endif
#if DIM_MAX > 1
  [2] = { { 0, 1 },
          { 3, 2 },
          { 4, 5 } },
#endif
#if DIM_MAX > 2
  [3] = { { 0,  1,  2 },
	  { 3,  4,  5 },
	  { 6,  7,  8 },
	  { 9, 10, 11 } }
#endif
};

#define MAX_RT_INTER_DEG 20

const BAS_FCTS *get_raviart_thomas(unsigned int dim, unsigned int inter_deg)
{
  FUNCNAME("get_raviart_thomas");
  static BAS_FCTS *rt_bfcts[DIM_MAX+1][MAX_RT_INTER_DEG+1];
  RT_DATA *data;

  TEST_EXIT(dim <= DIM_MAX, "dim = %d > DIM_MAX = %d.\n", dim, DIM_MAX);
 
  if (inter_deg > MAX_RT_INTER_DEG) {
    WARNING("Truncating quad-degree from %d to %d.\n",
	    inter_deg, MAX_RT_INTER_DEG);
    inter_deg = MAX_RT_INTER_DEG;
  }
  
  if (rt_bfcts[inter_deg][dim] == NULL) {
    char name[sizeof("RaviartThomas_IXX_Xd")];

    sprintf(name, "RaviartThomas_I%02d_%dd", inter_deg, dim);

    rt_bfcts[dim][inter_deg] = MEM_CALLOC(1, BAS_FCTS);
    rt_bfcts[dim][inter_deg]->name   = strdup(name);
    rt_bfcts[dim][inter_deg]->dim    = dim;
    rt_bfcts[dim][inter_deg]->rdim   = DIM_OF_WORLD;
    rt_bfcts[dim][inter_deg]->degree = 1;
    rt_bfcts[dim][inter_deg]->n_bas_fcts =
      rt_bfcts[dim][inter_deg]->n_bas_fcts_max = N_WALLS(dim)*dim;
    switch (dim) {
    case 1: rt_bfcts[dim][inter_deg]->n_dof[VERTEX] = 1; break;
    case 2: rt_bfcts[dim][inter_deg]->n_dof[EDGE] = 1; break;
    case 3: rt_bfcts[dim][inter_deg]->n_dof[FACE] = 1; break;
    }
    rt_bfcts[dim][inter_deg]->trace_admin = -1;
    CHAIN_INIT(rt_bfcts[dim][inter_deg]);
    rt_bfcts[dim][inter_deg]->unchained = rt_bfcts[dim][inter_deg];
    rt_bfcts[dim][inter_deg]->phi     = rt_phi[dim];
    rt_bfcts[dim][inter_deg]->grd_phi = rt_grd_phi[dim];
    rt_bfcts[dim][inter_deg]->D2_phi  = rt_D2_phi[dim];
    rt_bfcts[dim][inter_deg]->phi_d   = rt_phi_d[dim];
    if (dim > 0) {
      int o, t, w;
      rt_bfcts[dim][inter_deg]->trace_bas_fcts = get_null_bfcts(dim);
      for (w = 0; w < N_WALLS(dim); w++) {
	rt_bfcts[dim][inter_deg]->n_trace_bas_fcts[w] = dim;
	for (t = 0; t < 2; t++) {
	  for (o = 0; o < 2; o++) {
	    rt_bfcts[dim][inter_deg]->trace_dof_map[t][o][w] =
	      trace_mapping_rt[dim][w];
	  }
	}
      }
    } else {
      rt_bfcts[dim][inter_deg]->trace_bas_fcts = get_null_bfcts(0);
    }

    rt_bfcts[dim][inter_deg]->get_dof_indices = rt_get_dof_indices;
    switch (dim) {
    case 1:
      rt_bfcts[dim][inter_deg]->get_bound = rt_get_bound_1d;
      break;
#if DIM_MAX > 1
    case 2:
      rt_bfcts[dim][inter_deg]->get_bound = rt_get_bound_2d;
      break;
#endif
#if DIM_MAX > 2
    case 3:
      rt_bfcts[dim][inter_deg]->get_bound = rt_get_bound_3d;
      break;
#endif
    }
    rt_bfcts[dim][inter_deg]->interpol = NULL;
    rt_bfcts[dim][inter_deg]->interpol_d = NULL;
    rt_bfcts[dim][inter_deg]->interpol_dow = rt_interpol_dow;
    rt_bfcts[dim][inter_deg]->dir_pw_const = true;

    rt_bfcts[dim][inter_deg]->interpol_dow = rt_interpol_dow;
    rt_bfcts[dim][inter_deg]->get_int_vec = rt_get_int_vec;
    rt_bfcts[dim][inter_deg]->get_real_vec = rt_get_real_vec;
    rt_bfcts[dim][inter_deg]->get_real_d_vec = rt_get_real_d_vec;
    rt_bfcts[dim][inter_deg]->get_real_dd_vec = rt_get_real_dd_vec;
    rt_bfcts[dim][inter_deg]->get_real_vec_d = rt_get_real_vec_d;
    rt_bfcts[dim][inter_deg]->get_uchar_vec = rt_get_uchar_vec;
    rt_bfcts[dim][inter_deg]->get_schar_vec = rt_get_schar_vec;
    rt_bfcts[dim][inter_deg]->get_ptr_vec = rt_get_ptr_vec;

    rt_bfcts[dim][inter_deg]->real_refine_inter =
      (void  (*)(DOF_REAL_VEC *, RC_LIST_EL *, int))
      rt_real_refine_inter_d;
    rt_bfcts[dim][inter_deg]->real_coarse_inter =
      (void  (*)(DOF_REAL_VEC *, RC_LIST_EL *, int))
      rt_real_coarse_inter_d;
    rt_bfcts[dim][inter_deg]->real_coarse_restr =
      (void  (*)(DOF_REAL_VEC *, RC_LIST_EL *, int))
      rt_real_coarse_restr_d;

    rt_bfcts[dim][inter_deg]->real_refine_inter_d =
      rt_real_refine_inter_d;
    rt_bfcts[dim][inter_deg]->real_coarse_inter_d =
      rt_real_coarse_inter_d;
    rt_bfcts[dim][inter_deg]->real_coarse_restr_d =
      rt_real_coarse_restr_d;

    rt_bfcts[dim][inter_deg]->ext_data = data = MEM_CALLOC(1, RT_DATA);

    data->wquad = get_wall_quad(dim, inter_deg);

    INIT_ELEMENT_DEFUN(rt_bfcts[dim][inter_deg], rt_init_element,
		       FILL_NEIGH|FILL_COORDS);
    INIT_OBJECT(rt_bfcts[dim][inter_deg]);
  }
  
  return rt_bfcts[dim][inter_deg];
}

