Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
trixi-framework
GitHub Repository: trixi-framework/Trixi.jl
Path: blob/main/src/solvers/dgsem_tree/subcell_limiters_2d.jl
5591 views
1
# By default, Julia/LLVM does not use fused multiply-add operations (FMAs).
2
# Since these FMAs can increase the performance of many numerical algorithms,
3
# we need to opt-in explicitly.
4
# See https://ranocha.de/blog/Optimizing_EC_Trixi for further details.
5
@muladd begin
6
#! format: noindent
7
8
###############################################################################
9
# IDP Limiting
10
###############################################################################
11
12
###############################################################################
13
# Calculation of local bounds using low-order FV solution
14
15
@inline function calc_bounds_twosided!(var_min, var_max, variable,
16
u::AbstractArray{<:Any, 4}, t,
17
semi, equations)
18
mesh, _, dg, cache = mesh_equations_solver_cache(semi)
19
# Calc bounds inside elements
20
@threaded for element in eachelement(dg, cache)
21
# Calculate bounds at Gauss-Lobatto nodes
22
for j in eachnode(dg), i in eachnode(dg)
23
var = u[variable, i, j, element]
24
var_min[i, j, element] = var
25
var_max[i, j, element] = var
26
end
27
28
# Apply values in x direction
29
for j in eachnode(dg), i in 2:nnodes(dg)
30
var = u[variable, i - 1, j, element]
31
var_min[i, j, element] = min(var_min[i, j, element], var)
32
var_max[i, j, element] = max(var_max[i, j, element], var)
33
34
var = u[variable, i, j, element]
35
var_min[i - 1, j, element] = min(var_min[i - 1, j, element], var)
36
var_max[i - 1, j, element] = max(var_max[i - 1, j, element], var)
37
end
38
39
# Apply values in y direction
40
for j in 2:nnodes(dg), i in eachnode(dg)
41
var = u[variable, i, j - 1, element]
42
var_min[i, j, element] = min(var_min[i, j, element], var)
43
var_max[i, j, element] = max(var_max[i, j, element], var)
44
45
var = u[variable, i, j, element]
46
var_min[i, j - 1, element] = min(var_min[i, j - 1, element], var)
47
var_max[i, j - 1, element] = max(var_max[i, j - 1, element], var)
48
end
49
end
50
51
# Calc bounds at element interfaces and periodic boundaries
52
calc_bounds_twosided_interface!(var_min, var_max, variable, u,
53
semi, mesh, equations)
54
55
# Calc bounds at physical boundaries
56
(; boundary_conditions) = semi
57
calc_bounds_twosided_boundary!(var_min, var_max, variable, u, t,
58
boundary_conditions,
59
mesh, equations, dg, cache)
60
return nothing
61
end
62
63
@inline function calc_bounds_twosided_interface!(var_min, var_max, variable, u,
64
semi, mesh::TreeMesh2D, equations)
65
_, _, dg, cache = mesh_equations_solver_cache(semi)
66
67
for interface in eachinterface(dg, cache)
68
# Get neighboring element ids
69
left_element = cache.interfaces.neighbor_ids[1, interface]
70
right_element = cache.interfaces.neighbor_ids[2, interface]
71
72
orientation = cache.interfaces.orientations[interface]
73
74
for i in eachnode(dg)
75
# Define node indices for left and right element based on the interface orientation
76
if orientation == 1
77
index_left = (nnodes(dg), i)
78
index_right = (1, i)
79
else # if orientation == 2
80
index_left = (i, nnodes(dg))
81
index_right = (i, 1)
82
end
83
var_left = u[variable, index_left..., left_element]
84
var_right = u[variable, index_right..., right_element]
85
86
var_min[index_right..., right_element] = min(var_min[index_right...,
87
right_element],
88
var_left)
89
var_max[index_right..., right_element] = max(var_max[index_right...,
90
right_element],
91
var_left)
92
93
var_min[index_left..., left_element] = min(var_min[index_left...,
94
left_element], var_right)
95
var_max[index_left..., left_element] = max(var_max[index_left...,
96
left_element], var_right)
97
end
98
end
99
100
return nothing
101
end
102
103
@inline function calc_bounds_twosided_boundary!(var_min, var_max, variable, u, t,
104
boundary_conditions,
105
mesh::TreeMesh{2}, equations,
106
dg, cache)
107
for boundary in eachboundary(dg, cache)
108
element = cache.boundaries.neighbor_ids[boundary]
109
orientation = cache.boundaries.orientations[boundary]
110
neighbor_side = cache.boundaries.neighbor_sides[boundary]
111
112
for i in eachnode(dg)
113
if neighbor_side == 2 # Element is on the right, boundary on the left
114
node_index = (1, i)
115
boundary_index = 1
116
else # Element is on the left, boundary on the right
117
node_index = (nnodes(dg), i)
118
boundary_index = 2
119
end
120
if orientation == 2
121
node_index = reverse(node_index)
122
boundary_index += 2
123
end
124
u_inner = get_node_vars(u, equations, dg, node_index..., element)
125
u_outer = get_boundary_outer_state(u_inner, t,
126
boundary_conditions[boundary_index],
127
orientation, boundary_index,
128
mesh, equations, dg, cache,
129
node_index..., element)
130
var_outer = u_outer[variable]
131
132
var_min[node_index..., element] = min(var_min[node_index..., element],
133
var_outer)
134
var_max[node_index..., element] = max(var_max[node_index..., element],
135
var_outer)
136
end
137
end
138
139
return nothing
140
end
141
142
@inline function calc_bounds_onesided!(var_minmax, min_or_max, variable,
143
u::AbstractArray{<:Any, 4}, t,
144
semi)
145
mesh, equations, dg, cache = mesh_equations_solver_cache(semi)
146
147
# The approach used in `calc_bounds_twosided!` is not used here because it requires more
148
# evaluations of the variable and is therefore slower.
149
150
# Calc bounds inside elements
151
@threaded for element in eachelement(dg, cache)
152
# Reset bounds
153
for j in eachnode(dg), i in eachnode(dg)
154
if min_or_max === max
155
var_minmax[i, j, element] = typemin(eltype(var_minmax))
156
else
157
var_minmax[i, j, element] = typemax(eltype(var_minmax))
158
end
159
end
160
161
# Calculate bounds at Gauss-Lobatto nodes
162
for j in eachnode(dg), i in eachnode(dg)
163
var = variable(get_node_vars(u, equations, dg, i, j, element), equations)
164
var_minmax[i, j, element] = min_or_max(var_minmax[i, j, element], var)
165
166
if i > 1
167
var_minmax[i - 1, j, element] = min_or_max(var_minmax[i - 1, j,
168
element], var)
169
end
170
if i < nnodes(dg)
171
var_minmax[i + 1, j, element] = min_or_max(var_minmax[i + 1, j,
172
element], var)
173
end
174
if j > 1
175
var_minmax[i, j - 1, element] = min_or_max(var_minmax[i, j - 1,
176
element], var)
177
end
178
if j < nnodes(dg)
179
var_minmax[i, j + 1, element] = min_or_max(var_minmax[i, j + 1,
180
element], var)
181
end
182
end
183
end
184
185
# Calc bounds at element interfaces and periodic boundaries
186
calc_bounds_onesided_interface!(var_minmax, min_or_max, variable, u,
187
semi, mesh)
188
189
# Calc bounds at physical boundaries
190
(; boundary_conditions) = semi
191
calc_bounds_onesided_boundary!(var_minmax, min_or_max, variable, u, t,
192
boundary_conditions,
193
mesh, equations, dg, cache)
194
195
return nothing
196
end
197
198
@inline function calc_bounds_onesided_interface!(var_minmax, min_or_max, variable, u,
199
semi, mesh::TreeMesh2D)
200
_, equations, dg, cache = mesh_equations_solver_cache(semi)
201
202
for interface in eachinterface(dg, cache)
203
# Get neighboring element ids
204
left_element = cache.interfaces.neighbor_ids[1, interface]
205
right_element = cache.interfaces.neighbor_ids[2, interface]
206
207
orientation = cache.interfaces.orientations[interface]
208
209
for i in eachnode(dg)
210
# Define node indices for left and right element based on the interface orientation
211
if orientation == 1
212
index_left = (nnodes(dg), i)
213
index_right = (1, i)
214
else # if orientation == 2
215
index_left = (i, nnodes(dg))
216
index_right = (i, 1)
217
end
218
var_left = variable(get_node_vars(u, equations, dg, index_left...,
219
left_element),
220
equations)
221
var_right = variable(get_node_vars(u, equations, dg, index_right...,
222
right_element),
223
equations)
224
225
var_minmax[index_right..., right_element] = min_or_max(var_minmax[index_right...,
226
right_element],
227
var_left)
228
var_minmax[index_left..., left_element] = min_or_max(var_minmax[index_left...,
229
left_element],
230
var_right)
231
end
232
end
233
234
return nothing
235
end
236
237
@inline function calc_bounds_onesided_boundary!(var_minmax, min_or_max, variable, u, t,
238
boundary_conditions,
239
mesh::TreeMesh{2}, equations,
240
dg, cache)
241
for boundary in eachboundary(dg, cache)
242
element = cache.boundaries.neighbor_ids[boundary]
243
orientation = cache.boundaries.orientations[boundary]
244
neighbor_side = cache.boundaries.neighbor_sides[boundary]
245
246
for i in eachnode(dg)
247
if neighbor_side == 2 # Element is on the right, boundary on the left
248
node_index = (1, i)
249
boundary_index = 1
250
else # Element is on the left, boundary on the right
251
node_index = (nnodes(dg), i)
252
boundary_index = 2
253
end
254
if orientation == 2
255
node_index = reverse(node_index)
256
boundary_index += 2
257
end
258
u_inner = get_node_vars(u, equations, dg, node_index..., element)
259
u_outer = get_boundary_outer_state(u_inner, t,
260
boundary_conditions[boundary_index],
261
orientation, boundary_index,
262
mesh, equations, dg, cache,
263
node_index..., element)
264
var_outer = variable(u_outer, equations)
265
266
var_minmax[node_index..., element] = min_or_max(var_minmax[node_index...,
267
element],
268
var_outer)
269
end
270
end
271
272
return nothing
273
end
274
275
###############################################################################
276
# Local minimum and maximum limiting of conservative variables
277
278
@inline function idp_local_twosided!(alpha, limiter, u::AbstractArray{<:Any, 4},
279
t, dt, semi, variable)
280
mesh, equations, dg, cache = mesh_equations_solver_cache(semi)
281
(; antidiffusive_flux1_L, antidiffusive_flux2_L, antidiffusive_flux1_R, antidiffusive_flux2_R) = cache.antidiffusive_fluxes
282
(; inverse_weights) = dg.basis # Plays role of inverse DG-subcell sizes
283
284
(; variable_bounds) = limiter.cache.subcell_limiter_coefficients
285
variable_string = string(variable)
286
var_min = variable_bounds[Symbol(variable_string, "_min")]
287
var_max = variable_bounds[Symbol(variable_string, "_max")]
288
calc_bounds_twosided!(var_min, var_max, variable, u, t, semi, equations)
289
290
@threaded for element in eachelement(dg, semi.cache)
291
for j in eachnode(dg), i in eachnode(dg)
292
inverse_jacobian = get_inverse_jacobian(cache.elements.inverse_jacobian,
293
mesh, i, j, element)
294
var = u[variable, i, j, element]
295
# Real Zalesak type limiter
296
# * Zalesak (1979). "Fully multidimensional flux-corrected transport algorithms for fluids"
297
# * Kuzmin et al. (2010). "Failsafe flux limiting and constrained data projections for equations of gas dynamics"
298
# Note: The Zalesak limiter has to be computed, even if the state is valid, because the correction is
299
# for each interface, not each node
300
301
Qp = max(0, (var_max[i, j, element] - var) / dt)
302
Qm = min(0, (var_min[i, j, element] - var) / dt)
303
304
# Calculate Pp and Pm
305
# Note: Boundaries of antidiffusive_flux1/2 are constant 0, so they make no difference here.
306
val_flux1_local = inverse_weights[i] *
307
antidiffusive_flux1_R[variable, i, j, element]
308
val_flux1_local_ip1 = -inverse_weights[i] *
309
antidiffusive_flux1_L[variable, i + 1, j, element]
310
val_flux2_local = inverse_weights[j] *
311
antidiffusive_flux2_R[variable, i, j, element]
312
val_flux2_local_jp1 = -inverse_weights[j] *
313
antidiffusive_flux2_L[variable, i, j + 1, element]
314
315
Pp = max(0, val_flux1_local) + max(0, val_flux1_local_ip1) +
316
max(0, val_flux2_local) + max(0, val_flux2_local_jp1)
317
Pm = min(0, val_flux1_local) + min(0, val_flux1_local_ip1) +
318
min(0, val_flux2_local) + min(0, val_flux2_local_jp1)
319
320
Pp = inverse_jacobian * Pp
321
Pm = inverse_jacobian * Pm
322
323
# Compute blending coefficient avoiding division by zero
324
# (as in paper of [Guermond, Nazarov, Popov, Thomas] (4.8))
325
Qp = abs(Qp) /
326
(abs(Pp) + eps(typeof(Qp)) * 100 * abs(var_max[i, j, element]))
327
Qm = abs(Qm) /
328
(abs(Pm) + eps(typeof(Qm)) * 100 * abs(var_max[i, j, element]))
329
330
# Calculate alpha at nodes
331
alpha[i, j, element] = max(alpha[i, j, element], 1 - min(1, Qp, Qm))
332
end
333
end
334
335
return nothing
336
end
337
338
##############################################################################
339
# Local minimum or maximum limiting of nonlinear variables
340
341
@inline function idp_local_onesided!(alpha, limiter, u::AbstractArray{<:Real, 4},
342
t, dt, semi, variable, min_or_max)
343
mesh, equations, dg, cache = mesh_equations_solver_cache(semi)
344
(; variable_bounds) = limiter.cache.subcell_limiter_coefficients
345
var_minmax = variable_bounds[Symbol(string(variable), "_", string(min_or_max))]
346
calc_bounds_onesided!(var_minmax, min_or_max, variable, u, t, semi)
347
348
# Perform Newton's bisection method to find new alpha
349
@threaded for element in eachelement(dg, cache)
350
for j in eachnode(dg), i in eachnode(dg)
351
inverse_jacobian = get_inverse_jacobian(cache.elements.inverse_jacobian,
352
mesh, i, j, element)
353
u_local = get_node_vars(u, equations, dg, i, j, element)
354
newton_loops_alpha!(alpha, var_minmax[i, j, element], u_local,
355
i, j, element, variable, min_or_max,
356
initial_check_local_onesided_newton_idp,
357
final_check_local_onesided_newton_idp, inverse_jacobian,
358
dt, equations, dg, cache, limiter)
359
end
360
end
361
362
return nothing
363
end
364
365
###############################################################################
366
# Global positivity limiting of conservative variables
367
368
@inline function idp_positivity_conservative!(alpha, limiter,
369
u::AbstractArray{<:Real, 4},
370
dt, semi, variable)
371
mesh, _, dg, cache = mesh_equations_solver_cache(semi)
372
(; antidiffusive_flux1_L, antidiffusive_flux2_L, antidiffusive_flux1_R, antidiffusive_flux2_R) = cache.antidiffusive_fluxes
373
(; inverse_weights) = dg.basis
374
(; positivity_correction_factor) = limiter
375
376
(; variable_bounds) = limiter.cache.subcell_limiter_coefficients
377
var_min = variable_bounds[Symbol(string(variable), "_min")]
378
379
@threaded for element in eachelement(dg, semi.cache)
380
for j in eachnode(dg), i in eachnode(dg)
381
inverse_jacobian = get_inverse_jacobian(cache.elements.inverse_jacobian,
382
mesh, i, j, element)
383
var = u[variable, i, j, element]
384
if var < 0
385
error("Safe low-order method produces negative value for conservative variable $variable. Try a smaller time step.")
386
end
387
388
# Compute bound
389
if limiter.local_twosided &&
390
(variable in limiter.local_twosided_variables_cons) &&
391
(var_min[i, j, element] >= positivity_correction_factor * var)
392
# Local limiting is more restrictive that positivity limiting
393
# => Skip positivity limiting for this node
394
continue
395
end
396
var_min[i, j, element] = positivity_correction_factor * var
397
398
# Real one-sided Zalesak-type limiter
399
# * Zalesak (1979). "Fully multidimensional flux-corrected transport algorithms for fluids"
400
# * Kuzmin et al. (2010). "Failsafe flux limiting and constrained data projections for equations of gas dynamics"
401
# Note: The Zalesak limiter has to be computed, even if the state is valid, because the correction is
402
# for each interface, not each node
403
Qm = min(0, (var_min[i, j, element] - var) / dt)
404
405
# Calculate Pm
406
# Note: Boundaries of antidiffusive_flux1/2 are constant 0, so they make no difference here.
407
val_flux1_local = inverse_weights[i] *
408
antidiffusive_flux1_R[variable, i, j, element]
409
val_flux1_local_ip1 = -inverse_weights[i] *
410
antidiffusive_flux1_L[variable, i + 1, j, element]
411
val_flux2_local = inverse_weights[j] *
412
antidiffusive_flux2_R[variable, i, j, element]
413
val_flux2_local_jp1 = -inverse_weights[j] *
414
antidiffusive_flux2_L[variable, i, j + 1, element]
415
416
Pm = min(0, val_flux1_local) + min(0, val_flux1_local_ip1) +
417
min(0, val_flux2_local) + min(0, val_flux2_local_jp1)
418
Pm = inverse_jacobian * Pm
419
420
# Compute blending coefficient avoiding division by zero
421
# (as in paper of [Guermond, Nazarov, Popov, Thomas] (4.8))
422
Qm = abs(Qm) / (abs(Pm) + eps(typeof(Qm)) * 100)
423
424
# Calculate alpha
425
alpha[i, j, element] = max(alpha[i, j, element], 1 - Qm)
426
end
427
end
428
429
return nothing
430
end
431
432
###############################################################################
433
# Global positivity limiting of nonlinear variables
434
435
@inline function idp_positivity_nonlinear!(alpha, limiter,
436
u::AbstractArray{<:Real, 4},
437
dt, semi, variable)
438
mesh, equations, dg, cache = mesh_equations_solver_cache(semi)
439
(; positivity_correction_factor) = limiter
440
441
(; variable_bounds) = limiter.cache.subcell_limiter_coefficients
442
var_min = variable_bounds[Symbol(string(variable), "_min")]
443
444
@threaded for element in eachelement(dg, semi.cache)
445
for j in eachnode(dg), i in eachnode(dg)
446
inverse_jacobian = get_inverse_jacobian(cache.elements.inverse_jacobian,
447
mesh, i, j, element)
448
449
# Compute bound
450
u_local = get_node_vars(u, equations, dg, i, j, element)
451
var = variable(u_local, equations)
452
if var < 0
453
error("Safe low-order method produces negative value for variable $variable. Try a smaller time step.")
454
end
455
var_min[i, j, element] = positivity_correction_factor * var
456
457
# Perform Newton's bisection method to find new alpha
458
newton_loops_alpha!(alpha, var_min[i, j, element], u_local, i, j, element,
459
variable, min, initial_check_nonnegative_newton_idp,
460
final_check_nonnegative_newton_idp, inverse_jacobian,
461
dt, equations, dg, cache, limiter)
462
end
463
end
464
465
return nothing
466
end
467
468
###############################################################################
469
# Newton-bisection method
470
471
@inline function newton_loops_alpha!(alpha, bound, u, i, j, element,
472
variable, min_or_max,
473
initial_check, final_check,
474
inverse_jacobian, dt,
475
equations::AbstractEquations{2},
476
dg, cache, limiter)
477
(; inverse_weights) = dg.basis # Plays role of inverse DG-subcell sizes
478
(; antidiffusive_flux1_L, antidiffusive_flux2_L, antidiffusive_flux1_R, antidiffusive_flux2_R) = cache.antidiffusive_fluxes
479
480
(; gamma_constant_newton) = limiter
481
482
indices = (i, j, element)
483
484
# negative xi direction
485
antidiffusive_flux = gamma_constant_newton * inverse_jacobian * inverse_weights[i] *
486
get_node_vars(antidiffusive_flux1_R, equations, dg,
487
i, j, element)
488
newton_loop!(alpha, bound, u, indices, variable, min_or_max, initial_check,
489
final_check, equations, dt, limiter, antidiffusive_flux)
490
491
# positive xi direction
492
antidiffusive_flux = -gamma_constant_newton * inverse_jacobian *
493
inverse_weights[i] *
494
get_node_vars(antidiffusive_flux1_L, equations, dg,
495
i + 1, j, element)
496
newton_loop!(alpha, bound, u, indices, variable, min_or_max, initial_check,
497
final_check, equations, dt, limiter, antidiffusive_flux)
498
499
# negative eta direction
500
antidiffusive_flux = gamma_constant_newton * inverse_jacobian * inverse_weights[j] *
501
get_node_vars(antidiffusive_flux2_R, equations, dg,
502
i, j, element)
503
newton_loop!(alpha, bound, u, indices, variable, min_or_max, initial_check,
504
final_check, equations, dt, limiter, antidiffusive_flux)
505
506
# positive eta direction
507
antidiffusive_flux = -gamma_constant_newton * inverse_jacobian *
508
inverse_weights[j] *
509
get_node_vars(antidiffusive_flux2_L, equations, dg,
510
i, j + 1, element)
511
newton_loop!(alpha, bound, u, indices, variable, min_or_max, initial_check,
512
final_check, equations, dt, limiter, antidiffusive_flux)
513
514
return nothing
515
end
516
end # @muladd
517
518