Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
trixi-framework
GitHub Repository: trixi-framework/Trixi.jl
Path: blob/main/src/meshes/mesh_io.jl
5586 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
# Save current mesh with some context information as an HDF5 file.
9
function save_mesh_file(mesh::Union{TreeMesh, P4estMesh, P4estMeshView, T8codeMesh},
10
output_directory,
11
timestep = 0)
12
return save_mesh_file(mesh, output_directory, timestep, mpi_parallel(mesh))
13
end
14
15
function save_mesh_file(mesh::TreeMesh, output_directory, timestep,
16
mpi_parallel::False)
17
# Create output directory (if it does not exist)
18
mkpath(output_directory)
19
20
# Determine file name based on existence of meaningful time step
21
if timestep > 0
22
filename = joinpath(output_directory, @sprintf("mesh_%09d.h5", timestep))
23
else
24
filename = joinpath(output_directory, "mesh.h5")
25
end
26
27
# Open file (clobber existing content)
28
h5open(filename, "w") do file
29
# Add context information as attributes
30
n_cells = length(mesh.tree)
31
attributes(file)["mesh_type"] = get_name(mesh)
32
attributes(file)["ndims"] = ndims(mesh)
33
attributes(file)["n_cells"] = n_cells
34
attributes(file)["capacity"] = mesh.tree.capacity
35
attributes(file)["n_leaf_cells"] = count_leaf_cells(mesh.tree)
36
attributes(file)["minimum_level"] = minimum_level(mesh.tree)
37
attributes(file)["maximum_level"] = maximum_level(mesh.tree)
38
attributes(file)["center_level_0"] = mesh.tree.center_level_0
39
attributes(file)["length_level_0"] = mesh.tree.length_level_0
40
attributes(file)["periodicity"] = collect(mesh.tree.periodicity)
41
42
# Add tree data
43
file["parent_ids"] = @view mesh.tree.parent_ids[1:n_cells]
44
file["child_ids"] = @view mesh.tree.child_ids[:, 1:n_cells]
45
file["neighbor_ids"] = @view mesh.tree.neighbor_ids[:, 1:n_cells]
46
file["levels"] = @view mesh.tree.levels[1:n_cells]
47
file["coordinates"] = @view mesh.tree.coordinates[:, 1:n_cells]
48
return nothing
49
end
50
51
return filename
52
end
53
54
# Save current mesh with some context information as an HDF5 file.
55
function save_mesh_file(mesh::TreeMesh, output_directory, timestep,
56
mpi_parallel::True)
57
# Create output directory (if it does not exist)
58
mpi_isroot() && mkpath(output_directory)
59
60
# Determine file name based on existence of meaningful time step
61
if timestep >= 0
62
filename = joinpath(output_directory, @sprintf("mesh_%09d.h5", timestep))
63
else
64
filename = joinpath(output_directory, "mesh.h5")
65
end
66
67
# Since the mesh is replicated on all ranks, only save from MPI root
68
if !mpi_isroot()
69
return filename
70
end
71
72
# Open file (clobber existing content)
73
h5open(filename, "w") do file
74
# Add context information as attributes
75
n_cells = length(mesh.tree)
76
attributes(file)["mesh_type"] = get_name(mesh)
77
attributes(file)["ndims"] = ndims(mesh)
78
attributes(file)["n_cells"] = n_cells
79
attributes(file)["capacity"] = mesh.tree.capacity
80
attributes(file)["n_leaf_cells"] = count_leaf_cells(mesh.tree)
81
attributes(file)["minimum_level"] = minimum_level(mesh.tree)
82
attributes(file)["maximum_level"] = maximum_level(mesh.tree)
83
attributes(file)["center_level_0"] = mesh.tree.center_level_0
84
attributes(file)["length_level_0"] = mesh.tree.length_level_0
85
attributes(file)["periodicity"] = collect(mesh.tree.periodicity)
86
87
# Add tree data
88
file["parent_ids"] = @view mesh.tree.parent_ids[1:n_cells]
89
file["child_ids"] = @view mesh.tree.child_ids[:, 1:n_cells]
90
file["neighbor_ids"] = @view mesh.tree.neighbor_ids[:, 1:n_cells]
91
file["levels"] = @view mesh.tree.levels[1:n_cells]
92
file["coordinates"] = @view mesh.tree.coordinates[:, 1:n_cells]
93
return nothing
94
end
95
96
return filename
97
end
98
99
# Does not save the mesh itself to an HDF5 file. Instead saves important attributes
100
# of the mesh, like its size and the type of boundary mapping function.
101
# Then, within Trixi2Vtk, the StructuredMesh and its node coordinates are reconstructed from
102
# these attributes for plotting purposes
103
# Note: the `timestep` argument is needed for compatibility with the method for
104
# `StructuredMeshView`
105
function save_mesh_file(mesh::StructuredMesh, output_directory; system = "",
106
timestep = 0)
107
# Create output directory (if it does not exist)
108
mkpath(output_directory)
109
110
if isempty(system)
111
filename = joinpath(output_directory, "mesh.h5")
112
else
113
filename = joinpath(output_directory, @sprintf("mesh_%s.h5", system))
114
end
115
116
# Open file (clobber existing content)
117
h5open(filename, "w") do file
118
# Add context information as attributes
119
attributes(file)["mesh_type"] = get_name(mesh)
120
attributes(file)["ndims"] = ndims(mesh)
121
attributes(file)["size"] = collect(size(mesh))
122
attributes(file)["mapping"] = mesh.mapping_as_string
123
attributes(file)["periodicity"] = collect(mesh.periodicity)
124
return nothing
125
end
126
127
return filename
128
end
129
130
# Does not save the mesh itself to an HDF5 file. Instead saves important attributes
131
# of the mesh, like its size and the corresponding `.mesh` file used to construct the mesh.
132
# Then, within Trixi2Vtk, the UnstructuredMesh2D and its node coordinates are reconstructed
133
# from these attributes for plotting purposes
134
function save_mesh_file(mesh::UnstructuredMesh2D, output_directory)
135
# Create output directory (if it does not exist)
136
mkpath(output_directory)
137
138
filename = joinpath(output_directory, "mesh.h5")
139
140
# Open file (clobber existing content)
141
h5open(filename, "w") do file
142
# Add context information as attributes
143
attributes(file)["mesh_type"] = get_name(mesh)
144
attributes(file)["ndims"] = ndims(mesh)
145
attributes(file)["size"] = length(mesh)
146
attributes(file)["mesh_filename"] = mesh.filename
147
attributes(file)["periodicity"] = collect(mesh.periodicity)
148
return nothing
149
end
150
151
return filename
152
end
153
154
# Does not save the mesh itself to an HDF5 file. Instead saves important attributes
155
# of the mesh, like its size and the type of boundary mapping function.
156
# Then, within Trixi2Vtk, the P4estMesh and its node coordinates are reconstructed from
157
# these attributes for plotting purposes
158
function save_mesh_file(mesh::P4estMesh, output_directory, timestep,
159
mpi_parallel::False)
160
# Create output directory (if it does not exist)
161
mkpath(output_directory)
162
163
# Determine file name based on existence of meaningful time step
164
if timestep > 0
165
filename = joinpath(output_directory, @sprintf("mesh_%09d.h5", timestep))
166
p4est_filename = @sprintf("p4est_data_%09d", timestep)
167
else
168
filename = joinpath(output_directory, "mesh.h5")
169
p4est_filename = "p4est_data"
170
end
171
172
p4est_file = joinpath(output_directory, p4est_filename)
173
174
# Save the complete connectivity and `p4est` data to disk.
175
save_p4est!(p4est_file, mesh.p4est)
176
177
# Open file (clobber existing content)
178
h5open(filename, "w") do file
179
# Add context information as attributes
180
attributes(file)["mesh_type"] = get_name(mesh)
181
attributes(file)["ndims"] = ndims(mesh)
182
attributes(file)["p4est_file"] = p4est_filename
183
184
file["tree_node_coordinates"] = mesh.tree_node_coordinates
185
file["nodes"] = Vector(mesh.nodes) # the mesh uses `SVector`s for the nodes
186
# to increase the runtime performance
187
# but HDF5 can only handle plain arrays
188
file["boundary_names"] = mesh.boundary_names .|> String
189
return nothing
190
end
191
192
return filename
193
end
194
195
function save_mesh_file(mesh::P4estMesh, output_directory, timestep, mpi_parallel::True)
196
# Create output directory (if it does not exist)
197
mpi_isroot() && mkpath(output_directory)
198
199
# Determine file name based on existence of meaningful time step
200
if timestep > 0
201
filename = joinpath(output_directory, @sprintf("mesh_%09d.h5", timestep))
202
p4est_filename = @sprintf("p4est_data_%09d", timestep)
203
else
204
filename = joinpath(output_directory, "mesh.h5")
205
p4est_filename = "p4est_data"
206
end
207
208
p4est_file = joinpath(output_directory, p4est_filename)
209
210
# Save the complete connectivity/p4est data to disk.
211
save_p4est!(p4est_file, mesh.p4est)
212
213
# Since the mesh attributes are replicated on all ranks, only save from MPI root
214
if !mpi_isroot()
215
return filename
216
end
217
218
# Open file (clobber existing content)
219
h5open(filename, "w") do file
220
# Add context information as attributes
221
attributes(file)["mesh_type"] = get_name(mesh)
222
attributes(file)["ndims"] = ndims(mesh)
223
attributes(file)["p4est_file"] = p4est_filename
224
225
file["tree_node_coordinates"] = mesh.tree_node_coordinates
226
file["nodes"] = Vector(mesh.nodes) # the mesh uses `SVector`s for the nodes
227
# to increase the runtime performance
228
# but HDF5 can only handle plain arrays
229
file["boundary_names"] = mesh.boundary_names .|> String
230
return nothing
231
end
232
233
return filename
234
end
235
236
# This routine works for both, serial and MPI parallel mode. The forest
237
# information is collected on all ranks and then gathered by the root rank.
238
# Since only the `levels` array of UInt8 and the global number of elements per
239
# tree (Int32) is necessary to reconstruct the forest it is not worth the
240
# effort to have a collective write to the HDF5 file. Instead, `levels` and
241
# `num_elements_per_tree` gets gathered by the root rank and written to disk.
242
function save_mesh_file(mesh::T8codeMesh, output_directory, timestep,
243
mpi_parallel::Union{False, True})
244
245
# Create output directory (if it does not exist).
246
mpi_isroot() && mkpath(output_directory)
247
248
# Determine file name based on existence of meaningful time step.
249
if timestep > 0
250
filename = joinpath(output_directory, @sprintf("mesh_%09d.h5", timestep))
251
else
252
filename = joinpath(output_directory, "mesh.h5")
253
end
254
255
# Retrieve refinement levels of all elements.
256
local_levels = get_levels(mesh)
257
if mpi_isparallel()
258
count = [length(local_levels)]
259
counts = MPI.Gather(view(count, 1), mpi_root(), mpi_comm())
260
261
if mpi_isroot()
262
levels = similar(local_levels, ncellsglobal(mesh))
263
MPI.Gatherv!(local_levels, MPI.VBuffer(levels, counts),
264
mpi_root(), mpi_comm())
265
else
266
MPI.Gatherv!(local_levels, nothing, mpi_root(), mpi_comm())
267
end
268
else
269
levels = local_levels
270
end
271
272
# Retrieve the number of elements per tree. Since a tree can be distributed
273
# among multiple ranks a reduction operation sums them all up. The latter
274
# is done on the root rank only.
275
num_global_trees = t8_forest_get_num_global_trees(mesh.forest)
276
num_elements_per_tree = zeros(t8_gloidx_t, num_global_trees)
277
num_local_trees = t8_forest_get_num_local_trees(mesh.forest)
278
for local_tree_id in 0:(num_local_trees - 1)
279
num_local_elements_in_tree = t8_forest_get_tree_num_elements(mesh.forest,
280
local_tree_id)
281
global_tree_id = t8_forest_global_tree_id(mesh.forest, local_tree_id)
282
num_elements_per_tree[global_tree_id + 1] = num_local_elements_in_tree
283
end
284
285
if mpi_isparallel()
286
MPI.Reduce!(num_elements_per_tree, +, mpi_comm())
287
end
288
289
# Since the mesh attributes are replicated on all ranks, only save from MPI
290
# root.
291
if !mpi_isroot()
292
return filename
293
end
294
295
# Retrieve face connectivity info of the coarse mesh.
296
treeIDs, neighIDs, faces, duals, orientations = get_cmesh_info(mesh)
297
298
# Open file (clobber existing content).
299
h5open(filename, "w") do file
300
# Add context information as attributes.
301
attributes(file)["mesh_type"] = get_name(mesh)
302
attributes(file)["ndims"] = ndims(mesh)
303
attributes(file)["ntrees"] = ntrees(mesh)
304
attributes(file)["nelements"] = ncellsglobal(mesh)
305
306
file["tree_node_coordinates"] = mesh.tree_node_coordinates
307
file["nodes"] = Vector(mesh.nodes)
308
file["boundary_names"] = mesh.boundary_names .|> String
309
file["treeIDs"] = treeIDs
310
file["neighIDs"] = neighIDs
311
file["faces"] = faces
312
file["duals"] = duals
313
file["orientations"] = orientations
314
file["levels"] = levels
315
file["num_elements_per_tree"] = num_elements_per_tree
316
return nothing
317
end
318
319
return filename
320
end
321
322
@inline get_VXYZ(md::StartUpDG.MeshData) = get_VXYZ(md.mesh_type)
323
@inline get_VXYZ(mesh_type::StartUpDG.VertexMappedMesh) = mesh_type.VXYZ
324
@inline get_VXYZ(mesh_type::StartUpDG.CurvedMesh) = get_VXYZ(mesh_type.original_mesh_type)
325
@inline get_VXYZ(mesh_type::StartUpDG.HOHQMeshType) = mesh_type.hmd.VXYZ
326
327
@inline get_EToV(md::StartUpDG.MeshData) = get_EToV(md.mesh_type)
328
@inline get_EToV(mesh_type::StartUpDG.VertexMappedMesh) = mesh_type.EToV
329
@inline get_EToV(mesh_type::StartUpDG.CurvedMesh) = get_EToV(mesh_type.original_mesh_type)
330
@inline get_EToV(mesh_type::StartUpDG.HOHQMeshType) = mesh_type.hmd.EToV
331
332
# To save the data needed to reconstruct a DGMultiMesh object, we must include additional
333
# information contained within `dg.basis`. Currently, only the element shape and polynomial
334
# degree are stored, and it is assumed that the solution is stored at the default node
335
# positions for the `Polynomial` or `TensorProductWedge` approximation of that element
336
# shape and polynomial degree.
337
function save_mesh_file(mesh::DGMultiMesh, basis, output_directory, timestep = 0)
338
339
# Create output directory (if it does not exist).
340
mkpath(output_directory)
341
342
# Determine file name based on existence of meaningful time step.
343
if timestep > 0
344
filename = joinpath(output_directory, @sprintf("mesh_%09d.h5", timestep))
345
else
346
filename = joinpath(output_directory, "mesh.h5")
347
end
348
349
# Open file (clobber existing content)
350
h5open(filename, "w") do file
351
# Add context information as attributes
352
attributes(file)["mesh_type"] = get_name(mesh)
353
attributes(file)["ndims"] = ndims(mesh)
354
attributes(file)["nelements"] = ncells(mesh)
355
356
# For TensorProductWedge, the polynomial degree is a tuple
357
if basis.approximation_type isa TensorProductWedge
358
attributes(file)["polydeg_tri"] = basis.N[2]
359
attributes(file)["polydeg_line"] = basis.N[1]
360
else
361
attributes(file)["polydeg"] = basis.N
362
end
363
364
attributes(file)["element_type"] = basis.element_type |> typeof |> nameof |>
365
string
366
367
# Mesh-coordinates per element.
368
for idim in 1:ndims(mesh)
369
# ASCII: Char(120) => 'x'
370
file[119 + idim |> Char |> string] = mesh.md.xyz[idim]
371
end
372
373
# Transfer vectors of vectors to a matrix (2D array) and store into h5 file.
374
for (idim, vectors) in enumerate(get_VXYZ(mesh.md))
375
matrix = zeros(length(vectors[1]), length(vectors))
376
for ielem in eachindex(vectors)
377
@views matrix[:, ielem] .= vectors[ielem]
378
end
379
# ASCII: Char(58) => 'X'
380
# Vertex-coordinates per element.
381
file["V" * (87 + idim |> Char |> string)] = matrix
382
end
383
384
# Mapping element corners to vertices `VXYZ`.
385
file["EToV"] = get_EToV(mesh.md)
386
387
# TODO: Save boundaries.
388
# file["boundary_names"] = mesh.boundary_faces .|> String
389
return nothing
390
end
391
392
return filename
393
end
394
395
"""
396
load_mesh(restart_file::AbstractString; n_cells_max)
397
398
Load the mesh from the `restart_file`.
399
"""
400
function load_mesh(restart_file::AbstractString; n_cells_max = 0, RealT = Float64)
401
if mpi_isparallel()
402
mesh_file = get_restart_mesh_filename(restart_file, True())
403
return load_mesh_parallel(mesh_file; n_cells_max = n_cells_max, RealT = RealT)
404
else
405
mesh_file = get_restart_mesh_filename(restart_file, False())
406
load_mesh_serial(mesh_file; n_cells_max = n_cells_max, RealT = RealT)
407
end
408
end
409
410
function load_mesh_serial(mesh_file::AbstractString; n_cells_max, RealT)
411
ndims, mesh_type = h5open(mesh_file, "r") do file
412
return read(attributes(file)["ndims"]),
413
read(attributes(file)["mesh_type"])
414
end
415
416
if mesh_type == "TreeMesh"
417
capacity = h5open(mesh_file, "r") do file
418
return read(attributes(file)["capacity"])
419
end
420
mesh = TreeMesh(SerialTree{ndims, RealT}, max(n_cells_max, capacity),
421
RealT = RealT)
422
load_mesh!(mesh, mesh_file)
423
elseif mesh_type in ("StructuredMesh", "StructuredMeshView")
424
size_, periodicity, mapping_as_string = h5open(mesh_file, "r") do file
425
return read(attributes(file)["size"]),
426
read(attributes(file)["periodicity"]),
427
read(attributes(file)["mapping"])
428
end
429
430
size = Tuple(size_)
431
432
# TODO: `@eval` is evil
433
#
434
# This should be replaced with something more robust and secure,
435
# see https://github.com/trixi-framework/Trixi.jl/issues/541).
436
if ndims == 1
437
mapping = eval(Meta.parse("""function (xi)
438
$mapping_as_string
439
mapping(xi)
440
end
441
"""))
442
elseif ndims == 2
443
mapping = eval(Meta.parse("""function (xi, eta)
444
$mapping_as_string
445
mapping(xi, eta)
446
end
447
"""))
448
else # ndims == 3
449
mapping = eval(Meta.parse("""function (xi, eta, zeta)
450
$mapping_as_string
451
mapping(xi, eta, zeta)
452
end
453
"""))
454
end
455
456
mesh = StructuredMesh(size, mapping; RealT = RealT, unsaved_changes = false,
457
periodicity = periodicity,
458
mapping_as_string = mapping_as_string)
459
mesh.current_filename = mesh_file
460
elseif mesh_type == "UnstructuredMesh2D"
461
mesh_filename, periodicity_ = h5open(mesh_file, "r") do file
462
return read(attributes(file)["mesh_filename"]),
463
read(attributes(file)["periodicity"])
464
end
465
mesh = UnstructuredMesh2D(mesh_filename; RealT = RealT,
466
periodicity = periodicity_,
467
unsaved_changes = false)
468
mesh.current_filename = mesh_file
469
elseif mesh_type == "P4estMesh"
470
p4est_filename, tree_node_coordinates,
471
nodes, boundary_names_ = h5open(mesh_file, "r") do file
472
return read(attributes(file)["p4est_file"]),
473
read(file["tree_node_coordinates"]),
474
read(file["nodes"]),
475
read(file["boundary_names"])
476
end
477
478
boundary_names = boundary_names_ .|> Symbol
479
480
p4est_file = joinpath(dirname(mesh_file), p4est_filename)
481
# Prevent Julia crashes when `p4est` can't find the file
482
@assert isfile(p4est_file)
483
484
p4est = load_p4est(p4est_file, Val(ndims))
485
486
mesh = P4estMesh{ndims}(p4est, tree_node_coordinates,
487
nodes, boundary_names, mesh_file, false, true)
488
elseif mesh_type == "P4estMeshView"
489
p4est_filename, cell_ids, tree_node_coordinates,
490
nodes, boundary_names_ = h5open(mesh_file, "r") do file
491
return read(attributes(file)["p4est_file"]),
492
read(attributes(file)["cell_ids"]),
493
read(file["tree_node_coordinates"]),
494
read(file["nodes"]),
495
read(file["boundary_names"])
496
end
497
498
boundary_names = boundary_names_ .|> Symbol
499
500
p4est_file = joinpath(dirname(mesh_file), p4est_filename)
501
# Prevent Julia crashes when `p4est` can't find the file
502
@assert isfile(p4est_file)
503
504
p4est = load_p4est(p4est_file, Val(ndims))
505
506
unsaved_changes = false
507
p4est_partition_allow_for_coarsening = true
508
parent_mesh = P4estMesh{ndims}(p4est, tree_node_coordinates,
509
nodes, boundary_names, mesh_file,
510
unsaved_changes,
511
p4est_partition_allow_for_coarsening)
512
513
mesh = P4estMeshView(parent_mesh, cell_ids)
514
515
elseif mesh_type == "T8codeMesh"
516
ndims, ntrees, nelements, tree_node_coordinates,
517
nodes, boundary_names_, treeIDs, neighIDs, faces, duals, orientations,
518
levels, num_elements_per_tree = h5open(mesh_file, "r") do file
519
return read(attributes(file)["ndims"]),
520
read(attributes(file)["ntrees"]),
521
read(attributes(file)["nelements"]),
522
read(file["tree_node_coordinates"]),
523
read(file["nodes"]),
524
read(file["boundary_names"]),
525
read(file["treeIDs"]),
526
read(file["neighIDs"]),
527
read(file["faces"]),
528
read(file["duals"]),
529
read(file["orientations"]),
530
read(file["levels"]),
531
read(file["num_elements_per_tree"])
532
end
533
534
boundary_names = boundary_names_ .|> Symbol
535
536
mesh = T8codeMesh(ndims, ntrees, nelements, tree_node_coordinates,
537
nodes, boundary_names, treeIDs, neighIDs, faces,
538
duals, orientations, levels, num_elements_per_tree)
539
540
elseif mesh_type == "DGMultiMesh"
541
ndims, nelements, etype_str, EToV = h5open(mesh_file, "r") do file
542
return read(attributes(file)["ndims"]),
543
read(attributes(file)["nelements"]),
544
read(attributes(file)["element_type"]),
545
read(file["EToV"])
546
end
547
548
# Load RefElemData.
549
etype = get_element_type_from_string(etype_str)()
550
551
polydeg = h5open(mesh_file, "r") do file
552
if etype isa Trixi.Wedge && haskey(attributes(file), "polydeg_tri")
553
return tuple(read(attributes(file)["polydeg_tri"]),
554
read(attributes(file)["polydeg_line"]))
555
else
556
return read(attributes(file)["polydeg"])
557
end
558
end
559
560
# Currently, we assume that `basis.approximation_type` is a `TensorProductWedge`
561
# with 2-tuple polynomial degree or a `Polynomial` with integer polynomial degree.
562
# TODO: Add support for other approximation types. This would requires further
563
# information to be saved to the HDF5 file.
564
if etype isa StartUpDG.Wedge && polydeg isa NTuple{2}
565
factor_a = RefElemData(StartUpDG.Tri(), Polynomial(), polydeg[1])
566
factor_b = RefElemData(StartUpDG.Line(), Polynomial(), polydeg[2])
567
568
tensor = StartUpDG.TensorProductWedge(factor_a, factor_b)
569
rd = RefElemData(etype, tensor)
570
else
571
rd = RefElemData(etype, Polynomial(), polydeg)
572
end
573
574
# Load physical nodes.
575
xyz = h5open(mesh_file, "r") do file
576
# ASCII: Char(120) => 'x'
577
return tuple([read(file[119 + i |> Char |> string]) for i in 1:ndims]...)
578
end
579
580
# Load element vertices.
581
vxyz = h5open(mesh_file, "r") do file
582
# ASCII: Char(58) => 'X'
583
return tuple([read(file["V" * (87 + i |> Char |> string)]) for i in 1:ndims]...)
584
end
585
586
if ndims == 1
587
md = MeshData(vxyz[1][1, :], EToV, rd)
588
else
589
# Load MeshData and restore original physical nodes.
590
md = MeshData(rd, MeshData(vxyz, EToV, rd), xyz...)
591
end
592
593
mesh = DGMultiMesh{}(md, [])
594
else
595
error("Unknown mesh type!")
596
end
597
598
return mesh
599
end
600
601
function load_mesh!(mesh::TreeMeshSerial, mesh_file::AbstractString)
602
mesh.current_filename = mesh_file
603
mesh.unsaved_changes = false
604
605
# Read mesh file
606
h5open(mesh_file, "r") do file
607
# Set domain information
608
mesh.tree.center_level_0 = read(attributes(file)["center_level_0"])
609
mesh.tree.length_level_0 = read(attributes(file)["length_level_0"])
610
mesh.tree.periodicity = Tuple(read(attributes(file)["periodicity"]))
611
612
# Set length
613
n_cells = read(attributes(file)["n_cells"])
614
resize!(mesh.tree, n_cells)
615
616
# Read in data
617
mesh.tree.parent_ids[1:n_cells] = read(file["parent_ids"])
618
mesh.tree.child_ids[:, 1:n_cells] = read(file["child_ids"])
619
mesh.tree.neighbor_ids[:, 1:n_cells] = read(file["neighbor_ids"])
620
mesh.tree.levels[1:n_cells] = read(file["levels"])
621
mesh.tree.coordinates[:, 1:n_cells] = read(file["coordinates"])
622
return nothing
623
end
624
625
return mesh
626
end
627
628
function load_mesh_parallel(mesh_file::AbstractString; n_cells_max, RealT)
629
if mpi_isroot()
630
ndims_, mesh_type = h5open(mesh_file, "r") do file
631
return read(attributes(file)["ndims"]),
632
read(attributes(file)["mesh_type"])
633
end
634
MPI.Bcast!(Ref(ndims_), mpi_root(), mpi_comm())
635
MPI.bcast(mesh_type, mpi_root(), mpi_comm())
636
else
637
ndims_ = MPI.Bcast!(Ref(0), mpi_root(), mpi_comm())[]
638
mesh_type = MPI.bcast(nothing, mpi_root(), mpi_comm())
639
end
640
641
if mesh_type == "TreeMesh"
642
if mpi_isroot()
643
n_cells, capacity = h5open(mesh_file, "r") do file
644
return read(attributes(file)["n_cells"]),
645
read(attributes(file)["capacity"])
646
end
647
MPI.Bcast!(Ref(n_cells), mpi_root(), mpi_comm())
648
MPI.Bcast!(Ref(capacity), mpi_root(), mpi_comm())
649
else
650
n_cells = MPI.Bcast!(Ref(0), mpi_root(), mpi_comm())[]
651
capacity = MPI.Bcast!(Ref(0), mpi_root(), mpi_comm())[]
652
end
653
654
mesh = TreeMesh(ParallelTree{ndims_, RealT},
655
max(n_cells, n_cells_max, capacity),
656
RealT = RealT)
657
load_mesh!(mesh, mesh_file)
658
elseif mesh_type == "P4estMesh"
659
if mpi_isroot()
660
p4est_filename, tree_node_coordinates,
661
nodes, boundary_names_ = h5open(mesh_file, "r") do file
662
return read(attributes(file)["p4est_file"]),
663
read(file["tree_node_coordinates"]),
664
read(file["nodes"]),
665
read(file["boundary_names"])
666
end
667
668
boundary_names = boundary_names_ .|> Symbol
669
670
p4est_file = joinpath(dirname(mesh_file), p4est_filename)
671
672
data = (p4est_file, tree_node_coordinates, nodes, boundary_names)
673
MPI.bcast(data, mpi_root(), mpi_comm())
674
else
675
data = MPI.bcast(nothing, mpi_root(), mpi_comm())
676
p4est_file, tree_node_coordinates, nodes, boundary_names = data
677
end
678
679
# Prevent Julia crashes when `p4est` can't find the file
680
@assert isfile(p4est_file)
681
682
p4est = load_p4est(p4est_file, Val(ndims_))
683
684
mesh = P4estMesh{ndims_}(p4est, tree_node_coordinates,
685
nodes, boundary_names, mesh_file, false, true)
686
687
elseif mesh_type == "T8codeMesh"
688
if mpi_isroot()
689
ndims, ntrees, nelements, tree_node_coordinates, nodes,
690
boundary_names_, treeIDs, neighIDs, faces, duals, orientations, levels,
691
num_elements_per_tree = h5open(mesh_file, "r") do file
692
return read(attributes(file)["ndims"]),
693
read(attributes(file)["ntrees"]),
694
read(attributes(file)["nelements"]),
695
read(file["tree_node_coordinates"]),
696
read(file["nodes"]),
697
read(file["boundary_names"]),
698
read(file["treeIDs"]),
699
read(file["neighIDs"]),
700
read(file["faces"]),
701
read(file["duals"]),
702
read(file["orientations"]),
703
read(file["levels"]),
704
read(file["num_elements_per_tree"])
705
end
706
707
boundary_names = boundary_names_ .|> Symbol
708
709
data = (ndims, ntrees, nelements, tree_node_coordinates, nodes,
710
boundary_names, treeIDs, neighIDs, faces, duals,
711
orientations, levels, num_elements_per_tree)
712
MPI.bcast(data, mpi_root(), mpi_comm())
713
else
714
data = MPI.bcast(nothing, mpi_root(), mpi_comm())
715
ndims, ntrees, nelements, tree_node_coordinates, nodes,
716
boundary_names, treeIDs, neighIDs, faces, duals, orientations, levels,
717
num_elements_per_tree = data
718
end
719
720
mesh = T8codeMesh(ndims, ntrees, nelements, tree_node_coordinates,
721
nodes, boundary_names, treeIDs, neighIDs, faces,
722
duals, orientations, levels, num_elements_per_tree)
723
else
724
error("Unknown mesh type!")
725
end
726
727
return mesh
728
end
729
730
function load_mesh!(mesh::TreeMeshParallel, mesh_file::AbstractString)
731
mesh.current_filename = mesh_file
732
mesh.unsaved_changes = false
733
734
if mpi_isroot()
735
h5open(mesh_file, "r") do file
736
# Set domain information
737
mesh.tree.center_level_0 = read(attributes(file)["center_level_0"])
738
mesh.tree.length_level_0 = read(attributes(file)["length_level_0"])
739
mesh.tree.periodicity = Tuple(read(attributes(file)["periodicity"]))
740
MPI.Bcast!(collect(mesh.tree.center_level_0), mpi_root(), mpi_comm())
741
MPI.Bcast!(collect(mesh.tree.length_level_0), mpi_root(), mpi_comm())
742
MPI.Bcast!(collect(mesh.tree.periodicity), mpi_root(), mpi_comm())
743
744
# Set length
745
n_cells = read(attributes(file)["n_cells"])
746
MPI.Bcast!(Ref(n_cells), mpi_root(), mpi_comm())
747
resize!(mesh.tree, n_cells)
748
749
# Read in data
750
mesh.tree.parent_ids[1:n_cells] = read(file["parent_ids"])
751
mesh.tree.child_ids[:, 1:n_cells] = read(file["child_ids"])
752
mesh.tree.neighbor_ids[:, 1:n_cells] = read(file["neighbor_ids"])
753
mesh.tree.levels[1:n_cells] = read(file["levels"])
754
mesh.tree.coordinates[:, 1:n_cells] = read(file["coordinates"])
755
@views MPI.Bcast!(mesh.tree.parent_ids[1:n_cells], mpi_root(), mpi_comm())
756
@views MPI.Bcast!(mesh.tree.child_ids[:, 1:n_cells], mpi_root(), mpi_comm())
757
@views MPI.Bcast!(mesh.tree.neighbor_ids[:, 1:n_cells], mpi_root(),
758
mpi_comm())
759
@views MPI.Bcast!(mesh.tree.levels[1:n_cells], mpi_root(), mpi_comm())
760
@views MPI.Bcast!(mesh.tree.coordinates[:, 1:n_cells], mpi_root(),
761
mpi_comm())
762
end
763
else # non-root ranks
764
# Set domain information
765
mesh.tree.center_level_0 = MPI.Bcast!(collect(mesh.tree.center_level_0),
766
mpi_root(), mpi_comm())
767
mesh.tree.length_level_0 = MPI.Bcast!(collect(mesh.tree.length_level_0),
768
mpi_root(), mpi_comm())[1]
769
mesh.tree.periodicity = Tuple(MPI.Bcast!(collect(mesh.tree.periodicity),
770
mpi_root(), mpi_comm()))
771
772
# Set length
773
n_cells = MPI.Bcast!(Ref(0), mpi_root(), mpi_comm())[]
774
resize!(mesh.tree, n_cells)
775
776
# Read in data
777
@views MPI.Bcast!(mesh.tree.parent_ids[1:n_cells], mpi_root(), mpi_comm())
778
@views MPI.Bcast!(mesh.tree.child_ids[:, 1:n_cells], mpi_root(), mpi_comm())
779
@views MPI.Bcast!(mesh.tree.neighbor_ids[:, 1:n_cells], mpi_root(), mpi_comm())
780
@views MPI.Bcast!(mesh.tree.levels[1:n_cells], mpi_root(), mpi_comm())
781
@views MPI.Bcast!(mesh.tree.coordinates[:, 1:n_cells], mpi_root(), mpi_comm())
782
end
783
784
# Partition mesh
785
partition!(mesh)
786
787
return mesh
788
end
789
end # @muladd
790
791