Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
trixi-framework
GitHub Repository: trixi-framework/Trixi.jl
Path: blob/main/src/meshes/p4est_mesh.jl
2055 views
1
# By default, Julia/LLVM does not use fused multiply-add operations (FMAs).
2
# Since these FMAs can increase the performance of many numerical algorithms,
3
# we need to opt-in explicitly.
4
# See https://ranocha.de/blog/Optimizing_EC_Trixi for further details.
5
@muladd begin
6
#! format: noindent
7
8
"""
9
P4estMesh{NDIMS, NDIMS_AMBIENT} <: AbstractMesh{NDIMS}
10
11
An unstructured curved mesh based on trees that uses the C library `p4est`
12
to manage trees and mesh refinement.
13
14
The parameter `NDIMS` denotes the dimension of the spatial domain or manifold represented
15
by the mesh itself, while `NDIMS_AMBIENT` denotes the dimension of the ambient space in
16
which the mesh is embedded. For example, the type `P4estMesh{3, 3}` corresponds to a
17
standard mesh for a three-dimensional volume, whereas `P4estMesh{2, 3}` corresponds to a
18
mesh for a two-dimensional surface or shell in three-dimensional space.
19
20
!!! warning "Experimental implementation"
21
The use of `NDIMS != NDIMS_AMBIENT` is an experimental feature and may change in future
22
releases.
23
"""
24
mutable struct P4estMesh{NDIMS, NDIMS_AMBIENT, RealT <: Real, IsParallel, P, Ghost,
25
NDIMSP2, NNODES} <:
26
AbstractMesh{NDIMS}
27
p4est :: P # Either PointerWrapper{p4est_t} or PointerWrapper{p8est_t}
28
is_parallel :: IsParallel
29
ghost :: Ghost # Either PointerWrapper{p4est_ghost_t} or PointerWrapper{p8est_ghost_t}
30
# Coordinates at the nodes specified by the tensor product of `nodes` (NDIMS times).
31
# This specifies the geometry interpolation for each tree.
32
tree_node_coordinates::Array{RealT, NDIMSP2} # [dimension, i, j, k, tree]
33
nodes::SVector{NNODES, RealT}
34
boundary_names::Array{Symbol, 2} # [face direction, tree]
35
current_filename::String
36
unsaved_changes::Bool
37
p4est_partition_allow_for_coarsening::Bool
38
39
function P4estMesh{NDIMS}(p4est, tree_node_coordinates, nodes, boundary_names,
40
current_filename, unsaved_changes,
41
p4est_partition_allow_for_coarsening) where {NDIMS}
42
if NDIMS == 2
43
@assert p4est isa Ptr{p4est_t}
44
elseif NDIMS == 3
45
@assert p4est isa Ptr{p8est_t}
46
end
47
48
if mpi_isparallel()
49
if !P4est.uses_mpi()
50
error("p4est library does not support MPI")
51
end
52
is_parallel = True()
53
else
54
is_parallel = False()
55
end
56
57
p4est_pw = PointerWrapper(p4est)
58
59
ghost = ghost_new_p4est(p4est)
60
ghost_pw = PointerWrapper(ghost)
61
62
# To enable the treatment of a manifold of dimension NDIMS embedded within an
63
# ambient space of dimension NDIMS_AMBIENT, we store both as type parameters and
64
# allow them to differ in the general case. This functionality is used for
65
# constructing discretizations on spherical shell domains for applications in
66
# global atmospheric modelling. The ambient dimension NDIMS_AMBIENT is therefore
67
# set here in the inner constructor to size(tree_node_coordinates, 1).
68
mesh = new{NDIMS, size(tree_node_coordinates, 1),
69
eltype(tree_node_coordinates), typeof(is_parallel),
70
typeof(p4est_pw), typeof(ghost_pw), NDIMS + 2, length(nodes)}(p4est_pw,
71
is_parallel,
72
ghost_pw,
73
tree_node_coordinates,
74
nodes,
75
boundary_names,
76
current_filename,
77
unsaved_changes,
78
p4est_partition_allow_for_coarsening)
79
80
# Destroy `p4est` structs when the mesh is garbage collected
81
finalizer(destroy_mesh, mesh)
82
83
return mesh
84
end
85
end
86
87
const SerialP4estMesh{NDIMS} = P4estMesh{NDIMS, <:Any, <:Real, <:False}
88
const ParallelP4estMesh{NDIMS} = P4estMesh{NDIMS, <:Any, <:Real, <:True}
89
90
@inline mpi_parallel(mesh::SerialP4estMesh) = False()
91
@inline mpi_parallel(mesh::ParallelP4estMesh) = True()
92
93
function destroy_mesh(mesh::P4estMesh{2})
94
connectivity = mesh.p4est.connectivity
95
p4est_ghost_destroy(mesh.ghost)
96
p4est_destroy(mesh.p4est)
97
p4est_connectivity_destroy(connectivity)
98
end
99
100
function destroy_mesh(mesh::P4estMesh{3})
101
connectivity = mesh.p4est.connectivity
102
p8est_ghost_destroy(mesh.ghost)
103
p8est_destroy(mesh.p4est)
104
p8est_connectivity_destroy(connectivity)
105
end
106
107
@inline Base.ndims(::P4estMesh{NDIMS}) where {NDIMS} = NDIMS
108
@inline Base.real(::P4estMesh{NDIMS, NDIMS_AMBIENT, RealT}) where {NDIMS, NDIMS_AMBIENT, RealT} = RealT
109
@inline ndims_ambient(::P4estMesh{NDIMS, NDIMS_AMBIENT}) where {NDIMS, NDIMS_AMBIENT} = NDIMS_AMBIENT
110
111
@inline function ntrees(mesh::P4estMesh)
112
return mesh.p4est.trees.elem_count[]
113
end
114
# returns Int32 by default which causes a weird method error when creating the cache
115
@inline ncells(mesh::P4estMesh) = Int(mesh.p4est.local_num_quadrants[])
116
@inline ncellsglobal(mesh::P4estMesh) = Int(mesh.p4est.global_num_quadrants[])
117
118
function Base.show(io::IO, mesh::P4estMesh)
119
print(io, "P4estMesh{", ndims(mesh), ", ", ndims_ambient(mesh), ", ", real(mesh),
120
"}")
121
end
122
123
function Base.show(io::IO, ::MIME"text/plain", mesh::P4estMesh)
124
if get(io, :compact, false)
125
show(io, mesh)
126
else
127
setup = [
128
"#trees" => ntrees(mesh),
129
"current #cells" => ncellsglobal(mesh),
130
"polydeg" => length(mesh.nodes) - 1
131
]
132
summary_box(io,
133
"P4estMesh{" * string(ndims(mesh)) * ", " *
134
string(ndims_ambient(mesh)) *
135
", " * string(real(mesh)) * "}", setup)
136
end
137
end
138
139
"""
140
P4estMesh(trees_per_dimension; polydeg,
141
mapping=nothing, faces=nothing, coordinates_min=nothing, coordinates_max=nothing,
142
RealT=Float64, initial_refinement_level=0, periodicity=true, unsaved_changes=true,
143
p4est_partition_allow_for_coarsening=true)
144
145
Create a structured curved/higher-order `P4estMesh` of the specified size.
146
147
There are three ways to map the mesh to the physical domain.
148
1. Define a `mapping` that maps the hypercube `[-1, 1]^n`.
149
2. Specify a `Tuple` `faces` of functions that parametrize each face.
150
3. Create a rectangular mesh by specifying `coordinates_min` and `coordinates_max`.
151
152
Non-periodic boundaries will be called `:x_neg`, `:x_pos`, `:y_neg`, `:y_pos`, `:z_neg`, `:z_pos`.
153
154
# Arguments
155
- `trees_per_dimension::NTupleE{NDIMS, Int}`: the number of trees in each dimension.
156
- `polydeg::Integer`: polynomial degree used to store the geometry of the mesh.
157
The mapping will be approximated by an interpolation polynomial
158
of the specified degree for each tree.
159
- `mapping`: a function of `NDIMS` variables to describe the mapping that transforms
160
the reference mesh (`[-1, 1]^n`) to the physical domain.
161
Use only one of `mapping`, `faces` and `coordinates_min`/`coordinates_max`.
162
- `faces::NTuple{2*NDIMS}`: a tuple of `2 * NDIMS` functions that describe the faces of the domain.
163
Each function must take `NDIMS-1` arguments.
164
`faces[1]` describes the face onto which the face in negative x-direction
165
of the unit hypercube is mapped. The face in positive x-direction of
166
the unit hypercube will be mapped onto the face described by `faces[2]`.
167
`faces[3:4]` describe the faces in positive and negative y-direction respectively
168
(in 2D and 3D).
169
`faces[5:6]` describe the faces in positive and negative z-direction respectively (in 3D).
170
Use only one of `mapping`, `faces` and `coordinates_min`/`coordinates_max`.
171
- `coordinates_min`: vector or tuple of the coordinates of the corner in the negative direction of each dimension
172
to create a rectangular mesh.
173
Use only one of `mapping`, `faces` and `coordinates_min`/`coordinates_max`.
174
- `coordinates_max`: vector or tuple of the coordinates of the corner in the positive direction of each dimension
175
to create a rectangular mesh.
176
Use only one of `mapping`, `faces` and `coordinates_min`/`coordinates_max`.
177
- `RealT::Type`: the type that should be used for coordinates.
178
- `initial_refinement_level::Integer`: refine the mesh uniformly to this level before the simulation starts.
179
- `periodicity`: either a `Bool` deciding if all of the boundaries are periodic or an `NTuple{NDIMS, Bool}`
180
deciding for each dimension if the boundaries in this dimension are periodic.
181
- `unsaved_changes::Bool`: if set to `true`, the mesh will be saved to a mesh file.
182
- `p4est_partition_allow_for_coarsening::Bool`: Must be `true` when using AMR to make mesh adaptivity
183
independent of domain partitioning. Should be `false` for static meshes
184
to permit more fine-grained partitioning.
185
"""
186
function P4estMesh(trees_per_dimension; polydeg,
187
mapping = nothing, faces = nothing, coordinates_min = nothing,
188
coordinates_max = nothing,
189
RealT = Float64, initial_refinement_level = 0, periodicity = true,
190
unsaved_changes = true,
191
p4est_partition_allow_for_coarsening = true)
192
@assert ((coordinates_min === nothing)===(coordinates_max === nothing)) "Either both or none of coordinates_min and coordinates_max must be specified"
193
194
coordinates_min_max_check(coordinates_min, coordinates_max)
195
196
@assert count(i -> i !== nothing,
197
(mapping, faces, coordinates_min))==1 "Exactly one of mapping, faces and coordinates_min/max must be specified"
198
199
# Extract mapping
200
if faces !== nothing
201
validate_faces(faces)
202
mapping = transfinite_mapping(faces)
203
elseif coordinates_min !== nothing
204
mapping = coordinates2mapping(coordinates_min, coordinates_max)
205
end
206
207
NDIMS = length(trees_per_dimension)
208
209
# Convert periodicity to a Tuple of a Bool for every dimension
210
if all(periodicity)
211
# Also catches case where periodicity = true
212
periodicity = ntuple(_ -> true, NDIMS)
213
elseif !any(periodicity)
214
# Also catches case where periodicity = false
215
periodicity = ntuple(_ -> false, NDIMS)
216
else
217
# Default case if periodicity is an iterable
218
periodicity = Tuple(periodicity)
219
end
220
221
basis = LobattoLegendreBasis(RealT, polydeg)
222
nodes = basis.nodes
223
tree_node_coordinates = Array{RealT, NDIMS + 2}(undef, NDIMS,
224
ntuple(_ -> length(nodes),
225
NDIMS)...,
226
prod(trees_per_dimension))
227
calc_tree_node_coordinates!(tree_node_coordinates, nodes, mapping,
228
trees_per_dimension)
229
230
# p4est_connectivity_new_brick has trees in Z-order, so use our own function for this
231
connectivity = connectivity_structured(trees_per_dimension..., periodicity)
232
233
p4est = new_p4est(connectivity, initial_refinement_level)
234
235
# Non-periodic boundaries
236
boundary_names = fill(Symbol("---"), 2 * NDIMS, prod(trees_per_dimension))
237
238
structured_boundary_names!(boundary_names, trees_per_dimension, periodicity)
239
240
return P4estMesh{NDIMS}(p4est, tree_node_coordinates, nodes,
241
boundary_names, "", unsaved_changes,
242
p4est_partition_allow_for_coarsening)
243
end
244
245
# 2D version
246
function structured_boundary_names!(boundary_names, trees_per_dimension::NTuple{2},
247
periodicity)
248
linear_indices = LinearIndices(trees_per_dimension)
249
250
# Boundaries in x-direction
251
if !periodicity[1]
252
for cell_y in 1:trees_per_dimension[2]
253
tree = linear_indices[1, cell_y]
254
boundary_names[1, tree] = :x_neg
255
256
tree = linear_indices[end, cell_y]
257
boundary_names[2, tree] = :x_pos
258
end
259
end
260
261
# Boundaries in y-direction
262
if !periodicity[2]
263
for cell_x in 1:trees_per_dimension[1]
264
tree = linear_indices[cell_x, 1]
265
boundary_names[3, tree] = :y_neg
266
267
tree = linear_indices[cell_x, end]
268
boundary_names[4, tree] = :y_pos
269
end
270
end
271
end
272
273
# 3D version
274
function structured_boundary_names!(boundary_names, trees_per_dimension::NTuple{3},
275
periodicity)
276
linear_indices = LinearIndices(trees_per_dimension)
277
278
# Boundaries in x-direction
279
if !periodicity[1]
280
for cell_z in 1:trees_per_dimension[3], cell_y in 1:trees_per_dimension[2]
281
tree = linear_indices[1, cell_y, cell_z]
282
boundary_names[1, tree] = :x_neg
283
284
tree = linear_indices[end, cell_y, cell_z]
285
boundary_names[2, tree] = :x_pos
286
end
287
end
288
289
# Boundaries in y-direction
290
if !periodicity[2]
291
for cell_z in 1:trees_per_dimension[3], cell_x in 1:trees_per_dimension[1]
292
tree = linear_indices[cell_x, 1, cell_z]
293
boundary_names[3, tree] = :y_neg
294
295
tree = linear_indices[cell_x, end, cell_z]
296
boundary_names[4, tree] = :y_pos
297
end
298
end
299
300
# Boundaries in z-direction
301
if !periodicity[3]
302
for cell_y in 1:trees_per_dimension[2], cell_x in 1:trees_per_dimension[1]
303
tree = linear_indices[cell_x, cell_y, 1]
304
boundary_names[5, tree] = :z_neg
305
306
tree = linear_indices[cell_x, cell_y, end]
307
boundary_names[6, tree] = :z_pos
308
end
309
end
310
end
311
312
"""
313
P4estMesh{NDIMS}(meshfile::String;
314
mapping=nothing, polydeg=1, RealT=Float64,
315
initial_refinement_level=0, unsaved_changes=true,
316
p4est_partition_allow_for_coarsening=true,
317
boundary_symbols = nothing)
318
319
Main mesh constructor for the `P4estMesh` that imports an unstructured, conforming
320
mesh from an Abaqus mesh file (`.inp`). Each element of the conforming mesh parsed
321
from the `meshfile` is created as a [`p4est`](https://github.com/cburstedde/p4est)
322
tree datatype.
323
324
To create a curved/higher-order unstructured mesh `P4estMesh` two strategies are available:
325
326
- `p4est_mesh_from_hohqmesh_abaqus`: High-order (curved) boundary information created by
327
[`HOHQMesh.jl`](https://github.com/trixi-framework/HOHQMesh.jl) is
328
available in the `meshfile`. The mesh polynomial degree `polydeg`
329
of the boundaries is provided from the `meshfile`. The computation of
330
the mapped tree coordinates is done with transfinite interpolation
331
with linear blending similar to [`UnstructuredMesh2D`](@ref). Boundary name
332
information is also parsed from the `meshfile` such that different boundary
333
conditions can be set at each named boundary on a given tree.
334
- `p4est_mesh_from_standard_abaqus`: By default, with `mapping=nothing` and `polydeg=1`, this creates a
335
straight-sided from the information parsed from the `meshfile`. If a mapping
336
function is specified then it computes the mapped tree coordinates via polynomial
337
interpolants with degree `polydeg`. The mesh created by this function will only
338
have one boundary `:all` if `boundary_symbols` is not specified.
339
If `boundary_symbols` is specified the `meshfile` will be parsed for nodesets defining
340
the boundary nodes from which boundary edges (2D) and faces (3D) will be assigned.
341
342
Note that the `mapping` and `polydeg` keyword arguments are only used by the `p4est_mesh_from_standard_abaqus`
343
function. The `p4est_mesh_from_hohqmesh_abaqus` function obtains the mesh `polydeg` directly from the `meshfile`
344
and constructs the transfinite mapping internally.
345
346
The particular strategy is selected according to the header present in the `meshfile` where
347
the constructor checks whether or not the `meshfile` was created with
348
[HOHQMesh.jl](https://github.com/trixi-framework/HOHQMesh.jl).
349
If the Abaqus file header is not present in the `meshfile` then the `P4estMesh` is created
350
with the function `p4est_mesh_from_standard_abaqus`.
351
352
The default keyword argument `initial_refinement_level=0` corresponds to a forest
353
where the number of trees is the same as the number of elements in the original `meshfile`.
354
Increasing the `initial_refinement_level` allows one to uniformly refine the base mesh given
355
in the `meshfile` to create a forest with more trees before the simulation begins.
356
For example, if a two-dimensional base mesh contains 25 elements then setting
357
`initial_refinement_level=1` creates an initial forest of `2^2 * 25 = 100` trees.
358
359
# Arguments
360
- `meshfile::String`: an uncurved Abaqus mesh file that can be imported by `p4est`.
361
- `mapping`: a function of `NDIMS` variables to describe the mapping that transforms
362
the imported mesh to the physical domain. Use `nothing` for the identity map.
363
- `polydeg::Integer`: polynomial degree used to store the geometry of the mesh.
364
The mapping will be approximated by an interpolation polynomial
365
of the specified degree for each tree.
366
The default of `1` creates an uncurved geometry. Use a higher value if the mapping
367
will curve the imported uncurved mesh.
368
- `RealT::Type`: the type that should be used for coordinates.
369
- `initial_refinement_level::Integer`: refine the mesh uniformly to this level before the simulation starts.
370
- `unsaved_changes::Bool`: if set to `true`, the mesh will be saved to a mesh file.
371
- `p4est_partition_allow_for_coarsening::Bool`: Must be `true` when using AMR to make mesh adaptivity
372
independent of domain partitioning. Should be `false` for static meshes
373
to permit more fine-grained partitioning.
374
- `boundary_symbols::Vector{Symbol}`: A vector of symbols that correspond to the boundary names in the `meshfile`.
375
If `nothing` is passed then all boundaries are named `:all`.
376
"""
377
function P4estMesh{NDIMS}(meshfile::String;
378
mapping = nothing, polydeg = 1, RealT = Float64,
379
initial_refinement_level = 0, unsaved_changes = true,
380
p4est_partition_allow_for_coarsening = true,
381
boundary_symbols = nothing) where {NDIMS}
382
# Prevent `p4est` from crashing Julia if the file doesn't exist
383
@assert isfile(meshfile)
384
385
# Read in the Header of the meshfile to determine which constructor is appropriate
386
header = open(meshfile, "r") do io
387
readline(io) # *Header of the Abaqus file; discarded
388
readline(io) # Readin the actual header information
389
end
390
391
# Check if the meshfile was generated using HOHQMesh
392
if header == " File created by HOHQMesh"
393
# Mesh curvature and boundary naming is handled with additional information available in meshfile
394
p4est, tree_node_coordinates, nodes, boundary_names = p4est_mesh_from_hohqmesh_abaqus(meshfile,
395
initial_refinement_level,
396
NDIMS,
397
RealT)
398
else
399
# Mesh curvature is handled directly by applying the mapping keyword argument
400
p4est, tree_node_coordinates, nodes, boundary_names = p4est_mesh_from_standard_abaqus(meshfile,
401
mapping,
402
polydeg,
403
initial_refinement_level,
404
NDIMS,
405
RealT,
406
boundary_symbols)
407
end
408
409
return P4estMesh{NDIMS}(p4est, tree_node_coordinates, nodes,
410
boundary_names, "", unsaved_changes,
411
p4est_partition_allow_for_coarsening)
412
end
413
414
# Wrapper for `p4est_connectivity_from_hohqmesh_abaqus`. The latter is used
415
# by `T8codeMesh`, too.
416
function p4est_mesh_from_hohqmesh_abaqus(meshfile, initial_refinement_level,
417
n_dimensions, RealT)
418
connectivity, tree_node_coordinates, nodes, boundary_names = p4est_connectivity_from_hohqmesh_abaqus(meshfile,
419
n_dimensions,
420
RealT)
421
422
p4est = new_p4est(connectivity, initial_refinement_level)
423
424
return p4est, tree_node_coordinates, nodes, boundary_names
425
end
426
427
# Wrapper for `p4est_connectivity_from_standard_abaqus`. The latter is used
428
# by `T8codeMesh`, too.
429
function p4est_mesh_from_standard_abaqus(meshfile, mapping, polydeg,
430
initial_refinement_level, n_dimensions, RealT,
431
boundary_symbols)
432
connectivity, tree_node_coordinates, nodes, boundary_names = p4est_connectivity_from_standard_abaqus(meshfile,
433
mapping,
434
polydeg,
435
n_dimensions,
436
RealT,
437
boundary_symbols)
438
439
p4est = new_p4est(connectivity, initial_refinement_level)
440
441
return p4est, tree_node_coordinates, nodes, boundary_names
442
end
443
444
# Create the mesh connectivity, mapped node coordinates within each tree, reference nodes in [-1,1]
445
# and a list of boundary names for the `P4estMesh`. High-order boundary curve information as well as
446
# the boundary names on each tree are provided by the `meshfile` created by
447
# [`HOHQMesh.jl`](https://github.com/trixi-framework/HOHQMesh.jl).
448
function p4est_connectivity_from_hohqmesh_abaqus(meshfile,
449
n_dimensions, RealT)
450
# Create the mesh connectivity using `p4est`
451
connectivity = read_inp_p4est(meshfile, Val(n_dimensions))
452
connectivity_pw = PointerWrapper(connectivity)
453
454
# These need to be of the type Int for unsafe_wrap below to work
455
n_trees::Int = connectivity_pw.num_trees[] # = number elements
456
n_vertices::Int = connectivity_pw.num_vertices[]
457
458
# Extract a copy of the element vertices to compute the tree node coordinates
459
# `vertices` stores coordinates of all three dimensions (even for the 2D case)
460
# since the Abaqus `.inp` format always stores 3D coordinates.
461
vertices = unsafe_wrap(Array, connectivity_pw.vertices, (3, n_vertices))
462
463
# Readin all the information from the mesh file into a string array
464
file_lines = readlines(open(meshfile))
465
466
# Get the file index where the mesh polynomial degree is given in the meshfile
467
file_idx = findfirst(contains("** mesh polynomial degree"), file_lines)
468
469
# Get the polynomial order of the mesh boundary information
470
current_line = split(file_lines[file_idx])
471
mesh_polydeg = parse(Int, current_line[6])
472
mesh_nnodes = mesh_polydeg + 1
473
474
# Create the Chebyshev-Gauss-Lobatto nodes used by HOHQMesh to represent the boundaries
475
cheby_nodes, _ = chebyshev_gauss_lobatto_nodes_weights(mesh_nnodes)
476
nodes = SVector{mesh_nnodes}(cheby_nodes)
477
478
# Allocate the memory for the tree node coordinates
479
tree_node_coordinates = Array{RealT, n_dimensions + 2}(undef, n_dimensions,
480
ntuple(_ -> length(nodes),
481
n_dimensions)...,
482
n_trees)
483
484
# Compute the tree node coordinates and return the updated file index
485
file_idx = calc_tree_node_coordinates!(tree_node_coordinates, file_lines, nodes,
486
vertices, RealT)
487
488
# Allocate the memory for the boundary labels
489
boundary_names = Array{Symbol}(undef, (2 * n_dimensions, n_trees))
490
491
# Read in the boundary names from the last portion of the meshfile
492
# Note here the boundary names where "---" means an internal connection
493
for tree in 1:n_trees
494
current_line = split(file_lines[file_idx])
495
boundary_names[:, tree] = map(Symbol, current_line[2:end])
496
file_idx += 1
497
end
498
499
return connectivity, tree_node_coordinates, nodes, boundary_names
500
end
501
502
# This removes irrelevant elements from the meshfile, i.e., trusses/beams which are 1D elements.
503
# Thus, if `n_dimensions == 2`, only quads are kept, and if `n_dimensions == 3`,
504
# only hexes are kept, i.e., in that case also quads are removed.
505
function preprocess_standard_abaqus(meshfile,
506
linear_quads, linear_hexes,
507
quadratic_quads, quadratic_hexes,
508
n_dimensions)
509
meshfile_preproc = replace(meshfile, ".inp" => "_preproc.inp")
510
511
# Line number where the element section begins
512
elements_begin_idx = 0
513
# Line number where the node and element sets begin (if present)
514
sets_begin_idx = 0
515
516
open(meshfile, "r") do infile
517
# Copy header and node data to pre-processed file
518
open(meshfile_preproc, "w") do outfile
519
for (line_index, line) in enumerate(eachline(infile))
520
println(outfile, line)
521
if occursin("******* E L E M E N T S *************", line)
522
elements_begin_idx = line_index + 1
523
break
524
end
525
end
526
end
527
528
# Find the line number where the node and element sets begin
529
for (line_index, line) in enumerate(eachline(infile))
530
if startswith(line, "*ELSET") || startswith(line, "*NSET")
531
sets_begin_idx = line_index
532
break
533
end
534
end
535
end
536
# Catch the case where there are no node or element sets
537
if sets_begin_idx == 0
538
sets_begin_idx = length(readlines(meshfile)) + 1
539
else
540
# Need to add `elements_begin_idx - 1` since the loop above starts at `elements_begin_idx`
541
sets_begin_idx += elements_begin_idx - 1
542
end
543
544
element_index = 0
545
preproc_element_section_lines = 0
546
open(meshfile, "r") do infile
547
open(meshfile_preproc, "a") do outfile
548
print_following_lines = false
549
550
for (line_index, line) in enumerate(eachline(infile))
551
# Act only in the element section
552
if elements_begin_idx <= line_index < sets_begin_idx
553
# Check if a new element type/element set is defined
554
if startswith(line, "*ELEMENT")
555
# Retrieve element type
556
current_element_type = match(r"\*ELEMENT, type=([^,]+)", line).captures[1]
557
558
# Keep only quads (2D) or hexes (3D), i.e., eliminate all other elements
559
# like trusses (2D/3D) or quads (3D).
560
if n_dimensions == 2 &&
561
(occursin(linear_quads, current_element_type) ||
562
occursin(quadratic_quads, current_element_type))
563
print_following_lines = true
564
println(outfile, line)
565
preproc_element_section_lines += 1
566
elseif n_dimensions == 3 &&
567
(occursin(linear_hexes, current_element_type) ||
568
occursin(quadratic_hexes, current_element_type))
569
print_following_lines = true
570
println(outfile, line)
571
preproc_element_section_lines += 1
572
else
573
print_following_lines = false
574
end
575
else # Element data in line
576
if print_following_lines
577
element_index += 1
578
parts = split(line, ',')
579
# Exchange element index
580
parts[1] = string(element_index)
581
println(outfile, join(parts, ','))
582
preproc_element_section_lines += 1
583
end
584
end
585
end
586
587
# Print node and element sets as they are
588
if line_index >= sets_begin_idx
589
println(outfile, line)
590
end
591
end
592
end
593
end
594
# Adjust `sets_begin_idx` to correct line number after removing unnecessary elements
595
sets_begin_idx = elements_begin_idx + preproc_element_section_lines
596
597
return elements_begin_idx, sets_begin_idx
598
end
599
600
# p4est can handle only linear elements. This function checks the `meshfile_pre_proc`
601
# for quadratic elements (highest order supported by standard Abaqus) and
602
# replaces them with linear elements. The higher-order (quadratic) boundaries are handled
603
# "internally" by Trixi as for the HOHQMesh-Abaqus case.
604
function preprocess_standard_abaqus_for_p4est(meshfile_pre_proc,
605
linear_quads, linear_hexes,
606
quadratic_quads, quadratic_hexes,
607
elements_begin_idx,
608
sets_begin_idx)
609
meshfile_p4est_rdy = replace(meshfile_pre_proc,
610
"_preproc.inp" => "_p4est_ready.inp")
611
order = 1 # Assume linear elements by default
612
613
# Some useful function-wide variables
614
current_element_type = ""
615
new_line = ""
616
617
open(meshfile_pre_proc, "r") do infile
618
open(meshfile_p4est_rdy, "w") do outfile
619
for (line_index, line) in enumerate(eachline(infile))
620
# Copy header and node data
621
if line_index < elements_begin_idx || line_index >= sets_begin_idx
622
println(outfile, line)
623
else # Element data
624
# Check if a new element type/element set is defined
625
if startswith(line, "*ELEMENT")
626
# Retrieve element type
627
current_element_type = match(r"\*ELEMENT, type=([^,]+)", line).captures[1]
628
629
# Check for linear elements - then we just copy the line
630
if occursin(linear_quads, current_element_type) ||
631
occursin(linear_hexes, current_element_type)
632
println(outfile, line)
633
else # Quadratic element - replace with linear
634
order = 2
635
if occursin(quadratic_quads, current_element_type)
636
linear_quad_type = "CPS4"
637
new_line = replace(line,
638
current_element_type => linear_quad_type)
639
elseif occursin(quadratic_hexes, current_element_type)
640
linear_hex_type = "C3D8"
641
new_line = replace(line,
642
current_element_type => linear_hex_type)
643
end
644
println(outfile, new_line)
645
end
646
else # Element data in line
647
# Check for linear elements - then we just copy the line
648
if occursin(linear_quads, current_element_type) ||
649
occursin(linear_hexes, current_element_type)
650
println(outfile, line)
651
else
652
parts = split(line, ',')
653
if occursin(quadratic_quads, current_element_type)
654
# Print the first (element), second to fifth (vertices 1-4) indices to file.
655
# For node order of quadratic (second-order) quads, see
656
# http://130.149.89.49:2080/v2016/books/usb/default.htm?startat=pt06ch28s01ael02.html
657
new_line = join(parts[1:5], ',')
658
elseif occursin(quadratic_hexes, current_element_type)
659
# Print the first (element), second to ninth (vertices 1-8) indices to file.
660
# The node order is fortunately the same for hexes/bricks of type "C3D27", see
661
# http://130.149.89.49:2080/v2016/books/usb/default.htm?startat=pt06ch28s01ael03.html
662
new_line = join(parts[1:9], ',')
663
end
664
println(outfile, new_line)
665
end
666
end
667
end
668
end
669
end
670
end
671
672
return order
673
end
674
675
# Read all nodes (not only vertices, i.e., endpoints of elements) into a dict.
676
# Those are required to enable higher-order boundaries for quadratic elements.
677
function read_nodes_standard_abaqus(meshfile, n_dimensions,
678
elements_begin_idx, RealT)
679
mesh_nodes = Dict{Int, SVector{n_dimensions, RealT}}()
680
681
node_section = false
682
open(meshfile, "r") do infile
683
for (line_index, line) in enumerate(eachline(infile))
684
if line_index < elements_begin_idx
685
if startswith(line, "*NODE")
686
node_section = true
687
end
688
if node_section
689
parts = split(line, ',')
690
if length(parts) >= n_dimensions + 1
691
node_id = parse(Int, parts[1])
692
coordinates = SVector{n_dimensions, RealT}([parse(RealT,
693
parts[i])
694
for i in 2:(n_dimensions + 1)])
695
mesh_nodes[node_id] = coordinates
696
end
697
end
698
end
699
end
700
end
701
702
return mesh_nodes
703
end
704
705
# Create the mesh connectivity, mapped node coordinates within each tree, reference nodes in [-1,1]
706
# and a list of boundary names for the `P4estMesh`. For linear meshes, the tree node coordinates are
707
# computed according to the `mapping` passed to this function using polynomial interpolants of degree `polydeg`.
708
# For quadratic (second-order) meshes, the tree node coordinates are read from the meshfile, similar as for
709
# `p4est_connectivity_from_hohqmesh_abaqus`.
710
function p4est_connectivity_from_standard_abaqus(meshfile, mapping, polydeg,
711
n_dimensions,
712
RealT,
713
boundary_symbols)
714
715
### Regular expressions for Abaqus element types ###
716
717
# These are the standard Abaqus linear quads.
718
# Note that there are many(!) more variants designed for specific purposes in the Abaqus solver, see
719
# http://130.149.89.49:2080/v2016/books/usb/default.htm?startat=pt10eli01.html
720
# To keep it simple we support only basic quads, membranes, and shells here.
721
linear_quads = r"^(CPE4|CPEG4|CPS4|M3D4|S4|SFM3D4).*$"
722
723
# Same logic as for linear quads:
724
# Support only basic quads, membranes, and shells here.
725
quadratic_quads = r"^(CPE8|CPS8|CAX8|S8|M3D8|M3D9).*$"
726
727
# In 3D only standard hexahedra/bricks are supported.
728
linear_hexes = r"^(C3D8).*$"
729
quadratic_hexes = r"^(C3D27).*$"
730
731
meshfile_preproc = replace(meshfile, ".inp" => "_preproc.inp")
732
733
# Define variables that are retrieved in the MPI-parallel case on root and then bcasted
734
elements_begin_idx = -1
735
sets_begin_idx = -1
736
mesh_polydeg = 1
737
738
if !mpi_isparallel() || (mpi_isparallel() && mpi_isroot())
739
# Preprocess the meshfile to remove lower-dimensional elements
740
elements_begin_idx, sets_begin_idx = preprocess_standard_abaqus(meshfile,
741
linear_quads,
742
linear_hexes,
743
quadratic_quads,
744
quadratic_hexes,
745
n_dimensions)
746
747
# Copy of mesh for p4est with linear elements only
748
mesh_polydeg = preprocess_standard_abaqus_for_p4est(meshfile_preproc,
749
linear_quads,
750
linear_hexes,
751
quadratic_quads,
752
quadratic_hexes,
753
elements_begin_idx,
754
sets_begin_idx)
755
end
756
757
# Broadcast from meshfile retrieved variables across all MPI ranks
758
if mpi_isparallel()
759
if mpi_isroot()
760
MPI.Bcast!(Ref(elements_begin_idx), mpi_root(), mpi_comm())
761
MPI.Bcast!(Ref(sets_begin_idx), mpi_root(), mpi_comm())
762
MPI.Bcast!(Ref(mesh_polydeg), mpi_root(), mpi_comm())
763
else
764
elements_begin_idx = MPI.Bcast!(Ref(0), mpi_root(), mpi_comm())[]
765
sets_begin_idx = MPI.Bcast!(Ref(0), mpi_root(), mpi_comm())[]
766
mesh_polydeg = MPI.Bcast!(Ref(0), mpi_root(), mpi_comm())[]
767
end
768
end
769
770
# Create the mesh connectivity using `p4est`
771
meshfile_p4est_rdy = replace(meshfile, ".inp" => "_p4est_ready.inp")
772
connectivity = read_inp_p4est(meshfile_p4est_rdy, Val(n_dimensions))
773
connectivity_pw = PointerWrapper(connectivity)
774
775
# These need to be of the type Int for unsafe_wrap below to work
776
n_trees::Int = connectivity_pw.num_trees[] # = number elements
777
n_vertices::Int = connectivity_pw.num_vertices[]
778
779
# Extract a copy of the element vertices to compute the tree node coordinates
780
# `vertices` store coordinates of all three dimensions (even for the 2D case)
781
# since the Abaqus `.inp` format always stores 3D coordinates.
782
vertices = unsafe_wrap(Array, connectivity_pw.vertices, (3, n_vertices))
783
784
tree_to_vertex = unsafe_wrap(Array, connectivity_pw.tree_to_vertex,
785
(2^n_dimensions, n_trees))
786
787
basis = LobattoLegendreBasis(RealT, polydeg)
788
nodes = basis.nodes
789
790
# The highest supported element order is quadratic (second-order) in the standard Abaqus format.
791
# Thus, this check is equivalent to checking for higher-order boundary information.
792
if mesh_polydeg == 2
793
mesh_nnodes = mesh_polydeg + 1 # = 3
794
# Note: We ASSUME that the additional node between the end-vertices lies
795
# on the center on that line, such that we can use Chebyshev-Gauss-Lobatto nodes!
796
# For polydeg = 2, we have the 3 nodes [-1, 0, 1] (within the reference element).
797
# Note that these coincide for polydeg = 2 with the Legendre-Gauss-Lobatto nodes.
798
cheby_nodes, _ = chebyshev_gauss_lobatto_nodes_weights(mesh_nnodes)
799
nodes = SVector{mesh_nnodes}(cheby_nodes)
800
end
801
802
tree_node_coordinates = Array{RealT, n_dimensions + 2}(undef, n_dimensions,
803
ntuple(_ -> length(nodes),
804
n_dimensions)...,
805
n_trees)
806
807
# No nonlinearity in the mesh. Rely on the mapping function to realize the curvature.
808
if mesh_polydeg == 1
809
calc_tree_node_coordinates!(tree_node_coordinates, nodes, mapping,
810
vertices, tree_to_vertex)
811
else # mesh_polydeg = 2 => Nonlinearity in supplied mesh
812
mesh_nodes = read_nodes_standard_abaqus(meshfile_preproc, n_dimensions,
813
elements_begin_idx, RealT)
814
815
# Extract element section from pre-processed Abaqus meshfile
816
element_lines = readlines(open(meshfile_preproc))[elements_begin_idx:(sets_begin_idx - 1)]
817
818
if n_dimensions == 2
819
calc_tree_node_coordinates!(tree_node_coordinates, element_lines,
820
nodes, vertices, RealT,
821
linear_quads, mesh_nodes)
822
else # n_dimensions == 3
823
@error "Three dimensional quadratic elements are not supported yet!"
824
end
825
end
826
827
if boundary_symbols === nothing
828
# There's no simple and generic way to distinguish boundaries without any information given.
829
# Name all of them :all.
830
boundary_names = fill(:all, 2 * n_dimensions, n_trees)
831
else # Boundary information given
832
# Read in nodes belonging to boundaries
833
node_set_dict = parse_node_sets(meshfile_p4est_rdy, boundary_symbols)
834
# Read in all elements with associated nodes to specify the boundaries
835
element_node_matrix = parse_elements(meshfile_p4est_rdy, n_trees, n_dimensions,
836
elements_begin_idx, sets_begin_idx)
837
838
# Initialize boundary information matrix with symbol for no boundary / internal connection
839
boundary_names = fill(Symbol("---"), 2 * n_dimensions, n_trees)
840
841
# Fill `boundary_names` such that it can be processed by p4est
842
assign_boundaries_standard_abaqus!(boundary_names, n_trees,
843
element_node_matrix, node_set_dict,
844
Val(n_dimensions))
845
end
846
847
return connectivity, tree_node_coordinates, nodes, boundary_names
848
end
849
850
function parse_elements(meshfile, n_trees, n_dims,
851
elements_begin_idx,
852
sets_begin_idx)
853
# 2D quads: 4 nodes + element index, 3D hexes: 8 nodes + element index
854
expected_content_length = n_dims == 2 ? 5 : 9
855
856
element_node_matrix = Matrix{Int64}(undef, n_trees, expected_content_length - 1)
857
el_list_follows = false
858
tree_id = 1
859
860
open(meshfile, "r") do infile
861
for (line_index, line) in enumerate(eachline(infile))
862
if elements_begin_idx <= line_index < sets_begin_idx
863
if startswith(line, "*ELEMENT")
864
el_list_follows = true
865
elseif el_list_follows
866
content = split(line, ",")
867
if length(content) == expected_content_length # Check that we still read in connectivity data
868
content_int = parse.(Int64, content)
869
# Add constituent nodes to the element_node_matrix.
870
# Important: Do not use index from the Abaqus file, but the one from p4est.
871
element_node_matrix[tree_id, :] = content_int[2:end] # First entry is element id
872
tree_id += 1
873
else # Processed all elements for this `ELSET`
874
el_list_follows = false
875
end
876
end
877
end
878
end
879
end
880
881
return element_node_matrix
882
end
883
884
function parse_node_sets(meshfile, boundary_symbols)
885
nodes_dict = Dict{Symbol, Set{Int64}}()
886
current_symbol = nothing
887
current_nodes = Int64[]
888
889
open(meshfile, "r") do file
890
for line in eachline(file)
891
# Check if the line contains nodes assembled in a special set, i.e., a physical boundary
892
if startswith(line, "*NSET,NSET=")
893
# Safe the previous nodeset
894
if current_symbol !== nothing
895
nodes_dict[current_symbol] = current_nodes
896
end
897
898
current_symbol = Symbol(split(line, "=")[2])
899
if current_symbol in boundary_symbols
900
# New nodeset
901
current_nodes = Set{Int64}()
902
else # Read only boundary node sets
903
current_symbol = nothing
904
end
905
elseif current_symbol !== nothing # Read only if there was already a nodeset specified
906
try # Check if line contains nodes
907
# There is always a trailing comma, remove the corresponding empty string
908
union!(current_nodes, parse.(Int64, split(line, ",")[1:(end - 1)]))
909
catch # Something different, stop reading in nodes
910
# If parsing fails, set current_symbol to nothing
911
nodes_dict[current_symbol] = current_nodes
912
current_symbol = nothing
913
end
914
end
915
end
916
# Safe the previous nodeset
917
if current_symbol !== nothing
918
nodes_dict[current_symbol] = current_nodes
919
end
920
end
921
922
for symbol in boundary_symbols
923
if !haskey(nodes_dict, symbol)
924
@warn "No nodes found for nodeset :" * "$symbol" * " !"
925
end
926
end
927
928
return nodes_dict
929
end
930
931
# This function assigns the edges of elements to boundaries by
932
# checking if the nodes that define the edges are part of nodesets which correspond to boundaries.
933
function assign_boundaries_standard_abaqus!(boundary_names, n_trees,
934
element_node_matrix, node_set_dict,
935
::Val{2}) # 2D version
936
@threaded for tree in 1:n_trees
937
tree_nodes = element_node_matrix[tree, :]
938
# For node labeling, see
939
# https://docs.software.vt.edu/abaqusv2022/English/SIMACAEELMRefMap/simaelm-r-2delem.htm#simaelm-r-2delem-t-nodedef1
940
# and search for "Node ordering and face numbering on elements"
941
for boundary in keys(node_set_dict) # Loop over specified boundaries
942
# Check bottom edge
943
if tree_nodes[1] in node_set_dict[boundary] &&
944
tree_nodes[2] in node_set_dict[boundary]
945
# Bottom boundary is position 3 in p4est indexing
946
boundary_names[3, tree] = boundary
947
end
948
# Check right edge
949
if tree_nodes[2] in node_set_dict[boundary] &&
950
tree_nodes[3] in node_set_dict[boundary]
951
# Right boundary is position 2 in p4est indexing
952
boundary_names[2, tree] = boundary
953
end
954
# Check top edge
955
if tree_nodes[3] in node_set_dict[boundary] &&
956
tree_nodes[4] in node_set_dict[boundary]
957
# Top boundary is position 4 in p4est indexing
958
boundary_names[4, tree] = boundary
959
end
960
# Check left edge
961
if tree_nodes[4] in node_set_dict[boundary] &&
962
tree_nodes[1] in node_set_dict[boundary]
963
# Left boundary is position 1 in p4est indexing
964
boundary_names[1, tree] = boundary
965
end
966
end
967
end
968
969
return boundary_names
970
end
971
972
# This function assigns the edges of elements to boundaries by
973
# checking if the nodes that define the faces are part of nodesets which correspond to boundaries.
974
function assign_boundaries_standard_abaqus!(boundary_names, n_trees,
975
element_node_matrix, node_set_dict,
976
::Val{3}) # 3D version
977
@threaded for tree in 1:n_trees
978
tree_nodes = element_node_matrix[tree, :]
979
# For node labeling, see
980
# https://web.mit.edu/calculix_v2.7/CalculiX/ccx_2.7/doc/ccx/node26.html
981
for boundary in keys(node_set_dict) # Loop over specified boundaries
982
# Check "front face" (y_min)
983
if tree_nodes[1] in node_set_dict[boundary] &&
984
tree_nodes[2] in node_set_dict[boundary] &&
985
tree_nodes[5] in node_set_dict[boundary] &&
986
tree_nodes[6] in node_set_dict[boundary]
987
# Front face is position 3 in p4est indexing
988
boundary_names[3, tree] = boundary
989
end
990
# Check "back face" (y_max)
991
if tree_nodes[3] in node_set_dict[boundary] &&
992
tree_nodes[4] in node_set_dict[boundary] &&
993
tree_nodes[7] in node_set_dict[boundary] &&
994
tree_nodes[8] in node_set_dict[boundary]
995
# Front face is position 4 in p4est indexing
996
boundary_names[4, tree] = boundary
997
end
998
# Check "left face" (x_min)
999
if tree_nodes[1] in node_set_dict[boundary] &&
1000
tree_nodes[4] in node_set_dict[boundary] &&
1001
tree_nodes[5] in node_set_dict[boundary] &&
1002
tree_nodes[8] in node_set_dict[boundary]
1003
# Left face is position 1 in p4est indexing
1004
boundary_names[1, tree] = boundary
1005
end
1006
# Check "right face" (x_max)
1007
if tree_nodes[2] in node_set_dict[boundary] &&
1008
tree_nodes[3] in node_set_dict[boundary] &&
1009
tree_nodes[6] in node_set_dict[boundary] &&
1010
tree_nodes[7] in node_set_dict[boundary]
1011
# Right face is position 2 in p4est indexing
1012
boundary_names[2, tree] = boundary
1013
end
1014
# Check "bottom face" (z_min)
1015
if tree_nodes[1] in node_set_dict[boundary] &&
1016
tree_nodes[2] in node_set_dict[boundary] &&
1017
tree_nodes[3] in node_set_dict[boundary] &&
1018
tree_nodes[4] in node_set_dict[boundary]
1019
# Bottom face is position 5 in p4est indexing
1020
boundary_names[5, tree] = boundary
1021
end
1022
# Check "top face" (z_max)
1023
if tree_nodes[5] in node_set_dict[boundary] &&
1024
tree_nodes[6] in node_set_dict[boundary] &&
1025
tree_nodes[7] in node_set_dict[boundary] &&
1026
tree_nodes[8] in node_set_dict[boundary]
1027
# Top face is position 6 in p4est indexing
1028
boundary_names[6, tree] = boundary
1029
end
1030
end
1031
end
1032
1033
return boundary_names
1034
end
1035
1036
"""
1037
P4estMeshCubedSphere(trees_per_face_dimension, layers, inner_radius, thickness;
1038
polydeg, RealT=Float64,
1039
initial_refinement_level=0, unsaved_changes=true,
1040
p4est_partition_allow_for_coarsening=true)
1041
1042
Build a "Cubed Sphere" mesh as `P4estMesh` with
1043
`6 * trees_per_face_dimension^2 * layers` trees.
1044
1045
The mesh will have two boundaries, `:inside` and `:outside`.
1046
1047
# Arguments
1048
- `trees_per_face_dimension::Integer`: the number of trees in the first two local dimensions of
1049
each face.
1050
- `layers::Integer`: the number of trees in the third local dimension of each face, i.e., the number
1051
of layers of the sphere.
1052
- `inner_radius::Integer`: the inner radius of the sphere.
1053
- `thickness::Integer`: the thickness of the sphere. The outer radius will be `inner_radius + thickness`.
1054
- `polydeg::Integer`: polynomial degree used to store the geometry of the mesh.
1055
The mapping will be approximated by an interpolation polynomial
1056
of the specified degree for each tree.
1057
- `RealT::Type`: the type that should be used for coordinates.
1058
- `initial_refinement_level::Integer`: refine the mesh uniformly to this level before the simulation starts.
1059
- `unsaved_changes::Bool`: if set to `true`, the mesh will be saved to a mesh file.
1060
- `p4est_partition_allow_for_coarsening::Bool`: Must be `true` when using AMR to make mesh adaptivity
1061
independent of domain partitioning. Should be `false` for static meshes
1062
to permit more fine-grained partitioning.
1063
"""
1064
function P4estMeshCubedSphere(trees_per_face_dimension, layers, inner_radius, thickness;
1065
polydeg, RealT = Float64,
1066
initial_refinement_level = 0, unsaved_changes = true,
1067
p4est_partition_allow_for_coarsening = true)
1068
connectivity = connectivity_cubed_sphere(trees_per_face_dimension, layers)
1069
1070
n_trees = 6 * trees_per_face_dimension^2 * layers
1071
1072
basis = LobattoLegendreBasis(RealT, polydeg)
1073
nodes = basis.nodes
1074
1075
tree_node_coordinates = Array{RealT, 5}(undef, 3,
1076
ntuple(_ -> length(nodes), 3)...,
1077
n_trees)
1078
calc_tree_node_coordinates!(tree_node_coordinates, nodes, trees_per_face_dimension,
1079
layers,
1080
inner_radius, thickness)
1081
1082
p4est = new_p4est(connectivity, initial_refinement_level)
1083
1084
boundary_names = fill(Symbol("---"), 2 * 3, n_trees)
1085
boundary_names[5, :] .= Symbol("inside")
1086
boundary_names[6, :] .= Symbol("outside")
1087
1088
return P4estMesh{3}(p4est, tree_node_coordinates, nodes,
1089
boundary_names, "", unsaved_changes,
1090
p4est_partition_allow_for_coarsening)
1091
end
1092
1093
# Create a new p4est_connectivity that represents a structured rectangle.
1094
# Similar to p4est_connectivity_new_brick, but doesn't use Morton order.
1095
# This order makes `calc_tree_node_coordinates!` below and the calculation
1096
# of `boundary_names` above easier but is irrelevant otherwise.
1097
# 2D version
1098
function connectivity_structured(n_cells_x, n_cells_y, periodicity)
1099
linear_indices = LinearIndices((n_cells_x, n_cells_y))
1100
1101
# Vertices represent the coordinates of the forest. This is used by `p4est`
1102
# to write VTK files.
1103
# Trixi.jl doesn't use the coordinates from `p4est`, so the vertices can be empty.
1104
n_vertices = 0
1105
n_trees = n_cells_x * n_cells_y
1106
# No corner connectivity is needed
1107
n_corners = 0
1108
vertices = C_NULL
1109
tree_to_vertex = C_NULL
1110
1111
tree_to_tree = Array{p4est_topidx_t, 2}(undef, 4, n_trees)
1112
tree_to_face = Array{Int8, 2}(undef, 4, n_trees)
1113
1114
for cell_y in 1:n_cells_y, cell_x in 1:n_cells_x
1115
tree = linear_indices[cell_x, cell_y]
1116
1117
# Subtract 1 because `p4est` uses zero-based indexing
1118
# Negative x-direction
1119
if cell_x > 1
1120
tree_to_tree[1, tree] = linear_indices[cell_x - 1, cell_y] - 1
1121
tree_to_face[1, tree] = 1
1122
elseif periodicity[1]
1123
tree_to_tree[1, tree] = linear_indices[n_cells_x, cell_y] - 1
1124
tree_to_face[1, tree] = 1
1125
else # Non-periodic boundary, tree and face point to themselves (zero-based indexing)
1126
tree_to_tree[1, tree] = tree - 1
1127
tree_to_face[1, tree] = 0
1128
end
1129
1130
# Positive x-direction
1131
if cell_x < n_cells_x
1132
tree_to_tree[2, tree] = linear_indices[cell_x + 1, cell_y] - 1
1133
tree_to_face[2, tree] = 0
1134
elseif periodicity[1]
1135
tree_to_tree[2, tree] = linear_indices[1, cell_y] - 1
1136
tree_to_face[2, tree] = 0
1137
else # Non-periodic boundary, tree and face point to themselves (zero-based indexing)
1138
tree_to_tree[2, tree] = tree - 1
1139
tree_to_face[2, tree] = 1
1140
end
1141
1142
# Negative y-direction
1143
if cell_y > 1
1144
tree_to_tree[3, tree] = linear_indices[cell_x, cell_y - 1] - 1
1145
tree_to_face[3, tree] = 3
1146
elseif periodicity[2]
1147
tree_to_tree[3, tree] = linear_indices[cell_x, n_cells_y] - 1
1148
tree_to_face[3, tree] = 3
1149
else # Non-periodic boundary, tree and face point to themselves (zero-based indexing)
1150
tree_to_tree[3, tree] = tree - 1
1151
tree_to_face[3, tree] = 2
1152
end
1153
1154
# Positive y-direction
1155
if cell_y < n_cells_y
1156
tree_to_tree[4, tree] = linear_indices[cell_x, cell_y + 1] - 1
1157
tree_to_face[4, tree] = 2
1158
elseif periodicity[2]
1159
tree_to_tree[4, tree] = linear_indices[cell_x, 1] - 1
1160
tree_to_face[4, tree] = 2
1161
else # Non-periodic boundary, tree and face point to themselves (zero-based indexing)
1162
tree_to_tree[4, tree] = tree - 1
1163
tree_to_face[4, tree] = 3
1164
end
1165
end
1166
1167
tree_to_corner = C_NULL
1168
# `p4est` docs: "in trivial cases it is just a pointer to a p4est_topix value of 0."
1169
# We don't need corner connectivity, so this is a trivial case.
1170
ctt_offset = zeros(p4est_topidx_t, 1)
1171
1172
corner_to_tree = C_NULL
1173
corner_to_corner = C_NULL
1174
1175
connectivity = p4est_connectivity_new_copy(n_vertices, n_trees, n_corners,
1176
vertices, tree_to_vertex,
1177
tree_to_tree, tree_to_face,
1178
tree_to_corner, ctt_offset,
1179
corner_to_tree, corner_to_corner)
1180
1181
@assert p4est_connectivity_is_valid(connectivity) == 1
1182
1183
return connectivity
1184
end
1185
1186
# 3D version
1187
function connectivity_structured(n_cells_x, n_cells_y, n_cells_z, periodicity)
1188
linear_indices = LinearIndices((n_cells_x, n_cells_y, n_cells_z))
1189
1190
# Vertices represent the coordinates of the forest. This is used by `p4est`
1191
# to write VTK files.
1192
# Trixi.jl doesn't use the coordinates from `p4est`, so the vertices can be empty.
1193
n_vertices = 0
1194
n_trees = n_cells_x * n_cells_y * n_cells_z
1195
# No edge connectivity is needed
1196
n_edges = 0
1197
# No corner connectivity is needed
1198
n_corners = 0
1199
vertices = C_NULL
1200
tree_to_vertex = C_NULL
1201
1202
tree_to_tree = Array{p4est_topidx_t, 2}(undef, 6, n_trees)
1203
tree_to_face = Array{Int8, 2}(undef, 6, n_trees)
1204
1205
for cell_z in 1:n_cells_z, cell_y in 1:n_cells_y, cell_x in 1:n_cells_x
1206
tree = linear_indices[cell_x, cell_y, cell_z]
1207
1208
# Subtract 1 because `p4est` uses zero-based indexing
1209
# Negative x-direction
1210
if cell_x > 1
1211
tree_to_tree[1, tree] = linear_indices[cell_x - 1, cell_y, cell_z] - 1
1212
tree_to_face[1, tree] = 1
1213
elseif periodicity[1]
1214
tree_to_tree[1, tree] = linear_indices[n_cells_x, cell_y, cell_z] - 1
1215
tree_to_face[1, tree] = 1
1216
else # Non-periodic boundary, tree and face point to themselves (zero-based indexing)
1217
tree_to_tree[1, tree] = tree - 1
1218
tree_to_face[1, tree] = 0
1219
end
1220
1221
# Positive x-direction
1222
if cell_x < n_cells_x
1223
tree_to_tree[2, tree] = linear_indices[cell_x + 1, cell_y, cell_z] - 1
1224
tree_to_face[2, tree] = 0
1225
elseif periodicity[1]
1226
tree_to_tree[2, tree] = linear_indices[1, cell_y, cell_z] - 1
1227
tree_to_face[2, tree] = 0
1228
else # Non-periodic boundary, tree and face point to themselves (zero-based indexing)
1229
tree_to_tree[2, tree] = tree - 1
1230
tree_to_face[2, tree] = 1
1231
end
1232
1233
# Negative y-direction
1234
if cell_y > 1
1235
tree_to_tree[3, tree] = linear_indices[cell_x, cell_y - 1, cell_z] - 1
1236
tree_to_face[3, tree] = 3
1237
elseif periodicity[2]
1238
tree_to_tree[3, tree] = linear_indices[cell_x, n_cells_y, cell_z] - 1
1239
tree_to_face[3, tree] = 3
1240
else # Non-periodic boundary, tree and face point to themselves (zero-based indexing)
1241
tree_to_tree[3, tree] = tree - 1
1242
tree_to_face[3, tree] = 2
1243
end
1244
1245
# Positive y-direction
1246
if cell_y < n_cells_y
1247
tree_to_tree[4, tree] = linear_indices[cell_x, cell_y + 1, cell_z] - 1
1248
tree_to_face[4, tree] = 2
1249
elseif periodicity[2]
1250
tree_to_tree[4, tree] = linear_indices[cell_x, 1, cell_z] - 1
1251
tree_to_face[4, tree] = 2
1252
else # Non-periodic boundary, tree and face point to themselves (zero-based indexing)
1253
tree_to_tree[4, tree] = tree - 1
1254
tree_to_face[4, tree] = 3
1255
end
1256
1257
# Negative z-direction
1258
if cell_z > 1
1259
tree_to_tree[5, tree] = linear_indices[cell_x, cell_y, cell_z - 1] - 1
1260
tree_to_face[5, tree] = 5
1261
elseif periodicity[3]
1262
tree_to_tree[5, tree] = linear_indices[cell_x, cell_y, n_cells_z] - 1
1263
tree_to_face[5, tree] = 5
1264
else # Non-periodic boundary, tree and face point to themselves (zero-based indexing)
1265
tree_to_tree[5, tree] = tree - 1
1266
tree_to_face[5, tree] = 4
1267
end
1268
1269
# Positive z-direction
1270
if cell_z < n_cells_z
1271
tree_to_tree[6, tree] = linear_indices[cell_x, cell_y, cell_z + 1] - 1
1272
tree_to_face[6, tree] = 4
1273
elseif periodicity[3]
1274
tree_to_tree[6, tree] = linear_indices[cell_x, cell_y, 1] - 1
1275
tree_to_face[6, tree] = 4
1276
else # Non-periodic boundary, tree and face point to themselves (zero-based indexing)
1277
tree_to_tree[6, tree] = tree - 1
1278
tree_to_face[6, tree] = 5
1279
end
1280
end
1281
1282
tree_to_edge = C_NULL
1283
# `p4est` docs: "in trivial cases it is just a pointer to a p4est_topix value of 0."
1284
# We don't need edge connectivity, so this is a trivial case.
1285
ett_offset = zeros(p4est_topidx_t, 1)
1286
edge_to_tree = C_NULL
1287
edge_to_edge = C_NULL
1288
1289
tree_to_corner = C_NULL
1290
# `p4est` docs: "in trivial cases it is just a pointer to a p4est_topix value of 0."
1291
# We don't need corner connectivity, so this is a trivial case.
1292
ctt_offset = zeros(p4est_topidx_t, 1)
1293
1294
corner_to_tree = C_NULL
1295
corner_to_corner = C_NULL
1296
1297
connectivity = p8est_connectivity_new_copy(n_vertices, n_trees, n_corners, n_edges,
1298
vertices, tree_to_vertex,
1299
tree_to_tree, tree_to_face,
1300
tree_to_edge, ett_offset,
1301
edge_to_tree, edge_to_edge,
1302
tree_to_corner, ctt_offset,
1303
corner_to_tree, corner_to_corner)
1304
1305
@assert p8est_connectivity_is_valid(connectivity) == 1
1306
1307
return connectivity
1308
end
1309
1310
function connectivity_cubed_sphere(trees_per_face_dimension, layers)
1311
n_cells_x = n_cells_y = trees_per_face_dimension
1312
n_cells_z = layers
1313
1314
linear_indices = LinearIndices((trees_per_face_dimension, trees_per_face_dimension,
1315
layers, 6))
1316
1317
# Vertices represent the coordinates of the forest. This is used by `p4est`
1318
# to write VTK files.
1319
# Trixi.jl doesn't use the coordinates from `p4est`, so the vertices can be empty.
1320
n_vertices = 0
1321
n_trees = 6 * n_cells_x * n_cells_y * n_cells_z
1322
# No edge connectivity is needed
1323
n_edges = 0
1324
# No corner connectivity is needed
1325
n_corners = 0
1326
vertices = C_NULL
1327
tree_to_vertex = C_NULL
1328
1329
tree_to_tree = Array{p4est_topidx_t, 2}(undef, 6, n_trees)
1330
tree_to_face = Array{Int8, 2}(undef, 6, n_trees)
1331
1332
# Illustration of the local coordinates of each face. ξ and η are the first
1333
# local coordinates of each face. The third local coordinate ζ is always
1334
# pointing outwards, which yields a right-handed coordinate system for each face.
1335
# ┌────────────────────────────────────────────────────┐
1336
# ╱│ ╱│
1337
# ╱ │ ξ <───┐ ╱ │
1338
# ╱ │ ╱ ╱ │
1339
# ╱ │ 4 (+y) V ╱ │
1340
# ╱ │ η ╱ │
1341
# ╱ │ ╱ │
1342
# ╱ │ ╱ │
1343
# ╱ │ ╱ │
1344
# ╱ │ ╱ │
1345
# ╱ │ 5 (-z) η ╱ │
1346
# ╱ │ ↑ ╱ │
1347
# ╱ │ │ ╱ │
1348
# ╱ │ ξ <───┘ ╱ │
1349
# ┌────────────────────────────────────────────────────┐ 2 (+x) │
1350
# │ │ │ │
1351
# │ │ │ ξ │
1352
# │ │ │ ↑ │
1353
# │ 1 (-x) │ │ │ │
1354
# │ │ │ │ │
1355
# │ ╱│ │ │ ╱ │
1356
# │ V │ │ │ V │
1357
# │ η ↓ │ │ η │
1358
# │ ξ └──────────────────────────────────────│─────────────┘
1359
# │ ╱ η 6 (+z) │ ╱
1360
# │ ╱ ↑ │ ╱
1361
# │ ╱ │ │ ╱
1362
# │ ╱ └───> ξ │ ╱
1363
# │ ╱ │ ╱
1364
# │ ╱ │ ╱ Global coordinates:
1365
# │ ╱ │ ╱ y
1366
# │ ╱ ┌───> ξ │ ╱ ↑
1367
# │ ╱ ╱ │ ╱ │
1368
# │ ╱ V 3 (-y) │ ╱ │
1369
# │ ╱ η │ ╱ └─────> x
1370
# │ ╱ │ ╱ ╱
1371
# │╱ │╱ V
1372
# └────────────────────────────────────────────────────┘ z
1373
for direction in 1:6
1374
for cell_z in 1:n_cells_z, cell_y in 1:n_cells_y, cell_x in 1:n_cells_x
1375
tree = linear_indices[cell_x, cell_y, cell_z, direction]
1376
1377
# Subtract 1 because `p4est` uses zero-based indexing
1378
# Negative x-direction
1379
if cell_x > 1 # Connect to tree at the same face
1380
tree_to_tree[1, tree] = linear_indices[cell_x - 1, cell_y, cell_z,
1381
direction] - 1
1382
tree_to_face[1, tree] = 1
1383
elseif direction == 1 # This is the -x face
1384
target = 4
1385
tree_to_tree[1, tree] = linear_indices[end, cell_y, cell_z, target] - 1
1386
tree_to_face[1, tree] = 1
1387
elseif direction == 2 # This is the +x face
1388
target = 3
1389
tree_to_tree[1, tree] = linear_indices[end, cell_y, cell_z, target] - 1
1390
tree_to_face[1, tree] = 1
1391
elseif direction == 3 # This is the -y face
1392
target = 1
1393
tree_to_tree[1, tree] = linear_indices[end, cell_y, cell_z, target] - 1
1394
tree_to_face[1, tree] = 1
1395
elseif direction == 4 # This is the +y face
1396
target = 2
1397
tree_to_tree[1, tree] = linear_indices[end, cell_y, cell_z, target] - 1
1398
tree_to_face[1, tree] = 1
1399
elseif direction == 5 # This is the -z face
1400
target = 2
1401
tree_to_tree[1, tree] = linear_indices[cell_y, 1, cell_z, target] - 1
1402
tree_to_face[1, tree] = 2
1403
else # direction == 6, this is the +z face
1404
target = 1
1405
tree_to_tree[1, tree] = linear_indices[end - cell_y + 1, end, cell_z,
1406
target] - 1
1407
tree_to_face[1, tree] = 9 # first face dimensions are oppositely oriented, add 6
1408
end
1409
1410
# Positive x-direction
1411
if cell_x < n_cells_x # Connect to tree at the same face
1412
tree_to_tree[2, tree] = linear_indices[cell_x + 1, cell_y, cell_z,
1413
direction] - 1
1414
tree_to_face[2, tree] = 0
1415
elseif direction == 1 # This is the -x face
1416
target = 3
1417
tree_to_tree[2, tree] = linear_indices[1, cell_y, cell_z, target] - 1
1418
tree_to_face[2, tree] = 0
1419
elseif direction == 2 # This is the +x face
1420
target = 4
1421
tree_to_tree[2, tree] = linear_indices[1, cell_y, cell_z, target] - 1
1422
tree_to_face[2, tree] = 0
1423
elseif direction == 3 # This is the -y face
1424
target = 2
1425
tree_to_tree[2, tree] = linear_indices[1, cell_y, cell_z, target] - 1
1426
tree_to_face[2, tree] = 0
1427
elseif direction == 4 # This is the +y face
1428
target = 1
1429
tree_to_tree[2, tree] = linear_indices[1, cell_y, cell_z, target] - 1
1430
tree_to_face[2, tree] = 0
1431
elseif direction == 5 # This is the -z face
1432
target = 1
1433
tree_to_tree[2, tree] = linear_indices[end - cell_y + 1, 1, cell_z,
1434
target] - 1
1435
tree_to_face[2, tree] = 8 # first face dimensions are oppositely oriented, add 6
1436
else # direction == 6, this is the +z face
1437
target = 2
1438
tree_to_tree[2, tree] = linear_indices[cell_y, end, cell_z, target] - 1
1439
tree_to_face[2, tree] = 3
1440
end
1441
1442
# Negative y-direction
1443
if cell_y > 1 # Connect to tree at the same face
1444
tree_to_tree[3, tree] = linear_indices[cell_x, cell_y - 1, cell_z,
1445
direction] - 1
1446
tree_to_face[3, tree] = 3
1447
elseif direction == 1
1448
target = 5
1449
tree_to_tree[3, tree] = linear_indices[end, end - cell_x + 1, cell_z,
1450
target] - 1
1451
tree_to_face[3, tree] = 7 # first face dimensions are oppositely oriented, add 6
1452
elseif direction == 2
1453
target = 5
1454
tree_to_tree[3, tree] = linear_indices[1, cell_x, cell_z, target] - 1
1455
tree_to_face[3, tree] = 0
1456
elseif direction == 3
1457
target = 5
1458
tree_to_tree[3, tree] = linear_indices[end - cell_x + 1, 1, cell_z,
1459
target] - 1
1460
tree_to_face[3, tree] = 8 # first face dimensions are oppositely oriented, add 6
1461
elseif direction == 4
1462
target = 5
1463
tree_to_tree[3, tree] = linear_indices[cell_x, end, cell_z, target] - 1
1464
tree_to_face[3, tree] = 3
1465
elseif direction == 5
1466
target = 3
1467
tree_to_tree[3, tree] = linear_indices[end - cell_x + 1, 1, cell_z,
1468
target] - 1
1469
tree_to_face[3, tree] = 8 # first face dimensions are oppositely oriented, add 6
1470
else # direction == 6
1471
target = 3
1472
tree_to_tree[3, tree] = linear_indices[cell_x, end, cell_z, target] - 1
1473
tree_to_face[3, tree] = 3
1474
end
1475
1476
# Positive y-direction
1477
if cell_y < n_cells_y # Connect to tree at the same face
1478
tree_to_tree[4, tree] = linear_indices[cell_x, cell_y + 1, cell_z,
1479
direction] - 1
1480
tree_to_face[4, tree] = 2
1481
elseif direction == 1
1482
target = 6
1483
tree_to_tree[4, tree] = linear_indices[1, end - cell_x + 1, cell_z,
1484
target] - 1
1485
tree_to_face[4, tree] = 6 # first face dimensions are oppositely oriented, add 6
1486
elseif direction == 2
1487
target = 6
1488
tree_to_tree[4, tree] = linear_indices[end, cell_x, cell_z, target] - 1
1489
tree_to_face[4, tree] = 1
1490
elseif direction == 3
1491
target = 6
1492
tree_to_tree[4, tree] = linear_indices[cell_x, 1, cell_z, target] - 1
1493
tree_to_face[4, tree] = 2
1494
elseif direction == 4
1495
target = 6
1496
tree_to_tree[4, tree] = linear_indices[end - cell_x + 1, end, cell_z,
1497
target] - 1
1498
tree_to_face[4, tree] = 9 # first face dimensions are oppositely oriented, add 6
1499
elseif direction == 5
1500
target = 4
1501
tree_to_tree[4, tree] = linear_indices[cell_x, 1, cell_z, target] - 1
1502
tree_to_face[4, tree] = 2
1503
else # direction == 6
1504
target = 4
1505
tree_to_tree[4, tree] = linear_indices[end - cell_x + 1, end, cell_z,
1506
target] - 1
1507
tree_to_face[4, tree] = 9 # first face dimensions are oppositely oriented, add 6
1508
end
1509
1510
# Negative z-direction
1511
if cell_z > 1
1512
tree_to_tree[5, tree] = linear_indices[cell_x, cell_y, cell_z - 1,
1513
direction] - 1
1514
tree_to_face[5, tree] = 5
1515
else # Non-periodic boundary, tree and face point to themselves (zero-based indexing)
1516
tree_to_tree[5, tree] = tree - 1
1517
tree_to_face[5, tree] = 4
1518
end
1519
1520
# Positive z-direction
1521
if cell_z < n_cells_z
1522
tree_to_tree[6, tree] = linear_indices[cell_x, cell_y, cell_z + 1,
1523
direction] - 1
1524
tree_to_face[6, tree] = 4
1525
else # Non-periodic boundary, tree and face point to themselves (zero-based indexing)
1526
tree_to_tree[6, tree] = tree - 1
1527
tree_to_face[6, tree] = 5
1528
end
1529
end
1530
end
1531
1532
tree_to_edge = C_NULL
1533
# `p4est` docs: "in trivial cases it is just a pointer to a p4est_topix value of 0."
1534
# We don't need edge connectivity, so this is a trivial case.
1535
ett_offset = zeros(p4est_topidx_t, 1)
1536
edge_to_tree = C_NULL
1537
edge_to_edge = C_NULL
1538
1539
tree_to_corner = C_NULL
1540
# `p4est` docs: "in trivial cases it is just a pointer to a p4est_topix value of 0."
1541
# We don't need corner connectivity, so this is a trivial case.
1542
ctt_offset = zeros(p4est_topidx_t, 1)
1543
1544
corner_to_tree = C_NULL
1545
corner_to_corner = C_NULL
1546
1547
connectivity = p8est_connectivity_new_copy(n_vertices, n_trees, n_corners, n_edges,
1548
vertices, tree_to_vertex,
1549
tree_to_tree, tree_to_face,
1550
tree_to_edge, ett_offset,
1551
edge_to_tree, edge_to_edge,
1552
tree_to_corner, ctt_offset,
1553
corner_to_tree, corner_to_corner)
1554
1555
@assert p8est_connectivity_is_valid(connectivity) == 1
1556
1557
return connectivity
1558
end
1559
1560
# Calculate physical coordinates of each node of a structured mesh.
1561
# This function assumes a structured mesh with trees in row order.
1562
# 2D version
1563
function calc_tree_node_coordinates!(node_coordinates::AbstractArray{<:Any, 4},
1564
nodes, mapping, trees_per_dimension)
1565
linear_indices = LinearIndices(trees_per_dimension)
1566
1567
# Get cell length in reference mesh
1568
dx = 2 / trees_per_dimension[1]
1569
dy = 2 / trees_per_dimension[2]
1570
1571
for cell_y in 1:trees_per_dimension[2], cell_x in 1:trees_per_dimension[1]
1572
tree_id = linear_indices[cell_x, cell_y]
1573
1574
# Calculate node coordinates of reference mesh
1575
cell_x_offset = -1 + (cell_x - 1) * dx + dx / 2
1576
cell_y_offset = -1 + (cell_y - 1) * dy + dy / 2
1577
1578
for j in eachindex(nodes), i in eachindex(nodes)
1579
# node_coordinates are the mapped reference node coordinates
1580
node_coordinates[:, i, j, tree_id] .= mapping(cell_x_offset +
1581
dx / 2 * nodes[i],
1582
cell_y_offset +
1583
dy / 2 * nodes[j])
1584
end
1585
end
1586
end
1587
1588
# 3D version
1589
function calc_tree_node_coordinates!(node_coordinates::AbstractArray{<:Any, 5},
1590
nodes, mapping, trees_per_dimension)
1591
linear_indices = LinearIndices(trees_per_dimension)
1592
1593
# Get cell length in reference mesh
1594
dx = 2 / trees_per_dimension[1]
1595
dy = 2 / trees_per_dimension[2]
1596
dz = 2 / trees_per_dimension[3]
1597
1598
for cell_z in 1:trees_per_dimension[3],
1599
cell_y in 1:trees_per_dimension[2],
1600
cell_x in 1:trees_per_dimension[1]
1601
1602
tree_id = linear_indices[cell_x, cell_y, cell_z]
1603
1604
# Calculate node coordinates of reference mesh
1605
cell_x_offset = -1 + (cell_x - 1) * dx + dx / 2
1606
cell_y_offset = -1 + (cell_y - 1) * dy + dy / 2
1607
cell_z_offset = -1 + (cell_z - 1) * dz + dz / 2
1608
1609
for k in eachindex(nodes), j in eachindex(nodes), i in eachindex(nodes)
1610
# node_coordinates are the mapped reference node coordinates
1611
node_coordinates[:, i, j, k, tree_id] .= mapping(cell_x_offset +
1612
dx / 2 * nodes[i],
1613
cell_y_offset +
1614
dy / 2 * nodes[j],
1615
cell_z_offset +
1616
dz / 2 * nodes[k])
1617
end
1618
end
1619
end
1620
1621
# Calculate physical coordinates of each node of an unstructured mesh.
1622
# Extract corners of each tree from the connectivity,
1623
# interpolate to requested interpolation nodes,
1624
# map the resulting coordinates with the specified mapping.
1625
# 2D version
1626
function calc_tree_node_coordinates!(node_coordinates::AbstractArray{RealT, 4},
1627
nodes, mapping,
1628
vertices, tree_to_vertex) where {RealT}
1629
nodes_in = [-1.0, 1.0]
1630
matrix = polynomial_interpolation_matrix(nodes_in, nodes)
1631
data_in = Array{RealT, 3}(undef, 2, 2, 2)
1632
tmp1 = zeros(RealT, 2, length(nodes), length(nodes_in))
1633
1634
for tree in 1:size(tree_to_vertex, 2)
1635
# Tree vertices are stored in Z-order, ignore z-coordinate in 2D, zero-based indexing
1636
@views data_in[:, 1, 1] .= vertices[1:2, tree_to_vertex[1, tree] + 1]
1637
@views data_in[:, 2, 1] .= vertices[1:2, tree_to_vertex[2, tree] + 1]
1638
@views data_in[:, 1, 2] .= vertices[1:2, tree_to_vertex[3, tree] + 1]
1639
@views data_in[:, 2, 2] .= vertices[1:2, tree_to_vertex[4, tree] + 1]
1640
1641
# Interpolate corner coordinates to specified nodes
1642
multiply_dimensionwise!(view(node_coordinates, :, :, :, tree),
1643
matrix, matrix,
1644
data_in,
1645
tmp1)
1646
end
1647
1648
map_node_coordinates!(node_coordinates, mapping)
1649
end
1650
1651
function map_node_coordinates!(node_coordinates::AbstractArray{<:Any, 4}, mapping)
1652
for tree in axes(node_coordinates, 4),
1653
j in axes(node_coordinates, 3),
1654
i in axes(node_coordinates, 2)
1655
1656
node_coordinates[:, i, j, tree] .= mapping(node_coordinates[1, i, j, tree],
1657
node_coordinates[2, i, j, tree])
1658
end
1659
1660
return node_coordinates
1661
end
1662
1663
function map_node_coordinates!(node_coordinates::AbstractArray{<:Any, 4},
1664
mapping::Nothing)
1665
return node_coordinates
1666
end
1667
1668
# 3D version
1669
function calc_tree_node_coordinates!(node_coordinates::AbstractArray{RealT, 5},
1670
nodes, mapping,
1671
vertices, tree_to_vertex) where {RealT}
1672
nodes_in = [-1.0, 1.0]
1673
matrix = polynomial_interpolation_matrix(nodes_in, nodes)
1674
data_in = Array{RealT, 4}(undef, 3, 2, 2, 2)
1675
1676
for tree in 1:size(tree_to_vertex, 2)
1677
# Tree vertices are stored in Z-order, zero-based indexing
1678
@views data_in[:, 1, 1, 1] .= vertices[:, tree_to_vertex[1, tree] + 1]
1679
@views data_in[:, 2, 1, 1] .= vertices[:, tree_to_vertex[2, tree] + 1]
1680
@views data_in[:, 1, 2, 1] .= vertices[:, tree_to_vertex[3, tree] + 1]
1681
@views data_in[:, 2, 2, 1] .= vertices[:, tree_to_vertex[4, tree] + 1]
1682
@views data_in[:, 1, 1, 2] .= vertices[:, tree_to_vertex[5, tree] + 1]
1683
@views data_in[:, 2, 1, 2] .= vertices[:, tree_to_vertex[6, tree] + 1]
1684
@views data_in[:, 1, 2, 2] .= vertices[:, tree_to_vertex[7, tree] + 1]
1685
@views data_in[:, 2, 2, 2] .= vertices[:, tree_to_vertex[8, tree] + 1]
1686
1687
# Interpolate corner coordinates to specified nodes
1688
multiply_dimensionwise!(view(node_coordinates, :, :, :, :, tree),
1689
matrix, matrix, matrix,
1690
data_in)
1691
end
1692
1693
map_node_coordinates!(node_coordinates, mapping)
1694
end
1695
1696
function map_node_coordinates!(node_coordinates::AbstractArray{<:Any, 5}, mapping)
1697
for tree in axes(node_coordinates, 5),
1698
k in axes(node_coordinates, 4),
1699
j in axes(node_coordinates, 3),
1700
i in axes(node_coordinates, 2)
1701
1702
node_coordinates[:, i, j, k, tree] .= mapping(node_coordinates[1, i, j, k,
1703
tree],
1704
node_coordinates[2, i, j, k,
1705
tree],
1706
node_coordinates[3, i, j, k,
1707
tree])
1708
end
1709
1710
return node_coordinates
1711
end
1712
1713
function map_node_coordinates!(node_coordinates::AbstractArray{<:Any, 5},
1714
mapping::Nothing)
1715
return node_coordinates
1716
end
1717
1718
# Calculate physical coordinates of each node of a cubed sphere mesh.
1719
function calc_tree_node_coordinates!(node_coordinates::AbstractArray{<:Any, 5},
1720
nodes, trees_per_face_dimension, layers,
1721
inner_radius, thickness)
1722
n_cells_x = n_cells_y = trees_per_face_dimension
1723
n_cells_z = layers
1724
1725
linear_indices = LinearIndices((n_cells_x, n_cells_y, n_cells_z, 6))
1726
1727
# Get cell length in reference mesh
1728
dx = 2 / n_cells_x
1729
dy = 2 / n_cells_y
1730
dz = 2 / n_cells_z
1731
1732
for direction in 1:6
1733
for cell_z in 1:n_cells_z, cell_y in 1:n_cells_y, cell_x in 1:n_cells_x
1734
tree = linear_indices[cell_x, cell_y, cell_z, direction]
1735
1736
x_offset = -1 + (cell_x - 1) * dx + dx / 2
1737
y_offset = -1 + (cell_y - 1) * dy + dy / 2
1738
z_offset = -1 + (cell_z - 1) * dz + dz / 2
1739
1740
for k in eachindex(nodes), j in eachindex(nodes), i in eachindex(nodes)
1741
# node_coordinates are the mapped reference node coordinates
1742
node_coordinates[:, i, j, k, tree] .= cubed_sphere_mapping(x_offset +
1743
dx / 2 *
1744
nodes[i],
1745
y_offset +
1746
dy / 2 *
1747
nodes[j],
1748
z_offset +
1749
dz / 2 *
1750
nodes[k],
1751
inner_radius,
1752
thickness,
1753
direction)
1754
end
1755
end
1756
end
1757
end
1758
1759
# Map the computational coordinates xi, eta, zeta to the specified side of a cubed sphere
1760
# with the specified inner radius and thickness.
1761
function cubed_sphere_mapping(xi, eta, zeta, inner_radius, thickness, direction)
1762
alpha = xi * pi / 4
1763
beta = eta * pi / 4
1764
1765
# Equiangular projection
1766
x = tan(alpha)
1767
y = tan(beta)
1768
1769
# Coordinates on unit cube per direction, see illustration above in the function connectivity_cubed_sphere
1770
cube_coordinates = (SVector(-1, -x, y),
1771
SVector(1, x, y),
1772
SVector(x, -1, y),
1773
SVector(-x, 1, y),
1774
SVector(-x, y, -1),
1775
SVector(x, y, 1))
1776
1777
# Radius on cube surface
1778
r = sqrt(1 + x^2 + y^2)
1779
1780
# Radius of the sphere
1781
R = inner_radius + thickness * (0.5f0 * (zeta + 1))
1782
1783
# Projection onto the sphere
1784
return R / r * cube_coordinates[direction]
1785
end
1786
1787
# Calculate physical coordinates of each element of an unstructured mesh read
1788
# in from a HOHQMesh file. This calculation is done with the transfinite interpolation
1789
# routines found in `mappings_geometry_curved_2d.jl` or `mappings_geometry_straight_2d.jl`
1790
function calc_tree_node_coordinates!(node_coordinates::AbstractArray{<:Any, 4},
1791
file_lines::Vector{String}, nodes, vertices, RealT)
1792
# Get the number of trees (elements) and the number of interpolation nodes
1793
n_trees = last(size(node_coordinates))
1794
nnodes = length(nodes)
1795
1796
# Setup the starting file index to read in element indices and the additional
1797
# higher-order (curved) boundary information provided by HOHQMesh.
1798
file_idx = findfirst(contains("** mesh polynomial degree"), file_lines) + 1
1799
1800
# Create a work set of Gamma curves to create the node coordinates
1801
CurvedSurfaceT = CurvedSurface{RealT}
1802
surface_curves = Array{CurvedSurfaceT}(undef, 4)
1803
1804
# Create other work arrays to perform the mesh construction
1805
element_node_ids = Array{Int}(undef, 4)
1806
curved_check = Vector{Int}(undef, 4)
1807
quad_vertices = Array{RealT}(undef, (4, 2))
1808
quad_vertices_flipped = Array{RealT}(undef, (4, 2))
1809
curve_values = Array{RealT}(undef, (nnodes, 2))
1810
1811
# Create the barycentric weights used for the surface interpolations
1812
bary_weights_ = barycentric_weights(nodes)
1813
bary_weights = SVector{nnodes}(bary_weights_)
1814
1815
# Loop through all the trees, i.e., the elements generated by HOHQMesh and create the node coordinates.
1816
# When we extract information from the `current_line` we start at index 2 in order to
1817
# avoid the Abaqus comment character "** "
1818
for tree in 1:n_trees
1819
# Pull the vertex node IDs
1820
current_line = split(file_lines[file_idx])
1821
element_node_ids[1] = parse(Int, current_line[2])
1822
element_node_ids[2] = parse(Int, current_line[3])
1823
element_node_ids[3] = parse(Int, current_line[4])
1824
element_node_ids[4] = parse(Int, current_line[5])
1825
1826
# Pull the (x,y) values of the four vertices of the current tree out of the global vertices array
1827
for i in 1:4
1828
quad_vertices[i, :] .= vertices[1:2, element_node_ids[i]] # 2D => 1:2
1829
end
1830
# Pull the information to check if boundary is curved/higher-order in order to read in additional data
1831
file_idx += 1
1832
current_line = split(file_lines[file_idx])
1833
# Note: This strategy is HOHQMesh-Abaqus.inp specific!
1834
curved_check[1] = parse(Int, current_line[2])
1835
curved_check[2] = parse(Int, current_line[3])
1836
curved_check[3] = parse(Int, current_line[4])
1837
curved_check[4] = parse(Int, current_line[5])
1838
if sum(curved_check) == 0
1839
# Create the node coordinates on this particular element
1840
calc_node_coordinates!(node_coordinates, tree, nodes, quad_vertices)
1841
else
1842
# Quadrilateral element has at least one curved/higher-order side
1843
# Flip node ordering to make sure the element is right-handed for the interpolations
1844
m1 = 1
1845
m2 = 2
1846
@views quad_vertices_flipped[1, :] .= quad_vertices[4, :]
1847
@views quad_vertices_flipped[2, :] .= quad_vertices[2, :]
1848
@views quad_vertices_flipped[3, :] .= quad_vertices[3, :]
1849
@views quad_vertices_flipped[4, :] .= quad_vertices[1, :]
1850
for i in 1:4
1851
if curved_check[i] == 0
1852
# When curved_check[i] is 0 then the "curve" from vertex `i` to vertex `i+1` is a straight line.
1853
# Evaluate a linear interpolant between the two points at each of the nodes.
1854
for k in 1:nnodes
1855
curve_values[k, 1] = linear_interpolate(nodes[k],
1856
quad_vertices_flipped[m1,
1857
1],
1858
quad_vertices_flipped[m2,
1859
1])
1860
curve_values[k, 2] = linear_interpolate(nodes[k],
1861
quad_vertices_flipped[m1,
1862
2],
1863
quad_vertices_flipped[m2,
1864
2])
1865
end
1866
else
1867
# When curved_check[i] is 1 this curved/higher-order boundary information is supplied by the mesh
1868
# generator. So we just read it into a work array
1869
for k in 1:nnodes
1870
file_idx += 1
1871
current_line = split(file_lines[file_idx])
1872
curve_values[k, 1] = parse(RealT, current_line[2])
1873
curve_values[k, 2] = parse(RealT, current_line[3])
1874
end
1875
end
1876
# Construct the curve interpolant for the current side
1877
surface_curves[i] = CurvedSurfaceT(nodes, bary_weights,
1878
copy(curve_values))
1879
# Indexing update that contains a "flip" to ensure correct element orientation.
1880
# If we need to construct the straight line "curves" when curved_check[i] == 0
1881
m1 += 1
1882
if i == 3
1883
m2 = 1
1884
else
1885
m2 += 1
1886
end
1887
end
1888
# Create the node coordinates on this particular element
1889
calc_node_coordinates!(node_coordinates, tree, nodes, surface_curves)
1890
end
1891
# Move file index to the next tree
1892
file_idx += 1
1893
end
1894
1895
return file_idx
1896
end
1897
1898
# TODO: 3D version
1899
# Version for quadratic 2D elements, i.e., second-order quads.
1900
function calc_tree_node_coordinates!(node_coordinates::AbstractArray{<:Any, 4},
1901
element_lines, nodes, vertices, RealT,
1902
linear_quads, mesh_nodes)
1903
nnodes = length(nodes)
1904
1905
# Create a work set of Gamma curves to create the node coordinates
1906
CurvedSurfaceT = CurvedSurface{RealT}
1907
surface_curves = Array{CurvedSurfaceT}(undef, 4)
1908
1909
# Create other work arrays to perform the mesh construction
1910
element_node_ids = Array{Int}(undef, 4)
1911
quad_vertices = Array{RealT}(undef, (4, 2))
1912
curve_values = Array{RealT}(undef, (nnodes, 2))
1913
1914
# Create the barycentric weights used for the surface interpolations
1915
bary_weights_ = barycentric_weights(nodes)
1916
bary_weights = SVector{nnodes}(bary_weights_)
1917
1918
element_set_order = 1
1919
tree = 0
1920
for line_idx in 1:length(element_lines)
1921
line = element_lines[line_idx]
1922
1923
# Check if a new element type/element set is defined
1924
if startswith(line, "*ELEMENT")
1925
# Retrieve element type
1926
current_element_type = match(r"\*ELEMENT, type=([^,]+)", line).captures[1]
1927
1928
# Check if these are linear elements
1929
if occursin(linear_quads, current_element_type)
1930
element_set_order = 1
1931
else
1932
element_set_order = 2
1933
end
1934
else # Element data
1935
tree += 1
1936
1937
# Pull the vertex node IDs
1938
line_split = split(line, r",\s+")
1939
element_nodes = parse.(Int, line_split)
1940
1941
if element_set_order == 1
1942
element_node_ids[1] = element_nodes[2]
1943
element_node_ids[2] = element_nodes[3]
1944
element_node_ids[3] = element_nodes[4]
1945
element_node_ids[4] = element_nodes[5]
1946
1947
# Create the node coordinates on this particular element
1948
# Pull the (x,y) values of the four vertices of the current tree out of the global vertices array
1949
for i in 1:4
1950
quad_vertices[i, :] .= vertices[1:2, element_node_ids[i]] # 2D => 1:2
1951
end
1952
calc_node_coordinates!(node_coordinates, tree, nodes, quad_vertices)
1953
else # element_set_order == 2
1954
for edge in 1:4
1955
node1 = element_nodes[edge + 1] # "Left" node
1956
node2 = element_nodes[edge + 5] # "Middle" node
1957
node3 = edge == 4 ? element_nodes[2] : element_nodes[edge + 2] # "Right" node
1958
1959
node1_coords = mesh_nodes[node1]
1960
node2_coords = mesh_nodes[node2]
1961
node3_coords = mesh_nodes[node3]
1962
1963
# The nodes for an Abaqus element are labeled following a closed path
1964
# around the element:
1965
#
1966
# <----
1967
# *-----*-----*
1968
# | |
1969
# | | | ^
1970
# | * * |
1971
# v | | |
1972
# | |
1973
# *-----*-----*
1974
# ---->
1975
# `curve_values`, however, requires to sort the nodes into a
1976
# valid coordinate system,
1977
#
1978
# *-----*-----*
1979
# | |
1980
# | |
1981
# * *
1982
# | |
1983
# ^ η | |
1984
# | *-----*-----*
1985
# |----> ξ
1986
# thus we need to flip the node order for the second xi and eta edges met.
1987
1988
if edge in [1, 2]
1989
curve_values[1, 1] = node1_coords[1]
1990
curve_values[1, 2] = node1_coords[2]
1991
1992
curve_values[2, 1] = node2_coords[1]
1993
curve_values[2, 2] = node2_coords[2]
1994
1995
curve_values[3, 1] = node3_coords[1]
1996
curve_values[3, 2] = node3_coords[2]
1997
else # Flip "left" and "right" nodes
1998
curve_values[1, 1] = node3_coords[1]
1999
curve_values[1, 2] = node3_coords[2]
2000
2001
curve_values[2, 1] = node2_coords[1]
2002
curve_values[2, 2] = node2_coords[2]
2003
2004
curve_values[3, 1] = node1_coords[1]
2005
curve_values[3, 2] = node1_coords[2]
2006
end
2007
2008
# Construct the curve interpolant for the current side
2009
surface_curves[edge] = CurvedSurfaceT(nodes, bary_weights,
2010
copy(curve_values))
2011
end
2012
2013
# Create the node coordinates on this particular element
2014
calc_node_coordinates!(node_coordinates, tree, nodes, surface_curves)
2015
end
2016
end
2017
end
2018
end
2019
2020
# Calculate physical coordinates of each element of an unstructured mesh read
2021
# in from a HOHQMesh file. This calculation is done with the transfinite interpolation
2022
# routines found in `transfinite_mappings_3d.jl`
2023
function calc_tree_node_coordinates!(node_coordinates::AbstractArray{<:Any, 5},
2024
file_lines::Vector{String}, nodes, vertices, RealT)
2025
# Get the number of trees and the number of interpolation nodes
2026
n_trees = last(size(node_coordinates))
2027
nnodes = length(nodes)
2028
2029
# Setup the starting file index to read in element indices and the additional
2030
# curved/higher-order boundary information provided by HOHQMesh.
2031
file_idx = findfirst(contains("** mesh polynomial degree"), file_lines) + 1
2032
2033
# Create a work set of Gamma curves to create the node coordinates
2034
CurvedFaceT = CurvedFace{RealT}
2035
face_curves = Array{CurvedFaceT}(undef, 6)
2036
2037
# Create other work arrays to perform the mesh construction
2038
element_node_ids = Array{Int}(undef, 8)
2039
curved_check = Vector{Int}(undef, 6)
2040
hex_vertices = Array{RealT}(undef, (3, 8))
2041
face_vertices = Array{RealT}(undef, (3, 4))
2042
curve_values = Array{RealT}(undef, (3, nnodes, nnodes))
2043
2044
# Create the barycentric weights used for the surface interpolations
2045
bary_weights_ = barycentric_weights(nodes)
2046
bary_weights = SVector{nnodes}(bary_weights_)
2047
2048
# Loop through all the trees, i.e., the elements generated by HOHQMesh and create the node coordinates.
2049
# When we extract information from the `current_line` we start at index 2 in order to
2050
# avoid the Abaqus comment character "** "
2051
for tree in 1:n_trees
2052
# pull the vertex node IDs
2053
current_line = split(file_lines[file_idx])
2054
element_node_ids[1] = parse(Int, current_line[2])
2055
element_node_ids[2] = parse(Int, current_line[3])
2056
element_node_ids[3] = parse(Int, current_line[4])
2057
element_node_ids[4] = parse(Int, current_line[5])
2058
element_node_ids[5] = parse(Int, current_line[6])
2059
element_node_ids[6] = parse(Int, current_line[7])
2060
element_node_ids[7] = parse(Int, current_line[8])
2061
element_node_ids[8] = parse(Int, current_line[9])
2062
2063
# Pull the (x, y, z) values of the eight vertices of the current tree out of the global vertices array
2064
for i in 1:8
2065
hex_vertices[:, i] .= vertices[:, element_node_ids[i]]
2066
end
2067
# Pull the information to check if boundary is curved/higher-order in order to read in additional data
2068
file_idx += 1
2069
current_line = split(file_lines[file_idx])
2070
curved_check[1] = parse(Int, current_line[2])
2071
curved_check[2] = parse(Int, current_line[3])
2072
curved_check[3] = parse(Int, current_line[4])
2073
curved_check[4] = parse(Int, current_line[5])
2074
curved_check[5] = parse(Int, current_line[6])
2075
curved_check[6] = parse(Int, current_line[7])
2076
if sum(curved_check) == 0
2077
# Create the node coordinates on this element
2078
calc_node_coordinates!(node_coordinates, tree, nodes, hex_vertices)
2079
else
2080
# Hexahedral element has at least one curved/higher-order side
2081
for face in 1:6
2082
if curved_check[face] == 0
2083
# Face is a flat plane.
2084
# Evaluate a bilinear interpolant between the four vertices
2085
# of the face at each of the nodes.
2086
get_vertices_for_bilinear_interpolant!(face_vertices, face,
2087
hex_vertices)
2088
for q in 1:nnodes, p in 1:nnodes
2089
@views bilinear_interpolation!(curve_values[:, p, q],
2090
face_vertices, nodes[p],
2091
nodes[q])
2092
end
2093
else # curved_check[face] == 1
2094
# Curved/higher-order face boundary information is supplied by
2095
# the mesh file. Just read it into a work array
2096
for q in 1:nnodes, p in 1:nnodes
2097
file_idx += 1
2098
current_line = split(file_lines[file_idx])
2099
curve_values[1, p, q] = parse(RealT, current_line[2])
2100
curve_values[2, p, q] = parse(RealT, current_line[3])
2101
curve_values[3, p, q] = parse(RealT, current_line[4])
2102
end
2103
end
2104
# Construct the curve interpolant for the current side
2105
face_curves[face] = CurvedFaceT(nodes, bary_weights, copy(curve_values))
2106
end
2107
# Create the node coordinates on this particular element
2108
calc_node_coordinates!(node_coordinates, tree, nodes, face_curves)
2109
end
2110
# Move file index to the next tree
2111
file_idx += 1
2112
end
2113
2114
return file_idx
2115
end
2116
2117
# Given the eight `hex_vertices` for a hexahedral element extract
2118
# the four `face_vertices` for a particular `face_index`.
2119
function get_vertices_for_bilinear_interpolant!(face_vertices, face_index, hex_vertices)
2120
if face_index == 1
2121
@views face_vertices[:, 1] .= hex_vertices[:, 1]
2122
@views face_vertices[:, 2] .= hex_vertices[:, 2]
2123
@views face_vertices[:, 3] .= hex_vertices[:, 6]
2124
@views face_vertices[:, 4] .= hex_vertices[:, 5]
2125
elseif face_index == 2
2126
@views face_vertices[:, 1] .= hex_vertices[:, 4]
2127
@views face_vertices[:, 2] .= hex_vertices[:, 3]
2128
@views face_vertices[:, 3] .= hex_vertices[:, 7]
2129
@views face_vertices[:, 4] .= hex_vertices[:, 8]
2130
elseif face_index == 3
2131
@views face_vertices[:, 1] .= hex_vertices[:, 1]
2132
@views face_vertices[:, 2] .= hex_vertices[:, 2]
2133
@views face_vertices[:, 3] .= hex_vertices[:, 3]
2134
@views face_vertices[:, 4] .= hex_vertices[:, 4]
2135
elseif face_index == 4
2136
@views face_vertices[:, 1] .= hex_vertices[:, 2]
2137
@views face_vertices[:, 2] .= hex_vertices[:, 3]
2138
@views face_vertices[:, 3] .= hex_vertices[:, 6]
2139
@views face_vertices[:, 4] .= hex_vertices[:, 7]
2140
elseif face_index == 5
2141
@views face_vertices[:, 1] .= hex_vertices[:, 5]
2142
@views face_vertices[:, 2] .= hex_vertices[:, 6]
2143
@views face_vertices[:, 3] .= hex_vertices[:, 7]
2144
@views face_vertices[:, 4] .= hex_vertices[:, 8]
2145
else # face_index == 6
2146
@views face_vertices[:, 1] .= hex_vertices[:, 1]
2147
@views face_vertices[:, 2] .= hex_vertices[:, 4]
2148
@views face_vertices[:, 3] .= hex_vertices[:, 8]
2149
@views face_vertices[:, 4] .= hex_vertices[:, 5]
2150
end
2151
end
2152
2153
# Evaluate a bilinear interpolant at a point (u,v) given the four vertices where the face is right-handed
2154
# 4 3
2155
# o----------------o
2156
# | |
2157
# | |
2158
# | |
2159
# | |
2160
# | |
2161
# | |
2162
# o----------------o
2163
# 1 2
2164
# and return the 3D coordinate point (x, y, z)
2165
function bilinear_interpolation!(coordinate, face_vertices, u, v)
2166
for j in 1:3
2167
coordinate[j] = 0.25f0 * (face_vertices[j, 1] * (1 - u) * (1 - v)
2168
+ face_vertices[j, 2] * (1 + u) * (1 - v)
2169
+ face_vertices[j, 3] * (1 + u) * (1 + v)
2170
+ face_vertices[j, 4] * (1 - u) * (1 + v))
2171
end
2172
end
2173
2174
function get_global_first_element_ids(mesh::P4estMesh)
2175
return unsafe_wrap(Array, mesh.p4est.global_first_quadrant, mpi_nranks() + 1)
2176
end
2177
2178
function balance!(mesh::P4estMesh{2}, init_fn = C_NULL)
2179
p4est_balance(mesh.p4est, P4EST_CONNECT_FACE, init_fn)
2180
# Due to a bug in `p4est`, the forest needs to be rebalanced twice sometimes
2181
# See https://github.com/cburstedde/p4est/issues/112
2182
p4est_balance(mesh.p4est, P4EST_CONNECT_FACE, init_fn)
2183
end
2184
2185
function balance!(mesh::P4estMesh{3}, init_fn = C_NULL)
2186
p8est_balance(mesh.p4est, P8EST_CONNECT_FACE, init_fn)
2187
end
2188
2189
function partition!(mesh::P4estMesh{2}; weight_fn = C_NULL)
2190
p4est_partition(mesh.p4est, Int(mesh.p4est_partition_allow_for_coarsening),
2191
weight_fn)
2192
end
2193
2194
function partition!(mesh::P4estMesh{3}; weight_fn = C_NULL)
2195
p8est_partition(mesh.p4est, Int(mesh.p4est_partition_allow_for_coarsening),
2196
weight_fn)
2197
end
2198
2199
function update_ghost_layer!(mesh::P4estMesh)
2200
ghost_destroy_p4est(mesh.ghost)
2201
mesh.ghost = PointerWrapper(ghost_new_p4est(mesh.p4est))
2202
end
2203
2204
function init_fn(p4est, which_tree, quadrant)
2205
# Unpack quadrant's user data ([global quad ID, controller_value])
2206
# Use `unsafe_load` here since `quadrant.p.user_data isa Ptr{Ptr{Nothing}}`
2207
# and we only need the first (only!) entry
2208
pw = PointerWrapper(Int, unsafe_load(quadrant.p.user_data))
2209
2210
# Initialize quad ID as -1 and controller_value as 0 (don't refine or coarsen)
2211
pw[1] = -1
2212
pw[2] = 0
2213
return nothing
2214
end
2215
2216
# 2D
2217
function cfunction(::typeof(init_fn), ::Val{2})
2218
@cfunction(init_fn, Cvoid,
2219
(Ptr{p4est_t}, Ptr{p4est_topidx_t}, Ptr{p4est_quadrant_t}))
2220
end
2221
# 3D
2222
function cfunction(::typeof(init_fn), ::Val{3})
2223
@cfunction(init_fn, Cvoid,
2224
(Ptr{p8est_t}, Ptr{p4est_topidx_t}, Ptr{p8est_quadrant_t}))
2225
end
2226
2227
function refine_fn(p4est, which_tree, quadrant)
2228
# Controller value has been copied to the quadrant's user data storage before.
2229
# Unpack quadrant's user data ([global quad ID, controller_value]).
2230
# Use `unsafe_load` here since `quadrant.p.user_data isa Ptr{Ptr{Nothing}}`
2231
# and we only need the first (only!) entry
2232
pw = PointerWrapper(Int, unsafe_load(quadrant.p.user_data))
2233
controller_value = pw[2]
2234
2235
if controller_value > 0
2236
# return true (refine)
2237
return Cint(1)
2238
else
2239
# return false (don't refine)
2240
return Cint(0)
2241
end
2242
end
2243
2244
# 2D
2245
function cfunction(::typeof(refine_fn), ::Val{2})
2246
@cfunction(refine_fn, Cint,
2247
(Ptr{p4est_t}, Ptr{p4est_topidx_t}, Ptr{p4est_quadrant_t}))
2248
end
2249
# 3D
2250
function cfunction(::typeof(refine_fn), ::Val{3})
2251
@cfunction(refine_fn, Cint,
2252
(Ptr{p8est_t}, Ptr{p4est_topidx_t}, Ptr{p8est_quadrant_t}))
2253
end
2254
2255
# Refine marked cells and rebalance forest.
2256
# Return a list of all cells that have been refined during refinement or rebalancing.
2257
function refine!(mesh::P4estMesh)
2258
# Copy original element IDs to quad user data storage
2259
original_n_cells = ncells(mesh)
2260
save_original_ids(mesh)
2261
2262
init_fn_c = cfunction(init_fn, Val(ndims(mesh)))
2263
refine_fn_c = cfunction(refine_fn, Val(ndims(mesh)))
2264
2265
# Refine marked cells
2266
@trixi_timeit timer() "refine" refine_p4est!(mesh.p4est, false, refine_fn_c,
2267
init_fn_c)
2268
2269
@trixi_timeit timer() "rebalance" balance!(mesh, init_fn_c)
2270
2271
return collect_changed_cells(mesh, original_n_cells)
2272
end
2273
2274
function coarsen_fn(p4est, which_tree, quadrants_ptr)
2275
quadrants = unsafe_wrap_quadrants(quadrants_ptr, p4est)
2276
2277
# Controller value has been copied to the quadrant's user data storage before.
2278
# Load controller value from quadrant's user data ([global quad ID, controller_value]).
2279
# Use `unsafe_load` here since `quadrant.p.user_data isa Ptr{Ptr{Nothing}}`
2280
# and we only need the first (only!) entry
2281
controller_value(i) = PointerWrapper(Int, unsafe_load(quadrants[i].p.user_data))[2]
2282
2283
# `p4est` calls this function for each 2^ndims quads that could be coarsened to a single one.
2284
# Only coarsen if all these 2^ndims quads have been marked for coarsening.
2285
if all(i -> controller_value(i) < 0, eachindex(quadrants))
2286
# return true (coarsen)
2287
return Cint(1)
2288
else
2289
# return false (don't coarsen)
2290
return Cint(0)
2291
end
2292
end
2293
2294
# 2D
2295
function unsafe_wrap_quadrants(quadrants_ptr, ::Ptr{p4est_t})
2296
unsafe_wrap(Array, quadrants_ptr, 4)
2297
end
2298
# 3D
2299
function unsafe_wrap_quadrants(quadrants_ptr, ::Ptr{p8est_t})
2300
unsafe_wrap(Array, quadrants_ptr, 8)
2301
end
2302
2303
# 2D
2304
function cfunction(::typeof(coarsen_fn), ::Val{2})
2305
@cfunction(coarsen_fn, Cint,
2306
(Ptr{p4est_t}, Ptr{p4est_topidx_t}, Ptr{Ptr{p4est_quadrant_t}}))
2307
end
2308
# 3D
2309
function cfunction(::typeof(coarsen_fn), ::Val{3})
2310
@cfunction(coarsen_fn, Cint,
2311
(Ptr{p8est_t}, Ptr{p4est_topidx_t}, Ptr{Ptr{p8est_quadrant_t}}))
2312
end
2313
2314
# Coarsen marked cells if the forest will stay balanced.
2315
# Return a list of all cells that have been coarsened.
2316
function coarsen!(mesh::P4estMesh)
2317
# Copy original element IDs to quad user data storage
2318
original_n_cells = ncells(mesh)
2319
save_original_ids(mesh)
2320
2321
# Coarsen marked cells
2322
coarsen_fn_c = cfunction(coarsen_fn, Val(ndims(mesh)))
2323
init_fn_c = cfunction(init_fn, Val(ndims(mesh)))
2324
2325
@trixi_timeit timer() "coarsen!" coarsen_p4est!(mesh.p4est, false, coarsen_fn_c,
2326
init_fn_c)
2327
2328
# IDs of newly created cells (one-based)
2329
new_cells = collect_new_cells(mesh)
2330
# Old IDs of cells that have been coarsened (one-based)
2331
coarsened_cells_vec = collect_changed_cells(mesh, original_n_cells)
2332
# 2^ndims changed cells should have been coarsened to one new cell.
2333
# This matrix will store the IDs of all cells that have been coarsened to cell new_cells[i]
2334
# in the i-th column.
2335
coarsened_cells = reshape(coarsened_cells_vec, 2^ndims(mesh), length(new_cells))
2336
2337
# Save new original IDs to find out what changed after balancing
2338
intermediate_n_cells = ncells(mesh)
2339
save_original_ids(mesh)
2340
2341
@trixi_timeit timer() "rebalance" balance!(mesh, init_fn_c)
2342
2343
refined_cells = collect_changed_cells(mesh, intermediate_n_cells)
2344
2345
# Some cells may have been coarsened even though they unbalanced the forest.
2346
# These cells have now been refined again by p4est_balance.
2347
# refined_cells contains the intermediate IDs (ID of coarse cell
2348
# between coarsening and balancing) of these cells.
2349
# Find original ID of each cell that has been coarsened and then refined again.
2350
for refined_cell in refined_cells
2351
# i-th cell of the ones that have been created by coarsening has been refined again
2352
i = findfirst(==(refined_cell), new_cells)
2353
2354
# Remove IDs of the 2^ndims cells that have been coarsened to this cell
2355
coarsened_cells[:, i] .= -1
2356
end
2357
2358
# Return all IDs of cells that have been coarsened but not refined again by balancing
2359
return coarsened_cells_vec[coarsened_cells_vec .>= 0]
2360
end
2361
2362
# Copy global quad ID to quad's user data storage, will be called below
2363
function save_original_id_iter_volume(info, user_data)
2364
info_pw = PointerWrapper(info)
2365
2366
# Load tree from global trees array, one-based indexing
2367
tree_pw = load_pointerwrapper_tree(info_pw.p4est, info_pw.treeid[] + 1)
2368
# Quadrant numbering offset of this quadrant
2369
offset = tree_pw.quadrants_offset[]
2370
# Global quad ID
2371
quad_id = offset + info_pw.quadid[]
2372
2373
# Unpack quadrant's user data ([global quad ID, controller_value])
2374
pw = PointerWrapper(Int, info_pw.quad.p.user_data[])
2375
# Save global quad ID
2376
pw[1] = quad_id
2377
return nothing
2378
end
2379
2380
# 2D
2381
function cfunction(::typeof(save_original_id_iter_volume), ::Val{2})
2382
@cfunction(save_original_id_iter_volume, Cvoid,
2383
(Ptr{p4est_iter_volume_info_t}, Ptr{Cvoid}))
2384
end
2385
# 3D
2386
function cfunction(::typeof(save_original_id_iter_volume), ::Val{3})
2387
@cfunction(save_original_id_iter_volume, Cvoid,
2388
(Ptr{p8est_iter_volume_info_t}, Ptr{Cvoid}))
2389
end
2390
2391
# Copy old element IDs to each quad's user data storage
2392
function save_original_ids(mesh::P4estMesh)
2393
iter_volume_c = cfunction(save_original_id_iter_volume, Val(ndims(mesh)))
2394
2395
iterate_p4est(mesh.p4est, C_NULL; iter_volume_c = iter_volume_c)
2396
end
2397
2398
# Extract information about which cells have been changed
2399
function collect_changed_iter_volume(info, user_data)
2400
info_pw = PointerWrapper(info)
2401
2402
# The original element ID has been saved to user_data before.
2403
# Load original quad ID from quad's user data ([global quad ID, controller_value]).
2404
quad_data_pw = PointerWrapper(Int, info_pw.quad.p.user_data[])
2405
original_id = quad_data_pw[1]
2406
2407
# original_id of cells that have been newly created is -1
2408
if original_id >= 0
2409
# Unpack user_data = original_cells
2410
user_data_pw = PointerWrapper(Int, user_data)
2411
2412
# If quad has an original_id, it existed before refinement/coarsening,
2413
# and therefore wasn't changed.
2414
# Mark original_id as "not changed during refinement/coarsening" in original_cells
2415
user_data_pw[original_id + 1] = 0
2416
end
2417
return nothing
2418
end
2419
2420
# 2D
2421
function cfunction(::typeof(collect_changed_iter_volume), ::Val{2})
2422
@cfunction(collect_changed_iter_volume, Cvoid,
2423
(Ptr{p4est_iter_volume_info_t}, Ptr{Cvoid}))
2424
end
2425
# 3D
2426
function cfunction(::typeof(collect_changed_iter_volume), ::Val{3})
2427
@cfunction(collect_changed_iter_volume, Cvoid,
2428
(Ptr{p8est_iter_volume_info_t}, Ptr{Cvoid}))
2429
end
2430
2431
function collect_changed_cells(mesh::P4estMesh, original_n_cells)
2432
original_cells = collect(1:original_n_cells)
2433
2434
# Iterate over all quads and set original cells that haven't been changed to zero
2435
iter_volume_c = cfunction(collect_changed_iter_volume, Val(ndims(mesh)))
2436
2437
iterate_p4est(mesh.p4est, original_cells; iter_volume_c = iter_volume_c)
2438
2439
# Changed cells are all that haven't been set to zero above
2440
changed_original_cells = original_cells[original_cells .> 0]
2441
2442
return changed_original_cells
2443
end
2444
2445
# Extract newly created cells
2446
function collect_new_iter_volume(info, user_data)
2447
info_pw = PointerWrapper(info)
2448
2449
# The original element ID has been saved to user_data before.
2450
# Unpack quadrant's user data ([global quad ID, controller_value]).
2451
original_id = PointerWrapper(Int, info_pw.quad.p.user_data[])[1]
2452
2453
# original_id of cells that have been newly created is -1
2454
if original_id < 0
2455
# Load tree from global trees array, one-based indexing
2456
tree_pw = load_pointerwrapper_tree(info_pw.p4est, info_pw.treeid[] + 1)
2457
# Quadrant numbering offset of this quadrant
2458
offset = tree_pw.quadrants_offset[]
2459
# Global quad ID
2460
quad_id = offset + info_pw.quadid[]
2461
2462
# Unpack user_data = original_cells
2463
user_data_pw = PointerWrapper(Int, user_data)
2464
2465
# Mark cell as "newly created during refinement/coarsening/balancing"
2466
user_data_pw[quad_id + 1] = 1
2467
end
2468
return nothing
2469
end
2470
2471
# 2D
2472
function cfunction(::typeof(collect_new_iter_volume), ::Val{2})
2473
@cfunction(collect_new_iter_volume, Cvoid,
2474
(Ptr{p4est_iter_volume_info_t}, Ptr{Cvoid}))
2475
end
2476
# 3D
2477
function cfunction(::typeof(collect_new_iter_volume), ::Val{3})
2478
@cfunction(collect_new_iter_volume, Cvoid,
2479
(Ptr{p8est_iter_volume_info_t}, Ptr{Cvoid}))
2480
end
2481
2482
function collect_new_cells(mesh::P4estMesh)
2483
cell_is_new = zeros(Int, ncells(mesh))
2484
2485
# Iterate over all quads and set original cells that have been changed to one
2486
iter_volume_c = cfunction(collect_new_iter_volume, Val(ndims(mesh)))
2487
2488
iterate_p4est(mesh.p4est, cell_is_new; iter_volume_c = iter_volume_c)
2489
2490
# Changed cells are all that haven't been set to zero above
2491
new_cells = findall(==(1), cell_is_new)
2492
2493
return new_cells
2494
end
2495
end # @muladd
2496
2497