Skip to content

Commit bc7858a

Browse files
jlchanDanielDoehringranocha
authored
DGMulti shock capturing part 1: cleanup (#2940)
* changes related to cleanup of existing shock capturing remove extra end remove unnecessary comment * add 3d shock capturing elixir add 3d test * formatting * fix naming convention * Update src/solvers/dgmulti/shock_capturing.jl Co-authored-by: Daniel Doehring <doehringd2@gmail.com> * Update src/solvers/dgmulti/shock_capturing.jl Co-authored-by: Daniel Doehring <doehringd2@gmail.com> * Apply suggestions from code review Co-authored-by: Daniel Doehring <doehringd2@gmail.com> * formatting * num_modes -> nmodes --------- Co-authored-by: Daniel Doehring <doehringd2@gmail.com> Co-authored-by: Hendrik Ranocha <ranocha@users.noreply.github.com>
1 parent 3805c7c commit bc7858a

6 files changed

Lines changed: 143 additions & 29 deletions

File tree

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
using OrdinaryDiffEqLowStorageRK
2+
using Trixi
3+
4+
###############################################################################
5+
# semidiscretization of the compressible Euler equations
6+
7+
equations = CompressibleEulerEquations3D(1.4)
8+
9+
initial_condition = initial_condition_weak_blast_wave
10+
surface_flux = flux_lax_friedrichs
11+
volume_flux = flux_ranocha
12+
13+
polydeg = 3
14+
basis = DGMultiBasis(Hex(), polydeg, approximation_type = GaussSBP())
15+
16+
indicator_sc = IndicatorHennemannGassner(equations, basis,
17+
alpha_max = 0.5,
18+
alpha_min = 0.001,
19+
alpha_smooth = true,
20+
variable = density_pressure)
21+
volume_integral = VolumeIntegralShockCapturingHG(indicator_sc;
22+
volume_flux_dg = volume_flux,
23+
volume_flux_fv = surface_flux)
24+
dg = DGMulti(basis,
25+
surface_integral = SurfaceIntegralWeakForm(surface_flux),
26+
volume_integral = volume_integral)
27+
28+
cells_per_dimension = (4, 4, 4)
29+
mesh = DGMultiMesh(dg, cells_per_dimension;
30+
coordinates_min = (-2.0, -2.0, -2.0),
31+
coordinates_max = (2.0, 2.0, 2.0),
32+
periodicity = true)
33+
34+
semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, dg;
35+
boundary_conditions = boundary_condition_periodic)
36+
37+
tspan = (0.0, 0.4)
38+
ode = semidiscretize(semi, tspan)
39+
40+
summary_callback = SummaryCallback()
41+
alive_callback = AliveCallback(alive_interval = 10)
42+
analysis_interval = 100
43+
analysis_callback = AnalysisCallback(semi, interval = analysis_interval, uEltype = real(dg))
44+
save_solution = SaveSolutionCallback(interval = analysis_interval,
45+
solution_variables = cons2prim)
46+
callbacks = CallbackSet(summary_callback, alive_callback, analysis_callback, save_solution)
47+
48+
###############################################################################
49+
# run the simulation
50+
51+
sol = solve(ode, RDPK3SpFSAL49(); abstol = 1.0e-6, reltol = 1.0e-6,
52+
ode_default_options()..., callback = callbacks);

src/solvers/dgmulti/dg.jl

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,13 @@ In particular, not the face nodes themselves are returned.
143143
return Base.OneTo(dg.basis.Nfq * mesh.md.num_elements)
144144
end
145145

146+
# The `nmodes` functions returns the number of polynomial modes for a degree N
147+
# approximation on a specific type of element. For tensor product elements (Quad, Hex),
148+
# this is the total number of modes in all dimensions, i.e., (N+1)^NDIMS.
149+
@inline nmodes(N, ::Line) = N + 1
150+
@inline nmodes(N, ::Quad) = (N + 1)^2
151+
@inline nmodes(N, ::Hex) = (N + 1)^3
152+
146153
# interface with semidiscretization_hyperbolic
147154
wrap_array(u_ode, mesh::DGMultiMesh, equations, dg::DGMulti, cache) = u_ode
148155
wrap_array_native(u_ode, mesh::DGMultiMesh, equations, dg::DGMulti, cache) = u_ode

src/solvers/dgmulti/flux_differencing.jl

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,12 +73,28 @@ end
7373

7474
# Build element-to-element connectivity from face-to-face connectivity.
7575
# Used for smoothing of shock capturing blending parameters (see `apply_smoothing!`).
76+
#
77+
# Here, `mesh.md.FToF` is a `(num_faces_per_element × num_elements)` array where
78+
# `FToF[f, e]` stores the global face index of the neighbor of local face `f` on
79+
# element `e`.
80+
#
81+
# Global face indices are laid out as
82+
#
83+
# global_face_index = (element_index - 1) * num_faces + local_face_index,
84+
#
85+
# so that the element index can be recovered by integer division:
86+
#
87+
# element_index = (global_face_index - 1) ÷ num_faces + 1.
88+
#
89+
# For a non-periodic boundary face, `FToF[f, e]` points back to face `f` of element
90+
# `e` itself, so boundary elements are listed as their own neighbor.
7691
function build_element_to_element_connectivity(mesh::DGMultiMesh, dg::DGMulti)
7792
face_to_face_connectivity = mesh.md.FToF
7893
element_to_element_connectivity = similar(face_to_face_connectivity)
7994
for e in axes(face_to_face_connectivity, 2)
8095
for f in axes(face_to_face_connectivity, 1)
8196
neighbor_face_index = face_to_face_connectivity[f, e]
97+
8298
# Reverse-engineer element index from face index. Assumes all elements
8399
# have the same number of faces.
84100
neighbor_element_index = ((neighbor_face_index - 1) ÷ dg.basis.num_faces) +

src/solvers/dgmulti/shock_capturing.jl

Lines changed: 41 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
# Since `@muladd` can fuse multiply-add operations and thus improve performance in
2+
# the flux differencing loops, we opt-in explicitly.
3+
# See https://ranocha.de/blog/Optimizing_EC_Trixi for further details.
4+
@muladd begin
5+
#! format: noindent
6+
17
# by default, return an empty tuple for volume integral caches
28
function create_cache(mesh::DGMultiMesh{NDIMS}, equations,
39
volume_integral::VolumeIntegralShockCapturingHGType,
@@ -12,40 +18,52 @@ function create_cache(mesh::DGMultiMesh{NDIMS}, equations,
1218
# create sparse hybridized operators for low order scheme
1319
Qrst, E = StartUpDG.sparse_low_order_SBP_operators(dg.basis)
1420
Brst = map(n -> Diagonal(n .* dg.basis.wf), dg.basis.nrstJ)
15-
sparse_hybridized_SBP_operators = map((Q, B) -> 0.5 * [Q-Q' E'*B; -B*E zeros(size(B))],
16-
Qrst, Brst)
21+
sparse_SBP_operators = map((Q, B) -> 0.5f0 * [Q-Q' E'*B; -B*E zeros(size(B))],
22+
Qrst, Brst)
1723

1824
# Find the joint sparsity pattern of the entire matrix. We store the sparsity pattern as
1925
# an adjoint for faster iteration through the rows.
20-
sparsity_pattern = sum(map(A -> abs.(A)', sparse_hybridized_SBP_operators)) .>
26+
sparsity_pattern = sum(map(A -> abs.(A)', sparse_SBP_operators)) .>
2127
100 * eps()
2228

23-
return (; sparse_hybridized_SBP_operators, sparsity_pattern,
29+
return (; sparse_SBP_operators, sparsity_pattern,
2430
element_to_element_connectivity)
2531
end
2632

2733
# this method is used when the indicator is constructed as for shock-capturing volume integrals
2834
function create_cache(::Type{IndicatorHennemannGassner}, equations::AbstractEquations,
29-
basis::RefElemData{NDIMS}) where {NDIMS}
35+
basis::DGMultiBasis{NDIMS}) where {NDIMS}
3036
uEltype = real(basis)
3137
alpha = Vector{uEltype}()
3238
alpha_tmp = similar(alpha)
3339

34-
MVec = MVector{nnodes(basis), uEltype}
35-
indicator_threaded = MVec[MVec(undef) for _ in 1:Threads.maxthreadid()]
36-
modal_threaded = MVec[MVec(undef) for _ in 1:Threads.maxthreadid()]
40+
MVec_nodes = MVector{nnodes(basis), uEltype}
41+
indicator_threaded = MVec_nodes[MVec_nodes(undef) for _ in 1:Threads.maxthreadid()]
42+
MVec_modes = MVector{nmodes(basis.N, basis.element_type), uEltype}
43+
modal_threaded = MVec_modes[MVec_modes(undef) for _ in 1:Threads.maxthreadid()]
44+
45+
inverse_vandermonde = calc_inverse_vandermonde(basis)
3746

47+
return (; alpha, alpha_tmp, indicator_threaded, modal_threaded, inverse_vandermonde)
48+
end
49+
50+
# calculates the inverse of the Vandermonde matrix for shock capturing purposes.
51+
# This version is for tensor product elements (Line, Quad, Hex)
52+
function calc_inverse_vandermonde(basis::DGMultiBasis{NDIMS, <:Union{Line, Quad, Hex}}) where {NDIMS}
3853
# initialize inverse Vandermonde matrices at Gauss-Legendre nodes
3954
(; N) = basis
4055
lobatto_node_coordinates_1D, _ = StartUpDG.gauss_lobatto_quad(0, 0, N)
4156
VDM_1D = StartUpDG.vandermonde(Line(), N, lobatto_node_coordinates_1D)
4257
inverse_vandermonde = SimpleKronecker(NDIMS, inv(VDM_1D))
4358

44-
return (; alpha, alpha_tmp, indicator_threaded, modal_threaded, inverse_vandermonde)
59+
return inverse_vandermonde
4560
end
4661

4762
function (indicator_hg::IndicatorHennemannGassner)(u, mesh::DGMultiMesh,
48-
equations, dg::DGMulti{NDIMS}, cache;
63+
equations,
64+
dg::DGMulti{NDIMS,
65+
<:Union{Line, Quad, Hex}},
66+
cache;
4967
kwargs...) where {NDIMS}
5068
(; alpha_max, alpha_min, alpha_smooth, variable) = indicator_hg
5169
(; alpha, alpha_tmp, indicator_threaded, modal_threaded, inverse_vandermonde) = indicator_hg.cache
@@ -56,7 +74,7 @@ function (indicator_hg::IndicatorHennemannGassner)(u, mesh::DGMultiMesh,
5674
end
5775

5876
# magic parameters
59-
threshold = 0.5 * 10^(-1.8 * (dg.basis.N + 1)^0.25)
77+
threshold = 0.5f0 * 10^(-1.8 * (dg.basis.N + 1)^0.25)
6078
parameter_s = log((1 - 0.0001) / 0.0001)
6179

6280
@threaded for element in eachelement(mesh, dg)
@@ -101,7 +119,8 @@ function (indicator_hg::IndicatorHennemannGassner)(u, mesh::DGMultiMesh,
101119
energy_frac_1 = zero(total_energy)
102120
end
103121
if !(iszero(total_energy_clip1))
104-
energy_frac_2 = (total_energy_clip1 - total_energy_clip2) / total_energy_clip1
122+
energy_frac_2 = (total_energy_clip1 - total_energy_clip2) /
123+
total_energy_clip1
105124
else
106125
energy_frac_2 = zero(total_energy_clip1)
107126
end
@@ -134,15 +153,15 @@ end
134153
# Diffuse alpha values by setting each alpha to at least 50% of neighboring elements' alpha
135154
function apply_smoothing!(mesh::DGMultiMesh, alpha, alpha_tmp, dg::DGMulti, cache)
136155

137-
# Copy alpha values such that smoothing is indpedenent of the element access order
156+
# Copy alpha values such that smoothing is independent of the element access order
138157
alpha_tmp .= alpha
139158

140159
# smooth alpha with its neighboring value
141-
for element in eachelement(mesh, dg)
160+
@threaded for element in eachelement(mesh, dg)
142161
for face in Base.OneTo(StartUpDG.num_faces(dg.basis.element_type))
143162
neighboring_element = cache.element_to_element_connectivity[face, element]
144163
alpha_neighbor = alpha_tmp[neighboring_element]
145-
alpha[element] = max(alpha[element], 0.5 * alpha_neighbor)
164+
alpha[element] = max(alpha[element], 0.5f0 * alpha_neighbor)
146165
end
147166
end
148167

@@ -194,16 +213,16 @@ function calc_volume_integral!(du, u, mesh::DGMultiMesh,
194213
end
195214

196215
function get_sparse_operator_entries(i, j, mesh::DGMultiMesh{1}, cache)
197-
return SVector(cache.sparse_hybridized_SBP_operators[1][i, j])
216+
return SVector(cache.sparse_SBP_operators[1][i, j])
198217
end
199218

200219
function get_sparse_operator_entries(i, j, mesh::DGMultiMesh{2}, cache)
201-
Qr, Qs = cache.sparse_hybridized_SBP_operators
220+
Qr, Qs = cache.sparse_SBP_operators
202221
return SVector(Qr[i, j], Qs[i, j])
203222
end
204223

205224
function get_sparse_operator_entries(i, j, mesh::DGMultiMesh{3}, cache)
206-
Qr, Qs, Qt = cache.sparse_hybridized_SBP_operators
225+
Qr, Qs, Qt = cache.sparse_SBP_operators
207226
return SVector(Qr[i, j], Qs[i, j], Qt[i, j])
208227
end
209228

@@ -244,7 +263,7 @@ function get_contravariant_matrix(i, element, mesh::DGMultiMesh{3}, cache)
244263
end
245264

246265
function get_avg_contravariant_matrix(i, j, element, mesh::DGMultiMesh, cache)
247-
return 0.5 * (get_contravariant_matrix(i, element, mesh, cache) +
266+
return 0.5f0 * (get_contravariant_matrix(i, element, mesh, cache) +
248267
get_contravariant_matrix(j, element, mesh, cache))
249268
end
250269

@@ -290,7 +309,8 @@ function volume_integral_kernel!(du, u, element, mesh::DGMultiMesh,
290309
u_j = u_local[j]
291310

292311
# compute (Q_1[i,j], Q_2[i,j], ...) where Q_i = ∑_j dxidxhatj * Q̂_j
293-
geometric_matrix = get_low_order_geometric_matrix(i, j, element, mesh, cache)
312+
geometric_matrix = get_low_order_geometric_matrix(i, j, element,
313+
mesh, cache)
294314
reference_operator_entries = get_sparse_operator_entries(i, j, mesh, cache)
295315
normal_direction_ij = geometric_matrix * reference_operator_entries
296316

@@ -305,3 +325,4 @@ function volume_integral_kernel!(du, u, element, mesh::DGMultiMesh,
305325
# TODO: factor this out to avoid calling it twice during calc_volume_integral!
306326
return project_rhs_to_gauss_nodes!(du, rhs_local, element, mesh, dg, cache, alpha)
307327
end
328+
end # @muladd

src/solvers/dgmulti/types.jl

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,19 +23,21 @@ const DGMultiFluxDiff{ApproxType, ElemType} = DGMulti{NDIMS, ElemType, ApproxTyp
2323
<:SurfaceIntegralWeakForm,
2424
<:Union{VolumeIntegralFluxDifferencing,
2525
VolumeIntegralShockCapturingHGType,
26-
VolumeIntegralAdaptiveEC_WF_DG}} where {
27-
NDIMS
28-
}
26+
VolumeIntegralAdaptiveEC_WF_DG,
27+
VolumeIntegralPureLGLFiniteVolume}} where {
28+
NDIMS
29+
}
2930

3031
const DGMultiFluxDiffSBP{ApproxType, ElemType} = DGMulti{NDIMS, ElemType, ApproxType,
3132
<:SurfaceIntegralWeakForm,
3233
<:Union{VolumeIntegralFluxDifferencing,
33-
VolumeIntegralShockCapturingHGType}} where {
34-
NDIMS,
35-
ApproxType <:
36-
Union{SBP,
37-
AbstractDerivativeOperator}
38-
}
34+
VolumeIntegralShockCapturingHGType,
35+
VolumeIntegralPureLGLFiniteVolume}} where {
36+
NDIMS,
37+
ApproxType <:
38+
Union{SBP,
39+
AbstractDerivativeOperator}
40+
}
3941

4042
const DGMultiSBP{ApproxType, ElemType} = DGMulti{NDIMS, ElemType, ApproxType,
4143
SurfaceIntegral,
@@ -169,6 +171,10 @@ Constructs a basis for DGMulti solvers. Returns a "StartUpDG.RefElemData" object
169171
For more info, see the [StartUpDG.jl docs](https://jlchan.github.io/StartUpDG.jl/dev/).
170172
171173
"""
174+
const DGMultiBasis{NDIMS, element_type, approximation_type} = StartUpDG.RefElemData{NDIMS,
175+
element_type,
176+
approximation_type}
177+
172178
function DGMultiBasis(element_type, polydeg; approximation_type = Polynomial(),
173179
kwargs...)
174180
return RefElemData(element_type, approximation_type, polydeg; kwargs...)

test/test_dgmulti_3d.jl

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,18 @@ end
344344
n_cells_max = 0,
345345
RealT = Float64)
346346
end
347+
348+
@trixi_testset "elixir_euler_shockcapturing.jl (Hex, GaussSBP)" begin
349+
@test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_shockcapturing.jl"),
350+
cells_per_dimension=(4, 4, 4), tspan=(0.0, 0.1),
351+
l2=[1.33894396e-02, 7.62751979e-03, 7.62751979e-03,
352+
7.62751979e-03, 4.93582917e-02],
353+
linf=[2.18776976e-01, 1.04524872e-01, 1.04524872e-01,
354+
1.04524872e-01, 8.01212059e-01])
355+
# Ensure that we do not have excessive memory allocations
356+
# (e.g., from type instabilities)
357+
@test_allocations(Trixi.rhs!, semi, sol, 1000)
358+
end
347359
end
348360

349361
# Clean up afterwards: delete Trixi.jl output directory

0 commit comments

Comments
 (0)