ryujin 2.1.1 revision 0348cbb53a3e4b1da2a4c037e81f88f2d21ce219
parabolic_solver.template.h
Go to the documentation of this file.
1//
2// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
3// Copyright (C) 2023 by the ryujin authors
4//
5
6#pragma once
7
8#include "parabolic_solver.h"
9
10#include <introspection.h>
11#include <openmp.h>
12#include <scope.h>
13#include <simd.h>
14
15#include <deal.II/lac/linear_operator.h>
16#include <deal.II/lac/precondition.h>
17#include <deal.II/lac/solver_cg.h>
18#include <deal.II/matrix_free/fe_evaluation.h>
19#include <deal.II/multigrid/mg_coarse.h>
20#include <deal.II/multigrid/mg_matrix.h>
21#include <deal.II/multigrid/mg_transfer.templates.h>
22#include <deal.II/multigrid/mg_transfer_matrix_free.h>
23#include <deal.II/multigrid/multigrid.h>
24
25#include <atomic>
26
27namespace ryujin
28{
29 namespace NavierStokes
30 {
31 using namespace dealii;
32
33 template <typename Description, int dim, typename Number>
35 const MPIEnsemble &mpi_ensemble,
36 std::map<std::string, dealii::Timer> &computing_timer,
37 const HyperbolicSystem &hyperbolic_system,
38 const ParabolicSystem &parabolic_system,
39 const OfflineData<dim, Number> &offline_data,
40 const InitialValues<Description, dim, Number> &initial_values,
41 const std::string &subsection /*= "ParabolicSolver"*/)
42 : ParameterAcceptor(subsection)
43 , mpi_ensemble_(mpi_ensemble)
44 , computing_timer_(computing_timer)
45 , hyperbolic_system_(&hyperbolic_system)
46 , parabolic_system_(&parabolic_system)
47 , offline_data_(&offline_data)
48 , initial_values_(&initial_values)
49 , n_restarts_(0)
50 , n_warnings_(0)
51 , n_iterations_velocity_(0.)
52 , n_iterations_internal_energy_(0.)
53 {
54 use_gmg_velocity_ = false;
55 add_parameter("multigrid velocity",
56 use_gmg_velocity_,
57 "Use geometric multigrid for velocity component");
58
59 gmg_max_iter_vel_ = 12;
60 add_parameter("multigrid velocity - max iter",
61 gmg_max_iter_vel_,
62 "Maximal number of CG iterations with GMG smoother");
63
64 gmg_smoother_range_vel_ = 8.;
65 add_parameter("multigrid velocity - chebyshev range",
66 gmg_smoother_range_vel_,
67 "Chebyshev smoother: eigenvalue range parameter");
68
69 gmg_smoother_max_eig_vel_ = 2.0;
70 add_parameter("multigrid velocity - chebyshev max eig",
71 gmg_smoother_max_eig_vel_,
72 "Chebyshev smoother: maximal eigenvalue");
73
74 use_gmg_internal_energy_ = false;
75 add_parameter("multigrid energy",
76 use_gmg_internal_energy_,
77 "Use geometric multigrid for internal energy component");
78
79 gmg_max_iter_en_ = 15;
80 add_parameter("multigrid energy - max iter",
81 gmg_max_iter_en_,
82 "Maximal number of CG iterations with GMG smoother");
83
84 gmg_smoother_range_en_ = 15.;
85 add_parameter("multigrid energy - chebyshev range",
86 gmg_smoother_range_en_,
87 "Chebyshev smoother: eigenvalue range parameter");
88
89 gmg_smoother_max_eig_en_ = 2.0;
90 add_parameter("multigrid energy - chebyshev max eig",
91 gmg_smoother_max_eig_en_,
92 "Chebyshev smoother: maximal eigenvalue");
93
94 gmg_smoother_degree_ = 3;
95 add_parameter("multigrid - chebyshev degree",
96 gmg_smoother_degree_,
97 "Chebyshev smoother: degree");
98
99 gmg_smoother_n_cg_iter_ = 10;
100 add_parameter(
101 "multigrid - chebyshev cg iter",
102 gmg_smoother_n_cg_iter_,
103 "Chebyshev smoother: number of CG iterations to approximate "
104 "eigenvalue");
105
106 gmg_min_level_ = 0;
107 add_parameter(
108 "multigrid - min level",
109 gmg_min_level_,
110 "Minimal mesh level to be visited in the geometric multigrid "
111 "cycle where the coarse grid solver (Chebyshev) is called");
112
113 tolerance_ = Number(1.0e-12);
114 add_parameter("tolerance", tolerance_, "Tolerance for linear solvers");
115
116 tolerance_linfty_norm_ = false;
117 add_parameter("tolerance linfty norm",
118 tolerance_linfty_norm_,
119 "Use the l_infty norm instead of the l_2 norm for the "
120 "stopping criterion");
121 }
122
123
124 template <typename Description, int dim, typename Number>
126 {
127#ifdef DEBUG_OUTPUT
128 std::cout << "ParabolicSolver<dim, Number>::prepare()" << std::endl;
129#endif
130
131 const auto &discretization = offline_data_->discretization();
132 AssertThrow(discretization.ansatz() == Ansatz::cg_q1,
133 dealii::ExcMessage("The NavierStokes module currently only "
134 "supports cG Q1 finite elements."));
135
136 /* Initialize vectors: */
137
138 typename MatrixFree<dim, Number>::AdditionalData additional_data;
139 additional_data.tasks_parallel_scheme =
140 MatrixFree<dim, Number>::AdditionalData::none;
141
142 matrix_free_.reinit(discretization.mapping(),
143 offline_data_->dof_handler(),
144 offline_data_->affine_constraints(),
145 discretization.quadrature_1d(),
146 additional_data);
147
148 const auto &scalar_partitioner =
149 matrix_free_.get_dof_info(0).vector_partitioner;
150
151 velocity_.reinit(dim);
152 velocity_rhs_.reinit(dim);
153 for (unsigned int i = 0; i < dim; ++i) {
154 velocity_.block(i).reinit(scalar_partitioner);
155 velocity_rhs_.block(i).reinit(scalar_partitioner);
156 }
157
158 internal_energy_.reinit(scalar_partitioner);
159 internal_energy_rhs_.reinit(scalar_partitioner);
160
161 density_.reinit(scalar_partitioner);
162
163 /* Initialize multigrid: */
164
165 if (!use_gmg_velocity_ && !use_gmg_internal_energy_)
166 return;
167
168 const unsigned int n_levels =
169 offline_data_->dof_handler().get_triangulation().n_global_levels();
170 const unsigned int min_level = std::min(gmg_min_level_, n_levels - 1);
171 MGLevelObject<IndexSet> relevant_sets(0, n_levels - 1);
172 for (unsigned int level = 0; level < n_levels; ++level)
173 dealii::DoFTools::extract_locally_relevant_level_dofs(
174 offline_data_->dof_handler(), level, relevant_sets[level]);
175 mg_constrained_dofs_.initialize(offline_data_->dof_handler(),
176 relevant_sets);
177 std::set<types::boundary_id> boundary_ids;
178 boundary_ids.insert(Boundary::dirichlet);
179 boundary_ids.insert(Boundary::no_slip);
180 mg_constrained_dofs_.make_zero_boundary_constraints(
181 offline_data_->dof_handler(), boundary_ids);
182
183 typename MatrixFree<dim, float>::AdditionalData additional_data_level;
184 additional_data_level.tasks_parallel_scheme =
185 MatrixFree<dim, float>::AdditionalData::none;
186
187 level_matrix_free_.resize(min_level, n_levels - 1);
188 level_density_.resize(min_level, n_levels - 1);
189 for (unsigned int level = min_level; level < n_levels; ++level) {
190 additional_data_level.mg_level = level;
191 AffineConstraints<double> constraints(relevant_sets[level]);
192 // constraints.add_lines(mg_constrained_dofs_.get_boundary_indices(level));
193 // constraints.merge(mg_constrained_dofs_.get_level_constraints(level));
194 constraints.close();
195 level_matrix_free_[level].reinit(discretization.mapping(),
196 offline_data_->dof_handler(),
197 constraints,
198 discretization.quadrature_1d(),
199 additional_data_level);
200 level_matrix_free_[level].initialize_dof_vector(level_density_[level]);
201 }
202
203 mg_transfer_velocity_.build(offline_data_->dof_handler(),
204 mg_constrained_dofs_,
205 level_matrix_free_);
206 mg_transfer_energy_.build(offline_data_->dof_handler(),
207 level_matrix_free_);
208 }
209
210
211 template <typename Description, int dim, typename Number>
213 const StateVector &old_state_vector,
214 const Number t,
215 StateVector &new_state_vector,
216 Number tau,
217 const IDViolationStrategy id_violation_strategy,
218 const bool reinitialize_gmg) const
219 {
220#ifdef DEBUG_OUTPUT
221 std::cout << "ParabolicSolver<dim, Number>::step()" << std::endl;
222#endif
223
224 const auto &old_U = std::get<0>(old_state_vector);
225 auto &new_U = std::get<0>(new_state_vector);
226
228
229 using VA = VectorizedArray<Number>;
230
231 const auto &lumped_mass_matrix = offline_data_->lumped_mass_matrix();
232 const auto &affine_constraints = offline_data_->affine_constraints();
233
234 /* Index ranges for the iteration over the sparsity pattern : */
235
236 constexpr auto simd_length = VA::size();
237 const unsigned int n_owned = offline_data_->n_locally_owned();
238 const unsigned int n_regular = n_owned / simd_length * simd_length;
239
240 DiagonalMatrix<dim, Number> diagonal_matrix;
241
242#ifdef DEBUG_OUTPUT
243 std::cout << " perform time-step with tau = " << tau << std::endl;
244#endif
245
246 /* A boolean signalling that a restart is necessary: */
247 std::atomic<bool> restart_needed = false;
248
249 /*
250 * Step 1:
251 *
252 * Build right hand side for the velocity update.
253 * Also initialize solution vectors for internal energy and velocity
254 * update.
255 */
256 {
257 Scope scope(computing_timer_, "time step [P] 1 - update velocities");
259 LIKWID_MARKER_START("time_step_parabolic_1");
260
261 auto loop = [&](auto sentinel, unsigned int left, unsigned int right) {
262 using T = decltype(sentinel);
263 unsigned int stride_size = get_stride_size<T>;
264
265 const auto view = hyperbolic_system_->template view<dim, T>();
266
268 for (unsigned int i = left; i < right; i += stride_size) {
269 const auto U_i = old_U.template get_tensor<T>(i);
270 const auto rho_i = view.density(U_i);
271 const auto M_i = view.momentum(U_i);
272 const auto rho_e_i = view.internal_energy(U_i);
273 const auto m_i = get_entry<T>(lumped_mass_matrix, i);
274
275 write_entry<T>(density_, rho_i, i);
276 /* (5.4a) */
277 for (unsigned int d = 0; d < dim; ++d) {
278 write_entry<T>(velocity_.block(d), M_i[d] / rho_i, i);
279 write_entry<T>(velocity_rhs_.block(d), m_i * (M_i[d]), i);
280 }
281 write_entry<T>(internal_energy_, rho_e_i / rho_i, i);
282 }
283 };
284
285 /* Parallel non-vectorized loop: */
286 loop(Number(), n_regular, n_owned);
287 /* Parallel vectorized SIMD loop: */
288 loop(VA(), 0, n_regular);
289
291
292 /*
293 * Set up "strongly enforced" boundary conditions that are not stored
294 * in the AffineConstraints map. In this case we enforce boundary
295 * values by imposing them strongly in the iteration by setting the
296 * initial vector and the right hand side to the right value:
297 */
298
299 const auto &boundary_map = offline_data_->boundary_map();
300
301 for (auto entry : boundary_map) {
302 // [i, normal, normal_mass, boundary_mass, id, position] = entry
303 const auto i = std::get<0>(entry);
304 if (i >= n_owned)
305 continue;
306
307 const auto normal = std::get<1>(entry);
308 const auto id = std::get<4>(entry);
309 const auto position = std::get<5>(entry);
310
311 if (id == Boundary::slip) {
312 /* Remove normal component of velocity: */
313 Tensor<1, dim, Number> V_i;
314 Tensor<1, dim, Number> RHS_i;
315 for (unsigned int d = 0; d < dim; ++d) {
316 V_i[d] = velocity_.block(d).local_element(i);
317 RHS_i[d] = velocity_rhs_.block(d).local_element(i);
318 }
319 V_i -= 1. * (V_i * normal) * normal;
320 RHS_i -= 1. * (RHS_i * normal) * normal;
321 for (unsigned int d = 0; d < dim; ++d) {
322 velocity_.block(d).local_element(i) = V_i[d];
323 velocity_rhs_.block(d).local_element(i) = RHS_i[d];
324 }
325
326 } else if (id == Boundary::no_slip) {
327
328 /* Set velocity to zero: */
329 for (unsigned int d = 0; d < dim; ++d) {
330 velocity_.block(d).local_element(i) = Number(0.);
331 velocity_rhs_.block(d).local_element(i) = Number(0.);
332 }
333
334 } else if (id == Boundary::dirichlet) {
335
336 /* Prescribe velocity: */
337 const auto U_i = initial_values_->initial_state(position, t + tau);
338 const auto view = hyperbolic_system_->template view<dim, Number>();
339 const auto rho_i = view.density(U_i);
340 const auto V_i = view.momentum(U_i) / rho_i;
341 const auto e_i = view.internal_energy(U_i) / rho_i;
342
343 for (unsigned int d = 0; d < dim; ++d) {
344 velocity_.block(d).local_element(i) = V_i[d];
345 velocity_rhs_.block(d).local_element(i) = V_i[d];
346 }
347
348 internal_energy_.local_element(i) = e_i;
349 }
350 }
351
352 /*
353 * Zero out constrained degrees of freedom due to periodic boundary
354 * conditions. These boundary conditions are enforced by modifying
355 * the stencil - consequently we have to remove constrained dofs from
356 * the linear system.
357 */
358
359 affine_constraints.set_zero(density_);
360 affine_constraints.set_zero(internal_energy_);
361 for (unsigned int d = 0; d < dim; ++d) {
362 affine_constraints.set_zero(velocity_.block(d));
363 affine_constraints.set_zero(velocity_rhs_.block(d));
364 }
365
366 /* Prepare preconditioner: */
367
368 diagonal_matrix.reinit(
369 lumped_mass_matrix, density_, affine_constraints);
370
371 /*
372 * Update MG matrices all 4 time steps; this is a balance because more
373 * refreshes will render the approximation better, at some additional
374 * cost.
375 */
376 if (use_gmg_velocity_ && reinitialize_gmg) {
377 MGLevelObject<typename PreconditionChebyshev<
379 LinearAlgebra::distributed::BlockVector<float>,
380 DiagonalMatrix<dim, float>>::AdditionalData>
381 smoother_data(level_matrix_free_.min_level(),
382 level_matrix_free_.max_level());
383
384 level_velocity_matrices_.resize(level_matrix_free_.min_level(),
385 level_matrix_free_.max_level());
386 mg_transfer_velocity_.interpolate_to_mg(
387 offline_data_->dof_handler(), level_density_, density_);
388
389 for (unsigned int level = level_matrix_free_.min_level();
390 level <= level_matrix_free_.max_level();
391 ++level) {
392 level_velocity_matrices_[level].initialize(
393 *parabolic_system_,
394 *offline_data_,
395 level_matrix_free_[level],
396 level_density_[level],
397 tau,
398 level);
399 level_velocity_matrices_[level].compute_diagonal(
400 smoother_data[level].preconditioner);
401 if (level == level_matrix_free_.min_level()) {
402 smoother_data[level].degree = numbers::invalid_unsigned_int;
403 smoother_data[level].eig_cg_n_iterations = 500;
404 smoother_data[level].smoothing_range = 1e-3;
405 } else {
406 smoother_data[level].degree = gmg_smoother_degree_;
407 smoother_data[level].eig_cg_n_iterations =
408 gmg_smoother_n_cg_iter_;
409 smoother_data[level].smoothing_range = gmg_smoother_range_vel_;
410 if (gmg_smoother_n_cg_iter_ == 0)
411 smoother_data[level].max_eigenvalue = gmg_smoother_max_eig_vel_;
412 }
413 }
414 mg_smoother_velocity_.initialize(level_velocity_matrices_,
415 smoother_data);
416 }
417
418 LIKWID_MARKER_STOP("time_step_parabolic_1");
419 }
420
421 Number e_min_old;
422
423 {
424 Scope scope(computing_timer_,
425 "time step [P] _ - synchronization barriers");
426
427 /* Compute the global minimum of the internal energy: */
428
429 // .begin() and .end() denote the locally owned index range:
430 e_min_old =
431 *std::min_element(internal_energy_.begin(), internal_energy_.end());
432
433 e_min_old = Utilities::MPI::min(e_min_old,
434 mpi_ensemble_.ensemble_communicator());
435
436 // FIXME: create a meaningful relaxation based on global mesh size min.
437 constexpr Number eps = std::numeric_limits<Number>::epsilon();
438 e_min_old *= (1. - 1000. * eps);
439 }
440
441 /*
442 * Step 1: Solve velocity update:
443 */
444 {
445 Scope scope(computing_timer_, "time step [P] 1 - update velocities");
446
447 LIKWID_MARKER_START("time_step_parabolic_1");
448
449 VelocityMatrix<dim, Number, Number> velocity_operator;
450 velocity_operator.initialize(
451 *parabolic_system_, *offline_data_, matrix_free_, density_, tau);
452
453 const auto tolerance_velocity =
454 (tolerance_linfty_norm_ ? velocity_rhs_.linfty_norm()
455 : velocity_rhs_.l2_norm()) *
456 tolerance_;
457
458 /*
459 * Multigrid might lack robustness for some cases, so in case it takes
460 * too many iterations we better switch to the more robust plain
461 * conjugate gradient method.
462 */
463 try {
464 if (!use_gmg_velocity_)
465 throw SolverControl::NoConvergence(0, 0.);
466
467 using bvt_float = LinearAlgebra::distributed::BlockVector<float>;
468
469 MGCoarseGridApplySmoother<bvt_float> mg_coarse;
470 mg_coarse.initialize(mg_smoother_velocity_);
471
472 mg::Matrix<bvt_float> mg_matrix(level_velocity_matrices_);
473
474 Multigrid<bvt_float> mg(mg_matrix,
475 mg_coarse,
476 mg_transfer_velocity_,
477 mg_smoother_velocity_,
478 mg_smoother_velocity_,
479 level_velocity_matrices_.min_level(),
480 level_velocity_matrices_.max_level());
481
482 const auto &dof_handler = offline_data_->dof_handler();
483 PreconditionMG<dim, bvt_float, MGTransferVelocity<dim, float>>
484 preconditioner(dof_handler, mg, mg_transfer_velocity_);
485
486 SolverControl solver_control(gmg_max_iter_vel_, tolerance_velocity);
487 SolverCG<BlockVector> solver(solver_control);
488 solver.solve(
489 velocity_operator, velocity_, velocity_rhs_, preconditioner);
490
491 /* update exponential moving average */
492 n_iterations_velocity_ =
493 0.9 * n_iterations_velocity_ + 0.1 * solver_control.last_step();
494
495 } catch (SolverControl::NoConvergence &) {
496
497 SolverControl solver_control(1000, tolerance_velocity);
498 SolverCG<BlockVector> solver(solver_control);
499 solver.solve(
500 velocity_operator, velocity_, velocity_rhs_, diagonal_matrix);
501
502 /* update exponential moving average, counting also GMG iterations */
503 n_iterations_velocity_ *= 0.9;
504 n_iterations_velocity_ +=
505 0.1 * (use_gmg_velocity_ ? gmg_max_iter_vel_ : 0) +
506 0.1 * solver_control.last_step();
507 }
508
509 LIKWID_MARKER_STOP("time_step_parabolic_1");
510 }
511
512 /*
513 * Step 2: Build internal energy right hand side:
514 */
515 {
516 Scope scope(computing_timer_,
517 "time step [P] 2 - update internal energy");
518
519 LIKWID_MARKER_START("time_step_parabolic_2");
520
521 /* Compute m_i K_i^{n+1/2}: (5.5) */
522 matrix_free_.template cell_loop<ScalarVector, BlockVector>(
523 [this](const auto &data,
524 auto &dst,
525 const auto &src,
526 const auto cell_range) {
527 FEEvaluation<dim, order_fe, order_quad, dim, Number> velocity(
528 data);
529 FEEvaluation<dim, order_fe, order_quad, 1, Number> energy(data);
530
531 const auto mu = parabolic_system_->mu();
532 const auto lambda = parabolic_system_->lambda();
533
534 for (unsigned int cell = cell_range.first;
535 cell < cell_range.second;
536 ++cell) {
537 velocity.reinit(cell);
538 energy.reinit(cell);
539 velocity.gather_evaluate(src, EvaluationFlags::gradients);
540
541 for (unsigned int q = 0; q < velocity.n_q_points; ++q) {
542 if constexpr (dim == 1) {
543 /* Workaround: no symmetric gradient for dim == 1: */
544 const auto gradient = velocity.get_gradient(q);
545 auto S = (4. / 3. * mu + lambda) * gradient;
546 energy.submit_value(gradient * S, q);
547
548 } else {
549
550 const auto symmetric_gradient =
551 velocity.get_symmetric_gradient(q);
552 const auto divergence = trace(symmetric_gradient);
553 auto S = 2. * mu * symmetric_gradient;
554 for (unsigned int d = 0; d < dim; ++d)
555 S[d][d] += (lambda - 2. / 3. * mu) * divergence;
556 energy.submit_value(symmetric_gradient * S, q);
557 }
558 }
559 energy.integrate_scatter(EvaluationFlags::values, dst);
560 }
561 },
562 internal_energy_rhs_,
563 velocity_,
564 /* zero destination */ true);
565
566 const auto &lumped_mass_matrix = offline_data_->lumped_mass_matrix();
567
569
570 auto loop = [&](auto sentinel, unsigned int left, unsigned int right) {
571 using T = decltype(sentinel);
572 unsigned int stride_size = get_stride_size<T>;
573
574 const auto view = hyperbolic_system_->template view<dim, T>();
575
577 for (unsigned int i = left; i < right; i += stride_size) {
578 const auto rhs_i = get_entry<T>(internal_energy_rhs_, i);
579 const auto m_i = get_entry<T>(lumped_mass_matrix, i);
580 const auto rho_i = get_entry<T>(density_, i);
581 const auto e_i = get_entry<T>(internal_energy_, i);
582
583 const auto U_i = old_U.template get_tensor<T>(i);
584 const auto V_i = view.momentum(U_i) / rho_i;
585
586 dealii::Tensor<1, dim, T> V_i_new;
587 for (unsigned int d = 0; d < dim; ++d) {
588 V_i_new[d] = get_entry<T>(velocity_.block(d), i);
589 }
590
591 /*
592 * For backward Euler we have to add this algebraic correction
593 * to ensure conservation of total energy.
594 */
595 const auto correction = Number(0.5) * (V_i - V_i_new).norm_square();
596
597 /* rhs_i contains already m_i K_i^{n+1/2} */
598 const auto result = m_i * rho_i * (e_i + correction) + tau * rhs_i;
599 write_entry<T>(internal_energy_rhs_, result, i);
600 }
601 };
602
603 /* Parallel non-vectorized loop: */
604 loop(Number(), n_regular, n_owned);
605 /* Parallel vectorized SIMD loop: */
606 loop(VA(), 0, n_regular);
607
609
610 /*
611 * Set up "strongly enforced" boundary conditions that are not stored
612 * in the AffineConstraints map: We enforce Neumann conditions (i.e.,
613 * insulating boundary conditions) everywhere except for Dirichlet
614 * boundaries where we have to enforce prescribed conditions:
615 */
616
617 const auto &boundary_map = offline_data_->boundary_map();
618
619 for (auto entry : boundary_map) {
620 // [i, normal, normal_mass, boundary_mass, id, position] = entry
621 const auto i = std::get<0>(entry);
622 if (i >= n_owned)
623 continue;
624
625 const auto id = std::get<4>(entry);
626 const auto position = std::get<5>(entry);
627
628 if (id == Boundary::dirichlet) {
629 /* Prescribe internal energy: */
630 const auto U_i = initial_values_->initial_state(position, t + tau);
631 const auto view = hyperbolic_system_->template view<dim, Number>();
632 const auto rho_i = view.density(U_i);
633 const auto e_i = view.internal_energy(U_i) / rho_i;
634 internal_energy_rhs_.local_element(i) = e_i;
635 }
636 }
637
638 /*
639 * Zero out constrained degrees of freedom due to periodic boundary
640 * conditions. These boundary conditions are enforced by modifying
641 * the stencil - consequently we have to remove constrained dofs from
642 * the linear system.
643 */
644 affine_constraints.set_zero(internal_energy_rhs_);
645
646 /*
647 * Update MG matrices all 4 time steps; this is a balance because more
648 * refreshes will render the approximation better, at some additional
649 * cost.
650 */
651 if (use_gmg_internal_energy_ && reinitialize_gmg) {
652 MGLevelObject<typename PreconditionChebyshev<
654 LinearAlgebra::distributed::Vector<float>>::AdditionalData>
655 smoother_data(level_matrix_free_.min_level(),
656 level_matrix_free_.max_level());
657
658 level_energy_matrices_.resize(level_matrix_free_.min_level(),
659 level_matrix_free_.max_level());
660
661 for (unsigned int level = level_matrix_free_.min_level();
662 level <= level_matrix_free_.max_level();
663 ++level) {
664 level_energy_matrices_[level].initialize(
665 *offline_data_,
666 level_matrix_free_[level],
667 level_density_[level],
668 tau * parabolic_system_->cv_inverse_kappa(),
669 level);
670 level_energy_matrices_[level].compute_diagonal(
671 smoother_data[level].preconditioner);
672 if (level == level_matrix_free_.min_level()) {
673 smoother_data[level].degree = numbers::invalid_unsigned_int;
674 smoother_data[level].eig_cg_n_iterations = 500;
675 smoother_data[level].smoothing_range = 1e-3;
676 } else {
677 smoother_data[level].degree = gmg_smoother_degree_;
678 smoother_data[level].eig_cg_n_iterations =
679 gmg_smoother_n_cg_iter_;
680 smoother_data[level].smoothing_range = gmg_smoother_range_en_;
681 if (gmg_smoother_n_cg_iter_ == 0)
682 smoother_data[level].max_eigenvalue = gmg_smoother_max_eig_en_;
683 }
684 }
685 mg_smoother_energy_.initialize(level_energy_matrices_, smoother_data);
686 }
687
688 LIKWID_MARKER_STOP("time_step_parabolic_2");
689 }
690
691 /*
692 * Step 2: Solve internal energy update:
693 */
694 {
695 Scope scope(computing_timer_,
696 "time step [P] 2 - update internal energy");
697
698 LIKWID_MARKER_START("time_step_parabolic_2");
699
700 EnergyMatrix<dim, Number, Number> energy_operator;
701 const auto &kappa = parabolic_system_->cv_inverse_kappa();
702 energy_operator.initialize(
703 *offline_data_, matrix_free_, density_, tau * kappa);
704
705 const auto tolerance_internal_energy =
706 (tolerance_linfty_norm_ ? internal_energy_rhs_.linfty_norm()
707 : internal_energy_rhs_.l2_norm()) *
708 tolerance_;
709
710 try {
711 if (!use_gmg_internal_energy_)
712 throw SolverControl::NoConvergence(0, 0.);
713
714 using vt_float = LinearAlgebra::distributed::Vector<float>;
715 MGCoarseGridApplySmoother<vt_float> mg_coarse;
716 mg_coarse.initialize(mg_smoother_energy_);
717 mg::Matrix<vt_float> mg_matrix(level_energy_matrices_);
718
719 Multigrid<vt_float> mg(mg_matrix,
720 mg_coarse,
721 mg_transfer_energy_,
722 mg_smoother_energy_,
723 mg_smoother_energy_,
724 level_energy_matrices_.min_level(),
725 level_energy_matrices_.max_level());
726
727 const auto &dof_handler = offline_data_->dof_handler();
728 PreconditionMG<dim, vt_float, MGTransferEnergy<dim, float>>
729 preconditioner(dof_handler, mg, mg_transfer_energy_);
730
731 SolverControl solver_control(gmg_max_iter_en_,
732 tolerance_internal_energy);
733 SolverCG<ScalarVector> solver(solver_control);
734 solver.solve(energy_operator,
735 internal_energy_,
736 internal_energy_rhs_,
737 preconditioner);
738
739 /* update exponential moving average */
740 n_iterations_internal_energy_ = 0.9 * n_iterations_internal_energy_ +
741 0.1 * solver_control.last_step();
742
743 } catch (SolverControl::NoConvergence &) {
744
745 SolverControl solver_control(1000, tolerance_internal_energy);
746 SolverCG<ScalarVector> solver(solver_control);
747 solver.solve(energy_operator,
748 internal_energy_,
749 internal_energy_rhs_,
750 diagonal_matrix);
751
752 /* update exponential moving average, counting also GMG iterations */
753 n_iterations_internal_energy_ *= 0.9;
754 n_iterations_internal_energy_ +=
755 0.1 * (use_gmg_internal_energy_ ? gmg_max_iter_en_ : 0) +
756 0.1 * solver_control.last_step();
757 }
758
759 /*
760 * Check for local minimum principle on internal energy:
761 */
762 {
763 Scope scope(computing_timer_,
764 "time step [P] _ - synchronization barriers");
765
766 // .begin() and .end() denote the locally owned index range:
767 auto e_min_new = *std::min_element(internal_energy_.begin(),
768 internal_energy_.end());
769 e_min_new = Utilities::MPI::min(
770 e_min_new, mpi_ensemble_.ensemble_communicator());
771
772 if (e_min_new < e_min_old) {
773#ifdef DEBUG_OUTPUT
774 std::cout << std::fixed << std::setprecision(16);
775 std::cout << "Bounds violation: internal energy (critical)!\n"
776 << "\t\te_min_old: " << e_min_old << "\n"
777 << "\t\te_min_old (delta): "
778 << negative_part(e_min_new - e_min_old) << "\n"
779 << "\t\te_min_new: " << e_min_new << "\n"
780 << std::endl;
781#endif
782 restart_needed = true;
783 }
784 }
785
786 LIKWID_MARKER_STOP("time_step_parabolic_2");
787 }
788
789 /*
790 * Step 3: Copy vectors
791 *
792 * FIXME: Memory access is suboptimal...
793 */
794 {
795 Scope scope(computing_timer_, "time step [P] 3 - write back vectors");
796
798 LIKWID_MARKER_START("time_step_parabolic_3");
799
800 auto loop = [&](auto sentinel, unsigned int left, unsigned int right) {
801 using T = decltype(sentinel);
802 unsigned int stride_size = get_stride_size<T>;
803
804 const auto view = hyperbolic_system_->template view<dim, T>();
805
807 for (unsigned int i = left; i < right; i += stride_size) {
808 auto U_i = old_U.template get_tensor<T>(i);
809 const auto rho_i = view.density(U_i);
810
811 Tensor<1, dim, T> m_i_new;
812 for (unsigned int d = 0; d < dim; ++d) {
813 m_i_new[d] = rho_i * get_entry<T>(velocity_.block(d), i);
814 }
815
816 const auto rho_e_i_new = rho_i * get_entry<T>(internal_energy_, i);
817
818 const auto E_i_new = rho_e_i_new + 0.5 * m_i_new * m_i_new / rho_i;
819
820 for (unsigned int d = 0; d < dim; ++d)
821 U_i[1 + d] = m_i_new[d];
822 U_i[1 + dim] = E_i_new;
823
824 new_U.template write_tensor<T>(U_i, i);
825 }
826 };
827
828 /* Parallel non-vectorized loop: */
829 loop(Number(), n_regular, n_owned);
830 /* Parallel vectorized SIMD loop: */
831 loop(VA(), 0, n_regular);
832
833 LIKWID_MARKER_STOP("time_step_parabolic_3");
835
836 new_U.update_ghost_values();
837 }
838
840
841 {
842 Scope scope(computing_timer_,
843 "time step [H] _ - synchronization barriers");
844
845 /*
846 * Synchronize whether we have to restart the time step. Even though
847 * the restart condition itself only affects the local ensemble we
848 * nevertheless need to synchronize the boolean in case we perform
849 * synchronized global time steps. (Otherwise different ensembles
850 * might end up with a different time step.)
851 */
852 restart_needed.store(Utilities::MPI::logical_or(
853 restart_needed.load(),
854 mpi_ensemble_.synchronization_communicator()));
855 }
856
857 if (restart_needed) {
858 switch (id_violation_strategy) {
860 n_warnings_++;
861 break;
863 n_restarts_++;
864 throw Restart();
865 }
866 }
867 }
868
869
870 template <typename Description, int dim, typename Number>
872 std::ostream &output) const
873 {
874 output << " [ " << std::setprecision(2) << std::fixed
875 << n_iterations_velocity_
876 << (use_gmg_velocity_ ? " GMG vel -- " : " CG vel -- ")
877 << n_iterations_internal_energy_
878 << (use_gmg_internal_energy_ ? " GMG int ]" : " CG int ]")
879 << std::endl;
880 }
881
882 } // namespace NavierStokes
883} /* namespace ryujin */
void reinit(const vector_type &lumped_mass_matrix, const vector_type &density, const dealii::AffineConstraints< Number > &affine_constraints)
void initialize(const OfflineData< dim, Number2 > &offline_data, const dealii::MatrixFree< dim, Number > &matrix_free, const dealii::LinearAlgebra::distributed::Vector< Number > &density, const Number time_factor, const unsigned int level=dealii::numbers::invalid_unsigned_int)
void backward_euler_step(const StateVector &old_state_vector, const Number old_t, StateVector &new_state_vector, Number tau, const IDViolationStrategy id_violation_strategy, const bool reinitialize_gmg) const
typename Description::ParabolicSystem ParabolicSystem
typename View::StateVector StateVector
void print_solver_statistics(std::ostream &output) const
typename Description::HyperbolicSystem HyperbolicSystem
ParabolicSolver(const MPIEnsemble &mpi_ensemble, std::map< std::string, dealii::Timer > &computing_timer, const HyperbolicSystem &hyperbolic_system, const ParabolicSystem &parabolic_system, const OfflineData< dim, Number > &offline_data, const InitialValues< Description, dim, Number > &initial_values, const std::string &subsection="ParabolicSolver")
void initialize(const ParabolicSystem &parabolic_system, const OfflineData< dim, Number2 > &offline_data, const dealii::MatrixFree< dim, Number > &matrix_free, const dealii::LinearAlgebra::distributed::Vector< Number > &density, const Number theta_x_tau, const unsigned int level=dealii::numbers::invalid_unsigned_int)
#define RYUJIN_PARALLEL_REGION_BEGIN
Definition: openmp.h:54
#define RYUJIN_OMP_FOR
Definition: openmp.h:70
#define RYUJIN_PARALLEL_REGION_END
Definition: openmp.h:63
DEAL_II_ALWAYS_INLINE Number negative_part(const Number number)
Definition: simd.h:124
#define LIKWID_MARKER_START(opt)
Definition: introspection.h:68
#define CALLGRIND_START_INSTRUMENTATION
Definition: introspection.h:28
#define LIKWID_MARKER_STOP(opt)
Definition: introspection.h:73
#define CALLGRIND_STOP_INSTRUMENTATION
Definition: introspection.h:35