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 * Authors: Wolfgang Bangerth, Ralf Hartmann, University of Heidelberg, 2001
18 */
19
20
21 // The first of the following include files are probably well-known by now and
22 // need no further explanation.
23 #include <deal.II/base/quadrature_lib.h>
24 #include <deal.II/base/convergence_table.h>
25 #include <deal.II/grid/grid_generator.h>
26 #include <deal.II/grid/tria_accessor.h>
27 #include <deal.II/grid/tria_iterator.h>
28 #include <deal.II/grid/manifold_lib.h>
29 #include <deal.II/grid/tria.h>
30 #include <deal.II/grid/grid_out.h>
31 #include <deal.II/dofs/dof_handler.h>
32 #include <deal.II/dofs/dof_accessor.h>
33 #include <deal.II/fe/fe_values.h>
34
35 // This include file is new. Even if we are not solving a PDE in this tutorial,
36 // we want to use a dummy finite element with zero degrees of freedoms provided
37 // by the FE_Nothing class.
38 #include <deal.II/fe/fe_nothing.h>
39
40 // The following header file is also new: in it, we declare the MappingQ class
41 // which we will use for polynomial mappings of arbitrary order:
42 #include <deal.II/fe/mapping_q.h>
43
44 // And this again is C++:
45 #include <iostream>
46 #include <fstream>
47 #include <cmath>
48
49 // The last step is as in previous programs:
50 namespace Step10
51 {
52 using namespace dealii;
53
54 // Now, as we want to compute the value of $\pi$, we have to compare to
55 // something. These are the first few digits of $\pi$, which we define
56 // beforehand for later use. Since we would like to compute the difference
57 // between two numbers which are quite accurate, with the accuracy of the
58 // computed approximation to $\pi$ being in the range of the number of
59 // digits which a double variable can hold, we rather declare the reference
60 // value as a <code>long double</code> and give it a number of extra digits:
61 const long double pi = 3.141592653589793238462643L;
62
63
64
65 // Then, the first task will be to generate some output. Since this program
66 // is so small, we do not employ object oriented techniques in it and do not
67 // declare classes (although, of course, we use the object oriented features
68 // of the library). Rather, we just pack the functionality into separate
69 // functions. We make these functions templates on the number of space
70 // dimensions to conform to usual practice when using deal.II, although we
71 // will only use them for two space dimensions.
72 //
73 // The first of these functions just generates a triangulation of a circle
74 // (hyperball) and outputs the $Q_p$ mapping of its cells for different values
75 // of <code>p</code>. Then, we refine the grid once and do so again.
76 template <int dim>
gnuplot_output()77 void gnuplot_output()
78 {
79 std::cout << "Output of grids into gnuplot files:" << std::endl
80 << "===================================" << std::endl;
81
82 // So first generate a coarse triangulation of the circle and associate a
83 // suitable boundary description to it. By default,
84 // GridGenerator::hyper_ball attaches a SphericalManifold to the boundary
85 // (and uses FlatManifold for the interior) so we simply call that
86 // function and move on:
87 Triangulation<dim> triangulation;
88 GridGenerator::hyper_ball(triangulation);
89
90 // Then alternate between generating output on the current mesh
91 // for $Q_1$, $Q_2$, and $Q_3$ mappings, and (at the end of the
92 // loop body) refining the mesh once globally.
93 for (unsigned int refinement = 0; refinement < 2; ++refinement)
94 {
95 std::cout << "Refinement level: " << refinement << std::endl;
96
97 std::string filename_base = "ball_" + std::to_string(refinement);
98
99 for (unsigned int degree = 1; degree < 4; ++degree)
100 {
101 std::cout << "Degree = " << degree << std::endl;
102
103 // For this, first set up an object describing the mapping. This
104 // is done using the MappingQ class, which takes as
105 // argument to the constructor the polynomial degree which it
106 // shall use.
107 const MappingQ<dim> mapping(degree);
108 // As a side note, for a piecewise linear mapping, you
109 // could give a value of <code>1</code> to the constructor
110 // of MappingQ, but there is also a class MappingQ1 that
111 // achieves the same effect. Historically, it did a lot of
112 // things in a simpler way than MappingQ but is today just
113 // a wrapper around the latter. It is, however, still the
114 // class that is used implicitly in many places of the
115 // library if you do not specify another mapping
116 // explicitly.
117
118
119 // In order to actually write out the present grid with this
120 // mapping, we set up an object which we will use for output. We
121 // will generate Gnuplot output, which consists of a set of lines
122 // describing the mapped triangulation. By default, only one line
123 // is drawn for each face of the triangulation, but since we want
124 // to explicitly see the effect of the mapping, we want to have
125 // the faces in more detail. This can be done by passing the
126 // output object a structure which contains some flags. In the
127 // present case, since Gnuplot can only draw straight lines, we
128 // output a number of additional points on the faces so that each
129 // face is drawn by 30 small lines instead of only one. This is
130 // sufficient to give us the impression of seeing a curved line,
131 // rather than a set of straight lines.
132 GridOut grid_out;
133 GridOutFlags::Gnuplot gnuplot_flags(false, 60);
134 grid_out.set_flags(gnuplot_flags);
135
136 // Finally, generate a filename and a file for output:
137 std::string filename =
138 filename_base + "_mapping_q_" + std::to_string(degree) + ".dat";
139 std::ofstream gnuplot_file(filename);
140
141 // Then write out the triangulation to this file. The last
142 // argument of the function is a pointer to a mapping object. This
143 // argument has a default value, and if no value is given a simple
144 // MappingQ1 object is taken, which we briefly
145 // described above. This would then result in a piecewise linear
146 // approximation of the true boundary in the output.
147 grid_out.write_gnuplot(triangulation, gnuplot_file, &mapping);
148 }
149 std::cout << std::endl;
150
151 // At the end of the loop, refine the mesh globally.
152 triangulation.refine_global();
153 }
154 }
155
156 // Now we proceed with the main part of the code, the approximation of
157 // $\pi$. The area of a circle is of course given by $\pi r^2$, so having a
158 // circle of radius 1, the area represents just the number that is searched
159 // for. The numerical computation of the area is performed by integrating
160 // the constant function of value 1 over the whole computational domain,
161 // i.e. by computing the areas $\int_K 1 dx=\int_{\hat K} 1
162 // \ \textrm{det}\ J(\hat x) d\hat x \approx \sum_i \textrm{det}
163 // \ J(\hat x_i)w(\hat x_i)$,
164 // where the sum extends over all quadrature points on all active cells in
165 // the triangulation, with $w(x_i)$ being the weight of quadrature point
166 // $x_i$. The integrals on each cell are approximated by numerical
167 // quadrature, hence the only additional ingredient we need is to set up a
168 // FEValues object that provides the corresponding `JxW' values of each
169 // cell. (Note that `JxW' is meant to abbreviate <i>Jacobian determinant
170 // times weight</i>; since in numerical quadrature the two factors always
171 // occur at the same places, we only offer the combined quantity, rather
172 // than two separate ones.) We note that here we won't use the FEValues
173 // object in its original purpose, i.e. for the computation of values of
174 // basis functions of a specific finite element at certain quadrature
175 // points. Rather, we use it only to gain the `JxW' at the quadrature
176 // points, irrespective of the (dummy) finite element we will give to the
177 // constructor of the FEValues object. The actual finite element given to
178 // the FEValues object is not used at all, so we could give any.
179 template <int dim>
compute_pi_by_area()180 void compute_pi_by_area()
181 {
182 std::cout << "Computation of Pi by the area:" << std::endl
183 << "==============================" << std::endl;
184
185 // For the numerical quadrature on all cells we employ a quadrature rule
186 // of sufficiently high degree. We choose QGauss that is of order 8 (4
187 // points), to be sure that the errors due to numerical quadrature are of
188 // higher order than the order (maximal 6) that will occur due to the
189 // order of the approximation of the boundary, i.e. the order of the
190 // mappings employed. Note that the integrand, the Jacobian determinant,
191 // is not a polynomial function (rather, it is a rational one), so we do
192 // not use Gauss quadrature in order to get the exact value of the
193 // integral as done often in finite element computations, but could as
194 // well have used any quadrature formula of like order instead.
195 const QGauss<dim> quadrature(4);
196
197 // Now start by looping over polynomial mapping degrees=1..4:
198 for (unsigned int degree = 1; degree < 5; ++degree)
199 {
200 std::cout << "Degree = " << degree << std::endl;
201
202 // First generate the triangulation, the boundary and the mapping
203 // object as already seen.
204 Triangulation<dim> triangulation;
205 GridGenerator::hyper_ball(triangulation);
206
207 const MappingQ<dim> mapping(degree);
208
209 // We now create a finite element. Unlike the rest of the example
210 // programs, we do not actually need to do any computations with shape
211 // functions; we only need the `JxW' values from an FEValues
212 // object. Hence we use the special finite element class FE_Nothing
213 // which has exactly zero degrees of freedom per cell (as the name
214 // implies, the local basis on each cell is the empty set). A more
215 // typical usage of FE_Nothing is shown in step-46.
216 const FE_Nothing<dim> fe;
217
218 // Likewise, we need to create a DoFHandler object. We do not actually
219 // use it, but it will provide us with `active_cell_iterators' that
220 // are needed to reinitialize the FEValues object on each cell of the
221 // triangulation.
222 DoFHandler<dim> dof_handler(triangulation);
223
224 // Now we set up the FEValues object, giving the Mapping, the dummy
225 // finite element and the quadrature object to the constructor,
226 // together with the update flags asking for the `JxW' values at the
227 // quadrature points only. This tells the FEValues object that it
228 // needs not compute other quantities upon calling the
229 // <code>reinit</code> function, thus saving computation time.
230 //
231 // The most important difference in the construction of the FEValues
232 // object compared to previous example programs is that we pass a
233 // mapping object as first argument, which is to be used in the
234 // computation of the mapping from unit to real cell. In previous
235 // examples, this argument was omitted, resulting in the implicit use
236 // of an object of type MappingQ1.
237 FEValues<dim> fe_values(mapping, fe, quadrature, update_JxW_values);
238
239 // We employ an object of the ConvergenceTable class to store all
240 // important data like the approximated values for $\pi$ and the error
241 // with respect to the true value of $\pi$. We will also use functions
242 // provided by the ConvergenceTable class to compute convergence rates
243 // of the approximations to $\pi$.
244 ConvergenceTable table;
245
246 // Now we loop over several refinement steps of the triangulation.
247 for (unsigned int refinement = 0; refinement < 6;
248 ++refinement, triangulation.refine_global(1))
249 {
250 // In this loop we first add the number of active cells of the
251 // current triangulation to the table. This function automatically
252 // creates a table column with superscription `cells', in case
253 // this column was not created before.
254 table.add_value("cells", triangulation.n_active_cells());
255
256 // Then we distribute the degrees of freedom for the dummy finite
257 // element. Strictly speaking we do not need this function call in
258 // our special case but we call it to make the DoFHandler happy --
259 // otherwise it would throw an assertion in the FEValues::reinit
260 // function below.
261 dof_handler.distribute_dofs(fe);
262
263 // We define the variable area as `long double' like we did for
264 // the pi variable before.
265 long double area = 0;
266
267 // Now we loop over all cells, reinitialize the FEValues object
268 // for each cell, and add up all the `JxW' values for this cell to
269 // `area'...
270 for (const auto &cell : dof_handler.active_cell_iterators())
271 {
272 fe_values.reinit(cell);
273 for (unsigned int i = 0; i < fe_values.n_quadrature_points; ++i)
274 area += static_cast<long double>(fe_values.JxW(i));
275 }
276
277 // ...and store the resulting area values and the errors in the
278 // table. We need a static cast to double as there is no
279 // add_value(string, long double) function implemented. Note that
280 // this also concerns the second call as the <code>fabs</code>
281 // function in the <code>std</code> namespace is overloaded on its
282 // argument types, so there exists a version taking and returning
283 // a <code>long double</code>, in contrast to the global namespace
284 // where only one such function is declared (which takes and
285 // returns a double).
286 table.add_value("eval.pi", static_cast<double>(area));
287 table.add_value("error", static_cast<double>(std::fabs(area - pi)));
288 }
289
290 // We want to compute the convergence rates of the `error'
291 // column. Therefore we need to omit the other columns from the
292 // convergence rate evaluation before calling
293 // `evaluate_all_convergence_rates'
294 table.omit_column_from_convergence_rate_evaluation("cells");
295 table.omit_column_from_convergence_rate_evaluation("eval.pi");
296 table.evaluate_all_convergence_rates(
297 ConvergenceTable::reduction_rate_log2);
298
299 // Finally we set the precision and scientific mode for output of some
300 // of the quantities...
301 table.set_precision("eval.pi", 16);
302 table.set_scientific("error", true);
303
304 // ...and write the whole table to std::cout.
305 table.write_text(std::cout);
306
307 std::cout << std::endl;
308 }
309 }
310
311
312 // The following, second function also computes an approximation of $\pi$
313 // but this time via the perimeter $2\pi r$ of the domain instead of the
314 // area. This function is only a variation of the previous function. So we
315 // will mainly give documentation for the differences.
316 template <int dim>
compute_pi_by_perimeter()317 void compute_pi_by_perimeter()
318 {
319 std::cout << "Computation of Pi by the perimeter:" << std::endl
320 << "===================================" << std::endl;
321
322 // We take the same order of quadrature but this time a `dim-1'
323 // dimensional quadrature as we will integrate over (boundary) lines
324 // rather than over cells.
325 const QGauss<dim - 1> quadrature(4);
326
327 // We loop over all degrees, create the triangulation, the boundary, the
328 // mapping, the dummy finite element and the DoFHandler object as seen
329 // before.
330 for (unsigned int degree = 1; degree < 5; ++degree)
331 {
332 std::cout << "Degree = " << degree << std::endl;
333 Triangulation<dim> triangulation;
334 GridGenerator::hyper_ball(triangulation);
335
336 const MappingQ<dim> mapping(degree);
337 const FE_Nothing<dim> fe;
338
339 DoFHandler<dim> dof_handler(triangulation);
340
341 // Then we create a FEFaceValues object instead of a FEValues object
342 // as in the previous function. Again, we pass a mapping as first
343 // argument.
344 FEFaceValues<dim> fe_face_values(mapping,
345 fe,
346 quadrature,
347 update_JxW_values);
348 ConvergenceTable table;
349
350 for (unsigned int refinement = 0; refinement < 6;
351 ++refinement, triangulation.refine_global(1))
352 {
353 table.add_value("cells", triangulation.n_active_cells());
354
355 dof_handler.distribute_dofs(fe);
356
357 // Now we run over all cells and over all faces of each cell. Only
358 // the contributions of the `JxW' values on boundary faces are
359 // added to the long double variable `perimeter'.
360 long double perimeter = 0;
361 for (const auto &cell : dof_handler.active_cell_iterators())
362 for (const auto &face : cell->face_iterators())
363 if (face->at_boundary())
364 {
365 // We reinit the FEFaceValues object with the cell
366 // iterator and the number of the face.
367 fe_face_values.reinit(cell, face);
368 for (unsigned int i = 0;
369 i < fe_face_values.n_quadrature_points;
370 ++i)
371 perimeter +=
372 static_cast<long double>(fe_face_values.JxW(i));
373 }
374 // Then store the evaluated values in the table...
375 table.add_value("eval.pi", static_cast<double>(perimeter / 2.0L));
376 table.add_value(
377 "error", static_cast<double>(std::fabs(perimeter / 2.0L - pi)));
378 }
379
380 // ...and end this function as we did in the previous one:
381 table.omit_column_from_convergence_rate_evaluation("cells");
382 table.omit_column_from_convergence_rate_evaluation("eval.pi");
383 table.evaluate_all_convergence_rates(
384 ConvergenceTable::reduction_rate_log2);
385
386 table.set_precision("eval.pi", 16);
387 table.set_scientific("error", true);
388
389 table.write_text(std::cout);
390
391 std::cout << std::endl;
392 }
393 }
394 } // namespace Step10
395
396
397 // The following main function just calls the above functions in the order of
398 // their appearance. Apart from this, it looks just like the main functions of
399 // previous tutorial programs.
main()400 int main()
401 {
402 try
403 {
404 std::cout.precision(16);
405
406 Step10::gnuplot_output<2>();
407
408 Step10::compute_pi_by_area<2>();
409 Step10::compute_pi_by_perimeter<2>();
410 }
411 catch (std::exception &exc)
412 {
413 std::cerr << std::endl
414 << std::endl
415 << "----------------------------------------------------"
416 << std::endl;
417 std::cerr << "Exception on processing: " << std::endl
418 << exc.what() << std::endl
419 << "Aborting!" << std::endl
420 << "----------------------------------------------------"
421 << std::endl;
422
423 return 1;
424 }
425 catch (...)
426 {
427 std::cerr << std::endl
428 << std::endl
429 << "----------------------------------------------------"
430 << std::endl;
431 std::cerr << "Unknown exception!" << std::endl
432 << "Aborting!" << std::endl
433 << "----------------------------------------------------"
434 << std::endl;
435 return 1;
436 }
437
438 return 0;
439 }
440