Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
trixi-framework
GitHub Repository: trixi-framework/Trixi.jl
Path: blob/main/src/solvers/dgsem_p4est/containers.jl
5616 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
mutable struct P4estElementContainer{NDIMS, RealT <: Real, uEltype <: Real,
9
NDIMSP1, NDIMSP2, NDIMSP3,
10
ArrayRealTNDIMSP1 <: DenseArray{RealT, NDIMSP1},
11
ArrayRealTNDIMSP2 <: DenseArray{RealT, NDIMSP2},
12
ArrayRealTNDIMSP3 <: DenseArray{RealT, NDIMSP3},
13
VectorRealT <: DenseVector{RealT},
14
ArrayuEltypeNDIMSP2 <:
15
DenseArray{uEltype, NDIMSP2},
16
VectoruEltype <: DenseVector{uEltype}} <:
17
AbstractElementContainer
18
# Physical coordinates at each node
19
node_coordinates::ArrayRealTNDIMSP2 # [orientation, node_i, node_j, node_k, element]
20
21
# Jacobian matrix of the transformation
22
# [jacobian_i, jacobian_j, node_i, node_j, node_k, element] where jacobian_i is the first index of the Jacobian matrix
23
jacobian_matrix::ArrayRealTNDIMSP3
24
25
# Contravariant vectors, scaled by J, in Kopriva's blue book called Ja^i_n (i index, n dimension)
26
contravariant_vectors::ArrayRealTNDIMSP3 # [dimension, index, node_i, node_j, node_k, element]
27
28
# 1/J where J is the Jacobian determinant (determinant of Jacobian matrix)
29
inverse_jacobian::ArrayRealTNDIMSP1 # [node_i, node_j, node_k, element]
30
31
# Buffer for calculated surface flux
32
surface_flux_values::ArrayuEltypeNDIMSP2 # [variable, i, j, direction, element]
33
34
# internal `resize!`able storage
35
_node_coordinates::VectorRealT
36
_jacobian_matrix::VectorRealT
37
_contravariant_vectors::VectorRealT
38
_inverse_jacobian::VectorRealT
39
_surface_flux_values::VectoruEltype
40
end
41
42
@inline function nelements(elements::P4estElementContainer)
43
return size(elements.node_coordinates, ndims(elements) + 2)
44
end
45
@inline Base.ndims(::P4estElementContainer{NDIMS}) where {NDIMS} = NDIMS
46
@inline function Base.eltype(::P4estElementContainer{NDIMS, RealT, uEltype}) where {
47
NDIMS,
48
RealT,
49
uEltype
50
}
51
return uEltype
52
end
53
54
# Only one-dimensional `Array`s are `resize!`able in Julia.
55
# Hence, we use `Vector`s as internal storage and `resize!`
56
# them whenever needed. Then, we reuse the same memory by
57
# `unsafe_wrap`ping multi-dimensional `Array`s around the
58
# internal storage.
59
function Base.resize!(elements::P4estElementContainer, capacity)
60
@unpack _node_coordinates, _jacobian_matrix, _contravariant_vectors,
61
_inverse_jacobian, _surface_flux_values = elements
62
63
n_dims = ndims(elements)
64
n_nodes = size(elements.node_coordinates, 2)
65
n_variables = size(elements.surface_flux_values, 1)
66
ArrayType = storage_type(elements)
67
68
resize!(_node_coordinates, n_dims * n_nodes^n_dims * capacity)
69
elements.node_coordinates = unsafe_wrap_or_alloc(ArrayType,
70
_node_coordinates,
71
(n_dims,
72
ntuple(_ -> n_nodes, n_dims)...,
73
capacity))
74
75
resize!(_jacobian_matrix, n_dims^2 * n_nodes^n_dims * capacity)
76
elements.jacobian_matrix = unsafe_wrap_or_alloc(ArrayType,
77
_jacobian_matrix,
78
(n_dims, n_dims,
79
ntuple(_ -> n_nodes, n_dims)...,
80
capacity))
81
82
resize!(_contravariant_vectors, length(_jacobian_matrix))
83
elements.contravariant_vectors = unsafe_wrap_or_alloc(ArrayType,
84
_contravariant_vectors,
85
size(elements.jacobian_matrix))
86
87
resize!(_inverse_jacobian, n_nodes^n_dims * capacity)
88
elements.inverse_jacobian = unsafe_wrap_or_alloc(ArrayType,
89
_inverse_jacobian,
90
(ntuple(_ -> n_nodes, n_dims)...,
91
capacity))
92
93
resize!(_surface_flux_values,
94
n_variables * n_nodes^(n_dims - 1) * (n_dims * 2) * capacity)
95
elements.surface_flux_values = unsafe_wrap_or_alloc(ArrayType,
96
_surface_flux_values,
97
(n_variables,
98
ntuple(_ -> n_nodes,
99
n_dims - 1)...,
100
n_dims * 2, capacity))
101
102
return nothing
103
end
104
105
# Create element container and initialize element data
106
function init_elements(mesh::Union{P4estMesh{NDIMS, NDIMS, RealT},
107
P4estMeshView{NDIMS, NDIMS, RealT},
108
T8codeMesh{NDIMS, RealT}},
109
equations,
110
basis,
111
::Type{uEltype}) where {NDIMS, RealT <: Real, uEltype <: Real}
112
nelements = ncells(mesh)
113
114
_node_coordinates = Vector{RealT}(undef, NDIMS * nnodes(basis)^NDIMS * nelements)
115
node_coordinates = unsafe_wrap(Array, pointer(_node_coordinates),
116
(NDIMS, ntuple(_ -> nnodes(basis), NDIMS)...,
117
nelements))
118
119
_jacobian_matrix = Vector{RealT}(undef, NDIMS^2 * nnodes(basis)^NDIMS * nelements)
120
jacobian_matrix = unsafe_wrap(Array, pointer(_jacobian_matrix),
121
(NDIMS, NDIMS, ntuple(_ -> nnodes(basis), NDIMS)...,
122
nelements))
123
124
_contravariant_vectors = similar(_jacobian_matrix)
125
contravariant_vectors = unsafe_wrap(Array, pointer(_contravariant_vectors),
126
size(jacobian_matrix))
127
128
_inverse_jacobian = Vector{RealT}(undef, nnodes(basis)^NDIMS * nelements)
129
inverse_jacobian = unsafe_wrap(Array, pointer(_inverse_jacobian),
130
(ntuple(_ -> nnodes(basis), NDIMS)..., nelements))
131
132
_surface_flux_values = Vector{uEltype}(undef,
133
nvariables(equations) *
134
nnodes(basis)^(NDIMS - 1) * (NDIMS * 2) *
135
nelements)
136
surface_flux_values = unsafe_wrap(Array, pointer(_surface_flux_values),
137
(nvariables(equations),
138
ntuple(_ -> nnodes(basis), NDIMS - 1)...,
139
NDIMS * 2, nelements))
140
141
elements = P4estElementContainer{NDIMS, RealT, uEltype,
142
NDIMS + 1, NDIMS + 2, NDIMS + 3,
143
Array{RealT, NDIMS + 1},
144
Array{RealT, NDIMS + 2},
145
Array{RealT, NDIMS + 3},
146
Vector{RealT},
147
Array{uEltype, NDIMS + 2}, Vector{uEltype}}(node_coordinates,
148
jacobian_matrix,
149
contravariant_vectors,
150
inverse_jacobian,
151
surface_flux_values,
152
_node_coordinates,
153
_jacobian_matrix,
154
_contravariant_vectors,
155
_inverse_jacobian,
156
_surface_flux_values)
157
158
init_elements!(elements, mesh, basis)
159
return elements
160
end
161
162
function Adapt.parent_type(::Type{<:P4estElementContainer{<:Any, <:Any, <:Any, <:Any,
163
<:Any, <:Any, ArrayT}}) where {ArrayT}
164
return ArrayT
165
end
166
167
# Manual adapt_structure since we have aliasing memory
168
function Adapt.adapt_structure(to,
169
elements::P4estElementContainer{NDIMS}) where {NDIMS}
170
# Adapt underlying storage
171
_node_coordinates = adapt(to, elements._node_coordinates)
172
_jacobian_matrix = adapt(to, elements._jacobian_matrix)
173
_contravariant_vectors = adapt(to, elements._contravariant_vectors)
174
_inverse_jacobian = adapt(to, elements._inverse_jacobian)
175
_surface_flux_values = adapt(to, elements._surface_flux_values)
176
177
RealT = eltype(_inverse_jacobian)
178
uEltype = eltype(_surface_flux_values)
179
180
# Wrap arrays again
181
node_coordinates = unsafe_wrap_or_alloc(to, _node_coordinates,
182
size(elements.node_coordinates))
183
jacobian_matrix = unsafe_wrap_or_alloc(to, _jacobian_matrix,
184
size(elements.jacobian_matrix))
185
contravariant_vectors = unsafe_wrap_or_alloc(to, _contravariant_vectors,
186
size(jacobian_matrix))
187
inverse_jacobian = unsafe_wrap_or_alloc(to, _inverse_jacobian,
188
size(elements.inverse_jacobian))
189
surface_flux_values = unsafe_wrap_or_alloc(to, _surface_flux_values,
190
size(elements.surface_flux_values))
191
192
new_type_params = (NDIMS,
193
RealT, uEltype,
194
NDIMS + 1, NDIMS + 2, NDIMS + 3,
195
typeof(inverse_jacobian), # ArrayRealTNDIMSP1
196
typeof(node_coordinates), # ArrayRealTNDIMSP2
197
typeof(jacobian_matrix), # ArrayRealTNDIMSP3
198
typeof(_node_coordinates), # VectorRealT
199
typeof(surface_flux_values), # ArrayuEltypeNDIMSP2
200
typeof(_surface_flux_values)) # VectoruEltype
201
return P4estElementContainer{new_type_params...}(node_coordinates,
202
jacobian_matrix,
203
contravariant_vectors,
204
inverse_jacobian,
205
surface_flux_values,
206
_node_coordinates,
207
_jacobian_matrix,
208
_contravariant_vectors,
209
_inverse_jacobian,
210
_surface_flux_values)
211
end
212
213
mutable struct P4estInterfaceContainer{NDIMS, RealT <: Real, uEltype <: Real,
214
NDIMSP1, NDIMSP2,
215
uArray <: DenseArray{uEltype, NDIMSP2},
216
NormalArray <:
217
Union{DenseArray{RealT, NDIMSP1}, Nothing},
218
IdsMatrix <: DenseMatrix{Int},
219
IndicesMatrix <:
220
DenseMatrix{NTuple{NDIMS, Symbol}},
221
uVector <: DenseVector{uEltype},
222
NormalVector <:
223
Union{DenseVector{RealT}, Nothing},
224
IdsVector <: DenseVector{Int},
225
IndicesVector <:
226
DenseVector{NTuple{NDIMS, Symbol}}} <:
227
AbstractInterfaceContainer
228
u::uArray # [primary/secondary, variable, i, j, interface]
229
normal_directions::NormalArray # [dimension, i, j, interface]
230
neighbor_ids::IdsMatrix # [primary/secondary, interface]
231
node_indices::IndicesMatrix # [primary/secondary, interface]
232
233
# internal `resize!`able storage
234
_u::uVector
235
_normal_directions::NormalVector
236
_neighbor_ids::IdsVector
237
_node_indices::IndicesVector
238
end
239
240
# `trivial` means that the interface normals can be taken from
241
# the outer/surface element nodes
242
@inline trivial_interface_normals(::LobattoLegendreBasis) = true
243
# For Gauss-Legendre basis, the interface normals need to be interpolated,
244
# analogous to the surface values.
245
@inline trivial_interface_normals(::GaussLegendreBasis) = false
246
247
@inline function ninterfaces(interfaces::P4estInterfaceContainer)
248
return size(interfaces.neighbor_ids, 2)
249
end
250
@inline Base.ndims(::P4estInterfaceContainer{NDIMS}) where {NDIMS} = NDIMS
251
252
# See explanation of Base.resize! for the element container
253
function Base.resize!(interfaces::P4estInterfaceContainer, capacity)
254
@unpack _u, _normal_directions, _neighbor_ids, _node_indices = interfaces
255
256
n_dims = ndims(interfaces)
257
n_nodes = size(interfaces.u, 3)
258
n_variables = size(interfaces.u, 2)
259
ArrayType = storage_type(interfaces)
260
261
resize!(_u, 2 * n_variables * n_nodes^(n_dims - 1) * capacity)
262
interfaces.u = unsafe_wrap(ArrayType, pointer(_u),
263
(2, n_variables, ntuple(_ -> n_nodes, n_dims - 1)...,
264
capacity))
265
266
if _normal_directions === nothing # trivial interface normals, e.g. for Lobatto-Legendre basis
267
interfaces.normal_directions = nothing
268
else # non-trivial interface normals, e.g. for Gauss-Legendre basis
269
resize!(_normal_directions, n_dims * n_nodes^(n_dims - 1) * capacity)
270
interfaces.normal_directions = unsafe_wrap(ArrayType,
271
pointer(_normal_directions),
272
(n_dims,
273
ntuple(_ -> n_nodes,
274
n_dims - 1)...,
275
capacity))
276
end
277
278
resize!(_neighbor_ids, 2 * capacity)
279
interfaces.neighbor_ids = unsafe_wrap(ArrayType, pointer(_neighbor_ids),
280
(2, capacity))
281
282
resize!(_node_indices, 2 * capacity)
283
interfaces.node_indices = unsafe_wrap(ArrayType, pointer(_node_indices),
284
(2, capacity))
285
286
return nothing
287
end
288
289
# Create interface container and initialize interface data.
290
function init_interfaces(mesh::Union{P4estMesh, P4estMeshView, T8codeMesh}, equations,
291
basis, elements)
292
NDIMS = ndims(elements)
293
RealT = real(mesh)
294
uEltype = eltype(elements)
295
296
# Initialize container
297
n_interfaces = count_required_surfaces(mesh).interfaces
298
299
_u = Vector{uEltype}(undef,
300
2 * nvariables(equations) * nnodes(basis)^(NDIMS - 1) *
301
n_interfaces)
302
u = unsafe_wrap(Array, pointer(_u),
303
(2, nvariables(equations), ntuple(_ -> nnodes(basis), NDIMS - 1)...,
304
n_interfaces))
305
306
if !trivial_interface_normals(basis)
307
_normal_directions = Vector{RealT}(undef,
308
NDIMS * nnodes(basis)^(NDIMS - 1) *
309
n_interfaces)
310
normal_directions = unsafe_wrap(Array, pointer(_normal_directions),
311
(NDIMS,
312
ntuple(_ -> nnodes(basis), NDIMS - 1)...,
313
n_interfaces))
314
else
315
_normal_directions = nothing
316
normal_directions = nothing
317
end
318
319
_neighbor_ids = Vector{Int}(undef, 2 * n_interfaces)
320
neighbor_ids = unsafe_wrap(Array, pointer(_neighbor_ids), (2, n_interfaces))
321
322
_node_indices = Vector{NTuple{NDIMS, Symbol}}(undef, 2 * n_interfaces)
323
node_indices = unsafe_wrap(Array, pointer(_node_indices), (2, n_interfaces))
324
325
interfaces = P4estInterfaceContainer{NDIMS, RealT, uEltype,
326
NDIMS + 1, NDIMS + 2,
327
typeof(u), typeof(normal_directions),
328
typeof(neighbor_ids), typeof(node_indices),
329
typeof(_u), typeof(_normal_directions),
330
typeof(_neighbor_ids), typeof(_node_indices)}(u,
331
normal_directions,
332
neighbor_ids,
333
node_indices,
334
_u,
335
_normal_directions,
336
_neighbor_ids,
337
_node_indices)
338
339
init_interfaces!(interfaces, elements, mesh, basis)
340
341
return interfaces
342
end
343
344
function init_interfaces!(interfaces, elements,
345
mesh::Union{P4estMesh, P4estMeshView}, basis)
346
init_surfaces!(interfaces, nothing, nothing, mesh)
347
init_normal_directions!(interfaces, basis, elements)
348
349
return interfaces
350
end
351
352
function Adapt.parent_type(::Type{<:P4estInterfaceContainer{<:Any, <:Any, <:Any,
353
<:Any, <:Any,
354
ArrayT}}) where {ArrayT}
355
return ArrayT
356
end
357
358
# Manual adapt_structure since we have aliasing memory
359
function Adapt.adapt_structure(to,
360
interfaces::P4estInterfaceContainer{NDIMS,
361
RealT,
362
uEltype}) where {
363
NDIMS,
364
uEltype,
365
RealT
366
}
367
# Adapt underlying storage
368
_u = adapt(to, interfaces._u)
369
_normal_directions = interfaces._normal_directions === nothing ? nothing :
370
adapt(to, interfaces._normal_directions)
371
_neighbor_ids = adapt(to, interfaces._neighbor_ids)
372
_node_indices = adapt(to, interfaces._node_indices)
373
# Wrap arrays again
374
u = unsafe_wrap_or_alloc(to, _u, size(interfaces.u))
375
normal_directions = _normal_directions === nothing ? nothing : # Check if interface normals are trivial
376
unsafe_wrap_or_alloc(to, _normal_directions,
377
size(interfaces.normal_directions))
378
neighbor_ids = unsafe_wrap_or_alloc(to, _neighbor_ids,
379
size(interfaces.neighbor_ids))
380
node_indices = unsafe_wrap_or_alloc(to, _node_indices,
381
size(interfaces.node_indices))
382
383
new_type_params = (NDIMS,
384
RealT, eltype(_u),
385
NDIMS + 1, NDIMS + 2,
386
typeof(u), typeof(normal_directions),
387
typeof(neighbor_ids), typeof(node_indices),
388
typeof(_u), typeof(_normal_directions),
389
typeof(_neighbor_ids), typeof(_node_indices))
390
return P4estInterfaceContainer{new_type_params...}(u, normal_directions,
391
neighbor_ids, node_indices,
392
_u, _normal_directions,
393
_neighbor_ids, _node_indices)
394
end
395
396
mutable struct P4estBoundaryContainer{NDIMS, uEltype <: Real, NDIMSP1,
397
uArray <: DenseArray{uEltype, NDIMSP1},
398
IdsVector <: DenseVector{Int},
399
IndicesVector <:
400
DenseVector{NTuple{NDIMS, Symbol}},
401
uVector <: DenseVector{uEltype}} <:
402
AbstractBoundaryContainer
403
u::uArray # [variables, i, j, boundary]
404
neighbor_ids::IdsVector # [boundary]
405
node_indices::IndicesVector # [boundary]
406
name::Vector{Symbol} # [boundary]
407
408
# internal `resize!`able storage
409
_u::uVector
410
end
411
412
@inline function nboundaries(boundaries::P4estBoundaryContainer)
413
return length(boundaries.neighbor_ids)
414
end
415
@inline Base.ndims(::P4estBoundaryContainer{NDIMS}) where {NDIMS} = NDIMS
416
@inline function Base.eltype(::P4estBoundaryContainer{NDIMS, uEltype}) where {NDIMS,
417
uEltype}
418
return uEltype
419
end
420
421
# See explanation of Base.resize! for the element container
422
function Base.resize!(boundaries::P4estBoundaryContainer, capacity)
423
@unpack _u, neighbor_ids, node_indices, name = boundaries
424
425
n_dims = ndims(boundaries)
426
n_nodes = size(boundaries.u, 2)
427
n_variables = size(boundaries.u, 1)
428
ArrayType = storage_type(boundaries)
429
430
resize!(_u, n_variables * n_nodes^(n_dims - 1) * capacity)
431
boundaries.u = unsafe_wrap(ArrayType, pointer(_u),
432
(n_variables, ntuple(_ -> n_nodes, n_dims - 1)...,
433
capacity))
434
435
resize!(neighbor_ids, capacity)
436
437
resize!(node_indices, capacity)
438
439
resize!(name, capacity)
440
441
return nothing
442
end
443
444
# Create interface container and initialize interface data in `elements`.
445
function init_boundaries(mesh::Union{P4estMesh, P4estMeshView, T8codeMesh}, equations,
446
basis, elements)
447
NDIMS = ndims(elements)
448
uEltype = eltype(elements)
449
450
# Initialize container
451
n_boundaries = count_required_surfaces(mesh).boundaries
452
453
_u = Vector{uEltype}(undef,
454
nvariables(equations) * nnodes(basis)^(NDIMS - 1) *
455
n_boundaries)
456
u = unsafe_wrap(Array, pointer(_u),
457
(nvariables(equations), ntuple(_ -> nnodes(basis), NDIMS - 1)...,
458
n_boundaries))
459
460
neighbor_ids = Vector{Int}(undef, n_boundaries)
461
node_indices = Vector{NTuple{NDIMS, Symbol}}(undef, n_boundaries)
462
names = Vector{Symbol}(undef, n_boundaries)
463
464
boundaries = P4estBoundaryContainer{NDIMS, uEltype, NDIMS + 1, typeof(u),
465
typeof(neighbor_ids), typeof(node_indices),
466
typeof(_u)}(u, neighbor_ids,
467
node_indices, names,
468
_u)
469
470
if n_boundaries > 0
471
init_boundaries!(boundaries, mesh)
472
end
473
474
return boundaries
475
end
476
477
function init_boundaries!(boundaries, mesh::Union{P4estMesh, P4estMeshView})
478
init_surfaces!(nothing, nothing, boundaries, mesh)
479
480
return boundaries
481
end
482
483
# Function barrier for type stability
484
function init_boundaries_iter_face_inner(info_pw, boundaries, boundary_id, mesh)
485
# Extract boundary data
486
side_pw = load_pointerwrapper_side(info_pw)
487
# Get local tree, one-based indexing
488
tree_pw = load_pointerwrapper_tree(mesh.p4est, side_pw.treeid[] + 1)
489
# Quadrant numbering offset of this quadrant
490
offset = tree_pw.quadrants_offset[]
491
492
# Verify before accessing is.full, but this should never happen
493
@assert side_pw.is_hanging[] == false
494
495
local_quad_id = side_pw.is.full.quadid[]
496
# Global ID of this quad
497
quad_id = offset + local_quad_id
498
499
# Write data to boundaries container
500
# `p4est` uses zero-based indexing; convert to one-based indexing
501
boundaries.neighbor_ids[boundary_id] = quad_id + 1
502
503
# Face at which the boundary lies
504
face = side_pw.face[]
505
506
# Save boundaries.node_indices dimension specific in containers_[23]d.jl
507
init_boundary_node_indices!(boundaries, face, boundary_id)
508
509
# One-based indexing
510
boundaries.name[boundary_id] = mesh.boundary_names[face + 1, side_pw.treeid[] + 1]
511
512
return nothing
513
end
514
515
function Adapt.parent_type(::Type{<:P4estBoundaryContainer{<:Any, <:Any, <:Any, ArrayT}}) where {ArrayT}
516
return ArrayT
517
end
518
519
# Manual adapt_structure since we have aliasing memory
520
function Adapt.adapt_structure(to, boundaries::P4estBoundaryContainer)
521
_u = adapt(to, boundaries._u)
522
u = unsafe_wrap_or_alloc(to, _u, size(boundaries.u))
523
neighbor_ids = adapt(to, boundaries.neighbor_ids)
524
node_indices = adapt(to, boundaries.node_indices)
525
name = boundaries.name
526
527
NDIMS = ndims(boundaries)
528
return P4estBoundaryContainer{NDIMS, eltype(_u), NDIMS + 1, typeof(u),
529
typeof(neighbor_ids), typeof(node_indices),
530
typeof(_u)}(u, neighbor_ids, node_indices,
531
name, _u)
532
end
533
534
# Container data structure (structure-of-arrays style) for DG L2 mortars
535
#
536
# The positions used in `neighbor_ids` are 1:3 (in 2D) or 1:5 (in 3D), where 1:2 (in 2D)
537
# or 1:4 (in 3D) are the small elements numbered in z-order and 3 or 5 is the large element.
538
# The solution values on the mortar element are saved in `u`, where `position` is the number
539
# of the small element that corresponds to the respective part of the mortar element.
540
# The first dimension `small/large side` takes 1 for small side and 2 for large side.
541
#
542
# Illustration of the positions in `neighbor_ids` in 3D, where ξ and η are the local coordinates
543
# of the mortar element, which are precisely the local coordinates that span
544
# the surface of the smaller side.
545
# Note that the orientation in the physical space is completely irrelevant here.
546
# ┌─────────────┬─────────────┐ ┌───────────────────────────┐
547
# │ │ │ │ │
548
# │ small │ small │ │ │
549
# │ 3 │ 4 │ │ │
550
# │ │ │ │ large │
551
# ├─────────────┼─────────────┤ │ 5 │
552
# η │ │ │ │ │
553
# │ small │ small │ │ │
554
# ↑ │ 1 │ 2 │ │ │
555
# │ │ │ │ │ │
556
# │ └─────────────┴─────────────┘ └───────────────────────────┘
557
# │
558
# ⋅────> ξ
559
mutable struct P4estMortarContainer{NDIMS, uEltype <: Real, NDIMSP1, NDIMSP3,
560
uArray <: DenseArray{uEltype, NDIMSP3},
561
IdsMatrix <: DenseMatrix{Int},
562
IndicesMatrix <:
563
DenseMatrix{NTuple{NDIMS, Symbol}},
564
uVector <: DenseVector{uEltype},
565
IdsVector <: DenseVector{Int},
566
IndicesVector <:
567
DenseVector{NTuple{NDIMS, Symbol}}} <:
568
AbstractMortarContainer
569
u::uArray # [small/large side, variable, position, i, j, mortar]
570
neighbor_ids::IdsMatrix # [position, mortar]
571
node_indices::IndicesMatrix # [small/large, mortar]
572
573
# internal `resize!`able storage
574
_u::uVector
575
_neighbor_ids::IdsVector
576
_node_indices::IndicesVector
577
end
578
579
@inline nmortars(mortars::P4estMortarContainer) = size(mortars.neighbor_ids, 2)
580
@inline Base.ndims(::P4estMortarContainer{NDIMS}) where {NDIMS} = NDIMS
581
@inline function Base.eltype(::P4estMortarContainer{NDIMS, uEltype}) where {NDIMS,
582
uEltype}
583
return uEltype
584
end
585
586
# See explanation of Base.resize! for the element container
587
function Base.resize!(mortars::P4estMortarContainer, capacity)
588
@unpack _u, _neighbor_ids, _node_indices = mortars
589
590
n_dims = ndims(mortars)
591
n_nodes = size(mortars.u, 4)
592
n_variables = size(mortars.u, 2)
593
ArrayType = storage_type(mortars)
594
595
resize!(_u, 2 * n_variables * 2^(n_dims - 1) * n_nodes^(n_dims - 1) * capacity)
596
mortars.u = unsafe_wrap(ArrayType, pointer(_u),
597
(2, n_variables, 2^(n_dims - 1),
598
ntuple(_ -> n_nodes, n_dims - 1)..., capacity))
599
600
resize!(_neighbor_ids, (2^(n_dims - 1) + 1) * capacity)
601
mortars.neighbor_ids = unsafe_wrap(ArrayType, pointer(_neighbor_ids),
602
(2^(n_dims - 1) + 1, capacity))
603
604
resize!(_node_indices, 2 * capacity)
605
mortars.node_indices = unsafe_wrap(ArrayType, pointer(_node_indices), (2, capacity))
606
607
return nothing
608
end
609
610
# Create mortar container and initialize mortar data.
611
function init_mortars(mesh::Union{P4estMesh, P4estMeshView, T8codeMesh}, equations,
612
basis, elements)
613
NDIMS = ndims(elements)
614
uEltype = eltype(elements)
615
616
# Initialize container
617
n_mortars = count_required_surfaces(mesh).mortars
618
619
_u = Vector{uEltype}(undef,
620
2 * nvariables(equations) * 2^(NDIMS - 1) *
621
nnodes(basis)^(NDIMS - 1) * n_mortars)
622
u = unsafe_wrap(Array, pointer(_u),
623
(2, nvariables(equations), 2^(NDIMS - 1),
624
ntuple(_ -> nnodes(basis), NDIMS - 1)..., n_mortars))
625
626
_neighbor_ids = Vector{Int}(undef, (2^(NDIMS - 1) + 1) * n_mortars)
627
neighbor_ids = unsafe_wrap(Array, pointer(_neighbor_ids),
628
(2^(NDIMS - 1) + 1, n_mortars))
629
630
_node_indices = Vector{NTuple{NDIMS, Symbol}}(undef, 2 * n_mortars)
631
node_indices = unsafe_wrap(Array, pointer(_node_indices), (2, n_mortars))
632
633
mortars = P4estMortarContainer{NDIMS, uEltype, NDIMS + 1, NDIMS + 3, typeof(u),
634
typeof(neighbor_ids), typeof(node_indices),
635
typeof(_u), typeof(_neighbor_ids),
636
typeof(_node_indices)}(u,
637
neighbor_ids,
638
node_indices,
639
_u,
640
_neighbor_ids,
641
_node_indices)
642
643
if n_mortars > 0
644
init_mortars!(mortars, mesh)
645
end
646
647
return mortars
648
end
649
650
function init_mortars!(mortars, mesh::Union{P4estMesh, P4estMeshView})
651
init_surfaces!(nothing, mortars, nothing, mesh)
652
653
return mortars
654
end
655
656
function Adapt.parent_type(::Type{<:P4estMortarContainer{<:Any, <:Any, <:Any, <:Any,
657
ArrayT}}) where {ArrayT}
658
return ArrayT
659
end
660
661
# Manual adapt_structure since we have aliasing memory
662
function Adapt.adapt_structure(to, mortars::P4estMortarContainer)
663
# Adapt underlying storage
664
_u = adapt(to, mortars._u)
665
_neighbor_ids = adapt(to, mortars._neighbor_ids)
666
_node_indices = adapt(to, mortars._node_indices)
667
668
# Wrap arrays again
669
u = unsafe_wrap_or_alloc(to, _u, size(mortars.u))
670
neighbor_ids = unsafe_wrap_or_alloc(to, _neighbor_ids, size(mortars.neighbor_ids))
671
node_indices = unsafe_wrap_or_alloc(to, _node_indices, size(mortars.node_indices))
672
673
NDIMS = ndims(mortars)
674
new_type_params = (NDIMS,
675
eltype(_u),
676
NDIMS + 1, NDIMS + 3,
677
typeof(u), typeof(neighbor_ids), typeof(node_indices),
678
typeof(_u), typeof(_neighbor_ids), typeof(_node_indices))
679
return P4estMortarContainer{new_type_params...}(u, neighbor_ids, node_indices,
680
_u, _neighbor_ids, _node_indices)
681
end
682
683
function reinitialize_containers!(mesh::P4estMesh, equations, dg::DGSEM, cache)
684
n_cells = ncells(mesh)
685
686
# Re-initialize elements container
687
@unpack elements = cache
688
resize!(elements, n_cells)
689
init_elements!(elements, mesh, dg.basis)
690
691
# Resize volume integral and related datastructures
692
@unpack volume_integral = dg
693
resize_volume_integral_cache!(cache, mesh, volume_integral, n_cells)
694
reinit_volume_integral_cache!(cache, mesh, dg, volume_integral, n_cells)
695
696
required = count_required_surfaces(mesh)
697
698
# resize interfaces container
699
@unpack interfaces = cache
700
resize!(interfaces, required.interfaces)
701
702
# resize boundaries container
703
@unpack boundaries = cache
704
resize!(boundaries, required.boundaries)
705
706
# re-initialize mortars container
707
@unpack mortars = cache
708
resize!(mortars, required.mortars)
709
710
# re-initialize containers together to reduce
711
# the number of iterations over the mesh in `p4est`
712
init_surfaces!(interfaces, mortars, boundaries, mesh)
713
714
# init_normal_directions! requires that `node_indices` have been initialized
715
init_normal_directions!(interfaces, dg.basis, elements)
716
717
return nothing
718
end
719
720
# A helper struct used in initialization methods below
721
mutable struct InitSurfacesIterFaceUserData{Interfaces, Mortars, Boundaries, Mesh}
722
interfaces::Interfaces
723
interface_id::Int
724
mortars::Mortars
725
mortar_id::Int
726
boundaries::Boundaries
727
boundary_id::Int
728
mesh::Mesh
729
end
730
731
function InitSurfacesIterFaceUserData(interfaces, mortars, boundaries, mesh)
732
return InitSurfacesIterFaceUserData{typeof(interfaces), typeof(mortars),
733
typeof(boundaries), typeof(mesh)}(interfaces, 1,
734
mortars, 1,
735
boundaries, 1,
736
mesh)
737
end
738
739
function init_surfaces_iter_face(info, user_data)
740
# Unpack user_data
741
data = unsafe_pointer_to_objref(Ptr{InitSurfacesIterFaceUserData}(user_data))
742
743
# Function barrier because the unpacked user_data above is type-unstable
744
return init_surfaces_iter_face_inner(info, data)
745
end
746
747
# 2D
748
function cfunction(::typeof(init_surfaces_iter_face), ::Val{2})
749
@cfunction(init_surfaces_iter_face, Cvoid,
750
(Ptr{p4est_iter_face_info_t}, Ptr{Cvoid}))
751
end
752
# 3D
753
function cfunction(::typeof(init_surfaces_iter_face), ::Val{3})
754
@cfunction(init_surfaces_iter_face, Cvoid,
755
(Ptr{p8est_iter_face_info_t}, Ptr{Cvoid}))
756
end
757
758
# Function barrier for type stability
759
function init_surfaces_iter_face_inner(info, user_data)
760
@unpack interfaces, mortars, boundaries = user_data
761
info_pw = PointerWrapper(info)
762
elem_count = info_pw.sides.elem_count[]
763
764
if elem_count == 2
765
# Two neighboring elements => Interface or mortar
766
767
# Extract surface data
768
sides_pw = (load_pointerwrapper_side(info_pw, 1),
769
load_pointerwrapper_side(info_pw, 2))
770
771
if sides_pw[1].is_hanging[] == false && sides_pw[2].is_hanging[] == false
772
# No hanging nodes => normal interface
773
if interfaces !== nothing
774
init_interfaces_iter_face_inner(info_pw, sides_pw, user_data)
775
end
776
else
777
# Hanging nodes => mortar
778
if mortars !== nothing
779
init_mortars_iter_face_inner(info_pw, sides_pw, user_data)
780
end
781
end
782
elseif elem_count == 1
783
# One neighboring elements => boundary
784
if boundaries !== nothing
785
init_boundaries_iter_face_inner(info_pw, user_data)
786
end
787
end
788
return nothing
789
end
790
791
function init_surfaces!(interfaces, mortars, boundaries, mesh::P4estMesh)
792
# Let `p4est` iterate over all interfaces and call init_surfaces_iter_face
793
iter_face_c = cfunction(init_surfaces_iter_face, Val(ndims(mesh)))
794
user_data = InitSurfacesIterFaceUserData(interfaces, mortars, boundaries, mesh)
795
796
iterate_p4est(mesh.p4est, user_data; iter_face_c = iter_face_c)
797
798
return interfaces
799
end
800
801
# Initialization of interfaces after the function barrier
802
function init_interfaces_iter_face_inner(info_pw, sides_pw, user_data)
803
@unpack interfaces, interface_id, mesh = user_data
804
user_data.interface_id += 1
805
806
# Get Tuple of local trees, one-based indexing
807
trees_pw = (load_pointerwrapper_tree(mesh.p4est, sides_pw[1].treeid[] + 1),
808
load_pointerwrapper_tree(mesh.p4est, sides_pw[2].treeid[] + 1))
809
# Quadrant numbering offsets of the quadrants at this interface
810
offsets = SVector(trees_pw[1].quadrants_offset[],
811
trees_pw[2].quadrants_offset[])
812
813
local_quad_ids = SVector(sides_pw[1].is.full.quadid[], sides_pw[2].is.full.quadid[])
814
# Global IDs of the neighboring quads
815
quad_ids = offsets + local_quad_ids
816
817
# Write data to interfaces container
818
# `p4est` uses zero-based indexing; convert to one-based indexing
819
interfaces.neighbor_ids[1, interface_id] = quad_ids[1] + 1
820
interfaces.neighbor_ids[2, interface_id] = quad_ids[2] + 1
821
822
# Face at which the interface lies
823
faces = (sides_pw[1].face[], sides_pw[2].face[])
824
825
# Save interfaces.node_indices dimension specific in containers_[23]d.jl
826
init_interface_node_indices!(interfaces, faces, info_pw.orientation[], interface_id)
827
828
return nothing
829
end
830
831
# Initialization of boundaries after the function barrier
832
function init_boundaries_iter_face_inner(info_pw, user_data)
833
@unpack boundaries, boundary_id, mesh = user_data
834
user_data.boundary_id += 1
835
836
# Extract boundary data
837
side_pw = load_pointerwrapper_side(info_pw)
838
# Get local tree, one-based indexing
839
tree_pw = load_pointerwrapper_tree(mesh.p4est, side_pw.treeid[] + 1)
840
# Quadrant numbering offset of this quadrant
841
offset = tree_pw.quadrants_offset[]
842
843
# Verify before accessing is.full, but this should never happen
844
@assert side_pw.is_hanging[] == false
845
846
local_quad_id = side_pw.is.full.quadid[]
847
# Global ID of this quad
848
quad_id = offset + local_quad_id
849
850
# Write data to boundaries container
851
# `p4est` uses zero-based indexing; convert to one-based indexing
852
boundaries.neighbor_ids[boundary_id] = quad_id + 1
853
854
# Face at which the boundary lies
855
face = side_pw.face[]
856
857
# Save boundaries.node_indices dimension specific in containers_[23]d.jl
858
init_boundary_node_indices!(boundaries, face, boundary_id)
859
860
# One-based indexing
861
boundaries.name[boundary_id] = mesh.boundary_names[face + 1, side_pw.treeid[] + 1]
862
863
return nothing
864
end
865
866
# Initialization of mortars after the function barrier
867
function init_mortars_iter_face_inner(info_pw, sides_pw, user_data)
868
@unpack mortars, mortar_id, mesh = user_data
869
user_data.mortar_id += 1
870
871
# Get Tuple of local trees, one-based indexing
872
trees_pw = (load_pointerwrapper_tree(mesh.p4est, sides_pw[1].treeid[] + 1),
873
load_pointerwrapper_tree(mesh.p4est, sides_pw[2].treeid[] + 1))
874
# Quadrant numbering offsets of the quadrants at this interface
875
offsets = SVector(trees_pw[1].quadrants_offset[],
876
trees_pw[2].quadrants_offset[])
877
878
if sides_pw[1].is_hanging[] == true
879
# Left is small, right is large
880
faces = (sides_pw[1].face[], sides_pw[2].face[])
881
882
local_small_quad_ids = sides_pw[1].is.hanging.quadid[]
883
# Global IDs of the two small quads
884
small_quad_ids = offsets[1] .+ local_small_quad_ids
885
886
# Just be sure before accessing is.full
887
@assert sides_pw[2].is_hanging[] == false
888
large_quad_id = offsets[2] + sides_pw[2].is.full.quadid[]
889
else # sides_pw[2].is_hanging[] == true
890
# Right is small, left is large.
891
# init_mortar_node_indices! below expects side 1 to contain the small elements.
892
faces = (sides_pw[2].face[], sides_pw[1].face[])
893
894
local_small_quad_ids = sides_pw[2].is.hanging.quadid[]
895
# Global IDs of the two small quads
896
small_quad_ids = offsets[2] .+ local_small_quad_ids
897
898
# Just be sure before accessing is.full
899
@assert sides_pw[1].is_hanging[] == false
900
large_quad_id = offsets[1] + sides_pw[1].is.full.quadid[]
901
end
902
903
# Write data to mortar container, 1 and 2 are the small elements
904
# `p4est` uses zero-based indexing; convert to one-based indexing
905
mortars.neighbor_ids[1:(end - 1), mortar_id] .= small_quad_ids[:] .+ 1
906
# Last entry is the large element
907
mortars.neighbor_ids[end, mortar_id] = large_quad_id + 1
908
909
init_mortar_node_indices!(mortars, faces, info_pw.orientation[], mortar_id)
910
911
return nothing
912
end
913
914
# Iterate over all interfaces and count
915
# - (inner) interfaces
916
# - mortars
917
# - boundaries
918
# and collect the numbers in `user_data` in this order.
919
function count_surfaces_iter_face(info, user_data)
920
info_pw = PointerWrapper(info)
921
elem_count = info_pw.sides.elem_count[]
922
923
if elem_count == 2
924
# Two neighboring elements => Interface or mortar
925
926
# Extract surface data
927
sides_pw = (load_pointerwrapper_side(info_pw, 1),
928
load_pointerwrapper_side(info_pw, 2))
929
930
if sides_pw[1].is_hanging[] == false && sides_pw[2].is_hanging[] == false
931
# No hanging nodes => normal interface
932
# Unpack user_data = [interface_count] and increment interface_count
933
pw = PointerWrapper(Int, user_data)
934
id = pw[1]
935
pw[1] = id + 1
936
else
937
# Hanging nodes => mortar
938
# Unpack user_data = [mortar_count] and increment mortar_count
939
pw = PointerWrapper(Int, user_data)
940
id = pw[2]
941
pw[2] = id + 1
942
end
943
elseif elem_count == 1
944
# One neighboring elements => boundary
945
946
# Unpack user_data = [boundary_count] and increment boundary_count
947
pw = PointerWrapper(Int, user_data)
948
id = pw[3]
949
pw[3] = id + 1
950
end
951
952
return nothing
953
end
954
955
# 2D
956
function cfunction(::typeof(count_surfaces_iter_face), ::Val{2})
957
@cfunction(count_surfaces_iter_face, Cvoid,
958
(Ptr{p4est_iter_face_info_t}, Ptr{Cvoid}))
959
end
960
# 3D
961
function cfunction(::typeof(count_surfaces_iter_face), ::Val{3})
962
@cfunction(count_surfaces_iter_face, Cvoid,
963
(Ptr{p8est_iter_face_info_t}, Ptr{Cvoid}))
964
end
965
966
function count_required_surfaces(mesh::P4estMesh)
967
# Let `p4est` iterate over all interfaces and call count_surfaces_iter_face
968
iter_face_c = cfunction(count_surfaces_iter_face, Val(ndims(mesh)))
969
970
# interfaces, mortars, boundaries
971
user_data = [0, 0, 0]
972
973
iterate_p4est(mesh.p4est, user_data; iter_face_c = iter_face_c)
974
975
# Return counters
976
return (interfaces = user_data[1],
977
mortars = user_data[2],
978
boundaries = user_data[3])
979
end
980
981
# Return direction of the face, which is indexed by node_indices
982
@inline function indices2direction(indices::NTuple{3, Symbol})
983
if indices[1] === :begin
984
return 1
985
elseif indices[1] === :end
986
return 2
987
elseif indices[2] === :begin
988
return 3
989
elseif indices[2] === :end
990
return 4
991
elseif indices[3] === :begin
992
return 5
993
else # if indices[3] === :end
994
return 6
995
end
996
end
997
998
@inline function indices2direction(indices::NTuple{2, Symbol})
999
if indices[1] === :begin
1000
return 1
1001
elseif indices[1] === :end
1002
return 2
1003
elseif indices[2] === :begin
1004
return 3
1005
else # if indices[2] === :end
1006
return 4
1007
end
1008
end
1009
1010
# Build a reduced cache which can be passed to GPU kernels
1011
@inline function kernel_filter_cache(cache)
1012
return (;
1013
elements = (; contravariant_vectors = cache.elements.contravariant_vectors))
1014
end
1015
1016
include("containers_2d.jl")
1017
include("containers_3d.jl")
1018
include("containers_parallel.jl")
1019
include("containers_parallel_2d.jl")
1020
include("containers_parallel_3d.jl")
1021
end # @muladd
1022
1023