#ifndef _ALBERTA_EVALUATE_H_
#define _ALBERTA_EVALUATE_H_

#include "alberta.h"

/* <<< proto-types for all available functions */

/* <<< values */

static inline
REAL eval_uh(const REAL_B lambda, const EL_REAL_VEC *uh_loc, const BAS_FCTS *b);
static inline
const REAL *eval_uh_dow(REAL_D result, const REAL_B lambda,
			const EL_REAL_VEC_D *uh_loc, const BAS_FCTS *b);
static inline
const REAL *eval_uh_d(REAL_D result, const REAL_B lambda,
		      const EL_REAL_D_VEC *uh_loc, const BAS_FCTS *bfcts);

static inline
REAL eval_uh_fast(const EL_REAL_VEC *uh_loc, const QUAD_FAST *qfast, int iq);
static inline
const REAL *eval_uh_dow_fast(REAL_D result, const EL_REAL_VEC_D *uh_loc,
			     const QUAD_FAST *qfast, int iq);
static inline
const REAL *eval_uh_d_fast(REAL_D result, const EL_REAL_D_VEC *uh_el,
			   const QUAD_FAST *qfast, int iq);

static inline
const REAL *uh_at_qp(REAL *result,
		     const QUAD_FAST *qfast,
		     const EL_REAL_VEC *uh_loc);
static inline
const REAL_D *uh_dow_at_qp(REAL_D *result,
			   const QUAD_FAST *cache,
			   const EL_REAL_VEC_D *uh_loc);
static inline
const REAL_D *uh_d_at_qp(REAL_D *result,
			 const QUAD_FAST *qcache,
			 const EL_REAL_D_VEC *uh_loc);

/* >>> */

/* <<< first derivatives */

static inline
const REAL *eval_grd_uh(REAL_D result,
			const REAL_B lambda, const REAL_BD Lambda,
			const EL_REAL_VEC *uh_loc, const BAS_FCTS *bfcts);
static inline
const REAL_D *eval_grd_uh_d(REAL_DD result,
			    const REAL_B lambda, const REAL_BD Lambda,
			    const EL_REAL_D_VEC *uh_loc,
			    const BAS_FCTS *bfcts);
static inline
const REAL_D *eval_grd_uh_dow(REAL_DD result,
			      const REAL_B lambda, const REAL_BD Lambda,
			      const EL_REAL_VEC_D *uh_loc, const BAS_FCTS *b);
static inline
REAL eval_div_uh_dow(const REAL_B lambda, const REAL_BD Lambda,
		     const EL_REAL_VEC_D *uh_loc, const BAS_FCTS *b);
static inline
REAL eval_div_uh_d(const REAL_B lambda,
		   const REAL_BD Lambda,
		   const EL_REAL_D_VEC *uh_loc, const BAS_FCTS *b);

static inline
const REAL *eval_grd_uh_fast(REAL_D grd_uh,
			     const REAL_BD Lambda, const EL_REAL_VEC *uh_loc,
			     const QUAD_FAST *qfast, int iq);
static inline
const REAL_D *eval_grd_uh_dow_fast(REAL_DD result,
				   const REAL_BD Lambda,
				   const EL_REAL_VEC_D *uh_loc, 
				   const QUAD_FAST *qfast, int iq);
static inline
const REAL_D *eval_grd_uh_d_fast(REAL_DD result, const REAL_BD Lambda,
				 const EL_REAL_D_VEC *uh_loc,
				 const QUAD_FAST *qfast, int iq);
static inline
REAL eval_div_uh_dow_fast(const REAL_BD Lambda, const EL_REAL_VEC_D *uh_loc, 
			  const QUAD_FAST *qfast, int iq);
static inline
REAL eval_div_uh_d_fast(const REAL_BD Lambda,
			const EL_REAL_D_VEC *uh_loc,
			const QUAD_FAST *qfast, int iq);

static inline
const REAL_D *grd_uh_at_qp(REAL_D *result, const QUAD_FAST *qfast,
			   const REAL_BD Lambda,
			   const EL_REAL_VEC *uh_loc);
static inline
const REAL_DD *grd_uh_dow_at_qp(REAL_DD *result,
				const QUAD_FAST *cache,
				const REAL_BD Lambda, 
				const EL_REAL_VEC_D *uh_loc);
static inline
const REAL_DD *grd_uh_d_at_qp(REAL_DD *result, const QUAD_FAST *qcache,
			      const REAL_BD Lambda,
			      const EL_REAL_D_VEC *uh_loc);
static inline
const REAL *div_uh_dow_at_qp(REAL *result, const QUAD_FAST *cache,
			     const REAL_BD Lambda, 
			     const EL_REAL_VEC_D *uh_loc);
static inline
const REAL *div_uh_d_at_qp(REAL *result, const QUAD_FAST *qcache,
			   const REAL_BD Lambda,
			   const EL_REAL_D_VEC *uh_loc);


static inline
const REAL_D *param_grd_uh_at_qp(REAL_D vec[], const QUAD_FAST *qcache,
				 const REAL_BD Lambda[],
				 const EL_REAL_VEC *uh_loc);
static inline
const REAL_DD *param_grd_uh_dow_at_qp(REAL_DD vec[], const QUAD_FAST *cache,
				      const REAL_BD *Lambda,
				      const EL_REAL_VEC_D *uh_loc);
static inline
const REAL_DD *param_grd_uh_d_at_qp(REAL_DD vec[], const QUAD_FAST *qcache,
				    const REAL_BD Lambda[],
				    const EL_REAL_D_VEC *uh_loc);
static inline
const REAL *param_div_uh_dow_at_qp(REAL vec[], const QUAD_FAST *cache,
				   const REAL_BD *Lambda, 
				   const EL_REAL_VEC_D *uh_loc);
static inline
const REAL *param_div_uh_d_at_qp(REAL vec[], const QUAD_FAST *qcache,
				 const REAL_BD Lambda[],
				 const EL_REAL_D_VEC *uh_loc);

/* >>> */

/* <<< first barycentric derivatives */

static inline
const REAL *eval_bar_grd_uh(REAL_B result,
			    const REAL_B lambda,
			    const EL_REAL_VEC *uh_loc, const BAS_FCTS *bfcts);
static inline
const REAL_B *eval_bar_grd_uh_d(REAL_DB result,
				const REAL_B lambda,
				const EL_REAL_D_VEC *uh_loc,
				const BAS_FCTS *bfcts);
static inline
const REAL_B *eval_bar_grd_uh_dow(REAL_DB result,
				  const REAL_B lambda,
				  const EL_REAL_VEC_D *uh_loc,
				  const BAS_FCTS *b);
static inline
const REAL *eval_bar_grd_uh_fast(REAL_B grd_uh,
				 const EL_REAL_VEC *uh_loc,
				 const QUAD_FAST *qfast, int iq);
static inline
const REAL_B *eval_bar_grd_uh_dow_fast(REAL_DB result,
				       const EL_REAL_VEC_D *uh_loc, 
				       const QUAD_FAST *qfast, int iq);
static inline
const REAL_B *eval_bar_grd_uh_d_fast(REAL_DB result,
				     const EL_REAL_D_VEC *uh_loc,
				     const QUAD_FAST *qfast, int iq);

static inline
const REAL_B *bar_grd_uh_at_qp(REAL_B *result, const QUAD_FAST *qfast,
			       const EL_REAL_VEC *uh_loc);
static inline
const REAL_DB *bar_grd_uh_dow_at_qp(REAL_DB *result,
				    const QUAD_FAST *cache,
				    const EL_REAL_VEC_D *uh_loc);
static inline
const REAL_DB *bar_grd_uh_d_at_qp(REAL_DB *result, const QUAD_FAST *qcache,
				  const EL_REAL_D_VEC *uh_loc);

/* >>> */

/* <<< second derivatives */

static inline
const REAL_D *eval_D2_uh(REAL_DD result,
			 const REAL_B lambda, const REAL_BD Lambda,
			 const EL_REAL_VEC *uh_loc, const BAS_FCTS *bfcts);
static inline
const REAL_DD *eval_D2_uh_dow(REAL_DDD D2_uh,
			      const REAL_B lambda,
			      const REAL_BD Lambda,
			      const EL_REAL_VEC_D *uh_loc, const BAS_FCTS *b);
static inline
const REAL_DD *eval_D2_uh_d(REAL_DDD D2_uh,
			    const REAL_B lambda,
			    const REAL_BD Lambda,
			    const EL_REAL_D_VEC *uh_loc, const BAS_FCTS *b);

static inline
const REAL_D *eval_D2_uh_fast(REAL_DD result,
			      const REAL_BD Lambda, const EL_REAL_VEC *uh_el,
			      const QUAD_FAST *qfast, int iq);
static inline
const REAL_DD *eval_D2_uh_dow_fast(REAL_DDD D2_uh,
				   const REAL_BD Lambda, 
				   const EL_REAL_VEC_D *uh_loc, 
				   const QUAD_FAST *qfast, int iq);
static inline
const REAL_DD *eval_D2_uh_d_fast(REAL_DDD D2_uh,
				 const REAL_BD Lambda, 
				 const EL_REAL_D_VEC *uh_loc, 
				 const QUAD_FAST *qfast, int iq);
static inline
const REAL_DD *D2_uh_at_qp(REAL_DD *result,
			   const QUAD_FAST *qfast, const REAL_BD Lambda,
			   const EL_REAL_VEC *uh_loc);
static inline
const REAL_DDD *D2_uh_dow_at_qp(REAL_DDD vec[],
				const QUAD_FAST *cache,
				const REAL_BD Lambda,
				const EL_REAL_VEC_D *uh_loc);
static inline
const REAL_DDD *D2_uh_d_at_qp(REAL_DDD vec[],
			      const QUAD_FAST *cache,
			      const REAL_BD Lambda,
			      const EL_REAL_D_VEC *uh_loc);

static inline
const REAL_DD *param_D2_uh_at_qp(REAL_DD *result, 
				 const QUAD_FAST *qcache,
				 const REAL_BD Lambda[],
				 const REAL_BDD DLambda[],
				 const EL_REAL_VEC *uh_loc);
static inline
const REAL_DDD *param_D2_uh_dow_at_qp(REAL_DDD *result,
				      const QUAD_FAST *cache,
				      const REAL_BD *Lambda,
				      const REAL_BDD *DLambda,
				      const EL_REAL_VEC_D *uh_loc);
static inline
const REAL_DDD *param_D2_uh_d_at_qp(REAL_DDD vec[],
				    const QUAD_FAST *qcache,
				    const REAL_BD grd_lam[],
				    const REAL_BDD DLambda[],
				    const EL_REAL_D_VEC *uh_loc);

/* >>> */

/* <<< second barycentric derivatives */

static inline
const REAL_B *eval_bar_D2_uh(REAL_BB result,
			     const REAL_B lambda,
			     const EL_REAL_VEC *uh_loc,
			     const BAS_FCTS *bfcts);
static inline
const REAL_BB *eval_bar_D2_uh_dow(REAL_DBB D2_uh,
				  const REAL_B lambda,
				  const EL_REAL_VEC_D *uh_loc,
				  const BAS_FCTS *b);
static inline
const REAL_BB *eval_bar_D2_uh_d(REAL_DBB D2_uh,
				const REAL_B lambda,
				const EL_REAL_D_VEC *uh_loc,
				const BAS_FCTS *b);
static inline
const REAL_B *eval_bar_D2_uh_fast(REAL_BB result,
				  const EL_REAL_VEC *uh_el,
				  const QUAD_FAST *qfast, int iq);
static inline
const REAL_BB *eval_bar_D2_uh_dow_fast(REAL_DBB D2_uh,
				       const EL_REAL_VEC_D *uh_loc, 
				       const QUAD_FAST *qfast, int iq);
static inline
const REAL_BB *bar_D2_uh_at_qp(REAL_BB *result,
			       const QUAD_FAST *qfast,
			       const EL_REAL_VEC *uh_loc);
static inline
const REAL_DBB *bar_D2_uh_dow_at_qp(REAL_DBB vec[],
				    const QUAD_FAST *cache,
				    const EL_REAL_VEC_D *uh_loc);
static inline
const REAL_DBB *bar_D2_uh_d_at_qp(REAL_DBB vec[],
				  const QUAD_FAST *cache,
				  const EL_REAL_D_VEC *uh_loc);

/* >>> */

/* >>> */

/* <<< chain evaluation support */

#define DEFUN_CHAIN_VAL_FCT(name, VECNAME, el_vec,			\
			    chain, chaintype, res, restype, rettype,	\
			    ARGS_INIT, ARGS_UPDATE, DECL)		\
  __FLATTEN_ATTRIBUTE__							\
  static inline rettype name DECL					\
  {									\
    const EL_##VECNAME *uh_chain;					\
    rettype val;							\
									\
    (void)val;								\
									\
    res = (restype)__##name ARGS_INIT;					\
    uh_chain = el_vec;							\
    CHAIN_FOREACH(el_vec, uh_chain, const EL_##VECNAME) {		\
      chain = CHAIN_NEXT(chain, chaintype);				\
      val = __##name ARGS_UPDATE;					\
    }									\
    return (rettype)res;						\
  }									\
  struct _AI_semicolon_dummy

/* >>> */

/* <<< values of a function */

/* <<< slow evaluation */

static inline REAL
__eval_uh(const REAL_B lambda,
	  const EL_REAL_VEC *uh_loc, const BAS_FCTS *b)
{
  int       i;
  REAL      val = 0.0;
  
  for (i = 0; i < b->n_bas_fcts; i++) {
    val += uh_loc->vec[i]*PHI(b, i, lambda);
  }

  return val;
}

static inline const REAL *
__eval_uh_d(REAL_D values, const REAL_B lambda, const EL_REAL_D_VEC *uh_loc,
	    const BAS_FCTS *b, bool update)
{
  static REAL_D Values;
  int  i;
  REAL *val = values ? values : Values;

  if (!update) {
    SET_DOW(0.0, val);
  }

  for (i = 0; i < b->n_bas_fcts; i++) {
    AXPY_DOW(PHI(b, i, lambda), uh_loc->vec[i], val);
  }
  return (const REAL *)val;
}

static inline
const REAL *
__eval_uh_dow(REAL_D value, const REAL_B lambda,
	      const EL_REAL_VEC_D *uh_loc, const BAS_FCTS *b,
	      bool update)
{
  static REAL_D Values;
  int  i;

  value = value ? value : Values;

  if (uh_loc->stride != 1) {
    return __eval_uh_d(value, lambda, (const EL_REAL_D_VEC *)uh_loc, b, update);
  }

  if (!update) {
    SET_DOW(0.0, value);
  }
  for (i = 0; i < b->n_bas_fcts; i++) {
    AXPY_DOW(uh_loc->vec[i]*PHI(b, i, lambda), PHI_D(b, i, lambda), value);
  }

  return (const REAL *)value;
}

DEFUN_CHAIN_VAL_FCT(
  eval_uh, REAL_VEC, uh_loc, b, const BAS_FCTS, val, REAL, REAL,
  (lambda, uh_loc, b), (lambda, uh_loc, b) + val,
  (const REAL_B lambda, const EL_REAL_VEC *uh_loc, const BAS_FCTS *b));

DEFUN_CHAIN_VAL_FCT(
  eval_uh_d, REAL_D_VEC, uh_loc, b, const BAS_FCTS, res, REAL *, const REAL *,
  (res, lambda, uh_loc, b, false), (res, lambda, uh_loc, b, true),
  (REAL_D res,
   const REAL_B lambda, const EL_REAL_D_VEC *uh_loc, const BAS_FCTS *b));

DEFUN_CHAIN_VAL_FCT(
  eval_uh_dow, REAL_VEC_D, uh_loc, b, const BAS_FCTS, res, REAL *, const REAL *,
  (res, lambda, uh_loc, b, false), (res, lambda, uh_loc, b, true),
  (REAL_D res,
   const REAL_B lambda, const EL_REAL_VEC_D *uh_loc, const BAS_FCTS *b));

/* >>> */

/* <<< "fast" evaluation at a single quad-point */

static inline REAL
__eval_uh_fast(const EL_REAL_VEC *uh_loc, const QUAD_FAST *qfast, int iq)
{
  int       i;
  REAL      val = 0.0;
  const REAL *phi_val = qfast->phi[iq];

  for (i = 0; i < qfast->n_bas_fcts; i++) {
    val += uh_loc->vec[i]*phi_val[i];
  }

  return val;
}

static inline const REAL *
__eval_uh_d_fast(REAL_D result,
		 const EL_REAL_D_VEC *uh_loc, const QUAD_FAST *qfast, int iq,
		 bool update)
{
  static REAL_D result_space;
  const REAL *phi_val = qfast->phi[iq];
  int i;

  result = result ? result : result_space;

  if (!update) {
    AXEY_DOW(phi_val[0], uh_loc->vec[0], result);
  }
  for (i = !update; i < qfast->n_bas_fcts; i++) {
    AXPY_DOW(phi_val[i], uh_loc->vec[i], result);
  }

  return (const REAL *)result;
}

static inline const REAL *
__eval_uh_dow_fast(REAL_D value,
		   const EL_REAL_VEC_D *uh_loc, const QUAD_FAST *qfast, int iq,
		   bool update)
{
  static REAL_D result_space;
  const REAL_D *phi_val;
  int i;

  value = value ? value : result_space;

  if (uh_loc->stride != 1) {
    return __eval_uh_d_fast(
      value, (const EL_REAL_D_VEC *)uh_loc, qfast, iq, update);
  }
  
  phi_val = get_quad_fast_phi_dow(qfast)[iq];
  if (!update) {
    AXEY_DOW(uh_loc->vec[0], phi_val[0], value);
  }
  for (i = !update; i < qfast->n_bas_fcts; i++) {
    AXPY_DOW(uh_loc->vec[i], phi_val[i], value);
  }
  return (const REAL *)value;
}

DEFUN_CHAIN_VAL_FCT(
  eval_uh_fast, REAL_VEC, uh_loc, qfast, const QUAD_FAST, val, REAL, REAL,
  (uh_loc, qfast, iq), (uh_loc, qfast, iq) + val,
  (const EL_REAL_VEC *uh_loc, const QUAD_FAST *qfast, int iq));

DEFUN_CHAIN_VAL_FCT(
  eval_uh_d_fast, REAL_D_VEC,
  uh_loc, qfast, const QUAD_FAST, result, REAL *, const REAL *,
  (result, uh_loc, qfast, iq, false), (result, uh_loc, qfast, iq, true),
  (REAL_D result, const EL_REAL_D_VEC *uh_loc, const QUAD_FAST *qfast, int iq));
  
DEFUN_CHAIN_VAL_FCT(
  eval_uh_dow_fast, REAL_VEC_D,
  uh_loc, qfast, const QUAD_FAST, result, REAL *, const REAL *,
  (result, uh_loc, qfast, iq, false), (result, uh_loc, qfast, iq, true),
  (REAL_D result, const EL_REAL_VEC_D *uh_loc, const QUAD_FAST *qfast, int iq));

/* >>> */

/* <<< evaluation at multiple quad-points */

static inline const REAL *
__uh_at_qp(REAL vec[],
	   const QUAD_FAST *qfast, const EL_REAL_VEC *uh_loc, bool update)
{
  FUNCNAME("uh_at_qp");
  static REAL    *quad_vec = NULL;
  static size_t  size = 0;
  const REAL     *const*phi;
  int            i, j;

  if (!vec && !update) {
    if (size < (size_t)qfast->n_points) {
      MEM_FREE(quad_vec, size, REAL);
      size = qfast->n_points;
      quad_vec = MEM_ALLOC(size, REAL);
    }
    vec = quad_vec;
  }

  phi = qfast->phi;
  for (i = 0; i < qfast->n_points; i++) {
    if (!update) {
      vec[i] = 0.0;
    }
    for (j = 0; j < qfast->n_bas_fcts; j++) {
      vec[i] += uh_loc->vec[j]*phi[i][j];
    }
  }
    
  return (const REAL *)vec;
}

static inline const REAL_D *
__uh_d_at_qp(REAL_D vec[],
	     const QUAD_FAST *qfast, const EL_REAL_D_VEC *uh_loc,
	     bool update)
{
  FUNCNAME("uh_d_at_qp");
  static REAL_D *quad_vec = NULL;
  static size_t size = 0;
  const REAL    *const*phi;
  int           j, iq;

  if (!vec && !update) {
    if (size < (size_t)qfast->n_points) {
      MEM_FREE(quad_vec, size, REAL_D);
      size = qfast->n_points;
      quad_vec = MEM_ALLOC(size, REAL_D);
    }
    vec = quad_vec;
  }

  phi = qfast->phi;
  for (iq = 0; iq < qfast->n_points; iq++) {
    if (!update) {
      SET_DOW(0.0, vec[iq]);
    }
    for (j = 0; j < qfast->n_bas_fcts; j++) {
      AXPY_DOW(phi[iq][j], uh_loc->vec[j], vec[iq]);
    }
  }

  return (const REAL_D *)vec;
}

static inline const REAL_D *
__uh_dow_at_qp(REAL_D vec[],
	       const QUAD_FAST *qfast, const EL_REAL_VEC_D *uh_loc,
	       bool update)
{
  static REAL_D *quad_vec = NULL;
  static size_t size = 0;
  const REAL_D *const*phi;
  int iq, i;

  if (!vec) {
    if (size < (size_t)qfast->n_points) {
      MEM_FREE(quad_vec, size, REAL_D);
      size = qfast->n_points;
      quad_vec = MEM_ALLOC(size, REAL_D);
    }
    vec = quad_vec;
  }

  if (uh_loc->stride != 1) {
    return __uh_d_at_qp(vec, qfast, (const EL_REAL_D_VEC *)uh_loc, update);
  }

  phi = get_quad_fast_phi_dow(qfast);
  for (iq = 0; iq < qfast->n_points; iq++) {
    if (!update) {
      SET_DOW(0.0, vec[iq]);
    }
    for (i = 0; i < qfast->n_bas_fcts; i++) {
      AXPY_DOW(uh_loc->vec[i], phi[iq][i], vec[iq]);
    }
  }

  return (const REAL_D *)vec;
}

DEFUN_CHAIN_VAL_FCT(
  uh_at_qp, REAL_VEC,
  uh_loc, qfast, const QUAD_FAST, result, REAL *, const REAL *,
  (result, qfast, uh_loc, false), (result, qfast, uh_loc, true),
  (REAL *result, const QUAD_FAST *qfast, const EL_REAL_VEC *uh_loc));

DEFUN_CHAIN_VAL_FCT(
  uh_d_at_qp, REAL_D_VEC,
  uh_loc, qfast, const QUAD_FAST, result, REAL_D *, const REAL_D *,
  (result, qfast, uh_loc, false), (result, qfast, uh_loc, true),
  (REAL_D *result, const QUAD_FAST *qfast, const EL_REAL_D_VEC *uh_loc));

DEFUN_CHAIN_VAL_FCT(
  uh_dow_at_qp, REAL_VEC_D,
  uh_loc, qfast, const QUAD_FAST, result, REAL_D *, const REAL_D *,
  (result, qfast, uh_loc, false), (result, qfast, uh_loc, true),
  (REAL_D *result, const QUAD_FAST *qfast, const EL_REAL_VEC_D *uh_loc));


/* >>> */

/* >>> */

/* <<< first derivatives */

/* <<< slow evaluation, barycentric gradients */

static inline const REAL *
__eval_bar_grd_uh(REAL_B grd_uh,
		  const REAL_B lambda, 
		  const EL_REAL_VEC *uh_loc, const BAS_FCTS *b,
		  bool update)
{
  static REAL_B  grd;
  const REAL     *grd_b;
  int            i, dim = b->dim;

  if (grd_uh == NULL) {
    grd_uh = grd;
  }

  if (!update) {
    SET_BAR(dim, 0.0, grd_uh);
  }
  for (i = 0; i < b->n_bas_fcts; i++) {
    grd_b = GRD_PHI(b, i, lambda);
    AXPY_BAR(dim, uh_loc->vec[i], grd_b, grd_uh);
  }
  return (const REAL *)grd_uh;
}

static inline const REAL_B *
__eval_bar_grd_uh_d(REAL_DB grd_uh,
		    const REAL_B lambda, 
		    const EL_REAL_D_VEC *uh_loc, const BAS_FCTS *b,
		    bool update)
{
  static REAL_DB  grd;
  const REAL      *grd_b;
  int             i, j, n, dim = b->dim;

  if (grd_uh == NULL) {
    grd_uh = grd;
  }
 
  if (!update) {
    for (n = 0; n < DIM_OF_WORLD; n++) {
      SET_BAR(dim, 0.0, grd_uh[n]);
    }
  }
  for (i = 0; i < b->n_bas_fcts; i++) {
    grd_b = GRD_PHI(b, i, lambda);  
    for (j = 0; j < N_LAMBDA(dim); j++)
      for (n = 0; n < DIM_OF_WORLD; n++)
	grd_uh[n][j] += uh_loc->vec[i][n]*grd_b[j];
  }

  return (const REAL_B *)grd_uh;
}

static inline const REAL_B *
__eval_bar_grd_uh_dow(REAL_DB grd_uh,
		      const REAL_B lambda, 
		      const EL_REAL_VEC_D *uh_loc, const BAS_FCTS *b,
		      bool update)
{
  static REAL_DB  grd;
  const REAL      *grd_b, *dir;
  const REAL_B    *grd_dir;
  int             i, n, dim = b->dim;

  if (grd_uh == NULL) {
    grd_uh = grd;
  }
  
  if (uh_loc->stride != 1) {
    return __eval_bar_grd_uh_d(grd_uh, lambda, (const EL_REAL_D_VEC *)uh_loc, b,
			       update);
  }
  
  if (!update) {
    for (n = 0; n < DIM_OF_WORLD; n++) {
      SET_BAR(dim, 0.0, grd_uh[n]);
    }
  }

  if (!b->dir_pw_const) {
    for (i = 0; i < b->n_bas_fcts; i++) {
      REAL phi;

      grd_dir = GRD_PHI_D(b, i, lambda);
      phi     = PHI(b, i, lambda);

      for (n = 0; n < DIM_OF_WORLD; n++) {
	AXPY_BAR(dim, uh_loc->vec[i] * phi, grd_dir[n], grd_uh[n]);
      }
    }
  }

  for (i = 0; i < b->n_bas_fcts; i++) {
    grd_b = GRD_PHI(b, i, lambda);
    dir   = PHI_D(b, i, lambda);
      
    for (n = 0; n < DIM_OF_WORLD; n++) {
      AXPY_BAR(dim, uh_loc->vec[i]*dir[n], grd_b, grd_uh[n]);
    }
  }

  return (const REAL_B *)grd_uh;
}

DEFUN_CHAIN_VAL_FCT(
  eval_bar_grd_uh, REAL_VEC,
  uh_loc, b, const BAS_FCTS, grd_uh, REAL *, const REAL *,
  (grd_uh, lambda, uh_loc, b, false),
  (grd_uh, lambda, uh_loc, b, true),
  (REAL_B grd_uh, const REAL_B lambda,
   const EL_REAL_VEC *uh_loc, const BAS_FCTS *b));

DEFUN_CHAIN_VAL_FCT(
  eval_bar_grd_uh_d, REAL_D_VEC,
  uh_loc, b, const BAS_FCTS, grd_uh, REAL_B *, const REAL_B *,
  (grd_uh, lambda, uh_loc, b, false),
  (grd_uh, lambda, uh_loc, b, true),
  (REAL_DB grd_uh,
   const REAL_B lambda, const EL_REAL_D_VEC *uh_loc, const BAS_FCTS *b));

DEFUN_CHAIN_VAL_FCT(
  eval_bar_grd_uh_dow, REAL_VEC_D,
  uh_loc, b, const BAS_FCTS, grd_uh, REAL_B *, const REAL_B *,
  (grd_uh, lambda, uh_loc, b, false),
  (grd_uh, lambda, uh_loc, b, true),
  (REAL_DB grd_uh,
   const REAL_B lambda, const EL_REAL_VEC_D *uh_loc, const BAS_FCTS *b));

/* >>> */

/* <<< slow evaluation */

static inline const REAL *
__eval_grd_uh(REAL_D grd_uh,
	      const REAL_B lambda, 
	      const REAL_BD Lambda,
	      const EL_REAL_VEC *uh_loc, const BAS_FCTS *b,
	      bool update)
{
  static REAL_D  grd;
  const REAL     *grd_b;
  int            i, j, dim = b->dim;
  REAL           *val;
  REAL_B         grd1 = {0};
  
  val = grd_uh ? grd_uh : grd;
 
  for (i = 0; i < b->n_bas_fcts; i++) {
    grd_b = GRD_PHI(b, i, lambda);       
    for (j = 0; j < N_LAMBDA(dim); j++) {
      grd1[j] += uh_loc->vec[i]*grd_b[j];
    }
  }

  for (i = 0; i < DIM_OF_WORLD; i++) {
    if (!update) {
      val[i] = 0.0;
    }
    for (j = 0; j < N_LAMBDA(dim); j++)
      val[i] += Lambda[j][i]*grd1[j];
  }
  
  return (const REAL *)val;
}

static inline const REAL_D *
__eval_grd_uh_d(REAL_DD grd_uh,
		const REAL_B lambda, 
		const REAL_BD Lambda,
		const EL_REAL_D_VEC *uh_loc, const BAS_FCTS *b,
		bool update)
{
  static REAL_DD  grd;
  const REAL      *grd_b;
  int             i, j, n, dim = b->dim;
  REAL_D          *val;
  REAL_DB         grd1 = { { 0.0 } };
  
  val = grd_uh ? grd_uh : grd;
 
  for (i = 0; i < b->n_bas_fcts; i++) {
    grd_b = GRD_PHI(b, i, lambda);  
    for (j = 0; j < N_LAMBDA(dim); j++)
      for (n = 0; n < DIM_OF_WORLD; n++)
	grd1[n][j] += uh_loc->vec[i][n]*grd_b[j];
  }

  for (i = 0; i < DIM_OF_WORLD; i++) {
    for (n = 0; n < DIM_OF_WORLD; n++) {
      if (!update) {
	val[n][i] = 0.0;
      }
      for (j = 0; j < N_LAMBDA(dim); j++) {
	val[n][i] += Lambda[j][i]*grd1[n][j];
      }
    }
  }
  
  return (const REAL_D *)val;
}

static inline const REAL_D *
__eval_grd_uh_dow(REAL_DD grd_uh,
		  const REAL_B lambda, 
		  const REAL_BD Lambda,
		  const EL_REAL_VEC_D *uh_loc, const BAS_FCTS *b,
		  bool update)
{
  static REAL_DD  grd;
  const REAL      *grd_b, *dir;
  const REAL_B    *grd_dir;
  int             i, n, dim = b->dim;
  REAL_DD         tmp;
  REAL_DB         grd1 = {{0}};

  if (grd_uh == NULL) {
    grd_uh = grd;
  }
  
  if (uh_loc->stride != 1) {
    return
      __eval_grd_uh_d(grd_uh,
		      lambda, Lambda, (const EL_REAL_D_VEC *)uh_loc, b,
		      update);
  }
  
  if (!b->dir_pw_const) {
    for (i = 0; i < b->n_bas_fcts; i++) {
      REAL phi;

      grd_dir = GRD_PHI_D(b, i, lambda);
      phi     = PHI(b, i, lambda);

      for (n = 0; n < DIM_OF_WORLD; n++) {
	AXPY_BAR(dim, uh_loc->vec[i] * phi, grd_dir[n], grd1[n]);
      }
    }
  }

  for (i = 0; i < b->n_bas_fcts; i++) {
    grd_b = GRD_PHI(b, i, lambda);
    dir   = PHI_D(b, i, lambda);
      
    for (n = 0; n < DIM_OF_WORLD; n++) {
      AXPY_BAR(dim, uh_loc->vec[i]*dir[n], grd_b, grd1[n]);
    }
  }

  if (update) {
    MAXPY_DOW(1.0, MGRAD_DOW(dim, Lambda, (const REAL_B *)grd1, tmp), grd_uh);
  } else {
    MGRAD_DOW(dim, Lambda, (const REAL_B *)grd1, grd_uh);
  }

  return (const REAL_D *)grd_uh;
}

static inline REAL
__eval_div_uh_d(const REAL_B lambda, const REAL_BD Lambda,
		const EL_REAL_D_VEC *uh_loc, const BAS_FCTS *b)
{
  REAL            div;
  const REAL      *grd_b;
  int             i, j, n, dim = b->dim;
  REAL_B          grd1[DIM_OF_WORLD] = {{0}};
  
  for (i = 0; i < b->n_bas_fcts; i++)
  {
    grd_b = GRD_PHI(b, i, lambda);  
    for (j = 0; j < N_LAMBDA(dim); j++)
      for (n = 0; n < DIM_OF_WORLD; n++)
	grd1[n][j] += uh_loc->vec[i][n]*grd_b[j];
  }

  for (div = n = 0; n < DIM_OF_WORLD; n++)
    for (j = 0; j < N_LAMBDA(dim); j++)
      div += Lambda[j][n]*grd1[n][j];
  
  return div;
}

static inline REAL
__eval_div_uh_dow(const REAL_B lambda, const REAL_BD Lambda,
		  const EL_REAL_VEC_D *uh_loc, const BAS_FCTS *b)
{
  REAL            div;
  const REAL      *grd_b, *phi_d;
  int             i, j, n, dim = b->dim;
  REAL_B          grd1[DIM_OF_WORLD] = {{0}};

  if (uh_loc->stride != 1) {
    return __eval_div_uh_d(lambda, Lambda, (const EL_REAL_D_VEC *)uh_loc, b);
  }

  div = 0.0;
  if (!b->dir_pw_const) {
    for (i = 0; i < b->n_bas_fcts; i++) {
      const REAL_B *grd_phi_d;
      REAL phi;

      grd_phi_d = GRD_PHI_D(b, i, lambda);
      phi       = PHI(b, i, lambda);

      for (n = 0; n < DIM_OF_WORLD; n++) {
	AXPY_BAR(dim, uh_loc->vec[i] * phi, grd_phi_d[n], grd1[n]);
      }
    }
  }

  for (i = 0; i < b->n_bas_fcts; i++) {
    grd_b = GRD_PHI(b, i, lambda);
    phi_d = PHI_D(b, i, lambda);
      
    for (n = 0; n < DIM_OF_WORLD; n++) {
      AXPY_BAR(dim, uh_loc->vec[i]*phi_d[n], grd_b, grd1[n]);
    }
  }
  
  for (div = n = 0; n < DIM_OF_WORLD; n++) {
    for (j = 0; j < N_LAMBDA(dim); j++) {
      div += Lambda[j][n]*grd1[n][j];
    }
  }
  
  return div;
}

DEFUN_CHAIN_VAL_FCT(
  eval_grd_uh, REAL_VEC,
  uh_loc, b, const BAS_FCTS, grd_uh, REAL *, const REAL *,
  (grd_uh, lambda, Lambda, uh_loc, b, false),
  (grd_uh, lambda, Lambda, uh_loc, b, true),
  (REAL_D grd_uh, const REAL_B lambda, const REAL_BD Lambda,
   const EL_REAL_VEC *uh_loc, const BAS_FCTS *b));

DEFUN_CHAIN_VAL_FCT(
  eval_grd_uh_d, REAL_D_VEC,
  uh_loc, b, const BAS_FCTS, grd_uh, REAL_D *, const REAL_D *,
  (grd_uh, lambda, Lambda, uh_loc, b, false),
  (grd_uh, lambda, Lambda, uh_loc, b, true),
  (REAL_DD grd_uh,
   const REAL_B lambda, const REAL_BD Lambda,
   const EL_REAL_D_VEC *uh_loc, const BAS_FCTS *b));

DEFUN_CHAIN_VAL_FCT(
  eval_grd_uh_dow, REAL_VEC_D,
  uh_loc, b, const BAS_FCTS, grd_uh, REAL_D *, const REAL_D *,
  (grd_uh, lambda, Lambda, uh_loc, b, false),
  (grd_uh, lambda, Lambda, uh_loc, b, true),
  (REAL_DD grd_uh,
   const REAL_B lambda, const REAL_BD Lambda,
   const EL_REAL_VEC_D *uh_loc, const BAS_FCTS *b));


DEFUN_CHAIN_VAL_FCT(
  eval_div_uh_d, REAL_D_VEC,
  uh_loc, b, const BAS_FCTS, val, REAL, REAL,
  (lambda, Lambda, uh_loc, b), (lambda, Lambda, uh_loc, b) + val,
  (const REAL_B lambda, const REAL_BD Lambda,
   const EL_REAL_D_VEC *uh_loc, const BAS_FCTS *b));

DEFUN_CHAIN_VAL_FCT(
  eval_div_uh_dow, REAL_VEC_D,
  uh_loc, b, const BAS_FCTS, val, REAL, REAL,
  (lambda, Lambda, uh_loc, b), (lambda, Lambda, uh_loc, b) + val,
  (const REAL_B lambda, const REAL_BD Lambda,
   const EL_REAL_VEC_D *uh_loc, const BAS_FCTS *b));

/* >>> */

/* <<< "fast" evaluation at a single point, barycentric gradients */

static inline const REAL *
__eval_bar_grd_uh_fast(REAL_B grd_uh,
		       const EL_REAL_VEC *uh_loc,
		       const QUAD_FAST *qfast, int iq,
		       bool update)
{
  static REAL_D grd;
  int           i;
  const REAL_B  *grd_phi = qfast->grd_phi[iq];
  
  grd_uh = grd_uh ? grd_uh : grd;

  if (!update) {
    SET_BAR(DIM_MAX, 0.0, grd_uh);
  }
  for (i = 0; i < qfast->n_bas_fcts; i++) {
    AXPY_BAR(DIM_MAX, uh_loc->vec[i], grd_phi[i], grd_uh);
  }
  return (const REAL *)grd_uh;
}

static inline const REAL_B *
__eval_bar_grd_uh_d_fast(REAL_DB grd_uh,
			 const EL_REAL_D_VEC *uh_loc,
			 const QUAD_FAST *qfast, int iq,
			 bool update)
{
  static REAL_DB grd;
  int            i, n;
  const REAL_B   *grd_phi = qfast->grd_phi[iq];
  
  grd_uh = grd_uh ? grd_uh : grd;

  for (n = 0; n < DIM_OF_WORLD; n++) {
    if (!update) {
      SET_BAR(DIM_MAX, 0.0, grd_uh[n]);
    }
    for (i = 0; i < qfast->n_bas_fcts; i++) {
      AXPY_BAR(DIM_MAX, uh_loc->vec[i][n], grd_phi[i], grd_uh[n]);
    }
  }
  
  return (const REAL_B *)grd_uh;
}

static inline const REAL_B *
__eval_bar_grd_uh_dow_fast(REAL_DB grd_uh,
			   const EL_REAL_VEC_D *uh_loc, 
			   const QUAD_FAST *qfast, int iq,
			   bool update)
{
  static REAL_DB grd_space;
  int    i, n;
  const REAL_DB *grd_phi;

  grd_uh = grd_uh ? grd_uh : grd_space;

  if (uh_loc->stride != 1) {
    return __eval_bar_grd_uh_d_fast(grd_uh, (const EL_REAL_D_VEC *)uh_loc,
				    qfast, iq, update);
  }

  grd_phi = get_quad_fast_grd_phi_dow(qfast)[iq];

  for (n = 0; n < DIM_OF_WORLD; n++) {
    if (!update) {
      SET_BAR(DIM_MAX, 0.0, grd_uh[n]);
    }
    for (i = 0; i < qfast->n_bas_fcts; i++) {
      AXPY_BAR(DIM_MAX, uh_loc->vec[i], grd_phi[i][n], grd_uh[n]);
    }
  }
  
  return (const REAL_B *)grd_uh;
}

DEFUN_CHAIN_VAL_FCT(
  eval_bar_grd_uh_fast, REAL_VEC,
  uh_loc, qfast, const QUAD_FAST, grd_uh, REAL *, const REAL *,
  (grd_uh, uh_loc, qfast, iq, false),
  (grd_uh, uh_loc, qfast, iq, true),
  (REAL_D grd_uh, const EL_REAL_VEC *uh_loc,
   const QUAD_FAST *qfast, int iq));

DEFUN_CHAIN_VAL_FCT(
  eval_bar_grd_uh_d_fast, REAL_D_VEC,
  uh_loc, qfast, const QUAD_FAST, grd_uh, REAL_B *, const REAL_B *,
  (grd_uh, uh_loc, qfast, iq, false),
  (grd_uh, uh_loc, qfast, iq, true),
  (REAL_DB grd_uh,
   const EL_REAL_D_VEC *uh_loc, const QUAD_FAST *qfast, int iq));

DEFUN_CHAIN_VAL_FCT(
  eval_bar_grd_uh_dow_fast, REAL_VEC_D,
  uh_loc, qfast, const QUAD_FAST, grd_uh, REAL_B *, const REAL_B *,
  (grd_uh, uh_loc, qfast, iq, false),
  (grd_uh, uh_loc, qfast, iq, true),
  (REAL_DB grd_uh,
   const EL_REAL_VEC_D *uh_loc, const QUAD_FAST *qfast, int iq));

/* >>> */

/* <<< "fast" evaluation at a single point */

static inline const REAL *
__eval_grd_uh_fast(REAL_D grd_uh,
		   const REAL_BD Lambda, 
		   const EL_REAL_VEC *uh_loc,
		   const QUAD_FAST *qfast, int iq,
		   bool update)
{
  static REAL_D grd;
  int           i;
  REAL_B        grd1 = {0};
  const REAL_B  *grd_phi = qfast->grd_phi[iq];
  
  grd_uh = grd_uh ? grd_uh : grd;

  for (i = 0; i < qfast->n_bas_fcts; i++) {
    AXPY_BAR(DIM_MAX, uh_loc->vec[i], grd_phi[i], grd1);
  }
  
  if (update) {
    GRAD_P_DOW(DIM_MAX, Lambda, grd1, grd_uh);
  } else {
    GRAD_DOW(DIM_MAX, Lambda, grd1, grd_uh);
  }
  
  return (const REAL *)grd_uh;
}

static inline const REAL_D *
__eval_grd_uh_d_fast(REAL_DD grd_uh,
		     const REAL_BD Lambda, 
		     const EL_REAL_D_VEC *uh_loc,
		     const QUAD_FAST *qfast, int iq,
		     bool update)
{
  static REAL_DD grd;
  int            i, n;
  REAL_B         grd1;
  const REAL_B   *grd_phi = qfast->grd_phi[iq];
  
  grd_uh = grd_uh ? grd_uh : grd;

  for (n = 0; n < DIM_OF_WORLD; n++) {
    SET_BAR(DIM_MAX, 0.0, grd1);
    for (i = 0; i < qfast->n_bas_fcts; i++) {
      AXPY_BAR(DIM_MAX, uh_loc->vec[i][n], grd_phi[i], grd1);
    }
    if (update) {
      GRAD_P_DOW(DIM_MAX, Lambda, grd1, grd_uh[n]);
    } else {
      GRAD_DOW(DIM_MAX, Lambda, grd1, grd_uh[n]);
    }
  }
  
  return (const REAL_D *)grd_uh;
}

static inline const REAL_D *
__eval_grd_uh_dow_fast(REAL_DD grd_uh,
		       const REAL_BD Lambda, 
		       const EL_REAL_VEC_D *uh_loc, 
		       const QUAD_FAST *qfast, int iq,
		       bool update)
{
  static REAL_DD grd_space;
  int    i, n;
  REAL_B grd1;
  const REAL_DB *grd_phi;

  grd_uh = grd_uh ? grd_uh : grd_space;

  if (uh_loc->stride != 1) {
    return __eval_grd_uh_d_fast(grd_uh, Lambda, (const EL_REAL_D_VEC *)uh_loc,
				qfast, iq, update);
  }

  grd_phi = get_quad_fast_grd_phi_dow(qfast)[iq];

  for (n = 0; n < DIM_OF_WORLD; n++) {
    SET_BAR(DIM_MAX, 0.0, grd1);
    for (i = 0; i < qfast->n_bas_fcts; i++) {
      AXPY_BAR(DIM_MAX, uh_loc->vec[i], grd_phi[i][n], grd1);
    }
  
    if (update) {
      GRAD_P_DOW(DIM_MAX, Lambda, grd1, grd_uh[n]);
    } else {
      GRAD_DOW(DIM_MAX, Lambda, grd1, grd_uh[n]);
    }
  }
  
  return (const REAL_D *)grd_uh;
}

static inline REAL
__eval_div_uh_d_fast(const REAL_BD Lambda, const EL_REAL_D_VEC *uh_loc,
		     const QUAD_FAST *qfast, int iq)
{
  REAL      div;
  int       i, j, n;
  REAL_B    grd1;
  const REAL_B *grd_phi = qfast->grd_phi[iq];
  
  for (div = n = 0; n < DIM_OF_WORLD; n++) {
    for (j = 0; j < N_LAMBDA_MAX; j++) {
      grd1[j] = 0.0;

      for (i = 0; i < qfast->n_bas_fcts; i++)
	grd1[j] += uh_loc->vec[i][n]*grd_phi[i][j];
    }

    for (j = 0; j < N_LAMBDA_MAX; j++) {
      div += Lambda[j][n]*grd1[j];
    }
  }
  
  return div;
}

static inline REAL
__eval_div_uh_dow_fast(const REAL_BD Lambda, const EL_REAL_VEC_D *uh_loc,
		       const QUAD_FAST *qfast, int iq)
{
  REAL      div;
  int       i, j, n;
  REAL_B    grd1;
  const REAL_DB *grd_phi;

  if (uh_loc->stride != 1) {
    return
      __eval_div_uh_d_fast(Lambda, (const EL_REAL_D_VEC *)uh_loc, qfast, iq);
  }

  grd_phi = get_quad_fast_grd_phi_dow(qfast)[iq];

  for (div = n = 0; n < DIM_OF_WORLD; n++) {
    for (j = 0; j < N_LAMBDA_MAX; j++) {
      grd1[j] = 0.0;

      for (i = 0; i < qfast->n_bas_fcts; i++) {
	grd1[j] += uh_loc->vec[i]*grd_phi[i][n][j];
      }
    }

    for (j = 0; j < N_LAMBDA_MAX; j++) {
      div += Lambda[j][n]*grd1[j];
    }
  }
  
  return div;
}

DEFUN_CHAIN_VAL_FCT(
  eval_grd_uh_fast, REAL_VEC,
  uh_loc, qfast, const QUAD_FAST, grd_uh, REAL *, const REAL *,
  (grd_uh, Lambda, uh_loc, qfast, iq, false),
  (grd_uh, Lambda, uh_loc, qfast, iq, true),
  (REAL_D grd_uh, const REAL_BD Lambda, const EL_REAL_VEC *uh_loc,
   const QUAD_FAST *qfast, int iq));

DEFUN_CHAIN_VAL_FCT(
  eval_grd_uh_d_fast, REAL_D_VEC,
  uh_loc, qfast, const QUAD_FAST, grd_uh, REAL_D *, const REAL_D *,
  (grd_uh, Lambda, uh_loc, qfast, iq, false),
  (grd_uh, Lambda, uh_loc, qfast, iq, true),
  (REAL_DD grd_uh, const REAL_BD Lambda, const EL_REAL_D_VEC *uh_loc,
   const QUAD_FAST *qfast, int iq));

DEFUN_CHAIN_VAL_FCT(
  eval_grd_uh_dow_fast, REAL_VEC_D,
  uh_loc, qfast, const QUAD_FAST, grd_uh, REAL_D *, const REAL_D *,
  (grd_uh, Lambda, uh_loc, qfast, iq, false),
  (grd_uh, Lambda, uh_loc, qfast, iq, true),
  (REAL_DD grd_uh, const REAL_BD Lambda, const EL_REAL_VEC_D *uh_loc,
   const QUAD_FAST *qfast, int iq));

DEFUN_CHAIN_VAL_FCT(
  eval_div_uh_d_fast, REAL_D_VEC,
  uh_loc, qfast, const QUAD_FAST, val, REAL, REAL,
  (Lambda, uh_loc, qfast, iq), (Lambda, uh_loc, qfast, iq) + val,
  (const REAL_BD Lambda, const EL_REAL_D_VEC *uh_loc,
   const QUAD_FAST *qfast, int iq));

DEFUN_CHAIN_VAL_FCT(
  eval_div_uh_dow_fast, REAL_VEC_D,
  uh_loc, qfast, const QUAD_FAST, val, REAL, REAL,
  (Lambda, uh_loc, qfast, iq), (Lambda, uh_loc, qfast, iq) + val,
  (const REAL_BD Lambda, const EL_REAL_VEC_D *uh_loc,
   const QUAD_FAST *qfast, int iq));

/* >>> */

/* <<< first derivatives, compute a "quad-vec", barycentric gradients */

static inline
const REAL_B *__bar_grd_uh_at_qp(REAL_B vec[],
				 const QUAD_FAST *qfast,
				 const EL_REAL_VEC *uh_loc,
				 bool update)
{
  FUNCNAME("bar_grd_uh_at_qp");
  static REAL_B  *quad_vec = NULL;
  static size_t  size = 0;
  const REAL_B   *grd_phi; 
  int            i, j, k, dim = qfast->dim;

  if (!vec) {
    if (size < (size_t) qfast->n_points) {
      MEM_FREE(quad_vec, size, REAL_B);
      size = qfast->n_points;
      quad_vec = MEM_ALLOC(size, REAL_B);
    }
    vec = quad_vec;
  }

  for (i = 0; i < qfast->n_points; i++) {
    grd_phi = qfast->grd_phi[i];

    for (k = 0; k < N_LAMBDA(dim); k++) {
      if (!update) {
	vec[i][k] = 0.0;
      }
      for (j = 0; j < qfast->n_bas_fcts; j++)
	vec[i][k] += uh_loc->vec[j]*grd_phi[j][k];
    }
  }

  return (const REAL_B *)vec;
}

static inline const REAL_DB *
__bar_grd_uh_d_at_qp(REAL_DB vec[],
		     const QUAD_FAST *qfast,
		     const EL_REAL_D_VEC *uh_loc,
		     bool update)
{
  FUNCNAME("bar_grd_uh_d_at_qp");
  static REAL_DB *quad_vec = NULL;
  static size_t  size = 0;
  const REAL_B   *grd_phi;
  int            i, j, k, m, dim = qfast->dim;

  if (!vec) {
    if (size < (size_t)qfast->n_points) {
      MEM_FREE(quad_vec, size, REAL_DB);
      size = qfast->n_points;
      quad_vec = MEM_ALLOC(size, REAL_DB);
    }
    vec = quad_vec;
  }

  for (i = 0; i < qfast->n_points; i++) {
    grd_phi = qfast->grd_phi[i];

    for (k = 0; k < DIM_OF_WORLD; k++) {
      for (m = 0; m < N_LAMBDA(dim); m++) {
	if (!update) {
	  vec[i][k][m] = 0.0;
	}
	for (j = 0; j < qfast->n_bas_fcts; j++)
	  vec[i][k][m] += uh_loc->vec[j][k]*grd_phi[j][m];
      }
    }
  }
  return (const REAL_DB *)vec;
}

static inline const REAL_DB *
__bar_grd_uh_dow_at_qp(REAL_DB *grd_uh,
		       const QUAD_FAST *qfast,
		       const EL_REAL_VEC_D *uh_loc,
		       bool update)
{
  FUNCNAME("bar_grd_uh_dow_at_qp");
  static REAL_DB *quad_vec = NULL;
  static size_t  size = 0;
  const REAL_DB  *const*grd_phi;
  int            i, iq, n;

  if (!grd_uh) {
    if (size < (size_t)qfast->n_points) {
      MEM_FREE(quad_vec, size, REAL_DB);
      size = qfast->n_points;
      quad_vec = MEM_ALLOC(size, REAL_DB);
    }
    grd_uh = quad_vec;
  }

  if (uh_loc->stride != 1) {
    return __bar_grd_uh_d_at_qp(
      grd_uh, qfast, (const EL_REAL_D_VEC *)uh_loc, update);
  }

  grd_phi = get_quad_fast_grd_phi_dow(qfast);
  for (iq = 0; iq < qfast->n_points; iq++) {
    for (n = 0; n < DIM_OF_WORLD; n++) {
      if (!update) {
	SET_BAR(DIM_MAX, 0.0, grd_uh[iq][n]);
      }
      for (i = 0; i < qfast->n_bas_fcts; i++) {
	AXPY_BAR(DIM_MAX, uh_loc->vec[i], grd_phi[iq][i][n], grd_uh[i][n]);
      }
    }
  }
  return (const REAL_DB *)grd_uh;
}

DEFUN_CHAIN_VAL_FCT(
  bar_grd_uh_at_qp, REAL_VEC,
  uh_loc, qfast, const QUAD_FAST, grd_uh, REAL_B *, const REAL_B *,
  (grd_uh, qfast, uh_loc, false), (grd_uh, qfast, uh_loc, true),
  (REAL_B *grd_uh,
   const QUAD_FAST *qfast, const EL_REAL_VEC *uh_loc));

DEFUN_CHAIN_VAL_FCT(
  bar_grd_uh_d_at_qp, REAL_D_VEC,
  uh_loc, qfast, const QUAD_FAST, grd_uh, REAL_DB *, const REAL_DB *,
  (grd_uh, qfast, uh_loc, false), (grd_uh, qfast, uh_loc, true),
  (REAL_DB *grd_uh, const QUAD_FAST *qfast, const EL_REAL_D_VEC *uh_loc));

DEFUN_CHAIN_VAL_FCT(
  bar_grd_uh_dow_at_qp, REAL_VEC_D,
  uh_loc, qfast, const QUAD_FAST, grd_uh, REAL_DB *, const REAL_DB *,
  (grd_uh, qfast, uh_loc, false), (grd_uh, qfast, uh_loc, true),
  (REAL_DB *grd_uh, const QUAD_FAST *qfast, const EL_REAL_VEC_D *uh_loc));

/* >>> */

/* <<< first derivatives, compute a "quad-vec" */

static inline
const REAL_D *__grd_uh_at_qp(REAL_D vec[],
			     const QUAD_FAST *qfast,
			     const REAL_BD Lambda,
			     const EL_REAL_VEC *uh_loc,
			     bool update)
{
  FUNCNAME("grd_uh_at_qp");
  static REAL_D  *quad_vec = NULL;
  static size_t  size = 0;
  REAL_B         grd_bar;
  const REAL_B   *grd_phi; 
  int            i, j, k, dim = qfast->dim;

  if (!vec) {
    if (size < (size_t) qfast->n_points) {
      MEM_FREE(quad_vec, size, REAL_D);
      size = qfast->n_points;
      quad_vec = MEM_ALLOC(size, REAL_D);
    }
    vec = quad_vec;
  }

  for (i = 0; i < qfast->n_points; i++) {
    grd_phi = qfast->grd_phi[i];

    for (k = 0; k < N_LAMBDA(dim); k++) {
      grd_bar[k] = 0.0;

      for (j = 0; j < qfast->n_bas_fcts; j++)
	grd_bar[k] += uh_loc->vec[j]*grd_phi[j][k];
    }

    if (update) {
      GRAD_P_DOW(dim, Lambda, grd_bar, vec[i]);
    } else {
      GRAD_DOW(dim, Lambda, grd_bar, vec[i]);
    }
  }

  return (const REAL_D *)vec;
}

static inline const REAL_D *
__param_grd_uh_at_qp(REAL_D vec[],
		     const QUAD_FAST *qfast,
		     const REAL_BD *Lambda,
		     const EL_REAL_VEC *uh_loc,
		     bool update)
{
  FUNCNAME("__param_grd_uh_at_qp");
  static REAL_D  *quad_vec = NULL;
  static size_t  size = 0;
  const REAL_B   *grd_phi;
  REAL_B         grd_bar;
  int            i, j, k, dim = qfast->dim;

  if (!vec) {
    if (size < (size_t)qfast->n_points) {
      MEM_FREE(quad_vec, size, REAL_D);
      size = qfast->n_points;
      quad_vec = MEM_ALLOC(size, REAL_D);
    }
    vec =quad_vec;
  }

  for (i = 0; i < qfast->n_points; i++) {
    grd_phi = qfast->grd_phi[i];

    for (k = 0; k < N_LAMBDA(dim); k++) {
      grd_bar[k] = 0.0;

      for (j = 0; j < qfast->n_bas_fcts; j++)
	grd_bar[k] += uh_loc->vec[j]*grd_phi[j][k];
    }

    if (update) {
      GRAD_P_DOW(dim, Lambda[i], grd_bar, vec[i]);
    } else {
      GRAD_DOW(dim, Lambda[i], grd_bar, vec[i]);
    }
  }

  return (const REAL_D *)vec;
}

static inline const REAL_DD *
__grd_uh_d_at_qp(REAL_DD vec[],
		 const QUAD_FAST *qfast,
		 const REAL_BD Lambda, 
		 const EL_REAL_D_VEC *uh_loc,
		 bool update)
{
  FUNCNAME("grd_uh_d_at_qp");
  static REAL_DD *quad_vec = NULL;
  static size_t  size = 0;
  const REAL_B   *grd_phi;
  REAL_B         grd_bar;
  int            i, j, k, m, dim = qfast->dim;

  if (!vec) {
    if (size < (size_t)qfast->n_points) {
      MEM_FREE(quad_vec, size, REAL_DD);
      size = qfast->n_points;
      quad_vec = MEM_ALLOC(size, REAL_DD);
    }
    vec = quad_vec;
  }

  for (i = 0; i < qfast->n_points; i++) {
    grd_phi = qfast->grd_phi[i];

    for (k = 0; k < DIM_OF_WORLD; k++) {
      for (m = 0; m < N_LAMBDA(dim); m++) {
	grd_bar[m] = 0.0;

	for (j = 0; j < qfast->n_bas_fcts; j++)
	  grd_bar[m] += uh_loc->vec[j][k]*grd_phi[j][m];
      }

      if (update) {
	GRAD_P_DOW(dim, Lambda, grd_bar, vec[i][k]);
      } else {
	GRAD_DOW(dim, Lambda, grd_bar, vec[i][k]);
      }
    }
  }
  return (const REAL_DD *)vec;
}

static inline const REAL_DD *
__param_grd_uh_d_at_qp(REAL_DD vec[],
		       const QUAD_FAST *qfast,
		       const REAL_BD *Lambda,
		       const EL_REAL_D_VEC *uh_loc,
		       bool update)
{
  FUNCNAME("param_grd_uh_d_at_qp");
  static REAL_DD *quad_vec = NULL;
  static size_t  size = 0;
  const REAL_B   *grd_phi;
  REAL_B         grd_bar;
  int            i, j, k, m, dim = qfast->dim;

  if (!vec) {
    if (size < (size_t)qfast->n_points) {
      MEM_FREE(quad_vec, size, REAL_DD);
      size = qfast->n_points;
      quad_vec = MEM_ALLOC(size, REAL_DD);
    }
    vec = quad_vec;
  }

  for (i = 0; i < qfast->n_points; i++) {
    grd_phi = qfast->grd_phi[i];

    for (k = 0; k < DIM_OF_WORLD; k++) {
      for (m = 0; m < N_LAMBDA(dim); m++) {
	grd_bar[m] = 0.0;

	for (j = 0; j < qfast->n_bas_fcts; j++)
	  grd_bar[m] += uh_loc->vec[j][k]*grd_phi[j][m];
      }

      if (update) {
	GRAD_P_DOW(dim, Lambda[i], grd_bar, vec[i][k]);
      } else {
	GRAD_DOW(dim, Lambda[i], grd_bar, vec[i][k]);
      }
    }
  }

  return (const REAL_DD *)vec;
}

static inline const REAL_DD *
__grd_uh_dow_at_qp(REAL_DD *grd_uh,
		   const QUAD_FAST *qfast,
		   const REAL_BD Lambda, 
		   const EL_REAL_VEC_D *uh_loc,
		   bool update)
{
  FUNCNAME("grd_uh_dow_at_qp");
  static REAL_DD *quad_vec = NULL;
  static size_t  size = 0;
  const REAL_DB  *const*grd_phi;
  REAL_B         grd1;
  int            i, iq, n;

  if (!grd_uh) {
    if (size < (size_t)qfast->n_points) {
      MEM_FREE(quad_vec, size, REAL_DD);
      size = qfast->n_points;
      quad_vec = MEM_ALLOC(size, REAL_DD);
    }
    grd_uh = quad_vec;
  }

  if (uh_loc->stride != 1) {
    return __grd_uh_d_at_qp(
      grd_uh, qfast, Lambda, (const EL_REAL_D_VEC *)uh_loc, update);
  }

  grd_phi = get_quad_fast_grd_phi_dow(qfast);
  for (iq = 0; iq < qfast->n_points; iq++) {
    for (n = 0; n < DIM_OF_WORLD; n++) {
      SET_BAR(DIM_MAX, 0.0, grd1);
      for (i = 0; i < qfast->n_bas_fcts; i++) {
	AXPY_BAR(DIM_MAX, uh_loc->vec[i], grd_phi[iq][i][n], grd1);
      }
    
      if (update) {
	GRAD_P_DOW(DIM_MAX, Lambda, grd1, grd_uh[iq][n]);
      } else {
	GRAD_DOW(DIM_MAX, Lambda, grd1, grd_uh[iq][n]);
      }
    }
  }

  return (const REAL_DD *)grd_uh;
}

static inline const REAL_DD *
__param_grd_uh_dow_at_qp(REAL_DD *grd_uh,
		       const QUAD_FAST *qfast,
		       const REAL_BD *Lambda,
		       const EL_REAL_VEC_D *uh_loc,
		       bool update)
{
  FUNCNAME("param_grd_uh_d_at_qp");
  static REAL_DD *quad_vec = NULL;
  static size_t  size = 0;
  const REAL_DB  *const*grd_phi;
  REAL_B         grd1;
  int            i, iq, n;

  if (!grd_uh) {
    if (size < (size_t)qfast->n_points) {
      MEM_FREE(quad_vec, size, REAL_DD);
      size = qfast->n_points;
      quad_vec = MEM_ALLOC(size, REAL_DD);
    }
    grd_uh = quad_vec;
  }

  if (uh_loc->stride != 1) {
    return __param_grd_uh_d_at_qp(
      grd_uh, qfast, Lambda, (const EL_REAL_D_VEC *)uh_loc, update);
  }

  grd_phi = get_quad_fast_grd_phi_dow(qfast);
  for (iq = 0; iq < qfast->n_points; iq++) {
    for (n = 0; n < DIM_OF_WORLD; n++) {
      SET_BAR(DIM_MAX, 0.0, grd1);
      for (i = 0; i < qfast->n_bas_fcts; i++) {
	AXPY_BAR(DIM_MAX, uh_loc->vec[i], grd_phi[iq][i][n], grd1);
      }
    
      if (update) {
	GRAD_P_DOW(DIM_MAX, Lambda[iq], grd1, grd_uh[iq][n]);
      } else {
	GRAD_DOW(DIM_MAX, Lambda[iq], grd1, grd_uh[iq][n]);
      }
    }
  }

  return (const REAL_DD *)grd_uh;
}

static inline const REAL *
__div_uh_d_at_qp(REAL vec[], const QUAD_FAST *qfast,
		 const REAL_BD Lambda, 
		 const EL_REAL_D_VEC *uh_loc,
		 bool update)
{
  FUNCNAME("div_uh_d_at_qp");
  static REAL   *quad_vec = NULL;
  static size_t size = 0;
  REAL          div;
  const REAL_B  *grd_phi;
  REAL_B        grd1;
  int           i, j, n, m, dim = qfast->dim;

  if (!vec) {
    if (size < (size_t)qfast->n_points) {
      MEM_FREE(quad_vec, size, REAL);
      quad_vec = MEM_ALLOC(size, REAL);
    }
    vec = quad_vec;
  }

  for (m = 0; m < qfast->n_points; m++) {
    grd_phi = qfast->grd_phi[m];

    for (div = n = 0; n < DIM_OF_WORLD; n++) {
      for (j = 0; j < N_LAMBDA(dim); j++) {
	grd1[j] = 0.0;

	for (i = 0; i < qfast->n_bas_fcts; i++)
	  grd1[j] += uh_loc->vec[i][n]*grd_phi[i][j];
      }

      for (j = 0; j < N_LAMBDA(dim); j++)
	div += Lambda[j][n]*grd1[j];
    }
    if (update) {
      vec[m] += div;
    } else {
      vec[m] = div;
    }
  }

  return (const REAL *)vec;
}

static inline const REAL *
__param_div_uh_d_at_qp(REAL vec[],
		       const QUAD_FAST *qfast,
		       const REAL_BD *Lambda, 
		       const EL_REAL_D_VEC *uh_loc,
		       bool update)
{
  FUNCNAME("__param_div_uh_d_at_qp");
  static REAL   *quad_vec = NULL;
  static size_t size = 0;
  REAL          div;
  const REAL_B  *grd_phi;
  REAL_B        grd1;
  int           i, j, n, m, dim = qfast->dim;

  if (!vec) {
    if (size < (size_t)qfast->n_points) {
      MEM_FREE(quad_vec, size, REAL);
      size = qfast->n_points;
      quad_vec = MEM_ALLOC(size, REAL);
    }
    vec = quad_vec;
  }

  for (m = 0; m < qfast->n_points; m++)   {
    grd_phi = qfast->grd_phi[m];

    for (div = n = 0; n < DIM_OF_WORLD; n++) {
      for (j = 0; j < N_LAMBDA(dim); j++) {
	grd1[j] = 0.0;

	for (i = 0; i < qfast->n_bas_fcts; i++)
	  grd1[j] += uh_loc->vec[i][n]*grd_phi[i][j];
      }

      for (j = 0; j < N_LAMBDA(dim); j++)
	div += Lambda[m][j][n]*grd1[j];
    }
    if (update) {
      vec[m] += div;
    } else {
      vec[m] = div;
    }
  }

  return (const REAL *)vec;
}

static inline const REAL *
__div_uh_dow_at_qp(REAL vec[],
		   const QUAD_FAST *qfast,
		   const REAL_BD Lambda, 
		   const EL_REAL_VEC_D *uh_loc,
		   bool update)
{
  FUNCNAME("__div_uh_d_at_qp");
  static REAL   *quad_vec = NULL;
  static size_t size = 0;
  const REAL_DB *const*grd_phi;
  int iq, i, n, j;
  REAL div;
  REAL_B grd1;

  if (!vec) {
    if (size < (size_t)qfast->n_points) {
      MEM_FREE(quad_vec, size, REAL);
      quad_vec = MEM_ALLOC(size, REAL);
    }
    vec = quad_vec;
  }

  if (uh_loc->stride != 1) {
    return __div_uh_d_at_qp(
      vec, qfast, Lambda, (const EL_REAL_D_VEC *)uh_loc, update);
  }

  grd_phi = get_quad_fast_grd_phi_dow(qfast);
  for (iq = 0; iq < qfast->n_points; iq++) {
    for (div = n = 0; n < DIM_OF_WORLD; n++) {
      for (j = 0; j < N_LAMBDA_MAX; j++) {
	grd1[j] = 0.0;
	
	for (i = 0; i < qfast->n_bas_fcts; i++) {
	  grd1[j] += uh_loc->vec[i]*grd_phi[iq][i][n][j];
	}
      }
      
      for (j = 0; j < N_LAMBDA_MAX; j++) {
	div += Lambda[j][n]*grd1[j];
      }
    }
    if (update) {
      vec[iq] += div;
    } else {
      vec[iq] = div;
    }
  }
  
  return (const REAL *)vec;
}

static inline const REAL *
__param_div_uh_dow_at_qp(REAL vec[],
			 const QUAD_FAST *qfast,
			 const REAL_BD *Lambda, 
			 const EL_REAL_VEC_D *uh_loc,
			 bool update)
{
  FUNCNAME("__param_div_uh_dow_at_qp");
  static REAL   *quad_vec = NULL;
  static size_t size = 0;
  const REAL_DB *const*grd_phi;
  REAL_B        grd1;
  int           i, iq, n, j;

  if (!vec) {
    if (size < (size_t)qfast->n_points) {
      MEM_FREE(quad_vec, size, REAL);
      size = qfast->n_points;
      quad_vec = MEM_ALLOC(size, REAL);
    }
    vec = quad_vec;
  }

  if (uh_loc->stride != 1) {
    return __param_div_uh_d_at_qp(
      vec, qfast, Lambda, (const EL_REAL_D_VEC *)uh_loc, update);
  }

  grd_phi = get_quad_fast_grd_phi_dow(qfast);
  for (iq = 0; iq < qfast->n_points; iq++) {
    REAL div = 0.0;
    for (n = 0; n < DIM_OF_WORLD; n++) {
      for (j = 0; j < N_LAMBDA_MAX; j++) {
	grd1[j] = 0.0;

	for (i = 0; i < qfast->n_bas_fcts; i++) {
	  grd1[j] += uh_loc->vec[i]*grd_phi[iq][i][n][j];
	}
      }

      for (j = 0; j < N_LAMBDA_MAX; j++) {
	div += Lambda[iq][j][n]*grd1[j];
      }
    }
    if (update) {
      vec[iq] += div;
    } else {
      vec[iq] = div;
    }
  }
  
  return (const REAL *)vec;
}

DEFUN_CHAIN_VAL_FCT(
  grd_uh_at_qp, REAL_VEC,
  uh_loc, qfast, const QUAD_FAST, grd_uh, REAL_D *, const REAL_D *,
  (grd_uh, qfast, Lambda, uh_loc, false), (grd_uh, qfast, Lambda, uh_loc, true),
  (REAL_D *grd_uh,
   const QUAD_FAST *qfast, const REAL_BD Lambda, const EL_REAL_VEC *uh_loc));

DEFUN_CHAIN_VAL_FCT(
  param_grd_uh_at_qp, REAL_VEC,
  uh_loc, qfast, const QUAD_FAST, grd_uh, REAL_D *, const REAL_D *,
  (grd_uh, qfast, Lambda, uh_loc, false), (grd_uh, qfast, Lambda, uh_loc, true),
  (REAL_D *grd_uh,
   const QUAD_FAST *qfast, const REAL_BD Lambda[], const EL_REAL_VEC *uh_loc));

DEFUN_CHAIN_VAL_FCT(
  grd_uh_d_at_qp, REAL_D_VEC,
  uh_loc, qfast, const QUAD_FAST, grd_uh, REAL_DD *, const REAL_DD *,
  (grd_uh, qfast, Lambda, uh_loc, false), (grd_uh, qfast, Lambda, uh_loc, true),
  (REAL_DD *grd_uh, const QUAD_FAST *qfast,
   const REAL_BD Lambda, const EL_REAL_D_VEC *uh_loc));

DEFUN_CHAIN_VAL_FCT(
  param_grd_uh_d_at_qp, REAL_D_VEC,
  uh_loc, qfast, const QUAD_FAST, grd_uh, REAL_DD *, const REAL_DD *,
  (grd_uh, qfast, Lambda, uh_loc, false), (grd_uh, qfast, Lambda, uh_loc, true),
  (REAL_DD *grd_uh,
   const QUAD_FAST *qfast, const REAL_BD Lambda[],
   const EL_REAL_D_VEC *uh_loc));

DEFUN_CHAIN_VAL_FCT(
  grd_uh_dow_at_qp, REAL_VEC_D,
  uh_loc, qfast, const QUAD_FAST, grd_uh, REAL_DD *, const REAL_DD *,
  (grd_uh, qfast, Lambda, uh_loc, false), (grd_uh, qfast, Lambda, uh_loc, true),
  (REAL_DD *grd_uh, const QUAD_FAST *qfast,
   const REAL_BD Lambda, const EL_REAL_VEC_D *uh_loc));

DEFUN_CHAIN_VAL_FCT(
  param_grd_uh_dow_at_qp, REAL_VEC_D,
  uh_loc, qfast, const QUAD_FAST, grd_uh, REAL_DD *, const REAL_DD *,
  (grd_uh, qfast, Lambda, uh_loc, false), (grd_uh, qfast, Lambda, uh_loc, true),
  (REAL_DD *grd_uh,
   const QUAD_FAST *qfast, const REAL_BD Lambda[],
   const EL_REAL_VEC_D *uh_loc));

DEFUN_CHAIN_VAL_FCT(
  div_uh_d_at_qp, REAL_D_VEC,
  uh_loc, qfast, const QUAD_FAST, val, REAL *, const REAL *,
  (vec, qfast, Lambda, uh_loc, false), (vec, qfast, Lambda, uh_loc, true),
  (REAL vec[],
   const QUAD_FAST *qfast, const REAL_BD Lambda, const EL_REAL_D_VEC *uh_loc));

DEFUN_CHAIN_VAL_FCT(
  param_div_uh_d_at_qp, REAL_D_VEC,
  uh_loc, qfast, const QUAD_FAST, val, REAL *, const REAL *,
  (vec, qfast, Lambda, uh_loc, false), (vec, qfast, Lambda, uh_loc, true),
  (REAL vec[], const QUAD_FAST *qfast,
   const REAL_BD Lambda[], const EL_REAL_D_VEC *uh_loc));

DEFUN_CHAIN_VAL_FCT(
  div_uh_dow_at_qp, REAL_VEC_D,
  uh_loc, qfast, const QUAD_FAST, val, REAL *, const REAL *,
  (vec, qfast, Lambda, uh_loc, false), (vec, qfast, Lambda, uh_loc, true),
  (REAL vec[],
   const QUAD_FAST *qfast, const REAL_BD Lambda, const EL_REAL_VEC_D *uh_loc));

DEFUN_CHAIN_VAL_FCT(
  param_div_uh_dow_at_qp, REAL_VEC_D,
  uh_loc, qfast, const QUAD_FAST, val, REAL *, const REAL *,
  (vec, qfast, Lambda, uh_loc, false), (vec, qfast, Lambda, uh_loc, true),
  (REAL vec[], const QUAD_FAST *qfast,
   const REAL_BD Lambda[], const EL_REAL_VEC_D *uh_loc));

/* >>> */

/* >>> */

/* <<< Second derivatives */

/* <<< slow evaluation, barycentric derivatives */

static inline const REAL_B *
__eval_bar_D2_uh(REAL_BB D2_uh,
		 const REAL_B lambda,
		 const EL_REAL_VEC *uh_loc, const BAS_FCTS *b,
		 bool update)
{
  static REAL_BB D2;
  int            i;

  if (D2_uh == NULL) {
    D2_uh = D2;
  }
  if (!update) {
    MSET_BAR(DIM_MAX, 0.0, D2_uh);
  }
  for (i = 0; i < b->n_bas_fcts; i++) {
    MAXPY_BAR(DIM_MAX, uh_loc->vec[i], (const REAL_B *)D2_PHI(b, i, lambda), D2_uh);
  }
  return (const REAL_B *)D2_uh;
}

static inline const REAL_BB *
__eval_bar_D2_uh_d(REAL_DBB D2_uh,
		   const REAL_B lambda,
		   const EL_REAL_D_VEC *uh_loc, const BAS_FCTS *b,
		   bool update)
{
  static REAL_DBB D2;
  const REAL_B    *D2_b;
  int             i, n, dim = b->dim;
  
  if (D2_uh == NULL) {
    D2_uh = D2;
  }
  if (!update) {
    for (n = 0; n < DIM_OF_WORLD; n++) {
      MSET_BAR(DIM_MAX, 0.0, D2_uh[n]);
    }
  }
  for (i = 0; i < b->n_bas_fcts; i++) {
    D2_b = D2_PHI(b, i, lambda);
    for (n = 0; n < DIM_OF_WORLD; n++) {
      MAXPY_BAR(dim, uh_loc->vec[i][n], D2_b, D2_uh[n]);
    }
  }
  return (const REAL_BB *)D2_uh;
}

static inline const REAL_BB *
__eval_bar_D2_uh_dow(REAL_DBB D2_uh,
		     const REAL_B lambda,
		     const EL_REAL_VEC_D *uh_loc, const BAS_FCTS *b,
		     bool update)
{
  static REAL_DBB D2;
  const REAL_B    *D2_b;
  const REAL      *phi_d;
  int             i, k, l, n, dim = b->dim;

  if (D2_uh == NULL) {
    D2_uh = D2;
  }
  if (uh_loc->stride != 1) {
    return __eval_bar_D2_uh_d(
      D2_uh, lambda, (const EL_REAL_D_VEC *)uh_loc, b, update);
  }

  if (!update) {
    for (n = 0; n < DIM_OF_WORLD; n++) {
      MSET_BAR(DIM_MAX, 0.0, D2_uh[n]);
    }
  }
  if (!b->dir_pw_const) {
    REAL_BB tmp;
    REAL phi;
    const REAL    *grd_phi;
    const REAL_B  *grd_phi_d;
    const REAL_BB *D2_phi_d;

    /* The slow and complicated case ... */
    for (i = 0; i < b->n_bas_fcts; i++) {
      phi       = PHI(b, i, lambda);
      grd_phi   = GRD_PHI(b, i, lambda);
      grd_phi_d = GRD_PHI_D(b, i, lambda);
      D2_phi_d  = D2_PHI_D(b, i, lambda);

      for (n = 0; n < DIM_OF_WORLD; n++) {
	MAXEY_BAR(dim, phi, D2_phi_d[n], tmp);
	for (k = 0; k < N_LAMBDA(dim); k++) {
	  for (l = k; l < N_LAMBDA(dim); l++) {
	    tmp[k][l] +=
	      grd_phi[k]*grd_phi_d[n][l] + grd_phi[l]*grd_phi_d[n][k];
	  }
	}
	MAXPY_BAR(dim, uh_loc->vec[i], (const REAL_B *)tmp, D2_uh[n]);
      }
    }
  }
  
  for (i = 0; i < b->n_bas_fcts; i++) {
    D2_b   = D2_PHI(b, i, lambda);
    phi_d  = PHI_D(b, i, lambda);
 
    for (n = 0; n < DIM_OF_WORLD; n++) {
      MAXPY_BAR(dim, uh_loc->vec[i]*phi_d[n], D2_b, D2_uh[n]);
    }
  }
  return (const REAL_BB *)D2_uh;
}

DEFUN_CHAIN_VAL_FCT(
  eval_bar_D2_uh, REAL_VEC,
  uh_loc, b, const BAS_FCTS, D2_uh, REAL_B *, const REAL_B *,
  (D2_uh, lambda, uh_loc, b, false),
  (D2_uh, lambda, uh_loc, b, true),
  (REAL_BB D2_uh,
   const REAL_B lambda, const EL_REAL_VEC *uh_loc, const BAS_FCTS *b));

DEFUN_CHAIN_VAL_FCT(
  eval_bar_D2_uh_d, REAL_D_VEC,
  uh_loc, b, const BAS_FCTS, D2_uh, REAL_BB *, const REAL_BB *,
  (D2_uh, lambda, uh_loc, b, false),
  (D2_uh, lambda, uh_loc, b, true),
  (REAL_DBB D2_uh,
   const REAL_B lambda, const EL_REAL_D_VEC *uh_loc, const BAS_FCTS *b));

DEFUN_CHAIN_VAL_FCT(
  eval_bar_D2_uh_dow, REAL_VEC_D,
  uh_loc, b, const BAS_FCTS, D2_uh, REAL_BB *, const REAL_BB *,
  (D2_uh, lambda, uh_loc, b, false),
  (D2_uh, lambda, uh_loc, b, true),
  (REAL_DBB D2_uh,
   const REAL_B lambda, const EL_REAL_VEC_D *uh_loc, const BAS_FCTS *b));

/* >>> */

/* <<< slow evaluation */

static inline const REAL_D *
__eval_D2_uh(REAL_DD D2_uh,
	     const REAL_B lambda,
	     const REAL_BD Lambda,
	     const EL_REAL_VEC *uh_loc, const BAS_FCTS *b,
	     bool update)
{
  static REAL_DD D2;
  const REAL_B   *D2_b;
  int            i, k, l, dim = b->dim;
  REAL_D         *val;
  REAL_BB        D2_tmp = {{0}};

  val = D2_uh ? D2_uh : D2;
  
  for (i = 0; i < b->n_bas_fcts; i++)
  {
    D2_b = D2_PHI(b, i, lambda);     
    for (k = 0; k < N_LAMBDA(dim); k++)
      for (l = k; l < N_LAMBDA(dim); l++)
      D2_tmp[k][l] += uh_loc->vec[i]*D2_b[k][l];
  }

  if (update) {
    D2_P_DOW(DIM_MAX, Lambda, (const REAL_B *)D2_tmp, val);
  } else {
    D2_DOW(DIM_MAX, Lambda, (const REAL_B *)D2_tmp, val);
  }
  
  return (const REAL_D *)val;
}

static inline const REAL_DD *
__eval_D2_uh_d(REAL_DDD D2_uh,
	       const REAL_B lambda,
	       const REAL_BD Lambda,
	       const EL_REAL_D_VEC *uh_loc, const BAS_FCTS *b,
	       bool update)
{
  static REAL_DDD D2;
  const REAL_B    *D2_b;
  int             i, n, dim = b->dim;
  REAL_DBB        D21;
  REAL_DD         *val;
  
  val = D2_uh ? D2_uh : D2;
  
  for (i = 0; i < b->n_bas_fcts; i++) {
    D2_b = D2_PHI(b, i, lambda);
    for (n = 0; n < DIM_OF_WORLD; n++) {
      MAXPY_BAR(dim, uh_loc->vec[i][n], D2_b, D21[n]);
    }
  }

  if (update) {
    MD2_P_DOW(DIM_MAX, Lambda, (const REAL_BB *)D21, val);
  } else {
    MD2_DOW(DIM_MAX, Lambda, (const REAL_BB *)D21, val);
  }
  
  return (const REAL_DD *)val;
}

static inline const REAL_DD *
__eval_D2_uh_dow(REAL_DDD D2_uh,
		 const REAL_B lambda,
		 const REAL_BD Lambda,
		 const EL_REAL_VEC_D *uh_loc, const BAS_FCTS *b,
		 bool update)
{
  static REAL_DDD D2;
  const REAL_B    *D2_b;
  const REAL      *phi_d;
  int             i, k, l, n, dim = b->dim;
  REAL_DBB        D2_tmp = { { { 0.0, } } };	

  if (D2_uh == NULL) {
    D2_uh = D2;
  }

  if (uh_loc->stride != 1) {
    return __eval_D2_uh_d(
      D2_uh, lambda, Lambda, (const EL_REAL_D_VEC *)uh_loc, b, update);
  }

  if (!b->dir_pw_const) {
    REAL_BB tmp;
    REAL phi;
    const REAL    *grd_phi;
    const REAL_B  *grd_phi_d;
    const REAL_BB *D2_phi_d;

    /* The slow and complicated case ... */
    for (i = 0; i < b->n_bas_fcts; i++) {
      phi       = PHI(b, i, lambda);
      grd_phi   = GRD_PHI(b, i, lambda);
      grd_phi_d = GRD_PHI_D(b, i, lambda);
      D2_phi_d  = D2_PHI_D(b, i, lambda);

      for (n = 0; n < DIM_OF_WORLD; n++) {
	MAXEY_BAR(dim, phi, D2_phi_d[n], tmp);
	for (k = 0; k < N_LAMBDA(dim); k++) {
	  for (l = k; l < N_LAMBDA(dim); l++) {
	    tmp[k][l] +=
	      grd_phi[k]*grd_phi_d[n][l] + grd_phi[l]*grd_phi_d[n][k];
	  }
	}
	MAXPY_BAR(dim, uh_loc->vec[i], (const REAL_B *)tmp, D2_tmp[n]);
      }
    }
  }
  
  for (i = 0; i < b->n_bas_fcts; i++) {
    D2_b   = D2_PHI(b, i, lambda);
    phi_d  = PHI_D(b, i, lambda);
 
    for (n = 0; n < DIM_OF_WORLD; n++) {
      MAXPY_BAR(dim, uh_loc->vec[i]*phi_d[n], D2_b, D2_tmp[n]);
    }
  }

  if (update) {
    MD2_P_DOW(dim, Lambda, (const REAL_BB *)D2_tmp, D2_uh);
  } else {
    MD2_DOW(dim, Lambda, (const REAL_BB *)D2_tmp, D2_uh);
  }

  return (const REAL_DD *)D2_uh;
}

DEFUN_CHAIN_VAL_FCT(
  eval_D2_uh, REAL_VEC,
  uh_loc, b, const BAS_FCTS, D2_uh, REAL_D *, const REAL_D *,
  (D2_uh, lambda, Lambda, uh_loc, b, false),
  (D2_uh, lambda, Lambda, uh_loc, b, true),
  (REAL_DD D2_uh, const REAL_B lambda, const REAL_BD Lambda,
   const EL_REAL_VEC *uh_loc, const BAS_FCTS *b));

DEFUN_CHAIN_VAL_FCT(
  eval_D2_uh_d, REAL_D_VEC,
  uh_loc, b, const BAS_FCTS, D2_uh, REAL_DD *, const REAL_DD *,
  (D2_uh, lambda, Lambda, uh_loc, b, false),
  (D2_uh, lambda, Lambda, uh_loc, b, true),
  (REAL_DDD D2_uh, const REAL_B lambda, const REAL_BD Lambda,
   const EL_REAL_D_VEC *uh_loc, const BAS_FCTS *b));

DEFUN_CHAIN_VAL_FCT(
  eval_D2_uh_dow, REAL_VEC_D,
  uh_loc, b, const BAS_FCTS, D2_uh, REAL_DD *, const REAL_DD *,
  (D2_uh, lambda, Lambda, uh_loc, b, false),
  (D2_uh, lambda, Lambda, uh_loc, b, true),
  (REAL_DDD D2_uh, const REAL_B lambda, const REAL_BD Lambda,
   const EL_REAL_VEC_D *uh_loc, const BAS_FCTS *b));

/* >>> */

/* <<< "fast" evaluation at a single vertex, barycentric derivatives */

static inline
const REAL_B *
__eval_bar_D2_uh_fast(REAL_BB D2_uh,
		      const EL_REAL_VEC *uh_loc, 
		      const QUAD_FAST *qfast, int iq,
		      bool update)
{
  static REAL_BB D2;
  int            i;
  const REAL_BB  *D2_phi = qfast->D2_phi[iq];

  if (D2_uh == NULL) {
    D2_uh = D2;
  }
  if (!update) {
    MSET_BAR(DIM_MAX, 0.0, D2_uh);
  }
  for (i = 0; i < qfast->n_bas_fcts; i++) {
    MAXPY_BAR(DIM_MAX, uh_loc->vec[i], D2_phi[i], D2_uh);
  }
  return (const REAL_B *)D2_uh;
}

static inline
const REAL_BB *
__eval_bar_D2_uh_d_fast(REAL_DBB D2_uh,
			const EL_REAL_D_VEC *uh_loc, 
			const QUAD_FAST *qfast, int iq,
			bool update)
{
  static REAL_DBB D2;
  int             i,  n;
  const REAL_BB   *D2_phi = qfast->D2_phi[iq];

  if (D2_uh == NULL) {
    D2_uh = D2;
  }
  for (n = 0; n < DIM_OF_WORLD; n++) {
    if (!update) {
      MSET_BAR(DIM_MAX, 0.0, D2_uh[n]);
    }
    for (i = 0; i < qfast->n_bas_fcts; i++) {
      MAXPY_BAR(DIM_MAX, uh_loc->vec[i][n], D2_phi[i], D2_uh[n]);
    }
  }
  return (const REAL_BB *)D2_uh;
}

static inline const
REAL_BB *
__eval_bar_D2_uh_dow_fast(REAL_DBB D2_uh,
			  const EL_REAL_VEC_D *uh_loc, 
			  const QUAD_FAST *qfast, int iq,
			  bool update)
{
  static REAL_DBB D2_space;
  int             i, n;
  const REAL_DBB  *D2_phi;

  if (D2_uh == NULL) {
    D2_uh = D2_space;
  }
  if (uh_loc->stride != 1) {
    return __eval_bar_D2_uh_d_fast(D2_uh, (const EL_REAL_D_VEC *)uh_loc,
				   qfast, iq, update);
  }

  D2_phi = get_quad_fast_D2_phi_dow(qfast)[iq];

  for (n = 0; n < DIM_OF_WORLD; n++) {
    if (!update) {
      MSET_BAR(DIM_MAX, 0.0, D2_uh[n]);
    }
    for (i = 0; i < qfast->n_bas_fcts; i++) {
      MAXPY_BAR(DIM_MAX, uh_loc->vec[i], D2_phi[i][n], D2_uh[n]);
    }
  }

  return (const REAL_BB *)D2_uh;
}

DEFUN_CHAIN_VAL_FCT(
  eval_bar_D2_uh_fast, REAL_VEC,
  uh_loc, qfast, const QUAD_FAST, D2_uh, REAL_B *, const REAL_B *,
  (D2_uh, uh_loc, qfast, iq, false),
  (D2_uh, uh_loc, qfast, iq, true),
  (REAL_BB D2_uh, const EL_REAL_VEC *uh_loc, const QUAD_FAST *qfast, int iq));

DEFUN_CHAIN_VAL_FCT(
  eval_bar_D2_uh_d_fast, REAL_D_VEC,
  uh_loc, qfast, const QUAD_FAST, D2_uh, REAL_BB *, const REAL_BB *,
  (D2_uh, uh_loc, qfast, iq, false), (D2_uh, uh_loc, qfast, iq, true),
  (REAL_DBB D2_uh,
   const EL_REAL_D_VEC *uh_loc, const QUAD_FAST *qfast, int iq));

DEFUN_CHAIN_VAL_FCT(
  eval_bar_D2_uh_dow_fast, REAL_VEC_D,
  uh_loc, qfast, const QUAD_FAST, D2_uh, REAL_BB *, const REAL_BB *,
  (D2_uh, uh_loc, qfast, iq, false), (D2_uh, uh_loc, qfast, iq, true),
  (REAL_DBB D2_uh,
   const EL_REAL_VEC_D *uh_loc, const QUAD_FAST *qfast, int iq));

/* >>> */

/* <<< "fast" evaluation at a single vertex */

static inline
const REAL_D *
__eval_D2_uh_fast(REAL_DD D2_uh,
		  const REAL_BD Lambda, 
		  const EL_REAL_VEC *uh_loc, 
		  const QUAD_FAST *qfast, int iq,
		  bool update)
{
  static REAL_DD D2;
  int            i, k, l;
  REAL_BB        D2_tmp = {{0}};
  const REAL_BB  *D2_phi = qfast->D2_phi[iq];
  
  D2_uh = D2_uh ? D2_uh : D2;

  for (i = 0; i < qfast->n_bas_fcts; i++)
    for (k = 0; k < N_LAMBDA_MAX; k++)
      for (l = k; l < N_LAMBDA_MAX; l++)
	D2_tmp[k][l] += uh_loc->vec[i]*D2_phi[i][k][l];

  if (update) {
    D2_P_DOW(DIM_MAX, Lambda, (const REAL_B *)D2_tmp, D2_uh);
  } else {    
    D2_DOW(DIM_MAX, Lambda, (const REAL_B *)D2_tmp, D2_uh);
  }

  return (const REAL_D *)D2_uh;
}

static inline
const REAL_DD *
__eval_D2_uh_d_fast(REAL_DDD D2_uh,
		    const REAL_BD Lambda, 
		    const EL_REAL_D_VEC *uh_loc, 
		    const QUAD_FAST *qfast, int iq,
		    bool update)
{
  static REAL_DDD D2;
  int             i, k, l, n;
  REAL_BB         D2_tmp;
  const REAL_BB   *D2_phi = qfast->D2_phi[iq];

  D2_uh = D2_uh ? D2_uh : D2;
 
  for (n = 0; n < DIM_OF_WORLD; n++)
  {
    for (k = 0; k < N_LAMBDA_MAX; k++)
      for (l = k; l < N_LAMBDA_MAX; l++) {
	D2_tmp[k][l] = 0.0;
	
	for (i = 0; i < qfast->n_bas_fcts; i++)
	  D2_tmp[k][l] += uh_loc->vec[i][n]*D2_phi[i][k][l];
      }

    if (update) {
      D2_P_DOW(DIM_MAX, Lambda, (const REAL_B *)D2_tmp, D2_uh[n]);
    } else {
      D2_DOW(DIM_MAX, Lambda, (const REAL_B *)D2_tmp, D2_uh[n]);
    }
  }
  return (const REAL_DD *)D2_uh;
}

static inline const
REAL_DD *
__eval_D2_uh_dow_fast(REAL_DDD D2_uh,
		      const REAL_BD Lambda, 
		      const EL_REAL_VEC_D *uh_loc, 
		      const QUAD_FAST *qfast, int iq,
		      bool update)
{
  static REAL_DDD D2_space;
  int            i, n;
  REAL_BB        D2_tmp;
  const REAL_DBB *D2_phi;

  D2_uh = D2_uh ? D2_uh : D2_space;

  if (uh_loc->stride != 1) {
    return __eval_D2_uh_d_fast(D2_uh, Lambda, (const EL_REAL_D_VEC *)uh_loc,
			       qfast, iq, update);
  }

  D2_phi = get_quad_fast_D2_phi_dow(qfast)[iq];

  for (n = 0; n < DIM_OF_WORLD; n++) {
    MSET_BAR(DIM_MAX, 0.0, D2_tmp);
    for (i = 0; i < qfast->n_bas_fcts; i++) {
      MAXPY_BAR(DIM_MAX, uh_loc->vec[i], D2_phi[i][n], D2_tmp);
    }
    if (update) {
      D2_P_DOW(DIM_MAX, Lambda, (const REAL_B *)D2_tmp, D2_uh[n]);
    } else {
      D2_DOW(DIM_MAX, Lambda, (const REAL_B *)D2_tmp, D2_uh[n]);
    }
  }

  return (const REAL_DD *)D2_uh;
}

DEFUN_CHAIN_VAL_FCT(
  eval_D2_uh_fast, REAL_VEC,
  uh_loc, qfast, const QUAD_FAST, D2_uh, REAL_D *, const REAL_D *,
  (D2_uh, Lambda, uh_loc, qfast, iq, false),
  (D2_uh, Lambda, uh_loc, qfast, iq, true),
  (REAL_DD D2_uh, const REAL_BD Lambda, const EL_REAL_VEC *uh_loc,
   const QUAD_FAST *qfast, int iq));

DEFUN_CHAIN_VAL_FCT(
  eval_D2_uh_d_fast, REAL_D_VEC,
  uh_loc, qfast, const QUAD_FAST, D2_uh, REAL_DD *, const REAL_DD *,
  (D2_uh, Lambda, uh_loc, qfast, iq, false),
  (D2_uh, Lambda, uh_loc, qfast, iq, true),
  (REAL_DDD D2_uh, const REAL_BD Lambda, const EL_REAL_D_VEC *uh_loc,
   const QUAD_FAST *qfast, int iq));

DEFUN_CHAIN_VAL_FCT(
  eval_D2_uh_dow_fast, REAL_VEC_D,
  uh_loc, qfast, const QUAD_FAST, D2_uh, REAL_DD *, const REAL_DD *,
  (D2_uh, Lambda, uh_loc, qfast, iq, false),
  (D2_uh, Lambda, uh_loc, qfast, iq, true),
  (REAL_DDD D2_uh, const REAL_BD Lambda, const EL_REAL_VEC_D *uh_loc,
   const QUAD_FAST *qfast, int iq));

/* >>> */

/* <<< all quad-points, barycentric derivatives */

static inline
const REAL_BB *
__bar_D2_uh_at_qp(REAL_BB vec[],
		  const QUAD_FAST *qfast,
		  const EL_REAL_VEC *uh_loc,
		  bool update)
{
  FUNCNAME("__bar_D2_uh_at_qp");
  static REAL_BB *quad_vec = NULL;
  static size_t  size = 0;
  const REAL_BB  *D2_phi;
  int            i, iq;
  int            dim = qfast->dim;

  if (!vec) {
    if (size < (size_t)qfast->n_points) {
      MEM_FREE(quad_vec, size, REAL_BB);
      size = qfast->n_points;
      quad_vec = MEM_ALLOC(size, REAL_BB);
    }
    vec = quad_vec;
  }

  for (iq = 0; iq < qfast->n_points; iq++) {
    D2_phi = qfast->D2_phi[iq];
    if (!update) {
      MSET_BAR(DIM_MAX, 0.0, vec[iq]);
    }
    for (i = 0; i < qfast->n_bas_fcts; i++) {
      MAXPY_BAR(dim, uh_loc->vec[i], D2_phi[i], vec[iq]);
    }
  }
  return (const REAL_BB *)vec;
}

static inline
const REAL_DBB *
__bar_D2_uh_d_at_qp(REAL_DBB vec[],
		    const QUAD_FAST *qfast,
		    const EL_REAL_D_VEC *uh_loc,
		    bool update)
{
  FUNCNAME("bar_D2_uh_d_at_qp");
  static REAL_DBB *quad_vec = NULL;
  static size_t   size = 0;
  const REAL_BB   *D2_phi;
  int             i, n, iq, dim = qfast->dim;

  if (!vec) {
    if (size < (size_t)qfast->n_points) {
      MEM_FREE(quad_vec, size, REAL_DBB);
      size = qfast->n_points;
      quad_vec = MEM_ALLOC(size, REAL_DBB);
    }
    vec = quad_vec;
  }

  for (iq = 0; iq < qfast->n_points; iq++) {
    D2_phi = qfast->D2_phi[iq];

    for (n = 0; n < DIM_OF_WORLD; n++) {
      if (!update) {
	MSET_BAR(DIM_MAX, 0.0, vec[iq][n]);
      }
      for (i = 0; i < qfast->n_bas_fcts; i++) {
	MAXPY_BAR(dim, uh_loc->vec[i][n], D2_phi[i], vec[iq][n]);
      }
    }
  }

  return (const REAL_DBB *)vec;
}

static inline
const REAL_DBB *
__bar_D2_uh_dow_at_qp(REAL_DBB vec[],
		      const QUAD_FAST *qfast,
		      const EL_REAL_VEC_D *uh_loc, 
		      bool update)
{
  FUNCNAME("__bar_D2_uh_d_at_qp");
  static REAL_DBB *quad_vec = NULL;
  static size_t   size = 0;
  const REAL_DBB  *const*D2_phi;
  int             iq, ib, n;

  if (!vec) {
    if (size < (size_t)qfast->n_points) {
      MEM_FREE(quad_vec, size, REAL_DBB);
      size = qfast->n_points;
      quad_vec = MEM_ALLOC(size, REAL_DBB);
    }
    vec = quad_vec;
  }

  if (uh_loc->stride != 1) {
    return __bar_D2_uh_d_at_qp(
      vec, qfast, (const EL_REAL_D_VEC *)uh_loc, update);
  }

  D2_phi = get_quad_fast_D2_phi_dow(qfast);
  for (iq = 0; iq < qfast->n_points; iq++) {
    for (n = 0; n < DIM_OF_WORLD; n++) {
      if (!update) {
	MSET_BAR(DIM_MAX, 0.0, vec[iq][n]);
      }
      for (ib = 0; ib < qfast->n_bas_fcts; ib++) {
	MAXPY_BAR(DIM_MAX, uh_loc->vec[ib], D2_phi[iq][ib][n], vec[iq][n]);
      }
    }
  }

  return (const REAL_DBB *)vec;
}

DEFUN_CHAIN_VAL_FCT(
  bar_D2_uh_at_qp, REAL_VEC,
  uh_loc, qfast, const QUAD_FAST, D2_uh, REAL_BB *, const REAL_BB *,
  (D2_uh, qfast, uh_loc, false), (D2_uh, qfast, uh_loc, true),
  (REAL_BB *D2_uh, const QUAD_FAST *qfast, const EL_REAL_VEC *uh_loc));

DEFUN_CHAIN_VAL_FCT(
  bar_D2_uh_d_at_qp, REAL_D_VEC,
  uh_loc, qfast, const QUAD_FAST, D2_uh, REAL_DBB *, const REAL_DBB *,
  (D2_uh, qfast, uh_loc, false), (D2_uh, qfast, uh_loc, true),
  (REAL_DBB D2_uh[], const QUAD_FAST *qfast, const EL_REAL_D_VEC *uh_loc));

DEFUN_CHAIN_VAL_FCT(
  bar_D2_uh_dow_at_qp, REAL_VEC_D,
  uh_loc, qfast, const QUAD_FAST, D2_uh, REAL_DBB *, const REAL_DBB *,
  (D2_uh, qfast, uh_loc, false), (D2_uh, qfast, uh_loc, true),
  (REAL_DBB D2_uh[], const QUAD_FAST *qfast, const EL_REAL_VEC_D *uh_loc));

/* >>> */

/* <<< all quad-points */

static inline
const REAL_DD *
__D2_uh_at_qp(REAL_DD vec[],
	      const QUAD_FAST *qfast,
	      const REAL_BD Lambda,
	      const EL_REAL_VEC *uh_loc,
	      bool update)
{
  FUNCNAME("__D2_uh_at_qp");
  static REAL_DD *quad_vec = NULL;
  static size_t  size = 0;
  const REAL_BB  *D2_phi;
  REAL_BB        D2_tmp;
  int            i, k, l, iq;
  int            dim = qfast->dim;

  if (!vec) {
    if (size < (size_t)qfast->n_points) {
      MEM_FREE(quad_vec, size, REAL_DD);
      size = qfast->n_points;
      quad_vec = MEM_ALLOC(size, REAL_DD);
    }
    vec = quad_vec;
  }

  for (iq = 0; iq < qfast->n_points; iq++) {
    D2_phi = qfast->D2_phi[iq];

    for (k = 0; k < N_LAMBDA(dim); k++) {
      for (l = k; l < N_LAMBDA(dim); l++) {
	D2_tmp[k][l] = 0.0;

	for (i = 0; i < qfast->n_bas_fcts; i++)
	  D2_tmp[k][l] += uh_loc->vec[i]*D2_phi[i][k][l];
      }
    }

    if (update) {
      D2_P_DOW(dim, Lambda, (const REAL_B *)D2_tmp, vec[iq]);
    } else {
      D2_DOW(dim, Lambda, (const REAL_B *)D2_tmp, vec[iq]);
    }
  }

  return (const REAL_DD *)vec;
}

/* NOTE: to compute second Cartesian (tangential) derivatives on
 * curved elements we need the Cartesian (tangential) derivatives of
 * Lambda. We have (let 
 *
 * \nabla^2 f(\lambda(x))
 * =
 * \Lambda^t \nabla^2_\lambda f \Lambda
 * +
 * \nabla_\lambda f \nabla\Lambda
 *
 * So we need \nabla\Lambda, the (tangential) derivative of \Lambda,
 * called DLambda below. This is a N_LAMBDA_MAX\times DOW\times DOW matrix
 */
static inline
const REAL_DD *
__param_D2_uh_at_qp(REAL_DD vec[],
		    const QUAD_FAST *qfast,
		    const REAL_BD *Lambda,
		    const REAL_BDD *DLambda,
		    const EL_REAL_VEC *uh_loc,
		    bool update)
{
  FUNCNAME("__param_D2_uh_at_qp");
  static REAL_DD *quad_vec = NULL;
  static size_t  size = 0;
  const REAL_BB  *D2_phi;
  const REAL_B   *grd_phi;
  REAL_BB        D2_bar;
  REAL           grd_bar;
  int            i, k, l, iq;
  int            dim = qfast->dim;

  if (!vec) {
    if (size < (size_t)qfast->n_points) {
      MEM_FREE(quad_vec, size, REAL_DD);
      size = qfast->n_points;
      quad_vec = MEM_ALLOC(size, REAL_DD);
    }
    vec = quad_vec;
  }

  for (iq = 0; iq < qfast->n_points; iq++) {
    D2_phi = qfast->D2_phi[iq];

    for (k = 0; k < N_LAMBDA(dim); k++) {
      for (l = k; l < N_LAMBDA(dim); l++) {
	D2_bar[k][l] = 0.0;

	for (i = 0; i < qfast->n_bas_fcts; i++)
	  D2_bar[k][l] += uh_loc->vec[i]*D2_phi[i][k][l];
      }
    }

    if (update) {
      D2_P_DOW(dim, Lambda[iq], (const REAL_B *)D2_bar, vec[iq]);
    } else {
      D2_DOW(dim, Lambda[iq], (const REAL_B *)D2_bar, vec[iq]);
    }
  }

  /* Next the correction from the "curvature" */
  if (DLambda) {
    for (iq = 0; iq < qfast->n_points; iq++) {
      grd_phi = qfast->grd_phi[iq];

      for (k = 0; k < N_LAMBDA(dim); k++) {

	grd_bar = 0.0;
	for (i = 0; i < qfast->n_bas_fcts; i++)
	  grd_bar += uh_loc->vec[i]*grd_phi[i][k];

	MAXPY_DOW(grd_bar, DLambda[iq][k], vec[iq]);
      }
    }
  }

  return (const REAL_DD *)vec;
}

static inline
const REAL_DDD *
__D2_uh_d_at_qp(REAL_DDD vec[],
		const QUAD_FAST *qfast,
		const REAL_BD Lambda,
		const EL_REAL_D_VEC *uh_loc,
		bool update)
{
  FUNCNAME("D2_uh_d_at_qp");
  static REAL_DDD *quad_vec = NULL;
  static size_t   size = 0;
  const REAL_BB   *D2_phi;
  REAL_BB         D2_tmp;
  int             i, k, l, n, iq, dim = qfast->dim;

  if (!vec) {
    if (size < (size_t)qfast->n_points) {
      MEM_FREE(quad_vec, size, REAL_DDD);
      size = qfast->n_points;
      quad_vec = MEM_ALLOC(size, REAL_DDD);
    }
    vec = quad_vec;
  }

  for (iq = 0; iq < qfast->n_points; iq++) {
    D2_phi = qfast->D2_phi[iq];

    for (n = 0; n < DIM_OF_WORLD; n++) {
      for (k = 0; k < N_LAMBDA(dim); k++) {
	for (l = k; l < N_LAMBDA(dim); l++) {
	  D2_tmp[k][l] = 0.0;
	  
	  for (i = 0; i < qfast->n_bas_fcts; i++)
	    D2_tmp[k][l] += uh_loc->vec[i][n]*D2_phi[i][k][l];
	}
      }
      if (update) {
	D2_P_DOW(dim, Lambda, (const REAL_B *)D2_tmp, vec[iq][n]);
      } else {
	D2_DOW(dim, Lambda, (const REAL_B *)D2_tmp, vec[iq][n]);
      }
    }
  }

  return (const REAL_DDD *)vec;
}

static inline
const REAL_DDD *
__param_D2_uh_d_at_qp(REAL_DDD vec[],
		      const QUAD_FAST *qfast,
		      const REAL_BD *Lambda,
		      const REAL_BDD *DLambda,
		      const EL_REAL_D_VEC *uh_loc,
		      bool update)
{
  FUNCNAME("D2_uh_d_at_qp");
  static REAL_DDD *quad_vec = NULL;
  static size_t   size = 0;
  const REAL_BB   *D2_phi;
  const REAL_B    *grd_phi;
  REAL_BB         D2_bar;
  REAL            grd_bar;
  int             i, j, k, l, n, iq, dim = qfast->dim;

  if (!vec) {
    if (size < (size_t)qfast->n_points) {
      MEM_FREE(quad_vec, size, REAL_DDD);
      size = qfast->n_points;
      quad_vec = MEM_ALLOC(size, REAL_DDD);
    }
    vec = quad_vec;
  }

  for (iq = 0; iq < qfast->n_points; iq++) {
    D2_phi = qfast->D2_phi[iq];

    for (n = 0; n < DIM_OF_WORLD; n++) {
      for (k = 0; k < N_LAMBDA(dim); k++) {
	for (l = k; l < N_LAMBDA(dim); l++) {
	  D2_bar[k][l] = 0.0;

	  for (i = 0; i < qfast->n_bas_fcts; i++)
	    D2_bar[k][l] += uh_loc->vec[i][n]*D2_phi[i][k][l];
	}
      }
      if (update) {
	D2_P_DOW(dim, Lambda[iq], (const REAL_B *)D2_bar, vec[iq][n]);
      } else {
	D2_DOW(dim, Lambda[iq], (const REAL_B *)D2_bar, vec[iq][n]);
      }
    }
  }

  /* Next the correction from the "curvature" */
  if (DLambda) {
    for (i = 0; i < qfast->n_points; i++) {
      grd_phi = qfast->grd_phi[i];

      for (k = 0; k < DIM_OF_WORLD; k++) {
	for (l = 0; l < N_LAMBDA(dim); l++) {

	  grd_bar = 0.0;
	  for (j = 0; j < qfast->n_bas_fcts; j++)
	    grd_bar += uh_loc->vec[j][k]*grd_phi[j][l];
	  
	  MAXPY_DOW(grd_bar, DLambda[iq][l], vec[iq][k]);
	}
      }
    }
  }
  
  return (const REAL_DDD *)vec;
}

static inline
const REAL_DDD *
__D2_uh_dow_at_qp(REAL_DDD vec[],
		  const QUAD_FAST *qfast,
		  const REAL_BD Lambda,
		  const EL_REAL_VEC_D *uh_loc, 
		  bool update)
{
  FUNCNAME("D2_uh_d_at_qp");
  static REAL_DDD *quad_vec = NULL;
  static size_t   size = 0;
  const REAL_DBB  *const*D2_phi;
  REAL_BB         D2_tmp;
  int             iq, ib, n;

  if (!vec) {
    if (size < (size_t)qfast->n_points) {
      MEM_FREE(quad_vec, size, REAL_DDD);
      size = qfast->n_points;
      quad_vec = MEM_ALLOC(size, REAL_DDD);
    }
    vec = quad_vec;
  }

  if (uh_loc->stride != 1) {
    return __D2_uh_d_at_qp(
      vec, qfast, Lambda, (const EL_REAL_D_VEC *)uh_loc, update);
  }

  D2_phi = get_quad_fast_D2_phi_dow(qfast);
  for (iq = 0; iq < qfast->n_points; iq++) {
    for (n = 0; n < DIM_OF_WORLD; n++) {
      MSET_BAR(DIM_MAX, 0.0, D2_tmp);
      for (ib = 0; ib < qfast->n_bas_fcts; ib++) {
	MAXPY_BAR(DIM_MAX, uh_loc->vec[ib], D2_phi[iq][ib][n], D2_tmp);
      }
      if (update) {
	D2_P_DOW(DIM_MAX, Lambda, (const REAL_B *)D2_tmp, vec[iq][n]);
      } else {
	D2_DOW(DIM_MAX, Lambda, (const REAL_B *)D2_tmp, vec[iq][n]);
      }
    }
  }

  return (const REAL_DDD *)vec;
}

static inline
const REAL_DDD *
__param_D2_uh_dow_at_qp(REAL_DDD vec[],
			const QUAD_FAST *qfast,
			const REAL_BD *Lambda,
			const REAL_BDD *DLambda,
			const EL_REAL_VEC_D *uh_loc,
			bool update)
			
{
  FUNCNAME("D2_uh_d_at_qp");
  static REAL_DDD *quad_vec = NULL;
  static size_t   size = 0;
  const REAL_DBB  *const*D2_phi;
  const REAL_DB   *const*grd_phi;
  REAL            grd_bar;
  int             n, iq, ib, k, l, dim = qfast->dim;

  if (!vec) {
    if (size < (size_t)qfast->n_points) {
      MEM_FREE(quad_vec, size, REAL_DDD);
      size = qfast->n_points;
      quad_vec = MEM_ALLOC(size, REAL_DDD);
    }
    vec = quad_vec;
  }

  if (uh_loc->stride != 1) {
    return __param_D2_uh_d_at_qp(
      vec, qfast, Lambda, DLambda, (const EL_REAL_D_VEC *)uh_loc, update);
  }

  D2_phi = get_quad_fast_D2_phi_dow(qfast);
  for (iq = 0; iq < qfast->n_points; iq++) {
    for (n = 0; n < DIM_OF_WORLD; n++) {
      REAL_BB D2_tmp = { { 0.0, } };
      for (ib = 0; ib < qfast->n_bas_fcts; ib++) {
	MAXPY_BAR(DIM_MAX, uh_loc->vec[ib], D2_phi[iq][ib][n], D2_tmp);
      }
      if (update) {
	D2_P_DOW(DIM_MAX, Lambda[iq], (const REAL_B *)D2_tmp, vec[iq][n]);
      } else {
	D2_DOW(DIM_MAX, Lambda[iq], (const REAL_B *)D2_tmp, vec[iq][n]);
      }
    }
  }

  /* Next the correction from the "curvature" */
  if (DLambda) {
    grd_phi = get_quad_fast_grd_phi_dow(qfast);
    for (iq = 0; iq < qfast->n_points; iq++) {

      for (k = 0; k < DIM_OF_WORLD; k++) {
	for (l = 0; l < N_LAMBDA(dim); l++) {

	  grd_bar = 0.0;
	  for (ib = 0; ib < qfast->n_bas_fcts; ib++) {
	    grd_bar += uh_loc->vec[ib]*grd_phi[iq][ib][k][l];
	  }
	  
	  MAXPY_DOW(grd_bar, DLambda[iq][l], vec[iq][k]);
	}
      }
    }
  }
  
  return (const REAL_DDD *)vec;
}

DEFUN_CHAIN_VAL_FCT(
  D2_uh_at_qp, REAL_VEC,
  uh_loc, qfast, const QUAD_FAST, D2_uh, REAL_DD *, const REAL_DD *,
  (D2_uh, qfast, Lambda, uh_loc, false), (D2_uh, qfast, Lambda, uh_loc, true),
  (REAL_DD *D2_uh,
   const QUAD_FAST *qfast, const REAL_BD Lambda, const EL_REAL_VEC *uh_loc));

DEFUN_CHAIN_VAL_FCT(
  param_D2_uh_at_qp, REAL_VEC,
  uh_loc, qfast, const QUAD_FAST, D2_uh, REAL_DD *, const REAL_DD *,
  (D2_uh, qfast, Lambda, DLambda, uh_loc, false),
  (D2_uh, qfast, Lambda, DLambda, uh_loc, true),
  (REAL_DD *D2_uh, const QUAD_FAST *qfast,
   const REAL_BD Lambda[], const REAL_BDD DLambda[],
   const EL_REAL_VEC *uh_loc));

DEFUN_CHAIN_VAL_FCT(
  D2_uh_d_at_qp, REAL_D_VEC,
  uh_loc, qfast, const QUAD_FAST, D2_uh, REAL_DDD *, const REAL_DDD *,
  (D2_uh, qfast, Lambda, uh_loc, false), (D2_uh, qfast, Lambda, uh_loc, true),
  (REAL_DDD D2_uh[],
   const QUAD_FAST *qfast, const REAL_BD Lambda, const EL_REAL_D_VEC *uh_loc));

DEFUN_CHAIN_VAL_FCT(
  param_D2_uh_d_at_qp, REAL_D_VEC,
  uh_loc, qfast, const QUAD_FAST, D2_uh, REAL_DDD *, const REAL_DDD *,
  (D2_uh, qfast, Lambda, DLambda, uh_loc, false),
  (D2_uh, qfast, Lambda, DLambda, uh_loc, true),
  (REAL_DDD *D2_uh, const QUAD_FAST *qfast,
   const REAL_BD Lambda[], const REAL_BDD DLambda[],
   const EL_REAL_D_VEC *uh_loc));

DEFUN_CHAIN_VAL_FCT(
  D2_uh_dow_at_qp, REAL_VEC_D,
  uh_loc, qfast, const QUAD_FAST, D2_uh, REAL_DDD *, const REAL_DDD *,
  (D2_uh, qfast, Lambda, uh_loc, false), (D2_uh, qfast, Lambda, uh_loc, true),
  (REAL_DDD D2_uh[],
   const QUAD_FAST *qfast, const REAL_BD Lambda, const EL_REAL_VEC_D *uh_loc));

DEFUN_CHAIN_VAL_FCT(
  param_D2_uh_dow_at_qp, REAL_VEC_D,
  uh_loc, qfast, const QUAD_FAST, D2_uh, REAL_DDD *, const REAL_DDD *,
  (D2_uh, qfast, Lambda, DLambda, uh_loc, false),
  (D2_uh, qfast, Lambda, DLambda, uh_loc, true),
  (REAL_DDD *D2_uh, const QUAD_FAST *qfast,
   const REAL_BD Lambda[], const REAL_BDD DLambda[],
   const EL_REAL_VEC_D *uh_loc));

/* >>> */

/* >>> */

/* <<< Laplacian */

/* <<< slow evaluation */

static inline REAL
__eval_Laplace_uh(const REAL_B lambda,
		  const REAL_BD Lambda,
		  const EL_REAL_VEC *uh_loc, const BAS_FCTS *b)
{
  const REAL_B   *D2_b;
  int            i, k, l, dim = b->dim;
  REAL_BB        D2_tmp = {{0}};

  for (i = 0; i < b->n_bas_fcts; i++) {
    D2_b = D2_PHI(b, i, lambda);     
    for (k = 0; k < N_LAMBDA(dim); k++)
      for (l = k; l < N_LAMBDA(dim); l++)
      D2_tmp[k][l] += uh_loc->vec[i]*D2_b[k][l];
  }

  return LAPLACE_DOW(DIM_MAX, Lambda, (const REAL_B *)D2_tmp);
}

static inline const REAL *
__eval_Laplace_uh_d(REAL_D laplace_uh,
		    const REAL_B lambda,
		    const REAL_BD Lambda,
		    const EL_REAL_D_VEC *uh_loc, const BAS_FCTS *b,
		    bool update)
{
  static REAL_D space;
  const REAL_B  *D2_b;
  int           i, n, dim = b->dim;
  REAL_DBB      D21;
  
  if (laplace_uh == NULL) {
    laplace_uh = space;
  }
  
  for (i = 0; i < b->n_bas_fcts; i++) {
    D2_b = D2_PHI(b, i, lambda);
    for (n = 0; n < DIM_OF_WORLD; n++) {
      MAXPY_BAR(dim, uh_loc->vec[i][n], D2_b, D21[n]);
    }
  }

  if (update) {
    MLAPLACE_P_DOW(DIM_MAX, Lambda, (const REAL_BB *)D21, laplace_uh);
  } else {
    MLAPLACE_DOW(DIM_MAX, Lambda, (const REAL_BB *)D21, laplace_uh);
  }  

  return (const REAL *)laplace_uh;
}

static inline const REAL *
__eval_Laplace_uh_dow(REAL_D laplace_uh,
		      const REAL_B lambda,
		      const REAL_BD Lambda,
		      const EL_REAL_VEC_D *uh_loc, const BAS_FCTS *b,
		      bool update)
{
  static REAL_D space;
  const REAL_B  *D2_b;
  const REAL    *phi_d;
  int           i, k, l, n, dim = b->dim;
  REAL_DBB      D2_tmp = { { { 0.0, } } };	

  if (laplace_uh == NULL) {
    laplace_uh = space;
  }

  if (uh_loc->stride != 1) {
    return __eval_Laplace_uh_d(
      laplace_uh, lambda, Lambda, (const EL_REAL_D_VEC *)uh_loc, b, update);
  }

  if (!b->dir_pw_const) {
    REAL_BB tmp;
    REAL phi;
    const REAL    *grd_phi;
    const REAL_B  *grd_phi_d;
    const REAL_BB *D2_phi_d;

    /* The slow and complicated case ... */
    for (i = 0; i < b->n_bas_fcts; i++) {
      phi       = PHI(b, i, lambda);
      grd_phi   = GRD_PHI(b, i, lambda);
      grd_phi_d = GRD_PHI_D(b, i, lambda);
      D2_phi_d  = D2_PHI_D(b, i, lambda);

      for (n = 0; n < DIM_OF_WORLD; n++) {
	MAXEY_BAR(dim, phi, D2_phi_d[n], tmp);
	for (k = 0; k < N_LAMBDA(dim); k++) {
	  for (l = k; l < N_LAMBDA(dim); l++) {
	    tmp[k][l] +=
	      grd_phi[k]*grd_phi_d[n][l] + grd_phi[l]*grd_phi_d[n][k];
	  }
	}
	MAXPY_BAR(dim, uh_loc->vec[i], (const REAL_B *)tmp, D2_tmp[n]);
      }
    }
  }
  
  for (i = 0; i < b->n_bas_fcts; i++) {
    D2_b   = D2_PHI(b, i, lambda);
    phi_d  = PHI_D(b, i, lambda);
 
    for (n = 0; n < DIM_OF_WORLD; n++) {
      MAXPY_BAR(dim, uh_loc->vec[i]*phi_d[n], D2_b, D2_tmp[n]);
    }
  }

  if (update) {
    MLAPLACE_P_DOW(DIM_MAX, Lambda, (const REAL_BB *)D2_tmp, laplace_uh);
  } else {
    MLAPLACE_DOW(DIM_MAX, Lambda, (const REAL_BB *)D2_tmp, laplace_uh);
  }

  return (const REAL *)laplace_uh;
}

DEFUN_CHAIN_VAL_FCT(
  eval_Laplace_uh, REAL_VEC,
  uh_loc, b, const BAS_FCTS, val, REAL, REAL,
  (lambda, Lambda, uh_loc, b),
  (lambda, Lambda, uh_loc, b) + val,
  (const REAL_B lambda,
   const REAL_BD Lambda, const EL_REAL_VEC *uh_loc, const BAS_FCTS *b));

DEFUN_CHAIN_VAL_FCT(
  eval_Laplace_uh_d, REAL_D_VEC,
  uh_loc, b, const BAS_FCTS, laplace_uh, REAL *, const REAL *,
  (laplace_uh, lambda, Lambda, uh_loc, b, false),
  (laplace_uh, lambda, Lambda, uh_loc, b, true),
  (REAL_D laplace_uh, const REAL_B lambda, const REAL_BD Lambda,
   const EL_REAL_D_VEC *uh_loc, const BAS_FCTS *b));

DEFUN_CHAIN_VAL_FCT(
  eval_Laplace_uh_dow, REAL_VEC_D,
  uh_loc, b, const BAS_FCTS, laplace_uh, REAL *, const REAL *,
  (laplace_uh, lambda, Lambda, uh_loc, b, false),
  (laplace_uh, lambda, Lambda, uh_loc, b, true),
  (REAL_D laplace_uh, const REAL_B lambda, const REAL_BD Lambda,
   const EL_REAL_VEC_D *uh_loc, const BAS_FCTS *b));

/* >>> */

/* <<< "fast" evaluation at a single vertex */

static inline
REAL
__eval_Laplace_uh_fast(const REAL_BD Lambda,
		       const EL_REAL_VEC *uh_loc, 
		       const QUAD_FAST *qfast, int iq)
{
  int            i, k, l;
  REAL_BB        D2_tmp = {{0}};
  const REAL_BB  *D2_phi = qfast->D2_phi[iq];
  
  for (i = 0; i < qfast->n_bas_fcts; i++)
    for (k = 0; k < N_LAMBDA_MAX; k++)
      for (l = k; l < N_LAMBDA_MAX; l++)
	D2_tmp[k][l] += uh_loc->vec[i]*D2_phi[i][k][l];

  return LAPLACE_DOW(DIM_MAX, Lambda, (const REAL_B *)D2_tmp);
}

static inline
const REAL *
__eval_Laplace_uh_d_fast(REAL_D laplace_uh,
			 const REAL_BD Lambda, 
			 const EL_REAL_D_VEC *uh_loc, 
			 const QUAD_FAST *qfast, int iq,
			 bool update)
{
  static REAL_D space;
  int           i, k, l, n;
  REAL_BB       D2_tmp;
  const REAL_BB *D2_phi = qfast->D2_phi[iq];

  if (laplace_uh == NULL) {
    laplace_uh = space;
  }
 
  for (n = 0; n < DIM_OF_WORLD; n++) {
    for (k = 0; k < N_LAMBDA_MAX; k++) {
      for (l = k; l < N_LAMBDA_MAX; l++) {
	D2_tmp[k][l] = 0.0;
	
	for (i = 0; i < qfast->n_bas_fcts; i++) {
	  D2_tmp[k][l] += uh_loc->vec[i][n]*D2_phi[i][k][l];
	}
      }
    }
    
    if (update) {
      laplace_uh[n] += LAPLACE_DOW(DIM_MAX, Lambda, (const REAL_B *)D2_tmp);
    } else {
      laplace_uh[n] = LAPLACE_DOW(DIM_MAX, Lambda, (const REAL_B *)D2_tmp);
    }
  }
 
  return (const REAL *)laplace_uh;
}

static inline const
REAL *
__eval_Laplace_uh_dow_fast(REAL_D laplace_uh,
			   const REAL_BD Lambda, 
			   const EL_REAL_VEC_D *uh_loc, 
			   const QUAD_FAST *qfast, int iq,
			   bool update)
{
  static REAL_D  space;
  int            i, n;
  REAL_BB        D2_tmp;
  const REAL_DBB *D2_phi;

  if (laplace_uh == NULL) {
    laplace_uh = space;
  }

  if (uh_loc->stride != 1) {
    return
      __eval_Laplace_uh_d_fast(
	laplace_uh, Lambda, (const EL_REAL_D_VEC *)uh_loc, qfast, iq, update);
  }

  D2_phi = get_quad_fast_D2_phi_dow(qfast)[iq];

  for (n = 0; n < DIM_OF_WORLD; n++) {
    MSET_BAR(DIM_MAX, 0.0, D2_tmp);
    for (i = 0; i < qfast->n_bas_fcts; i++) {
      MAXPY_BAR(DIM_MAX, uh_loc->vec[i], D2_phi[i][n], D2_tmp);
    }

    if (update) {
      laplace_uh[n] += LAPLACE_DOW(DIM_MAX, Lambda, (const REAL_B *)D2_tmp);
    } else {
      laplace_uh[n] = LAPLACE_DOW(DIM_MAX, Lambda, (const REAL_B *)D2_tmp);
    }
  }

  return (const REAL *)laplace_uh;
}

DEFUN_CHAIN_VAL_FCT(
  eval_Laplace_uh_fast, REAL_VEC,
  uh_loc, qfast, const QUAD_FAST, val, REAL, REAL,
  (Lambda, uh_loc, qfast, iq),
  (Lambda, uh_loc, qfast, iq) + val,
  (const REAL_BD Lambda,
   const EL_REAL_VEC *uh_loc, const QUAD_FAST *qfast, int iq));

DEFUN_CHAIN_VAL_FCT(
  eval_Laplace_uh_d_fast, REAL_D_VEC,
  uh_loc, qfast, const QUAD_FAST, laplace_uh, REAL *, const REAL *,
  (laplace_uh, Lambda, uh_loc, qfast, iq, false),
  (laplace_uh, Lambda, uh_loc, qfast, iq, true),
  (REAL_D laplace_uh, const REAL_BD Lambda, const EL_REAL_D_VEC *uh_loc,
   const QUAD_FAST *qfast, int iq));

DEFUN_CHAIN_VAL_FCT(
  eval_Laplace_uh_dow_fast, REAL_VEC_D,
  uh_loc, qfast, const QUAD_FAST, laplace_uh, REAL *, const REAL *,
  (laplace_uh, Lambda, uh_loc, qfast, iq, false),
  (laplace_uh, Lambda, uh_loc, qfast, iq, true),
  (REAL_D laplace_uh, const REAL_BD Lambda, const EL_REAL_VEC_D *uh_loc,
   const QUAD_FAST *qfast, int iq));

/* >>> */

/* <<< all quad-points */

static inline
const REAL *
__Laplace_uh_at_qp(REAL vec[],
		   const QUAD_FAST *qfast,
		   const REAL_BD Lambda,
		   const EL_REAL_VEC *uh_loc,
		   bool update)
{
  FUNCNAME("__D2_uh_at_qp");
  static REAL    *quad_vec = NULL;
  static size_t  size = 0;
  const REAL_BB  *D2_phi;
  REAL_BB        D2_tmp;
  int            i, k, l, iq;
  int            dim = qfast->dim;

  if (!vec) {
    if (size < (size_t)qfast->n_points) {
      MEM_FREE(quad_vec, size, REAL);
      size = qfast->n_points;
      quad_vec = MEM_ALLOC(size, REAL);
    }
    vec = quad_vec;
  }

  for (iq = 0; iq < qfast->n_points; iq++) {
    D2_phi = qfast->D2_phi[iq];

    for (k = 0; k < N_LAMBDA(dim); k++) {
      for (l = k; l < N_LAMBDA(dim); l++) {
	D2_tmp[k][l] = 0.0;

	for (i = 0; i < qfast->n_bas_fcts; i++)
	  D2_tmp[k][l] += uh_loc->vec[i]*D2_phi[i][k][l];
      }
    }

    if (update) {
      vec[iq] += LAPLACE_DOW(dim, Lambda, (const REAL_B *)D2_tmp);
    } else {
      vec[iq]  = LAPLACE_DOW(dim, Lambda, (const REAL_B *)D2_tmp);
    }
  }

  return (const REAL *)vec;
}

/* NOTE: to compute second Cartesian (tangential) derivatives on
 * curved elements we need the Cartesian (tangential) derivatives of
 * Lambda. We have (let 
 *
 * \nabla^2 f(\lambda(x))
 * =
 * \Lambda^t \nabla^2_\lambda f \Lambda
 * +
 * \nabla_\lambda f \nabla\Lambda
 *
 * So we need \nabla\Lambda, the (tangential) derivative of \Lambda,
 * called DLambda below. This is a N_LAMBDA_MAX\times DOW\times DOW matrix
 */
static inline
const REAL *
__param_Laplace_uh_at_qp(REAL vec[],
			 const QUAD_FAST *qfast,
			 const REAL_BD *Lambda,
			 const REAL_BDD *DLambda,
			 const EL_REAL_VEC *uh_loc,
			 bool update)
{
  FUNCNAME("__param_Laplace_uh_at_qp");
  static REAL   *quad_vec = NULL;
  static size_t  size = 0;
  const REAL_BB  *D2_phi;
  const REAL_B   *grd_phi;
  REAL_BB        D2_bar;
  REAL           grd_bar;
  int            i, k, l, iq;
  int            dim = qfast->dim;

  if (!vec) {
    if (size < (size_t)qfast->n_points) {
      MEM_FREE(quad_vec, size, REAL);
      size = qfast->n_points;
      quad_vec = MEM_ALLOC(size, REAL);
    }
    vec = quad_vec;
  }

  for (iq = 0; iq < qfast->n_points; iq++) {
    D2_phi = qfast->D2_phi[iq];

    for (k = 0; k < N_LAMBDA(dim); k++) {
      for (l = k; l < N_LAMBDA(dim); l++) {
	D2_bar[k][l] = 0.0;

	for (i = 0; i < qfast->n_bas_fcts; i++)
	  D2_bar[k][l] += uh_loc->vec[i]*D2_phi[i][k][l];
      }
    }

    if (update) {
      vec[iq] += LAPLACE_DOW(dim, Lambda[iq], (const REAL_B *)D2_bar);
    } else {
      vec[iq]  = LAPLACE_DOW(dim, Lambda[iq], (const REAL_B *)D2_bar);
    }
  }

  /* Next the correction from the "curvature" */
  if (DLambda) {
    for (iq = 0; iq < qfast->n_points; iq++) {
      grd_phi = qfast->grd_phi[iq];

      for (k = 0; k < N_LAMBDA(dim); k++) {

	grd_bar = 0.0;
	for (i = 0; i < qfast->n_bas_fcts; i++) {
	  grd_bar += uh_loc->vec[i]*grd_phi[i][k];
	}

	for (i = 0; i < DIM_OF_WORLD; i++) {
	  vec[iq] += grd_bar * DLambda[iq][k][i][i];
	}
      }
    }
  }

  return (const REAL *)vec;
}

static inline
const REAL_D *
__Laplace_uh_d_at_qp(REAL_D vec[],
		     const QUAD_FAST *qfast,
		     const REAL_BD Lambda,
		     const EL_REAL_D_VEC *uh_loc,
		     bool update)
{
  FUNCNAME("D2_uh_d_at_qp");
  static REAL_D *quad_vec = NULL;
  static size_t size = 0;
  const REAL_BB *D2_phi;
  REAL_BB       D2_tmp;
  int           i, k, l, n, iq, dim = qfast->dim;

  if (!vec) {
    if (size < (size_t)qfast->n_points) {
      MEM_FREE(quad_vec, size, REAL_D);
      size = qfast->n_points;
      quad_vec = MEM_ALLOC(size, REAL_D);
    }
    vec = quad_vec;
  }

  for (iq = 0; iq < qfast->n_points; iq++) {
    D2_phi = qfast->D2_phi[iq];

    for (n = 0; n < DIM_OF_WORLD; n++) {
      for (k = 0; k < N_LAMBDA(dim); k++) {
	for (l = k; l < N_LAMBDA(dim); l++) {
	  D2_tmp[k][l] = 0.0;
	  
	  for (i = 0; i < qfast->n_bas_fcts; i++)
	    D2_tmp[k][l] += uh_loc->vec[i][n]*D2_phi[i][k][l];
	}
      }
      if (update) {
	vec[iq][n]  = LAPLACE_DOW(dim, Lambda, (const REAL_B *)D2_tmp);
      } else {
	vec[iq][n] += LAPLACE_DOW(dim, Lambda, (const REAL_B *)D2_tmp);
      }
    }
  }

  return (const REAL_D *)vec;
}

static inline
const REAL_D *
__param_Laplace_uh_d_at_qp(REAL_D vec[],
			   const QUAD_FAST *qfast,
			   const REAL_BD *Lambda,
			   const REAL_BDD *DLambda,
			   const EL_REAL_D_VEC *uh_loc,
			   bool update)
{
  FUNCNAME("Laplace_uh_d_at_qp");
  static REAL_D *quad_vec = NULL;
  static size_t size = 0;
  const REAL_BB *D2_phi;
  const REAL_B  *grd_phi;
  REAL_BB       D2_bar;
  REAL          grd_bar;
  int           i, j, k, l, n, iq, dim = qfast->dim;

  if (!vec) {
    if (size < (size_t)qfast->n_points) {
      MEM_FREE(quad_vec, size, REAL_D);
      size = qfast->n_points;
      quad_vec = MEM_ALLOC(size, REAL_D);
    }
    vec = quad_vec;
  }

  for (iq = 0; iq < qfast->n_points; iq++) {
    D2_phi = qfast->D2_phi[iq];

    for (n = 0; n < DIM_OF_WORLD; n++) {
      for (k = 0; k < N_LAMBDA(dim); k++) {
	for (l = k; l < N_LAMBDA(dim); l++) {
	  D2_bar[k][l] = 0.0;

	  for (i = 0; i < qfast->n_bas_fcts; i++)
	    D2_bar[k][l] += uh_loc->vec[i][n]*D2_phi[i][k][l];
	}
      }
      if (update) {
	vec[iq][n] += LAPLACE_DOW(dim, Lambda[iq], (const REAL_B *)D2_bar);
      } else {
	vec[iq][n]  = LAPLACE_DOW(dim, Lambda[iq], (const REAL_B *)D2_bar);
      }
    }
  }

  /* Next the correction from the "curvature" */
  if (DLambda) {
    for (i = 0; i < qfast->n_points; i++) {
      grd_phi = qfast->grd_phi[i];

      for (k = 0; k < DIM_OF_WORLD; k++) {
	for (l = 0; l < N_LAMBDA(dim); l++) {

	  grd_bar = 0.0;
	  for (j = 0; j < qfast->n_bas_fcts; j++) {
	    grd_bar += uh_loc->vec[j][k]*grd_phi[j][l];
	  }
	  
	  for (i = 0; i < DIM_OF_WORLD; i++) {
	    vec[iq][k] += grd_bar * DLambda[iq][k][i][i];
	  }
	}
      }
    }
  }
  
  return (const REAL_D *)vec;
}

static inline
const REAL_D *
__Laplace_uh_dow_at_qp(REAL_D vec[],
		       const QUAD_FAST *qfast,
		       const REAL_BD Lambda,
		       const EL_REAL_VEC_D *uh_loc, 
		       bool update)
{
  FUNCNAME("Laplace_uh_d_at_qp");
  static REAL_D  *quad_vec = NULL;
  static size_t  size = 0;
  const REAL_DBB *const*D2_phi;
  REAL_BB        D2_tmp;
  int            iq, ib, n;

  if (!vec) {
    if (size < (size_t)qfast->n_points) {
      MEM_FREE(quad_vec, size, REAL_D);
      size = qfast->n_points;
      quad_vec = MEM_ALLOC(size, REAL_D);
    }
    vec = quad_vec;
  }

  if (uh_loc->stride != 1) {
    return __Laplace_uh_d_at_qp(
      vec, qfast, Lambda, (const EL_REAL_D_VEC *)uh_loc, update);
  }

  D2_phi = get_quad_fast_D2_phi_dow(qfast);
  for (iq = 0; iq < qfast->n_points; iq++) {
    for (n = 0; n < DIM_OF_WORLD; n++) {
      MSET_BAR(DIM_MAX, 0.0, D2_tmp);
      for (ib = 0; ib < qfast->n_bas_fcts; ib++) {
	MAXPY_BAR(DIM_MAX, uh_loc->vec[ib], D2_phi[iq][ib][n], D2_tmp);
      }
      if (update) {
	vec[iq][n] += LAPLACE_DOW(DIM_MAX, Lambda, (const REAL_B *)D2_tmp);
      } else {
	vec[iq][n]  = LAPLACE_DOW(DIM_MAX, Lambda, (const REAL_B *)D2_tmp);
      }
    }
  }

  return (const REAL_D *)vec;
}

static inline
const REAL_D *
__param_Laplace_uh_dow_at_qp(REAL_D vec[],
			     const QUAD_FAST *qfast,
			     const REAL_BD *Lambda,
			     const REAL_BDD *DLambda,
			     const EL_REAL_VEC_D *uh_loc,
			     bool update)
			
{
  FUNCNAME("Laplace_uh_d_at_qp");
  static REAL_D  *quad_vec = NULL;
  static size_t  size = 0;
  const REAL_DBB *const*D2_phi;
  const REAL_DB  *const*grd_phi;
  REAL           grd_bar;
  int            n, i, iq, ib, k, l, dim = qfast->dim;

  if (!vec) {
    if (size < (size_t)qfast->n_points) {
      MEM_FREE(quad_vec, size, REAL_D);
      size = qfast->n_points;
      quad_vec = MEM_ALLOC(size, REAL_D);
    }
    vec = quad_vec;
  }

  if (uh_loc->stride != 1) {
    return __param_Laplace_uh_d_at_qp(
      vec, qfast, Lambda, DLambda, (const EL_REAL_D_VEC *)uh_loc, update);
  }

  D2_phi = get_quad_fast_D2_phi_dow(qfast);
  for (iq = 0; iq < qfast->n_points; iq++) {
    for (n = 0; n < DIM_OF_WORLD; n++) {
      REAL_BB D2_tmp = { { 0.0, } };
      for (ib = 0; ib < qfast->n_bas_fcts; ib++) {
	MAXPY_BAR(DIM_MAX, uh_loc->vec[ib], D2_phi[iq][ib][n], D2_tmp);
      }
      if (update) {
	vec[iq][n] += LAPLACE_DOW(dim, Lambda[iq], (const REAL_B *)D2_tmp);
      } else {
	vec[iq][n]  = LAPLACE_DOW(dim, Lambda[iq], (const REAL_B *)D2_tmp);
      }
    }
  }

  /* Next the correction from the "curvature" */
  if (DLambda) {
    grd_phi = get_quad_fast_grd_phi_dow(qfast);
    for (iq = 0; iq < qfast->n_points; iq++) {

      for (k = 0; k < DIM_OF_WORLD; k++) {
	for (l = 0; l < N_LAMBDA(dim); l++) {

	  grd_bar = 0.0;
	  for (ib = 0; ib < qfast->n_bas_fcts; ib++) {
	    grd_bar += uh_loc->vec[ib]*grd_phi[iq][ib][k][l];
	  }
	  
	  for (i = 0; i < DIM_OF_WORLD; i++) {
	    vec[iq][k] += grd_bar * DLambda[iq][k][i][i];
	  }
	}
      }
    }
  }
  
  return (const REAL_D *)vec;
}

DEFUN_CHAIN_VAL_FCT(
  Laplace_uh_at_qp, REAL_VEC,
  uh_loc, qfast, const QUAD_FAST, Laplace_uh, REAL *, const REAL *,
  (Laplace_uh,
   qfast, Lambda, uh_loc, false), (Laplace_uh, qfast, Lambda, uh_loc, true),
  (REAL *Laplace_uh,
   const QUAD_FAST *qfast, const REAL_BD Lambda, const EL_REAL_VEC *uh_loc));

DEFUN_CHAIN_VAL_FCT(
  param_Laplace_uh_at_qp, REAL_VEC,
  uh_loc, qfast, const QUAD_FAST, Laplace_uh, REAL *, const REAL *,
  (Laplace_uh, qfast, Lambda, DLambda, uh_loc, false),
  (Laplace_uh, qfast, Lambda, DLambda, uh_loc, true),
  (REAL *Laplace_uh, const QUAD_FAST *qfast,
   const REAL_BD Lambda[], const REAL_BDD DLambda[],
   const EL_REAL_VEC *uh_loc));

DEFUN_CHAIN_VAL_FCT(
  Laplace_uh_d_at_qp, REAL_D_VEC,
  uh_loc, qfast, const QUAD_FAST, Laplace_uh, REAL_D *, const REAL_D *,
  (Laplace_uh,
   qfast, Lambda, uh_loc, false), (Laplace_uh, qfast, Lambda, uh_loc, true),
  (REAL_D Laplace_uh[],
   const QUAD_FAST *qfast, const REAL_BD Lambda, const EL_REAL_D_VEC *uh_loc));

DEFUN_CHAIN_VAL_FCT(
  param_Laplace_uh_d_at_qp, REAL_D_VEC,
  uh_loc, qfast, const QUAD_FAST, Laplace_uh, REAL_D *, const REAL_D *,
  (Laplace_uh, qfast, Lambda, DLambda, uh_loc, false),
  (Laplace_uh, qfast, Lambda, DLambda, uh_loc, true),
  (REAL_D *Laplace_uh, const QUAD_FAST *qfast,
   const REAL_BD Lambda[], const REAL_BDD DLambda[],
   const EL_REAL_D_VEC *uh_loc));

DEFUN_CHAIN_VAL_FCT(
  Laplace_uh_dow_at_qp, REAL_VEC_D,
  uh_loc, qfast, const QUAD_FAST, Laplace_uh, REAL_D *, const REAL_D *,
  (Laplace_uh,
   qfast, Lambda, uh_loc, false), (Laplace_uh, qfast, Lambda, uh_loc, true),
  (REAL_D Laplace_uh[],
   const QUAD_FAST *qfast, const REAL_BD Lambda, const EL_REAL_VEC_D *uh_loc));

DEFUN_CHAIN_VAL_FCT(
  param_Laplace_uh_dow_at_qp, REAL_VEC_D,
  uh_loc, qfast, const QUAD_FAST, Laplace_uh, REAL_D *, const REAL_D *,
  (Laplace_uh, qfast, Lambda, DLambda, uh_loc, false),
  (Laplace_uh, qfast, Lambda, DLambda, uh_loc, true),
  (REAL_D *Laplace_uh, const QUAD_FAST *qfast,
   const REAL_BD Lambda[], const REAL_BDD DLambda[],
   const EL_REAL_VEC_D *uh_loc));

/* >>> */

/* >>> */

/* <<< evaluation of non-discrete functions at quadrature points */

/* <<< values */

static inline
const REAL *f_at_qp(REAL vec[],
		    const QUAD *quad, REAL (*f)(const REAL_B lambda))
{
  FUNCNAME("f_at_qp");
  static REAL   *quad_vec = NULL;
  static size_t size = 0;
  REAL          *val;
  int           i;

  if (vec)
  {
    val = vec;
  }
  else
  {
    if (size < (size_t) quad->n_points)
    {
      size_t  new_size = MAX(n_quad_points_max[quad->dim], quad->n_points);
      quad_vec = MEM_REALLOC(quad_vec, size, new_size, REAL);
      size = new_size;
    }
    val = quad_vec;
  }

  for (i = 0; i < quad->n_points; i++)
    val[i] = (*f)(quad->lambda[i]);

  return((const REAL *) val);
}

static inline
const REAL *f_loc_at_qp(REAL vec[],
			const EL_INFO *el_info,
			const QUAD *quad,
			REAL (*f)(const EL_INFO *el_info,
				  const QUAD *quad, int iq,
				  void *ud),
			void *ud)
{
  FUNCNAME("f_loc_at_qp");
  static REAL   *quad_vec = NULL;
  static size_t size = 0;
  REAL          *val;
  int           i;

  if (vec) {
    val = vec;
  } else {
    if (size < (size_t)quad->n_points) {
      size_t  new_size = MAX(n_quad_points_max[quad->dim], quad->n_points);
      quad_vec = MEM_REALLOC(quad_vec, size, new_size, REAL);
      size = new_size;
    }
    val = quad_vec;
  }

  for (i = 0; i < quad->n_points; i++) {
    val[i] = f(el_info, quad, i, ud);
  }

  return (const REAL *)val;
}

static inline
const REAL *fx_at_qp(REAL vec[],
		     const EL_INFO *el_info,
		     const QUAD *quad,
		     FCT_AT_X f)
{
  FUNCNAME("fx_at_qp");
  static REAL   *quad_vec = NULL;
  static size_t size = 0;
  REAL          *val;
  int           i;
  PARAMETRIC    *parametric = el_info->mesh->parametric;

  if (vec) {
    val = vec;
  } else {
    if (size < (size_t) quad->n_points) {
      size_t  new_size = MAX(n_quad_points_max[quad->dim], quad->n_points);
      quad_vec = MEM_REALLOC(quad_vec, size, new_size, REAL);
      size = new_size;
    }
    val = quad_vec;
  }

  if (parametric) {
    REAL_D x[quad->n_points];

    parametric->coord_to_world(el_info, quad, 0, NULL, x);
    for (i = 0; i < quad->n_points; i++) {
      val[i] = f(x[i]);
    }
  } else {
    for (i = 0; i < quad->n_points; i++) {
      REAL_D x;

      coord_to_world(el_info, quad->lambda[i], x);
      val[i] = f(x);
    }
  }

  return (const REAL *)val;
}

static inline
const REAL_D *f_d_at_qp(REAL_D vec[],
			const QUAD *quad,
			const REAL *(*f)(const REAL_B lambda))
{
  FUNCNAME("f_d_at_qp");
  static REAL_D *quad_vec = NULL;
  static size_t size = 0;
  REAL_D        *val;
  const REAL    *val_iq;
  int           iq;

  if (vec)
  {
    val = vec;
  }
  else
  {
    if (size < (size_t) quad->n_points)
    {
      size_t  new_size = MAX(n_quad_points_max[quad->dim], quad->n_points);
      quad_vec = MEM_REALLOC(quad_vec, size, new_size, REAL_D);
      size = new_size;
    }
    val = quad_vec;
  }

  for (iq = 0; iq < quad->n_points; iq++)
  {
    val_iq = (*f)(quad->lambda[iq]);
    COPY_DOW(val_iq, val[iq]);
  }
  return((const REAL_D *) val);
}

static inline
const REAL_D *f_loc_d_at_qp(REAL_D vec[],
			    const EL_INFO *el_info,
			    const QUAD *quad,
			    const REAL *(*f)(REAL_D result,
					     const EL_INFO *el_info,
					     const QUAD *quad, int iq,
					     void *ud),
			    void *ud)
{
  FUNCNAME("f_loc_d_at_qp");
  static REAL_D *quad_vec = NULL;
  static size_t size = 0;
  REAL_D        *val;
  int           iq;

  if (vec) {
    val = vec;
  } else {
    if (size < (size_t) quad->n_points) {
      size_t  new_size = MAX(n_quad_points_max[quad->dim], quad->n_points);
      quad_vec = MEM_REALLOC(quad_vec, size, new_size, REAL_D);
      size = new_size;
    }
    val = quad_vec;
  }

  for (iq = 0; iq < quad->n_points; iq++) {
    f(val[iq], el_info, quad, iq, ud);
  }

  return (const REAL_D *)val;
}

static inline
const REAL_D *fx_d_at_qp(REAL_D vec[],
			 const EL_INFO *el_info,
			 const QUAD *quad,
			 FCT_D_AT_X f)
{
  FUNCNAME("fx_at_qp");
  static REAL_D *quad_vec = NULL;
  static size_t size = 0;
  REAL_D        *val;
  int           i;
  PARAMETRIC    *parametric = el_info->mesh->parametric;

  if (vec) {
    val = vec;
  } else {
    if (size < (size_t) quad->n_points) {
      size_t  new_size = MAX(n_quad_points_max[quad->dim], quad->n_points);
      quad_vec = MEM_REALLOC(quad_vec, size, new_size, REAL_D);
      size = new_size;
    }
    val = quad_vec;
  }

  if (parametric) {
    REAL_D x[quad->n_points];

    parametric->coord_to_world(el_info, quad, 0, NULL, x);
    for (i = 0; i < quad->n_points; i++) {  
      f(x[i], val[i]);
    }
  } else {
    for (i = 0; i < quad->n_points; i++) {
      REAL_D x;

      coord_to_world(el_info, quad->lambda[i], x);
      f(x, val[i]);
    }
  }

  return (const REAL_D *)val;
}

/* >>> */

/* <<< first derivatives */

static inline
const REAL_D *grd_f_at_qp(REAL_D vec[],
			  const QUAD *quad, const REAL *(*f)(const REAL_B))
{
  FUNCNAME("grd_f_at_qp");
  static REAL_D  *quad_vec_d = NULL;
  static size_t  size = 0;
  REAL_D         *val;
  int            i;
  const REAL     *grd;

  if (vec)
  {
    val = vec;
  }
  else
  {
    if (size < (size_t) quad->n_points)
    {
      size_t  new_size = MAX(n_quad_points_max[quad->dim], quad->n_points);
      quad_vec_d = MEM_REALLOC(quad_vec_d, size, new_size, REAL_D);
      size = new_size;
    }
    val = quad_vec_d;
  }

  for (i = 0; i < quad->n_points; i++)
  {
    grd = (*f)(quad->lambda[i]);
    COPY_DOW(grd, val[i]);
  }

  return((const REAL_D *) val);
}

static inline
const REAL_DD *grd_f_d_at_qp(REAL_DD vec[],
			     const QUAD *quad,
			     const REAL_D *(*f)(const REAL_B lambda))
{
  FUNCNAME("grd_f_d_at_qp");
  static REAL_DD  *quad_vec_dd = NULL;
  static size_t   size = 0;
  REAL_DD         *val;
  int             i;
  const REAL_D    *grd_d;

  if (vec)
  {
    val = vec;
  }
  else
  {
    if (size < (size_t) quad->n_points)
    {
      size_t  new_size = MAX(n_quad_points_max[quad->dim], quad->n_points);
      quad_vec_dd = MEM_REALLOC(quad_vec_dd, size, new_size, REAL_DD);
      size = new_size;
    }
    val = quad_vec_dd;
  }

  for (i = 0; i < quad->n_points; i++)
  {
    grd_d = (*f)(quad->lambda[i]);
    MCOPY_DOW(grd_d, val[i]);
  }

  return((const REAL_DD *) val);
}

static inline
const REAL_D *grd_fx_at_qp(REAL_D vec[],
			   const EL_INFO *el_info,
			   const QUAD *quad,
			   GRD_FCT_AT_X grd_f)
{
  FUNCNAME("grd_fx_at_qp");
  static REAL_D *quad_vec_d = NULL;
  static size_t size = 0;
  REAL_D        *val;
  int           i;
  PARAMETRIC    *parametric = el_info->mesh->parametric;

  if (vec) {
    val = vec;
  } else {
    if (size < (size_t)quad->n_points) {
      size_t  new_size = MAX(n_quad_points_max[quad->dim], quad->n_points);
      quad_vec_d = MEM_REALLOC(quad_vec_d, size, new_size, REAL_D);
      size = new_size;
    }
    val = quad_vec_d;
  }

  if (parametric) {
    REAL_D x[quad->n_points];

    parametric->coord_to_world(el_info, quad, 0, NULL, x);
    for (i = 0; i < quad->n_points; i++) {
      grd_f(x[i], val[i]);
    }
  } else {
    for (i = 0; i < quad->n_points; i++) {
      REAL_D x;

      coord_to_world(el_info, quad->lambda[i], x);
      grd_f(x, val[i]);
    }
  }

  return (const REAL_D *)val;
}

static inline
const REAL_DD *
grd_fx_d_at_qp(REAL_DD vec[],
	       const EL_INFO *el_info,
	       const QUAD *quad,
	       GRD_FCT_D_AT_X grd_f)
{
  FUNCNAME("grd_fx_d_at_qp");
  static REAL_DD *quad_vec_dd = NULL;
  static size_t  size = 0;
  REAL_DD        *val;
  int            i;
  PARAMETRIC     *parametric = el_info->mesh->parametric;

  if (vec) {
    val = vec;
  } else {
    if (size < (size_t) quad->n_points) {
      size_t  new_size = MAX(n_quad_points_max[quad->dim], quad->n_points);
      quad_vec_dd = MEM_REALLOC(quad_vec_dd, size, new_size, REAL_DD);
      size = new_size;
    }
    val = quad_vec_dd;
  }

  if (parametric) {
    REAL_D x[quad->n_points];

    parametric->coord_to_world(el_info, quad, 0, NULL, x);
    for (i = 0; i < quad->n_points; i++) {
      grd_f(x[i], val[i]);
    }
  } else {
    for (i = 0; i < quad->n_points; i++) {
      REAL_D x;

      coord_to_world(el_info, quad->lambda[i], x);
      grd_f(x, val[i]);
    }
  }

  return (const REAL_DD *)val;
}

static inline
const REAL_D *grd_f_loc_at_qp(REAL_D vec[],
			      const EL_INFO *el_info,
			      const QUAD *quad,
			      const REAL_BD Lambda,
			      GRD_LOC_FCT_AT_QP grd_f,
			      void *ud)
{
  FUNCNAME("grd_f_loc_at_qp");
  static REAL_D *quad_vec_d = NULL;
  static size_t size = 0;
  REAL_D        *val;
  int           i;

  if (vec) {
    val = vec;
  } else {
    if (size < (size_t)quad->n_points) {
      size_t  new_size = MAX(n_quad_points_max[quad->dim], quad->n_points);
      quad_vec_d = MEM_REALLOC(quad_vec_d, size, new_size, REAL_D);
      size = new_size;
    }
    val = quad_vec_d;
  }

  for (i = 0; i < quad->n_points; i++) {
    grd_f(val[i], el_info, Lambda, quad, i, ud);
  }

  return (const REAL_D *)val;
}

static inline
const REAL_D *
param_grd_f_loc_at_qp(REAL_D vec[],
		      const EL_INFO *el_info,
		      const QUAD *quad,
		      const REAL_BD Lambda[],
		      GRD_LOC_FCT_AT_QP grd_f, void *ud)
{
  FUNCNAME("param_grd_f_loc_at_qp");
  static REAL_D *quad_vec_d = NULL;
  static size_t size = 0;
  REAL_D        *val;
  int           i;

  if (vec) {
    val = vec;
  } else {
    if (size < (size_t)quad->n_points) {
      size_t  new_size = MAX(n_quad_points_max[quad->dim], quad->n_points);
      quad_vec_d = MEM_REALLOC(quad_vec_d, size, new_size, REAL_D);
      size = new_size;
    }
    val = quad_vec_d;
  }

  for (i = 0; i < quad->n_points; i++) {
    grd_f(val[i], el_info, (const REAL_D *)Lambda[i], quad, i, ud);
  }

  return (const REAL_D *)val;
}

static inline
const REAL_DD *
grd_f_loc_d_at_qp(REAL_DD vec[],
		  const EL_INFO *el_info,
		  const QUAD *quad,
		  const REAL_BD Lambda,
		  GRD_LOC_FCT_D_AT_QP grd_f,
		  void *ud)
{
  FUNCNAME("grd_f_loc_d_at_qp");
  static REAL_DD *quad_vec_dd = NULL;
  static size_t  size = 0;
  REAL_DD        *val;
  int            i;

  if (vec) {
    val = vec;
  } else {
    if (size < (size_t) quad->n_points) {
      size_t  new_size = MAX(n_quad_points_max[quad->dim], quad->n_points);
      quad_vec_dd = MEM_REALLOC(quad_vec_dd, size, new_size, REAL_DD);
      size = new_size;
    }
    val = quad_vec_dd;
  }

  for (i = 0; i < quad->n_points; i++) {
    grd_f(val[i], el_info, Lambda, quad, i, ud);
  }

  return (const REAL_DD *)val;
}

static inline
const REAL_DD *
param_grd_f_loc_d_at_qp(REAL_DD vec[],
			const EL_INFO *el_info,
			const QUAD *quad,
			const REAL_BD Lambda[],
			GRD_LOC_FCT_D_AT_QP grd_f,
			void *ud)
{
  FUNCNAME("param_grd_f_loc_d_at_qp");
  static REAL_DD *quad_vec_dd = NULL;
  static size_t  size = 0;
  REAL_DD        *val;
  int            i;

  if (vec) {
    val = vec;
  } else {
    if (size < (size_t) quad->n_points) {
      size_t  new_size = MAX(n_quad_points_max[quad->dim], quad->n_points);
      quad_vec_dd = MEM_REALLOC(quad_vec_dd, size, new_size, REAL_DD);
      size = new_size;
    }
    val = quad_vec_dd;
  }

  for (i = 0; i < quad->n_points; i++) {
    grd_f(val[i], el_info, (const REAL_D *)Lambda[i], quad, i, ud);
  }

  return (const REAL_DD *)val;
}

/* >>> */

/* >>> */
 
#endif /* _ALBERTA_EVALUATE_H_ */
