@muladd begin
"""
P4estMesh{NDIMS, NDIMS_AMBIENT} <: AbstractMesh{NDIMS}
An unstructured curved mesh based on trees that uses the C library `p4est`
to manage trees and mesh refinement.
The parameter `NDIMS` denotes the dimension of the spatial domain or manifold represented
by the mesh itself, while `NDIMS_AMBIENT` denotes the dimension of the ambient space in
which the mesh is embedded. For example, the type `P4estMesh{3, 3}` corresponds to a
standard mesh for a three-dimensional volume, whereas `P4estMesh{2, 3}` corresponds to a
mesh for a two-dimensional surface or shell in three-dimensional space.
!!! warning "Experimental implementation"
The use of `NDIMS != NDIMS_AMBIENT` is an experimental feature and may change in future
releases.
"""
mutable struct P4estMesh{NDIMS, NDIMS_AMBIENT, RealT <: Real, IsParallel, P, Ghost,
NDIMSP2, NNODES} <:
AbstractMesh{NDIMS}
p4est :: P
is_parallel :: IsParallel
ghost :: Ghost
tree_node_coordinates::Array{RealT, NDIMSP2}
nodes::SVector{NNODES, RealT}
boundary_names::Array{Symbol, 2}
current_filename::String
unsaved_changes::Bool
p4est_partition_allow_for_coarsening::Bool
function P4estMesh{NDIMS}(p4est, tree_node_coordinates, nodes, boundary_names,
current_filename, unsaved_changes,
p4est_partition_allow_for_coarsening) where {NDIMS}
if NDIMS == 2
@assert p4est isa Ptr{p4est_t}
elseif NDIMS == 3
@assert p4est isa Ptr{p8est_t}
end
if mpi_isparallel()
if !P4est.uses_mpi()
error("p4est library does not support MPI")
end
is_parallel = True()
else
is_parallel = False()
end
p4est_pw = PointerWrapper(p4est)
ghost = ghost_new_p4est(p4est)
ghost_pw = PointerWrapper(ghost)
mesh = new{NDIMS, size(tree_node_coordinates, 1),
eltype(tree_node_coordinates), typeof(is_parallel),
typeof(p4est_pw), typeof(ghost_pw), NDIMS + 2, length(nodes)}(p4est_pw,
is_parallel,
ghost_pw,
tree_node_coordinates,
nodes,
boundary_names,
current_filename,
unsaved_changes,
p4est_partition_allow_for_coarsening)
finalizer(destroy_mesh, mesh)
return mesh
end
end
const SerialP4estMesh{NDIMS} = P4estMesh{NDIMS, <:Any, <:Real, <:False}
const ParallelP4estMesh{NDIMS} = P4estMesh{NDIMS, <:Any, <:Real, <:True}
@inline mpi_parallel(mesh::SerialP4estMesh) = False()
@inline mpi_parallel(mesh::ParallelP4estMesh) = True()
function destroy_mesh(mesh::P4estMesh{2})
connectivity = mesh.p4est.connectivity
p4est_ghost_destroy(mesh.ghost)
p4est_destroy(mesh.p4est)
p4est_connectivity_destroy(connectivity)
end
function destroy_mesh(mesh::P4estMesh{3})
connectivity = mesh.p4est.connectivity
p8est_ghost_destroy(mesh.ghost)
p8est_destroy(mesh.p4est)
p8est_connectivity_destroy(connectivity)
end
@inline Base.ndims(::P4estMesh{NDIMS}) where {NDIMS} = NDIMS
@inline Base.real(::P4estMesh{NDIMS, NDIMS_AMBIENT, RealT}) where {NDIMS, NDIMS_AMBIENT, RealT} = RealT
@inline ndims_ambient(::P4estMesh{NDIMS, NDIMS_AMBIENT}) where {NDIMS, NDIMS_AMBIENT} = NDIMS_AMBIENT
@inline function ntrees(mesh::P4estMesh)
return mesh.p4est.trees.elem_count[]
end
@inline ncells(mesh::P4estMesh) = Int(mesh.p4est.local_num_quadrants[])
@inline ncellsglobal(mesh::P4estMesh) = Int(mesh.p4est.global_num_quadrants[])
function Base.show(io::IO, mesh::P4estMesh)
print(io, "P4estMesh{", ndims(mesh), ", ", ndims_ambient(mesh), ", ", real(mesh),
"}")
end
function Base.show(io::IO, ::MIME"text/plain", mesh::P4estMesh)
if get(io, :compact, false)
show(io, mesh)
else
setup = [
"#trees" => ntrees(mesh),
"current #cells" => ncellsglobal(mesh),
"polydeg" => length(mesh.nodes) - 1
]
summary_box(io,
"P4estMesh{" * string(ndims(mesh)) * ", " *
string(ndims_ambient(mesh)) *
", " * string(real(mesh)) * "}", setup)
end
end
"""
P4estMesh(trees_per_dimension; polydeg,
mapping=nothing, faces=nothing, coordinates_min=nothing, coordinates_max=nothing,
RealT=Float64, initial_refinement_level=0, periodicity=true, unsaved_changes=true,
p4est_partition_allow_for_coarsening=true)
Create a structured curved/higher-order `P4estMesh` of the specified size.
There are three ways to map the mesh to the physical domain.
1. Define a `mapping` that maps the hypercube `[-1, 1]^n`.
2. Specify a `Tuple` `faces` of functions that parametrize each face.
3. Create a rectangular mesh by specifying `coordinates_min` and `coordinates_max`.
Non-periodic boundaries will be called `:x_neg`, `:x_pos`, `:y_neg`, `:y_pos`, `:z_neg`, `:z_pos`.
# Arguments
- `trees_per_dimension::NTupleE{NDIMS, Int}`: the number of trees in each dimension.
- `polydeg::Integer`: polynomial degree used to store the geometry of the mesh.
The mapping will be approximated by an interpolation polynomial
of the specified degree for each tree.
- `mapping`: a function of `NDIMS` variables to describe the mapping that transforms
the reference mesh (`[-1, 1]^n`) to the physical domain.
Use only one of `mapping`, `faces` and `coordinates_min`/`coordinates_max`.
- `faces::NTuple{2*NDIMS}`: a tuple of `2 * NDIMS` functions that describe the faces of the domain.
Each function must take `NDIMS-1` arguments.
`faces[1]` describes the face onto which the face in negative x-direction
of the unit hypercube is mapped. The face in positive x-direction of
the unit hypercube will be mapped onto the face described by `faces[2]`.
`faces[3:4]` describe the faces in positive and negative y-direction respectively
(in 2D and 3D).
`faces[5:6]` describe the faces in positive and negative z-direction respectively (in 3D).
Use only one of `mapping`, `faces` and `coordinates_min`/`coordinates_max`.
- `coordinates_min`: vector or tuple of the coordinates of the corner in the negative direction of each dimension
to create a rectangular mesh.
Use only one of `mapping`, `faces` and `coordinates_min`/`coordinates_max`.
- `coordinates_max`: vector or tuple of the coordinates of the corner in the positive direction of each dimension
to create a rectangular mesh.
Use only one of `mapping`, `faces` and `coordinates_min`/`coordinates_max`.
- `RealT::Type`: the type that should be used for coordinates.
- `initial_refinement_level::Integer`: refine the mesh uniformly to this level before the simulation starts.
- `periodicity`: either a `Bool` deciding if all of the boundaries are periodic or an `NTuple{NDIMS, Bool}`
deciding for each dimension if the boundaries in this dimension are periodic.
- `unsaved_changes::Bool`: if set to `true`, the mesh will be saved to a mesh file.
- `p4est_partition_allow_for_coarsening::Bool`: Must be `true` when using AMR to make mesh adaptivity
independent of domain partitioning. Should be `false` for static meshes
to permit more fine-grained partitioning.
"""
function P4estMesh(trees_per_dimension; polydeg,
mapping = nothing, faces = nothing, coordinates_min = nothing,
coordinates_max = nothing,
RealT = Float64, initial_refinement_level = 0, periodicity = true,
unsaved_changes = true,
p4est_partition_allow_for_coarsening = true)
@assert ((coordinates_min === nothing)===(coordinates_max === nothing)) "Either both or none of coordinates_min and coordinates_max must be specified"
coordinates_min_max_check(coordinates_min, coordinates_max)
@assert count(i -> i !== nothing,
(mapping, faces, coordinates_min))==1 "Exactly one of mapping, faces and coordinates_min/max must be specified"
if faces !== nothing
validate_faces(faces)
mapping = transfinite_mapping(faces)
elseif coordinates_min !== nothing
mapping = coordinates2mapping(coordinates_min, coordinates_max)
end
NDIMS = length(trees_per_dimension)
if all(periodicity)
periodicity = ntuple(_ -> true, NDIMS)
elseif !any(periodicity)
periodicity = ntuple(_ -> false, NDIMS)
else
periodicity = Tuple(periodicity)
end
basis = LobattoLegendreBasis(RealT, polydeg)
nodes = basis.nodes
tree_node_coordinates = Array{RealT, NDIMS + 2}(undef, NDIMS,
ntuple(_ -> length(nodes),
NDIMS)...,
prod(trees_per_dimension))
calc_tree_node_coordinates!(tree_node_coordinates, nodes, mapping,
trees_per_dimension)
connectivity = connectivity_structured(trees_per_dimension..., periodicity)
p4est = new_p4est(connectivity, initial_refinement_level)
boundary_names = fill(Symbol("---"), 2 * NDIMS, prod(trees_per_dimension))
structured_boundary_names!(boundary_names, trees_per_dimension, periodicity)
return P4estMesh{NDIMS}(p4est, tree_node_coordinates, nodes,
boundary_names, "", unsaved_changes,
p4est_partition_allow_for_coarsening)
end
function structured_boundary_names!(boundary_names, trees_per_dimension::NTuple{2},
periodicity)
linear_indices = LinearIndices(trees_per_dimension)
if !periodicity[1]
for cell_y in 1:trees_per_dimension[2]
tree = linear_indices[1, cell_y]
boundary_names[1, tree] = :x_neg
tree = linear_indices[end, cell_y]
boundary_names[2, tree] = :x_pos
end
end
if !periodicity[2]
for cell_x in 1:trees_per_dimension[1]
tree = linear_indices[cell_x, 1]
boundary_names[3, tree] = :y_neg
tree = linear_indices[cell_x, end]
boundary_names[4, tree] = :y_pos
end
end
end
function structured_boundary_names!(boundary_names, trees_per_dimension::NTuple{3},
periodicity)
linear_indices = LinearIndices(trees_per_dimension)
if !periodicity[1]
for cell_z in 1:trees_per_dimension[3], cell_y in 1:trees_per_dimension[2]
tree = linear_indices[1, cell_y, cell_z]
boundary_names[1, tree] = :x_neg
tree = linear_indices[end, cell_y, cell_z]
boundary_names[2, tree] = :x_pos
end
end
if !periodicity[2]
for cell_z in 1:trees_per_dimension[3], cell_x in 1:trees_per_dimension[1]
tree = linear_indices[cell_x, 1, cell_z]
boundary_names[3, tree] = :y_neg
tree = linear_indices[cell_x, end, cell_z]
boundary_names[4, tree] = :y_pos
end
end
if !periodicity[3]
for cell_y in 1:trees_per_dimension[2], cell_x in 1:trees_per_dimension[1]
tree = linear_indices[cell_x, cell_y, 1]
boundary_names[5, tree] = :z_neg
tree = linear_indices[cell_x, cell_y, end]
boundary_names[6, tree] = :z_pos
end
end
end
"""
P4estMesh{NDIMS}(meshfile::String;
mapping=nothing, polydeg=1, RealT=Float64,
initial_refinement_level=0, unsaved_changes=true,
p4est_partition_allow_for_coarsening=true,
boundary_symbols = nothing)
Main mesh constructor for the `P4estMesh` that imports an unstructured, conforming
mesh from an Abaqus mesh file (`.inp`). Each element of the conforming mesh parsed
from the `meshfile` is created as a [`p4est`](https://github.com/cburstedde/p4est)
tree datatype.
To create a curved/higher-order unstructured mesh `P4estMesh` two strategies are available:
- `p4est_mesh_from_hohqmesh_abaqus`: High-order (curved) boundary information created by
[`HOHQMesh.jl`](https://github.com/trixi-framework/HOHQMesh.jl) is
available in the `meshfile`. The mesh polynomial degree `polydeg`
of the boundaries is provided from the `meshfile`. The computation of
the mapped tree coordinates is done with transfinite interpolation
with linear blending similar to [`UnstructuredMesh2D`](@ref). Boundary name
information is also parsed from the `meshfile` such that different boundary
conditions can be set at each named boundary on a given tree.
- `p4est_mesh_from_standard_abaqus`: By default, with `mapping=nothing` and `polydeg=1`, this creates a
straight-sided from the information parsed from the `meshfile`. If a mapping
function is specified then it computes the mapped tree coordinates via polynomial
interpolants with degree `polydeg`. The mesh created by this function will only
have one boundary `:all` if `boundary_symbols` is not specified.
If `boundary_symbols` is specified the `meshfile` will be parsed for nodesets defining
the boundary nodes from which boundary edges (2D) and faces (3D) will be assigned.
Note that the `mapping` and `polydeg` keyword arguments are only used by the `p4est_mesh_from_standard_abaqus`
function. The `p4est_mesh_from_hohqmesh_abaqus` function obtains the mesh `polydeg` directly from the `meshfile`
and constructs the transfinite mapping internally.
The particular strategy is selected according to the header present in the `meshfile` where
the constructor checks whether or not the `meshfile` was created with
[HOHQMesh.jl](https://github.com/trixi-framework/HOHQMesh.jl).
If the Abaqus file header is not present in the `meshfile` then the `P4estMesh` is created
with the function `p4est_mesh_from_standard_abaqus`.
The default keyword argument `initial_refinement_level=0` corresponds to a forest
where the number of trees is the same as the number of elements in the original `meshfile`.
Increasing the `initial_refinement_level` allows one to uniformly refine the base mesh given
in the `meshfile` to create a forest with more trees before the simulation begins.
For example, if a two-dimensional base mesh contains 25 elements then setting
`initial_refinement_level=1` creates an initial forest of `2^2 * 25 = 100` trees.
# Arguments
- `meshfile::String`: an uncurved Abaqus mesh file that can be imported by `p4est`.
- `mapping`: a function of `NDIMS` variables to describe the mapping that transforms
the imported mesh to the physical domain. Use `nothing` for the identity map.
- `polydeg::Integer`: polynomial degree used to store the geometry of the mesh.
The mapping will be approximated by an interpolation polynomial
of the specified degree for each tree.
The default of `1` creates an uncurved geometry. Use a higher value if the mapping
will curve the imported uncurved mesh.
- `RealT::Type`: the type that should be used for coordinates.
- `initial_refinement_level::Integer`: refine the mesh uniformly to this level before the simulation starts.
- `unsaved_changes::Bool`: if set to `true`, the mesh will be saved to a mesh file.
- `p4est_partition_allow_for_coarsening::Bool`: Must be `true` when using AMR to make mesh adaptivity
independent of domain partitioning. Should be `false` for static meshes
to permit more fine-grained partitioning.
- `boundary_symbols::Vector{Symbol}`: A vector of symbols that correspond to the boundary names in the `meshfile`.
If `nothing` is passed then all boundaries are named `:all`.
"""
function P4estMesh{NDIMS}(meshfile::String;
mapping = nothing, polydeg = 1, RealT = Float64,
initial_refinement_level = 0, unsaved_changes = true,
p4est_partition_allow_for_coarsening = true,
boundary_symbols = nothing) where {NDIMS}
@assert isfile(meshfile)
header = open(meshfile, "r") do io
readline(io)
readline(io)
end
if header == " File created by HOHQMesh"
p4est, tree_node_coordinates, nodes, boundary_names = p4est_mesh_from_hohqmesh_abaqus(meshfile,
initial_refinement_level,
NDIMS,
RealT)
else
p4est, tree_node_coordinates, nodes, boundary_names = p4est_mesh_from_standard_abaqus(meshfile,
mapping,
polydeg,
initial_refinement_level,
NDIMS,
RealT,
boundary_symbols)
end
return P4estMesh{NDIMS}(p4est, tree_node_coordinates, nodes,
boundary_names, "", unsaved_changes,
p4est_partition_allow_for_coarsening)
end
function p4est_mesh_from_hohqmesh_abaqus(meshfile, initial_refinement_level,
n_dimensions, RealT)
connectivity, tree_node_coordinates, nodes, boundary_names = p4est_connectivity_from_hohqmesh_abaqus(meshfile,
n_dimensions,
RealT)
p4est = new_p4est(connectivity, initial_refinement_level)
return p4est, tree_node_coordinates, nodes, boundary_names
end
function p4est_mesh_from_standard_abaqus(meshfile, mapping, polydeg,
initial_refinement_level, n_dimensions, RealT,
boundary_symbols)
connectivity, tree_node_coordinates, nodes, boundary_names = p4est_connectivity_from_standard_abaqus(meshfile,
mapping,
polydeg,
n_dimensions,
RealT,
boundary_symbols)
p4est = new_p4est(connectivity, initial_refinement_level)
return p4est, tree_node_coordinates, nodes, boundary_names
end
function p4est_connectivity_from_hohqmesh_abaqus(meshfile,
n_dimensions, RealT)
connectivity = read_inp_p4est(meshfile, Val(n_dimensions))
connectivity_pw = PointerWrapper(connectivity)
n_trees::Int = connectivity_pw.num_trees[]
n_vertices::Int = connectivity_pw.num_vertices[]
vertices = unsafe_wrap(Array, connectivity_pw.vertices, (3, n_vertices))
file_lines = readlines(open(meshfile))
file_idx = findfirst(contains("** mesh polynomial degree"), file_lines)
current_line = split(file_lines[file_idx])
mesh_polydeg = parse(Int, current_line[6])
mesh_nnodes = mesh_polydeg + 1
cheby_nodes, _ = chebyshev_gauss_lobatto_nodes_weights(mesh_nnodes)
nodes = SVector{mesh_nnodes}(cheby_nodes)
tree_node_coordinates = Array{RealT, n_dimensions + 2}(undef, n_dimensions,
ntuple(_ -> length(nodes),
n_dimensions)...,
n_trees)
file_idx = calc_tree_node_coordinates!(tree_node_coordinates, file_lines, nodes,
vertices, RealT)
boundary_names = Array{Symbol}(undef, (2 * n_dimensions, n_trees))
for tree in 1:n_trees
current_line = split(file_lines[file_idx])
boundary_names[:, tree] = map(Symbol, current_line[2:end])
file_idx += 1
end
return connectivity, tree_node_coordinates, nodes, boundary_names
end
function preprocess_standard_abaqus(meshfile,
linear_quads, linear_hexes,
quadratic_quads, quadratic_hexes,
n_dimensions)
meshfile_preproc = replace(meshfile, ".inp" => "_preproc.inp")
elements_begin_idx = 0
sets_begin_idx = 0
open(meshfile, "r") do infile
open(meshfile_preproc, "w") do outfile
for (line_index, line) in enumerate(eachline(infile))
println(outfile, line)
if occursin("******* E L E M E N T S *************", line)
elements_begin_idx = line_index + 1
break
end
end
end
for (line_index, line) in enumerate(eachline(infile))
if startswith(line, "*ELSET") || startswith(line, "*NSET")
sets_begin_idx = line_index
break
end
end
end
if sets_begin_idx == 0
sets_begin_idx = length(readlines(meshfile)) + 1
else
sets_begin_idx += elements_begin_idx - 1
end
element_index = 0
preproc_element_section_lines = 0
open(meshfile, "r") do infile
open(meshfile_preproc, "a") do outfile
print_following_lines = false
for (line_index, line) in enumerate(eachline(infile))
if elements_begin_idx <= line_index < sets_begin_idx
if startswith(line, "*ELEMENT")
current_element_type = match(r"\*ELEMENT, type=([^,]+)", line).captures[1]
if n_dimensions == 2 &&
(occursin(linear_quads, current_element_type) ||
occursin(quadratic_quads, current_element_type))
print_following_lines = true
println(outfile, line)
preproc_element_section_lines += 1
elseif n_dimensions == 3 &&
(occursin(linear_hexes, current_element_type) ||
occursin(quadratic_hexes, current_element_type))
print_following_lines = true
println(outfile, line)
preproc_element_section_lines += 1
else
print_following_lines = false
end
else
if print_following_lines
element_index += 1
parts = split(line, ',')
parts[1] = string(element_index)
println(outfile, join(parts, ','))
preproc_element_section_lines += 1
end
end
end
if line_index >= sets_begin_idx
println(outfile, line)
end
end
end
end
sets_begin_idx = elements_begin_idx + preproc_element_section_lines
return elements_begin_idx, sets_begin_idx
end
function preprocess_standard_abaqus_for_p4est(meshfile_pre_proc,
linear_quads, linear_hexes,
quadratic_quads, quadratic_hexes,
elements_begin_idx,
sets_begin_idx)
meshfile_p4est_rdy = replace(meshfile_pre_proc,
"_preproc.inp" => "_p4est_ready.inp")
order = 1
current_element_type = ""
new_line = ""
open(meshfile_pre_proc, "r") do infile
open(meshfile_p4est_rdy, "w") do outfile
for (line_index, line) in enumerate(eachline(infile))
if line_index < elements_begin_idx || line_index >= sets_begin_idx
println(outfile, line)
else
if startswith(line, "*ELEMENT")
current_element_type = match(r"\*ELEMENT, type=([^,]+)", line).captures[1]
if occursin(linear_quads, current_element_type) ||
occursin(linear_hexes, current_element_type)
println(outfile, line)
else
order = 2
if occursin(quadratic_quads, current_element_type)
linear_quad_type = "CPS4"
new_line = replace(line,
current_element_type => linear_quad_type)
elseif occursin(quadratic_hexes, current_element_type)
linear_hex_type = "C3D8"
new_line = replace(line,
current_element_type => linear_hex_type)
end
println(outfile, new_line)
end
else
if occursin(linear_quads, current_element_type) ||
occursin(linear_hexes, current_element_type)
println(outfile, line)
else
parts = split(line, ',')
if occursin(quadratic_quads, current_element_type)
new_line = join(parts[1:5], ',')
elseif occursin(quadratic_hexes, current_element_type)
new_line = join(parts[1:9], ',')
end
println(outfile, new_line)
end
end
end
end
end
end
return order
end
function read_nodes_standard_abaqus(meshfile, n_dimensions,
elements_begin_idx, RealT)
mesh_nodes = Dict{Int, SVector{n_dimensions, RealT}}()
node_section = false
open(meshfile, "r") do infile
for (line_index, line) in enumerate(eachline(infile))
if line_index < elements_begin_idx
if startswith(line, "*NODE")
node_section = true
end
if node_section
parts = split(line, ',')
if length(parts) >= n_dimensions + 1
node_id = parse(Int, parts[1])
coordinates = SVector{n_dimensions, RealT}([parse(RealT,
parts[i])
for i in 2:(n_dimensions + 1)])
mesh_nodes[node_id] = coordinates
end
end
end
end
end
return mesh_nodes
end
function p4est_connectivity_from_standard_abaqus(meshfile, mapping, polydeg,
n_dimensions,
RealT,
boundary_symbols)
linear_quads = r"^(CPE4|CPEG4|CPS4|M3D4|S4|SFM3D4).*$"
quadratic_quads = r"^(CPE8|CPS8|CAX8|S8|M3D8|M3D9).*$"
linear_hexes = r"^(C3D8).*$"
quadratic_hexes = r"^(C3D27).*$"
meshfile_preproc = replace(meshfile, ".inp" => "_preproc.inp")
elements_begin_idx = -1
sets_begin_idx = -1
mesh_polydeg = 1
if !mpi_isparallel() || (mpi_isparallel() && mpi_isroot())
elements_begin_idx, sets_begin_idx = preprocess_standard_abaqus(meshfile,
linear_quads,
linear_hexes,
quadratic_quads,
quadratic_hexes,
n_dimensions)
mesh_polydeg = preprocess_standard_abaqus_for_p4est(meshfile_preproc,
linear_quads,
linear_hexes,
quadratic_quads,
quadratic_hexes,
elements_begin_idx,
sets_begin_idx)
end
if mpi_isparallel()
if mpi_isroot()
MPI.Bcast!(Ref(elements_begin_idx), mpi_root(), mpi_comm())
MPI.Bcast!(Ref(sets_begin_idx), mpi_root(), mpi_comm())
MPI.Bcast!(Ref(mesh_polydeg), mpi_root(), mpi_comm())
else
elements_begin_idx = MPI.Bcast!(Ref(0), mpi_root(), mpi_comm())[]
sets_begin_idx = MPI.Bcast!(Ref(0), mpi_root(), mpi_comm())[]
mesh_polydeg = MPI.Bcast!(Ref(0), mpi_root(), mpi_comm())[]
end
end
meshfile_p4est_rdy = replace(meshfile, ".inp" => "_p4est_ready.inp")
connectivity = read_inp_p4est(meshfile_p4est_rdy, Val(n_dimensions))
connectivity_pw = PointerWrapper(connectivity)
n_trees::Int = connectivity_pw.num_trees[]
n_vertices::Int = connectivity_pw.num_vertices[]
vertices = unsafe_wrap(Array, connectivity_pw.vertices, (3, n_vertices))
tree_to_vertex = unsafe_wrap(Array, connectivity_pw.tree_to_vertex,
(2^n_dimensions, n_trees))
basis = LobattoLegendreBasis(RealT, polydeg)
nodes = basis.nodes
if mesh_polydeg == 2
mesh_nnodes = mesh_polydeg + 1
cheby_nodes, _ = chebyshev_gauss_lobatto_nodes_weights(mesh_nnodes)
nodes = SVector{mesh_nnodes}(cheby_nodes)
end
tree_node_coordinates = Array{RealT, n_dimensions + 2}(undef, n_dimensions,
ntuple(_ -> length(nodes),
n_dimensions)...,
n_trees)
if mesh_polydeg == 1
calc_tree_node_coordinates!(tree_node_coordinates, nodes, mapping,
vertices, tree_to_vertex)
else
mesh_nodes = read_nodes_standard_abaqus(meshfile_preproc, n_dimensions,
elements_begin_idx, RealT)
element_lines = readlines(open(meshfile_preproc))[elements_begin_idx:(sets_begin_idx - 1)]
if n_dimensions == 2
calc_tree_node_coordinates!(tree_node_coordinates, element_lines,
nodes, vertices, RealT,
linear_quads, mesh_nodes)
else
@error "Three dimensional quadratic elements are not supported yet!"
end
end
if boundary_symbols === nothing
boundary_names = fill(:all, 2 * n_dimensions, n_trees)
else
node_set_dict = parse_node_sets(meshfile_p4est_rdy, boundary_symbols)
element_node_matrix = parse_elements(meshfile_p4est_rdy, n_trees, n_dimensions,
elements_begin_idx, sets_begin_idx)
boundary_names = fill(Symbol("---"), 2 * n_dimensions, n_trees)
assign_boundaries_standard_abaqus!(boundary_names, n_trees,
element_node_matrix, node_set_dict,
Val(n_dimensions))
end
return connectivity, tree_node_coordinates, nodes, boundary_names
end
function parse_elements(meshfile, n_trees, n_dims,
elements_begin_idx,
sets_begin_idx)
expected_content_length = n_dims == 2 ? 5 : 9
element_node_matrix = Matrix{Int64}(undef, n_trees, expected_content_length - 1)
el_list_follows = false
tree_id = 1
open(meshfile, "r") do infile
for (line_index, line) in enumerate(eachline(infile))
if elements_begin_idx <= line_index < sets_begin_idx
if startswith(line, "*ELEMENT")
el_list_follows = true
elseif el_list_follows
content = split(line, ",")
if length(content) == expected_content_length
content_int = parse.(Int64, content)
element_node_matrix[tree_id, :] = content_int[2:end]
tree_id += 1
else
el_list_follows = false
end
end
end
end
end
return element_node_matrix
end
function parse_node_sets(meshfile, boundary_symbols)
nodes_dict = Dict{Symbol, Set{Int64}}()
current_symbol = nothing
current_nodes = Int64[]
open(meshfile, "r") do file
for line in eachline(file)
if startswith(line, "*NSET,NSET=")
if current_symbol !== nothing
nodes_dict[current_symbol] = current_nodes
end
current_symbol = Symbol(split(line, "=")[2])
if current_symbol in boundary_symbols
current_nodes = Set{Int64}()
else
current_symbol = nothing
end
elseif current_symbol !== nothing
try
union!(current_nodes, parse.(Int64, split(line, ",")[1:(end - 1)]))
catch
nodes_dict[current_symbol] = current_nodes
current_symbol = nothing
end
end
end
if current_symbol !== nothing
nodes_dict[current_symbol] = current_nodes
end
end
for symbol in boundary_symbols
if !haskey(nodes_dict, symbol)
@warn "No nodes found for nodeset :" * "$symbol" * " !"
end
end
return nodes_dict
end
function assign_boundaries_standard_abaqus!(boundary_names, n_trees,
element_node_matrix, node_set_dict,
::Val{2})
@threaded for tree in 1:n_trees
tree_nodes = element_node_matrix[tree, :]
for boundary in keys(node_set_dict)
if tree_nodes[1] in node_set_dict[boundary] &&
tree_nodes[2] in node_set_dict[boundary]
boundary_names[3, tree] = boundary
end
if tree_nodes[2] in node_set_dict[boundary] &&
tree_nodes[3] in node_set_dict[boundary]
boundary_names[2, tree] = boundary
end
if tree_nodes[3] in node_set_dict[boundary] &&
tree_nodes[4] in node_set_dict[boundary]
boundary_names[4, tree] = boundary
end
if tree_nodes[4] in node_set_dict[boundary] &&
tree_nodes[1] in node_set_dict[boundary]
boundary_names[1, tree] = boundary
end
end
end
return boundary_names
end
function assign_boundaries_standard_abaqus!(boundary_names, n_trees,
element_node_matrix, node_set_dict,
::Val{3})
@threaded for tree in 1:n_trees
tree_nodes = element_node_matrix[tree, :]
for boundary in keys(node_set_dict)
if tree_nodes[1] in node_set_dict[boundary] &&
tree_nodes[2] in node_set_dict[boundary] &&
tree_nodes[5] in node_set_dict[boundary] &&
tree_nodes[6] in node_set_dict[boundary]
boundary_names[3, tree] = boundary
end
if tree_nodes[3] in node_set_dict[boundary] &&
tree_nodes[4] in node_set_dict[boundary] &&
tree_nodes[7] in node_set_dict[boundary] &&
tree_nodes[8] in node_set_dict[boundary]
boundary_names[4, tree] = boundary
end
if tree_nodes[1] in node_set_dict[boundary] &&
tree_nodes[4] in node_set_dict[boundary] &&
tree_nodes[5] in node_set_dict[boundary] &&
tree_nodes[8] in node_set_dict[boundary]
boundary_names[1, tree] = boundary
end
if tree_nodes[2] in node_set_dict[boundary] &&
tree_nodes[3] in node_set_dict[boundary] &&
tree_nodes[6] in node_set_dict[boundary] &&
tree_nodes[7] in node_set_dict[boundary]
boundary_names[2, tree] = boundary
end
if tree_nodes[1] in node_set_dict[boundary] &&
tree_nodes[2] in node_set_dict[boundary] &&
tree_nodes[3] in node_set_dict[boundary] &&
tree_nodes[4] in node_set_dict[boundary]
boundary_names[5, tree] = boundary
end
if tree_nodes[5] in node_set_dict[boundary] &&
tree_nodes[6] in node_set_dict[boundary] &&
tree_nodes[7] in node_set_dict[boundary] &&
tree_nodes[8] in node_set_dict[boundary]
boundary_names[6, tree] = boundary
end
end
end
return boundary_names
end
"""
P4estMeshCubedSphere(trees_per_face_dimension, layers, inner_radius, thickness;
polydeg, RealT=Float64,
initial_refinement_level=0, unsaved_changes=true,
p4est_partition_allow_for_coarsening=true)
Build a "Cubed Sphere" mesh as `P4estMesh` with
`6 * trees_per_face_dimension^2 * layers` trees.
The mesh will have two boundaries, `:inside` and `:outside`.
# Arguments
- `trees_per_face_dimension::Integer`: the number of trees in the first two local dimensions of
each face.
- `layers::Integer`: the number of trees in the third local dimension of each face, i.e., the number
of layers of the sphere.
- `inner_radius::Integer`: the inner radius of the sphere.
- `thickness::Integer`: the thickness of the sphere. The outer radius will be `inner_radius + thickness`.
- `polydeg::Integer`: polynomial degree used to store the geometry of the mesh.
The mapping will be approximated by an interpolation polynomial
of the specified degree for each tree.
- `RealT::Type`: the type that should be used for coordinates.
- `initial_refinement_level::Integer`: refine the mesh uniformly to this level before the simulation starts.
- `unsaved_changes::Bool`: if set to `true`, the mesh will be saved to a mesh file.
- `p4est_partition_allow_for_coarsening::Bool`: Must be `true` when using AMR to make mesh adaptivity
independent of domain partitioning. Should be `false` for static meshes
to permit more fine-grained partitioning.
"""
function P4estMeshCubedSphere(trees_per_face_dimension, layers, inner_radius, thickness;
polydeg, RealT = Float64,
initial_refinement_level = 0, unsaved_changes = true,
p4est_partition_allow_for_coarsening = true)
connectivity = connectivity_cubed_sphere(trees_per_face_dimension, layers)
n_trees = 6 * trees_per_face_dimension^2 * layers
basis = LobattoLegendreBasis(RealT, polydeg)
nodes = basis.nodes
tree_node_coordinates = Array{RealT, 5}(undef, 3,
ntuple(_ -> length(nodes), 3)...,
n_trees)
calc_tree_node_coordinates!(tree_node_coordinates, nodes, trees_per_face_dimension,
layers,
inner_radius, thickness)
p4est = new_p4est(connectivity, initial_refinement_level)
boundary_names = fill(Symbol("---"), 2 * 3, n_trees)
boundary_names[5, :] .= Symbol("inside")
boundary_names[6, :] .= Symbol("outside")
return P4estMesh{3}(p4est, tree_node_coordinates, nodes,
boundary_names, "", unsaved_changes,
p4est_partition_allow_for_coarsening)
end
function connectivity_structured(n_cells_x, n_cells_y, periodicity)
linear_indices = LinearIndices((n_cells_x, n_cells_y))
n_vertices = 0
n_trees = n_cells_x * n_cells_y
n_corners = 0
vertices = C_NULL
tree_to_vertex = C_NULL
tree_to_tree = Array{p4est_topidx_t, 2}(undef, 4, n_trees)
tree_to_face = Array{Int8, 2}(undef, 4, n_trees)
for cell_y in 1:n_cells_y, cell_x in 1:n_cells_x
tree = linear_indices[cell_x, cell_y]
if cell_x > 1
tree_to_tree[1, tree] = linear_indices[cell_x - 1, cell_y] - 1
tree_to_face[1, tree] = 1
elseif periodicity[1]
tree_to_tree[1, tree] = linear_indices[n_cells_x, cell_y] - 1
tree_to_face[1, tree] = 1
else
tree_to_tree[1, tree] = tree - 1
tree_to_face[1, tree] = 0
end
if cell_x < n_cells_x
tree_to_tree[2, tree] = linear_indices[cell_x + 1, cell_y] - 1
tree_to_face[2, tree] = 0
elseif periodicity[1]
tree_to_tree[2, tree] = linear_indices[1, cell_y] - 1
tree_to_face[2, tree] = 0
else
tree_to_tree[2, tree] = tree - 1
tree_to_face[2, tree] = 1
end
if cell_y > 1
tree_to_tree[3, tree] = linear_indices[cell_x, cell_y - 1] - 1
tree_to_face[3, tree] = 3
elseif periodicity[2]
tree_to_tree[3, tree] = linear_indices[cell_x, n_cells_y] - 1
tree_to_face[3, tree] = 3
else
tree_to_tree[3, tree] = tree - 1
tree_to_face[3, tree] = 2
end
if cell_y < n_cells_y
tree_to_tree[4, tree] = linear_indices[cell_x, cell_y + 1] - 1
tree_to_face[4, tree] = 2
elseif periodicity[2]
tree_to_tree[4, tree] = linear_indices[cell_x, 1] - 1
tree_to_face[4, tree] = 2
else
tree_to_tree[4, tree] = tree - 1
tree_to_face[4, tree] = 3
end
end
tree_to_corner = C_NULL
ctt_offset = zeros(p4est_topidx_t, 1)
corner_to_tree = C_NULL
corner_to_corner = C_NULL
connectivity = p4est_connectivity_new_copy(n_vertices, n_trees, n_corners,
vertices, tree_to_vertex,
tree_to_tree, tree_to_face,
tree_to_corner, ctt_offset,
corner_to_tree, corner_to_corner)
@assert p4est_connectivity_is_valid(connectivity) == 1
return connectivity
end
function connectivity_structured(n_cells_x, n_cells_y, n_cells_z, periodicity)
linear_indices = LinearIndices((n_cells_x, n_cells_y, n_cells_z))
n_vertices = 0
n_trees = n_cells_x * n_cells_y * n_cells_z
n_edges = 0
n_corners = 0
vertices = C_NULL
tree_to_vertex = C_NULL
tree_to_tree = Array{p4est_topidx_t, 2}(undef, 6, n_trees)
tree_to_face = Array{Int8, 2}(undef, 6, n_trees)
for cell_z in 1:n_cells_z, cell_y in 1:n_cells_y, cell_x in 1:n_cells_x
tree = linear_indices[cell_x, cell_y, cell_z]
if cell_x > 1
tree_to_tree[1, tree] = linear_indices[cell_x - 1, cell_y, cell_z] - 1
tree_to_face[1, tree] = 1
elseif periodicity[1]
tree_to_tree[1, tree] = linear_indices[n_cells_x, cell_y, cell_z] - 1
tree_to_face[1, tree] = 1
else
tree_to_tree[1, tree] = tree - 1
tree_to_face[1, tree] = 0
end
if cell_x < n_cells_x
tree_to_tree[2, tree] = linear_indices[cell_x + 1, cell_y, cell_z] - 1
tree_to_face[2, tree] = 0
elseif periodicity[1]
tree_to_tree[2, tree] = linear_indices[1, cell_y, cell_z] - 1
tree_to_face[2, tree] = 0
else
tree_to_tree[2, tree] = tree - 1
tree_to_face[2, tree] = 1
end
if cell_y > 1
tree_to_tree[3, tree] = linear_indices[cell_x, cell_y - 1, cell_z] - 1
tree_to_face[3, tree] = 3
elseif periodicity[2]
tree_to_tree[3, tree] = linear_indices[cell_x, n_cells_y, cell_z] - 1
tree_to_face[3, tree] = 3
else
tree_to_tree[3, tree] = tree - 1
tree_to_face[3, tree] = 2
end
if cell_y < n_cells_y
tree_to_tree[4, tree] = linear_indices[cell_x, cell_y + 1, cell_z] - 1
tree_to_face[4, tree] = 2
elseif periodicity[2]
tree_to_tree[4, tree] = linear_indices[cell_x, 1, cell_z] - 1
tree_to_face[4, tree] = 2
else
tree_to_tree[4, tree] = tree - 1
tree_to_face[4, tree] = 3
end
if cell_z > 1
tree_to_tree[5, tree] = linear_indices[cell_x, cell_y, cell_z - 1] - 1
tree_to_face[5, tree] = 5
elseif periodicity[3]
tree_to_tree[5, tree] = linear_indices[cell_x, cell_y, n_cells_z] - 1
tree_to_face[5, tree] = 5
else
tree_to_tree[5, tree] = tree - 1
tree_to_face[5, tree] = 4
end
if cell_z < n_cells_z
tree_to_tree[6, tree] = linear_indices[cell_x, cell_y, cell_z + 1] - 1
tree_to_face[6, tree] = 4
elseif periodicity[3]
tree_to_tree[6, tree] = linear_indices[cell_x, cell_y, 1] - 1
tree_to_face[6, tree] = 4
else
tree_to_tree[6, tree] = tree - 1
tree_to_face[6, tree] = 5
end
end
tree_to_edge = C_NULL
ett_offset = zeros(p4est_topidx_t, 1)
edge_to_tree = C_NULL
edge_to_edge = C_NULL
tree_to_corner = C_NULL
ctt_offset = zeros(p4est_topidx_t, 1)
corner_to_tree = C_NULL
corner_to_corner = C_NULL
connectivity = p8est_connectivity_new_copy(n_vertices, n_trees, n_corners, n_edges,
vertices, tree_to_vertex,
tree_to_tree, tree_to_face,
tree_to_edge, ett_offset,
edge_to_tree, edge_to_edge,
tree_to_corner, ctt_offset,
corner_to_tree, corner_to_corner)
@assert p8est_connectivity_is_valid(connectivity) == 1
return connectivity
end
function connectivity_cubed_sphere(trees_per_face_dimension, layers)
n_cells_x = n_cells_y = trees_per_face_dimension
n_cells_z = layers
linear_indices = LinearIndices((trees_per_face_dimension, trees_per_face_dimension,
layers, 6))
n_vertices = 0
n_trees = 6 * n_cells_x * n_cells_y * n_cells_z
n_edges = 0
n_corners = 0
vertices = C_NULL
tree_to_vertex = C_NULL
tree_to_tree = Array{p4est_topidx_t, 2}(undef, 6, n_trees)
tree_to_face = Array{Int8, 2}(undef, 6, n_trees)
for direction in 1:6
for cell_z in 1:n_cells_z, cell_y in 1:n_cells_y, cell_x in 1:n_cells_x
tree = linear_indices[cell_x, cell_y, cell_z, direction]
if cell_x > 1
tree_to_tree[1, tree] = linear_indices[cell_x - 1, cell_y, cell_z,
direction] - 1
tree_to_face[1, tree] = 1
elseif direction == 1
target = 4
tree_to_tree[1, tree] = linear_indices[end, cell_y, cell_z, target] - 1
tree_to_face[1, tree] = 1
elseif direction == 2
target = 3
tree_to_tree[1, tree] = linear_indices[end, cell_y, cell_z, target] - 1
tree_to_face[1, tree] = 1
elseif direction == 3
target = 1
tree_to_tree[1, tree] = linear_indices[end, cell_y, cell_z, target] - 1
tree_to_face[1, tree] = 1
elseif direction == 4
target = 2
tree_to_tree[1, tree] = linear_indices[end, cell_y, cell_z, target] - 1
tree_to_face[1, tree] = 1
elseif direction == 5
target = 2
tree_to_tree[1, tree] = linear_indices[cell_y, 1, cell_z, target] - 1
tree_to_face[1, tree] = 2
else
target = 1
tree_to_tree[1, tree] = linear_indices[end - cell_y + 1, end, cell_z,
target] - 1
tree_to_face[1, tree] = 9
end
if cell_x < n_cells_x
tree_to_tree[2, tree] = linear_indices[cell_x + 1, cell_y, cell_z,
direction] - 1
tree_to_face[2, tree] = 0
elseif direction == 1
target = 3
tree_to_tree[2, tree] = linear_indices[1, cell_y, cell_z, target] - 1
tree_to_face[2, tree] = 0
elseif direction == 2
target = 4
tree_to_tree[2, tree] = linear_indices[1, cell_y, cell_z, target] - 1
tree_to_face[2, tree] = 0
elseif direction == 3
target = 2
tree_to_tree[2, tree] = linear_indices[1, cell_y, cell_z, target] - 1
tree_to_face[2, tree] = 0
elseif direction == 4
target = 1
tree_to_tree[2, tree] = linear_indices[1, cell_y, cell_z, target] - 1
tree_to_face[2, tree] = 0
elseif direction == 5
target = 1
tree_to_tree[2, tree] = linear_indices[end - cell_y + 1, 1, cell_z,
target] - 1
tree_to_face[2, tree] = 8
else
target = 2
tree_to_tree[2, tree] = linear_indices[cell_y, end, cell_z, target] - 1
tree_to_face[2, tree] = 3
end
if cell_y > 1
tree_to_tree[3, tree] = linear_indices[cell_x, cell_y - 1, cell_z,
direction] - 1
tree_to_face[3, tree] = 3
elseif direction == 1
target = 5
tree_to_tree[3, tree] = linear_indices[end, end - cell_x + 1, cell_z,
target] - 1
tree_to_face[3, tree] = 7
elseif direction == 2
target = 5
tree_to_tree[3, tree] = linear_indices[1, cell_x, cell_z, target] - 1
tree_to_face[3, tree] = 0
elseif direction == 3
target = 5
tree_to_tree[3, tree] = linear_indices[end - cell_x + 1, 1, cell_z,
target] - 1
tree_to_face[3, tree] = 8
elseif direction == 4
target = 5
tree_to_tree[3, tree] = linear_indices[cell_x, end, cell_z, target] - 1
tree_to_face[3, tree] = 3
elseif direction == 5
target = 3
tree_to_tree[3, tree] = linear_indices[end - cell_x + 1, 1, cell_z,
target] - 1
tree_to_face[3, tree] = 8
else
target = 3
tree_to_tree[3, tree] = linear_indices[cell_x, end, cell_z, target] - 1
tree_to_face[3, tree] = 3
end
if cell_y < n_cells_y
tree_to_tree[4, tree] = linear_indices[cell_x, cell_y + 1, cell_z,
direction] - 1
tree_to_face[4, tree] = 2
elseif direction == 1
target = 6
tree_to_tree[4, tree] = linear_indices[1, end - cell_x + 1, cell_z,
target] - 1
tree_to_face[4, tree] = 6
elseif direction == 2
target = 6
tree_to_tree[4, tree] = linear_indices[end, cell_x, cell_z, target] - 1
tree_to_face[4, tree] = 1
elseif direction == 3
target = 6
tree_to_tree[4, tree] = linear_indices[cell_x, 1, cell_z, target] - 1
tree_to_face[4, tree] = 2
elseif direction == 4
target = 6
tree_to_tree[4, tree] = linear_indices[end - cell_x + 1, end, cell_z,
target] - 1
tree_to_face[4, tree] = 9
elseif direction == 5
target = 4
tree_to_tree[4, tree] = linear_indices[cell_x, 1, cell_z, target] - 1
tree_to_face[4, tree] = 2
else
target = 4
tree_to_tree[4, tree] = linear_indices[end - cell_x + 1, end, cell_z,
target] - 1
tree_to_face[4, tree] = 9
end
if cell_z > 1
tree_to_tree[5, tree] = linear_indices[cell_x, cell_y, cell_z - 1,
direction] - 1
tree_to_face[5, tree] = 5
else
tree_to_tree[5, tree] = tree - 1
tree_to_face[5, tree] = 4
end
if cell_z < n_cells_z
tree_to_tree[6, tree] = linear_indices[cell_x, cell_y, cell_z + 1,
direction] - 1
tree_to_face[6, tree] = 4
else
tree_to_tree[6, tree] = tree - 1
tree_to_face[6, tree] = 5
end
end
end
tree_to_edge = C_NULL
ett_offset = zeros(p4est_topidx_t, 1)
edge_to_tree = C_NULL
edge_to_edge = C_NULL
tree_to_corner = C_NULL
ctt_offset = zeros(p4est_topidx_t, 1)
corner_to_tree = C_NULL
corner_to_corner = C_NULL
connectivity = p8est_connectivity_new_copy(n_vertices, n_trees, n_corners, n_edges,
vertices, tree_to_vertex,
tree_to_tree, tree_to_face,
tree_to_edge, ett_offset,
edge_to_tree, edge_to_edge,
tree_to_corner, ctt_offset,
corner_to_tree, corner_to_corner)
@assert p8est_connectivity_is_valid(connectivity) == 1
return connectivity
end
function calc_tree_node_coordinates!(node_coordinates::AbstractArray{<:Any, 4},
nodes, mapping, trees_per_dimension)
linear_indices = LinearIndices(trees_per_dimension)
dx = 2 / trees_per_dimension[1]
dy = 2 / trees_per_dimension[2]
for cell_y in 1:trees_per_dimension[2], cell_x in 1:trees_per_dimension[1]
tree_id = linear_indices[cell_x, cell_y]
cell_x_offset = -1 + (cell_x - 1) * dx + dx / 2
cell_y_offset = -1 + (cell_y - 1) * dy + dy / 2
for j in eachindex(nodes), i in eachindex(nodes)
node_coordinates[:, i, j, tree_id] .= mapping(cell_x_offset +
dx / 2 * nodes[i],
cell_y_offset +
dy / 2 * nodes[j])
end
end
end
function calc_tree_node_coordinates!(node_coordinates::AbstractArray{<:Any, 5},
nodes, mapping, trees_per_dimension)
linear_indices = LinearIndices(trees_per_dimension)
dx = 2 / trees_per_dimension[1]
dy = 2 / trees_per_dimension[2]
dz = 2 / trees_per_dimension[3]
for cell_z in 1:trees_per_dimension[3],
cell_y in 1:trees_per_dimension[2],
cell_x in 1:trees_per_dimension[1]
tree_id = linear_indices[cell_x, cell_y, cell_z]
cell_x_offset = -1 + (cell_x - 1) * dx + dx / 2
cell_y_offset = -1 + (cell_y - 1) * dy + dy / 2
cell_z_offset = -1 + (cell_z - 1) * dz + dz / 2
for k in eachindex(nodes), j in eachindex(nodes), i in eachindex(nodes)
node_coordinates[:, i, j, k, tree_id] .= mapping(cell_x_offset +
dx / 2 * nodes[i],
cell_y_offset +
dy / 2 * nodes[j],
cell_z_offset +
dz / 2 * nodes[k])
end
end
end
function calc_tree_node_coordinates!(node_coordinates::AbstractArray{RealT, 4},
nodes, mapping,
vertices, tree_to_vertex) where {RealT}
nodes_in = [-1.0, 1.0]
matrix = polynomial_interpolation_matrix(nodes_in, nodes)
data_in = Array{RealT, 3}(undef, 2, 2, 2)
tmp1 = zeros(RealT, 2, length(nodes), length(nodes_in))
for tree in 1:size(tree_to_vertex, 2)
@views data_in[:, 1, 1] .= vertices[1:2, tree_to_vertex[1, tree] + 1]
@views data_in[:, 2, 1] .= vertices[1:2, tree_to_vertex[2, tree] + 1]
@views data_in[:, 1, 2] .= vertices[1:2, tree_to_vertex[3, tree] + 1]
@views data_in[:, 2, 2] .= vertices[1:2, tree_to_vertex[4, tree] + 1]
multiply_dimensionwise!(view(node_coordinates, :, :, :, tree),
matrix, matrix,
data_in,
tmp1)
end
map_node_coordinates!(node_coordinates, mapping)
end
function map_node_coordinates!(node_coordinates::AbstractArray{<:Any, 4}, mapping)
for tree in axes(node_coordinates, 4),
j in axes(node_coordinates, 3),
i in axes(node_coordinates, 2)
node_coordinates[:, i, j, tree] .= mapping(node_coordinates[1, i, j, tree],
node_coordinates[2, i, j, tree])
end
return node_coordinates
end
function map_node_coordinates!(node_coordinates::AbstractArray{<:Any, 4},
mapping::Nothing)
return node_coordinates
end
function calc_tree_node_coordinates!(node_coordinates::AbstractArray{RealT, 5},
nodes, mapping,
vertices, tree_to_vertex) where {RealT}
nodes_in = [-1.0, 1.0]
matrix = polynomial_interpolation_matrix(nodes_in, nodes)
data_in = Array{RealT, 4}(undef, 3, 2, 2, 2)
for tree in 1:size(tree_to_vertex, 2)
@views data_in[:, 1, 1, 1] .= vertices[:, tree_to_vertex[1, tree] + 1]
@views data_in[:, 2, 1, 1] .= vertices[:, tree_to_vertex[2, tree] + 1]
@views data_in[:, 1, 2, 1] .= vertices[:, tree_to_vertex[3, tree] + 1]
@views data_in[:, 2, 2, 1] .= vertices[:, tree_to_vertex[4, tree] + 1]
@views data_in[:, 1, 1, 2] .= vertices[:, tree_to_vertex[5, tree] + 1]
@views data_in[:, 2, 1, 2] .= vertices[:, tree_to_vertex[6, tree] + 1]
@views data_in[:, 1, 2, 2] .= vertices[:, tree_to_vertex[7, tree] + 1]
@views data_in[:, 2, 2, 2] .= vertices[:, tree_to_vertex[8, tree] + 1]
multiply_dimensionwise!(view(node_coordinates, :, :, :, :, tree),
matrix, matrix, matrix,
data_in)
end
map_node_coordinates!(node_coordinates, mapping)
end
function map_node_coordinates!(node_coordinates::AbstractArray{<:Any, 5}, mapping)
for tree in axes(node_coordinates, 5),
k in axes(node_coordinates, 4),
j in axes(node_coordinates, 3),
i in axes(node_coordinates, 2)
node_coordinates[:, i, j, k, tree] .= mapping(node_coordinates[1, i, j, k,
tree],
node_coordinates[2, i, j, k,
tree],
node_coordinates[3, i, j, k,
tree])
end
return node_coordinates
end
function map_node_coordinates!(node_coordinates::AbstractArray{<:Any, 5},
mapping::Nothing)
return node_coordinates
end
function calc_tree_node_coordinates!(node_coordinates::AbstractArray{<:Any, 5},
nodes, trees_per_face_dimension, layers,
inner_radius, thickness)
n_cells_x = n_cells_y = trees_per_face_dimension
n_cells_z = layers
linear_indices = LinearIndices((n_cells_x, n_cells_y, n_cells_z, 6))
dx = 2 / n_cells_x
dy = 2 / n_cells_y
dz = 2 / n_cells_z
for direction in 1:6
for cell_z in 1:n_cells_z, cell_y in 1:n_cells_y, cell_x in 1:n_cells_x
tree = linear_indices[cell_x, cell_y, cell_z, direction]
x_offset = -1 + (cell_x - 1) * dx + dx / 2
y_offset = -1 + (cell_y - 1) * dy + dy / 2
z_offset = -1 + (cell_z - 1) * dz + dz / 2
for k in eachindex(nodes), j in eachindex(nodes), i in eachindex(nodes)
node_coordinates[:, i, j, k, tree] .= cubed_sphere_mapping(x_offset +
dx / 2 *
nodes[i],
y_offset +
dy / 2 *
nodes[j],
z_offset +
dz / 2 *
nodes[k],
inner_radius,
thickness,
direction)
end
end
end
end
function cubed_sphere_mapping(xi, eta, zeta, inner_radius, thickness, direction)
alpha = xi * pi / 4
beta = eta * pi / 4
x = tan(alpha)
y = tan(beta)
cube_coordinates = (SVector(-1, -x, y),
SVector(1, x, y),
SVector(x, -1, y),
SVector(-x, 1, y),
SVector(-x, y, -1),
SVector(x, y, 1))
r = sqrt(1 + x^2 + y^2)
R = inner_radius + thickness * (0.5f0 * (zeta + 1))
return R / r * cube_coordinates[direction]
end
function calc_tree_node_coordinates!(node_coordinates::AbstractArray{<:Any, 4},
file_lines::Vector{String}, nodes, vertices, RealT)
n_trees = last(size(node_coordinates))
nnodes = length(nodes)
file_idx = findfirst(contains("** mesh polynomial degree"), file_lines) + 1
CurvedSurfaceT = CurvedSurface{RealT}
surface_curves = Array{CurvedSurfaceT}(undef, 4)
element_node_ids = Array{Int}(undef, 4)
curved_check = Vector{Int}(undef, 4)
quad_vertices = Array{RealT}(undef, (4, 2))
quad_vertices_flipped = Array{RealT}(undef, (4, 2))
curve_values = Array{RealT}(undef, (nnodes, 2))
bary_weights_ = barycentric_weights(nodes)
bary_weights = SVector{nnodes}(bary_weights_)
for tree in 1:n_trees
current_line = split(file_lines[file_idx])
element_node_ids[1] = parse(Int, current_line[2])
element_node_ids[2] = parse(Int, current_line[3])
element_node_ids[3] = parse(Int, current_line[4])
element_node_ids[4] = parse(Int, current_line[5])
for i in 1:4
quad_vertices[i, :] .= vertices[1:2, element_node_ids[i]]
end
file_idx += 1
current_line = split(file_lines[file_idx])
curved_check[1] = parse(Int, current_line[2])
curved_check[2] = parse(Int, current_line[3])
curved_check[3] = parse(Int, current_line[4])
curved_check[4] = parse(Int, current_line[5])
if sum(curved_check) == 0
calc_node_coordinates!(node_coordinates, tree, nodes, quad_vertices)
else
m1 = 1
m2 = 2
@views quad_vertices_flipped[1, :] .= quad_vertices[4, :]
@views quad_vertices_flipped[2, :] .= quad_vertices[2, :]
@views quad_vertices_flipped[3, :] .= quad_vertices[3, :]
@views quad_vertices_flipped[4, :] .= quad_vertices[1, :]
for i in 1:4
if curved_check[i] == 0
for k in 1:nnodes
curve_values[k, 1] = linear_interpolate(nodes[k],
quad_vertices_flipped[m1,
1],
quad_vertices_flipped[m2,
1])
curve_values[k, 2] = linear_interpolate(nodes[k],
quad_vertices_flipped[m1,
2],
quad_vertices_flipped[m2,
2])
end
else
for k in 1:nnodes
file_idx += 1
current_line = split(file_lines[file_idx])
curve_values[k, 1] = parse(RealT, current_line[2])
curve_values[k, 2] = parse(RealT, current_line[3])
end
end
surface_curves[i] = CurvedSurfaceT(nodes, bary_weights,
copy(curve_values))
m1 += 1
if i == 3
m2 = 1
else
m2 += 1
end
end
calc_node_coordinates!(node_coordinates, tree, nodes, surface_curves)
end
file_idx += 1
end
return file_idx
end
function calc_tree_node_coordinates!(node_coordinates::AbstractArray{<:Any, 4},
element_lines, nodes, vertices, RealT,
linear_quads, mesh_nodes)
nnodes = length(nodes)
CurvedSurfaceT = CurvedSurface{RealT}
surface_curves = Array{CurvedSurfaceT}(undef, 4)
element_node_ids = Array{Int}(undef, 4)
quad_vertices = Array{RealT}(undef, (4, 2))
curve_values = Array{RealT}(undef, (nnodes, 2))
bary_weights_ = barycentric_weights(nodes)
bary_weights = SVector{nnodes}(bary_weights_)
element_set_order = 1
tree = 0
for line_idx in 1:length(element_lines)
line = element_lines[line_idx]
if startswith(line, "*ELEMENT")
current_element_type = match(r"\*ELEMENT, type=([^,]+)", line).captures[1]
if occursin(linear_quads, current_element_type)
element_set_order = 1
else
element_set_order = 2
end
else
tree += 1
line_split = split(line, r",\s+")
element_nodes = parse.(Int, line_split)
if element_set_order == 1
element_node_ids[1] = element_nodes[2]
element_node_ids[2] = element_nodes[3]
element_node_ids[3] = element_nodes[4]
element_node_ids[4] = element_nodes[5]
for i in 1:4
quad_vertices[i, :] .= vertices[1:2, element_node_ids[i]]
end
calc_node_coordinates!(node_coordinates, tree, nodes, quad_vertices)
else
for edge in 1:4
node1 = element_nodes[edge + 1]
node2 = element_nodes[edge + 5]
node3 = edge == 4 ? element_nodes[2] : element_nodes[edge + 2]
node1_coords = mesh_nodes[node1]
node2_coords = mesh_nodes[node2]
node3_coords = mesh_nodes[node3]
if edge in [1, 2]
curve_values[1, 1] = node1_coords[1]
curve_values[1, 2] = node1_coords[2]
curve_values[2, 1] = node2_coords[1]
curve_values[2, 2] = node2_coords[2]
curve_values[3, 1] = node3_coords[1]
curve_values[3, 2] = node3_coords[2]
else
curve_values[1, 1] = node3_coords[1]
curve_values[1, 2] = node3_coords[2]
curve_values[2, 1] = node2_coords[1]
curve_values[2, 2] = node2_coords[2]
curve_values[3, 1] = node1_coords[1]
curve_values[3, 2] = node1_coords[2]
end
surface_curves[edge] = CurvedSurfaceT(nodes, bary_weights,
copy(curve_values))
end
calc_node_coordinates!(node_coordinates, tree, nodes, surface_curves)
end
end
end
end
function calc_tree_node_coordinates!(node_coordinates::AbstractArray{<:Any, 5},
file_lines::Vector{String}, nodes, vertices, RealT)
n_trees = last(size(node_coordinates))
nnodes = length(nodes)
file_idx = findfirst(contains("** mesh polynomial degree"), file_lines) + 1
CurvedFaceT = CurvedFace{RealT}
face_curves = Array{CurvedFaceT}(undef, 6)
element_node_ids = Array{Int}(undef, 8)
curved_check = Vector{Int}(undef, 6)
hex_vertices = Array{RealT}(undef, (3, 8))
face_vertices = Array{RealT}(undef, (3, 4))
curve_values = Array{RealT}(undef, (3, nnodes, nnodes))
bary_weights_ = barycentric_weights(nodes)
bary_weights = SVector{nnodes}(bary_weights_)
for tree in 1:n_trees
current_line = split(file_lines[file_idx])
element_node_ids[1] = parse(Int, current_line[2])
element_node_ids[2] = parse(Int, current_line[3])
element_node_ids[3] = parse(Int, current_line[4])
element_node_ids[4] = parse(Int, current_line[5])
element_node_ids[5] = parse(Int, current_line[6])
element_node_ids[6] = parse(Int, current_line[7])
element_node_ids[7] = parse(Int, current_line[8])
element_node_ids[8] = parse(Int, current_line[9])
for i in 1:8
hex_vertices[:, i] .= vertices[:, element_node_ids[i]]
end
file_idx += 1
current_line = split(file_lines[file_idx])
curved_check[1] = parse(Int, current_line[2])
curved_check[2] = parse(Int, current_line[3])
curved_check[3] = parse(Int, current_line[4])
curved_check[4] = parse(Int, current_line[5])
curved_check[5] = parse(Int, current_line[6])
curved_check[6] = parse(Int, current_line[7])
if sum(curved_check) == 0
calc_node_coordinates!(node_coordinates, tree, nodes, hex_vertices)
else
for face in 1:6
if curved_check[face] == 0
get_vertices_for_bilinear_interpolant!(face_vertices, face,
hex_vertices)
for q in 1:nnodes, p in 1:nnodes
@views bilinear_interpolation!(curve_values[:, p, q],
face_vertices, nodes[p],
nodes[q])
end
else
for q in 1:nnodes, p in 1:nnodes
file_idx += 1
current_line = split(file_lines[file_idx])
curve_values[1, p, q] = parse(RealT, current_line[2])
curve_values[2, p, q] = parse(RealT, current_line[3])
curve_values[3, p, q] = parse(RealT, current_line[4])
end
end
face_curves[face] = CurvedFaceT(nodes, bary_weights, copy(curve_values))
end
calc_node_coordinates!(node_coordinates, tree, nodes, face_curves)
end
file_idx += 1
end
return file_idx
end
function get_vertices_for_bilinear_interpolant!(face_vertices, face_index, hex_vertices)
if face_index == 1
@views face_vertices[:, 1] .= hex_vertices[:, 1]
@views face_vertices[:, 2] .= hex_vertices[:, 2]
@views face_vertices[:, 3] .= hex_vertices[:, 6]
@views face_vertices[:, 4] .= hex_vertices[:, 5]
elseif face_index == 2
@views face_vertices[:, 1] .= hex_vertices[:, 4]
@views face_vertices[:, 2] .= hex_vertices[:, 3]
@views face_vertices[:, 3] .= hex_vertices[:, 7]
@views face_vertices[:, 4] .= hex_vertices[:, 8]
elseif face_index == 3
@views face_vertices[:, 1] .= hex_vertices[:, 1]
@views face_vertices[:, 2] .= hex_vertices[:, 2]
@views face_vertices[:, 3] .= hex_vertices[:, 3]
@views face_vertices[:, 4] .= hex_vertices[:, 4]
elseif face_index == 4
@views face_vertices[:, 1] .= hex_vertices[:, 2]
@views face_vertices[:, 2] .= hex_vertices[:, 3]
@views face_vertices[:, 3] .= hex_vertices[:, 6]
@views face_vertices[:, 4] .= hex_vertices[:, 7]
elseif face_index == 5
@views face_vertices[:, 1] .= hex_vertices[:, 5]
@views face_vertices[:, 2] .= hex_vertices[:, 6]
@views face_vertices[:, 3] .= hex_vertices[:, 7]
@views face_vertices[:, 4] .= hex_vertices[:, 8]
else
@views face_vertices[:, 1] .= hex_vertices[:, 1]
@views face_vertices[:, 2] .= hex_vertices[:, 4]
@views face_vertices[:, 3] .= hex_vertices[:, 8]
@views face_vertices[:, 4] .= hex_vertices[:, 5]
end
end
function bilinear_interpolation!(coordinate, face_vertices, u, v)
for j in 1:3
coordinate[j] = 0.25f0 * (face_vertices[j, 1] * (1 - u) * (1 - v)
+ face_vertices[j, 2] * (1 + u) * (1 - v)
+ face_vertices[j, 3] * (1 + u) * (1 + v)
+ face_vertices[j, 4] * (1 - u) * (1 + v))
end
end
function get_global_first_element_ids(mesh::P4estMesh)
return unsafe_wrap(Array, mesh.p4est.global_first_quadrant, mpi_nranks() + 1)
end
function balance!(mesh::P4estMesh{2}, init_fn = C_NULL)
p4est_balance(mesh.p4est, P4EST_CONNECT_FACE, init_fn)
p4est_balance(mesh.p4est, P4EST_CONNECT_FACE, init_fn)
end
function balance!(mesh::P4estMesh{3}, init_fn = C_NULL)
p8est_balance(mesh.p4est, P8EST_CONNECT_FACE, init_fn)
end
function partition!(mesh::P4estMesh{2}; weight_fn = C_NULL)
p4est_partition(mesh.p4est, Int(mesh.p4est_partition_allow_for_coarsening),
weight_fn)
end
function partition!(mesh::P4estMesh{3}; weight_fn = C_NULL)
p8est_partition(mesh.p4est, Int(mesh.p4est_partition_allow_for_coarsening),
weight_fn)
end
function update_ghost_layer!(mesh::P4estMesh)
ghost_destroy_p4est(mesh.ghost)
mesh.ghost = PointerWrapper(ghost_new_p4est(mesh.p4est))
end
function init_fn(p4est, which_tree, quadrant)
pw = PointerWrapper(Int, unsafe_load(quadrant.p.user_data))
pw[1] = -1
pw[2] = 0
return nothing
end
function cfunction(::typeof(init_fn), ::Val{2})
@cfunction(init_fn, Cvoid,
(Ptr{p4est_t}, Ptr{p4est_topidx_t}, Ptr{p4est_quadrant_t}))
end
function cfunction(::typeof(init_fn), ::Val{3})
@cfunction(init_fn, Cvoid,
(Ptr{p8est_t}, Ptr{p4est_topidx_t}, Ptr{p8est_quadrant_t}))
end
function refine_fn(p4est, which_tree, quadrant)
pw = PointerWrapper(Int, unsafe_load(quadrant.p.user_data))
controller_value = pw[2]
if controller_value > 0
return Cint(1)
else
return Cint(0)
end
end
function cfunction(::typeof(refine_fn), ::Val{2})
@cfunction(refine_fn, Cint,
(Ptr{p4est_t}, Ptr{p4est_topidx_t}, Ptr{p4est_quadrant_t}))
end
function cfunction(::typeof(refine_fn), ::Val{3})
@cfunction(refine_fn, Cint,
(Ptr{p8est_t}, Ptr{p4est_topidx_t}, Ptr{p8est_quadrant_t}))
end
function refine!(mesh::P4estMesh)
original_n_cells = ncells(mesh)
save_original_ids(mesh)
init_fn_c = cfunction(init_fn, Val(ndims(mesh)))
refine_fn_c = cfunction(refine_fn, Val(ndims(mesh)))
@trixi_timeit timer() "refine" refine_p4est!(mesh.p4est, false, refine_fn_c,
init_fn_c)
@trixi_timeit timer() "rebalance" balance!(mesh, init_fn_c)
return collect_changed_cells(mesh, original_n_cells)
end
function coarsen_fn(p4est, which_tree, quadrants_ptr)
quadrants = unsafe_wrap_quadrants(quadrants_ptr, p4est)
controller_value(i) = PointerWrapper(Int, unsafe_load(quadrants[i].p.user_data))[2]
if all(i -> controller_value(i) < 0, eachindex(quadrants))
return Cint(1)
else
return Cint(0)
end
end
function unsafe_wrap_quadrants(quadrants_ptr, ::Ptr{p4est_t})
unsafe_wrap(Array, quadrants_ptr, 4)
end
function unsafe_wrap_quadrants(quadrants_ptr, ::Ptr{p8est_t})
unsafe_wrap(Array, quadrants_ptr, 8)
end
function cfunction(::typeof(coarsen_fn), ::Val{2})
@cfunction(coarsen_fn, Cint,
(Ptr{p4est_t}, Ptr{p4est_topidx_t}, Ptr{Ptr{p4est_quadrant_t}}))
end
function cfunction(::typeof(coarsen_fn), ::Val{3})
@cfunction(coarsen_fn, Cint,
(Ptr{p8est_t}, Ptr{p4est_topidx_t}, Ptr{Ptr{p8est_quadrant_t}}))
end
function coarsen!(mesh::P4estMesh)
original_n_cells = ncells(mesh)
save_original_ids(mesh)
coarsen_fn_c = cfunction(coarsen_fn, Val(ndims(mesh)))
init_fn_c = cfunction(init_fn, Val(ndims(mesh)))
@trixi_timeit timer() "coarsen!" coarsen_p4est!(mesh.p4est, false, coarsen_fn_c,
init_fn_c)
new_cells = collect_new_cells(mesh)
coarsened_cells_vec = collect_changed_cells(mesh, original_n_cells)
coarsened_cells = reshape(coarsened_cells_vec, 2^ndims(mesh), length(new_cells))
intermediate_n_cells = ncells(mesh)
save_original_ids(mesh)
@trixi_timeit timer() "rebalance" balance!(mesh, init_fn_c)
refined_cells = collect_changed_cells(mesh, intermediate_n_cells)
for refined_cell in refined_cells
i = findfirst(==(refined_cell), new_cells)
coarsened_cells[:, i] .= -1
end
return coarsened_cells_vec[coarsened_cells_vec .>= 0]
end
function save_original_id_iter_volume(info, user_data)
info_pw = PointerWrapper(info)
tree_pw = load_pointerwrapper_tree(info_pw.p4est, info_pw.treeid[] + 1)
offset = tree_pw.quadrants_offset[]
quad_id = offset + info_pw.quadid[]
pw = PointerWrapper(Int, info_pw.quad.p.user_data[])
pw[1] = quad_id
return nothing
end
function cfunction(::typeof(save_original_id_iter_volume), ::Val{2})
@cfunction(save_original_id_iter_volume, Cvoid,
(Ptr{p4est_iter_volume_info_t}, Ptr{Cvoid}))
end
function cfunction(::typeof(save_original_id_iter_volume), ::Val{3})
@cfunction(save_original_id_iter_volume, Cvoid,
(Ptr{p8est_iter_volume_info_t}, Ptr{Cvoid}))
end
function save_original_ids(mesh::P4estMesh)
iter_volume_c = cfunction(save_original_id_iter_volume, Val(ndims(mesh)))
iterate_p4est(mesh.p4est, C_NULL; iter_volume_c = iter_volume_c)
end
function collect_changed_iter_volume(info, user_data)
info_pw = PointerWrapper(info)
quad_data_pw = PointerWrapper(Int, info_pw.quad.p.user_data[])
original_id = quad_data_pw[1]
if original_id >= 0
user_data_pw = PointerWrapper(Int, user_data)
user_data_pw[original_id + 1] = 0
end
return nothing
end
function cfunction(::typeof(collect_changed_iter_volume), ::Val{2})
@cfunction(collect_changed_iter_volume, Cvoid,
(Ptr{p4est_iter_volume_info_t}, Ptr{Cvoid}))
end
function cfunction(::typeof(collect_changed_iter_volume), ::Val{3})
@cfunction(collect_changed_iter_volume, Cvoid,
(Ptr{p8est_iter_volume_info_t}, Ptr{Cvoid}))
end
function collect_changed_cells(mesh::P4estMesh, original_n_cells)
original_cells = collect(1:original_n_cells)
iter_volume_c = cfunction(collect_changed_iter_volume, Val(ndims(mesh)))
iterate_p4est(mesh.p4est, original_cells; iter_volume_c = iter_volume_c)
changed_original_cells = original_cells[original_cells .> 0]
return changed_original_cells
end
function collect_new_iter_volume(info, user_data)
info_pw = PointerWrapper(info)
original_id = PointerWrapper(Int, info_pw.quad.p.user_data[])[1]
if original_id < 0
tree_pw = load_pointerwrapper_tree(info_pw.p4est, info_pw.treeid[] + 1)
offset = tree_pw.quadrants_offset[]
quad_id = offset + info_pw.quadid[]
user_data_pw = PointerWrapper(Int, user_data)
user_data_pw[quad_id + 1] = 1
end
return nothing
end
function cfunction(::typeof(collect_new_iter_volume), ::Val{2})
@cfunction(collect_new_iter_volume, Cvoid,
(Ptr{p4est_iter_volume_info_t}, Ptr{Cvoid}))
end
function cfunction(::typeof(collect_new_iter_volume), ::Val{3})
@cfunction(collect_new_iter_volume, Cvoid,
(Ptr{p8est_iter_volume_info_t}, Ptr{Cvoid}))
end
function collect_new_cells(mesh::P4estMesh)
cell_is_new = zeros(Int, ncells(mesh))
iter_volume_c = cfunction(collect_new_iter_volume, Val(ndims(mesh)))
iterate_p4est(mesh.p4est, cell_is_new; iter_volume_c = iter_volume_c)
new_cells = findall(==(1), cell_is_new)
return new_cells
end
end