/*******************************************************************************
 * ALBERTA:  an Adaptive multi Level finite element toolbox using
 *           Bisectioning refinement and Error control by Residual
 *           Techniques for scientific Applications
 *
 * file:     el_vec.h
 *
 * description: support routines for operation on element vector and matrices
 *
 *******************************************************************************
 *
 *  this file's author(s): Claus-Justus Heine
 *                         Abteilung fuer Angewandte Mathematik
 *                         Albert-Ludwigs-Universitaet Freiburg
 *                         Hermann-Herder-Str. 10
 *                         D-79104 Freiburg im Breisgau, Germany
 *
 *  http://www.alberta-fem.de
 *
 *  (c) by C.-J. Heine (2009)
 *
 ******************************************************************************/

#ifndef _ALBERTA_EL_VEC_H_
#define _ALBERTA_EL_VEC_H_

#include "alberta.h"

/* Initializer for an uninitialized element-vector. */
#define DEFUN_INIT_EL_VEC(VECNAME)			\
  static inline EL_##VECNAME##_VEC *			\
  CPP_CONCAT3(init_el_, VECNAME##_name, _vec)(		\
    EL_##VECNAME##_VEC *vec, int size, int size_max)	\
  {							\
    vec->n_components     = size;			\
    vec->n_components_max = size_max;			\
    vec->reserved         = 1;				\
    DBL_LIST_INIT(&vec->chain);				\
    return vec;						\
  }							\
  struct _AI_semicolon_dummy

DEFUN_INIT_EL_VEC(INT);
DEFUN_INIT_EL_VEC(DOF);
DEFUN_INIT_EL_VEC(REAL);
DEFUN_INIT_EL_VEC(REAL_DD);
/*DEFUN_INIT_EL_VEC(REAL_D);*/
DEFUN_INIT_EL_VEC(UCHAR);
DEFUN_INIT_EL_VEC(SCHAR);
DEFUN_INIT_EL_VEC(PTR);

static inline EL_REAL_D_VEC *
init_el_real_d_vec(EL_REAL_D_VEC *vec, int size, int size_max)
{
  vec->n_components     = size;
  vec->n_components_max = size_max;
  vec->reserved         = DIM_OF_WORLD;
  DBL_LIST_INIT(&vec->chain);

  return vec;
}

#define EL_VEC_SIZEOF(VECNAME, _size_max)	\
  (((_size_max) - 1)				\
   *						\
   sizeof(EL_##VECNAME##_VEC_TYPE)		\
   +						\
   sizeof(EL_##VECNAME##_VEC))

/* An automatic element vector, beware that this is just an
 * uninitialized memory region if _init == false.
 */
#define DEF_EL_VEC_VAR(VECNAME, name, _size, _size_max, _init)	\
  char _AI_##name##_space[EL_VEC_SIZEOF(VECNAME, (_size_max))];	\
  EL_##VECNAME##_VEC *name =					\
    (_init)							\
    ? CPP_CONCAT3(init_el_, VECNAME##_name, _vec)(		\
      (EL_##VECNAME##_VEC *)_AI_##name##_space,			\
      (_size), (_size_max))					\
    : (EL_##VECNAME##_VEC *)_AI_##name##_space

/* Automatic element vector of fixed maximal size. The macro
 * "size_max" needs to be constant.
 */
#define DEF_EL_VEC_CONST(VECNAME, name, _size, _size_max)		\
  struct {								\
      EL_##VECNAME##_VEC el_vec;					\
      EL_##VECNAME##_VEC_TYPE data[MAX(1, (_size_max)-1)];		\
  } _AI_##name = {							\
    {									\
      (_size),     /* n_components */					\
      (_size_max), /* n_components_max */				\
      DBL_LIST_INITIALIZER(_AI_##name.el_vec.chain), /* chain */	\
      ((sizeof(EL_##VECNAME##_VEC_TYPE) + sizeof(REAL) - 1)		\
       / sizeof(REAL)), /* reserved (stride) */				\
    },									\
  };									\
  EL_##VECNAME##_VEC *name = &_AI_##name.el_vec

/* Allocate and initialize a single, un-chained element vector. */
#define ALLOC_EL_VEC(VECNAME, _size, _size_max)		\
  CPP_CONCAT3(init_el_, VECNAME##_name, _vec)(		\
    (EL_##VECNAME##_VEC *)MEM_ALLOC(			\
      EL_VEC_SIZEOF(VECNAME, (_size_max)), char),	\
    (_size), (_size_max))

/* <<< get_dof_indices() */

static inline EL_DOF_VEC *get_dof_indices(EL_DOF_VEC *dofs,
					  const FE_SPACE *fe_space,
					  const EL *el)
{
  const BAS_FCTS *bas_fcts = fe_space->bas_fcts;
  const FE_SPACE *fe_chain;

  if (dofs) {
    CHAIN_DO(fe_space, const FE_SPACE) {
      GET_DOF_INDICES(fe_space->bas_fcts, el, fe_space->admin, dofs->vec);
      dofs->n_components = fe_space->bas_fcts->n_bas_fcts;
      dofs = CHAIN_NEXT(dofs, EL_DOF_VEC);
    } CHAIN_WHILE(fe_space, const FE_SPACE);
  } else {
    EL_DOF_VEC *chain_dofs;

    dofs = (EL_DOF_VEC *)GET_DOF_INDICES(bas_fcts, el, fe_space->admin, NULL);
    dofs->n_components = bas_fcts->n_bas_fcts;
    DBL_LIST_INIT(&dofs->chain);
    CHAIN_FOREACH(fe_chain, fe_space, const FE_SPACE) {
      chain_dofs = (EL_DOF_VEC *)
	GET_DOF_INDICES(fe_chain->bas_fcts, el, fe_chain->admin, NULL);
      chain_dofs->n_components = fe_chain->bas_fcts->n_bas_fcts;
      CHAIN_ADD_TAIL(dofs, chain_dofs);
    }
  }
  return dofs;
}

/* >>> */

/* <<< get_bound() */

static inline EL_BNDRY_VEC *get_bound(EL_BNDRY_VEC *bndry,
				      const BAS_FCTS *bas_fcts,
				      const EL_INFO *el_info)

{
  const BAS_FCTS *bfcts_chain;

  if (bndry) {
    CHAIN_DO(bas_fcts, const BAS_FCTS) {
      GET_BOUND(bas_fcts, el_info, bndry->vec);
      bndry->n_components = bas_fcts->n_bas_fcts;
      bndry = CHAIN_NEXT(bndry, EL_BNDRY_VEC);
    } CHAIN_WHILE(bas_fcts, const BAS_FCTS);
  } else {
    EL_BNDRY_VEC *chain_bndry;

    bndry = (EL_BNDRY_VEC *)GET_BOUND(bas_fcts, el_info, NULL);
    bndry->n_components = bas_fcts->n_bas_fcts;
    DBL_LIST_INIT(&bndry->chain);
    CHAIN_FOREACH(bfcts_chain, bas_fcts, const BAS_FCTS) {
      chain_bndry = (EL_BNDRY_VEC *)GET_BOUND(bfcts_chain, el_info, NULL);
      chain_bndry->n_components = bfcts_chain->n_bas_fcts;
      CHAIN_ADD_TAIL(bndry, chain_bndry);
    }
  }
  return bndry;
}

/* >>> */

/* <<< interpol() */

static inline
void el_interpol(EL_REAL_VEC *coeff,
		 const EL_INFO *el_info, int wall,
		 const EL_INT_VEC *indices,
		 LOC_FCT_AT_QP f, void *ud,
		 const BAS_FCTS *bas_fcts)
{
  const BAS_FCTS *bfcts_chain;
  const int *ind = NULL;
  int n_ind = -1;

  if (indices) {
    ind   = indices->vec;
    n_ind = indices->n_components;
  }
  INTERPOL(bas_fcts, coeff, el_info, wall, n_ind, ind, f, ud);
  CHAIN_FOREACH(bfcts_chain, bas_fcts, const BAS_FCTS) {
    if (indices) {
      indices = CHAIN_NEXT(indices, EL_INT_VEC);
      ind     = indices->vec;
      n_ind   = indices->n_components;
    }
    coeff   = CHAIN_NEXT(coeff, EL_REAL_VEC);
    INTERPOL(bfcts_chain, coeff, el_info, wall, n_ind, ind, f, ud);
  }
}

/* >>> */

/* <<< interpol_dow() */

static inline
void el_interpol_dow(EL_REAL_VEC_D *coeff,
		     const EL_INFO *el_info, int wall,
		     const EL_INT_VEC *indices,
		     LOC_FCT_D_AT_QP f, void *f_data,
		     const BAS_FCTS *bas_fcts)
{
  const BAS_FCTS *bfcts_chain;
  const int *ind = NULL;
  int n_ind = -1;

  if (indices) {
    ind   = indices->vec;
    n_ind = indices->n_components;
  }
  INTERPOL_DOW(bas_fcts, coeff, el_info, wall, n_ind, ind, f, f_data);
  CHAIN_FOREACH(bfcts_chain, bas_fcts, const BAS_FCTS) {
    if (indices) {
      indices = CHAIN_NEXT(indices, EL_INT_VEC);
      ind     = indices->vec;
      n_ind   = indices->n_components;
    }
    coeff   = CHAIN_NEXT(coeff, EL_REAL_VEC_D);
    INTERPOL_DOW(bfcts_chain, coeff, el_info, wall, n_ind, ind, f, f_data);
  }
}

/* >>> */

/* <<< dirichlet_map() */

static inline void __dirichlet_map(S_CHAR *bound,
				   const BNDRY_FLAGS *bndry_bits,
				   int n_components,
				   const BNDRY_FLAGS mask)
{
  int i;
  BNDRY_FLAGS all;

  if (mask == NULL) {
    BNDRY_FLAGS_ALL(all);
    mask = all;
  }

  for (i = 0; i < n_components; i++) {
    if (BNDRY_FLAGS_IS_INTERIOR(bndry_bits[i])) {
      bound[i] = INTERIOR;
      continue;
    }
    if (BNDRY_FLAGS_IS_PARTOF(bndry_bits[i], mask)) {
      bound[i] = DIRICHLET;
    } else {
      bound[i] = INTERIOR;
    }
  }
}

static inline void dirichlet_map_single(EL_SCHAR_VEC *bound,
					const EL_BNDRY_VEC *bndry_bits,
					const BNDRY_FLAGS mask)
{
  bound->n_components = bndry_bits->n_components;

  __dirichlet_map(bound->vec, bndry_bits->vec, bound->n_components, mask);
}

static inline void dirichlet_map(EL_SCHAR_VEC *bound,
				 const EL_BNDRY_VEC *bndry_bits,
				 const BNDRY_FLAGS mask)
{
  const EL_BNDRY_VEC *bndry_chain;

  dirichlet_map_single(bound, bndry_bits, mask);
  CHAIN_FOREACH(bndry_chain, bndry_bits, EL_BNDRY_VEC) {
    bound = CHAIN_NEXT(bound, EL_SCHAR_VEC);
    dirichlet_map_single(bound, bndry_chain, mask);
  }
}

/* >>> */

/* <<< filling el-vecs with data */

#define DEFUN_FILL_EL_VEC(VECNAME, vecname)				\
  static inline const EL_##VECNAME##_VEC *				\
  fill_el_##vecname##_vec(EL_##VECNAME##_VEC *el_vec,			\
			  EL *el,					\
			  const DOF_##VECNAME##_VEC *dof_vec)		\
  {									\
    const FE_SPACE *fe_space = dof_vec->fe_space;			\
    const FE_SPACE *fe_chain;						\
									\
    if (el_vec) {							\
      CHAIN_DO(fe_space, const FE_SPACE) {				\
	fe_space->bas_fcts->get_##vecname##_vec(el_vec->vec, el, dof_vec); \
	el_vec->n_components = fe_space->bas_fcts->n_bas_fcts;		\
	el_vec  = CHAIN_NEXT(el_vec, EL_##VECNAME##_VEC);		\
	dof_vec = CHAIN_NEXT(dof_vec, DOF_##VECNAME##_VEC);		\
      } CHAIN_WHILE(fe_space, const FE_SPACE);				\
    } else {								\
      EL_##VECNAME##_VEC *chain_vec;					\
									\
      el_vec = (EL_##VECNAME##_VEC *)					\
	fe_space->bas_fcts->get_##vecname##_vec(NULL, el, dof_vec);	\
      el_vec->n_components = fe_space->bas_fcts->n_bas_fcts;		\
      DBL_LIST_INIT(&el_vec->chain);					\
      CHAIN_FOREACH(fe_chain, fe_space, const FE_SPACE) {		\
	dof_vec = CHAIN_NEXT(dof_vec, DOF_##VECNAME##_VEC);		\
	chain_vec = (EL_##VECNAME##_VEC *)				\
	  fe_chain->bas_fcts->get_##vecname##_vec(NULL, el, dof_vec);	\
	chain_vec->n_components = fe_chain->bas_fcts->n_bas_fcts;	\
	CHAIN_ADD_TAIL(el_vec, chain_vec);				\
      }									\
    }									\
    return el_vec;							\
  }									\
  struct _AI_semicolon_dummy

DEFUN_FILL_EL_VEC(INT, int);
DEFUN_FILL_EL_VEC(REAL, real);
DEFUN_FILL_EL_VEC(REAL_D, real_d);
DEFUN_FILL_EL_VEC(REAL_DD, real_dd);
DEFUN_FILL_EL_VEC(UCHAR, uchar);
DEFUN_FILL_EL_VEC(SCHAR, schar);

static inline
const EL_REAL_VEC_D *
fill_el_real_vec_d(EL_REAL_VEC_D *el_vec, EL *el, const DOF_REAL_VEC_D *dof_vec)
{
  const FE_SPACE *fe_space = dof_vec->fe_space;
  const FE_SPACE *fe_chain;

  if (el_vec) {
    CHAIN_DO(fe_space, const FE_SPACE) {
      fe_space->bas_fcts->get_real_vec_d(el_vec->vec, el, dof_vec);
      el_vec->n_components = fe_space->bas_fcts->n_bas_fcts;
      el_vec  = CHAIN_NEXT(el_vec, EL_REAL_VEC_D);
      dof_vec = CHAIN_NEXT(dof_vec, DOF_REAL_VEC_D);
    } CHAIN_WHILE(fe_space, const FE_SPACE);
  } else {
    EL_REAL_VEC_D *el_vec_chain;

    el_vec = (EL_REAL_VEC_D *)
      fe_space->bas_fcts->get_real_vec_d(NULL, el, dof_vec);
    el_vec->n_components = fe_space->bas_fcts->n_bas_fcts;
    DBL_LIST_INIT(&el_vec->chain);
    CHAIN_FOREACH(fe_chain, fe_space, const FE_SPACE) {
      dof_vec = CHAIN_NEXT(dof_vec, DOF_REAL_VEC_D);
      el_vec_chain =
	(EL_REAL_VEC_D *)fe_chain->bas_fcts->get_real_vec_d(NULL, el, dof_vec);
      el_vec_chain->n_components = fe_chain->bas_fcts->n_bas_fcts;
      CHAIN_ADD_TAIL(el_vec, el_vec_chain);
    }
  }

  return el_vec;
}

/* >>> */

/* <<< f = c * f + (a A + b B) u, result is an element-vector */

/* We have two kinds of functions: those starting with two underscores
 * are for internal use, they assume fixed data-types and ignore any
 * chain attached to the respective objects.
 *
 * The naming scheme rather treats the beasts as local operators; the
 * different kinds are distinguished by RangeDim/DomainDim
 * combinations.
 *
 */

/* <<< SCAL x SCAL (RANGE x DOMAIN) */

static inline void __el_bi_mat_vec(REAL a, const EL_MATRIX *A,
				   REAL b, const EL_MATRIX *B,
				   const EL_REAL_VEC *u_h,
				   REAL c, EL_REAL_VEC *f_h)
{
  int i, j;
  REAL sum;

  if (A && B) {
    for (i = 0; i < A->n_row; i++) {
      sum = 0.0;
      for (j = 0; j < A->n_col; j++) {
	sum += (a * A->data.real[i][j] + b * B->data.real[i][j]) * u_h->vec[j];
      }
      f_h->vec[i] = c * f_h->vec[i] + sum;
    }
  } else {
    if (B) {
      A = B;
      a = b;
      B = NULL;
      b = 0.0;
    }
    for (i = 0; i < A->n_row; i++) {
      sum = 0.0;
      for (j = 0; j < A->n_col; j++) {
	sum += a * A->data.real[i][j] * u_h->vec[j];
      }
      f_h->vec[i] = c * f_h->vec[i] + sum;
    }
  }
}

__FLATTEN_ATTRIBUTE__
static inline
EL_REAL_VEC *el_bi_mat_vec(REAL a,
			   const EL_MATRIX *A,
			   REAL b,
			   const EL_MATRIX *B,
			   const EL_REAL_VEC *u_h,
			   REAL c,
			   EL_REAL_VEC *f_h)
{
  const EL_MATRIX *A_chain;

  if (A == NULL) {
    A = B;
    a = b;
    B = NULL;
    b = 0.0;
  }
  COL_CHAIN_DO(A, const EL_MATRIX) {
    __el_bi_mat_vec(a, A, b, B, u_h, c, f_h);
    ROW_CHAIN_FOREACH(A_chain, A, const EL_MATRIX) {
      B = B ? ROW_CHAIN_NEXT(B, const EL_MATRIX) : NULL;
      u_h = CHAIN_NEXT(u_h, const EL_REAL_VEC);
      __el_bi_mat_vec(a, A_chain, b, B, u_h, 1.0, f_h);
    }
    /* roll-over to start of this row */
    B = B ? ROW_CHAIN_NEXT(B, const EL_MATRIX) : NULL;
    u_h = CHAIN_NEXT(u_h, const EL_REAL_VEC);
    /* next row */
    f_h = CHAIN_NEXT(f_h, EL_REAL_VEC);
    B = B ? COL_CHAIN_NEXT(B, const EL_MATRIX) : NULL;
  } COL_CHAIN_WHILE(A, const EL_MATRIX);

  return f_h;
}

/* >>> */

/* <<< SCAL x DOW (RANGE x DOMAIN) */

/* <<< REAL x REAL_D, fixed stride */

static inline void __el_bi_mat_vec_rrd(REAL a, const EL_MATRIX *A,
				       REAL b, const EL_MATRIX *B,
				       const EL_REAL_D_VEC *u_h,
				       REAL c, EL_REAL_VEC *f_h)
{
  int i, j;
  REAL sum;

  /* A and B must be either REAL or REAL_D matrices */
  if (A && B) {
    if (A->type == MATENT_REAL) {
      if (B->type == MATENT_REAL) {
	for (j = 0; j < A->n_col; j++) {
	  sum = SUM_DOW(u_h->vec[j]);
	  for (i = 0; i < A->n_row; i++) {
	    f_h->vec[i] =
	      c * f_h->vec[i]
	      +
	      (a * A->data.real[i][j] + b * B->data.real[i][j]) * sum;
	  }
	}
	return;
      } else {
	for (j = 0; j < A->n_col; j++) {
	  REAL u_sum = SUM_DOW(u_h->vec[j]);
	  for (i = 0; i < A->n_row; i++) {
	    f_h->vec[i] =
	      c * f_h->vec[i]
	      +
	      a * A->data.real[i][j] * u_sum
	      +
	      b * SCP_DOW(B->data.real_d[i][j], u_h->vec[j]);
	  }
	}
	return;
      }
    } else {
      if (B->type == MATENT_REAL) {
	__el_bi_mat_vec_rrd(b, B, a, A, u_h, c, f_h);
      } else {
	for (j = 0; j < A->n_col; j++) {
	  for (i = 0; i < A->n_row; i++) {
	    f_h->vec[i] =
	      c * f_h->vec[i]
	      +
	      a * SCP_DOW(A->data.real_d[i][j], u_h->vec[j])
	      +
	      b * SCP_DOW(B->data.real_d[i][j], u_h->vec[j]);
	  }
	}
      }
      return;
    }
  } else if (A) {
    if (A->type == MATENT_REAL) {
      for (j = 0; j < A->n_col; j++) {
	sum = SUM_DOW(u_h->vec[j]);
	for (i = 0; i < A->n_row; i++) {
	  f_h->vec[i] = c * f_h->vec[i] + a * A->data.real[i][j] * sum;
	}
      }
    } else {
      for (i = 0; i < A->n_row; i++) {
	for (j = 0; j < A->n_col; j++) {
	  f_h->vec[i] =
	    c * f_h->vec[i] + a * SCP_DOW(A->data.real_d[i][j], u_h->vec[j]);
	}
      }
    }
    return;
  }
}

__FLATTEN_ATTRIBUTE__
static inline
EL_REAL_VEC *el_bi_mat_vec_rrd(REAL a,
			       const EL_MATRIX *A,
			       REAL b,
			       const EL_MATRIX *B,
			       const EL_REAL_D_VEC *u_h,
			       REAL c,
			       EL_REAL_VEC *f_h)
{
  const EL_MATRIX *A_chain;

  if (A == NULL) {
    A = B;
    a = b;
    B = NULL;
    b = 0.0;
  }
  COL_CHAIN_DO(A, const EL_MATRIX) {
    __el_bi_mat_vec_rrd(a, A, b, B, u_h, c, f_h);
    ROW_CHAIN_FOREACH(A_chain, A, const EL_MATRIX) {
      B = B ? ROW_CHAIN_NEXT(B, const EL_MATRIX) : NULL;
      u_h = CHAIN_NEXT(u_h, const EL_REAL_D_VEC);
      __el_bi_mat_vec_rrd(a, A_chain, b, B, u_h, 1.0, f_h);
    }
    /* roll over to start of this row */
    B = B ? ROW_CHAIN_NEXT(B, const EL_MATRIX) : NULL;
    u_h = CHAIN_NEXT(u_h, const EL_REAL_D_VEC);
    /* next row */
    f_h = CHAIN_NEXT(f_h, EL_REAL_VEC);
    B = B ? COL_CHAIN_NEXT(B, const EL_MATRIX) : NULL;
  } COL_CHAIN_WHILE(A, const EL_MATRIX);

  return f_h;
}

/* >>> */

/* <<< REAL x REAL_D, vaiable stride */

static inline
void __el_bi_mat_vec_scl_dow(REAL a,
			     const EL_MATRIX *A,
			     REAL b,
			     const EL_MATRIX *B,
			     const EL_REAL_VEC_D *u_h,
			     REAL c,
			     EL_REAL_VEC *f_h)
{
  if (u_h->stride != 1) {
    __el_bi_mat_vec_rrd(a, A, b, B, (const EL_REAL_D_VEC *)u_h, c, f_h);
  } else {
    __el_bi_mat_vec(a, A, b, B, (const EL_REAL_VEC *)u_h, c, f_h);
  }
}

__FLATTEN_ATTRIBUTE__
static inline
EL_REAL_VEC *el_bi_mat_vec_scl_dow(REAL a,
				   const EL_MATRIX *A,
				   REAL b,
				   const EL_MATRIX *B,
				   const EL_REAL_VEC_D *u_h,
				   REAL c,
				   EL_REAL_VEC *f_h)
{
  const EL_MATRIX *A_chain;
  if (A == NULL) {
    A = B;
    a = b;
    B = NULL;
    b = 0.0;
  }
  COL_CHAIN_DO(A, const EL_MATRIX) {
    __el_bi_mat_vec_scl_dow(a, A, b, B, u_h, c, f_h);
    ROW_CHAIN_FOREACH(A_chain, A, const EL_MATRIX) {
      B = B ? ROW_CHAIN_NEXT(B, const EL_MATRIX) : NULL;
      u_h = CHAIN_NEXT(u_h, const EL_REAL_VEC_D);
      __el_bi_mat_vec_scl_dow(a, A_chain, b, B, u_h, 1.0, f_h);
    }
    /* roll-over to start of row */
    B = B ? ROW_CHAIN_NEXT(B, const EL_MATRIX) : NULL;
    u_h = CHAIN_NEXT(u_h, const EL_REAL_VEC_D);
    /* next row */
    B = B ? COL_CHAIN_NEXT(B, const EL_MATRIX) : NULL;
    f_h = CHAIN_NEXT(f_h, EL_REAL_VEC);
  } COL_CHAIN_WHILE(A, const EL_MATRIX);

  return f_h;
}

/* >>> */

/* >>> */

/* <<< DOW x SCAL (RANGE x DOMAIN) */

/* <<< READ_D x REAL, fixed stride */

static inline void
__el_bi_mat_vec_rdr(REAL a, const EL_MATRIX *A,
		    REAL b, const EL_MATRIX *B,
		    const EL_REAL_VEC *u_h,
		    REAL c, EL_REAL_D_VEC *f_h)
{
  int i, j;
  REAL val;

  if (A && B) {
    if (A->type == MATENT_REAL) {
      if (B->type == MATENT_REAL) {
	for (i = 0; i < A->n_row; i++) {
	  val = 0.0;
	  for (j = 0; j < A->n_col; j++) {
	    val +=
	      (a * A->data.real[i][j] + b * B->data.real[i][j]) * u_h->vec[j];
	  }
	  for (j = 0; j < DIM_OF_WORLD; j++) {
	    f_h->vec[i][j] = c * f_h->vec[i][j] + val;
	  }
	}
	return;
      } else {
	for (i = 0; i < A->n_row; i++) {
	  val = 0.0;
	  for (j = 0; j < A->n_col; j++) {
	    val += a * A->data.real[i][j] * u_h->vec[j];
	  }
	  for (j = 0; j < DIM_OF_WORLD; j++) {
	    f_h->vec[i][j] = c * f_h->vec[i][j] + val;
	  }
	  for (j = 0; j < A->n_col; j++) {
	    AXPY_DOW(b * u_h->vec[j], B->data.real_d[i][j], f_h->vec[i]);
	  }
	}
	return;
      }
    } else {
      if (B->type == MATENT_REAL) {
	__el_bi_mat_vec_rdr(b, B, a, A, u_h, c, f_h);
      } else {
	for (i = 0; i < A->n_row; i++) {
	  for (j = 0; j < DIM_OF_WORLD; j++) {
	    f_h->vec[i][j] = c * f_h->vec[i][j];
	  }
	  for (j = 0; j < A->n_col; j++) {
	    AXPY_DOW(a * u_h->vec[j], A->data.real_d[i][j], f_h->vec[i]);
	    AXPY_DOW(b * u_h->vec[j], B->data.real_d[i][j], f_h->vec[i]);
	  }
	}
      }
      return;
    }
  } else if (A) {
    if (A->type == MATENT_REAL) {
      for (i = 0; i < A->n_row; i++) {
	val = 0.0;
	for (j = 0; j < A->n_col; j++) {
	  val += a * A->data.real[i][j] * u_h->vec[j];
	}
	for (j = 0; j < DIM_OF_WORLD; j++) {
	  f_h->vec[i][j] = c * f_h->vec[i][j] + val;
	}
      }
    } else {
      for (i = 0; i < A->n_row; i++) {
	for (j = 0; j < A->n_col; j++) {
	  AXPBY_DOW(a * u_h->vec[j], A->data.real_d[i][j], c, f_h->vec[i],
		    f_h->vec[i]);
	}
      }
    }
    return;
  }
}

__FLATTEN_ATTRIBUTE__
static inline
EL_REAL_D_VEC *el_bi_mat_vec_rdr(REAL a,
				 const EL_MATRIX *A,
				 REAL b,
				 const EL_MATRIX *B,
				 const EL_REAL_VEC *u_h,
				 REAL c,
				 EL_REAL_D_VEC *f_h)
{
  const EL_MATRIX *A_chain;

  if (A == NULL) {
    A = B;
    a = b;
    B = NULL;
    b = 0.0;
  }
  COL_CHAIN_DO(A, const EL_MATRIX) {
    __el_bi_mat_vec_rdr(a, A, b, B, u_h, c, f_h);
    ROW_CHAIN_FOREACH(A_chain, A, const EL_MATRIX) {
      B = B ? ROW_CHAIN_NEXT(B, const EL_MATRIX) : NULL;
      u_h = CHAIN_NEXT(u_h, const EL_REAL_VEC);
      __el_bi_mat_vec_rdr(a, A, b, B, u_h, 1.0, f_h);
    }
    /* roll-over to start of row */
    B = B ? ROW_CHAIN_NEXT(B, const EL_MATRIX) : NULL;
    u_h = CHAIN_NEXT(u_h, const EL_REAL_VEC);
    /* next row */
    B = B ? COL_CHAIN_NEXT(B, const EL_MATRIX) : NULL;
    f_h = CHAIN_NEXT(f_h, EL_REAL_D_VEC);
  } COL_CHAIN_WHILE(A, const EL_MATRIX);

  return f_h;
}

/* >>> */

/* <<< REAL_D x REAL, variable stride */

static inline
void __el_bi_mat_vec_dow_scl(REAL a,
			     const EL_MATRIX *A,
			     REAL b,
			     const EL_MATRIX *B,
			     const EL_REAL_VEC *u_h,
			     REAL c,
			     EL_REAL_VEC_D *f_h)
{
  if (f_h->stride != 1) {
    __el_bi_mat_vec_rdr(a, A, b, B, u_h, c, (EL_REAL_D_VEC *)f_h);
  } else {
    __el_bi_mat_vec(a, A, b, B, u_h, c, (EL_REAL_VEC *)f_h);
  }
}

__FLATTEN_ATTRIBUTE__
static inline
EL_REAL_VEC_D *el_bi_mat_vec_dow_scl(REAL a,
				     const EL_MATRIX *A,
				     REAL b,
				     const EL_MATRIX *B,
				     const EL_REAL_VEC *u_h,
				     REAL c,
				     EL_REAL_VEC_D *f_h)
{
  const EL_MATRIX *A_chain;

  if (A == NULL) {
    A = B;
    a = b;
    B = NULL;
    b = 0.0;
  }
  COL_CHAIN_DO(A, const EL_MATRIX) {
    __el_bi_mat_vec_dow_scl(a, A, b, B, u_h, c, f_h);
    ROW_CHAIN_FOREACH(A_chain, A, const EL_MATRIX) {
      B = B ? ROW_CHAIN_NEXT(B, const EL_MATRIX) : NULL;
      u_h = CHAIN_NEXT(u_h, const EL_REAL_VEC);
      __el_bi_mat_vec_dow_scl(a, A, b, B, u_h, 1.0, f_h);
    }
    /* roll-over to start of row */
    B = B ? ROW_CHAIN_NEXT(B, const EL_MATRIX) : NULL;
    u_h = CHAIN_NEXT(u_h, const EL_REAL_VEC);
    /* next row */
    B = B ? COL_CHAIN_NEXT(B, const EL_MATRIX) : NULL;
    f_h = CHAIN_NEXT(f_h, EL_REAL_VEC_D);
  } COL_CHAIN_WHILE(A, const EL_MATRIX);

  return f_h;
}

/* >>> */

/* >>> */

/* <<< DOW x DOW (RANGE x DOMAIN) */

/* <<< fixed stride */

/* Multiply an element matrix by an element vector and store the
 * result inside an element vector.
 */
static inline
void __el_bi_mat_vec_d(REAL a, const EL_MATRIX *A,
		       REAL b, const EL_MATRIX *B,
		       const EL_REAL_D_VEC *u_h,
		       REAL c, EL_REAL_D_VEC *f_h)
{
  int i, j;
  REAL *sum;

  if (A && B) {
#undef MAT_BI_BODY
#define MAT_BI_BODY(PFX1, CC1, C1, S1, TYPE1,		\
		    PFX2, CC2, C2, S2, TYPE2)		\
    for (i = 0; i < A->n_row; i++) {			\
      sum = f_h->vec[i];				\
      for (j = 0; j < A->n_col; j++) {			\
	PFX1##PFX2##BIMV_DOW(a, CC1 A->data.S1[i][j],	\
			     b, CC2 B->data.S2[i][j],	\
			     u_h->vec[j],		\
			     c, sum);			\
      }							\
    }
    MAT_EMIT_BI_BODY_SWITCH(A->type, B->type);
  } else {
    if (A == NULL) {
      A = B;
      a = b;
      B = NULL;
      b = 0.0;
    }
#undef MAT_BODY
#define MAT_BODY(PFX, CC, C, S, TYPE)					\
    for (i = 0; i < A->n_row; i++) {					\
      sum = f_h->vec[i];						\
      for (j = 0; j < A->n_col; j++) {					\
	PFX##GEMV_DOW(a, CC A->data.S[i][j], u_h->vec[j], c, sum);	\
      }									\
    }
    MAT_EMIT_BODY_SWITCH(A->type);
  }
}

__FLATTEN_ATTRIBUTE__
static inline
EL_REAL_D_VEC *el_bi_mat_vec_d(REAL a,
			       const EL_MATRIX *A,
			       REAL b,
			       const EL_MATRIX *B,
			       const EL_REAL_D_VEC *u_h,
			       REAL c,
			       EL_REAL_D_VEC *f_h)
{
  const EL_MATRIX *A_chain;

  if (A == NULL) {
    A = B;
    a = b;
    B = NULL;
    b = 0.0;
  }
  COL_CHAIN_DO(A, const EL_MATRIX) {
    __el_bi_mat_vec_d(a, A, b, B, u_h, c, f_h);
    ROW_CHAIN_FOREACH(A_chain, A, const EL_MATRIX) {
      /* next column */
      B = B ? ROW_CHAIN_NEXT(B, const EL_MATRIX) : NULL;
      u_h = CHAIN_NEXT(u_h, const EL_REAL_D_VEC);
      __el_bi_mat_vec_d(a, A, b, B, u_h, 1.0, f_h);
    }
    /* roll-over to start of row */
    B = B ? ROW_CHAIN_NEXT(B, const EL_MATRIX) : NULL;
    u_h = CHAIN_NEXT(u_h, const EL_REAL_D_VEC);
    /* next row */
    B = B ? COL_CHAIN_NEXT(B, const EL_MATRIX) : NULL;
    f_h = CHAIN_NEXT(f_h, EL_REAL_D_VEC);
  } COL_CHAIN_WHILE(A, const EL_MATRIX);

  return f_h;
}

/* >>> */

/* <<< REAL_D x REAL_D, variable stride */

static inline
void __el_bi_mat_vec_dow(REAL a,
			 const EL_MATRIX *A,
			 REAL b,
			 const EL_MATRIX *B,
			 const EL_REAL_VEC_D *u_h,
			 REAL c,
			 EL_REAL_VEC_D *f_h)
{
  if (f_h->stride == 1) {
    __el_bi_mat_vec_scl_dow(a, A, b, B, u_h, c, (EL_REAL_VEC *)f_h);
  } else if (u_h->stride == 1) {
    __el_bi_mat_vec_dow_scl(a, A, b, B, (const EL_REAL_VEC *)u_h, c, f_h);
  } else {
    __el_bi_mat_vec_d(a, A, b, B, (const EL_REAL_D_VEC *)u_h,
		      c, (EL_REAL_D_VEC *)f_h);
  }
}

__FLATTEN_ATTRIBUTE__
static inline
EL_REAL_VEC_D *el_bi_mat_vec_dow(REAL a,
				 const EL_MATRIX *A,
				 REAL b,
				 const EL_MATRIX *B,
				 const EL_REAL_VEC_D *u_h,
				 REAL c,
				 EL_REAL_VEC_D *f_h)
{
  const EL_MATRIX *A_chain;

  if (A == NULL) {
    A = B;
    a = b;
    B = NULL;
    b = 0.0;
  }
  COL_CHAIN_DO(A, const EL_MATRIX) {
    __el_bi_mat_vec_dow(a, A, b, B, u_h, c, f_h);
    ROW_CHAIN_FOREACH(A_chain, A, const EL_MATRIX) {
      /* next column */
      B = B ? ROW_CHAIN_NEXT(B, const EL_MATRIX) : NULL;
      u_h = CHAIN_NEXT(u_h, const EL_REAL_VEC_D);
      __el_bi_mat_vec_dow(a, A, b, B, u_h, 1.0, f_h);
    }
    /* roll-over to start of row */
    B = B ? ROW_CHAIN_NEXT(B, const EL_MATRIX) : NULL;
    u_h = CHAIN_NEXT(u_h, const EL_REAL_VEC_D);
    /* next row */
    f_h = CHAIN_NEXT(f_h, EL_REAL_VEC_D);
    B = B ? COL_CHAIN_NEXT(B, const EL_MATRIX) : NULL;
  } COL_CHAIN_WHILE(A, const EL_MATRIX);

  return f_h;
}

/* >>> */

/* >>> */

/* <<< f = c * f + a A u, f += a A u versions */

/* <<< SCL x SCL */

static inline EL_REAL_VEC *
el_gen_mat_vec(REAL a, const EL_MATRIX *A, const EL_REAL_VEC *u_h,
	       REAL b, EL_REAL_VEC *f_h)
{
  return el_bi_mat_vec(a, A, 0.0, NULL, u_h, b, f_h);
}

static inline EL_REAL_VEC *
el_mat_vec(REAL a, const EL_MATRIX *A, const EL_REAL_VEC *u_h, EL_REAL_VEC *f_h)
{
  return el_gen_mat_vec(a, A, u_h, 1.0, f_h);
}

/* >>> */

/* <<< SCL x DOW */

static inline EL_REAL_VEC *
el_gen_mat_vec_rrd(REAL a, const EL_MATRIX *A, const EL_REAL_D_VEC *u_h,
		   REAL b, EL_REAL_VEC *f_h)
{
  return el_bi_mat_vec_rrd(a, A, 0.0, NULL, u_h, b, f_h);
}

static inline EL_REAL_VEC *
el_mat_vec_rrd(REAL a, const EL_MATRIX *A, const EL_REAL_D_VEC *u_h,
	       EL_REAL_VEC *f_h)
{
  return el_gen_mat_vec_rrd(a, A, u_h, 1.0, f_h);
}

static inline EL_REAL_VEC *
el_gen_mat_vec_scl_dow(REAL a, const EL_MATRIX *A, const EL_REAL_VEC_D *u_h,
		       REAL b, EL_REAL_VEC *f_h)
{
  return el_bi_mat_vec_scl_dow(a, A, 0.0, NULL, u_h, b, f_h);
}

static inline EL_REAL_VEC *
el_mat_vec_scl_dow(REAL a, const EL_MATRIX *A, const EL_REAL_VEC_D *u_h,
		   EL_REAL_VEC *f_h)
{
  return el_gen_mat_vec_scl_dow(a, A, u_h, 1.0, f_h);
}

/* >>> */

/* <<< DOW x SCL */

static inline EL_REAL_D_VEC *
el_gen_mat_vec_rdr(REAL a, const EL_MATRIX *A, const EL_REAL_VEC *u_h,
		   REAL b, EL_REAL_D_VEC *f_h)
{
  return el_bi_mat_vec_rdr(a, A, 0.0, NULL, u_h, b, f_h);
}

static inline EL_REAL_D_VEC *
el_mat_vec_rdr(REAL a, const EL_MATRIX *A, const EL_REAL_VEC *u_h,
	       EL_REAL_D_VEC *f_h)
{
  return el_gen_mat_vec_rdr(a, A, u_h, 1.0, f_h);
}

static inline EL_REAL_VEC_D *
el_gen_mat_vec_dow_scl(REAL a, const EL_MATRIX *A, const EL_REAL_VEC *u_h,
		       REAL b, EL_REAL_VEC_D *f_h)
{
  return el_bi_mat_vec_dow_scl(a, A, 0.0, NULL, u_h, b, f_h);
}

static inline EL_REAL_VEC_D *
el_mat_vec_dow_scl(REAL a, const EL_MATRIX *A, const EL_REAL_VEC *u_h,
		   EL_REAL_VEC_D *f_h)
{
  return el_gen_mat_vec_dow_scl(a, A, u_h, 1.0, f_h);
}

/* >>> */

/* <<< DOW x DOW */

static inline EL_REAL_D_VEC *
el_gen_mat_vec_d(REAL a, const EL_MATRIX *A, const EL_REAL_D_VEC *u_h,
		 REAL b, EL_REAL_D_VEC *f_h)
{
  return el_bi_mat_vec_d(a, A, 0.0, NULL, u_h, b, f_h);
}

static inline EL_REAL_D_VEC *
el_mat_vec_d(REAL a, const EL_MATRIX *A, const EL_REAL_D_VEC *u_h,
	     EL_REAL_D_VEC *f_h)
{
  return el_gen_mat_vec_d(a, A, u_h, 1.0, f_h);
}

static inline EL_REAL_VEC_D *
el_gen_mat_vec_dow(REAL a, const EL_MATRIX *A, const EL_REAL_VEC_D *u_h,
		   REAL b, EL_REAL_VEC_D *f_h)
{
  return el_bi_mat_vec_dow(a, A, 0.0, NULL, u_h, b, f_h);
}

static inline EL_REAL_VEC_D *
el_mat_vec_dow(REAL a, const EL_MATRIX *A, const EL_REAL_VEC_D *u_h,
	       EL_REAL_VEC_D *f_h)
{
  return el_gen_mat_vec_dow(a, A, u_h, 1.0, f_h);
}

/* >>> */

/* >>> */

/* >>> */

/* <<< f = c * f + (a A + b B) u, result is a global DOF-vector */

/* <<< SCAL-SCAL */

static inline
void __bi_mat_el_vec(REAL a, const EL_MATRIX *A,
		     REAL b, const EL_MATRIX *B,
		     const EL_REAL_VEC *u_h,
		     REAL c, DOF_REAL_VEC *f_h,
		     const EL_DOF_VEC *dof,
		     const EL_SCHAR_VEC *bnd)
{
  int i, j;
  REAL sum;

  if (A && B) {
    for (i = 0; i < A->n_row; i++) {
      if (bnd == NULL || bnd->vec[i] < DIRICHLET) {
	sum = 0.0;
	for (j = 0; j < A->n_col; j++) {
	  sum +=
	    (a * A->data.real[i][j] + b * B->data.real[i][j]) * u_h->vec[j];
	}
	f_h->vec[dof->vec[i]] = c * f_h->vec[dof->vec[i]] + sum;
      }
    }
  } else {
    if (B) {
      A = B;
      a = b;
      B = NULL;
      b = 0.0;
    }
    for (i = 0; i < A->n_row; i++) {
      if (bnd == NULL || bnd->vec[i] < DIRICHLET) {
	sum = 0.0;
	for (j = 0; j < A->n_col; j++) {
	  sum += a * A->data.real[i][j] * u_h->vec[j];
	}
	f_h->vec[dof->vec[i]] = c * f_h->vec[dof->vec[i]] + sum;
      }
    }
  }
}

__FLATTEN_ATTRIBUTE__
static inline
void bi_mat_el_vec(REAL a,
		   const EL_MATRIX *A,
		   REAL b,
		   const EL_MATRIX *B,
		   const EL_REAL_VEC *u_h,
		   REAL c,
		   DOF_REAL_VEC *f_h,
		   const EL_DOF_VEC *dof,
		   const EL_SCHAR_VEC *bnd)
{
  if (A == NULL) {
    A = B;
    a = b;
    B = NULL;
    b = 0.0;
  }
  COL_CHAIN_DO(A, const EL_MATRIX) {
    ROW_CHAIN_DO(A, const EL_MATRIX) {

      __bi_mat_el_vec(a, A, b, B, u_h, c, f_h, dof, bnd);

      B = B ? ROW_CHAIN_NEXT(B, const EL_MATRIX) : NULL;
      u_h = CHAIN_NEXT(u_h, const EL_REAL_VEC);
    } ROW_CHAIN_WHILE(A, const EL_MATRIX);
    f_h   = CHAIN_NEXT(f_h, DOF_REAL_VEC);
    dof   = CHAIN_NEXT(dof, EL_DOF_VEC);
    bnd   = bnd ? CHAIN_NEXT(bnd, EL_SCHAR_VEC) : NULL;
    B     = B ? COL_CHAIN_NEXT(B, const EL_MATRIX) : NULL;
  } COL_CHAIN_WHILE(A, const EL_MATRIX);
}

/* >>> */

/* <<< SCAL-DOW version */

/* <<< fixed stride */

static inline
void __bi_mat_el_vec_rrd(REAL a, const EL_MATRIX *A,
			 REAL b, const EL_MATRIX *B,
			 const EL_REAL_D_VEC *u_h,
			 REAL c, DOF_REAL_VEC *f_h,
			 const EL_DOF_VEC *dof,
			 const EL_SCHAR_VEC *bnd)
{
  int i, j;
  REAL sum;

  /* A and B must be either REAL or REAL_D matrices */
  if (A && B) {
    if (A->type == MATENT_REAL) {
      if (B->type == MATENT_REAL) {
	for (j = 0; j < A->n_col; j++) {
	  sum = SUM_DOW(u_h->vec[j]);
	  for (i = 0; i < A->n_row; i++) {
	    if (bnd == NULL || bnd->vec[i] < DIRICHLET) {
	      f_h->vec[dof->vec[i]] =
		c * f_h->vec[dof->vec[i]]
		+
		(a * A->data.real[i][j] + b * B->data.real[i][j]) * sum;
	    }
	  }
	}
	return;
      } else {
	for (j = 0; j < A->n_col; j++) {
	  REAL u_sum = SUM_DOW(u_h->vec[j]);
	  for (i = 0; i < A->n_row; i++) {
	    if (bnd == NULL || bnd->vec[i] < DIRICHLET) {
	      f_h->vec[dof->vec[i]] =
		c * f_h->vec[dof->vec[i]]
		+
		a * A->data.real[i][j] * u_sum
		+
		b * SCP_DOW(B->data.real_d[i][j], u_h->vec[j]);
	    }
	  }
	}
	return;
      }
    } else {
      if (B->type == MATENT_REAL) {
	__bi_mat_el_vec_rrd(b, B, a, A, u_h, c, f_h, dof, bnd);
      } else {
	for (j = 0; j < A->n_col; j++) {
	  for (i = 0; i < A->n_row; i++) {
	    if (bnd == NULL || bnd->vec[i] < DIRICHLET) {
	      f_h->vec[dof->vec[i]] =
		c * f_h->vec[dof->vec[i]]
		+
		a * SCP_DOW(A->data.real_d[i][j], u_h->vec[j])
		+
		b * SCP_DOW(B->data.real_d[i][j], u_h->vec[j]);
	    }
	  }
	}
      }
      return;
    }
  } else if (A) {
    if (A->type == MATENT_REAL) {
      for (j = 0; j < A->n_col; j++) {
	sum = SUM_DOW(u_h->vec[j]);
	for (i = 0; i < A->n_row; i++) {
	  if (bnd == NULL || bnd->vec[i] < DIRICHLET) {
	    f_h->vec[dof->vec[i]] =
	      c * f_h->vec[dof->vec[i]] + + a * A->data.real[i][j] * sum;
	  }
	}
      }
    } else {
      for (i = 0; i < A->n_row; i++) {
	for (j = 0; j < A->n_col; j++) {
	  if (bnd == NULL || bnd->vec[i] < DIRICHLET) {
	    f_h->vec[dof->vec[i]] =
	      c * f_h->vec[dof->vec[i]]
	      +
	      a * SCP_DOW(A->data.real_d[i][j], u_h->vec[j]);
	  }
	}
      }
    }
    return;
  }
}

__FLATTEN_ATTRIBUTE__
static inline
void bi_mat_el_vec_rrd(REAL a,
		       const EL_MATRIX *A,
		       REAL b,
		       const EL_MATRIX *B,
		       const EL_REAL_D_VEC *u_h,
		       REAL c,
		       DOF_REAL_VEC *f_h,
		       const EL_DOF_VEC *dof,
		       const EL_SCHAR_VEC *bnd)
{
  if (A == NULL) {
    A = B;
    a = b;
    B = NULL;
    b = 0.0;
  }
  COL_CHAIN_DO(A, const EL_MATRIX) {
    ROW_CHAIN_DO(A, const EL_MATRIX) {

      __bi_mat_el_vec_rrd(a, A, b, B, u_h, c, f_h, dof, bnd);

      B = B ? ROW_CHAIN_NEXT(B, const EL_MATRIX) : NULL;
      u_h = CHAIN_NEXT(u_h, const EL_REAL_D_VEC);
    } ROW_CHAIN_WHILE(A, const EL_MATRIX);
    f_h   = CHAIN_NEXT(f_h, DOF_REAL_VEC);
    dof   = CHAIN_NEXT(dof, EL_DOF_VEC);
    bnd   = bnd ? CHAIN_NEXT(bnd, EL_SCHAR_VEC) : NULL;
    B     = B ? COL_CHAIN_NEXT(B, const EL_MATRIX) : NULL;
  } COL_CHAIN_WHILE(A, const EL_MATRIX);
}

/* >>> */

/* <<< variable stride version */

static inline
void __bi_mat_el_vec_scl_dow(REAL a, const EL_MATRIX *A,
			     REAL b, const EL_MATRIX *B,
			     const EL_REAL_VEC_D *u_h,
			     REAL c, DOF_REAL_VEC *f_h,
			     const EL_DOF_VEC *dof,
			     const EL_SCHAR_VEC *bnd)
{
  if (u_h->stride != 1) {
    __bi_mat_el_vec_rrd(a, A, b, B, (const EL_REAL_D_VEC *)u_h,
			c, f_h, dof, bnd);
  } else {
    __bi_mat_el_vec(a, A, b, B, (const EL_REAL_VEC *)u_h,
		    c, f_h, dof, bnd);
  }
}

__FLATTEN_ATTRIBUTE__
static inline
void bi_mat_el_vec_scl_dow(REAL a,
			   const EL_MATRIX *A,
			   REAL b,
			   const EL_MATRIX *B,
			   const EL_REAL_VEC_D *u_h,
			   REAL c,
			   DOF_REAL_VEC *f_h,
			   const EL_DOF_VEC *dof,
			   const EL_SCHAR_VEC *bnd)
{
  if (A == NULL) {
    A = B;
    a = b;
    B = NULL;
    b = 0.0;
  }
  COL_CHAIN_DO(A, const EL_MATRIX) {
    ROW_CHAIN_DO(A, const EL_MATRIX) {

      __bi_mat_el_vec_scl_dow(a, A, b, B, u_h, c, f_h, dof, bnd);

      B = B ? ROW_CHAIN_NEXT(B, const EL_MATRIX) : NULL;
      u_h = CHAIN_NEXT(u_h, const EL_REAL_VEC_D);
    } ROW_CHAIN_WHILE(A, const EL_MATRIX);
    f_h   = CHAIN_NEXT(f_h, DOF_REAL_VEC);
    dof   = CHAIN_NEXT(dof, EL_DOF_VEC);
    bnd   = bnd ? CHAIN_NEXT(bnd, EL_SCHAR_VEC) : NULL;
    B     = B ? COL_CHAIN_NEXT(B, const EL_MATRIX) : NULL;
  } COL_CHAIN_WHILE(A, const EL_MATRIX);
}

/* >>> */

/* >>> */

/* <<< DOW-SCAL version */

/* <<< fixed stride */

static inline
void __bi_mat_el_vec_rdr(REAL a, const EL_MATRIX *A,
			 REAL b, const EL_MATRIX *B,
			 const EL_REAL_VEC *u_h,
			 REAL c, DOF_REAL_D_VEC *f_h,
			 const EL_DOF_VEC *dof,
			 const EL_SCHAR_VEC *bnd)
{
  int i, j;
  REAL val;

  if (A && B) {
    if (A->type == MATENT_REAL) {
      if (B->type == MATENT_REAL) {
	for (i = 0; i < A->n_row; i++) {
	  if (bnd == NULL || bnd->vec[i] < DIRICHLET) {
	    val = 0.0;
	    for (j = 0; j < A->n_col; j++) {
	      val +=
		(a * A->data.real[i][j] + b * B->data.real[i][j]) * u_h->vec[j];
	    }
	    for (j = 0; j < DIM_OF_WORLD; j++) {
	      f_h->vec[dof->vec[i]][j] = c * f_h->vec[dof->vec[i]][j] + val;
	    }
	  }
	}
	return;
      } else {
	for (i = 0; i < A->n_row; i++) {
	  if (bnd == NULL || bnd->vec[i] < DIRICHLET) {
	    val = 0.0;
	    for (j = 0; j < A->n_col; j++) {
	      val += a * A->data.real[i][j] * u_h->vec[j];
	    }
	    for (j = 0; j < DIM_OF_WORLD; j++) {
	      f_h->vec[dof->vec[i]][j] = c * f_h->vec[dof->vec[i]][j] + val;
	    }
	    for (j = 0; j < A->n_col; j++) {
	      AXPY_DOW(b * u_h->vec[j], B->data.real_d[i][j],
		       f_h->vec[dof->vec[i]]);
	    }
	  }
	}
	return;
      }
    } else {
      if (B->type == MATENT_REAL) {
	__bi_mat_el_vec_rdr(b, B, a, A, u_h, c, f_h, dof, bnd);
      } else {
	for (i = 0; i < A->n_row; i++) {
	  if (bnd == NULL || bnd->vec[i] < DIRICHLET) {
	    for (j = 0; j < DIM_OF_WORLD; j++) {
	      f_h->vec[dof->vec[i]][j] = c * f_h->vec[dof->vec[i]][j];
	    }
	    for (j = 0; j < A->n_col; j++) {
	      AXPY_DOW(a * u_h->vec[j], A->data.real_d[i][j],
		       f_h->vec[dof->vec[i]]);
	      AXPY_DOW(b * u_h->vec[j], B->data.real_d[i][j],
		       f_h->vec[dof->vec[i]]);
	    }
	  }
	}
      }
    }
  } else if (A) {
    if (A->type == MATENT_REAL) {
      for (i = 0; i < A->n_row; i++) {
	if (bnd == NULL || bnd->vec[i] < DIRICHLET) {
	  val = 0.0;
	  for (j = 0; j < A->n_col; j++) {
	    val += a * A->data.real[i][j] * u_h->vec[j];
	  }
	  for (j = 0; j < DIM_OF_WORLD; j++) {
	    f_h->vec[dof->vec[i]][j] = c * f_h->vec[dof->vec[i]][j] + val;
	  }
	}
      }
    } else {
      for (i = 0; i < A->n_row; i++) {
	if (bnd == NULL || bnd->vec[i] < DIRICHLET) {
	  for (j = 0; j < A->n_col; j++) {
	    AXPBY_DOW(a * u_h->vec[j], A->data.real_d[i][j],
		      c, f_h->vec[dof->vec[i]],
		      f_h->vec[dof->vec[i]]);
	  }
	}
      }
    }
    return;
  }
}

static inline void bi_mat_el_vec_rdr(REAL a,
				     const EL_MATRIX *A,
				     REAL b,
				     const EL_MATRIX *B,
				     const EL_REAL_VEC *u_h,
				     REAL c,
				     DOF_REAL_D_VEC *f_h,
				     const EL_DOF_VEC *dof,
				     const EL_SCHAR_VEC *bnd)
{
  if (A == NULL) {
    A = B;
    a = b;
    B = NULL;
    b = 0.0;
  }
  COL_CHAIN_DO(A, const EL_MATRIX) {
    ROW_CHAIN_DO(A, const EL_MATRIX) {

      __bi_mat_el_vec_rdr(a, A, b, B, u_h, c, f_h, dof, bnd);

      B = B ? ROW_CHAIN_NEXT(B, const EL_MATRIX) : NULL;
      u_h = CHAIN_NEXT(u_h, const EL_REAL_VEC);
    } ROW_CHAIN_WHILE(A, const EL_MATRIX);
    f_h   = CHAIN_NEXT(f_h, DOF_REAL_D_VEC);
    dof   = CHAIN_NEXT(dof, EL_DOF_VEC);
    bnd   = bnd ? CHAIN_NEXT(bnd, EL_SCHAR_VEC) : NULL;
    B     = B ? COL_CHAIN_NEXT(B, const EL_MATRIX) : NULL;
  } COL_CHAIN_WHILE(A, const EL_MATRIX);
}

/* >>> */

/* <<< variable stride version */

static inline
void __bi_mat_el_vec_dow_scl(REAL a, const EL_MATRIX *A,
			     REAL b, const EL_MATRIX *B,
			     const EL_REAL_VEC *u_h,
			     REAL c, DOF_REAL_VEC_D *f_h,
			     const EL_DOF_VEC *dof,
			     const EL_SCHAR_VEC *bnd)
{
  if (f_h->stride != 1) {
    __bi_mat_el_vec_rdr(a, A, b, B, u_h, c, (DOF_REAL_D_VEC *)f_h, dof, bnd);
  } else {
    __bi_mat_el_vec(a, A, b, B, u_h, c, (DOF_REAL_VEC *)f_h, dof, bnd);
  }
}

__FLATTEN_ATTRIBUTE__
static inline
void bi_mat_el_vec_dow_scl(REAL a,
			   const EL_MATRIX *A,
			   REAL b,
			   const EL_MATRIX *B,
			   const EL_REAL_VEC *u_h,
			   REAL c,
			   DOF_REAL_VEC_D *f_h,
			   const EL_DOF_VEC *dof,
			   const EL_SCHAR_VEC *bnd)
{
  if (A == NULL) {
    A = B;
    a = b;
    B = NULL;
    b = 0.0;
  }
  COL_CHAIN_DO(A, const EL_MATRIX) {
    ROW_CHAIN_DO(A, const EL_MATRIX) {

      __bi_mat_el_vec_dow_scl(a, A, b, B, u_h, c, f_h, dof, bnd);

      B = B ? ROW_CHAIN_NEXT(B, const EL_MATRIX) : NULL;
      u_h = CHAIN_NEXT(u_h, const EL_REAL_VEC);
    } ROW_CHAIN_WHILE(A, const EL_MATRIX);
    f_h   = CHAIN_NEXT(f_h, DOF_REAL_VEC_D);
    dof   = CHAIN_NEXT(dof, EL_DOF_VEC);
    bnd   = bnd ? CHAIN_NEXT(bnd, EL_SCHAR_VEC) : NULL;
    B     = B ? COL_CHAIN_NEXT(B, const EL_MATRIX) : NULL;
  } COL_CHAIN_WHILE(A, const EL_MATRIX);
}

/* >>> */

/* >>> */

/* <<< DOW-DOW version */

/* <<< fixed stride */

/* Multiply an element matrix by an element vector and store the
 * result inside an element vector.
 */
static inline
void __bi_mat_el_vec_d(REAL a, const EL_MATRIX *A,
		       REAL b, const EL_MATRIX *B,
		       const EL_REAL_D_VEC *u_h,
		       REAL c, DOF_REAL_D_VEC *f_h,
		       const EL_DOF_VEC *dof,
		       const EL_SCHAR_VEC *bnd)
{
  int i, j;
  REAL *sum;

  if (A && B) {
#undef MAT_BI_BODY
#define MAT_BI_BODY(PFX1, CC1, C1, S1, TYPE1,		\
		    PFX2, CC2, C2, S2, TYPE2)		\
    for (i = 0; i < A->n_row; i++) {			\
      if (bnd == NULL || bnd->vec[i] < DIRICHLET) {	\
	sum = f_h->vec[dof->vec[i]];			\
	for (j = 0; j < A->n_col; j++) {		\
	  PFX1##PFX2##BIMV_DOW(a, CC1 A->data.S1[i][j],	\
			       b, CC2 B->data.S2[i][j],	\
			       u_h->vec[j],		\
			       c, sum);			\
	}						\
      }							\
    }
    MAT_EMIT_BI_BODY_SWITCH(A->type, B->type);
  } else {
    if (A == NULL) {
      A = B;
      a = b;
      B = NULL;
      b = 0.0;
    }
#undef MAT_BODY
#define MAT_BODY(PFX, CC, C, S, TYPE)					\
    for (i = 0; i < A->n_row; i++) {					\
      if (bnd == NULL || bnd->vec[i] < DIRICHLET) {			\
	sum = f_h->vec[dof->vec[i]];					\
	for (j = 0; j < A->n_col; j++) {				\
	  PFX##GEMV_DOW(a, CC A->data.S[i][j], u_h->vec[j], c, sum);	\
	}								\
      }									\
    }
    MAT_EMIT_BODY_SWITCH(A->type);
  }
}

__FLATTEN_ATTRIBUTE__
static inline
void bi_mat_el_vec_d(REAL a,
		     const EL_MATRIX *A,
		     REAL b,
		     const EL_MATRIX *B,
		     const EL_REAL_D_VEC *u_h,
		     REAL c,
		     DOF_REAL_D_VEC *f_h,
		     const EL_DOF_VEC *dof,
		     const EL_SCHAR_VEC *bnd)
{
  if (A == NULL) {
    A = B;
    a = b;
    B = NULL;
    b = 0.0;
  }
  COL_CHAIN_DO(A, const EL_MATRIX) {
    ROW_CHAIN_DO(A, const EL_MATRIX) {

      __bi_mat_el_vec_d(a, A, b, B, u_h, c, f_h, dof, bnd);

      B = B ? ROW_CHAIN_NEXT(B, const EL_MATRIX) : NULL;
      u_h = CHAIN_NEXT(u_h, const EL_REAL_D_VEC);
    } ROW_CHAIN_WHILE(A, const EL_MATRIX);
    f_h = CHAIN_NEXT(f_h, DOF_REAL_D_VEC);
    dof = CHAIN_NEXT(dof, EL_DOF_VEC);
    bnd = bnd ? CHAIN_NEXT(bnd, EL_SCHAR_VEC) : NULL;
    B   = B ? COL_CHAIN_NEXT(B, const EL_MATRIX) : NULL;
  } COL_CHAIN_WHILE(A, const EL_MATRIX);
}

/* >>> */

/* <<< variable stride version */

static inline
void __bi_mat_el_vec_dow(REAL a, const EL_MATRIX *A,
			 REAL b, const EL_MATRIX *B,
			 const EL_REAL_VEC_D *u_h,
			 REAL c, DOF_REAL_VEC_D *f_h,
			 const EL_DOF_VEC *dof,
			 const EL_SCHAR_VEC *bnd)
{
  if (f_h->stride == 1) {
    __bi_mat_el_vec_scl_dow(a, A, b, B, u_h,
			    c, (DOF_REAL_VEC *)f_h, dof, bnd);
  } else if (u_h->stride == 1) {
    __bi_mat_el_vec_dow_scl(a, A, b, B, (const EL_REAL_VEC *)u_h,
			    c, f_h, dof, bnd);
  } else {
    __bi_mat_el_vec_d(a, A, b, B, (const EL_REAL_D_VEC *)u_h,
		      c, (DOF_REAL_D_VEC *)f_h, dof, bnd);
  }
}

__FLATTEN_ATTRIBUTE__
static inline
void bi_mat_el_vec_dow(REAL a,
		       const EL_MATRIX *A,
		       REAL b,
		       const EL_MATRIX *B,
		       const EL_REAL_VEC_D *u_h,
		       REAL c,
		       DOF_REAL_VEC_D *f_h,
		       const EL_DOF_VEC *dof,
		       const EL_SCHAR_VEC *bnd)
{
  if (A == NULL) {
    A = B;
    a = b;
    B = NULL;
    b = 0.0;
  }
  COL_CHAIN_DO(A, const EL_MATRIX) {
    ROW_CHAIN_DO(A, const EL_MATRIX) {

      __bi_mat_el_vec_dow(a, A, b, B, u_h, c, f_h, dof, bnd);

      B = B ? ROW_CHAIN_NEXT(B, const EL_MATRIX) : NULL;
      u_h = CHAIN_NEXT(u_h, const EL_REAL_VEC_D);
    } ROW_CHAIN_WHILE(A, const EL_MATRIX);
    f_h = CHAIN_NEXT(f_h, DOF_REAL_VEC_D);
    dof = CHAIN_NEXT(dof, EL_DOF_VEC);
    bnd = bnd ? CHAIN_NEXT(bnd, EL_SCHAR_VEC) : NULL;
    B   = B ? COL_CHAIN_NEXT(B, const EL_MATRIX) : NULL;
  } COL_CHAIN_WHILE(A, const EL_MATRIX);
}

/* >>> */

/* >>> */

/* <<< f = c * f + a A u, f += a A u versions */

/* <<< SCL x SCL */

static inline void
gen_mat_el_vec(REAL a, const EL_MATRIX *A, const EL_REAL_VEC *u_h,
	       REAL b, DOF_REAL_VEC *f_h,
	       const EL_DOF_VEC *dof, const EL_SCHAR_VEC *bnd)
{
  bi_mat_el_vec(a, A, 0.0, NULL, u_h, b, f_h, dof, bnd);
}

static inline void
mat_el_vec(REAL a, const EL_MATRIX *A, const EL_REAL_VEC *u_h,
	   DOF_REAL_VEC *f_h, const EL_DOF_VEC *dof, const EL_SCHAR_VEC *bnd)
{
  gen_mat_el_vec(a, A, u_h, 1.0, f_h, dof, bnd);
}

/* >>> */

/* <<< DOW x DOW */

static inline void
gen_mat_el_vec_d(REAL a, const EL_MATRIX *A, const EL_REAL_D_VEC *u_h,
		 REAL b, DOF_REAL_D_VEC *f_h,
		 const EL_DOF_VEC *dof, const EL_SCHAR_VEC *bnd)
{
  bi_mat_el_vec_d(a, A, 0.0, NULL, u_h, b, f_h, dof, bnd);
}

static inline void
mat_el_vec_d(REAL a, const EL_MATRIX *A, const EL_REAL_D_VEC *u_h,
	     DOF_REAL_D_VEC *f_h,
	     const EL_DOF_VEC *dof, const EL_SCHAR_VEC *bnd)
{
  gen_mat_el_vec_d(a, A, u_h, 1.0, f_h, dof, bnd);
}

static inline void
gen_mat_el_vec_dow(REAL a, const EL_MATRIX *A, const EL_REAL_VEC_D *u_h,
		   REAL b, DOF_REAL_VEC_D *f_h,
		   const EL_DOF_VEC *dof, const EL_SCHAR_VEC *bnd)
{
  bi_mat_el_vec_dow(a, A, 0.0, NULL, u_h, b, f_h, dof, bnd);
}

static inline void
mat_el_vec_dow(REAL a, const EL_MATRIX *A, const EL_REAL_VEC_D *u_h,
	       DOF_REAL_VEC_D *f_h,
	       const EL_DOF_VEC *dof, const EL_SCHAR_VEC *bnd)
{
  gen_mat_el_vec_dow(a, A, u_h, 1.0, f_h, dof, bnd);
}

/* >>> */

/* <<< SCL x DOW */

static inline void
gen_mat_el_vec_rrd(REAL a, const EL_MATRIX *A, const EL_REAL_D_VEC *u_h,
		   REAL b, DOF_REAL_VEC *f_h,
		   const EL_DOF_VEC *dof, const EL_SCHAR_VEC *bnd)
{
  bi_mat_el_vec_rrd(a, A, 0.0, NULL, u_h, b, f_h, dof, bnd);
}

static inline void
mat_el_vec_rrd(REAL a, const EL_MATRIX *A, const EL_REAL_D_VEC *u_h,
	       DOF_REAL_VEC *f_h,
	       const EL_DOF_VEC *dof, const EL_SCHAR_VEC *bnd)
{
  gen_mat_el_vec_rrd(a, A, u_h, 1.0, f_h, dof, bnd);
}

static inline void
gen_mat_el_vec_scl_dow(REAL a, const EL_MATRIX *A, const EL_REAL_VEC_D *u_h,
		       REAL b, DOF_REAL_VEC *f_h,
		       const EL_DOF_VEC *dof, const EL_SCHAR_VEC *bnd)
{
  bi_mat_el_vec_scl_dow(a, A, 0.0, NULL, u_h, b, f_h, dof, bnd);
}

static inline void
mat_el_vec_scl_dow(REAL a, const EL_MATRIX *A, const EL_REAL_VEC_D *u_h,
		   DOF_REAL_VEC *f_h,
		   const EL_DOF_VEC *dof, const EL_SCHAR_VEC *bnd)
{
  gen_mat_el_vec_scl_dow(a, A, u_h, 1.0, f_h, dof, bnd);
}

/* >>> */

/* <<< DOW x SCL */

static inline void
gen_mat_el_vec_rdr(REAL a, const EL_MATRIX *A, const EL_REAL_VEC *u_h,
		   REAL b, DOF_REAL_D_VEC *f_h,
		   const EL_DOF_VEC *dof, const EL_SCHAR_VEC *bnd)
{
  bi_mat_el_vec_rdr(a, A, 0.0, NULL, u_h, b, f_h, dof, bnd);
}

static inline void
mat_el_vec_rdr(REAL a, const EL_MATRIX *A, const EL_REAL_VEC *u_h,
	       DOF_REAL_D_VEC *f_h,
	       const EL_DOF_VEC *dof, const EL_SCHAR_VEC *bnd)
{
  gen_mat_el_vec_rdr(a, A, u_h, 1.0, f_h, dof, bnd);
}

static inline void
gen_mat_el_vec_dow_scl(REAL a, const EL_MATRIX *A, const EL_REAL_VEC *u_h,
		       REAL b, DOF_REAL_VEC_D *f_h,
		       const EL_DOF_VEC *dof, const EL_SCHAR_VEC *bnd)
{
  bi_mat_el_vec_dow_scl(a, A, 0.0, NULL, u_h, b, f_h, dof, bnd);
}

static inline void
mat_el_vec_dow_scl(REAL a, const EL_MATRIX *A, const EL_REAL_VEC *u_h,
		   DOF_REAL_VEC_D *f_h,
		   const EL_DOF_VEC *dof, const EL_SCHAR_VEC *bnd)
{
  gen_mat_el_vec_dow_scl(a, A, u_h, 1.0, f_h, dof, bnd);
}

/* >>> */

/* >>> */

/* >>> */

/* <<< some blas functions (incomplete) */

/* <<< axpy, axey, set for EL_MATRIX */

static inline
EL_MATRIX *__el_mat_set(REAL a, EL_MATRIX *res)
{
  int i, j;

#undef MAT_BODY
#define MAT_BODY(PFX, CC, C, S, TYPE)		\
  for (i = 0; i < res->n_row; i++) {		\
    for (j = 0; j < res->n_col; j++) {		\
      PFX##SET_DOW(a, res->data.S[i][j]);	\
    }						\
  }
  MAT_EMIT_BODY_SWITCH(res->type);

  return res;
}

static inline EL_MATRIX *el_mat_set(REAL a, EL_MATRIX *res)
{
  COL_CHAIN_DO(res, EL_MATRIX) {
    ROW_CHAIN_DO(res, EL_MATRIX) {
      __el_mat_set(a, res);
    } ROW_CHAIN_WHILE(res, EL_MATRIX);
  } COL_CHAIN_WHILE(res, EL_MATRIX);

  return res;
}

static inline
EL_MATRIX *__el_mat_axey(REAL a, const EL_MATRIX *A, EL_MATRIX *res)
{
  int i, j;

#undef MAT_TRI_BODY
#define MAT_TRI_BODY(PFX1, CC1, C1, S1, TYPE1,				\
		     PFX2, CC2, C2, S2, TYPE2)				\
  for (i = 0; i < A->n_row; i++) {					\
    for (j = 0; j < A->n_col; j++) {					\
      PFX1##PFX2##AXEY_DOW(a, CC2 A->data.S2[i][j], res->data.S1[i][j]); \
    }									\
  }
  MAT_EMIT_TRI_BODY_SWITCH(A->type, res->type);

  return res;
}

static inline EL_MATRIX *
el_mat_axey(REAL a, const EL_MATRIX *A, EL_MATRIX *res)
{
  COL_CHAIN_DO(res, EL_MATRIX) {
    ROW_CHAIN_DO(res, EL_MATRIX) {
      __el_mat_axey(a, A, res);
      A = ROW_CHAIN_NEXT(A, const EL_MATRIX);
    } ROW_CHAIN_WHILE(res, EL_MATRIX);
    A = COL_CHAIN_NEXT(A, const EL_MATRIX);
  } COL_CHAIN_WHILE(res, EL_MATRIX);

  return res;
}

static inline
EL_MATRIX *__el_mat_axpy(REAL a, const EL_MATRIX *A, EL_MATRIX *res)
{
  int i, j;

#undef MAT_TRI_BODY
#define MAT_TRI_BODY(PFX1, CC1, C1, S1, TYPE1,				\
		     PFX2, CC2, C2, S2, TYPE2)				\
  for (i = 0; i < A->n_row; i++) {					\
    for (j = 0; j < A->n_col; j++) {					\
      PFX1##PFX2##AXPY_DOW(a, CC2 A->data.S2[i][j], res->data.S1[i][j]); \
    }									\
  }
  MAT_EMIT_TRI_BODY_SWITCH(res->type, A->type);

  return res;
}

static inline EL_MATRIX *
el_mat_axpy(REAL a, const EL_MATRIX *A, EL_MATRIX *res)
{
  COL_CHAIN_DO(res, EL_MATRIX) {
    ROW_CHAIN_DO(res, EL_MATRIX) {
      __el_mat_axpy(a, A, res);
      A = ROW_CHAIN_NEXT(A, const EL_MATRIX);
    } ROW_CHAIN_WHILE(res, EL_MATRIX);
    A = COL_CHAIN_NEXT(A, const EL_MATRIX);
  } COL_CHAIN_WHILE(res, EL_MATRIX);

  return res;
}

static inline
EL_MATRIX *M_el_mat_axpby(REAL a, const EL_MATRIX *X,
			  REAL b, const EL_MATRIX *Y, EL_MATRIX *res)
{

  int i, j;

  switch (X->type) {
  case MATENT_REAL_DD:
    switch (Y->type) {
    case MATENT_REAL_DD:
      for (i = 0; i < res->n_row; i++) {
	for (j = 0; j < res->n_col; j++) {
	  MMMAXPBY_DOW(a, (const REAL_D *) X->data.real_dd[i][j],
		       b, (const REAL_D *) Y->data.real_dd[i][j],
		       res->data.real_dd[i][j]);
	}
      }
      break;
    case MATENT_REAL_D:
      for (i = 0; i < res->n_row; i++) {
	for (j = 0; j < res->n_col; j++) {
	  MMDMAXPBY_DOW(a, (const REAL_D *) X->data.real_dd[i][j],
			b, Y->data.real_d[i][j], res->data.real_dd[i][j]);
	}
      }
      break;
    case MATENT_REAL:
      for (i = 0; i < res->n_row; i++) {
	for (j = 0; j < res->n_col; j++) {
	  MMSCMAXPBY_DOW(a, (const REAL_D *) X->data.real_dd[i][j],
			 b, Y->data.real[i][j], res->data.real_dd[i][j]);
	}
      }
      break;
    default:
      ERROR_EXIT("!!! unknown MATENT_TYPE: %d", Y->type);
      break;
    }
    break;
  case MATENT_REAL_D:
    if (Y->type == MATENT_REAL_D) {
      for (i = 0; i < res->n_row; i++) {
	for (j = 0; j < res->n_col; j++) {
	  MDMDMAXPBY_DOW(a, X->data.real_d[i][j],
			 b, Y->data.real_d[i][j], res->data.real_dd[i][j]);
	}
      }
    } else if (Y->type == MATENT_REAL) {
      for (i = 0; i < res->n_row; i++) {
	for (j = 0; j < res->n_col; j++) {
	  MDMSCMAXPBY_DOW(a, X->data.real_d[i][j],
			  b, Y->data.real[i][j], res->data.real_dd[i][j]);
	}
      }
    }
    break;
  case MATENT_REAL:
    if (res->type == MATENT_REAL) {
      for (i = 0; i < res->n_row; i++) {
	for (j = 0; j < res->n_col; j++) {
	  MSCMSCMAXPBY_DOW(a, X->data.real[i][j],
			   b, Y->data.real[i][j], res->data.real_dd[i][j]);
	}
      }
    }
    break;
  default:
    ERROR_EXIT("!!! unknown MATENT_TYPE: %d", X->type);
    break;
  }

  return res;
}

static inline
EL_MATRIX *DM_el_mat_axpby(REAL a, const EL_MATRIX *X,
			   REAL b, const EL_MATRIX *Y, EL_MATRIX *res)
{

  int i, j;

  switch (X->type) {

  case MATENT_REAL_D:
    if (Y->type == MATENT_REAL_D) {
      for (i = 0; i < res->n_row; i++) {
	for (j = 0; j < res->n_col; j++) {
	  DMDMDMAXPBY_DOW(a, X->data.real_d[i][j],
			  b, Y->data.real_d[i][j], res->data.real_d[i][j]);
	}
      }
    } else if (Y->type == MATENT_REAL) {
      for (i = 0; i < res->n_row; i++) {
	for (j = 0; j < res->n_col; j++) {
	  DMDMSCMAXPBY_DOW(a, X->data.real_d[i][j],
			   b, Y->data.real[i][j], res->data.real_d[i][j]);
	}
      }
    }
    break;
  case MATENT_REAL:
    if (Y->type == MATENT_REAL) {
      for (i = 0; i < res->n_row; i++) {
	for (j = 0; j < res->n_col; j++) {
	  DMSCMSCMAXPBY_DOW(a, X->data.real[i][j],
			    b, Y->data.real[i][j], res->data.real_d[i][j]);
	}
      }
    }
    break;
  default:
    ERROR_EXIT("!!! unknown MATENT_TYPE: %d", X->type);
    break;
  }

  return res;
}

static inline
EL_MATRIX *SCM_el_mat_axpby(REAL a, const EL_MATRIX *X,
			    REAL b, const EL_MATRIX *Y, EL_MATRIX *res)
{
  int i, j;

  for (i = 0; i < res->n_row; i++) {
    for (j = 0; j < res->n_col; j++) {
      SCMSCMSCMAXPBY_DOW(a, X->data.real[i][j],
			 b, Y->data.real[i][j], res->data.real[i][j]);
    }
  }

  return res;
}

static inline
EL_MATRIX *__el_mat_axpby(REAL a, const EL_MATRIX *X,
			  REAL b, const EL_MATRIX *Y, EL_MATRIX *res)
{
  FUNCNAME("el_mat_axpby");


  REAL tmp;
  const EL_MATRIX *tmp_mat;


  TEST_EXIT(res,
	    "please get_el_matrix(result,...) bevore using el_mat_axpby!!!\n");
  TEST_EXIT(res->type >= X->type,
	    "MATENT_TYPE of EL_MATRIX *result have to be equal "
	    "or greater then MATENT_TYPE of EL_MATRIX *X!!!\n");
  TEST_EXIT(res->type >= Y->type,
	    "MATENT_TYPE of EL_MATRIX *result have to be equal "
	    "or greater then MATENT_TYPE of EL_MATRIX *Y!!!\n");


  if (Y->type > X->type) {
    tmp_mat = X;
    tmp = a;
    X = Y;
    a = b;
    Y = tmp_mat;
    b = tmp;
  }

  switch (res->type) {
  case MATENT_REAL_DD:
    res = M_el_mat_axpby(a, X, b, Y, res);
    break;
  case MATENT_REAL_D:
    res = DM_el_mat_axpby(a, X, b, Y, res);
    break;
  case MATENT_REAL:
    res = SCM_el_mat_axpby(a, X, b, Y, res);
    break;
  default:
    ERROR_EXIT("!!! unknown MATENT_TYPE: %d", res->type);
    break;
  }

  return (res);

}

static inline EL_MATRIX *
el_mat_axpby(REAL a, const EL_MATRIX *X,
	     REAL b, const EL_MATRIX *Y, EL_MATRIX *res)
{
  COL_CHAIN_DO(res, EL_MATRIX) {
    ROW_CHAIN_DO(res, EL_MATRIX) {
      __el_mat_axpby(a, X, b, Y, res);
      X = ROW_CHAIN_NEXT(X, const EL_MATRIX);
      Y = ROW_CHAIN_NEXT(Y, const EL_MATRIX);
    } ROW_CHAIN_WHILE(res, EL_MATRIX);
    X = COL_CHAIN_NEXT(X, const EL_MATRIX);
    Y = COL_CHAIN_NEXT(Y, const EL_MATRIX);
  } COL_CHAIN_WHILE(res, EL_MATRIX);

  return res;
}

/* >>> */

/* <<< axpy() for element vectors  */

static inline
void __el_real_vec_axpy(REAL a, const EL_REAL_VEC *x, EL_REAL_VEC *y)
{
  FUNCNAME("__el_real_vec_axpy");
  int i;
  DEBUG_TEST_EXIT(x->n_components == y->n_components,
		  "element vector dimensions do not match, x: %d, y: %d.\n",
		  x->n_components, y->n_components);
  for (i = 0; i < x->n_components; i++) {
    y->vec[i] += a * x->vec[i];
  }
}

__FLATTEN_ATTRIBUTE__
static inline
void el_real_vec_axpy(REAL a, const EL_REAL_VEC *x, EL_REAL_VEC *y)
{
  CHAIN_DO(x, const EL_REAL_VEC) {
    __el_real_vec_axpy(a, x, y);
    y = CHAIN_NEXT(y, EL_REAL_VEC);
  } CHAIN_WHILE(x, const EL_REAL_VEC);
}

static inline
void __el_real_d_vec_axpy(REAL a, const EL_REAL_D_VEC *x, EL_REAL_D_VEC *y)
{
  FUNCNAME("__el_real_d_vec_axpy");
  int i;
  DEBUG_TEST_EXIT(x->n_components == y->n_components,
		  "element vector dimensions do not match, x: %d, y: %d.\n",
		  x->n_components, y->n_components);
  for (i = 0; i < x->n_components; i++) {
    AXPY_DOW(a, x->vec[i], y->vec[i]);
  }
}

__FLATTEN_ATTRIBUTE__
static inline
void el_real_d_vec_axpy(REAL a, const EL_REAL_D_VEC *x, EL_REAL_D_VEC *y)
{
  CHAIN_DO(x, const EL_REAL_D_VEC) {
    __el_real_d_vec_axpy(a, x, y);
    y = CHAIN_NEXT(y, EL_REAL_D_VEC);
  } CHAIN_WHILE(x, const EL_REAL_D_VEC);
}

__FLATTEN_ATTRIBUTE__
static inline
void el_real_vec_d_axpy(REAL a, const EL_REAL_VEC_D *x, EL_REAL_VEC_D *y)
{
  CHAIN_DO(x, const EL_REAL_VEC_D) {
    if (x->stride == 1) {
      __el_real_vec_axpy(a, (const EL_REAL_VEC *)x, (EL_REAL_VEC *)y);
    } else {
      __el_real_d_vec_axpy(a, (const EL_REAL_D_VEC *)x, (EL_REAL_D_VEC *)y);
    }
    y = CHAIN_NEXT(y, EL_REAL_VEC_D);
  } CHAIN_WHILE(x, const EL_REAL_VEC_D);
}

/* >>> */

/* <<< axey() for element vectors  */

static inline
void __el_real_vec_axey(REAL a, const EL_REAL_VEC *x, EL_REAL_VEC *y)
{
  FUNCNAME("__el_real_vec_axey");
  int i;
  DEBUG_TEST_EXIT(x->n_components == y->n_components,
		  "element vector dimensions do not match, x: %d, y: %d.\n",
		  x->n_components, y->n_components);
  for (i = 0; i < x->n_components; i++) {
    y->vec[i] = a * x->vec[i];
  }
}

__FLATTEN_ATTRIBUTE__
static inline
void el_real_vec_axey(REAL a, const EL_REAL_VEC *x, EL_REAL_VEC *y)
{
  CHAIN_DO(x, const EL_REAL_VEC) {
    __el_real_vec_axey(a, x, y);
    y = CHAIN_NEXT(y, EL_REAL_VEC);
  } CHAIN_WHILE(x, const EL_REAL_VEC);
}

static inline
void __el_real_d_vec_axey(REAL a, const EL_REAL_D_VEC *x, EL_REAL_D_VEC *y)
{
  FUNCNAME("__el_real_d_vec_axey");
  int i;
  DEBUG_TEST_EXIT(x->n_components == y->n_components,
		  "element vector dimensions do not match, x: %d, y: %d.\n",
		  x->n_components, y->n_components);
  for (i = 0; i < x->n_components; i++) {
    AXEY_DOW(a, x->vec[i], y->vec[i]);
  }
}

__FLATTEN_ATTRIBUTE__
static inline
void el_real_d_vec_axey(REAL a, const EL_REAL_D_VEC *x, EL_REAL_D_VEC *y)
{
  CHAIN_DO(x, const EL_REAL_D_VEC) {
    __el_real_d_vec_axey(a, x, y);
    y = CHAIN_NEXT(y, EL_REAL_D_VEC);
  } CHAIN_WHILE(x, const EL_REAL_D_VEC);
}

__FLATTEN_ATTRIBUTE__
static inline
void el_real_vec_d_axey(REAL a, const EL_REAL_VEC_D *x, EL_REAL_VEC_D *y)
{
  CHAIN_DO(x, const EL_REAL_VEC_D) {
    if (x->stride == 1) {
      __el_real_vec_axey(a, (const EL_REAL_VEC *)x, (EL_REAL_VEC *)y);
    } else {
      __el_real_d_vec_axey(a, (const EL_REAL_D_VEC *)x, (EL_REAL_D_VEC *)y);
    }
    y = CHAIN_NEXT(y, EL_REAL_VEC_D);
  } CHAIN_WHILE(x, const EL_REAL_VEC_D);
}

/* >>> */

/* <<< set() for element vectors  */

static inline
void __el_real_vec_set(REAL a, EL_REAL_VEC *y)
{
  int i;
  for (i = 0; i < y->n_components; i++) {
    y->vec[i] = a;
  }
}

__FLATTEN_ATTRIBUTE__
static inline
void el_real_vec_set(REAL a, EL_REAL_VEC *y)
{
  CHAIN_DO(y, EL_REAL_VEC) {
    __el_real_vec_set(a, y);
  } CHAIN_WHILE(y, EL_REAL_VEC);
}

static inline
void __el_real_d_vec_set(REAL a, EL_REAL_D_VEC *y)
{
  int i;
  for (i = 0; i < y->n_components; i++) {
    SET_DOW(a, y->vec[i]);
  }
}

__FLATTEN_ATTRIBUTE__
static inline
void el_real_d_vec_set(REAL a, EL_REAL_D_VEC *y)
{
  CHAIN_DO(y, EL_REAL_D_VEC) {
    __el_real_d_vec_set(a, y);
  } CHAIN_WHILE(y, EL_REAL_D_VEC);
}

__FLATTEN_ATTRIBUTE__
static inline
void el_real_vec_d_set(REAL a, EL_REAL_VEC_D *y)
{
  CHAIN_DO(y, EL_REAL_VEC_D) {
    if (y->stride == 1) {
      __el_real_vec_set(a, (EL_REAL_VEC *)y);
    } else {
      __el_real_d_vec_set(a, (EL_REAL_D_VEC *)y);
    }
  } CHAIN_WHILE(y, EL_REAL_VEC_D);
}

/* >>> */

/* >>> */

#endif /* _ALBERTA_EL_VEC_H_ */
