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