Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
trixi-framework
GitHub Repository: trixi-framework/Trixi.jl
Path: blob/main/src/meshes/parallel_tree.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
# Composite type that represents a NDIMS-dimensional tree (parallel version).
9
#
10
# Implements everything required for AbstractContainer.
11
#
12
# Note: The way the data structures are set up and the way most algorithms
13
# work, it is *always* assumed that
14
# a) we have a balanced tree (= at most one level difference between
15
# neighboring cells, or 2:1 rule)
16
# b) we may not have all children (= some children may not exist)
17
# c) the tree is stored depth-first
18
#
19
# However, the way the refinement/coarsening algorithms are currently
20
# implemented, we only have fully refined cells. That is, a cell either has 2^NDIMS children or
21
# no children at all (= leaf cell). This restriction is also assumed at
22
# multiple positions in the refinement/coarsening algorithms.
23
#
24
# An exception to the 2:1 rule exists for the low-level `refine_unbalanced!`
25
# function, which is required for implementing level-wise refinement in a sane
26
# way. Also, depth-first ordering *might* not be guaranteed during
27
# refinement/coarsening operations.
28
mutable struct ParallelTree{NDIMS, RealT <: Real} <: AbstractTree{NDIMS}
29
const capacity::Int
30
length::Int
31
32
parent_ids::Vector{Int}
33
child_ids::Matrix{Int}
34
neighbor_ids::Matrix{Int}
35
levels::Vector{Int}
36
coordinates::Matrix{RealT}
37
original_cell_ids::Vector{Int}
38
39
center_level_0::SVector{NDIMS, RealT}
40
length_level_0::RealT
41
periodicity::NTuple{NDIMS, Bool}
42
43
mpi_ranks::Vector{Int} # Addition compared to `SerialTree`
44
45
function ParallelTree{NDIMS, RealT}(capacity::Integer) where {NDIMS, RealT <: Real}
46
@assert NDIMS isa Integer
47
48
parent_ids = fill(typemin(Int), capacity + 1)
49
child_ids = fill(typemin(Int), 2^NDIMS, capacity + 1)
50
neighbor_ids = fill(typemin(Int), 2 * NDIMS, capacity + 1)
51
levels = fill(typemin(Int), capacity + 1)
52
coordinates = fill(convert(RealT, NaN), NDIMS, capacity + 1)
53
original_cell_ids = fill(typemin(Int), capacity + 1)
54
mpi_ranks = fill(typemin(Int), capacity + 1)
55
56
center_level_0 = SVector(ntuple(_ -> convert(RealT, NaN), NDIMS))
57
length_level_0 = convert(RealT, NaN)
58
periodicity = ntuple(_ -> false, NDIMS)
59
60
return new(capacity, 0, # length
61
parent_ids, child_ids, neighbor_ids,
62
levels, coordinates, original_cell_ids,
63
center_level_0, length_level_0,
64
periodicity, mpi_ranks)
65
end
66
end
67
68
# Constructor for passing the dimension as an argument. Default datatype: Float64
69
ParallelTree(::Val{NDIMS}, args...) where {NDIMS} = ParallelTree{NDIMS, Float64}(args...)
70
71
# Create and initialize tree
72
function ParallelTree{NDIMS, RealT}(capacity::Int, center::AbstractArray{RealT},
73
length::RealT,
74
periodicity = false) where {NDIMS, RealT <: Real}
75
# Create instance
76
t = ParallelTree{NDIMS, RealT}(capacity)
77
78
# Initialize root cell
79
init!(t, center, length, periodicity)
80
81
return t
82
end
83
84
# Constructors accepting a single number as center (as opposed to an array) for 1D
85
function ParallelTree{1, RealT}(cap::Int, center::RealT, len::RealT,
86
periodicity = false) where {RealT <: Real}
87
return ParallelTree{1, RealT}(cap, [center], len, periodicity)
88
end
89
function ParallelTree{1}(cap::Int, center::RealT, len::RealT,
90
periodicity = false) where {RealT <: Real}
91
return ParallelTree{1, RealT}(cap, [center], len, periodicity)
92
end
93
94
# Clear tree with deleting data structures, store center and length, and create root cell
95
function init!(t::ParallelTree, center::AbstractArray{RealT}, length::RealT,
96
periodicity = false) where {RealT}
97
clear!(t)
98
99
# Set domain information
100
t.center_level_0 = center
101
t.length_level_0 = length
102
103
# Create root cell
104
t.length += 1
105
t.parent_ids[1] = 0
106
t.child_ids[:, 1] .= 0
107
t.levels[1] = 0
108
set_cell_coordinates!(t, t.center_level_0, 1)
109
t.original_cell_ids[1] = 0
110
t.mpi_ranks[1] = typemin(Int)
111
112
# Set neighbor ids: for each periodic direction, the level-0 cell is its own neighbor
113
if all(periodicity)
114
# Also catches case where periodicity = true
115
t.neighbor_ids[:, 1] .= 1
116
t.periodicity = ntuple(x -> true, ndims(t))
117
elseif !any(periodicity)
118
# Also catches case where periodicity = false
119
t.neighbor_ids[:, 1] .= 0
120
t.periodicity = ntuple(x -> false, ndims(t))
121
else
122
# Default case if periodicity is an iterable
123
for dimension in 1:ndims(t)
124
if periodicity[dimension]
125
t.neighbor_ids[2 * dimension - 1, 1] = 1
126
t.neighbor_ids[2 * dimension - 0, 1] = 1
127
else
128
t.neighbor_ids[2 * dimension - 1, 1] = 0
129
t.neighbor_ids[2 * dimension - 0, 1] = 0
130
end
131
end
132
133
t.periodicity = Tuple(periodicity)
134
end
135
end
136
137
# Convenience output for debugging
138
function Base.show(io::IO, ::MIME"text/plain", t::ParallelTree)
139
@nospecialize t # reduce precompilation time
140
141
l = t.length
142
println(io, '*'^20)
143
println(io, "t.parent_ids[1:l] = $(t.parent_ids[1:l])")
144
println(io, "transpose(t.child_ids[:, 1:l]) = $(transpose(t.child_ids[:, 1:l]))")
145
println(io,
146
"transpose(t.neighbor_ids[:, 1:l]) = $(transpose(t.neighbor_ids[:, 1:l]))")
147
println(io, "t.levels[1:l] = $(t.levels[1:l])")
148
println(io,
149
"transpose(t.coordinates[:, 1:l]) = $(transpose(t.coordinates[:, 1:l]))")
150
println(io, "t.original_cell_ids[1:l] = $(t.original_cell_ids[1:l])")
151
println(io, "t.mpi_ranks[1:l] = $(t.mpi_ranks[1:l])")
152
println(io, "t.capacity = $(t.capacity)")
153
println(io, "t.length = $(t.length)")
154
println(io, "t.center_level_0 = $(t.center_level_0)")
155
println(io, "t.length_level_0 = $(t.length_level_0)")
156
println(io, '*'^20)
157
return nothing
158
end
159
160
# Check if cell is own cell, i.e., belongs to this MPI rank
161
is_own_cell(t::ParallelTree, cell_id) = t.mpi_ranks[cell_id] == mpi_rank()
162
163
# Return an array with the ids of all leaf cells for a given rank
164
leaf_cells_by_rank(t::ParallelTree, rank) =
165
filter_leaf_cells(t) do cell_id
166
return t.mpi_ranks[cell_id] == rank
167
end
168
169
# Return an array with the ids of all local leaf cells
170
local_leaf_cells(t::ParallelTree) = leaf_cells_by_rank(t, mpi_rank())
171
172
# Set information for child cell `child_id` based on parent cell `cell_id` (except neighbors)
173
function init_child!(t::ParallelTree, cell_id, child, child_id)
174
t.parent_ids[child_id] = cell_id
175
t.child_ids[child, cell_id] = child_id
176
t.child_ids[:, child_id] .= 0
177
t.levels[child_id] = t.levels[cell_id] + 1
178
set_cell_coordinates!(t,
179
child_coordinates(t, cell_coordinates(t, cell_id),
180
length_at_cell(t, cell_id), child),
181
child_id)
182
t.original_cell_ids[child_id] = 0
183
t.mpi_ranks[child_id] = t.mpi_ranks[cell_id]
184
185
return nothing
186
end
187
188
# Reset range of cells to values that are prone to cause errors as soon as they are used.
189
#
190
# Rationale: If an invalid cell is accidentally used, we want to know it as soon as possible.
191
function invalidate!(t::ParallelTree{NDIMS, RealT},
192
first::Int, last::Int) where {NDIMS, RealT <: Real}
193
@assert first > 0
194
@assert last <= t.capacity + 1
195
196
# Integer values are set to smallest negative value, floating point values to NaN
197
t.parent_ids[first:last] .= typemin(Int)
198
t.child_ids[:, first:last] .= typemin(Int)
199
t.neighbor_ids[:, first:last] .= typemin(Int)
200
t.levels[first:last] .= typemin(Int)
201
t.coordinates[:, first:last] .= convert(RealT, NaN) # `NaN` is of type Float64
202
t.original_cell_ids[first:last] .= typemin(Int)
203
t.mpi_ranks[first:last] .= typemin(Int)
204
205
return nothing
206
end
207
208
# Raw copy operation for ranges of cells.
209
#
210
# This method is used by the higher-level copy operations for AbstractContainer
211
function raw_copy!(target::ParallelTree, source::ParallelTree, first::Int, last::Int,
212
destination::Int)
213
copy_data!(target.parent_ids, source.parent_ids, first, last, destination)
214
copy_data!(target.child_ids, source.child_ids, first, last, destination,
215
n_children_per_cell(target))
216
copy_data!(target.neighbor_ids, source.neighbor_ids, first, last,
217
destination, n_directions(target))
218
copy_data!(target.levels, source.levels, first, last, destination)
219
copy_data!(target.coordinates, source.coordinates, first, last, destination,
220
ndims(target))
221
copy_data!(target.original_cell_ids, source.original_cell_ids, first, last,
222
destination)
223
return copy_data!(target.mpi_ranks, source.mpi_ranks, first, last, destination)
224
end
225
226
# Reset data structures by recreating all internal storage containers and invalidating all elements
227
function reset_data_structures!(t::ParallelTree{NDIMS, RealT}) where {NDIMS,
228
RealT <: Real}
229
t.parent_ids = Vector{Int}(undef, t.capacity + 1)
230
t.child_ids = Matrix{Int}(undef, 2^NDIMS, t.capacity + 1)
231
t.neighbor_ids = Matrix{Int}(undef, 2 * NDIMS, t.capacity + 1)
232
t.levels = Vector{Int}(undef, t.capacity + 1)
233
t.coordinates = Matrix{RealT}(undef, NDIMS, t.capacity + 1)
234
t.original_cell_ids = Vector{Int}(undef, t.capacity + 1)
235
t.mpi_ranks = Vector{Int}(undef, t.capacity + 1)
236
237
return invalidate!(t, 1, capacity(t) + 1)
238
end
239
end # @muladd
240
241