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