/*******************************************************************************
 *
 * ALBERTA:  an Adaptive multi Level finite element toolbox using           
 *           Bisectioning refinement and Error control by Residual          
 *           Techniques for scientific Applications                         
 *                                                                          
 * www.alberta-fem.de                                                       
 *                                                                          
 * file:     assemble_wall.c
 *                                                                          
 * description:  assembling of contributions across interior walls, meant as
 *               support for DG methods.
 *               
 *                                                                          
 *******************************************************************************
 *                                                                          
 * This file's authors: Claus-Justus Heine                                  
 *                      Abteilung fuer Angewandte Mathematik
 *                      Universitaet Freiburg
 *                      Hermann-Herder-Str. 10
 *                      79104 Freiburg, Germany                             
 *                                                                          
 * (c) by C.-J. Heine (2009-2009)
 *                                                                          
 ******************************************************************************/

#include "alberta_intern.h"
#include "alberta.h"
#include "assemble_bndry.h"

#if !HAVE_ROW_FCTS_V_TYPE && !HAVE_COL_FCTS_V_TYPE
# define VEC_PFX SS
# define EMIT_SS_VERSIONS 1
#elif HAVE_ROW_FCTS_V_TYPE && HAVE_COL_FCTS_V_TYPE
# define VEC_PFX VV
# define EMIT_VV_VERSIONS 1
#elif HAVE_ROW_FCTS_V_TYPE && HAVE_COL_FCTS_C_TYPE
# define VEC_PFX VC
# define EMIT_VC_VERSIONS 1
#elif HAVE_ROW_FCTS_C_TYPE && HAVE_COL_FCTS_V_TYPE
# define VEC_PFX CV
# define EMIT_CV_VERSIONS 1
#elif !HAVE_M_DST_TYPE && HAVE_ROW_FCTS_V_TYPE && !HAVE_COL_FCTS_V_TYPE
# define VEC_PFX VS
# define EMIT_VS_VERSIONS 1
#elif !HAVE_M_DST_TYPE && !HAVE_ROW_FCTS_V_TYPE && HAVE_COL_FCTS_V_TYPE
# define VEC_PFX SV
# define EMIT_SV_VERSIONS 1
#else
# error impossible block-matrix combinations
#endif

#define HAVE_VSSV (EMIT_SV_VERSIONS || EMIT_VS_VERSIONS)

#define VNAME(base) _AI_CONCAT(VEC_PFX, _AI_CONCAT(_, base))

/* <<< el_wall_fcts lookup table */

#define N_TYPE_DEFUNS 6 /* MM, MDM, MSCM, DMDM, DMSCM, SCMSCM */

#if !HAVE_VSSV
extern AI_EL_WALL_FCT VNAME(MM_el_wall_fcts)[DIM_MAX+1][N_WALLS_MAX][5][16];
extern AI_EL_WALL_FCT VNAME(MDM_el_wall_fcts)[DIM_MAX+1][N_WALLS_MAX][5][16];
extern AI_EL_WALL_FCT VNAME(MSCM_el_wall_fcts)[DIM_MAX+1][N_WALLS_MAX][5][16];
#endif
extern AI_EL_WALL_FCT VNAME(DMDM_el_wall_fcts)[DIM_MAX+1][N_WALLS_MAX][5][16];
extern AI_EL_WALL_FCT VNAME(DMSCM_el_wall_fcts)[DIM_MAX+1][N_WALLS_MAX][5][16];
extern AI_EL_WALL_FCT VNAME(SCMSCM_el_wall_fcts)[DIM_MAX+1][N_WALLS_MAX][5][16];

/* This function is called with srctype <= dsttype, it computes the
 * index into the el_wall_fcts[] table.
 */
static inline unsigned int type_index(MATENT_TYPE dsttype,
				      MATENT_TYPE srctype)
{
  unsigned int dstnr, srcnr;
  
  switch (dsttype) {
  case MATENT_REAL_DD: dstnr = 0; break;
  case MATENT_REAL_D: dstnr = 1; break;
  case MATENT_REAL: dstnr = 2; break;
  default: dstnr = ~0; break;
  }
  switch (srctype) {
  case MATENT_REAL_DD: srcnr = 0; break;
  case MATENT_REAL_D: srcnr = 1; break;
  case MATENT_REAL: srcnr = 2; break;
  default: srcnr = ~0; break;
  }

#define N_TYPES 3
  /* just the difference between two arithmetic sums */
  return ((2*N_TYPES+1)*dstnr - SQR(dstnr))/2 + srcnr - dstnr;
#undef N_TYPES
}

/* >>> */

/* <<< element_matrix() definitions */

/* Poor C-programmers explicit template specialization. Further below
 * specific versions of the element matrices are emitted where those
 * flags are constant s.t. the compiler can optimize the code.
 *
 * Note that "MIXED" -- meaning differing ansatz- and test-space -- is
 * in some sense always true because we pair the test space on the
 * current element with the ansatz space on the neighbour element.
 */

#define ZERO_ORDER (1 << 0)
#define FRST_ORDER (1 << 1)
#define SCND_ORDER (1 << 2)
#define INIT_EL    (1 << 3)
#define OI_INIT_EL (1 << 4)
#define MIXED      (1 << 5)
#define TANGENTIAL (1 << 6)
#define PARTPARAM  (1 << 7) /* only used in get_fill_data() */

/* NOTE: these function just adds something to an existing
 * element-matrix, there is no BNDRY_EL_MATRIX_INFO().
 */


/* <<< init_element helper */

static inline void init_objects(BNDRY_FILL_INFO *info, int wall, U_CHAR mask)
{
  if (mask & INIT_EL) {
    if (mask & SCND_ORDER) {
      INIT_OBJECT(info->row_wquad_fast[2]);
    }
    if (mask & FRST_ORDER) {
      INIT_OBJECT(info->row_wquad_fast[1]);
    }
    if (mask & ZERO_ORDER) {
      INIT_OBJECT(info->row_wquad_fast[0]);
    }
    if (mask & MIXED) {
      if (mask & SCND_ORDER) {
	INIT_OBJECT(info->col_wquad_fast[2]);
      }
      if (mask & FRST_ORDER) {
	INIT_OBJECT(info->col_wquad_fast[1]);
      }
      if (mask & ZERO_ORDER) {
	INIT_OBJECT(info->col_wquad_fast[0]);
      }
    }

    /* Reallocate scl_el_mat if necessary */
    ROW_CHAIN_DO(info, BNDRY_FILL_INFO) {
      COL_CHAIN_DO(info, BNDRY_FILL_INFO) {
	if (mask & TANGENTIAL) {
	  info->row_fcts_trace_map[wall] =
	    info->op_info.row_fe_space->bas_fcts->trace_dof_map[0][0][wall];
	  info->n_trace_row_fcts[wall] =
	    info->op_info.row_fe_space->bas_fcts->n_trace_bas_fcts[wall];
	}

	if (info->scl_el_mat != NULL) {
	  int n_row_max =
	    info->op_info.row_fe_space->bas_fcts->n_bas_fcts_max;
	  int n_col_max =
	    info->op_info.col_fe_space->bas_fcts->n_bas_fcts_max;
	  if (n_row_max > info->n_row_max || n_col_max > info->n_col_max) {
#undef MAT_BODY
#define MAT_BODY(F, CC, C, S, TYPE)					\
	    MAT_FREE(info->el_mat, info->n_row_max, info->n_col_max, TYPE); \
	    info->scl_el_mat =						\
	      (void *)MAT_ALLOC(n_row_max, n_col_max, TYPE)
	    MAT_EMIT_BODY_SWITCH(info->krn_blk_type);
	    info->n_row_max = n_row_max;
	    info->n_col_max = n_col_max;
	  }
	}
      } COL_CHAIN_WHILE(info, BNDRY_FILL_INFO);
    } ROW_CHAIN_WHILE(info, BNDRY_FILL_INFO);
  }

  info->cur_el      = NULL;
  info->cur_el_info = NULL;
}

static inline INIT_EL_TAG
init_element(const EL_INFO *el_info, BNDRY_FILL_INFO *info, U_CHAR mask)
{  
  INIT_EL_TAG init_tag = INIT_EL_TAG_NONE;
  
  if (info->cur_el != el_info->el || info->cur_el_info != el_info) {
#if HAVE_ROW_FCTS_V_TYPE
    int iw;
#endif
    int dim = el_info->mesh->dim;

    ROW_CHAIN_DO(info, BNDRY_FILL_INFO) {
      COL_CHAIN_DO(info, BNDRY_FILL_INFO) {
	INIT_ELEMENT_SINGLE(el_info, info->op_info.row_fe_space->bas_fcts);
	info->el_mat->n_row = info->op_info.row_fe_space->bas_fcts->n_bas_fcts;

	if (mask & TANGENTIAL) {
	  int wall;
	  for (wall = 0; wall < N_WALLS(dim); ++wall) {
	    info->row_fcts_trace_map[wall] =
	      info->op_info.row_fe_space->bas_fcts->trace_dof_map[0][0][wall];
	    info->n_trace_row_fcts[wall] =
	      info->op_info.row_fe_space->bas_fcts->n_trace_bas_fcts[wall];
	  }
	}
      } COL_CHAIN_WHILE(info, BNDRY_FILL_INFO);
    } ROW_CHAIN_WHILE(info, BNDRY_FILL_INFO);

    if (mask & SCND_ORDER) {
      init_tag |= INIT_ELEMENT(el_info, info->row_wquad_fast[2]);
#if HAVE_ROW_FCTS_V_TYPE
      /* force the computation of the gradients */
      if (init_tag != INIT_EL_TAG_NULL) {
	for (iw = 0; iw < N_WALLS(dim); iw++) {
	  const QUAD_FAST *cache = info->row_wquad_fast[2]->quad_fast[iw];
	  CHAIN_DO(cache, const QUAD_FAST) {
	    if (!cache->bas_fcts->dir_pw_const) {
	      get_quad_fast_grd_phi_dow(cache);
	    }
	  } CHAIN_WHILE(cache, const QUAD_FAST);
	}
      }
#endif
    }
    if (mask & FRST_ORDER) {
      init_tag |= INIT_ELEMENT(el_info, info->row_wquad_fast[1]);
#if HAVE_ROW_FCTS_V_TYPE
      /* force the computation of the gradients and/or values */
      if (init_tag != INIT_EL_TAG_NULL) {
	for (iw = 0; iw < N_WALLS(dim); iw++) {
	  const QUAD_FAST *cache = info->row_wquad_fast[1]->quad_fast[iw];
	  CHAIN_DO(cache, const QUAD_FAST) {
	    if (!cache->bas_fcts->dir_pw_const) {
	      if (cache->init_flag & INIT_GRD_PHI) {
		get_quad_fast_grd_phi_dow(cache);
	      }
	      if (cache->init_flag & INIT_PHI) {
		get_quad_fast_phi_dow(cache);
	      }
	    }
	  } CHAIN_WHILE(cache, const QUAD_FAST);
	}
      }
#endif
    }
    if (mask & ZERO_ORDER) {
      init_tag |= INIT_ELEMENT(el_info, info->row_wquad_fast[0]);
#if HAVE_ROW_FCTS_V_TYPE
      /* force the computation of the gradients and/or values */
      if (init_tag != INIT_EL_TAG_NULL) {
	for (iw = 0; iw < N_WALLS(dim); iw++) {
	  const QUAD_FAST *cache = info->row_wquad_fast[0]->quad_fast[iw];
	  CHAIN_DO(cache, const QUAD_FAST) {
	    get_quad_fast_phi_dow(cache);
	  } CHAIN_WHILE(cache, const QUAD_FAST);
	}
      }
#endif
    }
    info->cur_el      = el_info->el;
    info->cur_el_info = el_info;
    if (init_tag == INIT_EL_TAG_NULL) {
      /* any none-NULL tag has set another bit in init_tag */
      return init_tag;
    }
  }
  return init_tag;
}

/* >>> */

/* <<< element_matrix_default */

static inline
const EL_MATRIX *VNAME(element_matrix_default)(const EL_INFO *el_info,
					       int wall,
					       const void *fill_info,
					       U_CHAR mask)
{
  BNDRY_FILL_INFO *info = (BNDRY_FILL_INFO *)fill_info;
  EL_INFO neigh_info[1];

  if (el_info == NULL) {
    init_objects(info, wall, mask);
    return NULL;
  }

  if (el_info->neigh[wall] == NULL) {
    return NULL;
  }

  if (mask & INIT_EL) {
    const EL_GEOM_CACHE *elgc;
    if (init_element(el_info, info, mask) == INIT_EL_TAG_NULL) {
      return NULL;
    }
    elgc = fill_el_geom_cache(el_info, FILL_EL_WALL_REL_ORIENTATION(wall));
    fill_neigh_el_info(neigh_info, el_info, wall, elgc->rel_orientation[wall]);
    INIT_ELEMENT(neigh_info, info->op_info.col_fe_space->bas_fcts);
  }

  ROW_CHAIN_DO(info, BNDRY_FILL_INFO) {
    COL_CHAIN_DO(info, BNDRY_FILL_INFO) {
      void **velmat = (void **)info->el_mat->data.real;
      int i, j;

      if ((mask & OI_INIT_EL)) {
	info->op_info.init_element(
	  el_info, wall, info->op_info.quad, info->op_info.user_data);
      }

      if (mask & INIT_EL) {
	info->el_mat->n_col = info->op_info.col_fe_space->bas_fcts->n_bas_fcts;
      }

#undef MAT_BODY
#define MAT_BODY(F, CC, C, S, TYPE)			\
      for(i = 0; i < info->el_mat->n_row; i++) 		\
	for (j = 0; j < info->el_mat->n_col; j++)	\
	  F##SET_DOW(0.0, info->el_mat->data.S[i][j]);
      MAT_EMIT_BODY_SWITCH(info->el_mat->type);

      if (mask & SCND_ORDER) {
	info->col_quad_fast[2] =
	  get_neigh_quad_fast(el_info, info->col_wquad_fast[2], wall);
	if (mask & INIT_EL) {
          if (info->col_quad_fast[2]) {
            INIT_ELEMENT_SINGLE(neigh_info, info->col_quad_fast[2]);
            info->dflt_second_order[wall](el_info, info, velmat);
          }
	} else {
          info->dflt_second_order[wall](el_info, info, velmat);
        }
      }
      if (mask & FRST_ORDER) {
	info->col_quad_fast[1] =
	  get_neigh_quad_fast(el_info, info->col_wquad_fast[1], wall);
	if (mask & INIT_EL) {
          if (info->col_quad_fast[1]) {
            INIT_ELEMENT_SINGLE(neigh_info, info->col_quad_fast[1]);
            info->dflt_first_order[wall](el_info, info, velmat);
          }
	} else {
          info->dflt_first_order[wall](el_info, info, velmat);
        }
      }
      if (mask & ZERO_ORDER) {
	info->col_quad_fast[0] =
	  get_neigh_quad_fast(el_info, info->col_wquad_fast[0], wall);
	if (mask & INIT_EL) {
          if (info->col_quad_fast[0]) {
            INIT_ELEMENT_SINGLE(neigh_info, info->col_quad_fast[0]);
            info->dflt_zero_order[wall](el_info, info, velmat);
          }
	} else {
          info->dflt_zero_order[wall](el_info, info, velmat);
        }
      }
    } COL_CHAIN_WHILE(info, BNDRY_FILL_INFO);
  } ROW_CHAIN_WHILE(info, BNDRY_FILL_INFO);

  return info->el_mat;
}

/* >>> */

/* <<< element_matrix_partparam */

static inline
const EL_MATRIX *VNAME(element_matrix_partparam)(const EL_INFO *el_info,
						 int wall,
						 const void *fill_info,
						 U_CHAR mask)
{
  BNDRY_FILL_INFO *info = (BNDRY_FILL_INFO *)fill_info;
  bool is_parametric = true;
  EL_INFO neigh_info[1];

  if (el_info == NULL) {
    init_objects(info, wall, mask);
    return NULL;
  }
  if (el_info->neigh[wall] == NULL) {
    return NULL;
  }

  if (info->cur_el != el_info->el || info->cur_el_info != el_info) {
    is_parametric = info->parametric->init_element(el_info, info->parametric);
  }

  if (mask & INIT_EL) {
    const EL_GEOM_CACHE *elgc;
    if (init_element(el_info, info, mask) == INIT_EL_TAG_NULL) {
      return NULL;
    }
    elgc = fill_el_geom_cache(el_info, FILL_EL_WALL_REL_ORIENTATION(wall));
    fill_neigh_el_info(neigh_info, el_info, wall, elgc->rel_orientation[wall]);
    INIT_ELEMENT(neigh_info, info->op_info.col_fe_space->bas_fcts);
  }

  ROW_CHAIN_DO(info, BNDRY_FILL_INFO) {
    COL_CHAIN_DO(info, BNDRY_FILL_INFO) {
      void **velmat = (void **)info->el_mat->data.real;
      int i, j;

      if (mask & OI_INIT_EL) {
	info->op_info.init_element(
	  el_info, wall, info->op_info.quad, info->op_info.user_data);
      }

      if (mask & INIT_EL) {
	info->el_mat->n_col = info->op_info.col_fe_space->bas_fcts->n_bas_fcts;
      }

#undef MAT_BODY
#define MAT_BODY(F, CC, C, S, TYPE)			\
      for(i = 0; i < info->el_mat->n_row; i++) 		\
	for (j = 0; j < info->el_mat->n_col; j++)	\
	  F##SET_DOW(0.0, info->el_mat->data.S[i][j]);
      MAT_EMIT_BODY_SWITCH(info->el_mat->type);

      if (mask & SCND_ORDER) {
	info->col_quad_fast[2] =
	  get_neigh_quad_fast(el_info, info->col_wquad_fast[2], wall);
	if (mask & INIT_EL) {
          if (info->col_quad_fast[2]) {
            INIT_ELEMENT_SINGLE(neigh_info, info->col_quad_fast[2]);
            if (!is_parametric && info->fast_second_order[wall]) {
              info->fast_second_order[wall](el_info, info, velmat);
            } else {
              info->dflt_second_order[wall](el_info, info, velmat);
            }
          }
	} else {
          if (!is_parametric && info->fast_second_order[wall]) {
            info->fast_second_order[wall](el_info, info, velmat);
          } else {
            info->dflt_second_order[wall](el_info, info, velmat);
          }
        }
      }
      if (mask & FRST_ORDER) {
	info->col_quad_fast[1] =
	  get_neigh_quad_fast(el_info, info->col_wquad_fast[1], wall);
	if (mask & INIT_EL) {
          if (info->col_quad_fast[1]) {
            INIT_ELEMENT_SINGLE(neigh_info, info->col_quad_fast[1]);
            if (!is_parametric && info->fast_first_order[wall]) {
              info->fast_first_order[wall](el_info, info, velmat);
            } else {
              info->dflt_first_order[wall](el_info, info, velmat);
            }
          }
	} else {
          if (!is_parametric && info->fast_first_order[wall]) {
            info->fast_first_order[wall](el_info, info, velmat);
          } else {
            info->dflt_first_order[wall](el_info, info, velmat);
          }
        }
      }
      if (mask & ZERO_ORDER) {
	info->col_quad_fast[0] =
	  get_neigh_quad_fast(el_info, info->col_wquad_fast[0], wall);
	if (mask & INIT_EL) {
          if (info->col_quad_fast[0]) {
            INIT_ELEMENT_SINGLE(neigh_info, info->col_quad_fast[0]);
            if (!is_parametric && info->fast_first_order[wall]) {
              info->fast_zero_order[wall](el_info, info, velmat);
            } else {
              info->dflt_zero_order[wall](el_info, info, velmat);
            }
          }
	} else {
          if (!is_parametric && info->fast_first_order[wall]) {
            info->fast_zero_order[wall](el_info, info, velmat);
          } else {
            info->dflt_zero_order[wall](el_info, info, velmat);
          }
        }
      }
    } COL_CHAIN_WHILE(info, BNDRY_FILL_INFO);
  } ROW_CHAIN_WHILE(info, BNDRY_FILL_INFO);

  return info->el_mat;
}

/* >>> */

/* <<< element matrix lookup-table */

/* We instantiate various versions of the element matrices where the
 * flags bits defined above are kept constant s.t. the compiler can
 * optimize the code for the various cases:
 *
 * #define ZERO_ORDER (1 << 0)
 * #define FRST_ORDER (1 << 1)
 * #define SCND_ORDER (1 << 2)
 * #define INIT_EL    (1 << 3)
 * #define OI_INIT_EL (1 << 4)
 * #define MIXED      (1 << 5)
 * #define TANGENTIAL (1 << 6)
 * #define PARTPARAM  (1 << 7)
 *
 */

#define ELEMENT_MATRIX_DEFAULT_FUN(wall, n2, n1)	\
  VNAME(element_matrix_default_##wall##_##n2##_##n1)
#define DEFUN_ELEMENT_MATRIX_DEFAULT(wall, n2, n1)			\
  static const EL_MATRIX * FLATTEN_ATTR					\
  ELEMENT_MATRIX_DEFAULT_FUN(wall, n2, n1)(const EL_INFO *el_info,	\
					   void *fill_info)		\
  {									\
    return VNAME(element_matrix_default)(el_info, wall, fill_info, n2|n1); \
  }									\
  struct _AI_semicolon_dummy
#if DIM_MAX == 1
# define DEFUNS_ELEMENT_MATRIX_DEFAULT(n2, n1)	\
  DEFUN_ELEMENT_MATRIX_DEFAULT(0, n2, n1);	\
  DEFUN_ELEMENT_MATRIX_DEFAULT(1, n2, n1)
#elif DIM_MAX == 2
# define DEFUNS_ELEMENT_MATRIX_DEFAULT(n2, n1)	\
  DEFUN_ELEMENT_MATRIX_DEFAULT(0, n2, n1);	\
  DEFUN_ELEMENT_MATRIX_DEFAULT(1, n2, n1);	\
  DEFUN_ELEMENT_MATRIX_DEFAULT(2, n2, n1)
#elif DIM_MAX == 3
# define DEFUNS_ELEMENT_MATRIX_DEFAULT(n2, n1)	\
  DEFUN_ELEMENT_MATRIX_DEFAULT(0, n2, n1);	\
  DEFUN_ELEMENT_MATRIX_DEFAULT(1, n2, n1);	\
  DEFUN_ELEMENT_MATRIX_DEFAULT(2, n2, n1);	\
  DEFUN_ELEMENT_MATRIX_DEFAULT(3, n2, n1)
#else
# error unsupported DIM_MAX
#endif

#define ELEMENT_MATRIX_PARTPARAM_FUN(wall, n2, n1)	\
  VNAME(element_matrix_partparm_##wall##_##n2##_##n1)
#define DEFUN_ELEMENT_MATRIX_PARTPARAM(wall, n2, n1)			\
  static const EL_MATRIX * FLATTEN_ATTR					\
  ELEMENT_MATRIX_PARTPARAM_FUN(wall, n2, n1)(const EL_INFO *el_info,	\
					     void *fill_info)		\
  {									\
    return VNAME(element_matrix_partparam)(el_info, wall, fill_info, n2|n1); \
  }									\
  struct _AI_semicolon_dummy
#if DIM_MAX == 1
# define DEFUNS_ELEMENT_MATRIX_PARTPARAM(n2, n1)	\
  DEFUN_ELEMENT_MATRIX_PARTPARAM(0, n2, n1);		\
  DEFUN_ELEMENT_MATRIX_PARTPARAM(1, n2, n1)
#elif DIM_MAX == 2
# define DEFUNS_ELEMENT_MATRIX_PARTPARAM(n2, n1)	\
  DEFUN_ELEMENT_MATRIX_PARTPARAM(0, n2, n1);		\
  DEFUN_ELEMENT_MATRIX_PARTPARAM(1, n2, n1);		\
  DEFUN_ELEMENT_MATRIX_PARTPARAM(2, n2, n1)
#elif DIM_MAX >= 3
# define DEFUNS_ELEMENT_MATRIX_PARTPARAM(n2, n1)	\
  DEFUN_ELEMENT_MATRIX_PARTPARAM(0, n2, n1);		\
  DEFUN_ELEMENT_MATRIX_PARTPARAM(1, n2, n1);		\
  DEFUN_ELEMENT_MATRIX_PARTPARAM(2, n2, n1);		\
  DEFUN_ELEMENT_MATRIX_PARTPARAM(3, n2, n1)
#else
# error unsupported DIM_MAX
#endif

/* Stuff for non-parametric meshes, or entirely parametric meshes, at
 * least one of the three least significant bits must be set, coding
 * for the presence of a zero, first, second order part in the
 * BNDRY_OPERATOR_INFO structure.
 */
#define DEFUNS_16_EL_MAT_DEFAULT(n2)		\
  /* 0x0 */					\
  DEFUNS_ELEMENT_MATRIX_DEFAULT(n2, 0x1);	\
  DEFUNS_ELEMENT_MATRIX_DEFAULT(n2, 0x2);	\
  DEFUNS_ELEMENT_MATRIX_DEFAULT(n2, 0x3);	\
  DEFUNS_ELEMENT_MATRIX_DEFAULT(n2, 0x4);	\
  DEFUNS_ELEMENT_MATRIX_DEFAULT(n2, 0x5);	\
  DEFUNS_ELEMENT_MATRIX_DEFAULT(n2, 0x6);	\
  DEFUNS_ELEMENT_MATRIX_DEFAULT(n2, 0x7);	\
  /* 0x8 */					\
  DEFUNS_ELEMENT_MATRIX_DEFAULT(n2, 0x9);	\
  DEFUNS_ELEMENT_MATRIX_DEFAULT(n2, 0xA);	\
  DEFUNS_ELEMENT_MATRIX_DEFAULT(n2, 0xB);	\
  DEFUNS_ELEMENT_MATRIX_DEFAULT(n2, 0xC);	\
  DEFUNS_ELEMENT_MATRIX_DEFAULT(n2, 0xD);	\
  DEFUNS_ELEMENT_MATRIX_DEFAULT(n2, 0xE);	\
  DEFUNS_ELEMENT_MATRIX_DEFAULT(n2, 0xF)

/* Instantiate the templates for each of the three least significant
 * bits of the upper nibble, partparam is implemented using a
 * different function template.
 */
DEFUNS_16_EL_MAT_DEFAULT(0x00);
DEFUNS_16_EL_MAT_DEFAULT(0x10);
DEFUNS_16_EL_MAT_DEFAULT(0x20);
DEFUNS_16_EL_MAT_DEFAULT(0x30);
DEFUNS_16_EL_MAT_DEFAULT(0x40);
DEFUNS_16_EL_MAT_DEFAULT(0x50);
DEFUNS_16_EL_MAT_DEFAULT(0x60);
DEFUNS_16_EL_MAT_DEFAULT(0x70);

#if DIM_MAX == 1
# define NULL_N_WALLS { NULL, NULL }
# define ELEMENT_MATRIX_DEFAULT_FUNS(n2, n1)	\
  { ELEMENT_MATRIX_DEFAULT_FUN(0, n2, n1),	\
      ELEMENT_MATRIX_DEFAULT_FUN(1, n2, n1) }
# define ELEMENT_MATRIX_PARTPARAM_FUNS(n2, n1)	\
  { ELEMENT_MATRIX_PARTPARAM_FUN(0, n2, n1),	\
      ELEMENT_MATRIX_PARTPARAM_FUN(1, n2, n1) }
#elif DIM_MAX == 2
# define NULL_N_WALLS { NULL, NULL, NULL }
# define ELEMENT_MATRIX_DEFAULT_FUNS(n2, n1)	\
  { ELEMENT_MATRIX_DEFAULT_FUN(0, n2, n1),	\
      ELEMENT_MATRIX_DEFAULT_FUN(1, n2, n1),	\
      ELEMENT_MATRIX_DEFAULT_FUN(2, n2, n1) }
# define ELEMENT_MATRIX_PARTPARAM_FUNS(n2, n1)	\
  { ELEMENT_MATRIX_PARTPARAM_FUN(0, n2, n1),	\
      ELEMENT_MATRIX_PARTPARAM_FUN(1, n2, n1),	\
      ELEMENT_MATRIX_PARTPARAM_FUN(2, n2, n1) }
#elif DIM_MAX == 3
# define NULL_N_WALLS { NULL, NULL, NULL, NULL }
# define ELEMENT_MATRIX_DEFAULT_FUNS(n2, n1)	\
  { ELEMENT_MATRIX_DEFAULT_FUN(0, n2, n1),	\
      ELEMENT_MATRIX_DEFAULT_FUN(1, n2, n1),	\
      ELEMENT_MATRIX_DEFAULT_FUN(2, n2, n1),	\
      ELEMENT_MATRIX_DEFAULT_FUN(3, n2, n1) }
# define ELEMENT_MATRIX_PARTPARAM_FUNS(n2, n1)	\
  { ELEMENT_MATRIX_PARTPARAM_FUN(0, n2, n1),	\
      ELEMENT_MATRIX_PARTPARAM_FUN(1, n2, n1),	\
      ELEMENT_MATRIX_PARTPARAM_FUN(2, n2, n1),	\
      ELEMENT_MATRIX_PARTPARAM_FUN(3, n2, n1) }
#else
# error unsupported DIM_MAX
#endif

/* Initializer for the look-up table. "n2" must loop form 0 -- 7. */
#define EL_MAT_16_DEFAULT_FUNS(n2)		\
  NULL_N_WALLS,					\
    ELEMENT_MATRIX_DEFAULT_FUNS(n2, 0x1),	\
    ELEMENT_MATRIX_DEFAULT_FUNS(n2, 0x2),	\
    ELEMENT_MATRIX_DEFAULT_FUNS(n2, 0x3),	\
    ELEMENT_MATRIX_DEFAULT_FUNS(n2, 0x4),	\
    ELEMENT_MATRIX_DEFAULT_FUNS(n2, 0x5),	\
    ELEMENT_MATRIX_DEFAULT_FUNS(n2, 0x6),	\
    ELEMENT_MATRIX_DEFAULT_FUNS(n2, 0x7),	\
    NULL_N_WALLS,				\
    ELEMENT_MATRIX_DEFAULT_FUNS(n2, 0x9),	\
    ELEMENT_MATRIX_DEFAULT_FUNS(n2, 0xA),	\
    ELEMENT_MATRIX_DEFAULT_FUNS(n2, 0xB),	\
    ELEMENT_MATRIX_DEFAULT_FUNS(n2, 0xC),	\
    ELEMENT_MATRIX_DEFAULT_FUNS(n2, 0xD),	\
    ELEMENT_MATRIX_DEFAULT_FUNS(n2, 0xE),	\
    ELEMENT_MATRIX_DEFAULT_FUNS(n2, 0xF)

/* Stuff for partly parametric meshes, at least one of the three least
 * significant bits must be set, coding for the presence of a zero,
 * first, second order part in the BNDRY_OPERATOR_INFO structure. The
 * OI_INIT_EL bit (bit 4) must be set.
 */
#define DEFUNS_16_EL_MAT_PARTPARAM(n2)		\
  /* 0x0 */					\
  DEFUNS_ELEMENT_MATRIX_PARTPARAM(n2, 0x1);	\
  DEFUNS_ELEMENT_MATRIX_PARTPARAM(n2, 0x2);	\
  DEFUNS_ELEMENT_MATRIX_PARTPARAM(n2, 0x3);	\
  DEFUNS_ELEMENT_MATRIX_PARTPARAM(n2, 0x4);	\
  DEFUNS_ELEMENT_MATRIX_PARTPARAM(n2, 0x5);	\
  DEFUNS_ELEMENT_MATRIX_PARTPARAM(n2, 0x6);	\
  DEFUNS_ELEMENT_MATRIX_PARTPARAM(n2, 0x7);	\
  /* 0x8 */					\
  DEFUNS_ELEMENT_MATRIX_PARTPARAM(n2, 0x9);	\
  DEFUNS_ELEMENT_MATRIX_PARTPARAM(n2, 0xA);	\
  DEFUNS_ELEMENT_MATRIX_PARTPARAM(n2, 0xB);	\
  DEFUNS_ELEMENT_MATRIX_PARTPARAM(n2, 0xC);	\
  DEFUNS_ELEMENT_MATRIX_PARTPARAM(n2, 0xD);	\
  DEFUNS_ELEMENT_MATRIX_PARTPARAM(n2, 0xE);	\
  DEFUNS_ELEMENT_MATRIX_PARTPARAM(n2, 0xF)

DEFUNS_16_EL_MAT_PARTPARAM(0x00);
DEFUNS_16_EL_MAT_PARTPARAM(0x10);
DEFUNS_16_EL_MAT_PARTPARAM(0x20);
DEFUNS_16_EL_MAT_PARTPARAM(0x30);
DEFUNS_16_EL_MAT_PARTPARAM(0x40);
DEFUNS_16_EL_MAT_PARTPARAM(0x50);
DEFUNS_16_EL_MAT_PARTPARAM(0x60);
DEFUNS_16_EL_MAT_PARTPARAM(0x70);

/* initializer for the lookup-table */
#define EL_MAT_16_PARTPARAM_FUNS(n2)		\
  NULL_N_WALLS,					\
    ELEMENT_MATRIX_PARTPARAM_FUNS(n2, 0x1),	\
    ELEMENT_MATRIX_PARTPARAM_FUNS(n2, 0x2),	\
    ELEMENT_MATRIX_PARTPARAM_FUNS(n2, 0x3),	\
    ELEMENT_MATRIX_PARTPARAM_FUNS(n2, 0x4),	\
    ELEMENT_MATRIX_PARTPARAM_FUNS(n2, 0x5),	\
    ELEMENT_MATRIX_PARTPARAM_FUNS(n2, 0x6),	\
    ELEMENT_MATRIX_PARTPARAM_FUNS(n2, 0x7),	\
    NULL_N_WALLS,				\
    ELEMENT_MATRIX_PARTPARAM_FUNS(n2, 0x9),	\
    ELEMENT_MATRIX_PARTPARAM_FUNS(n2, 0xA),	\
    ELEMENT_MATRIX_PARTPARAM_FUNS(n2, 0xB),	\
    ELEMENT_MATRIX_PARTPARAM_FUNS(n2, 0xC),	\
    ELEMENT_MATRIX_PARTPARAM_FUNS(n2, 0xD),	\
    ELEMENT_MATRIX_PARTPARAM_FUNS(n2, 0xE),	\
    ELEMENT_MATRIX_PARTPARAM_FUNS(n2, 0xF)

EL_MATRIX_FCT VNAME(neigh_el_mat_fct_table)[256][N_WALLS_MAX] =
{
  /* Non-parametric or entirely parametric meshes. */
  EL_MAT_16_DEFAULT_FUNS(0x00),
  EL_MAT_16_DEFAULT_FUNS(0x10),
  EL_MAT_16_DEFAULT_FUNS(0x20),
  EL_MAT_16_DEFAULT_FUNS(0x30),
  EL_MAT_16_DEFAULT_FUNS(0x40),
  EL_MAT_16_DEFAULT_FUNS(0x50),
  EL_MAT_16_DEFAULT_FUNS(0x60),
  EL_MAT_16_DEFAULT_FUNS(0x70),
  /* Partly parametric meshes. */
  EL_MAT_16_PARTPARAM_FUNS(0x00),
  EL_MAT_16_PARTPARAM_FUNS(0x10),
  EL_MAT_16_PARTPARAM_FUNS(0x20),
  EL_MAT_16_PARTPARAM_FUNS(0x30),
  EL_MAT_16_PARTPARAM_FUNS(0x40),
  EL_MAT_16_PARTPARAM_FUNS(0x50),
  EL_MAT_16_PARTPARAM_FUNS(0x60),
  EL_MAT_16_PARTPARAM_FUNS(0x70),
};

/* >>> */
  
/* >>> */

#if EMIT_SS_VERSIONS

/* lookup-tables for element function */
static EL_MATRIX_FCT
(*neigh_el_mat_fct_table[N_OP_BLOCK_TYPES])[N_WALLS_MAX] = {
  /* [OP_TYPE_SS] = */ SS_neigh_el_mat_fct_table,
#if VECTOR_BASIS_FUNCTIONS
  /* [OP_TYPE_SV] = */ SV_neigh_el_mat_fct_table,
  /* [OP_TYPE_VS] = */ VS_neigh_el_mat_fct_table,
  /* [OP_TYPE_CV] = */ CV_neigh_el_mat_fct_table,
  /* [OP_TYPE_VC] = */ VC_neigh_el_mat_fct_table,
  /* [OP_TYPE_VV] = */ VV_neigh_el_mat_fct_table
#endif
};

static bool unify_bop_info(BNDRY_OPERATOR_INFO *oinfo,
			   const BNDRY_OPERATOR_INFO *oi_orig,
			   const WALL_QUAD_TENSOR *quad_tensor[3],
			   const FE_SPACE *row_fe_space,
			   const FE_SPACE *col_fe_space,
			   MATENT_TYPE blk_type)
{
  const BAS_FCTS *row_fcts, *col_fcts;
  PARAMETRIC     *parametric = NULL;
  int            dim;
  int            row_fcts_deg, col_fcts_deg, max_deg, i;

  *oinfo = *oi_orig;
  for (i = 0; i < 3; i++) {
    oinfo->quad_tensor[i] = quad_tensor[i];
    if (quad_tensor[i]) {
      oinfo->quad[i] = quad_tensor[i]->quad;
    }
  }

  oinfo->row_fe_space = row_fe_space;
  oinfo->col_fe_space = col_fe_space;

  /* <<< consistency checks etc. */

  row_fcts = oinfo->row_fe_space->bas_fcts;
  col_fcts = oinfo->col_fe_space->bas_fcts;

  if(col_fcts->dim != row_fcts->dim) {
    ERROR("Support dimensions of col_fcts and row_fcts do not match!\n");
    ERROR("cannot initialize EL_MATRIX_INFO; returning NULL\n");
    return false;
  }

  dim = col_fcts->dim;

  row_fcts_deg = row_fcts->unchained->degree;
  col_fcts_deg = col_fcts->unchained->degree;

  parametric = oinfo->row_fe_space->mesh->parametric;

  if (!oinfo->c.real_dd &&
      !oinfo->Lb0.real_dd && !oinfo->Lb1.real_dd &&
      !oinfo->LALt.real_dd) {
    ERROR("no function for 2nd, 1st, and 0 order term;\n");
    ERROR("can not initialize EL_MATRIX_INFO; returning NULL\n");
    return false;
  }

  if (oinfo->LALt.real_dd == NULL) {
    /* reset all LALt related settings to zero */
    oinfo->LALt_type      = MATENT_REAL;
    oinfo->LALt_pw_const  = false;
    oinfo->LALt_symmetric = false;
    oinfo->LALt_degree    = 0;
    oinfo->quad[2]        = NULL;
    oinfo->quad_tensor[2] = NULL;    
  }
  if (oinfo->Lb0.real_dd == NULL) {
    oinfo->Lb0_pw_const = false;
  }
  if (oinfo->Lb1.real_dd == NULL) {
    oinfo->Lb1_pw_const = false;
  }
  if (oinfo->Lb0.real_dd == NULL && oinfo->Lb1.real_dd == NULL) {
    oinfo->Lb_type                = MATENT_REAL;
    oinfo->Lb0_Lb1_anti_symmetric = false;
    oinfo->Lb_degree              = 0;
    oinfo->advection_field        = NULL; /* unused, though */
    oinfo->adv_fe_space           = NULL; /* unused, though */
    oinfo->quad[1]                = NULL;
    oinfo->quad_tensor[1]         = NULL;    
  }
  if (oinfo->c.real_dd == NULL) {
    oinfo->c_pw_const     = false;
    oinfo->c_type         = MATENT_REAL;
    oinfo->c_degree       = 0;
    oinfo->quad[0]        = NULL;
    oinfo->quad_tensor[0] = NULL;    
  }

  if(parametric) {
    if(!oinfo->quad[0] && !oinfo->quad[1] && !oinfo->quad[2]) {
      ERROR("User is responsible for providing at least one quadrature\n");
      ERROR("when using a parametric mesh!\n");
      ERROR("can not initialize EL_MATRIX_INFO; returning NULL\n");
      return false;
    }
  }

  /* the element matrices are always non-symmetric because we pair the
   * test-space of the current element with the ansatz space on the
   * neighbour element.
   */
  oinfo->LALt_symmetric =
    oinfo->Lb0_Lb1_anti_symmetric = false;

  max_deg = 0;

  if (oinfo->c.real_dd && oinfo->quad[0] == NULL) {
    if (oinfo->c_pw_const) {
      oinfo->c_degree = 0;
    }
    max_deg = MAX(max_deg, row_fcts_deg + col_fcts_deg + oinfo->c_degree);
  }

  if ((oinfo->Lb0.real_dd || oinfo->Lb1.real_dd) && oinfo->quad[1] == NULL) {
    if (oinfo->Lb0_pw_const && oinfo->Lb1_pw_const) {
      oinfo->Lb_degree = 0;
    }
    max_deg = MAX(max_deg, row_fcts_deg + col_fcts_deg - 1 + oinfo->Lb_degree);
  }

  if (oinfo->LALt.real_dd && oinfo->quad[2] == NULL) {
    if (oinfo->LALt_pw_const) {
      oinfo->LALt_degree = 0;
    }
    max_deg =
      MAX(max_deg, row_fcts_deg + col_fcts_deg - 2 + oinfo->LALt_degree);
  }

#if 1
  if (oinfo->LALt.real_dd && !oinfo->quad[2]) {
    oinfo->quad[2] = get_wall_quad(dim, max_deg);
  } else if (!oinfo->LALt.real_dd) {
    oinfo->LALt_degree = 0;
    oinfo->quad[2]     = NULL;
  }

  if ((oinfo->Lb0.real_dd || oinfo->Lb1.real_dd) && !oinfo->quad[1]) {
    if ((!oinfo->Lb0_pw_const || !oinfo->Lb1_pw_const) && oinfo->quad[2]) {
      oinfo->quad[1] = oinfo->quad[2];
    } else {
      oinfo->quad[1] = get_wall_quad(dim, row_fcts_deg + col_fcts_deg - 1);
    }
  } else if (!oinfo->Lb0.real_dd && !oinfo->Lb1.real_dd) {
    oinfo->Lb_degree = 0;
    oinfo->quad[1]   = NULL;
  }

  if (oinfo->c.real_dd && !oinfo->quad[0]) {
    if (!oinfo->c_pw_const && oinfo->quad[2]) {
      oinfo->quad[0] = oinfo->quad[2];
    } else if (!oinfo->c_pw_const && oinfo->quad[1]) {
      oinfo->quad[0] = oinfo->quad[1];
    } else {
      oinfo->quad[0] = get_wall_quad(dim, row_fcts_deg + col_fcts_deg);
    }
  } else if (!oinfo->c.real_dd) {
    oinfo->c_degree = 0;
    oinfo->quad[0]  = NULL;
  }
#else
  if (oinfo->LALt.real_dd && !oinfo->quad[2]) {
    oinfo->quad[2] = get_wall_quad(dim, row_fcts_deg + col_fcts_deg - 2);
  } else if (!oinfo->LALt.real_dd) {
    oinfo->quad[2] = NULL;
  }

  if ((oinfo->Lb0.real_dd || oinfo->Lb1.real_dd) && !oinfo->quad[1]) {
    if ((!oinfo->Lb0_pw_const || !oinfo->Lb1_pw_const) && oinfo->quad[2]) {
      oinfo->quad[1] = oinfo->quad[2];
    } else {
      oinfo->quad[1] = get_wall_quad(dim, row_fcts_deg + col_fcts_deg - 1);
    }
  } else if (!oinfo->Lb0.real_dd && !oinfo->Lb1.real_dd) {
    oinfo->quad[1] = NULL;
  }

  if (oinfo->c.real_dd && !oinfo->quad[0]) {
    if (!oinfo->c_pw_const && oinfo->quad[2]) {
      oinfo->quad[0] = oinfo->quad[2];
    } else if (!oinfo->c_pw_const && oinfo->quad[1]) {
      oinfo->quad[0] = oinfo->quad[1];
    } else {
      oinfo->quad[0] = get_wall_quad(dim, row_fcts_deg + col_fcts_deg);
    }
  } else if (!oinfo->c.real_dd) {
    oinfo->quad[0] = NULL;
  }
#endif

  /* >>> */

  return true;
}

static BNDRY_FILL_INFO *first_fill_info = NULL;

static BNDRY_FILL_INFO *__get_neigh_fill_info(const BNDRY_OPERATOR_INFO *oinfo,
					      MATENT_TYPE krn_blk_type)
{
  FUNCNAME("__get_neigh_fill_info");
  BNDRY_FILL_INFO        *fill_info;
  const BAS_FCTS         *row_fcts, *col_fcts;
  PARAMETRIC             *parametric = NULL;
  int                    dim, not_all_param = false;
  unsigned int           dflt_flags[3];
  unsigned int           fast_flags[3];
  U_CHAR                 init_row_fcts[3];
  U_CHAR                 init_col_fcts[3];
  int                    i, wall, wall_fcts_first;
  unsigned int           el_mat_idx, typeidx = 0;
  OP_BLOCK_TYPE          op_type;
  const WALL_QUAD_FAST   **row_fcts_fast, **col_fcts_fast;

  op_type = operator_type(oinfo->row_fe_space, oinfo->col_fe_space);

  /* <<< generate a new fill_info object */

  fill_info = MEM_CALLOC(1, BNDRY_FILL_INFO);

  ROW_CHAIN_INIT(fill_info);
  COL_CHAIN_INIT(fill_info);

  fill_info->next = first_fill_info;
  first_fill_info = fill_info;

  fill_info->krn_blk_type = krn_blk_type;

  /* First clone the OPERATOR_INFO structure again */
  fill_info->op_info = *oinfo;

  row_fcts_fast = fill_info->row_wquad_fast;
  col_fcts_fast = fill_info->col_wquad_fast;

  row_fcts = oinfo->row_fe_space->bas_fcts;
  col_fcts = oinfo->col_fe_space->bas_fcts;

  dim = row_fcts->dim;

  if (op_type != OP_TYPE_SS) {
#undef MAT_BODY
#define MAT_BODY(F, CC, C, S, TYPE)			\
    fill_info->scl_el_mat =				\
      (void *)MAT_ALLOC(row_fcts->n_bas_fcts_max,	\
			col_fcts->n_bas_fcts_max,	\
			TYPE)
    MAT_EMIT_BODY_SWITCH(fill_info->krn_blk_type);
    fill_info->n_row_max = row_fcts->n_bas_fcts_max;
    fill_info->n_col_max = col_fcts->n_bas_fcts_max;
  }

  fill_info->parametric = parametric;

  el_mat_idx = 0;

  if (fill_info->op_info.tangential)
    el_mat_idx |= TANGENTIAL;
  if (fill_info->op_info.init_element)
    el_mat_idx |= OI_INIT_EL;
  if (parametric && not_all_param)
    el_mat_idx |= PARTPARAM;
  if (row_fcts != col_fcts)
    el_mat_idx |= MIXED;
  if (INIT_ELEMENT_NEEDED(col_fcts) || INIT_ELEMENT_NEEDED(row_fcts))
    el_mat_idx |= INIT_EL;

  /* <<< 2nd/1st/0th order parsing */

  row_fcts_fast[2] = col_fcts_fast[2] = NULL;
  init_row_fcts[2] = init_col_fcts[2] = 0x00;
  dflt_flags[2] = fast_flags[2] = 0x00;

  if (fill_info->op_info.LALt.real_dd) {
    /* <<< LALt stuff */

    el_mat_idx |= SCND_ORDER;

    /* always the "mixed" case: one of the local Ansatz spaces belongs
     * to the neighbour element.
     */
    dflt_flags[2] |= WALL_FCT_MIX;

    if (fill_info->op_info.tangential) {
      dflt_flags[2]    |= WALL_FCT_TAN;
      init_row_fcts[2] |= INIT_TANGENTIAL;
      init_col_fcts[2] |= INIT_TANGENTIAL;
    }

    init_row_fcts[2] = init_col_fcts[2] = INIT_GRD_PHI;
	
    if (INIT_ELEMENT_NEEDED(fill_info->op_info.quad[2])) {
      el_mat_idx |= INIT_EL;
    }
      
    if (fill_info->op_info.LALt_pw_const) {
      dflt_flags[2] |= WALL_FCT_PWC;
	
      if(parametric && !not_all_param) {
	WARNING("You have selected piecewise constant LALt but seem to\n");
	WARNING("have a parametric mesh without affine elements!\n");
      }
    }

    if(!oinfo->LALt_pw_const || parametric) {
      fast_flags[2] = dflt_flags[2];
      dflt_flags[2] &= ~WALL_FCT_PWC;
    }

    /* >>> */
  }

  row_fcts_fast[1] = col_fcts_fast[1] = NULL;
  init_row_fcts[1] = init_col_fcts[1] = 0x00;
  dflt_flags[1] = fast_flags[1] = 0x00;

  if (fill_info->op_info.Lb0.real_dd) {
    /* <<< Lb0 stuff */

    el_mat_idx |= FRST_ORDER|TANGENTIAL;

    dflt_flags[1] |= WALL_FCT_MIX;

    if (fill_info->op_info.tangential) {
      dflt_flags[1]    |= WALL_FCT_TAN;
      init_row_fcts[1] |= INIT_TANGENTIAL;
      init_col_fcts[1] |= INIT_TANGENTIAL;
    }

    init_row_fcts[1] |= INIT_PHI;
    init_col_fcts[1] |= INIT_GRD_PHI;
	
    if (INIT_ELEMENT_NEEDED(fill_info->op_info.quad[1])) {
      el_mat_idx |= INIT_EL;
    }
      
    if (oinfo->Lb0_pw_const) {

      dflt_flags[1] |= WALL_FCT_PWC;
	
      if(parametric && !not_all_param) {
	WARNING("You have selected piecewise constant Lb0 but seem to\n");
	WARNING("have a parametric mesh without affine elements!\n");
      }
    }

    if(!oinfo->Lb0_pw_const || parametric) {

      /* We will need the slow routines on some elements at least. */
      fast_flags[1] = dflt_flags[1];
      dflt_flags[1] &= ~WALL_FCT_PWC;
    }

    /* >>> */
  }

  if (fill_info->op_info.Lb1.real_dd) {
    /* <<< Lb1 stuff */

    el_mat_idx |= FRST_ORDER|TANGENTIAL;

    dflt_flags[1] |= WALL_FCT_MIX;

    if (fill_info->op_info.tangential) {
      dflt_flags[1]    |= WALL_FCT_TAN;
      init_row_fcts[1] |= INIT_TANGENTIAL;
      init_col_fcts[1] |= INIT_TANGENTIAL;
    }

    init_row_fcts[1] |= INIT_GRD_PHI;
    init_col_fcts[1] |= INIT_PHI;
	
    if (INIT_ELEMENT_NEEDED(fill_info->op_info.quad[1])) {
      el_mat_idx |= INIT_EL;
    }
      
    if (oinfo->Lb1_pw_const) {

      dflt_flags[1] |= WALL_FCT_PWC;
	
      if(parametric && !not_all_param) {
	WARNING("You have selected piecewise constant Lb1 but seem to\n");
	WARNING("have a parametric mesh without affine elements!\n");
      }
    }

    if(!oinfo->Lb1_pw_const || parametric) {
      /* We will need the slow routines on some elements at least. */
      fast_flags[1] = dflt_flags[1];
      dflt_flags[1] &= ~WALL_FCT_PWC;
    }

    /* >>> */
  }

  row_fcts_fast[0] = col_fcts_fast[0] = NULL;
  init_row_fcts[0] = init_col_fcts[0] = 0x00;
  dflt_flags[0] = fast_flags[0] = 0x00;

  if (fill_info->op_info.c.real_dd) {
    /* <<< c stuff */

    el_mat_idx |= ZERO_ORDER;

    dflt_flags[0] |= WALL_FCT_MIX;

    if (true || fill_info->op_info.tangential) {
      /* The zero-order term is always "tangential", i.e. only the
       * basis functions with non-vanishing trace on the respective
       * wall need to be assembled.
       */
      dflt_flags[0]    |= WALL_FCT_TAN;
      init_row_fcts[0] |= INIT_TANGENTIAL;
      init_col_fcts[0] |= INIT_TANGENTIAL;
      el_mat_idx       |= TANGENTIAL;
    }

    init_row_fcts[0] = INIT_PHI;
    init_col_fcts[0] = INIT_PHI;
	
    if (INIT_ELEMENT_NEEDED(fill_info->op_info.quad[0])) {
      el_mat_idx |= INIT_EL;
    }
      
    if (oinfo->c_pw_const) {

      dflt_flags[0] |= WALL_FCT_PWC;

      if(parametric && !not_all_param) {
	WARNING("You have selected piecewise constant c but seem to\n");
	WARNING("have a parametric mesh without affine elements!\n");
      }
    }

    if(!oinfo->c_pw_const || parametric) {

      /* We will need the slow routines on some elements at least. */
      fast_flags[0] = dflt_flags[0];
      dflt_flags[0] &= ~WALL_FCT_PWC;
    }

    /* >>> */
  }

  /* >>> */

  if (el_mat_idx & TANGENTIAL) {
    for (wall = 0; wall < N_WALLS(dim); ++wall) {
      fill_info->row_fcts_trace_map[wall] = row_fcts->trace_dof_map[0][0][wall];
      fill_info->n_trace_row_fcts[wall] = row_fcts->n_trace_bas_fcts[wall];
    }
  }

  /* <<< aquire quad_fast structs */

  if (fill_info->op_info.quad[0] == fill_info->op_info.quad[1]) {
    init_row_fcts[1] |= init_row_fcts[0];
    init_row_fcts[0]  = 0x00;
    init_col_fcts[1] |= init_col_fcts[0];
    init_col_fcts[0]  = 0x00;
  }
  if (fill_info->op_info.quad[1] == fill_info->op_info.quad[2]) {
    init_row_fcts[2] |= init_row_fcts[1];
    init_row_fcts[1]  = 0x00;
    init_col_fcts[2] |= init_col_fcts[1];
    init_col_fcts[1]  = 0x00;
  }
    
  if (row_fcts == col_fcts) {
    for (i = 0; i < 3; i++) {
      init_row_fcts[i] |= init_col_fcts[i];
      if (init_row_fcts[i]) {
	fill_info->row_wquad_fast[i] = fill_info->col_wquad_fast[i] =
	  get_wall_quad_fast(row_fcts,
			     fill_info->op_info.quad[i], init_row_fcts[i]);
      }
    }
  } else {
    for (i = 0; i < 3; i++) {
      if (init_row_fcts[i]) {
	fill_info->row_wquad_fast[i] =
	  get_wall_quad_fast(row_fcts,
			     fill_info->op_info.quad[i], init_row_fcts[i]);
      }
      if (init_col_fcts[i]) {
	fill_info->col_wquad_fast[i] =
	  get_wall_quad_fast(col_fcts,
			     fill_info->op_info.quad[i], init_col_fcts[i]);
      }
    }
  }

  /* >>> */

  /* <<< fast/dflt cleanup */

  if (dflt_flags[2]) {
    typeidx = type_index(fill_info->krn_blk_type, oinfo->LALt_type);
    for (wall = 0; wall < N_WALLS(dim); wall++) {
      fill_info->dflt_second_order[wall] = 
	_AI_el_wall_fcts[op_type][typeidx][dim][wall][4][dflt_flags[2]];
    }
  }
  if (fast_flags[2]) {
    for (wall = 0; wall < N_WALLS(dim); wall++) {
      fill_info->fast_second_order[wall] = 
	_AI_el_wall_fcts[op_type][typeidx][dim][wall][4][
	  fast_flags[2]*N_TYPE_DEFUNS];
    }
  }

  if (fill_info->op_info.Lb0.real_dd && fill_info->op_info.Lb1.real_dd) {
    wall_fcts_first = 3;
  } else if (fill_info->op_info.Lb1.real_dd) {
    wall_fcts_first = 2;
  } else if (fill_info->op_info.Lb1.real_dd) {
    wall_fcts_first = 1;
  } else {
    wall_fcts_first = -1;      
  }
    
  if (dflt_flags[1]) {
    typeidx = type_index(fill_info->krn_blk_type, oinfo->Lb_type);
    for (wall = 0; wall < N_WALLS(dim); wall++) {
      fill_info->dflt_first_order[wall] = 
	_AI_el_wall_fcts[op_type][
	  typeidx][dim][wall][wall_fcts_first][dflt_flags[1]];
    }
  }
  if (fast_flags[1]) {
    for (wall = 0; wall < N_WALLS(dim); wall++) {
      fill_info->fast_first_order[wall] = 
	_AI_el_wall_fcts[op_type][
	  typeidx][dim][wall][wall_fcts_first][fast_flags[1]];
    }
  }
    
  if (dflt_flags[0]) {
    typeidx = type_index(fill_info->krn_blk_type, oinfo->c_type);
    for (wall = 0; wall < N_WALLS(dim); wall++) {
      fill_info->dflt_zero_order[wall] = 
	_AI_el_wall_fcts[op_type][typeidx][dim][wall][0][dflt_flags[0]];
    }
  }
  if (fast_flags[1]) {
    for (wall = 0; wall < N_WALLS(dim); wall++) {
      fill_info->fast_zero_order[wall] = 
	_AI_el_wall_fcts[op_type][typeidx][dim][wall][0][fast_flags[0]];
    }
  }
  /* >>> */

  fill_info->neigh_el_mat_fcts = neigh_el_mat_fct_table[op_type][el_mat_idx];

  TEST_EXIT(fill_info->neigh_el_mat_fcts != NULL,
	    "Bogus choice for element matrix.\n");

  /* >>> */

  return fill_info;
}

/* <<<  AI_get_bndry_el_mat_fct() */

BNDRY_FILL_INFO *AI_get_neigh_fill_info(const BNDRY_OPERATOR_INFO *oi_orig,
					MATENT_TYPE blk_type)
{
  /* FUNCNAME("AI_get_neigh_fill_info"); */
  BNDRY_OPERATOR_INFO    oinfo[1];
  BNDRY_FILL_INFO        *fill_info;
  const FE_SPACE         *row_fe_space, *col_fe_space;
  PARAMETRIC             *parametric = NULL;
  const WALL_QUAD_TENSOR *quad_tensor[3];
  int i;

  row_fe_space = oi_orig->row_fe_space;
  col_fe_space = oi_orig->col_fe_space;
  if (col_fe_space == NULL) {
    col_fe_space = row_fe_space;
  }

  for (i = 0; i < 3; i++) {
    quad_tensor[i] = oi_orig->quad_tensor[i];
  }

  if (!unify_bop_info(
	oinfo, oi_orig, quad_tensor, row_fe_space, col_fe_space, blk_type)) {
    return NULL;
  }

  /* <<< look for an existing fill_info object */

  for (fill_info = first_fill_info; fill_info; fill_info = fill_info->next) {

    /* standard tests */
    if (!OP_INFO_EQ_P(&fill_info->op_info, oinfo))
      continue;
    if (fill_info->parametric != parametric)
      continue;

    /* Check whether the block-types match */
    if (fill_info->krn_blk_type != blk_type)
      continue;

#if 0
    /* Check for special bndry-operator stuff */
    if (BNDRY_FLAGS_CMP(fill_info->op_info.bndry_type, oinfo->bndry_type) != 0)
      continue;
#endif
    if (fill_info->op_info.tangential != oinfo->tangential)
      continue;

    /* All tests passed, but still the application-data pointer could
     * be different. We remember the first matching operator info with
     * possibly non-matching application-data pointer and clone that
     * if we do not find a fully matching fill-info structure.
     */
    if (fill_info->op_info.user_data != oinfo->user_data) {
      continue;
    }

    break;
  }

  if (fill_info != NULL) {
    return fill_info;
  }

  /* >>> */

  oinfo->row_fe_space = row_fe_space = copy_fe_space(row_fe_space);
  oinfo->col_fe_space = col_fe_space = copy_fe_space(col_fe_space);

  fill_info = __get_neigh_fill_info(oinfo, blk_type);

  fill_info->el_mat = get_el_matrix(row_fe_space, col_fe_space, blk_type);

  if (!CHAIN_SINGLE(row_fe_space) || !CHAIN_SINGLE(col_fe_space)) {
    const FE_SPACE *col_fesp, *row_fesp;
    BNDRY_FILL_INFO *row_chain, *col_chain, *chain_info;
    EL_MATRIX *elm;
      
    /* See whether we need to allocate a block-operator */
    elm       = fill_info->el_mat;
    row_fesp  = row_fe_space;
    col_chain = fill_info;
    CHAIN_FOREACH(col_fesp, col_fe_space, const FE_SPACE) {
      elm = ROW_CHAIN_NEXT(elm, EL_MATRIX);
      for (i = 0; i < 3; i++) {
	if (quad_tensor[i]) {
	  quad_tensor[i] =
	    ROW_CHAIN_NEXT(quad_tensor[i], const WALL_QUAD_TENSOR);
	}
      }      
      unify_bop_info(oinfo, oi_orig, quad_tensor, row_fesp, col_fesp, blk_type);
      row_chain = __get_neigh_fill_info(oinfo, blk_type);
      ROW_CHAIN_ADD_TAIL(col_chain, row_chain);
      row_chain->el_mat = elm;
    }
    /* reset to list-head */
    elm       = fill_info->el_mat;
    col_fesp  = col_fe_space;
    row_chain = fill_info;
    CHAIN_FOREACH(row_fesp, row_fe_space, const FE_SPACE) {
      elm = COL_CHAIN_NEXT(elm, EL_MATRIX);
      for (i = 0; i < 3; i++) {
	if (quad_tensor[i]) {
	  quad_tensor[i] =
	    COL_CHAIN_NEXT(quad_tensor[i], const WALL_QUAD_TENSOR);
	}
      }
      unify_bop_info(oinfo, oi_orig, quad_tensor, row_fesp, col_fesp, blk_type);
      col_chain = __get_neigh_fill_info(oinfo, blk_type);
      COL_CHAIN_ADD_TAIL(row_chain, col_chain);
      col_chain->el_mat = elm;
      CHAIN_FOREACH(col_fesp, col_fe_space, const FE_SPACE) {
	elm       = ROW_CHAIN_NEXT(elm, EL_MATRIX);
	row_chain = ROW_CHAIN_NEXT(row_chain, BNDRY_FILL_INFO);
	for (i = 0; i < 3; i++) {
	  if (quad_tensor[i]) {
	    quad_tensor[i] =
	      ROW_CHAIN_NEXT(quad_tensor[i], const WALL_QUAD_TENSOR);
	  }
	}
	unify_bop_info(
	  oinfo, oi_orig, quad_tensor, row_fesp, col_fesp, blk_type);
	chain_info = __get_neigh_fill_info(oinfo, blk_type);
	ROW_CHAIN_ADD_TAIL(col_chain, chain_info);
	COL_CHAIN_ADD_TAIL(row_chain, chain_info);
      }
      /* roll over to the list head */
      elm       = ROW_CHAIN_NEXT(elm, EL_MATRIX);
      row_chain = ROW_CHAIN_NEXT(row_chain, BNDRY_FILL_INFO);
      for (i = 0; i < 3; i++) {
	if (quad_tensor[i]) {
	  quad_tensor[i] =
	    ROW_CHAIN_NEXT(quad_tensor[i], const WALL_QUAD_TENSOR);
	}
      }
    }
  }
  
  return fill_info;
}

/* >>> */

#endif /* EMIT_SS_VERSIONS */
