/**@file 
 *
 * Bubble basis functions, meant to be chained with some standard
 * Lagrange space, optionally with divergence preserving interpolation
 * routine.
 *
 *      author:  Claus-Justus Heine
 *               Abteilung fuer Angewandte Mathematik                     
 *               Albert-Ludwigs-Universitaet Freiburg
 *               Hermann-Herder-Str. 10
 *               79104 Freiburg i.Br.
 *               Germany
 *               claus@mathematik.uni-freiburg.de
 *
 * Copyright (C) by Claus-Justus Heine (2008).
 *
 */

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

#include <alberta.h>

#include "albas.h"

#define N_BAS_BUBBLE 1

/* Define _PROPER_ weights for the basis functions, s.t. there _mass_ is
 * normalized.
 */

#define WEIGHT_0D (1.0)
#define WEIGHT_1D (6.0)
#define WEIGHT_2D (120.0)
#define WEIGHT_3D (5040.0)

typedef struct bubble_data 
{
  int inter_deg; /* Quadrature degree for mean-value preserving
		  * interpolation in conjunction with a standard
		  * Lagrange space.
		  */
  const QUAD      *quad;  /* for interpolation */
  const QUAD_FAST *qfast; /* for interpolation */
} BUBBLE_DATA;

static REAL phic_0d(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  return WEIGHT_0D;
}

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

static REAL phic_2d(const REAL_B lambda, const BAS_FCTS *bfcts)
{
#if DIM_MAX > 1 
  return WEIGHT_2D*lambda[0]*lambda[1]*lambda[2];
#else
  return 0.0;
#endif
}

static REAL phic_3d(const REAL_B lambda, const BAS_FCTS *bfcts)
{
#if DIM_MAX > 2
  return WEIGHT_3D*lambda[0]*lambda[1]*lambda[2]*lambda[3];
#else
  return 0.0;
#endif
}

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

static const REAL *grd_phic_1d(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  static REAL_B grd;

  grd[0] = WEIGHT_1D*lambda[1];
  grd[1] = WEIGHT_1D*lambda[2];

  return (const REAL *)grd;
}

static const REAL *grd_phic_2d(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  static REAL_B grd;

#if DIM_MAX > 1
  grd[0] = WEIGHT_2D*lambda[1]*lambda[2];
  grd[1] = WEIGHT_2D*lambda[0]*lambda[2];
  grd[2] = WEIGHT_2D*lambda[0]*lambda[1];
#endif

  return (const REAL *)grd;
}

static const REAL *grd_phic_3d(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  static REAL_B grd;

#if DIM_MAX > 2
  grd[0] = WEIGHT_3D*lambda[1]*lambda[2]*lambda[3];
  grd[1] = WEIGHT_3D*lambda[0]*lambda[2]*lambda[3];
  grd[2] = WEIGHT_3D*lambda[0]*lambda[1]*lambda[3];
  grd[3] = WEIGHT_3D*lambda[0]*lambda[1]*lambda[2];
#endif

  return (const REAL *)grd;
}

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

static const REAL_B *D2_phic_1d(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  static const REAL_BB D2 = { [0][1] = WEIGHT_1D, [1][0] = WEIGHT_1D };
  
  return (const REAL_B *)D2;
}

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

#if DIM_MAX > 1
  D2[0][1] = D2[1][0] = WEIGHT_2D*lambda[2];
  D2[0][2] = D2[2][0] = WEIGHT_2D*lambda[1];
  D2[1][2] = D2[2][1] = WEIGHT_2D*lambda[0];
#endif

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

#if DIM_MAX > 2
  D2[0][1] = D2[1][0] = WEIGHT_3D*lambda[2]*lambda[3];
  D2[0][2] = D2[2][0] = WEIGHT_3D*lambda[1]*lambda[3];
  D2[0][3] = D2[3][0] = WEIGHT_3D*lambda[1]*lambda[2];
  D2[1][2] = D2[2][1] = WEIGHT_3D*lambda[0]*lambda[3];
  D2[1][3] = D2[3][1] = WEIGHT_3D*lambda[0]*lambda[2];
  D2[2][3] = D2[3][2] = WEIGHT_3D*lambda[0]*lambda[1];
#endif

  return (const REAL_B *)D2;
}

/******************************************************************************/

#undef DEF_EL_VEC_BUBBLE
#define DEF_EL_VEC_BUBBLE(type, name)				\
  DEF_EL_VEC_CONST(type, name, N_BAS_BUBBLE, N_BAS_BUBBLE)

#undef DEFUN_GET_EL_VEC_BUBBLE
#define DEFUN_GET_EL_VEC_BUBBLE(name, type, admin, body, ...)		\
  static const EL_##type##_VEC *					\
  bubble_get_##name(type##_VEC_TYPE *vec, const EL *el, __VA_ARGS__)	\
  {									\
    FUNCNAME("bubble_get_"#name);					\
    static DEF_EL_VEC_BUBBLE(type, rvec_space);				\
    type##_VEC_TYPE *rvec = vec ? vec : rvec_space->vec;		\
    int n0, node, ibas;							\
    DOF **dofptr = el->dof, dof;					\
									\
    DEBUG_TEST_EXIT(true, "");						\
									\
    node = (admin)->mesh->node[CENTER];					\
    n0   = (admin)->n0_dof[CENTER];					\
    dof = dofptr[node][n0]; ibas = 0;					\
    body;								\
									\
    return vec ? NULL : rvec_space;					\
  }									\
  struct _AI_semicolon_dummy

#undef DEFUN_GET_EL_DOF_VEC_BUBBLE
#define DEFUN_GET_EL_DOF_VEC_BUBBLE(name, type, ASSIGN)			\
  DEFUN_GET_EL_VEC_BUBBLE(_##name##_vec, type, dv->fe_space->admin,	\
			  ASSIGN(dv->vec[dof], rvec[ibas]),		\
			  const DOF_##type##_VEC *dv);			\
  static const EL_##type##_VEC *					\
  bubble_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 bubble_get__##name##_vec(vec, el, dv);			\
    } else {								\
      bubble_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_BUBBLE(dof_indices, DOF, admin,
			rvec[ibas] = dof,
			const DOF_ADMIN *admin, const BAS_FCTS *thisptr);

static const EL_BNDRY_VEC *
bubble_get_bound_1d(BNDRY_FLAGS *vec,
		    const EL_INFO *el_info, const BAS_FCTS *thisptr)
{
  static DEF_EL_VEC_BUBBLE(BNDRY, rvec_space);
  BNDRY_FLAGS *rvec = vec ? vec : rvec_space->vec;

  BNDRY_FLAGS_CPY(rvec[0], el_info->edge_bound[0]);

  return vec ? NULL : rvec_space;
}

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

  BNDRY_FLAGS_INIT(rvec[0]);
  BNDRY_FLAGS_SET(rvec[0], el_info->face_bound[0]);

  return vec ? NULL : rvec_space;
}
#endif

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

  BNDRY_FLAGS_INIT(rvec[0]);

  return vec ? NULL : rvec_space;
}
#endif

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

DEFUN_GET_EL_DOF_VEC_BUBBLE(int, INT, COPY_EQ);

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

DEFUN_GET_EL_DOF_VEC_BUBBLE(real, REAL, COPY_EQ);

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

DEFUN_GET_EL_DOF_VEC_BUBBLE(real_d, REAL_D, COPY_DOW);

static const EL_REAL_VEC_D *
bubble_get_real_vec_d(REAL result[], const EL *el, const DOF_REAL_VEC_D *dv)
{
  return (const EL_REAL_VEC_D *)
    bubble_get_real_d_vec((REAL_D *)result, el, (const DOF_REAL_D_VEC *)dv);
}

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

DEFUN_GET_EL_DOF_VEC_BUBBLE(schar, SCHAR, COPY_EQ);

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

DEFUN_GET_EL_DOF_VEC_BUBBLE(uchar, UCHAR, COPY_EQ);

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

DEFUN_GET_EL_DOF_VEC_BUBBLE(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_BUBBLE(real_dd, REAL_DD, MYMCOPY);

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

static void bubble_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)
{
/* const EL_REAL_VEC *other_ipol = NULL; */

  /* We have (R = reference element)
   *
   *                d!            1
   * \int_R B = -------- |R| = -------
   *            (2d+1)!        (2d+1)!
   *
   * 1d: 1/3! = 1/6
   * 2d: 1/5! = 1/120
   * 3d: 1/7! = 1/5040
   * 
   * To define an interpolation which preserves (in conjunction with
   * some standard Lagrangian FE-space) the mean-value of f, the value
   * for the coefficient for the bubble would be
   *
   * (\int_h(f) - \sum_i f(a_i)/NV) * (2d+1)!
   *
   * where a_i are the Lagrange nodes, \int_h(f) some numerical
   * integration for f.
   */
   
  REAL val;
  int iq;
  BUBBLE_DATA *data = (BUBBLE_DATA *)thisptr->ext_data;
  const QUAD  *quad = data->quad;
  const QUAD_FAST *quad_fast;
  REAL f_res, u_res;

  val = 0.0;
  vec->vec[0] = 0.0;
  if (data->qfast->bas_fcts != thisptr) {
    data->qfast = get_quad_fast(thisptr, quad, INIT_PHI);
    INIT_ELEMENT(el_info, data->qfast);
  }
  quad_fast = data->qfast;
  for (iq = 0; iq < quad->n_points; iq++) {
    f_res = f(el_info, quad, iq, f_data);
    u_res = eval_uh_fast(vec, quad_fast, iq);

    val += quad->w[iq] * (f_res - u_res);
  }

  vec->vec[0] = val;
}

static void bubble_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)
{
  int iq;
  BUBBLE_DATA *data = (BUBBLE_DATA *)thisptr->ext_data;
  const QUAD  *quad = data->quad;
  const QUAD_FAST *quad_fast;
  REAL_D f_res;
  REAL_D uh_res;
  REAL_D val;

  SET_DOW(0.0, vec->vec);
  SET_DOW(0.0, val);
  if (data->qfast->bas_fcts != thisptr) {
    data->qfast = get_quad_fast(thisptr, quad, INIT_PHI);
    INIT_ELEMENT(el_info, data->qfast);
  }
  quad_fast = data->qfast;
  for (iq = 0; iq < quad->n_points; iq++) {
    f(f_res, el_info, quad, iq, f_data);
    eval_uh_dow_fast(uh_res, vec, quad_fast, iq);
    
    AXPBYP_DOW(+quad->w[iq], f_res, -quad->w[iq], uh_res, val);
  }
  
  COPY_DOW(val, vec->vec);
}

static void bubble_interpol_d(EL_REAL_D_VEC *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)
{
  bubble_interpol_dow(
    (EL_REAL_VEC_D *)vec, el_info, wall, no, b_no, f, f_data, thisptr);
}

static void
bubble_real_refine_inter(DOF_REAL_VEC *drv, RC_LIST_EL *list, int n)
{
  /*FUNCNAME("bubble_real_refine_inter");*/
  EL  *el;
  int cdof, pdof, node0, n0, i;

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

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

    pdof = el->dof[node0][n0];
    cdof = el->child[0]->dof[node0][n0];
    drv->vec[cdof] = /*0.5 **/ drv->vec[pdof];
    cdof = el->child[1]->dof[node0][n0];
    drv->vec[cdof] = /*0.5 **/ drv->vec[pdof];
  }
}
static void bubble_real_coarse_inter(DOF_REAL_VEC *drv, RC_LIST_EL *list, int n)
{
  /*FUNCNAME("bubble_real_coarse_inter");*/
  EL  *el;
  int cdof0, cdof1, pdof, node0, n0, i;

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

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

    pdof = el->dof[node0][n0];
    cdof0 = el->child[0]->dof[node0][n0];
    cdof1 = el->child[1]->dof[node0][n0];

    drv->vec[pdof] = 0.5 * (drv->vec[cdof0] + drv->vec[cdof1]);
  }
}

static void bubble_real_coarse_restr(DOF_REAL_VEC *drv, RC_LIST_EL *list, int n)
{
  /*FUNCNAME("bubble_real_coarse_restr");*/
  EL  *el;
  int cdof0, cdof1, pdof, node0, n0, i;

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

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

    pdof = el->dof[node0][n0];
    cdof0 = el->child[0]->dof[node0][n0];
    cdof1 = el->child[1]->dof[node0][n0];

    drv->vec[pdof] = 0.5 * (drv->vec[cdof0] + drv->vec[cdof1]);
  }
}

static void bubble_real_d_refine_inter(DOF_REAL_D_VEC *drv, RC_LIST_EL *list,
				       int n)
{
  /*FUNCNAME("bubble_real_d_refine_inter");*/
  EL  *el;
  int cdof, pdof, node0, n0, i;

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

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

    pdof = el->dof[node0][n0];
    cdof = el->child[0]->dof[node0][n0];
    COPY_DOW(drv->vec[pdof], drv->vec[cdof]);
    cdof = el->child[1]->dof[node0][n0];
    COPY_DOW(drv->vec[pdof], drv->vec[cdof]);
  }
}

static void bubble_real_d_coarse_inter(DOF_REAL_D_VEC *drv,
				       RC_LIST_EL *list, int n)
{
  /*FUNCNAME("bubble_real_d_coarse_inter");*/
  EL      *el;
  int     cdof0, cdof1, pdof, node0, n0, i;

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

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

    pdof = el->dof[node0][n0];
    cdof0 = el->child[0]->dof[node0][n0];
    cdof1 = el->child[1]->dof[node0][n0];

    AXPBY_DOW(0.5, drv->vec[cdof0], 0.5, drv->vec[cdof1], drv->vec[pdof]);
  }
}

static void bubble_real_d_coarse_restr(DOF_REAL_D_VEC *drv,
				       RC_LIST_EL *list, int n)
{
  /*FUNCNAME("bubble_real_d_coarse_restr");*/
  EL      *el;
  int     cdof0, cdof1, pdof, node0, n0, i;

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

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

    pdof = el->dof[node0][n0];
    cdof0 = el->child[0]->dof[node0][n0];
    cdof1 = el->child[1]->dof[node0][n0];

    AXPBY_DOW(0.5, drv->vec[cdof0], 0.5, drv->vec[cdof1], drv->vec[pdof]);
  }
}

static const BAS_FCT bubble_phi[DIM_LIMIT+1][1] = {
  { phic_0d },
  { phic_1d },
  { phic_2d },
  { phic_3d }
};

static const GRD_BAS_FCT bubble_grd_phi[DIM_LIMIT+1][1] = {
  { grd_phic_0d },
  { grd_phic_1d },
  { grd_phic_2d },
  { grd_phic_3d }
};

static const D2_BAS_FCT bubble_D2_phi[DIM_LIMIT+1][1] = {
  { D2_phic_0d },
  { D2_phic_1d },
  { D2_phic_2d },
  { D2_phic_3d }
};

#define MAX_BUBBLE_INTER_DEG 9

const BAS_FCTS *get_bubble(unsigned int dim, unsigned int inter_deg)
{
  FUNCNAME("get_bubble");
  static BAS_FCTS *bubble_fcts[DIM_MAX+1][MAX_BUBBLE_INTER_DEG+1];
  
  TEST_EXIT(dim <= DIM_MAX, "dim = %d > DIM_MAX = %d.\n", dim, DIM_MAX);
 
  if (inter_deg > MAX_BUBBLE_INTER_DEG) {
    WARNING("Truncating quad-degree from %d to %d.\n",
	    inter_deg, MAX_BUBBLE_INTER_DEG);
    inter_deg = MAX_BUBBLE_INTER_DEG;
  }
  
  if (bubble_fcts[inter_deg][dim] == NULL) {
    char name[sizeof("Bubble_IXX_Xd")];
    BUBBLE_DATA *data;

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

    bubble_fcts[dim][inter_deg] = MEM_CALLOC(1, BAS_FCTS);
    bubble_fcts[dim][inter_deg]->name = strdup(name);
    bubble_fcts[dim][inter_deg]->dim  = dim;
    bubble_fcts[dim][inter_deg]->rdim = 1;
    bubble_fcts[dim][inter_deg]->degree = N_LAMBDA(dim);
    bubble_fcts[dim][inter_deg]->n_dof[CENTER] = 1;
    bubble_fcts[dim][inter_deg]->trace_admin = -1;
    bubble_fcts[dim][inter_deg]->n_bas_fcts = 
      bubble_fcts[dim][inter_deg]->n_bas_fcts_max = 1;
    CHAIN_INIT(bubble_fcts[dim][inter_deg]);
    bubble_fcts[dim][inter_deg]->unchained = bubble_fcts[dim][inter_deg];
    bubble_fcts[dim][inter_deg]->phi     = bubble_phi[dim];
    bubble_fcts[dim][inter_deg]->grd_phi = bubble_grd_phi[dim];
    bubble_fcts[dim][inter_deg]->D2_phi  = bubble_D2_phi[dim];
    bubble_fcts[dim][inter_deg]->trace_bas_fcts = get_null_bfcts(MAX(0, dim-1));
    bubble_fcts[dim][inter_deg]->get_dof_indices = bubble_get_dof_indices;
    switch (dim) {
    case 1:
      bubble_fcts[dim][inter_deg]->get_bound = bubble_get_bound_1d;
      break;
#if DIM_MAX > 1
    case 2:
      bubble_fcts[dim][inter_deg]->get_bound = bubble_get_bound_2d;
      break;
#endif
#if DIM_MAX > 2
    case 3:
      bubble_fcts[dim][inter_deg]->get_bound = bubble_get_bound_3d;
      break;
#endif
    }
    bubble_fcts[dim][inter_deg]->interpol = bubble_interpol;
    bubble_fcts[dim][inter_deg]->interpol_d = bubble_interpol_d;
    bubble_fcts[dim][inter_deg]->interpol_dow = bubble_interpol_dow;
    bubble_fcts[dim][inter_deg]->get_int_vec = bubble_get_int_vec;
    bubble_fcts[dim][inter_deg]->get_real_vec = bubble_get_real_vec;
    bubble_fcts[dim][inter_deg]->get_real_d_vec = bubble_get_real_d_vec;
    bubble_fcts[dim][inter_deg]->get_real_dd_vec = bubble_get_real_dd_vec;
    bubble_fcts[dim][inter_deg]->get_real_vec_d = bubble_get_real_vec_d;
    bubble_fcts[dim][inter_deg]->get_uchar_vec = bubble_get_uchar_vec;
    bubble_fcts[dim][inter_deg]->get_schar_vec = bubble_get_schar_vec;
    bubble_fcts[dim][inter_deg]->get_ptr_vec = bubble_get_ptr_vec;

    bubble_fcts[dim][inter_deg]->real_refine_inter = bubble_real_refine_inter;
    bubble_fcts[dim][inter_deg]->real_coarse_inter = bubble_real_coarse_inter;
    bubble_fcts[dim][inter_deg]->real_coarse_restr = bubble_real_coarse_restr;

    bubble_fcts[dim][inter_deg]->real_d_refine_inter =
      bubble_real_d_refine_inter;
    bubble_fcts[dim][inter_deg]->real_d_coarse_inter =
      bubble_real_d_coarse_inter;
    bubble_fcts[dim][inter_deg]->real_d_coarse_restr =
      bubble_real_d_coarse_restr;

    bubble_fcts[dim][inter_deg]->real_refine_inter_d =
      (REF_INTER_FCT_D)bubble_real_d_refine_inter;
    bubble_fcts[dim][inter_deg]->real_coarse_inter_d =
      (REF_INTER_FCT_D)bubble_real_d_coarse_inter;
    bubble_fcts[dim][inter_deg]->real_coarse_restr_d =
      (REF_INTER_FCT_D)bubble_real_d_coarse_restr;
    
    bubble_fcts[dim][inter_deg]->ext_data = data = MEM_ALLOC(1, BUBBLE_DATA);
    data->inter_deg = inter_deg;
    data->quad = get_quadrature(dim, inter_deg);
    data->qfast =
      get_quad_fast(bubble_fcts[dim][inter_deg], data->quad, INIT_PHI);
  }
  
  return bubble_fcts[dim][inter_deg];
}

