Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
trixi-framework
GitHub Repository: trixi-framework/Trixi.jl
Path: blob/main/src/auxiliary/containers.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
# 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
return 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
return 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
return 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
return 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
# Insert blank elements in container, shifting the following elements back.
153
#
154
# After a call to insert!, the range `position:position + count - 1` will be available for use.
155
# TODO: Shall we extend Base.insert! ?
156
function insert!(c::AbstractContainer, position::Int, count::Int)
157
@assert 1<=position<=length(c)+1 "Insert position out of range"
158
@assert count>=0 "Count must be non-negative"
159
@assert count + length(c)<=capacity(c) "New length would exceed capacity"
160
161
# Return if insertation would be a no-op
162
if count == 0
163
return c
164
end
165
166
# Append and return if insertion is beyond last current element
167
if position == length(c) + 1
168
resize!(c, length(c) + count)
169
return c
170
end
171
172
# Increase length
173
c.length += count
174
175
# Move original cells that currently occupy the insertion region, unless
176
# insert position is one beyond previous length
177
if position <= length(c) - count
178
move!(c, position, length(c) - count, position + count)
179
end
180
181
return c
182
end
183
184
# Remove cells and shift existing cells forward to close the gap
185
function remove_shift!(c::AbstractContainer, first::Int, last::Int)
186
@assert 1<=first<=length(c) "First cell out of range"
187
@assert 1<=last<=length(c) "Last cell out of range"
188
189
# Return if removal would be a no-op
190
if last < first
191
return c
192
end
193
194
# Delete connectivity of cells to be removed
195
delete_connectivity!(c, first, last)
196
197
if last == length(c)
198
# If everything up to the last cell is removed, no shifting is required
199
invalidate!(c, first, last)
200
else
201
# Otherwise, the corresponding cells are moved forward
202
move!(c, last + 1, length(c), first)
203
end
204
205
# Reduce length
206
count = last - first + 1
207
c.length -= count
208
209
return c
210
end
211
remove_shift!(c::AbstractContainer, id::Int) = remove_shift!(c, id, id)
212
213
# Invalidate all elements and set length to zero.
214
function clear!(c::AbstractContainer)
215
invalidate!(c)
216
c.length = 0
217
218
return c
219
end
220
221
# Helpful overloads for `raw_copy`
222
function raw_copy!(c::AbstractContainer, first::Int, last::Int, destination::Int)
223
return raw_copy!(c, c, first, last, destination)
224
end
225
function raw_copy!(target::AbstractContainer, source::AbstractContainer, from::Int,
226
destination::Int)
227
return raw_copy!(target, source, from, from, destination)
228
end
229
function raw_copy!(c::AbstractContainer, from::Int, destination::Int)
230
return raw_copy!(c, c, from, from, destination)
231
end
232
233
# Trixi storage types must implement these two Adapt.jl methods
234
function Adapt.adapt_structure(to, c::AbstractContainer)
235
error("Interface: Must implement Adapt.adapt_structure(to, ::$(typeof(c)))")
236
return nothing
237
end
238
239
function Adapt.parent_type(C::Type{<:AbstractContainer})
240
error("Interface: Must implement Adapt.parent_type(::Type{$C}")
241
return nothing
242
end
243
244
function Adapt.unwrap_type(C::Type{<:AbstractContainer})
245
return Adapt.unwrap_type(Adapt.parent_type(C))
246
end
247
248
# TODO: Upstream to Adapt
249
"""
250
storage_type(x)
251
252
Return the storage type of `x`, which is a concrete array type, such as `Array`, `CuArray`, or `ROCArray`.
253
"""
254
function storage_type(x)
255
return storage_type(typeof(x))
256
end
257
258
function storage_type(T::Type)
259
error("Interface: Must implement storage_type(::Type{$T}")
260
return nothing
261
end
262
263
function storage_type(::Type{<:Array})
264
return Array
265
end
266
267
function storage_type(C::Type{<:AbstractContainer})
268
return storage_type(Adapt.unwrap_type(C))
269
end
270
271
# backend handling
272
"""
273
trixi_backend(x)
274
275
Return the computational backend for `x`, which is either a KernelAbstractions backend or `nothing`.
276
If the backend is `nothing`, the default multi-threaded CPU backend is used.
277
"""
278
function trixi_backend(x)
279
if (_PREFERENCE_THREADING === :polyester && LoopVectorization.check_args(x)) ||
280
(_PREFERENCE_THREADING !== :kernelabstractions &&
281
get_backend(x) isa KernelAbstractions.CPU)
282
return nothing
283
end
284
return get_backend(x)
285
end
286
287
# TODO: After https://github.com/SciML/RecursiveArrayTools.jl/pull/455 we need to investigate the right way to handle StaticArray as uEltype for MultiDG.
288
function trixi_backend(x::VectorOfArray)
289
u = parent(x)
290
# FIXME(vchuravy): This is a workaround because KA.get_backend is ambivalent of where a SArray is residing.
291
if eltype(u) <: StaticArrays.StaticArray
292
return nothing
293
end
294
if length(u) == 0
295
error("VectorOfArray is empty, cannot determine backend.")
296
end
297
# Use the backend of the first element in the parent array
298
return get_backend(u[1])
299
end
300
301
# For some storage backends like CUDA.jl, empty arrays do seem to simply be
302
# null pointers which can cause `unsafe_wrap` to fail when calling
303
# Adapt.adapt (ArgumentError, see
304
# https://github.com/JuliaGPU/CUDA.jl/blob/v5.4.2/src/array.jl#L212-L229).
305
# To circumvent this, on length zero arrays this allocates
306
# a separate empty array instead of wrapping.
307
# However, since zero length arrays are not used in calculations,
308
# it should be okay if the underlying storage vectors and wrapped arrays
309
# are not the same as long as they are properly wrapped when `resize!`d etc.
310
function unsafe_wrap_or_alloc(to, vector, size)
311
if length(vector) == 0
312
return similar(vector, size)
313
else
314
return unsafe_wrap(to, pointer(vector), size)
315
end
316
end
317
318
struct TrixiAdaptor{Storage, RealT} end
319
320
"""
321
trixi_adapt(Storage, RealT, x)
322
323
Adapt `x` to the storage type `Storage` and real type `RealT`.
324
"""
325
function trixi_adapt(Storage, RealT, x)
326
return adapt(TrixiAdaptor{Storage, RealT}(), x)
327
end
328
329
# Custom rules
330
# 1. handling of StaticArrays
331
function Adapt.adapt_storage(::TrixiAdaptor{<:Any, RealT},
332
x::StaticArrays.StaticArray) where {RealT}
333
return StaticArrays.similar_type(x, RealT)(x)
334
end
335
336
# 2. Handling of Arrays
337
function Adapt.adapt_storage(::TrixiAdaptor{Storage, RealT},
338
x::AbstractArray{T}) where {Storage, RealT,
339
T <: AbstractFloat}
340
return adapt(Storage{RealT}, x)
341
end
342
343
function Adapt.adapt_storage(::TrixiAdaptor{Storage, RealT},
344
x::AbstractArray{T}) where {Storage, RealT,
345
T <: StaticArrays.StaticArray}
346
return adapt(Storage{StaticArrays.similar_type(T, RealT)}, x)
347
end
348
349
# Our threaded cache contains MArray, it is unlikely that we would want to adapt those
350
function Adapt.adapt_storage(::TrixiAdaptor{Storage, RealT},
351
x::Array{T}) where {Storage, RealT,
352
T <: StaticArrays.MArray}
353
return adapt(Array{StaticArrays.similar_type(T, RealT)}, x)
354
end
355
356
function Adapt.adapt_storage(::TrixiAdaptor{Storage, RealT},
357
x::AbstractArray) where {Storage, RealT}
358
return adapt(Storage, x)
359
end
360
361
# 3. TODO: Should we have a fallback? But that would imply implementing things for NamedTuple again
362
363
function unsafe_wrap_or_alloc(::TrixiAdaptor{Storage}, vec, size) where {Storage}
364
return unsafe_wrap_or_alloc(Storage, vec, size)
365
end
366
end # @muladd
367
368