1 /* ---------------------------------------------------------------------
2 *
3 * Copyright (C) 2001 - 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 Heidelberg, 2001
18 */
19
20
21 // As usual, the program starts with a rather long list of include files which
22 // you are probably already used to by now:
23 #include <deal.II/base/quadrature_lib.h>
24 #include <deal.II/base/function.h>
25 #include <deal.II/base/logstream.h>
26 #include <deal.II/base/table_handler.h>
27 #include <deal.II/lac/vector.h>
28 #include <deal.II/lac/sparse_matrix.h>
29 #include <deal.II/lac/solver_cg.h>
30 #include <deal.II/lac/precondition.h>
31 #include <deal.II/lac/affine_constraints.h>
32 #include <deal.II/grid/tria.h>
33 #include <deal.II/grid/grid_generator.h>
34 #include <deal.II/grid/tria_accessor.h>
35 #include <deal.II/grid/tria_iterator.h>
36 #include <deal.II/dofs/dof_handler.h>
37 #include <deal.II/dofs/dof_accessor.h>
38 #include <deal.II/dofs/dof_tools.h>
39 #include <deal.II/fe/fe_q.h>
40 #include <deal.II/fe/fe_values.h>
41 #include <deal.II/fe/mapping_q.h>
42 #include <deal.II/numerics/vector_tools.h>
43 #include <deal.II/numerics/matrix_tools.h>
44 #include <deal.II/numerics/data_out.h>
45
46 // Just this one is new: it declares a class
47 // DynamicSparsityPattern, which we will use and explain
48 // further down below.
49 #include <deal.II/lac/dynamic_sparsity_pattern.h>
50
51 // We will make use of the std::find algorithm of the C++ standard library, so
52 // we have to include the following file for its declaration:
53 #include <algorithm>
54 #include <iostream>
55 #include <iomanip>
56 #include <cmath>
57
58 // The last step is as in all previous programs:
59 namespace Step11
60 {
61 using namespace dealii;
62
63 // Then we declare a class which represents the solution of a Laplace
64 // problem. As this example program is based on step-5, the class looks
65 // rather the same, with the sole structural difference that the functions
66 // <code>assemble_system</code> now calls <code>solve</code> itself, and is
67 // thus called <code>assemble_and_solve</code>, and that the output function
68 // was dropped since the solution function is so boring that it is not worth
69 // being viewed.
70 //
71 // The only other noteworthy change is that the constructor takes a value
72 // representing the polynomial degree of the mapping to be used later on,
73 // and that it has another member variable representing exactly this
74 // mapping. In general, this variable will occur in real applications at the
75 // same places where the finite element is declared or used.
76 template <int dim>
77 class LaplaceProblem
78 {
79 public:
80 LaplaceProblem(const unsigned int mapping_degree);
81 void run();
82
83 private:
84 void setup_system();
85 void assemble_and_solve();
86 void solve();
87 void write_high_order_mesh(const unsigned cycle);
88
89 Triangulation<dim> triangulation;
90 FE_Q<dim> fe;
91 DoFHandler<dim> dof_handler;
92 MappingQ<dim> mapping;
93
94 SparsityPattern sparsity_pattern;
95 SparseMatrix<double> system_matrix;
96 AffineConstraints<double> mean_value_constraints;
97
98 Vector<double> solution;
99 Vector<double> system_rhs;
100
101 TableHandler output_table;
102 };
103
104
105
106 // Construct such an object, by initializing the variables. Here, we use
107 // linear finite elements (the argument to the <code>fe</code> variable
108 // denotes the polynomial degree), and mappings of given order. Print to
109 // screen what we are about to do.
110 template <int dim>
LaplaceProblem(const unsigned int mapping_degree)111 LaplaceProblem<dim>::LaplaceProblem(const unsigned int mapping_degree)
112 : fe(1)
113 , dof_handler(triangulation)
114 , mapping(mapping_degree)
115 {
116 std::cout << "Using mapping with degree " << mapping_degree << ":"
117 << std::endl
118 << "============================" << std::endl;
119 }
120
121
122
123 // The first task is to set up the variables for this problem. This includes
124 // generating a valid <code>DoFHandler</code> object, as well as the
125 // sparsity patterns for the matrix, and the object representing the
126 // constraints that the mean value of the degrees of freedom on the boundary
127 // be zero.
128 template <int dim>
setup_system()129 void LaplaceProblem<dim>::setup_system()
130 {
131 // The first task is trivial: generate an enumeration of the degrees of
132 // freedom, and initialize solution and right hand side vector to their
133 // correct sizes:
134 dof_handler.distribute_dofs(fe);
135 solution.reinit(dof_handler.n_dofs());
136 system_rhs.reinit(dof_handler.n_dofs());
137
138 // Next task is to construct the object representing the constraint that
139 // the mean value of the degrees of freedom on the boundary shall be
140 // zero. For this, we first want a list of those nodes which are actually
141 // at the boundary. The <code>DoFTools</code> namespace has a function
142 // that returns an array of Boolean values where <code>true</code>
143 // indicates that the node is at the boundary. The second argument denotes
144 // a mask selecting which components of vector valued finite elements we
145 // want to be considered. This sort of information is encoded using the
146 // ComponentMask class (see also @ref GlossComponentMask). Since we have a
147 // scalar finite element anyway, this mask in reality should have only one
148 // entry with a <code>true</code> value. However, the ComponentMask class
149 // has semantics that allow it to represents a mask of indefinite size
150 // whose every element equals <code>true</code> when one just default
151 // constructs such an object, so this is what we'll do here.
152 std::vector<bool> boundary_dofs(dof_handler.n_dofs(), false);
153 DoFTools::extract_boundary_dofs(dof_handler,
154 ComponentMask(),
155 boundary_dofs);
156
157 // Now first for the generation of the constraints: as mentioned in the
158 // introduction, we constrain one of the nodes on the boundary by the
159 // values of all other DoFs on the boundary. So, let us first pick out the
160 // first boundary node from this list. We do that by searching for the
161 // first <code>true</code> value in the array (note that
162 // <code>std::find</code> returns an iterator to this element), and
163 // computing its distance to the overall first element in the array to get
164 // its index:
165 const unsigned int first_boundary_dof = std::distance(
166 boundary_dofs.begin(),
167 std::find(boundary_dofs.begin(), boundary_dofs.end(), true));
168
169 // Then generate a constraints object with just this one constraint. First
170 // clear all previous content (which might reside there from the previous
171 // computation on a once coarser grid), then add this one line
172 // constraining the <code>first_boundary_dof</code> to the sum of other
173 // boundary DoFs each with weight -1. Finally, close the constraints
174 // object, i.e. do some internal bookkeeping on it for faster processing
175 // of what is to come later:
176 mean_value_constraints.clear();
177 mean_value_constraints.add_line(first_boundary_dof);
178 for (unsigned int i = first_boundary_dof + 1; i < dof_handler.n_dofs(); ++i)
179 if (boundary_dofs[i] == true)
180 mean_value_constraints.add_entry(first_boundary_dof, i, -1);
181 mean_value_constraints.close();
182
183 // Next task is to generate a sparsity pattern. This is indeed a tricky
184 // task here. Usually, we just call
185 // <code>DoFTools::make_sparsity_pattern</code> and condense the result
186 // using the hanging node constraints. We have no hanging node constraints
187 // here (since we only refine globally in this example), but we have this
188 // global constraint on the boundary. This poses one severe problem in
189 // this context: the <code>SparsityPattern</code> class wants us to state
190 // beforehand the maximal number of entries per row, either for all rows
191 // or for each row separately. There are functions in the library which
192 // can tell you this number in case you just have hanging node constraints
193 // (namely DoFHandler::max_couplings_between_dofs), but how is
194 // this for the present case? The difficulty arises because the
195 // elimination of the constrained degree of freedom requires a number of
196 // additional entries in the matrix at places that are not so simple to
197 // determine. We would therefore have a problem had we to give a maximal
198 // number of entries per row here.
199 //
200 // Since this can be so difficult that no reasonable answer can be given
201 // that allows allocation of only a reasonable amount of memory, there is
202 // a class DynamicSparsityPattern, that can help us out
203 // here. It does not require that we know in advance how many entries rows
204 // could have, but allows just about any length. It is thus significantly
205 // more flexible in case you do not have good estimates of row lengths,
206 // however at the price that building up such a pattern is also
207 // significantly more expensive than building up a pattern for which you
208 // had information in advance. Nevertheless, as we have no other choice
209 // here, we'll just build such an object by initializing it with the
210 // dimensions of the matrix and calling another function
211 // <code>DoFTools::make_sparsity_pattern</code> to get the sparsity
212 // pattern due to the differential operator, then condense it with the
213 // constraints object which adds those positions in the sparsity pattern
214 // that are required for the elimination of the constraint.
215 DynamicSparsityPattern dsp(dof_handler.n_dofs(), dof_handler.n_dofs());
216 DoFTools::make_sparsity_pattern(dof_handler, dsp);
217 mean_value_constraints.condense(dsp);
218
219 // Finally, once we have the full pattern, we can initialize an object of
220 // type <code>SparsityPattern</code> from it and in turn initialize the
221 // matrix with it. Note that this is actually necessary, since the
222 // DynamicSparsityPattern is so inefficient compared to
223 // the <code>SparsityPattern</code> class due to the more flexible data
224 // structures it has to use, that we can impossibly base the sparse matrix
225 // class on it, but rather need an object of type
226 // <code>SparsityPattern</code>, which we generate by copying from the
227 // intermediate object.
228 //
229 // As a further sidenote, you will notice that we do not explicitly have
230 // to <code>compress</code> the sparsity pattern here. This, of course, is
231 // due to the fact that the <code>copy_from</code> function generates a
232 // compressed object right from the start, to which you cannot add new
233 // entries anymore. The <code>compress</code> call is therefore implicit
234 // in the <code>copy_from</code> call.
235 sparsity_pattern.copy_from(dsp);
236 system_matrix.reinit(sparsity_pattern);
237 }
238
239
240
241 // The next function then assembles the linear system of equations, solves
242 // it, and evaluates the solution. This then makes three actions, and we
243 // will put them into eight true statements (excluding declaration of
244 // variables, and handling of temporary vectors). Thus, this function is
245 // something for the very lazy. Nevertheless, the functions called are
246 // rather powerful, and through them this function uses a good deal of the
247 // whole library. But let's look at each of the steps.
248 template <int dim>
assemble_and_solve()249 void LaplaceProblem<dim>::assemble_and_solve()
250 {
251 // First, we have to assemble the matrix and the right hand side. In all
252 // previous examples, we have investigated various ways how to do this
253 // manually. However, since the Laplace matrix and simple right hand sides
254 // appear so frequently in applications, the library provides functions
255 // for actually doing this for you, i.e. they perform the loop over all
256 // cells, setting up the local matrices and vectors, and putting them
257 // together for the end result.
258 //
259 // The following are the two most commonly used ones: creation of the
260 // Laplace matrix and creation of a right hand side vector from body or
261 // boundary forces. They take the mapping object, the
262 // <code>DoFHandler</code> object representing the degrees of freedom and
263 // the finite element in use, a quadrature formula to be used, and the
264 // output object. The function that creates a right hand side vector also
265 // has to take a function object describing the (continuous) right hand
266 // side function.
267 //
268 // Let us look at the way the matrix and body forces are integrated:
269 const unsigned int gauss_degree =
270 std::max(static_cast<unsigned int>(
271 std::ceil(1. * (mapping.get_degree() + 1) / 2)),
272 2U);
273 MatrixTools::create_laplace_matrix(mapping,
274 dof_handler,
275 QGauss<dim>(gauss_degree),
276 system_matrix);
277 VectorTools::create_right_hand_side(mapping,
278 dof_handler,
279 QGauss<dim>(gauss_degree),
280 Functions::ConstantFunction<dim>(-2),
281 system_rhs);
282 // That's quite simple, right?
283 //
284 // Two remarks are in order, though: First, these functions are used in a
285 // lot of contexts. Maybe you want to create a Laplace or mass matrix for
286 // a vector values finite element; or you want to use the default Q1
287 // mapping; or you want to assembled the matrix with a coefficient in the
288 // Laplace operator. For this reason, there are quite a large number of
289 // variants of these functions in the <code>MatrixCreator</code> and
290 // <code>MatrixTools</code> namespaces. Whenever you need a slightly
291 // different version of these functions than the ones called above, it is
292 // certainly worthwhile to take a look at the documentation and to check
293 // whether something fits your needs.
294 //
295 // The second remark concerns the quadrature formula we use: we want to
296 // integrate over bilinear shape functions, so we know that we have to use
297 // at least an order two Gauss quadrature formula. On the other hand, we
298 // want the quadrature rule to have at least the order of the boundary
299 // approximation. Since the order of Gauss rule with $r$ points is $2r -
300 // 1$, and the order of the boundary approximation using polynomials of
301 // degree $p$ is $p+1$, we know that $2r \geq p$. Since r has to be an
302 // integer and (as mentioned above) has to be at least $2$, this makes up
303 // for the formula above computing <code>gauss_degree</code>.
304 //
305 // Since the generation of the body force contributions to the right hand
306 // side vector was so simple, we do that all over again for the boundary
307 // forces as well: allocate a vector of the right size and call the right
308 // function. The boundary function has constant values, so we can generate
309 // an object from the library on the fly, and we use the same quadrature
310 // formula as above, but this time of lower dimension since we integrate
311 // over faces now instead of cells:
312 Vector<double> tmp(system_rhs.size());
313 VectorTools::create_boundary_right_hand_side(
314 mapping,
315 dof_handler,
316 QGauss<dim - 1>(gauss_degree),
317 Functions::ConstantFunction<dim>(1),
318 tmp);
319 // Then add the contributions from the boundary to those from the interior
320 // of the domain:
321 system_rhs += tmp;
322 // For assembling the right hand side, we had to use two different vector
323 // objects, and later add them together. The reason we had to do so is
324 // that the <code>VectorTools::create_right_hand_side</code> and
325 // <code>VectorTools::create_boundary_right_hand_side</code> functions
326 // first clear the output vector, rather than adding up their results to
327 // previous contents. This can reasonably be called a design flaw in the
328 // library made in its infancy, but unfortunately things are as they are
329 // for some time now and it is difficult to change such things that
330 // silently break existing code, so we have to live with that.
331
332 // Now, the linear system is set up, so we can eliminate the one degree of
333 // freedom which we constrained to the other DoFs on the boundary for the
334 // mean value constraint from matrix and right hand side vector, and solve
335 // the system. After that, distribute the constraints again, which in this
336 // case means setting the constrained degree of freedom to its proper
337 // value
338 mean_value_constraints.condense(system_matrix);
339 mean_value_constraints.condense(system_rhs);
340
341 solve();
342 mean_value_constraints.distribute(solution);
343
344 // Finally, evaluate what we got as solution. As stated in the
345 // introduction, we are interested in the H1 semi-norm of the
346 // solution. Here, as well, we have a function in the library that does
347 // this, although in a slightly non-obvious way: the
348 // <code>VectorTools::integrate_difference</code> function integrates the
349 // norm of the difference between a finite element function and a
350 // continuous function. If we therefore want the norm of a finite element
351 // field, we just put the continuous function to zero. Note that this
352 // function, just as so many other ones in the library as well, has at
353 // least two versions, one which takes a mapping as argument (which we
354 // make us of here), and the one which we have used in previous examples
355 // which implicitly uses <code>MappingQ1</code>. Also note that we take a
356 // quadrature formula of one degree higher, in order to avoid
357 // superconvergence effects where the solution happens to be especially
358 // close to the exact solution at certain points (we don't know whether
359 // this might be the case here, but there are cases known of this, and we
360 // just want to make sure):
361 Vector<float> norm_per_cell(triangulation.n_active_cells());
362 VectorTools::integrate_difference(mapping,
363 dof_handler,
364 solution,
365 Functions::ZeroFunction<dim>(),
366 norm_per_cell,
367 QGauss<dim>(gauss_degree + 1),
368 VectorTools::H1_seminorm);
369 // Then, the function just called returns its results as a vector of
370 // values each of which denotes the norm on one cell. To get the global
371 // norm, we do the following:
372 const double norm =
373 VectorTools::compute_global_error(triangulation,
374 norm_per_cell,
375 VectorTools::H1_seminorm);
376
377 // Last task -- generate output:
378 output_table.add_value("cells", triangulation.n_active_cells());
379 output_table.add_value("|u|_1", norm);
380 output_table.add_value("error",
381 std::fabs(norm - std::sqrt(3.14159265358 / 2)));
382 }
383
384
385
386 // The following function solving the linear system of equations is copied
387 // from step-5 and is explained there in some detail:
388 template <int dim>
solve()389 void LaplaceProblem<dim>::solve()
390 {
391 SolverControl solver_control(1000, 1e-12);
392 SolverCG<Vector<double>> cg(solver_control);
393
394 PreconditionSSOR<SparseMatrix<double>> preconditioner;
395 preconditioner.initialize(system_matrix, 1.2);
396
397 cg.solve(system_matrix, solution, system_rhs, preconditioner);
398 }
399
400
401
402 // Next, we write the solution as well as the
403 // material ids to a VTU file. This is similar to what was done in many
404 // other tutorial programs. The new ingredient presented in this tutorial
405 // program is that we want to ensure that the data written to the file
406 // used for visualization is actually a faithful representation of what
407 // is used internally by deal.II. That is because most of the visualization
408 // data formats only represent cells by their vertex coordinates, but
409 // have no way of representing the curved boundaries that are used
410 // in deal.II when using higher order mappings -- in other words, what
411 // you see in the visualization tool is not actually what you are computing
412 // on. (The same, incidentally, is true when using higher order shape
413 // functions: Most visualization tools only render bilinear/trilinear
414 // representations. This is discussed in detail in DataOut::build_patches().)
415 //
416 // So we need to ensure that a high-order representation is written
417 // to the file. We need to consider two particular topics. Firstly, we tell
418 // the DataOut object via the DataOutBase::VtkFlags that we intend to
419 // interpret the subdivisions of the elements as a high-order Lagrange
420 // polynomial rather than a collection of bilinear patches.
421 // Recent visualization programs, like ParaView version 5.5
422 // or newer, can then render a high-order solution (see a <a
423 // href="https://github.com/dealii/dealii/wiki/Notes-on-visualizing-high-order-output">wiki
424 // page</a> for more details). Secondly, we need to make sure that the mapping
425 // is passed to the DataOut::build_patches() method. Finally, the DataOut
426 // class only prints curved faces for <i>boundary</i> cells by default, so we
427 // need to ensure that also inner cells are printed in a curved representation
428 // via the mapping.
429 template <int dim>
write_high_order_mesh(const unsigned cycle)430 void LaplaceProblem<dim>::write_high_order_mesh(const unsigned cycle)
431 {
432 DataOut<dim> data_out;
433
434 DataOutBase::VtkFlags flags;
435 flags.write_higher_order_cells = true;
436 data_out.set_flags(flags);
437
438 data_out.attach_dof_handler(dof_handler);
439 data_out.add_data_vector(solution, "solution");
440
441 data_out.build_patches(mapping,
442 mapping.get_degree(),
443 DataOut<dim>::curved_inner_cells);
444
445 std::ofstream file("solution-c=" + std::to_string(cycle) +
446 ".p=" + std::to_string(mapping.get_degree()) + ".vtu");
447
448 data_out.write_vtu(file);
449 }
450
451
452 // Finally the main function controlling the different steps to be
453 // performed. Its content is rather straightforward, generating a
454 // triangulation of a circle, associating a boundary to it, and then doing
455 // several cycles on subsequently finer grids. Note again that we have put
456 // mesh refinement into the loop header; this may be something for a test
457 // program, but for real applications you should consider that this implies
458 // that the mesh is refined after the loop is executed the last time since
459 // the increment clause (the last part of the three-parted loop header) is
460 // executed before the comparison part (the second one), which may be rather
461 // costly if the mesh is already quite refined. In that case, you should
462 // arrange code such that the mesh is not further refined after the last
463 // loop run (or you should do it at the beginning of each run except for the
464 // first one).
465 template <int dim>
run()466 void LaplaceProblem<dim>::run()
467 {
468 GridGenerator::hyper_ball(triangulation);
469
470 for (unsigned int cycle = 0; cycle < 6; ++cycle)
471 {
472 setup_system();
473 assemble_and_solve();
474 write_high_order_mesh(cycle);
475
476 triangulation.refine_global();
477 }
478
479 // After all the data is generated, write a table of results to the
480 // screen:
481 output_table.set_precision("|u|_1", 6);
482 output_table.set_precision("error", 6);
483 output_table.write_text(std::cout);
484 std::cout << std::endl;
485 }
486 } // namespace Step11
487
488
489
490 // Finally the main function. It's structure is the same as that used in
491 // several of the previous examples, so probably needs no more explanation.
main()492 int main()
493 {
494 try
495 {
496 std::cout.precision(5);
497
498 // This is the main loop, doing the computations with mappings of linear
499 // through cubic mappings. Note that since we need the object of type
500 // <code>LaplaceProblem@<2@></code> only once, we do not even name it,
501 // but create an unnamed such object and call the <code>run</code>
502 // function of it, subsequent to which it is immediately destroyed
503 // again.
504 for (unsigned int mapping_degree = 1; mapping_degree <= 3;
505 ++mapping_degree)
506 Step11::LaplaceProblem<2>(mapping_degree).run();
507 }
508 catch (std::exception &exc)
509 {
510 std::cerr << std::endl
511 << std::endl
512 << "----------------------------------------------------"
513 << std::endl;
514 std::cerr << "Exception on processing: " << std::endl
515 << exc.what() << std::endl
516 << "Aborting!" << std::endl
517 << "----------------------------------------------------"
518 << std::endl;
519 return 1;
520 }
521 catch (...)
522 {
523 std::cerr << std::endl
524 << std::endl
525 << "----------------------------------------------------"
526 << std::endl;
527 std::cerr << "Unknown exception!" << std::endl
528 << "Aborting!" << std::endl
529 << "----------------------------------------------------"
530 << std::endl;
531 return 1;
532 };
533
534 return 0;
535 }
536