Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
trixi-framework
GitHub Repository: trixi-framework/Trixi.jl
Path: blob/main/src/meshes/abstract_tree.jl
2055 views
1
# By default, Julia/LLVM does not use fused multiply-add operations (FMAs).
2
# Since these FMAs can increase the performance of many numerical algorithms,
3
# we need to opt-in explicitly.
4
# See https://ranocha.de/blog/Optimizing_EC_Trixi for further details.
5
@muladd begin
6
#! format: noindent
7
8
abstract type AbstractTree{NDIMS} <: AbstractContainer end
9
10
# Type traits to obtain dimension
11
@inline Base.ndims(::AbstractTree{NDIMS}) where {NDIMS} = NDIMS
12
13
# Auxiliary methods to allow semantic queries on the tree
14
# Check whether cell has parent cell
15
has_parent(t::AbstractTree, cell_id::Int) = t.parent_ids[cell_id] > 0
16
17
# Count number of children for a given cell
18
function n_children(t::AbstractTree, cell_id::Int)
19
count(x -> (x > 0), @view t.child_ids[:, cell_id])
20
end
21
22
# Check whether cell has any child cell
23
has_children(t::AbstractTree, cell_id::Int) = n_children(t, cell_id) > 0
24
25
# Check whether cell is leaf cell
26
is_leaf(t::AbstractTree, cell_id::Int) = !has_children(t, cell_id)
27
28
# Check whether cell has specific child cell
29
has_child(t::AbstractTree, cell_id::Int, child::Int) = t.child_ids[child, cell_id] > 0
30
31
# Check if cell has a neighbor at the same refinement level in the given direction
32
function has_neighbor(t::AbstractTree, cell_id::Int, direction::Int)
33
t.neighbor_ids[direction, cell_id] > 0
34
end
35
36
# Check if cell has a coarse neighbor, i.e., with one refinement level lower
37
function has_coarse_neighbor(t::AbstractTree, cell_id::Int, direction::Int)
38
return has_parent(t, cell_id) && has_neighbor(t, t.parent_ids[cell_id], direction)
39
end
40
41
# Check if cell has any neighbor (same-level or lower-level)
42
function has_any_neighbor(t::AbstractTree, cell_id::Int, direction::Int)
43
return has_neighbor(t, cell_id, direction) ||
44
has_coarse_neighbor(t, cell_id, direction)
45
end
46
47
# Check if cell is own cell, i.e., belongs to this MPI rank
48
is_own_cell(t::AbstractTree, cell_id) = true
49
50
# Return cell length for a given level
51
# We know that the `level` is at least 0, so we can safely use `1 << level`
52
# instead of `2^level` to calculate the cell length.
53
length_at_level(t::AbstractTree, level::Int) = t.length_level_0 / (1 << level)
54
55
# Return cell length for a given cell
56
length_at_cell(t::AbstractTree, cell_id::Int) = length_at_level(t, t.levels[cell_id])
57
58
# Return minimum level of any leaf cell
59
minimum_level(t::AbstractTree) = minimum(t.levels[leaf_cells(t)])
60
61
# Return maximum level of any leaf cell
62
maximum_level(t::AbstractTree) = maximum(t.levels[leaf_cells(t)])
63
64
# Check if tree is periodic
65
isperiodic(t::AbstractTree) = all(t.periodicity)
66
isperiodic(t::AbstractTree, dimension) = t.periodicity[dimension]
67
68
# Auxiliary methods for often-required calculations
69
# Number of potential child cells
70
n_children_per_cell(::AbstractTree{NDIMS}) where {NDIMS} = 2^NDIMS
71
72
# Number of directions
73
#
74
# Directions are indicated by numbers from 1 to 2*ndims:
75
# 1 -> -x
76
# 2 -> +x
77
# 3 -> -y
78
# 4 -> +y
79
# 5 -> -z
80
# 6 -> +z
81
@inline n_directions(::AbstractTree{NDIMS}) where {NDIMS} = 2 * NDIMS
82
# TODO: Taal performance, 1:n_directions(tree) vs. Base.OneTo(n_directions(tree)) vs. SOneTo(n_directions(tree))
83
"""
84
eachdirection(tree::AbstractTree)
85
86
Return an iterator over the indices that specify the location in relevant data structures
87
for the directions in `AbstractTree`.
88
In particular, not the directions themselves are returned.
89
"""
90
@inline eachdirection(tree::AbstractTree) = Base.OneTo(n_directions(tree))
91
92
# For a given direction, return its opposite direction
93
#
94
# dir -> opp
95
# 1 -> 2
96
# 2 -> 1
97
# 3 -> 4
98
# 4 -> 3
99
# 5 -> 6
100
# 6 -> 5
101
opposite_direction(direction::Int) = direction + 1 - 2 * ((direction + 1) % 2)
102
103
# For a given child position (from 1 to 8) and dimension (from 1 to 3),
104
# calculate a child cell's position relative to its parent cell.
105
#
106
# Essentially calculates the following
107
# dim=1 dim=2 dim=3
108
# child x y z
109
# 1 - - -
110
# 2 + - -
111
# 3 - + -
112
# 4 + + -
113
# 5 - - +
114
# 6 + - +
115
# 7 - + +
116
# 8 + + +
117
# child_sign(child::Int, dim::Int) = 1 - 2 * (div(child + 2^(dim - 1) - 1, 2^(dim-1)) % 2)
118
# Since we use only a fixed number of dimensions, we use a lookup table for improved performance.
119
const _child_signs = [-1 -1 -1;
120
+1 -1 -1;
121
-1 +1 -1;
122
+1 +1 -1;
123
-1 -1 +1;
124
+1 -1 +1;
125
-1 +1 +1;
126
+1 +1 +1]
127
child_sign(child::Int, dim::Int) = _child_signs[child, dim]
128
129
# For each child position (1 to 8) and a given direction (from 1 to 6), return
130
# neighboring child position.
131
const _adjacent_child_ids = [2 2 3 3 5 5;
132
1 1 4 4 6 6;
133
4 4 1 1 7 7;
134
3 3 2 2 8 8;
135
6 6 7 7 1 1;
136
5 5 8 8 2 2;
137
8 8 5 5 3 3;
138
7 7 6 6 4 4]
139
adjacent_child(child::Int, direction::Int) = _adjacent_child_ids[child, direction]
140
141
# For each child position (1 to 8) and a given direction (from 1 to 6), return
142
# if neighbor is a sibling
143
function has_sibling(child::Int, direction::Int)
144
return (child_sign(child, div(direction + 1, 2)) * (-1)^(direction - 1)) > 0
145
end
146
147
# Obtain leaf cells that fulfill a given criterion.
148
#
149
# The function `f` is passed the cell id of each leaf cell
150
# as an argument.
151
function filter_leaf_cells(f, t::AbstractTree)
152
filtered = Vector{Int}(undef, length(t))
153
count = 0
154
for cell_id in 1:length(t)
155
if is_leaf(t, cell_id) && f(cell_id)
156
count += 1
157
filtered[count] = cell_id
158
end
159
end
160
161
return filtered[1:count]
162
end
163
164
# Return an array with the ids of all leaf cells
165
leaf_cells(t::AbstractTree) = filter_leaf_cells((cell_id) -> true, t)
166
167
# Return an array with the ids of all leaf cells for a given rank
168
leaf_cells_by_rank(t::AbstractTree, rank) = leaf_cells(t)
169
170
# Return an array with the ids of all local leaf cells
171
local_leaf_cells(t::AbstractTree) = leaf_cells(t)
172
173
# Count the number of leaf cells.
174
count_leaf_cells(t::AbstractTree) = length(leaf_cells(t))
175
176
@inline function cell_coordinates(t::AbstractTree{NDIMS}, cell) where {NDIMS}
177
SVector(ntuple(d -> t.coordinates[d, cell], Val(NDIMS)))
178
end
179
180
@inline function set_cell_coordinates!(t::AbstractTree{NDIMS}, coords,
181
cell) where {NDIMS}
182
for d in 1:NDIMS
183
t.coordinates[d, cell] = coords[d]
184
end
185
end
186
187
# Determine if point is located inside cell
188
@inline function is_point_in_cell(t::AbstractTree, point_coordinates, cell_id)
189
cell_length = length_at_cell(t, cell_id)
190
cell_coordinates_ = cell_coordinates(t, cell_id)
191
min_coordinates = cell_coordinates_ .- cell_length / 2
192
max_coordinates = cell_coordinates_ .+ cell_length / 2
193
194
return all(min_coordinates .<= point_coordinates .<= max_coordinates)
195
end
196
197
# Store cell id in each cell to use for post-AMR analysis
198
function reset_original_cell_ids!(t::AbstractTree)
199
t.original_cell_ids[1:length(t)] .= 1:length(t)
200
end
201
202
# Efficiently perform uniform refinement up to a given level (works only on mesh with a single cell)
203
function refine_uniformly!(t::AbstractTree, max_level)
204
@assert length(t)==1 "efficient uniform refinement only works for a newly created tree"
205
@assert max_level>=0 "the uniform refinement level must be non-zero"
206
207
# Calculate size of final tree and resize tree
208
total_length = 1
209
for level in 1:max_level
210
total_length += n_children_per_cell(t)^level
211
end
212
resize!(t, total_length)
213
214
# Traverse tree to set parent-child relationships
215
init_children!(t, 1, max_level)
216
217
# Set all neighbor relationships
218
init_neighbors!(t, max_level)
219
end
220
221
# Recursively initialize children up to level `max_level` in depth-first ordering, starting with
222
# cell `cell_id` and set all information except neighbor relations (see `init_neighbors!`).
223
#
224
# Return the number of offspring of the initialized cell plus one
225
function init_children!(t::AbstractTree, cell_id, max_level)
226
# Stop recursion if max_level has been reached
227
if t.levels[cell_id] >= max_level
228
return 1
229
else
230
# Initialize each child cell, counting the total number of offspring
231
n_offspring = 0
232
for child in 1:n_children_per_cell(t)
233
# Get cell id of child
234
child_id = cell_id + 1 + n_offspring
235
236
# Initialize child cell (except neighbors)
237
init_child!(t, cell_id, child, child_id)
238
239
# Recursively initialize child cell
240
n_offspring += init_children!(t, child_id, max_level)
241
end
242
243
return n_offspring + 1
244
end
245
end
246
247
# Iteratively set all neighbor relations, starting at an initialized level 0 cell. Assume that
248
# parent-child relations have already been initialized (see `init_children!`).
249
function init_neighbors!(t::AbstractTree, max_level = maximum_level(t))
250
@assert all(n >= 0 for n in t.neighbor_ids[:, 1]) "level 0 cell neighbors must be initialized"
251
252
# Initialize neighbors level by level
253
for level in 1:max_level
254
# Walk entire tree, starting from level 0 cell
255
for cell_id in 1:length(t)
256
# Skip cells whose immediate children are already initialized *or* whose level is too high for this round
257
if t.levels[cell_id] != level - 1
258
continue
259
end
260
261
# Iterate over children and set neighbor information
262
for child in 1:n_children_per_cell(t)
263
child_id = t.child_ids[child, cell_id]
264
init_child_neighbors!(t, cell_id, child, child_id)
265
end
266
end
267
end
268
269
return nothing
270
end
271
272
# Initialize the neighbors of child cell `child_id` based on parent cell `cell_id`
273
function init_child_neighbors!(t::AbstractTree, cell_id, child, child_id)
274
t.neighbor_ids[:, child_id] .= zero(eltype(t.neighbor_ids))
275
for direction in eachdirection(t)
276
# If neighbor is a sibling, establish one-sided connectivity
277
# Note: two-sided is not necessary, as each sibling will do this
278
if has_sibling(child, direction)
279
adjacent = adjacent_child(child, direction)
280
neighbor_id = t.child_ids[adjacent, cell_id]
281
282
t.neighbor_ids[direction, child_id] = neighbor_id
283
continue
284
end
285
286
# Skip if original cell does have no neighbor in direction
287
if !has_neighbor(t, cell_id, direction)
288
continue
289
end
290
291
# Otherwise, check if neighbor has children - if not, skip again
292
neighbor_id = t.neighbor_ids[direction, cell_id]
293
if !has_children(t, neighbor_id)
294
continue
295
end
296
297
# Check if neighbor has corresponding child and if yes, establish connectivity
298
adjacent = adjacent_child(child, direction)
299
if has_child(t, neighbor_id, adjacent)
300
neighbor_child_id = t.child_ids[adjacent, neighbor_id]
301
opposite = opposite_direction(direction)
302
303
t.neighbor_ids[direction, child_id] = neighbor_child_id
304
t.neighbor_ids[opposite, neighbor_child_id] = child_id
305
end
306
end
307
308
return nothing
309
end
310
311
# Refine given cells without rebalancing tree.
312
#
313
# Note: After a call to this method the tree may be unbalanced!
314
function refine_unbalanced!(t::AbstractTree, cell_ids,
315
sorted_unique_cell_ids = sort(unique(cell_ids)))
316
# Store actual ids refined cells (shifted due to previous insertions)
317
refined = zeros(Int, length(cell_ids))
318
319
# Loop over all cells that are to be refined
320
for (count, original_cell_id) in enumerate(sorted_unique_cell_ids)
321
# Determine actual cell id, taking into account previously inserted cells
322
n_children = n_children_per_cell(t)
323
cell_id = original_cell_id + (count - 1) * n_children
324
refined[count] = cell_id
325
326
@assert !has_children(t, cell_id) "Non-leaf cell $cell_id cannot be refined"
327
328
# Insert new cells directly behind parent (depth-first)
329
insert!(t, cell_id + 1, n_children)
330
331
# Flip sign of refined cell such that we can easily find it later
332
t.original_cell_ids[cell_id] = -t.original_cell_ids[cell_id]
333
334
# Initialize child cells (except neighbors)
335
for child in 1:n_children
336
child_id = cell_id + child
337
init_child!(t, cell_id, child, child_id)
338
end
339
340
# Initialize child cells (only neighbors)
341
# This separate loop is required since init_child_neighbors requires initialized parent-child
342
# relationships
343
for child in 1:n_children
344
child_id = cell_id + child
345
init_child_neighbors!(t, cell_id, child, child_id)
346
end
347
end
348
349
return refined
350
end
351
352
# Refine entire tree by one level
353
function refine!(t::AbstractTree)
354
cells = @trixi_timeit timer() "collect all leaf cells" leaf_cells(t)
355
@trixi_timeit timer() "refine!" refine!(t, cells, cells)
356
end
357
358
# Refine given cells and rebalance tree.
359
#
360
# Note 1: Rebalancing is iterative, i.e., neighboring cells are refined if
361
# otherwise the 2:1 rule would be violated, which can cause more
362
# refinements.
363
# Note 2: Rebalancing currently only considers *Cartesian* neighbors, not diagonal neighbors!
364
function refine!(t::AbstractTree, cell_ids,
365
sorted_unique_cell_ids = sort(unique(cell_ids)))
366
# Reset original cell ids such that each cell knows its current id
367
reset_original_cell_ids!(t)
368
369
# Refine all requested cells
370
refined = @trixi_timeit timer() "refine_unbalanced!" refine_unbalanced!(t, cell_ids,
371
sorted_unique_cell_ids)
372
refinement_count = length(refined)
373
374
# Iteratively rebalance the tree until it does not change anymore
375
while length(refined) > 0
376
refined = @trixi_timeit timer() "rebalance!" rebalance!(t, refined)
377
refinement_count += length(refined)
378
end
379
380
# Determine list of *original* cell ids that were refined
381
# Note: original_cell_ids contains the cell_id *before* refinement. At
382
# refinement, the refined cell's original_cell_ids value has its sign flipped
383
# to easily find it now.
384
refined_original_cells = @views(-t.original_cell_ids[1:length(t)][t.original_cell_ids[1:length(t)] .< 0])
385
386
# Check if count of refinement cells matches information in original_cell_ids
387
@assert refinement_count==length(refined_original_cells) ("Mismatch in number of refined cells")
388
389
return refined_original_cells
390
end
391
392
@inline function coordinates_min_max_check(coordinates_min, coordinates_max)
393
for dim in eachindex(coordinates_min)
394
@assert coordinates_min[dim]<coordinates_max[dim] "coordinates_min[$dim] must be smaller than coordinates_max[$dim]!"
395
end
396
end
397
# For the p4est and the t8code mesh we allow `coordinates_min` and `coordinates_max` to be `nothing`.
398
# This corresponds to meshes constructed from analytic mapping functions.
399
coordinates_min_max_check(::Nothing, ::Nothing) = nothing
400
401
# Refine all leaf cells with coordinates in a given rectangular box
402
function refine_box!(t::AbstractTree{NDIMS},
403
coordinates_min,
404
coordinates_max) where {NDIMS}
405
coordinates_min_max_check(coordinates_min, coordinates_max)
406
407
# Find all leaf cells within box
408
cells = filter_leaf_cells(t) do cell_id
409
return (all(coordinates_min .< cell_coordinates(t, cell_id)) &&
410
all(coordinates_max .> cell_coordinates(t, cell_id)))
411
end
412
413
# Refine cells
414
refine!(t, cells)
415
end
416
417
# Convenience method for 1D (arguments are no arrays)
418
function refine_box!(t::AbstractTree{1},
419
coordinates_min::Real,
420
coordinates_max::Real)
421
return refine_box!(t, [coordinates_min], [coordinates_max])
422
end
423
424
# Refine all leaf cells with coordinates in a given sphere
425
function refine_sphere!(t::AbstractTree{NDIMS}, center::SVector{NDIMS},
426
radius) where {NDIMS}
427
@assert radius>=0 "Radius must be positive."
428
429
# Find all leaf cells within sphere
430
cells = filter_leaf_cells(t) do cell_id
431
return sum(abs2, cell_coordinates(t, cell_id) - center) < radius^2
432
end
433
434
# Refine cells
435
refine!(t, cells)
436
end
437
438
# Convenience function to allow passing center as a tuple
439
function refine_sphere!(t::AbstractTree{NDIMS}, center::NTuple{NDIMS},
440
radius) where {NDIMS}
441
refine_sphere!(t, SVector(center), radius)
442
end
443
444
# For the given cell ids, check if neighbors need to be refined to restore a rebalanced tree.
445
#
446
# Note 1: Rebalancing currently only considers *Cartesian* neighbors, not diagonal neighbors!
447
# Note 2: The current algorithm assumes that a previous refinement step has
448
# created level differences of at most 2. That is, before the previous
449
# refinement step, the tree was balanced.
450
function rebalance!(t::AbstractTree, refined_cell_ids)
451
# Create buffer for newly refined cells
452
to_refine = zeros(Int, n_directions(t) * length(refined_cell_ids))
453
count = 0
454
455
# Iterate over cell ids that have previously been refined
456
for cell_id in refined_cell_ids
457
# Go over all potential neighbors of child cell
458
for direction in eachdirection(t)
459
# Continue if refined cell has a neighbor in that direction
460
if has_neighbor(t, cell_id, direction)
461
continue
462
end
463
464
# Continue if refined cell has no coarse neighbor, since that would
465
# mean it there is no neighbor in that direction at all (domain
466
# boundary)
467
if !has_coarse_neighbor(t, cell_id, direction)
468
continue
469
end
470
471
# Otherwise, the coarse neighbor exists and is not refined, thus it must
472
# be marked for refinement
473
coarse_neighbor_id = t.neighbor_ids[direction, t.parent_ids[cell_id]]
474
count += 1
475
to_refine[count] = coarse_neighbor_id
476
end
477
end
478
479
# Finally, refine all marked cells...
480
refined = refine_unbalanced!(t, unique(to_refine[1:count]))
481
482
# ...and return list of refined cells
483
return refined
484
end
485
486
# Refine given cells without rebalancing tree.
487
#
488
# Note: After a call to this method the tree may be unbalanced!
489
# function refine_unbalanced!(t::AbstractTree, cell_ids) end
490
491
# Wrap single-cell refinements such that `sort(...)` does not complain
492
refine_unbalanced!(t::AbstractTree, cell_id::Int) = refine_unbalanced!(t, [cell_id])
493
494
# Coarsen entire tree by one level
495
function coarsen!(t::AbstractTree)
496
# Special case: if there is only one cell (root), there is nothing to do
497
if length(t) == 1
498
return Int[]
499
end
500
501
# Get list of unique parent ids for all leaf cells
502
parent_ids = unique(t.parent_ids[leaf_cells(t)])
503
coarsen!(t, parent_ids)
504
end
505
506
# Coarsen given *parent* cells (= these cells must have children who are all
507
# leaf cells) while retaining a balanced tree.
508
#
509
# A cell to be coarsened might cause an unbalanced tree if the neighboring cell
510
# was already refined. Since it is generally not desired that cells are
511
# coarsened without specifically asking for it, these cells will then *not* be
512
# coarsened.
513
function coarsen!(t::AbstractTree, cell_ids::AbstractArray{Int})
514
# Return early if array is empty
515
if length(cell_ids) == 0
516
return Int[]
517
end
518
519
# Reset original cell ids such that each cell knows its current id
520
reset_original_cell_ids!(t)
521
522
# To maximize the number of cells that may be coarsened, start with the cells at the highest level
523
sorted_by_level = sort(cell_ids, by = i -> t.levels[i])
524
525
# Keep track of number of cells that were actually coarsened
526
n_coarsened = 0
527
528
# Local function to adjust cell ids after some cells have been removed
529
function adjust_cell_ids!(cell_ids, coarsened_cell_id, count)
530
for (id, cell_id) in enumerate(cell_ids)
531
if cell_id > coarsened_cell_id
532
cell_ids[id] = cell_id - count
533
end
534
end
535
end
536
537
# Iterate backwards over cells to coarsen
538
while true
539
# Retrieve next cell or quit
540
if length(sorted_by_level) > 0
541
coarse_cell_id = pop!(sorted_by_level)
542
else
543
break
544
end
545
546
# Ensure that cell has children (violation is an error)
547
if !has_children(t, coarse_cell_id)
548
error("cell is leaf and cannot be coarsened to: $coarse_cell_id")
549
end
550
551
# Ensure that all child cells are leaf cells (violation is an error)
552
for child in 1:n_children_per_cell(t)
553
if has_child(t, coarse_cell_id, child)
554
if !is_leaf(t, t.child_ids[child, coarse_cell_id])
555
error("cell $coarse_cell_id has child cell at position $child that is not a leaf cell")
556
end
557
end
558
end
559
560
# Check if coarse cell has refined neighbors that would prevent coarsening
561
skip = false
562
# Iterate over all children (which are to be removed)
563
for child in 1:n_children_per_cell(t)
564
# Continue if child does not exist
565
if !has_child(t, coarse_cell_id, child)
566
continue
567
end
568
child_id = t.child_ids[child, coarse_cell_id]
569
570
# Go over all neighbors of child cell. If it has a neighbor that is *not*
571
# a sibling and that is not a leaf cell, we cannot coarsen its parent
572
# without creating an unbalanced tree.
573
for direction in eachdirection(t)
574
# Continue if neighbor would be a sibling
575
if has_sibling(child, direction)
576
continue
577
end
578
579
# Continue if child cell has no neighbor in that direction
580
if !has_neighbor(t, child_id, direction)
581
continue
582
end
583
neighbor_id = t.neighbor_ids[direction, child_id]
584
585
if !has_children(t, neighbor_id)
586
continue
587
end
588
589
# If neighbor is not a sibling, is existing, and has children, do not coarsen
590
skip = true
591
break
592
end
593
end
594
# Skip if a neighboring cell prevents coarsening
595
if skip
596
continue
597
end
598
599
# Flip sign of cell to be coarsened to such that we can easily find it
600
t.original_cell_ids[coarse_cell_id] = -t.original_cell_ids[coarse_cell_id]
601
602
# If a coarse cell has children that are all leaf cells, they must follow
603
# immediately due to depth-first ordering of the tree
604
count = n_children(t, coarse_cell_id)
605
@assert count==n_children_per_cell(t) "cell $coarse_cell_id does not have all child cells"
606
remove_shift!(t, coarse_cell_id + 1, coarse_cell_id + count)
607
608
# Take into account shifts in tree that alters cell ids
609
adjust_cell_ids!(sorted_by_level, coarse_cell_id, count)
610
611
# Keep track of number of coarsened cells
612
n_coarsened += 1
613
end
614
615
# Determine list of *original* cell ids that were coarsened to
616
# Note: original_cell_ids contains the cell_id *before* coarsening. At
617
# coarsening, the coarsened parent cell's original_cell_ids value has its sign flipped
618
# to easily find it now.
619
@views coarsened_original_cells = (-t.original_cell_ids[1:length(t)][t.original_cell_ids[1:length(t)] .< 0])
620
621
# Check if count of coarsened cells matches information in original_cell_ids
622
@assert n_coarsened==length(coarsened_original_cells) ("Mismatch in number of coarsened cells")
623
624
return coarsened_original_cells
625
end
626
627
# Wrap single-cell coarsening such that `sort(...)` does not complain
628
coarsen!(t::AbstractTree, cell_id::Int) = coarsen!(t::AbstractTree, [cell_id])
629
630
# Coarsen all viable parent cells with coordinates in a given rectangular box
631
function coarsen_box!(t::AbstractTree{NDIMS},
632
coordinates_min,
633
coordinates_max) where {NDIMS}
634
for dim in 1:NDIMS
635
@assert coordinates_min[dim]<coordinates_max[dim] "Minimum coordinates are not minimum."
636
end
637
638
# Find all leaf cells within box
639
leaves = filter_leaf_cells(t) do cell_id
640
return (all(coordinates_min .< cell_coordinates(t, cell_id)) &&
641
all(coordinates_max .> cell_coordinates(t, cell_id)))
642
end
643
644
# Get list of unique parent ids for all leaf cells
645
parent_ids = unique(t.parent_ids[leaves])
646
647
# Filter parent ids to be within box
648
parents = filter(parent_ids) do cell_id
649
return (all(coordinates_min .< cell_coordinates(t, cell_id)) &&
650
all(coordinates_max .> cell_coordinates(t, cell_id)))
651
end
652
653
# Coarsen cells
654
coarsen!(t, parents)
655
end
656
657
# Convenience method for 1D (arguments are no arrays)
658
function coarsen_box!(t::AbstractTree{1},
659
coordinates_min::Real,
660
coordinates_max::Real)
661
return coarsen_box!(t, [coordinates_min], [coordinates_max])
662
end
663
664
# Return coordinates of a child cell based on its relative position to the parent.
665
function child_coordinates(::AbstractTree{NDIMS}, parent_coordinates,
666
parent_length::Number, child::Int) where {NDIMS}
667
# Calculate length of child cells
668
child_length = parent_length / 2
669
return SVector(ntuple(d -> parent_coordinates[d] +
670
child_sign(child, d) * child_length / 2, Val(NDIMS)))
671
end
672
673
# Reset range of cells to values that are prone to cause errors as soon as they are used.
674
#
675
# Rationale: If an invalid cell is accidentally used, we want to know it as soon as possible.
676
# function invalidate!(t::AbstractTree, first::Int, last::Int) end
677
invalidate!(t::AbstractTree, id::Int) = invalidate!(t, id, id)
678
invalidate!(t::AbstractTree) = invalidate!(t, 1, length(t))
679
680
# Delete connectivity with parents/children/neighbors before cells are erased
681
function delete_connectivity!(t::AbstractTree, first::Int, last::Int)
682
@assert first > 0
683
@assert first <= last
684
@assert last <= t.capacity + 1
685
686
# Iterate over all cells
687
for cell_id in first:last
688
# Delete connectivity from parent cell
689
if has_parent(t, cell_id)
690
parent_id = t.parent_ids[cell_id]
691
for child in 1:n_children_per_cell(t)
692
if t.child_ids[child, parent_id] == cell_id
693
t.child_ids[child, parent_id] = 0
694
break
695
end
696
end
697
end
698
699
# Delete connectivity from child cells
700
for child in 1:n_children_per_cell(t)
701
if has_child(t, cell_id, child)
702
t.parent_ids[t._child_ids[child, cell_id]] = 0
703
end
704
end
705
706
# Delete connectivity from neighboring cells
707
for direction in eachdirection(t)
708
if has_neighbor(t, cell_id, direction)
709
t.neighbor_ids[opposite_direction(direction), t.neighbor_ids[direction, cell_id]] = 0
710
end
711
end
712
end
713
end
714
715
# Move connectivity with parents/children/neighbors after cells have been moved
716
function move_connectivity!(t::AbstractTree, first::Int, last::Int, destination::Int)
717
@assert first > 0
718
@assert first <= last
719
@assert last <= t.capacity + 1
720
@assert destination > 0
721
@assert destination <= t.capacity + 1
722
723
# Strategy
724
# 1) Loop over moved cells (at target location)
725
# 2) Check if parent/children/neighbors connections are to a cell that was moved
726
# a) if cell was moved: apply offset to current cell
727
# b) if cell was not moved: go to connected cell and update connectivity there
728
729
offset = destination - first
730
has_moved(n) = (first <= n <= last)
731
732
for source in first:last
733
target = source + offset
734
735
# Update parent
736
if has_parent(t, target)
737
# Get parent cell
738
parent_id = t.parent_ids[target]
739
if has_moved(parent_id)
740
# If parent itself was moved, just update parent id accordingly
741
t.parent_ids[target] += offset
742
else
743
# If parent was not moved, update its corresponding child id
744
for child in 1:n_children_per_cell(t)
745
if t.child_ids[child, parent_id] == source
746
t.child_ids[child, parent_id] = target
747
end
748
end
749
end
750
end
751
752
# Update children
753
for child in 1:n_children_per_cell(t)
754
if has_child(t, target, child)
755
# Get child cell
756
child_id = t.child_ids[child, target]
757
if has_moved(child_id)
758
# If child itself was moved, just update child id accordingly
759
t.child_ids[child, target] += offset
760
else
761
# If child was not moved, update its parent id
762
t.parent_ids[child_id] = target
763
end
764
end
765
end
766
767
# Update neighbors
768
for direction in eachdirection(t)
769
if has_neighbor(t, target, direction)
770
# Get neighbor cell
771
neighbor_id = t.neighbor_ids[direction, target]
772
if has_moved(neighbor_id)
773
# If neighbor itself was moved, just update neighbor id accordingly
774
t.neighbor_ids[direction, target] += offset
775
else
776
# If neighbor was not moved, update its opposing neighbor id
777
t.neighbor_ids[opposite_direction(direction), neighbor_id] = target
778
end
779
end
780
end
781
end
782
end
783
784
# Raw copy operation for ranges of cells.
785
#
786
# This method is used by the higher-level copy operations for AbstractContainer
787
# function raw_copy!(target::AbstractTree, source::AbstractTree, first::Int, last::Int, destination::Int) end
788
789
# Reset data structures by recreating all internal storage containers and invalidating all elements
790
# function reset_data_structures!(t::AbstractTree{NDIMS}) where NDIMS end
791
792
end # @muladd
793
794