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é
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