diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ddefb72dc..409a845d0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,6 +12,7 @@ jobs: test: name: Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }} runs-on: ${{ matrix.os }} + continue-on-error: ${{ matrix.allow_failure }} strategy: fail-fast: true matrix: @@ -23,10 +24,12 @@ jobs: - windows-latest arch: - x64 + allow_failure: [false] include: - version: 'nightly' os: ubuntu-latest arch: x64 + allow_failure: true steps: - uses: actions/checkout@v2 - uses: julia-actions/setup-julia@v1 diff --git a/docs/src/api/lookuparrays.md b/docs/src/api/lookuparrays.md index ea8098380..d308c1724 100644 --- a/docs/src/api/lookuparrays.md +++ b/docs/src/api/lookuparrays.md @@ -19,7 +19,7 @@ Lookups.Transformed Dimensions.MergedLookup Lookups.NoLookup Lookups.AutoLookup -Lookups.AutoIndex +Lookups.AutoValues ``` The generic value getter `val` @@ -33,7 +33,6 @@ Lookup methods: ```@docs bounds hasselection -Lookups.index Lookups.sampling Lookups.span Lookups.order @@ -91,14 +90,15 @@ Lookups.Points Lookups.Intervals ``` -### Loci +### Positions ```@docs -Lookups.Locus +Position Lookups.Center Lookups.Start +Lookups.Begin Lookups.End -Lookups.AutoLocus +Lookups.AutoPosition ``` ## Metadata diff --git a/docs/src/dimarrays.md b/docs/src/dimarrays.md index b48c666a1..cc73ac43c 100644 --- a/docs/src/dimarrays.md +++ b/docs/src/dimarrays.md @@ -116,6 +116,23 @@ Mixing them will throw an error: da1[X(3), 4] ``` +## Begin End indexing + +```@ansi dimarray +da1[X=Begin+1, Y=End] +``` + +It also works in ranges, even with basic math: + +```@ansi dimarray +da1[X=Begin:Begin+1, Y=Begin+1:End-1] +``` + +In base julia the keywords `begin` and `end` can be used to +index the first or last element of an array. But this doesn't +work when named indexing is used. Instead you can use the types +`Begin` and `End`. + ::: info Indexing Indexing `AbstractDimArray`s works with `getindex`, `setindex!` and diff --git a/src/DimensionalData.jl b/src/DimensionalData.jl index 2ea87fff9..9eb0c0ae2 100644 --- a/src/DimensionalData.jl +++ b/src/DimensionalData.jl @@ -38,7 +38,7 @@ using .Dimensions.Lookups using .Dimensions: StandardIndices, DimOrDimType, DimTuple, DimTupleOrEmpty, DimType, AllDims import .Lookups: metadata, set, _set, rebuild, basetypeof, order, span, sampling, locus, val, index, bounds, intervalbounds, - hasselection, units, SelectorOrInterval + hasselection, units, SelectorOrInterval, Begin, End import .Dimensions: dims, refdims, name, lookup, kw2dims, hasdim, label, _astuple import DataAPI.groupby @@ -56,6 +56,8 @@ export X, Y, Z, Ti, Dim, Coord # Selector export At, Between, Touches, Contains, Near, Where, All, .., Not, Bins, CyclicBins +export Begin, End + export AbstractDimArray, DimArray export AbstractDimVector, AbstractDimMatrix, AbstractDimVecOrMat, DimVector, DimMatrix, DimVecOrMat diff --git a/src/Dimensions/Dimensions.jl b/src/Dimensions/Dimensions.jl index cfe6d216b..a4b3dc89d 100644 --- a/src/Dimensions/Dimensions.jl +++ b/src/Dimensions/Dimensions.jl @@ -25,9 +25,10 @@ const LookupArrays = Lookups import .Lookups: rebuild, order, span, sampling, locus, val, index, set, _set, metadata, bounds, intervalbounds, units, basetypeof, unwrap, selectindices, hasselection, - shiftlocus, maybeshiftlocus, SelectorOrInterval, Interval + shiftlocus, maybeshiftlocus using .Lookups: StandardIndices, SelTuple, CategoricalEltypes, - LookupTrait, AllMetadata, LookupSetters + LookupTrait, AllMetadata, LookupSetters, AbstractBeginEndRange, + SelectorOrInterval, Interval using Base: tail, OneTo, @propagate_inbounds diff --git a/src/Dimensions/dimension.jl b/src/Dimensions/dimension.jl index f8bcce963..d229c0bb9 100644 --- a/src/Dimensions/dimension.jl +++ b/src/Dimensions/dimension.jl @@ -7,15 +7,14 @@ Example concrete implementations are [`X`](@ref), [`Y`](@ref), [`Z`](@ref), [`Ti`](@ref) (Time), and the custom [`Dim`]@ref) dimension. `Dimension`s label the axes of an `AbstractDimArray`, -or other dimensional objects, and are used to index into the array. +or other dimensional objects, and are used to index into an array. -They may also provide an alternate index to lookup for each array axis. -This may be any `AbstractVector` matching the array axis length, or a `Val` -holding a tuple for compile-time index lookups. +They may also wrap lookup values for each array axis. +This may be any `AbstractVector` matching the array axis length, +but will usually be converted to a `Lookup` when use in a constructed +object. -`Dimension`s also have `lookup` and `metadata` fields. - -`lookup` gives more details about the dimension, such as that it is +A `Lookup` gives more details about the dimension, such as that it is [`Categorical`](@ref) or [`Sampled`](@ref) as [`Points`](@ref) or [`Intervals`](@ref) along some transect. DimensionalData will attempt to guess the lookup from the passed-in index value. @@ -90,9 +89,6 @@ x = A[X(Between(3, 4)), Y(At('b'))] ↓ → 2021-01-01T00:00:00 2021-02-01T00:00:00 … 2021-12-01T00:00:00 4 0.0 0.0 0.0 ``` - -`Dimension` objects may have [`lookup`](@ref) and [`metadata`](@ref) fields -to track additional information about the data and the index, and their relationship. """ abstract type Dimension{T} end @@ -194,9 +190,6 @@ label(x) = string(string(name(x)), (units(x) === nothing ? "" : string(" ", unit # Lookups methods Lookups.metadata(dim::Dimension) = metadata(lookup(dim)) -Lookups.index(dim::Dimension{<:AbstractArray}) = index(val(dim)) -Lookups.index(dim::Dimension{<:Val}) = unwrap(index(val(dim))) - Lookups.bounds(dim::Dimension) = bounds(val(dim)) Lookups.intervalbounds(dim::Dimension, args...) = intervalbounds(val(dim), args...) for f in (:shiftlocus, :maybeshiftlocus) @@ -255,6 +248,9 @@ end @inline selectindices(ds::DimTuple, sel::Tuple) = selectindices(val(ds), sel) @inline selectindices(dim::Dimension, sel) = selectindices(val(dim), sel) +# Deprecated +Lookups.index(dim::Dimension{<:AbstractArray}) = index(val(dim)) +Lookups.index(dim::Dimension{<:Val}) = unwrap(index(val(dim))) # Base methods const ArrayOrVal = Union{AbstractArray,Val} @@ -309,7 +305,7 @@ _dim2boundsmatrix(::Locus, span::Explicit, lookup) = val(span) function _dim2boundsmatrix(::Locus, span::Regular, lookup) # Only offset starts and reuse them for ends, # so floating point error is the same. - starts = Lookups._shiftindexlocus(Start(), lookup) + starts = Lookups._shiftlocus(Start(), lookup) dest = Array{eltype(starts),2}(undef, 2, length(starts)) # Use `bounds` as the start/end values if order(lookup) isa ReverseOrdered diff --git a/src/Dimensions/format.jl b/src/Dimensions/format.jl index ae92e79ae..9aa441d21 100644 --- a/src/Dimensions/format.jl +++ b/src/Dimensions/format.jl @@ -11,7 +11,7 @@ Errors are thrown if dims don't match the array dims or size, and any fields holding `Auto-` objects are filled with guessed objects. If a [`Lookup`](@ref) hasn't been specified, a lookup is chosen -based on the type and element type of the index. +based on the type and element type of the values. """ format(dims, A::AbstractArray) = format((dims,), A) function format(dims::NamedTuple, A::AbstractArray) @@ -49,99 +49,99 @@ format(v, D::Type, axis::AbstractRange) = _valformaterror(v, D) # Format Lookups # No more identification required for NoLookup -format(m::NoLookup, D::Type, index, axis::AbstractRange) = m -format(m::NoLookup, D::Type, index::AutoIndex, axis::AbstractRange) = NoLookup(axis) +format(m::NoLookup, D::Type, values, axis::AbstractRange) = m +format(m::NoLookup, D::Type, values::AutoValues, axis::AbstractRange) = NoLookup(axis) # # AutoLookup -function format(m::AutoLookup, D::Type, index::AbstractArray{T}, axis::AbstractRange) where T - # A mixed type index is Categorical +function format(m::AutoLookup, D::Type, values::AbstractArray{T}, axis::AbstractRange) where T + # A mixed type lookup is Categorical m = if isconcretetype(T) Sampled(; order=order(m), span=span(m), sampling=sampling(m), metadata=metadata(m)) else o = order(m) isa AutoOrder ? Unordered() : order(m) Categorical(; order=o, metadata=metadata(m)) end - format(m, D, index, axis) + format(m, D, values, axis) end -function format(m::AutoLookup, D::Type, index::AbstractArray{<:CategoricalEltypes}, axis::AbstractRange) - o = _format(order(m), D, index) - return Categorical(index; order=o, metadata=metadata(m)) +function format(m::AutoLookup, D::Type, values::AbstractArray{<:CategoricalEltypes}, axis::AbstractRange) + o = _format(order(m), D, values) + return Categorical(values; order=o, metadata=metadata(m)) end -function format(m::Categorical, D::Type, index, axis::AbstractRange) - i = _format(index, axis) - o = _format(order(m), D, index) +function format(m::Categorical, D::Type, values, axis::AbstractRange) + i = _format(values, axis) + o = _format(order(m), D, values) return rebuild(m; data=i, order=o) end -# # Sampled -function format(m::AbstractSampled, D::Type, index, axis::AbstractRange) - i = _format(index, axis) - o = _format(order(m), D, index) - sp = _format(span(m), D, index) - sa = _format(sampling(m), sp, D, index) +# Sampled +function format(m::AbstractSampled, D::Type, values, axis::AbstractRange) + i = _format(values, axis) + o = _format(order(m), D, values) + sp = _format(span(m), D, values) + sa = _format(sampling(m), sp, D, values) x = rebuild(m; data=i, order=o, span=sp, sampling=sa) return x end -# # Transformed -format(m::Transformed, D::Type, index::AutoIndex, axis::AbstractRange) = +# Transformed +format(m::Transformed, D::Type, values::AutoValues, axis::AbstractRange) = rebuild(m; dim=D(), data=axis) -format(m::Transformed, D::Type, index, axis::AbstractRange) = rebuild(m; dim=D()) +format(m::Transformed, D::Type, values, axis::AbstractRange) = rebuild(m; dim=D()) -# Index -_format(index::AbstractArray, axis::AbstractRange) = index -_format(index::AutoLookup, axis::AbstractRange) = axis +# Values +_format(values::AbstractArray, axis::AbstractRange) = values +_format(values::AutoLookup, axis::AbstractRange) = axis # Order -_format(order::Order, D::Type, index) = order -_format(order::AutoOrder, D::Type, index) = Lookups.orderof(index) +_format(order::Order, D::Type, values) = order +_format(order::AutoOrder, D::Type, values) = Lookups.orderof(values) # Span -_format(span::AutoSpan, D::Type, index::Union{AbstractArray,Val}) = - _format(Irregular(), D, index) -_format(span::AutoSpan, D::Type, index::AbstractRange) = Regular(step(index)) -_format(span::Regular{AutoStep}, D::Type, index::Union{AbstractArray,Val}) = _arraynosteperror() -_format(span::Regular{AutoStep}, D::Type, index::LinRange) = Regular(step(index)) -_format(span::Regular{AutoStep}, D::Type, index::AbstractRange) = Regular(step(index)) -_format(span::Regular, D::Type, index::Union{AbstractArray,Val}) = span -function _format(span::Regular, D::Type, index::AbstractRange) - step(span) isa Number && !(step(span) ≈ step(index)) && _steperror(index, span) +_format(span::AutoSpan, D::Type, values::Union{AbstractArray,Val}) = + _format(Irregular(), D, values) +_format(span::AutoSpan, D::Type, values::AbstractRange) = Regular(step(values)) +_format(span::Regular{AutoStep}, D::Type, values::Union{AbstractArray,Val}) = _arraynosteperror() +_format(span::Regular{AutoStep}, D::Type, values::LinRange) = Regular(step(values)) +_format(span::Regular{AutoStep}, D::Type, values::AbstractRange) = Regular(step(values)) +_format(span::Regular, D::Type, values::Union{AbstractArray,Val}) = span +function _format(span::Regular, D::Type, values::AbstractRange) + step(span) isa Number && !(step(span) ≈ step(values)) && _steperror(values, span) return span end -function _format(span::Regular, D::Type, index::LinRange{T}) where T - step(span) isa Number && step(index) > zero(T) && !(step(span) ≈ step(index)) && _steperror(index, span) +function _format(span::Regular, D::Type, values::LinRange{T}) where T + step(span) isa Number && step(values) > zero(T) && !(step(span) ≈ step(values)) && _steperror(values, span) return span end -_format(span::Irregular{AutoBounds}, D, index) = Irregular(nothing, nothing) -_format(span::Irregular{<:Tuple}, D, index) = span -_format(span::Explicit, D, index) = span +_format(span::Irregular{AutoBounds}, D, values) = Irregular(nothing, nothing) +_format(span::Irregular{<:Tuple}, D, values) = span +_format(span::Explicit, D, values) = span # Sampling -_format(sampling::AutoSampling, span::Span, D::Type, index) = Points() +_format(sampling::AutoSampling, span::Span, D::Type, values) = Points() _format(::AutoSampling, ::Span, D::Type, ::AbstractArray{<:IntervalSets.Interval}) = Intervals(Start()) -_format(sampling::AutoSampling, span::Explicit, D::Type, index) = - Intervals(_format(locus(sampling), D, index)) +_format(sampling::AutoSampling, span::Explicit, D::Type, values) = + Intervals(_format(locus(sampling), D, values)) # For ambiguity, not likely to happen in practice _format(::AutoSampling, ::Explicit, D::Type, ::AbstractArray{<:IntervalSets.Interval}) = - Intervals(_format(locus(sampling), D, index)) -_format(sampling::Points, span::Span, D::Type, index) = sampling -_format(sampling::Points, span::Explicit, D::Type, index) = _explicitpoints_error() -_format(sampling::Intervals, span::Span, D::Type, index) = - rebuild(sampling, _format(locus(sampling), D, index)) + Intervals(_format(locus(sampling), D, values)) +_format(sampling::Points, span::Span, D::Type, values) = sampling +_format(sampling::Points, span::Explicit, D::Type, values) = _explicitpoints_error() +_format(sampling::Intervals, span::Span, D::Type, values) = + rebuild(sampling, _format(locus(sampling), D, values)) # Locus -_format(locus::AutoLocus, D::Type, index) = Center() +_format(locus::AutoLocus, D::Type, values) = Center() # Time dimensions need to default to the Start() locus, as that is # nearly always the _format and Center intervals are difficult to # calculate with DateTime step values. -_format(locus::AutoLocus, D::Type{<:TimeDim}, index) = Start() -_format(locus::Locus, D::Type, index) = locus +_format(locus::AutoLocus, D::Type{<:TimeDim}, values) = Start() +_format(locus::Locus, D::Type, values) = locus -_order(index) = first(index) <= last(index) ? ForwardOrdered() : ReverseOrdered() +_order(values) = first(values) <= last(values) ? ForwardOrdered() : ReverseOrdered() checkaxis(lookup::Transformed, axis) = nothing checkaxis(lookup, axis) = first(axes(lookup)) == axis || _checkaxiserror(lookup, axis) @noinline _explicitpoints_error() = throw(ArgumentError("Cannot use Explicit span with Points sampling")) -@noinline _steperror(index, span) = - throw(ArgumentError("lookup step $(step(span)) does not match index step $(step(index))")) +@noinline _steperror(values, span) = + throw(ArgumentError("lookup step $(step(span)) does not match lookup step $(step(values))")) @noinline _arraynosteperror() = - throw(ArgumentError("`Regular` must specify `step` size with an index other than `AbstractRange`")) + throw(ArgumentError("`Regular` must specify `step` size with values other than `AbstractRange`")) @noinline _checkaxiserror(lookup, axis) = throw(DimensionMismatch( "axes of $(basetypeof(lookup)) of $(first(axes(lookup))) do not match array axis of $axis" diff --git a/src/Dimensions/indexing.jl b/src/Dimensions/indexing.jl index 6827e5885..b590e9aaa 100644 --- a/src/Dimensions/indexing.jl +++ b/src/Dimensions/indexing.jl @@ -14,9 +14,9 @@ for f in (:getindex, :view, :dotview) @propagate_inbounds function Base.$f(d::Dimension{<:AbstractArray}, i::SelectorOrInterval) Base.$f(d, selectindices(val(d), i)) end - # Everything else (like custom indexing from other packages) passes through to the parent @propagate_inbounds function Base.$f(d::Dimension{<:AbstractArray}, i) - Base.$f(parent(d), i) + x = Base.$f(parent(d), i) + x isa AbstractArray ? rebuild(d, x) : x end end end @@ -28,9 +28,7 @@ end Convert a `Dimension` or `Selector` `I` to indices of `Int`, `AbstractArray` or `Colon`. """ -@inline dims2indices(dim::Dimension, I::StandardIndices) = I @inline dims2indices(dim::Dimension, I) = _dims2indices(dim, I) - @inline dims2indices(x, I) = dims2indices(dims(x), I) @inline dims2indices(::Nothing, I) = _dimsnotdefinederror() @inline dims2indices(::Tuple{}, I) = () @@ -106,13 +104,16 @@ _unwrapdim(dim::Dimension) = val(dim) _unwrapdim(x) = x # Single dim methods +# Simply unwrap dimensions +@inline _dims2indices(dim::Dimension, seldim::Dimension) = _dims2indices(dim, val(seldim)) # A Dimension type always means Colon(), as if it was constructed with the default value. @inline _dims2indices(dim::Dimension, ::Type{<:Dimension}) = Colon() # Nothing means nothing was passed for this dimension +@inline _dims2indices(dim::Dimension, i::AbstractBeginEndRange) = i +@inline _dims2indices(dim::Dimension, i::Union{LU.Begin,LU.End,Type{LU.Begin},Type{LU.End}}) = + to_indices(parent(dim), (i,))[1] @inline _dims2indices(dim::Dimension, ::Nothing) = Colon() -# Simply unwrap dimensions -@inline _dims2indices(dim::Dimension, seldim::Dimension) = - Lookups.selectindices(val(dim), val(seldim)) +@inline _dims2indices(dim::Dimension, x) = Lookups.selectindices(val(dim), x) function _extent_as_intervals(extent::Extents.Extent{Keys}) where Keys map(map(key2dim, Keys), values(extent)) do k, v diff --git a/src/Lookups/Lookups.jl b/src/Lookups/Lookups.jl index 8b1e55362..306911fe4 100644 --- a/src/Lookups/Lookups.jl +++ b/src/Lookups/Lookups.jl @@ -25,9 +25,12 @@ import InvertedIndices using InvertedIndices: Not using Base: tail, OneTo, @propagate_inbounds -export order, sampling, span, bounds, locus, hasselection, dim, - metadata, units, sort, selectindices, val, index, reducelookup, - shiftlocus, maybeshiftlocus, intervalbounds +export order, sampling, span, bounds, hasselection, dim, + metadata, units, sort, selectindices, val, reducelookup, + locus, shiftlocus, maybeshiftlocus, intervalbounds + +# Deprecated +export index export issampled, iscategorical, iscyclic, isnolookup, isintervals, ispoints, isregular, isexplicit, isstart, iscenter, isend, isordered, isforward, isreverse @@ -41,9 +44,9 @@ export LookupTrait export Order, Ordered, ForwardOrdered, ReverseOrdered, Unordered, AutoOrder export Sampling, Points, Intervals, AutoSampling, NoSampling export Span, Regular, Irregular, Explicit, AutoSpan, NoSpan -export Locus, Center, Start, End, AutoLocus +export Position, Locus, Center, Start, End, AutoLocus, AutoPosition export Metadata, NoMetadata -export AutoStep, AutoBounds, AutoIndex +export AutoStep, AutoBounds, AutoValues export Lookup export AutoLookup, NoLookup @@ -63,6 +66,7 @@ include("lookup_traits.jl") include("lookup_arrays.jl") include("predicates.jl") include("selector.jl") +include("beginend.jl") include("indexing.jl") include("methods.jl") include("utils.jl") diff --git a/src/Lookups/beginend.jl b/src/Lookups/beginend.jl new file mode 100644 index 000000000..d1227532f --- /dev/null +++ b/src/Lookups/beginend.jl @@ -0,0 +1,96 @@ +struct LazyMath{T,F} + f::F +end +LazyMath{T}(f::F) where {T,F} = LazyMath{T,F}(f) +Base.show(io::IO, l::LazyMath{T}) where T = print(io, _print_f(T, l.f)) + +const BeginEndRangeVals = Union{Begin,End,LazyMath,Int} + +# Ranges +abstract type AbstractBeginEndRange <: Function end +struct BeginEndRange{A<:BeginEndRangeVals,B<:BeginEndRangeVals} <: AbstractBeginEndRange + start::A + stop::B +end +struct BeginEndStepRange{A<:BeginEndRangeVals,B<:BeginEndRangeVals} <: AbstractBeginEndRange + start::A + step::Int + stop::B +end + +Base.first(r::AbstractBeginEndRange) = r.start +Base.last(r::AbstractBeginEndRange) = r.stop +Base.step(r::BeginEndStepRange) = r.step + +(::Colon)(a::Int, b::Union{Begin,End,Type{Begin},Type{End},LazyMath}) = BeginEndRange(a, _x(b)) +(::Colon)(a::Union{Begin,End,Type{Begin},Type{End},LazyMath}, b::Int) = BeginEndRange(_x(a), b) +(::Colon)(a::Union{Begin,End,Type{Begin},Type{End},LazyMath}, b::Union{Begin,End,Type{Begin},Type{End},LazyMath}) = + BeginEndRange(_x(a), _x(b)) + +(::Colon)(a::Union{Int,LazyMath}, step::Int, b::Union{Type{Begin},Type{End}}) = BeginEndStepRange(a, step, _x(b)) +(::Colon)(a::Union{Type{Begin},Type{End}}, step::Int, b::Union{Int,LazyMath}) = BeginEndStepRange(_x(a), step, b) +(::Colon)(a::Union{Type{Begin},Type{End}}, step::Int, b::Union{Type{Begin},Type{End}}) = + BeginEndStepRange(_x(a), step, _x(b)) + +_x(T::Type) = T() +_x(x) = x + +Base.to_indices(A, inds, (r, args...)::Tuple{BeginEndRange,Vararg}) = + (_to_index(inds[1], r.start):_to_index(inds[1], r.stop), to_indices(A, Base.tail(inds), args)...) +Base.to_indices(A, inds, (r, args...)::Tuple{BeginEndStepRange,Vararg}) = + (_to_index(inds[1], r.start):r.step:_to_index(inds[1], r.stop), to_indices(A, Base.tail(inds), args)...) +Base._to_indices1(A, inds, ::Type{Begin}) = first(inds[1]) +Base._to_indices1(A, inds, ::Type{End}) = last(inds[1]) +Base._to_indices1(A, inds, ::Begin) = first(inds[1]) +Base._to_indices1(A, inds, ::End) = last(inds[1]) + +_to_index(inds, a::Int) = a +_to_index(inds, ::Begin) = first(inds) +_to_index(inds, ::End) = last(inds) +_to_index(inds, l::LazyMath{End}) = l.f(last(inds)) +_to_index(inds, l::LazyMath{Begin}) = l.f(first(inds)) + +Base.checkindex(::Type{Bool}, inds::AbstractUnitRange, ber::AbstractBeginEndRange) = + Base.checkindex(Bool, inds, _to_index(inds, first(ber)):_to_index(inds, last(ber))) + +for f in (:+, :-, :*, :÷, :|, :&) + @eval Base.$f(::Type{T}, i::Int) where T <: Union{Begin,End} = LazyMath{T}(Base.Fix2($f, i)) + @eval Base.$f(i::Int, ::Type{T}) where T <: Union{Begin,End} = LazyMath{T}(Base.Fix1($f, i)) + @eval Base.$f(::T, i::Int) where T <: Union{Begin,End} = LazyMath{T}(Base.Fix2($f, i)) + @eval Base.$f(i::Int, ::T) where T <: Union{Begin,End} = LazyMath{T}(Base.Fix1($f, i)) + @eval Base.$f(x::LazyMath{T}, i::Int) where T = LazyMath{T}(Base.Fix2(x.f ∘ $f, i)) + @eval Base.$f(i::Int, x::LazyMath{T}) where T = LazyMath{T}(Base.Fix1(x.f ∘ $f, i)) +end + + +Base.show(io::IO, ::MIME"text/plain", r::AbstractBeginEndRange) = show(io, r) +function Base.show(io::IO, r::BeginEndRange) + _show(io, first(r)) + print(io, ':') + _show(io, last(r)) +end +function Base.show(io::IO, r::BeginEndStepRange) + _show(io, first(r)) + print(io, ':') + show(io, step(r)) + print(io, ':') + _show(io, last(r)) +end + +_show(io, x::Union{Begin,End}) = show(io, typeof(x)) +_show(io, x) = show(io, x) +# Here we recursively print `Fix1` and `Fix2` either left or right +# to recreate the function +_print_f(T, f) = string(T, _pf(f)) +_print_f(T, f::Base.ComposedFunction) = string('(', _print_f(T, f.outer), ')', _print_f("", f.inner)) +_print_f(T, f::Base.Fix1) = string(f.x, _print_f(T, f.f)) +_print_f(T, f::Base.Fix2) = string(_print_f(T, f.f), f.x) + +_pf(::typeof(div)) = "÷" +_pf(f) = string(f) + +for T in (UnitRange, AbstractUnitRange, StepRange, StepRangeLen, LinRange) + for f in (:getindex, :view, :dotview) + @eval Base.$f(A::$T, i::AbstractBeginEndRange) = Base.$f(A, to_indices(A, (i,))...) + end +end diff --git a/src/Lookups/indexing.jl b/src/Lookups/indexing.jl index 01c50ac10..87c95b7c1 100644 --- a/src/Lookups/indexing.jl +++ b/src/Lookups/indexing.jl @@ -9,7 +9,12 @@ for f in (:getindex, :view, :dotview) rebuild(l; data=Base.$f(parent(l), i)) # Selector gets processed with `selectindices` @propagate_inbounds Base.$f(l::Lookup, i::SelectorOrInterval) = Base.$f(l, selectindices(l, i)) - # Everything else (like custom indexing from other packages) passes through to the parent - @propagate_inbounds Base.$f(l::Lookup, i) = Base.$f(parent(l), i) + @propagate_inbounds function Base.$f(l::Lookup, i) + x = Base.$f(parent(l), i) + x isa AbstractArray ? rebuild(l; data=x) : x + end + @propagate_inbounds function Base.$f(l::Lookup, i::AbstractBeginEndRange) + l[Base.to_indices(l, (i,))...] + end end end diff --git a/src/Lookups/lookup_arrays.jl b/src/Lookups/lookup_arrays.jl index 52459b2fc..0ee18b899 100644 --- a/src/Lookups/lookup_arrays.jl +++ b/src/Lookups/lookup_arrays.jl @@ -5,8 +5,8 @@ Types defining the behaviour of a lookup index, how it is plotted and how [`Selector`](@ref)s like [`Between`](@ref) work. -A `Lookup` may be [`NoLookup`](@ref) indicating that the index is just the -underlying array axis, [`Categorical`](@ref) for ordered or unordered categories, +A `Lookup` may be [`NoLookup`](@ref) indicating that there are no +lookup values, [`Categorical`](@ref) for ordered or unordered categories, or a [`Sampled`](@ref) index for [`Points`](@ref) or [`Intervals`](@ref). """ abstract type Lookup{T,N} <: AbstractArray{T,N} end @@ -19,9 +19,12 @@ sampling(lookup::Lookup) = NoSampling() dims(::Lookup) = nothing val(l::Lookup) = parent(l) -index(l::Lookup) = parent(l) locus(l::Lookup) = Center() +# Deprecated +index(l::Lookup) = parent(l) +@deprecate locus locus + Base.eltype(l::Lookup{T}) where T = T Base.parent(l::Lookup) = l.data Base.size(l::Lookup) = size(parent(l)) @@ -61,10 +64,10 @@ end AutoLookup <: Lookup AutoLookup() - AutoLookup(index=AutoIndex(); kw...) + AutoLookup(values=AutoValues(); kw...) Automatic [`Lookup`](@ref), the default lookup. It will be converted automatically -to another [`Lookup`](@ref) when it is possible to detect it from the index. +to another [`Lookup`](@ref) when it is possible to detect it from the lookup values. Keywords will be used in the detected `Lookup` constructor. """ @@ -72,7 +75,7 @@ struct AutoLookup{T,A<:AbstractVector{T},K} <: Lookup{T,1} data::A kw::K end -AutoLookup(index=AutoIndex(); kw...) = AutoLookup(index, kw) +AutoLookup(values=AutoValues(); kw...) = AutoLookup(values, kw) order(lookup::AutoLookup) = hasproperty(lookup.kw, :order) ? lookup.kw.order : AutoOrder() span(lookup::AutoLookup) = hasproperty(lookup.kw, :span) ? lookup.kw.span : AutoSpan() @@ -94,7 +97,7 @@ _bounds(::Unordered, l::Lookup) = (nothing, nothing) Aligned <: Lookup Abstract supertype for [`Lookup`](@ref)s -where the index is aligned with the array axes. +where the lookup is aligned with the array axes. This is by far the most common supertype for `Lookup`. """ @@ -112,7 +115,7 @@ A [`Lookup`](@ref) that is identical to the array axis. ## Example -Defining a `DimArray` without passing an index +Defining a `DimArray` without passing lookup values to the dimensions, it will be assigned `NoLookup`: ```jldoctest NoLookup @@ -141,7 +144,7 @@ NoLookup, NoLookup struct NoLookup{A<:AbstractVector{Int}} <: Aligned{Int,Order} data::A end -NoLookup() = NoLookup(AutoIndex()) +NoLookup() = NoLookup(AutoValues()) order(lookup::NoLookup) = ForwardOrdered() span(lookup::NoLookup) = Regular(1) @@ -153,7 +156,7 @@ Base.step(lookup::NoLookup) = 1 """ AbstractSampled <: Aligned -Abstract supertype for [`Lookup`](@ref)s where the index is +Abstract supertype for [`Lookup`](@ref)s where the lookup is aligned with the array, and is independent of other dimensions. [`Sampled`](@ref) is provided by this package. @@ -218,9 +221,9 @@ _bounds(::End, ::ReverseOrdered, span, lookup) = last(lookup) + step(span), firs const SAMPLED_ARGUMENTS_DOC = """ -- `data`: An `AbstractVector` of index values, matching the length of the curresponding +- `data`: An `AbstractVector` of lookup values, matching the length of the curresponding array axis. -- `order`: [`Order`](@ref)) indicating the order of the index, +- `order`: [`Order`](@ref)) indicating the order of the lookup, [`AutoOrder`](@ref) by default, detected from the order of `data` to be [`ForwardOrdered`](@ref), [`ReverseOrdered`](@ref) or [`Unordered`](@ref). These can be provided explicitly if they are known and performance is important. @@ -239,7 +242,7 @@ const SAMPLED_ARGUMENTS_DOC = """ Sampled <: AbstractSampled Sampled(data::AbstractVector, order::Order, span::Span, sampling::Sampling, metadata) - Sampled(data=AutoIndex(); order=AutoOrder(), span=AutoSpan(), sampling=Points(), metadata=NoMetadata()) + Sampled(data=AutoValues(); order=AutoOrder(), span=AutoSpan(), sampling=Points(), metadata=NoMetadata()) A concrete implementation of the [`Lookup`](@ref) [`AbstractSampled`](@ref). It can be used to represent @@ -247,7 +250,7 @@ A concrete implementation of the [`Lookup`](@ref) `Sampled` is capable of representing gridded data from a wide range of sources, allowing correct `bounds` and [`Selector`](@ref)s for points or intervals of regular, -irregular, forward and reverse indexes. +irregular, forward and reverse lookups. On `AbstractDimArray` construction, `Sampled` lookup is assigned for all lookups of `AbstractRange` not assigned to [`Categorical`](@ref). @@ -260,8 +263,8 @@ $SAMPLED_ARGUMENTS_DOC Create an array with [`Interval`] sampling, and `Regular` span for a vector with known spacing. -We set the [`Locus`](@ref) of the `Intervals` to `Start` specifying -that the index values are for the positions at the start of each interval. +We set the [`locus`](@ref) of the `Intervals` to `Start` specifying +that the lookup values are for the locuss at the start of each interval. ```jldoctest Sampled using DimensionalData, DimensionalData.Lookups @@ -292,7 +295,7 @@ struct Sampled{T,A<:AbstractVector{T},O,Sp,Sa,M} <: AbstractSampled{T,O,Sp,Sa} sampling::Sa metadata::M end -function Sampled(data=AutoIndex(); +function Sampled(data=AutoValues(); order=AutoOrder(), span=AutoSpan(), sampling=AutoSampling(), metadata=NoMetadata() ) @@ -412,7 +415,7 @@ struct Cyclic{X,T,A<:AbstractVector{T},O,Sp,Sa,M,C} <: AbstractCyclic{X,T,O,Sp,S new{X,T,A,O,Sp,Sa,M,C}(data, order, span, sampling, metadata, cycle, cycle_status) end end -function Cyclic(data=AutoIndex(); +function Cyclic(data=AutoValues(); order=AutoOrder(), span=AutoSpan(), sampling=AutoSampling(), metadata=NoMetadata(), cycle, # Mandatory keyword, there are too many possible bugs with auto detection @@ -461,18 +464,18 @@ end Categorical(o::Order) Categorical(; order=Unordered()) -An Lookup where the values are categories. +A [`Lookup`](@ref) where the values are categories. -This will be automatically assigned if the index contains `AbstractString`, +This will be automatically assigned if the lookup contains `AbstractString`, `Symbol` or `Char`. Otherwise it can be assigned manually. [`Order`](@ref) will be determined automatically where possible. ## Arguments -- `data`: An `AbstractVector` of index values, matching the length of the curresponding +- `data`: An `AbstractVector` matching the length of the curresponding array axis. -- `order`: [`Order`](@ref)) indicating the order of the index, +- `order`: [`Order`](@ref)) indicating the order of the lookup, [`AutoOrder`](@ref) by default, detected from the order of `data` to be `ForwardOrdered`, `ReverseOrdered` or `Unordered`. Can be provided if this is known and performance is important. @@ -502,7 +505,7 @@ struct Categorical{T,A<:AbstractVector{T},O<:Order,M} <: AbstractCategorical{T,O order::O metadata::M end -function Categorical(data=AutoIndex(); order=AutoOrder(), metadata=NoMetadata()) +function Categorical(data=AutoValues(); order=AutoOrder(), metadata=NoMetadata()) Categorical(data, order, metadata) end @@ -520,7 +523,7 @@ end """ Unaligned <: Lookup -Abstract supertype for [`Lookup`](@ref) where the index is not aligned to the grid. +Abstract supertype for [`Lookup`](@ref) where the lookup is not aligned to the grid. Indexing an [`Unaligned`](@ref) with [`Selector`](@ref)s must provide all other [`Unaligned`](@ref) dimensions. @@ -572,7 +575,7 @@ struct Transformed{T,A<:AbstractVector{T},F,D,M} <: Unaligned{T,1} metadata::M end function Transformed(f; metadata=NoMetadata()) - Transformed(AutoIndex(), f, AutoDim(), metadata) + Transformed(AutoValues(), f, AutoDim(), metadata) end function Transformed(f, data::AbstractArray; metadata=NoMetadata()) Transformed(data, f, AutoDim(), metadata) @@ -758,44 +761,44 @@ end @inline reducelookup(lookup::AbstractSampled) = _reducelookup(span(lookup), lookup) @inline _reducelookup(::Irregular, lookup::AbstractSampled) = begin - rebuild(lookup; data=_reduceindex(lookup), order=ForwardOrdered()) + rebuild(lookup; data=_reducevalues(lookup), order=ForwardOrdered()) end @inline _reducelookup(span::Regular, lookup::AbstractSampled) = begin newstep = step(span) * length(lookup) - newindex = _reduceindex(lookup, newstep) - # Make sure the step type matches the new index eltype - newstep = convert(promote_type(eltype(newindex), typeof(newstep)), newstep) + newvalues = _reducevalues(lookup, newstep) + # Make sure the step type matches the new eltype + newstep = convert(promote_type(eltype(newvalues), typeof(newstep)), newstep) newspan = Regular(newstep) - rebuild(lookup; data=newindex, order=ForwardOrdered(), span=newspan) + rebuild(lookup; data=newvalues, order=ForwardOrdered(), span=newspan) end @inline _reducelookup( span::Regular{<:Dates.CompoundPeriod}, lookup::AbstractSampled ) = begin newstep = Dates.CompoundPeriod(step(span).periods .* length(lookup)) # We don't pass the step here - the range doesn't work with CompoundPeriod - newindex = _reduceindex(lookup) - # Make sure the step type matches the new index eltype + newvalues = _reducevalues(lookup) + # Make sure the step type matches the new eltype newspan = Regular(newstep) - rebuild(lookup; data=newindex, order=ForwardOrdered(), span=newspan) + rebuild(lookup; data=newvalues, order=ForwardOrdered(), span=newspan) end @inline _reducelookup(span::Explicit, lookup::AbstractSampled) = begin bnds = val(span) newstep = bnds[2] - bnds[1] - newindex = _reduceindex(lookup, newstep) - # Make sure the step type matches the new index eltype - newstep = convert(promote_type(eltype(newindex), typeof(newstep)), newstep) + newvalues = _reducevalues(lookup, newstep) + # Make sure the step type matches the new eltype + newstep = convert(promote_type(eltype(newvalues), typeof(newstep)), newstep) newspan = Explicit(reshape([bnds[1, 1]; bnds[2, end]], 2, 1)) - newlookup = rebuild(lookup; data=newindex, order=ForwardOrdered(), span=newspan) -end -# Get the index value at the reduced locus. -# This is the start, center or end point of the whole index. -@inline _reduceindex(lookup::Lookup, step=nothing) = _reduceindex(locus(lookup), lookup, step) -@inline _reduceindex(locus::Start, lookup::Lookup, step) = _mayberange(first(lookup), step) -@inline _reduceindex(locus::End, lookup::Lookup, step) = _mayberange(last(lookup), step) -@inline _reduceindex(locus::Center, lookup::Lookup, step) = begin - index = parent(lookup) - len = length(index) - newval = centerval(index, len) + newlookup = rebuild(lookup; data=newvalues, order=ForwardOrdered(), span=newspan) +end +# Get the lookup value at the reduced locus. +# This is the start, center or end point of the whole lookup. +@inline _reducevalues(lookup::Lookup, step=nothing) = _reducevalues(locus(lookup), lookup, step) +@inline _reducevalues(locus::Start, lookup::Lookup, step) = _mayberange(first(lookup), step) +@inline _reducevalues(locus::End, lookup::Lookup, step) = _mayberange(last(lookup), step) +@inline _reducevalues(locus::Center, lookup::Lookup, step) = begin + values = parent(lookup) + len = length(values) + newval = centerval(values, len) _mayberange(newval, step) end # Ranges with a known step always return a range @@ -803,17 +806,17 @@ _mayberange(x, step) = x:step:x # Arrays return a vector _mayberange(x, step::Nothing) = [x] -@inline centerval(index::AbstractArray{<:Number}, len) = (first(index) + last(index)) / 2 -@inline function centerval(index::AbstractArray{<:DateTime}, len) - f = first(index) - l = last(index) +@inline centerval(values::AbstractArray{<:Number}, len) = (first(values) + last(values)) / 2 +@inline function centerval(values::AbstractArray{<:DateTime}, len) + f = first(values) + l = last(values) if f <= l - return (l - f) / 2 + first(index) + return (l - f) / 2 + first(values) else - return (f - l) / 2 + last(index) + return (f - l) / 2 + last(values) end end -@inline centerval(index::AbstractArray, len) = index[len ÷ 2 + 1] +@inline centerval(values::AbstractArray, len) = values[len ÷ 2 + 1] ordering(::ForwardOrdered) = Base.Order.ForwardOrdering() ordering(::ReverseOrdered) = Base.Order.ReverseOrdering() diff --git a/src/Lookups/lookup_traits.jl b/src/Lookups/lookup_traits.jl index 176f3805d..c1e57182b 100644 --- a/src/Lookups/lookup_traits.jl +++ b/src/Lookups/lookup_traits.jl @@ -72,9 +72,9 @@ isrev(::Type{<:ForwardOrdered}) = false isrev(::Type{<:ReverseOrdered}) = true """ - Locus <: LookupTrait + Position <: LookupTrait -Abstract supertype of types that indicate the position of index values +Abstract supertype of types that indicate the locus of index values where they represent [`Intervals`](@ref). These allow for values array cells to align with the [`Start`](@ref), @@ -83,47 +83,68 @@ These allow for values array cells to align with the [`Start`](@ref), This means they can be plotted with correct axis markers, and allows automatic converrsions to between formats with different standards (such as NetCDF and GeoTiff). """ -abstract type Locus <: LookupTrait end +abstract type Position <: LookupTrait end """ - Center <: Locus + Center <: Position Center() -Indicates a lookup value is for the center of its corresponding array cell. +Used to specify lookup values correspond to the center locus in an interval. """ -struct Center <: Locus end +struct Center <: Position end """ - Start <: Locus + Start <: Position Start() -Indicates a lookup value is for the start of its corresponding array cell, -in the direction of the lookup index order. +Used to specify lookup values correspond to the center +locus of an interval. """ -struct Start <: Locus end +struct Start <: Position end """ - End <: Locus + Begin <: Position + + Begin() + +Used to specify the `begin` index of a `Dimension` axis. +as regular `begin` will not work with named dimensions. + +Can be used with `:` to create a `BeginEndRange` or +`BeginEndStepRange`. +""" +struct Begin <: Position end + +""" + End <: Position End() -Indicates a lookup value is for the end of its corresponding array cell, -in the direction of the lookup index order. +Used to specify the `end` index of a `Dimension` axis, +as regular `end` will not work with named dimensions. +Can be used with `:` to create a `BeginEndRange` or +`BeginEndStepRange`. + +Also used to specify lookup values correspond to the end +locus of an interval. """ -struct End <: Locus end +struct End <: Position end """ - AutoLocus <: Locus + AutoPosition <: Position - AutoLocus() + AutoPosition() -Indicates a interval where the index position is not yet known. +Indicates a interval where the index locus is not yet known. This will be filled with a default value on object construction. """ -struct AutoLocus <: Locus end +struct AutoPosition <: Position end +# Locus does not include `Begin` +const Locus = Union{AutoPosition,Start,Center,End} +const AutoLocus = AutoPosition """ Sampling <: LookupTrait @@ -137,7 +158,7 @@ struct NoSampling <: Sampling end locus(sampling::NoSampling) = Center() struct AutoSampling <: Sampling end -locus(sampling::AutoSampling) = AutoLocus() +locus(sampling::AutoSampling) = AutoPosition() """ Points <: Sampling @@ -155,18 +176,18 @@ locus(sampling::Points) = Center() """ Intervals <: Sampling - Intervals(locus::Locus) + Intervals(locus::Position) [`Sampling`](@ref) specifying that sampled values are the mean (or similar) value over an _interval_, rather than at one specific point. -Intervals require a [`Locus`](@ref) of [`Start`](@ref), [`Center`](@ref) or +Intervals require a [`locus`](@ref) of [`Start`](@ref), [`Center`](@ref) or [`End`](@ref) to define the location in the interval that the index values refer to. """ -struct Intervals{L} <: Sampling - locus::L +struct Intervals{P} <: Sampling + locus::P end -Intervals() = Intervals(AutoLocus()) +Intervals() = Intervals(AutoPosition()) locus(sampling::Intervals) = sampling.locus rebuild(::Intervals, locus) = Intervals(locus) @@ -254,12 +275,12 @@ Base.:(==)(l1::Explicit, l2::Explicit) = val(l1) == val(l2) Adapt.adapt_structure(to, s::Explicit) = Explicit(Adapt.adapt_structure(to, val(s))) """ - AutoIndex + AutoValues -Detect a `Lookup` index from the context. This is used in `NoLookup` to simply +Detect `Lookup` values from the context. This is used in `NoLookup` to simply use the array axis as the index when the array is constructed, and in `set` to change the `Lookup` type without changing the index values. """ -struct AutoIndex <: AbstractVector{Int} end +struct AutoValues <: AbstractVector{Int} end -Base.size(::AutoIndex) = (0,) +Base.size(::AutoValues) = (0,) diff --git a/src/Lookups/methods.jl b/src/Lookups/methods.jl index e76f82b52..1d0e49122 100644 --- a/src/Lookups/methods.jl +++ b/src/Lookups/methods.jl @@ -3,7 +3,7 @@ Base.reverse(lookup::NoLookup) = lookup Base.reverse(lookup::AutoLookup) = lookup function Base.reverse(lookup::AbstractCategorical) - i = reverse(index(lookup)) + i = reverse(parent(lookup)) o = reverse(order(lookup)) rebuild(lookup; data=i, order=o) end diff --git a/src/Lookups/selector.jl b/src/Lookups/selector.jl index bf507232d..19a3f3a5e 100644 --- a/src/Lookups/selector.jl +++ b/src/Lookups/selector.jl @@ -77,6 +77,10 @@ function selectindices(l::Lookup, sel::Not; kw...) indices = selectindices(l, sel.skip; kw...) return first(to_indices(l, (Not(indices),))) end +@inline function selectindices(l::Lookup, sel; kw...) + selstr = sprint(show, sel) + throw(ArgumentError("Invalid index `$selstr`. Did you mean `At($selstr)`? Use stardard indices, `Selector`s, or `Val` for compile-time `At`.")) +end """ At <: IntSelector @@ -231,7 +235,7 @@ Selector that selects the nearest index to `x`. With [`Points`](@ref) this is simply the index values nearest to the `x`, however with [`Intervals`](@ref) it is the interval _center_ nearest to `x`. This will be offset from the index value for `Start` and -[`End`](@ref) loci. +[`End`](@ref) locuss. ## Example @@ -262,7 +266,7 @@ end near(lookup::NoLookup, sel::Near{<:Real}) = max(1, min(round(Int, val(sel)), lastindex(lookup))) function near(lookup::Lookup, sel::Near) !isregular(lookup) && !iscenter(lookup) && - throw(ArgumentError("Near is not implemented for Irregular or Explicit with Start or End loci. Use Contains")) + throw(ArgumentError("Near is not implemented for Irregular or Explicit with Start or End locus. Use Contains")) near(order(lookup), sampling(lookup), lookup, sel) end near(order::Order, ::NoSampling, lookup::Lookup, sel::Near) = at(lookup, At(val(sel))) @@ -273,7 +277,7 @@ function near(order::Ordered, ::Union{Intervals,Points}, lookup::Lookup, sel::Ne if v isa Union{Dates.DateTime,Dates.Date} v = eltype(lookup)(v) end - v_adj = _locus_adjust(locus(lookup), v, lookup) + v_adj = _adjust_locus(locus(lookup), v, lookup) # searchsortedfirst or searchsortedlast searchfunc = _searchfunc(order) # Search for the value @@ -304,13 +308,13 @@ function near(::Unordered, ::Union{Intervals,Points}, lookup::Lookup, sel::Near) throw(ArgumentError("`Near` has no meaning in an `Unordered` lookup")) end -_locus_adjust(locus::Center, v, lookup) = v -_locus_adjust(locus::Start, v, lookup) = v - abs(step(lookup)) / 2 -_locus_adjust(locus::End, v, lookup) = v + abs(step(lookup)) / 2 -_locus_adjust(locus::Start, v::Dates.TimeType, lookup) = v - (v - (v - abs(step(lookup)))) / 2 -_locus_adjust(locus::End, v::Dates.TimeType, lookup) = v + (v + abs(step(lookup)) - v) / 2 -_locus_adjust(locus::Start, v::Dates.Date, lookup) = v - (v - (v - abs(step(lookup)))) ÷ 2 -_locus_adjust(locus::End, v::Dates.Date, lookup) = v + (v + abs(step(lookup)) - v) ÷ 2 +_adjust_locus(locus::Center, v, lookup) = v +_adjust_locus(locus::Start, v, lookup) = v - abs(step(lookup)) / 2 +_adjust_locus(locus::End, v, lookup) = v + abs(step(lookup)) / 2 +_adjust_locus(locus::Start, v::Dates.TimeType, lookup) = v - (v - (v - abs(step(lookup)))) / 2 +_adjust_locus(locus::End, v::Dates.TimeType, lookup) = v + (v + abs(step(lookup)) - v) / 2 +_adjust_locus(locus::Start, v::Dates.Date, lookup) = v - (v - (v - abs(step(lookup)))) ÷ 2 +_adjust_locus(locus::End, v::Dates.Date, lookup) = v + (v + abs(step(lookup)) - v) ÷ 2 """ Contains <: IntSelector @@ -376,7 +380,7 @@ function contains(::Order, ::Points, l::Lookup{<:AbstractArray}, sel::Contains{< end # Intervals ----------------------------------- function contains(sampling::Intervals, l::Lookup, sel::Contains; err=_True()) - _locus_checkbounds(locus(l), l, sel) || return _selector_error_or_nothing(err, l, sel) + _checkbounds_locus(l, sel) || return _selector_error_or_nothing(err, l, sel) contains(order(l), span(l), sampling, locus(l), l, sel; err) end function contains( @@ -626,7 +630,7 @@ end # Regular Intervals ------------------------- # Adjust the value for the lookup locus before search function _between_side(side, o::Ordered, ::Regular, ::Intervals, l, interval, v) - adj = _locus_adjust(side, l) + adj = _adjust_locus(side, l) v1 = v + adj i = _searchfunc(side, o)(l, v1) # Sideshift (1 or -1) expands the selection to the outside of any touched intervals @@ -683,7 +687,7 @@ function _between_irreg_side(side, locus::Union{Start,End}, o, l, interval, v) s = _ordscalar(o) # Search for the value and offset per order/locus/side i = _searchfunc(o)(l, v; lt=_lt(side)) - i -= s * (_locscalar(locus) + _sideshift(side)) + i -= s * (_posscalar(locus) + _sideshift(side)) # Get the value on the interval edge cellbound = if i < firstindex(l) _maybeflipbounds(l, bounds(l))[1] @@ -734,16 +738,16 @@ function _close_interval(side::_Upper, l, interval::Interval{<:Any,:open}, cellb cellbound == interval.right ? i - _ordscalar(l) : i end -_locus_adjust(side, l) = _locus_adjust(side, locus(l), abs(step(span(l)))) -_locus_adjust(::_Lower, locus::Start, step) = zero(step) -_locus_adjust(::_Upper, locus::Start, step) = -step -_locus_adjust(::_Lower, locus::Center, step) = step/2 -_locus_adjust(::_Upper, locus::Center, step) = -step/2 -_locus_adjust(::_Lower, locus::End, step) = step -_locus_adjust(::_Upper, locus::End, step) = -zero(step) +_adjust_locus(side, l) = _adjust_locus(side, locus(l), abs(step(span(l)))) +_adjust_locus(::_Lower, locus::Start, step) = zero(step) +_adjust_locus(::_Upper, locus::Start, step) = -step +_adjust_locus(::_Lower, locus::Center, step) = step/2 +_adjust_locus(::_Upper, locus::Center, step) = -step/2 +_adjust_locus(::_Lower, locus::End, step) = step +_adjust_locus(::_Upper, locus::End, step) = -zero(step) -_locscalar(::Start) = 1 -_locscalar(::End) = 0 +_posscalar(::Start) = 1 +_posscalar(::End) = 0 _sideshift(::_Lower) = -1 _sideshift(::_Upper) = 1 _ordscalar(l) = _ordscalar(order(l)) @@ -870,7 +874,7 @@ end # Regular Intervals ------------------------- # Adjust the value for the lookup locus before search function _touches(side, o::Ordered, ::Regular, ::Intervals, l, sel, v) - adj = _locus_adjust(side, l) + adj = _adjust_locus(side, l) v1 = v + adj i = _searchfunc(side, o)(l, v1) # Sideshift (1 or -1) expands the selection to the outside of any touched sels @@ -911,7 +915,7 @@ function _touches_irreg_side(side, locus::Union{Start,End}, o, l, sel, v) ordered_lastindex(l) else # Search for the value and offset per order/locus/side - _searchfunc(o)(l, v; lt=_lt(side)) - _ordscalar(o) * _locscalar(locus) + _searchfunc(o)(l, v; lt=_lt(side)) - _ordscalar(o) * _posscalar(locus) end return i end @@ -1042,10 +1046,6 @@ function selectindices end # @inline selectindices(dim::Lookup, sel::Val) = selectindices(val(dim), At(sel)) # Standard indices are just returned. @inline selectindices(::Lookup, sel::StandardIndices) = sel -@inline function selectindices(l::Lookup, sel) - selstr = sprint(show, sel) - throw(ArgumentError("Invalid index `$selstr`. Did you mean `At($selstr)`? Use stardard indices, `Selector`s, or `Val` for compile-time `At`.")) -end # Vectors are mapped @inline selectindices(lookup::Lookup, sel::Selector{<:AbstractVector}) = [selectindices(lookup, rebuild(sel; val=v)) for v in val(sel)] @@ -1099,8 +1099,8 @@ _lt(::End) = (<=) _gt(::Locus) = (>=) _gt(::End) = (>) -_locus_checkbounds(loc, lookup::Lookup, sel::Selector) = _locus_checkbounds(loc, bounds(lookup), val(sel)) -_locus_checkbounds(loc, (l, h)::Tuple, v) = !(_lt(loc)(v, l) || _gt(loc)(v, h)) +_checkbounds_locus(l::Lookup, sel::Selector) = _checkbounds_locus(locus(l), bounds(l), val(sel)) +_checkbounds_locus(pos, (l, h)::Tuple, v) = !(_lt(pos)(v, l) || _gt(pos)(v, h)) _searchfunc(::ForwardOrdered) = searchsortedfirst _searchfunc(::ReverseOrdered) = searchsortedlast diff --git a/src/Lookups/set.jl b/src/Lookups/set.jl index 5484e1fd3..cd4aed760 100644 --- a/src/Lookups/set.jl +++ b/src/Lookups/set.jl @@ -15,7 +15,7 @@ _set(lookup::Lookup, newlookup::AbstractCategorical) = begin rebuild(newlookup; data=parent(lookup), order=o, metadata=md) end _set(lookup::AbstractSampled, newlookup::AutoLookup) = begin - # Update index + # Update lookup values lookup = _set(lookup, parent(newlookup)) o = _set(order(lookup), order(newlookup)) sa = _set(sampling(lookup), sampling(newlookup)) @@ -34,24 +34,24 @@ _set(lookup::Lookup, newlookup::AbstractSampled) = begin # Rebuild the new lookup with the merged fields rebuild(newlookup; data=parent(lookup), order=o, span=sp, sampling=sa, metadata=md) end -_set(lookup::AbstractArray, newlookup::NoLookup{<:AutoIndex}) = NoLookup(axes(lookup, 1)) -_set(lookup::Lookup, newlookup::NoLookup{<:AutoIndex}) = NoLookup(axes(lookup, 1)) +_set(lookup::AbstractArray, newlookup::NoLookup{<:AutoValues}) = NoLookup(axes(lookup, 1)) +_set(lookup::Lookup, newlookup::NoLookup{<:AutoValues}) = NoLookup(axes(lookup, 1)) _set(lookup::Lookup, newlookup::NoLookup) = newlookup -# Set the index -_set(lookup::Lookup, index::Val) = rebuild(lookup; data=index) -_set(lookup::Lookup, index::Colon) = lookup -_set(lookup::Lookup, index::AutoLookup) = lookup -_set(lookup::Lookup, index::AbstractArray) = rebuild(lookup; data=index) +# Set the lookup values +_set(lookup::Lookup, values::Val) = rebuild(lookup; data=values) +_set(lookup::Lookup, values::Colon) = lookup +_set(lookup::Lookup, values::AutoLookup) = lookup +_set(lookup::Lookup, values::AbstractArray) = rebuild(lookup; data=values) -_set(lookup::Lookup, index::AutoIndex) = lookup -_set(lookup::Lookup, index::AbstractRange) = - rebuild(lookup; data=_set(parent(lookup), index), order=orderof(index)) +_set(lookup::Lookup, values::AutoValues) = lookup +_set(lookup::Lookup, values::AbstractRange) = + rebuild(lookup; data=_set(parent(lookup), values), order=orderof(values)) # Update the Sampling lookup of Sampled dims - it must match the range. -_set(lookup::AbstractSampled, index::AbstractRange) = begin - i = _set(parent(lookup), index) - o = orderof(index) - sp = Regular(step(index)) +_set(lookup::AbstractSampled, values::AbstractRange) = begin + i = _set(parent(lookup), values) + o = orderof(values) + sp = Regular(step(values)) rebuild(lookup; data=i, span=sp, order=o) end @@ -79,26 +79,26 @@ _set(sampling::Sampling, newsampling::Intervals) = # Locus _set(lookup::AbstractSampled, locus::Locus) = rebuild(lookup; sampling=_set(sampling(lookup), locus)) -_set(sampling::Points, locus::Union{AutoLocus,Center}) = Points() +_set(sampling::Points, locus::Union{AutoPosition,Center}) = Points() _set(sampling::Points, locus::Locus) = _locuserror() _set(sampling::Intervals, locus::Locus) = Intervals(locus) -_set(sampling::Intervals, locus::AutoLocus) = sampling +_set(sampling::Intervals, locus::AutoPosition) = sampling _set(locus::Locus, newlocus::Locus) = newlocus -_set(locus::Locus, newlocus::AutoLocus) = locus +_set(locus::Locus, newlocus::AutoPosition) = locus # Metadata _set(lookup::Lookup, newmetadata::AllMetadata) = rebuild(lookup; metadata=newmetadata) _set(metadata::AllMetadata, newmetadata::AllMetadata) = newmetadata -# Index -_set(index::AbstractArray, newindex::AbstractArray) = newindex -_set(index::AbstractArray, newindex::AutoLookup) = index -_set(index::AbstractArray, newindex::Colon) = index -_set(index::Colon, newindex::AbstractArray) = newindex -_set(index::Colon, newindex::Colon) = index +# Looup values +_set(values::AbstractArray, newvalues::AbstractArray) = newvalues +_set(values::AbstractArray, newvalues::AutoLookup) = values +_set(values::AbstractArray, newvalues::Colon) = values +_set(values::Colon, newvalues::AbstractArray) = newvalues +_set(values::Colon, newvalues::Colon) = values _set(A, x) = _cantseterror(A, x) -@noinline _locuserror() = throw(ArgumentError("Can't set a locus for `Points` sampling other than `Center` - the index values are the exact points")) +@noinline _locuserror() = throw(ArgumentError("Can't set a locus for `Points` sampling other than `Center` - the lookup values are the exact points")) @noinline _cantseterror(a, b) = throw(ArgumentError("Can not set any fields of $(typeof(a)) to $(typeof(b))")) diff --git a/src/Lookups/utils.jl b/src/Lookups/utils.jl index 2ec4e2301..93795b600 100644 --- a/src/Lookups/utils.jl +++ b/src/Lookups/utils.jl @@ -1,47 +1,47 @@ """ shiftlocus(locus::Locus, x) -Shift the index of `x` from the current locus to the new locus. +Shift the values of `x` from the current locus to the new locus. We only shift `Sampled`, `Regular` or `Explicit`, `Intervals`. """ function shiftlocus(locus::Locus, lookup::Lookup) samp = sampling(lookup) samp isa Intervals || error("Cannot shift locus of $(nameof(typeof(samp)))") - newindex = _shiftindexlocus(locus, lookup) - newlookup = rebuild(lookup; data=newindex) + newvalues = _shiftlocus(locus, lookup) + newlookup = rebuild(lookup; data=newvalues) return set(newlookup, locus) end # Fallback - no shifting -_shiftindexlocus(locus::Locus, lookup::Lookup) = index(lookup) +_shiftlocus(locus::Locus, lookup::Lookup) = parent(lookup) # Sampled -function _shiftindexlocus(locus::Locus, lookup::AbstractSampled) - _shiftindexlocus(locus, span(lookup), sampling(lookup), lookup) +function _shiftlocus(locus::Locus, lookup::AbstractSampled) + _shiftlocus(locus, span(lookup), sampling(lookup), lookup) end # TODO: -_shiftindexlocus(locus::Locus, span::Irregular, sampling::Sampling, lookup::Lookup) = index(lookup) +_shiftlocus(locus::Locus, span::Irregular, sampling::Sampling, lookup::Lookup) = parent(lookup) # Sampled Regular -function _shiftindexlocus(destlocus::Center, span::Regular, sampling::Intervals, dim::Lookup) +function _shiftlocus(destlocus::Center, span::Regular, sampling::Intervals, l::Lookup) if destlocus === locus(sampling) - return index(dim) + return parent(l) else offset = _offset(locus(sampling), destlocus) - shift = ((index(dim) .+ abs(step(span))) .- index(dim)) .* offset - return index(dim) .+ shift + shift = ((parent(l) .+ abs(step(span))) .- parent(l)) .* offset + return parent(l) .+ shift end end -function _shiftindexlocus(destlocus::Locus, span::Regular, sampling::Intervals, lookup::Lookup) - index(lookup) .+ (abs(step(span)) * _offset(locus(sampling), destlocus)) +function _shiftlocus(destlocus::Locus, span::Regular, sampling::Intervals, lookup::Lookup) + parent(lookup) .+ (abs(step(span)) * _offset(locus(sampling), destlocus)) end # Sampled Explicit -_shiftindexlocus(::Start, span::Explicit, sampling::Intervals, lookup::Lookup) = val(span)[1, :] -_shiftindexlocus(::End, span::Explicit, sampling::Intervals, lookup::Lookup) = val(span)[2, :] -function _shiftindexlocus(destlocus::Center, span::Explicit, sampling::Intervals, lookup::Lookup) - _shiftindexlocus(destlocus, locus(lookup), span, sampling, lookup) +_shiftlocus(::Start, span::Explicit, sampling::Intervals, lookup::Lookup) = val(span)[1, :] +_shiftlocus(::End, span::Explicit, sampling::Intervals, lookup::Lookup) = val(span)[2, :] +function _shiftlocus(destlocus::Center, span::Explicit, sampling::Intervals, lookup::Lookup) + _shiftlocus(destlocus, locus(lookup), span, sampling, lookup) end -_shiftindexlocus(::Center, ::Center, span::Explicit, sampling::Intervals, lookup::Lookup) = index(lookup) -function _shiftindexlocus(::Center, ::Locus, span::Explicit, sampling::Intervals, lookup::Lookup) +_shiftlocus(::Center, ::Center, span::Explicit, sampling::Intervals, lookup::Lookup) = parent(lookup) +function _shiftlocus(::Center, ::Locus, span::Explicit, sampling::Intervals, lookup::Lookup) # A little complicated so that DateTime works (view(val(span), 2, :) .- view(val(span), 1, :)) ./ 2 .+ view(val(span), 1, :) end @@ -110,3 +110,6 @@ end _order(A) = first(A) <= last(A) ? ForwardOrdered() : ReverseOrdered() _order(A::AbstractArray{<:IntervalSets.Interval}) = first(A).left <= last(A).left ? ForwardOrdered() : ReverseOrdered() + +@deprecate maybeshiftlocus maybeshiftlocus +@deprecate shiftlocus shiftlocus diff --git a/src/array/indexing.jl b/src/array/indexing.jl index 35e507eac..0f8ef31fb 100644 --- a/src/array/indexing.jl +++ b/src/array/indexing.jl @@ -2,12 +2,6 @@ #### getindex/view #### -# Integer returns a single value, but not for view -@propagate_inbounds Base.getindex(A::AbstractDimArray, i1::Integer, i2::Integer, I::Integer...) = - Base.getindex(parent(A), i1, i2, I...) -# No indices. These just prevent stack overflows -@propagate_inbounds Base.getindex(A::AbstractDimArray) = Base.getindex(parent(A)) -@propagate_inbounds Base.view(A::AbstractDimArray) = rebuild(A, Base.view(parent(A)), ()) const SelectorOrStandard = Union{SelectorOrInterval,StandardIndices} const DimensionIndsArrays = Union{AbstractArray{<:Dimension},AbstractArray{<:DimTuple}} @@ -15,27 +9,52 @@ const DimensionalIndices = Union{DimTuple,DimIndices,DimSelectors,Dimension,Dime const _DimIndicesAmb = Union{AbstractArray{Union{}},DimIndices{<:Integer},DimSelectors{<:Integer}} for f in (:getindex, :view, :dotview) - _f = Symbol(:_, f) + _dim_f = Symbol(:_dim_, f) + if f === :view + # No indices and we try to rebuild, for 0d + @eval @propagate_inbounds Base.view(A::AbstractDimArray) = rebuild(A, Base.view(parent(A)), ()) + # With one Integer and 0d and 1d we try to rebuild + @eval @propagate_inbounds Base.$f(A::AbstractDimArray{<:Any,0}, i::Integer) = + rebuildsliced(Base.$f, A, Base.$f(parent(A), i), (i,)) + @eval @propagate_inbounds Base.$f(A::AbstractDimVector, i::Integer) = + rebuildsliced(Base.$f, A, Base.$f(parent(A), i), (i,)) + # Otherwise its linear indexing, don't rebuild + @eval @propagate_inbounds Base.$f(A::AbstractDimArray, i::Integer) = + Base.$f(parent(A), i) + # More Integera and we rebuild again + @eval @propagate_inbounds Base.$f(A::AbstractDimArray, i1::Integer, i2::Integer, I::Integer...) = + rebuildsliced(Base.$f, A, Base.$f(parent(A), i1, i2, I...), (i1, i2, I...)) + else + @eval @propagate_inbounds Base.$f(A::AbstractDimVector, i::Integer) = Base.$f(parent(A), i) + @eval @propagate_inbounds Base.$f(A::AbstractDimArray, i::Integer) = Base.$f(parent(A), i) + @eval @propagate_inbounds Base.$f(A::AbstractDimArray, i1::Integer, i2::Integer, I::Integer...) = + Base.$f(parent(A), i1, i2, I...) + @eval @propagate_inbounds Base.$f(A::AbstractDimArray) = Base.$f(parent(A)) + end @eval begin - if Base.$f === Base.view - @eval @propagate_inbounds function Base.$f(A::AbstractDimArray, i::Union{CartesianIndex,CartesianIndices}) - x = Base.$f(parent(A), i) - I = to_indices(A, (i,)) - rebuildsliced(Base.$f, A, x, I) + @propagate_inbounds Base.$f(A::AbstractDimVector, I::CartesianIndex) = + Base.$f(A, to_indices(A, (I,))...) + @propagate_inbounds Base.$f(A::AbstractDimArray, I::CartesianIndex) = + Base.$f(A, to_indices(A, (I,))...) + @propagate_inbounds Base.$f(A::AbstractDimVector, I::CartesianIndices) = + rebuildsliced(Base.$f, A, Base.$f(parent(A), I), (I,)) + @propagate_inbounds Base.$f(A::AbstractDimArray, I::CartesianIndices) = + rebuildsliced(Base.$f, A, Base.$f(parent(A), I), (I,)) + @propagate_inbounds function Base.$f(A::AbstractDimVector, i) + x = Base.$f(parent(A), i) + if x isa AbstractArray + rebuildsliced(Base.$f, A, x, to_indices(A, (i,))) + else + x end - @eval @propagate_inbounds function Base.$f(A::AbstractDimArray, i::Integer) - x = Base.$f(parent(A), i) - I = to_indices(A, (CartesianIndices(A)[i],)) - rebuildsliced(Base.$f, A, x, I) + end + @propagate_inbounds function Base.$f(A::AbstractDimArray, i1, i2, I...) + x = Base.$f(parent(A), i1, i2, I...) + if x isa AbstractArray + rebuildsliced(Base.$f, A, x, to_indices(A, (i1, i2, I...))) + else + x end - else - #### Array getindex/view ### - # These are needed to resolve ambiguity - @propagate_inbounds Base.$f(A::AbstractDimArray, i::Integer) = Base.$f(parent(A), i) - @propagate_inbounds Base.$f(A::AbstractDimArray, i::CartesianIndex) = Base.$f(parent(A), i) - # CartesianIndices - @propagate_inbounds Base.$f(A::AbstractDimArray, I::CartesianIndices) = - Base.$f(A, to_indices(A, (I,))...) end # Linear indexing forwards to the parent array as it will break the dimensions @propagate_inbounds Base.$f(A::AbstractDimArray, i::Union{Colon,AbstractArray{<:Integer}}) = @@ -43,29 +62,55 @@ for f in (:getindex, :view, :dotview) # Except 1D DimArrays @propagate_inbounds Base.$f(A::AbstractDimVector, i::Union{Colon,AbstractArray{<:Integer}}) = rebuildsliced(Base.$f, A, Base.$f(parent(A), i), (i,)) + @propagate_inbounds Base.$f(A::AbstractDimVector, i::SelectorOrInterval) = + Base.$f(A, dims2indices(A, (i,))...) # Selector/Interval indexing - @propagate_inbounds Base.$f(A::AbstractDimArray, i1::SelectorOrStandard, I::SelectorOrStandard...) = - Base.$f(A, dims2indices(A, (i1, I...))...) + @propagate_inbounds Base.$f(A::AbstractDimArray, i1::SelectorOrStandard, i2::SelectorOrStandard, I::SelectorOrStandard...) = + Base.$f(A, dims2indices(A, (i1, i2, I...))...) - @propagate_inbounds Base.$f(A::AbstractBasicDimArray, extent::Extents.Extent) = + @propagate_inbounds Base.$f(A::AbstractDimVector, extent::Union{Extents.Extent,Touches{<:Extents.Extent}}) = + Base.$f(A, dims2indices(A, extent)...) + @propagate_inbounds Base.$f(A::AbstractDimArray, extent::Union{Extents.Extent,Touches{<:Extents.Extent}}) = + Base.$f(A, dims2indices(A, extent)...) + @propagate_inbounds Base.$f(A::AbstractBasicDimVector, extent::Union{Extents.Extent,Touches{<:Extents.Extent}}) = + Base.$f(A, dims2indices(A, extent)...) + @propagate_inbounds Base.$f(A::AbstractBasicDimArray, extent::Union{Extents.Extent,Touches{<:Extents.Extent}}) = Base.$f(A, dims2indices(A, extent)...) # All Dimension indexing modes combined - @propagate_inbounds Base.$f(A::AbstractBasicDimArray, D::DimensionalIndices...; kw...) = - $_f(A, _simplify_dim_indices(D..., kw2dims(values(kw))...)...) + @propagate_inbounds Base.$f(A::AbstractBasicDimArray; kw...) = + $_dim_f(A, _simplify_dim_indices(kw2dims(values(kw))...,)...) + @propagate_inbounds Base.$f(A::AbstractBasicDimArray, d1::DimensionalIndices; kw...) = + $_dim_f(A, _simplify_dim_indices(d1, kw2dims(values(kw))...)...) + @propagate_inbounds Base.$f(A::AbstractBasicDimArray, d1::DimensionalIndices, d2::DimensionalIndices, D::DimensionalIndices...; kw...) = + $_dim_f(A, _simplify_dim_indices(d1, d2, D..., kw2dims(values(kw))...)...) + @propagate_inbounds Base.$f(A::AbstractDimArray, i1::DimensionalIndices, i2::DimensionalIndices, I::DimensionalIndices...) = + $_dim_f(A, _simplify_dim_indices(i1, i2, I...)...) + @propagate_inbounds Base.$f(A::AbstractDimArray, i1::_DimIndicesAmb, i2::_DimIndicesAmb, I::_DimIndicesAmb...) = + $_dim_f(A, _simplify_dim_indices(i1, i2, I...)...) + @propagate_inbounds Base.$f(A::AbstractDimVector, i::DimensionalIndices) = + $_dim_f(A, _simplify_dim_indices(i)...) + @propagate_inbounds Base.$f(A::AbstractBasicDimVector, i::DimensionalIndices) = + $_dim_f(A, _simplify_dim_indices(i)...) # For ambiguity - @propagate_inbounds Base.$f(A::AbstractDimArray, i::DimIndices) = $_f(A, i) - @propagate_inbounds Base.$f(A::AbstractDimArray, i::DimSelectors) = $_f(A, i) - @propagate_inbounds Base.$f(A::AbstractDimVector, i::DimIndices) = $_f(A, i) - @propagate_inbounds Base.$f(A::AbstractDimVector, i::DimSelectors) = $_f(A, i) - @propagate_inbounds Base.$f(A::AbstractDimVector, i::_DimIndicesAmb) = $_f(A, i) - @propagate_inbounds Base.$f(A::AbstractDimArray, i1::_DimIndicesAmb, I::_DimIndicesAmb...) = $_f(A, i1, I...) + @propagate_inbounds Base.$f(A::AbstractDimArray, i::DimIndices) = $_dim_f(A, i) + @propagate_inbounds Base.$f(A::AbstractDimArray, i::DimSelectors) = $_dim_f(A, i) + @propagate_inbounds Base.$f(A::AbstractDimArray, i::_DimIndicesAmb) = $_dim_f(A, i) + @propagate_inbounds Base.$f(A::AbstractDimVector, i::DimIndices) = $_dim_f(A, i) + @propagate_inbounds Base.$f(A::AbstractDimVector, i::DimSelectors) = $_dim_f(A, i) + @propagate_inbounds Base.$f(A::AbstractDimVector, i::_DimIndicesAmb) = $_dim_f(A, i) + @propagate_inbounds Base.$f(A::AbstractBasicDimArray, i::DimIndices) = $_dim_f(A, i) + @propagate_inbounds Base.$f(A::AbstractBasicDimArray, i::DimSelectors) = $_dim_f(A, i) + @propagate_inbounds Base.$f(A::AbstractBasicDimArray, i::_DimIndicesAmb) = $_dim_f(A, i) + @propagate_inbounds Base.$f(A::AbstractBasicDimVector, i::DimIndices) = $_dim_f(A, i) + @propagate_inbounds Base.$f(A::AbstractBasicDimVector, i::DimSelectors) = $_dim_f(A, i) + @propagate_inbounds Base.$f(A::AbstractBasicDimVector, i::_DimIndicesAmb) = $_dim_f(A, i) # Use underscore methods to minimise ambiguities - @propagate_inbounds $_f(A::AbstractBasicDimArray, d1::Dimension, ds::Dimension...) = + @propagate_inbounds $_dim_f(A::AbstractBasicDimArray, d1::Dimension, ds::Dimension...) = Base.$f(A, dims2indices(A, (d1, ds...))...) - @propagate_inbounds $_f(A::AbstractBasicDimArray, ds::Dimension...) = + @propagate_inbounds $_dim_f(A::AbstractBasicDimArray, ds::Dimension...) = Base.$f(A, dims2indices(A, ds)...) - @propagate_inbounds function $_f( + @propagate_inbounds function $_dim_f( A::AbstractBasicDimArray, dims::Union{Dimension,DimensionIndsArrays}... ) return merge_and_index($f, A, dims) @@ -85,14 +130,13 @@ for f in (:getindex, :view, :dotview) all(i -> i isa Integer, I) ? x : rebuildsliced(Base.$f, A, x, I) end end - # Special caase zero dimensional arrays being indexed with missing dims + # Special case zero dimensional arrays being indexed with missing dims if f == :getindex # Catch this before the dimension is converted to () - @eval @propagate_inbounds function $_f(A::AbstractDimArray{<:Any,0}, ds::Dimension...) - Dimensions._extradimswarn(ds) + @eval @propagate_inbounds function $_dim_f(A::AbstractDimArray{<:Any,0}) return rebuild(A, fill(A[])) end - @eval @propagate_inbounds function $_f(A::AbstractDimArray{<:Any,0}, d1::Dimension, ds::Dimension...) + @eval @propagate_inbounds function $_dim_f(A::AbstractDimArray{<:Any,0}, d1::Dimension, ds::Dimension...) Dimensions._extradimswarn((d1, ds...)) return rebuild(A, fill(A[])) end @@ -181,25 +225,25 @@ function _separate_dims_arrays(a::AbstractArray, ds...) end _separate_dims_arrays() = (), () -Base.@assume_effects :foldable _simplify_dim_indices(d::Dimension, ds...) = +Base.@assume_effects :foldable @inline _simplify_dim_indices(d::Dimension, ds...) = (d, _simplify_dim_indices(ds)...) -Base.@assume_effects :foldable _simplify_dim_indices(d::Tuple, ds...) = +Base.@assume_effects :foldable @inline _simplify_dim_indices(d::Tuple, ds...) = (d..., _simplify_dim_indices(ds)...) -Base.@assume_effects :foldable _simplify_dim_indices(d::AbstractArray{<:Dimension}, ds...) = +Base.@assume_effects :foldable @inline _simplify_dim_indices(d::AbstractArray{<:Dimension}, ds...) = (d, _simplify_dim_indices(ds)...) -Base.@assume_effects :foldable _simplify_dim_indices(d::AbstractArray{<:DimTuple}, ds...) = +Base.@assume_effects :foldable @inline _simplify_dim_indices(d::AbstractArray{<:DimTuple}, ds...) = (d, _simplify_dim_indices(ds)...) -Base.@assume_effects :foldable _simplify_dim_indices(::Tuple{}) = () -Base.@assume_effects :foldable _simplify_dim_indices(d::DimIndices, ds...) = +Base.@assume_effects :foldable @inline _simplify_dim_indices(::Tuple{}) = () +Base.@assume_effects :foldable @inline _simplify_dim_indices(d::DimIndices, ds...) = (dims(d)..., _simplify_dim_indices(ds)...) -Base.@assume_effects :foldable function _simplify_dim_indices(d::DimSelectors, ds...) +Base.@assume_effects :foldable @inline function _simplify_dim_indices(d::DimSelectors, ds...) seldims = map(dims(d), d.selectors) do d, s # But the dimension values inside selectors rebuild(d, rebuild(s; val=val(d))) end return (seldims..., _simplify_dim_indices(ds)...) end -Base.@assume_effects :foldable _simplify_dim_indices() = () +Base.@assume_effects :foldable @inline _simplify_dim_indices() = () @inline _unwrap_cartesian(i1::CartesianIndices, I...) = (Tuple(i1)..., _unwrap_cartesian(I...)...) @inline _unwrap_cartesian(i1::CartesianIndex, I...) = (Tuple(i1)..., _unwrap_cartesian(I...)...) diff --git a/src/array/methods.jl b/src/array/methods.jl index 22726b59f..f343f0709 100644 --- a/src/array/methods.jl +++ b/src/array/methods.jl @@ -301,7 +301,7 @@ function _cat(catdims::Tuple, A1::AbstractDimArray, As::AbstractDimArray...) else # Concatenate new dims if all(map(x -> hasdim(refdims(x), catdim), Xin)) - if catdim isa Dimension && val(catdim) isa AbstractArray && !(lookup(catdim) isa NoLookup{AutoIndex}) + if catdim isa Dimension && val(catdim) isa AbstractArray && !(lookup(catdim) isa NoLookup{AutoValues}) # Combine the refdims properties with the passed in catdim set(refdims(first(Xin), catdim), catdim) else diff --git a/src/array/show.jl b/src/array/show.jl index 25ea992f6..6b7398284 100644 --- a/src/array/show.jl +++ b/src/array/show.jl @@ -142,7 +142,7 @@ function print_metadata_block(io, mime, metadata; blockwidth=0, displaywidth) else metadata_lines = split(sprint(show, mime, metadata), "\n") new_blockwidth = min(displaywidth-2, max(blockwidth, maximum(length, metadata_lines) + 4)) - print_block_separator(io, "metadata", blockwidth, new_blockwidth) + new_blockwidth = print_block_separator(io, "metadata", blockwidth, new_blockwidth) println(io) print(io, " ") show(io, mime, metadata) @@ -179,7 +179,9 @@ function print_block_separator(io, label, prev_width, new_width=prev_width) line = string('├', '─'^max(0, new_width - textwidth(label) - 2)) corner = '┤' end - printstyled(io, string(line, ' ', label, ' ', corner); color=:light_black) + full = string(line, ' ', label, ' ', corner) + printstyled(io, full; color=:light_black) + return length(full) - 2 end function print_block_close(io, blockwidth) diff --git a/src/interface.jl b/src/interface.jl index 54d7ab56f..1d9a0f64b 100644 --- a/src/interface.jl +++ b/src/interface.jl @@ -95,10 +95,15 @@ function val end lookup(x, dim) => Lookup Returns the [`Lookup`](@ref) of a dimension. This dictates -properties of the dimension such as array axis and index order, +properties of the dimension such as array axis and lookup order, and sampling properties. `dims` can be a `Dimension`, a dimension type, or a tuple of either. + +This is separate from `val` in that it will only work when dimensions +actually contain an `AbstractArray` lookup, and can be used on a +`DimArray` or `DimStack` to retriev all lookups, as there is no ambiguity +of meaning as there is with `val`. """ function lookup end @@ -109,23 +114,6 @@ function lookup end # can be supplied to select a subset of dimensions or a single # Dimension. -""" - index(x) => Tuple{Vararg{AbstractArray}} - index(x, dims::Tuple) => Tuple{Vararg{AbstractArray}} - index(dims::Tuple) => Tuple{Vararg{AbstractArray}}} - index(x, dim) => AbstractArray - index(dim::Dimension) => AbstractArray - -Return the contained index of a `Dimension`. - -Only valid when a `Dimension` contains an `AbstractArray` -or a Val tuple like `Val{(:a, :b)}()`. The `Val` is unwrapped -to return just the `Tuple` - -`dims` can be a `Dimension`, or a tuple of `Dimension`. -""" -function index end - """ metadata(x) => (object metadata) metadata(x, dims::Tuple) => Tuple (Dimension metadata) @@ -202,7 +190,7 @@ function bounds end order(xs::Tuple) => Tuple order(x::Union{Dimension,Lookup}) => Order -Return the `Ordering` of the dimension index for each dimension: +Return the `Ordering` of the dimension lookup for each dimension: `ForwardOrdered`, `ReverseOrdered`, or [`Unordered`](@ref) Second argument `dims` can be `Dimension`s, `Dimension` types, @@ -242,7 +230,7 @@ function span end locus(xs::Tuple) => Tuple{Vararg{Locus,N}} locus(x::Union{Dimension,Lookup}) => Locus -Return the [`Locus`](@ref) for each dimension. +Return the [`Position`](@ref) of lookup values for each dimension. Second argument `dims` can be `Dimension`s, `Dimension` types, or `Symbols` for `Dim{Symbol}`. diff --git a/src/plotrecipes.jl b/src/plotrecipes.jl index e81fd6ed4..2ee1627ea 100644 --- a/src/plotrecipes.jl +++ b/src/plotrecipes.jl @@ -75,7 +75,7 @@ end ind, dep = dims(A) :xguide --> label(A) :legendtitle --> label(dep) - :label --> string.(permutedims(index(dep))) + :label --> string.(permutedims(parent(lookup(dep)))) _withaxes(ind, A) end @@ -91,7 +91,7 @@ end :xguide --> label(ind) :yguide --> label(A) :legendtitle --> label(ind) - :label --> string.(permutedims(index(ind))) + :label --> string.(permutedims(parent(lookup(ind)))) _xticks!(plotattributes, s, ind) parent(A) end @@ -102,7 +102,7 @@ end :xguide --> label(ind) :yguide --> label(A) :legendtitle --> label(ind) - :label --> string.(permutedims(index(ind))) + :label --> string.(permutedims(parent(lookup(ind)))) _xticks!(plotattributes, s, ind) parent(A) end @@ -132,27 +132,27 @@ end _withaxes(dim::Dimension, A::AbstractDimArray) = - _withaxes(lookup(dim), index(dim), parent(A)) -_withaxes(::NoLookup, index, A::AbstractArray) = A -_withaxes(::Lookup, index, A::AbstractArray) = index, A -_withaxes(::Categorical, index, A::AbstractArray) = eachindex(index), A + _withaxes(lookup(dim), parent(lookup(dim)), parent(A)) +_withaxes(::NoLookup, values, A::AbstractArray) = A +_withaxes(::Lookup, values, A::AbstractArray) = values, A +_withaxes(::Categorical, values, A::AbstractArray) = eachindex(values), A _withaxes(dx::Dimension, dy::Dimension, A::AbstractDimArray) = - _withaxes(lookup(dx), lookup(dy), index(dx), index(dy), parent(A)) + _withaxes(lookup(dx), lookup(dy), parent(lookup(dx)), parent(lookup(dy)), parent(A)) _withaxes(::Lookup, ::Lookup, ix, iy, A) = ix, iy, A _withaxes(::NoLookup, ::Lookup, ix, iy, A) = axes(A, 2), iy, A _withaxes(::Lookup, ::NoLookup, ix, iy, A) = ix, axes(A, 1), A _withaxes(::NoLookup, ::NoLookup, ix, iy, A) = axes(A, 2), axes(A, 1), A -_xticks!(attr, s, d::Dimension) = _xticks!(attr, s, lookup(d), index(d)) -_xticks!(attr, s, ::Categorical, index) = - RecipesBase.is_explicit(attr, :xticks) || (attr[:xticks] = (eachindex(index), index)) -_xticks!(attr, s, ::Lookup, index) = nothing +_xticks!(attr, s, d::Dimension) = _xticks!(attr, s, lookup(d), parent(lookup(d))) +_xticks!(attr, s, ::Categorical, values) = + RecipesBase.is_explicit(attr, :xticks) || (attr[:xticks] = (eachindex(values), values)) +_xticks!(attr, s, ::Lookup, values) = nothing -_yticks!(attr, s, d::Dimension) = _yticks!(attr, s, lookup(d), index(d)) -_yticks!(attr, s, ::Categorical, index) = - RecipesBase.is_explicit(attr, :yticks) || (attr[:yticks] = (eachindex(index), index)) -_yticks!(attr, s, ::Lookup, index) = nothing +_yticks!(attr, s, d::Dimension) = _yticks!(attr, s, lookup(d), parent(lookup(d))) +_yticks!(attr, s, ::Categorical, values) = + RecipesBase.is_explicit(attr, :yticks) || (attr[:yticks] = (eachindex(values), values)) +_yticks!(attr, s, ::Lookup, values) = nothing ### Shared utils diff --git a/src/stack/indexing.jl b/src/stack/indexing.jl index 764eb0e72..84f8a857f 100644 --- a/src/stack/indexing.jl +++ b/src/stack/indexing.jl @@ -6,7 +6,7 @@ for f in (:getindex, :view, :dotview) @eval Base.@assume_effects :foldable @propagate_inbounds Base.$f(s::AbstractDimStack, key::Symbol) = DimArray(data(s)[key], dims(s, layerdims(s, key)), refdims(s), key, layermetadata(s, key)) - @eval Base.@assume_effects :foldable @propagate_inbounds function Base.$f(s::AbstractDimStack, keys::Tuple) + @eval Base.@assume_effects :foldable @propagate_inbounds function Base.$f(s::AbstractDimStack, keys::NTuple{<:Any,Symbol}) rebuild_from_arrays(s, NamedTuple{keys}(map(k -> s[k], keys))) end @eval Base.@assume_effects :foldable @propagate_inbounds function Base.$f( @@ -17,13 +17,16 @@ for f in (:getindex, :view, :dotview) end for f in (:getindex, :view, :dotview) - _f = Symbol(:_, f) + _dim_f = Symbol(:_dim_, f) @eval begin + @propagate_inbounds function Base.$f(s::AbstractDimStack, i) + Base.$f(s, to_indices(CartesianIndices(s), (i,))...) + end @propagate_inbounds function Base.$f(s::AbstractDimStack, i::Union{SelectorOrInterval,Extents.Extent}) Base.$f(s, dims2indices(s, i)...) end @propagate_inbounds function Base.$f(s::AbstractDimStack, i::Integer) - if hassamedims(s) + if hassamedims(s) && length(dims(s)) == 1 map(l -> Base.$f(l, i), s) else Base.$f(s, DimIndices(s)[i]) @@ -50,7 +53,7 @@ for f in (:getindex, :view, :dotview) checkbounds(s, i) end end - @propagate_inbounds function Base.$f(s::AbstractDimStack, i1::SelectorOrStandard, i2, Is::SelectorOrStandard...) + @propagate_inbounds function Base.$f(s::AbstractDimStack, i1, i2, Is...) I = to_indices(CartesianIndices(s), (i1, i2, Is...)) # Check we have the right number of dimensions if length(dims(s)) > length(I) @@ -69,7 +72,7 @@ for f in (:getindex, :view, :dotview) @propagate_inbounds function Base.$f( s::AbstractDimStack, D::DimensionalIndices...; kw... ) - $_f(s, _simplify_dim_indices(D..., kw2dims(values(kw))...)...) + $_dim_f(s, _simplify_dim_indices(D..., kw2dims(values(kw))...)...) end # Ambiguities @propagate_inbounds function Base.$f( @@ -78,46 +81,44 @@ for f in (:getindex, :view, :dotview) ::Union{Tuple{Dimension,Vararg{Dimension}},AbstractArray{<:Dimension},AbstractArray{<:Tuple{Dimension,Vararg{Dimension}}},DimIndices,DimSelectors,Dimension}, ::_DimIndicesAmb... ) - $_f(s, _simplify_dim_indices(D..., kw2dims(values(kw))...)...) + $_dim_f(s, _simplify_dim_indices(D..., kw2dims(values(kw))...)...) end @propagate_inbounds function Base.$f( s::AbstractDimStack, d1::Union{AbstractArray{Union{}}, DimIndices{<:Integer}, DimSelectors{<:Integer}}, D::Vararg{Union{AbstractArray{Union{}}, DimIndices{<:Integer}, DimSelectors{<:Integer}}} ) - $_f(s, _simplify_dim_indices(d1, D...)) + $_dim_f(s, _simplify_dim_indices(d1, D...)) end @propagate_inbounds function Base.$f( s::AbstractDimStack, D::Union{AbstractArray{Union{}},DimIndices{<:Integer},DimSelectors{<:Integer}} ) - $_f(s, _simplify_dim_indices(D...)) + $_dim_f(s, _simplify_dim_indices(D...)) end - @propagate_inbounds function $_f( + @propagate_inbounds function $_dim_f( A::AbstractDimStack, a1::Union{Dimension,DimensionIndsArrays}, args::Union{Dimension,DimensionIndsArrays}... ) return merge_and_index(Base.$f, A, (a1, args...)) end # Handle zero-argument getindex, this will error unless all layers are zero dimensional - @propagate_inbounds function $_f(s::AbstractDimStack) + @propagate_inbounds function $_dim_f(s::AbstractDimStack) map(Base.$f, s) end - @propagate_inbounds function $_f(s::AbstractDimStack, d1::Dimension, ds::Dimension...) + Base.@assume_effects :foldable @propagate_inbounds function $_dim_f(s::AbstractDimStack, d1::Dimension, ds::Dimension...) D = (d1, ds...) extradims = otherdims(D, dims(s)) length(extradims) > 0 && Dimensions._extradimswarn(extradims) - newlayers = map(layers(s)) do A + function f(A) layerdims = dims(D, dims(A)) I = length(layerdims) > 0 ? layerdims : map(_ -> :, size(A)) Base.$f(A, I...) end + newlayers = map(f, layers(s)) # Dicide to rewrap as an AbstractDimStack, or return a scalar - if all(map(v -> v isa AbstractDimArray, newlayers)) - # All arrays, wrap - rebuildsliced(Base.$f, s, newlayers, (dims2indices(dims(s), D))) - elseif any(map(v -> v isa AbstractDimArray, newlayers)) + if any(map(v -> v isa AbstractDimArray, newlayers)) # Some scalars, re-wrap them as zero dimensional arrays non_scalar_layers = map(layers(s), newlayers) do l, nl nl isa AbstractDimArray ? nl : rebuild(l, fill(nl), ()) diff --git a/src/stack/show.jl b/src/stack/show.jl index 8f9de50de..861db1f52 100644 --- a/src/stack/show.jl +++ b/src/stack/show.jl @@ -37,7 +37,7 @@ function print_layers_block(io, mime, stack; blockwidth, displaywidth) for key in keys(layers) newblockwidth = min(displaywidth - 2, max(newblockwidth, length(sprint(print_layer, stack, key, keylen)))) end - print_block_separator(io, "layers", blockwidth, newblockwidth) + newblockwidth = print_block_separator(io, "layers", blockwidth, newblockwidth) println(io) for key in keys(layers) print_layer(io, stack, key, keylen) diff --git a/test/adapt.jl b/test/adapt.jl index c3f9e1f62..e1f7c143c 100644 --- a/test/adapt.jl +++ b/test/adapt.jl @@ -43,7 +43,7 @@ end @test parent(parent(l1)) isa Base.OneTo l = AutoLookup() l1 = Adapt.adapt(CustomArray, l) - @test parent(l1) isa AutoIndex + @test parent(l1) isa AutoValues end @testset "Dimension" begin diff --git a/test/indexing.jl b/test/indexing.jl index c6fce19f4..781a2b30f 100644 --- a/test/indexing.jl +++ b/test/indexing.jl @@ -26,24 +26,14 @@ using DimensionalData.Lookups, DimensionalData.Dimensions da2 = DimArray(fill(3), ()) dimz2 = dims(da2) @test dims2indices(dimz2, ()) === () - - @testset "mixed dimensions" begin - a = [[1 2 3; 4 5 6];;; [11 12 13; 14 15 16];;;] - da = DimArray(a, (X(143.0:2:145.0), Y(-38.0:-36.0), Ti(100:100:200)); name=:test) - da[Ti=1, DimIndices(da[Ti=1])] - da[DimIndices(da[Ti=1]), Ti(2)] - da[DimIndices(da[Ti=1])[:], Ti(2)] - da[DimIndices(da[Ti=1])[:], DimIndices(da[X=1, Y=1])] - da[DimIndices(da[X=1, Y=1]), DimIndices(da[Ti=1])[:]] - da[DimIndices(da[X=1, Y=1])[:], DimIndices(da[Ti=1])[:]] - end end @testset "lookup" begin @testset "Points" begin l = Sampled(2.0:2.0:10, ForwardOrdered(), Regular(2.0), Points(), nothing) - @test l[:] == l - @test l[1:5] == l + @test l[:] == l[Begin:End] == l + @test l[Begin:5] == l[1:5] == l + @test l[Begin:End] isa typeof(l) @test l[1:5] isa typeof(l) @test l[[1, 3, 4]] == view(l, [1, 3, 4]) == Base.dotview(l, [1, 3, 4]) == @@ -110,9 +100,10 @@ end @testset "Intervals" begin l = Sampled(2.0:2.0:10, ForwardOrdered(), Regular(2.0), Intervals(Start()), nothing) - @test l[:] == l + @test l[:] == l[Begin:End] == l @test l[1:5] == l @test l[1:5] isa typeof(l) + @test l[Begin:End] isa typeof(l) @test l[[1, 3, 4]] == Sampled([2.0, 6.0, 8.0], ForwardOrdered(), Irregular(2.0, 10.0), Intervals(Start()), nothing) @test l[Int[]] == Sampled(Float64[], ForwardOrdered(), Irregular(nothing, nothing), Intervals(Start()), nothing) @test l[Near(2.1)] == 2.0 @@ -168,7 +159,9 @@ end d = X(Sampled(2.0:2.0:10, ForwardOrdered(), Regular(2.0), Points(), nothing)) @test @inferred d[:] == d @test @inferred d[1:5] == d - @test @inferred d[1:5] isa typeof(d) + @test d[1:5] isa typeof(d) + @test @inferred d[Begin:End] == d + @test d[Begin:End] isa typeof(d) # TODO properly handle index mashing arrays: here Regular should become Irregular # @test d[[1, 3, 4]] == X(Sampled([2.0, 6.0, 8.0], ForwardOrdered(), Regular(2.0), Points(), nothing)) # @test d[[true, false, false, false, true]] == X(Sampled([2.0, 10.0], ForwardOrdered(), Regular(2.0), Points(), nothing)) @@ -203,13 +196,15 @@ end @testset "LinearIndex getindex returns an Array, except Vector" begin @test @inferred da[1:2] isa Array + @test @inferred da[Begin:Begin+1] isa Array + @test da[1:2] == da[begin:begin+1] == da[Begin:Begin+1] @test @inferred da[rand(Bool, length(da))] isa Array @test @inferred da[rand(Bool, size(da))] isa Array @test @inferred da[:] isa Array - @test @inferred da[:] == vec(da) + @test da[:] == da[Begin:End] == vec(da) b = @inferred da[[!iseven(i) for i in 1:length(da)]] @test b isa Array - @test b == da[1:2:end] + @test b == da[1:2:end] == da[Begin:2:End] v = @inferred da[1, :] @test @inferred v[1:2] isa DimArray @@ -230,7 +225,7 @@ end end @testset "getindex returns DimensionArray slices with the right dimensions" begin - a = da[X(1:2), Y(1)] + a = da[X(Begin:Begin+1), Y(1)] @test a == [1, 3] @test typeof(a) <: DimArray{Int,1} @test dims(a) == (X(Sampled(143.0:2.0:145.0, ForwardOrdered(), Regular(2.0), Points(), xmeta)),) @@ -244,7 +239,7 @@ end @test locus(da, X) == Center() a = da[(X(1), Y(1:2))] # Can use a tuple of dimensions like a CartesianIndex - @test a == [1, 2] + @test a == [1, 2] == da[(X(1), Y(Begin:Begin+1))] @test typeof(a) <: DimArray{Int,1} @test typeof(parent(a)) <: Array{Int,1} @test dims(a) == (Y(Sampled(-38.0:2.0:-36.0, ForwardOrdered(), Regular(2.0), Points(), ymeta)),) @@ -275,6 +270,7 @@ end @test da[DimIndices(da)] == da da[DimIndices(da)[X(1)]] da[DimSelectors(da)] + da[DimSelectors(da)[X(1)]] end @testset "selectors work" begin @@ -338,9 +334,9 @@ end da2 = DimArray(randn(2, 3), (X(1:2), Y(1:3))) for inds in ((), (1,), (1, 1), (1, 1, 1), (CartesianIndex(),), (CartesianIndices(da0),)) - @test typeof(parent(view(da0, inds...))) === typeof(view(parent(da0), inds...)) - @test parent(view(da0, inds...)) == view(parent(da0), inds...) a = view(da0, inds...) + @test typeof(parent(a)) === typeof(view(parent(da0), inds...)) + @test parent(a) == view(parent(da0), inds...) @test a isa DimArray{eltype(da0),0} @test length(dims(a)) == 0 @test length(refdims(a)) == 0 @@ -355,7 +351,8 @@ end @test length(refdims(a)) == 1 end - for inds in ((2,), (2, 3), (1, 3, 1), (CartesianIndex(2, 1),)) + for inds in ((2, 3), (1, 3, 1), (CartesianIndex(2, 1),)) + inds = (CartesianIndex(2, 1),) @test typeof(parent(view(da2, inds...))) === typeof(view(parent(da2), inds...)) @test parent(view(da2, inds...)) == view(parent(da2), inds...) a = view(da2, inds...) @@ -485,6 +482,17 @@ end @test da[2] == 3 @inferred getindex(da, X(2), Y(2)) end + + @testset "mixed dimensions" begin + a = [[1 2 3; 4 5 6];;; [11 12 13; 14 15 16];;;] + da = DimArray(a, (X(143.0:2:145.0), Y(-38.0:-36.0), Ti(100:100:200)); name=:test) + da[Ti=1, DimIndices(da[Ti=1])] + da[DimIndices(da[Ti=1]), Ti(2)] + da[DimIndices(da[Ti=1])[:], Ti(2)] + da[DimIndices(da[Ti=1])[:], DimIndices(da[X=1, Y=1])] + da[DimIndices(da[X=1, Y=1]), DimIndices(da[Ti=1])[:]] + da[DimIndices(da[X=1, Y=1])[:], DimIndices(da[Ti=1])[:]] + end end @testset "stack" begin @@ -552,7 +560,7 @@ end end @testset "view" begin - sv = @inferred view(s, 1, 1) + sv = @inferred view(s, Begin, Begin) @test parent(sv) == (one=fill(1.0), two=fill(2.0f0), three=fill(3)) @test dims(sv) == () sv = @inferred view(s, X(1:2), Y(3:3)) @@ -561,12 +569,12 @@ end @test @inferred slicedds[:one] == [1.0, 2.0, 3.0] @test parent(slicedds) == (one=[1.0, 2.0, 3.0], two=[2.0f0, 4.0f0, 6.0f0], three=[3, 6, 9]) @testset "linear indices" begin - @test_broken linear2d = @inferred view(s, 1) + linear2d = @inferred view(s, 1) linear2d = view(s, 1) @test linear2d isa DimStack @test parent(linear2d) == (one=fill(1.0), two=fill(2.0f0), three=fill(3)) @test_broken linear1d = @inferred view(s[X(1)], 1) - linear1d = view(s[X(1)], 1) + linear1d = view(s, 1) @test linear1d isa DimStack @test parent(linear1d) == (one=fill(1.0), two=fill(2.0f0), three=fill(3)) linear2d = view(s, 1:2) @@ -633,3 +641,28 @@ end @test @inferred view(A2, Ti(5)) == permutedims([5]) @test @inferred view(A3, Ti(5)) == permutedims([5]) end + +@testset "Begin End indexng" begin + @testset "generic indexing" begin + @test (1:10)[Begin] == 1 + @test (1:10)[Begin()] == 1 + @test (1:10)[End] == 10 + @test (1:10)[End()] == 10 + @test (1:10)[Begin:End] == 1:10 + @test (1:10)[Begin:10] == 1:10 + @test (1:10)[1:End] == 1:10 + @test (1:10)[Begin():End()] == 1:10 + @test (1:10)[Begin+1:End-1] == 2:9 + @test (1:10)[Begin()+1:End()-1] == 2:9 + @test (1:10)[Begin:End÷2] == 1:5 + @test (1:10)[Begin|3:End] == 3:10 + @test (1:10)[Begin:End&3] == 1:2 + @test (1:10)[Begin()+1:End()-1] == 2:9 + end + @testset "dimension indexing" begin + A = DimArray((1:5)*(6:3:20)', (X, Y)) + @test A[X=Begin, Y=End] == 18 + @test A[X=End(), Y=Begin()] == 30 + @test A[X=Begin:Begin+1, Y=End] == [18, 36] + end +end diff --git a/test/selector.jl b/test/selector.jl index db65eccb6..bf7de7a92 100644 --- a/test/selector.jl +++ b/test/selector.jl @@ -976,7 +976,7 @@ end (Near([13]), Near([1.3u"s", 3.3u"s"])), (Between(11, 20), At((2:3)u"s")) ] - positions = [ + locuss = [ (1:3, [3, 4]), (2, [3, 4]), (2, [2, 3]), @@ -984,7 +984,7 @@ end ([1], [1, 3]), (2:2, [2, 3]) ] - for (selector, pos) in zip(selectors, positions) + for (selector, pos) in zip(selectors, locuss) pairs = collect(zip(selector, pos)) cases = [(i, j) for i in pairs[1], j in pairs[2]] for (case1, case2) in combinations(cases, 2) @@ -1027,11 +1027,7 @@ end for idx in indices from2d = view(da, idx) @test from2d == view(parent(da), idx) - if idx isa Integer - @test from2d isa DimArray - else - @test from2d isa SubArray - end + @test from2d isa SubArray from1d = view(da[Y(At(10))], idx) @test from1d == view(parent(da)[1, :], idx) @test from1d isa DimArray @@ -1144,7 +1140,7 @@ end @testset "Extent indexing" begin # THese should be the same because da is the maximum size # we can index with `Touches` - da[Touches(Extents.extent(da))] == da[Extents.extent(da)] == da + @test da[Touches(Extents.extent(da))] == da[Extents.extent(da)] == da end @testset "with dim wrappers" begin diff --git a/test/set.jl b/test/set.jl index f22916976..d559dfb0b 100644 --- a/test/set.jl +++ b/test/set.jl @@ -69,7 +69,7 @@ end cat_da = set(da, X=NoLookup(), Y=Categorical()) @test index(cat_da) == (NoLookup(Base.OneTo(2)), Categorical(-38.0:2.0:-36.0, Unordered(), NoMetadata())) - cat_da_m = set(dims(cat_da, Y), X(DimensionalData.AutoIndex(); metadata=Dict())) + cat_da_m = set(dims(cat_da, Y), X(DimensionalData.AutoValues(); metadata=Dict())) @test metadata(cat_da_m) == Dict() @testset "span" begin