/**@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/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_WALL_BTTB_MAX N_VERTICES(DIM_MAX-1)
#define N_BTTB_MAX (N_WALLS_MAX*N_WALL_BTTB_MAX)

/* 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_WALL_BTTB_MAX][N_WALL_BTTB_MAX] = {
  { { 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_WALL_BTTB_MAX][N_WALL_BTTB_MAX] = {
  { { 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 bulk_tensor_trace_bubbles_data
{
  const EL_INFO        *cur_el_info;
  const EL             *cur_el;
  const EL             *cur_trace_el[N_WALLS_MAX];
  int                  cur_wall[N_WALLS_MAX];
  int                  cur_wall_idx[N_WALLS_MAX]; /* inverse to cur_wall */
  int                  n_walls;
  int                  n_wall_bfcts;
  MESH                 *trace_mesh;
  int                  trace_id;
  REAL_D               wall_normals[N_WALLS_MAX];
  BAS_FCT              phi[N_BTTB_MAX];
  GRD_BAS_FCT          grd_phi[N_BTTB_MAX];
  D2_BAS_FCT           D2_phi[N_BTTB_MAX];
  BAS_FCT_D            phi_d[N_BTTB_MAX];
  int                  trace_dof_map[N_WALLS_MAX][N_WALL_BTTB_MAX];
  const WALL_QUAD      *wquad;
  const WALL_QUAD_FAST *wqfast;
  int                  tensor_deg;
  int                  inter_deg;
} BTTB_DATA;

/* scalar factors */

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

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

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

static const REAL *bttb_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 *bttb_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 *bttb_D2_phi_w0_1d(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  static const REAL_BB D2;
  return D2;
}

static const REAL_B *bttb_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 bttb_P1_phi_0_w0_2d(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  return WEIGHT_2D*SQR(lambda[1])*lambda[2];
}

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

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

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

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

static REAL bttb_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 *bttb_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 *bttb_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 *bttb_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 *bttb_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 *bttb_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 *bttb_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 *bttb_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 *bttb_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 *bttb_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 *bttb_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 *bttb_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 *bttb_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 bttb_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 bttb_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 bttb_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 bttb_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 bttb_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 bttb_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 bttb_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 bttb_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 bttb_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 bttb_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 bttb_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 bttb_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 *bttb_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 *bttb_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 *bttb_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 *bttb_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 *bttb_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 *bttb_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 *bttb_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 *bttb_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 *bttb_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 *bttb_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 *bttb_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 *bttb_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 *bttb_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 *bttb_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 *bttb_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 *bttb_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 *bttb_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 *bttb_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 *bttb_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 *bttb_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 *bttb_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 *bttb_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 *bttb_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 *bttb_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 *
bttb_phi_d_w0(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  BTTB_DATA *data = (BTTB_DATA *)bfcts->ext_data;

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

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

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

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

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

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

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

static
const BAS_FCT bttb_phi[DIM_MAX+1][N_VERTICES(DIM_MAX-1)*N_WALLS_MAX] = {
  { NULL, /* 0 dim */ },
#if DIM_MAX >= 1
  { bttb_phi_w0_1d, bttb_phi_w1_1d, },
#endif
#if DIM_MAX >= 2
  { bttb_P1_phi_0_w0_2d, bttb_P1_phi_1_w0_2d, 
    bttb_P1_phi_0_w1_2d, bttb_P1_phi_1_w1_2d,
    bttb_P1_phi_0_w2_2d, bttb_P1_phi_1_w2_2d, },
#endif
#if DIM_MAX >= 3
  { bttb_P1_phi_0_w0_3d, bttb_P1_phi_1_w0_3d, bttb_P1_phi_2_w0_3d,
    bttb_P1_phi_0_w1_3d, bttb_P1_phi_1_w1_3d, bttb_P1_phi_2_w1_3d,
    bttb_P1_phi_0_w2_3d, bttb_P1_phi_1_w2_3d, bttb_P1_phi_2_w2_3d,
    bttb_P1_phi_0_w3_3d, bttb_P1_phi_1_w3_3d, bttb_P1_phi_2_w3_3d, },
#endif
};

static
const GRD_BAS_FCT bttb_grd_phi[DIM_MAX+1][N_VERTICES(DIM_MAX-1)*N_WALLS_MAX] = {
  { NULL, /* 0 dim */ },
#if DIM_MAX >= 1
  { bttb_grd_phi_w0_1d, bttb_grd_phi_w1_1d, },
#endif
#if DIM_MAX >= 2
  { bttb_P1_grd_phi_0_w0_2d, bttb_P1_grd_phi_1_w0_2d, 
    bttb_P1_grd_phi_0_w1_2d, bttb_P1_grd_phi_1_w1_2d,
    bttb_P1_grd_phi_0_w2_2d, bttb_P1_grd_phi_1_w2_2d, },
#endif
#if DIM_MAX >= 3
  { bttb_P1_grd_phi_0_w0_3d, bttb_P1_grd_phi_1_w0_3d, bttb_P1_grd_phi_2_w0_3d,
    bttb_P1_grd_phi_0_w1_3d, bttb_P1_grd_phi_1_w1_3d, bttb_P1_grd_phi_2_w1_3d,
    bttb_P1_grd_phi_0_w2_3d, bttb_P1_grd_phi_1_w2_3d, bttb_P1_grd_phi_2_w2_3d,
    bttb_P1_grd_phi_0_w3_3d, bttb_P1_grd_phi_1_w3_3d, bttb_P1_grd_phi_2_w3_3d, },
#endif
};

static
const D2_BAS_FCT bttb_D2_phi[DIM_MAX+1][N_VERTICES(DIM_MAX-1)*N_WALLS_MAX] = {
  { NULL, /* 0 dim */ },
#if DIM_MAX >= 1
  { bttb_D2_phi_w0_1d, bttb_D2_phi_w1_1d, },
#endif
#if DIM_MAX >= 2
  { bttb_P1_D2_phi_0_w0_2d, bttb_P1_D2_phi_1_w0_2d, 
    bttb_P1_D2_phi_0_w1_2d, bttb_P1_D2_phi_1_w1_2d,
    bttb_P1_D2_phi_0_w2_2d, bttb_P1_D2_phi_1_w2_2d, },
#endif
#if DIM_MAX >= 3
  { bttb_P1_D2_phi_0_w0_3d, bttb_P1_D2_phi_1_w0_3d, bttb_P1_D2_phi_2_w0_3d,
    bttb_P1_D2_phi_0_w1_3d, bttb_P1_D2_phi_1_w1_3d, bttb_P1_D2_phi_2_w1_3d,
    bttb_P1_D2_phi_0_w2_3d, bttb_P1_D2_phi_1_w2_3d, bttb_P1_D2_phi_2_w2_3d,
    bttb_P1_D2_phi_0_w3_3d, bttb_P1_D2_phi_1_w3_3d, bttb_P1_D2_phi_2_w3_3d, },
#endif
};

static
const BAS_FCT_D bttb_phi_d[DIM_MAX+1][N_VERTICES(DIM_MAX-1)*N_WALLS_MAX] = {
  { NULL, /* 0 dim */ },
#if DIM_MAX >= 1
  { bttb_phi_d_w0, bttb_phi_d_w1, },
#endif
#if DIM_MAX >= 2
  { bttb_phi_d_w0, bttb_phi_d_w0,
    bttb_phi_d_w1, bttb_phi_d_w1,
    bttb_phi_d_w2, bttb_phi_d_w2, },
#endif
#if DIM_MAX >= 3
  { bttb_phi_d_w0, bttb_phi_d_w0, bttb_phi_d_w0,
    bttb_phi_d_w1, bttb_phi_d_w1, bttb_phi_d_w1,
    bttb_phi_d_w2, bttb_phi_d_w2, bttb_phi_d_w2,
    bttb_phi_d_w3, bttb_phi_d_w3, bttb_phi_d_w3,},
#endif
};

/* Trace-mapping lookup-table, has to correspond to slave_numbering_3d
 * defined in alberta_intern.h. The numbers are zero-based,
 * init_element will add an offset if more than one wall of the
 * current element belongs to the trace mesh.
 */
static const int bttb_trace_mapping_3d[2][2][N_WALLS_LIMIT][N_VERTICES_2D] = {
  { /* t == 0 */
    { /* o == + */
      { 2, 0, 1, }, { 1, 0, 2, }, { 0, 1, 2, }, { 1, 0, 2, },
    },
    { /* o == - */
      { 0, 2, 1, }, { 0, 1, 2, }, { 1, 0, 2, }, { 0, 1, 2, },
    },
  },
  { /* t > 0 */
    { /* o == + */
      { 0, 1, 2, }, { 1, 0, 2, }, { 0, 1, 2, }, { 1, 0, 2, },
    },
    { /* o == - */
      { 1, 0, 2, }, { 0, 1, 2, }, { 1, 0, 2, }, { 0, 1, 2, },
    },
  },
};

/* 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 bttb_init_element(const EL_INFO *el_info, void *vself)
{
  FUNCNAME("bttb_init_element");
  BAS_FCTS  *self = (BAS_FCTS *)vself;
  BTTB_DATA *data = (BTTB_DATA *)self->ext_data;
  MESH *mesh;
  int w, dim, n_bas, n_walls, t, o;
  const EL_GEOM_CACHE *elgc;
  static bool coord_warning = false;

  if (el_info == NULL) {
    data->cur_el = NULL;
    data->cur_el_info = NULL;

    self->dir_pw_const = true;
    self->n_bas_fcts = 0;
    for (w = 0; w < N_WALLS_MAX; w++) {
      self->n_trace_bas_fcts[w] =
	((BAS_FCTS *)self->unchained)->n_trace_bas_fcts[w] = 0;
    }
    memset(data->cur_wall, -1, sizeof(data->cur_wall));
    memset(data->cur_trace_el, 0, sizeof(data->cur_trace_el));
    self->n_bas_fcts =
      ((BAS_FCTS *)self->unchained)->n_bas_fcts = 0;

    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 = MIN(DIM_MAX, mesh->dim);

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

  if (data->trace_mesh == NULL) {
    data->trace_mesh = lookup_submesh_by_id(mesh, data->trace_id);
    TEST_EXIT(data->trace_mesh != NULL,
	      "No trace-mesh with id %d\n", data->trace_id);
  }

  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);    
  }

  /* Determine whether one of the faces belongs to our dedicated
   * trace-mesh.
   */
  for (n_bas = n_walls = w = 0; w < N_WALLS(dim); w++) {
    const EL *tr_el;
    int i;

    if ((tr_el = get_slave_el(el_info->el, w, data->trace_mesh)) == NULL) {
      self->n_trace_bas_fcts[w] =
	((BAS_FCTS *)self->unchained)->n_trace_bas_fcts[w] = 0;
      data->cur_wall[n_walls] = -1;
      data->cur_wall_idx[w]   = -1;
      continue;
    }
    data->cur_trace_el[n_walls] = tr_el;
    data->cur_wall[n_walls]     = w;
    data->cur_wall_idx[w]       = n_walls;
    elgc = fill_el_geom_cache(el_info, FILL_EL_WALL_NORMAL(w));
    COPY_DOW(elgc->wall_normal[w], data->wall_normals[w]);

    o = el_info->orientation < 0;
    t = el_info->el_type > 0;

    for (i = 0; i < data->n_wall_bfcts; i++, n_bas++) {
      int b = w*data->n_wall_bfcts + i;
      data->phi[n_bas]          = bttb_phi[dim][b];
      data->grd_phi[n_bas]      = bttb_grd_phi[dim][b];
      data->D2_phi[n_bas]       = bttb_D2_phi[dim][b];
      data->phi_d[n_bas]        = bttb_phi_d[dim][b];
      if (dim == 3) {
	data->trace_dof_map[w][i] =
	  bttb_trace_mapping_3d[t][o][w][i] + n_walls * N_VERTICES_2D;
      } else {
	data->trace_dof_map[w][i] = n_bas;
      }
    }

    self->n_trace_bas_fcts[w] =
      ((BAS_FCTS *)self->unchained)->n_trace_bas_fcts[w] = data->n_wall_bfcts;

    ++n_walls;
  }
  data->n_walls = n_walls;

  if (n_bas == 0) {
    /* The default (not NULL) case, actually */
    if (INIT_EL_TAG_CTX_TAG(&self->tag_ctx) != INIT_EL_TAG_DFLT) {
      for (w = 0; w < N_WALLS(dim); w++) {
	self->n_trace_bas_fcts[w] =
	  ((BAS_FCTS *)self->unchained)->n_trace_bas_fcts[w] = 0;
      }
      memset(data->cur_wall, -1, sizeof(data->cur_wall));
      memset(data->cur_trace_el, 0, sizeof(data->cur_trace_el));
      self->n_bas_fcts =
	((BAS_FCTS *)self->unchained)->n_bas_fcts = 0;
    }
    INIT_EL_TAG_CTX_DFLT(&self->tag_ctx);
  } else {
    self->n_bas_fcts =
      ((BAS_FCTS *)self->unchained)->n_bas_fcts = n_bas;

    INIT_EL_TAG_CTX_UNIQ(&self->tag_ctx);
  }

  return INIT_EL_TAG_CTX_TAG(&self->tag_ctx);    
}

#undef DEF_EL_VEC_BTTB
#define DEF_EL_VEC_BTTB(type, name)			\
  DEF_EL_VEC_CONST(type, name, N_BTTB_MAX, N_BTTB_MAX)

#undef DEFUN_GET_EL_VEC_BTTB
#define DEFUN_GET_EL_VEC_BTTB(name, type, self, admin, body, ...)	\
  static const EL_##type##_VEC *					\
  bttb_get_##name(type##_VEC_TYPE *vec, const EL *el, __VA_ARGS__)	\
  {									\
    FUNCNAME("wall_bubble_get_"#name);					\
    static DEF_EL_VEC_BTTB(type, rvec_space);				\
    type##_VEC_TYPE *rvec = vec ? vec : rvec_space->vec;		\
    BTTB_DATA *data = (BTTB_DATA *)self->ext_data;			\
    int n0, node, ibas = 0, w, i;					\
									\
    DEBUG_TEST_EXIT(true, "");						\
									\
    node = (admin)->mesh->node[CENTER];					\
    n0   = (admin)->n0_dof[CENTER];					\
    for (ibas = w = 0; w < data->n_walls; w++) {			\
      DOF **dofptr = data->cur_trace_el[w]->dof;			\
      for (i = 0; i < data->n_wall_bfcts; i++, ibas++) {		\
	DOF dof = dofptr[node][n0+i];					\
	body;								\
      }									\
    }									\
									\
    if (vec) {								\
      return NULL;							\
    } else {								\
      rvec_space->n_components = ibas;					\
      return rvec_space;						\
    }									\
  }									\
  struct _AI_semicolon_dummy

#undef DEFUN_GET_EL_DOF_VEC_BTTB
#define DEFUN_GET_EL_DOF_VEC_BTTB(name, type, ASSIGN)			\
  DEFUN_GET_EL_VEC_BTTB(_##name##_vec, type, dv->fe_space->bas_fcts,	\
			dv->fe_space->admin,				\
			ASSIGN(dv->vec[dof], rvec[ibas]),		\
			const DOF_##type##_VEC *dv);			\
  static const EL_##type##_VEC *					\
  bttb_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 bttb_get__##name##_vec(vec, el, dv);			\
    } else {								\
      bttb_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_BTTB(dof_indices, DOF, self, admin,
		      rvec[ibas] = dof,
		      const DOF_ADMIN *admin, const BAS_FCTS *self);

static const EL_BNDRY_VEC *
bttb_get_bound_1d(BNDRY_FLAGS *vec,
		 const EL_INFO *el_info, const BAS_FCTS *self)
{
  static DEF_EL_VEC_BTTB(BNDRY, rvec_space);
  BTTB_DATA   *data = (BTTB_DATA *)self->ext_data;
  BNDRY_FLAGS *rvec = vec ? vec : rvec_space->vec;
  int i;

  for (i = 0; i < self->n_bas_fcts; i++) {
    int wall = data->cur_wall[i];
    BNDRY_FLAGS_CPY(rvec[i], el_info->vertex_bound[1-wall]);
  }

  return vec ? NULL : rvec_space;
}

#if DIM_MAX > 1
static const EL_BNDRY_VEC *
bttb_get_bound_2d(BNDRY_FLAGS *vec,
		 const EL_INFO *el_info, const BAS_FCTS *self)
{
  static DEF_EL_VEC_BTTB(BNDRY, rvec_space);
  BTTB_DATA   *data = (BTTB_DATA *)self->ext_data;
  BNDRY_FLAGS *rvec = vec ? vec : rvec_space->vec;
  int w, ibas, i;

  for (w = 0, ibas = 0; w < data->n_walls; w++) {
    int wall = data->cur_wall[w];
    for (i = 0; i < data->n_wall_bfcts; i++, ibas++) {
      BNDRY_FLAGS_CPY(rvec[ibas], el_info->edge_bound[wall]);
    }
  }

  return vec ? NULL : rvec_space;
}
#endif

#if DIM_MAX > 2
static const EL_BNDRY_VEC *
bttb_get_bound_3d(BNDRY_FLAGS *vec,
		 const EL_INFO *el_info, const BAS_FCTS *self)
{
  static DEF_EL_VEC_BTTB(BNDRY, rvec_space);
  BTTB_DATA   *data = (BTTB_DATA *)self->ext_data;
  BNDRY_FLAGS *rvec = vec ? vec : rvec_space->vec;
  int w, ibas, i;

  for (w = 0, ibas = 0; w < data->n_walls; w++) {
    int wall = data->cur_wall[w];
    for (i = 0; i < data->n_wall_bfcts; i++, ibas++) {
      BNDRY_FLAGS_INIT(rvec[ibas]);
      BNDRY_FLAGS_SET(rvec[ibas], el_info->face_bound[wall]);
    }
  }

  return vec ? NULL : rvec_space;
}
#endif

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

DEFUN_GET_EL_DOF_VEC_BTTB(int, INT, COPY_EQ);

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

DEFUN_GET_EL_DOF_VEC_BTTB(real, REAL, COPY_EQ);

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

DEFUN_GET_EL_DOF_VEC_BTTB(real_d, REAL_D, COPY_DOW);

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

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

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

DEFUN_GET_EL_DOF_VEC_BTTB(schar, SCHAR, COPY_EQ);

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

DEFUN_GET_EL_DOF_VEC_BTTB(uchar, UCHAR, COPY_EQ);

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

DEFUN_GET_EL_DOF_VEC_BTTB(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_BTTB(real_dd, REAL_DD, MYMCOPY);

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

static void bttb_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,
				  BTTB_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 bttb_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 *self)
{
  BTTB_DATA            *data = (BTTB_DATA *)self->ext_data;
  int                  dim   = self->dim;
  const WALL_QUAD_FAST *wquad_fast;
  REAL                 ipolvec[N_VERTICES(DIM_MAX-1)];
  int i;

  vec->n_components = self->n_bas_fcts;

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

  if (b_no) {
    for (i = 0; i < no; i++) {
      vec->vec[b_no[i]] = 0.0;
    }
  } else if (wall >= 0) {
    if (data->cur_wall_idx[wall] == -1) {
      return;
    }
    for (i = 0; i < data->n_wall_bfcts; i++) {
      vec->vec[data->cur_wall_idx[wall]*data->n_wall_bfcts + i] = 0.0;
    }
  } else {
    for (i = 0; i < self->n_bas_fcts; i++) {
      vec->vec[i] = 0.0;
    }
  }
  if (wall >= 0) {
    int offset = data->cur_wall_idx[wall] * data->n_wall_bfcts;
    int b;
    bttb_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 idx = b_no[0] / data->n_wall_bfcts;
    while (n < no) {
      int b, i;
      int wall = data->cur_wall[idx];
      int offset = idx * data->n_wall_bfcts;
      bttb_interpol_wall_dow(ipolvec, vec, el_info, wall, dim,
			     f, f_data, wquad_fast->quad_fast[wall], data);
      wall_done[idx] = true;
      for (i = 0; i < no; i++) {
	for (b = 0; b < data->n_wall_bfcts; b++) {
	  if (b_no[i] == offset + b) {
	    vec->vec[offset+b] = ipolvec[b];
	    n++;
	  } else {
	    int w = b_no[i] / data->n_wall_bfcts; /* next chunk */
	    if (!wall_done[w]) {
	      idx = w;
	    }
	  }
	}
      }
    }
  } else {
    int w;
    for (w = 0; w < data->n_walls; w++) {
      int wall = data->cur_wall[w];
      int offset = w * data->n_wall_bfcts;

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

static void bttb_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,
			      BTTB_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 bttb_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 *self)
{
  BTTB_DATA            *data = (BTTB_DATA *)self->ext_data;
  int                  dim   = self->dim;
  const WALL_QUAD_FAST *wquad_fast;
  REAL                 ipolvec[N_VERTICES(DIM_MAX-1)];
  int i;

  vec->n_components = self->n_bas_fcts;

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

  if (b_no) {
    for (i = 0; i < no; i++) {
      vec->vec[b_no[i]] = 0.0;
    }
  } else if (wall >= 0) {
    if (data->cur_wall_idx[wall] == -1) {
      return;
    }
    for (i = 0; i < data->n_wall_bfcts; i++) {
      vec->vec[data->cur_wall_idx[wall]*data->n_wall_bfcts + i] = 0.0;
    }
  } else {
    for (i = 0; i < self->n_bas_fcts; i++) {
      vec->vec[i] = 0.0;
    }
  }
  if (wall >= 0) {
    int offset = data->cur_wall_idx[wall] * data->n_wall_bfcts;
    int b;
    bttb_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 idx = b_no[0] / data->n_wall_bfcts;
    while (n < no) {
      int b, i;
      int wall = data->cur_wall[idx];
      int offset = idx * data->n_wall_bfcts;
      bttb_interpol_wall(ipolvec, vec, el_info, wall, dim,
			 f, f_data, wquad_fast->quad_fast[wall], data);
      wall_done[idx] = true;
      for (i = 0; i < no; i++) {
	for (b = 0; b < data->n_wall_bfcts; b++) {
	  if (b_no[i] == offset + b) {
	    vec->vec[offset+b] = ipolvec[b];
	    n++;
	  } else {
	    int w = b_no[i] / data->n_wall_bfcts; /* next chunk */
	    if (!wall_done[w]) {
	      idx = w;
	    }
	  }
	}
      }
    }
  } else {
    int w;
    for (w = 0; w < data->n_walls; w++) {
      int wall = data->cur_wall[w];
      int offset = w * data->n_wall_bfcts;

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

/* Called on the trace mesh */
static void bttb_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;
  EL *el;

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

  switch (self->dim) {
  case 1: {
    DOF cdof, pdof0, pdof1;

    el = rclist->el_info.el;

    pdof0 = el->dof[node][n0+0];
    pdof1 = el->dof[node][n0+1];

    cdof = el->child[0]->dof[node][n0+1];
    drv->vec[cdof] = 0.25 * (drv->vec[pdof0] + drv->vec[pdof1]);

    cdof = el->child[0]->dof[node][n0+0];
    drv->vec[cdof] = 0.5 * drv->vec[pdof0];
    cdof = el->child[1]->dof[node][n0+1];
    drv->vec[cdof] = 0.5 * drv->vec[pdof1];
    break;
  }
  case 2: {
    DOF cdof, pdof0, pdof1, pdof2;
    int i;
    
    for (i = 0; i < n; i++) {
      el = rclist[i].el_info.el;

      pdof0 = el->dof[node][n0+0];
      pdof1 = el->dof[node][n0+1];
      pdof2 = el->dof[node][n0+2];

      cdof = el->child[0]->dof[node][n0+0];
      drv->vec[cdof] = 0.5 * drv->vec[pdof2];
      cdof = el->child[0]->dof[node][n0+1];
      drv->vec[cdof] = 0.5 * drv->vec[pdof0];
      cdof = el->child[0]->dof[node][n0+2];
      drv->vec[cdof] = 0.25 * (drv->vec[pdof0] + drv->vec[pdof1]);

      cdof = el->child[1]->dof[node][n0+0];
      drv->vec[cdof] = 0.5 * drv->vec[pdof1];
      cdof = el->child[1]->dof[node][n0+1];
      drv->vec[cdof] = 0.5 * drv->vec[pdof2];
      cdof = el->child[1]->dof[node][n0+2];
      drv->vec[cdof] = 0.25 * (drv->vec[pdof0] + drv->vec[pdof1]);
    }
    break;
  }
  default:
    break;
  }
}

static void bttb_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;
  EL *el; 

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

  switch (self->dim) {
  case 1: {
    DOF cdof, pdof0, pdof1;

    el = rclist->el_info.el;

    pdof0 = el->dof[node][n0+0];
    pdof1 = el->dof[node][n0+1];

    cdof = el->child[0]->dof[node][n0+1];

    cdof = el->child[0]->dof[node][n0+0];
    drv->vec[pdof0] = 2.0 * drv->vec[cdof];
    cdof = el->child[1]->dof[node][n0+1];
    drv->vec[pdof1] = 2.0 * drv->vec[cdof];
    break;
  }
  case 2: {
    DOF cdof, pdof0, pdof1, pdof2;
    int i;
    
    for (i = 0; i < n; i++) {
      el = rclist[i].el_info.el;

      pdof0 = el->dof[node][n0+0];
      pdof1 = el->dof[node][n0+1];
      pdof2 = el->dof[node][n0+2];

      cdof = el->child[0]->dof[node][n0+0];
      drv->vec[pdof2] = drv->vec[cdof];
      cdof = el->child[0]->dof[node][n0+1];
      drv->vec[pdof0] = 2.0 * drv->vec[cdof];

      cdof = el->child[1]->dof[node][n0+0];
      drv->vec[pdof1] = 2.0 * drv->vec[pdof1];
      cdof = el->child[1]->dof[node][n0+1];
      drv->vec[pdof2] += drv->vec[cdof];
    }
    break;
  }
  default:
    break;
  }
}

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

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

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

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

const BAS_FCTS *get_bulk_trace_tensor_bubbles(unsigned int dim,
					      unsigned int tensor_deg,
					      unsigned int inter_deg,
					      int trace_id)
{
  FUNCNAME("get_tensor_wall_bubbles");
  static BAS_FCTS *bttb_bfcts[DIM_MAX+1][MAX_TENSOR_DEG+1][MAX_INTER_DEG+1];
  BAS_FCTS *bfcts;
  BTTB_DATA *data;
  
  if (tensor_deg == 0) {
    return get_bulk_trace_bubble(dim, inter_deg, trace_id);
  }

  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 = bttb_bfcts[tensor_deg][inter_deg][dim]) == NULL) {
    char name[sizeof("BulkTraceTensorBubbles@XX_TX_IXX_Xd")];

    sprintf(name, "BulkTraceTensorBubbles@%02d_T%d_I%02d_%dd",
	    trace_id, tensor_deg, inter_deg, dim);

    bttb_bfcts[dim][tensor_deg][inter_deg] = bfcts = MEM_CALLOC(1, BAS_FCTS);
    data = bfcts->ext_data = MEM_CALLOC(1, BTTB_DATA);
    data->n_wall_bfcts = N_BAS_LAGRANGE(tensor_deg, dim-1);

    bfcts->name   = strdup(name);
    bfcts->dim    = dim;
    bfcts->rdim   = DIM_OF_WORLD;
    bfcts->degree = N_LAMBDA(dim-1)+tensor_deg;
    bfcts->n_bas_fcts = 0;
    bfcts->n_bas_fcts_max = N_WALLS(dim)*data->n_wall_bfcts;
    bfcts->n_dof[CENTER] = data->n_wall_bfcts;
    bfcts->trace_admin = trace_id;
    CHAIN_INIT(bfcts);
    bfcts->unchained = bfcts;
    bfcts->phi     = data->phi;
    bfcts->grd_phi = data->grd_phi;
    bfcts->D2_phi  = data->D2_phi;
    bfcts->phi_d   = data->phi_d;
    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] = data->n_wall_bfcts;
	for (t = 0; t < 2; t++) {
	  for (o = 0; o < 2; o++) {
	    bfcts->trace_dof_map[t][o][w] = data->trace_dof_map[w];
	  }
	}
      }
    } else {
      bfcts->trace_bas_fcts = get_null_bfcts(0);
    }
    bfcts->get_dof_indices = bttb_get_dof_indices;
    switch (dim) {
    case 1:
      bfcts->get_bound = bttb_get_bound_1d;
      break;
#if DIM_MAX > 1
    case 2:
      bfcts->get_bound = bttb_get_bound_2d;
      break;
#endif
#if DIM_MAX > 2
    case 3:
      bfcts->get_bound = bttb_get_bound_3d;
      break;
#endif
    }
    bfcts->interpol = bttb_interpol;
    bfcts->interpol_d = NULL;
    bfcts->interpol_dow = bttb_interpol_dow;
    bfcts->dir_pw_const = true;

    bfcts->get_int_vec     = bttb_get_int_vec;
    bfcts->get_real_vec    = bttb_get_real_vec;
    bfcts->get_real_d_vec  = bttb_get_real_d_vec;
    bfcts->get_real_dd_vec = bttb_get_real_dd_vec;
    bfcts->get_real_vec_d  = bttb_get_real_vec_d;
    bfcts->get_uchar_vec   = bttb_get_uchar_vec;
    bfcts->get_schar_vec   = bttb_get_schar_vec;
    bfcts->get_ptr_vec     = bttb_get_ptr_vec;

    bfcts->real_refine_inter =
      (void  (*)(DOF_REAL_VEC *, RC_LIST_EL *, int))
      bttb_real_refine_inter_d;
    bfcts->real_coarse_inter =
      (void  (*)(DOF_REAL_VEC *, RC_LIST_EL *, int))
      bttb_real_coarse_inter_d;
    bfcts->real_coarse_restr =
      (void  (*)(DOF_REAL_VEC *, RC_LIST_EL *, int))
      bttb_real_coarse_restr_d;

    bfcts->real_refine_inter_d =
      bttb_real_refine_inter_d;
    bfcts->real_coarse_inter_d =
      bttb_real_coarse_inter_d;
    bfcts->real_coarse_restr_d =
      bttb_real_coarse_restr_d;

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

    data->trace_mesh = NULL;
    data->trace_id   = trace_id;
    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;
}

