1\section{Heat equation}
2\label{S:heat}%
3\idx{implementation of model problems!heat equation}%
4\idx{heat equation!implementation}
5
6In this section we describe a model implementation for the (linear)
7heat equation
8\begin{alignat*}{2}
9 \partial_t u - \Delta u &= f &\qquad&
10                            \mbox{in } \Omega \subset \R^d \times (0,T),\\
11                       u &= g &&\mbox{on } \partial \Omega \times (0,T),\\
12                       u &= u_0 && \mbox{on } \Omega\times\{0\}.
13\end{alignat*}
14
15We describe here only differences to the implementation of the linear
16Poisson problem.  For common (or similar) routines we refer to Section
17\ref{S:poisson-impl}.
18
19\subsection{Global variables}
20Additionally to the finite element space \code{fe\_space}, the matrix
21\code{matrix}, the vectors \code{u\_h} and \code{f\_h} and the
22bit-mask \code{dirichlet\_mask} for marking Dirichlet
23boundary-segments, we need a vector for storage of the solution
24$U_{n}$ from the last time step.  This one is implemented as a global
25variable, too. All these global variables are initialized in
26\code{main()}.
27%%
28\bv\begin{lstlisting}
29static DOF_REAL_VEC *u_old;
30\end{lstlisting}\ev
31%%
32A global pointer to the \code{ADAPT\_INSTAT} structure is used for
33access in the \code{build()} and \code{estimate()} routines, see below.
34\bv\begin{lstlisting}
35static ADAPT_INSTAT   *adapt_instat;
36\end{lstlisting}\ev
37Finally, a global variable \code{theta} is used for storing the
38parameter $\theta$ and \code{err\_L2} for storing the actual $L^2$
39error between true and discrete solution in the actual time step.
40\bv\begin{lstlisting}
41static REAL theta = 0.5;   /*---  parameter of the time discretization   ---*/
42static REAL err_L2  = 0.0; /*---  spatial error in a single time step    ---*/
43\end{lstlisting}\ev
44
45\subsection{The main program for the heat equation}%
46\label{S:heat_main}
47
48The main function initializes all program parameters from file and
49command line (compare \secref{S:parse_parameters}), generates a mesh and a
50finite element space, the DOF matrix and vectors, and allocates and
51fills the parameter structure \code{ADAPT\_INSTAT} for the adaptive
52method for time dependent problems. This structure is accessed by
53\code{get\_adapt\_instat()} which already initializes besides the
54function pointers all members of this structure from the program
55parameters, compare Sections \ref{S:get_adapt} and
56\ref{S:ellipt_main}.
57
58The (initial) time step size, read from the parameter file, is reduced
59when an initial global mesh refinement is performed. This reduction is
60automatically adapted to the order of time discretization (2nd order
61when $\theta=0.5$, 1st order otherwise) and space discretization.  For
62stability reasons, the time step size is scaled by a factor $10^{-3}$
63if $\theta < 0.5$, see also \secref{S:heat_time_discrete}.
64
65Finally, the function pointers for the \code{adapt\_instat()} structure
66are adjusted to the problem dependent routines for the heat equation
67and the complete numerical simulation is performed by a call to
68\code{adapt\_method\_instat()}.
69
70The \code{heat.c} demo-program only implements Dirichlet boundary
71conditions by setting all bits of \code{dirichlet\_mask} to $1$. The
72implementation of more complicated boundary conditions is exemplified
73in the explanation for the \code{ellipt.c} program, see
74\secref{S:poisson-impl}.
75%%
76\bv\begin{lstlisting}
77int main(int argc, char **argv)
78{
79  FUNCNAME("main");
80  MACRO_DATA     *data;
81  MESH           *mesh;
82  const BAS_FCTS *lagrange;
83  int             n_refine = 0, p = 1, dim;
84  char            filename[PATH_MAX];
85  REAL            fac = 1.0;
86
87  /*****************************************************************************
88   * first of all, initialize the access to parameters of the init file
89   ****************************************************************************/
90  parse_parameters(argc, argv, "INIT/heat.dat");
91
92  GET_PARAMETER(1, "mesh dimension", "%d", &dim);
93  GET_PARAMETER(1, "macro file name", "%s", filename);
94  GET_PARAMETER(1, "global refinements", "%d", &n_refine);
95  GET_PARAMETER(1, "parameter theta", "%e", &theta);
96  GET_PARAMETER(1, "polynomial degree", "%d", &p);
97  GET_PARAMETER(1, "online graphics", "%d", &do_graphics);
98
99  /*****************************************************************************
100  *  get a mesh, and read the macro triangulation from file
101  ****************************************************************************/
102  data = read_macro(filename);
103  mesh = GET_MESH(dim, "ALBERTA mesh", data,
104                  NULL /* init_node_projection() */,
105                  NULL /* init_wall_trafos() */);
106  free_macro_data(data);
107
108  init_leaf_data(mesh, sizeof(struct heat_leaf_data),
109                 NULL /* refine_leaf_data() */,
110                 NULL /* coarsen_leaf_data() */);
111
112  /* Finite element spaces can be added at any time, but it is more
113   * efficient to do so before refining the mesh a lot.
114   */
115  lagrange = get_lagrange(mesh->dim, p);
116  TEST_EXIT(lagrange, "no lagrange BAS_FCTS\n");
117
118  fe_space = get_fe_space(mesh, lagrange->name, lagrange, 1, ADM_FLAGS_DFLT);
119
120  global_refine(mesh, n_refine * dim, FILL_NOTHING);
121
122  /*****************************************************************************
123   *  initialize the global variables shared across build(), solve()
124   *  and estimate().
125   ****************************************************************************/
126  matrix = get_dof_matrix("A", fe_space, NULL);
127  f_h    = get_dof_real_vec("f_h", fe_space);
128  u_h    = get_dof_real_vec("u_h", fe_space);
129  u_h->refine_interpol = fe_space->bas_fcts->real_refine_inter;
130  u_h->coarse_restrict = fe_space->bas_fcts->real_coarse_inter;
131  u_old  = get_dof_real_vec("u_old", fe_space);
132  u_old->refine_interpol = fe_space->bas_fcts->real_refine_inter;
133  u_old->coarse_restrict = fe_space->bas_fcts->real_coarse_inter;
134  dof_set(0.0, u_h);      /*  initialize u_h  !                          */
135
136  BNDRY_FLAGS_ALL(dirichlet_mask); /* Only Dirichlet b.c. supported here */
137
138  /*****************************************************************************
139   *  init adapt_instat structure
140   ****************************************************************************/
141  adapt_instat = get_adapt_instat(dim, "heat", "adapt", 2, adapt_instat);
142
143  /* Some animation in between ... */
144  if (do_graphics) {
145    graphics(mesh, NULL, NULL, NULL, adapt_instat->start_time);
146  }
147
148  /*****************************************************************************
149   *  adapt time step size to refinement level and polynomial degree,
150   *  based on the known L2-error error estimates.
151   ****************************************************************************/
152  if (theta < 0.5) {
153    WARNING("You are using the explicit Euler scheme\n");
154    WARNING("Use a sufficiently small time step size!!!\n");
155    fac = 1.0e-3;
156  }
157
158  if (theta == 0.5) {
159    adapt_instat->timestep *= fac*pow(2, -(REAL)(p*(n_refine))/2.0);
160  } else {
161    adapt_instat->timestep *= fac*pow(2, -(REAL)(p*(n_refine)));
162  }
163  MSG("using initial timestep size = %.4le\n", adapt_instat->timestep);
164
165  eval_time_u0 = adapt_instat->start_time;
166
167  adapt_instat->adapt_initial->get_el_est = get_el_est;
168  adapt_instat->adapt_initial->estimate = est_initial;
169  adapt_instat->adapt_initial->solve = interpol_u0;
170
171  adapt_instat->adapt_space->get_el_est   = get_el_est;
172  adapt_instat->adapt_space->get_el_estc  = get_el_estc;
173  adapt_instat->adapt_space->estimate = estimate;
174  adapt_instat->adapt_space->build_after_coarsen = build;
175  adapt_instat->adapt_space->solve = solve;
176
177  adapt_instat->init_timestep  = init_timestep;
178  adapt_instat->set_time       = set_time;
179  adapt_instat->get_time_est   = get_time_est;
180  adapt_instat->close_timestep = close_timestep;
181
182  /*****************************************************************************
183   * ... off we go ...
184   ****************************************************************************/
185  adapt_method_instat(mesh, adapt_instat);
186
187  WAIT_REALLY;
188
189  return 0;
190}
191\end{lstlisting}\ev
192
193\subsection{The parameter file for the heat equation}%
194\label{S:heat_param}
195
196The parameter file for the heat equation \code{INIT/heat.dat} (here
197for the 2d simulations) is similar to the parameter file for the
198Poisson problem. The main differences are additional parameters for
199the adaptive procedure, see \secref{S:adapt_instat_in_ALBERTA}. These
200additional parameters may also be optimized for 1d, 2d, and 3d.
201
202Via the parameter \code{write~finite~element~data} storage of
203meshes and finite element solution for post-processing purposes
204can be done. The parameter \code{write~statistical~data} selects
205storage of files containing number of DOFs, estimate, error, etc. versus
206time. Finally, \code{data~path} can prescribe an existing path
207for storing such data.
208
209\bv\begin{verbatim}
210mesh dimension:       2
211macro file name:      Macro/macro.amc
212global refinements:   4
213polynomial degree:    1
214
215online graphics:      1
216
217% graphic windows: solution, estimate, mesh, and error if size > 0
218graphic windows:       400 400 400 400
219% for gltools graphics you can specify the range for the values of
220% discrete solution for displaying:  min max
221% automatical scaling by display routine if min >= max
222gltools range:  -1.0 1.0
223
224solver:                2 % 1: BICGSTAB 2: CG 3: GMRES 4: ODIR 5: ORES
225solver max iteration:  1000
226solver restart:        10  %  only used for GMRES
227solver tolerance:      1.e-12
228solver info:           2
229solver precon:         1   % 0: no precon 1: diag precon
230                           % 2: HB precon 3: BPX precon
231                           % 4: SSOR, omega = 1.0, #iter = 3
232                           % 5: SSOR, with control over omega and #iter
233                           % 6: ILU(k)
234precon ssor omega:    1.0  % for precon == 5
235precon ssor iter:     1    % for precon == 5
236precon ilu(k):        8    % for precon == 6
237
238parameter theta:               1.0
239adapt->start_time:             0.0
240adapt->end_time:               2.0
241
242adapt->tolerance:              1.0e-3
243adapt->timestep:               1.0e-1
244adapt->rel_initial_error:      0.5
245adapt->rel_space_error:        0.5
246adapt->rel_time_error:         0.5
247adapt->strategy:               0   % 0=explicit, 1=implicit
248adapt->max_iteration:          10
249adapt->info:                   2
250
251adapt->initial->strategy:      3    % 0=none, 1=GR, 2=MS, 3=ES, 4=GERS
252adapt->initial->MS_gamma:      0.5
253adapt->initial->max_iteration: 10
254adapt->initial->info:          2
255
256adapt->space->strategy:        3    % 0=none, 1=GR, 2=MS, 3=ES, 4=GERS
257adapt->space->ES_theta:        0.9
258adapt->space->ES_theta_c:      0.2
259adapt->space->max_iteration:   10
260adapt->space->coarsen_allowed: 1   % 0|1
261adapt->space->info:            2
262
263estimator C0:                  0.1
264estimator C1:                  0.1
265estimator C2:                  0.1
266estimator C3:                  0.1
267
268write finite element data:     1  % write data for post-processing or not
269write statistical data:        0  % write statistical data or not
270data path:                     ./data  % path for data to be written
271
272WAIT:                          0
273\end{verbatim}\ev
274
275\begin{figure}[htbp]
276%\vspace*{-4mm}
277\begin{center}
278\includegraphics[width=0.45\hsize]{EPS/tau-2d}
279\hfil
280\includegraphics[width=0.45\hsize]{EPS/n_dof-2d}
281\end{center}
282\vspace*{-4mm}
283\caption[Adaptivity: heat-equation, time-step size and number of DOFs, 2d]{Time step
284  size (left) and number of DOFs for different polynomial degree
285  (right) over time in 2d.}
286\label{F:heat2d}
287\end{figure}
288\begin{figure}[htbp]
289\vspace*{-4mm}
290\begin{center}
291\includegraphics[width=0.45\hsize]{EPS/tau-3d}
292\quad
293\includegraphics[width=0.45\hsize]{EPS/n_dof-3d}
294\end{center}
295\vspace*{-4mm}
296\caption[Adaptivity: heat-equation, time-step size and number of DOFs, 3d]{Time step size (left) and number of DOFs for different
297  polynomial degree (right) over time in 3d.}
298\label{F:heat3d}
299\end{figure}
300
301Figures \ref{F:heat2d} and \ref{F:heat3d} show the variation of time
302step sizes and number of DOFs over time, automatically generated by
303the adaptive method in two and three space dimensions for a problem
304with time-periodic data. The number of DOFs is depicted for different
305spatial discretization order and shows the strong benefit from using a
306higher order method. The size of time steps was nearly the same for
307all spatial discretizations. Parameters for the adaptive procedure can
308be taken from the corresponding parameter files in 2d and 3d in the
309distribution.
310
311\subsection{Functions for leaf data}%
312\label{S:heat_leaf_data}
313
314For time dependent problems, mesh adaption usually also includes
315coarsening of previously (for smaller $t$) refined parts of the mesh.
316For storage of local coarsening error estimates, the leaf data structure
317is enlarged by a second \code{REAL}. Functions \code{rw\_el\_estc()} and
318\code{get\_el\_estc()} are provided for access to that storage location,
319in addition to the functions \code{rw\_el\_est()} and \code{get\_el\_est()}
320which were already defined in \code{ellipt.c}.
321\bv\begin{lstlisting}
322struct heat_leaf_data
323{
324  REAL estimate;            /*  one real for the element indicator          */
325  REAL est_c;               /*  one real for the coarsening indicator       */
326};
327
328static REAL *rw_el_est(EL *el)
329{
330  if (IS_LEAF_EL(el))
331    return &((struct heat_leaf_data *)LEAF_DATA(el))->estimate;
332  else
333    return NULL;
334}
335
336static REAL get_el_est(EL *el)
337{
338  if (IS_LEAF_EL(el))
339    return ((struct heat_leaf_data *)LEAF_DATA(el))->estimate;
340  else
341    return 0.0;
342}
343
344static REAL *rw_el_estc(EL *el)
345{
346  if (IS_LEAF_EL(el))
347    return &((struct heat_leaf_data *)LEAF_DATA(el))->est_c;
348  else
349    return NULL;
350}
351
352static REAL get_el_estc(EL *el)
353{
354  if (IS_LEAF_EL(el))
355    return ((struct heat_leaf_data *)LEAF_DATA(el))->est_c;
356  else
357    return 0.0;
358}
359\end{lstlisting}\ev
360
361\subsection{Data of the differential equation}%
362\label{S:heat_data}
363
364Data for the heat equation are the initial values $u_0$, right hand
365side $f$, and boundary values $g$. When the true solution $u$ is
366known, it can be used for computing the true error between discrete
367and exact solution.
368
369The sample problem is defined such that the exact solution is
370\[
371   u(x,t) = \sin(\pi t) e^{-10 |x|^2} \quad\text{on } (0,1)^d \times [0,1].
372\]
373
374All library subroutines which evaluate a given data function (for
375integration, e.g.) are defined for space dependent functions only and
376do not know about a time variable. Thus, such a `simple' space dependent
377function $f_{\textrm{space}}(x)$ has to be derived from a space--time
378dependent function $f(x,t)$.  We do this by keeping the time in a
379global variable, and setting
380\[
381  f_{\textrm{space}}(x) := f(x,t).
382\]
383\bv\begin{lstlisting}
384static REAL eval_time_u = 0.0;
385static REAL u(const REAL_D x)
386{
387  return sin(M_PI*eval_time_u)*exp(-10.0*SCP_DOW(x,x));
388}
389
390static REAL eval_time_u0 = 0.0;
391static REAL u0(const REAL_D x)
392{
393  eval_time_u = eval_time_u0;
394  return u(x);
395}
396
397static REAL eval_time_g = 0.0;
398static REAL g(const REAL_D x)              /* boundary values, not optional */
399{
400  eval_time_u = eval_time_g;
401  return u(x);
402}
403
404static REAL eval_time_f = 0.0;
405static REAL f(const REAL_D x)              /* -Delta u, not optional        */
406{
407  REAL  r2 = SCP_DOW(x,x), ux  = sin(M_PI*eval_time_f)*exp(-10.0*r2);
408  REAL  ut = M_PI*cos(M_PI*eval_time_f)*exp(-10.0*r2);
409  return ut - (400.0*r2 - 20.0*DIM)*ux;
410}
411\end{lstlisting}\ev
412
413  As indicated, the times for evaluation of boundary data and right
414  hand side may be chosen independent of each other depending on the
415  kind of time discretization. The value of \code{eval\_time\_f} and
416  \code{eval\_time\_g} are set by the function \code{set\_time()}.
417  Similarly, the evaluation time for the exact solution is set by
418  \code{estimate()} where also the evaluation time of $f$ is set for
419  the evaluation of the element residual. In order to start the
420  simulation not only at $t=0$, we have introduced a variable
421  \code{eval\_time\_u0}, which is set in \code{main()} at the
422  beginning of the program to the value of
423  \code{adapt\_instat->start\_time}.
424
425\subsection{Time discretization}%
426\label{S:heat_time_discrete}
427
428The model implementation uses a variable time discretization scheme.
429Initial data is interpolated on the initial mesh,
430\[
431    U_0 = I_0 u_0.
432\]
433For $\theta\in[0,1]$, the solution $U_{n+1} \approx u(\cdot, t_{n+1})$
434is given by $U_{n+1} \in I_{n+1}g(\cdot, t_{n+1}) + \Xc_{n+1}$ such that
435\begin{alignat}{1}
436\label{E:heat_time_discrete}
437  \frac1{\tau_{n+1}} (U_{n+1}, \Phi) + \theta (\nabla U_{n+1}, \nabla\Phi)
438    =& \frac1{\tau_{n+1}} (I_{n+1}U_{n}, \Phi)
439      - (1-\theta)(\nabla I_{n+1}U_{n}, \nabla\Phi) \\
440      &+ (f(\cdot, t_{n}+\theta\tau_{n+1}), \Phi)
441    \qquad \text{for all }\Phi\in\Xc_{n+1}. \notag
442\end{alignat}
443For $\theta=0$, this is the forward (explicit) Euler scheme, for
444$\theta=1$ the backward (implicit) Euler scheme. For $\theta=0.5$,
445we obtain the Cranck--Nicholson scheme, which is of second order in
446time. For $\theta\in[0.5,1.0]$, the scheme is unconditionally stable,
447while for $\theta<0.5$ stability is only guaranteed if the time step
448size is small enough. For that reason, the time step size is scaled
449by an additional factor of $10^{-3}$ in the main program if $\theta<0.5$.
450But this might not be enough for guaranteeing stability of the scheme!
451We do recommend to use $\theta = 0.5,1$ only.
452
453\subsection{Initial data interpolation}%
454\label{S:heat_initial}
455
456Initial data $u_0$ is just interpolated on the initial mesh, thus the
457\code{solve()} entry in \code{adapt\_instat->adapt\_initial} will
458point to a routine \code{interpol\_u0()} which implements this by the
459library interpolation routine. No \code{build()} routine is needed by
460the initial mesh adaption procedure.
461\bv\begin{lstlisting}
462static void interpol_u0(MESH *mesh)
463{
464  dof_compress(mesh);
465  interpol(u0, u_h);
466
467  return;
468}
469\end{lstlisting}\ev
470
471
472\subsection{The assemblage of the discrete system}%
473\label{S:heat_build}
474
475Using a matrix notation, the discrete problem (\ref{E:heat_time_discrete})
476can be written as
477\[
478   \Big(\frac1{\tau_{n+1}} \boldsymbol{M}
479          + \theta \boldsymbol{A} \Big) \boldsymbol{U}_{n+1}
480    = \Big(\frac1{\tau_{n+1}} \boldsymbol{M}
481   - (1-\theta) \boldsymbol{A} \Big) \boldsymbol{U}_{n} + \boldsymbol{F}_{n+1}.
482\]
483Here, $\boldsymbol{M}=(\Phi_i, \Phi_j)$ denotes the mass matrix and
484$\boldsymbol{A}=(\nabla \Phi_i, \nabla \Phi_j)$ the stiffness matrix
485(up to Dirichlet boundary DOFs). The system matrix on the left hand
486side is not the same as the one applied to the
487old solution on the right hand side. But we want to compute the
488contribution of the solution form the old time step $U_n$ to the right
489hand side vector efficiently by a simple matrix--vector multiplication
490and thus avoiding additional element-wise integration.  For doing this
491without storing both matrices $\boldsymbol{M}$ and $\boldsymbol{A}$ we
492are using the element-wise strategy explained and used in
493Section~\ref{S:nonlin_solve} when assembling the linearized equation
494in the Newton iteration for solving the nonlinear reaction--diffusion
495equation.
496
497The subroutine \code{assemble()} generates both the system matrix and
498the right hand side at the same time. The mesh elements are visited
499via the non-recursive mesh traversal routines. On every leaf element,
500both the element mass matrix \code{c\_mat} and the element stiffness
501matrix \code{a\_mat} are calculated using the \code{el\_matrix\_fct()}
502provided by \code{fill\_matrix\_info()}.  For this purpose, two
503different operators (the mass and stiffness operators) are defined and
504applied on each element.  The stiffness operator uses the same
505\code{LALt()} function for the second order term as described in
506Section \ref{S:ellipt_build}; the mass operator implements only the
507constant zero order coefficient $c=1/\tau_{n+1}$, which is passed in
508\code{struct op\_data} and evaluated in the function \code{c()}. The
509initialization and access to these operators is done in the same way
510as in Section~\ref{S:nonlin_solve} where this is described in detail.
511During the non-recursive mesh traversal, the element stiffness matrix
512and the mass matrix are computed and added to the global system
513matrix.  Then, the contribution to the right hand side vector of the
514solution from the old time step is computed by a matrix--vector
515product of these element matrices with the local coefficient vector on
516the element of $U_n$ and added to the global load vector (see
517\tableref{tab:elementblas2} for \code{bi\_mat\_el\_vec()}).
518
519After this step, the the right hand side $f$ and Dirichlet boundary
520values $g$ are treated by the standard routines.
521
522\begin{compatibility}
523  In contrast to previous \ALBERTA versions, the element-vectors and
524  -matrices are no longer flat \code{C}-arrays, but ``cooked''
525  data-structures, with some support routines doing basis linear
526  algebra. See \secref{S:elvecmat}.
527\end{compatibility}
528
529\bv\begin{lstlisting}
530struct op_data         /* application data (resp. "user_data") */
531{
532  REAL_BD Lambda;      /*  the gradient of the barycentric coordinates */
533  REAL    det;         /*  |det D F_S|                                 */
534
535  REAL    tau_1;
536};
537
538static REAL c(const EL_INFO *el_info, const QUAD *quad, int iq, void *ud)
539{
540  struct op_data *info = (struct op_data *)ud;
541
542  return info->tau_1*info->det;
543}
544
545static void assemble(DOF_MATRIX *matrix, DOF_REAL_VEC *fh, DOF_REAL_VEC *uh,
546                     const DOF_REAL_VEC *u_old,
547                     REAL theta, REAL tau,
548                     REAL (*f)(const REAL_D), REAL (*g)(const REAL_D),
549                     const BNDRY_FLAGS dirichlet_mask)
550{
551  /* Some quantities remembered across calls. Think of this routine
552   * like being a "library function" ... The stuff is re-initialized
553   * whenever the finite element space changes. We use fe_space->admin
554   * to check for changes in the finite element space because
555   * DOF_ADMIN's are persisitent within ALBERTA, while fe-space are
556   * not.
557   */
558  static EL_MATRIX_INFO stiff_elmi, mass_elmi;
559  static const DOF_ADMIN *admin = NULL;
560  static const QUAD *quad = NULL;
561  static struct op_data op_data[1]; /* storage for det and Lambda */
562
563  /* Remaining (non-static) variables. */
564  const BAS_FCTS *bas_fcts;
565  FLAGS          fill_flag;
566  REAL           *f_vec;
567  int            nbf;
568  EL_SCHAR_VEC   *bound;
569
570  /* Initialize persistent variables. */
571  if (admin != uh->fe_space->admin) {
572    OPERATOR_INFO stiff_opi = { NULL, }, mass_opi = { NULL, };
573
574    admin    = uh->fe_space->admin;
575
576    stiff_opi.row_fe_space   = uh->fe_space;
577    stiff_opi.quad[2]        = quad;
578    stiff_opi.LALt.real      = LALt;
579    stiff_opi.LALt_pw_const  = true;
580    stiff_opi.LALt_symmetric = true;
581    stiff_opi.user_data      = op_data;
582
583    fill_matrix_info(&stiff_opi, &stiff_elmi);
584
585    mass_opi.row_fe_space = uh->fe_space;
586    mass_opi.quad[0]      = quad;
587    mass_opi.c.real       = c;
588    mass_opi.c_pw_const   = true;
589    mass_opi.user_data    = op_data;
590
591    fill_matrix_info(&mass_opi, &mass_elmi);
592
593    quad = get_quadrature(uh->fe_space->bas_fcts->dim,
594                          2*uh->fe_space->bas_fcts->degree);
595  }
596
597  op_data->tau_1 = 1.0/tau;
598
599  /* Assemble the matrix and the right hand side. The idea is to
600   * assemble the local mass and stiffness matrices only once, and to
601   * use it to update both, the system matrix and the contribution of
602   * the time discretisation to the RHS.
603   */
604  clear_dof_matrix(matrix);
605  dof_set(0.0, fh);
606  f_vec = fh->vec;
607
608  bas_fcts = uh->fe_space->bas_fcts;
609  nbf      = bas_fcts->n_bas_fcts;
610
611  bound = get_el_schar_vec(bas_fcts);
612
613  fill_flag = CALL_LEAF_EL|FILL_COORDS|FILL_BOUND;
614  TRAVERSE_FIRST(uh->fe_space->mesh, -1, fill_flag) {
615    const EL_REAL_VEC  *u_old_loc;
616    const EL_DOF_VEC   *dof;
617    const EL_BNDRY_VEC *bndry_bits;
618    const EL_MATRIX *stiff_loc, *mass_loc;
619
620    /* Get the local coefficients of u_old, boundary info, dof-mapping */
621    u_old_loc  = fill_el_real_vec(NULL, el_info->el, u_old);
622    dof        = get_dof_indices(NULL, uh->fe_space, el_info->el);
623    bndry_bits = get_bound(NULL, bas_fcts, el_info);
624
625    /* Initialization of values used by LALt and c. It is not
626     * necessary to introduce an extra "init_element()" hook for our
627     * OPERATOR_INFO structures; the line below is just what would be
628     * contained in that function (compare with ellipt.c).
629     *
630     * Beware to replace the "..._0cd()" for co-dimension 0 by its
631     * proper ..._dim() variant if ever "porting" this stuff to
632     * parametric meshes on manifolds.
633     */
634    op_data->det = el_grd_lambda_0cd(el_info, op_data->Lambda);
635
636    /* Obtain the local (i.e. per-element) matrices. */
637    stiff_loc = stiff_elmi.el_matrix_fct(el_info, stiff_elmi.fill_info);
638    mass_loc  = mass_elmi.el_matrix_fct(el_info, mass_elmi.fill_info);
639
640    /* Translate the geometric boundary classification into
641     * Dirichlet/Neumann/Interior boundary condition
642     * interpretation. Inside the loop over the mesh-elements we need
643     * only to care about Dirichlet boundary conditions.
644     */
645    dirichlet_map(bound, bndry_bits, dirichlet_mask);
646
647    /* add theta*a(psi_i,psi_j) + 1/tau*m(4*u^3*psi_i,psi_j) */
648    if (theta) {
649      add_element_matrix(matrix,
650                         theta, stiff_loc, NoTranspose, dof, dof, bound);
651    }
652    add_element_matrix(matrix, 1.0, mass_loc, NoTranspose, dof, dof, bound);
653
654      /* compute the contributions from the old time-step:
655       *
656       * f += -(1-theta)*a(u_old,psi_i) + 1/tau*m(u_old,psi_i)
657       */
658    bi_mat_el_vec(-(1.0 - theta), stiff_loc,
659                  1.0, mass_loc, u_old_loc,
660                  1.0, fh, dof, bound);
661
662  } TRAVERSE_NEXT();
663
664  free_el_schar_vec(bound);
665
666  /* Indicate that the boundary conditions are built into the matrix,
667   * needed e.g. by the hierarchical preconditioners.
668   */
669  BNDRY_FLAGS_CPY(matrix->dirichlet_bndry, dirichlet_mask);
670
671  /* Add the "force-term" to the right hand side (L2scp_...() is additive) */
672  L2scp_fct_bas(f, quad, fh);
673
674  /* Close the system by imposing suitable boundary conditions. Have a
675   * look at ellipt.c for how to impose more complicated stuff; here
676   * we only use Dirichlet b.c.
677   */
678  dirichlet_bound(fh, uh, NULL, dirichlet_mask, g);
679}
680\end{lstlisting}\ev
681
682The \code{build()} routine for one time step of the heat equation is
683nearly a dummy routine and just calls the \code{assemble()} routine
684described above. In order to avoid holes in vectors and matrices, as a
685first step, the mesh is compressed. This guarantees optimal
686performance of the BLAS1 routines used in the iterative solvers.
687
688\bv\begin{lstlisting}
689static void build(MESH *mesh, U_CHAR flag)
690{
691  FUNCNAME("build");
692
693  dof_compress(mesh);
694
695  INFO(adapt_instat->adapt_space->info, 2,
696    "%d DOFs for %s\n", fe_space->admin->size_used, fe_space->name);
697
698  assemble(matrix, f_h, u_h, u_old, theta, adapt_instat->timestep,
699           f, g, dirichlet_mask);
700}
701\end{lstlisting}\ev
702
703The resulting linear system is solved by calling the \code{oem\_solve\_s()}
704library routine. This is done via the \code{solve()} subroutine described
705in \secref{S:ellipt_solve}.
706
707
708\subsection{Error estimation}%
709\label{S:heat_estimate}
710
711The initial error $\|U_0-u_0\|_{L^2(\Omega)}$ is calculated exactly
712(up to quadrature error) by a call to \code{L2\_err()}. Local error
713contributions are written via \code{rw\_el\_est()} to the \code{estimate}
714value in \code{struct heat\_leaf\_data}. The \code{err\_max} and
715\code{err\_sum} of the \code{ADAPT\_STAT} structure (which will be
716\code{adapt\_instat->adapt\_initial}, see below) are set accordingly.
717\bv\begin{lstlisting}
718static REAL est_initial(MESH *mesh, ADAPT_STAT *adapt)
719{
720  err_L2 = adapt->err_sum =
721    L2_err(u0, u_h, NULL, false, false, rw_el_est, &adapt->err_max);
722  return adapt->err_sum;
723}
724\end{lstlisting}\ev
725
726  In each time step, error estimation is done by the library routine
727  \code{heat\_est()}, which generates both time and space
728  discretization indicators, compare Section \ref{S:heat_estimate}.
729  Similar to the estimator for elliptic problems, a function
730  \code{r()} is needed for computing contributions of lower order
731  terms and the right hand side. The flag for passing information
732  about the discrete solution $U_{n+1}$ or its gradient to \code{r()}
733  is set to zero in \code{estimate()} since no lower order term is
734  involved.
735
736  Local element indicators are stored to the \code{estimate} or
737  \code{est\_c} entries inside the data structure
738  \code{struct heat\_leaf\_data} via
739  \code{rw\_el\_est()} and \code{rw\_el\_estc()}. The \code{err\_max}
740  and \code{err\_sum} entries of \code{adapt->adapt\_space} are set
741  accordingly. The temporal error indicator is the return value by
742  \code{heat\_est()} and is stored in a global variable for later
743  access by \code{get\_time\_est()}. In this example, the true
744  solution is known and thus the true error $\|u(\cdot,t_{n+1})
745  -U_{n+1}\|_{L^2(\Omega)}$ is calculated additionally for comparison.
746\bv\begin{lstlisting}
747static REAL r(const EL_INFO *el_info,
748              const QUAD *quad, int iq,
749              REAL uh_at_qp, const REAL_D grd_uh_at_qp,
750              REAL t)
751{
752  REAL_D      x;
753
754  coord_to_world(el_info, quad->lambda[iq], x);
755  eval_time_f = t;
756
757  return -f(x);
758}
759
760
761static REAL estimate(MESH *mesh, ADAPT_STAT *adapt)
762{
763  FUNCNAME("estimate");
764  static REAL    C[4] = {-1.0, 1.0, 1.0, 1.0};
765  static REAL_DD A = {{0.0}};
766  FLAGS          r_flag = 0;  /* = (INIT_UH|INIT_GRD_UH), if needed by r()  */
767  int            n;
768  REAL           space_est;
769
770  eval_time_u = adapt_instat->time;
771
772  if (C[0] < 0) {
773    C[0] = 1.0;
774    GET_PARAMETER(1, "estimator C0", "%f", &C[0]);
775    GET_PARAMETER(1, "estimator C1", "%f", &C[1]);
776    GET_PARAMETER(1, "estimator C2", "%f", &C[2]);
777    GET_PARAMETER(1, "estimator C3", "%f", &C[3]);
778
779    for (n = 0; n < DIM_OF_WORLD; n++) {
780      A[n][n] = 1.0;   /* set diogonal of A; all other elements are zero */
781    }
782  }
783
784  time_est = heat_est(u_h, u_old, adapt_instat, rw_el_est, rw_el_estc,
785                      -1 /* quad_degree */,
786                      C, (const REAL_D *)A, dirichlet_mask,
787                      r, r_flag, NULL /* gn() */, 0 /* gn_flag */);
788
789  space_est = adapt_instat->adapt_space->err_sum;
790  err_L2 = L2_err(u, u_h, NULL, false, false, NULL, NULL);
791
792  INFO(adapt_instat->info,2,
793    "---8<---------------------------------------------------\n");
794  INFO(adapt_instat->info, 2,"time = %.4le with timestep = %.4le\n",
795                              adapt_instat->time, adapt_instat->timestep);
796  INFO(adapt_instat->info, 2,"estimate   = %.4le, max = %.4le\n", space_est,
797                              sqrt(adapt_instat->adapt_space->err_max));
798  INFO(adapt_instat->info, 2,"||u-uh||L2 = %.4le, ratio = %.2lf\n", err_L2,
799                              err_L2/MAX(space_est,1.e-20));
800
801  return adapt_instat->adapt_space->err_sum;
802}
803\end{lstlisting}\ev
804
805
806\subsection{Time steps}%
807\label{S:heat_timestep}
808
809Time dependent problems are calculated step by step in single time steps. In a
810fully implicit time-adaptive strategy, each time step includes an adaptation
811of the time step size as well as an adaptation of the corresponding spatial
812discretization. First, the time step size is adapted and then the mesh
813adaptation procedure is performed. This second part may again push the
814estimate for the time discretization error over the corresponding tolerance.
815In this case, the time step size is again reduced and the whole procedure is
816iterated until both, time and space discretization error estimates meet the
817prescribed tolerances (or until a maximal number of iterations is performed).
818For details and other time-adaptive strategies see
819\secref{S:adapt_instat_in_ALBERTA}.
820
821Besides the \code{build()}, \code{solve()}, and \code{estimate()}
822routines for the adaptation of the initial grid and the grids in each
823time steps, additional routines for initializing time steps, setting
824time step size of the actual time step, and finalizing a time
825step are needed. For adaptive control of the time step size, the
826function \code{get\_time\_est()} gives information
827about the size of the time discretization error.  The actual time
828discretization error is stored in the global variable \code{time\_est}
829and its value is set in the function \code{estimate()}.
830
831During the initialization of a new time step in
832\code{init\_timestep()}, the discrete solution \code{u\_h} from the
833old time step (or from interpolation of initial data) is copied to
834\code{u\_old}. In the function \code{set\_time()} evaluation times for
835the right hand side $f$ and Dirichlet boundary data $g$ are set
836accordingly to the chosen time discretization scheme. Since a time
837step can be rejected by the adaptive method by a too large time
838discretization error, this function can be called several times during
839the computation of a single time step.  On each call, information
840about the actual time and time step size is accessed via the entries
841\code{time} and \code{timestep} of the \code{adapt\_instat} structure.
842
843After accepting the current time step size and current grid by the
844adaptive method, the time step is completed by
845\code{close\_time\_step()}.  The variables \code{space\_est},
846\code{time\_est}, and \code{err\_L2} now hold the final estimates resp.
847error, and \code{u\_h} the accepted finite element solution for this
848time step. The final mesh and discrete solution can now be written to
849file for post-processing purposes, depending on the parameter value of
850\code{write finite element data}. The file name for the mesh/solution
851consists of the data path, the base name \code{mesh}/\code{u\_h}, and
852the iteration counter of the actual time step. Such a composition can
853be easily constructed by the function \code{generate\_filename()},
854described in \secref{S:generate_filename}.
855Mesh and finite element solution are then exported to file
856by the \code{write\_*\_xdr()} routines in a portable binary format.
857Using this procedure, the sequence of discrete solutions can easily
858be visualized by the program \code{alberta\_movi} which
859is an interface to GRAPE and comes with the distribution of \ALBERTA,
860compare \secref{S:graph_GRAPE}.
861
862Depending on the parameter value of \code{write statistical data}, the
863evolution of estimates, error, number of DOFs, size of time step size,
864etc.  are written to files by the function
865\code{write\_statistical\_data()}, which is included in \code{heat.c}
866but not described here. It produces for each quantity a two-column
867data file where the first column contains time and the second column
868estimate, error, etc. Such data can easily be evaluated by standard
869(graphic) tools.
870
871Finally, a graphical output of the solution and the mesh is generated
872via the \code{graphics()} routine already used in the previous examples.
873
874\bv\begin{lstlisting}
875static REAL time_est = 0.0;
876
877static REAL get_time_est(MESH *mesh, ADAPT_INSTAT *adapt)
878{
879  return(time_est);
880}
881
882static void init_timestep(MESH *mesh, ADAPT_INSTAT *adapt)
883{
884  FUNCNAME("init_timestep");
885
886  INFO(adapt_instat->info,1,
887    "---8<---------------------------------------------------\n");
888  INFO(adapt_instat->info, 1,"starting new timestep\n");
889
890  dof_copy(u_h, u_old);
891  return;
892}
893
894static void set_time(MESH *mesh, ADAPT_INSTAT *adapt)
895{
896  FUNCNAME("set_time");
897
898  INFO(adapt->info,1,
899    "---8<---------------------------------------------------\n");
900  if (adapt->time == adapt->start_time)
901  {
902    INFO(adapt->info, 1,"start time: %.4le\n", adapt->time);
903  }
904  else
905  {
906    INFO(adapt->info, 1,"timestep for (%.4le %.4le), tau = %.4le\n",
907                         adapt->time-adapt->timestep, adapt->time,
908                         adapt->timestep);
909  }
910
911  eval_time_f = adapt->time - (1 - theta)*adapt->timestep;
912  eval_time_g = adapt->time;
913
914  return;
915}
916
917static void close_timestep(MESH *mesh, ADAPT_INSTAT *adapt)
918{
919  FUNCNAME("close_timestep");
920  static REAL err_max = 0.0;                     /* max space-time error    */
921  static REAL est_max = 0.0;                     /* max space-time estimate */
922  static int  write_fe_data = 0, write_stat_data = 0;
923  static int  step = 0;
924  static char path[256] = "./";
925
926  REAL        space_est = adapt->adapt_space->err_sum;
927  REAL        tolerance = adapt->rel_time_error*adapt->tolerance;
928
929  err_max = MAX(err_max, err_L2);
930  est_max = MAX(est_max, space_est + time_est);
931
932  INFO(adapt->info,1,
933    "---8<---------------------------------------------------\n");
934
935  if (adapt->time == adapt->start_time)
936  {
937    tolerance = adapt->adapt_initial->tolerance;
938    INFO(adapt->info,1,"start time: %.4le\n", adapt->time);
939  }
940  else
941  {
942    tolerance += adapt->adapt_space->tolerance;
943    INFO(adapt->info,1,"timestep for (%.4le %.4le), tau = %.4le\n",
944                        adapt->time-adapt->timestep, adapt->time,
945                        adapt->timestep);
946  }
947  INFO(adapt->info,2,"max. est.  = %.4le, tolerance = %.4le\n",
948                      est_max, tolerance);
949  INFO(adapt->info,2,"max. error = %.4le, ratio = %.2lf\n",
950                      err_max, err_max/MAX(est_max,1.0e-20));
951
952  if (!step) {
953    GET_PARAMETER(1, "write finite element data", "%d", &write_fe_data);
954    GET_PARAMETER(1, "write statistical data", "%d", &write_stat_data);
955    GET_PARAMETER(1, "data path", "%s", path);
956  }
957
958  /*****************************************************************************
959   * write mesh and discrete solution to file for post-processing
960   ****************************************************************************/
961
962  if (write_fe_data) {
963    const char *fn;
964
965    fn= generate_filename(path, "mesh", step);
966    write_mesh_xdr(mesh, fn, adapt->time);
967    fn= generate_filename(path, "u_h", step);
968    write_dof_real_vec_xdr(u_h, fn);
969  }
970
971  step++;
972
973  /*****************************************************************************
974   * write data about estimate, error, time step size, etc.
975   ****************************************************************************/
976
977  if (write_stat_data) {
978    int n_dof = fe_space->admin->size_used;
979    write_statistics(path, adapt, n_dof, space_est, time_est, err_L2);
980  }
981
982  if (do_graphics) {
983    graphics(mesh, u_h, get_el_est, u, adapt->time);
984  }
985
986  return;
987}
988\end{lstlisting}\ev
989
990
991%%% Local Variables:
992%%% mode: latex
993%%% TeX-master: "alberta-man"
994%%% End:
995