Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
trixi-framework
GitHub Repository: trixi-framework/Trixi.jl
Path: blob/main/src/solvers/fdsbp_tree/fdsbp_3d.jl
5591 views
1
# !!! warning "Experimental implementation (upwind SBP)"
2
# This is an experimental feature and may change in future releases.
3
4
# By default, Julia/LLVM does not use fused multiply-add operations (FMAs).
5
# Since these FMAs can increase the performance of many numerical algorithms,
6
# we need to opt-in explicitly.
7
# See https://ranocha.de/blog/Optimizing_EC_Trixi for further details.
8
@muladd begin
9
#! format: noindent
10
11
# 3D caches
12
function create_cache(mesh::TreeMesh{3}, equations,
13
volume_integral::VolumeIntegralStrongForm,
14
dg, cache_containers, uEltype)
15
prototype = Array{SVector{nvariables(equations), uEltype}, ndims(mesh)}(undef,
16
ntuple(_ -> nnodes(dg),
17
ndims(mesh))...)
18
f_threaded = [similar(prototype) for _ in 1:Threads.maxthreadid()]
19
20
return (; f_threaded)
21
end
22
23
function create_cache(mesh::TreeMesh{3}, equations,
24
volume_integral::VolumeIntegralUpwind,
25
dg, cache_containers, uEltype)
26
u_node = SVector{nvariables(equations), uEltype}(ntuple(_ -> zero(uEltype),
27
Val{nvariables(equations)}()))
28
f = StructArray([(u_node, u_node)])
29
f_minus_plus_threaded = [similar(f, ntuple(_ -> nnodes(dg), ndims(mesh))...)
30
for _ in 1:Threads.maxthreadid()]
31
32
f_minus, f_plus = StructArrays.components(f_minus_plus_threaded[1])
33
f_minus_threaded = [f_minus]
34
f_plus_threaded = [f_plus]
35
for i in 2:Threads.maxthreadid()
36
f_minus, f_plus = StructArrays.components(f_minus_plus_threaded[i])
37
push!(f_minus_threaded, f_minus)
38
push!(f_plus_threaded, f_plus)
39
end
40
41
return (; f_minus_plus_threaded, f_minus_threaded, f_plus_threaded)
42
end
43
44
# 3D volume integral contributions for `VolumeIntegralStrongForm`
45
function calc_volume_integral!(backend::Nothing, du, u,
46
mesh::TreeMesh{3},
47
have_nonconservative_terms::False, equations,
48
volume_integral::VolumeIntegralStrongForm,
49
dg::FDSBP, cache)
50
D = dg.basis # SBP derivative operator
51
@unpack f_threaded = cache
52
53
# SBP operators from SummationByPartsOperators.jl implement the basic interface
54
# of matrix-vector multiplication. Thus, we pass an "array of structures",
55
# packing all variables per node in an `SVector`.
56
if nvariables(equations) == 1
57
# `reinterpret(reshape, ...)` removes the leading dimension only if more
58
# than one variable is used.
59
u_vectors = reshape(reinterpret(SVector{nvariables(equations), eltype(u)}, u),
60
nnodes(dg), nnodes(dg), nnodes(dg), nelements(dg, cache))
61
du_vectors = reshape(reinterpret(SVector{nvariables(equations), eltype(du)},
62
du),
63
nnodes(dg), nnodes(dg), nnodes(dg), nelements(dg, cache))
64
else
65
u_vectors = reinterpret(reshape, SVector{nvariables(equations), eltype(u)}, u)
66
du_vectors = reinterpret(reshape, SVector{nvariables(equations), eltype(du)},
67
du)
68
end
69
70
# Use the tensor product structure to compute the discrete derivatives of
71
# the fluxes line-by-line and add them to `du` for each element.
72
@threaded for element in eachelement(dg, cache)
73
f_element = f_threaded[Threads.threadid()]
74
u_element = view(u_vectors, :, :, :, element)
75
76
# x direction
77
@. f_element = flux(u_element, 1, equations)
78
for j in eachnode(dg), k in eachnode(dg)
79
mul!(view(du_vectors, :, j, k, element), D, view(f_element, :, j, k),
80
one(eltype(du)), one(eltype(du)))
81
end
82
83
# y direction
84
@. f_element = flux(u_element, 2, equations)
85
for i in eachnode(dg), k in eachnode(dg)
86
mul!(view(du_vectors, i, :, k, element), D, view(f_element, i, :, k),
87
one(eltype(du)), one(eltype(du)))
88
end
89
90
# z direction
91
@. f_element = flux(u_element, 3, equations)
92
for i in eachnode(dg), j in eachnode(dg)
93
mul!(view(du_vectors, i, j, :, element), D, view(f_element, i, j, :),
94
one(eltype(du)), one(eltype(du)))
95
end
96
end
97
98
return nothing
99
end
100
101
# 3D volume integral contributions for `VolumeIntegralUpwind`.
102
# Note that the plus / minus notation of the operators does not refer to the
103
# upwind / downwind directions of the fluxes.
104
# Instead, the plus / minus refers to the direction of the biasing within
105
# the finite difference stencils. Thus, the D^- operator acts on the positive
106
# part of the flux splitting f^+ and the D^+ operator acts on the negative part
107
# of the flux splitting f^-.
108
function calc_volume_integral!(backend::Nothing, du, u,
109
mesh::TreeMesh{3},
110
have_nonconservative_terms::False, equations,
111
volume_integral::VolumeIntegralUpwind,
112
dg::FDSBP, cache)
113
# Assume that
114
# dg.basis isa SummationByPartsOperators.UpwindOperators
115
D_minus = dg.basis.minus # Upwind SBP D^- derivative operator
116
D_plus = dg.basis.plus # Upwind SBP D^+ derivative operator
117
@unpack f_minus_plus_threaded, f_minus_threaded, f_plus_threaded = cache
118
@unpack splitting = volume_integral
119
120
# SBP operators from SummationByPartsOperators.jl implement the basic interface
121
# of matrix-vector multiplication. Thus, we pass an "array of structures",
122
# packing all variables per node in an `SVector`.
123
if nvariables(equations) == 1
124
# `reinterpret(reshape, ...)` removes the leading dimension only if more
125
# than one variable is used.
126
u_vectors = reshape(reinterpret(SVector{nvariables(equations), eltype(u)}, u),
127
nnodes(dg), nnodes(dg), nnodes(dg), nelements(dg, cache))
128
du_vectors = reshape(reinterpret(SVector{nvariables(equations), eltype(du)},
129
du),
130
nnodes(dg), nnodes(dg), nnodes(dg), nelements(dg, cache))
131
else
132
u_vectors = reinterpret(reshape, SVector{nvariables(equations), eltype(u)}, u)
133
du_vectors = reinterpret(reshape, SVector{nvariables(equations), eltype(du)},
134
du)
135
end
136
137
# Use the tensor product structure to compute the discrete derivatives of
138
# the fluxes line-by-line and add them to `du` for each element.
139
@threaded for element in eachelement(dg, cache)
140
# f_minus_plus_element wraps the storage provided by f_minus_element and
141
# f_plus_element such that we can use a single plain broadcasting below.
142
# f_minus_element and f_plus_element are updated in broadcasting calls
143
# of the form `@. f_minus_plus_element = ...`.
144
f_minus_plus_element = f_minus_plus_threaded[Threads.threadid()]
145
f_minus_element = f_minus_threaded[Threads.threadid()]
146
f_plus_element = f_plus_threaded[Threads.threadid()]
147
u_element = view(u_vectors, :, :, :, element)
148
149
# x direction
150
@. f_minus_plus_element = splitting(u_element, 1, equations)
151
for j in eachnode(dg), k in eachnode(dg)
152
mul!(view(du_vectors, :, j, k, element), D_minus,
153
view(f_plus_element, :, j, k),
154
one(eltype(du)), one(eltype(du)))
155
mul!(view(du_vectors, :, j, k, element), D_plus,
156
view(f_minus_element, :, j, k),
157
one(eltype(du)), one(eltype(du)))
158
end
159
160
# y direction
161
@. f_minus_plus_element = splitting(u_element, 2, equations)
162
for i in eachnode(dg), k in eachnode(dg)
163
mul!(view(du_vectors, i, :, k, element), D_minus,
164
view(f_plus_element, i, :, k),
165
one(eltype(du)), one(eltype(du)))
166
mul!(view(du_vectors, i, :, k, element), D_plus,
167
view(f_minus_element, i, :, k),
168
one(eltype(du)), one(eltype(du)))
169
end
170
171
# z direction
172
@. f_minus_plus_element = splitting(u_element, 3, equations)
173
for i in eachnode(dg), j in eachnode(dg)
174
mul!(view(du_vectors, i, j, :, element), D_minus,
175
view(f_plus_element, i, j, :),
176
one(eltype(du)), one(eltype(du)))
177
mul!(view(du_vectors, i, j, :, element), D_plus,
178
view(f_minus_element, i, j, :),
179
one(eltype(du)), one(eltype(du)))
180
end
181
end
182
183
return nothing
184
end
185
186
function calc_surface_integral!(backend::Nothing, du, u, mesh::TreeMesh{3},
187
equations, surface_integral::SurfaceIntegralStrongForm,
188
dg::DG, cache)
189
inv_weight_left = inv(left_boundary_weight(dg.basis))
190
inv_weight_right = inv(right_boundary_weight(dg.basis))
191
@unpack surface_flux_values = cache.elements
192
193
@threaded for element in eachelement(dg, cache)
194
for m in eachnode(dg), l in eachnode(dg)
195
# surface at -x
196
u_node = get_node_vars(u, equations, dg, 1, l, m, element)
197
f_node = flux(u_node, 1, equations)
198
f_num = get_node_vars(surface_flux_values, equations, dg, l, m, 1, element)
199
multiply_add_to_node_vars!(du, inv_weight_left, -(f_num - f_node),
200
equations, dg, 1, l, m, element)
201
202
# surface at +x
203
u_node = get_node_vars(u, equations, dg, nnodes(dg), l, m, element)
204
f_node = flux(u_node, 1, equations)
205
f_num = get_node_vars(surface_flux_values, equations, dg, l, m, 2, element)
206
multiply_add_to_node_vars!(du, inv_weight_right, +(f_num - f_node),
207
equations, dg, nnodes(dg), l, m, element)
208
209
# surface at -y
210
u_node = get_node_vars(u, equations, dg, l, 1, m, element)
211
f_node = flux(u_node, 2, equations)
212
f_num = get_node_vars(surface_flux_values, equations, dg, l, m, 3, element)
213
multiply_add_to_node_vars!(du, inv_weight_left, -(f_num - f_node),
214
equations, dg, l, 1, m, element)
215
216
# surface at +y
217
u_node = get_node_vars(u, equations, dg, l, nnodes(dg), m, element)
218
f_node = flux(u_node, 2, equations)
219
f_num = get_node_vars(surface_flux_values, equations, dg, l, m, 4, element)
220
multiply_add_to_node_vars!(du, inv_weight_right, +(f_num - f_node),
221
equations, dg, l, nnodes(dg), m, element)
222
223
# surface at -z
224
u_node = get_node_vars(u, equations, dg, l, m, 1, element)
225
f_node = flux(u_node, 3, equations)
226
f_num = get_node_vars(surface_flux_values, equations, dg, l, m, 5, element)
227
multiply_add_to_node_vars!(du, inv_weight_left, -(f_num - f_node),
228
equations, dg, l, m, 1, element)
229
230
# surface at +z
231
u_node = get_node_vars(u, equations, dg, l, m, nnodes(dg), element)
232
f_node = flux(u_node, 3, equations)
233
f_num = get_node_vars(surface_flux_values, equations, dg, l, m, 6, element)
234
multiply_add_to_node_vars!(du, inv_weight_right, +(f_num - f_node),
235
equations, dg, l, m, nnodes(dg), element)
236
end
237
end
238
239
return nothing
240
end
241
242
# Periodic FDSBP operators need to use a single element without boundaries
243
function calc_surface_integral!(backend::Nothing, du, u, mesh::TreeMesh3D,
244
equations, surface_integral::SurfaceIntegralStrongForm,
245
dg::PeriodicFDSBP, cache)
246
@assert nelements(dg, cache) == 1
247
return nothing
248
end
249
250
# Specialized interface flux computation because the upwind solver does
251
# not require a standard numerical flux (Riemann solver). The flux splitting
252
# already separates the solution information into right-traveling and
253
# left-traveling information. So we only need to compute the appropriate
254
# flux information at each side of an interface.
255
function calc_interface_flux!(backend::Nothing, surface_flux_values,
256
mesh::TreeMesh{3},
257
have_nonconservative_terms::False, equations,
258
surface_integral::SurfaceIntegralUpwind,
259
dg::FDSBP, cache)
260
@unpack splitting = surface_integral
261
@unpack u, neighbor_ids, orientations = cache.interfaces
262
263
@threaded for interface in eachinterface(dg, cache)
264
# Get neighboring elements
265
left_id = neighbor_ids[1, interface]
266
right_id = neighbor_ids[2, interface]
267
268
# Determine interface direction with respect to elements:
269
# orientation = 1: left -> 2, right -> 1
270
# orientation = 2: left -> 4, right -> 3
271
# orientation = 3: left -> 6, right -> 5
272
left_direction = 2 * orientations[interface]
273
right_direction = 2 * orientations[interface] - 1
274
275
for j in eachnode(dg), i in eachnode(dg)
276
# Pull the left and right solution data
277
u_ll, u_rr = get_surface_node_vars(u, equations, dg, i, j, interface)
278
279
# Compute the upwind coupling terms where right-traveling
280
# information comes from the left and left-traveling information
281
# comes from the right
282
flux_minus_rr = splitting(u_rr, Val{:minus}(), orientations[interface],
283
equations)
284
flux_plus_ll = splitting(u_ll, Val{:plus}(), orientations[interface],
285
equations)
286
287
# Save the upwind coupling into the appropriate side of the elements
288
for v in eachvariable(equations)
289
surface_flux_values[v, i, j, left_direction, left_id] = flux_minus_rr[v]
290
surface_flux_values[v, i, j, right_direction, right_id] = flux_plus_ll[v]
291
end
292
end
293
end
294
295
return nothing
296
end
297
298
# Implementation of fully upwind SATs. The surface flux values are pre-computed
299
# in the specialized `calc_interface_flux` routine. These SATs are still of
300
# a strong form penalty type, except that the interior flux at a particular
301
# side of the element are computed in the upwind direction.
302
function calc_surface_integral!(backend::Nothing, du, u, mesh::TreeMesh{3},
303
equations, surface_integral::SurfaceIntegralUpwind,
304
dg::FDSBP, cache)
305
inv_weight_left = inv(left_boundary_weight(dg.basis))
306
inv_weight_right = inv(right_boundary_weight(dg.basis))
307
@unpack surface_flux_values = cache.elements
308
@unpack splitting = surface_integral
309
310
@threaded for element in eachelement(dg, cache)
311
for m in eachnode(dg), l in eachnode(dg)
312
# surface at -x
313
u_node = get_node_vars(u, equations, dg, 1, l, m, element)
314
f_node = splitting(u_node, Val{:plus}(), 1, equations)
315
f_num = get_node_vars(surface_flux_values, equations, dg, l, m, 1, element)
316
multiply_add_to_node_vars!(du, inv_weight_left, -(f_num - f_node),
317
equations, dg, 1, l, m, element)
318
319
# surface at +x
320
u_node = get_node_vars(u, equations, dg, nnodes(dg), l, m, element)
321
f_node = splitting(u_node, Val{:minus}(), 1, equations)
322
f_num = get_node_vars(surface_flux_values, equations, dg, l, m, 2, element)
323
multiply_add_to_node_vars!(du, inv_weight_right, +(f_num - f_node),
324
equations, dg, nnodes(dg), l, m, element)
325
326
# surface at -y
327
u_node = get_node_vars(u, equations, dg, l, 1, m, element)
328
f_node = splitting(u_node, Val{:plus}(), 2, equations)
329
f_num = get_node_vars(surface_flux_values, equations, dg, l, m, 3, element)
330
multiply_add_to_node_vars!(du, inv_weight_left, -(f_num - f_node),
331
equations, dg, l, 1, m, element)
332
333
# surface at +y
334
u_node = get_node_vars(u, equations, dg, l, nnodes(dg), m, element)
335
f_node = splitting(u_node, Val{:minus}(), 2, equations)
336
f_num = get_node_vars(surface_flux_values, equations, dg, l, m, 4, element)
337
multiply_add_to_node_vars!(du, inv_weight_right, +(f_num - f_node),
338
equations, dg, l, nnodes(dg), m, element)
339
340
# surface at -z
341
u_node = get_node_vars(u, equations, dg, l, m, 1, element)
342
f_node = splitting(u_node, Val{:plus}(), 3, equations)
343
f_num = get_node_vars(surface_flux_values, equations, dg, l, m, 5, element)
344
multiply_add_to_node_vars!(du, inv_weight_left, -(f_num - f_node),
345
equations, dg, l, m, 1, element)
346
347
# surface at +z
348
u_node = get_node_vars(u, equations, dg, l, m, nnodes(dg), element)
349
f_node = splitting(u_node, Val{:minus}(), 3, equations)
350
f_num = get_node_vars(surface_flux_values, equations, dg, l, m, 6, element)
351
multiply_add_to_node_vars!(du, inv_weight_right, +(f_num - f_node),
352
equations, dg, l, m, nnodes(dg), element)
353
end
354
end
355
356
return nothing
357
end
358
359
# Periodic FDSBP operators need to use a single element without boundaries
360
function calc_surface_integral!(backend::Nothing, du, u, mesh::TreeMesh3D,
361
equations, surface_integral::SurfaceIntegralUpwind,
362
dg::PeriodicFDSBP, cache)
363
@assert nelements(dg, cache) == 1
364
return nothing
365
end
366
367
# AnalysisCallback
368
369
function integrate_via_indices(func::Func, u,
370
mesh::TreeMesh{3}, equations,
371
dg::FDSBP, cache, args...; normalize = true) where {Func}
372
# TODO: FD. This is rather inefficient right now and allocates...
373
M = SummationByPartsOperators.mass_matrix(dg.basis)
374
if M isa UniformScaling
375
M = M(nnodes(dg))
376
end
377
weights = diag(M)
378
379
# Initialize integral with zeros of the right shape
380
integral = zero(func(u, 1, 1, 1, 1, equations, dg, args...))
381
382
# Use quadrature to numerically integrate over entire domain
383
@batch reduction=(+, integral) for element in eachelement(dg, cache)
384
volume_jacobian_ = volume_jacobian(element, mesh, cache)
385
for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg)
386
integral += volume_jacobian_ * weights[i] * weights[j] * weights[k] *
387
func(u, i, j, k, element, equations, dg, args...)
388
end
389
end
390
391
# Normalize with total volume
392
if normalize
393
integral = integral / total_volume(mesh)
394
end
395
396
return integral
397
end
398
399
function calc_error_norms(func, u, t, analyzer,
400
mesh::TreeMesh{3}, equations, initial_condition,
401
dg::FDSBP, cache, cache_analysis)
402
# TODO: FD. This is rather inefficient right now and allocates...
403
M = SummationByPartsOperators.mass_matrix(dg.basis)
404
if M isa UniformScaling
405
M = M(nnodes(dg))
406
end
407
weights = diag(M)
408
@unpack node_coordinates = cache.elements
409
410
# Set up data structures
411
l2_error = zero(func(get_node_vars(u, equations, dg, 1, 1, 1, 1), equations))
412
linf_error = copy(l2_error)
413
414
# Iterate over all elements for error calculations
415
for element in eachelement(dg, cache)
416
# Calculate errors at each node
417
volume_jacobian_ = volume_jacobian(element, mesh, cache)
418
419
for k in eachnode(analyzer), j in eachnode(analyzer), i in eachnode(analyzer)
420
u_exact = initial_condition(get_node_coords(node_coordinates, equations, dg,
421
i, j, k, element), t, equations)
422
diff = func(u_exact, equations) -
423
func(get_node_vars(u, equations, dg, i, j, k, element), equations)
424
l2_error += diff .^ 2 *
425
(weights[i] * weights[j] * weights[k] * volume_jacobian_)
426
linf_error = @. max(linf_error, abs(diff))
427
end
428
end
429
430
# For L2 error, divide by total volume
431
total_volume_ = total_volume(mesh)
432
l2_error = @. sqrt(l2_error / total_volume_)
433
434
return l2_error, linf_error
435
end
436
end # @muladd
437
438