Skip to content

Commit 90c269f

Browse files
authored
Docs Workflow, README, and ArrayIndex boundschecks (#177)
Add docs workflow and StrideIndex docs
1 parent 91795a6 commit 90c269f

File tree

7 files changed

+55
-216
lines changed

7 files changed

+55
-216
lines changed

.github/workflows/ci.yml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,3 +44,19 @@ jobs:
4444
- uses: codecov/codecov-action@v1
4545
with:
4646
file: lcov.info
47+
docs:
48+
name: Documentation
49+
runs-on: ubuntu-latest
50+
steps:
51+
- uses: actions/checkout@v1
52+
- uses: julia-actions/setup-julia@latest
53+
with:
54+
version: '1.2'
55+
- run: julia --project=docs -e '
56+
using Pkg;
57+
Pkg.develop(PackageSpec(; path=pwd()));
58+
Pkg.instantiate();'
59+
- run: julia --project=docs docs/make.jl
60+
env:
61+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
62+
DOCUMENTER_KEY: ${{ secrets.DOCUMENTER_KEY }}

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name = "ArrayInterface"
22
uuid = "4fba245c-0d91-5ea0-9b3e-6abc04ee57a9"
3-
version = "3.1.19"
3+
version = "3.1.20"
44

55
[deps]
66
IfElse = "615f187c-cbe4-4ef1-ba3b-2fcf58d6d173"

README.md

Lines changed: 0 additions & 200 deletions
Original file line numberDiff line numberDiff line change
@@ -16,206 +16,6 @@ serve as a staging ground for ideas before they are merged into Base Julia. For
1616
reason, no functionality is exported so that if such functions are added
1717
and exported in a future Base Julia, there will be no issues with the upgrade.
1818

19-
## parent_type(x)
20-
21-
Returns the parent array that `x` wraps.
22-
23-
## can_change_size(x)
24-
25-
Returns `true` if the size of `T` can change, in which case operations
26-
such as `pop!` and `popfirst!` are available for collections of type `T`.
27-
28-
## indices(x[, d])
29-
30-
Given an array `x`, this returns the indices along dimension `d`. If `x` is a tuple
31-
of arrays, then the indices corresponding to dimension `d` of all arrays in `x` are
32-
returned. If any indices are not equal along dimension `d`, an error is thrown. A
33-
tuple may be used to specify a different dimension for each array. If `d` is not
34-
specified, then indices for visiting each index of `x` are returned.
35-
36-
## ismutable(x)
37-
38-
A trait function for whether `x` is a mutable or an immutable array. Used for
39-
dispatching to in-place and out-of-place versions of functions.
40-
41-
## aos_to_soa(x)
42-
43-
Converts an array of structs formulation to a struct of arrays.
44-
45-
## isstructured(x)
46-
47-
A trait function for whether a matrix `x` is a sparse structured matrix.
48-
49-
## can_setindex(x)
50-
51-
A trait function for whether an array `x` can use `setindex!`.
52-
53-
## has_sparsestruct(x)
54-
55-
Determine whether `findstructralnz` accepts the parameter `x`.
56-
57-
## findstructralnz(x)
58-
59-
Returns iterators `(I,J)` of the non-zeros in the structure of the matrix `x`.
60-
The same as the first two elements of `findnz(::SparseMatrixCSC)`.
61-
62-
## fast_matrix_colors(A)
63-
64-
A trait function for whether `matrix_colors(A)` is a fast algorithm or a slow
65-
(graphically-based) method.
66-
67-
## matrix_colors(A)
68-
69-
Returns an array for the sparsity colors of a matrix type `A`. Also includes
70-
an abstract type `ColoringAlgorithm` for `matrix_colors(A,alg::ColoringAlgorithm)`
71-
of non-structured matrices.
72-
73-
## fast_scalar_indexing(A)
74-
75-
A trait function for whether scalar indexing is fast on a given array type.
76-
77-
## allowed_getindex(A,i...)
78-
79-
A `getindex` which is always allowed.
80-
81-
## allowed_setindex!(A,v,i...)
82-
83-
A `setindex!` which is always allowed.
84-
85-
## lu_instance(A)
86-
87-
Returns an instance of the LU factorization object with the correct type
88-
cheaply.
89-
90-
## issingular(A)
91-
92-
Returns an instance of the LU factorization object with the correct type cheaply.
93-
94-
## safevec(v)
95-
96-
Is a form of `vec` which is safe for all values in vector spaces, i.e., if it
97-
is already a vector, like an AbstractVector or Number, it will return said
98-
AbstractVector or Number.
99-
100-
## zeromatrix(u)
101-
102-
Creates the zero'd matrix version of `u`. Note that this is unique because
103-
`similar(u,length(u),length(u))` returns a mutable type, so is not type-matching,
104-
while `fill(zero(eltype(u)),length(u),length(u))` doesn't match the array type,
105-
i.e., you'll get a CPU array from a GPU array. The generic fallback is
106-
`u .* u' .* false`, which works on a surprising number of types, but can be broken
107-
with weird (recursive) broadcast overloads. For higher-order tensors, this
108-
returns the matrix linear operator type, which acts on the `vec` of the array.
109-
110-
## restructure(x,y)
111-
112-
Restructures the object `y` into a shape of `x`, keeping its values intact. For
113-
simple objects, like an `Array`, this simply amounts to a reshape. However, for
114-
more complex objects such as an `ArrayPartition`, not all of the structural
115-
information is adequately contained in the type for standard tools to work. In
116-
these cases, `restructure` gives a way to convert, for example, an `Array` into
117-
a matching `ArrayPartition`.
118-
119-
## known_first(::Type{T})
120-
121-
If `first` of instances of type `T` are known at compile time, return that first
122-
element. Otherwise, return `nothing`. For example, `known_first(Base.OneTo{Int})`
123-
returns `one(Int)`.
124-
125-
## known_last(::Type{T})
126-
127-
If `last` of instances of type `T` are known at compile time, return that
128-
last element. Otherwise, return `nothing`. For example,
129-
`known_last(StaticArrays.SOneTo{4})` returns 4.
130-
131-
## known_step(::Type{T})
132-
133-
If `step` of instances of type `T` are known at compile time, return that step.
134-
Otherwise, returns `nothing`. For example, `known_step(UnitRange{Int})` returns
135-
`one(Int)`.
136-
137-
## known_length(::Type{T})
138-
139-
If `length` of an instance of type `T` is known at compile time, return it.
140-
Otherwise, return `nothing`.
141-
142-
## device(::Type{T})
143-
144-
Indicates the most efficient way to access elements from the collection in low-level code.
145-
For `GPUArrays`, returns `ArrayInterface.GPU()`.
146-
For `AbstractArray` supporting a `pointer` method, returns `ArrayInterface.CPUPointer()`.
147-
For other `AbstractArray`s and `Tuple`s, returns `ArrayInterface.CPUIndex()`.
148-
Otherwise, returns `nothing`.
149-
150-
## contiguous_axis(::Type{T})
151-
152-
Returns the axis of an array of type `T` containing contiguous data.
153-
If no axis is contiguous, it returns `Contiguous{-1}`.
154-
If unknown, it returns `nothing`.
155-
156-
## contiguous_axis_indicator(::Type{T})
157-
158-
Returns a tuple of boolean `StaticBool`s indicating whether that axis is contiguous.
159-
160-
## contiguous_batch_size(::Type{T})
161-
162-
Returns the size of contiguous batches if `!isone(stride_rank(T, contiguous_axis(T)))`.
163-
If `isone(stride_rank(T, contiguous_axis(T)))`, then returns `ContiguousBatch{0}()`.
164-
If `contiguous_axis(T) == -1`, returns `ContiguousBatch{-1}()`.
165-
If unknown, returns `nothing`.
166-
167-
## stride_rank(::Type{T})
168-
169-
Returns the rank of each stride.
170-
171-
## is_column_major(A)
172-
173-
Returns a `True` if `A` is column major, and a `True/False` otherwise.
174-
175-
## dense_dims(::Type{T})
176-
Returns a tuple of indicators for whether each axis is dense.
177-
An axis `i` of array `A` is dense if `stride(A, i) * size(A, i) == stride(A, j)`, where `j` is the axis (if it exists) such that `stride_rank(A)[i] + 1 == stride_rank(A)[j]`.
178-
179-
## ArrayInterface.size(A)
180-
181-
Returns the size of `A`. If the sizes of any axes are known at compile time,
182-
these should be returned as `StaticInt`s. For example:
183-
```julia
184-
julia> using StaticArrays, ArrayInterface
185-
186-
julia> A = @SMatrix rand(3,4);
187-
188-
julia> ArrayInterface.size(A)
189-
(static(3), static(4))
190-
```
191-
192-
## ArrayInterface.strides(A)
193-
194-
Returns the strides of array `A`. If any strides are known at compile time,
195-
these should be returned as `StaticInt`s. For example:
196-
```julia
197-
julia> using ArrayInterface
198-
199-
julia> A = rand(3,4);
200-
201-
julia> ArrayInterface.strides(A)
202-
(static(1), 3)
203-
```
204-
## offsets(A)
205-
206-
Returns offsets of indices with respect to 0. If values are known at compile time,
207-
it should return them as `StaticInt`s.
208-
For example, if `A isa Base.Matrix`, `offsets(A) === (StaticInt(1), StaticInt(1))`.
209-
210-
## can_avx(f)
211-
212-
Is the function `f` whitelisted for `LoopVectorization.@avx`?
213-
214-
## `is_lazy_conjugate(A)`
215-
216-
Does the array `A` lazyily take complex conjugates of it's elements?
217-
218-
21919
# List of things to add
22020

22121
- https://github.com/JuliaLang/julia/issues/22216

docs/src/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,4 @@ CurrentModule = ArrayInterface
1010
```@autodocs
1111
Modules = [ArrayInterface]
1212
```
13+

src/array_index.jl

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,12 @@ function BandedBlockBandedMatrixIndex(
183183
rowindobj, colindobj
184184
end
185185

186+
"""
187+
StrideIndex(x)
188+
189+
Subtype of `ArrayIndex` that transforms and index using stride layout information
190+
derived from `x`.
191+
"""
186192
struct StrideIndex{N,R,C,S,O,O1} <: ArrayIndex{N}
187193
strides::S
188194
offsets::O
@@ -202,8 +208,9 @@ end
202208
Base.firstindex(i::Union{TridiagonalIndex,BandedBlockBandedMatrixIndex,BandedMatrixIndex,BidiagonalIndex,BlockBandedMatrixIndex}) = 1
203209
Base.lastindex(i::Union{TridiagonalIndex,BandedBlockBandedMatrixIndex,BandedMatrixIndex,BidiagonalIndex,BlockBandedMatrixIndex}) = i.count
204210
Base.length(i::Union{TridiagonalIndex,BandedBlockBandedMatrixIndex,BandedMatrixIndex,BidiagonalIndex,BlockBandedMatrixIndex}) = i.count
205-
function Base.getindex(ind::BidiagonalIndex, i::Int)
206-
1 <= i <= ind.count || throw(BoundsError(ind, i))
211+
@propagate_inbounds Base.getindex(x::ArrayIndex, i::CanonicalInt, ii::CanonicalInt...) = x[NDIndex(i, ii...)]
212+
@propagate_inbounds function Base.getindex(ind::BidiagonalIndex, i::Int)
213+
@boundscheck 1 <= i <= ind.count || throw(BoundsError(ind, i))
207214
if ind.isup
208215
ii = i + 1
209216
else
@@ -212,8 +219,8 @@ function Base.getindex(ind::BidiagonalIndex, i::Int)
212219
convert(Int, floor(ii / 2))
213220
end
214221

215-
function Base.getindex(ind::TridiagonalIndex, i::Int)
216-
1 <= i <= ind.count || throw(BoundsError(ind, i))
222+
@propagate_inbounds function Base.getindex(ind::TridiagonalIndex, i::Int)
223+
@boundscheck 1 <= i <= ind.count || throw(BoundsError(ind, i))
217224
offsetu = ind.isrow ? 0 : 1
218225
offsetl = ind.isrow ? 1 : 0
219226
if 1 <= i <= ind.nsize
@@ -225,8 +232,8 @@ function Base.getindex(ind::TridiagonalIndex, i::Int)
225232
end
226233
end
227234

228-
function Base.getindex(ind::BandedMatrixIndex, i::Int)
229-
1 <= i <= ind.count || throw(BoundsError(ind, i))
235+
@propagate_inbounds function Base.getindex(ind::BandedMatrixIndex, i::Int)
236+
@boundscheck 1 <= i <= ind.count || throw(BoundsError(ind, i))
230237
_i = i
231238
p = 1
232239
while _i - ind.bandsizes[p] > 0
@@ -242,8 +249,8 @@ function Base.getindex(ind::BandedMatrixIndex, i::Int)
242249
end
243250
end
244251

245-
function Base.getindex(ind::BlockBandedMatrixIndex, i::Int)
246-
1 <= i <= ind.count || throw(BoundsError(ind, i))
252+
@propagate_inbounds function Base.getindex(ind::BlockBandedMatrixIndex, i::Int)
253+
@boundscheck 1 <= i <= ind.count || throw(BoundsError(ind, i))
247254
p = 1
248255
while i - ind.refinds[p] >= 0
249256
p += 1
@@ -257,8 +264,8 @@ function Base.getindex(ind::BlockBandedMatrixIndex, i::Int)
257264
end
258265
end
259266

260-
function Base.getindex(ind::BandedBlockBandedMatrixIndex, i::Int)
261-
1 <= i <= ind.count || throw(BoundsError(ind, i))
267+
@propagate_inbounds function Base.getindex(ind::BandedBlockBandedMatrixIndex, i::Int)
268+
@boundscheck 1 <= i <= ind.count || throw(BoundsError(ind, i))
262269
p = 1
263270
while i - ind.refinds[p] >= 0
264271
p += 1
@@ -268,11 +275,16 @@ function Base.getindex(ind::BandedBlockBandedMatrixIndex, i::Int)
268275
ind.reflocalinds[p][_i] + ind.refcoords[p] - 1
269276
end
270277

271-
@inline function Base.getindex(x::StrideIndex{N}, i::CanonicalInt...) where {N}
272-
return _strides2int(x.offsets, x.strides, Tuple(i)) + x.offset1
278+
@inline function Base.getindex(x::StrideIndex{N}, i::AbstractCartesianIndex{N}) where {N}
279+
return _strides2int(offsets(x), strides(x), Tuple(i)) + offset1(x)
273280
end
274-
@inline function _strides2int(o::Tuple, s::Tuple, i::Tuple)
275-
return ((first(i) - first(o)) * first(s)) + _strides2int(tail(o), tail(s), tail(i))
281+
@generated function _strides2int(o::O, s::S, i::I) where {O,S,I}
282+
N = known_length(I)
283+
out = :()
284+
for i in 1:N
285+
tmp = :(((getfield(i, $i) - getfield(o, $i)) * getfield(s, $i)))
286+
out = ifelse(i === 1, tmp, :($out + $tmp))
287+
end
288+
return Expr(:block, Expr(:meta, :inline), out)
276289
end
277-
_strides2int(::Tuple{}, ::Tuple{}, ::Tuple{}) = static(0)
278290

src/stridelayout.jl

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,13 +41,16 @@ function known_offsets(::Type{T}) where {T}
4141
end
4242
_known_offsets(::Type{T}, dim::StaticInt) where {T} = known_first(_get_tuple(T, dim))
4343

44+
known_offsets(::Type{<:StrideIndex{N,R,C,S,O,O1}}) where {N,R,C,S,O,O1} = known(O)
45+
4446
"""
4547
offsets(A[, dim]) -> Tuple
4648
4749
Returns offsets of indices with respect to 0. If values are known at compile time,
4850
it should return them as `Static` numbers.
4951
For example, if `A isa Base.Matrix`, `offsets(A) === (StaticInt(1), StaticInt(1))`.
5052
"""
53+
offsets(x::StrideIndex) = getfield(x, :offsets)
5154
@inline offsets(x, i) = static_first(indices(x, i))
5255
offsets(::Tuple) = (One(),)
5356
offsets(x) = eachop(_offsets, nstatic(Val(ndims(x))), x)
@@ -69,6 +72,7 @@ known_offset1(x) = known_offset1(typeof(x))
6972
known_offset1(::Type{T}) where {T} = _known_offset1(has_parent(T), T)
7073
_known_offset1(::True, ::Type{T}) where {T} = known_offset1(parent_type(T))
7174
_known_offset1(::False, ::Type{T}) where {T} = 1
75+
known_offset(::Type{<:StrideIndex{N,R,C,S,O,O1}}) where {N,R,C,S,O,O1} = known(O1)
7276

7377
"""
7478
offset1(x) -> Integer
@@ -83,6 +87,7 @@ Returns the offset of the linear indices for `x`.
8387
return static(o1)
8488
end
8589
end
90+
offset1(x::StrideIndex) = getfield(x, :offset1)
8691

8792
"""
8893
contiguous_axis(::Type{T}) -> StaticInt{N}
@@ -434,6 +439,7 @@ function known_strides(::Type{T}, dim::Integer) where {T}
434439
return known_strides(T)[dim]
435440
end
436441
end
442+
known_strides(::Type{<:StrideIndex{N,R,C,S,O,O1}}) where {N,R,C,S,O,O1} = known(S)
437443

438444
known_strides(x) = known_strides(typeof(x))
439445
known_strides(::Type{T}) where {T<:Vector} = (1,)
@@ -483,6 +489,7 @@ This is to support the pattern of using just the first stride for linear indexin
483489
while still producing correct behavior when using valid cartesian indices, such as `x[1,i]`.
484490
```
485491
"""
492+
strides(A::StrideIndex) = getfield(A, :strides)
486493
@inline strides(A::Vector{<:Any}) = (StaticInt(1),)
487494
@inline strides(A::Array{<:Any,N}) where {N} = (StaticInt(1), Base.tail(Base.strides(A))...)
488495
function strides(x)

test/runtests.jl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -694,6 +694,9 @@ end
694694
@test ap_index[x_i, y_i] == ap_index[x_i, y_i]
695695
end
696696
end
697+
@test @inferred(ArrayInterface.known_offsets(ap_index)) === ArrayInterface.known_offsets(Ap)
698+
@test @inferred(ArrayInterface.known_offset1(ap_index)) === ArrayInterface.known_offset1(Ap)
699+
@test @inferred(ArrayInterface.known_strides(ap_index)) === ArrayInterface.known_strides(Ap)
697700
end
698701

699702
if VERSION v"1.6.0-DEV.1581"

0 commit comments

Comments
 (0)