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_1d.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 1D,
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::TreeMesh{1}, 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
# Container cache
26
cache = (; elements, interfaces, boundaries)
27
28
# Add Volume-Integral cache
29
cache = (; cache...,
30
create_cache(mesh, equations, dg.volume_integral, dg, cache, uEltype)...)
31
32
return cache
33
end
34
35
# The methods below are specialized on the volume integral type
36
# and called from the basic `create_cache` method at the top.
37
38
function create_cache(mesh::Union{TreeMesh{1}, StructuredMesh{1}}, equations,
39
volume_integral::AbstractVolumeIntegralSubcell,
40
dg::DG, cache_containers, uEltype)
41
MA2d = MArray{Tuple{nvariables(equations), nnodes(dg) + 1},
42
uEltype, 2, nvariables(equations) * (nnodes(dg) + 1)}
43
fstar1_L_threaded = MA2d[MA2d(undef) for _ in 1:Threads.maxthreadid()]
44
fstar1_R_threaded = MA2d[MA2d(undef) for _ in 1:Threads.maxthreadid()]
45
46
@threaded for t in eachindex(fstar1_L_threaded)
47
fstar1_L_threaded[t][:, 1] .= zero(uEltype)
48
fstar1_R_threaded[t][:, 1] .= zero(uEltype)
49
fstar1_L_threaded[t][:, nnodes(dg) + 1] .= zero(uEltype)
50
fstar1_R_threaded[t][:, nnodes(dg) + 1] .= zero(uEltype)
51
end
52
53
return (; fstar1_L_threaded, fstar1_R_threaded)
54
end
55
56
# TODO: Taal discuss/refactor timer, allowing users to pass a custom timer?
57
58
# This function is valid for all conforming mesh types (except for `StructuredMesh`), i.e.,
59
# all meshes that do not involve mortar operations.
60
# Thus, we can use it for 1D `TreeMesh` and `UnstructuredMesh2D`.
61
function rhs!(du, u, t,
62
mesh::Union{TreeMesh{1},
63
UnstructuredMesh2D},
64
equations,
65
boundary_conditions, source_terms::Source,
66
dg::DG, cache) where {Source}
67
backend = trixi_backend(u)
68
69
# Reset du
70
@trixi_timeit timer() "reset ∂u/∂t" set_zero!(du, dg, cache)
71
72
# Calculate volume integral
73
@trixi_timeit timer() "volume integral" begin
74
calc_volume_integral!(backend, du, u, mesh,
75
have_nonconservative_terms(equations), equations,
76
dg.volume_integral, dg, cache)
77
end
78
79
# Prolong solution to interfaces
80
@trixi_timeit timer() "prolong2interfaces" begin
81
prolong2interfaces!(cache, u, mesh, equations, dg)
82
end
83
84
# Calculate interface fluxes
85
@trixi_timeit timer() "interface flux" begin
86
calc_interface_flux!(cache.elements.surface_flux_values, mesh,
87
have_nonconservative_terms(equations), equations,
88
dg.surface_integral, dg, cache)
89
end
90
91
# Prolong solution to boundaries
92
@trixi_timeit timer() "prolong2boundaries" begin
93
prolong2boundaries!(cache, u, mesh, equations, dg)
94
end
95
96
# Calculate boundary fluxes
97
@trixi_timeit timer() "boundary flux" begin
98
calc_boundary_flux!(cache, t, boundary_conditions, mesh, equations,
99
dg.surface_integral, dg)
100
end
101
102
# Calculate surface integrals
103
@trixi_timeit timer() "surface integral" begin
104
calc_surface_integral!(backend, du, u, mesh, equations,
105
dg.surface_integral, dg, cache)
106
end
107
108
# Apply Jacobian from mapping to reference element
109
@trixi_timeit timer() "Jacobian" apply_jacobian!(backend, du, mesh, equations, dg,
110
cache)
111
112
# Calculate source terms
113
@trixi_timeit timer() "source terms" begin
114
calc_sources!(du, u, t, source_terms, equations, dg, cache)
115
end
116
117
return nothing
118
end
119
120
#=
121
`weak_form_kernel!` is only implemented for conserved terms as
122
non-conservative terms should always be discretized in conjunction with a flux-splitting scheme,
123
see `flux_differencing_kernel!`.
124
This treatment is required to achieve, e.g., entropy-stability or well-balancedness.
125
See also https://github.com/trixi-framework/Trixi.jl/issues/1671#issuecomment-1765644064
126
=#
127
@inline function weak_form_kernel!(du, u,
128
element,
129
::Type{<:Union{TreeMesh{1}, StructuredMesh{1}}},
130
have_nonconservative_terms::False, equations,
131
dg::DGSEM, cache, alpha = true)
132
# true * [some floating point value] == [exactly the same floating point value]
133
# This can (hopefully) be optimized away due to constant propagation.
134
@unpack derivative_hat = dg.basis
135
136
for i in eachnode(dg)
137
u_node = get_node_vars(u, equations, dg, i, element)
138
139
flux1 = flux(u_node, 1, equations)
140
for ii in eachnode(dg)
141
multiply_add_to_node_vars!(du, alpha * derivative_hat[ii, i], flux1,
142
equations, dg, ii, element)
143
end
144
end
145
146
return nothing
147
end
148
149
@inline function flux_differencing_kernel!(du, u, element,
150
::Type{<:Union{TreeMesh{1},
151
StructuredMesh{1}}},
152
have_nonconservative_terms::False, equations,
153
volume_flux, dg::DGSEM, cache, alpha = true)
154
# true * [some floating point value] == [exactly the same floating point value]
155
# This can (hopefully) be optimized away due to constant propagation.
156
@unpack derivative_split = dg.basis
157
158
# Calculate volume integral in one element
159
for i in eachnode(dg)
160
u_node = get_node_vars(u, equations, dg, i, element)
161
162
# All diagonal entries of `derivative_split` are zero. Thus, we can skip
163
# the computation of the diagonal terms. In addition, we use the symmetry
164
# of the `volume_flux` to save half of the possible two-point flux
165
# computations.
166
167
# x direction
168
for ii in (i + 1):nnodes(dg)
169
u_node_ii = get_node_vars(u, equations, dg, ii, element)
170
flux1 = volume_flux(u_node, u_node_ii, 1, equations)
171
multiply_add_to_node_vars!(du, alpha * derivative_split[i, ii], flux1,
172
equations, dg, i, element)
173
multiply_add_to_node_vars!(du, alpha * derivative_split[ii, i], flux1,
174
equations, dg, ii, element)
175
end
176
end
177
end
178
179
@inline function flux_differencing_kernel!(du, u, element,
180
MeshT::Type{<:Union{TreeMesh{1},
181
StructuredMesh{1}}},
182
have_nonconservative_terms::True, equations,
183
volume_flux, dg::DGSEM, cache, alpha = true)
184
# true * [some floating point value] == [exactly the same floating point value]
185
# This can (hopefully) be optimized away due to constant propagation.
186
@unpack derivative_split = dg.basis
187
symmetric_flux, nonconservative_flux = volume_flux
188
189
# Apply the symmetric flux as usual
190
flux_differencing_kernel!(du, u, element, MeshT, False(), equations, symmetric_flux,
191
dg, cache, alpha)
192
193
# Calculate the remaining volume terms using the nonsymmetric generalized flux
194
for i in eachnode(dg)
195
u_node = get_node_vars(u, equations, dg, i, element)
196
197
# The diagonal terms are zero since the diagonal of `derivative_split`
198
# is zero. We ignore this for now.
199
200
# x direction
201
integral_contribution = zero(u_node)
202
for ii in eachnode(dg)
203
u_node_ii = get_node_vars(u, equations, dg, ii, element)
204
noncons_flux1 = nonconservative_flux(u_node, u_node_ii, 1, equations)
205
integral_contribution = integral_contribution +
206
derivative_split[i, ii] * noncons_flux1
207
end
208
209
# The factor 0.5 cancels the factor 2 in the flux differencing form
210
multiply_add_to_node_vars!(du, alpha * 0.5f0, integral_contribution, equations,
211
dg, i, element)
212
end
213
end
214
215
@inline function fv_kernel!(du, u,
216
MeshT::Type{<:Union{TreeMesh{1}, StructuredMesh{1}}},
217
have_nonconservative_terms, equations,
218
volume_flux_fv, dg::DGSEM, cache, element, alpha = true)
219
@unpack fstar1_L_threaded, fstar1_R_threaded = cache
220
@unpack inverse_weights = dg.basis # Plays role of inverse DG-subcell sizes
221
222
# Calculate FV two-point fluxes
223
fstar1_L = fstar1_L_threaded[Threads.threadid()]
224
fstar1_R = fstar1_R_threaded[Threads.threadid()]
225
calcflux_fv!(fstar1_L, fstar1_R, u, MeshT,
226
have_nonconservative_terms, equations,
227
volume_flux_fv, dg, element, cache)
228
229
# Calculate FV volume integral contribution
230
for i in eachnode(dg)
231
for v in eachvariable(equations)
232
du[v, i, element] += (alpha *
233
(inverse_weights[i] *
234
(fstar1_L[v, i + 1] - fstar1_R[v, i])))
235
end
236
end
237
238
return nothing
239
end
240
241
@inline function fvO2_kernel!(du, u,
242
MeshT::Type{<:Union{TreeMesh{1}, StructuredMesh{1}}},
243
nonconservative_terms, equations,
244
volume_flux_fv, dg::DGSEM, cache, element,
245
sc_interface_coords, reconstruction_mode, slope_limiter,
246
cons2recon, recon2cons,
247
alpha = true)
248
@unpack fstar1_L_threaded, fstar1_R_threaded = cache
249
@unpack inverse_weights = dg.basis # Plays role of inverse DG-subcell sizes
250
251
# Calculate FV two-point fluxes
252
fstar1_L = fstar1_L_threaded[Threads.threadid()]
253
fstar1_R = fstar1_R_threaded[Threads.threadid()]
254
calcflux_fvO2!(fstar1_L, fstar1_R, u, MeshT, nonconservative_terms, equations,
255
volume_flux_fv, dg, element, cache,
256
sc_interface_coords, reconstruction_mode, slope_limiter,
257
cons2recon, recon2cons)
258
259
# Calculate FV volume integral contribution
260
for i in eachnode(dg)
261
for v in eachvariable(equations)
262
du[v, i, element] += (alpha *
263
(inverse_weights[i] *
264
(fstar1_L[v, i + 1] - fstar1_R[v, i])))
265
end
266
end
267
268
return nothing
269
end
270
271
# Compute the normal flux for the FV method on subcells of the LGL subgrid, see
272
# Hennemann, Rueda-Ramírez, Hindenlang, Gassner (2020)
273
# "A provably entropy stable subcell shock capturing approach for high order split form DG for the compressible Euler equations"
274
# [arXiv: 2008.12044v2](https://arxiv.org/pdf/2008.12044)
275
@inline function calcflux_fv!(fstar1_L, fstar1_R, u,
276
::Type{<:Union{TreeMesh{1}, StructuredMesh{1}}},
277
have_nonconservative_terms::False,
278
equations, volume_flux_fv, dg::DGSEM, element, cache)
279
for i in 2:nnodes(dg)
280
u_ll = get_node_vars(u, equations, dg, i - 1, element)
281
u_rr = get_node_vars(u, equations, dg, i, element)
282
flux = volume_flux_fv(u_ll, u_rr, 1, equations) # orientation 1: x direction
283
set_node_vars!(fstar1_L, flux, equations, dg, i)
284
set_node_vars!(fstar1_R, flux, equations, dg, i)
285
end
286
287
return nothing
288
end
289
290
@inline function calcflux_fv!(fstar1_L, fstar1_R, u,
291
::Type{<:TreeMesh{1}},
292
have_nonconservative_terms::True,
293
equations, volume_flux_fv, dg::DGSEM, element, cache)
294
volume_flux, nonconservative_flux = volume_flux_fv
295
for i in 2:nnodes(dg)
296
u_ll = get_node_vars(u, equations, dg, i - 1, element)
297
u_rr = get_node_vars(u, equations, dg, i, element)
298
299
# Compute conservative part
300
f1 = volume_flux(u_ll, u_rr, 1, equations) # orientation 1: x direction
301
302
# Compute nonconservative part
303
# Note the factor 0.5 necessary for the nonconservative fluxes based on
304
# the interpretation of global SBP operators coupled discontinuously via
305
# central fluxes/SATs
306
f1_L = f1 + 0.5f0 * nonconservative_flux(u_ll, u_rr, 1, equations)
307
f1_R = f1 + 0.5f0 * nonconservative_flux(u_rr, u_ll, 1, equations)
308
309
# Copy to temporary storage
310
set_node_vars!(fstar1_L, f1_L, equations, dg, i)
311
set_node_vars!(fstar1_R, f1_R, equations, dg, i)
312
end
313
314
return nothing
315
end
316
317
# Compute the normal flux for the second-order FV method on subcells of the LGL subgrid, see
318
# Rueda-Ramírez, Hennemann, Hindenlang, Winters, & Gassner (2021)
319
# "An entropy stable nodal discontinuous Galerkin method for the resistive MHD equations. Part II: Subcell finite volume shock capturing"
320
# [JCP: 2021.110580](https://doi.org/10.1016/j.jcp.2021.110580)
321
@inline function calcflux_fvO2!(fstar1_L, fstar1_R, u,
322
::Type{<:Union{TreeMesh{1}, StructuredMesh{1}}},
323
nonconservative_terms::False,
324
equations, volume_flux_fv, dg::DGSEM, element, cache,
325
sc_interface_coords, reconstruction_mode, slope_limiter,
326
cons2recon, recon2cons)
327
for i in 2:nnodes(dg) # We compute FV02 fluxes at the (nnodes(dg) - 1) subcell boundaries
328
# Reference element:
329
# -1 ------------------0------------------ 1 -> x
330
# Gauss-Lobatto-Legendre nodes (schematic for k = 3):
331
# . . . .
332
# ^ ^ ^ ^
333
# Node indices:
334
# 1 2 3 4
335
# The inner subcell boundaries are governed by the
336
# cumulative sum of the quadrature weights - 1 .
337
# -1 ------------------0------------------ 1 -> x
338
# w1-1 (w1+w2)-1 (w1+w2+w3)-1
339
# | | | | |
340
# Note that only the inner boundaries are stored.
341
# Subcell interface indices, loop only over 2 -> nnodes(dg) = 4
342
# 1 2 3 4 5
343
#
344
# In general a four-point stencil is required, since we reconstruct the
345
# piecewise linear solution in both subcells next to the subcell interface.
346
# Since these subcell boundaries are not aligned with the DG nodes,
347
# on each neighboring subcell two linear solutions are reconstructed => 4 point stencil.
348
# For the outer interfaces the stencil shrinks since we do not consider values
349
# outside the element (this is a volume integral).
350
#
351
# The left subcell node values are labelled `_ll` (left-left) and `_lr` (left-right), while
352
# the right subcell node values are labelled `_rl` (right-left) and `_rr` (right-right).
353
354
## Obtain unlimited values in reconstruction variables ##
355
356
# Note: If i - 2 = 0 we do not go to neighbor element, as one would do in a finite volume scheme.
357
# Here, we keep it purely cell-local, thus overshoots between elements are not strictly ruled out,
358
# **unless** `reconstruction_mode` is set to `reconstruction_O2_inner`
359
u_ll = cons2recon(get_node_vars(u, equations, dg, max(1, i - 2), element),
360
equations)
361
u_lr = cons2recon(get_node_vars(u, equations, dg, i - 1, element),
362
equations)
363
u_rl = cons2recon(get_node_vars(u, equations, dg, i, element),
364
equations)
365
# Note: If i + 1 > nnodes(dg) we do not go to neighbor element, as one would do in a finite volume scheme.
366
# Here, we keep it purely cell-local, thus overshoots between elements are not strictly ruled out,
367
# **unless** `reconstruction_mode` is set to `reconstruction_O2_inner`
368
u_rr = cons2recon(get_node_vars(u, equations, dg, min(nnodes(dg), i + 1),
369
element), equations)
370
371
## Reconstruct values at interfaces with limiting ##
372
u_l, u_r = reconstruction_mode(u_ll, u_lr, u_rl, u_rr,
373
sc_interface_coords, i,
374
slope_limiter, dg)
375
376
## Convert reconstruction variables back to conservative variables ##
377
flux = volume_flux_fv(recon2cons(u_l, equations), recon2cons(u_r, equations),
378
1, equations) # orientation 1: x direction
379
380
set_node_vars!(fstar1_L, flux, equations, dg, i)
381
set_node_vars!(fstar1_R, flux, equations, dg, i)
382
end
383
384
return nothing
385
end
386
387
# Used for both the purely hyperbolic conserved variables `u`
388
# and the parabolic flux in x-direction in the 1D parabolic case.
389
function prolong2interfaces!(cache, u_or_flux_parabolic,
390
mesh::TreeMesh{1}, equations, dg::DG)
391
@unpack interfaces = cache
392
@unpack neighbor_ids = interfaces
393
interfaces_u = interfaces.u
394
395
@threaded for interface in eachinterface(dg, cache)
396
left_element = neighbor_ids[1, interface]
397
right_element = neighbor_ids[2, interface]
398
399
# interface in x-direction
400
for v in eachvariable(equations)
401
interfaces_u[1, v, interface] = u_or_flux_parabolic[v, nnodes(dg),
402
left_element]
403
interfaces_u[2, v, interface] = u_or_flux_parabolic[v, 1, right_element]
404
end
405
end
406
407
return nothing
408
end
409
410
function prolong2interfaces!(cache, u_or_flux_parabolic,
411
mesh::TreeMesh{1}, equations,
412
dg::DGSEM{<:GaussLegendreBasis})
413
@unpack interfaces = cache
414
@unpack neighbor_ids = interfaces
415
@unpack boundary_interpolation = dg.basis
416
interfaces_u = interfaces.u
417
418
@threaded for interface in eachinterface(dg, cache)
419
left_element = neighbor_ids[1, interface]
420
right_element = neighbor_ids[2, interface]
421
422
# interface in x-direction
423
for v in eachvariable(equations)
424
# Interpolate to the interfaces using a local variable for
425
# the accumulation of values (to reduce global memory operations).
426
interface_u_1 = zero(eltype(interfaces_u))
427
interface_u_2 = zero(eltype(interfaces_u))
428
for ii in eachnode(dg)
429
# Not += to allow `@muladd` to turn these into FMAs
430
# (see comment at the top of the file)
431
# Need `boundary_interpolation` at right (+1) node for left element
432
interface_u_1 = (interface_u_1 +
433
u_or_flux_parabolic[v, ii, left_element] *
434
boundary_interpolation[ii, 2])
435
# Need `boundary_interpolation` at left (-1) node for right element
436
interface_u_2 = (interface_u_2 +
437
u_or_flux_parabolic[v, ii, right_element] *
438
boundary_interpolation[ii, 1])
439
end
440
interfaces_u[1, v, interface] = interface_u_1
441
interfaces_u[2, v, interface] = interface_u_2
442
end
443
end
444
445
return nothing
446
end
447
448
function calc_interface_flux!(surface_flux_values,
449
mesh::TreeMesh{1},
450
have_nonconservative_terms::False, equations,
451
surface_integral, dg::DG, cache)
452
@unpack surface_flux = surface_integral
453
@unpack u, neighbor_ids, orientations = cache.interfaces
454
455
@threaded for interface in eachinterface(dg, cache)
456
# Get neighboring elements
457
left_id = neighbor_ids[1, interface]
458
right_id = neighbor_ids[2, interface]
459
460
# Determine interface direction with respect to elements:
461
# orientation = 1: left -> 2, right -> 1
462
left_direction = 2 * orientations[interface]
463
right_direction = 2 * orientations[interface] - 1
464
465
# Call pointwise Riemann solver
466
u_ll, u_rr = get_surface_node_vars(u, equations, dg, interface)
467
flux = surface_flux(u_ll, u_rr, orientations[interface], equations)
468
469
# Copy flux to left and right element storage
470
for v in eachvariable(equations)
471
surface_flux_values[v, left_direction, left_id] = flux[v]
472
surface_flux_values[v, right_direction, right_id] = flux[v]
473
end
474
end
475
476
return nothing
477
end
478
479
function calc_interface_flux!(surface_flux_values,
480
mesh::TreeMesh{1},
481
have_nonconservative_terms::True, equations,
482
surface_integral, dg::DG, cache)
483
surface_flux, nonconservative_flux = surface_integral.surface_flux
484
@unpack u, neighbor_ids, orientations = cache.interfaces
485
486
@threaded for interface in eachinterface(dg, cache)
487
# Get neighboring elements
488
left_id = neighbor_ids[1, interface]
489
right_id = neighbor_ids[2, interface]
490
491
# Determine interface direction with respect to elements:
492
# orientation = 1: left -> 2, right -> 1
493
# orientation = 2: left -> 4, right -> 3
494
left_direction = 2 * orientations[interface]
495
right_direction = 2 * orientations[interface] - 1
496
497
# Call pointwise Riemann solver
498
orientation = orientations[interface]
499
u_ll, u_rr = get_surface_node_vars(u, equations, dg, interface)
500
flux = surface_flux(u_ll, u_rr, orientation, equations)
501
502
# Compute both nonconservative fluxes
503
noncons_left = nonconservative_flux(u_ll, u_rr, orientation, equations)
504
noncons_right = nonconservative_flux(u_rr, u_ll, orientation, equations)
505
506
# Copy flux to left and right element storage
507
for v in eachvariable(equations)
508
# Note the factor 0.5 necessary for the nonconservative fluxes based on
509
# the interpretation of global SBP operators coupled discontinuously via
510
# central fluxes/SATs
511
surface_flux_values[v, left_direction, left_id] = flux[v] +
512
0.5f0 * noncons_left[v]
513
surface_flux_values[v, right_direction, right_id] = flux[v] +
514
0.5f0 * noncons_right[v]
515
end
516
end
517
518
return nothing
519
end
520
521
# Used for both the purely hyperbolic conserved variables `u`
522
# and the parabolic flux in x-direction in the 1D parabolic case.
523
function prolong2boundaries!(cache, u_or_flux_parabolic,
524
mesh::TreeMesh{1}, equations, dg::DG)
525
@unpack boundaries = cache
526
@unpack neighbor_sides = boundaries
527
528
@threaded for boundary in eachboundary(dg, cache)
529
element = boundaries.neighbor_ids[boundary]
530
531
# boundary in x-direction
532
if neighbor_sides[boundary] == 1
533
# element in -x direction of boundary
534
for v in eachvariable(equations)
535
boundaries.u[1, v, boundary] = u_or_flux_parabolic[v, nnodes(dg),
536
element]
537
end
538
else # Element in +x direction of boundary
539
for v in eachvariable(equations)
540
boundaries.u[2, v, boundary] = u_or_flux_parabolic[v, 1, element]
541
end
542
end
543
end
544
545
return nothing
546
end
547
548
function prolong2boundaries!(cache, u_or_flux_parabolic,
549
mesh::TreeMesh{1}, equations,
550
dg::DGSEM{<:GaussLegendreBasis})
551
@unpack boundaries = cache
552
@unpack neighbor_sides = boundaries
553
@unpack boundary_interpolation = dg.basis
554
555
@threaded for boundary in eachboundary(dg, cache)
556
element = boundaries.neighbor_ids[boundary]
557
558
# boundary in x-direction
559
if neighbor_sides[boundary] == 1
560
# element in -x direction of boundary => need to evaluate at right boundary node (+1)
561
for v in eachvariable(equations)
562
# Interpolate to the boundaries using a local variable for
563
# the accumulation of values (to reduce global memory operations).
564
boundary_u_1 = zero(eltype(boundaries.u))
565
for ii in eachnode(dg)
566
# Not += to allow `@muladd` to turn these into FMAs
567
# (see comment at the top of the file)
568
boundary_u_1 = (boundary_u_1 +
569
u_or_flux_parabolic[v, ii, element] *
570
boundary_interpolation[ii, 2])
571
end
572
boundaries.u[1, v, boundary] = boundary_u_1
573
end
574
else # Element in +x direction of boundary => need to evaluate at left boundary node (-1)
575
for v in eachvariable(equations)
576
boundary_u_2 = zero(eltype(boundaries.u))
577
for ii in eachnode(dg)
578
boundary_u_2 = (boundary_u_2 +
579
u_or_flux_parabolic[v, ii, element] *
580
boundary_interpolation[ii, 1])
581
end
582
boundaries.u[2, v, boundary] = boundary_u_2
583
end
584
end
585
end
586
587
return nothing
588
end
589
590
function calc_boundary_flux!(cache, t, boundary_conditions::NamedTuple,
591
mesh::TreeMesh{1}, equations, surface_integral, dg::DG)
592
@unpack surface_flux_values = cache.elements
593
@unpack n_boundaries_per_direction = cache.boundaries
594
595
# Calculate indices
596
lasts = accumulate(+, n_boundaries_per_direction)
597
firsts = lasts - n_boundaries_per_direction .+ 1
598
599
# Calc boundary fluxes in each direction
600
calc_boundary_flux_by_direction!(surface_flux_values, t, boundary_conditions[1],
601
have_nonconservative_terms(equations), equations,
602
surface_integral, dg, cache,
603
1, firsts[1], lasts[1])
604
calc_boundary_flux_by_direction!(surface_flux_values, t, boundary_conditions[2],
605
have_nonconservative_terms(equations), equations,
606
surface_integral, dg, cache,
607
2, firsts[2], lasts[2])
608
609
return nothing
610
end
611
612
function calc_boundary_flux_by_direction!(surface_flux_values::AbstractArray{<:Any, 3},
613
t,
614
boundary_condition,
615
have_nonconservative_terms::False, equations,
616
surface_integral, dg::DG, cache,
617
direction, first_boundary, last_boundary)
618
@unpack surface_flux = surface_integral
619
@unpack u, neighbor_ids, neighbor_sides, node_coordinates, orientations = cache.boundaries
620
621
@threaded for boundary in first_boundary:last_boundary
622
# Get neighboring element
623
neighbor = neighbor_ids[boundary]
624
625
# Get boundary flux
626
u_ll, u_rr = get_surface_node_vars(u, equations, dg, boundary)
627
if neighbor_sides[boundary] == 1 # Element is on the left, boundary on the right
628
u_inner = u_ll
629
else # Element is on the right, boundary on the left
630
u_inner = u_rr
631
end
632
x = get_node_coords(node_coordinates, equations, dg, boundary)
633
flux = boundary_condition(u_inner, orientations[boundary], direction, x, t,
634
surface_flux, equations)
635
636
# Copy flux to left and right element storage
637
for v in eachvariable(equations)
638
surface_flux_values[v, direction, neighbor] = flux[v]
639
end
640
end
641
642
return nothing
643
end
644
645
function calc_boundary_flux_by_direction!(surface_flux_values::AbstractArray{<:Any, 3},
646
t,
647
boundary_condition,
648
have_nonconservative_terms::True, equations,
649
surface_integral, dg::DG, cache,
650
direction, first_boundary, last_boundary)
651
@unpack u, neighbor_ids, neighbor_sides, node_coordinates, orientations = cache.boundaries
652
653
@threaded for boundary in first_boundary:last_boundary
654
# Get neighboring element
655
neighbor = neighbor_ids[boundary]
656
657
# Get boundary flux
658
u_ll, u_rr = get_surface_node_vars(u, equations, dg, boundary)
659
if neighbor_sides[boundary] == 1 # Element is on the left, boundary on the right
660
u_inner = u_ll
661
else # Element is on the right, boundary on the left
662
u_inner = u_rr
663
end
664
x = get_node_coords(node_coordinates, equations, dg, boundary)
665
666
flux, noncons_flux = boundary_condition(u_inner, orientations[boundary],
667
direction, x, t,
668
surface_integral.surface_flux,
669
equations)
670
671
# Copy flux to left and right element storage
672
for v in eachvariable(equations)
673
surface_flux_values[v, direction, neighbor] = flux[v] +
674
0.5f0 * noncons_flux[v]
675
end
676
end
677
678
return nothing
679
end
680
681
function calc_surface_integral!(backend::Nothing, du, u,
682
mesh::Union{TreeMesh{1}, StructuredMesh{1}},
683
equations, surface_integral::SurfaceIntegralWeakForm,
684
dg::DGSEM, cache)
685
@unpack inverse_weights = dg.basis
686
@unpack surface_flux_values = cache.elements
687
688
# This computes the **negative** surface integral contribution,
689
# i.e., M^{-1} * boundary_interpolation^T (which is for Gauss-Lobatto DGSEM just M^{-1} * B)
690
# and the missing "-" is taken care of by `apply_jacobian!`.
691
#
692
# We also use explicit assignments instead of `+=` to let `@muladd` turn these
693
# into FMAs (see comment at the top of the file).
694
factor = inverse_weights[1] # For LGL basis: Identical to weighted boundary interpolation at x = ±1
695
@threaded for element in eachelement(dg, cache)
696
for v in eachvariable(equations)
697
# surface at -x
698
du[v, 1, element] = (du[v, 1, element] -
699
surface_flux_values[v, 1, element] *
700
factor)
701
702
# surface at +x
703
du[v, nnodes(dg), element] = (du[v, nnodes(dg), element] +
704
surface_flux_values[v, 2, element] *
705
factor)
706
end
707
end
708
709
return nothing
710
end
711
712
function calc_surface_integral!(backend::Nothing, du, u,
713
mesh::Union{TreeMesh{1}, StructuredMesh{1}},
714
equations, surface_integral::SurfaceIntegralWeakForm,
715
dg::DGSEM{<:GaussLegendreBasis}, cache)
716
@unpack boundary_interpolation_inverse_weights = dg.basis
717
@unpack surface_flux_values = cache.elements
718
719
# This computes the **negative** surface integral contribution,
720
# i.e., M^{-1} * boundary_interpolation^T (which is for Gauss-Legendre DGSEM M^{-1} * L)
721
# and the missing "-" is taken care of by `apply_jacobian!`.
722
#
723
# We also use explicit assignments instead of `+=` to let `@muladd` turn these
724
# into FMAs (see comment at the top of the file).
725
@threaded for element in eachelement(dg, cache)
726
for v in eachvariable(equations)
727
# Aliases for repeatedly accessed variables
728
surface_flux_minus = surface_flux_values[v, 1, element]
729
surface_flux_plus = surface_flux_values[v, 2, element]
730
for ii in eachnode(dg)
731
# surface at -x
732
du[v, ii, element] = (du[v, ii, element] -
733
surface_flux_minus *
734
boundary_interpolation_inverse_weights[ii, 1])
735
736
# surface at +x
737
du[v, ii, element] = (du[v, ii, element] +
738
surface_flux_plus *
739
boundary_interpolation_inverse_weights[ii, 2])
740
end
741
end
742
end
743
744
return nothing
745
end
746
747
function apply_jacobian!(backend::Nothing, du, mesh::TreeMesh{1},
748
equations, dg::DG, cache)
749
@unpack inverse_jacobian = cache.elements
750
751
@threaded for element in eachelement(dg, cache)
752
# Negative sign included to account for the negated surface and volume terms,
753
# see e.g. the computation of `derivative_hat` in the basis setup and
754
# the comment in `calc_surface_integral!`.
755
factor = -inverse_jacobian[element]
756
757
for i in eachnode(dg)
758
for v in eachvariable(equations)
759
du[v, i, element] *= factor
760
end
761
end
762
end
763
764
return nothing
765
end
766
767
# Need dimension specific version to avoid error at dispatching
768
function calc_sources!(du, u, t, source_terms::Nothing,
769
equations::AbstractEquations{1}, dg::DG, cache)
770
return nothing
771
end
772
773
function calc_sources!(du, u, t, source_terms,
774
equations::AbstractEquations{1}, dg::DG, cache)
775
@unpack node_coordinates = cache.elements
776
777
@threaded for element in eachelement(dg, cache)
778
for i in eachnode(dg)
779
u_local = get_node_vars(u, equations, dg, i, element)
780
x_local = get_node_coords(node_coordinates, equations, dg,
781
i, element)
782
du_local = source_terms(u_local, x_local, t, equations)
783
add_to_node_vars!(du, du_local, equations, dg, i, element)
784
end
785
end
786
787
return nothing
788
end
789
end # @muladd
790
791