Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add Begin End ranges #585

Merged
merged 13 commits into from
Mar 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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
Expand Down
10 changes: 5 additions & 5 deletions docs/src/api/lookuparrays.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ Lookups.Transformed
Dimensions.MergedLookup
Lookups.NoLookup
Lookups.AutoLookup
Lookups.AutoIndex
Lookups.AutoValues
```

The generic value getter `val`
Expand All @@ -33,7 +33,6 @@ Lookup methods:
```@docs
bounds
hasselection
Lookups.index
Lookups.sampling
Lookups.span
Lookups.order
Expand Down Expand Up @@ -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
Expand Down
17 changes: 17 additions & 0 deletions docs/src/dimarrays.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 3 additions & 1 deletion src/DimensionalData.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
5 changes: 3 additions & 2 deletions src/Dimensions/Dimensions.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
24 changes: 10 additions & 14 deletions src/Dimensions/dimension.jl
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,14 @@
[`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.
Expand Down Expand Up @@ -90,9 +89,6 @@
↓ → 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

Expand Down Expand Up @@ -194,9 +190,6 @@
# 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)
Expand Down Expand Up @@ -255,6 +248,9 @@
@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)))

Check warning on line 253 in src/Dimensions/dimension.jl

View check run for this annotation

Codecov / codecov/patch

src/Dimensions/dimension.jl#L253

Added line #L253 was not covered by tests

# Base methods
const ArrayOrVal = Union{AbstractArray,Val}
Expand Down Expand Up @@ -309,7 +305,7 @@
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
Expand Down
110 changes: 55 additions & 55 deletions src/Dimensions/format.jl
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
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)
Expand Down Expand Up @@ -49,99 +49,99 @@

# 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())

Check warning on line 86 in src/Dimensions/format.jl

View check run for this annotation

Codecov / codecov/patch

src/Dimensions/format.jl#L86

Added line #L86 was not covered by tests

# 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

Check warning on line 90 in src/Dimensions/format.jl

View check run for this annotation

Codecov / codecov/patch

src/Dimensions/format.jl#L90

Added line #L90 was not covered by tests
# 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))

Check warning on line 99 in src/Dimensions/format.jl

View check run for this annotation

Codecov / codecov/patch

src/Dimensions/format.jl#L99

Added line #L99 was not covered by tests
_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)

Check warning on line 107 in src/Dimensions/format.jl

View check run for this annotation

Codecov / codecov/patch

src/Dimensions/format.jl#L106-L107

Added lines #L106 - L107 were not covered by tests
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()

Check warning on line 123 in src/Dimensions/format.jl

View check run for this annotation

Codecov / codecov/patch

src/Dimensions/format.jl#L123

Added line #L123 was not covered by tests
_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()

Check warning on line 134 in src/Dimensions/format.jl

View check run for this annotation

Codecov / codecov/patch

src/Dimensions/format.jl#L134

Added line #L134 was not covered by tests

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"
Expand Down
15 changes: 8 additions & 7 deletions src/Dimensions/indexing.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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) = ()
Expand Down Expand Up @@ -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
Expand Down
Loading
Loading