1 /* ---------------------------------------------------------------------
2  *
3  * Copyright (C) 2012 - 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  * Authors: Joerg Frohne, Texas A&M University and
18  *                        University of Siegen, 2012, 2013
19  *          Wolfgang Bangerth, Texas A&M University, 2012, 2013
20  *          Timo Heister, Texas A&M University, 2013
21  */
22 
23 // @sect3{Include files}
24 // The set of include files is not much of a surprise any more at this time:
25 #include <deal.II/base/conditional_ostream.h>
26 #include <deal.II/base/parameter_handler.h>
27 #include <deal.II/base/utilities.h>
28 #include <deal.II/base/index_set.h>
29 #include <deal.II/base/quadrature_lib.h>
30 #include <deal.II/base/function.h>
31 #include <deal.II/base/timer.h>
32 
33 #include <deal.II/lac/vector.h>
34 #include <deal.II/lac/full_matrix.h>
35 #include <deal.II/lac/sparsity_tools.h>
36 #include <deal.II/lac/sparse_matrix.h>
37 #include <deal.II/lac/block_sparsity_pattern.h>
38 #include <deal.II/lac/solver_bicgstab.h>
39 #include <deal.II/lac/precondition.h>
40 #include <deal.II/lac/affine_constraints.h>
41 #include <deal.II/lac/trilinos_sparse_matrix.h>
42 #include <deal.II/lac/trilinos_block_sparse_matrix.h>
43 #include <deal.II/lac/trilinos_vector.h>
44 #include <deal.II/lac/trilinos_parallel_block_vector.h>
45 #include <deal.II/lac/trilinos_precondition.h>
46 #include <deal.II/lac/trilinos_solver.h>
47 
48 #include <deal.II/grid/tria.h>
49 #include <deal.II/grid/grid_generator.h>
50 #include <deal.II/grid/grid_tools.h>
51 #include <deal.II/grid/tria_accessor.h>
52 #include <deal.II/grid/tria_iterator.h>
53 #include <deal.II/grid/manifold_lib.h>
54 
55 #include <deal.II/distributed/tria.h>
56 #include <deal.II/distributed/grid_refinement.h>
57 #include <deal.II/distributed/solution_transfer.h>
58 
59 #include <deal.II/dofs/dof_handler.h>
60 #include <deal.II/dofs/dof_accessor.h>
61 #include <deal.II/dofs/dof_renumbering.h>
62 #include <deal.II/dofs/dof_tools.h>
63 
64 #include <deal.II/fe/fe_q.h>
65 #include <deal.II/fe/fe_system.h>
66 #include <deal.II/fe/fe_values.h>
67 
68 #include <deal.II/numerics/vector_tools.h>
69 #include <deal.II/numerics/matrix_tools.h>
70 #include <deal.II/numerics/data_out.h>
71 #include <deal.II/numerics/error_estimator.h>
72 #include <deal.II/numerics/fe_field_function.h>
73 
74 #include <fstream>
75 #include <iostream>
76 
77 // Finally, we include two system headers that let us create a directory for
78 // output files. The first header provides the <code>mkdir</code> function and
79 // the second lets us determine what happened if <code>mkdir</code> fails.
80 #include <sys/stat.h>
81 #include <cerrno>
82 
83 namespace Step42
84 {
85   using namespace dealii;
86 
87   // @sect3{The <code>ConstitutiveLaw</code> class template}
88 
89   // This class provides an interface for a constitutive law, i.e., for the
90   // relationship between strain $\varepsilon(\mathbf u)$ and stress
91   // $\sigma$. In this example we are using an elastoplastic material behavior
92   // with linear, isotropic hardening. Such materials are characterized by
93   // Young's modulus $E$, Poisson's ratio $\nu$, the initial yield stress
94   // $\sigma_0$ and the isotropic hardening parameter $\gamma$.  For $\gamma =
95   // 0$ we obtain perfect elastoplastic behavior.
96   //
97   // As explained in the paper that describes this program, the first Newton
98   // steps are solved with a completely elastic material model to avoid having
99   // to deal with both nonlinearities (plasticity and contact) at once. To this
100   // end, this class has a function <code>set_sigma_0()</code> that we use later
101   // on to simply set $\sigma_0$ to a very large value -- essentially
102   // guaranteeing that the actual stress will not exceed it, and thereby
103   // producing an elastic material. When we are ready to use a plastic model, we
104   // set $\sigma_0$ back to its proper value, using the same function.  As a
105   // result of this approach, we need to leave <code>sigma_0</code> as the only
106   // non-const member variable of this class.
107   template <int dim>
108   class ConstitutiveLaw
109   {
110   public:
111     ConstitutiveLaw(const double E,
112                     const double nu,
113                     const double sigma_0,
114                     const double gamma);
115 
116     void set_sigma_0(double sigma_zero);
117 
118     bool get_stress_strain_tensor(
119       const SymmetricTensor<2, dim> &strain_tensor,
120       SymmetricTensor<4, dim> &      stress_strain_tensor) const;
121 
122     void get_linearized_stress_strain_tensors(
123       const SymmetricTensor<2, dim> &strain_tensor,
124       SymmetricTensor<4, dim> &      stress_strain_tensor_linearized,
125       SymmetricTensor<4, dim> &      stress_strain_tensor) const;
126 
127   private:
128     const double kappa;
129     const double mu;
130     double       sigma_0;
131     const double gamma;
132 
133     const SymmetricTensor<4, dim> stress_strain_tensor_kappa;
134     const SymmetricTensor<4, dim> stress_strain_tensor_mu;
135   };
136 
137   // The constructor of the ConstitutiveLaw class sets the required material
138   // parameter for our deformable body. Material parameters for elastic
139   // isotropic media can be defined in a variety of ways, such as the pair $E,
140   // \nu$ (elastic modulus and Poisson's number), using the Lam&eacute;
141   // parameters
142   // $\lambda,mu$ or several other commonly used conventions. Here, the
143   // constructor takes a description of material parameters in the form of
144   // $E,\nu$, but since this turns out to these are not the coefficients that
145   // appear in the equations of the plastic projector, we immediately convert
146   // them into the more suitable set $\kappa,\mu$ of bulk and shear moduli.  In
147   // addition, the constructor takes $\sigma_0$ (the yield stress absent any
148   // plastic strain) and $\gamma$ (the hardening parameter) as arguments. In
149   // this constructor, we also compute the two principal components of the
150   // stress-strain relation and its linearization.
151   template <int dim>
ConstitutiveLaw(double E,double nu,double sigma_0,double gamma)152   ConstitutiveLaw<dim>::ConstitutiveLaw(double E,
153                                         double nu,
154                                         double sigma_0,
155                                         double gamma)
156     : kappa(E / (3 * (1 - 2 * nu)))
157     , mu(E / (2 * (1 + nu)))
158     , sigma_0(sigma_0)
159     , gamma(gamma)
160     , stress_strain_tensor_kappa(kappa *
161                                  outer_product(unit_symmetric_tensor<dim>(),
162                                                unit_symmetric_tensor<dim>()))
163     , stress_strain_tensor_mu(
164         2 * mu *
165         (identity_tensor<dim>() - outer_product(unit_symmetric_tensor<dim>(),
166                                                 unit_symmetric_tensor<dim>()) /
167                                     3.0))
168   {}
169 
170 
171   template <int dim>
set_sigma_0(double sigma_zero)172   void ConstitutiveLaw<dim>::set_sigma_0(double sigma_zero)
173   {
174     sigma_0 = sigma_zero;
175   }
176 
177 
178   // @sect4{ConstitutiveLaw::get_stress_strain_tensor}
179 
180   // This is the principal component of the constitutive law. It
181   // computes the fourth order symmetric tensor that relates the
182   // strain to the stress according to the projection given above,
183   // when evaluated at a particular strain point. We need this
184   // function to calculate the nonlinear residual in
185   // <code>PlasticityContactProblem::residual_nl_system()</code> where
186   // we multiply this tensor with the strain given in a quadrature
187   // point. The computations follow the formulas laid out in the
188   // introduction. In comparing the formulas there with the
189   // implementation below, recall that $C_\mu : \varepsilon = \tau_D$
190   // and that $C_\kappa : \varepsilon = \kappa
191   // \text{trace}(\varepsilon) I = \frac 13 \text{trace}(\tau) I$.
192   //
193   // The function returns whether the quadrature point is plastic to allow for
194   // some statistics downstream on how many of the quadrature points are
195   // plastic and how many are elastic.
196   template <int dim>
get_stress_strain_tensor(const SymmetricTensor<2,dim> & strain_tensor,SymmetricTensor<4,dim> & stress_strain_tensor) const197   bool ConstitutiveLaw<dim>::get_stress_strain_tensor(
198     const SymmetricTensor<2, dim> &strain_tensor,
199     SymmetricTensor<4, dim> &      stress_strain_tensor) const
200   {
201     Assert(dim == 3, ExcNotImplemented());
202 
203     SymmetricTensor<2, dim> stress_tensor;
204     stress_tensor =
205       (stress_strain_tensor_kappa + stress_strain_tensor_mu) * strain_tensor;
206 
207     const SymmetricTensor<2, dim> deviator_stress_tensor =
208       deviator(stress_tensor);
209     const double deviator_stress_tensor_norm = deviator_stress_tensor.norm();
210 
211     stress_strain_tensor = stress_strain_tensor_mu;
212     if (deviator_stress_tensor_norm > sigma_0)
213       {
214         const double beta = sigma_0 / deviator_stress_tensor_norm;
215         stress_strain_tensor *= (gamma + (1 - gamma) * beta);
216       }
217 
218     stress_strain_tensor += stress_strain_tensor_kappa;
219 
220     return (deviator_stress_tensor_norm > sigma_0);
221   }
222 
223 
224   // @sect4{ConstitutiveLaw::get_linearized_stress_strain_tensors}
225 
226   // This function returns the linearized stress strain tensor, linearized
227   // around the solution $u^{i-1}$ of the previous Newton step $i-1$.  The
228   // parameter <code>strain_tensor</code> (commonly denoted
229   // $\varepsilon(u^{i-1})$) must be passed as an argument, and serves as the
230   // linearization point. The function returns the derivative of the nonlinear
231   // constitutive law in the variable stress_strain_tensor, as well as the
232   // stress-strain tensor of the linearized problem in
233   // stress_strain_tensor_linearized.  See
234   // PlasticityContactProblem::assemble_nl_system where this function is used.
235   template <int dim>
get_linearized_stress_strain_tensors(const SymmetricTensor<2,dim> & strain_tensor,SymmetricTensor<4,dim> & stress_strain_tensor_linearized,SymmetricTensor<4,dim> & stress_strain_tensor) const236   void ConstitutiveLaw<dim>::get_linearized_stress_strain_tensors(
237     const SymmetricTensor<2, dim> &strain_tensor,
238     SymmetricTensor<4, dim> &      stress_strain_tensor_linearized,
239     SymmetricTensor<4, dim> &      stress_strain_tensor) const
240   {
241     Assert(dim == 3, ExcNotImplemented());
242 
243     SymmetricTensor<2, dim> stress_tensor;
244     stress_tensor =
245       (stress_strain_tensor_kappa + stress_strain_tensor_mu) * strain_tensor;
246 
247     stress_strain_tensor            = stress_strain_tensor_mu;
248     stress_strain_tensor_linearized = stress_strain_tensor_mu;
249 
250     SymmetricTensor<2, dim> deviator_stress_tensor = deviator(stress_tensor);
251     const double deviator_stress_tensor_norm = deviator_stress_tensor.norm();
252 
253     if (deviator_stress_tensor_norm > sigma_0)
254       {
255         const double beta = sigma_0 / deviator_stress_tensor_norm;
256         stress_strain_tensor *= (gamma + (1 - gamma) * beta);
257         stress_strain_tensor_linearized *= (gamma + (1 - gamma) * beta);
258         deviator_stress_tensor /= deviator_stress_tensor_norm;
259         stress_strain_tensor_linearized -=
260           (1 - gamma) * beta * 2 * mu *
261           outer_product(deviator_stress_tensor, deviator_stress_tensor);
262       }
263 
264     stress_strain_tensor += stress_strain_tensor_kappa;
265     stress_strain_tensor_linearized += stress_strain_tensor_kappa;
266   }
267 
268   // <h3>Equation data: boundary forces, boundary values, obstacles</h3>
269   //
270   // The following should be relatively standard. We need classes for
271   // the boundary forcing term (which we here choose to be zero)
272   // and boundary values on those part of the boundary that are not part
273   // of the contact surface (also chosen to be zero here).
274   namespace EquationData
275   {
276     template <int dim>
277     class BoundaryForce : public Function<dim>
278     {
279     public:
280       BoundaryForce();
281 
282       virtual double value(const Point<dim> & p,
283                            const unsigned int component = 0) const override;
284 
285       virtual void vector_value(const Point<dim> &p,
286                                 Vector<double> &  values) const override;
287     };
288 
289     template <int dim>
BoundaryForce()290     BoundaryForce<dim>::BoundaryForce()
291       : Function<dim>(dim)
292     {}
293 
294 
295     template <int dim>
value(const Point<dim> &,const unsigned int) const296     double BoundaryForce<dim>::value(const Point<dim> &,
297                                      const unsigned int) const
298     {
299       return 0.;
300     }
301 
302     template <int dim>
vector_value(const Point<dim> & p,Vector<double> & values) const303     void BoundaryForce<dim>::vector_value(const Point<dim> &p,
304                                           Vector<double> &  values) const
305     {
306       for (unsigned int c = 0; c < this->n_components; ++c)
307         values(c) = BoundaryForce<dim>::value(p, c);
308     }
309 
310 
311 
312     template <int dim>
313     class BoundaryValues : public Function<dim>
314     {
315     public:
316       BoundaryValues();
317 
318       virtual double value(const Point<dim> & p,
319                            const unsigned int component = 0) const override;
320     };
321 
322 
323     template <int dim>
BoundaryValues()324     BoundaryValues<dim>::BoundaryValues()
325       : Function<dim>(dim)
326     {}
327 
328 
329     template <int dim>
value(const Point<dim> &,const unsigned int) const330     double BoundaryValues<dim>::value(const Point<dim> &,
331                                       const unsigned int) const
332     {
333       return 0.;
334     }
335 
336 
337 
338     // @sect4{The <code>SphereObstacle</code> class}
339 
340     // The following class is the first of two obstacles that can be
341     // selected from the input file. It describes a sphere centered
342     // at position $x=y=0.5, z=z_{\text{surface}}+0.59$ and radius $r=0.6$,
343     // where $z_{\text{surface}}$ is the vertical position of the (flat)
344     // surface of the deformable body. The function's <code>value</code>
345     // returns the location of the obstacle for a given $x,y$ value if the
346     // point actually lies below the sphere, or a large positive value that
347     // can't possibly interfere with the deformation if it lies outside
348     // the "shadow" of the sphere.
349     template <int dim>
350     class SphereObstacle : public Function<dim>
351     {
352     public:
353       SphereObstacle(const double z_surface);
354 
355       virtual double value(const Point<dim> & p,
356                            const unsigned int component = 0) const override;
357 
358       virtual void vector_value(const Point<dim> &p,
359                                 Vector<double> &  values) const override;
360 
361     private:
362       const double z_surface;
363     };
364 
365 
366     template <int dim>
SphereObstacle(const double z_surface)367     SphereObstacle<dim>::SphereObstacle(const double z_surface)
368       : Function<dim>(dim)
369       , z_surface(z_surface)
370     {}
371 
372 
373     template <int dim>
value(const Point<dim> & p,const unsigned int component) const374     double SphereObstacle<dim>::value(const Point<dim> & p,
375                                       const unsigned int component) const
376     {
377       if (component == 0)
378         return p(0);
379       else if (component == 1)
380         return p(1);
381       else if (component == 2)
382         {
383           if ((p(0) - 0.5) * (p(0) - 0.5) + (p(1) - 0.5) * (p(1) - 0.5) < 0.36)
384             return (-std::sqrt(0.36 - (p(0) - 0.5) * (p(0) - 0.5) -
385                                (p(1) - 0.5) * (p(1) - 0.5)) +
386                     z_surface + 0.59);
387           else
388             return 1000;
389         }
390 
391       Assert(false, ExcNotImplemented());
392       return 1e9; // an unreasonable value; ignored in debug mode because of the
393                   // preceding Assert
394     }
395 
396 
397     template <int dim>
vector_value(const Point<dim> & p,Vector<double> & values) const398     void SphereObstacle<dim>::vector_value(const Point<dim> &p,
399                                            Vector<double> &  values) const
400     {
401       for (unsigned int c = 0; c < this->n_components; ++c)
402         values(c) = SphereObstacle<dim>::value(p, c);
403     }
404 
405     // @sect4{The <code>BitmapFile</code> and <code>ChineseObstacle</code> classes}
406 
407     // The following two classes describe the obstacle outlined in the
408     // introduction, i.e., the Chinese character. The first of the two,
409     // <code>BitmapFile</code> is responsible for reading in data from a picture
410     // file stored in pbm ascii format. This data will be bilinearly
411     // interpolated and thereby provides a function that describes the obstacle.
412     // (The code below shows how one can construct a function by interpolating
413     // between given data points. One could use the
414     // Functions::InterpolatedUniformGridData, introduced after this tutorial
415     // program was written, which does exactly what we want here, but it is
416     // instructive to see how to do it by hand.)
417     //
418     // The data which we read from the file will be stored in a double
419     // std::vector named obstacle_data.  This vector composes the base to
420     // calculate a piecewise bilinear function as a polynomial interpolation.
421     // The data we will read from a file consists of zeros (white) and ones
422     // (black).
423     //
424     // The <code>hx,hy</code> variables denote the spacing between pixels in $x$
425     // and $y$ directions. <code>nx,ny</code> are the numbers of pixels in each
426     // of these directions.  <code>get_value()</code> returns the value of the
427     // image at a given location, interpolated from the adjacent pixel values.
428     template <int dim>
429     class BitmapFile
430     {
431     public:
432       BitmapFile(const std::string &name);
433 
434       double get_value(const double x, const double y) const;
435 
436     private:
437       std::vector<double> obstacle_data;
438       double              hx, hy;
439       int                 nx, ny;
440 
441       double get_pixel_value(const int i, const int j) const;
442     };
443 
444     // The constructor of this class reads in the data that describes
445     // the obstacle from the given file name.
446     template <int dim>
BitmapFile(const std::string & name)447     BitmapFile<dim>::BitmapFile(const std::string &name)
448       : obstacle_data(0)
449       , hx(0)
450       , hy(0)
451       , nx(0)
452       , ny(0)
453     {
454       std::ifstream f(name);
455       AssertThrow(f,
456                   ExcMessage(std::string("Can't read from file <") + name +
457                              ">!"));
458 
459       std::string temp;
460       f >> temp >> nx >> ny;
461 
462       AssertThrow(nx > 0 && ny > 0, ExcMessage("Invalid file format."));
463 
464       for (int k = 0; k < nx * ny; ++k)
465         {
466           double val;
467           f >> val;
468           obstacle_data.push_back(val);
469         }
470 
471       hx = 1.0 / (nx - 1);
472       hy = 1.0 / (ny - 1);
473 
474       if (Utilities::MPI::this_mpi_process(MPI_COMM_WORLD) == 0)
475         std::cout << "Read obstacle from file <" << name << ">" << std::endl
476                   << "Resolution of the scanned obstacle picture: " << nx
477                   << " x " << ny << std::endl;
478     }
479 
480     // The following two functions return the value of a given pixel with
481     // coordinates $i,j$, which we identify with the values of a function
482     // defined at positions <code>i*hx, j*hy</code>, and at arbitrary
483     // coordinates $x,y$ where we do a bilinear interpolation between
484     // point values returned by the first of the two functions. In the
485     // second function, for each $x,y$, we first compute the (integer)
486     // location of the nearest pixel coordinate to the bottom left of
487     // $x,y$, and then compute the coordinates $\xi,\eta$ within this
488     // pixel. We truncate both kinds of variables from both below
489     // and above to avoid problems when evaluating the function outside
490     // of its defined range as may happen due to roundoff errors.
491     template <int dim>
get_pixel_value(const int i,const int j) const492     double BitmapFile<dim>::get_pixel_value(const int i, const int j) const
493     {
494       assert(i >= 0 && i < nx);
495       assert(j >= 0 && j < ny);
496       return obstacle_data[nx * (ny - 1 - j) + i];
497     }
498 
499     template <int dim>
get_value(const double x,const double y) const500     double BitmapFile<dim>::get_value(const double x, const double y) const
501     {
502       const int ix = std::min(std::max(static_cast<int>(x / hx), 0), nx - 2);
503       const int iy = std::min(std::max(static_cast<int>(y / hy), 0), ny - 2);
504 
505       const double xi  = std::min(std::max((x - ix * hx) / hx, 1.), 0.);
506       const double eta = std::min(std::max((y - iy * hy) / hy, 1.), 0.);
507 
508       return ((1 - xi) * (1 - eta) * get_pixel_value(ix, iy) +
509               xi * (1 - eta) * get_pixel_value(ix + 1, iy) +
510               (1 - xi) * eta * get_pixel_value(ix, iy + 1) +
511               xi * eta * get_pixel_value(ix + 1, iy + 1));
512     }
513 
514     // Finally, this is the class that actually uses the class above. It
515     // has a BitmapFile object as a member that describes the height of the
516     // obstacle. As mentioned above, the BitmapFile class will provide us
517     // with a mask, i.e., values that are either zero or one (and, if you
518     // ask for locations between pixels, values that are interpolated between
519     // zero and one). This class translates this to heights that are either
520     // 0.001 below the surface of the deformable body (if the BitmapFile
521     // class reports a one at this location) or 0.999 above the obstacle (if
522     // the BitmapFile class reports a zero). The following function should then
523     // be self-explanatory.
524     template <int dim>
525     class ChineseObstacle : public Function<dim>
526     {
527     public:
528       ChineseObstacle(const std::string &filename, const double z_surface);
529 
530       virtual double value(const Point<dim> & p,
531                            const unsigned int component = 0) const override;
532 
533       virtual void vector_value(const Point<dim> &p,
534                                 Vector<double> &  values) const override;
535 
536     private:
537       const BitmapFile<dim> input_obstacle;
538       double                z_surface;
539     };
540 
541 
542     template <int dim>
ChineseObstacle(const std::string & filename,const double z_surface)543     ChineseObstacle<dim>::ChineseObstacle(const std::string &filename,
544                                           const double       z_surface)
545       : Function<dim>(dim)
546       , input_obstacle(filename)
547       , z_surface(z_surface)
548     {}
549 
550 
551     template <int dim>
value(const Point<dim> & p,const unsigned int component) const552     double ChineseObstacle<dim>::value(const Point<dim> & p,
553                                        const unsigned int component) const
554     {
555       if (component == 0)
556         return p(0);
557       if (component == 1)
558         return p(1);
559       else if (component == 2)
560         {
561           if (p(0) >= 0.0 && p(0) <= 1.0 && p(1) >= 0.0 && p(1) <= 1.0)
562             return z_surface + 0.999 - input_obstacle.get_value(p(0), p(1));
563         }
564 
565       Assert(false, ExcNotImplemented());
566       return 1e9; // an unreasonable value; ignored in debug mode because of the
567                   // preceding Assert
568     }
569 
570     template <int dim>
vector_value(const Point<dim> & p,Vector<double> & values) const571     void ChineseObstacle<dim>::vector_value(const Point<dim> &p,
572                                             Vector<double> &  values) const
573     {
574       for (unsigned int c = 0; c < this->n_components; ++c)
575         values(c) = ChineseObstacle<dim>::value(p, c);
576     }
577   } // namespace EquationData
578 
579   // @sect3{The <code>PlasticityContactProblem</code> class template}
580 
581   // This is the main class of this program and supplies all functions and
582   // variables needed to describe the nonlinear contact problem. It is close to
583   // step-41 but with some additional features like handling hanging nodes, a
584   // Newton method, using Trilinos and p4est for parallel distributed computing.
585   // To deal with hanging nodes makes life a bit more complicated since we need
586   // another AffineConstraints object now. We create a Newton method for the
587   // active set method for the contact situation and to handle the nonlinear
588   // operator for the constitutive law.
589   //
590   // The general layout of this class is very much like for most other tutorial
591   // programs. To make our life a bit easier, this class reads a set of input
592   // parameters from an input file. These parameters, using the ParameterHandler
593   // class, are declared in the <code>declare_parameters</code> function (which
594   // is static so that it can be called before we even create an object of the
595   // current type), and a ParameterHandler object that has been used to read an
596   // input file will then be passed to the constructor of this class.
597   //
598   // The remaining member functions are by and large as we have seen in several
599   // of the other tutorial programs, though with additions for the current
600   // nonlinear system. We will comment on their purpose as we get to them
601   // further below.
602   template <int dim>
603   class PlasticityContactProblem
604   {
605   public:
606     PlasticityContactProblem(const ParameterHandler &prm);
607 
608     void run();
609 
610     static void declare_parameters(ParameterHandler &prm);
611 
612   private:
613     void make_grid();
614     void setup_system();
615     void compute_dirichlet_constraints();
616     void update_solution_and_constraints();
617     void
618          assemble_mass_matrix_diagonal(TrilinosWrappers::SparseMatrix &mass_matrix);
619     void assemble_newton_system(
620       const TrilinosWrappers::MPI::Vector &linearization_point);
621     void compute_nonlinear_residual(
622       const TrilinosWrappers::MPI::Vector &linearization_point);
623     void solve_newton_system();
624     void solve_newton();
625     void refine_grid();
626     void move_mesh(const TrilinosWrappers::MPI::Vector &displacement) const;
627     void output_results(const unsigned int current_refinement_cycle);
628 
629     void output_contact_force() const;
630 
631     // As far as member variables are concerned, we start with ones that we use
632     // to indicate the MPI universe this program runs on, a stream we use to let
633     // exactly one processor produce output to the console (see step-17) and
634     // a variable that is used to time the various sections of the program:
635     MPI_Comm           mpi_communicator;
636     ConditionalOStream pcout;
637     TimerOutput        computing_timer;
638 
639     // The next group describes the mesh and the finite element space.
640     // In particular, for this parallel program, the finite element
641     // space has associated with it variables that indicate which degrees
642     // of freedom live on the current processor (the index sets, see
643     // also step-40 and the @ref distributed documentation module) as
644     // well as a variety of constraints: those imposed by hanging nodes,
645     // by Dirichlet boundary conditions, and by the active set of
646     // contact nodes. Of the three AffineConstraints variables defined
647     // here, the first only contains hanging node constraints, the
648     // second also those associated with Dirichlet boundary conditions,
649     // and the third these plus the contact constraints.
650     //
651     // The variable <code>active_set</code> consists of those degrees
652     // of freedom constrained by the contact, and we use
653     // <code>fraction_of_plastic_q_points_per_cell</code> to keep
654     // track of the fraction of quadrature points on each cell where
655     // the stress equals the yield stress. The latter is only used to
656     // create graphical output showing the plastic zone, but not for
657     // any further computation; the variable is a member variable of
658     // this class since the information is computed as a by-product
659     // of computing the residual, but is used only much later. (Note
660     // that the vector is a vector of length equal to the number of
661     // active cells on the <i>local mesh</i>; it is never used to
662     // exchange information between processors and can therefore be
663     // a regular deal.II vector.)
664     const unsigned int                        n_initial_global_refinements;
665     parallel::distributed::Triangulation<dim> triangulation;
666 
667     const unsigned int fe_degree;
668     FESystem<dim>      fe;
669     DoFHandler<dim>    dof_handler;
670 
671     IndexSet locally_owned_dofs;
672     IndexSet locally_relevant_dofs;
673 
674     AffineConstraints<double> constraints_hanging_nodes;
675     AffineConstraints<double> constraints_dirichlet_and_hanging_nodes;
676     AffineConstraints<double> all_constraints;
677 
678     IndexSet      active_set;
679     Vector<float> fraction_of_plastic_q_points_per_cell;
680 
681 
682     // The next block of variables corresponds to the solution
683     // and the linear systems we need to form. In particular, this
684     // includes the Newton matrix and right hand side; the vector
685     // that corresponds to the residual (i.e., the Newton right hand
686     // side) but from which we have not eliminated the various
687     // constraints and that is used to determine which degrees of
688     // freedom need to be constrained in the next iteration; and
689     // a vector that corresponds to the diagonal of the $B$ matrix
690     // briefly mentioned in the introduction and discussed in the
691     // accompanying paper.
692     TrilinosWrappers::SparseMatrix newton_matrix;
693 
694     TrilinosWrappers::MPI::Vector solution;
695     TrilinosWrappers::MPI::Vector newton_rhs;
696     TrilinosWrappers::MPI::Vector newton_rhs_uncondensed;
697     TrilinosWrappers::MPI::Vector diag_mass_matrix_vector;
698 
699     // The next block contains the variables that describe the material
700     // response:
701     const double         e_modulus, nu, gamma, sigma_0;
702     ConstitutiveLaw<dim> constitutive_law;
703 
704     // And then there is an assortment of other variables that are used
705     // to identify the mesh we are asked to build as selected by the
706     // parameter file, the obstacle that is being pushed into the
707     // deformable body, the mesh refinement strategy, whether to transfer
708     // the solution from one mesh to the next, and how many mesh
709     // refinement cycles to perform. As possible, we mark these kinds
710     // of variables as <code>const</code> to help the reader identify
711     // which ones may or may not be modified later on (the output directory
712     // being an exception -- it is never modified outside the constructor
713     // but it is awkward to initialize in the member-initializer-list
714     // following the colon in the constructor since there we have only
715     // one shot at setting it; the same is true for the mesh refinement
716     // criterion):
717     const std::string                          base_mesh;
718     const std::shared_ptr<const Function<dim>> obstacle;
719 
720     struct RefinementStrategy
721     {
722       enum value
723       {
724         refine_global,
725         refine_percentage,
726         refine_fix_dofs
727       };
728     };
729     typename RefinementStrategy::value refinement_strategy;
730 
731     const bool         transfer_solution;
732     std::string        output_dir;
733     const unsigned int n_refinement_cycles;
734     unsigned int       current_refinement_cycle;
735   };
736 
737 
738   // @sect3{Implementation of the <code>PlasticityContactProblem</code> class}
739 
740   // @sect4{PlasticityContactProblem::declare_parameters}
741 
742   // Let us start with the declaration of run-time parameters that can be
743   // selected in the input file. These values will be read back in the
744   // constructor of this class to initialize the member variables of this
745   // class:
746   template <int dim>
declare_parameters(ParameterHandler & prm)747   void PlasticityContactProblem<dim>::declare_parameters(ParameterHandler &prm)
748   {
749     prm.declare_entry(
750       "polynomial degree",
751       "1",
752       Patterns::Integer(),
753       "Polynomial degree of the FE_Q finite element space, typically 1 or 2.");
754     prm.declare_entry("number of initial refinements",
755                       "2",
756                       Patterns::Integer(),
757                       "Number of initial global mesh refinement steps before "
758                       "the first computation.");
759     prm.declare_entry(
760       "refinement strategy",
761       "percentage",
762       Patterns::Selection("global|percentage"),
763       "Mesh refinement strategy:\n"
764       " global: one global refinement\n"
765       " percentage: a fixed percentage of cells gets refined using the Kelly estimator.");
766     prm.declare_entry("number of cycles",
767                       "5",
768                       Patterns::Integer(),
769                       "Number of adaptive mesh refinement cycles to run.");
770     prm.declare_entry(
771       "obstacle",
772       "sphere",
773       Patterns::Selection("sphere|read from file"),
774       "The name of the obstacle to use. This may either be 'sphere' if we should "
775       "use a spherical obstacle, or 'read from file' in which case the obstacle "
776       "will be read from a file named 'obstacle.pbm' that is supposed to be in "
777       "ASCII PBM format.");
778     prm.declare_entry(
779       "output directory",
780       "",
781       Patterns::Anything(),
782       "Directory for output files (graphical output and benchmark "
783       "statistics). If empty, use the current directory.");
784     prm.declare_entry(
785       "transfer solution",
786       "false",
787       Patterns::Bool(),
788       "Whether the solution should be used as a starting guess "
789       "for the next finer mesh. If false, then the iteration starts at "
790       "zero on every mesh.");
791     prm.declare_entry("base mesh",
792                       "box",
793                       Patterns::Selection("box|half sphere"),
794                       "Select the shape of the domain: 'box' or 'half sphere'");
795   }
796 
797 
798   // @sect4{The <code>PlasticityContactProblem</code> constructor}
799 
800   // Given the declarations of member variables as well as the
801   // declarations of run-time parameters that are read from the input
802   // file, there is nothing surprising in this constructor. In the body
803   // we initialize the mesh refinement strategy and the output directory,
804   // creating such a directory if necessary.
805   template <int dim>
PlasticityContactProblem(const ParameterHandler & prm)806   PlasticityContactProblem<dim>::PlasticityContactProblem(
807     const ParameterHandler &prm)
808     : mpi_communicator(MPI_COMM_WORLD)
809     , pcout(std::cout,
810             (Utilities::MPI::this_mpi_process(mpi_communicator) == 0))
811     , computing_timer(MPI_COMM_WORLD,
812                       pcout,
813                       TimerOutput::never,
814                       TimerOutput::wall_times)
815 
816     , n_initial_global_refinements(
817         prm.get_integer("number of initial refinements"))
818     , triangulation(mpi_communicator)
819     , fe_degree(prm.get_integer("polynomial degree"))
820     , fe(FE_Q<dim>(QGaussLobatto<1>(fe_degree + 1)), dim)
821     , dof_handler(triangulation)
822 
823     , e_modulus(200000)
824     , nu(0.3)
825     , gamma(0.01)
826     , sigma_0(400.0)
827     , constitutive_law(e_modulus, nu, sigma_0, gamma)
828 
829     , base_mesh(prm.get("base mesh"))
830     , obstacle(prm.get("obstacle") == "read from file" ?
831                  static_cast<const Function<dim> *>(
832                    new EquationData::ChineseObstacle<dim>(
833                      "obstacle.pbm",
834                      (base_mesh == "box" ? 1.0 : 0.5))) :
835                  static_cast<const Function<dim> *>(
836                    new EquationData::SphereObstacle<dim>(
837                      base_mesh == "box" ? 1.0 : 0.5)))
838 
839     , transfer_solution(prm.get_bool("transfer solution"))
840     , n_refinement_cycles(prm.get_integer("number of cycles"))
841     , current_refinement_cycle(0)
842 
843   {
844     std::string strat = prm.get("refinement strategy");
845     if (strat == "global")
846       refinement_strategy = RefinementStrategy::refine_global;
847     else if (strat == "percentage")
848       refinement_strategy = RefinementStrategy::refine_percentage;
849     else
850       AssertThrow(false, ExcNotImplemented());
851 
852     output_dir = prm.get("output directory");
853     if (output_dir != "" && *(output_dir.rbegin()) != '/')
854       output_dir += "/";
855 
856     // If necessary, create a new directory for the output.
857     if (Utilities::MPI::this_mpi_process(mpi_communicator) == 0)
858       {
859         const int ierr = mkdir(output_dir.c_str(), 0777);
860         AssertThrow(ierr == 0 || errno == EEXIST, ExcIO());
861       }
862 
863     pcout << "    Using output directory '" << output_dir << "'" << std::endl;
864     pcout << "    FE degree " << fe_degree << std::endl;
865     pcout << "    transfer solution " << (transfer_solution ? "true" : "false")
866           << std::endl;
867   }
868 
869 
870 
871   // @sect4{PlasticityContactProblem::make_grid}
872 
873   // The next block deals with constructing the starting mesh.
874   // We will use the following helper function and the first
875   // block of the <code>make_grid()</code> to construct a
876   // mesh that corresponds to a half sphere. deal.II has a function
877   // that creates such a mesh, but it is in the wrong location
878   // and facing the wrong direction, so we need to shift and rotate
879   // it a bit before using it.
880   //
881   // For later reference, as described in the documentation of
882   // GridGenerator::half_hyper_ball(), the flat surface of the halfsphere
883   // has boundary indicator zero, while the remainder has boundary
884   // indicator one.
rotate_half_sphere(const Point<3> & in)885   Point<3> rotate_half_sphere(const Point<3> &in)
886   {
887     return {in(2), in(1), -in(0)};
888   }
889 
890   template <int dim>
make_grid()891   void PlasticityContactProblem<dim>::make_grid()
892   {
893     if (base_mesh == "half sphere")
894       {
895         const Point<dim> center(0, 0, 0);
896         const double     radius = 0.8;
897         GridGenerator::half_hyper_ball(triangulation, center, radius);
898         // Since we will attach a different manifold below, we immediately
899         // clear the default manifold description:
900         triangulation.reset_all_manifolds();
901 
902         GridTools::transform(&rotate_half_sphere, triangulation);
903         GridTools::shift(Point<dim>(0.5, 0.5, 0.5), triangulation);
904 
905         SphericalManifold<dim> manifold_description(Point<dim>(0.5, 0.5, 0.5));
906         GridTools::copy_boundary_to_manifold_id(triangulation);
907         triangulation.set_manifold(0, manifold_description);
908       }
909     // Alternatively, create a hypercube mesh. After creating it,
910     // assign boundary indicators as follows:
911     // @code
912     // >     _______
913     // >    /  1    /|
914     // >   /______ / |
915     // >  |       | 8|
916     // >  |   8   | /
917     // >  |_______|/
918     // >      6
919     // @endcode
920     // In other words, the boundary indicators of the sides of the cube are 8.
921     // The boundary indicator of the bottom is 6 and the top has indicator 1.
922     // We set these by looping over all cells of all faces and looking at
923     // coordinate values of the cell center, and will make use of these
924     // indicators later when evaluating which boundary will carry Dirichlet
925     // boundary conditions or will be subject to potential contact.
926     // (In the current case, the mesh contains only a single cell, and all of
927     // its faces are on the boundary, so both the loop over all cells and the
928     // query whether a face is on the boundary are, strictly speaking,
929     // unnecessary; we retain them simply out of habit: this kind of code can
930     // be found in many programs in essentially this form.)
931     else
932       {
933         const Point<dim> p1(0, 0, 0);
934         const Point<dim> p2(1.0, 1.0, 1.0);
935 
936         GridGenerator::hyper_rectangle(triangulation, p1, p2);
937 
938         for (const auto &cell : triangulation.active_cell_iterators())
939           for (const auto &face : cell->face_iterators())
940             if (face->at_boundary())
941               {
942                 if (std::fabs(face->center()[2] - p2[2]) < 1e-12)
943                   face->set_boundary_id(1);
944                 if (std::fabs(face->center()[0] - p1[0]) < 1e-12 ||
945                     std::fabs(face->center()[0] - p2[0]) < 1e-12 ||
946                     std::fabs(face->center()[1] - p1[1]) < 1e-12 ||
947                     std::fabs(face->center()[1] - p2[1]) < 1e-12)
948                   face->set_boundary_id(8);
949                 if (std::fabs(face->center()[2] - p1[2]) < 1e-12)
950                   face->set_boundary_id(6);
951               }
952       }
953 
954     triangulation.refine_global(n_initial_global_refinements);
955   }
956 
957 
958 
959   // @sect4{PlasticityContactProblem::setup_system}
960 
961   // The next piece in the puzzle is to set up the DoFHandler, resize
962   // vectors and take care of various other status variables such as
963   // index sets and constraint matrices.
964   //
965   // In the following, each group of operations is put into a brace-enclosed
966   // block that is being timed by the variable declared at the top of the
967   // block (the constructor of the TimerOutput::Scope variable starts the
968   // timed section, the destructor that is called at the end of the block
969   // stops it again).
970   template <int dim>
setup_system()971   void PlasticityContactProblem<dim>::setup_system()
972   {
973     /* setup dofs and get index sets for locally owned and relevant dofs */
974     {
975       TimerOutput::Scope t(computing_timer, "Setup: distribute DoFs");
976       dof_handler.distribute_dofs(fe);
977 
978       locally_owned_dofs = dof_handler.locally_owned_dofs();
979       locally_relevant_dofs.clear();
980       DoFTools::extract_locally_relevant_dofs(dof_handler,
981                                               locally_relevant_dofs);
982     }
983 
984     /* setup hanging nodes and Dirichlet constraints */
985     {
986       TimerOutput::Scope t(computing_timer, "Setup: constraints");
987       constraints_hanging_nodes.reinit(locally_relevant_dofs);
988       DoFTools::make_hanging_node_constraints(dof_handler,
989                                               constraints_hanging_nodes);
990       constraints_hanging_nodes.close();
991 
992       pcout << "   Number of active cells: "
993             << triangulation.n_global_active_cells() << std::endl
994             << "   Number of degrees of freedom: " << dof_handler.n_dofs()
995             << std::endl;
996 
997       compute_dirichlet_constraints();
998     }
999 
1000     /* initialization of vectors and the active set */
1001     {
1002       TimerOutput::Scope t(computing_timer, "Setup: vectors");
1003       solution.reinit(locally_relevant_dofs, mpi_communicator);
1004       newton_rhs.reinit(locally_owned_dofs, mpi_communicator);
1005       newton_rhs_uncondensed.reinit(locally_owned_dofs, mpi_communicator);
1006       diag_mass_matrix_vector.reinit(locally_owned_dofs, mpi_communicator);
1007       fraction_of_plastic_q_points_per_cell.reinit(
1008         triangulation.n_active_cells());
1009 
1010       active_set.clear();
1011       active_set.set_size(dof_handler.n_dofs());
1012     }
1013 
1014     // Finally, we set up sparsity patterns and matrices.
1015     // We temporarily (ab)use the system matrix to also build the (diagonal)
1016     // matrix that we use in eliminating degrees of freedom that are in contact
1017     // with the obstacle, but we then immediately set the Newton matrix back
1018     // to zero.
1019     {
1020       TimerOutput::Scope                t(computing_timer, "Setup: matrix");
1021       TrilinosWrappers::SparsityPattern sp(locally_owned_dofs,
1022                                            mpi_communicator);
1023 
1024       DoFTools::make_sparsity_pattern(dof_handler,
1025                                       sp,
1026                                       constraints_dirichlet_and_hanging_nodes,
1027                                       false,
1028                                       Utilities::MPI::this_mpi_process(
1029                                         mpi_communicator));
1030       sp.compress();
1031       newton_matrix.reinit(sp);
1032 
1033 
1034       TrilinosWrappers::SparseMatrix &mass_matrix = newton_matrix;
1035 
1036       assemble_mass_matrix_diagonal(mass_matrix);
1037 
1038       const unsigned int start = (newton_rhs.local_range().first),
1039                          end   = (newton_rhs.local_range().second);
1040       for (unsigned int j = start; j < end; ++j)
1041         diag_mass_matrix_vector(j) = mass_matrix.diag_element(j);
1042       diag_mass_matrix_vector.compress(VectorOperation::insert);
1043 
1044       mass_matrix = 0;
1045     }
1046   }
1047 
1048 
1049   // @sect4{PlasticityContactProblem::compute_dirichlet_constraints}
1050 
1051   // This function, broken out of the preceding one, computes the constraints
1052   // associated with Dirichlet-type boundary conditions and puts them into the
1053   // <code>constraints_dirichlet_and_hanging_nodes</code> variable by merging
1054   // with the constraints that come from hanging nodes.
1055   //
1056   // As laid out in the introduction, we need to distinguish between two
1057   // cases:
1058   // - If the domain is a box, we set the displacement to zero at the bottom,
1059   //   and allow vertical movement in z-direction along the sides. As
1060   //   shown in the <code>make_grid()</code> function, the former corresponds
1061   //   to boundary indicator 6, the latter to 8.
1062   // - If the domain is a half sphere, then we impose zero displacement along
1063   //   the curved part of the boundary, associated with boundary indicator zero.
1064   template <int dim>
compute_dirichlet_constraints()1065   void PlasticityContactProblem<dim>::compute_dirichlet_constraints()
1066   {
1067     constraints_dirichlet_and_hanging_nodes.reinit(locally_relevant_dofs);
1068     constraints_dirichlet_and_hanging_nodes.merge(constraints_hanging_nodes);
1069 
1070     if (base_mesh == "box")
1071       {
1072         // interpolate all components of the solution
1073         VectorTools::interpolate_boundary_values(
1074           dof_handler,
1075           6,
1076           EquationData::BoundaryValues<dim>(),
1077           constraints_dirichlet_and_hanging_nodes,
1078           ComponentMask());
1079 
1080         // interpolate x- and y-components of the
1081         // solution (this is a bit mask, so apply
1082         // operator| )
1083         const FEValuesExtractors::Scalar x_displacement(0);
1084         const FEValuesExtractors::Scalar y_displacement(1);
1085         VectorTools::interpolate_boundary_values(
1086           dof_handler,
1087           8,
1088           EquationData::BoundaryValues<dim>(),
1089           constraints_dirichlet_and_hanging_nodes,
1090           (fe.component_mask(x_displacement) |
1091            fe.component_mask(y_displacement)));
1092       }
1093     else
1094       VectorTools::interpolate_boundary_values(
1095         dof_handler,
1096         0,
1097         EquationData::BoundaryValues<dim>(),
1098         constraints_dirichlet_and_hanging_nodes,
1099         ComponentMask());
1100 
1101     constraints_dirichlet_and_hanging_nodes.close();
1102   }
1103 
1104 
1105 
1106   // @sect4{PlasticityContactProblem::assemble_mass_matrix_diagonal}
1107 
1108   // The next helper function computes the (diagonal) mass matrix that
1109   // is used to determine the active set of the active set method we use in
1110   // the contact algorithm. This matrix is of mass matrix type, but unlike
1111   // the standard mass matrix, we can make it diagonal (even in the case of
1112   // higher order elements) by using a quadrature formula that has its
1113   // quadrature points at exactly the same locations as the interpolation points
1114   // for the finite element are located. We achieve this by using a
1115   // QGaussLobatto quadrature formula here, along with initializing the finite
1116   // element with a set of interpolation points derived from the same quadrature
1117   // formula. The remainder of the function is relatively straightforward: we
1118   // put the resulting matrix into the given argument; because we know the
1119   // matrix is diagonal, it is sufficient to have a loop over only $i$ and
1120   // not over $j$. Strictly speaking, we could even avoid multiplying the
1121   // shape function's values at quadrature point <code>q_point</code> by itself
1122   // because we know the shape value to be a vector with exactly one one which
1123   // when dotted with itself yields one. Since this function is not time
1124   // critical we add this term for clarity.
1125   template <int dim>
assemble_mass_matrix_diagonal(TrilinosWrappers::SparseMatrix & mass_matrix)1126   void PlasticityContactProblem<dim>::assemble_mass_matrix_diagonal(
1127     TrilinosWrappers::SparseMatrix &mass_matrix)
1128   {
1129     QGaussLobatto<dim - 1> face_quadrature_formula(fe.degree + 1);
1130 
1131     FEFaceValues<dim> fe_values_face(fe,
1132                                      face_quadrature_formula,
1133                                      update_values | update_JxW_values);
1134 
1135     const unsigned int dofs_per_cell   = fe.n_dofs_per_cell();
1136     const unsigned int n_face_q_points = face_quadrature_formula.size();
1137 
1138     FullMatrix<double> cell_matrix(dofs_per_cell, dofs_per_cell);
1139     std::vector<types::global_dof_index> local_dof_indices(dofs_per_cell);
1140 
1141     const FEValuesExtractors::Vector displacement(0);
1142 
1143     for (const auto &cell : dof_handler.active_cell_iterators())
1144       if (cell->is_locally_owned())
1145         for (const auto &face : cell->face_iterators())
1146           if (face->at_boundary() && face->boundary_id() == 1)
1147             {
1148               fe_values_face.reinit(cell, face);
1149               cell_matrix = 0;
1150 
1151               for (unsigned int q_point = 0; q_point < n_face_q_points;
1152                    ++q_point)
1153                 for (unsigned int i = 0; i < dofs_per_cell; ++i)
1154                   cell_matrix(i, i) +=
1155                     (fe_values_face[displacement].value(i, q_point) *
1156                      fe_values_face[displacement].value(i, q_point) *
1157                      fe_values_face.JxW(q_point));
1158 
1159               cell->get_dof_indices(local_dof_indices);
1160 
1161               for (unsigned int i = 0; i < dofs_per_cell; ++i)
1162                 mass_matrix.add(local_dof_indices[i],
1163                                 local_dof_indices[i],
1164                                 cell_matrix(i, i));
1165             }
1166     mass_matrix.compress(VectorOperation::add);
1167   }
1168 
1169 
1170   // @sect4{PlasticityContactProblem::update_solution_and_constraints}
1171 
1172   // The following function is the first function we call in each Newton
1173   // iteration in the <code>solve_newton()</code> function. What it does is
1174   // to project the solution onto the feasible set and update the active set
1175   // for the degrees of freedom that touch or penetrate the obstacle.
1176   //
1177   // In order to function, we first need to do some bookkeeping: We need
1178   // to write into the solution vector (which we can only do with fully
1179   // distributed vectors without ghost elements) and we need to read
1180   // the Lagrange multiplier and the elements of the diagonal mass matrix
1181   // from their respective vectors (which we can only do with vectors that
1182   // do have ghost elements), so we create the respective vectors. We then
1183   // also initialize the constraints object that will contain constraints
1184   // from contact and all other sources, as well as an object that contains
1185   // an index set of all locally owned degrees of freedom that are part of
1186   // the contact:
1187   template <int dim>
update_solution_and_constraints()1188   void PlasticityContactProblem<dim>::update_solution_and_constraints()
1189   {
1190     std::vector<bool> dof_touched(dof_handler.n_dofs(), false);
1191 
1192     TrilinosWrappers::MPI::Vector distributed_solution(locally_owned_dofs,
1193                                                        mpi_communicator);
1194     distributed_solution = solution;
1195 
1196     TrilinosWrappers::MPI::Vector lambda(locally_relevant_dofs,
1197                                          mpi_communicator);
1198     lambda = newton_rhs_uncondensed;
1199 
1200     TrilinosWrappers::MPI::Vector diag_mass_matrix_vector_relevant(
1201       locally_relevant_dofs, mpi_communicator);
1202     diag_mass_matrix_vector_relevant = diag_mass_matrix_vector;
1203 
1204 
1205     all_constraints.reinit(locally_relevant_dofs);
1206     active_set.clear();
1207 
1208     // The second part is a loop over all cells in which we look at each
1209     // point where a degree of freedom is defined whether the active set
1210     // condition is true and we need to add this degree of freedom to
1211     // the active set of contact nodes. As we always do, if we want to
1212     // evaluate functions at individual points, we do this with an
1213     // FEValues object (or, here, an FEFaceValues object since we need to
1214     // check contact at the surface) with an appropriately chosen quadrature
1215     // object. We create this face quadrature object by choosing the
1216     // "support points" of the shape functions defined on the faces
1217     // of cells (for more on support points, see this
1218     // @ref GlossSupport "glossary entry"). As a consequence, we have as
1219     // many quadrature points as there are shape functions per face and
1220     // looping over quadrature points is equivalent to looping over shape
1221     // functions defined on a face. With this, the code looks as follows:
1222     Quadrature<dim - 1> face_quadrature(fe.get_unit_face_support_points());
1223     FEFaceValues<dim>   fe_values_face(fe,
1224                                      face_quadrature,
1225                                      update_quadrature_points);
1226 
1227     const unsigned int dofs_per_face   = fe.n_dofs_per_face();
1228     const unsigned int n_face_q_points = face_quadrature.size();
1229 
1230     std::vector<types::global_dof_index> dof_indices(dofs_per_face);
1231 
1232     for (const auto &cell : dof_handler.active_cell_iterators())
1233       if (!cell->is_artificial())
1234         for (const auto &face : cell->face_iterators())
1235           if (face->at_boundary() && face->boundary_id() == 1)
1236             {
1237               fe_values_face.reinit(cell, face);
1238               face->get_dof_indices(dof_indices);
1239 
1240               for (unsigned int q_point = 0; q_point < n_face_q_points;
1241                    ++q_point)
1242                 {
1243                   // At each quadrature point (i.e., at each support point of a
1244                   // degree of freedom located on the contact boundary), we then
1245                   // ask whether it is part of the z-displacement degrees of
1246                   // freedom and if we haven't encountered this degree of
1247                   // freedom yet (which can happen for those on the edges
1248                   // between faces), we need to evaluate the gap between the
1249                   // deformed object and the obstacle. If the active set
1250                   // condition is true, then we add a constraint to the
1251                   // AffineConstraints object that the next Newton update needs
1252                   // to satisfy, set the solution vector's corresponding element
1253                   // to the correct value, and add the index to the IndexSet
1254                   // object that stores which degree of freedom is part of the
1255                   // contact:
1256                   const unsigned int component =
1257                     fe.face_system_to_component_index(q_point).first;
1258 
1259                   const unsigned int index_z = dof_indices[q_point];
1260 
1261                   if ((component == 2) && (dof_touched[index_z] == false))
1262                     {
1263                       dof_touched[index_z] = true;
1264 
1265                       const Point<dim> this_support_point =
1266                         fe_values_face.quadrature_point(q_point);
1267 
1268                       const double obstacle_value =
1269                         obstacle->value(this_support_point, 2);
1270                       const double solution_here = solution(index_z);
1271                       const double undeformed_gap =
1272                         obstacle_value - this_support_point(2);
1273 
1274                       const double c = 100.0 * e_modulus;
1275                       if ((lambda(index_z) /
1276                                diag_mass_matrix_vector_relevant(index_z) +
1277                              c * (solution_here - undeformed_gap) >
1278                            0) &&
1279                           !constraints_hanging_nodes.is_constrained(index_z))
1280                         {
1281                           all_constraints.add_line(index_z);
1282                           all_constraints.set_inhomogeneity(index_z,
1283                                                             undeformed_gap);
1284                           distributed_solution(index_z) = undeformed_gap;
1285 
1286                           active_set.add_index(index_z);
1287                         }
1288                     }
1289                 }
1290             }
1291 
1292     // At the end of this function, we exchange data between processors updating
1293     // those ghost elements in the <code>solution</code> variable that have been
1294     // written by other processors. We then merge the Dirichlet constraints and
1295     // those from hanging nodes into the AffineConstraints object that already
1296     // contains the active set. We finish the function by outputting the total
1297     // number of actively constrained degrees of freedom for which we sum over
1298     // the number of actively constrained degrees of freedom owned by each
1299     // of the processors. This number of locally owned constrained degrees of
1300     // freedom is of course the number of elements of the intersection of the
1301     // active set and the set of locally owned degrees of freedom, which
1302     // we can get by using <code>operator&</code> on two IndexSets:
1303     distributed_solution.compress(VectorOperation::insert);
1304     solution = distributed_solution;
1305 
1306     all_constraints.close();
1307     all_constraints.merge(constraints_dirichlet_and_hanging_nodes);
1308 
1309     pcout << "         Size of active set: "
1310           << Utilities::MPI::sum((active_set & locally_owned_dofs).n_elements(),
1311                                  mpi_communicator)
1312           << std::endl;
1313   }
1314 
1315 
1316   // @sect4{PlasticityContactProblem::assemble_newton_system}
1317 
1318   // Given the complexity of the problem, it may come as a bit of a surprise
1319   // that assembling the linear system we have to solve in each Newton iteration
1320   // is actually fairly straightforward. The following function builds the
1321   // Newton right hand side and Newton matrix. It looks fairly innocent because
1322   // the heavy lifting happens in the call to
1323   // <code>ConstitutiveLaw::get_linearized_stress_strain_tensors()</code> and in
1324   // particular in AffineConstraints::distribute_local_to_global(), using the
1325   // constraints we have previously computed.
1326   template <int dim>
assemble_newton_system(const TrilinosWrappers::MPI::Vector & linearization_point)1327   void PlasticityContactProblem<dim>::assemble_newton_system(
1328     const TrilinosWrappers::MPI::Vector &linearization_point)
1329   {
1330     TimerOutput::Scope t(computing_timer, "Assembling");
1331 
1332     QGauss<dim>     quadrature_formula(fe.degree + 1);
1333     QGauss<dim - 1> face_quadrature_formula(fe.degree + 1);
1334 
1335     FEValues<dim> fe_values(fe,
1336                             quadrature_formula,
1337                             update_values | update_gradients |
1338                               update_JxW_values);
1339 
1340     FEFaceValues<dim> fe_values_face(fe,
1341                                      face_quadrature_formula,
1342                                      update_values | update_quadrature_points |
1343                                        update_JxW_values);
1344 
1345     const unsigned int dofs_per_cell   = fe.n_dofs_per_cell();
1346     const unsigned int n_q_points      = quadrature_formula.size();
1347     const unsigned int n_face_q_points = face_quadrature_formula.size();
1348 
1349     const EquationData::BoundaryForce<dim> boundary_force;
1350     std::vector<Vector<double>> boundary_force_values(n_face_q_points,
1351                                                       Vector<double>(dim));
1352 
1353     FullMatrix<double> cell_matrix(dofs_per_cell, dofs_per_cell);
1354     Vector<double>     cell_rhs(dofs_per_cell);
1355 
1356     std::vector<types::global_dof_index> local_dof_indices(dofs_per_cell);
1357 
1358     const FEValuesExtractors::Vector displacement(0);
1359 
1360     for (const auto &cell : dof_handler.active_cell_iterators())
1361       if (cell->is_locally_owned())
1362         {
1363           fe_values.reinit(cell);
1364           cell_matrix = 0;
1365           cell_rhs    = 0;
1366 
1367           std::vector<SymmetricTensor<2, dim>> strain_tensor(n_q_points);
1368           fe_values[displacement].get_function_symmetric_gradients(
1369             linearization_point, strain_tensor);
1370 
1371           for (unsigned int q_point = 0; q_point < n_q_points; ++q_point)
1372             {
1373               SymmetricTensor<4, dim> stress_strain_tensor_linearized;
1374               SymmetricTensor<4, dim> stress_strain_tensor;
1375               constitutive_law.get_linearized_stress_strain_tensors(
1376                 strain_tensor[q_point],
1377                 stress_strain_tensor_linearized,
1378                 stress_strain_tensor);
1379 
1380               for (unsigned int i = 0; i < dofs_per_cell; ++i)
1381                 {
1382                   // Having computed the stress-strain tensor and its
1383                   // linearization, we can now put together the parts of the
1384                   // matrix and right hand side. In both, we need the linearized
1385                   // stress-strain tensor times the symmetric gradient of
1386                   // $\varphi_i$, i.e. the term $I_\Pi\varepsilon(\varphi_i)$,
1387                   // so we introduce an abbreviation of this term. Recall that
1388                   // the matrix corresponds to the bilinear form
1389                   // $A_{ij}=(I_\Pi\varepsilon(\varphi_i),\varepsilon(\varphi_j))$
1390                   // in the notation of the accompanying publication, whereas
1391                   // the right hand side is $F_i=([I_\Pi-P_\Pi
1392                   // C]\varepsilon(\varphi_i),\varepsilon(\mathbf u))$ where $u$
1393                   // is the current linearization points (typically the last
1394                   // solution). This might suggest that the right hand side will
1395                   // be zero if the material is completely elastic (where
1396                   // $I_\Pi=P_\Pi$) but this ignores the fact that the right
1397                   // hand side will also contain contributions from
1398                   // non-homogeneous constraints due to the contact.
1399                   //
1400                   // The code block that follows this adds contributions that
1401                   // are due to boundary forces, should there be any.
1402                   const SymmetricTensor<2, dim> stress_phi_i =
1403                     stress_strain_tensor_linearized *
1404                     fe_values[displacement].symmetric_gradient(i, q_point);
1405 
1406                   for (unsigned int j = 0; j < dofs_per_cell; ++j)
1407                     cell_matrix(i, j) +=
1408                       (stress_phi_i *
1409                        fe_values[displacement].symmetric_gradient(j, q_point) *
1410                        fe_values.JxW(q_point));
1411 
1412                   cell_rhs(i) +=
1413                     ((stress_phi_i -
1414                       stress_strain_tensor *
1415                         fe_values[displacement].symmetric_gradient(i,
1416                                                                    q_point)) *
1417                      strain_tensor[q_point] * fe_values.JxW(q_point));
1418                 }
1419             }
1420 
1421           for (const auto &face : cell->face_iterators())
1422             if (face->at_boundary() && face->boundary_id() == 1)
1423               {
1424                 fe_values_face.reinit(cell, face);
1425 
1426                 boundary_force.vector_value_list(
1427                   fe_values_face.get_quadrature_points(),
1428                   boundary_force_values);
1429 
1430                 for (unsigned int q_point = 0; q_point < n_face_q_points;
1431                      ++q_point)
1432                   {
1433                     Tensor<1, dim> rhs_values;
1434                     rhs_values[2] = boundary_force_values[q_point][2];
1435                     for (unsigned int i = 0; i < dofs_per_cell; ++i)
1436                       cell_rhs(i) +=
1437                         (fe_values_face[displacement].value(i, q_point) *
1438                          rhs_values * fe_values_face.JxW(q_point));
1439                   }
1440               }
1441 
1442           cell->get_dof_indices(local_dof_indices);
1443           all_constraints.distribute_local_to_global(cell_matrix,
1444                                                      cell_rhs,
1445                                                      local_dof_indices,
1446                                                      newton_matrix,
1447                                                      newton_rhs,
1448                                                      true);
1449         }
1450 
1451     newton_matrix.compress(VectorOperation::add);
1452     newton_rhs.compress(VectorOperation::add);
1453   }
1454 
1455 
1456 
1457   // @sect4{PlasticityContactProblem::compute_nonlinear_residual}
1458 
1459   // The following function computes the nonlinear residual of the equation
1460   // given the current solution (or any other linearization point). This
1461   // is needed in the linear search algorithm where we need to try various
1462   // linear combinations of previous and current (trial) solution to
1463   // compute the (real, globalized) solution of the current Newton step.
1464   //
1465   // That said, in a slight abuse of the name of the function, it actually
1466   // does significantly more. For example, it also computes the vector
1467   // that corresponds to the Newton residual but without eliminating
1468   // constrained degrees of freedom. We need this vector to compute contact
1469   // forces and, ultimately, to compute the next active set. Likewise, by
1470   // keeping track of how many quadrature points we encounter on each cell
1471   // that show plastic yielding, we also compute the
1472   // <code>fraction_of_plastic_q_points_per_cell</code> vector that we
1473   // can later output to visualize the plastic zone. In both of these cases,
1474   // the results are not necessary as part of the line search, and so we may
1475   // be wasting a small amount of time computing them. At the same time, this
1476   // information appears as a natural by-product of what we need to do here
1477   // anyway, and we want to collect it once at the end of each Newton
1478   // step, so we may as well do it here.
1479   //
1480   // The actual implementation of this function should be rather obvious:
1481   template <int dim>
compute_nonlinear_residual(const TrilinosWrappers::MPI::Vector & linearization_point)1482   void PlasticityContactProblem<dim>::compute_nonlinear_residual(
1483     const TrilinosWrappers::MPI::Vector &linearization_point)
1484   {
1485     QGauss<dim>     quadrature_formula(fe.degree + 1);
1486     QGauss<dim - 1> face_quadrature_formula(fe.degree + 1);
1487 
1488     FEValues<dim> fe_values(fe,
1489                             quadrature_formula,
1490                             update_values | update_gradients |
1491                               update_JxW_values);
1492 
1493     FEFaceValues<dim> fe_values_face(fe,
1494                                      face_quadrature_formula,
1495                                      update_values | update_quadrature_points |
1496                                        update_JxW_values);
1497 
1498     const unsigned int dofs_per_cell   = fe.n_dofs_per_cell();
1499     const unsigned int n_q_points      = quadrature_formula.size();
1500     const unsigned int n_face_q_points = face_quadrature_formula.size();
1501 
1502     const EquationData::BoundaryForce<dim> boundary_force;
1503     std::vector<Vector<double>> boundary_force_values(n_face_q_points,
1504                                                       Vector<double>(dim));
1505 
1506     Vector<double> cell_rhs(dofs_per_cell);
1507 
1508     std::vector<types::global_dof_index> local_dof_indices(dofs_per_cell);
1509 
1510     const FEValuesExtractors::Vector displacement(0);
1511 
1512     newton_rhs             = 0;
1513     newton_rhs_uncondensed = 0;
1514 
1515     fraction_of_plastic_q_points_per_cell = 0;
1516 
1517     for (const auto &cell : dof_handler.active_cell_iterators())
1518       if (cell->is_locally_owned())
1519         {
1520           fe_values.reinit(cell);
1521           cell_rhs = 0;
1522 
1523           std::vector<SymmetricTensor<2, dim>> strain_tensors(n_q_points);
1524           fe_values[displacement].get_function_symmetric_gradients(
1525             linearization_point, strain_tensors);
1526 
1527           for (unsigned int q_point = 0; q_point < n_q_points; ++q_point)
1528             {
1529               SymmetricTensor<4, dim> stress_strain_tensor;
1530               const bool              q_point_is_plastic =
1531                 constitutive_law.get_stress_strain_tensor(
1532                   strain_tensors[q_point], stress_strain_tensor);
1533               if (q_point_is_plastic)
1534                 ++fraction_of_plastic_q_points_per_cell(
1535                   cell->active_cell_index());
1536 
1537               for (unsigned int i = 0; i < dofs_per_cell; ++i)
1538                 {
1539                   cell_rhs(i) -=
1540                     (strain_tensors[q_point] * stress_strain_tensor *
1541                      fe_values[displacement].symmetric_gradient(i, q_point) *
1542                      fe_values.JxW(q_point));
1543 
1544                   Tensor<1, dim> rhs_values;
1545                   rhs_values = 0;
1546                   cell_rhs(i) += (fe_values[displacement].value(i, q_point) *
1547                                   rhs_values * fe_values.JxW(q_point));
1548                 }
1549             }
1550 
1551           for (const auto &face : cell->face_iterators())
1552             if (face->at_boundary() && face->boundary_id() == 1)
1553               {
1554                 fe_values_face.reinit(cell, face);
1555 
1556                 boundary_force.vector_value_list(
1557                   fe_values_face.get_quadrature_points(),
1558                   boundary_force_values);
1559 
1560                 for (unsigned int q_point = 0; q_point < n_face_q_points;
1561                      ++q_point)
1562                   {
1563                     Tensor<1, dim> rhs_values;
1564                     rhs_values[2] = boundary_force_values[q_point][2];
1565                     for (unsigned int i = 0; i < dofs_per_cell; ++i)
1566                       cell_rhs(i) +=
1567                         (fe_values_face[displacement].value(i, q_point) *
1568                          rhs_values * fe_values_face.JxW(q_point));
1569                   }
1570               }
1571 
1572           cell->get_dof_indices(local_dof_indices);
1573           constraints_dirichlet_and_hanging_nodes.distribute_local_to_global(
1574             cell_rhs, local_dof_indices, newton_rhs);
1575 
1576           for (unsigned int i = 0; i < dofs_per_cell; ++i)
1577             newton_rhs_uncondensed(local_dof_indices[i]) += cell_rhs(i);
1578         }
1579 
1580     fraction_of_plastic_q_points_per_cell /= quadrature_formula.size();
1581     newton_rhs.compress(VectorOperation::add);
1582     newton_rhs_uncondensed.compress(VectorOperation::add);
1583   }
1584 
1585 
1586 
1587   // @sect4{PlasticityContactProblem::solve_newton_system}
1588 
1589   // The last piece before we can discuss the actual Newton iteration
1590   // on a single mesh is the solver for the linear systems. There are
1591   // a couple of complications that slightly obscure the code, but
1592   // mostly it is just setup then solve. Among the complications are:
1593   //
1594   // - For the hanging nodes we have to apply
1595   //   the AffineConstraints::set_zero function to newton_rhs.
1596   //   This is necessary if a hanging node with solution value $x_0$
1597   //   has one neighbor with value $x_1$ which is in contact with the
1598   //   obstacle and one neighbor $x_2$ which is not in contact. Because
1599   //   the update for the former will be prescribed, the hanging node constraint
1600   //   will have an inhomogeneity and will look like $x_0 = x_1/2 +
1601   //   \text{gap}/2$. So the corresponding entries in the right-hand-side are
1602   //   non-zero with a meaningless value. These values we have to set to zero.
1603   // - Like in step-40, we need to shuffle between vectors that do and do
1604   //   not have ghost elements when solving or using the solution.
1605   //
1606   // The rest of the function is similar to step-40 and
1607   // step-41 except that we use a BiCGStab solver
1608   // instead of CG. This is due to the fact that for very small hardening
1609   // parameters $\gamma$, the linear system becomes almost semidefinite though
1610   // still symmetric. BiCGStab appears to have an easier time with such linear
1611   // systems.
1612   template <int dim>
solve_newton_system()1613   void PlasticityContactProblem<dim>::solve_newton_system()
1614   {
1615     TimerOutput::Scope t(computing_timer, "Solve");
1616 
1617     TrilinosWrappers::MPI::Vector distributed_solution(locally_owned_dofs,
1618                                                        mpi_communicator);
1619     distributed_solution = solution;
1620 
1621     constraints_hanging_nodes.set_zero(distributed_solution);
1622     constraints_hanging_nodes.set_zero(newton_rhs);
1623 
1624     TrilinosWrappers::PreconditionAMG preconditioner;
1625     {
1626       TimerOutput::Scope t(computing_timer, "Solve: setup preconditioner");
1627 
1628       std::vector<std::vector<bool>> constant_modes;
1629       DoFTools::extract_constant_modes(dof_handler,
1630                                        ComponentMask(),
1631                                        constant_modes);
1632 
1633       TrilinosWrappers::PreconditionAMG::AdditionalData additional_data;
1634       additional_data.constant_modes        = constant_modes;
1635       additional_data.elliptic              = true;
1636       additional_data.n_cycles              = 1;
1637       additional_data.w_cycle               = false;
1638       additional_data.output_details        = false;
1639       additional_data.smoother_sweeps       = 2;
1640       additional_data.aggregation_threshold = 1e-2;
1641 
1642       preconditioner.initialize(newton_matrix, additional_data);
1643     }
1644 
1645     {
1646       TimerOutput::Scope t(computing_timer, "Solve: iterate");
1647 
1648       TrilinosWrappers::MPI::Vector tmp(locally_owned_dofs, mpi_communicator);
1649 
1650       const double relative_accuracy = 1e-8;
1651       const double solver_tolerance =
1652         relative_accuracy *
1653         newton_matrix.residual(tmp, distributed_solution, newton_rhs);
1654 
1655       SolverControl solver_control(newton_matrix.m(), solver_tolerance);
1656       SolverBicgstab<TrilinosWrappers::MPI::Vector> solver(solver_control);
1657       solver.solve(newton_matrix,
1658                    distributed_solution,
1659                    newton_rhs,
1660                    preconditioner);
1661 
1662       pcout << "         Error: " << solver_control.initial_value() << " -> "
1663             << solver_control.last_value() << " in "
1664             << solver_control.last_step() << " Bicgstab iterations."
1665             << std::endl;
1666     }
1667 
1668     all_constraints.distribute(distributed_solution);
1669 
1670     solution = distributed_solution;
1671   }
1672 
1673 
1674   // @sect4{PlasticityContactProblem::solve_newton}
1675 
1676   // This is, finally, the function that implements the damped Newton method
1677   // on the current mesh. There are two nested loops: the outer loop for the
1678   // Newton iteration and the inner loop for the line search which will be used
1679   // only if necessary. To obtain a good and reasonable starting value we solve
1680   // an elastic problem in the very first Newton step on each mesh (or only on
1681   // the first mesh if we transfer solutions between meshes). We do so by
1682   // setting the yield stress to an unreasonably large value in these iterations
1683   // and then setting it back to the correct value in subsequent iterations.
1684   //
1685   // Other than this, the top part of this function should be
1686   // reasonably obvious. We initialize the variable
1687   // <code>previous_residual_norm</code> to the most negative value
1688   // representable with double precision numbers so that the
1689   // comparison whether the current residual is less than that of the
1690   // previous step will always fail in the first step.
1691   template <int dim>
solve_newton()1692   void PlasticityContactProblem<dim>::solve_newton()
1693   {
1694     TrilinosWrappers::MPI::Vector old_solution(locally_owned_dofs,
1695                                                mpi_communicator);
1696     TrilinosWrappers::MPI::Vector residual(locally_owned_dofs,
1697                                            mpi_communicator);
1698     TrilinosWrappers::MPI::Vector tmp_vector(locally_owned_dofs,
1699                                              mpi_communicator);
1700     TrilinosWrappers::MPI::Vector locally_relevant_tmp_vector(
1701       locally_relevant_dofs, mpi_communicator);
1702     TrilinosWrappers::MPI::Vector distributed_solution(locally_owned_dofs,
1703                                                        mpi_communicator);
1704 
1705     double residual_norm;
1706     double previous_residual_norm = -std::numeric_limits<double>::max();
1707 
1708     const double correct_sigma = sigma_0;
1709 
1710     IndexSet old_active_set(active_set);
1711 
1712     for (unsigned int newton_step = 1; newton_step <= 100; ++newton_step)
1713       {
1714         if (newton_step == 1 &&
1715             ((transfer_solution && current_refinement_cycle == 0) ||
1716              !transfer_solution))
1717           constitutive_law.set_sigma_0(1e+10);
1718         else if (newton_step == 2 || current_refinement_cycle > 0 ||
1719                  !transfer_solution)
1720           constitutive_law.set_sigma_0(correct_sigma);
1721 
1722         pcout << " " << std::endl;
1723         pcout << "   Newton iteration " << newton_step << std::endl;
1724         pcout << "      Updating active set..." << std::endl;
1725 
1726         {
1727           TimerOutput::Scope t(computing_timer, "update active set");
1728           update_solution_and_constraints();
1729         }
1730 
1731         pcout << "      Assembling system... " << std::endl;
1732         newton_matrix = 0;
1733         newton_rhs    = 0;
1734         assemble_newton_system(solution);
1735 
1736         pcout << "      Solving system... " << std::endl;
1737         solve_newton_system();
1738 
1739         // It gets a bit more hairy after we have computed the
1740         // trial solution $\tilde{\mathbf u}$ of the current Newton step.
1741         // We handle a highly nonlinear problem so we have to damp
1742         // Newton's method using a line search. To understand how we do this,
1743         // recall that in our formulation, we compute a trial solution
1744         // in each Newton step and not the update between old and new solution.
1745         // Since the solution set is a convex set, we will use a line
1746         // search that tries linear combinations of the
1747         // previous and the trial solution to guarantee that the
1748         // damped solution is in our solution set again.
1749         // At most we apply 5 damping steps.
1750         //
1751         // There are exceptions to when we use a line search. First,
1752         // if this is the first Newton step on any mesh, then we don't have
1753         // any point to compare the residual to, so we always accept a full
1754         // step. Likewise, if this is the second Newton step on the first mesh
1755         // (or the second on any mesh if we don't transfer solutions from mesh
1756         // to mesh), then we have computed the first of these steps using just
1757         // an elastic model (see how we set the yield stress sigma to an
1758         // unreasonably large value above). In this case, the first Newton
1759         // solution was a purely elastic one, the second one a plastic one,
1760         // and any linear combination would not necessarily be expected to
1761         // lie in the feasible set -- so we just accept the solution we just
1762         // got.
1763         //
1764         // In either of these two cases, we bypass the line search and just
1765         // update residual and other vectors as necessary.
1766         if ((newton_step == 1) ||
1767             (transfer_solution && newton_step == 2 &&
1768              current_refinement_cycle == 0) ||
1769             (!transfer_solution && newton_step == 2))
1770           {
1771             compute_nonlinear_residual(solution);
1772             old_solution = solution;
1773 
1774             residual                     = newton_rhs;
1775             const unsigned int start_res = (residual.local_range().first),
1776                                end_res   = (residual.local_range().second);
1777             for (unsigned int n = start_res; n < end_res; ++n)
1778               if (all_constraints.is_inhomogeneously_constrained(n))
1779                 residual(n) = 0;
1780 
1781             residual.compress(VectorOperation::insert);
1782 
1783             residual_norm = residual.l2_norm();
1784 
1785             pcout << "      Accepting Newton solution with residual: "
1786                   << residual_norm << std::endl;
1787           }
1788         else
1789           {
1790             for (unsigned int i = 0; i < 5; ++i)
1791               {
1792                 distributed_solution = solution;
1793 
1794                 const double alpha = std::pow(0.5, static_cast<double>(i));
1795                 tmp_vector         = old_solution;
1796                 tmp_vector.sadd(1 - alpha, alpha, distributed_solution);
1797 
1798                 TimerOutput::Scope t(computing_timer, "Residual and lambda");
1799 
1800                 locally_relevant_tmp_vector = tmp_vector;
1801                 compute_nonlinear_residual(locally_relevant_tmp_vector);
1802                 residual = newton_rhs;
1803 
1804                 const unsigned int start_res = (residual.local_range().first),
1805                                    end_res   = (residual.local_range().second);
1806                 for (unsigned int n = start_res; n < end_res; ++n)
1807                   if (all_constraints.is_inhomogeneously_constrained(n))
1808                     residual(n) = 0;
1809 
1810                 residual.compress(VectorOperation::insert);
1811 
1812                 residual_norm = residual.l2_norm();
1813 
1814                 pcout
1815                   << "      Residual of the non-contact part of the system: "
1816                   << residual_norm << std::endl
1817                   << "         with a damping parameter alpha = " << alpha
1818                   << std::endl;
1819 
1820                 if (residual_norm < previous_residual_norm)
1821                   break;
1822               }
1823 
1824             solution     = tmp_vector;
1825             old_solution = solution;
1826           }
1827 
1828         previous_residual_norm = residual_norm;
1829 
1830 
1831         // The final step is to check for convergence. If the active set
1832         // has not changed across all processors and the residual is
1833         // less than a threshold of $10^{-10}$, then we terminate
1834         // the iteration on the current mesh:
1835         if (Utilities::MPI::sum((active_set == old_active_set) ? 0 : 1,
1836                                 mpi_communicator) == 0)
1837           {
1838             pcout << "      Active set did not change!" << std::endl;
1839             if (residual_norm < 1e-10)
1840               break;
1841           }
1842 
1843         old_active_set = active_set;
1844       }
1845   }
1846 
1847   // @sect4{PlasticityContactProblem::refine_grid}
1848 
1849   // If you've made it this far into the deal.II tutorial, the following
1850   // function refining the mesh should not pose any challenges to you
1851   // any more. It refines the mesh, either globally or using the Kelly
1852   // error estimator, and if so asked also transfers the solution from
1853   // the previous to the next mesh. In the latter case, we also need
1854   // to compute the active set and other quantities again, for which we
1855   // need the information computed by <code>compute_nonlinear_residual()</code>.
1856   template <int dim>
refine_grid()1857   void PlasticityContactProblem<dim>::refine_grid()
1858   {
1859     if (refinement_strategy == RefinementStrategy::refine_global)
1860       {
1861         for (typename Triangulation<dim>::active_cell_iterator cell =
1862                triangulation.begin_active();
1863              cell != triangulation.end();
1864              ++cell)
1865           if (cell->is_locally_owned())
1866             cell->set_refine_flag();
1867       }
1868     else
1869       {
1870         Vector<float> estimated_error_per_cell(triangulation.n_active_cells());
1871         KellyErrorEstimator<dim>::estimate(
1872           dof_handler,
1873           QGauss<dim - 1>(fe.degree + 2),
1874           std::map<types::boundary_id, const Function<dim> *>(),
1875           solution,
1876           estimated_error_per_cell);
1877 
1878         parallel::distributed::GridRefinement ::refine_and_coarsen_fixed_number(
1879           triangulation, estimated_error_per_cell, 0.3, 0.03);
1880       }
1881 
1882     triangulation.prepare_coarsening_and_refinement();
1883 
1884     parallel::distributed::SolutionTransfer<dim, TrilinosWrappers::MPI::Vector>
1885       solution_transfer(dof_handler);
1886     if (transfer_solution)
1887       solution_transfer.prepare_for_coarsening_and_refinement(solution);
1888 
1889     triangulation.execute_coarsening_and_refinement();
1890 
1891     setup_system();
1892 
1893     if (transfer_solution)
1894       {
1895         TrilinosWrappers::MPI::Vector distributed_solution(locally_owned_dofs,
1896                                                            mpi_communicator);
1897         solution_transfer.interpolate(distributed_solution);
1898 
1899         // enforce constraints to make the interpolated solution conforming on
1900         // the new mesh:
1901         constraints_hanging_nodes.distribute(distributed_solution);
1902 
1903         solution = distributed_solution;
1904         compute_nonlinear_residual(solution);
1905       }
1906   }
1907 
1908 
1909   // @sect4{PlasticityContactProblem::move_mesh}
1910 
1911   // The remaining three functions before we get to <code>run()</code>
1912   // have to do with generating output. The following one is an attempt
1913   // at showing the deformed body in its deformed configuration. To this
1914   // end, this function takes a displacement vector field and moves every
1915   // vertex of the (local part) of the mesh by the previously computed
1916   // displacement. We will call this function with the current
1917   // displacement field before we generate graphical output, and we will
1918   // call it again after generating graphical output with the negative
1919   // displacement field to undo the changes to the mesh so made.
1920   //
1921   // The function itself is pretty straightforward. All we have to do
1922   // is keep track which vertices we have already touched, as we
1923   // encounter the same vertices multiple times as we loop over cells.
1924   template <int dim>
move_mesh(const TrilinosWrappers::MPI::Vector & displacement) const1925   void PlasticityContactProblem<dim>::move_mesh(
1926     const TrilinosWrappers::MPI::Vector &displacement) const
1927   {
1928     std::vector<bool> vertex_touched(triangulation.n_vertices(), false);
1929 
1930     for (const auto &cell : dof_handler.active_cell_iterators())
1931       if (cell->is_locally_owned())
1932         for (const auto v : cell->vertex_indices())
1933           if (vertex_touched[cell->vertex_index(v)] == false)
1934             {
1935               vertex_touched[cell->vertex_index(v)] = true;
1936 
1937               Point<dim> vertex_displacement;
1938               for (unsigned int d = 0; d < dim; ++d)
1939                 vertex_displacement[d] =
1940                   displacement(cell->vertex_dof_index(v, d));
1941 
1942               cell->vertex(v) += vertex_displacement;
1943             }
1944   }
1945 
1946 
1947 
1948   // @sect4{PlasticityContactProblem::output_results}
1949 
1950   // Next is the function we use to actually generate graphical output. The
1951   // function is a bit tedious, but not actually particularly complicated.
1952   // It moves the mesh at the top (and moves it back at the end), then
1953   // computes the contact forces along the contact surface. We can do
1954   // so (as shown in the accompanying paper) by taking the untreated
1955   // residual vector and identifying which degrees of freedom
1956   // correspond to those with contact by asking whether they have an
1957   // inhomogeneous constraints associated with them. As always, we need
1958   // to be mindful that we can only write into completely distributed
1959   // vectors (i.e., vectors without ghost elements) but that when we
1960   // want to generate output, we need vectors that do indeed have
1961   // ghost entries for all locally relevant degrees of freedom.
1962   template <int dim>
output_results(const unsigned int current_refinement_cycle)1963   void PlasticityContactProblem<dim>::output_results(
1964     const unsigned int current_refinement_cycle)
1965   {
1966     TimerOutput::Scope t(computing_timer, "Graphical output");
1967 
1968     pcout << "      Writing graphical output... " << std::flush;
1969 
1970     move_mesh(solution);
1971 
1972     // Calculation of the contact forces
1973     TrilinosWrappers::MPI::Vector distributed_lambda(locally_owned_dofs,
1974                                                      mpi_communicator);
1975     const unsigned int start_res = (newton_rhs_uncondensed.local_range().first),
1976                        end_res = (newton_rhs_uncondensed.local_range().second);
1977     for (unsigned int n = start_res; n < end_res; ++n)
1978       if (all_constraints.is_inhomogeneously_constrained(n))
1979         distributed_lambda(n) =
1980           newton_rhs_uncondensed(n) / diag_mass_matrix_vector(n);
1981     distributed_lambda.compress(VectorOperation::insert);
1982     constraints_hanging_nodes.distribute(distributed_lambda);
1983 
1984     TrilinosWrappers::MPI::Vector lambda(locally_relevant_dofs,
1985                                          mpi_communicator);
1986     lambda = distributed_lambda;
1987 
1988     TrilinosWrappers::MPI::Vector distributed_active_set_vector(
1989       locally_owned_dofs, mpi_communicator);
1990     distributed_active_set_vector = 0.;
1991     for (const auto index : active_set)
1992       distributed_active_set_vector[index] = 1.;
1993     distributed_lambda.compress(VectorOperation::insert);
1994 
1995     TrilinosWrappers::MPI::Vector active_set_vector(locally_relevant_dofs,
1996                                                     mpi_communicator);
1997     active_set_vector = distributed_active_set_vector;
1998 
1999     DataOut<dim> data_out;
2000 
2001     data_out.attach_dof_handler(dof_handler);
2002 
2003     const std::vector<DataComponentInterpretation::DataComponentInterpretation>
2004       data_component_interpretation(
2005         dim, DataComponentInterpretation::component_is_part_of_vector);
2006     data_out.add_data_vector(solution,
2007                              std::vector<std::string>(dim, "displacement"),
2008                              DataOut<dim>::type_dof_data,
2009                              data_component_interpretation);
2010     data_out.add_data_vector(lambda,
2011                              std::vector<std::string>(dim, "contact_force"),
2012                              DataOut<dim>::type_dof_data,
2013                              data_component_interpretation);
2014     data_out.add_data_vector(active_set_vector,
2015                              std::vector<std::string>(dim, "active_set"),
2016                              DataOut<dim>::type_dof_data,
2017                              data_component_interpretation);
2018 
2019     Vector<float> subdomain(triangulation.n_active_cells());
2020     for (unsigned int i = 0; i < subdomain.size(); ++i)
2021       subdomain(i) = triangulation.locally_owned_subdomain();
2022     data_out.add_data_vector(subdomain, "subdomain");
2023 
2024     data_out.add_data_vector(fraction_of_plastic_q_points_per_cell,
2025                              "fraction_of_plastic_q_points");
2026 
2027     data_out.build_patches();
2028 
2029     // In the remainder of the function, we generate one VTU file on
2030     // every processor, indexed by the subdomain id of this processor.
2031     // On the first processor, we then also create a <code>.pvtu</code>
2032     // file that indexes <i>all</i> of the VTU files so that the entire
2033     // set of output files can be read at once. These <code>.pvtu</code>
2034     // are used by Paraview to describe an entire parallel computation's
2035     // output files. We then do the same again for the competitor of
2036     // Paraview, the VisIt visualization program, by creating a matching
2037     // <code>.visit</code> file.
2038     const std::string pvtu_filename = data_out.write_vtu_with_pvtu_record(
2039       output_dir, "solution", current_refinement_cycle, mpi_communicator, 2);
2040     pcout << pvtu_filename << std::endl;
2041 
2042     TrilinosWrappers::MPI::Vector tmp(solution);
2043     tmp *= -1;
2044     move_mesh(tmp);
2045   }
2046 
2047 
2048   // @sect4{PlasticityContactProblem::output_contact_force}
2049 
2050   // This last auxiliary function computes the contact force by
2051   // calculating an integral over the contact pressure in z-direction
2052   // over the contact area. For this purpose we set the contact
2053   // pressure lambda to 0 for all inactive dofs (whether a degree
2054   // of freedom is part of the contact is determined just as
2055   // we did in the previous function). For all
2056   // active dofs, lambda contains the quotient of the nonlinear
2057   // residual (newton_rhs_uncondensed) and corresponding diagonal entry
2058   // of the mass matrix (diag_mass_matrix_vector). Because it is
2059   // not unlikely that hanging nodes show up in the contact area
2060   // it is important to apply constraints_hanging_nodes.distribute
2061   // to the distributed_lambda vector.
2062   template <int dim>
output_contact_force() const2063   void PlasticityContactProblem<dim>::output_contact_force() const
2064   {
2065     TrilinosWrappers::MPI::Vector distributed_lambda(locally_owned_dofs,
2066                                                      mpi_communicator);
2067     const unsigned int start_res = (newton_rhs_uncondensed.local_range().first),
2068                        end_res = (newton_rhs_uncondensed.local_range().second);
2069     for (unsigned int n = start_res; n < end_res; ++n)
2070       if (all_constraints.is_inhomogeneously_constrained(n))
2071         distributed_lambda(n) =
2072           newton_rhs_uncondensed(n) / diag_mass_matrix_vector(n);
2073       else
2074         distributed_lambda(n) = 0;
2075     distributed_lambda.compress(VectorOperation::insert);
2076     constraints_hanging_nodes.distribute(distributed_lambda);
2077 
2078     TrilinosWrappers::MPI::Vector lambda(locally_relevant_dofs,
2079                                          mpi_communicator);
2080     lambda = distributed_lambda;
2081 
2082     double contact_force = 0.0;
2083 
2084     QGauss<dim - 1>   face_quadrature_formula(fe.degree + 1);
2085     FEFaceValues<dim> fe_values_face(fe,
2086                                      face_quadrature_formula,
2087                                      update_values | update_JxW_values);
2088 
2089     const unsigned int n_face_q_points = face_quadrature_formula.size();
2090 
2091     const FEValuesExtractors::Vector displacement(0);
2092 
2093     for (const auto &cell : dof_handler.active_cell_iterators())
2094       if (cell->is_locally_owned())
2095         for (const auto &face : cell->face_iterators())
2096           if (face->at_boundary() && face->boundary_id() == 1)
2097             {
2098               fe_values_face.reinit(cell, face);
2099 
2100               std::vector<Tensor<1, dim>> lambda_values(n_face_q_points);
2101               fe_values_face[displacement].get_function_values(lambda,
2102                                                                lambda_values);
2103 
2104               for (unsigned int q_point = 0; q_point < n_face_q_points;
2105                    ++q_point)
2106                 contact_force +=
2107                   lambda_values[q_point][2] * fe_values_face.JxW(q_point);
2108             }
2109     contact_force = Utilities::MPI::sum(contact_force, MPI_COMM_WORLD);
2110 
2111     pcout << "Contact force = " << contact_force << std::endl;
2112   }
2113 
2114 
2115   // @sect4{PlasticityContactProblem::run}
2116 
2117   // As in all other tutorial programs, the <code>run()</code> function contains
2118   // the overall logic. There is not very much to it here: in essence, it
2119   // performs the loops over all mesh refinement cycles, and within each, hands
2120   // things over to the Newton solver in <code>solve_newton()</code> on the
2121   // current mesh and calls the function that creates graphical output for
2122   // the so-computed solution. It then outputs some statistics concerning both
2123   // run times and memory consumption that has been collected over the course of
2124   // computations on this mesh.
2125   template <int dim>
run()2126   void PlasticityContactProblem<dim>::run()
2127   {
2128     computing_timer.reset();
2129     for (; current_refinement_cycle < n_refinement_cycles;
2130          ++current_refinement_cycle)
2131       {
2132         {
2133           TimerOutput::Scope t(computing_timer, "Setup");
2134 
2135           pcout << std::endl;
2136           pcout << "Cycle " << current_refinement_cycle << ':' << std::endl;
2137 
2138           if (current_refinement_cycle == 0)
2139             {
2140               make_grid();
2141               setup_system();
2142             }
2143           else
2144             {
2145               TimerOutput::Scope t(computing_timer, "Setup: refine mesh");
2146               refine_grid();
2147             }
2148         }
2149 
2150         solve_newton();
2151 
2152         output_results(current_refinement_cycle);
2153 
2154         computing_timer.print_summary();
2155         computing_timer.reset();
2156 
2157         Utilities::System::MemoryStats stats;
2158         Utilities::System::get_memory_stats(stats);
2159         pcout << "Peak virtual memory used, resident in kB: " << stats.VmSize
2160               << " " << stats.VmRSS << std::endl;
2161 
2162         if (base_mesh == "box")
2163           output_contact_force();
2164       }
2165   }
2166 } // namespace Step42
2167 
2168 // @sect3{The <code>main</code> function}
2169 
2170 // There really isn't much to the <code>main()</code> function. It looks
2171 // like they always do:
main(int argc,char * argv[])2172 int main(int argc, char *argv[])
2173 {
2174   using namespace dealii;
2175   using namespace Step42;
2176 
2177   try
2178     {
2179       ParameterHandler prm;
2180       PlasticityContactProblem<3>::declare_parameters(prm);
2181       if (argc != 2)
2182         {
2183           std::cerr << "*** Call this program as <./step-42 input.prm>"
2184                     << std::endl;
2185           return 1;
2186         }
2187 
2188       prm.parse_input(argv[1]);
2189       Utilities::MPI::MPI_InitFinalize mpi_initialization(
2190         argc, argv, numbers::invalid_unsigned_int);
2191       {
2192         PlasticityContactProblem<3> problem(prm);
2193         problem.run();
2194       }
2195     }
2196   catch (std::exception &exc)
2197     {
2198       std::cerr << std::endl
2199                 << std::endl
2200                 << "----------------------------------------------------"
2201                 << std::endl;
2202       std::cerr << "Exception on processing: " << std::endl
2203                 << exc.what() << std::endl
2204                 << "Aborting!" << std::endl
2205                 << "----------------------------------------------------"
2206                 << std::endl;
2207 
2208       return 1;
2209     }
2210   catch (...)
2211     {
2212       std::cerr << std::endl
2213                 << std::endl
2214                 << "----------------------------------------------------"
2215                 << std::endl;
2216       std::cerr << "Unknown exception!" << std::endl
2217                 << "Aborting!" << std::endl
2218                 << "----------------------------------------------------"
2219                 << std::endl;
2220       return 1;
2221     }
2222 
2223   return 0;
2224 }
2225