<h1>Results</h1>


The output of the program looks as follows:
@code
Cycle 0:
   Number of active cells:       20
   Number of degrees of freedom: 89
Cycle 1:
   Number of active cells:       38
   Number of degrees of freedom: 183
Cycle 2:
   Number of active cells:       71
   Number of degrees of freedom: 325
Cycle 3:
   Number of active cells:       146
   Number of degrees of freedom: 689
Cycle 4:
   Number of active cells:       284
   Number of degrees of freedom: 1373
Cycle 5:
   Number of active cells:       599
   Number of degrees of freedom: 2769
Cycle 6:
   Number of active cells:       1241
   Number of degrees of freedom: 5833
Cycle 7:
   Number of active cells:       2507
   Number of degrees of freedom: 11916
@endcode



As intended, the number of cells roughly doubles in each cycle. The
number of degrees is slightly more than four times the number of
cells; one would expect a factor of exactly four in two spatial
dimensions on an infinite grid (since the spacing between the degrees
of freedom is half the cell width: one additional degree of freedom on
each edge and one in the middle of each cell), but it is larger than
that factor due to the finite size of the mesh and due to additional
degrees of freedom which are introduced by hanging nodes and local
refinement.



The program outputs the solution and mesh in each cycle of the
refinement loop. The solution looks as follows:

<img src="https://www.dealii.org/images/steps/developer/step-6.solution.9.2.png" alt="">

It is interesting to follow how the program arrives at the final mesh:

<div class="twocolumn" style="width: 80%">
  <div>
    <img src="https://www.dealii.org/images/steps/developer/step_6_grid_0.svg"
         alt="Initial grid: the five-cell circle grid with one global refinement."
         width="300" height="300">
  </div>
  <div>
    <img src="https://www.dealii.org/images/steps/developer/step_6_grid_1.svg"
         alt="First grid: the five-cell circle grid with two global refinements."
         width="300" height="300">
  </div>
  <div>
    <img src="https://www.dealii.org/images/steps/developer/step_6_grid_2.svg"
         alt="Second grid: the five-cell circle grid with one adaptive refinement."
         width="300" height="300">
  </div>
  <div>
    <img src="https://www.dealii.org/images/steps/developer/step_6_grid_3.svg"
         alt="Third grid: the five-cell circle grid with two adaptive
         refinements, showing clustering around the inner circle."
         width="300" height="300">
  </div>
  <div>
    <img src="https://www.dealii.org/images/steps/developer/step_6_grid_4.svg"
         alt="Fourth grid: the five-cell circle grid with three adaptive
         refinements, showing clustering around the inner circle."
         width="300" height="300">
  </div>
  <div>
    <img src="https://www.dealii.org/images/steps/developer/step_6_grid_5.svg"
         alt="Fifth grid: the five-cell circle grid with four adaptive
         refinements, showing clustering around the inner circle."
         width="300" height="300">
  </div>
  <div>
    <img src="https://www.dealii.org/images/steps/developer/step_6_grid_6.svg"
         alt="Sixth grid: the five-cell circle grid with five adaptive
         refinements, showing clustering around the inner circle."
         width="300" height="300">
  </div>
  <div>
    <img src="https://www.dealii.org/images/steps/developer/step_6_grid_7.svg"
         alt="Last grid: the five-cell circle grid with six adaptive
         refinements, showing that most cells are clustered around the inner circle."
         width="300" height="300">
  </div>
</div>


It is clearly visible that the region where the solution has a kink,
i.e. the circle at radial distance 0.5 from the center, is
refined most. Furthermore, the central region where the solution is
very smooth and almost flat, is almost not refined at all, but this
results from the fact that we did not take into account that the
coefficient is large there. The region outside is refined rather
arbitrarily, since the second derivative is constant there and refinement
is therefore mostly based on the size of the cells and their deviation
from the optimal square.



<a name="step-6-extensions"></a>
<h3>Possibilities for extensions</h3>

<h4>Solvers and preconditioners</h4>

One thing that is always worth playing around with if one solves
problems of appreciable size (much bigger than the one we have here)
is to try different solvers or preconditioners. In the current case,
the linear system is symmetric and positive definite, which makes the
Conjugate Gradient (CG) algorithm pretty much the canonical choice for solving. However,
the SSOR preconditioner we use in the <code>solve()</code> function is
up for grabs.

In deal.II, it is relatively simple to change the preconditioner.
Several simple preconditioner choices are accessible
by changing the following two lines
@code
  PreconditionSSOR<SparseMatrix<double>> preconditioner;
  preconditioner.initialize(system_matrix, 1.2);
@endcode
of code in the program. For example, switching this into
@code
  PreconditionSSOR<SparseMatrix<double>> preconditioner;
  preconditioner.initialize(system_matrix, 1.0);
@endcode
allows us to try out a different relaxation parameter for SSOR (1.0 instead
of the 1.2 in the original program). Similarly, by using
@code
  PreconditionJacobi<SparseMatrix<double>> preconditioner;
  preconditioner.initialize(system_matrix);
@endcode
we can use Jacobi as a preconditioner. And by using
@code
  SparseILU<double> preconditioner;
  preconditioner.initialize(system_matrix);
@endcode
we can use a simple incomplete LU decomposition without any thresholding or
strengthening of the diagonal (to use this preconditioner, you have to also
add the header file <code>deal.II/lac/sparse_ilu.h</code> to the include list
at the top of the file).

Using these various different preconditioners, we can compare the
number of CG iterations needed (available through the
<code>solver_control.last_step()</code> call, see
step-4) as well as CPU time needed (using the Timer class,
discussed, for example, in step-28) and get the
following results (left: iterations; right: CPU time):

<table width="60%" align="center">
  <tr>
    <td align="center">
      <img src="https://www.dealii.org/images/steps/developer/step-6.q2.dofs_vs_iterations.png" alt="">
    </td>
    <td align="center">
      <img src="https://www.dealii.org/images/steps/developer/step-6.q2.dofs_vs_time.png" alt="">
    </td>
  </tr>
</table>

As we can see, all preconditioners behave pretty much the same on this
simple problem, with the number of iterations growing like ${\cal
O}(N^{1/2})$ and because each iteration requires around ${\cal
O}(N)$ operations the total CPU time grows like ${\cal
O}(N^{3/2})$ (for the few smallest meshes, the CPU time is so small
that it doesn't record). Note that even though it is the simplest
method, Jacobi is the fastest for this problem.

The situation changes slightly when the finite element is not a
bi-quadratic one (i.e., polynomial degree two) as selected in the
constructor of this program, but a bi-linear one (polynomial degree one).
If one makes this change, the results are as follows:

<table width="60%" align="center">
  <tr>
    <td align="center">
      <img src="https://www.dealii.org/images/steps/developer/step-6.q1.dofs_vs_iterations.png" alt="">
    </td>
    <td align="center">
      <img src="https://www.dealii.org/images/steps/developer/step-6.q1.dofs_vs_time.png" alt="">
    </td>
  </tr>
</table>

In other words, while the increase in iterations and CPU time is as
before, Jacobi is now the method that requires the most iterations; it
is still the fastest one, however, owing to the simplicity of the
operations it has to perform. This is not to say that Jacobi
is actually a good preconditioner -- for problems of appreciable size, it is
definitely not, and other methods will be substantially better -- but really
only that it is fast because its implementation is so simple that it can
compensate for a larger number of iterations.

The message to take away from this is not that simplicity in
preconditioners is always best. While this may be true for the current
problem, it definitely is not once we move to more complicated
problems (elasticity or Stokes, for examples step-8 or
step-22). Secondly, all of these preconditioners still
lead to an increase in the number of iterations as the number $N$ of
degrees of freedom grows, for example ${\cal O}(N^\alpha)$; this, in
turn, leads to a total growth in effort as ${\cal O}(N^{1+\alpha})$
since each iteration takes ${\cal O}(N)$ work. This behavior is
undesirable: we would really like to solve linear systems with $N$
unknowns in a total of ${\cal O}(N)$ work; there is a class
of preconditioners that can achieve this, namely geometric (step-16,
step-37, step-39)
or algebraic multigrid (step-31, step-40, and several others)
preconditioners. They are, however, significantly more complex than
the preconditioners outlined above, and so we will leave their use
to these later tutorial programs. The point to make, however, is
that "real" finite element programs do not use the preconditioners
we mention above: These are simply shown for expository purposes.

Finally, the last message to take
home is that when the data shown above was generated (in 2018), linear
systems with 100,000 unknowns are
easily solved on a desktop or laptop machine in about a second, making
the solution of relatively simple 2d problems even to very high
accuracy not that big a task as it used to be in the
past. At the same time, the situation for 3d problems continues to be
quite different: A uniform 2d mesh with 100,000 unknowns corresponds to
a grid with about $300 \times 300$ nodes; the corresponding 3d mesh has
$300 \times 300 \times 300$ nodes and 30 million unknowns. Because
finite element matrices in 3d have many more nonzero entries than in 2d,
solving these linear systems will not only take 300 times as much CPU
time, but substantially longer. In other words, achieving the
same resolution in 3d *is* quite a large problem, and solving it
within a reasonable amount of time *will* require much more work to
implement better linear solvers. As mentioned above, multigrid methods
and matrix-free methods (see, for example, step-37), along with
parallelization (step-40) will be necessary, but are then also able
to comfortably solve such linear systems.



<h4>A better mesh</h4>

If you look at the meshes above, you will see even though the domain is the
unit disk, and the jump in the coefficient lies along a circle, the cells
that make up the mesh do not track this geometry well. The reason, already hinted
at in step-1, is that in the absence of other information,
the Triangulation class only sees a bunch of
coarse grid cells but has, of course, no real idea what kind of geometry they
might represent when looked at together. For this reason, we need to tell
the Triangulation what to do when a cell is refined: where should the new
vertices at the edge midpoints and the cell midpoint be located so that the
child cells better represent the desired geometry than the parent cell.

To visualize what the triangulation actually knows about the geometry,
it is not enough to just output the location of vertices and draw a
straight line for each edge; instead, we have to output both interior
and boundary lines as multiple segments so that they look
curved. We can do this by making one change to the gnuplot part of
<code>output_results</code>:
@code
{
  GridOut       grid_out;
  std::ofstream output("grid-" + std::to_string(cycle) + ".gnuplot");
  GridOutFlags::Gnuplot gnuplot_flags(false, 5, /*curved_interior_cells*/true);
  grid_out.set_flags(gnuplot_flags);
  MappingQ<dim> mapping(3);
  grid_out.write_gnuplot(triangulation, output, &mapping);
}
@endcode

In the code above, we already do this for faces that sit at the boundary: this
happens automatically since we use GridGenerator::hyper_ball, which attaches a
SphericalManifold to the boundary of the domain. To make the mesh
<i>interior</i> also track a circular domain, we need to work a bit harder,
though. First, recall that our coarse mesh consists of a central square
cell and four cells around it. Now first consider what would happen if we
also attached the SphericalManifold object not only to the four exterior faces
but also the four cells at the perimeter as well as all of their faces. We can
do this by adding the following snippet (testing that the center of a cell is
larger than a small multiple, say one tenth, of the cell diameter away from
center of the mesh only fails for the central square of the mesh):
@code
GridGenerator::hyper_ball(triangulation);
// after GridGenerator::hyper_ball is called the Triangulation has
// a SphericalManifold with id 0. We can use it again on the interior.
const Point<dim> mesh_center;
for (const auto &cell : triangulation.active_cell_iterators())
  if (mesh_center.distance (cell->center()) > cell->diameter()/10)
    cell->set_all_manifold_ids(0);

triangulation.refine_global(1);
@endcode

After a few global refinement steps, this would lead to a mesh of the following
kind:


  <div class="onecolumn" style="width: 80%">
    <div>
      <img src="https://www.dealii.org/images/steps/developer/step_6_bad_grid_4.svg"
           alt="Grid where some central cells are nearly triangular."
           width="300" height="300">
    </div>
  </div>

This is not a good mesh: the central cell has been refined in such a way that
the children located in the four corners of the original central cell
<i>degenerate</i>: they all tend towards triangles as mesh refinement
continues. This means that the Jacobian matrix of the transformation from
reference cell to actual cell degenerates for these cells, and because
all error estimates for finite element solutions contain the norm of the
inverse of the Jacobian matrix, you will get very large errors on these
cells and, in the limit as mesh refinement, a loss of convergence order because
the cells in these corners become worse and worse under mesh refinement.

So we need something smarter. To this end, consider the following solution
originally developed by Konstantin Ladutenko. We will use the following code:
@code
GridGenerator::hyper_ball(triangulation);

const Point<dim> mesh_center;
const double core_radius  = 1.0/5.0,
             inner_radius = 1.0/3.0;

// Step 1: Shrink the inner cell
//
// We cannot get a circle out of the inner cell because of
// the degeneration problem mentioned above. Rather, shrink
// the inner cell to a core radius of 1/5 that stays
// sufficiently far away from the place where the
// coefficient will have a discontinuity and where we want
// to have cell interfaces that actually lie on a circle.
// We do this shrinking by just scaling the location of each
// of the vertices, given that the center of the circle is
// simply the origin of the coordinate system.
for (const auto &cell : triangulation.active_cell_iterators())
  if (mesh_center.distance(cell->center()) < 1e-5)
    {
      for (const auto v : cell->vertex_indices())
        cell->vertex(v) *= core_radius/mesh_center.distance(cell->vertex(v));
    }

// Step 2: Refine all cells except the central one
for (const auto &cell : triangulation.active_cell_iterators())
  if (mesh_center.distance(cell->center()) >= 1e-5)
    cell->set_refine_flag();
triangulation.execute_coarsening_and_refinement();

// Step 3: Resize the inner children of the outer cells
//
// The previous step replaced each of the four outer cells
// by its four children, but the radial distance at which we
// have intersected is not what we want to later refinement
// steps. Consequently, move the vertices that were just
// created in radial direction to a place where we need
// them.
for (const auto &cell : triangulation.active_cell_iterators())
  for (const auto v : cell->vertex_indices())
    {
      const double dist = mesh_center.distance(cell->vertex(v));
      if (dist > core_radius*1.0001 && dist < 0.9999)
        cell->vertex(v) *= inner_radius/dist;
    }

// Step 4: Apply curved manifold description
//
// As discussed above, we can not expect to subdivide the
// inner four cells (or their faces) onto concentric rings,
// but we can do so for all other cells that are located
// outside the inner radius. To this end, we loop over all
// cells and determine whether it is in this zone. If it
// isn't, then we set the manifold description of the cell
// and all of its bounding faces to the one that describes
// the spherical manifold already introduced above and that
// will be used for all further mesh refinement.
for (const auto &cell : triangulation.active_cell_iterators())
  {
    bool is_in_inner_circle = false;
    for (const auto v : cell->vertex_indices())
      if (mesh_center.distance(cell->vertex(v)) < inner_radius)
        {
          is_in_inner_circle = true;
          break;
        }

    if (is_in_inner_circle == false)
    // The Triangulation already has a SphericalManifold with
    // manifold id 0 (see the documentation of
    // GridGenerator::hyper_ball) so we just attach it to the outer
    // ring here:
      cell->set_all_manifold_ids(0);
  }
@endcode

This code then generates the following, much better sequence of meshes:

<div class="twocolumn" style="width: 80%">
  <div>
    <img src="https://www.dealii.org/images/steps/developer/step_6_grid_0_ladutenko.svg"
         alt="Initial grid: the Ladutenko grid with one global refinement."
         width="300" height="300">
  </div>
  <div>
    <img src="https://www.dealii.org/images/steps/developer/step_6_grid_1_ladutenko.svg"
         alt="First adaptively refined Ladutenko grid."
         width="300" height="300">
  </div>
  <div>
    <img src="https://www.dealii.org/images/steps/developer/step_6_grid_2_ladutenko.svg"
         alt="Second adaptively refined Ladutenko grid."
         width="300" height="300">
  </div>
  <div>
    <img src="https://www.dealii.org/images/steps/developer/step_6_grid_3_ladutenko.svg"
         alt="Third adaptively refined Ladutenko grid."
         width="300" height="300">
  </div>
  <div>
    <img src="https://www.dealii.org/images/steps/developer/step_6_grid_4_ladutenko.svg"
         alt="Fourth adaptively refined Ladutenko grid. The cells are clustered
         along the inner circle."
         width="300" height="300">
  </div>
  <div>
    <img src="https://www.dealii.org/images/steps/developer/step_6_grid_5_ladutenko.svg"
         alt="Fifth adaptively refined Ladutenko grid: the cells are clustered
         along the inner circle."
         width="300" height="300">
  </div>
</div>

Creating good meshes, and in particular making them fit the geometry you
want, is a complex topic in itself. You can find much more on this in
step-49, step-53, and step-54, among other tutorial programs that cover
the issue. step-65 shows another, less manual way to achieve a mesh
well fit to the problem here.
Information on curved domains can also be found in the
documentation topic on @ref manifold "Manifold descriptions".

Why does it make sense to choose a mesh that tracks the internal
interface? There are a number of reasons, but the most essential one
comes down to what we actually integrate in our bilinear
form. Conceptually, we want to integrate the term $A_{ij}^K=\int_K
a(\mathbf x) \nabla \varphi_i(\mathbf x) \nabla \varphi_j(\mathbf x) ; dx$ as the
contribution of cell $K$ to the matrix entry $A_{ij}$. We can not
compute it exactly and have to resort to quadrature. We know that
quadrature is accurate if the integrand is smooth. That is because
quadrature in essence computes a polynomial approximation to the
integrand that coincides with the integrand in the quadrature points,
and then computes the volume under this polynomial as an approximation
to the volume under the original integrand. This polynomial
interpolant is accurate if the integrand is smooth on a cell, but it
is usually rather inaccurate if the integrand is discontinuous on a
cell.

Consequently, it is worthwhile to align cells in such a way that the
interfaces across which the coefficient is discontinuous are aligned
with cell interfaces. This way, the coefficient is constant on each
cell, following which the integrand will be smooth, and its polynomial
approximation and the quadrature approximation of the integral will
both be accurate. Note that such an alignment is common in many
practical cases, so deal.II provides a number of functions (such as
@ref GlossMaterialId "material_id") to help manage such a scenario.
Refer to step-28 and step-46 for examples of how material ids can be
applied.

Finally, let us consider the case of a coefficient that has a smooth
and non-uniform distribution in space. We can repeat once again all of
the above discussion on the representation of such a function with the
quadrature. So, to simulate it accurately there are a few readily
available options: you could reduce the cell size, increase the order
of the polynomial used in the quadrature formula, select a more
appropriate quadrature formula, or perform a combination of these
steps. The key is that providing the best fit of the coefficient's
spatial dependence with the quadrature polynomial will lead to a more
accurate finite element solution of the PDE.

As a final note: The discussion in the previous paragraphs shows, we here
have a very concrete way of stating what we think of a good mesh -- it should
be aligned with the jump in the coefficient. But one could also have asked
this kind of question in a more general setting: Given some equation with
a smooth solution and smooth coefficients, can we say what a good mesh
would look like? This is a question for which the answer is easier to state
in intuitive terms than mathematically: A good mesh has cells that all,
by and large, look like squares (or cubes, in 3d). A bad mesh would contain
cells that are very elongated in some directions or, more generally, for which
there are cells that have both short and long edges. There are many ways
in which one could assign a numerical quality index to each cell that measures
whether the cell is "good" or "bad"; some of these are often chosen because
they are cheap and easy to compute, whereas others are based on what enters
into proofs of convergence. An example of the former would be the ratio of
the longest to the shortest edge of a cell: In the ideal case, that ratio
would be one; bad cells have values much larger than one. An example of the
latter kind would consider the gradient (the "Jacobian") of the mapping
from the reference cell $\hat K=[0,1]^d$ to the real cell $K$; this
gradient is a matrix, and a quantity that enters into error estimates
is the maximum over all points on the reference cell of the ratio of the
largest to the smallest eigenvalue of this matrix. It is again not difficult
to see that this ratio is constant if the cell $K$ is an affine image of
$\hat K$, and that it is one for squares and cubes.

In practice, it might be interesting to visualize such quality measures.
The function GridTools::compute_aspect_ratio_of_cells() provides one
way to get this kind of information. Even better, visualization tools
such as VisIt often allow you to visualize this sort of information
for a variety of measures from within the visualization software
itself; in the case of VisIt, just add a "pseudo-color" plot and select
one of the mesh quality measures instead of the solution field.


<h4>Playing with the regularity of the solution</h4>

From a mathematical perspective, solutions of the Laplace equation
@f[
  -\Delta u = f
@f]
on smoothly bounded, convex domains are known to be smooth themselves. The exact degree
of smoothness, i.e., the function space in which the solution lives, depends
on how smooth exactly the boundary of the domain is, and how smooth the right
hand side is. Some regularity of the solution may be lost at the boundary, but
one generally has that the solution is twice more differentiable in
compact subsets of the domain than the right hand side.
If, in particular, the right hand side satisfies $f\in C^\infty(\Omega)$, then
$u \in C^\infty(\Omega_i)$ where $\Omega_i$ is any compact subset of $\Omega$
($\Omega$ is an open domain, so a compact subset needs to keep a positive distance
from $\partial\Omega$).

The situation we chose for the current example is different, however: we look
at an equation with a non-constant coefficient $a(\mathbf x)$:
@f[
  -\nabla \cdot (a \nabla u) = f.
@f]
Here, if $a$ is not smooth, then the solution will not be smooth either,
regardless of $f$. In particular, we expect that wherever $a$ is discontinuous
along a line (or along a plane in 3d),
the solution will have a kink. This is easy to see: if for example $f$
is continuous, then $f=-\nabla \cdot (a \nabla u)$ needs to be
continuous. This means that $a \nabla u$ must be continuously differentiable
(not have a kink). Consequently, if $a$ has a discontinuity, then $\nabla u$
must have an opposite discontinuity so that the two exactly cancel and their
product yields a function without a discontinuity. But for $\nabla u$ to have
a discontinuity, $u$ must have a kink. This is of course exactly what is
happening in the current example, and easy to observe in the pictures of the
solution.

In general, if the coefficient $a(\mathbf x)$ is discontinuous along a line in 2d,
or a plane in 3d, then the solution may have a kink, but the gradient of the
solution will not go to infinity. That means, that the solution is at least
still in the <a href="https://en.wikipedia.org/wiki/Sobolev_space">Sobolev space</a>
$W^{1,\infty}$ (i.e., roughly speaking, in the
space of functions whose derivatives are bounded). On the other hand,
we know that in the most
extreme cases -- i.e., where the domain has reentrant corners, the
right hand side only satisfies $f\in H^{-1}$, or the coefficient $a$ is only in
$L^\infty$ -- all we can expect is that $u\in H^1$ (i.e., the
<a
href="https://en.wikipedia.org/wiki/Sobolev_space#Sobolev_spaces_with_integer_k">Sobolev
space</a> of functions whose derivative is square integrable), a much larger space than
$W^{1,\infty}$. It is not very difficult to create cases where
the solution is in a space $H^{1+s}$ where we can get $s$ to become as small
as we want. Such cases are often used to test adaptive finite element
methods because the mesh will have to resolve the singularity that causes
the solution to not be in $W^{1,\infty}$ any more.

The typical example one uses for this is called the <i>Kellogg problem</i>
(referring to @cite Kel74), which in the commonly used form has a coefficient
$a(\mathbf x)$ that has different values in the four quadrants of the plane
(or eight different values in the octants of ${\mathbb R}^3$). The exact degree
of regularity (the $s$ in the index of the Sobolev space above) depends on the
values of $a(\mathbf x)$ coming together at the origin, and by choosing the
jumps large enough, the regularity of the solution can be made as close as
desired to $H^1$.

To implement something like this, one could replace the coefficient
function by the following (shown here only for the 2d case):
@code
template <int dim>
double coefficient (const Point<dim> &p)
{
  if ((p[0] < 0) && (p[1] < 0))           // lower left quadrant
    return 1;
  else if ((p[0] >= 0) && (p[1] < 0))     // lower right quadrant
    return 10;
  else if ((p[0] < 0) && (p[1] >= 0))     // upper left quadrant
    return 100;
  else if ((p[0] >= 0) && (p[1] >= 0))    // upper right quadrant
    return 1000;
  else
    DEAL_II_ASSERT_UNREACHABLE();
}
@endcode
(Adding the `DEAL_II_ASSERT_UNREACHABLE();` call at the end ensures that either an exception
is thrown or that the program aborts if we ever get to that point
-- which of course we shouldn't,
but this is a good way to insure yourself: We all make mistakes by
sometimes not thinking of all cases, for example by checking
for <code>p[0]</code> to be less than and greater than zero,
rather than greater-or-equal to zero, and thereby forgetting
some cases that would otherwise lead to bugs that are awkward
to find.

By playing with such cases where four or more sectors come
together and on which the coefficient has different values, one can
construct cases where the solution has singularities at the
origin. One can also see how the meshes are refined in such cases.
