Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
trixi-framework
GitHub Repository: trixi-framework/Trixi.jl
Path: blob/main/src/solvers/dgsem_tree/containers_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
# Container data structure (structure-of-arrays style) for DG elements
9
mutable struct TreeElementContainer1D{RealT <: Real, uEltype <: Real} <:
10
AbstractTreeElementContainer
11
inverse_jacobian::Vector{RealT} # [elements]
12
node_coordinates::Array{RealT, 3} # [orientation, i, elements]
13
surface_flux_values::Array{uEltype, 3} # [variables, direction, elements]
14
cell_ids::Vector{Int} # [elements]
15
# internal `resize!`able storage
16
_node_coordinates::Vector{RealT}
17
_surface_flux_values::Vector{uEltype}
18
end
19
20
# Only one-dimensional `Array`s are `resize!`able in Julia.
21
# Hence, we use `Vector`s as internal storage and `resize!`
22
# them whenever needed. Then, we reuse the same memory by
23
# `unsafe_wrap`ping multi-dimensional `Array`s around the
24
# internal storage.
25
function Base.resize!(elements::TreeElementContainer1D, capacity)
26
n_nodes = nnodes(elements)
27
n_variables = nvariables(elements)
28
@unpack _node_coordinates, _surface_flux_values,
29
inverse_jacobian, cell_ids = elements
30
31
resize!(inverse_jacobian, capacity)
32
33
resize!(_node_coordinates, 1 * n_nodes * capacity)
34
elements.node_coordinates = unsafe_wrap(Array, pointer(_node_coordinates),
35
(1, n_nodes, capacity))
36
37
resize!(_surface_flux_values, n_variables * 2 * 1 * capacity)
38
elements.surface_flux_values = unsafe_wrap(Array, pointer(_surface_flux_values),
39
(n_variables, 2 * 1, capacity))
40
41
resize!(cell_ids, capacity)
42
43
return nothing
44
end
45
46
function TreeElementContainer1D{RealT, uEltype}(capacity::Integer, n_variables,
47
n_nodes) where {RealT <: Real,
48
uEltype <: Real}
49
nan_RealT = convert(RealT, NaN)
50
nan_uEltype = convert(uEltype, NaN)
51
52
# Initialize fields with defaults
53
inverse_jacobian = fill(nan_RealT, capacity)
54
55
_node_coordinates = fill(nan_RealT, 1 * n_nodes * capacity)
56
node_coordinates = unsafe_wrap(Array, pointer(_node_coordinates),
57
(1, n_nodes, capacity))
58
59
_surface_flux_values = fill(nan_uEltype, n_variables * 2 * 1 * capacity)
60
surface_flux_values = unsafe_wrap(Array, pointer(_surface_flux_values),
61
(n_variables, 2 * 1, capacity))
62
63
cell_ids = fill(typemin(Int), capacity)
64
65
return TreeElementContainer1D{RealT, uEltype}(inverse_jacobian, node_coordinates,
66
surface_flux_values, cell_ids,
67
_node_coordinates,
68
_surface_flux_values)
69
end
70
71
# Create element container and initialize element data
72
function init_elements(cell_ids, mesh::TreeMesh1D,
73
equations::AbstractEquations{1},
74
basis, ::Type{RealT},
75
::Type{uEltype}) where {RealT <: Real, uEltype <: Real}
76
# Initialize container
77
n_elements = length(cell_ids)
78
elements = TreeElementContainer1D{RealT, uEltype}(n_elements, nvariables(equations),
79
nnodes(basis))
80
81
init_elements!(elements, cell_ids, mesh, basis)
82
return elements
83
end
84
85
function init_elements!(elements, cell_ids, mesh::TreeMesh1D, basis)
86
nodes = get_nodes(basis)
87
# Compute the length of the 1D reference interval by integrating
88
# the function with constant value unity on the corresponding
89
# element data type (using \circ)
90
reference_length = integrate(one eltype, nodes, basis)
91
# Compute the offset of the midpoint of the 1D reference interval
92
# (its difference from zero)
93
reference_offset = (first(nodes) + last(nodes)) / 2
94
95
# Store cell ids
96
elements.cell_ids .= cell_ids
97
98
# Calculate inverse Jacobian and node coordinates
99
for element in eachelement(elements)
100
# Get cell id
101
cell_id = cell_ids[element]
102
103
# Get cell length
104
dx = length_at_cell(mesh.tree, cell_id)
105
106
# Calculate inverse Jacobian
107
jacobian = dx / reference_length
108
elements.inverse_jacobian[element] = inv(jacobian)
109
110
# Calculate node coordinates
111
# Note that the `tree_coordinates` are the midpoints of the cells.
112
# Hence, we need to add an offset for `nodes` with a midpoint
113
# different from zero.
114
for i in eachnode(basis)
115
elements.node_coordinates[1, i, element] = (mesh.tree.coordinates[1,
116
cell_id] +
117
jacobian *
118
(nodes[i] - reference_offset))
119
end
120
end
121
122
return elements
123
end
124
125
# Container data structure (structure-of-arrays style) for DG interfaces
126
mutable struct TreeInterfaceContainer1D{uEltype <: Real} <:
127
AbstractTreeInterfaceContainer
128
u::Array{uEltype, 3} # [leftright, variables, interfaces]
129
neighbor_ids::Matrix{Int} # [leftright, interfaces]
130
orientations::Vector{Int} # [interfaces]
131
# internal `resize!`able storage
132
_u::Vector{uEltype}
133
_neighbor_ids::Vector{Int}
134
end
135
136
# 1D: Only one node per interface
137
nnodes(interfaces::TreeInterfaceContainer1D) = 1
138
139
# See explanation of Base.resize! for the element container
140
function Base.resize!(interfaces::TreeInterfaceContainer1D, capacity)
141
n_variables = nvariables(interfaces)
142
@unpack _u, _neighbor_ids, orientations = interfaces
143
144
resize!(_u, 2 * n_variables * capacity)
145
interfaces.u = unsafe_wrap(Array, pointer(_u),
146
(2, n_variables, capacity))
147
148
resize!(_neighbor_ids, 2 * capacity)
149
interfaces.neighbor_ids = unsafe_wrap(Array, pointer(_neighbor_ids),
150
(2, capacity))
151
152
resize!(orientations, capacity)
153
154
return nothing
155
end
156
157
function TreeInterfaceContainer1D{uEltype}(capacity::Integer,
158
n_variables) where {uEltype <: Real}
159
nan = convert(uEltype, NaN)
160
161
# Initialize fields with defaults
162
_u = fill(nan, 2 * n_variables * capacity)
163
u = unsafe_wrap(Array, pointer(_u),
164
(2, n_variables, capacity))
165
166
_neighbor_ids = fill(typemin(Int), 2 * capacity)
167
neighbor_ids = unsafe_wrap(Array, pointer(_neighbor_ids),
168
(2, capacity))
169
170
orientations = fill(typemin(Int), capacity)
171
172
return TreeInterfaceContainer1D{uEltype}(u, neighbor_ids, orientations,
173
_u, _neighbor_ids)
174
end
175
176
# Create interface container and initialize interface data in `elements`.
177
function init_interfaces(cell_ids, mesh::TreeMesh1D,
178
elements::TreeElementContainer1D)
179
# Initialize container
180
n_interfaces = count_required_interfaces(mesh, cell_ids)
181
interfaces = TreeInterfaceContainer1D{eltype(elements)}(n_interfaces,
182
nvariables(elements))
183
184
# Connect elements with interfaces
185
init_interfaces!(interfaces, elements, mesh)
186
return interfaces
187
end
188
189
# Count the number of interfaces that need to be created
190
function count_required_interfaces(mesh::TreeMesh1D, cell_ids)
191
count = 0
192
193
# Iterate over all cells
194
for cell_id in cell_ids
195
for direction in eachdirection(mesh.tree)
196
# Only count interfaces in positive direction to avoid double counting
197
if direction == 1
198
continue
199
end
200
201
# Skip if no neighbor exists
202
if !has_any_neighbor(mesh.tree, cell_id, direction)
203
continue
204
end
205
206
count += 1
207
end
208
end
209
210
return count
211
end
212
213
# Initialize connectivity between elements and interfaces
214
function init_interfaces!(interfaces, elements, mesh::TreeMesh1D)
215
# Construct cell -> element mapping for easier algorithm implementation
216
tree = mesh.tree
217
c2e = zeros(Int, length(tree))
218
for element in eachelement(elements)
219
c2e[elements.cell_ids[element]] = element
220
end
221
222
# Reset interface count
223
count = 0
224
225
# Iterate over all elements to find neighbors and to connect via interfaces
226
for element in eachelement(elements)
227
# Get cell id
228
cell_id = elements.cell_ids[element]
229
230
# Loop over directions
231
for direction in eachdirection(mesh.tree)
232
# Only create interfaces in positive direction
233
if direction == 1
234
continue
235
end
236
237
# Skip if no neighbor exists and current cell is not small
238
if !has_any_neighbor(mesh.tree, cell_id, direction)
239
continue
240
end
241
242
count += 1
243
244
if has_neighbor(mesh.tree, cell_id, direction)
245
neighbor_cell_id = mesh.tree.neighbor_ids[direction, cell_id]
246
if has_children(mesh.tree, neighbor_cell_id) # Cell has small neighbor
247
interfaces.neighbor_ids[2, count] = c2e[mesh.tree.child_ids[1,
248
neighbor_cell_id]]
249
else # Cell has same refinement level neighbor
250
interfaces.neighbor_ids[2, count] = c2e[neighbor_cell_id]
251
end
252
else # Cell is small and has large neighbor
253
parent_id = mesh.tree.parent_ids[cell_id]
254
neighbor_cell_id = mesh.tree.neighbor_ids[direction, parent_id]
255
interfaces.neighbor_ids[2, count] = c2e[neighbor_cell_id]
256
end
257
258
interfaces.neighbor_ids[1, count] = element
259
# Set orientation (x -> 1)
260
interfaces.orientations[count] = 1
261
end
262
end
263
264
@assert count==ninterfaces(interfaces) ("Actual interface count ($count) does not match "*
265
"expectations $(ninterfaces(interfaces))")
266
end
267
268
# Container data structure (structure-of-arrays style) for DG boundaries
269
mutable struct TreeBoundaryContainer1D{RealT <: Real, uEltype <: Real} <:
270
AbstractTreeBoundaryContainer
271
u::Array{uEltype, 3} # [leftright, variables, boundaries]
272
neighbor_ids::Vector{Int} # [boundaries]
273
orientations::Vector{Int} # [boundaries]
274
neighbor_sides::Vector{Int} # [boundaries]
275
node_coordinates::Array{RealT, 2} # [orientation, elements]
276
n_boundaries_per_direction::SVector{2, Int} # [direction]
277
# internal `resize!`able storage
278
_u::Vector{uEltype}
279
_node_coordinates::Vector{RealT}
280
end
281
282
# 1D: Only one boundary node
283
nnodes(boundaries::TreeBoundaryContainer1D) = 1
284
285
# See explanation of Base.resize! for the element container
286
function Base.resize!(boundaries::TreeBoundaryContainer1D, capacity)
287
n_variables = nvariables(boundaries)
288
@unpack _u, _node_coordinates,
289
neighbor_ids, orientations, neighbor_sides = boundaries
290
291
resize!(_u, 2 * n_variables * capacity)
292
boundaries.u = unsafe_wrap(Array, pointer(_u),
293
(2, n_variables, capacity))
294
295
resize!(_node_coordinates, 1 * capacity)
296
boundaries.node_coordinates = unsafe_wrap(Array, pointer(_node_coordinates),
297
(1, capacity))
298
299
resize!(neighbor_ids, capacity)
300
301
resize!(orientations, capacity)
302
303
resize!(neighbor_sides, capacity)
304
305
return nothing
306
end
307
308
function TreeBoundaryContainer1D{RealT, uEltype}(capacity::Integer,
309
n_variables) where {RealT <: Real,
310
uEltype <: Real}
311
nan_RealT = convert(RealT, NaN)
312
nan_uEltype = convert(uEltype, NaN)
313
314
# Initialize fields with defaults
315
_u = fill(nan_uEltype, 2 * n_variables * capacity)
316
u = unsafe_wrap(Array, pointer(_u),
317
(2, n_variables, capacity))
318
319
neighbor_ids = fill(typemin(Int), capacity)
320
321
orientations = fill(typemin(Int), capacity)
322
323
neighbor_sides = fill(typemin(Int), capacity)
324
325
_node_coordinates = fill(nan_RealT, 1 * capacity)
326
node_coordinates = unsafe_wrap(Array, pointer(_node_coordinates),
327
(1, capacity))
328
329
n_boundaries_per_direction = SVector(0, 0)
330
331
return TreeBoundaryContainer1D{RealT, uEltype}(u, neighbor_ids, orientations,
332
neighbor_sides,
333
node_coordinates,
334
n_boundaries_per_direction,
335
_u, _node_coordinates)
336
end
337
338
# Create boundaries container and initialize boundary data in `elements`.
339
function init_boundaries(cell_ids, mesh::TreeMesh1D,
340
elements::TreeElementContainer1D, basis)
341
# Initialize container
342
n_boundaries = count_required_boundaries(mesh, cell_ids)
343
boundaries = TreeBoundaryContainer1D{real(elements), eltype(elements)}(n_boundaries,
344
nvariables(elements))
345
346
# Connect elements with boundaries
347
init_boundaries!(boundaries, elements, mesh, basis)
348
return boundaries
349
end
350
351
# Count the number of boundaries that need to be created
352
function count_required_boundaries(mesh::TreeMesh1D, cell_ids)
353
count = 0
354
355
# Iterate over all cells
356
for cell_id in cell_ids
357
for direction in eachdirection(mesh.tree)
358
# If neighbor exists, current cell is not at a boundary
359
if has_neighbor(mesh.tree, cell_id, direction)
360
continue
361
end
362
363
# If coarse neighbor exists, current cell is not at a boundary
364
if has_coarse_neighbor(mesh.tree, cell_id, direction)
365
continue
366
end
367
368
# No neighbor exists in this direction -> must be a boundary
369
count += 1
370
end
371
end
372
373
return count
374
end
375
376
# For Lobtto points, we can simply use the outer nodes of the elements as boundary nodes.
377
function calc_boundary_node_coordinates!(boundaries, element, count, direction,
378
elements, mesh::TreeMesh1D,
379
basis::LobattoLegendreBasis)
380
el_node_coords = elements.node_coordinates
381
bnd_node_coords = boundaries.node_coordinates
382
383
orientation = 1 # always 1 in 1D
384
if direction == 1
385
bnd_node_coords[orientation, count] = el_node_coords[orientation, 1,
386
element]
387
elseif direction == 2
388
bnd_node_coords[orientation, count] = el_node_coords[orientation, end,
389
element]
390
else
391
error("should not happen")
392
end
393
394
return nothing
395
end
396
397
# For Gauss points, we need to interpolate the boundary node coordinates.
398
function calc_boundary_node_coordinates!(boundaries, element, count, direction,
399
elements, mesh::TreeMesh1D,
400
basis::GaussLegendreBasis)
401
boundary_matrix = basis.boundary_interpolation
402
el_node_coords = elements.node_coordinates
403
bnd_node_coords = boundaries.node_coordinates
404
405
orientation = 1 # always 1 in 1D
406
if direction == 1
407
@views x_interpolated_left = dot(boundary_matrix[:, 1],
408
el_node_coords[orientation, :,
409
element])
410
bnd_node_coords[orientation, count] = x_interpolated_left
411
elseif direction == 2
412
@views x_interpolated_right = dot(boundary_matrix[:, 2],
413
el_node_coords[orientation, :,
414
element])
415
bnd_node_coords[orientation, count] = x_interpolated_right
416
else
417
error("should not happen")
418
end
419
420
return nothing
421
end
422
423
# Initialize connectivity between elements and boundaries
424
function init_boundaries!(boundaries, elements, mesh::TreeMesh1D, basis)
425
# Reset boundaries count
426
count = 0
427
428
# Initialize boundary counts
429
counts_per_direction = MVector(0, 0)
430
431
# OBS! Iterate over directions first, then over elements, and count boundaries in each direction
432
# Rationale: This way the boundaries are internally sorted by the directions -x, +x, -y etc.,
433
# obviating the need to store the boundary condition to be applied explicitly.
434
# Loop over directions
435
for direction in eachdirection(mesh.tree)
436
# Iterate over all elements to find missing neighbors and to connect to boundaries
437
for element in eachelement(elements)
438
# Get cell id
439
cell_id = elements.cell_ids[element]
440
441
# If neighbor exists, current cell is not at a boundary
442
if has_neighbor(mesh.tree, cell_id, direction)
443
continue
444
end
445
446
# If coarse neighbor exists, current cell is not at a boundary
447
if has_coarse_neighbor(mesh.tree, cell_id, direction)
448
continue
449
end
450
451
# Create boundary
452
count += 1
453
counts_per_direction[direction] += 1
454
455
# Set neighbor element id
456
boundaries.neighbor_ids[count] = element
457
458
# Set neighbor side, which denotes the direction (1 -> negative, 2 -> positive) of the element
459
if direction == 2
460
boundaries.neighbor_sides[count] = 1
461
else
462
boundaries.neighbor_sides[count] = 2
463
end
464
465
# Set orientation (x -> 1)
466
boundaries.orientations[count] = 1
467
468
# Calculate node coordinates
469
calc_boundary_node_coordinates!(boundaries, element, count, direction,
470
elements, mesh, basis)
471
end
472
end
473
474
@assert count==nboundaries(boundaries) ("Actual boundaries count ($count) does not match "*
475
"expectations $(nboundaries(boundaries))")
476
@assert sum(counts_per_direction) == count
477
478
boundaries.n_boundaries_per_direction = SVector(counts_per_direction)
479
480
return boundaries.n_boundaries_per_direction
481
end
482
483
function reinitialize_containers!(mesh::TreeMesh{1}, equations, dg::DGSEM, cache)
484
# Get new list of leaf cells
485
leaf_cell_ids = local_leaf_cells(mesh.tree)
486
n_cells = length(leaf_cell_ids)
487
488
# re-initialize elements container
489
@unpack elements = cache
490
resize!(elements, n_cells)
491
init_elements!(elements, leaf_cell_ids, mesh, dg.basis)
492
493
# Resize volume integral and related datastructures
494
@unpack volume_integral = dg
495
resize_volume_integral_cache!(cache, mesh, volume_integral, n_cells)
496
reinit_volume_integral_cache!(cache, mesh, dg, volume_integral, n_cells)
497
498
# re-initialize interfaces container
499
@unpack interfaces = cache
500
resize!(interfaces, count_required_interfaces(mesh, leaf_cell_ids))
501
init_interfaces!(interfaces, elements, mesh)
502
503
# re-initialize boundaries container
504
@unpack boundaries = cache
505
resize!(boundaries, count_required_boundaries(mesh, leaf_cell_ids))
506
init_boundaries!(boundaries, elements, mesh, dg.basis)
507
508
return nothing
509
end
510
end # @muladd
511
512