Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
trixi-framework
GitHub Repository: trixi-framework/Trixi.jl
Path: blob/main/src/auxiliary/containers.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 base type - all containers that want to use these features must inherit from it
9
abstract type AbstractContainer end
10
11
# Generic functions for which concrete containers must add implementations
12
function invalidate! end
13
function raw_copy! end
14
function move_connectivity! end
15
function delete_connectivity! end
16
function reset_data_structures! end
17
18
# Auxiliary copy function to copy data between containers
19
function copy_data!(target::AbstractArray, source::AbstractArray,
20
first::Int, last::Int, destination::Int, block_size::Int = 1)
21
count = last - first + 1
22
if destination <= first || destination > last
23
# In this case it is safe to copy forward (left-to-right) without overwriting data
24
for i in 0:(count - 1), j in 1:block_size
25
target[block_size * (destination + i - 1) + j] = source[block_size * (first + i - 1) + j]
26
end
27
else
28
# In this case we need to copy backward (right-to-left) to prevent overwriting data
29
for i in (count - 1):-1:0, j in 1:block_size
30
target[block_size * (destination + i - 1) + j] = source[block_size * (first + i - 1) + j]
31
end
32
end
33
34
return target
35
end
36
37
# Inquire about capacity and size
38
capacity(c::AbstractContainer) = c.capacity
39
Base.length(c::AbstractContainer) = c.length
40
Base.size(c::AbstractContainer) = (length(c),)
41
42
"""
43
resize!(c::AbstractContainer, new_length) -> AbstractContainer
44
45
Resize `c` to contain `new_length` elements. If `new_length` is smaller than the current container
46
length, the first `new_length` elements will be retained. If `new_length` is
47
larger, the new elements are invalidated.
48
"""
49
function Base.resize!(c::AbstractContainer, new_length)
50
@assert new_length>=zero(new_length) "New length must be >= 0"
51
@assert new_length<=capacity(c) "New length would exceed capacity"
52
53
# If new length is greater than current length, append to container.
54
# If new length is less than current length, shrink container.
55
# If new length is equal to current length, do nothing.
56
if new_length > length(c)
57
# First, invalidate range (to be sure that no sensible values are accidentally left there)
58
invalidate!(c, length(c) + 1, new_length)
59
60
# Then, set new container length
61
c.length = new_length
62
elseif new_length < length(c)
63
# Rely on remove&shift to do The Right Thing (`remove_shift!` also updates the length)
64
remove_shift!(c, new_length + 1, length(c))
65
end
66
67
return c
68
end
69
70
# Copy data range from source to target container.
71
#
72
# Calls `raw_copy` internally, which must be implemented for each concrete type
73
# inheriting from AbstractContainer.
74
# TODO: Shall we extend Base.copyto! ?
75
function Trixi.copy!(target::AbstractContainer, source::AbstractContainer,
76
first::Int, last::Int, destination::Int)
77
@assert 1<=first<=length(source) "First cell out of range"
78
@assert 1<=last<=length(source) "Last cell out of range"
79
@assert 1<=destination<=length(target) "Destination out of range"
80
@assert destination + (last - first)<=length(target) "Target range out of bounds"
81
82
# Return if copy would be a no-op
83
if last < first || (source === target && first == destination)
84
return target
85
end
86
87
raw_copy!(target, source, first, last, destination)
88
89
return target
90
end
91
92
# Convenience method to copy a single element
93
function Trixi.copy!(target::AbstractContainer, source::AbstractContainer, from::Int,
94
destination::Int)
95
Trixi.copy!(target, source, from, from, destination)
96
end
97
98
# Convenience method for copies within a single container
99
function Trixi.copy!(c::AbstractContainer, first::Int, last::Int, destination::Int)
100
Trixi.copy!(c, c, first, last, destination)
101
end
102
103
# Convenience method for copying a single element within a single container
104
function Trixi.copy!(c::AbstractContainer, from::Int, destination::Int)
105
Trixi.copy!(c, c, from, from, destination)
106
end
107
108
# Move elements in a way that preserves connectivity.
109
function move!(c::AbstractContainer, first::Int, last::Int, destination::Int)
110
@assert 1<=first<=length(c) "First cell $first out of range"
111
@assert 1<=last<=length(c) "Last cell $last out of range"
112
@assert 1<=destination<=length(c) "Destination $destination out of range"
113
@assert destination + (last - first)<=length(c) "Target range out of bounds"
114
115
# Return if move would be a no-op
116
if last < first || first == destination
117
return c
118
end
119
120
# Copy cells to new location
121
raw_copy!(c, first, last, destination)
122
123
# Move connectivity
124
move_connectivity!(c, first, last, destination)
125
126
# Invalidate original cell locations (unless they already contain new data due to overlap)
127
# 1) If end of destination range is within original range, shift first_invalid to the right
128
count = last - first + 1
129
first_invalid = (first <= destination + count - 1 <= last) ? destination + count :
130
first
131
# 2) If beginning of destination range is within original range, shift last_invalid to the left
132
last_invalid = (first <= destination <= last) ? destination - 1 : last
133
# 3) Invalidate range
134
invalidate!(c, first_invalid, last_invalid)
135
136
return c
137
end
138
function move!(c::AbstractContainer, from::Int, destination::Int)
139
move!(c, from, from, destination)
140
end
141
142
# Default implementation for moving a single element
143
function move_connectivity!(c::AbstractContainer, from::Int, destination::Int)
144
return move_connectivity!(c, from, from, destination)
145
end
146
147
# Default implementation for invalidating a single element
148
function invalidate!(c::AbstractContainer, id::Int)
149
return invalidate!(c, id, id)
150
end
151
152
# Swap two elements in a container while preserving element connectivity.
153
function swap!(c::AbstractContainer, a::Int, b::Int)
154
@assert 1<=a<=length(c) "a out of range"
155
@assert 1<=b<=length(c) "b out of range"
156
157
# Return if swap would be a no-op
158
if a == b
159
return c
160
end
161
162
# Move a to dummy location
163
raw_copy!(c, a, c.dummy)
164
move_connectivity!(c, a, c.dummy)
165
166
# Move b to a
167
raw_copy!(c, b, a)
168
move_connectivity!(c, b, a)
169
170
# Move from dummy location to b
171
raw_copy!(c, c.dummy, b)
172
move_connectivity!(c, c.dummy, b)
173
174
# Invalidate dummy to be sure
175
invalidate!(c, c.dummy)
176
177
return c
178
end
179
180
# Insert blank elements in container, shifting the following elements back.
181
#
182
# After a call to insert!, the range `position:position + count - 1` will be available for use.
183
# TODO: Shall we extend Base.insert! ?
184
function insert!(c::AbstractContainer, position::Int, count::Int)
185
@assert 1<=position<=length(c)+1 "Insert position out of range"
186
@assert count>=0 "Count must be non-negative"
187
@assert count + length(c)<=capacity(c) "New length would exceed capacity"
188
189
# Return if insertation would be a no-op
190
if count == 0
191
return c
192
end
193
194
# Append and return if insertion is beyond last current element
195
if position == length(c) + 1
196
resize!(c, length(c) + count)
197
return c
198
end
199
200
# Increase length
201
c.length += count
202
203
# Move original cells that currently occupy the insertion region, unless
204
# insert position is one beyond previous length
205
if position <= length(c) - count
206
move!(c, position, length(c) - count, position + count)
207
end
208
209
return c
210
end
211
212
# Erase elements from container, deleting their connectivity and then invalidating their data.
213
# TODO: Shall we extend Base.deleteat! or Base.delete! ?
214
function erase!(c::AbstractContainer, first::Int, last::Int)
215
@assert 1<=first<=length(c) "First cell out of range"
216
@assert 1<=last<=length(c) "Last cell out of range"
217
218
# Return if eraseure would be a no-op
219
if last < first
220
return c
221
end
222
223
# Delete connectivity and invalidate cells
224
delete_connectivity!(c, first, last)
225
invalidate!(c, first, last)
226
227
return c
228
end
229
erase!(c::AbstractContainer, id::Int) = erase!(c, id, id)
230
231
# Remove cells and shift existing cells forward to close the gap
232
function remove_shift!(c::AbstractContainer, first::Int, last::Int)
233
@assert 1<=first<=length(c) "First cell out of range"
234
@assert 1<=last<=length(c) "Last cell out of range"
235
236
# Return if removal would be a no-op
237
if last < first
238
return c
239
end
240
241
# Delete connectivity of cells to be removed
242
delete_connectivity!(c, first, last)
243
244
if last == length(c)
245
# If everything up to the last cell is removed, no shifting is required
246
invalidate!(c, first, last)
247
else
248
# Otherwise, the corresponding cells are moved forward
249
move!(c, last + 1, length(c), first)
250
end
251
252
# Reduce length
253
count = last - first + 1
254
c.length -= count
255
256
return c
257
end
258
remove_shift!(c::AbstractContainer, id::Int) = remove_shift!(c, id, id)
259
260
# Remove cells and fill gap with cells from the end of the container (to reduce copy operations)
261
function remove_fill!(c::AbstractContainer, first::Int, last::Int)
262
@assert 1<=first<=length(c) "First cell out of range"
263
@assert 1<=last<=length(c) "Last cell out of range"
264
265
# Return if removal would be a no-op
266
if last < first
267
return c
268
end
269
270
# Delete connectivity of cells to be removed and then invalidate them
271
delete_connectivity!(c, first, last)
272
invalidate!(c, first, last)
273
274
# Copy cells from end (unless last is already the last cell)
275
count = last - first + 1
276
if last < length(c)
277
move!(c, max(length(c) - count + 1, last + 1), length(c), first)
278
end
279
280
# Reduce length
281
c.length -= count
282
283
return c
284
end
285
286
# Reset container to zero-length and with a new capacity
287
function reset!(c::AbstractContainer, capacity::Int)
288
@assert capacity >= 0
289
290
c.capacity = capacity
291
c.length = 0
292
c.dummy = capacity + 1
293
reset_data_structures!(c)
294
295
return c
296
end
297
298
# Invalidate all elements and set length to zero.
299
function clear!(c::AbstractContainer)
300
invalidate!(c)
301
c.length = 0
302
303
return c
304
end
305
306
# Helpful overloads for `raw_copy`
307
function raw_copy!(c::AbstractContainer, first::Int, last::Int, destination::Int)
308
raw_copy!(c, c, first, last, destination)
309
end
310
function raw_copy!(target::AbstractContainer, source::AbstractContainer, from::Int,
311
destination::Int)
312
raw_copy!(target, source, from, from, destination)
313
end
314
function raw_copy!(c::AbstractContainer, from::Int, destination::Int)
315
raw_copy!(c, c, from, from, destination)
316
end
317
318
# Trixi storage types must implement these two Adapt.jl methods
319
function Adapt.adapt_structure(to, c::AbstractContainer)
320
error("Interface: Must implement Adapt.adapt_structure(to, ::$(typeof(c)))")
321
end
322
323
function Adapt.parent_type(C::Type{<:AbstractContainer})
324
error("Interface: Must implement Adapt.parent_type(::Type{$C}")
325
end
326
327
function Adapt.unwrap_type(C::Type{<:AbstractContainer})
328
return Adapt.unwrap_type(Adapt.parent_type(C))
329
end
330
331
# TODO: Upstream to Adapt
332
"""
333
storage_type(x)
334
335
Return the storage type of `x`, which is a concrete array type, such as `Array`, `CuArray`, or `ROCArray`.
336
"""
337
function storage_type(x)
338
return storage_type(typeof(x))
339
end
340
341
function storage_type(T::Type)
342
error("Interface: Must implement storage_type(::Type{$T}")
343
end
344
345
function storage_type(::Type{<:Array})
346
Array
347
end
348
349
function storage_type(C::Type{<:AbstractContainer})
350
return storage_type(Adapt.unwrap_type(C))
351
end
352
353
# backend handling
354
"""
355
trixi_backend(x)
356
357
Return the computational backend for `x`, which is either a KernelAbstractions backend or `nothing`.
358
If the backend is `nothing`, the default multi-threaded CPU backend is used.
359
"""
360
function trixi_backend(x)
361
if (_PREFERENCE_THREADING === :polyester && LoopVectorization.check_args(x)) ||
362
(_PREFERENCE_THREADING !== :kernelabstractions &&
363
get_backend(x) isa KernelAbstractions.CPU)
364
return nothing
365
end
366
return get_backend(x)
367
end
368
369
# TODO: After https://github.com/SciML/RecursiveArrayTools.jl/pull/455 we need to investigate the right way to handle StaticArray as uEltype for MultiDG.
370
function trixi_backend(x::VectorOfArray)
371
u = parent(x)
372
# FIXME(vchuravy): This is a workaround because KA.get_backend is ambivalent of where a SArray is residing.
373
if eltype(u) <: StaticArrays.StaticArray
374
return nothing
375
end
376
if length(u) == 0
377
error("VectorOfArray is empty, cannot determine backend.")
378
end
379
# Use the backend of the first element in the parent array
380
return get_backend(u[1])
381
end
382
383
# For some storage backends like CUDA.jl, empty arrays do seem to simply be
384
# null pointers which can cause `unsafe_wrap` to fail when calling
385
# Adapt.adapt (ArgumentError, see
386
# https://github.com/JuliaGPU/CUDA.jl/blob/v5.4.2/src/array.jl#L212-L229).
387
# To circumvent this, on length zero arrays this allocates
388
# a separate empty array instead of wrapping.
389
# However, since zero length arrays are not used in calculations,
390
# it should be okay if the underlying storage vectors and wrapped arrays
391
# are not the same as long as they are properly wrapped when `resize!`d etc.
392
function unsafe_wrap_or_alloc(to, vector, size)
393
if length(vector) == 0
394
return similar(vector, size)
395
else
396
return unsafe_wrap(to, pointer(vector), size)
397
end
398
end
399
400
struct TrixiAdaptor{Storage, RealT} end
401
402
"""
403
trixi_adapt(Storage, RealT, x)
404
405
Adapt `x` to the storage type `Storage` and real type `RealT`.
406
"""
407
function trixi_adapt(Storage, RealT, x)
408
adapt(TrixiAdaptor{Storage, RealT}(), x)
409
end
410
411
# Custom rules
412
# 1. handling of StaticArrays
413
function Adapt.adapt_storage(::TrixiAdaptor{<:Any, RealT},
414
x::StaticArrays.StaticArray) where {RealT}
415
StaticArrays.similar_type(x, RealT)(x)
416
end
417
418
# 2. Handling of Arrays
419
function Adapt.adapt_storage(::TrixiAdaptor{Storage, RealT},
420
x::AbstractArray{T}) where {Storage, RealT,
421
T <: AbstractFloat}
422
adapt(Storage{RealT}, x)
423
end
424
425
function Adapt.adapt_storage(::TrixiAdaptor{Storage, RealT},
426
x::AbstractArray{T}) where {Storage, RealT,
427
T <: StaticArrays.StaticArray}
428
adapt(Storage{StaticArrays.similar_type(T, RealT)}, x)
429
end
430
431
# Our threaded cache contains MArray, it is unlikely that we would want to adapt those
432
function Adapt.adapt_storage(::TrixiAdaptor{Storage, RealT},
433
x::Array{T}) where {Storage, RealT,
434
T <: StaticArrays.MArray}
435
adapt(Array{StaticArrays.similar_type(T, RealT)}, x)
436
end
437
438
function Adapt.adapt_storage(::TrixiAdaptor{Storage, RealT},
439
x::AbstractArray) where {Storage, RealT}
440
adapt(Storage, x)
441
end
442
443
# 3. TODO: Should we have a fallback? But that would imply implementing things for NamedTuple again
444
445
function unsafe_wrap_or_alloc(::TrixiAdaptor{Storage}, vec, size) where {Storage}
446
return unsafe_wrap_or_alloc(Storage, vec, size)
447
end
448
end # @muladd
449
450