ryujin 2.1.1 revision b04fb4aba7bd97ddfa5994a6b4b6e8586eb15db2
Loading...
Searching...
No Matches
wave_speed_estimator.h
Go to the documentation of this file.
1//
2// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
3// Copyright (C) 2023 - 2026 by the ryujin authors
4//
5
6#pragma once
7
8#include <compile_time_options.h>
9
10#include "hyperbolic_system.h"
11
12#include <gpu.h>
13#include <observer_pointer.h>
14#include <simd.h>
15
16#include <deal.II/base/point.h>
17#include <deal.II/base/tensor.h>
18
19#include <random>
20
21// #define DEBUG_WAVE_SPEED_ESTIMATOR
22
23namespace ryujin
24{
25 namespace ScalarConservation
26 {
27 template <int dim,
28 typename Number = double,
29 typename MemorySpace = dealii::MemorySpace::Host>
30 class WaveSpeedEstimatorView;
31
42 template <typename ScalarNumber = double>
43 class WaveSpeedEstimator : public dealii::ParameterAcceptor
44 {
45 public:
50
60
62
66
70 WaveSpeedEstimator(const HyperbolicSystem &hyperbolic_system,
71 const std::string &subsection = "/WaveSpeedEstimator")
72 : ParameterAcceptor(subsection)
73 , parameters_("scalar_conservation_wave_speed_estimator_parameters",
75 , hyperbolic_system_(&hyperbolic_system)
76 {
77 /* reference remains valid due to implicit_transfers_host_resident */
78 auto &parameters = *parameters_.view();
79
80 parameters.use_greedy_wavespeed = false;
81 add_parameter("use greedy wavespeed",
82 parameters.use_greedy_wavespeed,
83 "Use a greedy wavespeed estimate instead of a guaranteed "
84 "upper bound "
85 "on the maximal wavespeed (for convex fluxes).");
86
87 parameters.use_averaged_entropy = false;
88 add_parameter("use averaged entropy",
89 parameters.use_averaged_entropy,
90 "In addition to the wavespeed estimate based on the Roe "
91 "average and "
92 "flux gradients of the left and right state also enforce "
93 "an entropy "
94 "inequality on the averaged Krŭzkov entropy.");
95
96 parameters.random_entropies = 0;
97 add_parameter(
98 "random entropies",
99 parameters.random_entropies,
100 "In addition to the wavespeed estimate based on the Roe average "
101 "and "
102 "flux gradients of the left and right state also enforce an "
103 "entropy "
104 "inequality on the prescribed number of random Krŭzkov entropies.");
105
106 /* invalidates view on default memory space */
107 ParameterAcceptor::parse_parameters_call_back.connect(
108 [this] { parameters_.view(); });
109 }
110
123 template <int dim,
124 typename Number,
125 typename MemorySpace = dealii::MemorySpace::Host>
126 auto view() const
127 {
128 if constexpr (!std::is_same_v<MemorySpace, dealii::MemorySpace::Host>) {
129 const auto &parameters = *parameters_.view();
130 AssertThrow(
131 !parameters.use_averaged_entropy &&
132 parameters.random_entropies == 0,
133 dealii::ExcMessage(
134 "The runtime options »use averaged entropy« and »random "
135 "entropies« evaluate the selected flux, which is only "
136 "possible on the host memory space. They are thus not "
137 "supported for a view on the device memory space."));
138 }
139
141 hyperbolic_system_->template view<dim, Number, MemorySpace>(),
142 *this};
143 }
144
145 private:
147
151
152 Mirrored<Parameters> parameters_;
153
154 dealii::ObserverPointer<const HyperbolicSystem> hyperbolic_system_;
155
156 template <int, typename, typename>
158
160 };
161
162
171 template <int dim, typename Number, typename MemorySpace>
173 {
174 public:
175 static_assert(
176 std::is_same_v<MemorySpace, dealii::MemorySpace::Host> ||
177 std::is_same_v<MemorySpace, dealii::MemorySpace::Default>,
178 "Unexpected memory space");
179
184
186
188
189 using state_type = typename View::state_type;
190
192
194
196
200
206 const View &view,
207 const WaveSpeedEstimator<ScalarNumber> &wave_speed_estimator)
208 : view_(view)
209 , parameters_(
210 wave_speed_estimator.parameters_.template view<MemorySpace>())
211 {
212 }
213
218 DEAL_II_HOST_DEVICE_ALWAYS_INLINE bool use_greedy_wavespeed() const
219 {
220 return parameters_->use_greedy_wavespeed;
221 }
222
227 DEAL_II_HOST_DEVICE_ALWAYS_INLINE bool use_averaged_entropy() const
228 {
229 return parameters_->use_averaged_entropy;
230 }
231
236 DEAL_II_HOST_DEVICE_ALWAYS_INLINE unsigned int random_entropies() const
237 {
238 return parameters_->random_entropies;
239 }
240
249 DEAL_II_HOST_DEVICE Number
250 compute(const Number &u_i,
251 const Number &u_j,
252 const precomputed_type &prec_i,
253 const precomputed_type &prec_j,
254 const dealii::Tensor<1, dim, Number> &n_ij) const;
255
260 DEAL_II_HOST_DEVICE Number
262 const state_type &U_i,
263 const state_type &U_j,
264 const unsigned int i,
265 const unsigned int *js,
266 const dealii::Tensor<1, dim, Number> &n_ij) const;
267
268 private:
270
274
275 const View view_;
276 const WaveSpeedEstimator<ScalarNumber>::Parameters *const parameters_;
277
279 };
280
281
282 /*
283 * -------------------------------------------------------------------------
284 * Inline definitions
285 * -------------------------------------------------------------------------
286 */
287
288
289 template <int dim, typename Number, typename MemorySpace>
290 DEAL_II_HOST_DEVICE_ALWAYS_INLINE Number
292 const Number &u_i,
293 const Number &u_j,
294 const precomputed_type &prec_i,
295 const precomputed_type &prec_j,
296 const dealii::Tensor<1, dim, Number> &n_ij) const
297 {
298 /* Project all fluxes to 1D: */
299 const Number f_i = view_.construct_flux_tensor(prec_i) * n_ij;
300 const Number f_j = view_.construct_flux_tensor(prec_j) * n_ij;
301 const Number df_i = view_.construct_flux_gradient_tensor(prec_i) * n_ij;
302 const Number df_j = view_.construct_flux_gradient_tensor(prec_j) * n_ij;
303
304 const auto h2 = Number(2. * view_.derivative_approximation_delta());
305
306#ifdef DEBUG_WAVE_SPEED_ESTIMATOR
307 std::cout << "\nu_i = " << u_i << std::endl;
308 std::cout << "u_j = " << u_j << std::endl;
309 std::cout << "f_i = " << f_i << std::endl;
310 std::cout << "f_j = " << f_j << std::endl;
311 std::cout << "df_i = " << df_i << std::endl;
312 std::cout << "df_j = " << df_j << std::endl;
313#endif
314
315 /*
316 * The Roe average with a regularization based on $h$ which is the
317 * step size used for the central difference approximation of f'(u).
318 *
319 * The regularization max(|u_i - u_j|, 2 * h) ensures that the
320 * quotient approximates the derivative f'( (u_i + u_j)/2 ) to the
321 * same precision that we use to compute f'(u_i) and f'(u_j) in the
322 * FunctionParser (via a central difference approximation).
323 *
324 * This implies that in contrast to the actual limit of the
325 * difference quotient we will approach 0 as |u_j - u_i| goes to
326 * zero. We fix this by taking the maximum with our approximation of
327 * f'(u_i) and f'(u_j) further down below.
328 */
329
330 auto lambda_max = std::abs(f_i - f_j) / std::max(std::abs(u_i - u_j), h2);
331#ifdef DEBUG_WAVE_SPEED_ESTIMATOR
332 std::cout << " Roe average = " << lambda_max << std::endl;
333#endif
334
335 constexpr auto gte = dealii::SIMDComparison::greater_than_or_equal;
336
337 if (use_greedy_wavespeed()) {
338 /*
339 * In case of a greedy estimate we make sure that we always use the
340 * Roe average and only fall back to the derivative approximation
341 * when u_i and u_j are close to each other within 2h:
342 */
343 lambda_max = ryujin::compare_and_apply_mask<gte>(
344 std::abs(u_i - u_j),
345 h2,
346 lambda_max,
347 /* Approximate derivative in centerpoint: */
348 std::abs(ScalarNumber(0.5) * (df_i + df_j)));
349#ifdef DEBUG_WAVE_SPEED_ESTIMATOR
350 std::cout << " interpolated = "
351 << std::abs(ScalarNumber(0.5) * (df_i + df_j)) << std::endl;
352#endif
353
354 } else {
355 /*
356 * Always take the maximum with |f'(u_i)| and |f'(u_j)|.
357 *
358 * For convex fluxes this implies that lambda_max is indeed the
359 * maximal wavespeed of the system. See Example 79.17 in reference
360 * @cite ErnGuermond2021.
361 */
362 lambda_max = std::max(lambda_max, std::abs(df_i));
363 lambda_max = std::max(lambda_max, std::abs(df_j));
364#ifdef DEBUG_WAVE_SPEED_ESTIMATOR
365 std::cout << " left derivative = " << std::abs(df_i) << std::endl;
366 std::cout << " right derivative = " << std::abs(df_j) << std::endl;
367#endif
368 }
369
370 /*
371 * Enforcing entropy inequalities for additional Krŭzkov entropies
372 * requires calling into the selected flux, which is only possible on
373 * the host memory space:
374 */
375
376 if constexpr (std::is_same_v<MemorySpace, dealii::MemorySpace::Host>) {
377 /*
378 * Thread-local helper lambda to generate a random number in [0,1]:
379 */
380
381 thread_local static const auto draw = []() {
382 static std::random_device random_device;
383 static auto generator = std::default_random_engine(random_device());
384 static std::uniform_real_distribution<ScalarNumber> dist(0., 1.);
385
386 if constexpr (std::is_same_v<ScalarNumber, Number>) {
387 /*
388 * Scalar quantity:
389 */
390 return dist(generator);
391
392 } else {
393 /*
394 * Populate a vectorized array:
395 */
396 Number result;
397 for (unsigned int s = 0; s < Number::size(); ++s)
398 result[s] = dist(generator);
399 return result;
400 }
401 };
402
403 /*
404 * Helper functions for enforcing entropy inequalities:
405 */
406
407 const auto enforce_entropy = [&](const Number &k) {
408 const Number f_k = view_.flux_function(k) * n_ij;
409
410#ifdef DEBUG_WAVE_SPEED_ESTIMATOR
411 std::cout << "k = " << k << std::endl;
412 std::cout << "f_k = " << f_k << std::endl;
413#endif
414
415 const Number eta_i = view_.kruzkov_entropy(k, u_i);
416 const Number q_i =
417 view_.kruzkov_entropy_derivative(k, u_i) * (f_i - f_k);
418
419 const Number eta_j = view_.kruzkov_entropy(k, u_j);
420 const Number q_j =
421 view_.kruzkov_entropy_derivative(k, u_j) * (f_j - f_k);
422
423 const Number a = u_i + u_j - ScalarNumber(2.) * k;
424 const Number b = f_j - f_i;
425 const Number c = eta_i + eta_j;
426 const Number d = q_j - q_i;
427
428 /*
429 * FIXME: Ordinarily, lambda_left and lambda_right would be
430 * computed without taking the absolute value of the numerator.
431 * (The denominator is - in the absence of rounding errors - always
432 * nonnegative. The numerator has a sign.)
433 * But empirically it turns out that taking the absolute value and
434 * letting both estimates participate in the maximal wavespeed
435 * estimate helps a lot.
436 */
437 const Number lambda_left = std::abs(d + b) / (std::abs(c + a) + h2);
438 const Number lambda_right = std::abs(d - b) / (std::abs(c - a) + h2);
439
440#ifdef DEBUG_WAVE_SPEED_ESTIMATOR
441 std::cout << " left wavespeed = " << lambda_left << std::endl;
442 std::cout << " right wavespeed = " << lambda_right << std::endl;
443#endif
444 lambda_max = std::max(lambda_max, lambda_left);
445 lambda_max = std::max(lambda_max, lambda_right);
446 };
447
448
449 if (use_averaged_entropy()) {
450 const Number k = ScalarNumber(0.5) * (u_i + u_j);
451 enforce_entropy(k);
452 }
453
454 const unsigned int n_entropies = random_entropies();
455 for (unsigned int i = 0; i < n_entropies; ++i) {
456 const Number factor = draw();
457 const Number k = factor * u_i + (Number(1.) - factor) * u_j;
458 enforce_entropy(k);
459 }
460 }
461
462#ifdef DEBUG_WAVE_SPEED_ESTIMATOR
463 std::cout << "-> lambda_max = " << lambda_max << std::endl;
464#endif
465 return lambda_max;
466 }
467
468
469 template <int dim, typename Number, typename MemorySpace>
470 DEAL_II_HOST_DEVICE_ALWAYS_INLINE Number
472 const PrecomputedVectorView &pv,
473 const state_type &U_i,
474 const state_type &U_j,
475 const unsigned int i,
476 const unsigned int *js,
477 const dealii::Tensor<1, dim, Number> &n_ij) const
478 {
479 using pst = typename View::precomputed_type;
480
481 const auto u_i = view_.state(U_i);
482 const auto u_j = view_.state(U_j);
483
484 const auto prec_i = pv.template read_tensor<Number, pst>(i);
485 const auto prec_j = pv.template read_tensor<Number, pst>(js);
486
487 return compute(u_i, u_j, prec_i, prec_j, n_ij);
488 }
489 } // namespace ScalarConservation
490} // namespace ryujin
dealii::Tensor< 1, problem_dimension, Number > state_type
std::array< Number, n_precomputed_values > precomputed_type
typename get_value_type< Number >::type ScalarNumber
VectorView< n_precomputed_values > PrecomputedVectorView
DEAL_II_HOST_DEVICE Number compute(const Number &u_i, const Number &u_j, const precomputed_type &prec_i, const precomputed_type &prec_j, const dealii::Tensor< 1, dim, Number > &n_ij) const
HyperbolicSystemView< dim, Number, MemorySpace > View
DEAL_II_HOST_DEVICE_ALWAYS_INLINE bool use_averaged_entropy() const
WaveSpeedEstimatorView(const View &view, const WaveSpeedEstimator< ScalarNumber > &wave_speed_estimator)
typename View::PrecomputedVectorView PrecomputedVectorView
DEAL_II_HOST_DEVICE_ALWAYS_INLINE bool use_greedy_wavespeed() const
DEAL_II_HOST_DEVICE_ALWAYS_INLINE unsigned int random_entropies() const
WaveSpeedEstimator(const HyperbolicSystem &hyperbolic_system, const std::string &subsection="/WaveSpeedEstimator")
TransferPolicy
Definition gpu.h:88