Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
trixi-framework
GitHub Repository: trixi-framework/Trixi.jl
Path: blob/main/src/solvers/dgsem_unstructured/dg_2d.jl
5590 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
# This method is called when a SemidiscretizationHyperbolic is constructed.
9
# It constructs the basic `cache` used throughout the simulation to compute
10
# the RHS etc.
11
function create_cache(mesh::UnstructuredMesh2D, equations,
12
dg::DG, RealT, uEltype)
13
elements = init_elements(mesh, equations, dg.basis, RealT, uEltype)
14
15
interfaces = init_interfaces(mesh, elements)
16
17
boundaries = init_boundaries(mesh, elements)
18
19
# Container cache
20
cache = (; elements, interfaces, boundaries)
21
22
# perform a check on the sufficient metric identities condition for free-stream preservation
23
# and halt computation if it fails
24
# For `Float64`, this gives 1.8189894035458565e-12
25
# For `Float32`, this gives 1.1920929f-5
26
atol = max(100 * eps(RealT), eps(RealT)^convert(RealT, 0.75f0))
27
if !isapprox(max_discrete_metric_identities(dg, cache), 0, atol = atol)
28
error("metric terms fail free-stream preservation check with maximum error $(max_discrete_metric_identities(dg, cache))")
29
end
30
31
# Add Volume-Integral cache
32
cache = (; cache...,
33
create_cache(mesh, equations, dg.volume_integral, dg, cache, uEltype)...)
34
35
return cache
36
end
37
38
# prolong the solution into the convenience array in the interior interface container
39
# Note! this routine is for quadrilateral elements with "right-handed" orientation
40
function prolong2interfaces!(cache, u, mesh::UnstructuredMesh2D, equations, dg::DG)
41
@unpack interfaces = cache
42
@unpack element_ids, element_side_ids = interfaces
43
interfaces_u = interfaces.u
44
45
@threaded for interface in eachinterface(dg, cache)
46
primary_element = element_ids[1, interface]
47
secondary_element = element_ids[2, interface]
48
49
primary_side = element_side_ids[1, interface]
50
secondary_side = element_side_ids[2, interface]
51
52
if primary_side == 1
53
for i in eachnode(dg), v in eachvariable(equations)
54
interfaces_u[1, v, i, interface] = u[v, i, 1,
55
primary_element]
56
end
57
elseif primary_side == 2
58
for i in eachnode(dg), v in eachvariable(equations)
59
interfaces_u[1, v, i, interface] = u[v, nnodes(dg), i,
60
primary_element]
61
end
62
elseif primary_side == 3
63
for i in eachnode(dg), v in eachvariable(equations)
64
interfaces_u[1, v, i, interface] = u[v, i, nnodes(dg),
65
primary_element]
66
end
67
else # primary_side == 4
68
for i in eachnode(dg), v in eachvariable(equations)
69
interfaces_u[1, v, i, interface] = u[v, 1, i,
70
primary_element]
71
end
72
end
73
74
if secondary_side == 1
75
for i in eachnode(dg), v in eachvariable(equations)
76
interfaces_u[2, v, i, interface] = u[v, i, 1,
77
secondary_element]
78
end
79
elseif secondary_side == 2
80
for i in eachnode(dg), v in eachvariable(equations)
81
interfaces_u[2, v, i, interface] = u[v, nnodes(dg), i,
82
secondary_element]
83
end
84
elseif secondary_side == 3
85
for i in eachnode(dg), v in eachvariable(equations)
86
interfaces_u[2, v, i, interface] = u[v, i, nnodes(dg),
87
secondary_element]
88
end
89
else # secondary_side == 4
90
for i in eachnode(dg), v in eachvariable(equations)
91
interfaces_u[2, v, i, interface] = u[v, 1, i,
92
secondary_element]
93
end
94
end
95
end
96
97
return nothing
98
end
99
100
# compute the numerical flux interface coupling between two elements on an unstructured
101
# quadrilateral mesh
102
function calc_interface_flux!(surface_flux_values,
103
mesh::UnstructuredMesh2D,
104
have_nonconservative_terms::False, equations,
105
surface_integral, dg::DG, cache)
106
@unpack surface_flux = surface_integral
107
@unpack u, start_index, index_increment, element_ids, element_side_ids = cache.interfaces
108
@unpack normal_directions = cache.elements
109
110
@threaded for interface in eachinterface(dg, cache)
111
# Get neighboring elements
112
primary_element = element_ids[1, interface]
113
secondary_element = element_ids[2, interface]
114
115
# Get the local side id on which to compute the flux
116
primary_side = element_side_ids[1, interface]
117
secondary_side = element_side_ids[2, interface]
118
119
# initial index for the coordinate system on the secondary element
120
secondary_index = start_index[interface]
121
122
# loop through the primary element coordinate system and compute the interface coupling
123
for primary_index in eachnode(dg)
124
# pull the primary and secondary states from the boundary u values
125
u_ll = get_one_sided_surface_node_vars(u, equations, dg, 1, primary_index,
126
interface)
127
u_rr = get_one_sided_surface_node_vars(u, equations, dg, 2, secondary_index,
128
interface)
129
130
# pull the outward pointing (normal) directional vector
131
# Note! this assumes a conforming approximation, more must be done in terms of the normals
132
# for hanging nodes and other non-conforming approximation spaces
133
outward_direction = get_surface_normal(normal_directions, primary_index,
134
primary_side, primary_element)
135
136
# Call pointwise numerical flux with rotation. Direction is normalized inside this function
137
flux = surface_flux(u_ll, u_rr, outward_direction, equations)
138
139
# Copy flux back to primary/secondary element storage
140
# Note the sign change for the normal flux in the secondary element!
141
for v in eachvariable(equations)
142
surface_flux_values[v, primary_index, primary_side, primary_element] = flux[v]
143
surface_flux_values[v, secondary_index, secondary_side, secondary_element] = -flux[v]
144
end
145
146
# increment the index of the coordinate system in the secondary element
147
secondary_index += index_increment[interface]
148
end
149
end
150
151
return nothing
152
end
153
154
# compute the numerical flux interface with nonconservative terms coupling between two elements
155
# on an unstructured quadrilateral mesh
156
function calc_interface_flux!(surface_flux_values,
157
mesh::UnstructuredMesh2D,
158
have_nonconservative_terms::True, equations,
159
surface_integral, dg::DG, cache)
160
surface_flux, nonconservative_flux = surface_integral.surface_flux
161
@unpack u, start_index, index_increment, element_ids, element_side_ids = cache.interfaces
162
@unpack normal_directions = cache.elements
163
164
@threaded for interface in eachinterface(dg, cache)
165
# Get the primary element index and local side index
166
primary_element = element_ids[1, interface]
167
primary_side = element_side_ids[1, interface]
168
169
# Get neighboring element, local side index, and index increment on the
170
# secondary element
171
secondary_element = element_ids[2, interface]
172
secondary_side = element_side_ids[2, interface]
173
secondary_index_increment = index_increment[interface]
174
175
secondary_index = start_index[interface]
176
for primary_index in eachnode(dg)
177
# pull the primary and secondary states from the boundary u values
178
u_ll = get_one_sided_surface_node_vars(u, equations, dg, 1, primary_index,
179
interface)
180
u_rr = get_one_sided_surface_node_vars(u, equations, dg, 2, secondary_index,
181
interface)
182
183
# pull the outward pointing (normal) directional vector
184
# Note! This assumes a conforming approximation, more must be done in terms
185
# of the normals for hanging nodes and other non-conforming approximation spaces
186
outward_direction = get_surface_normal(normal_directions, primary_index,
187
primary_side, primary_element)
188
189
# Calculate the conservative portion of the numerical flux
190
# Call pointwise numerical flux with rotation. Direction is normalized
191
# inside this function
192
flux = surface_flux(u_ll, u_rr, outward_direction, equations)
193
194
# Compute both nonconservative fluxes
195
noncons_primary = nonconservative_flux(u_ll, u_rr, outward_direction,
196
equations)
197
noncons_secondary = nonconservative_flux(u_rr, u_ll, outward_direction,
198
equations)
199
200
# Copy flux to primary and secondary element storage
201
# Note the sign change for the components in the secondary element!
202
for v in eachvariable(equations)
203
# Note the factor 0.5 necessary for the nonconservative fluxes based on
204
# the interpretation of global SBP operators coupled discontinuously via
205
# central fluxes/SATs
206
surface_flux_values[v, primary_index, primary_side, primary_element] = (flux[v] +
207
0.5f0 *
208
noncons_primary[v])
209
surface_flux_values[v, secondary_index, secondary_side, secondary_element] = -(flux[v] +
210
0.5f0 *
211
noncons_secondary[v])
212
end
213
214
# increment the index of the coordinate system in the secondary element
215
secondary_index += secondary_index_increment
216
end
217
end
218
219
return nothing
220
end
221
222
# move the approximate solution onto physical boundaries within a "right-handed" element
223
function prolong2boundaries!(cache, u,
224
mesh::UnstructuredMesh2D,
225
equations, dg::DG)
226
@unpack boundaries = cache
227
@unpack element_id, element_side_id = boundaries
228
boundaries_u = boundaries.u
229
230
@threaded for boundary in eachboundary(dg, cache)
231
element = element_id[boundary]
232
side = element_side_id[boundary]
233
234
if side == 1
235
for l in eachnode(dg), v in eachvariable(equations)
236
boundaries_u[v, l, boundary] = u[v, l, 1, element]
237
end
238
elseif side == 2
239
for l in eachnode(dg), v in eachvariable(equations)
240
boundaries_u[v, l, boundary] = u[v, nnodes(dg), l, element]
241
end
242
elseif side == 3
243
for l in eachnode(dg), v in eachvariable(equations)
244
boundaries_u[v, l, boundary] = u[v, l, nnodes(dg), element]
245
end
246
else # side == 4
247
for l in eachnode(dg), v in eachvariable(equations)
248
boundaries_u[v, l, boundary] = u[v, 1, l, element]
249
end
250
end
251
end
252
253
return nothing
254
end
255
256
function calc_boundary_flux!(cache, t, boundary_condition::BoundaryConditionPeriodic,
257
mesh::Union{UnstructuredMesh2D, P4estMesh, T8codeMesh},
258
equations, surface_integral, dg::DG)
259
@assert isempty(eachboundary(dg, cache))
260
261
return nothing
262
end
263
264
# Function barrier for type stability
265
function calc_boundary_flux!(cache, t, boundary_conditions,
266
mesh::Union{UnstructuredMesh2D, P4estMesh, T8codeMesh},
267
equations, surface_integral, dg::DG)
268
@unpack boundary_condition_types, boundary_indices = boundary_conditions
269
270
calc_boundary_flux_by_type!(cache, t, boundary_condition_types, boundary_indices,
271
mesh, equations, surface_integral, dg)
272
return nothing
273
end
274
275
# Iterate over tuples of boundary condition types and associated indices
276
# in a type-stable way using "lispy tuple programming".
277
function calc_boundary_flux_by_type!(cache, t, BCs::NTuple{N, Any},
278
BC_indices::NTuple{N, Vector{Int}},
279
mesh::Union{UnstructuredMesh2D, P4estMesh,
280
T8codeMesh},
281
equations, surface_integral, dg::DG) where {N}
282
# Extract the boundary condition type and index vector
283
boundary_condition = first(BCs)
284
boundary_condition_indices = first(BC_indices)
285
# Extract the remaining types and indices to be processed later
286
remaining_boundary_conditions = Base.tail(BCs)
287
remaining_boundary_condition_indices = Base.tail(BC_indices)
288
289
# process the first boundary condition type
290
calc_boundary_flux!(cache, t, boundary_condition, boundary_condition_indices,
291
mesh, equations, surface_integral, dg)
292
293
# recursively call this method with the unprocessed boundary types
294
calc_boundary_flux_by_type!(cache, t, remaining_boundary_conditions,
295
remaining_boundary_condition_indices,
296
mesh, equations, surface_integral, dg)
297
298
return nothing
299
end
300
301
# terminate the type-stable iteration over tuples
302
function calc_boundary_flux_by_type!(cache, t, BCs::Tuple{}, BC_indices::Tuple{},
303
mesh::Union{UnstructuredMesh2D, P4estMesh,
304
T8codeMesh},
305
equations, surface_integral, dg::DG)
306
return nothing
307
end
308
309
function calc_boundary_flux!(cache, t, boundary_condition::BC, boundary_indexing,
310
mesh::UnstructuredMesh2D, equations,
311
surface_integral, dg::DG) where {BC}
312
@unpack surface_flux_values = cache.elements
313
@unpack element_id, element_side_id = cache.boundaries
314
315
@threaded for local_index in eachindex(boundary_indexing)
316
# use the local index to get the global boundary index from the pre-sorted list
317
boundary = boundary_indexing[local_index]
318
319
# get the element and side IDs on the boundary element
320
element = element_id[boundary]
321
side = element_side_id[boundary]
322
323
# calc boundary flux on the current boundary interface
324
for node in eachnode(dg)
325
calc_boundary_flux!(surface_flux_values, t, boundary_condition,
326
mesh, have_nonconservative_terms(equations),
327
equations, surface_integral, dg, cache,
328
node, side, element, boundary)
329
end
330
end
331
332
return nothing
333
end
334
335
# inlined version of the boundary flux calculation along a physical interface where the
336
# boundary flux values are set according to a particular `boundary_condition` function
337
@inline function calc_boundary_flux!(surface_flux_values, t, boundary_condition,
338
mesh::UnstructuredMesh2D,
339
have_nonconservative_terms::False, equations,
340
surface_integral, dg::DG, cache,
341
node_index, side_index, element_index,
342
boundary_index)
343
@unpack normal_directions = cache.elements
344
@unpack u, node_coordinates = cache.boundaries
345
@unpack surface_flux = surface_integral
346
347
# pull the inner solution state from the boundary u values on the boundary element
348
u_inner = get_node_vars(u, equations, dg, node_index, boundary_index)
349
350
# pull the outward pointing (normal) directional vector
351
outward_direction = get_surface_normal(normal_directions, node_index, side_index,
352
element_index)
353
354
# get the external solution values from the prescribed external state
355
x = get_node_coords(node_coordinates, equations, dg, node_index, boundary_index)
356
357
# Call pointwise numerical flux function in the normal direction on the boundary
358
flux = boundary_condition(u_inner, outward_direction, x, t, surface_flux, equations)
359
360
for v in eachvariable(equations)
361
surface_flux_values[v, node_index, side_index, element_index] = flux[v]
362
end
363
364
return nothing
365
end
366
367
# inlined version of the boundary flux and nonconseravtive terms calculation along a
368
# physical interface. The conservative portion of the boundary flux values
369
# are set according to a particular `boundary_condition` function
370
# Note, it is necessary to set and add in the nonconservative values because
371
# the upper left/lower right diagonal terms have been peeled off due to the use of
372
# `derivative_split` from `dg.basis` in [`flux_differencing_kernel!`](@ref)
373
@inline function calc_boundary_flux!(surface_flux_values, t, boundary_condition,
374
mesh::UnstructuredMesh2D,
375
have_nonconservative_terms::True, equations,
376
surface_integral, dg::DG, cache,
377
node_index, side_index, element_index,
378
boundary_index)
379
@unpack normal_directions = cache.elements
380
@unpack u, node_coordinates = cache.boundaries
381
382
# pull the inner solution state from the boundary u values on the boundary element
383
u_inner = get_node_vars(u, equations, dg, node_index, boundary_index)
384
385
# pull the outward pointing (normal) directional vector
386
outward_direction = get_surface_normal(normal_directions, node_index, side_index,
387
element_index)
388
389
# get the external solution values from the prescribed external state
390
x = get_node_coords(node_coordinates, equations, dg, node_index, boundary_index)
391
392
# Call pointwise numerical flux functions for the conservative and nonconservative part
393
# in the normal direction on the boundary
394
flux, noncons_flux = boundary_condition(u_inner, outward_direction, x, t,
395
surface_integral.surface_flux, equations)
396
397
for v in eachvariable(equations)
398
# Note the factor 0.5 necessary for the nonconservative fluxes based on
399
# the interpretation of global SBP operators coupled discontinuously via
400
# central fluxes/SATs
401
surface_flux_values[v, node_index, side_index, element_index] = flux[v] +
402
0.5f0 *
403
noncons_flux[v]
404
end
405
406
return nothing
407
end
408
409
# Note! The local side numbering for the unstructured quadrilateral element implementation differs
410
# from the structured TreeMesh or StructuredMesh local side numbering:
411
#
412
# TreeMesh/StructuredMesh sides versus UnstructuredMesh sides
413
# 4 3
414
# ----------------- -----------------
415
# | | | |
416
# | ^ eta | | ^ eta |
417
# 1 | | | 2 4 | | | 2
418
# | | | | | |
419
# | ---> xi | | ---> xi |
420
# ----------------- -----------------
421
# 3 1
422
# Therefore, we require a different surface integral routine here despite their similar structure.
423
function calc_surface_integral!(backend, du, u, mesh::UnstructuredMesh2D,
424
equations, surface_integral, dg::DGSEM, cache)
425
@unpack inverse_weights = dg.basis
426
@unpack surface_flux_values = cache.elements
427
428
# Note that all fluxes have been computed with outward-pointing normal vectors.
429
# This computes the **negative** surface integral contribution,
430
# i.e., M^{-1} * boundary_interpolation^T
431
# and the missing "-" is taken care of by `apply_jacobian!`.
432
#
433
# We also use explicit assignments instead of `+=` and `-=` to let `@muladd`
434
# turn these into FMAs (see comment at the top of the file).
435
factor = inverse_weights[1] # For LGL basis: Identical to weighted boundary interpolation at x = ±1
436
@threaded for element in eachelement(dg, cache)
437
for l in eachnode(dg), v in eachvariable(equations)
438
# surface contribution along local sides 2 and 4 (fixed x and y varies)
439
du[v, 1, l, element] = du[v, 1, l, element] +
440
surface_flux_values[v, l, 4, element] *
441
factor
442
du[v, nnodes(dg), l, element] = du[v, nnodes(dg), l, element] +
443
surface_flux_values[v, l, 2, element] *
444
factor
445
446
# surface contribution along local sides 1 and 3 (fixed y and x varies)
447
du[v, l, 1, element] = du[v, l, 1, element] +
448
surface_flux_values[v, l, 1, element] *
449
factor
450
du[v, l, nnodes(dg), element] = du[v, l, nnodes(dg), element] +
451
surface_flux_values[v, l, 3, element] *
452
factor
453
end
454
end
455
456
return nothing
457
end
458
459
# This routine computes the maximum value of the discrete metric identities necessary to ensure
460
# that the approximation will be free-stream preserving (i.e. a constant solution remains constant)
461
# on a curvilinear mesh.
462
# Note! Independent of the equation system and is only a check on the discrete mapping terms.
463
# Can be used for a metric identities check on StructuredMesh{2} or UnstructuredMesh2D
464
function max_discrete_metric_identities(dg::DGSEM, cache)
465
@unpack derivative_matrix = dg.basis
466
@unpack contravariant_vectors = cache.elements
467
468
ndims_ = size(contravariant_vectors, 1)
469
470
metric_id_dx = zeros(eltype(contravariant_vectors), nnodes(dg), nnodes(dg))
471
metric_id_dy = zeros(eltype(contravariant_vectors), nnodes(dg), nnodes(dg))
472
473
max_metric_ids = zero(eltype(contravariant_vectors))
474
475
for i in 1:ndims_, element in eachelement(dg, cache)
476
# compute D*Ja_1^i + Ja_2^i*D^T
477
@views mul!(metric_id_dx, derivative_matrix,
478
contravariant_vectors[i, 1, :, :, element])
479
@views mul!(metric_id_dy, contravariant_vectors[i, 2, :, :, element],
480
derivative_matrix')
481
local_max_metric_ids = maximum(abs.(metric_id_dx + metric_id_dy))
482
483
max_metric_ids = max(max_metric_ids, local_max_metric_ids)
484
end
485
486
return max_metric_ids
487
end
488
end # @muladd
489
490