1 /* ---------------------------------------------------------------------
2 *
3 * Copyright (C) 2006 - 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, Texas A&M University, 2006, 2007;
18 * Denis Davydov, University of Erlangen-Nuremberg, 2016.
19 */
20
21
22 // @sect3{Include files}
23
24 // The first few files have already been covered in previous examples and will
25 // thus not be further commented on.
26 #include <deal.II/base/quadrature_lib.h>
27 #include <deal.II/base/function.h>
28 #include <deal.II/base/logstream.h>
29 #include <deal.II/base/utilities.h>
30 #include <deal.II/lac/dynamic_sparsity_pattern.h>
31 #include <deal.II/lac/vector.h>
32 #include <deal.II/lac/full_matrix.h>
33 #include <deal.II/lac/sparse_matrix.h>
34 #include <deal.II/lac/solver_cg.h>
35 #include <deal.II/lac/precondition.h>
36 #include <deal.II/lac/affine_constraints.h>
37 #include <deal.II/grid/tria.h>
38 #include <deal.II/grid/grid_generator.h>
39 #include <deal.II/grid/tria_accessor.h>
40 #include <deal.II/grid/tria_iterator.h>
41 #include <deal.II/grid/grid_refinement.h>
42 #include <deal.II/dofs/dof_accessor.h>
43 #include <deal.II/dofs/dof_tools.h>
44 #include <deal.II/fe/fe_q.h>
45 #include <deal.II/numerics/vector_tools.h>
46 #include <deal.II/numerics/matrix_tools.h>
47 #include <deal.II/numerics/data_out.h>
48 #include <deal.II/numerics/error_estimator.h>
49
50 // These are the new files we need. The first and second provide <i>hp</i>
51 // versions of the DoFHandler and FEValues classes as described in the
52 // introduction of this program. The last one provides Fourier transformation
53 // class on the unit cell.
54 #include <deal.II/hp/dof_handler.h>
55 #include <deal.II/hp/fe_values.h>
56 #include <deal.II/fe/fe_series.h>
57
58 // The last set of include files are standard C++ headers. We need support for
59 // complex numbers when we compute the Fourier transform.
60 #include <fstream>
61 #include <iostream>
62 #include <complex>
63
64
65 // Finally, this is as in previous programs:
66 namespace Step27
67 {
68 using namespace dealii;
69
70
71 // @sect3{The main class}
72
73 // The main class of this program looks very much like the one already used
74 // in the first few tutorial programs, for example the one in step-6. The
75 // main difference is that we have merged the refine_grid and output_results
76 // functions into one since we will also want to output some of the
77 // quantities used in deciding how to refine the mesh (in particular the
78 // estimated smoothness of the solution). There is also a function that
79 // computes this estimated smoothness, as discussed in the introduction.
80 //
81 // As far as member variables are concerned, we use the same structure as
82 // already used in step-6, but instead of a regular DoFHandler we use an
83 // object of type hp::DoFHandler, and we need collections instead of
84 // individual finite element, quadrature, and face quadrature objects. We
85 // will fill these collections in the constructor of the class. The last
86 // variable, <code>max_degree</code>, indicates the maximal polynomial
87 // degree of shape functions used.
88 template <int dim>
89 class LaplaceProblem
90 {
91 public:
92 LaplaceProblem();
93 ~LaplaceProblem();
94
95 void run();
96
97 private:
98 void setup_system();
99 void assemble_system();
100 void solve();
101 void create_coarse_grid();
102 void estimate_smoothness(Vector<float> &smoothness_indicators);
103 void postprocess(const unsigned int cycle);
104 std::pair<bool, unsigned int> predicate(const TableIndices<dim> &indices);
105
106 Triangulation<dim> triangulation;
107
108 DoFHandler<dim> dof_handler;
109 hp::FECollection<dim> fe_collection;
110 hp::QCollection<dim> quadrature_collection;
111 hp::QCollection<dim - 1> face_quadrature_collection;
112
113 hp::QCollection<dim> fourier_q_collection;
114 std::unique_ptr<FESeries::Fourier<dim>> fourier;
115 std::vector<double> ln_k;
116 Table<dim, std::complex<double>> fourier_coefficients;
117
118 AffineConstraints<double> constraints;
119
120 SparsityPattern sparsity_pattern;
121 SparseMatrix<double> system_matrix;
122
123 Vector<double> solution;
124 Vector<double> system_rhs;
125
126 const unsigned int max_degree;
127 };
128
129
130
131 // @sect3{Equation data}
132 //
133 // Next, let us define the right hand side function for this problem. It is
134 // $x+1$ in 1d, $(x+1)(y+1)$ in 2d, and so on.
135 template <int dim>
136 class RightHandSide : public Function<dim>
137 {
138 public:
139 virtual double value(const Point<dim> & p,
140 const unsigned int component) const override;
141 };
142
143
144 template <int dim>
value(const Point<dim> & p,const unsigned int) const145 double RightHandSide<dim>::value(const Point<dim> &p,
146 const unsigned int /*component*/) const
147 {
148 double product = 1;
149 for (unsigned int d = 0; d < dim; ++d)
150 product *= (p[d] + 1);
151 return product;
152 }
153
154
155
156 // @sect3{Implementation of the main class}
157
158 // @sect4{LaplaceProblem::LaplaceProblem constructor}
159
160 // The constructor of this class is fairly straightforward. It associates
161 // the hp::DoFHandler object with the triangulation, and then sets the
162 // maximal polynomial degree to 7 (in 1d and 2d) or 5 (in 3d and higher). We
163 // do so because using higher order polynomial degrees becomes prohibitively
164 // expensive, especially in higher space dimensions.
165 //
166 // Following this, we fill the collections of finite element, and cell and
167 // face quadrature objects. We start with quadratic elements, and each
168 // quadrature formula is chosen so that it is appropriate for the matching
169 // finite element in the hp::FECollection object.
170 //
171 // Finally, we initialize FESeries::Fourier object which will be used to
172 // calculate coefficient in Fourier series as described in the introduction.
173 // In addition to the hp::FECollection, we need to provide quadrature rules
174 // hp::QCollection for integration on the reference cell.
175 //
176 // In order to resize fourier_coefficients Table, we use the following
177 // auxiliary function
178 template <int dim, typename T>
resize(Table<dim,T> & coeff,const unsigned int N)179 void resize(Table<dim, T> &coeff, const unsigned int N)
180 {
181 TableIndices<dim> size;
182 for (unsigned int d = 0; d < dim; d++)
183 size[d] = N;
184 coeff.reinit(size);
185 }
186
187 template <int dim>
LaplaceProblem()188 LaplaceProblem<dim>::LaplaceProblem()
189 : dof_handler(triangulation, /*enable_hp_capability */ true)
190 , max_degree(dim <= 2 ? 7 : 5)
191 {
192 for (unsigned int degree = 2; degree <= max_degree; ++degree)
193 {
194 fe_collection.push_back(FE_Q<dim>(degree));
195 quadrature_collection.push_back(QGauss<dim>(degree + 1));
196 face_quadrature_collection.push_back(QGauss<dim - 1>(degree + 1));
197 }
198
199 // As described in the introduction, we define the Fourier vectors ${\bf
200 // k}$ for which we want to compute Fourier coefficients of the solution
201 // on each cell as follows. In 2d, we will need coefficients corresponding
202 // to vectors ${\bf k}=(2 \pi i, 2\pi j)^T$ for which $\sqrt{i^2+j^2}\le N$,
203 // with $i,j$ integers and $N$ being the maximal polynomial degree we use
204 // for the finite elements in this program. The FESeries::Fourier class'
205 // constructor first parameter $N$ defines the number of coefficients in 1D
206 // with the total number of coefficients being $N^{dim}$. Although we will
207 // not use coefficients corresponding to
208 // $\sqrt{i^2+j^2}> N$ and $i+j==0$, the overhead of their calculation is
209 // minimal. The transformation matrices for each FiniteElement will be
210 // calculated only once the first time they are required in the course of
211 // hp-adaptive refinement. Because we work on the unit cell, we can do all
212 // this work without a mapping from reference to real cell and consequently
213 // can precalculate these matrices. The calculation of expansion
214 // coefficients for a particular set of local degrees of freedom on a given
215 // cell then follows as a simple matrix-vector product.
216 // The 3d case is handled analogously.
217 const unsigned int N = max_degree;
218
219 // We will need to assemble the matrices that do the Fourier transforms
220 // for each of the finite elements we deal with, i.e. the matrices ${\cal
221 // F}_{{\bf k},j}$ defined in the introduction. We have to do that for
222 // each of the finite elements in use. To that end we need a quadrature
223 // rule. In this example we use the same quadrature formula for each
224 // finite element, namely that is obtained by iterating a
225 // 2-point Gauss formula as many times as the maximal exponent we use for
226 // the term $e^{i{\bf k}\cdot{\bf x}}$:
227 QGauss<1> base_quadrature(2);
228 QIterated<dim> quadrature(base_quadrature, N);
229 for (unsigned int i = 0; i < fe_collection.size(); i++)
230 fourier_q_collection.push_back(quadrature);
231
232 // Now we are ready to set-up the FESeries::Fourier object
233 const std::vector<unsigned int> n_coefficients_per_direction(
234 fe_collection.size(), N);
235 fourier =
236 std::make_unique<FESeries::Fourier<dim>>(n_coefficients_per_direction,
237 fe_collection,
238 fourier_q_collection);
239
240 // We need to resize the matrix of fourier coefficients according to the
241 // number of modes N.
242 resize(fourier_coefficients, N);
243 }
244
245
246 // @sect4{LaplaceProblem::~LaplaceProblem destructor}
247
248 // The destructor is unchanged from what we already did in step-6:
249 template <int dim>
~LaplaceProblem()250 LaplaceProblem<dim>::~LaplaceProblem()
251 {
252 dof_handler.clear();
253 }
254
255
256 // @sect4{LaplaceProblem::setup_system}
257 //
258 // This function is again a verbatim copy of what we already did in
259 // step-6. Despite function calls with exactly the same names and arguments,
260 // the algorithms used internally are different in some aspect since the
261 // dof_handler variable here is an hp object.
262 template <int dim>
setup_system()263 void LaplaceProblem<dim>::setup_system()
264 {
265 dof_handler.distribute_dofs(fe_collection);
266
267 solution.reinit(dof_handler.n_dofs());
268 system_rhs.reinit(dof_handler.n_dofs());
269
270 constraints.clear();
271 DoFTools::make_hanging_node_constraints(dof_handler, constraints);
272 VectorTools::interpolate_boundary_values(dof_handler,
273 0,
274 Functions::ZeroFunction<dim>(),
275 constraints);
276 constraints.close();
277
278 DynamicSparsityPattern dsp(dof_handler.n_dofs(), dof_handler.n_dofs());
279 DoFTools::make_sparsity_pattern(dof_handler, dsp, constraints, false);
280 sparsity_pattern.copy_from(dsp);
281
282 system_matrix.reinit(sparsity_pattern);
283 }
284
285
286
287 // @sect4{LaplaceProblem::assemble_system}
288
289 // This is the function that assembles the global matrix and right hand side
290 // vector from the local contributions of each cell. Its main working is as
291 // has been described in many of the tutorial programs before. The
292 // significant deviations are the ones necessary for <i>hp</i> finite
293 // element methods. In particular, that we need to use a collection of
294 // FEValues object (implemented through the hp::FEValues class), and that we
295 // have to eliminate constrained degrees of freedom already when copying
296 // local contributions into global objects. Both of these are explained in
297 // detail in the introduction of this program.
298 //
299 // One other slight complication is the fact that because we use different
300 // polynomial degrees on different cells, the matrices and vectors holding
301 // local contributions do not have the same size on all cells. At the
302 // beginning of the loop over all cells, we therefore each time have to
303 // resize them to the correct size (given by
304 // <code>dofs_per_cell</code>). Because these classes are implement in such
305 // a way that reducing the size of a matrix or vector does not release the
306 // currently allocated memory (unless the new size is zero), the process of
307 // resizing at the beginning of the loop will only require re-allocation of
308 // memory during the first few iterations. Once we have found in a cell with
309 // the maximal finite element degree, no more re-allocations will happen
310 // because all subsequent <code>reinit</code> calls will only set the size
311 // to something that fits the currently allocated memory. This is important
312 // since allocating memory is expensive, and doing so every time we visit a
313 // new cell would take significant compute time.
314 template <int dim>
assemble_system()315 void LaplaceProblem<dim>::assemble_system()
316 {
317 hp::FEValues<dim> hp_fe_values(fe_collection,
318 quadrature_collection,
319 update_values | update_gradients |
320 update_quadrature_points |
321 update_JxW_values);
322
323 RightHandSide<dim> rhs_function;
324
325 FullMatrix<double> cell_matrix;
326 Vector<double> cell_rhs;
327
328 std::vector<types::global_dof_index> local_dof_indices;
329
330 for (const auto &cell : dof_handler.active_cell_iterators())
331 {
332 const unsigned int dofs_per_cell = cell->get_fe().n_dofs_per_cell();
333
334 cell_matrix.reinit(dofs_per_cell, dofs_per_cell);
335 cell_matrix = 0;
336
337 cell_rhs.reinit(dofs_per_cell);
338 cell_rhs = 0;
339
340 hp_fe_values.reinit(cell);
341
342 const FEValues<dim> &fe_values = hp_fe_values.get_present_fe_values();
343
344 std::vector<double> rhs_values(fe_values.n_quadrature_points);
345 rhs_function.value_list(fe_values.get_quadrature_points(), rhs_values);
346
347 for (unsigned int q_point = 0; q_point < fe_values.n_quadrature_points;
348 ++q_point)
349 for (unsigned int i = 0; i < dofs_per_cell; ++i)
350 {
351 for (unsigned int j = 0; j < dofs_per_cell; ++j)
352 cell_matrix(i, j) +=
353 (fe_values.shape_grad(i, q_point) * // grad phi_i(x_q)
354 fe_values.shape_grad(j, q_point) * // grad phi_j(x_q)
355 fe_values.JxW(q_point)); // dx
356
357 cell_rhs(i) += (fe_values.shape_value(i, q_point) * // phi_i(x_q)
358 rhs_values[q_point] * // f(x_q)
359 fe_values.JxW(q_point)); // dx
360 }
361
362 local_dof_indices.resize(dofs_per_cell);
363 cell->get_dof_indices(local_dof_indices);
364
365 constraints.distribute_local_to_global(
366 cell_matrix, cell_rhs, local_dof_indices, system_matrix, system_rhs);
367 }
368 }
369
370
371
372 // @sect4{LaplaceProblem::solve}
373
374 // The function solving the linear system is entirely unchanged from
375 // previous examples. We simply try to reduce the initial residual (which
376 // equals the $l_2$ norm of the right hand side) by a certain factor:
377 template <int dim>
solve()378 void LaplaceProblem<dim>::solve()
379 {
380 SolverControl solver_control(system_rhs.size(),
381 1e-12 * system_rhs.l2_norm());
382 SolverCG<Vector<double>> cg(solver_control);
383
384 PreconditionSSOR<SparseMatrix<double>> preconditioner;
385 preconditioner.initialize(system_matrix, 1.2);
386
387 cg.solve(system_matrix, solution, system_rhs, preconditioner);
388
389 constraints.distribute(solution);
390 }
391
392
393
394 // @sect4{LaplaceProblem::postprocess}
395
396 // After solving the linear system, we will want to postprocess the
397 // solution. Here, all we do is to estimate the error, estimate the local
398 // smoothness of the solution as described in the introduction, then write
399 // graphical output, and finally refine the mesh in both $h$ and $p$
400 // according to the indicators computed before. We do all this in the same
401 // function because we want the estimated error and smoothness indicators
402 // not only for refinement, but also include them in the graphical output.
403 template <int dim>
postprocess(const unsigned int cycle)404 void LaplaceProblem<dim>::postprocess(const unsigned int cycle)
405 {
406 // Let us start with computing estimated error and smoothness indicators,
407 // which each are one number for each active cell of our
408 // triangulation. For the error indicator, we use the KellyErrorEstimator
409 // class as always. Estimating the smoothness is done in the respective
410 // function of this class; that function is discussed further down below:
411 Vector<float> estimated_error_per_cell(triangulation.n_active_cells());
412 KellyErrorEstimator<dim>::estimate(
413 dof_handler,
414 face_quadrature_collection,
415 std::map<types::boundary_id, const Function<dim> *>(),
416 solution,
417 estimated_error_per_cell);
418
419
420 Vector<float> smoothness_indicators(triangulation.n_active_cells());
421 estimate_smoothness(smoothness_indicators);
422
423 // Next we want to generate graphical output. In addition to the two
424 // estimated quantities derived above, we would also like to output the
425 // polynomial degree of the finite elements used on each of the elements
426 // on the mesh.
427 //
428 // The way to do that requires that we loop over all cells and poll the
429 // active finite element index of them using
430 // <code>cell-@>active_fe_index()</code>. We then use the result of this
431 // operation and query the finite element collection for the finite
432 // element with that index, and finally determine the polynomial degree of
433 // that element. The result we put into a vector with one element per
434 // cell. The DataOut class requires this to be a vector of
435 // <code>float</code> or <code>double</code>, even though our values are
436 // all integers, so that it what we use:
437 {
438 Vector<float> fe_degrees(triangulation.n_active_cells());
439 for (const auto &cell : dof_handler.active_cell_iterators())
440 fe_degrees(cell->active_cell_index()) =
441 fe_collection[cell->active_fe_index()].degree;
442
443 // With now all data vectors available -- solution, estimated errors and
444 // smoothness indicators, and finite element degrees --, we create a
445 // DataOut object for graphical output and attach all data. Note that
446 // the DataOut class has a second template argument (which defaults to
447 // DoFHandler@<dim@>, which is why we have never seen it in previous
448 // tutorial programs) that indicates the type of DoF handler to be
449 // used. Here, we have to use the hp::DoFHandler class:
450 DataOut<dim, DoFHandler<dim>> data_out;
451
452 data_out.attach_dof_handler(dof_handler);
453 data_out.add_data_vector(solution, "solution");
454 data_out.add_data_vector(estimated_error_per_cell, "error");
455 data_out.add_data_vector(smoothness_indicators, "smoothness");
456 data_out.add_data_vector(fe_degrees, "fe_degree");
457 data_out.build_patches();
458
459 // The final step in generating output is to determine a file name, open
460 // the file, and write the data into it (here, we use VTK format):
461 const std::string filename =
462 "solution-" + Utilities::int_to_string(cycle, 2) + ".vtk";
463 std::ofstream output(filename);
464 data_out.write_vtk(output);
465 }
466
467 // After this, we would like to actually refine the mesh, in both $h$ and
468 // $p$. The way we are going to do this is as follows: first, we use the
469 // estimated error to flag those cells for refinement that have the
470 // largest error. This is what we have always done:
471 {
472 GridRefinement::refine_and_coarsen_fixed_number(triangulation,
473 estimated_error_per_cell,
474 0.3,
475 0.03);
476
477 // Next we would like to figure out which of the cells that have been
478 // flagged for refinement should actually have $p$ increased instead of
479 // $h$ decreased. The strategy we choose here is that we look at the
480 // smoothness indicators of those cells that are flagged for refinement,
481 // and increase $p$ for those with a smoothness larger than a certain
482 // threshold. For this, we first have to determine the maximal and
483 // minimal values of the smoothness indicators of all flagged cells,
484 // which we do using a loop over all cells and comparing current minimal
485 // and maximal values. (We start with the minimal and maximal values of
486 // <i>all</i> cells, a range within which the minimal and maximal values
487 // on cells flagged for refinement must surely lie.) Absent any better
488 // strategies, we will then set the threshold above which will increase
489 // $p$ instead of reducing $h$ as the mean value between minimal and
490 // maximal smoothness indicators on cells flagged for refinement:
491 float max_smoothness = *std::min_element(smoothness_indicators.begin(),
492 smoothness_indicators.end()),
493 min_smoothness = *std::max_element(smoothness_indicators.begin(),
494 smoothness_indicators.end());
495 for (const auto &cell : dof_handler.active_cell_iterators())
496 if (cell->refine_flag_set())
497 {
498 max_smoothness =
499 std::max(max_smoothness,
500 smoothness_indicators(cell->active_cell_index()));
501 min_smoothness =
502 std::min(min_smoothness,
503 smoothness_indicators(cell->active_cell_index()));
504 }
505 const float threshold_smoothness = (max_smoothness + min_smoothness) / 2;
506
507 // With this, we can go back, loop over all cells again, and for those
508 // cells for which (i) the refinement flag is set, (ii) the smoothness
509 // indicator is larger than the threshold, and (iii) we still have a
510 // finite element with a polynomial degree higher than the current one
511 // in the finite element collection, we then increase the polynomial
512 // degree and in return remove the flag indicating that the cell should
513 // undergo bisection. For all other cells, the refinement flags remain
514 // untouched:
515 for (const auto &cell : dof_handler.active_cell_iterators())
516 if (cell->refine_flag_set() &&
517 (smoothness_indicators(cell->active_cell_index()) >
518 threshold_smoothness) &&
519 (cell->active_fe_index() + 1 < fe_collection.size()))
520 {
521 cell->clear_refine_flag();
522 cell->set_active_fe_index(cell->active_fe_index() + 1);
523 }
524
525 // At the end of this procedure, we then refine the mesh. During this
526 // process, children of cells undergoing bisection inherit their mother
527 // cell's finite element index:
528 triangulation.execute_coarsening_and_refinement();
529 }
530 }
531
532
533 // @sect4{LaplaceProblem::create_coarse_grid}
534
535 // The following function is used when creating the initial grid. It is a
536 // specialization for the 2d case, i.e. a corresponding function needs to be
537 // implemented if the program is run in anything other then 2d. The function
538 // is actually stolen from step-14 and generates the same mesh used already
539 // there, i.e. the square domain with the square hole in the middle. The
540 // meaning of the different parts of this function are explained in the
541 // documentation of step-14:
542 template <>
create_coarse_grid()543 void LaplaceProblem<2>::create_coarse_grid()
544 {
545 const unsigned int dim = 2;
546
547 const std::vector<Point<2>> vertices = {
548 {-1.0, -1.0}, {-0.5, -1.0}, {+0.0, -1.0}, {+0.5, -1.0}, {+1.0, -1.0}, //
549 {-1.0, -0.5}, {-0.5, -0.5}, {+0.0, -0.5}, {+0.5, -0.5}, {+1.0, -0.5}, //
550 {-1.0, +0.0}, {-0.5, +0.0}, {+0.5, +0.0}, {+1.0, +0.0}, //
551 {-1.0, +0.5}, {-0.5, +0.5}, {+0.0, +0.5}, {+0.5, +0.5}, {+1.0, +0.5}, //
552 {-1.0, +1.0}, {-0.5, +1.0}, {+0.0, +1.0}, {+0.5, +1.0}, {+1.0, +1.0}};
553
554 const std::vector<std::array<int, GeometryInfo<dim>::vertices_per_cell>>
555 cell_vertices = {{{0, 1, 5, 6}},
556 {{1, 2, 6, 7}},
557 {{2, 3, 7, 8}},
558 {{3, 4, 8, 9}},
559 {{5, 6, 10, 11}},
560 {{8, 9, 12, 13}},
561 {{10, 11, 14, 15}},
562 {{12, 13, 17, 18}},
563 {{14, 15, 19, 20}},
564 {{15, 16, 20, 21}},
565 {{16, 17, 21, 22}},
566 {{17, 18, 22, 23}}};
567
568 const unsigned int n_cells = cell_vertices.size();
569
570 std::vector<CellData<dim>> cells(n_cells, CellData<dim>());
571 for (unsigned int i = 0; i < n_cells; ++i)
572 {
573 for (unsigned int j = 0; j < cell_vertices[i].size(); ++j)
574 cells[i].vertices[j] = cell_vertices[i][j];
575 cells[i].material_id = 0;
576 }
577
578 triangulation.create_triangulation(vertices, cells, SubCellData());
579 triangulation.refine_global(3);
580 }
581
582
583
584 // @sect4{LaplaceProblem::run}
585
586 // This function implements the logic of the program, as did the respective
587 // function in most of the previous programs already, see for example
588 // step-6.
589 //
590 // Basically, it contains the adaptive loop: in the first iteration create a
591 // coarse grid, and then set up the linear system, assemble it, solve, and
592 // postprocess the solution including mesh refinement. Then start over
593 // again. In the meantime, also output some information for those staring at
594 // the screen trying to figure out what the program does:
595 template <int dim>
run()596 void LaplaceProblem<dim>::run()
597 {
598 for (unsigned int cycle = 0; cycle < 6; ++cycle)
599 {
600 std::cout << "Cycle " << cycle << ':' << std::endl;
601
602 if (cycle == 0)
603 create_coarse_grid();
604
605 setup_system();
606
607 std::cout << " Number of active cells : "
608 << triangulation.n_active_cells() << std::endl
609 << " Number of degrees of freedom: " << dof_handler.n_dofs()
610 << std::endl
611 << " Number of constraints : "
612 << constraints.n_constraints() << std::endl;
613
614 assemble_system();
615 solve();
616 postprocess(cycle);
617 }
618 }
619
620
621 // @sect4{LaplaceProblem::estimate_smoothness}
622
623 // As described in the introduction, we will need to take the maximum
624 // absolute value of fourier coefficients which correspond to $k$-vector
625 // $|{\bf k}|= const$. To filter the coefficients Table we
626 // will use the FESeries::process_coefficients() which requires a predicate
627 // to be specified. The predicate should operate on TableIndices and return
628 // a pair of <code>bool</code> and <code>unsigned int</code>. The latter
629 // is the value of the map from TableIndicies to unsigned int. It is
630 // used to define subsets of coefficients from which we search for the one
631 // with highest absolute value, i.e. $l^\infty$-norm. The <code>bool</code>
632 // parameter defines which indices should be used in processing. In the
633 // current case we are interested in coefficients which correspond to
634 // $0 < i*i+j*j < N*N$ and $0 < i*i+j*j+k*k < N*N$ in 2D and 3D, respectively.
635 template <int dim>
636 std::pair<bool, unsigned int>
predicate(const TableIndices<dim> & ind)637 LaplaceProblem<dim>::predicate(const TableIndices<dim> &ind)
638 {
639 unsigned int v = 0;
640 for (unsigned int i = 0; i < dim; i++)
641 v += ind[i] * ind[i];
642 if (v > 0 && v < max_degree * max_degree)
643 return std::make_pair(true, v);
644 else
645 return std::make_pair(false, v);
646 }
647
648 // This last function of significance implements the algorithm to estimate
649 // the smoothness exponent using the algorithms explained in detail in the
650 // introduction. We will therefore only comment on those points that are of
651 // implementational importance.
652 template <int dim>
653 void
estimate_smoothness(Vector<float> & smoothness_indicators)654 LaplaceProblem<dim>::estimate_smoothness(Vector<float> &smoothness_indicators)
655 {
656 // Since most of the hard work is done for us in FESeries::Fourier and
657 // we set up the object of this class in the constructor, what we are left
658 // to do here is apply this class to calculate coefficients and then
659 // perform linear regression to fit their decay slope.
660
661
662 // First thing to do is to loop over all cells and do our work there, i.e.
663 // to locally do the Fourier transform and estimate the decay coefficient.
664 // We will use the following array as a scratch array in the loop to store
665 // local DoF values:
666 Vector<double> local_dof_values;
667
668 // Then here is the loop:
669 for (const auto &cell : dof_handler.active_cell_iterators())
670 {
671 // Inside the loop, we first need to get the values of the local
672 // degrees of freedom (which we put into the
673 // <code>local_dof_values</code> array after setting it to the right
674 // size) and then need to compute the Fourier transform by multiplying
675 // this vector with the matrix ${\cal F}$ corresponding to this finite
676 // element. This is done by calling FESeries::Fourier::calculate(),
677 // that has to be provided with the <code>local_dof_values</code>,
678 // <code>cell->active_fe_index()</code> and a Table to store
679 // coefficients.
680 local_dof_values.reinit(cell->get_fe().n_dofs_per_cell());
681 cell->get_dof_values(solution, local_dof_values);
682
683 fourier->calculate(local_dof_values,
684 cell->active_fe_index(),
685 fourier_coefficients);
686
687 // The next thing, as explained in the introduction, is that we wanted
688 // to only fit our exponential decay of Fourier coefficients to the
689 // largest coefficients for each possible value of $|{\bf k}|$. To
690 // this end, we use FESeries::process_coefficients() to rework
691 // coefficients into the desired format. We'll only take those Fourier
692 // coefficients with the largest magnitude for a given value of $|{\bf
693 // k}|$ and thereby need to use VectorTools::Linfty_norm:
694 std::pair<std::vector<unsigned int>, std::vector<double>> res =
695 FESeries::process_coefficients<dim>(
696 fourier_coefficients,
697 [this](const TableIndices<dim> &indices) {
698 return this->predicate(indices);
699 },
700 VectorTools::Linfty_norm);
701
702 Assert(res.first.size() == res.second.size(), ExcInternalError());
703
704 // The first vector in the <code>std::pair</code> will store values of
705 // the predicate, that is $i*i+j*j= const$ or $i*i+j*j+k*k = const$ in
706 // 2D or 3D respectively. This vector will be the same for all the cells
707 // so we can calculate logarithms of the corresponding Fourier vectors
708 // $|{\bf k}|$ only once in the whole hp-refinement cycle:
709 if (ln_k.size() == 0)
710 {
711 ln_k.resize(res.first.size(), 0);
712 for (unsigned int f = 0; f < ln_k.size(); f++)
713 ln_k[f] =
714 std::log(2.0 * numbers::PI * std::sqrt(1. * res.first[f]));
715 }
716
717 // We have to calculate the logarithms of absolute values of
718 // coefficients and use it in a linear regression fit to obtain $\mu$.
719 for (double &residual_element : res.second)
720 residual_element = std::log(residual_element);
721
722 std::pair<double, double> fit =
723 FESeries::linear_regression(ln_k, res.second);
724
725 // The final step is to compute the Sobolev index $s=\mu-\frac d2$ and
726 // store it in the vector of estimated values for each cell:
727 smoothness_indicators(cell->active_cell_index()) =
728 -fit.first - 1. * dim / 2;
729 }
730 }
731 } // namespace Step27
732
733
734 // @sect3{The main function}
735
736 // The main function is again verbatim what we had before: wrap creating and
737 // running an object of the main class into a <code>try</code> block and catch
738 // whatever exceptions are thrown, thereby producing meaningful output if
739 // anything should go wrong:
main()740 int main()
741 {
742 try
743 {
744 using namespace Step27;
745
746 LaplaceProblem<2> laplace_problem;
747 laplace_problem.run();
748 }
749 catch (std::exception &exc)
750 {
751 std::cerr << std::endl
752 << std::endl
753 << "----------------------------------------------------"
754 << std::endl;
755 std::cerr << "Exception on processing: " << std::endl
756 << exc.what() << std::endl
757 << "Aborting!" << std::endl
758 << "----------------------------------------------------"
759 << std::endl;
760
761 return 1;
762 }
763 catch (...)
764 {
765 std::cerr << std::endl
766 << std::endl
767 << "----------------------------------------------------"
768 << std::endl;
769 std::cerr << "Unknown exception!" << std::endl
770 << "Aborting!" << std::endl
771 << "----------------------------------------------------"
772 << std::endl;
773 return 1;
774 }
775
776 return 0;
777 }
778