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