/*--------------------------------------------------------------------------*/
/* 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_bndry_fcts.c.in                                       */
/*                                                                          */
/* description: assemblage of boundary integral contributions               */
/*                                                                          */
/*--------------------------------------------------------------------------*/
/*                                                                          */
/* 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 (2006-2009)                                          */
/*                                                                          */
/*--------------------------------------------------------------------------*/

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

#line 29 "../../../../alberta/src/Common/assemble_bndry_fcts.c.in"

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

/* Throughout this file:
 *
 * row_fcts == row fe-space, meaning the test space
 * col_fcts == column fe-space, meaning the ansatz space
 *
 * First order terms: e.g.:
 *
 * quad_01 == Lb0 assembly, derivative is applied to col_fcts, i.e. to the
 * space of ansatz functions.
 *
 * quad_10 == Lb1 assembly, derivative is applied to row_fcts, i.e. to the
 * space of test functions.
 */

/* V -- vector valued basis functions
 * C -- Cartesian product space (trivially vector valued)
 * S -- Scalar space
 *
 * VEC_PFX is SS for C-C and S-S combinations, otherwise VV, VC, CV,
 * VS or SV.
 *
 * The boolean values have the following meaning:
 *
 * row_fcts_V true: vector valued row basis functions (trial space)
 * col_fcts_V true: vector valued column basis functions (ansatz space)
 * row_fcts_C true: Cartesian product row basis functions (pseudo vector valued)
 * col_fcts_C true: Cartesian product column basis functions
 *
 * The VS_... and VC_... variants differ if the application supplied
 * integral kernel of the operator (e.g. LALt etc.) is a REAL_D
 * block-matrix, for VS_... this means to contract the components of
 * the kernel with the components of the vector valued basis
 * functions, for VC_... the resulting matrix is again a REAL_D block
 * matrix.
 */

#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 row_fcts_V HAVE_ROW_FCTS_V_TYPE
#define col_fcts_V HAVE_COL_FCTS_V_TYPE
#define row_fcts_C HAVE_ROW_FCTS_C_TYPE
#define col_fcts_C HAVE_COL_FCTS_C_TYPE

#define HAVE_DM_DST_TYPE 1
#define HAVE_DM_SRC_TYPE 1

#undef NAME
#undef INLINE_NAME
#undef NAMEPFX
#define NAMEPFX _AI_CONCAT(VEC_PFX, _DMDM_)
#define NAME(base)           _AI_CONCAT(NAMEPFX, base)
#define MULTIPLEX_NAME(base) _AI_CONCAT(NAME(base),_multiplex)

/* <<< condensation stuff for vector-valued basis functions */

static inline void
DM_clear_tmp_mat(REAL_D  **mat, const BNDRY_FILL_INFO *fill_info)
{
  int i, j;

  for (i = 0; i < fill_info->el_mat->n_row; i++) {
    for (j = 0; j < fill_info->el_mat->n_col; j++) {
      DMSET_DOW(0.0, mat[i][j]);
    }
  }
}

#if !HAVE_DM_DST_TYPE
static inline void
DM_clear_tmp_mat(REAL_D **mat, const BNDRY_FILL_INFO *fill_info)
{
  int i, j;

  for (i = 0; i < fill_info->el_mat->n_row; i++) {
    for (j = 0; j < fill_info->el_mat->n_col; j++) {
      DMSET_DOW(0.0, mat[i][j]);
    }
  }
}
#endif

static inline REAL_D **
DM_assign_matrices(void **vmat,
			      REAL ***real_mat,
			      REAL_D ***real_d_mat,
			      const BNDRY_FILL_INFO *fill_info,
			      /* bool row_fcts_V, bool col_fcts_V, */
			      bool row_fcts_V_const, bool col_fcts_V_const)
{
  REAL_D **mat = NULL;

  if (row_fcts_V || col_fcts_V) {

    *real_mat = (REAL **)vmat;

    if (col_fcts_V_const && row_fcts_V_const) {
      mat = (REAL_D **)fill_info->scl_el_mat;
      DM_clear_tmp_mat(mat, fill_info);
    } else if (row_fcts_V_const /* && col_fcts_V */) {
      if (row_fcts_V) {
	*real_d_mat = (REAL_D **)fill_info->scl_el_mat;
	DM_clear_tmp_mat(*real_d_mat, fill_info);
      } else {
	*real_d_mat = (REAL_D **)vmat;
      }
    } else if (col_fcts_V_const /* && row_fcts_V */) {
      if (col_fcts_V) {
	*real_d_mat = (REAL_D **)fill_info->scl_el_mat;
	DM_clear_tmp_mat(*real_d_mat, fill_info);
      } else {
	*real_d_mat = (REAL_D **)vmat;
      }
    }
  } else {
    mat = (REAL_D **)vmat;
  }

  return mat;
}

/* <<< VV_condense_el_mat */

/* Row- and column fe-space has vector valued basis functions. */
static inline void
VV_DM_condense_el_mat(REAL **mat,
				 const BNDRY_FILL_INFO *fill_info,
				 const QUAD_FAST *row_fcts_qfast,
				 const QUAD_FAST *col_fcts_qfast,
				 bool symmetric, bool antisym)
{
  REAL_D **tmp_mat = (REAL_D **)fill_info->scl_el_mat;
  int        i, j, n_row, n_col;
  REAL val;

  n_row = row_fcts_qfast->n_bas_fcts;

  if (symmetric) {
    col_fcts_qfast = row_fcts_qfast;
    n_col = n_row;
    for (i = 0; i < n_row; i++) {
      const REAL *row_fcts_d = row_fcts_qfast->phi_d[i];
      mat[i][i] +=
	DMGRAMSCP_DOW(
	  /**/ tmp_mat[i][i], row_fcts_d, row_fcts_d);
      for (j = i+1; j < n_col; j++) {
	const REAL *col_fcts_d = col_fcts_qfast->phi_d[j];

	val =
	  DMGRAMSCP_DOW(
	    /**/ tmp_mat[i][j], row_fcts_d, col_fcts_d);
	mat[i][j] += val;
	mat[j][i] += val;
      }
    }
  } else if (antisym) {
    col_fcts_qfast = row_fcts_qfast;
    n_col = n_row;
    for (i = 0; i < n_row; i++) {
      const REAL *row_fcts_d = row_fcts_qfast->phi_d[i];

      for (j = i+1; j < n_col; j++) {
	const REAL *col_fcts_d = col_fcts_qfast->phi_d[j];
	val =
	  DMGRAMSCP_DOW(
	    /**/ tmp_mat[i][j], row_fcts_d, col_fcts_d);
	mat[i][j] += val;
	mat[j][i] -= val;
      }
    }
  } else {
    n_col = col_fcts_qfast->n_bas_fcts;
    for (i = 0; i < n_row; i++) {
      for (j = 0; j < n_col; j++) {
	const REAL *row_fcts_d = row_fcts_qfast->phi_d[i];
	const REAL *col_fcts_d = col_fcts_qfast->phi_d[j];
	mat[i][j] +=
	  DMGRAMSCP_DOW(
	    /**/ tmp_mat[i][j], row_fcts_d, col_fcts_d);
      }
    }
  }
}

/* >>> */

/* <<< VC_condense_el_mat */

/* Vector-valued row fe-space, column fe-space is a Cartesian product space */
static inline void
VC_DM_condense_el_mat(REAL_D **mat,
				 const BNDRY_FILL_INFO *fill_info,
				 const QUAD_FAST *row_fcts_qfast,
				 const QUAD_FAST *col_fcts_qfast)
{
  REAL_D **tmp_mat = (REAL_D **)fill_info->scl_el_mat;
  int        i, j, n_row, n_col;

  n_row = row_fcts_qfast->n_bas_fcts;
  n_col = col_fcts_qfast->n_bas_fcts;

  for (i = 0; i < n_row; i++) {
    const REAL *row_fcts_d = row_fcts_qfast->phi_d[i];
    for (j = 0; j < n_col; j++) {
      DMTV_DOW(/**/ tmp_mat[i][j], row_fcts_d, mat[i][j]);
    }
  }
}

/* >>> */

/* <<< CV_condense_el_mat */

/* Vector-valued column fe-space, row fe-space is a Cartesian product space */
static inline void
CV_DM_condense_el_mat(REAL_D **mat,
				 const BNDRY_FILL_INFO *fill_info,
				 const QUAD_FAST *row_fcts_qfast,
				 const QUAD_FAST *col_fcts_qfast)
{
  REAL_D **tmp_mat = (REAL_D **)fill_info->scl_el_mat;
  int        i, j, n_row, n_col;

  n_row = row_fcts_qfast->n_bas_fcts;
  n_col = col_fcts_qfast->n_bas_fcts;

  for (j = 0; j < n_col; j++) {
    const REAL *col_fcts_d = col_fcts_qfast->phi_d[j];
    for (i = 0; i < n_row; i++) {

      DMV_DOW(/**/ tmp_mat[i][j], col_fcts_d, mat[i][j]);
    }
  }
}

/* >>> */

/* <<< SV_condense_el_mat */

/* Vector-valued column fe-space, row fe-space is a scalar space,
 * e.g. in the case of a divergence constraint.
 *
 * This only makes sense for operators with a REAL_D coefficient
 * matrix. Probably this will really only happen for the divergence
 * constraint of a Stokes-problem.
 */
static inline void
SV_DM_condense_el_mat(REAL **mat,
		      const BNDRY_FILL_INFO *fill_info,
		      const QUAD_FAST *row_fcts_qfast,
		      const QUAD_FAST *col_fcts_qfast)
{
  REAL_D **tmp_mat = (REAL_D **)fill_info->scl_el_mat;
  int    i, j, n_row, n_col;

  n_row = row_fcts_qfast->n_bas_fcts;
  n_col = col_fcts_qfast->n_bas_fcts;

  for (j = 0; j < n_col; j++) {
    const REAL *col_fcts_d = col_fcts_qfast->phi_d[j];
    for (i = 0; i < n_row; i++) {

      mat[i][j] += SCP_DOW(tmp_mat[i][j], col_fcts_d);
    }
  }
}

# if HAVE_SCM_DST_TYPE
static inline void
SV_SCM_condense_el_mat(REAL **mat,
		       const BNDRY_FILL_INFO *fill_info,
		       const QUAD_FAST *row_fcts_qfast,
		       const QUAD_FAST *col_fcts_qfast)
{
  REAL **tmp_mat = (REAL **)fill_info->scl_el_mat;
  int  i, j, n_row, n_col;

  n_row = row_fcts_qfast->n_bas_fcts;
  n_col = col_fcts_qfast->n_bas_fcts;

  for (j = 0; j < n_col; j++) {
    const REAL *col_fcts_d = col_fcts_qfast->phi_d[j];
    for (i = 0; i < n_row; i++) {
      mat[i][j] += tmp_mat[i][j] * SUM_DOW(col_fcts_d);
    }
  }
}
# endif

/* >>> */

/* <<< VS_condense_el_mat */

/* Vector-valued row fe-space, column fe-space is a scalar space,
 * e.g. in the case of a divergence constraint.
 *
 * This only makes sense for operators with a REAL_D coefficient
 * matrix. Probably this will really only happen for the divergence
 * constraint of a Stokes-problem.
 */
static inline void
VS_DM_condense_el_mat(REAL **mat,
		      const BNDRY_FILL_INFO *fill_info,
		      const QUAD_FAST *row_fcts_qfast,
		      const QUAD_FAST *col_fcts_qfast)
{
  REAL_D **tmp_mat = (REAL_D **)fill_info->scl_el_mat;
  int    i, j, n_row, n_col;

  n_row = row_fcts_qfast->n_bas_fcts;
  n_col = col_fcts_qfast->n_bas_fcts;

  for (i = 0; i < n_row; i++) {
    const REAL *row_fcts_d = row_fcts_qfast->phi_d[i];
    for (j = 0; j < n_col; j++) {

      mat[i][j] += SCP_DOW(tmp_mat[i][j], row_fcts_d);
    }
  }
}

#if HAVE_SCM_DST_TYPE
static inline void
VS_SCM_condense_el_mat(REAL **mat,
		       const BNDRY_FILL_INFO *fill_info,
		       const QUAD_FAST *row_fcts_qfast,
		       const QUAD_FAST *col_fcts_qfast)
{
  REAL **tmp_mat = (REAL **)fill_info->scl_el_mat;
  int  i, j, n_row, n_col;

  n_row = row_fcts_qfast->n_bas_fcts;
  n_col = col_fcts_qfast->n_bas_fcts;

  for (i = 0; i < n_row; i++) {
    const REAL *row_fcts_d = row_fcts_qfast->phi_d[i];
    for (j = 0; j < n_col; j++) {
      mat[i][j] += tmp_mat[i][j] * SUM_DOW(row_fcts_d);
    }
  }
}
# endif

/* >>> */

/* Possibly condense the temporary matrix if either of the directions
 * was p.w. constant
 */
static inline void
DM_condense_matrices(void **mat,
				const BNDRY_FILL_INFO *fill_info,
				const QUAD_FAST *row_fcts_qfast,
				const QUAD_FAST *col_fcts_qfast,
				bool row_fcts_V_const, bool col_fcts_V_const)
{
  if (row_fcts_V && col_fcts_V) {
    if (row_fcts_V_const && col_fcts_V_const) {
      VV_DM_condense_el_mat((REAL **)mat, fill_info,
				       row_fcts_qfast, col_fcts_qfast,
				       false, false);
    } else if (row_fcts_V_const) {
      VS_DM_condense_el_mat(
	(REAL **)mat, fill_info, row_fcts_qfast, col_fcts_qfast);
    } else if (col_fcts_V_const) {
      SV_DM_condense_el_mat(
	(REAL **)mat, fill_info, row_fcts_qfast, col_fcts_qfast);
    }
  } else if (row_fcts_V && row_fcts_V_const) {
    if (col_fcts_C) {
      VC_DM_condense_el_mat((REAL_D **)mat, fill_info,
				       row_fcts_qfast, col_fcts_qfast);
#if HAVE_SCM_DST_TYPE || HAVE_DM_DST_TYPE
    } else {
      VS_DM_condense_el_mat((REAL **)mat, fill_info,
				       row_fcts_qfast, col_fcts_qfast);
#endif
    }
  } else if (col_fcts_V && col_fcts_V_const) {
    if (row_fcts_C) {
      CV_DM_condense_el_mat((REAL_D **)mat, fill_info,
				       row_fcts_qfast, col_fcts_qfast);
#if HAVE_SCM_DST_TYPE || HAVE_DM_DST_TYPE
    } else {
      SV_DM_condense_el_mat((REAL **)mat, fill_info,
				       row_fcts_qfast, col_fcts_qfast);
#endif
    }
  }
}

/* >>> */

/* <<< btv_tan() and utAv_tan(), block version */

#define NEED_DM_BTV_UTAV 1

/* <<< special scalar case, avoiding pointers to scalars */

#if NEED_SCM_BTV_UTAV
# undef NEED_SCM_BTV_UTAV

static inline REAL __SCMbtv_tan(int n_lambda,
				const REAL_B b, const REAL_B v,
				REAL r, int wall)
{
  int i;

  r = 0.0;
  for(i = 0; i < wall; i++)
    r += v[i] * b[i];
  for(++i; i < n_lambda; i++)
    r += v[i] * b[i];

  return r;
}

# define SCMbtv_tan(n_lambda, b, v, r, wall)		\
  ((r) = __SCMbtv_tan(n_lambda, b, v, 0.0, wall))

static inline REAL __SCMutAv_tan(int n_lambda,
				 const REAL_B u,
				 const REAL_B *A,
				 const REAL_B v,
				 REAL r,
				 int wall)
{
  int i;

  r = 0.0;
  for(i = 0; i < wall; i++) {
    r += u[i] * __SCMbtv_tan(n_lambda, A[i], v, 0.0, wall);
  }
  for(++i; i < n_lambda; i++) {
    r += u[i] * __SCMbtv_tan(n_lambda, A[i], v, 0.0, wall);
  }

  return r;
}

# define SCMutAv_tan(n_lambda, u, A, v, r, wall)	\
  ((r) = __SCMutAv_tan(n_lambda, u, A, v, 0.0, wall))

#endif /* NEED_SCM_BTV_UTAV */

/* >>> */

/* <<< REAL_D and REAL_DD case */

#if NEED_DM_BTV_UTAV
# undef NEED_DM_BTV_UTAV

static inline REAL * DMbtv_tan(
  int n_lambda,
  const REAL_D b[], const REAL_B v,
  REAL * r, int wall)
{
  int i;

  DMSET_DOW(0.0, r);
  for(i = 0; i < wall; i++)
    DMAXPY_DOW(v[i], /**/ b[i], r);
  for(++i; i < n_lambda; i++)
    DMAXPY_DOW(v[i], /**/ b[i], r);

  return r;
}

static inline REAL * DMutAv_tan(
  int n_lambda,
  const REAL_B u,
  const REAL_D (*A)[N_LAMBDA_MAX],
  const REAL_B v,
  REAL * r,
  int wall)
{
  int i;
  REAL_D tmp;

  DMSET_DOW(0.0, r);
  for(i = 0; i < wall; i++) {
    DMAXPY_DOW(
      u[i],
      /**/ DMbtv_tan(n_lambda, A[i], v, tmp, wall), r);
  }
  for(++i; i < n_lambda; i++) {
    DMAXPY_DOW(
      u[i],
      /**/ DMbtv_tan(n_lambda, A[i], v, tmp, wall), r);
  }

  return r;
}

#endif /* NEED_DM_BTV_UTAV */

/* >>> */

/* <<< utAv for vector/scalar - vector/scalar combinations */

/* Du and Dv are the Jacobians of some "really" vector valued
 * functions, _NOT_ factored into scalar part and direction. This
 * function is used in the case when the directions of the
 * vector-valued basis functions are not constant on an element.
 */
static inline REAL
VV_DMutAv_tan(
  int n_lambda,
  const REAL_DB Du,
  const REAL_D (*A)[N_LAMBDA_MAX],
  const REAL_DB Dv,
  int wall)
{
  int i, alpha, beta;
  REAL r;

  r = 0.0;
  for (alpha = 0; alpha < n_lambda; alpha++, alpha += alpha == wall) {
    for (beta = 0; beta < n_lambda; beta++, beta += beta == wall) {
# if HAVE_M_SRC_TYPE
      for (i = 0; i < DIM_OF_WORLD; i++) {
	int j;

	for (j = 0; j < DIM_OF_WORLD; j++) {
	  r += A[alpha][beta][i][j]*Du[i][alpha]*Dv[j][beta];
	}
      }
# elif HAVE_DM_SRC_TYPE
      for (i = 0; i < DIM_OF_WORLD; i++) {
	r += A[alpha][beta][i]*Du[i][alpha]*Dv[i][beta];
      }
# elif HAVE_SCM_SRC_TYPE
      {
	REAL tmp;

	tmp = 0.0;
	for (i = 0; i < DIM_OF_WORLD; i++) {
	  tmp += Du[i][alpha]*Dv[i][beta];
	}
	r += A[alpha][beta]*tmp;
      }
# endif
    }
  }
  return r;
}

/* Du is the Jacobian of a vector-valued basis function, Dv is the
 * gradient of a scalar basis function.  u is _NOT_ factored into
 * scalar part and directional part. Intended for the case when the
 * directional part of a basis functions is not constant on an
 * element.
 */
static inline REAL *
VC_DMutAv_tan(int n_lambda,
			 const REAL_DB Du,
			 const REAL_D (*A)[N_LAMBDA_MAX],
			 const REAL_B Dv,
			 REAL *r,
			 int wall)
{
  int i, alpha, beta;

  SET_DOW(0.0, r);
  for (alpha = 0; alpha < n_lambda; alpha++, alpha += alpha == wall) {
    for (beta = 0; beta < n_lambda; beta++, beta += beta == wall) {
# if HAVE_M_SRC_TYPE
      for (i = 0; i < DIM_OF_WORLD; i++) {
	int j;

	for (j = 0; j < DIM_OF_WORLD; j++) {
	  r[j] += A[alpha][beta][i][j]*Du[i][alpha]*Dv[beta];
	}
      }
# elif HAVE_DM_SRC_TYPE
      for (i = 0; i < DIM_OF_WORLD; i++) {
	r[i] += A[alpha][beta][i]*Du[i][alpha]*Dv[beta];
      }
# elif HAVE_SCM_SRC_TYPE
      for (i = 0; i < DIM_OF_WORLD; i++) {
	r[i] += Du[i][alpha]*A[alpha][beta]*Dv[beta];
      }
# endif
    }
  }
  return r;
}

/* Dv is the Jacobian of a vector-valued basis function which is _NOT_
 * factored into a scalar and a directional part. For use when v is
 * not p.w. constant.
 */
static inline REAL *
CV_DMutAv_tan(int n_lambda,
			 const REAL_B Du,
			 const REAL_D (*A)[N_LAMBDA_MAX],
			 const REAL_DB Dv,
			 REAL *r,
			 int wall)
{
  int i, alpha, beta;

  SET_DOW(0.0, r);
  for (alpha = 0; alpha < n_lambda; alpha++, alpha += alpha == wall) {
    for (beta = 0; beta < n_lambda; beta++, beta += beta == wall) {
# if HAVE_M_SRC_TYPE
      for (i = 0; i < DIM_OF_WORLD; i++) {
	int j;

	for (j = 0; j < DIM_OF_WORLD; j++) {
	  r[i] += A[alpha][beta][i][j]*Du[alpha]*Dv[j][beta];
	}
      }
# elif HAVE_DM_SRC_TYPE
      for (i = 0; i < DIM_OF_WORLD; i++) {
	r[i] += A[alpha][beta][i]*Du[alpha]*Dv[i][beta];
      }
# elif HAVE_SCM_SRC_TYPE
      for (i = 0; i < DIM_OF_WORLD; i++) {
	r[i] += Du[alpha]*A[alpha][beta]*Dv[i][beta];
      }
# endif
    }
  }
  return r;
}

# if HAVE_DM_SRC_TYPE || HAVE_SCM_SRC_TYPE
/* There is an ambiguity if the entries of the operator kernel are
 * REAL_D blocks and one of the finite element space is scalar and the
 * other vector valued: the scalar space may either be a real scalar
 * space, in which case we have to form the scalar product of the
 * kernel with the components of the vector valued basis functions, or
 * the scalar space may in fact be the scalar components of a
 * Cartesian product space, in which case the resulting matrix is a
 * block matrix with REAL_D entries. The VS_... and SV_... versions
 * are for the scalar-vector case, the VC_... and CV_... versions are
 * for the Cartesian product case.
 */

/* Du is the Jacobian of a vector-valued basis function, Dv is the
 * gradient of a scalar basis function.  u is _NOT_ factored into
 * scalar part and directional part. Intended for the case when the
 * directional part of a basis functions is not constant on an
 * element.
 */
static inline REAL
VS_DMutAv_tan(int n_lambda,
			 const REAL_DB Du,
			 const REAL_D (*A)[N_LAMBDA_MAX],
			 const REAL_B Dv,
			 int wall)
{
  int i, alpha, beta;
  REAL r;

  r = 0.0;
  for (alpha = 0; alpha < n_lambda; alpha++, alpha += alpha == wall) {
    for (beta = 0; beta < n_lambda; beta++, beta += beta == wall) {
# if HAVE_DM_SRC_TYPE
      {
	REAL tmp;

	tmp = 0.0;
	for (i = 0; i < DIM_OF_WORLD; i++) {
	  tmp += A[alpha][beta][i]*Du[i][alpha];
	}
	r += tmp * Dv[beta];
      }
# elif HAVE_SCM_SRC_TYPE
      {
	REAL tmp;

	tmp = 0.0;
	for (i = 0; i < DIM_OF_WORLD; i++) {
	  tmp += Du[i][alpha];
	}
	r += tmp * A[alpha][beta]*Dv[beta];
      }
# endif
    }
  }
  return r;
}

/* Dv is the Jacobian of a vector-valued basis function which is _NOT_
 * factored into a scalar and a directional part. For use when v is
 * not p.w. constant.
 */
static inline REAL
SV_DMutAv_tan(int n_lambda,
			 const REAL_B Du,
			 const REAL_D (*A)[N_LAMBDA_MAX],
			 const REAL_DB Dv,
			 int wall)
{
  int i, alpha, beta;
  REAL r;

  r = 0.0;
  for (alpha = 0; alpha < n_lambda; alpha++, alpha += alpha == wall) {
    for (beta = 0; beta < n_lambda; beta++, beta += beta == wall) {
# if HAVE_DM_SRC_TYPE
      for (i = 0; i < DIM_OF_WORLD; i++) {
	r += A[alpha][beta][i]*Du[alpha]*Dv[i][beta];
      }
# elif HAVE_SCM_SRC_TYPE
      {
	REAL tmp;

	tmp = 0.0;
	for (i = 0; i < DIM_OF_WORLD; i++) {
	  tmp += Dv[i][beta];
	}
	r += A[alpha][beta]*Du[alpha]*tmp;
      }
#endif
    }
  }
  return r;
}

# endif /* HAVE_DM_SRC_TYPE || HAVE_SCM_SRC_TYPE */

/* >>> */

/* <<< ubtDv() and Dutbv() for vector/scalar - vector/scalar combinations */

/* <<< vector-vector */

/* Helper for first order term Lb0, derivative on ansatz function */
static inline REAL
VV_DMubtDv_tan(int n_lambda,
			  const REAL_D u, const REAL_D b[], const REAL_DB Dv,
			  int wall)
{
  int i, alpha;
  REAL r;

  r = 0.0;
  for (alpha = 0; alpha < n_lambda; alpha++, alpha += alpha == wall) {
#if HAVE_M_SRC_TYPE
    for (i = 0; i < DIM_OF_WORLD; i++) {
      int j;
      for (j = 0; j < DIM_OF_WORLD; j++) {
	r += u[i] * b[alpha][i][j] * Dv[j][alpha];
      }
    }
#elif HAVE_DM_SRC_TYPE
    for (i = 0; i < DIM_OF_WORLD; i++) {
      r += u[i] * b[alpha][i] * Dv[i][alpha];
    }
#elif HAVE_SCM_SRC_TYPE
    for (i = 0; i < DIM_OF_WORLD; i++) {
      r += u[i] * b[alpha] * Dv[i][alpha];
    }
#endif
  }
  return r;
}

/* Helper for first order term Lb1, derivative on test function */
static inline REAL
VV_DMDutbv_tan(int n_lambda,
			  const REAL_DB Du, const REAL_D b[], const REAL_D v,
			  int wall)
{
  int i, alpha;
  REAL r;

  r = 0.0;
  for (alpha = 0; alpha < n_lambda; alpha++, alpha += alpha == wall) {
#if HAVE_M_SRC_TYPE
    for (i = 0; i < DIM_OF_WORLD; i++) {
      int j;
      for (j = 0; j < DIM_OF_WORLD; j++) {
	r += Du[i][alpha] * b[alpha][i][j] * v[j];
      }
    }
#elif HAVE_DM_SRC_TYPE
    for (i = 0; i < DIM_OF_WORLD; i++) {
      r += Du[i][alpha] * b[alpha][i] * v[i];
    }
#elif HAVE_SCM_SRC_TYPE
    for (i = 0; i < DIM_OF_WORLD; i++) {
      r += Du[i][alpha] * b[alpha] * v[i];
    }
#endif
  }
  return r;
}

/* >>> */

/* <<< vector-Cartesian */

/* Helper for first order term Lb0, derivative on ansatz function */

static inline REAL *
VC_DMubtDv_tan(
  int n_lambda,
  const REAL_D u, const REAL_D b[], const REAL_B Dv, REAL_D r,
  int wall)
{
  int i, alpha;

  SET_DOW(0.0, r);
  for (alpha = 0; alpha < n_lambda; alpha++, alpha += alpha == wall) {
#if HAVE_M_SRC_TYPE
    for (i = 0; i < DIM_OF_WORLD; i++) {
      int j;
      for (j = 0; j < DIM_OF_WORLD; j++) {
	r[j] += u[i] * b[alpha][i][j] * Dv[alpha];
      }
    }
#elif HAVE_DM_SRC_TYPE
    for (i = 0; i < DIM_OF_WORLD; i++) {
      r[i] += u[i] * b[alpha][i] * Dv[alpha];
    }
#elif HAVE_SCM_SRC_TYPE
    for (i = 0; i < DIM_OF_WORLD; i++) {
      r[i] += u[i] * b[alpha] * Dv[alpha];
    }
#endif
  }
  return r;
}

/* Helper for first order term Lb1, derivative on test function */
static inline REAL *
VC_DMDutbv_tan(
  int n_lambda,
  const REAL_DB Du, const REAL_D b[], REAL v, REAL_D r,
  int wall)
{
  int i, alpha;

  SET_DOW(0.0, r);
  for (alpha = 0; alpha < n_lambda; alpha++, alpha += alpha == wall) {
#if HAVE_M_SRC_TYPE
    for (i = 0; i < DIM_OF_WORLD; i++) {
      int j;
      for (j = 0; j < DIM_OF_WORLD; j++) {
	r[j] += Du[i][alpha] * b[alpha][i][j] * v;
      }
    }
#elif HAVE_DM_SRC_TYPE
    for (i = 0; i < DIM_OF_WORLD; i++) {
      r[i] += Du[i][alpha] * b[alpha][i] * v;
    }
#elif HAVE_SCM_SRC_TYPE
    for (i = 0; i < DIM_OF_WORLD; i++) {
      r[i] += Du[i][alpha] * b[alpha] * v;
    }
#endif
  }
  return r;
}

/* >>> */

/* <<< Cartesian-vector */

/* Helper for first order term Lb0, derivative on ansatz function */

static inline REAL *CV_DMubtDv_tan(
  int n_lambda,
  REAL u, const REAL_D b[], const REAL_DB Dv, REAL_D r,
  int wall)
{
  int i, alpha;

  SET_DOW(0.0, r);
  for (alpha = 0; alpha < n_lambda; alpha++, alpha += alpha == wall) {
#if HAVE_M_SRC_TYPE
    for (i = 0; i < DIM_OF_WORLD; i++) {
      int j;
      for (j = 0; j < DIM_OF_WORLD; j++) {
	r[i] += u * b[alpha][i][j] * Dv[j][alpha];
      }
    }
#elif HAVE_DM_SRC_TYPE
    for (i = 0; i < DIM_OF_WORLD; i++) {
      r[i] += u * b[alpha][i] * Dv[i][alpha];
    }
#elif HAVE_SCM_SRC_TYPE
    for (i = 0; i < DIM_OF_WORLD; i++) {
      r[i] += u * b[alpha] * Dv[i][alpha];
    }
#endif
  }
  return r;
}

/* Helper for first order term Lb1, derivative on test function */
static inline REAL *CV_DMDutbv_tan(
  int n_lambda,
  const REAL_B Du, const REAL_D b[], const REAL_D v, REAL_D r,
  int wall)
{
  int i, alpha;

  SET_DOW(0.0, r);
  for (alpha = 0; alpha < n_lambda; alpha++, alpha += alpha == wall) {
#if HAVE_M_SRC_TYPE
    for (i = 0; i < DIM_OF_WORLD; i++) {
      int j;
      for (j = 0; j < DIM_OF_WORLD; j++) {
	r[i] += Du[alpha] * b[alpha][i][j] * v[j];
      }
    }
#elif HAVE_DM_SRC_TYPE
    for (i = 0; i < DIM_OF_WORLD; i++) {
      r[i] += Du[alpha] * b[alpha][i] * v[i];
    }
#elif HAVE_SCM_SRC_TYPE
    for (i = 0; i < DIM_OF_WORLD; i++) {
      r[i] += Du[alpha] * b[alpha] * v[i];
    }
#endif
  }
  return r;
}

/* >>> */

#if HAVE_DM_DST_TYPE || HAVE_SCM_DST_TYPE

/* <<< vector-Scalar */

/* Helper for first order term Lb0, derivative on ansatz function */

static inline REAL
VS_DMubtDv_tan(
  int n_lambda,
  const REAL_D u, const REAL_D b[], const REAL_B Dv,
  int wall)
{
  int i, alpha;
  REAL r;

  r = 0.0;
  for (alpha = 0; alpha < n_lambda; alpha++, alpha += wall) {
#if HAVE_DM_SRC_TYPE
    for (i = 0; i < DIM_OF_WORLD; i++) {
      r += u[i] * b[alpha][i] * Dv[alpha];
    }
#elif HAVE_SCM_SRC_TYPE
    for (i = 0; i < DIM_OF_WORLD; i++) {
      r += u[i] * b[alpha] * Dv[alpha];
    }
#endif
  }
  return r;
}

/* Helper for first order term Lb1, derivative on test function */
static inline REAL
VS_DMDutbv_tan(
  int n_lambda,
  const REAL_DB Du, const REAL_D b[], REAL v,
  int wall)
{
  int i, alpha;
  REAL r;

  r = 0.0;
  for (alpha = 0; alpha < n_lambda; alpha++, alpha += alpha == wall) {
#if HAVE_M_SRC_TYPE
    for (i = 0; i < DIM_OF_WORLD; i++) {
      int j;
      for (j = 0; j < DIM_OF_WORLD; j++) {
	r += Du[i][alpha] * b[alpha][i][j] * v;
      }
    }
#elif HAVE_DM_SRC_TYPE
    for (i = 0; i < DIM_OF_WORLD; i++) {
      r += Du[i][alpha] * b[alpha][i] * v;
    }
#elif HAVE_SCM_SRC_TYPE
    for (i = 0; i < DIM_OF_WORLD; i++) {
      r += Du[i][alpha] * b[alpha] * v;
    }
#endif
  }
  return r;
}

/* >>> */

/* <<< Scalar-vector */

/* Helper for first order term Lb0, derivative on ansatz function */

static inline REAL
SV_DMubtDv_tan(
  int n_lambda,
  REAL u, const REAL_D b[], const REAL_DB Dv,
  int wall)
{
  int i, alpha;
  REAL r;

  r = 0.0;
  for (alpha = 0; alpha < n_lambda; alpha++, alpha += alpha == wall) {
#if HAVE_DM_SRC_TYPE
    for (i = 0; i < DIM_OF_WORLD; i++) {
      r += u * b[alpha][i] * Dv[i][alpha];
    }
#elif HAVE_SCM_SRC_TYPE
    for (i = 0; i < DIM_OF_WORLD; i++) {
      r += u * b[alpha] * Dv[i][alpha];
    }
#endif
  }
  return r;
}

/* Helper for first order term Lb1, derivative on test function */
static inline REAL
SV_DMDutbv_tan(
  int n_lambda,
  const REAL_B Du, const REAL_D b[], const REAL_D v,
  int wall)
{
  int i, alpha;
  REAL r;

  r = 0.0;
  for (alpha = 0; alpha < n_lambda; alpha++, alpha += alpha == wall) {
#if HAVE_DM_SRC_TYPE
    for (i = 0; i < DIM_OF_WORLD; i++) {
      r += Du[alpha] * b[alpha][i] * v[i];
    }
#elif HAVE_SCM_SRC_TYPE
    for (i = 0; i < DIM_OF_WORLD; i++) {
      r += Du[alpha] * b[alpha] * v[i];
    }
#endif
  }
  return r;
}

/* >>> */

#endif

/* >>> */

/* >>> */

/* <<< DEFUN macros */

#if 0 /* defined in assemble_bndry_h */
#define WALL_FCT_MIX (1 << 0)
#define WALL_FCT_SYM (1 << 1)
#define WALL_FCT_TAN (1 << 2)
#define WALL_FCT_PWC (1 << 3)
#endif

#define WALL_FCT(namebase, meshdim, wall, mixed, sym, tan, pwc)		\
  NAME(namebase##_##wall##_##mixed##sym##tan##pwc##_##meshdim##D)

#define DEFUN_WALL_FCT(namebase, meshdim, wall, mixed, sym, tan, pwc)	\
  static void FLATTEN_ATTR						\
  WALL_FCT(namebase, meshdim, wall, mixed, sym, tan, pwc)(		\
    const EL_INFO *el_info,						\
    const BNDRY_FILL_INFO *fill_info,					\
    void **el_mat)							\
  {									\
    if (!mixed && row_fcts_V != col_fcts_V) {				\
      FUNCNAME(#namebase);						\
      ERROR_EXIT("Inpossible symmetries");				\
    } else {								\
      MULTIPLEX_NAME(namebase)(el_info, meshdim+1, wall,		\
			       fill_info, el_mat, mixed, sym, tan, pwc); \
    }									\
  }									\
  struct _AI_semicolon_dummy

#define DEFUN_EL_FCTS_1D(namebase)		\
  DEFUN_WALL_FCTS(namebase, 1, 0);		\
  DEFUN_WALL_FCTS(namebase, 1, 1)
#define DEFUN_EL_FCTS_2D(namebase)		\
  DEFUN_WALL_FCTS(namebase, 2, 0);		\
  DEFUN_WALL_FCTS(namebase, 2, 1);		\
  DEFUN_WALL_FCTS(namebase, 2, 2)
#define DEFUN_EL_FCTS_3D(namebase)		\
  DEFUN_WALL_FCTS(namebase, 3, 0);		\
  DEFUN_WALL_FCTS(namebase, 3, 1);		\
  DEFUN_WALL_FCTS(namebase, 3, 2);		\
  DEFUN_WALL_FCTS(namebase, 3, 3)

#if DIM_MAX == 1
# define DEFUN_EL_FCTS(namebase)		\
  DEFUN_EL_FCTS_1D(namebase)
#elif DIM_MAX == 2
# define DEFUN_EL_FCTS(namebase)		\
  DEFUN_EL_FCTS_1D(namebase);			\
  DEFUN_EL_FCTS_2D(namebase)
#elif DIM_MAX == 3
# define DEFUN_EL_FCTS(namebase)		\
  DEFUN_EL_FCTS_1D(namebase);			\
  DEFUN_EL_FCTS_2D(namebase);			\
  DEFUN_EL_FCTS_3D(namebase)
#else
# error unsupported DIM_MAX
#endif

/* >>> */

/* <<< quad_2 */

/* <<< quad2_multiplex */

/* Use only the tangential components. P_i == projection to tangent
 * space on i-th wall.
 *
 * L P_i A P_i L^t
 *
 * L P_i = L_i where L_i is L with the i-th row set to zero. So:
 *
 * L P_i A P_i L^t == L_i A L_i^t = LALt_i where the i-the row and
 * column of LALt_i is set to zero.
 */
static inline void
MULTIPLEX_NAME(quad_2)(const EL_INFO *el_info,
		       int n_lambda,
		       int wall,
		       const BNDRY_FILL_INFO *fill_info,
		       void **voidmat,
		       int mixed, int sym, int tan, int pwc)
{
  const REAL_BD *LALt = NULL;
  const REAL_B    *grd_row_fcts, *grd_col_fcts;
  int             iq, i, j;
  const QUAD_FAST *row_fcts_qfast, *col_fcts_qfast;
  const BAS_FCTS  *row_fcts, *col_fcts;
  const QUAD      *quad;
  const int       *row_fcts_map = NULL, *col_fcts_map = NULL;
  int             n_tr_row_fcts, n_tr_col_fcts;
  int             i_tr, j_tr;
  REAL_D      val, tmp;
  REAL_D      **mat;
  REAL_D          **real_d_mat = NULL;
  REAL            **real_mat = NULL;
  bool            row_fcts_V_const, col_fcts_V_const;
  const REAL_DB   *const*grd_row_fcts_d = NULL;
  const REAL_DB   *const*grd_col_fcts_d = NULL;
  REAL_D          val_d;

  row_fcts_qfast = fill_info->row_wquad_fast[2]->quad_fast[wall];
  quad      = row_fcts_qfast->quad;
  row_fcts       = row_fcts_qfast->bas_fcts;

  row_fcts_V_const = !row_fcts_V || row_fcts->dir_pw_const;

  if (tan) {
    /* we do not need to worry here about the orientation and element type */
    row_fcts_map  = fill_info->row_fcts_trace_map[wall];
    n_tr_row_fcts = fill_info->n_trace_row_fcts[wall];
  } else {
    n_tr_row_fcts = row_fcts_qfast->n_bas_fcts;
  }

  if (!mixed) {
    col_fcts_qfast   = row_fcts_qfast;
    col_fcts         = col_fcts_qfast->bas_fcts;
    col_fcts_V_const = row_fcts_V_const;
    if (tan) {
      col_fcts_map = row_fcts_map;
    }
    n_tr_col_fcts = n_tr_row_fcts;
  } else {
    col_fcts_qfast   = fill_info->col_quad_fast[2];
    col_fcts         = col_fcts_qfast->bas_fcts;
    col_fcts_V_const = !col_fcts_V || col_fcts->dir_pw_const;
    if (tan) {
      /* we do not need to worry here about the orientation and element type */
      col_fcts_map  = col_fcts_qfast->bas_fcts->trace_dof_map[0][0][wall];
      n_tr_col_fcts = col_fcts_qfast->bas_fcts->n_trace_bas_fcts[wall];
    } else {
      n_tr_col_fcts = col_fcts_qfast->n_bas_fcts;
    }
  }

  if (pwc) {
    LALt = fill_info->op_info.LALt.real_d(
      el_info, quad, 0, fill_info->op_info.user_data);
  }

  if (sym) {

    if (row_fcts_V) {
      if (row_fcts_V_const) {
	mat = (REAL_D **)fill_info->scl_el_mat;
	DM_clear_tmp_mat(mat, fill_info);
      } else {
	grd_row_fcts_d =
	  grd_col_fcts_d = get_quad_fast_grd_phi_dow(row_fcts_qfast);
      }
    } else {
      mat = (REAL_D **)voidmat;
    }

    if (row_fcts_V_const) {
      for (iq = 0; iq < quad->n_points; iq++) {
	if (!pwc) {
	  LALt = fill_info->op_info.LALt.real_d(
	    el_info, quad, iq, fill_info->op_info.user_data);
	}
	grd_col_fcts = grd_row_fcts = row_fcts_qfast->grd_phi[iq];

	for (i_tr = 0; i_tr < n_tr_row_fcts; i_tr++) {
	  i = tan ? row_fcts_map[i_tr] : i_tr;

	  DMDMAXPY_DOW(
	    quad->w[iq],
	    /**/ DMutAv_tan(
	      n_lambda, grd_row_fcts[i], LALt, grd_col_fcts[i], tmp,
	      tan ? wall : n_lambda),
	    mat[i][i]);
	  for (j_tr = i_tr+1; j_tr < n_tr_col_fcts; j_tr++) {
	    j = tan ? col_fcts_map[j_tr] : j_tr;

	    DMutAv_tan(
	      n_lambda, grd_row_fcts[i], LALt, grd_col_fcts[j], val,
	      tan ? wall : n_lambda);
	    DMAX_DOW(quad->w[iq],  val);
	    DMDMAXPY_DOW(
	      1.0, /**/ val, mat[i][j]);
	    DMDMAXTPY_DOW(
	      1.0, /**/ val, mat[j][i]);
	  }
	}
      }
      if (row_fcts_V) {
	/* condense with the directions of the basis functions */
	VV_DM_condense_el_mat(
	  (REAL **)voidmat, fill_info, row_fcts_qfast, col_fcts_qfast,
	  true, false);
      }
    } else { /* vector-valued, and direction not p.w. constant */
      real_mat = (REAL **)voidmat;

      for (iq = 0; iq < quad->n_points; iq++) {
	if (!pwc) {
	  LALt = fill_info->op_info.LALt.real_d(
	    el_info, quad, iq, fill_info->op_info.user_data);
	}
	grd_col_fcts = grd_row_fcts = row_fcts_qfast->grd_phi[iq];

	for (i_tr = 0; i_tr < n_tr_row_fcts; i_tr++) {
	  i = tan ? row_fcts_map[i_tr] : i_tr;

	  real_mat[i][i] +=
	    quad->w[iq] *
	    VV_DMutAv_tan(
	      n_lambda,
	      grd_row_fcts_d[iq][i], LALt, grd_col_fcts_d[iq][i],
	      tan ? wall : n_lambda);
	  for (j_tr = i_tr+1; j_tr < n_tr_col_fcts; j_tr++) {
	    REAL val;

	    j = tan ? col_fcts_map[j_tr] : j_tr;

	    val =
	      quad->w[iq] *
	      VV_DMutAv_tan(
		n_lambda,
		grd_row_fcts_d[iq][i], LALt, grd_col_fcts_d[iq][j],
		tan ? wall : n_lambda);
	    real_mat[i][j] += val;
	    real_mat[j][i] += val;
	  }
	}
      }
    }
  } else { /*  non symmetric assembling   */

    if (row_fcts_V || col_fcts_V) {
      if (row_fcts_V && !row_fcts_V_const) {
	grd_row_fcts_d = get_quad_fast_grd_phi_dow(row_fcts_qfast);
      }
      if (col_fcts_V && !col_fcts_V_const) {
	grd_col_fcts_d = get_quad_fast_grd_phi_dow(col_fcts_qfast);
      }
    }

    mat = DM_assign_matrices(voidmat, &real_mat, &real_d_mat,
					fill_info,
					/* row_fcts_V, col_fcts_V, */
					row_fcts_V_const, col_fcts_V_const);

    for (iq = 0; iq < quad->n_points; iq++) {
      if (!pwc) {
	LALt = fill_info->op_info.LALt.real_d(
	  el_info, quad, iq, fill_info->op_info.user_data);
      }
      grd_row_fcts = row_fcts_qfast->grd_phi[iq];
      grd_col_fcts = col_fcts_qfast->grd_phi[iq];

      for (i_tr = 0; i_tr < n_tr_row_fcts; i_tr++) {
	i = tan ? row_fcts_map[i_tr] : i_tr;
	for (j_tr = 0; j_tr < n_tr_col_fcts; j_tr++) {
	  j = tan ? col_fcts_map[j_tr] : j_tr;

	  if (row_fcts_V_const && col_fcts_V_const) {
	    DMDMAXPY_DOW(
	      quad->w[iq],
	      /**/ DMutAv_tan(
		n_lambda,
		grd_row_fcts[i], LALt, grd_col_fcts[j], tmp,
		tan ? wall : n_lambda),
	      mat[i][j]);
	  } else if (row_fcts_V_const) {
	    if (!row_fcts_V && !row_fcts_C) {
#if HAVE_DM_SRC_TYPE || HAVE_SCM_SRC_TYPE
	      real_mat[i][j] +=
		quad->w[iq] * SV_DMutAv_tan(
		  n_lambda, grd_row_fcts[i], LALt, grd_col_fcts_d[iq][j],
		  tan ? wall : n_lambda);
#endif
	    } else {
	      CV_DMutAv_tan(
		n_lambda, grd_row_fcts[i], LALt, grd_col_fcts_d[iq][j], val_d,
		tan ? wall : n_lambda);
	      DMDMAXPY_DOW(quad->w[iq], val_d, real_d_mat[i][j]);
	    }
	  } else if (col_fcts_V_const) {
	    if (!col_fcts_V && !col_fcts_C) {
#if HAVE_DM_SRC_TYPE || HAVE_SCM_SRC_TYPE
	      real_mat[i][j] +=
		quad->w[iq] * VS_DMutAv_tan(
		  n_lambda, grd_row_fcts_d[iq][i], LALt, grd_col_fcts[j],
		  tan ? wall : n_lambda);
#endif
	    } else {
	      VC_DMutAv_tan(
		n_lambda, grd_row_fcts_d[iq][i], LALt, grd_col_fcts[j], val_d,
		tan ? wall : n_lambda);
	      DMDMAXPY_DOW(quad->w[iq], val_d, real_d_mat[i][j]);
	    }
	  } else {
	    real_mat[i][j] +=
	      quad->w[iq] * VV_DMutAv_tan(
		n_lambda, grd_row_fcts_d[iq][i], LALt, grd_col_fcts_d[iq][j],
		tan ? wall : n_lambda);
	  }
	} /* column loop */
      } /* row loop */
    } /* quad-point loop */

    /* Now possibly condense the temporary matrix if either of the
     * directions was p.w. constant
     */
    DM_condense_matrices(voidmat, fill_info,
				    row_fcts_qfast, col_fcts_qfast,
				    row_fcts_V_const, col_fcts_V_const);
  } /* non-symmetric assembly */
}

/* >>> */

/* <<< quad_2 DEFUNs */

#define DEFUN_WALL_FCTS(namebase, meshdim, wall)		\
  DEFUN_WALL_FCT(namebase, meshdim, wall, 0, 0, 0, 0);		\
  DEFUN_WALL_FCT(namebase, meshdim, wall, 1, 0, 0, 0);		\
  DEFUN_WALL_FCT(namebase, meshdim, wall, 0, 1, 0, 0);		\
  /*DEFUN_WALL_FCT(namebase, meshdim, wall, 1, 1, 0, 0);*/	\
  DEFUN_WALL_FCT(namebase, meshdim, wall, 0, 0, 1, 0);		\
  DEFUN_WALL_FCT(namebase, meshdim, wall, 1, 0, 1, 0);		\
  DEFUN_WALL_FCT(namebase, meshdim, wall, 0, 1, 1, 0);		\
  /*DEFUN_WALL_FCT(namebase, meshdim, wall, 1, 1, 1, 0);*/	\
  DEFUN_WALL_FCT(namebase, meshdim, wall, 0, 0, 0, 1);		\
  DEFUN_WALL_FCT(namebase, meshdim, wall, 1, 0, 0, 1);		\
  DEFUN_WALL_FCT(namebase, meshdim, wall, 0, 1, 0, 1);		\
  /*DEFUN_WALL_FCT(namebase, meshdim, wall, 1, 1, 0, 1);*/	\
  DEFUN_WALL_FCT(namebase, meshdim, wall, 0, 0, 1, 1);		\
  DEFUN_WALL_FCT(namebase, meshdim, wall, 1, 0, 1, 1);		\
  DEFUN_WALL_FCT(namebase, meshdim, wall, 0, 1, 1, 1)		\
  /*DEFUN_WALL_FCT(namebase, meshdim, wall, 1, 1, 1, 1)*/

DEFUN_EL_FCTS(quad_2);

#undef DEFUN_WALL_FCTS

/* >>> */

/* <<< quad_2 table initializers */

#define WALL_FCTS_2(meshdim, wall)			\
  WALL_FCT(quad_2, meshdim, wall, 0, 0, 0, 0),		\
    WALL_FCT(quad_2, meshdim, wall, 1, 0, 0, 0),	\
    WALL_FCT(quad_2, meshdim, wall, 0, 1, 0, 0),	\
    NULL,						\
    WALL_FCT(quad_2, meshdim, wall, 0, 0, 1, 0),	\
    WALL_FCT(quad_2, meshdim, wall, 1, 0, 1, 0),	\
    WALL_FCT(quad_2, meshdim, wall, 0, 1, 1, 0),	\
    NULL,						\
    WALL_FCT(quad_2, meshdim, wall, 0, 0, 0, 1),	\
    WALL_FCT(quad_2, meshdim, wall, 1, 0, 0, 1),	\
    WALL_FCT(quad_2, meshdim, wall, 0, 1, 0, 1),	\
    NULL,						\
    WALL_FCT(quad_2, meshdim, wall, 0, 0, 1, 1),	\
    WALL_FCT(quad_2, meshdim, wall, 1, 0, 1, 1),	\
    WALL_FCT(quad_2, meshdim, wall, 0, 1, 1, 1),	\
    NULL,

/* >>> */

/* >>> */

/* <<< quad_01 */

/* <<< quad_01_multiplex */

static inline void
MULTIPLEX_NAME(quad_01)(const EL_INFO *el_info,
			int n_lambda,
			int wall,
			const BNDRY_FILL_INFO *fill_info,
			void **voidmat,
			int mixed, int sym, int tan, int pwc)
{
  const REAL_D *Lb0 = NULL;
  const REAL_B    *grd_col_fcts;
  const REAL      *row_fcts_val;
  int             iq, i, j;
  const QUAD_FAST *row_fcts_qfast, *col_fcts_qfast;
  const BAS_FCTS  *row_fcts, *col_fcts;
  const QUAD      *quad;
  const int       *row_fcts_map, *col_fcts_map = NULL;
  int             n_tr_row_fcts, n_tr_col_fcts;
  int             i_tr, j_tr;
  REAL_D      tmp;
  REAL_D      **mat;
  REAL_D          val_d;
  REAL_D          **real_d_mat = NULL;
  REAL            **real_mat = NULL;
  bool            row_fcts_V_const, col_fcts_V_const;
  const REAL_DB   *const*grd_col_fcts_d = NULL;
  const REAL_D    *const*row_fcts_d = NULL;

  /* columns first, because we need n_tr_col_fcts, col_fcts_map anyway */
  row_fcts_qfast = fill_info->row_wquad_fast[1]->quad_fast[wall];
  quad      = row_fcts_qfast->quad;
  row_fcts       = row_fcts_qfast->bas_fcts;

  /* we do not need to worry here about the orientation and element type */
  row_fcts_map  = fill_info->row_fcts_trace_map[wall];
  n_tr_row_fcts = fill_info->n_trace_row_fcts[wall];

  row_fcts_V_const = !row_fcts_V || row_fcts->dir_pw_const;

  if (!mixed) {
    col_fcts_qfast   = row_fcts_qfast;
    col_fcts         = col_fcts_qfast->bas_fcts;
    col_fcts_V_const = row_fcts_V_const;
    if (tan) {
      col_fcts_map  = row_fcts_map;
      n_tr_col_fcts = n_tr_row_fcts;
    } else {
      n_tr_col_fcts = col_fcts_qfast->n_bas_fcts;
    }
  } else {
    col_fcts_qfast   = fill_info->col_quad_fast[1];
    col_fcts         = col_fcts_qfast->bas_fcts;
    col_fcts_V_const = !col_fcts_V || col_fcts->dir_pw_const;
    if (tan) {
      /* we do not need to worry here about the orientation and element type */
      col_fcts_map  = col_fcts_qfast->bas_fcts->trace_dof_map[0][0][wall];
      n_tr_col_fcts = col_fcts_qfast->bas_fcts->n_trace_bas_fcts[wall];
    } else {
      n_tr_col_fcts = col_fcts_qfast->n_bas_fcts;
    }
  }

  if (row_fcts_V || col_fcts_V) {
    if (row_fcts_V && !row_fcts_V_const) {
      row_fcts_d = get_quad_fast_phi_dow(row_fcts_qfast);
    }
    if (col_fcts_V && !col_fcts_V_const) {
      grd_col_fcts_d = get_quad_fast_grd_phi_dow(col_fcts_qfast);
    }
  }

  mat = DM_assign_matrices(voidmat, &real_mat, &real_d_mat,
				      fill_info,
				      /* row_fcts_V, col_fcts_V, */
				      row_fcts_V_const, col_fcts_V_const);

  if (pwc) {
    Lb0 = fill_info->op_info.Lb0.real_d(
      el_info, quad, 0, fill_info->op_info.user_data);
  }

  for (iq = 0; iq < quad->n_points; iq++) {

    if (!pwc) {
      Lb0 = fill_info->op_info.Lb0.real_d(
	el_info, quad, iq, fill_info->op_info.user_data);
    }

    row_fcts_val = row_fcts_qfast->phi[iq];
    grd_col_fcts = col_fcts_qfast->grd_phi[iq];

    for (i_tr = 0; i_tr < n_tr_row_fcts; i_tr++) {
      i = row_fcts_map[i_tr];
      for (j_tr = 0; j_tr < n_tr_col_fcts; j_tr++) {

	j = tan ? col_fcts_map[j_tr] : j_tr;

	if (row_fcts_V_const && col_fcts_V_const) {
	  DMDMAXPY_DOW(
	    quad->w[iq]*row_fcts_val[i],
	    /**/ DMbtv_tan(
	      n_lambda, Lb0, grd_col_fcts[j], tmp,
	      tan ? wall : n_lambda),
	    mat[i][j]);
	} else if (row_fcts_V_const) {
	  if (!row_fcts_V && !row_fcts_C) {
#if HAVE_DM_DST_TYPE || HAVE_SCM_DST_TYPE
	    real_mat[i][j] +=
	      quad->w[iq] * SV_DMubtDv_tan(
		n_lambda, row_fcts_val[i], Lb0, grd_col_fcts_d[iq][j],
		tan ? wall : n_lambda);
#endif
	  } else {
	    CV_DMubtDv_tan(
	      n_lambda, row_fcts_val[i], Lb0, grd_col_fcts_d[iq][j], val_d,
	      tan ? wall : n_lambda);
	    DMDMAXPY_DOW(quad->w[iq], val_d, real_d_mat[i][j]);
	  }
	} else if (row_fcts_V_const) {
	  if (!row_fcts_V && !row_fcts_C) {
#if HAVE_DM_DST_TYPE || HAVE_SCM_DST_TYPE
	    real_mat[i][j] +=
	      quad->w[iq] * VS_DMubtDv_tan(
		n_lambda, row_fcts_d[iq][i], Lb0, grd_col_fcts[j],
		tan ? wall : n_lambda);
#endif
	  } else {
	    VC_DMubtDv_tan(
	      n_lambda, row_fcts_d[iq][i], Lb0, grd_col_fcts[j], val_d,
	      tan ? wall : n_lambda);
	    DMDMAXPY_DOW(quad->w[iq], val_d, real_d_mat[i][j]);
	  }
	} else {
	  real_mat[i][j] +=
	    quad->w[iq] * VV_DMubtDv_tan(
	      n_lambda, row_fcts_d[iq][i], Lb0, grd_col_fcts_d[iq][j],
	      tan ? wall : n_lambda);
	}
      } /* column loop */
    } /* row loop */
  } /* quad-point loop */

  /* Now possibly condense the temporary matrix if either of the
   * directions was p.w. constant
   */
  DM_condense_matrices(voidmat, fill_info,
				  row_fcts_qfast, col_fcts_qfast,
				  row_fcts_V_const, col_fcts_V_const);
}

/* >>> */

/* <<< quad_01 DEFUNs */

/* only mixed, tan and pwc are relevant, sym is fixed at 0 */
#define DEFUN_WALL_FCTS(namebase, meshdim, wall)	\
  DEFUN_WALL_FCT(namebase, meshdim, wall, 0, 0, 0, 0);	\
  DEFUN_WALL_FCT(namebase, meshdim, wall, 1, 0, 0, 0);	\
  DEFUN_WALL_FCT(namebase, meshdim, wall, 0, 0, 1, 0);	\
  DEFUN_WALL_FCT(namebase, meshdim, wall, 1, 0, 1, 0);	\
  DEFUN_WALL_FCT(namebase, meshdim, wall, 0, 0, 0, 1);	\
  DEFUN_WALL_FCT(namebase, meshdim, wall, 1, 0, 0, 1);	\
  DEFUN_WALL_FCT(namebase, meshdim, wall, 0, 0, 1, 1);	\
  DEFUN_WALL_FCT(namebase, meshdim, wall, 1, 0, 1, 1)

DEFUN_EL_FCTS(quad_01);

#undef DEFUN_WALL_FCTS

/* >>> */

/* <<< quad_01 table initializer */

#define WALL_FCTS_01(meshdim, wall)			\
  WALL_FCT(quad_01, meshdim, wall, 0, 0, 0, 0),		\
    WALL_FCT(quad_01, meshdim, wall, 1, 0, 0, 0),	\
    NULL,						\
    NULL,						\
    WALL_FCT(quad_01, meshdim, wall, 0, 0, 1, 0),	\
    WALL_FCT(quad_01, meshdim, wall, 1, 0, 1, 0),	\
    NULL,						\
    NULL,						\
    WALL_FCT(quad_01, meshdim, wall, 0, 0, 0, 1),	\
    WALL_FCT(quad_01, meshdim, wall, 1, 0, 0, 1),	\
    NULL,						\
    NULL,						\
    WALL_FCT(quad_01, meshdim, wall, 0, 0, 1, 1),	\
    WALL_FCT(quad_01, meshdim, wall, 1, 0, 1, 1),	\
    NULL,						\
    NULL,

/* >>> */

/* >>> */

/* <<< quad_10 */

/* <<< quad_10_multiplex */

static inline void
MULTIPLEX_NAME(quad_10)(const EL_INFO *el_info,
			int n_lambda,
			int wall,
			const BNDRY_FILL_INFO *fill_info,
			void **voidmat,
			int mixed, int sym, int tan, int pwc)
{
  const REAL_D *Lb1 = NULL;
  const REAL_B    *grd_row_fcts;
  const REAL      *col_fcts_val;
  int             iq, i, j;
  const QUAD_FAST *row_fcts_qfast, *col_fcts_qfast;
  const BAS_FCTS  *row_fcts, *col_fcts;
  const QUAD      *quad;
  const int       *row_fcts_map = NULL, *col_fcts_map;
  int             n_tr_row_fcts, n_tr_col_fcts;
  int             i_tr, j_tr;
  REAL_D      tmp;
  REAL_D      **mat;
  REAL_D          val_d;
  REAL_D          **real_d_mat = NULL;
  REAL            **real_mat = NULL;
  bool            row_fcts_V_const, col_fcts_V_const;
  const REAL_DB   *const*grd_row_fcts_d = NULL;
  const REAL_D    *const*col_fcts_d = NULL;

  /* columns first, because we need n_tr_col_fcts, col_fcts_map anyway */
  col_fcts_qfast = fill_info->col_quad_fast[1];
  quad      = col_fcts_qfast->quad;
  /* we do not need to worry here about the orientation and element type */
  col_fcts       = col_fcts_qfast->bas_fcts;
  if (!mixed) {
    col_fcts_map   = col_fcts->trace_dof_map[0][0][wall];
    n_tr_col_fcts  = col_fcts->n_trace_bas_fcts[wall];
  } else {
    col_fcts_map   = col_fcts->trace_dof_map[0][0][quad->subsplx];
    n_tr_col_fcts  = col_fcts->n_trace_bas_fcts[quad->subsplx];
  }

  col_fcts_V_const = !col_fcts_V || col_fcts->dir_pw_const;

  if (!mixed) {
    row_fcts_qfast   = col_fcts_qfast;
    row_fcts         = row_fcts_qfast->bas_fcts;
    row_fcts_V_const = col_fcts_V_const;
    if (tan) {
      row_fcts_map  = col_fcts_map;
      n_tr_row_fcts = n_tr_col_fcts;
    } else {
      n_tr_row_fcts = row_fcts_qfast->n_bas_fcts;
    }
  } else {
    row_fcts_qfast   = fill_info->row_wquad_fast[1]->quad_fast[wall];
    row_fcts         = row_fcts_qfast->bas_fcts;
    row_fcts_V_const = !row_fcts_V || row_fcts->dir_pw_const;
    if (tan) {
      /* we do not need to worry here about the orientation and element type */
      row_fcts_map  = fill_info->row_fcts_trace_map[wall];
      n_tr_row_fcts = fill_info->n_trace_row_fcts[wall];
    } else {
      n_tr_row_fcts = row_fcts_qfast->n_bas_fcts;
    }
  }

  if (row_fcts_V || col_fcts_V) {
    if (row_fcts_V && !row_fcts_V_const) {
      grd_row_fcts_d = get_quad_fast_grd_phi_dow(row_fcts_qfast);
    }
    if (col_fcts_V && !col_fcts_V_const) {
      col_fcts_d = get_quad_fast_phi_dow(col_fcts_qfast);
    }
  }

  mat = DM_assign_matrices(voidmat, &real_mat, &real_d_mat,
				      fill_info,
				      /* row_fcts_V, col_fcts_V, */
				      row_fcts_V_const, col_fcts_V_const);

  if (pwc) {
    Lb1 = fill_info->op_info.Lb1.real_d(
      el_info, quad, 0, fill_info->op_info.user_data);
  }

  for (iq = 0; iq < quad->n_points; iq++) {
    if (!pwc) {
      Lb1 = fill_info->op_info.Lb1.real_d(
	el_info, quad, iq, fill_info->op_info.user_data);
    }

    col_fcts_val = col_fcts_qfast->phi[iq];
    grd_row_fcts = row_fcts_qfast->grd_phi[iq];

    for (i_tr = 0; i_tr < n_tr_row_fcts; i_tr++) {
      i = tan ? row_fcts_map[i_tr] : i_tr;
      for (j_tr = 0; j_tr < n_tr_col_fcts; j_tr++) {
	j = col_fcts_map[j_tr];

	if (row_fcts_V_const && col_fcts_V_const) {
	  DMDMAXPY_DOW(
	    quad->w[iq]*col_fcts_val[j],
	    /**/ DMbtv_tan(
	      n_lambda, Lb1, grd_row_fcts[i], tmp,
	      tan ? wall : n_lambda),
	    mat[i][j]);
	} else if (row_fcts_V_const) {
	  if (!row_fcts_V && !row_fcts_C) {
#if HAVE_DM_DST_TYPE || HAVE_SCM_DST_TYPE
	    real_mat[i][j] +=
	      quad->w[iq] * SV_DMDutbv_tan(
		n_lambda, grd_row_fcts[i], Lb1, col_fcts_d[iq][j],
		tan ? wall : n_lambda);
#endif
	  } else {
	    CV_DMDutbv_tan(
	      n_lambda, grd_row_fcts[i], Lb1, col_fcts_d[iq][j], val_d,
	      tan ? wall : n_lambda);
	    DMDMAXPY_DOW(quad->w[iq], val_d, real_d_mat[i][j]);
	  }
	} else if (row_fcts_V_const) {
	  if (!row_fcts_V && !row_fcts_C) {
#if HAVE_DM_DST_TYPE || HAVE_SCM_DST_TYPE
	    real_mat[i][j] +=
	      quad->w[iq] * VS_DMDutbv_tan(
		n_lambda, grd_row_fcts_d[iq][i], Lb1, col_fcts_val[j],
		tan ? wall : n_lambda);
#endif
	  } else {
	    VC_DMDutbv_tan(
	      n_lambda, grd_row_fcts_d[iq][i], Lb1, col_fcts_val[j], val_d,
	      tan ? wall : n_lambda);
	    DMDMAXPY_DOW(quad->w[iq], val_d, real_d_mat[i][j]);
	  }
	} else {
	  real_mat[i][j] +=
	    quad->w[iq] * VV_DMDutbv_tan(
	      n_lambda, grd_row_fcts_d[iq][i], Lb1, col_fcts_d[iq][j],
	      tan ? wall : n_lambda);
	}
      } /* column loop */
    } /* row loop */
  } /* quad-point loop */

  /* Now possibly condense the temporary matrix if either of the
   * directions was p.w. constant
   */
  DM_condense_matrices(voidmat, fill_info,
				  row_fcts_qfast, col_fcts_qfast,
				  row_fcts_V_const, col_fcts_V_const);
}

/* >>> */

/* <<< quad_10 DEFUNs */

/* only mixed, tan and pwc are relevant, sym is fixed at 0 */
#define DEFUN_WALL_FCTS(namebase, meshdim, wall)	\
  DEFUN_WALL_FCT(namebase, meshdim, wall, 0, 0, 0, 0);	\
  DEFUN_WALL_FCT(namebase, meshdim, wall, 1, 0, 0, 0);	\
  DEFUN_WALL_FCT(namebase, meshdim, wall, 0, 0, 1, 0);	\
  DEFUN_WALL_FCT(namebase, meshdim, wall, 1, 0, 1, 0);	\
  DEFUN_WALL_FCT(namebase, meshdim, wall, 0, 0, 0, 1);	\
  DEFUN_WALL_FCT(namebase, meshdim, wall, 1, 0, 0, 1);	\
  DEFUN_WALL_FCT(namebase, meshdim, wall, 0, 0, 1, 1);	\
  DEFUN_WALL_FCT(namebase, meshdim, wall, 1, 0, 1, 1)

DEFUN_EL_FCTS(quad_10);

#undef DEFUN_WALL_FCTS

/* >>> */

/* <<< quad_10 table initializer */

#define WALL_FCTS_10(meshdim, wall)			\
  WALL_FCT(quad_10, meshdim, wall, 0, 0, 0, 0),		\
    WALL_FCT(quad_10, meshdim, wall, 1, 0, 0, 0),	\
    NULL,						\
    NULL,						\
    WALL_FCT(quad_10, meshdim, wall, 0, 0, 1, 0),	\
    WALL_FCT(quad_10, meshdim, wall, 1, 0, 1, 0),	\
    NULL,						\
    NULL,						\
    WALL_FCT(quad_10, meshdim, wall, 0, 0, 0, 1),	\
    WALL_FCT(quad_10, meshdim, wall, 1, 0, 0, 1),	\
    NULL,						\
    NULL,						\
    WALL_FCT(quad_10, meshdim, wall, 0, 0, 1, 1),	\
    WALL_FCT(quad_10, meshdim, wall, 1, 0, 1, 1),	\
    NULL,						\
    NULL,

/* >>> */

/* >>> */

/* <<< quad_11 */

/* <<< quad_11_multiplex */

/* Note: this function is only called with sym == true if also tan ==
 * true.
 */
static inline void
MULTIPLEX_NAME(quad_11)(const EL_INFO *el_info,
			int n_lambda,
			int wall,
			const BNDRY_FILL_INFO *fill_info,
			void **voidmat,
			int mixed, int sym, int tan, int pwc)
{
  const REAL_D *Lb0 = NULL;
  const REAL_D *Lb1 = NULL;
  const REAL      *row_fcts_val, *col_fcts_val;
  const REAL_B    *grd_row_fcts, *grd_col_fcts;
  int             iq, i, j;
  const QUAD_FAST *row_fcts_qfast, *col_fcts_qfast;
  const BAS_FCTS  *row_fcts, *col_fcts;
  const QUAD      *quad;
  const int       *row_fcts_map = NULL, *col_fcts_map = NULL;
  int             n_tr_row_fcts, n_tr_col_fcts;
  int             i_tr, j_tr;
  REAL_D tmp1, tmp2, val;
  REAL_D **mat = (REAL_D **)voidmat;
  REAL_D          val_d;
  REAL_D          **real_d_mat = NULL;
  REAL            **real_mat = NULL;
  bool            row_fcts_V_const, col_fcts_V_const;
  const REAL_DB   *const*grd_row_fcts_d = NULL, *const*grd_col_fcts_d = NULL;
  const REAL_D    *const*row_fcts_d = NULL, *const*col_fcts_d = NULL;

  row_fcts_qfast = fill_info->row_wquad_fast[1]->quad_fast[wall];
  quad      = row_fcts_qfast->quad;
  row_fcts       = row_fcts_qfast->bas_fcts;

  row_fcts_V_const = !row_fcts_V || row_fcts->dir_pw_const;

  if (!mixed) {
    col_fcts_qfast   = row_fcts_qfast;
    col_fcts         = row_fcts;
    col_fcts_V_const = row_fcts_V_const;
  } else {
    col_fcts_qfast   = fill_info->col_quad_fast[1];
    col_fcts         = col_fcts_qfast->bas_fcts;
    col_fcts_V_const = !col_fcts_V || col_fcts->dir_pw_const;
  }

  if (pwc) {
    Lb0 = fill_info->op_info.Lb0.real_d(
      el_info, quad, 0, fill_info->op_info.user_data);
    Lb1 = fill_info->op_info.Lb1.real_d(
      el_info, quad, 0, fill_info->op_info.user_data);
  }

  if (sym) {

    if (row_fcts_V) {
      if (row_fcts_V_const) {
	mat = (REAL_D **)fill_info->scl_el_mat;
	DM_clear_tmp_mat(mat, fill_info);
      } else {
	grd_row_fcts_d =
	  grd_col_fcts_d = get_quad_fast_grd_phi_dow(row_fcts_qfast);
	row_fcts_d = col_fcts_d = get_quad_fast_phi_dow(row_fcts_qfast);
      }
    } else {
      mat = (REAL_D **)voidmat;
    }

    /* we do not need to worry here about the orientation and element type */
    row_fcts_map = col_fcts_map = row_fcts->trace_dof_map[0][0][wall];
    n_tr_row_fcts = n_tr_col_fcts = row_fcts->n_trace_bas_fcts[wall];

    if (row_fcts_V_const) { /* non-vector valued, or direction p.w. constant */
      for (iq = 0; iq < quad->n_points; iq++) {
	if (!pwc) {
	  Lb0 = fill_info->op_info.Lb0.real_d(
	    el_info, quad, iq, fill_info->op_info.user_data);
	  Lb1 = fill_info->op_info.Lb1.real_d(
	    el_info, quad, iq, fill_info->op_info.user_data);
	}

	grd_row_fcts = grd_col_fcts = col_fcts_qfast->grd_phi[iq];
	row_fcts_val = col_fcts_val = col_fcts_qfast->phi[iq];

	for (i_tr = 0; i_tr < n_tr_row_fcts; i_tr++) {
	  i = row_fcts_map[i_tr];
	  for (j_tr = i_tr+1; j_tr < n_tr_row_fcts; j_tr++){
	    j = col_fcts_map[j_tr];

	    DMAXPBY_DOW(
	      quad->w[iq]*row_fcts_val[i],
	      /**/ DMbtv_tan(
		n_lambda, Lb0, grd_col_fcts[j], tmp1, wall),
	      quad->w[iq]*col_fcts_val[j],
	      /**/ DMbtv_tan(
		n_lambda, Lb1, grd_row_fcts[i], tmp2, wall),
	      val);
	    DMDMAXPY_DOW(
	      1.0, /**/ val, mat[i][j]);
	    DMDMAXTPY_DOW(
	      -1.0, /**/ val, mat[j][i]);

	  }
	}
      }
      if (row_fcts_V) {
	/* condense with the directions of the basis functions */
	VV_DM_condense_el_mat(
	  (REAL **)voidmat, fill_info, row_fcts_qfast, col_fcts_qfast,
	  true, false);
      }
    } else { /* vector-valued, and direction not p.w. constant */
      real_mat = (REAL **)voidmat;

      for (iq = 0; iq < quad->n_points; iq++) {
	if (!pwc) {
	  Lb0 = fill_info->op_info.Lb0.real_d(
	    el_info, quad, iq, fill_info->op_info.user_data);
	  Lb1 = fill_info->op_info.Lb1.real_d(
	    el_info, quad, iq, fill_info->op_info.user_data);
	}

	grd_row_fcts = grd_col_fcts = col_fcts_qfast->grd_phi[iq];
	row_fcts_val = col_fcts_val = col_fcts_qfast->phi[iq];

	for (i_tr = 0; i_tr < n_tr_row_fcts; i_tr++) {
	  i = row_fcts_map[i_tr];
	  for (j_tr = i_tr+1; j_tr < n_tr_row_fcts; j_tr++){
	    REAL val;

	    j = row_fcts_map[j_tr];

	    val = quad->w[iq] *
	      (VV_DMDutbv_tan(
		n_lambda, grd_row_fcts_d[iq][i], Lb1, col_fcts_d[iq][j], wall),
	       +
	       VV_DMubtDv_tan(
		 n_lambda,
		 row_fcts_d[iq][i], Lb0, grd_col_fcts_d[iq][j], wall));

	    real_mat[i][j] += val;
	    real_mat[j][i] -= val;
	  }
	}
      }
    }
  } else { /* !sym */
    if (row_fcts_V || col_fcts_V) {
      if (row_fcts_V && !row_fcts_V_const) {
	row_fcts_d     = get_quad_fast_phi_dow(row_fcts_qfast);
	grd_row_fcts_d = get_quad_fast_grd_phi_dow(row_fcts_qfast);
      }
      if (col_fcts_V && !col_fcts_V_const) {
	grd_col_fcts_d = get_quad_fast_grd_phi_dow(col_fcts_qfast);
	col_fcts_d     = get_quad_fast_phi_dow(col_fcts_qfast);
      }
    }

    mat = DM_assign_matrices(voidmat, &real_mat, &real_d_mat,
					fill_info,
					/* row_fcts_V, col_fcts_V, */
					row_fcts_V_const, col_fcts_V_const);

    for (iq = 0; iq < quad->n_points; iq++) {
      if (!pwc) {
	Lb0 = fill_info->op_info.Lb0.real_d(
	  el_info, quad, iq, fill_info->op_info.user_data);
	Lb1 = fill_info->op_info.Lb1.real_d(
	  el_info, quad, iq, fill_info->op_info.user_data);
      }

      grd_col_fcts = col_fcts_qfast->grd_phi[iq];
      col_fcts_val = col_fcts_qfast->phi[iq];
      grd_row_fcts = row_fcts_qfast->grd_phi[iq];
      row_fcts_val = row_fcts_qfast->phi[iq];

      if (tan) {
	n_tr_col_fcts = col_fcts->n_trace_bas_fcts[wall];
	col_fcts_map  = col_fcts->trace_dof_map[0][0][wall];
      } else {
	n_tr_col_fcts = col_fcts_qfast->n_bas_fcts;
      }
      n_tr_row_fcts = fill_info->n_trace_row_fcts[wall];
      row_fcts_map  = fill_info->row_fcts_trace_map[wall];

      for (j_tr = 0; j_tr < n_tr_col_fcts; j_tr++) {
	j = tan ? col_fcts_map[j_tr] : j_tr;
	for (i_tr = 0; i_tr < n_tr_row_fcts; i_tr++) {
	  i = row_fcts_map[i_tr];

	  if (row_fcts_V_const && col_fcts_V_const) {
	    DMDMAXPY_DOW(
	      quad->w[iq]*row_fcts_val[i],
	      /**/ DMbtv_tan(
		n_lambda, Lb0, grd_col_fcts[j], tmp1,
		tan ? wall : n_lambda),
	      mat[i][j]);
	  } else if (row_fcts_V_const) {
	    if (!row_fcts_V && !row_fcts_C) {
#if HAVE_DM_DST_TYPE || HAVE_SCM_DST_TYPE
	      real_mat[i][j] +=
		quad->w[iq] * SV_DMubtDv_tan(
		  n_lambda, row_fcts_val[i], Lb0, grd_col_fcts_d[iq][j],
		  tan ? wall : n_lambda);
#endif
	    } else {
	      CV_DMubtDv_tan(
		n_lambda, row_fcts_val[i], Lb0, grd_col_fcts_d[iq][j], val_d,
		tan ? wall : n_lambda);
	      DMDMAXPY_DOW(quad->w[iq], val_d, real_d_mat[i][j]);
	    }
	  } else if (row_fcts_V_const) {
	    if (!row_fcts_V && !row_fcts_C) {
#if HAVE_DM_DST_TYPE || HAVE_SCM_DST_TYPE
	      real_mat[i][j] +=
		quad->w[iq] * VS_DMubtDv_tan(
		  n_lambda, row_fcts_d[iq][i], Lb0, grd_col_fcts[j],
		  tan ? wall : n_lambda);
#endif
	    } else {
	      VC_DMubtDv_tan(
		n_lambda, row_fcts_d[iq][i], Lb0, grd_col_fcts[j], val_d,
		tan ? wall : n_lambda);
	      DMDMAXPY_DOW(quad->w[iq], val_d, real_d_mat[i][j]);
	    }
	  } else {
	    real_mat[i][j] +=
	      quad->w[iq] * VV_DMubtDv_tan(
		n_lambda, row_fcts_d[iq][i], Lb0, grd_col_fcts_d[iq][j],
		tan ? wall : n_lambda);
	  }
	} /* row loop */
      } /* column loop */

      if (tan) {
	n_tr_row_fcts = fill_info->n_trace_row_fcts[wall];
	row_fcts_map  = fill_info->row_fcts_trace_map[wall];
      } else {
	n_tr_row_fcts = row_fcts_qfast->n_bas_fcts;
      }
      if (!mixed) {
	n_tr_col_fcts = col_fcts->n_trace_bas_fcts[wall];
	col_fcts_map  = col_fcts->trace_dof_map[0][0][wall];
      } else {
	int ov = col_fcts_qfast->quad->subsplx;
	n_tr_col_fcts = col_fcts->n_trace_bas_fcts[ov];
	col_fcts_map  = col_fcts->trace_dof_map[0][0][ov];
      }

      for (i_tr = 0; i_tr < n_tr_row_fcts; i_tr++) {
	i = tan ? row_fcts_map[i_tr] : i_tr;
	for (j_tr = 0; j_tr < n_tr_col_fcts; j_tr++) {
	  j = col_fcts_map[j_tr];

	  if (row_fcts_V_const && col_fcts_V_const) {
	    DMDMAXPY_DOW(
	      quad->w[iq]*col_fcts_val[j],
	      /**/ DMbtv_tan(
		n_lambda, Lb1, grd_row_fcts[i], tmp1,
		tan ? wall : n_lambda),
	      mat[i][j]);
	  } else if (row_fcts_V_const) {
	    if (!row_fcts_V && !row_fcts_C) {
#if HAVE_DM_DST_TYPE || HAVE_SCM_DST_TYPE
	      real_mat[i][j] +=
		quad->w[iq] * SV_DMDutbv_tan(
		  n_lambda, grd_row_fcts[i], Lb1, col_fcts_d[iq][j],
		  tan ? wall : n_lambda);
#endif
	    } else {
	      CV_DMDutbv_tan(
		n_lambda, grd_row_fcts[i], Lb1, col_fcts_d[iq][j], val_d,
		tan ? wall : n_lambda);
	      DMDMAXPY_DOW(quad->w[iq], val_d, real_d_mat[i][j]);
	    }
	  } else if (row_fcts_V_const) {
	    if (!row_fcts_V && !row_fcts_C) {
#if HAVE_DM_DST_TYPE || HAVE_SCM_DST_TYPE
	      real_mat[i][j] +=
		quad->w[iq] * VS_DMDutbv_tan(
		  n_lambda, grd_row_fcts_d[iq][i], Lb1, col_fcts_val[j],
		  tan ? wall : n_lambda);
#endif
	    } else {
	      VC_DMDutbv_tan(
		n_lambda, grd_row_fcts_d[iq][i], Lb1, col_fcts_val[j], val_d,
		tan ? wall : n_lambda);
	      DMDMAXPY_DOW(quad->w[iq], val_d, real_d_mat[i][j]);
	    }
	  } else {
	    real_mat[i][j] +=
	      quad->w[iq] * VV_DMDutbv_tan(
		n_lambda, grd_row_fcts_d[iq][i], Lb1, col_fcts_d[iq][j],
		tan ? wall : n_lambda);
	  }
	} /* column loop */
      } /* row loop */
    } /* quad-point loop */

    /* Now possibly condense the temporary matrix if either of the
     * directions was p.w. constant
     */
    DM_condense_matrices(voidmat, fill_info,
				    row_fcts_qfast, col_fcts_qfast,
				    row_fcts_V_const, col_fcts_V_const);
  }
}

/* >>> */

/* <<< quad_11 DEFUNs */

/* sym implies tan and !mixed. */
#define DEFUN_WALL_FCTS(namebase, meshdim, wall)		\
  DEFUN_WALL_FCT(namebase, meshdim, wall, 0, 0, 0, 0);		\
  DEFUN_WALL_FCT(namebase, meshdim, wall, 1, 0, 0, 0);		\
  /*DEFUN_WALL_FCT(namebase, meshdim, wall, 0, 1, 0, 0);*/	\
  /*DEFUN_WALL_FCT(namebase, meshdim, wall, 1, 1, 0, 0);*/	\
  DEFUN_WALL_FCT(namebase, meshdim, wall, 0, 0, 1, 0);		\
  DEFUN_WALL_FCT(namebase, meshdim, wall, 1, 0, 1, 0);		\
  DEFUN_WALL_FCT(namebase, meshdim, wall, 0, 1, 1, 0);		\
  /*DEFUN_WALL_FCT(namebase, meshdim, wall, 1, 1, 1, 0);*/	\
  DEFUN_WALL_FCT(namebase, meshdim, wall, 0, 0, 0, 1);		\
  DEFUN_WALL_FCT(namebase, meshdim, wall, 1, 0, 0, 1);		\
  /*DEFUN_WALL_FCT(namebase, meshdim, wall, 0, 1, 0, 1);*/	\
  /*DEFUN_WALL_FCT(namebase, meshdim, wall, 1, 1, 0, 1);*/	\
  DEFUN_WALL_FCT(namebase, meshdim, wall, 0, 0, 1, 1);		\
  DEFUN_WALL_FCT(namebase, meshdim, wall, 1, 0, 1, 1);		\
  DEFUN_WALL_FCT(namebase, meshdim, wall, 0, 1, 1, 1)		\
  /*DEFUN_WALL_FCT(namebase, meshdim, wall, 1, 1, 1, 1)*/

DEFUN_EL_FCTS(quad_11);

#undef DEFUN_WALL_FCTS

/* >>> */

/* <<< quad_11 table initializer */

#define WALL_FCTS_11(meshdim, wall)			\
  WALL_FCT(quad_11, meshdim, wall, 0, 0, 0, 0),		\
    WALL_FCT(quad_11, meshdim, wall, 1, 0, 0, 0),	\
    NULL,						\
    NULL,						\
    WALL_FCT(quad_11, meshdim, wall, 0, 0, 1, 0),	\
    WALL_FCT(quad_11, meshdim, wall, 1, 0, 1, 0),	\
    WALL_FCT(quad_11, meshdim, wall, 0, 1, 1, 0),	\
    NULL,						\
    WALL_FCT(quad_11, meshdim, wall, 0, 0, 0, 1),	\
    WALL_FCT(quad_11, meshdim, wall, 1, 0, 0, 1),	\
    NULL,						\
    NULL,						\
    WALL_FCT(quad_11, meshdim, wall, 0, 0, 1, 1),	\
    WALL_FCT(quad_11, meshdim, wall, 1, 0, 1, 1),	\
    WALL_FCT(quad_11, meshdim, wall, 0, 1, 1, 1),	\
    NULL,

/* >>> */

/* >>> */

/* <<< quad_0 */

/* <<< quad_0_multiplex */

static inline void
MULTIPLEX_NAME(quad_0)(const EL_INFO *el_info,
		       int n_lambda,
		       int wall,
		       const BNDRY_FILL_INFO *fill_info,
		       void **voidmat,
		       int mixed, int sym, int tan, int pwc)
{
  const REAL *c = (const REAL *)0;
  const REAL      *row_fcts_val, *col_fcts_val;
  int             iq, i, j;
  const QUAD_FAST *row_fcts_qfast, *col_fcts_qfast;
  const BAS_FCTS  *row_fcts, *col_fcts;
  const QUAD      *quad;
  const int       *row_fcts_map = NULL, *col_fcts_map = NULL;
  int             n_tr_row_fcts, n_tr_col_fcts;
  int             i_tr, j_tr;
  REAL_D      val;
  REAL_D      **mat;
  REAL_D          **real_d_mat = NULL;
  REAL            **real_mat = NULL;
  bool            row_fcts_V_const, col_fcts_V_const;
  const REAL_D    *const*row_fcts_d = NULL;
  const REAL_D    *const*col_fcts_d = NULL;

  row_fcts_qfast = fill_info->row_wquad_fast[0]->quad_fast[wall];
  quad      = row_fcts_qfast->quad;
  row_fcts       = row_fcts_qfast->bas_fcts;

  row_fcts_V_const = !row_fcts_V || row_fcts->dir_pw_const;

  if (tan) { /* always */
    /* we do not need to worry here about the orientation and element type */
    row_fcts_map  = fill_info->row_fcts_trace_map[wall];
    n_tr_row_fcts = fill_info->n_trace_row_fcts[wall];
  } else {
    /* not reached */
    n_tr_row_fcts = row_fcts_qfast->n_bas_fcts;
  }

  if (!mixed) {
    col_fcts_qfast   = row_fcts_qfast;
    col_fcts         = row_fcts;
    n_tr_col_fcts    = n_tr_row_fcts;
    col_fcts_V_const = row_fcts_V_const;
    if (tan) { /* always */
      col_fcts_map = row_fcts_map;
    }
  } else {
    col_fcts_qfast   = fill_info->col_quad_fast[0];
    col_fcts         = col_fcts_qfast->bas_fcts;
    col_fcts_V_const = !col_fcts_V || col_fcts->dir_pw_const;
    if (tan) { /* always */
      /* we do not need to worry here about the orientation and element type */
      int ov = col_fcts_qfast->quad->subsplx;
      col_fcts_map  = col_fcts->trace_dof_map[0][0][ov];
      n_tr_col_fcts = col_fcts->n_trace_bas_fcts[ov];
    } else {
      /* not reached */
      n_tr_col_fcts = col_fcts_qfast->n_bas_fcts;
    }
  }

  if (pwc) {
    c = fill_info->op_info.c.real_d(
      el_info, quad, 0, fill_info->op_info.user_data);
  }
  if (sym) {

    if (row_fcts_V) {
      if (row_fcts_V_const) {
	mat = (REAL_D **)fill_info->scl_el_mat;
	DM_clear_tmp_mat(mat, fill_info);
      } else {
	row_fcts_d = col_fcts_d = get_quad_fast_phi_dow(row_fcts_qfast);
      }
    } else {
      mat = (REAL_D **)voidmat;
    }

    if (row_fcts_V_const) {

      for (iq = 0; iq < quad->n_points; iq++) {
	if (!pwc) {
	  c = fill_info->op_info.c.real_d(
	    el_info, quad, iq, fill_info->op_info.user_data);
	}

	col_fcts_val = row_fcts_val = row_fcts_qfast->phi[iq];

	for (i_tr = 0; i_tr < n_tr_row_fcts; i_tr++) {
	  i = row_fcts_map[i_tr];

	  DMDMAXPY_DOW(
	    quad->w[iq] * row_fcts_val[i] * col_fcts_val[i],
	    /**/ c, mat[i][i]);

	  for (j_tr = i_tr+1; j_tr < n_tr_row_fcts; j_tr++){
	    j = row_fcts_map[j_tr];

	    DMAXEY_DOW(
	      quad->w[iq] * row_fcts_val[i] * col_fcts_val[j],
	      /**/ c, val);
	    DMDMAXPY_DOW(
	      1.0, /**/ val, mat[i][j]);
	    DMDMAXTPY_DOW(
	      1.0, /**/ val, mat[j][i]);
	  }
	}
	if (row_fcts_V) {
	  /* condense with the directions of the basis functions */
	  VV_DM_condense_el_mat(
	    (REAL **)voidmat,
	    fill_info, row_fcts_qfast, col_fcts_qfast, true, false);
	}
      }
    } else { /* vector-valued, and direction not p.w. constant */
      real_mat = (REAL **)voidmat;

      for (iq = 0; iq < quad->n_points; iq++) {
	c = fill_info->op_info.c.real_d(
	  el_info, quad, iq, fill_info->op_info.user_data);

	for (i_tr = 0; i_tr < n_tr_row_fcts; i_tr++) {
	  i = row_fcts_map[i_tr];

	  real_mat[i][i] +=
	    quad->w[iq]
	    *
	    DMGRAMSCP_DOW(
	      /**/ c, row_fcts_d[iq][i], col_fcts_d[iq][i]);

	  for (j_tr = i_tr+1; j_tr < n_tr_row_fcts; j_tr++){
	    REAL val;

	    j = row_fcts_map[j_tr];

	    val =
	      quad->w[iq]
	      *
	      DMGRAMSCP_DOW(/**/ c,
				     row_fcts_d[iq][i], col_fcts_d[iq][j]);

	    real_mat[i][j] += val;
	    real_mat[j][i] += val;
	  }
	}
      }
    }
  } else {      /*  non symmetric assembling   */
    const REAL_D *const*row_fcts_d = NULL;
    const REAL_D *const*col_fcts_d = NULL;

    if (row_fcts_V || col_fcts_V) {
      if (row_fcts_V && !row_fcts_V_const) {
	row_fcts_d = get_quad_fast_phi_dow(row_fcts_qfast);
      }
      if (col_fcts_V && !col_fcts_V_const) {
	col_fcts_d = get_quad_fast_phi_dow(col_fcts_qfast);
      }
    }

    mat = DM_assign_matrices(voidmat, &real_mat, &real_d_mat,
					fill_info,
					/* row_fcts_V, col_fcts_V, */
					row_fcts_V_const, col_fcts_V_const);

    for (iq = 0; iq < quad->n_points; iq++) {
      if (!pwc) {
	c = fill_info->op_info.c.real_d(
	  el_info, quad, iq, fill_info->op_info.user_data);
      }
      row_fcts_val = row_fcts_qfast->phi[iq];
      col_fcts_val = col_fcts_qfast->phi[iq];

      for (i_tr = 0; i_tr < n_tr_row_fcts; i_tr++) {
	i = row_fcts_map[i_tr];

	for (j_tr = 0; j_tr < n_tr_col_fcts; j_tr++) {
	  j = col_fcts_map[j_tr];

	  if (row_fcts_V_const && col_fcts_V_const) {
	    DMDMAXPY_DOW(
	      quad->w[iq] * row_fcts_val[i] * col_fcts_val[j],
	      /**/ c, mat[i][j]);
	  } else if (row_fcts_V_const) {
	    if (!row_fcts_V && !row_fcts_C) {
#if HAVE_DM_DST_TYPE || HAVE_SCM_DST_TYPE
	      REAL_D val_d = { 0.0, };
	      real_mat[i][j] +=
		quad->w[iq] * row_fcts_val[i] *
		SUM_DOW(DMGEMV_DOW(
			  1.0, /**/ c, col_fcts_d[iq][j], 0.0, val_d));
#endif
	    } else {
	      DMGEMV_DOW(
		quad->w[iq]*row_fcts_val[i], /**/ c,
		col_fcts_d[iq][j], 1.0, real_d_mat[i][j]);
	    }
	  } else if (col_fcts_V_const) {
	    if (!col_fcts_V && !col_fcts_C) {
#if HAVE_DM_DST_TYPE || HAVE_SCM_DST_TYPE
	      REAL_D val_d = { 0, };
	      real_mat[i][j] +=
		quad->w[iq] * col_fcts_val[j] *
		SUM_DOW(DMGEMTV_DOW(
			  1.0, /**/ c, row_fcts_d[iq][i], 0.0, val_d));
#endif
	    } else {
	      DMGEMTV_DOW(
		quad->w[iq]*col_fcts_val[j],
		/**/ c, row_fcts_d[iq][i], 1.0, real_d_mat[i][j]);
	    }
	  } else {
	    real_mat[i][j] +=
	      quad->w[iq]
	      *
	      DMGRAMSCP_DOW(
		/**/ c, row_fcts_d[iq][i], col_fcts_d[iq][i]);
	  }
	} /* column loop */
      } /* row loop */
    } /* quad-point loop */

    /* Now possibly condense the temporary matrix if either of the
     * directions was p.w. constant
     */
    DM_condense_matrices(voidmat, fill_info,
				    row_fcts_qfast, col_fcts_qfast,
				    row_fcts_V_const, col_fcts_V_const);
  } /* non-symmetric assembly */
}

/* >>> */

/* <<< quad_0 DEFUNs */

/* sym implies !mixed, tan is fixed at 1 */
#define DEFUN_WALL_FCTS(namebase, meshdim, wall)		\
  /*DEFUN_WALL_FCT(namebase, meshdim, wall, 0, 0, 0, 0);*/	\
  /*DEFUN_WALL_FCT(namebase, meshdim, wall, 1, 0, 0, 0);*/	\
  /*DEFUN_WALL_FCT(namebase, meshdim, wall, 0, 1, 0, 0);*/	\
  /*DEFUN_WALL_FCT(namebase, meshdim, wall, 1, 1, 0, 0);*/	\
  DEFUN_WALL_FCT(namebase, meshdim, wall, 0, 0, 1, 0);		\
  DEFUN_WALL_FCT(namebase, meshdim, wall, 1, 0, 1, 0);		\
  DEFUN_WALL_FCT(namebase, meshdim, wall, 0, 1, 1, 0);		\
  /*DEFUN_WALL_FCT(namebase, meshdim, wall, 1, 1, 1, 0);*/	\
  /*DEFUN_WALL_FCT(namebase, meshdim, wall, 0, 0, 0, 1);*/	\
  /*DEFUN_WALL_FCT(namebase, meshdim, wall, 1, 0, 0, 1);*/	\
  /*DEFUN_WALL_FCT(namebase, meshdim, wall, 0, 1, 0, 1);*/	\
  /*DEFUN_WALL_FCT(namebase, meshdim, wall, 1, 1, 0, 1);*/	\
  DEFUN_WALL_FCT(namebase, meshdim, wall, 0, 0, 1, 1);		\
  DEFUN_WALL_FCT(namebase, meshdim, wall, 1, 0, 1, 1);		\
  DEFUN_WALL_FCT(namebase, meshdim, wall, 0, 1, 1, 1)		\
  /*DEFUN_WALL_FCT(namebase, meshdim, wall, 1, 1, 1, 1)*/

DEFUN_EL_FCTS(quad_0);

#undef DEFUN_WALL_FCTS

/* >>> */

/* <<< quad_0 table initializer */

/* WALL_FCT(namebase, meshdim, wall, mixed, sym, tan, pwc)
 *
 * sym implies !mixed, tan is fixed at 1 (tangential or not makes no
 * difference for the 0-th order term).
 */

#define WALL_FCTS_0(meshdim, wall)			\
  WALL_FCT(quad_0, meshdim, wall, 0, 0, 1, 0),		\
    WALL_FCT(quad_0, meshdim, wall, 1, 0, 1, 0),	\
    WALL_FCT(quad_0, meshdim, wall, 0, 1, 1, 0),	\
    NULL,						\
    WALL_FCT(quad_0, meshdim, wall, 0, 0, 1, 0),	\
    WALL_FCT(quad_0, meshdim, wall, 1, 0, 1, 0),	\
    WALL_FCT(quad_0, meshdim, wall, 0, 1, 1, 0),	\
    NULL,						\
    WALL_FCT(quad_0, meshdim, wall, 0, 0, 1, 1),	\
    WALL_FCT(quad_0, meshdim, wall, 1, 0, 1, 1),	\
    WALL_FCT(quad_0, meshdim, wall, 0, 1, 1, 1),	\
    NULL,						\
    WALL_FCT(quad_0, meshdim, wall, 0, 0, 1, 1),	\
    WALL_FCT(quad_0, meshdim, wall, 1, 0, 1, 1),	\
    WALL_FCT(quad_0, meshdim, wall, 0, 1, 1, 1),	\
    NULL,

/* >>> */

/* >>> */

/* <<< el_wall_fcts lookup tables */

#define EL_WALL_FCTS(MESHDIM, WALL)		\
  {						\
    { WALL_FCTS_0 (MESHDIM, WALL) },		\
    { WALL_FCTS_11(MESHDIM, WALL) },		\
    { WALL_FCTS_10(MESHDIM, WALL) },		\
    { WALL_FCTS_01(MESHDIM, WALL) },		\
    { WALL_FCTS_2 (MESHDIM, WALL) },		\
      }

void (*NAME(el_wall_fcts)
      [DIM_MAX+1][N_WALLS_MAX][5][16])(const EL_INFO *el_info,
				       const BNDRY_FILL_INFO *fill_info,
				       void **el_mat) =
{
  /* only here to make sure that indexing with "dim" works */
  {{{ NULL, }, }, },
#if DIM_MAX > 0 /* always */
  {
    EL_WALL_FCTS(1, 0),
    EL_WALL_FCTS(1, 1),
  },
#endif
#if DIM_MAX > 1
  {
    EL_WALL_FCTS(2, 0),
    EL_WALL_FCTS(2, 1),
    EL_WALL_FCTS(2, 2),
  },
#endif
#if DIM_MAX > 2
  {
    EL_WALL_FCTS(3, 0),
    EL_WALL_FCTS(3, 1),
    EL_WALL_FCTS(3, 2),
    EL_WALL_FCTS(3, 3),
  },
#endif
#if DIM_MAX > 3
# error unsupported DIM_MAX (> 3)
#endif
};

/* >>> */

/*
 * Local Variables: ***
 * mode: C ***
 * End: ***
 */
