/*******************************************************************************
 *
 * ALBERTA:  an Adaptive multi Level finite element toolbox  using
 *           Bisectioning refinement and Error control by Residual
 *           Techniques
 *
 * File:     ellipt-klein-bottle.c
 *
 * Description:  solver for a simple Poisson equation on a Klein's bottle:
 *
 *                 -\Delta_\Gamma u = f
 *                                u = g  on \partial\Gamma
 *
 * The major difference to ../3d/ellipt_moebius.c is that in this
 * example the coordinate functions are implemented as periodic finite
 * element functions, while the (explicit) paramter values at the
 * vertices of the mesh are kept in _NON_-periodic p.w. linear finite
 * element functions on the same mesh. In contrast to this
 * ../3d/ellipt_moebius.c uses the leaf-data construct to store the
 * parameter values at the vertices of the triangulation.
 *
 ******************************************************************************
 *
 * author(s): Claus-Justus Heine
 *            Abteilung fuer Angewandte Mathematik
 *            Albert-Ludwigs-Universitaet Freiburg
 *            Hermann-Herder-Str. 10
 *            79104 Freiburg
 *            Germany
 *            Claus.Heine@Mathematik.Uni-Freiburg.DE
 *
 * (c) by C.-J. Heine (2006-2007)
 *
 * Based on the famous ellipt.c demo-program by K.G. Siebert and A. Schmidt.
 *
 ******************************************************************************/

#include <alberta/alberta.h>

#include "../Common/alberta-demo.h"

#if DIM_OF_WORLD < 4
# error This is a test program for an embedded Klein bottle! (DOW >= 4)
#endif

#define MESH_DIM 2 /* ... just to avoid a literal 2 ... */

static bool do_graphics = true;

/*******************************************************************************
 * global variables: finite element space, discrete solution
 *                   load vector and system matrix
 *
 * These variables are kept global because they are shared across build(),
 * solve() and estimate().
 ******************************************************************************/

static const FE_SPACE *fe_space;  /* initialized by main() */
static DOF_REAL_VEC   *u_h;       /* initialized by main() */
static DOF_REAL_VEC   *f_h;       /* initialized by main() */
static DOF_MATRIX     *matrix;    /* initialized by main() */

/* We implement the parameterisation of the mesh by an explicit
 * parameterisation of the Klein's bottle. We remember the values of
 * the parameterisation at each vertex of the triangulation and
 * interpolate between them according to the baricentric coordinates
 * given by "lambda"
 */
static DOF_REAL_VEC *vertex_s;
static DOF_REAL_VEC *vertex_t;
static const BAS_FCTS *st_bfcts;

static const REAL A = 0.5; /* something like the "diameter" of the tube */

/*******************************************************************************
 * Extract the parameter values at the vertices, do some caching for
 * the sake of efficieny.
 ******************************************************************************/

static inline void get_st_at_vertex(REAL **s, REAL **t, EL *el)
{
  static EL *last_el;
  static REAL_B s_v, t_v;

  if (last_el != el) {
    st_bfcts->get_real_vec(s_v, el, vertex_s);
  /*  fill_el_real_vec(s_v, el, vertex_s); */
    st_bfcts->get_real_vec(t_v, el, vertex_t);
  /*  fill_el_real_vec(t_v, el, vertex_t); */
    last_el = el;
  }

  *s = s_v;
  *t = t_v;
}

/*******************************************************************************
 * struct ellipt_leaf_data: structure for storing one REAL value on each
 *                          leaf element as LEAF_DATA
 * rw_el_est():  return a pointer to the memory for storing the element
 *               estimate (stored as LEAF_DATA), called by ellipt_est()
 * get_el_est(): return the value of the element estimates (from LEAF_DATA),
 *               called by adapt_method_stat() and graphics()
 ******************************************************************************/

struct ellipt_leaf_data
{
  REAL estimate; /* one real for the estimate */
};

static REAL *rw_el_est(EL *el)
{
  if (IS_LEAF_EL(el))
    return &((struct ellipt_leaf_data *)LEAF_DATA(el))->estimate;
  else
    return NULL;
}

static REAL get_el_est(EL *el)
{
  if (IS_LEAF_EL(el))
    return ((struct ellipt_leaf_data *)LEAF_DATA(el))->estimate;
  else
    return 0.0;
}

/*******************************************************************************
 * Problem data: function a (_this_) Klein's bottle; need to compute
 * the Laplace-Beltrami of u(). This is nearly the same as in
 * ../Common/ellipt-periodic.c, just scaled to the parameter domain
 * (0,2 pi)^2.
 ******************************************************************************/

/* various parameters to tune the test problems. */

#define K_s 1.0 /* number of periods in s direction */
#define K_t 1.0 /* number of periods in t direction */

/* just something to avoid zero boundary conditions */
#define PHASE_OFFSET 0.0 /*0.378*/

static REAL u(REAL s, REAL t)
{
  REAL result;
  REAL k, phase;

  /* no phase-offset in x[0] direction */
  k      = 2.0*K_s-1.0;
  phase  = M_PI_2 * PHASE_OFFSET * k;
  result = sin(k*s/2.0 + phase) * sin(K_t*t);

  return result;
}

static REAL u_loc(const EL_INFO *el_info, const REAL_B lambda, void *ud)
{
  REAL *s_v, *t_v;
  
  get_st_at_vertex(&s_v, &t_v, el_info->el);

  return u(SCP_BAR(MESH_DIM, s_v, lambda), SCP_BAR(MESH_DIM, t_v, lambda));
}

static REAL u_at_qp(const EL_INFO *el_info, const QUAD *quad, int iq, void *ud)
{
  return u_loc(el_info, quad->lambda[iq], ud);
}

/* grd_u_at_qp() is used for H1_error(), however, using H1_error() in
 * the context of parametric meshes is somewhat wrong: in principle we
 * would first have to lift u_h to the continuous surface, and compute
 * the tangential gradient of the lifted u_h and compare it with the
 * exact solution on \Gamma, not on \Gamma_h. Here we simply compare
 * the tangential gradient of the smooth function on \Gamma with the
 * tangential gradient of u_h on \Gamma_h; this is nonsense.
 *
 * Instead we could lift the smooth function to \Gamma_h and compute
 * the tangential gradient of the lift on \Gamma_h; this is ok as long
 * as we use iso-parametric elements because then the error from the
 * discretization of the geometry is of higher order.
 */
static const REAL *grd_u_at_qp(REAL_D result, const EL_INFO *el_info,
			       const REAL_BD Lambda,
			       const QUAD *quad, int iq, void *ud)
{
  static REAL_D _result;
  REAL s, t, *s_v, *t_v;
  REAL u_s, u_t, k, phase;
  REAL_B grd_bar;
  const REAL *lambda = quad->lambda[iq];
  
  if (!result) {
    result = _result;
  }

  get_st_at_vertex(&s_v, &t_v, el_info->el);
  s = SCP_BAR(MESH_DIM, s_v, lambda);
  t = SCP_BAR(MESH_DIM, t_v, lambda);

  k     = 2.0*K_s-1.0;
  phase = M_PI_2 * PHASE_OFFSET * k;
  u_s   = k/2.0*cos(k*s/2.0 + phase)*sin(K_t*t);
  u_t   = K_t*sin(k*s/2.0 + phase)*cos(K_t*t);

  AXPBY_BAR(MESH_DIM, u_s, s_v, u_t, t_v, grd_bar);

  GRAD_DOW(MESH_DIM, Lambda, grd_bar, result);

  return result;
}

/* f is not so easy, unluckily:
 *
 * \Delta_\Gamma u(s, t)
 * =
 * \frac1{\sqrt{|g|}}\partial_i(\sqrt{|g|}g^{ij}\partial_j u)
 */
static REAL f(REAL s, REAL t)
{
  REAL cost    = cos(t), sint = sin(t);
  REAL cos2t   = SQR(cost);
  REAL coss2   = cos(s/2.0), sins2 = sin(s/2.0);
  REAL cos2s2  = SQR(coss2);
  REAL gss     = SQR(A)*(
    0.25-0.25*cos2t+1.0*cos2s2-1.0*cos2s2*cos2t+1.0/SQR(A)+2.0/A*sint*coss2);
  REAL gtt     = SQR(A);
  REAL ds_gss  = A*sins2*(-A*coss2+A*coss2*cos2t-sint);
  REAL dt_gss  = 0.5*A*cost*(A*sint+4.0*A*cos2s2*sint+4.0*coss2);
  REAL k       = 2.0*K_s-1.0;
  REAL phase   = M_PI_2 * PHASE_OFFSET * k;
  REAL ds_u    = k/2.0*cos(k*s/2.0 + phase)*sin(K_t*t);
  REAL dt_u    = K_t*sin(k*s/2.0 + phase)*cos(K_t*t);
  REAL d2s_u   = -SQR(k/2.0)*sin(k*s/2.0 + phase)*sin(K_t*t);
  REAL d2t_u   = -SQR(K_t)*sin(k*s/2.0 + phase)*sin(K_t*t);

  /* off-diagonals are zero, g_tt is constant */
  REAL f;
  
  f  = d2s_u/gss + d2t_u/gtt;
  f += -0.5*ds_u*ds_gss/SQR(gss) + 0.5*dt_u*dt_gss/gss/gtt;

  return -f; /* -Delta u */
}

static REAL f_loc(const EL_INFO *el_info, const QUAD *quad, int iq, void *ud)
{
  REAL s, t, *s_v, *t_v;
  
  get_st_at_vertex(&s_v, &t_v, el_info->el);
  s = SCP_BAR(MESH_DIM, s_v, quad->lambda[iq]);
  t = SCP_BAR(MESH_DIM, t_v, quad->lambda[iq]);

  return f(s, t);
}

/*****************************************************************************/

/* We use the following explicit parameterisation. Change to your
 * liking (but do not forget to change f, too; the Laplace-Beltrami
 * operator will change when you change the geometry of the surface).
 * s and t to range from 0 ... 2\pi.
 *
 * We use the following parameterisation with A == 0.5:
 *
 * x_0 = sin(s) (1 + A sin(t) cos(s/2)),
 * x_1 = cos(s) (1 + A sin(t) cos(s/2)),
 * x_2 = A cos(t),
 * x_3 = A sin(s/2) sin(t).
 *
 * This is just a 2-Torus where the smaller circle is rotated by an
 * angle of \pi through the additional 4th dimension before gluing it
 * together with the start of the tube.
 */
static void klein_bottle_proj_func(REAL_D vertex,
				   const EL_INFO *el_info,
				   const REAL_B lambda)
{
  REAL s, t, *s_v, *t_v, cos_s, sin_s, cos_s2, sin_s2, cos_t, sin_t;

  get_st_at_vertex(&s_v, &t_v, el_info->el);
  s = SCP_BAR(MESH_DIM, s_v, lambda);
  t = SCP_BAR(MESH_DIM, t_v, lambda);

  cos_s  = cos(s);
  sin_s  = sin(s);
  cos_s2 = cos(s/2.0);
  sin_s2 = sin(s/2.0);
  cos_t  = cos(t);
  sin_t  = sin(t);

  vertex[0] = sin_s*(1.0+A*sin_t*cos_s2);
  vertex[1] = cos_s*(1.0+A*sin_t*cos_s2);
  vertex[2] = A*cos_t;
  vertex[3] = A*sin_s2*sin_t;
}

/* init_node_projection(): initialize the node-projection functions
 * for the macro-triangulation. Return value != NULL for c == 0 means
 * to install a default projection for all nodes, otherwise (c-1)
 * means the number of the wall this projection is meant for.
 */
static NODE_PROJECTION *init_node_proj(MESH *mesh, MACRO_EL *mel, int c)
{
  static NODE_PROJECTION proj = { klein_bottle_proj_func };

  if (c == 0) {
    return &proj;
  }

  return NULL;
}

/*******************************************************************************
 * build(): assemblage of the linear system: matrix, load vector,
 *          boundary values, called by adapt_method_stat()
 *          on the first call initialize u_h, f_h, matrix and information
 *          for assembling the system matrix
 *
 * struct op_data: structure for passing information from init_element() to
 *                 LALt()
 *
 * init_element(): initialization on the element; calculates the
 *                 coordinates and |det DF_S| used by LALt; passes these
 *                 values to LALt via user_data,
 *                 called on each element by update_matrix()
 *
 * LALt():         implementation of -Lambda id Lambda^t for -Delta u,
 *                 called by update_matrix() after init_element()
 *
 ******************************************************************************/

struct op_data
{
  REAL_BD *Lambda;
  REAL    *det;
};

/* This version of init_element() is for entirely parametric meshes,
 * i.e. for the strategy PARAM_ALL.
 */
static bool init_element(const EL_INFO *el_info, const QUAD *quad[3], void *ud)
{
  struct op_data *info = (struct op_data *)ud;
  PARAMETRIC     *parametric = el_info->mesh->parametric;

  if (parametric->init_element(el_info, parametric)) {
    parametric->grd_lambda(el_info, quad[2], 0, NULL,
			   info->Lambda, NULL, info->det);
    return true;
  } else {
    /* This is the case where either the "new_coord()" componenent of
     * the mesh-elements is used to store the parametric co-ordinates,
     * or we use a linear finite element function to store the
     * co-ordinates at the vertices of the triangulation. In either
     * case we can use the ordinary el_grd_lambda() function because
     * parametric-init_element() is required to fill el_info->coord
     * for affine elements.
     */
    *info->det = el_grd_lambda(el_info, info->Lambda[0]);
    return false;
  }
}

const REAL_B *LALt(const EL_INFO *el_info, const QUAD *quad, 
		   int iq, void *ud)
{
  struct op_data *info = (struct op_data *)ud;
  int            i, j;
  static REAL_BB LALt;

  /* We always use det[iq], Lambda[iq], though in the case of a
   * p.w. linear parameterisation det[iq] and Lambda[iq] are note
   * initialized for "iq > 0". This is safe, because the assemble
   * machinery only calls us with "iq == 0" in the p.w. linear case.
   */
  for (i = 0; i < N_VERTICES(MESH_DIM); i++) {
    LALt[i][i]  = SCP_DOW(info->Lambda[iq][i], info->Lambda[iq][i]);
    LALt[i][i] *= info->det[iq];
    for (j = i+1; j <  N_VERTICES(MESH_DIM); j++) {
      LALt[i][j]  = SCP_DOW(info->Lambda[iq][i], info->Lambda[iq][j]);
      LALt[i][j] *= info->det[iq];
      LALt[j][i]  = LALt[i][j];
    }
  }

  return (const REAL_B *)LALt;
}

static void build(MESH *mesh, U_CHAR flag)
{
  FUNCNAME("build");
  static const EL_MATRIX_INFO *matrix_info = NULL;
  static const QUAD           *quad = NULL;

  dof_compress(mesh);
  MSG("%d DOFs for %s\n", fe_space->admin->size_used, fe_space->name);

  if (!matrix_info) { /* information for matrix assembling         */
    static struct op_data user_data;
    OPERATOR_INFO         o_info = { NULL, };
 
    if (mesh->parametric) {
      quad = get_quadrature(MESH_DIM, 2*fe_space->bas_fcts->degree + 2);
    } else {
     quad = get_quadrature(MESH_DIM, 2*fe_space->bas_fcts->degree-2);
    }
    user_data.Lambda = MEM_ALLOC(quad->n_points, REAL_BD);
    user_data.det    = MEM_ALLOC(quad->n_points, REAL);

    o_info.quad[2]        = quad;
    o_info.row_fe_space   = o_info.col_fe_space = fe_space;
    o_info.init_element   = init_element;
    o_info.LALt.real      = LALt;
    o_info.LALt_symmetric = true;        /* symmetric assemblage is faster */
    o_info.user_data      = &user_data;  /* user data. */

    /* We set "pw_const" in the case of a p.w. linear
     * parameterisation. This also makes sure that in this case
     * LALt(..., iq, ...) is only called with iq == 0.
     */
    o_info.LALt_pw_const =
      (mesh->parametric == NULL) || mesh->parametric->not_all;

    /* only FILL_BOUND is added automatically. */
    o_info.fill_flag = CALL_LEAF_EL|FILL_COORDS;

    matrix_info = fill_matrix_info(&o_info, NULL);
  }

  /* assembling of matrix */
  clear_dof_matrix(matrix);
  update_matrix(matrix, matrix_info, NoTranspose);

#if DEBUG_MATRIX
  {
    REAL sum;
    
    sum = 0.0;
    FOR_ALL_DOFS(matrix->row_fe_space->admin,
		 FOR_ALL_MAT_COLS(REAL, matrix->matrix_row[dof],
				  sum += row->entry[col_idx]));
    MSG("Matrix sum: %e\n", sum);
  }    
#endif

  /* assembling of load vector */
  dof_set(0.0, f_h);
  L2scp_fct_bas_loc(f_h, f_loc, NULL, 0, quad);

  /*  boundary values, the combination alpha_r < 0.0 flags automatic
   *  mean-value correction iff f_h has non-zero mean-value and no
   *  non-Neumann boundary conditions were detected during mesh
   *  traversal.
   */
  boundary_conditions_loc(NULL /* *matrix, only for Robin */,
			  f_h, u_h, NULL /* boundary-type vector */,
			  NULL /* bndry bit-mask */,
			  u_at_qp,
			  NULL /* gn_loc, inhomogenous Neumann conditions */,
			  NULL /* user-data */,
			  FILL_NOTHING /* fill_flags for g_loc(), gn_loc() */,
			  -1.0 /* alpha_r Robin b.c. */,
			  NULL /* bndry_quad */);
}

/*******************************************************************************
 * solve(): solve the linear system, called by adapt_method_stat()
 ******************************************************************************/
static void solve(MESH *mesh)
{
  FUNCNAME("solve");
  static REAL       tol = 1.e-8, ssor_omega = 1.0;
  static int        max_iter = 1000, info = 2, restart = 0, ssor_iter = 1;
  static OEM_PRECON icon = DiagPrecon;
  static OEM_SOLVER solver = NoSolver;
  const PRECON *precon;
  
  if (solver == NoSolver) {
    GET_PARAMETER(1, "solver", "%d", &solver);
    GET_PARAMETER(1, "solver tolerance", "%f", &tol);
    GET_PARAMETER(1, "solver precon", "%d", &icon);
    GET_PARAMETER(1, "solver max iteration", "%d", &max_iter);
    GET_PARAMETER(1, "solver info", "%d", &info);
    GET_PARAMETER(1, "precon ssor omega", "%f", &ssor_omega);
    GET_PARAMETER(1, "precon ssor iter", "%d", &ssor_iter);
    if (solver == GMRes)
      GET_PARAMETER(1, "solver restart", "%d", &restart);
  }
  precon = init_oem_precon(matrix, NULL, info, icon, ssor_omega, ssor_iter);
  oem_solve_s(matrix, NULL, f_h, u_h, solver, tol, precon, restart, max_iter, info);

  return;
}
#if 0
static void solve(MESH *mesh)
{
  FUNCNAME("solve");
  static REAL       tol = 1.e-8;
  static int        miter = 1000, info = 2, icon = 1, restart = 0;
  static OEM_SOLVER solver = NoSolver;

  if (solver == NoSolver) {
    GET_PARAMETER(1, "solver", "%d", &solver);
    GET_PARAMETER(1, "solver tolerance", "%f", &tol);
    GET_PARAMETER(1, "solver precon", "%d", &icon);
    GET_PARAMETER(1, "solver max iteration", "%d", &miter);
    GET_PARAMETER(1, "solver info", "%d", &info);
    if (solver == GMRes) {
      GET_PARAMETER(1, "solver restart", "%d", &restart);
    }
  }
  oem_solve_s(matrix, NULL, f_h, u_h, solver, tol, icon, restart, miter, info);

  return;
}
#endif
/*******************************************************************************
 * Functions for error estimate:
 * estimate():   calculates error estimate via ellipt_est()
 *               calculates exact error also (only for test purpose),
 *               called by adapt_method_stat()
 * r():          calculates the lower order terms of the element residual
 *               on each element at the quadrature node iq of quad
 *               argument to ellipt_est() and called by ellipt_est()
 ******************************************************************************/

static REAL r(const EL_INFO *el_info, const QUAD *quad, int iq,
	      REAL uh_at_qp, const REAL_D grd_uh_at_qp)
{
  return -f_loc(el_info, quad, iq, NULL);
}

#define EOC(e,eo) log(eo/MAX(e,1.0e-15))/M_LN2

static REAL estimate(MESH *mesh, ADAPT_STAT *adapt)
{
  FUNCNAME("estimate");
  static int     norm = -1;
  static REAL    C[3] = {1.0, 1.0, 0.0};
  static REAL    est, est_old = -1.0, err, err_old = -1.0;
  static FLAGS   r_flag;  /* = (INIT_UH | INIT_GRD_UH),  if needed by r() */
  static REAL_DD A;
  static const QUAD *quad;
  
  if (norm < 0) {
    int i;
    
    norm = H1_NORM;
    GET_PARAMETER(1, "error norm", "%d", &norm);
    GET_PARAMETER(1, "estimator C0", "%f", &C[0]);
    GET_PARAMETER(1, "estimator C1", "%f", &C[1]);
    GET_PARAMETER(1, "estimator C2", "%f", &C[2]);

    for (i = 0; i < DIM_OF_WORLD; i++) {
      A[i][i] = 1.0;
    }

    quad = get_quadrature(MESH_DIM, 2*u_h->fe_space->bas_fcts->degree);
  }

  est = ellipt_est(u_h, adapt, rw_el_est, NULL /* rw_est_c() */,
		   -1 /* quad_degree */, norm, C,
		   (const REAL_D *)A, NULL,
		   r, r_flag, NULL /* gn() */, 0 /* gn_flag */);
  
  MSG("estimate   = %.8le", est);
  if (est_old >= 0) {
    print_msg(", EOC: %.2lf\n", EOC(est,est_old));
  } else {
    print_msg("\n");
  }
  est_old = est;

  if (norm == L2_NORM) {
    err = L2_err_loc(u_at_qp, NULL /* data */, FILL_NOTHING,
		     u_h, quad,
		     false /* rel_error */,
		     true /* mean-value correction */,
		     NULL /* rw_err_el */, NULL /* max_l2_err2 */);
  } else {
    err = H1_err_loc(grd_u_at_qp, NULL /* data */, FILL_NOTHING,
		     u_h, quad, false /* rel_error */,
		     NULL /* rw_err_el() */, NULL /* max_h1_err2 */);
  }

  MSG("||u-uh||%s = %.8le", norm == L2_NORM ? "L2" : "H1", err);
  if (err_old >= 0) {
    print_msg(", EOC: %.2lf\n", EOC(err,err_old));
  } else {
    print_msg("\n");
  }
  err_old = err;

  MSG("||u-uh||%s/estimate = %.2lf\n", norm == L2_NORM ? "L2" : "H1",
      err/MAX(est,1.e-15));

  if (do_graphics) {
    togeomview(NULL /*mesh */,
	       u_h, 1.0, -1.0,
	       get_el_est, 1.0, -1.0,
	       u_loc, NULL, 0, 1.0, -1.0);
    WAIT;
  }

  return adapt->err_sum;
}

/*******************************************************************************
 * main program
 ******************************************************************************/

int main(int argc, char **argv)
{
  FUNCNAME("main");
  MACRO_DATA     *data;
  MESH           *mesh;
  const FE_SPACE *st_fe_space;
  int            n_refine = 0, degree = 1, parm_degree = -1;
  const BAS_FCTS *lagrange;
  ADAPT_STAT     *adapt;
  char           filename[PATH_MAX];
  int            i, j;

  /*****************************************************************************
   * first of all, initialize the access to parameters of the init file
   ****************************************************************************/
  parse_parameters(argc, argv, "INIT/ellipt-klein-bottle.dat");

  /*****************************************************************************
   * then read some basic parameters
   ****************************************************************************/
  GET_PARAMETER(1, "macro file name", "%s", filename);
  GET_PARAMETER(1, "polynomial degree", "%d", &degree);
  GET_PARAMETER(1, "global refinements", "%d", &n_refine);
  GET_PARAMETER(1, "parametric degree", "%d", &parm_degree);
  GET_PARAMETER(1, "online graphics", "%d", &do_graphics);

  /*****************************************************************************
   *  get a mesh, and read the macro triangulation from file
   ****************************************************************************/
  data = read_macro(filename);
  mesh = GET_MESH(MESH_DIM, "Klein's Bottle", data, init_node_proj,
		  NULL /* init_wall_trafos */);

  /*****************************************************************************
   * First allocate the standard finite element space; otherwise
   * ALBERTA will generate a periodic vertex admin per default, which
   * would be slightly inefficient for higher degree
   * parameterisations.
   ****************************************************************************/
  lagrange = get_lagrange(MESH_DIM, degree);
  TEST_EXIT(lagrange, "no lagrange BAS_FCTS\n");
  fe_space = get_fe_space(mesh, lagrange->name, lagrange, 1, ADM_PERIODIC);

  /*****************************************************************************
   * Only now get a non-periodic linear fe-space to store the parameter values
   * of each vertex of the periodic mesh.
   ****************************************************************************/
  st_bfcts = get_lagrange(MESH_DIM, 1);
  st_fe_space =
    get_fe_space(mesh, st_bfcts->name, st_bfcts, 1, ADM_FLAGS_DFLT);
  vertex_s = get_dof_real_vec("s at vertex", st_fe_space);
  vertex_t = get_dof_real_vec("t at vertex", st_fe_space);
  vertex_s->refine_interpol =
    vertex_t->refine_interpol = st_bfcts->real_refine_inter;
  vertex_s->coarse_restrict =
    vertex_t->coarse_restrict = st_bfcts->real_coarse_inter;

  /* Initialize the parameter values at the vertices. This piece of
   * code will break if you change the macro triangulation.
   */
  for (i = 0; i < mesh->n_macro_el; i++) {
    EL *el = mesh->macro_els[i].el;
    DOF dof[N_VERTICES(MESH_DIM)];

    GET_DOF_INDICES(st_bfcts, el, st_fe_space->admin, dof);
    for (j = 0; j < N_VERTICES(MESH_DIM); j++) {
      int v_nr = data->mel_vertices[i * N_VERTICES(MESH_DIM) + j];
      
      vertex_s->vec[dof[j]] = (REAL)(v_nr / 3) * M_PI;
      vertex_t->vec[dof[j]] = (REAL)(v_nr % 3) * M_PI;
    }
  }

  /* Only now free the macro data */
  free_macro_data(data);

  /*****************************************************************************
   * Initialize the leaf data for the storage of the estimate (why do
   * we not simply use a DOF_PTR_VEC with a center DOF-admin?)
   ****************************************************************************/
  init_leaf_data(mesh, sizeof(struct ellipt_leaf_data),
		 NULL /* refine_leaf_data */,
		 NULL /* coarsen_leaf_data */);
  
  /*****************************************************************************
   * Always use a parametric mesh, even for the linear case. Otherwise
   * we would have to use a non-periodic mesh and store the parameter
   * values of the vertices in the leaf-data structure. See
   * 3d/ellipt-moebius.c for an example.
   ****************************************************************************/
  use_lagrange_parametric(mesh, parm_degree,
			  init_node_proj(NULL, NULL, 0),
			  PARAM_ALL|PARAM_PERIODIC_COORDS);
  
  /*****************************************************************************
   * Globally refine the mesh a little bit. Maybe do some graphics
   * output to amuse the spectators.
   ****************************************************************************/
  global_refine(mesh, MESH_DIM*n_refine, FILL_NOTHING);

  if (do_graphics) {
    togeomview(mesh,
	       NULL, 0.0, -1.0, NULL, 0.0, -1.0, NULL, NULL, 0, 0.0, -1.0);
    WAIT;
  }

  /*****************************************************************************
   *  initialize the global variables shared across build(), solve()
   *  and estimate().
   ****************************************************************************/
  matrix = get_dof_matrix("A", fe_space, NULL);
  f_h    = get_dof_real_vec("f_h", fe_space);
  u_h    = get_dof_real_vec("u_h", fe_space);
  u_h->refine_interpol = fe_space->bas_fcts->real_refine_inter;
  u_h->coarse_restrict = fe_space->bas_fcts->real_coarse_inter;
  dof_set(0.0, u_h);

  /*****************************************************************************
   *  init adapt structure and start adaptive method
   ****************************************************************************/
  adapt = get_adapt_stat(MESH_DIM, "Klein's bottle" /* name */,
			 "adapt" /* prefix */, 2 /* info */, NULL);
  adapt->estimate = estimate;
  adapt->get_el_est = get_el_est;
  adapt->build_after_coarsen = build;
  adapt->solve = solve;

  adapt_method_stat(mesh, adapt);

  WAIT_REALLY;

  return 0;
}

