Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
trixi-framework
GitHub Repository: trixi-framework/Trixi.jl
Path: blob/main/src/solvers/dgmulti/sbp.jl
5591 views
1
2
"""
3
DGMulti(approximation_type::AbstractDerivativeOperator;
4
element_type::AbstractElemShape,
5
surface_flux=flux_central,
6
surface_integral=SurfaceIntegralWeakForm(surface_flux),
7
volume_integral=VolumeIntegralWeakForm(),
8
kwargs...)
9
10
Create a summation by parts (SBP) discretization on the given `element_type`
11
using a tensor product structure based on the 1D SBP derivative operator
12
passed as `approximation_type`.
13
14
For more info, see the documentations of
15
[StartUpDG.jl](https://jlchan.github.io/StartUpDG.jl/dev/)
16
and
17
[SummationByPartsOperators.jl](https://ranocha.de/SummationByPartsOperators.jl/stable/).
18
"""
19
function DGMulti(approximation_type::AbstractDerivativeOperator;
20
element_type::AbstractElemShape,
21
surface_flux = flux_central,
22
surface_integral = SurfaceIntegralWeakForm(surface_flux),
23
volume_integral = VolumeIntegralWeakForm(),
24
kwargs...)
25
rd = RefElemData(element_type, approximation_type; kwargs...)
26
# `nothing` is passed as `mortar`
27
return DG(rd, nothing, surface_integral, volume_integral)
28
end
29
30
function DGMulti(element_type::AbstractElemShape,
31
approximation_type::AbstractDerivativeOperator,
32
volume_integral,
33
surface_integral;
34
kwargs...)
35
return DGMulti(approximation_type, element_type = element_type,
36
surface_integral = surface_integral, volume_integral = volume_integral)
37
end
38
39
# type alias for specializing on a periodic SBP operator
40
const DGMultiPeriodicFDSBP{NDIMS, ApproxType, ElemType} = DGMulti{NDIMS, ElemType,
41
ApproxType,
42
SurfaceIntegral,
43
VolumeIntegral} where {
44
NDIMS,
45
ElemType,
46
ApproxType <:
47
SummationByPartsOperators.AbstractPeriodicDerivativeOperator,
48
SurfaceIntegral,
49
VolumeIntegral
50
}
51
52
const DGMultiFluxDiffPeriodicFDSBP{NDIMS, ApproxType, ElemType} = DGMulti{NDIMS, ElemType,
53
ApproxType,
54
SurfaceIntegral,
55
VolumeIntegral} where {
56
NDIMS,
57
ElemType,
58
ApproxType <:
59
SummationByPartsOperators.AbstractPeriodicDerivativeOperator,
60
SurfaceIntegral <:
61
SurfaceIntegralWeakForm,
62
VolumeIntegral <:
63
VolumeIntegralFluxDifferencing
64
}
65
66
"""
67
DGMultiMesh(dg::DGMulti)
68
69
Constructs a single-element [`DGMultiMesh`](@ref) for a single periodic element given
70
a DGMulti with `approximation_type` set to a periodic (finite difference) SBP operator from
71
SummationByPartsOperators.jl.
72
"""
73
function DGMultiMesh(dg::DGMultiPeriodicFDSBP{NDIMS};
74
coordinates_min = ntuple(_ -> -one(real(dg)), NDIMS),
75
coordinates_max = ntuple(_ -> one(real(dg)), NDIMS)) where {NDIMS}
76
rd = dg.basis
77
78
e = Ones{eltype(rd.r)}(size(rd.r))
79
z = Zeros{eltype(rd.r)}(size(rd.r))
80
81
VXYZ = ntuple(_ -> [], NDIMS)
82
EToV = NaN # StartUpDG.jl uses size(EToV, 1) for the number of elements, this lets us reuse that.
83
FToF = []
84
85
# We need to scale the domain from `[-1, 1]^NDIMS` (default in StartUpDG.jl)
86
# to the given `coordinates_min, coordinates_max`
87
xyz = xyzq = map(copy, rd.rst)
88
for dim in 1:NDIMS
89
factor = (coordinates_max[dim] - coordinates_min[dim]) / 2
90
@. xyz[dim] = factor * (xyz[dim] + 1) + coordinates_min[dim]
91
end
92
xyzf = ntuple(_ -> [], NDIMS)
93
wJq = diag(rd.M)
94
95
# arrays of connectivity indices between face nodes
96
mapM = mapP = mapB = []
97
98
# volume geofacs Gij = dx_i/dxhat_j
99
coord_diffs = coordinates_max .- coordinates_min
100
101
J_scalar = prod(coord_diffs) / 2^NDIMS
102
J = e * J_scalar
103
104
if NDIMS == 1
105
rxJ = J_scalar * 2 / coord_diffs[1]
106
rstxyzJ = @SMatrix [rxJ * e]
107
elseif NDIMS == 2
108
rxJ = J_scalar * 2 / coord_diffs[1]
109
syJ = J_scalar * 2 / coord_diffs[2]
110
rstxyzJ = @SMatrix [rxJ*e z; z syJ*e]
111
elseif NDIMS == 3
112
rxJ = J_scalar * 2 / coord_diffs[1]
113
syJ = J_scalar * 2 / coord_diffs[2]
114
tzJ = J_scalar * 2 / coord_diffs[3]
115
rstxyzJ = @SMatrix [rxJ*e z z; z syJ*e z; z z tzJ*e]
116
end
117
118
# surface geofacs
119
nxyzJ = ntuple(_ -> [], NDIMS)
120
Jf = []
121
122
periodicity = ntuple(_ -> true, NDIMS)
123
124
if NDIMS == 1
125
mesh_type = Line()
126
elseif NDIMS == 2
127
mesh_type = Quad()
128
elseif NDIMS == 3
129
mesh_type = Hex()
130
end
131
132
md = MeshData(StartUpDG.VertexMappedMesh(mesh_type, VXYZ, EToV), FToF, xyz, xyzf, xyzq,
133
wJq,
134
mapM, mapP, mapB, rstxyzJ, J, nxyzJ, Jf,
135
periodicity)
136
137
boundary_faces = []
138
return DGMultiMesh{NDIMS, rd.element_type, typeof(md),
139
typeof(boundary_faces)}(md, boundary_faces)
140
end
141
142
# By default, Julia/LLVM does not use fused multiply-add operations (FMAs).
143
# Since these FMAs can increase the performance of many numerical algorithms,
144
# we need to opt-in explicitly.
145
# See https://ranocha.de/blog/Optimizing_EC_Trixi for further details.
146
@muladd begin
147
#! format: noindent
148
149
# specialized for DGMultiPeriodicFDSBP since there are no face nodes
150
# and thus no inverse trace constant for periodic domains.
151
function estimate_dt(mesh::DGMultiMesh, dg::DGMultiPeriodicFDSBP)
152
rd = dg.basis # RefElemData
153
return StartUpDG.estimate_h(rd, mesh.md)
154
end
155
156
# do nothing for interface terms if using a periodic operator
157
function prolong2interfaces!(cache, u,
158
mesh::DGMultiMesh, equations, dg::DGMultiPeriodicFDSBP)
159
@assert nelements(mesh, dg, cache) == 1
160
return nothing
161
end
162
163
function calc_interface_flux!(cache, surface_integral::SurfaceIntegralWeakForm,
164
mesh::DGMultiMesh,
165
have_nonconservative_terms::False, equations,
166
dg::DGMultiPeriodicFDSBP)
167
@assert nelements(mesh, dg, cache) == 1
168
return nothing
169
end
170
171
function calc_surface_integral!(du, u, mesh::DGMultiMesh, equations,
172
surface_integral::SurfaceIntegralWeakForm,
173
dg::DGMultiPeriodicFDSBP, cache)
174
@assert nelements(mesh, dg, cache) == 1
175
return nothing
176
end
177
178
function create_cache(mesh::DGMultiMesh, equations,
179
dg::DGMultiFluxDiffPeriodicFDSBP, RealT, uEltype)
180
md = mesh.md
181
182
solution_container = initialize_dgmulti_solution_container(mesh, equations, dg,
183
uEltype)
184
185
# since dg.basis.Drst is not skew-symmetric for CG operators, we explicitly construct
186
# the skew-symmetric operators for flux differencing
187
Qrst = map(A -> dg.basis.M * A, dg.basis.Drst)
188
189
# check for skew-symmetry
190
for dim in eachdim(mesh)
191
Q = Qrst[dim]
192
@assert isapprox(Q, -Q', atol = 1e-12, rtol = 0.0)
193
end
194
return (; solution_container, Qrst, invM = inv(dg.basis.M), invJ = inv.(md.J))
195
end
196
197
# Specialize calc_volume_integral for periodic SBP operators (assumes the operator is sparse).
198
function calc_volume_integral!(du, u, mesh::DGMultiMesh,
199
have_nonconservative_terms::False, equations,
200
volume_integral::VolumeIntegralFluxDifferencing,
201
dg::DGMultiFluxDiffPeriodicFDSBP, cache)
202
@unpack volume_flux = volume_integral
203
204
# We expect speedup over the serial version only when using two or more threads
205
# since the threaded version below does not exploit the symmetry properties,
206
# resulting in a performance penalty of 1/2
207
if Threads.nthreads() > 1
208
for dim in eachdim(mesh)
209
normal_direction = get_contravariant_vector(1, dim, mesh, cache)
210
211
# These are weak-form operators of the form `Q = M * D` where `M` is diagonal
212
# and `Q` is skew-symmetric.
213
# TODO: DGMulti.
214
# This would have to be changed if `have_nonconservative_terms = False()`
215
# because then `volume_flux` is non-symmetric.
216
A = cache.Qrst[dim]
217
218
# sparse_operator_data retrieves column indices and row offsets, but because
219
# A is skew-symmetric, these are also the row indices and column offsets.
220
A_base, row_ids, cols, vals = sparse_operator_data(A)
221
222
@threaded for i in row_ids
223
u_i = u[i]
224
du_i = du[i]
225
@inbounds for id in nzrange(A_base, i)
226
j = cols[id]
227
u_j = u[j]
228
229
# we use the negative of A_ij since A is skew-symmetric,
230
# and we are accessing the transpose of A.
231
A_ij = -vals[id]
232
AF_ij = 2 * A_ij *
233
volume_flux(u_i, u_j, normal_direction, equations)
234
du_i = du_i + AF_ij
235
end
236
du[i] = du_i
237
end
238
end
239
240
# apply M^{-1} once after all spatial dimensions.
241
@inbounds for i in eachindex(du)
242
du[i] = du[i] * cache.invM.diag[i]
243
end
244
245
else # if using two threads or fewer
246
247
# Exploit skew-symmetry to halve the number of flux evaluations (≈2x speedup).
248
# A = Qrst[dim] is skew-symmetric for periodic SBP operators on uniform grids, so
249
# A[i,j] = -A[j,i]. The stored CSC value vals[id] = A[j,i] = -A[i,j], hence
250
# we use -vals[id] to recover A[i,j], matching the multithreaded branch above.
251
for dim in eachdim(mesh)
252
normal_direction = get_contravariant_vector(1, dim, mesh, cache)
253
254
A = cache.Qrst[dim]
255
# sparse_operator_data retrieves column indices and row offsets, but because
256
# A is skew-symmetric, these are also the row indices and column offsets.
257
A_base, row_ids, cols, vals = sparse_operator_data(A)
258
259
@inbounds for i in row_ids
260
u_i = u[i]
261
du_i = du[i]
262
for id in nzrange(A_base, i)
263
j = cols[id]
264
# We use the symmetry of the volume flux and the anti-symmetry
265
# of the derivative operator to save half of the volume flux
266
# computations.
267
if j > i
268
A_ij = -vals[id] # A[j,i] stored; skew-symmetry: -A[j,i] = A[i,j]
269
u_j = u[j]
270
AF_ij = 2 * A_ij *
271
volume_flux(u_i, u_j, normal_direction, equations)
272
du_i = du_i + AF_ij
273
du[j] = du[j] - AF_ij # Due to skew-symmetry
274
end
275
end
276
du[i] = du_i
277
end
278
end
279
280
# apply M^{-1} only after all skew-symmetric contributions are
281
# accumulated over each dimension.
282
@inbounds for i in eachindex(du)
283
du[i] = du[i] * cache.invM.diag[i]
284
end
285
end
286
287
return nothing
288
end
289
end # @muladd
290
291