/*--------------------------------------------------------------------------*/
/* ALBERTA:  an Adaptive multi Level finite element toolbox using           */
/*           Bisectioning refinement and Error control by Residual          */
/*           Techniques for scientific Applications                         */
/*--------------------------------------------------------------------------*/
/*                                                                          */
/* file:     parametric_1d.c                                                */
/*                                                                          */
/* description: Support for parametric elements in 1D                       */
/*--------------------------------------------------------------------------*/
/*                                                                          */
/*  authors:   Daniel Koester                                               */
/*             Institut fuer Mathematik                                     */
/*             Universitaet Augsburg                                        */
/*             Universitaetsstr. 14                                         */
/*             D-86159 Augsburg, Germany                                    */
/*                                                                          */
/*             Claus-Justus Heine                                           */
/*             Numerische Mathematik fuer Hoechstleistungsrechner           */
/*             IANS / Universitaet Stuttgart                                */
/*             Pfaffenwaldring 57                                           */
/*             70569 Stuttgart, Germany                                     */
/*                                                                          */
/*  http://www.alberta-fem.de                                               */
/*                                                                          */
/*  (c) by D. Koester (2005),                                               */
/*         C.-J. Heine (2006-2012).                                         */
/*--------------------------------------------------------------------------*/

#ifdef MESH_DIM
# undef MESH_DIM
#endif
#define MESH_DIM 1

#ifdef N_BAS_MAX
# undef N_BAS_MAX
#endif
#define N_BAS_MAX N_BAS_LAGRANGE(LAGRANGE_DEG_MAX, MESH_DIM)

static const REAL_B vertex_bary_1d[N_BAS_LAG_1D(1)] = {
  { 1.0, 0.0 }, { 0.0, 1.0 }
};

static const REAL_B mid_lambda_1d = { 0.5, 0.5 };

/*--------------------------------------------------------------------------*/
/* Functions for affine elements as parametric elements. (suffix 1_1d)      */
/*--------------------------------------------------------------------------*/

static bool param_init_element1_1d(const EL_INFO *el_info,
				   const PARAMETRIC *parametric)
{
  LAGRANGE_PARAM_DATA *data = (LAGRANGE_PARAM_DATA *)parametric->data;
  DOF_REAL_D_VEC *coords = data->coords;
  int node_v, n0_v, i;
  EL *el = el_info->el;
  EL_INFO *mod_el_info = (EL_INFO *)el_info; /* modifyable pointer reference */

  data->el = el_info->el;

  node_v = el_info->mesh->node[VERTEX];
  n0_v   = coords->fe_space->admin->n0_dof[VERTEX];
   
  if (!parametric->use_reference_mesh) {
    data->local_coords = mod_el_info->coord;
    mod_el_info->fill_flag |= FILL_COORDS;
  } else {
    data->local_coords = data->param_local_coords;
  }
  for (i = 0; i < N_VERTICES_1D; i++) {
    COPY_DOW(coords->vec[el->dof[node_v+i][n0_v]], data->local_coords[i]);
  }

  return false; /* not really parametric */
}

static void det1_1d(const EL_INFO *el_info, const QUAD *quad, int N,
		    const REAL_B lambda[], REAL dets[])
{
  REAL det;
  int  n;

  det = el_det_1d(el_info);

  if (quad) {
    N = quad->n_points;
  }

  for (n = 0; n < N; n++) {
    dets[n] = det;
  }
 
  return;
}


static void grd_lambda1_1d(const EL_INFO *el_info, const QUAD *quad,
			   int N, const REAL_B lambda[],
			   REAL_BD grd_lam[], REAL_BDD D2_lam[], REAL dets[])
{
  int i, n;

  dets[0] = el_grd_lambda_1d(el_info, grd_lam[0]);

  if (quad) {
    N = quad->n_points;
  }

  for (n = 1; n < N; n++) {
    for (i = 0; i < N_LAMBDA_1D; i++) {
      COPY_DOW(grd_lam[0][i], grd_lam[n][i]);
    }
    for (; i < N_LAMBDA_MAX; i++) {
      SET_DOW(0.0, grd_lam[n][i]);
    }
    if (dets) {
      dets[n] = dets[0];
    }
  }

  if (D2_lam) {
    for (n = 0; n < N; n++) {
      for (i = 0; i < N_LAMBDA_MAX;i++) {
	MSET_DOW(0.0, D2_lam[n][i]);
      }
    }
  }
}

static void
grd_world1_1d(const EL_INFO *el_info, const QUAD *quad,
	      int N, const REAL_B lambda[],
	      REAL_BD grd_Xtr[], REAL_BDB D2_Xtr[], REAL_BDBB D3_Xtr[])
{
  int i;

  if (quad) {
    N = quad->n_points;
  }

  for (i = 0; i < N_LAMBDA_1D; i++) {
    COPY_DOW(el_info->coord[i], grd_Xtr[0][i]);
  }
  for (; i < N_LAMBDA_MAX; i++) {
    SET_DOW(0.0, grd_Xtr[0][i]);
  }
  
  memcpy(grd_Xtr+1, grd_Xtr[0], (N-1)*sizeof(REAL_BD));

  if (D2_Xtr) {
    memset(D2_Xtr, 0, N*sizeof(REAL_BDB));
  }

  if (D3_Xtr) {
    memset(D3_Xtr, 0, N*sizeof(REAL_BDBB));
  }
}

/* Compute the co-normal for WALL at the barycentric coordinates
 * specified by LAMBDA. LAMBDA are 1d barycentric coordinates,
 * i.e. lambda[wall] == 0.0.
 */
static void
wall_normal1_1d(const EL_INFO *el_info, int wall,
		const QUAD *quad,
		int n, const REAL_B lambda[],
		REAL_D normals[], REAL_DB grd_normals[], REAL_DBB D2_normals[],
		REAL dets[])
{
  int i;

  if (quad) {
    n = quad->n_points;
  }

  if (grd_normals) {
    memset(grd_normals, 0, n*sizeof(REAL_DB));
  }

  if (D2_normals) {
    memset(D2_normals, 0, n*sizeof(REAL_DBB));
  }

  if (normals) {
    REAL detsbuffer[n];
    
    if (!dets) {
      dets = detsbuffer;
    }
    dets[0] = get_wall_normal_1d(el_info, wall, normals[0]);

    for (i = 1; i < n; i++) {
      dets[i] = dets[0];
      COPY_DOW(normals[0], normals[i]);
    }
  } else {
    dets[0] = get_wall_normal_1d(el_info, wall, NULL);
    for (i = 1; i < n; i++) {
      dets[i] = dets[0];
    }
  }
}


/****************************************************************************/
/* fill_coords1_1d(data): initialize the DOF_REAL_D_VEC coords containing   */
/* the position data of the parametric elements (coordinates of vertices in */
/* this case).                                                              */
/****************************************************************************/

static void fill_coords1_1d(LAGRANGE_PARAM_DATA *data)
{
  DOF_REAL_D_VEC  *coords   = data->coords;
  NODE_PROJECTION *n_proj   = data->n_proj;
  bool            selective = n_proj != NULL;
  const NODE_PROJECTION *act_proj;
  FLAGS           fill_flag = CALL_LEAF_EL|FILL_COORDS|FILL_PROJECTION;
  const BAS_FCTS  *bfcts;
  const DOF_ADMIN *admin;
  MESH            *mesh;
  int             i;
  DOF             dof[N_VERTICES_1D];

  admin = coords->fe_space->admin;
  bfcts = coords->fe_space->bas_fcts;
  mesh  = coords->fe_space->mesh;

  TRAVERSE_FIRST(mesh, -1, fill_flag) {
    REAL *vec;

    GET_DOF_INDICES(bfcts, el_info->el, admin, dof);

    for (i = 0; i < N_VERTICES_1D; i++) {
      vec = coords->vec[dof[i]];

      COPY_DOW(el_info->coord[i], vec);

      /* Look for a projection function that applies to vertex[i]. */
      /* Apply this projection if found.                           */

      if (!selective || n_proj->func) {
	act_proj = wall_proj(el_info, 1-i);

	if (!act_proj) {
	  act_proj = el_info->active_projection;
	}

	if (act_proj && act_proj->func && (!selective || act_proj == n_proj)) {
	  act_proj->func(vec, el_info, vertex_bary_1d[i]);
	}
      }
    }
  } TRAVERSE_NEXT();

  return;
}


/****************************************************************************/
/* refine_interpol1_1d(drdv,list,n): update coords vector during refinement.*/
/****************************************************************************/

static void refine_interpol1_1d(DOF_REAL_D_VEC *drdv, RC_LIST_EL *list, int n)
{
  /*FUNCNAME("refine_interpol1_1d");*/
  MESH                *mesh    = drdv->fe_space->mesh;
  LAGRANGE_PARAM_DATA *data    = (LAGRANGE_PARAM_DATA *)mesh->parametric->data;
  NODE_PROJECTION     *n_proj  = data->n_proj;
  bool                selective = n_proj != NULL;
  const NODE_PROJECTION *act_proj;
  EL     *el;
  REAL_D *vec;
  DOF    dof_new, dof0, dof1;
  int    n0, j;
 
  vec = drdv->vec;
  n0 = drdv->fe_space->admin->n0_dof[VERTEX];
  el = list->el_info.el;

  dof0 = el->dof[0][n0];           /* left point of refinement elem. */
  dof1 = el->dof[1][n0];           /* right point */
  dof_new = el->child[0]->dof[1][n0];

  for (j = 0; j < DIM_OF_WORLD; j++)
    vec[dof_new][j] = 0.5*(vec[dof0][j] + vec[dof1][j]);

  act_proj = list->el_info.active_projection;

  if (act_proj && act_proj->func && (!selective || n_proj == act_proj)) {
    act_proj->func(vec[dof_new], &list->el_info, mid_lambda_1d);
  }

  _AI_refine_update_bbox(mesh, vec[dof_new]);

  return;
}

/*--------------------------------------------------------------------------*/
/* Common functions for higher order elements. (suffix Y_1d)                */
/*--------------------------------------------------------------------------*/

static void vertex_coordsY_1d(EL_INFO *el_info)
{
  PARAMETRIC          *parametric = el_info->mesh->parametric;
  LAGRANGE_PARAM_DATA *data       = (LAGRANGE_PARAM_DATA *)parametric->data;
  DOF_REAL_D_VEC      *coords     = data->coords;
  EL                  *el         = el_info->el;
  int node_v, n0_v, i;

  node_v = el_info->mesh->node[VERTEX];
  n0_v   = coords->fe_space->admin->n0_dof[VERTEX];
    
  el_info->fill_flag |= FILL_COORDS;
  for (i = 0; i < N_VERTICES_1D; i++) {
    COPY_DOW(coords->vec[el->dof[node_v+i][n0_v]], el_info->coord[i]);
  }
}


/*--------------------------------------------------------------------------*/
/* param_init_element: returns true iff we are on a parametric element.     */
/*--------------------------------------------------------------------------*/

static bool param_init_elementY_1d(const EL_INFO *el_info,
				   const PARAMETRIC *parametric)
{
  LAGRANGE_PARAM_DATA *data    = (LAGRANGE_PARAM_DATA *)parametric->data;
  DOF_PTR_VEC         *edge_pr = data->edge_projections;
  DOF_REAL_D_VEC      *coords  = data->coords;
  int                 node_c, n0_edge_pr;
  const BAS_FCTS      *bas_fcts;

  if (data->el != el_info->el) {

    data->el = el_info->el;

    if (data->strategy == PARAM_ALL) { /* use a short path */
      bas_fcts = data->coords->fe_space->bas_fcts;
      bas_fcts->get_real_d_vec(data->local_coords, el_info->el, coords);
      return true;
    } else {
      node_c = el_info->mesh->node[CENTER];
      n0_edge_pr = edge_pr->fe_space->admin->n0_dof[CENTER];
      data->i_am_affine = !edge_pr->vec[el_info->el->dof[node_c][n0_edge_pr]];

      if (!data->i_am_affine) { /* full-featured parametric element */
	bas_fcts = data->coords->fe_space->bas_fcts;
	data->local_coords = data->param_local_coords;
	bas_fcts->get_real_d_vec(data->local_coords, el_info->el, coords);
	return true;
      }
      if (parametric->use_reference_mesh) {
	const BAS_FCTS *bas_fcts = data->coords->fe_space->bas_fcts;
	data->local_coords = data->param_local_coords;
	bas_fcts->get_real_d_vec(data->local_coords, data->el, coords);
      }
    }
  }
  
  if (!parametric->use_reference_mesh) {
    EL_INFO *mod_el_info = (EL_INFO *)el_info; /* modifiable pointer ref */
    if (data->i_am_affine) {
      /* Affine, need only the vertices,  use el_info->coord */
      int node_v, n0_v, i;
      EL *el = el_info->el;

      node_v = el_info->mesh->node[VERTEX];
      n0_v   = coords->fe_space->admin->n0_dof[VERTEX];
   
      data->local_coords = mod_el_info->coord;
      mod_el_info->fill_flag |= FILL_COORDS;
      for (i = 0; i < N_VERTICES_1D; i++) {
	COPY_DOW(coords->vec[el->dof[node_v+i][n0_v]], data->local_coords[i]);
      }
    } else {
      mod_el_info->fill_flag &= ~FILL_COORDS;
    }
  }

  return !data->i_am_affine;
}

/* Much of the parametric stuff is actually dimension independent,
 * therefore a bunch of functions shared between (at least)
 * parametric_2d.c and parametric_3d.c is implemented in the following
 * header-file:
 */
#include "parametric_intern.h"

static inline
void compute_wall_DD_1d(DD_DATA_1D *data,
			int wall,
			const QUAD_FAST *quad_fast)
{
  const BAS_FCTS *bas_fcts = quad_fast->bas_fcts;
  const QUAD     *quad     = quad_fast->quad;
  const REAL     *grd;
  int i1, i;
  
  i1 = 1 - wall;
  for (i = 0; i < bas_fcts->n_bas_fcts; i++) {
    grd = GRD_PHI(bas_fcts, i, quad->lambda[0]);
    data->wall_DD[i] = grd[i1] - grd[wall];
  }
}

/* Compute the co-normal for WALL at a given quadrature point. Helper
 * routine for wall_normalY_1d().
 *
 * The following is in principle Cramer's rule, used to extract one
 * line of grd_lambda (which, of course, just holds the wall-normal)
 */
static inline REAL wall_normal_iq_1d(REAL_D *const coords,
				     const REAL DD[], int n_bas, int wall,
				     REAL_D normal)
{
  int i;
  
  if (normal) {
    SET_DOW(0.0, normal);
    for (i = 0; i < n_bas; i++) {
      AXPY_DOW(DD[i], coords[i], normal);
    }
    SCAL_DOW(1.0/NORM_DOW(normal), normal);
  }

  return 1.0;
}

/* Compute the co-normal for WALL at the barycentric coordinates
 * specified by LAMBDA. LAMBDA are 1d barycentric coordinates,
 * i.e. lambda[wall] == 0.0.
 */
static void
wall_normalY_1d(const EL_INFO *el_info, int wall,
		const QUAD *quad,
		int n, const REAL_B lambda[],
		REAL_D normals[], REAL_DB grd_normals[], REAL_DBB D2_normals[],
		REAL dets[])
{
  LAGRANGE_PARAM_DATA *data =
    ((LAGRANGE_PARAM_DATA *)el_info->mesh->parametric->data);
  REAL_D         *coords = data->local_coords;
  const BAS_FCTS *bas_fcts;
  int iq, i1, n_bas, i;

  /* First of all, check if we are on a parametric simplex.            */
  /* If not, treat this simplex as an affine simplex, even though some */
  /* higher order vertices might be shifted.                           */
  if(data->i_am_affine) {
    wall_normal1_1d(el_info, wall, quad, n, lambda,
		    normals, grd_normals, D2_normals, dets);
    return;
  }

  bas_fcts = data->coords->fe_space->bas_fcts;

  if (quad) {
    n = quad->n_points;
  }

  if (grd_normals) { /* grin */
    memset(grd_normals, 0, n * sizeof(REAL_DB));
  }

  if (D2_normals) { /* grin */
    memset(grd_normals, 0, n * sizeof(REAL_DBB));
  }

  if (quad) {
    REAL detsbuffer[n];
    const DD_DATA_1D *dd_data = init_wall_dd_data_1d(el_info, quad, bas_fcts);

    DEBUG_TEST_EXIT(quad->n_points == 1,
		    "Dimension 0 quadrature with more than 1 point?\n");

    if (!dets) {
      dets = detsbuffer;
    }

    for (iq = 0; iq < quad->n_points; iq++) {
      dets[iq] = wall_normal_iq_1d(coords,
				   dd_data->wall_DD,
				   dd_data->n_bas_fcts,
				   wall,
				   normals ? normals[iq] : NULL);
    }
  } else {
    REAL detsbuffer[n];
    const REAL *grd;
    REAL       DD[N_BAS_MAX];
    
    i1 = 1 - wall; 
    n_bas = bas_fcts->n_bas_fcts;

    if (!dets) {
      dets = detsbuffer;
    }

    /* Mmmh. n should be 1 ... */
    for (iq = 0; iq < n; iq++) {
      SET_DOW(0.0, normals[iq]);
      for (i = 0; i < n_bas; i++) {
	grd = GRD_PHI(bas_fcts, i, lambda[iq]);
	DD[i] = grd[i1] - grd[wall];
      }

      dets[iq] = wall_normal_iq_1d(coords, DD, n_bas, wall,
				   normals ? normals[iq] : NULL);
    }
  }
}

/*--------------------------------------------------------------------------*/
/* Functions for quadratic elements. (suffix 2_1d)                          */
/*--------------------------------------------------------------------------*/

static void refine_interpol2_1d(DOF_REAL_D_VEC *drdv, RC_LIST_EL *list,
				int n_neigh)
{
  /* FUNCNAME("refine_interpol2_1d"); */
  MESH                *mesh     = drdv->fe_space->mesh;
  LAGRANGE_PARAM_DATA *data     = (LAGRANGE_PARAM_DATA *)mesh->parametric->data;
  PARAM_STRATEGY      strategy  = data->strategy;
  int                 node_c    = mesh->node[CENTER];
  const DOF_ADMIN     *admin    = drdv->fe_space->admin;
  DOF_PTR_VEC         *edge_pr  = data->edge_projections;
  REAL_D              *vec      = drdv->vec;
  NODE_PROJECTION     *n_proj   = data->n_proj;
  bool                selective = n_proj != NULL;
  const NODE_PROJECTION *act_proj;
  EL                  *el;
  DOF                 cdof[N_BAS_LAG_1D(2)];
  REAL_D              pvec[N_BAS_LAG_1D(2)];
  int                 n;
  int                 n0_edge_pr = -1;
  const BAS_FCTS      *bas_fcts = drdv->fe_space->bas_fcts;
  static const REAL_B child_mid_lambda[] = {
    { 0.75, 0.25 },
    { 0.25, 0.75 }
  };

  el   = list->el_info.el;
  bas_fcts->get_real_d_vec(pvec, el, drdv);
 
  if (edge_pr) {
    n0_edge_pr = edge_pr->fe_space->admin->n0_dof[CENTER];
  }

/****************************************************************************/
/*  DOFs on first child                                                     */
/****************************************************************************/
  GET_DOF_INDICES(bas_fcts, el->child[0], admin, cdof);

  if(strategy != PARAM_STRAIGHT_CHILDS)
    for (n = 0; n < DIM_OF_WORLD; n++) {
      vec[cdof[1]][n] = pvec[2][n];
      vec[cdof[2]][n] =(0.375*pvec[0][n] - 0.125*pvec[1][n] + 0.75*pvec[2][n]);
    }
  else
    for (n = 0; n < DIM_OF_WORLD; n++) {
      vec[cdof[1]][n] = pvec[2][n];
      vec[cdof[2]][n] = 0.5 * (pvec[0][n] + pvec[2][n]);
    }   

  act_proj = list->el_info.active_projection;
  if (act_proj && (!selective || act_proj == n_proj)) {
    if (act_proj->func) {
      act_proj->func(vec[cdof[2]], &list->el_info, child_mid_lambda[0]);
      _AI_refine_update_bbox(mesh, vec[cdof[2]]);
    }
   
    if(edge_pr) {
      edge_pr->vec[el->child[0]->dof[node_c][n0_edge_pr]] = (void *)act_proj;
    }
  } else if (edge_pr) {
    edge_pr->vec[el->child[0]->dof[node_c][n0_edge_pr]] = NULL;
  }

/****************************************************************************/
/*  DOF on second child                                                     */
/****************************************************************************/

  GET_DOF_INDICES(bas_fcts, el->child[1], admin, cdof);

  if(strategy != PARAM_STRAIGHT_CHILDS)
    for (n = 0; n < DIM_OF_WORLD; n++)
      vec[cdof[2]][n] =(-0.125*pvec[0][n]+ 0.375*pvec[1][n] + 0.75*pvec[2][n]);
  else
    for (n = 0; n < DIM_OF_WORLD; n++)
      vec[cdof[2]][n] = 0.5 * (pvec[1][n]+ pvec[2][n]);

  act_proj = list->el_info.active_projection;
  if (act_proj && (!selective || n_proj == act_proj)) {
    if (act_proj->func) {
      act_proj->func(vec[cdof[2]], &list->el_info, child_mid_lambda[1]);
      _AI_refine_update_bbox(mesh, vec[cdof[2]]);
    }
    if (edge_pr) {
      edge_pr->vec[el->child[1]->dof[node_c][n0_edge_pr]] = (void *)act_proj;
    }
  } else if (edge_pr) {
    edge_pr->vec[el->child[1]->dof[node_c][n0_edge_pr]] = NULL;
  }

  return;
}

static void coarse_interpol2_1d(DOF_REAL_D_VEC *drdv, RC_LIST_EL *list, int n)
{
  FUNCNAME("coarse_interpol2_1d");
  MESH                *mesh    = drdv->fe_space->mesh;
  const DOF_ADMIN     *admin   = drdv->fe_space->admin;
  LAGRANGE_PARAM_DATA *data    = (LAGRANGE_PARAM_DATA *)mesh->parametric->data;
  DOF_PTR_VEC         *edge_pr = data->edge_projections;
  REAL_D              *vec     = drdv->vec;
  EL                  *el;
  int                 node_v, node_c, n0_v, n0_c;
  DOF                 cdof, pdof;

  GET_DOF_VEC(vec, drdv);
  el = list->el_info.el;

  node_v = mesh->node[VERTEX];       
  node_c = mesh->node[CENTER];
  n0_v   = admin->n0_dof[VERTEX];
  n0_c   = admin->n0_dof[CENTER];

/****************************************************************************/
/*  copy values at refinement vertex to the parent center DOF.              */
/****************************************************************************/

  cdof = el->child[0]->dof[node_v+1][n0_v];      /* newest vertex is dim */
  pdof = el->dof[node_c][n0_c];

  COPY_DOW(vec[cdof], vec[pdof]);

  if(edge_pr) {
    int n0_edge_pr = edge_pr->fe_space->admin->n0_dof[CENTER];

    pdof = el->dof[node_c][n0_edge_pr];
    cdof = el->child[0]->dof[node_c][n0_edge_pr];

    edge_pr->vec[pdof] = edge_pr->vec[cdof];
  }

  return;
}

static void fill_coords2_1d(LAGRANGE_PARAM_DATA *data)
{
  DOF_REAL_D_VEC  *coords  = data->coords;
  MESH            *mesh    = coords->fe_space->mesh;
  const DOF_ADMIN *admin   = coords->fe_space->admin;
  const BAS_FCTS  *bfcts   = coords->fe_space->bas_fcts;
  DOF_PTR_VEC     *edge_pr = data->edge_projections;
  NODE_PROJECTION *n_proj  = data->n_proj;
  bool            selective = n_proj != NULL;
  const NODE_PROJECTION *act_proj;
  FLAGS           fill_flag = CALL_LEAF_EL|FILL_COORDS|FILL_PROJECTION;
  DOF             dof[N_VERTICES_1D+N_EDGES_1D];
  int             n0_edge_pr = -1;
  int             node_c = -1;
  int             i;

  if (edge_pr) {
    node_c     = mesh->node[CENTER];
    n0_edge_pr = edge_pr->fe_space->admin->n0_dof[CENTER];
  }

  TRAVERSE_FIRST(mesh, -1, fill_flag) {
    REAL      *vec;

    GET_DOF_INDICES(bfcts, el_info->el, admin, dof);

    /* Fill the two vertices. */
    for (i = 0; i < N_VERTICES_1D; i++) {
      vec = coords->vec[dof[i]];

      COPY_DOW(el_info->coord[i], vec);

      /* Look for a projection function that applies to vertex[i]. */
      /* Apply this projection if found.                           */

      if (!selective || n_proj->func) {
	act_proj = el_info->active_projection;

	if (act_proj && act_proj->func && (!selective || act_proj == n_proj)){
	  act_proj->func(vec, el_info, vertex_bary_1d[i]);
	}
      }
    }

    /* Fill the midpoint. */
    vec = coords->vec[dof[2]];
    AXPBY_DOW(0.5, coords->vec[dof[0]], 0.5, coords->vec[dof[1]], vec);

    if (!selective || n_proj) {
      act_proj = el_info->active_projection;
     
      if (act_proj && (!selective || act_proj == n_proj)) {
	if (act_proj->func) {
	  act_proj->func(vec, el_info, mid_lambda_1d);
	}
	if (edge_pr) {
	  DOF edge_dof = el_info->el->dof[node_c][n0_edge_pr];
	  edge_pr->vec[edge_dof] = (void *)act_proj;
	}
      }
    } else if (edge_pr) {
      DOF edge_dof = el_info->el->dof[node_c][n0_edge_pr];
      edge_pr->vec[edge_dof] = NULL;
    }
  } TRAVERSE_NEXT();

  return;
}

static void refine_interpolY_1d(DOF_REAL_D_VEC *drdv, RC_LIST_EL *list,
				int n_neigh)
{
  /* FUNCNAME("refine_interpolY_1d"); */
  MESH                *mesh     = drdv->fe_space->mesh;
  LAGRANGE_PARAM_DATA *data     = (LAGRANGE_PARAM_DATA *)mesh->parametric->data;
  PARAM_STRATEGY      strategy  = data->strategy;
  REAL_D              *vec      = drdv->vec;
  const DOF_ADMIN     *admin    = drdv->fe_space->admin;
  const BAS_FCTS      *bas_fcts = drdv->fe_space->bas_fcts;
  DOF_PTR_VEC         *edge_pr  = data->edge_projections;
  NODE_PROJECTION     *n_proj   = data->n_proj;
  bool                selective = n_proj != NULL;
  const NODE_PROJECTION *act_proj;
  const EL_INFO       *el_info;
  EL                  *el;
  DOF                 cdof[N_BAS_MAX];
  DOF                 *pvdof;
  REAL                *pv[2];
  int                 i, nc, n0v, i_child;
  int                 n0_edge_pr = -1, node_c = -1;
  const REAL_B        *nodes = LAGRANGE_NODES(bas_fcts);

  el_info  = &list->el_info;
  el       = el_info->el;
  act_proj = el_info->active_projection;
 
  pvdof   = el->dof[drdv->fe_space->mesh->node[VERTEX]];
  n0v     = admin->n0_dof[VERTEX];
  pv[0]   = drdv->vec[pvdof[n0v+0]];
  pv[1]   = drdv->vec[pvdof[n0v+1]];
  nc      = admin->n_dof[CENTER];

  if (edge_pr) {
    node_c     = mesh->node[CENTER];
    n0_edge_pr = edge_pr->fe_space->admin->n0_dof[CENTER];
  }

  if (strategy != PARAM_STRAIGHT_CHILDS) {
    /* call the default interpolation routine */
    bas_fcts->real_d_refine_inter(drdv, list, n_neigh);
  }

  for (i_child = 0; i_child < 2; i_child++) {
   
    GET_DOF_INDICES(bas_fcts, el->child[i_child], admin, cdof);

    if (i_child == 0) {
      if (strategy == PARAM_STRAIGHT_CHILDS) {
	AXPBY_DOW(0.5, pv[0], 0.5, pv[1], vec[cdof[1]]);
      }

      if (act_proj && act_proj->func && (!selective || act_proj == n_proj)) {
	act_proj->func(vec[cdof[1]], el_info, mid_lambda_1d);
	_AI_refine_update_bbox(mesh, vec[cdof[1]]);
      }
    }

    /* Now loop through all center DOFs */
    if (true /* || nc */) {
      if (strategy == PARAM_STRAIGHT_CHILDS) {
	for (i = N_VERTICES_1D; i < N_VERTICES_1D+nc; i++) {
	  AXPBY_DOW(nodes[i][0], vec[cdof[0]], nodes[i][1], vec[cdof[1]],
		    vec[cdof[i]]);
	}
      }
     
      if (act_proj && (!selective || act_proj == n_proj)) {
	REAL_B lambda;

	if (act_proj->func) {
	  for (i = N_VERTICES_1D; i < N_VERTICES_1D+nc; i++) {
	    lambda[1-i_child] = nodes[i][1-i_child]*0.5;
	    lambda[i_child]   = 1.0 - lambda[1-i_child];

	    act_proj->func(vec[cdof[i]], el_info, lambda);
	    _AI_refine_update_bbox(mesh, vec[cdof[i]]);
	  }
	}

	if(edge_pr) {
	  DOF edge_dof = el_info->el->child[i_child]->dof[node_c][n0_edge_pr];
	  edge_pr->vec[edge_dof] = (void *)act_proj;
	}
      } else if (edge_pr) {
	DOF edge_dof = el_info->el->child[i_child]->dof[node_c][n0_edge_pr];
	edge_pr->vec[edge_dof] = NULL;

	if (strategy != PARAM_STRAIGHT_CHILDS) {
	  /* Undo potentially non-affine arrangement of center nodes. */
	  for (i = N_VERTICES_1D; i < N_VERTICES_1D+nc; i++) {
	    AXPBY_DOW(nodes[i][0], vec[cdof[0]], nodes[i][1], vec[cdof[1]],
		      vec[cdof[i]]);
	  }
	}
      }
    }
  }
}

static void coarse_interpolY_1d(DOF_REAL_D_VEC *drdv, RC_LIST_EL *list, int n)
{
  /* FUNCNAME("coarse_interpolY_1d"); */
  LAGRANGE_PARAM_DATA *data =
    (LAGRANGE_PARAM_DATA *)list->el_info.mesh->parametric->data;
  DOF_PTR_VEC         *edge_pr = data->edge_projections;
  const BAS_FCTS      *bas_fcts = drdv->fe_space->bas_fcts;
 
  /* Just call the default coarse_interpol() routine for the coords,
   * then update the edge_projections vector separately.
   */
  bas_fcts->real_d_coarse_inter(drdv, list, n);

  if(edge_pr) {
    int node_c     = edge_pr->fe_space->mesh->node[CENTER];
    int n0_edge_pr = edge_pr->fe_space->admin->n0_dof[CENTER];
    EL  *el        = list->el_info.el;
    DOF pdof       = el->dof[node_c][n0_edge_pr];
    DOF cdof       = el->child[0]->dof[node_c][n0_edge_pr];

    edge_pr->vec[pdof] = edge_pr->vec[cdof];
  }
}

static void fill_coordsY_1d(LAGRANGE_PARAM_DATA *data)
{
  DOF_REAL_D_VEC  *coords   = data->coords;
  MESH            *mesh     = coords->fe_space->mesh;
  const DOF_ADMIN *admin    = coords->fe_space->admin;
  const BAS_FCTS  *bas_fcts = coords->fe_space->bas_fcts;
  DOF_PTR_VEC     *edge_pr  = data->edge_projections;
  NODE_PROJECTION *n_proj   = data->n_proj;
  bool            selective = n_proj != NULL;
  const NODE_PROJECTION *act_proj;
  FLAGS           fill_flag = CALL_LEAF_EL|FILL_COORDS|FILL_PROJECTION;
  DOF             dof[N_BAS_MAX];
  int             n0_edge_pr = -1;
  int             node_c = -1;
  int             i;
  const REAL_B    *nodes = LAGRANGE_NODES(bas_fcts);

  if (edge_pr) {
    node_c     = mesh->node[CENTER];
    n0_edge_pr = edge_pr->fe_space->admin->n0_dof[CENTER];
  }

  TRAVERSE_FIRST(coords->fe_space->mesh, -1, fill_flag) {
    REAL *vec;

    GET_DOF_INDICES(bas_fcts, el_info->el, admin, dof);

    /* Fill the two vertices. */
    for (i = 0; i < N_VERTICES_1D; i++) {
      vec = coords->vec[dof[i]];

      COPY_DOW(el_info->coord[i], vec);

      /* Look for a projection function that applies to vertex[i]. */
      /* Apply this projection if found.                           */

      if (!selective || n_proj) {
	act_proj = el_info->active_projection;

	if (act_proj && act_proj->func && (!selective || act_proj == n_proj)) {
	  act_proj->func(vec, el_info, vertex_bary_1d[i]);
	}
      }
    }

    /* Fill the center DOFs. */
    for (i = N_VERTICES_1D; i < bas_fcts->n_bas_fcts; i++) {
      AXPBY_DOW(nodes[i][0], coords->vec[dof[0]],
		nodes[i][1], coords->vec[dof[1]],
		coords->vec[dof[i]]);
    }
   
    if (!selective || n_proj) {
      act_proj = el_info->active_projection;
     
      if (act_proj && (!selective || act_proj == n_proj)) {
	if (act_proj->func) {
	  for (i = N_VERTICES_1D; i < bas_fcts->n_bas_fcts; i++) {
	    act_proj->func(coords->vec[dof[i]], el_info, nodes[i]);
	  }
	}
	if (edge_pr) {
	  DOF edge_dof = el_info->el->dof[node_c][n0_edge_pr];
	  edge_pr->vec[edge_dof] = (void *)act_proj;
	}
      }
    } else if (edge_pr) {
      DOF edge_dof = el_info->el->dof[node_c][n0_edge_pr];
      edge_pr->vec[edge_dof] = NULL;
    }

    } TRAVERSE_NEXT();

  return;
}

#if DIM_MAX > 1
/* For mesh-hierarchies we simply hand down the coordinates and
 * possibly "edge_projectins" information from the master.
 */
static void
slave_refine_interpolY_1d(DOF_REAL_D_VEC *s_coords, RC_LIST_EL *list, int n)
{
  const FE_SPACE      *fe_space     = s_coords->fe_space;
  MESH                *slave        = fe_space->mesh;
  const DOF_ADMIN     *s_admin      = fe_space->admin;
  PARAMETRIC          *s_parametric = slave->parametric;
  LAGRANGE_PARAM_DATA *s_data       = (LAGRANGE_PARAM_DATA *)s_parametric->data;
  MESH                *master       = get_master(slave);
  DOF_PTR_VEC         *s_edge_pr, *m_edge_pr = NULL;
  DOF_REAL_D_VEC      *m_coords;
  const BAS_FCTS      *m_bfcts;
  const DOF_ADMIN     *m_admin;
  const EL_INFO *el_info;
  EL *m_el, *el, *child, *m_child;
  int wall;
  int n0_v, node_c, n0_c, n_c;
  int m_n0_v, m_node_e, m_n0_e, m_n_e;
  int n0_edge_pr = -1, m_n0_edge_pr = -1;
  int m_edge, s_dof, m_dof, s_dof_loc, m_dof_loc;
  int i_child, i, m_i;
  const int *trace_map;   

  m_coords = ((LAGRANGE_PARAM_DATA *)master->parametric->data)->coords;
  m_bfcts  = m_coords->fe_space->bas_fcts;
  m_admin  = m_coords->fe_space->admin;

  if ((s_edge_pr = s_data->edge_projections)) {
    n0_edge_pr = s_edge_pr->fe_space->admin->n0_dof[CENTER];
    m_edge_pr =
      ((LAGRANGE_PARAM_DATA *)master->parametric->data)->edge_projections;
    m_n0_edge_pr = m_edge_pr->fe_space->admin->n0_dof[EDGE];
  }

  n0_v   = s_admin->n0_dof[VERTEX];

  n_c    = s_admin->n_dof[CENTER];
  n0_c   = s_admin->n0_dof[CENTER];
  node_c = slave->node[CENTER];

  m_n0_v   = m_admin->n0_dof[VERTEX];

  m_n_e    = m_admin->n_dof[EDGE];
  m_n0_e   = m_admin->n0_dof[EDGE];
  m_node_e = master->node[EDGE];

  /* The slave->master map has not yet been set up for the children,
   * but we know that we are on the refinement patch of the parent,
   * child 0 belongs to edge 0 of m_el's child 0 etc.
   */
  el_info = &list->el_info;
  el      = el_info->el;
  m_el    = el_info->master.el;
  wall    = el_info->master.opp_vertex;

  for (i_child = 0; i_child < 2; i_child++) {

    m_child = m_el->child[i_child];
    child   = el->child[i_child];

    trace_map = m_bfcts->trace_dof_map[0][0][wall];

    if (i_child == 0) {
      /* New vertex */
      s_dof     = child->dof[0+1][n0_v]; /* vertices: node == 0 */
      m_dof     = m_child->dof[0+2][m_n0_v];
      COPY_DOW(m_coords->vec[m_dof], s_coords->vec[s_dof]);

      /* interior DOFs (edge-dofs on the master) */
      for (i = 0; i < n_c; i++) {
	s_dof_loc = N_VERTICES_1D + i;
	s_dof     = child->dof[node_c][n0_c + i];
	m_dof_loc = trace_map[s_dof_loc];
	m_edge = (m_dof_loc - N_VERTICES_2D) / m_n_e;
	m_i    = (m_dof_loc - N_VERTICES_2D) % m_n_e;
	m_dof  = m_child->dof[m_node_e + m_edge][m_n0_e + m_i];
	COPY_DOW(m_coords->vec[m_dof], s_coords->vec[s_dof]);

	if (s_edge_pr) {
	  s_dof = child->dof[node_c][n0_edge_pr];
	  m_dof = m_child->dof[m_node_e + m_edge][m_n0_edge_pr];
	  s_edge_pr->vec[s_dof] = m_edge_pr->vec[m_dof];
	}
      }
    }
  }
}

static void slave_fill_coordsY_1d(LAGRANGE_PARAM_DATA *s_data)
{
  DOF_REAL_D_VEC      *s_coords     = s_data->coords;
  DOF_PTR_VEC         *s_edge_pr    = s_data->edge_projections;
  MESH                *slave        = s_coords->fe_space->mesh;
  const BAS_FCTS      *s_bfcts      = s_coords->fe_space->bas_fcts;
  const DOF_ADMIN     *s_admin      = s_coords->fe_space->admin;
  MESH                *master       = get_master(slave);
  PARAMETRIC          *m_parametric = master->parametric;
  LAGRANGE_PARAM_DATA *m_data       = (LAGRANGE_PARAM_DATA *)m_parametric->data;
  DOF_REAL_D_VEC      *m_coords     = m_data->coords;
  DOF_PTR_VEC         *m_edge_pr    = m_data->edge_projections;
  const BAS_FCTS      *m_bfcts      = m_coords->fe_space->bas_fcts;
  const DOF_ADMIN     *m_admin      = m_coords->fe_space->admin;
  int                 n0_edge_pr    =
    s_edge_pr ? s_edge_pr->fe_space->admin->n0_dof[EDGE] : -1;
  int                 m_n0_edge_pr  =
    m_edge_pr ? m_edge_pr->fe_space->admin->n0_dof[EDGE] : -1;
  EL *m_el, *s_el;
  int  wall;
  int node_c;
  int m_node_e, m_n_e;
  int m_edge, s_dof, m_dof, s_dof_loc, m_dof_loc, i;
  const int *trace_map;
  DOF m_dofs[m_bfcts->n_bas_fcts];
  DOF s_dofs[s_bfcts->n_bas_fcts];

  node_c   = slave->node[CENTER];

  m_n_e    = m_admin->n_dof[EDGE];
  m_node_e = master->node[EDGE];

  TRAVERSE_FIRST(slave, -1, CALL_LEAF_EL|FILL_MASTER_INFO) {

    s_el = el_info->el;
    m_el = el_info->master.el;
    wall = el_info->master.opp_vertex;
    trace_map = m_bfcts->trace_dof_map[0][0][wall];

    GET_DOF_INDICES(s_bfcts, s_el, s_admin, s_dofs);
    GET_DOF_INDICES(m_bfcts, m_el, m_admin, m_dofs);

    for (i = 0; i < s_data->n_local_coords; i++) {
      COPY_DOW(m_coords->vec[m_dofs[trace_map[i]]], s_coords->vec[s_dofs[i]]);
    }
     
    if (s_edge_pr) {
      /* first determine the matching edge on the master */
      s_dof_loc = N_VERTICES_1D+0;
      m_dof_loc = trace_map[s_dof_loc];
      m_edge = (m_dof_loc - N_VERTICES_2D) / m_n_e;

      /* Copy over the edge_projections status */
      s_dof = s_el->dof[node_c][n0_edge_pr];
      m_dof = m_el->dof[m_node_e+m_edge][m_n0_edge_pr];
      s_edge_pr->vec[s_dof] = m_edge_pr->vec[m_dof];
    }
  } TRAVERSE_NEXT();
}
#endif

static PARAMETRIC lagrange_parametric1_1d = {
  "1D Lagrange parametric elements of degree 1",
  false,  /* not_all */
  false,  /* use_reference_mesh */
  param_init_element1_1d,
  vertex_coordsY_1d,
  param_coord_to_world,
  param_world_to_coord,
  det1_1d,
  grd_lambda1_1d,
  grd_world1_1d,
  wall_normal1_1d,
  NULL       /* data */
};

static PARAMETRIC lagrange_parametric2_1d = {
  "1D Lagrange parametric elements of degree 2",
  false,  /* not_all */
  false,  /* use_reference_mesh */
  param_init_elementY_1d,
  vertex_coordsY_1d,
  param_coord_to_world,
  param_world_to_coord,
  detY_1d,
  grd_lambdaY_1d,
  grd_worldY_1d,
  wall_normalY_1d,
  NULL       /* data */
};

static PARAMETRIC lagrange_parametricY_1d = {
  "1D Lagrange parametric elements of generic degree",
  false,  /* not_all */
  false,  /* use_reference_mesh */
  param_init_elementY_1d,
  vertex_coordsY_1d,
  param_coord_to_world,
  param_world_to_coord,
  detY_1d,
  grd_lambdaY_1d,
  grd_worldY_1d,
  wall_normalY_1d,
  NULL       /* data */
};
