ryujin 2.1.1 revision b04fb4aba7bd97ddfa5994a6b4b6e8586eb15db2
Loading...
Searching...
No Matches
hyperbolic_module.template.h
Go to the documentation of this file.
1//
2// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
3// Copyright (C) 2020 - 2026 by the ryujin authors
4//
5
6#pragma once
7
8#include "computing_timer.h"
9#include "gpu.h"
10#include "hyperbolic_module.h"
11#include "loop.h"
12#include "mpi_ensemble.h"
13#include "simd.h"
14
15#include <numeric>
16#include <utility>
17
18namespace ryujin
19{
20 namespace ShallowWater
21 {
23 }
24
25 using namespace dealii;
26
27 namespace
28 {
35 template <typename T>
36 DEAL_II_HOST_DEVICE_ALWAYS_INLINE void atomic_store(T *ptr, const T value)
37 {
38#if KOKKOS_VERSION >= 40000 || defined(KOKKOS_ENABLE_IMPL_DESUL_ATOMICS)
39 Kokkos::atomic_store(ptr, value);
40#else
41 Kokkos::atomic_exchange(ptr, value);
42#endif
43 }
44 } // namespace
45
46 template <typename HyperbolicDescription, int dim, typename Number>
48 const MPIEnsemble &mpi_ensemble,
49 const OfflineData<dim, Number> &offline_data,
50 const HyperbolicSystem &hyperbolic_system,
52 const std::string &subsection /*= "HyperbolicModule"*/)
53 : ParameterAcceptor(subsection)
54 , indicator_(hyperbolic_system, subsection + "/indicator")
55 , limiter_(hyperbolic_system, subsection + "/limiter")
56 , wave_speed_estimator_(hyperbolic_system,
57 subsection + "/wave speed estimator")
58 , mpi_ensemble_(mpi_ensemble)
59 , offline_data_(&offline_data)
60 , hyperbolic_system_(&hyperbolic_system)
61 , initial_values_(&initial_values)
62 , cfl_(0.2)
63 , acceptable_tau_max_ratio_(1.e6)
64 , id_violation_strategy_(IDViolationStrategy::warn)
65 , n_restarts_(0)
66 , n_corrections_(0)
67 , n_warnings_(0)
68 {
69 }
70
71
72 template <typename HyperbolicDescription, int dim, typename Number>
74 {
75#ifdef DEBUG_OUTPUT
76 std::cout
77 << "HyperbolicModule<HyperbolicDescription, dim, Number>::prepare()"
78 << std::endl;
79#endif
80
81 const auto limiter_view = limiter_.template view<dim, Number>();
82 AssertThrow(limiter_view.iterations() <= 2,
83 dealii::ExcMessage(
84 "The number of limiter iterations must be between [0,2]"));
85
86 /* Initialize vectors: */
87
88 const auto &scalar_partitioner = offline_data_->scalar_partitioner();
89
90 /* The alpha vector is also read on the host memory space: */
91 alpha_.reinit_with_scalar_partitioner(
92 scalar_partitioner, 1, TransferPolicy::implicit_transfers);
93
94 bounds_.reinit_with_scalar_partitioner(scalar_partitioner, n_bounds);
95 r_.reinit_with_vector_partitioner(
96 offline_data_->hyperbolic_vector_partitioner(), problem_dimension);
97
98 /* Initialize the compact buffer used for updating boundary values: */
99
100 boundary_states_.reinit(offline_data_->boundary_indices().size() *
101 problem_dimension,
103
104 /* Initialize matrices: */
105
106 const auto &sparsity_simd = offline_data_->sparsity_pattern_simd();
107 dij_matrix_.reinit(sparsity_simd);
108 lij_matrix_.reinit(sparsity_simd);
109 lij_matrix_next_.reinit(sparsity_simd);
110 pij_matrix_.reinit(sparsity_simd);
111
112 /* Set up initial precomputed vector: */
113
114 initial_precomputed_ =
115 initial_values_->interpolate_initial_precomputed_vector();
116
117 /*
118 * Move all temporary data structures to the correct memory space:
119 */
120
121 if constexpr (have_separate_memory_spaces) {
122 using MemorySpace = selected_memory_space_t;
123
124 bounds_.template move_to_memory_space<MemorySpace>();
125 r_.template move_to_memory_space<MemorySpace>();
126
127 dij_matrix_.template move_to_memory_space<MemorySpace>();
128 lij_matrix_.template move_to_memory_space<MemorySpace>();
129 lij_matrix_next_.template move_to_memory_space<MemorySpace>();
130 pij_matrix_.template move_to_memory_space<MemorySpace>();
131
132 /* The initial_precomputed vector is also read on the host memory space:*/
133 initial_precomputed_.template copy_to_memory_space<MemorySpace>();
134 }
135 }
136
137
138 /*
139 * -------------------------------------------------------------------------
140 * Step 0: Reinitialize vector
141 * -------------------------------------------------------------------------
142 */
143
144
153 template <typename HyperbolicDescription, int dim, typename Number>
154 void
156 StateVector<Number> &state_vector) const
157 {
158#ifdef DEBUG_OUTPUT
159 std::cout << "HyperbolicModule<dim, Number>::reinit_state_vector()"
160 << std::endl;
161#endif
162
163 auto &[U, precomputed, V] = state_vector;
164 U.reinit_with_vector_partitioner(
165 offline_data_->hyperbolic_vector_partitioner(), problem_dimension);
166 precomputed.reinit_with_vector_partitioner(
167 offline_data_->precomputed_vector_partitioner(), n_precomputed_values);
168
169#ifdef DEBUG
170 /* Poison all vectors: */
171 constexpr auto nan = std::numeric_limits<Number>::signaling_NaN();
172 for (auto *vector : {&U, &precomputed}) {
173 auto &data = vector->deal_ii_vector();
174 std::fill_n(data.begin(), data.locally_owned_size(), nan);
175 }
176#endif
177 }
178
179
180 /*
181 * -------------------------------------------------------------------------
182 * Step 1: Apply boundary conditions and precompute values
183 * -------------------------------------------------------------------------
184 */
185
186
187 template <typename HyperbolicDescription, int dim, typename Number>
188 void
190 StateVector<Number> &state_vector, Number t) const
191 {
192#ifdef DEBUG_OUTPUT
193 std::cout << "HyperbolicModule<HyperbolicDescription, dim, "
194 "Number>::prepare_state_vector()"
195 << std::endl;
196#endif
197
198 using MemorySpace = selected_memory_space_t;
199
200 move_to_memory_space<MemorySpace>(state_vector,
203
204 auto &[U, precomputed, parabolic] = state_vector;
205
207 "time step [H] 1 - update boundary values, precompute values");
208
209 /*
210 * Update boundary values and distribute the result over all MPI ranks.
211 */
212
213 apply_boundary_conditions<MemorySpace>(U, t);
214
215 U.template deal_ii_vector<MemorySpace>().update_ghost_values();
216
217 /*
218 * Compute and populate precomputed values.
219 */
220
221 if constexpr (have_separate_memory_spaces)
222 hyperbolic_system_->template fill_precomputed_values<MemorySpace>(
223 *offline_data_, state_vector);
224 else
225 hyperbolic_system_->fill_precomputed_values(*offline_data_, state_vector);
226
227 precomputed.template view<n_precomputed_values, MemorySpace>()
228 .update_ghost_values();
229 }
230
231
232 template <typename HyperbolicDescription, int dim, typename Number>
233 template <typename MemorySpace>
236 const Number t) const
237 {
238 constexpr auto n_comp = problem_dimension;
239
240 const auto &boundary_indices = offline_data_->boundary_indices();
241 const auto n_boundary_indices =
242 static_cast<unsigned int>(boundary_indices.size());
243
244 /* Gather all boundary states into a mirrored buffer: */
245
246 {
247 const auto U_view = std::as_const(U).template view<n_comp, MemorySpace>();
248 const auto *indices = boundary_indices.template view<MemorySpace>();
249 auto *states = boundary_states_.template view<MemorySpace>();
250
251 const auto body = [=](auto /*sentinel*/, unsigned int k) {
252 const auto U_i = U_view.read_tensor(indices[k]);
253 for (unsigned int d = 0; d < n_comp; ++d)
254 states[k * n_comp + d] = U_i[d];
255 };
256
257 loop<MemorySpace, Number>("hyperbolic_kernel_01a",
258 body,
259 0,
260 /*no vectorization*/ 0,
261 n_boundary_indices);
262 }
263
264 /* Apply boundary conditions on the host memory space: */
265
266 {
267 auto *states = [&]() {
268 if constexpr (have_separate_memory_spaces) {
270 "time step [X] _ - memory space transfers");
271 return boundary_states_.template view<dealii::MemorySpace::Host>();
272 }
273 return boundary_states_.template view<dealii::MemorySpace::Host>();
274 }();
275
276 const auto &boundary_map = offline_data_->boundary_map();
277 const auto &boundary_slots = offline_data_->boundary_slots();
278 const auto view = hyperbolic_system_->template view<dim, Number>();
279
280 /* FIXME: not thread parallel... */
281 for (std::size_t e = 0; e < boundary_map.size(); ++e) {
282 const auto &[i, normal, normal_mass, boundary_mass, id, position] =
283 boundary_map[e];
284
285 /*
286 * Relay the task of applying appropriate boundary conditions to the
287 * Problem Description.
288 */
289
290 if (id == Boundary::do_nothing)
291 continue;
292
293 /*
294 * Note: The boundary map can contain more than one entry for the
295 * same degree of freedom. All such entries share the same position
296 * in the buffer so that boundary conditions compose in the same way
297 * as they would when operating on the state vector directly.
298 */
299 const auto k = boundary_slots[e];
300
301 state_type U_i;
302 for (unsigned int d = 0; d < n_comp; ++d)
303 U_i[d] = states[k * n_comp + d];
304
305 /* Use a lambda to avoid computing unnecessary state values */
306 auto get_dirichlet = [position = position, t = t, this]() {
307 return initial_values_->initial_state(position, t);
308 };
309
310 U_i = view.apply_boundary_conditions(id, U_i, normal, get_dirichlet);
311
312 for (unsigned int d = 0; d < n_comp; ++d)
313 states[k * n_comp + d] = U_i[d];
314 }
315 }
316
317 /* Write back the updated boundary states: */
318
319 {
320 const auto U_view = U.template view<n_comp, MemorySpace>();
321 const auto *indices = boundary_indices.template view<MemorySpace>();
322 const auto *states = [&]() {
323 if constexpr (have_separate_memory_spaces) {
325 "time step [X] _ - memory space transfers");
326 return std::as_const(boundary_states_).template view<MemorySpace>();
327 }
328 return std::as_const(boundary_states_).template view<MemorySpace>();
329 }();
330
331 const auto body = [=](auto /*sentinel*/, unsigned int k) {
332 state_type U_i;
333 for (unsigned int d = 0; d < n_comp; ++d)
334 U_i[d] = states[k * n_comp + d];
335 U_view.write_tensor(U_i, indices[k]);
336 };
337
338 loop<MemorySpace, Number>("hyperbolic_kernel_01a",
339 body,
340 0,
341 /*no vectorization*/ 0,
342 n_boundary_indices);
343 }
344 }
345
346
347 /*
348 * -------------------------------------------------------------------------
349 * Step 2 - 7: Perform an explicit Euler step
350 * -------------------------------------------------------------------------
351 */
352
353
354 namespace
355 {
360 template <typename T>
361 DEAL_II_HOST_DEVICE_ALWAYS_INLINE bool
362 all_below_diagonal(unsigned int i, const unsigned int *js)
363 {
364 if constexpr (std::is_same_v<T, typename get_value_type<T>::type>) {
365 /* Non-vectorized sequential access. */
366 const auto j = *js;
367 return j < i;
368
369 } else {
370 /* Vectorized fast access. index must be divisible by simd_length */
371
372 constexpr auto simd_length = T::size();
373
374 bool all_below_diagonal = true;
375 for (unsigned int k = 0; k < simd_length; ++k)
376 if (js[k] >= i + k) {
377 all_below_diagonal = false;
378 break;
379 }
380 return all_below_diagonal;
381 }
382 }
383 } // namespace
384
385
386 template <typename HyperbolicDescription, int dim, typename Number>
387 template <int stages>
389 const StateVector<Number> &old_state_vector,
390 std::array<std::reference_wrapper<const StateVector<Number>>, stages>
391 stage_state_vectors,
392 const std::array<Number, stages> stage_weights,
393 StateVector<Number> &new_state_vector,
394 Number tau /*= 0.*/,
395 Number tau_max /*= std::numeric_limits<Number>::max()*/) const
396 {
397#ifdef DEBUG_OUTPUT
398 std::cout << "HyperbolicModule<HyperbolicDescription, dim, Number>::step()"
399 << std::endl;
400#endif
401
402 using MemorySpace = selected_memory_space_t;
403
404 copy_to_memory_space<MemorySpace>(old_state_vector,
407 for (int s = 0; s < stages; ++s)
408 copy_to_memory_space<MemorySpace>(stage_state_vectors[s].get(),
411 move_to_memory_space<MemorySpace>(new_state_vector,
413
414 auto &[old_U, old_precomputed, old_parabolic] = old_state_vector;
415 auto &new_U = new_state_vector.U;
416
417 /*
418 * Taking a view<>() might imply implicit memory space transfers. Let's
419 * account for them in our computing timers.
420 */
422 ComputingTimer::timer("time step [X] _ - memory space transfers").start();
423
424 /*
425 * Workaround: A constexpr boolean storing the fact whether we
426 * instantiate the HyperbolicModule for the shallow water equations.
427 *
428 * Rationale: Currently, the shallow water equations is the only
429 * hyperbolic system for which we have to (a) form equilibrated states
430 * for the low-order update, and (b) apply an affine shift for
431 * computing limiter bounds. It's not so easy to come up with a
432 * meaningful abstraction layer for this (in particular because we only
433 * have one PDE). Thus, for the time being we simply special case a
434 * small amount of code in this routine.
435 *
436 * FIXME: Refactor into a proper abstraction layer / interface.
437 */
438 constexpr bool shallow_water =
439 std::is_same_v<HyperbolicDescription,
441
442 /* Index ranges for the iteration over the sparsity pattern : */
443
444 const unsigned int n_internal = offline_data_->n_locally_internal();
445 const unsigned int n_owned = offline_data_->n_locally_owned();
446
447 /* Sparsity pattern, matrices, boundary information: */
448
449 const auto sparsity_simd_view =
450 offline_data_->sparsity_pattern_simd().template view<MemorySpace>();
451
452 const auto mass_matrix_view =
453 offline_data_->mass_matrix().template view<MemorySpace>();
454 const auto lumped_mass_matrix_view =
455 offline_data_->lumped_mass_matrix().template view<1, MemorySpace>();
456 const auto lumped_mass_matrix_inverse_view =
457 offline_data_->lumped_mass_matrix_inverse()
458 .template view<1, MemorySpace>();
459
460 const auto cij_matrix_view =
461 offline_data_->cij_matrix().template view<MemorySpace>();
462
463 /*
464 * The mass_matrix_inverse and incidence_matrix objects are only
465 * initialized (and accessed) for a discontinuous ansatz:
466 */
467 const bool have_discontinuous_ansatz =
468 offline_data_->discretization().have_discontinuous_ansatz();
469 using MatrixReadView =
470 decltype(offline_data_->mass_matrix().template view<MemorySpace>());
471 const auto mass_matrix_inverse_view =
472 have_discontinuous_ansatz
473 ? offline_data_->mass_matrix_inverse().template view<MemorySpace>()
474 : MatrixReadView{};
475 const auto incidence_matrix_view =
476 have_discontinuous_ansatz
477 ? offline_data_->incidence_matrix().template view<MemorySpace>()
478 : MatrixReadView{};
479
480 const auto *coupling_boundary_pairs =
481 offline_data_->coupling_boundary_pairs().template view<MemorySpace>();
482 const auto n_coupling_boundary_pairs =
483 offline_data_->coupling_boundary_pairs().size();
484
485 const Number measure_of_omega_inverse =
486 Number(1.) / offline_data_->measure_of_omega();
487
488 /* Temporary matrices: */
489
490 const auto dij_matrix_view = dij_matrix_.template view<MemorySpace>();
491 const auto lij_matrix_view = lij_matrix_.template view<MemorySpace>();
492 const auto lij_matrix_next_view =
493 lij_matrix_next_.template view<MemorySpace>();
494 const auto pij_matrix_view = pij_matrix_.template view<MemorySpace>();
495
496 /* Vectors: */
497
498 const auto initial_precomputed_view =
499 initial_precomputed_
500 .template view<n_initial_precomputed_values, MemorySpace>();
501
502 const auto old_U_view =
503 old_U.template view<problem_dimension, MemorySpace>();
504 const auto old_precomputed_view =
505 old_precomputed.template view<n_precomputed_values, MemorySpace>();
506
507 /*
508 * FIXME GPU: std::array::operator[] is not device capable.
509 * Use a Kokkos::Array for wrapping for the time being:
510 */
511
512 using HyperbolicVectorView = std::remove_const_t<decltype(old_U_view)>;
513 using PrecomputedVectorView =
514 std::remove_const_t<decltype(old_precomputed_view)>;
515
516 Kokkos::Array<HyperbolicVectorView, stages> stage_U_view;
517 Kokkos::Array<PrecomputedVectorView, stages> stage_precomputed_view;
518 Kokkos::Array<Number, stages> stage_weight;
519 for (int s = 0; s < stages; ++s) {
520 const auto &[U_s, prec_s, V_s] = stage_state_vectors[s].get();
521 stage_U_view[s] = U_s.template view<problem_dimension, MemorySpace>();
522 stage_precomputed_view[s] =
523 prec_s.template view<n_precomputed_values, MemorySpace>();
524 stage_weight[s] = stage_weights[s];
525 }
526
527 const auto new_U_view =
528 new_U.template view<problem_dimension, MemorySpace>();
529
530 const auto alpha_view = alpha_.template view<1, MemorySpace>();
531 const auto bounds_view = bounds_.template view<n_bounds, MemorySpace>();
532 const auto r_view = r_.template view<problem_dimension, MemorySpace>();
533
535 ComputingTimer::timer("time step [X] _ - memory space transfers").stop();
536
537 /*
538 * Create a local copy of cfl_ so that we do not capture "this" in the
539 * compute kernels
540 */
541 const Number cfl = cfl_;
542
543 const auto hyperbolic_system_views =
544 make_select_view<dim, Number, MemorySpace>(*hyperbolic_system_);
545
546 const auto indicator_views =
547 make_select_view<dim, Number, MemorySpace>(indicator_);
548
549 const auto limiter_views =
550 make_select_view<dim, Number, MemorySpace>(limiter_);
551 const auto n_limiter_iterations =
552 limiter_.template view<dim, Number>().iterations();
553
554 const auto wave_speed_estimator_views =
555 make_select_view<dim, Number, MemorySpace>(wave_speed_estimator_);
556
557 /*
558 * Lambdas for creating the computing timer and loop strings:
559 */
560
561 int step_no = 1;
562
563 const auto scoped_name = [&step_no](const auto &name,
564 const bool advance = true) {
565 advance || step_no--;
566 return "time step [H] " + std::to_string(++step_no) + " - " + name;
567 };
568
569 const auto loop_name = [&step_no]() {
570 return "hyperbolic_kernel_" + std::format("{:02}", step_no);
571 };
572
573 /* A flag signalling that a restart is necessary. */
574 Mirrored<int> restart_needed("hyperbolic_module_restart_needed",
576 *restart_needed.view() = 0;
577
578 /*
579 * -------------------------------------------------------------------------
580 * Step 2: Compute off-diagonal d_ij, and alpha_i
581 *
582 * The computation of the d_ij is quite costly. So we do a trick to
583 * save a bit of computational resources. Instead of computing all d_ij
584 * entries for a row of a given local index i, we only compute d_ij for
585 * which j > i,
586 *
587 * llllrr
588 * l .xxxxx
589 * l ..xxxx
590 * l ...xxx
591 * l ....xx
592 * r ......
593 * r ......
594 *
595 * and symmetrize in Step 2.
596 *
597 * MM: We could save a bit more computational resources by only
598 * computing entries for which *IN A GLOBAL* enumeration j > i. But
599 * the index translation, subsequent symmetrization, and exchange
600 * sounds a bit too expensive...
601 * -------------------------------------------------------------------------
602 */
603 {
604 ComputingTimer::Scope scope(scoped_name("compute d_ij, and alpha_i"));
605
606 const auto body = [=](auto sentinel, unsigned int i) {
607 using T = decltype(sentinel);
608
609 const unsigned int stride_size = sparsity_simd_view.stride_of_row(i);
610
611 const auto wave_speed_estimator_view =
612 wave_speed_estimator_views.template view<T>();
613
614 auto indicator_view = indicator_views.template view<T>();
615
616 /* Skip constrained degrees of freedom: */
617 const unsigned int row_length = sparsity_simd_view.row_length(i);
618 if (row_length == 1)
619 return;
620
621 const auto U_i = old_U_view.template read_tensor<T>(i);
622
623 indicator_view.reset(old_precomputed_view, i, U_i);
624
625 const unsigned int *js = sparsity_simd_view.columns(i);
626 for (unsigned int col_idx = 0; col_idx < row_length;
627 ++col_idx, js += stride_size) {
628
629 const auto U_j = old_U_view.template read_tensor<T>(js);
630
631 const auto c_ij = cij_matrix_view.template read_tensor<T>(i, col_idx);
632
633 indicator_view.accumulate(old_precomputed_view, js, U_j, c_ij);
634
635 /* Skip diagonal. */
636 if (col_idx == 0)
637 continue;
638
639 /* Only iterate over the upper triangular portion of d_ij */
640 if (all_below_diagonal<T>(i, js))
641 continue;
642
643 const auto norm = c_ij.norm();
644 const auto n_ij = c_ij / norm;
645 const auto lambda_max = wave_speed_estimator_view.compute(
646 old_precomputed_view, U_i, U_j, i, js, n_ij);
647 const auto d_ij = norm * lambda_max;
648
649 dij_matrix_view.write_entry(d_ij, i, col_idx, true);
650 }
651
652 const auto mass = lumped_mass_matrix_view.template read_entry<T>(i);
653 const auto hd_i = mass * measure_of_omega_inverse;
654 alpha_view.template write_entry<T>(indicator_view.alpha(hd_i), i);
655 };
656
657 loop<MemorySpace, Number>(loop_name(), body, 0, n_internal, n_owned);
658
659 alpha_view.update_ghost_values();
660 }
661
662 /*
663 * -------------------------------------------------------------------------
664 * Step 3: Compute diagonal of d_ij, and maximal time-step size.
665 * -------------------------------------------------------------------------
666 */
667
668 {
670 scoped_name("compute bdry d_ij, diag d_ii, and tau_max"));
671
672 /*
673 * Complete d_ij at boundary:
674 *
675 * Here, for continuous finite elements the assumption c_ij = -c_ji
676 * no longer holds true. This implies that d_ij != d_ji. We thus need
677 * to compute the lower-triangular part of d_ij, where i and j are
678 * boundary degrees of freedom as well.
679 */
680
681 /*
682 * Note: we need this dance of iterating over an integer and then
683 * accessing the element to make Apple's OpenMP implementation
684 * happy.
685 */
686 const auto body_boundary = [=](auto, const unsigned int k) {
687 const auto &[i, col_idx, j] = coupling_boundary_pairs[k];
688
689 const auto wave_speed_estimator_view =
690 wave_speed_estimator_views.template view<Number>();
691
692 /*
693 * Only work on index pairs "i < j" that point to the upper
694 * triangular portion of the d_ij matrix. For all of these index
695 * pairs we compute the corresponding d_ji entry and fix up the
696 * d_ij entry (from step 2) by taking the maximum. Note that we
697 * actually do not store anything in the d_ji entry itself because
698 * we symmetrize the matrix later on anyway.
699 */
700 if (j < i)
701 return;
702
703 const auto U_i = old_U_view.read_tensor(i);
704 const auto U_j = old_U_view.read_tensor(j);
705
706 const auto c_ji = cij_matrix_view.read_transposed_tensor(i, col_idx);
707 Assert(c_ji.norm() > 1.e-12, ExcInternalError());
708 const auto norm_ji = c_ji.norm();
709 const auto n_ji = c_ji / norm_ji;
710
711 const auto d_ij = dij_matrix_view.read_entry(i, col_idx);
712
713 const auto lambda_max = wave_speed_estimator_view.compute(
714 old_precomputed_view, U_j, U_i, j, &i, n_ji);
715 const auto d_ji = norm_ji * lambda_max;
716
717 dij_matrix_view.write_entry(std::max(d_ij, d_ji), i, col_idx);
718 };
719
720 loop<MemorySpace, Number>(loop_name() + 'a',
721 body_boundary,
722 0,
723 /*no vectorization*/ 0,
724 n_coupling_boundary_pairs);
725
726 /* Symmetrize d_ij and compute the maximal time-step size: */
727 const auto body = [=](auto, unsigned int i) -> Number {
728
729#ifdef DEBUG_SYMMETRY_CHECK
730 const auto wave_speed_estimator_view =
731 wave_speed_estimator_views.template view<Number>();
732#endif
733
734 /* Skip constrained degrees of freedom: */
735 const unsigned int row_length = sparsity_simd_view.row_length(i);
736 if (row_length == 1)
737 return std::numeric_limits<Number>::max();
738
739 Number d_sum = Number(0.);
740
741 /* skip diagonal: */
742 const unsigned int stride_size = sparsity_simd_view.stride_of_row(i);
743 const unsigned int *js = sparsity_simd_view.columns(i);
744 for (unsigned int col_idx = 1; col_idx < row_length; ++col_idx) {
745 const auto j = *(js + col_idx * stride_size);
746
747 // fill lower triangular part of dij_matrix missing from step 1
748 if (j < i) {
749 const auto d_ji = dij_matrix_view.read_transposed_entry(i, col_idx);
750
751#ifdef DEBUG_SYMMETRY_CHECK
752 /* Verify that d_ji == std::max(d_ij, d_ji): */
753
754 const auto U_i = old_U_view.read_tensor(i);
755 const auto U_j = old_U_view.read_tensor(j);
756
757 const auto c_ij = cij_matrix_view.read_tensor(i, col_idx);
758 Assert(c_ij.norm() > 1.e-12, ExcInternalError());
759 const auto norm_ij = c_ij.norm();
760 const auto n_ij = c_ij / norm_ij;
761
762 const auto lambda_max = wave_speed_estimator_view.compute(
763 old_precomputed_view, U_i, U_j, i, &j, n_ij);
764 const auto d_ij = norm_ij * lambda_max;
765
766 Assert(d_ij <= d_ji + 1.0e-12,
767 dealii::ExcMessage("d_ij not symmetrized correctly on "
768 "boundary degrees of freedom."));
769#endif
770
771 dij_matrix_view.write_entry(d_ji, i, col_idx);
772 }
773
774 d_sum -= dij_matrix_view.read_entry(i, col_idx);
775 }
776
777 /*
778 * Make sure that we do not accidentally divide by zero. (Yes, this
779 * can happen for some (admittedly, rather esoteric) scalar
780 * conservation equations...).
781 */
782 d_sum =
783 std::min(d_sum, Number(-1.e6) * std::numeric_limits<Number>::min());
784
785 /* write diagonal element */
786 dij_matrix_view.write_entry(d_sum, i, 0);
787
788 const Number mass = lumped_mass_matrix_view.read_entry(i);
789 return cfl * mass / (Number(-2.) * d_sum);
790 };
791
792 reduction_loop<MemorySpace>(
793 loop_name() + 'b', body, Kokkos::Min<Number>(tau_max), 0, n_owned);
794 }
795
796 {
797 ComputingTimer::Scope scope("time step [X] _ - synchronization barriers");
798
799 /*
800 * MPI Barrier: Synchronize the maximal time-step size. This has to
801 * happen either over the global, or the local subrange communicator:
802 */
803 tau_max = Utilities::MPI::min(
804 tau_max, mpi_ensemble_.synchronization_communicator());
805
806 AssertThrow(
807 !std::isnan(tau_max) && !std::isinf(tau_max) && tau_max > 0.,
808 ExcMessage(
809 "I'm sorry, Dave. I'm afraid I can't do that.\nWe crashed."));
810
811 tau = (tau == Number(0.) ? tau_max : tau);
812
813#ifdef DEBUG_OUTPUT
814 std::cout << " computed tau_max = " << tau_max << " (CFL = " << cfl
815 << ")" << std::endl;
816 std::cout << " step with tau = " << tau << std::endl;
817#endif
818
819 /* We need to signal a restart if the enforced tau is too wacky: */
820 *restart_needed.view() = (tau > acceptable_tau_max_ratio_ * tau_max);
821
822 /* Don't bother with computing the update step, signal a restart: */
823 if (*restart_needed.view() &&
824 id_violation_strategy_ == IDViolationStrategy::raise_exception) {
825 n_restarts_++;
826 /* Suggest a restart with tau_max: */
827#ifdef DEBUG_OUTPUT
828 std::cout << " signalling restart (suggested_tau_max = "
829 << tau_max << ")" << std::endl;
830#endif
831
832 throw Restart{tau_max};
833 }
834 }
835
836 /* moves the "boolean" to device memory space: */
837 int *restart_needed_view = restart_needed.view<MemorySpace>();
838
839#ifdef DEBUG
840 /* Exchange d_ij so that we can check for symmetry: */
841 dij_matrix_view.update_ghost_rows();
842#endif
843
844 /*
845 * -------------------------------------------------------------------------
846 * Step 4: Low-order update, also compute limiter bounds, R_i
847 * -------------------------------------------------------------------------
848 */
849
850 {
852 scoped_name("l.-o. update, compute bounds, r_i, and p_ij"));
853
854 const Number weight =
855 -std::accumulate(stage_weights.begin(), stage_weights.end(), -1.);
856
857 const auto body = [=](auto sentinel,
858 auto have_discontinuous_ansatz,
859 const unsigned int i) {
860 using T = decltype(sentinel);
861
862 const auto view = hyperbolic_system_views.template view<T>();
863
864 using View = decltype(view);
865 using flux_contribution_type = typename View::flux_contribution_type;
866 using state_type = typename View::state_type;
867
868 const unsigned int stride_size = sparsity_simd_view.stride_of_row(i);
869
870 auto limiter_view = limiter_views.template view<T>();
871
872 /* Skip constrained degrees of freedom: */
873 const unsigned int row_length = sparsity_simd_view.row_length(i);
874 if (row_length == 1)
875 return;
876
877 const auto U_i = old_U_view.template read_tensor<T>(i);
878 auto U_i_new = U_i;
879
880 const auto alpha_i = alpha_view.template read_entry<T>(i);
881 const auto m_i = lumped_mass_matrix_view.template read_entry<T>(i);
882 const auto m_i_inv =
883 lumped_mass_matrix_inverse_view.template read_entry<T>(i);
884
885 const auto flux_i = view.flux_contribution(
886 old_precomputed_view, initial_precomputed_view, i, U_i);
887
888 Kokkos::Array<flux_contribution_type, stages> flux_iHs;
889 [[maybe_unused]] state_type S_iH;
890
891 for (int s = 0; s < stages; ++s) {
892 const auto U_iHs = stage_U_view[s].template read_tensor<T>(i);
893 flux_iHs[s] = view.flux_contribution(
894 stage_precomputed_view[s], initial_precomputed_view, i, U_iHs);
895
896 if constexpr (View::have_source_terms) {
897 S_iH += stage_weight[s] *
898 view.nodal_source(stage_precomputed_view[s], i, U_iHs, tau);
899 }
900 }
901
902 [[maybe_unused]] state_type S_i;
903 state_type F_iH;
904
905 if constexpr (View::have_source_terms) {
906 S_i = view.nodal_source(old_precomputed_view, i, U_i, tau);
907 S_iH += weight * S_i;
908 U_i_new += tau * /* m_i_inv * m_i */ S_i;
909 F_iH += m_i * S_iH;
910 }
911
912 limiter_view.reset(old_precomputed_view, i, U_i, flux_i);
913
914 [[maybe_unused]] state_type affine_shift;
915
916 /*
917 * Workaround: For shallow water we need to accumulate an
918 * additional contribution to the affine shift over the stencil
919 * before we can compute limiter bounds.
920 */
921
922 const unsigned int *js = sparsity_simd_view.columns(i);
923 if constexpr (shallow_water) {
924 for (unsigned int col_idx = 0; col_idx < row_length;
925 ++col_idx, js += stride_size) {
926
927 const auto U_j = old_U_view.template read_tensor<T>(js);
928 const auto flux_j = view.flux_contribution(
929 old_precomputed_view, initial_precomputed_view, js, U_j);
930
931 const auto d_ij =
932 dij_matrix_view.template read_entry<T>(i, col_idx);
933 const auto c_ij =
934 cij_matrix_view.template read_tensor<T>(i, col_idx);
935
936 const auto B_ij = view.affine_shift(flux_i, flux_j, c_ij, d_ij);
937 affine_shift += B_ij;
938 }
939
940 affine_shift *= tau * m_i_inv;
941 }
942
943 if constexpr (View::have_source_terms) {
944 affine_shift += tau * /* m_i_inv * m_i */ S_i;
945 }
946
947 js = sparsity_simd_view.columns(i);
948 for (unsigned int col_idx = 0; col_idx < row_length;
949 ++col_idx, js += stride_size) {
950
951 const auto U_j = old_U_view.template read_tensor<T>(js);
952
953 const auto alpha_j = alpha_view.template read_entry<T>(js);
954
955 const auto d_ij = dij_matrix_view.template read_entry<T>(i, col_idx);
956 auto factor = (alpha_i + alpha_j) * Number(.5);
957
958 if constexpr (have_discontinuous_ansatz) {
959 const auto incidence_ij =
960 incidence_matrix_view.template read_entry<T>(i, col_idx);
961 factor = std::max(factor, incidence_ij);
962 }
963
964 const auto d_ijH = d_ij * factor;
965
966#ifdef DEBUG_SYMMETRY_CHECK
967 /*
968 * Verify that all local chunks of the d_ij matrix have been
969 * computed consistently over all MPI ranks. For that we import
970 * all ghost rows from neighboring MPI ranks and simply check
971 * that the (local) values of d_ij and d_ji match.
972 */
973 const auto d_ji =
974 dij_matrix_view.template read_transposed_entry<T>(i, col_idx);
975 Assert(std::max(std::abs(d_ij - d_ji), T(1.0e-12)) == T(1.0e-12),
976 dealii::ExcMessage(
977 "d_ij not symmetrized correctly over MPI ranks"));
978#endif
979
980 const auto c_ij = cij_matrix_view.template read_tensor<T>(i, col_idx);
981 constexpr auto eps = std::numeric_limits<Number>::epsilon();
982
983 const auto scale =
984 ryujin::compare_and_apply_mask<dealii::SIMDComparison::less_than>(
985 std::abs(d_ij), T(eps * eps), T(0.), T(1.) / d_ij);
986
987 const auto scaled_c_ij = c_ij * scale;
988
989 const auto flux_j = view.flux_contribution(
990 old_precomputed_view, initial_precomputed_view, js, U_j);
991
992 const auto m_ij = mass_matrix_view.template read_entry<T>(i, col_idx);
993
994 /*
995 * Compute low-order flux and limiter bounds:
996 */
997
998 const auto flux_ij = view.flux_divergence(flux_i, flux_j, c_ij);
999 U_i_new += tau * m_i_inv * flux_ij;
1000 auto P_ij = -flux_ij;
1001
1002 if constexpr (shallow_water) {
1003 /*
1004 * Workaround: Shallow water (and related) are special:
1005 */
1006
1007 const auto &[U_star_ij, U_star_ji] =
1008 view.equilibrated_states(flux_i, flux_j);
1009
1010 U_i_new += tau * m_i_inv * d_ij * (U_star_ji - U_star_ij);
1011 F_iH += d_ijH * (U_star_ji - U_star_ij);
1012 P_ij += (d_ijH - d_ij) * (U_star_ji - U_star_ij);
1013
1014 limiter_view.accumulate(old_precomputed_view,
1015 U_j,
1016 U_star_ij,
1017 U_star_ji,
1018 scaled_c_ij,
1019 affine_shift);
1020
1021 } else {
1022
1023 U_i_new += tau * m_i_inv * d_ij * (U_j - U_i);
1024 F_iH += d_ijH * (U_j - U_i);
1025 P_ij += (d_ijH - d_ij) * (U_j - U_i);
1026
1027 limiter_view.accumulate(old_precomputed_view,
1028 js,
1029 U_j,
1030 flux_j,
1031 scaled_c_ij,
1032 affine_shift);
1033 }
1034
1035 if constexpr (View::have_source_terms) {
1036 F_iH -= m_ij * S_iH;
1037 P_ij -= m_ij * /*sic!*/ S_i;
1038 }
1039
1040 /*
1041 * Compute high-order fluxes and source terms:
1042 */
1043
1044 if constexpr (View::have_high_order_flux) {
1045 const auto high_order_flux_ij =
1046 view.high_order_flux_divergence(flux_i, flux_j, c_ij);
1047 F_iH += weight * high_order_flux_ij;
1048 P_ij += weight * high_order_flux_ij;
1049 } else {
1050 F_iH += weight * flux_ij;
1051 P_ij += weight * flux_ij;
1052 }
1053
1054 if constexpr (View::have_source_terms) {
1055 const auto S_j =
1056 view.nodal_source(old_precomputed_view, js, U_j, tau);
1057 F_iH += weight * m_ij * S_j;
1058 P_ij += weight * m_ij * S_j;
1059 }
1060
1061 for (int s = 0; s < stages; ++s) {
1062 const auto U_jHs = stage_U_view[s].template read_tensor<T>(js);
1063 const auto flux_jHs = view.flux_contribution(
1064 stage_precomputed_view[s], initial_precomputed_view, js, U_jHs);
1065
1066 if constexpr (View::have_high_order_flux) {
1067 const auto high_order_flux_ij =
1068 view.high_order_flux_divergence(flux_iHs[s], flux_jHs, c_ij);
1069 F_iH += stage_weight[s] * high_order_flux_ij;
1070 P_ij += stage_weight[s] * high_order_flux_ij;
1071 } else {
1072 const auto flux_ij =
1073 view.flux_divergence(flux_iHs[s], flux_jHs, c_ij);
1074 F_iH += stage_weight[s] * flux_ij;
1075 P_ij += stage_weight[s] * flux_ij;
1076 }
1077
1078 if constexpr (View::have_source_terms) {
1079 const auto S_js =
1080 view.nodal_source(stage_precomputed_view[s], js, U_jHs, tau);
1081 F_iH += stage_weight[s] * m_ij * S_js;
1082 P_ij += stage_weight[s] * m_ij * S_js;
1083 }
1084 }
1085
1086 pij_matrix_view.template write_tensor<T>(P_ij, i, col_idx, true);
1087 }
1088
1089#ifdef DEBUG_EXPENSIVE_BOUNDS_CHECK
1090 if (!view.is_admissible(U_i_new)) {
1091 atomic_store(restart_needed_view, 1);
1092 }
1093#endif
1094
1095 new_U_view.template write_tensor<T>(U_i_new, i);
1096 r_view.template write_tensor<T>(F_iH, i);
1097
1098 const auto hd_i = m_i * measure_of_omega_inverse;
1099 const auto relaxed_bounds = limiter_view.bounds(hd_i);
1100 bounds_view.template write_tensor<T>(relaxed_bounds, i);
1101 };
1102
1103 /*
1104 * Chain through a compile time integral constant std::true_type for
1105 * a discontinuous ansatz and std::false_type otherwise. We use the
1106 * (constexpr) integral constant later on to avoid branching when
1107 * computing d_ijH.
1108 */
1109 if (have_discontinuous_ansatz) {
1110 loop<MemorySpace, Number>(
1111 loop_name(), body, 0, n_internal, n_owned, std::true_type{});
1112 } else {
1113 loop<MemorySpace, Number>(
1114 loop_name(), body, 0, n_internal, n_owned, std::false_type{});
1115 }
1116
1117 r_view.update_ghost_values();
1118 if (have_discontinuous_ansatz) {
1119 /*
1120 * In case we extend bounds over the stencil, we have to ensure
1121 * that ghost ranges are properly communicated over all MPI
1122 * ranks.
1123 */
1124 bounds_view.update_ghost_values();
1125 }
1126 }
1127
1128 /*
1129 * -------------------------------------------------------------------------
1130 * Step 5: Compute second part of P_ij, and l_ij (first round):
1131 * -------------------------------------------------------------------------
1132 */
1133
1134 if (n_limiter_iterations != 0) {
1135 ComputingTimer::Scope scope(scoped_name("compute p_ij, and l_ij"));
1136
1137 const auto body = [=](auto sentinel,
1138 auto have_discontinuous_ansatz,
1139 const unsigned int i) {
1140 using T = decltype(sentinel);
1141
1142 const unsigned int stride_size = sparsity_simd_view.stride_of_row(i);
1143
1144 auto limiter_view = limiter_views.template view<T>();
1145
1146 /* Skip constrained degrees of freedom: */
1147 const unsigned int row_length = sparsity_simd_view.row_length(i);
1148 if (row_length == 1)
1149 return;
1150
1151 auto local_bounds =
1152 bounds_view.template read_tensor<T, std::array<T, n_bounds>>(i);
1153
1154 /*
1155 * In case of a discontinuous finite element ansatz we need to
1156 * extend bounds over the stencil. We do this by looping over the
1157 * stencil once and taking the minimum/maximum:
1158 */
1159 if constexpr (have_discontinuous_ansatz) {
1160 /* Skip diagonal. */
1161 const unsigned int *js = sparsity_simd_view.columns(i) + stride_size;
1162 for (unsigned int col_idx = 1; col_idx < row_length;
1163 ++col_idx, js += stride_size) {
1164 local_bounds = limiter_view.combine_bounds(
1165 local_bounds,
1166 bounds_view.template read_tensor<T, std::array<T, n_bounds>>(
1167 js));
1168 }
1169 bounds_view.template write_tensor<T>(local_bounds, i);
1170 }
1171
1172 [[maybe_unused]] T m_i;
1173 if constexpr (have_discontinuous_ansatz)
1174 m_i = lumped_mass_matrix_view.template read_entry<T>(i);
1175
1176 const auto m_i_inv =
1177 lumped_mass_matrix_inverse_view.template read_entry<T>(i);
1178
1179 const auto U_i_new = new_U_view.template read_tensor<T>(i);
1180
1181 const auto F_iH = r_view.template read_tensor<T>(i);
1182
1183 const auto lambda_inv = Number(row_length - 1);
1184 const auto factor = tau * m_i_inv * lambda_inv;
1185
1186 /*
1187 * Note: We "software-pipeline" the read access into the p_ij
1188 * matrix by one column index. This ensures that the P_ij entry of
1189 * the next column index is loaded *before* we store the updated
1190 * entry for the current column index. Otherwise we run into
1191 * aliasing issues that force the (cuda) compiler to serialize the
1192 * final store of P_ij and the read of the next entry.
1193 */
1194 auto P_ij = pij_matrix_view.template read_tensor<T>(i, 1);
1195
1196 /* Skip diagonal. */
1197 const unsigned int *js = sparsity_simd_view.columns(i) + stride_size;
1198 for (unsigned int col_idx = 1; col_idx < row_length;
1199 ++col_idx, js += stride_size) {
1200
1201 const auto P_ij_next = pij_matrix_view.template read_tensor<T>(
1202 i, col_idx + 1 < row_length ? col_idx + 1 : col_idx);
1203 const auto F_jH = r_view.template read_tensor<T>(js);
1204
1205 /*
1206 * Mass matrix correction:
1207 */
1208
1209 const auto kronecker_ij = col_idx == 0 ? T(1.) : T(0.);
1210
1211 if constexpr (have_discontinuous_ansatz) {
1212 /* Use full consistent mass matrix inverse: */
1213
1214 const auto m_j = lumped_mass_matrix_view.template read_entry<T>(js);
1215 const auto m_ij_inv =
1216 mass_matrix_inverse_view.template read_entry<T>(i, col_idx);
1217 const auto b_ij = m_i * m_ij_inv - kronecker_ij;
1218 const auto b_ji = m_j * m_ij_inv - kronecker_ij;
1219
1220 P_ij += b_ij * F_jH - b_ji * F_iH;
1221
1222 } else {
1223 /* Use Neumann series expansion: */
1224
1225 const auto m_j_inv =
1226 lumped_mass_matrix_inverse_view.template read_entry<T>(js);
1227 const auto m_ij =
1228 mass_matrix_view.template read_entry<T>(i, col_idx);
1229 const auto b_ij = kronecker_ij - m_ij * m_j_inv;
1230 const auto b_ji = kronecker_ij - m_ij * m_i_inv;
1231
1232 P_ij += b_ij * F_jH - b_ji * F_iH;
1233 }
1234
1235 P_ij *= factor;
1236 pij_matrix_view.template write_tensor<T>(P_ij, i, col_idx);
1237
1238 /*
1239 * Compute limiter coefficients:
1240 */
1241
1242 const auto &[l_ij, success] =
1243 limiter_view.limit(local_bounds, U_i_new, P_ij);
1244 lij_matrix_view.template write_entry<T>(l_ij, i, col_idx, true);
1245
1246 /*
1247 * If the success is set to false then the low-order update
1248 * resulted in a state outside of the limiter bounds. This can
1249 * happen if we compute with an aggressive CFL number. We
1250 * signal this condition by setting the restart_needed flag and
1251 * defer further action to the chosen IDViolationStrategy and the
1252 * policy set in the TimeIntegrator.
1253 */
1254 if (!success)
1255 atomic_store(restart_needed_view, 1);
1256
1257 P_ij = P_ij_next;
1258 }
1259 };
1260
1261 /*
1262 * Chain through a compile time integral constant std::true_type for
1263 * a discontinuous ansatz and std::false_type otherwise. We use the
1264 * (constexpr) integral constant later on to avoid branching when
1265 * computing d_ijH.
1266 */
1267 if (have_discontinuous_ansatz) {
1268 loop<MemorySpace, Number>(
1269 loop_name(), body, 0, n_internal, n_owned, std::true_type{});
1270 } else {
1271 loop<MemorySpace, Number>(
1272 loop_name(), body, 0, n_internal, n_owned, std::false_type{});
1273 }
1274
1275 lij_matrix_view.update_ghost_rows();
1276 }
1277
1278 /*
1279 * -------------------------------------------------------------------------
1280 * Step 6, 7: Perform high-order update:
1281 *
1282 * Symmetrize l_ij
1283 * High-order update: += l_ij * lambda * P_ij
1284 * Compute next l_ij
1285 * -------------------------------------------------------------------------
1286 */
1287
1288 for (unsigned int pass = 0; pass < n_limiter_iterations; ++pass) {
1289 bool last_round = (pass + 1 == n_limiter_iterations);
1290
1291 std::string additional_step = (last_round ? "" : ", next l_ij");
1293 scoped_name("symmetrize l_ij, h.-o. update" + additional_step));
1294
1295 const auto lij_view = (n_limiter_iterations == 2 && last_round)
1296 ? lij_matrix_next_view
1297 : lij_matrix_view;
1298
1299 const auto body = [=](auto sentinel, const unsigned int i) {
1300 using T = decltype(sentinel);
1301
1302 auto limiter_view = limiter_views.template view<T>();
1303
1304 /* Skip constrained degrees of freedom: */
1305 const unsigned int row_length = sparsity_simd_view.row_length(i);
1306 if (row_length == 1)
1307 return;
1308
1309 auto U_i_new = new_U_view.template read_tensor<T>(i);
1310
1311 const Number lambda = Number(1.) / Number(row_length - 1);
1312
1313 /* Skip diagonal. */
1314 for (unsigned int col_idx = 1; col_idx < row_length; ++col_idx) {
1315
1316 const auto l_ij =
1317 std::min(lij_view.template read_entry<T>(i, col_idx),
1318 lij_view.template read_transposed_entry<T>(i, col_idx));
1319
1320 const auto p_ij = pij_matrix_view.template read_tensor<T>(i, col_idx);
1321
1322 U_i_new += l_ij * lambda * p_ij;
1323 }
1324
1325#ifdef DEBUG_EXPENSIVE_BOUNDS_CHECK
1326 const auto view = hyperbolic_system_views.template view<T>();
1327 if (!view.is_admissible(U_i_new)) {
1328 atomic_store(restart_needed_view, 1);
1329 }
1330#endif
1331
1332 new_U_view.template write_tensor<T>(U_i_new, i);
1333
1334 /* Skip computating l_ij and updating p_ij in the last round */
1335 if (last_round)
1336 return;
1337
1338 const auto local_bounds =
1339 bounds_view.template read_tensor<T, std::array<T, n_bounds>>(i);
1340 /* Skip diagonal. */
1341 for (unsigned int col_idx = 1; col_idx < row_length; ++col_idx) {
1342
1343 const auto old_l_ij =
1344 std::min(lij_view.template read_entry<T>(i, col_idx),
1345 lij_view.template read_transposed_entry<T>(i, col_idx));
1346
1347 const auto new_p_ij =
1348 (T(1.) - old_l_ij) *
1349 pij_matrix_view.template read_tensor<T>(i, col_idx);
1350
1351 const auto &[new_l_ij, success] =
1352 limiter_view.limit(local_bounds, U_i_new, new_p_ij);
1353
1354 /*
1355 * This is the second pass of the limiter. Under rare
1356 * circumstances the previous high-order update might be
1357 * slightly out of bounds due to roundoff errors. This happens
1358 * for example in flat regions or in stagnation points at a
1359 * (slip boundary) point. The limiter should ensure that we do
1360 * not further manipulate the state in this case. We thus only
1361 * signal a restart condition if the `EXPENSIVE_BOUNDS_CHECK` debug
1362 * macro is defined.
1363 */
1364#ifdef DEBUG_EXPENSIVE_BOUNDS_CHECK
1365 if (!success)
1366 atomic_store(restart_needed_view, 1);
1367#endif
1368
1369 /*
1370 * Shortcut: We omit updating the p_ij and q_ij matrices and
1371 * simply write (1 - l_ij^(1)) * l_ij^(2) into the l_ij matrix.
1372 *
1373 * This approach only works for at most two limiting steps.
1374 */
1375 const auto entry = (T(1.) - old_l_ij) * new_l_ij;
1376 lij_matrix_next_view.write_entry(entry, i, col_idx, true);
1377 }
1378 };
1379
1380 loop<MemorySpace, Number>(loop_name(), body, 0, n_internal, n_owned);
1381
1382 if (!last_round) {
1383 lij_matrix_next_view.update_ghost_rows();
1384 }
1385 } /* limiter_iter_ */
1386
1387 /*
1388 * Pass through the parabolic state vector
1389 */
1390 const auto &old_V = old_state_vector.V;
1391 auto &new_V = new_state_vector.V;
1392 new_V = old_V;
1393
1394 /*
1395 * Do we have to restart?
1396 */
1397
1398 {
1399 ComputingTimer::Scope scope("time step [X] _ - synchronization barriers");
1400
1401 /*
1402 * Synchronize whether we have to restart the time step. Even though
1403 * the restart condition itself only affects the local ensemble we
1404 * nevertheless need to synchronize the flag in case we perform
1405 * synchronized global time steps. (Otherwise different ensembles
1406 * might end up with a different time step.)
1407 *
1408 * The host view reads the flag back from the selected memory space.
1409 */
1410 int &restart_flag = *restart_needed.view();
1411 restart_flag = Utilities::MPI::logical_or(
1412 restart_flag != 0, mpi_ensemble_.synchronization_communicator());
1413 }
1414
1415 if (*restart_needed.view()) {
1416 switch (id_violation_strategy_) {
1418 n_warnings_++;
1419#ifdef DEBUG_OUTPUT
1420 std::cout << " raised warning, CFL/IDP violation encountered "
1421 << std::endl;
1422#endif
1423 break;
1425 n_restarts_++;
1426 /* Suggest a restart with tau_max: */
1427#ifdef DEBUG_OUTPUT
1428 std::cout << " signalling restart (suggested_tau_max = "
1429 << tau_max << ")" << std::endl;
1430#endif
1431 throw Restart{tau_max};
1432 }
1433 }
1434
1435 /* Poison all values that are left invalid after the update step: */
1436 debug_poison_invalid_values(new_state_vector, *offline_data_);
1437
1438 /* Return the time step size tau: */
1439 return tau;
1440 }
1441
1442} /* namespace ryujin */
static dealii::Timer & timer(const std::string &section)
void reinit_state_vector(StateVector< Number > &state_vector) const
typename HyperbolicSystem::template View< dim, Number > View
HyperbolicModule(const MPIEnsemble &mpi_ensemble, const OfflineData< dim, Number > &offline_data, const HyperbolicSystem &hyperbolic_system, const InitialValues< HyperbolicDescription, dim, Number > &initial_values, const std::string &subsection="/HyperbolicModule")
typename HyperbolicDescription::HyperbolicSystem HyperbolicSystem
void prepare_state_vector(StateVector< Number > &state_vector, Number t) const
Number step(const StateVector< Number > &old_state_vector, std::array< std::reference_wrapper< const StateVector< Number > >, stages > stage_state_vectors, const std::array< Number, stages > stage_weights, StateVector< Number > &new_state_vector, Number tau=Number(0.), Number tau_max=std::numeric_limits< Number >::max()) const
value_type * view()
std::conditional_t< have_separate_memory_spaces, dealii::MemorySpace::Default, dealii::MemorySpace::Host > selected_memory_space_t
Definition gpu.h:65
constexpr bool have_separate_memory_spaces
Definition gpu.h:29
void debug_poison_invalid_values(StateVector< Number > &state_vector, const OfflineData &offline_data)
MultiComponentVector< Number > U
std::vector< ScalarVector< Number > > V