/*******************************************************************************
 *
 * ALBERTA:  an Adaptive multi Level finite element toolbox  using
 *           Bisectioning refinement and Error control by Residual
 *           Techniques
 *
 * www.alberta-fem.de
 *
 * File:     ellipt-sphere.c
 *
 * Description:  solver for a simple Poisson equation on a sphere:
 *
 *                 -\Delta_\Gamma u = f
 *                                u = g  on \partial\Gamma
 *
 * The computational domain is a sphere embedded into \R^n, n = 2,3,4, i.e.
 * S^1 \subset \R^2, S^2 \subset \R^3, S^3 \subset \R^4
 *
 ******************************************************************************
 *
 * 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 (2007)
 *
 * Based on the famous ellipt.c demo-program by K.G. Siebert and A. Schmidt.
 *
 ******************************************************************************/

#include <alberta/alberta.h>

#include "alberta-demo.h"

/* whether or not to do some graphics, using geomview or whatever */
static bool do_graphics = 1;

#define MESH_DIM (DIM_OF_WORLD-1)
#if MESH_DIM > DIM_MAX
# error This test-program is for an embedded sphere with co-dimension 1
#endif

/* Define to 1 to test whether the constants are in the kernel of the matrix */
#define DEBUG_MATRIX 1

/* Define to 1 to enable code which computes the area or volume of the sphere */
#define COMPUTE_VOLUME 1

void togeomview(MESH *mesh,
		const DOF_REAL_VEC *u_h,
		REAL uh_min, REAL uh_max,
		REAL (*get_est)(EL *el),
		REAL est_min, REAL est_max,
		REAL (*u_loc)(const EL_INFO *el_info,
			      const REAL_B lambda,
			      void *ud),
		void *ud, FLAGS fill_flags,
		REAL u_min, REAL u_max);

/*******************************************************************************
 * 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() */

/*******************************************************************************
 * 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 for an embedded sphere.
 ******************************************************************************/

static REAL_D K;

/* just something to avoid mesh-alignment */
#define PHASE_OFFSET 0.378

/*******************************************************************************
 * For test purposes: exact solution and its gradient (optional)
 ******************************************************************************/

/* just some trigonometric stuff */
static REAL u(const REAL_D _x)
{
  REAL u_val;
  int i;
  REAL_D x;

  AXEY_DOW(1.0/NORM_DOW(_x), _x, x);
  
  u_val = cos(K[0]*M_PI*x[0]+K[0]*PHASE_OFFSET);
  for (i = 1; i < DIM_OF_WORLD; i++) {
    u_val *= cos(K[i]*M_PI*x[i]+K[i]*PHASE_OFFSET);
  }

  return u_val;
}

/* only needed for togeomview() */
static REAL u_loc(const EL_INFO *el_info, const REAL_B lambda, void *ud)
{
  const PARAMETRIC *parametric = el_info->mesh->parametric;
  REAL_D x;

  if (parametric && parametric->init_element(el_info, parametric)) {
    parametric->coord_to_world(el_info, NULL, 1,
			       (const REAL_B *)lambda, (REAL_D *)x);
  } else {
    coord_to_world(el_info, lambda, x);
  }

  return u(x);
}

/* Tangential gradient on the sphere (exact surface). This will be
 * used to compute some approximation of an H^1-error; however,
 * H1_error() does not work correctly (yet) for parametric meshes
 * because it ignores terms resulting from the geometry approximation.
 */
static void cart_grd_u(const REAL_D x, REAL_D cart_grad)
{
  int i, j;
  
  for (i = 0; i < DIM_OF_WORLD; i++) {
    cart_grad[i] = -K[i]*M_PI*sin(K[i]*M_PI*x[i]+K[i]*PHASE_OFFSET);
    for (j = 0; j < i; j++) {
      cart_grad[i] *= cos(K[j]*M_PI*x[j]+K[j]*PHASE_OFFSET);
    }
    for (++j; j < DIM_OF_WORLD; j++) {
      cart_grad[i] *= cos(K[j]*M_PI*x[j]+K[j]*PHASE_OFFSET);
    }
  }
}

static const REAL *grd_u(const REAL_D _x, REAL_D grd)
{
  static REAL_D grd_space;
  REAL_D x, cart_grad;

  if (!grd) {
    grd = grd_space; 
  }

  AXEY_DOW(1.0/NORM_DOW(_x), _x, x);
  
  cart_grd_u(x, cart_grad);

  AXPBY_DOW(1.0, cart_grad, -SCP_DOW(cart_grad, x), x, grd);
  
  return grd;
}

/*******************************************************************************
 * problem data: right hand side, boundary values not needed
 ******************************************************************************/

/* Right hand side, we compute the Laplace-Beltrami of u() by using an
 * explicit projection to the tangent space of the sphere, exploiting
 * the fact that the normal field coincides with the identity mapping
 * on the unit-sphere.
 *
 * We have 
 *
 * \Delta_S U = \Delta u - DIM \nu\cdot\nabla u - (\nabla^2 u \nu)\cdot\nu
 *
 */
static REAL f(const REAL_D _x)
{
  REAL    Delta;
  REAL_D  x, cart_grad_u;
  REAL_DD D2u; /* Cartesian 2nd derivatives of u() */
  int i, j, k;

  AXEY_DOW(1.0/NORM_DOW(_x), _x, x);

  /* compute the Cartesion gradient */
  cart_grd_u(x, cart_grad_u);

  /* compute the complete Cartesian 2nd derivatives of u() */
  for (i = 0; i < DIM_OF_WORLD; i++) {
    D2u[i][i] = -SQR(K[i]*M_PI)*cos(K[i]*M_PI*x[i]+K[i]*PHASE_OFFSET);
    for (k = 0; k < i; k++) {
      D2u[i][i] *= cos(K[k]*M_PI*x[k]+K[k]*PHASE_OFFSET);
    }
    for (++k; k < DIM_OF_WORLD; k++) {
      D2u[i][i] *= cos(K[k]*M_PI*x[k]+K[k]*PHASE_OFFSET);
    }
    for (j = 0; j < i; j++) {
      D2u[i][j] =
	K[i]*K[j]*SQR(M_PI)
	*
	sin(K[i]*M_PI*x[i]+K[i]*PHASE_OFFSET)
	*
	sin(K[j]*M_PI*x[j]+K[j]*PHASE_OFFSET);
      for (k = 0; k < DIM_OF_WORLD; k++) {
	if (k == i || k == j) {
	  continue;
	}
	D2u[i][j] *= cos(K[k]*M_PI*x[k]+K[k]*PHASE_OFFSET);
      }
      D2u[j][i] = D2u[i][j];
    }
  }

  Delta = 0.0;
  for (i = 0; i < DIM_OF_WORLD; i++) {
    Delta += D2u[i][i];
  }
  Delta -= (REAL)(DIM_OF_WORLD-1.0)*SCP_DOW(cart_grad_u, x);
  for (i = 0; i < DIM_OF_WORLD; i++) {
    Delta -= D2u[i][i]*x[i]*x[i];
    for (j = 0; j < i; j++) {
      Delta -= 2.0*D2u[i][j]*x[i]*x[j];
    }
  }

  return -Delta;
}


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

/*******************************************************************************
 * The sphere-case is really easy: simply scale the given vertex to
 * unit-length. This works because the input to this routine always is
 * some polygonal interpolation between existing nodes of the
 * triangulation; at worst it is a linear interpolation between the
 * vertices of the simplex.
 ******************************************************************************/
static void sphere_proj_func(REAL_D vertex,
			     const EL_INFO *el_info,
			     const REAL_B lambda)
{
  SCAL_DOW(1.0/NORM_DOW(vertex), vertex);
}

/* init_node_projection() for the macro nodes. 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 sphere_proj = { sphere_proj_func };

static NODE_PROJECTION *init_node_proj(MESH *mesh, MACRO_EL *mel, int c)
{
  if (c == 0) {
    return &sphere_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 && 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;
  static const QUAD           *quad;

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

  /* initialize information for matrix assembling */
  if (!matrix_info) {
    static struct op_data user_data; /* storage for det and Lambda */
    OPERATOR_INFO  o_info = { NULL, };

    if (mesh->parametric && !mesh->parametric->not_all) {
      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_max, REAL_BD);
    user_data.det    = MEM_ALLOC(quad->n_points_max, 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 = (void *)&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(f, quad, f_h);

  /*  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(NULL /* *matrix, only for Robin */,
		      f_h,
		      NULL /* u_h */,
		      NULL /* boundary-type vector */,
		      NULL /* Dirichlet bndry bit-mask */,
		      NULL /* u() */,
		      NULL /* gn(), for inhomogenous Neumann conditions */,
		      -1.0 /* alpha_r Robin b.c. */,
		      NULL /* bndry_quad */);
  
#if COMPUTE_VOLUME
  {
    REAL volume = 0.0, val;
    REAL det[quad->n_points_max];
    REAL_BD Lambda[quad->n_points_max];    
    PARAMETRIC *parametric = fe_space->mesh->parametric;
    int i;

    TRAVERSE_FIRST(fe_space->mesh, -1, FILL_COORDS|CALL_LEAF_EL) {
      
      if (parametric && parametric->init_element(el_info, parametric)) {
	parametric->grd_lambda(el_info, quad, 0, NULL, Lambda, NULL, det); \
	for (i = 0; i < quad->n_points; i++) {
	  volume += quad->w[i]*det[i];
	}
      } else {
	det[0] = el_det(el_info);
	val = 0.0;
	for (i = 0; i < quad->n_points; i++) {
	  val += quad->w[i];
	}
	volume += det[0]*val;
      }

    } TRAVERSE_NEXT();

    MSG("Volume: %e.\n", volume);
  }
#endif
}

/*******************************************************************************
 * 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, ilu_k = 8;
  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);
    if (icon == __SSORPrecon) {
    	GET_PARAMETER(1, "precon ssor omega", "%f", &ssor_omega);
    	GET_PARAMETER(1, "precon ssor iter", "%d", &ssor_iter);
    }
    if (icon == ILUkPrecon)
        GET_PARAMETER(1, "precon ilu(k)", "%d", &ilu_k);
    if (solver == GMRes) {
      GET_PARAMETER(1, "solver restart", "%d", &restart);
    }
  }
  
  if (icon == ILUkPrecon)
	  precon = init_oem_precon(matrix, NULL, info, ILUkPrecon, ilu_k);
  else
	  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);
}

/*******************************************************************************
 * 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)
{
  const PARAMETRIC *parametric = el_info->mesh->parametric;
  REAL_D x;

  if (!(el_info->fill_flag & FILL_COORDS)) {
    parametric->coord_to_world(el_info, NULL, 1,
			       (const REAL_B *)quad->lambda[iq],
			       (REAL_D *)x);
  } else {
    coord_to_world(el_info, quad->lambda[iq], x);
  }

  return -f(x);
}

#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 = 0;  /* = (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(u, 
		 u_h, quad,
		 false /* rel_error */,
		 true /* mean-value correction */,
		 NULL /* rw_err_el */, NULL /* max_l2_err2 */);
  } else {
    err = H1_err(grd_u,
		 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) {
    FLAGS fill_flags = mesh->parametric ? 0 : FILL_COORDS;

    togeomview(NULL /*mesh */,
	       u_h, 1.0, -1.0,
	       get_el_est, 1.0, -1.0,
	       u_loc, NULL, fill_flags, 1.0, -1.0);
  }
  WAIT; /* controlled by "wait" parameter in INIT/ellipt-torus.dat */

  return adapt->err_sum;
}

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

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

  /*****************************************************************************
   * first of all, initialize the access to parameters of the init file
   ****************************************************************************/
  parse_parameters(argc, argv, "INIT/ellipt-sphere.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", &param_degree);
  GET_PARAMETER(1, "online graphics", "%d", &do_graphics);
  GET_PARAMETER(1, "test problem frequencies",
		SCAN_FORMAT_DOW, SCAN_EXPAND_DOW(K));

  /*****************************************************************************
  *  get a mesh, and read the macro triangulation from file
  ****************************************************************************/
  data = read_macro(filename);
  mesh = GET_MESH(MESH_DIM, "ALBERTA mesh", data,
		  init_node_proj, NULL /* init_wall_trafos */);
  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 */);


  /*****************************************************************************
   * Tell ALBERTA to use a parameterisation of p.w. degree
   * "param_degree". Per convention if "param_degree < 1" we use the
   * "new_coords" component of the EL structure to store the
   * parametric co-ordinates. In resulting parameterisation is
   * p.w. linear.
   ****************************************************************************/
  if (param_degree > 0) {
    use_lagrange_parametric(mesh, param_degree, &sphere_proj, PARAM_ALL);
  }

  /*****************************************************************************
   * Get the fe-space before calling global_refine() to reduce the startup-time.
   * Adding FE-spaces on refined meshes works but is somewhat inefficient.
   ****************************************************************************/
  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_FLAGS_DFLT /* flags */);
  
  /*****************************************************************************
   * 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 /* col_fe_space */);
  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);      /* initialize u_h */

  /*****************************************************************************
   *  init the adapt structure and start adaptive method
   ****************************************************************************/
  adapt = get_adapt_stat(MESH_DIM, "ellipt", "adapt", 2,
			 NULL /* ADAPT_STAT storage area, optional */);
  adapt->estimate = estimate;
  adapt->get_el_est = get_el_est;
  adapt->build_after_coarsen = build;
  adapt->solve = solve;

  adapt_method_stat(mesh, adapt);

  if (do_graphics) {
    FLAGS fill_flags = mesh->parametric ? 0 : FILL_COORDS;

    togeomview(NULL /*mesh */,
	       u_h, 1.0, -1.0,
	       get_el_est, 1.0, -1.0,
	       u_loc, NULL, fill_flags, 1.0, -1.0);
  }

  WAIT_REALLY; /* should we? */

  return 0;
}
