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