Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
trixi-framework
GitHub Repository: trixi-framework/Trixi.jl
Path: blob/main/src/solvers/dgmulti/dg.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
# out <- A*x
9
mul_by!(A) = @inline (out, x) -> matmul!(out, A, x)
10
mul_by!(A::T) where {T <: SimpleKronecker} = @inline (out, x) -> mul!(out, A, x)
11
mul_by!(A::AbstractSparseMatrix) = @inline (out, x) -> mul!(out, A, x)
12
function mul_by!(A::LinearAlgebra.AdjOrTrans{T, S}) where {T, S <: AbstractSparseMatrix}
13
@inline (out, x) -> mul!(out, A, x)
14
end
15
16
# out <- out + α * A * x
17
mul_by_accum!(A, α) = @inline (out, x) -> matmul!(out, A, x, α, One())
18
function mul_by_accum!(A::AbstractSparseMatrix, α)
19
@inline (out, x) -> mul!(out, A, x, α, One())
20
end
21
22
# out <- out + A * x
23
mul_by_accum!(A) = mul_by_accum!(A, One())
24
25
# specialize for SBP operators since `matmul!` doesn't work for `UniformScaling` types.
26
struct MulByUniformScaling end
27
struct MulByAccumUniformScaling end
28
mul_by!(A::UniformScaling) = MulByUniformScaling()
29
mul_by_accum!(A::UniformScaling) = MulByAccumUniformScaling()
30
31
# StructArray fallback
32
@inline function apply_to_each_field(f::F, args::Vararg{Any, N}) where {F, N}
33
return StructArrays.foreachfield(f, args...)
34
end
35
36
# specialize for UniformScaling types: works for either StructArray{SVector} or Matrix{SVector}
37
# solution storage formats.
38
@inline apply_to_each_field(f::MulByUniformScaling, out, x, args...) = copy!(out, x)
39
@inline function apply_to_each_field(f::MulByAccumUniformScaling, out, x, args...)
40
@threaded for i in eachindex(x)
41
out[i] = out[i] + x[i]
42
end
43
end
44
45
@inline nelements(dg::DGMulti, cache) = size(cache.solution_container.u_values)[end]
46
47
# Returns the components needed to iterate efficiently over the entries of either a
48
# `SparseMatrixCSC` or `Adjoint{SparseMatrixCSC}`, for example when performing flux
49
# differencing calculations.
50
#
51
# For `Adjoint{SparseMatrixCSC}` (used by `DGMultiFluxDiff`), since `parent(A)` is a
52
# `SparseMatrixCSC` stored in column-major order, iterating over its columns gives
53
# row-major access to `A`.
54
#
55
# For `SparseMatrixCSC` (used by `DGMultiPeriodicFDSBP`, for example), `parent(A)`
56
# simply returns `A`.
57
@inline function sparse_operator_data(A::Union{<:SparseMatrixCSC,
58
<:Adjoint{<:Any, <:SparseMatrixCSC}})
59
A_base = parent(A)
60
return A_base, axes(A, 2), rowvals(A_base), nonzeros(A_base)
61
end
62
63
"""
64
eachdim(mesh)
65
66
Return an iterator over the indices that specify the location in relevant data structures
67
for the dimensions in `AbstractTree`.
68
In particular, not the dimensions themselves are returned.
69
"""
70
@inline eachdim(mesh) = Base.OneTo(ndims(mesh))
71
72
# iteration over all elements in a mesh
73
@inline function ndofs(mesh::DGMultiMesh, dg::DGMulti, other_args...)
74
return dg.basis.Np * mesh.md.num_elements
75
end
76
"""
77
eachelement(mesh::DGMultiMesh, dg::DGMulti, other_args...)
78
79
Return an iterator over the indices that specify the location in relevant data structures
80
for the elements in `mesh`.
81
In particular, not the elements themselves are returned.
82
"""
83
@inline function eachelement(mesh::DGMultiMesh, dg::DGMulti, other_args...)
84
return Base.OneTo(mesh.md.num_elements)
85
end
86
87
# iteration over quantities in a single element
88
@inline nnodes(basis::RefElemData) = basis.Np
89
90
"""
91
each_face_node(mesh::DGMultiMesh, dg::DGMulti, other_args...)
92
93
Return an iterator over the indices that specify the location in relevant data structures
94
for the face nodes in `dg`.
95
In particular, not the face_nodes themselves are returned.
96
"""
97
@inline function each_face_node(mesh::DGMultiMesh, dg::DGMulti, other_args...)
98
return Base.OneTo(dg.basis.Nfq)
99
end
100
101
"""
102
each_quad_node(mesh::DGMultiMesh, dg::DGMulti, other_args...)
103
104
Return an iterator over the indices that specify the location in relevant data structures
105
for the quadrature nodes in `dg`.
106
In particular, not the quadrature nodes themselves are returned.
107
"""
108
@inline function each_quad_node(mesh::DGMultiMesh, dg::DGMulti, other_args...)
109
return Base.OneTo(dg.basis.Nq)
110
end
111
112
# iteration over quantities over the entire mesh (dofs, quad nodes, face nodes).
113
"""
114
each_dof_global(mesh::DGMultiMesh, dg::DGMulti, other_args...)
115
116
Return an iterator over the indices that specify the location in relevant data structures
117
for the degrees of freedom (DOF) in `dg`.
118
In particular, not the DOFs themselves are returned.
119
"""
120
@inline function each_dof_global(mesh::DGMultiMesh, dg::DGMulti, other_args...)
121
return Base.OneTo(ndofs(mesh, dg, other_args...))
122
end
123
124
"""
125
each_quad_node_global(mesh::DGMultiMesh, dg::DGMulti, other_args...)
126
127
Return an iterator over the indices that specify the location in relevant data structures
128
for the global quadrature nodes in `mesh`.
129
In particular, not the quadrature nodes themselves are returned.
130
"""
131
@inline function each_quad_node_global(mesh::DGMultiMesh, dg::DGMulti, other_args...)
132
return Base.OneTo(dg.basis.Nq * mesh.md.num_elements)
133
end
134
135
"""
136
each_face_node_global(mesh::DGMultiMesh, dg::DGMulti, other_args...)
137
138
Return an iterator over the indices that specify the location in relevant data structures
139
for the face nodes in `mesh`.
140
In particular, not the face nodes themselves are returned.
141
"""
142
@inline function each_face_node_global(mesh::DGMultiMesh, dg::DGMulti, other_args...)
143
return Base.OneTo(dg.basis.Nfq * mesh.md.num_elements)
144
end
145
146
# The `nmodes` functions returns the number of polynomial modes for a degree N
147
# approximation on a specific type of element. For tensor product elements (Quad, Hex),
148
# this is the total number of modes in all dimensions, i.e., (N+1)^NDIMS.
149
@inline nmodes(N, ::Line) = N + 1
150
@inline nmodes(N, ::Quad) = (N + 1)^2
151
@inline nmodes(N, ::Hex) = (N + 1)^3
152
153
# interface with semidiscretization_hyperbolic
154
wrap_array(u_ode, mesh::DGMultiMesh, equations, dg::DGMulti, cache) = u_ode
155
wrap_array_native(u_ode, mesh::DGMultiMesh, equations, dg::DGMulti, cache) = u_ode
156
157
# used to initialize `u_ode` in `semidiscretize`
158
function allocate_coefficients(mesh::DGMultiMesh, equations, dg::DGMulti, cache)
159
return VectorOfArray(allocate_nested_array(real(dg), nvariables(equations),
160
size(mesh.md.x), dg))
161
end
162
wrap_array(u_ode::VectorOfArray, mesh::DGMultiMesh, equations, dg::DGMulti, cache) = parent(u_ode)
163
164
function digest_boundary_conditions(boundary_conditions::NamedTuple{Keys, ValueTypes},
165
mesh::DGMultiMesh, dg::DGMulti,
166
cache) where {Keys, ValueTypes <: NTuple{N, Any}
167
} where {N}
168
return boundary_conditions
169
end
170
171
# Allocate nested array type for DGMulti solution storage.
172
function allocate_nested_array(uEltype, nvars, array_dimensions, dg)
173
# store components as separate arrays, combine via StructArrays
174
return StructArray{SVector{nvars, uEltype}}(ntuple(_ -> zeros(uEltype,
175
array_dimensions...),
176
nvars))
177
end
178
179
function set_zero!(du, dg::DGMulti, other_args...)
180
@threaded for i in eachindex(du)
181
du[i] = zero(eltype(du))
182
end
183
184
return nothing
185
end
186
187
# Holds arrays shared across most DGMulti cache types:
188
# solution values at volume/face quadrature points and thread-local scratch storage.
189
struct DGMultiSolutionContainer{uType, ufType, ffType, lType}
190
u_values::uType
191
u_face_values::ufType
192
flux_face_values::ffType
193
local_values_threaded::lType
194
end
195
196
# Allocates arrays shared across most DGMulti cache types.
197
function initialize_dgmulti_solution_container(mesh::DGMultiMesh, equations,
198
dg::DGMulti,
199
uEltype)
200
rd = dg.basis
201
md = mesh.md
202
nvars = nvariables(equations)
203
u_values = allocate_nested_array(uEltype, nvars, size(md.xq), dg)
204
u_face_values = allocate_nested_array(uEltype, nvars, size(md.xf), dg)
205
flux_face_values = allocate_nested_array(uEltype, nvars, size(md.xf), dg)
206
local_values_threaded = [allocate_nested_array(uEltype, nvars, (rd.Nq,), dg)
207
for _ in 1:Threads.maxthreadid()]
208
return DGMultiSolutionContainer(u_values, u_face_values, flux_face_values,
209
local_values_threaded)
210
end
211
212
# Constructs cache variables for both affine and non-affine (curved) DGMultiMeshes
213
function create_cache(mesh::DGMultiMesh{NDIMS}, equations, dg::DGMultiWeakForm, RealT,
214
uEltype) where {NDIMS}
215
rd = dg.basis
216
md = mesh.md
217
218
# volume quadrature weights, volume interpolation matrix, mass matrix, differentiation matrices
219
@unpack wq, Vq, M, Drst = rd
220
221
# ∫f(u) * dv/dx_i = ∑_j (Vq*Drst[i])'*diagm(wq)*(rstxyzJ[i,j].*f(Vq*u))
222
weak_differentiation_matrices = map(D -> -M \ ((Vq * D)' * Diagonal(wq)), Drst)
223
224
if typeof(rd.approximation_type) <:
225
Union{SBP, AbstractNonperiodicDerivativeOperator}
226
lift_scalings = rd.wf ./ rd.wq[rd.Fmask] # lift scalings for diag-norm SBP operators
227
else
228
lift_scalings = nothing
229
end
230
231
# For curved meshes, we interpolate geometric terms from nodal points to quadrature points.
232
# For affine meshes, we just access one element of this interpolated data.
233
dxidxhatj = map(x -> rd.Vq * x, md.rstxyzJ)
234
235
# interpolate J to quadrature points for weight-adjusted DG (WADG)
236
invJ = inv.(rd.Vq * md.J)
237
238
# for scaling by curved geometric terms (not used by affine DGMultiMesh)
239
nvars = nvariables(equations)
240
flux_threaded = [[allocate_nested_array(uEltype, nvars, (rd.Nq,), dg)
241
for _ in 1:NDIMS] for _ in 1:Threads.maxthreadid()]
242
rotated_flux_threaded = [allocate_nested_array(uEltype, nvars, (rd.Nq,), dg)
243
for _ in 1:Threads.maxthreadid()]
244
245
solution_container = initialize_dgmulti_solution_container(mesh, equations, dg,
246
uEltype)
247
248
return (; md, weak_differentiation_matrices, lift_scalings, invJ, dxidxhatj,
249
solution_container, flux_threaded, rotated_flux_threaded)
250
end
251
252
function compute_coefficients!(::Nothing, u, initial_condition, t,
253
mesh::DGMultiMesh, equations, dg::DGMulti, cache)
254
md = mesh.md
255
rd = dg.basis
256
(; u_values) = cache.solution_container
257
258
# evaluate the initial condition at quadrature points
259
@threaded for i in each_quad_node_global(mesh, dg, cache)
260
u_values[i] = initial_condition(SVector(getindex.(md.xyzq, i)),
261
t, equations)
262
end
263
264
# multiplying by Pq computes the L2 projection
265
apply_to_each_field(mul_by!(rd.Pq), u, u_values)
266
267
return nothing
268
end
269
270
# estimates the timestep based on polynomial degree and mesh. Does not account for physics (e.g.,
271
# computes an estimate of `dt` based on the advection equation with constant unit advection speed).
272
function estimate_dt(mesh::DGMultiMesh, dg::DGMulti)
273
rd = dg.basis # RefElemData
274
return StartUpDG.estimate_h(rd, mesh.md) / StartUpDG.inverse_trace_constant(rd)
275
end
276
277
dt_polydeg_scaling(dg::DGMulti) = inv(dg.basis.N + 1)
278
function dt_polydeg_scaling(dg::DGMulti{3, <:Wedge, <:TensorProductWedge})
279
return inv(maximum(dg.basis.N) + 1)
280
end
281
282
# for the stepsize callback
283
function max_dt(u, t, mesh::DGMultiMesh,
284
constant_diffusivity::False, equations,
285
equations_parabolic::AbstractEquationsParabolic,
286
dg::DGMulti{NDIMS},
287
cache) where {NDIMS}
288
@unpack md = mesh
289
rd = dg.basis
290
291
dt_min = floatmax(typeof(t))
292
for e in eachelement(mesh, dg, cache)
293
h_e = StartUpDG.estimate_h(e, rd, md)
294
max_speeds = ntuple(_ -> nextfloat(zero(t)), NDIMS)
295
for i in Base.OneTo(rd.Np) # loop over nodes
296
lambda_i = max_abs_speeds(u[i, e], equations)
297
298
# estimate diffusive "wavespeed" as diffusivity / h
299
# this corresponds to a CFL of h^2 * diffusivity
300
diffusivity = max_diffusivity(u[i, e], equations_parabolic)
301
max_speeds = max.(max_speeds, lambda_i, diffusivity / h_e)
302
end
303
dt_min = min(dt_min, h_e / sum(max_speeds))
304
end
305
# This mimics `max_dt` for `TreeMesh`, except that `nnodes(dg)` is replaced by
306
# `polydeg+1`. This is because `nnodes(dg)` returns the total number of
307
# multi-dimensional nodes for DGMulti solver types, while `nnodes(dg)` returns
308
# the number of 1D nodes for `DGSEM` solvers.
309
return 2 * dt_min * dt_polydeg_scaling(dg)
310
end
311
312
function max_dt(u, t, mesh::DGMultiMesh,
313
constant_diffusivity::True, equations,
314
equations_parabolic::AbstractEquationsParabolic,
315
dg::DGMulti{NDIMS},
316
cache) where {NDIMS}
317
@unpack md = mesh
318
rd = dg.basis
319
320
# Compute max_speeds only once, since it's constant for all nodes/elements
321
max_speeds = max_abs_speeds(equations)
322
323
# estimate diffusive "wavespeed" as diffusivity / h
324
# this corresponds to a CFL of h^2 * diffusivity
325
diffusivity = max_diffusivity(equations_parabolic)
326
327
dt_min = floatmax(typeof(t))
328
for e in eachelement(mesh, dg, cache)
329
h_e = StartUpDG.estimate_h(e, rd, md)
330
max_speeds = max.(max_speeds, diffusivity / h_e)
331
dt_min = min(dt_min, h_e / sum(max_speeds))
332
end
333
# This mimics `max_dt` for `TreeMesh`, except that `nnodes(dg)` is replaced by
334
# `polydeg+1`. This is because `nnodes(dg)` returns the total number of
335
# multi-dimensional nodes for DGMulti solver types, while `nnodes(dg)` returns
336
# the number of 1D nodes for `DGSEM` solvers.
337
return 2 * dt_min * dt_polydeg_scaling(dg)
338
end
339
340
# for the stepsize callback
341
function max_dt(u, t, mesh::DGMultiMesh,
342
constant_speed::False, equations, dg::DGMulti{NDIMS},
343
cache) where {NDIMS}
344
@unpack md = mesh
345
rd = dg.basis
346
347
dt_min = floatmax(typeof(t))
348
for e in eachelement(mesh, dg, cache)
349
h_e = StartUpDG.estimate_h(e, rd, md)
350
max_speeds = ntuple(_ -> nextfloat(zero(t)), NDIMS)
351
for i in Base.OneTo(rd.Np) # loop over nodes
352
lambda_i = max_abs_speeds(u[i, e], equations)
353
max_speeds = max.(max_speeds, lambda_i)
354
end
355
dt_min = min(dt_min, h_e / sum(max_speeds))
356
end
357
# This mimics `max_dt` for `TreeMesh`, except that `nnodes(dg)` is replaced by
358
# `polydeg+1`. This is because `nnodes(dg)` returns the total number of
359
# multi-dimensional nodes for DGMulti solver types, while `nnodes(dg)` returns
360
# the number of 1D nodes for `DGSEM` solvers.
361
return 2 * dt_min * dt_polydeg_scaling(dg)
362
end
363
364
function max_dt(u, t, mesh::DGMultiMesh,
365
constant_speed::True, equations, dg::DGMulti{NDIMS},
366
cache) where {NDIMS}
367
@unpack md = mesh
368
rd = dg.basis
369
370
# Compute max_speeds only once, since it's constant for all nodes/elements
371
max_speeds = max_abs_speeds(equations)
372
373
dt_min = floatmax(typeof(t))
374
for e in eachelement(mesh, dg, cache)
375
h_e = StartUpDG.estimate_h(e, rd, md)
376
dt_min = min(dt_min, h_e / sum(max_speeds))
377
end
378
# This mimics `max_dt` for `TreeMesh`, except that `nnodes(dg)` is replaced by
379
# `polydeg+1`. This is because `nnodes(dg)` returns the total number of
380
# multi-dimensional nodes for DGMulti solver types, while `nnodes(dg)` returns
381
# the number of 1D nodes for `DGSEM` solvers.
382
return 2 * dt_min * dt_polydeg_scaling(dg)
383
end
384
385
# interpolates from solution coefficients to face quadrature points
386
function prolong2interfaces!(cache, u,
387
mesh::DGMultiMesh, equations, dg::DGMulti)
388
rd = dg.basis
389
(; u_face_values) = cache.solution_container
390
apply_to_each_field(mul_by!(rd.Vf), u_face_values, u)
391
392
return nothing
393
end
394
395
# CARE: This function requires that interpolation to quadrature points is performed before
396
# to populate cache.solution_container.u_values, see `calc_volume_integral!` for `VolumeIntegralWeakForm`.
397
# version for affine meshes
398
@inline function volume_integral_kernel!(du, u, element, mesh::DGMultiMesh,
399
have_nonconservative_terms::False, equations,
400
volume_integral::VolumeIntegralWeakForm,
401
dg::DGMulti, cache)
402
@unpack weak_differentiation_matrices, dxidxhatj = cache
403
(; u_values, local_values_threaded) = cache.solution_container
404
405
flux_values = local_values_threaded[Threads.threadid()]
406
for i in eachdim(mesh)
407
# Here, the broadcasting operation does allocate
408
#flux_values .= flux.(view(u_values, :, e), i, equations)
409
# Use loop instead
410
for j in eachindex(flux_values)
411
flux_values[j] = flux(u_values[j, element], i, equations)
412
end
413
for j in eachdim(mesh)
414
apply_to_each_field(mul_by_accum!(weak_differentiation_matrices[j],
415
dxidxhatj[i, j][1, element]),
416
view(du, :, element), flux_values)
417
end
418
end
419
420
return nothing
421
end
422
423
# CARE: This function requires that interpolation to quadrature points is performed before
424
# to populate cache.solution_container.u_values, see `calc_volume_integral!` for `VolumeIntegralWeakForm`.
425
# version for curved meshes
426
@inline function volume_integral_kernel!(du, u, element,
427
mesh::DGMultiMesh{NDIMS, <:NonAffine},
428
have_nonconservative_terms::False, equations,
429
volume_integral::VolumeIntegralWeakForm,
430
dg::DGMulti, cache) where {NDIMS}
431
(; weak_differentiation_matrices, dxidxhatj) = cache
432
(; u_values) = cache.solution_container
433
434
flux_values = cache.flux_threaded[Threads.threadid()]
435
for i in eachdim(mesh)
436
# Here, the broadcasting operation does not allocate
437
flux_values[i] .= flux.(view(u_values, :, element), i, equations)
438
end
439
440
# rotate flux with df_i/dx_i = sum_j d(x_i)/d(x̂_j) * d(f_i)/d(x̂_j).
441
# Example: df_x/dx + df_y/dy = dr/dx * df_x/dr + ds/dx * df_x/ds
442
# + dr/dy * df_y/dr + ds/dy * df_y/ds
443
# = Dr * (dr/dx * fx + dr/dy * fy) + Ds * (...)
444
# = Dr * (f_r) + Ds * (f_s)
445
446
rotated_flux_values = cache.rotated_flux_threaded[Threads.threadid()]
447
for j in eachdim(mesh)
448
fill!(rotated_flux_values, zero(eltype(rotated_flux_values)))
449
450
# compute rotated fluxes
451
for i in eachdim(mesh)
452
for ii in eachindex(rotated_flux_values)
453
flux_i_node = flux_values[i][ii]
454
dxidxhatj_node = dxidxhatj[i, j][ii, element]
455
rotated_flux_values[ii] = rotated_flux_values[ii] +
456
dxidxhatj_node * flux_i_node
457
end
458
end
459
460
# apply weak differentiation matrices to rotated fluxes
461
apply_to_each_field(mul_by_accum!(weak_differentiation_matrices[j]),
462
view(du, :, element), rotated_flux_values)
463
end
464
465
return nothing
466
end
467
468
function calc_volume_integral!(du, u, mesh::DGMultiMesh,
469
have_nonconservative_terms, equations,
470
volume_integral::VolumeIntegralWeakForm, dg::DGMulti,
471
cache)
472
rd = dg.basis
473
(; u_values) = cache.solution_container
474
# interpolate to quadrature points
475
apply_to_each_field(mul_by!(rd.Vq), u_values, u)
476
477
@threaded for element in eachelement(mesh, dg, cache)
478
volume_integral_kernel!(du, u, element, mesh,
479
have_nonconservative_terms, equations,
480
volume_integral, dg, cache)
481
end
482
483
return nothing
484
end
485
486
function calc_interface_flux!(cache, surface_integral::SurfaceIntegralWeakForm,
487
mesh::DGMultiMesh,
488
have_nonconservative_terms::False, equations,
489
dg::DGMulti{NDIMS}) where {NDIMS}
490
@unpack surface_flux = surface_integral
491
md = mesh.md
492
@unpack mapM, mapP, nxyzJ, Jf = md
493
(; u_face_values, flux_face_values) = cache.solution_container
494
495
@threaded for face_node_index in each_face_node_global(mesh, dg, cache)
496
497
# inner (idM -> minus) and outer (idP -> plus) indices
498
idM, idP = mapM[face_node_index], mapP[face_node_index]
499
uM = u_face_values[idM]
500
uP = u_face_values[idP]
501
normal = SVector{NDIMS}(getindex.(nxyzJ, idM)) / Jf[idM]
502
flux_face_values[idM] = surface_flux(uM, uP, normal, equations) * Jf[idM]
503
end
504
505
return nothing
506
end
507
508
function calc_interface_flux!(cache, surface_integral::SurfaceIntegralWeakForm,
509
mesh::DGMultiMesh,
510
have_nonconservative_terms::True, equations,
511
dg::DGMulti{NDIMS}) where {NDIMS}
512
flux_conservative, flux_nonconservative = surface_integral.surface_flux
513
md = mesh.md
514
@unpack mapM, mapP, nxyzJ, Jf = md
515
(; u_face_values, flux_face_values) = cache.solution_container
516
517
@threaded for face_node_index in each_face_node_global(mesh, dg, cache)
518
519
# inner (idM -> minus) and outer (idP -> plus) indices
520
idM, idP = mapM[face_node_index], mapP[face_node_index]
521
uM = u_face_values[idM]
522
523
# compute flux if node is not a boundary node
524
if idM != idP
525
uP = u_face_values[idP]
526
normal = SVector{NDIMS}(getindex.(nxyzJ, idM)) / Jf[idM]
527
conservative_part = flux_conservative(uM, uP, normal, equations)
528
529
# Two notes on the use of `flux_nonconservative`:
530
# 1. In contrast to other mesh types, only one nonconservative part needs to be
531
# computed since we loop over the elements, not the unique interfaces.
532
nonconservative_part = flux_nonconservative(uM, uP, normal, equations)
533
# The factor 0.5 is necessary for the nonconservative fluxes based on the
534
# interpretation of global SBP operators.
535
flux_face_values[idM] = (conservative_part + 0.5 * nonconservative_part) *
536
Jf[idM]
537
end
538
end
539
540
return nothing
541
end
542
543
# assumes cache.solution_container.flux_face_values is computed and filled with
544
# for polynomial discretizations, use dense LIFT matrix for surface contributions.
545
function calc_surface_integral!(du, u, mesh::DGMultiMesh, equations,
546
surface_integral::SurfaceIntegralWeakForm,
547
dg::DGMulti, cache)
548
rd = dg.basis
549
apply_to_each_field(mul_by_accum!(rd.LIFT), du,
550
cache.solution_container.flux_face_values)
551
552
return nothing
553
end
554
555
# Specialize for nodal SBP discretizations. Uses that Vf*u = u[Fmask,:]
556
function prolong2interfaces!(cache, u,
557
mesh::DGMultiMesh, equations, dg::DGMultiSBP)
558
rd = dg.basis
559
@unpack Fmask = rd
560
(; u_face_values) = cache.solution_container
561
@threaded for e in eachelement(mesh, dg, cache)
562
for (i, fid) in enumerate(Fmask)
563
u_face_values[i, e] = u[fid, e]
564
end
565
end
566
567
return nothing
568
end
569
570
# Specialize for nodal SBP discretizations. Uses that du = LIFT*u is equivalent to
571
# du[Fmask,:] .= u ./ rd.wq[rd.Fmask]
572
function calc_surface_integral!(du, u, mesh::DGMultiMesh, equations,
573
surface_integral::SurfaceIntegralWeakForm,
574
dg::DGMultiSBP, cache)
575
rd = dg.basis
576
(; flux_face_values) = cache.solution_container
577
@unpack lift_scalings = cache
578
579
@threaded for e in eachelement(mesh, dg, cache)
580
for i in each_face_node(mesh, dg, cache)
581
fid = rd.Fmask[i]
582
du[fid, e] = du[fid, e] + flux_face_values[i, e] * lift_scalings[i]
583
end
584
end
585
586
return nothing
587
end
588
589
# do nothing for periodic (default) boundary conditions
590
function calc_boundary_flux!(cache, t, boundary_conditions::BoundaryConditionPeriodic,
591
mesh, have_nonconservative_terms, equations, dg::DGMulti)
592
return nothing
593
end
594
595
function calc_boundary_flux!(cache, t, boundary_conditions, mesh,
596
have_nonconservative_terms, equations, dg::DGMulti)
597
for (key, value) in zip(keys(boundary_conditions), boundary_conditions)
598
calc_single_boundary_flux!(cache, t, value,
599
key,
600
mesh, have_nonconservative_terms, equations, dg)
601
end
602
603
return nothing
604
end
605
606
function calc_single_boundary_flux!(cache, t, boundary_condition, boundary_key, mesh,
607
have_nonconservative_terms::False, equations,
608
dg::DGMulti{NDIMS}) where {NDIMS}
609
rd = dg.basis
610
md = mesh.md
611
(; u_face_values, flux_face_values) = cache.solution_container
612
@unpack xyzf, nxyzJ, Jf = md
613
@unpack surface_flux = dg.surface_integral
614
615
# reshape face/normal arrays to have size = (num_points_on_face, num_faces_total).
616
# mesh.boundary_faces indexes into the columns of these face-reshaped arrays.
617
num_faces = StartUpDG.num_faces(rd.element_type)
618
num_pts_per_face = rd.Nfq ÷ num_faces
619
num_faces_total = num_faces * md.num_elements
620
621
# This function was originally defined as
622
# `reshape_by_face(u) = reshape(view(u, :), num_pts_per_face, num_faces_total)`.
623
# This results in allocations due to https://github.com/JuliaLang/julia/issues/36313.
624
# To avoid allocations, we use Tim Holy's suggestion:
625
# https://github.com/JuliaLang/julia/issues/36313#issuecomment-782336300.
626
reshape_by_face(u) = Base.ReshapedArray(u, (num_pts_per_face, num_faces_total), ())
627
628
u_face_values = reshape_by_face(u_face_values)
629
flux_face_values = reshape_by_face(flux_face_values)
630
Jf = reshape_by_face(Jf)
631
nxyzJ, xyzf = reshape_by_face.(nxyzJ), reshape_by_face.(xyzf) # broadcast over nxyzJ::NTuple{NDIMS,Matrix}
632
633
# loop through boundary faces, which correspond to columns of reshaped u_face_values, ...
634
for f in mesh.boundary_faces[boundary_key]
635
for i in Base.OneTo(num_pts_per_face)
636
face_normal = SVector{NDIMS}(getindex.(nxyzJ, i, f)) / Jf[i, f]
637
face_coordinates = SVector{NDIMS}(getindex.(xyzf, i, f))
638
flux_face_values[i, f] = boundary_condition(u_face_values[i, f],
639
face_normal, face_coordinates,
640
t,
641
surface_flux, equations) *
642
Jf[i, f]
643
end
644
end
645
646
# Note: modifying the values of the reshaped array modifies the values of cache.solution_container.flux_face_values.
647
# However, we don't have to re-reshape, since cache.solution_container.flux_face_values still retains its original shape.
648
649
return nothing
650
end
651
652
function calc_single_boundary_flux!(cache, t, boundary_condition, boundary_key, mesh,
653
have_nonconservative_terms::True, equations,
654
dg::DGMulti{NDIMS}) where {NDIMS}
655
rd = dg.basis
656
md = mesh.md
657
658
# reshape face/normal arrays to have size = (num_points_on_face, num_faces_total).
659
# mesh.boundary_faces indexes into the columns of these face-reshaped arrays.
660
num_pts_per_face = rd.Nfq ÷ StartUpDG.num_faces(rd.element_type)
661
num_faces_total = StartUpDG.num_faces(rd.element_type) * md.num_elements
662
663
# This function was originally defined as
664
# `reshape_by_face(u) = reshape(view(u, :), num_pts_per_face, num_faces_total)`.
665
# This results in allocations due to https://github.com/JuliaLang/julia/issues/36313.
666
# To avoid allocations, we use Tim Holy's suggestion:
667
# https://github.com/JuliaLang/julia/issues/36313#issuecomment-782336300.
668
reshape_by_face(u) = Base.ReshapedArray(u, (num_pts_per_face, num_faces_total), ())
669
670
u_face_values = reshape_by_face(cache.solution_container.u_face_values)
671
flux_face_values = reshape_by_face(cache.solution_container.flux_face_values)
672
Jf = reshape_by_face(md.Jf)
673
nxyzJ, xyzf = reshape_by_face.(md.nxyzJ), reshape_by_face.(md.xyzf) # broadcast over nxyzJ::NTuple{NDIMS,Matrix}
674
675
# loop through boundary faces, which correspond to columns of reshaped u_face_values, ...
676
for f in mesh.boundary_faces[boundary_key]
677
for i in Base.OneTo(num_pts_per_face)
678
face_normal = SVector{NDIMS}(getindex.(nxyzJ, i, f)) / Jf[i, f]
679
face_coordinates = SVector{NDIMS}(getindex.(xyzf, i, f))
680
681
# Compute conservative and non-conservative fluxes separately.
682
# This imposes boundary conditions on the conservative part of the flux.
683
cons_flux_at_face_node, noncons_flux_at_face_node = boundary_condition(u_face_values[i,
684
f],
685
face_normal,
686
face_coordinates,
687
t,
688
dg.surface_integral.surface_flux,
689
equations)
690
691
flux_face_values[i, f] = (cons_flux_at_face_node +
692
0.5f0 * noncons_flux_at_face_node) * Jf[i, f]
693
end
694
end
695
696
# Note: modifying the values of the reshaped array modifies the values of cache.solution_container.flux_face_values.
697
# However, we don't have to re-reshape, since cache.solution_container.flux_face_values still retains its original shape.
698
699
return nothing
700
end
701
702
# inverts Jacobian and scales by -1.0
703
function invert_jacobian!(du, mesh::DGMultiMesh, equations, dg::DGMulti, cache;
704
scaling = -1)
705
@threaded for e in eachelement(mesh, dg, cache)
706
invJ = cache.invJ[1, e]
707
for i in axes(du, 1)
708
du[i, e] *= scaling * invJ
709
end
710
end
711
712
return nothing
713
end
714
715
# inverts Jacobian using weight-adjusted DG, and scales by -1.0.
716
# - Chan, Jesse, Russell J. Hewett, and Timothy Warburton.
717
# "Weight-adjusted discontinuous Galerkin methods: curvilinear meshes."
718
# https://doi.org/10.1137/16M1089198
719
function invert_jacobian!(du, mesh::DGMultiMesh{NDIMS, <:NonAffine}, equations,
720
dg::DGMulti, cache; scaling = -1) where {NDIMS}
721
# Vq = interpolation matrix to quadrature points, Pq = quadrature-based L2 projection matrix
722
(; Pq, Vq) = dg.basis
723
(; invJ) = cache
724
(; local_values_threaded) = cache.solution_container
725
726
@threaded for e in eachelement(mesh, dg, cache)
727
du_at_quad_points = local_values_threaded[Threads.threadid()]
728
729
# interpolate solution to quadrature
730
apply_to_each_field(mul_by!(Vq), du_at_quad_points, view(du, :, e))
731
732
# scale by quadrature points
733
for i in eachindex(du_at_quad_points)
734
du_at_quad_points[i] *= scaling * invJ[i, e]
735
end
736
737
# project back to polynomials
738
apply_to_each_field(mul_by!(Pq), view(du, :, e), du_at_quad_points)
739
end
740
741
return nothing
742
end
743
744
# Multiple calc_sources! to resolve method ambiguities
745
function calc_sources!(du, u, t, source_terms::Nothing,
746
mesh, equations, dg::DGMulti, cache)
747
return nothing
748
end
749
function calc_sources!(du, u, t, source_terms::Nothing,
750
mesh, equations, dg::DGMultiFluxDiffSBP, cache)
751
return nothing
752
end
753
754
# uses quadrature + projection to compute source terms.
755
function calc_sources!(du, u, t, source_terms,
756
mesh, equations, dg::DGMulti, cache)
757
rd = dg.basis
758
md = mesh.md
759
@unpack Pq = rd
760
(; u_values, local_values_threaded) = cache.solution_container
761
@threaded for e in eachelement(mesh, dg, cache)
762
source_values = local_values_threaded[Threads.threadid()]
763
764
u_e = view(u_values, :, e) # u_values should already be computed from volume integral
765
766
for i in each_quad_node(mesh, dg, cache)
767
source_values[i] = source_terms(u_e[i], SVector(getindex.(md.xyzq, i, e)),
768
t, equations)
769
end
770
apply_to_each_field(mul_by_accum!(Pq), view(du, :, e), source_values)
771
end
772
773
return nothing
774
end
775
776
function rhs!(du, u, t, mesh, equations,
777
boundary_conditions::BC, source_terms::Source,
778
dg::DGMulti, cache) where {BC, Source}
779
@trixi_timeit timer() "reset ∂u/∂t" set_zero!(du, dg, cache)
780
781
@trixi_timeit timer() "volume integral" begin
782
calc_volume_integral!(du, u, mesh,
783
have_nonconservative_terms(equations), equations,
784
dg.volume_integral, dg, cache)
785
end
786
787
@trixi_timeit timer() "prolong2interfaces" begin
788
prolong2interfaces!(cache, u, mesh, equations, dg)
789
end
790
791
@trixi_timeit timer() "interface flux" begin
792
calc_interface_flux!(cache, dg.surface_integral, mesh,
793
have_nonconservative_terms(equations), equations, dg)
794
end
795
796
@trixi_timeit timer() "boundary flux" begin
797
calc_boundary_flux!(cache, t, boundary_conditions, mesh,
798
have_nonconservative_terms(equations), equations, dg)
799
end
800
801
@trixi_timeit timer() "surface integral" begin
802
calc_surface_integral!(du, u, mesh, equations, dg.surface_integral, dg, cache)
803
end
804
805
@trixi_timeit timer() "Jacobian" invert_jacobian!(du, mesh, equations, dg, cache)
806
807
@trixi_timeit timer() "source terms" begin
808
calc_sources!(du, u, t, source_terms, mesh, equations, dg, cache)
809
end
810
811
return nothing
812
end
813
end # @muladd
814
815