ryujin 2.1.1 revision ee5cbcbf2346c1299c942d0e1f13b46449973c18
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 &result) {
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;
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 const Number local_tau = cfl * mass / (Number(-2.) * d_sum);
793
794 result = std::min(result, local_tau);
795 };
796
797 tau_max = reduction_loop<MemorySpace, Kokkos::Min<Number>>(
798 loop_name() + 'b', body, tau_max, 0, n_owned);
799 }
800
801 {
802 ComputingTimer::Scope scope("time step [X] _ - synchronization barriers");
803
804 /*
805 * MPI Barrier: Synchronize the maximal time-step size. This has to
806 * happen either over the global, or the local subrange communicator:
807 */
808 tau_max = Utilities::MPI::min(
809 tau_max, mpi_ensemble_.synchronization_communicator());
810
811 AssertThrow(
812 !std::isnan(tau_max) && !std::isinf(tau_max) && tau_max > 0.,
813 ExcMessage(
814 "I'm sorry, Dave. I'm afraid I can't do that.\nWe crashed."));
815
816 tau = (tau == Number(0.) ? tau_max : tau);
817
818#ifdef DEBUG_OUTPUT
819 std::cout << " computed tau_max = " << tau_max << " (CFL = " << cfl
820 << ")" << std::endl;
821 std::cout << " step with tau = " << tau << std::endl;
822#endif
823
824 /* We need to signal a restart if the enforced tau is too wacky: */
825 *restart_needed.view() = (tau > acceptable_tau_max_ratio_ * tau_max);
826
827 /* Don't bother with computing the update step, signal a restart: */
828 if (*restart_needed.view() &&
829 id_violation_strategy_ == IDViolationStrategy::raise_exception) {
830 n_restarts_++;
831 /* Suggest a restart with tau_max: */
832#ifdef DEBUG_OUTPUT
833 std::cout << " signalling restart (suggested_tau_max = "
834 << tau_max << ")" << std::endl;
835#endif
836
837 throw Restart{tau_max};
838 }
839 }
840
841 /* moves the "boolean" to device memory space: */
842 int *restart_needed_view = restart_needed.view<MemorySpace>();
843
844#ifdef DEBUG
845 /* Exchange d_ij so that we can check for symmetry: */
846 dij_matrix_view.update_ghost_rows();
847#endif
848
849 /*
850 * -------------------------------------------------------------------------
851 * Step 4: Low-order update, also compute limiter bounds, R_i
852 * -------------------------------------------------------------------------
853 */
854
855 {
857 scoped_name("l.-o. update, compute bounds, r_i, and p_ij"));
858
859 const Number weight =
860 -std::accumulate(stage_weights.begin(), stage_weights.end(), -1.);
861
862 const auto body = [=](auto sentinel,
863 auto have_discontinuous_ansatz,
864 const unsigned int i) {
865 using T = decltype(sentinel);
866
867 const auto view = hyperbolic_system_views.template view<T>();
868
869 using View = decltype(view);
870 using flux_contribution_type = typename View::flux_contribution_type;
871 using state_type = typename View::state_type;
872
873 const unsigned int stride_size = sparsity_simd_view.stride_of_row(i);
874
875 auto limiter_view = limiter_views.template view<T>();
876
877 /* Skip constrained degrees of freedom: */
878 const unsigned int row_length = sparsity_simd_view.row_length(i);
879 if (row_length == 1)
880 return;
881
882 const auto U_i = old_U_view.template read_tensor<T>(i);
883 auto U_i_new = U_i;
884
885 const auto alpha_i = alpha_view.template read_entry<T>(i);
886 const auto m_i = lumped_mass_matrix_view.template read_entry<T>(i);
887 const auto m_i_inv =
888 lumped_mass_matrix_inverse_view.template read_entry<T>(i);
889
890 const auto flux_i = view.flux_contribution(
891 old_precomputed_view, initial_precomputed_view, i, U_i);
892
893 Kokkos::Array<flux_contribution_type, stages> flux_iHs;
894 [[maybe_unused]] state_type S_iH;
895
896 for (int s = 0; s < stages; ++s) {
897 const auto U_iHs = stage_U_view[s].template read_tensor<T>(i);
898 flux_iHs[s] = view.flux_contribution(
899 stage_precomputed_view[s], initial_precomputed_view, i, U_iHs);
900
901 if constexpr (View::have_source_terms) {
902 S_iH += stage_weight[s] *
903 view.nodal_source(stage_precomputed_view[s], i, U_iHs, tau);
904 }
905 }
906
907 [[maybe_unused]] state_type S_i;
908 state_type F_iH;
909
910 if constexpr (View::have_source_terms) {
911 S_i = view.nodal_source(old_precomputed_view, i, U_i, tau);
912 S_iH += weight * S_i;
913 U_i_new += tau * /* m_i_inv * m_i */ S_i;
914 F_iH += m_i * S_iH;
915 }
916
917 limiter_view.reset(old_precomputed_view, i, U_i, flux_i);
918
919 [[maybe_unused]] state_type affine_shift;
920
921 /*
922 * Workaround: For shallow water we need to accumulate an
923 * additional contribution to the affine shift over the stencil
924 * before we can compute limiter bounds.
925 */
926
927 const unsigned int *js = sparsity_simd_view.columns(i);
928 if constexpr (shallow_water) {
929 for (unsigned int col_idx = 0; col_idx < row_length;
930 ++col_idx, js += stride_size) {
931
932 const auto U_j = old_U_view.template read_tensor<T>(js);
933 const auto flux_j = view.flux_contribution(
934 old_precomputed_view, initial_precomputed_view, js, U_j);
935
936 const auto d_ij =
937 dij_matrix_view.template read_entry<T>(i, col_idx);
938 const auto c_ij =
939 cij_matrix_view.template read_tensor<T>(i, col_idx);
940
941 const auto B_ij = view.affine_shift(flux_i, flux_j, c_ij, d_ij);
942 affine_shift += B_ij;
943 }
944
945 affine_shift *= tau * m_i_inv;
946 }
947
948 if constexpr (View::have_source_terms) {
949 affine_shift += tau * /* m_i_inv * m_i */ S_i;
950 }
951
952 js = sparsity_simd_view.columns(i);
953 for (unsigned int col_idx = 0; col_idx < row_length;
954 ++col_idx, js += stride_size) {
955
956 const auto U_j = old_U_view.template read_tensor<T>(js);
957
958 const auto alpha_j = alpha_view.template read_entry<T>(js);
959
960 const auto d_ij = dij_matrix_view.template read_entry<T>(i, col_idx);
961 auto factor = (alpha_i + alpha_j) * Number(.5);
962
963 if constexpr (have_discontinuous_ansatz) {
964 const auto incidence_ij =
965 incidence_matrix_view.template read_entry<T>(i, col_idx);
966 factor = std::max(factor, incidence_ij);
967 }
968
969 const auto d_ijH = d_ij * factor;
970
971#ifdef DEBUG_SYMMETRY_CHECK
972 /*
973 * Verify that all local chunks of the d_ij matrix have been
974 * computed consistently over all MPI ranks. For that we import
975 * all ghost rows from neighboring MPI ranks and simply check
976 * that the (local) values of d_ij and d_ji match.
977 */
978 const auto d_ji =
979 dij_matrix_view.template read_transposed_entry<T>(i, col_idx);
980 Assert(std::max(std::abs(d_ij - d_ji), T(1.0e-12)) == T(1.0e-12),
981 dealii::ExcMessage(
982 "d_ij not symmetrized correctly over MPI ranks"));
983#endif
984
985 const auto c_ij = cij_matrix_view.template read_tensor<T>(i, col_idx);
986 constexpr auto eps = std::numeric_limits<Number>::epsilon();
987
988 const auto scale =
989 ryujin::compare_and_apply_mask<dealii::SIMDComparison::less_than>(
990 std::abs(d_ij), T(eps * eps), T(0.), T(1.) / d_ij);
991
992 const auto scaled_c_ij = c_ij * scale;
993
994 const auto flux_j = view.flux_contribution(
995 old_precomputed_view, initial_precomputed_view, js, U_j);
996
997 const auto m_ij = mass_matrix_view.template read_entry<T>(i, col_idx);
998
999 /*
1000 * Compute low-order flux and limiter bounds:
1001 */
1002
1003 const auto flux_ij = view.flux_divergence(flux_i, flux_j, c_ij);
1004 U_i_new += tau * m_i_inv * flux_ij;
1005 auto P_ij = -flux_ij;
1006
1007 if constexpr (shallow_water) {
1008 /*
1009 * Workaround: Shallow water (and related) are special:
1010 */
1011
1012 const auto &[U_star_ij, U_star_ji] =
1013 view.equilibrated_states(flux_i, flux_j);
1014
1015 U_i_new += tau * m_i_inv * d_ij * (U_star_ji - U_star_ij);
1016 F_iH += d_ijH * (U_star_ji - U_star_ij);
1017 P_ij += (d_ijH - d_ij) * (U_star_ji - U_star_ij);
1018
1019 limiter_view.accumulate(old_precomputed_view,
1020 U_j,
1021 U_star_ij,
1022 U_star_ji,
1023 scaled_c_ij,
1024 affine_shift);
1025
1026 } else {
1027
1028 U_i_new += tau * m_i_inv * d_ij * (U_j - U_i);
1029 F_iH += d_ijH * (U_j - U_i);
1030 P_ij += (d_ijH - d_ij) * (U_j - U_i);
1031
1032 limiter_view.accumulate(old_precomputed_view,
1033 js,
1034 U_j,
1035 flux_j,
1036 scaled_c_ij,
1037 affine_shift);
1038 }
1039
1040 if constexpr (View::have_source_terms) {
1041 F_iH -= m_ij * S_iH;
1042 P_ij -= m_ij * /*sic!*/ S_i;
1043 }
1044
1045 /*
1046 * Compute high-order fluxes and source terms:
1047 */
1048
1049 if constexpr (View::have_high_order_flux) {
1050 const auto high_order_flux_ij =
1051 view.high_order_flux_divergence(flux_i, flux_j, c_ij);
1052 F_iH += weight * high_order_flux_ij;
1053 P_ij += weight * high_order_flux_ij;
1054 } else {
1055 F_iH += weight * flux_ij;
1056 P_ij += weight * flux_ij;
1057 }
1058
1059 if constexpr (View::have_source_terms) {
1060 const auto S_j =
1061 view.nodal_source(old_precomputed_view, js, U_j, tau);
1062 F_iH += weight * m_ij * S_j;
1063 P_ij += weight * m_ij * S_j;
1064 }
1065
1066 for (int s = 0; s < stages; ++s) {
1067 const auto U_jHs = stage_U_view[s].template read_tensor<T>(js);
1068 const auto flux_jHs = view.flux_contribution(
1069 stage_precomputed_view[s], initial_precomputed_view, js, U_jHs);
1070
1071 if constexpr (View::have_high_order_flux) {
1072 const auto high_order_flux_ij =
1073 view.high_order_flux_divergence(flux_iHs[s], flux_jHs, c_ij);
1074 F_iH += stage_weight[s] * high_order_flux_ij;
1075 P_ij += stage_weight[s] * high_order_flux_ij;
1076 } else {
1077 const auto flux_ij =
1078 view.flux_divergence(flux_iHs[s], flux_jHs, c_ij);
1079 F_iH += stage_weight[s] * flux_ij;
1080 P_ij += stage_weight[s] * flux_ij;
1081 }
1082
1083 if constexpr (View::have_source_terms) {
1084 const auto S_js =
1085 view.nodal_source(stage_precomputed_view[s], js, U_jHs, tau);
1086 F_iH += stage_weight[s] * m_ij * S_js;
1087 P_ij += stage_weight[s] * m_ij * S_js;
1088 }
1089 }
1090
1091 pij_matrix_view.template write_tensor<T>(P_ij, i, col_idx, true);
1092 }
1093
1094#ifdef DEBUG_EXPENSIVE_BOUNDS_CHECK
1095 if (!view.is_admissible(U_i_new)) {
1096 atomic_store(restart_needed_view, 1);
1097 }
1098#endif
1099
1100 new_U_view.template write_tensor<T>(U_i_new, i);
1101 r_view.template write_tensor<T>(F_iH, i);
1102
1103 const auto hd_i = m_i * measure_of_omega_inverse;
1104 const auto relaxed_bounds = limiter_view.bounds(hd_i);
1105 bounds_view.template write_tensor<T>(relaxed_bounds, i);
1106 };
1107
1108 /*
1109 * Chain through a compile time integral constant std::true_type for
1110 * a discontinuous ansatz and std::false_type otherwise. We use the
1111 * (constexpr) integral constant later on to avoid branching when
1112 * computing d_ijH.
1113 */
1114 if (have_discontinuous_ansatz) {
1115 loop<MemorySpace, Number>(
1116 loop_name(), body, 0, n_internal, n_owned, std::true_type{});
1117 } else {
1118 loop<MemorySpace, Number>(
1119 loop_name(), body, 0, n_internal, n_owned, std::false_type{});
1120 }
1121
1122 r_view.update_ghost_values();
1123 if (have_discontinuous_ansatz) {
1124 /*
1125 * In case we extend bounds over the stencil, we have to ensure
1126 * that ghost ranges are properly communicated over all MPI
1127 * ranks.
1128 */
1129 bounds_view.update_ghost_values();
1130 }
1131 }
1132
1133 /*
1134 * -------------------------------------------------------------------------
1135 * Step 5: Compute second part of P_ij, and l_ij (first round):
1136 * -------------------------------------------------------------------------
1137 */
1138
1139 if (n_limiter_iterations != 0) {
1140 ComputingTimer::Scope scope(scoped_name("compute p_ij, and l_ij"));
1141
1142 const auto body = [=](auto sentinel,
1143 auto have_discontinuous_ansatz,
1144 const unsigned int i) {
1145 using T = decltype(sentinel);
1146
1147 const unsigned int stride_size = sparsity_simd_view.stride_of_row(i);
1148
1149 auto limiter_view = limiter_views.template view<T>();
1150
1151 /* Skip constrained degrees of freedom: */
1152 const unsigned int row_length = sparsity_simd_view.row_length(i);
1153 if (row_length == 1)
1154 return;
1155
1156 auto local_bounds =
1157 bounds_view.template read_tensor<T, std::array<T, n_bounds>>(i);
1158
1159 /*
1160 * In case of a discontinuous finite element ansatz we need to
1161 * extend bounds over the stencil. We do this by looping over the
1162 * stencil once and taking the minimum/maximum:
1163 */
1164 if constexpr (have_discontinuous_ansatz) {
1165 /* Skip diagonal. */
1166 const unsigned int *js = sparsity_simd_view.columns(i) + stride_size;
1167 for (unsigned int col_idx = 1; col_idx < row_length;
1168 ++col_idx, js += stride_size) {
1169 local_bounds = limiter_view.combine_bounds(
1170 local_bounds,
1171 bounds_view.template read_tensor<T, std::array<T, n_bounds>>(
1172 js));
1173 }
1174 bounds_view.template write_tensor<T>(local_bounds, i);
1175 }
1176
1177 [[maybe_unused]] T m_i;
1178 if constexpr (have_discontinuous_ansatz)
1179 m_i = lumped_mass_matrix_view.template read_entry<T>(i);
1180
1181 const auto m_i_inv =
1182 lumped_mass_matrix_inverse_view.template read_entry<T>(i);
1183
1184 const auto U_i_new = new_U_view.template read_tensor<T>(i);
1185
1186 const auto F_iH = r_view.template read_tensor<T>(i);
1187
1188 const auto lambda_inv = Number(row_length - 1);
1189 const auto factor = tau * m_i_inv * lambda_inv;
1190
1191 /*
1192 * Note: We "software-pipeline" the read access into the p_ij
1193 * matrix by one column index. This ensures that the P_ij entry of
1194 * the next column index is loaded *before* we store the updated
1195 * entry for the current column index. Otherwise we run into
1196 * aliasing issues that force the (cuda) compiler to serialize the
1197 * final store of P_ij and the read of the next entry.
1198 */
1199 auto P_ij = pij_matrix_view.template read_tensor<T>(i, 1);
1200
1201 /* Skip diagonal. */
1202 const unsigned int *js = sparsity_simd_view.columns(i) + stride_size;
1203 for (unsigned int col_idx = 1; col_idx < row_length;
1204 ++col_idx, js += stride_size) {
1205
1206 const auto P_ij_next = pij_matrix_view.template read_tensor<T>(
1207 i, col_idx + 1 < row_length ? col_idx + 1 : col_idx);
1208 const auto F_jH = r_view.template read_tensor<T>(js);
1209
1210 /*
1211 * Mass matrix correction:
1212 */
1213
1214 const auto kronecker_ij = col_idx == 0 ? T(1.) : T(0.);
1215
1216 if constexpr (have_discontinuous_ansatz) {
1217 /* Use full consistent mass matrix inverse: */
1218
1219 const auto m_j = lumped_mass_matrix_view.template read_entry<T>(js);
1220 const auto m_ij_inv =
1221 mass_matrix_inverse_view.template read_entry<T>(i, col_idx);
1222 const auto b_ij = m_i * m_ij_inv - kronecker_ij;
1223 const auto b_ji = m_j * m_ij_inv - kronecker_ij;
1224
1225 P_ij += b_ij * F_jH - b_ji * F_iH;
1226
1227 } else {
1228 /* Use Neumann series expansion: */
1229
1230 const auto m_j_inv =
1231 lumped_mass_matrix_inverse_view.template read_entry<T>(js);
1232 const auto m_ij =
1233 mass_matrix_view.template read_entry<T>(i, col_idx);
1234 const auto b_ij = kronecker_ij - m_ij * m_j_inv;
1235 const auto b_ji = kronecker_ij - m_ij * m_i_inv;
1236
1237 P_ij += b_ij * F_jH - b_ji * F_iH;
1238 }
1239
1240 P_ij *= factor;
1241 pij_matrix_view.template write_tensor<T>(P_ij, i, col_idx);
1242
1243 /*
1244 * Compute limiter coefficients:
1245 */
1246
1247 const auto &[l_ij, success] =
1248 limiter_view.limit(local_bounds, U_i_new, P_ij);
1249 lij_matrix_view.template write_entry<T>(l_ij, i, col_idx, true);
1250
1251 /*
1252 * If the success is set to false then the low-order update
1253 * resulted in a state outside of the limiter bounds. This can
1254 * happen if we compute with an aggressive CFL number. We
1255 * signal this condition by setting the restart_needed flag and
1256 * defer further action to the chosen IDViolationStrategy and the
1257 * policy set in the TimeIntegrator.
1258 */
1259 if (!success)
1260 atomic_store(restart_needed_view, 1);
1261
1262 P_ij = P_ij_next;
1263 }
1264 };
1265
1266 /*
1267 * Chain through a compile time integral constant std::true_type for
1268 * a discontinuous ansatz and std::false_type otherwise. We use the
1269 * (constexpr) integral constant later on to avoid branching when
1270 * computing d_ijH.
1271 */
1272 if (have_discontinuous_ansatz) {
1273 loop<MemorySpace, Number>(
1274 loop_name(), body, 0, n_internal, n_owned, std::true_type{});
1275 } else {
1276 loop<MemorySpace, Number>(
1277 loop_name(), body, 0, n_internal, n_owned, std::false_type{});
1278 }
1279
1280 lij_matrix_view.update_ghost_rows();
1281 }
1282
1283 /*
1284 * -------------------------------------------------------------------------
1285 * Step 6, 7: Perform high-order update:
1286 *
1287 * Symmetrize l_ij
1288 * High-order update: += l_ij * lambda * P_ij
1289 * Compute next l_ij
1290 * -------------------------------------------------------------------------
1291 */
1292
1293 for (unsigned int pass = 0; pass < n_limiter_iterations; ++pass) {
1294 bool last_round = (pass + 1 == n_limiter_iterations);
1295
1296 std::string additional_step = (last_round ? "" : ", next l_ij");
1298 scoped_name("symmetrize l_ij, h.-o. update" + additional_step));
1299
1300 const auto lij_view = (n_limiter_iterations == 2 && last_round)
1301 ? lij_matrix_next_view
1302 : lij_matrix_view;
1303
1304 const auto body = [=](auto sentinel, const unsigned int i) {
1305 using T = decltype(sentinel);
1306
1307 auto limiter_view = limiter_views.template view<T>();
1308
1309 /* Skip constrained degrees of freedom: */
1310 const unsigned int row_length = sparsity_simd_view.row_length(i);
1311 if (row_length == 1)
1312 return;
1313
1314 auto U_i_new = new_U_view.template read_tensor<T>(i);
1315
1316 const Number lambda = Number(1.) / Number(row_length - 1);
1317
1318 /* Skip diagonal. */
1319 for (unsigned int col_idx = 1; col_idx < row_length; ++col_idx) {
1320
1321 const auto l_ij =
1322 std::min(lij_view.template read_entry<T>(i, col_idx),
1323 lij_view.template read_transposed_entry<T>(i, col_idx));
1324
1325 const auto p_ij = pij_matrix_view.template read_tensor<T>(i, col_idx);
1326
1327 U_i_new += l_ij * lambda * p_ij;
1328 }
1329
1330#ifdef DEBUG_EXPENSIVE_BOUNDS_CHECK
1331 const auto view = hyperbolic_system_views.template view<T>();
1332 if (!view.is_admissible(U_i_new)) {
1333 atomic_store(restart_needed_view, 1);
1334 }
1335#endif
1336
1337 new_U_view.template write_tensor<T>(U_i_new, i);
1338
1339 /* Skip computating l_ij and updating p_ij in the last round */
1340 if (last_round)
1341 return;
1342
1343 const auto local_bounds =
1344 bounds_view.template read_tensor<T, std::array<T, n_bounds>>(i);
1345 /* Skip diagonal. */
1346 for (unsigned int col_idx = 1; col_idx < row_length; ++col_idx) {
1347
1348 const auto old_l_ij =
1349 std::min(lij_view.template read_entry<T>(i, col_idx),
1350 lij_view.template read_transposed_entry<T>(i, col_idx));
1351
1352 const auto new_p_ij =
1353 (T(1.) - old_l_ij) *
1354 pij_matrix_view.template read_tensor<T>(i, col_idx);
1355
1356 const auto &[new_l_ij, success] =
1357 limiter_view.limit(local_bounds, U_i_new, new_p_ij);
1358
1359 /*
1360 * This is the second pass of the limiter. Under rare
1361 * circumstances the previous high-order update might be
1362 * slightly out of bounds due to roundoff errors. This happens
1363 * for example in flat regions or in stagnation points at a
1364 * (slip boundary) point. The limiter should ensure that we do
1365 * not further manipulate the state in this case. We thus only
1366 * signal a restart condition if the `EXPENSIVE_BOUNDS_CHECK` debug
1367 * macro is defined.
1368 */
1369#ifdef DEBUG_EXPENSIVE_BOUNDS_CHECK
1370 if (!success)
1371 atomic_store(restart_needed_view, 1);
1372#endif
1373
1374 /*
1375 * Shortcut: We omit updating the p_ij and q_ij matrices and
1376 * simply write (1 - l_ij^(1)) * l_ij^(2) into the l_ij matrix.
1377 *
1378 * This approach only works for at most two limiting steps.
1379 */
1380 const auto entry = (T(1.) - old_l_ij) * new_l_ij;
1381 lij_matrix_next_view.write_entry(entry, i, col_idx, true);
1382 }
1383 };
1384
1385 loop<MemorySpace, Number>(loop_name(), body, 0, n_internal, n_owned);
1386
1387 if (!last_round) {
1388 lij_matrix_next_view.update_ghost_rows();
1389 }
1390 } /* limiter_iter_ */
1391
1392 /*
1393 * Pass through the parabolic state vector
1394 */
1395 const auto &old_V = std::get<2>(old_state_vector);
1396 auto &new_V = std::get<2>(new_state_vector);
1397 new_V = old_V;
1398
1399 /*
1400 * Do we have to restart?
1401 */
1402
1403 {
1404 ComputingTimer::Scope scope("time step [X] _ - synchronization barriers");
1405
1406 /*
1407 * Synchronize whether we have to restart the time step. Even though
1408 * the restart condition itself only affects the local ensemble we
1409 * nevertheless need to synchronize the flag in case we perform
1410 * synchronized global time steps. (Otherwise different ensembles
1411 * might end up with a different time step.)
1412 *
1413 * The host view reads the flag back from the selected memory space.
1414 */
1415 int &restart_flag = *restart_needed.view();
1416 restart_flag = Utilities::MPI::logical_or(
1417 restart_flag != 0, mpi_ensemble_.synchronization_communicator());
1418 }
1419
1420 if (*restart_needed.view()) {
1421 switch (id_violation_strategy_) {
1423 n_warnings_++;
1424#ifdef DEBUG_OUTPUT
1425 std::cout << " raised warning, CFL/IDP violation encountered "
1426 << std::endl;
1427#endif
1428 break;
1430 n_restarts_++;
1431 /* Suggest a restart with tau_max: */
1432#ifdef DEBUG_OUTPUT
1433 std::cout << " signalling restart (suggested_tau_max = "
1434 << tau_max << ")" << std::endl;
1435#endif
1436 throw Restart{tau_max};
1437 }
1438 }
1439
1440 /* Poison all values that are left invalid after the update step: */
1441 Vectors::debug_poison_invalid_values(new_state_vector, *offline_data_);
1442
1443 /* Return the time step size tau: */
1444 return tau;
1445 }
1446
1447} /* 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)