Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
trixi-framework
GitHub Repository: trixi-framework/Trixi.jl
Path: blob/main/src/meshes/t8code_mesh.jl
5586 views
1
"""
2
T8codeMesh{NDIMS} <: AbstractMesh{NDIMS}
3
4
An unstructured curved mesh based on trees that uses the C library
5
['t8code'](https://github.com/DLR-AMR/t8code)
6
to manage trees and mesh refinement.
7
"""
8
mutable struct T8codeMesh{NDIMS, RealT <: Real, IsParallel, NDIMSP2, NNODES} <:
9
AbstractMesh{NDIMS}
10
forest::T8code.ForestWrapper
11
const is_parallel::IsParallel
12
13
# This specifies the geometry interpolation for each tree.
14
const tree_node_coordinates::Array{RealT, NDIMSP2} # [dimension, i, j, k, tree]
15
16
# Stores the quadrature nodes.
17
const nodes::SVector{NNODES, RealT}
18
const boundary_names::Array{Symbol, 2} # [face direction, tree]
19
20
current_filename::String
21
22
# These guys are set in `fill_mesh_info`
23
ninterfaces :: Int
24
nmortars :: Int
25
nboundaries :: Int
26
27
nmpiinterfaces :: Int
28
nmpimortars :: Int
29
30
unsaved_changes::Bool
31
32
function T8codeMesh{NDIMS}(forest::Ptr{t8_forest}, tree_node_coordinates, nodes,
33
boundary_names,
34
current_filename,
35
RealT = Float64) where {NDIMS}
36
is_parallel = mpi_isparallel() ? True() : False()
37
mesh = new{NDIMS, RealT, typeof(is_parallel), NDIMS + 2, length(nodes)}(T8code.ForestWrapper(forest),
38
is_parallel,
39
tree_node_coordinates,
40
nodes,
41
boundary_names,
42
current_filename,
43
-1, # ninterfaces
44
-1, # nmortars
45
-1, # nboundaries
46
-1, # nmpiinterfaces
47
-1, # nmpimortars
48
true)
49
50
finalizer(mesh) do mesh
51
# In serial mode we can finalize the forest right away. In parallel
52
# mode we keep the forest till Trixi shuts down or the user
53
# finalizes the forest wrapper explicitly.
54
if !mpi_isparallel()
55
finalize(mesh.forest)
56
end
57
end
58
59
return mesh
60
end
61
end
62
63
"""
64
Trixi.update_forest!(mesh::T8codeMesh, new_forest::Ptr{t8_forest})
65
66
Update the `mesh` object with the `new_forest`. Ownership of the old forest
67
goes to caller.
68
69
# Arguments
70
- `mesh::T8codeMesh`: Initialized mesh.
71
- `new_forest::Ptr{t8_forest}`: New forest.
72
73
Returns `nothing`.
74
"""
75
function update_forest!(mesh::T8codeMesh, new_forest::Ptr{t8_forest})
76
# `mesh.forest` must not be overwritten. Its lifetime is attached to the
77
# `mesh` lifetime. Thus, this setter function.
78
mesh.forest.pointer = new_forest
79
return nothing
80
end
81
82
const T8codeMeshSerial{NDIMS} = T8codeMesh{NDIMS, <:Real, <:False}
83
const T8codeMeshParallel{NDIMS} = T8codeMesh{NDIMS, <:Real, <:True}
84
@inline mpi_parallel(mesh::T8codeMeshSerial) = False()
85
@inline mpi_parallel(mesh::T8codeMeshParallel) = True()
86
87
@inline Base.ndims(::T8codeMesh{NDIMS}) where {NDIMS} = NDIMS
88
@inline Base.real(::T8codeMesh{NDIMS, RealT}) where {NDIMS, RealT} = RealT
89
90
@inline ntrees(mesh::T8codeMesh) = size(mesh.tree_node_coordinates)[end]
91
@inline ncells(mesh::T8codeMesh) = Int(t8_forest_get_local_num_elements(mesh.forest))
92
@inline ncellsglobal(mesh::T8codeMesh) = Int(t8_forest_get_global_num_elements(mesh.forest))
93
94
function Base.show(io::IO, mesh::T8codeMesh)
95
print(io, "T8codeMesh{", ndims(mesh), ", ", real(mesh), "}")
96
return nothing
97
end
98
99
function Base.show(io::IO, ::MIME"text/plain", mesh::T8codeMesh)
100
if get(io, :compact, false)
101
show(io, mesh)
102
else
103
setup = [
104
"#trees" => ntrees(mesh),
105
"current #cells" => ncellsglobal(mesh),
106
"polydeg" => length(mesh.nodes) - 1
107
]
108
summary_box(io,
109
"T8codeMesh{" * string(ndims(mesh)) * ", " * string(real(mesh)) * "}",
110
setup)
111
end
112
end
113
114
"""
115
T8codeMesh(ndims, ntrees, nelements, tree_node_coordinates, nodes,
116
boundary_names, treeIDs, neighIDs, faces, duals,
117
orientations, levels, num_elements_per_tree)
118
119
Constructor for the `T8codeMesh`. Typically called by the `load_mesh` routine.
120
121
# Arguments
122
- `ndims`: Dimension of the mesh.
123
- `ntrees`: Global number of trees.
124
- `nelements`: Global number of elements.
125
- `tree_node_coordinates`: Node coordinates for each tree: [dimension, i, j, k, tree]
126
- `nodes`: Array of interpolation nodes.
127
- `boundary_names`: List of boundary names.
128
- `treeIDs`: List of tree IDs. The length is the number of conforming interfaces of the coarse mesh.
129
- `neighIDs`: List of neighboring tree IDs. Same length as `treeIDs`.
130
- `faces`: List of face IDs. Same length as `treeIDs`.
131
- `duals`: List of face IDs of the neighboring tree. Same length as `treeIDs`.
132
- `orientations`: Orientation number of the interface. Same length as `treeIDs`.
133
- `levels`: List of levels of each element. Has length `nelements`.
134
- `num_elements_per_tree`: List of global number of elements per tree. Has length `ntrees`.
135
136
Returns a `T8codeMesh` object with a forest reconstructed by the input arguments.
137
"""
138
function T8codeMesh(ndims, ntrees, nelements, tree_node_coordinates, nodes,
139
boundary_names, treeIDs, neighIDs, faces, duals,
140
orientations, levels, num_elements_per_tree)
141
# Allocate new cmesh object.
142
cmesh = t8_cmesh_new()
143
144
# Use linear geometry for now. There is no real Lagrange geometry
145
# implementation (volume nodes) yet in t8code.
146
linear_geom = t8_geometry_linear_new()
147
t8_cmesh_register_geometry(cmesh, linear_geom)
148
149
# Determine element class.
150
eclass = ndims > 2 ? T8_ECLASS_HEX : T8_ECLASS_QUAD
151
152
# Store element vertices inside the cmesh.
153
N = length(nodes)
154
vertices = zeros(3 * 2^ndims) # quads/hexs only
155
for i in 1:ntrees
156
t8_cmesh_set_tree_class(cmesh, i - 1, eclass)
157
158
if ndims == 2
159
vertices[1] = tree_node_coordinates[1, 1, 1, i]
160
vertices[2] = tree_node_coordinates[2, 1, 1, i]
161
162
vertices[4] = tree_node_coordinates[1, N, 1, i]
163
vertices[5] = tree_node_coordinates[2, N, 1, i]
164
165
vertices[7] = tree_node_coordinates[1, 1, N, i]
166
vertices[8] = tree_node_coordinates[2, 1, N, i]
167
168
vertices[10] = tree_node_coordinates[1, N, N, i]
169
vertices[11] = tree_node_coordinates[2, N, N, i]
170
else
171
vertices[1] = tree_node_coordinates[1, 1, 1, 1, i]
172
vertices[2] = tree_node_coordinates[2, 1, 1, 1, i]
173
vertices[3] = tree_node_coordinates[3, 1, 1, 1, i]
174
175
vertices[4] = tree_node_coordinates[1, N, 1, 1, i]
176
vertices[5] = tree_node_coordinates[2, N, 1, 1, i]
177
vertices[6] = tree_node_coordinates[3, N, 1, 1, i]
178
179
vertices[7] = tree_node_coordinates[1, 1, N, 1, i]
180
vertices[8] = tree_node_coordinates[2, 1, N, 1, i]
181
vertices[9] = tree_node_coordinates[3, 1, N, 1, i]
182
183
vertices[10] = tree_node_coordinates[1, N, N, 1, i]
184
vertices[11] = tree_node_coordinates[2, N, N, 1, i]
185
vertices[12] = tree_node_coordinates[3, N, N, 1, i]
186
187
vertices[13] = tree_node_coordinates[1, 1, 1, N, i]
188
vertices[14] = tree_node_coordinates[2, 1, 1, N, i]
189
vertices[15] = tree_node_coordinates[3, 1, 1, N, i]
190
191
vertices[16] = tree_node_coordinates[1, N, 1, N, i]
192
vertices[17] = tree_node_coordinates[2, N, 1, N, i]
193
vertices[18] = tree_node_coordinates[3, N, 1, N, i]
194
195
vertices[19] = tree_node_coordinates[1, 1, N, N, i]
196
vertices[20] = tree_node_coordinates[2, 1, N, N, i]
197
vertices[21] = tree_node_coordinates[3, 1, N, N, i]
198
199
vertices[22] = tree_node_coordinates[1, N, N, N, i]
200
vertices[23] = tree_node_coordinates[2, N, N, N, i]
201
vertices[24] = tree_node_coordinates[3, N, N, N, i]
202
end
203
204
t8_cmesh_set_tree_vertices(cmesh, i - 1, vertices, 2^ndims)
205
end
206
207
# Connect the coarse mesh elements.
208
for i in eachindex(treeIDs)
209
t8_cmesh_set_join(cmesh, treeIDs[i], neighIDs[i], faces[i], duals[i],
210
orientations[i])
211
end
212
213
t8_cmesh_commit(cmesh, mpi_comm())
214
215
# Init a new forest with just one element per tree.
216
do_face_ghost = mpi_isparallel()
217
scheme = t8_scheme_new_default_cxx()
218
initial_refinement_level = 0
219
forest = t8_forest_new_uniform(cmesh, scheme, initial_refinement_level, do_face_ghost,
220
mpi_comm())
221
222
cum_sum_num_elements_per_tree = cumsum(num_elements_per_tree)
223
224
global_element_id = 0 # zero-based index
225
226
# Compute the offset within the to-be-reconstructed forest. Depends on the
227
# MPI rank resp. first global tree id.
228
if mpi_rank() > 0 && t8_forest_get_local_num_elements(forest) > 0
229
last_global_tree_id_of_preceding_rank = t8_forest_global_tree_id(forest, 0) - 1
230
global_element_id += cum_sum_num_elements_per_tree[last_global_tree_id_of_preceding_rank + 1]
231
end
232
233
function adapt_callback(forest, local_tree_id, eclass_scheme, local_element_id,
234
elements, is_family,
235
user_data)
236
237
# Check if we are already in the next tree in terms of the `global_element_id`.
238
global_tree_id = t8_forest_global_tree_id(forest, local_tree_id)
239
if global_element_id + 1 > cum_sum_num_elements_per_tree[global_tree_id + 1]
240
return 0
241
end
242
243
# Test if we already reached the targeted level.
244
level = t8_element_level(eclass_scheme, elements[1])
245
if level < levels[global_element_id + 1]
246
return 1 # Go one refinement level deeper.
247
end
248
249
# Targeted level is reached.
250
global_element_id += 1
251
return 0
252
end
253
254
# The adapt callback refines the forest according to the `levels` array.
255
# For each tree the callback recursively increases the refinement level
256
# till it matches with the associated section in `levels.
257
forest = adapt_forest(forest, adapt_callback; recursive = true, balance = false,
258
partition = false, ghost = false, user_data = C_NULL)
259
260
@assert t8_forest_get_global_num_elements(forest) == nelements
261
262
if mpi_isparallel()
263
forest = partition_forest(forest)
264
end
265
266
return T8codeMesh{ndims}(forest, tree_node_coordinates, nodes, boundary_names, "")
267
end
268
269
"""
270
T8codeMesh{NDIMS, RealT}(forest, boundary_names; polydeg = 1, mapping = nothing)
271
272
Main mesh constructor for the `T8codeMesh` wrapping around a given t8code
273
`forest` object. This constructor is typically called by other `T8codeMesh`
274
constructors.
275
276
# Arguments
277
- `forest`: Pointer to a t8code forest.
278
- `boundary_names`: List of boundary names.
279
- `polydeg::Integer`: Polynomial degree used to store the geometry of the mesh.
280
The mapping will be approximated by an interpolation polynomial
281
of the specified degree for each tree.
282
- `mapping`: A function of `NDIMS` variables to describe the mapping that transforms
283
the imported mesh to the physical domain. Use `nothing` for the identity map.
284
"""
285
function T8codeMesh{NDIMS, RealT}(forest::Ptr{t8_forest}, boundary_names; polydeg = 1,
286
mapping = nothing) where {NDIMS, RealT}
287
# In t8code reference space is [0,1].
288
basis = LobattoLegendreBasis(RealT, polydeg)
289
nodes = 0.5f0 .* (basis.nodes .+ 1)
290
291
cmesh = t8_forest_get_cmesh(forest)
292
number_of_trees = t8_forest_get_num_global_trees(forest)
293
294
tree_node_coordinates = Array{RealT, NDIMS + 2}(undef, NDIMS,
295
ntuple(_ -> length(nodes), NDIMS)...,
296
number_of_trees)
297
298
reference_coordinates = Vector{RealT}(undef, 3)
299
300
# Calculate node coordinates of reference mesh.
301
if NDIMS == 2
302
number_of_corners = 4 # quadrilateral
303
304
# Testing for negative element volumes.
305
vertices = zeros(3, number_of_corners)
306
for itree in 1:number_of_trees
307
vertices_pointer = t8_cmesh_get_tree_vertices(cmesh, itree - 1)
308
309
# Note, `vertices = unsafe_wrap(Array, vertices_pointer, (3, 1 << NDIMS))`
310
# sometimes does not work since `vertices_pointer` is not necessarily properly
311
# aligned to 8 bytes.
312
for icorner in 1:number_of_corners
313
vertices[1, icorner] = unsafe_load(vertices_pointer, (icorner - 1) * 3 + 1)
314
vertices[2, icorner] = unsafe_load(vertices_pointer, (icorner - 1) * 3 + 2)
315
end
316
317
# Check if tree's node ordering is right-handed or print a warning.
318
let z = zero(eltype(vertices)), o = one(eltype(vertices))
319
u = vertices[:, 2] - vertices[:, 1]
320
v = vertices[:, 3] - vertices[:, 1]
321
w = [z, z, o]
322
323
# Triple product gives signed volume of spanned parallelepiped.
324
vol = dot(cross(u, v), w)
325
326
if vol < z
327
error("Discovered negative volumes in `cmesh`: vol = $vol")
328
end
329
end
330
331
# Query geometry data from t8code.
332
for j in eachindex(nodes), i in eachindex(nodes)
333
reference_coordinates[1] = nodes[i]
334
reference_coordinates[2] = nodes[j]
335
reference_coordinates[3] = 0.0
336
t8_geometry_evaluate(cmesh, itree - 1, reference_coordinates, 1,
337
@view(tree_node_coordinates[:, i, j, itree]))
338
end
339
end
340
341
elseif NDIMS == 3
342
number_of_corners = 8 # hexahedron
343
344
# Testing for negative element volumes.
345
vertices = zeros(3, number_of_corners)
346
for itree in 1:number_of_trees
347
vertices_pointer = t8_cmesh_get_tree_vertices(cmesh, itree - 1)
348
349
# Note, `vertices = unsafe_wrap(Array, vertices_pointer, (3, 1 << NDIMS))`
350
# sometimes does not work since `vertices_pointer` is not necessarily properly
351
# aligned to 8 bytes.
352
for icorner in 1:number_of_corners
353
vertices[1, icorner] = unsafe_load(vertices_pointer, (icorner - 1) * 3 + 1)
354
vertices[2, icorner] = unsafe_load(vertices_pointer, (icorner - 1) * 3 + 2)
355
vertices[3, icorner] = unsafe_load(vertices_pointer, (icorner - 1) * 3 + 3)
356
end
357
358
# Check if tree's node ordering is right-handed or print a warning.
359
let z = zero(eltype(vertices))
360
u = vertices[:, 2] - vertices[:, 1]
361
v = vertices[:, 3] - vertices[:, 1]
362
w = vertices[:, 5] - vertices[:, 1]
363
364
# Triple product gives signed volume of spanned parallelepiped.
365
vol = dot(cross(u, v), w)
366
367
if vol < z
368
error("Discovered negative volumes in `cmesh`: vol = $vol")
369
end
370
end
371
372
# Query geometry data from t8code.
373
for k in eachindex(nodes), j in eachindex(nodes), i in eachindex(nodes)
374
reference_coordinates[1] = nodes[i]
375
reference_coordinates[2] = nodes[j]
376
reference_coordinates[3] = nodes[k]
377
t8_geometry_evaluate(cmesh, itree - 1, reference_coordinates, 1,
378
@view(tree_node_coordinates[:, i, j, k, itree]))
379
end
380
end
381
else
382
throw(ArgumentError("$NDIMS dimensions are not supported."))
383
end
384
385
# Apply user defined mapping.
386
map_node_coordinates!(tree_node_coordinates, mapping)
387
388
return T8codeMesh{NDIMS}(forest, tree_node_coordinates, basis.nodes,
389
boundary_names, "")
390
end
391
392
"""
393
T8codeMesh(trees_per_dimension; polydeg, mapping=identity,
394
RealT=Float64, initial_refinement_level=0, periodicity=false)
395
396
Create a structured potentially curved 'T8codeMesh' of the specified size.
397
398
Non-periodic boundaries will be called ':x_neg', ':x_pos', ':y_neg', ':y_pos', ':z_neg', ':z_pos'.
399
400
# Arguments
401
- 'trees_per_dimension::NTupleE{NDIMS, Int}': the number of trees in each dimension.
402
- 'polydeg::Integer': polynomial degree used to store the geometry of the mesh.
403
The mapping will be approximated by an interpolation polynomial
404
of the specified degree for each tree.
405
- `mapping`: a function of `NDIMS` variables to describe the mapping that transforms
406
the reference mesh (`[-1, 1]^n`) to the physical domain.
407
Use only one of `mapping`, `faces` and `coordinates_min`/`coordinates_max`.
408
- `faces::NTuple{2*NDIMS}`: a tuple of `2 * NDIMS` functions that describe the faces of the domain.
409
Each function must take `NDIMS-1` arguments.
410
`faces[1]` describes the face onto which the face in negative x-direction
411
of the unit hypercube is mapped. The face in positive x-direction of
412
the unit hypercube will be mapped onto the face described by `faces[2]`.
413
`faces[3:4]` describe the faces in positive and negative y-direction respectively
414
(in 2D and 3D).
415
`faces[5:6]` describe the faces in positive and negative z-direction respectively (in 3D).
416
Use only one of `mapping`, `faces` and `coordinates_min`/`coordinates_max`.
417
- `coordinates_min`: vector or tuple of the coordinates of the corner in the negative direction of each dimension
418
to create a rectangular mesh.
419
Use only one of `mapping`, `faces` and `coordinates_min`/`coordinates_max`.
420
- `coordinates_max`: vector or tuple of the coordinates of the corner in the positive direction of each dimension
421
to create a rectangular mesh.
422
Use only one of `mapping`, `faces` and `coordinates_min`/`coordinates_max`.
423
- 'RealT::Type': the type that should be used for coordinates.
424
- 'initial_refinement_level::Integer': refine the mesh uniformly to this level before the simulation starts.
425
- 'periodicity': either a `Bool` deciding if all of the boundaries are periodic or an `NTuple{NDIMS, Bool}`
426
deciding for each dimension if the boundaries in this dimension are periodic.
427
"""
428
function T8codeMesh(trees_per_dimension; polydeg = 1,
429
mapping = nothing, faces = nothing, coordinates_min = nothing,
430
coordinates_max = nothing,
431
RealT = Float64, initial_refinement_level = 0,
432
periodicity = false)
433
@assert ((coordinates_min === nothing)===(coordinates_max === nothing)) "Either both or none of coordinates_min and coordinates_max must be specified"
434
435
coordinates_min_max_check(coordinates_min, coordinates_max)
436
437
@assert count(i -> i !== nothing,
438
(mapping, faces, coordinates_min))==1 "Exactly one of mapping, faces and coordinates_min/max must be specified"
439
440
# Extract mapping
441
if faces !== nothing
442
validate_faces(faces)
443
mapping = transfinite_mapping(faces)
444
elseif coordinates_min !== nothing
445
mapping = coordinates2mapping(coordinates_min, coordinates_max)
446
end
447
448
NDIMS = length(trees_per_dimension)
449
@assert (NDIMS == 2||NDIMS == 3) "NDIMS should be 2 or 3."
450
451
# Convert periodicity to a Tuple of a Bool for every dimension
452
if all(periodicity)
453
# Also catches case where periodicity = true
454
periodicity = ntuple(_ -> true, NDIMS)
455
elseif !any(periodicity)
456
# Also catches case where periodicity = false
457
periodicity = ntuple(_ -> false, NDIMS)
458
else
459
# Default case if periodicity is an iterable
460
periodicity = Tuple(periodicity)
461
end
462
463
do_partition = 0
464
if NDIMS == 2
465
cmesh = t8_cmesh_new_brick_2d(trees_per_dimension..., periodicity...,
466
sc_MPI_COMM_WORLD)
467
elseif NDIMS == 3
468
cmesh = t8_cmesh_new_brick_3d(trees_per_dimension..., periodicity...,
469
sc_MPI_COMM_WORLD)
470
end
471
472
do_face_ghost = mpi_isparallel()
473
scheme = t8_scheme_new_default_cxx()
474
forest = t8_forest_new_uniform(cmesh, scheme, initial_refinement_level, do_face_ghost,
475
mpi_comm())
476
477
# Non-periodic boundaries.
478
boundary_names = fill(Symbol("---"), 2 * NDIMS, prod(trees_per_dimension))
479
480
for itree in 1:t8_forest_get_num_global_trees(forest)
481
if !periodicity[1]
482
boundary_names[1, itree] = :x_neg
483
boundary_names[2, itree] = :x_pos
484
end
485
486
if !periodicity[2]
487
boundary_names[3, itree] = :y_neg
488
boundary_names[4, itree] = :y_pos
489
end
490
491
if NDIMS > 2
492
if !periodicity[3]
493
boundary_names[5, itree] = :z_neg
494
boundary_names[6, itree] = :z_pos
495
end
496
end
497
end
498
499
# Note, `t8_cmesh_new_brick_*d` converts a domain of `[0,nx] x [0,ny] x ....`.
500
# Hence, transform mesh coordinates to reference space [-1,1]^NDIMS before applying user defined mapping.
501
mapping_(xyz...) = mapping((x * 2.0 / tpd - 1.0 for (x, tpd) in zip(xyz,
502
trees_per_dimension))...)
503
504
return T8codeMesh{NDIMS, RealT}(forest, boundary_names; polydeg = polydeg,
505
mapping = mapping_)
506
end
507
508
"""
509
T8codeMesh(cmesh::Ptr{t8_cmesh},
510
mapping=nothing, polydeg=1, RealT=Float64,
511
initial_refinement_level=0)
512
513
Main mesh constructor for the `T8codeMesh` that imports an unstructured,
514
conforming mesh from a `t8_cmesh` data structure.
515
516
# Arguments
517
- `cmesh::Ptr{t8_cmesh}`: Pointer to a cmesh object.
518
- `mapping`: a function of `NDIMS` variables to describe the mapping that transforms
519
the imported mesh to the physical domain. Use `nothing` for the identity map.
520
- `polydeg::Integer`: polynomial degree used to store the geometry of the mesh.
521
The mapping will be approximated by an interpolation polynomial
522
of the specified degree for each tree.
523
The default of `1` creates an uncurved geometry. Use a higher value if the mapping
524
will curve the imported uncurved mesh.
525
- `RealT::Type`: the type that should be used for coordinates.
526
- `initial_refinement_level::Integer`: refine the mesh uniformly to this level before the simulation starts.
527
"""
528
function T8codeMesh(cmesh::Ptr{t8_cmesh};
529
mapping = nothing, polydeg = 1, RealT = Float64,
530
initial_refinement_level = 0)
531
@assert (t8_cmesh_get_num_trees(cmesh)>0) "Given `cmesh` does not contain any trees."
532
533
# Infer NDIMS from the geometry of the first tree.
534
NDIMS = Int(t8_cmesh_get_dimension(cmesh))
535
536
@assert (NDIMS == 2||NDIMS == 3) "NDIMS should be 2 or 3."
537
538
do_face_ghost = mpi_isparallel()
539
scheme = t8_scheme_new_default_cxx()
540
forest = t8_forest_new_uniform(cmesh, scheme, initial_refinement_level, do_face_ghost,
541
mpi_comm())
542
543
# There's no simple and generic way to distinguish boundaries, yet. Name all of them :all.
544
boundary_names = fill(:all, 2 * NDIMS, t8_cmesh_get_num_trees(cmesh))
545
546
return T8codeMesh{NDIMS, RealT}(forest, boundary_names; polydeg = polydeg,
547
mapping = mapping)
548
end
549
550
"""
551
T8codeMesh(conn::Ptr{p4est_connectivity}; kwargs...)
552
553
Main mesh constructor for the `T8codeMesh` that imports an unstructured,
554
conforming mesh from a `p4est_connectivity` data structure.
555
556
# Arguments
557
- `conn::Ptr{p4est_connectivity}`: Pointer to a P4est connectivity object.
558
- `mapping`: a function of `NDIMS` variables to describe the mapping that transforms
559
the imported mesh to the physical domain. Use `nothing` for the identity map.
560
- `polydeg::Integer`: polynomial degree used to store the geometry of the mesh.
561
The mapping will be approximated by an interpolation polynomial
562
of the specified degree for each tree.
563
The default of `1` creates an uncurved geometry. Use a higher value if the mapping
564
will curve the imported uncurved mesh.
565
- `RealT::Type`: the type that should be used for coordinates.
566
- `initial_refinement_level::Integer`: refine the mesh uniformly to this level before the simulation starts.
567
"""
568
function T8codeMesh(conn::Ptr{p4est_connectivity}; kwargs...)
569
cmesh = t8_cmesh_new_from_p4est(conn, mpi_comm(), 0)
570
571
return T8codeMesh(cmesh; kwargs...)
572
end
573
574
"""
575
T8codeMesh(conn::Ptr{p8est_connectivity}; kwargs...)
576
577
Main mesh constructor for the `T8codeMesh` that imports an unstructured,
578
conforming mesh from a `p4est_connectivity` data structure.
579
580
# Arguments
581
- `conn::Ptr{p4est_connectivity}`: Pointer to a P4est connectivity object.
582
- `mapping`: a function of `NDIMS` variables to describe the mapping that transforms
583
the imported mesh to the physical domain. Use `nothing` for the identity map.
584
- `polydeg::Integer`: polynomial degree used to store the geometry of the mesh.
585
The mapping will be approximated by an interpolation polynomial
586
of the specified degree for each tree.
587
The default of `1` creates an uncurved geometry. Use a higher value if the mapping
588
will curve the imported uncurved mesh.
589
- `RealT::Type`: the type that should be used for coordinates.
590
- `initial_refinement_level::Integer`: refine the mesh uniformly to this level before the simulation starts.
591
"""
592
function T8codeMesh(conn::Ptr{p8est_connectivity}; kwargs...)
593
cmesh = t8_cmesh_new_from_p8est(conn, mpi_comm(), 0)
594
595
return T8codeMesh(cmesh; kwargs...)
596
end
597
598
# Convenience types for multiple dispatch. Only used in this file.
599
struct GmshFile{NDIMS}
600
path::String
601
end
602
603
struct AbaqusFile{NDIMS}
604
path::String
605
end
606
607
"""
608
T8codeMesh(filepath::String, NDIMS; kwargs...)
609
610
Main mesh constructor for the `T8codeMesh` that imports an unstructured, conforming
611
mesh from either a Gmsh mesh file (`.msh`) or Abaqus mesh file (`.inp`) which is determined
612
by the file extension.
613
614
# Arguments
615
- `filepath::String`: path to a Gmsh or Abaqus mesh file.
616
- `ndims`: Mesh file dimension: `2` or `3`.
617
618
# Optional Keyword Arguments
619
- `mapping`: A function of `NDIMS` variables to describe the mapping that transforms
620
the imported mesh to the physical domain. Use `nothing` for the identity map.
621
- `polydeg::Integer`: Polynomial degree used to store the geometry of the mesh.
622
The mapping will be approximated by an interpolation polynomial
623
of the specified degree for each tree.
624
The default of `1` creates an uncurved geometry. Use a higher value if the mapping
625
will curve the imported uncurved mesh.
626
- `RealT::Type`: The type that should be used for coordinates.
627
- `initial_refinement_level::Integer`: Refine the mesh uniformly to this level before the simulation starts.
628
"""
629
function T8codeMesh(filepath::String, ndims; kwargs...)
630
# Prevent `t8code` from crashing Julia if the file doesn't exist.
631
@assert isfile(filepath)
632
633
meshfile_prefix, meshfile_suffix = splitext(filepath)
634
635
file_extension = lowercase(meshfile_suffix)
636
637
if file_extension == ".msh"
638
return T8codeMesh(GmshFile{ndims}(filepath); kwargs...)
639
end
640
641
if file_extension == ".inp"
642
return T8codeMesh(AbaqusFile{ndims}(filepath); kwargs...)
643
end
644
645
throw(ArgumentError("Unknown file extension: " * file_extension))
646
end
647
648
"""
649
T8codeMesh(meshfile::GmshFile{NDIMS}; kwargs...)
650
651
Main mesh constructor for the `T8codeMesh` that imports an unstructured, conforming
652
mesh from a Gmsh mesh file (`.msh`).
653
654
# Arguments
655
- `meshfile::GmshFile{NDIMS}`: Gmsh mesh file object of dimension NDIMS and give `path` to the file.
656
657
# Optional Keyword Arguments
658
- `mapping`: A function of `NDIMS` variables to describe the mapping that transforms
659
the imported mesh to the physical domain. Use `nothing` for the identity map.
660
- `polydeg::Integer`: Polynomial degree used to store the geometry of the mesh.
661
The mapping will be approximated by an interpolation polynomial
662
of the specified degree for each tree.
663
The default of `1` creates an uncurved geometry. Use a higher value if the mapping
664
will curve the imported uncurved mesh.
665
- `RealT::Type`: The type that should be used for coordinates.
666
- `initial_refinement_level::Integer`: Refine the mesh uniformly to this level before the simulation starts.
667
"""
668
function T8codeMesh(meshfile::GmshFile{NDIMS}; kwargs...) where {NDIMS}
669
# Prevent `t8code` from crashing Julia if the file doesn't exist.
670
@assert isfile(meshfile.path)
671
672
meshfile_prefix, meshfile_suffix = splitext(meshfile.path)
673
674
cmesh = t8_cmesh_from_msh_file(meshfile_prefix, 0, mpi_comm(), NDIMS, 0, 0)
675
676
return T8codeMesh(cmesh; kwargs...)
677
end
678
679
"""
680
T8codeMesh(meshfile::AbaqusFile{NDIMS};
681
mapping=nothing, polydeg=1, RealT=Float64,
682
initial_refinement_level=0, unsaved_changes=true,
683
boundary_symbols = nothing)
684
685
Main mesh constructor for the `T8codeMesh` that imports an unstructured, conforming
686
mesh from an Abaqus mesh file (`.inp`).
687
688
To create a curved unstructured `T8codeMesh` two strategies are available:
689
690
- `HOHQMesh Abaqus`: High-order, curved boundary information created by
691
[`HOHQMesh.jl`](https://github.com/trixi-framework/HOHQMesh.jl) is
692
available in the `meshfile`. The mesh polynomial degree `polydeg`
693
of the boundaries is provided from the `meshfile`. The computation of
694
the mapped tree coordinates is done with transfinite interpolation
695
with linear blending similar to [`UnstructuredMesh2D`](@ref). Boundary name
696
information is also parsed from the `meshfile` such that different boundary
697
conditions can be set at each named boundary on a given tree.
698
699
- `Standard Abaqus`: By default, with `mapping=nothing` and `polydeg=1`, this creates a
700
straight-sided mesh from the information parsed from the `meshfile`. If a mapping
701
function is specified then it computes the mapped tree coordinates via polynomial
702
interpolants with degree `polydeg`. The mesh created by this function will only
703
have one boundary `:all` if `boundary_symbols` is not specified.
704
If `boundary_symbols` is specified the mesh file will be parsed for nodesets defining
705
the boundary nodes from which boundary edges (2D) and faces (3D) will be assigned.
706
707
Note that the `mapping` and `polydeg` keyword arguments are only used by the `HOHQMesh Abaqus` option.
708
The `Standard Abaqus` routine obtains the mesh `polydeg` directly from the `meshfile`
709
and constructs the transfinite mapping internally.
710
711
The particular strategy is selected according to the header present in the `meshfile` where
712
the constructor checks whether or not the `meshfile` was created with
713
[HOHQMesh.jl](https://github.com/trixi-framework/HOHQMesh.jl).
714
If the Abaqus file header is not present in the `meshfile` then the `T8codeMesh` is created
715
by `Standard Abaqus`.
716
717
The default keyword argument `initial_refinement_level=0` corresponds to a forest
718
where the number of trees is the same as the number of elements in the original `meshfile`.
719
Increasing the `initial_refinement_level` allows one to uniformly refine the base mesh given
720
in the `meshfile` to create a forest with more trees before the simulation begins.
721
For example, if a two-dimensional base mesh contains 25 elements then setting
722
`initial_refinement_level=1` creates an initial forest of `2^2 * 25 = 100` trees.
723
724
# Arguments
725
- `meshfile::AbaqusFile{NDIMS}`: Abaqus mesh file object of dimension NDIMS and given `path` to the file.
726
727
# Optional Keyword Arguments
728
- `mapping`: A function of `NDIMS` variables to describe the mapping that transforms
729
the imported mesh to the physical domain. Use `nothing` for the identity map.
730
- `polydeg::Integer`: Polynomial degree used to store the geometry of the mesh.
731
The mapping will be approximated by an interpolation polynomial
732
of the specified degree for each tree.
733
The default of `1` creates an uncurved geometry. Use a higher value if the mapping
734
will curve the imported uncurved mesh.
735
- `RealT::Type`: The type that should be used for coordinates.
736
- `initial_refinement_level::Integer`: Refine the mesh uniformly to this level before the simulation starts.
737
- `boundary_symbols::Vector{Symbol}`: A vector of symbols that correspond to the boundary names in the `meshfile`.
738
If `nothing` is passed then all boundaries are named `:all`.
739
"""
740
function T8codeMesh(meshfile::AbaqusFile{NDIMS};
741
mapping = nothing, polydeg = 1, RealT = Float64,
742
initial_refinement_level = 0,
743
boundary_symbols = nothing) where {NDIMS}
744
# Prevent `t8code` from crashing Julia if the file doesn't exist.
745
@assert isfile(meshfile.path)
746
747
# Read in the Header of the meshfile to determine which constructor is appropriate.
748
header = open(meshfile.path, "r") do io
749
readline(io) # Header of the Abaqus file; discarded
750
return readline(io) # Read in the actual header information
751
end
752
753
# Check if the meshfile was generated using HOHQMesh.
754
if header == " File created by HOHQMesh"
755
# Mesh curvature and boundary naming is handled with additional information available in meshfile
756
connectivity, tree_node_coordinates, nodes, boundary_names = p4est_connectivity_from_hohqmesh_abaqus(meshfile.path,
757
NDIMS,
758
RealT)
759
# Apply user defined mapping.
760
map_node_coordinates!(tree_node_coordinates, mapping)
761
else
762
# Mesh curvature is handled directly by applying the mapping keyword argument.
763
connectivity, tree_node_coordinates, nodes, boundary_names = p4est_connectivity_from_standard_abaqus(meshfile.path,
764
mapping,
765
polydeg,
766
NDIMS,
767
RealT,
768
boundary_symbols)
769
end
770
771
cmesh = t8_cmesh_new_from_connectivity(connectivity, mpi_comm())
772
p4est_connectivity_destroy(connectivity)
773
774
do_face_ghost = mpi_isparallel()
775
scheme = t8_scheme_new_default_cxx()
776
forest = t8_forest_new_uniform(cmesh, scheme, initial_refinement_level, do_face_ghost,
777
mpi_comm())
778
779
return T8codeMesh{NDIMS}(forest, tree_node_coordinates, nodes,
780
boundary_names, "")
781
end
782
783
function t8_cmesh_new_from_connectivity(connectivity::Ptr{p4est_connectivity}, comm)
784
return t8_cmesh_new_from_p4est(connectivity, comm, 0)
785
end
786
787
function t8_cmesh_new_from_connectivity(connectivity::Ptr{p8est_connectivity}, comm)
788
return t8_cmesh_new_from_p8est(connectivity, comm, 0)
789
end
790
791
"""
792
T8codeMeshCubedSphere(trees_per_face_dimension, layers, inner_radius, thickness;
793
polydeg, RealT=Float64, initial_refinement_level=0)
794
795
Construct a cubed spherical shell of given inner radius and thickness as `T8codeMesh` with
796
`6 * trees_per_face_dimension^2 * layers` trees. The mesh will have two boundaries,
797
`:inside` and `:outside`.
798
799
# Arguments
800
- `trees_per_face_dimension::Integer`: number of trees per patch in longitudinal
801
and latitudinal direction.
802
- `layers::Integer`: the number of trees in the third local dimension of each face, i.e.,
803
the number of layers of the shell.
804
- `inner_radius::RealT`: Radius of the inner side of the shell.
805
- `thickness::RealT`: Thickness of the spherical shell. The outer radius will be
806
`inner_radius + thickness`.
807
- `polydeg::Integer`: polynomial degree used to store the geometry of the mesh.
808
The mapping will be approximated by an interpolation polynomial
809
of the specified degree for each tree.
810
- `RealT::Type`: the type that should be used for coordinates.
811
- `initial_refinement_level::Integer`: refine the mesh uniformly to this level before the
812
simulation starts.
813
"""
814
function T8codeMeshCubedSphere(trees_per_face_dimension, layers, inner_radius,
815
thickness;
816
polydeg, RealT = Float64, initial_refinement_level = 0)
817
NDIMS = 3
818
cmesh = t8_cmesh_new_cubed_spherical_shell(inner_radius, thickness,
819
trees_per_face_dimension,
820
layers, mpi_comm())
821
do_face_ghost = mpi_isparallel()
822
scheme = t8_scheme_new_default_cxx()
823
forest = t8_forest_new_uniform(cmesh, scheme, initial_refinement_level, do_face_ghost,
824
mpi_comm())
825
826
num_trees = t8_cmesh_get_num_trees(cmesh)
827
boundary_names = fill(Symbol("---"), 2 * NDIMS, num_trees)
828
for itree in 1:num_trees
829
boundary_names[5, itree] = :inside
830
boundary_names[6, itree] = :outside
831
end
832
833
return T8codeMesh{NDIMS, RealT}(forest, boundary_names; polydeg = polydeg)
834
end
835
836
struct adapt_callback_passthrough
837
adapt_callback::Function
838
user_data::Any
839
end
840
841
# Callback function prototype to decide for refining and coarsening.
842
# If `is_family` equals 1, the first `num_elements` in elements
843
# form a family and we decide whether this family should be coarsened
844
# or only the first element should be refined.
845
# Otherwise `is_family` must equal zero and we consider the first entry
846
# of the element array for refinement.
847
# Entries of the element array beyond the first `num_elements` are undefined.
848
# \param [in] forest the forest to which the new elements belong
849
# \param [in] forest_from the forest that is adapted.
850
# \param [in] which_tree the local tree containing `elements`
851
# \param [in] lelement_id the local element id in `forest_old` in the tree of the current element
852
# \param [in] ts the eclass scheme of the tree
853
# \param [in] is_family if 1, the first `num_elements` entries in `elements` form a family. If 0, they do not.
854
# \param [in] num_elements the number of entries in `elements` that are defined
855
# \param [in] elements Pointers to a family or, if `is_family` is zero,
856
# pointer to one element.
857
# \return greater zero if the first entry in `elements` should be refined,
858
# smaller zero if the family `elements` shall be coarsened,
859
# zero else.
860
function adapt_callback_wrapper(forest,
861
forest_from,
862
which_tree,
863
lelement_id,
864
ts,
865
is_family,
866
num_elements,
867
elements_ptr)::Cint
868
passthrough = unsafe_pointer_to_objref(t8_forest_get_user_data(forest))[]
869
870
elements = unsafe_wrap(Array, elements_ptr, num_elements)
871
872
return passthrough.adapt_callback(forest_from, which_tree, ts, lelement_id, elements,
873
Bool(is_family), passthrough.user_data)
874
end
875
876
"""
877
Trixi.adapt_forest(forest::Ptr{t8_forest}, adapt_callback; kwargs...)
878
879
Adapt a `T8codeMesh` according to a user-defined `adapt_callback`. This
880
function is primarily for internal use. See `Trixi.adapt!(mesh::T8codeMesh,
881
adapt_callback; kwargs...)` for a more detailed documentation.
882
883
# Arguments
884
- `forest::Ptr{t8_forest}`: New forest.
885
- `adapt_callback`: A user-defined callback which tells the adaption routines
886
if an element should be refined, coarsened or stay unchanged.
887
- `kwargs`: Refer to `Trixi.adapt!(mesh::T8codeMesh, adapt_callback; kwargs...)`.
888
889
Note that the old forest usually gets deallocated within t8code. Call
890
`t8_forest_ref(Ref(mesh.forest))` beforehand to prevent that.
891
892
Returns a `Ptr{t8_forest}` to a new forest.
893
"""
894
function adapt_forest(forest::Union{T8code.ForestWrapper, Ptr{t8_forest}}, adapt_callback;
895
recursive = true,
896
balance = true,
897
partition = true, ghost = true, user_data = C_NULL)
898
# Check that forest is a committed, that is valid and usable, forest.
899
@assert t8_forest_is_committed(forest) != 0
900
901
# Init new forest.
902
new_forest_ref = Ref{t8_forest_t}()
903
t8_forest_init(new_forest_ref)
904
new_forest = new_forest_ref[]
905
906
# Check out `examples/t8_step4_partition_balance_ghost.jl` in
907
# https://github.com/DLR-AMR/T8code.jl for detailed explanations.
908
let set_from = C_NULL, set_for_coarsening = 0, no_repartition = !partition
909
t8_forest_set_user_data(new_forest,
910
pointer_from_objref(Ref(adapt_callback_passthrough(adapt_callback,
911
user_data))))
912
t8_forest_set_adapt(new_forest, forest,
913
@t8_adapt_callback(adapt_callback_wrapper),
914
recursive)
915
if balance
916
t8_forest_set_balance(new_forest, set_from, no_repartition)
917
end
918
919
if partition
920
t8_forest_set_partition(new_forest, set_from, set_for_coarsening)
921
end
922
923
if ghost
924
t8_forest_set_ghost(new_forest, ghost, T8_GHOST_FACES)
925
end
926
927
# Julias's GC leads to random segfaults here. Temporarily switch it off.
928
GC.enable(false)
929
930
# The old forest is destroyed here.
931
# Call `t8_forest_ref(Ref(mesh.forest))` to keep it.
932
t8_forest_commit(new_forest)
933
934
GC.enable(true)
935
end
936
937
return new_forest
938
end
939
940
"""
941
Trixi.adapt!(mesh::T8codeMesh, adapt_callback; kwargs...)
942
943
Adapt a `T8codeMesh` according to a user-defined `adapt_callback`.
944
945
# Arguments
946
- `mesh::T8codeMesh`: Initialized mesh object.
947
- `adapt_callback`: A user-defined callback which tells the adaption routines
948
if an element should be refined, coarsened or stay unchanged.
949
950
The expected callback signature is as follows:
951
952
`adapt_callback(forest, ltreeid, eclass_scheme, lelemntid, elements, is_family, user_data)`
953
# Arguments
954
- `forest`: Pointer to the analyzed forest.
955
- `ltreeid`: Local index of the current tree where the analyzed elements are part of.
956
- `eclass_scheme`: Element class of `elements`.
957
- `lelemntid`: Local index of the first element in `elements`.
958
- `elements`: Array of elements. If consecutive elements form a family
959
they are passed together, otherwise `elements` consists of just one element.
960
- `is_family`: Boolean signifying if `elements` represents a family or not.
961
- `user_data`: Void pointer to some arbitrary user data. Default value is `C_NULL`.
962
# Returns
963
-1 : Coarsen family of elements.
964
0 : Stay unchanged.
965
1 : Refine element.
966
967
- `kwargs`:
968
- `recursive = true`: Adapt the forest recursively. If true the caller must ensure that the callback
969
returns 0 for every analyzed element at some point to stop the recursion.
970
- `balance = true`: Make sure the adapted forest is 2^(NDIMS-1):1 balanced.
971
- `partition = true`: Partition the forest to redistribute elements evenly among MPI ranks.
972
- `ghost = true`: Create a ghost layer for MPI data exchange.
973
- `user_data = C_NULL`: Pointer to some arbitrary user-defined data.
974
975
Returns `nothing`.
976
"""
977
function adapt!(mesh::T8codeMesh, adapt_callback; kwargs...)
978
# Call `t8_forest_ref(Ref(mesh.forest))` to keep it.
979
update_forest!(mesh, adapt_forest(mesh.forest, adapt_callback; kwargs...))
980
return nothing
981
end
982
983
function balance_forest(forest::Union{T8code.ForestWrapper, Ptr{t8_forest}})
984
new_forest_ref = Ref{t8_forest_t}()
985
t8_forest_init(new_forest_ref)
986
new_forest = new_forest_ref[]
987
988
let set_from = forest, no_repartition = 1, do_ghost = 1
989
t8_forest_set_balance(new_forest, set_from, no_repartition)
990
t8_forest_set_ghost(new_forest, do_ghost, T8_GHOST_FACES)
991
t8_forest_commit(new_forest)
992
end
993
994
return new_forest
995
end
996
997
"""
998
Trixi.balance!(mesh::T8codeMesh)
999
1000
Balance a `T8codeMesh` to ensure 2^(NDIMS-1):1 face neighbors.
1001
1002
# Arguments
1003
- `mesh::T8codeMesh`: Initialized mesh object.
1004
1005
Returns `nothing`.
1006
"""
1007
function balance!(mesh::T8codeMesh)
1008
update_forest!(mesh, balance_forest(mesh.forest))
1009
return nothing
1010
end
1011
1012
function partition_forest(forest::Union{T8code.ForestWrapper, Ptr{t8_forest}})
1013
new_forest_ref = Ref{t8_forest_t}()
1014
t8_forest_init(new_forest_ref)
1015
new_forest = new_forest_ref[]
1016
1017
let set_from = forest, do_ghost = 1, allow_for_coarsening = 1
1018
t8_forest_set_partition(new_forest, set_from, allow_for_coarsening)
1019
t8_forest_set_ghost(new_forest, do_ghost, T8_GHOST_FACES)
1020
t8_forest_commit(new_forest)
1021
end
1022
1023
return new_forest
1024
end
1025
1026
"""
1027
Trixi.partition!(mesh::T8codeMesh)
1028
1029
Partition a `T8codeMesh` in order to redistribute elements evenly among MPI ranks.
1030
1031
# Arguments
1032
- `mesh::T8codeMesh`: Initialized mesh object.
1033
1034
Returns `nothing`.
1035
"""
1036
function partition!(mesh::T8codeMesh)
1037
update_forest!(mesh, partition_forest(mesh.forest))
1038
return nothing
1039
end
1040
1041
# Compute the global ids (zero-indexed) of first element in each MPI rank.
1042
function get_global_first_element_ids(mesh::T8codeMesh)
1043
n_elements_local = Int(t8_forest_get_local_num_elements(mesh.forest))
1044
n_elements_by_rank = Vector{Int}(undef, mpi_nranks())
1045
n_elements_by_rank[mpi_rank() + 1] = n_elements_local
1046
MPI.Allgather!(MPI.UBuffer(n_elements_by_rank, 1), mpi_comm())
1047
return [sum(n_elements_by_rank[1:(rank - 1)]) for rank in 1:(mpi_nranks() + 1)]
1048
end
1049
1050
function count_interfaces(mesh::T8codeMesh)
1051
return count_interfaces(mesh.forest, ndims(mesh))
1052
end
1053
1054
function count_interfaces(forest, ndims)
1055
@assert t8_forest_is_committed(forest) != 0
1056
1057
num_local_elements = t8_forest_get_local_num_elements(forest)
1058
num_local_trees = t8_forest_get_num_local_trees(forest)
1059
1060
current_index = t8_locidx_t(0)
1061
1062
local_num_conform = 0
1063
local_num_mortars = 0
1064
local_num_boundary = 0
1065
1066
local_num_mpi_conform = 0
1067
local_num_mpi_mortars = 0
1068
1069
visited_global_mortar_ids = Set{UInt128}([])
1070
1071
max_level = t8_forest_get_maxlevel(forest)
1072
max_tree_num_elements = UInt128(2^ndims)^max_level
1073
1074
if mpi_isparallel()
1075
ghost_num_trees = t8_forest_ghost_num_trees(forest)
1076
1077
ghost_tree_element_offsets = [num_local_elements +
1078
t8_forest_ghost_get_tree_element_offset(forest,
1079
itree)
1080
for itree in 0:(ghost_num_trees - 1)]
1081
ghost_global_treeids = [t8_forest_ghost_get_global_treeid(forest, itree)
1082
for itree in 0:(ghost_num_trees - 1)]
1083
end
1084
1085
for itree in 0:(num_local_trees - 1)
1086
tree_class = t8_forest_get_tree_class(forest, itree)
1087
eclass_scheme = t8_forest_get_eclass_scheme(forest, tree_class)
1088
1089
num_elements_in_tree = t8_forest_get_tree_num_elements(forest, itree)
1090
1091
global_itree = t8_forest_global_tree_id(forest, itree)
1092
1093
for ielement in 0:(num_elements_in_tree - 1)
1094
element = t8_forest_get_element_in_tree(forest, itree, ielement)
1095
1096
level = t8_element_level(eclass_scheme, element)
1097
1098
num_faces = t8_element_num_faces(eclass_scheme, element)
1099
1100
# Note: This works only for forests of one element class.
1101
current_linear_id = global_itree * max_tree_num_elements +
1102
t8_element_get_linear_id(eclass_scheme, element, max_level)
1103
1104
for iface in 0:(num_faces - 1)
1105
pelement_indices_ref = Ref{Ptr{t8_locidx_t}}()
1106
pneighbor_leaves_ref = Ref{Ptr{Ptr{t8_element}}}()
1107
pneigh_scheme_ref = Ref{Ptr{t8_eclass_scheme}}()
1108
1109
dual_faces_ref = Ref{Ptr{Cint}}()
1110
num_neighbors_ref = Ref{Cint}()
1111
1112
forest_is_balanced = Cint(1)
1113
1114
t8_forest_leaf_face_neighbors(forest, itree, element,
1115
pneighbor_leaves_ref, iface, dual_faces_ref,
1116
num_neighbors_ref,
1117
pelement_indices_ref, pneigh_scheme_ref,
1118
forest_is_balanced)
1119
1120
num_neighbors = num_neighbors_ref[]
1121
dual_faces = unsafe_wrap(Array, dual_faces_ref[], num_neighbors)
1122
neighbor_ielements = unsafe_wrap(Array, pelement_indices_ref[],
1123
num_neighbors)
1124
neighbor_leaves = unsafe_wrap(Array, pneighbor_leaves_ref[], num_neighbors)
1125
neighbor_scheme = pneigh_scheme_ref[]
1126
1127
if num_neighbors == 0
1128
local_num_boundary += 1
1129
else
1130
neighbor_level = t8_element_level(neighbor_scheme, neighbor_leaves[1])
1131
1132
if all(neighbor_ielements .< num_local_elements)
1133
# Conforming interface: The second condition ensures we
1134
# only visit the interface once.
1135
if level == neighbor_level && current_index <= neighbor_ielements[1]
1136
local_num_conform += 1
1137
elseif level < neighbor_level
1138
local_num_mortars += 1
1139
# `else level > neighbor_level` is ignored since we
1140
# only want to count the mortar interface once.
1141
end
1142
else
1143
if level == neighbor_level
1144
local_num_mpi_conform += 1
1145
elseif level < neighbor_level
1146
local_num_mpi_mortars += 1
1147
1148
global_mortar_id = 2 * ndims * current_linear_id + iface
1149
1150
else # level > neighbor_level
1151
neighbor_global_ghost_itree = ghost_global_treeids[findlast(ghost_tree_element_offsets .<=
1152
neighbor_ielements[1])]
1153
neighbor_linear_id = neighbor_global_ghost_itree *
1154
max_tree_num_elements +
1155
t8_element_get_linear_id(neighbor_scheme,
1156
neighbor_leaves[1],
1157
max_level)
1158
global_mortar_id = 2 * ndims * neighbor_linear_id +
1159
dual_faces[1]
1160
1161
if !(global_mortar_id in visited_global_mortar_ids)
1162
push!(visited_global_mortar_ids, global_mortar_id)
1163
local_num_mpi_mortars += 1
1164
end
1165
end
1166
end
1167
1168
t8_element_destroy(neighbor_scheme, num_neighbors, neighbor_leaves)
1169
t8_free(dual_faces_ref[])
1170
t8_free(pneighbor_leaves_ref[])
1171
t8_free(pelement_indices_ref[])
1172
end
1173
end # for
1174
1175
current_index += 1
1176
end # for
1177
end # for
1178
1179
return (interfaces = local_num_conform,
1180
mortars = local_num_mortars,
1181
boundaries = local_num_boundary,
1182
mpi_interfaces = local_num_mpi_conform,
1183
mpi_mortars = local_num_mpi_mortars)
1184
end
1185
1186
# I know this routine is an unmaintainable behemoth. However, I see no real
1187
# and elegant way to refactor this into, for example, smaller parts. The
1188
# `t8_forest_leaf_face_neighbors` routine is as of now rather costly and it
1189
# makes sense to query it only once per face per element and extract all the
1190
# information needed at once in order to fill the connectivity information.
1191
# Instead, I opted for good documentation.
1192
function fill_mesh_info!(mesh::T8codeMesh, interfaces, mortars, boundaries,
1193
boundary_names; mpi_mesh_info = nothing)
1194
@assert t8_forest_is_committed(mesh.forest) != 0
1195
1196
num_local_elements = t8_forest_get_local_num_elements(mesh.forest)
1197
num_local_trees = t8_forest_get_num_local_trees(mesh.forest)
1198
1199
if !isnothing(mpi_mesh_info)
1200
#! format: off
1201
remotes = t8_forest_ghost_get_remotes(mesh.forest)
1202
ghost_num_trees = t8_forest_ghost_num_trees(mesh.forest)
1203
1204
ghost_remote_first_elem = [num_local_elements +
1205
t8_forest_ghost_remote_first_elem(mesh.forest, remote)
1206
for remote in remotes]
1207
1208
ghost_tree_element_offsets = [num_local_elements +
1209
t8_forest_ghost_get_tree_element_offset(mesh.forest, itree)
1210
for itree in 0:(ghost_num_trees - 1)]
1211
1212
ghost_global_treeids = [t8_forest_ghost_get_global_treeid(mesh.forest, itree)
1213
for itree in 0:(ghost_num_trees - 1)]
1214
#! format: on
1215
end
1216
1217
# Process-local index of the current element in the space-filling curve.
1218
current_index = t8_locidx_t(0)
1219
1220
# Increment counters for the different interface/mortar/boundary types.
1221
local_num_conform = 0
1222
local_num_mortars = 0
1223
local_num_boundary = 0
1224
1225
local_num_mpi_conform = 0
1226
local_num_mpi_mortars = 0
1227
1228
# Works for quads and hexs only. This mapping is needed in the MPI mortar
1229
# sections below.
1230
map_iface_to_ichild_to_position = [
1231
# 0 1 2 3 4 5 6 7 ichild/iface
1232
[1, 0, 2, 0, 3, 0, 4, 0], # 0
1233
[0, 1, 0, 2, 0, 3, 0, 4], # 1
1234
[1, 2, 0, 0, 3, 4, 0, 0], # 2
1235
[0, 0, 1, 2, 0, 0, 3, 4], # 3
1236
[1, 2, 3, 4, 0, 0, 0, 0], # 4
1237
[0, 0, 0, 0, 1, 2, 3, 4] # 5
1238
]
1239
1240
# Helper variables to compute unique global MPI interface/mortar ids.
1241
max_level = t8_forest_get_maxlevel(mesh.forest)
1242
max_tree_num_elements = UInt128(2^ndims(mesh))^max_level
1243
1244
# These two variables help to ensure that we count MPI mortars from smaller
1245
# elements point of view only once.
1246
visited_global_mortar_ids = Set{UInt128}([])
1247
global_mortar_id_to_local = Dict{UInt128, Int}([])
1248
1249
cmesh = t8_forest_get_cmesh(mesh.forest)
1250
1251
# Loop over all local trees.
1252
for itree in 0:(num_local_trees - 1)
1253
tree_class = t8_forest_get_tree_class(mesh.forest, itree)
1254
eclass_scheme = t8_forest_get_eclass_scheme(mesh.forest, tree_class)
1255
1256
num_elements_in_tree = t8_forest_get_tree_num_elements(mesh.forest, itree)
1257
1258
global_itree = t8_forest_global_tree_id(mesh.forest, itree)
1259
1260
# Loop over all local elements of the current local tree.
1261
for ielement in 0:(num_elements_in_tree - 1)
1262
element = t8_forest_get_element_in_tree(mesh.forest, itree, ielement)
1263
1264
level = t8_element_level(eclass_scheme, element)
1265
1266
num_faces = t8_element_num_faces(eclass_scheme, element)
1267
1268
# Note: This works only for forests of one element class.
1269
current_linear_id = global_itree * max_tree_num_elements +
1270
t8_element_get_linear_id(eclass_scheme, element, max_level)
1271
1272
# Loop over all faces of the current local element.
1273
for iface in 0:(num_faces - 1)
1274
pelement_indices_ref = Ref{Ptr{t8_locidx_t}}()
1275
pneighbor_leaves_ref = Ref{Ptr{Ptr{t8_element}}}()
1276
pneigh_scheme_ref = Ref{Ptr{t8_eclass_scheme}}()
1277
1278
dual_faces_ref = Ref{Ptr{Cint}}()
1279
num_neighbors_ref = Ref{Cint}()
1280
1281
forest_is_balanced = Cint(1)
1282
1283
# Query neighbor information from t8code.
1284
t8_forest_leaf_face_neighbors(mesh.forest, itree, element,
1285
pneighbor_leaves_ref, iface, dual_faces_ref,
1286
num_neighbors_ref,
1287
pelement_indices_ref, pneigh_scheme_ref,
1288
forest_is_balanced)
1289
1290
num_neighbors = num_neighbors_ref[]
1291
dual_faces = unsafe_wrap(Array, dual_faces_ref[], num_neighbors)
1292
neighbor_ielements = unsafe_wrap(Array, pelement_indices_ref[],
1293
num_neighbors)
1294
neighbor_leaves = unsafe_wrap(Array, pneighbor_leaves_ref[], num_neighbors)
1295
neighbor_scheme = pneigh_scheme_ref[]
1296
1297
# Now we check for the different cases. The nested if-structure is as follows:
1298
#
1299
# if `boundary`:
1300
# <fill boundary info>
1301
#
1302
# else: // It must be an interface or mortar.
1303
#
1304
# if `all neighbors are local elements`:
1305
#
1306
# if `local interface`:
1307
# <fill interface info>
1308
# elseif `local mortar from larger element point of view`:
1309
# <fill mortar info>
1310
# else: // `local mortar from smaller elements point of view`
1311
# <skip> // We only count local mortars once.
1312
#
1313
# else: // It must be either a MPI interface or a MPI mortar.
1314
#
1315
# if `MPI interface`:
1316
# <fill MPI interface info>
1317
# elseif `MPI mortar from larger element point of view`:
1318
# <fill MPI mortar info>
1319
# else: // `MPI mortar from smaller elements point of view`
1320
# <fill MPI mortar info>
1321
#
1322
# // end
1323
1324
# Domain boundary.
1325
if num_neighbors == 0
1326
local_num_boundary += 1
1327
boundary_id = local_num_boundary
1328
1329
boundaries.neighbor_ids[boundary_id] = current_index + 1
1330
1331
init_boundary_node_indices!(boundaries, iface, boundary_id)
1332
1333
# One-based indexing.
1334
boundaries.name[boundary_id] = boundary_names[iface + 1, itree + 1]
1335
1336
# Interface or mortar.
1337
else
1338
neighbor_level = t8_element_level(neighbor_scheme, neighbor_leaves[1])
1339
1340
# Compute the `orientation` of the touching faces.
1341
if t8_element_is_root_boundary(eclass_scheme, element, iface) == 1
1342
itree_in_cmesh = t8_forest_ltreeid_to_cmesh_ltreeid(mesh.forest,
1343
itree)
1344
iface_in_tree = t8_element_tree_face(eclass_scheme, element, iface)
1345
orientation_ref = Ref{Cint}()
1346
1347
t8_cmesh_get_face_neighbor(cmesh, itree_in_cmesh, iface_in_tree,
1348
C_NULL,
1349
orientation_ref)
1350
orientation = orientation_ref[]
1351
else
1352
orientation = zero(Cint)
1353
end
1354
1355
# Local interface or mortar.
1356
if all(neighbor_ielements .< num_local_elements)
1357
1358
# Local interface: The second condition ensures we only visit the interface once.
1359
if level == neighbor_level && current_index <= neighbor_ielements[1]
1360
local_num_conform += 1
1361
1362
interfaces.neighbor_ids[1, local_num_conform] = current_index +
1363
1
1364
interfaces.neighbor_ids[2, local_num_conform] = neighbor_ielements[1] +
1365
1
1366
1367
init_interface_node_indices!(interfaces, (iface, dual_faces[1]),
1368
orientation,
1369
local_num_conform)
1370
# Local mortar.
1371
elseif level < neighbor_level
1372
local_num_mortars += 1
1373
1374
# Last entry is the large element.
1375
mortars.neighbor_ids[end, local_num_mortars] = current_index + 1
1376
1377
init_mortar_neighbor_ids!(mortars, iface, dual_faces[1],
1378
orientation, neighbor_ielements,
1379
local_num_mortars)
1380
1381
init_mortar_node_indices!(mortars, (dual_faces[1], iface),
1382
orientation, local_num_mortars)
1383
1384
# else: `level > neighbor_level` is skipped since we visit the mortar interface only once.
1385
end
1386
1387
# MPI interface or MPI mortar.
1388
else
1389
1390
# MPI interface.
1391
if level == neighbor_level
1392
local_num_mpi_conform += 1
1393
1394
neighbor_global_ghost_itree = ghost_global_treeids[findlast(ghost_tree_element_offsets .<=
1395
neighbor_ielements[1])]
1396
1397
neighbor_linear_id = neighbor_global_ghost_itree *
1398
max_tree_num_elements +
1399
t8_element_get_linear_id(neighbor_scheme,
1400
neighbor_leaves[1],
1401
max_level)
1402
1403
if current_linear_id < neighbor_linear_id
1404
local_side = 1
1405
smaller_iface = iface
1406
smaller_linear_id = current_linear_id
1407
faces = (iface, dual_faces[1])
1408
else
1409
local_side = 2
1410
smaller_iface = dual_faces[1]
1411
smaller_linear_id = neighbor_linear_id
1412
faces = (dual_faces[1], iface)
1413
end
1414
1415
global_interface_id = 2 * ndims(mesh) * smaller_linear_id +
1416
smaller_iface
1417
1418
mpi_mesh_info.mpi_interfaces.local_neighbor_ids[local_num_mpi_conform] = current_index +
1419
1
1420
mpi_mesh_info.mpi_interfaces.local_sides[local_num_mpi_conform] = local_side
1421
1422
init_mpi_interface_node_indices!(mpi_mesh_info.mpi_interfaces,
1423
faces, local_side, orientation,
1424
local_num_mpi_conform)
1425
1426
neighbor_rank = remotes[findlast(ghost_remote_first_elem .<=
1427
neighbor_ielements[1])]
1428
mpi_mesh_info.neighbor_ranks_interface[local_num_mpi_conform] = neighbor_rank
1429
1430
mpi_mesh_info.global_interface_ids[local_num_mpi_conform] = global_interface_id
1431
1432
# MPI Mortar: from larger element point of view
1433
elseif level < neighbor_level
1434
local_num_mpi_mortars += 1
1435
1436
global_mortar_id = 2 * ndims(mesh) * current_linear_id + iface
1437
1438
neighbor_ids = neighbor_ielements .+ 1
1439
1440
local_neighbor_positions = findall(neighbor_ids .<=
1441
num_local_elements)
1442
local_neighbor_ids = [neighbor_ids[i]
1443
for i in local_neighbor_positions]
1444
local_neighbor_positions = [map_iface_to_ichild_to_position[dual_faces[1] + 1][t8_element_child_id(neighbor_scheme, neighbor_leaves[i]) + 1]
1445
for i in local_neighbor_positions]
1446
1447
# Last entry is the large element.
1448
push!(local_neighbor_ids, current_index + 1)
1449
push!(local_neighbor_positions, 2^(ndims(mesh) - 1) + 1)
1450
1451
mpi_mesh_info.mpi_mortars.local_neighbor_ids[local_num_mpi_mortars] = local_neighbor_ids
1452
mpi_mesh_info.mpi_mortars.local_neighbor_positions[local_num_mpi_mortars] = local_neighbor_positions
1453
1454
init_mortar_node_indices!(mpi_mesh_info.mpi_mortars,
1455
(dual_faces[1], iface), orientation,
1456
local_num_mpi_mortars)
1457
1458
neighbor_ranks = [remotes[findlast(ghost_remote_first_elem .<=
1459
ineighbor_ghost)]
1460
for ineighbor_ghost in filter(x -> x >=
1461
num_local_elements,
1462
neighbor_ielements)]
1463
mpi_mesh_info.neighbor_ranks_mortar[local_num_mpi_mortars] = neighbor_ranks
1464
1465
mpi_mesh_info.global_mortar_ids[local_num_mpi_mortars] = global_mortar_id
1466
1467
# MPI Mortar: from smaller elements point of view
1468
else
1469
neighbor_global_ghost_itree = ghost_global_treeids[findlast(ghost_tree_element_offsets .<=
1470
neighbor_ielements[1])]
1471
neighbor_linear_id = neighbor_global_ghost_itree *
1472
max_tree_num_elements +
1473
t8_element_get_linear_id(neighbor_scheme,
1474
neighbor_leaves[1],
1475
max_level)
1476
global_mortar_id = 2 * ndims(mesh) * neighbor_linear_id +
1477
dual_faces[1]
1478
1479
if global_mortar_id in visited_global_mortar_ids
1480
local_mpi_mortar_id = global_mortar_id_to_local[global_mortar_id]
1481
1482
push!(mpi_mesh_info.mpi_mortars.local_neighbor_ids[local_mpi_mortar_id],
1483
current_index + 1)
1484
push!(mpi_mesh_info.mpi_mortars.local_neighbor_positions[local_mpi_mortar_id],
1485
map_iface_to_ichild_to_position[iface + 1][t8_element_child_id(eclass_scheme, element) + 1])
1486
else
1487
local_num_mpi_mortars += 1
1488
local_mpi_mortar_id = local_num_mpi_mortars
1489
push!(visited_global_mortar_ids, global_mortar_id)
1490
global_mortar_id_to_local[global_mortar_id] = local_mpi_mortar_id
1491
1492
mpi_mesh_info.mpi_mortars.local_neighbor_ids[local_mpi_mortar_id] = [
1493
current_index + 1
1494
]
1495
mpi_mesh_info.mpi_mortars.local_neighbor_positions[local_mpi_mortar_id] = [
1496
map_iface_to_ichild_to_position[iface + 1][t8_element_child_id(eclass_scheme, element) + 1]
1497
]
1498
init_mortar_node_indices!(mpi_mesh_info.mpi_mortars,
1499
(iface, dual_faces[1]),
1500
orientation, local_mpi_mortar_id)
1501
1502
neighbor_ranks = [
1503
remotes[findlast(ghost_remote_first_elem .<=
1504
neighbor_ielements[1])]
1505
]
1506
mpi_mesh_info.neighbor_ranks_mortar[local_mpi_mortar_id] = neighbor_ranks
1507
1508
mpi_mesh_info.global_mortar_ids[local_mpi_mortar_id] = global_mortar_id
1509
end
1510
end
1511
end
1512
1513
t8_element_destroy(neighbor_scheme, num_neighbors, neighbor_leaves)
1514
t8_free(dual_faces_ref[])
1515
t8_free(pneighbor_leaves_ref[])
1516
t8_free(pelement_indices_ref[])
1517
end # num_neighbors
1518
end # for iface
1519
1520
current_index += 1
1521
end # for ielement
1522
end # for itree
1523
1524
return nothing
1525
end
1526
1527
function get_levels(mesh::T8codeMesh)
1528
return trixi_t8_get_local_element_levels(mesh.forest)
1529
end
1530
1531
function get_cmesh_info(mesh::T8codeMesh)
1532
@assert t8_forest_is_committed(mesh.forest) == 1
1533
cmesh = t8_forest_get_cmesh(mesh.forest)
1534
return get_cmesh_info(cmesh, ndims(mesh))
1535
end
1536
1537
# Note, `cmesh` is not partitioned as of now.
1538
# Every MPI rank has a full copy of the `cmesh`.
1539
function get_cmesh_info(cmesh::Ptr{t8_cmesh}, ndims)
1540
num_trees = t8_cmesh_get_num_trees(cmesh)
1541
num_faces = 2 * ndims
1542
1543
num_interfaces = 0
1544
1545
dual_face_ref = Ref{Cint}()
1546
orientation_ref = Ref{Cint}()
1547
1548
# Count connected faces.
1549
for itree in 0:(num_trees - 1)
1550
for iface in 0:(num_faces - 1)
1551
neigh_itree = t8_cmesh_get_face_neighbor(cmesh, itree, iface, dual_face_ref,
1552
C_NULL)
1553
if itree < neigh_itree || itree == neigh_itree && iface < dual_face_ref[]
1554
num_interfaces += 1
1555
end
1556
end
1557
end
1558
1559
# Allocate arrays.
1560
treeIDs = zeros(Int, num_interfaces)
1561
neighIDs = zeros(Int, num_interfaces)
1562
orientations = zeros(Int8, num_interfaces)
1563
faces = zeros(Int8, num_interfaces)
1564
duals = zeros(Int8, num_interfaces)
1565
1566
itf_index = 0 # interface index
1567
1568
for itree in 0:(num_trees - 1)
1569
for iface in 0:(num_faces - 1)
1570
neigh_itree = t8_cmesh_get_face_neighbor(cmesh, itree, iface, dual_face_ref,
1571
orientation_ref)
1572
1573
if itree < neigh_itree || itree == neigh_itree && iface < dual_face_ref[]
1574
itf_index += 1
1575
treeIDs[itf_index] = itree
1576
neighIDs[itf_index] = neigh_itree
1577
orientations[itf_index] = orientation_ref[]
1578
faces[itf_index] = iface
1579
duals[itf_index] = dual_face_ref[]
1580
end
1581
end
1582
end
1583
1584
return treeIDs, neighIDs, faces, duals, orientations
1585
end
1586
1587