Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
trixi-framework
GitHub Repository: trixi-framework/Trixi.jl
Path: blob/main/src/solvers/dgsem_tree/dg_2d.jl
5591 views
1
# By default, Julia/LLVM does not use fused multiply-add operations (FMAs).
2
# Since these FMAs can increase the performance of many numerical algorithms,
3
# we need to opt-in explicitly.
4
# See https://ranocha.de/blog/Optimizing_EC_Trixi for further details.
5
@muladd begin
6
#! format: noindent
7
8
# everything related to a DG semidiscretization in 2D,
9
# currently limited to Lobatto-Legendre nodes
10
11
# This method is called when a SemidiscretizationHyperbolic is constructed.
12
# It constructs the basic `cache` used throughout the simulation to compute
13
# the RHS etc.
14
function create_cache(mesh::Union{TreeMesh{2}, TreeMesh{3}}, equations,
15
dg::DG, RealT, uEltype)
16
# Get cells for which an element needs to be created (i.e. all leaf cells)
17
leaf_cell_ids = local_leaf_cells(mesh.tree)
18
19
elements = init_elements(leaf_cell_ids, mesh, equations, dg.basis, RealT, uEltype)
20
21
interfaces = init_interfaces(leaf_cell_ids, mesh, elements)
22
23
boundaries = init_boundaries(leaf_cell_ids, mesh, elements, dg.basis)
24
25
mortars = init_mortars(leaf_cell_ids, mesh, elements, dg.mortar)
26
27
# Container cache
28
cache = (; elements, interfaces, boundaries, mortars)
29
30
# Add Volume-Integral cache
31
cache = (; cache...,
32
create_cache(mesh, equations, dg.volume_integral, dg, cache, uEltype)...)
33
# Add Mortar cache
34
cache = (; cache..., create_cache(mesh, equations, dg.mortar, uEltype)...)
35
36
return cache
37
end
38
39
function create_f_threaded(mesh::AbstractMesh{2}, equations,
40
dg::DG, uEltype)
41
A3d = Array{uEltype, 3}
42
43
f1_L_threaded = A3d[A3d(undef, nvariables(equations),
44
nnodes(dg) + 1, nnodes(dg))
45
for _ in 1:Threads.maxthreadid()]
46
f1_R_threaded = A3d[A3d(undef, nvariables(equations),
47
nnodes(dg) + 1, nnodes(dg))
48
for _ in 1:Threads.maxthreadid()]
49
f2_L_threaded = A3d[A3d(undef, nvariables(equations),
50
nnodes(dg), nnodes(dg) + 1)
51
for _ in 1:Threads.maxthreadid()]
52
f2_R_threaded = A3d[A3d(undef, nvariables(equations),
53
nnodes(dg), nnodes(dg) + 1)
54
for _ in 1:Threads.maxthreadid()]
55
56
@threaded for t in eachindex(f1_L_threaded)
57
f1_L_threaded[t][:, 1, :] .= zero(uEltype)
58
f1_R_threaded[t][:, 1, :] .= zero(uEltype)
59
f1_L_threaded[t][:, nnodes(dg) + 1, :] .= zero(uEltype)
60
f1_R_threaded[t][:, nnodes(dg) + 1, :] .= zero(uEltype)
61
62
f2_L_threaded[t][:, :, 1] .= zero(uEltype)
63
f2_R_threaded[t][:, :, 1] .= zero(uEltype)
64
f2_L_threaded[t][:, :, nnodes(dg) + 1] .= zero(uEltype)
65
f2_R_threaded[t][:, :, nnodes(dg) + 1] .= zero(uEltype)
66
end
67
68
return f1_L_threaded, f1_R_threaded,
69
f2_L_threaded, f2_R_threaded
70
end
71
72
function create_cache(mesh::TreeMesh{2}, equations,
73
volume_integral::AbstractVolumeIntegralSubcell,
74
dg::DG, cache_containers, uEltype)
75
fstar1_L_threaded, fstar1_R_threaded,
76
fstar2_L_threaded, fstar2_R_threaded = create_f_threaded(mesh, equations, dg,
77
uEltype)
78
79
cache_subcell_limiting = create_cache_subcell_limiting(mesh, equations,
80
volume_integral, dg,
81
cache_containers, uEltype)
82
83
return (; fstar1_L_threaded, fstar1_R_threaded,
84
fstar2_L_threaded, fstar2_R_threaded,
85
cache_subcell_limiting...)
86
end
87
88
# The methods below are specialized on the mortar type
89
# and called from the basic `create_cache` method at the top.
90
function create_cache(mesh::TreeMesh{2}, equations,
91
mortar_l2::LobattoLegendreMortarL2, uEltype)
92
MA2d = MArray{Tuple{nvariables(equations), nnodes(mortar_l2)},
93
uEltype, 2,
94
nvariables(equations) * nnodes(mortar_l2)}
95
fstar_primary_upper_threaded = MA2d[MA2d(undef) for _ in 1:Threads.maxthreadid()]
96
fstar_primary_lower_threaded = MA2d[MA2d(undef) for _ in 1:Threads.maxthreadid()]
97
fstar_secondary_upper_threaded = MA2d[MA2d(undef) for _ in 1:Threads.maxthreadid()]
98
fstar_secondary_lower_threaded = MA2d[MA2d(undef) for _ in 1:Threads.maxthreadid()]
99
100
cache = (; fstar_primary_upper_threaded, fstar_primary_lower_threaded,
101
fstar_secondary_upper_threaded, fstar_secondary_lower_threaded)
102
103
return cache
104
end
105
106
# TODO: Taal discuss/refactor timer, allowing users to pass a custom timer?
107
108
# This function is valid for all non-conforming mesh types, i.e.,
109
# all meshes that do involve mortar operations.
110
# Thus, we can use it for the serial (i.e., non-distributed memory parallelized)
111
# 2D/3D `TreeMesh`es, `P4estMesh`es, and `T8codeMesh`es.
112
function rhs!(du, u, t,
113
mesh::Union{TreeMesh{2}, P4estMesh{2}, P4estMeshView{2}, T8codeMesh{2},
114
TreeMesh{3}, P4estMesh{3}, T8codeMesh{3}},
115
equations,
116
boundary_conditions, source_terms::Source,
117
dg::DG, cache) where {Source}
118
backend = trixi_backend(u)
119
120
# Reset du
121
@trixi_timeit_ext backend timer() "reset ∂u/∂t" begin
122
set_zero!(du, dg, cache)
123
end
124
125
# Calculate volume integral
126
@trixi_timeit_ext backend timer() "volume integral" begin
127
calc_volume_integral!(backend, du, u, mesh,
128
have_nonconservative_terms(equations), equations,
129
dg.volume_integral, dg, cache)
130
end
131
132
# Prolong solution to interfaces
133
@trixi_timeit_ext backend timer() "prolong2interfaces" begin
134
prolong2interfaces!(backend, cache, u, mesh, equations, dg)
135
end
136
137
# Calculate interface fluxes
138
@trixi_timeit_ext backend timer() "interface flux" begin
139
calc_interface_flux!(backend, cache.elements.surface_flux_values, mesh,
140
have_nonconservative_terms(equations), equations,
141
dg.surface_integral, dg, cache)
142
end
143
144
# Prolong solution to boundaries
145
@trixi_timeit_ext backend timer() "prolong2boundaries" begin
146
prolong2boundaries!(cache, u, mesh, equations, dg)
147
end
148
149
# Calculate boundary fluxes
150
@trixi_timeit_ext backend timer() "boundary flux" begin
151
calc_boundary_flux!(cache, t, boundary_conditions, mesh, equations,
152
dg.surface_integral, dg)
153
end
154
155
# Prolong solution to mortars
156
@trixi_timeit_ext backend timer() "prolong2mortars" begin
157
prolong2mortars!(cache, u, mesh, equations,
158
dg.mortar, dg)
159
end
160
161
# Calculate mortar fluxes
162
@trixi_timeit_ext backend timer() "mortar flux" begin
163
calc_mortar_flux!(cache.elements.surface_flux_values, mesh,
164
have_nonconservative_terms(equations), equations,
165
dg.mortar, dg.surface_integral, dg, cache)
166
end
167
168
# Calculate surface integrals
169
@trixi_timeit_ext backend timer() "surface integral" begin
170
calc_surface_integral!(backend, du, u, mesh, equations,
171
dg.surface_integral, dg, cache)
172
end
173
174
# Apply Jacobian from mapping to reference element
175
@trixi_timeit_ext backend timer() "Jacobian" begin
176
apply_jacobian!(backend, du, mesh, equations, dg, cache)
177
end
178
179
# Calculate source terms
180
@trixi_timeit_ext backend timer() "source terms" begin
181
calc_sources!(du, u, t, source_terms, equations, dg, cache)
182
end
183
184
return nothing
185
end
186
187
#=
188
`weak_form_kernel!` is only implemented for conserved terms as
189
non-conservative terms should always be discretized in conjunction with a flux-splitting scheme,
190
see `flux_differencing_kernel!`.
191
This treatment is required to achieve, e.g., entropy-stability or well-balancedness.
192
See also https://github.com/trixi-framework/Trixi.jl/issues/1671#issuecomment-1765644064
193
=#
194
@inline function weak_form_kernel!(du, u,
195
element, ::Type{<:TreeMesh{2}},
196
have_nonconservative_terms::False, equations,
197
dg::DGSEM, cache, alpha = true)
198
# true * [some floating point value] == [exactly the same floating point value]
199
# This can (hopefully) be optimized away due to constant propagation.
200
@unpack derivative_hat = dg.basis
201
202
# Calculate volume terms in one element
203
for j in eachnode(dg), i in eachnode(dg)
204
u_node = get_node_vars(u, equations, dg, i, j, element)
205
206
flux1 = flux(u_node, 1, equations)
207
for ii in eachnode(dg)
208
multiply_add_to_node_vars!(du, alpha * derivative_hat[ii, i], flux1,
209
equations, dg, ii, j, element)
210
end
211
212
flux2 = flux(u_node, 2, equations)
213
for jj in eachnode(dg)
214
multiply_add_to_node_vars!(du, alpha * derivative_hat[jj, j], flux2,
215
equations, dg, i, jj, element)
216
end
217
end
218
219
return nothing
220
end
221
222
@inline function flux_differencing_kernel!(du, u, element, ::Type{<:TreeMesh{2}},
223
have_nonconservative_terms::False, equations,
224
volume_flux, dg::DGSEM, cache, alpha = true)
225
# true * [some floating point value] == [exactly the same floating point value]
226
# This can (hopefully) be optimized away due to constant propagation.
227
@unpack derivative_split = dg.basis
228
229
# Calculate volume integral in one element
230
for j in eachnode(dg), i in eachnode(dg)
231
u_node = get_node_vars(u, equations, dg, i, j, element)
232
233
# All diagonal entries of `derivative_split` are zero. Thus, we can skip
234
# the computation of the diagonal terms. In addition, we use the symmetry
235
# of the `volume_flux` to save half of the possible two-point flux
236
# computations.
237
238
# x direction
239
for ii in (i + 1):nnodes(dg)
240
u_node_ii = get_node_vars(u, equations, dg, ii, j, element)
241
flux1 = volume_flux(u_node, u_node_ii, 1, equations)
242
multiply_add_to_node_vars!(du, alpha * derivative_split[i, ii], flux1,
243
equations, dg, i, j, element)
244
multiply_add_to_node_vars!(du, alpha * derivative_split[ii, i], flux1,
245
equations, dg, ii, j, element)
246
end
247
248
# y direction
249
for jj in (j + 1):nnodes(dg)
250
u_node_jj = get_node_vars(u, equations, dg, i, jj, element)
251
flux2 = volume_flux(u_node, u_node_jj, 2, equations)
252
multiply_add_to_node_vars!(du, alpha * derivative_split[j, jj], flux2,
253
equations, dg, i, j, element)
254
multiply_add_to_node_vars!(du, alpha * derivative_split[jj, j], flux2,
255
equations, dg, i, jj, element)
256
end
257
end
258
end
259
260
@inline function flux_differencing_kernel!(du, u, element, MeshT::Type{<:TreeMesh{2}},
261
have_nonconservative_terms::True, equations,
262
volume_flux, dg::DGSEM, cache, alpha = true)
263
# true * [some floating point value] == [exactly the same floating point value]
264
# This can (hopefully) be optimized away due to constant propagation.
265
@unpack derivative_split = dg.basis
266
symmetric_flux, nonconservative_flux = volume_flux
267
268
# Apply the symmetric flux as usual
269
flux_differencing_kernel!(du, u, element, MeshT, False(), equations, symmetric_flux,
270
dg, cache, alpha)
271
272
# Calculate the remaining volume terms using the nonsymmetric generalized flux
273
for j in eachnode(dg), i in eachnode(dg)
274
u_node = get_node_vars(u, equations, dg, i, j, element)
275
276
# The diagonal terms are zero since the diagonal of `derivative_split`
277
# is zero. We ignore this for now.
278
279
# x direction
280
integral_contribution = zero(u_node)
281
for ii in eachnode(dg)
282
u_node_ii = get_node_vars(u, equations, dg, ii, j, element)
283
noncons_flux1 = nonconservative_flux(u_node, u_node_ii, 1, equations)
284
integral_contribution = integral_contribution +
285
derivative_split[i, ii] * noncons_flux1
286
end
287
288
# y direction
289
for jj in eachnode(dg)
290
u_node_jj = get_node_vars(u, equations, dg, i, jj, element)
291
noncons_flux2 = nonconservative_flux(u_node, u_node_jj, 2, equations)
292
integral_contribution = integral_contribution +
293
derivative_split[j, jj] * noncons_flux2
294
end
295
296
# The factor 0.5 cancels the factor 2 in the flux differencing form
297
multiply_add_to_node_vars!(du, alpha * 0.5f0, integral_contribution, equations,
298
dg, i, j, element)
299
end
300
end
301
302
@inline function fvO2_kernel!(du, u,
303
MeshT::Type{<:Union{TreeMesh{2}, StructuredMesh{2},
304
UnstructuredMesh2D, P4estMesh{2},
305
T8codeMesh{2}}},
306
have_nonconservative_terms, equations,
307
volume_flux_fv, dg::DGSEM, cache, element,
308
sc_interface_coords, reconstruction_mode, slope_limiter,
309
cons2recon, recon2cons,
310
alpha = true)
311
@unpack fstar1_L_threaded, fstar1_R_threaded, fstar2_L_threaded, fstar2_R_threaded = cache
312
@unpack inverse_weights = dg.basis # Plays role of inverse DG-subcell sizes
313
314
# Calculate FV two-point fluxes
315
fstar1_L = fstar1_L_threaded[Threads.threadid()]
316
fstar2_L = fstar2_L_threaded[Threads.threadid()]
317
fstar1_R = fstar1_R_threaded[Threads.threadid()]
318
fstar2_R = fstar2_R_threaded[Threads.threadid()]
319
calcflux_fvO2!(fstar1_L, fstar1_R, fstar2_L, fstar2_R, u, MeshT,
320
have_nonconservative_terms, equations,
321
volume_flux_fv, dg, element, cache,
322
sc_interface_coords, reconstruction_mode, slope_limiter,
323
cons2recon, recon2cons)
324
325
# Calculate FV volume integral contribution
326
for j in eachnode(dg), i in eachnode(dg)
327
for v in eachvariable(equations)
328
du[v, i, j, element] += (alpha *
329
(inverse_weights[i] *
330
(fstar1_L[v, i + 1, j] - fstar1_R[v, i, j]) +
331
inverse_weights[j] *
332
(fstar2_L[v, i, j + 1] - fstar2_R[v, i, j])))
333
end
334
end
335
336
return nothing
337
end
338
339
@inline function calcflux_fvO2!(fstar1_L, fstar1_R, fstar2_L, fstar2_R, u,
340
::Type{<:TreeMesh{2}},
341
have_nonconservative_terms::False,
342
equations, volume_flux_fv, dg::DGSEM, element, cache,
343
sc_interface_coords, reconstruction_mode, slope_limiter,
344
cons2recon, recon2cons)
345
for j in eachnode(dg), i in 2:nnodes(dg)
346
# We compute FV02 fluxes at the (nnodes(dg) - 1) subcell boundaries
347
# See `calcflux_fvO2!` in dg_1d.jl for a schematic of how it works
348
349
# The left subcell node values are labelled `_ll` (left-left) and `_lr` (left-right), while
350
# the right subcell node values are labelled `_rl` (right-left) and `_rr` (right-right).
351
352
## Obtain unlimited values in reconstruction variables ##
353
354
# Note: If i - 2 = 0 we do not go to neighbor element, as one would do in a finite volume scheme.
355
# Here, we keep it purely cell-local, thus overshoots between elements are not strictly ruled out,
356
# **unless** `reconstruction_mode` is set to `reconstruction_O2_inner`
357
u_ll = cons2recon(get_node_vars(u, equations, dg, max(1, i - 2), j, element),
358
equations)
359
u_lr = cons2recon(get_node_vars(u, equations, dg, i - 1, j, element),
360
equations)
361
u_rl = cons2recon(get_node_vars(u, equations, dg, i, j, element),
362
equations)
363
# Note: If i + 1 > nnodes(dg) we do not go to neighbor element, as one would do in a finite volume scheme.
364
# Here, we keep it purely cell-local, thus overshoots between elements are not strictly ruled out,
365
# **unless** `reconstruction_mode` is set to `reconstruction_O2_inner`
366
u_rr = cons2recon(get_node_vars(u, equations, dg, min(nnodes(dg), i + 1), j,
367
element), equations)
368
369
## Reconstruct values at interfaces with limiting ##
370
u_l, u_r = reconstruction_mode(u_ll, u_lr, u_rl, u_rr,
371
sc_interface_coords, i,
372
slope_limiter, dg)
373
374
## Convert reconstruction variables back to conservative variables ##
375
flux = volume_flux_fv(recon2cons(u_l, equations), recon2cons(u_r, equations),
376
1, equations) # orientation 1: x direction
377
378
set_node_vars!(fstar1_L, flux, equations, dg, i, j)
379
set_node_vars!(fstar1_R, flux, equations, dg, i, j)
380
end
381
382
for j in 2:nnodes(dg), i in eachnode(dg)
383
u_ll = cons2recon(get_node_vars(u, equations, dg, i, max(1, j - 2), element),
384
equations)
385
u_lr = cons2recon(get_node_vars(u, equations, dg, i, j - 1, element),
386
equations)
387
u_rl = cons2recon(get_node_vars(u, equations, dg, i, j, element),
388
equations)
389
u_rr = cons2recon(get_node_vars(u, equations, dg, i, min(nnodes(dg), j + 1),
390
element), equations)
391
392
u_l, u_r = reconstruction_mode(u_ll, u_lr, u_rl, u_rr,
393
sc_interface_coords, j,
394
slope_limiter, dg)
395
396
flux = volume_flux_fv(recon2cons(u_l, equations), recon2cons(u_r, equations),
397
2, equations) # orientation 2: y direction
398
399
set_node_vars!(fstar2_L, flux, equations, dg, i, j)
400
set_node_vars!(fstar2_R, flux, equations, dg, i, j)
401
end
402
403
return nothing
404
end
405
406
@inline function fv_kernel!(du, u,
407
MeshT::Type{<:Union{TreeMesh{2}, StructuredMesh{2},
408
UnstructuredMesh2D, P4estMesh{2},
409
T8codeMesh{2}}},
410
have_nonconservative_terms, equations,
411
volume_flux_fv, dg::DGSEM, cache, element, alpha = true)
412
@unpack fstar1_L_threaded, fstar1_R_threaded, fstar2_L_threaded, fstar2_R_threaded = cache
413
@unpack inverse_weights = dg.basis # Plays role of inverse DG-subcell sizes
414
415
# Calculate FV two-point fluxes
416
fstar1_L = fstar1_L_threaded[Threads.threadid()]
417
fstar2_L = fstar2_L_threaded[Threads.threadid()]
418
fstar1_R = fstar1_R_threaded[Threads.threadid()]
419
fstar2_R = fstar2_R_threaded[Threads.threadid()]
420
calcflux_fv!(fstar1_L, fstar1_R, fstar2_L, fstar2_R, u, MeshT,
421
have_nonconservative_terms, equations, volume_flux_fv, dg, element,
422
cache)
423
424
# Calculate FV volume integral contribution
425
for j in eachnode(dg), i in eachnode(dg)
426
for v in eachvariable(equations)
427
du[v, i, j, element] += (alpha *
428
(inverse_weights[i] *
429
(fstar1_L[v, i + 1, j] - fstar1_R[v, i, j]) +
430
inverse_weights[j] *
431
(fstar2_L[v, i, j + 1] - fstar2_R[v, i, j])))
432
end
433
end
434
435
return nothing
436
end
437
438
# Compute the normal flux for the FV method on cartesian subcells, see
439
# Hennemann, Rueda-Ramírez, Hindenlang, Gassner (2020)
440
# "A provably entropy stable subcell shock capturing approach for high order split form DG for the compressible Euler equations"
441
# [arXiv: 2008.12044v2](https://arxiv.org/pdf/2008.12044)
442
@inline function calcflux_fv!(fstar1_L, fstar1_R, fstar2_L, fstar2_R, u,
443
::Type{<:TreeMesh{2}},
444
have_nonconservative_terms::False, equations,
445
volume_flux_fv, dg::DGSEM, element, cache)
446
for j in eachnode(dg), i in 2:nnodes(dg)
447
u_ll = get_node_vars(u, equations, dg, i - 1, j, element)
448
u_rr = get_node_vars(u, equations, dg, i, j, element)
449
flux = volume_flux_fv(u_ll, u_rr, 1, equations) # orientation 1: x direction
450
set_node_vars!(fstar1_L, flux, equations, dg, i, j)
451
set_node_vars!(fstar1_R, flux, equations, dg, i, j)
452
end
453
454
for j in 2:nnodes(dg), i in eachnode(dg)
455
u_ll = get_node_vars(u, equations, dg, i, j - 1, element)
456
u_rr = get_node_vars(u, equations, dg, i, j, element)
457
flux = volume_flux_fv(u_ll, u_rr, 2, equations) # orientation 2: y direction
458
set_node_vars!(fstar2_L, flux, equations, dg, i, j)
459
set_node_vars!(fstar2_R, flux, equations, dg, i, j)
460
end
461
462
return nothing
463
end
464
465
@inline function calcflux_fv!(fstar1_L, fstar1_R, fstar2_L, fstar2_R, u,
466
::Type{<:TreeMesh{2}},
467
have_nonconservative_terms::True, equations,
468
volume_flux_fv, dg::DGSEM, element, cache)
469
volume_flux, nonconservative_flux = volume_flux_fv
470
471
# Fluxes in x-direction
472
for j in eachnode(dg), i in 2:nnodes(dg)
473
u_ll = get_node_vars(u, equations, dg, i - 1, j, element)
474
u_rr = get_node_vars(u, equations, dg, i, j, element)
475
476
# Compute conservative part
477
f1 = volume_flux(u_ll, u_rr, 1, equations) # orientation 1: x direction
478
479
# Compute nonconservative part
480
# Note the factor 0.5 necessary for the nonconservative fluxes based on
481
# the interpretation of global SBP operators coupled discontinuously via
482
# central fluxes/SATs
483
f1_L = f1 + 0.5f0 * nonconservative_flux(u_ll, u_rr, 1, equations)
484
f1_R = f1 + 0.5f0 * nonconservative_flux(u_rr, u_ll, 1, equations)
485
486
# Copy to temporary storage
487
set_node_vars!(fstar1_L, f1_L, equations, dg, i, j)
488
set_node_vars!(fstar1_R, f1_R, equations, dg, i, j)
489
end
490
491
# Fluxes in y-direction
492
for j in 2:nnodes(dg), i in eachnode(dg)
493
u_ll = get_node_vars(u, equations, dg, i, j - 1, element)
494
u_rr = get_node_vars(u, equations, dg, i, j, element)
495
496
# Compute conservative part
497
f2 = volume_flux(u_ll, u_rr, 2, equations) # orientation 2: y direction
498
499
# Compute nonconservative part
500
# Note the factor 0.5 necessary for the nonconservative fluxes based on
501
# the interpretation of global SBP operators coupled discontinuously via
502
# central fluxes/SATs
503
f2_L = f2 + 0.5f0 * nonconservative_flux(u_ll, u_rr, 2, equations)
504
f2_R = f2 + 0.5f0 * nonconservative_flux(u_rr, u_ll, 2, equations)
505
506
# Copy to temporary storage
507
set_node_vars!(fstar2_L, f2_L, equations, dg, i, j)
508
set_node_vars!(fstar2_R, f2_R, equations, dg, i, j)
509
end
510
511
return nothing
512
end
513
514
function prolong2interfaces!(backend::Nothing, cache, u, mesh::TreeMesh{2}, equations,
515
dg::DG)
516
@unpack interfaces = cache
517
@unpack orientations, neighbor_ids = interfaces
518
interfaces_u = interfaces.u
519
520
@threaded for interface in eachinterface(dg, cache)
521
left_element = neighbor_ids[1, interface]
522
right_element = neighbor_ids[2, interface]
523
524
if orientations[interface] == 1
525
# interface in x-direction
526
for j in eachnode(dg), v in eachvariable(equations)
527
interfaces_u[1, v, j, interface] = u[v, nnodes(dg), j, left_element]
528
interfaces_u[2, v, j, interface] = u[v, 1, j, right_element]
529
end
530
else # if orientations[interface] == 2
531
# interface in y-direction
532
for i in eachnode(dg), v in eachvariable(equations)
533
interfaces_u[1, v, i, interface] = u[v, i, nnodes(dg), left_element]
534
interfaces_u[2, v, i, interface] = u[v, i, 1, right_element]
535
end
536
end
537
end
538
539
return nothing
540
end
541
542
function prolong2interfaces!(backend::Nothing, cache, u, mesh::TreeMesh{2}, equations,
543
dg::DGSEM{<:GaussLegendreBasis})
544
@unpack interfaces = cache
545
@unpack orientations, neighbor_ids = interfaces
546
@unpack boundary_interpolation = dg.basis
547
interfaces_u = interfaces.u
548
549
@threaded for interface in eachinterface(dg, cache)
550
left_element = neighbor_ids[1, interface]
551
right_element = neighbor_ids[2, interface]
552
553
if orientations[interface] == 1
554
# interface in x-direction
555
for j in eachnode(dg)
556
for v in eachvariable(equations)
557
# Interpolate to the interfaces using a local variable for
558
# the accumulation of values (to reduce global memory operations).
559
interface_u_1 = zero(eltype(interfaces_u))
560
interface_u_2 = zero(eltype(interfaces_u))
561
for ii in eachnode(dg)
562
# Not += to allow `@muladd` to turn these into FMAs
563
# (see comment at the top of the file)
564
interface_u_1 = (interface_u_1 +
565
u[v, ii, j, left_element] *
566
boundary_interpolation[ii, 2])
567
interface_u_2 = (interface_u_2 +
568
u[v, ii, j, right_element] *
569
boundary_interpolation[ii, 1])
570
end
571
interfaces_u[1, v, j, interface] = interface_u_1
572
interfaces_u[2, v, j, interface] = interface_u_2
573
end
574
end
575
else # if orientations[interface] == 2
576
# interface in y-direction
577
for i in eachnode(dg)
578
for v in eachvariable(equations)
579
interface_u_1 = zero(eltype(interfaces_u))
580
interface_u_2 = zero(eltype(interfaces_u))
581
for jj in eachnode(dg)
582
interface_u_1 = (interface_u_1 +
583
u[v, i, jj, left_element] *
584
boundary_interpolation[jj, 2])
585
interface_u_2 = (interface_u_2 +
586
u[v, i, jj, right_element] *
587
boundary_interpolation[jj, 1])
588
end
589
interfaces_u[1, v, i, interface] = interface_u_1
590
interfaces_u[2, v, i, interface] = interface_u_2
591
end
592
end
593
end
594
end
595
596
return nothing
597
end
598
599
function calc_interface_flux!(backend::Nothing, surface_flux_values,
600
mesh::TreeMesh{2},
601
have_nonconservative_terms::False, equations,
602
surface_integral, dg::DG, cache)
603
@unpack surface_flux = surface_integral
604
@unpack u, neighbor_ids, orientations = cache.interfaces
605
606
@threaded for interface in eachinterface(dg, cache)
607
# Get neighboring elements
608
left_id = neighbor_ids[1, interface]
609
right_id = neighbor_ids[2, interface]
610
611
# Determine interface direction with respect to elements:
612
# orientation = 1: left -> 2, right -> 1
613
# orientation = 2: left -> 4, right -> 3
614
left_direction = 2 * orientations[interface]
615
right_direction = 2 * orientations[interface] - 1
616
617
for i in eachnode(dg)
618
# Call pointwise Riemann solver
619
u_ll, u_rr = get_surface_node_vars(u, equations, dg, i, interface)
620
flux = surface_flux(u_ll, u_rr, orientations[interface], equations)
621
622
# Copy flux to left and right element storage
623
for v in eachvariable(equations)
624
surface_flux_values[v, i, left_direction, left_id] = flux[v]
625
surface_flux_values[v, i, right_direction, right_id] = flux[v]
626
end
627
end
628
end
629
630
return nothing
631
end
632
633
function calc_interface_flux!(backend::Nothing, surface_flux_values,
634
mesh::TreeMesh{2},
635
have_nonconservative_terms::True, equations,
636
surface_integral, dg::DG, cache)
637
surface_flux, nonconservative_flux = surface_integral.surface_flux
638
@unpack u, neighbor_ids, orientations = cache.interfaces
639
640
@threaded for interface in eachinterface(dg, cache)
641
# Get neighboring elements
642
left_id = neighbor_ids[1, interface]
643
right_id = neighbor_ids[2, interface]
644
645
# Determine interface direction with respect to elements:
646
# orientation = 1: left -> 2, right -> 1
647
# orientation = 2: left -> 4, right -> 3
648
left_direction = 2 * orientations[interface]
649
right_direction = 2 * orientations[interface] - 1
650
651
for i in eachnode(dg)
652
# Call pointwise Riemann solver
653
orientation = orientations[interface]
654
u_ll, u_rr = get_surface_node_vars(u, equations, dg, i, interface)
655
flux = surface_flux(u_ll, u_rr, orientation, equations)
656
657
# Compute both nonconservative fluxes
658
noncons_left = nonconservative_flux(u_ll, u_rr, orientation, equations)
659
noncons_right = nonconservative_flux(u_rr, u_ll, orientation, equations)
660
661
# Copy flux to left and right element storage
662
for v in eachvariable(equations)
663
# Note the factor 0.5 necessary for the nonconservative fluxes based on
664
# the interpretation of global SBP operators coupled discontinuously via
665
# central fluxes/SATs
666
surface_flux_values[v, i, left_direction, left_id] = flux[v] +
667
0.5f0 *
668
noncons_left[v]
669
surface_flux_values[v, i, right_direction, right_id] = flux[v] +
670
0.5f0 *
671
noncons_right[v]
672
end
673
end
674
end
675
676
return nothing
677
end
678
679
function prolong2boundaries!(cache, u,
680
mesh::TreeMesh{2}, equations, dg::DG)
681
@unpack boundaries = cache
682
@unpack orientations, neighbor_sides = boundaries
683
684
@threaded for boundary in eachboundary(dg, cache)
685
element = boundaries.neighbor_ids[boundary]
686
687
if orientations[boundary] == 1
688
# boundary in x-direction
689
if neighbor_sides[boundary] == 1
690
# element in -x direction of boundary
691
for l in eachnode(dg), v in eachvariable(equations)
692
boundaries.u[1, v, l, boundary] = u[v, nnodes(dg), l, element]
693
end
694
else # Element in +x direction of boundary
695
for l in eachnode(dg), v in eachvariable(equations)
696
boundaries.u[2, v, l, boundary] = u[v, 1, l, element]
697
end
698
end
699
else # if orientations[boundary] == 2
700
# boundary in y-direction
701
if neighbor_sides[boundary] == 1
702
# element in -y direction of boundary
703
for l in eachnode(dg), v in eachvariable(equations)
704
boundaries.u[1, v, l, boundary] = u[v, l, nnodes(dg), element]
705
end
706
else
707
# element in +y direction of boundary
708
for l in eachnode(dg), v in eachvariable(equations)
709
boundaries.u[2, v, l, boundary] = u[v, l, 1, element]
710
end
711
end
712
end
713
end
714
715
return nothing
716
end
717
718
function prolong2boundaries!(cache, u,
719
mesh::TreeMesh{2}, equations,
720
dg::DGSEM{<:GaussLegendreBasis})
721
@unpack boundaries = cache
722
@unpack orientations, neighbor_sides = boundaries
723
@unpack boundary_interpolation = dg.basis
724
725
@threaded for boundary in eachboundary(dg, cache)
726
element = boundaries.neighbor_ids[boundary]
727
728
if orientations[boundary] == 1
729
# boundary in x-direction
730
if neighbor_sides[boundary] == 1
731
# element in -x direction of boundary => interpolate to right boundary node (+1)
732
for l in eachnode(dg)
733
for v in eachvariable(equations)
734
# Interpolate to the boundaries using a local variable for
735
# the accumulation of values (to reduce global memory operations).
736
boundary_u = zero(eltype(boundaries.u))
737
for ii in eachnode(dg)
738
# Not += to allow `@muladd` to turn these into FMAs
739
# (see comment at the top of the file)
740
boundary_u = (boundary_u +
741
u[v, ii, l, element] *
742
boundary_interpolation[ii, 2])
743
end
744
boundaries.u[1, v, l, boundary] = boundary_u
745
end
746
end
747
else # element in +x direction of boundary => interpolate to left boundary node (-1)
748
for l in eachnode(dg)
749
for v in eachvariable(equations)
750
boundary_u = zero(eltype(boundaries.u))
751
for ii in eachnode(dg)
752
boundary_u = (boundary_u +
753
u[v, ii, l, element] *
754
boundary_interpolation[ii, 1])
755
end
756
boundaries.u[2, v, l, boundary] = boundary_u
757
end
758
end
759
end
760
else # if orientations[boundary] == 2
761
# boundary in y-direction
762
if neighbor_sides[boundary] == 1
763
# element in -y direction of boundary => interpolate to right boundary node (+1)
764
for l in eachnode(dg)
765
for v in eachvariable(equations)
766
boundary_u = zero(eltype(boundaries.u))
767
for jj in eachnode(dg)
768
boundary_u = (boundary_u +
769
u[v, l, jj, element] *
770
boundary_interpolation[jj, 2])
771
end
772
boundaries.u[1, v, l, boundary] = boundary_u
773
end
774
end
775
else # element in +y direction of boundary => interpolate to left boundary node (-1)
776
for l in eachnode(dg)
777
for v in eachvariable(equations)
778
boundary_u = zero(eltype(boundaries.u))
779
for jj in eachnode(dg)
780
boundary_u = (boundary_u +
781
u[v, l, jj, element] *
782
boundary_interpolation[jj, 1])
783
end
784
boundaries.u[2, v, l, boundary] = boundary_u
785
end
786
end
787
end
788
end
789
end
790
791
return nothing
792
end
793
794
function calc_boundary_flux!(cache, t, boundary_conditions::NamedTuple,
795
mesh::TreeMesh{2}, equations, surface_integral, dg::DG)
796
@unpack surface_flux_values = cache.elements
797
@unpack n_boundaries_per_direction = cache.boundaries
798
799
# Calculate indices
800
lasts = accumulate(+, n_boundaries_per_direction)
801
firsts = lasts - n_boundaries_per_direction .+ 1
802
803
# Calc boundary fluxes in each direction
804
calc_boundary_flux_by_direction!(surface_flux_values, t, boundary_conditions[1],
805
have_nonconservative_terms(equations),
806
equations, surface_integral, dg, cache,
807
1, firsts[1], lasts[1])
808
calc_boundary_flux_by_direction!(surface_flux_values, t, boundary_conditions[2],
809
have_nonconservative_terms(equations),
810
equations, surface_integral, dg, cache,
811
2, firsts[2], lasts[2])
812
calc_boundary_flux_by_direction!(surface_flux_values, t, boundary_conditions[3],
813
have_nonconservative_terms(equations),
814
equations, surface_integral, dg, cache,
815
3, firsts[3], lasts[3])
816
calc_boundary_flux_by_direction!(surface_flux_values, t, boundary_conditions[4],
817
have_nonconservative_terms(equations),
818
equations, surface_integral, dg, cache,
819
4, firsts[4], lasts[4])
820
821
return nothing
822
end
823
824
function calc_boundary_flux_by_direction!(surface_flux_values::AbstractArray{<:Any, 4},
825
t,
826
boundary_condition,
827
have_nonconservative_terms::False, equations,
828
surface_integral, dg::DG, cache,
829
direction, first_boundary, last_boundary)
830
@unpack surface_flux = surface_integral
831
@unpack u, neighbor_ids, neighbor_sides, node_coordinates, orientations = cache.boundaries
832
833
@threaded for boundary in first_boundary:last_boundary
834
# Get neighboring element
835
neighbor = neighbor_ids[boundary]
836
837
for i in eachnode(dg)
838
# Get boundary flux
839
u_ll, u_rr = get_surface_node_vars(u, equations, dg, i, boundary)
840
if neighbor_sides[boundary] == 1 # Element is on the left, boundary on the right
841
u_inner = u_ll
842
else # Element is on the right, boundary on the left
843
u_inner = u_rr
844
end
845
x = get_node_coords(node_coordinates, equations, dg, i, boundary)
846
flux = boundary_condition(u_inner, orientations[boundary], direction, x, t,
847
surface_flux,
848
equations)
849
850
# Copy flux to left and right element storage
851
for v in eachvariable(equations)
852
surface_flux_values[v, i, direction, neighbor] = flux[v]
853
end
854
end
855
end
856
857
return nothing
858
end
859
860
function calc_boundary_flux_by_direction!(surface_flux_values::AbstractArray{<:Any, 4},
861
t,
862
boundary_condition,
863
have_nonconservative_terms::True, equations,
864
surface_integral, dg::DG, cache,
865
direction, first_boundary, last_boundary)
866
@unpack u, neighbor_ids, neighbor_sides, node_coordinates, orientations = cache.boundaries
867
868
@threaded for boundary in first_boundary:last_boundary
869
# Get neighboring element
870
neighbor = neighbor_ids[boundary]
871
872
for i in eachnode(dg)
873
# Get boundary flux
874
u_ll, u_rr = get_surface_node_vars(u, equations, dg, i, boundary)
875
if neighbor_sides[boundary] == 1 # Element is on the left, boundary on the right
876
u_inner = u_ll
877
else # Element is on the right, boundary on the left
878
u_inner = u_rr
879
end
880
x = get_node_coords(node_coordinates, equations, dg, i, boundary)
881
flux, noncons_flux = boundary_condition(u_inner, orientations[boundary],
882
direction, x, t,
883
surface_integral.surface_flux,
884
equations)
885
886
# Copy flux to left and right element storage
887
for v in eachvariable(equations)
888
surface_flux_values[v, i, direction, neighbor] = flux[v] +
889
0.5f0 * noncons_flux[v]
890
end
891
end
892
end
893
894
return nothing
895
end
896
897
function prolong2mortars!(cache, u,
898
mesh::TreeMesh{2}, equations,
899
mortar_l2::LobattoLegendreMortarL2,
900
dg::DGSEM)
901
@threaded for mortar in eachmortar(dg, cache)
902
large_element = cache.mortars.neighbor_ids[3, mortar]
903
upper_element = cache.mortars.neighbor_ids[2, mortar]
904
lower_element = cache.mortars.neighbor_ids[1, mortar]
905
906
# Copy solution small to small
907
if cache.mortars.large_sides[mortar] == 1 # -> small elements on right side
908
if cache.mortars.orientations[mortar] == 1
909
# L2 mortars in x-direction
910
for l in eachnode(dg)
911
for v in eachvariable(equations)
912
cache.mortars.u_upper[2, v, l, mortar] = u[v, 1, l,
913
upper_element]
914
cache.mortars.u_lower[2, v, l, mortar] = u[v, 1, l,
915
lower_element]
916
end
917
end
918
else
919
# L2 mortars in y-direction
920
for l in eachnode(dg)
921
for v in eachvariable(equations)
922
cache.mortars.u_upper[2, v, l, mortar] = u[v, l, 1,
923
upper_element]
924
cache.mortars.u_lower[2, v, l, mortar] = u[v, l, 1,
925
lower_element]
926
end
927
end
928
end
929
else # large_sides[mortar] == 2 -> small elements on left side
930
if cache.mortars.orientations[mortar] == 1
931
# L2 mortars in x-direction
932
for l in eachnode(dg)
933
for v in eachvariable(equations)
934
cache.mortars.u_upper[1, v, l, mortar] = u[v, nnodes(dg), l,
935
upper_element]
936
cache.mortars.u_lower[1, v, l, mortar] = u[v, nnodes(dg), l,
937
lower_element]
938
end
939
end
940
else
941
# L2 mortars in y-direction
942
for l in eachnode(dg)
943
for v in eachvariable(equations)
944
cache.mortars.u_upper[1, v, l, mortar] = u[v, l, nnodes(dg),
945
upper_element]
946
cache.mortars.u_lower[1, v, l, mortar] = u[v, l, nnodes(dg),
947
lower_element]
948
end
949
end
950
end
951
end
952
953
# Interpolate large element face data to small interface locations
954
if cache.mortars.large_sides[mortar] == 1 # -> large element on left side
955
leftright = 1
956
if cache.mortars.orientations[mortar] == 1
957
# L2 mortars in x-direction
958
u_large = view(u, :, nnodes(dg), :, large_element)
959
element_solutions_to_mortars!(cache.mortars, mortar_l2, leftright,
960
mortar, u_large)
961
else
962
# L2 mortars in y-direction
963
u_large = view(u, :, :, nnodes(dg), large_element)
964
element_solutions_to_mortars!(cache.mortars, mortar_l2, leftright,
965
mortar, u_large)
966
end
967
else # large_sides[mortar] == 2 -> large element on right side
968
leftright = 2
969
if cache.mortars.orientations[mortar] == 1
970
# L2 mortars in x-direction
971
u_large = view(u, :, 1, :, large_element)
972
element_solutions_to_mortars!(cache.mortars, mortar_l2, leftright,
973
mortar, u_large)
974
else
975
# L2 mortars in y-direction
976
u_large = view(u, :, :, 1, large_element)
977
element_solutions_to_mortars!(cache.mortars, mortar_l2, leftright,
978
mortar, u_large)
979
end
980
end
981
end
982
983
return nothing
984
end
985
986
@inline function element_solutions_to_mortars!(mortars,
987
mortar_l2::LobattoLegendreMortarL2,
988
leftright, mortar,
989
u_large::AbstractArray{<:Any, 2})
990
multiply_dimensionwise!(view(mortars.u_upper, leftright, :, :, mortar),
991
mortar_l2.forward_upper, u_large)
992
multiply_dimensionwise!(view(mortars.u_lower, leftright, :, :, mortar),
993
mortar_l2.forward_lower, u_large)
994
return nothing
995
end
996
997
function calc_mortar_flux!(surface_flux_values,
998
mesh::TreeMesh{2},
999
have_nonconservative_terms::False, equations,
1000
mortar_l2::LobattoLegendreMortarL2,
1001
surface_integral, dg::DG, cache)
1002
@unpack surface_flux = surface_integral
1003
@unpack u_lower, u_upper, orientations = cache.mortars
1004
@unpack (fstar_primary_upper_threaded, fstar_primary_lower_threaded,
1005
fstar_secondary_upper_threaded, fstar_secondary_lower_threaded) = cache
1006
1007
@threaded for mortar in eachmortar(dg, cache)
1008
# Choose thread-specific pre-allocated container
1009
fstar_primary_upper = fstar_primary_upper_threaded[Threads.threadid()]
1010
fstar_primary_lower = fstar_primary_lower_threaded[Threads.threadid()]
1011
fstar_secondary_upper = fstar_secondary_upper_threaded[Threads.threadid()]
1012
fstar_secondary_lower = fstar_secondary_lower_threaded[Threads.threadid()]
1013
1014
# Calculate fluxes
1015
orientation = orientations[mortar]
1016
calc_fstar!(fstar_primary_upper, equations, surface_flux, dg, u_upper, mortar,
1017
orientation)
1018
calc_fstar!(fstar_primary_lower, equations, surface_flux, dg, u_lower, mortar,
1019
orientation)
1020
calc_fstar!(fstar_secondary_upper, equations, surface_flux, dg, u_upper, mortar,
1021
orientation)
1022
calc_fstar!(fstar_secondary_lower, equations, surface_flux, dg, u_lower, mortar,
1023
orientation)
1024
1025
mortar_fluxes_to_elements!(surface_flux_values,
1026
mesh, equations, mortar_l2, dg, cache,
1027
mortar, fstar_primary_upper, fstar_primary_lower,
1028
fstar_secondary_upper, fstar_secondary_lower)
1029
end
1030
1031
return nothing
1032
end
1033
1034
function calc_mortar_flux!(surface_flux_values,
1035
mesh::TreeMesh{2},
1036
have_nonconservative_terms::True, equations,
1037
mortar_l2::LobattoLegendreMortarL2,
1038
surface_integral, dg::DG, cache)
1039
surface_flux, nonconservative_flux = surface_integral.surface_flux
1040
@unpack u_lower, u_upper, orientations, large_sides = cache.mortars
1041
@unpack (fstar_primary_upper_threaded, fstar_primary_lower_threaded,
1042
fstar_secondary_upper_threaded, fstar_secondary_lower_threaded) = cache
1043
1044
@threaded for mortar in eachmortar(dg, cache)
1045
# Choose thread-specific pre-allocated container
1046
fstar_primary_upper = fstar_primary_upper_threaded[Threads.threadid()]
1047
fstar_primary_lower = fstar_primary_lower_threaded[Threads.threadid()]
1048
fstar_secondary_upper = fstar_secondary_upper_threaded[Threads.threadid()]
1049
fstar_secondary_lower = fstar_secondary_lower_threaded[Threads.threadid()]
1050
1051
# Calculate fluxes
1052
orientation = orientations[mortar]
1053
calc_fstar!(fstar_primary_upper, equations, surface_flux, dg, u_upper, mortar,
1054
orientation)
1055
calc_fstar!(fstar_primary_lower, equations, surface_flux, dg, u_lower, mortar,
1056
orientation)
1057
calc_fstar!(fstar_secondary_upper, equations, surface_flux, dg, u_upper, mortar,
1058
orientation)
1059
calc_fstar!(fstar_secondary_lower, equations, surface_flux, dg, u_lower, mortar,
1060
orientation)
1061
1062
# Add nonconservative fluxes.
1063
# These need to be adapted on the geometry (left/right) since the order of
1064
# the arguments matters, based on the global SBP operator interpretation.
1065
# The same interpretation (global SBP operators coupled discontinuously via
1066
# central fluxes/SATs) explains why we need the factor 0.5.
1067
# Alternatively, you can also follow the argumentation of Bohm et al. 2018
1068
# ("nonconservative diamond flux")
1069
if large_sides[mortar] == 1 # -> small elements on right side
1070
for i in eachnode(dg)
1071
# Pull the left and right solutions
1072
u_upper_ll, u_upper_rr = get_surface_node_vars(u_upper, equations, dg,
1073
i, mortar)
1074
u_lower_ll, u_lower_rr = get_surface_node_vars(u_lower, equations, dg,
1075
i, mortar)
1076
# Call pointwise nonconservative term
1077
noncons_primary_upper = nonconservative_flux(u_upper_ll, u_upper_rr,
1078
orientation, equations)
1079
noncons_primary_lower = nonconservative_flux(u_lower_ll, u_lower_rr,
1080
orientation, equations)
1081
noncons_secondary_upper = nonconservative_flux(u_upper_rr, u_upper_ll,
1082
orientation, equations)
1083
noncons_secondary_lower = nonconservative_flux(u_lower_rr, u_lower_ll,
1084
orientation, equations)
1085
# Add to primary and secondary temporary storage
1086
multiply_add_to_node_vars!(fstar_primary_upper, 0.5f0,
1087
noncons_primary_upper, equations,
1088
dg, i)
1089
multiply_add_to_node_vars!(fstar_primary_lower, 0.5f0,
1090
noncons_primary_lower, equations,
1091
dg, i)
1092
multiply_add_to_node_vars!(fstar_secondary_upper, 0.5f0,
1093
noncons_secondary_upper, equations,
1094
dg, i)
1095
multiply_add_to_node_vars!(fstar_secondary_lower, 0.5f0,
1096
noncons_secondary_lower, equations,
1097
dg, i)
1098
end
1099
else # large_sides[mortar] == 2 -> small elements on the left
1100
for i in eachnode(dg)
1101
# Pull the left and right solutions
1102
u_upper_ll, u_upper_rr = get_surface_node_vars(u_upper, equations, dg,
1103
i, mortar)
1104
u_lower_ll, u_lower_rr = get_surface_node_vars(u_lower, equations, dg,
1105
i, mortar)
1106
# Call pointwise nonconservative term
1107
noncons_primary_upper = nonconservative_flux(u_upper_rr, u_upper_ll,
1108
orientation, equations)
1109
noncons_primary_lower = nonconservative_flux(u_lower_rr, u_lower_ll,
1110
orientation, equations)
1111
noncons_secondary_upper = nonconservative_flux(u_upper_ll, u_upper_rr,
1112
orientation, equations)
1113
noncons_secondary_lower = nonconservative_flux(u_lower_ll, u_lower_rr,
1114
orientation, equations)
1115
# Add to primary and secondary temporary storage
1116
multiply_add_to_node_vars!(fstar_primary_upper, 0.5f0,
1117
noncons_primary_upper, equations,
1118
dg, i)
1119
multiply_add_to_node_vars!(fstar_primary_lower, 0.5f0,
1120
noncons_primary_lower, equations,
1121
dg, i)
1122
multiply_add_to_node_vars!(fstar_secondary_upper, 0.5f0,
1123
noncons_secondary_upper, equations,
1124
dg, i)
1125
multiply_add_to_node_vars!(fstar_secondary_lower, 0.5f0,
1126
noncons_secondary_lower, equations,
1127
dg, i)
1128
end
1129
end
1130
1131
mortar_fluxes_to_elements!(surface_flux_values,
1132
mesh, equations, mortar_l2, dg, cache,
1133
mortar, fstar_primary_upper, fstar_primary_lower,
1134
fstar_secondary_upper, fstar_secondary_lower)
1135
end
1136
1137
return nothing
1138
end
1139
1140
# For Gauss-Legendre DGSEM mortars are not yet implemented
1141
function calc_mortar_flux!(surface_flux_values,
1142
mesh::TreeMesh{2},
1143
have_nonconservative_terms, equations,
1144
mortar::Nothing, surface_integral,
1145
dg::DGSEM{<:GaussLegendreBasis}, cache)
1146
@assert isempty(eachmortar(dg, cache))
1147
return nothing
1148
end
1149
1150
@inline function calc_fstar!(destination::AbstractArray{<:Any, 2}, equations,
1151
surface_flux, dg::DGSEM,
1152
u_interfaces, interface, orientation)
1153
for i in eachnode(dg)
1154
# Call pointwise two-point numerical flux function
1155
u_ll, u_rr = get_surface_node_vars(u_interfaces, equations, dg, i, interface)
1156
flux = surface_flux(u_ll, u_rr, orientation, equations)
1157
1158
# Copy flux to left and right element storage
1159
set_node_vars!(destination, flux, equations, dg, i)
1160
end
1161
1162
return nothing
1163
end
1164
1165
@inline function mortar_fluxes_to_elements!(surface_flux_values,
1166
mesh::TreeMesh{2}, equations,
1167
mortar_l2::LobattoLegendreMortarL2,
1168
dg::DGSEM, cache,
1169
mortar, fstar_primary_upper,
1170
fstar_primary_lower,
1171
fstar_secondary_upper,
1172
fstar_secondary_lower)
1173
large_element = cache.mortars.neighbor_ids[3, mortar]
1174
upper_element = cache.mortars.neighbor_ids[2, mortar]
1175
lower_element = cache.mortars.neighbor_ids[1, mortar]
1176
1177
# Copy flux small to small
1178
if cache.mortars.large_sides[mortar] == 1 # -> small elements on right side
1179
if cache.mortars.orientations[mortar] == 1
1180
# L2 mortars in x-direction
1181
direction = 1
1182
else
1183
# L2 mortars in y-direction
1184
direction = 3
1185
end
1186
else # large_sides[mortar] == 2 -> small elements on left side
1187
if cache.mortars.orientations[mortar] == 1
1188
# L2 mortars in x-direction
1189
direction = 2
1190
else
1191
# L2 mortars in y-direction
1192
direction = 4
1193
end
1194
end
1195
surface_flux_values[:, :, direction, upper_element] .= fstar_primary_upper
1196
surface_flux_values[:, :, direction, lower_element] .= fstar_primary_lower
1197
1198
# Project small fluxes to large element
1199
if cache.mortars.large_sides[mortar] == 1 # -> large element on left side
1200
if cache.mortars.orientations[mortar] == 1
1201
# L2 mortars in x-direction
1202
direction = 2
1203
else
1204
# L2 mortars in y-direction
1205
direction = 4
1206
end
1207
else # large_sides[mortar] == 2 -> large element on right side
1208
if cache.mortars.orientations[mortar] == 1
1209
# L2 mortars in x-direction
1210
direction = 1
1211
else
1212
# L2 mortars in y-direction
1213
direction = 3
1214
end
1215
end
1216
1217
# TODO: Taal performance
1218
# for v in eachvariable(equations)
1219
# # The code below is semantically equivalent to
1220
# # surface_flux_values[v, :, direction, large_element] .=
1221
# # (mortar_l2.reverse_upper * fstar_upper[v, :] + mortar_l2.reverse_lower * fstar_lower[v, :])
1222
# # but faster and does not allocate.
1223
# # Note that `true * some_float == some_float` in Julia, i.e. `true` acts as
1224
# # a universal `one`. Hence, the second `mul!` means "add the matrix-vector
1225
# # product to the current value of the destination".
1226
# @views mul!(surface_flux_values[v, :, direction, large_element],
1227
# mortar_l2.reverse_upper, fstar_upper[v, :])
1228
# @views mul!(surface_flux_values[v, :, direction, large_element],
1229
# mortar_l2.reverse_lower, fstar_lower[v, :], true, true)
1230
# end
1231
# The code above could be replaced by the following code. However, the relative efficiency
1232
# depends on the types of fstar_upper/fstar_lower and dg.l2mortar_reverse_upper.
1233
# Using StaticArrays for both makes the code above faster for common test cases.
1234
multiply_dimensionwise!(view(surface_flux_values, :, :, direction, large_element),
1235
mortar_l2.reverse_upper, fstar_secondary_upper,
1236
mortar_l2.reverse_lower, fstar_secondary_lower)
1237
1238
return nothing
1239
end
1240
1241
function calc_surface_integral!(backend::Nothing, du, u,
1242
mesh::Union{TreeMesh{2}, StructuredMesh{2},
1243
StructuredMeshView{2}},
1244
equations, surface_integral::SurfaceIntegralWeakForm,
1245
dg::DG, cache)
1246
@unpack inverse_weights = dg.basis
1247
@unpack surface_flux_values = cache.elements
1248
1249
# This computes the **negative** surface integral contribution,
1250
# i.e., M^{-1} * boundary_interpolation^T (which is for Gauss-Lobatto DGSEM just M^{-1} * B)
1251
# and the missing "-" is taken care of by `apply_jacobian!`.
1252
#
1253
# We also use explicit assignments instead of `+=` to let `@muladd` turn these
1254
# into FMAs (see comment at the top of the file).
1255
factor = inverse_weights[1] # For LGL basis: Identical to weighted boundary interpolation at x = ±1
1256
@threaded for element in eachelement(dg, cache)
1257
for l in eachnode(dg)
1258
for v in eachvariable(equations)
1259
# surface at -x
1260
du[v, 1, l, element] = (du[v, 1, l, element] -
1261
surface_flux_values[v, l, 1, element] *
1262
factor)
1263
1264
# surface at +x
1265
du[v, nnodes(dg), l, element] = (du[v, nnodes(dg), l, element] +
1266
surface_flux_values[v, l, 2, element] *
1267
factor)
1268
1269
# surface at -y
1270
du[v, l, 1, element] = (du[v, l, 1, element] -
1271
surface_flux_values[v, l, 3, element] *
1272
factor)
1273
1274
# surface at +y
1275
du[v, l, nnodes(dg), element] = (du[v, l, nnodes(dg), element] +
1276
surface_flux_values[v, l, 4, element] *
1277
factor)
1278
end
1279
end
1280
end
1281
1282
return nothing
1283
end
1284
1285
function calc_surface_integral!(backend::Nothing, du, u,
1286
mesh::Union{TreeMesh{2},
1287
StructuredMesh{2}, StructuredMeshView{2}},
1288
equations, surface_integral::SurfaceIntegralWeakForm,
1289
dg::DGSEM{<:GaussLegendreBasis}, cache)
1290
@unpack boundary_interpolation_inverse_weights = dg.basis
1291
@unpack surface_flux_values = cache.elements
1292
1293
# This computes the **negative** surface integral contribution,
1294
# i.e., M^{-1} * boundary_interpolation^T (which is for Gauss-Legendre DGSEM M^{-1} * L)
1295
# and the missing "-" is taken care of by `apply_jacobian!`.
1296
#
1297
# We also use explicit assignments instead of `+=` to let `@muladd` turn these
1298
# into FMAs (see comment at the top of the file).
1299
@threaded for element in eachelement(dg, cache)
1300
for l in eachnode(dg)
1301
for v in eachvariable(equations)
1302
# Aliases for repeatedly accessed variables
1303
surface_flux_minus = surface_flux_values[v, l, 1, element]
1304
surface_flux_plus = surface_flux_values[v, l, 2, element]
1305
for ii in eachnode(dg)
1306
# surface at -x
1307
du[v, ii, l, element] = (du[v, ii, l, element] -
1308
surface_flux_minus *
1309
boundary_interpolation_inverse_weights[ii,
1310
1])
1311
1312
# surface at +x
1313
du[v, ii, l, element] = (du[v, ii, l, element] +
1314
surface_flux_plus *
1315
boundary_interpolation_inverse_weights[ii,
1316
2])
1317
end
1318
1319
surface_flux_minus = surface_flux_values[v, l, 3, element]
1320
surface_flux_plus = surface_flux_values[v, l, 4, element]
1321
for jj in eachnode(dg)
1322
# surface at -y
1323
du[v, l, jj, element] = (du[v, l, jj, element] -
1324
surface_flux_minus *
1325
boundary_interpolation_inverse_weights[jj,
1326
1])
1327
1328
# surface at +y
1329
du[v, l, jj, element] = (du[v, l, jj, element] +
1330
surface_flux_plus *
1331
boundary_interpolation_inverse_weights[jj,
1332
2])
1333
end
1334
end
1335
end
1336
end
1337
1338
return nothing
1339
end
1340
1341
function apply_jacobian!(backend::Nothing, du, mesh::TreeMesh{2},
1342
equations, dg::DG, cache)
1343
@unpack inverse_jacobian = cache.elements
1344
1345
@threaded for element in eachelement(dg, cache)
1346
# Negative sign included to account for the negated surface and volume terms,
1347
# see e.g. the computation of `derivative_hat` in the basis setup and
1348
# the comment in `calc_surface_integral!`.
1349
factor = -inverse_jacobian[element]
1350
1351
for j in eachnode(dg), i in eachnode(dg)
1352
for v in eachvariable(equations)
1353
du[v, i, j, element] *= factor
1354
end
1355
end
1356
end
1357
1358
return nothing
1359
end
1360
1361
# Need dimension specific version to avoid error at dispatching
1362
function calc_sources!(du, u, t, source_terms::Nothing,
1363
equations::AbstractEquations{2}, dg::DG, cache)
1364
return nothing
1365
end
1366
1367
function calc_sources!(du, u, t, source_terms,
1368
equations::AbstractEquations{2}, dg::DG, cache)
1369
@unpack node_coordinates = cache.elements
1370
1371
@threaded for element in eachelement(dg, cache)
1372
for j in eachnode(dg), i in eachnode(dg)
1373
u_local = get_node_vars(u, equations, dg, i, j, element)
1374
x_local = get_node_coords(node_coordinates, equations, dg,
1375
i, j, element)
1376
du_local = source_terms(u_local, x_local, t, equations)
1377
add_to_node_vars!(du, du_local, equations, dg, i, j, element)
1378
end
1379
end
1380
1381
return nothing
1382
end
1383
end # @muladd
1384
1385