ryujin 2.1.1 revision 0348cbb53a3e4b1da2a4c037e81f88f2d21ce219
limiter.template.h
Go to the documentation of this file.
1//
2// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
3// Copyright (C) 2020 - 2024 by the ryujin authors
4//
5
6#pragma once
7
8#include "limiter.h"
9// #define DEBUG_OUTPUT_LIMITER
10
11namespace ryujin
12{
13 namespace Euler
14 {
15 template <int dim, typename Number>
16 std::tuple<Number, bool>
18 const state_type &U,
19 const state_type &P,
20 const Number t_min /* = Number(0.) */,
21 const Number t_max /* = Number(1.) */)
22 {
23 const auto view = hyperbolic_system.view<dim, Number>();
24
25 bool success = true;
26 Number t_r = t_max;
27
28 constexpr ScalarNumber eps = std::numeric_limits<ScalarNumber>::epsilon();
29 const auto small = view.vacuum_state_relaxation_small();
30 const auto large = view.vacuum_state_relaxation_large();
31 const ScalarNumber relax_small = ScalarNumber(1. + small * eps);
32 const ScalarNumber relax = ScalarNumber(1. + large * eps);
33
34 /*
35 * First limit the density rho.
36 *
37 * See [Guermond, Nazarov, Popov, Thomas] (4.8):
38 */
39
40 {
41 const auto &rho_U = view.density(U);
42 const auto &rho_P = view.density(P);
43
44 const auto &rho_min = std::get<0>(bounds);
45 const auto &rho_max = std::get<1>(bounds);
46
47 /*
48 * Verify that rho_U is within bounds. This property might be
49 * violated for relative CFL numbers larger than 1.
50 */
51 const auto test_min = view.filter_vacuum_density(
52 std::max(Number(0.), rho_U - relax * rho_max));
53 const auto test_max = view.filter_vacuum_density(
54 std::max(Number(0.), rho_min - relax * rho_U));
55 if (!(test_min == Number(0.) && test_max == Number(0.))) {
56#ifdef DEBUG_OUTPUT
57 std::cout << std::fixed << std::setprecision(16);
58 std::cout << "Bounds violation: low-order density (critical)!"
59 << "\n\t\trho min: " << rho_min
60 << "\n\t\trho min (delta): "
61 << negative_part(rho_U - rho_min)
62 << "\n\t\trho: " << rho_U
63 << "\n\t\trho max (delta): "
64 << positive_part(rho_U - rho_max)
65 << "\n\t\trho max: " << rho_max << "\n"
66 << std::endl;
67#endif
68 success = false;
69 }
70
71 const Number denominator =
72 ScalarNumber(1.) / (std::abs(rho_P) + eps * rho_max);
73
74 constexpr auto lt = dealii::SIMDComparison::less_than;
75
76 t_r = dealii::compare_and_apply_mask<lt>( //
77 rho_max,
78 rho_U + t_r * rho_P,
79 /*
80 * rho_P is positive.
81 *
82 * Note: Do not take an absolute value here. If we are out of
83 * bounds we have to ensure that t_r is set to t_min.
84 */
85 (rho_max - rho_U) * denominator,
86 t_r);
87
88 t_r = dealii::compare_and_apply_mask<lt>( //
89 rho_U + t_r * rho_P,
90 rho_min,
91 /*
92 * rho_P is negative.
93 *
94 * Note: Do not take an absolute value here. If we are out of
95 * bounds we have to ensure that t_r is set to t_min.
96 */
97 (rho_U - rho_min) * denominator,
98 t_r);
99
100 /*
101 * Ensure that t_min <= t <= t_max. This might not be the case if
102 * rho_U is outside the interval [rho_min, rho_max]. Furthermore,
103 * the quotient we take above is prone to numerical cancellation in
104 * particular in the second pass of the limiter when rho_P might be
105 * small.
106 */
107 t_r = std::min(t_r, t_max);
108 t_r = std::max(t_r, t_min);
109
110#ifdef EXPENSIVE_BOUNDS_CHECK
111 /*
112 * Verify that the new state is within bounds:
113 */
114 const auto rho_new = view.density(U + t_r * P);
115 const auto test_new_min = view.filter_vacuum_density(
116 std::max(Number(0.), rho_new - relax * rho_max));
117 const auto test_new_max = view.filter_vacuum_density(
118 std::max(Number(0.), rho_min - relax * rho_new));
119 if (!(test_new_min == Number(0.) && test_new_max == Number(0.))) {
120#ifdef DEBUG_OUTPUT
121 std::cout << std::fixed << std::setprecision(16);
122 std::cout << "Bounds violation: high-order density!"
123 << "\n\t\trho min: " << rho_min
124 << "\n\t\trho min (delta): "
125 << negative_part(rho_new - rho_min)
126 << "\n\t\trho: " << rho_new
127 << "\n\t\trho max (delta): "
128 << positive_part(rho_new - rho_max)
129 << "\n\t\trho max: " << rho_max << "\n"
130 << std::endl;
131#endif
132 success = false;
133 }
134#endif
135 }
136
137 /*
138 * Then limit the specific entropy:
139 *
140 * See [Guermond, Nazarov, Popov, Thomas], Section 4.6 + Section 5.1:
141 */
142
143 Number t_l = t_min; // good state
144
145 const ScalarNumber gamma = view.gamma();
146 const ScalarNumber gp1 = gamma + ScalarNumber(1.);
147
148 {
149 /*
150 * Prepare a quadratic Newton method:
151 *
152 * Given initial limiter values t_l and t_r with psi(t_l) > 0 and
153 * psi(t_r) < 0 we try to find t^\ast with psi(t^\ast) \approx 0.
154 *
155 * Here, psi is a 3-convex function obtained by scaling the specific
156 * entropy s:
157 *
158 * psi = \rho ^ {\gamma + 1} s
159 *
160 * (s in turn was defined as s =\varepsilon \rho ^{-\gamma}, where
161 * \varepsilon = (\rho e) is the internal energy.)
162 */
163
164 const auto &s_min = std::get<2>(bounds);
165
166#ifdef DEBUG_OUTPUT_LIMITER
167 std::cout << std::endl;
168 std::cout << std::fixed << std::setprecision(16);
169 std::cout << "t_l: (start) " << t_l << std::endl;
170 std::cout << "t_r: (start) " << t_r << std::endl;
171#endif
172
173 for (unsigned int n = 0; n < parameters.newton_max_iterations(); ++n) {
174
175 const auto U_r = U + t_r * P;
176 const auto rho_r = view.density(U_r);
177 const auto rho_r_gamma = ryujin::pow(rho_r, gamma);
178 const auto rho_e_r = view.internal_energy(U_r);
179
180 auto psi_r =
181 relax_small * rho_r * rho_e_r - s_min * rho_r * rho_r_gamma;
182
183#ifndef EXPENSIVE_BOUNDS_CHECK
184 /*
185 * If psi_r > 0 the right state is fine, force returning t_r by
186 * setting t_l = t_r:
187 */
188 t_l = dealii::compare_and_apply_mask<
189 dealii::SIMDComparison::greater_than>(
190 psi_r, Number(0.), t_r, t_l);
191
192 /*
193 * If we have set t_l = t_r everywhere then all states state U_r
194 * with t_r obey the specific entropy inequality and we can
195 * break.
196 *
197 * This is a very important optimization: Only for 1 in (25 to
198 * 50) cases do we actually need to limit on the specific entropy
199 * because one of the right states failed. So we can skip
200 * constructing the left state U_l, which is expensive.
201 *
202 * This implies unfortunately that we might not accurately report
203 * whether the low_order update U itself obeyed bounds because
204 * U_r = U + t_r * P pushed us back into bounds. We thus skip
205 * this shortcut if `EXPENSIVE_BOUNDS_CHECK` is set.
206 */
207 if (t_l == t_r) {
208#ifdef DEBUG_OUTPUT_LIMITER
209 std::cout << "shortcut: t_l == t_r" << std::endl;
210 std::cout << "psi_l: " << psi_l << std::endl;
211 std::cout << "psi_r: " << psi_r << std::endl;
212 std::cout << "t_l: ( " << n << " ) " << t_l << std::endl;
213 std::cout << "t_r: ( " << n << " ) " << t_r << std::endl;
214#endif
215 break;
216 }
217#endif
218
219 const auto U_l = U + t_l * P;
220 const auto rho_l = view.density(U_l);
221 const auto rho_l_gamma = ryujin::pow(rho_l, gamma);
222 const auto rho_e_l = view.internal_energy(U_l);
223
224 auto psi_l =
225 relax_small * rho_l * rho_e_l - s_min * rho_l * rho_l_gamma;
226
227 /*
228 * Verify that the left state is within bounds. This property might
229 * be violated for relative CFL numbers larger than 1.
230 */
231 const auto lower_bound =
232 (ScalarNumber(1.) - relax) * s_min * rho_l * rho_l_gamma;
233 if (n == 0 &&
234 !(std::min(Number(0.), psi_l - lower_bound) == Number(0.))) {
235#ifdef DEBUG_OUTPUT
236 std::cout << std::fixed << std::setprecision(16);
237 std::cout
238 << "Bounds violation: low-order specific entropy (critical)!\n";
239 std::cout << "\t\tPsi left: 0 <= " << psi_l << "\n" << std::endl;
240#endif
241 success = false;
242 }
243
244#ifdef EXPENSIVE_BOUNDS_CHECK
245 /*
246 * If psi_r > 0 the right state is fine, force returning t_r by
247 * setting t_l = t_r:
248 */
249 t_l = dealii::compare_and_apply_mask<
250 dealii::SIMDComparison::greater_than>(
251 psi_r, Number(0.), t_r, t_l);
252#endif
253
254 /*
255 * Break if the window between t_l and t_r is within the prescribed
256 * tolerance:
257 */
258 const Number tolerance(parameters.newton_tolerance());
259 if (std::max(Number(0.), t_r - t_l - tolerance) == Number(0.)) {
260#ifdef DEBUG_OUTPUT_LIMITER
261 std::cout << "break: t_l and t_r within tolerance" << std::endl;
262 std::cout << "psi_l: " << psi_l << std::endl;
263 std::cout << "psi_r: " << psi_r << std::endl;
264 std::cout << "t_l: ( " << n << " ) " << t_l << std::endl;
265 std::cout << "t_r: ( " << n << " ) " << t_r << std::endl;
266#endif
267 break;
268 }
269
270 /* We got unlucky and have to perform a Newton step: */
271
272 const auto drho = view.density(P);
273 const auto drho_e_l = view.internal_energy_derivative(U_l) * P;
274 const auto drho_e_r = view.internal_energy_derivative(U_r) * P;
275 const auto dpsi_l =
276 rho_l * drho_e_l + (rho_e_l - gp1 * s_min * rho_l_gamma) * drho;
277 const auto dpsi_r =
278 rho_r * drho_e_r + (rho_e_r - gp1 * s_min * rho_r_gamma) * drho;
279
281 t_l, t_r, psi_l, psi_r, dpsi_l, dpsi_r, Number(-1.));
282
283#ifdef DEBUG_OUTPUT_LIMITER
284 std::cout << "psi_l: " << psi_l << std::endl;
285 std::cout << "psi_r: " << psi_r << std::endl;
286 std::cout << "dpsi_l: " << dpsi_l << std::endl;
287 std::cout << "dpsi_r: " << dpsi_r << std::endl;
288 std::cout << "t_l: ( " << n << " ) " << t_l << std::endl;
289 std::cout << "t_r: ( " << n << " ) " << t_r << std::endl;
290#endif
291 }
292
293#ifdef EXPENSIVE_BOUNDS_CHECK
294 /*
295 * Verify that the new state is within bounds:
296 */
297 {
298 const auto U_new = U + t_l * P;
299 const auto rho_new = view.density(U_new);
300 const auto rho_new_gamma = ryujin::pow(rho_new, gamma);
301 const auto rho_e_new = view.internal_energy(U_new);
302
303 auto psi_new = relax_small * rho_new * rho_e_new -
304 s_min * rho_new * rho_new_gamma;
305
306 const auto lower_bound =
307 (ScalarNumber(1.) - relax) * s_min * rho_new * rho_new_gamma;
308
309 const bool e_valid = std::min(Number(0.), rho_e_new) == Number(0.);
310 const bool psi_valid =
311 std::min(Number(0.), psi_new - lower_bound) == Number(0.);
312
313 if (!e_valid || !psi_valid) {
314#ifdef DEBUG_OUTPUT
315 std::cout << std::fixed << std::setprecision(16);
316 std::cout << "Bounds violation: high-order specific entropy!\n";
317 std::cout << "\t\trho e: 0 <= " << rho_e_new << "\n";
318 std::cout << "\t\tPsi: 0 <= " << psi_new << "\n" << std::endl;
319#endif
320 success = false;
321 }
322 }
323#endif
324 }
325
326 return {t_l, success};
327 }
328
329 } // namespace Euler
330} // namespace ryujin
typename View::ScalarNumber ScalarNumber
Definition: limiter.h:106
typename View::state_type state_type
Definition: limiter.h:110
std::array< Number, n_bounds > Bounds
Definition: limiter.h:134
std::tuple< Number, bool > limit(const Bounds &bounds, const state_type &U, const state_type &P, const Number t_min=Number(0.), const Number t_max=Number(1.))
T pow(const T x, const T b)
DEAL_II_ALWAYS_INLINE Number negative_part(const Number number)
Definition: simd.h:124
DEAL_II_ALWAYS_INLINE Number positive_part(const Number number)
Definition: simd.h:112
DEAL_II_ALWAYS_INLINE void quadratic_newton_step(Number &p_1, Number &p_2, const Number phi_p_1, const Number phi_p_2, const Number dphi_p_1, const Number dphi_p_2, const Number sign=Number(1.0))
Definition: newton.h:39