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