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