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