Skip to content

Commit f1d3971

Browse files
committed
Add VectorOfArrays
1 parent bc9a53d commit f1d3971

File tree

2 files changed

+312
-0
lines changed

2 files changed

+312
-0
lines changed

src/ArraysOfArrays.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,6 @@ using UnsafeArrays
1212

1313
include("util.jl")
1414
include("array_of_similar_arrays.jl")
15+
include("vector_of_arrays.jl")
1516

1617
end # module

src/vector_of_arrays.jl

Lines changed: 311 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,311 @@
1+
# This file is a part of ArraysOfArrays.jl, licensed under the MIT License (MIT).
2+
3+
4+
doc"""
5+
VectorOfArrays{T,N,M} <: AbstractVector{AbstractArray{T,N}}
6+
7+
An `VectorOfArrays` represents a vector of `N`-dimensional arrays (that may
8+
differ in size). Internally, `VectorOfArrays` stores all elements of all
9+
arrays in a single flat vector. `M` must equal `N - 1`
10+
11+
The `VectorOfArrays` itself supports `push!`, `unshift!`, etc., but the size
12+
of each individual array in the vector is fixed. `resize!` is not supported,
13+
as the size of all arrays in the vector must be defined. However, memory space
14+
for up to `n` arrays with a maximum size `s` can be reserved via
15+
`sizehint!(A::VectorOfArrays, n, s::Dims{N})`
16+
17+
Constructors:
18+
19+
```
20+
VectorOfArrays{T, N}()
21+
22+
VectorOfArrays(
23+
data::AbstractVector,
24+
elem_ptr::AbstractVector{Int},
25+
kernel_size::AbstractVector{<:Dims}
26+
checks::Function = ArraysOfArrays.full_consistency_checks
27+
)
28+
```
29+
30+
Other suitable values for `checks` are `ArraysOfArrays.simple_consistency_checks`
31+
and `ArraysOfArrays.no_consistency_checks`.
32+
33+
The following type aliases are defined:
34+
35+
* `VectorOfVectors{T,VT,VI,VD} = VectorOfArrays{T,1,VT,VI,VD}`
36+
"""
37+
struct VectorOfArrays{
38+
T, N, M,
39+
VT<:AbstractVector{T},
40+
VI<:AbstractVector{Int},
41+
VD<:AbstractVector{Dims{M}}
42+
} <: AbstractVector{AbstractArray{T,N}}
43+
data::VT
44+
elem_ptr::VI
45+
kernel_size::VD
46+
47+
function VectorOfArrays{T,N}() where {T,N}
48+
M = length(Base.front(ntuple(_ -> 0, Val{N}())))
49+
data = Vector{T}()
50+
elem_ptr = [firstindex(data)]
51+
kernel_size = Vector{Dims{M}}()
52+
53+
new{
54+
T, N, M,
55+
typeof(data),
56+
typeof(elem_ptr),
57+
typeof(kernel_size)
58+
}(data, elem_ptr, kernel_size)
59+
end
60+
61+
function VectorOfArrays(
62+
data::VT,
63+
elem_ptr::VI,
64+
kernel_size::VD,
65+
checks::Function = full_consistency_checks
66+
) where {
67+
T, M,
68+
VT<:AbstractVector{T},
69+
VI<:AbstractVector{Int},
70+
VD<:AbstractVector{Dims{M}}
71+
}
72+
N = length((ntuple(_ -> 0, Val{M}())..., 0))
73+
A = new{T,N,M,VT,VI,VD}(data, elem_ptr, kernel_size)
74+
checks(A)
75+
A
76+
end
77+
end
78+
79+
export VectorOfArrays
80+
81+
82+
function full_consistency_checks(A::VectorOfArrays)
83+
simple_consistency_checks(A)
84+
all(eachindex(A.kernel_size)) do i
85+
len = A.elem_ptr[i+1] - A.elem_ptr[i]
86+
klen = prod(A.kernel_size[i])
87+
len > 0 && (klen == 1 || mod(len, klen) == 0)
88+
end || throw(ArgumentError("VectorOfArrays inconsistent: Content of elem_ptr and kernel_size is inconsistent"))
89+
nothing
90+
end
91+
92+
93+
function simple_consistency_checks(A::VectorOfArrays{T,N,M}) where {T,N,M}
94+
M == N - 1 || throw(ArgumentError("VectorOfArrays{T,N,M} inconsistent: M must equal N - 1"))
95+
firstindex(A.elem_ptr) == firstindex(A.kernel_size) || throw(ArgumentError("VectorOfArrays inconsistent: elem_ptr and kernel_size have incompatible indexing"))
96+
size(A.elem_ptr, 1) == size(A.kernel_size, 1) + 1 || throw(ArgumentError("VectorOfArrays inconsistent: elem_ptr and kernel_size have incompatible size"))
97+
first(A.elem_ptr) >= firstindex(A.data) || throw(ArgumentError("VectorOfArrays inconsistent: First elem_ptr inconsistent with data indices"))
98+
last(A.elem_ptr) - 1 <= lastindex(A.data) || throw(ArgumentError("VectorOfArrays inconsistent: Last elem_ptr inconsistent with data indices"))
99+
nothing
100+
end
101+
102+
103+
function no_consistency_checks(A::VectorOfArrays)
104+
nothing
105+
end
106+
107+
108+
Base.@propagate_inbounds function _elem_range_size(A::VectorOfArrays, i::Integer)
109+
elem_ptr = A.elem_ptr
110+
111+
from = elem_ptr[i]
112+
until = elem_ptr[i+1]
113+
to = until - 1
114+
len = until - from
115+
116+
ksize = A.kernel_size[i]
117+
klen = prod(ksize)
118+
sz_lastdim = div(len, klen)
119+
sz = (ksize..., sz_lastdim)
120+
121+
(from:to, sz)
122+
end
123+
124+
125+
import Base.==
126+
(==)(A::VectorOfArrays, B::VectorOfArrays) =
127+
A.data == B.data && A.elem_ptr == B.elem_ptr && A.kernel_size == B.kernel_size
128+
129+
130+
Base.parent(A::VectorOfArrays) = A.data
131+
132+
Base.size(A::VectorOfArrays) = size(A.kernel_size)
133+
134+
135+
Base.@propagate_inbounds function Base.getindex(A::VectorOfArrays, i::Integer)
136+
@boundscheck checkbounds(A, i)
137+
r, s = _elem_range_size(A, i)
138+
dataview = view(A.data, r)
139+
Base.__reshape((dataview, IndexStyle(dataview)), s)
140+
end
141+
142+
143+
Base.@propagate_inbounds function Base._getindex(l::IndexStyle, A::VectorOfArrays, idxs::AbstractUnitRange{<:Integer})
144+
from = first(idxs)
145+
to = last(idxs)
146+
elem_ptr = A.elem_ptr[from:(to+1)]
147+
kernel_size = A.kernel_size[from:to]
148+
data = A.data[first(elem_ptr):(last(elem_ptr) - 1)]
149+
broadcast!(+, elem_ptr, elem_ptr, firstindex(data) - first(elem_ptr))
150+
VectorOfArrays(data, elem_ptr, kernel_size, no_consistency_checks)
151+
end
152+
153+
154+
Base.@propagate_inbounds function Base._getindex(l::IndexStyle, A::VectorOfArrays, idxs::AbstractVector{<:Integer})
155+
@boundscheck checkbounds(A, idxs)
156+
157+
A_ep = A.elem_ptr
158+
A_data = A.data
159+
160+
elem_ptr = similar(A_ep, length(linearindices(idxs)) + 1)
161+
delta_i = firstindex(elem_ptr) - firstindex(idxs)
162+
163+
elem_ptr[firstindex(elem_ptr)] = firstindex(A_data)
164+
for i in linearindices(idxs)
165+
idx = idxs[i]
166+
l = A_ep[idx + 1] - A_ep[idx]
167+
elem_ptr[i + 1 + delta_i] = elem_ptr[i + delta_i] + l
168+
end
169+
170+
data = similar(A_data, last(elem_ptr) - first(elem_ptr))
171+
if firstindex(data) != firstindex(A_data)
172+
@assert firstindex(data) != first(elem_ptr)
173+
broadcast!(+, elem_ptr, elem_ptr, firstindex(data) - first(elem_ptr))
174+
end
175+
176+
for i in linearindices(idxs)
177+
idx = idxs[i]
178+
l = A_ep[idx + 1] - A_ep[idx]
179+
180+
# Sanity check:
181+
@assert l == elem_ptr[i + 1 + delta_i] - elem_ptr[i + delta_i]
182+
183+
copyto!(data, elem_ptr[i + delta_i], A_data, A_ep[idx], l)
184+
end
185+
186+
kernel_size = A.kernel_size[idxs]
187+
188+
VectorOfArrays(data, elem_ptr, kernel_size, no_consistency_checks)
189+
end
190+
191+
192+
Base.@propagate_inbounds function Base.setindex!(A::VectorOfArrays{T,N}, x::AbstractArray{U,N}, i::Integer) where {T,N,U}
193+
r, s = _view_reshape_spec(A, i)
194+
@boundscheck s == size(x) || throw(DimensionMismatch("Can't assign array to element $i of VectorOfArrays, array size is incompatible"))
195+
A.data[rng] = x
196+
A
197+
end
198+
199+
Base.length(A::VectorOfArrays) = length(A.kernel_size)
200+
Base._length(A::VectorOfArrays) = Base._length(A.kernel_size)
201+
Base.linearindices(A::VectorOfArrays) = linearindices(A.kernel_size)
202+
203+
204+
function Base.append!(A::VectorOfArrays{T,N}, B::VectorOfArrays{U,N}) where {T,N,U}
205+
if !isempty(B)
206+
# Implementation supports A === B
207+
208+
A_ep = A.elem_ptr
209+
B_ep = B.elem_ptr
210+
idxs_B = firstindex(B_ep):(lastindex(B_ep) - 1)
211+
delta_ep_idx = lastindex(A_ep) + 1 - firstindex(B_ep)
212+
delta_ep = last(A_ep) - first(B_ep)
213+
resize!(A_ep, length(linearindices(A_ep)) + length(idxs_B))
214+
@assert checkbounds(Bool, B_ep, idxs_B)
215+
@assert checkbounds(Bool, A_ep, broadcast(+, idxs_B, delta_ep_idx))
216+
@inbounds @simd for i_B in idxs_B
217+
A_ep[i_B + delta_ep_idx] = B_ep[i_B + 1] + delta_ep
218+
end
219+
220+
append!(A.data, B.data)
221+
append!(A.kernel_size, B.kernel_size)
222+
223+
simple_consistency_checks(A)
224+
end
225+
A
226+
end
227+
228+
229+
function Base.append!(A::VectorOfArrays{T,N}, B::AbstractVector{AbstractArray{U,N}}) where {T,N,U}
230+
if !isempty(B)
231+
n_A = length(linearindices(A))
232+
n_B = length(linearindices(B))
233+
datalen_A = length(linearindices(A.data))
234+
datalen_B = zero(Int)
235+
for i in eachindex(B)
236+
datalen_B += Int(length(linearindices(B[i])))
237+
end
238+
239+
sizehint!(A.data, datalen_A + datalen_B)
240+
sizehint!(A.elem_ptr, n_A + n_B + 1)
241+
sizehint!(A.kernel_size, n_A + n_B)
242+
243+
for i in eachindex(B)
244+
push!(A, B[i])
245+
end
246+
end
247+
A
248+
end
249+
250+
251+
Base.@propagate_inbounds function Base.unsafe_view(A::VectorOfArrays, idxs::AbstractUnitRange{<:Integer})
252+
from = first(idxs)
253+
to = last(idxs)
254+
VectorOfArrays(
255+
A.data,
256+
view(A.elem_ptr, from:(to+1)),
257+
view(A.kernel_size, from:to),
258+
no_consistency_checks
259+
)
260+
end
261+
262+
263+
function Base.sizehint!(A::VectorOfArrays{T,N}, n, s::Dims{N}) where {T,N}
264+
sizehint!(A.data, n * mul(s))
265+
sizehint!(A.elem_ptr, n + 1)
266+
sizehint!(A.kernel_size, n)
267+
A
268+
end
269+
270+
271+
function Base.push!(A::VectorOfArrays{T,N}, x::AbstractArray{U,N}) where {T,N,U}
272+
@assert last(A.elem_ptr) == lastindex(A.data) + 1
273+
append!(A.data, x)
274+
push!(A.elem_ptr, lastindex(A.data) + 1)
275+
push!(A.kernel_size, Base.front(size(x)))
276+
A
277+
end
278+
279+
280+
function UnsafeArrays.uview(A::VectorOfArrays)
281+
VectorOfArrays(
282+
uview(A.data),
283+
uview(A.elem_ptr),
284+
uview(A.kernel_size),
285+
no_consistency_checks
286+
)
287+
end
288+
289+
290+
291+
const VectorOfVectors{
292+
T,
293+
VT<:AbstractVector{T},
294+
VI<:AbstractVector{Int},
295+
VD<:AbstractVector{Dims{0}}
296+
} = VectorOfArrays{T,1,0,VT,VI,VD}
297+
298+
export VectorOfVectors
299+
300+
VectorOfVectors{T}() where {T} = VectorOfArrays{T,1}()
301+
302+
VectorOfVectors(
303+
data::AbstractVector,
304+
elem_ptr::AbstractVector{Int},
305+
checks::Function = consistency_checks
306+
) = VectorOfArrays(
307+
data,
308+
elem_ptr,
309+
similar(A.elem_ptr, Dims{0}, size(elem_ptr, 1) - 1),
310+
checks
311+
)

0 commit comments

Comments
 (0)