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_2d.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 TreeElementContainer2D{RealT <: Real, uEltype <: Real} <:
10
AbstractTreeElementContainer
11
inverse_jacobian::Vector{RealT} # [elements]
12
node_coordinates::Array{RealT, 4} # [orientation, i, j, elements]
13
surface_flux_values::Array{uEltype, 4} # [variables, i, 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::TreeElementContainer2D, 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, 2 * n_nodes * n_nodes * capacity)
34
elements.node_coordinates = unsafe_wrap(Array, pointer(_node_coordinates),
35
(2, n_nodes, n_nodes, capacity))
36
37
resize!(_surface_flux_values, n_variables * n_nodes * 2 * 2 * capacity)
38
elements.surface_flux_values = unsafe_wrap(Array, pointer(_surface_flux_values),
39
(n_variables, n_nodes, 2 * 2, capacity))
40
41
resize!(cell_ids, capacity)
42
43
return nothing
44
end
45
46
function TreeElementContainer2D{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, 2 * n_nodes * n_nodes * capacity)
56
node_coordinates = unsafe_wrap(Array, pointer(_node_coordinates),
57
(2, n_nodes, n_nodes, capacity))
58
59
_surface_flux_values = fill(nan_uEltype, n_variables * n_nodes * 2 * 2 * capacity)
60
surface_flux_values = unsafe_wrap(Array, pointer(_surface_flux_values),
61
(n_variables, n_nodes, 2 * 2, capacity))
62
63
cell_ids = fill(typemin(Int), capacity)
64
65
return TreeElementContainer2D{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::TreeMesh2D,
73
equations::AbstractEquations{2},
74
basis, ::Type{RealT},
75
::Type{uEltype}) where {RealT <: Real, uEltype <: Real}
76
# Initialize container
77
n_elements = length(cell_ids)
78
elements = TreeElementContainer2D{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::TreeMesh2D, 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 j in eachnode(basis), i in eachnode(basis)
115
elements.node_coordinates[1, i, j, element] = (mesh.tree.coordinates[1,
116
cell_id] +
117
jacobian *
118
(nodes[i] - reference_offset))
119
elements.node_coordinates[2, i, j, element] = (mesh.tree.coordinates[2,
120
cell_id] +
121
jacobian *
122
(nodes[j] - reference_offset))
123
end
124
end
125
126
return elements
127
end
128
129
# Container data structure (structure-of-arrays style) for DG interfaces
130
mutable struct TreeInterfaceContainer2D{uEltype <: Real} <:
131
AbstractTreeInterfaceContainer
132
u::Array{uEltype, 4} # [leftright, variables, i, interfaces]
133
neighbor_ids::Array{Int, 2} # [leftright, interfaces]
134
orientations::Vector{Int} # [interfaces]
135
# internal `resize!`able storage
136
_u::Vector{uEltype}
137
_neighbor_ids::Vector{Int}
138
end
139
140
# See explanation of Base.resize! for the element container
141
function Base.resize!(interfaces::TreeInterfaceContainer2D, capacity)
142
n_nodes = nnodes(interfaces)
143
n_variables = nvariables(interfaces)
144
@unpack _u, _neighbor_ids, orientations = interfaces
145
146
resize!(_u, 2 * n_variables * n_nodes * capacity)
147
interfaces.u = unsafe_wrap(Array, pointer(_u),
148
(2, n_variables, n_nodes, capacity))
149
150
resize!(_neighbor_ids, 2 * capacity)
151
interfaces.neighbor_ids = unsafe_wrap(Array, pointer(_neighbor_ids),
152
(2, capacity))
153
154
resize!(orientations, capacity)
155
156
return nothing
157
end
158
159
function TreeInterfaceContainer2D{uEltype}(capacity::Integer, n_variables,
160
n_nodes) where {uEltype <: Real}
161
nan = convert(uEltype, NaN)
162
163
# Initialize fields with defaults
164
_u = fill(nan, 2 * n_variables * n_nodes * capacity)
165
u = unsafe_wrap(Array, pointer(_u),
166
(2, n_variables, n_nodes, capacity))
167
168
_neighbor_ids = fill(typemin(Int), 2 * capacity)
169
neighbor_ids = unsafe_wrap(Array, pointer(_neighbor_ids),
170
(2, capacity))
171
172
orientations = fill(typemin(Int), capacity)
173
174
return TreeInterfaceContainer2D{uEltype}(u, neighbor_ids, orientations,
175
_u, _neighbor_ids)
176
end
177
178
# Create interface container and initialize interface data in `elements`.
179
function init_interfaces(cell_ids, mesh::TreeMesh2D,
180
elements::TreeElementContainer2D)
181
# Initialize container
182
n_interfaces = count_required_interfaces(mesh, cell_ids)
183
interfaces = TreeInterfaceContainer2D{eltype(elements)}(n_interfaces,
184
nvariables(elements),
185
nnodes(elements))
186
187
# Connect elements with interfaces
188
init_interfaces!(interfaces, elements, mesh)
189
return interfaces
190
end
191
192
# Count the number of interfaces that need to be created
193
function count_required_interfaces(mesh::TreeMesh2D, cell_ids)
194
count = 0
195
196
# Iterate over all cells
197
for cell_id in cell_ids
198
for direction in eachdirection(mesh.tree)
199
# Only count interfaces in positive direction to avoid double counting
200
if direction % 2 == 1
201
continue
202
end
203
204
# If no neighbor exists, current cell is small or at boundary and thus we need a mortar
205
if !has_neighbor(mesh.tree, cell_id, direction)
206
continue
207
end
208
209
# Skip if neighbor has children
210
neighbor_cell_id = mesh.tree.neighbor_ids[direction, cell_id]
211
if has_children(mesh.tree, neighbor_cell_id)
212
continue
213
end
214
215
# Skip if neighbor is on different rank -> create MPI interface instead
216
if mpi_isparallel() && !is_own_cell(mesh.tree, neighbor_cell_id)
217
continue
218
end
219
220
count += 1
221
end
222
end
223
224
return count
225
end
226
227
# Initialize connectivity between elements and interfaces
228
function init_interfaces!(interfaces, elements, mesh::TreeMesh2D)
229
# Exit early if there are no interfaces to initialize
230
if ninterfaces(interfaces) == 0
231
return nothing
232
end
233
234
# Construct cell -> element mapping for easier algorithm implementation
235
tree = mesh.tree
236
c2e = zeros(Int, length(tree))
237
for element in eachelement(elements)
238
c2e[elements.cell_ids[element]] = element
239
end
240
241
# Reset interface count
242
count = 0
243
244
# Iterate over all elements to find neighbors and to connect via interfaces
245
for element in eachelement(elements)
246
# Get cell id
247
cell_id = elements.cell_ids[element]
248
249
# Loop over directions
250
for direction in eachdirection(mesh.tree)
251
# Only create interfaces in positive direction
252
if direction % 2 == 1
253
continue
254
end
255
256
# If no neighbor exists, current cell is small and thus we need a mortar
257
if !has_neighbor(mesh.tree, cell_id, direction)
258
continue
259
end
260
261
# Skip if neighbor has children
262
neighbor_cell_id = mesh.tree.neighbor_ids[direction, cell_id]
263
if has_children(mesh.tree, neighbor_cell_id)
264
continue
265
end
266
267
# Skip if neighbor is on different rank -> create MPI interface instead
268
if mpi_isparallel() && !is_own_cell(mesh.tree, neighbor_cell_id)
269
continue
270
end
271
272
# Create interface between elements (1 -> "left" of interface, 2 -> "right" of interface)
273
count += 1
274
interfaces.neighbor_ids[2, count] = c2e[neighbor_cell_id]
275
interfaces.neighbor_ids[1, count] = element
276
277
# Set orientation (x -> 1, y -> 2)
278
interfaces.orientations[count] = div(direction, 2)
279
end
280
end
281
282
@assert count==ninterfaces(interfaces) ("Actual interface count ($count) does not match "*
283
"expectations $(ninterfaces(interfaces))")
284
end
285
286
# Container data structure (structure-of-arrays style) for DG boundaries
287
mutable struct TreeBoundaryContainer2D{RealT <: Real, uEltype <: Real} <:
288
AbstractTreeBoundaryContainer
289
u::Array{uEltype, 4} # [leftright, variables, i, boundaries]
290
neighbor_ids::Vector{Int} # [boundaries]
291
orientations::Vector{Int} # [boundaries]
292
neighbor_sides::Vector{Int} # [boundaries]
293
node_coordinates::Array{RealT, 3} # [orientation, i, elements]
294
n_boundaries_per_direction::SVector{4, Int} # [direction]
295
# internal `resize!`able storage
296
_u::Vector{uEltype}
297
_node_coordinates::Vector{RealT}
298
end
299
300
# See explanation of Base.resize! for the element container
301
function Base.resize!(boundaries::TreeBoundaryContainer2D, capacity)
302
n_nodes = nnodes(boundaries)
303
n_variables = nvariables(boundaries)
304
@unpack _u, _node_coordinates,
305
neighbor_ids, orientations, neighbor_sides = boundaries
306
307
resize!(_u, 2 * n_variables * n_nodes * capacity)
308
boundaries.u = unsafe_wrap(Array, pointer(_u),
309
(2, n_variables, n_nodes, capacity))
310
311
resize!(_node_coordinates, 2 * n_nodes * capacity)
312
boundaries.node_coordinates = unsafe_wrap(Array, pointer(_node_coordinates),
313
(2, n_nodes, capacity))
314
315
resize!(neighbor_ids, capacity)
316
317
resize!(orientations, capacity)
318
319
resize!(neighbor_sides, capacity)
320
321
return nothing
322
end
323
324
function TreeBoundaryContainer2D{RealT, uEltype}(capacity::Integer, n_variables,
325
n_nodes) where {RealT <: Real,
326
uEltype <: Real}
327
nan_RealT = convert(RealT, NaN)
328
nan_uEltype = convert(uEltype, NaN)
329
330
# Initialize fields with defaults
331
_u = fill(nan_uEltype, 2 * n_variables * n_nodes * capacity)
332
u = unsafe_wrap(Array, pointer(_u),
333
(2, n_variables, n_nodes, capacity))
334
335
neighbor_ids = fill(typemin(Int), capacity)
336
337
orientations = fill(typemin(Int), capacity)
338
339
neighbor_sides = fill(typemin(Int), capacity)
340
341
_node_coordinates = fill(nan_RealT, 2 * n_nodes * capacity)
342
node_coordinates = unsafe_wrap(Array, pointer(_node_coordinates),
343
(2, n_nodes, capacity))
344
345
n_boundaries_per_direction = SVector(0, 0, 0, 0)
346
347
return TreeBoundaryContainer2D{RealT, uEltype}(u, neighbor_ids, orientations,
348
neighbor_sides,
349
node_coordinates,
350
n_boundaries_per_direction,
351
_u, _node_coordinates)
352
end
353
354
# Create boundaries container and initialize boundary data in `elements`.
355
function init_boundaries(cell_ids, mesh::TreeMesh2D,
356
elements::TreeElementContainer2D, basis)
357
# Initialize container
358
n_boundaries = count_required_boundaries(mesh, cell_ids)
359
boundaries = TreeBoundaryContainer2D{real(elements), eltype(elements)}(n_boundaries,
360
nvariables(elements),
361
nnodes(elements))
362
363
# Connect elements with boundaries
364
init_boundaries!(boundaries, elements, mesh, basis)
365
return boundaries
366
end
367
368
# Count the number of boundaries that need to be created
369
function count_required_boundaries(mesh::TreeMesh2D, cell_ids)
370
count = 0
371
372
# Iterate over all cells
373
for cell_id in cell_ids
374
for direction in eachdirection(mesh.tree)
375
# If neighbor exists, current cell is not at a boundary
376
if has_neighbor(mesh.tree, cell_id, direction)
377
continue
378
end
379
380
# If coarse neighbor exists, current cell is not at a boundary
381
if has_coarse_neighbor(mesh.tree, cell_id, direction)
382
continue
383
end
384
385
# No neighbor exists in this direction -> must be a boundary
386
count += 1
387
end
388
end
389
390
return count
391
end
392
393
# For Lobatto points, we can simply use the outer nodes of the elements as boundary nodes.
394
function calc_boundary_node_coordinates!(boundaries, element, count, direction,
395
elements, mesh::TreeMesh2D,
396
basis::LobattoLegendreBasis)
397
el_node_coords = elements.node_coordinates
398
bnd_node_coords = boundaries.node_coordinates
399
400
if direction == 1 # -x direction
401
@views bnd_node_coords[:, :, count] .= el_node_coords[:, 1, :, element]
402
elseif direction == 2 # +x direction
403
@views bnd_node_coords[:, :, count] .= el_node_coords[:, end, :, element]
404
elseif direction == 3 # -y direction
405
@views bnd_node_coords[:, :, count] .= el_node_coords[:, :, 1, element]
406
elseif direction == 4 # +y direction
407
@views bnd_node_coords[:, :, count] .= el_node_coords[:, :, end, element]
408
else
409
error("should not happen")
410
end
411
412
return nothing
413
end
414
415
# For Gauss points, we need to interpolate the boundary node coordinates.
416
function calc_boundary_node_coordinates!(boundaries, element, count, direction,
417
elements, mesh::TreeMesh2D,
418
basis::GaussLegendreBasis)
419
boundary_matrix = basis.boundary_interpolation
420
el_node_coords = elements.node_coordinates
421
bnd_node_coords = boundaries.node_coordinates
422
423
if direction == 1 # -x direction: interpolate in x for each y node j
424
for j in eachnode(basis)
425
for orientation in 1:2 # Need to set both x and y coordinate of boundary node
426
@views bnd_node_coords[orientation, j, count] = dot(boundary_matrix[:,
427
1],
428
el_node_coords[orientation,
429
:,
430
j,
431
element])
432
end
433
end
434
elseif direction == 2 # +x direction: interpolate in x for each y node j
435
for j in eachnode(basis)
436
for orientation in 1:2 # Need to set both x and y coordinate of boundary node
437
@views bnd_node_coords[orientation, j, count] = dot(boundary_matrix[:,
438
2],
439
el_node_coords[orientation,
440
:,
441
j,
442
element])
443
end
444
end
445
elseif direction == 3 # -y direction: interpolate in y for each x node i
446
for i in eachnode(basis)
447
for orientation in 1:2 # Need to set both x and y coordinate of boundary node
448
@views bnd_node_coords[orientation, i, count] = dot(boundary_matrix[:,
449
1],
450
el_node_coords[orientation,
451
i,
452
:,
453
element])
454
end
455
end
456
elseif direction == 4 # +y direction: interpolate in y for each x node i
457
for i in eachnode(basis)
458
for orientation in 1:2 # Need to set both x and y coordinate of boundary node
459
@views bnd_node_coords[orientation, i, count] = dot(boundary_matrix[:,
460
2],
461
el_node_coords[orientation,
462
i,
463
:,
464
element])
465
end
466
end
467
else
468
error("should not happen")
469
end
470
471
return nothing
472
end
473
474
# Initialize connectivity between elements and boundaries
475
function init_boundaries!(boundaries, elements, mesh::TreeMesh2D, basis)
476
# Exit early if there are no boundaries to initialize
477
if nboundaries(boundaries) == 0
478
# In this case n_boundaries_per_direction still needs to be reset!
479
boundaries.n_boundaries_per_direction = SVector(0, 0, 0, 0)
480
return nothing
481
end
482
483
# Reset boundaries count
484
count = 0
485
486
# Initialize boundary counts
487
counts_per_direction = MVector(0, 0, 0, 0)
488
489
# OBS! Iterate over directions first, then over elements, and count boundaries in each direction
490
# Rationale: This way the boundaries are internally sorted by the directions -x, +x, -y etc.,
491
# obviating the need to store the boundary condition to be applied explicitly.
492
# Loop over directions
493
for direction in eachdirection(mesh.tree)
494
# Iterate over all elements to find missing neighbors and to connect to boundaries
495
for element in eachelement(elements)
496
# Get cell id
497
cell_id = elements.cell_ids[element]
498
499
# If neighbor exists, current cell is not at a boundary
500
if has_neighbor(mesh.tree, cell_id, direction)
501
continue
502
end
503
504
# If coarse neighbor exists, current cell is not at a boundary
505
if has_coarse_neighbor(mesh.tree, cell_id, direction)
506
continue
507
end
508
509
# Create boundary
510
count += 1
511
counts_per_direction[direction] += 1
512
513
# Set neighbor element id
514
boundaries.neighbor_ids[count] = element
515
516
# Set neighbor side, which denotes the direction (1 -> negative, 2 -> positive) of the element
517
if iseven(direction)
518
boundaries.neighbor_sides[count] = 1
519
else
520
boundaries.neighbor_sides[count] = 2
521
end
522
523
# Set orientation (x -> 1, y -> 2)
524
if direction in (1, 2)
525
boundaries.orientations[count] = 1 # x direction
526
else
527
boundaries.orientations[count] = 2 # y direction
528
end
529
530
# Calculate node coordinates
531
calc_boundary_node_coordinates!(boundaries, element, count, direction,
532
elements, mesh, basis)
533
end
534
end
535
536
@assert count==nboundaries(boundaries) ("Actual boundaries count ($count) does not match "*
537
"expectations $(nboundaries(boundaries))")
538
@assert sum(counts_per_direction) == count
539
540
boundaries.n_boundaries_per_direction = SVector(counts_per_direction)
541
542
return boundaries.n_boundaries_per_direction
543
end
544
545
# Container data structure (structure-of-arrays style) for DG L2 mortars
546
# Positions/directions for orientations = 1, large_sides = 2:
547
# mortar is orthogonal to x-axis, large side is in positive coordinate direction wrt mortar
548
# | |
549
# upper = 2 | |
550
# | |
551
# | 3 = large side
552
# | |
553
# lower = 1 | |
554
# | |
555
mutable struct TreeL2MortarContainer2D{uEltype <: Real} <:
556
AbstractTreeL2MortarContainer
557
u_upper::Array{uEltype, 4} # [leftright, variables, i, mortars]
558
u_lower::Array{uEltype, 4} # [leftright, variables, i, mortars]
559
neighbor_ids::Array{Int, 2} # [position, mortars]
560
# Large sides: left -> 1, right -> 2
561
large_sides::Vector{Int} # [mortars]
562
orientations::Vector{Int} # [mortars]
563
# internal `resize!`able storage
564
_u_upper::Vector{uEltype}
565
_u_lower::Vector{uEltype}
566
_neighbor_ids::Vector{Int}
567
end
568
569
# Return number of mortar nodes (L2 mortars are only h-adaptive, not p-adaptive)
570
@inline nnodes(mortars::TreeL2MortarContainer2D) = size(mortars.u_upper, 3)
571
# Return number of equation variables
572
@inline nvariables(mortars::TreeL2MortarContainer2D) = size(mortars.u_upper, 2)
573
574
# See explanation of Base.resize! for the element container
575
function Base.resize!(mortars::TreeL2MortarContainer2D, capacity)
576
n_nodes = nnodes(mortars)
577
n_variables = nvariables(mortars)
578
@unpack _u_upper, _u_lower, _neighbor_ids,
579
large_sides, orientations = mortars
580
581
resize!(_u_upper, 2 * n_variables * n_nodes * capacity)
582
mortars.u_upper = unsafe_wrap(Array, pointer(_u_upper),
583
(2, n_variables, n_nodes, capacity))
584
585
resize!(_u_lower, 2 * n_variables * n_nodes * capacity)
586
mortars.u_lower = unsafe_wrap(Array, pointer(_u_lower),
587
(2, n_variables, n_nodes, capacity))
588
589
resize!(_neighbor_ids, 3 * capacity)
590
mortars.neighbor_ids = unsafe_wrap(Array, pointer(_neighbor_ids),
591
(3, capacity))
592
593
resize!(large_sides, capacity)
594
595
resize!(orientations, capacity)
596
597
return nothing
598
end
599
600
function TreeL2MortarContainer2D{uEltype}(capacity::Integer, n_variables,
601
n_nodes) where {uEltype <: Real}
602
nan = convert(uEltype, NaN)
603
604
# Initialize fields with defaults
605
_u_upper = fill(nan, 2 * n_variables * n_nodes * capacity)
606
u_upper = unsafe_wrap(Array, pointer(_u_upper),
607
(2, n_variables, n_nodes, capacity))
608
609
_u_lower = fill(nan, 2 * n_variables * n_nodes * capacity)
610
u_lower = unsafe_wrap(Array, pointer(_u_lower),
611
(2, n_variables, n_nodes, capacity))
612
613
_neighbor_ids = fill(typemin(Int), 3 * capacity)
614
neighbor_ids = unsafe_wrap(Array, pointer(_neighbor_ids),
615
(3, capacity))
616
617
large_sides = fill(typemin(Int), capacity)
618
619
orientations = fill(typemin(Int), capacity)
620
621
return TreeL2MortarContainer2D{uEltype}(u_upper, u_lower,
622
neighbor_ids,
623
large_sides, orientations,
624
_u_upper, _u_lower, _neighbor_ids)
625
end
626
627
# Allow printing container contents
628
function Base.show(io::IO, ::MIME"text/plain", c::TreeL2MortarContainer2D)
629
@nospecialize c # reduce precompilation time
630
631
println(io, '*'^20)
632
for idx in CartesianIndices(c.u_upper)
633
println(io, "c.u_upper[$idx] = $(c.u_upper[idx])")
634
end
635
for idx in CartesianIndices(c.u_lower)
636
println(io, "c.u_lower[$idx] = $(c.u_lower[idx])")
637
end
638
println(io, "transpose(c.neighbor_ids) = $(transpose(c.neighbor_ids))")
639
println(io, "c.large_sides = $(c.large_sides)")
640
println(io, "c.orientations = $(c.orientations)")
641
print(io, '*'^20)
642
return nothing
643
end
644
645
# Create mortar container and initialize mortar data in `elements`.
646
function init_mortars(cell_ids, mesh::TreeMesh2D,
647
elements::TreeElementContainer2D,
648
::LobattoLegendreMortarL2)
649
# Initialize containers
650
n_mortars = count_required_mortars(mesh, cell_ids)
651
mortars = TreeL2MortarContainer2D{eltype(elements)}(n_mortars, nvariables(elements),
652
nnodes(elements))
653
654
# Connect elements with mortars
655
init_mortars!(mortars, elements, mesh)
656
return mortars
657
end
658
659
# Count the number of mortars that need to be created
660
function count_required_mortars(mesh::TreeMesh2D, cell_ids)
661
count = 0
662
663
# Iterate over all cells and count mortars from perspective of coarse cells
664
for cell_id in cell_ids
665
for direction in eachdirection(mesh.tree)
666
# If no neighbor exists, cell is small with large neighbor or at boundary -> do nothing
667
if !has_neighbor(mesh.tree, cell_id, direction)
668
continue
669
end
670
671
# If neighbor has no children, this is a conforming interface -> do nothing
672
neighbor_id = mesh.tree.neighbor_ids[direction, cell_id]
673
if !has_children(mesh.tree, neighbor_id)
674
continue
675
end
676
677
# Skip if one of the small cells is on different rank -> create mpi mortar instead
678
# (the coarse cell is always on the local rank)
679
if mpi_isparallel()
680
if direction == 1 # small cells left, mortar in x-direction
681
lower_cell_id = mesh.tree.child_ids[2, neighbor_id]
682
upper_cell_id = mesh.tree.child_ids[4, neighbor_id]
683
elseif direction == 2 # small cells right, mortar in x-direction
684
lower_cell_id = mesh.tree.child_ids[1, neighbor_id]
685
upper_cell_id = mesh.tree.child_ids[3, neighbor_id]
686
elseif direction == 3 # small cells left, mortar in y-direction
687
lower_cell_id = mesh.tree.child_ids[3, neighbor_id]
688
upper_cell_id = mesh.tree.child_ids[4, neighbor_id]
689
else # direction == 4, small cells right, mortar in y-direction
690
lower_cell_id = mesh.tree.child_ids[1, neighbor_id]
691
upper_cell_id = mesh.tree.child_ids[2, neighbor_id]
692
end
693
small_cell_ids = (lower_cell_id, upper_cell_id)
694
if any(cell -> !is_own_cell(mesh.tree, cell), small_cell_ids)
695
continue
696
end
697
end
698
699
count += 1
700
end
701
end
702
703
return count
704
end
705
706
# Initialize connectivity between elements and mortars
707
function init_mortars!(mortars, elements, mesh::TreeMesh2D)
708
# Exit early if there are no mortars to initialize
709
if nmortars(mortars) == 0
710
return nothing
711
end
712
713
# Construct cell -> element mapping for easier algorithm implementation
714
tree = mesh.tree
715
c2e = zeros(Int, length(tree))
716
for element in eachelement(elements)
717
c2e[elements.cell_ids[element]] = element
718
end
719
720
# Reset interface count
721
count = 0
722
723
# Iterate over all elements to find neighbors and to connect via interfaces
724
for element in eachelement(elements)
725
# Get cell id
726
cell_id = elements.cell_ids[element]
727
728
for direction in eachdirection(mesh.tree)
729
# If no neighbor exists, cell is small with large neighbor -> do nothing
730
if !has_neighbor(mesh.tree, cell_id, direction)
731
continue
732
end
733
734
# If neighbor has no children, this is a conforming interface -> do nothing
735
neighbor_cell_id = mesh.tree.neighbor_ids[direction, cell_id]
736
if !has_children(mesh.tree, neighbor_cell_id)
737
continue
738
end
739
740
# Skip if one of the small cells is on different rank -> create mpi mortar instead
741
# (the coarse cell is always on the local rank)
742
if mpi_isparallel()
743
if direction == 1 # small cells left, mortar in x-direction
744
lower_cell_id = mesh.tree.child_ids[2, neighbor_cell_id]
745
upper_cell_id = mesh.tree.child_ids[4, neighbor_cell_id]
746
elseif direction == 2 # small cells right, mortar in x-direction
747
lower_cell_id = mesh.tree.child_ids[1, neighbor_cell_id]
748
upper_cell_id = mesh.tree.child_ids[3, neighbor_cell_id]
749
elseif direction == 3 # small cells left, mortar in y-direction
750
lower_cell_id = mesh.tree.child_ids[3, neighbor_cell_id]
751
upper_cell_id = mesh.tree.child_ids[4, neighbor_cell_id]
752
else # direction == 4, small cells right, mortar in y-direction
753
lower_cell_id = mesh.tree.child_ids[1, neighbor_cell_id]
754
upper_cell_id = mesh.tree.child_ids[2, neighbor_cell_id]
755
end
756
small_cell_ids = (lower_cell_id, upper_cell_id)
757
if any(cell -> !is_own_cell(mesh.tree, cell), small_cell_ids)
758
continue
759
end
760
end
761
762
# Create mortar between elements:
763
# 1 -> small element in negative coordinate direction
764
# 2 -> small element in positive coordinate direction
765
# 3 -> large element
766
count += 1
767
mortars.neighbor_ids[3, count] = element
768
if direction == 1
769
mortars.neighbor_ids[1, count] = c2e[mesh.tree.child_ids[2,
770
neighbor_cell_id]]
771
mortars.neighbor_ids[2, count] = c2e[mesh.tree.child_ids[4,
772
neighbor_cell_id]]
773
elseif direction == 2
774
mortars.neighbor_ids[1, count] = c2e[mesh.tree.child_ids[1,
775
neighbor_cell_id]]
776
mortars.neighbor_ids[2, count] = c2e[mesh.tree.child_ids[3,
777
neighbor_cell_id]]
778
elseif direction == 3
779
mortars.neighbor_ids[1, count] = c2e[mesh.tree.child_ids[3,
780
neighbor_cell_id]]
781
mortars.neighbor_ids[2, count] = c2e[mesh.tree.child_ids[4,
782
neighbor_cell_id]]
783
elseif direction == 4
784
mortars.neighbor_ids[1, count] = c2e[mesh.tree.child_ids[1,
785
neighbor_cell_id]]
786
mortars.neighbor_ids[2, count] = c2e[mesh.tree.child_ids[2,
787
neighbor_cell_id]]
788
else
789
error("should not happen")
790
end
791
792
# Set large side, which denotes the direction (1 -> negative, 2 -> positive) of the large side
793
if iseven(direction)
794
mortars.large_sides[count] = 1
795
else
796
mortars.large_sides[count] = 2
797
end
798
799
# Set orientation (x -> 1, y -> 2)
800
if direction in (1, 2)
801
mortars.orientations[count] = 1
802
else
803
mortars.orientations[count] = 2
804
end
805
end
806
end
807
808
@assert count==nmortars(mortars) ("Actual mortar count ($count) does not match "*
809
"expectations $(nmortars(mortars))")
810
end
811
812
# Container data structure (structure-of-arrays style) for DG MPI interfaces
813
mutable struct TreeMPIInterfaceContainer2D{uEltype <: Real} <:
814
AbstractTreeMPIInterfaceContainer
815
u::Array{uEltype, 4} # [leftright, variables, i, interfaces]
816
# Note: `local_neighbor_ids` stores the MPI-local neighbors, but with globally valid index!
817
local_neighbor_ids::Vector{Int} # [interfaces]
818
orientations::Vector{Int} # [interfaces]
819
remote_sides::Vector{Int} # [interfaces]
820
# internal `resize!`able storage
821
_u::Vector{uEltype}
822
end
823
824
# See explanation of Base.resize! for the element container
825
function Base.resize!(mpi_interfaces::TreeMPIInterfaceContainer2D, capacity)
826
n_nodes = nnodes(mpi_interfaces)
827
n_variables = nvariables(mpi_interfaces)
828
@unpack _u, local_neighbor_ids, orientations, remote_sides = mpi_interfaces
829
830
resize!(_u, 2 * n_variables * n_nodes * capacity)
831
mpi_interfaces.u = unsafe_wrap(Array, pointer(_u),
832
(2, n_variables, n_nodes, capacity))
833
834
resize!(local_neighbor_ids, capacity)
835
836
resize!(orientations, capacity)
837
838
resize!(remote_sides, capacity)
839
840
return nothing
841
end
842
843
function TreeMPIInterfaceContainer2D{uEltype}(capacity::Integer, n_variables,
844
n_nodes) where {uEltype <: Real}
845
nan = convert(uEltype, NaN)
846
847
# Initialize fields with defaults
848
_u = fill(nan, 2 * n_variables * n_nodes * capacity)
849
u = unsafe_wrap(Array, pointer(_u),
850
(2, n_variables, n_nodes, capacity))
851
852
local_neighbor_ids = fill(typemin(Int), capacity)
853
854
orientations = fill(typemin(Int), capacity)
855
856
remote_sides = fill(typemin(Int), capacity)
857
858
return TreeMPIInterfaceContainer2D{uEltype}(u, local_neighbor_ids, orientations,
859
remote_sides, _u)
860
end
861
862
# Create MPI interface container and initialize MPI interface data in `elements`.
863
function init_mpi_interfaces(cell_ids, mesh::TreeMesh2D,
864
elements::TreeElementContainer2D)
865
# Initialize container
866
n_mpi_interfaces = count_required_mpi_interfaces(mesh, cell_ids)
867
mpi_interfaces = TreeMPIInterfaceContainer2D{eltype(elements)}(n_mpi_interfaces,
868
nvariables(elements),
869
nnodes(elements))
870
871
# Connect elements with interfaces
872
init_mpi_interfaces!(mpi_interfaces, elements, mesh)
873
return mpi_interfaces
874
end
875
876
# Count the number of MPI interfaces that need to be created
877
function count_required_mpi_interfaces(mesh::TreeMesh2D, cell_ids)
878
# No MPI interfaces needed if MPI is not used
879
if !mpi_isparallel()
880
return 0
881
end
882
883
count = 0
884
885
# Iterate over all cells
886
for cell_id in cell_ids
887
for direction in eachdirection(mesh.tree)
888
# If no neighbor exists, current cell is small or at boundary and thus we need a mortar
889
if !has_neighbor(mesh.tree, cell_id, direction)
890
continue
891
end
892
893
# Skip if neighbor has children
894
neighbor_cell_id = mesh.tree.neighbor_ids[direction, cell_id]
895
if has_children(mesh.tree, neighbor_cell_id)
896
continue
897
end
898
899
# Skip if neighbor is on this rank -> create regular interface instead
900
if is_own_cell(mesh.tree, neighbor_cell_id)
901
continue
902
end
903
904
count += 1
905
end
906
end
907
908
return count
909
end
910
911
# Initialize connectivity between elements and interfaces
912
function init_mpi_interfaces!(mpi_interfaces, elements, mesh::TreeMesh2D)
913
# Exit early if there are no MPI interfaces to initialize
914
if nmpiinterfaces(mpi_interfaces) == 0
915
return nothing
916
end
917
918
# Reset interface count
919
count = 0
920
921
# Iterate over all elements to find neighbors and to connect via mpi_interfaces
922
for element in eachelement(elements)
923
# Get cell id
924
cell_id = elements.cell_ids[element]
925
926
# Loop over directions
927
for direction in eachdirection(mesh.tree)
928
# If no neighbor exists, current cell is small and thus we need a mortar
929
if !has_neighbor(mesh.tree, cell_id, direction)
930
continue
931
end
932
933
# Skip if neighbor has children
934
neighbor_cell_id = mesh.tree.neighbor_ids[direction, cell_id]
935
if has_children(mesh.tree, neighbor_cell_id)
936
continue
937
end
938
939
# Skip if neighbor is on this MPI rank -> create regular interface instead
940
if is_own_cell(mesh.tree, neighbor_cell_id)
941
continue
942
end
943
944
# Create interface between elements
945
count += 1
946
# Note: `local_neighbor_ids` stores the MPI-local neighbors,
947
# but with globally valid index!
948
mpi_interfaces.local_neighbor_ids[count] = element
949
950
if iseven(direction) # element is "left" of interface, remote cell is "right" of interface
951
mpi_interfaces.remote_sides[count] = 2
952
else
953
mpi_interfaces.remote_sides[count] = 1
954
end
955
956
# Set orientation (x -> 1, y -> 2)
957
if direction in (1, 2) # x-direction
958
mpi_interfaces.orientations[count] = 1
959
else # y-direction
960
mpi_interfaces.orientations[count] = 2
961
end
962
end
963
end
964
965
@assert count==nmpiinterfaces(mpi_interfaces) ("Actual interface count ($count) does not match "
966
*"expectations $(nmpiinterfaces(mpi_interfaces))")
967
end
968
969
# Container data structure (structure-of-arrays style) for DG L2 mortars
970
# Positions/directions for orientations = 1, large_sides = 2:
971
# mortar is orthogonal to x-axis, large side is in positive coordinate direction wrt mortar
972
# | |
973
# upper = 2 | |
974
# | |
975
# | 3 = large side
976
# | |
977
# lower = 1 | |
978
# | |
979
mutable struct TreeMPIL2MortarContainer2D{uEltype <: Real} <:
980
AbstractTreeL2MPIMortarContainer
981
u_upper::Array{uEltype, 4} # [leftright, variables, i, mortars]
982
u_lower::Array{uEltype, 4} # [leftright, variables, i, mortars]
983
# Note: `local_neighbor_ids` stores the MPI-local neighbors, but with globally valid index!
984
local_neighbor_ids::Vector{Vector{Int}} # [mortars][ids]
985
local_neighbor_positions::Vector{Vector{Int}} # [mortars][positions]
986
# Large sides: left -> 1, right -> 2
987
large_sides::Vector{Int} # [mortars]
988
orientations::Vector{Int} # [mortars]
989
# internal `resize!`able storage
990
_u_upper::Vector{uEltype}
991
_u_lower::Vector{uEltype}
992
end
993
994
# Return number of mortar nodes (L2 mortars are only h-adaptive, not p-adaptive)
995
@inline nnodes(mortars::TreeMPIL2MortarContainer2D) = size(mortars.u_upper, 3)
996
# Return number of equation variables
997
@inline nvariables(mortars::TreeMPIL2MortarContainer2D) = size(mortars.u_upper, 2)
998
999
# See explanation of Base.resize! for the element container
1000
function Base.resize!(mpi_mortars::TreeMPIL2MortarContainer2D, capacity)
1001
n_nodes = nnodes(mpi_mortars)
1002
n_variables = nvariables(mpi_mortars)
1003
@unpack _u_upper, _u_lower, local_neighbor_ids, local_neighbor_positions,
1004
large_sides, orientations = mpi_mortars
1005
1006
resize!(_u_upper, 2 * n_variables * n_nodes * capacity)
1007
mpi_mortars.u_upper = unsafe_wrap(Array, pointer(_u_upper),
1008
(2, n_variables, n_nodes, capacity))
1009
1010
resize!(_u_lower, 2 * n_variables * n_nodes * capacity)
1011
mpi_mortars.u_lower = unsafe_wrap(Array, pointer(_u_lower),
1012
(2, n_variables, n_nodes, capacity))
1013
1014
resize!(local_neighbor_ids, capacity)
1015
resize!(local_neighbor_positions, capacity)
1016
1017
resize!(large_sides, capacity)
1018
1019
resize!(orientations, capacity)
1020
1021
return nothing
1022
end
1023
1024
function TreeMPIL2MortarContainer2D{uEltype}(capacity::Integer, n_variables,
1025
n_nodes) where {uEltype <: Real}
1026
nan = convert(uEltype, NaN)
1027
1028
# Initialize fields with defaults
1029
_u_upper = fill(nan, 2 * n_variables * n_nodes * capacity)
1030
u_upper = unsafe_wrap(Array, pointer(_u_upper),
1031
(2, n_variables, n_nodes, capacity))
1032
1033
_u_lower = fill(nan, 2 * n_variables * n_nodes * capacity)
1034
u_lower = unsafe_wrap(Array, pointer(_u_lower),
1035
(2, n_variables, n_nodes, capacity))
1036
1037
local_neighbor_ids = fill(Vector{Int}(), capacity)
1038
local_neighbor_positions = fill(Vector{Int}(), capacity)
1039
1040
large_sides = fill(typemin(Int), capacity)
1041
1042
orientations = fill(typemin(Int), capacity)
1043
1044
return TreeMPIL2MortarContainer2D{uEltype}(u_upper, u_lower,
1045
local_neighbor_ids,
1046
local_neighbor_positions,
1047
large_sides, orientations,
1048
_u_upper, _u_lower)
1049
end
1050
1051
# Create MPI mortar container and initialize MPI mortar data in `elements`.
1052
function init_mpi_mortars(cell_ids, mesh::TreeMesh2D,
1053
elements::TreeElementContainer2D,
1054
::LobattoLegendreMortarL2)
1055
# Initialize containers
1056
n_mpi_mortars = count_required_mpi_mortars(mesh, cell_ids)
1057
mpi_mortars = TreeMPIL2MortarContainer2D{eltype(elements)}(n_mpi_mortars,
1058
nvariables(elements),
1059
nnodes(elements))
1060
1061
# Connect elements with mortars
1062
init_mpi_mortars!(mpi_mortars, elements, mesh)
1063
return mpi_mortars
1064
end
1065
1066
# Count the number of MPI mortars that need to be created
1067
function count_required_mpi_mortars(mesh::TreeMesh2D, cell_ids)
1068
# No MPI mortars needed if MPI is not used
1069
if !mpi_isparallel()
1070
return 0
1071
end
1072
1073
count = 0
1074
1075
for cell_id in cell_ids
1076
for direction in eachdirection(mesh.tree)
1077
# If no neighbor exists, cell is small with large neighbor or at boundary
1078
if !has_neighbor(mesh.tree, cell_id, direction)
1079
# If no large neighbor exists, cell is at boundary -> do nothing
1080
if !has_coarse_neighbor(mesh.tree, cell_id, direction)
1081
continue
1082
end
1083
1084
# Skip if the large neighbor is on the same rank to prevent double counting
1085
parent_id = mesh.tree.parent_ids[cell_id]
1086
large_cell_id = mesh.tree.neighbor_ids[direction, parent_id]
1087
if is_own_cell(mesh.tree, large_cell_id)
1088
continue
1089
end
1090
1091
# Current cell is small with large neighbor on a different rank, find the other
1092
# small cell
1093
if direction == 1 # small cells right, mortar in x-direction
1094
lower_cell_id = mesh.tree.child_ids[1, parent_id]
1095
upper_cell_id = mesh.tree.child_ids[3, parent_id]
1096
elseif direction == 2 # small cells left, mortar in x-direction
1097
lower_cell_id = mesh.tree.child_ids[2, parent_id]
1098
upper_cell_id = mesh.tree.child_ids[4, parent_id]
1099
elseif direction == 3 # small cells right, mortar in y-direction
1100
lower_cell_id = mesh.tree.child_ids[1, parent_id]
1101
upper_cell_id = mesh.tree.child_ids[2, parent_id]
1102
else # direction == 4, small cells left, mortar in y-direction
1103
lower_cell_id = mesh.tree.child_ids[3, parent_id]
1104
upper_cell_id = mesh.tree.child_ids[4, parent_id]
1105
end
1106
1107
if cell_id == lower_cell_id
1108
sibling_id = upper_cell_id
1109
elseif cell_id == upper_cell_id
1110
sibling_id = lower_cell_id
1111
else
1112
error("should not happen")
1113
end
1114
1115
# Skip if the other small cell is on the same rank and its id is smaller than the current
1116
# cell id to prevent double counting
1117
if is_own_cell(mesh.tree, sibling_id) && sibling_id < cell_id
1118
continue
1119
end
1120
else # Cell has a neighbor
1121
# If neighbor has no children, this is a conforming interface -> do nothing
1122
neighbor_id = mesh.tree.neighbor_ids[direction, cell_id]
1123
if !has_children(mesh.tree, neighbor_id)
1124
continue
1125
end
1126
1127
# Skip if both small cells are on this rank -> create regular mortar instead
1128
if direction == 1 # small cells left, mortar in x-direction
1129
lower_cell_id = mesh.tree.child_ids[2, neighbor_id]
1130
upper_cell_id = mesh.tree.child_ids[4, neighbor_id]
1131
elseif direction == 2 # small cells right, mortar in x-direction
1132
lower_cell_id = mesh.tree.child_ids[1, neighbor_id]
1133
upper_cell_id = mesh.tree.child_ids[3, neighbor_id]
1134
elseif direction == 3 # small cells left, mortar in y-direction
1135
lower_cell_id = mesh.tree.child_ids[3, neighbor_id]
1136
upper_cell_id = mesh.tree.child_ids[4, neighbor_id]
1137
else # direction == 4, small cells right, mortar in y-direction
1138
lower_cell_id = mesh.tree.child_ids[1, neighbor_id]
1139
upper_cell_id = mesh.tree.child_ids[2, neighbor_id]
1140
end
1141
small_cell_ids = (lower_cell_id, upper_cell_id)
1142
if all(cell -> is_own_cell(mesh.tree, cell), small_cell_ids)
1143
continue
1144
end
1145
end
1146
1147
count += 1
1148
end
1149
end
1150
1151
return count
1152
end
1153
1154
# Initialize connectivity between elements and mortars
1155
function init_mpi_mortars!(mpi_mortars, elements, mesh::TreeMesh2D)
1156
# Exit early if there are no MPI mortars to initialize
1157
if nmpimortars(mpi_mortars) == 0
1158
return nothing
1159
end
1160
1161
# Construct cell -> element mapping for easier algorithm implementation
1162
tree = mesh.tree
1163
c2e = zeros(Int, length(tree))
1164
for element in eachelement(elements)
1165
c2e[elements.cell_ids[element]] = element
1166
end
1167
1168
# Reset mortar count
1169
count = 0
1170
1171
# Iterate over all elements to find neighbors and to connect via mortars
1172
for element in eachelement(elements)
1173
cell_id = elements.cell_ids[element]
1174
1175
for direction in eachdirection(mesh.tree)
1176
# If no neighbor exists, cell is small with large neighbor or at boundary
1177
if !has_neighbor(mesh.tree, cell_id, direction)
1178
# If no large neighbor exists, cell is at boundary -> do nothing
1179
if !has_coarse_neighbor(mesh.tree, cell_id, direction)
1180
continue
1181
end
1182
1183
# Skip if the large neighbor is on the same rank -> will be handled in another iteration
1184
parent_cell_id = mesh.tree.parent_ids[cell_id]
1185
large_cell_id = mesh.tree.neighbor_ids[direction, parent_cell_id]
1186
if is_own_cell(mesh.tree, large_cell_id)
1187
continue
1188
end
1189
1190
# Current cell is small with large neighbor on a different rank, find the other
1191
# small cell
1192
if direction == 1 # small cells right, mortar in x-direction
1193
lower_cell_id = mesh.tree.child_ids[1, parent_cell_id]
1194
upper_cell_id = mesh.tree.child_ids[3, parent_cell_id]
1195
elseif direction == 2 # small cells left, mortar in x-direction
1196
lower_cell_id = mesh.tree.child_ids[2, parent_cell_id]
1197
upper_cell_id = mesh.tree.child_ids[4, parent_cell_id]
1198
elseif direction == 3 # small cells right, mortar in y-direction
1199
lower_cell_id = mesh.tree.child_ids[1, parent_cell_id]
1200
upper_cell_id = mesh.tree.child_ids[2, parent_cell_id]
1201
else # direction == 4, small cells left, mortar in y-direction
1202
lower_cell_id = mesh.tree.child_ids[3, parent_cell_id]
1203
upper_cell_id = mesh.tree.child_ids[4, parent_cell_id]
1204
end
1205
1206
if cell_id == lower_cell_id
1207
sibling_id = upper_cell_id
1208
elseif cell_id == upper_cell_id
1209
sibling_id = lower_cell_id
1210
else
1211
error("should not happen")
1212
end
1213
1214
# Skip if the other small cell is on the same rank and its id is smaller than the current
1215
# cell id to prevent double counting
1216
if is_own_cell(mesh.tree, sibling_id) && sibling_id < cell_id
1217
continue
1218
end
1219
else # Cell has a neighbor
1220
large_cell_id = cell_id # save explicitly for later processing
1221
1222
# If neighbor has no children, this is a conforming interface -> do nothing
1223
neighbor_cell_id = mesh.tree.neighbor_ids[direction, cell_id]
1224
if !has_children(mesh.tree, neighbor_cell_id)
1225
continue
1226
end
1227
1228
# Skip if both small cells are on this rank -> create regular mortar instead
1229
if direction == 1 # small cells left, mortar in x-direction
1230
lower_cell_id = mesh.tree.child_ids[2, neighbor_cell_id]
1231
upper_cell_id = mesh.tree.child_ids[4, neighbor_cell_id]
1232
elseif direction == 2 # small cells right, mortar in x-direction
1233
lower_cell_id = mesh.tree.child_ids[1, neighbor_cell_id]
1234
upper_cell_id = mesh.tree.child_ids[3, neighbor_cell_id]
1235
elseif direction == 3 # small cells left, mortar in y-direction
1236
lower_cell_id = mesh.tree.child_ids[3, neighbor_cell_id]
1237
upper_cell_id = mesh.tree.child_ids[4, neighbor_cell_id]
1238
else # direction == 4, small cells right, mortar in y-direction
1239
lower_cell_id = mesh.tree.child_ids[1, neighbor_cell_id]
1240
upper_cell_id = mesh.tree.child_ids[2, neighbor_cell_id]
1241
end
1242
small_cell_ids = (lower_cell_id, upper_cell_id)
1243
if all(cell -> is_own_cell(mesh.tree, cell), small_cell_ids)
1244
continue
1245
end
1246
end
1247
1248
# Create mortar between elements:
1249
# 1 -> small element in negative coordinate direction
1250
# 2 -> small element in positive coordinate direction
1251
# 3 -> large element
1252
count += 1
1253
1254
# Note: `local_neighbor_ids` stores the MPI-local neighbors,
1255
# but with globally valid index!
1256
local_neighbor_ids = Vector{Int}()
1257
local_neighbor_positions = Vector{Int}()
1258
if is_own_cell(mesh.tree, lower_cell_id)
1259
push!(local_neighbor_ids, c2e[lower_cell_id])
1260
push!(local_neighbor_positions, 1)
1261
end
1262
if is_own_cell(mesh.tree, upper_cell_id)
1263
push!(local_neighbor_ids, c2e[upper_cell_id])
1264
push!(local_neighbor_positions, 2)
1265
end
1266
if is_own_cell(mesh.tree, large_cell_id)
1267
push!(local_neighbor_ids, c2e[large_cell_id])
1268
push!(local_neighbor_positions, 3)
1269
end
1270
1271
mpi_mortars.local_neighbor_ids[count] = local_neighbor_ids
1272
mpi_mortars.local_neighbor_positions[count] = local_neighbor_positions
1273
1274
# Set large side, which denotes the direction (1 -> negative, 2 -> positive) of the large side
1275
# To prevent double counting, the mortars are always identified from the point of view of
1276
# a large cell, if it is on this rank. In that case, direction points towards the small cells.
1277
# If the large cell is not on this rank, the point of view of a small cell is taken instead,
1278
# hence direction points towards the large cell in this case.
1279
if iseven(direction)
1280
mpi_mortars.large_sides[count] = is_own_cell(mesh.tree, large_cell_id) ?
1281
1 : 2
1282
else
1283
mpi_mortars.large_sides[count] = is_own_cell(mesh.tree, large_cell_id) ?
1284
2 : 1
1285
end
1286
1287
# Set orientation (1, 2 -> x; 3, 4 -> y)
1288
if direction in (1, 2)
1289
mpi_mortars.orientations[count] = 1
1290
else
1291
mpi_mortars.orientations[count] = 2
1292
end
1293
end
1294
end
1295
1296
return nothing
1297
end
1298
1299
# Container data structure (structure-of-arrays style) for FCT-type antidiffusive fluxes
1300
# (i, j+1)
1301
# |
1302
# flux2(i, j+1)
1303
# |
1304
# (i-1, j) ---flux1(i, j)--- (i, j) ---flux1(i+1, j)--- (i+1, j)
1305
# |
1306
# flux2(i, j)
1307
# |
1308
# (i, j-1)
1309
mutable struct ContainerAntidiffusiveFlux2D{uEltype <: Real} <:
1310
AbstractContainerAntidiffusiveFlux
1311
antidiffusive_flux1_L::Array{uEltype, 4} # [variables, i, j, elements]
1312
antidiffusive_flux1_R::Array{uEltype, 4} # [variables, i, j, elements]
1313
antidiffusive_flux2_L::Array{uEltype, 4} # [variables, i, j, elements]
1314
antidiffusive_flux2_R::Array{uEltype, 4} # [variables, i, j, elements]
1315
# internal `resize!`able storage
1316
_antidiffusive_flux1_L::Vector{uEltype}
1317
_antidiffusive_flux1_R::Vector{uEltype}
1318
_antidiffusive_flux2_L::Vector{uEltype}
1319
_antidiffusive_flux2_R::Vector{uEltype}
1320
end
1321
1322
function ContainerAntidiffusiveFlux2D{uEltype}(capacity::Integer, n_variables,
1323
n_nodes) where {uEltype <: Real}
1324
nan_uEltype = convert(uEltype, NaN)
1325
1326
# Initialize fields with defaults
1327
_antidiffusive_flux1_L = fill(nan_uEltype,
1328
n_variables * (n_nodes + 1) * n_nodes * capacity)
1329
antidiffusive_flux1_L = unsafe_wrap(Array, pointer(_antidiffusive_flux1_L),
1330
(n_variables, n_nodes + 1, n_nodes, capacity))
1331
_antidiffusive_flux1_R = fill(nan_uEltype,
1332
n_variables * (n_nodes + 1) * n_nodes * capacity)
1333
antidiffusive_flux1_R = unsafe_wrap(Array, pointer(_antidiffusive_flux1_R),
1334
(n_variables, n_nodes + 1, n_nodes, capacity))
1335
1336
_antidiffusive_flux2_L = fill(nan_uEltype,
1337
n_variables * n_nodes * (n_nodes + 1) * capacity)
1338
antidiffusive_flux2_L = unsafe_wrap(Array, pointer(_antidiffusive_flux2_L),
1339
(n_variables, n_nodes, n_nodes + 1, capacity))
1340
_antidiffusive_flux2_R = fill(nan_uEltype,
1341
n_variables * n_nodes * (n_nodes + 1) * capacity)
1342
antidiffusive_flux2_R = unsafe_wrap(Array, pointer(_antidiffusive_flux2_R),
1343
(n_variables, n_nodes, n_nodes + 1, capacity))
1344
1345
reset_antidiffusive_fluxes!(antidiffusive_flux1_L, antidiffusive_flux1_R,
1346
antidiffusive_flux2_L, antidiffusive_flux2_R)
1347
1348
return ContainerAntidiffusiveFlux2D{uEltype}(antidiffusive_flux1_L,
1349
antidiffusive_flux1_R,
1350
antidiffusive_flux2_L,
1351
antidiffusive_flux2_R,
1352
_antidiffusive_flux1_L,
1353
_antidiffusive_flux1_R,
1354
_antidiffusive_flux2_L,
1355
_antidiffusive_flux2_R)
1356
end
1357
1358
# Only one-dimensional `Array`s are `resize!`able in Julia.
1359
# Hence, we use `Vector`s as internal storage and `resize!`
1360
# them whenever needed. Then, we reuse the same memory by
1361
# `unsafe_wrap`ping multi-dimensional `Array`s around the
1362
# internal storage.
1363
function Base.resize!(fluxes::ContainerAntidiffusiveFlux2D, capacity)
1364
n_nodes = nnodes(fluxes)
1365
n_variables = nvariables(fluxes)
1366
1367
@unpack _antidiffusive_flux1_L, _antidiffusive_flux2_L, _antidiffusive_flux1_R, _antidiffusive_flux2_R = fluxes
1368
1369
resize!(_antidiffusive_flux1_L, n_variables * (n_nodes + 1) * n_nodes * capacity)
1370
fluxes.antidiffusive_flux1_L = unsafe_wrap(Array, pointer(_antidiffusive_flux1_L),
1371
(n_variables, n_nodes + 1, n_nodes,
1372
capacity))
1373
resize!(_antidiffusive_flux1_R, n_variables * (n_nodes + 1) * n_nodes * capacity)
1374
fluxes.antidiffusive_flux1_R = unsafe_wrap(Array, pointer(_antidiffusive_flux1_R),
1375
(n_variables, n_nodes + 1, n_nodes,
1376
capacity))
1377
resize!(_antidiffusive_flux2_L, n_variables * n_nodes * (n_nodes + 1) * capacity)
1378
fluxes.antidiffusive_flux2_L = unsafe_wrap(Array, pointer(_antidiffusive_flux2_L),
1379
(n_variables, n_nodes, n_nodes + 1,
1380
capacity))
1381
resize!(_antidiffusive_flux2_R, n_variables * n_nodes * (n_nodes + 1) * capacity)
1382
fluxes.antidiffusive_flux2_R = unsafe_wrap(Array, pointer(_antidiffusive_flux2_R),
1383
(n_variables, n_nodes, n_nodes + 1,
1384
capacity))
1385
1386
return nothing
1387
end
1388
1389
function reset_antidiffusive_fluxes!(antidiffusive_flux1_L, antidiffusive_flux1_R,
1390
antidiffusive_flux2_L, antidiffusive_flux2_R)
1391
uEltype = eltype(antidiffusive_flux1_L)
1392
@threaded for element in axes(antidiffusive_flux1_L, 4)
1393
antidiffusive_flux1_L[:, 1, :, element] .= zero(uEltype)
1394
antidiffusive_flux1_L[:, end, :, element] .= zero(uEltype)
1395
antidiffusive_flux1_R[:, 1, :, element] .= zero(uEltype)
1396
antidiffusive_flux1_R[:, end, :, element] .= zero(uEltype)
1397
1398
antidiffusive_flux2_L[:, :, 1, element] .= zero(uEltype)
1399
antidiffusive_flux2_L[:, :, end, element] .= zero(uEltype)
1400
antidiffusive_flux2_R[:, :, 1, element] .= zero(uEltype)
1401
antidiffusive_flux2_R[:, :, end, element] .= zero(uEltype)
1402
end
1403
1404
return nothing
1405
end
1406
1407
function reinitialize_containers!(mesh::Union{TreeMesh{2}, TreeMesh{3}}, equations,
1408
dg::DGSEM, cache)
1409
# Get new list of leaf cells
1410
leaf_cell_ids = local_leaf_cells(mesh.tree)
1411
n_cells = length(leaf_cell_ids)
1412
1413
# re-initialize elements container
1414
@unpack elements = cache
1415
resize!(elements, n_cells)
1416
init_elements!(elements, leaf_cell_ids, mesh, dg.basis)
1417
1418
# Resize volume integral and related datastructures
1419
@unpack volume_integral = dg
1420
resize_volume_integral_cache!(cache, mesh, volume_integral, n_cells)
1421
reinit_volume_integral_cache!(cache, mesh, dg, volume_integral, n_cells)
1422
1423
# re-initialize interfaces container
1424
@unpack interfaces = cache
1425
resize!(interfaces, count_required_interfaces(mesh, leaf_cell_ids))
1426
init_interfaces!(interfaces, elements, mesh)
1427
1428
# re-initialize boundaries container
1429
@unpack boundaries = cache
1430
resize!(boundaries, count_required_boundaries(mesh, leaf_cell_ids))
1431
init_boundaries!(boundaries, elements, mesh, dg.basis)
1432
1433
# re-initialize mortars container
1434
@unpack mortars = cache
1435
resize!(mortars, count_required_mortars(mesh, leaf_cell_ids))
1436
init_mortars!(mortars, elements, mesh)
1437
1438
if mpi_isparallel() # currently only implemented for 2D
1439
# re-initialize mpi_interfaces container
1440
@unpack mpi_interfaces = cache
1441
resize!(mpi_interfaces, count_required_mpi_interfaces(mesh, leaf_cell_ids))
1442
init_mpi_interfaces!(mpi_interfaces, elements, mesh)
1443
1444
# re-initialize mpi_mortars container
1445
@unpack mpi_mortars = cache
1446
resize!(mpi_mortars, count_required_mpi_mortars(mesh, leaf_cell_ids))
1447
init_mpi_mortars!(mpi_mortars, elements, mesh)
1448
1449
# re-initialize mpi cache
1450
@unpack mpi_cache = cache
1451
init_mpi_cache!(mpi_cache, mesh, elements, mpi_interfaces, mpi_mortars,
1452
nvariables(equations), nnodes(dg), eltype(elements))
1453
end
1454
1455
return nothing
1456
end
1457
end # @muladd
1458
1459