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