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