ryujin 2.5.0 revision c64172211cf21344cb9b9b0f3a1266aa6400d392
Loading...
Searching...
No Matches
parabolic_module.template.h
Go to the documentation of this file.
1//
2// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
3// Copyright (C) 2023 - 2026 by the ryujin authors
4//
5
6#pragma once
7
9#include <ryujin/base/loop.h>
10#include <ryujin/base/simd.h>
12
13#include <deal.II/lac/linear_operator.h>
14#include <deal.II/lac/precondition.h>
15#include <deal.II/lac/solver_cg.h>
16#include <deal.II/matrix_free/fe_evaluation.h>
17#include <deal.II/multigrid/mg_coarse.h>
18#include <deal.II/multigrid/mg_matrix.h>
19#include <deal.II/multigrid/mg_transfer.templates.h>
20#include <deal.II/multigrid/mg_transfer_matrix_free.h>
21#include <deal.II/multigrid/multigrid.h>
22
23#include <atomic>
24
25namespace ryujin
26{
27 namespace NavierStokes
28 {
29 using namespace dealii;
30
31 template <int dim, typename Number>
33 const MPIEnsemble &mpi_ensemble,
34 const OfflineData<dim, Number> &offline_data,
35 const HyperbolicSystem &hyperbolic_system,
36 const ParabolicSystem &parabolic_system,
38 const std::string &subsection /*= "ParabolicModule"*/)
39 : Interface::ParabolicModule<dim, Number>(subsection)
40 , mpi_ensemble_(mpi_ensemble)
41 , hyperbolic_system_(&hyperbolic_system)
42 , parabolic_system_(&parabolic_system)
43 , offline_data_(&offline_data)
44 , initial_values_(&initial_values)
45 , cycle_(0)
46 , n_iterations_velocity_(0.)
47 , n_iterations_internal_energy_(0.)
48 {
49 use_gmg_velocity_ = false;
50 this->add_parameter("multigrid velocity",
51 use_gmg_velocity_,
52 "Use geometric multigrid for velocity component");
53
54 gmg_max_iter_vel_ = 12;
55 this->add_parameter("multigrid velocity - max iter",
56 gmg_max_iter_vel_,
57 "Maximal number of CG iterations with GMG smoother");
58
59 gmg_smoother_range_vel_ = 8.;
60 this->add_parameter("multigrid velocity - chebyshev range",
61 gmg_smoother_range_vel_,
62 "Chebyshev smoother: eigenvalue range parameter");
63
64 gmg_smoother_max_eig_vel_ = 2.0;
65 this->add_parameter("multigrid velocity - chebyshev max eig",
66 gmg_smoother_max_eig_vel_,
67 "Chebyshev smoother: maximal eigenvalue");
68
69 use_gmg_internal_energy_ = false;
70 this->add_parameter(
71 "multigrid energy",
72 use_gmg_internal_energy_,
73 "Use geometric multigrid for internal energy component");
74
75 gmg_max_iter_en_ = 15;
76 this->add_parameter("multigrid energy - max iter",
77 gmg_max_iter_en_,
78 "Maximal number of CG iterations with GMG smoother");
79
80 gmg_smoother_range_en_ = 15.;
81 this->add_parameter("multigrid energy - chebyshev range",
82 gmg_smoother_range_en_,
83 "Chebyshev smoother: eigenvalue range parameter");
84
85 gmg_smoother_max_eig_en_ = 2.0;
86 this->add_parameter("multigrid energy - chebyshev max eig",
87 gmg_smoother_max_eig_en_,
88 "Chebyshev smoother: maximal eigenvalue");
89
90 gmg_smoother_degree_ = 3;
91 this->add_parameter("multigrid - chebyshev degree",
92 gmg_smoother_degree_,
93 "Chebyshev smoother: degree");
94
95 gmg_smoother_n_cg_iter_ = 10;
96 this->add_parameter(
97 "multigrid - chebyshev cg iter",
98 gmg_smoother_n_cg_iter_,
99 "Chebyshev smoother: number of CG iterations to approximate "
100 "eigenvalue");
101
102 gmg_min_level_ = 0;
103 this->add_parameter(
104 "multigrid - min level",
105 gmg_min_level_,
106 "Minimal mesh level to be visited in the geometric multigrid "
107 "cycle where the coarse grid solver (Chebyshev) is called");
108
109 tolerance_ = Number(1.0e-12);
110 this->add_parameter(
111 "tolerance", tolerance_, "Tolerance for linear solvers");
112
113 tolerance_linfty_norm_ = false;
114 this->add_parameter(
115 "tolerance linfty norm",
116 tolerance_linfty_norm_,
117 "Use the l_infty norm instead of the l_2 norm for the "
118 "stopping criterion");
119 }
120
121
122 template <int dim, typename Number>
124 {
125#ifdef DEBUG_OUTPUT
126 std::cout << "ParabolicModule<dim, Number>::prepare()" << std::endl;
127#endif
128 /*
129 * The cycle_ variabe is only used for gmg reinitialization, simply
130 * reset it to zero on prepare().
131 */
132 cycle_ = 0;
133
134 const auto &discretization = offline_data_->discretization();
135 AssertThrow(discretization.ansatz() == Ansatz::cg_q1,
136 dealii::ExcMessage("The Navier-Stokes module currently only "
137 "supports cG Q1 finite elements."));
138
139 AssertThrow(!offline_data_->dof_handler().has_hp_capabilities(),
140 dealii::ExcMessage(
141 "The Navier-Stokes module currently does not support "
142 "DoFHandlers set up with hp capabilities."));
143
144 /* Initialize vectors: */
145
146 typename MatrixFree<dim, Number>::AdditionalData additional_data;
147 additional_data.tasks_parallel_scheme =
148 MatrixFree<dim, Number>::AdditionalData::none;
149
150 matrix_free_.reinit(discretization.mapping(),
151 offline_data_->dof_handler(),
152 offline_data_->affine_constraints(),
153 discretization.quadrature_1d(),
154 additional_data);
155
156 const auto &scalar_partitioner =
157 matrix_free_.get_dof_info(0).vector_partitioner;
158
159 velocity_.reinit(dim);
160 velocity_rhs_.reinit(dim);
161 for (unsigned int i = 0; i < dim; ++i) {
162 velocity_.block(i).reinit(scalar_partitioner);
163 velocity_rhs_.block(i).reinit(scalar_partitioner);
164 }
165
166 internal_energy_.reinit(scalar_partitioner);
167 internal_energy_rhs_.reinit(scalar_partitioner);
168
169 density_.reinit(scalar_partitioner);
170
171 /* Initialize multigrid: */
172
173 if (!use_gmg_velocity_ && !use_gmg_internal_energy_)
174 return;
175
176 const unsigned int n_levels =
177 offline_data_->dof_handler().get_triangulation().n_global_levels();
178 const unsigned int min_level = std::min(gmg_min_level_, n_levels - 1);
179 MGLevelObject<IndexSet> relevant_sets(0, n_levels - 1);
180 for (unsigned int level = 0; level < n_levels; ++level)
181 relevant_sets[level] =
182 dealii::DoFTools::extract_locally_relevant_level_dofs(
183 offline_data_->dof_handler(), level);
184 mg_constrained_dofs_.initialize(offline_data_->dof_handler(),
185 relevant_sets);
186 std::set<types::boundary_id> boundary_ids;
187 boundary_ids.insert(Boundary::dirichlet);
188 boundary_ids.insert(Boundary::no_slip);
189 mg_constrained_dofs_.make_zero_boundary_constraints(
190 offline_data_->dof_handler(), boundary_ids);
191
192 typename MatrixFree<dim, float>::AdditionalData additional_data_level;
193 additional_data_level.tasks_parallel_scheme =
194 MatrixFree<dim, float>::AdditionalData::none;
195
196 level_matrix_free_.resize(min_level, n_levels - 1);
197 level_density_.resize(min_level, n_levels - 1);
198 for (unsigned int level = min_level; level < n_levels; ++level) {
199 additional_data_level.mg_level = level;
200 AffineConstraints<double> constraints(relevant_sets[level],
201 relevant_sets[level]);
202 // constraints.add_lines(mg_constrained_dofs_.get_boundary_indices(level));
203 // constraints.merge(mg_constrained_dofs_.get_level_constraints(level));
204 constraints.close();
205 level_matrix_free_[level].reinit(discretization.mapping(),
206 offline_data_->dof_handler(),
207 constraints,
208 discretization.quadrature_1d(),
209 additional_data_level);
210 level_matrix_free_[level].initialize_dof_vector(level_density_[level]);
211 }
212
213 mg_transfer_velocity_.build(offline_data_->dof_handler(),
214 mg_constrained_dofs_,
215 level_matrix_free_);
216 mg_transfer_energy_.build(offline_data_->dof_handler(),
217 level_matrix_free_);
218 }
219
220
221 template <int dim, typename Number>
223 StateVector<Number> & /*state_vector*/, Number /*t*/) const
224 {
225 /*
226 * There is no parabolic part of the state vector for Navier-Stokes,
227 * so we do nothing.
228 */
229 }
230
231
232 template <int dim, typename Number>
234 const StateVector<Number> &old_state_vector,
235 StageVectors stage_state_vectors,
236 StageWeights stage_weights,
237 StateVector<Number> &new_state_vector,
238 Number t,
239 Number tau,
240 Number /*tau_max*/) const
241 {
242 AssertThrow(tau != Number(0.),
243 dealii::ExcMessage("A parabolic module cannot choose the "
244 "time step size: tau must be nonzero."));
245
246 switch (stage_state_vectors.size()) {
247 case 0:
248 backward_euler_step<0>(old_state_vector,
249 stage_state_vectors,
250 stage_weights,
251 new_state_vector,
252 t,
253 tau);
254 break;
255 case 1:
256 backward_euler_step<1>(old_state_vector,
257 stage_state_vectors,
258 stage_weights,
259 new_state_vector,
260 t,
261 tau);
262 break;
263 case 2:
264 backward_euler_step<2>(old_state_vector,
265 stage_state_vectors,
266 stage_weights,
267 new_state_vector,
268 t,
269 tau);
270 break;
271 case 3:
272 backward_euler_step<3>(old_state_vector,
273 stage_state_vectors,
274 stage_weights,
275 new_state_vector,
276 t,
277 tau);
278 break;
279 case 4:
280 backward_euler_step<4>(old_state_vector,
281 stage_state_vectors,
282 stage_weights,
283 new_state_vector,
284 t,
285 tau);
286 break;
287 default:
288 AssertThrow(false, dealii::ExcMessage("Unsupported number of stages."));
289 }
290
291 return tau;
292 }
293
294
295 template <int dim, typename Number>
296 template <int stages>
298 const StateVector<Number> &old_state_vector,
299 StageVectors /*stage_state_vectors*/,
300 StageWeights /*stage_weights*/,
301 StateVector<Number> &new_state_vector,
302 Number t,
303 Number tau) const
304 {
305 /* Backward Euler step to half time step, and extrapolate: */
306
307 step(old_state_vector,
308 new_state_vector,
309 t,
310 tau,
311 /*crank_nicolson_extrapolation = */ false);
312 }
313
314
315 template <int dim, typename Number>
317 const StateVector<Number> &old_state_vector,
318 StateVector<Number> &new_state_vector,
319 Number t,
320 Number tau,
321 Number /*tau_max*/) const
322 {
323 AssertThrow(tau != Number(0.),
324 dealii::ExcMessage("A parabolic module cannot choose the "
325 "time step size: tau must be nonzero."));
326
327 try {
328 step(old_state_vector,
329 new_state_vector,
330 t,
331 tau / Number(2.),
332 /*crank_nicolson_extrapolation = */ true);
333
334 } catch (Correction) {
335
336 /*
337 * Under very rare circumstances we might fail to perform a Crank
338 * Nicolson step because the extrapolation step produced
339 * inadmissible states. We could correct the update now by
340 * performing a limiting step (either convex limiting, or flux
341 * corrected transport)... but *meh*, just perform a backward Euler
342 * step:
343 */
344 step(old_state_vector,
345 new_state_vector,
346 t,
347 tau,
348 /*crank_nicolson_extrapolation = */ false);
349 }
350
351 return tau;
352 }
353
354
355 template <int dim, typename Number>
357 std::ostream &output) const
358 {
359 output << " [ " << std::setprecision(2) << std::fixed
360 << n_iterations_velocity_
361 << (use_gmg_velocity_ ? " GMG vel -- " : " CG vel -- ")
362 << n_iterations_internal_energy_
363 << (use_gmg_internal_energy_ ? " GMG int ]" : " CG int ]")
364 << std::endl;
365 }
366
367
368 template <int dim, typename Number>
370 const StateVector<Number> &old_state_vector,
371 StateVector<Number> &new_state_vector,
372 Number t,
373 Number tau,
374 const bool crank_nicolson_extrapolation) const
375 {
376#ifdef DEBUG_OUTPUT
377 std::cout << "ParabolicModule<dim, Number>::step()" << std::endl;
378#endif
379 constexpr ScalarNumber eps = std::numeric_limits<ScalarNumber>::epsilon();
380
381 using HostSpace = dealii::MemorySpace::Host;
382
383 copy_to_memory_space<HostSpace>(old_state_vector, StateVectorPart::state);
384 move_to_memory_space<HostSpace>(new_state_vector, StateVectorPart::state);
385
386 const auto old_U_view =
387 old_state_vector.state.template view<problem_dimension>();
388 const auto new_U_view =
389 new_state_vector.state.template view<problem_dimension>();
390
391 const auto lumped_mass_matrix_view =
392 offline_data_->lumped_mass_matrix().template view<1>();
393 const auto &affine_constraints = offline_data_->affine_constraints();
394
395 /* Index ranges for the iteration over the sparsity pattern : */
396
397 const unsigned int n_owned = offline_data_->n_locally_owned();
398
399 const auto sparsity_simd_view =
400 offline_data_->sparsity_pattern_simd().view();
401
402 DiagonalMatrix<dim, Number> diagonal_matrix;
403
404#ifdef DEBUG_OUTPUT
405 std::cout << " perform time-step with tau = " << tau << std::endl;
406 if (crank_nicolson_extrapolation)
407 std::cout << " and extrapolate to t + 2 * tau" << std::endl;
408#endif
409
410 /*
411 * Update MG matrices all 4 time steps; this is a balance because more
412 * refreshes will render the approximation better, at some additional
413 * cost.
414 */
415 const bool reinitialize_gmg = (cycle_++ % 4 == 0);
416
417 /*
418 * A boolean indicating that a restart is required.
419 *
420 * In our current implementation we set this boolean to true if the
421 * backward Euler step produces an internal energy update that
422 * violates the minimum principle, i.e., the minimum of the new
423 * internal energy is smaller than the minimum of the old internal
424 * energy.
425 *
426 * Depending on the chosen "id_violation_strategy" we either signal a
427 * restart by throwing a "Restart" object, or we simply increase the
428 * number of warnings.
429 */
430 std::atomic<bool> restart_needed = false;
431
432 /*
433 * A boolean indicating that we have to correct the high-order Crank
434 * Nicolson update. Note that this is a truly exceptional case
435 * indicating that the high-order update produced an inadmissible
436 * state, *boo*.
437 *
438 * Our current limiting strategy is to simply fall back to perform a
439 * single backward Euler step...
440 */
441 std::atomic<bool> correction_needed = false;
442
443 /*
444 * Step 1:
445 *
446 * Build right hand side for the velocity update.
447 * Also initialize solution vectors for internal energy and velocity
448 * update.
449 */
450 {
451 ComputingTimer::Scope scope("time step [P] 1 - update velocities");
452
453 const auto body = [&](auto sentinel, unsigned int i) {
454 using T = decltype(sentinel);
455
456 const auto view = hyperbolic_system_->template view<dim, T>();
457
458 const auto U_i = old_U_view.template read_tensor<T>(i);
459 const auto rho_i = view.density(U_i);
460 const auto M_i = view.momentum(U_i);
461 const auto rho_e_i = view.internal_energy(U_i);
462 const auto m_i = lumped_mass_matrix_view.template read_entry<T>(i);
463
464 write_entry<T>(density_, rho_i, i);
465 /* (5.4a) */
466 for (unsigned int d = 0; d < dim; ++d) {
467 write_entry<T>(velocity_.block(d), M_i[d] / rho_i, i);
468 write_entry<T>(velocity_rhs_.block(d), m_i * (M_i[d]), i);
469 }
470 write_entry<T>(internal_energy_, rho_e_i / rho_i, i);
471 };
472
473 cpu_simd_loop<Number>(
474 "time_step_parabolic_1", body, 0, n_owned, n_owned);
475
476 /*
477 * Set up "strongly enforced" boundary conditions that are not stored
478 * in the AffineConstraints map. In this case we enforce boundary
479 * values by imposing them strongly in the iteration by setting the
480 * initial vector and the right hand side to the right value:
481 */
482
483 const auto &boundary_map = offline_data_->boundary_map();
484
485 for (auto entry : boundary_map) {
486 // [i, normal, normal_mass, boundary_mass, id, position] = entry
487 const auto i = std::get<0>(entry);
488 if (i >= n_owned)
489 continue;
490
491 const auto normal = std::get<1>(entry);
492 const auto id = std::get<4>(entry);
493 const auto position = std::get<5>(entry);
494
495 if (id == Boundary::slip) {
496 /* Remove normal component of velocity: */
497 Tensor<1, dim, Number> V_i;
498 Tensor<1, dim, Number> RHS_i;
499 for (unsigned int d = 0; d < dim; ++d) {
500 V_i[d] = velocity_.block(d).local_element(i);
501 RHS_i[d] = velocity_rhs_.block(d).local_element(i);
502 }
503 V_i -= 1. * (V_i * normal) * normal;
504 RHS_i -= 1. * (RHS_i * normal) * normal;
505 for (unsigned int d = 0; d < dim; ++d) {
506 velocity_.block(d).local_element(i) = V_i[d];
507 velocity_rhs_.block(d).local_element(i) = RHS_i[d];
508 }
509
510 } else if (id == Boundary::no_slip) {
511
512 /* Set velocity to zero: */
513 for (unsigned int d = 0; d < dim; ++d) {
514 velocity_.block(d).local_element(i) = Number(0.);
515 velocity_rhs_.block(d).local_element(i) = Number(0.);
516 }
517
518 } else if (id == Boundary::dirichlet) {
519
520 /* Prescribe velocity: */
521 const auto U_i = initial_values_->initial_state(position, t + tau);
522 const auto view = hyperbolic_system_->template view<dim, Number>();
523 const auto rho_i = view.density(U_i);
524 const auto V_i = view.momentum(U_i) / rho_i;
525 const auto e_i = view.internal_energy(U_i) / rho_i;
526
527 for (unsigned int d = 0; d < dim; ++d) {
528 velocity_.block(d).local_element(i) = V_i[d];
529 velocity_rhs_.block(d).local_element(i) = V_i[d];
530 }
531
532 internal_energy_.local_element(i) = e_i;
533 }
534 }
535
536 /*
537 * Zero out constrained degrees of freedom due to hanging nodes and
538 * periodic boundary conditions. These boundary conditions are
539 * enforced by modifying the stencil - consequently we have to
540 * remove constrained dofs from the linear system.
541 */
542
543 affine_constraints.set_zero(density_);
544 affine_constraints.set_zero(internal_energy_);
545 for (unsigned int d = 0; d < dim; ++d) {
546 affine_constraints.set_zero(velocity_.block(d));
547 affine_constraints.set_zero(velocity_rhs_.block(d));
548 }
549
550 /* Prepare preconditioner: */
551
552 diagonal_matrix.reinit(
553 lumped_mass_matrix_view, density_, affine_constraints);
554
555 if (use_gmg_velocity_ && reinitialize_gmg) {
556 MGLevelObject<typename PreconditionChebyshev<
557 VelocityMatrix<dim, float, Number>,
558 LinearAlgebra::distributed::BlockVector<float>,
559 DiagonalMatrix<dim, float>>::AdditionalData>
560 smoother_data(level_matrix_free_.min_level(),
561 level_matrix_free_.max_level());
562
563 level_velocity_matrices_.resize(level_matrix_free_.min_level(),
564 level_matrix_free_.max_level());
565 mg_transfer_velocity_.interpolate_to_mg(
566 offline_data_->dof_handler(), level_density_, density_);
567
568 for (unsigned int level = level_matrix_free_.min_level();
569 level <= level_matrix_free_.max_level();
570 ++level) {
571 level_velocity_matrices_[level].initialize(
572 *parabolic_system_,
573 *offline_data_,
574 level_matrix_free_[level],
575 level_density_[level],
576 tau,
577 level);
578 level_velocity_matrices_[level].compute_diagonal(
579 smoother_data[level].preconditioner);
580 if (level == level_matrix_free_.min_level()) {
581 smoother_data[level].degree = numbers::invalid_unsigned_int;
582 smoother_data[level].eig_cg_n_iterations = 500;
583 smoother_data[level].smoothing_range = 1e-3;
584 } else {
585 smoother_data[level].degree = gmg_smoother_degree_;
586 smoother_data[level].eig_cg_n_iterations =
587 gmg_smoother_n_cg_iter_;
588 smoother_data[level].smoothing_range = gmg_smoother_range_vel_;
589 if (gmg_smoother_n_cg_iter_ == 0)
590 smoother_data[level].max_eigenvalue = gmg_smoother_max_eig_vel_;
591 }
592 }
593 mg_smoother_velocity_.initialize(level_velocity_matrices_,
594 smoother_data);
595 }
596 }
597
598 Number e_min_old;
599
600 {
601 ComputingTimer::Scope scope(
602 "time step [X] _ - synchronization barriers");
603
604 /* Compute the global minimum of the internal energy: */
605
606 // .begin() and .end() denote the locally owned index range:
607 e_min_old =
608 *std::min_element(internal_energy_.begin(), internal_energy_.end());
609
610 e_min_old = Utilities::MPI::min(e_min_old,
611 mpi_ensemble_.ensemble_communicator());
612
613 // FIXME: create a meaningful relaxation based on global mesh size min.
614 constexpr Number eps = std::numeric_limits<Number>::epsilon();
615 e_min_old *= (1. - 1000. * eps);
616 }
617
618 /*
619 * Step 1: Solve velocity update:
620 */
621 {
622 ComputingTimer::Scope scope("time step [P] 1 - update velocities");
623
624 VelocityMatrix<dim, Number, Number> velocity_operator;
625 velocity_operator.initialize(
626 *parabolic_system_, *offline_data_, matrix_free_, density_, tau);
627
628 const auto tolerance_velocity =
629 (tolerance_linfty_norm_ ? velocity_rhs_.linfty_norm()
630 : velocity_rhs_.l2_norm()) *
631 tolerance_;
632
633 /*
634 * Multigrid might lack robustness for some cases, so in case it takes
635 * too many iterations we better switch to the more robust plain
636 * conjugate gradient method.
637 */
638 try {
639 if (!use_gmg_velocity_)
640 throw SolverControl::NoConvergence(0, 0.);
641
642 using bvt_float = LinearAlgebra::distributed::BlockVector<float>;
643
644 MGCoarseGridApplySmoother<bvt_float> mg_coarse;
645 mg_coarse.initialize(mg_smoother_velocity_);
646
647 mg::Matrix<bvt_float> mg_matrix(level_velocity_matrices_);
648
649 Multigrid<bvt_float> mg(mg_matrix,
650 mg_coarse,
651 mg_transfer_velocity_,
652 mg_smoother_velocity_,
653 mg_smoother_velocity_,
654 level_velocity_matrices_.min_level(),
655 level_velocity_matrices_.max_level());
656
657 const auto &dof_handler = offline_data_->dof_handler();
658 PreconditionMG<dim, bvt_float, MGTransferVelocity<dim, float>>
659 preconditioner(dof_handler, mg, mg_transfer_velocity_);
660
661 SolverControl solver_control(gmg_max_iter_vel_, tolerance_velocity);
662 SolverCG<BlockHostVector> solver(solver_control);
663 solver.solve(
664 velocity_operator, velocity_, velocity_rhs_, preconditioner);
665
666 /* update exponential moving average */
667 n_iterations_velocity_ =
668 0.9 * n_iterations_velocity_ + 0.1 * solver_control.last_step();
669
670 } catch (SolverControl::NoConvergence &) {
671
672 SolverControl solver_control(1000, tolerance_velocity);
673 SolverCG<BlockHostVector> solver(solver_control);
674 solver.solve(
675 velocity_operator, velocity_, velocity_rhs_, diagonal_matrix);
676
677 /* update exponential moving average, counting also GMG iterations */
678 n_iterations_velocity_ *= 0.9;
679 n_iterations_velocity_ +=
680 0.1 * (use_gmg_velocity_ ? gmg_max_iter_vel_ : 0) +
681 0.1 * solver_control.last_step();
682 }
683 }
684
685 /*
686 * Step 2: Build internal energy right hand side:
687 */
688 {
689 ComputingTimer::Scope scope("time step [P] 2 - update internal energy");
690
691 /* Compute m_i K_i^{n+1/2}: (5.5) */
692 matrix_free_
693 .template cell_loop<ScalarHostVector<Number>, BlockHostVector>(
694 [this](const auto &data,
695 auto &dst,
696 const auto &src,
697 const auto cell_range) {
698 FEEvaluation<dim, order_fe, order_quad, dim, Number> velocity(
699 data);
700 FEEvaluation<dim, order_fe, order_quad, 1, Number> energy(
701 data);
702
703 const auto mu = parabolic_system_->mu();
704 const auto lambda = parabolic_system_->lambda();
705
706 for (unsigned int cell = cell_range.first;
707 cell < cell_range.second;
708 ++cell) {
709 velocity.reinit(cell);
710 energy.reinit(cell);
711 velocity.gather_evaluate(src, EvaluationFlags::gradients);
712
713 for (unsigned int q = 0; q < velocity.n_q_points; ++q) {
714 if constexpr (dim == 1) {
715 /* Workaround: no symmetric gradient for dim == 1: */
716 const auto gradient = velocity.get_gradient(q);
717 auto S = (4. / 3. * mu + lambda) * gradient;
718 energy.submit_value(gradient * S, q);
719
720 } else {
721
722 const auto symmetric_gradient =
723 velocity.get_symmetric_gradient(q);
724 const auto divergence = trace(symmetric_gradient);
725 auto S = 2. * mu * symmetric_gradient;
726 for (unsigned int d = 0; d < dim; ++d)
727 S[d][d] += (lambda - 2. / 3. * mu) * divergence;
728 energy.submit_value(symmetric_gradient * S, q);
729 }
730 }
731 energy.integrate_scatter(EvaluationFlags::values, dst);
732 }
733 },
734 internal_energy_rhs_,
735 velocity_,
736 /* zero destination */ true);
737
738 const auto lumped_mass_matrix_view =
739 offline_data_->lumped_mass_matrix().template view<1>();
740
741 const auto body = [&](auto sentinel, unsigned int i) {
742 using T = decltype(sentinel);
743
744 const auto view = hyperbolic_system_->template view<dim, T>();
745
746 const auto rhs_i = read_entry<T>(internal_energy_rhs_, i);
747 const auto m_i = lumped_mass_matrix_view.template read_entry<T>(i);
748 const auto rho_i = read_entry<T>(density_, i);
749 const auto e_i = read_entry<T>(internal_energy_, i);
750
751 const auto U_i = old_U_view.template read_tensor<T>(i);
752 const auto V_i = view.momentum(U_i) / rho_i;
753
754 dealii::Tensor<1, dim, T> V_i_new;
755 for (unsigned int d = 0; d < dim; ++d) {
756 V_i_new[d] = read_entry<T>(velocity_.block(d), i);
757 }
758
759 /*
760 * For backward Euler we have to add this algebraic correction
761 * to ensure conservation of total energy.
762 */
763 const auto correction =
764 crank_nicolson_extrapolation
765 ? T(0.)
766 : Number(0.5) * (V_i - V_i_new).norm_square();
767
768 /* rhs_i contains already m_i K_i^{n+1/2} */
769 const auto result = m_i * rho_i * (e_i + correction) + tau * rhs_i;
770 write_entry<T>(internal_energy_rhs_, result, i);
771 };
772
773 cpu_simd_loop<Number>(
774 "time_step_parabolic_2", body, 0, n_owned, n_owned);
775
776 /*
777 * Set up "strongly enforced" boundary conditions that are not stored
778 * in the AffineConstraints map: We enforce Neumann conditions (i.e.,
779 * insulating boundary conditions) everywhere except for Dirichlet
780 * boundaries where we have to enforce prescribed conditions:
781 */
782
783 const auto &boundary_map = offline_data_->boundary_map();
784
785 for (auto entry : boundary_map) {
786 // [i, normal, normal_mass, boundary_mass, id, position] = entry
787 const auto i = std::get<0>(entry);
788 if (i >= n_owned)
789 continue;
790
791 const auto id = std::get<4>(entry);
792 const auto position = std::get<5>(entry);
793
794 if (id == Boundary::dirichlet) {
795 /* Prescribe internal energy: */
796 const auto U_i = initial_values_->initial_state(position, t + tau);
797 const auto view = hyperbolic_system_->template view<dim, Number>();
798 const auto rho_i = view.density(U_i);
799 const auto e_i = view.internal_energy(U_i) / rho_i;
800 internal_energy_rhs_.local_element(i) = e_i;
801 }
802 }
803
804 /*
805 * Zero out constrained degrees of freedom due to hanging nodes and
806 * periodic boundary conditions. These boundary conditions are
807 * enforced by modifying the stencil - consequently we have to
808 * remove constrained dofs from the linear system.
809 */
810 affine_constraints.set_zero(internal_energy_);
811 affine_constraints.set_zero(internal_energy_rhs_);
812
813 /*
814 * Update MG matrices all 4 time steps; this is a balance because more
815 * refreshes will render the approximation better, at some additional
816 * cost.
817 */
818 if (use_gmg_internal_energy_ && reinitialize_gmg) {
819 MGLevelObject<typename PreconditionChebyshev<
820 EnergyMatrix<dim, float, Number>,
821 LinearAlgebra::distributed::Vector<float>>::AdditionalData>
822 smoother_data(level_matrix_free_.min_level(),
823 level_matrix_free_.max_level());
824
825 level_energy_matrices_.resize(level_matrix_free_.min_level(),
826 level_matrix_free_.max_level());
827
828 for (unsigned int level = level_matrix_free_.min_level();
829 level <= level_matrix_free_.max_level();
830 ++level) {
831 level_energy_matrices_[level].initialize(
832 *offline_data_,
833 level_matrix_free_[level],
834 level_density_[level],
835 tau * parabolic_system_->cv_inverse_kappa(),
836 level);
837 level_energy_matrices_[level].compute_diagonal(
838 smoother_data[level].preconditioner);
839 if (level == level_matrix_free_.min_level()) {
840 smoother_data[level].degree = numbers::invalid_unsigned_int;
841 smoother_data[level].eig_cg_n_iterations = 500;
842 smoother_data[level].smoothing_range = 1e-3;
843 } else {
844 smoother_data[level].degree = gmg_smoother_degree_;
845 smoother_data[level].eig_cg_n_iterations =
846 gmg_smoother_n_cg_iter_;
847 smoother_data[level].smoothing_range = gmg_smoother_range_en_;
848 if (gmg_smoother_n_cg_iter_ == 0)
849 smoother_data[level].max_eigenvalue = gmg_smoother_max_eig_en_;
850 }
851 }
852 mg_smoother_energy_.initialize(level_energy_matrices_, smoother_data);
853 }
854 }
855
856 /*
857 * Step 2: Solve internal energy update:
858 */
859 {
860 ComputingTimer::Scope scope("time step [P] 2 - update internal energy");
861
862 EnergyMatrix<dim, Number, Number> energy_operator;
863 const auto &kappa = parabolic_system_->cv_inverse_kappa();
864 energy_operator.initialize(
865 *offline_data_, matrix_free_, density_, tau * kappa);
866
867 const auto tolerance_internal_energy =
868 (tolerance_linfty_norm_ ? internal_energy_rhs_.linfty_norm()
869 : internal_energy_rhs_.l2_norm()) *
870 tolerance_;
871
872 try {
873 if (!use_gmg_internal_energy_)
874 throw SolverControl::NoConvergence(0, 0.);
875
876 using vt_float = LinearAlgebra::distributed::Vector<float>;
877 MGCoarseGridApplySmoother<vt_float> mg_coarse;
878 mg_coarse.initialize(mg_smoother_energy_);
879 mg::Matrix<vt_float> mg_matrix(level_energy_matrices_);
880
881 Multigrid<vt_float> mg(mg_matrix,
882 mg_coarse,
883 mg_transfer_energy_,
884 mg_smoother_energy_,
885 mg_smoother_energy_,
886 level_energy_matrices_.min_level(),
887 level_energy_matrices_.max_level());
888
889 const auto &dof_handler = offline_data_->dof_handler();
890 PreconditionMG<dim, vt_float, MGTransferEnergy<dim, float>>
891 preconditioner(dof_handler, mg, mg_transfer_energy_);
892
893 SolverControl solver_control(gmg_max_iter_en_,
894 tolerance_internal_energy);
895 SolverCG<ScalarHostVector<Number>> solver(solver_control);
896 solver.solve(energy_operator,
897 internal_energy_,
898 internal_energy_rhs_,
899 preconditioner);
900
901 /* update exponential moving average */
902 n_iterations_internal_energy_ = 0.9 * n_iterations_internal_energy_ +
903 0.1 * solver_control.last_step();
904
905 } catch (SolverControl::NoConvergence &) {
906
907 SolverControl solver_control(1000, tolerance_internal_energy);
908 SolverCG<ScalarHostVector<Number>> solver(solver_control);
909 solver.solve(energy_operator,
910 internal_energy_,
911 internal_energy_rhs_,
912 diagonal_matrix);
913
914 /* update exponential moving average, counting also GMG iterations */
915 n_iterations_internal_energy_ *= 0.9;
916 n_iterations_internal_energy_ +=
917 0.1 * (use_gmg_internal_energy_ ? gmg_max_iter_en_ : 0) +
918 0.1 * solver_control.last_step();
919 }
920 }
921
922 /*
923 * Step 3: Copy vectors and check for local minimum principle on
924 * internal energy:
925 *
926 * FIXME: Memory access is suboptimal...
927 */
928 {
929 ComputingTimer::Scope scope("time step [P] 3 - write back vectors");
930
931 const auto body = [&](auto sentinel, unsigned int i) {
932 using T = decltype(sentinel);
933
934 const auto view = hyperbolic_system_->template view<dim, T>();
935
936 /* Skip constrained degrees of freedom: */
937 const unsigned int row_length = sparsity_simd_view.row_length(i);
938 if (row_length == 1)
939 return;
940
941 auto U_i = old_U_view.template read_tensor<T>(i);
942 const auto rho_i = view.density(U_i);
943
944 Tensor<1, dim, T> m_i_new;
945 for (unsigned int d = 0; d < dim; ++d) {
946 m_i_new[d] = rho_i * read_entry<T>(velocity_.block(d), i);
947 }
948
949 auto rho_e_i_new = rho_i * read_entry<T>(internal_energy_, i);
950
951 /*
952 * Check that the backward Euler step itself (which is our "low
953 * order" update) satisfies bounds. If not, signal a restart.
954 */
955
956 if (!(T(0.) == std::max(T(0.), rho_i * e_min_old - rho_e_i_new))) {
957#ifdef DEBUG_OUTPUT
958 std::cout << std::fixed << std::setprecision(16);
959 const auto e_i_new = rho_e_i_new / rho_i;
960 std::cout << "Bounds violation: internal energy (critical)!\n"
961 << "\t\te_min_old: " << e_min_old << "\n"
962 << "\t\te_min_old (delta): "
963 << negative_part(e_i_new - e_min_old) << "\n"
964 << "\t\te_min_new: " << e_i_new << "\n"
965 << std::endl;
966#endif
967 restart_needed = true;
968 }
969
970 if (crank_nicolson_extrapolation) {
971 m_i_new = Number(2.0) * m_i_new - view.momentum(U_i);
972 rho_e_i_new = Number(2.0) * rho_e_i_new - view.internal_energy(U_i);
973
974 /*
975 * If we do perform an extrapolation step for Crank Nicolson
976 * we have to check whether we maintain admissibility
977 */
978
979 if (!(T(0.) ==
980 std::max(T(0.), eps * rho_i * e_min_old - rho_e_i_new))) {
981#ifdef DEBUG_OUTPUT
982 std::cout << std::fixed << std::setprecision(16);
983 const auto e_i_new = rho_e_i_new / rho_i;
984
985 std::cout << "Bounds violation: high-order internal energy!"
986 << "\t\te_min_new: " << e_i_new << "\n"
987 << "\t\t-- correction required --" << std::endl;
988#endif
989 correction_needed = true;
990 }
991 }
992
993 const auto E_i_new = rho_e_i_new + 0.5 * m_i_new * m_i_new / rho_i;
994
995 for (unsigned int d = 0; d < dim; ++d)
996 U_i[1 + d] = m_i_new[d];
997 U_i[1 + dim] = E_i_new;
998
999 new_U_view.template write_tensor<T>(U_i, i);
1000 };
1001
1002 cpu_simd_loop<Number>(
1003 "time_step_parabolic_3", body, 0, n_owned, n_owned);
1004
1005 new_U_view.update_ghost_values();
1006 }
1007
1008 {
1009 ComputingTimer::Scope scope(
1010 "time step [X] _ - synchronization barriers");
1011
1012 /*
1013 * Synchronize whether we have to restart or correct the time step.
1014 * Even though the restart/correction condition itself only affects
1015 * the local ensemble we nevertheless need to synchronize the
1016 * boolean in case we perform synchronized global time steps.
1017 * (Otherwise different ensembles might end up with a different
1018 * time step.)
1019 */
1020
1021 restart_needed.store(Utilities::MPI::logical_or(
1022 restart_needed.load(),
1023 mpi_ensemble_.synchronization_communicator()));
1024
1025 correction_needed.store(Utilities::MPI::logical_or(
1026 correction_needed.load(),
1027 mpi_ensemble_.synchronization_communicator()));
1028 }
1029
1030 if (correction_needed) {
1031 /* If we can do a restart try that first: */
1032 if (this->id_violation_strategy_ ==
1034 this->n_restarts_++;
1035 /* Half step size is a good heuristic: */
1036 throw Restart{Number(0.5) * tau};
1037 } else {
1038 this->n_corrections_++;
1039 throw Correction();
1040 }
1041 }
1042
1043 if (restart_needed) {
1044 switch (this->id_violation_strategy_) {
1046 this->n_warnings_++;
1047 break;
1049 this->n_restarts_++;
1050 /* Half step size is a good heuristic: */
1051 throw Restart{Number(0.5) * tau};
1052 }
1053 }
1054 }
1055
1056 } // namespace NavierStokes
1057} // namespace ryujin
void reinit(const Vector &lumped_mass_matrix, const vector_type &density, const dealii::AffineConstraints< Number > &affine_constraints)
typename Interface::ParabolicModule< dim, Number >::StageWeights StageWeights
Number crank_nicolson_step(const StateVector< Number > &old_state_vector, StateVector< Number > &new_state_vector, Number t, Number tau, Number tau_max=std::numeric_limits< Number >::max()) const override
Number backward_euler_step(const StateVector< Number > &old_state_vector, StageVectors stage_state_vectors, StageWeights stage_weights, StateVector< Number > &new_state_vector, Number t, Number tau, Number tau_max=std::numeric_limits< Number >::max()) const override
typename Interface::ParabolicModule< dim, Number >::StageVectors StageVectors
ParabolicModule(const MPIEnsemble &mpi_ensemble, const OfflineData< dim, Number > &offline_data, const HyperbolicSystem &hyperbolic_system, const ParabolicSystem &parabolic_system, const InitialValues< HyperbolicDescription, dim, Number > &initial_values, const std::string &subsection="ParabolicModule")
void prepare_state_vector(StateVector< Number > &state_vector, Number t) const override
void print_solver_statistics(std::ostream &output) const override
DEAL_II_HOST_DEVICE_ALWAYS_INLINE Number negative_part(const Number number)
Definition simd.h:146
MultiComponentVector< Number > state