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


This tutorial program is another one in the series on the elasticity problem
that we have already started with step-8 and step-17. It extends it into two
different directions: first, it solves the quasistatic but time dependent
elasticity problem for large deformations with a Lagrangian mesh movement
approach. Secondly, it shows some more techniques for solving such problems
using %parallel processing with PETSc's linear algebra. In addition to this,
we show how to work around one of the two major bottlenecks of step-17, namely
that we generated graphical output from only one process, and that this scaled
very badly with larger numbers of processes and on large problems. (The other
bottleneck, namely that every processor has to hold the entire mesh and
DoFHandler, is addressed in step-40.) Finally, a
good number of assorted improvements and techniques are demonstrated that have
not been shown yet in previous programs.

As before in step-17, the program runs just as fine on a single sequential
machine as long as you have PETSc installed. Information on how to tell
deal.II about a PETSc installation on your system can be found in the deal.II
README file, which is linked to from the <a href="../../index.html">main
documentation page</a>
in your installation of deal.II, or on <a href="http://www.dealii.org/">the
deal.II webpage</a>.


<h3>Quasistatic elastic deformation</h3>

<h4>Motivation of the model</h4>

In general, time-dependent small elastic deformations are described by the
elastic wave equation
@f[
  \rho \frac{\partial^2 \mathbf{u}}{\partial t^2}
  + c \frac{\partial \mathbf{u}}{\partial t}
  - \textrm{div}\  ( C \varepsilon(\mathbf{u})) = \mathbf{f}
  \qquad
  \textrm{in}\ \Omega,
@f]
where $\mathbf{u}=\mathbf{u} (\mathbf{x},t)$ is the deformation of the body, $\rho$
and $c$ the density and attenuation coefficient, and $\mathbf{f}$ external forces.
In addition, initial conditions
@f[
  \mathbf{u}(\cdot, 0) = \mathbf{u}_0(\cdot)
  \qquad
  \textrm{on}\ \Omega,
@f]
and Dirichlet (displacement) or Neumann (traction) boundary conditions need
to be specified for a unique solution:
@f{eqnarray*}
  \mathbf{u}(\mathbf{x},t) &=& \mathbf{d}(\mathbf{x},t)
  \qquad
  \textrm{on}\ \Gamma_D\subset\partial\Omega,
  \\
  \mathbf{n} \ C \varepsilon(\mathbf{u}(\mathbf{x},t)) &=& \mathbf{b}(\mathbf{x},t)
  \qquad
  \textrm{on}\ \Gamma_N=\partial\Omega\backslash\Gamma_D.
@f}
In above formulation, $\varepsilon(\mathbf{u})= \frac 12 (\nabla \mathbf{u} + \nabla
\mathbf{u}^T)$ is the symmetric gradient of the displacement, also called the
<em>strain</em>. $C$ is a tensor of rank 4, called the <em>stress-strain
  tensor</em> (the inverse of the <a
  href="https://en.wikipedia.org/wiki/Hooke%27s_law#Hooke's_law_for_continuous_media"><em>compliance
  tensor</em></a>)
that contains knowledge of the elastic strength of the material; its
symmetry properties make sure that it maps symmetric tensors of rank 2
(&ldquo;matrices&rdquo; of dimension $d$, where $d$ is the spatial dimensionality) onto
symmetric tensors of the same rank. We will comment on the roles of the strain
and stress tensors more below. For the moment it suffices to say that we
interpret the term $\textrm{div}\  ( C \varepsilon(\mathbf{u}))$ as the vector with
components $\frac \partial{\partial x_j} C_{ijkl} \varepsilon(\mathbf{u})_{kl}$,
where summation over indices $j,k,l$ is implied.

The quasistatic limit of this equation is motivated as follows: each small
perturbation of the body, for example by changes in boundary condition or the
forcing function, will result in a corresponding change in the configuration
of the body. In general, this will be in the form of waves radiating away from
the location of the disturbance. Due to the presence of the damping term,
these waves will be attenuated on a time scale of, say, $\tau$. Now, assume
that all changes in external forcing happen on times scales that are
much larger than $\tau$. In that case, the dynamic nature of the change is
unimportant: we can consider the body to always be in static equilibrium,
i.e. we can assume that at all times the body satisfies
@f{eqnarray*}
  - \textrm{div}\  ( C \varepsilon(\mathbf{u})) &=& \mathbf{f}(\mathbf{x},t)
  \qquad
  \textrm{in}\ \Omega,
  \\
  \mathbf{u}(\mathbf{x},t) &=& \mathbf{d}(\mathbf{x},t)
  \qquad
  \textrm{on}\ \Gamma_D,
  \\
  \mathbf{n} \ C \varepsilon(\mathbf{u}(\mathbf{x},t)) &=& \mathbf{b}(\mathbf{x},t)
  \qquad
  \textrm{on}\ \Gamma_N.
@f}
Note that the differential equation does not contain any time derivatives any
more -- all time dependence is introduced through boundary conditions and a
possibly time-varying force function $\mathbf{f}(\mathbf{x},t)$. The changes in
configuration can therefore be considered as being stationary
instantaneously. An alternative view of this is that $t$ is not really a time
variable, but only a time-like parameter that governs the evolution of the
problem.

While these equations are sufficient to describe small deformations, computing
large deformations is a little more complicated and, in general, leads
to nonlinear equations such as those treated in step-44. In the
following, let us consider some of the tools one would employ when
simulating problems in which the deformation becomes <i>large</i>.

@note The model we will consider below is not founded on anything that
would be mathematically sound: we will consider a model in which we
produce a small deformation, deform the physical coordinates of the
body by this deformation, and then consider the next loading step
again as a linear problem. This isn't consistent, since the assumption
of linearity implies that deformations are infinitesimal and so moving
around the vertices of our mesh by a finite amount before solving the
next linear problem is an inconsistent approach. We should therefore
note that it is not surprising that the equations discussed below
can't be found in the literature: <b>The model considered here has
little to do with reality!</b> On the other hand, the implementation
techniques we consider are very much what one would need to use when
implementing a <i>real</i> model, as we will see in step-44.


To come back to defining our "artificial" model, let us first
introduce a tensorial stress variable $\sigma$, and write the differential
equations in terms of the stress:
@f{eqnarray*}
  - \textrm{div}\  \sigma &=& \mathbf{f}(\mathbf{x},t)
  \qquad
  \textrm{in}\ \Omega(t),
  \\
  \mathbf{u}(\mathbf{x},t) &=& \mathbf{d}(\mathbf{x},t)
  \qquad
  \textrm{on}\ \Gamma_D\subset\partial\Omega(t),
  \\
  \mathbf{n} \ C \varepsilon(\mathbf{u}(\mathbf{x},t)) &=& \mathbf{b}(\mathbf{x},t)
  \qquad
  \textrm{on}\ \Gamma_N=\partial\Omega(t)\backslash\Gamma_D.
@f}
Note that these equations are posed on a domain $\Omega(t)$ that
changes with time, with the boundary moving according to the
displacements $\mathbf{u}(\mathbf{x},t)$ of the points on the boundary. To
complete this system, we have to specify the incremental relationship between
the stress and the strain, as follows:
<a name="step_18.stress-strain"></a>
@f[
  \dot\sigma = C \varepsilon (\dot{\mathbf{u}}),
  \qquad
  \qquad
  \textrm{[stress-strain]}
@f]
where a dot indicates a time derivative. Both the stress $\sigma$ and the
strain $\varepsilon(\mathbf{u})$ are symmetric tensors of rank 2.


<h4>Time discretization</h4>

Numerically, this system is solved as follows: first, we discretize
the time component using a backward Euler scheme. This leads to a
discrete equilibrium of force at time step $n$:
@f[
  -\textrm{div}\  \sigma^n = f^n,
@f]
where
@f[
  \sigma^n = \sigma^{n-1} + C \varepsilon (\Delta \mathbf{u}^n),
@f]
and $\Delta \mathbf{u}^n$ the incremental displacement for time step
$n$. In addition, we have to specify initial data $\mathbf{u}(\cdot,0)=\mathbf{u}_0$.
This way, if we want to solve for the displacement increment, we
have to solve the following system:
@f{align*}
  - \textrm{div}\   C \varepsilon(\Delta\mathbf{u}^n) &= \mathbf{f} + \textrm{div}\  \sigma^{n-1}
  \qquad
  &&\textrm{in}\ \Omega(t_{n-1}),
  \\
  \Delta \mathbf{u}^n(\mathbf{x},t) &= \mathbf{d}(\mathbf{x},t_n) - \mathbf{d}(\mathbf{x},t_{n-1})
  \qquad
  &&\textrm{on}\ \Gamma_D\subset\partial\Omega(t_{n-1}),
  \\
  \mathbf{n} \ C \varepsilon(\Delta \mathbf{u}^n(\mathbf{x},t)) &= \mathbf{b}(\mathbf{x},t_n)-\mathbf{b}(\mathbf{x},t_{n-1})
  \qquad
  &&\textrm{on}\ \Gamma_N=\partial\Omega(t_{n-1})\backslash\Gamma_D.
@f}
The weak form of this set of equations, which as usual is the basis for the
finite element formulation, reads as follows: find $\Delta \mathbf{u}^n \in
\{v\in H^1(\Omega(t_{n-1}))^d: v|_{\Gamma_D}=\mathbf{d}(\cdot,t_n) - \mathbf{d}(\cdot,t_{n-1})\}$
such that
<a name="step_18.linear-system"></a>
@f{align*}
  (C \varepsilon(\Delta\mathbf{u}^n), \varepsilon(\varphi) )_{\Omega(t_{n-1})}
  &=
  (\mathbf{f}, \varphi)_{\Omega(t_{n-1})}
  -(\sigma^{n-1},\varepsilon(\varphi))_{\Omega(t_{n-1})}
  \\
  &\qquad
  +(\mathbf{b}(\mathbf{x},t_n)-\mathbf{b}(\mathbf{x},t_{n-1}), \varphi)_{\Gamma_N}
  +(\sigma^{n-1} \mathbf{n}, \varphi)_{\Gamma_N}
  \\
  &\qquad\qquad
  \forall \varphi \in \{\mathbf{v}\in H^1(\Omega(t_{n-1}))^d: \mathbf{v}|_{\Gamma_D}=0\}.
@f}
Using that $\sigma^{n-1} \mathbf{n}
            = [C \varepsilon(\mathbf{u}^{n-1})] \mathbf{n}
            = \mathbf{b}(\mathbf x, t_{n-1})$,
these equations can be simplified to
@f{align*}
  (C \varepsilon(\Delta\mathbf{u}^n), \varepsilon(\varphi) )_{\Omega(t_{n-1})}
  &=
  (\mathbf{f}, \varphi)_{\Omega(t_{n-1})}
  -(\sigma^{n-1},\varepsilon(\varphi))_{\Omega(t_{n-1})}
  +(\mathbf{b}(\mathbf{x},t_n),t_{n-1}), \varphi)_{\Gamma_N}
  \\
  &\qquad\qquad
  \forall \varphi \in \{\mathbf{v}\in H^1(\Omega(t_{n-1}))^d: \mathbf{v}|_{\Gamma_D}=0\}.
  \qquad
  \qquad
  \textrm{[linear-system]}
@f}

We note that, for simplicity, in the program we will always assume that there
are no boundary forces, i.e. $\mathbf{b} = 0$, and that the deformation of the
body is driven by body forces $\mathbf{f}$ and prescribed boundary displacements
$\mathbf{d}$ alone. It is also worth noting that when integrating by parts, we
would get terms of the form $(C \varepsilon(\Delta\mathbf{u}^n), \nabla \varphi
)_{\Omega(t_{n-1})}$, but that we replace them with the term involving the
symmetric gradient $\varepsilon(\varphi)$ instead of $\nabla\varphi$. Due to
the symmetry of $C$, the two terms are mathematically equivalent, but
the symmetric version avoids the potential for round-off errors making
the resulting matrix slightly non-symmetric.

The system at time step $n$, to be solved on the old domain
$\Omega(t_{n-1})$, has exactly the form of a stationary elastic
problem, and is therefore similar to what we have already implemented
in previous example programs. We will therefore not comment on the
space discretization beyond saying that we again use lowest order
continuous finite elements.

There are differences, however:
<ol>
  <li> We have to move (update) the mesh after each time step, in order to be
  able to solve the next time step on a new domain;

  <li> We need to know $\sigma^{n-1}$ to compute the next incremental
  displacement, i.e. we need to compute it at the end of the time step
  to make sure it is available for the next time step. Essentially,
  the stress variable is our window to the history of deformation of
  the body.
</ol>
These two operations are done in the functions <code>move_mesh</code> and
<code>update_quadrature_point_history</code> in the program. While moving
the mesh is only a technicality, updating the stress is a little more
complicated and will be discussed in the next section.


<h4>Updating the stress variable</h4>

As indicated above, we need to have the stress variable $\sigma^n$ available
when computing time step $n+1$, and we can compute it using
<a name="step_18.stress-update"></a>
@f[
  \sigma^n = \sigma^{n-1} + C \varepsilon (\Delta \mathbf{u}^n).
  \qquad
  \qquad
  \textrm{[stress-update]}
@f]
There are, despite the apparent simplicity of this equation, two questions
that we need to discuss. The first concerns the way we store $\sigma^n$: even
if we compute the incremental updates $\Delta\mathbf{u}^n$ using lowest-order
finite elements, then its symmetric gradient $\varepsilon(\Delta\mathbf{u}^n)$ is
in general still a function that is not easy to describe. In particular, it is
not a piecewise constant function, and on general meshes (with cells that are
not rectangles %parallel to the coordinate axes) or with non-constant
stress-strain tensors $C$ it is not even a bi- or trilinear function. Thus, it
is a priori not clear how to store $\sigma^n$ in a computer program.

To decide this, we have to see where it is used. The only place where we
require the stress is in the term
$(\sigma^{n-1},\varepsilon(\varphi))_{\Omega(t_{n-1})}$. In practice, we of
course replace this term by numerical quadrature:
@f[
  (\sigma^{n-1},\varepsilon(\varphi))_{\Omega(t_{n-1})}
  =
  \sum_{K\subset {T}}
  (\sigma^{n-1},\varepsilon(\varphi))_K
  \approx
  \sum_{K\subset {T}}
  \sum_q
  w_q \ \sigma^{n-1}(\mathbf{x}_q) : \varepsilon(\varphi(\mathbf{x}_q),
@f]
where $w_q$ are the quadrature weights and $\mathbf{x}_q$ the quadrature points on
cell $K$. This should make clear that what we really need is not the stress
$\sigma^{n-1}$ in itself, but only the values of the stress in the quadrature
points on all cells. This, however, is a simpler task: we only have to provide
a data structure that is able to hold one symmetric tensor of rank 2 for each
quadrature point on all cells (or, since we compute in parallel, all
quadrature points of all cells that the present MPI process &ldquo;owns&rdquo;). At the
end of each time step we then only have to evaluate $\varepsilon(\Delta \mathbf{u}^n(\mathbf{x}_q))$, multiply it by the stress-strain tensor $C$, and use the
result to update the stress $\sigma^n(\mathbf{x}_q)$ at quadrature point $q$.

The second complication is not visible in our notation as chosen above. It is
due to the fact that we compute $\Delta u^n$ on the domain $\Omega(t_{n-1})$,
and then use this displacement increment to both update the stress as well as
move the mesh nodes around to get to $\Omega(t_n)$ on which the next increment
is computed. What we have to make sure, in this context, is that moving the
mesh does not only involve moving around the nodes, but also making
corresponding changes to the stress variable: the updated stress is a variable
that is defined with respect to the coordinate system of the material in the
old domain, and has to be transferred to the new domain. The reason for this
can be understood as follows: locally, the incremental deformation $\Delta\mathbf{u}$ can be decomposed into three parts, a linear translation (the constant part
of the displacement increment field in the neighborhood of a point), a
dilational
component (that part of the gradient of the displacement field that has a
nonzero divergence), and a rotation. A linear translation of the material does
not affect the stresses that are frozen into it -- the stress values are
simply translated along. The dilational or compressional change produces a
corresponding stress update. However, the rotational component does not
necessarily induce a nonzero stress update (think, in 2d, for example of the
situation where $\Delta\mathbf{u}=(y, -x)^T$, with which $\varepsilon(\Delta
\mathbf{u})=0$). Nevertheless, if the material was prestressed in a certain
direction, then this direction will be rotated along with the material.  To
this end, we have to define a rotation matrix $R(\Delta \mathbf{u}^n)$ that
describes, in each point the rotation due to the displacement increments. It
is not hard to see that the actual dependence of $R$ on $\Delta \mathbf{u}^n$ can
only be through the curl of the displacement, rather than the displacement
itself or its full gradient (as mentioned above, the constant components of
the increment describe translations, its divergence the dilational modes, and
the curl the rotational modes). Since the exact form of $R$ is cumbersome, we
only state it in the program code, and note that the correct updating formula
for the stress variable is then
<a name="step_18.stress-update+rot"></a>
@f[
  \sigma^n
  =
  R(\Delta \mathbf{u}^n)^T
  [\sigma^{n-1} + C \varepsilon (\Delta \mathbf{u}^n)]
  R(\Delta \mathbf{u}^n).
  \qquad
  \qquad
  \textrm{[stress-update+rot]}
@f]

Both stress update and rotation are implemented in the function
<code>update_quadrature_point_history</code> of the example program.


<h3>Parallel graphical output</h3>

In step-17, the main bottleneck for %parallel computations as far as run time
is concerned
was that only the first processor generated output for the entire domain.
Since generating graphical output is expensive, this did not scale well when
larger numbers of processors were involved. We will address this here. (For a
definition of what it means for a program to "scale", see
@ref GlossParallelScaling "this glossary entry".)

Basically, what we need to do is let every process
generate graphical output for that subset of cells that it owns, write them
into separate files and have a way to display all files for a certain timestep
at the same time. This way the code produces one <code>.vtu</code> file per process per
time step. The two common VTK file viewers ParaView and VisIt both support
opening more than one <code>.vtu</code> file at once. To simplify the process of picking
the correct files and allow moving around in time, both support record files
that reference all files for a given timestep. Sadly, the record files have a
different format between VisIt and Paraview, so we write out both formats.

The code will generate the files <code>solution-TTTT.NNN.vtu</code>,
where <code>TTTT</code> is the timestep number (starting from 1) and
<code>NNN</code> is the process rank (starting from
0). These files contain the locally owned cells for the timestep and
processor. The files <code>solution-TTTT.visit</code> is the visit record
for timestep <code>TTTT</code>, while <code>solution-TTTT.pvtu</code> is
the same for ParaView. (More recent versions of VisIt can actually read
<code>.pvtu</code> files as well, but it doesn't hurt to output both
kinds of record files.) Finally, the file
<code>solution.pvd</code> is a special record only supported by ParaView that references
all time steps. So in ParaView, only solution.pvd needs to be opened, while
one needs to select the group of all .visit files in VisIt for the same
effect.


<h3>A triangulation with automatic partitioning</h3>

In step-17, we used a regular triangulation that was simply replicated on
every processor, and a corresponding DoFHandler. Both had no idea that they
were used in a %parallel context -- they just existed in their entirety
on every processor, and we argued that this was eventually going to be a
major memory bottleneck.

We do not address this issue here (we will do so in step-40) but make
the situation slightly more automated. In step-17, we created the triangulation
and then manually "partitioned" it, i.e., we assigned
@ref GlossSubdomainId "subdomain ids" to every cell that indicated which
@ref GlossMPIProcess "MPI process" "owned" the cell. Here, we use a class
parallel::shared::Triangulation that at least does this part automatically:
whenever you create or refine such a triangulation, it automatically
partitions itself among all involved processes (which it knows about because
you have to tell it about the @ref GlossMPICommunicator "MPI communicator"
that connects these processes upon construction of the triangulation).
Otherwise, the parallel::shared::Triangulation looks, for all practical
purposes, like a regular Triangulation object.

The convenience of using this class does not only result from being able
to avoid the manual call to GridTools::partition(). Rather, the DoFHandler
class now also knows that you want to use it in a parallel context, and
by default automatically enumerates degrees of freedom in such a way
that all DoFs owned by process zero come before all DoFs owned by process 1,
etc. In other words, you can also avoid the call to
DoFRenumbering::subdomain_wise().

There are other benefits. For example, because the triangulation knows that
it lives in a %parallel universe, it also knows that it "owns" certain
cells (namely, those whose subdomain id equals its MPI rank; previously,
the triangulation only stored these subdomain ids, but had no way to
make sense of them). Consequently, in the assembly function, you can
test whether a cell is "locally owned" (i.e., owned by the current
process, see @ref GlossLocallyOwnedCell) when you loop over all cells
using the syntax
@code
  if (cell->is_locally_owned())
@endcode
This knowledge extends to the DoFHandler object built on such triangulations,
which can then identify which degrees of freedom are locally owned
(see @ref GlossLocallyOwnedDof) via calls such as
DoFHandler::compute_n_locally_owned_dofs_per_processor() and
DoFTools::extract_locally_relevant_dofs(). Finally, the DataOut class
also knows how to deal with such triangulations and will simply skip
generating graphical output on cells not locally owned.

Of course, as has been noted numerous times in the discussion in step-17,
keeping the entire triangulation on every process will not scale: large
problems may simply not fit into each process's memory any more, even if
we have sufficiently many processes around to solve them in a reasonable
time. In such cases, the parallel::shared::Triangulation is no longer
a reasonable basis for computations and we will show in step-40 how the
parallel::distributed::Triangulation class can be used to work around
this, namely by letting each process store only a <i>part</i> of the
triangulation.


<h3>Overall structure of the program</h3>

The overall structure of the program can be inferred from the <code>run()</code>
function that first calls <code>do_initial_timestep()</code> for the first time
step, and then <code>do_timestep()</code> on all subsequent time steps. The
difference between these functions is only that in the first time step we
start on a coarse mesh, solve on it, refine the mesh adaptively, and then
start again with a clean state on that new mesh. This procedure gives us a
better starting mesh, although we should of course keep adapting the mesh as
iterations proceed -- this isn't done in this program, but commented on below.

The common part of the two functions treating time steps is the following
sequence of operations on the present mesh:
<ul>
<li> <code>assemble_system ()</code> [via <code>solve_timestep ()</code>]:
  This first function is also the most interesting one. It assembles the
  linear system corresponding to the discretized version of equation
  <a href="#step_18.linear-system">[linear-system]</a>. This leads to a system matrix $A_{ij} = \sum_K
  A^K_{ij}$ built up of local contributions on each cell $K$ with entries
  @f[
    A^K_{ij} = (C \varepsilon(\varphi_j), \varepsilon(\varphi_i))_K;
  @f]
  In practice, $A^K$ is computed using numerical quadrature according to the
  formula
  @f[
    A^K_{ij} = \sum_q w_q [\varepsilon(\varphi_i(\mathbf{x}_q)) : C :
                           \varepsilon(\varphi_j(\mathbf{x}_q))],
  @f]
  with quadrature points $\mathbf{x}_q$ and weights $w_q$. We have built these
  contributions before, in step-8 and step-17, but in both of these cases we
  have done so rather clumsily by using knowledge of how the rank-4 tensor $C$
  is composed, and considering individual elements of the strain tensors
  $\varepsilon(\varphi_i),\varepsilon(\varphi_j)$. This is not really
  convenient, in particular if we want to consider more complicated elasticity
  models than the isotropic case for which $C$ had the convenient form
  $C_{ijkl}  = \lambda \delta_{ij} \delta_{kl} + \mu (\delta_{ik} \delta_{jl}
  + \delta_{il} \delta_{jk})$. While we in fact do not use a more complicated
  form than this in the present program, we nevertheless want to write it in a
  way that would easily allow for this. It is then natural to introduce
  classes that represent symmetric tensors of rank 2 (for the strains and
  stresses) and 4 (for the stress-strain tensor $C$). Fortunately, deal.II
  provides these: the <code>SymmetricTensor<rank,dim></code> class template
  provides a full-fledged implementation of such tensors of rank <code>rank</code>
  (which needs to be an even number) and dimension <code>dim</code>.

  What we then need is two things: a way to create the stress-strain rank-4
  tensor $C$ as well as to create a symmetric tensor of rank 2 (the strain
  tensor) from the gradients of a shape function $\varphi_i$ at a quadrature
  point $\mathbf{x}_q$ on a given cell. At the top of the implementation of this
  example program, you will find such functions. The first one,
  <code>get_stress_strain_tensor</code>, takes two arguments corresponding to
  the Lam&eacute; constants $\lambda$ and $\mu$ and returns the stress-strain tensor
  for the isotropic case corresponding to these constants (in the program, we
  will choose constants corresponding to steel); it would be simple to replace
  this function by one that computes this tensor for the anisotropic case, or
  taking into account crystal symmetries, for example. The second one,
  <code>get_strain</code> takes an object of type <code>FEValues</code> and indices
  $i$ and $q$ and returns the symmetric gradient, i.e. the strain,
  corresponding to shape function $\varphi_i(\mathbf{x}_q)$, evaluated on the cell
  on which the <code>FEValues</code> object was last reinitialized.

  Given this, the innermost loop of <code>assemble_system</code> computes the
  local contributions to the matrix in the following elegant way (the variable
  <code>stress_strain_tensor</code>, corresponding to the tensor $C$, has
  previously been initialized with the result of the first function above):
  @code
for (unsigned int i=0; i<dofs_per_cell; ++i)
  for (unsigned int j=0; j<dofs_per_cell; ++j)
    for (unsigned int q_point=0; q_point<n_q_points;
         ++q_point)
      {
        const SymmetricTensor<2,dim>
          eps_phi_i = get_strain (fe_values, i, q_point),
          eps_phi_j = get_strain (fe_values, j, q_point);

        cell_matrix(i,j)
          += (eps_phi_i * stress_strain_tensor * eps_phi_j *
              fe_values.JxW (q_point));
      }
  @endcode
  It is worth noting the expressive power of this piece of code, and to
  compare it with the complications we had to go through in previous examples
  for the elasticity problem. (To be fair, the SymmetricTensor class
  template did not exist when these previous examples were written.) For
  simplicity, <code>operator*</code> provides for the (double summation) product
  between symmetric tensors of even rank here.

  Assembling the local contributions
  @f{eqnarray*}
      f^K_i &=&
      (\mathbf{f}, \varphi_i)_K -(\sigma^{n-1},\varepsilon(\varphi_i))_K
      \\
      &\approx&
      \sum_q
      w_q \left\{
        \mathbf{f}(\mathbf{x}_q) \cdot \varphi_i(\mathbf{x}_q) -
        \sigma^{n-1}_q : \varepsilon(\varphi_i(\mathbf{x}_q))
      \right\}
  @f}
  to the right hand side of <a href="#step_18.linear-system">[linear-system]</a> is equally
  straightforward (note that we do not consider any boundary tractions $\mathbf{b}$ here). Remember that we only had to store the old stress in the
  quadrature points of cells. In the program, we will provide a variable
  <code>local_quadrature_points_data</code> that allows to access the stress
  $\sigma^{n-1}_q$ in each quadrature point. With this the code for the right
  hand side looks as this, again rather elegant:
  @code
for (unsigned int i=0; i<dofs_per_cell; ++i)
  {
    const unsigned int
      component_i = fe.system_to_component_index(i).first;

    for (unsigned int q_point=0; q_point<n_q_points; ++q_point)
      {
        const SymmetricTensor<2,dim> &old_stress
          = local_quadrature_points_data[q_point].old_stress;

        cell_rhs(i) += (body_force_values[q_point](component_i) *
                        fe_values.shape_value (i,q_point)
                        -
                        old_stress *
                        get_strain (fe_values,i,q_point)) *
                       fe_values.JxW (q_point);
      }
  }
  @endcode
  Note that in the multiplication $\mathbf{f}(\mathbf{x}_q) \cdot \varphi_i(\mathbf{x}_q)$, we have made use of the fact that for the chosen finite element, only
  one vector component (namely <code>component_i</code>) of $\varphi_i$ is
  nonzero, and that we therefore also have to consider only one component of
  $\mathbf{f}(\mathbf{x}_q)$.

  This essentially concludes the new material we present in this function. It
  later has to deal with boundary conditions as well as hanging node
  constraints, but this parallels what we had to do previously in other
  programs already.

<li> <code>solve_linear_problem ()</code> [via <code>solve_timestep ()</code>]:
  Unlike the previous one, this function is not really interesting, since it
  does what similar functions have done in all previous tutorial programs --
  solving the linear system using the CG method, using an incomplete LU
  decomposition as a preconditioner (in the %parallel case, it uses an ILU of
  each processor's block separately). It is virtually unchanged
  from step-17.

<li> <code>update_quadrature_point_history ()</code> [via
  <code>solve_timestep ()</code>]: Based on the displacement field $\Delta \mathbf{u}^n$ computed before, we update the stress values in all quadrature points
  according to <a href="#step_18.stress-update">[stress-update]</a> and <a href="#step_18.stress-update+rot">[stress-update+rot]</a>,
  including the rotation of the coordinate system.

<li> <code>move_mesh ()</code>: Given the solution computed before, in this
  function we deform the mesh by moving each vertex by the displacement vector
  field evaluated at this particular vertex.

<li> <code>output_results ()</code>: This function simply outputs the solution
  based on what we have said above, i.e. every processor computes output only
  for its own portion of the domain. In addition to the solution, we also compute the norm of
  the stress averaged over all the quadrature points on each cell.
</ul>

With this general structure of the code, we only have to define what case we
want to solve. For the present program, we have chosen to simulate the
quasistatic deformation of a vertical cylinder for which the bottom boundary
is fixed and the top boundary is pushed down at a prescribed vertical
velocity. However, the horizontal velocity of the top boundary is left
unspecified -- one can imagine this situation as a well-greased plate pushing
from the top onto the cylinder, the points on the top boundary of the cylinder
being allowed to slide horizontally along the surface of the plate, but forced
to move downward by the plate. The inner and outer boundaries of the cylinder
are free and not subject to any prescribed deflection or traction. In
addition, gravity acts on the body.

The program text will reveal more about how to implement this situation, and
the results section will show what displacement pattern comes out of this
simulation.
