1 /* ---------------------------------------------------------------------
2  *
3  * Copyright (C) 2008 - 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  * Author: Liang Zhao and Timo Heister, Clemson University, 2016
17  */
18 
19 // @sect3{Include files}
20 
21 // As usual, we start by including some well-known files:
22 #include <deal.II/base/quadrature_lib.h>
23 #include <deal.II/base/function.h>
24 #include <deal.II/base/utilities.h>
25 #include <deal.II/base/tensor.h>
26 
27 #include <deal.II/lac/block_vector.h>
28 #include <deal.II/lac/full_matrix.h>
29 #include <deal.II/lac/block_sparse_matrix.h>
30 #include <deal.II/lac/dynamic_sparsity_pattern.h>
31 #include <deal.II/lac/solver_cg.h>
32 #include <deal.II/lac/solver_gmres.h>
33 #include <deal.II/lac/precondition.h>
34 #include <deal.II/lac/affine_constraints.h>
35 
36 #include <deal.II/grid/tria.h>
37 #include <deal.II/grid/grid_generator.h>
38 #include <deal.II/grid/grid_refinement.h>
39 #include <deal.II/grid/grid_tools.h>
40 
41 #include <deal.II/dofs/dof_handler.h>
42 #include <deal.II/dofs/dof_renumbering.h>
43 #include <deal.II/dofs/dof_tools.h>
44 
45 #include <deal.II/fe/fe_q.h>
46 #include <deal.II/fe/fe_system.h>
47 #include <deal.II/fe/fe_values.h>
48 
49 #include <deal.II/numerics/vector_tools.h>
50 #include <deal.II/numerics/matrix_tools.h>
51 #include <deal.II/numerics/data_out.h>
52 #include <deal.II/numerics/error_estimator.h>
53 
54 // To transfer solutions between meshes, this file is included:
55 #include <deal.II/numerics/solution_transfer.h>
56 
57 // This file includes UMFPACK: the direct solver:
58 #include <deal.II/lac/sparse_direct.h>
59 
60 // And the one for ILU preconditioner:
61 #include <deal.II/lac/sparse_ilu.h>
62 
63 
64 #include <fstream>
65 #include <iostream>
66 
67 namespace Step57
68 {
69   using namespace dealii;
70 
71   // @sect3{The <code>NavierStokesProblem</code> class template}
72 
73   // This class manages the matrices and vectors described in the
74   // introduction: in particular, we store a BlockVector for the current
75   // solution, current Newton update, and the line search update.  We also
76   // store two AffineConstraints objects: one which enforces the Dirichlet
77   // boundary conditions and one that sets all boundary values to zero. The
78   // first constrains the solution vector while the second constraints the
79   // updates (i.e., we never update boundary values, so we force the relevant
80   // update vector values to be zero).
81   template <int dim>
82   class StationaryNavierStokes
83   {
84   public:
85     StationaryNavierStokes(const unsigned int degree);
86     void run(const unsigned int refinement);
87 
88   private:
89     void setup_dofs();
90 
91     void initialize_system();
92 
93     void assemble(const bool initial_step, const bool assemble_matrix);
94 
95     void assemble_system(const bool initial_step);
96 
97     void assemble_rhs(const bool initial_step);
98 
99     void solve(const bool initial_step);
100 
101     void refine_mesh();
102 
103     void process_solution(unsigned int refinement);
104 
105     void output_results(const unsigned int refinement_cycle) const;
106 
107     void newton_iteration(const double       tolerance,
108                           const unsigned int max_n_line_searches,
109                           const unsigned int max_n_refinements,
110                           const bool         is_initial_step,
111                           const bool         output_result);
112 
113     void compute_initial_guess(double step_size);
114 
115     double                               viscosity;
116     double                               gamma;
117     const unsigned int                   degree;
118     std::vector<types::global_dof_index> dofs_per_block;
119 
120     Triangulation<dim> triangulation;
121     FESystem<dim>      fe;
122     DoFHandler<dim>    dof_handler;
123 
124     AffineConstraints<double> zero_constraints;
125     AffineConstraints<double> nonzero_constraints;
126 
127     BlockSparsityPattern      sparsity_pattern;
128     BlockSparseMatrix<double> system_matrix;
129     SparseMatrix<double>      pressure_mass_matrix;
130 
131     BlockVector<double> present_solution;
132     BlockVector<double> newton_update;
133     BlockVector<double> system_rhs;
134     BlockVector<double> evaluation_point;
135   };
136 
137   // @sect3{Boundary values and right hand side}
138 
139   // In this problem we set the velocity along the upper surface of the cavity
140   // to be one and zero on the other three walls. The right hand side function
141   // is zero so we do not need to set the right hand side function in this
142   // tutorial. The number of components of the boundary function is
143   // <code>dim+1</code>. We will ultimately use
144   // VectorTools::interpolate_boundary_values to set boundary values, which
145   // requires the boundary value functions to have the same number of
146   // components as the solution, even if all are not used. Put another way: to
147   // make this function happy we define boundary values for the pressure even
148   // though we will never actually use them.
149   template <int dim>
150   class BoundaryValues : public Function<dim>
151   {
152   public:
BoundaryValues()153     BoundaryValues()
154       : Function<dim>(dim + 1)
155     {}
156     virtual double value(const Point<dim> & p,
157                          const unsigned int component) const override;
158   };
159 
160   template <int dim>
value(const Point<dim> & p,const unsigned int component) const161   double BoundaryValues<dim>::value(const Point<dim> & p,
162                                     const unsigned int component) const
163   {
164     Assert(component < this->n_components,
165            ExcIndexRange(component, 0, this->n_components));
166     if (component == 0 && std::abs(p[dim - 1] - 1.0) < 1e-10)
167       return 1.0;
168 
169     return 0;
170   }
171 
172   // @sect3{BlockSchurPreconditioner for Navier Stokes equations}
173   //
174   // As discussed in the introduction, the preconditioner in Krylov iterative
175   // methods is implemented as a matrix-vector product operator. In practice,
176   // the Schur complement preconditioner is decomposed as a product of three
177   // matrices (as presented in the first section). The $\tilde{A}^{-1}$ in the
178   // first factor involves a solve for the linear system $\tilde{A}x=b$. Here
179   // we solve this system via a direct solver for simplicity. The computation
180   // involved in the second factor is a simple matrix-vector
181   // multiplication. The Schur complement $\tilde{S}$ can be well approximated
182   // by the pressure mass matrix and its inverse can be obtained through an
183   // inexact solver. Because the pressure mass matrix is symmetric and
184   // positive definite, we can use CG to solve the corresponding linear
185   // system.
186   template <class PreconditionerMp>
187   class BlockSchurPreconditioner : public Subscriptor
188   {
189   public:
190     BlockSchurPreconditioner(double                           gamma,
191                              double                           viscosity,
192                              const BlockSparseMatrix<double> &S,
193                              const SparseMatrix<double> &     P,
194                              const PreconditionerMp &         Mppreconditioner);
195 
196     void vmult(BlockVector<double> &dst, const BlockVector<double> &src) const;
197 
198   private:
199     const double                     gamma;
200     const double                     viscosity;
201     const BlockSparseMatrix<double> &stokes_matrix;
202     const SparseMatrix<double> &     pressure_mass_matrix;
203     const PreconditionerMp &         mp_preconditioner;
204     SparseDirectUMFPACK              A_inverse;
205   };
206 
207   // We can notice that the initialization of the inverse of the matrix at the
208   // top left corner is completed in the constructor. If so, every application
209   // of the preconditioner then no longer requires the computation of the
210   // matrix factors.
211 
212   template <class PreconditionerMp>
BlockSchurPreconditioner(double gamma,double viscosity,const BlockSparseMatrix<double> & S,const SparseMatrix<double> & P,const PreconditionerMp & Mppreconditioner)213   BlockSchurPreconditioner<PreconditionerMp>::BlockSchurPreconditioner(
214     double                           gamma,
215     double                           viscosity,
216     const BlockSparseMatrix<double> &S,
217     const SparseMatrix<double> &     P,
218     const PreconditionerMp &         Mppreconditioner)
219     : gamma(gamma)
220     , viscosity(viscosity)
221     , stokes_matrix(S)
222     , pressure_mass_matrix(P)
223     , mp_preconditioner(Mppreconditioner)
224   {
225     A_inverse.initialize(stokes_matrix.block(0, 0));
226   }
227 
228   template <class PreconditionerMp>
vmult(BlockVector<double> & dst,const BlockVector<double> & src) const229   void BlockSchurPreconditioner<PreconditionerMp>::vmult(
230     BlockVector<double> &      dst,
231     const BlockVector<double> &src) const
232   {
233     Vector<double> utmp(src.block(0));
234 
235     {
236       SolverControl solver_control(1000, 1e-6 * src.block(1).l2_norm());
237       SolverCG<Vector<double>> cg(solver_control);
238 
239       dst.block(1) = 0.0;
240       cg.solve(pressure_mass_matrix,
241                dst.block(1),
242                src.block(1),
243                mp_preconditioner);
244       dst.block(1) *= -(viscosity + gamma);
245     }
246 
247     {
248       stokes_matrix.block(0, 1).vmult(utmp, dst.block(1));
249       utmp *= -1.0;
250       utmp += src.block(0);
251     }
252 
253     A_inverse.vmult(dst.block(0), utmp);
254   }
255 
256   // @sect3{StationaryNavierStokes class implementation}
257   // @sect4{StationaryNavierStokes::StationaryNavierStokes}
258   //
259   // The constructor of this class looks very similar to the one in step-22. The
260   // only difference is the viscosity and the Augmented Lagrangian coefficient
261   // <code>gamma</code>.
262   template <int dim>
StationaryNavierStokes(const unsigned int degree)263   StationaryNavierStokes<dim>::StationaryNavierStokes(const unsigned int degree)
264     : viscosity(1.0 / 7500.0)
265     , gamma(1.0)
266     , degree(degree)
267     , triangulation(Triangulation<dim>::maximum_smoothing)
268     , fe(FE_Q<dim>(degree + 1), dim, FE_Q<dim>(degree), 1)
269     , dof_handler(triangulation)
270   {}
271 
272   // @sect4{StationaryNavierStokes::setup_dofs}
273   //
274   // This function initializes the DoFHandler enumerating the degrees of freedom
275   // and constraints on the current mesh.
276   template <int dim>
setup_dofs()277   void StationaryNavierStokes<dim>::setup_dofs()
278   {
279     system_matrix.clear();
280     pressure_mass_matrix.clear();
281 
282     // The first step is to associate DoFs with a given mesh.
283     dof_handler.distribute_dofs(fe);
284 
285     // We renumber the components to have all velocity DoFs come before
286     // the pressure DoFs to be able to split the solution vector in two blocks
287     // which are separately accessed in the block preconditioner.
288     std::vector<unsigned int> block_component(dim + 1, 0);
289     block_component[dim] = 1;
290     DoFRenumbering::component_wise(dof_handler, block_component);
291 
292     dofs_per_block =
293       DoFTools::count_dofs_per_fe_block(dof_handler, block_component);
294     unsigned int dof_u = dofs_per_block[0];
295     unsigned int dof_p = dofs_per_block[1];
296 
297     // In Newton's scheme, we first apply the boundary condition on the solution
298     // obtained from the initial step. To make sure the boundary conditions
299     // remain satisfied during Newton's iteration, zero boundary conditions are
300     // used for the update $\delta u^k$. Therefore we set up two different
301     // constraint objects.
302     FEValuesExtractors::Vector velocities(0);
303     {
304       nonzero_constraints.clear();
305 
306       DoFTools::make_hanging_node_constraints(dof_handler, nonzero_constraints);
307       VectorTools::interpolate_boundary_values(dof_handler,
308                                                0,
309                                                BoundaryValues<dim>(),
310                                                nonzero_constraints,
311                                                fe.component_mask(velocities));
312     }
313     nonzero_constraints.close();
314 
315     {
316       zero_constraints.clear();
317 
318       DoFTools::make_hanging_node_constraints(dof_handler, zero_constraints);
319       VectorTools::interpolate_boundary_values(dof_handler,
320                                                0,
321                                                Functions::ZeroFunction<dim>(
322                                                  dim + 1),
323                                                zero_constraints,
324                                                fe.component_mask(velocities));
325     }
326     zero_constraints.close();
327 
328     std::cout << "Number of active cells: " << triangulation.n_active_cells()
329               << std::endl
330               << "Number of degrees of freedom: " << dof_handler.n_dofs()
331               << " (" << dof_u << " + " << dof_p << ')' << std::endl;
332   }
333 
334   // @sect4{StationaryNavierStokes::initialize_system}
335   //
336   // On each mesh the SparsityPattern and the size of the linear system
337   // are different. This function initializes them after mesh refinement.
338   template <int dim>
initialize_system()339   void StationaryNavierStokes<dim>::initialize_system()
340   {
341     {
342       BlockDynamicSparsityPattern dsp(dofs_per_block, dofs_per_block);
343       DoFTools::make_sparsity_pattern(dof_handler, dsp, nonzero_constraints);
344       sparsity_pattern.copy_from(dsp);
345     }
346 
347     system_matrix.reinit(sparsity_pattern);
348 
349     present_solution.reinit(dofs_per_block);
350     newton_update.reinit(dofs_per_block);
351     system_rhs.reinit(dofs_per_block);
352   }
353 
354   // @sect4{StationaryNavierStokes::assemble}
355   //
356   // This function builds the system matrix and right hand side that we
357   // currently work on. The @p initial_step argument is used to determine
358   // which set of constraints we apply (nonzero for the initial step and zero
359   // for the others). The @p assemble_matrix argument determines whether to
360   // assemble the whole system or only the right hand side vector,
361   // respectively.
362   template <int dim>
assemble(const bool initial_step,const bool assemble_matrix)363   void StationaryNavierStokes<dim>::assemble(const bool initial_step,
364                                              const bool assemble_matrix)
365   {
366     if (assemble_matrix)
367       system_matrix = 0;
368 
369     system_rhs = 0;
370 
371     QGauss<dim> quadrature_formula(degree + 2);
372 
373     FEValues<dim> fe_values(fe,
374                             quadrature_formula,
375                             update_values | update_quadrature_points |
376                               update_JxW_values | update_gradients);
377 
378     const unsigned int dofs_per_cell = fe.n_dofs_per_cell();
379     const unsigned int n_q_points    = quadrature_formula.size();
380 
381     const FEValuesExtractors::Vector velocities(0);
382     const FEValuesExtractors::Scalar pressure(dim);
383 
384     FullMatrix<double> local_matrix(dofs_per_cell, dofs_per_cell);
385     Vector<double>     local_rhs(dofs_per_cell);
386 
387     std::vector<types::global_dof_index> local_dof_indices(dofs_per_cell);
388 
389     // For the linearized system, we create temporary storage for present
390     // velocity and gradient, and present pressure. In practice, they are all
391     // obtained through their shape functions at quadrature points.
392 
393     std::vector<Tensor<1, dim>> present_velocity_values(n_q_points);
394     std::vector<Tensor<2, dim>> present_velocity_gradients(n_q_points);
395     std::vector<double>         present_pressure_values(n_q_points);
396 
397     std::vector<double>         div_phi_u(dofs_per_cell);
398     std::vector<Tensor<1, dim>> phi_u(dofs_per_cell);
399     std::vector<Tensor<2, dim>> grad_phi_u(dofs_per_cell);
400     std::vector<double>         phi_p(dofs_per_cell);
401 
402     for (const auto &cell : dof_handler.active_cell_iterators())
403       {
404         fe_values.reinit(cell);
405 
406         local_matrix = 0;
407         local_rhs    = 0;
408 
409         fe_values[velocities].get_function_values(evaluation_point,
410                                                   present_velocity_values);
411 
412         fe_values[velocities].get_function_gradients(
413           evaluation_point, present_velocity_gradients);
414 
415         fe_values[pressure].get_function_values(evaluation_point,
416                                                 present_pressure_values);
417 
418         // The assembly is similar to step-22. An additional term with gamma
419         // as a coefficient is the Augmented Lagrangian (AL), which is
420         // assembled via grad-div stabilization.  As we discussed in the
421         // introduction, the bottom right block of the system matrix should be
422         // zero. Since the pressure mass matrix is used while creating the
423         // preconditioner, we assemble it here and then move it into a
424         // separate SparseMatrix at the end (same as in step-22).
425         for (unsigned int q = 0; q < n_q_points; ++q)
426           {
427             for (unsigned int k = 0; k < dofs_per_cell; ++k)
428               {
429                 div_phi_u[k]  = fe_values[velocities].divergence(k, q);
430                 grad_phi_u[k] = fe_values[velocities].gradient(k, q);
431                 phi_u[k]      = fe_values[velocities].value(k, q);
432                 phi_p[k]      = fe_values[pressure].value(k, q);
433               }
434 
435             for (unsigned int i = 0; i < dofs_per_cell; ++i)
436               {
437                 if (assemble_matrix)
438                   {
439                     for (unsigned int j = 0; j < dofs_per_cell; ++j)
440                       {
441                         local_matrix(i, j) +=
442                           (viscosity *
443                              scalar_product(grad_phi_u[j], grad_phi_u[i]) +
444                            present_velocity_gradients[q] * phi_u[j] * phi_u[i] +
445                            grad_phi_u[j] * present_velocity_values[q] *
446                              phi_u[i] -
447                            div_phi_u[i] * phi_p[j] - phi_p[i] * div_phi_u[j] +
448                            gamma * div_phi_u[j] * div_phi_u[i] +
449                            phi_p[i] * phi_p[j]) *
450                           fe_values.JxW(q);
451                       }
452                   }
453 
454                 double present_velocity_divergence =
455                   trace(present_velocity_gradients[q]);
456                 local_rhs(i) +=
457                   (-viscosity * scalar_product(present_velocity_gradients[q],
458                                                grad_phi_u[i]) -
459                    present_velocity_gradients[q] * present_velocity_values[q] *
460                      phi_u[i] +
461                    present_pressure_values[q] * div_phi_u[i] +
462                    present_velocity_divergence * phi_p[i] -
463                    gamma * present_velocity_divergence * div_phi_u[i]) *
464                   fe_values.JxW(q);
465               }
466           }
467 
468         cell->get_dof_indices(local_dof_indices);
469 
470         const AffineConstraints<double> &constraints_used =
471           initial_step ? nonzero_constraints : zero_constraints;
472 
473         if (assemble_matrix)
474           {
475             constraints_used.distribute_local_to_global(local_matrix,
476                                                         local_rhs,
477                                                         local_dof_indices,
478                                                         system_matrix,
479                                                         system_rhs);
480           }
481         else
482           {
483             constraints_used.distribute_local_to_global(local_rhs,
484                                                         local_dof_indices,
485                                                         system_rhs);
486           }
487       }
488 
489     if (assemble_matrix)
490       {
491         // Finally we move pressure mass matrix into a separate matrix:
492         pressure_mass_matrix.reinit(sparsity_pattern.block(1, 1));
493         pressure_mass_matrix.copy_from(system_matrix.block(1, 1));
494 
495         // Note that settings this pressure block to zero is not identical to
496         // not assembling anything in this block, because this operation here
497         // will (incorrectly) delete diagonal entries that come in from
498         // hanging node constraints for pressure DoFs. This means that our
499         // whole system matrix will have rows that are completely
500         // zero. Luckily, FGMRES handles these rows without any problem.
501         system_matrix.block(1, 1) = 0;
502       }
503   }
504 
505   template <int dim>
assemble_system(const bool initial_step)506   void StationaryNavierStokes<dim>::assemble_system(const bool initial_step)
507   {
508     assemble(initial_step, true);
509   }
510 
511   template <int dim>
assemble_rhs(const bool initial_step)512   void StationaryNavierStokes<dim>::assemble_rhs(const bool initial_step)
513   {
514     assemble(initial_step, false);
515   }
516 
517   // @sect4{StationaryNavierStokes::solve}
518   //
519   // In this function, we use FGMRES together with the block preconditioner,
520   // which is defined at the beginning of the program, to solve the linear
521   // system. What we obtain at this step is the solution vector. If this is
522   // the initial step, the solution vector gives us an initial guess for the
523   // Navier Stokes equations. For the initial step, nonzero constraints are
524   // applied in order to make sure boundary conditions are satisfied. In the
525   // following steps, we will solve for the Newton update so zero
526   // constraints are used.
527   template <int dim>
solve(const bool initial_step)528   void StationaryNavierStokes<dim>::solve(const bool initial_step)
529   {
530     const AffineConstraints<double> &constraints_used =
531       initial_step ? nonzero_constraints : zero_constraints;
532 
533     SolverControl solver_control(system_matrix.m(),
534                                  1e-4 * system_rhs.l2_norm(),
535                                  true);
536 
537     SolverFGMRES<BlockVector<double>> gmres(solver_control);
538     SparseILU<double>                 pmass_preconditioner;
539     pmass_preconditioner.initialize(pressure_mass_matrix,
540                                     SparseILU<double>::AdditionalData());
541 
542     const BlockSchurPreconditioner<SparseILU<double>> preconditioner(
543       gamma,
544       viscosity,
545       system_matrix,
546       pressure_mass_matrix,
547       pmass_preconditioner);
548 
549     gmres.solve(system_matrix, newton_update, system_rhs, preconditioner);
550     std::cout << "FGMRES steps: " << solver_control.last_step() << std::endl;
551 
552     constraints_used.distribute(newton_update);
553   }
554 
555   // @sect4{StationaryNavierStokes::refine_mesh}
556   //
557   // After finding a good initial guess on the coarse mesh, we hope to
558   // decrease the error through refining the mesh. Here we do adaptive
559   // refinement similar to step-15 except that we use the Kelly estimator on
560   // the velocity only. We also need to transfer the current solution to the
561   // next mesh using the SolutionTransfer class.
562   template <int dim>
refine_mesh()563   void StationaryNavierStokes<dim>::refine_mesh()
564   {
565     Vector<float> estimated_error_per_cell(triangulation.n_active_cells());
566     FEValuesExtractors::Vector velocity(0);
567     KellyErrorEstimator<dim>::estimate(
568       dof_handler,
569       QGauss<dim - 1>(degree + 1),
570       std::map<types::boundary_id, const Function<dim> *>(),
571       present_solution,
572       estimated_error_per_cell,
573       fe.component_mask(velocity));
574 
575     GridRefinement::refine_and_coarsen_fixed_number(triangulation,
576                                                     estimated_error_per_cell,
577                                                     0.3,
578                                                     0.0);
579 
580     triangulation.prepare_coarsening_and_refinement();
581     SolutionTransfer<dim, BlockVector<double>> solution_transfer(dof_handler);
582     solution_transfer.prepare_for_coarsening_and_refinement(present_solution);
583     triangulation.execute_coarsening_and_refinement();
584 
585     // First the DoFHandler is set up and constraints are generated. Then we
586     // create a temporary BlockVector <code>tmp</code>, whose size is
587     // according with the solution on the new mesh.
588     setup_dofs();
589 
590     BlockVector<double> tmp(dofs_per_block);
591 
592     // Transfer solution from coarse to fine mesh and apply boundary value
593     // constraints to the new transferred solution. Note that present_solution
594     // is still a vector corresponding to the old mesh.
595     solution_transfer.interpolate(present_solution, tmp);
596     nonzero_constraints.distribute(tmp);
597 
598     // Finally set up matrix and vectors and set the present_solution to the
599     // interpolated data.
600     initialize_system();
601     present_solution = tmp;
602   }
603 
604   // @sect4{StationaryNavierStokes<dim>::newton_iteration}
605   //
606   // This function implements the Newton iteration with given tolerance, maximum
607   // number of iterations, and the number of mesh refinements to do.
608   //
609   // The argument <code>is_initial_step</code> tells us whether
610   // <code>setup_system</code> is necessary, and which part, system matrix or
611   // right hand side vector, should be assembled. If we do a line search, the
612   // right hand side is already assembled while checking the residual norm in
613   // the last iteration. Therefore, we just need to assemble the system matrix
614   // at the current iteration. The last argument <code>output_result</code>
615   // determines whether or not graphical output should be produced.
616   template <int dim>
newton_iteration(const double tolerance,const unsigned int max_n_line_searches,const unsigned int max_n_refinements,const bool is_initial_step,const bool output_result)617   void StationaryNavierStokes<dim>::newton_iteration(
618     const double       tolerance,
619     const unsigned int max_n_line_searches,
620     const unsigned int max_n_refinements,
621     const bool         is_initial_step,
622     const bool         output_result)
623   {
624     bool first_step = is_initial_step;
625 
626     for (unsigned int refinement_n = 0; refinement_n < max_n_refinements + 1;
627          ++refinement_n)
628       {
629         unsigned int line_search_n = 0;
630         double       last_res      = 1.0;
631         double       current_res   = 1.0;
632         std::cout << "grid refinements: " << refinement_n << std::endl
633                   << "viscosity: " << viscosity << std::endl;
634 
635         while ((first_step || (current_res > tolerance)) &&
636                line_search_n < max_n_line_searches)
637           {
638             if (first_step)
639               {
640                 setup_dofs();
641                 initialize_system();
642                 evaluation_point = present_solution;
643                 assemble_system(first_step);
644                 solve(first_step);
645                 present_solution = newton_update;
646                 nonzero_constraints.distribute(present_solution);
647                 first_step       = false;
648                 evaluation_point = present_solution;
649                 assemble_rhs(first_step);
650                 current_res = system_rhs.l2_norm();
651                 std::cout << "The residual of initial guess is " << current_res
652                           << std::endl;
653                 last_res = current_res;
654               }
655             else
656               {
657                 evaluation_point = present_solution;
658                 assemble_system(first_step);
659                 solve(first_step);
660 
661                 // To make sure our solution is getting close to the exact
662                 // solution, we let the solution be updated with a weight
663                 // <code>alpha</code> such that the new residual is smaller
664                 // than the one of last step, which is done in the following
665                 // loop. This is the same line search algorithm used in
666                 // step-15.
667                 for (double alpha = 1.0; alpha > 1e-5; alpha *= 0.5)
668                   {
669                     evaluation_point = present_solution;
670                     evaluation_point.add(alpha, newton_update);
671                     nonzero_constraints.distribute(evaluation_point);
672                     assemble_rhs(first_step);
673                     current_res = system_rhs.l2_norm();
674                     std::cout << "  alpha: " << std::setw(10) << alpha
675                               << std::setw(0) << "  residual: " << current_res
676                               << std::endl;
677                     if (current_res < last_res)
678                       break;
679                   }
680                 {
681                   present_solution = evaluation_point;
682                   std::cout << "  number of line searches: " << line_search_n
683                             << "  residual: " << current_res << std::endl;
684                   last_res = current_res;
685                 }
686                 ++line_search_n;
687               }
688 
689             if (output_result)
690               {
691                 output_results(max_n_line_searches * refinement_n +
692                                line_search_n);
693 
694                 if (current_res <= tolerance)
695                   process_solution(refinement_n);
696               }
697           }
698 
699         if (refinement_n < max_n_refinements)
700           {
701             refine_mesh();
702           }
703       }
704   }
705 
706   // @sect4{StationaryNavierStokes::compute_initial_guess}
707   //
708   // This function will provide us with an initial guess by using a
709   // continuation method as we discussed in the introduction. The Reynolds
710   // number is increased step-by-step until we reach the target value. By
711   // experiment, the solution to Stokes is good enough to be the initial guess
712   // of NSE with Reynolds number 1000 so we start there.  To make sure the
713   // solution from previous problem is close enough to the next one, the step
714   // size must be small enough.
715   template <int dim>
compute_initial_guess(double step_size)716   void StationaryNavierStokes<dim>::compute_initial_guess(double step_size)
717   {
718     const double target_Re = 1.0 / viscosity;
719 
720     bool is_initial_step = true;
721 
722     for (double Re = 1000.0; Re < target_Re;
723          Re        = std::min(Re + step_size, target_Re))
724       {
725         viscosity = 1.0 / Re;
726         std::cout << "Searching for initial guess with Re = " << Re
727                   << std::endl;
728         newton_iteration(1e-12, 50, 0, is_initial_step, false);
729         is_initial_step = false;
730       }
731   }
732 
733   // @sect4{StationaryNavierStokes::output_results}
734   //
735   // This function is the same as in step-22 except that we choose a name
736   // for the output file that also contains the Reynolds number (i.e., the
737   // inverse of the viscosity in the current context).
738   template <int dim>
output_results(const unsigned int output_index) const739   void StationaryNavierStokes<dim>::output_results(
740     const unsigned int output_index) const
741   {
742     std::vector<std::string> solution_names(dim, "velocity");
743     solution_names.emplace_back("pressure");
744 
745     std::vector<DataComponentInterpretation::DataComponentInterpretation>
746       data_component_interpretation(
747         dim, DataComponentInterpretation::component_is_part_of_vector);
748     data_component_interpretation.push_back(
749       DataComponentInterpretation::component_is_scalar);
750     DataOut<dim> data_out;
751     data_out.attach_dof_handler(dof_handler);
752     data_out.add_data_vector(present_solution,
753                              solution_names,
754                              DataOut<dim>::type_dof_data,
755                              data_component_interpretation);
756     data_out.build_patches();
757 
758     std::ofstream output(std::to_string(1.0 / viscosity) + "-solution-" +
759                          Utilities::int_to_string(output_index, 4) + ".vtk");
760     data_out.write_vtk(output);
761   }
762 
763   // @sect4{StationaryNavierStokes::process_solution}
764   //
765   // In our test case, we do not know the analytical solution. This function
766   // outputs the velocity components along $x=0.5$ and $0 \leq y \leq 1$ so they
767   // can be compared with data from the literature.
768   template <int dim>
process_solution(unsigned int refinement)769   void StationaryNavierStokes<dim>::process_solution(unsigned int refinement)
770   {
771     std::ofstream f(std::to_string(1.0 / viscosity) + "-line-" +
772                     std::to_string(refinement) + ".txt");
773     f << "# y u_x u_y" << std::endl;
774 
775     Point<dim> p;
776     p(0) = 0.5;
777     p(1) = 0.5;
778 
779     f << std::scientific;
780 
781     for (unsigned int i = 0; i <= 100; ++i)
782       {
783         p(dim - 1) = i / 100.0;
784 
785         Vector<double> tmp_vector(dim + 1);
786         VectorTools::point_value(dof_handler, present_solution, p, tmp_vector);
787         f << p(dim - 1);
788 
789         for (int j = 0; j < dim; j++)
790           f << " " << tmp_vector(j);
791         f << std::endl;
792       }
793   }
794 
795   // @sect4{StationaryNavierStokes::run}
796   //
797   // This is the last step of this program. In this part, we generate the grid
798   // and run the other functions respectively. The max refinement can be set by
799   // the argument.
800   template <int dim>
run(const unsigned int refinement)801   void StationaryNavierStokes<dim>::run(const unsigned int refinement)
802   {
803     GridGenerator::hyper_cube(triangulation);
804     triangulation.refine_global(5);
805 
806     const double Re = 1.0 / viscosity;
807 
808     // If the viscosity is smaller than $1/1000$, we have to first search for an
809     // initial guess via a continuation method. What we should notice is the
810     // search is always on the initial mesh, that is the $8 \times 8$ mesh in
811     // this program. After that, we just do the same as we did when viscosity
812     // is larger than $1/1000$: run Newton's iteration, refine the mesh,
813     // transfer solutions, and repeat.
814     if (Re > 1000.0)
815       {
816         std::cout << "Searching for initial guess ..." << std::endl;
817         const double step_size = 2000.0;
818         compute_initial_guess(step_size);
819         std::cout << "Found initial guess." << std::endl;
820         std::cout << "Computing solution with target Re = " << Re << std::endl;
821         viscosity = 1.0 / Re;
822         newton_iteration(1e-12, 50, refinement, false, true);
823       }
824     else
825       {
826         // When the viscosity is larger than 1/1000, the solution to Stokes
827         // equations is good enough as an initial guess. If so, we do not need
828         // to search for the initial guess using a continuation
829         // method. Newton's iteration can be started directly.
830 
831         newton_iteration(1e-12, 50, refinement, true, true);
832       }
833   }
834 } // namespace Step57
835 
main()836 int main()
837 {
838   try
839     {
840       using namespace Step57;
841 
842       StationaryNavierStokes<2> flow(/* degree = */ 1);
843       flow.run(4);
844     }
845   catch (std::exception &exc)
846     {
847       std::cerr << std::endl
848                 << std::endl
849                 << "----------------------------------------------------"
850                 << std::endl;
851       std::cerr << "Exception on processing: " << std::endl
852                 << exc.what() << std::endl
853                 << "Aborting!" << std::endl
854                 << "----------------------------------------------------"
855                 << std::endl;
856       return 1;
857     }
858   catch (...)
859     {
860       std::cerr << std::endl
861                 << std::endl
862                 << "----------------------------------------------------"
863                 << std::endl;
864       std::cerr << "Unknown exception!" << std::endl
865                 << "Aborting!" << std::endl
866                 << "----------------------------------------------------"
867                 << std::endl;
868       return 1;
869     }
870   return 0;
871 }
872