<a name="Intro"></a>
<h1>Introduction</h1>

<h3>The maths</h3>

The Heidelberg group of Professor Rolf Rannacher, to which the three initial
authors of the deal.II library belonged during their PhD time and partly also
afterwards, has been involved with adaptivity and error estimation for finite
element discretizations since the mid-1990ies. The main achievement is the
development of error estimates for arbitrary functionals of the solution, and
of optimal mesh refinement for its computation.

We will not discuss the derivation of these concepts in too great detail, but
will implement the main ideas in the present example program. For a thorough
introduction into the general idea, we refer to the seminal work of Becker and
Rannacher @cite BR95, @cite BR96r, and the overview article of the same authors in
Acta Numerica @cite BR01; the first introduces the concept of error
estimation and adaptivity for general functional output for the Laplace
equation, while the second gives many examples of applications of these
concepts to a large number of other, more complicated equations. For
applications to individual types of equations, see also the publications by
Becker @cite Bec95, @cite Bec98, Kanschat @cite Kan96, @cite FK97, Suttmeier
@cite Sut96, @cite RS97, @cite RS98c, @cite RS99, Bangerth @cite BR99b,
@cite Ban00w, @cite BR01a, @cite Ban02, and Hartmann @cite Har02, @cite HH01,
@cite HH01b. All of these works, from the original introduction by Becker and
Rannacher to individual contributions to particular equations, have later been
summarized in a book by Bangerth and Rannacher that covers all of these topics,
see @cite BR03.


The basic idea is the following: in applications, one is not usually
interested in the solution per se, but rather in certain aspects of it. For
example, in simulations of flow problems, one may want to know the lift or
drag of a body immersed in the fluid; it is this quantity that we want to know
to best accuracy, and whether the rest of the solution of the describing
equations is well resolved is not of primary interest. Likewise, in elasticity
one might want to know about values of the stress at certain points to guess
whether maximal load values of joints are safe, for example. Or, in radiative
transfer problems, mean flux intensities are of interest.

In all the cases just listed, it is the evaluation of a functional $J(u)$ of
the solution which we are interested in, rather than the values of $u$
everywhere. Since the exact solution $u$ is not available, but only its
numerical approximation $u_h$, it is sensible to ask whether the computed
value $J(u_h)$ is within certain limits of the exact value $J(u)$, i.e. we
want to bound the error with respect to this functional, $J(u)-J(u_h)$.

For simplicity of exposition, we henceforth assume that both the quantity of
interest $J$ as well as the equation are linear, and we will in particular
show the derivation for the Laplace equation with homogeneous Dirichlet
boundary conditions, although the concept is much more general. For this
general case, we refer to the references listed above.  The goal is to obtain
bounds on the error, $J(e)=J(u)-J(u_h)$. For this, let us denote by $z$ the
solution of a dual problem, defined as follows:
@f[
  a(\varphi,z) = J(\varphi) \qquad \forall \varphi,
@f]
where $a(\cdot,\cdot)$ is the bilinear form associated with the differential
equation, and the test functions are chosen from the corresponding solution
space. Then, taking as special test function $\varphi=e$ the error, we have
that
@f[
  J(e) = a(e,z)
@f]
and we can, by Galerkin orthogonality, rewrite this as
@f[
  J(e) = a(e,z-\varphi_h)
@f]
where $\varphi_h$ can be chosen from the discrete test space in
whatever way we find convenient.

Concretely, for Laplace's equation, the error identity reads
@f[
  J(e) = (\nabla e, \nabla(z-\varphi_h)).
@f]
Because we want to use this formula not only to compute error, but
also to refine the mesh, we need to rewrite the expression above as a
sum over cells where each cell's contribution can then be used as an
error indicator for this cell.
Thus, we split the scalar products into terms for each cell, and
integrate by parts on each of them:
@f{eqnarray*}
  J(e)
  &=&
  \sum_K (\nabla (u-u_h), \nabla (z-\varphi_h))_K
  \\
  &=&
  \sum_K (-\Delta (u-u_h), z-\varphi_h)_K
  + (\partial_n (u-u_h), z-z_h)_{\partial K}.
@f}
Next we use that $-\Delta u=f$, and that
the solution of the Laplace equation is smooth enough that $\partial_n u$ is
continuous almost everywhere -- so the terms involving $\partial_n u$ on one
cell cancels with that on its neighbor, where the normal vector has the
opposite sign. (The same is not true for $\partial_n u_h$, though.)
At the boundary of the domain, where there is no neighbor cell
with which this term could cancel, the weight $z-\varphi_h$ can be chosen as
zero, and the whole term disappears.

Thus, we have
@f{eqnarray*}
  J(e)
  &=&
  \sum_K (f+\Delta u_h, z-\varphi_h)_K
  - (\partial_n u_h, z-\varphi_h)_{\partial K\backslash \partial\Omega}.
@f}
In a final step, note that when taking the normal derivative of $u_h$, we mean
the value of this quantity as taken from this side of the cell (for the usual
Lagrange elements, derivatives are not continuous across edges). We then
rewrite the above formula by exchanging half of the edge integral of cell $K$
with the neighbor cell $K'$, to obtain
@f{eqnarray*}
  J(e)
  &=&
  \sum_K (f+\Delta u_h, z-\varphi_h)_K
  - \frac 12 (\partial_n u_h|_K + \partial_{n'} u_h|_{K'},
              z-\varphi_h)_{\partial K\backslash \partial\Omega}.
@f}
Using that for the normal vectors on adjacent cells we have $n'=-n$, we define
the jump of the normal derivative by
@f[
  [\partial_n u_h] \dealcoloneq \partial_n u_h|_K + \partial_{n'} u_h|_{K'}
  =
  \partial_n u_h|_K - \partial_n u_h|_{K'},
@f]
and get the final form after setting the discrete function $\varphi_h$, which
is by now still arbitrary, to the point interpolation of the dual solution,
$\varphi_h=I_h z$:
@f{eqnarray*}
  J(e)
  &=&
  \sum_K (f+\Delta u_h, z-I_h z)_K
  - \frac 12 ([\partial_n u_h],
              z-I_h z)_{\partial K\backslash \partial\Omega}.
@f}

With this, we have obtained an exact representation of the error of the finite
element discretization with respect to arbitrary (linear) functionals
$J(\cdot)$. Its structure is a weighted form of a residual estimator, as both
$f+\Delta u_h$ and $[\partial_n u_h]$ are cell and edge residuals that vanish
on the exact solution, and $z-I_h z$ are weights indicating how important the
residual on a certain cell is for the evaluation of the given functional.
Furthermore, it is a cell-wise quantity, so we can use it as a mesh refinement
criterion. The question is: how to evaluate it? After all, the evaluation
requires knowledge of the dual solution $z$, which carries the information
about the quantity we want to know to best accuracy.

In some, very special cases, this dual solution is known. For example, if the
functional $J(\cdot)$ is the point evaluation, $J(\varphi)=\varphi(x_0)$, then
the dual solution has to satisfy
@f[
  -\Delta z = \delta(x-x_0),
@f]
with the Dirac delta function on the right hand side, and the dual solution is
the Green's function with respect to the point $x_0$. For simple geometries,
this function is analytically known, and we could insert it into the error
representation formula.

However, we do not want to restrict ourselves to such special cases. Rather,
we will compute the dual solution numerically, and approximate $z$ by some
numerically obtained $\tilde z$. We note that it is not sufficient to compute
this approximation $\tilde z$ using the same method as used for the primal
solution $u_h$, since then $\tilde z-I_h \tilde z=0$, and the overall error
estimate would be zero. Rather, the approximation $\tilde z$ has to be from a
larger space than the primal finite element space. There are various ways to
obtain such an approximation (see the cited literature), and we will choose to
compute it with a higher order finite element space. While this is certainly
not the most efficient way, it is simple since we already have all we need to
do that in place, and it also allows for simple experimenting. For more
efficient methods, again refer to the given literature, in particular
@cite BR95, @cite BR03.

With this, we end the discussion of the mathematical side of this program and
turn to the actual implementation.


@note There are two steps above that do not seem necessary if all you
care about is computing the error: namely, (i) the subtraction of
$\phi_h$ from $z$, and (ii) splitting the integral into a sum of cells
and integrating by parts on each. Indeed, neither of these two steps
change $J(e)$ at all, as we only ever consider identities above until
the substitution of $z$ by $\tilde z$. In other words, if you care
only about <i>estimating the global error</i> $J(e)$, then these steps
are not necessary. On the other hand, if you want to use the error
estimate also as a refinement criterion for each cell of the mesh,
then it is necessary to (i) break the estimate into a sum of cells,
and (ii) massage the formulas in such a way that each cell's
contributions have something to do with the local error. (While the
contortions above do not change the value of the <i>sum</i> $J(e)$,
they change the values we compute for each cell $K$.) To this end, we
want to write everything in the form "residual times dual weight"
where a "residual" is something that goes to zero as the approximation
becomes $u_h$ better and better. For example, the quantity $\partial_n
u_h$ is not a residual, since it simply converges to the (normal
component of) the gradient of the exact solution. On the other hand,
$[\partial_n u_h]$ is a residual because it converges to $[\partial_n
u]=0$. All of the steps we have taken above in developing the final
form of $J(e)$ have indeed had the goal of bringing the final formula
into a form where each term converges to zero as the discrete solution
$u_h$ converges to $u$. This then allows considering each cell's
contribution as an "error indicator" that also converges to zero -- as
it should as the mesh is refined.



<h3>The software</h3>

The step-14 example program builds heavily on the techniques already used in
the step-13 program. Its implementation of the dual weighted residual error
estimator explained above is done by deriving a second class, properly called
<code>DualSolver</code>, from the <code>Solver</code> base class, and having a class
(<code>WeightedResidual</code>) that joins the two again and controls the solution
of the primal and dual problem, and then uses both to compute the error
indicator for mesh refinement.

The program continues the modular concept of the previous example, by
implementing the dual functional, describing quantity of interest, by an
abstract base class, and providing two different functionals which implement
this interface. Adding a different quantity of interest is thus simple.

One of the more fundamental differences is the handling of data. A common case
is that you develop a program that solves a certain equation, and test it with
different right hand sides, different domains, different coefficients and
boundary values, etc. Usually, these have to match, so that exact solutions
are known, or that their combination makes sense at all.

We demonstrate a way how this can be achieved in a simple, yet very flexible
way. We will put everything that belongs to a certain setup into one class,
and provide a little C++ mortar around it, so that entire setups (domains,
coefficients, right hand sides, etc.) can be exchanged by only changing
something in <em>one</em> place.

Going this way a little further, we have also centralized all the other
parameters that describe how the program is to work in one place, such as the
order of the finite element, the maximal number of degrees of freedom, the
evaluation objects that shall be executed on the computed solutions, and so
on. This allows for simpler configuration of the program, and we will show in
a later program how to use a library class that can handle setting these
parameters by reading an input file. The general aim is to reduce the places
within a program where one may have to look when wanting to change some
parameter, as it has turned out in practice that one forgets where they are as
programs grow. Furthermore, putting all options describing what the program
does in a certain run into a file (that can be stored with the results) helps
repeatability of results more than if the various flags were set somewhere in
the program, where their exact values are forgotten after the next change to
this place.

Unfortunately, the program has become rather long. While this admittedly
reduces its usefulness as an example program, we think that it is a very good
starting point for development of a program for other kinds of problems,
involving different equations than the Laplace equation treated here.
Furthermore, it shows everything that we can show you about our way of a
posteriori error estimation, and its structure should make it simple for you
to adjust this method to other problems, other functionals, other geometries,
coefficients, etc.

The author believes that the present program is his masterpiece among the
example programs, regarding the mathematical complexity, as well as the
simplicity to add extensions. If you use this program as a basis for your own
programs, we would kindly like to ask you to state this fact and the name of
the author of the example program, Wolfgang Bangerth, in publications that
arise from that, if your program consists in a considerable part of the
example program.
