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.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
abstract type AbstractSubcellLimiter end
9
10
function create_cache(typ::Type{LimiterType},
11
semi) where {LimiterType <: AbstractSubcellLimiter}
12
return create_cache(typ, mesh_equations_solver_cache(semi)...)
13
end
14
15
"""
16
SubcellLimiterIDP(equations::AbstractEquations, basis;
17
local_twosided_variables_cons = String[],
18
positivity_variables_cons = String[],
19
positivity_variables_nonlinear = [],
20
positivity_correction_factor = 0.1,
21
local_onesided_variables_nonlinear = [],
22
max_iterations_newton = 10,
23
newton_tolerances = (1.0e-12, 1.0e-14),
24
gamma_constant_newton = 2 * ndims(equations))
25
26
Subcell invariant domain preserving (IDP) limiting used with [`VolumeIntegralSubcellLimiting`](@ref)
27
including:
28
- Local two-sided Zalesak-type limiting for conservative variables (`local_twosided_variables_cons`)
29
- Positivity limiting for conservative variables (`positivity_variables_cons`) and nonlinear variables
30
(`positivity_variables_nonlinear`)
31
- Local one-sided limiting for nonlinear variables, e.g. [`entropy_guermond_etal`](@ref) and [`entropy_math`](@ref)
32
with `local_onesided_variables_nonlinear`
33
34
To use these three limiting options use the following structure:
35
36
***Conservative variables*** to be limited are passed as a vector of strings, e.g.
37
`local_twosided_variables_cons = ["rho"]` and `positivity_variables_cons = ["rho"]`.
38
For ***nonlinear variables***, the wanted variable functions are passed within a vector: To ensure
39
positivity use a plain vector including the desired variables, e.g. `positivity_variables_nonlinear = [pressure]`.
40
For local one-sided limiting pass the variable function combined with the requested bound
41
(`min` or `max`) as a tuple. For instance, to impose a lower local bound on the modified specific
42
entropy by Guermond et al. use `local_onesided_variables_nonlinear = [(entropy_guermond_etal, min)]`.
43
44
The bounds are calculated using the low-order FV solution. The positivity limiter uses
45
`positivity_correction_factor` such that `u^new >= positivity_correction_factor * u^FV`.
46
Local and global limiting of nonlinear variables uses a Newton-bisection method with a maximum of
47
`max_iterations_newton` iterations, relative and absolute tolerances of `newton_tolerances`
48
and a provisional update constant `gamma_constant_newton` (`gamma_constant_newton>=2*d`,
49
where `d = #dimensions`). See equation (20) of Pazner (2020) and equation (30) of Rueda-Ramírez et al. (2022).
50
51
!!! note
52
This limiter and the correction callback [`SubcellLimiterIDPCorrection`](@ref) only work together.
53
Without the callback, no correction takes place, leading to a standard low-order FV scheme.
54
55
Implementation in 3D:
56
In 3D, only the positivity limiter for conservative variables using
57
(`positivity_variables_cons`) is implemented and merged for `P4estMesh`.
58
`BoundsCheckCallback` is not supported in 3D yet.
59
More features will follow soon.
60
61
## References
62
63
- Rueda-Ramírez, Pazner, Gassner (2022)
64
Subcell Limiting Strategies for Discontinuous Galerkin Spectral Element Methods
65
[DOI: 10.1016/j.compfluid.2022.105627](https://doi.org/10.1016/j.compfluid.2022.105627)
66
- Pazner (2020)
67
Sparse invariant domain preserving discontinuous Galerkin methods with subcell convex limiting
68
[DOI: 10.1016/j.cma.2021.113876](https://doi.org/10.1016/j.cma.2021.113876)
69
"""
70
struct SubcellLimiterIDP{RealT <: Real, LimitingVariablesNonlinear,
71
LimitingOnesidedVariablesNonlinear, Cache} <:
72
AbstractSubcellLimiter
73
local_twosided::Bool
74
local_twosided_variables_cons::Vector{Int} # Local two-sided limiting for conservative variables
75
positivity::Bool
76
positivity_variables_cons::Vector{Int} # Positivity for conservative variables
77
positivity_variables_nonlinear::LimitingVariablesNonlinear # Positivity for nonlinear variables
78
positivity_correction_factor::RealT
79
local_onesided::Bool
80
local_onesided_variables_nonlinear::LimitingOnesidedVariablesNonlinear # Local one-sided limiting for nonlinear variables
81
cache::Cache
82
max_iterations_newton::Int
83
newton_tolerances::Tuple{RealT, RealT} # Relative and absolute tolerances for Newton's method
84
gamma_constant_newton::RealT # Constant for the subcell limiting of convex (nonlinear) constraints
85
end
86
87
# this method is used when the limiter is constructed as for shock-capturing volume integrals
88
function SubcellLimiterIDP(equations::AbstractEquations, basis;
89
local_twosided_variables_cons = String[],
90
positivity_variables_cons = String[],
91
positivity_variables_nonlinear = [],
92
positivity_correction_factor = 0.1,
93
local_onesided_variables_nonlinear = [],
94
max_iterations_newton = 10,
95
newton_tolerances = (1.0e-12, 1.0e-14),
96
gamma_constant_newton = 2 * ndims(equations))
97
local_twosided = (length(local_twosided_variables_cons) > 0)
98
local_onesided = (length(local_onesided_variables_nonlinear) > 0)
99
positivity = (length(positivity_variables_cons) +
100
length(positivity_variables_nonlinear) > 0)
101
102
# When passing `min` or `max` in the elixir, the specific function of Base is used.
103
# To speed up the simulation, we replace it with `Trixi.min` and `Trixi.max` respectively.
104
local_onesided_variables_nonlinear_ = Tuple{Function, Function}[]
105
for (variable, min_or_max) in local_onesided_variables_nonlinear
106
if min_or_max === Base.max
107
push!(local_onesided_variables_nonlinear_, (variable, max))
108
elseif min_or_max === Base.min
109
push!(local_onesided_variables_nonlinear_, (variable, min))
110
elseif min_or_max === Trixi.max || min_or_max === Trixi.min
111
push!(local_onesided_variables_nonlinear_, (variable, min_or_max))
112
else
113
error("Parameter $min_or_max is not a valid input. Use `max` or `min` instead.")
114
end
115
end
116
local_onesided_variables_nonlinear_ = Tuple(local_onesided_variables_nonlinear_)
117
positivity_variables_nonlinear = Tuple(positivity_variables_nonlinear)
118
119
local_twosided_variables_cons_ = get_variable_index.(local_twosided_variables_cons,
120
equations)
121
positivity_variables_cons_ = get_variable_index.(positivity_variables_cons,
122
equations)
123
124
bound_keys = ()
125
if local_twosided
126
for v in local_twosided_variables_cons_
127
v_string = string(v)
128
bound_keys = (bound_keys..., Symbol(v_string, "_min"),
129
Symbol(v_string, "_max"))
130
end
131
end
132
if local_onesided
133
for (variable, min_or_max) in local_onesided_variables_nonlinear_
134
bound_keys = (bound_keys...,
135
Symbol(string(variable), "_", string(min_or_max)))
136
end
137
end
138
for v in positivity_variables_cons_
139
if !(v in local_twosided_variables_cons_)
140
bound_keys = (bound_keys..., Symbol(string(v), "_min"))
141
end
142
end
143
for variable in positivity_variables_nonlinear
144
bound_keys = (bound_keys..., Symbol(string(variable), "_min"))
145
end
146
147
cache = create_cache(SubcellLimiterIDP, equations, basis, bound_keys)
148
149
return SubcellLimiterIDP{typeof(positivity_correction_factor),
150
typeof(positivity_variables_nonlinear),
151
typeof(local_onesided_variables_nonlinear_),
152
typeof(cache)}(local_twosided,
153
local_twosided_variables_cons_,
154
positivity, positivity_variables_cons_,
155
positivity_variables_nonlinear,
156
positivity_correction_factor,
157
local_onesided,
158
local_onesided_variables_nonlinear_,
159
cache,
160
max_iterations_newton, newton_tolerances,
161
gamma_constant_newton)
162
end
163
164
function Base.show(io::IO, limiter::SubcellLimiterIDP)
165
@nospecialize limiter # reduce precompilation time
166
(; local_twosided, positivity, local_onesided) = limiter
167
168
print(io, "SubcellLimiterIDP(")
169
if !(local_twosided || positivity || local_onesided)
170
print(io, "No limiter selected => pure DG method")
171
else
172
features = String[]
173
if local_twosided
174
push!(features, "local min/max")
175
end
176
if positivity
177
push!(features, "positivity")
178
end
179
if local_onesided
180
push!(features, "local onesided")
181
end
182
join(io, features, ", ")
183
print(io, "Limiter=($features), ")
184
end
185
print(io, "Local bounds with FV solution")
186
print(io, ")")
187
return nothing
188
end
189
190
function Base.show(io::IO, ::MIME"text/plain", limiter::SubcellLimiterIDP)
191
@nospecialize limiter # reduce precompilation time
192
(; local_twosided, positivity, local_onesided) = limiter
193
194
if get(io, :compact, false)
195
show(io, limiter)
196
else
197
if !(local_twosided || positivity || local_onesided)
198
setup = ["Limiter" => "No limiter selected => pure DG method"]
199
else
200
setup = ["Limiter" => ""]
201
if local_twosided
202
push!(setup,
203
"" => "Local two-sided limiting for conservative variables $(limiter.local_twosided_variables_cons)")
204
end
205
if positivity
206
if !isempty(limiter.positivity_variables_cons)
207
string = "conservative variables $(limiter.positivity_variables_cons)"
208
push!(setup, "" => "Positivity limiting for " * string)
209
end
210
if !isempty(limiter.positivity_variables_nonlinear)
211
string = "$(limiter.positivity_variables_nonlinear)"
212
push!(setup, "" => "Positivity limiting for " * string)
213
end
214
push!(setup,
215
"" => "- with positivity correction factor = $(limiter.positivity_correction_factor)")
216
end
217
if local_onesided
218
for (variable, min_or_max) in limiter.local_onesided_variables_nonlinear
219
push!(setup, "" => "Local $min_or_max limiting for $variable")
220
end
221
end
222
push!(setup, "Local bounds" => "FV solution")
223
end
224
summary_box(io, "SubcellLimiterIDP", setup)
225
end
226
end
227
228
# this method is used when the limiter is constructed as for shock-capturing volume integrals
229
function create_cache(limiter::Type{SubcellLimiterIDP},
230
equations::AbstractEquations{NDIMS},
231
basis::LobattoLegendreBasis, bound_keys) where {NDIMS}
232
# The number of elements is not yet known here. So, we initialize the container with 0 elements
233
# and resize it later while creating the cache for the volume integral.
234
subcell_limiter_coefficients = Trixi.ContainerSubcellLimiterIDP{NDIMS, real(basis)}(0,
235
nnodes(basis),
236
bound_keys)
237
238
# Memory for bounds checking routine with `BoundsCheckCallback`.
239
# Local variable contains the maximum deviation since the last export.
240
idp_bounds_delta_local = Dict{Symbol, real(basis)}()
241
# Global variable contains the total maximum deviation.
242
idp_bounds_delta_global = Dict{Symbol, real(basis)}()
243
for key in bound_keys
244
idp_bounds_delta_local[key] = zero(real(basis))
245
idp_bounds_delta_global[key] = zero(real(basis))
246
end
247
248
return (; subcell_limiter_coefficients, idp_bounds_delta_local,
249
idp_bounds_delta_global)
250
end
251
252
function resize_subcell_limiter_cache!(limiter::SubcellLimiterIDP, new_size)
253
resize!(limiter.cache.subcell_limiter_coefficients, new_size)
254
255
return nothing
256
end
257
258
# While for the element-wise limiting with `VolumeIntegralShockCapturingHG` the indicator is
259
# called here to get up-to-date values for IO, this is not easily possible in this case
260
# because the calculation is very integrated into the method.
261
# See also https://github.com/trixi-framework/Trixi.jl/pull/1611#discussion_r1334553206.
262
# Therefore, the coefficients at `t=t^{n-1}` are saved. Thus, the coefficients of the first
263
# stored solution (initial condition) are not yet defined and were manually set to `NaN`.
264
function get_node_variable(::Val{:limiting_coefficient}, u, mesh, equations, dg, cache)
265
return dg.volume_integral.limiter.cache.subcell_limiter_coefficients.alpha
266
end
267
function get_node_variable(::Val{:limiting_coefficient}, u, mesh, equations, dg, cache,
268
equations_parabolic, cache_parabolic)
269
return get_node_variable(Val(:limiting_coefficient), u, mesh, equations, dg, cache)
270
end
271
272
function (limiter::SubcellLimiterIDP)(u, semi, equations, dg::DGSEM,
273
t, dt;
274
kwargs...)
275
@unpack alpha = limiter.cache.subcell_limiter_coefficients
276
@trixi_timeit timer() "reset alpha" set_zero!(alpha, dg, semi.cache)
277
278
if limiter.local_twosided
279
@trixi_timeit timer() "local twosided" idp_local_twosided!(alpha, limiter,
280
u, t, dt, semi)
281
end
282
if limiter.positivity
283
@trixi_timeit timer() "positivity" idp_positivity!(alpha, limiter, u, dt, semi)
284
end
285
if limiter.local_onesided
286
@trixi_timeit timer() "local onesided" idp_local_onesided!(alpha, limiter,
287
u, t, dt, semi)
288
end
289
290
return nothing
291
end
292
293
###############################################################################
294
# Local minimum and maximum limiting (conservative variables)
295
296
@inline function idp_local_twosided!(alpha, limiter, u, t, dt, semi)
297
for variable in limiter.local_twosided_variables_cons
298
idp_local_twosided!(alpha, limiter, u, t, dt, semi, variable)
299
end
300
301
return nothing
302
end
303
304
##############################################################################
305
# Local minimum or maximum limiting (nonlinear variables)
306
307
@inline function idp_local_onesided!(alpha, limiter, u, t, dt, semi)
308
for (variable, min_or_max) in limiter.local_onesided_variables_nonlinear
309
idp_local_onesided!(alpha, limiter, u, t, dt, semi, variable, min_or_max)
310
end
311
312
return nothing
313
end
314
315
###############################################################################
316
# Global positivity limiting (conservative and nonlinear variables)
317
318
@inline function idp_positivity!(alpha, limiter, u, dt, semi)
319
# Conservative variables
320
@trixi_timeit timer() "conservative variables" for variable in limiter.positivity_variables_cons
321
idp_positivity_conservative!(alpha, limiter, u, dt, semi, variable)
322
end
323
324
# Nonlinear variables
325
@trixi_timeit timer() "nonlinear variables" for variable in limiter.positivity_variables_nonlinear
326
idp_positivity_nonlinear!(alpha, limiter, u, dt, semi, variable)
327
end
328
329
return nothing
330
end
331
332
###############################################################################
333
# Newton-bisection method
334
335
@inline function newton_loop!(alpha, bound, u, indices, variable, min_or_max,
336
initial_check, final_check,
337
equations, dt, limiter, antidiffusive_flux)
338
newton_reltol, newton_abstol = limiter.newton_tolerances
339
340
beta = 1 - alpha[indices...]
341
342
beta_L = 0 # alpha = 1
343
beta_R = beta # No higher beta (lower alpha) than the current one
344
345
u_curr = u + beta * dt * antidiffusive_flux
346
347
# If state is valid, perform initial check and return if correction is not needed
348
if isvalid(u_curr, equations)
349
goal = goal_function_newton_idp(variable, bound, u_curr, equations)
350
351
initial_check(min_or_max, bound, goal, newton_abstol) && return nothing
352
end
353
354
# Newton iterations
355
for iter in 1:(limiter.max_iterations_newton)
356
beta_old = beta
357
358
# If the state is valid, evaluate d(goal)/d(beta)
359
if isvalid(u_curr, equations)
360
dgoal_dbeta = dgoal_function_newton_idp(variable, u_curr, dt,
361
antidiffusive_flux, equations)
362
else # Otherwise, perform a bisection step
363
dgoal_dbeta = 0
364
end
365
366
if dgoal_dbeta != 0
367
# Update beta with Newton's method
368
beta = beta - goal / dgoal_dbeta
369
end
370
371
# Check bounds
372
if (beta < beta_L) || (beta > beta_R) || (dgoal_dbeta == 0) || isnan(beta)
373
# Out of bounds, do a bisection step
374
beta = 0.5f0 * (beta_L + beta_R)
375
# Get new u
376
u_curr = u + beta * dt * antidiffusive_flux
377
378
# If the state is invalid, finish bisection step without checking tolerance and iterate further
379
if !isvalid(u_curr, equations)
380
beta_R = beta
381
continue
382
end
383
384
# Check new beta for condition and update bounds
385
goal = goal_function_newton_idp(variable, bound, u_curr, equations)
386
if initial_check(min_or_max, bound, goal, newton_abstol)
387
# New beta fulfills condition
388
beta_L = beta
389
else
390
# New beta does not fulfill condition
391
beta_R = beta
392
end
393
else
394
# Get new u
395
u_curr = u + beta * dt * antidiffusive_flux
396
397
# If the state is invalid, redefine right bound without checking tolerance and iterate further
398
if !isvalid(u_curr, equations)
399
beta_R = beta
400
continue
401
end
402
403
# Evaluate goal function
404
goal = goal_function_newton_idp(variable, bound, u_curr, equations)
405
end
406
407
# Check relative tolerance
408
if abs(beta_old - beta) <= newton_reltol
409
break
410
end
411
412
# Check absolute tolerance
413
if final_check(bound, goal, newton_abstol)
414
break
415
end
416
end
417
418
alpha[indices...] = 1 - beta # new alpha
419
420
return nothing
421
end
422
423
### Auxiliary routines for Newton's bisection method ###
424
# Initial checks
425
@inline function initial_check_local_onesided_newton_idp(::typeof(min), bound,
426
goal, newton_abstol)
427
return goal <= max(newton_abstol, abs(bound) * newton_abstol)
428
end
429
430
@inline function initial_check_local_onesided_newton_idp(::typeof(max), bound,
431
goal, newton_abstol)
432
return goal >= -max(newton_abstol, abs(bound) * newton_abstol)
433
end
434
435
@inline initial_check_nonnegative_newton_idp(min_or_max, bound, goal, newton_abstol) = goal <=
436
0
437
438
# Goal and d(Goal)/d(u) function
439
@inline goal_function_newton_idp(variable, bound, u, equations) = bound -
440
variable(u, equations)
441
@inline function dgoal_function_newton_idp(variable, u, dt, antidiffusive_flux,
442
equations)
443
return -dot(gradient_conservative(variable, u, equations), dt * antidiffusive_flux)
444
end
445
446
# Final checks
447
# final check for one-sided local limiting
448
@inline function final_check_local_onesided_newton_idp(bound, goal, newton_abstol)
449
return abs(goal) < max(newton_abstol, abs(bound) * newton_abstol)
450
end
451
452
# final check for nonnegativity limiting
453
@inline function final_check_nonnegative_newton_idp(bound, goal, newton_abstol)
454
return (goal <= eps()) && (goal > -max(newton_abstol, abs(bound) * newton_abstol))
455
end
456
457
###############################################################################
458
# Auxiliary routine `get_boundary_outer_state` for non-periodic domains
459
460
"""
461
get_boundary_outer_state(u_inner, t,
462
boundary_condition::BoundaryConditionDirichlet,
463
orientation_or_normal, direction,
464
mesh, equations, dg, cache, indices...)
465
For subcell limiting, the calculation of local bounds for non-periodic domains requires the boundary
466
outer state. This function returns the boundary value for [`BoundaryConditionDirichlet`](@ref) at
467
time `t` and for node with spatial indices `indices` at the boundary with `orientation_or_normal`
468
and `direction`.
469
470
Should be used together with [`TreeMesh`](@ref) or [`StructuredMesh`](@ref).
471
472
!!! warning "Experimental implementation"
473
This is an experimental feature and may change in future releases.
474
"""
475
@inline function get_boundary_outer_state(u_inner, t,
476
boundary_condition::BoundaryConditionDirichlet,
477
orientation_or_normal, direction,
478
mesh::Union{TreeMesh, StructuredMesh},
479
equations, dg, cache, indices...)
480
(; node_coordinates) = cache.elements
481
482
x = get_node_coords(node_coordinates, equations, dg, indices...)
483
u_outer = boundary_condition.boundary_value_function(x, t, equations)
484
485
return u_outer
486
end
487
end # @muladd
488
489