1 /*******************************************************************************
2  *
3  * ALBERTA:  an Adaptive multi Level finite element toolbox  using
4  *           Bisectioning refinement and Error control by Residual
5  *           Techniques
6  *
7  * www.alberta-fem.de
8  *
9  *
10  * file:     ellipt-dg.c
11  *
12  * description:  solver for an elliptic model problem
13  *
14  *                        -\Delta u = f  in \Omega
15  *                          D_\nu u = g  on \partial \Omega
16  *
17  * This version of the Poisson solver is just a quick and dirty hack
18  * to test the basic functionality of the crude DG support built into
19  * ALBERTA. We use the following scheme:
20  *
21  * (\nabla u, \nabla v) + \alpha \int_E [u][v] = (f, v) + b.c. in \Omega.
22  *
23  * alpha is chosen to be 1/h^q for some q. The discretisation is of
24  * course not state of the art, however, this is really only meant as
25  * test program.
26  *
27  ******************************************************************************
28  *
29  * author(s): Claus-Justus Heine
30  *            Abteilung fuer Angewandte Mathematik
31  *            Albert-Ludwigs-Universitaet Freiburg
32  *            Hermann-Herder-Str. 10
33  *            79104 Freiburg
34  *            Germany
35  *            Claus.Heine@Mathematik.Uni-Freiburg.DE
36  *
37  * (c) by C.-J. Heine (2008)
38  *
39  * Based on the famous ellipt.c demo-program by K.G. Siebert and A. Schmidt.
40  *
41  ******************************************************************************/
42 
43 #include "alberta-demo.h" /* proto-types for some helper functions */
44 
45 static bool do_graphics = true; /* global graphics "kill-switch" */
46 
47 /*******************************************************************************
48  * global variables: finite element space, discrete solution
49  *                   load vector and system matrix
50  *
51  * These variables are kept global because they are shared across build(),
52  * solve() and estimate().
53  ******************************************************************************/
54 
55 static const FE_SPACE *fe_space;  /* initialized by main() */
56 static DOF_REAL_VEC   *u_h;       /* initialized by main() */
57 static DOF_REAL_VEC   *f_h;       /* initialized by main() */
58 static DOF_MATRIX     *matrix;    /* initialized by main() */
59 
60 static REAL robin_alpha = -1.0;    /* GET_PARAMTER() in main() */
61 static REAL jmp_alpha   = 1.0;     /* penalty for jump over walls */
62 static REAL jmp_exp     = 1.0;     /* jmp_alpha / pow(h, jmp_exp) */
63 
64 /*******************************************************************************
65  * struct ellipt_leaf_data: structure for storing one REAL value on each
66  *                          leaf element as LEAF_DATA
67  * rw_el_est():  return a pointer to the memory for storing the element
68  *               estimate (stored as LEAF_DATA), called by ellipt_est()
69  * get_el_est(): return the value of the element estimates (from LEAF_DATA),
70  *               called by adapt_method_stat() and graphics()
71  ******************************************************************************/
72 
73 struct ellipt_leaf_data
74 {
75   REAL estimate; /*  one real for the estimate */
76 };
77 
rw_el_est(EL * el)78 static REAL *rw_el_est(EL *el)
79 {
80   if (IS_LEAF_EL(el))
81     return(&((struct ellipt_leaf_data *)LEAF_DATA(el))->estimate);
82   else
83     return(NULL);
84 }
85 
get_el_est(EL * el)86 static REAL get_el_est(EL *el)
87 {
88   if (IS_LEAF_EL(el))
89     return(((struct ellipt_leaf_data *)LEAF_DATA(el))->estimate);
90   else
91     return(0.0);
92 }
93 
94 /*******************************************************************************
95  * For test purposes: exact solution and its gradient (optional)
96  ******************************************************************************/
97 
98 #define GAUSS_SCALE 10.0
99 
u(const REAL_D x)100 static REAL u(const REAL_D x)
101 {
102   return exp(-GAUSS_SCALE*SCP_DOW(x,x));
103 }
104 
grd_u(const REAL_D x,REAL_D grd)105 static const REAL *grd_u(const REAL_D x, REAL_D grd)
106 {
107   static REAL_D buffer;
108   REAL          ux = exp(-GAUSS_SCALE*SCP_DOW(x,x));
109   int           n;
110 
111   if (!grd) {
112     grd = buffer;
113   }
114 
115   for (n = 0;  n < DIM_OF_WORLD; n++)
116     grd[n] = -2.0*GAUSS_SCALE*x[n]*ux;
117 
118   return grd;
119 }
120 
121 /*******************************************************************************
122  * problem data: right hand side, boundary values
123  ******************************************************************************/
124 
125 #if 0
126 static REAL g(const REAL_D x) /* boundary values, not optional */
127 {
128   return u(x);
129 }
130 #endif
131 
gn(const REAL_D x,const REAL_D normal)132 static REAL gn(const REAL_D x, const REAL_D normal) /* Neumann b.c. */
133 {
134   return robin_alpha > 0.0
135     ? SCP_DOW(grd_u(x, NULL), normal) + robin_alpha * u(x)
136     : SCP_DOW(grd_u(x, NULL), normal);
137 }
138 
f(const REAL_D x)139 static REAL f(const REAL_D x) /* -Delta u, not optional        */
140 {
141   REAL  r2 = SCP_DOW(x,x), ux  = exp(-GAUSS_SCALE*r2);
142   return -(4.0*SQR(GAUSS_SCALE)*r2 - 2.0*GAUSS_SCALE*DIM_OF_WORLD)*ux;
143 }
144 
145 /*******************************************************************************
146  * build(): assemblage of the linear system: matrix, load vector,
147  *          boundary values, called by adapt_method_stat()
148  *          on the first call initialize u_h, f_h, matrix and information
149  *          for assembling the system matrix
150  *
151  * struct op_data: structure for passing information from init_element() to
152  *                 LALt() ("operator data").
153  * init_element(): initialization on the element; calculates the
154  *                 coordinates and |det DF_S| used by LALt; passes these
155  *                 values to LALt via user_data,
156  *                 called on each element by update_matrix()
157  * LALt():         implementation of Lambda id Lambda^t for -Delta u,
158  *                 called by update_matrix() after init_element()
159  ******************************************************************************/
160 
161 struct op_data
162 {
163   REAL_BD Lambda; /*  the gradient of the barycentric coordinates */
164   REAL    det;    /*  |det D F_S| */
165   REAL    wall_det[N_WALLS_MAX];
166   REAL    c_factor;
167 };
168 
init_element(const EL_INFO * el_info,const QUAD * quad[3],void * ud)169 static bool init_element(const EL_INFO *el_info, const QUAD *quad[3], void *ud)
170 {
171   struct op_data *info = (struct op_data *)ud;
172 
173   /* ..._0cd: co-dimension 0 version of el_grd_lambda(dim, ...) */
174   info->det = el_grd_lambda_0cd(el_info, info->Lambda);
175 
176   return false; /* not parametric */
177 }
178 
bndry_init_element(const EL_INFO * el_info,int wall,const WALL_QUAD * quad[3],void * ud)179 static bool bndry_init_element(const EL_INFO *el_info, int wall,
180 			       const WALL_QUAD *quad[3], void *ud)
181 {
182   struct op_data *info = (struct op_data *)ud;
183 
184   info->wall_det[wall] = get_wall_normal(el_info, wall, NULL);
185 
186   if (fe_space->bas_fcts->degree > 0) {
187     REAL det = info->wall_det[wall];
188     info->wall_det[wall] = info->c_factor =
189       (det *= jmp_alpha * pow(det, -(jmp_exp / (REAL)(DIM_OF_WORLD-1))));
190   } else {
191     /* Use a very simplistic finite-volume method */
192 #define VAL (1.0/(REAL)N_LAMBDA_MAX)
193     const REAL_B c_b = INIT_BARY_MAX(VAL, VAL, VAL, VAL);
194 #undef VAL
195     EL_INFO neigh_info[1];
196     const EL_GEOM_CACHE *elgc =
197       fill_el_geom_cache(el_info, FILL_EL_WALL_REL_ORIENTATION(wall));
198     REAL_D c, cn;
199     REAL delta;
200 
201     fill_neigh_el_info(neigh_info, el_info, wall, elgc->rel_orientation[wall]);
202 
203     coord_to_world(el_info, c_b, c);
204     coord_to_world(neigh_info, c_b, cn);
205     delta = DIST_DOW(c, cn);
206 
207     info->c_factor = (info->wall_det[wall] /= delta);
208   }
209 
210   return false;
211 }
212 
neigh_init_element(const EL_INFO * el_info,int wall,const WALL_QUAD * quad[3],void * ud)213 static bool neigh_init_element(const EL_INFO *el_info, int wall,
214 			       const WALL_QUAD *quad[3], void *ud)
215 {
216   struct op_data *info = (struct op_data *)ud;
217 
218   info->c_factor = -info->wall_det[wall];
219 
220   return false;
221 }
222 
LALt(const EL_INFO * el_info,const QUAD * quad,int iq,void * ud)223 static const REAL_B *LALt(const EL_INFO *el_info, const QUAD *quad,
224 			  int iq, void *ud)
225 {
226   static REAL_BB LALt;
227   struct op_data *info = (struct op_data *)ud;
228   int            i, j, dim = el_info->mesh->dim;
229 
230   for (i = 0; i < N_VERTICES(dim); i++) {
231     LALt[i][i] = info->det*SCP_DOW(info->Lambda[i], info->Lambda[i]);
232     for (j = i+1; j < N_VERTICES(dim); j++) {
233       LALt[i][j] = SCP_DOW(info->Lambda[i], info->Lambda[j]);
234       LALt[i][j] *= info->det;
235       LALt[j][i] = LALt[i][j];
236     }
237   }
238 
239   return (const REAL_B *)LALt;
240 }
241 
c(const EL_INFO * el_info,const QUAD * quad,int iq,void * ud)242 static REAL c(const EL_INFO *el_info, const QUAD *quad, int iq, void *ud)
243 {
244   struct op_data *info = (struct op_data *)ud;
245 
246   return info->c_factor;
247 }
248 
build(MESH * mesh,U_CHAR flag)249 static void build(MESH *mesh, U_CHAR flag)
250 {
251   FUNCNAME("build");
252   static const EL_MATRIX_INFO *matrix_info;
253 
254   dof_compress(mesh);
255   MSG("%d DOFs for %s\n", fe_space->admin->size_used, fe_space->name);
256 
257   if (!matrix_info) {
258     /* information for matrix assembling (only once) */
259     OPERATOR_INFO       o_info = { NULL, }, *oi_ptr;
260     BNDRY_OPERATOR_INFO el_bop_info = { NULL, };
261     BNDRY_OPERATOR_INFO neigh_bop_info = { NULL, };
262     static struct op_data user_data; /* storage for det and Lambda,
263 				      * must be static.
264 				      */
265 
266     o_info.row_fe_space    = fe_space;
267     o_info.init_element    = init_element;
268     o_info.LALt.real       = LALt;
269     o_info.LALt_pw_const   = true;        /* pw const. assemblage is faster */
270     o_info.LALt_symmetric  = true;        /* symmetric assemblage is faster */
271     o_info.user_data = (void *)&user_data; /* application data */
272 
273     /* only FILL_BOUND is added automatically. */
274     o_info.fill_flag = CALL_LEAF_EL|FILL_COORDS;
275 
276     /* ALBERTA requires that the boundary contributions are split into
277      * one part which is assembled using only the current's element
278      * local basis functions, and a second part which pairs the given
279      * element with its neighbour. In our case both contributions are
280      * simple mass-matrices
281      */
282     el_bop_info.row_fe_space = fe_space;
283     el_bop_info.init_element = bndry_init_element;
284     el_bop_info.c.real       = c;
285     el_bop_info.c_pw_const   = true;
286     el_bop_info.user_data    = (void *)&user_data;
287     BNDRY_FLAGS_INIT(el_bop_info.bndry_type); /* all interior edges */
288 
289     el_bop_info.fill_flag = CALL_LEAF_EL|FILL_COORDS|FILL_OPP_COORDS;
290 
291     /* The contribution of the pairing with the neighbouring basis
292      * functions is almost the same:
293      */
294     neigh_bop_info = el_bop_info;
295     neigh_bop_info.init_element = neigh_init_element;
296     neigh_bop_info.discontinuous = true;
297 
298     oi_ptr = fe_space->bas_fcts->degree > 0 ? &o_info : NULL;
299     matrix_info =
300       fill_matrix_info_ext(NULL, oi_ptr, &el_bop_info, &neigh_bop_info, NULL);
301   }
302 
303   /* assembling of matrix */
304   clear_dof_matrix(matrix);
305   update_matrix(matrix, matrix_info, NoTranspose);
306 
307   /* assembling of load vector */
308   dof_set(0.0, f_h);
309   L2scp_fct_bas(f, get_quadrature(DIM_OF_WORLD, 4) /* quadrature */, f_h);
310 
311   /* Boundary values, the combination alpha_r < 0.0 flags automatic
312    * mean-value correction iff f_h has non-zero mean-value and no
313    * non-Neumann boundary conditions were detected during mesh
314    * traversal.
315    */
316   boundary_conditions(matrix, f_h, u_h, NULL /* bound */,
317 		      NULL /* Dirichlet_mask */,
318 		      NULL /* g */, gn,
319 		      robin_alpha, /* < 0: automatic mean-value correction */
320 		      NULL /* wall_quad, use default */);
321 }
322 
323 /*******************************************************************************
324  * solve(): solve the linear system, called by adapt_method_stat()
325  ******************************************************************************/
326 
solve(MESH * mesh)327 static void solve(MESH *mesh)
328 {
329   FUNCNAME("solve");
330   static REAL       tol = 1.e-8, ssor_omega = 1.0;
331   static int        miter = 1000, info = 2, restart = 0, ssor_iter = 1, ilu_k = 8;
332   static OEM_PRECON icon = DiagPrecon;
333   static OEM_SOLVER solver = NoSolver;
334   const PRECON *precon;
335 
336   if (solver == NoSolver)
337   {
338     tol = 1.e-8;
339     GET_PARAMETER(1, "solver", "%d", &solver);
340     GET_PARAMETER(1, "solver tolerance", "%f", &tol);
341     GET_PARAMETER(1, "solver precon", "%d", &icon);
342     GET_PARAMETER(1, "solver max iteration", "%d", &miter);
343     GET_PARAMETER(1, "solver info", "%d", &info);
344     if (icon == __SSORPrecon) {
345     	GET_PARAMETER(1, "precon ssor omega", "%f", &ssor_omega);
346     	GET_PARAMETER(1, "precon ssor iter", "%d", &ssor_iter);
347     }
348     if (icon == ILUkPrecon)
349          GET_PARAMETER(1, "precon ilu(k)", "%d", &ilu_k);
350     if (solver == GMRes)
351       GET_PARAMETER(1, "solver restart", "%d", &restart);
352   }
353 
354   if (icon == ILUkPrecon)
355 	  precon = init_oem_precon(matrix, NULL, info, ILUkPrecon, ilu_k);
356   else
357 	  precon = init_oem_precon(matrix, NULL, info, icon, ssor_omega, ssor_iter);
358   oem_solve_s(matrix, NULL, f_h, u_h,
359 	      solver, tol, precon, restart, miter, info);
360 
361   if (do_graphics) {
362     MSG("Displaying u_h, u and (u_h-u).\n");
363     graphics(mesh, u_h, NULL /* get_el_est */, u, HUGE_VAL /* time */);
364   }
365 
366   return;
367 }
368 
369 /*******************************************************************************
370  * Functions for error estimate:
371  * estimate():   calculates error estimate via ellipt_est()
372  *               calculates exact error also (only for test purpose),
373  *               called by adapt_method_stat()
374  * r():          calculates the lower order terms of the element residual
375  *               on each element at the quadrature node iq of quad
376  *               argument to ellipt_est() and called by ellipt_est()
377  ******************************************************************************/
378 
r(const EL_INFO * el_info,const QUAD * quad,int iq,REAL uh_at_qp,const REAL_D grd_uh_at_qp)379 static REAL r(const EL_INFO *el_info, const QUAD *quad, int iq,
380 	      REAL uh_at_qp, const REAL_D grd_uh_at_qp)
381 {
382   REAL_D x;
383 
384   coord_to_world(el_info, quad->lambda[iq], x);
385 
386   return -f(x);
387 }
388 
est_gn(const EL_INFO * el_info,const QUAD * quad,int qp,REAL uh_at_qp,const REAL_D normal)389 static REAL est_gn(const EL_INFO *el_info,
390 		   const QUAD *quad,
391 		   int qp,
392 		   REAL uh_at_qp,
393 		   const REAL_D normal)
394 {
395   /* we simply return gn(), expoiting the fact that the geometry cache
396    * of the quadrature already contains the world-coordinates of the
397    * quadrature points.
398    */
399   const QUAD_EL_CACHE *qelc =
400     fill_quad_el_cache(el_info, quad, FILL_EL_QUAD_WORLD);
401 
402   if (robin_alpha > 0.0) {
403     return gn(qelc->world[qp], normal) - robin_alpha * uh_at_qp;
404   } else {
405     return gn(qelc->world[qp], normal);
406   }
407 }
408 
409 #define EOC(e,eo) log(eo/MAX(e,1.0e-15))/M_LN2
410 
estimate(MESH * mesh,ADAPT_STAT * adapt)411 static REAL estimate(MESH *mesh, ADAPT_STAT *adapt)
412 {
413   FUNCNAME("estimate");
414   static int   norm = -1;
415   static REAL  C[3] = {1.0, 1.0, 0.0};
416   static REAL  est_old = -1.0, err_old = -1.0;
417   REAL         est, err;
418   REAL_DD      A = {{0.0}};
419   int          n;
420 
421   for (n = 0; n < DIM_OF_WORLD; n++) {
422     A[n][n] = 1.0; /* set diagonal of A; all other elements are zero */
423   }
424 
425   if (norm < 0) {
426     norm = H1_NORM;
427     GET_PARAMETER(1, "error norm", "%d", &norm);
428     GET_PARAMETER(1, "estimator C0", "%f", &C[0]);
429     GET_PARAMETER(1, "estimator C1", "%f", &C[1]);
430     GET_PARAMETER(1, "estimator C2", "%f", &C[2]);
431   }
432 
433   est = ellipt_est(u_h, adapt, rw_el_est, NULL /* rw_est_c() */,
434 		   -1 /* quad_degree */,
435 		   norm, C,
436 		   (const REAL_D *) A,
437 		   NULL,
438 		   r, 0 /* (INIT_UH | INIT_GRD_UH), if needed by r() */,
439 		   est_gn, robin_alpha > 0.0 ? INIT_UH : 0);
440 
441   MSG("estimate   = %.8le", est);
442   if (est_old >= 0)
443     print_msg(", EOC: %.2lf\n", EOC(est,est_old));
444   else
445     print_msg("\n");
446   est_old = est;
447 
448   if (norm == L2_NORM) {
449     err = L2_err(u, u_h, NULL /* quad */,
450 		 false /* relative error*/,
451 		 true /* mean-value adjust */,
452 		 NULL /* rw_err_el()*/, NULL /* max_err_el2 */);
453   } else {
454     err = H1_err(grd_u, u_h, NULL /* quad */,
455 		 false /* relative error */,
456 		 NULL /* rw_err_el()*/, NULL /* max_err_el2 */);
457   }
458 
459   MSG("||u-uh||%s = %.8le", norm == L2_NORM ? "L2" : "H1", err);
460   if (err_old >= 0)
461     print_msg(", EOC: %.2lf\n", EOC(err,err_old));
462   else
463     print_msg("\n");
464   err_old = err;
465   MSG("||u-uh||%s/estimate = %.2lf\n", norm == L2_NORM ? "L2" : "H1",
466       err/MAX(est,1.e-15));
467 
468   if (do_graphics) {
469     MSG("Displaying the estimate.\n");
470     graphics(mesh, NULL /* u_h */, get_el_est, NULL /* u_exact() */,
471 	     HUGE_VAL /* time */);
472   }
473 
474   return adapt->err_sum;
475 }
476 
477 /*******************************************************************************
478  * main program
479  ******************************************************************************/
480 
main(int argc,char ** argv)481 int main(int argc, char **argv)
482 {
483   FUNCNAME("main");
484   MACRO_DATA     *data;
485   MESH           *mesh;
486   int            n_refine = 0, dim, degree = 1;
487   const BAS_FCTS *bas_fcts;
488   ADAPT_STAT     *adapt;
489   char           filename[PATH_MAX];
490   char           bfcts_name[PATH_MAX] = "disc_lagrange";
491 
492   /*****************************************************************************
493    * first of all, initialize the access to parameters of the init file
494    ****************************************************************************/
495   parse_parameters(argc, argv, "INIT/ellipt-dg.dat");
496 
497   GET_PARAMETER(1, "mesh dimension", "%d", &dim);
498   GET_PARAMETER(1, "macro file name", "%s", filename);
499   GET_PARAMETER(1, "polynomial degree", "%d", &degree);
500   GET_PARAMETER(1, "global refinements", "%d", &n_refine);
501   GET_PARAMETER(1, "online graphics", "%d", &do_graphics);
502   GET_PARAMETER(1, "robin factor", "%f", &robin_alpha);
503   GET_PARAMETER(1, "jump penalty", "%f", &jmp_alpha);
504   GET_PARAMETER(1, "penalty exponent", "%f", &jmp_exp);
505   GET_PARAMETER(1, "basis functions", "%s", bfcts_name);
506 
507   /*****************************************************************************
508    *  get a mesh, and read the macro triangulation from file
509    ****************************************************************************/
510   data = read_macro(filename);
511   mesh = GET_MESH(dim, "ALBERTA mesh", data,
512 		  NULL /* init_node_projection() */,
513 		  NULL /* init_wall_trafos() */);
514   free_macro_data(data);
515 
516   init_leaf_data(mesh, sizeof(struct ellipt_leaf_data),
517 		 NULL /* refine_leaf_data() */,
518 		 NULL /* coarsen_leaf_data() */);
519 
520   global_refine(mesh, n_refine * mesh->dim, FILL_NOTHING);
521 
522   if (do_graphics) {
523     MSG("Displaying the mesh.\n");
524     graphics(mesh, NULL /* u_h */, NULL /* get_est()*/ , NULL /* u_exact() */,
525 	     HUGE_VAL /* time */);
526   }
527 
528   /*****************************************************************************
529    *  initialize the global variables shared across build(), solve()
530    *  and estimate().
531    ****************************************************************************/
532   sprintf(bfcts_name + strlen(bfcts_name), "%d", degree);
533   bas_fcts = get_bas_fcts(mesh->dim, bfcts_name);
534 
535 #if 0
536   {
537     const QUAD *quad = get_quadrature(bas_fcts->dim, 2 * bas_fcts->degree);
538     const QUAD_FAST *qfast = get_quad_fast(bas_fcts, quad, INIT_PHI);
539     REAL mass_matrix[bas_fcts->n_bas_fcts][bas_fcts->n_bas_fcts];
540     int i, j, iq;
541 
542     for (i = 0; i < bas_fcts->n_bas_fcts; i++) {
543       for (j = 0; j < bas_fcts->n_bas_fcts; j++) {
544 	mass_matrix[i][j] = 0.0;
545 	for (iq = 0; iq < quad->n_points; iq++) {
546 	  mass_matrix[i][j] +=
547 	    qfast->w[iq] * qfast->phi[iq][i] *  qfast->phi[iq][j];
548 	}
549 	MSG("m[%d,%d] = %e\n", i, j, mass_matrix[i][j]);
550       }
551     }
552 
553   }
554 #endif
555 
556   TEST_EXIT(bas_fcts, "no BAS_FCTS of name \"%s\"\n", bfcts_name);
557   fe_space =
558     get_fe_space(mesh, bas_fcts->name, bas_fcts, 1 /* rdim */, ADM_FLAGS_DFLT);
559 
560   matrix = get_dof_matrix("A", fe_space, NULL /* col_fe_space */);
561   f_h    = get_dof_real_vec("f_h", fe_space);
562   u_h    = get_dof_real_vec("u_h", fe_space);
563   u_h->refine_interpol = fe_space->bas_fcts->real_refine_inter;
564   u_h->coarse_restrict = fe_space->bas_fcts->real_coarse_inter;
565   dof_set(0.0, u_h);      /*  initialize u_h */
566 
567   /*****************************************************************************
568    *  init adapt structure and start adaptive method
569    ****************************************************************************/
570   adapt = get_adapt_stat(mesh->dim, "ellipt", "adapt", 2,
571 			 NULL /* ADAPT_STAT storage area, optional */);
572   adapt->estimate = estimate;
573   adapt->get_el_est = get_el_est;
574   adapt->build_after_coarsen = build;
575   adapt->solve = solve;
576 
577 #if 0
578   {
579     const QUAD *quad = get_quadrature(mesh->dim, 10);
580     interpol(u, u_h);
581     MSG("L2-interpolation error before global refine: %e\n",
582 	L2_err(u, u_h, quad,
583 	       false /* relative error*/,
584 	       false /* mean-value adjust */,
585 	       NULL /* rw_err_el()*/, NULL /* max_err_el2 */));
586     if (do_graphics) {
587       graphics(mesh, u_h, NULL, u, HUGE_VAL /* time */);
588     }
589     global_refine(mesh, mesh->dim);
590     MSG("L2-interpolation error after global refine: %e\n",
591 	L2_err(u, u_h, quad,
592 	       false /* relative error*/,
593 	       false /* mean-value adjust */,
594 	       NULL /* rw_err_el()*/, NULL /* max_err_el2 */));
595     if (do_graphics) {
596       graphics(mesh, u_h, NULL, u, HUGE_VAL /* time */);
597     }
598     global_coarsen(mesh, -mesh->dim);
599     MSG("L2-interpolation error after global coarsen: %e\n",
600 	L2_err(u, u_h, quad,
601 	       false /* relative error*/,
602 	       false /* mean-value adjust */,
603 	       NULL /* rw_err_el()*/, NULL /* max_err_el2 */));
604     if (do_graphics) {
605       graphics(mesh, u_h, NULL, u, HUGE_VAL /* time */);
606     }
607   }
608 #endif
609 
610   adapt_method_stat(mesh, adapt);
611 
612   if (do_graphics) {
613     MSG("Displaying u_h, u, (u_h-u) and the final estimate.\n");
614     graphics(mesh, u_h, get_el_est, u, HUGE_VAL /* time */);
615   }
616   WAIT_REALLY;
617 
618   return 0;
619 }
620