/**@file 
 *
 * Tensor-product face-bubbles (tensor product with the trace-space of
 * some standard Lagrange space.
 *
 *      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-2009).
 *
 */

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

#include <alberta.h>

#include "alberta_intern.h"
#include "albas.h"

#define WEIGHT_1D 1.0
#define WEIGHT_2D 6.0
#define WEIGHT_3D 120.0

#define N_TWB_MAX (N_WALLS_MAX*N_VERTICES(DIM_MAX-1))

/* Mass-matrices and inverse on the reference element, used to define
 * the interpolation operator.
 *
 * We define the interpolation by an L2-projection w.r.t. to the
 * Lagrange-factor of the tensor space, where the mass-matrix is
 * weighted by the bubble. The effect is that the flux of the
 * interpolant across the boundary of a mesh element is the same as
 * the flux of the interpolated function across the boundary.
 */
#if 0
static
REAL mass[DIM_MAX+1][N_VERTICES(DIM_MAX-1)][N_VERTICES(DIM_MAX-1)] = {
  { { HUGE_VAL, }, },
  { { 1.0, } },
# if DIM_MAX > 1
  { { 3.0 / 10.0, 1.0 / 5.0, },
    { 1.0 / 5.0, 3.0 / 10.0, }, },
# endif
# if DIM_MAX > 2
  { { 1.0 / 7.0, 2.0 / 21.0, 2.0 / 21.0 },
    { 2.0 / 21.0, 1.0 / 7.0, 2.0 / 21.0 },
    { 2.0 / 21.0, 2.0 / 21.0, 1.0 / 7.0 } },
# endif
};
#endif
static
REAL mass_inv[DIM_MAX+1][N_VERTICES(DIM_MAX-1)][N_VERTICES(DIM_MAX-1)] = {
  { { HUGE_VAL, }, },
  { { 1.0, }, },
#if DIM_MAX > 1
  { { 6.0, -4.0, },
    { -4.0, 6.0, }, },
#endif
#if DIM_MAX > 2
  { { 15.0, -6.0, -6.0 },
    { -6.0, 15.0, -6.0 },
    { -6.0, -6.0, 15.0 }, },
#endif 
};

typedef struct tensor_wall_bubbles_data
{
  const EL             *cur_el;
  const EL_INFO        *cur_el_info;
  REAL_D               wall_normals[N_WALLS_MAX];
  const WALL_QUAD      *wquad;
  const WALL_QUAD_FAST *wqfast;
  int                  tensor_deg;
  int                  inter_deg;
} TWB_DATA;

/* scalar factors */

#if DIM_OF_WORLD >= 1
/* <<< 1d */

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

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

static const REAL *twb_grd_phi_w0_1d(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  static const REAL_B grd = { 0.0, WEIGHT_1D, };
  return grd;
}

static const REAL *twb_grd_phi_w1_1d(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  static const REAL_B grd = { WEIGHT_1D, };
  return grd;
}

static const REAL_B *twb_D2_phi_w0_1d(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  static const REAL_BB D2;
  return D2;
}

static const REAL_B *twb_D2_phi_w1_1d(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  static const REAL_BB D2;
  return D2;
}

/* >>> */
#endif

#if DIM_OF_WORLD >= 2
/* <<< 2d, P1 */

/* <<< values */

static REAL twb_P1_phi_0_w0_2d(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  return WEIGHT_2D*SQR(lambda[1])*lambda[2];
}

static REAL twb_P1_phi_1_w0_2d(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  return WEIGHT_2D*lambda[1]*SQR(lambda[2]);
}

static REAL twb_P1_phi_0_w1_2d(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  return WEIGHT_2D*lambda[0]*SQR(lambda[2]);
}

static REAL twb_P1_phi_1_w1_2d(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  return WEIGHT_2D*SQR(lambda[0])*lambda[2];
}

static REAL twb_P1_phi_0_w2_2d(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  return WEIGHT_2D*SQR(lambda[0])*lambda[1];
}

static REAL twb_P1_phi_1_w2_2d(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  return WEIGHT_2D*lambda[0]*SQR(lambda[1]);
}

/* >>> */

/* <<< gradients */

static
const REAL *twb_P1_grd_phi_0_w0_2d(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  static REAL_B grd;
  
  grd[1] = 2.0*WEIGHT_2D*lambda[1]*lambda[2];
  grd[2] = WEIGHT_2D*SQR(lambda[1]);

  return grd;
}

static
const REAL *twb_P1_grd_phi_1_w0_2d(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  static REAL_B grd;
  
  grd[1] = WEIGHT_2D*SQR(lambda[2]);
  grd[2] = 2.0*WEIGHT_2D*lambda[1]*lambda[2];

  return grd;
}

static
const REAL *twb_P1_grd_phi_0_w1_2d(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  static REAL_B grd;
  
  grd[2] = 2.0*WEIGHT_2D*lambda[0]*lambda[2];
  grd[0] = WEIGHT_2D*SQR(lambda[2]);

  return grd;
}

static
const REAL *twb_P1_grd_phi_1_w1_2d(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  static REAL_B grd;
  
  grd[0] = 2.0*WEIGHT_2D*lambda[2]*lambda[0];
  grd[2] = WEIGHT_2D*SQR(lambda[0]);

  return grd;
}

static
const REAL *twb_P1_grd_phi_0_w2_2d(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  static REAL_B grd;
  
  grd[0] = 2.0*WEIGHT_2D*lambda[0]*lambda[1];
  grd[1] = WEIGHT_2D*SQR(lambda[0]);

  return grd;
}

static
const REAL *twb_P1_grd_phi_1_w2_2d(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  static REAL_B grd;
  
  grd[0] = WEIGHT_2D*SQR(lambda[1]);
  grd[1] = 2.0*WEIGHT_2D*lambda[0]*lambda[1];

  return grd;
}

/* >>> */

/* <<< hessians */

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

  D2[1][1] = 2.0*WEIGHT_2D*lambda[2];
  D2[1][2] = D2[2][1] = 2.0*WEIGHT_2D*lambda[1];

  return (const REAL_B *)D2;
}

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

  D2[2][2] = 2.0*WEIGHT_2D*lambda[1];
  D2[1][2] = D2[2][1] = 2.0*WEIGHT_2D*lambda[2];

  return (const REAL_B *)D2;
}

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

  D2[2][2] = 2.0*WEIGHT_2D*lambda[0];
  D2[2][0] = D2[0][2] = 2.0*WEIGHT_2D*lambda[2];

  return (const REAL_B *)D2;
}

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

  D2[0][0] = 2.0*WEIGHT_2D*lambda[2];
  D2[2][0] = D2[0][2] = 2.0*WEIGHT_2D*lambda[0];

  return (const REAL_B *)D2;
}

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

  D2[0][0] = 2.0*WEIGHT_2D*lambda[1];
  D2[0][1] = D2[1][0] = 2.0*WEIGHT_2D*lambda[0];

  return (const REAL_B *)D2;
}

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

  D2[1][1] = 2.0*WEIGHT_2D*lambda[0];
  D2[0][1] = D2[1][0] = 2.0*WEIGHT_2D*lambda[1];

  return (const REAL_B *)D2;
}

/* >>> */

/* >>> */
#endif

#if DIM_OF_WORLD >= 3
/* <<< 3d, P1 */

/* <<< values */

/* face 0 */

static REAL twb_P1_phi_0_w0_3d(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  return WEIGHT_3D*SQR(lambda[1])*lambda[2]*lambda[3];
}

static REAL twb_P1_phi_1_w0_3d(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  return WEIGHT_3D*lambda[1]*SQR(lambda[2])*lambda[3];
}

static REAL twb_P1_phi_2_w0_3d(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  return WEIGHT_3D*lambda[1]*lambda[2]*SQR(lambda[3]);
}

/* face 1 */

static REAL twb_P1_phi_0_w1_3d(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  return WEIGHT_3D*SQR(lambda[0])*lambda[2]*lambda[3];
}

static REAL twb_P1_phi_1_w1_3d(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  return WEIGHT_3D*lambda[0]*SQR(lambda[2])*lambda[3];
}

static REAL twb_P1_phi_2_w1_3d(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  return WEIGHT_3D*lambda[0]*lambda[2]*SQR(lambda[3]);
}

/* face 2 */

static REAL twb_P1_phi_0_w2_3d(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  return WEIGHT_3D*SQR(lambda[0])*lambda[1]*lambda[3];
}

static REAL twb_P1_phi_1_w2_3d(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  return WEIGHT_3D*lambda[0]*SQR(lambda[1])*lambda[3];
}

static REAL twb_P1_phi_2_w2_3d(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  return WEIGHT_3D*lambda[0]*lambda[1]*SQR(lambda[3]);
}

/* face 3 */

static REAL twb_P1_phi_0_w3_3d(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  return WEIGHT_3D*SQR(lambda[0])*lambda[1]*lambda[2];
}

static REAL twb_P1_phi_1_w3_3d(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  return WEIGHT_3D*lambda[0]*SQR(lambda[1])*lambda[2];
}

static REAL twb_P1_phi_2_w3_3d(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  return WEIGHT_3D*lambda[0]*lambda[1]*SQR(lambda[2]);
}

/* >>> */

/* <<< gradients */

/* face 0 */

static
const REAL *twb_P1_grd_phi_0_w0_3d(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  static REAL_B grd;
  
  grd[1] = 2.0*WEIGHT_3D*lambda[1]*lambda[2]*lambda[3];
  grd[2] = WEIGHT_3D*SQR(lambda[1])*lambda[3];
  grd[3] = WEIGHT_3D*SQR(lambda[1])*lambda[2];
  
  return grd;
}

static
const REAL *twb_P1_grd_phi_1_w0_3d(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  static REAL_B grd;
  
  grd[1] = WEIGHT_3D*SQR(lambda[2])*lambda[3];
  grd[2] = 2.0*WEIGHT_3D*lambda[1]*lambda[2]*lambda[3];
  grd[3] = WEIGHT_3D*lambda[1]*SQR(lambda[2]);
  
  return grd;
}

static
const REAL *twb_P1_grd_phi_2_w0_3d(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  static REAL_B grd;
  
  grd[1] = WEIGHT_3D*lambda[2]*SQR(lambda[3]);
  grd[2] = WEIGHT_3D*lambda[1]*SQR(lambda[3]);
  grd[3] = 2.0*WEIGHT_3D*lambda[1]*lambda[2]*lambda[3];
  
  return grd;
}

/* face 1 */

static
const REAL *twb_P1_grd_phi_0_w1_3d(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  static REAL_B grd;
  
  grd[0] = 2.0*WEIGHT_3D*lambda[0]*lambda[2]*lambda[3];
  grd[2] = WEIGHT_3D*SQR(lambda[0])*lambda[3];
  grd[3] = WEIGHT_3D*SQR(lambda[0])*lambda[2];
  
  return grd;
}

static
const REAL *twb_P1_grd_phi_1_w1_3d(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  static REAL_B grd;
  
  grd[0] = WEIGHT_3D*SQR(lambda[2])*lambda[3];
  grd[2] = 2.0*WEIGHT_3D*lambda[0]*lambda[2]*lambda[3];
  grd[3] = WEIGHT_3D*lambda[0]*SQR(lambda[2]);
  
  return grd;
}

static
const REAL *twb_P1_grd_phi_2_w1_3d(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  static REAL_B grd;
  
  grd[0] = WEIGHT_3D*lambda[2]*SQR(lambda[3]);
  grd[2] = WEIGHT_3D*lambda[0]*SQR(lambda[3]);
  grd[3] = 2.0*WEIGHT_3D*lambda[0]*lambda[2]*lambda[3];
  
  return grd;
}

/* face 2 */

static
const REAL *twb_P1_grd_phi_0_w2_3d(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  static REAL_B grd;
  
  grd[0] = 2.0*WEIGHT_3D*lambda[0]*lambda[1]*lambda[3];
  grd[1] = WEIGHT_3D*SQR(lambda[0])*lambda[3];
  grd[3] = WEIGHT_3D*SQR(lambda[0])*lambda[1];
  
  return grd;
}

static
const REAL *twb_P1_grd_phi_1_w2_3d(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  static REAL_B grd;
  
  grd[0] = WEIGHT_3D*SQR(lambda[1])*lambda[3];
  grd[1] = 2.0*WEIGHT_3D*lambda[0]*lambda[1]*lambda[3];
  grd[3] = WEIGHT_3D*lambda[0]*SQR(lambda[1]);
  
  return grd;
}

static
const REAL *twb_P1_grd_phi_2_w2_3d(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  static REAL_B grd;
  
  grd[0] = WEIGHT_3D*lambda[1]*SQR(lambda[3]);
  grd[1] = WEIGHT_3D*lambda[0]*SQR(lambda[3]);
  grd[3] = 2.0*WEIGHT_3D*lambda[0]*lambda[1]*lambda[3];
  
  return grd;
}

/* face 3 */

static
const REAL *twb_P1_grd_phi_0_w3_3d(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  static REAL_B grd;
  
  grd[0] = 2.0*WEIGHT_3D*lambda[0]*lambda[1]*lambda[2];
  grd[1] = WEIGHT_3D*lambda[0]*lambda[2];
  grd[2] = WEIGHT_3D*lambda[0]*lambda[1];
  
  return grd;
}

static
const REAL *twb_P1_grd_phi_1_w3_3d(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  static REAL_B grd;
  
  grd[0] = WEIGHT_3D*SQR(lambda[1])*lambda[2];
  grd[1] = 2.0*WEIGHT_3D*lambda[0]*lambda[1]*lambda[2];
  grd[2] = WEIGHT_3D*lambda[0]*SQR(lambda[1]);
  
  return grd;
}

static
const REAL *twb_P1_grd_phi_2_w3_3d(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  static REAL_B grd;
  
  grd[0] = WEIGHT_3D*lambda[1]*SQR(lambda[2]);
  grd[1] = WEIGHT_3D*lambda[0]*SQR(lambda[2]);
  grd[2] = 2.0*WEIGHT_3D*lambda[0]*lambda[1]*lambda[2];
  
  return grd;
}

/* >>> */

/* <<< Hessians */

/* face 0 */

static
const REAL_B *twb_P1_D2_phi_0_w0_3d(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  static REAL_BB D2;
  
  D2[1][1] = 2.0*WEIGHT_3D*lambda[2]*lambda[3];
  D2[2][1] = D2[1][2] = 2.0*WEIGHT_3D*lambda[1]*lambda[3];
  D2[3][1] = D2[1][3] = 2.0*WEIGHT_3D*lambda[1]*lambda[2];
  D2[2][3] = D2[3][2] = WEIGHT_3D*SQR(lambda[1]);

  return (const REAL_B *)D2;
}

static
const REAL_B *twb_P1_D2_phi_1_w0_3d(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  static REAL_BB D2;
  
  D2[2][2] = 2.0*WEIGHT_3D*lambda[1]*lambda[3];
  D2[2][1] = D2[1][2] = 2.0*WEIGHT_3D*lambda[2]*lambda[3];
  D2[3][1] = D2[1][3] = WEIGHT_3D*SQR(lambda[2]);
  D2[2][3] = D2[3][2] = 2.0*WEIGHT_3D*lambda[1]*lambda[2];

  return (const REAL_B *)D2;
}

static
const REAL_B *twb_P1_D2_phi_2_w0_3d(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  static REAL_BB D2;
  
  D2[3][3] = 2.0*WEIGHT_3D*lambda[1]*lambda[2];
  D2[2][1] = D2[1][2] = WEIGHT_3D*SQR(lambda[3]);
  D2[3][1] = D2[1][3] = 2.0*WEIGHT_3D*lambda[2]*lambda[3];
  D2[2][3] = D2[3][2] = 2.0*WEIGHT_3D*lambda[1]*lambda[3];

  return (const REAL_B *)D2;
}

/* face 1 */

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

  D2[0][0] = 2.0*WEIGHT_3D*lambda[2]*lambda[3];
  D2[2][0] = D2[0][2] = 2.0*WEIGHT_3D*lambda[0]*lambda[3];
  D2[3][0] = D2[0][3] = 2.0*WEIGHT_3D*lambda[0]*lambda[2];
  D2[2][3] = D2[3][2] = WEIGHT_3D*SQR(lambda[0]);

  return (const REAL_B *)D2;
}

static
const REAL_B *twb_P1_D2_phi_1_w1_3d(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  static REAL_BB D2;
  
  D2[2][2] = 2.0*WEIGHT_3D*lambda[0]*lambda[3];
  D2[2][0] = D2[0][2] = 2.0*WEIGHT_3D*lambda[2]*lambda[3];
  D2[3][0] = D2[0][3] = WEIGHT_3D*SQR(lambda[2]);
  D2[2][3] = D2[3][2] = 2.0*WEIGHT_3D*lambda[0]*lambda[2];

  return (const REAL_B *)D2;
}

static
const REAL_B *twb_P1_D2_phi_2_w1_3d(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  static REAL_BB D2;
  
  D2[3][3] = 2.0*WEIGHT_3D*lambda[0]*lambda[2];
  D2[2][0] = D2[0][2] = WEIGHT_3D*SQR(lambda[3]);
  D2[3][0] = D2[0][3] = 2.0*WEIGHT_3D*lambda[2]*lambda[3];
  D2[2][3] = D2[3][2] = 2.0*WEIGHT_3D*lambda[0]*lambda[3];

  return (const REAL_B *)D2;
}

/* face 2 */

static
const REAL_B *twb_P1_D2_phi_0_w2_3d(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  static REAL_BB D2;
  
  D2[0][0] = 2.0*WEIGHT_3D*lambda[1]*lambda[3];
  D2[1][0] = D2[0][1] = 2.0*WEIGHT_3D*lambda[0]*lambda[3];
  D2[3][0] = D2[0][3] = 2.0*WEIGHT_3D*lambda[0]*lambda[1];
  D2[1][3] = D2[3][1] = WEIGHT_3D*SQR(lambda[0]);

  return (const REAL_B *)D2;
}

static
const REAL_B *twb_P1_D2_phi_1_w2_3d(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  static REAL_BB D2;
  
  D2[1][1] = 2.0*WEIGHT_3D*lambda[0]*lambda[3];
  D2[1][0] = D2[0][1] = 2.0*WEIGHT_3D*lambda[1]*lambda[3];
  D2[3][0] = D2[0][3] = WEIGHT_3D*SQR(lambda[1]);
  D2[1][3] = D2[3][1] = 2.0*WEIGHT_3D*lambda[0]*lambda[1];

  return (const REAL_B *)D2;
}

static
const REAL_B *twb_P1_D2_phi_2_w2_3d(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  static REAL_BB D2;
  
  D2[3][3] = 2.0*WEIGHT_3D*lambda[0]*lambda[1];
  D2[1][0] = D2[0][1] = WEIGHT_3D*SQR(lambda[3]);
  D2[3][0] = D2[0][3] = 2.0*WEIGHT_3D*lambda[1]*lambda[3];
  D2[1][3] = D2[3][1] = 2.0*WEIGHT_3D*lambda[0]*lambda[3];

  return (const REAL_B *)D2;
}

/* face 3 */

static
const REAL_B *twb_P1_D2_phi_0_w3_3d(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  static REAL_BB D2;
  
  D2[0][0] = 2.0*WEIGHT_3D*lambda[1]*lambda[2];
  D2[1][0] = D2[0][1] = 2.0*WEIGHT_3D*lambda[0]*lambda[2];
  D2[2][0] = D2[0][2] = 2.0*WEIGHT_3D*lambda[0]*lambda[1];
  D2[1][2] = D2[2][1] = WEIGHT_3D*SQR(lambda[0]);

  return (const REAL_B *)D2;
}

static
const REAL_B *twb_P1_D2_phi_1_w3_3d(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  static REAL_BB D2;
  
  D2[1][1] = 2.0*WEIGHT_3D*lambda[0]*lambda[2];
  D2[1][0] = D2[0][1] = 2.0*WEIGHT_3D*lambda[1]*lambda[2];
  D2[2][0] = D2[0][2] = WEIGHT_3D*SQR(lambda[1]);
  D2[1][2] = D2[2][1] = 2.0*WEIGHT_3D*lambda[0]*lambda[1];

  return (const REAL_B *)D2;
}

static
const REAL_B *twb_P1_D2_phi_2_w3_3d(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  static REAL_BB D2;
  
  D2[2][2] = 2.0*WEIGHT_3D*lambda[0]*lambda[1];
  D2[1][0] = D2[0][1] = WEIGHT_3D*SQR(lambda[2]);
  D2[2][0] = D2[0][2] = 2.0*WEIGHT_3D*lambda[1]*lambda[2];
  D2[1][2] = D2[2][1] = 2.0*WEIGHT_3D*lambda[0]*lambda[2];

  return (const REAL_B *)D2;
}

/* >>> */

/* >>> */
#endif

#if DIM_OF_WORLD >= 0
static const REAL *
twb_phi_d_w0(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  TWB_DATA *data = (TWB_DATA *)bfcts->ext_data;

  return data->wall_normals[0];
}
#endif

#if DIM_OF_WORLD >= 1
static const REAL *
twb_phi_d_w1(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  TWB_DATA *data = (TWB_DATA *)bfcts->ext_data;

  return data->wall_normals[1];
}
#endif

#if DIM_OF_WORLD >= 2
static const REAL *
twb_phi_d_w2(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  TWB_DATA *data = (TWB_DATA *)bfcts->ext_data;

  return data->wall_normals[2];
}
#endif

#if DIM_OF_WORLD >= 3
static const REAL *
twb_phi_d_w3(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  TWB_DATA *data = (TWB_DATA *)bfcts->ext_data;

  return data->wall_normals[3];
}
#endif

/* init_element() routines for vector-valued basis-functions: ALBERTA
 * assumes that the directional part is never constant, so the return
 * value should _ONLY_ reflect the status of the scalar factor.
 *
 * This version need FILL_NEIGH all the time. Maybe we should store
 * the wall normals globally, a suitable
 * refine_interpol()/coarse_restrict() routine could then be used to
 * update the global normals during mesh adaption. The normals would
 * be automatically oriented.
 */
static
INIT_EL_TAG twb_init_element(const EL_INFO *el_info, void *thisptr)
{
  FUNCNAME("twb_init_element");
  BAS_FCTS *self = (BAS_FCTS *)thisptr;
  TWB_DATA *data = (TWB_DATA *)self->ext_data;
  MESH *mesh;
  int w, dim;
  const EL_GEOM_CACHE *elgc;
  static bool neigh_warning = false;
  static bool coord_warning = false;

  if (el_info == NULL) {
    self->dir_pw_const = true;
    data->cur_el = NULL;
    data->cur_el_info = NULL;
    INIT_EL_TAG_CTX_DFLT(&self->tag_ctx);
    return INIT_EL_TAG_CTX_TAG(&self->tag_ctx);
  }

  if (data->cur_el == el_info->el && data->cur_el_info == el_info) {
    return INIT_EL_TAG_CTX_TAG(&self->tag_ctx);    
  }
  data->cur_el      = el_info->el;
  data->cur_el_info = el_info;
  
  mesh = el_info->mesh;
  dim = mesh->dim;

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

  if ((el_info->fill_flag & FILL_COORDS) == 0) {
    if (!coord_warning) {
      WARNING("FILL_COORDS not set, doing nothing.\n");
      coord_warning = true;
    }
    return INIT_EL_TAG_CTX_TAG(&self->tag_ctx);    
  }

  elgc = fill_el_geom_cache(el_info, FILL_EL_WALL_NORMALS);

  if (!neigh_warning && (el_info->fill_flag & FILL_NEIGH) == 0) {
    WARNING("Fill-flag FILL_NEIGH not set, unconditionally\n"
	    "using the outer normal.\n");
    for (w = 0; w < N_WALLS(dim); w++) {
      COPY_DOW(elgc->wall_normal[w], data->wall_normals[w]);
    }
    
    neigh_warning = true;
  } else for (w = 0; w < N_WALLS(dim); w++) {
      const EL *neigh;
      REAL orient = 1.0;

      if ((neigh = el_info->neigh[w]) != NULL) {
	int ov = el_info->opp_vertex[w];
      
	if (el_info->el->dof[w][0] < neigh->dof[ov][0]) {
	  orient = -orient;
	}
      }
      AXEY_DOW(orient, elgc->wall_normal[w], data->wall_normals[w]);
    }

  return INIT_EL_TAG_CTX_TAG(&self->tag_ctx);    
}

static const int *order_f_indices_3d(const EL *el, int face)
{
  FUNCNAME("order_f_indices_3d");
  static const int sorted[DIM_FAC_3D][N_VERTICES_2D] = {
    {0,2,1}, {1,0,2}, {0,1,2}, {2,1,0}, {1,2,0}, {2,0,1}
  };
  int       no;
  const int *vof;
  DOF       **dof = el->dof;

  vof = vertex_of_wall_3d[face];
  no = -1;
  if (dof[vof[0]][0] < dof[vof[1]][0])
    no++;
  if (dof[vof[1]][0] < dof[vof[2]][0])
    no += 2;
  if (dof[vof[2]][0] < dof[vof[0]][0])
    no += 4;

  if (no >= 0  &&  no <= 5)
    return (const int *) sorted[no];
  else
  {
    MSG("can not sort face indices of element %d at face %d\n",
	INDEX(el), face);
    return NULL;
  }
}

#undef DEF_EL_VEC_TWB
#define DEF_EL_VEC_TWB(type, name)			\
  DEF_EL_VEC_CONST(type, name, N_TWB_MAX, N_TWB_MAX)

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

#undef DEFUN_GET_EL_DOF_VEC_TWB
#define DEFUN_GET_EL_DOF_VEC_TWB(name, type, ASSIGN)		\
  DEFUN_GET_EL_VEC_TWB(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 *					\
  twb_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 twb_get__##name##_vec(vec, el, dv);			\
    } else {								\
      twb_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_TWB(thisptr->dim, dof_indices, DOF, admin,
		     rvec[ibas] = dof,
		     const DOF_ADMIN *admin, const BAS_FCTS *thisptr);

static const EL_BNDRY_VEC *
twb_get_bound_1d(BNDRY_FLAGS *vec,
		 const EL_INFO *el_info, const BAS_FCTS *thisptr)
{
  static DEF_EL_VEC_TWB(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[w], el_info->vertex_bound[1-w]);
  }

  return vec ? NULL : rvec_space;
}

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

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

  return vec ? NULL : rvec_space;
}
#endif

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

  for (w = 0, ibas = 0; w < N_WALLS_3D; w++) {
    for (i = 0; i < N_VERTICES_2D; i++) {
      BNDRY_FLAGS_INIT(rvec[ibas]);
      BNDRY_FLAGS_SET(rvec[ibas], el_info->face_bound[w]);
      ibas++;
    }
  }

  return vec ? NULL : rvec_space;
}
#endif

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

DEFUN_GET_EL_DOF_VEC_TWB(int, INT, COPY_EQ);

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

DEFUN_GET_EL_DOF_VEC_TWB(real, REAL, COPY_EQ);

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

DEFUN_GET_EL_DOF_VEC_TWB(real_d, REAL_D, COPY_DOW);

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

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

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

DEFUN_GET_EL_DOF_VEC_TWB(schar, SCHAR, COPY_EQ);

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

DEFUN_GET_EL_DOF_VEC_TWB(uchar, UCHAR, COPY_EQ);

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

DEFUN_GET_EL_DOF_VEC_TWB(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_TWB(real_dd, REAL_DD, MYMCOPY);

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

static void twb_interpol_wall_dow(REAL ipolvec[],
				  const EL_REAL_VEC_D *vec,
				  const EL_INFO *el_info, int wall, int dim,
				  LOC_FCT_D_AT_QP f, void *f_data,
				  const QUAD_FAST *qfast,
				  TWB_DATA *data)
{
  REAL rhs[N_LAMBDA(DIM_MAX-1)];
  REAL_D uh_res, f_res, diff;
  int iq, b;
  const int *vow = VERTEX_OF_WALL(dim, wall);

  INIT_ELEMENT(el_info, qfast);
  for (b = 0; b < N_LAMBDA(dim-1); b++) {
    rhs[b] = 0.0;
    for (iq = 0; iq < qfast->n_points; iq++) {
      eval_uh_dow_fast(uh_res, vec, qfast, iq);
      f(f_res, el_info, qfast->quad, iq, f_data);
      AXPBY_DOW(1.0, f_res, -1.0, uh_res, diff);
      rhs[b] +=
	qfast->w[iq]
	*
	SCP_DOW(diff, data->wall_normals[wall])
	*
	qfast->quad->lambda[iq][vow[b]];
    }
  }
  for (b = 0; b < N_LAMBDA(dim-1); b++) {
    int j;
    ipolvec[b] = 0.0;
    for (j = 0; j < N_LAMBDA(dim-1); j++) {
      ipolvec[b] += mass_inv[dim][b][j] * rhs[j];
    }
  }
}

static void twb_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)
{
  TWB_DATA             *data = (TWB_DATA *)thisptr->ext_data;
  int                  dim   = thisptr->dim;
  const WALL_QUAD_FAST *wquad_fast;
  REAL                 ipolvec[N_VERTICES(DIM_MAX-1)];
  int i;    

  if (data->wqfast->bas_fcts != thisptr) {
    data->wqfast = get_wall_quad_fast(thisptr, data->wquad, INIT_PHI);
    INIT_ELEMENT(el_info, thisptr);
  }
  wquad_fast = data->wqfast;

  if (b_no) {
    for (i = 0; i < no; i++) {
      vec->vec[b_no[i]] = 0.0;
    }
  } else if (wall >= 0) {
    for (i = 0; i < N_LAMBDA(dim-1); i++) {
      vec->vec[wall*N_LAMBDA(dim-1) + i] = 0.0;
    }
  } else {
    for (i = 0; i < thisptr->n_bas_fcts; i++) {
      vec->vec[i] = 0.0;
    }
  }
  if (wall >= 0) {
    int offset = wall * N_LAMBDA(dim-1), b;
    twb_interpol_wall_dow(ipolvec, vec, el_info, wall, dim,
			  f, f_data, wquad_fast->quad_fast[wall], data);
    if (b_no != NULL) {
      int n;
      for (n = 0, b = 0; n < no; b++) {
	if (b_no[n] == offset+b) {
	  vec->vec[offset+b] = ipolvec[b];
	  n++;
	}
      }      
    } else {
      for (b = 0; b < N_LAMBDA(dim-1); b++) {
	vec->vec[offset+b] = ipolvec[b];
      }
    }
  } else if (b_no) {
    bool wall_done[N_WALLS_MAX] = { false, };
    int n = 0;
    int wall = b_no[0] / N_LAMBDA(dim-1);
    while (n < no) { /* We assume that b_no is sorted */
      int offset, b, i;
      offset = wall * N_LAMBDA(dim-1);
      twb_interpol_wall_dow(ipolvec, vec, el_info, wall, dim,
			    f, f_data, wquad_fast->quad_fast[wall], data);
      wall_done[wall] = true;
      for (i = 0; i < no; i++) {
	for (b = 0; b < N_LAMBDA(dim-1); b++) {
	  if (b_no[i] == offset + b) {
	    vec->vec[offset+b] = ipolvec[b];
	    n++;
	  } else {
	    int w = b_no[i] / N_LAMBDA(dim-1); /* next wall */
	    if (!wall_done[w]) {
	      wall = w;
	    }
	  }
	}
      }
    }
  } else {
    for (wall = 0; wall < N_WALLS(thisptr->dim); wall++) {
      int offset = wall * N_LAMBDA(dim-1);

      twb_interpol_wall_dow(vec->vec + offset,
			    vec, el_info, wall, dim,
			    f, f_data, wquad_fast->quad_fast[wall], data);
    }
  }
}

static void twb_interpol_wall(REAL ipolvec[],
			      const EL_REAL_VEC *vec,
			      const EL_INFO *el_info, int wall, int dim,
			      LOC_FCT_AT_QP f, void *f_data,
			      const QUAD_FAST *qfast,
			      TWB_DATA *data)
{
  REAL rhs[N_LAMBDA(DIM_MAX-1)];
  REAL uh_res, f_res;
  int iq, b;
  const int *vow = VERTEX_OF_WALL(dim, wall);

  INIT_ELEMENT(el_info, qfast);
  for (b = 0; b < N_LAMBDA(dim-1); b++) {
    rhs[b] = 0.0;
    for (iq = 0; iq < qfast->n_points; iq++) {
      uh_res = eval_uh_fast(vec, qfast, iq);
      f_res  = f(el_info, qfast->quad, iq, f_data);
      
      rhs[b] +=
	qfast->w[iq]
	*
	(f_res - uh_res)
	*
	qfast->quad->lambda[iq][vow[b]];
    }
  }
  for (b = 0; b < N_LAMBDA(dim-1); b++) {
    int j;
    ipolvec[b] = 0.0;
    for (j = 0; j < N_LAMBDA(dim-1); j++) {
      ipolvec[b] += mass_inv[dim][b][j] * rhs[j];
    }
  }
}

static void twb_interpol(EL_REAL_VEC *vec,
			 const EL_INFO *el_info, int wall,
			 int no, const int *b_no,
			 LOC_FCT_AT_QP f, void *f_data,
			 const BAS_FCTS *thisptr)
{
  TWB_DATA             *data = (TWB_DATA *)thisptr->ext_data;
  int                  dim   = thisptr->dim;
  const WALL_QUAD_FAST *wquad_fast;
  REAL                 ipolvec[N_VERTICES(DIM_MAX-1)];
  int i;

  if (data->wqfast->bas_fcts != thisptr) {
    data->wqfast = get_wall_quad_fast(thisptr, data->wquad, INIT_PHI);
    INIT_ELEMENT(el_info, thisptr);
  }
  wquad_fast = data->wqfast;

  if (b_no) {
    for (i = 0; i < no; i++) {
      vec->vec[b_no[i]] = 0.0;
    }
  } else if (wall >= 0) {
    for (i = 0; i < N_LAMBDA(dim-1); i++) {
      vec->vec[wall*N_LAMBDA(dim-1) + i] = 0.0;
    }
  } else {
    for (i = 0; i < thisptr->n_bas_fcts; i++) {
      vec->vec[i] = 0.0;
    }
  }
  if (wall >= 0) {
    int offset = wall * N_LAMBDA(dim-1), b;
    twb_interpol_wall(ipolvec, vec, el_info, wall, dim,
		      f, f_data, wquad_fast->quad_fast[wall], data);
    if (b_no != NULL) {
      int n;
      for (n = 0, b = 0; n < no; b++) {
	if (b_no[n] == offset+b) {
	  vec->vec[offset+b] = ipolvec[b];
	  n++;
	}
      }      
    } else {
      for (b = 0; b < N_LAMBDA(dim-1); b++) {
	vec->vec[offset+b] = ipolvec[b];
      }
    }
  } else if (b_no) {
    bool wall_done[N_WALLS_MAX] = { false, };
    int n = 0;
    int wall = b_no[0] / N_LAMBDA(dim-1);
    while (n < no) { /* We assume that b_no is sorted */
      int offset, b, i;
      offset = wall * N_LAMBDA(dim-1);
      twb_interpol_wall(ipolvec, vec, el_info, wall, dim,
			f, f_data, wquad_fast->quad_fast[wall], data);
      wall_done[wall] = true;
      for (i = 0; i < no; i++) {
	for (b = 0; b < N_LAMBDA(dim-1); b++) {
	  if (b_no[i] == offset + b) {
	    vec->vec[offset+b] = ipolvec[b];
	    n++;
	  } else {
	    int w = b_no[i] / N_LAMBDA(dim-1); /* next wall */
	    if (!wall_done[w]) {
	      wall = w;
	    }
	  }
	}
      }
    }
  } else {
    for (wall = 0; wall < N_WALLS(thisptr->dim); wall++) {
      int offset = wall * N_LAMBDA(dim-1);

      twb_interpol_wall(vec->vec + offset,
			vec, el_info, wall, dim,
			f, f_data, wquad_fast->quad_fast[wall], data);
    }
  }
}

static void twb_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: { /* Be lazy ... this could be optimized somewhat */
    const DOF_ADMIN *admin = drv->fe_space->admin;
    REAL *v = drv->vec;
    DOF  pdof[N_TWB_MAX];
    DOF  cdof[N_TWB_MAX];

    el = rclist->el_info.el;

    twb_get_dof_indices(pdof, el, admin, self);
    twb_get_dof_indices(cdof, el->child[0], admin, self);
    
    /* interior edge */
    v[cdof[2]] = v[cdof[3]] = 0.0;
    
    /* new edge */
    v[cdof[0]] = 0.5  * v[pdof[4]];
    v[cdof[1]] = 0.25 * (v[pdof[4]] + v[pdof[5]]);

    twb_get_dof_indices(cdof, el->child[1], admin, self);
    
    /* new edge */
    v[cdof[3]] = 0.5  * v[pdof[5]];
    v[cdof[2]] = 0.25 * (v[pdof[4]] + v[pdof[5]]);

    if (n > 1) {
      el = rclist[1].el_info.el;

      twb_get_dof_indices(cdof, el->child[0], admin, self);

      /* interior edge */
      v[cdof[2]] = v[cdof[3]] = 0.0;
    }
    break;
  }
#endif
#if DIM_MAX > 2
  case 3: {
    const DOF_ADMIN *admin = drv->fe_space->admin;
    REAL *v = drv->vec;
    DOF pdof[N_TWB_MAX];
    DOF cdof[N_TWB_MAX];
    int i;

    for (i = 0; i < n; i++) {
      el = rclist[i].el_info.el;

      twb_get_dof_indices(pdof, el, admin, self);

      /* wall 0 of both children is the interior wall */
      twb_get_dof_indices(cdof, el->child[0], admin, self);

      v[cdof[0]] = v[cdof[1]] = v[cdof[2]] = 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
       */

      /* child 0, wall 1, cdof 3 4 5, pdof 6 7 8 */
      v[cdof[3]] = 0.5 * v[pdof[6]];
      v[cdof[4]] = 0.5 * v[pdof[8]];
      v[cdof[5]] = 0.25 * (v[pdof[6]]+ v[pdof[7]]);

      /* child 0, wall 2, cdof 6 7 8, pdof 9 10 11 */
      v[cdof[6]] = 0.5 * v[pdof[9]];
      v[cdof[7]] = 0.5 * v[pdof[11]];
      v[cdof[8]] = 0.25 * (v[pdof[9]] + v[pdof[10]]);
      
      /* child 1 */
      twb_get_dof_indices(cdof, el->child[1], admin, self);

      if (rclist[i].el_info.el_type > 0) {
	/* cwall 1, cdof 3 4 5, pwall 2, pdof 6 7 8 */
	v[cdof[3]] = 0.5 * v[pdof[7]];
	v[cdof[4]] = 0.5 * v[pdof[8]];
	v[cdof[5]] = 0.25 * (v[pdof[6]] + v[pdof[7]]);

	/* cwall 2, cdof 6 7 8, pwall 3, pdof 9 10 11 */
	v[cdof[6]] = 0.5 * v[pdof[10]];
	v[cdof[7]] = 0.5 * v[pdof[11]];
	v[cdof[8]] = 0.25 * (v[pdof[9]] + v[pdof[10]]);
      } else {
	/* cwall 1, cdof 3 4 5, pwall 3, pdof 9 10 11 */
	v[cdof[3]] = 0.5 * v[pdof[10]];
	v[cdof[4]] = 0.5 * v[pdof[11]];
	v[cdof[5]] = 0.25 * (v[pdof[9]] + v[pdof[10]]);

	/* cwall 2, cdof 6 7 8, pwall 2, peof 6 7 8 */
	v[cdof[6]] = 0.5 * v[pdof[7]];
	v[cdof[7]] = 0.5 * v[pdof[8]];
	v[cdof[8]] = 0.25 * (v[pdof[6]] + v[pdof[7]]);
      }
    }
    break;
  }
#endif
  default:
    break;
  }
}

static void twb_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: {
    const DOF_ADMIN *admin = drv->fe_space->admin;
    REAL *v     = drv->vec;
    DOF pdof[N_TWB_MAX];
    DOF cdof[N_TWB_MAX];

    el = rclist->el_info.el;
    twb_get_dof_indices(pdof, el, admin, self);

    twb_get_dof_indices(cdof, el->child[0], admin, self);
    v[pdof[4]] = 2.0 * v[cdof[0]];

    twb_get_dof_indices(cdof, el->child[1], admin, self);
    v[pdof[4]] = 2.0 * v[cdof[3]];

    break;
  }
#endif
#if DIM_MAX > 2
  case 3: {
    const DOF_ADMIN *admin = drv->fe_space->admin;
    REAL *v     = drv->vec;
    int i;
    DOF pdof[N_TWB_MAX];
    DOF cdof[N_TWB_MAX];

    for (i = 0; i < n; i++) {
      el = rclist[i].el_info.el;

      twb_get_dof_indices(pdof, el, admin, self);

      twb_get_dof_indices(cdof, el->child[0], admin, self);

      /* wall 1, child 0 */
      v[pdof[6]] = 2.0 * v[cdof[3]];
      v[pdof[8]] = v[cdof[4]];

      /* wall 2, child 0 */
      v[pdof[9]]  = 2.0 * v[cdof[6]];
      v[pdof[11]] = v[cdof[7]];

      /* 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
       */
      /* child 1 */
      twb_get_dof_indices(cdof, el->child[1], admin, self);

      if (rclist[i].el_info.el_type > 0) {
	/* wall 1, child 1, pwall 2 */
	v[pdof[7]]  = 2.0 * v[cdof[3]];
	v[pdof[8]] += v[cdof[4]];
	/* wall 2, child 1, pwall 3 */
	v[pdof[10]]  = 2.0 * v[cdof[6]];
	v[pdof[11]] += v[cdof[7]];
      } else {
	/* wall 1, child 1, pwall 3 */
	v[pdof[10]]  = 2.0 * v[cdof[3]];
	v[pdof[11]] += v[cdof[4]];
	/* wall 2, child 1, pwall 2 */
	v[pdof[7]]  = 2.0 * v[cdof[6]];
	v[pdof[8]] += v[cdof[7]];
      }
    }
    break;
  }
#endif
  default:
    break;
  }
}

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

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

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

static
const BAS_FCT twb_phi[DIM_MAX+1][N_VERTICES(DIM_MAX-1)*N_WALLS_MAX] = {
  { NULL, /* 0 dim */ },
#if DIM_MAX >= 1
  { twb_phi_w0_1d, twb_phi_w1_1d, },
#endif
#if DIM_MAX >= 2
  { twb_P1_phi_0_w0_2d, twb_P1_phi_1_w0_2d, 
    twb_P1_phi_0_w1_2d, twb_P1_phi_1_w1_2d,
    twb_P1_phi_0_w2_2d, twb_P1_phi_1_w2_2d, },
#endif
#if DIM_MAX >= 3
  { twb_P1_phi_0_w0_3d, twb_P1_phi_1_w0_3d, twb_P1_phi_2_w0_3d,
    twb_P1_phi_0_w1_3d, twb_P1_phi_1_w1_3d, twb_P1_phi_2_w1_3d,
    twb_P1_phi_0_w2_3d, twb_P1_phi_1_w2_3d, twb_P1_phi_2_w2_3d,
    twb_P1_phi_0_w3_3d, twb_P1_phi_1_w3_3d, twb_P1_phi_2_w3_3d, },
#endif
};

static
const GRD_BAS_FCT twb_grd_phi[DIM_MAX+1][N_VERTICES(DIM_MAX-1)*N_WALLS_MAX] = {
  { NULL, /* 0 dim */ },
#if DIM_MAX >= 1
  { twb_grd_phi_w0_1d, twb_grd_phi_w1_1d, },
#endif
#if DIM_MAX >= 2
  { twb_P1_grd_phi_0_w0_2d, twb_P1_grd_phi_1_w0_2d, 
    twb_P1_grd_phi_0_w1_2d, twb_P1_grd_phi_1_w1_2d,
    twb_P1_grd_phi_0_w2_2d, twb_P1_grd_phi_1_w2_2d, },
#endif
#if DIM_MAX >= 3
  { twb_P1_grd_phi_0_w0_3d, twb_P1_grd_phi_1_w0_3d, twb_P1_grd_phi_2_w0_3d,
    twb_P1_grd_phi_0_w1_3d, twb_P1_grd_phi_1_w1_3d, twb_P1_grd_phi_2_w1_3d,
    twb_P1_grd_phi_0_w2_3d, twb_P1_grd_phi_1_w2_3d, twb_P1_grd_phi_2_w2_3d,
    twb_P1_grd_phi_0_w3_3d, twb_P1_grd_phi_1_w3_3d, twb_P1_grd_phi_2_w3_3d, },
#endif
};

static
const D2_BAS_FCT twb_D2_phi[DIM_MAX+1][N_VERTICES(DIM_MAX-1)*N_WALLS_MAX] = {
  { NULL, /* 0 dim */ },
#if DIM_MAX >= 1
  { twb_D2_phi_w0_1d, twb_D2_phi_w1_1d, },
#endif
#if DIM_MAX >= 2
  { twb_P1_D2_phi_0_w0_2d, twb_P1_D2_phi_1_w0_2d, 
    twb_P1_D2_phi_0_w1_2d, twb_P1_D2_phi_1_w1_2d,
    twb_P1_D2_phi_0_w2_2d, twb_P1_D2_phi_1_w2_2d, },
#endif
#if DIM_MAX >= 3
  { twb_P1_D2_phi_0_w0_3d, twb_P1_D2_phi_1_w0_3d, twb_P1_D2_phi_2_w0_3d,
    twb_P1_D2_phi_0_w1_3d, twb_P1_D2_phi_1_w1_3d, twb_P1_D2_phi_2_w1_3d,
    twb_P1_D2_phi_0_w2_3d, twb_P1_D2_phi_1_w2_3d, twb_P1_D2_phi_2_w2_3d,
    twb_P1_D2_phi_0_w3_3d, twb_P1_D2_phi_1_w3_3d, twb_P1_D2_phi_2_w3_3d, },
#endif
};

static
const BAS_FCT_D twb_phi_d[DIM_MAX+1][N_VERTICES(DIM_MAX-1)*N_WALLS_MAX] = {
  { NULL, /* 0 dim */ },
#if DIM_MAX >= 1
  { twb_phi_d_w0, twb_phi_d_w1, },
#endif
#if DIM_MAX >= 2
  { twb_phi_d_w0, twb_phi_d_w0,
    twb_phi_d_w1, twb_phi_d_w1,
    twb_phi_d_w2, twb_phi_d_w2, },
#endif
#if DIM_MAX >= 3
  { twb_phi_d_w0, twb_phi_d_w0, twb_phi_d_w0,
    twb_phi_d_w1, twb_phi_d_w1, twb_phi_d_w1,
    twb_phi_d_w2, twb_phi_d_w2, twb_phi_d_w2,
    twb_phi_d_w3, twb_phi_d_w3, twb_phi_d_w3,},
#endif
};

static
const int twb_trace_mapping[
  DIM_MAX+1][2][2][N_WALLS_MAX][N_VERTICES(DIM_MAX-1)] = {
  { { { { 0, }, }, }, },
#if DIM_MAX > 0
  { { { { 0, }, { 1, }, }, }, },
#endif
#if DIM_MAX > 1
  { { { { 0, 1, }, { 2, 3, }, { 4, 5, }, }, }, },
#endif
#if DIM_MAX > 2
  { /* dim == 3 */
    { /* t == 0 */
      { /* o == + */
	{ 2, 0, 1, }, { 4, 3, 5, }, { 6, 7, 8, }, { 10, 9, 11, },
      },
      { /* o == - */
	{ 0, 2, 1, }, { 3, 4, 5, }, { 7, 6, 8, }, { 9, 10, 11, },
      },
    },
    { /* t > 0 */
      { /* o == + */
	{ 0, 1, 2, }, { 4, 3, 5, }, { 6, 7, 8, }, { 10, 9, 11, },
      },
      { /* o == - */
	{ 1, 0, 2, }, { 3, 4, 5, }, { 7, 6, 8, }, { 9, 10, 11, },
      },
    },
  },
#endif
};

#define MAX_INTER_DEG  20 /* Overkill */
#define MAX_TENSOR_DEG  1

const BAS_FCTS *get_tensor_wall_bubbles(unsigned int dim,
					unsigned int tensor_deg,
					unsigned int inter_deg)
{
  FUNCNAME("get_tensor_wall_bubbles");
  static BAS_FCTS *twb_bfcts[DIM_MAX+1][MAX_TENSOR_DEG+1][MAX_INTER_DEG+1];
  BAS_FCTS *bfcts;
  TWB_DATA *data;
  
  if (tensor_deg == 0) {
    return get_wall_bubbles(dim, inter_deg);
  }

  TEST_EXIT(dim <= DIM_MAX, "dim = %d > DIM_MAX = %d.\n", dim, DIM_MAX);
 
  TEST_EXIT(tensor_deg <= MAX_TENSOR_DEG,
	    "Sorry, tensor-product face-bubbles only implemented up to "
	    "degree %d\n", MAX_TENSOR_DEG);

  if (inter_deg > MAX_INTER_DEG) {
    WARNING("Truncating quad-degree from %d to %d.\n",
	    inter_deg, MAX_INTER_DEG);
    inter_deg = MAX_INTER_DEG;
  }
  
  if ((bfcts = twb_bfcts[tensor_deg][inter_deg][dim]) == NULL) {
    char name[sizeof("TensorWallBubbles_TX_IX_Xd")];

    sprintf(name, "TensorWallBubbles_T%d_I%d_%dd", tensor_deg, inter_deg, dim);

    twb_bfcts[dim][tensor_deg][inter_deg] = bfcts = MEM_CALLOC(1, BAS_FCTS);
    bfcts->name   = strdup(name);
    bfcts->dim    = dim;
    bfcts->rdim   = DIM_OF_WORLD;
    bfcts->degree = N_LAMBDA(dim-1)+tensor_deg;
    bfcts->n_bas_fcts =
      bfcts->n_bas_fcts_max = N_WALLS(dim)*N_BAS_LAGRANGE(tensor_deg, dim-1);
    switch (dim) {
    case 1: bfcts->n_dof[VERTEX] = 1; break;
    case 2: bfcts->n_dof[EDGE] = 2; break;
    case 3: bfcts->n_dof[FACE] = 3; break;
    }
    bfcts->trace_admin = -1;
    CHAIN_INIT(bfcts);
    bfcts->unchained = bfcts;
    bfcts->phi     = twb_phi[dim];
    bfcts->grd_phi = twb_grd_phi[dim];
    bfcts->D2_phi  = twb_D2_phi[dim];
    bfcts->phi_d   = twb_phi_d[dim];
    if (dim > 0) {
      int o, t, w;
      bfcts->trace_bas_fcts =
	get_trace_tensor_bubbles(dim-1, tensor_deg, inter_deg);
      for (w = 0; w < N_WALLS(dim); w++) {
	bfcts->n_trace_bas_fcts[w] = N_VERTICES(dim-1);
	for (t = 0; t < 2; t++) {
	  for (o = 0; o < 2; o++) {
	    bfcts->trace_dof_map[t][o][w] =
	      twb_trace_mapping[dim][t][o][w];
	  }
	}
      }
    } else {
      bfcts->trace_bas_fcts = get_null_bfcts(0);
    }
    bfcts->get_dof_indices = twb_get_dof_indices;
    switch (dim) {
    case 1:
      bfcts->get_bound = twb_get_bound_1d;
      break;
#if DIM_MAX > 1
    case 2:
      bfcts->get_bound = twb_get_bound_2d;
      break;
#endif
#if DIM_MAX > 2
    case 3:
      bfcts->get_bound = twb_get_bound_3d;
      break;
#endif
    }
    bfcts->interpol = twb_interpol;
    bfcts->interpol_d = NULL;
    bfcts->interpol_dow = twb_interpol_dow;
    bfcts->dir_pw_const = true;

    bfcts->get_int_vec     = twb_get_int_vec;
    bfcts->get_real_vec    = twb_get_real_vec;
    bfcts->get_real_d_vec  = twb_get_real_d_vec;
    bfcts->get_real_dd_vec = twb_get_real_dd_vec;
    bfcts->get_real_vec_d  = twb_get_real_vec_d;
    bfcts->get_uchar_vec   = twb_get_uchar_vec;
    bfcts->get_schar_vec   = twb_get_schar_vec;
    bfcts->get_ptr_vec     = twb_get_ptr_vec;

    bfcts->real_refine_inter =
      (void  (*)(DOF_REAL_VEC *, RC_LIST_EL *, int))
      twb_real_refine_inter_d;
    bfcts->real_coarse_inter =
      (void  (*)(DOF_REAL_VEC *, RC_LIST_EL *, int))
      twb_real_coarse_inter_d;
    bfcts->real_coarse_restr =
      (void  (*)(DOF_REAL_VEC *, RC_LIST_EL *, int))
      twb_real_coarse_restr_d;

    bfcts->real_refine_inter_d =
      twb_real_refine_inter_d;
    bfcts->real_coarse_inter_d =
      twb_real_coarse_inter_d;
    bfcts->real_coarse_restr_d =
      twb_real_coarse_restr_d;

    data = bfcts->ext_data = MEM_CALLOC(1, TWB_DATA);

    INIT_ELEMENT_DEFUN(bfcts, twb_init_element,
		       FILL_NEIGH|FILL_COORDS);
    INIT_OBJECT(bfcts);

    data->wquad      = get_wall_quad(dim, inter_deg);
    data->inter_deg  = inter_deg;
    data->tensor_deg = tensor_deg;
    data->wqfast     = get_wall_quad_fast(bfcts, data->wquad, INIT_PHI);
  }
  
  return bfcts;
}

