Skip to content

Commit 29677bd

Browse files
committed
Add docs and tests
1 parent 9c79c61 commit 29677bd

File tree

4 files changed

+67
-4
lines changed

4 files changed

+67
-4
lines changed

docs/make.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ makedocs(;
1212
"sparsearrays.md",
1313
"tuples.md",
1414
"wrapping.md",
15+
"index_labels.md",
1516
]
1617
)
1718

docs/src/index_labels.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Index Labels Interface
2+
3+
The following ArrayInterface functions provide support for indices with labels.
4+
5+
```@docs
6+
ArrayInterface.has_index_labels
7+
ArrayInterface.index_labels
8+
```
9+

src/ArrayInterface.jl

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1023,28 +1023,48 @@ ensures_sorted(@nospecialize( T::Type{<:AbstractRange})) = true
10231023
ensures_sorted(T::Type) = is_forwarding_wrapper(T) ? ensures_sorted(parent_type(T)) : false
10241024
ensures_sorted(@nospecialize(x)) = ensures_sorted(typeof(x))
10251025

1026+
const INDEX_LABELS_EXTENDED_HELP = """
1027+
## Extended help
1028+
1029+
Structures that explicitly provide labels along their indices must define both
1030+
`has_index_labels` and `index_labels`. Wrappers that don't change the layout
1031+
of their parent data and define `is_forwarding_wrapper` will propagate these methods
1032+
freely, but all other wrappers must define these two methods in order to propagate
1033+
labelled indices information.
1034+
1035+
Labeled indices are expected to hold the following properties:
1036+
* `length(index_labels(x)) == ndims(x)`
1037+
* `map(length, index_labels(x)) == size(x)`
1038+
"""
1039+
10261040
"""
10271041
has_index_labels(T::Type) -> Bool
10281042
10291043
Returns `true` if instances of `T` have labeled indices. Structures overloading this
10301044
method are also responsible for defining [`ArrayInterface.index_labels`](@ref).
1045+
1046+
$INDEX_LABELS_EXTENDED_HELP
10311047
"""
10321048
function has_index_labels(T::Type)
10331049
is_forwarding_wrapper(T) ? has_index_labels(parent_type(T)) : false
10341050
end
10351051

10361052
"""
1037-
index_labels(x)
1038-
index_labels(x, dim)
1053+
index_labels(x) -> Tuple{Vararg{Any, ndims(x)}}
1054+
index_labels(x, dim) -> itr
10391055
10401056
Returns a tuple of labels assigned to each axis or a collection of labels corresponding to
1041-
each index along `dim` of `x`. Default is to return `UnlabelledIndices(axes(x, dim))`.
1057+
each index along `dim` of `x`.
1058+
1059+
$INDEX_LABELS_EXTENDED_HELP
10421060
"""
10431061
function index_labels(x::T) where {T}
10441062
has_index_labels(T) || (@noinline; throw(ArgumentError("Objects of type $T do not support `index_labels`")))
10451063
is_forwarding_wrapper(T) || (@noinline; throw(ArgumentError("`has_index_labels($(T)) == true` but does not have `ArrayInterface.index_labels(::$T)` defined.")))
10461064
return index_labels(parent(x))
10471065
end
1066+
index_labels(x, dim::Integer) = index_labels(x)[Int(dim)]
1067+
10481068

10491069
## Extensions
10501070

test/core.jl

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,21 @@ using Random
77
using SparseArrays
88
using Test
99

10+
struct LabeledIndicesArray{T,N,P<:AbstractArray{T,N},L} <: AbstractArray{T,N}
11+
parent::P
12+
labels::L
13+
14+
LabeledIndicesArray(p::P, labels::L) where {P,L} = new{eltype(P),ndims(p),P,L}(p, labels)
15+
end
16+
ArrayInterface.is_forwarding_wrapper(::Type{<:LabeledIndicesArray}) = true
17+
Base.parent(x::LabeledIndicesArray) = getfield(x, :parent)
18+
ArrayInterface.parent_type(::Type{T}) where {P,T<:LabeledIndicesArray{<:Any,<:Any,P}} = P
19+
ArrayInterface.index_labels(x::LabeledIndicesArray) = getfield(x, :labels)
20+
ArrayInterface.has_index_labels(T::Type{<:LabeledIndicesArray}) = true
21+
ArrayInterface.is_forwarding_wrapper(::Type{<:LabeledIndicesArray}) = true
22+
Base.size(x::LabeledIndicesArray) = size(parent(x))
23+
Base.@propagate_inbounds Base.getindex(x::LabeledIndicesArray, inds...) = parent(x)[inds...]
24+
1025
# ensure we are correctly parsing these
1126
ArrayInterface.@assume_effects :total foo(x::Bool) = x
1227
ArrayInterface.@assume_effects bar(x::Bool) = x
@@ -273,4 +288,22 @@ end
273288
@test ArrayInterface.svd_instance(A) isa typeof(svd(A))
274289
end
275290
end
276-
end
291+
end
292+
293+
@testset "index_labels" begin
294+
a = ones(2, 3)
295+
lia = LabeledIndicesArray(a, ([:a, :b], ["x", "y", "z"]))
296+
297+
@test @inferred(ArrayInterface.has_index_labels(typeof(lia)))
298+
@test !@inferred(ArrayInterface.has_index_labels(typeof(a)))
299+
300+
@test @inferred(ArrayInterface.index_labels(lia)) == lia.labels
301+
@test ArrayInterface.index_labels(lia, 1) == lia.labels[1]
302+
@test_throws ArgumentError ArrayInterface.index_labels(a)
303+
304+
# throw errors when interface isn't implemented correctly
305+
struct IllegalLabelledIndices end
306+
ArrayInterface.has_index_labels(::Type{IllegalLabelledIndices}) = true
307+
@test_throws ArgumentError ArrayInterface.index_labels(IllegalLabelledIndices())
308+
end
309+

0 commit comments

Comments
 (0)