/*******************************************************************************
 *
 * 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.c
 *                                                                          
 * description:  fe-space independent assemblation routines of              
 *               REAL_D x REAL_D matrices                                   
 *                                                                          
 *******************************************************************************
 *                                                                          
 * This file's authors: Claus-Justus Heine                                  
 *                      Abteilung fuer Angewandte Mathematik                 
 *                      Universitaet Freiburg                                
 *                      Hermann-Herder-Strasse 10
 *                      79104 Freiburg, Germany                             
 *                                                                          
 * (c) by C.-J. Heine (2004-2009)
 *                                                                          
 ******************************************************************************/

#include "alberta_intern.h"
#include "alberta.h"
#include "assemble.h"
#include <stdarg.h>

#define HAVE_VSSV (!HAVE_ROW_FCTS_C_TYPE && !HAVE_COL_FCTS_C_TYPE &&	\
		   (HAVE_ROW_FCTS_V_TYPE != HAVE_COL_FCTS_V_TYPE))

#if !HAVE_VSSV
# include "M_M_M_M_assemble_fcts.h"
# include "M_M_DM_DM_assemble_fcts.h"
# include "M_M_SCM_SCM_assemble_fcts.h"
#endif
#include "DM_DM_DM_DM_assemble_fcts.h"
#include "DM_DM_SCM_SCM_assemble_fcts.h"
#include "SCM_SCM_SCM_SCM_assemble_fcts.h"

#if ALBERTA_DEBUG == 1
# define inline /* */
#endif

/* NOTE: we allow low-order terms with higher symmetry than the
 * leading term. To compile all combinations would consume too much
 * memory and time during compilation, so we keep only the diagonal
 * combinations, and the combinations where the low-order parts have
 * both the same type.
 */

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

/* <<< assign_assemble_fcts() */

/* <<<  ASSIGN() */

#define DIM_TRANSFORM(base) DIM_VARIANT(VNAME(base), dim)
#define NO_TRANSFORM(base)  VNAME(base)

#define TYPENAME(NAME, D, S1, S2, S3) D##S1##S2##S3##_##NAME

/* Assignment for DSTTYPE == M */
#if HAVE_VSSV
# define M_ASSIGN_FUN(TRANSFORM, dst, namebase, srctype1, srctype2, srctype3) \
  ERROR_EXIT("Everything is foobar.\n"); break;
#else
# define M_ASSIGN_FUN(TRANSFORM,					\
		      dst, namebase, srctype1, srctype2, srctype3)	\
  switch (srctype1) {							\
  case MATENT_REAL_DD:							\
    switch (srctype2) {							\
    case MATENT_REAL_DD:						\
      switch (srctype3) {						\
      case MATENT_REAL_DD:						\
	(dst) = TRANSFORM(TYPENAME(namebase, M, M, M, M)); break;	\
      default:								\
	ERROR_EXIT("Everything is foobar.\n"); break;			\
      }									\
      break;								\
    case MATENT_REAL_D:							\
      switch (srctype3) {						\
      case MATENT_REAL_D:						\
	(dst) = TRANSFORM(TYPENAME(namebase, M, M, DM, DM)); break;	\
      default:								\
	ERROR_EXIT("Everything is foobar.\n"); break;			\
      }									\
      break;								\
    case MATENT_REAL:							\
      switch (srctype3) {						\
      case MATENT_REAL:							\
	(dst) = TRANSFORM(TYPENAME(namebase, M, M, SCM, SCM)); break;	\
      default:								\
	ERROR_EXIT("Everything is foobar.\n"); break;			\
      }									\
      break;								\
    default:								\
      ERROR_EXIT("Everything is foobar.\n"); break;			\
    }									\
    break;								\
  default:								\
    ERROR_EXIT("Everything is foobar.\n"); break;			\
  }									\
  break;
#endif

#define ASSIGN_FUN(TRANSFORM,						\
		   dst, namebase, dsttype, srctype1, srctype2, srctype3) \
  switch (dsttype) {							\
  case MATENT_REAL_DD:							\
    M_ASSIGN_FUN(TRANSFORM, dst, namebase, srctype1, srctype2, srctype3); \
  case MATENT_REAL_D:							\
    switch (srctype1) {							\
    case MATENT_REAL_D:							\
      switch (srctype2) {						\
      case MATENT_REAL_D:						\
	switch (srctype3) {						\
	case MATENT_REAL_D:						\
	  (dst) = TRANSFORM(TYPENAME(namebase, DM, DM, DM, DM)); break; \
	default:							\
	  ERROR_EXIT("Everything is foobar.\n"); break;			\
	}								\
	break;								\
      case MATENT_REAL:							\
	switch (srctype3) {						\
	case MATENT_REAL:						\
	  (dst) = TRANSFORM(TYPENAME(namebase, DM, DM, SCM, SCM)); break; \
	default:							\
	  ERROR_EXIT("Everything is foobar.\n"); break;			\
	}								\
	break;								\
      default:								\
	ERROR_EXIT("Everything is foobar.\n"); break;			\
      }									\
      break;								\
    default:								\
      ERROR_EXIT("Everything is foobar.\n"); break;			\
    }									\
    break;								\
  case MATENT_REAL:							\
    (dst) = TRANSFORM(TYPENAME(namebase, SCM, SCM, SCM, SCM)); break;	\
  default:								\
    ERROR_EXIT("Everything is foobar.\n"); break;			\
  }

/* >>> */

/* Just some defines to make the code below a little bit more readable.
 * also, use |= below instead of += so that the reader knows: it's not
 * magic, it's just a flag value.
 */
#define FLAG_C    0x8
#define FLAG_LB1  0x4
#define FLAG_LB0  0x2
#define FLAG_LALt 0x1

void VNAME(AI_assign_assemble_fcts)(FILL_INFO *fill_info,
				    const OPERATOR_INFO *oinfo,
				    U_CHAR pre_fl[3], U_CHAR qud_fl[3])
{
#if DIM_MAX > 1
  int dim = oinfo->row_fe_space->mesh->dim;
#endif

  /* Now assign the function pointers. Be careful to keep the
   * pre... stuff in sync with the quad... stuff, otherwise
   * assemblin on partly parametric meshes will not work correctly
   * in the general case.
   */

  /* <<< assign fast pre... functions */
  switch (pre_fl[2]) {
  case FLAG_LALt:
    ASSIGN_FUN(NO_TRANSFORM,
	       fill_info->dflt_second_order, pre_2,
	       fill_info->krn_blk_type,
	       oinfo->LALt_type, oinfo->Lb_type, oinfo->c_type);
    INFO(0,2,"using second order pre_2\n");
    break;
  case FLAG_LALt|FLAG_LB0:
    ASSIGN_FUN(NO_TRANSFORM,
	       fill_info->dflt_second_order, pre_2_01,
	       fill_info->krn_blk_type,
	       oinfo->LALt_type, oinfo->Lb_type, oinfo->c_type);
    INFO(0,2,"using second order pre_2_01\n");
    break;
  case FLAG_LALt|FLAG_LB1:
    ASSIGN_FUN(NO_TRANSFORM,
	       fill_info->dflt_second_order, pre_2_10,
	       fill_info->krn_blk_type,
	       oinfo->LALt_type, oinfo->Lb_type, oinfo->c_type);
    INFO(0,2,"using second order pre_2_10\n");
    break;
  case FLAG_LALt|FLAG_LB1|FLAG_LB0:
    ASSIGN_FUN(NO_TRANSFORM,
	       fill_info->dflt_second_order, pre_2_11,
	       fill_info->krn_blk_type,
	       oinfo->LALt_type, oinfo->Lb_type, oinfo->c_type);
    INFO(0,2,"using second order pre_2_11\n");
    break;
  case FLAG_LALt|FLAG_C:
    ASSIGN_FUN(NO_TRANSFORM,
	       fill_info->dflt_second_order, pre_2_0,
	       fill_info->krn_blk_type,
	       oinfo->LALt_type, oinfo->Lb_type, oinfo->c_type);
    INFO(0,2,"using second order pre_2_0\n");
    break;
  case FLAG_LALt|FLAG_LB0|FLAG_C:
    ASSIGN_FUN(NO_TRANSFORM,
	       fill_info->dflt_second_order, pre_2_01_0,
	       fill_info->krn_blk_type,
	       oinfo->LALt_type, oinfo->Lb_type, oinfo->c_type);
    INFO(0,2,"using second order pre_2_01_0\n");
    break;
  case FLAG_LALt|FLAG_LB1|FLAG_C:
    ASSIGN_FUN(NO_TRANSFORM,
	       fill_info->dflt_second_order, pre_2_10_0,
	       fill_info->krn_blk_type,
	       oinfo->LALt_type, oinfo->Lb_type, oinfo->c_type);
    INFO(0,2,"using second order pre_2_10_0\n");
    break;
  case FLAG_LALt|FLAG_LB1|FLAG_LB0|FLAG_C:
    ASSIGN_FUN(NO_TRANSFORM,
	       fill_info->dflt_second_order, pre_2_11_0,
	       fill_info->krn_blk_type,
	       oinfo->LALt_type, oinfo->Lb_type, oinfo->c_type);
    INFO(0,2,"using second order quad2_11_0\n");
    break;
  }

  switch (pre_fl[1]) {
  case FLAG_LB0:
    if (oinfo->adv_fe_space != NULL) {
      ASSIGN_FUN(NO_TRANSFORM,
		 fill_info->dflt_first_order, adv_pre_01,
		 fill_info->krn_blk_type,
		 oinfo->LALt_type, oinfo->Lb_type, oinfo->c_type);
      INFO(0,2,"using first order adv_pre_01\n");
    } else {
      ASSIGN_FUN(NO_TRANSFORM,
		 fill_info->dflt_first_order, pre_01,
		 fill_info->krn_blk_type,
		 oinfo->LALt_type, oinfo->Lb_type, oinfo->c_type);
      INFO(0,2,"using first order pre_01\n");
    }
    break;
  case FLAG_LB1:
    if (oinfo->adv_fe_space != NULL) {
      ASSIGN_FUN(NO_TRANSFORM,
		 fill_info->dflt_first_order, adv_pre_10,
		 fill_info->krn_blk_type,
		 oinfo->LALt_type, oinfo->Lb_type, oinfo->c_type);
      INFO(0,2,"using first order adv_pre_10\n");
    } else {
      ASSIGN_FUN(NO_TRANSFORM,
		 fill_info->dflt_first_order, pre_10,
		 fill_info->krn_blk_type,
		 oinfo->LALt_type, oinfo->Lb_type, oinfo->c_type);
      INFO(0,2,"using first order pre_10\n");
    }
    break;
  case FLAG_LB0|FLAG_LB1:
    if (oinfo->adv_fe_space != NULL) {
      ASSIGN_FUN(NO_TRANSFORM,
		 fill_info->dflt_first_order, adv_pre_11,
		 fill_info->krn_blk_type,
		 oinfo->LALt_type, oinfo->Lb_type, oinfo->c_type);
      INFO(0,2,"using first order adv_pre_11\n");
    } else {
      ASSIGN_FUN(NO_TRANSFORM,
		 fill_info->dflt_first_order, pre_11,
		 fill_info->krn_blk_type,
		 oinfo->LALt_type, oinfo->Lb_type, oinfo->c_type);
      INFO(0,2,"using first order pre_11\n");
    }
    break;
  case FLAG_LB0|FLAG_C:
    ASSIGN_FUN(NO_TRANSFORM,
	       fill_info->dflt_first_order, pre_01_0,
	       fill_info->krn_blk_type,
	       oinfo->LALt_type, oinfo->Lb_type, oinfo->c_type);
    INFO(0,2,"using first order pre_01_0\n");
    break;
  case FLAG_LB1|FLAG_C:
    ASSIGN_FUN(NO_TRANSFORM,
	       fill_info->dflt_first_order, pre_10_0,
	       fill_info->krn_blk_type,
	       oinfo->LALt_type, oinfo->Lb_type, oinfo->c_type);
    INFO(0,2,"using first order pre_10_0\n");
    break;
  case FLAG_LB0|FLAG_LB1|FLAG_C:
    ASSIGN_FUN(NO_TRANSFORM,
	       fill_info->dflt_first_order, pre_11_0,
	       fill_info->krn_blk_type,
	       oinfo->LALt_type, oinfo->Lb_type, oinfo->c_type);
    INFO(0,2,"using first order pre_11_0\n");
    break;
  }

  if (pre_fl[0] /* == FLAG_C */) {
    ASSIGN_FUN(NO_TRANSFORM,
	       fill_info->dflt_zero_order, pre_0,
	       fill_info->krn_blk_type,
	       oinfo->LALt_type, oinfo->Lb_type, oinfo->c_type);
    INFO(0,2,"using zero order pre_0\n");
  }

  /* >>> */

  /* Now fill in the slow functions as needed. Move the already
   * existing function entries to fast_..._order.
   */
  /* <<< assign slow quad... functions */

  if (qud_fl[2]) { /* remember the fast entry, if any. */
    fill_info->fast_second_order = fill_info->dflt_second_order;
  }
    
  switch (qud_fl[2]) {
  case FLAG_LALt:
    ASSIGN_FUN(DIM_TRANSFORM,
	       fill_info->dflt_second_order, quad_2,
	       fill_info->krn_blk_type,
	       oinfo->LALt_type, oinfo->Lb_type, oinfo->c_type);
    INFO(0,2,"using second order quad_2\n");
    break;
  case FLAG_LALt|FLAG_LB0:
    ASSIGN_FUN(DIM_TRANSFORM,
	       fill_info->dflt_second_order, quad_2_01,
	       fill_info->krn_blk_type,
	       oinfo->LALt_type, oinfo->Lb_type, oinfo->c_type);
    INFO(0,2,"using second order quad_2_01\n");
    break;
  case FLAG_LALt|FLAG_LB1:
    ASSIGN_FUN(DIM_TRANSFORM,
	       fill_info->dflt_second_order, quad_2_10,
	       fill_info->krn_blk_type,
	       oinfo->LALt_type, oinfo->Lb_type, oinfo->c_type);
    INFO(0,2,"using second order quad_2_10\n");
    break;
  case FLAG_LALt|FLAG_LB1|FLAG_LB0:
    ASSIGN_FUN(DIM_TRANSFORM,
	       fill_info->dflt_second_order, quad_2_11,
	       fill_info->krn_blk_type,
	       oinfo->LALt_type, oinfo->Lb_type, oinfo->c_type);
    INFO(0,2,"using second order quad_2_11\n");
    break;
  case FLAG_LALt|FLAG_C:
    ASSIGN_FUN(DIM_TRANSFORM,
	       fill_info->dflt_second_order, quad_2_0,
	       fill_info->krn_blk_type,
	       oinfo->LALt_type, oinfo->Lb_type, oinfo->c_type);
    INFO(0,2,"using second order quad_2_0\n");
    break;
  case FLAG_LALt|FLAG_LB0|FLAG_C:
    ASSIGN_FUN(DIM_TRANSFORM,
	       fill_info->dflt_second_order, quad_2_01_0,
	       fill_info->krn_blk_type,
	       oinfo->LALt_type, oinfo->Lb_type, oinfo->c_type);
    INFO(0,2,"using second order quad_2_01_0\n");
    break;
  case FLAG_LALt|FLAG_LB1|FLAG_C:
    ASSIGN_FUN(DIM_TRANSFORM,
	       fill_info->dflt_second_order, quad_2_10_0,
	       fill_info->krn_blk_type,
	       oinfo->LALt_type, oinfo->Lb_type, oinfo->c_type);
    INFO(0,2,"using second order quad_2_10_0\n");
    break;
  case FLAG_LALt|FLAG_LB1|FLAG_LB0|FLAG_C:
    ASSIGN_FUN(DIM_TRANSFORM,
	       fill_info->dflt_second_order, quad_2_11_0,
	       fill_info->krn_blk_type,
	       oinfo->LALt_type, oinfo->Lb_type, oinfo->c_type);
    INFO(0,2,"using second order quad2_11_0\n");
    break;
  }

  if (qud_fl[1]) { /* remember the fast entry, if any */
    fill_info->fast_first_order = fill_info->dflt_first_order;
  }

  switch (qud_fl[1]) {
  case FLAG_LB0:
    if (oinfo->adv_fe_space != NULL) {
      ASSIGN_FUN(DIM_TRANSFORM,
		 fill_info->dflt_first_order, adv_quad_01,
		 fill_info->krn_blk_type,
		 oinfo->LALt_type, oinfo->Lb_type, oinfo->c_type);
      INFO(0,2,"using first order adv_quad_01\n");
    } else {
      ASSIGN_FUN(DIM_TRANSFORM,
		 fill_info->dflt_first_order, quad_01,
		 fill_info->krn_blk_type,
		 oinfo->LALt_type, oinfo->Lb_type, oinfo->c_type);
      INFO(0,2,"using first order quad_01\n");
    }
    break;
  case FLAG_LB1:
    if (oinfo->adv_fe_space != NULL) {
      ASSIGN_FUN(DIM_TRANSFORM,
		 fill_info->dflt_first_order, adv_quad_10,
		 fill_info->krn_blk_type,
		 oinfo->LALt_type, oinfo->Lb_type, oinfo->c_type);
      INFO(0,2,"using first order adv_quad_10\n");
    } else {
      ASSIGN_FUN(DIM_TRANSFORM,
		 fill_info->dflt_first_order, quad_10,
		 fill_info->krn_blk_type,
		 oinfo->LALt_type, oinfo->Lb_type, oinfo->c_type);
      INFO(0,2,"using first order quad_10\n");
    }
    break;
  case FLAG_LB0|FLAG_LB1:
    if (oinfo->adv_fe_space != NULL) {
      ASSIGN_FUN(DIM_TRANSFORM,
		 fill_info->dflt_first_order, adv_quad_11,
		 fill_info->krn_blk_type,
		 oinfo->LALt_type, oinfo->Lb_type, oinfo->c_type);
      INFO(0,2,"using first order adv_quad_11\n");
    } else {
      ASSIGN_FUN(DIM_TRANSFORM,
		 fill_info->dflt_first_order, quad_11,
		 fill_info->krn_blk_type,
		 oinfo->LALt_type, oinfo->Lb_type, oinfo->c_type);
      INFO(0,2,"using first order quad_11\n");
    }
    break;
  case FLAG_LB0|FLAG_C:
    ASSIGN_FUN(DIM_TRANSFORM,
	       fill_info->dflt_first_order, quad_01_0,
	       fill_info->krn_blk_type,
	       oinfo->LALt_type, oinfo->Lb_type, oinfo->c_type);
    INFO(0,2,"using first order quad_01_0\n");
    break;
  case FLAG_LB1|FLAG_C:
    ASSIGN_FUN(DIM_TRANSFORM,
	       fill_info->dflt_first_order, quad_10_0,
	       fill_info->krn_blk_type,
	       oinfo->LALt_type, oinfo->Lb_type, oinfo->c_type);
    INFO(0,2,"using first order quad_10_0\n");
    break;
  case FLAG_LB0|FLAG_LB1|FLAG_C:
    ASSIGN_FUN(DIM_TRANSFORM,
	       fill_info->dflt_first_order, quad_11_0,
	       fill_info->krn_blk_type,
	       oinfo->LALt_type, oinfo->Lb_type, oinfo->c_type);
    INFO(0,2,"using first order quad_11_0\n");
    break;
  }

  if (qud_fl[0] /* == FLAG_C */) {
    fill_info->fast_zero_order = fill_info->dflt_zero_order;
    ASSIGN_FUN(NO_TRANSFORM,
	       fill_info->dflt_zero_order, quad_0,
	       fill_info->krn_blk_type,
	       oinfo->LALt_type, oinfo->Lb_type, oinfo->c_type);
    INFO(0,2,"using zero order quad_0\n");
  }

  /* >>> */
}

/* >>> */

#if EMIT_SS_VERSIONS

/* <<< definition of element matrix functions */

/* Flags coding for specific variants of the element-matrix
 * routine. These are used below to instantiate lots of different
 * versions of the element-matrix routine where those flags have a
 * constant value, which can be exploited by the C-compiler to
 * optimize the code.
 */
#define ZERO_ORDER (1 << 0)
#define FRST_ORDER (1 << 1)
#define SCND_ORDER (1 << 2)
#define ORDER_MASK (ZERO_ORDER|FRST_ORDER|SCND_ORDER)
#define INIT_EL    (1 << 3)
#define OI_INIT_EL (1 << 4)
#define MIXED      (1 << 5)
#define BNDRY_FLAG (1 << 6) /* invoked on a specific part of the boundary */
#define WALL_FLAG  (1 << 7) /* invoked on all walls */
#define PARTPARAM  (1 << 8)

#define ASSIGN_MAT(thing, val)			\
  union						\
  {						\
    REAL_DD  **real_dd;				\
    REAL_D   **real_d;				\
    REAL     **real;				\
  } thing = { (REAL_DD **)val }

static inline
void init_objects(FILL_INFO *info, U_CHAR mask)
{
  const FE_SPACE *row_fesp = info->op_info.row_fe_space;
  const FE_SPACE *col_fesp = info->op_info.col_fe_space;
  EL_MATRIX *el_mat, *old_el_mat;

  int i = 0;
  ROW_CHAIN_DO(info, FILL_INFO) {
    int j = 0;
    COL_CHAIN_DO(info, FILL_INFO) {
      bool diag = ((mask & MIXED) == 0) && (i == j);
      if (mask & SCND_ORDER) {
	if (info->q11_cache) {
	  INIT_OBJECT(info->q11_cache);
	}
	if (info->row_quad_fast[2]) {
	  INIT_OBJECT_SINGLE(info->row_quad_fast[2]);
	  if (!diag) {
	    INIT_OBJECT_SINGLE(info->col_quad_fast[2]);
	  }
	}
      }
      if (mask & FRST_ORDER) {
	if (info->op_info.adv_fe_space != NULL) {
	  ADV_CACHE *adv_cache = info->adv_cache;
	  CHAIN_DO(adv_cache, ADV_CACHE) {
	    if (adv_cache->adv_quad_fast) {
	      INIT_OBJECT_SINGLE(adv_cache->adv_quad_fast);
	    }
	    if (adv_cache->row_quad_fast) {
	      INIT_OBJECT_SINGLE(adv_cache->row_quad_fast);
	      if (!diag) {
		INIT_OBJECT_SINGLE(adv_cache->col_quad_fast);
	      }
	    }
	    if (adv_cache->q010) {
	      INIT_OBJECT(adv_cache->q010);
	    }
	    if (adv_cache->q100) {
	      INIT_OBJECT(adv_cache->q100);
	    }
	    if (adv_cache->quad->n_points_max > adv_cache->adv_field_size) {
	      MEM_FREE(adv_cache->adv_field, adv_cache->adv_field_size, REAL_D);
	      adv_cache->adv_field =
		MEM_ALLOC(adv_cache->quad->n_points_max, REAL_D);
	      adv_cache->adv_field_size = adv_cache->quad->n_points_max;
	    }
	  } CHAIN_WHILE(adv_cache, ADV_CACHE);
	} else {	  
	  if (info->row_quad_fast[1]) {
	    INIT_OBJECT_SINGLE(info->row_quad_fast[1]);
	    if (!diag) {
	      INIT_OBJECT_SINGLE(info->col_quad_fast[1]);
	    }
	  }
	  if (info->q01_cache) {
	    INIT_OBJECT(info->q01_cache);
	  }
	  if (info->q10_cache) {
	    INIT_OBJECT(info->q10_cache);
	  }
	}
      }
      if (mask & ZERO_ORDER) {
	if (info->q00_cache) {
	  INIT_OBJECT(info->q00_cache);
	}
	if (info->row_quad_fast[0]) {
	  INIT_OBJECT_SINGLE(info->row_quad_fast[0]);
	  if (!diag) {
	    INIT_OBJECT_SINGLE(info->col_quad_fast[0]);
	  }
	}
      }
      ++j;
    } COL_CHAIN_WHILE(info, FILL_INFO);
    ++i;
  } ROW_CHAIN_WHILE(info, FILL_INFO);

  if ((mask & (SCND_ORDER|FRST_ORDER|ZERO_ORDER)) == 0) {
    INIT_OBJECT(info->op_info.row_fe_space->bas_fcts);
    if (mask & MIXED) {
      INIT_OBJECT(info->op_info.col_fe_space->bas_fcts);
    }
  }

  /* Unconditionally reallocate the element matrices, this is not a
   * timing critical code-path.
   */
  old_el_mat = info->el_mat;
  el_mat = get_el_matrix(row_fesp, col_fesp, info->krn_blk_type);
  ROW_CHAIN_DO(info, FILL_INFO) {
    COL_CHAIN_DO(info, FILL_INFO) {
      if (info->scl_el_mat != NULL) {
#undef MAT_BODY
#define MAT_BODY(F, CC, C, S, TYPE)		\
	MAT_FREE(info->scl_el_mat,		\
		 info->el_mat->n_row_max,	\
		 info->el_mat->n_col_max,	\
		 TYPE);				\
	info->scl_el_mat =			\
	  (void *)MAT_ALLOC(el_mat->n_row_max,	\
			    el_mat->n_col_max,	\
			    TYPE)
	MAT_EMIT_BODY_SWITCH(info->krn_blk_type);
      }
      info->el_mat = el_mat;
      el_mat = COL_CHAIN_NEXT(el_mat, EL_MATRIX);
    } COL_CHAIN_WHILE(info, FILL_INFO);
    el_mat = ROW_CHAIN_NEXT(el_mat, EL_MATRIX);
  } ROW_CHAIN_WHILE(info, FILL_INFO);
  free_el_matrix(old_el_mat);

  /* Initialize any boundary contributions. */
  if (mask & WALL_FLAG) {
    int dim = info->op_info.row_fe_space->mesh->dim;
    int wall;
    for (wall = 0; wall < N_WALLS(dim); wall++) {
      info->wall_el_mat_fct[wall](NULL, info->wall_fill_info, info->el_mat);
    }
  }

  if (mask & BNDRY_FLAG) {
    int dim = info->op_info.row_fe_space->mesh->dim;
    int wall, i;
    BNDRY_OP *bndry_op;   /* pointer into boundary operator list */

    for (wall = 0; wall < N_WALLS(dim); wall++) {
      for (i = 0; i < info->n_bndry_op; i++) {
	bndry_op = info->bndry_op + i;
	bndry_op->el_mat_fct[wall](NULL, bndry_op->fill_info, info->el_mat);
      }
    }
  }
}

static void do_nothing(const EL_INFO *el_info, const FILL_INFO *fill_info) 
{}

static inline INIT_EL_TAG
clear_el_matrix(const EL_INFO *el_info, FILL_INFO *info, U_CHAR mask)
{
  INIT_EL_TAG chain_tag = INIT_EL_TAG_NONE;

  int k = 0;
  ROW_CHAIN_DO(info, FILL_INFO) {
    int l = 0;
    COL_CHAIN_DO(info, FILL_INFO) {
      bool diag = ((mask & MIXED) == 0) && (k == l);
      int i, j;

      if (mask & INIT_EL) {
	INIT_EL_TAG init_tag = INIT_EL_TAG_NONE;

	init_tag |=
	  INIT_ELEMENT_SINGLE(el_info, info->op_info.row_fe_space->bas_fcts);
	if (!diag) {
	  init_tag |=
	    INIT_ELEMENT_SINGLE(el_info, info->op_info.col_fe_space->bas_fcts);
	}

	info->el_mat->n_row =
	  info->op_info.row_fe_space->bas_fcts->n_bas_fcts;
	info->el_mat->n_col =
	  info->op_info.col_fe_space->bas_fcts->n_bas_fcts;
	
	chain_tag |= init_tag;

	if (init_tag == INIT_EL_TAG_NULL) {
	  continue;
	}
      }

#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);
      ++l;
    } COL_CHAIN_WHILE(info, FILL_INFO);
    ++k;
  } ROW_CHAIN_WHILE(info, FILL_INFO);
  
  return chain_tag;
}

/* <<< element_matrix_default() */

/* An element_matrix() version which ignores the return value of the
 * init_element() routine, because either all or no element is
 * parametric.
 *
 * The compiler can optimize away all of the INIT_ELEMENT() cruft when
 * inlining this function with a constant "mask" parameter.
 */
static inline
const EL_MATRIX *element_matrix_default(const EL_INFO *el_info,
					void *fill_info,
					U_CHAR mask)
{
  FILL_INFO   *info = (FILL_INFO *)fill_info;
  INIT_EL_TAG chain_tag = INIT_EL_TAG_NONE;
  bool        el_mat_cleared = false;
  int         i, j, k;

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

  if ((mask & (SCND_ORDER|FRST_ORDER|ZERO_ORDER)) != 0) {
    info->adv_coeffs = NULL;
    k = 0;
    ROW_CHAIN_DO(info, FILL_INFO) {
      int l = 0;
      COL_CHAIN_DO(info, FILL_INFO) {
	bool diag = ((mask & MIXED) == 0) && k == l;

	if (mask & INIT_EL) {
	  INIT_EL_TAG init_tag = INIT_EL_TAG_NONE;

	  if (mask & SCND_ORDER) {
	    if (info->q11_cache) {
	      init_tag |= INIT_ELEMENT(el_info, info->q11_cache);
	    } else if (info->row_quad_fast[2]) {
	      init_tag |= INIT_ELEMENT_SINGLE(el_info, info->row_quad_fast[2]);
	      if (!diag) {
		init_tag |=
		  INIT_ELEMENT_SINGLE(el_info, info->col_quad_fast[2]);
	      }
	    }
	  }
    
	  if (mask & FRST_ORDER) {
	    if (info->op_info.adv_fe_space != NULL) {
	      ADV_CACHE *adv_cache = info->adv_cache;
	      if (adv_cache->q010 || adv_cache->q100) {
		CHAIN_DO(adv_cache, ADV_CACHE) {
		  if (adv_cache->q010) {
		    init_tag |= INIT_ELEMENT(el_info, adv_cache->q010);
		  }
		  if (adv_cache->q100) {
		    init_tag |= INIT_ELEMENT(el_info, adv_cache->q100);
		  }
		} CHAIN_WHILE(adv_cache, ADV_CACHE);
	      } else {
		CHAIN_DO(adv_cache, ADV_CACHE) {
		  init_tag |=
		    INIT_ELEMENT_SINGLE(el_info, adv_cache->adv_quad_fast);
		  init_tag |=
		    INIT_ELEMENT_SINGLE(el_info, adv_cache->row_quad_fast);
		  if (!diag) {
		    init_tag |=
		      INIT_ELEMENT_SINGLE(el_info, adv_cache->col_quad_fast);
		  }
		} CHAIN_WHILE(adv_cache, ADV_CACHE);
	      }
	    } else {
	      if (info->q01_cache || info->q10_cache) {
		if (info->q01_cache) {
		  init_tag |= INIT_ELEMENT(el_info, info->q01_cache);
		}
		if (info->q10_cache) {
		  init_tag |= INIT_ELEMENT(el_info, info->q10_cache);
		}
	      } else if (info->row_quad_fast[1]) {
		init_tag |=
		  INIT_ELEMENT_SINGLE(el_info, info->row_quad_fast[1]);
		if (!diag) {
		  init_tag |=
		    INIT_ELEMENT_SINGLE(el_info, info->col_quad_fast[1]);
		}
	      }
	    }
	  }

	  if (mask & ZERO_ORDER) {
	    if (info->q00_cache) {
	      init_tag |= INIT_ELEMENT(el_info, info->q00_cache);
	    } else if (info->row_quad_fast[0]) {
	      init_tag |= INIT_ELEMENT_SINGLE(el_info, info->row_quad_fast[0]);
	      if (!diag) {
		init_tag |=
		  INIT_ELEMENT_SINGLE(el_info, info->col_quad_fast[0]);
	      }
	    }
	  }

	  info->el_mat->n_row =
	    info->op_info.row_fe_space->bas_fcts->n_bas_fcts;
	  info->el_mat->n_col =
	    info->op_info.col_fe_space->bas_fcts->n_bas_fcts;

	  chain_tag |= init_tag;

	  if (init_tag == INIT_EL_TAG_NULL) {
	    /* any none-NULL tag has set another bit in init_tag */
	    continue;
	  }
	}
  
	if (mask & OI_INIT_EL) {
	  info->op_info.init_element(
	    el_info, info->op_info.quad, info->op_info.user_data);
	}

#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);
	el_mat_cleared = true;

	if (mask & SCND_ORDER) {
	  info->dflt_second_order(el_info, info);
	}
	if (mask & FRST_ORDER) {
	  info->dflt_first_order(el_info, info);
	}
	if (mask & ZERO_ORDER) {
	  info->dflt_zero_order(el_info, info);
	}
	++l;
      } COL_CHAIN_WHILE(info, FILL_INFO);
      ++k;
    } ROW_CHAIN_WHILE(info, FILL_INFO);

    if (mask & INIT_EL) {
      if (chain_tag == INIT_EL_TAG_NULL) {
	return NULL;
      }
    }
  }

  if (mask & BNDRY_FLAG) {
    int wall, i;
    int dim = el_info->mesh->dim;
    BNDRY_TYPE wall_type; /* boundary classification of a wall */
    BNDRY_OP *bndry_op;   /* pointer into boundary operator list */

    for (wall = 0; wall < N_WALLS(dim); wall++) {
      wall_type = wall_bound(el_info, wall);
      if (wall_type == INTERIOR) {
	continue;
      }

      for (i = 0; i < info->n_bndry_op; i++) {
	bndry_op = info->bndry_op + i;
	if (BNDRY_FLAGS_IS_AT_BNDRY(bndry_op->bndry_type, wall_type)) {
	  if (!el_mat_cleared) {
	    if (clear_el_matrix(el_info, info, mask) == INIT_EL_TAG_NULL) {
	      return NULL;
	    }
	    el_mat_cleared = true;
	  }
	  bndry_op->el_mat_fct[wall](
	    el_info, bndry_op->fill_info, info->el_mat);
	}
      }
    }
  }

  if (mask & WALL_FLAG) {
    int dim = el_info->mesh->dim, wall;
    for (wall = 0; wall < N_WALLS(dim); wall++) {
      BNDRY_TYPE wall_type = wall_bound(el_info, wall);
      if (wall_type != INTERIOR) {
	continue;
      }
      if (!el_mat_cleared) {
	if (clear_el_matrix(el_info, info, mask) == INIT_EL_TAG_NULL) {
	  return NULL;
	}
	el_mat_cleared = true;
      }
      info->wall_el_mat_fct[wall](el_info, info->wall_fill_info, info->el_mat);
    }
  }

  return el_mat_cleared ? info->el_mat : NULL;
}

/* >>> */

/* <<< element_matrix_partparam() */

/* An element_matrix() version which honours the return value of the
 * init_element() routine, because some elements are parametric, some
 * are affine.
 *
 * The compiler can optimize away all of the INIT_ELEMENT() cruft when
 * inlining this function with a constant "mask" parameter.
 */
static inline
const EL_MATRIX *element_matrix_partparam(const EL_INFO *el_info,
					  void *fill_info,
					  U_CHAR mask)
{
  FILL_INFO   *info = (FILL_INFO *)fill_info;
  int         is_parametric = true;
  INIT_EL_TAG chain_tag = INIT_EL_TAG_NONE;
  bool        el_mat_cleared = false;
  int         i, j, k, l;

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

  is_parametric = info->parametric->init_element(el_info, info->parametric);

  if ((mask & (SCND_ORDER|FRST_ORDER|ZERO_ORDER)) != 0) {
    info->adv_coeffs = NULL;
    k = 0;
    ROW_CHAIN_DO(info, FILL_INFO) {
      l = 0;
      COL_CHAIN_DO(info, FILL_INFO) {
	bool diag = ((mask & MIXED) == 0) && k == l;

	if (mask & INIT_EL) {
	  INIT_EL_TAG init_tag = INIT_EL_TAG_NONE;

	  if (is_parametric) {
	    if (mask & SCND_ORDER) {
	      init_tag |= INIT_ELEMENT_SINGLE(el_info, info->row_quad_fast[2]);
	      if (!diag) {
		init_tag |=
		  INIT_ELEMENT_SINGLE(el_info, info->col_quad_fast[2]);
	      }
	    }
	    if (mask & FRST_ORDER) {
	      if (info->op_info.adv_fe_space != NULL) {
		ADV_CACHE *adv_cache = info->adv_cache;
		CHAIN_DO(adv_cache, ADV_CACHE) {
		  init_tag |=
		    INIT_ELEMENT_SINGLE(el_info, adv_cache->adv_quad_fast);
		  init_tag |=
		    INIT_ELEMENT_SINGLE(el_info, adv_cache->row_quad_fast);
		  if (!diag) {
		    init_tag |=
		      INIT_ELEMENT_SINGLE(el_info, adv_cache->col_quad_fast);
		  }
		} CHAIN_WHILE(adv_cache, ADV_CACHE);
	      } else {
		init_tag |=
		  INIT_ELEMENT_SINGLE(el_info, info->row_quad_fast[1]);
		if (!diag) {
		  init_tag |=
		    INIT_ELEMENT_SINGLE(el_info, info->col_quad_fast[1]);
		}
	      }
	    }
	    if (mask & ZERO_ORDER) {
	      init_tag |= INIT_ELEMENT_SINGLE(el_info, info->row_quad_fast[0]);
	      if (!diag) {
		init_tag |=
		  INIT_ELEMENT_SINGLE(el_info, info->col_quad_fast[0]);
	      }
	    }
	  } else {
	    if (mask & SCND_ORDER) {
	      if (info->fast_second_order) {
		init_tag |= INIT_ELEMENT(el_info, info->q11_cache);
	      } else {
		init_tag |=
		  INIT_ELEMENT_SINGLE(el_info, info->row_quad_fast[2]);
		if (!diag) {
		  init_tag |=
		    INIT_ELEMENT_SINGLE(el_info, info->col_quad_fast[2]);
		}
	      }
	    }
	    if (mask & FRST_ORDER) {
	      if (info->op_info.adv_fe_space != NULL) {
		ADV_CACHE *adv_cache = info->adv_cache;
		if (adv_cache->q010 || adv_cache->q100) {
		  CHAIN_DO(adv_cache, ADV_CACHE) {
		    if (adv_cache->q010) {
		      init_tag |= INIT_ELEMENT(el_info, adv_cache->q010);
		    }
		    if (adv_cache->q100) {
		      init_tag |= INIT_ELEMENT(el_info, adv_cache->q100);
		    }
		  } CHAIN_WHILE(adv_cache, ADV_CACHE);
		} else {
		  CHAIN_DO(adv_cache, ADV_CACHE) {
		    init_tag |=
		      INIT_ELEMENT_SINGLE(el_info, adv_cache->adv_quad_fast);
		    init_tag |=
		      INIT_ELEMENT_SINGLE(el_info, adv_cache->row_quad_fast);
		    if (!diag) {
		      init_tag |=
			INIT_ELEMENT_SINGLE(el_info, adv_cache->col_quad_fast);
		    }
		  } CHAIN_WHILE(adv_cache, ADV_CACHE);
		}
	      } else {
		if (info->fast_first_order) {
		  if (info->q01_cache) {
		    init_tag |=
		      INIT_ELEMENT(el_info, info->q01_cache);
		  }
		  if (info->q10_cache) {
		    init_tag |=
		      INIT_ELEMENT(el_info, info->q10_cache);
		  }
		} else {
		  init_tag |=
		    INIT_ELEMENT_SINGLE(el_info, info->row_quad_fast[1]);
		  if (!diag) {
		    init_tag |=
		      INIT_ELEMENT_SINGLE(el_info, info->col_quad_fast[1]);
		  }
		}
	      }
	    }
	    if (mask & ZERO_ORDER) {
	      if (info->fast_second_order) {
		init_tag |= INIT_ELEMENT(el_info, info->q00_cache);
	      } else {
		init_tag |=
		  INIT_ELEMENT_SINGLE(el_info, info->row_quad_fast[0]);
		if (!diag) {
		  init_tag |=
		    INIT_ELEMENT_SINGLE(el_info, info->col_quad_fast[0]);
		}
	      }
	    }
	  }

	  chain_tag |= init_tag;

	  info->el_mat->n_row =
	    info->op_info.row_fe_space->bas_fcts->n_bas_fcts;
	  info->el_mat->n_col =
	    info->op_info.col_fe_space->bas_fcts->n_bas_fcts;

	  if (init_tag == INIT_EL_TAG_NULL) {
	    /* any none-NULL tag has set another bit in init_tag */
	    continue;
	  }    
	}
      
	if (mask & OI_INIT_EL) {
	  info->op_info.init_element(
	    el_info, info->op_info.quad, info->op_info.user_data);
	}

#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);
	el_mat_cleared = true;

	if (mask & SCND_ORDER) {
	  if (!is_parametric && info->fast_second_order)
	    info->fast_second_order(el_info, info);
	  else
	    info->dflt_second_order(el_info, info);
	}
	if (mask & FRST_ORDER) {
	  if (!is_parametric && info->fast_first_order)
	    info->fast_first_order(el_info, info);
	  else
	    info->dflt_first_order(el_info, info);
	}
	if (mask & ZERO_ORDER) {
	  if (!is_parametric && info->fast_zero_order)
	    info->fast_zero_order(el_info, info);
	  else
	    info->dflt_zero_order(el_info, info);
	}
	++l;
      } COL_CHAIN_WHILE(info, FILL_INFO);
      ++k;
    } ROW_CHAIN_WHILE(info, FILL_INFO);

    if (mask & INIT_EL) {
      if (chain_tag == INIT_EL_TAG_NULL) {
	return NULL;
      }
    }
  }
  
  if (mask & BNDRY_FLAG) {
    int wall, i;
    int dim = el_info->mesh->dim;
    BNDRY_TYPE wall_type; /* boundary classification of a wall */
    BNDRY_OP *bndry_op;   /* pointer into boundary operator list */

    for (wall = 0; wall < N_WALLS(dim); wall++) {
      wall_type = wall_bound(el_info, wall);
      if (wall_type == INTERIOR) {
	continue;
      }
      for (i = 0; i < info->n_bndry_op; i++) {
	bndry_op = info->bndry_op + i;
	if (BNDRY_FLAGS_IS_AT_BNDRY(bndry_op->bndry_type, wall_type)) {
	  if (!el_mat_cleared) {
	    if (clear_el_matrix(el_info, info, mask) == INIT_EL_TAG_NULL) {
	      return NULL;
	    }
	    el_mat_cleared = true;
	  }
	  bndry_op->el_mat_fct[wall](el_info, 
				     bndry_op->fill_info, info->el_mat);
	}
      }
    }
  }

  if (mask & WALL_FLAG) {
    int dim = el_info->mesh->dim, wall;
    for (wall = 0; wall < N_WALLS(dim); wall++) {
      BNDRY_TYPE wall_type = wall_bound(el_info, wall);
      if (wall_type != INTERIOR) {
	continue;
      }
      if (!el_mat_cleared) {
	if (clear_el_matrix(el_info, info, mask) == INIT_EL_TAG_NULL) {
	  return NULL;
	}
	el_mat_cleared = true;
      }
      info->wall_el_mat_fct[wall](el_info, info->wall_fill_info, info->el_mat);
    }
  }

  return el_mat_cleared ? info->el_mat : NULL;
}

/* >>> */

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

/* The poor C-programmers explicit template instantiation.
 *
 * The numbers n2 and n1 code for the upper and lower nibble of the
 * flag definitions further above:
 *
 * #define ZERO_ORDER (1 << 0)
 * #define FRST_ORDER (1 << 1)
 * #define SCND_ORDER (1 << 2)
 * #define ORDER_MASK (ZERO_ORDER|FRST_ORDER|SCND_ORDER)
 * #define INIT_EL    (1 << 3)
 * #define OI_INIT_EL (1 << 4)
 * #define MIXED      (1 << 5)
 * #define BNDRY_FLAG (1 << 6)
 * #define WALL_FLAG  (1 << 7)
 * #define PARTPARAM  (1 << 8)
 *
 */
#define ELEMENT_MATRIX_DEFAULT_FUN(n2, n1) element_matrix_default_##n2##_##n1
#define DEFUN_ELEMENT_MATRIX_DEFAULT(n2, n1)			\
  FLATTEN_ATTR							\
  static const EL_MATRIX *					\
  ELEMENT_MATRIX_DEFAULT_FUN(n2, n1)(const EL_INFO *el_info,	\
				     void *fill_info)		\
  {								\
    return element_matrix_default(el_info, fill_info, n2|n1);	\
  }								\
  struct _AI_semicolon_dummy

#define ELEMENT_MATRIX_PARTPARAM_FUN(n2, n1) element_matrix_partparm_##n2##_##n1
#define DEFUN_ELEMENT_MATRIX_PARTPARAM(n2, n1)			\
  FLATTEN_ATTR							\
  static const EL_MATRIX *					\
  ELEMENT_MATRIX_PARTPARAM_FUN(n2, n1)(const EL_INFO *el_info,	\
				       void *fill_info)		\
  {								\
    return element_matrix_partparam(el_info, fill_info, n2|n1);	\
  }								\
  struct _AI_semicolon_dummy

#define DEFUN_16_EL_MAT_DEFAULT(n2)		\
  DEFUN_ELEMENT_MATRIX_DEFAULT(n2, 0x00);	\
  DEFUN_ELEMENT_MATRIX_DEFAULT(n2, 0x01);	\
  DEFUN_ELEMENT_MATRIX_DEFAULT(n2, 0x02);	\
  DEFUN_ELEMENT_MATRIX_DEFAULT(n2, 0x03);	\
  DEFUN_ELEMENT_MATRIX_DEFAULT(n2, 0x04);	\
  DEFUN_ELEMENT_MATRIX_DEFAULT(n2, 0x05);	\
  DEFUN_ELEMENT_MATRIX_DEFAULT(n2, 0x06);	\
  DEFUN_ELEMENT_MATRIX_DEFAULT(n2, 0x07);	\
  DEFUN_ELEMENT_MATRIX_DEFAULT(n2, 0x08);	\
  DEFUN_ELEMENT_MATRIX_DEFAULT(n2, 0x09);	\
  DEFUN_ELEMENT_MATRIX_DEFAULT(n2, 0x0A);	\
  DEFUN_ELEMENT_MATRIX_DEFAULT(n2, 0x0B);	\
  DEFUN_ELEMENT_MATRIX_DEFAULT(n2, 0x0C);	\
  DEFUN_ELEMENT_MATRIX_DEFAULT(n2, 0x0D);	\
  DEFUN_ELEMENT_MATRIX_DEFAULT(n2, 0x0E);	\
  DEFUN_ELEMENT_MATRIX_DEFAULT(n2, 0x0F)

/* Instantiate the templates for the relevant bits of the upper nibble. */
DEFUN_16_EL_MAT_DEFAULT(0x00);
DEFUN_16_EL_MAT_DEFAULT(0x10); /* with OPERATOR_INFO.init_element() */
DEFUN_16_EL_MAT_DEFAULT(0x20); /* with differing row and column spaces */
DEFUN_16_EL_MAT_DEFAULT(0x30);
DEFUN_16_EL_MAT_DEFAULT(0x40); /* with boundary operator component */
DEFUN_16_EL_MAT_DEFAULT(0x50);
DEFUN_16_EL_MAT_DEFAULT(0x60);
DEFUN_16_EL_MAT_DEFAULT(0x70);
DEFUN_16_EL_MAT_DEFAULT(0x80); /* with a component defined on the walls */
DEFUN_16_EL_MAT_DEFAULT(0x90);
DEFUN_16_EL_MAT_DEFAULT(0xA0);
DEFUN_16_EL_MAT_DEFAULT(0xB0);
DEFUN_16_EL_MAT_DEFAULT(0xC0);
DEFUN_16_EL_MAT_DEFAULT(0xD0);
DEFUN_16_EL_MAT_DEFAULT(0xE0);
DEFUN_16_EL_MAT_DEFAULT(0xF0);

/* Initializer for the look-up table. */
#define EL_MAT_16_DEFAULT_FUNS(n2)		\
  ELEMENT_MATRIX_DEFAULT_FUN(n2, 0x00),		\
    ELEMENT_MATRIX_DEFAULT_FUN(n2, 0x01),	\
    ELEMENT_MATRIX_DEFAULT_FUN(n2, 0x02),	\
    ELEMENT_MATRIX_DEFAULT_FUN(n2, 0x03),	\
    ELEMENT_MATRIX_DEFAULT_FUN(n2, 0x04),	\
    ELEMENT_MATRIX_DEFAULT_FUN(n2, 0x05),	\
    ELEMENT_MATRIX_DEFAULT_FUN(n2, 0x06),	\
    ELEMENT_MATRIX_DEFAULT_FUN(n2, 0x07),	\
    ELEMENT_MATRIX_DEFAULT_FUN(n2, 0x08),	\
    ELEMENT_MATRIX_DEFAULT_FUN(n2, 0x09),	\
    ELEMENT_MATRIX_DEFAULT_FUN(n2, 0x0A),	\
    ELEMENT_MATRIX_DEFAULT_FUN(n2, 0x0B),	\
    ELEMENT_MATRIX_DEFAULT_FUN(n2, 0x0C),	\
    ELEMENT_MATRIX_DEFAULT_FUN(n2, 0x0D),	\
    ELEMENT_MATRIX_DEFAULT_FUN(n2, 0x0E),	\
    ELEMENT_MATRIX_DEFAULT_FUN(n2, 0x0F)

/* Stuff for partly parametric meshes, at least one of the three least
 * significant bits must be set, and the OI_INIT_EL bit (bit 4) must
 * be set.
 */
#define DEFUN_16_EL_MAT_PARTPARAM(n2)		\
  DEFUN_ELEMENT_MATRIX_PARTPARAM(n2, 0x00);	\
  DEFUN_ELEMENT_MATRIX_PARTPARAM(n2, 0x01);	\
  DEFUN_ELEMENT_MATRIX_PARTPARAM(n2, 0x02);	\
  DEFUN_ELEMENT_MATRIX_PARTPARAM(n2, 0x03);	\
  DEFUN_ELEMENT_MATRIX_PARTPARAM(n2, 0x04);	\
  DEFUN_ELEMENT_MATRIX_PARTPARAM(n2, 0x05);	\
  DEFUN_ELEMENT_MATRIX_PARTPARAM(n2, 0x06);	\
  DEFUN_ELEMENT_MATRIX_PARTPARAM(n2, 0x07);	\
  DEFUN_ELEMENT_MATRIX_PARTPARAM(n2, 0x08);	\
  DEFUN_ELEMENT_MATRIX_PARTPARAM(n2, 0x09);	\
  DEFUN_ELEMENT_MATRIX_PARTPARAM(n2, 0x0A);	\
  DEFUN_ELEMENT_MATRIX_PARTPARAM(n2, 0x0B);	\
  DEFUN_ELEMENT_MATRIX_PARTPARAM(n2, 0x0C);	\
  DEFUN_ELEMENT_MATRIX_PARTPARAM(n2, 0x0D);	\
  DEFUN_ELEMENT_MATRIX_PARTPARAM(n2, 0x0E);	\
  DEFUN_ELEMENT_MATRIX_PARTPARAM(n2, 0x0F)

DEFUN_16_EL_MAT_PARTPARAM(0x00);
DEFUN_16_EL_MAT_PARTPARAM(0x10); /* with OPERATOR_INFO.init_element() */
DEFUN_16_EL_MAT_PARTPARAM(0x20); /* with differing row and column spaces */
DEFUN_16_EL_MAT_PARTPARAM(0x30);
DEFUN_16_EL_MAT_PARTPARAM(0x40); /* with boundary operator component */
DEFUN_16_EL_MAT_PARTPARAM(0x50);
DEFUN_16_EL_MAT_PARTPARAM(0x60);
DEFUN_16_EL_MAT_PARTPARAM(0x70);
DEFUN_16_EL_MAT_PARTPARAM(0x80); /* with a component defined on the walls */
DEFUN_16_EL_MAT_PARTPARAM(0x90);
DEFUN_16_EL_MAT_PARTPARAM(0xA0);
DEFUN_16_EL_MAT_PARTPARAM(0xB0);
DEFUN_16_EL_MAT_PARTPARAM(0xC0);
DEFUN_16_EL_MAT_PARTPARAM(0xD0);
DEFUN_16_EL_MAT_PARTPARAM(0xE0);
DEFUN_16_EL_MAT_PARTPARAM(0xF0);

/* initializer for the lookup-table */
#define EL_MAT_16_PARTPARAM_FUNS(n2)		\
  ELEMENT_MATRIX_PARTPARAM_FUN(n2, 0x00),	\
    ELEMENT_MATRIX_PARTPARAM_FUN(n2, 0x01),	\
    ELEMENT_MATRIX_PARTPARAM_FUN(n2, 0x02),	\
    ELEMENT_MATRIX_PARTPARAM_FUN(n2, 0x03),	\
    ELEMENT_MATRIX_PARTPARAM_FUN(n2, 0x04),	\
    ELEMENT_MATRIX_PARTPARAM_FUN(n2, 0x05),	\
    ELEMENT_MATRIX_PARTPARAM_FUN(n2, 0x06),	\
    ELEMENT_MATRIX_PARTPARAM_FUN(n2, 0x07),	\
    ELEMENT_MATRIX_PARTPARAM_FUN(n2, 0x08),	\
    ELEMENT_MATRIX_PARTPARAM_FUN(n2, 0x09),	\
    ELEMENT_MATRIX_PARTPARAM_FUN(n2, 0x0A),	\
    ELEMENT_MATRIX_PARTPARAM_FUN(n2, 0x0B),	\
    ELEMENT_MATRIX_PARTPARAM_FUN(n2, 0x0C),	\
    ELEMENT_MATRIX_PARTPARAM_FUN(n2, 0x0D),	\
    ELEMENT_MATRIX_PARTPARAM_FUN(n2, 0x0E),	\
    ELEMENT_MATRIX_PARTPARAM_FUN(n2, 0x0F)

EL_MATRIX_FCT el_matrix_table[512] = {
  /* Either no or all elements are parametric. */
  EL_MAT_16_DEFAULT_FUNS(0x00),
  EL_MAT_16_DEFAULT_FUNS(0x10), /* with OPERATOR_INFO.init_element() */
  EL_MAT_16_DEFAULT_FUNS(0x20), /* with differing row and column spaces */
  EL_MAT_16_DEFAULT_FUNS(0x30),
  EL_MAT_16_DEFAULT_FUNS(0x40), /* with boundary operator component */
  EL_MAT_16_DEFAULT_FUNS(0x50),
  EL_MAT_16_DEFAULT_FUNS(0x60),
  EL_MAT_16_DEFAULT_FUNS(0x70),
  EL_MAT_16_DEFAULT_FUNS(0x80),
  EL_MAT_16_DEFAULT_FUNS(0x90),
  EL_MAT_16_DEFAULT_FUNS(0xA0),
  EL_MAT_16_DEFAULT_FUNS(0xB0),
  EL_MAT_16_DEFAULT_FUNS(0xC0),
  EL_MAT_16_DEFAULT_FUNS(0xD0),
  EL_MAT_16_DEFAULT_FUNS(0xE0),
  EL_MAT_16_DEFAULT_FUNS(0xF0),
  /* "Partially" parametric versions. */
  EL_MAT_16_PARTPARAM_FUNS(0x00),
  EL_MAT_16_PARTPARAM_FUNS(0x10), /* with OPERATOR_INFO.init_element() */
  EL_MAT_16_PARTPARAM_FUNS(0x20), /* with differing row and column spaces */
  EL_MAT_16_PARTPARAM_FUNS(0x30),
  EL_MAT_16_PARTPARAM_FUNS(0x40), /* with boundary operator component */
  EL_MAT_16_PARTPARAM_FUNS(0x50),
  EL_MAT_16_PARTPARAM_FUNS(0x60),
  EL_MAT_16_PARTPARAM_FUNS(0x70),
  EL_MAT_16_PARTPARAM_FUNS(0x80), /* with a component defined on the walls */
  EL_MAT_16_PARTPARAM_FUNS(0x90),
  EL_MAT_16_PARTPARAM_FUNS(0xA0),
  EL_MAT_16_PARTPARAM_FUNS(0xB0),
  EL_MAT_16_PARTPARAM_FUNS(0xC0),
  EL_MAT_16_PARTPARAM_FUNS(0xD0),
  EL_MAT_16_PARTPARAM_FUNS(0xE0),
  EL_MAT_16_PARTPARAM_FUNS(0xF0),
};

/* >>> */

/* >>> */

/* Fill a quad-matrix, result is a quad-tensor with trivial depth
 * link. This function allocates quadratures suitable for assembling a
 * bilinear form; the allocated quadrature rules have the degree
 *
 * krn_degree + row_bas_fcts->degree + col_bas_fcts->degree - n_derivatives
 *
 * @param[in] row_fe_space The finite element space defining the
 * vector space for the columns of the block operator (i.e. the
 * row_fe_space determines the number of rows).
 *
 * @param[in] col_fe_space The finite element space defining the
 * vector space for the rows of the block operator (i.e. the
 * col_fe_space determines the number of columns).
 *
 * @param[in] krn_degree The tentative quadrature degree of the kernel
 * of the bilinear form, i.e. without multiplying with the basis
 * functions.
 *
 * @param[in] n_derivatives The number of factors of the bilinear-form
 * which are differentiated. This reduces the degree of the allocated
 * quadratures.
 */
const QUAD_TENSOR *get_quad_matrix(const FE_SPACE *row_fe_space,
				   const FE_SPACE *col_fe_space,
				   int krn_degree,
				   int n_derivatives)
{
  int size = CHAIN_LENGTH(row_fe_space) * CHAIN_LENGTH(col_fe_space);
  int dim = row_fe_space->mesh->dim;
  const BAS_FCTS *row_bas_fcts = row_fe_space->bas_fcts;
  const BAS_FCTS *col_bas_fcts = col_fe_space->bas_fcts;
  const BAS_FCTS *row_bfcts;
  const BAS_FCTS *col_bfcts;
  QUAD_TENSOR *qtensor, *qt;
  QUAD_TENSOR *row_chain, *col_chain;

  qtensor = qt = MEM_ALLOC(size, QUAD_TENSOR);
  /* First, get the connectivity righht. */

  ROW_CHAIN_INIT(qtensor);
  COL_CHAIN_INIT(qtensor);
  DEP_CHAIN_INIT(qtensor);

  /* rest of row */
  CHAIN_FOREACH(col_bfcts, col_bas_fcts, const BAS_FCTS) {
    row_chain = qt++;
    ROW_CHAIN_INIT(row_chain);
    COL_CHAIN_INIT(row_chain);
    DEP_CHAIN_INIT(row_chain);
    ROW_CHAIN_ADD_TAIL(qtensor, row_chain);
  }
  row_chain = qtensor;
  /* following rows */
  CHAIN_FOREACH(row_bfcts, row_bas_fcts, const BAS_FCTS) {
    col_chain = qt++;
    ROW_CHAIN_INIT(col_chain);
    COL_CHAIN_INIT(col_chain);
    DEP_CHAIN_INIT(col_chain);
    COL_CHAIN_ADD_TAIL(row_chain, col_chain);
    CHAIN_FOREACH(col_bfcts, col_bas_fcts, const BAS_FCTS) {
      row_chain  = ROW_CHAIN_NEXT(row_chain, QUAD_TENSOR);
      ROW_CHAIN_INIT(qt);
      COL_CHAIN_INIT(qt);
      DEP_CHAIN_INIT(qt);
      ROW_CHAIN_ADD_TAIL(col_chain, qt);
      COL_CHAIN_ADD_TAIL(row_chain, qt); 	
      qt++;
    }
    row_chain = ROW_CHAIN_NEXT(row_chain, QUAD_TENSOR);
  }

  ROW_CHAIN_DO(qtensor, QUAD_TENSOR) {
    COL_CHAIN_DO(qtensor, QUAD_TENSOR) {
      int row_deg = row_bas_fcts->unchained->degree;
      int col_deg = col_bas_fcts->unchained->degree;
      int degree;
      
      degree        = row_deg + col_deg - n_derivatives + krn_degree;
      qtensor->quad = get_quadrature(dim, degree);

      col_bas_fcts = CHAIN_NEXT(col_bas_fcts, const BAS_FCTS);
    } COL_CHAIN_WHILE(qtensor, QUAD_TENSOR);
    row_bas_fcts = CHAIN_NEXT(row_bas_fcts, const BAS_FCTS);
  } ROW_CHAIN_WHILE(qtensor, QUAD_TENSOR);

  return qtensor;
}

/* Fill a quad-matrix, result is a quad-tensor with trivial depth
 * link. This function allocates quadratures suitable for assembling a
 * bilinear form; the allocated quadrature rules have the degree
 *
 * krn_degree
 * +
 * row_bas_fcts->degree + col_bas_fcts->degree + dep_bas_fcts->degree
 * -
 * n_derivatives
 *
 * @param[in] row_fe_space The finite element space defining the
 * vector space for the columns of the block operator (i.e. the
 * row_fe_space determines the number of rows).
 *
 * @param[in] col_fe_space The finite element space defining the
 * vector space for the rows of the block operator (i.e. the
 * col_fe_space determines the number of columns).
 *
 * @param[in] depth_fe_space FE-space for the third "dimension".
 *
 * @param[in] krn_degree The tentative quadrature degree of the kernel
 * of the bilinear form, i.e. without multiplying with the basis
 * functions.
 *
 * @param[in] n_derivatives The number of factors of the bilinear-form
 * which are differentiated. This reduces the degree of the allocated
 * quadratures.
 */
const QUAD_TENSOR *get_quad_tensor(const FE_SPACE *row_fe_space,
				   const FE_SPACE *col_fe_space,
				   const FE_SPACE *depth_fe_space,
				   int krn_degree,
				   int n_derivatives)
{
  int size;
  int dim = row_fe_space->mesh->dim;
  const BAS_FCTS *row_bas_fcts = row_fe_space->bas_fcts;
  const BAS_FCTS *col_bas_fcts = col_fe_space->bas_fcts;
  const BAS_FCTS *dep_bas_fcts = depth_fe_space->bas_fcts;
  QUAD_TENSOR *qtensor, *qt;
  int n_rows = CHAIN_LENGTH(row_fe_space);
  int n_cols = CHAIN_LENGTH(col_fe_space);
  int depth  = CHAIN_LENGTH(depth_fe_space);
  int i, j, k;

  size = n_rows * n_cols * depth;

  qtensor = qt = MEM_ALLOC(size, QUAD_TENSOR);

  /* First, get the connectivity righht. */

  /* Initialize all list-links to empty */
  for (i = 0; i < n_rows; i++) {
    for (j = 0; j < n_cols; j++) {
      for (k = 0; k < depth; k++) {
	QUAD_TENSOR *qt = &qtensor[i * n_cols * depth + j * depth + k];
	ROW_CHAIN_INIT(qt);
	COL_CHAIN_INIT(qt);
	DEP_CHAIN_INIT(qt);
      }
    }
  }

  /* Then for each element, add the successor in each direction to the
   * list link.
   */
  for (i = 0; i < n_rows; i++) {
    for (j = 0; j < n_cols; j++) {
      for (k = 0; k < depth; k++) {
	QUAD_TENSOR *qt = &qtensor[i * n_cols * depth + j * depth + k];
	int ip1 = i + 1 >= n_rows ? 0 : i + 1;
	int jp1 = j + 1 >= n_cols ? 0 : j + 1;
	int kp1 = k + 1 >= depth ? 0 : k + 1;
	QUAD_TENSOR *qt_row = &qtensor[ip1 * n_cols * depth + j * depth + k];
	QUAD_TENSOR *qt_col = &qtensor[i * n_cols * depth + jp1 * depth + k];
	QUAD_TENSOR *qt_dep = &qtensor[i * n_cols * depth + j * depth + kp1];
	ROW_CHAIN_ADD_HEAD(qt, qt_row);
	COL_CHAIN_ADD_HEAD(qt, qt_col);
	DEP_CHAIN_ADD_HEAD(qt, qt_dep);
      }
    }
  }

  /* Now allocate the quadrature rules. */
  ROW_CHAIN_DO(qtensor, QUAD_TENSOR) {
    COL_CHAIN_DO(qtensor, QUAD_TENSOR) {
      DEP_CHAIN_DO(qtensor, QUAD_TENSOR) {
	int row_deg = row_bas_fcts->unchained->degree;
	int col_deg = col_bas_fcts->unchained->degree;
	int dep_deg = dep_bas_fcts->unchained->degree;
	int degree;
      
	degree = row_deg + col_deg + dep_deg - n_derivatives + krn_degree;
	qtensor->quad = get_quadrature(dim, degree);

	dep_bas_fcts = CHAIN_NEXT(dep_bas_fcts, const BAS_FCTS);
      } DEP_CHAIN_WHILE(qtensor, QUAD_TENSOR);
      col_bas_fcts = CHAIN_NEXT(col_bas_fcts, const BAS_FCTS);
    } COL_CHAIN_WHILE(qtensor, QUAD_TENSOR);
    row_bas_fcts = CHAIN_NEXT(row_bas_fcts, const BAS_FCTS);
  } ROW_CHAIN_WHILE(qtensor, QUAD_TENSOR);

  return qtensor;
}

void free_quad_tensor(const QUAD_TENSOR *qtensor)
{
  int n_rows = ROW_CHAIN_LENGTH(qtensor);
  int n_cols = COL_CHAIN_LENGTH(qtensor);
  int depth  = DEP_CHAIN_LENGTH(qtensor);
  
  MEM_FREE(qtensor, n_rows * n_cols * depth, QUAD_TENSOR);
}

/* <<< fill_matrix_info() */

static FILL_INFO *first_fill_info;

/* <<< find_fill_info() */

/* Take an OPERATOR_INFO (which must have been unified by
 * unify_op_info()) and search for an existing fill-info
 * structure. This routine does not depend on the vector/scalar status
 * of the respective basis functions.
 */
static FILL_INFO *
find_fill_info(const OPERATOR_INFO *oinfo,
	       MATENT_TYPE blk_type,
	       const BNDRY_OP *bndry_op, int n_bop,
	       const AI_BNDRY_EL_MAT_FCT *wall_el_mat_fct,
	       const BNDRY_FILL_INFO *wall_fill_info)
{
  /* FUNCNAME("find_fill_info"); */
  FILL_INFO      *fill_info;
  PARAMETRIC     *parametric = NULL;
  int            i;

  /*  look for an existing fill_info */

  for (fill_info = first_fill_info; fill_info; fill_info = fill_info->next) {
    /* <<< a couple of tests ... */

    /* 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 (blk_type != fill_info->krn_blk_type)
      continue;

    if (fill_info->wall_el_mat_fct != wall_el_mat_fct)
      continue;

    if (fill_info->wall_fill_info != wall_fill_info)
      continue;

    /* check for boundary operator info's */
    if (fill_info->n_bndry_op != n_bop)
      continue;
    
    for (i = 0; i < n_bop; i++) {
      if ((BNDRY_FLAGS_CMP(fill_info->bndry_op[i].bndry_type,
			   bndry_op[i].bndry_type) != 0)
	  ||
	  (fill_info->bndry_op[i].el_mat_fct != bndry_op[i].el_mat_fct)
	  ||
	  (fill_info->bndry_op[i].fill_info != bndry_op[i].fill_info)) {
	break;
      }
    }
    if (i < n_bop)
      continue;

    /* All tests passed, but still the application-data pointer could
     * be different.
     */
    if (fill_info->op_info.user_data != oinfo->user_data) {
      continue;
    }
    
    /* >>> */

    break;    
  }

  return fill_info;
}

/* >>> */

/* <<< unify_op_info() */

/* Fill in default quadratures etc. 
 *
 * BLK_TYPE is the type of the operator _KERNEL_ on the reference
 * element, which may differ from the actual type of the associated
 * matrices if the respective basis functions are vector-valued by
 * themselves.
 */
static bool unify_op_info(OPERATOR_INFO *oinfo, 
			  const OPERATOR_INFO *oi_orig,
			  const FE_SPACE *row_fe_space,
			  const FE_SPACE *col_fe_space,
			  MATENT_TYPE blk_type)
{
  FUNCNAME("unify_op_info");
  const BAS_FCTS *row_fcts, *col_fcts;
  PARAMETRIC     *parametric = NULL;
  int            dim;
  int            row_fcts_deg, col_fcts_deg, max_deg;

  if (oi_orig) {
    int i;
    *oinfo = *oi_orig;
    for (i = 0; i < 3; i++) {
      if (oinfo->quad_tensor[i]) {
	oinfo->quad[i] = oinfo->quad_tensor[i]->quad;
      }
    }
  } else {
    memset(oinfo, 0, sizeof(*oinfo));
  }
  oinfo->row_fe_space = row_fe_space;
  oinfo->col_fe_space = col_fe_space;

  if (oi_orig == NULL) {
    return true;
  }

  /* <<< consistency checks */

  /* set missing MATENT_TYPE info to something we support, this is just
   * a normalization of the function names used with the different
   * type combinations.
   */
  if (oinfo->LALt.real_dd == NULL) {
    oinfo->LALt_type = blk_type;    /* give it the correct block-type ... */
    oinfo->LALt_pw_const  = false;  /* ... but reset everything else */
    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) {
    if (oinfo->c.real_dd) {
      oinfo->Lb_type = oinfo->c_type;
    } else {
      oinfo->Lb_type = blk_type;
    }
    /* Reset all other terms */
    oinfo->Lb0_Lb1_anti_symmetric = false;
    oinfo->Lb_degree              = 0;
    oinfo->advection_field        = NULL;
    oinfo->adv_fe_space           = NULL;
    oinfo->quad[1]                = NULL;
    oinfo->quad_tensor[1]         = NULL;
  }
  if (oinfo->c.real_dd == NULL) {
    if (oinfo->Lb0.real_dd || oinfo->Lb1.real_dd) {
      oinfo->c_type = oinfo->Lb_type;
    } else {
      oinfo->c_type = blk_type;
    }
    /* reset to default valus */
    oinfo->c_pw_const     = false;
    oinfo->c_degree       = 0;
    oinfo->quad[0]        = NULL;
    oinfo->quad_tensor[0] = NULL;
  }

  if (!oinfo->row_fe_space && !oinfo->col_fe_space) {
    ERROR("both pointer to row and column FE_SPACEs NULL\n");
    ERROR("cannot initialize EL_MATRIX_INFO; returning false\n");
    return false;
  }

  if (oinfo->row_fe_space->mesh != oinfo->col_fe_space->mesh) {
    MSG("Mesh must be the same for row and column fe_space; returning NULL\n");
    return false;
  }

  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("cannot initialize EL_MATRIX_INFO; returning NULL\n");
    return false;
  }

  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("cannot initialize EL_MATRIX_INFO; returning NULL\n");
      return false;
    }
  }

  if (row_fcts != col_fcts) {
    oinfo->LALt_symmetric = oinfo->Lb0_Lb1_anti_symmetric = false;
  }

  /* >>> */

  /* <<< allocate default quadratures */

  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] == NULL) {
    if (oinfo->LALt_pw_const) {
      oinfo->LALt_degree = 0;
    }
    oinfo->quad[2] = get_quadrature(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] == NULL) {
    if ((!oinfo->Lb0_pw_const || !oinfo->Lb1_pw_const) && oinfo->quad[2]) {
      oinfo->quad[1] = oinfo->quad[2];
    } else {
      oinfo->quad[1] = get_quadrature(dim, max_deg);
    }
  } 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_quadrature(dim, max_deg);
    }
  } else if (!oinfo->c.real_dd) {
    oinfo->c_degree = 0;
    oinfo->quad[0]  = NULL;
  }
#else
  if (oinfo->LALt.real_dd && oinfo->quad[2] == NULL) {
    if (oinfo->LALt_pw_const) {
      oinfo->LALt_degree = 0;
    }
    oinfo->quad[2] = 
      get_quadrature(dim, row_fcts_deg + col_fcts_deg - 2 + oinfo->LALt_degree);
  } else if (!oinfo->LALt.real_dd) {
    oinfo->quad[2] = NULL;
  }

  if ((oinfo->Lb0.real_dd || oinfo->Lb1.real_dd) && oinfo->quad[1] == NULL) {
    if ((!oinfo->Lb0_pw_const || !oinfo->Lb1_pw_const) && oinfo->quad[2]) {
      oinfo->quad[1] = oinfo->quad[2];
    } else {
      oinfo->quad[1] = get_quadrature(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_quadrature(dim, row_fcts_deg + col_fcts_deg);
    }
  } else if (!oinfo->c.real_dd) {
    oinfo->quad[0] = NULL;
  }
#endif

  /* >>> */

  return true;
}

/* >>> */

/* <<< get_fill_info() */

/* <<< __get_fill_info() */

static FILL_INFO *
__get_fill_info(unsigned int *el_mat_idx, const OPERATOR_INFO *oinfo,
		U_CHAR pre_fl[3], U_CHAR qud_fl[3])
{
  FUNCNAME("get_fill_info");
  FILL_INFO       *fill_info;
  PARAMETRIC      *parametric;
  int             not_all_param = false;
  U_CHAR          init_row_fcts[3];
  U_CHAR          init_col_fcts[3];
  int             i;
  const QUAD_FAST **row_fcts_fast, **col_fcts_fast;
  const BAS_FCTS  *row_fcts, *col_fcts;
  bool            Lb_adv;

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

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

  /* <<< allocate a new fill_info */

  fill_info = MEM_CALLOC(1, FILL_INFO);

  ROW_CHAIN_INIT(fill_info);
  COL_CHAIN_INIT(fill_info);

  fill_info->next = first_fill_info;
  first_fill_info = fill_info;

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

  fill_info->krn_blk_type =
    MAX(oinfo->LALt_type,
	MAX(oinfo->Lb_type,
	    MAX(oinfo->c_type, MATENT_REAL)));

  row_fcts_fast = fill_info->row_quad_fast;
  col_fcts_fast = fill_info->col_quad_fast;

  fill_info->parametric = parametric;
  if (parametric) {
    not_all_param = parametric->not_all;
  }

  /* Special case, not passed down by the user. Actually, we also
   * should cross-check all other symmetry flags.
   */
  fill_info->c_symmetric = (col_fcts == row_fcts);

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

  /* <<< decode application needs */

  row_fcts_fast[2] = col_fcts_fast[2] = NULL;
  pre_fl[2] = qud_fl[2] = 0;
  init_row_fcts[2] = init_col_fcts[2] = 0x00;

  if (fill_info->op_info.LALt.real_dd) {

    if (oinfo->LALt_pw_const) {
      pre_fl[2]  |= FLAG_LALt;
      fill_info->q11_cache =
	get_q11_psi_phi(row_fcts, col_fcts, oinfo->quad[2]);
      if (INIT_ELEMENT_NEEDED(fill_info->q11_cache)) {
	*el_mat_idx |= INIT_EL;
      }
	
      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 (!fill_info->op_info.LALt_pw_const || parametric) {

      /* We will need the slow routines on some elements at least. */
      qud_fl[2] |= FLAG_LALt;
      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;
      }
    }
  }

  row_fcts_fast[1] = col_fcts_fast[1] = NULL;
  pre_fl[1] = qud_fl[1] = 0;
  init_row_fcts[1] = 0x00;
  init_col_fcts[1] = 0x00;

  Lb_adv = oinfo->advection_field != NULL;
  if (Lb_adv) {
    const BAS_FCTS *adv_fcts;
    CHAIN_INIT(fill_info->adv_cache);
    CHAIN_FOREACH(adv_fcts, oinfo->adv_fe_space->bas_fcts, const BAS_FCTS) {
      ADV_CACHE *adv_cache = MEM_CALLOC(1, ADV_CACHE);
      CHAIN_INIT(adv_cache);
      CHAIN_ADD_TAIL(fill_info->adv_cache, adv_cache);
    }
    if (INIT_ELEMENT_NEEDED(oinfo->adv_fe_space->bas_fcts)) {
      *el_mat_idx |= INIT_EL;
    }
  }

  if (fill_info->op_info.Lb0.real_dd) {
    if (oinfo->Lb0_pw_const) {
      if (parametric) {	
	if (!Lb_adv &&
	    pre_fl[2] && qud_fl[2] && oinfo->quad[1] == oinfo->quad[2]) {
	  /* just to keep the pre... flags in sync with the quad... flags */
	  pre_fl[2]   |= FLAG_LB0;
	  qud_fl[2]   |= FLAG_LB0;
	  init_row_fcts[2] |= INIT_PHI;
	} else {
	  pre_fl[1]   |= FLAG_LB0;
	  qud_fl[1]   |= FLAG_LB0;
	  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;
	  }
	}
      } else {
	pre_fl[1]  |= FLAG_LB0;
      }
      if (Lb_adv) {
	const BAS_FCTS *adv_fcts = oinfo->adv_fe_space->bas_fcts;
	ADV_CACHE *adv_cache = fill_info->adv_cache;
	const QUAD_TENSOR *qt = oinfo->quad_tensor[1];
	CHAIN_DO(adv_cache, ADV_CACHE) {
	  const QUAD *quad = qt ? qt->quad : oinfo->quad[1];

	  adv_cache->q010 =
	    get_q010_eta_psi_phi(row_fcts, col_fcts, adv_fcts, quad);

	  qt       = qt ? DEP_CHAIN_NEXT(qt, const QUAD_TENSOR) : NULL;
	  adv_fcts = CHAIN_NEXT(adv_fcts, const BAS_FCTS);
	} CHAIN_WHILE(adv_cache, ADV_CACHE);
      } else {
	fill_info->q01_cache =
	  get_q01_psi_phi(row_fcts, col_fcts, oinfo->quad[1]);
	if (INIT_ELEMENT_NEEDED(fill_info->q01_cache)) {
	  *el_mat_idx |= INIT_EL;
	}
      }

      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");
      }
    } else {
      /* We will need the slow routines on some elements at least. */

      if (!Lb_adv &&
	  !pre_fl[2] && qud_fl[2] && oinfo->quad[1] == oinfo->quad[2]) {
	qud_fl[2]   |= FLAG_LB0;
	init_row_fcts[2] |= INIT_PHI;
      } else {
	qud_fl[1]   |= FLAG_LB0;
	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 (fill_info->op_info.Lb1.real_dd) {
    if (oinfo->Lb1_pw_const) {
      if (parametric) {
	if (!Lb_adv &&
	    pre_fl[2] && qud_fl[2] && oinfo->quad[1] == oinfo->quad[2]) {
	  pre_fl[2]   |= FLAG_LB1;
	  qud_fl[2]   |= FLAG_LB1;
	  init_col_fcts[2] |= INIT_PHI;
	} else {
	  pre_fl[1]   |= FLAG_LB1;
	  qud_fl[1]   |= FLAG_LB1;
	  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;
	  }
	}
      } else {
	pre_fl[1]  |= FLAG_LB1;
      }

      if (Lb_adv) {
	const BAS_FCTS *adv_fcts = oinfo->adv_fe_space->bas_fcts;
	ADV_CACHE *adv_cache = fill_info->adv_cache;
	const QUAD_TENSOR *qt = oinfo->quad_tensor[1];
	CHAIN_DO(adv_cache, ADV_CACHE) {
	  const QUAD *quad = qt ? qt->quad : oinfo->quad[1];

	  adv_cache->q100 =
	    get_q100_eta_psi_phi(row_fcts, col_fcts, adv_fcts, quad);

	  qt       = qt ? DEP_CHAIN_NEXT(qt, const QUAD_TENSOR) : NULL;
	  adv_fcts = CHAIN_NEXT(adv_fcts, const BAS_FCTS);
	} CHAIN_WHILE(adv_cache, ADV_CACHE);
      } else {
	fill_info->q10_cache =
	  get_q10_psi_phi(row_fcts, col_fcts, oinfo->quad[1]);
	if (INIT_ELEMENT_NEEDED(fill_info->q10_cache)) {
	  *el_mat_idx |= INIT_EL;
	}
      }

      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");
      }
    } else {
      /* We will need the slow routines on some elements at
       * least.
       */
      if (!Lb_adv &&
	  !pre_fl[2] && qud_fl[2] && oinfo->quad[1] == oinfo->quad[2]) {
	qud_fl[2] |= FLAG_LB1;
	init_col_fcts[2] |= INIT_PHI;
      } else {
	qud_fl[1] |= FLAG_LB1;
	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;
	}
      }
    }
  }

  row_fcts_fast[0] = col_fcts_fast[0] = NULL;
  pre_fl[0] = qud_fl[0] = 0;
  init_row_fcts[0] = init_col_fcts[0] = 0x00;

  if (fill_info->op_info.c.real_dd) {
    if (oinfo->c_pw_const) {
      if (parametric) {
	/* keep the pre... flags in sync with the quad... flags */
	if (pre_fl[2] && qud_fl[2] && oinfo->quad[0] == oinfo->quad[2]) {
	  pre_fl[2]   |= FLAG_C;
	  qud_fl[2]   |= FLAG_C;
	  init_row_fcts[2] |= INIT_PHI;
	  init_col_fcts[2] |= INIT_PHI;
	} else if (!Lb_adv &&
		   pre_fl[1] && qud_fl[1] && oinfo->quad[0] == oinfo->quad[1]) {
	  pre_fl[1]   |= FLAG_C;
	  qud_fl[1]   |= FLAG_C;
	  init_row_fcts[1] |= INIT_PHI;
	  init_col_fcts[1] |= INIT_PHI;
	} else {
	  pre_fl[0]   |= FLAG_C;
	  qud_fl[0]   |= FLAG_C;
	  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;
	  }
	}
      } else {
	pre_fl[0] |= FLAG_C;
      }
      fill_info->q00_cache =
	get_q00_psi_phi(row_fcts, col_fcts, oinfo->quad[0]);
      if (INIT_ELEMENT_NEEDED(fill_info->q00_cache)) {
	*el_mat_idx |= INIT_EL;
      }

      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");
      }
    } else {
      /* We will need the slow routines on some elements at
       * least. Only combine with 2nd and 1st order terms if those are
       * non-constant, too.
       */
      if (!pre_fl[2] && qud_fl[2] && oinfo->quad[0] == oinfo->quad[2]) {
	qud_fl[2]   |= FLAG_C;
	init_row_fcts[2] |= INIT_PHI;
	init_col_fcts[2] |= INIT_PHI;
      } else if (!Lb_adv &&
		 !pre_fl[1] && qud_fl[1] && oinfo->quad[0] == oinfo->quad[1]) {
	qud_fl[1]   |= FLAG_C;
	init_row_fcts[1] |= INIT_PHI;
	init_col_fcts[1] |= INIT_PHI;
      } else {
	qud_fl[0]   |= FLAG_C;
	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;
	}
      }
    }
  }

  /* >>> */

  /* Allocate quad-fast objects in case we need the slow routines. */
  /* <<< allocate quad_fast objects */

  if (row_fcts == col_fcts) {
    for (i = 0; i < 3; i++) {
      if (Lb_adv && i == 1) {
	continue;
      }
      if (fill_info->op_info.quad[i] &&
	  (init_row_fcts[i]|init_col_fcts[i])) {
	fill_info->row_quad_fast[i] = fill_info->col_quad_fast[i] =
	  get_quad_fast(row_fcts, fill_info->op_info.quad[i],
			init_row_fcts[i] | init_col_fcts[i]);
      }
    }
  } else {
    for (i = 0; i < 3; i++) {
      if (Lb_adv && i == 1) {
	continue;
      }
      if (fill_info->op_info.quad[i] &&
	  (init_row_fcts[i]|init_col_fcts[i])) {
	fill_info->row_quad_fast[i] =
	  get_quad_fast(
	    row_fcts, fill_info->op_info.quad[i], init_row_fcts[i]);
	fill_info->col_quad_fast[i] =
	  get_quad_fast(
	    col_fcts, fill_info->op_info.quad[i], init_col_fcts[i]);
      }
    }
  }
  if (Lb_adv) {
    const BAS_FCTS *adv_fcts = oinfo->adv_fe_space->bas_fcts;
    ADV_CACHE *adv_cache = fill_info->adv_cache;
    const QUAD_TENSOR *qt = oinfo->quad_tensor[1];
    CHAIN_DO(adv_cache, ADV_CACHE) {
      const QUAD *quad = qt ? qt->quad : oinfo->quad[1];
      adv_cache->quad = quad;
      if (init_row_fcts[1]|init_col_fcts[1]) {
	adv_cache->adv_quad_fast = get_quad_fast(adv_fcts, quad, INIT_PHI);
	if (row_fcts == col_fcts) {
	  adv_cache->row_quad_fast = adv_cache->col_quad_fast =
	    get_quad_fast(row_fcts, quad, init_row_fcts[1]|init_col_fcts[1]);
	} else {
	  adv_cache->row_quad_fast =
	    get_quad_fast(row_fcts, quad, init_row_fcts[1]);
	  adv_cache->col_quad_fast =
	    get_quad_fast(row_fcts, quad, init_col_fcts[1]);
	}
      }
      adv_cache->adv_field      = MEM_ALLOC(quad->n_points_max, REAL_D);
      adv_cache->adv_field_size = quad->n_points_max;
      qt       = qt ? DEP_CHAIN_NEXT(qt, const QUAD_TENSOR) : NULL;
      adv_fcts = CHAIN_NEXT(adv_fcts, const BAS_FCTS);
    } CHAIN_WHILE(adv_cache, ADV_CACHE);
  }

/* >>> */

  /* >>> */

  return fill_info;
}

/* >>> */

static inline 
FILL_INFO *get_fill_info(const OPERATOR_INFO *oinfo_orig,
			 const FE_SPACE *row_fe_space,
			 const FE_SPACE *col_fe_space,
			 MATENT_TYPE blk_type,
			 unsigned int el_mat_idx)
{
  static void
    (*assign_ass_fct_table[N_OP_BLOCK_TYPES])(FILL_INFO *fill_info,
					      const OPERATOR_INFO *oinfo,
					      U_CHAR pre_fl[3],
					      U_CHAR qud_fl[3]) = {
    /* [OP_TYPE_SS] = */ SS_AI_assign_assemble_fcts,
#if VECTOR_BASIS_FUNCTIONS
    /* [OP_TYPE_SV] = */ SV_AI_assign_assemble_fcts,
    /* [OP_TYPE_VS] = */ VS_AI_assign_assemble_fcts,
    /* [OP_TYPE_CV] = */ CV_AI_assign_assemble_fcts,
    /* [OP_TYPE_VC] = */ VC_AI_assign_assemble_fcts,
    /* [OP_TYPE_VV] = */ VV_AI_assign_assemble_fcts
#endif
  };
  U_CHAR pre_fl[3], qud_fl[3];
  OPERATOR_INFO oinfo[1];
  FILL_INFO *fill_info;
  OP_BLOCK_TYPE op_type;

  /* Fill in fe-space, default quadratures etc. */
  if (!unify_op_info(oinfo, oinfo_orig, row_fe_space, col_fe_space, blk_type)) {
    return NULL;
  }

  /* block-type independent part */
  fill_info = __get_fill_info(&el_mat_idx, oinfo, pre_fl, qud_fl);

  /* Do the right thing, depending on the block type, we still do not
   * allocate the element matrix here.
   */
  op_type = operator_type(oinfo->row_fe_space, oinfo->col_fe_space);
  assign_ass_fct_table[op_type](fill_info, oinfo, pre_fl, qud_fl);
  fill_info->krn_blk_type = blk_type;

  if (fill_info->dflt_zero_order)
    el_mat_idx |= ZERO_ORDER;
  if (fill_info->dflt_first_order)
    el_mat_idx |= FRST_ORDER;
  if (fill_info->dflt_second_order)
    el_mat_idx |= SCND_ORDER;

  fill_info->el_matrix_fct = el_matrix_table[el_mat_idx];
  fill_info->el_mat_idx = el_mat_idx;

  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_fe_space->bas_fcts->n_bas_fcts_max,	\
			col_fe_space->bas_fcts->n_bas_fcts_max,	\
			TYPE)
    MAT_EMIT_BODY_SWITCH(fill_info->krn_blk_type);      
  }
  return fill_info;
}

/* >>> */

/* <<< fill_matrix_info_ext() */

const EL_MATRIX_INFO *
fill_matrix_info_ext(EL_MATRIX_INFO *matrix_info,
		     const OPERATOR_INFO *app_oinfo,
		     const BNDRY_OPERATOR_INFO *app_bndry_oinfo,
		     ...)
{
  FUNCNAME("fill_matrix_info_ext");
  OPERATOR_INFO oinfo[1];
  const BNDRY_OPERATOR_INFO *bop_info;
  const AI_BNDRY_EL_MAT_FCT *wall_el_mat_fct = NULL;
  const BNDRY_FILL_INFO     *wall_fill_info = NULL;
  const EL_MATRIX_FCT       *neigh_el_mat_fcts = NULL;
  const BNDRY_FILL_INFO     *neigh_fill_info = NULL;
  FILL_INFO      *fill_info;
  int            n_bop;
  BNDRY_OP       bndry_op[N_BNDRY_TYPES];
  unsigned int   el_mat_idx = 0;
  unsigned int   order_flags = 0;
  const FE_SPACE *row_fe_space;
  const FE_SPACE *col_fe_space;
  FLAGS          fill_flag;
  int            i;
  MATENT_TYPE    blk_type, bop_blk_type;
  va_list        ap;

  TEST_EXIT(app_oinfo != NULL || app_bndry_oinfo != NULL,
	    "operator_info or bop_info must be != NULL.\n");

  /* for boundary operator-infos we must first determine the
   * block-matrix type of the element matrix.
   */
  blk_type = MATENT_REAL;

  if (app_oinfo) {
    row_fe_space  = app_oinfo->row_fe_space;
    col_fe_space  = app_oinfo->col_fe_space;
    fill_flag     = app_oinfo->fill_flag;

    if (app_oinfo->LALt.real_dd) {      
      blk_type = MAX(app_oinfo->LALt_type, blk_type);
    }
    if (app_oinfo->Lb0.real_dd || app_oinfo->Lb1.real_dd) {
      blk_type = MAX(app_oinfo->Lb_type, blk_type);
    }
    if (app_oinfo->c.real_dd) {
      blk_type = MAX(app_oinfo->c_type, blk_type);
    }
    TEST_EXIT(blk_type >= MATENT_REAL && blk_type <= MATENT_REAL_DD,
	      "Unknown matrix type: %d\n", blk_type);

  } else { /* bop_info != NULL is a must here */
    TEST_EXIT(app_bndry_oinfo != NULL,
	      "Either operator_info _AND_ bop_info == NULL.\n");
    bop_info = app_bndry_oinfo;
    row_fe_space  = bop_info->row_fe_space;
    col_fe_space  = bop_info->col_fe_space;
    fill_flag     = bop_info->fill_flag;
  }
  if (!col_fe_space) {
    col_fe_space = row_fe_space;
  }

  bop_blk_type = MATENT_REAL;
  n_bop = 0;
  va_start(ap, app_bndry_oinfo);
  for (bop_info = app_bndry_oinfo;
       bop_info != NULL;
       bop_info = va_arg(ap, BNDRY_OPERATOR_INFO *)) {
    if (!BNDRY_FLAGS_IS_INTERIOR(bop_info->bndry_type) &&
	!bop_info->discontinuous) {
      n_bop++;
    }
    if (bop_info->LALt.real_dd) {
      bop_blk_type = MAX(bop_info->LALt_type, bop_blk_type);
    }
    if (bop_info->Lb0.real_dd || bop_info->Lb1.real_dd) {
      bop_blk_type = MAX(bop_info->Lb_type, bop_blk_type);
    }
    if (bop_info->c.real_dd) {
      bop_blk_type = MAX(bop_info->c_type, bop_blk_type);
    }
  }
  va_end(ap);

  if (app_oinfo) {
    TEST_EXIT(bop_blk_type <= blk_type,
	      "Boundary operator block matrix type must not be less symmetric "
	      "than operator info block matrix types.\n");
    bop_blk_type = blk_type;
  } else {
    blk_type = bop_blk_type;
  }
  TEST_EXIT(n_bop < N_BNDRY_TYPES,
	    "Too many boundary operators: %d (supported: %d)\n",
	    n_bop, N_BNDRY_TYPES);

  n_bop = 0;
  va_start(ap, app_bndry_oinfo);
  for (bop_info = app_bndry_oinfo;
       bop_info != NULL;
       bop_info = va_arg(ap, BNDRY_OPERATOR_INFO *)) {

    fill_flag |= bop_info->fill_flag;

    TEST_EXIT(FE_SPACE_EQ_P(bop_info->row_fe_space, row_fe_space) &&
	      (bop_info->col_fe_space == NULL ||
	       FE_SPACE_EQ_P(bop_info->col_fe_space, col_fe_space)),
	      "Fe-space inconsistency.\n");

    if (BNDRY_FLAGS_IS_INTERIOR(bop_info->bndry_type)) {
      if (bop_info->discontinuous) {
	/* make sure we have a DOF-admin with DOFs at the vertices to
	 * be able to orient faces across neighbouring elements.
	 */
	get_vertex_admin(row_fe_space->mesh, ADM_PERIODIC);
	fill_flag |= FILL_NEIGH;
	neigh_fill_info = AI_get_neigh_fill_info(bop_info, bop_blk_type);
	TEST_EXIT(neigh_fill_info,
		  "ERROR: could not create jump fill-info.\n");
	neigh_el_mat_fcts = neigh_fill_info->neigh_el_mat_fcts;
	TEST_EXIT(neigh_el_mat_fcts != NULL,
		  "ERROR: could not create jump fill-info (el-fct == NULL).\n");
      } else {
	wall_fill_info = AI_get_bndry_fill_info(bop_info, bop_blk_type);
	TEST_EXIT(wall_fill_info,
		  "ERROR: could not create wall fill-info.\n");
	wall_el_mat_fct = wall_fill_info->el_matrix_fct;
	TEST_EXIT(wall_el_mat_fct != NULL,
		  "ERROR: could not create wall fill-info (el-fct == NULL).\n");
	el_mat_idx |= WALL_FLAG;
      }
    } else {
      for (i = n_bop; i > 0; i--) {
	/* Build a sorted list of boundary operators */
	if (BNDRY_FLAGS_CMP(bndry_op[i-1].bndry_type,
			    bop_info->bndry_type) > 0) {
	  bndry_op[i] = bndry_op[i-1];
	} else {
	  break;
	}
      }
      BNDRY_FLAGS_CPY(bndry_op[i].bndry_type, bop_info->bndry_type);
      bndry_op[i].fill_info  = AI_get_bndry_fill_info(bop_info, bop_blk_type);
      TEST_EXIT(bndry_op[i].fill_info,
		"ERROR: could not create boundary fill-info (no. %d).\n",
		n_bop);
      bndry_op[i].el_mat_fct = bndry_op[i].fill_info->el_matrix_fct;
      TEST_EXIT(bndry_op[i].el_mat_fct != NULL,
		"ERROR: could not create boundary fill-info (no. %d).\n",
		n_bop);
      n_bop++;

      fill_flag |= FILL_MACRO_WALLS;
      el_mat_idx |= BNDRY_FLAG;
    }
  }
  va_end(ap);

  unify_op_info(oinfo, app_oinfo, row_fe_space, col_fe_space, blk_type);
  fill_info = find_fill_info(oinfo, blk_type, bndry_op, n_bop,
			     wall_el_mat_fct, wall_fill_info);

  if (fill_info != NULL) {
    row_fe_space = fill_info->op_info.row_fe_space;
    col_fe_space = fill_info->op_info.col_fe_space;
  } else {
    FILL_INFO *row_chain, *col_chain, *chain_info;
    EL_MATRIX *elm;
    const FE_SPACE  *row_fesp, *col_fesp;
    OPERATOR_INFO *oinfoptr;

    if (app_oinfo) {
      /* reset the "unification" */
      *oinfo = *app_oinfo;
      oinfoptr = oinfo;
      for (i = 0; i < 3; i++) {
	if (oinfo->quad_tensor[i]) {
	  oinfo->quad[i] = oinfo->quad_tensor[i]->quad;
	}
      }
    } else {
      oinfoptr = NULL;
    }

    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_fill_info(oinfoptr, row_fe_space, col_fe_space, blk_type, el_mat_idx);
    order_flags = fill_info->el_mat_idx & ORDER_MASK;

    /* Allocate a (block-) element-matrix; get_fill_info() does not do
     * this.
     */
    fill_info->el_mat = get_el_matrix(row_fe_space, col_fe_space, blk_type);

    /* See if we have a chain of fe-spaces and add additional blocks
     * accordingly. Generate the first row.
     */
    row_fesp  = row_fe_space;
    col_chain = fill_info;
    elm       = fill_info->el_mat;
    /* rest of row */
    CHAIN_FOREACH(col_fesp, col_fe_space, const FE_SPACE) {
      elm     = ROW_CHAIN_NEXT(elm, EL_MATRIX);
      if (oinfoptr != NULL) {
	for (i = 0; i < 3; i++) {
	  if (oinfo->quad_tensor[i]) {
	    oinfo->quad_tensor[i] =
	      ROW_CHAIN_NEXT(oinfo->quad_tensor[i], QUAD_TENSOR);
	    oinfo->quad[i] = oinfo->quad_tensor[i]->quad;
	  }
	}
      }
      row_chain =
	get_fill_info(oinfoptr, row_fesp, col_fesp, blk_type, el_mat_idx);
      order_flags |= row_chain->el_mat_idx & ORDER_MASK;

      ROW_CHAIN_ADD_TAIL(fill_info, row_chain);
      row_chain->el_mat = elm;
    }
    /* reset to the list head */
    elm = fill_info->el_mat;
    if (oinfoptr != NULL) {
      for (i = 0; i < 3; i++) {
	oinfo->quad_tensor[i] = app_oinfo->quad_tensor[i];
      }
    }
    col_fesp  = col_fe_space;
    row_chain = fill_info;
    /* Then generate all following rows */
    CHAIN_FOREACH(row_fesp, row_fe_space, const FE_SPACE) {
      elm = COL_CHAIN_NEXT(elm, EL_MATRIX);
      if (oinfoptr != NULL) {
	for (i = 0; i < 3; i++) {
	  if (oinfo->quad_tensor[i]) {
	    oinfo->quad_tensor[i] =
	      COL_CHAIN_NEXT(oinfo->quad_tensor[i], QUAD_TENSOR);
	    oinfo->quad[i] = oinfo->quad_tensor[i]->quad;
	  }
	}
      }
      col_chain =
	get_fill_info(oinfoptr, row_fesp, col_fesp, blk_type, el_mat_idx);
      order_flags |= col_chain->el_mat_idx & ORDER_MASK;
      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, FILL_INFO);
	if (oinfoptr != NULL) {
	  for (i = 0; i < 3; i++) {
	    if (oinfo->quad_tensor[i]) {
	      oinfo->quad_tensor[i] =
		ROW_CHAIN_NEXT(oinfo->quad_tensor[i], QUAD_TENSOR);
	      oinfo->quad[i] = oinfo->quad_tensor[i]->quad;
	    }
	  }
	}
	chain_info = get_fill_info(
	  oinfoptr, row_fesp, col_fesp, blk_type, el_mat_idx);
	order_flags |= chain_info->el_mat_idx & ORDER_MASK;
	ROW_CHAIN_ADD_TAIL(col_chain, chain_info);
	COL_CHAIN_ADD_TAIL(row_chain, chain_info);
	chain_info->el_mat = elm;	
      }
      /* roll over to the list-head */
      elm       = ROW_CHAIN_NEXT(elm, EL_MATRIX);
      if (oinfoptr) {
	for (i = 0; i < 3; i++) {
	  oinfo->quad_tensor[i] =
	    oinfo->quad_tensor[i]
	    ? ROW_CHAIN_NEXT(oinfo->quad_tensor[i], QUAD_TENSOR) : NULL;
	}
      }
      row_chain = ROW_CHAIN_NEXT(row_chain, FILL_INFO);
    }

    ROW_CHAIN_DO(fill_info, FILL_INFO) {
      COL_CHAIN_DO(fill_info, FILL_INFO) {
	fill_info->el_mat_idx |= order_flags;
	fill_info->el_matrix_fct = el_matrix_table[fill_info->el_mat_idx];

	if (order_flags & SCND_ORDER && fill_info->dflt_second_order == NULL) {
	  fill_info->dflt_second_order = do_nothing;
	}
	if (order_flags & FRST_ORDER && fill_info->dflt_first_order == NULL) {
	  fill_info->dflt_first_order = do_nothing;
	}
	if (order_flags & ZERO_ORDER && fill_info->dflt_zero_order == NULL) {
	  fill_info->dflt_zero_order = do_nothing;
	}
      } COL_CHAIN_WHILE(fill_info, FILL_INFO);
    } ROW_CHAIN_WHILE(fill_info, FILL_INFO);

    if (n_bop) {
      /* We assume that all boundary contributions are already
       * chained-up correctly, so we can use a simpler loop mechnism:
       */
      ROW_CHAIN_DO(fill_info, FILL_INFO) {
	COL_CHAIN_DO(fill_info, FILL_INFO) {
	  fill_info->n_bndry_op = n_bop;
	  fill_info->bndry_op  = MEM_ALLOC(n_bop, BNDRY_OP);
	  memcpy(fill_info->bndry_op, bndry_op, n_bop*sizeof(BNDRY_OP));
	  /* advance all boundary operators */
	  for (i = 0; i < n_bop; i++) {
	    bndry_op[i].fill_info =
	      COL_CHAIN_NEXT(bndry_op[i].fill_info, BNDRY_FILL_INFO);
	  }
	} COL_CHAIN_WHILE(fill_info, FILL_INFO);
	/* advance all boundary operators */
	for (i = 0; i < n_bop; i++) {
	  bndry_op[i].fill_info =
	    ROW_CHAIN_NEXT(bndry_op[i].fill_info, BNDRY_FILL_INFO);
	}
      } ROW_CHAIN_WHILE(fill_info, FILL_INFO);
    }

    if (wall_el_mat_fct) {
      /* We assume that the wall contributions are already chained-up
       * correctly, so we can use a simpler loop mechnism:
       */
      ROW_CHAIN_DO(fill_info, FILL_INFO) {
	COL_CHAIN_DO(fill_info, FILL_INFO) {
	  fill_info->wall_el_mat_fct = wall_fill_info->el_matrix_fct;
	  fill_info->wall_fill_info  = wall_fill_info;
	  wall_fill_info =
	    COL_CHAIN_NEXT(wall_fill_info, const BNDRY_FILL_INFO);
	} COL_CHAIN_WHILE(fill_info, FILL_INFO);
	wall_fill_info = ROW_CHAIN_NEXT(wall_fill_info, const BNDRY_FILL_INFO);
      } ROW_CHAIN_WHILE(fill_info, FILL_INFO);
    }
  }

  if (!matrix_info) {
    matrix_info = MEM_CALLOC(1, EL_MATRIX_INFO);
  } else {
    memset(matrix_info, 0, sizeof(EL_MATRIX_INFO));
  }

  matrix_info->row_fe_space = row_fe_space;
  matrix_info->col_fe_space = col_fe_space;

  matrix_info->krn_blk_type = fill_info->krn_blk_type;

  fill_flag |= row_fe_space->bas_fcts->fill_flags;
  fill_flag |= col_fe_space->bas_fcts->fill_flags;
  for (i = 0; i < 3; i++) {
    if (fill_info->op_info.quad[i]) {
      fill_flag |= fill_info->op_info.quad[i]->fill_flags;
    }
  }
  matrix_info->fill_flag = fill_flag;
  if (!BNDRY_FLAGS_IS_INTERIOR(oinfo->dirichlet_bndry)) {
    /* Prepare for Dirichlet boundary conditions built into the
     * matrix. Note: this does not affect the element matrix, but just
     * the fill-flags, and matrix_info->dirichlet_bndry is honoured by
     * update_matrix().
     */
    BNDRY_FLAGS_CPY(matrix_info->dirichlet_bndry, oinfo->dirichlet_bndry);    
    matrix_info->fill_flag |= FILL_BOUND;
  }
  if (row_fe_space->mesh->is_periodic
      && !(row_fe_space->admin->flags & ADM_PERIODIC)) {
    matrix_info->fill_flag |= FILL_NON_PERIODIC;
  }
  if (n_bop || wall_el_mat_fct) {
    matrix_info->fill_flag |= FILL_MACRO_WALLS;
  }

  matrix_info->factor = 1.0;
  matrix_info->el_matrix_fct = fill_info->el_matrix_fct;

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

  matrix_info->fill_info = fill_info;

  matrix_info->neigh_el_mat_fcts = neigh_el_mat_fcts;
  matrix_info->neigh_fill_info   = (void *)neigh_fill_info;

  return matrix_info;
}

/* >>> */

/* <<< fill_matrix_info() */

const EL_MATRIX_INFO *
fill_matrix_info(const OPERATOR_INFO *operator_info,
		 EL_MATRIX_INFO *matrix_info)
{
  return fill_matrix_info_ext(matrix_info, operator_info, NULL);
}

/* >>> */

/* >>> */

#endif /* EMIT_SS_VERSIONS */
