/*--------------------------------------------------------------------------*/
/* ALBERTA:   an Adaptive multi Level finite element toolbox using          */
/*           Bisectioning refinement and Error control by Residual          */
/*           Techniques                                                     */
/*                                                                          */
/* file:     ellipt2.c                                                      */
/*                                                                          */
/* description:  solver for an elliptic model problem                       */
/*                                                                          */
/*                        -\Delta u = f  in \Omega                          */
/*                                u = g  on \partial \Omega                 */
/*                                                                          */
/* This program is meant to show off some of the new features of            */
/* ALBERTA 2.0, including submeshes, parametric meshes and the GMV          */
/* visualization interface.                                                 */
/*                                                                          */
/*--------------------------------------------------------------------------*/
/*                                                                          */
/*  author:    Daniel Koester                                               */
/*             Institut fuer Mathematik                                     */
/*             Universitaet Augsburg                                        */
/*             Universitaetsstr. 14                                         */
/*             D-86159 Augsburg, Germany                                    */
/*                                                                          */
/*             Claus-Justus Heine                                           */
/*             Abteilung fuer Angewandte Mathematik                         */
/*             Universitaet Freiburg                                        */
/*             Hermann-Herder-Strasse 10                                    */
/*             79104 Freiburg, Germany                                      */
/*                                                                          */
/*  http://www.alberta-fem.de                                               */
/*                                                                          */
/*  (c) by D. Koester (2002-2005),                                          */
/*         C.-J. Heine (2006-2007).                                         */
/*--------------------------------------------------------------------------*/

#include <alberta/alberta.h>

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

#define TEST_MESH_CHAIN true

/*--------------------------------------------------------------------------*/
/* global variables: finite element space, discrete solution                */
/*                   load vector and system matrix                          */
/*--------------------------------------------------------------------------*/

static const FE_SPACE *fe_space;          /* initialized by main()           */
static DOF_REAL_VEC   *u_h = NULL;        /* initialized by build()          */
static DOF_REAL_VEC   *error_h = NULL;    /* initialized by estimate()       */
static DOF_REAL_VEC   *f_h = NULL;        /* initialized by build()          */
static DOF_MATRIX     *matrix = NULL;     /* initialized by build()          */

static BNDRY_FLAGS dirichlet_mask; /* bit-mask of Dirichlet segments */

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

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);
}

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

static void phi_theta(const REAL_D old_x, REAL *phi, REAL *theta)
{
  FUNCNAME("phi_theta");
  REAL_D x;
  REAL   r;

  COPY_DOW(old_x, x);
  r= NORM_DOW(x);

  if(r < 1.0E-15)
    ERROR_EXIT("Radius is too small!\n");
  else
    AXEY_DOW(1.0/r, old_x, x);

  if(ABS(x[0]) < 1.0E-15) {
    if(x[1] > 0.0)
      *phi = M_PI / 2.0;
    else
      *phi = -M_PI / 2.0;
  }
  else {
    if(x[0] > 0.0) 
      *phi = atan(x[1]/x[0]);
    else
      *phi = atan(x[1]/x[0]) + M_PI;
  }

  *theta = acos(x[2]);

  return;
}

static REAL u(const REAL_D x)
{
  REAL phi, theta, result;

  phi_theta(x, &phi, &theta);

  result = sin(2.0 * theta);
  result *= result * result * cos(3.0 * phi); 

  return result;
}

static const REAL *grd_u(const REAL_D x, REAL_D input)
{
  static REAL_D buffer;
  REAL         *grd = input ? input : buffer;
  REAL          ph, th;

  phi_theta(x, &ph, &th);

  if(th < 1.0E-15)
    SET_DOW(0.0, grd);
  else {
    REAL s1p = sin(ph), s3p = sin(3.0*ph), 
      c1p = cos(ph), c3p = cos(3.0*ph),
      s1t = sin(th), s2t = sin(2.0*th), 
      c1t = cos(th), c2t = cos(2.0*th);
    
    grd[0] =  3.0*s1p*s3p*s2t*s2t*s2t/s1t + 6.0*c1p*c3p*c1t*s2t*s2t*c2t;
    grd[1] = -3.0*c1p*s3p*s2t*s2t*s2t/s1t + 6.0*s1p*c3p*c1t*s2t*s2t*c2t;
    grd[2] = -6.0*c3p*s1t*s2t*s2t*c2t;
  }

  return(grd);
}

/*--------------------------------------------------------------------------*/
/* problem data: right hand side, boundary values                           */
/*--------------------------------------------------------------------------*/

static REAL g(const REAL_D x)              /* boundary values, not optional */
{
  return u(x);
}

static REAL f(const REAL_D x)              /* -Delta u, not optional        */
{
  REAL result, ph, th;

  phi_theta(x, &ph, &th);

  if(th < 1.0E-15)
    result = 0.0;
  else {
    REAL /* s1p = sin(ph), */ /* s3p = sin(3.0*ph), */
      /* c1p = cos(ph), */c3p = cos(3.0*ph),
      s1t = sin(th), s2t = sin(2.0*th), 
      c1t = cos(th), c2t = cos(2.0*th);
    
    result = 9.0*c3p*s2t*s2t*s2t/(s1t*s1t) - 6.0*(c3p*s2t/s1t)
      * (c1t*s2t*c2t + 4.0*s1t*c2t*c2t - 2.0*s1t*s2t*s2t);
  }

  return result;
}

static void ball_proj_func(REAL_D vertex, const EL_INFO *eli, 
			   const REAL_B lambda)
{
  REAL norm = NORM_DOW(vertex);
  
  norm = 1.0 / MAX(1.0E-15, norm);
  SCAL_DOW(norm, vertex);

  return;
}

static NODE_PROJECTION *init_node_proj(MESH *mesh, MACRO_EL *mel, int c)
{
  /* FUNCNAME("init_node_proj"); */
  static NODE_PROJECTION ball_proj = {ball_proj_func};

  if(!mesh || ((c > 0) && mel->wall_bound[c-1] == 2))
    return &ball_proj;

  return NULL;
}

static bool binding_method(MESH *master, MACRO_EL *mel, int face, void *ud)
{
  if(mel->wall_bound[face] == 2)
    return true;

  return false;
}


/*--------------------------------------------------------------------------*/
/* 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;
  int     is_parametric;
};

static bool init_element(const EL_INFO *el_info, const QUAD *quad[3], void *ud)
{
  /* FUNCNAME("init_element"); */
  struct op_data *info = (struct op_data *)ud;
  PARAMETRIC     *parametric = el_info->mesh->parametric;

  if(parametric) {
    info->is_parametric = parametric->init_element(el_info, parametric);
  } else {
    info->is_parametric = -1;
  }
  
  switch (info->is_parametric) {
  case true:
    parametric->grd_lambda(el_info, quad[2], 0, NULL,
			   info->Lambda, NULL, info->det);
    break;
  case false:
    parametric->grd_lambda(el_info, NULL, 1, NULL,
			   info->Lambda, NULL, info->det);
    break;
  case -1:
    info->det[0] = el_grd_lambda_2d(el_info, info->Lambda[0]);
    break;
  }

  return info->is_parametric == true;
}

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;

  switch (info->is_parametric) {
  case true:
    for (i = 0; i < N_VERTICES_2D; 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_2D; 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];
      }
    }
    break;
  case false:
  case -1:
    for (i = 0; i < N_VERTICES_2D; i++) {
      LALt[i][i]  = SCP_DOW(info->Lambda[0][i], info->Lambda[0][i]);
      LALt[i][i] *= info->det[0];
      for (j = i+1; j <  N_VERTICES_2D; j++) {
	LALt[i][j]  = SCP_DOW(info->Lambda[0][i], info->Lambda[0][j]);
	LALt[i][j] *= info->det[0];
	LALt[j][i]  = LALt[i][j];
      }
    }
    break;
  }
  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;
  static struct op_data       opiud;
  REAL_BD                     opi_Lambda[n_quad_points_max[mesh->dim]];
  REAL                        opi_det[n_quad_points_max[mesh->dim]];

  opiud.Lambda = opi_Lambda;
  opiud.det    = opi_det;

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

  if (!u_h)                 /*  access matrix and vector for linear system */
  {
    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);      /*  initialize u_h  !                          */
  }

  if (!matrix_info)           /* information for matrix assembling         */
  {
    OPERATOR_INFO  o_info = {NULL};

    if(mesh->parametric)
      quad = get_quadrature(2, 2*fe_space->bas_fcts->degree + 2);
    else
      quad = get_quadrature(2, 2*fe_space->bas_fcts->degree-2);

    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_pw_const  = true;        /* pw const. assemblage is faster */
    o_info.LALt_symmetric = true;        /* symmetric assemblage is faster */
    BNDRY_FLAGS_CPY(o_info.dirichlet_bndry,
		    dirichlet_mask); /* Dirichlet boundary conditions. */
    o_info.user_data      = &opiud;      /* user data! */

    o_info.fill_flag = CALL_LEAF_EL|FILL_COORDS;

    matrix_info = fill_matrix_info(&o_info, NULL);
  }

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

  dof_set(0.0, f_h);                       /* assembling of load vector    */
  L2scp_fct_bas(f, quad, f_h);

  dirichlet_bound(f_h, u_h, NULL, dirichlet_mask, g); /*  boundary values */
  return;
}


/*--------------------------------------------------------------------------*/
/* 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;
}


/*--------------------------------------------------------------------------*/
/* 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)
{
  REAL_D      x;
  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     degree, 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() */
  REAL_DD        A = {{1.0, 0.0, 0.0},{0.0, 1.0, 0.0}, {0.0, 0.0, 1.0}};
  const QUAD     *quad;
  
  if (norm < 0) {
    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]);

    error_h = get_dof_real_vec("u-u_h", u_h->fe_space);
  }

  /* Error estimation is not yet implemented for parametric meshes.         */
  if(!mesh->parametric) {
    est = ellipt_est(u_h, adapt, rw_el_est, NULL /* rw_est_c() */,
		     -1 /* quad_degree */, norm, C, 
		     (const REAL_D *) A, dirichlet_mask,
		     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;
  } 
  else
    adapt->err_sum = 1.0; /* To keep mesh refinement going. */

  quad = get_quadrature(2, degree);
  if (norm == L2_NORM)
    err = L2_err(u, u_h, quad, 0, 0, NULL, NULL);
  else
    err = H1_err(grd_u, u_h, quad, 0, rw_el_est, NULL);

  interpol(u, error_h);
  dof_axpy(-1.0, u_h, error_h);

  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;

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

#if 0
  togeomview(NULL /*mesh */,
	     u_h, 1.0, -1.0,
	     get_el_est, 1.0, -1.0,
	     u_loc, NULL, 0, 1.0, -1.0);
#endif

  WAIT_REALLY;

  write_mesh_gmv(mesh, "ellipt2.gmv",
		 true /* write ascii */,
		 true /* use Lagrange grid */,
		 1, &u_h, 0, NULL, NULL, 0.0);

  return(adapt->err_sum);
}

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

int main(int argc, char **argv)
{
  FUNCNAME("main");
  MACRO_DATA        *data;
  MESH              *volume_mesh, *surf_mesh;
#if TEST_MESH_CHAIN
  MESH              *line_mesh;
#endif
  int               n_refine = 0, degree = 1, param_degree = -1;
  PARAM_STRATEGY    param_strategy = PARAM_STRAIGHT_CHILDS;
  const BAS_FCTS    *lagrange;
  static ADAPT_STAT *adapt;
  char               filename[100];

/*--------------------------------------------------------------------------*/
/*  first of all, init parameters of the init file                          */
/*--------------------------------------------------------------------------*/

  init_parameters(0, "INIT/ellipt2.dat");
  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, "parametric strategy", "%d", &param_strategy);
  
/*--------------------------------------------------------------------------*/
/*  get a mesh, and read the macro triangulation from file                  */
/*--------------------------------------------------------------------------*/

  data = read_macro(filename);

  volume_mesh = GET_MESH(3, "ALBERTA volume mesh", data, init_node_proj, NULL);

  free_macro_data(data);

#if TEST_MESH_CHAIN
  /* For testing purposes: do it "The Wrong Way (tm)": refine the
   * master mesh alittle bit before doing anything else, then derive a
   * sub-mesh, then refine the master mesh a little bit further, then
   * add a parametric structure, then derive a 1d sub-mesh from the
   * slave.
   */
#if 1
  if (n_refine-- > 0) {
    global_refine(volume_mesh, 1*2/*volume_mesh->dim*/, FILL_NOTHING);
  }
#endif

  surf_mesh = get_submesh(volume_mesh, "ALBERTA surface mesh",
			  binding_method, NULL);

#if 1
  togeomview(surf_mesh,
	     NULL, 1.0, -1.0,
	     NULL, 1.0, -1.0,
	     NULL, NULL, 0, 1.0, -1.0);

  WAIT_REALLY;  
#endif

#if 1
  /* Now refine the surface-mesh, the volume mesh should be refined
   * on-the-fly to stay compatible.
   */
  if (n_refine-- > 0) {
    global_refine(surf_mesh, 1*surf_mesh->dim, FILL_NOTHING);
  }
#endif

  /* Only now add a parametric structure, it should be handed on to
   * the 2d slave mesh.
   */
  if (param_degree > 0) {
    use_lagrange_parametric(volume_mesh, param_degree,
			    init_node_proj(NULL, NULL, 0), param_strategy);
  }
#if 1
  togeomview(surf_mesh,
	     NULL, 1.0, -1.0,
	     NULL, 1.0, -1.0,
	     NULL, NULL, 0, 1.0, -1.0);
  
#if 0
  {
    FILE *blah;
    DOF_REAL_D_VEC *coords = get_lagrange_coords(volume_mesh);
    const BAS_FCTS *bas_fcts = coords->fe_space->bas_fcts;
    const DOF_ADMIN *admin = coords->fe_space->admin;
    DOF dofs[bas_fcts->n_bas_fcts];
    int n_e = admin->n_dof[EDGE];
    int n_f = admin->n_dof[FACE];
    int n_c = admin->n_dof[CENTER];
    float blue[4] = { 0.0, 0.0, 1.0, 1.0 };
    float red[4]  = { 1.0, 0.0, 0.0, 1.0 };

    blah = fopen("/tmp/blah", "w");
    TRAVERSE_FIRST(volume_mesh, -1, CALL_LEAF_EL) {
      float *el_color =
	volume_mesh->parametric->init_element(el_info, volume_mesh->parametric)
	? blue : red;
      
      GET_DOF_INDICES(bas_fcts, el_info->el, admin, dofs);
      fprintf(blah, "(geometry el-%d\n", el_info->el->index);
      print_geomview_tetra(blah, coords->vec,
			   dofs, bas_fcts->n_bas_fcts,
			   n_e, n_f, n_c, el_color, NULL);
      fprintf(blah, "\n)", el_info->el->index);
    } TRAVERSE_NEXT();
    fclose(blah);
  }
#endif

  WAIT_REALLY;  
#endif

  init_leaf_data(surf_mesh, sizeof(struct ellipt_leaf_data), NULL, NULL);

  lagrange = get_lagrange(2, degree);
  TEST_EXIT(lagrange, "no lagrange BAS_FCTS\n");

  fe_space = get_fe_space(surf_mesh, lagrange->name, lagrange, 1, ADM_FLAGS_DFLT);

#if 1
  /* Derive the boundary line of the 2d mesh by calling a convenience
   * function: get_bndry_sub_mesh(MASTER, NAME) is equivalent to
   * get_sub_mesh(MASTER, NAME, bndry_binding_method, NULL), where
   * "bndry_binding_method()" is define by the ALBERTA library.
   */
  line_mesh = get_bndry_submesh(surf_mesh, "ALBERTA line mesh");
#endif
  /* refine the bottom-most slave mesh, the master meshes should stay
   * compatible.
   */
  if (n_refine-- > 0) {
    global_refine(surf_mesh, 1*line_mesh->dim, FILL_NOTHING);
  }

#if 1
  togeomview(surf_mesh,
	     NULL, 1.0, -1.0,
	     NULL, 1.0, -1.0,
	     NULL, NULL, 0, 1.0, -1.0);
  WAIT_REALLY;  
#endif

  /* Finally refine the mid-level mesh (i.e. the surface mesh). Its
   * slave should just inherit the refinement, the volume mesh should
   * get the conformal closure.
   */
  if (n_refine-- > 0) {
    global_refine(surf_mesh, 1*surf_mesh->dim, FILL_NOTHING);
  }

  /* Release the master mesh at this point */
  free_mesh(volume_mesh);

  if (n_refine > 0) {
    global_refine(surf_mesh, n_refine*surf_mesh->dim, FILL_NOTHING);
  }
#else

  surf_mesh = get_submesh(volume_mesh, "ALBERTA surface mesh",
			  binding_method, NULL);

  if (param_degree > 0) {
    use_lagrange_parametric(volume_mesh, param_degree,
			    init_node_proj(NULL, NULL, 0), param_strategy);
  }

  free_mesh(volume_mesh);

  init_leaf_data(surf_mesh, NULL, sizeof(struct ellipt_leaf_data), NULL, NULL);

  lagrange = get_lagrange(2, degree);
  TEST_EXIT(lagrange, "no lagrange BAS_FCTS\n");

  fe_space = get_fe_space(surf_mesh, lagrange->name, NULL, lagrange, false);

  global_refine(surf_mesh, n_refine);
#endif

/*--------------------------------------------------------------------------*/
/*  init adapt structure and start adaptive method                          */
/*--------------------------------------------------------------------------*/

  adapt = get_adapt_stat(2, "ellipt", "adapt", 2, NULL);
  adapt->estimate = estimate;
  adapt->get_el_est = get_el_est;
  adapt->build_after_coarsen = build;
  adapt->solve = solve;

  adapt_method_stat(surf_mesh, adapt);

  return(0);
}
