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