1 /* ---------------------------------------------------------------------
2  *
3  * Copyright (C) 2000 - 2020 by the deal.II authors
4  *
5  * This file is part of the deal.II library.
6  *
7  * The deal.II library is free software; you can use it, redistribute
8  * it, and/or modify it under the terms of the GNU Lesser General
9  * Public License as published by the Free Software Foundation; either
10  * version 2.1 of the License, or (at your option) any later version.
11  * The full text of the license can be found in the file LICENSE.md at
12  * the top level directory of deal.II.
13  *
14  * ---------------------------------------------------------------------
15 
16  *
17  * Author: Wolfgang Bangerth, University of Texas at Austin, 2000, 2004, 2005,
18  * Timo Heister, 2013
19  */
20 
21 
22 // First the usual list of header files that have already been used in
23 // previous example programs:
24 #include <deal.II/base/quadrature_lib.h>
25 #include <deal.II/base/function.h>
26 #include <deal.II/base/logstream.h>
27 #include <deal.II/base/multithread_info.h>
28 #include <deal.II/base/conditional_ostream.h>
29 #include <deal.II/base/utilities.h>
30 #include <deal.II/lac/vector.h>
31 #include <deal.II/lac/full_matrix.h>
32 #include <deal.II/lac/dynamic_sparsity_pattern.h>
33 #include <deal.II/lac/petsc_vector.h>
34 #include <deal.II/lac/petsc_sparse_matrix.h>
35 #include <deal.II/lac/petsc_solver.h>
36 #include <deal.II/lac/petsc_precondition.h>
37 #include <deal.II/lac/affine_constraints.h>
38 #include <deal.II/lac/sparsity_tools.h>
39 #include <deal.II/distributed/shared_tria.h>
40 #include <deal.II/grid/tria.h>
41 #include <deal.II/grid/grid_generator.h>
42 #include <deal.II/grid/grid_refinement.h>
43 #include <deal.II/grid/tria_accessor.h>
44 #include <deal.II/grid/tria_iterator.h>
45 #include <deal.II/grid/manifold_lib.h>
46 #include <deal.II/grid/grid_tools.h>
47 #include <deal.II/dofs/dof_handler.h>
48 #include <deal.II/dofs/dof_accessor.h>
49 #include <deal.II/dofs/dof_tools.h>
50 #include <deal.II/dofs/dof_renumbering.h>
51 #include <deal.II/fe/fe_values.h>
52 #include <deal.II/fe/fe_system.h>
53 #include <deal.II/fe/fe_q.h>
54 #include <deal.II/numerics/vector_tools.h>
55 #include <deal.II/numerics/matrix_tools.h>
56 #include <deal.II/numerics/data_out.h>
57 #include <deal.II/numerics/error_estimator.h>
58 
59 // And here the only three new things among the header files: an include file in
60 // which symmetric tensors of rank 2 and 4 are implemented, as introduced in
61 // the introduction:
62 #include <deal.II/base/symmetric_tensor.h>
63 
64 // And lastly a header that contains some functions that will help us compute
65 // rotaton matrices of the local coordinate systems at specific points in the
66 // domain.
67 #include <deal.II/physics/transformations.h>
68 
69 // This is then simply C++ again:
70 #include <fstream>
71 #include <iostream>
72 #include <iomanip>
73 
74 // The last step is as in all previous programs:
75 namespace Step18
76 {
77   using namespace dealii;
78 
79   // @sect3{The <code>PointHistory</code> class}
80 
81   // As was mentioned in the introduction, we have to store the old stress in
82   // quadrature point so that we can compute the residual forces at this point
83   // during the next time step. This alone would not warrant a structure with
84   // only one member, but in more complicated applications, we would have to
85   // store more information in quadrature points as well, such as the history
86   // variables of plasticity, etc. In essence, we have to store everything
87   // that affects the present state of the material here, which in plasticity
88   // is determined by the deformation history variables.
89   //
90   // We will not give this class any meaningful functionality beyond being
91   // able to store data, i.e. there are no constructors, destructors, or other
92   // member functions. In such cases of `dumb' classes, we usually opt to
93   // declare them as <code>struct</code> rather than <code>class</code>, to
94   // indicate that they are closer to C-style structures than C++-style
95   // classes.
96   template <int dim>
97   struct PointHistory
98   {
99     SymmetricTensor<2, dim> old_stress;
100   };
101 
102 
103   // @sect3{The stress-strain tensor}
104 
105   // Next, we define the linear relationship between the stress and the strain
106   // in elasticity. It is given by a tensor of rank 4 that is usually written
107   // in the form $C_{ijkl} = \mu (\delta_{ik} \delta_{jl} + \delta_{il}
108   // \delta_{jk}) + \lambda \delta_{ij} \delta_{kl}$. This tensor maps
109   // symmetric tensor of rank 2 to symmetric tensors of rank 2. A function
110   // implementing its creation for given values of the Lam&eacute; constants
111   // $\lambda$ and $\mu$ is straightforward:
112   template <int dim>
get_stress_strain_tensor(const double lambda,const double mu)113   SymmetricTensor<4, dim> get_stress_strain_tensor(const double lambda,
114                                                    const double mu)
115   {
116     SymmetricTensor<4, dim> tmp;
117     for (unsigned int i = 0; i < dim; ++i)
118       for (unsigned int j = 0; j < dim; ++j)
119         for (unsigned int k = 0; k < dim; ++k)
120           for (unsigned int l = 0; l < dim; ++l)
121             tmp[i][j][k][l] = (((i == k) && (j == l) ? mu : 0.0) +
122                                ((i == l) && (j == k) ? mu : 0.0) +
123                                ((i == j) && (k == l) ? lambda : 0.0));
124     return tmp;
125   }
126 
127   // With this function, we will define a static member variable of the main
128   // class below that will be used throughout the program as the stress-strain
129   // tensor. Note that in more elaborate programs, this will probably be a
130   // member variable of some class instead, or a function that returns the
131   // stress-strain relationship depending on other input. For example in
132   // damage theory models, the Lam&eacute; constants are considered a function
133   // of the prior stress/strain history of a point. Conversely, in plasticity
134   // the form of the stress-strain tensor is modified if the material has
135   // reached the yield stress in a certain point, and possibly also depending on
136   // its prior history.
137   //
138   // In the present program, however, we assume that the material is
139   // completely elastic and linear, and a constant stress-strain tensor is
140   // sufficient for our present purposes.
141 
142 
143 
144   // @sect3{Auxiliary functions}
145 
146   // Before the rest of the program, here are a few functions that we need as
147   // tools. These are small functions that are called in inner loops, so we
148   // mark them as <code>inline</code>.
149   //
150   // The first one computes the symmetric strain tensor for shape function
151   // <code>shape_func</code> at quadrature point <code>q_point</code> by
152   // forming the symmetric gradient of this shape function. We need that when
153   // we want to form the matrix, for example.
154   //
155   // We should note that in previous examples where we have treated
156   // vector-valued problems, we have always asked the finite element object in
157   // which of the vector component the shape function is actually non-zero,
158   // and thereby avoided to compute any terms that we could prove were zero
159   // anyway. For this, we used the <code>fe.system_to_component_index</code>
160   // function that returns in which component a shape function was zero, and
161   // also that the <code>fe_values.shape_value</code> and
162   // <code>fe_values.shape_grad</code> functions only returned the value and
163   // gradient of the single non-zero component of a shape function if this is
164   // a vector-valued element.
165   //
166   // This was an optimization, and if it isn't terribly time critical, we can
167   // get away with a simpler technique: just ask the <code>fe_values</code>
168   // for the value or gradient of a given component of a given shape function
169   // at a given quadrature point. This is what the
170   // <code>fe_values.shape_grad_component(shape_func,q_point,i)</code> call
171   // does: return the full gradient of the <code>i</code>th component of shape
172   // function <code>shape_func</code> at quadrature point
173   // <code>q_point</code>. If a certain component of a certain shape function
174   // is always zero, then this will simply always return zero.
175   //
176   // As mentioned, using <code>fe_values.shape_grad_component</code> instead
177   // of the combination of <code>fe.system_to_component_index</code> and
178   // <code>fe_values.shape_grad</code> may be less efficient, but its
179   // implementation is optimized for such cases and shouldn't be a big
180   // slowdown. We demonstrate the technique here since it is so much simpler
181   // and straightforward.
182   template <int dim>
get_strain(const FEValues<dim> & fe_values,const unsigned int shape_func,const unsigned int q_point)183   inline SymmetricTensor<2, dim> get_strain(const FEValues<dim> &fe_values,
184                                             const unsigned int   shape_func,
185                                             const unsigned int   q_point)
186   {
187     // Declare a temporary that will hold the return value:
188     SymmetricTensor<2, dim> tmp;
189 
190     // First, fill diagonal terms which are simply the derivatives in
191     // direction <code>i</code> of the <code>i</code> component of the
192     // vector-valued shape function:
193     for (unsigned int i = 0; i < dim; ++i)
194       tmp[i][i] = fe_values.shape_grad_component(shape_func, q_point, i)[i];
195 
196     // Then fill the rest of the strain tensor. Note that since the tensor is
197     // symmetric, we only have to compute one half (here: the upper right
198     // corner) of the off-diagonal elements, and the implementation of the
199     // <code>SymmetricTensor</code> class makes sure that at least to the
200     // outside the symmetric entries are also filled (in practice, the class
201     // of course stores only one copy). Here, we have picked the upper right
202     // half of the tensor, but the lower left one would have been just as
203     // good:
204     for (unsigned int i = 0; i < dim; ++i)
205       for (unsigned int j = i + 1; j < dim; ++j)
206         tmp[i][j] =
207           (fe_values.shape_grad_component(shape_func, q_point, i)[j] +
208            fe_values.shape_grad_component(shape_func, q_point, j)[i]) /
209           2;
210 
211     return tmp;
212   }
213 
214 
215   // The second function does something very similar (and therefore is given
216   // the same name): compute the symmetric strain tensor from the gradient of
217   // a vector-valued field. If you already have a solution field, the
218   // <code>fe_values.get_function_gradients</code> function allows you to
219   // extract the gradients of each component of your solution field at a
220   // quadrature point. It returns this as a vector of rank-1 tensors: one rank-1
221   // tensor (gradient) per vector component of the solution. From this we have
222   // to reconstruct the (symmetric) strain tensor by transforming the data
223   // storage format and symmetrization. We do this in the same way as above,
224   // i.e. we avoid a few computations by filling first the diagonal and then
225   // only one half of the symmetric tensor (the <code>SymmetricTensor</code>
226   // class makes sure that it is sufficient to write only one of the two
227   // symmetric components).
228   //
229   // Before we do this, though, we make sure that the input has the kind of
230   // structure we expect: that is that there are <code>dim</code> vector
231   // components, i.e. one displacement component for each coordinate
232   // direction. We test this with the <code>Assert</code> macro that will
233   // simply abort our program if the condition is not met.
234   template <int dim>
235   inline SymmetricTensor<2, dim>
get_strain(const std::vector<Tensor<1,dim>> & grad)236   get_strain(const std::vector<Tensor<1, dim>> &grad)
237   {
238     Assert(grad.size() == dim, ExcInternalError());
239 
240     SymmetricTensor<2, dim> strain;
241     for (unsigned int i = 0; i < dim; ++i)
242       strain[i][i] = grad[i][i];
243 
244     for (unsigned int i = 0; i < dim; ++i)
245       for (unsigned int j = i + 1; j < dim; ++j)
246         strain[i][j] = (grad[i][j] + grad[j][i]) / 2;
247 
248     return strain;
249   }
250 
251 
252   // Finally, below we will need a function that computes the rotation matrix
253   // induced by a displacement at a given point. In fact, of course, the
254   // displacement at a single point only has a direction and a magnitude, it
255   // is the change in direction and magnitude that induces rotations. In
256   // effect, the rotation matrix can be computed from the gradients of a
257   // displacement, or, more specifically, from the curl.
258   //
259   // The formulas by which the rotation matrices are determined are a little
260   // awkward, especially in 3d. For 2d, there is a simpler way, so we
261   // implement this function twice, once for 2d and once for 3d, so that we
262   // can compile and use the program in both space dimensions if so desired --
263   // after all, deal.II is all about dimension independent programming and
264   // reuse of algorithm thoroughly tested with cheap computations in 2d, for
265   // the more expensive computations in 3d. Here is one case, where we have to
266   // implement different algorithms for 2d and 3d, but then can write the rest
267   // of the program in a way that is independent of the space dimension.
268   //
269   // So, without further ado to the 2d implementation:
get_rotation_matrix(const std::vector<Tensor<1,2>> & grad_u)270   Tensor<2, 2> get_rotation_matrix(const std::vector<Tensor<1, 2>> &grad_u)
271   {
272     // First, compute the curl of the velocity field from the gradients. Note
273     // that we are in 2d, so the rotation is a scalar:
274     const double curl = (grad_u[1][0] - grad_u[0][1]);
275 
276     // From this, compute the angle of rotation:
277     const double angle = std::atan(curl);
278 
279     // And from this, build the antisymmetric rotation matrix. We want this
280     // rotation matrix to represent the rotation of the local coordinate system
281     // with respect to the global Cartesian basis, to we construct it with a
282     // negative angle. The rotation matrix therefore represents the rotation
283     // required to move from the local to the global coordinate system.
284     return Physics::Transformations::Rotations::rotation_matrix_2d(-angle);
285   }
286 
287 
288   // The 3d case is a little more contrived:
get_rotation_matrix(const std::vector<Tensor<1,3>> & grad_u)289   Tensor<2, 3> get_rotation_matrix(const std::vector<Tensor<1, 3>> &grad_u)
290   {
291     // Again first compute the curl of the velocity field. This time, it is a
292     // real vector:
293     const Point<3> curl(grad_u[2][1] - grad_u[1][2],
294                         grad_u[0][2] - grad_u[2][0],
295                         grad_u[1][0] - grad_u[0][1]);
296 
297     // From this vector, using its magnitude, compute the tangent of the angle
298     // of rotation, and from it the actual angle of rotation with respect to
299     // the Cartesian basis:
300     const double tan_angle = std::sqrt(curl * curl);
301     const double angle     = std::atan(tan_angle);
302 
303     // Now, here's one problem: if the angle of rotation is too small, that
304     // means that there is no rotation going on (for example a translational
305     // motion). In that case, the rotation matrix is the identity matrix.
306     //
307     // The reason why we stress that is that in this case we have that
308     // <code>tan_angle==0</code>. Further down, we need to divide by that
309     // number in the computation of the axis of rotation, and we would get
310     // into trouble when dividing doing so. Therefore, let's shortcut this and
311     // simply return the identity matrix if the angle of rotation is really
312     // small:
313     if (std::abs(angle) < 1e-9)
314       {
315         static const double rotation[3][3] = {{1, 0, 0}, {0, 1, 0}, {0, 0, 1}};
316         static const Tensor<2, 3> rot(rotation);
317         return rot;
318       }
319 
320     // Otherwise compute the real rotation matrix. For this, again we rely on
321     // a predefined function to compute the rotation matrix of the local
322     // coordinate system.
323     const Point<3> axis = curl / tan_angle;
324     return Physics::Transformations::Rotations::rotation_matrix_3d(axis,
325                                                                    -angle);
326   }
327 
328 
329 
330   // @sect3{The <code>TopLevel</code> class}
331 
332   // This is the main class of the program. Since the namespace already
333   // indicates what problem we are solving, let's call it by what it does: it
334   // directs the flow of the program, i.e. it is the toplevel driver.
335   //
336   // The member variables of this class are essentially as before, i.e. it has
337   // to have a triangulation, a DoF handler and associated objects such as
338   // constraints, variables that describe the linear system, etc. There are a
339   // good number of more member functions now, which we will explain below.
340   //
341   // The external interface of the class, however, is unchanged: it has a
342   // public constructor and destructor, and it has a <code>run</code>
343   // function that initiated all the work.
344   template <int dim>
345   class TopLevel
346   {
347   public:
348     TopLevel();
349     ~TopLevel();
350     void run();
351 
352   private:
353     // The private interface is more extensive than in step-17. First, we
354     // obviously need functions that create the initial mesh, set up the
355     // variables that describe the linear system on the present mesh
356     // (i.e. matrices and vectors), and then functions that actually assemble
357     // the system, direct what has to be solved in each time step, a function
358     // that solves the linear system that arises in each timestep (and returns
359     // the number of iterations it took), and finally output the solution
360     // vector on the correct mesh:
361     void create_coarse_grid();
362 
363     void setup_system();
364 
365     void assemble_system();
366 
367     void solve_timestep();
368 
369     unsigned int solve_linear_problem();
370 
371     void output_results() const;
372 
373     // All, except for the first two, of these functions are called in each
374     // timestep. Since the first time step is a little special, we have
375     // separate functions that describe what has to happen in a timestep: one
376     // for the first, and one for all following timesteps:
377     void do_initial_timestep();
378 
379     void do_timestep();
380 
381     // Then we need a whole bunch of functions that do various things. The
382     // first one refines the initial grid: we start on the coarse grid with a
383     // pristine state, solve the problem, then look at it and refine the mesh
384     // accordingly, and start the same process over again, again with a
385     // pristine state. Thus, refining the initial mesh is somewhat simpler
386     // than refining a grid between two successive time steps, since it does
387     // not involve transferring data from the old to the new triangulation, in
388     // particular the history data that is stored in each quadrature point.
389     void refine_initial_grid();
390 
391     // At the end of each time step, we want to move the mesh vertices around
392     // according to the incremental displacement computed in this time
393     // step. This is the function in which this is done:
394     void move_mesh();
395 
396     // Next are two functions that handle the history variables stored in each
397     // quadrature point. The first one is called before the first timestep to
398     // set up a pristine state for the history variables. It only works on
399     // those quadrature points on cells that belong to the present processor:
400     void setup_quadrature_point_history();
401 
402     // The second one updates the history variables at the end of each
403     // timestep:
404     void update_quadrature_point_history();
405 
406     // This is the new shared Triangulation:
407     parallel::shared::Triangulation<dim> triangulation;
408 
409     FESystem<dim> fe;
410 
411     DoFHandler<dim> dof_handler;
412 
413     AffineConstraints<double> hanging_node_constraints;
414 
415     // One difference of this program is that we declare the quadrature
416     // formula in the class declaration. The reason is that in all the other
417     // programs, it didn't do much harm if we had used different quadrature
418     // formulas when computing the matrix and the right hand side, for
419     // example. However, in the present case it does: we store information in
420     // the quadrature points, so we have to make sure all parts of the program
421     // agree on where they are and how many there are on each cell. Thus, let
422     // us first declare the quadrature formula that will be used throughout...
423     const QGauss<dim> quadrature_formula;
424 
425     // ... and then also have a vector of history objects, one per quadrature
426     // point on those cells for which we are responsible (i.e. we don't store
427     // history data for quadrature points on cells that are owned by other
428     // processors).
429     // Note that, instead of storing and managing this data ourself, we
430     // could use the CellDataStorage class like is done in step-44. However,
431     // for the purpose of demonstration, in this case we manage the storage
432     // manually.
433     std::vector<PointHistory<dim>> quadrature_point_history;
434 
435     // The way this object is accessed is through a <code>user pointer</code>
436     // that each cell, face, or edge holds: it is a <code>void*</code> pointer
437     // that can be used by application programs to associate arbitrary data to
438     // cells, faces, or edges. What the program actually does with this data
439     // is within its own responsibility, the library just allocates some space
440     // for these pointers, and application programs can set and read the
441     // pointers for each of these objects.
442 
443 
444     // Further: we need the objects of linear systems to be solved,
445     // i.e. matrix, right hand side vector, and the solution vector. Since we
446     // anticipate solving big problems, we use the same types as in step-17,
447     // i.e. distributed %parallel matrices and vectors built on top of the
448     // PETSc library. Conveniently, they can also be used when running on only
449     // a single machine, in which case this machine happens to be the only one
450     // in our %parallel universe.
451     //
452     // However, as a difference to step-17, we do not store the solution
453     // vector -- which here is the incremental displacements computed in each
454     // time step -- in a distributed fashion. I.e., of course it must be a
455     // distributed vector when computing it, but immediately after that we
456     // make sure each processor has a complete copy. The reason is that we had
457     // already seen in step-17 that many functions needed a complete
458     // copy. While it is not hard to get it, this requires communication on
459     // the network, and is thus slow. In addition, these were repeatedly the
460     // same operations, which is certainly undesirable unless the gains of not
461     // always having to store the entire vector outweighs it. When writing
462     // this program, it turned out that we need a complete copy of the
463     // solution in so many places that it did not seem worthwhile to only get
464     // it when necessary. Instead, we opted to obtain the complete copy once
465     // and for all, and instead get rid of the distributed copy
466     // immediately. Thus, note that the declaration of
467     // <code>incremental_displacement</code> does not denote a distribute
468     // vector as would be indicated by the middle namespace <code>MPI</code>:
469     PETScWrappers::MPI::SparseMatrix system_matrix;
470 
471     PETScWrappers::MPI::Vector system_rhs;
472 
473     Vector<double> incremental_displacement;
474 
475     // The next block of variables is then related to the time dependent
476     // nature of the problem: they denote the length of the time interval
477     // which we want to simulate, the present time and number of time step,
478     // and length of present timestep:
479     double       present_time;
480     double       present_timestep;
481     double       end_time;
482     unsigned int timestep_no;
483 
484     // Then a few variables that have to do with %parallel processing: first,
485     // a variable denoting the MPI communicator we use, and then two numbers
486     // telling us how many participating processors there are, and where in
487     // this world we are. Finally, a stream object that makes sure only one
488     // processor is actually generating output to the console. This is all the
489     // same as in step-17:
490     MPI_Comm mpi_communicator;
491 
492     const unsigned int n_mpi_processes;
493 
494     const unsigned int this_mpi_process;
495 
496     ConditionalOStream pcout;
497 
498     // We are storing the locally owned and the locally relevant indices:
499     IndexSet locally_owned_dofs;
500     IndexSet locally_relevant_dofs;
501 
502     // Finally, we have a static variable that denotes the linear relationship
503     // between the stress and strain. Since it is a constant object that does
504     // not depend on any input (at least not in this program), we make it a
505     // static variable and will initialize it in the same place where we
506     // define the constructor of this class:
507     static const SymmetricTensor<4, dim> stress_strain_tensor;
508   };
509 
510 
511   // @sect3{The <code>BodyForce</code> class}
512 
513   // Before we go on to the main functionality of this program, we have to
514   // define what forces will act on the body whose deformation we want to
515   // study. These may either be body forces or boundary forces. Body forces
516   // are generally mediated by one of the four basic physical types of forces:
517   // gravity, strong and weak interaction, and electromagnetism. Unless one
518   // wants to consider subatomic objects (for which quasistatic deformation is
519   // irrelevant and an inappropriate description anyway), only gravity and
520   // electromagnetic forces need to be considered. Let us, for simplicity
521   // assume that our body has a certain mass density, but is either
522   // non-magnetic and not electrically conducting or that there are no
523   // significant electromagnetic fields around. In that case, the body forces
524   // are simply <code>rho g</code>, where <code>rho</code> is the material
525   // density and <code>g</code> is a vector in negative z-direction with
526   // magnitude 9.81 m/s^2.  Both the density and <code>g</code> are defined in
527   // the function, and we take as the density 7700 kg/m^3, a value commonly
528   // assumed for steel.
529   //
530   // To be a little more general and to be able to do computations in 2d as
531   // well, we realize that the body force is always a function returning a
532   // <code>dim</code> dimensional vector. We assume that gravity acts along
533   // the negative direction of the last, i.e. <code>dim-1</code>th
534   // coordinate. The rest of the implementation of this function should be
535   // mostly self-explanatory given similar definitions in previous example
536   // programs. Note that the body force is independent of the location; to
537   // avoid compiler warnings about unused function arguments, we therefore
538   // comment out the name of the first argument of the
539   // <code>vector_value</code> function:
540   template <int dim>
541   class BodyForce : public Function<dim>
542   {
543   public:
544     BodyForce();
545 
546     virtual void vector_value(const Point<dim> &p,
547                               Vector<double> &  values) const override;
548 
549     virtual void
550     vector_value_list(const std::vector<Point<dim>> &points,
551                       std::vector<Vector<double>> &  value_list) const override;
552   };
553 
554 
555   template <int dim>
BodyForce()556   BodyForce<dim>::BodyForce()
557     : Function<dim>(dim)
558   {}
559 
560 
561   template <int dim>
vector_value(const Point<dim> &,Vector<double> & values) const562   inline void BodyForce<dim>::vector_value(const Point<dim> & /*p*/,
563                                            Vector<double> &values) const
564   {
565     Assert(values.size() == dim, ExcDimensionMismatch(values.size(), dim));
566 
567     const double g   = 9.81;
568     const double rho = 7700;
569 
570     values          = 0;
571     values(dim - 1) = -rho * g;
572   }
573 
574 
575 
576   template <int dim>
vector_value_list(const std::vector<Point<dim>> & points,std::vector<Vector<double>> & value_list) const577   void BodyForce<dim>::vector_value_list(
578     const std::vector<Point<dim>> &points,
579     std::vector<Vector<double>> &  value_list) const
580   {
581     const unsigned int n_points = points.size();
582 
583     Assert(value_list.size() == n_points,
584            ExcDimensionMismatch(value_list.size(), n_points));
585 
586     for (unsigned int p = 0; p < n_points; ++p)
587       BodyForce<dim>::vector_value(points[p], value_list[p]);
588   }
589 
590 
591 
592   // @sect3{The <code>IncrementalBoundaryValue</code> class}
593 
594   // In addition to body forces, movement can be induced by boundary forces
595   // and forced boundary displacement. The latter case is equivalent to forces
596   // being chosen in such a way that they induce certain displacement.
597   //
598   // For quasistatic displacement, typical boundary forces would be pressure
599   // on a body, or tangential friction against another body. We chose a
600   // somewhat simpler case here: we prescribe a certain movement of (parts of)
601   // the boundary, or at least of certain components of the displacement
602   // vector. We describe this by another vector-valued function that, for a
603   // given point on the boundary, returns the prescribed displacement.
604   //
605   // Since we have a time-dependent problem, the displacement increment of the
606   // boundary equals the displacement accumulated during the length of the
607   // timestep. The class therefore has to know both the present time and the
608   // length of the present time step, and can then approximate the incremental
609   // displacement as the present velocity times the present timestep.
610   //
611   // For the purposes of this program, we choose a simple form of boundary
612   // displacement: we displace the top boundary with constant velocity
613   // downwards. The rest of the boundary is either going to be fixed (and is
614   // then described using an object of type
615   // <code>Functions::ZeroFunction</code>) or free (Neumann-type, in which case
616   // nothing special has to be done).  The implementation of the class
617   // describing the constant downward motion should then be obvious using the
618   // knowledge we gained through all the previous example programs:
619   template <int dim>
620   class IncrementalBoundaryValues : public Function<dim>
621   {
622   public:
623     IncrementalBoundaryValues(const double present_time,
624                               const double present_timestep);
625 
626     virtual void vector_value(const Point<dim> &p,
627                               Vector<double> &  values) const override;
628 
629     virtual void
630     vector_value_list(const std::vector<Point<dim>> &points,
631                       std::vector<Vector<double>> &  value_list) const override;
632 
633   private:
634     const double velocity;
635     const double present_time;
636     const double present_timestep;
637   };
638 
639 
640   template <int dim>
IncrementalBoundaryValues(const double present_time,const double present_timestep)641   IncrementalBoundaryValues<dim>::IncrementalBoundaryValues(
642     const double present_time,
643     const double present_timestep)
644     : Function<dim>(dim)
645     , velocity(.08)
646     , present_time(present_time)
647     , present_timestep(present_timestep)
648   {}
649 
650 
651   template <int dim>
652   void
vector_value(const Point<dim> &,Vector<double> & values) const653   IncrementalBoundaryValues<dim>::vector_value(const Point<dim> & /*p*/,
654                                                Vector<double> &values) const
655   {
656     Assert(values.size() == dim, ExcDimensionMismatch(values.size(), dim));
657 
658     values    = 0;
659     values(2) = -present_timestep * velocity;
660   }
661 
662 
663 
664   template <int dim>
vector_value_list(const std::vector<Point<dim>> & points,std::vector<Vector<double>> & value_list) const665   void IncrementalBoundaryValues<dim>::vector_value_list(
666     const std::vector<Point<dim>> &points,
667     std::vector<Vector<double>> &  value_list) const
668   {
669     const unsigned int n_points = points.size();
670 
671     Assert(value_list.size() == n_points,
672            ExcDimensionMismatch(value_list.size(), n_points));
673 
674     for (unsigned int p = 0; p < n_points; ++p)
675       IncrementalBoundaryValues<dim>::vector_value(points[p], value_list[p]);
676   }
677 
678 
679 
680   // @sect3{Implementation of the <code>TopLevel</code> class}
681 
682   // Now for the implementation of the main class. First, we initialize the
683   // stress-strain tensor, which we have declared as a static const
684   // variable. We chose Lam&eacute; constants that are appropriate for steel:
685   template <int dim>
686   const SymmetricTensor<4, dim> TopLevel<dim>::stress_strain_tensor =
687     get_stress_strain_tensor<dim>(/*lambda = */ 9.695e10,
688                                   /*mu     = */ 7.617e10);
689 
690 
691 
692   // @sect4{The public interface}
693 
694   // The next step is the definition of constructors and destructors. There
695   // are no surprises here: we choose linear and continuous finite elements
696   // for each of the <code>dim</code> vector components of the solution, and a
697   // Gaussian quadrature formula with 2 points in each coordinate
698   // direction. The destructor should be obvious:
699   template <int dim>
TopLevel()700   TopLevel<dim>::TopLevel()
701     : triangulation(MPI_COMM_WORLD)
702     , fe(FE_Q<dim>(1), dim)
703     , dof_handler(triangulation)
704     , quadrature_formula(fe.degree + 1)
705     , present_time(0.0)
706     , present_timestep(1.0)
707     , end_time(10.0)
708     , timestep_no(0)
709     , mpi_communicator(MPI_COMM_WORLD)
710     , n_mpi_processes(Utilities::MPI::n_mpi_processes(mpi_communicator))
711     , this_mpi_process(Utilities::MPI::this_mpi_process(mpi_communicator))
712     , pcout(std::cout, this_mpi_process == 0)
713   {}
714 
715 
716 
717   template <int dim>
~TopLevel()718   TopLevel<dim>::~TopLevel()
719   {
720     dof_handler.clear();
721   }
722 
723 
724 
725   // The last of the public functions is the one that directs all the work,
726   // <code>run()</code>. It initializes the variables that describe where in
727   // time we presently are, then runs the first time step, then loops over all
728   // the other time steps. Note that for simplicity we use a fixed time step,
729   // whereas a more sophisticated program would of course have to choose it in
730   // some more reasonable way adaptively:
731   template <int dim>
run()732   void TopLevel<dim>::run()
733   {
734     do_initial_timestep();
735 
736     while (present_time < end_time)
737       do_timestep();
738   }
739 
740 
741   // @sect4{TopLevel::create_coarse_grid}
742 
743   // The next function in the order in which they were declared above is the
744   // one that creates the coarse grid from which we start. For this example
745   // program, we want to compute the deformation of a cylinder under axial
746   // compression. The first step therefore is to generate a mesh for a
747   // cylinder of length 3 and with inner and outer radii of 0.8 and 1,
748   // respectively. Fortunately, there is a library function for such a mesh.
749   //
750   // In a second step, we have to associated boundary conditions with the
751   // upper and lower faces of the cylinder. We choose a boundary indicator of
752   // 0 for the boundary faces that are characterized by their midpoints having
753   // z-coordinates of either 0 (bottom face), an indicator of 1 for z=3 (top
754   // face); finally, we use boundary indicator 2 for all faces on the inside
755   // of the cylinder shell, and 3 for the outside.
756   template <int dim>
create_coarse_grid()757   void TopLevel<dim>::create_coarse_grid()
758   {
759     const double inner_radius = 0.8, outer_radius = 1;
760     GridGenerator::cylinder_shell(triangulation, 3, inner_radius, outer_radius);
761     for (const auto &cell : triangulation.active_cell_iterators())
762       for (const auto &face : cell->face_iterators())
763         if (face->at_boundary())
764           {
765             const Point<dim> face_center = face->center();
766 
767             if (face_center[2] == 0)
768               face->set_boundary_id(0);
769             else if (face_center[2] == 3)
770               face->set_boundary_id(1);
771             else if (std::sqrt(face_center[0] * face_center[0] +
772                                face_center[1] * face_center[1]) <
773                      (inner_radius + outer_radius) / 2)
774               face->set_boundary_id(2);
775             else
776               face->set_boundary_id(3);
777           }
778 
779     // Once all this is done, we can refine the mesh once globally:
780     triangulation.refine_global(1);
781 
782     // As the final step, we need to set up a clean state of the data that we
783     // store in the quadrature points on all cells that are treated on the
784     // present processor.
785     setup_quadrature_point_history();
786   }
787 
788 
789 
790   // @sect4{TopLevel::setup_system}
791 
792   // The next function is the one that sets up the data structures for a given
793   // mesh. This is done in most the same way as in step-17: distribute the
794   // degrees of freedom, then sort these degrees of freedom in such a way that
795   // each processor gets a contiguous chunk of them. Note that subdivisions into
796   // chunks for each processor is handled in the functions that create or
797   // refine grids, unlike in the previous example program (the point where
798   // this happens is mostly a matter of taste; here, we chose to do it when
799   // grids are created since in the <code>do_initial_timestep</code> and
800   // <code>do_timestep</code> functions we want to output the number of cells
801   // on each processor at a point where we haven't called the present function
802   // yet).
803   template <int dim>
setup_system()804   void TopLevel<dim>::setup_system()
805   {
806     dof_handler.distribute_dofs(fe);
807     locally_owned_dofs = dof_handler.locally_owned_dofs();
808     DoFTools::extract_locally_relevant_dofs(dof_handler, locally_relevant_dofs);
809 
810     // The next step is to set up constraints due to hanging nodes. This has
811     // been handled many times before:
812     hanging_node_constraints.clear();
813     DoFTools::make_hanging_node_constraints(dof_handler,
814                                             hanging_node_constraints);
815     hanging_node_constraints.close();
816 
817     // And then we have to set up the matrix. Here we deviate from step-17, in
818     // which we simply used PETSc's ability to just know about the size of the
819     // matrix and later allocate those nonzero elements that are being written
820     // to. While this works just fine from a correctness viewpoint, it is not
821     // at all efficient: if we don't give PETSc a clue as to which elements
822     // are written to, it is (at least at the time of this writing) unbearably
823     // slow when we set the elements in the matrix for the first time (i.e. in
824     // the first timestep). Later on, when the elements have been allocated,
825     // everything is much faster. In experiments we made, the first timestep
826     // can be accelerated by almost two orders of magnitude if we instruct
827     // PETSc which elements will be used and which are not.
828     //
829     // To do so, we first generate the sparsity pattern of the matrix we are
830     // going to work with, and make sure that the condensation of hanging node
831     // constraints add the necessary additional entries in the sparsity
832     // pattern:
833     DynamicSparsityPattern sparsity_pattern(locally_relevant_dofs);
834     DoFTools::make_sparsity_pattern(dof_handler,
835                                     sparsity_pattern,
836                                     hanging_node_constraints,
837                                     /*keep constrained dofs*/ false);
838     SparsityTools::distribute_sparsity_pattern(sparsity_pattern,
839                                                locally_owned_dofs,
840                                                mpi_communicator,
841                                                locally_relevant_dofs);
842     // Note that we have used the <code>DynamicSparsityPattern</code> class
843     // here that was already introduced in step-11, rather than the
844     // <code>SparsityPattern</code> class that we have used in all other
845     // cases. The reason for this is that for the latter class to work we have
846     // to give an initial upper bound for the number of entries in each row, a
847     // task that is traditionally done by
848     // <code>DoFHandler::max_couplings_between_dofs()</code>. However, this
849     // function suffers from a serious problem: it has to compute an upper
850     // bound to the number of nonzero entries in each row, and this is a
851     // rather complicated task, in particular in 3d. In effect, while it is
852     // quite accurate in 2d, it often comes up with much too large a number in
853     // 3d, and in that case the <code>SparsityPattern</code> allocates much
854     // too much memory at first, often several 100 MBs. This is later
855     // corrected when <code>DoFTools::make_sparsity_pattern</code> is called
856     // and we realize that we don't need all that much memory, but at time it
857     // is already too late: for large problems, the temporary allocation of
858     // too much memory can lead to out-of-memory situations.
859     //
860     // In order to avoid this, we resort to the
861     // <code>DynamicSparsityPattern</code> class that is slower but does
862     // not require any up-front estimate on the number of nonzero entries per
863     // row. It therefore only ever allocates as much memory as it needs at any
864     // given time, and we can build it even for large 3d problems.
865     //
866     // It is also worth noting that due to the specifics of
867     // parallel::shared::Triangulation, the sparsity pattern we construct is
868     // global, i.e. comprises all degrees of freedom whether they will be
869     // owned by the processor we are on or another one (in case this program
870     // is run in %parallel via MPI). This of course is not optimal -- it
871     // limits the size of the problems we can solve, since storing the entire
872     // sparsity pattern (even if only for a short time) on each processor does
873     // not scale well. However, there are several more places in the program
874     // in which we do this, for example we always keep the global
875     // triangulation and DoF handler objects around, even if we only work on
876     // part of them. At present, deal.II does not have the necessary
877     // facilities to completely distribute these objects (a task that, indeed,
878     // is very hard to achieve with adaptive meshes, since well-balanced
879     // subdivisions of a domain tend to become unbalanced as the mesh is
880     // adaptively refined).
881     //
882     // With this data structure, we can then go to the PETSc sparse matrix and
883     // tell it to preallocate all the entries we will later want to write to:
884     system_matrix.reinit(locally_owned_dofs,
885                          locally_owned_dofs,
886                          sparsity_pattern,
887                          mpi_communicator);
888     // After this point, no further explicit knowledge of the sparsity pattern
889     // is required any more and we can let the <code>sparsity_pattern</code>
890     // variable go out of scope without any problem.
891 
892     // The last task in this function is then only to reset the right hand
893     // side vector as well as the solution vector to its correct size;
894     // remember that the solution vector is a local one, unlike the right hand
895     // side that is a distributed %parallel one and therefore needs to know
896     // the MPI communicator over which it is supposed to transmit messages:
897     system_rhs.reinit(locally_owned_dofs, mpi_communicator);
898     incremental_displacement.reinit(dof_handler.n_dofs());
899   }
900 
901 
902 
903   // @sect4{TopLevel::assemble_system}
904 
905   // Again, assembling the system matrix and right hand side follows the same
906   // structure as in many example programs before. In particular, it is mostly
907   // equivalent to step-17, except for the different right hand side that now
908   // only has to take into account internal stresses. In addition, assembling
909   // the matrix is made significantly more transparent by using the
910   // <code>SymmetricTensor</code> class: note the elegance of forming the
911   // scalar products of symmetric tensors of rank 2 and 4. The implementation
912   // is also more general since it is independent of the fact that we may or
913   // may not be using an isotropic elasticity tensor.
914   //
915   // The first part of the assembly routine is as always:
916   template <int dim>
assemble_system()917   void TopLevel<dim>::assemble_system()
918   {
919     system_rhs    = 0;
920     system_matrix = 0;
921 
922     FEValues<dim> fe_values(fe,
923                             quadrature_formula,
924                             update_values | update_gradients |
925                               update_quadrature_points | update_JxW_values);
926 
927     const unsigned int dofs_per_cell = fe.n_dofs_per_cell();
928     const unsigned int n_q_points    = quadrature_formula.size();
929 
930     FullMatrix<double> cell_matrix(dofs_per_cell, dofs_per_cell);
931     Vector<double>     cell_rhs(dofs_per_cell);
932 
933     std::vector<types::global_dof_index> local_dof_indices(dofs_per_cell);
934 
935     BodyForce<dim>              body_force;
936     std::vector<Vector<double>> body_force_values(n_q_points,
937                                                   Vector<double>(dim));
938 
939     // As in step-17, we only need to loop over all cells that belong to the
940     // present processor:
941     for (const auto &cell : dof_handler.active_cell_iterators())
942       if (cell->is_locally_owned())
943         {
944           cell_matrix = 0;
945           cell_rhs    = 0;
946 
947           fe_values.reinit(cell);
948 
949           // Then loop over all indices i,j and quadrature points and assemble
950           // the system matrix contributions from this cell.  Note how we
951           // extract the symmetric gradients (strains) of the shape functions
952           // at a given quadrature point from the <code>FEValues</code>
953           // object, and the elegance with which we form the triple
954           // contraction <code>eps_phi_i : C : eps_phi_j</code>; the latter
955           // needs to be compared to the clumsy computations needed in
956           // step-17, both in the introduction as well as in the respective
957           // place in the program:
958           for (unsigned int i = 0; i < dofs_per_cell; ++i)
959             for (unsigned int j = 0; j < dofs_per_cell; ++j)
960               for (unsigned int q_point = 0; q_point < n_q_points; ++q_point)
961                 {
962                   const SymmetricTensor<2, dim>
963                     eps_phi_i = get_strain(fe_values, i, q_point),
964                     eps_phi_j = get_strain(fe_values, j, q_point);
965 
966                   cell_matrix(i, j) += (eps_phi_i *            //
967                                         stress_strain_tensor * //
968                                         eps_phi_j              //
969                                         ) *                    //
970                                        fe_values.JxW(q_point); //
971                 }
972 
973 
974           // Then also assemble the local right hand side contributions. For
975           // this, we need to access the prior stress value in this quadrature
976           // point. To get it, we use the user pointer of this cell that
977           // points into the global array to the quadrature point data
978           // corresponding to the first quadrature point of the present cell,
979           // and then add an offset corresponding to the index of the
980           // quadrature point we presently consider:
981           const PointHistory<dim> *local_quadrature_points_data =
982             reinterpret_cast<PointHistory<dim> *>(cell->user_pointer());
983           // In addition, we need the values of the external body forces at
984           // the quadrature points on this cell:
985           body_force.vector_value_list(fe_values.get_quadrature_points(),
986                                        body_force_values);
987           // Then we can loop over all degrees of freedom on this cell and
988           // compute local contributions to the right hand side:
989           for (unsigned int i = 0; i < dofs_per_cell; ++i)
990             {
991               const unsigned int component_i =
992                 fe.system_to_component_index(i).first;
993 
994               for (unsigned int q_point = 0; q_point < n_q_points; ++q_point)
995                 {
996                   const SymmetricTensor<2, dim> &old_stress =
997                     local_quadrature_points_data[q_point].old_stress;
998 
999                   cell_rhs(i) +=
1000                     (body_force_values[q_point](component_i) *
1001                        fe_values.shape_value(i, q_point) -
1002                      old_stress * get_strain(fe_values, i, q_point)) *
1003                     fe_values.JxW(q_point);
1004                 }
1005             }
1006 
1007           // Now that we have the local contributions to the linear system, we
1008           // need to transfer it into the global objects. This is done exactly
1009           // as in step-17:
1010           cell->get_dof_indices(local_dof_indices);
1011 
1012           hanging_node_constraints.distribute_local_to_global(cell_matrix,
1013                                                               cell_rhs,
1014                                                               local_dof_indices,
1015                                                               system_matrix,
1016                                                               system_rhs);
1017         }
1018 
1019     // Now compress the vector and the system matrix:
1020     system_matrix.compress(VectorOperation::add);
1021     system_rhs.compress(VectorOperation::add);
1022 
1023 
1024     // The last step is to again fix up boundary values, just as we already
1025     // did in previous programs. A slight complication is that the
1026     // <code>apply_boundary_values</code> function wants to have a solution
1027     // vector compatible with the matrix and right hand side (i.e. here a
1028     // distributed %parallel vector, rather than the sequential vector we use
1029     // in this program) in order to preset the entries of the solution vector
1030     // with the correct boundary values. We provide such a compatible vector
1031     // in the form of a temporary vector which we then copy into the
1032     // sequential one.
1033 
1034     // We make up for this complication by showing how boundary values can be
1035     // used flexibly: following the way we create the triangulation, there are
1036     // three distinct boundary indicators used to describe the domain,
1037     // corresponding to the bottom and top faces, as well as the inner/outer
1038     // surfaces. We would like to impose boundary conditions of the following
1039     // type: The inner and outer cylinder surfaces are free of external
1040     // forces, a fact that corresponds to natural (Neumann-type) boundary
1041     // conditions for which we don't have to do anything. At the bottom, we
1042     // want no movement at all, corresponding to the cylinder being clamped or
1043     // cemented in at this part of the boundary. At the top, however, we want
1044     // a prescribed vertical downward motion compressing the cylinder; in
1045     // addition, we only want to restrict the vertical movement, but not the
1046     // horizontal ones -- one can think of this situation as a well-greased
1047     // plate sitting on top of the cylinder pushing it downwards: the atoms of
1048     // the cylinder are forced to move downward, but they are free to slide
1049     // horizontally along the plate.
1050 
1051     // The way to describe this is as follows: for boundary indicator zero
1052     // (bottom face) we use a dim-dimensional zero function representing no
1053     // motion in any coordinate direction. For the boundary with indicator 1
1054     // (top surface), we use the <code>IncrementalBoundaryValues</code> class,
1055     // but we specify an additional argument to the
1056     // <code>VectorTools::interpolate_boundary_values</code> function denoting
1057     // which vector components it should apply to; this is a vector of bools
1058     // for each vector component and because we only want to restrict vertical
1059     // motion, it has only its last component set:
1060     FEValuesExtractors::Scalar                z_component(dim - 1);
1061     std::map<types::global_dof_index, double> boundary_values;
1062     VectorTools::interpolate_boundary_values(dof_handler,
1063                                              0,
1064                                              Functions::ZeroFunction<dim>(dim),
1065                                              boundary_values);
1066     VectorTools::interpolate_boundary_values(
1067       dof_handler,
1068       1,
1069       IncrementalBoundaryValues<dim>(present_time, present_timestep),
1070       boundary_values,
1071       fe.component_mask(z_component));
1072 
1073     PETScWrappers::MPI::Vector tmp(locally_owned_dofs, mpi_communicator);
1074     MatrixTools::apply_boundary_values(
1075       boundary_values, system_matrix, tmp, system_rhs, false);
1076     incremental_displacement = tmp;
1077   }
1078 
1079 
1080 
1081   // @sect4{TopLevel::solve_timestep}
1082 
1083   // The next function is the one that controls what all has to happen within
1084   // a timestep. The order of things should be relatively self-explanatory
1085   // from the function names:
1086   template <int dim>
solve_timestep()1087   void TopLevel<dim>::solve_timestep()
1088   {
1089     pcout << "    Assembling system..." << std::flush;
1090     assemble_system();
1091     pcout << " norm of rhs is " << system_rhs.l2_norm() << std::endl;
1092 
1093     const unsigned int n_iterations = solve_linear_problem();
1094 
1095     pcout << "    Solver converged in " << n_iterations << " iterations."
1096           << std::endl;
1097 
1098     pcout << "    Updating quadrature point data..." << std::flush;
1099     update_quadrature_point_history();
1100     pcout << std::endl;
1101   }
1102 
1103 
1104 
1105   // @sect4{TopLevel::solve_linear_problem}
1106 
1107   // Solving the linear system again works mostly as before. The only
1108   // difference is that we want to only keep a complete local copy of the
1109   // solution vector instead of the distributed one that we get as output from
1110   // PETSc's solver routines. To this end, we declare a local temporary
1111   // variable for the distributed vector and initialize it with the contents
1112   // of the local variable (remember that the
1113   // <code>apply_boundary_values</code> function called in
1114   // <code>assemble_system</code> preset the values of boundary nodes in this
1115   // vector), solve with it, and at the end of the function copy it again into
1116   // the complete local vector that we declared as a member variable. Hanging
1117   // node constraints are then distributed only on the local copy,
1118   // i.e. independently of each other on each of the processors:
1119   template <int dim>
solve_linear_problem()1120   unsigned int TopLevel<dim>::solve_linear_problem()
1121   {
1122     PETScWrappers::MPI::Vector distributed_incremental_displacement(
1123       locally_owned_dofs, mpi_communicator);
1124     distributed_incremental_displacement = incremental_displacement;
1125 
1126     SolverControl solver_control(dof_handler.n_dofs(),
1127                                  1e-16 * system_rhs.l2_norm());
1128 
1129     PETScWrappers::SolverCG cg(solver_control, mpi_communicator);
1130 
1131     PETScWrappers::PreconditionBlockJacobi preconditioner(system_matrix);
1132 
1133     cg.solve(system_matrix,
1134              distributed_incremental_displacement,
1135              system_rhs,
1136              preconditioner);
1137 
1138     incremental_displacement = distributed_incremental_displacement;
1139 
1140     hanging_node_constraints.distribute(incremental_displacement);
1141 
1142     return solver_control.last_step();
1143   }
1144 
1145 
1146 
1147   // @sect4{TopLevel::output_results}
1148 
1149   // This function generates the graphical output in .vtu format as explained
1150   // in the introduction. Each process will only work on the cells it owns,
1151   // and then write the result into a file of its own. Additionally, processor
1152   // 0 will write the record files the reference all the .vtu files.
1153   //
1154   // The crucial part of this function is to give the <code>DataOut</code>
1155   // class a way to only work on the cells that the present process owns.
1156 
1157   template <int dim>
output_results() const1158   void TopLevel<dim>::output_results() const
1159   {
1160     DataOut<dim> data_out;
1161     data_out.attach_dof_handler(dof_handler);
1162 
1163     // Then, just as in step-17, define the names of solution variables (which
1164     // here are the displacement increments) and queue the solution vector for
1165     // output. Note in the following switch how we make sure that if the space
1166     // dimension should be unhandled that we throw an exception saying that we
1167     // haven't implemented this case yet (another case of defensive
1168     // programming):
1169     std::vector<std::string> solution_names;
1170     switch (dim)
1171       {
1172         case 1:
1173           solution_names.emplace_back("delta_x");
1174           break;
1175         case 2:
1176           solution_names.emplace_back("delta_x");
1177           solution_names.emplace_back("delta_y");
1178           break;
1179         case 3:
1180           solution_names.emplace_back("delta_x");
1181           solution_names.emplace_back("delta_y");
1182           solution_names.emplace_back("delta_z");
1183           break;
1184         default:
1185           Assert(false, ExcNotImplemented());
1186       }
1187 
1188     data_out.add_data_vector(incremental_displacement, solution_names);
1189 
1190 
1191     // The next thing is that we wanted to output something like the average
1192     // norm of the stresses that we have stored in each cell. This may seem
1193     // complicated, since on the present processor we only store the stresses
1194     // in quadrature points on those cells that actually belong to the present
1195     // process. In other words, it seems as if we can't compute the average
1196     // stresses for all cells. However, remember that our class derived from
1197     // <code>DataOut</code> only iterates over those cells that actually do
1198     // belong to the present processor, i.e. we don't have to compute anything
1199     // for all the other cells as this information would not be touched. The
1200     // following little loop does this. We enclose the entire block into a
1201     // pair of braces to make sure that the iterator variables do not remain
1202     // accidentally visible beyond the end of the block in which they are
1203     // used:
1204     Vector<double> norm_of_stress(triangulation.n_active_cells());
1205     {
1206       // Loop over all the cells...
1207       for (auto &cell : triangulation.active_cell_iterators())
1208         if (cell->is_locally_owned())
1209           {
1210             // On these cells, add up the stresses over all quadrature
1211             // points...
1212             SymmetricTensor<2, dim> accumulated_stress;
1213             for (unsigned int q = 0; q < quadrature_formula.size(); ++q)
1214               accumulated_stress +=
1215                 reinterpret_cast<PointHistory<dim> *>(cell->user_pointer())[q]
1216                   .old_stress;
1217 
1218             // ...then write the norm of the average to their destination:
1219             norm_of_stress(cell->active_cell_index()) =
1220               (accumulated_stress / quadrature_formula.size()).norm();
1221           }
1222         // And on the cells that we are not interested in, set the respective
1223         // value in the vector to a bogus value (norms must be positive, and a
1224         // large negative value should catch your eye) in order to make sure
1225         // that if we were somehow wrong about our assumption that these
1226         // elements would not appear in the output file, that we would find out
1227         // by looking at the graphical output:
1228         else
1229           norm_of_stress(cell->active_cell_index()) = -1e+20;
1230     }
1231     // Finally attach this vector as well to be treated for output:
1232     data_out.add_data_vector(norm_of_stress, "norm_of_stress");
1233 
1234     // As a last piece of data, let us also add the partitioning of the domain
1235     // into subdomains associated with the processors if this is a parallel
1236     // job. This works in the exact same way as in the step-17 program:
1237     std::vector<types::subdomain_id> partition_int(
1238       triangulation.n_active_cells());
1239     GridTools::get_subdomain_association(triangulation, partition_int);
1240     const Vector<double> partitioning(partition_int.begin(),
1241                                       partition_int.end());
1242     data_out.add_data_vector(partitioning, "partitioning");
1243 
1244     // Finally, with all this data, we can instruct deal.II to munge the
1245     // information and produce some intermediate data structures that contain
1246     // all these solution and other data vectors:
1247     data_out.build_patches();
1248 
1249     // Let us call a function that opens the necessary output files and writes
1250     // the data we have generated into them. The function automatically
1251     // constructs the file names from the given directory name (the first
1252     // argument) and file name base (second argument). It augments the resulting
1253     // string by pieces that result from the time step number and a "piece
1254     // number" that corresponds to a part of the overall domain that can consist
1255     // of one or more subdomains.
1256     //
1257     // The function also writes a record files (with suffix `.pvd`) for Paraview
1258     // that describes how all of these output files combine into the data for
1259     // this single time step:
1260     const std::string pvtu_filename = data_out.write_vtu_with_pvtu_record(
1261       "./", "solution", timestep_no, mpi_communicator, 4);
1262 
1263     // The record files must be written only once and not by each processor,
1264     // so we do this on processor 0:
1265     if (this_mpi_process == 0)
1266       {
1267         // Finally, we write the paraview record, that references all .pvtu
1268         // files and their respective time. Note that the variable
1269         // times_and_names is declared static, so it will retain the entries
1270         // from the previous timesteps.
1271         static std::vector<std::pair<double, std::string>> times_and_names;
1272         times_and_names.push_back(
1273           std::pair<double, std::string>(present_time, pvtu_filename));
1274         std::ofstream pvd_output("solution.pvd");
1275         DataOutBase::write_pvd_record(pvd_output, times_and_names);
1276       }
1277   }
1278 
1279 
1280 
1281   // @sect4{TopLevel::do_initial_timestep}
1282 
1283   // This and the next function handle the overall structure of the first and
1284   // following timesteps, respectively. The first timestep is slightly more
1285   // involved because we want to compute it multiple times on successively
1286   // refined meshes, each time starting from a clean state. At the end of
1287   // these computations, in which we compute the incremental displacements
1288   // each time, we use the last results obtained for the incremental
1289   // displacements to compute the resulting stress updates and move the mesh
1290   // accordingly. On this new mesh, we then output the solution and any
1291   // additional data we consider important.
1292   //
1293   // All this is interspersed by generating output to the console to update
1294   // the person watching the screen on what is going on. As in step-17, the
1295   // use of <code>pcout</code> instead of <code>std::cout</code> makes sure
1296   // that only one of the parallel processes is actually writing to the
1297   // console, without having to explicitly code an if-statement in each place
1298   // where we generate output:
1299   template <int dim>
do_initial_timestep()1300   void TopLevel<dim>::do_initial_timestep()
1301   {
1302     present_time += present_timestep;
1303     ++timestep_no;
1304     pcout << "Timestep " << timestep_no << " at time " << present_time
1305           << std::endl;
1306 
1307     for (unsigned int cycle = 0; cycle < 2; ++cycle)
1308       {
1309         pcout << "  Cycle " << cycle << ':' << std::endl;
1310 
1311         if (cycle == 0)
1312           create_coarse_grid();
1313         else
1314           refine_initial_grid();
1315 
1316         pcout << "    Number of active cells:       "
1317               << triangulation.n_active_cells() << " (by partition:";
1318         for (unsigned int p = 0; p < n_mpi_processes; ++p)
1319           pcout << (p == 0 ? ' ' : '+')
1320                 << (GridTools::count_cells_with_subdomain_association(
1321                      triangulation, p));
1322         pcout << ")" << std::endl;
1323 
1324         setup_system();
1325 
1326         pcout << "    Number of degrees of freedom: " << dof_handler.n_dofs()
1327               << " (by partition:";
1328         for (unsigned int p = 0; p < n_mpi_processes; ++p)
1329           pcout << (p == 0 ? ' ' : '+')
1330                 << (DoFTools::count_dofs_with_subdomain_association(dof_handler,
1331                                                                     p));
1332         pcout << ")" << std::endl;
1333 
1334         solve_timestep();
1335       }
1336 
1337     move_mesh();
1338     output_results();
1339 
1340     pcout << std::endl;
1341   }
1342 
1343 
1344 
1345   // @sect4{TopLevel::do_timestep}
1346 
1347   // Subsequent timesteps are simpler, and probably do not require any more
1348   // documentation given the explanations for the previous function above:
1349   template <int dim>
do_timestep()1350   void TopLevel<dim>::do_timestep()
1351   {
1352     present_time += present_timestep;
1353     ++timestep_no;
1354     pcout << "Timestep " << timestep_no << " at time " << present_time
1355           << std::endl;
1356     if (present_time > end_time)
1357       {
1358         present_timestep -= (present_time - end_time);
1359         present_time = end_time;
1360       }
1361 
1362 
1363     solve_timestep();
1364 
1365     move_mesh();
1366     output_results();
1367 
1368     pcout << std::endl;
1369   }
1370 
1371 
1372   // @sect4{TopLevel::refine_initial_grid}
1373 
1374   // The following function is called when solving the first time step on
1375   // successively refined meshes. After each iteration, it computes a
1376   // refinement criterion, refines the mesh, and sets up the history variables
1377   // in each quadrature point again to a clean state.
1378   template <int dim>
refine_initial_grid()1379   void TopLevel<dim>::refine_initial_grid()
1380   {
1381     // First, let each process compute error indicators for the cells it owns:
1382     Vector<float> error_per_cell(triangulation.n_active_cells());
1383     KellyErrorEstimator<dim>::estimate(
1384       dof_handler,
1385       QGauss<dim - 1>(fe.degree + 1),
1386       std::map<types::boundary_id, const Function<dim> *>(),
1387       incremental_displacement,
1388       error_per_cell,
1389       ComponentMask(),
1390       nullptr,
1391       MultithreadInfo::n_threads(),
1392       this_mpi_process);
1393 
1394     // Then set up a global vector into which we merge the local indicators
1395     // from each of the %parallel processes:
1396     const unsigned int n_local_cells =
1397       triangulation.n_locally_owned_active_cells();
1398 
1399     PETScWrappers::MPI::Vector distributed_error_per_cell(
1400       mpi_communicator, triangulation.n_active_cells(), n_local_cells);
1401 
1402     for (unsigned int i = 0; i < error_per_cell.size(); ++i)
1403       if (error_per_cell(i) != 0)
1404         distributed_error_per_cell(i) = error_per_cell(i);
1405     distributed_error_per_cell.compress(VectorOperation::insert);
1406 
1407     // Once we have that, copy it back into local copies on all processors and
1408     // refine the mesh accordingly:
1409     error_per_cell = distributed_error_per_cell;
1410     GridRefinement::refine_and_coarsen_fixed_number(triangulation,
1411                                                     error_per_cell,
1412                                                     0.35,
1413                                                     0.03);
1414     triangulation.execute_coarsening_and_refinement();
1415 
1416     // Finally, set up quadrature point data again on the new mesh, and only
1417     // on those cells that we have determined to be ours:
1418     setup_quadrature_point_history();
1419   }
1420 
1421 
1422 
1423   // @sect4{TopLevel::move_mesh}
1424 
1425   // At the end of each time step, we move the nodes of the mesh according to
1426   // the incremental displacements computed in this time step. To do this, we
1427   // keep a vector of flags that indicate for each vertex whether we have
1428   // already moved it around, and then loop over all cells and move those
1429   // vertices of the cell that have not been moved yet. It is worth noting
1430   // that it does not matter from which of the cells adjacent to a vertex we
1431   // move this vertex: since we compute the displacement using a continuous
1432   // finite element, the displacement field is continuous as well and we can
1433   // compute the displacement of a given vertex from each of the adjacent
1434   // cells. We only have to make sure that we move each node exactly once,
1435   // which is why we keep the vector of flags.
1436   //
1437   // There are two noteworthy things in this function. First, how we get the
1438   // displacement field at a given vertex using the
1439   // <code>cell-@>vertex_dof_index(v,d)</code> function that returns the index
1440   // of the <code>d</code>th degree of freedom at vertex <code>v</code> of the
1441   // given cell. In the present case, displacement in the k-th coordinate
1442   // direction corresponds to the k-th component of the finite element. Using a
1443   // function like this bears a certain risk, because it uses knowledge of the
1444   // order of elements that we have taken together for this program in the
1445   // <code>FESystem</code> element. If we decided to add an additional
1446   // variable, for example a pressure variable for stabilization, and happened
1447   // to insert it as the first variable of the element, then the computation
1448   // below will start to produce nonsensical results. In addition, this
1449   // computation rests on other assumptions: first, that the element we use
1450   // has, indeed, degrees of freedom that are associated with vertices. This
1451   // is indeed the case for the present Q1 element, as would be for all Qp
1452   // elements of polynomial order <code>p</code>. However, it would not hold
1453   // for discontinuous elements, or elements for mixed formulations. Secondly,
1454   // it also rests on the assumption that the displacement at a vertex is
1455   // determined solely by the value of the degree of freedom associated with
1456   // this vertex; in other words, all shape functions corresponding to other
1457   // degrees of freedom are zero at this particular vertex. Again, this is the
1458   // case for the present element, but is not so for all elements that are
1459   // presently available in deal.II. Despite its risks, we choose to use this
1460   // way in order to present a way to query individual degrees of freedom
1461   // associated with vertices.
1462   //
1463   // In this context, it is instructive to point out what a more general way
1464   // would be. For general finite elements, the way to go would be to take a
1465   // quadrature formula with the quadrature points in the vertices of a
1466   // cell. The <code>QTrapezoid</code> formula for the trapezoidal rule does
1467   // exactly this. With this quadrature formula, we would then initialize an
1468   // <code>FEValues</code> object in each cell, and use the
1469   // <code>FEValues::get_function_values</code> function to obtain the values
1470   // of the solution function in the quadrature points, i.e. the vertices of
1471   // the cell. These are the only values that we really need, i.e. we are not
1472   // at all interested in the weights (or the <code>JxW</code> values)
1473   // associated with this particular quadrature formula, and this can be
1474   // specified as the last argument in the constructor to
1475   // <code>FEValues</code>. The only point of minor inconvenience in this
1476   // scheme is that we have to figure out which quadrature point corresponds
1477   // to the vertex we consider at present, as they may or may not be ordered
1478   // in the same order.
1479   //
1480   // This inconvenience could be avoided if finite elements have support
1481   // points on vertices (which the one here has; for the concept of support
1482   // points, see @ref GlossSupport "support points"). For such a case, one
1483   // could construct a custom quadrature rule using
1484   // FiniteElement::get_unit_support_points(). The first
1485   // <code>cell-&gt;n_vertices()*fe.dofs_per_vertex</code>
1486   // quadrature points will then correspond to the vertices of the cell and
1487   // are ordered consistent with <code>cell-@>vertex(i)</code>, taking into
1488   // account that support points for vector elements will be duplicated
1489   // <code>fe.dofs_per_vertex</code> times.
1490   //
1491   // Another point worth explaining about this short function is the way in
1492   // which the triangulation class exports information about its vertices:
1493   // through the <code>Triangulation::n_vertices</code> function, it
1494   // advertises how many vertices there are in the triangulation. Not all of
1495   // them are actually in use all the time -- some are left-overs from cells
1496   // that have been coarsened previously and remain in existence since deal.II
1497   // never changes the number of a vertex once it has come into existence,
1498   // even if vertices with lower number go away. Secondly, the location
1499   // returned by <code>cell-@>vertex(v)</code> is not only a read-only object
1500   // of type <code>Point@<dim@></code>, but in fact a reference that can be
1501   // written to. This allows to move around the nodes of a mesh with relative
1502   // ease, but it is worth pointing out that it is the responsibility of an
1503   // application program using this feature to make sure that the resulting
1504   // cells are still useful, i.e. are not distorted so much that the cell is
1505   // degenerated (indicated, for example, by negative Jacobians). Note that we
1506   // do not have any provisions in this function to actually ensure this, we
1507   // just have faith.
1508   //
1509   // After this lengthy introduction, here are the full 20 or so lines of
1510   // code:
1511   template <int dim>
move_mesh()1512   void TopLevel<dim>::move_mesh()
1513   {
1514     pcout << "    Moving mesh..." << std::endl;
1515 
1516     std::vector<bool> vertex_touched(triangulation.n_vertices(), false);
1517     for (auto &cell : dof_handler.active_cell_iterators())
1518       for (const auto v : cell->vertex_indices())
1519         if (vertex_touched[cell->vertex_index(v)] == false)
1520           {
1521             vertex_touched[cell->vertex_index(v)] = true;
1522 
1523             Point<dim> vertex_displacement;
1524             for (unsigned int d = 0; d < dim; ++d)
1525               vertex_displacement[d] =
1526                 incremental_displacement(cell->vertex_dof_index(v, d));
1527 
1528             cell->vertex(v) += vertex_displacement;
1529           }
1530   }
1531 
1532 
1533   // @sect4{TopLevel::setup_quadrature_point_history}
1534 
1535   // At the beginning of our computations, we needed to set up initial values
1536   // of the history variables, such as the existing stresses in the material,
1537   // that we store in each quadrature point. As mentioned above, we use the
1538   // <code>user_pointer</code> for this that is available in each cell.
1539   //
1540   // To put this into larger perspective, we note that if we had previously
1541   // available stresses in our model (which we assume do not exist for the
1542   // purpose of this program), then we would need to interpolate the field of
1543   // preexisting stresses to the quadrature points. Likewise, if we were to
1544   // simulate elasto-plastic materials with hardening/softening, then we would
1545   // have to store additional history variables like the present yield stress
1546   // of the accumulated plastic strains in each quadrature
1547   // points. Pre-existing hardening or weakening would then be implemented by
1548   // interpolating these variables in the present function as well.
1549   template <int dim>
setup_quadrature_point_history()1550   void TopLevel<dim>::setup_quadrature_point_history()
1551   {
1552     // For good measure, we set all user pointers of all cells, whether
1553     // ours of not, to the null pointer. This way, if we ever access the user
1554     // pointer of a cell which we should not have accessed, a segmentation
1555     // fault will let us know that this should not have happened:
1556 
1557     triangulation.clear_user_data();
1558 
1559     // Next, allocate the quadrature objects that are within the responsibility
1560     // of this processor. This, of course, equals the number of cells that
1561     // belong to this processor times the number of quadrature points our
1562     // quadrature formula has on each cell. Since the `resize()` function does
1563     // not actually shrink the amount of allocated memory if the requested new
1564     // size is smaller than the old size, we resort to a trick to first free all
1565     // memory, and then reallocate it: we declare an empty vector as a temporary
1566     // variable and then swap the contents of the old vector and this temporary
1567     // variable. This makes sure that the `quadrature_point_history` is now
1568     // really empty, and we can let the temporary variable that now holds the
1569     // previous contents of the vector go out of scope and be destroyed. In the
1570     // next step we can then re-allocate as many elements as we need, with the
1571     // vector default-initializing the `PointHistory` objects, which includes
1572     // setting the stress variables to zero.
1573     {
1574       std::vector<PointHistory<dim>> tmp;
1575       quadrature_point_history.swap(tmp);
1576     }
1577     quadrature_point_history.resize(
1578       triangulation.n_locally_owned_active_cells() * quadrature_formula.size());
1579 
1580     // Finally loop over all cells again and set the user pointers from the
1581     // cells that belong to the present processor to point to the first
1582     // quadrature point objects corresponding to this cell in the vector of
1583     // such objects:
1584     unsigned int history_index = 0;
1585     for (auto &cell : triangulation.active_cell_iterators())
1586       if (cell->is_locally_owned())
1587         {
1588           cell->set_user_pointer(&quadrature_point_history[history_index]);
1589           history_index += quadrature_formula.size();
1590         }
1591 
1592     // At the end, for good measure make sure that our count of elements was
1593     // correct and that we have both used up all objects we allocated
1594     // previously, and not point to any objects beyond the end of the
1595     // vector. Such defensive programming strategies are always good checks to
1596     // avoid accidental errors and to guard against future changes to this
1597     // function that forget to update all uses of a variable at the same
1598     // time. Recall that constructs using the <code>Assert</code> macro are
1599     // optimized away in optimized mode, so do not affect the run time of
1600     // optimized runs:
1601     Assert(history_index == quadrature_point_history.size(),
1602            ExcInternalError());
1603   }
1604 
1605 
1606 
1607   // @sect4{TopLevel::update_quadrature_point_history}
1608 
1609   // At the end of each time step, we should have computed an incremental
1610   // displacement update so that the material in its new configuration
1611   // accommodates for the difference between the external body and boundary
1612   // forces applied during this time step minus the forces exerted through
1613   // preexisting internal stresses. In order to have the preexisting
1614   // stresses available at the next time step, we therefore have to update the
1615   // preexisting stresses with the stresses due to the incremental
1616   // displacement computed during the present time step. Ideally, the
1617   // resulting sum of internal stresses would exactly counter all external
1618   // forces. Indeed, a simple experiment can make sure that this is so: if we
1619   // choose boundary conditions and body forces to be time independent, then
1620   // the forcing terms (the sum of external forces and internal stresses)
1621   // should be exactly zero. If you make this experiment, you will realize
1622   // from the output of the norm of the right hand side in each time step that
1623   // this is almost the case: it is not exactly zero, since in the first time
1624   // step the incremental displacement and stress updates were computed
1625   // relative to the undeformed mesh, which was then deformed. In the second
1626   // time step, we again compute displacement and stress updates, but this
1627   // time in the deformed mesh -- there, the resulting updates are very small
1628   // but not quite zero. This can be iterated, and in each such iteration the
1629   // residual, i.e. the norm of the right hand side vector, is reduced; if one
1630   // makes this little experiment, one realizes that the norm of this residual
1631   // decays exponentially with the number of iterations, and after an initial
1632   // very rapid decline is reduced by roughly a factor of about 3.5 in each
1633   // iteration (for one testcase I looked at, other testcases, and other
1634   // numbers of unknowns change the factor, but not the exponential decay).
1635 
1636   // In a sense, this can then be considered as a quasi-timestepping scheme to
1637   // resolve the nonlinear problem of solving large-deformation elasticity on
1638   // a mesh that is moved along in a Lagrangian manner.
1639   //
1640   // Another complication is that the existing (old) stresses are defined on
1641   // the old mesh, which we will move around after updating the stresses. If
1642   // this mesh update involves rotations of the cell, then we need to also
1643   // rotate the updated stress, since it was computed relative to the
1644   // coordinate system of the old cell.
1645   //
1646   // Thus, what we need is the following: on each cell which the present
1647   // processor owns, we need to extract the old stress from the data stored
1648   // with each quadrature point, compute the stress update, add the two
1649   // together, and then rotate the result together with the incremental
1650   // rotation computed from the incremental displacement at the present
1651   // quadrature point. We will detail these steps below:
1652   template <int dim>
update_quadrature_point_history()1653   void TopLevel<dim>::update_quadrature_point_history()
1654   {
1655     // First, set up an <code>FEValues</code> object by which we will evaluate
1656     // the incremental displacements and the gradients thereof at the
1657     // quadrature points, together with a vector that will hold this
1658     // information:
1659     FEValues<dim> fe_values(fe,
1660                             quadrature_formula,
1661                             update_values | update_gradients);
1662 
1663     std::vector<std::vector<Tensor<1, dim>>> displacement_increment_grads(
1664       quadrature_formula.size(), std::vector<Tensor<1, dim>>(dim));
1665 
1666     // Then loop over all cells and do the job in the cells that belong to our
1667     // subdomain:
1668     for (auto &cell : dof_handler.active_cell_iterators())
1669       if (cell->is_locally_owned())
1670         {
1671           // Next, get a pointer to the quadrature point history data local to
1672           // the present cell, and, as a defensive measure, make sure that
1673           // this pointer is within the bounds of the global array:
1674           PointHistory<dim> *local_quadrature_points_history =
1675             reinterpret_cast<PointHistory<dim> *>(cell->user_pointer());
1676           Assert(local_quadrature_points_history >=
1677                    &quadrature_point_history.front(),
1678                  ExcInternalError());
1679           Assert(local_quadrature_points_history <=
1680                    &quadrature_point_history.back(),
1681                  ExcInternalError());
1682 
1683           // Then initialize the <code>FEValues</code> object on the present
1684           // cell, and extract the gradients of the displacement at the
1685           // quadrature points for later computation of the strains
1686           fe_values.reinit(cell);
1687           fe_values.get_function_gradients(incremental_displacement,
1688                                            displacement_increment_grads);
1689 
1690           // Then loop over the quadrature points of this cell:
1691           for (unsigned int q = 0; q < quadrature_formula.size(); ++q)
1692             {
1693               // On each quadrature point, compute the strain increment from
1694               // the gradients, and multiply it by the stress-strain tensor to
1695               // get the stress update. Then add this update to the already
1696               // existing strain at this point:
1697               const SymmetricTensor<2, dim> new_stress =
1698                 (local_quadrature_points_history[q].old_stress +
1699                  (stress_strain_tensor *
1700                   get_strain(displacement_increment_grads[q])));
1701 
1702               // Finally, we have to rotate the result. For this, we first
1703               // have to compute a rotation matrix at the present quadrature
1704               // point from the incremental displacements. In fact, it can be
1705               // computed from the gradients, and we already have a function
1706               // for that purpose:
1707               const Tensor<2, dim> rotation =
1708                 get_rotation_matrix(displacement_increment_grads[q]);
1709               // Note that the result, a rotation matrix, is in general an
1710               // antisymmetric tensor of rank 2, so we must store it as a full
1711               // tensor.
1712 
1713               // With this rotation matrix, we can compute the rotated tensor
1714               // by contraction from the left and right, after we expand the
1715               // symmetric tensor <code>new_stress</code> into a full tensor:
1716               const SymmetricTensor<2, dim> rotated_new_stress =
1717                 symmetrize(transpose(rotation) *
1718                            static_cast<Tensor<2, dim>>(new_stress) * rotation);
1719               // Note that while the result of the multiplication of these
1720               // three matrices should be symmetric, it is not due to floating
1721               // point round off: we get an asymmetry on the order of 1e-16 of
1722               // the off-diagonal elements of the result. When assigning the
1723               // result to a <code>SymmetricTensor</code>, the constructor of
1724               // that class checks the symmetry and realizes that it isn't
1725               // exactly symmetric; it will then raise an exception. To avoid
1726               // that, we explicitly symmetrize the result to make it exactly
1727               // symmetric.
1728 
1729               // The result of all these operations is then written back into
1730               // the original place:
1731               local_quadrature_points_history[q].old_stress =
1732                 rotated_new_stress;
1733             }
1734         }
1735   }
1736 
1737   // This ends the project specific namespace <code>Step18</code>. The rest is
1738   // as usual and as already shown in step-17: A <code>main()</code> function
1739   // that initializes and terminates PETSc, calls the classes that do the
1740   // actual work, and makes sure that we catch all exceptions that propagate
1741   // up to this point:
1742 } // namespace Step18
1743 
1744 
main(int argc,char ** argv)1745 int main(int argc, char **argv)
1746 {
1747   try
1748     {
1749       using namespace dealii;
1750       using namespace Step18;
1751 
1752       Utilities::MPI::MPI_InitFinalize mpi_initialization(argc, argv, 1);
1753 
1754       TopLevel<3> elastic_problem;
1755       elastic_problem.run();
1756     }
1757   catch (std::exception &exc)
1758     {
1759       std::cerr << std::endl
1760                 << std::endl
1761                 << "----------------------------------------------------"
1762                 << std::endl;
1763       std::cerr << "Exception on processing: " << std::endl
1764                 << exc.what() << std::endl
1765                 << "Aborting!" << std::endl
1766                 << "----------------------------------------------------"
1767                 << std::endl;
1768 
1769       return 1;
1770     }
1771   catch (...)
1772     {
1773       std::cerr << std::endl
1774                 << std::endl
1775                 << "----------------------------------------------------"
1776                 << std::endl;
1777       std::cerr << "Unknown exception!" << std::endl
1778                 << "Aborting!" << std::endl
1779                 << "----------------------------------------------------"
1780                 << std::endl;
1781       return 1;
1782     }
1783 
1784   return 0;
1785 }
1786