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