Skip to content

Commit

Permalink
Merge pull request #21 from rafaqz/improve_selectors
Browse files Browse the repository at this point in the history
Improve selectors and testing
  • Loading branch information
rafaqz authored Dec 4, 2019
2 parents 89fbd42 + 9b99701 commit 8c5ce2a
Show file tree
Hide file tree
Showing 14 changed files with 306 additions and 143 deletions.
1 change: 1 addition & 0 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ version = "0.2.0"

[deps]
ConstructionBase = "187b0558-2788-49d3-abe0-74a17ed4e7c9"
Dates = "ade2ca70-3891-5945-98fb-dc099432e06a"
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
RecipesBase = "3cdcf5f2-1ef4-517c-9805-6587b60abb01"
Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"
Expand Down
4 changes: 2 additions & 2 deletions src/DimensionalData.jl
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ std(a; dims=Y())
"""
module DimensionalData

using ConstructionBase, LinearAlgebra, RecipesBase, Statistics
using ConstructionBase, LinearAlgebra, RecipesBase, Statistics, Dates

using Base: tail, OneTo

Expand All @@ -78,7 +78,7 @@ export AbstractDimensionalArray, DimensionalArray
export Metadata, AbstractArrayMetadata, ArrayMetadata, AbstractDimMetadata, DimMetadata

export dims, refdims, metadata, name, shortname,
val, label, units, order, bounds, grid, <|
val, label, units, order, bounds, locus, grid, <|

include("interface.jl")
include("metadata.jl")
Expand Down
11 changes: 3 additions & 8 deletions src/array.jl
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ const StandardIndices = Union{AbstractArray,Colon,Integer}
# Interface methods ############################################################

dims(A::AbDimArray) = A.dims
label(A::AbDimArray) = ""
bounds(A::AbDimArray) = bounds(dims(A))
@inline rebuild(x, data, dims=dims(x)) = rebuild(x, data, dims, refdims(x))

Expand Down Expand Up @@ -48,7 +47,9 @@ Base.similar(A::AbDimArray) = rebuild(A, similar(parent(A)))
Base.similar(A::AbDimArray, ::Type{T}) where T = rebuild(A, similar(parent(A), T))
Base.similar(A::AbDimArray, ::Type{T}, I::Tuple{Int64,Vararg{Int64}}) where T =
rebuild(A, similar(parent(A), T, I))
Base.similar(A::AbDimArray, ::Type{T}, I::Tuple{Union{Integer,OneTo},Vararg{Union{Integer,OneTo},N}}) where {T,N} =
Base.similar(A::AbDimArray, ::Type{T}, I::Tuple{Union{Integer,AbstractRange},Vararg{Union{Integer,AbstractRange},N}}) where {T,N} =
rebuildsliced(A, similar(parent(A), T, I...), I)
Base.similar(A::AbDimArray, ::Type{T}, I::Vararg{<:Integer}) where T =
rebuildsliced(A, similar(parent(A), T, I...), I)
Base.similar(bc::Broadcast.Broadcasted{Broadcast.ArrayStyle{AbDimArray}}, ::Type{ElType}) where ElType = begin
A = find_dimensional(bc)
Expand All @@ -58,12 +59,6 @@ end

# Need to cover a few type signatures to avoid ambiguity with base
# Don't remove these even though they look redundant
Base.similar(A::AbDimArray, ::Type{T}, I::Vararg{<:Integer}) where T =
rebuildsliced(A, similar(parent(A), T, I...), I)
Base.similar(A::AbDimArray, ::Type{T}, ::Tuple{Int,Vararg{Int}}) where T =
rebuild(A, similar(parent(A), T))
Base.similar(A::AbDimArray, ::Type{T}, I::Tuple{Union{Integer,OneTo},Vararg{Union{Integer,OneTo},N}}) where {T,N} =
rebuildsliced(A, similar(parent(A), T, I...), I)

@inline find_dimensional(bc::Base.Broadcast.Broadcasted) = find_dimensional(bc.args)
@inline find_dimensional(ext::Base.Broadcast.Extruded) = find_dimensional(ext.x)
Expand Down
51 changes: 34 additions & 17 deletions src/grid.jl
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,28 @@ always happens in smallest to largest order.
Base also defines Forward and Reverse, but they seem overly complicated for our purposes.
"""
struct Ordered{D,A} <: Order
struct Ordered{D,A,R} <: Order
index::D
array::A
relation::R
end
Ordered() = Ordered(Forward(), Forward())
Ordered() = Ordered(Forward(), Forward(), Forward())

indexorder(order::Ordered) = order.index
arrayorder(order::Ordered) = order.array
relationorder(order::Ordered) = order.relation

"""
Trait indicating that the array or dimension has no order.
"""
struct Unordered <: Order end
struct Unordered{R} <: Order
relation::R
end
Unordered() = Unordered(Forward())

indexorder(order::Ordered) = order.index
indexorder(order::Unordered) = Unordered()
arrayorder(order::Ordered) = order.array
arrayorder(order::Unordered) = Unordered()
indexorder(order::Unordered) = order
arrayorder(order::Unordered) = order
relationorder(order::Unordered) = order.relation

"""
Trait indicating that the array or dimension is in the normal forward order.
Expand All @@ -45,14 +52,23 @@ struct Reverse <: Order end

Base.reverse(::Reverse) = Forward()
Base.reverse(::Forward) = Reverse()
Base.reverse(o::Ordered) = Ordered(reverse(indexorder(o)), reverse(arrayorder(o)))
Base.reverse(::Unordered) = Unordered()
# Base.reverse(o::Ordered) =
# Ordered(indexorder(o), reverse(relationorder(o)), reverse(arrayorder(o)))
# Base.reverse(o::Unordered) =
# Unordered(reverse(relationorder(o)))

reverseindex(o::Unordered) =
Unordered(reverse(relationorder(o)))
reverseindex(o::Ordered) =
Ordered(reverse(indexorder(o)), arrayorder(o), reverse(relationorder(o)))

reverseindex(o::Unordered) = Unordered()
reverseindex(o::Ordered) = Ordered(reverse(indexorder(o)), arrayorder(o))
reversearray(o::Unordered) =
Unordered(reverse(relationorder(o)))
reversearray(o::Ordered) =
Ordered(indexorder(o), reverse(arrayorder(o)), reverse(relationorder(o)))

reversearray(o::Unordered) = Unordered()
reversearray(o::Ordered) = Ordered(indexorder(o), reverse(arrayorder(o)))
isrev(::Forward) = false
isrev(::Reverse) = true


"""
Expand Down Expand Up @@ -118,6 +134,11 @@ abstract type Grid end
dims(g::Grid) = nothing
arrayorder(grid::Grid) = arrayorder(order(grid))
indexorder(grid::Grid) = indexorder(order(grid))
relationorder(grid::Grid) = relationorder(order(grid))

Base.reverse(g::Grid) = rebuild(g; order=reverse(order(g)))
reversearray(g::Grid) = rebuild(g; order=reversearray(order(g)))
reverseindex(g::Grid) = rebuild(g; order=reverseindex(order(g)))

"""
Fallback grid type
Expand All @@ -136,10 +157,6 @@ A grid dimension aligned exactly with a standard dimension, such as lattitude or
"""
abstract type AbstractAllignedGrid{O} <: IndependentGrid{O} end

Base.reverse(g::AbstractAllignedGrid) = rebuild(g; order=reverse(order(g)))
reversearray(g::AbstractAllignedGrid) = rebuild(g; order=reversearray(order(g)))
reverseindex(g::AbstractAllignedGrid) = rebuild(g; order=reverseindex(order(g)))

order(g::AbstractAllignedGrid) = g.order
locus(g::AbstractAllignedGrid) = g.locus
sampling(g::AbstractAllignedGrid) = g.sampling
Expand Down
8 changes: 7 additions & 1 deletion src/primitives.jl
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ Get the number of an AbstractDimension as ordered in the array
end
end

hasdim(A, lookup::Tuple) = map(l -> hasdim(A, l), lookup)
hasdim(A, lookup) = hasdim(typeof(dims(A)), typeof(lookup))
hasdim(A, lookup::Type) = hasdim(typeof(dims(A)), lookup)
@generated hasdim(dimtypes::Type{DTS}, lookup::Type{D}) where {DTS,D} = begin
Expand Down Expand Up @@ -215,7 +216,12 @@ regularise(::UnknownGrid, start, stop, len) =
regularise(grid::Grid, start, stop, len) = grid

spanof(a, b, len) = (b - a)/(len - 1)
orderof(a, b) = Ordered(a <= b ? Forward() : Reverse(), Forward())
orderof(a, b) =
if a <= b
Ordered(Forward(), Forward(), Forward())
else
Ordered(Reverse(), Forward(), Forward())
end


"""
Expand Down
111 changes: 67 additions & 44 deletions src/selector.jl
Original file line number Diff line number Diff line change
@@ -1,22 +1,31 @@
"""
Selectors indicate that index values are not indices, but points to
Selectors indicate that index values are not indices, but points to
be selected from the dimension values, such as DateTime objects on a Time dimension.
"""
abstract type Selector{T} end

val(m::Selector) = m.val

(::Type{T})(args...) where T <: Selector = T{typeof(args)}(args)
val(m::Selector) = m.val

"""
At(x)
Selector that exactly matches the value on the passed-in dimensions, or throws an error.
For ranges and arrays, every value must match an existing value - not just the end points.
Selector that exactly matches the value on the passed-in dimensions, or throws an error.
For ranges and arrays, every value must match an existing value - not just the end points.
"""
struct At{T} <: Selector{T}
struct At{T,A,R} <: Selector{T}
val::T
atol::A
rtol::R
end
At(val::Union{Number,AbstractArray{<:Number},Tuple{<:Number,Vararg}};
atol=zero(eltype(val)),
rtol=(atol > zero(eltype(val)) ? zero(rtol) : Base.rtoldefault(eltype(val)))
) = At{typeof.((val, atol, rtol))...}(val, atol, rtol)
At(val; atol=nothing, rtol=nothing) =
At{typeof.((val, atol, rtol))...}(val, atol, rtol)

atol(sel::At) = sel.atol
rtol(sel::At) = sel.rtol

"""
Near(x)
Expand All @@ -35,35 +44,38 @@ Selector that retreive all indices located between 2 values.
struct Between{T<:Union{Tuple{Any,Any},Nothing}} <: Selector{T}
val::T
end
Between(args...) = Between{typeof(args)}(args)
Between(x::Tuple) = Between{typeof(x)}(x)

# Get the dims in the same order as the grid
# This would be called after RegularGrid and/or CategoricalGrid
# dimensions are removed
dims2indices(grid::TransformedGrid, dims::Tuple, lookups::Tuple, emptyval) =
dims2indices(grid::TransformedGrid, dims::Tuple, lookups::Tuple, emptyval) =
sel2indices(grid, dims, map(val, permutedims(dimz, dims(grid))))

sel2indices(A::AbstractArray, lookup) = sel2indices(dims(A), lookup)
sel2indices(dims::Tuple, lookup) = sel2indices(dims, (lookup,))
sel2indices(dims::Tuple, lookup::Tuple) = sel2indices(map(grid, dims), dims, lookup)
sel2indices(grids, dims::Tuple, lookup::Tuple) =
(sel2indices(grids[1], dims[1], lookup[1]),
(sel2indices(grids[1], dims[1], lookup[1]),
sel2indices(tail(grids), tail(dims), tail(lookup))...)
sel2indices(grids::Tuple{}, dims::Tuple{}, lookup::Tuple{}) = ()

# At selector
sel2indices(grid, dim::AbDim, sel::At) = at(dim, val(sel))
sel2indices(grid, dim::AbDim, sel::At{<:Tuple}) = [at.(Ref(dim), val(sel))...]
sel2indices(grid, dim::AbDim, sel::At{<:AbstractVector}) = at.(Ref(dim), val(sel))
sel2indices(grid, dim::AbDim, sel::At) = at(dim, sel)
sel2indices(grid, dim::AbDim, sel::At{<:Tuple}) =
[at.(Ref(dim), val(sel), atol(sel), rtol(sel))...]
sel2indices(grid, dim::AbDim, sel::At{<:AbstractVector}) =
at.(Ref(dim), val(sel), atol(sel), rtol(sel))

# Near selector
sel2indices(grid::T, dim::AbDim, sel::Near) where T<:Union{CategoricalGrid,UnknownGrid} =
sel2indices(grid::T, dim::AbDim, sel::Near) where T<:Union{CategoricalGrid,UnknownGrid} =
throw(ArgumentError("`Near` has no meaning in a `$T`. Use `At`"))
sel2indices(grid::AbstractAllignedGrid, dim::AbDim, sel::Near) =
sel2indices(grid::AbstractAllignedGrid, dim::AbDim, sel::Near) =
near(dim, val(sel))
sel2indices(grid::AbstractAllignedGrid, dim::AbDim, sel::Near{<:Tuple}) =
sel2indices(grid::AbstractAllignedGrid, dim::AbDim, sel::Near{<:Tuple}) =
[near.(Ref(dim), val(sel))...]
sel2indices(grid::AbstractAllignedGrid, dim::AbDim, sel::Near{<:AbstractVector}) =
sel2indices(grid::AbstractAllignedGrid, dim::AbDim, sel::Near{<:AbstractVector}) =
near.(Ref(dim), val(sel))

# Between selector
Expand All @@ -72,61 +84,72 @@ sel2indices(grid, dim::AbDim, sel::Between{<:Tuple}) = between(dim, val(sel))

# Transformed grid

# We use the transformation from the first TransformedGrid dim.
# We use the transformation from the first TransformedGrid dim.
# In practice the others could be empty.
sel2indices(grids::Tuple{Vararg{<:TransformedGrid}}, dims::AbDimTuple,
sel::Tuple{Vararg{<:Selector}}) =
sel2indices(grids::Tuple{Vararg{<:TransformedGrid}}, dims::AbDimTuple,
sel::Tuple{Vararg{<:Selector}}) =
map(to_int, sel, val(dims[1])([map(val, sel)...]))

to_int(::At, x) = convert(Int, x)
to_int(::Near, x) = round(Int, x)
to_int(::At, x) = convert(Int, x)
to_int(::Near, x) = round(Int, x)

# Do the input values need some kind of scalar conversion?
# Do the input values need some kind of scalar conversion?
# what is the scale of these lookup matrices?
sel2indices(grid::LookupGrid, sel::Tuple{Vararg{At}}) =
sel2indices(grid::LookupGrid, sel::Tuple{Vararg{At}}) =
lookup(grid)[map(val, sel)...]


at(dim::AbDim, selval) = at(val(dim), selval)
at(dim, selval) = begin
at(dim::AbDim, sel::At) = at(dim, val(sel), atol(sel), rtol(sel))
at(dim::AbDim, selval, atol::Nothing, rtol::Nothing) = begin
ind = findfirst(x -> x == selval, val(dim))
ind == nothing ? throw(ArgumentError("$selval not found in $dim")) : ind
end
at(dim::AbDim, selval, atol, rtol) = begin
# This is not particularly efficient.
# It should be separated out for unordered categorical
# It should be separated out for unordered
# dims and otherwise treated as an ordered list.
ind = findfirst(x -> x == selval, val(dim))
ind = findfirst(x -> isapprox(x, selval; atol=atol, rtol=rtol), val(dim))
ind == nothing ? throw(ArgumentError("$selval not found in $dim")) : ind
end

near(list, selval) = begin
list = val(list)
ind = searchsortedfirst(list, selval)
if ind <= firstindex(list)
ind
elseif abs(list[ind] - selval) < abs(list[ind-1] - selval)
ind

near(dim::AbDim, selval) =
near(indexorder(dim), dim::AbDim, selval)
near(indexorder::Unordered, dim, selval) =
throw(ArgumentError("`Near` has no meaning in an `Unordered` grid"))
near(indexorder, dim, selval) = begin
index = val(dim)
i = searchsortedfirst(index, selval; rev=isrev(indexorder))
if i > lastindex(index)
lastindex(index)
elseif i <= firstindex(index)
firstindex(index)
elseif abs(index[i] - selval) < abs(index[i-1] - selval)
i
else
ind - 1
i - 1
end
end

between(dim::AbDim, sel) = between(indexorder(dim), dim, sel)
between(::Unordered, dim::AbDim, sel) =
between(::Unordered, dim::AbDim, sel) =
throw(ArgumentError("Cannot use `Between` on an unordered dimension"))
between(::Forward, dim::AbDim, sel) =
between(::Forward, dim::AbDim, sel) =
rangeorder(dim, searchsortedfirst(val(dim), first(sel)), searchsortedlast(val(dim), last(sel)))
between(::Reverse, dim::AbDim, sel) =
rangeorder(dim, searchsortedlast(val(dim), last(sel); rev=true),
searchsortedfirst(val(dim), first(sel); rev=true))
between(::Reverse, dim::AbDim, sel) =
rangeorder(dim, searchsortedfirst(val(dim), last(sel); rev=true),
searchsortedlast(val(dim), first(sel); rev=true))

rangeorder(dim::AbDim, lower, upper) = rangeorder(arrayorder(dim), dim, lower, upper)
rangeorder(::Forward, dim::AbDim, lower, upper) = lower:upper
rangeorder(::Reverse, dim::AbDim, lower, upper) = length(val(dim)) - upper + 1:length(val(dim)) - lower + 1


# Selector indexing without dim wrappers. Must be in the right order!
Base.@propagate_inbounds Base.getindex(a::AbstractArray, I::Vararg{Selector}) =
getindex(a, sel2indices(a, I)...)
Base.@propagate_inbounds Base.setindex!(a::AbstractArray, x, I::Vararg{Selector}) =
Base.@propagate_inbounds Base.getindex(a::AbstractArray, I::Vararg{Selector}) =
getindex(a, sel2indices(a, I)...)
Base.@propagate_inbounds Base.setindex!(a::AbstractArray, x, I::Vararg{Selector}) =
setindex!(a, x, sel2indices(a, I)...)
Base.view(a::AbstractArray, I::Vararg{Selector}) =
Base.view(a::AbstractArray, I::Vararg{Selector}) =
view(a, sel2indices(a, I)...)

Loading

4 comments on commit 8c5ce2a

@rafaqz
Copy link
Owner Author

@rafaqz rafaqz commented on 8c5ce2a Dec 4, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JuliaRegistrator
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Registration pull request created: JuliaRegistries/General/6256

After the above pull request is merged, it is recommended that a tag is created on this repository for the registered package version.

This will be done automatically if Julia TagBot is installed, or can be done manually through the github interface, or via:

git tag -a v0.2.0 -m "<description of version>" 8c5ce2a6d7cda61a3af86b7fa84367c83f8fbfba
git push origin v0.2.0

@rafaqz
Copy link
Owner Author

@rafaqz rafaqz commented on 8c5ce2a Dec 4, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JuliaRegistrator
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Registration pull request updated: JuliaRegistries/General/6256

After the above pull request is merged, it is recommended that a tag is created on this repository for the registered package version.

This will be done automatically if Julia TagBot is installed, or can be done manually through the github interface, or via:

git tag -a v0.2.0 -m "<description of version>" 8c5ce2a6d7cda61a3af86b7fa84367c83f8fbfba
git push origin v0.2.0

Please sign in to comment.