Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
trixi-framework
GitHub Repository: trixi-framework/Trixi.jl
Path: blob/main/src/solvers/dg.jl
5586 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 AbstractVolumeIntegral end
9
10
function get_element_variables!(element_variables, u, mesh, equations,
11
volume_integral::AbstractVolumeIntegral, dg, cache)
12
return nothing
13
end
14
15
# Function to define "element variables" for the SaveSolutionCallback. It does
16
# nothing by default, but can be specialized for certain mesh types. For instance,
17
# parallel meshes output the mpi rank as an "element variable".
18
function get_element_variables!(element_variables, mesh, dg, cache)
19
return nothing
20
end
21
22
### Functions to define `node variables` for the `SaveSolutionCallback`. ###
23
24
# Abstract function which is to be overwritten for the specific `node_variable`
25
# in e.g. the elixirs.
26
function get_node_variable end
27
28
# Version for (purely) hyperbolic equations.
29
function get_node_variables!(node_variables, u_ode, mesh, equations,
30
dg, cache)
31
if !isempty(node_variables)
32
u = wrap_array(u_ode, mesh, equations, dg, cache)
33
for var in keys(node_variables)
34
node_variables[var] = get_node_variable(Val(var), u, mesh, equations,
35
dg, cache)
36
end
37
end
38
39
return nothing
40
end
41
42
# Version for purely parabolic equations (adds cache_parabolic).
43
function get_node_variables!(node_variables, u_ode, mesh, equations,
44
dg, cache, cache_parabolic)
45
if !isempty(node_variables)
46
u = wrap_array(u_ode, mesh, equations, dg, cache)
47
for var in keys(node_variables)
48
node_variables[var] = get_node_variable(Val(var), u, mesh, equations,
49
dg, cache, cache_parabolic)
50
end
51
end
52
53
return nothing
54
end
55
56
# Version for hyperbolic-parabolic equations (adds equations_parabolic and cache_parabolic).
57
function get_node_variables!(node_variables, u_ode, mesh, equations,
58
dg, cache,
59
equations_parabolic, cache_parabolic)
60
if !isempty(node_variables)
61
u = wrap_array(u_ode, mesh, equations, dg, cache)
62
for var in keys(node_variables)
63
node_variables[var] = get_node_variable(Val(var), u, mesh, equations,
64
dg, cache,
65
equations_parabolic,
66
cache_parabolic)
67
end
68
end
69
70
return nothing
71
end
72
73
"""
74
VolumeIntegralStrongForm()
75
76
The classical strong form volume integral type for FD/DG methods.
77
"""
78
struct VolumeIntegralStrongForm <: AbstractVolumeIntegral end
79
80
"""
81
VolumeIntegralWeakForm()
82
83
The classical weak form volume integral type for DG methods as explained in
84
standard textbooks.
85
86
## References
87
88
- Kopriva (2009)
89
Implementing Spectral Methods for Partial Differential Equations:
90
Algorithms for Scientists and Engineers
91
[doi: 10.1007/978-90-481-2261-5](https://doi.org/10.1007/978-90-481-2261-5)
92
- Hesthaven, Warburton (2007)
93
Nodal Discontinuous Galerkin Methods: Algorithms, Analysis, and
94
Applications
95
[doi: 10.1007/978-0-387-72067-8](https://doi.org/10.1007/978-0-387-72067-8)
96
97
`VolumeIntegralWeakForm()` is only implemented for conserved terms as
98
non-conservative terms should always be discretized in conjunction with a flux-splitting scheme,
99
see [`VolumeIntegralFluxDifferencing`](@ref).
100
This treatment is required to achieve, e.g., entropy-stability or well-balancedness.
101
"""
102
struct VolumeIntegralWeakForm <: AbstractVolumeIntegral end
103
104
create_cache(mesh, equations, ::VolumeIntegralWeakForm, dg, uEltype) = NamedTuple()
105
106
"""
107
VolumeIntegralFluxDifferencing(volume_flux)
108
VolumeIntegralFluxDifferencing(; volume_flux = flux_central)
109
110
Volume integral type for DG methods based on SBP operators and flux differencing
111
using a symmetric two-point `volume_flux`. This `volume_flux` needs to satisfy
112
the interface of numerical fluxes in Trixi.jl.
113
114
## References
115
116
- LeFloch, Mercier, Rohde (2002)
117
Fully Discrete, Entropy Conservative Schemes of Arbitrary Order
118
[doi: 10.1137/S003614290240069X](https://doi.org/10.1137/S003614290240069X)
119
- Fisher, Carpenter (2013)
120
High-order entropy stable finite difference schemes for nonlinear
121
conservation laws: Finite domains
122
[doi: 10.1016/j.jcp.2013.06.014](https://doi.org/10.1016/j.jcp.2013.06.014)
123
- Hendrik Ranocha (2017)
124
Comparison of Some Entropy Conservative Numerical Fluxes for the Euler Equations
125
[arXiv: 1701.02264](https://arxiv.org/abs/1701.02264)
126
[doi: 10.1007/s10915-017-0618-1](https://doi.org/10.1007/s10915-017-0618-1)
127
- Chen, Shu (2017)
128
Entropy stable high order discontinuous Galerkin methods with suitable
129
quadrature rules for hyperbolic conservation laws
130
[doi: 10.1016/j.jcp.2017.05.025](https://doi.org/10.1016/j.jcp.2017.05.025)
131
"""
132
struct VolumeIntegralFluxDifferencing{VolumeFlux} <: AbstractVolumeIntegral
133
volume_flux::VolumeFlux
134
end
135
136
function VolumeIntegralFluxDifferencing(; volume_flux = flux_central)
137
return VolumeIntegralFluxDifferencing{typeof(volume_flux)}(volume_flux)
138
end
139
140
function Base.show(io::IO, ::MIME"text/plain", integral::VolumeIntegralFluxDifferencing)
141
@nospecialize integral # reduce precompilation time
142
143
if get(io, :compact, false)
144
show(io, integral)
145
else
146
setup = [
147
"volume flux" => integral.volume_flux
148
]
149
summary_box(io, "VolumeIntegralFluxDifferencing", setup)
150
end
151
end
152
153
create_cache(mesh, equations, ::VolumeIntegralFluxDifferencing, dg, uEltype) = NamedTuple()
154
155
# Abstract supertype for DG subcell-based volume integrals with
156
# finite volume schemes on the subcells.
157
abstract type AbstractVolumeIntegralSubcell <: AbstractVolumeIntegral end
158
abstract type AbstractVolumeIntegralShockCapturing <: AbstractVolumeIntegralSubcell end
159
160
function create_cache_subcell_limiting(mesh, equations,
161
volume_integral::AbstractVolumeIntegralSubcell,
162
dg, cache_containers, uEltype)
163
return NamedTuple()
164
end
165
166
struct VolumeIntegralShockCapturingHGType{Indicator, VolumeIntegralDefault,
167
VolumeIntegralBlendHighOrder,
168
VolumeIntegralBlendLowOrder} <:
169
AbstractVolumeIntegralShockCapturing
170
# A-priori indicator to determine the amount of blending (if any)
171
# between the high-order and low-order volume integrals.
172
indicator::Indicator
173
174
# In classic HG shock capturing this is also `VolumeIntegralBlendHighOrder`.
175
# This implementation is a generalization, which allows also usage of e.g.
176
# the (potentially) cheaper weak form volume integral.
177
volume_integral_default::VolumeIntegralDefault
178
179
# The volume integral used for the DG portion in the convex blending.
180
# Usually symmetric, e.g. split-form with entropy-conserative flux.
181
volume_integral_blend_high_order::VolumeIntegralBlendHighOrder
182
183
# Typically a first- or second-order finite volume method on the DG subcells.
184
# Non-symmetric, e.g. entropy-dissipative, to achieve shock-capturing behaviour.
185
volume_integral_blend_low_order::VolumeIntegralBlendLowOrder
186
end
187
188
"""
189
VolumeIntegralShockCapturingHG(indicator;
190
volume_flux_dg=flux_central,
191
volume_flux_fv=flux_lax_friedrichs)
192
193
Shock-capturing volume integral type for DG methods using a convex blending of
194
the **first-order** finite volume method with numerical flux `volume_flux_fv` and the
195
[`VolumeIntegralFluxDifferencing`](@ref) with volume flux `volume_flux_dg`.
196
The amount of blending is determined by the `indicator`, e.g.,
197
[`IndicatorHennemannGassner`](@ref).
198
199
## References
200
201
- Hennemann, Gassner (2020)
202
"A provably entropy stable subcell shock capturing approach for high order split form DG"
203
[arXiv: 2008.12044](https://arxiv.org/abs/2008.12044)
204
"""
205
function VolumeIntegralShockCapturingHG(indicator;
206
volume_flux_dg = flux_central,
207
volume_flux_fv = flux_lax_friedrichs)
208
volume_integral_fd = VolumeIntegralFluxDifferencing(volume_flux_dg)
209
volume_integral_fv = VolumeIntegralPureLGLFiniteVolume(volume_flux_fv)
210
211
return VolumeIntegralShockCapturingHGType{typeof(indicator),
212
typeof(volume_integral_fd),
213
typeof(volume_integral_fd),
214
typeof(volume_integral_fv)}(indicator,
215
volume_integral_fd,
216
volume_integral_fd,
217
volume_integral_fv)
218
end
219
220
"""
221
VolumeIntegralShockCapturingRRG(basis, indicator;
222
volume_flux_dg=flux_central,
223
volume_flux_fv=flux_lax_friedrichs,
224
slope_limiter=minmod,
225
cons2recon=cons2prim,
226
recon2cons=prim2cons)
227
228
Shock-capturing volume integral type for DG methods using a convex blending of
229
a **second-order** finite volume method with numerical flux `volume_flux_fv` and
230
slope limiter `slope_limiter` and the
231
[`VolumeIntegralFluxDifferencing`](@ref) with volume flux `volume_flux_dg`.
232
The amount of blending is determined by the `indicator`, e.g.,
233
[`IndicatorHennemannGassner`](@ref).
234
235
**Symmetric** total-Variation-Diminishing (TVD) choices for the `slope_limiter` are
236
1) [`minmod`](@ref)
237
2) [`monotonized_central`](@ref)
238
3) [`superbee`](@ref)
239
4) [`vanleer`](@ref)
240
5) [`koren_symmetric`](@ref)
241
**Asymmetric** TVD limiters are also available, e.g.,
242
1) [`koren`](@ref) for positive (right-going) velocities
243
2) [`koren_flipped`](@ref) for negative (left-going) velocities
244
245
The reconstruction is performed in reconstruction variables, which default to the primitive variables.
246
Other choices are possible, e.g., thermodynamic variables, see [`cons2thermo`](@ref) and [`thermo2cons`](@ref)
247
for [`NonIdealCompressibleEulerEquations1D`](@ref) and [`NonIdealCompressibleEulerEquations2D`](@ref).
248
249
!!! note "Conservative Systems only"
250
Currently only implemented for systems in conservative form, i.e.,
251
`have_nonconservative_terms(equations) = False()`
252
253
## References
254
255
See especially Section 3.2, Section 4, and Appendix D of the paper
256
257
- Rueda-Ramírez, Hennemann, Hindenlang, Winters, & Gassner (2021).
258
"An entropy stable nodal discontinuous Galerkin method for the resistive MHD equations.
259
Part II: Subcell finite volume shock capturing"
260
[JCP: 2021.110580](https://doi.org/10.1016/j.jcp.2021.110580)
261
"""
262
function VolumeIntegralShockCapturingRRG(basis, indicator;
263
volume_flux_dg = flux_central,
264
volume_flux_fv = flux_lax_friedrichs,
265
slope_limiter = minmod,
266
cons2recon = cons2prim,
267
recon2cons = prim2cons)
268
volume_integral_fd = VolumeIntegralFluxDifferencing(volume_flux_dg)
269
270
# `reconstruction_mode` is hard-coded to `reconstruction_O2_inner` to
271
# achieve shock-capturing behaviour. See the paper mentioned above for details.
272
volume_integral_fv = VolumeIntegralPureLGLFiniteVolumeO2(basis;
273
volume_flux_fv = volume_flux_fv,
274
reconstruction_mode = reconstruction_O2_inner,
275
slope_limiter = slope_limiter,
276
cons2recon = cons2recon,
277
recon2cons = recon2cons)
278
return VolumeIntegralShockCapturingHGType{typeof(indicator),
279
typeof(volume_integral_fd),
280
typeof(volume_integral_fd),
281
typeof(volume_integral_fv)}(indicator,
282
volume_integral_fd,
283
volume_integral_fd,
284
volume_integral_fv)
285
end
286
287
"""
288
VolumeIntegralShockCapturingHGType(indicator;
289
volume_integral_default,
290
volume_integral_blend_high_order,
291
volume_integral_blend_low_order)
292
293
Generalized Henneman-Gassner a-priori shock-capturing volume integral for DG methods.
294
Works naturally with the a-priori [`IndicatorHennemannGassner`](@ref) `indicator`.
295
296
In the non-stabilized region, `volume_integral_default` is used,
297
which is typically a high-order accurate volume integral such as [`VolumeIntegralWeakForm`](@ref)
298
or [`VolumeIntegralFluxDifferencing`](@ref).
299
300
The volume integral used for the DG portion in the convex blending `volume_integral_blend_high_order` is blended with
301
the `volume_integral_blend_low_order` to achieve shock-capturing behaviour.
302
This is typically a symmetric, entropy-conservative volume integral such as [`VolumeIntegralFluxDifferencing`](@ref),
303
but [`VolumeIntegralWeakForm`](@ref) can be used (in principle) as well.
304
305
Finally, the `volume_integral_blend_low_order` should be entropy-dissipative, for instance
306
[`VolumeIntegralPureLGLFiniteVolume`](@ref) or [`VolumeIntegralPureLGLFiniteVolumeO2`](@ref).
307
308
## References
309
310
- Hennemann, Gassner (2020)
311
"A provably entropy stable subcell shock capturing approach for high order split form DG"
312
[arXiv: 2008.12044](https://arxiv.org/abs/2008.12044)
313
314
- Rueda-Ramírez, Hennemann, Hindenlang, Winters, & Gassner (2021).
315
"An entropy stable nodal discontinuous Galerkin method for the resistive MHD equations.
316
Part II: Subcell finite volume shock capturing"
317
[JCP: 2021.110580](https://doi.org/10.1016/j.jcp.2021.110580)
318
"""
319
function VolumeIntegralShockCapturingHGType(indicator;
320
volume_integral_default,
321
volume_integral_blend_high_order,
322
volume_integral_blend_low_order)
323
return VolumeIntegralShockCapturingHGType{typeof(indicator),
324
typeof(volume_integral_default),
325
typeof(volume_integral_blend_high_order),
326
typeof(volume_integral_blend_low_order)}(indicator,
327
volume_integral_default,
328
volume_integral_blend_high_order,
329
volume_integral_blend_low_order)
330
end
331
332
function Base.show(io::IO, mime::MIME"text/plain",
333
integral::VolumeIntegralShockCapturingHGType)
334
@nospecialize integral # reduce precompilation time
335
@unpack volume_integral_default, volume_integral_blend_high_order, volume_integral_blend_low_order, indicator = integral
336
337
if get(io, :compact, false)
338
show(io, integral)
339
else
340
summary_header(io, "VolumeIntegralShockCapturingHGType")
341
342
summary_line(io, "volume integral DG default",
343
volume_integral_default |> typeof |> nameof)
344
if !(volume_integral_default isa VolumeIntegralWeakForm)
345
show(increment_indent(io), mime, volume_integral_default)
346
end
347
348
summary_line(io, "volume integral DG blend",
349
volume_integral_blend_high_order |> typeof |> nameof)
350
show(increment_indent(io), mime, volume_integral_blend_high_order)
351
352
summary_line(io, "volume integral FV",
353
volume_integral_blend_low_order |> typeof |> nameof)
354
show(increment_indent(io), mime, volume_integral_blend_low_order)
355
356
summary_line(io, "indicator", indicator |> typeof |> nameof)
357
show(increment_indent(io), mime, indicator)
358
359
summary_footer(io)
360
end
361
end
362
363
function get_element_variables!(element_variables, u, mesh, equations,
364
volume_integral::AbstractVolumeIntegralShockCapturing,
365
dg, cache)
366
# call the indicator to get up-to-date values for IO
367
volume_integral.indicator(u, mesh, equations, dg, cache)
368
return get_element_variables!(element_variables, volume_integral.indicator,
369
volume_integral)
370
end
371
372
# `resize_volume_integral_cache!` is called after mesh adaptation in `reinitialize_containers!`.
373
# Default `nothing` required for dispatch
374
function resize_volume_integral_cache!(cache, mesh,
375
volume_integral::AbstractVolumeIntegral,
376
new_size)
377
return nothing
378
end
379
# `AbstractVolumeIntegralSubcell` require resizing of the subcell normal vectors for
380
# non-Cartesian meshes
381
function resize_volume_integral_cache!(cache, mesh,
382
volume_integral::AbstractVolumeIntegralSubcell,
383
new_size)
384
resize_normal_vectors!(cache, mesh, new_size)
385
386
return nothing
387
end
388
function resize_volume_integral_cache!(cache, mesh,
389
volume_integral::VolumeIntegralShockCapturingHGType,
390
new_size)
391
@unpack (volume_integral_default,
392
volume_integral_blend_high_order, volume_integral_blend_low_order) = volume_integral
393
resize_volume_integral_cache!(cache, mesh, volume_integral_default,
394
new_size)
395
resize_volume_integral_cache!(cache, mesh, volume_integral_blend_high_order,
396
new_size)
397
resize_volume_integral_cache!(cache, mesh, volume_integral_blend_low_order,
398
new_size)
399
400
return nothing
401
end
402
403
# `reinit_volume_integral_cache!` is called after mesh adaptation in `reinitialize_containers!`.
404
function reinit_volume_integral_cache!(cache, mesh, dg,
405
volume_integral::AbstractVolumeIntegral,
406
new_size)
407
return nothing
408
end
409
# `AbstractVolumeIntegralSubcell` require reinitializing of the subcell normal vectors for
410
# non-Cartesian meshes
411
function reinit_volume_integral_cache!(cache, mesh, dg,
412
volume_integral::AbstractVolumeIntegralSubcell,
413
new_size)
414
reinit_normal_vectors!(cache, mesh, dg)
415
416
return nothing
417
end
418
function reinit_volume_integral_cache!(cache, mesh, dg,
419
volume_integral::VolumeIntegralShockCapturingHGType,
420
new_size)
421
@unpack (volume_integral_default,
422
volume_integral_blend_high_order, volume_integral_blend_low_order) = volume_integral
423
reinit_volume_integral_cache!(cache, mesh, dg, volume_integral_default,
424
new_size)
425
reinit_volume_integral_cache!(cache, mesh, dg, volume_integral_blend_high_order,
426
new_size)
427
reinit_volume_integral_cache!(cache, mesh, dg, volume_integral_blend_low_order,
428
new_size)
429
430
return nothing
431
end
432
433
@doc raw"""
434
VolumeIntegralAdaptive(;
435
indicator = IndicatorEntropyChange(),
436
volume_integral_default,
437
volume_integral_stabilized)
438
439
This volume integral allows for a-priori and a-posteriori style adaptation of the volume integral/term computation.
440
441
Choosing `indicator` as [`IndicatorEntropyChange`](@ref) corresponds to the a-posteriori implementation.
442
At every Runge-Kutta stage and for every element, the volume update is computed using
443
`volume_integral_default` and the element-wise `indicator` is then evaluated based on this update.
444
If the `indicator` deems the default volume integral unstable, the default update is discarded
445
and the `volume_integral_stabilized` is computed and used instead for the update.
446
447
The motivation for this volume integral are simulations, which require in some cells usage of e.g.
448
an entropy-conservative volume integral (i.e., [`VolumeIntegralFluxDifferencing`](@ref) with an appropriate flux)
449
for stability, but not everywhere in the domain.
450
In such cases, the `volume_integral_default` can be a cheaper volume integral such as [`VolumeIntegralWeakForm`](@ref).
451
452
For reference, see
453
- Doehring, Chan, Ranocha, Schlottke-Lakemper, Torrilhon, Gassner (2026)
454
Volume Term Adaptivity for Discontinuous Galerkin Schemes
455
[DOI: 10.48550/arXiv.2603.24189](https://doi.org/10.48550/arXiv.2603.24189)
456
457
especially Sections 3 and 3.1 for a detailed description of the method.
458
459
In turn, choosing `indicator` as [`IndicatorHennemannGassner`](@ref) corresponds to the a-priori implementation.
460
Similar to [`VolumeIntegralShockCapturingHGType`](@ref), the indicator is evaluated before any volume update is performed.
461
If the indicator value ``\alpha`` is zero (i.e., below ``\alpha_\text{min}``) the `volume_integral_default` is used.
462
Otherwise, the `volume_integral_stabilized` is used.
463
This kind strategy was for certain choices of the default and stabilized volume integrals already presented as the "ES-DG" scheme in
464
- Bilocq, Borbouse, Levaux, Terrapon, Hillewaert (2025)
465
Comparison of stabilization strategies applied to scale-resolved simulations using the discontinuous Galerkin method
466
[DOI: 10.1016/j.jcp.2025.114238](https://doi.org/10.1016/j.jcp.2025.114238)
467
468
!!! warning "Experimental code"
469
This code is experimental and may change in any future release.
470
"""
471
struct VolumeIntegralAdaptive{Indicator,
472
VolumeIntegralDefault, VolumeIntegralStabilized} <:
473
AbstractVolumeIntegral
474
indicator::Indicator # A-posteriori indicator called after computation of `volume_integral_default`
475
volume_integral_default::VolumeIntegralDefault # Cheap(er) default volume integral to be used in non-critical regions
476
volume_integral_stabilized::VolumeIntegralStabilized # More expensive volume integral with stabilizing effect
477
end
478
479
function VolumeIntegralAdaptive(;
480
indicator = IndicatorEntropyChange(),
481
volume_integral_default,
482
volume_integral_stabilized)
483
return VolumeIntegralAdaptive{typeof(indicator),
484
typeof(volume_integral_default),
485
typeof(volume_integral_stabilized)}(indicator,
486
volume_integral_default,
487
volume_integral_stabilized)
488
end
489
490
function Base.show(io::IO, mime::MIME"text/plain",
491
integral::VolumeIntegralAdaptive)
492
@nospecialize integral # reduce precompilation time
493
@unpack volume_integral_default, volume_integral_stabilized, indicator = integral
494
495
if get(io, :compact, false)
496
show(io, integral)
497
else
498
summary_header(io, "VolumeIntegralAdaptive")
499
500
summary_line(io, "volume integral default",
501
volume_integral_default |> typeof |> nameof)
502
if !(volume_integral_default isa VolumeIntegralWeakForm)
503
show(increment_indent(io), mime, volume_integral_default)
504
end
505
506
summary_line(io, "volume integral stabilized",
507
volume_integral_stabilized |> typeof |> nameof)
508
show(increment_indent(io), mime, volume_integral_stabilized)
509
510
summary_line(io, "indicator", indicator |> typeof |> nameof)
511
show(increment_indent(io), mime, indicator)
512
513
summary_footer(io)
514
end
515
end
516
517
function create_cache(mesh, equations,
518
volume_integral::VolumeIntegralAdaptive,
519
dg, cache_containers, uEltype)
520
cache_default = create_cache(mesh, equations,
521
volume_integral.volume_integral_default,
522
dg, cache_containers, uEltype)
523
cache_stabilized = create_cache(mesh, equations,
524
volume_integral.volume_integral_stabilized,
525
dg, cache_containers, uEltype)
526
527
return (; cache_default..., cache_stabilized...)
528
end
529
530
function resize_volume_integral_cache!(cache, mesh,
531
volume_integral::VolumeIntegralAdaptive,
532
new_size)
533
@unpack volume_integral_default, volume_integral_stabilized = volume_integral
534
resize_volume_integral_cache!(cache, mesh, volume_integral_default, new_size)
535
resize_volume_integral_cache!(cache, mesh, volume_integral_stabilized, new_size)
536
537
return nothing
538
end
539
540
function reinit_volume_integral_cache!(cache, mesh, dg,
541
volume_integral::VolumeIntegralAdaptive,
542
new_size)
543
@unpack volume_integral_default, volume_integral_stabilized = volume_integral
544
reinit_volume_integral_cache!(cache, mesh, dg, volume_integral_default, new_size)
545
reinit_volume_integral_cache!(cache, mesh, dg, volume_integral_stabilized, new_size)
546
547
return nothing
548
end
549
550
# Abstract supertype for first-order `VolumeIntegralPureLGLFiniteVolume` and
551
# second-order `VolumeIntegralPureLGLFiniteVolumeO2` subcell-based finite volume
552
# volume integrals.
553
abstract type AbstractVolumeIntegralPureLGLFiniteVolume <: AbstractVolumeIntegralSubcell end
554
555
"""
556
VolumeIntegralPureLGLFiniteVolume(volume_flux_fv)
557
VolumeIntegralPureLGLFiniteVolume(; volume_flux_fv = flux_lax_friedrichs)
558
559
A volume integral that only uses the subcell finite volume schemes of the
560
[`VolumeIntegralShockCapturingHG`](@ref).
561
562
This gives a formally O(1)-accurate finite volume scheme on an LGL-type subcell
563
mesh (LGL = Legendre-Gauss-Lobatto).
564
565
## References
566
567
- Hennemann, Gassner (2020)
568
"A provably entropy stable subcell shock capturing approach for high order split form DG"
569
[arXiv: 2008.12044](https://arxiv.org/abs/2008.12044)
570
"""
571
struct VolumeIntegralPureLGLFiniteVolume{VolumeFluxFV} <:
572
AbstractVolumeIntegralPureLGLFiniteVolume
573
volume_flux_fv::VolumeFluxFV # non-symmetric in general, e.g. entropy-dissipative
574
end
575
# TODO: Figure out if this can also be used for Gauss nodes, not just LGL, and adjust the name accordingly
576
577
function VolumeIntegralPureLGLFiniteVolume(; volume_flux_fv = flux_lax_friedrichs)
578
return VolumeIntegralPureLGLFiniteVolume{typeof(volume_flux_fv)}(volume_flux_fv)
579
end
580
581
function Base.show(io::IO, ::MIME"text/plain",
582
integral::VolumeIntegralPureLGLFiniteVolume)
583
@nospecialize integral # reduce precompilation time
584
585
if get(io, :compact, false)
586
show(io, integral)
587
else
588
setup = [
589
"FV flux" => integral.volume_flux_fv
590
]
591
summary_box(io, "VolumeIntegralPureLGLFiniteVolume", setup)
592
end
593
end
594
595
"""
596
VolumeIntegralPureLGLFiniteVolumeO2(basis;
597
volume_flux_fv = flux_lax_friedrichs,
598
reconstruction_mode = reconstruction_O2_full,
599
slope_limiter = minmod,
600
cons2recon = cons2prim,
601
recon2cons = prim2cons)
602
603
This gives an up to second order accurate finite volume scheme on an LGL-type subcell
604
mesh (LGL = Legendre-Gauss-Lobatto).
605
Depending on the `reconstruction_mode` and `slope_limiter`, experimental orders of convergence
606
between 1 and 2 can be expected in practice.
607
Since this is a volume integral, all reconstructions are purely cell-local, i.e.,
608
no neighboring elements are queried at reconstruction stage.
609
610
The interface values of the inner DG-subcells are reconstructed using the standard MUSCL-type reconstruction.
611
For the DG-subcells at the boundaries, two options are available:
612
613
1) The unlimited slope is used on these cells.
614
This gives full second order accuracy, but also does not damp overshoots between cells.
615
The `reconstruction_mode` corresponding to this is `reconstruction_O2_full`.
616
2) On boundary subcells, the solution is represented using a constant value, thereby falling back to formally only first order.
617
The `reconstruction_mode` corresponding to this is `reconstruction_O2_inner`.
618
In the reference below, this is the recommended reconstruction mode and is thus used by default.
619
620
**Symmetric** total-Variation-Diminishing (TVD) choices for the `slope_limiter` are
621
1) [`minmod`](@ref)
622
2) [`monotonized_central`](@ref)
623
3) [`superbee`](@ref)
624
4) [`vanleer`](@ref)
625
5) [`koren_symmetric`](@ref)
626
**Asymmetric** TVD limiters are also available, e.g.,
627
1) [`koren`](@ref) for positive (right-going) velocities
628
2) [`koren_flipped`](@ref) for negative (left-going) velocities
629
630
The reconstruction is performed in reconstruction variables, which default to the primitive variables.
631
Other choices are possible, e.g., thermodynamic variables, see [`cons2thermo`](@ref) and [`thermo2cons`](@ref)
632
for [`NonIdealCompressibleEulerEquations1D`](@ref) and [`NonIdealCompressibleEulerEquations2D`](@ref).
633
634
!!! note "Conservative systems only"
635
Currently only implemented for systems in conservative form, i.e.,
636
`have_nonconservative_terms(equations) = False()`
637
638
## References
639
640
See especially Section 3.2, Section 4, and Appendix D of the paper
641
642
- Rueda-Ramírez, Hennemann, Hindenlang, Winters, & Gassner (2021).
643
"An entropy stable nodal discontinuous Galerkin method for the resistive MHD equations.
644
Part II: Subcell finite volume shock capturing"
645
[JCP: 2021.110580](https://doi.org/10.1016/j.jcp.2021.110580)
646
"""
647
struct VolumeIntegralPureLGLFiniteVolumeO2{SubCellInterfaceCoordinates, VolumeFluxFV,
648
Reconstruction, Limiter,
649
Cons2Recon, Recon2Cons} <:
650
AbstractVolumeIntegralPureLGLFiniteVolume
651
sc_interface_coords::SubCellInterfaceCoordinates # (x-)coordinates of the sub-cell element interfaces
652
volume_flux_fv::VolumeFluxFV # non-symmetric in general, e.g. entropy-dissipative
653
reconstruction_mode::Reconstruction # which type of FV reconstruction to use
654
slope_limiter::Limiter # which type of slope limiter function
655
cons2recon::Cons2Recon # function to convert from conservative variables to the variables used for reconstruction
656
recon2cons::Recon2Cons # function to convert from the variables used for reconstruction back to conservative variables
657
end
658
659
function VolumeIntegralPureLGLFiniteVolumeO2(basis;
660
volume_flux_fv = flux_lax_friedrichs,
661
reconstruction_mode = reconstruction_O2_full,
662
slope_limiter = minmod,
663
cons2recon = cons2prim,
664
recon2cons = prim2cons)
665
polydeg = nnodes(basis) - 1
666
# Suffices to store only the intermediate boundaries of the sub-cell elements
667
sc_interface_coords = SVector{polydeg}(cumsum(basis.weights)[1:polydeg] .- 1)
668
return VolumeIntegralPureLGLFiniteVolumeO2{typeof(sc_interface_coords),
669
typeof(volume_flux_fv),
670
typeof(reconstruction_mode),
671
typeof(slope_limiter),
672
typeof(cons2recon),
673
typeof(recon2cons)}(sc_interface_coords,
674
volume_flux_fv,
675
reconstruction_mode,
676
slope_limiter,
677
cons2recon,
678
recon2cons)
679
end
680
681
function Base.show(io::IO, ::MIME"text/plain",
682
integral::VolumeIntegralPureLGLFiniteVolumeO2)
683
@nospecialize integral # reduce precompilation time
684
685
if get(io, :compact, false)
686
show(io, integral)
687
else
688
setup = [
689
"FV flux" => integral.volume_flux_fv,
690
"Reconstruction" => integral.reconstruction_mode,
691
"Slope limiter" => integral.slope_limiter,
692
"Subcell boundaries" => vcat([-1.0], integral.sc_interface_coords, [1.0]),
693
"cons2recon" => integral.cons2recon,
694
"recon2cons" => integral.recon2cons
695
]
696
summary_box(io, "VolumeIntegralPureLGLFiniteVolumeO2", setup)
697
end
698
end
699
700
"""
701
VolumeIntegralSubcellLimiting(limiter;
702
volume_flux_dg = flux_central,
703
volume_flux_fv = flux_lax_friedrichs)
704
705
A subcell limiting volume integral type for DG methods based on subcell blending approaches
706
with a low-order FV method. Used with limiter [`SubcellLimiterIDP`](@ref).
707
708
!!! note
709
Subcell limiting methods are not fully functional on non-conforming meshes. This is
710
mainly because the implementation assumes that low- and high-order schemes have the same
711
surface terms, which is not guaranteed for non-conforming meshes. The low-order scheme
712
with a high-order mortar is not invariant domain preserving.
713
"""
714
struct VolumeIntegralSubcellLimiting{VolumeFluxDG, VolumeFluxFV, Limiter} <:
715
AbstractVolumeIntegralSubcell
716
volume_flux_dg::VolumeFluxDG
717
volume_flux_fv::VolumeFluxFV
718
limiter::Limiter
719
end
720
721
function VolumeIntegralSubcellLimiting(limiter;
722
volume_flux_dg = flux_central,
723
volume_flux_fv = flux_lax_friedrichs)
724
return VolumeIntegralSubcellLimiting{typeof(volume_flux_dg), typeof(volume_flux_fv),
725
typeof(limiter)}(volume_flux_dg,
726
volume_flux_fv,
727
limiter)
728
end
729
730
function Base.show(io::IO, mime::MIME"text/plain",
731
integral::VolumeIntegralSubcellLimiting)
732
@nospecialize integral # reduce precompilation time
733
734
if get(io, :compact, false)
735
show(io, integral)
736
else
737
summary_header(io, "VolumeIntegralSubcellLimiting")
738
summary_line(io, "volume flux DG", integral.volume_flux_dg)
739
summary_line(io, "volume flux FV", integral.volume_flux_fv)
740
summary_line(io, "limiter", integral.limiter |> typeof |> nameof)
741
show(increment_indent(io), mime, integral.limiter)
742
summary_footer(io)
743
end
744
end
745
746
# TODO: FD. Should this definition live in a different file because it is
747
# not strictly a DG method?
748
"""
749
VolumeIntegralUpwind(splitting)
750
751
Specialized volume integral for finite difference summation-by-parts (FDSBP)
752
solvers. Can be used together with the upwind SBP operators of Mattsson (2017)
753
implemented in SummationByPartsOperators.jl. The `splitting` controls the
754
discretization.
755
756
See also [`splitting_steger_warming`](@ref), [`splitting_lax_friedrichs`](@ref),
757
[`splitting_vanleer_haenel`](@ref).
758
759
## References
760
761
- Mattsson (2017)
762
Diagonal-norm upwind SBP operators
763
[doi: 10.1016/j.jcp.2017.01.042](https://doi.org/10.1016/j.jcp.2017.01.042)
764
765
!!! warning "Experimental implementation (upwind SBP)"
766
This is an experimental feature and may change in future releases.
767
"""
768
struct VolumeIntegralUpwind{FluxSplitting} <: AbstractVolumeIntegral
769
splitting::FluxSplitting
770
end
771
772
function Base.show(io::IO, ::MIME"text/plain", integral::VolumeIntegralUpwind)
773
@nospecialize integral # reduce precompilation time
774
775
if get(io, :compact, false)
776
show(io, integral)
777
else
778
setup = [
779
"flux splitting" => integral.splitting
780
]
781
summary_box(io, "VolumeIntegralUpwind", setup)
782
end
783
end
784
785
abstract type AbstractSurfaceIntegral end
786
787
"""
788
SurfaceIntegralWeakForm(surface_flux=flux_central)
789
790
The classical weak form surface integral type for DG methods as explained in standard
791
textbooks.
792
793
See also [`VolumeIntegralWeakForm`](@ref).
794
795
## References
796
797
- Kopriva (2009)
798
Implementing Spectral Methods for Partial Differential Equations:
799
Algorithms for Scientists and Engineers
800
[doi: 10.1007/978-90-481-2261-5](https://doi.org/10.1007/978-90-481-2261-5)
801
- Hesthaven, Warburton (2007)
802
Nodal Discontinuous Galerkin Methods: Algorithms, Analysis, and
803
Applications
804
[doi: 10.1007/978-0-387-72067-8](https://doi.org/10.1007/978-0-387-72067-8)
805
"""
806
struct SurfaceIntegralWeakForm{SurfaceFlux} <: AbstractSurfaceIntegral
807
surface_flux::SurfaceFlux
808
end
809
810
SurfaceIntegralWeakForm() = SurfaceIntegralWeakForm(flux_central)
811
812
function Base.show(io::IO, ::MIME"text/plain", integral::SurfaceIntegralWeakForm)
813
@nospecialize integral # reduce precompilation time
814
815
if get(io, :compact, false)
816
show(io, integral)
817
else
818
setup = [
819
"surface flux" => integral.surface_flux
820
]
821
summary_box(io, "SurfaceIntegralWeakForm", setup)
822
end
823
end
824
825
"""
826
SurfaceIntegralStrongForm(surface_flux=flux_central)
827
828
The classical strong form surface integral type for FD/DG methods.
829
830
See also [`VolumeIntegralStrongForm`](@ref).
831
"""
832
struct SurfaceIntegralStrongForm{SurfaceFlux} <: AbstractSurfaceIntegral
833
surface_flux::SurfaceFlux
834
end
835
836
SurfaceIntegralStrongForm() = SurfaceIntegralStrongForm(flux_central)
837
838
function Base.show(io::IO, ::MIME"text/plain", integral::SurfaceIntegralStrongForm)
839
@nospecialize integral # reduce precompilation time
840
841
if get(io, :compact, false)
842
show(io, integral)
843
else
844
setup = [
845
"surface flux" => integral.surface_flux
846
]
847
summary_box(io, "SurfaceIntegralStrongForm", setup)
848
end
849
end
850
851
# TODO: FD. Should this definition live in a different file because it is
852
# not strictly a DG method?
853
"""
854
SurfaceIntegralUpwind(splitting)
855
856
Couple elements with upwind simultaneous approximation terms (SATs)
857
that use a particular flux `splitting`, e.g.,
858
[`splitting_steger_warming`](@ref).
859
860
See also [`VolumeIntegralUpwind`](@ref).
861
862
!!! warning "Experimental implementation (upwind SBP)"
863
This is an experimental feature and may change in future releases.
864
"""
865
struct SurfaceIntegralUpwind{FluxSplitting} <: AbstractSurfaceIntegral
866
splitting::FluxSplitting
867
end
868
869
function Base.show(io::IO, ::MIME"text/plain", integral::SurfaceIntegralUpwind)
870
@nospecialize integral # reduce precompilation time
871
872
if get(io, :compact, false)
873
show(io, integral)
874
else
875
setup = [
876
"flux splitting" => integral.splitting
877
]
878
summary_box(io, "SurfaceIntegralUpwind", setup)
879
end
880
end
881
882
"""
883
DG(; basis, mortar, surface_integral, volume_integral)
884
885
Create a discontinuous Galerkin method.
886
If [`basis isa LobattoLegendreBasis`](@ref LobattoLegendreBasis),
887
this creates a [`DGSEM`](@ref).
888
"""
889
struct DG{Basis, Mortar, SurfaceIntegral, VolumeIntegral}
890
basis::Basis
891
mortar::Mortar
892
surface_integral::SurfaceIntegral
893
volume_integral::VolumeIntegral
894
end
895
896
# @eval due to @muladd
897
@eval Adapt.@adapt_structure(DG)
898
899
function Base.show(io::IO, dg::DG)
900
@nospecialize dg # reduce precompilation time
901
902
print(io, "DG{", real(dg), "}(")
903
print(io, dg.basis)
904
print(io, ", ", dg.mortar)
905
print(io, ", ", dg.surface_integral)
906
print(io, ", ", dg.volume_integral)
907
print(io, ")")
908
return nothing
909
end
910
911
function Base.show(io::IO, mime::MIME"text/plain", dg::DG)
912
@nospecialize dg # reduce precompilation time
913
914
if get(io, :compact, false)
915
show(io, dg)
916
else
917
summary_header(io, "DG{" * string(real(dg)) * "}")
918
summary_line(io, "basis", dg.basis)
919
summary_line(io, "mortar", dg.mortar)
920
summary_line(io, "surface integral", dg.surface_integral |> typeof |> nameof)
921
show(increment_indent(io), mime, dg.surface_integral)
922
summary_line(io, "volume integral", dg.volume_integral |> typeof |> nameof)
923
if !(dg.volume_integral isa VolumeIntegralWeakForm) &&
924
!(dg.volume_integral isa VolumeIntegralStrongForm)
925
show(increment_indent(io), mime, dg.volume_integral)
926
end
927
summary_footer(io)
928
end
929
end
930
931
Base.summary(io::IO, dg::DG) = print(io, "DG(" * summary(dg.basis) * ")")
932
933
@inline Base.real(dg::DG) = real(dg.basis)
934
935
function get_element_variables!(element_variables, u, mesh, equations, dg::DG, cache)
936
get_element_variables!(element_variables, u, mesh, equations, dg.volume_integral,
937
dg, cache)
938
return get_element_variables!(element_variables, mesh, dg, cache)
939
end
940
941
const MeshesDGSEM = Union{TreeMesh, StructuredMesh, StructuredMeshView,
942
UnstructuredMesh2D,
943
P4estMesh, P4estMeshView, T8codeMesh}
944
945
@inline function ndofs(mesh::MeshesDGSEM, dg::DG, cache)
946
return nelements(cache.elements) * nnodes(dg)^ndims(mesh)
947
end
948
949
# TODO: Taal performance, 1:nnodes(dg) vs. Base.OneTo(nnodes(dg)) vs. SOneTo(nnodes(dg)) for DGSEM
950
"""
951
eachnode(dg::DG)
952
953
Return an iterator over the indices that specify the location in relevant data structures
954
for the nodes in `dg`.
955
In particular, not the nodes themselves are returned.
956
"""
957
@inline eachnode(dg::DG) = Base.OneTo(nnodes(dg))
958
@inline nnodes(dg::DG) = nnodes(dg.basis)
959
960
# This is used in some more general analysis code and needs to dispatch on the
961
# `mesh` for some combinations of mesh/solver.
962
@inline nelements(mesh, dg::DG, cache) = nelements(dg, cache)
963
@inline function ndofsglobal(mesh, dg::DG, cache)
964
return nelementsglobal(mesh, dg, cache) * nnodes(dg)^ndims(mesh)
965
end
966
967
"""
968
eachelement(dg::DG, cache)
969
970
Return an iterator over the indices that specify the location in relevant data structures
971
for the elements in `cache`.
972
In particular, not the elements themselves are returned.
973
"""
974
@inline eachelement(dg::DG, cache) = Base.OneTo(nelements(dg, cache))
975
976
"""
977
eachinterface(dg::DG, cache)
978
979
Return an iterator over the indices that specify the location in relevant data structures
980
for the interfaces in `cache`.
981
In particular, not the interfaces themselves are returned.
982
"""
983
@inline eachinterface(dg::DG, cache) = Base.OneTo(ninterfaces(dg, cache))
984
985
"""
986
eachboundary(dg::DG, cache)
987
988
Return an iterator over the indices that specify the location in relevant data structures
989
for the boundaries in `cache`.
990
In particular, not the boundaries themselves are returned.
991
"""
992
@inline eachboundary(dg::DG, cache) = Base.OneTo(nboundaries(dg, cache))
993
994
"""
995
eachmortar(dg::DG, cache)
996
997
Return an iterator over the indices that specify the location in relevant data structures
998
for the mortars in `cache`.
999
In particular, not the mortars themselves are returned.
1000
"""
1001
@inline eachmortar(dg::DG, cache) = Base.OneTo(nmortars(dg, cache))
1002
1003
"""
1004
eachmpiinterface(dg::DG, cache)
1005
1006
Return an iterator over the indices that specify the location in relevant data structures
1007
for the MPI interfaces in `cache`.
1008
In particular, not the interfaces themselves are returned.
1009
"""
1010
@inline eachmpiinterface(dg::DG, cache) = Base.OneTo(nmpiinterfaces(dg, cache))
1011
1012
"""
1013
eachmpimortar(dg::DG, cache)
1014
1015
Return an iterator over the indices that specify the location in relevant data structures
1016
for the MPI mortars in `cache`.
1017
In particular, not the mortars themselves are returned.
1018
"""
1019
@inline eachmpimortar(dg::DG, cache) = Base.OneTo(nmpimortars(dg, cache))
1020
1021
@inline nelements(dg::DG, cache) = nelements(cache.elements)
1022
@inline function nelementsglobal(mesh, dg::DG, cache)
1023
return mpi_isparallel() ? cache.mpi_cache.n_elements_global : nelements(dg, cache)
1024
end
1025
@inline ninterfaces(dg::DG, cache) = ninterfaces(cache.interfaces)
1026
@inline nboundaries(dg::DG, cache) = nboundaries(cache.boundaries)
1027
@inline nmortars(dg::DG, cache) = nmortars(cache.mortars)
1028
@inline nmpiinterfaces(dg::DG, cache) = nmpiinterfaces(cache.mpi_interfaces)
1029
@inline nmpimortars(dg::DG, cache) = nmpimortars(cache.mpi_mortars)
1030
1031
# The following functions assume an array-of-structs memory layout
1032
# We would like to experiment with different memory layout choices
1033
# in the future, see
1034
# - https://github.com/trixi-framework/Trixi.jl/issues/88
1035
# - https://github.com/trixi-framework/Trixi.jl/issues/87
1036
# - https://github.com/trixi-framework/Trixi.jl/issues/86
1037
@inline function get_node_coords(x, equations, solver::DG, indices...)
1038
return SVector(ntuple(@inline(idx->x[idx, indices...]), Val(ndims(equations))))
1039
end
1040
1041
"""
1042
get_node_vars(u, equations, solver::DG, indices...)
1043
1044
Return the value of the variable (vector) `u` at a node inside a specific element.
1045
The node is specified by the indices `indices...` argument which is a combination of
1046
node index inside the element and the element index itself.
1047
Thus, in 1D this is a two integer tuple `indices = (i, element)`,
1048
in 2D a three integer tuple `indices = (i, j, element)`,
1049
and in 3D a four integer tuple `indices = (i, j, k, element)`.
1050
It is also possible to call this function without `indices` being explicitly a tuple,
1051
i.e., `get_node_vars(u, equations, solver::DG, i, j, k, element)` is also valid.
1052
For more details, see the documentation:
1053
https://docs.julialang.org/en/v1/manual/functions/#Varargs-Functions
1054
"""
1055
@inline function get_node_vars(u, equations, solver::DG, indices...)
1056
# There is a cut-off at `n == 10` inside of the method
1057
# `ntuple(f::F, n::Integer) where F` in Base at ntuple.jl:17
1058
# in Julia `v1.5`, leading to type instabilities if
1059
# more than ten variables are used. That's why we use
1060
# `Val(...)` below.
1061
# We use `@inline` to make sure that the `getindex` calls are
1062
# really inlined, which might be the default choice of the Julia
1063
# compiler for standard `Array`s but not necessarily for more
1064
# advanced array types such as `PtrArray`s, cf.
1065
# https://github.com/JuliaSIMD/VectorizationBase.jl/issues/55
1066
return SVector(ntuple(@inline(v->u[v, indices...]), Val(nvariables(equations))))
1067
end
1068
1069
@inline function get_surface_node_vars(u, equations, solver::DG, indices...)
1070
# There is a cut-off at `n == 10` inside of the method
1071
# `ntuple(f::F, n::Integer) where F` in Base at ntuple.jl:17
1072
# in Julia `v1.5`, leading to type instabilities if
1073
# more than ten variables are used. That's why we use
1074
# `Val(...)` below.
1075
u_ll = SVector(ntuple(@inline(v->u[1, v, indices...]), Val(nvariables(equations))))
1076
u_rr = SVector(ntuple(@inline(v->u[2, v, indices...]), Val(nvariables(equations))))
1077
return u_ll, u_rr
1078
end
1079
1080
# As above but dispatches on an type argument
1081
@inline function get_surface_node_vars(u, equations, ::Type{<:DG}, indices...)
1082
u_ll = SVector(ntuple(@inline(v->u[1, v, indices...]), Val(nvariables(equations))))
1083
u_rr = SVector(ntuple(@inline(v->u[2, v, indices...]), Val(nvariables(equations))))
1084
return u_ll, u_rr
1085
end
1086
1087
@inline function set_node_vars!(u, u_node, equations, solver::DG, indices...)
1088
for v in eachvariable(equations)
1089
u[v, indices...] = u_node[v]
1090
end
1091
return nothing
1092
end
1093
1094
@inline function add_to_node_vars!(u, u_node, equations, solver::DG, indices...)
1095
for v in eachvariable(equations)
1096
u[v, indices...] += u_node[v]
1097
end
1098
return nothing
1099
end
1100
1101
# Use this function instead of `add_to_node_vars` to speed up
1102
# multiply-and-add-to-node-vars operations
1103
# See https://github.com/trixi-framework/Trixi.jl/pull/643
1104
@inline function multiply_add_to_node_vars!(u, factor, u_node, equations, solver::DG,
1105
indices...)
1106
for v in eachvariable(equations)
1107
u[v, indices...] = u[v, indices...] + factor * u_node[v]
1108
end
1109
return nothing
1110
end
1111
1112
# Used for analyze_solution
1113
SolutionAnalyzer(dg::DG; kwargs...) = SolutionAnalyzer(dg.basis; kwargs...)
1114
1115
AdaptorAMR(mesh, dg::DG) = AdaptorL2(dg.basis)
1116
1117
# General structs for discretizations based on the basic principle of
1118
# DGSEM (discontinuous Galerkin spectral element method)
1119
include("dgsem/dgsem.jl")
1120
1121
# Finite difference methods using summation by parts (SBP) operators
1122
# These methods are very similar to DG methods since they also impose interface
1123
# and boundary conditions weakly. Thus, these methods can re-use a lot of
1124
# functionality implemented for DGSEM.
1125
include("fdsbp_tree/fdsbp.jl")
1126
include("fdsbp_unstructured/fdsbp.jl")
1127
1128
function allocate_coefficients(mesh::AbstractMesh, equations, dg::DG, cache)
1129
# We must allocate a `Vector` in order to be able to `resize!` it (AMR).
1130
# cf. wrap_array
1131
u_ode = similar(cache.elements.node_coordinates, eltype(cache.elements),
1132
nvariables(equations) * nnodes(dg)^ndims(mesh) *
1133
nelements(dg, cache))
1134
fill!(u_ode, zero(eltype(u_ode)))
1135
return u_ode
1136
end
1137
1138
@inline function wrap_array(u_ode::AbstractVector, mesh::AbstractMesh, equations,
1139
dg::DGSEM, cache)
1140
@boundscheck begin
1141
@assert length(u_ode) ==
1142
nvariables(equations) * nnodes(dg)^ndims(mesh) * nelements(dg, cache)
1143
end
1144
# We would like to use
1145
# reshape(u_ode, (nvariables(equations), ntuple(_ -> nnodes(dg), ndims(mesh))..., nelements(dg, cache)))
1146
# but that results in
1147
# ERROR: LoadError: cannot resize array with shared data
1148
# when we resize! `u_ode` during AMR.
1149
#
1150
# !!! danger "Segfaults"
1151
# Remember to `GC.@preserve` temporaries such as copies of `u_ode`
1152
# and other stuff that is only used indirectly via `wrap_array` afterwards!
1153
1154
# Currently, there are problems when AD is used with `PtrArray`s in broadcasts
1155
# since LoopVectorization does not support `ForwardDiff.Dual`s. Hence, we use
1156
# optimized `PtrArray`s whenever possible and fall back to plain `Array`s
1157
# otherwise.
1158
if _PREFERENCE_THREADING === :polyester && LoopVectorization.check_args(u_ode)
1159
# This version using `PtrArray`s from StrideArrays.jl is very fast and
1160
# does not result in allocations.
1161
#
1162
# !!! danger "Heisenbug"
1163
# Do not use this code when `@threaded` uses `Threads.@threads`. There is
1164
# a very strange Heisenbug that makes some parts very slow *sometimes*.
1165
# In fact, everything can be fast and fine for many cases but some parts
1166
# of the RHS evaluation can take *exactly* (!) five seconds randomly...
1167
# Hence, this version should only be used when `@threaded` is based on
1168
# `@batch` from Polyester.jl or something similar.
1169
PtrArray(pointer(u_ode),
1170
(StaticInt(nvariables(equations)),
1171
ntuple(_ -> StaticInt(nnodes(dg)), ndims(mesh))...,
1172
nelements(dg, cache)))
1173
# (nvariables(equations), ntuple(_ -> nnodes(dg), ndims(mesh))..., nelements(dg, cache)))
1174
else
1175
# The following version is reasonably fast and allows us to `resize!(u_ode, ...)`.
1176
ArrayType = Trixi.storage_type(u_ode)
1177
unsafe_wrap(ArrayType{eltype(u_ode), ndims(mesh) + 2}, pointer(u_ode),
1178
(nvariables(equations), ntuple(_ -> nnodes(dg), ndims(mesh))...,
1179
nelements(dg, cache)))
1180
end
1181
end
1182
1183
# Finite difference summation by parts (FDSBP) methods
1184
@inline function wrap_array(u_ode::AbstractVector, mesh::AbstractMesh, equations,
1185
dg::FDSBP, cache)
1186
@boundscheck begin
1187
@assert length(u_ode) ==
1188
nvariables(equations) * nnodes(dg)^ndims(mesh) * nelements(dg, cache)
1189
end
1190
# See comments on the DGSEM version above
1191
if _PREFERENCE_THREADING === :polyester && LoopVectorization.check_args(u_ode)
1192
# Here, we do not specialize on the number of nodes using `StaticInt` since
1193
# - it will not be type stable (SBP operators just store it as a runtime value)
1194
# - FD methods tend to use high node counts
1195
PtrArray(pointer(u_ode),
1196
(StaticInt(nvariables(equations)),
1197
ntuple(_ -> nnodes(dg), ndims(mesh))..., nelements(dg, cache)))
1198
else
1199
# The following version is reasonably fast and allows us to `resize!(u_ode, ...)`.
1200
unsafe_wrap(Array{eltype(u_ode), ndims(mesh) + 2}, pointer(u_ode),
1201
(nvariables(equations), ntuple(_ -> nnodes(dg), ndims(mesh))...,
1202
nelements(dg, cache)))
1203
end
1204
end
1205
1206
# General fallback
1207
@inline function wrap_array(u_ode::AbstractVector, mesh::AbstractMesh, equations,
1208
dg::DG, cache)
1209
return wrap_array_native(u_ode, mesh, equations, dg, cache)
1210
end
1211
1212
# Like `wrap_array`, but guarantees to return a plain `Array`, which can be better
1213
# for interfacing with external C libraries (MPI, HDF5, visualization),
1214
# writing solution files etc.
1215
@inline function wrap_array_native(u_ode::AbstractVector, mesh::AbstractMesh, equations,
1216
dg::DG, cache)
1217
@boundscheck begin
1218
@assert length(u_ode) ==
1219
nvariables(equations) * nnodes(dg)^ndims(mesh) * nelements(dg, cache)
1220
end
1221
return unsafe_wrap(Array{eltype(u_ode), ndims(mesh) + 2}, pointer(u_ode),
1222
(nvariables(equations), ntuple(_ -> nnodes(dg), ndims(mesh))...,
1223
nelements(dg, cache)))
1224
end
1225
1226
function compute_coefficients!(backend::Nothing, u, func, t, mesh::AbstractMesh{1},
1227
equations, dg::DG, cache)
1228
@threaded for element in eachelement(dg, cache)
1229
for i in eachnode(dg)
1230
x_node = get_node_coords(cache.elements.node_coordinates, equations, dg, i,
1231
element)
1232
# Changing the node positions passed to the initial condition by the minimum
1233
# amount possible with the current type of floating point numbers allows setting
1234
# discontinuous initial data in a simple way. In particular, a check like `if x < x_jump`
1235
# works if the jump location `x_jump` is at the position of an interface.
1236
if i == 1
1237
x_node = SVector(nextfloat(x_node[1]))
1238
elseif i == nnodes(dg)
1239
x_node = SVector(prevfloat(x_node[1]))
1240
end
1241
u_node = func(x_node, t, equations)
1242
set_node_vars!(u, u_node, equations, dg, i, element)
1243
end
1244
end
1245
1246
return nothing
1247
end
1248
1249
function compute_coefficients!(backend::Nothing, u, func, t,
1250
mesh::Union{AbstractMesh{2}, AbstractMesh{3}},
1251
equations, dg::DG, cache)
1252
@unpack node_coordinates = cache.elements
1253
node_indices = CartesianIndices(ntuple(_ -> nnodes(dg), ndims(mesh)))
1254
@threaded for element in eachelement(dg, cache)
1255
compute_coefficients_per_element!(u, func, t, equations, dg, node_coordinates,
1256
element, node_indices)
1257
end
1258
1259
return nothing
1260
end
1261
1262
function compute_coefficients!(backend::Backend, u, func, t,
1263
mesh::Union{AbstractMesh{2}, AbstractMesh{3}},
1264
equations, dg::DG, cache)
1265
nelements(dg, cache) == 0 && return nothing
1266
1267
@unpack node_coordinates = cache.elements
1268
node_indices = CartesianIndices(ntuple(_ -> nnodes(dg), ndims(mesh)))
1269
1270
kernel! = compute_coefficients_KAkernel!(backend)
1271
kernel!(u, func, t, equations, dg, node_coordinates, node_indices,
1272
ndrange = nelements(dg, cache))
1273
return nothing
1274
end
1275
1276
@kernel function compute_coefficients_KAkernel!(u, func, t, equations,
1277
dg::DG, node_coordinates, node_indices)
1278
element = @index(Global)
1279
compute_coefficients_per_element!(u, func, t, equations, dg, node_coordinates,
1280
element,
1281
node_indices)
1282
end
1283
1284
@inline function compute_coefficients_per_element!(u, func, t, equations, dg::DG,
1285
node_coordinates, element,
1286
node_indices)
1287
for indices in node_indices
1288
x_node = get_node_coords(node_coordinates, equations, dg, indices, element)
1289
u_node = func(x_node, t, equations)
1290
set_node_vars!(u, u_node, equations, dg, indices, element)
1291
end
1292
1293
return nothing
1294
end
1295
1296
# Discretizations specific to each mesh type of Trixi.jl
1297
1298
# If some functionality is shared by multiple combinations of meshes/solvers,
1299
# it is defined in the directory of the most basic mesh and solver type.
1300
# The most basic solver type in Trixi.jl is DGSEM (historic reasons and background
1301
# of the main contributors).
1302
# We consider the `TreeMesh` to be the most basic mesh type since it is Cartesian
1303
# and was the first mesh in Trixi.jl.
1304
# The order of the other mesh types is the same as the include order below.
1305
include("dgsem_tree/dg.jl")
1306
include("dgsem_structured/dg.jl")
1307
include("dgsem_unstructured/dg.jl")
1308
include("dgsem_p4est/dg.jl")
1309
include("dgsem_t8code/dg.jl")
1310
end # @muladd
1311
1312