ryujin 2.1.1 revision 0b194b984a74af675d09b5e928529ca8c7b634f2
limiter.template.h
Go to the documentation of this file.
1//
2// SPDX-License-Identifier: Apache-2.0
3// [LANL Copyright Statement]
4// Copyright (C) 2022 - 2024 by the ryujin authors
5// Copyright (C) 2023 - 2024 by Triad National Security, LLC
6//
7
8#pragma once
9
10#include "limiter.h"
11
12namespace ryujin
13{
14 namespace ShallowWater
15 {
16 template <int dim, typename Number>
17 std::tuple<Number, bool>
19 const state_type &U,
20 const state_type &P,
21 const Number t_min /* = Number(0.) */,
22 const Number t_max /* = Number(1.) */)
23 {
24 const auto view = hyperbolic_system.view<dim, Number>();
25
26 bool success = true;
27 Number t_l = t_min;
28 Number t_r = t_max;
29
30 const auto &[h_min, h_max, h_small, kin_max, v2_max] = bounds;
31
32 constexpr ScalarNumber min = std::numeric_limits<ScalarNumber>::min();
33 constexpr ScalarNumber eps = std::numeric_limits<ScalarNumber>::epsilon();
34 const auto small = view.dry_state_relaxation_small();
35 const auto large = view.dry_state_relaxation_large();
36 const auto relax_small = ScalarNumber(1. + small * eps);
37 const auto relax = ScalarNumber(1. + large * eps);
38
39 /*
40 * We first limit the water_depth h.
41 *
42 * See [Guermond et al, 2021] (5.7).
43 */
44
45 {
46 auto h_U = view.water_depth(U);
47 const auto &h_P = view.water_depth(P);
48
49 const auto test_min = view.filter_dry_water_depth(
50 std::max(Number(0.), h_U - relax * h_max));
51 const auto test_max = view.filter_dry_water_depth(
52 std::max(Number(0.), h_min - relax * h_U));
53
54 if (!(test_min == Number(0.) && test_max == Number(0.))) {
55#ifdef DEBUG_OUTPUT
56 std::cout << std::fixed << std::setprecision(16);
57 std::cout << "Bounds violation: low-order water depth (critical)!\n"
58 << "\n\t\th min: " << h_min
59 << "\n\t\th min (delta): " << negative_part(h_U - h_min)
60 << "\n\t\th: " << h_U
61 << "\n\t\th max (delta): " << positive_part(h_U - h_max)
62 << "\n\t\th max: " << h_max << "\n"
63 << std::endl;
64#endif
65 success = false;
66 }
67
68 const Number denominator =
69 ScalarNumber(1.) / (std::abs(h_P) + eps * h_max + min);
70
71 constexpr auto lt = dealii::SIMDComparison::less_than;
72
73 t_r = dealii::compare_and_apply_mask<lt>( //
74 h_max,
75 h_U + t_r * h_P,
76 /*
77 * h_P is positive.
78 *
79 * Note: Do not take an absolute value here. If we are out of
80 * bounds we have to ensure that t_r is set to t_min.
81 */
82 (h_max - h_U) * denominator,
83 t_r);
84
85 /*
86 * Enforce strict limiting on dry states (i.e., if we happen to get
87 * below h_small):
88 */
89 const auto h_min_tilde = std::max(h_small, h_min);
90
91 t_r = dealii::compare_and_apply_mask<lt>( //
92 h_U + t_r * h_P,
93 h_min_tilde,
94 /*
95 * h_P is negative.
96 *
97 * Note: Do not take an absolute value here. If we are out of
98 * bounds we have to ensure that t_r is set to t_min.
99 */
100 (h_U - h_min_tilde) * denominator,
101 t_r);
102
103 /*
104 * Ensure that t_min <= t <= t_max. This might not be the case if
105 * h_U is outside the interval [h_min, h_max]. Furthermore, the
106 * quotient we take above is prone to numerical cancellation in
107 * particular in the second pass of the limiter when h_P might be
108 * small.
109 */
110 t_r = std::min(t_r, t_max);
111 t_r = std::max(t_r, t_min);
112
113
114#ifdef CHECK_BOUNDS
115 /*
116 * Verify that the new state is within bounds:
117 */
118 const auto h_new = view.water_depth(U + t_r * P);
119 const auto test_new_min = view.filter_dry_water_depth(
120 std::max(Number(0.), h_new - relax * h_max));
121 const auto test_new_max = view.filter_dry_water_depth(
122 std::max(Number(0.), h_min - relax * h_new));
123
124 if (!(test_new_min == Number(0.) && test_new_max == Number(0.))) {
125#ifdef DEBUG_OUTPUT
126 std::cout << std::fixed << std::setprecision(30);
127 std::cout << "Bounds violation: high-order water depth!\n"
128 << "\n\t\th min: " << h_min
129 << "\n\t\th min (delta): " << negative_part(h_new - h_min)
130 << "\n\t\th: " << h_new
131 << "\n\t\th max (delta): " << positive_part(h_new - h_max)
132 << "\n\t\th max: " << h_max << "\n"
133 << std::endl;
134#endif
135 success = false;
136 }
137#endif
138 }
139
140 /*
141 * Return early if we neither limit on kinetic energy or on square
142 * velocity.
143 */
144 if (RYUJIN_UNLIKELY(!parameters.limit_on_square_velocity() &&
145 !parameters.limit_on_kinetic_energy()))
146 return {t_l, success};
147
148 /*
149 * Limit the (negative) kinetic energy:
150 *
151 * See [Guermond et al, 2021] (5.10).
152 *
153 * Given initial limiter values t_l and t_r with psi(t_l) > 0 and
154 * psi(t_r) < 0 we try to find t^\ast with psi(t^\ast) \approx 0.
155 *
156 * Here, psi is the function:
157 *
158 * psi = h KE_i^max - 1/2 |q|^2
159 */
160
161 if (parameters.limit_on_kinetic_energy()) {
162
163 /* We first check if t_r is a good state */
164
165 const auto U_r = U + t_r * P;
166 const auto h_r = view.water_depth(U_r);
167 const auto q_r = view.momentum(U_r);
168
169 const auto psi_r =
170 relax_small * h_r * kin_max - ScalarNumber(0.5) * q_r.norm_square();
171
172 /*
173 * If psi_r > 0 the right state is fine, force returning t_r by
174 * setting t_l = t_r:
175 */
176 t_l = dealii::compare_and_apply_mask<
177 dealii::SIMDComparison::greater_than>(psi_r, Number(0.), t_r, t_l);
178
179 /* If we have set t_l = t_r everywhere we can return: */
180 if (!parameters.limit_on_square_velocity() && t_l == t_r)
181 return {t_l, success};
182
183#ifdef DEBUG_OUTPUT_LIMITER
184 {
185 std::cout << std::endl;
186 std::cout << std::fixed << std::setprecision(16);
187 std::cout << "t_l: (start) " << t_l << std::endl;
188 std::cout << "t_r: (start) " << t_r << std::endl;
189 }
190#endif
191
192 const auto U_l = U + t_l * P;
193 const auto h_l = view.water_depth(U_l);
194 const auto q_l = view.momentum(U_l);
195
196 const auto psi_l =
197 relax_small * h_l * kin_max - ScalarNumber(0.5) * q_l.norm_square();
198
199 /*
200 * Verify that the left state is within bounds. This property might
201 * be violated for relative CFL numbers larger than 1.
202 *
203 * We use a non-scaled "+min" here to force the lower_bound to be
204 * negative so that we do not accidentally trigger in "perfect" dry
205 * states with h_l equal to zero.
206 */
207 const auto filtered_h_l = view.filter_dry_water_depth(h_l);
208 const auto lower_bound =
209 (ScalarNumber(1.) - relax) * filtered_h_l * kin_max - eps;
210 if (!(std::min(Number(0.), psi_l - lower_bound) == Number(0.))) {
211#ifdef DEBUG_OUTPUT
212 std::cout << std::fixed << std::setprecision(16);
213 std::cout
214 << "Bounds violation: low-order kinetic energy (critical)!\n";
215 std::cout << "\t\tPsi left: 0 <= " << psi_l << "\n" << std::endl;
216#endif
217 success = false;
218 }
219
220 /*
221 * Skip the quadratic Newton step if the window between t_l and t_r
222 * is within the prescribed tolerance:
223 */
224 const Number tolerance(parameters.newton_tolerance());
225 if (!(std::max(Number(0.), t_r - t_l - tolerance) == Number(0.))) {
226 /*
227 * If the bound is not satisfied, we need to find the root of a
228 * quadratic function:
229 *
230 * psi(t) = (h_U + t h_P) kin_max
231 * - 1/2 (|q_U|^2 + 2(q_U * q_P) t + |q_P|^2 t^2)
232 *
233 * d_psi(t) = h_P kin_max - (q_U * q_P) - |q_P|^2 t
234 *
235 * We can compute the root of this function efficiently by using our
236 * standard quadratic_newton_step() function that will use the points
237 * [p1, p1, p2] as well as [p1, p2, p2] to construct two quadratic
238 * polynomials to compute new candiates for the bounds [t_l, t_r]. In
239 * case of a quadratic function psi(t) both polynomials will coincide
240 * so that (up to round-off error) t_l = t_r.
241 */
242
243 const auto &h_P = view.water_depth(P);
244 const auto &q_U = view.momentum(U);
245 const auto &q_P = view.momentum(P);
246
247 const auto dpsi_l = h_P * kin_max - (q_U * q_P) - q_P * q_P * t_l;
248 const auto dpsi_r = h_P * kin_max - (q_U * q_P) - q_P * q_P * t_r;
249
251 t_l, t_r, psi_l, psi_r, dpsi_l, dpsi_r, Number(-1.));
252
253#ifdef DEBUG_OUTPUT_LIMITER
254 if (std::max(Number(0.), psi_r + Number(eps)) == Number(0.)) {
255 std::cout << "psi_l: " << psi_l << std::endl;
256 std::cout << "psi_r: " << psi_r << std::endl;
257 std::cout << "dpsi_l: " << dpsi_l << std::endl;
258 std::cout << "dpsi_r: " << dpsi_r << std::endl;
259 std::cout << "t_l: (end) " << t_l << std::endl;
260 std::cout << "t_r: (end) " << t_r << std::endl;
261 }
262#endif
263 }
264
265#ifdef CHECK_BOUNDS
266 /*
267 * Verify that the new state is within bounds:
268 */
269 {
270 const auto U_new = U + t_l * P;
271 const auto h_new = view.water_depth(U_new);
272 const auto q_new = view.momentum(U_new);
273
274 const auto psi_new = relax_small * h_new * kin_max -
275 ScalarNumber(0.5) * q_new.norm_square();
276
277 const auto lower_bound =
278 (ScalarNumber(1.) - relax) * h_new * kin_max - eps;
279
280 const bool psi_valid =
281 std::min(Number(0.), psi_new - lower_bound) == Number(0.);
282 if (!psi_valid) {
283#ifdef DEBUG_OUTPUT
284 std::cout << std::fixed << std::setprecision(16);
285 std::cout << "Bounds violation: high-order kinetic energy!\n";
286 std::cout << "\t\tPsi: 0 <= " << psi_new << "\n" << std::endl;
287#endif
288 success = false;
289 }
290 }
291#endif
292
293 /* Flip bounds for the square velocity limiter: */
294 if (parameters.limit_on_square_velocity()) {
295 t_r = t_l;
296 t_l = t_min;
297 }
298 }
299
300 /*
301 * Limit the (negative) |v|^2:
302 *
303 * Given initial limiter values t_l and t_r with psi(t_l) > 0 and
304 * psi(t_r) < 0 we try to find t^\ast with psi(t^\ast) \approx 0.
305 *
306 * Here, psi is the function:
307 *
308 * psi = h^2 (|v|^2)^max - |q|^2
309 */
310
311 if (parameters.limit_on_square_velocity()) {
312 /* We first check if t_r is a good state */
313
314 const auto U_r = U + t_r * P;
315 const auto h_r = view.water_depth(U_r);
316 const auto q_r = view.momentum(U_r);
317
318 const auto psi_r = relax_small * h_r * h_r * v2_max - q_r.norm_square();
319
320 /*
321 * If psi_r > 0 the right state is fine, force returning t_r by
322 * setting t_l = t_r:
323 */
324 t_l = dealii::compare_and_apply_mask<
325 dealii::SIMDComparison::greater_than>(psi_r, Number(0.), t_r, t_l);
326
327 /* If we have set t_l = t_r everywhere we can return: */
328 if (t_l == t_r)
329 return {t_l, success};
330
331#ifdef DEBUG_OUTPUT_LIMITER
332 {
333 std::cout << std::endl;
334 std::cout << std::fixed << std::setprecision(16);
335 std::cout << "t_l: (start) " << t_l << std::endl;
336 std::cout << "t_r: (start) " << t_r << std::endl;
337 }
338#endif
339
340 const auto U_l = U + t_l * P;
341 const auto h_l = view.water_depth(U_l);
342 const auto q_l = view.momentum(U_l);
343
344 const auto psi_l = relax_small * h_l * h_l * v2_max - q_l.norm_square();
345
346 /*
347 * Verify that the left state is within bounds. This property might
348 * be violated for relative CFL numbers larger than 1.
349 *
350 * We use a non-scaled eps here to force the lower_bound to be
351 * negative so that we do not accidentally trigger in "perfect" dry
352 * states with h_l equal to zero.
353 */
354 const auto filtered_h_l = view.filter_dry_water_depth(h_l);
355 const auto lower_bound =
356 (ScalarNumber(1.) - relax) * filtered_h_l * filtered_h_l * v2_max -
357 ScalarNumber(100.) * eps;
358 if (!(std::min(Number(0.), psi_l - lower_bound) == Number(0.))) {
359#ifdef DEBUG_OUTPUT
360 std::cout << std::fixed << std::setprecision(16);
361 std::cout
362 << "Bounds violation: low-order square velocity (critical)!\n";
363 std::cout << "\t\tPsi left: 0 <= " << psi_l << "\n" << std::endl;
364#endif
365 success = false;
366 }
367
368 /*
369 * Skip the quadratic Newton step if the window between t_l and t_r
370 * is within the prescribed tolerance:
371 */
372 const Number tolerance(parameters.newton_tolerance());
373 if (!(std::max(Number(0.), t_r - t_l - tolerance) == Number(0.))) {
374 /*
375 * If the bound is not satisfied, we need to find the root of a
376 * quadratic function:
377 *
378 * psi(t) = (h_U + t h_P)^2 v2_max
379 * - (|q_U|^2 + 2(q_U * q_P) t + |q_P|^2 t^2)
380 *
381 * d_psi(t) = 2 (h_U + t * h_P) * h_P v2_max
382 * - 2 (q_U * q_P) - |q_P|^2 t
383 *
384 * We can compute the root of this function efficiently by using our
385 * standard quadratic_newton_step() function that will use the points
386 * [p1, p1, p2] as well as [p1, p2, p2] to construct two quadratic
387 * polynomials to compute new candiates for the bounds [t_l, t_r]. In
388 * case of a quadratic function psi(t) both polynomials will coincide
389 * so that (up to round-off error) t_l = t_r.
390 */
391 const auto &h_U = view.water_depth(U);
392 const auto &h_P = view.water_depth(P);
393 const auto &q_U = view.momentum(U);
394 const auto &q_P = view.momentum(P);
395
396 const auto dpsi_l =
397 (h_U + t_l * h_P) * h_P * v2_max -
398 ScalarNumber(2.) * ((q_U * q_P) - q_P * q_P * t_l);
399 const auto dpsi_r =
400 (h_U + t_r * h_P) * h_P * v2_max -
401 ScalarNumber(2.) * ((q_U * q_P) - q_P * q_P * t_r);
402
404 t_l, t_r, psi_l, psi_r, dpsi_l, dpsi_r, Number(-1.));
405
406#ifdef DEBUG_OUTPUT_LIMITER
407 if (std::max(Number(0.), psi_r + Number(eps)) == Number(0.)) {
408 std::cout << "psi_l: " << psi_l << std::endl;
409 std::cout << "psi_r: " << psi_r << std::endl;
410 std::cout << "dpsi_l: " << dpsi_l << std::endl;
411 std::cout << "dpsi_r: " << dpsi_r << std::endl;
412 std::cout << "t_l: (end) " << t_l << std::endl;
413 std::cout << "t_r: (end) " << t_r << std::endl;
414 }
415#endif
416 }
417
418#ifdef CHECK_BOUNDS
419 /*
420 * Verify that the new state is within bounds:
421 */
422 {
423 const auto U_new = U + t_l * P;
424 const auto h_new = view.water_depth(U_new);
425 const auto q_new = view.momentum(U_new);
426
427 const auto psi_new =
428 relax_small * h_new * h_new * v2_max - q_new.norm_square();
429
430 const auto lower_bound =
431 (ScalarNumber(1.) - relax) * h_new * h_new * v2_max -
432 ScalarNumber(100.) * eps;
433
434 const bool psi_valid =
435 std::min(Number(0.), psi_new - lower_bound) == Number(0.);
436 if (!psi_valid) {
437#ifdef DEBUG_OUTPUT
438 std::cout << std::fixed << std::setprecision(16);
439 std::cout << "Bounds violation: high-order square velocity!\n";
440 std::cout << "\t\tPsi: 0 <= " << psi_new << "\n" << std::endl;
441#endif
442 success = false;
443 }
444 }
445#endif
446 }
447
448 return {t_l, success};
449 }
450
451 } // namespace ShallowWater
452} // namespace ryujin
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.))
typename View::ScalarNumber ScalarNumber
Definition: limiter.h:97
std::array< Number, n_bounds > Bounds
Definition: limiter.h:139
typename View::state_type state_type
Definition: limiter.h:101
#define RYUJIN_UNLIKELY(x)
Definition: openmp.h:132
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