From d417e36191540ac4a3f0592160aa8d97db9b4093 Mon Sep 17 00:00:00 2001 From: Rafael Schouten Date: Tue, 23 Jan 2024 23:07:35 +0100 Subject: [PATCH 001/108] allow extra dims in reorder (#592) --- src/utils.jl | 5 ++++- test/utils.jl | 6 ++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/utils.jl b/src/utils.jl index af16858dd..4cb58bfbc 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -40,7 +40,10 @@ reorder(dim::Dimension, ot::Type{<:Order}) = ot <: basetypeof(order(dim)) ? dim : reverse(dim) # Recursive reordering. x may be reversed here -_reorder(x, orderdims::DimTuple) = _reorder(reorder(x, orderdims[1]), tail(orderdims)) +function _reorder(x, orderdims::DimTuple) + ods = commondims(orderdims, dims(x)) + _reorder(reorder(x, ods[1]), tail(ods)) +end _reorder(x, orderdims::Tuple{}) = x reorder(x, orderdim::Dimension) = _reorder(val(orderdim), x, dims(x, orderdim)) diff --git a/test/utils.jl b/test/utils.jl index 33d2b75ac..c2e5a7b27 100644 --- a/test/utils.jl +++ b/test/utils.jl @@ -88,6 +88,12 @@ end @test rev_s != s @test reo_s == s @test dims(reo_s) == dims(s) + + + @testset "reorder handles extra dimensions" begin + @test reorder(da[X=1], X=>ReverseOrdered(), Y=>ForwardOrdered()) == rev[X=1] + @test reorder(rev_s[X=1], da) == s[X=1] + end end @testset "modify" begin From 2f8f4a42bc09aca508df355c37204fe58aacd834 Mon Sep 17 00:00:00 2001 From: Ben Arthur Date: Wed, 24 Jan 2024 15:06:06 -0500 Subject: [PATCH 002/108] honor :limit kwarg in IOContext (#588) --- src/array/show.jl | 41 +++++++++++++++++++++++++---------------- 1 file changed, 25 insertions(+), 16 deletions(-) diff --git a/src/array/show.jl b/src/array/show.jl index 2af3a2536..ae75cde10 100644 --- a/src/array/show.jl +++ b/src/array/show.jl @@ -22,9 +22,7 @@ function Base.show(io::IO, mime::MIME"text/plain", A::AbstractDimArray) # Printing the array data is optional, subtypes can # show other things here instead. - ds = displaysize(io) - ioctx = IOContext(io, :displaysize => (ds[1] - lines, ds[2])) - show_after(ioctx, mime, A) + show_after(io, mime, A) return nothing end @@ -64,7 +62,7 @@ function print_array(io::IO, mime, A::AbstractDimArray{T,N}) where {T,N} end function _print_array_ctx(io, T) - IOContext(io, :compact=>true, :limit=>true, :typeinfo=>T) + IOContext(io, :compact=>true, :typeinfo=>T) end # print a name of something, in yellow function print_name(io::IO, name) @@ -76,12 +74,16 @@ end Base.print_matrix(io::IO, A::AbstractDimArray) = _print_matrix(io, parent(A), lookup(A)) # Labelled matrix printing is modified from AxisKeys.jl, thanks @mcabbot function _print_matrix(io::IO, A::AbstractArray{<:Any,1}, lookups::Tuple) - h, w = displaysize(io) - lu = lookups[1] - wn = w ÷ 3 # integers take 3 columns each when printed, floats more f1, l1, s1 = firstindex(A, 1), lastindex(A, 1), size(A, 1) - itop = s1 < h ? (f1:l1) : (f1:f1 + (h ÷ 2) - 1) - ibottom = s1 < h ? (1:0) : (f1 + s1 - (h ÷ 2) - 1:f1 + s1 - 1) + if get(io, :limit, false) + h, _ = displaysize(io) + itop = s1 < h ? (f1:l1) : (f1:f1 + (h ÷ 2) - 1) + ibottom = s1 < h ? (1:0) : (f1 + s1 - (h ÷ 2) - 1:f1 + s1 - 1) + else + itop = f1:l1 + ibottom = 1:0 + end + lu = lookups[1] labels = vcat(map(showblack, parent(lu)[itop]), map(showblack, parent(lu))[ibottom]) vals = map(showdefault, vcat(A[itop], A[ibottom])) A_dims = hcat(labels, vals) @@ -91,15 +93,22 @@ end _print_matrix(io::IO, A::AbstractDimArray, lookups::Tuple) = _print_matrix(io, parent(A), lookups) function _print_matrix(io::IO, A::AbstractArray, lookups::Tuple) lu1, lu2 = lookups - h, w = displaysize(io) - wn = w ÷ 3 # integers take 3 columns each when printed, floats more f1, f2 = firstindex(lu1), firstindex(lu2) l1, l2 = lastindex(lu1), lastindex(lu2) - s1, s2 = size(A) - itop = s1 < h ? (f1:l1) : (f1:h ÷ 2 + f1 - 1) - ibottom = s1 < h ? (f1:f1 - 1) : (f1 + s1 - h ÷ 2 - 1:f1 + s1 - 1) - ileft = s2 < wn ? (f2:l2) : (f2:f2 + wn ÷ 2 - 1) - iright = s2 < wn ? (f2:f2 - 1) : (f2 + s2 - wn ÷ 2:f2 + s2 - 1) + if get(io, :limit, false) + h, w = displaysize(io) + wn = w ÷ 3 # integers take 3 columns each when printed, floats more + s1, s2 = size(A) + itop = s1 < h ? (f1:l1) : (f1:h ÷ 2 + f1 - 1) + ibottom = s1 < h ? (f1:f1 - 1) : (f1 + s1 - h ÷ 2 - 1:f1 + s1 - 1) + ileft = s2 < wn ? (f2:l2) : (f2:f2 + wn ÷ 2 - 1) + iright = s2 < wn ? (f2:f2 - 1) : (f2 + s2 - wn ÷ 2:f2 + s2 - 1) + else + itop = f1:l1 + ibottom = f1:f1-1 + ileft = f2:l2 + iright = f2:f2-1 + end topleft = map(showdefault, A[itop, ileft]) bottomleft = A[ibottom, ileft] From de11399b5baf85121807a7741206a734179a0990 Mon Sep 17 00:00:00 2001 From: rafaqz Date: Wed, 24 Jan 2024 22:37:32 +0100 Subject: [PATCH 003/108] bugfix --- src/array/show.jl | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/array/show.jl b/src/array/show.jl index ae75cde10..9569333c0 100644 --- a/src/array/show.jl +++ b/src/array/show.jl @@ -22,7 +22,9 @@ function Base.show(io::IO, mime::MIME"text/plain", A::AbstractDimArray) # Printing the array data is optional, subtypes can # show other things here instead. - show_after(io, mime, A) + ds = displaysize(io) + ioctx = IOContext(io, :displaysize => (ds[1] - lines, ds[2])) + show_after(ioctx, mime, A) return nothing end @@ -76,7 +78,7 @@ Base.print_matrix(io::IO, A::AbstractDimArray) = _print_matrix(io, parent(A), lo function _print_matrix(io::IO, A::AbstractArray{<:Any,1}, lookups::Tuple) f1, l1, s1 = firstindex(A, 1), lastindex(A, 1), size(A, 1) if get(io, :limit, false) - h, _ = displaysize(io) + h, _ = displaysize(io) itop = s1 < h ? (f1:l1) : (f1:f1 + (h ÷ 2) - 1) ibottom = s1 < h ? (1:0) : (f1 + s1 - (h ÷ 2) - 1:f1 + s1 - 1) else From 195fa93f7983b64781e364dd8b7ed65db306ea9b Mon Sep 17 00:00:00 2001 From: Rafael Schouten Date: Tue, 30 Jan 2024 23:56:55 +0100 Subject: [PATCH 004/108] simplify summary (#593) --- src/array/show.jl | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/array/show.jl b/src/array/show.jl index 9569333c0..b8336f2e8 100644 --- a/src/array/show.jl +++ b/src/array/show.jl @@ -1,13 +1,5 @@ -function Base.summary(io::IO, A::AbstractDimArray{T,0}) where {T} - print(io, "0-dimensional ") - print(io, string(nameof(typeof(A)), "{$T,0}")) -end -function Base.summary(io::IO, A::AbstractDimArray{T,1}) where {T} - print(io, size(A, 1), "-element ") - print(io, string(nameof(typeof(A)), "{$T,1}")) -end function Base.summary(io::IO, A::AbstractDimArray{T,N}) where {T,N} - print(io, join(size(A), "×"), " ") + print(io, Base.dims2string(ndims(A)), " ") print(io, string(nameof(typeof(A)), "{$T,$N}")) end From b9298af13d4b07912cebc81f13836e44fcdccb20 Mon Sep 17 00:00:00 2001 From: Rafael Schouten Date: Wed, 31 Jan 2024 00:28:33 +0100 Subject: [PATCH 005/108] Use Date in `Near` (#598) * allow using Date in Near and test in other selectors * intervals * broken show s --- src/LookupArrays/selector.jl | 9 +++++++-- test/ecosystem.jl | 2 +- test/selector.jl | 32 ++++++++++++++++++++++++++++++-- 3 files changed, 38 insertions(+), 5 deletions(-) diff --git a/src/LookupArrays/selector.jl b/src/LookupArrays/selector.jl index cca5b4cf9..9d8d03ae8 100644 --- a/src/LookupArrays/selector.jl +++ b/src/LookupArrays/selector.jl @@ -245,6 +245,9 @@ function near(order::Ordered, ::Union{Intervals,Points}, lookup::LookupArray, se # Unwrap the selector value and adjust it for # inderval locus if neccessary v = unwrap(val(sel)) + if v isa Dates.TimeType + v = eltype(lookup)(v) + end v_adj = _locus_adjust(locus(lookup), v, lookup) # searchsortedfirst or searchsortedlast searchfunc = _searchfunc(order) @@ -278,8 +281,10 @@ 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::DateTime, lookup) = v - (v - (v - abs(step(lookup)))) / 2 -_locus_adjust(locus::End, v::DateTime, lookup) = v + (v + abs(step(lookup)) - v) / 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 """ Contains <: IntSelector diff --git a/test/ecosystem.jl b/test/ecosystem.jl index d4f867e30..9a47a5183 100644 --- a/test/ecosystem.jl +++ b/test/ecosystem.jl @@ -52,7 +52,7 @@ end @test occursin("'a'", s) @test occursin("'j'", s) s = sprint(show, MIME("text/plain"), oda3; context=:displaysize=>(15, 25)) - @test !occursin("'d'", s) + @test_broken !occursin("'d'", s) # Not sure what happened here? end end diff --git a/test/selector.jl b/test/selector.jl index 1b26dacd6..196816d71 100644 --- a/test/selector.jl +++ b/test/selector.jl @@ -1183,8 +1183,22 @@ end )) da = DimArray(1:12, timedim) @test @inferred da[Ti(At(DateTime(2001, 3)))] == 3 + @test @inferred da[Ti(At(Date(2001, 3)))] == 3 @test @inferred da[Near(DateTime(2001, 4, 7))] == 4 - @test @inferred da[Between(DateTime(2001, 4, 7), DateTime(2001, 8, 30))] == [5, 6, 7] + @test @inferred da[Near(Date(2001, 4, 7))] == 4 + @test @inferred da[DateTime(2001, 4, 7) .. DateTime(2001, 8, 30)] == [5, 6, 7] + @test @inferred da[Date(2001, 4, 7) .. Date(2001, 8, 30)] == [5, 6, 7] + + timedim = Ti(Sampled(Date(2001):Month(1):Date(2001, 12); + span=Regular(Month(1)), sampling=Intervals(Start()) + )) + da = DimArray(1:12, timedim) + @test @inferred da[Ti(At(DateTime(2001, 3)))] == 3 + @test @inferred da[Ti(At(Date(2001, 3)))] == 3 + @test @inferred da[Near(DateTime(2001, 4, 7))] == 4 + @test @inferred da[Near(Date(2001, 4, 7))] == 4 + @test @inferred da[DateTime(2001, 4, 7) .. DateTime(2001, 8, 30)] == [5, 6, 7] + @test @inferred da[Date(2001, 4, 7) .. Date(2001, 8, 30)] == [5, 6, 7] end @testset "End locus" begin timedim = Ti(Sampled(DateTime(2001):Month(1):DateTime(2001, 12); @@ -1192,8 +1206,22 @@ end ) da = DimArray(1:12, timedim) @test @inferred da[Ti(At(DateTime(2001, 3)))] == 3 + @test @inferred da[Ti(At(Date(2001, 3)))] == 3 + @test @inferred da[Near(DateTime(2001, 4, 7))] == 5 + @test @inferred da[Near(Date(2001, 4, 7))] == 5 + @test @inferred da[DateTime(2001, 4, 7) .. DateTime(2001, 8, 30)] == [6, 7, 8] + @test @inferred da[Date(2001, 4, 7) .. Date(2001, 8, 30)] == [6, 7, 8] + + timedim = Ti(Sampled(Date(2001):Month(1):Date(2001, 12); + span=Regular(Month(1)), sampling=Intervals(End())) + ) + da = DimArray(1:12, timedim) + @test @inferred da[Ti(At(DateTime(2001, 3)))] == 3 + @test @inferred da[Ti(At(Date(2001, 3)))] == 3 @test @inferred da[Near(DateTime(2001, 4, 7))] == 5 - @test @inferred da[Between(DateTime(2001, 4, 7), DateTime(2001, 8, 30))] == [6, 7, 8] + @test @inferred da[Near(Date(2001, 4, 7))] == 5 + @test @inferred da[DateTime(2001, 4, 7) .. DateTime(2001, 8, 30)] == [6, 7, 8] + @test @inferred da[Date(2001, 4, 7) .. Date(2001, 8, 30)] == [6, 7, 8] end end From 3539d85a3b378e188ba1938fb5d079444efaebb2 Mon Sep 17 00:00:00 2001 From: Rafael Schouten Date: Wed, 31 Jan 2024 00:35:43 +0100 Subject: [PATCH 006/108] add line break to fix formatting (#600) --- src/array/array.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/src/array/array.jl b/src/array/array.jl index 9144b1367..9daf85854 100644 --- a/src/array/array.jl +++ b/src/array/array.jl @@ -421,6 +421,7 @@ There are two kinds of `Dimension` value acepted: Keywords are the same as for [`DimArray`](@ref). # Example + ```@doctest julia> using DimensionalData From b4e80b3514512c376a28d5ed5e5796504ed3e4ad Mon Sep 17 00:00:00 2001 From: Rafael Schouten Date: Wed, 31 Jan 2024 00:52:51 +0100 Subject: [PATCH 007/108] fix dim macro scope (#596) * fix dim macro scope * fix macro scope --- src/Dimensions/dimension.jl | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Dimensions/dimension.jl b/src/Dimensions/dimension.jl index e31ed627c..7a5c61a4c 100644 --- a/src/Dimensions/dimension.jl +++ b/src/Dimensions/dimension.jl @@ -405,7 +405,7 @@ using DimensionalData: @dim, YDim, XDim """ macro dim end macro dim(typ::Symbol, args...) - dimmacro(typ::Symbol, :(DimensionalData.Dimension), args...) + dimmacro(typ::Symbol, Dimension, args...) end macro dim(typ::Symbol, supertyp::Symbol, args...) dimmacro(typ, supertyp, args...) @@ -417,7 +417,7 @@ function dimmacro(typ, supertype, name::String=string(typ)) val::T function $typ(val; kw...) if length(kw) > 0 - val = AutoVal(val, values(kw)) + val = $Dimensions.AutoVal(val, values(kw)) end new{typeof(val)}(val) end @@ -425,13 +425,13 @@ function dimmacro(typ, supertype, name::String=string(typ)) end function $typ(val::AbstractArray; kw...) if length(kw) > 0 - val = AutoLookup(val, values(kw)) + val = $Dimensions.AutoLookup(val, values(kw)) end $typ{typeof(val)}(val) end $typ() = $typ(:) - Dimensions.name(::Type{<:$typ}) = $(QuoteNode(Symbol(name))) - Dimensions.key2dim(::Val{$(QuoteNode(typ))}) = $typ() + $Dimensions.name(::Type{<:$typ}) = $(QuoteNode(Symbol(name))) + $Dimensions.key2dim(::Val{$(QuoteNode(typ))}) = $typ() end |> esc end From 9247429f5a2b6e95131416358b2b27f8f1372e4c Mon Sep 17 00:00:00 2001 From: Rafael Schouten Date: Wed, 31 Jan 2024 00:54:50 +0100 Subject: [PATCH 008/108] allow cat of empty arrays (#597) --- src/array/methods.jl | 11 ++++++----- test/methods.jl | 23 +++++++++++++++++++++++ 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/src/array/methods.jl b/src/array/methods.jl index 892a2fd9a..4e7f09f40 100644 --- a/src/array/methods.jl +++ b/src/array/methods.jl @@ -234,12 +234,11 @@ function _cat(catdims::Tuple, A1::AbstractDimArray, As::AbstractDimArray...) if hasdim(A1, catdim) catdim = basedims(dims(A1, catdim)) else - return AnonDim(NoLookup()) + return AnonDim(NoLookup()) # TODO: handle larger dimension extensions, this is half broken end else catdim = basedims(key2dim(catdim)) end - # Dimension # Dimension Types and Symbols if all(x -> hasdim(x, catdim), Xin) # We concatenate an existing dimension @@ -249,7 +248,7 @@ function _cat(catdims::Tuple, A1::AbstractDimArray, As::AbstractDimArray...) # vcat the index for the catdim in each of Xin joindims = map(A -> dims(A, catdim), Xin) if !check_cat_lookups(joindims...) - return Base.cat(map(parent, Xin)...; dims=dimnum(A1, catdims)) + return rebuild(catdim, NoLookup()) end _vcat_dims(joindims...) end @@ -270,14 +269,13 @@ function _cat(catdims::Tuple, A1::AbstractDimArray, As::AbstractDimArray...) end end - any(map(isnothing, newcatdims)) && return Base.cat(map(parent, Xin)...; dims=cat_dnums) - inserted_dims = dims(newcatdims, dims(A1)) appended_dims = otherdims(newcatdims, inserted_dims) inserted_dnums = dimnum(A1, inserted_dims) appended_dnums = ntuple(i -> i + length(dims(A1)), length(appended_dims)) cat_dnums = (inserted_dnums..., appended_dnums...) + # Warn if dims or val do not match, and cat the parent if !comparedims(Bool, map(x -> otherdims(x, newcatdims), Xin)...; order=true, val=true, warn=" Can't `cat` AbstractDimArray, applying to `parent` object." @@ -350,6 +348,7 @@ check_cat_lookups(dims::Dimension...) = _check_cat_lookups(D, lookups::LookupArray...) = _check_cat_lookup_order(D, lookups...) _check_cat_lookups(D, l1::NoLookup, lookups::NoLookup...) = true function _check_cat_lookups(D, l1::AbstractSampled, lookups::AbstractSampled...) + length(lookups) > 0 || return true _check_cat_lookup_order(D, l1, lookups...) || return false _check_cat_lookups(D, span(l1), l1, lookups...) end @@ -387,10 +386,12 @@ end function _check_cat_lookup_order(D, lookups::LookupArray...) l1 = first(lookups) + length(l1) == 0 && return _check_cat_lookup_order(D, Base.tail(lookups)...) L = basetypeof(l1) x = last(l1) if isordered(l1) map(Base.tail(lookups)) do lookup + length(lookup) > 0 || return true if isforward(lookup) if isreverse(l1) _cat_mixed_ordered_warn(D) diff --git a/test/methods.jl b/test/methods.jl index 9bcaa4cd5..63f84332f 100644 --- a/test/methods.jl +++ b/test/methods.jl @@ -463,6 +463,29 @@ end @test dims(cat(da, db; dims=X(NoLookup())), X) === X(NoLookup(Base.OneTo(4))) @test dims(cat(da, db; dims=X(1.0:4.0)), X) === X(Sampled(1.0:4.0, ForwardOrdered(), Regular(1.0), Points(), NoMetadata())) end + + @testset "cat empty dimarrays" begin + a = rand(X([1, 2, 3])) + b = rand(X(Int64[])) + c = cat(a, b; dims=X) + @test c == a + @test dims(c) == dims(a) + a = rand(X(Int64[]; order=DimensionalData.ForwardOrdered())) + b = rand(X([1, 2, 3])) + c = cat(a, b; dims=X) + @test c == b + @test dims(c) == dims(b) + a = rand(X(1:3)) + b = rand(X(4:0)) + c = cat(a, b; dims=X) + @test c == a + @test dims(c) == dims(a) + a = rand(X(1:0)) + b = rand(X(1:3)) + c = cat(a, b; dims=X) + @test c == b + @test dims(c) == dims(b) + end end @testset "vcat" begin From e605e754fe9436b70e2b46efae7985322c83a02f Mon Sep 17 00:00:00 2001 From: rafaqz Date: Wed, 31 Jan 2024 01:38:09 +0100 Subject: [PATCH 009/108] fix dims2string show --- src/array/show.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/array/show.jl b/src/array/show.jl index b8336f2e8..55bd47111 100644 --- a/src/array/show.jl +++ b/src/array/show.jl @@ -1,5 +1,5 @@ function Base.summary(io::IO, A::AbstractDimArray{T,N}) where {T,N} - print(io, Base.dims2string(ndims(A)), " ") + print(io, Base.dims2string(size(A)), " ") print(io, string(nameof(typeof(A)), "{$T,$N}")) end From cda985275f99654d1f100339817f34f10f3048d2 Mon Sep 17 00:00:00 2001 From: Rafael Schouten Date: Wed, 31 Jan 2024 01:40:47 +0100 Subject: [PATCH 010/108] delete lost docs paragraph --- docs/crash/course/course.jl | 4 ---- 1 file changed, 4 deletions(-) diff --git a/docs/crash/course/course.jl b/docs/crash/course/course.jl index a6f0725a3..f8fc20222 100644 --- a/docs/crash/course/course.jl +++ b/docs/crash/course/course.jl @@ -281,7 +281,3 @@ using .LookupArrays # However, indexing with sorted vectors of `Int` can be useful, so it's allowed. # But it may do strange things to interval sizes for [`Intervals`](@ref) that are # not [`Explicit`](@ref). - -# This selects the first 5 entries of the underlying array. In the case that `A` -# has only one dimension, it will be retained. Multidimensional `AbstracDimArray` -# indexed this way will return a regular array. From 718e977cc2b4ee35cc9d6ff306148cfc8f605d91 Mon Sep 17 00:00:00 2001 From: Rafael Schouten Date: Wed, 31 Jan 2024 01:50:33 +0100 Subject: [PATCH 011/108] allow zero dim eachslice in DimArray (#601) --- src/array/methods.jl | 8 ++++++-- test/methods.jl | 4 ++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/array/methods.jl b/src/array/methods.jl index 4e7f09f40..9eff6421b 100644 --- a/src/array/methods.jl +++ b/src/array/methods.jl @@ -119,13 +119,17 @@ end """ function Base.eachslice(A::AbstractDimArray; dims) dimtuple = _astuple(dims) - all(hasdim(A, dimtuple...)) || throw(DimensionMismatch("A doesn't have all dimensions $dims")) + if !(dimtuple == ()) + all(hasdim(A, dimtuple...)) || throw(DimensionMismatch("A doesn't have all dimensions $dims")) + end _eachslice(A, dimtuple) end else @inline function Base.eachslice(A::AbstractDimArray; dims, drop=true) dimtuple = _astuple(dims) - all(hasdim(A, dimtuple...)) || throw(DimensionMismatch("A doesn't have all dimensions $dims")) + if !(dimtuple == ()) + all(hasdim(A, dimtuple...)) || throw(DimensionMismatch("A doesn't have all dimensions $dims")) + end _eachslice(A, dimtuple, drop) end Base.@constprop :aggressive function _eachslice(A::AbstractDimArray{T,N}, dims, drop) where {T,N} diff --git a/test/methods.jl b/test/methods.jl index 63f84332f..a17ff44fd 100644 --- a/test/methods.jl +++ b/test/methods.jl @@ -223,6 +223,10 @@ end end end end + @testset "eachslice with empty tuple dims" begin + A = rand(X(10)) + @test ndims(eachslice(A; dims=())) == 0 + end end @testset "simple dimension permuting methods" begin From 7392044fe5a3da3c922689e31bcce5a745407554 Mon Sep 17 00:00:00 2001 From: Rafael Schouten Date: Wed, 31 Jan 2024 13:28:06 +0100 Subject: [PATCH 012/108] allow pairs for dims in constructors (#602) * allow pairs for dims in constructors * fix stack overflow --- src/Dimensions/format.jl | 8 +++++++- test/array.jl | 4 ++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/Dimensions/format.jl b/src/Dimensions/format.jl index ff31f4af2..7a12568a4 100644 --- a/src/Dimensions/format.jl +++ b/src/Dimensions/format.jl @@ -18,7 +18,13 @@ function format(dims::NamedTuple, A::AbstractArray) dims = map(keys(dims), values(dims)) do k, v rebuild(key2dim(k), v) end - return format(dims, axes(A)) + return format(dims, A) +end +function format(dims::Tuple{<:Pair,Vararg{Pair}}, A::AbstractArray) + dims = map(dims) do (k, v) + rebuild(basedims(k), v) + end + return format(dims, A) end format(dims::Tuple{Vararg{Any,N}}, A::AbstractArray{<:Any,N}) where N = format(dims, axes(A)) diff --git a/test/array.jl b/test/array.jl index 6295653d9..63f69060d 100644 --- a/test/array.jl +++ b/test/array.jl @@ -415,6 +415,10 @@ end da_reconstructed = DimArray(da) @test da == da_reconstructed @test dims(da) == dims(da_reconstructed) + @test DimArray(zeros(5, 4), (X(1:5), Y(1:4))) == + DimArray(zeros(5, 4), (X => 1:5, Y => 1:4)) == + DimArray(zeros(5, 4), (:X => 1:5, :Y => 1:4)) == + DimArray(zeros(5, 4), (X = 1:5, Y = 1:4)) end @testset "fill constructor" begin From c932bf5dcf1a8af4ec5a8cecaa1f7d749cd21367 Mon Sep 17 00:00:00 2001 From: Rafael Schouten Date: Thu, 1 Feb 2024 15:48:46 +0100 Subject: [PATCH 013/108] Try to fix reducing method inference (#603) * fix reducing method inference * test inference --- src/array/methods.jl | 23 +++++++++++------------ test/methods.jl | 11 ++++++++--- 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/src/array/methods.jl b/src/array/methods.jl index 9eff6421b..e249d84c6 100644 --- a/src/array/methods.jl +++ b/src/array/methods.jl @@ -13,17 +13,17 @@ for (m, f) in ((:Base, :sum), (:Base, :prod), (:Base, :maximum), (:Base, :minimu _f = Symbol('_', f) @eval begin # Base methods - $m.$f(A::AbstractDimArray; dims=:, kw...) = $_f(A::AbstractDimArray, dims; kw...) - $m.$f(f, A::AbstractDimArray; dims=:, kw...) = $_f(f, A::AbstractDimArray, dims; kw...) + @inline $m.$f(A::AbstractDimArray; dims=:, kw...) = $_f(A, dims; kw...) + @inline $m.$f(f, A::AbstractDimArray; dims=:, kw...) = $_f(f, A, dims; kw...) # Local dispatch methods # - Return a reduced DimArray - $_f(A::AbstractDimArray, dims; kw...) = + @inline $_f(A::AbstractDimArray, dims; kw...) = rebuild(A, $m.$f(parent(A); dims=dimnum(A, _astuple(dims)), kw...), reducedims(A, dims)) - $_f(f, A::AbstractDimArray, dims; kw...) = + @inline $_f(f, A::AbstractDimArray, dims; kw...) = rebuild(A, $m.$f(f, parent(A); dims=dimnum(A, _astuple(dims)), kw...), reducedims(A, dims)) # - Return a scalar - $_f(A::AbstractDimArray, dims::Colon; kw...) = $m.$f(parent(A); dims, kw...) - $_f(f, A::AbstractDimArray, dims::Colon; kw...) = $m.$f(f, parent(A); dims, kw...) + @inline $_f(A::AbstractDimArray, dims::Colon; kw...) = $m.$f(parent(A); dims, kw...) + @inline $_f(f, A::AbstractDimArray, dims::Colon; kw...) = $m.$f(f, parent(A); dims, kw...) end end # With no function arg version @@ -34,23 +34,22 @@ for (m, f) in ((:Statistics, :std), (:Statistics, :var)) $m.$f(A::AbstractDimArray; corrected::Bool=true, mean=nothing, dims=:) = $_f(A, corrected, mean, dims) # Local dispatch methods - Returns a reduced array - $_f(A::AbstractDimArray, corrected, mean, dims) = + @inline $_f(A::AbstractDimArray, corrected, mean, dims) = rebuild(A, $m.$f(parent(A); corrected=corrected, mean=mean, dims=dimnum(A, _astuple(dims))), reducedims(A, dims)) # - Returns a scalar - $_f(A::AbstractDimArray, corrected, mean, dims::Colon) = + @inline $_f(A::AbstractDimArray, corrected, mean, dims::Colon) = $m.$f(parent(A); corrected=corrected, mean=mean, dims=:) end end for (m, f) in ((:Statistics, :median), (:Base, :any), (:Base, :all)) _f = Symbol('_', f) @eval begin - # Base methods - $m.$f(A::AbstractDimArray; dims=:) = $_f(A, dims) + @inline $m.$f(A::AbstractDimArray; dims=:) = $_f(A, dims) # Local dispatch methods - Returns a reduced array - $_f(A::AbstractDimArray, dims) = + @inline $_f(A::AbstractDimArray, dims) = rebuild(A, $m.$f(parent(A); dims=dimnum(A, _astuple(dims))), reducedims(A, dims)) # - Returns a scalar - $_f(A::AbstractDimArray, dims::Colon) = $m.$f(parent(A); dims=:) + @inline $_f(A::AbstractDimArray, dims::Colon) = $m.$f(parent(A); dims=:) end end diff --git a/test/methods.jl b/test/methods.jl index a17ff44fd..8257d740d 100644 --- a/test/methods.jl +++ b/test/methods.jl @@ -108,9 +108,9 @@ end a = [1 2 3; 4 5 6] dimz = X(143:2:145), Y(-38:-36) da = DimArray(a, dimz) - @test median(da) == 3.5 - @test median(da; dims=X()) == [2.5 3.5 4.5] - @test median(da; dims=2) == [2.0 5.0]' + @test @inferred median(da) == 3.5 + @test @inferred median(da; dims=X()) == [2.5 3.5 4.5] + @test @inferred median(da; dims=2) == [2.0 5.0]' a = Bool[0 1 1; 0 0 0] da = DimArray(a, dimz) @@ -120,6 +120,11 @@ end @test all(da; dims=Y) == reshape([false, false], 2, 1) @test all(da; dims=(X, Y)) == reshape([false], 1, 1) + @testset "inference" begin + x = DimArray(randn(2, 3, 4), (X, Y, Z)); + foo(x) = maximum(x; dims=(1, 2)) + @inferred foo(x) + end end @testset "dimension dropping methods" begin From 4b577f1660836ffd2e08e3a968f044ed91fa1421 Mon Sep 17 00:00:00 2001 From: Rafael Schouten Date: Thu, 1 Feb 2024 19:16:29 +0100 Subject: [PATCH 014/108] no custom `lookup` for AnonDim (#604) * no custom `lookup` for AnonDim * fix AnonDim tests --- src/Dimensions/dimension.jl | 1 - test/dimension.jl | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Dimensions/dimension.jl b/src/Dimensions/dimension.jl index 7a5c61a4c..c33e3f8b1 100644 --- a/src/Dimensions/dimension.jl +++ b/src/Dimensions/dimension.jl @@ -380,7 +380,6 @@ end AnonDim() = AnonDim(Colon()) AnonDim(val, arg1, args...) = AnonDim(val) -lookup(::AnonDim) = NoLookup() metadata(::AnonDim) = NoMetadata() name(::AnonDim) = :Anon diff --git a/test/dimension.jl b/test/dimension.jl index 81eaa7fde..2c00a0f77 100644 --- a/test/dimension.jl +++ b/test/dimension.jl @@ -67,7 +67,7 @@ end @testset "AnonDim" begin @test val(AnonDim()) == Colon() - @test lookup(AnonDim()) == NoLookup() + @test lookup(AnonDim(NoLookup())) == NoLookup() @test metadata(AnonDim()) == NoMetadata() @test name(AnonDim()) == :Anon end From d23b6068513d1d2d1b0dc1e1c0f40e4564388464 Mon Sep 17 00:00:00 2001 From: Rafael Schouten Date: Sat, 3 Feb 2024 16:46:45 +0100 Subject: [PATCH 015/108] Fix mapslices (#606) * fix mapslices when output size changes * update mapslices * a typo * bugfix mapslices tests * bugfix --- src/array/methods.jl | 19 ++++++++++++++++--- test/methods.jl | 17 +++++++++++++++-- 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/src/array/methods.jl b/src/array/methods.jl index e249d84c6..9030c2c29 100644 --- a/src/array/methods.jl +++ b/src/array/methods.jl @@ -101,10 +101,23 @@ function Base.map(f, As::AbstractDimArray...) rebuild(first(As); data=newdata) end -function Base.mapslices(f, A::AbstractDimArray; dims=1, kw...) + +@inline function Base.mapslices(f, A::AbstractDimArray; dims=1, kw...) + # Run `mapslices` on the parent array dimnums = dimnum(A, _astuple(dims)) - data = mapslices(f, parent(A); dims=dimnums, kw...) - rebuild(A, data) + newdata = mapslices(f, parent(A); dims=dimnums, kw...) + ds = DD.dims(A, _astuple(dims)) + # Run one slice with dimensions to get the transformed dim + d_inds = map(d -> rebuild(d, 1), otherdims(A, ds)) + example_dims = DD.dims(f(view(A, d_inds...))) + replacement_dims = if isnothing(example_dims) || length(example_dims) != length(ds) + map(d -> rebuild(d, NoLookup()), ds) + else + example_dims + end + newdims = format(setdims(DD.dims(A), replacement_dims), newdata) + + return rebuild(A, newdata, newdims) end @static if VERSION < v"1.9-alpha1" diff --git a/test/methods.jl b/test/methods.jl index 8257d740d..eba8f9736 100644 --- a/test/methods.jl +++ b/test/methods.jl @@ -345,14 +345,27 @@ end ms = mapslices(sum, da; dims) @test ms == [9 12 15 18] @test DimensionalData.dims(ms) == - (Y(Sampled(10:10:30, ForwardOrdered(), Regular(10), Intervals(Center()), NoMetadata())), - Ti(Sampled(1:4, ForwardOrdered(), Regular(1), Intervals(Start()), NoMetadata()))) + (Y(NoLookup(Base.OneTo(1))), Ti(Sampled(1:4, ForwardOrdered(), Regular(1), Intervals(Start()), NoMetadata()))) @test refdims(ms) == () end for dims in tis ms = mapslices(sum, da; dims) @test parent(ms) == [10 18 26]' end + + @testset "size changes" begin + x = DimArray(randn(4, 100, 2), (:chain, :draw, :x_dim_1)); + y = mapslices(vec, x; dims=(:chain, :draw)) + @test size(y) == size(dims(y)) + x = rand(X(1:10), Y([:a, :b, :c]), Ti(10)) + y = mapslices(sum, x; dims=(X, Y)) + @test size(y) == size(dims(y)) + @test dims(y) == (X(NoLookup(Base.OneTo(1))), Y(NoLookup(Base.OneTo(1))), Ti(NoLookup(Base.OneTo(10)))) + + y = mapslices(A -> A[2:9, :], x; dims=(X, Y)) + @test size(y) == size(dims(y)) + @test dims(y) == dims(x[2:9, :, :]) + end end @testset "cat" begin From a9dd0e912948063fa40cdda092901dd487e0a66b Mon Sep 17 00:00:00 2001 From: Rafael Schouten Date: Sun, 4 Feb 2024 17:18:55 +0100 Subject: [PATCH 016/108] Breaking: Stack rand (#567) * test rand on stacks * add rand for stack * indexing fixes --- src/stack/indexing.jl | 50 ++++++++++++++++++++++++++++++++++++++----- src/stack/methods.jl | 7 ++++++ src/stack/stack.jl | 2 +- test/stack.jl | 15 +++++++++---- 4 files changed, 64 insertions(+), 10 deletions(-) diff --git a/src/stack/indexing.jl b/src/stack/indexing.jl index cf62404b2..b7df97814 100644 --- a/src/stack/indexing.jl +++ b/src/stack/indexing.jl @@ -12,18 +12,58 @@ end end # Array-like indexing -@propagate_inbounds Base.getindex(s::AbstractDimStack, i::Int, I::Int...) = - map(A -> Base.getindex(A, i, I...), data(s)) for f in (:getindex, :view, :dotview) @eval begin - @propagate_inbounds function Base.$f(s::AbstractDimStack, I...; kw...) - newlayers = map(A -> Base.$f(A, I...; kw...), layers(s)) + @propagate_inbounds function Base.$f( + s::AbstractDimStack, i1::Union{Integer,CartesianIndex}, Is::Union{Integer,CartesianIndex}... + ) + # Convert to Dimension wrappers to handle mixed size layers + Base.$f(s, DimIndices(s)[i1, Is...]...) + end + @propagate_inbounds function Base.$f(s::AbstractDimStack, i1, Is...) + I = (i1, Is...) + if length(dims(s)) > length(I) + throw(BoundsError(dims(s), I)) + elseif length(dims(s)) < length(I) + # Allow trailing ones + if all(i -> i isa Integer && i == 1, I[length(dims(s)):end]) + I = I[1:length(dims)] + else + throw(BoundsError(dims(s), I)) + end + end + # Convert to Dimension wrappers to handle mixed size layers + Base.$f(s, map(rebuild, dims(s), I)...) + end + @propagate_inbounds function Base.$f(s::AbstractDimStack, i::AbstractArray) + # Multidimensional: return vectors of values + if length(dims(s)) > 1 + Ds = DimIndices(s)[i] + map(s) do A + map(D -> A[D...], Ds) + end + else + map(A -> A[i], s) + end + end + @propagate_inbounds function Base.$f(s::AbstractDimStack, D::Dimension...; kw...) + alldims = (D..., kwdims(values(kw))...) + extradims = otherdims(alldims, dims(s)) + length(extradims) > 0 && Dimensions._extradimswarn(extradims) + newlayers = map(layers(s)) do A + layerdims = dims(alldims, dims(A)) + I = length(layerdims) > 0 ? layerdims : map(_ -> :, size(A)) + Base.$f(A, I...) + end if all(map(v -> v isa AbstractDimArray, newlayers)) - rebuildsliced(Base.$f, s, newlayers, (dims2indices(dims(s), (I..., kwdims(values(kw))...)))) + rebuildsliced(Base.$f, s, newlayers, (dims2indices(dims(s), alldims))) else newlayers end end + @propagate_inbounds function Base.$f(s::AbstractDimStack) + map($f, s) + end end end diff --git a/src/stack/methods.jl b/src/stack/methods.jl index 52a956c2c..4b8ba7ddb 100644 --- a/src/stack/methods.jl +++ b/src/stack/methods.jl @@ -222,3 +222,10 @@ for fname in (:one, :oneunit, :zero, :copy) end Base.reverse(s::AbstractDimStack; dims=1) = map(A -> reverse(A; dims=dims), s) + +# Random +Random.Sampler(RNG::Type{<:AbstractRNG}, st::AbstractDimStack, n::Random.Repetition) = + Random.SamplerSimple(st, Random.Sampler(RNG, DimIndices(st), n)) + +Random.rand(rng::AbstractRNG, sp::Random.SamplerSimple{<:AbstractDimStack,<:Random.Sampler}) = + @inbounds return sp[][rand(rng, sp.data)...] diff --git a/src/stack/stack.jl b/src/stack/stack.jl index 815938bac..8a671b0dc 100644 --- a/src/stack/stack.jl +++ b/src/stack/stack.jl @@ -103,7 +103,7 @@ Base.axes(s::AbstractDimStack) = map(first ∘ axes, dims(s)) Base.axes(s::AbstractDimStack, dims::DimOrDimType) = axes(s, dimnum(s, dims)) Base.axes(s::AbstractDimStack, dims::Integer) = axes(s)[dims] Base.similar(s::AbstractDimStack, args...) = map(A -> similar(A, args...), s) -Base.eltype(s::AbstractDimStack, args...) = map(eltype, s) +Base.eltype(s::AbstractDimStack, args...) = NamedTuple{keys(s),Tuple{map(eltype, s)...}} Base.iterate(s::AbstractDimStack, args...) = iterate(layers(s), args...) Base.read(s::AbstractDimStack) = map(read, s) # `merge` for AbstractDimStack and NamedTuple. diff --git a/test/stack.jl b/test/stack.jl index 0c03c5881..0e2796a25 100644 --- a/test/stack.jl +++ b/test/stack.jl @@ -1,4 +1,4 @@ -using DimensionalData, Test, LinearAlgebra, Statistics, ConstructionBase +using DimensionalData, Test, LinearAlgebra, Statistics, ConstructionBase, Random using DimensionalData: data using DimensionalData: Sampled, Categorical, AutoLookup, NoLookup, Transformed, @@ -63,7 +63,7 @@ end @testset "low level base methods" begin @test keys(data(s)) == (:one, :two, :three) @test keys(data(mixed)) == (:one, :two, :extradim) - @test eltype(mixed) === (one=Float64, two=Float32, extradim=Float64) + @test eltype(mixed) === @NamedTuple{one::Float64, two::Float32, extradim::Float64} @test haskey(s, :one) == true @test haskey(s, :zero) == false @test length(s) == 3 # length is as for NamedTuple @@ -86,11 +86,11 @@ end @test all(map(similar(mixed), mixed) do s, m dims(s) == dims(m) && dims(s) === dims(m) && eltype(s) === eltype(m) end) - @test all(map(==(Int), eltype(similar(s, Int)))) + @test eltype(similar(s, Int)) === @NamedTuple{one::Int, two::Int, three::Int} st2 = similar(mixed, Bool, x, y) @test dims(st2) === (x, y) @test dims(st2[:one]) === (x, y) - @test all(map(==(Bool), eltype(st2))) + @test eltype(st2) === @NamedTuple{one::Bool, two::Bool, extradim::Bool} end @testset "merge" begin @@ -332,3 +332,10 @@ end @test extrema(f, s) == (one=(2.0, 12.0), two=(4.0, 24.0), three=(6.0, 36.0)) @test mean(f, s) == (one=7.0, two=14.0, three=21) end + +@testset "rand sampling" begin + @test rand(s) isa @NamedTuple{one::Float64, two::Float32, three::Int} + @test rand(Xoshiro(), s) isa @NamedTuple{one::Float64, two::Float32, three::Int} + @test rand(mixed) isa @NamedTuple{one::Float64, two::Float32, extradim::Float64} + @test rand(MersenneTwister(), mixed) isa @NamedTuple{one::Float64, two::Float32, extradim::Float64} +end From 5f0d129fa8a920507cf789048b861d109f51c734 Mon Sep 17 00:00:00 2001 From: Rafael Schouten Date: Sun, 4 Feb 2024 17:19:17 +0100 Subject: [PATCH 017/108] Breaking: add cumsum and cumsum! (#608) * add cumsum and cumsum! * a typo --- src/array/methods.jl | 5 +++++ test/methods.jl | 16 ++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/src/array/methods.jl b/src/array/methods.jl index 9030c2c29..f1f1341d7 100644 --- a/src/array/methods.jl +++ b/src/array/methods.jl @@ -165,6 +165,11 @@ else end end +Base.cumsum(A::AbstractDimVector) = rebuild(A, Base.cumsum(parent(A))) +Base.cumsum(A::AbstractDimArray; dims) = rebuild(A, cumsum(parent(A); dims=dimnum(A, dims))) +Base.cumsum!(B::AbstractArray, A::AbstractDimVector) = cumsum!(B, parent(A)) +Base.cumsum!(B::AbstractArray, A::AbstractDimArray; dims) = cumsum!(B, parent(A); dims=dimnum(A, dims)) + # works for arrays and for stacks function _eachslice(x, dims::Tuple) slicedims = Dimensions.dims(x, dims) diff --git a/test/methods.jl b/test/methods.jl index eba8f9736..b8cff9ac3 100644 --- a/test/methods.jl +++ b/test/methods.jl @@ -368,6 +368,22 @@ end end end +@testset "cumsum" begin + v = DimArray([10:-1:1...], X) + @test cumsum(v) == cumsum(parent(v)) + @test dims(cumsum(v)) == dims(v) + A = rand((X(5:-1:1), Y(11:15))) + @test cumsum(A; dims=X) == cumsum(parent(A); dims=1) + @test dims(cumsum(A; dims=X)) == dims(A) +end + +@testset "cumsum!" begin + v = DimArray([10:-1:1...], X) + @test cumsum!(copy(v), v) == cumsum(parent(v)) + A = rand((X(5:-1:1), Y(11:15))) + @test cumsum!(copy(A), A; dims=X) == cumsum(parent(A); dims=1) +end + @testset "cat" begin a = [1 2 3; 4 5 6] da = DimArray(a, (X(4.0:5.0), Y(6.0:8.0))) From 000887403d3e725fd8af35fe7b3051e77d58b0b6 Mon Sep 17 00:00:00 2001 From: Rafael Schouten Date: Sun, 4 Feb 2024 17:54:06 +0100 Subject: [PATCH 018/108] Breaking: add sort and sortslices (#607) * add sort and sortslices * mk --- src/array/methods.jl | 25 +++++++++++++++++++++++++ test/methods.jl | 18 ++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/src/array/methods.jl b/src/array/methods.jl index f1f1341d7..cef587395 100644 --- a/src/array/methods.jl +++ b/src/array/methods.jl @@ -165,6 +165,31 @@ else end end +# These just return the parent for now +function Base.sort(A::AbstractDimVector; kw...) + newdims = (set(only(dims(A)), NoLookup()),) + newdata = sort(parent(A), kw...) + return rebuild(A, newdata, newdims) +end +function Base.sort(A::AbstractDimArray; dims, kw...) + newdata = sort(parent(A), dims=dimnum(A, dims), kw...) + replacement_dims = map(DD.dims(A, _astuple(dims))) do d + set(d, NoLookup()) + end + newdims = setdims(DD.dims(A), replacement_dims) + return rebuild(A, newdata, newdims) +end + +function Base.sortslices(A::AbstractDimArray; dims, kw...) + newdata = sortslices(parent(A), dims=dimnum(A, dims), kw...) + replacement_dims = map(DD.dims(A, _astuple(dims))) do d + set(d, NoLookup()) + end + newdims = setdims(DD.dims(A), replacement_dims) + return rebuild(A, newdata, newdims) +end + + Base.cumsum(A::AbstractDimVector) = rebuild(A, Base.cumsum(parent(A))) Base.cumsum(A::AbstractDimArray; dims) = rebuild(A, cumsum(parent(A); dims=dimnum(A, dims))) Base.cumsum!(B::AbstractArray, A::AbstractDimVector) = cumsum!(B, parent(A)) diff --git a/test/methods.jl b/test/methods.jl index b8cff9ac3..01bb29fd5 100644 --- a/test/methods.jl +++ b/test/methods.jl @@ -384,6 +384,24 @@ end @test cumsum!(copy(A), A; dims=X) == cumsum(parent(A); dims=1) end +@testset "sort" begin + v = DimArray([10:-1:1...], X) + @test sort(v) == sort(parent(v)) + @test dims(sort(v)) == (X(NoLookup(Base.OneTo(10))),) + A = rand((X(5:-1:1), Y(11:15))) + @test sort(A; dims=X) == sort(parent(A); dims=1) + @test dims(sort(A; dims=X)) == (X(NoLookup(Base.OneTo(5))), dims(A, Y)) +end + +@testset "sortslices" begin + M = rand((X(5:-1:1), Y(11:15))) + @test sortslices(M; dims=X) == sortslices(parent(M); dims=1) + @test dims(sort(M; dims=X)) == (X(NoLookup(Base.OneTo(5))), dims(M, Y)) + M = rand((X(5:-1:1), Y(11:15), Ti(3:10))) + @test sortslices(M; dims=(X, Y)) == sortslices(parent(M); dims=(1, 2)) + @test dims(sortslices(M; dims=(X, Y))) == (X(NoLookup(Base.OneTo(5))), Y(NoLookup(Base.OneTo(5))), dims(M, Ti)) +end + @testset "cat" begin a = [1 2 3; 4 5 6] da = DimArray(a, (X(4.0:5.0), Y(6.0:8.0))) From ebf432a51e65ee2fe8aa1407e81c91264c65e6e6 Mon Sep 17 00:00:00 2001 From: Rafael Schouten Date: Sun, 4 Feb 2024 18:41:24 +0100 Subject: [PATCH 019/108] add DimVector, DimMatrix, DimVecOrMat etc (#612) --- src/DimensionalData.jl | 2 ++ src/array/array.jl | 10 ++++++++++ test/array.jl | 15 +++++++++++++++ 3 files changed, 27 insertions(+) diff --git a/src/DimensionalData.jl b/src/DimensionalData.jl index 89506640f..cdea06ada 100644 --- a/src/DimensionalData.jl +++ b/src/DimensionalData.jl @@ -53,6 +53,8 @@ export At, Between, Touches, Contains, Near, Where, All, .., Not export AbstractDimArray, DimArray +export AbstractDimVector, AbstractDimMatrix, AbstractDimVecOrMat, DimVector, DimMatrix, DimVecOrMat + export AbstractDimStack, DimStack export AbstractDimTable, DimTable diff --git a/src/array/array.jl b/src/array/array.jl index 9daf85854..feb5e7fab 100644 --- a/src/array/array.jl +++ b/src/array/array.jl @@ -21,6 +21,7 @@ abstract type AbstractDimArray{T,N,D<:Tuple,A} <: AbstractArray{T,N} end const AbstractDimVector = AbstractDimArray{T,1} where T const AbstractDimMatrix = AbstractDimArray{T,2} where T +const AbstractDimVecOrMat = Union{AbstractDimVector,AbstractDimMatrix} # DimensionalData.jl interface methods #################################################### @@ -382,6 +383,15 @@ function DimArray(f::Function, dim::Dimension; name=Symbol(nameof(f), "(", name( DimArray(f.(val(dim)), (dim,); name) end +const DimVector = DimArray{T,1} where T +const DimMatrix = DimArray{T,2} where T +const DimVecOrMat = Union{DimVector,DimMatrix} + +DimVector(A::AbstractVector, dim::Dimension, args...; kw...) = + DimArray(A, (dim,), args...; kw...) +DimVector(A::AbstractVector, args...; kw...) = DimArray(A, args...; kw...) +DimMatrix(A::AbstractMatrix, args...; kw...) = DimArray(A, args...; kw...) + Base.convert(::Type{DimArray}, A::AbstractDimArray) = DimArray(A) Base.convert(::Type{DimArray{T}}, A::AbstractDimArray) where {T} = DimArray{T}(A) diff --git a/test/array.jl b/test/array.jl index 63f69060d..2148be7f6 100644 --- a/test/array.jl +++ b/test/array.jl @@ -421,6 +421,21 @@ end DimArray(zeros(5, 4), (X = 1:5, Y = 1:4)) end +@testset "DimVector and DimMatrix" begin + dv = DimVector([1, 2, 3], X); + @test dv isa DimArray{Int,1} + @test dv isa DimVector + @test dv isa DimVecOrMat + @test dv isa AbstractDimVector + @test dv isa AbstractDimVecOrMat + dm = DimMatrix(zeros(3, 4), (X(), Y())); + @test dm isa DimArray{Float64,2} + @test dm isa DimMatrix + @test dm isa DimVecOrMat + @test dm isa AbstractDimMatrix + @test dm isa AbstractDimVecOrMat +end + @testset "fill constructor" begin da = fill(5.0, X(4), Y(40.0:10.0:80.0)) @test parent(da) == fill(5.0, (4, 5)) From ee64df0504d3e7fb836b20d1e24f76d10609c2d8 Mon Sep 17 00:00:00 2001 From: Rafael Schouten Date: Sun, 4 Feb 2024 18:53:56 +0100 Subject: [PATCH 020/108] Breaking: per dimension colors in show (#611) * color per dims show * bugfix * tweaks * add show_after interface * box instructions * bugfix show * tweaks * layout tweaks for dims * add last filled block * revert show * bugfix show tests --- docs/src/reference.md | 6 ++ src/Dimensions/merged.jl | 2 +- src/Dimensions/show.jl | 68 ++++++++------ src/LookupArrays/show.jl | 10 +- src/array/show.jl | 194 ++++++++++++++++++++++++++++++--------- src/stack/show.jl | 94 +++++++++++-------- test/merged.jl | 6 +- test/runtests.jl | 2 +- test/show.jl | 6 +- 9 files changed, 268 insertions(+), 120 deletions(-) diff --git a/docs/src/reference.md b/docs/src/reference.md index 1e2670870..91ee71f64 100644 --- a/docs/src/reference.md +++ b/docs/src/reference.md @@ -253,3 +253,9 @@ DimensionalData.AbstractName DimensionalData.Name DimensionalData.NoName ``` + +## Show methods for packages extending DimensionalData.jl + +```@docs +show_after +``` diff --git a/src/Dimensions/merged.jl b/src/Dimensions/merged.jl index d07f02717..174d70bf3 100644 --- a/src/Dimensions/merged.jl +++ b/src/Dimensions/merged.jl @@ -59,7 +59,7 @@ end function LookupArrays.show_properties(io::IO, mime, lookup::MergedLookup) print(io, " ") - show(io, mime, basedims(lookup)) + show(IOContext(io, :inset => "", :dimcolor => 244), mime, basedims(lookup)) end # Dimension methods diff --git a/src/Dimensions/show.jl b/src/Dimensions/show.jl index efc6ec6eb..936961813 100644 --- a/src/Dimensions/show.jl +++ b/src/Dimensions/show.jl @@ -1,26 +1,49 @@ +function dimsymbols(i) + symbols = ['↓', '→', '↗', '⬔', '◩', '⬒', '⬓', '■'] + symbols[min(i, length(symbols))] +end +function dimcolors(i) + # colors = [203, 37, 162, 106, 67, 173, 91] + # colors = [110, 216, 223, 218, 153, 79, 185, 142, 253] + # colors = reverse([61, 153, 73, 29, 143, 186, 174, 132, 133]) + # colors = [61, 153, 73, 29, 143, 186, 174, 132, 133] + # colors = [67, 210, 71, 185, 117, 132, 249] + colors = [209, 32, 81, 204, 249, 166, 37] + colors[min(i, length(colors))] +end function Base.show(io::IO, mime::MIME"text/plain", dims::DimTuple) - ctx = IOContext(io, :compact=>true) + ctx = IOContext(io, :compact => true) + inset = get(io, :inset, "") + print(io, inset) if all(map(d -> !(parent(d) isa AbstractArray) || (parent(d) isa NoLookup), dims)) - show(ctx, mime, first(dims)) - map(Base.tail(dims)) do d + dc = get(ctx, :dimcolor, dimcolors(1)) + printstyled(io, dimsymbols(1), ' '; color=dc) + show(IOContext(ctx, :dimcolor => dc, :dimname_len => 0), mime, first(dims)) + foreach(enumerate(Base.tail(dims))) do (i, d) + n = i + 1 print(io, ", ") - show(ctx, mime, d) + dc = get(ctx, :dimcolor, dimcolors(n)) + printstyled(io, dimsymbols(n), ' '; color=dc) + show(IOContext(ctx, :dimcolor => dc, :dimname_len => 0), mime, d) end return 0 else # Dims get a line each - haskey(io, :inset) && println(io) - inset = get(io, :inset, "") lines = 3 - print(io, inset) - show(ctx, mime, first(dims)) - lines += 2 # Often they wrap - map(Base.tail(dims)) do d - print(io, ",") - lines += 2 # Often they wrap - print(io, "\n") - print(io, inset) - show(ctx, mime, d) + dc = get(ctx, :dimcolor, dimcolors(1)) + printstyled(io, dimsymbols(1), ' '; color=dc) + maxname = maximum(length ∘ string ∘ dim2key, dims) + dim_ctx = IOContext(ctx, :dimcolor => dc, :dimname_len=> maxname) + show(dim_ctx, mime, first(dims)) + lines += 1 + map(Base.tail(dims), ntuple(x -> x + 1, length(dims) - 1)) do d, n + lines += 1 + s = dimsymbols(n) + print(io, ",\n", inset) + dc = get(ctx, :dimcolor, dimcolors(n)) + printstyled(io, s, ' '; color=dc) + dim_ctx = IOContext(ctx, :dimcolor => dc, :dimname_len => maxname) + show(dim_ctx, mime, d) end return lines end @@ -50,7 +73,7 @@ function show_compact(io::IO, mime, dim::Dimension) end end -dimcolor(io) = get(io, :is_ref_dim, false) ? :magenta : :red +dimcolor(io) = get(io, :dimcolor, dimcolors(1)) # print dims with description string and inset function print_dims(io::IO, mime, dims::Tuple{}) @@ -59,8 +82,6 @@ function print_dims(io::IO, mime, dims::Tuple{}) return 0 end function print_dims(io::IO, mime, dims::Tuple) - @nospecialize io mime dims - printstyled(io, " with dimensions: "; color=:light_black) ctx = IOContext(io, :inset => " ") return show(ctx, mime, dims) end @@ -75,21 +96,16 @@ function print_refdims(io::IO, mime, refdims::Tuple) return lines end # print a dimension name -function print_dimname(io::IO, dim::Dim) - color = dimcolor(io) - printstyled(io, "Dim{"; color=color) - printstyled(io, string(":", name(dim)); color=:yellow) - printstyled(io, "}"; color=color) -end function print_dimname(io, dim::Dimension) - printstyled(io, dim2key(dim); color = dimcolor(io)) + dimname_len = get(io, :dimname_len, 0) + printstyled(io, rpad(dim2key(dim), dimname_len); color=dimcolor(io)) end # print the dimension value function print_dimval(io, mime, val, nchars=0) val isa Colon || print(io, " ") - printstyled(io, val; color=:cyan) + printstyled(io, val; color=get(io, :dimcolor, 1)) end function print_dimval(io, mime, lookup::AbstractArray, nchars=0) LookupArrays.print_index(io, mime, lookup, nchars) diff --git a/src/LookupArrays/show.jl b/src/LookupArrays/show.jl index 2094a642a..5a7f9fc63 100644 --- a/src/LookupArrays/show.jl +++ b/src/LookupArrays/show.jl @@ -71,9 +71,9 @@ function show_compact(io, mime, lookup::LookupArray) print(io, "}") end -print_order(io, lookup) = print(io, nameof(typeof(order(lookup)))) -print_span(io, lookup) = print(io, nameof(typeof(span(lookup)))) -print_sampling(io, lookup) = print(io, typeof(sampling(lookup))) +print_order(io, lookup) = printstyled(io, nameof(typeof(order(lookup))); color=244) +print_span(io, lookup) = printstyled(io, nameof(typeof(span(lookup))); color=244) +print_sampling(io, lookup) = printstyled(io, typeof(sampling(lookup)); color=244) function print_metadata(io, lookup) metadata(lookup) isa NoMetadata && return nothing print(io, nameof(typeof(metadata(lookup)))) @@ -81,7 +81,7 @@ end function print_index(io, mime, A::AbstractRange, nchars=0) print(io, " ") - printstyled(io, A; color=:cyan) + printstyled(io, repr(A); color=get(io, :dimcolor, :white)) end function print_index(io, mime, v::AbstractVector, nchars=0) print(io, " ") @@ -94,5 +94,5 @@ function print_index(io, mime, v::AbstractVector, nchars=0) else join((repr(va) for va in v), ", ") end - printstyled(io, string(string(eltype(v)), "[", string(vals), "]"); color=:cyan) + printstyled(io, "[", string(vals), "]"; color=get(io, :dimcolor, :white)) end diff --git a/src/array/show.jl b/src/array/show.jl index 55bd47111..ae398cf2f 100644 --- a/src/array/show.jl +++ b/src/array/show.jl @@ -1,30 +1,123 @@ +using DimensionalData.Dimensions: dimcolors, dimsymbols, print_dims + function Base.summary(io::IO, A::AbstractDimArray{T,N}) where {T,N} - print(io, Base.dims2string(size(A)), " ") + if N > 1 + print_sizes(io, size(A)) + print(io, ' ') + else + print(io, Base.dims2string(size(A)), " ") + end print(io, string(nameof(typeof(A)), "{$T,$N}")) + print_name(io, name(A)) end -function Base.show(io::IO, mime::MIME"text/plain", A::AbstractDimArray) - lines = 0 - summary(io, A) - print_name(io, name(A)) - lines += Dimensions.print_dims(io, mime, dims(A)) - !(isempty(dims(A)) || isempty(refdims(A))) && println(io) - lines += Dimensions.print_refdims(io, mime, refdims(A)) - println(io) +function Base.show(io::IO, mime::MIME"text/plain", A::AbstractDimArray{T,N}) where {T,N} + lines, maxlen, width = print_top(io, mime, A) + m, _ = print_metadata_block(io, mime, metadata(A); width, maxlen=min(width, maxlen)) + lines += m - # Printing the array data is optional, subtypes can + # Printing the array data is optional, subtypes can # show other things here instead. ds = displaysize(io) ioctx = IOContext(io, :displaysize => (ds[1] - lines, ds[2])) - show_after(ioctx, mime, A) + show_after(ioctx, mime, A; maxlen) return nothing end +function print_top(io, mime, A; bottom_border=metadata(A) isa Union{Nothing,NoMetadata}) + lines = 4 + _, width = displaysize(io) + upchar = maxlen = min(width, length(sprint(summary, A)) + 2) + printstyled(io, '╭', '─'^maxlen, '╮'; color=:light_black) + println(io) + printstyled(io, "│ "; color=:light_black) + summary(io, A) + printstyled(io, " │"; color=:light_black) + p = sprint(mime, dims(A)) do args... + print_dims_block(args...; upchar=maxlen, bottom_border, width, maxlen) + end + maxlen = max(maxlen, maximum(length, split(p, '\n'))) + p = sprint(mime, metadata(A)) do args... + print_metadata_block(args...; width, maxlen) + end + maxlen = max(maxlen, maximum(length, split(p, '\n'))) + n, maxlen = print_dims_block(io, mime, dims(A); upchar, width, bottom_border, maxlen) + lines += n + return lines, maxlen, width +end + +function print_sizes(io, size; + colors=map(dimcolors, ntuple(identity, length(size))) +) + foreach(enumerate(size[1:end-1])) do (n, s) + printstyled(io, s; color=colors[n]) + print(io, '×') + end + printstyled(io, last(size); color=colors[length(size)]) +end + +function print_dims_block(io, mime, dims; bottom_border=true, upchar, width, maxlen) + lines = 0 + if !isempty(dims) + # if all(l -> l isa NoLookup, lookup(dims)) + # printstyled(io, '├', '─'^(upchar), '┴', '─'^max(0, (maxlen - 7 - upchar)), " dims ┐"; color=:light_black) + # print(io, ' ') + # lines += print_dims(io, mime, dims) + # else + dim_string = sprint(print_dims, mime, dims) + maxlen = min(width - 2, max(maxlen, maximum(length, split(dim_string, '\n')))) + println(io) + printstyled(io, '├', '─'^(upchar), '┴', '─'^max(0, (maxlen - 7 - upchar)), " dims ┐"; color=:light_black) + println(io) + lines += print_dims(io, mime, dims) + println(io) + lines += 2 + # end + end + return lines, maxlen +end + +function print_metadata_block(io, mime, metadata; maxlen=0, width) + lines = 0 + if !(metadata isa NoMetadata) + metadata_print = split(sprint(show, mime, metadata), "\n") + maxlen = min(width-2, max(maxlen, maximum(length, metadata_print))) + printstyled(io, '├', '─'^max(0, maxlen - 10), " metadata ┤"; color=:light_black) + println(io) + print(io, " ") + show(io, mime, metadata) + println(io) + lines += length(metadata_print) + 3 + end + return lines, maxlen +end + + +""" + show_after(io::IO, mime, A::AbstractDimArray; maxlen, kw...) + show_after(io::IO, mime, A::AbstractDimStack; maxlen, kw...) + +Interface methods for adding addional `show` text +for AbstractDimArray/AbstractDimStack subtypes. + +*Always include `kw` to avoid future breaking changes* + +Additional keywords may be added at any time. + +Note - a anssi box is left unclosed. This method needs to close it, +or add more. `maxlen` is the maximum length of the inner text. -# Semi-interface methods for adding addional `show` text -# for AbstractDimArray/AbstractDimStack subtypes -# TODO actually document in the interface -show_after(io::IO, mime, A::AbstractDimArray) = print_array(io, mime, A) +Most likely you always want to close the box with: + +'''julia +printstyled(io, '└', '─'^maxlen, '┘'; color=:light_black) +''' +""" +function show_after(io::IO, mime, A::AbstractDimArray; maxlen, kw...) + printstyled(io, '└', '─'^maxlen, '┘'; color=:light_black) + println(io) + print_array(io, mime, A) +end # Showing the array is optional for AbstractDimArray # `print_array` must be called from `show_after`. @@ -40,19 +133,28 @@ end function print_array(io::IO, mime, A::AbstractDimArray{T,3}) where T i3 = firstindex(A, 3) frame = view(parent(A), :, :, i3) - println(io, "[:, :, $i3]") + + _print_indices_vec(io, i3) _print_matrix(_print_array_ctx(io, T), frame, lookup(A, (1, 2))) - nremaining = size(A, 3) - 1 - nremaining > 0 && printstyled(io, "\n[and $nremaining more slices...]"; color=:light_black) end function print_array(io::IO, mime, A::AbstractDimArray{T,N}) where {T,N} o = ntuple(x -> firstindex(A, x + 2), N-2) frame = view(A, :, :, o...) - onestring = join(o, ", ") - println(io, "[:, :, $(onestring)]") + + _print_indices_vec(io, o...) _print_matrix(_print_array_ctx(io, T), frame, lookup(A, (1, 2))) - nremaining = prod(size(A, d) for d=3:N) - 1 - nremaining > 0 && printstyled(io, "\n[and $nremaining more slices...]"; color=:light_black) +end + +function _print_indices_vec(io, o...) + print(io, "[") + printstyled(io, ":"; color=dimcolors(1)) + print(io, ", ") + printstyled(io, ":"; color=dimcolors(2)) + foreach(enumerate(o)) do (i, fi) + print(io, ", ") + printstyled(io, fi; color=dimcolors(i + 2)) + end + println(io, "]") end function _print_array_ctx(io, T) @@ -61,7 +163,7 @@ end # print a name of something, in yellow function print_name(io::IO, name) if !(name == Symbol("") || name isa NoName) - printstyled(io, string(" ", name); color=:yellow) + printstyled(io, string(" ", name); color=dimcolors(100)) end end @@ -70,7 +172,7 @@ Base.print_matrix(io::IO, A::AbstractDimArray) = _print_matrix(io, parent(A), lo function _print_matrix(io::IO, A::AbstractArray{<:Any,1}, lookups::Tuple) f1, l1, s1 = firstindex(A, 1), lastindex(A, 1), size(A, 1) if get(io, :limit, false) - h, _ = displaysize(io) + h, _ = displaysize(io) itop = s1 < h ? (f1:l1) : (f1:f1 + (h ÷ 2) - 1) ibottom = s1 < h ? (1:0) : (f1 + s1 - (h ÷ 2) - 1:f1 + s1 - 1) else @@ -84,7 +186,7 @@ function _print_matrix(io::IO, A::AbstractArray{<:Any,1}, lookups::Tuple) Base.print_matrix(io, A_dims) return nothing end -_print_matrix(io::IO, A::AbstractDimArray, lookups::Tuple) = _print_matrix(io, parent(A), lookups) +_print_matrix(io::IO, A::AbstractDimArray, lookups::Tuple) = _print_matrix(io, parent(A), lookups) function _print_matrix(io::IO, A::AbstractArray, lookups::Tuple) lu1, lu2 = lookups f1, f2 = firstindex(lu1), firstindex(lu2) @@ -106,9 +208,9 @@ function _print_matrix(io::IO, A::AbstractArray, lookups::Tuple) topleft = map(showdefault, A[itop, ileft]) bottomleft = A[ibottom, ileft] - if !(lookups[1] isa NoLookup) - topleft = hcat(map(showblack, parent(lu1)[itop]), topleft) - bottomleft = hcat(map(showblack, parent(lu1)[ibottom]), bottomleft) + if !(lu1 isa NoLookup) + topleft = hcat(map(show1, parent(lu1)[itop]), topleft) + bottomleft = hcat(map(show1, parent(lu1)[ibottom]), bottomleft) end leftblock = vcat(parent(topleft), parent(bottomleft)) @@ -118,11 +220,11 @@ function _print_matrix(io::IO, A::AbstractArray, lookups::Tuple) A_dims = if lu2 isa NoLookup map(showdefault, bottomblock) else - toplabels = map(showblack, parent(lu2)[ileft]), map(showblack, parent(lu2)[iright]) + toplabels = map(show2, parent(lu2)[ileft]), map(show2, parent(lu2)[iright]) toprow = if lu1 isa NoLookup vcat(toplabels...) else - vcat(showhide(0), toplabels...) + vcat(showarrows(), toplabels...) end |> permutedims vcat(toprow, map(showdefault, bottomblock)) end @@ -132,27 +234,37 @@ end struct ShowWith <: AbstractString val::Any - hide::Bool - color::Symbol + mode::Symbol + color::Union{Int,Symbol} end -ShowWith(val; hide=false, color=:light_black) = ShowWith(val; hide, color) +ShowWith(val; mode=:nothing, color=:light_black) = ShowWith(val, mode, color) function Base.show(io::IO, mime::MIME"text/plain", x::ShowWith; kw...) - s = sprint(show, mime, x.val; context=io, kw...) - s1 = x.hide ? " "^length(s) : s - printstyled(io, s1; color=x.color) + if x.mode == :print_arrows + printstyled(io, dimsymbols(1); color=dimcolors(1)) + print(io, " ") + printstyled(io, dimsymbols(2); color=dimcolors(2)) + elseif x.mode == :hide + print(io, " ") + else + s = sprint(show, mime, x.val; context=io, kw...) + printstyled(io, s; color=x.color) + end end -showdefault(x) = ShowWith(x, false, :default) -showblack(x) = ShowWith(x, false, :light_black) -showhide(x) = ShowWith(x, true, :nothing) +showdefault(x) = ShowWith(x, :nothing, :default) +showblack(x) = ShowWith(x, :nothing, 242) +show1(x) = ShowWith(x, :nothing, dimcolors(1)) +show2(x) = ShowWith(x, :nothing, dimcolors(2)) +showhide(x) = ShowWith(x, :hide, :nothing) +showarrows() = ShowWith(1.0, :print_arrows, :nothing) Base.alignment(io::IO, x::ShowWith) = Base.alignment(io, x.val) Base.length(x::ShowWith) = length(string(x.val)) Base.ncodeunits(x::ShowWith) = ncodeunits(string(x.val)) function Base.print(io::IO, x::ShowWith) - printstyled(io, string(x.val); color = x.color, hidden = x.hide) + printstyled(io, string(x.val); color = x.color, hidden = x.mode == :hide) end function Base.show(io::IO, x::ShowWith) - printstyled(io, string(x.val); color = x.color, hidden = x.hide) + printstyled(io, string(x.val); color = x.color, hidden = x.mode == :hide) end Base.iterate(x::ShowWith) = iterate(string(x.val)) diff --git a/src/stack/show.jl b/src/stack/show.jl index bdc17066c..7bc7036b5 100644 --- a/src/stack/show.jl +++ b/src/stack/show.jl @@ -1,50 +1,64 @@ -function Base.show(io::IO, mime::MIME"text/plain", stack::AbstractDimStack) +function Base.summary(io::IO, stack::AbstractDimStack) + print_sizes(io, size(stack)) + print(io, ' ') print(io, nameof(typeof(stack))) - Dimensions.print_dims(io, mime, dims(stack)) - nlayers = length(keys(stack)) - layers_str = nlayers == 1 ? "layer" : "layers" - printstyled(io, "\nand "; color=:light_black) - print(io, "$nlayers $layers_str:\n") - maxlen = if length(keys(stack)) == 0 +end + +function Base.show(io::IO, mime::MIME"text/plain", stack::AbstractDimStack) + lines, maxlen, width = print_top(io, mime, stack; bottom_border=false) + print_layers_block(io, mime, stack; maxlen, width, bottom_border=metadata(stack) isa Union{Nothing,NoMetadata}) + print_metadata_block(io, mime, metadata(stack); width, maxlen=min(width-2, maxlen)) + + # Show anything else subtypes want to append + show_after(io, mime, stack; maxlen) + return nothing +end + +function print_layers_block(io, mime, stack; maxlen, width, bottom_border=true) + roundedtop = maxlen == 0 + layers = DD.layers(stack) + lines = 0 + keylen = if length(keys(layers)) == 0 0 else - reduce(max, map(length ∘ string, collect(keys(stack)))) + reduce(max, map(length ∘ string, collect(keys(layers)))) end - for key in keys(stack) - layer = stack[key] - pkey = rpad(key, maxlen) - printstyled(io, " :$pkey", color=:yellow) - print(io, string(" ", eltype(layer))) - field_dims = DD.dims(layer) - n_dims = length(field_dims) - printstyled(io, " dims: "; color=:light_black) - if n_dims > 0 - for (d, dim) in enumerate(field_dims) - Dimensions.print_dimname(io, dim) - d != length(field_dims) && print(io, ", ") - end - print(io, " (") - for (d, dim) in enumerate(field_dims) - print(io, "$(length(dim))") - d != length(field_dims) && print(io, '×') - end - print(io, ')') - end - print(io, '\n') + for key in keys(layers) + maxlen = min(width - 2, max(maxlen, length(sprint(print_layer, stack, key, keylen)))) + end + if roundedtop + printstyled(io, '┌', '─'^max(0, maxlen - 8), " layers ┐"; color=:light_black) + else + printstyled(io, '├', '─'^max(0, maxlen - 8), " layers ┤"; color=:light_black) end + println(io) + for key in keys(layers) + print_layer(io, stack, key, keylen) + end + return lines +end - md = metadata(stack) - if !(md isa NoMetadata) - n_metadata = length(md) - if n_metadata > 0 - printstyled(io, "\nwith metadata "; color=:light_black) - show(io, mime, md) +function print_layer(io, stack, key, keylen) + layer = stack[key] + pkey = rpad(key, keylen) + printstyled(io, " :$pkey", color=dimcolors(100)) + print(io, string(" ", eltype(layer))) + field_dims = DD.dims(layer) + n_dims = length(field_dims) + colors = map(dimcolors, dimnum(stack, field_dims)) + printstyled(io, " dims: "; color=:light_black) + if n_dims > 0 + for (i, (dim, color)) in enumerate(zip(field_dims, colors)) + Dimensions.print_dimname(IOContext(io, :dimcolor => color), dim) + i != length(field_dims) && print(io, ", ") end + print(io, " (") + print_sizes(io, size(field_dims); colors) + print(io, ')') end - - # Show anything else subtypes want to append - show_after(io, mime, stack) - return nothing + print(io, '\n') end -show_after(io, mime, stack::DimStack) = nothing +function show_after(io, mime, stack::DimStack; maxlen) + printstyled(io, '└', '─'^maxlen, '┘'; color=:light_black) +end diff --git a/test/merged.jl b/test/merged.jl index cdf18780d..ada4d9b1d 100644 --- a/test/merged.jl +++ b/test/merged.jl @@ -4,7 +4,7 @@ using Statistics: mean dim = Coord([(1.0,1.0,1.0), (1.0,2.0,2.0), (3.0,4.0,4.0), (1.0,3.0,4.0)], (X(), Y(), Z())) da = DimArray(0.1:0.1:0.4, dim) -da2 = DimArray((0.1:0.1:0.4) * (1:1:3)', (dim, Ti(1u"s":1u"s":3u"s"))) +da2 = DimArray((0.1:0.1:0.4) * (1:1:3)', (dim, Ti(1u"s":1u"s":3u"s")); metadata=Dict()) @testset "regular indexing" begin @test da[Coord()] === da[Coord(:)] === da @@ -56,8 +56,6 @@ end sprint(show, dim) sp = sprint(show, MIME"text/plain"(), dim) @test occursin("Coord", sp) - @test occursin("X, Y, Z", sp) - da sp = sprint(show, MIME"text/plain"(), da) @test occursin("Coord", sp) @test occursin("X", sp) @@ -87,4 +85,4 @@ end @test size(unmerged) == size(a) @test all(a .== unmerged) @test all(a .== perm_unmerged) -end \ No newline at end of file +end diff --git a/test/runtests.jl b/test/runtests.jl index 5bcc22bbb..9d3c88e73 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -19,7 +19,7 @@ end @time @safetestset "lookup" begin include("lookup.jl") end @time @safetestset "selector" begin include("selector.jl") end -@time @safetestset "coord" begin include("merged.jl") end +@time @safetestset "merged" begin include("merged.jl") end @time @safetestset "dimension" begin include("dimension.jl") end @time @safetestset "DimUnitRange" begin include("dimunitrange.jl") end @time @safetestset "primitives" begin include("primitives.jl") end diff --git a/test/show.jl b/test/show.jl index 27062aa03..68ebfa4ad 100644 --- a/test/show.jl +++ b/test/show.jl @@ -47,11 +47,13 @@ end @test occursin("X", sv) nds = (X(NoLookup(Base.OneTo(10))), Y(NoLookup(Base.OneTo(5)))) sv = sprint(show, MIME("text/plain"), nds) - @test sv == "X, Y" + @test sv == "↓ X, → Y" end @testset "arrays" begin - for (d, str) in ((Ti(), "Ti"), (Lat(), "Lat"), (Lon(), "Lon"), (:n, ":n"), (Z(), "Z")) s1 = sprint(show, MIME("text/plain"), A) + d, str = Lat(), "Lat" + for (d, str) in ((Ti(), "Ti"), (Lat(), "Lat"), (Lon(), "Lon"), (:n, "n"), (Z(), "Z")) + s1 = sprint(show, MIME("text/plain"), A) s2 = sprint(show, MIME("text/plain"), dims(A, ds)) s3 = sprint(show, MIME("text/plain"), dims(A, ds)) @test occursin("DimArray", s1) From 06343de465e981798687f345ff251ea0789bb1d6 Mon Sep 17 00:00:00 2001 From: Rafael Schouten Date: Tue, 6 Feb 2024 18:56:56 +0100 Subject: [PATCH 021/108] tweak show (#613) --- src/Dimensions/show.jl | 3 +- src/LookupArrays/show.jl | 2 +- src/array/show.jl | 99 +++++++++++++++++++++------------------- src/stack/show.jl | 25 +++++----- 4 files changed, 67 insertions(+), 62 deletions(-) diff --git a/src/Dimensions/show.jl b/src/Dimensions/show.jl index 936961813..73cfa64ee 100644 --- a/src/Dimensions/show.jl +++ b/src/Dimensions/show.jl @@ -9,7 +9,8 @@ function dimcolors(i) # colors = [61, 153, 73, 29, 143, 186, 174, 132, 133] # colors = [67, 210, 71, 185, 117, 132, 249] colors = [209, 32, 81, 204, 249, 166, 37] - colors[min(i, length(colors))] + c = rem(i - 1, length(colors)) + 1 + colors[c] end function Base.show(io::IO, mime::MIME"text/plain", dims::DimTuple) diff --git a/src/LookupArrays/show.jl b/src/LookupArrays/show.jl index 5a7f9fc63..0acccddb1 100644 --- a/src/LookupArrays/show.jl +++ b/src/LookupArrays/show.jl @@ -5,7 +5,7 @@ Base.show(io::IO, mime::MIME"text/plain", lookup::NoLookup) = print(io, "NoLooku function Base.show(io::IO, mime::MIME"text/plain", lookup::Transformed) show_compact(io, mime, lookup) - show(io, mime, f(lookup)) + show(io, mime, lookup.f) print(io, " ") ctx = IOContext(io, :compact=>true) show(ctx, mime, dim(lookup)) diff --git a/src/array/show.jl b/src/array/show.jl index ae398cf2f..520d766f0 100644 --- a/src/array/show.jl +++ b/src/array/show.jl @@ -1,19 +1,23 @@ using DimensionalData.Dimensions: dimcolors, dimsymbols, print_dims function Base.summary(io::IO, A::AbstractDimArray{T,N}) where {T,N} - if N > 1 - print_sizes(io, size(A)) + print_ndims(io, size(A)) + print(io, string(nameof(typeof(A)), "{$T,$N}")) + print_name(io, name(A)) +end + +function print_ndims(io, size::Tuple) + if length(size) > 1 + print_sizes(io, size) print(io, ' ') else - print(io, Base.dims2string(size(A)), " ") + print(io, Base.dims2string(size), " ") end - print(io, string(nameof(typeof(A)), "{$T,$N}")) - print_name(io, name(A)) end function Base.show(io::IO, mime::MIME"text/plain", A::AbstractDimArray{T,N}) where {T,N} lines, maxlen, width = print_top(io, mime, A) - m, _ = print_metadata_block(io, mime, metadata(A); width, maxlen=min(width, maxlen)) + m, maxlen = print_metadata_block(io, mime, metadata(A); width, maxlen=min(width, maxlen)) lines += m # Printing the array data is optional, subtypes can @@ -27,21 +31,13 @@ end function print_top(io, mime, A; bottom_border=metadata(A) isa Union{Nothing,NoMetadata}) lines = 4 _, width = displaysize(io) - upchar = maxlen = min(width, length(sprint(summary, A)) + 2) + maxlen = min(width, length(sprint(summary, A)) + 2) printstyled(io, '╭', '─'^maxlen, '╮'; color=:light_black) println(io) printstyled(io, "│ "; color=:light_black) summary(io, A) printstyled(io, " │"; color=:light_black) - p = sprint(mime, dims(A)) do args... - print_dims_block(args...; upchar=maxlen, bottom_border, width, maxlen) - end - maxlen = max(maxlen, maximum(length, split(p, '\n'))) - p = sprint(mime, metadata(A)) do args... - print_metadata_block(args...; width, maxlen) - end - maxlen = max(maxlen, maximum(length, split(p, '\n'))) - n, maxlen = print_dims_block(io, mime, dims(A); upchar, width, bottom_border, maxlen) + n, maxlen = print_dims_block(io, mime, dims(A); width, bottom_border, maxlen) lines += n return lines, maxlen, width end @@ -49,47 +45,58 @@ end function print_sizes(io, size; colors=map(dimcolors, ntuple(identity, length(size))) ) - foreach(enumerate(size[1:end-1])) do (n, s) - printstyled(io, s; color=colors[n]) - print(io, '×') + if !isempty(size) + foreach(enumerate(size[1:end-1])) do (n, s) + printstyled(io, s; color=colors[n]) + print(io, '×') + end + printstyled(io, last(size); color=colors[length(size)]) end - printstyled(io, last(size); color=colors[length(size)]) end -function print_dims_block(io, mime, dims; bottom_border=true, upchar, width, maxlen) +function print_dims_block(io, mime, dims; bottom_border=true, width, maxlen) lines = 0 - if !isempty(dims) - # if all(l -> l isa NoLookup, lookup(dims)) - # printstyled(io, '├', '─'^(upchar), '┴', '─'^max(0, (maxlen - 7 - upchar)), " dims ┐"; color=:light_black) - # print(io, ' ') - # lines += print_dims(io, mime, dims) - # else - dim_string = sprint(print_dims, mime, dims) - maxlen = min(width - 2, max(maxlen, maximum(length, split(dim_string, '\n')))) - println(io) - printstyled(io, '├', '─'^(upchar), '┴', '─'^max(0, (maxlen - 7 - upchar)), " dims ┐"; color=:light_black) - println(io) - lines += print_dims(io, mime, dims) - println(io) - lines += 2 - # end + if isempty(dims) + println(io) + printed = false + newmaxlen = maxlen + else + println(io) + dim_lines = split(sprint(print_dims, mime, dims), '\n') + newmaxlen = min(width - 2, max(maxlen, maximum(length, dim_lines))) + block_line = if newmaxlen > maxlen + string('─'^(maxlen), '┴', '─'^max(0, (newmaxlen - 6 - maxlen)), " dims ") + else + string('─'^max(0, newmaxlen - 6), " dims ") + end + newmaxlen = min(width - 2, max(maxlen, length(block_line))) + corner = (newmaxlen > maxlen) ? '┐' : '┤' + printstyled(io, '├', block_line, corner; color=:light_black) + println(io) + lines += print_dims(io, mime, dims) + println(io) + lines += 2 + printed = true end - return lines, maxlen + return lines, newmaxlen, printed end -function print_metadata_block(io, mime, metadata; maxlen=0, width) +function print_metadata_block(io, mime, metadata; maxlen=0, width, firstblock=false) lines = 0 - if !(metadata isa NoMetadata) - metadata_print = split(sprint(show, mime, metadata), "\n") - maxlen = min(width-2, max(maxlen, maximum(length, metadata_print))) - printstyled(io, '├', '─'^max(0, maxlen - 10), " metadata ┤"; color=:light_black) + if metadata isa NoMetadata + newmaxlen = maxlen + else + metadata_lines = split(sprint(show, mime, metadata), "\n") + newmaxlen = min(width-2, max(maxlen, maximum(length, metadata_lines))) + corner = (newmaxlen > maxlen) ? '┐' : '┤' + printstyled(io, '├', '─'^max(0, newmaxlen - 10), " metadata $corner"; color=:light_black) println(io) print(io, " ") show(io, mime, metadata) println(io) - lines += length(metadata_print) + 3 + lines += length(metadata_lines) + 3 end - return lines, maxlen + return lines, newmaxlen end @@ -115,7 +122,7 @@ printstyled(io, '└', '─'^maxlen, '┘'; color=:light_black) """ function show_after(io::IO, mime, A::AbstractDimArray; maxlen, kw...) printstyled(io, '└', '─'^maxlen, '┘'; color=:light_black) - println(io) + ndims(A) > 0 && println(io) print_array(io, mime, A) end @@ -180,7 +187,7 @@ function _print_matrix(io::IO, A::AbstractArray{<:Any,1}, lookups::Tuple) ibottom = 1:0 end lu = lookups[1] - labels = vcat(map(showblack, parent(lu)[itop]), map(showblack, parent(lu))[ibottom]) + labels = vcat(map(show1, parent(lu)[itop]), map(showblack, parent(lu))[ibottom]) vals = map(showdefault, vcat(A[itop], A[ibottom])) A_dims = hcat(labels, vals) Base.print_matrix(io, A_dims) diff --git a/src/stack/show.jl b/src/stack/show.jl index 7bc7036b5..ffee1afae 100644 --- a/src/stack/show.jl +++ b/src/stack/show.jl @@ -1,13 +1,13 @@ function Base.summary(io::IO, stack::AbstractDimStack) - print_sizes(io, size(stack)) - print(io, ' ') + print_ndims(io, size(stack)) print(io, nameof(typeof(stack))) end function Base.show(io::IO, mime::MIME"text/plain", stack::AbstractDimStack) - lines, maxlen, width = print_top(io, mime, stack; bottom_border=false) - print_layers_block(io, mime, stack; maxlen, width, bottom_border=metadata(stack) isa Union{Nothing,NoMetadata}) - print_metadata_block(io, mime, metadata(stack); width, maxlen=min(width-2, maxlen)) + lines, maxlen, width= print_top(io, mime, stack; bottom_border=false) + bottom_border = metadata(stack) isa Union{Nothing,NoMetadata} + maxlen = print_layers_block(io, mime, stack; maxlen, width, bottom_border) + _, maxlen = print_metadata_block(io, mime, metadata(stack); width, maxlen=min(width-2, maxlen)) # Show anything else subtypes want to append show_after(io, mime, stack; maxlen) @@ -15,27 +15,24 @@ function Base.show(io::IO, mime::MIME"text/plain", stack::AbstractDimStack) end function print_layers_block(io, mime, stack; maxlen, width, bottom_border=true) - roundedtop = maxlen == 0 layers = DD.layers(stack) - lines = 0 keylen = if length(keys(layers)) == 0 0 else reduce(max, map(length ∘ string, collect(keys(layers)))) end + newmaxlen = maxlen for key in keys(layers) - maxlen = min(width - 2, max(maxlen, length(sprint(print_layer, stack, key, keylen)))) - end - if roundedtop - printstyled(io, '┌', '─'^max(0, maxlen - 8), " layers ┐"; color=:light_black) - else - printstyled(io, '├', '─'^max(0, maxlen - 8), " layers ┤"; color=:light_black) + newmaxlen = min(width - 2, max(maxlen, length(sprint(print_layer, stack, key, keylen)))) end + # Round the corner if this block is larger and sticks out further + corner = newmaxlen > maxlen ? '┐' : '┤' + printstyled(io, '├', '─'^max(0, newmaxlen - 8), " layers $corner"; color=:light_black) println(io) for key in keys(layers) print_layer(io, stack, key, keylen) end - return lines + return newmaxlen end function print_layer(io, stack, key, keylen) From 5e68bd91babb69ad58293979a9aa1addfa0b6f3d Mon Sep 17 00:00:00 2001 From: Rafael Schouten Date: Tue, 6 Feb 2024 19:25:08 +0100 Subject: [PATCH 022/108] fix copy! return value and copy_similar (#614) * fix copy! return value and copy_similar --- src/array/array.jl | 33 +++++++++++++++++---------------- src/array/methods.jl | 3 +++ test/array.jl | 29 ++++++++++++++++++----------- 3 files changed, 38 insertions(+), 27 deletions(-) diff --git a/src/array/array.jl b/src/array/array.jl index feb5e7fab..ebb382661 100644 --- a/src/array/array.jl +++ b/src/array/array.jl @@ -242,33 +242,33 @@ for (d, s) in ((:AbstractDimArray, :AbstractDimArray), (:AbstractDimArray, :AbstractArray), (:AbstractArray, :AbstractDimArray)) @eval begin - Base.copy!(dst::$d{T,N}, src::$s{T,N}) where {T,N} = copy!(_maybeunwrap(dst), _maybeunwrap(src)) - Base.copy!(dst::$d{T,1}, src::$s{T,1}) where T = copy!(_maybeunwrap(dst), _maybeunwrap(src)) - Base.copyto!(dst::$d, src::$s) = copyto!(_maybeunwrap(dst), _maybeunwrap(src)) + Base.copy!(dst::$d{T,N}, src::$s{T,N}) where {T,N} = (copy!(_maybeunwrap(dst), _maybeunwrap(src)); dst) + Base.copy!(dst::$d{T,1}, src::$s{T,1}) where T = (copy!(_maybeunwrap(dst), _maybeunwrap(src)); dst) + Base.copyto!(dst::$d, src::$s) = (copyto!(_maybeunwrap(dst), _maybeunwrap(src)); dst) Base.copyto!(dst::$d, dstart::Integer, src::$s) = - copyto!(_maybeunwrap(dst), dstart, _maybeunwrap(src)) + (copyto!(_maybeunwrap(dst), dstart, _maybeunwrap(src)); dst) Base.copyto!(dst::$d, dstart::Integer, src::$s, sstart::Integer) = - copyto!(_maybeunwrap(dst), dstart, _maybeunwrap(src), sstart) + (copyto!(_maybeunwrap(dst), dstart, _maybeunwrap(src), sstart); dst) Base.copyto!(dst::$d, dstart::Integer, src::$s, sstart::Integer, n::Integer) = - copyto!(_maybeunwrap(dst), dstart, _maybeunwrap(src), sstart, n) + (copyto!(_maybeunwrap(dst), dstart, _maybeunwrap(src), sstart, n); dst) Base.copyto!(dst::$d{T1,N}, Rdst::CartesianIndices{N}, src::$s{T2,N}, Rsrc::CartesianIndices{N}) where {T1,T2,N} = - copyto!(_maybeunwrap(dst), Rdst, _maybeunwrap(src), Rsrc) + (copyto!(_maybeunwrap(dst), Rdst, _maybeunwrap(src), Rsrc); dst) end end # Ambiguity @static if VERSION >= v"1.9.0" Base.copyto!(dst::AbstractDimArray{T,2}, src::SparseArrays.CHOLMOD.Dense{T}) where T<:Union{Float64,ComplexF64} = - copyto!(parent(dst), src) + (copyto!(parent(dst), src); dst) Base.copyto!(dst::AbstractDimArray{T}, src::SparseArrays.CHOLMOD.Dense{T}) where T<:Union{Float64,ComplexF64} = - copyto!(parent(dst), src) + (copyto!(parent(dst), src); dst) Base.copyto!(dst::DimensionalData.AbstractDimArray, src::SparseArrays.CHOLMOD.Dense) = - copyto!(parent(dst), src) + (copyto!(parent(dst), src); dst) Base.copyto!(dst::SparseArrays.AbstractCompressedVector, src::AbstractDimArray{T, 1} where T) = - copyto!(dst, parent(src)) + (copyto!(dst, parent(src)); dst) Base.copyto!(dst::AbstractDimArray{T,2} where T, src::SparseArrays.AbstractSparseMatrixCSC) = - copyto!(parent(dst), src) + (copyto!(parent(dst), src); dst) Base.copyto!(dst::AbstractDimArray{T,2} where T, src::LinearAlgebra.AbstractQ) = - copyto!(parent(dst), src) + (copyto!(parent(dst), src); dst) function Base.copyto!( dst::AbstractDimArray{<:Any,2}, dst_i::CartesianIndices{2, R} where R<:Tuple{OrdinalRange{Int64, Int64}, OrdinalRange{Int64, Int64}}, @@ -276,14 +276,15 @@ end src_i::CartesianIndices{2, R} where R<:Tuple{OrdinalRange{Int64, Int64}, OrdinalRange{Int64, Int64}} ) copyto!(parent(dst), dst_i, src, src_i) + return dst end Base.copy!(dst::SparseArrays.AbstractCompressedVector{T}, src::AbstractDimArray{T, 1}) where T = - copy!(dst, parent(src)) + (copy!(dst, parent(src)); dst) end Base.copy!(dst::SparseArrays.SparseVector, src::AbstractDimArray{T,1}) where T = - copy!(dst, parent(src)) + (copy!(dst, parent(src)); dst) Base.copyto!(dst::PermutedDimsArray, src::AbstractDimArray) = - copyto!(dst, parent(src)) + (copyto!(dst, parent(src)); dst) ArrayInterface.parent_type(::Type{<:AbstractDimArray{T,N,D,A}}) where {T,N,D,A} = A diff --git a/src/array/methods.jl b/src/array/methods.jl index cef587395..efbdf32a6 100644 --- a/src/array/methods.jl +++ b/src/array/methods.jl @@ -593,3 +593,6 @@ Base.reverse(dim::Dimension) = rebuild(dim, reverse(lookup(dim))) Base.dataids(A::AbstractDimArray) = Base.dataids(parent(A)) +# We need to override copy_similar because our `similar` doesn't work with size changes +# Fixed in Base in https://github.com/JuliaLang/julia/pull/53210 +LinearAlgebra.copy_similar(A::AbstractDimArray, ::Type{T}) where {T} = copyto!(similar(A, T), A) diff --git a/test/array.jl b/test/array.jl index 2148be7f6..7e3e2454f 100644 --- a/test/array.jl +++ b/test/array.jl @@ -1,5 +1,6 @@ using DimensionalData, Test, Unitful, SparseArrays, Dates, Random using DimensionalData: layerdims, checkdims +using LinearAlgebra using DimensionalData.LookupArrays, DimensionalData.Dimensions @@ -370,11 +371,11 @@ if VERSION > v"1.1-" db = DimArray(deepcopy(A), dimz) dc = DimArray(deepcopy(A), dimz) - copy!(A, da2) + @test copy!(A, da2) isa Matrix @test A == parent(da2) - copy!(db, da2) + @test copy!(db, da2) isa DimMatrix @test parent(db) == parent(da2) - copy!(dc, a2) + @test copy!(dc, a2) isa DimMatrix @test parent(db) == a2 # Sparse vector has its own method for ambiguity copy!(sp, da2[1, :]) @@ -383,31 +384,37 @@ if VERSION > v"1.1-" @testset "vector copy! (ambiguity fix)" begin v = zeros(3) dv = DimArray(zeros(3), X) - copy!(v, DimArray([1.0, 2.0, 3.0], X)) + @test copy!(v, DimArray([1.0, 2.0, 3.0], X)) isa Vector @test v == [1.0, 2.0, 3.0] - copy!(dv, DimArray([9.9, 9.9, 9.9], X)) + @test copy!(dv, DimArray([9.9, 9.9, 9.9], X)) isa DimVector @test dv == [9.9, 9.9, 9.9] - copy!(dv, [5.0, 5.0, 5.0]) + @test copy!(dv, [5.0, 5.0, 5.0]) isa DimVector @test dv == [5.0, 5.0, 5.0] end - end end @testset "copyto!" begin A = zero(a2) da = DimArray(ones(size(A)), dims(da2)) - copyto!(A, da) + @test copyto!(A, da) isa Matrix @test all(A .== 1) - copyto!(da, 1, zeros(5, 5), 1, 12) + @test copyto!(da, 1, zeros(5, 5), 1, 12) isa DimMatrix @test all(da .== 0) x = reshape(10:10:40, 1, 4) - copyto!(da, CartesianIndices(view(da, 1:1, 1:4)), x, CartesianIndices(x)) + @test copyto!(da, CartesianIndices(view(da, 1:1, 1:4)), x, CartesianIndices(x)) isa DimMatrix @test da[1, 1:4] == 10:10:40 - copyto!(A, CartesianIndices(view(da, 1:1, 1:4)), DimArray(x, (X, Y)), CartesianIndices(x)) + @test copyto!(A, CartesianIndices(view(da, 1:1, 1:4)), DimArray(x, (X, Y)), CartesianIndices(x)) isa Matrix @test A[1, 1:4] == 10:10:40 end +@testset "copy_similar" begin + A = rand(Float32, X(10), Y(5)) + cp = LinearAlgebra.copy_similar(A, Float64) + @test cp isa DimMatrix{Float64,<:Tuple{<:X,<:Y}} + @test A == cp +end + @testset "constructor" begin da = DimArray(; data=rand(5, 4), dims=(X, Y)) @test_throws DimensionMismatch DimArray(1:5, X(1:6)) From 05ff3dc47085352c70278cab8fde34637dabdb15 Mon Sep 17 00:00:00 2001 From: Rafael Schouten Date: Sat, 10 Feb 2024 01:01:52 +0100 Subject: [PATCH 023/108] More show (#617) * more tweaks to show --- src/array/show.jl | 136 +++++++++++++++++++++++++++++----------------- src/stack/show.jl | 40 ++++++++------ 2 files changed, 109 insertions(+), 67 deletions(-) diff --git a/src/array/show.jl b/src/array/show.jl index 520d766f0..24f053012 100644 --- a/src/array/show.jl +++ b/src/array/show.jl @@ -1,25 +1,15 @@ using DimensionalData.Dimensions: dimcolors, dimsymbols, print_dims +# Base show function Base.summary(io::IO, A::AbstractDimArray{T,N}) where {T,N} print_ndims(io, size(A)) print(io, string(nameof(typeof(A)), "{$T,$N}")) print_name(io, name(A)) end -function print_ndims(io, size::Tuple) - if length(size) > 1 - print_sizes(io, size) - print(io, ' ') - else - print(io, Base.dims2string(size), " ") - end -end - +# Fancy show for text/plain function Base.show(io::IO, mime::MIME"text/plain", A::AbstractDimArray{T,N}) where {T,N} - lines, maxlen, width = print_top(io, mime, A) - m, maxlen = print_metadata_block(io, mime, metadata(A); width, maxlen=min(width, maxlen)) - lines += m - + lines, maxlen = show_main(io, mime, A::AbstractDimArray) # Printing the array data is optional, subtypes can # show other things here instead. ds = displaysize(io) @@ -27,17 +17,77 @@ function Base.show(io::IO, mime::MIME"text/plain", A::AbstractDimArray{T,N}) whe show_after(ioctx, mime, A; maxlen) return nothing end +# Defer simple 2-arg show to the parent array +Base.show(io::IO, A::AbstractDimArray) = show(io, parent(A)) -function print_top(io, mime, A; bottom_border=metadata(A) isa Union{Nothing,NoMetadata}) +""" + show_main(io::IO, mime, A::AbstractDimArray; maxlen, kw...) + show_main(io::IO, mime, A::AbstractDimStack; maxlen, kw...) + +Interface methods for adding the main part of `show` + +At the least, you likely want to call: + +'''julia +print_top(io, mime, A) +''' + +But read the DimensionalData.jl `show.jl` code for details. +""" +function show_main(io, mime, A::AbstractDimArray) + lines_t, maxlen, width = print_top(io, mime, A) + lines_m, maxlen = print_metadata_block(io, mime, metadata(A); width, maxlen=min(width, maxlen)) + return lines_t + lines_m, maxlen +end + +""" + show_after(io::IO, mime, A::AbstractDimArray; maxlen, kw...) + show_after(io::IO, mime, A::AbstractDimStack; maxlen, kw...) + +Interface methods for adding addional `show` text +for AbstractDimArray/AbstractDimStack subtypes. + +*Always include `kw` to avoid future breaking changes* + +Additional keywords may be added at any time. + +Note - a anssi box is left unclosed. This method needs to close it, +or add more. `maxlen` is the maximum length of the inner text. + +Most likely you always want to at least close the show blocks with: + +'''julia +print_block_close(io, maxlen) +''' + +But read the DimensionalData.jl `show.jl` code for details. +""" +function show_after(io::IO, mime, A::AbstractDimArray; maxlen, kw...) + print_block_close(io, maxlen) + ndims(A) > 0 && println(io) + print_array(io, mime, A) +end + + +function print_ndims(io, size::Tuple) + if length(size) > 1 + print_sizes(io, size) + print(io, ' ') + else + print(io, Base.dims2string(size), " ") + end +end + +function print_top(io, mime, A) lines = 4 _, width = displaysize(io) - maxlen = min(width, length(sprint(summary, A)) + 2) + maxlen = min(width - 2, length(sprint(summary, A)) + 2) printstyled(io, '╭', '─'^maxlen, '╮'; color=:light_black) println(io) printstyled(io, "│ "; color=:light_black) summary(io, A) printstyled(io, " │"; color=:light_black) - n, maxlen = print_dims_block(io, mime, dims(A); width, bottom_border, maxlen) + n, maxlen = print_dims_block(io, mime, dims(A); width, maxlen) lines += n return lines, maxlen, width end @@ -54,7 +104,7 @@ function print_sizes(io, size; end end -function print_dims_block(io, mime, dims; bottom_border=true, width, maxlen) +function print_dims_block(io, mime, dims; width, maxlen) lines = 0 if isempty(dims) println(io) @@ -64,15 +114,7 @@ function print_dims_block(io, mime, dims; bottom_border=true, width, maxlen) println(io) dim_lines = split(sprint(print_dims, mime, dims), '\n') newmaxlen = min(width - 2, max(maxlen, maximum(length, dim_lines))) - block_line = if newmaxlen > maxlen - string('─'^(maxlen), '┴', '─'^max(0, (newmaxlen - 6 - maxlen)), " dims ") - else - string('─'^max(0, newmaxlen - 6), " dims ") - end - newmaxlen = min(width - 2, max(maxlen, length(block_line))) - corner = (newmaxlen > maxlen) ? '┐' : '┤' - printstyled(io, '├', block_line, corner; color=:light_black) - println(io) + print_block_top(io, "dims", maxlen, newmaxlen) lines += print_dims(io, mime, dims) println(io) lines += 2 @@ -88,8 +130,7 @@ function print_metadata_block(io, mime, metadata; maxlen=0, width, firstblock=fa else metadata_lines = split(sprint(show, mime, metadata), "\n") newmaxlen = min(width-2, max(maxlen, maximum(length, metadata_lines))) - corner = (newmaxlen > maxlen) ? '┐' : '┤' - printstyled(io, '├', '─'^max(0, newmaxlen - 10), " metadata $corner"; color=:light_black) + print_block_separator(io, "metadata", maxlen, newmaxlen) println(io) print(io, " ") show(io, mime, metadata) @@ -99,31 +140,26 @@ function print_metadata_block(io, mime, metadata; maxlen=0, width, firstblock=fa return lines, newmaxlen end +# Block lines -""" - show_after(io::IO, mime, A::AbstractDimArray; maxlen, kw...) - show_after(io::IO, mime, A::AbstractDimStack; maxlen, kw...) - -Interface methods for adding addional `show` text -for AbstractDimArray/AbstractDimStack subtypes. - -*Always include `kw` to avoid future breaking changes* - -Additional keywords may be added at any time. - -Note - a anssi box is left unclosed. This method needs to close it, -or add more. `maxlen` is the maximum length of the inner text. +function print_block_top(io, label, prevmaxlen, newmaxlen) + corner = (newmaxlen > prevmaxlen) ? '┐' : '┤' + block_line = if newmaxlen > prevmaxlen + string('─'^(prevmaxlen), '┴', '─'^max(0, (newmaxlen - length(label) - 3 - prevmaxlen)), ' ', label, ' ') + else + string('─'^max(0, newmaxlen - length(label) - 2), ' ', label, ' ') + end + printstyled(io, '├', block_line, corner; color=:light_black) + println(io) +end -Most likely you always want to close the box with: +function print_block_separator(io, label, prevmaxlen, newmaxlen) + corner = (newmaxlen > prevmaxlen) ? '┐' : '┤' + printstyled(io, '├', '─'^max(0, newmaxlen - length(label) - 2), ' ', label, ' ', corner; color=:light_black) +end -'''julia -printstyled(io, '└', '─'^maxlen, '┘'; color=:light_black) -''' -""" -function show_after(io::IO, mime, A::AbstractDimArray; maxlen, kw...) +function print_block_close(io, maxlen) printstyled(io, '└', '─'^maxlen, '┘'; color=:light_black) - ndims(A) > 0 && println(io) - print_array(io, mime, A) end # Showing the array is optional for AbstractDimArray @@ -170,7 +206,7 @@ end # print a name of something, in yellow function print_name(io::IO, name) if !(name == Symbol("") || name isa NoName) - printstyled(io, string(" ", name); color=dimcolors(100)) + printstyled(io, string(" ", name); color=dimcolors(7)) end end diff --git a/src/stack/show.jl b/src/stack/show.jl index ffee1afae..5b49823d3 100644 --- a/src/stack/show.jl +++ b/src/stack/show.jl @@ -1,20 +1,30 @@ +# Base show function Base.summary(io::IO, stack::AbstractDimStack) print_ndims(io, size(stack)) print(io, nameof(typeof(stack))) end function Base.show(io::IO, mime::MIME"text/plain", stack::AbstractDimStack) - lines, maxlen, width= print_top(io, mime, stack; bottom_border=false) - bottom_border = metadata(stack) isa Union{Nothing,NoMetadata} - maxlen = print_layers_block(io, mime, stack; maxlen, width, bottom_border) - _, maxlen = print_metadata_block(io, mime, metadata(stack); width, maxlen=min(width-2, maxlen)) - + # Show main blocks - summar, dims, layers, metadata + _, maxlen = show_main(io, mime, stack) # Show anything else subtypes want to append show_after(io, mime, stack; maxlen) return nothing end -function print_layers_block(io, mime, stack; maxlen, width, bottom_border=true) +# Show customisation interface +function show_main(io, mime, stack::AbstractDimStack) + lines, maxlen, width = print_top(io, mime, stack) + maxlen = print_layers_block(io, mime, stack; maxlen, width) + _, maxlen = print_metadata_block(io, mime, metadata(stack); width, maxlen=min(width-2, maxlen)) +end + +function show_after(io, mime, stack::AbstractDimStack; maxlen) + print_block_close(io, maxlen) +end + +# Show blocks +function print_layers_block(io, mime, stack; maxlen, width) layers = DD.layers(stack) keylen = if length(keys(layers)) == 0 0 @@ -25,9 +35,7 @@ function print_layers_block(io, mime, stack; maxlen, width, bottom_border=true) for key in keys(layers) newmaxlen = min(width - 2, max(maxlen, length(sprint(print_layer, stack, key, keylen)))) end - # Round the corner if this block is larger and sticks out further - corner = newmaxlen > maxlen ? '┐' : '┤' - printstyled(io, '├', '─'^max(0, newmaxlen - 8), " layers $corner"; color=:light_black) + print_block_separator(io, "layers", maxlen, newmaxlen) println(io) for key in keys(layers) print_layer(io, stack, key, keylen) @@ -38,8 +46,9 @@ end function print_layer(io, stack, key, keylen) layer = stack[key] pkey = rpad(key, keylen) - printstyled(io, " :$pkey", color=dimcolors(100)) - print(io, string(" ", eltype(layer))) + printstyled(io, " :$pkey", color=dimcolors(7)) + printstyled(io, " eltype: "; color=:light_black) + print(io, string(eltype(layer))) field_dims = DD.dims(layer) n_dims = length(field_dims) colors = map(dimcolors, dimnum(stack, field_dims)) @@ -49,13 +58,10 @@ function print_layer(io, stack, key, keylen) Dimensions.print_dimname(IOContext(io, :dimcolor => color), dim) i != length(field_dims) && print(io, ", ") end - print(io, " (") + # print(io, " (") + printstyled(io, " size: "; color=:light_black) print_sizes(io, size(field_dims); colors) - print(io, ')') + # print(io, ')') end print(io, '\n') end - -function show_after(io, mime, stack::DimStack; maxlen) - printstyled(io, '└', '─'^maxlen, '┘'; color=:light_black) -end From f4d51b47d3cc5bc51cc75511eddb31615f23f8da Mon Sep 17 00:00:00 2001 From: rafaqz Date: Sun, 11 Feb 2024 10:53:02 +0100 Subject: [PATCH 024/108] show tweak --- src/array/show.jl | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/array/show.jl b/src/array/show.jl index 24f053012..6211c1053 100644 --- a/src/array/show.jl +++ b/src/array/show.jl @@ -223,7 +223,7 @@ function _print_matrix(io::IO, A::AbstractArray{<:Any,1}, lookups::Tuple) ibottom = 1:0 end lu = lookups[1] - labels = vcat(map(show1, parent(lu)[itop]), map(showblack, parent(lu))[ibottom]) + labels = vcat(map(show1, parent(lu)[itop]), map(show1, parent(lu))[ibottom]) vals = map(showdefault, vcat(A[itop], A[ibottom])) A_dims = hcat(labels, vals) Base.print_matrix(io, A_dims) @@ -294,7 +294,6 @@ function Base.show(io::IO, mime::MIME"text/plain", x::ShowWith; kw...) end end showdefault(x) = ShowWith(x, :nothing, :default) -showblack(x) = ShowWith(x, :nothing, 242) show1(x) = ShowWith(x, :nothing, dimcolors(1)) show2(x) = ShowWith(x, :nothing, dimcolors(2)) showhide(x) = ShowWith(x, :hide, :nothing) From e34e5ab24f57905f0885385cd46b99b60824ba3c Mon Sep 17 00:00:00 2001 From: Rafael Schouten Date: Sun, 11 Feb 2024 12:18:58 +0100 Subject: [PATCH 025/108] allow selecting dimensions with predicates (#618) --- src/Dimensions/primitives.jl | 12 +++++++++--- test/primitives.jl | 6 ++++++ 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/Dimensions/primitives.jl b/src/Dimensions/primitives.jl index d96b38b6f..7d6253bd5 100644 --- a/src/Dimensions/primitives.jl +++ b/src/Dimensions/primitives.jl @@ -206,10 +206,10 @@ julia> dimnum(A, Y) all(hasdim(x, q1, query...)) || _extradimserror() _call_primitive(_dimnum, MaybeFirst(), x, q1, query...) end +@inline dimnum(x, query::Function) = + _call_primitive(_dimnum, MaybeFirst(), x, query) -@inline function _dimnum(f::Function, ds::Tuple, query::Tuple{Vararg{Int}}) - query -end +@inline _dimnum(f::Function, ds::Tuple, query::Tuple{Vararg{Int}}) = query @inline function _dimnum(f::Function, ds::Tuple, query::Tuple) numbered = map(ds, ntuple(identity, length(ds))) do d, i rebuild(d, i) @@ -659,6 +659,12 @@ struct AlwaysTuple end @inline _call_primitive1(f, t, op::Function, x, query) = _call_primitive1(f, t, op, dims(x), query) @inline _call_primitive1(f, t, op::Function, x::Nothing) = _dimsnotdefinederror() @inline _call_primitive1(f, t, op::Function, x::Nothing, query) = _dimsnotdefinederror() +@inline function _call_primitive1(f, t, op::Function, ds::Tuple, query::Function) + selection = foldl(ds; init=()) do acc, d + query(d) ? (acc..., d) : acc + end + _call_primitive1(f, t, op, ds, selection) +end @inline function _call_primitive1(f, t, op::Function, d::Tuple, query) ds = dims(query) isnothing(ds) && _dims_are_not_dims() diff --git a/test/primitives.jl b/test/primitives.jl index ac07ae649..d865edf23 100644 --- a/test/primitives.jl +++ b/test/primitives.jl @@ -139,6 +139,8 @@ end @test (@ballocated $f1($dimz)) == 0 @test dims(da, X()) isa X + @test dims(da, isforward) isa Tuple{<:X,<:Y} + @test dims(da, !isforward) isa Tuple{} @test dims(da, Z()) isa Nothing @test (@inferred dims(da, XDim, YDim)) isa Tuple{<:X,<:Y} @test (@ballocated dims($da, XDim, YDim)) == 0 @@ -171,6 +173,7 @@ end @testset "commondims" begin @test commondims(da, X) == (dims(da, X),) + @test commondims(da, x -> x isa X) == (dims(da, X),) # Dims are always in the base order @test (@inferred commondims(da, (Y(), X()))) == dims(da, (X, Y)) @test (@ballocated commondims($da, (Y(), X()))) == 0 @@ -205,6 +208,7 @@ end @testset "dimnum" begin dims(da) @test dimnum(da, Y()) == dimnum(da, 2) == 2 + @test dimnum(da, Base.Fix2(isa,Y)) == (2,) @test (@ballocated dimnum($da, Y())) == 0 @test dimnum(da, X) == 1 @test (@ballocated dimnum($da, X)) == 0 @@ -228,6 +232,7 @@ end @testset "hasdim" begin @test hasdim(da, X()) == true + @test hasdim(da, isforward) == (true, true) @test (@ballocated hasdim($da, X())) == 0 @test hasdim(da, Ti) == false @test (@ballocated hasdim($da, Ti)) == 0 @@ -264,6 +269,7 @@ end @testset "otherdims" begin A = DimArray(ones(5, 10, 15), (X, Y, Z)); @test otherdims(A, X()) == dims(A, (Y, Z)) + @test otherdims(A, x -> x isa X) == dims(A, (Y, Z)) @test (@ballocated otherdims($A, X())) == 0 @test otherdims(A, Y) == dims(A, (X, Z)) @test otherdims(A, Z) == dims(A, (X, Y)) From 1ee6424afcb9eac6ad0e42923d5a5781961b1f00 Mon Sep 17 00:00:00 2001 From: Rafael Schouten Date: Tue, 13 Feb 2024 10:10:57 +0100 Subject: [PATCH 026/108] add an Interfaces.jl extension, and some tests (#605) * add an Interfases.jl extension, and some tests * update InterfacesCore syntax * dont use an extension for Interfaces.jl * fix stack interface --- Project.toml | 2 + src/DimensionalData.jl | 2 + src/interface_tests.jl | 146 +++++++++++++++++++++++++++++++++++++++++ src/stack/stack.jl | 1 + test/interface.jl | 6 +- 5 files changed, 156 insertions(+), 1 deletion(-) create mode 100644 src/interface_tests.jl diff --git a/Project.toml b/Project.toml index f9486f601..b8b569b4f 100644 --- a/Project.toml +++ b/Project.toml @@ -9,6 +9,7 @@ ArrayInterface = "4fba245c-0d91-5ea0-9b3e-6abc04ee57a9" ConstructionBase = "187b0558-2788-49d3-abe0-74a17ed4e7c9" Dates = "ade2ca70-3891-5945-98fb-dc099432e06a" Extents = "411431e0-e8b7-467b-b5e0-f676ba4f2910" +Interfaces = "85a1e053-f937-4924-92a5-1367d23b7b87" IntervalSets = "8197267c-284f-5f27-9208-e0e47529a953" InvertedIndices = "41ab1584-1d38-5bbf-9106-f11c6c58b48f" IteratorInterfaceExtensions = "82899510-4779-5014-852e-03e436cf321d" @@ -44,6 +45,7 @@ Documenter = "1" Extents = "0.1" ImageFiltering = "0.7" ImageTransformations = "0.10" +Interfaces = "0.3" IntervalSets = "0.5, 0.6, 0.7" InvertedIndices = "1" IteratorInterfaceExtensions = "1" diff --git a/src/DimensionalData.jl b/src/DimensionalData.jl index cdea06ada..d069dd682 100644 --- a/src/DimensionalData.jl +++ b/src/DimensionalData.jl @@ -24,6 +24,7 @@ import Adapt, ArrayInterface, ConstructionBase, Extents, + Interfaces, InvertedIndices, IteratorInterfaceExtensions, RecipesBase, @@ -96,5 +97,6 @@ include("plotrecipes.jl") include("utils.jl") include("set.jl") include("precompile.jl") +include("interface_tests.jl") end diff --git a/src/interface_tests.jl b/src/interface_tests.jl new file mode 100644 index 000000000..fe1cc582a --- /dev/null +++ b/src/interface_tests.jl @@ -0,0 +1,146 @@ + +# Interfaces.jl interface + +function rebuild_all(A::AbstractDimArray) + # argument version + A1 = rebuild(A, parent(A), dims(A), refdims(A), name(A), metadata(A)) + # keyword version, will work magically using ConstructionBase.jl if you use the same fieldnames. + # If not, define it and remap these names to your fields. + A2 = rebuild(A; data=parent(A), dims=dims(A), refdims=refdims(A), name=name(A), metadata=metadata(A)) + # all should be identical. If any fields are not used, they will always be `nothing` or `()` for `refdims` + return parent(A) === parent(A1) === parent(A2) && + dims(A) === dims(A1) === dims(A2) && + refdims(A) === refdims(A1) === refdims(A2) && + metadata(A) === metadata(A1) === metadata(A2) && + name(A) === name(A1) === name(A2) +end + +function rebuild_all(A::AbstractDimStack) + # argument version + A1 = rebuild(A, parent(A), dims(A), refdims(A), layerdims(A), metadata(A), layermetadata(A)) + # keyword version, will work magically using ConstructionBase.jl if you use the same fieldnames. + # If not, define it and remap these names to your fields. + A2 = rebuild(A; data=parent(A), dims=dims(A), refdims=refdims(A), metadata=metadata(A)) + # all should be identical. If any fields are not used, they will always be `nothing` or `()` for `refdims` + return parent(A) === parent(A1) === parent(A2) && + dims(A) === dims(A1) === dims(A2) && + refdims(A) === refdims(A1) === refdims(A2) && + metadata(A) === metadata(A1) === metadata(A2) +end + +array_tests = (; + mandatory = ( + dims = ( + "defines a `dims` method" => A -> dims(A) isa Tuple{Vararg{Dimension}}, + "dims are updated on getindex" => A -> dims(view(A, rebuild(first(dims(A)), 1))) == Base.tail(dims(A)), + ), + refdims_base = "`refdims` returns a tuple of Dimension or empty" => + A -> refdims(A) isa Tuple{Vararg{Dimension}}, + ndims = "number of dims matches dimensions of array" => + A -> length(dims(A)) == ndims(A), + size = "length of dims matches dimensions of array" => + A -> map(length, dims(A)) == size(A), + rebuild_parent = "rebuild parent from args" => + A -> parent(rebuild(A, reverse(A; dims=1))) == reverse(A; dims=1), + rebuild_dims = "rebuild paaarnet and dims from args" => + A -> dims(rebuild(A, parent(A), map(reverse, dims(A)))) == map(reverse, dims(A)), + rebuild_parent_kw = "rebuild parent from args" => + A -> parent(rebuild(A; data=reverse(A; dims=1))) == reverse(A; dims=1), + rebuild_dims_kw = "rebuild dims from args" => + A -> dims(rebuild(A; dims=map(reverse, dims(A)))) == map(reverse, dims(A)), + rebuild="all rebuild arguments and keywords are accepted" => rebuild_all, + ), + optional = (; + refdims = ( + "refdims are updated in args rebuild" => + A -> refdims(rebuild(A, parent(A), dims(A), refdims(A))) == refdims(A), + "refdims are updated in kw rebuild" => + A -> refdims(rebuild(A; refdims=refdims(A))) == refdims(A), + "dropped dimensions are added to refdims" => + A -> refdims(view(A, rebuild(first(dims(A)), 1))) isa Tuple{<:Dimension}, + ), + name = ( + "rebuild updates name in arg rebuild" => + A -> DD.name(rebuild(A, parent(A), DD.dims(A), DD.refdims(A), DD.name(A))) === DD.name(A), + "rebuild updates name in kw rebuild" => + A -> DD.name(rebuild(A; name=DD.name(A))) === DD.name(A), + ), + metadata = ( + "rebuild updates metadata in arg rebuild" => + A -> metadata(rebuild(A, parent(A), DD.dims(A), refdims(A), name(A), metadata(A))) === metadata(A), + "rebuild updates metadata in kw rebuild" => + A -> metadata(rebuild(A; metadata=metadata(A))) === metadata(A), + ) + ) +) + +stack_tests = (; + mandatory = ( + dims = ( + "defines a `dims` method" => A -> dims(A) isa Tuple{Vararg{Dimension}}, + "dims are updated on getindex" => A -> dims(view(A, rebuild(first(dims(A)), 1))) == Base.tail(dims(A)), + ), + refdims_base = "`refdims` returns a tuple of Dimension or empty" => + A -> refdims(A) isa Tuple{Vararg{Dimension}}, + ndims = "number of dims matches dimensions of array" => + A -> length(dims(A)) == ndims(A), + size = "length of dims matches dimensions of array" => + A -> map(length, dims(A)) == size(A), + rebuild_parent = "rebuild parent from args" => + A -> parent(rebuild(A, map(a -> reverse(a; dims=1), parent(A)))) == map(a -> reverse(a; dims=1), parent(A)), + rebuild_dims = "rebuild paaarnet and dims from args" => + A -> dims(rebuild(A, parent(A), map(reverse, dims(A)))) == map(reverse, dims(A)), + rebuild_layerdims = "rebuild paaarnet and dims from args" => + A -> layerdims(rebuild(A, parent(A), dims(A), refdims(A), layerdims(A))) == layerdims(A), + rebuild_dims_kw = "rebuild dims from args" => + A -> dims(rebuild(A; dims=map(reverse, dims(A)))) == map(reverse, dims(A)), + rebuild_parent_kw = "rebuild parent from args" => + A -> parent(rebuild(A; data=map(a -> reverse(a; dims=1), parent(A)))) == map(a -> reverse(a; dims=1), parent(A)), + rebuild_layerdims_kw = "rebuild parent from args" => + A -> layerdims(rebuild(A; layerdims=map(reverse, layerdims(A)))) == map(reverse, layerdims(A)), + rebuild="all rebuild arguments and keywords are accepted" => rebuild_all, + ), + optional = (; + refdims = ( + "refdims are updated in args rebuild" => + A -> refdims(rebuild(A, parent(A), dims(A), refdims(A))) == refdims(A), + "refdims are updated in kw rebuild" => + A -> refdims(rebuild(A; refdims=refdims(A))) == refdims(A), + "dropped dimensions are added to refdims" => + A -> refdims(view(A, rebuild(first(dims(A)), 1))) isa Tuple{<:Dimension}, + ), + metadata = ( + "rebuild updates metadata in arg rebuild" => + A -> metadata(rebuild(A, parent(A), DD.dims(A), refdims(A), layerdims(A), metadata(A))) === metadata(A), + "rebuild updates metadata in kw rebuild" => + A -> metadata(rebuild(A; metadata=metadata(A))) === metadata(A), + ) + ) +) + + +const array_docs = """ +This is an early stage of inteface definition, many things are not yet tested. + +Pass constructed AbstractDimArrays as test data. + +They must not be zero dimensional, and should test at least 1, 2, and 3 dimensions. +""" + +const stack_docs = """ +This is an early stage of inteface definition, many things are not yet tested. + +Pass constructed AbstractDimArrays as test data. + +They must not be zero dimensional, and should test at least 1, 2, and 3 dimensions. +""" + +Interfaces.@interface DimArrayInterface AbstractDimArray array_tests array_docs +Interfaces.@interface DimStackInterface AbstractDimStack stack_tests stack_docs + + +# Interfaces.jl implementations + +Interfaces.@implements DimArrayInterface{(:refdims,:name,:metadata)} DimArray [rand(X(10), Y(10)), zeros(Z(10))] +Interfaces.@implements DimStackInterface{(:refdims,:metadata)} DimStack [DimStack(zeros(Z(10))), DimStack(rand(X(10), Y(10))), DimStack(rand(X(10), Y(10)), rand(X(10)))] + diff --git a/src/stack/stack.jl b/src/stack/stack.jl index 8a671b0dc..6d5a9bf97 100644 --- a/src/stack/stack.jl +++ b/src/stack/stack.jl @@ -96,6 +96,7 @@ Base.:(==)(s1::AbstractDimStack, s2::AbstractDimStack) = data(s1) == data(s2) && dims(s1) == dims(s2) && layerdims(s1) == layerdims(s2) Base.getproperty(s::AbstractDimStack, x::Symbol) = s[x] Base.length(s::AbstractDimStack) = length(keys(s)) +Base.ndims(s::AbstractDimStack) = length(dims(s)) Base.size(s::AbstractDimStack) = map(length, dims(s)) Base.size(s::AbstractDimStack, dims::DimOrDimType) = size(s, dimnum(s, dims)) Base.size(s::AbstractDimStack, dims::Integer) = size(s)[dims] diff --git a/test/interface.jl b/test/interface.jl index 4a758b65f..900409551 100644 --- a/test/interface.jl +++ b/test/interface.jl @@ -1,7 +1,11 @@ -using DimensionalData, Test +using DimensionalData, Interfaces, Test @test name(nothing) == "" @test name(Nothing) == "" @test dims(1) == nothing @test dims(nothing) == nothing @test refdims(1) == () + +# @test Interfaces.test(DimensionalData) +@test Interfaces.test(DimensionalData.DimArrayInterface) +@test Interfaces.test(DimensionalData.DimStackInterface) From d18daa419ad7474d641dc4f191ec7bcd43608204 Mon Sep 17 00:00:00 2001 From: Lazaro Alonso Date: Thu, 15 Feb 2024 21:43:56 +0100 Subject: [PATCH 027/108] new vitepress docs (#616) --- .github/workflows/Documenter.yml | 89 +- .gitignore | 8 +- docs/Project.toml | 7 +- docs/_overrides/partials/source.html | 30 - docs/crash/course/course.jl | 283 ----- docs/genfiles.jl | 28 - docs/logo.jl | 50 + docs/make.jl | 35 +- docs/mkdocs.yml | 120 -- docs/src/.vitepress/config.mts | 86 ++ docs/src/.vitepress/theme/index.ts | 17 + docs/src/.vitepress/theme/style.css | 171 +++ docs/src/basics.md | 76 ++ docs/src/cuda.md | 1 + docs/src/dimensions.md | 160 +++ docs/src/ext_dd.md | 1 + docs/src/index.md | 88 +- docs/src/integrations.md | 7 + docs/src/javascripts/mathjax.js | 16 - docs/src/lookup_customization.md | 4 + docs/src/plots.md | 1 + docs/src/reference.md | 5 +- docs/src/selectors.md | 29 + docs/src/stacks.md | 1 + docs/src/stylesheets/custom.css | 157 --- docs/src/tables.md | 1 + package-lock.json | 1694 ++++++++++++++++++++++++++ package.json | 10 + 28 files changed, 2428 insertions(+), 747 deletions(-) delete mode 100644 docs/_overrides/partials/source.html delete mode 100644 docs/crash/course/course.jl delete mode 100644 docs/genfiles.jl create mode 100644 docs/logo.jl delete mode 100644 docs/mkdocs.yml create mode 100644 docs/src/.vitepress/config.mts create mode 100644 docs/src/.vitepress/theme/index.ts create mode 100644 docs/src/.vitepress/theme/style.css create mode 100644 docs/src/basics.md create mode 100644 docs/src/cuda.md create mode 100644 docs/src/dimensions.md create mode 100644 docs/src/ext_dd.md create mode 100644 docs/src/integrations.md delete mode 100644 docs/src/javascripts/mathjax.js create mode 100644 docs/src/lookup_customization.md create mode 100644 docs/src/plots.md create mode 100644 docs/src/selectors.md create mode 100644 docs/src/stacks.md delete mode 100644 docs/src/stylesheets/custom.css create mode 100644 docs/src/tables.md create mode 100644 package-lock.json create mode 100644 package.json diff --git a/.github/workflows/Documenter.yml b/.github/workflows/Documenter.yml index 160132647..40df19501 100644 --- a/.github/workflows/Documenter.yml +++ b/.github/workflows/Documenter.yml @@ -1,35 +1,80 @@ -name: Documenter +# Sample workflow for building and deploying a VitePress site to GitHub Pages +# +name: Deploy VitePress site to Pages + on: + # Runs on pushes targeting the `main` branch. Change this to `master` if you're + # using the `master` branch as the default branch. push: branches: [main] - tags: [v*] pull_request: branches: [main] + types: [opened, synchronize, reopened] + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages +permissions: + actions: write # useful to delete gh cache + contents: read + pages: write + id-token: write + +# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. +# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. concurrency: - # Skip intermediate builds: always. - # Cancel intermediate builds: only if it is a pull request build. - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: ${{ startsWith(github.ref, 'refs/pull/') }} + group: pages + cancel-in-progress: false + jobs: + # Build job build: - permissions: - contents: write runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: julia-actions/setup-julia@v1 - - uses: julia-actions/cache@v1 + - name: Checkout + uses: actions/checkout@v4 with: - cache-registries: "true" + fetch-depth: 0 # Not needed if lastUpdated is not enabled + # - uses: pnpm/action-setup@v2 # Uncomment this if you're using pnpm + # - uses: oven-sh/setup-bun@v1 # Uncomment this if you're using Bun + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm # or pnpm / yarn + cache-dependency-path: 'package-lock.json' # this should be a package-lock.json file + - name: Setup Pages + uses: actions/configure-pages@v4 + - name: Install dependencies + run: npm ci # or pnpm install / yarn install / bun install + - name: Julia version + uses: julia-actions/setup-julia@v1 + - name: Julia cache + uses: julia-actions/cache@v1 - name: Install documentation dependencies - run: julia --project=docs -e 'using Pkg; pkg"dev ."; Pkg.instantiate()' - - name: Build and deploy - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # For authentication with GitHub Actions token - DOCUMENTER_KEY: ${{ secrets.DOCUMENTER_KEY }} # For authentication with SSH deploy key - GKSwstype: "100" # https://discourse.julialang.org/t/generation-of-documentation-fails-qt-qpa-xcb-could-not-connect-to-display/60988 - JULIA_DEBUG: "Documenter" - DATADEPS_ALWAYS_ACCEPT: true + run: julia --project=docs -e 'using Pkg; pkg"add https://github.com/LuxDL/DocumenterVitepress.jl.git"; pkg"dev ."; Pkg.instantiate(); Pkg.precompile(); Pkg.status()' + - name: generating examples md files + run: | + julia --project=docs/ --color=yes docs/make.jl + - name: Build with VitePress run: | - julia --code-coverage=user --project=docs/ --color=yes docs/genfiles.jl - julia --code-coverage=user --project=docs/ --color=yes docs/make.jl + NODE_OPTIONS=--max-old-space-size=32768 npm run docs:build # or pnpm docs:build / yarn docs:build / bun run docs:build + touch docs/docs_site/.vitepress/dist/.nojekyll + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + with: + path: docs/docs_site/.vitepress/dist + + # Deployment job + deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + needs: build + runs-on: ubuntu-latest + name: Deploy + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 \ No newline at end of file diff --git a/.gitignore b/.gitignore index 03d3e0a82..58fe77e9d 100644 --- a/.gitignore +++ b/.gitignore @@ -13,7 +13,13 @@ build docs/docs docs/site +docs/docs_site docs/build docs/var deps/build.jl -Manifest.toml \ No newline at end of file +Manifest.toml +docs/.vitepress/dist +docs/.vitepress/cache +docs/src/.vitepress/dist +docs/src/.vitepress/cache +node_modules \ No newline at end of file diff --git a/docs/Project.toml b/docs/Project.toml index 90a9739e9..be71d0cfd 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -1,9 +1,8 @@ [deps] BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf" -CoordinateTransformations = "150eb455-5306-5404-9cee-2592286d6298" +CairoMakie = "13f3f980-e62b-5c42-98c6-ff1f3baf88f0" +Colors = "5ae59095-9a9b-59fe-a467-6f913c188581" Dates = "ade2ca70-3891-5945-98fb-dc099432e06a" DimensionalData = "0703355e-b756-11e9-17c0-8b28908087d0" Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" -DocumenterMarkdown = "997ab1e6-3595-5248-9280-8efb232c3433" -Literate = "98b081ad-f1c9-55d3-8b20-4c87d4299306" -Unitful = "1986cc42-f94f-5a68-af5c-568840ba703d" +DocumenterVitepress = "4710194d-e776-4893-9690-8d956a29c365" diff --git a/docs/_overrides/partials/source.html b/docs/_overrides/partials/source.html deleted file mode 100644 index 413044452..000000000 --- a/docs/_overrides/partials/source.html +++ /dev/null @@ -1,30 +0,0 @@ -{% import "partials/language.html" as lang with context %} - -
- {% set icon = config.theme.icon.repo or "fontawesome/brands/git-alt" %} - {% include ".icons/" ~ icon ~ ".svg" %} -
-
- {{ config.repo_name }} -
-
-{% if config.theme.twitter_url %} - -
- {% include ".icons/fontawesome/brands/twitter.svg" %} -
-
- {{ config.theme.twitter_name }} -
-
-{% endif %} -{% if config.theme.sponsor_url %} - -
- {% include ".icons/fontawesome/regular/heart.svg" %} -
-
- {{ config.theme.sponsor_name }} -
-
-{% endif %} \ No newline at end of file diff --git a/docs/crash/course/course.jl b/docs/crash/course/course.jl deleted file mode 100644 index f8fc20222..000000000 --- a/docs/crash/course/course.jl +++ /dev/null @@ -1,283 +0,0 @@ -using DimensionalData - -# ## Dimensions and DimArrays - -# The core type of DimensionalData.jl is the [`Dimension`](@ref) and the types -# that inherit from it, such as `Ti`, `X`, `Y`, `Z`, the generic `Dim{:x}`, or -# others that you define manually using the [`@dim`](@ref) macro. - -# `Dimension`s are primarily used in [`DimArray`](@ref), other -# [`AbstractDimArray`](@ref). - -# We can use dimensions without a value index - these simply label the axis. -# A `DimArray` with labelled dimensions is constructed by: - -using DimensionalData -A = rand(X(5), Y(5)) - -# get value - -A[Y(1), X(2)] - -# As shown above, `Dimension`s can be used to construct arrays in `rand`, `ones`, -# `zeros` and `fill` with either a range for a lookup index or a number for the -# dimension length. - -# Or we can use the `Dim{X}` dims by using `Symbol`s, and indexing with keywords: - -A = DimArray(rand(5, 5), (:a, :b)) - -# get value - -A[a=3, b=5] - -# Often, we want to provide a lookup index for the dimension: - -using Dates -t = DateTime(2001):Month(1):DateTime(2001,12) -x = 10:10:100 -A = rand(X(x), Ti(t)) - -# Here both `X` and `Ti` are dimensions from `DimensionalData`. The currently -# exported dimensions are `X, Y, Z, Ti` (`Ti` is shortening of `Time`). - -# The length of each dimension index has to match the size of the corresponding -# array axis. - -# This can also be done with `Symbol`, using `Dim{X}`: - -A2 = DimArray(rand(12, 10), (time=t, distance=x)) - -# Symbols can be more convenient to use than defining custom dims with `@dim`, but -# have some downsides. They don't inherit from a specific `Dimension` type, so -# plots will not know what axis to put them on. They also cannot use the basic -# constructor methods like `rand` or `zeros`, as we cannot dispatch on `Symbol` -# for Base methods without "type-piracy". - - -# ## Indexing the array by name and index - -# Dimensions can be used to index the array by name, without having to worry -# about the order of the dimensions. - -# The simplest case is to select a dimension by index. Let's say every 2nd point -# of the `Ti` dimension and every 3rd point of the `X` dimension. This is done -# with the simple `Ti(range)` syntax like so: - -A[X(1:3:11), Ti(1:2:11)] - -# When specifying only one dimension, all elements of the other -# dimensions are assumed to be included: - -A[X(1:3:10)] - -# !!! info "Indexing" -# Indexing `AbstractDimArray`s works with `getindex`, `setindex!` and -# `view`. The result is still an `AbstracDimArray`, unless using all single -# `Int` or `Selector`s that resolve to `Int`. - - -# `Dimension`s can be used to construct arrays in `rand`, `ones`, `zeros` and -# `fill` with either a range for a lookup index or a number for the dimension -# length. - -using DimensionalData -A1 = ones(X(1:40), Y(50)) - -# We can also use dim wrappers for indexing, so that the dimension order in the underlying array -# does not need to be known: - -A1[Y(1), X(1:10)] - - -# ## Indexing Performance - -# Indexing with `Dimension` has no runtime cost: - -A2 = ones(X(3), Y(3)) - -# time ? -using BenchmarkTools - -println(@btime $A2[X(1), Y(2)]) - -# and - -println(@btime parent($A2)[1, 2]) - -# ## Specifying `dims` keyword arguments with `Dimension` - -# In many Julia functions like `size` or `sum`, you can specify the dimension -# along which to perform the operation as an `Int`. It is also possible to do this -# using [`Dimension`](@ref) types with `AbstractDimArray`: - -A3 = rand(X(3), Y(4), Ti(5)); -sum(A3; dims=Ti) - -# This also works in methods from `Statistics`: - -using Statistics -mean(A3; dims=Ti) - -# ## Methods where dims, dim types, or `Symbol`s can be used to indicate the array dimension: - -# - `size`, `axes`, `firstindex`, `lastindex` -# - `cat`, `reverse`, `dropdims` -# - `reduce`, `mapreduce` -# - `sum`, `prod`, `maximum`, `minimum`, -# - `mean`, `median`, `extrema`, `std`, `var`, `cor`, `cov` -# - `permutedims`, `adjoint`, `transpose`, `Transpose` -# - `mapslices`, `eachslice` - -# ## LookupArrays and Selectors - -# Indexing by value in `DimensionalData` is done with [Selectors](@ref). -# IntervalSets.jl is now used for selecting ranges of values (formerly `Between`). - -# | Selector | Description | -# | :---------------------- | :-------------------------------------------------------------------- | -# | [`At(x)`](@ref) | get the index exactly matching the passed in value(s) | -# | [`Near(x)`](@ref) | get the closest index to the passed in value(s) | -# | [`Contains(x)`](@ref) | get indices where the value x falls within an interval | -# | [`Where(f)`](@ref) | filter the array axis by a function of the dimension index values. | -# | [`Not(x)`] | get all indices _not_ selected by `x`, which can be another selector. | -# | [`a..b`] | get all indices between two values, inclusively. | -# | [`OpenInterval(a, b)`] | get all indices between `a` and `b`, exclusively. | -# | [`Interval{A,B}(a, b)`] | get all indices between `a` and `b`, as `:closed` or `:open`. | - - -# Selectors find indices in the `LookupArray`, for each dimension. -# Here we use an `Interval` to select a range between integers and `DateTime`: - -A[X(12..35), Ti(Date(2001, 5)..Date(2001, 7))] - -# To select intervals in DimArrays (e.g. ```A2```) you need to specify ```dimname=a .. b```: - -A2[distance=12 .. 35, time=Date(2001, 5) .. Date(2001, 7)] - -# Selectors can be used in `getindex`, `setindex!` and -# `view` to select indices matching the passed in value(s) - -# We can use selectors inside dim wrappers, here selecting values from `DateTime` and `Int`: - -using Dates -timespan = DateTime(2001,1):Month(1):DateTime(2001,12) -A4 = rand(Ti(timespan), X(10:10:100)) -A4[X(Near(35)), Ti(At(DateTime(2001,5)))] - -# Without dim wrappers selectors must be in the right order, and specify all axes: - -using Unitful -A5 = rand(Y((1:10:100)u"m"), Ti((1:5:100)u"s")); -A5[10.5u"m" .. 50.5u"m", Near(23u"s")] - - -# We can also use Linear indices as in standard `Array`: - -A5[1:5] - -# But unless the `DimArray` is one dimensional, this will return a regular -# `Array`. It is not possible to keep the `LookupArray` or even `Dimension`s after -# linear indexing is used. - -# ## LookupArrays and traits - -# Using a regular range or `Vector` as a lookup index has a number of downsides. -# We cannot use `searchsorted` for fast searches without knowing the order of the -# array, and this is slow to compute at runtime. It also means `reverse` or -# rotations cannot be used while keeping the `DimArray` wrapper. - -# Step sizes are also a problem. Some ranges like `LinRange` lose their step size -# with a length of `1`. Often, instead of a range, multi-dimensional data formats -# provide a `Vector` of evenly spaced values for a lookup, with a step size -# specified separately. Converting to a range introduces floating point errors -# that means points may not be selected with `At` without setting tolerances. - -# This means using a lookup wrapper with traits is more generally robust and -# versatile than simply using a range or vector. DimensionalData provides types -# for specifying details about the dimension index, in the [`LookupArrays`](@ref) -# sub-module: - -using DimensionalData -using .LookupArrays - -# The main [`LookupArray`](@ref) are : - -# - [`Sampled`](@ref) -# - [`Categorical`](@ref), -# - [`NoLookup`](@ref) - -# Each comes with specific traits that are either fixed or variable, depending -# on the contained index. These enable optimisations with `Selector`s, and modified -# behaviours, such as: - -# 1. Selection of [`Intervals`](@ref) or [`Points`](@ref), which will give slightly -# different results for selectors like `..` - as whole intervals are -# selected, and have different `bounds` values. - -# 2. Tracking of lookup order. A reverse order is labelled `ReverseOrdered` and -# will still work with `searchsorted`, and for plots to always be the right way -# up when either the index or the array is backwards. Reversing a `DimArray` -# will reverse the `LookupArray` for that dimension, swapping `ReverseOrdered` -# to `ForwardOrdered`. - -# 3. `Sampled` [`Intervals`](@ref) can have index located at a [`Locus`](@ref) of: - -# - [`Start`](@ref), -# - [`Center`](@ref) -# - [`End`](@ref) - -# Which specifies the point of the interval represented in the index, to match -# different data standards, e.g. GeoTIFF (`Start`) and NetCDF (`Center`). - -# 4. A [`Span`](@ref) specifies the gap between `Points` or the size of -# `Intervals`. This may be: - -# - [`Regular`](@ref), in the case of a range and equally spaced vector, -# - [`Irregular`](@ref) for unequally spaced vectors -# - [`Explicit`](@ref) for the case where all interval start and end points are -# specified explicitly - as is common in the NetCDF standard. - -# These traits all for subtypes of [`Aligned`](@ref). - -# [`Unaligned`](@ref) also exists to handle dimensions with an index that is -# rotated or otherwise transformed in relation to the underlying array, such as -# [`Transformed`](@ref). - - -# ## LookupArray detection - -# [`Aligned`](@ref) types will be detected automatically if not specified - which -# usually isn't required. - -# - An empty `Dimension` or a `Type` or `Symbol` will be assigned `NoLookup` - -# this behaves as a simple named dimension without a lookup index. -# - A `Dimension` containing and index of `String`, `Char`, `Symbol` or mixed -# types will be given the [`Categorical`](@ref) mode, -# - A range will be assigned [`Sampled`](@ref), defaulting to -# [`Regular`](@ref), [`Points`](@ref) -# - Other `AbstractVector` will be assigned [`Sampled`](@ref) [`Irregular`](@ref) -# [`Points`](@ref). - -# In all cases the [`Order`](@ref) of [`ForwardOrdered`](@ref) or -# [`ReverseOrdered`](@ref) will be be detected, otherwise [`Unordered`](@ref) -# for an unsorted `Array`. - -# See the [`LookupArray`](@ref) API docs for more detail. - -# ## Referenced dimensions - -# The reference dimensions record the previous dimensions that an array was -# selected from. These can be use for plot labelling, and tracking array changes -# so that `cat` can reconstruct the lookup array from previous dimensions that -# have been sliced. - -# ## Warnings - -# Indexing with unordered or reverse-ordered arrays has undefined behaviour. -# It will trash the dimension index, break `searchsorted` and nothing will make -# sense any more. So do it at you own risk. - -# However, indexing with sorted vectors of `Int` can be useful, so it's allowed. -# But it may do strange things to interval sizes for [`Intervals`](@ref) that are -# not [`Explicit`](@ref). diff --git a/docs/genfiles.jl b/docs/genfiles.jl deleted file mode 100644 index 2f2bd58b5..000000000 --- a/docs/genfiles.jl +++ /dev/null @@ -1,28 +0,0 @@ -using Documenter, DocumenterMarkdown -using Literate - -get_example_path(p) = joinpath(@__DIR__, ".", "crash", p) -OUTPUT = joinpath(@__DIR__, "src", "crash", "generated") - -folders = readdir(joinpath(@__DIR__, ".", "crash")) -setdiff!(folders, [".DS_Store"]) - -function getfiles() - srcsfiles = [] - for f in folders - names = readdir(joinpath(@__DIR__, ".", "crash", f)) - setdiff!(names, [".DS_Store"]) - fpaths = "$(f)/" .* names - srcsfiles = vcat(srcsfiles, fpaths...) - end - return srcsfiles -end - -srcsfiles = getfiles() - -for (d, paths) in (("tutorial", srcsfiles),) - for p in paths - Literate.markdown(get_example_path(p), joinpath(OUTPUT, dirname(p)); - documenter=true) - end -end \ No newline at end of file diff --git a/docs/logo.jl b/docs/logo.jl new file mode 100644 index 000000000..d0c098423 --- /dev/null +++ b/docs/logo.jl @@ -0,0 +1,50 @@ +using Colors +using CairoMakie +CairoMakie.activate!() +# using GLMakie +# GLMakie.activate!() + + +rpyz = [Rect3f(Vec3f(0, j-0.8,k), Vec3f(0.1, 0.8,0.8)) + for j in 1:7 for k in 1:7] +rmyz = [Rect3f(Vec3f(j-0.8, 0,k), Vec3f(0.8, 0.1,0.8)) + for j in 1:7 for k in 1:7] + +colors = ["#ff875f", "#0087d7", "#5fd7ff", "#ff5f87", "#b2b2b2", "#d75f00", "#00afaf"] +fig = Figure(; size=(500,500), + backgroundcolor=:transparent, + fonts = (; regular = "Barlow")) +ax = LScene(fig[1,1]; show_axis=false) + +wireframe!.(ax, rpyz; color = colors[3], transparency=true) # shading=NoShading # bug! +mesh!.(ax, rmyz; color=colors[1], transparency=true, shading=NoShading) + +meshscatter!(ax, [Point3f(0.1,0.1,0.8), Point3f(0.1+7,0.1,0.8), + Point3f(0.1,0.1+7,0.8), Point3f(0.1+7,0.1+7,0.8)]; color = colors[4], + markersize=0.25, shading=FastShading) + +lines!(ax, [Point3f(0.1,0.1,0.8), Point3f(0.1+7,0.1,0.8), Point3f(0.1+7,0.1+7,0.8), + Point3f(0.1,0.1+7,0.8), Point3f(0.1,0.1,0.8)]; color = colors[4], + linewidth=2, transparency=true) +meshscatter!(ax, Point3f(4,4,-0.01); color=:transparent) +meshscatter!(ax, [Point3f(0.1,0.1,8), Point3f(0.1+7,0.1,8), Point3f(0.1,0.1+7,8), Point3f(0.1+7,0.1+7,8)]; color = colors[2], markersize=0.2, shading=FastShading) + +lines!(ax, [ Point3f(0.1+7,0.1,8), Point3f(0.1+7,0.1+7,8), + Point3f(0.1,0.1+7,8),# Point3f(0.1,0.1,0.8) + ]; + color = colors[2], + linewidth=2, transparency=true) + +# text!(ax, "D", position = Point3f(6,3,3.75), +# fontsize=150, font=:bold, +# align= (:center, :center), +# color=colors[2], +# ) +# text!(ax, "D", position = Point3f(3,6,3.75), +# fontsize=150, align= (:center, :center), +# font=:bold, +# color=colors[1], +# #rotation=Vec3f(1,0,1) +# ) +save(joinpath(@__DIR__, "./src/public/logoDD.png"), fig) +fig \ No newline at end of file diff --git a/docs/make.jl b/docs/make.jl index 78b8070f7..1a7b0f3e8 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -1,29 +1,10 @@ -using Documenter, DocumenterMarkdown +using DocumenterVitepress ## add https://github.com/LuxDL/DocumenterVitepress.jl.git +using Documenter using DimensionalData -using DimensionalData.LookupArrays, DimensionalData.Dimensions -using CoordinateTransformations, Dates, Unitful -makedocs( - modules = [DimensionalData], - clean=true, - doctest=false, - #format = Documenter.HTML(prettyurls = get(ENV, "CI", nothing) == "true"), - sitename="DimensionalData.jl", - authors="Rafael Schouten et al.", - strict=[ - :doctest, - :linkcheck, - :parse_error, - :example_block, - # Other available options are - # :autodocs_block, :cross_references, :docs_block, :eval_block, :example_block, - # :footnote, :meta_block, :missing_docs, :setup_block - ], checkdocs=:all, format=Markdown(), draft=false, - build=joinpath(@__DIR__, "docs") -) - -deploydocs(; repo="github.com/rafaqz/DimensionalData.jl.git", push_preview=true, - deps=Deps.pip("mkdocs", "pygments", "python-markdown-math", "mkdocs-material", - "pymdown-extensions", "mkdocstrings", "mknotebooks", - "pytkdocs_tweaks", "mkdocs_include_exclude_files", "jinja2"), - make=() -> run(`mkdocs build`), target="site", devbranch="main") \ No newline at end of file +makedocs(; sitename="DimensionalData", authors="Rafael Schouten et al.", + # modules=[DimensionalData], + # checkdocs=:all, + format=DocumenterVitepress.MarkdownVitepress(), + draft=false, + source="src", build=joinpath(@__DIR__, "docs_site")) \ No newline at end of file diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml deleted file mode 100644 index edbc118ca..000000000 --- a/docs/mkdocs.yml +++ /dev/null @@ -1,120 +0,0 @@ -theme: - name: material - #logo: assets/DimensionalData_logo.png - features: - - content.code.copy - # - announce.dismiss - - content.code.annotate - # - content.tabs.link - #- content.tooltips - # - header.autohide - # - navigation.expand - #- navigation.indexes - # - navigation.instant - # - navigation.prune - #- navigation.sections - #- navigation.tabs - # - navigation.tabs.sticky - - navigation.top - - navigation.footer - #- navigation.tracking - - search.highlight - - search.share - - search.suggest - - toc.follow - #- toc.integrate # Table of contents is integrated on the left; does not appear separately on the right. - - header.autohide # header disappears as you scroll - palette: - - # Light mode - - media: "(prefers-color-scheme: light)" - scheme: default - primary: grey - accent: orange - toggle: - icon: material/weather-sunny - name: Switch to dark mode - - # Dark mode - - media: "(prefers-color-scheme: dark)" - scheme: slate - primary: blue grey - accent: orange - toggle: - icon: material/weather-night - name: Switch to light mode - font: - text: Lato - icon: - repo: fontawesome/brands/github # GitHub logo in top right - #logo: "material/gridoff" # Equinox logo in top left - # favicon: "_static/icon_transparent.svg" - custom_dir: "_overrides" # Overriding part of the HTML - - # These additions are my own custom ones, having overridden a partial. - #twitter_name: "" - #twitter_url: "" -site_name: DimensionalData.jl -site_description: DimensionalData.jl -site_author: Lazaro Alonso and Rafael Schouten -site_url: "" - -repo_url: https://github.com/rafaqz/DimensionalData.jl -repo_name: DimensionalData.jl -edit_uri: "" # No edit button, as some of our pages are in /docs and some in /examples via symlink, so it's impossible for them all to be accurate - -strict: true # Don't allow warnings during the build process -extra_javascript: - # The below three make MathJax work, see https://squidfunk.github.io/mkdocs-material/reference/mathjax/ - - _static/mathjax.js - - https://polyfill.io/v3/polyfill.min.js?features=es6 - - https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js - -extra: - social: - - icon: fontawesome/brands/github - #link: - - icon: fontawesome/brands/twitter - #link: - -extra_css: - - stylesheets/custom.css - - assets/Documenter.css - -extra_javascript: - - javascripts/mathjax.js - - https://polyfill.io/v3/polyfill.min.js?features=es6 - - https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js - -markdown_extensions: - - tables - - admonition - - toc: - permalink: "¤" # Adds a clickable permalink to each section heading - toc_depth: 4 - - pymdownx.arithmatex: # Render LaTeX via MathJax - generic: true - - pymdownx.details # Allowing hidden expandable regions denoted by ??? - - pymdownx.highlight - - pymdownx.inlinehilite - - pymdownx.snippets - - pymdownx.superfences # Seems to enable syntax highlighting when used with the Material theme. - - pymdownx.tasklist: - custom_checkbox: true - - def_list - - pymdownx.tabbed: - alternate_style: true - - attr_list - - md_in_html -plugins: - - search # default search plugin; needs manually re-enabling when using any other plugins - - autorefs # Cross-links to headings - - include_exclude_files: - exclude: - - "_overrides" -# - mknotebooks # Jupyter notebooks -# - mkdocs-video -nav: - - "Home": "index.md" - - "Crash course" : "crash/generated/course/course.md" - - "Reference" : "reference.md" \ No newline at end of file diff --git a/docs/src/.vitepress/config.mts b/docs/src/.vitepress/config.mts new file mode 100644 index 000000000..28c9af6e4 --- /dev/null +++ b/docs/src/.vitepress/config.mts @@ -0,0 +1,86 @@ +import type { DefaultTheme } from 'vitepress' +import { defineConfig } from 'vitepress' +import { tabsMarkdownPlugin } from 'vitepress-plugin-tabs' +const version= '0.25.8' + +const VERSIONS: DefaultTheme.NavItemWithLink[] = [ + { text: `v${version} (current)`, link: '/' }, + { text: `Release Notes`, link: 'https://github.com/rafaqz/DimensionalData.jl/releases/' }, + // { text: `Contributing`, link: 'https://github.com/twoslashes/twoslash/blob/main/CONTRIBUTING.md' }, +] + +// https://vitepress.dev/reference/site-config +export default defineConfig({ + base: '/DimensionalData.jl/', + title: "DimensionalData", + description: "Datasets with named dimensions", + lastUpdated: true, + cleanUrls: true, + ignoreDeadLinks: true, + + markdown: { + config(md) { + md.use(tabsMarkdownPlugin) + }, + // https://shiki.style/themes + theme: { + light: "github-light", + dark: "github-dark"} + }, + themeConfig: { + // https://vitepress.dev/reference/default-theme-config + logo: { src: '/logoDD.png', width: 24, height: 24 }, + search: { + provider: 'local', + options: { + detailedView: true + } + }, + nav: [ + { text: 'Home', link: '/' }, + { text: 'Getting Started', link: '/basics' }, + { text: 'Dimensions', link: '/dimensions' }, + { text: 'Selectors', link: '/selectors' }, + { text: 'Integrations', + items: [ + {text: 'Dependants', link: '/integrations' }, + { text: 'Tables and DataFrames', link: '/tables' }, + { text: 'Plots with Makie', link: '/plots' }, + { text: 'CUDA & GPUs', link: '/cuda.md' }, + { text: 'Extending DimensionalData', link: '/ext_dd' }, + ], + }, + { + text: `v${version}`, + items: VERSIONS, + }, + ], + + sidebar: [ + { + text: '', + items: [ + { text: 'Getting Started', link: '/basics' }, + { text: 'Dimensions', link: '/dimensions' }, + { text: 'Selectors', link: '/selectors' }, + { text: 'Stacks', link: '/stacks' }, + { text: 'Tables and DataFrames', link: '/tables' }, + { text: 'Lookup customazation', link: '/lookup_customazation' }, + { text: 'Extending DimensionalData', link: '/ext_dd' }, + { text: 'Plots with Makie', link: '/plots' }, + { text: 'CUDA & GPUs', link: '/cuda' }, + { text: 'API Reference', link: '/reference' }, + ] + } + ], + + socialLinks: [ + { icon: 'github', link: 'https://github.com/rafaqz/DimensionalData.jl' }, + + ], + footer: { + message: 'Made with DocumenterVitepress.jl by Lazaro Alonso
', + copyright: `© Copyright ${new Date().getUTCFullYear()}. Released under the MIT License.` + } + } +}) diff --git a/docs/src/.vitepress/theme/index.ts b/docs/src/.vitepress/theme/index.ts new file mode 100644 index 000000000..a0086bbcd --- /dev/null +++ b/docs/src/.vitepress/theme/index.ts @@ -0,0 +1,17 @@ +// .vitepress/theme/index.ts +import { h } from 'vue' +import Theme from 'vitepress/theme' +import { enhanceAppWithTabs } from 'vitepress-plugin-tabs/client' +import './style.css' + +export default { + extends: Theme, + Layout: () => { + return h(Theme.Layout, null, { + // https://vitepress.dev/guide/extending-default-theme#layout-slots + }) + }, + enhanceApp({ app, router, siteData }) { + enhanceAppWithTabs(app) + } +} \ No newline at end of file diff --git a/docs/src/.vitepress/theme/style.css b/docs/src/.vitepress/theme/style.css new file mode 100644 index 000000000..bf94c02a5 --- /dev/null +++ b/docs/src/.vitepress/theme/style.css @@ -0,0 +1,171 @@ +@import url(https://fonts.googleapis.com/css?family=Space+Mono:regular,italic,700,700italic); +@import url(https://fonts.googleapis.com/css?family=Space+Grotesk:regular,italic,700,700italic); + +/* Customize default theme styling by overriding CSS variables: +https://github.com/vuejs/vitepress/blob/main/src/client/theme-default/styles/vars.css + */ + + /* Layouts */ + +/* + :root { + --vp-layout-max-width: 1440px; +} */ + +.VPHero .clip { + white-space: pre; + max-width: 500px; +} + +/* Fonts */ + +:root { + /* Typography */ + --vp-font-family-base: "Barlow", "Inter var experimental", "Inter var", + -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, + Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; + + /* Code Snippet font */ + --vp-font-family-mono: "Fira Code", Menlo, Monaco, Consolas, "Courier New", + monospace; +} + +/* Colors */ +/* more colors ansi colors = [209, 32, 81, 204, 249, 166, 37] */ +/* more colors colors = ["#ff875f", "#0087d7", "#5fd7ff", "#ff5f87", "#b2b2b2", "#d75f00", "#00afaf"] */ +:root { + --julia-blue: #5fd7ff ; + --julia-purple: #5fd7ff; + --julia-red: #CB3C33; + --julia-green: #0087d7; + + --vp-c-brand: #0087d7; + --vp-c-brand-light: #0087d7; + --vp-c-brand-lighter: #5fd7ff ; + --vp-c-brand-lightest: #5fd7ff ; + --vp-c-brand-dark: #5fd7ff; + --vp-c-brand-darker: #5fd7ff ; + --vp-c-brand-dimm: #212425; +} + + /* Component: Button */ + +:root { + --vp-button-brand-border: var(--vp-c-brand-light); + --vp-button-brand-text: var(--vp-c-white); + --vp-button-brand-bg: var(--vp-c-brand); + --vp-button-brand-hover-border: var(--vp-c-brand-light); + --vp-button-brand-hover-text: var(--vp-c-white); + --vp-button-brand-hover-bg: var(--vp-c-brand-light); + --vp-button-brand-active-border: var(--vp-c-brand-light); + --vp-button-brand-active-text: var(--vp-c-white); + --vp-button-brand-active-bg: var(--vp-button-brand-bg); +} + +/* Component: Home */ + +:root { + --vp-home-hero-name-color: transparent; + --vp-home-hero-name-background: -webkit-linear-gradient( + 120deg, + #ff875f 30%, + #0087d7 + ); + + --vp-home-hero-image-background-image: linear-gradient( + -45deg, + #1f1b1c 5%, + #1c1b1b 5%, + #083f5f + ); + --vp-home-hero-image-filter: blur(40px); +} + +@media (min-width: 640px) { + :root { + --vp-home-hero-image-filter: blur(56px); + } +} + +@media (min-width: 960px) { + :root { + --vp-home-hero-image-filter: blur(72px); + } +} + +/* Component: Custom Block */ + +:root.dark { + --vp-custom-block-tip-border: var(--vp-c-brand); + --vp-custom-block-tip-text: var(--vp-c-brand-lightest); + --vp-custom-block-tip-bg: var(--vp-c-brand-dimm); + + /* // Tweak the color palette for blacks and dark grays */ + --vp-c-black: hsl(220 20% 9%); + --vp-c-black-pure: hsl(220, 24%, 4%); + --vp-c-black-soft: hsl(220 16% 13%); + --vp-c-black-mute: hsl(220 14% 17%); + --vp-c-gray: hsl(220 8% 56%); + --vp-c-gray-dark-1: hsl(220 10% 39%); + --vp-c-gray-dark-2: hsl(220 12% 28%); + --vp-c-gray-dark-3: hsl(220 12% 23%); + --vp-c-gray-dark-4: hsl(220 14% 17%); + --vp-c-gray-dark-5: hsl(220 16% 13%); + + /* // Backgrounds */ + --vp-c-bg: hsl(240, 2%, 11%); + --vp-custom-block-info-bg: hsl(220 14% 17%); + --vp-c-gutter: hsl(220 20% 9%); + + --vp-c-bg-alt: hsl(220 20% 9%); + --vp-c-bg-soft: hsl(220 14% 17%); + --vp-c-bg-mute: hsl(220 12% 23%); + +} + + /* Component: Algolia */ + +.DocSearch { + --docsearch-primary-color: var(--vp-c-brand) !important; +} + +/* Component: MathJax */ + +mjx-container > svg { + display: block; + margin: auto; +} + +mjx-container { + padding: 0.5rem 0; +} + +mjx-container { + display: inline-block; + margin: auto 2px -2px; +} + +mjx-container > svg { + margin: auto; + display: inline-block; +} + +/** + * Colors links + * -------------------------------------------------------------------------- */ + + :root { + --vp-c-brand-1: #ff875f; + --vp-c-brand-2: #ff875f; + --vp-c-brand-3: #ff875f; + --vp-c-sponsor: #ca2971; + --vitest-c-sponsor-hover: #c13071; +} + +.dark { + --vp-c-brand-1: #0087d7; + --vp-c-brand-2: #0087d7; + --vp-c-brand-3: #0087d7; + --vp-c-sponsor: #ee4e95; + --vitest-c-sponsor-hover: #e51370; +} \ No newline at end of file diff --git a/docs/src/basics.md b/docs/src/basics.md new file mode 100644 index 000000000..a74e19bdc --- /dev/null +++ b/docs/src/basics.md @@ -0,0 +1,76 @@ +# Getting Started + +`DimensionalData.jl` provides tools and abstractions for working with datasets that have named dimensions, and optionally a lookup index. + +## Installation +````shell +julia>] +pkg> add DimensionalData +```` + +Check the installed version: + +````shell +julia>] +pkg> st +```` + +Start using the package: + +````@example basics +using DimensionalData +```` + +## Philosophy + +DimensionalData is a pluggable, generalised version of +[AxisArrays.jl](https://github.com/JuliaArrays/AxisArrays.jl) with a cleaner +syntax, and additional functionality found in NamedDims.jl. It has similar goals +to pythons [xarray](http://xarray.pydata.org/en/stable/), and is primarily +written for use with spatial data in [Rasters.jl](https://github.com/rafaqz/Rasters.jl). + +::: info + +- Clean, readable syntax. Minimise required parentheses, minimise of exported +- Zero-cost dimensional indexing `a[Y(4), X(5)]` of a single value. + methods, and instead extend Base methods whenever possible. +- Plotting is easy: data should plot sensibly and correctly with useful labels, by default. +- Least surprise: everything works the same as in Base, but with named dims. If + a method accepts numeric indices or `dims=X` in base, you should be able to + use DimensionalData.jl dims. +- Minimal interface: implementing a dimension-aware type should be easy. +- Maximum extensibility: always use method dispatch. Regular types over special + syntax. Recursion over @generated. Always dispatch on abstract types. +- Type stability: dimensional methods should be type stable _more often_ than Base methods +- Functional style: structs are always rebuilt, and other than the array data, + fields are not mutated in place. + +::: + +## Data types and the interface + +DimensionalData.jl provides the concrete `DimArray` type. But its +behaviours are intended to be easily applied to other array types. + +::: details more + +The main requirement for extending DimensionalData.jl is to define a `dims` method +that returns a `Tuple` of `Dimension` that matches the dimension order +and axis values of your data. Define `rebuild` and base methods for `similar` +and `parent` if you want the metadata to persist through transformations (see +the `DimArray` and `AbstractDimArray` types). A `refdims` method +returns the lost dimensions of a previous transformation, passed in to the +`rebuild` method. `refdims` can be discarded, the main loss being plot labels +and ability to reconstruct dimensions in `cat`. + +Inheriting from `AbstractDimArray` in this way will give nearly all the functionality +of using `DimArray`. + +::: + +## LookupArrays and Dimensions + +Sub modules `LookupArrays` and `Dimensions` define the behaviour of +dimensions and their lookup index. + +[`LookupArrays`](@ref) and [`Dimensions`](@ref). \ No newline at end of file diff --git a/docs/src/cuda.md b/docs/src/cuda.md new file mode 100644 index 000000000..1708bf6dd --- /dev/null +++ b/docs/src/cuda.md @@ -0,0 +1 @@ +# CUDA & GPUs \ No newline at end of file diff --git a/docs/src/dimensions.md b/docs/src/dimensions.md new file mode 100644 index 000000000..edc09f3f7 --- /dev/null +++ b/docs/src/dimensions.md @@ -0,0 +1,160 @@ +# Dimensions + +````@example dimensions +using DimensionalData +```` + +The core type of DimensionalData.jl is the [`Dimension`](@ref) and the types +that inherit from it, such as `Ti`, `X`, `Y`, `Z`, the generic `Dim{:x}`, or +others that you define manually using the [`@dim`](@ref) macro. + +`Dimension`s are primarily used in [`DimArray`](@ref), other +[`AbstractDimArray`](@ref). + +## DimArray +We can use dimensions without a value index - these simply label the axis. +A `DimArray` with labelled dimensions is constructed by: + +````@ansi dimensions +A = rand(X(5), Y(5)) +```` + +get a value + +````@ansi dimensions +A[Y(1), X(2)] +```` + +As shown above, `Dimension`s can be used to construct arrays in `rand`, `ones`, +`zeros` and `fill` with either a range for a lookup index or a number for the +dimension length. + +Or we can use the `Dim{X}` dims by using `Symbol`s, and indexing with keywords: + +````@ansi dimensions +A = DimArray(rand(5, 5), (:a, :b)) +```` + +get value + +````@ansi dimensions +A[a=3, b=5] +```` + +## What is a dimension? + +````@example dimensions +using DimensionalData +using Dates +t = DateTime(2001):Month(1):DateTime(2001,12) +x = 10:10:100 +nothing # hide +```` + +````@ansi dimensions +A = rand(X(x), Ti(t)); +```` + +Here both `X` and `Ti` are dimensions from `DimensionalData`. The currently +exported dimensions are `X, Y, Z, Ti` (`Ti` is shortening of `Time`). + +The length of each dimension index has to match the size of the corresponding +array axis. + +This can also be done with `Symbol`, using `Dim{X}`: + +````@ansi dimensions +A2 = DimArray(rand(12, 10), (time=t, distance=x)) +```` + +## Dimensional Indexing +Dimensions can be used to index the array by name, without having to worry +about the order of the dimensions. + +The simplest case is to select a dimension by index. Let's say every 2nd point +of the `Ti` dimension and every 3rd point of the `X` dimension. This is done +with the simple `Ti(range)` syntax like so: + +````@ansi dimensions +A[X(1:3:11), Ti(1:2:11)] +```` + +When specifying only one dimension, all elements of the other dimensions are assumed to be included: + +````@ansi dimensions +A[X(1:3:10)] +```` + +::: info Indexing + +Indexing `AbstractDimArray`s works with `getindex`, `setindex!` and +`view`. The result is still an `AbstracDimArray`, unless using all single +`Int` or `Selector`s that resolve to `Int`. + +::: + +### Indexing Performance + +Indexing with `Dimension` has no runtime cost: + +````@ansi dimensions +A2 = ones(X(3), Y(3)) +```` + +time ? + +````@example dimensions +using BenchmarkTools +```` + +````@ansi dimensions +@benchmark $A2[X(1), Y(2)] +```` + +and + +````@ansi dimensions +@btime parent($A2)[1, 2] +```` + +In many Julia functions like `size` or `sum`, you can specify the dimension +along which to perform the operation as an `Int`. It is also possible to do this +using [`Dimension`](@ref) types with `AbstractDimArray`: + +````@ansi dimensions +A3 = rand(X(3), Y(4), Ti(5)); +sum(A3; dims=Ti) +```` + +This also works in methods from `Statistics`: + +````@example dimensions +using Statistics +```` + +````@ansi dimensions +mean(A3; dims=Ti) +```` + +::: info + +Methods where dims, dim types, or `Symbol`s can be used to indicate the array dimension: + +- `size`, `axes`, `firstindex`, `lastindex` +- `cat`, `reverse`, `dropdims` +- `reduce`, `mapreduce` +- `sum`, `prod`, `maximum`, `minimum` +- `mean`, `median`, `extrema`, `std`, `var`, `cor`, `cov` +- `permutedims`, `adjoint`, `transpose`, `Transpose` +- `mapslices`, `eachslice` + +::: + + +## Dimensions +## DimIndices +## Vectors of Dimensions + +## How to name dimensions? +## How to name an array? +## Adding metadata \ No newline at end of file diff --git a/docs/src/ext_dd.md b/docs/src/ext_dd.md new file mode 100644 index 000000000..db7c828a7 --- /dev/null +++ b/docs/src/ext_dd.md @@ -0,0 +1 @@ +# Extending DimensionalData \ No newline at end of file diff --git a/docs/src/index.md b/docs/src/index.md index 557c9efab..b4c5e6d43 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -1,57 +1,33 @@ -## DimensionalData - -DimensionalData.jl provides tools and abstractions for working with datasets -that have named dimensions, and optionally a lookup index. - -DimensionalData is a pluggable, generalised version of -[AxisArrays.jl](https://github.com/JuliaArrays/AxisArrays.jl) with a cleaner -syntax, and additional functionality found in NamedDims.jl. It has similar goals -to pythons [xarray](http://xarray.pydata.org/en/stable/), and is primarily -written for use with spatial data in [Rasters.jl](https://github.com/rafaqz/Rasters.jl). - -## Goals -!!! info "" - - - Clean, readable syntax. Minimise required parentheses, minimise of exported - - Zero-cost dimensional indexing `a[Y(4), X(5)]` of a single value. - methods, and instead extend Base methods whenever possible. - - Plotting is easy: data should plot sensibly and correctly with useful labels, by default. - - Least surprise: everything works the same as in Base, but with named dims. If - a method accepts numeric indices or `dims=X` in base, you should be able to - use DimensionalData.jl dims. - - Minimal interface: implementing a dimension-aware type should be easy. - - Maximum extensibility: always use method dispatch. Regular types over special - syntax. Recursion over @generated. Always dispatch on abstract types. - - Type stability: dimensional methods should be type stable _more often_ than Base methods - - Functional style: structs are always rebuilt, and other than the array data, - fields are not mutated in place. - -## For package developers - -## Data types and the interface - -DimensionalData.jl provides the concrete `DimArray` type. But its -behaviours are intended to be easily applied to other array types. - ```@raw html -??? question "more" - - The main requirement for extending DimensionalData.jl is to define a `dims` method - that returns a `Tuple` of `Dimension` that matches the dimension order - and axis values of your data. Define `rebuild` and base methods for `similar` - and `parent` if you want the metadata to persist through transformations (see - the `DimArray` and `AbstractDimArray` types). A `refdims` method - returns the lost dimensions of a previous transformation, passed in to the - `rebuild` method. `refdims` can be discarded, the main loss being plot labels - and ability to reconstruct dimensions in `cat`. - - Inheriting from `AbstractDimArray` in this way will give nearly all the functionality - of using `DimArray`. -``` - -## LookupArrays and Dimensions - -Sub modules `LookupArrays` and `Dimensions` define the behviour of -dimensions and their lookup index. - -[`LookupArrays`](@ref) and [`Dimensions`](@ref) +--- +# https://vitepress.dev/reference/default-theme-home-page +layout: home + +hero: + name: "DimensionalData.jl" + text: "Datasets with named dimensions" + tagline: High performance name indexing for Julia + image: + src: 'logoDD.png' + actions: + - theme: brand + text: Getting Started + link: /basics + - theme: alt + text: Examples + link: /api-examples + - theme: alt + text: API reference + link: /reference +features: + - icon: 3d-scale + title: Intelligent Indexing + details: Use names and values to retrieve data. + - icon: 3d-scale + title: Powerful Array Manipulation + details: permute, broadcast whatever... + - icon: 3d-scale + title: Seamlessly integrated with the julia ecosystem + details: Works with base methods, extended in many packages blah +--- +``` \ No newline at end of file diff --git a/docs/src/integrations.md b/docs/src/integrations.md new file mode 100644 index 000000000..f5d122584 --- /dev/null +++ b/docs/src/integrations.md @@ -0,0 +1,7 @@ +# Integrations + +## Rasters.jl +## YAXArrays.jl +## ClimateBase.jl +## ArviZ.jl +## JuMP.jl diff --git a/docs/src/javascripts/mathjax.js b/docs/src/javascripts/mathjax.js deleted file mode 100644 index a80ddbff7..000000000 --- a/docs/src/javascripts/mathjax.js +++ /dev/null @@ -1,16 +0,0 @@ -window.MathJax = { - tex: { - inlineMath: [["\\(", "\\)"]], - displayMath: [["\\[", "\\]"]], - processEscapes: true, - processEnvironments: true - }, - options: { - ignoreHtmlClass: ".*|", - processHtmlClass: "arithmatex" - } -}; - -document$.subscribe(() => { - MathJax.typesetPromise() -}) \ No newline at end of file diff --git a/docs/src/lookup_customization.md b/docs/src/lookup_customization.md new file mode 100644 index 000000000..b8596abce --- /dev/null +++ b/docs/src/lookup_customization.md @@ -0,0 +1,4 @@ +# lookup customization +## Defaults +## custom lookup properties +## modifying existing lookups \ No newline at end of file diff --git a/docs/src/plots.md b/docs/src/plots.md new file mode 100644 index 000000000..67c595007 --- /dev/null +++ b/docs/src/plots.md @@ -0,0 +1 @@ +# Plots with Makie \ No newline at end of file diff --git a/docs/src/reference.md b/docs/src/reference.md index 91ee71f64..147b0cf1d 100644 --- a/docs/src/reference.md +++ b/docs/src/reference.md @@ -1,5 +1,5 @@ -# API +# API Reference ## Arrays @@ -98,7 +98,6 @@ Ti Dim Coord Dimensions.AnonDim -@dim ``` ### Exported methods @@ -257,5 +256,5 @@ DimensionalData.NoName ## Show methods for packages extending DimensionalData.jl ```@docs -show_after +DimensionalData.show_after ``` diff --git a/docs/src/selectors.md b/docs/src/selectors.md new file mode 100644 index 000000000..3222cb82e --- /dev/null +++ b/docs/src/selectors.md @@ -0,0 +1,29 @@ +# Selectors + +Indexing by value in `DimensionalData` is done with [Selectors](@ref). +IntervalSets.jl is now used for selecting ranges of values (formerly `Between`). + +| Selector | Description | +| :---------------------- | :-------------------------------------------------------------------- | +| [`At(x)`](@ref) | get the index exactly matching the passed in value(s) | +| [`Near(x)`](@ref) | get the closest index to the passed in value(s) | +| [`Contains(x)`](@ref) | get indices where the value x falls within an interval | +| [`Where(f)`](@ref) | filter the array axis by a function of the dimension index values. | +| [`Not(x)`] | get all indices _not_ selected by `x`, which can be another selector. | +| [`a..b`] | get all indices between two values, inclusively. | +| [`OpenInterval(a, b)`] | get all indices between `a` and `b`, exclusively. | +| [`Interval{A,B}(a, b)`] | get all indices between `a` and `b`, as `:closed` or `:open`. | + + +Selectors find indices in the `LookupArray`, for each dimension. + +## lookup +## At +## Between, .. +## Where +## Near +## Touches +## Contains +## All +## IntervalSets +## DimSelectors \ No newline at end of file diff --git a/docs/src/stacks.md b/docs/src/stacks.md new file mode 100644 index 000000000..28c2c8f07 --- /dev/null +++ b/docs/src/stacks.md @@ -0,0 +1 @@ +# Stacks \ No newline at end of file diff --git a/docs/src/stylesheets/custom.css b/docs/src/stylesheets/custom.css deleted file mode 100644 index 84d99c0e2..000000000 --- a/docs/src/stylesheets/custom.css +++ /dev/null @@ -1,157 +0,0 @@ -/* Fix /page#foo going to the top of the viewport and being hidden by the navbar */ -html { - scroll-padding-top: 50px; - } - - /* Fit the Twitter handle alongside the GitHub one in the top right. */ - - div.md-header__source { - width: revert; - max-width: revert; - } - - a.md-source { - display: inline-block; - } - - .md-source__repository { - max-width: 100%; - } - - /* Emphasise sections of nav on left hand side */ - - nav.md-nav { - padding-left: 5px; - } - - nav.md-nav--secondary { - border-left: revert !important; - } - - .md-nav__title { - font-size: 0.9rem; - } - - .md-nav__item--section > .md-nav__link { - font-size: 0.9rem; - } - - /* Indent autogenerated documentation */ - - div.doc-contents { - padding-left: 25px; - border-left: 4px solid rgba(230, 230, 230); - } - - /* Increase visibility of splitters "---" */ - - [data-md-color-scheme="default"] .md-typeset hr { - border-bottom-color: rgb(0, 0, 0); - border-bottom-width: 1pt; - } - - [data-md-color-scheme="slate"] .md-typeset hr { - border-bottom-color: rgb(230, 230, 230); - } - - /* More space at the bottom of the page */ - - .md-main__inner { - margin-bottom: 1.5rem; - } - - /* Remove prev/next footer buttons */ - - .md-footer__inner { - display: none; - } - - /* Bugfix: remove the superfluous parts generated when doing: - - ??? Blah - - ::: library.something - */ - - .md-typeset details .mkdocstrings > h4 { - display: none; - } - - .md-typeset details .mkdocstrings > h5 { - display: none; - } - - /* Change default colours for
tags */ - - [data-md-color-scheme="default"] { - --md-typeset-a-color: rgb(0, 150, 255) !important; - } - [data-md-color-scheme="slate"] { - --md-typeset-a-color: rgb(0, 150, 255) !important; - } - - /* Highlight functions, classes etc. type signatures. Really helps to make clear where - one item ends and another begins. */ - - [data-md-color-scheme="default"] { - --doc-heading-color: #DDD; - --doc-heading-border-color: #CCC; - --doc-heading-color-alt: #F0F0F0; - } - [data-md-color-scheme="slate"] { - --doc-heading-color: rgb(25,25,33); - --doc-heading-border-color: rgb(25,25,33); - --doc-heading-color-alt: rgb(33,33,44); - --md-code-bg-color: rgb(38,38,50); - } - - h4.doc-heading { - /* NOT var(--md-code-bg-color) as that's not visually distinct from other code blocks.*/ - background-color: var(--doc-heading-color); - border: solid var(--doc-heading-border-color); - border-width: 1.5pt; - border-radius: 2pt; - padding: 0pt 5pt 2pt 5pt; - } - h5.doc-heading, h6.heading { - background-color: var(--doc-heading-color-alt); - border-radius: 2pt; - padding: 0pt 5pt 2pt 5pt; - } - - /* From Pretty Pandas Dataframes */ -/* Supports mkdocs-material color variables */ -.data-frame { - border: 0; - font-size: smaller; -} -.data-frame tr { - border: none; - background: var(--md-code-bg-color, #ffffff); -} -.data-frame tr:nth-child(even) { - background: var(--md-default-bg-color, #f5f5f5); -} -.data-frame tr:hover { - background-color: var(--md-footer-bg-color--dark, #e1f5fe); -} - -.data-frame thead th { - background: var(--md-default-bg-color, #ffffff); - border-bottom: 1px solid #aaa; - font-weight: bold; -} -.data-frame th { - border: none; - padding-left: 10px; - padding-right: 10px; -} - -.data-frame td{ - /* background: #fff; */ - border: none; - text-align: right; - min-width:5em; - padding-left: 10px; - padding-right: 10px; -} \ No newline at end of file diff --git a/docs/src/tables.md b/docs/src/tables.md new file mode 100644 index 000000000..afb1b4c41 --- /dev/null +++ b/docs/src/tables.md @@ -0,0 +1 @@ +# Tables diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 000000000..727bde254 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1694 @@ +{ + "name": "DimensionalData.jl", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "devDependencies": { + "vitepress-plugin-tabs": "^0.5.0" + } + }, + "node_modules/@algolia/autocomplete-core": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-core/-/autocomplete-core-1.9.3.tgz", + "integrity": "sha512-009HdfugtGCdC4JdXUbVJClA0q0zh24yyePn+KUGk3rP7j8FEe/m5Yo/z65gn6nP/cM39PxpzqKrL7A6fP6PPw==", + "dev": true, + "peer": true, + "dependencies": { + "@algolia/autocomplete-plugin-algolia-insights": "1.9.3", + "@algolia/autocomplete-shared": "1.9.3" + } + }, + "node_modules/@algolia/autocomplete-plugin-algolia-insights": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-plugin-algolia-insights/-/autocomplete-plugin-algolia-insights-1.9.3.tgz", + "integrity": "sha512-a/yTUkcO/Vyy+JffmAnTWbr4/90cLzw+CC3bRbhnULr/EM0fGNvM13oQQ14f2moLMcVDyAx/leczLlAOovhSZg==", + "dev": true, + "peer": true, + "dependencies": { + "@algolia/autocomplete-shared": "1.9.3" + }, + "peerDependencies": { + "search-insights": ">= 1 < 3" + } + }, + "node_modules/@algolia/autocomplete-preset-algolia": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.9.3.tgz", + "integrity": "sha512-d4qlt6YmrLMYy95n5TB52wtNDr6EgAIPH81dvvvW8UmuWRgxEtY0NJiPwl/h95JtG2vmRM804M0DSwMCNZlzRA==", + "dev": true, + "peer": true, + "dependencies": { + "@algolia/autocomplete-shared": "1.9.3" + }, + "peerDependencies": { + "@algolia/client-search": ">= 4.9.1 < 6", + "algoliasearch": ">= 4.9.1 < 6" + } + }, + "node_modules/@algolia/autocomplete-shared": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-shared/-/autocomplete-shared-1.9.3.tgz", + "integrity": "sha512-Wnm9E4Ye6Rl6sTTqjoymD+l8DjSTHsHboVRYrKgEt8Q7UHm9nYbqhN/i0fhUYA3OAEH7WA8x3jfpnmJm3rKvaQ==", + "dev": true, + "peer": true, + "peerDependencies": { + "@algolia/client-search": ">= 4.9.1 < 6", + "algoliasearch": ">= 4.9.1 < 6" + } + }, + "node_modules/@algolia/cache-browser-local-storage": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/@algolia/cache-browser-local-storage/-/cache-browser-local-storage-4.22.1.tgz", + "integrity": "sha512-Sw6IAmOCvvP6QNgY9j+Hv09mvkvEIDKjYW8ow0UDDAxSXy664RBNQk3i/0nt7gvceOJ6jGmOTimaZoY1THmU7g==", + "dev": true, + "peer": true, + "dependencies": { + "@algolia/cache-common": "4.22.1" + } + }, + "node_modules/@algolia/cache-common": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/@algolia/cache-common/-/cache-common-4.22.1.tgz", + "integrity": "sha512-TJMBKqZNKYB9TptRRjSUtevJeQVXRmg6rk9qgFKWvOy8jhCPdyNZV1nB3SKGufzvTVbomAukFR8guu/8NRKBTA==", + "dev": true, + "peer": true + }, + "node_modules/@algolia/cache-in-memory": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/@algolia/cache-in-memory/-/cache-in-memory-4.22.1.tgz", + "integrity": "sha512-ve+6Ac2LhwpufuWavM/aHjLoNz/Z/sYSgNIXsinGofWOysPilQZPUetqLj8vbvi+DHZZaYSEP9H5SRVXnpsNNw==", + "dev": true, + "peer": true, + "dependencies": { + "@algolia/cache-common": "4.22.1" + } + }, + "node_modules/@algolia/client-account": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/@algolia/client-account/-/client-account-4.22.1.tgz", + "integrity": "sha512-k8m+oegM2zlns/TwZyi4YgCtyToackkOpE+xCaKCYfBfDtdGOaVZCM5YvGPtK+HGaJMIN/DoTL8asbM3NzHonw==", + "dev": true, + "peer": true, + "dependencies": { + "@algolia/client-common": "4.22.1", + "@algolia/client-search": "4.22.1", + "@algolia/transporter": "4.22.1" + } + }, + "node_modules/@algolia/client-analytics": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-4.22.1.tgz", + "integrity": "sha512-1ssi9pyxyQNN4a7Ji9R50nSdISIumMFDwKNuwZipB6TkauJ8J7ha/uO60sPJFqQyqvvI+px7RSNRQT3Zrvzieg==", + "dev": true, + "peer": true, + "dependencies": { + "@algolia/client-common": "4.22.1", + "@algolia/client-search": "4.22.1", + "@algolia/requester-common": "4.22.1", + "@algolia/transporter": "4.22.1" + } + }, + "node_modules/@algolia/client-common": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.22.1.tgz", + "integrity": "sha512-IvaL5v9mZtm4k4QHbBGDmU3wa/mKokmqNBqPj0K7lcR8ZDKzUorhcGp/u8PkPC/e0zoHSTvRh7TRkGX3Lm7iOQ==", + "dev": true, + "peer": true, + "dependencies": { + "@algolia/requester-common": "4.22.1", + "@algolia/transporter": "4.22.1" + } + }, + "node_modules/@algolia/client-personalization": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-4.22.1.tgz", + "integrity": "sha512-sl+/klQJ93+4yaqZ7ezOttMQ/nczly/3GmgZXJ1xmoewP5jmdP/X/nV5U7EHHH3hCUEHeN7X1nsIhGPVt9E1cQ==", + "dev": true, + "peer": true, + "dependencies": { + "@algolia/client-common": "4.22.1", + "@algolia/requester-common": "4.22.1", + "@algolia/transporter": "4.22.1" + } + }, + "node_modules/@algolia/client-search": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-4.22.1.tgz", + "integrity": "sha512-yb05NA4tNaOgx3+rOxAmFztgMTtGBi97X7PC3jyNeGiwkAjOZc2QrdZBYyIdcDLoI09N0gjtpClcackoTN0gPA==", + "dev": true, + "peer": true, + "dependencies": { + "@algolia/client-common": "4.22.1", + "@algolia/requester-common": "4.22.1", + "@algolia/transporter": "4.22.1" + } + }, + "node_modules/@algolia/logger-common": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/@algolia/logger-common/-/logger-common-4.22.1.tgz", + "integrity": "sha512-OnTFymd2odHSO39r4DSWRFETkBufnY2iGUZNrMXpIhF5cmFE8pGoINNPzwg02QLBlGSaLqdKy0bM8S0GyqPLBg==", + "dev": true, + "peer": true + }, + "node_modules/@algolia/logger-console": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/@algolia/logger-console/-/logger-console-4.22.1.tgz", + "integrity": "sha512-O99rcqpVPKN1RlpgD6H3khUWylU24OXlzkavUAMy6QZd1776QAcauE3oP8CmD43nbaTjBexZj2nGsBH9Tc0FVA==", + "dev": true, + "peer": true, + "dependencies": { + "@algolia/logger-common": "4.22.1" + } + }, + "node_modules/@algolia/requester-browser-xhr": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-4.22.1.tgz", + "integrity": "sha512-dtQGYIg6MteqT1Uay3J/0NDqD+UciHy3QgRbk7bNddOJu+p3hzjTRYESqEnoX/DpEkaNYdRHUKNylsqMpgwaEw==", + "dev": true, + "peer": true, + "dependencies": { + "@algolia/requester-common": "4.22.1" + } + }, + "node_modules/@algolia/requester-common": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/@algolia/requester-common/-/requester-common-4.22.1.tgz", + "integrity": "sha512-dgvhSAtg2MJnR+BxrIFqlLtkLlVVhas9HgYKMk2Uxiy5m6/8HZBL40JVAMb2LovoPFs9I/EWIoFVjOrFwzn5Qg==", + "dev": true, + "peer": true + }, + "node_modules/@algolia/requester-node-http": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-4.22.1.tgz", + "integrity": "sha512-JfmZ3MVFQkAU+zug8H3s8rZ6h0ahHZL/SpMaSasTCGYR5EEJsCc8SI5UZ6raPN2tjxa5bxS13BRpGSBUens7EA==", + "dev": true, + "peer": true, + "dependencies": { + "@algolia/requester-common": "4.22.1" + } + }, + "node_modules/@algolia/transporter": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/@algolia/transporter/-/transporter-4.22.1.tgz", + "integrity": "sha512-kzWgc2c9IdxMa3YqA6TN0NW5VrKYYW/BELIn7vnLyn+U/RFdZ4lxxt9/8yq3DKV5snvoDzzO4ClyejZRdV3lMQ==", + "dev": true, + "peer": true, + "dependencies": { + "@algolia/cache-common": "4.22.1", + "@algolia/logger-common": "4.22.1", + "@algolia/requester-common": "4.22.1" + } + }, + "node_modules/@babel/parser": { + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.9.tgz", + "integrity": "sha512-9tcKgqKbs3xGJ+NtKF2ndOBBLVwPjl1SHxPQkd36r3Dlirw3xWUeGaTbqr7uGZcTaxkVNwc+03SVP7aCdWrTlA==", + "dev": true, + "peer": true, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@docsearch/css": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-3.5.2.tgz", + "integrity": "sha512-SPiDHaWKQZpwR2siD0KQUwlStvIAnEyK6tAE2h2Wuoq8ue9skzhlyVQ1ddzOxX6khULnAALDiR/isSF3bnuciA==", + "dev": true, + "peer": true + }, + "node_modules/@docsearch/js": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/@docsearch/js/-/js-3.5.2.tgz", + "integrity": "sha512-p1YFTCDflk8ieHgFJYfmyHBki1D61+U9idwrLh+GQQMrBSP3DLGKpy0XUJtPjAOPltcVbqsTjiPFfH7JImjUNg==", + "dev": true, + "peer": true, + "dependencies": { + "@docsearch/react": "3.5.2", + "preact": "^10.0.0" + } + }, + "node_modules/@docsearch/react": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/@docsearch/react/-/react-3.5.2.tgz", + "integrity": "sha512-9Ahcrs5z2jq/DcAvYtvlqEBHImbm4YJI8M9y0x6Tqg598P40HTEkX7hsMcIuThI+hTFxRGZ9hll0Wygm2yEjng==", + "dev": true, + "peer": true, + "dependencies": { + "@algolia/autocomplete-core": "1.9.3", + "@algolia/autocomplete-preset-algolia": "1.9.3", + "@docsearch/css": "3.5.2", + "algoliasearch": "^4.19.1" + }, + "peerDependencies": { + "@types/react": ">= 16.8.0 < 19.0.0", + "react": ">= 16.8.0 < 19.0.0", + "react-dom": ">= 16.8.0 < 19.0.0", + "search-insights": ">= 1 < 3" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + }, + "search-insights": { + "optional": true + } + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.12.tgz", + "integrity": "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.12.tgz", + "integrity": "sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.12.tgz", + "integrity": "sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.12.tgz", + "integrity": "sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.12.tgz", + "integrity": "sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.12.tgz", + "integrity": "sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.12.tgz", + "integrity": "sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.12.tgz", + "integrity": "sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.12.tgz", + "integrity": "sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.12.tgz", + "integrity": "sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.12.tgz", + "integrity": "sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.12.tgz", + "integrity": "sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.12.tgz", + "integrity": "sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.12.tgz", + "integrity": "sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.12.tgz", + "integrity": "sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.12.tgz", + "integrity": "sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.12.tgz", + "integrity": "sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.12.tgz", + "integrity": "sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.12.tgz", + "integrity": "sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.12.tgz", + "integrity": "sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.12.tgz", + "integrity": "sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.12.tgz", + "integrity": "sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.12.tgz", + "integrity": "sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true, + "peer": true + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.9.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.9.6.tgz", + "integrity": "sha512-MVNXSSYN6QXOulbHpLMKYi60ppyO13W9my1qogeiAqtjb2yR4LSmfU2+POvDkLzhjYLXz9Rf9+9a3zFHW1Lecg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "peer": true + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.9.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.9.6.tgz", + "integrity": "sha512-T14aNLpqJ5wzKNf5jEDpv5zgyIqcpn1MlwCrUXLrwoADr2RkWA0vOWP4XxbO9aiO3dvMCQICZdKeDrFl7UMClw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "peer": true + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.9.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.9.6.tgz", + "integrity": "sha512-CqNNAyhRkTbo8VVZ5R85X73H3R5NX9ONnKbXuHisGWC0qRbTTxnF1U4V9NafzJbgGM0sHZpdO83pLPzq8uOZFw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "peer": true + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.9.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.9.6.tgz", + "integrity": "sha512-zRDtdJuRvA1dc9Mp6BWYqAsU5oeLixdfUvkTHuiYOHwqYuQ4YgSmi6+/lPvSsqc/I0Omw3DdICx4Tfacdzmhog==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "peer": true + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.9.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.9.6.tgz", + "integrity": "sha512-oNk8YXDDnNyG4qlNb6is1ojTOGL/tRhbbKeE/YuccItzerEZT68Z9gHrY3ROh7axDc974+zYAPxK5SH0j/G+QQ==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "peer": true + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.9.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.9.6.tgz", + "integrity": "sha512-Z3O60yxPtuCYobrtzjo0wlmvDdx2qZfeAWTyfOjEDqd08kthDKexLpV97KfAeUXPosENKd8uyJMRDfFMxcYkDQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "peer": true + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.9.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.9.6.tgz", + "integrity": "sha512-gpiG0qQJNdYEVad+1iAsGAbgAnZ8j07FapmnIAQgODKcOTjLEWM9sRb+MbQyVsYCnA0Im6M6QIq6ax7liws6eQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "peer": true + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.9.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.9.6.tgz", + "integrity": "sha512-+uCOcvVmFUYvVDr27aiyun9WgZk0tXe7ThuzoUTAukZJOwS5MrGbmSlNOhx1j80GdpqbOty05XqSl5w4dQvcOA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "peer": true + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.9.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.9.6.tgz", + "integrity": "sha512-HUNqM32dGzfBKuaDUBqFB7tP6VMN74eLZ33Q9Y1TBqRDn+qDonkAUyKWwF9BR9unV7QUzffLnz9GrnKvMqC/fw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "peer": true + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.9.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.9.6.tgz", + "integrity": "sha512-ch7M+9Tr5R4FK40FHQk8VnML0Szi2KRujUgHXd/HjuH9ifH72GUmw6lStZBo3c3GB82vHa0ZoUfjfcM7JiiMrQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "peer": true + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.9.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.9.6.tgz", + "integrity": "sha512-VD6qnR99dhmTQ1mJhIzXsRcTBvTjbfbGGwKAHcu+52cVl15AC/kplkhxzW/uT0Xl62Y/meBKDZvoJSJN+vTeGA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "peer": true + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.9.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.9.6.tgz", + "integrity": "sha512-J9AFDq/xiRI58eR2NIDfyVmTYGyIZmRcvcAoJ48oDld/NTR8wyiPUu2X/v1navJ+N/FGg68LEbX3Ejd6l8B7MQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "peer": true + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.9.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.9.6.tgz", + "integrity": "sha512-jqzNLhNDvIZOrt69Ce4UjGRpXJBzhUBzawMwnaDAwyHriki3XollsewxWzOzz+4yOFDkuJHtTsZFwMxhYJWmLQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "peer": true + }, + "node_modules/@shikijs/core": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-1.0.0.tgz", + "integrity": "sha512-UMKGMZ+8b88N0/n6DWwWth1PHsOaxjW+R2u+hzSiargZWTv+l3s1l8dhuIxUSsEUPlBDKLs2CSMiFZeviKQM1w==", + "dev": true, + "peer": true + }, + "node_modules/@shikijs/transformers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@shikijs/transformers/-/transformers-1.0.0.tgz", + "integrity": "sha512-US0Sc0OdH7eGL64BtfvX3XezPfqhqF5mPyBFLlbZqSpFt2/emnv9GveAWzELGsIuvXoJ6N1RjeAdmQx5Xni6BQ==", + "dev": true, + "peer": true, + "dependencies": { + "shiki": "1.0.0" + } + }, + "node_modules/@types/estree": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "dev": true, + "peer": true + }, + "node_modules/@types/linkify-it": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-3.0.5.tgz", + "integrity": "sha512-yg6E+u0/+Zjva+buc3EIb+29XEg4wltq7cSmd4Uc2EE/1nUVmxyzpX6gUXD0V8jIrG0r7YeOGVIbYRkxeooCtw==", + "dev": true, + "peer": true + }, + "node_modules/@types/markdown-it": { + "version": "13.0.7", + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-13.0.7.tgz", + "integrity": "sha512-U/CBi2YUUcTHBt5tjO2r5QV/x0Po6nsYwQU4Y04fBS6vfoImaiZ6f8bi3CjTCxBPQSO1LMyUqkByzi8AidyxfA==", + "dev": true, + "peer": true, + "dependencies": { + "@types/linkify-it": "*", + "@types/mdurl": "*" + } + }, + "node_modules/@types/mdurl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-1.0.5.tgz", + "integrity": "sha512-6L6VymKTzYSrEf4Nev4Xa1LCHKrlTlYCBMTlQKFuddo1CvQcE52I0mwfOJayueUC7MJuXOeHTcIU683lzd0cUA==", + "dev": true, + "peer": true + }, + "node_modules/@types/web-bluetooth": { + "version": "0.0.20", + "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.20.tgz", + "integrity": "sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==", + "dev": true, + "peer": true + }, + "node_modules/@vitejs/plugin-vue": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.0.3.tgz", + "integrity": "sha512-b8S5dVS40rgHdDrw+DQi/xOM9ed+kSRZzfm1T74bMmBDCd8XO87NKlFYInzCtwvtWwXZvo1QxE2OSspTATWrbA==", + "dev": true, + "peer": true, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "vite": "^5.0.0", + "vue": "^3.2.25" + } + }, + "node_modules/@vue/compiler-core": { + "version": "3.4.15", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.15.tgz", + "integrity": "sha512-XcJQVOaxTKCnth1vCxEChteGuwG6wqnUHxAm1DO3gCz0+uXKaJNx8/digSz4dLALCy8n2lKq24jSUs8segoqIw==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/parser": "^7.23.6", + "@vue/shared": "3.4.15", + "entities": "^4.5.0", + "estree-walker": "^2.0.2", + "source-map-js": "^1.0.2" + } + }, + "node_modules/@vue/compiler-dom": { + "version": "3.4.15", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.15.tgz", + "integrity": "sha512-wox0aasVV74zoXyblarOM3AZQz/Z+OunYcIHe1OsGclCHt8RsRm04DObjefaI82u6XDzv+qGWZ24tIsRAIi5MQ==", + "dev": true, + "peer": true, + "dependencies": { + "@vue/compiler-core": "3.4.15", + "@vue/shared": "3.4.15" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.4.15", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.15.tgz", + "integrity": "sha512-LCn5M6QpkpFsh3GQvs2mJUOAlBQcCco8D60Bcqmf3O3w5a+KWS5GvYbrrJBkgvL1BDnTp+e8q0lXCLgHhKguBA==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/parser": "^7.23.6", + "@vue/compiler-core": "3.4.15", + "@vue/compiler-dom": "3.4.15", + "@vue/compiler-ssr": "3.4.15", + "@vue/shared": "3.4.15", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.5", + "postcss": "^8.4.33", + "source-map-js": "^1.0.2" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.4.15", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.15.tgz", + "integrity": "sha512-1jdeQyiGznr8gjFDadVmOJqZiLNSsMa5ZgqavkPZ8O2wjHv0tVuAEsw5hTdUoUW4232vpBbL/wJhzVW/JwY1Uw==", + "dev": true, + "peer": true, + "dependencies": { + "@vue/compiler-dom": "3.4.15", + "@vue/shared": "3.4.15" + } + }, + "node_modules/@vue/devtools-api": { + "version": "7.0.14", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-7.0.14.tgz", + "integrity": "sha512-TluWR9qZ6aO11bwtYK8+fzXxBqLfsE0mWZz1q/EQBmO9k82Cm6deieLwNNXjNFJz7xutazoia5Qa+zTYkPPOfw==", + "dev": true, + "peer": true, + "dependencies": { + "@vue/devtools-kit": "^7.0.14" + } + }, + "node_modules/@vue/devtools-kit": { + "version": "7.0.14", + "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-7.0.14.tgz", + "integrity": "sha512-wAAJazr4hI0aVRpgWOCVPw+NzMQdthhnprHHIg4njp1MkKrpCNGQ7MtQbZF1AltAA7xpMCGyyt+0kYH0FqTiPg==", + "dev": true, + "peer": true, + "dependencies": { + "@vue/devtools-schema": "^7.0.14", + "@vue/devtools-shared": "^7.0.14", + "hookable": "^5.5.3", + "mitt": "^3.0.1", + "perfect-debounce": "^1.0.0", + "speakingurl": "^14.0.1" + } + }, + "node_modules/@vue/devtools-schema": { + "version": "7.0.14", + "resolved": "https://registry.npmjs.org/@vue/devtools-schema/-/devtools-schema-7.0.14.tgz", + "integrity": "sha512-tpUeCLVrdHX+KzWMLTAwx/vAPFbo6jAUi7sr6Q+0mBIqIVSSIxNr5wEhegiFvYva+OtDeM2OrT+f7/X/5bvZNg==", + "dev": true, + "peer": true + }, + "node_modules/@vue/devtools-shared": { + "version": "7.0.14", + "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-7.0.14.tgz", + "integrity": "sha512-79RP1NDakBVWou9rDpVnT1WMjTbL1lJKm6YEOodjQ0dq5ehf0wsRbeYDhgAlnjehWRzTq5GAYFBFUPYBs0/QpA==", + "dev": true, + "peer": true, + "dependencies": { + "rfdc": "^1.3.1" + } + }, + "node_modules/@vue/reactivity": { + "version": "3.4.15", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.15.tgz", + "integrity": "sha512-55yJh2bsff20K5O84MxSvXKPHHt17I2EomHznvFiJCAZpJTNW8IuLj1xZWMLELRhBK3kkFV/1ErZGHJfah7i7w==", + "dev": true, + "peer": true, + "dependencies": { + "@vue/shared": "3.4.15" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.4.15", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.15.tgz", + "integrity": "sha512-6E3by5m6v1AkW0McCeAyhHTw+3y17YCOKG0U0HDKDscV4Hs0kgNT5G+GCHak16jKgcCDHpI9xe5NKb8sdLCLdw==", + "dev": true, + "peer": true, + "dependencies": { + "@vue/reactivity": "3.4.15", + "@vue/shared": "3.4.15" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.4.15", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.15.tgz", + "integrity": "sha512-EVW8D6vfFVq3V/yDKNPBFkZKGMFSvZrUQmx196o/v2tHKdwWdiZjYUBS+0Ez3+ohRyF8Njwy/6FH5gYJ75liUw==", + "dev": true, + "peer": true, + "dependencies": { + "@vue/runtime-core": "3.4.15", + "@vue/shared": "3.4.15", + "csstype": "^3.1.3" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.4.15", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.15.tgz", + "integrity": "sha512-3HYzaidu9cHjrT+qGUuDhFYvF/j643bHC6uUN9BgM11DVy+pM6ATsG6uPBLnkwOgs7BpJABReLmpL3ZPAsUaqw==", + "dev": true, + "peer": true, + "dependencies": { + "@vue/compiler-ssr": "3.4.15", + "@vue/shared": "3.4.15" + }, + "peerDependencies": { + "vue": "3.4.15" + } + }, + "node_modules/@vue/shared": { + "version": "3.4.15", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.15.tgz", + "integrity": "sha512-KzfPTxVaWfB+eGcGdbSf4CWdaXcGDqckoeXUh7SB3fZdEtzPCK2Vq9B/lRRL3yutax/LWITz+SwvgyOxz5V75g==", + "dev": true, + "peer": true + }, + "node_modules/@vueuse/core": { + "version": "10.7.2", + "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-10.7.2.tgz", + "integrity": "sha512-AOyAL2rK0By62Hm+iqQn6Rbu8bfmbgaIMXcE3TSr7BdQ42wnSFlwIdPjInO62onYsEMK/yDMU8C6oGfDAtZ2qQ==", + "dev": true, + "peer": true, + "dependencies": { + "@types/web-bluetooth": "^0.0.20", + "@vueuse/metadata": "10.7.2", + "@vueuse/shared": "10.7.2", + "vue-demi": ">=0.14.6" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/core/node_modules/vue-demi": { + "version": "0.14.7", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.7.tgz", + "integrity": "sha512-EOG8KXDQNwkJILkx/gPcoL/7vH+hORoBaKgGe+6W7VFMvCYJfmF2dGbvgDroVnI8LU7/kTu8mbjRZGBU1z9NTA==", + "dev": true, + "hasInstallScript": true, + "peer": true, + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/@vueuse/integrations": { + "version": "10.7.2", + "resolved": "https://registry.npmjs.org/@vueuse/integrations/-/integrations-10.7.2.tgz", + "integrity": "sha512-+u3RLPFedjASs5EKPc69Ge49WNgqeMfSxFn+qrQTzblPXZg6+EFzhjarS5edj2qAf6xQ93f95TUxRwKStXj/sQ==", + "dev": true, + "peer": true, + "dependencies": { + "@vueuse/core": "10.7.2", + "@vueuse/shared": "10.7.2", + "vue-demi": ">=0.14.6" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "async-validator": "*", + "axios": "*", + "change-case": "*", + "drauu": "*", + "focus-trap": "*", + "fuse.js": "*", + "idb-keyval": "*", + "jwt-decode": "*", + "nprogress": "*", + "qrcode": "*", + "sortablejs": "*", + "universal-cookie": "*" + }, + "peerDependenciesMeta": { + "async-validator": { + "optional": true + }, + "axios": { + "optional": true + }, + "change-case": { + "optional": true + }, + "drauu": { + "optional": true + }, + "focus-trap": { + "optional": true + }, + "fuse.js": { + "optional": true + }, + "idb-keyval": { + "optional": true + }, + "jwt-decode": { + "optional": true + }, + "nprogress": { + "optional": true + }, + "qrcode": { + "optional": true + }, + "sortablejs": { + "optional": true + }, + "universal-cookie": { + "optional": true + } + } + }, + "node_modules/@vueuse/integrations/node_modules/vue-demi": { + "version": "0.14.7", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.7.tgz", + "integrity": "sha512-EOG8KXDQNwkJILkx/gPcoL/7vH+hORoBaKgGe+6W7VFMvCYJfmF2dGbvgDroVnI8LU7/kTu8mbjRZGBU1z9NTA==", + "dev": true, + "hasInstallScript": true, + "peer": true, + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/@vueuse/metadata": { + "version": "10.7.2", + "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-10.7.2.tgz", + "integrity": "sha512-kCWPb4J2KGrwLtn1eJwaJD742u1k5h6v/St5wFe8Quih90+k2a0JP8BS4Zp34XUuJqS2AxFYMb1wjUL8HfhWsQ==", + "dev": true, + "peer": true, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/shared": { + "version": "10.7.2", + "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-10.7.2.tgz", + "integrity": "sha512-qFbXoxS44pi2FkgFjPvF4h7c9oMDutpyBdcJdMYIMg9XyXli2meFMuaKn+UMgsClo//Th6+beeCgqweT/79BVA==", + "dev": true, + "peer": true, + "dependencies": { + "vue-demi": ">=0.14.6" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/shared/node_modules/vue-demi": { + "version": "0.14.7", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.7.tgz", + "integrity": "sha512-EOG8KXDQNwkJILkx/gPcoL/7vH+hORoBaKgGe+6W7VFMvCYJfmF2dGbvgDroVnI8LU7/kTu8mbjRZGBU1z9NTA==", + "dev": true, + "hasInstallScript": true, + "peer": true, + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/algoliasearch": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-4.22.1.tgz", + "integrity": "sha512-jwydKFQJKIx9kIZ8Jm44SdpigFwRGPESaxZBaHSV0XWN2yBJAOT4mT7ppvlrpA4UGzz92pqFnVKr/kaZXrcreg==", + "dev": true, + "peer": true, + "dependencies": { + "@algolia/cache-browser-local-storage": "4.22.1", + "@algolia/cache-common": "4.22.1", + "@algolia/cache-in-memory": "4.22.1", + "@algolia/client-account": "4.22.1", + "@algolia/client-analytics": "4.22.1", + "@algolia/client-common": "4.22.1", + "@algolia/client-personalization": "4.22.1", + "@algolia/client-search": "4.22.1", + "@algolia/logger-common": "4.22.1", + "@algolia/logger-console": "4.22.1", + "@algolia/requester-browser-xhr": "4.22.1", + "@algolia/requester-common": "4.22.1", + "@algolia/requester-node-http": "4.22.1", + "@algolia/transporter": "4.22.1" + } + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "dev": true, + "peer": true + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "peer": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/esbuild": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.12.tgz", + "integrity": "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==", + "dev": true, + "hasInstallScript": true, + "peer": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.19.12", + "@esbuild/android-arm": "0.19.12", + "@esbuild/android-arm64": "0.19.12", + "@esbuild/android-x64": "0.19.12", + "@esbuild/darwin-arm64": "0.19.12", + "@esbuild/darwin-x64": "0.19.12", + "@esbuild/freebsd-arm64": "0.19.12", + "@esbuild/freebsd-x64": "0.19.12", + "@esbuild/linux-arm": "0.19.12", + "@esbuild/linux-arm64": "0.19.12", + "@esbuild/linux-ia32": "0.19.12", + "@esbuild/linux-loong64": "0.19.12", + "@esbuild/linux-mips64el": "0.19.12", + "@esbuild/linux-ppc64": "0.19.12", + "@esbuild/linux-riscv64": "0.19.12", + "@esbuild/linux-s390x": "0.19.12", + "@esbuild/linux-x64": "0.19.12", + "@esbuild/netbsd-x64": "0.19.12", + "@esbuild/openbsd-x64": "0.19.12", + "@esbuild/sunos-x64": "0.19.12", + "@esbuild/win32-arm64": "0.19.12", + "@esbuild/win32-ia32": "0.19.12", + "@esbuild/win32-x64": "0.19.12" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true, + "peer": true + }, + "node_modules/focus-trap": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-7.5.4.tgz", + "integrity": "sha512-N7kHdlgsO/v+iD/dMoJKtsSqs5Dz/dXZVebRgJw23LDk+jMi/974zyiOYDziY2JPp8xivq9BmUGwIJMiuSBi7w==", + "dev": true, + "peer": true, + "dependencies": { + "tabbable": "^6.2.0" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/hookable": { + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz", + "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==", + "dev": true, + "peer": true + }, + "node_modules/magic-string": { + "version": "0.30.7", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.7.tgz", + "integrity": "sha512-8vBuFF/I/+OSLRmdf2wwFCJCz+nSn0m6DPvGH1fS/KiQoSaR+sETbov0eIk9KhEKy8CYqIkIAnbohxT/4H0kuA==", + "dev": true, + "peer": true, + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/mark.js": { + "version": "8.11.1", + "resolved": "https://registry.npmjs.org/mark.js/-/mark.js-8.11.1.tgz", + "integrity": "sha512-1I+1qpDt4idfgLQG+BNWmrqku+7/2bi5nLf4YwF8y8zXvmfiTBY3PV3ZibfrjBueCByROpuBjLLFCajqkgYoLQ==", + "dev": true, + "peer": true + }, + "node_modules/minisearch": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/minisearch/-/minisearch-6.3.0.tgz", + "integrity": "sha512-ihFnidEeU8iXzcVHy74dhkxh/dn8Dc08ERl0xwoMMGqp4+LvRSCgicb+zGqWthVokQKvCSxITlh3P08OzdTYCQ==", + "dev": true, + "peer": true + }, + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", + "dev": true, + "peer": true + }, + "node_modules/nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "peer": true, + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/perfect-debounce": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", + "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", + "dev": true, + "peer": true + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true, + "peer": true + }, + "node_modules/postcss": { + "version": "8.4.35", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.35.tgz", + "integrity": "sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "peer": true, + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/preact": { + "version": "10.19.3", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.19.3.tgz", + "integrity": "sha512-nHHTeFVBTHRGxJXKkKu5hT8C/YWBkPso4/Gad6xuj5dbptt9iF9NZr9pHbPhBrnT2klheu7mHTxTZ/LjwJiEiQ==", + "dev": true, + "peer": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + } + }, + "node_modules/rfdc": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.1.tgz", + "integrity": "sha512-r5a3l5HzYlIC68TpmYKlxWjmOP6wiPJ1vWv2HeLhNsRZMrCkxeqxiHlQ21oXmQ4F3SiryXBHhAD7JZqvOJjFmg==", + "dev": true, + "peer": true + }, + "node_modules/rollup": { + "version": "4.9.6", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.9.6.tgz", + "integrity": "sha512-05lzkCS2uASX0CiLFybYfVkwNbKZG5NFQ6Go0VWyogFTXXbR039UVsegViTntkk4OglHBdF54ccApXRRuXRbsg==", + "dev": true, + "peer": true, + "dependencies": { + "@types/estree": "1.0.5" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.9.6", + "@rollup/rollup-android-arm64": "4.9.6", + "@rollup/rollup-darwin-arm64": "4.9.6", + "@rollup/rollup-darwin-x64": "4.9.6", + "@rollup/rollup-linux-arm-gnueabihf": "4.9.6", + "@rollup/rollup-linux-arm64-gnu": "4.9.6", + "@rollup/rollup-linux-arm64-musl": "4.9.6", + "@rollup/rollup-linux-riscv64-gnu": "4.9.6", + "@rollup/rollup-linux-x64-gnu": "4.9.6", + "@rollup/rollup-linux-x64-musl": "4.9.6", + "@rollup/rollup-win32-arm64-msvc": "4.9.6", + "@rollup/rollup-win32-ia32-msvc": "4.9.6", + "@rollup/rollup-win32-x64-msvc": "4.9.6", + "fsevents": "~2.3.2" + } + }, + "node_modules/search-insights": { + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/search-insights/-/search-insights-2.13.0.tgz", + "integrity": "sha512-Orrsjf9trHHxFRuo9/rzm0KIWmgzE8RMlZMzuhZOJ01Rnz3D0YBAe+V6473t6/H6c7irs6Lt48brULAiRWb3Vw==", + "dev": true, + "peer": true + }, + "node_modules/shiki": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-1.0.0.tgz", + "integrity": "sha512-rOUGJa3yFGgOrEoiELYxraoBbag3ZWf9bpodlr05Wjm85Scx8OIX+otdSefq9Pk7L47TKEzGodSQb4L38jka6A==", + "dev": true, + "peer": true, + "dependencies": { + "@shikijs/core": "1.0.0" + } + }, + "node_modules/source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "dev": true, + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/speakingurl": { + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/speakingurl/-/speakingurl-14.0.1.tgz", + "integrity": "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tabbable": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz", + "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==", + "dev": true, + "peer": true + }, + "node_modules/vite": { + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.0.12.tgz", + "integrity": "sha512-4hsnEkG3q0N4Tzf1+t6NdN9dg/L3BM+q8SWgbSPnJvrgH2kgdyzfVJwbR1ic69/4uMJJ/3dqDZZE5/WwqW8U1w==", + "dev": true, + "peer": true, + "dependencies": { + "esbuild": "^0.19.3", + "postcss": "^8.4.32", + "rollup": "^4.2.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vitepress": { + "version": "1.0.0-rc.42", + "resolved": "https://registry.npmjs.org/vitepress/-/vitepress-1.0.0-rc.42.tgz", + "integrity": "sha512-VeiVVXFblt/sjruFSJBNChMWwlztMrRMe8UXdNpf4e05mKtTYEY38MF5qoP90KxPTCfMQiKqwEGwXAGuOTK8HQ==", + "dev": true, + "peer": true, + "dependencies": { + "@docsearch/css": "^3.5.2", + "@docsearch/js": "^3.5.2", + "@shikijs/core": "^1.0.0-rc.0", + "@shikijs/transformers": "^1.0.0-rc.0", + "@types/markdown-it": "^13.0.7", + "@vitejs/plugin-vue": "^5.0.3", + "@vue/devtools-api": "^7.0.14", + "@vueuse/core": "^10.7.2", + "@vueuse/integrations": "^10.7.2", + "focus-trap": "^7.5.4", + "mark.js": "8.11.1", + "minisearch": "^6.3.0", + "shiki": "^1.0.0-rc.0", + "vite": "^5.0.12", + "vue": "^3.4.15" + }, + "bin": { + "vitepress": "bin/vitepress.js" + }, + "peerDependencies": { + "markdown-it-mathjax3": "^4.3.2", + "postcss": "^8.4.34" + }, + "peerDependenciesMeta": { + "markdown-it-mathjax3": { + "optional": true + }, + "postcss": { + "optional": true + } + } + }, + "node_modules/vitepress-plugin-tabs": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/vitepress-plugin-tabs/-/vitepress-plugin-tabs-0.5.0.tgz", + "integrity": "sha512-SIhFWwGsUkTByfc2b279ray/E0Jt8vDTsM1LiHxmCOBAEMmvzIBZSuYYT1DpdDTiS3SuJieBheJkYnwCq/yD9A==", + "dev": true, + "peerDependencies": { + "vitepress": "^1.0.0-rc.27", + "vue": "^3.3.8" + } + }, + "node_modules/vue": { + "version": "3.4.15", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.15.tgz", + "integrity": "sha512-jC0GH4KkWLWJOEQjOpkqU1bQsBwf4R1rsFtw5GQJbjHVKWDzO6P0nWWBTmjp1xSemAioDFj1jdaK1qa3DnMQoQ==", + "dev": true, + "peer": true, + "dependencies": { + "@vue/compiler-dom": "3.4.15", + "@vue/compiler-sfc": "3.4.15", + "@vue/runtime-dom": "3.4.15", + "@vue/server-renderer": "3.4.15", + "@vue/shared": "3.4.15" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 000000000..c9a50ac5a --- /dev/null +++ b/package.json @@ -0,0 +1,10 @@ +{ + "scripts": { + "docs:dev": "vitepress dev docs/docs_site", + "docs:build": "vitepress build docs/docs_site", + "docs:preview": "vitepress preview docs/docs_site" + }, + "devDependencies": { + "vitepress-plugin-tabs": "^0.5.0" + } +} From f9a74570bc1fbb33842fcc1e99c4e007ab7f1d49 Mon Sep 17 00:00:00 2001 From: Lazaro Alonso Date: Thu, 15 Feb 2024 23:15:37 +0100 Subject: [PATCH 028/108] Fix logo and friends (#627) * create logo on ci and update readme * flip text --- .github/workflows/Documenter.yml | 1 + README.md | 142 ++++++++++++++++--------------- docs/logo.jl | 18 +--- 3 files changed, 75 insertions(+), 86 deletions(-) diff --git a/.github/workflows/Documenter.yml b/.github/workflows/Documenter.yml index 40df19501..8bb2007dd 100644 --- a/.github/workflows/Documenter.yml +++ b/.github/workflows/Documenter.yml @@ -56,6 +56,7 @@ jobs: run: julia --project=docs -e 'using Pkg; pkg"add https://github.com/LuxDL/DocumenterVitepress.jl.git"; pkg"dev ."; Pkg.instantiate(); Pkg.precompile(); Pkg.status()' - name: generating examples md files run: | + julia --project=docs/ --color=yes docs/logo.jl julia --project=docs/ --color=yes docs/make.jl - name: Build with VitePress run: | diff --git a/README.md b/README.md index a3c10e458..721da03b2 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,6 @@ # DimensionalData -[![](https://img.shields.io/badge/docs-stable-blue.svg)](https://rafaqz.github.io/DimensionalData.jl/stable) -[![](https://img.shields.io/badge/docs-dev-blue.svg)](https://rafaqz.github.io/DimensionalData.jl/dev) +[![](https://img.shields.io/badge/docs-dev-blue.svg)](https://rafaqz.github.io/DimensionalData.jl/) [![CI](https://github.com/rafaqz/DimensionalData.jl/actions/workflows/ci.yml/badge.svg)](https://github.com/rafaqz/DimensionalData.jl/actions/workflows/ci.yml) [![Codecov](https://codecov.io/gh/rafaqz/DimensionalData.jl/branch/main/graph/badge.svg)](https://codecov.io/gh/rafaqz/DimensionalData.jl/tree/main) [![Aqua.jl Quality Assurance](https://img.shields.io/badge/Aqua.jl-%F0%9F%8C%A2-aqua.svg)](https://github.com/JuliaTesting/Aqua.jl) @@ -16,62 +15,89 @@ syntax, and additional functionality found in NamedDims.jl. It has similar goals to pythons [xarray](http://xarray.pydata.org/en/stable/), and is primarily written for use with spatial data in [Rasters.jl](https://github.com/rafaqz/Rasters.jl). -The basic syntax is: +> [!IMPORTANT] +> INSTALLATION + +```shell +julia>] +pkg> add DimensionalData +``` + +Start using the package: ```julia -julia> using DimensionalData +using DimensionalData +``` -julia> A = DimArray(rand(50, 31), (X(), Y(10.0:40.0))); +The basic syntax is: + +```julia +A = DimArray(rand(50, 31), (X(), Y(10.0:40.0))); ``` Or just use `rand` directly, which also works for `zeros`, `ones` and `fill`: ```julia -julia> A = rand(X(50), Y(10.0:40.0)) -50×31 DimArray{Float64,2} with dimensions: - X, - Y Sampled{Float64} 10.0:1.0:40.0 ForwardOrdered Regular Points - 10.0 11.0 12.0 13.0 14.0 15.0 16.0 17.0 … 32.0 33.0 34.0 35.0 36.0 37.0 38.0 39.0 40.0 - 0.293347 0.737456 0.986853 0.780584 0.707698 0.804148 0.632667 0.780715 0.767575 0.555214 0.872922 0.808766 0.880933 0.624759 0.803766 0.796118 0.696768 - 0.199599 0.290297 0.791926 0.564099 0.0241986 0.239102 0.0169679 0.186455 0.644238 0.467091 0.524335 0.42627 0.982347 0.324083 0.0356058 0.306446 0.117187 - ⋮ ⋮ ⋱ ⋮ ⋮ - 0.720404 0.388392 0.635609 0.430277 0.943823 0.661993 0.650442 0.91391 … 0.299713 0.518607 0.411973 0.410308 0.438817 0.580232 0.751231 0.519257 0.598583 - 0.00602102 0.270036 0.696129 0.139551 0.924883 0.190963 0.164888 0.13436 0.717962 0.0452556 0.230943 0.848782 0.0362465 0.363868 0.709489 0.644131 0.801824 +A = rand(X(10), Y(10.0:20.0)) +``` +```julia +╭───────────────────────────╮ +│ 10×11 DimArray{Float64,2} │ +├───────────────────────────┴──────────────────────────────── dims ┐ + ↓ X, + → Y Sampled{Float64} 10.0:1.0:20.0 ForwardOrdered Regular Points +└──────────────────────────────────────────────────────────────────┘ + 10.0 11.0 12.0 13.0 14.0 … 16.0 17.0 18.0 19.0 20.0 + 0.71086 0.689255 0.672889 0.766345 0.00277696 0.773863 0.252199 0.279538 0.808931 0.783528 + 0.934464 0.815631 0.815715 0.890573 0.158584 0.304733 0.936321 0.499803 0.839926 0.979722 + ⋮ ⋱ ⋮ + 0.935495 0.460879 0.0218015 0.703387 0.756411 … 0.431141 0.619897 0.0536918 0.506488 0.170494 + 0.800226 0.208188 0.512795 0.421171 0.492668 0.238562 0.4694 0.320596 0.934364 0.147563 ``` -Subsetting by index is easy: +> [!NOTE] +> Subsetting by index is easy: ```julia -julia> A[Y=1:10, X=1] -10-element DimArray{Float64,1} with dimensions: - Y Sampled{Float64} 10.0:1.0:19.0 ForwardOrdered Regular Points -and reference dimensions: X - 10.0 0.293347 - 11.0 0.737456 - 12.0 0.986853 - 13.0 0.780584 +A[Y=1:10, X=1] +``` +```julia +╭────────────────────────────────╮ +│ 10-element DimArray{Float64,1} │ +├────────────────────────────────┴─────────────────────────── dims ┐ + ↓ Y Sampled{Float64} 10.0:1.0:19.0 ForwardOrdered Regular Points +└──────────────────────────────────────────────────────────────────┘ + 10.0 0.130198 + 11.0 0.693343 + 12.0 0.400656 ⋮ - 17.0 0.780715 - 18.0 0.472306 - 19.0 0.20442 + 17.0 0.877581 + 18.0 0.866406 + 19.0 0.605331 ``` We can also subset by lookup, using a `Selector`, lets try `At`: ```julia -julia> A[Y(At(25))] -50-element DimArray{Float64,1} with dimensions: X -and reference dimensions: - Y Sampled{Float64} 25.0:1.0:25.0 ForwardOrdered Regular Points - 1 0.459012 - 2 0.829744 - 3 0.633234 - 4 0.971626 - ⋮ - 47 0.454685 - 48 0.912836 - 49 0.906528 - 50 0.36339 +A[Y(At(25))] +``` +```julia +╭────────────────────────────────╮ +│ 50-element DimArray{Float64,1} │ +├────────────────────────── dims ┤ + ↓ X +└────────────────────────────────┘ + 1 0.5318 + 2 0.212491 + 3 0.99119 + 4 0.373549 + 5 0.0987397 + ⋮ + 46 0.503611 + 47 0.225421 + 48 0.293564 + 49 0.976395 + 50 0.622586 ``` There is also `Near` (for inexact/nearest selection), `Contains` (for `Intervals` containing values), @@ -86,35 +112,10 @@ boxplot(rand(X('a':'d'), Y(2:5:20))) And the plot will have the right ticks and labels. -[See the docs for more details](https://rafaqz.github.io/DimensionalData.jl/stable) - -Some properties of DimensionalData.jl objects: -- broadcasting and most Base methods maintain and sync dimension context. -- comprehensive plot recipes for both Plots.jl and Makie.jl. -- a Tables.jl interface with `DimTable` -- multi-layered `DimStack`s that can be indexed together, - and have base methods applied to all layers. -- the Adapt.jl interface for use on GPUs, even as GPU kernel arguments. -- traits for handling a wide range of spatial data types accurately. - -## Methods where dims can be used containing indices or Selectors - -`getindex`, `setindex!` `view` - -## Methods where dims, dim types, or `Symbol`s can be used to indicate the array dimension: - -- `size`, `axes`, `firstindex`, `lastindex` -- `cat`, `reverse`, `dropdims` -- `reduce`, `mapreduce` -- `sum`, `prod`, `maximum`, `minimum`, -- `mean`, `median`, `extrema`, `std`, `var`, `cor`, `cov` -- `permutedims`, `adjoint`, `transpose`, `Transpose` -- `mapslices`, `eachslice` - -## Methods where dims can be used to construct `DimArray`s: -- `fill`, `ones`, `zeros`, `falses`, `trues`, `rand` +[See the docs for more details](https://rafaqz.github.io/DimensionalData.jl/) -## **Note**: recent changes have greatly reduced the exported API +> [!NOTE] +> Recent changes have greatly reduced the exported API. Previously exported methods can me brought into global scope by `using` the sub-modules they have been moved to - `LookupArrays` and `Dimensions`: @@ -124,11 +125,12 @@ using DimensionalData using DimensionalData.LookupArrays, DimensionalData.Dimensions ``` -## Alternate Packages +> [!IMPORTANT] +> Alternate Packages There are a lot of similar Julia packages in this space. AxisArrays.jl, NamedDims.jl, NamedArrays.jl are registered alternative that each cover some of the functionality provided by DimensionalData.jl. DimensionalData.jl should be able to replicate most of their syntax and functionality. [AxisKeys.jl](https://github.com/mcabbott/AxisKeys.jl) and [AbstractIndices.jl](https://github.com/Tokazama/AbstractIndices.jl) are some other interesting developments. For more detail on why there are so many similar options and where things are headed, read this [thread](https://github.com/JuliaCollections/AxisArraysFuture/issues/1). The main functionality is explained here, but the full list of features is -listed at the [API](https://rafaqz.github.io/DimensionalData.jl/stable/api/) page. +listed at the [API](https://rafaqz.github.io/DimensionalData.jl/reference) page. \ No newline at end of file diff --git a/docs/logo.jl b/docs/logo.jl index d0c098423..7ca651abf 100644 --- a/docs/logo.jl +++ b/docs/logo.jl @@ -1,9 +1,7 @@ using Colors using CairoMakie CairoMakie.activate!() -# using GLMakie -# GLMakie.activate!() - +mkpath(joinpath(@__DIR__, "./src/public/")) rpyz = [Rect3f(Vec3f(0, j-0.8,k), Vec3f(0.1, 0.8,0.8)) for j in 1:7 for k in 1:7] @@ -30,21 +28,9 @@ meshscatter!(ax, Point3f(4,4,-0.01); color=:transparent) meshscatter!(ax, [Point3f(0.1,0.1,8), Point3f(0.1+7,0.1,8), Point3f(0.1,0.1+7,8), Point3f(0.1+7,0.1+7,8)]; color = colors[2], markersize=0.2, shading=FastShading) lines!(ax, [ Point3f(0.1+7,0.1,8), Point3f(0.1+7,0.1+7,8), - Point3f(0.1,0.1+7,8),# Point3f(0.1,0.1,0.8) + Point3f(0.1,0.1+7,8), ]; color = colors[2], linewidth=2, transparency=true) - -# text!(ax, "D", position = Point3f(6,3,3.75), -# fontsize=150, font=:bold, -# align= (:center, :center), -# color=colors[2], -# ) -# text!(ax, "D", position = Point3f(3,6,3.75), -# fontsize=150, align= (:center, :center), -# font=:bold, -# color=colors[1], -# #rotation=Vec3f(1,0,1) -# ) save(joinpath(@__DIR__, "./src/public/logoDD.png"), fig) fig \ No newline at end of file From fd2988bdf84a989d13b95164d5f4a8cbd6b7c817 Mon Sep 17 00:00:00 2001 From: Nicholas Chisholm <2267458+nchisholm@users.noreply.github.com> Date: Fri, 16 Feb 2024 12:45:48 -0500 Subject: [PATCH 029/108] Reverse along all dimensions by default (#623) * Reverse along all dimensions by default Fixes #622. Changes `reverse(::AbstractDimArray)` to reverse along all dimensions instead of just the first one. Equivalently, a `:` may be passed to the `dims` argument and is the new default. This behavior is consistent with what happens for a plain `AbstractArray`. * Update src/array/methods.jl * Update src/array/methods.jl --------- Co-authored-by: Rafael Schouten --- src/array/methods.jl | 13 ++++++++++--- test/utils.jl | 22 +++++++++++++++------- 2 files changed, 25 insertions(+), 10 deletions(-) diff --git a/src/array/methods.jl b/src/array/methods.jl index efbdf32a6..dc7d9ee0c 100644 --- a/src/array/methods.jl +++ b/src/array/methods.jl @@ -578,14 +578,21 @@ function Base._replace!(new::Base.Callable, res::AbstractDimArray, A::AbstractDi return res end -function Base.reverse(A::AbstractDimArray; dims=1) +Base.reverse(A::AbstractDimArray; dims=:) = _reverse(A, dims) + +function _reverse(A::AbstractDimArray, ::Colon) + newdims = _reverse(DD.dims(A)) + newdata = reverse(parent(A)) + # Use setdims here because newdims is not all the dims + setdims(rebuild(A, newdata), newdims) +end +function _reverse(A::AbstractDimArray, dims) newdims = _reverse(DD.dims(A, dims)) newdata = reverse(parent(A); dims=dimnum(A, dims)) # Use setdims here because newdims is not all the dims setdims(rebuild(A, newdata), newdims) end - -_reverse(dims::Tuple) = map(d -> reverse(d), dims) +_reverse(dims::Tuple{Vararg{Dimension}}) = map(d -> reverse(d), dims) _reverse(dim::Dimension) = reverse(dim) # Dimension diff --git a/test/utils.jl b/test/utils.jl index c2e5a7b27..3e70277b3 100644 --- a/test/utils.jl +++ b/test/utils.jl @@ -15,13 +15,21 @@ using DimensionalData: uniquekeys da = DimArray(A, (X(10:10:20), Y(300:-100:100)); name=:test) s = DimStack(da) - rev = reverse(da; dims=Y) - @test rev == [3 2 1; 6 5 4] - @test index(rev, X) == 10:10:20 - @test index(rev, Y) == 100:100:300 - @test span(rev, Y) == Regular(100) - @test order(rev, Y) == ForwardOrdered() - @test order(rev, X) == ForwardOrdered() + rev_y = reverse(da; dims=Y) + @test rev_y == [3 2 1; 6 5 4] + @test index(rev_y, X) == 10:10:20 + @test index(rev_y, Y) == 100:100:300 + @test span(rev_y, Y) == Regular(100) + @test order(rev_y, Y) == ForwardOrdered() + @test order(rev_y, X) == ForwardOrdered() + + rev = reverse(da; dims=:) + @test parent(rev) == reverse(parent(da)) + @test all(index(rev, d) == reverse(index(da, d)) for d in (X,Y)) + @test all(span(rev, d) == reverse(span(da, d)) for d in (X,Y)) + @test all(order(rev, d) == reverse(order(da, d)) for d in (X,Y)) + @test rev == reverse(da; dims=(X,Y)) + @testset "NoLookup dim index is not reversed" begin da = DimArray(A, (X(), Y())) From 27c6d0e94094b3d6a1bb2f5f31a06414bb58391f Mon Sep 17 00:00:00 2001 From: Rafael Schouten Date: Fri, 16 Feb 2024 22:49:09 +0100 Subject: [PATCH 030/108] Fill out docs (#628) * write some docs * more docs * document basedims * fix 1 dim print matrix --- docs/src/.vitepress/config.mts | 13 +- docs/src/api/dimensions.md | 74 +++++++++ docs/src/api/lookuparrays.md | 110 +++++++++++++ docs/src/api/reference.md | 94 +++++++++++ docs/src/basics.md | 61 +------ docs/src/cuda.md | 47 +++++- docs/src/dimensions.md | 218 ++++++++++++++++--------- docs/src/diskarrays.md | 94 +++++++++++ docs/src/ext_dd.md | 86 +++++++++- docs/src/integrations.md | 84 +++++++++- docs/src/plots.md | 43 ++++- docs/src/reference.md | 260 ------------------------------ docs/src/selectors.md | 57 ++++--- docs/src/stacks.md | 30 +++- docs/src/tables.md | 19 ++- package-lock.json | 2 +- src/Dimensions/primitives.jl | 8 + src/LookupArrays/lookup_traits.jl | 7 - src/LookupArrays/selector.jl | 15 +- src/array/methods.jl | 2 +- src/array/show.jl | 34 ++-- src/plotrecipes.jl | 8 +- test/plotrecipes.jl | 5 +- 23 files changed, 908 insertions(+), 463 deletions(-) create mode 100644 docs/src/api/dimensions.md create mode 100644 docs/src/api/lookuparrays.md create mode 100644 docs/src/api/reference.md create mode 100644 docs/src/diskarrays.md delete mode 100644 docs/src/reference.md diff --git a/docs/src/.vitepress/config.mts b/docs/src/.vitepress/config.mts index 28c9af6e4..d019071e5 100644 --- a/docs/src/.vitepress/config.mts +++ b/docs/src/.vitepress/config.mts @@ -43,10 +43,11 @@ export default defineConfig({ { text: 'Selectors', link: '/selectors' }, { text: 'Integrations', items: [ - {text: 'Dependants', link: '/integrations' }, + { text: 'Integrations', link: '/integrations' }, { text: 'Tables and DataFrames', link: '/tables' }, { text: 'Plots with Makie', link: '/plots' }, - { text: 'CUDA & GPUs', link: '/cuda.md' }, + { text: 'CUDA & GPUs', link: '/cuda' }, + { text: 'DiskArrays', link: '/diskarrays' }, { text: 'Extending DimensionalData', link: '/ext_dd' }, ], }, @@ -69,7 +70,13 @@ export default defineConfig({ { text: 'Extending DimensionalData', link: '/ext_dd' }, { text: 'Plots with Makie', link: '/plots' }, { text: 'CUDA & GPUs', link: '/cuda' }, - { text: 'API Reference', link: '/reference' }, + { text: 'API Reference', + items: [ + { text: 'General Reference', link: '/api/reference' }, + { text: 'Dimensions Reference', link: '/api/dimensions' }, + { text: 'LookupArrays Reference', link: '/api/lookuparrays' }, + ], + }, ] } ], diff --git a/docs/src/api/dimensions.md b/docs/src/api/dimensions.md new file mode 100644 index 000000000..3841beb3d --- /dev/null +++ b/docs/src/api/dimensions.md @@ -0,0 +1,74 @@ + +# Dimensions + +Dimensions are kept in the sub-module `Dimensions`. + +```@docs +Dimensions.Dimensions +``` + +Dimensions have a type-heirarchy that organises plotting and +dimension matching. + +```@docs +Dimensions.Dimension +Dimensions.DependentDim +Dimensions.IndependentDim +Dimensions.XDim +Dimensions.YDim +Dimensions.ZDim +Dimensions.TimeDim +X +Y +Z +Ti +Dim +Coord +Dimensions.AnonDim +``` + +### Exported methods + +These are widely useful methods for working with dimensions. + +```@docs +dims +dimnum +hasdim +otherdims +val +``` + +### Non-exported methods + +```@docs +Dimensions.label +DimensionalData.format +DimensionalData.dims2indices +DimensionalData.selectindices +``` + +### Primitive methods + +These low-level methods are really for internal use, but +can be useful for writing dimensional algorithms. + +They are not guaranteed to keep their interface, but usually will. + +```@docs +DimensionalData.commondims +DimensionalData.dim2key +DimensionalData.key2dim +DimensionalData.reducedims +DimensionalData.swapdims +DimensionalData.slicedims +DimensionalData.comparedims +DimensionalData.combinedims +DimensionalData.sortdims +DimensionalData.basetypeof +DimensionalData.basedims +DimensionalData.setdims +DimensionalData.dimsmatch +DimensionalData.dimstride +DimensionalData.refdims_title +``` diff --git a/docs/src/api/lookuparrays.md b/docs/src/api/lookuparrays.md new file mode 100644 index 000000000..316ab9b3b --- /dev/null +++ b/docs/src/api/lookuparrays.md @@ -0,0 +1,110 @@ + +# LookupArrays + +```@docs +LookupArrays.LookupArrays +``` + +```@docs +LookupArrays.LookupArray +LookupArrays.Aligned +LookupArrays.AbstractSampled +LookupArrays.Sampled +LookupArrays.AbstractCyclic +LookupArrays.Cyclic +LookupArrays.AbstractCategorical +LookupArrays.Categorical +LookupArrays.Unaligned +LookupArrays.Transformed +Dimensions.MergedLookup +LookupArrays.NoLookup +LookupArrays.AutoLookup +LookupArrays.AutoIndex +``` + +The generic value getter `val` + +```@docs +LookupArrays.val +``` + +Lookup methods: + +```@docs +bounds +hasselection +LookupArrays.index +LookupArrays.sampling +LookupArrays.span +LookupArrays.order +LookupArrays.locus +LookupArrays.shiftlocus +``` + +## Selectors + +```@docs +LookupArrays.Selector +LookupArrays.IntSelector +LookupArrays.ArraySelector +At +Near +Between +Touches +Contains +Where +All +``` + +## LookupArray traits + +```@docs +LookupArrays.LookupArrayTrait +``` + +### Order + +```@docs +LookupArrays.Order +LookupArrays.Ordered +LookupArrays.ForwardOrdered +LookupArrays.ReverseOrdered +LookupArrays.Unordered +LookupArrays.AutoOrder +``` + +### Span + +```@docs +LookupArrays.Span +LookupArrays.Regular +LookupArrays.Irregular +LookupArrays.Explicit +LookupArrays.AutoSpan +``` + +### Sampling + +```@docs +LookupArrays.Sampling +LookupArrays.Points +LookupArrays.Intervals +``` + +### Loci + +```@docs +LookupArrays.Locus +LookupArrays.Center +LookupArrays.Start +LookupArrays.End +LookupArrays.AutoLocus +``` + +## Metadata + +```@docs +LookupArrays.AbstractMetadata +LookupArrays.Metadata +LookupArrays.NoMetadata +``` diff --git a/docs/src/api/reference.md b/docs/src/api/reference.md new file mode 100644 index 000000000..b5dcefbd5 --- /dev/null +++ b/docs/src/api/reference.md @@ -0,0 +1,94 @@ + +# API Reference + +## Arrays + +```@docs +AbstractDimArray +DimArray +``` + +Shorthand `AbstractDimArray` constructors: + +```@docs +Base.fill +Base.rand +Base.zeros +Base.ones +``` + +Functions for getting information from objects: + +```@docs +dims +refdims +metadata +name +``` + +## Multi-array datasets + +```@docs +AbstractDimStack +DimStack +``` + +## Dimension generators + +```@docs +DimIndices +DimKeys +DimPoints +``` + +## Tables.jl/TableTraits.jl interface + +```@docs +DimensionalData.AbstractDimTable +DimTable +DimensionalData.DimColumn +``` + +# Utility methods + +For transforming DimensionalData objects: + +```@docs +set +rebuild +modify +broadcast_dims +broadcast_dims! +mergedims +unmergedims +reorder +``` + +Base methods + +```@docs +Base.cat +Base.map +Base.copy! +Base.eachslice +``` + + +Most base methods work as expected, using `Dimension` wherever a `dims` +keyword is used. They are not allspecifically documented here. + +## Name + +```@docs +DimensionalData.AbstractName +DimensionalData.Name +DimensionalData.NoName +``` + +## Internal interface methods + +```@docs +DimensionalData.rebuild_from_arrays +DimensionalData.show_main +DimensionalData.show_after +``` diff --git a/docs/src/basics.md b/docs/src/basics.md index a74e19bdc..b784512c6 100644 --- a/docs/src/basics.md +++ b/docs/src/basics.md @@ -1,8 +1,5 @@ -# Getting Started +# Installation -`DimensionalData.jl` provides tools and abstractions for working with datasets that have named dimensions, and optionally a lookup index. - -## Installation ````shell julia>] pkg> add DimensionalData @@ -12,7 +9,7 @@ Check the installed version: ````shell julia>] -pkg> st +pkg> status DimensionalData ```` Start using the package: @@ -20,57 +17,3 @@ Start using the package: ````@example basics using DimensionalData ```` - -## Philosophy - -DimensionalData is a pluggable, generalised version of -[AxisArrays.jl](https://github.com/JuliaArrays/AxisArrays.jl) with a cleaner -syntax, and additional functionality found in NamedDims.jl. It has similar goals -to pythons [xarray](http://xarray.pydata.org/en/stable/), and is primarily -written for use with spatial data in [Rasters.jl](https://github.com/rafaqz/Rasters.jl). - -::: info - -- Clean, readable syntax. Minimise required parentheses, minimise of exported -- Zero-cost dimensional indexing `a[Y(4), X(5)]` of a single value. - methods, and instead extend Base methods whenever possible. -- Plotting is easy: data should plot sensibly and correctly with useful labels, by default. -- Least surprise: everything works the same as in Base, but with named dims. If - a method accepts numeric indices or `dims=X` in base, you should be able to - use DimensionalData.jl dims. -- Minimal interface: implementing a dimension-aware type should be easy. -- Maximum extensibility: always use method dispatch. Regular types over special - syntax. Recursion over @generated. Always dispatch on abstract types. -- Type stability: dimensional methods should be type stable _more often_ than Base methods -- Functional style: structs are always rebuilt, and other than the array data, - fields are not mutated in place. - -::: - -## Data types and the interface - -DimensionalData.jl provides the concrete `DimArray` type. But its -behaviours are intended to be easily applied to other array types. - -::: details more - -The main requirement for extending DimensionalData.jl is to define a `dims` method -that returns a `Tuple` of `Dimension` that matches the dimension order -and axis values of your data. Define `rebuild` and base methods for `similar` -and `parent` if you want the metadata to persist through transformations (see -the `DimArray` and `AbstractDimArray` types). A `refdims` method -returns the lost dimensions of a previous transformation, passed in to the -`rebuild` method. `refdims` can be discarded, the main loss being plot labels -and ability to reconstruct dimensions in `cat`. - -Inheriting from `AbstractDimArray` in this way will give nearly all the functionality -of using `DimArray`. - -::: - -## LookupArrays and Dimensions - -Sub modules `LookupArrays` and `Dimensions` define the behaviour of -dimensions and their lookup index. - -[`LookupArrays`](@ref) and [`Dimensions`](@ref). \ No newline at end of file diff --git a/docs/src/cuda.md b/docs/src/cuda.md index 1708bf6dd..839498b08 100644 --- a/docs/src/cuda.md +++ b/docs/src/cuda.md @@ -1 +1,46 @@ -# CUDA & GPUs \ No newline at end of file +# CUDA & GPUs + +Running regular julia code on GPUs is one of the most amazing things +about the language. DimensionalData.jl leans into this as much as possible. + +From the beginning DimensionalData.jl has had two GPU-related goals: + +1. Work seamlessly with Base julia broadcasts and other operations that already + work on GPU. +2. Work as arguments to custom GPU kernel funcions. + +This means any `AbstractDimArray` must be automatically moved to the gpu and its +fields converted to GPU friendly forms whenever required, using [Adapt.jl](https://github.com/JuliaGPU/Adapt.jl)). + +- The array data must converts to the correct GPU array backend + when `Adapt.adapt(dimarray)` is called. +- All DimensionalData.jl objects, except the actual parent array, need to be immutable `isbits` or + convertable to them. This is one reason DimensionalData.jl uses `rebuild` and a functional style, + rather than in-place modification of fields. +- Symbols need to be moved to the type system `Name{:layer_name}()` replaces `:layer_name` +- Metadata dicts need to be stripped, they are often too difficult to convert, + and not needed on GPU. + + +As an example, DynamicGrids.jl uses `AbstractDimArray` for auxiliary +model data that are passed into [KernelAbstractions.jl](https://github.com/JuliaGPU/KernelAbstractions.jl)/ +[CUDA.jl](https://github.com/JuliaGPU/CUDA.jl) kernels. + + +Note: due to limitations of the machines available in our github actions CI, +we *do not* currently test on GPU. But we should. + +If anyone wants to set us up with CI that has a GPU, please make a PR! + +```julia +using DimensionalData, CUDA + +# Create a Float32 array to use on the GPU +A = rand(Float32, X(1.0:1000.0), Y(1.0:2000.0)) + +# Move the parent data to the GPU with `modify` and the `CuArray` constructor: +cuA = modify(CuArray, A) + +# Broadcast to a new GPU array: it will still be a DimArray! +cuA2 = cuA .* 2 +``` diff --git a/docs/src/dimensions.md b/docs/src/dimensions.md index edc09f3f7..aa2247b7c 100644 --- a/docs/src/dimensions.md +++ b/docs/src/dimensions.md @@ -1,128 +1,178 @@ # Dimensions -````@example dimensions -using DimensionalData -```` +Dimensions are "wrapper types" that can be used to wrap any +object to associate it with a named dimension. -The core type of DimensionalData.jl is the [`Dimension`](@ref) and the types -that inherit from it, such as `Ti`, `X`, `Y`, `Z`, the generic `Dim{:x}`, or -others that you define manually using the [`@dim`](@ref) macro. +The abstract supertype is [`Dimension`](@ref), and the types +that inherit from it aare `Ti`, `X`, `Y`, `Z`, the generic `Dim{:x}`, +or others that you define manually using the [`@dim`](@ref) macro. -`Dimension`s are primarily used in [`DimArray`](@ref), other -[`AbstractDimArray`](@ref). +DimensionalData.jl uses `Dimensions` pretty much everywhere: -## DimArray -We can use dimensions without a value index - these simply label the axis. -A `DimArray` with labelled dimensions is constructed by: +- `Dimension` are returned from `dims` to specify the names of the dimensions of an object +- they wrap [`LookupArrays`](@ref) to associate the lookups with those names +- to index into these objects, they can wrap indices like `Int` or a `Selector` -````@ansi dimensions -A = rand(X(5), Y(5)) -```` +This symmetry means we can just ignore how data is organised, and +just label and access it by name, letting DD work out the details for us. -get a value +Dimensions are defined in the [`Dimensions`](@ref) submodule, some +Dimension-specific methods can be brought into scope with: -````@ansi dimensions -A[Y(1), X(2)] -```` +```julia +using DimensionalData.Dimensions +``` -As shown above, `Dimension`s can be used to construct arrays in `rand`, `ones`, -`zeros` and `fill` with either a range for a lookup index or a number for the -dimension length. +## Examples -Or we can use the `Dim{X}` dims by using `Symbol`s, and indexing with keywords: +## Use in a `DimArray` -````@ansi dimensions -A = DimArray(rand(5, 5), (:a, :b)) -```` +We can use dimensions without a `LookupArray` to simply label the axis. +A `DimArray` with labelled dimensions can be constructed by: -get value +```@ansi dimensions +using DimensionalData -````@ansi dimensions -A[a=3, b=5] -```` +A1 = zeros(X(5), Y(10)) +``` -## What is a dimension? +And we can access a value with: -````@example dimensions -using DimensionalData -using Dates -t = DateTime(2001):Month(1):DateTime(2001,12) -x = 10:10:100 -nothing # hide -```` +```@ansi dimensions +A1[Y(1), X(2)] +``` -````@ansi dimensions -A = rand(X(x), Ti(t)); -```` +As shown above, `Dimension`s can be used to construct arrays in `rand`, `zeros`, +`ones` and `fill`, with either a range for a lookup index or a number for the +dimension length. + +For completely arbitrary names, we can use the `Dim{:name}` dims +by using `Symbol`s, and indexing with keywords: + +```@ansi dimensions +A2 = DimArray(rand(5, 5), (:a, :b)) +``` + +and get a value: + +```@ansi dimensions +A2[a=3, b=1:3] +``` + +Keywords also work with our first example: -Here both `X` and `Ti` are dimensions from `DimensionalData`. The currently -exported dimensions are `X, Y, Z, Ti` (`Ti` is shortening of `Time`). +```@ansi dimensions +A1[X=3] +``` The length of each dimension index has to match the size of the corresponding array axis. -This can also be done with `Symbol`, using `Dim{X}`: - -````@ansi dimensions -A2 = DimArray(rand(12, 10), (time=t, distance=x)) -```` ## Dimensional Indexing -Dimensions can be used to index the array by name, without having to worry -about the order of the dimensions. -The simplest case is to select a dimension by index. Let's say every 2nd point -of the `Ti` dimension and every 3rd point of the `X` dimension. This is done -with the simple `Ti(range)` syntax like so: +When used in indexing, dimension wrappers free us from knowing the +order of our objects axes, or from even keeping it consistent. -````@ansi dimensions -A[X(1:3:11), Ti(1:2:11)] -```` +We can index in whatever order we want to. These are the same: -When specifying only one dimension, all elements of the other dimensions are assumed to be included: +```@ansi dimensions +A1[X(2), Y(1)] +A1[Y(1), X(2)] +``` -````@ansi dimensions -A[X(1:3:10)] -```` +We can Index with a single dimsions, and the remaining will be filled with colons: + +```@ansi dimensions +A1[Y(1:2:5)] +``` + +We can use Tuples of dimensions like `CartesianIndex`, but they don't have to +be in order or for consecutive axes. + +```@ansi dimensions +A3 = rand(X(10), Y(7), Z(5)) +# TODO not merged yet A3[(X(3), Z(5))] +``` + +We can index with `Vector` of `Tuple{Vararg(Dimension}}` like vectors of +`CartesianIndex` + +```@ansi dimensions +# TODO not merged yet A3[[(X(3), Z(5)), (X(7), Z(x)), (X(8), Z(2))]] +``` + +`DimIndices` can be used like `CartesianIndices` but again, without the +constraint of consecutive dimensions or known order. + +```@ansi dimensions +# TODO not merged yet A3[DimIndices(dims(A3, (X, Z))), Y(3)] +``` + +All of this indexing can be combined arbitrarily. + +This will regurn values for `:e` at 6, `:a` at 3, all of `:d` an `:b`, and a vector of `:c` +and `:f`. Unlike base, we know that `:c` and `:f` are now related and merge the `:c` and `:f` +dimensions into a lookup of tuples: + +```@ansi dimensions +A4 = DimArray(rand(10, 9, 8, 7, 6, 5), (:a, :b, :c, :d, :e, :f)) + +# TODO not merged yet A4[e=6, DimIndices(dims(A4, (:d, :b))), a=3, collect(DimIndices(dims(A4, (:c, :f))))] +``` + +The `Dimension` indexing layer sits on top of regular indexing and _can not_ be combined +with it! Regular indexing specifies order, so doesn't mix well with our dimensions. + +Mixing them will throw an error: + +```@example dimension +A1[X(3), 4] +ERROR: ArgumentError: invalid index: X{Int64}(3) of type X{Int64} +``` ::: info Indexing Indexing `AbstractDimArray`s works with `getindex`, `setindex!` and `view`. The result is still an `AbstracDimArray`, unless using all single -`Int` or `Selector`s that resolve to `Int`. +`Int` or `Selector`s that resolve to `Int` inside `Dimension`. ::: -### Indexing Performance -Indexing with `Dimension` has no runtime cost: +## Indexing Performance -````@ansi dimensions +Indexing with `Dimension`s has no runtime cost: + +```@ansi dimensions A2 = ones(X(3), Y(3)) -```` +``` -time ? +Lets benchmark it -````@example dimensions +```@example dimensions using BenchmarkTools -```` +``` -````@ansi dimensions +```@ansi dimensions @benchmark $A2[X(1), Y(2)] -```` +``` -and +the same as accessing the parent array directly: + +```@ansi dimensions +@benchmark parent($A2)[1, 2] +``` -````@ansi dimensions -@btime parent($A2)[1, 2] -```` -In many Julia functions like `size` or `sum`, you can specify the dimension +## `dims` keywords + +In many Julia functions like, `size` or `sum`, you can specify the dimension along which to perform the operation as an `Int`. It is also possible to do this using [`Dimension`](@ref) types with `AbstractDimArray`: ````@ansi dimensions -A3 = rand(X(3), Y(4), Ti(5)); +A3 = rand(X(3), Y(4), Ti(5)) sum(A3; dims=Ti) ```` @@ -136,7 +186,20 @@ using Statistics mean(A3; dims=Ti) ```` -::: info +This can be especially useful when you are working with multiple objects. +Here we take the mean of A3 over all dimensions _not in_ A2, using `otherdims`. + +In this case, thats the `Z` dimension. But we don't need to know it the Z +dimension, some other dimensions, or even if it has extra dimensions at all! + +This will work either way, leaveing us with the same dims as A1: + +````@ansi dimensions +d = otherdims(A3, dims(A1)) +dropdims(mean(A3; dims=d); dims=d) +```` + +::: info Dims keywords Methods where dims, dim types, or `Symbol`s can be used to indicate the array dimension: @@ -151,10 +214,9 @@ Methods where dims, dim types, or `Symbol`s can be used to indicate the array di ::: -## Dimensions ## DimIndices ## Vectors of Dimensions ## How to name dimensions? ## How to name an array? -## Adding metadata \ No newline at end of file +## Adding metadata diff --git a/docs/src/diskarrays.md b/docs/src/diskarrays.md new file mode 100644 index 000000000..3fa62791a --- /dev/null +++ b/docs/src/diskarrays.md @@ -0,0 +1,94 @@ +### [DiskArrays.jl](https://github.com/meggart/DiskArrays.jl) compatability + + +The combination of DiskArrays.jl and DimensionalData.jl is Julias answer to +pythons [xarray](https://xarray.dev/). + +Rasters.jl and YAXArrays.jl are the user-facing tools building on this +combination. + +DiskArrays.jl is rarely used directly by users, but is present in most +disk and cloud based spatial data packages in julia, including: +- ArchGDAL.jl +- NetCDF.jl +- Zarr.jl +- NCDatasets.lj +- GRIBDatasets.jl +- CommonDataModel.jl +- etc... + +So that lazy, chunked data access conforms to julias array +interface but also scales to operating on terrabytes of data. + +DiskArrays enables chunk ordered lazy application of: + +- broadcast +- reduce +- iteration +- generators +- zip + +DimensionalData.jl is a common front-end for accessing DiskArrays.jl +compatible datasets. Wherever An `AbstractDimArray` wraps a disk array we +will do our best to make sure all of the DimensionalData.jl indexing and +DiskArrays.jl lazy/chunked operations work together cleanly. + +They have no direct dependency relationships, with but are intentionally +designed to integrate via both adherence to julias `AbstractArray` +interface, and by coordination during development of both packages. + + +# Example + +Out of the box integration. + +DimensionalData.jl and DiskArrays.jl play nice no matter the size of the data. +To make this all work in CI we will simulate some huge data by multiplying +a huge `BitArray` with a `BigInt`, meant to make it 128 x larger in memory. + +```@example diskarray +using DimensionalData, DiskArrays + +# This holds is a 100_100 * 50_000 `BitArray` +A = trues(100_000, 50_000) +diska = DiskArrays.TestTypes.AccessCountDiskArray(A; chunksize=(100, 100)) +dima = DimArray(diska, (X(0.01:0.01:1000), Y(0.02:0.02:1000))) +``` + +# How big is this thing? +```@example diskarray +GB = sizeof(A) / 1e9 +``` + + +Now if we multiply that by 2.0 they will be Float64, ie 64 x larger. + +But: + +```@example diskarray +dimb = view(permutedims(dima .* BigInt(200000000000), (X, Y)); X=1:99999) +sizeof(dimb) +``` + +The size should be: +```@example diskarray +GB = (sizeof(eltype(dimb)) * prod(size(dimb))) / 1e9 +``` + +I'm writing this on a laptop with only 32Gb or ram, but this runs instantly. + +The trick is nothing happens until we index: + +```@example diskarray +diska.getindex_count +``` + +These are just access for printing in the repl! + +When we actually get data the calulations happen, +and for real disk arrays the chunked reads too: + +```@example diskarray +dimb[X=1:100, Y=1:10] +``` + diff --git a/docs/src/ext_dd.md b/docs/src/ext_dd.md index db7c828a7..3d0284083 100644 --- a/docs/src/ext_dd.md +++ b/docs/src/ext_dd.md @@ -1 +1,85 @@ -# Extending DimensionalData \ No newline at end of file +# Extending DimensionalData + +Nearly everything in DimensionalData.jl is designed to be extensible. + +- `AbstractDimArray` are easily extended to custom array types. `Raster` or + `YAXArray` are examples from other packages. +- `AbstractDimStack` are easily extended to custom mixed array dataset. + `RasterStack` or `ArViZ.Dataset` are examples. +- `LookupArray` can have new types added, e.g. to `AbstractSampled` or + `AbstractCategorical`. `Rasters.Projected` is a lookup that knows + its coordinate reference system, but otherwise behaves as a regular + `Sampled` lookup. + +`dims` and `rebuild` are the key interface methods in most of these cases. + +## `dims` + +Objects extending DimensionalData.jl that have dimensions must return +a `Tuple` of constructed `Dimension`s from `dims(obj)`. + +### `Dimension` axes + +Dimensions return from `dims` should hold a `LookupArray` or in some cases +just an `AbstractArray` (like wiht `DimIndices`). When attached to +mullti-dimensional objects, lookups must be the _same length_ as the axis +of the array it represents, and `eachindex(A, i)` and `eachindex(dim)` must +return the same values. + +This means that if the array has OffsetArrays.jl axes, the array the dimension +wraps must also have OffsetArrays.jl axes. + +### `dims` keywords + +To any `dims` keyword argument that only marks the dimension name, +objects should accept any `Dimension`, `Type{<:Dimension}`, `Symbol`, +`Val{:Symbol}`, `Val{<:Type{<:Dimension}}` or regular `Integer`. +This is easier than it sounds, calling `DD.dims(objs, dims)` will +return the matching dimension and `DD.dimnum(obj, dims)` will return +the matching `Int` for any of these inputs as long as `dims(obj)` is +implemented. + + +## `rebuild` + +Rebuild methods are used to rebuild immutable objects with new field values, +in a way that is more flexible and extensible than just using ConstructionBase.jl +reconstruction. Developers can choose to ignore some of the fields passed +by `rebuild`. + +The function signature is always one of: + +```julia +rebuild(obj, args...) +rebuild(obj; kw...) +``` + +`rebuild` has keyword versions automatically generated for all objects +using [ConstructionBase.jl](https://github.com/JuliaObjects/ConstructionBase.jl). + +These will work without further work as long as your object has the fields +used by DimensionalData.jl objects. For example, `AbstractDimArray` will +receive these keywords in `rebuild`: `data`, `dims`, `refdims`, `name`, `metadata`. + +If your `AbstractDimArray` does not have all these fields, you must implement +`rebuild(x::YourDimArray; kw...)` manually. + +An argument method is also defined with the same arguments as the +keyword version. For `AbstractDimArray` it should only be used for +updating `data` and `dims`, any more that that is confusing. + +For `Dimension` and `Selector` the single argument versions are easiest to use, +as there is only one argument. + +## `rebuild(obj, ...)` argument table + +| Type | Keywords | Arguments | +|------------------|------------------------------------------------------------ |----------------------| +| AbstractDimArray | data, dims, [refdims, name, metadata] | as with kw, in order | +| AbstractDimStack | data, dims, [refdims], layerdims, [metadata, layermetadata] | as with kw, in order | +| Dimension | val | val | +| Selector | val, [atol] | val | +| LookupArray | data, [order, span, sampling, metadata] | keywords only | + +You can always add your ownd keywords to `rebuild` calls, but these will only +work on your own objects or other objects with those fields. diff --git a/docs/src/integrations.md b/docs/src/integrations.md index f5d122584..b8c9aef94 100644 --- a/docs/src/integrations.md +++ b/docs/src/integrations.md @@ -1,7 +1,83 @@ # Integrations -## Rasters.jl -## YAXArrays.jl -## ClimateBase.jl +## Spatial sciences + +### Rasters.jl + +[Raster.jl](https://rafaqz.github.io/Rasters.jl/stable) extends DD +for geospatial data manipulation, providing file load/save for +a wide range of raster data sources and common GIS tools like +polygon rasterization and masking. `Raster` types are aware +of `crs` and their `missingval` (which is often not `missing` +for performance and storage reasons). + +Rasters.jl is also the reason DimensionalData.jl exists at all! +But it always made sense to separate out spatial indexing from +GIS tools and dependencies. + +A `Raster` is a `AbstractDimArray`, a `RasterStack` is a `AbstractDimStack`, +and `Projected` and `Mapped` are `AbstractSample` lookups. + +### YAXArrays.jl + +[YAXArrays.jl](https://juliadatacubes.github.io/YAXArrays.jl/dev/) is another +spatial data package aimmed more at (very) large datasets. It's functionality +is slowly converging with Rasters.jl (both wrapping DiskArray.jl/DimensionalData.jl) +and we work closely with the developers. + +`YAXArray` is a `AbstractDimArray` and inherits its behaviours. + +### ClimateBase.jl + +[ClimateBase.jl](https://juliaclimate.github.io/ClimateBase.jl/dev/) +Extends DD with methods for analysis of climate data. + +## Statistics + ## ArviZ.jl -## JuMP.jl + +[ArviZ.jl](https://arviz-devs.github.io/ArviZ.jl/dev/) +Is a julia package for exploratory analysis of Bayesian models. + +An `ArviZ.Dataset` is an `AbstractDimStack`! + +## Optimization + +### JuMP.jl + +[JuMP.jl](https://jump.dev/) is a powerful omptimisation DSL. +It defines its own named array types but now accepts any `AbstractDimArray` +too, through a package extension. + +## Simulations + +### CryoGrid.jl + +[CryoGrid.jl](https://juliahub.com/ui/Packages/General/CryoGrid) +A Juia implementation of the CryoGrid permafrost model. + +`CryoGridOutput` uses `DimArray` for views into output data. + +### DynamicGrids.jl + +[DynamicGrids.jl](https://github.com/cesaraustralia/DynamicGrids.jl) +is a spatial simulation engine, for cellular automata and spatial process +models. + +All DynamicGrids.jl `Outputs` are `<: AbstractDimArray`, and +`AbstractDimArray` are used for auxiliary data to allow temporal +synchronisation during simulations. Notably, this all works on GPUs! + +## Analysis + +### AstroImages.jl + +[AstroImages.jl](http://juliaastro.org/dev/modules/AstroImages/) +Provides tools to load and visualise astromical images. +`AstroImage` is `<: AbstractDimArray`. + +### TimeseriesTools.jl + +[TimeseriesTools.jl](https://juliahub.com/ui/Packages/General/TimeseriesTools +Uses `DimArray` for time-series data. + diff --git a/docs/src/plots.md b/docs/src/plots.md index 67c595007..1d1955f9a 100644 --- a/docs/src/plots.md +++ b/docs/src/plots.md @@ -1 +1,42 @@ -# Plots with Makie \ No newline at end of file +# Plots.jl + +Plots.jl and Makie.jl functions mostly work out of the box on `AbstractDimArray`, +although not with the same results - they choose to follow each packages default +behaviour as much as possible. + +This will plot a line plot with 'a', 'b' and 'c' in the legend, +and values 1-10 on the labelled X axis: + +```@example Plots +using DimensionalData, Plots + +A = rand(X(1:10), Y([:a, :b, :c])) +Plots.plot(A) +``` + +Plots.jl support is deprecated, as development is moving to Makie.jl + + +# Makie.jl + +Makie.jl functions also mostly work with `AbstractDimArray` and will `permute` and +`reorder` axes into the right places, especially if `X`/`Y`/`Z`/`Ti` dimensions are used. + +In makie a `DimMatrix` will plot as a heatmap by defualt, but again it will have labels +and axes in the right places: + +```@example Makie +using DimensionalData, CairoMakie + +A = rand(X(1:10), Y([:a, :b, :c])) +Makie.plot(A; colormap=:inferno) +``` + +Other plots also work, here we ignore the axis order and instead favour the +categorical varable for the X axis: + +```@example Makie +Makie.rainclouds(A) +``` + +A lot more is planned for Make.jl plots in future! diff --git a/docs/src/reference.md b/docs/src/reference.md deleted file mode 100644 index 147b0cf1d..000000000 --- a/docs/src/reference.md +++ /dev/null @@ -1,260 +0,0 @@ - -# API Reference - -## Arrays - -```@docs -AbstractDimArray -DimArray -``` - -## Multi-array datasets - -```@docs -AbstractDimStack -DimStack -``` - -## Dimension indices generators - -```@docs -DimIndices -DimKeys -DimPoints -``` - -## Tables.jl/TableTraits.jl interface - -```@docs -DimensionalData.AbstractDimTable -DimTable -DimensionalData.DimColumn -``` - -## Common methods - -Common functions for obtaining information from objects: - -```@docs -dims -refdims -metadata -name -``` - -Utility methods for transforming DimensionalData objects: - -```@docs -set -rebuild -modify -broadcast_dims -broadcast_dims! -mergedims -unmergedims -reorder -Base.cat -Base.map -Base.copy! -Base.eachslice -``` - -Most base methods work as expected, using `Dimension` wherever a `dims` -keyword is used. They are not allspecifically documented here. - - -Shorthand constructors: - -```@docs -Base.fill -Base.rand -Base.zeros -Base.ones -``` - -# Dimensions - -Handling of Dimensions is kept in a sub-module `Dimensions`. - -```@docs -Dimensions.Dimensions -``` - -Dimensions have a type-heirarchy that organises plotting and -dimension matching. - -```@docs -Dimensions.Dimension -Dimensions.DependentDim -Dimensions.IndependentDim -Dimensions.XDim -Dimensions.YDim -Dimensions.ZDim -Dimensions.TimeDim -X -Y -Z -Ti -Dim -Coord -Dimensions.AnonDim -``` - -### Exported methods - -```@docs -hasdim -dimnum -``` - - -### Non-exported methods - -```@docs -Dimensions.lookup -Dimensions.label -DimensionalData.dim2key -DimensionalData.key2dim -DimensionalData.dims2indices -DimensionalData.selectindices -DimensionalData.format -DimensionalData.reducedims -DimensionalData.swapdims -DimensionalData.slicedims -DimensionalData.comparedims -DimensionalData.combinedims -DimensionalData.otherdims -DimensionalData.commondims -DimensionalData.sortdims -DimensionalData.basetypeof -DimensionalData.setdims -DimensionalData.dimsmatch -DimensionalData.dimstride -DimensionalData.refdims_title -DimensionalData.rebuild_from_arrays -``` - -# LookupArrays - -```@docs -LookupArrays.LookupArrays -``` - -## Selectors - -```@docs -LookupArrays.Selector -LookupArrays.IntSelector -LookupArrays.ArraySelector -At -Near -Between -Touches -Contains -Where -All -``` - -Lookup properties: - -```@docs -bounds -LookupArrays.val -``` - -```@docs -LookupArrays.LookupArray -LookupArrays.Aligned -LookupArrays.AbstractSampled -LookupArrays.Sampled -LookupArrays.AbstractCyclic -LookupArrays.Cyclic -LookupArrays.AbstractCategorical -LookupArrays.Categorical -LookupArrays.Unaligned -LookupArrays.Transformed -Dimensions.MergedLookup -LookupArrays.NoLookup -LookupArrays.AutoLookup -LookupArrays.AutoIndex -``` - -## Metadata - -```@docs -LookupArrays.AbstractMetadata -LookupArrays.Metadata -LookupArrays.NoMetadata -``` - -## LookupArray traits - -```@docs -LookupArrays.LookupArrayTrait -``` - -### Order - -```@docs -LookupArrays.Order -LookupArrays.Ordered -LookupArrays.ForwardOrdered -LookupArrays.ReverseOrdered -LookupArrays.Unordered -LookupArrays.AutoOrder -``` - -### Span - -```@docs -LookupArrays.Span -LookupArrays.Regular -LookupArrays.Irregular -LookupArrays.Explicit -LookupArrays.AutoSpan -``` - -### Sampling - -```@docs -LookupArrays.Sampling -LookupArrays.Points -LookupArrays.Intervals -``` - -### Loci - -```@docs -LookupArrays.Locus -LookupArrays.Center -LookupArrays.Start -LookupArrays.End -LookupArrays.AutoLocus -``` - -## LookupArrays methods - -```@docs -hasselection -LookupArrays.shiftlocus -LookupArrays.sampling -LookupArrays.span -LookupArrays.order -LookupArrays.index -LookupArrays.locus -LookupArrays.units -``` - -## Name - -```@docs -DimensionalData.AbstractName -DimensionalData.Name -DimensionalData.NoName -``` - -## Show methods for packages extending DimensionalData.jl - -```@docs -DimensionalData.show_after -``` diff --git a/docs/src/selectors.md b/docs/src/selectors.md index 3222cb82e..5e631904e 100644 --- a/docs/src/selectors.md +++ b/docs/src/selectors.md @@ -1,29 +1,40 @@ -# Selectors +# Selectors and LookupArrays -Indexing by value in `DimensionalData` is done with [Selectors](@ref). -IntervalSets.jl is now used for selecting ranges of values (formerly `Between`). +http://localhost:5173/DimensionalData.jl/reference#lookuparrays -| Selector | Description | -| :---------------------- | :-------------------------------------------------------------------- | -| [`At(x)`](@ref) | get the index exactly matching the passed in value(s) | -| [`Near(x)`](@ref) | get the closest index to the passed in value(s) | -| [`Contains(x)`](@ref) | get indices where the value x falls within an interval | -| [`Where(f)`](@ref) | filter the array axis by a function of the dimension index values. | -| [`Not(x)`] | get all indices _not_ selected by `x`, which can be another selector. | -| [`a..b`] | get all indices between two values, inclusively. | -| [`OpenInterval(a, b)`] | get all indices between `a` and `b`, exclusively. | -| [`Interval{A,B}(a, b)`] | get all indices between `a` and `b`, as `:closed` or `:open`. | +DimensionalData.jl [`Dimension`](@ref)s in an `AbstractDimArray` or +`AbstactDimStack` usually hold [`LookupArrays`](@ref). +These are `AbstractArray` with added features to facilitate fast and +accurate lookups of their values, using a [`Selector`](@ref) + +| Selector | Description | Indexing style | +| :---------------------- | :--------------------------------------------------------------------------- |------------------ | +| [`At(x)`](@ref) | get the index exactly matching the passed in value(s) | `Int/Vector{Int}` | +| [`Near(x)`](@ref) | get the closest index to the passed in value(s) | `Int/Vector{Int}` | +| [`Contains(x)`](@ref) | get indices where the value x falls within an interval in the lookup | `Int/Vector{Int}` | +| [`Where(f)`](@ref) | filter the array axis by a function of the dimension index values. | `Vector{Bool}` | +| [`Not(x)`] | get all indices _not_ selected by `x`, which can be another selector. | `Vector{Bool}` | +| [`a .. b`] | get all indices between two values, inclusively. | `UnitRange` | +| [`OpenInterval(a, b)`] | get all indices between `a` and `b`, exclusively. | `UnitRange` | +| [`Interval{A,B}(a, b)`] | get all indices between `a` and `b`, as `:closed` or `:open`. | `UnitRange` | +| [`Touches(a, b)`] | like `..` but includes all cells touched by the interval, not just inside it | `UnitRange` | + +Note: `At`, `Near` and `Contains` can wrap either single values or an +`AbstractArray` of values, to select one index with an `Int` or multiple +indices with a `Vector{Int}`. Selectors find indices in the `LookupArray`, for each dimension. +LookupArrays wrap other `AbstractArray` (often `AbstractRange`) but add +aditional traits to facilitate fast lookups or specifing point or interval +behviour. + +Some common `LookupArray` that are: + +| LookupArray | Description | +| :---------------------- | :----------------------------------------------------------------------------------------------------------- | +| [`Sampled(x)`](@ref) | values sampled along an axis - may be `Ordered`/`Unordered`, `Intervals`/`Points`, and `Regular`/`Irregular` | +| [`Categorical(x)`](@ref) | a categorical lookup that holds categories, and may be ordered | +| [`Cyclic(x)`](@ref) | an `AbstractSampled` lookup for cyclical values. | +| [`NoLookup(x)`](@ref) | no lookup values provided, so `Selector`s will not work. Not show in repl printing. | -## lookup -## At -## Between, .. -## Where -## Near -## Touches -## Contains -## All -## IntervalSets -## DimSelectors \ No newline at end of file diff --git a/docs/src/stacks.md b/docs/src/stacks.md index 28c2c8f07..7c521add2 100644 --- a/docs/src/stacks.md +++ b/docs/src/stacks.md @@ -1 +1,29 @@ -# Stacks \ No newline at end of file +# Stacks + +An `AbstractDimStack` represents a collection of `AbstractDimArray` +layers that share some or all dimensions. For any two layers, a dimension +of the same name must have the identical lookup - in fact only one is stored +for all layers to enforce this consistency. + +The behaviour is somewhere ebetween a `NamedTuple` and an `AbstractArray` + +Indexing layers by name with `stack[:layer]` or `stack.layer` works as with a +`NamedTuple`, and returns an `AbstractDimArray`. +Indexing with `Dimensions`, `Selectors` works as with an `AbstractDimArray`, +except it indexes for all layers at the same time, returning either a new +small `AbstractDimStack` or a scalar value, if all layers are scalars. + +Base functions like `mean`, `maximum`, `reverse` are applied to all layers of the stack. + +`broadcast_dims` broadcasts functions over any mix of `AbstractDimStack` and +`AbstractDimArray` returning a new `AbstractDimStack` with layers the size of +the largest layer in the broadcast. This will work even if dimension permutation +does not match in the objects. + +# Performance + +Indexing stack is fast - indexing a single value return a `NamedTuple` from all layers +usingally, measures in nanoseconds. There are some compilation overheads to this +though, and stacks with very many layers can take a long time to compile. + +Hopefully compiler fixes planned for Julia v1.11 will improve this. diff --git a/docs/src/tables.md b/docs/src/tables.md index afb1b4c41..6ebeba319 100644 --- a/docs/src/tables.md +++ b/docs/src/tables.md @@ -1 +1,18 @@ -# Tables +# Tables and DataFrames + +Tables.jl provides an ecosystem-wide interface to tabular data in julia, +giving interop with DataFrames.jl, CSV.jl and hundreds of other packages +that implement the standard. + +DimensionalData.jl implements the Tables.jl interface for +`AbstractDimArray` and `AbstractDimStack`. `DimStack` layers +are unrolled so they are all the same size, and dimensions similarly loop +over array strides to match the length of the largest layer. + +Columns are given the `name` or the array or the stack layer key. +`Dimension` columns use the `Symbol` version (the result of `DD.dim2key(dimension)`). + +Looping of unevenly size dimensions and layers is done _lazily_, +and does not allocate unless collected. + + diff --git a/package-lock.json b/package-lock.json index 727bde254..d033d9b14 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,5 +1,5 @@ { - "name": "DimensionalData.jl", + "name": "DimensionalData", "lockfileVersion": 3, "requires": true, "packages": { diff --git a/src/Dimensions/primitives.jl b/src/Dimensions/primitives.jl index 7d6253bd5..aecd74f2a 100644 --- a/src/Dimensions/primitives.jl +++ b/src/Dimensions/primitives.jl @@ -635,6 +635,14 @@ although it will be for `Array`. @inline dimstride(dims::DimTuple, n::Int) = prod(map(length, dims)[1:n-1]) +""" + basedims(ds::Tuple) + basedims(d::Union{Dimension,Symbol,Type}) + +Returns `basetypeof(d)()` or a `Tuple` of called on a `Tuple`. + +See [`basetypeof`](@ref) +""" @inline basedims(x) = basedims(dims(x)) @inline basedims(ds::Tuple) = map(basedims, ds) @inline basedims(d::Dimension) = basetypeof(d)() diff --git a/src/LookupArrays/lookup_traits.jl b/src/LookupArrays/lookup_traits.jl index a2f7cf1f2..2f2e2b55e 100644 --- a/src/LookupArrays/lookup_traits.jl +++ b/src/LookupArrays/lookup_traits.jl @@ -169,12 +169,6 @@ end Intervals() = Intervals(AutoLocus()) locus(sampling::Intervals) = sampling.locus - -""" - rebuild(::Intervals, locus::Locus) => Intervals - -Rebuild `Intervals` with a new Locus. -""" rebuild(::Intervals, locus) = Intervals(locus) """ @@ -218,7 +212,6 @@ val(span::Regular) = span.step Base.step(span::Regular) = span.step Base.:(==)(l1::Regular, l2::Regular) = val(l1) == val(l2) - """ Irregular <: Span diff --git a/src/LookupArrays/selector.jl b/src/LookupArrays/selector.jl index 9d8d03ae8..7e642bb4f 100644 --- a/src/LookupArrays/selector.jl +++ b/src/LookupArrays/selector.jl @@ -35,6 +35,8 @@ Selectors provided in DimensionalData are: abstract type Selector{T} end val(sel::Selector) = sel.val +rebuild(sel::Selector, val) = basetypeof(sel)(val) + Base.parent(sel::Selector) = sel.val abstract type Selector{T} end @@ -111,6 +113,7 @@ struct At{T,A,R} <: IntSelector{T} end At(val; atol=nothing, rtol=nothing) = At(val, atol, rtol) +rebvuild(sel::At, val) = At(val, sel.atol, sel.rtol) atol(sel::At) = sel.atol rtol(sel::At) = sel.rtol @@ -120,10 +123,10 @@ struct _False end selectindices(l::LookupArray, sel::At; kw...) = at(l, sel; kw...) selectindices(l::LookupArray, sel::At{<:AbstractVector}) = _selectvec(l, sel) -_selectvec(l, sel) = [selectindices(l, rebuild(sel; val=v)) for v in val(sel)] +_selectvec(l, sel) = [selectindices(l, rebuild(sel, v)) for v in val(sel)] function at(lookup::AbstractCyclic{Cycling}, sel::At; kw...) - cycled_sel = rebuild(sel; val=cycle_val(lookup, val(sel))) + cycled_sel = rebuild(sel, cycle_val(lookup, val(sel))) return at(no_cycling(lookup), cycled_sel; kw...) end function at(lookup::NoLookup, sel::At; kw...) @@ -231,7 +234,7 @@ selectindices(l::LookupArray, sel::Near) = near(l, sel) selectindices(l::LookupArray, sel::Near{<:AbstractVector}) = _selectvec(l, sel) function near(lookup::AbstractCyclic{Cycling}, sel::Near) - cycled_sel = rebuild(sel; val=cycle_val(lookup, val(sel))) + cycled_sel = rebuild(sel, cycle_val(lookup, val(sel))) near(no_cycling(lookup), cycled_sel) end near(lookup::NoLookup, sel::Near{<:Real}) = max(1, min(round(Int, val(sel)), lastindex(lookup))) @@ -320,7 +323,7 @@ selectindices(l::LookupArray, sel::Contains; kw...) = contains(l, sel) selectindices(l::LookupArray, sel::Contains{<:AbstractVector}) = _selectvec(l, sel) function contains(lookup::AbstractCyclic{Cycling}, sel::Contains; kw...) - cycled_sel = rebuild(sel; val=cycle_val(lookup, val(sel))) + cycled_sel = rebuild(sel, cycle_val(lookup, val(sel))) return contains(no_cycling(lookup), cycled_sel; kw...) end function contains(l::NoLookup, sel::Contains; kw...) @@ -486,7 +489,7 @@ selectindices(l::LookupArray, sel::Union{Between{<:Tuple},Interval}) = between(l function selectindices(lookup::LookupArray, sel::Between{<:AbstractVector}) inds = Int[] for v in val(sel) - append!(inds, selectindices(lookup, rebuild(sel; val=v))) + append!(inds, selectindices(lookup, rebuild(sel, v))) end end @@ -738,7 +741,7 @@ selectindices(l::LookupArray, sel::Touches) = touches(l, sel) function selectindices(lookup::LookupArray, sel::Touches{<:AbstractVector}) inds = Int[] for v in val(sel) - append!(inds, selectindices(lookup, rebuild(sel; val=v))) + append!(inds, selectindices(lookup, rebuild(sel, v))) end end diff --git a/src/array/methods.jl b/src/array/methods.jl index dc7d9ee0c..4cbb98305 100644 --- a/src/array/methods.jl +++ b/src/array/methods.jl @@ -89,7 +89,7 @@ function Base.dropdims(A::AbstractDimArray; dims) rebuildsliced(A, data, _dropinds(A, dims)) end -@inline _dropinds(A, dims::DimTuple) = dims2indices(A, map(d -> rebuild(d, 1), dims)) +@inline _dropinds(A, dims::Tuple) = dims2indices(A, map(d -> rebuild(d, 1), dims)) @inline _dropinds(A, dim::Dimension) = dims2indices(A, rebuild(dim, 1)) diff --git a/src/array/show.jl b/src/array/show.jl index 6211c1053..b22a2774c 100644 --- a/src/array/show.jl +++ b/src/array/show.jl @@ -222,11 +222,18 @@ function _print_matrix(io::IO, A::AbstractArray{<:Any,1}, lookups::Tuple) itop = f1:l1 ibottom = 1:0 end - lu = lookups[1] - labels = vcat(map(show1, parent(lu)[itop]), map(show1, parent(lu))[ibottom]) - vals = map(showdefault, vcat(A[itop], A[ibottom])) - A_dims = hcat(labels, vals) - Base.print_matrix(io, A_dims) + top = Array{eltype(A)}(undef, length(itop)) + copyto!(top, CartesianIndices(top), A, CartesianIndices(itop)) + bottom = Array{eltype(A)}(undef, length(ibottom)) + copyto!(bottom, CartesianIndices(bottom), A, CartesianIndices(ibottom)) + vals = vcat(A[itop], A[ibottom]) + lu = only(lookups) + if lu isa NoLookup + Base.print_matrix(io, vals) + else + labels = vcat(map(show1, parent(lu)[itop]), map(show1, parent(lu))[ibottom]) + Base.print_matrix(io, hcat(labels, vals)) + end return nothing end _print_matrix(io::IO, A::AbstractDimArray, lookups::Tuple) = _print_matrix(io, parent(A), lookups) @@ -249,15 +256,21 @@ function _print_matrix(io::IO, A::AbstractArray, lookups::Tuple) iright = f2:f2-1 end - topleft = map(showdefault, A[itop, ileft]) - bottomleft = A[ibottom, ileft] + # A bit convoluted so it plays nice with GPU arrays + topleft = Matrix{eltype(A)}(undef, map(length, (itop, ileft))) + copyto!(topleft, CartesianIndices(topleft), A, CartesianIndices((itop, ileft))) + bottomleft= Matrix{eltype(A)}(undef, map(length, (ibottom, ileft))) + copyto!(bottomleft, CartesianIndices(bottomleft), A, CartesianIndices((ibottom, ileft))) if !(lu1 isa NoLookup) topleft = hcat(map(show1, parent(lu1)[itop]), topleft) bottomleft = hcat(map(show1, parent(lu1)[ibottom]), bottomleft) end - - leftblock = vcat(parent(topleft), parent(bottomleft)) - rightblock = vcat(A[itop, iright], A[ibottom, iright]) + leftblock = vcat(topleft, bottomleft) + topright = Matrix{eltype(A)}(undef, map(length, (itop, iright))) + copyto!(topright, CartesianIndices(topright), A, CartesianIndices((itop, iright))) + bottomright= Matrix{eltype(A)}(undef, map(length, (ibottom, iright))) + copyto!(bottomright, CartesianIndices(bottomright), A, CartesianIndices((ibottom, iright))) + rightblock = vcat(topright, bottomright) bottomblock = hcat(leftblock, rightblock) A_dims = if lu2 isa NoLookup @@ -271,6 +284,7 @@ function _print_matrix(io::IO, A::AbstractArray, lookups::Tuple) end |> permutedims vcat(toprow, map(showdefault, bottomblock)) end + Base.print_matrix(io, A_dims) return nothing end diff --git a/src/plotrecipes.jl b/src/plotrecipes.jl index 44ae7c53b..c7a270696 100644 --- a/src/plotrecipes.jl +++ b/src/plotrecipes.jl @@ -58,7 +58,7 @@ end :xguide --> label(ind) :yguide --> label(A) :legendtitle --> label(dep) - :label --> permutedims(val(dep)) + :label --> string.(permutedims(val(dep))) :tickfontalign --> :left _xticks!(plotattributes, s, ind) _withaxes(ind, A) @@ -75,7 +75,7 @@ end ind, dep = dims(A) :xguide --> label(A) :legendtitle --> label(dep) - :label --> permutedims(index(dep)) + :label --> string.(permutedims(index(dep))) _withaxes(ind, A) end @@ -91,7 +91,7 @@ end :xguide --> label(ind) :yguide --> label(A) :legendtitle --> label(ind) - :label --> permutedims(index(ind)) + :label --> string.(permutedims(index(ind))) _xticks!(plotattributes, s, ind) parent(A) end @@ -102,7 +102,7 @@ end :xguide --> label(ind) :yguide --> label(A) :legendtitle --> label(ind) - :label --> permutedims(index(ind)) + :label --> string.(permutedims(index(ind))) _xticks!(plotattributes, s, ind) parent(A) end diff --git a/test/plotrecipes.jl b/test/plotrecipes.jl index b77853a78..396886b12 100644 --- a/test/plotrecipes.jl +++ b/test/plotrecipes.jl @@ -10,12 +10,13 @@ ref = (Ti(Sampled(1:1; order=ForwardOrdered(), span=Regular(Day(1)), sampling=Po da1_regular = DimArray(A1, X(1:50:1000); name=:Normal, refdims=ref) da1_noindex = DimArray(A1, X(); name=:Normal, refdims=ref) da1_categorical = DimArray(A1, X('A':'T'); name=:Normal, refdims=ref) +da1_categorical_symbol = DimArray(A1, X(Symbol.('A':'T')); name=:Normal, refdims=ref) da1_z = DimArray(A1, Z(1:50:1000); name=:Normal, refdims=ref) # For manual testing -da1 = da1_z +da1 = da1_categorical -for da in (da1_regular, da1_noindex, da1_categorical, da1_z) +for da in (da1_regular, da1_noindex, da1_categorical, da1_categorical_symbol, da1_z) for da1 in (da, reverse(da)) # Plots plot(da1) From 9c6f0a55c8b0d8557e1f854ad99b423291f98d7e Mon Sep 17 00:00:00 2001 From: Rafael Schouten Date: Sat, 17 Feb 2024 00:23:00 +0100 Subject: [PATCH 031/108] Add a `groupby` function (#591) * add groupby * ambiguity fixes * update test * fix groupby more --- Project.toml | 2 + docs/Project.toml | 2 + docs/crash/course/groupby.jl | 115 +++++++++ src/DimensionalData.jl | 15 +- src/Dimensions/Dimensions.jl | 2 +- src/Dimensions/format.jl | 10 +- src/Dimensions/primitives.jl | 4 +- src/Dimensions/show.jl | 21 +- src/LookupArrays/lookup_arrays.jl | 11 +- src/LookupArrays/metadata.jl | 2 +- src/LookupArrays/selector.jl | 77 +++++- src/LookupArrays/set.jl | 2 +- src/LookupArrays/utils.jl | 6 + src/array/show.jl | 52 ++-- src/dimindices.jl | 24 ++ src/groupby.jl | 415 ++++++++++++++++++++++++++++++ src/stack/show.jl | 2 - src/stack/stack.jl | 2 + test/groupby.jl | 72 ++++++ test/lookup.jl | 2 +- test/runtests.jl | 1 + 21 files changed, 786 insertions(+), 53 deletions(-) create mode 100644 docs/crash/course/groupby.jl create mode 100644 src/groupby.jl create mode 100644 test/groupby.jl diff --git a/Project.toml b/Project.toml index b8b569b4f..d65c88542 100644 --- a/Project.toml +++ b/Project.toml @@ -7,6 +7,7 @@ version = "0.25.8" Adapt = "79e6a3ab-5dfb-504d-930d-738a2a938a0e" ArrayInterface = "4fba245c-0d91-5ea0-9b3e-6abc04ee57a9" ConstructionBase = "187b0558-2788-49d3-abe0-74a17ed4e7c9" +DataAPI = "9a962f9c-6df0-11e9-0e5d-c546b8b5ee8a" Dates = "ade2ca70-3891-5945-98fb-dc099432e06a" Extents = "411431e0-e8b7-467b-b5e0-f676ba4f2910" Interfaces = "85a1e053-f937-4924-92a5-1367d23b7b87" @@ -38,6 +39,7 @@ ColorTypes = "0.11" Combinatorics = "1" ConstructionBase = "1" CoordinateTransformations = "0.6" +DataAPI = "1" DataFrames = "1" Dates = "1" Distributions = "0.25" diff --git a/docs/Project.toml b/docs/Project.toml index be71d0cfd..04c18b73f 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -6,3 +6,5 @@ Dates = "ade2ca70-3891-5945-98fb-dc099432e06a" DimensionalData = "0703355e-b756-11e9-17c0-8b28908087d0" Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" DocumenterVitepress = "4710194d-e776-4893-9690-8d956a29c365" +Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" +Unitful = "1986cc42-f94f-5a68-af5c-568840ba703d" diff --git a/docs/crash/course/groupby.jl b/docs/crash/course/groupby.jl new file mode 100644 index 000000000..99dbcbf92 --- /dev/null +++ b/docs/crash/course/groupby.jl @@ -0,0 +1,115 @@ +using DimensionalData +using Dates +using Statistics +const DD = DimensionalData + +# # Basics: DateTime operations we can use for grouping + +# First lets look at the kind of functions that can be used to group `DateTime`. +# Other types will follow the same principles, but are usually simpler. + +# Create a demo `DateTime` range +tempo = range(DateTime(2000), step=Hour(1), length=365*24*2) + +# Now lets see how some common functions work. + +# The `hour` function will transform values to hour of the day - the integers `0:23` + +# ### hour +hour.(tempo) + +# These do similar things with other time periods + +# ### dayofweek +dayofweek.(tempo) + +# ### month +month.(tempo) + +# ### dayofyear +dayofyear.(tempo) + +# ## Tuple grouping + +# Some functions return a tuple - we can also use tuples for grouping. +# They are sorted by the left to right values. + +# ### yearmonth +yearmonth.(tempo) + +# We can creat our own anonymous function that return tuples +yearday(x) = year(x), dayofyear(x) + +yearhour(x) = year(x), hour(x) + +# And you can probably guess what they do: +yearday.(tempo) + +# All of these functions can be used in `groupby` on `DateTime` objects. + +# # Practical example: grouping by season + +# ### TODOS: We will need several functions. + +# # groupby operations +# Here we use the same time functions from above + +ds = rand(X(1:0.01:2), Ti(tempo)) + +# ## select by month, days, years and seasons +# ### TODO, how do we select month 1 or 2, and even a group of them, i.e. [1,3,5]? Same for days, years and seasons. +mean.(groupby(ds, Ti=>Bins(month, 1:2))) +mean.(groupby(ds, Ti=>Bins(month, [1, 3, 5]))) +mean.(groupby(ds, Ti => season(; start=December))) +mean.(groupby(ds, Ti => Bins(dayofyear, intervals(1:8:370)))) +mean.(groupby(ds, Ti => Bins(yearday, [[1,2,3], [4,5,6]], labels=x -> join(string.(x), ',')))) +mean.(groupby(ds, Ti => week)) +mean.(groupby(ds, Ti => hours(12; start=6, labels=x -> 6 in x ? :night : :day))) +mean.(groupby(ds, Ti => dims(ds, Ti))) + +# ### TODO, we need a new function that can return DJF (Dec-Jan-Feb), MAM (Mar-Apr-May)... etc. +# THIS IS HARD. We need a succinct way to select around the end-start of the year. + +# is combining month from different years +mean.(groupby(ds, Ti=>month)) + +# Use three-month bins. The 13 is the open side of the last interval. +mean.(groupby(ds, Ti=>Bins(yearmonth, intervals(1:3:12)))) + +mean.(groupby(ds, Ti=>Bins(month, 4))) # is combining month from different years + +# +mean.(groupby(ds, Ti=>year)) + +# +mean.(groupby(ds, Ti=>yearmonth)) + +# +mean.(groupby(ds, Ti=>hour)) + +# +mean.(groupby(ds, Ti=>Dates.hour12)) + +# ### TODO. How do could we incorporate resample? Let's say if we have hour resolution I want to resample every 3,6,12.. hours? +mean.(groupby(ds, Ti=>Bins(yearhour, intervals(1:3:24)))) # it will combine the same day from different year. + +mean.(groupby(ds, Ti=>dayofyear)) # it will combine the same day from different year. + +# +mean.(groupby(ds, Ti=>yearmonthday)) # this does the a daily mean aggregation. + +# +mean.(groupby(ds, Ti=>yearmonth)) # this does a monthly mean aggregation + +# +mean.(groupby(ds, Ti=>yearday)) # this does a daily mean aggregation + +mean.(groupby(ds, Ti=>Bins(yearhour, 12))) # this does a daily mean aggregation + +# ### TODO. Similar to the hourly resample, how do we do it for more than 1 day, let's say 8daily? +mean.(groupby(ds, Ti=>Bins(dayofyear, map(x -> x:x+7, 1:8:370)))) + +# ### TODO: Group by Dims. This should include the rasters input sampling. +mean.(groupby(ds, dims(ds, Ti))) + +# ## Apply custom function (i.e. normalization) to grouped output. diff --git a/src/DimensionalData.jl b/src/DimensionalData.jl index d069dd682..a01d1aadc 100644 --- a/src/DimensionalData.jl +++ b/src/DimensionalData.jl @@ -17,14 +17,16 @@ using Dates, using Base.Broadcast: Broadcasted, BroadcastStyle, DefaultArrayStyle, AbstractArrayStyle, Unknown -using Base: tail, OneTo, @propagate_inbounds +using Base: tail, OneTo, Callable, @propagate_inbounds # Ecosystem import Adapt, ArrayInterface, ConstructionBase, + DataAPI, Extents, Interfaces, + IntervalSets, InvertedIndices, IteratorInterfaceExtensions, RecipesBase, @@ -34,23 +36,27 @@ import Adapt, using RecipesBase: @recipe +# using IntervalSets: .., Interval + include("Dimensions/Dimensions.jl") using .Dimensions using .Dimensions.LookupArrays using .Dimensions: StandardIndices, DimOrDimType, DimTuple, DimTupleOrEmpty, DimType, AllDims import .LookupArrays: metadata, set, _set, rebuild, basetypeof, - order, span, sampling, locus, val, index, bounds, intervalbounds, + order, span, sampling, locus, val, index, bounds, intervalbounds, hasselection, units, SelectorOrInterval import .Dimensions: dims, refdims, name, lookup, dimstride, kwdims, hasdim, label, _astuple +import DataAPI.groupby + export LookupArrays, Dimensions # Dimension export X, Y, Z, Ti, Dim, Coord # Selector -export At, Between, Touches, Contains, Near, Where, All, .., Not +export At, Between, Touches, Contains, Near, Where, All, .., Not, Bins export AbstractDimArray, DimArray @@ -71,6 +77,8 @@ export dimnum, hasdim, hasselection, otherdims # utils export set, rebuild, reorder, modify, broadcast_dims, broadcast_dims!, mergedims, unmergedims +export groupby, season, months, hours, yeardays, monthdays, intervals, ranges + const DD = DimensionalData # Common @@ -96,6 +104,7 @@ include("tables.jl") include("plotrecipes.jl") include("utils.jl") include("set.jl") +include("groupby.jl") include("precompile.jl") include("interface_tests.jl") diff --git a/src/Dimensions/Dimensions.jl b/src/Dimensions/Dimensions.jl index 99387aaf4..92d4e33e9 100644 --- a/src/Dimensions/Dimensions.jl +++ b/src/Dimensions/Dimensions.jl @@ -13,7 +13,7 @@ using DimensionalData.Dimensions """ module Dimensions -import Adapt, ConstructionBase, Extents +import Adapt, ConstructionBase, Extents, IntervalSets using Dates include("../LookupArrays/LookupArrays.jl") diff --git a/src/Dimensions/format.jl b/src/Dimensions/format.jl index 7a12568a4..11ebea1a6 100644 --- a/src/Dimensions/format.jl +++ b/src/Dimensions/format.jl @@ -26,11 +26,12 @@ function format(dims::Tuple{<:Pair,Vararg{Pair}}, A::AbstractArray) end return format(dims, A) end -format(dims::Tuple{Vararg{Any,N}}, A::AbstractArray{<:Any,N}) where N = - format(dims, axes(A)) +format(dims::Tuple{Vararg{Any,N}}, A::AbstractArray{<:Any,N}) where N = format(dims, axes(A)) @noinline format(dims::Tuple{Vararg{Any,M}}, A::AbstractArray{<:Any,N}) where {N,M} = throw(DimensionMismatch("Array A has $N axes, while the number of dims is $M: $(map(basetypeof, dims))")) format(dims::Tuple{Vararg{Any,N}}, axes::Tuple{Vararg{Any,N}}) where N = map(_format, dims, axes) +format(d::Dimension{<:AbstractArray}) = _format(d, axes(val(d), 1)) +format(d::Dimension, axis::AbstractRange) = _format(d, axis) _format(dimname::Symbol, axis::AbstractRange) = Dim{dimname}(NoLookup(axes(axis, 1))) _format(::Type{D}, axis::AbstractRange) where D<:Dimension = D(NoLookup(axes(axis, 1))) @@ -110,8 +111,13 @@ _format(span::Irregular{<:Tuple}, D, index) = span _format(span::Explicit, D, index) = span # Sampling _format(sampling::AutoSampling, span::Span, D::Type, index) = 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)) +# 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) = diff --git a/src/Dimensions/primitives.jl b/src/Dimensions/primitives.jl index aecd74f2a..2a5d0895c 100644 --- a/src/Dimensions/primitives.jl +++ b/src/Dimensions/primitives.jl @@ -203,7 +203,7 @@ julia> dimnum(A, Y) ``` """ @inline function dimnum(x, q1, query...) - all(hasdim(x, q1, query...)) || _extradimserror() + all(hasdim(x, q1, query...)) || _extradimserror(otherdims(x, (q1, query))) _call_primitive(_dimnum, MaybeFirst(), x, q1, query...) end @inline dimnum(x, query::Function) = @@ -757,5 +757,5 @@ _typemsg(a, b) = "Lookups do not all have the same type: $(order(a)), $(order(b) @noinline _valerror(a, b) = throw(DimensionMismatch(_valmsg(a, b))) @noinline _ordererror(a, b) = throw(DimensionMismatch(_ordermsg(a, b))) @noinline _metadataerror(a, b) = throw(DimensionMismatch(_metadatamsg(a, b))) -@noinline _extradimserror(args...) = throw(ArgumentError(_extradimsmsg(args))) +@noinline _extradimserror(args) = throw(ArgumentError(_extradimsmsg(args))) @noinline _dimsnotdefinederror() = throw(ArgumentError("Object does not define a `dims` method")) diff --git a/src/Dimensions/show.jl b/src/Dimensions/show.jl index 73cfa64ee..feff6aee0 100644 --- a/src/Dimensions/show.jl +++ b/src/Dimensions/show.jl @@ -13,25 +13,27 @@ function dimcolors(i) colors[c] end -function Base.show(io::IO, mime::MIME"text/plain", dims::DimTuple) +function show_dims(io::IO, mime::MIME"text/plain", dims::DimTuple; + colors=map(x -> get(io, :dimcolo, dimcolors(x)), ntuple(identity, length(dims))) +) ctx = IOContext(io, :compact => true) inset = get(io, :inset, "") print(io, inset) if all(map(d -> !(parent(d) isa AbstractArray) || (parent(d) isa NoLookup), dims)) - dc = get(ctx, :dimcolor, dimcolors(1)) + dc = colors[1] printstyled(io, dimsymbols(1), ' '; color=dc) show(IOContext(ctx, :dimcolor => dc, :dimname_len => 0), mime, first(dims)) foreach(enumerate(Base.tail(dims))) do (i, d) n = i + 1 print(io, ", ") - dc = get(ctx, :dimcolor, dimcolors(n)) + dc = colors[n] printstyled(io, dimsymbols(n), ' '; color=dc) show(IOContext(ctx, :dimcolor => dc, :dimname_len => 0), mime, d) end return 0 else # Dims get a line each lines = 3 - dc = get(ctx, :dimcolor, dimcolors(1)) + dc = colors[1] printstyled(io, dimsymbols(1), ' '; color=dc) maxname = maximum(length ∘ string ∘ dim2key, dims) dim_ctx = IOContext(ctx, :dimcolor => dc, :dimname_len=> maxname) @@ -41,7 +43,7 @@ function Base.show(io::IO, mime::MIME"text/plain", dims::DimTuple) lines += 1 s = dimsymbols(n) print(io, ",\n", inset) - dc = get(ctx, :dimcolor, dimcolors(n)) + dc = colors[n] printstyled(io, s, ' '; color=dc) dim_ctx = IOContext(ctx, :dimcolor => dc, :dimname_len => maxname) show(dim_ctx, mime, d) @@ -49,6 +51,9 @@ function Base.show(io::IO, mime::MIME"text/plain", dims::DimTuple) return lines end end + +Base.show(io::IO, mime::MIME"text/plain", dims::DimTuple) = + show_dims(io, mime, dims) function Base.show(io::IO, mime::MIME"text/plain", dim::Dimension) get(io, :compact, false) && return show_compact(io, mime, dim) print_dimname(io, dim) @@ -77,14 +82,14 @@ end dimcolor(io) = get(io, :dimcolor, dimcolors(1)) # print dims with description string and inset -function print_dims(io::IO, mime, dims::Tuple{}) +function print_dims(io::IO, mime, dims::Tuple{}; kw...) @nospecialize io mime dims print(io, ": ") return 0 end -function print_dims(io::IO, mime, dims::Tuple) +function print_dims(io::IO, mime, dims::Tuple; kw...) ctx = IOContext(io, :inset => " ") - return show(ctx, mime, dims) + return show_dims(ctx, mime, dims; kw...) end # print refdims with description string and inset function print_refdims(io::IO, mime, refdims::Tuple) diff --git a/src/LookupArrays/lookup_arrays.jl b/src/LookupArrays/lookup_arrays.jl index 136caa827..e2fb5a462 100644 --- a/src/LookupArrays/lookup_arrays.jl +++ b/src/LookupArrays/lookup_arrays.jl @@ -46,11 +46,11 @@ ordered_lastindex(o::ForwardOrdered, l::LookupArray) = lastindex(parent(l)) ordered_lastindex(o::ReverseOrdered, l::LookupArray) = firstindex(parent(l)) ordered_lastindex(o::Unordered, l::LookupArray) = lastindex(parent(l)) -function Base.searchsortedfirst(lookup::LookupArray, val; lt=<) - searchsortedfirst(parent(lookup), unwrap(val); order=ordering(order(lookup)), lt=lt) +function Base.searchsortedfirst(lookup::LookupArray, val; lt=<, kw...) + searchsortedfirst(parent(lookup), unwrap(val); order=ordering(order(lookup)), lt=lt, kw...) end -function Base.searchsortedlast(lookup::LookupArray, val; lt=<) - searchsortedlast(parent(lookup), unwrap(val); order=ordering(order(lookup)), lt=lt) +function Base.searchsortedlast(lookup::LookupArray, val; lt=<, kw...) + searchsortedlast(parent(lookup), unwrap(val); order=ordering(order(lookup)), lt=lt, kw...) end function Adapt.adapt_structure(to, l::LookupArray) @@ -593,7 +593,8 @@ Base.:(==)(l1::Transformed, l2::Transformed) = typeof(l1) == typeof(l2) && f(l1) intervalbounds(l::LookupArray, args...) = _intervalbounds_no_interval_error() intervalbounds(l::AbstractSampled, args...) = intervalbounds(span(l), sampling(l), l, args...) -intervalbounds(span::Span, ::Points, l::LookupArray, args...) = _intervalbounds_no_interval_error() +intervalbounds(span::Span, ::Points, ls::LookupArray) = map(l -> (l, l), ls) +intervalbounds(span::Span, ::Points, ls::LookupArray, i::Int) = ls[i], ls[i] intervalbounds(span::Span, sampling::Intervals, l::LookupArray, i::Int) = intervalbounds(order(l), locus(sampling), span, l, i) function intervalbounds(order::ForwardOrdered, locus::Start, span::Span, l::LookupArray, i::Int) diff --git a/src/LookupArrays/metadata.jl b/src/LookupArrays/metadata.jl index 029713285..410ca4a27 100644 --- a/src/LookupArrays/metadata.jl +++ b/src/LookupArrays/metadata.jl @@ -13,7 +13,7 @@ objects to [`set`](@ref) without ambiguity about where to put them. """ abstract type AbstractMetadata{X,T} end -const _MetadataContents =Union{AbstractDict,NamedTuple} +const _MetadataContents = Union{AbstractDict,NamedTuple} const AllMetadata = Union{AbstractMetadata,AbstractDict} Base.get(m::AbstractMetadata, args...) = get(val(m), args...) diff --git a/src/LookupArrays/selector.jl b/src/LookupArrays/selector.jl index 7e642bb4f..4ce5ed72c 100644 --- a/src/LookupArrays/selector.jl +++ b/src/LookupArrays/selector.jl @@ -159,6 +159,15 @@ function at( return _selnotfound_or_nothing(err, lookup, selval) end end +function at( + ::Ordered, ::Span, lookup::LookupArray{<:IntervalSets.Interval}, selval, atol, rtol::Nothing; + err=_True() +) + x = unwrap(selval) + i = searchsortedlast(lookup, x; lt=(a, b) -> a.left < b.left) + lookup[i].left == x.left && lookup[i].right == x.right || _selvalnotfound(lookup, selval) + return i +end function at( ::Ordered, ::Span, lookup::LookupArray{<:Union{Number,Dates.TimeType,AbstractString}}, selval, atol, rtol::Nothing; err=_True() @@ -246,7 +255,7 @@ end near(order::Order, ::NoSampling, lookup::LookupArray, sel::Near) = at(lookup, At(val(sel))) function near(order::Ordered, ::Union{Intervals,Points}, lookup::LookupArray, sel::Near) # Unwrap the selector value and adjust it for - # inderval locus if neccessary + # interval locus if neccessary v = unwrap(val(sel)) if v isa Dates.TimeType v = eltype(lookup)(v) @@ -257,16 +266,14 @@ function near(order::Ordered, ::Union{Intervals,Points}, lookup::LookupArray, se # Search for the value found_i = _inbounds(searchfunc(lookup, v_adj), lookup) - # Check if this is the lowest possible value allready, - # and return if so + # Check if this is the lowest possible value allready, and return if so if order isa ForwardOrdered found_i <= firstindex(lookup) && return found_i elseif order isa ReverseOrdered found_i >= lastindex(lookup) && return found_i end - # Find which index is nearest, - # the found index or previous index + # Find which index is nearest, the found index or previous index prev_i = found_i - _ordscalar(order) dist_to_prev = abs(v_adj - lookup[prev_i]) dist_to_found = abs(v_adj - lookup[found_i]) @@ -277,8 +284,11 @@ function near(order::Ordered, ::Union{Intervals,Points}, lookup::LookupArray, se return closest_i end +function near(order::Ordered, ::Intervals, lookup::LookupArray{<:IntervalSets.Interval}, sel::Near) + throw(ArgumentError("`Near` is not yet implemented for lookups of `IntervalSets.Interval`")) +end function near(::Unordered, ::Union{Intervals,Points}, lookup::LookupArray, sel::Near) - throw(ArgumentError("`Near` has no meaning in an `Unordered` `Sampled` index")) + throw(ArgumentError("`Near` has no meaning in an `Unordered` lookup")) end _locus_adjust(locus::Center, v, lookup) = v @@ -340,20 +350,56 @@ end function contains(::Points, l::LookupArray, sel::Contains; kw...) at(l, At(val(sel)); kw...) end +function contains(::Points, l::LookupArray{<:AbstractArray}, sel::Contains; kw...) + x = unwrap(val(sel)) + i = searchsortedlast(l, x; by=_by) + i >= firstindex(l) && x in l[i] || _selvalnotfound(l, val(sel)) + return i +end +function contains( + ::Points, l::LookupArray{<:AbstractArray}, sel::Contains{<:AbstractArray}; + kw... +) + at(l, At(val(sel)); kw...) +end # Intervals ----------------------------------- function contains(sampling::Intervals, l::LookupArray, sel::Contains; err=_True()) _locus_checkbounds(locus(l), l, sel) || return _selector_error_or_nothing(err, l, sel) contains(order(l), span(l), sampling, locus(l), l, sel; err) end +function contains( + sampling::Intervals, l::LookupArray{<:IntervalSets.Interval}, sel::Contains; + err=_True() +) + v = val(sel) + interval_sel = Contains(Interval{:closed,:open}(v, v)) + contains(sampling, l, interval_sel; err) +end +function contains( + ::Intervals, + l::LookupArray{<:IntervalSets.Interval}, + sel::Contains{<:IntervalSets.Interval}; + err=_True() +) + v = val(sel) + i = searchsortedlast(l, v; by=_by) + if _in(v, l[i]) + return i + else + return _notcontained_or_nothing(err, v) + end +end # Regular Intervals --------------------------- -function contains(o::Ordered, span::Regular, ::Intervals, locus::Locus, l::LookupArray, sel::Contains; +function contains( + o::Ordered, span::Regular, ::Intervals, locus::Locus, l::LookupArray, sel::Contains; err=_True() ) v = val(sel) i = _searchfunc(locus, o)(l, v) return check_regular_contains(span, locus, l, v, i, err) end -function contains(o::Ordered, span::Regular, ::Intervals, locus::Center, l::LookupArray, sel::Contains; +function contains( + o::Ordered, span::Regular, ::Intervals, locus::Center, l::LookupArray, sel::Contains; err=_True() ) v = val(sel) + abs(val(span)) / 2 @@ -1034,6 +1080,21 @@ _searchfunc(::_Lower, ::ReverseOrdered) = searchsortedlast _searchfunc(::_Upper, ::ForwardOrdered) = searchsortedlast _searchfunc(::_Upper, ::ReverseOrdered) = searchsortedfirst +# by helpers so sort and searchsorted works on more types +_by(x::Pair) = _by(x[1]) +_by(x::Tuple) = map(_by, x) +_by(x::AbstractRange) = first(x) +_by(x::IntervalSets.Interval) = x.left +_by(x) = x + +_in(needle::Dates.TimeType, haystack::Dates.TimeType) = needle == haystack +_in(needle, haystack) = needle in haystack +_in(needles::Tuple, haystacks::Tuple) = all(map(_in, needles, haystacks)) +_in(needle::Interval, haystack::ClosedInterval) = needle.left in haystack && needle.right in haystack +_in(needle::Interval{<:Any,:open}, haystack::Interval{:closed,:open}) = needle.left in haystack && needle.right in haystack +_in(needle::Interval{:open,<:Any}, haystack::Interval{:open,:closed}) = needle.left in haystack && needle.right in haystack +_in(needle::OpenInterval, haystack::OpenInterval) = needle.left in haystack && needle.right in haystack + hasselection(lookup::LookupArray, sel::At) = at(lookup, sel; err=_False()) === nothing ? false : true hasselection(lookup::LookupArray, sel::Contains) = contains(lookup, sel; err=_False()) === nothing ? false : true # Near and Between only fail on Unordered diff --git a/src/LookupArrays/set.jl b/src/LookupArrays/set.jl index 88e726c13..be40636e1 100644 --- a/src/LookupArrays/set.jl +++ b/src/LookupArrays/set.jl @@ -1,4 +1,4 @@ -const LookupArraySetters = Union{AllMetadata,LookupArray,LookupArrayTrait,Nothing} +const LookupArraySetters = Union{AllMetadata,LookupArray,LookupArrayTrait,Nothing,AbstractArray} set(lookup::LookupArray, x::LookupArraySetters) = _set(lookup, x) # _set(lookup::LookupArray, newlookup::LookupArray) = lookup diff --git a/src/LookupArrays/utils.jl b/src/LookupArrays/utils.jl index 6eb19d870..88f785ebc 100644 --- a/src/LookupArrays/utils.jl +++ b/src/LookupArrays/utils.jl @@ -89,6 +89,11 @@ unwrap(x) = x # Detect the order of an abstract array orderof(A::AbstractUnitRange) = ForwardOrdered() orderof(A::AbstractRange) = _order(A) +function orderof(A::AbstractArray{<:IntervalSets.Interval}) + indord = _order(A) + sorted = issorted(A; rev=LookupArrays.isrev(indord), by=x -> x.left) + return sorted ? indord : Unordered() +end function orderof(A::AbstractArray) local sorted, indord # This is awful. But we don't know if we can @@ -104,3 +109,4 @@ function orderof(A::AbstractArray) end _order(A) = first(A) <= last(A) ? ForwardOrdered() : ReverseOrdered() +_order(A::AbstractArray{<:IntervalSets.Interval}) = first(A).left <= last(A).left ? ForwardOrdered() : ReverseOrdered() diff --git a/src/array/show.jl b/src/array/show.jl index b22a2774c..8ae263f9b 100644 --- a/src/array/show.jl +++ b/src/array/show.jl @@ -3,7 +3,7 @@ using DimensionalData.Dimensions: dimcolors, dimsymbols, print_dims # Base show function Base.summary(io::IO, A::AbstractDimArray{T,N}) where {T,N} print_ndims(io, size(A)) - print(io, string(nameof(typeof(A)), "{$T,$N}")) + print_type(io, A) print_name(io, name(A)) end @@ -69,26 +69,34 @@ function show_after(io::IO, mime, A::AbstractDimArray; maxlen, kw...) end -function print_ndims(io, size::Tuple) +function print_ndims(io, size::Tuple; + colors=map(dimcolors, ntuple(identity, length(size))) +) if length(size) > 1 - print_sizes(io, size) + print_sizes(io, size; colors) print(io, ' ') + elseif length(size) == 1 + printstyled(io, Base.dims2string(size), " "; color=first(colors)) else print(io, Base.dims2string(size), " ") end end +print_type(io, x::AbstractArray{T,N}) where {T,N} = print(io, string(nameof(typeof(x)), "{$T,$N}")) +print_type(io, x) = print(io, string(nameof(typeof(x)))) + function print_top(io, mime, A) lines = 4 _, width = displaysize(io) - maxlen = min(width - 2, length(sprint(summary, A)) + 2) + maxlen = min(width - 2, textwidth(sprint(summary, A)) + 2) printstyled(io, '╭', '─'^maxlen, '╮'; color=:light_black) println(io) printstyled(io, "│ "; color=:light_black) summary(io, A) printstyled(io, " │"; color=:light_black) + println(io) n, maxlen = print_dims_block(io, mime, dims(A); width, maxlen) - lines += n + lines += n + 1 return lines, maxlen, width end @@ -104,18 +112,16 @@ function print_sizes(io, size; end end -function print_dims_block(io, mime, dims; width, maxlen) +function print_dims_block(io, mime, dims; width, maxlen, label="dims", kw...) lines = 0 if isempty(dims) - println(io) printed = false newmaxlen = maxlen else - println(io) dim_lines = split(sprint(print_dims, mime, dims), '\n') - newmaxlen = min(width - 2, max(maxlen, maximum(length, dim_lines))) - print_block_top(io, "dims", maxlen, newmaxlen) - lines += print_dims(io, mime, dims) + newmaxlen = min(width - 2, max(maxlen, maximum(textwidth, dim_lines))) + print_block_top(io, label, maxlen, newmaxlen) + lines += print_dims(io, mime, dims; kw...) println(io) lines += 2 printed = true @@ -135,7 +141,7 @@ function print_metadata_block(io, mime, metadata; maxlen=0, width, firstblock=fa print(io, " ") show(io, mime, metadata) println(io) - lines += length(metadata_lines) + 3 + lines += length(metadata_lines) + 2 end return lines, newmaxlen end @@ -145,9 +151,9 @@ end function print_block_top(io, label, prevmaxlen, newmaxlen) corner = (newmaxlen > prevmaxlen) ? '┐' : '┤' block_line = if newmaxlen > prevmaxlen - string('─'^(prevmaxlen), '┴', '─'^max(0, (newmaxlen - length(label) - 3 - prevmaxlen)), ' ', label, ' ') + string('─'^(prevmaxlen), '┴', '─'^max(0, (newmaxlen - textwidth(label) - 3 - prevmaxlen)), ' ', label, ' ') else - string('─'^max(0, newmaxlen - length(label) - 2), ' ', label, ' ') + string('─'^max(0, newmaxlen - textwidth(label) - 2), ' ', label, ' ') end printstyled(io, '├', block_line, corner; color=:light_black) println(io) @@ -155,7 +161,7 @@ end function print_block_separator(io, label, prevmaxlen, newmaxlen) corner = (newmaxlen > prevmaxlen) ? '┐' : '┤' - printstyled(io, '├', '─'^max(0, newmaxlen - length(label) - 2), ' ', label, ' ', corner; color=:light_black) + printstyled(io, '├', '─'^max(0, newmaxlen - textwidth(label) - 2), ' ', label, ' ', corner; color=:light_black) end function print_block_close(io, maxlen) @@ -259,7 +265,7 @@ function _print_matrix(io::IO, A::AbstractArray, lookups::Tuple) # A bit convoluted so it plays nice with GPU arrays topleft = Matrix{eltype(A)}(undef, map(length, (itop, ileft))) copyto!(topleft, CartesianIndices(topleft), A, CartesianIndices((itop, ileft))) - bottomleft= Matrix{eltype(A)}(undef, map(length, (ibottom, ileft))) + bottomleft = Matrix{eltype(A)}(undef, map(length, (ibottom, ileft))) copyto!(bottomleft, CartesianIndices(bottomleft), A, CartesianIndices((ibottom, ileft))) if !(lu1 isa NoLookup) topleft = hcat(map(show1, parent(lu1)[itop]), topleft) @@ -274,7 +280,7 @@ function _print_matrix(io::IO, A::AbstractArray, lookups::Tuple) bottomblock = hcat(leftblock, rightblock) A_dims = if lu2 isa NoLookup - map(showdefault, bottomblock) + bottomblock else toplabels = map(show2, parent(lu2)[ileft]), map(show2, parent(lu2)[iright]) toprow = if lu1 isa NoLookup @@ -282,7 +288,7 @@ function _print_matrix(io::IO, A::AbstractArray, lookups::Tuple) else vcat(showarrows(), toplabels...) end |> permutedims - vcat(toprow, map(showdefault, bottomblock)) + vcat(toprow, bottomblock) end Base.print_matrix(io, A_dims) @@ -313,8 +319,16 @@ show2(x) = ShowWith(x, :nothing, dimcolors(2)) showhide(x) = ShowWith(x, :hide, :nothing) showarrows() = ShowWith(1.0, :print_arrows, :nothing) -Base.alignment(io::IO, x::ShowWith) = Base.alignment(io, x.val) +function Base.alignment(io::IO, x::ShowWith) + # Base bug means we need to special-case this... + if x.val isa DateTime + 0, textwidth(sprint(print, x.val)) + else + Base.alignment(io, x.val) + end +end Base.length(x::ShowWith) = length(string(x.val)) +Base.textwidth(x::ShowWith) = textwidth(string(x.val)) Base.ncodeunits(x::ShowWith) = ncodeunits(string(x.val)) function Base.print(io::IO, x::ShowWith) printstyled(io, string(x.val); color = x.color, hidden = x.mode == :hide) diff --git a/src/dimindices.jl b/src/dimindices.jl index c4a2108e6..d343a308f 100644 --- a/src/dimindices.jl +++ b/src/dimindices.jl @@ -112,6 +112,30 @@ end Base.getindex(di::DimPoints{<:Any,1}, i::Int) = (dims(di, 1)[i],) Base.getindex(di::DimPoints, i::Int) = di[Tuple(CartesianIndices(di)[i])...] +struct DimViews{T,N,D<:DimTuple,A} <: AbstractDimIndices{T,N} + data::A + dims::D + function DimViews(data::A, dims::D) where {A,D<:DimTuple} + T = typeof(view(data, map(rebuild, dims, map(first, dims))...)) + N = length(dims) + new{T,N,D,A}(data, dims) + end +end + +function Base.getindex(dv::DimViews, i1::Int, i2::Int, I::Int...) + # Get dim-wrapped point values at i1, I... + D = map(dims(dv), (i1, i2, I...)) do d, i + rebuild(d, d[i]) + end + # Return the unwrapped point sorted by `order + return view(dv.data, D...) +end +function Base.getindex(dv::DimViews{<:Any,1}, i::Int) + d = dims(dv, 1) + return view(dv.data, rebuild(d, d[i])) +end +Base.getindex(dv::DimViews, i::Int) = dv[Tuple(CartesianIndices(dv)[i])...] + """ DimKeys <: AbstractArray diff --git a/src/groupby.jl b/src/groupby.jl new file mode 100644 index 000000000..dc66d2005 --- /dev/null +++ b/src/groupby.jl @@ -0,0 +1,415 @@ +""" + DimGroupByArray <: AbstractDimArray + +`DimGroupByArray` is essentially a `DimArray` but holding +the results of a `groupby` operation. + +Its dimensions are the sorted results of the grouping functions +used in `groupby`. + +This wrapper allows for specialisations on later broadcast or +reducing operations, e.g. for chunk reading with DiskArrays.jl, +because we know the data originates from a single array. +""" +struct DimGroupByArray{T,N,D<:Tuple,R<:Tuple,A<:AbstractArray{T,N},Na,Me} <: AbstractDimArray{T,N,D,A} + data::A + dims::D + refdims::R + name::Na + metadata::Me + function DimGroupByArray( + data::A, dims::D, refdims::R, name::Na, metadata::Me + ) where {D<:Tuple,R<:Tuple,A<:AbstractArray{T,N},Na,Me} where {T,N} + checkdims(data, dims) + new{T,N,D,R,A,Na,Me}(data, dims, refdims, name, metadata) + end +end +function DimGroupByArray(data::AbstractArray, dims::Union{Tuple,NamedTuple}; + refdims=(), name=NoName(), metadata=NoMetadata() +) + DimGroupByArray(data, format(dims, data), refdims, name, metadata) +end +@inline function rebuild( + A::DimGroupByArray, data::AbstractArray, dims::Tuple, refdims::Tuple, name, metadata +) + if eltype(data) <: Union{AbstractDimArray,AbstractDimStack} + # We have DimArrays or DimStacks. Rebuild as a DimGroupArray + DimGroupByArray(data, dims, refdims, name, metadata) + else + # Some other values. Rebuild as a reguilar DimArray + dimconstructor(dims)(data, dims, refdims, name, metadata) + end +end +@inline function rebuild(A::DimGroupByArray; + data=parent(A), dims=dims(A), refdims=refdims(A), name=name(A), metadata=metadata(A) +) + rebuild(A, data, dims, refdims, name, metadata) # Rebuild as a reguilar DimArray +end + +function Base.summary(io::IO, A::DimGroupByArray{T,N}) where {T,N} + print_ndims(io, size(A)) + print(io, string(nameof(typeof(A)), "{$(nameof(T)),$N}")) +end + +function show_after(io::IO, mime, A::DimGroupByArray; maxlen=0) + _, width = displaysize(io) + sorteddims = (dims(A)..., otherdims(first(A), dims(A))...) + colordims = dims(map(rebuild, sorteddims, ntuple(dimcolors, Val(length(sorteddims)))), dims(first(A))) + colors = collect(map(val, colordims)) + print_dims_block(io, mime, basedims(first(A)); width, maxlen, label="group dims", colors) + length(A) > 0 || return nothing + A1 = map(x -> DimSummariser(x, colors), A) + show_after(io, mime, A1; maxlen) + return nothing +end + +mutable struct DimSummariser{T} + obj::T + colors::Vector{Int} +end +function Base.show(io::IO, s::DimSummariser) + print_ndims(io, size(s.obj); colors=s.colors) + print(io, string(nameof(typeof(s.obj)))) +end +Base.alignment(io::IO, s::DimSummariser) = (textwidth(sprint(show, s)), 0) + + +abstract type AbstractBins <: Function end + +(bins::AbstractBins)(x) = bins.f(x) + +""" + Bins(f, bins; pad) + Bins(bins; pad) + +Specify bins to reduce groups after applying function `f`. + +- `f` a grouping function of the lookup values, by default `identity`. +- `bins`: + * an `Integer` will divide the group values into equally spaced sections. + * an `AbstractArray` of values will be treated as exact + matches for the return value of `f`. For example, `1:3` will create 3 bins - 1, 2, 3. + * an `AbstractArray` of `IntervalSets.Interval` can be used to + explictly define the intervals. Overlapping intervals have undefined behaviour. + +## Keywords + +- `pad`: fraction of the total interval to pad at each end when `Bins` contains an + `Integer`. This avoids losing the edge values. Note this is a messy solution - + it will often be prefereble to manually specify a `Vector` of chosen `Interval`s + rather than relying on passing an `Integer` and `pad`. + +When the return value of `f` is a tuple, binning is applied to the _last_ value of the tuples. +""" +struct Bins{F<:Callable,B<:Union{Integer,AbstractVector,Tuple},L,P} <: AbstractBins + f::F + bins::B + labels::L + pad::P +end +Bins(bins; labels=nothing, pad=0.001) = Bins(identity, bins, labels, pad) +Bins(f, bins; labels=nothing, pad=0.001) = Bins(f, bins, labels, pad) + +Base.show(io::IO, bins::Bins) = println(io, nameof(typeof(bins)), "(", bins.f, ", ", bins.bins, ")") + +abstract type AbstractCyclicBins end +struct CyclicBins{F,C,Sta,Ste,L} <: AbstractBins + f::F + cycle::C + start::Sta + step::Ste + labels::L +end +CyclicBins(f; cycle, step, start=1, labels=nothing) = CyclicBins(f, cycle, start, step, labels) + +Base.show(io::IO, bins::CyclicBins) = +println(io, nameof(typeof(bins)), "(", bins.f, "; ", join(map(k -> "$k=$(getproperty(bins, k))", (:cycle, :step, :start)), ", "), ")") + +yearhour(x) = year(x), hour(x) + +season(; start=January, kw...) = months(3; start, kw...) +months(step; start=January, labels=Dict(1:12 .=> monthabbr.(1:12))) = CyclicBins(month; cycle=12, step, start, labels) +hours(step; start=0, labels=nothing) = CyclicBins(hour; cycle=24, step, start, labels) +yearhours(step; start=0, labels=nothing) = CyclicBins(yearhour; cycle=24, step, start, labels) +yeardays(step; start=1, labels=nothing) = CyclicBins(dayofyear; cycle=daysinyear, step, start, labels) +monthdays(step; start=1, labels=nothing) = CyclicBins(dayofmonth; cycle=daysinmonth, step, start, labels) + +""" + groupby(A::Union{AbstractDimArray,AbstractDimStack}, dims::Pair...) + groupby(A::Union{AbstractDimArray,AbstractDimStack}, dims::Dimension{<:Callable}...) + +Group `A` by grouping functions or [`Bins`](@ref) over multiple dimensions. + +## Arguments + +- `A`: any `AbstractDimArray` or `AbsractDimStack`. +- `dims`: `Pair`s such as `groups = groupby(A, :dimname => groupingfunction)` or wrapped + [`Dimension`](@ref)s like `groups = groupby(A, DimType(groupingfunction))`. Instead of + a grouping function [`Bins`](@ref) can be used to specify group bins. + +## Return value + +A [`DimGroupByArray`](@ref) is returned, which is basically a regular `AbstractDimArray` +but holding the grouped `AbstractDimArray` or `AbstractDimStrack`. Its `dims` +hold the sorted values returned by the grouping function/s. + +Base julia and package methods work on `DimGroupByArray` as for any other +`AbstractArray` of `AbstractArray`. + +It is common to broadcast or `map` a reducing function over groups, +such as `mean` or `sum`, like `mean.(groups)` or `map(mean, groups)`. +This will return a regular `DimArray`, or `DimGroupByArray` if `dims` +keyword is used in the reducing function or it otherwise returns an +`AbstractDimArray` or `AbstractDimStack`. + +# Example + +Group some data along the time dimension: + +```julia +julia> using DimensionalData, Dates + +julia> A = rand(X(1:0.1:20), Y(1:20), Ti(DateTime(2000):Day(3):DateTime(2003))); + +julia> groups = groupby(A, Ti => month) # Group by month +╭────────────────────────────────────────╮ +│ 12-element DimGroupByArray{DimArray,1} │ +├────────────────────────────────────────┴──────────────────────── dims ┐ + ↓ Ti Sampled{Int64} [1, 2, …, 11, 12] ForwardOrdered Irregular Points +├───────────────────────────────────────────────────────────── metadata ┤ + Dict{Symbol, Any} with 1 entry: + :groupby => (Ti{typeof(month)}(month),) +├─────────────────────────────────────────────────────────── group dims ┤ + ↓ X, → Y, ↗ Ti +└───────────────────────────────────────────────────────────────────────┘ + 1 191×20×32 DimArray + 2 191×20×28 DimArray + 3 191×20×31 DimArray + 4 191×20×30 DimArray + ⋮ + 9 191×20×30 DimArray + 10 191×20×31 DimArray + 11 191×20×30 DimArray + 12 191×20×31 DimArray +``` + +And take the mean: + +``` +julia> groupmeans = mean.(groups) # Take the monthly mean +╭────────────────────────────────╮ +│ 12-element DimArray{Float64,1} │ +├────────────────────────────────┴──────────────────────────────── dims ┐ + ↓ Ti Sampled{Int64} [1, 2, …, 11, 12] ForwardOrdered Irregular Points +├───────────────────────────────────────────────────────────── metadata ┤ + Dict{Symbol, Any} with 1 entry: + :groupby => (Ti{typeof(month)}(month),) +└───────────────────────────────────────────────────────────────────────┘ + 1 0.499943 + 2 0.499352 + 3 0.499289 + 4 0.499899 + ⋮ + 10 0.500755 + 11 0.498912 + 12 0.500352 +``` + +Calculate daily anomalies from the monthly mean. Notice we map a broadcast +`.-` rather than `-`. This is because the size of the arrays to not +match after application of `mean`. + +```julia +julia> map(.-, groupby(A, Ti=>month), mean.(groupby(A, Ti=>month), dims=Ti)); +``` + +Or do something else with Y: + +```julia +julia> groupmeans = mean.(groupby(A, Ti=>month, Y=>isodd)) +╭──────────────────────────╮ +│ 12×2 DimArray{Float64,2} │ +├──────────────────────────┴─────────────────────────────────────── dims ┐ + ↓ Ti Sampled{Int64} [1, 2, …, 11, 12] ForwardOrdered Irregular Points, + → Y Sampled{Bool} [false, true] ForwardOrdered Irregular Points +├──────────────────────────────────────────────────────────────────────── metadata ┐ + Dict{Symbol, Any} with 1 entry: + :groupby => (Ti{typeof(month)}(month), Y{typeof(isodd)}(isodd)) +└──────────────────────────────────────────────────────────────────────────────────┘ + ↓ → false true + 1 0.500465 0.499421 + 2 0.498681 0.500024 + ⋮ + 10 0.500183 0.501327 + 11 0.497746 0.500079 + 12 0.500287 0.500417 +``` +""" +DataAPI.groupby(A::DimArrayOrStack, x) = groupby(A, dims(x)) +DataAPI.groupby(A::DimArrayOrStack, dimfuncs::Dimension...) = groupby(A, dimfuncs) +function DataAPI.groupby( + A::DimArrayOrStack, p1::Pair{<:Any,<:Base.Callable}, ps::Pair{<:Any,<:Base.Callable}...; +) + dims = map((p1, ps...)) do (d, v) + rebuild(basedims(d), v) + end + return groupby(A, dims) +end +function DataAPI.groupby(A::DimArrayOrStack, dimfuncs::DimTuple) + length(otherdims(dimfuncs, dims(A))) > 0 && + Dimensions._extradimserror(otherdims(dimfuncs, dims(A))) + + # Get groups for each dimension + dim_groups_indices = map(dimfuncs) do d + _group_indices(dims(A, d), DD.val(d)) + end + # Separate lookups dims from indices + group_dims = map(first, dim_groups_indices) + indices = map(rebuild, dimfuncs, map(last, dim_groups_indices)) + + views = DimViews(A, indices) + # Put the groupby query in metadata + meta = map(d -> dim2key(d) => val(d), dimfuncs) + metadata = Dict{Symbol,Any}(:groupby => length(meta) == 1 ? only(meta) : meta) + # Return a DimGroupByArray + return DimGroupByArray(views, format(group_dims, views), (), :groupby, metadata) +end + +# Define the groups and find all the indices for values that fall in them +function _group_indices(dim::Dimension, f::Base.Callable; labels=nothing) + orig_lookup = lookup(dim) + k1 = f(first(orig_lookup)) + indices_dict = Dict{typeof(k1),Vector{Int}}() + for (i, x) in enumerate(orig_lookup) + k = f(x) + inds = get!(() -> Int[], indices_dict, k) + push!(inds, i) + end + ps = sort!(collect(pairs(indices_dict))) + group_dim = format(rebuild(dim, _maybe_label(labels, first.(ps)))) + indices = last.(ps) + return group_dim, indices +end +function _group_indices(dim::Dimension, group_lookup::LookupArray; labels=nothing) + orig_lookup = lookup(dim) + indices = map(_ -> Int[], 1:length(group_lookup)) + for (i, v) in enumerate(orig_lookup) + n = selectindices(group_lookup, Contains(v)) + push!(indices[n], i) + end + group_dim = if isnothing(labels) + rebuild(dim, group_lookup) + else + label_lookup = _maybe_label(labels, group_lookup) + rebuild(dim, label_lookup) + end + return group_dim, indices +end +function _group_indices(dim::Dimension, bins::AbstractBins; labels=nothing) + l = lookup(dim) + # Apply the function first unless its `identity` + transformed = bins.f == identity ? parent(l) : map(bins.f, parent(l)) + # Calculate the bin groups + groups = if eltype(transformed) <: Tuple + # Get all values of the tuples but the last one and take the union + outer_groups = union!(map(t -> t[1:end-1], transformed)) + inner_groups = _groups_from(transformed, bins) + # Combine the groups + mapreduce(vcat, outer_groups) do og + map(ig -> (og..., ig), inner_groups) + end + else + _groups_from(transformed, bins) + end + group_lookup = lookup(format(rebuild(dim, groups))) + transformed_lookup = rebuild(dim, transformed) + + # Call the LookupArray version to do the work using selectors + return _group_indices(transformed_lookup, group_lookup) +end + + +# Get a vector of intervals for the bins +_groups_from(_, bins::Bins{<:Any,<:AbstractArray}) = bins.bins +function _groups_from(transformed, bins::Bins{<:Any,<:Integer}) + # With an Integer, we calculate evenly-spaced bins from the extreme values + a, b = extrema(last, transformed) + # pad a tiny bit so the top open interval includes the top value (xarray also does this) + b_p = b + (b - a) * bins.pad + # Create a range + rng = range(IntervalSets.Interval{:closed,:open}(a, b_p), bins.bins) + # Return a Vector of Interval{:closed,:open} for the range + return IntervalSets.Interval{:closed,:open}.(rng, rng .+ step(rng)) +end +function _groups_from(_, bins::CyclicBins) + map(bins.start:bins.step:bins.start+bins.cycle-1) do g + map(0:bins.step-1) do n + rem(n + g - 1, bins.cycle) + 1 + end + end +end + +# Return the bin for a value +# function _choose_bin(b::AbstractBins, groups::LookupArray, val) +# groups[ispoints(groups) ? At(val) : Contains(val)] +# end +# function _choose_bin(b::AbstractBins, groups, val) +# i = findfirst(Base.Fix1(_in, val), groups) +# isnothing(i) && return nothing +# return groups[i] +# end +# function _choose_bin(b::Bins, groups::AbstractArray{<:Number}, val) +# i = searchsortedlast(groups, val; by=_by) +# i >= firstindex(groups) && val in groups[i] || return nothing +# return groups[i] +# end +# function _choose_bin(b::Bins, groups::AbstractArray{<:Tuple{Vararg{Union{Number,AbstractRange,IntervalSets.Interval}}}}, val::Tuple) +# @assert length(val) == length(first(groups)) +# i = searchsortedlast(groups, val; by=_by) +# i >= firstindex(groups) && last(val) in last(groups[i]) || return nothing +# return groups[i] +# end +# _choose_bin(b::Bins, groups::AbstractArray{<:IntervalSets.Interval}, val::Tuple) = _choose_bin(b::Bins, groups, last(val)) +# function _choose_bin(b::Bins, groups::AbstractArray{<:IntervalSets.Interval}, val) +# i = searchsortedlast(groups, val; by=_by) +# i >= firstindex(groups) && val in groups[i] || return nothing +# return groups[i] +# end + +_maybe_label(vals) = vals +_maybe_label(f::Function, vals) = f.(vals) +_maybe_label(::Nothing, vals) = vals +function _maybe_label(labels::AbstractArray, vals) + @assert length(labels) == length(vals) + return labels +end +function _maybe_label(labels::Dict, vals) + map(vals) do val + if haskey(labels, val) + labels[val] + else + Symbol(join(map(v -> string(labels[v]), val), '_')) + end + end +end + +# Helpers +intervals(rng::AbstractRange) = IntervalSets.Interval{:closed,:open}.(rng, rng .+ step(rng)) +function intervals(la::LookupArray) + if ispoints(la) + rebuild(la; data=(x -> IntervalSets.Interval{:closed,:closed}(x, x)).(la)) + else + rebuild(la; data=(x -> IntervalSets.Interval{:closed,:open}(x[1], x[2])).(intervalbounds(la))) + end +end +function intervals(A::AbstractVector{T}; upper::T) where T + is = Vector{IntervalSets.Interval{:closed,:open}}(undef, length(A)) + for i in eachindex(A)[1:end-1] + is[i] = IntervalSets.Interval{:closed,:open}(A[i], A[i + 1]) + end + is[end] = IntervalSets.Interval{:closed,:open}.(A[end], upper) + return is +end + +ranges(rng::AbstractRange) = map(x -> x:x+step(rng)-1, rng) +ranges(rng::LookupArray{<:AbstractRange}) = rebuild(rng; data=ranges(parent(rng))) diff --git a/src/stack/show.jl b/src/stack/show.jl index 5b49823d3..01cb1c0a1 100644 --- a/src/stack/show.jl +++ b/src/stack/show.jl @@ -58,10 +58,8 @@ function print_layer(io, stack, key, keylen) Dimensions.print_dimname(IOContext(io, :dimcolor => color), dim) i != length(field_dims) && print(io, ", ") end - # print(io, " (") printstyled(io, " size: "; color=:light_black) print_sizes(io, size(field_dims); colors) - # print(io, ')') end print(io, '\n') end diff --git a/src/stack/stack.jl b/src/stack/stack.jl index 6d5a9bf97..76067d8d2 100644 --- a/src/stack/stack.jl +++ b/src/stack/stack.jl @@ -42,6 +42,8 @@ function layers(s::AbstractDimStack{<:NamedTuple{Keys}}) where Keys NamedTuple{Keys}(map(K -> s[K], Keys)) end +const DimArrayOrStack = Union{AbstractDimArray,AbstractDimStack} + """ rebuild_from_arrays(s::AbstractDimStack, das::NamedTuple{<:Any,<:Tuple{Vararg{AbstractDimArray}}}; kw...) diff --git a/test/groupby.jl b/test/groupby.jl new file mode 100644 index 000000000..6f6d0dcb2 --- /dev/null +++ b/test/groupby.jl @@ -0,0 +1,72 @@ +using DimensionalData, Test, Dates, Statistics, IntervalSets + +using DimensionalData.Dimensions +using DimensionalData.LookupArrays +const DD = DimensionalData + +days = DateTime(2000):Day(1):DateTime(2000, 12, 31) +A = DimArray((1:6) * (1:366)', (X(1:0.2:2), Ti(days))) +st = DimStack((a=A, b=A, c=A[X=1])) + +@testset "manual groupby comparisons" begin + # Group by month and even/odd Y axis values + months = DateTime(2000):Month(1):DateTime(2000, 12, 31) + manualmeans = map(months) do m + mean(A[Ti=dayofyear(m):dayofyear(m)+daysinmonth(m)-1]) + end + @test mean.(groupby(A, Ti=>month)) == manualmeans + manualmeans_st = map(months) do m + mean(st[Ti=dayofyear(m):dayofyear(m)+daysinmonth(m)-1]) + end + @test mean.(groupby(st, Ti=>month)) == manualmeans_st + + manualsums = mapreduce(hcat, months) do m + vcat(sum(A[Ti=dayofyear(m):dayofyear(m)+daysinmonth(m)-1, X=1 .. 1.5]), + sum(A[Ti=dayofyear(m):dayofyear(m)+daysinmonth(m)-1, X=1.5 .. 2]) + ) + end |> permutedims + gb_sum = sum.(groupby(A, Ti=>month, X => >(1.5))) + @test dims(gb_sum, Ti) == Ti(Sampled([1:12...], ForwardOrdered(), Irregular((nothing, nothing)), Points(), NoMetadata())) + @test typeof(dims(gb_sum, X)) == typeof(X(Sampled(BitVector([false, true]), ForwardOrdered(), Irregular((nothing, nothing)), Points(), NoMetadata()))) + @test gb_sum == manualsums + + manualsums_st = mapreduce(hcat, months) do m + vcat(sum(st[Ti=dayofyear(m):dayofyear(m)+daysinmonth(m)-1, X=1 .. 1.5]), + sum(st[Ti=dayofyear(m):dayofyear(m)+daysinmonth(m)-1, X=1.5 .. 2]) + ) + end |> permutedims + gb_sum_st = sum.(groupby(st, Ti=>month, X => >(1.5))) + @test dims(gb_sum_st, Ti) == Ti(Sampled([1:12...], ForwardOrdered(), Irregular((nothing, nothing)), Points(), NoMetadata())) + @test typeof(dims(gb_sum_st, X)) == typeof(X(Sampled(BitVector([false, true]), ForwardOrdered(), Irregular((nothing, nothing)), Points(), NoMetadata()))) + @test gb_sum_st == manualsums_st + + @test_throws ArgumentError groupby(st, Ti=>month, Y=>isodd) +end + +@testset "bins" begin + seasons = DateTime(2000):Month(3):DateTime(2000, 12, 31) + manualmeans = map(seasons) do s + range = dayofyear(s):dayofyear(s)+daysinmonth(s)+daysinmonth(s+Month(1))+daysinmonth(s+Month(2))-1 + mean(A[Ti=range]) + end + @test mean.(groupby(A, Ti=>Bins(month, ranges(1:3:12)))) == manualmeans + @test mean.(groupby(A, Ti=>Bins(month, intervals(1:3:12)))) == manualmeans + @test mean.(groupby(A, Ti=>Bins(month, 4))) == manualmeans +end + +@testset "dimension matching groupby" begin + dates = DateTime(2000):Month(1):DateTime(2000, 12, 31) + xs = 1.0:1:3.0 + B = rand(X(xs; sampling=Intervals(Start())), Ti(dates; sampling=Intervals(Start()))) + gb = groupby(A, B) + @test size(gb) === size(B) === size(mean.(gb)) + @test dims(gb) === dims(B) === dims(mean.(gb)) + manualmeans = mapreduce(hcat, intervals(dates)) do d + map(intervals(xs)) do x + mean(A[X=x, Ti=d]) + end + end + @test all(collect(mean.(gb)) .=== manualmeans) + @test all(mean.(gb) .=== manualmeans) +end + diff --git a/test/lookup.jl b/test/lookup.jl index 11bb8e9e0..979a7d7a6 100644 --- a/test/lookup.jl +++ b/test/lookup.jl @@ -223,7 +223,7 @@ end @test bounds(dim) == (10, 15) dim = X(Sampled(ind; order=Unordered(), sampling=Points())) @test bounds(dim) == (nothing, nothing) - @test_throws ErrorException intervalbounds(dim) + @test intervalbounds(dim) == collect(zip(15:-1:10, 15:-1:10)) end @testset "Categorical" begin diff --git a/test/runtests.jl b/test/runtests.jl index 9d3c88e73..dff91beb8 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -38,6 +38,7 @@ end @time @safetestset "show" begin include("show.jl") end @time @safetestset "tables" begin include("tables.jl") end @time @safetestset "utils" begin include("utils.jl") end +@time @safetestset "groupby" begin include("groupby.jl") end if Sys.islinux() From f41d082b6b7ed23a8372557b0ba82cdd228e029d Mon Sep 17 00:00:00 2001 From: Lazaro Alonso Date: Sat, 17 Feb 2024 12:19:05 +0100 Subject: [PATCH 032/108] Continue main (#631) * write some docs * more docs * document basedims * fix 1 dim print matrix * up to Documenter1 * rm val * Warn, not error, during docs build * Ensure that some value is returned from every example block * Add DiskArrays to the docs project * Use insecticide generously TODO: revert this! * Done with insecticide * rm Plots example, svg is still failing * continue with groupby, some still fail --------- Co-authored-by: rafaqz Co-authored-by: Anshul Singhvi --- docs/Project.toml | 2 + docs/crash/course/groupby.jl | 115 ------------------- docs/make.jl | 5 +- docs/src/.vitepress/config.mts | 3 +- docs/src/api/dimensions.md | 3 +- docs/src/dimensions.md | 7 +- docs/src/groupby.md | 198 +++++++++++++++++++++++++++++++++ docs/src/plots.md | 6 - 8 files changed, 212 insertions(+), 127 deletions(-) delete mode 100644 docs/crash/course/groupby.jl create mode 100644 docs/src/groupby.md diff --git a/docs/Project.toml b/docs/Project.toml index 04c18b73f..b570f61ce 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -4,7 +4,9 @@ CairoMakie = "13f3f980-e62b-5c42-98c6-ff1f3baf88f0" Colors = "5ae59095-9a9b-59fe-a467-6f913c188581" Dates = "ade2ca70-3891-5945-98fb-dc099432e06a" DimensionalData = "0703355e-b756-11e9-17c0-8b28908087d0" +DiskArrays = "3c3547ce-8d99-4f5e-a174-61eb10b00ae3" Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" DocumenterVitepress = "4710194d-e776-4893-9690-8d956a29c365" +Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" Unitful = "1986cc42-f94f-5a68-af5c-568840ba703d" diff --git a/docs/crash/course/groupby.jl b/docs/crash/course/groupby.jl deleted file mode 100644 index 99dbcbf92..000000000 --- a/docs/crash/course/groupby.jl +++ /dev/null @@ -1,115 +0,0 @@ -using DimensionalData -using Dates -using Statistics -const DD = DimensionalData - -# # Basics: DateTime operations we can use for grouping - -# First lets look at the kind of functions that can be used to group `DateTime`. -# Other types will follow the same principles, but are usually simpler. - -# Create a demo `DateTime` range -tempo = range(DateTime(2000), step=Hour(1), length=365*24*2) - -# Now lets see how some common functions work. - -# The `hour` function will transform values to hour of the day - the integers `0:23` - -# ### hour -hour.(tempo) - -# These do similar things with other time periods - -# ### dayofweek -dayofweek.(tempo) - -# ### month -month.(tempo) - -# ### dayofyear -dayofyear.(tempo) - -# ## Tuple grouping - -# Some functions return a tuple - we can also use tuples for grouping. -# They are sorted by the left to right values. - -# ### yearmonth -yearmonth.(tempo) - -# We can creat our own anonymous function that return tuples -yearday(x) = year(x), dayofyear(x) - -yearhour(x) = year(x), hour(x) - -# And you can probably guess what they do: -yearday.(tempo) - -# All of these functions can be used in `groupby` on `DateTime` objects. - -# # Practical example: grouping by season - -# ### TODOS: We will need several functions. - -# # groupby operations -# Here we use the same time functions from above - -ds = rand(X(1:0.01:2), Ti(tempo)) - -# ## select by month, days, years and seasons -# ### TODO, how do we select month 1 or 2, and even a group of them, i.e. [1,3,5]? Same for days, years and seasons. -mean.(groupby(ds, Ti=>Bins(month, 1:2))) -mean.(groupby(ds, Ti=>Bins(month, [1, 3, 5]))) -mean.(groupby(ds, Ti => season(; start=December))) -mean.(groupby(ds, Ti => Bins(dayofyear, intervals(1:8:370)))) -mean.(groupby(ds, Ti => Bins(yearday, [[1,2,3], [4,5,6]], labels=x -> join(string.(x), ',')))) -mean.(groupby(ds, Ti => week)) -mean.(groupby(ds, Ti => hours(12; start=6, labels=x -> 6 in x ? :night : :day))) -mean.(groupby(ds, Ti => dims(ds, Ti))) - -# ### TODO, we need a new function that can return DJF (Dec-Jan-Feb), MAM (Mar-Apr-May)... etc. -# THIS IS HARD. We need a succinct way to select around the end-start of the year. - -# is combining month from different years -mean.(groupby(ds, Ti=>month)) - -# Use three-month bins. The 13 is the open side of the last interval. -mean.(groupby(ds, Ti=>Bins(yearmonth, intervals(1:3:12)))) - -mean.(groupby(ds, Ti=>Bins(month, 4))) # is combining month from different years - -# -mean.(groupby(ds, Ti=>year)) - -# -mean.(groupby(ds, Ti=>yearmonth)) - -# -mean.(groupby(ds, Ti=>hour)) - -# -mean.(groupby(ds, Ti=>Dates.hour12)) - -# ### TODO. How do could we incorporate resample? Let's say if we have hour resolution I want to resample every 3,6,12.. hours? -mean.(groupby(ds, Ti=>Bins(yearhour, intervals(1:3:24)))) # it will combine the same day from different year. - -mean.(groupby(ds, Ti=>dayofyear)) # it will combine the same day from different year. - -# -mean.(groupby(ds, Ti=>yearmonthday)) # this does the a daily mean aggregation. - -# -mean.(groupby(ds, Ti=>yearmonth)) # this does a monthly mean aggregation - -# -mean.(groupby(ds, Ti=>yearday)) # this does a daily mean aggregation - -mean.(groupby(ds, Ti=>Bins(yearhour, 12))) # this does a daily mean aggregation - -# ### TODO. Similar to the hourly resample, how do we do it for more than 1 day, let's say 8daily? -mean.(groupby(ds, Ti=>Bins(dayofyear, map(x -> x:x+7, 1:8:370)))) - -# ### TODO: Group by Dims. This should include the rasters input sampling. -mean.(groupby(ds, dims(ds, Ti))) - -# ## Apply custom function (i.e. normalization) to grouped output. diff --git a/docs/make.jl b/docs/make.jl index 1a7b0f3e8..f81a253a4 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -7,4 +7,7 @@ makedocs(; sitename="DimensionalData", authors="Rafael Schouten et al.", # checkdocs=:all, format=DocumenterVitepress.MarkdownVitepress(), draft=false, - source="src", build=joinpath(@__DIR__, "docs_site")) \ No newline at end of file + source="src", + build=joinpath(@__DIR__, "docs_site"), + warnonly = true, +) \ No newline at end of file diff --git a/docs/src/.vitepress/config.mts b/docs/src/.vitepress/config.mts index d019071e5..575a28e50 100644 --- a/docs/src/.vitepress/config.mts +++ b/docs/src/.vitepress/config.mts @@ -64,9 +64,10 @@ export default defineConfig({ { text: 'Getting Started', link: '/basics' }, { text: 'Dimensions', link: '/dimensions' }, { text: 'Selectors', link: '/selectors' }, + { text: 'GroupBy', link: '/groupby' }, { text: 'Stacks', link: '/stacks' }, { text: 'Tables and DataFrames', link: '/tables' }, - { text: 'Lookup customazation', link: '/lookup_customazation' }, + { text: 'Lookup customazation', link: '/lookup_customization' }, { text: 'Extending DimensionalData', link: '/ext_dd' }, { text: 'Plots with Makie', link: '/plots' }, { text: 'CUDA & GPUs', link: '/cuda' }, diff --git a/docs/src/api/dimensions.md b/docs/src/api/dimensions.md index 3841beb3d..9155d5d34 100644 --- a/docs/src/api/dimensions.md +++ b/docs/src/api/dimensions.md @@ -31,12 +31,11 @@ Dimensions.AnonDim These are widely useful methods for working with dimensions. -```@docs +```@docs; canonical=false dims dimnum hasdim otherdims -val ``` ### Non-exported methods diff --git a/docs/src/dimensions.md b/docs/src/dimensions.md index aa2247b7c..c3b39eb62 100644 --- a/docs/src/dimensions.md +++ b/docs/src/dimensions.md @@ -100,6 +100,7 @@ We can index with `Vector` of `Tuple{Vararg(Dimension}}` like vectors of ```@ansi dimensions # TODO not merged yet A3[[(X(3), Z(5)), (X(7), Z(x)), (X(8), Z(2))]] +nothing # hide ``` `DimIndices` can be used like `CartesianIndices` but again, without the @@ -107,6 +108,7 @@ constraint of consecutive dimensions or known order. ```@ansi dimensions # TODO not merged yet A3[DimIndices(dims(A3, (X, Z))), Y(3)] +nothing # hide ``` All of this indexing can be combined arbitrarily. @@ -119,6 +121,7 @@ dimensions into a lookup of tuples: A4 = DimArray(rand(10, 9, 8, 7, 6, 5), (:a, :b, :c, :d, :e, :f)) # TODO not merged yet A4[e=6, DimIndices(dims(A4, (:d, :b))), a=3, collect(DimIndices(dims(A4, (:c, :f))))] +nothing # hide ``` The `Dimension` indexing layer sits on top of regular indexing and _can not_ be combined @@ -126,9 +129,9 @@ with it! Regular indexing specifies order, so doesn't mix well with our dimensio Mixing them will throw an error: -```@example dimension +```@example dimensions A1[X(3), 4] -ERROR: ArgumentError: invalid index: X{Int64}(3) of type X{Int64} +# ERROR: ArgumentError: invalid index: X{Int64}(3) of type X{Int64} ``` ::: info Indexing diff --git a/docs/src/groupby.md b/docs/src/groupby.md new file mode 100644 index 000000000..c71466299 --- /dev/null +++ b/docs/src/groupby.md @@ -0,0 +1,198 @@ +# groupby + +## Basics: DateTime operations we can use for grouping + +````@example groupby +using DimensionalData +using Dates +using Statistics +const DD = DimensionalData +```` + +First lets look at the kind of functions that can be used to group `DateTime`. +Other types will follow the same principles, but are usually simpler. + +Create a demo `DateTime` range + +````@ansi groupby +tempo = range(DateTime(2000), step=Hour(1), length=365*24*2) +```` + +Now lets see how some common functions work. + +The `hour` function will transform values to hour of the day - the integers `0:23` + +## hour + +````@ansi groupby +hour.(tempo) +```` + +These do similar things with other time periods + +## dayofweek + +````@ansi groupby +dayofweek.(tempo) +```` + +## month + +````@ansi groupby +month.(tempo) +```` + +## dayofyear + +````@ansi groupby +dayofyear.(tempo) +```` + +## Tuple grouping + +Some functions return a tuple - we can also use tuples for grouping. +They are sorted by the left to right values. + +## yearmonth + +````@ansi groupby +yearmonth.(tempo) +```` + +We can creat our own anonymous function that return tuples + +````@example groupby +yearday(x) = year(x), dayofyear(x) +yearhour(x) = year(x), hour(x) +```` + +And you can probably guess what they do: + +````@ansi groupby +yearday.(tempo) +```` + +All of these functions can be used in `groupby` on `DateTime` objects. + +# Practical example: grouping by season + +## groupby operations + +Here we use the same time functions from above + +````@ansi groupby +ds = rand(X(1:0.01:2), Ti(tempo)) +```` + +## select by month, days, years and seasons + +How do we select month 1 or 2, and even a group of them, i.e. [1,3,5]? Same for days, years and seasons. + +````@ansi groupby +mean.(groupby(ds, Ti=>Bins(month, 1:2))) +```` + +````@ansi groupby +mean.(groupby(ds, Ti=>Bins(month, [1, 3, 5]))) +```` + +````@ansi groupby +mean.(groupby(ds, Ti => season(; start=December))) +```` + +````@ansi groupby +mean.(groupby(ds, Ti => Bins(dayofyear, intervals(1:8:370)))) +```` + +````@ansi groupby +mean.(groupby(ds, Ti => Bins(yearday, [[1,2,3], [4,5,6]], labels=x -> join(string.(x), ',')))) +```` + +````@ansi groupby +mean.(groupby(ds, Ti => week)) +```` + +````@ansi groupby +mean.(groupby(ds, Ti => hours(12; start=6, labels=x -> 6 in x ? :night : :day))) +```` + +````@ansi groupby +mean.(groupby(ds, Ti => dims(ds, Ti))) +```` + +We need a new function that can return DJF (Dec-Jan-Feb), MAM (Mar-Apr-May)... etc. + +THIS IS HARD. We need a succinct way to select around the end-start of the year. + +is combining month from different years + +````@ansi groupby +mean.(groupby(ds, Ti=>month)) +```` + +Use three-month bins. The 13 is the open side of the last interval. + +````@ansi groupby +mean.(groupby(ds, Ti=>Bins(yearmonth, intervals(1:3:12)))) +```` + +````@ansi groupby +mean.(groupby(ds, Ti=>Bins(month, 4))) # is combining month from different years +```` + +````@ansi groupby +mean.(groupby(ds, Ti=>year)) +```` + +````@ansi groupby +mean.(groupby(ds, Ti=>yearmonth)) +```` + +````@ansi groupby +mean.(groupby(ds, Ti=>hour)) +```` + +````@ansi groupby +mean.(groupby(ds, Ti=>Dates.hour12)) +```` + +How do could we incorporate resample? Let's say if we have hour resolution I want to resample every 3,6,12.. hours? + +````@ansi groupby +mean.(groupby(ds, Ti=>Bins(yearhour, intervals(1:3:24)))) # it will combine the same day from different year. +```` + +````@ansi groupby +mean.(groupby(ds, Ti=>dayofyear)) # it will combine the same day from different year. +```` + +````@ansi groupby +mean.(groupby(ds, Ti=>yearmonthday)) # this does the a daily mean aggregation. +```` + +````@ansi groupby +mean.(groupby(ds, Ti=>yearmonth)) # this does a monthly mean aggregation +```` + +````@ansi groupby +mean.(groupby(ds, Ti=>yearday)) # this does a daily mean aggregation +```` + +````@ansi groupby +mean.(groupby(ds, Ti=>Bins(yearhour, 12))) # this does a daily mean aggregation +```` + +Similar to the hourly resample, how do we do it for more than 1 day, let's say 8daily? + +````@ansi groupby +mean.(groupby(ds, Ti=>Bins(dayofyear, map(x -> x:x+7, 1:8:370)))) +```` + +## Group by Dims. +This should include the rasters input sampling. + +````@ansi groupby +mean.(groupby(ds, dims(ds, Ti))) +```` + +## Apply custom function (i.e. normalization) to grouped output. diff --git a/docs/src/plots.md b/docs/src/plots.md index 1d1955f9a..496152b3c 100644 --- a/docs/src/plots.md +++ b/docs/src/plots.md @@ -7,12 +7,6 @@ behaviour as much as possible. This will plot a line plot with 'a', 'b' and 'c' in the legend, and values 1-10 on the labelled X axis: -```@example Plots -using DimensionalData, Plots - -A = rand(X(1:10), Y([:a, :b, :c])) -Plots.plot(A) -``` Plots.jl support is deprecated, as development is moving to Makie.jl From 09c5d41044ab848d8eb1509289b3433fa63a95ee Mon Sep 17 00:00:00 2001 From: Anshul Singhvi Date: Sat, 17 Feb 2024 14:34:21 -0500 Subject: [PATCH 033/108] Refactor to new gh-pages deployment scheme (#632) * Refactor to new gh-pages deployment scheme * make docs actually run * try to fix CI deployment * again * copy+paste known good * fix again * yet another fix * try to avoid double CI * fix documenter key * add keys back in * here was the issue --- .github/workflows/Documenter.yml | 67 +- .github/workflows/ci.yml | 7 +- docs/logo.jl | 6 +- docs/make.jl | 43 +- docs/package-lock.json | 2774 ++++++++++++++++++++++++++++++ docs/package.json | 13 + docs/src/.vitepress/config.mts | 2 +- package-lock.json | 1694 ------------------ package.json | 10 - 9 files changed, 2863 insertions(+), 1753 deletions(-) create mode 100644 docs/package-lock.json create mode 100644 docs/package.json delete mode 100644 package-lock.json delete mode 100644 package.json diff --git a/.github/workflows/Documenter.yml b/.github/workflows/Documenter.yml index 8bb2007dd..c1c3b240a 100644 --- a/.github/workflows/Documenter.yml +++ b/.github/workflows/Documenter.yml @@ -1,23 +1,22 @@ # Sample workflow for building and deploying a VitePress site to GitHub Pages # -name: Deploy VitePress site to Pages +name: Deploy documentation on: # Runs on pushes targeting the `main` branch. Change this to `master` if you're # using the `master` branch as the default branch. push: - branches: [main] + branches: + - master + tags: ['*'] pull_request: - branches: [main] - types: [opened, synchronize, reopened] # Allows you to run this workflow manually from the Actions tab workflow_dispatch: # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages permissions: - actions: write # useful to delete gh cache - contents: read + contents: write pages: write id-token: write @@ -34,48 +33,30 @@ jobs: steps: - name: Checkout uses: actions/checkout@v4 - with: - fetch-depth: 0 # Not needed if lastUpdated is not enabled - # - uses: pnpm/action-setup@v2 # Uncomment this if you're using pnpm - # - uses: oven-sh/setup-bun@v1 # Uncomment this if you're using Bun + with: # Fetches the last commit only + fetch-depth: 0 - name: Setup Node - uses: actions/setup-node@v4 + uses: actions/setup-node@v3 with: node-version: 20 cache: npm # or pnpm / yarn - cache-dependency-path: 'package-lock.json' # this should be a package-lock.json file - - name: Setup Pages - uses: actions/configure-pages@v4 - - name: Install dependencies - run: npm ci # or pnpm install / yarn install / bun install - - name: Julia version + cache-dependency-path: 'docs/package-lock.json' # this should be a package-lock.json file + - name: Setup Julia uses: julia-actions/setup-julia@v1 - - name: Julia cache + - name: Pull Julia cache uses: julia-actions/cache@v1 - - name: Install documentation dependencies + - name: Install custom documentation dependencies run: julia --project=docs -e 'using Pkg; pkg"add https://github.com/LuxDL/DocumenterVitepress.jl.git"; pkg"dev ."; Pkg.instantiate(); Pkg.precompile(); Pkg.status()' - - name: generating examples md files - run: | - julia --project=docs/ --color=yes docs/logo.jl - julia --project=docs/ --color=yes docs/make.jl - - name: Build with VitePress + - name: Instantiate NPM + run: cd docs/; npm i; cd .. + - name: Generate logo + run: julia --project=docs/ --color=yes docs/logo.jl + - name: Build and deploy + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # For authentication with GitHub Actions token + DOCUMENTER_KEY: ${{ secrets.DOCUMENTER_KEY }} # For authentication with SSH deploy key + GKSwstype: "100" # https://discourse.julialang.org/t/generation-of-documentation-fails-qt-qpa-xcb-could-not-connect-to-display/60988 + JULIA_DEBUG: "Documenter" + DATADEPS_ALWAYS_ACCEPT: true run: | - NODE_OPTIONS=--max-old-space-size=32768 npm run docs:build # or pnpm docs:build / yarn docs:build / bun run docs:build - touch docs/docs_site/.vitepress/dist/.nojekyll - - name: Upload artifact - uses: actions/upload-pages-artifact@v3 - with: - path: docs/docs_site/.vitepress/dist - - # Deployment job - deploy: - environment: - name: github-pages - url: ${{ steps.deployment.outputs.page_url }} - needs: build - runs-on: ubuntu-latest - name: Deploy - steps: - - name: Deploy to GitHub Pages - id: deployment - uses: actions/deploy-pages@v4 \ No newline at end of file + julia --project=docs/ --color=yes docs/make.jl # this should ideally AUTO-DEPLOY! \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 57cc562db..ddefb72dc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,7 +1,10 @@ name: CI on: - - push - - pull_request + push: + branches: + - main + tags: ['*'] + pull_request: defaults: run: shell: bash diff --git a/docs/logo.jl b/docs/logo.jl index 7ca651abf..e538c459e 100644 --- a/docs/logo.jl +++ b/docs/logo.jl @@ -15,7 +15,7 @@ fig = Figure(; size=(500,500), ax = LScene(fig[1,1]; show_axis=false) wireframe!.(ax, rpyz; color = colors[3], transparency=true) # shading=NoShading # bug! -mesh!.(ax, rmyz; color=colors[1], transparency=true, shading=NoShading) +poly!.(ax, rmyz; color=colors[1], transparency=true, shading=NoShading) meshscatter!(ax, [Point3f(0.1,0.1,0.8), Point3f(0.1+7,0.1,0.8), Point3f(0.1,0.1+7,0.8), Point3f(0.1+7,0.1+7,0.8)]; color = colors[4], @@ -32,5 +32,7 @@ lines!(ax, [ Point3f(0.1+7,0.1,8), Point3f(0.1+7,0.1+7,8), ]; color = colors[2], linewidth=2, transparency=true) -save(joinpath(@__DIR__, "./src/public/logoDD.png"), fig) + +save(joinpath(@__DIR__, "src", "public", "logoDD.svg"), fig; pt_per_unit=0.75) +save(joinpath(@__DIR__, "src", "public", "logoDD.png"), fig; px_per_unit=2) fig \ No newline at end of file diff --git a/docs/make.jl b/docs/make.jl index f81a253a4..aa11792f2 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -8,6 +8,47 @@ makedocs(; sitename="DimensionalData", authors="Rafael Schouten et al.", format=DocumenterVitepress.MarkdownVitepress(), draft=false, source="src", - build=joinpath(@__DIR__, "docs_site"), + build=joinpath(@__DIR__, "build"), warnonly = true, +) + + +# We manually obtain the Documenter deploy configuration, +# so we can use it to set Vitepress's settings. +# TODO: make this better / encapsulate it in `makedocs` +# so the user does not need to know! +deploy_config = Documenter.auto_detect_deploy_system() +deploy_decision = Documenter.deploy_folder( + deploy_config; + repo="github.com/rafaqz/DimensionalData.jl", + devbranch="main", + devurl = "dev", + push_preview=true, +) + +# VitePress relies on its config file in order to understand where files will exist. +# We need to modify this file to reflect the correct base URL, however, Documenter +# only knows about the base URL at the time of deployment. + +# So, after building the Markdown, we need to modify the config file to reflect the +# correct base URL, and then build the VitePress site. +folder = deploy_decision.subfolder +println("Deploying to $folder") +vitepress_config_file = joinpath(@__DIR__, "build", ".vitepress", "config.mts") +config = read(vitepress_config_file, String) +new_config = replace(config, "base: 'REPLACE_ME_WITH_DOCUMENTER_VITEPRESS_BASE_URL_WITH_TRAILING_SLASH'" => "base: '/DimensionalData.jl/$folder/'") +write(vitepress_config_file, new_config) + +# Build the docs using `npm` - we are assuming it's installed here! +cd(@__DIR__) do + run(`npm run docs:build`) +end +touch(joinpath(@__DIR__, "build", ".vitepress", "dist", ".nojekyll")) + +deploydocs(; + repo="github.com/rafaqz/DimensionalData.jl", + target="build/.vitepress/dist", # this is where Vitepress stores its output + branch = "gh-pages", + devbranch = "main", + push_preview = true ) \ No newline at end of file diff --git a/docs/package-lock.json b/docs/package-lock.json new file mode 100644 index 000000000..b870e7fc5 --- /dev/null +++ b/docs/package-lock.json @@ -0,0 +1,2774 @@ +{ + "name": "DimensionalData", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "devDependencies": { + "markdown-it-mathjax3": "^4.3.2", + "vitepress": "^1.0.0-rc.41", + "vitepress-plugin-tabs": "^0.5.0", + "vitest": "^1.2.0" + } + }, + "node_modules/@algolia/autocomplete-core": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-core/-/autocomplete-core-1.9.3.tgz", + "integrity": "sha512-009HdfugtGCdC4JdXUbVJClA0q0zh24yyePn+KUGk3rP7j8FEe/m5Yo/z65gn6nP/cM39PxpzqKrL7A6fP6PPw==", + "dev": true, + "dependencies": { + "@algolia/autocomplete-plugin-algolia-insights": "1.9.3", + "@algolia/autocomplete-shared": "1.9.3" + } + }, + "node_modules/@algolia/autocomplete-plugin-algolia-insights": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-plugin-algolia-insights/-/autocomplete-plugin-algolia-insights-1.9.3.tgz", + "integrity": "sha512-a/yTUkcO/Vyy+JffmAnTWbr4/90cLzw+CC3bRbhnULr/EM0fGNvM13oQQ14f2moLMcVDyAx/leczLlAOovhSZg==", + "dev": true, + "dependencies": { + "@algolia/autocomplete-shared": "1.9.3" + }, + "peerDependencies": { + "search-insights": ">= 1 < 3" + } + }, + "node_modules/@algolia/autocomplete-preset-algolia": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.9.3.tgz", + "integrity": "sha512-d4qlt6YmrLMYy95n5TB52wtNDr6EgAIPH81dvvvW8UmuWRgxEtY0NJiPwl/h95JtG2vmRM804M0DSwMCNZlzRA==", + "dev": true, + "dependencies": { + "@algolia/autocomplete-shared": "1.9.3" + }, + "peerDependencies": { + "@algolia/client-search": ">= 4.9.1 < 6", + "algoliasearch": ">= 4.9.1 < 6" + } + }, + "node_modules/@algolia/autocomplete-shared": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-shared/-/autocomplete-shared-1.9.3.tgz", + "integrity": "sha512-Wnm9E4Ye6Rl6sTTqjoymD+l8DjSTHsHboVRYrKgEt8Q7UHm9nYbqhN/i0fhUYA3OAEH7WA8x3jfpnmJm3rKvaQ==", + "dev": true, + "peerDependencies": { + "@algolia/client-search": ">= 4.9.1 < 6", + "algoliasearch": ">= 4.9.1 < 6" + } + }, + "node_modules/@algolia/cache-browser-local-storage": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/@algolia/cache-browser-local-storage/-/cache-browser-local-storage-4.22.1.tgz", + "integrity": "sha512-Sw6IAmOCvvP6QNgY9j+Hv09mvkvEIDKjYW8ow0UDDAxSXy664RBNQk3i/0nt7gvceOJ6jGmOTimaZoY1THmU7g==", + "dev": true, + "dependencies": { + "@algolia/cache-common": "4.22.1" + } + }, + "node_modules/@algolia/cache-common": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/@algolia/cache-common/-/cache-common-4.22.1.tgz", + "integrity": "sha512-TJMBKqZNKYB9TptRRjSUtevJeQVXRmg6rk9qgFKWvOy8jhCPdyNZV1nB3SKGufzvTVbomAukFR8guu/8NRKBTA==", + "dev": true + }, + "node_modules/@algolia/cache-in-memory": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/@algolia/cache-in-memory/-/cache-in-memory-4.22.1.tgz", + "integrity": "sha512-ve+6Ac2LhwpufuWavM/aHjLoNz/Z/sYSgNIXsinGofWOysPilQZPUetqLj8vbvi+DHZZaYSEP9H5SRVXnpsNNw==", + "dev": true, + "dependencies": { + "@algolia/cache-common": "4.22.1" + } + }, + "node_modules/@algolia/client-account": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/@algolia/client-account/-/client-account-4.22.1.tgz", + "integrity": "sha512-k8m+oegM2zlns/TwZyi4YgCtyToackkOpE+xCaKCYfBfDtdGOaVZCM5YvGPtK+HGaJMIN/DoTL8asbM3NzHonw==", + "dev": true, + "dependencies": { + "@algolia/client-common": "4.22.1", + "@algolia/client-search": "4.22.1", + "@algolia/transporter": "4.22.1" + } + }, + "node_modules/@algolia/client-analytics": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-4.22.1.tgz", + "integrity": "sha512-1ssi9pyxyQNN4a7Ji9R50nSdISIumMFDwKNuwZipB6TkauJ8J7ha/uO60sPJFqQyqvvI+px7RSNRQT3Zrvzieg==", + "dev": true, + "dependencies": { + "@algolia/client-common": "4.22.1", + "@algolia/client-search": "4.22.1", + "@algolia/requester-common": "4.22.1", + "@algolia/transporter": "4.22.1" + } + }, + "node_modules/@algolia/client-common": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.22.1.tgz", + "integrity": "sha512-IvaL5v9mZtm4k4QHbBGDmU3wa/mKokmqNBqPj0K7lcR8ZDKzUorhcGp/u8PkPC/e0zoHSTvRh7TRkGX3Lm7iOQ==", + "dev": true, + "dependencies": { + "@algolia/requester-common": "4.22.1", + "@algolia/transporter": "4.22.1" + } + }, + "node_modules/@algolia/client-personalization": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-4.22.1.tgz", + "integrity": "sha512-sl+/klQJ93+4yaqZ7ezOttMQ/nczly/3GmgZXJ1xmoewP5jmdP/X/nV5U7EHHH3hCUEHeN7X1nsIhGPVt9E1cQ==", + "dev": true, + "dependencies": { + "@algolia/client-common": "4.22.1", + "@algolia/requester-common": "4.22.1", + "@algolia/transporter": "4.22.1" + } + }, + "node_modules/@algolia/client-search": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-4.22.1.tgz", + "integrity": "sha512-yb05NA4tNaOgx3+rOxAmFztgMTtGBi97X7PC3jyNeGiwkAjOZc2QrdZBYyIdcDLoI09N0gjtpClcackoTN0gPA==", + "dev": true, + "dependencies": { + "@algolia/client-common": "4.22.1", + "@algolia/requester-common": "4.22.1", + "@algolia/transporter": "4.22.1" + } + }, + "node_modules/@algolia/logger-common": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/@algolia/logger-common/-/logger-common-4.22.1.tgz", + "integrity": "sha512-OnTFymd2odHSO39r4DSWRFETkBufnY2iGUZNrMXpIhF5cmFE8pGoINNPzwg02QLBlGSaLqdKy0bM8S0GyqPLBg==", + "dev": true + }, + "node_modules/@algolia/logger-console": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/@algolia/logger-console/-/logger-console-4.22.1.tgz", + "integrity": "sha512-O99rcqpVPKN1RlpgD6H3khUWylU24OXlzkavUAMy6QZd1776QAcauE3oP8CmD43nbaTjBexZj2nGsBH9Tc0FVA==", + "dev": true, + "dependencies": { + "@algolia/logger-common": "4.22.1" + } + }, + "node_modules/@algolia/requester-browser-xhr": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-4.22.1.tgz", + "integrity": "sha512-dtQGYIg6MteqT1Uay3J/0NDqD+UciHy3QgRbk7bNddOJu+p3hzjTRYESqEnoX/DpEkaNYdRHUKNylsqMpgwaEw==", + "dev": true, + "dependencies": { + "@algolia/requester-common": "4.22.1" + } + }, + "node_modules/@algolia/requester-common": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/@algolia/requester-common/-/requester-common-4.22.1.tgz", + "integrity": "sha512-dgvhSAtg2MJnR+BxrIFqlLtkLlVVhas9HgYKMk2Uxiy5m6/8HZBL40JVAMb2LovoPFs9I/EWIoFVjOrFwzn5Qg==", + "dev": true + }, + "node_modules/@algolia/requester-node-http": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-4.22.1.tgz", + "integrity": "sha512-JfmZ3MVFQkAU+zug8H3s8rZ6h0ahHZL/SpMaSasTCGYR5EEJsCc8SI5UZ6raPN2tjxa5bxS13BRpGSBUens7EA==", + "dev": true, + "dependencies": { + "@algolia/requester-common": "4.22.1" + } + }, + "node_modules/@algolia/transporter": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/@algolia/transporter/-/transporter-4.22.1.tgz", + "integrity": "sha512-kzWgc2c9IdxMa3YqA6TN0NW5VrKYYW/BELIn7vnLyn+U/RFdZ4lxxt9/8yq3DKV5snvoDzzO4ClyejZRdV3lMQ==", + "dev": true, + "dependencies": { + "@algolia/cache-common": "4.22.1", + "@algolia/logger-common": "4.22.1", + "@algolia/requester-common": "4.22.1" + } + }, + "node_modules/@babel/parser": { + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.9.tgz", + "integrity": "sha512-9tcKgqKbs3xGJ+NtKF2ndOBBLVwPjl1SHxPQkd36r3Dlirw3xWUeGaTbqr7uGZcTaxkVNwc+03SVP7aCdWrTlA==", + "dev": true, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@docsearch/css": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-3.5.2.tgz", + "integrity": "sha512-SPiDHaWKQZpwR2siD0KQUwlStvIAnEyK6tAE2h2Wuoq8ue9skzhlyVQ1ddzOxX6khULnAALDiR/isSF3bnuciA==", + "dev": true + }, + "node_modules/@docsearch/js": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/@docsearch/js/-/js-3.5.2.tgz", + "integrity": "sha512-p1YFTCDflk8ieHgFJYfmyHBki1D61+U9idwrLh+GQQMrBSP3DLGKpy0XUJtPjAOPltcVbqsTjiPFfH7JImjUNg==", + "dev": true, + "dependencies": { + "@docsearch/react": "3.5.2", + "preact": "^10.0.0" + } + }, + "node_modules/@docsearch/react": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/@docsearch/react/-/react-3.5.2.tgz", + "integrity": "sha512-9Ahcrs5z2jq/DcAvYtvlqEBHImbm4YJI8M9y0x6Tqg598P40HTEkX7hsMcIuThI+hTFxRGZ9hll0Wygm2yEjng==", + "dev": true, + "dependencies": { + "@algolia/autocomplete-core": "1.9.3", + "@algolia/autocomplete-preset-algolia": "1.9.3", + "@docsearch/css": "3.5.2", + "algoliasearch": "^4.19.1" + }, + "peerDependencies": { + "@types/react": ">= 16.8.0 < 19.0.0", + "react": ">= 16.8.0 < 19.0.0", + "react-dom": ">= 16.8.0 < 19.0.0", + "search-insights": ">= 1 < 3" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + }, + "search-insights": { + "optional": true + } + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.19.11", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.11.tgz", + "integrity": "sha512-FnzU0LyE3ySQk7UntJO4+qIiQgI7KoODnZg5xzXIrFJlKd2P2gwHsHY4927xj9y5PJmJSzULiUCWmv7iWnNa7g==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.19.11", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.11.tgz", + "integrity": "sha512-5OVapq0ClabvKvQ58Bws8+wkLCV+Rxg7tUVbo9xu034Nm536QTII4YzhaFriQ7rMrorfnFKUsArD2lqKbFY4vw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.19.11", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.11.tgz", + "integrity": "sha512-aiu7K/5JnLj//KOnOfEZ0D90obUkRzDMyqd/wNAUQ34m4YUPVhRZpnqKV9uqDGxT7cToSDnIHsGooyIczu9T+Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.19.11", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.11.tgz", + "integrity": "sha512-eccxjlfGw43WYoY9QgB82SgGgDbibcqyDTlk3l3C0jOVHKxrjdc9CTwDUQd0vkvYg5um0OH+GpxYvp39r+IPOg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.19.11", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.11.tgz", + "integrity": "sha512-ETp87DRWuSt9KdDVkqSoKoLFHYTrkyz2+65fj9nfXsaV3bMhTCjtQfw3y+um88vGRKRiF7erPrh/ZuIdLUIVxQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.19.11", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.11.tgz", + "integrity": "sha512-fkFUiS6IUK9WYUO/+22omwetaSNl5/A8giXvQlcinLIjVkxwTLSktbF5f/kJMftM2MJp9+fXqZ5ezS7+SALp4g==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.19.11", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.11.tgz", + "integrity": "sha512-lhoSp5K6bxKRNdXUtHoNc5HhbXVCS8V0iZmDvyWvYq9S5WSfTIHU2UGjcGt7UeS6iEYp9eeymIl5mJBn0yiuxA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.19.11", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.11.tgz", + "integrity": "sha512-JkUqn44AffGXitVI6/AbQdoYAq0TEullFdqcMY/PCUZ36xJ9ZJRtQabzMA+Vi7r78+25ZIBosLTOKnUXBSi1Kw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.19.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.11.tgz", + "integrity": "sha512-3CRkr9+vCV2XJbjwgzjPtO8T0SZUmRZla+UL1jw+XqHZPkPgZiyWvbDvl9rqAN8Zl7qJF0O/9ycMtjU67HN9/Q==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.19.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.11.tgz", + "integrity": "sha512-LneLg3ypEeveBSMuoa0kwMpCGmpu8XQUh+mL8XXwoYZ6Be2qBnVtcDI5azSvh7vioMDhoJFZzp9GWp9IWpYoUg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.19.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.11.tgz", + "integrity": "sha512-caHy++CsD8Bgq2V5CodbJjFPEiDPq8JJmBdeyZ8GWVQMjRD0sU548nNdwPNvKjVpamYYVL40AORekgfIubwHoA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.19.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.11.tgz", + "integrity": "sha512-ppZSSLVpPrwHccvC6nQVZaSHlFsvCQyjnvirnVjbKSHuE5N24Yl8F3UwYUUR1UEPaFObGD2tSvVKbvR+uT1Nrg==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.19.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.11.tgz", + "integrity": "sha512-B5x9j0OgjG+v1dF2DkH34lr+7Gmv0kzX6/V0afF41FkPMMqaQ77pH7CrhWeR22aEeHKaeZVtZ6yFwlxOKPVFyg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.19.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.11.tgz", + "integrity": "sha512-MHrZYLeCG8vXblMetWyttkdVRjQlQUb/oMgBNurVEnhj4YWOr4G5lmBfZjHYQHHN0g6yDmCAQRR8MUHldvvRDA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.19.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.11.tgz", + "integrity": "sha512-f3DY++t94uVg141dozDu4CCUkYW+09rWtaWfnb3bqe4w5NqmZd6nPVBm+qbz7WaHZCoqXqHz5p6CM6qv3qnSSQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.19.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.11.tgz", + "integrity": "sha512-A5xdUoyWJHMMlcSMcPGVLzYzpcY8QP1RtYzX5/bS4dvjBGVxdhuiYyFwp7z74ocV7WDc0n1harxmpq2ePOjI0Q==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.19.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.11.tgz", + "integrity": "sha512-grbyMlVCvJSfxFQUndw5mCtWs5LO1gUlwP4CDi4iJBbVpZcqLVT29FxgGuBJGSzyOxotFG4LoO5X+M1350zmPA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.19.11", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.11.tgz", + "integrity": "sha512-13jvrQZJc3P230OhU8xgwUnDeuC/9egsjTkXN49b3GcS5BKvJqZn86aGM8W9pd14Kd+u7HuFBMVtrNGhh6fHEQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.19.11", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.11.tgz", + "integrity": "sha512-ysyOGZuTp6SNKPE11INDUeFVVQFrhcNDVUgSQVDzqsqX38DjhPEPATpid04LCoUr2WXhQTEZ8ct/EgJCUDpyNw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.19.11", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.11.tgz", + "integrity": "sha512-Hf+Sad9nVwvtxy4DXCZQqLpgmRTQqyFyhT3bZ4F2XlJCjxGmRFF0Shwn9rzhOYRB61w9VMXUkxlBy56dk9JJiQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.19.11", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.11.tgz", + "integrity": "sha512-0P58Sbi0LctOMOQbpEOvOL44Ne0sqbS0XWHMvvrg6NE5jQ1xguCSSw9jQeUk2lfrXYsKDdOe6K+oZiwKPilYPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.19.11", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.11.tgz", + "integrity": "sha512-6YOrWS+sDJDmshdBIQU+Uoyh7pQKrdykdefC1avn76ss5c+RN6gut3LZA4E2cH5xUEp5/cA0+YxRaVtRAb0xBg==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.19.11", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.11.tgz", + "integrity": "sha512-vfkhltrjCAb603XaFhqhAF4LGDi2M4OrCRrFusyQ+iTLQ/o60QQXxc9cZC/FFpihBI9N1Grn6SMKVJ4KP7Fuiw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.9.5.tgz", + "integrity": "sha512-idWaG8xeSRCfRq9KpRysDHJ/rEHBEXcHuJ82XY0yYFIWnLMjZv9vF/7DOq8djQ2n3Lk6+3qfSH8AqlmHlmi1MA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.9.5.tgz", + "integrity": "sha512-f14d7uhAMtsCGjAYwZGv6TwuS3IFaM4ZnGMUn3aCBgkcHAYErhV1Ad97WzBvS2o0aaDv4mVz+syiN0ElMyfBPg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.9.5.tgz", + "integrity": "sha512-ndoXeLx455FffL68OIUrVr89Xu1WLzAG4n65R8roDlCoYiQcGGg6MALvs2Ap9zs7AHg8mpHtMpwC8jBBjZrT/w==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.9.5.tgz", + "integrity": "sha512-UmElV1OY2m/1KEEqTlIjieKfVwRg0Zwg4PLgNf0s3glAHXBN99KLpw5A5lrSYCa1Kp63czTpVll2MAqbZYIHoA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.9.5.tgz", + "integrity": "sha512-Q0LcU61v92tQB6ae+udZvOyZ0wfpGojtAKrrpAaIqmJ7+psq4cMIhT/9lfV6UQIpeItnq/2QDROhNLo00lOD1g==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.9.5.tgz", + "integrity": "sha512-dkRscpM+RrR2Ee3eOQmRWFjmV/payHEOrjyq1VZegRUa5OrZJ2MAxBNs05bZuY0YCtpqETDy1Ix4i/hRqX98cA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.9.5.tgz", + "integrity": "sha512-QaKFVOzzST2xzY4MAmiDmURagWLFh+zZtttuEnuNn19AiZ0T3fhPyjPPGwLNdiDT82ZE91hnfJsUiDwF9DClIQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.9.5.tgz", + "integrity": "sha512-HeGqmRJuyVg6/X6MpE2ur7GbymBPS8Np0S/vQFHDmocfORT+Zt76qu+69NUoxXzGqVP1pzaY6QIi0FJWLC3OPA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.9.5.tgz", + "integrity": "sha512-Dq1bqBdLaZ1Gb/l2e5/+o3B18+8TI9ANlA1SkejZqDgdU/jK/ThYaMPMJpVMMXy2uRHvGKbkz9vheVGdq3cJfA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.9.5.tgz", + "integrity": "sha512-ezyFUOwldYpj7AbkwyW9AJ203peub81CaAIVvckdkyH8EvhEIoKzaMFJj0G4qYJ5sw3BpqhFrsCc30t54HV8vg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.9.5.tgz", + "integrity": "sha512-aHSsMnUw+0UETB0Hlv7B/ZHOGY5bQdwMKJSzGfDfvyhnpmVxLMGnQPGNE9wgqkLUs3+gbG1Qx02S2LLfJ5GaRQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.9.5.tgz", + "integrity": "sha512-AiqiLkb9KSf7Lj/o1U3SEP9Zn+5NuVKgFdRIZkvd4N0+bYrTOovVd0+LmYCPQGbocT4kvFyK+LXCDiXPBF3fyA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.9.5.tgz", + "integrity": "sha512-1q+mykKE3Vot1kaFJIDoUFv5TuW+QQVaf2FmTT9krg86pQrGStOSJJ0Zil7CFagyxDuouTepzt5Y5TVzyajOdQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@shikijs/core": { + "version": "1.0.0-beta.5", + "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-1.0.0-beta.5.tgz", + "integrity": "sha512-C/MxtvK3FFCQZSsDq6OfjDHHOmyP1Jc9wO66cnE8VLEyWXzWch7Zpoc2MWuVJTSC0Pz9QxyUlsBCnroplFqoSg==", + "dev": true + }, + "node_modules/@shikijs/transformers": { + "version": "1.0.0-beta.5", + "resolved": "https://registry.npmjs.org/@shikijs/transformers/-/transformers-1.0.0-beta.5.tgz", + "integrity": "sha512-Kd3312yH6sh8Jw0xjBFfGpXTU3Qts1bwuB19wDDoKRvJqjrkffftdSuKzhHPa+DP/L0ZFhq96xMPngzQ15rQmQ==", + "dev": true, + "dependencies": { + "shiki": "1.0.0-beta.5" + } + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true + }, + "node_modules/@types/estree": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "dev": true + }, + "node_modules/@types/linkify-it": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-3.0.5.tgz", + "integrity": "sha512-yg6E+u0/+Zjva+buc3EIb+29XEg4wltq7cSmd4Uc2EE/1nUVmxyzpX6gUXD0V8jIrG0r7YeOGVIbYRkxeooCtw==", + "dev": true + }, + "node_modules/@types/markdown-it": { + "version": "13.0.7", + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-13.0.7.tgz", + "integrity": "sha512-U/CBi2YUUcTHBt5tjO2r5QV/x0Po6nsYwQU4Y04fBS6vfoImaiZ6f8bi3CjTCxBPQSO1LMyUqkByzi8AidyxfA==", + "dev": true, + "dependencies": { + "@types/linkify-it": "*", + "@types/mdurl": "*" + } + }, + "node_modules/@types/mdurl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-1.0.5.tgz", + "integrity": "sha512-6L6VymKTzYSrEf4Nev4Xa1LCHKrlTlYCBMTlQKFuddo1CvQcE52I0mwfOJayueUC7MJuXOeHTcIU683lzd0cUA==", + "dev": true + }, + "node_modules/@types/web-bluetooth": { + "version": "0.0.20", + "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.20.tgz", + "integrity": "sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==", + "dev": true + }, + "node_modules/@vitejs/plugin-vue": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.0.3.tgz", + "integrity": "sha512-b8S5dVS40rgHdDrw+DQi/xOM9ed+kSRZzfm1T74bMmBDCd8XO87NKlFYInzCtwvtWwXZvo1QxE2OSspTATWrbA==", + "dev": true, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "vite": "^5.0.0", + "vue": "^3.2.25" + } + }, + "node_modules/@vitest/expect": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.2.0.tgz", + "integrity": "sha512-H+2bHzhyvgp32o7Pgj2h9RTHN0pgYaoi26Oo3mE+dCi1PAqV31kIIVfTbqMO3Bvshd5mIrJLc73EwSRrbol9Lw==", + "dev": true, + "dependencies": { + "@vitest/spy": "1.2.0", + "@vitest/utils": "1.2.0", + "chai": "^4.3.10" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.2.0.tgz", + "integrity": "sha512-vaJkDoQaNUTroT70OhM0NPznP7H3WyRwt4LvGwCVYs/llLaqhoSLnlIhUClZpbF5RgAee29KRcNz0FEhYcgxqA==", + "dev": true, + "dependencies": { + "@vitest/utils": "1.2.0", + "p-limit": "^5.0.0", + "pathe": "^1.1.1" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.2.0.tgz", + "integrity": "sha512-P33EE7TrVgB3HDLllrjK/GG6WSnmUtWohbwcQqmm7TAk9AVHpdgf7M3F3qRHKm6vhr7x3eGIln7VH052Smo6Kw==", + "dev": true, + "dependencies": { + "magic-string": "^0.30.5", + "pathe": "^1.1.1", + "pretty-format": "^29.7.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.2.0.tgz", + "integrity": "sha512-MNxSAfxUaCeowqyyGwC293yZgk7cECZU9wGb8N1pYQ0yOn/SIr8t0l9XnGRdQZvNV/ZHBYu6GO/W3tj5K3VN1Q==", + "dev": true, + "dependencies": { + "tinyspy": "^2.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.2.0.tgz", + "integrity": "sha512-FyD5bpugsXlwVpTcGLDf3wSPYy8g541fQt14qtzo8mJ4LdEpDKZ9mQy2+qdJm2TZRpjY5JLXihXCgIxiRJgi5g==", + "dev": true, + "dependencies": { + "diff-sequences": "^29.6.3", + "estree-walker": "^3.0.3", + "loupe": "^2.3.7", + "pretty-format": "^29.7.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vue/compiler-core": { + "version": "3.4.15", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.15.tgz", + "integrity": "sha512-XcJQVOaxTKCnth1vCxEChteGuwG6wqnUHxAm1DO3gCz0+uXKaJNx8/digSz4dLALCy8n2lKq24jSUs8segoqIw==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.23.6", + "@vue/shared": "3.4.15", + "entities": "^4.5.0", + "estree-walker": "^2.0.2", + "source-map-js": "^1.0.2" + } + }, + "node_modules/@vue/compiler-core/node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true + }, + "node_modules/@vue/compiler-dom": { + "version": "3.4.15", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.15.tgz", + "integrity": "sha512-wox0aasVV74zoXyblarOM3AZQz/Z+OunYcIHe1OsGclCHt8RsRm04DObjefaI82u6XDzv+qGWZ24tIsRAIi5MQ==", + "dev": true, + "dependencies": { + "@vue/compiler-core": "3.4.15", + "@vue/shared": "3.4.15" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.4.15", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.15.tgz", + "integrity": "sha512-LCn5M6QpkpFsh3GQvs2mJUOAlBQcCco8D60Bcqmf3O3w5a+KWS5GvYbrrJBkgvL1BDnTp+e8q0lXCLgHhKguBA==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.23.6", + "@vue/compiler-core": "3.4.15", + "@vue/compiler-dom": "3.4.15", + "@vue/compiler-ssr": "3.4.15", + "@vue/shared": "3.4.15", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.5", + "postcss": "^8.4.33", + "source-map-js": "^1.0.2" + } + }, + "node_modules/@vue/compiler-sfc/node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.4.15", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.15.tgz", + "integrity": "sha512-1jdeQyiGznr8gjFDadVmOJqZiLNSsMa5ZgqavkPZ8O2wjHv0tVuAEsw5hTdUoUW4232vpBbL/wJhzVW/JwY1Uw==", + "dev": true, + "dependencies": { + "@vue/compiler-dom": "3.4.15", + "@vue/shared": "3.4.15" + } + }, + "node_modules/@vue/devtools-api": { + "version": "7.0.14", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-7.0.14.tgz", + "integrity": "sha512-TluWR9qZ6aO11bwtYK8+fzXxBqLfsE0mWZz1q/EQBmO9k82Cm6deieLwNNXjNFJz7xutazoia5Qa+zTYkPPOfw==", + "dev": true, + "dependencies": { + "@vue/devtools-kit": "^7.0.14" + } + }, + "node_modules/@vue/devtools-kit": { + "version": "7.0.14", + "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-7.0.14.tgz", + "integrity": "sha512-wAAJazr4hI0aVRpgWOCVPw+NzMQdthhnprHHIg4njp1MkKrpCNGQ7MtQbZF1AltAA7xpMCGyyt+0kYH0FqTiPg==", + "dev": true, + "dependencies": { + "@vue/devtools-schema": "^7.0.14", + "@vue/devtools-shared": "^7.0.14", + "hookable": "^5.5.3", + "mitt": "^3.0.1", + "perfect-debounce": "^1.0.0", + "speakingurl": "^14.0.1" + } + }, + "node_modules/@vue/devtools-schema": { + "version": "7.0.14", + "resolved": "https://registry.npmjs.org/@vue/devtools-schema/-/devtools-schema-7.0.14.tgz", + "integrity": "sha512-tpUeCLVrdHX+KzWMLTAwx/vAPFbo6jAUi7sr6Q+0mBIqIVSSIxNr5wEhegiFvYva+OtDeM2OrT+f7/X/5bvZNg==", + "dev": true + }, + "node_modules/@vue/devtools-shared": { + "version": "7.0.14", + "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-7.0.14.tgz", + "integrity": "sha512-79RP1NDakBVWou9rDpVnT1WMjTbL1lJKm6YEOodjQ0dq5ehf0wsRbeYDhgAlnjehWRzTq5GAYFBFUPYBs0/QpA==", + "dev": true, + "dependencies": { + "rfdc": "^1.3.1" + } + }, + "node_modules/@vue/reactivity": { + "version": "3.4.15", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.15.tgz", + "integrity": "sha512-55yJh2bsff20K5O84MxSvXKPHHt17I2EomHznvFiJCAZpJTNW8IuLj1xZWMLELRhBK3kkFV/1ErZGHJfah7i7w==", + "dev": true, + "dependencies": { + "@vue/shared": "3.4.15" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.4.15", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.15.tgz", + "integrity": "sha512-6E3by5m6v1AkW0McCeAyhHTw+3y17YCOKG0U0HDKDscV4Hs0kgNT5G+GCHak16jKgcCDHpI9xe5NKb8sdLCLdw==", + "dev": true, + "dependencies": { + "@vue/reactivity": "3.4.15", + "@vue/shared": "3.4.15" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.4.15", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.15.tgz", + "integrity": "sha512-EVW8D6vfFVq3V/yDKNPBFkZKGMFSvZrUQmx196o/v2tHKdwWdiZjYUBS+0Ez3+ohRyF8Njwy/6FH5gYJ75liUw==", + "dev": true, + "dependencies": { + "@vue/runtime-core": "3.4.15", + "@vue/shared": "3.4.15", + "csstype": "^3.1.3" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.4.15", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.15.tgz", + "integrity": "sha512-3HYzaidu9cHjrT+qGUuDhFYvF/j643bHC6uUN9BgM11DVy+pM6ATsG6uPBLnkwOgs7BpJABReLmpL3ZPAsUaqw==", + "dev": true, + "dependencies": { + "@vue/compiler-ssr": "3.4.15", + "@vue/shared": "3.4.15" + }, + "peerDependencies": { + "vue": "3.4.15" + } + }, + "node_modules/@vue/shared": { + "version": "3.4.15", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.15.tgz", + "integrity": "sha512-KzfPTxVaWfB+eGcGdbSf4CWdaXcGDqckoeXUh7SB3fZdEtzPCK2Vq9B/lRRL3yutax/LWITz+SwvgyOxz5V75g==", + "dev": true + }, + "node_modules/@vueuse/core": { + "version": "10.7.2", + "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-10.7.2.tgz", + "integrity": "sha512-AOyAL2rK0By62Hm+iqQn6Rbu8bfmbgaIMXcE3TSr7BdQ42wnSFlwIdPjInO62onYsEMK/yDMU8C6oGfDAtZ2qQ==", + "dev": true, + "dependencies": { + "@types/web-bluetooth": "^0.0.20", + "@vueuse/metadata": "10.7.2", + "@vueuse/shared": "10.7.2", + "vue-demi": ">=0.14.6" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/core/node_modules/vue-demi": { + "version": "0.14.6", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.6.tgz", + "integrity": "sha512-8QA7wrYSHKaYgUxDA5ZC24w+eHm3sYCbp0EzcDwKqN3p6HqtTCGR/GVsPyZW92unff4UlcSh++lmqDWN3ZIq4w==", + "dev": true, + "hasInstallScript": true, + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/@vueuse/integrations": { + "version": "10.7.2", + "resolved": "https://registry.npmjs.org/@vueuse/integrations/-/integrations-10.7.2.tgz", + "integrity": "sha512-+u3RLPFedjASs5EKPc69Ge49WNgqeMfSxFn+qrQTzblPXZg6+EFzhjarS5edj2qAf6xQ93f95TUxRwKStXj/sQ==", + "dev": true, + "dependencies": { + "@vueuse/core": "10.7.2", + "@vueuse/shared": "10.7.2", + "vue-demi": ">=0.14.6" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "async-validator": "*", + "axios": "*", + "change-case": "*", + "drauu": "*", + "focus-trap": "*", + "fuse.js": "*", + "idb-keyval": "*", + "jwt-decode": "*", + "nprogress": "*", + "qrcode": "*", + "sortablejs": "*", + "universal-cookie": "*" + }, + "peerDependenciesMeta": { + "async-validator": { + "optional": true + }, + "axios": { + "optional": true + }, + "change-case": { + "optional": true + }, + "drauu": { + "optional": true + }, + "focus-trap": { + "optional": true + }, + "fuse.js": { + "optional": true + }, + "idb-keyval": { + "optional": true + }, + "jwt-decode": { + "optional": true + }, + "nprogress": { + "optional": true + }, + "qrcode": { + "optional": true + }, + "sortablejs": { + "optional": true + }, + "universal-cookie": { + "optional": true + } + } + }, + "node_modules/@vueuse/integrations/node_modules/vue-demi": { + "version": "0.14.6", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.6.tgz", + "integrity": "sha512-8QA7wrYSHKaYgUxDA5ZC24w+eHm3sYCbp0EzcDwKqN3p6HqtTCGR/GVsPyZW92unff4UlcSh++lmqDWN3ZIq4w==", + "dev": true, + "hasInstallScript": true, + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/@vueuse/metadata": { + "version": "10.7.2", + "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-10.7.2.tgz", + "integrity": "sha512-kCWPb4J2KGrwLtn1eJwaJD742u1k5h6v/St5wFe8Quih90+k2a0JP8BS4Zp34XUuJqS2AxFYMb1wjUL8HfhWsQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/shared": { + "version": "10.7.2", + "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-10.7.2.tgz", + "integrity": "sha512-qFbXoxS44pi2FkgFjPvF4h7c9oMDutpyBdcJdMYIMg9XyXli2meFMuaKn+UMgsClo//Th6+beeCgqweT/79BVA==", + "dev": true, + "dependencies": { + "vue-demi": ">=0.14.6" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/shared/node_modules/vue-demi": { + "version": "0.14.6", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.6.tgz", + "integrity": "sha512-8QA7wrYSHKaYgUxDA5ZC24w+eHm3sYCbp0EzcDwKqN3p6HqtTCGR/GVsPyZW92unff4UlcSh++lmqDWN3ZIq4w==", + "dev": true, + "hasInstallScript": true, + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/acorn": { + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", + "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/algoliasearch": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-4.22.1.tgz", + "integrity": "sha512-jwydKFQJKIx9kIZ8Jm44SdpigFwRGPESaxZBaHSV0XWN2yBJAOT4mT7ppvlrpA4UGzz92pqFnVKr/kaZXrcreg==", + "dev": true, + "dependencies": { + "@algolia/cache-browser-local-storage": "4.22.1", + "@algolia/cache-common": "4.22.1", + "@algolia/cache-in-memory": "4.22.1", + "@algolia/client-account": "4.22.1", + "@algolia/client-analytics": "4.22.1", + "@algolia/client-common": "4.22.1", + "@algolia/client-personalization": "4.22.1", + "@algolia/client-search": "4.22.1", + "@algolia/logger-common": "4.22.1", + "@algolia/logger-console": "4.22.1", + "@algolia/requester-browser-xhr": "4.22.1", + "@algolia/requester-common": "4.22.1", + "@algolia/requester-node-http": "4.22.1", + "@algolia/transporter": "4.22.1" + } + }, + "node_modules/ansi-colors": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "dev": true + }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/chai": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.4.1.tgz", + "integrity": "sha512-13sOfMv2+DWduEU+/xbun3LScLoqN17nBeTLUsmDfKdoiC1fr0n9PU4guu4AhRcOVFk/sW8LyZWHuhWtQZiF+g==", + "dev": true, + "dependencies": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.3", + "deep-eql": "^4.1.3", + "get-func-name": "^2.0.2", + "loupe": "^2.3.6", + "pathval": "^1.1.1", + "type-detect": "^4.0.8" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/check-error": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", + "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==", + "dev": true, + "dependencies": { + "get-func-name": "^2.0.2" + }, + "engines": { + "node": "*" + } + }, + "node_modules/cheerio": { + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.10.tgz", + "integrity": "sha512-g0J0q/O6mW8z5zxQ3A8E8J1hUgp4SMOvEoW/x84OwyHKe/Zccz83PVT4y5Crcr530FV6NgmKI1qvGTKVl9XXVw==", + "dev": true, + "dependencies": { + "cheerio-select": "^1.5.0", + "dom-serializer": "^1.3.2", + "domhandler": "^4.2.0", + "htmlparser2": "^6.1.0", + "parse5": "^6.0.1", + "parse5-htmlparser2-tree-adapter": "^6.0.1", + "tslib": "^2.2.0" + }, + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/cheeriojs/cheerio?sponsor=1" + } + }, + "node_modules/cheerio-select": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-1.6.0.tgz", + "integrity": "sha512-eq0GdBvxVFbqWgmCm7M3XGs1I8oLy/nExUnh6oLqmBditPO9AqQJrkslDpMun/hZ0yyTs8L0m85OHp4ho6Qm9g==", + "dev": true, + "dependencies": { + "css-select": "^4.3.0", + "css-what": "^6.0.1", + "domelementtype": "^2.2.0", + "domhandler": "^4.3.1", + "domutils": "^2.8.0" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/commander": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", + "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css-select": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz", + "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==", + "dev": true, + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.0.1", + "domhandler": "^4.3.1", + "domutils": "^2.8.0", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-what": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", + "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", + "dev": true, + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "dev": true + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-eql": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", + "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==", + "dev": true, + "dependencies": { + "type-detect": "^4.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/dom-serializer": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", + "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", + "dev": true, + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^4.2.0", + "entities": "^2.0.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/dom-serializer/node_modules/entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "dev": true, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ] + }, + "node_modules/domhandler": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", + "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", + "dev": true, + "dependencies": { + "domelementtype": "^2.2.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", + "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", + "dev": true, + "dependencies": { + "dom-serializer": "^1.0.1", + "domelementtype": "^2.2.0", + "domhandler": "^4.2.0" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/esbuild": { + "version": "0.19.11", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.11.tgz", + "integrity": "sha512-HJ96Hev2hX/6i5cDVwcqiJBBtuo9+FeIJOtZ9W1kA5M6AMJRHUZlpYZ1/SbEwtO0ioNAW8rUooVpC/WehY2SfA==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.19.11", + "@esbuild/android-arm": "0.19.11", + "@esbuild/android-arm64": "0.19.11", + "@esbuild/android-x64": "0.19.11", + "@esbuild/darwin-arm64": "0.19.11", + "@esbuild/darwin-x64": "0.19.11", + "@esbuild/freebsd-arm64": "0.19.11", + "@esbuild/freebsd-x64": "0.19.11", + "@esbuild/linux-arm": "0.19.11", + "@esbuild/linux-arm64": "0.19.11", + "@esbuild/linux-ia32": "0.19.11", + "@esbuild/linux-loong64": "0.19.11", + "@esbuild/linux-mips64el": "0.19.11", + "@esbuild/linux-ppc64": "0.19.11", + "@esbuild/linux-riscv64": "0.19.11", + "@esbuild/linux-s390x": "0.19.11", + "@esbuild/linux-x64": "0.19.11", + "@esbuild/netbsd-x64": "0.19.11", + "@esbuild/openbsd-x64": "0.19.11", + "@esbuild/sunos-x64": "0.19.11", + "@esbuild/win32-arm64": "0.19.11", + "@esbuild/win32-ia32": "0.19.11", + "@esbuild/win32-x64": "0.19.11" + } + }, + "node_modules/escape-goat": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-3.0.0.tgz", + "integrity": "sha512-w3PwNZJwRxlp47QGzhuEBldEqVHHhh8/tIPcl6ecf2Bou99cdAt0knihBV0Ecc7CGxYduXVBDheH1K2oADRlvw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/esm": { + "version": "3.2.25", + "resolved": "https://registry.npmjs.org/esm/-/esm-3.2.25.tgz", + "integrity": "sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/execa": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", + "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^8.0.1", + "human-signals": "^5.0.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/focus-trap": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-7.5.4.tgz", + "integrity": "sha512-N7kHdlgsO/v+iD/dMoJKtsSqs5Dz/dXZVebRgJw23LDk+jMi/974zyiOYDziY2JPp8xivq9BmUGwIJMiuSBi7w==", + "dev": true, + "dependencies": { + "tabbable": "^6.2.0" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-func-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", + "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/get-stream": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", + "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", + "dev": true, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/hookable": { + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz", + "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==", + "dev": true + }, + "node_modules/htmlparser2": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz", + "integrity": "sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==", + "dev": true, + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^4.0.0", + "domutils": "^2.5.2", + "entities": "^2.0.0" + } + }, + "node_modules/htmlparser2/node_modules/entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "dev": true, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/human-signals": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", + "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", + "dev": true, + "engines": { + "node": ">=16.17.0" + } + }, + "node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/jsonc-parser": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", + "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==", + "dev": true + }, + "node_modules/juice": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/juice/-/juice-8.1.0.tgz", + "integrity": "sha512-FLzurJrx5Iv1e7CfBSZH68dC04EEvXvvVvPYB7Vx1WAuhCp1ZPIMtqxc+WTWxVkpTIC2Ach/GAv0rQbtGf6YMA==", + "dev": true, + "dependencies": { + "cheerio": "1.0.0-rc.10", + "commander": "^6.1.0", + "mensch": "^0.3.4", + "slick": "^1.12.2", + "web-resource-inliner": "^6.0.1" + }, + "bin": { + "juice": "bin/juice" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/local-pkg": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.5.0.tgz", + "integrity": "sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg==", + "dev": true, + "dependencies": { + "mlly": "^1.4.2", + "pkg-types": "^1.0.3" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/loupe": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", + "integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==", + "dev": true, + "dependencies": { + "get-func-name": "^2.0.1" + } + }, + "node_modules/magic-string": { + "version": "0.30.5", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.5.tgz", + "integrity": "sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==", + "dev": true, + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/mark.js": { + "version": "8.11.1", + "resolved": "https://registry.npmjs.org/mark.js/-/mark.js-8.11.1.tgz", + "integrity": "sha512-1I+1qpDt4idfgLQG+BNWmrqku+7/2bi5nLf4YwF8y8zXvmfiTBY3PV3ZibfrjBueCByROpuBjLLFCajqkgYoLQ==", + "dev": true + }, + "node_modules/markdown-it-mathjax3": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/markdown-it-mathjax3/-/markdown-it-mathjax3-4.3.2.tgz", + "integrity": "sha512-TX3GW5NjmupgFtMJGRauioMbbkGsOXAAt1DZ/rzzYmTHqzkO1rNAdiMD4NiruurToPApn2kYy76x02QN26qr2w==", + "dev": true, + "dependencies": { + "juice": "^8.0.0", + "mathjax-full": "^3.2.0" + } + }, + "node_modules/mathjax-full": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/mathjax-full/-/mathjax-full-3.2.2.tgz", + "integrity": "sha512-+LfG9Fik+OuI8SLwsiR02IVdjcnRCy5MufYLi0C3TdMT56L/pjB0alMVGgoWJF8pN9Rc7FESycZB9BMNWIid5w==", + "dev": true, + "dependencies": { + "esm": "^3.2.25", + "mhchemparser": "^4.1.0", + "mj-context-menu": "^0.6.1", + "speech-rule-engine": "^4.0.6" + } + }, + "node_modules/mensch": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/mensch/-/mensch-0.3.4.tgz", + "integrity": "sha512-IAeFvcOnV9V0Yk+bFhYR07O3yNina9ANIN5MoXBKYJ/RLYPurd2d0yw14MDhpr9/momp0WofT1bPUh3hkzdi/g==", + "dev": true + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "node_modules/mhchemparser": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/mhchemparser/-/mhchemparser-4.2.1.tgz", + "integrity": "sha512-kYmyrCirqJf3zZ9t/0wGgRZ4/ZJw//VwaRVGA75C4nhE60vtnIzhl9J9ndkX/h6hxSN7pjg/cE0VxbnNM+bnDQ==", + "dev": true + }, + "node_modules/mime": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "dev": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minisearch": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/minisearch/-/minisearch-6.3.0.tgz", + "integrity": "sha512-ihFnidEeU8iXzcVHy74dhkxh/dn8Dc08ERl0xwoMMGqp4+LvRSCgicb+zGqWthVokQKvCSxITlh3P08OzdTYCQ==", + "dev": true + }, + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", + "dev": true + }, + "node_modules/mj-context-menu": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/mj-context-menu/-/mj-context-menu-0.6.1.tgz", + "integrity": "sha512-7NO5s6n10TIV96d4g2uDpG7ZDpIhMh0QNfGdJw/W47JswFcosz457wqz/b5sAKvl12sxINGFCn80NZHKwxQEXA==", + "dev": true + }, + "node_modules/mlly": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.5.0.tgz", + "integrity": "sha512-NPVQvAY1xr1QoVeG0cy8yUYC7FQcOx6evl/RjT1wL5FvzPnzOysoqB/jmx/DhssT2dYa8nxECLAaFI/+gVLhDQ==", + "dev": true, + "dependencies": { + "acorn": "^8.11.3", + "pathe": "^1.1.2", + "pkg-types": "^1.0.3", + "ufo": "^1.3.2" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dev": true, + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/npm-run-path": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.2.0.tgz", + "integrity": "sha512-W4/tgAXFqFA0iL7fk0+uQ3g7wkL8xJmx3XdK0VGb4cHW//eZTtKGvFBBoRKVTpY7n6ze4NL9ly7rgXcHufqXKg==", + "dev": true, + "dependencies": { + "path-key": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dev": true, + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dev": true, + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-limit": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-5.0.0.tgz", + "integrity": "sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", + "dev": true + }, + "node_modules/parse5-htmlparser2-tree-adapter": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz", + "integrity": "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==", + "dev": true, + "dependencies": { + "parse5": "^6.0.1" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/pathe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", + "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "dev": true + }, + "node_modules/pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/perfect-debounce": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", + "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", + "dev": true + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "node_modules/pkg-types": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.0.3.tgz", + "integrity": "sha512-nN7pYi0AQqJnoLPC9eHFQ8AcyaixBUOwvqc5TDnIKCMEE6I0y8P7OKA7fPexsXGCGxQDl/cmrLAp26LhcwxZ4A==", + "dev": true, + "dependencies": { + "jsonc-parser": "^3.2.0", + "mlly": "^1.2.0", + "pathe": "^1.1.0" + } + }, + "node_modules/postcss": { + "version": "8.4.33", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.33.tgz", + "integrity": "sha512-Kkpbhhdjw2qQs2O2DGX+8m5OVqEcbB9HRBvuYM9pgrjEFUg30A9LmXNlTAUj4S9kgtGyrMbTzVjH7E+s5Re2yg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/preact": { + "version": "10.19.3", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.19.3.tgz", + "integrity": "sha512-nHHTeFVBTHRGxJXKkKu5hT8C/YWBkPso4/Gad6xuj5dbptt9iF9NZr9pHbPhBrnT2klheu7mHTxTZ/LjwJiEiQ==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + } + }, + "node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + }, + "node_modules/rfdc": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.1.tgz", + "integrity": "sha512-r5a3l5HzYlIC68TpmYKlxWjmOP6wiPJ1vWv2HeLhNsRZMrCkxeqxiHlQ21oXmQ4F3SiryXBHhAD7JZqvOJjFmg==", + "dev": true + }, + "node_modules/rollup": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.9.5.tgz", + "integrity": "sha512-E4vQW0H/mbNMw2yLSqJyjtkHY9dslf/p0zuT1xehNRqUTBOFMqEjguDvqhXr7N7r/4ttb2jr4T41d3dncmIgbQ==", + "dev": true, + "dependencies": { + "@types/estree": "1.0.5" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.9.5", + "@rollup/rollup-android-arm64": "4.9.5", + "@rollup/rollup-darwin-arm64": "4.9.5", + "@rollup/rollup-darwin-x64": "4.9.5", + "@rollup/rollup-linux-arm-gnueabihf": "4.9.5", + "@rollup/rollup-linux-arm64-gnu": "4.9.5", + "@rollup/rollup-linux-arm64-musl": "4.9.5", + "@rollup/rollup-linux-riscv64-gnu": "4.9.5", + "@rollup/rollup-linux-x64-gnu": "4.9.5", + "@rollup/rollup-linux-x64-musl": "4.9.5", + "@rollup/rollup-win32-arm64-msvc": "4.9.5", + "@rollup/rollup-win32-ia32-msvc": "4.9.5", + "@rollup/rollup-win32-x64-msvc": "4.9.5", + "fsevents": "~2.3.2" + } + }, + "node_modules/search-insights": { + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/search-insights/-/search-insights-2.13.0.tgz", + "integrity": "sha512-Orrsjf9trHHxFRuo9/rzm0KIWmgzE8RMlZMzuhZOJ01Rnz3D0YBAe+V6473t6/H6c7irs6Lt48brULAiRWb3Vw==", + "dev": true, + "peer": true + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/shiki": { + "version": "1.0.0-beta.5", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-1.0.0-beta.5.tgz", + "integrity": "sha512-S5FV55ZH8zLicVyqlJZj8LYqh/VuUICDDNG/L9eDM9I4d69EX+FbgSnKRIuJIwLrmJfTiPoGVnH1HsHX5whP/g==", + "dev": true, + "dependencies": { + "@shikijs/core": "1.0.0-beta.5" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/slick": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/slick/-/slick-1.12.2.tgz", + "integrity": "sha512-4qdtOGcBjral6YIBCWJ0ljFSKNLz9KkhbWtuGvUyRowl1kxfuE1x/Z/aJcaiilpb3do9bl5K7/1h9XC5wWpY/A==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/speakingurl": { + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/speakingurl/-/speakingurl-14.0.1.tgz", + "integrity": "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/speech-rule-engine": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/speech-rule-engine/-/speech-rule-engine-4.0.7.tgz", + "integrity": "sha512-sJrL3/wHzNwJRLBdf6CjJWIlxC04iYKkyXvYSVsWVOiC2DSkHmxsqOhEeMsBA9XK+CHuNcsdkbFDnoUfAsmp9g==", + "dev": true, + "dependencies": { + "commander": "9.2.0", + "wicked-good-xpath": "1.3.0", + "xmldom-sre": "0.1.31" + }, + "bin": { + "sre": "bin/sre" + } + }, + "node_modules/speech-rule-engine/node_modules/commander": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.2.0.tgz", + "integrity": "sha512-e2i4wANQiSXgnrBlIatyHtP1odfUp0BbV5Y5nEGbxtIrStkEOAAzCUirvLBNXHLr7kwLvJl6V+4V3XV9x7Wd9w==", + "dev": true, + "engines": { + "node": "^12.20.0 || >=14" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true + }, + "node_modules/std-env": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.7.0.tgz", + "integrity": "sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==", + "dev": true + }, + "node_modules/strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-literal": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-1.3.0.tgz", + "integrity": "sha512-PugKzOsyXpArk0yWmUwqOZecSO0GH0bPoctLcqNDH9J04pVW3lflYE0ujElBGTloevcxF5MofAOZ7C5l2b+wLg==", + "dev": true, + "dependencies": { + "acorn": "^8.10.0" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/tabbable": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz", + "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==", + "dev": true + }, + "node_modules/tinybench": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.5.1.tgz", + "integrity": "sha512-65NKvSuAVDP/n4CqH+a9w2kTlLReS9vhsAP06MWx+/89nMinJyB2icyl58RIcqCmIggpojIGeuJGhjU1aGMBSg==", + "dev": true + }, + "node_modules/tinypool": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.8.1.tgz", + "integrity": "sha512-zBTCK0cCgRROxvs9c0CGK838sPkeokNGdQVUUwHAbynHFlmyJYj825f/oRs528HaIJ97lo0pLIlDUzwN+IorWg==", + "dev": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-2.2.0.tgz", + "integrity": "sha512-d2eda04AN/cPOR89F7Xv5bK/jrQEhmcLFe6HFldoeO9AJtps+fqEnh486vnT/8y4bw38pSyxDcTCAq+Ks2aJTg==", + "dev": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "dev": true + }, + "node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", + "dev": true + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/ufo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.3.2.tgz", + "integrity": "sha512-o+ORpgGwaYQXgqGDwd+hkS4PuZ3QnmqMMxRuajK/a38L6fTpcE5GPIfrf+L/KemFzfUpeUQc1rRS1iDBozvnFA==", + "dev": true + }, + "node_modules/valid-data-url": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/valid-data-url/-/valid-data-url-3.0.1.tgz", + "integrity": "sha512-jOWVmzVceKlVVdwjNSenT4PbGghU0SBIizAev8ofZVgivk/TVHXSbNL8LP6M3spZvkR9/QolkyJavGSX5Cs0UA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/vite": { + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.0.12.tgz", + "integrity": "sha512-4hsnEkG3q0N4Tzf1+t6NdN9dg/L3BM+q8SWgbSPnJvrgH2kgdyzfVJwbR1ic69/4uMJJ/3dqDZZE5/WwqW8U1w==", + "dev": true, + "dependencies": { + "esbuild": "^0.19.3", + "postcss": "^8.4.32", + "rollup": "^4.2.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vite-node": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.2.0.tgz", + "integrity": "sha512-ETnQTHeAbbOxl7/pyBck9oAPZZZo+kYnFt1uQDD+hPReOc+wCjXw4r4jHriBRuVDB5isHmPXxrfc1yJnfBERqg==", + "dev": true, + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.3.4", + "pathe": "^1.1.1", + "picocolors": "^1.0.0", + "vite": "^5.0.0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vitepress": { + "version": "1.0.0-rc.41", + "resolved": "https://registry.npmjs.org/vitepress/-/vitepress-1.0.0-rc.41.tgz", + "integrity": "sha512-PAEoIIc9J//k/Wg39C6k86hZpXPmLZjRiTBwieDNeYGdevD7xr5Ve8o1W/w+e9dtyQMkuvzgianEamXDX3aj7g==", + "dev": true, + "dependencies": { + "@docsearch/css": "^3.5.2", + "@docsearch/js": "^3.5.2", + "@shikijs/core": "^1.0.0-beta.3", + "@shikijs/transformers": "^1.0.0-beta.3", + "@types/markdown-it": "^13.0.7", + "@vitejs/plugin-vue": "^5.0.3", + "@vue/devtools-api": "^7.0.14", + "@vueuse/core": "^10.7.2", + "@vueuse/integrations": "^10.7.2", + "focus-trap": "^7.5.4", + "mark.js": "8.11.1", + "minisearch": "^6.3.0", + "shiki": "^1.0.0-beta.3", + "vite": "^5.0.12", + "vue": "^3.4.15" + }, + "bin": { + "vitepress": "bin/vitepress.js" + }, + "peerDependencies": { + "markdown-it-mathjax3": "^4.3.2", + "postcss": "^8.4.33" + }, + "peerDependenciesMeta": { + "markdown-it-mathjax3": { + "optional": true + }, + "postcss": { + "optional": true + } + } + }, + "node_modules/vitepress-plugin-tabs": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/vitepress-plugin-tabs/-/vitepress-plugin-tabs-0.5.0.tgz", + "integrity": "sha512-SIhFWwGsUkTByfc2b279ray/E0Jt8vDTsM1LiHxmCOBAEMmvzIBZSuYYT1DpdDTiS3SuJieBheJkYnwCq/yD9A==", + "dev": true, + "peerDependencies": { + "vitepress": "^1.0.0-rc.27", + "vue": "^3.3.8" + } + }, + "node_modules/vitest": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.2.0.tgz", + "integrity": "sha512-Ixs5m7BjqvLHXcibkzKRQUvD/XLw0E3rvqaCMlrm/0LMsA0309ZqYvTlPzkhh81VlEyVZXFlwWnkhb6/UMtcaQ==", + "dev": true, + "dependencies": { + "@vitest/expect": "1.2.0", + "@vitest/runner": "1.2.0", + "@vitest/snapshot": "1.2.0", + "@vitest/spy": "1.2.0", + "@vitest/utils": "1.2.0", + "acorn-walk": "^8.3.1", + "cac": "^6.7.14", + "chai": "^4.3.10", + "debug": "^4.3.4", + "execa": "^8.0.1", + "local-pkg": "^0.5.0", + "magic-string": "^0.30.5", + "pathe": "^1.1.1", + "picocolors": "^1.0.0", + "std-env": "^3.5.0", + "strip-literal": "^1.3.0", + "tinybench": "^2.5.1", + "tinypool": "^0.8.1", + "vite": "^5.0.0", + "vite-node": "1.2.0", + "why-is-node-running": "^2.2.2" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/node": "^18.0.0 || >=20.0.0", + "@vitest/browser": "^1.0.0", + "@vitest/ui": "^1.0.0", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/vue": { + "version": "3.4.15", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.15.tgz", + "integrity": "sha512-jC0GH4KkWLWJOEQjOpkqU1bQsBwf4R1rsFtw5GQJbjHVKWDzO6P0nWWBTmjp1xSemAioDFj1jdaK1qa3DnMQoQ==", + "dev": true, + "dependencies": { + "@vue/compiler-dom": "3.4.15", + "@vue/compiler-sfc": "3.4.15", + "@vue/runtime-dom": "3.4.15", + "@vue/server-renderer": "3.4.15", + "@vue/shared": "3.4.15" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/web-resource-inliner": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/web-resource-inliner/-/web-resource-inliner-6.0.1.tgz", + "integrity": "sha512-kfqDxt5dTB1JhqsCUQVFDj0rmY+4HLwGQIsLPbyrsN9y9WV/1oFDSx3BQ4GfCv9X+jVeQ7rouTqwK53rA/7t8A==", + "dev": true, + "dependencies": { + "ansi-colors": "^4.1.1", + "escape-goat": "^3.0.0", + "htmlparser2": "^5.0.0", + "mime": "^2.4.6", + "node-fetch": "^2.6.0", + "valid-data-url": "^3.0.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/web-resource-inliner/node_modules/domhandler": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-3.3.0.tgz", + "integrity": "sha512-J1C5rIANUbuYK+FuFL98650rihynUOEzRLxW+90bKZRWB6A1X1Tf82GxR1qAWLyfNPRvjqfip3Q5tdYlmAa9lA==", + "dev": true, + "dependencies": { + "domelementtype": "^2.0.1" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/web-resource-inliner/node_modules/entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "dev": true, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/web-resource-inliner/node_modules/htmlparser2": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-5.0.1.tgz", + "integrity": "sha512-vKZZra6CSe9qsJzh0BjBGXo8dvzNsq/oGvsjfRdOrrryfeD9UOBEEQdeoqCRmKZchF5h2zOBMQ6YuQ0uRUmdbQ==", + "dev": true, + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^3.3.0", + "domutils": "^2.4.2", + "entities": "^2.0.0" + }, + "funding": { + "url": "https://github.com/fb55/htmlparser2?sponsor=1" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "dev": true + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dev": true, + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/why-is-node-running": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.2.2.tgz", + "integrity": "sha512-6tSwToZxTOcotxHeA+qGCq1mVzKR3CwcJGmVcY+QE8SHy6TnpFnh8PAvPNHYr7EcuVeG0QSMxtYCuO1ta/G/oA==", + "dev": true, + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wicked-good-xpath": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/wicked-good-xpath/-/wicked-good-xpath-1.3.0.tgz", + "integrity": "sha512-Gd9+TUn5nXdwj/hFsPVx5cuHHiF5Bwuc30jZ4+ronF1qHK5O7HD0sgmXWSEgwKquT3ClLoKPVbO6qGwVwLzvAw==", + "dev": true + }, + "node_modules/xmldom-sre": { + "version": "0.1.31", + "resolved": "https://registry.npmjs.org/xmldom-sre/-/xmldom-sre-0.1.31.tgz", + "integrity": "sha512-f9s+fUkX04BxQf+7mMWAp5zk61pciie+fFLC9hX9UVvCeJQfNHRHXpeo5MPcR0EUf57PYLdt+ZO4f3Ipk2oZUw==", + "dev": true, + "engines": { + "node": ">=0.1" + } + }, + "node_modules/yocto-queue": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz", + "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==", + "dev": true, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/docs/package.json b/docs/package.json new file mode 100644 index 000000000..c21d8c433 --- /dev/null +++ b/docs/package.json @@ -0,0 +1,13 @@ +{ + "scripts": { + "docs:dev": "vitepress dev build", + "docs:build": "vitepress build build", + "docs:preview": "vitepress preview build" + }, + "devDependencies": { + "markdown-it-mathjax3": "^4.3.2", + "vitepress": "^1.0.0-rc.41", + "vitepress-plugin-tabs": "^0.5.0", + "vitest": "^1.2.0" + } +} diff --git a/docs/src/.vitepress/config.mts b/docs/src/.vitepress/config.mts index 575a28e50..673faf614 100644 --- a/docs/src/.vitepress/config.mts +++ b/docs/src/.vitepress/config.mts @@ -11,7 +11,7 @@ const VERSIONS: DefaultTheme.NavItemWithLink[] = [ // https://vitepress.dev/reference/site-config export default defineConfig({ - base: '/DimensionalData.jl/', + base: 'REPLACE_ME_WITH_DOCUMENTER_VITEPRESS_BASE_URL_WITH_TRAILING_SLASH', title: "DimensionalData", description: "Datasets with named dimensions", lastUpdated: true, diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index d033d9b14..000000000 --- a/package-lock.json +++ /dev/null @@ -1,1694 +0,0 @@ -{ - "name": "DimensionalData", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "devDependencies": { - "vitepress-plugin-tabs": "^0.5.0" - } - }, - "node_modules/@algolia/autocomplete-core": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-core/-/autocomplete-core-1.9.3.tgz", - "integrity": "sha512-009HdfugtGCdC4JdXUbVJClA0q0zh24yyePn+KUGk3rP7j8FEe/m5Yo/z65gn6nP/cM39PxpzqKrL7A6fP6PPw==", - "dev": true, - "peer": true, - "dependencies": { - "@algolia/autocomplete-plugin-algolia-insights": "1.9.3", - "@algolia/autocomplete-shared": "1.9.3" - } - }, - "node_modules/@algolia/autocomplete-plugin-algolia-insights": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-plugin-algolia-insights/-/autocomplete-plugin-algolia-insights-1.9.3.tgz", - "integrity": "sha512-a/yTUkcO/Vyy+JffmAnTWbr4/90cLzw+CC3bRbhnULr/EM0fGNvM13oQQ14f2moLMcVDyAx/leczLlAOovhSZg==", - "dev": true, - "peer": true, - "dependencies": { - "@algolia/autocomplete-shared": "1.9.3" - }, - "peerDependencies": { - "search-insights": ">= 1 < 3" - } - }, - "node_modules/@algolia/autocomplete-preset-algolia": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.9.3.tgz", - "integrity": "sha512-d4qlt6YmrLMYy95n5TB52wtNDr6EgAIPH81dvvvW8UmuWRgxEtY0NJiPwl/h95JtG2vmRM804M0DSwMCNZlzRA==", - "dev": true, - "peer": true, - "dependencies": { - "@algolia/autocomplete-shared": "1.9.3" - }, - "peerDependencies": { - "@algolia/client-search": ">= 4.9.1 < 6", - "algoliasearch": ">= 4.9.1 < 6" - } - }, - "node_modules/@algolia/autocomplete-shared": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-shared/-/autocomplete-shared-1.9.3.tgz", - "integrity": "sha512-Wnm9E4Ye6Rl6sTTqjoymD+l8DjSTHsHboVRYrKgEt8Q7UHm9nYbqhN/i0fhUYA3OAEH7WA8x3jfpnmJm3rKvaQ==", - "dev": true, - "peer": true, - "peerDependencies": { - "@algolia/client-search": ">= 4.9.1 < 6", - "algoliasearch": ">= 4.9.1 < 6" - } - }, - "node_modules/@algolia/cache-browser-local-storage": { - "version": "4.22.1", - "resolved": "https://registry.npmjs.org/@algolia/cache-browser-local-storage/-/cache-browser-local-storage-4.22.1.tgz", - "integrity": "sha512-Sw6IAmOCvvP6QNgY9j+Hv09mvkvEIDKjYW8ow0UDDAxSXy664RBNQk3i/0nt7gvceOJ6jGmOTimaZoY1THmU7g==", - "dev": true, - "peer": true, - "dependencies": { - "@algolia/cache-common": "4.22.1" - } - }, - "node_modules/@algolia/cache-common": { - "version": "4.22.1", - "resolved": "https://registry.npmjs.org/@algolia/cache-common/-/cache-common-4.22.1.tgz", - "integrity": "sha512-TJMBKqZNKYB9TptRRjSUtevJeQVXRmg6rk9qgFKWvOy8jhCPdyNZV1nB3SKGufzvTVbomAukFR8guu/8NRKBTA==", - "dev": true, - "peer": true - }, - "node_modules/@algolia/cache-in-memory": { - "version": "4.22.1", - "resolved": "https://registry.npmjs.org/@algolia/cache-in-memory/-/cache-in-memory-4.22.1.tgz", - "integrity": "sha512-ve+6Ac2LhwpufuWavM/aHjLoNz/Z/sYSgNIXsinGofWOysPilQZPUetqLj8vbvi+DHZZaYSEP9H5SRVXnpsNNw==", - "dev": true, - "peer": true, - "dependencies": { - "@algolia/cache-common": "4.22.1" - } - }, - "node_modules/@algolia/client-account": { - "version": "4.22.1", - "resolved": "https://registry.npmjs.org/@algolia/client-account/-/client-account-4.22.1.tgz", - "integrity": "sha512-k8m+oegM2zlns/TwZyi4YgCtyToackkOpE+xCaKCYfBfDtdGOaVZCM5YvGPtK+HGaJMIN/DoTL8asbM3NzHonw==", - "dev": true, - "peer": true, - "dependencies": { - "@algolia/client-common": "4.22.1", - "@algolia/client-search": "4.22.1", - "@algolia/transporter": "4.22.1" - } - }, - "node_modules/@algolia/client-analytics": { - "version": "4.22.1", - "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-4.22.1.tgz", - "integrity": "sha512-1ssi9pyxyQNN4a7Ji9R50nSdISIumMFDwKNuwZipB6TkauJ8J7ha/uO60sPJFqQyqvvI+px7RSNRQT3Zrvzieg==", - "dev": true, - "peer": true, - "dependencies": { - "@algolia/client-common": "4.22.1", - "@algolia/client-search": "4.22.1", - "@algolia/requester-common": "4.22.1", - "@algolia/transporter": "4.22.1" - } - }, - "node_modules/@algolia/client-common": { - "version": "4.22.1", - "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.22.1.tgz", - "integrity": "sha512-IvaL5v9mZtm4k4QHbBGDmU3wa/mKokmqNBqPj0K7lcR8ZDKzUorhcGp/u8PkPC/e0zoHSTvRh7TRkGX3Lm7iOQ==", - "dev": true, - "peer": true, - "dependencies": { - "@algolia/requester-common": "4.22.1", - "@algolia/transporter": "4.22.1" - } - }, - "node_modules/@algolia/client-personalization": { - "version": "4.22.1", - "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-4.22.1.tgz", - "integrity": "sha512-sl+/klQJ93+4yaqZ7ezOttMQ/nczly/3GmgZXJ1xmoewP5jmdP/X/nV5U7EHHH3hCUEHeN7X1nsIhGPVt9E1cQ==", - "dev": true, - "peer": true, - "dependencies": { - "@algolia/client-common": "4.22.1", - "@algolia/requester-common": "4.22.1", - "@algolia/transporter": "4.22.1" - } - }, - "node_modules/@algolia/client-search": { - "version": "4.22.1", - "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-4.22.1.tgz", - "integrity": "sha512-yb05NA4tNaOgx3+rOxAmFztgMTtGBi97X7PC3jyNeGiwkAjOZc2QrdZBYyIdcDLoI09N0gjtpClcackoTN0gPA==", - "dev": true, - "peer": true, - "dependencies": { - "@algolia/client-common": "4.22.1", - "@algolia/requester-common": "4.22.1", - "@algolia/transporter": "4.22.1" - } - }, - "node_modules/@algolia/logger-common": { - "version": "4.22.1", - "resolved": "https://registry.npmjs.org/@algolia/logger-common/-/logger-common-4.22.1.tgz", - "integrity": "sha512-OnTFymd2odHSO39r4DSWRFETkBufnY2iGUZNrMXpIhF5cmFE8pGoINNPzwg02QLBlGSaLqdKy0bM8S0GyqPLBg==", - "dev": true, - "peer": true - }, - "node_modules/@algolia/logger-console": { - "version": "4.22.1", - "resolved": "https://registry.npmjs.org/@algolia/logger-console/-/logger-console-4.22.1.tgz", - "integrity": "sha512-O99rcqpVPKN1RlpgD6H3khUWylU24OXlzkavUAMy6QZd1776QAcauE3oP8CmD43nbaTjBexZj2nGsBH9Tc0FVA==", - "dev": true, - "peer": true, - "dependencies": { - "@algolia/logger-common": "4.22.1" - } - }, - "node_modules/@algolia/requester-browser-xhr": { - "version": "4.22.1", - "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-4.22.1.tgz", - "integrity": "sha512-dtQGYIg6MteqT1Uay3J/0NDqD+UciHy3QgRbk7bNddOJu+p3hzjTRYESqEnoX/DpEkaNYdRHUKNylsqMpgwaEw==", - "dev": true, - "peer": true, - "dependencies": { - "@algolia/requester-common": "4.22.1" - } - }, - "node_modules/@algolia/requester-common": { - "version": "4.22.1", - "resolved": "https://registry.npmjs.org/@algolia/requester-common/-/requester-common-4.22.1.tgz", - "integrity": "sha512-dgvhSAtg2MJnR+BxrIFqlLtkLlVVhas9HgYKMk2Uxiy5m6/8HZBL40JVAMb2LovoPFs9I/EWIoFVjOrFwzn5Qg==", - "dev": true, - "peer": true - }, - "node_modules/@algolia/requester-node-http": { - "version": "4.22.1", - "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-4.22.1.tgz", - "integrity": "sha512-JfmZ3MVFQkAU+zug8H3s8rZ6h0ahHZL/SpMaSasTCGYR5EEJsCc8SI5UZ6raPN2tjxa5bxS13BRpGSBUens7EA==", - "dev": true, - "peer": true, - "dependencies": { - "@algolia/requester-common": "4.22.1" - } - }, - "node_modules/@algolia/transporter": { - "version": "4.22.1", - "resolved": "https://registry.npmjs.org/@algolia/transporter/-/transporter-4.22.1.tgz", - "integrity": "sha512-kzWgc2c9IdxMa3YqA6TN0NW5VrKYYW/BELIn7vnLyn+U/RFdZ4lxxt9/8yq3DKV5snvoDzzO4ClyejZRdV3lMQ==", - "dev": true, - "peer": true, - "dependencies": { - "@algolia/cache-common": "4.22.1", - "@algolia/logger-common": "4.22.1", - "@algolia/requester-common": "4.22.1" - } - }, - "node_modules/@babel/parser": { - "version": "7.23.9", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.9.tgz", - "integrity": "sha512-9tcKgqKbs3xGJ+NtKF2ndOBBLVwPjl1SHxPQkd36r3Dlirw3xWUeGaTbqr7uGZcTaxkVNwc+03SVP7aCdWrTlA==", - "dev": true, - "peer": true, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@docsearch/css": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-3.5.2.tgz", - "integrity": "sha512-SPiDHaWKQZpwR2siD0KQUwlStvIAnEyK6tAE2h2Wuoq8ue9skzhlyVQ1ddzOxX6khULnAALDiR/isSF3bnuciA==", - "dev": true, - "peer": true - }, - "node_modules/@docsearch/js": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/@docsearch/js/-/js-3.5.2.tgz", - "integrity": "sha512-p1YFTCDflk8ieHgFJYfmyHBki1D61+U9idwrLh+GQQMrBSP3DLGKpy0XUJtPjAOPltcVbqsTjiPFfH7JImjUNg==", - "dev": true, - "peer": true, - "dependencies": { - "@docsearch/react": "3.5.2", - "preact": "^10.0.0" - } - }, - "node_modules/@docsearch/react": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/@docsearch/react/-/react-3.5.2.tgz", - "integrity": "sha512-9Ahcrs5z2jq/DcAvYtvlqEBHImbm4YJI8M9y0x6Tqg598P40HTEkX7hsMcIuThI+hTFxRGZ9hll0Wygm2yEjng==", - "dev": true, - "peer": true, - "dependencies": { - "@algolia/autocomplete-core": "1.9.3", - "@algolia/autocomplete-preset-algolia": "1.9.3", - "@docsearch/css": "3.5.2", - "algoliasearch": "^4.19.1" - }, - "peerDependencies": { - "@types/react": ">= 16.8.0 < 19.0.0", - "react": ">= 16.8.0 < 19.0.0", - "react-dom": ">= 16.8.0 < 19.0.0", - "search-insights": ">= 1 < 3" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "react": { - "optional": true - }, - "react-dom": { - "optional": true - }, - "search-insights": { - "optional": true - } - } - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.12.tgz", - "integrity": "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "optional": true, - "os": [ - "aix" - ], - "peer": true, - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.12.tgz", - "integrity": "sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "peer": true, - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.12.tgz", - "integrity": "sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "peer": true, - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.12.tgz", - "integrity": "sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "peer": true, - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.12.tgz", - "integrity": "sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "peer": true, - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.12.tgz", - "integrity": "sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "peer": true, - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.12.tgz", - "integrity": "sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "peer": true, - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.12.tgz", - "integrity": "sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "peer": true, - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.12.tgz", - "integrity": "sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.12.tgz", - "integrity": "sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.12.tgz", - "integrity": "sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.12.tgz", - "integrity": "sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==", - "cpu": [ - "loong64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.12.tgz", - "integrity": "sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==", - "cpu": [ - "mips64el" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.12.tgz", - "integrity": "sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==", - "cpu": [ - "ppc64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.12.tgz", - "integrity": "sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==", - "cpu": [ - "riscv64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.12.tgz", - "integrity": "sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==", - "cpu": [ - "s390x" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.12.tgz", - "integrity": "sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.12.tgz", - "integrity": "sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "netbsd" - ], - "peer": true, - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.12.tgz", - "integrity": "sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "openbsd" - ], - "peer": true, - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.12.tgz", - "integrity": "sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "sunos" - ], - "peer": true, - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.12.tgz", - "integrity": "sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "peer": true, - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.12.tgz", - "integrity": "sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "peer": true, - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.12.tgz", - "integrity": "sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "peer": true, - "engines": { - "node": ">=12" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.15", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", - "dev": true, - "peer": true - }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.9.6.tgz", - "integrity": "sha512-MVNXSSYN6QXOulbHpLMKYi60ppyO13W9my1qogeiAqtjb2yR4LSmfU2+POvDkLzhjYLXz9Rf9+9a3zFHW1Lecg==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "peer": true - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.9.6.tgz", - "integrity": "sha512-T14aNLpqJ5wzKNf5jEDpv5zgyIqcpn1MlwCrUXLrwoADr2RkWA0vOWP4XxbO9aiO3dvMCQICZdKeDrFl7UMClw==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "peer": true - }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.9.6.tgz", - "integrity": "sha512-CqNNAyhRkTbo8VVZ5R85X73H3R5NX9ONnKbXuHisGWC0qRbTTxnF1U4V9NafzJbgGM0sHZpdO83pLPzq8uOZFw==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "peer": true - }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.9.6.tgz", - "integrity": "sha512-zRDtdJuRvA1dc9Mp6BWYqAsU5oeLixdfUvkTHuiYOHwqYuQ4YgSmi6+/lPvSsqc/I0Omw3DdICx4Tfacdzmhog==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.9.6.tgz", - "integrity": "sha512-oNk8YXDDnNyG4qlNb6is1ojTOGL/tRhbbKeE/YuccItzerEZT68Z9gHrY3ROh7axDc974+zYAPxK5SH0j/G+QQ==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.9.6.tgz", - "integrity": "sha512-Z3O60yxPtuCYobrtzjo0wlmvDdx2qZfeAWTyfOjEDqd08kthDKexLpV97KfAeUXPosENKd8uyJMRDfFMxcYkDQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.9.6.tgz", - "integrity": "sha512-gpiG0qQJNdYEVad+1iAsGAbgAnZ8j07FapmnIAQgODKcOTjLEWM9sRb+MbQyVsYCnA0Im6M6QIq6ax7liws6eQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.9.6.tgz", - "integrity": "sha512-+uCOcvVmFUYvVDr27aiyun9WgZk0tXe7ThuzoUTAukZJOwS5MrGbmSlNOhx1j80GdpqbOty05XqSl5w4dQvcOA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.9.6.tgz", - "integrity": "sha512-HUNqM32dGzfBKuaDUBqFB7tP6VMN74eLZ33Q9Y1TBqRDn+qDonkAUyKWwF9BR9unV7QUzffLnz9GrnKvMqC/fw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.9.6.tgz", - "integrity": "sha512-ch7M+9Tr5R4FK40FHQk8VnML0Szi2KRujUgHXd/HjuH9ifH72GUmw6lStZBo3c3GB82vHa0ZoUfjfcM7JiiMrQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.9.6.tgz", - "integrity": "sha512-VD6qnR99dhmTQ1mJhIzXsRcTBvTjbfbGGwKAHcu+52cVl15AC/kplkhxzW/uT0Xl62Y/meBKDZvoJSJN+vTeGA==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "peer": true - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.9.6.tgz", - "integrity": "sha512-J9AFDq/xiRI58eR2NIDfyVmTYGyIZmRcvcAoJ48oDld/NTR8wyiPUu2X/v1navJ+N/FGg68LEbX3Ejd6l8B7MQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "peer": true - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.9.6.tgz", - "integrity": "sha512-jqzNLhNDvIZOrt69Ce4UjGRpXJBzhUBzawMwnaDAwyHriki3XollsewxWzOzz+4yOFDkuJHtTsZFwMxhYJWmLQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "peer": true - }, - "node_modules/@shikijs/core": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-1.0.0.tgz", - "integrity": "sha512-UMKGMZ+8b88N0/n6DWwWth1PHsOaxjW+R2u+hzSiargZWTv+l3s1l8dhuIxUSsEUPlBDKLs2CSMiFZeviKQM1w==", - "dev": true, - "peer": true - }, - "node_modules/@shikijs/transformers": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@shikijs/transformers/-/transformers-1.0.0.tgz", - "integrity": "sha512-US0Sc0OdH7eGL64BtfvX3XezPfqhqF5mPyBFLlbZqSpFt2/emnv9GveAWzELGsIuvXoJ6N1RjeAdmQx5Xni6BQ==", - "dev": true, - "peer": true, - "dependencies": { - "shiki": "1.0.0" - } - }, - "node_modules/@types/estree": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", - "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", - "dev": true, - "peer": true - }, - "node_modules/@types/linkify-it": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-3.0.5.tgz", - "integrity": "sha512-yg6E+u0/+Zjva+buc3EIb+29XEg4wltq7cSmd4Uc2EE/1nUVmxyzpX6gUXD0V8jIrG0r7YeOGVIbYRkxeooCtw==", - "dev": true, - "peer": true - }, - "node_modules/@types/markdown-it": { - "version": "13.0.7", - "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-13.0.7.tgz", - "integrity": "sha512-U/CBi2YUUcTHBt5tjO2r5QV/x0Po6nsYwQU4Y04fBS6vfoImaiZ6f8bi3CjTCxBPQSO1LMyUqkByzi8AidyxfA==", - "dev": true, - "peer": true, - "dependencies": { - "@types/linkify-it": "*", - "@types/mdurl": "*" - } - }, - "node_modules/@types/mdurl": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-1.0.5.tgz", - "integrity": "sha512-6L6VymKTzYSrEf4Nev4Xa1LCHKrlTlYCBMTlQKFuddo1CvQcE52I0mwfOJayueUC7MJuXOeHTcIU683lzd0cUA==", - "dev": true, - "peer": true - }, - "node_modules/@types/web-bluetooth": { - "version": "0.0.20", - "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.20.tgz", - "integrity": "sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==", - "dev": true, - "peer": true - }, - "node_modules/@vitejs/plugin-vue": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.0.3.tgz", - "integrity": "sha512-b8S5dVS40rgHdDrw+DQi/xOM9ed+kSRZzfm1T74bMmBDCd8XO87NKlFYInzCtwvtWwXZvo1QxE2OSspTATWrbA==", - "dev": true, - "peer": true, - "engines": { - "node": "^18.0.0 || >=20.0.0" - }, - "peerDependencies": { - "vite": "^5.0.0", - "vue": "^3.2.25" - } - }, - "node_modules/@vue/compiler-core": { - "version": "3.4.15", - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.15.tgz", - "integrity": "sha512-XcJQVOaxTKCnth1vCxEChteGuwG6wqnUHxAm1DO3gCz0+uXKaJNx8/digSz4dLALCy8n2lKq24jSUs8segoqIw==", - "dev": true, - "peer": true, - "dependencies": { - "@babel/parser": "^7.23.6", - "@vue/shared": "3.4.15", - "entities": "^4.5.0", - "estree-walker": "^2.0.2", - "source-map-js": "^1.0.2" - } - }, - "node_modules/@vue/compiler-dom": { - "version": "3.4.15", - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.15.tgz", - "integrity": "sha512-wox0aasVV74zoXyblarOM3AZQz/Z+OunYcIHe1OsGclCHt8RsRm04DObjefaI82u6XDzv+qGWZ24tIsRAIi5MQ==", - "dev": true, - "peer": true, - "dependencies": { - "@vue/compiler-core": "3.4.15", - "@vue/shared": "3.4.15" - } - }, - "node_modules/@vue/compiler-sfc": { - "version": "3.4.15", - "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.15.tgz", - "integrity": "sha512-LCn5M6QpkpFsh3GQvs2mJUOAlBQcCco8D60Bcqmf3O3w5a+KWS5GvYbrrJBkgvL1BDnTp+e8q0lXCLgHhKguBA==", - "dev": true, - "peer": true, - "dependencies": { - "@babel/parser": "^7.23.6", - "@vue/compiler-core": "3.4.15", - "@vue/compiler-dom": "3.4.15", - "@vue/compiler-ssr": "3.4.15", - "@vue/shared": "3.4.15", - "estree-walker": "^2.0.2", - "magic-string": "^0.30.5", - "postcss": "^8.4.33", - "source-map-js": "^1.0.2" - } - }, - "node_modules/@vue/compiler-ssr": { - "version": "3.4.15", - "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.15.tgz", - "integrity": "sha512-1jdeQyiGznr8gjFDadVmOJqZiLNSsMa5ZgqavkPZ8O2wjHv0tVuAEsw5hTdUoUW4232vpBbL/wJhzVW/JwY1Uw==", - "dev": true, - "peer": true, - "dependencies": { - "@vue/compiler-dom": "3.4.15", - "@vue/shared": "3.4.15" - } - }, - "node_modules/@vue/devtools-api": { - "version": "7.0.14", - "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-7.0.14.tgz", - "integrity": "sha512-TluWR9qZ6aO11bwtYK8+fzXxBqLfsE0mWZz1q/EQBmO9k82Cm6deieLwNNXjNFJz7xutazoia5Qa+zTYkPPOfw==", - "dev": true, - "peer": true, - "dependencies": { - "@vue/devtools-kit": "^7.0.14" - } - }, - "node_modules/@vue/devtools-kit": { - "version": "7.0.14", - "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-7.0.14.tgz", - "integrity": "sha512-wAAJazr4hI0aVRpgWOCVPw+NzMQdthhnprHHIg4njp1MkKrpCNGQ7MtQbZF1AltAA7xpMCGyyt+0kYH0FqTiPg==", - "dev": true, - "peer": true, - "dependencies": { - "@vue/devtools-schema": "^7.0.14", - "@vue/devtools-shared": "^7.0.14", - "hookable": "^5.5.3", - "mitt": "^3.0.1", - "perfect-debounce": "^1.0.0", - "speakingurl": "^14.0.1" - } - }, - "node_modules/@vue/devtools-schema": { - "version": "7.0.14", - "resolved": "https://registry.npmjs.org/@vue/devtools-schema/-/devtools-schema-7.0.14.tgz", - "integrity": "sha512-tpUeCLVrdHX+KzWMLTAwx/vAPFbo6jAUi7sr6Q+0mBIqIVSSIxNr5wEhegiFvYva+OtDeM2OrT+f7/X/5bvZNg==", - "dev": true, - "peer": true - }, - "node_modules/@vue/devtools-shared": { - "version": "7.0.14", - "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-7.0.14.tgz", - "integrity": "sha512-79RP1NDakBVWou9rDpVnT1WMjTbL1lJKm6YEOodjQ0dq5ehf0wsRbeYDhgAlnjehWRzTq5GAYFBFUPYBs0/QpA==", - "dev": true, - "peer": true, - "dependencies": { - "rfdc": "^1.3.1" - } - }, - "node_modules/@vue/reactivity": { - "version": "3.4.15", - "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.15.tgz", - "integrity": "sha512-55yJh2bsff20K5O84MxSvXKPHHt17I2EomHznvFiJCAZpJTNW8IuLj1xZWMLELRhBK3kkFV/1ErZGHJfah7i7w==", - "dev": true, - "peer": true, - "dependencies": { - "@vue/shared": "3.4.15" - } - }, - "node_modules/@vue/runtime-core": { - "version": "3.4.15", - "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.15.tgz", - "integrity": "sha512-6E3by5m6v1AkW0McCeAyhHTw+3y17YCOKG0U0HDKDscV4Hs0kgNT5G+GCHak16jKgcCDHpI9xe5NKb8sdLCLdw==", - "dev": true, - "peer": true, - "dependencies": { - "@vue/reactivity": "3.4.15", - "@vue/shared": "3.4.15" - } - }, - "node_modules/@vue/runtime-dom": { - "version": "3.4.15", - "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.15.tgz", - "integrity": "sha512-EVW8D6vfFVq3V/yDKNPBFkZKGMFSvZrUQmx196o/v2tHKdwWdiZjYUBS+0Ez3+ohRyF8Njwy/6FH5gYJ75liUw==", - "dev": true, - "peer": true, - "dependencies": { - "@vue/runtime-core": "3.4.15", - "@vue/shared": "3.4.15", - "csstype": "^3.1.3" - } - }, - "node_modules/@vue/server-renderer": { - "version": "3.4.15", - "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.15.tgz", - "integrity": "sha512-3HYzaidu9cHjrT+qGUuDhFYvF/j643bHC6uUN9BgM11DVy+pM6ATsG6uPBLnkwOgs7BpJABReLmpL3ZPAsUaqw==", - "dev": true, - "peer": true, - "dependencies": { - "@vue/compiler-ssr": "3.4.15", - "@vue/shared": "3.4.15" - }, - "peerDependencies": { - "vue": "3.4.15" - } - }, - "node_modules/@vue/shared": { - "version": "3.4.15", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.15.tgz", - "integrity": "sha512-KzfPTxVaWfB+eGcGdbSf4CWdaXcGDqckoeXUh7SB3fZdEtzPCK2Vq9B/lRRL3yutax/LWITz+SwvgyOxz5V75g==", - "dev": true, - "peer": true - }, - "node_modules/@vueuse/core": { - "version": "10.7.2", - "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-10.7.2.tgz", - "integrity": "sha512-AOyAL2rK0By62Hm+iqQn6Rbu8bfmbgaIMXcE3TSr7BdQ42wnSFlwIdPjInO62onYsEMK/yDMU8C6oGfDAtZ2qQ==", - "dev": true, - "peer": true, - "dependencies": { - "@types/web-bluetooth": "^0.0.20", - "@vueuse/metadata": "10.7.2", - "@vueuse/shared": "10.7.2", - "vue-demi": ">=0.14.6" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/@vueuse/core/node_modules/vue-demi": { - "version": "0.14.7", - "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.7.tgz", - "integrity": "sha512-EOG8KXDQNwkJILkx/gPcoL/7vH+hORoBaKgGe+6W7VFMvCYJfmF2dGbvgDroVnI8LU7/kTu8mbjRZGBU1z9NTA==", - "dev": true, - "hasInstallScript": true, - "peer": true, - "bin": { - "vue-demi-fix": "bin/vue-demi-fix.js", - "vue-demi-switch": "bin/vue-demi-switch.js" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - }, - "peerDependencies": { - "@vue/composition-api": "^1.0.0-rc.1", - "vue": "^3.0.0-0 || ^2.6.0" - }, - "peerDependenciesMeta": { - "@vue/composition-api": { - "optional": true - } - } - }, - "node_modules/@vueuse/integrations": { - "version": "10.7.2", - "resolved": "https://registry.npmjs.org/@vueuse/integrations/-/integrations-10.7.2.tgz", - "integrity": "sha512-+u3RLPFedjASs5EKPc69Ge49WNgqeMfSxFn+qrQTzblPXZg6+EFzhjarS5edj2qAf6xQ93f95TUxRwKStXj/sQ==", - "dev": true, - "peer": true, - "dependencies": { - "@vueuse/core": "10.7.2", - "@vueuse/shared": "10.7.2", - "vue-demi": ">=0.14.6" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - }, - "peerDependencies": { - "async-validator": "*", - "axios": "*", - "change-case": "*", - "drauu": "*", - "focus-trap": "*", - "fuse.js": "*", - "idb-keyval": "*", - "jwt-decode": "*", - "nprogress": "*", - "qrcode": "*", - "sortablejs": "*", - "universal-cookie": "*" - }, - "peerDependenciesMeta": { - "async-validator": { - "optional": true - }, - "axios": { - "optional": true - }, - "change-case": { - "optional": true - }, - "drauu": { - "optional": true - }, - "focus-trap": { - "optional": true - }, - "fuse.js": { - "optional": true - }, - "idb-keyval": { - "optional": true - }, - "jwt-decode": { - "optional": true - }, - "nprogress": { - "optional": true - }, - "qrcode": { - "optional": true - }, - "sortablejs": { - "optional": true - }, - "universal-cookie": { - "optional": true - } - } - }, - "node_modules/@vueuse/integrations/node_modules/vue-demi": { - "version": "0.14.7", - "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.7.tgz", - "integrity": "sha512-EOG8KXDQNwkJILkx/gPcoL/7vH+hORoBaKgGe+6W7VFMvCYJfmF2dGbvgDroVnI8LU7/kTu8mbjRZGBU1z9NTA==", - "dev": true, - "hasInstallScript": true, - "peer": true, - "bin": { - "vue-demi-fix": "bin/vue-demi-fix.js", - "vue-demi-switch": "bin/vue-demi-switch.js" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - }, - "peerDependencies": { - "@vue/composition-api": "^1.0.0-rc.1", - "vue": "^3.0.0-0 || ^2.6.0" - }, - "peerDependenciesMeta": { - "@vue/composition-api": { - "optional": true - } - } - }, - "node_modules/@vueuse/metadata": { - "version": "10.7.2", - "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-10.7.2.tgz", - "integrity": "sha512-kCWPb4J2KGrwLtn1eJwaJD742u1k5h6v/St5wFe8Quih90+k2a0JP8BS4Zp34XUuJqS2AxFYMb1wjUL8HfhWsQ==", - "dev": true, - "peer": true, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/@vueuse/shared": { - "version": "10.7.2", - "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-10.7.2.tgz", - "integrity": "sha512-qFbXoxS44pi2FkgFjPvF4h7c9oMDutpyBdcJdMYIMg9XyXli2meFMuaKn+UMgsClo//Th6+beeCgqweT/79BVA==", - "dev": true, - "peer": true, - "dependencies": { - "vue-demi": ">=0.14.6" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/@vueuse/shared/node_modules/vue-demi": { - "version": "0.14.7", - "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.7.tgz", - "integrity": "sha512-EOG8KXDQNwkJILkx/gPcoL/7vH+hORoBaKgGe+6W7VFMvCYJfmF2dGbvgDroVnI8LU7/kTu8mbjRZGBU1z9NTA==", - "dev": true, - "hasInstallScript": true, - "peer": true, - "bin": { - "vue-demi-fix": "bin/vue-demi-fix.js", - "vue-demi-switch": "bin/vue-demi-switch.js" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - }, - "peerDependencies": { - "@vue/composition-api": "^1.0.0-rc.1", - "vue": "^3.0.0-0 || ^2.6.0" - }, - "peerDependenciesMeta": { - "@vue/composition-api": { - "optional": true - } - } - }, - "node_modules/algoliasearch": { - "version": "4.22.1", - "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-4.22.1.tgz", - "integrity": "sha512-jwydKFQJKIx9kIZ8Jm44SdpigFwRGPESaxZBaHSV0XWN2yBJAOT4mT7ppvlrpA4UGzz92pqFnVKr/kaZXrcreg==", - "dev": true, - "peer": true, - "dependencies": { - "@algolia/cache-browser-local-storage": "4.22.1", - "@algolia/cache-common": "4.22.1", - "@algolia/cache-in-memory": "4.22.1", - "@algolia/client-account": "4.22.1", - "@algolia/client-analytics": "4.22.1", - "@algolia/client-common": "4.22.1", - "@algolia/client-personalization": "4.22.1", - "@algolia/client-search": "4.22.1", - "@algolia/logger-common": "4.22.1", - "@algolia/logger-console": "4.22.1", - "@algolia/requester-browser-xhr": "4.22.1", - "@algolia/requester-common": "4.22.1", - "@algolia/requester-node-http": "4.22.1", - "@algolia/transporter": "4.22.1" - } - }, - "node_modules/csstype": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "dev": true, - "peer": true - }, - "node_modules/entities": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "dev": true, - "peer": true, - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/esbuild": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.12.tgz", - "integrity": "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==", - "dev": true, - "hasInstallScript": true, - "peer": true, - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=12" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.19.12", - "@esbuild/android-arm": "0.19.12", - "@esbuild/android-arm64": "0.19.12", - "@esbuild/android-x64": "0.19.12", - "@esbuild/darwin-arm64": "0.19.12", - "@esbuild/darwin-x64": "0.19.12", - "@esbuild/freebsd-arm64": "0.19.12", - "@esbuild/freebsd-x64": "0.19.12", - "@esbuild/linux-arm": "0.19.12", - "@esbuild/linux-arm64": "0.19.12", - "@esbuild/linux-ia32": "0.19.12", - "@esbuild/linux-loong64": "0.19.12", - "@esbuild/linux-mips64el": "0.19.12", - "@esbuild/linux-ppc64": "0.19.12", - "@esbuild/linux-riscv64": "0.19.12", - "@esbuild/linux-s390x": "0.19.12", - "@esbuild/linux-x64": "0.19.12", - "@esbuild/netbsd-x64": "0.19.12", - "@esbuild/openbsd-x64": "0.19.12", - "@esbuild/sunos-x64": "0.19.12", - "@esbuild/win32-arm64": "0.19.12", - "@esbuild/win32-ia32": "0.19.12", - "@esbuild/win32-x64": "0.19.12" - } - }, - "node_modules/estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "dev": true, - "peer": true - }, - "node_modules/focus-trap": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-7.5.4.tgz", - "integrity": "sha512-N7kHdlgsO/v+iD/dMoJKtsSqs5Dz/dXZVebRgJw23LDk+jMi/974zyiOYDziY2JPp8xivq9BmUGwIJMiuSBi7w==", - "dev": true, - "peer": true, - "dependencies": { - "tabbable": "^6.2.0" - } - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "peer": true, - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/hookable": { - "version": "5.5.3", - "resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz", - "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==", - "dev": true, - "peer": true - }, - "node_modules/magic-string": { - "version": "0.30.7", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.7.tgz", - "integrity": "sha512-8vBuFF/I/+OSLRmdf2wwFCJCz+nSn0m6DPvGH1fS/KiQoSaR+sETbov0eIk9KhEKy8CYqIkIAnbohxT/4H0kuA==", - "dev": true, - "peer": true, - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.4.15" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/mark.js": { - "version": "8.11.1", - "resolved": "https://registry.npmjs.org/mark.js/-/mark.js-8.11.1.tgz", - "integrity": "sha512-1I+1qpDt4idfgLQG+BNWmrqku+7/2bi5nLf4YwF8y8zXvmfiTBY3PV3ZibfrjBueCByROpuBjLLFCajqkgYoLQ==", - "dev": true, - "peer": true - }, - "node_modules/minisearch": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/minisearch/-/minisearch-6.3.0.tgz", - "integrity": "sha512-ihFnidEeU8iXzcVHy74dhkxh/dn8Dc08ERl0xwoMMGqp4+LvRSCgicb+zGqWthVokQKvCSxITlh3P08OzdTYCQ==", - "dev": true, - "peer": true - }, - "node_modules/mitt": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", - "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", - "dev": true, - "peer": true - }, - "node_modules/nanoid": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", - "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "peer": true, - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/perfect-debounce": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", - "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", - "dev": true, - "peer": true - }, - "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true, - "peer": true - }, - "node_modules/postcss": { - "version": "8.4.35", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.35.tgz", - "integrity": "sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "peer": true, - "dependencies": { - "nanoid": "^3.3.7", - "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/preact": { - "version": "10.19.3", - "resolved": "https://registry.npmjs.org/preact/-/preact-10.19.3.tgz", - "integrity": "sha512-nHHTeFVBTHRGxJXKkKu5hT8C/YWBkPso4/Gad6xuj5dbptt9iF9NZr9pHbPhBrnT2klheu7mHTxTZ/LjwJiEiQ==", - "dev": true, - "peer": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/preact" - } - }, - "node_modules/rfdc": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.1.tgz", - "integrity": "sha512-r5a3l5HzYlIC68TpmYKlxWjmOP6wiPJ1vWv2HeLhNsRZMrCkxeqxiHlQ21oXmQ4F3SiryXBHhAD7JZqvOJjFmg==", - "dev": true, - "peer": true - }, - "node_modules/rollup": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.9.6.tgz", - "integrity": "sha512-05lzkCS2uASX0CiLFybYfVkwNbKZG5NFQ6Go0VWyogFTXXbR039UVsegViTntkk4OglHBdF54ccApXRRuXRbsg==", - "dev": true, - "peer": true, - "dependencies": { - "@types/estree": "1.0.5" - }, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.9.6", - "@rollup/rollup-android-arm64": "4.9.6", - "@rollup/rollup-darwin-arm64": "4.9.6", - "@rollup/rollup-darwin-x64": "4.9.6", - "@rollup/rollup-linux-arm-gnueabihf": "4.9.6", - "@rollup/rollup-linux-arm64-gnu": "4.9.6", - "@rollup/rollup-linux-arm64-musl": "4.9.6", - "@rollup/rollup-linux-riscv64-gnu": "4.9.6", - "@rollup/rollup-linux-x64-gnu": "4.9.6", - "@rollup/rollup-linux-x64-musl": "4.9.6", - "@rollup/rollup-win32-arm64-msvc": "4.9.6", - "@rollup/rollup-win32-ia32-msvc": "4.9.6", - "@rollup/rollup-win32-x64-msvc": "4.9.6", - "fsevents": "~2.3.2" - } - }, - "node_modules/search-insights": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/search-insights/-/search-insights-2.13.0.tgz", - "integrity": "sha512-Orrsjf9trHHxFRuo9/rzm0KIWmgzE8RMlZMzuhZOJ01Rnz3D0YBAe+V6473t6/H6c7irs6Lt48brULAiRWb3Vw==", - "dev": true, - "peer": true - }, - "node_modules/shiki": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shiki/-/shiki-1.0.0.tgz", - "integrity": "sha512-rOUGJa3yFGgOrEoiELYxraoBbag3ZWf9bpodlr05Wjm85Scx8OIX+otdSefq9Pk7L47TKEzGodSQb4L38jka6A==", - "dev": true, - "peer": true, - "dependencies": { - "@shikijs/core": "1.0.0" - } - }, - "node_modules/source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", - "dev": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/speakingurl": { - "version": "14.0.1", - "resolved": "https://registry.npmjs.org/speakingurl/-/speakingurl-14.0.1.tgz", - "integrity": "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==", - "dev": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/tabbable": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz", - "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==", - "dev": true, - "peer": true - }, - "node_modules/vite": { - "version": "5.0.12", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.0.12.tgz", - "integrity": "sha512-4hsnEkG3q0N4Tzf1+t6NdN9dg/L3BM+q8SWgbSPnJvrgH2kgdyzfVJwbR1ic69/4uMJJ/3dqDZZE5/WwqW8U1w==", - "dev": true, - "peer": true, - "dependencies": { - "esbuild": "^0.19.3", - "postcss": "^8.4.32", - "rollup": "^4.2.0" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^18.0.0 || >=20.0.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^18.0.0 || >=20.0.0", - "less": "*", - "lightningcss": "^1.21.0", - "sass": "*", - "stylus": "*", - "sugarss": "*", - "terser": "^5.4.0" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - } - } - }, - "node_modules/vitepress": { - "version": "1.0.0-rc.42", - "resolved": "https://registry.npmjs.org/vitepress/-/vitepress-1.0.0-rc.42.tgz", - "integrity": "sha512-VeiVVXFblt/sjruFSJBNChMWwlztMrRMe8UXdNpf4e05mKtTYEY38MF5qoP90KxPTCfMQiKqwEGwXAGuOTK8HQ==", - "dev": true, - "peer": true, - "dependencies": { - "@docsearch/css": "^3.5.2", - "@docsearch/js": "^3.5.2", - "@shikijs/core": "^1.0.0-rc.0", - "@shikijs/transformers": "^1.0.0-rc.0", - "@types/markdown-it": "^13.0.7", - "@vitejs/plugin-vue": "^5.0.3", - "@vue/devtools-api": "^7.0.14", - "@vueuse/core": "^10.7.2", - "@vueuse/integrations": "^10.7.2", - "focus-trap": "^7.5.4", - "mark.js": "8.11.1", - "minisearch": "^6.3.0", - "shiki": "^1.0.0-rc.0", - "vite": "^5.0.12", - "vue": "^3.4.15" - }, - "bin": { - "vitepress": "bin/vitepress.js" - }, - "peerDependencies": { - "markdown-it-mathjax3": "^4.3.2", - "postcss": "^8.4.34" - }, - "peerDependenciesMeta": { - "markdown-it-mathjax3": { - "optional": true - }, - "postcss": { - "optional": true - } - } - }, - "node_modules/vitepress-plugin-tabs": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/vitepress-plugin-tabs/-/vitepress-plugin-tabs-0.5.0.tgz", - "integrity": "sha512-SIhFWwGsUkTByfc2b279ray/E0Jt8vDTsM1LiHxmCOBAEMmvzIBZSuYYT1DpdDTiS3SuJieBheJkYnwCq/yD9A==", - "dev": true, - "peerDependencies": { - "vitepress": "^1.0.0-rc.27", - "vue": "^3.3.8" - } - }, - "node_modules/vue": { - "version": "3.4.15", - "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.15.tgz", - "integrity": "sha512-jC0GH4KkWLWJOEQjOpkqU1bQsBwf4R1rsFtw5GQJbjHVKWDzO6P0nWWBTmjp1xSemAioDFj1jdaK1qa3DnMQoQ==", - "dev": true, - "peer": true, - "dependencies": { - "@vue/compiler-dom": "3.4.15", - "@vue/compiler-sfc": "3.4.15", - "@vue/runtime-dom": "3.4.15", - "@vue/server-renderer": "3.4.15", - "@vue/shared": "3.4.15" - }, - "peerDependencies": { - "typescript": "*" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - } - } -} diff --git a/package.json b/package.json deleted file mode 100644 index c9a50ac5a..000000000 --- a/package.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "scripts": { - "docs:dev": "vitepress dev docs/docs_site", - "docs:build": "vitepress build docs/docs_site", - "docs:preview": "vitepress preview docs/docs_site" - }, - "devDependencies": { - "vitepress-plugin-tabs": "^0.5.0" - } -} From c313ea61115897b8f393c2c98cdc5d2154db3336 Mon Sep 17 00:00:00 2001 From: Anshul Singhvi Date: Sat, 17 Feb 2024 15:37:00 -0500 Subject: [PATCH 034/108] Fix main branch name in documenter.yml (#633) --- .github/workflows/Documenter.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/Documenter.yml b/.github/workflows/Documenter.yml index c1c3b240a..386f5ea7e 100644 --- a/.github/workflows/Documenter.yml +++ b/.github/workflows/Documenter.yml @@ -7,7 +7,7 @@ on: # using the `master` branch as the default branch. push: branches: - - master + - main tags: ['*'] pull_request: @@ -59,4 +59,4 @@ jobs: JULIA_DEBUG: "Documenter" DATADEPS_ALWAYS_ACCEPT: true run: | - julia --project=docs/ --color=yes docs/make.jl # this should ideally AUTO-DEPLOY! \ No newline at end of file + julia --project=docs/ --color=yes docs/make.jl # this should ideally AUTO-DEPLOY! From 3316875fcbb42b75c072ccccf83fda310195599d Mon Sep 17 00:00:00 2001 From: rafaqz Date: Sun, 18 Feb 2024 13:23:32 +0100 Subject: [PATCH 035/108] tweak groupby text --- docs/src/groupby.md | 117 ++++++++++++++++++++++++++------------------ 1 file changed, 69 insertions(+), 48 deletions(-) diff --git a/docs/src/groupby.md b/docs/src/groupby.md index c71466299..2ce43f00f 100644 --- a/docs/src/groupby.md +++ b/docs/src/groupby.md @@ -1,6 +1,19 @@ -# groupby +# `groupby` -## Basics: DateTime operations we can use for grouping +DimensionalData.jl provides a `groupby` function for dimensional +grouping. This guide will cover: + +- simple grouping with a function +- grouping with `Bins` +- grouping with other existing `AbstractDimArry` or `Dimension` + + +# Basics: DateTime operations we can use for grouping + +Lets look at the kind of functions that can be used to group `DateTime`. +Other types will follow the same principles, but are usually simpler. + +First load some packages: ````@example groupby using DimensionalData @@ -9,16 +22,13 @@ using Statistics const DD = DimensionalData ```` -First lets look at the kind of functions that can be used to group `DateTime`. -Other types will follow the same principles, but are usually simpler. - -Create a demo `DateTime` range +Now create a demo `DateTime` range ````@ansi groupby tempo = range(DateTime(2000), step=Hour(1), length=365*24*2) ```` -Now lets see how some common functions work. +Lets see how some common functions work. The `hour` function will transform values to hour of the day - the integers `0:23` @@ -59,7 +69,7 @@ They are sorted by the left to right values. yearmonth.(tempo) ```` -We can creat our own anonymous function that return tuples +We can create our own anonymous function that return tuples ````@example groupby yearday(x) = year(x), dayofyear(x) @@ -74,125 +84,136 @@ yearday.(tempo) All of these functions can be used in `groupby` on `DateTime` objects. -# Practical example: grouping by season -## groupby operations +# Grouping and reducing -Here we use the same time functions from above +Now lets define an array ````@ansi groupby -ds = rand(X(1:0.01:2), Ti(tempo)) +A = rand(X(1:0.01:2), Ti(tempo)) ```` -## select by month, days, years and seasons - -How do we select month 1 or 2, and even a group of them, i.e. [1,3,5]? Same for days, years and seasons. +Simple groupbys using the functions from above ````@ansi groupby -mean.(groupby(ds, Ti=>Bins(month, 1:2))) +group = groupby(A, Ti=>month) ```` -````@ansi groupby -mean.(groupby(ds, Ti=>Bins(month, [1, 3, 5]))) -```` +We take the mean of each group by broadcasting over the group ````@ansi groupby -mean.(groupby(ds, Ti => season(; start=December))) +mean.(group) ```` +Here are some more examples + ````@ansi groupby -mean.(groupby(ds, Ti => Bins(dayofyear, intervals(1:8:370)))) +sum.(groupby(A, Ti=>dayofyear)) # it will combine the same day from different year. ```` ````@ansi groupby -mean.(groupby(ds, Ti => Bins(yearday, [[1,2,3], [4,5,6]], labels=x -> join(string.(x), ',')))) +maximum.(groupby(A, Ti=>yearmonthday)) # this does the a daily mean aggregation. ```` ````@ansi groupby -mean.(groupby(ds, Ti => week)) +minimum.(groupby(A, Ti=>yearmonth)) # this does a monthly mean aggregation ```` ````@ansi groupby -mean.(groupby(ds, Ti => hours(12; start=6, labels=x -> 6 in x ? :night : :day))) +median.(groupby(A, Ti=>Dates.hour12)) ```` +We can also use the function we defined above + ````@ansi groupby -mean.(groupby(ds, Ti => dims(ds, Ti))) +mean.(groupby(A, Ti=>yearday)) # this does a daily mean aggregation ```` -We need a new function that can return DJF (Dec-Jan-Feb), MAM (Mar-Apr-May)... etc. +# Binning -THIS IS HARD. We need a succinct way to select around the end-start of the year. +Sometimes we want to further aggregate our groups after running a function, +or just bin the raw data directly. We can use the [`Bins`](@ref) wrapper to +do this. -is combining month from different years +When our function returns an `Int`, we can just use a range of values we want to keep: ````@ansi groupby -mean.(groupby(ds, Ti=>month)) +mean.(groupby(A, Ti=>Bins(month, 1:2))) ```` -Use three-month bins. The 13 is the open side of the last interval. - ````@ansi groupby -mean.(groupby(ds, Ti=>Bins(yearmonth, intervals(1:3:12)))) +mean.(groupby(A, Ti=>Bins(month, [1, 3, 5]))) ```` +Or an array of arrays + ````@ansi groupby -mean.(groupby(ds, Ti=>Bins(month, 4))) # is combining month from different years +mean.(groupby(A, Ti => Bins(yearday, [[1,2,3], [4,5,6]], labels=x -> join(string.(x), ',')))) ```` +The `ranges` function is a helper for creating these bin groupings + ````@ansi groupby -mean.(groupby(ds, Ti=>year)) +ranges(1:8:370) ```` ````@ansi groupby -mean.(groupby(ds, Ti=>yearmonth)) +mean.(groupby(A, Ti => Bins(dayofyear, ranges(1:8:370)))) ```` ````@ansi groupby -mean.(groupby(ds, Ti=>hour)) +mean.(groupby(A, Ti => season(; start=December))) ```` ````@ansi groupby -mean.(groupby(ds, Ti=>Dates.hour12)) +mean.(groupby(A, Ti => hours(12; start=6, labels=x -> 6 in x ? :night : :day))) ```` -How do could we incorporate resample? Let's say if we have hour resolution I want to resample every 3,6,12.. hours? +## select by month, days, years and seasons -````@ansi groupby -mean.(groupby(ds, Ti=>Bins(yearhour, intervals(1:3:24)))) # it will combine the same day from different year. -```` +How do we select month 1 or 2, and even a group of them, i.e. [1,3,5]? Same for days, years and seasons. + +Use three-month bins. The 13 is the open side of the last interval. ````@ansi groupby -mean.(groupby(ds, Ti=>dayofyear)) # it will combine the same day from different year. +mean.(groupby(A, Ti=>Bins(yearmonth, intervals(1:3:12)))) ```` ````@ansi groupby -mean.(groupby(ds, Ti=>yearmonthday)) # this does the a daily mean aggregation. +mean.(groupby(A, Ti=>Bins(month, 4))) # is combining month from different years ```` +# Select by [`Dimension`](@ref) + ````@ansi groupby -mean.(groupby(ds, Ti=>yearmonth)) # this does a monthly mean aggregation +A +B = +A[:, 1:3:100] +C = mean.(groupby(A, B)) +@assert size(A) == size(B) ```` +How do could we incorporate resample? Let's say if we have hour resolution I want to resample every 3,6,12.. hours? + ````@ansi groupby -mean.(groupby(ds, Ti=>yearday)) # this does a daily mean aggregation +mean.(groupby(A, Ti=>Bins(yearhour, intervals(1:3:24)))) # it will combine the same day from different year. ```` ````@ansi groupby -mean.(groupby(ds, Ti=>Bins(yearhour, 12))) # this does a daily mean aggregation +mean.(groupby(A, Ti=>Bins(yearhour, 12))) # this does a daily mean aggregation ```` Similar to the hourly resample, how do we do it for more than 1 day, let's say 8daily? ````@ansi groupby -mean.(groupby(ds, Ti=>Bins(dayofyear, map(x -> x:x+7, 1:8:370)))) +mean.(groupby(A, Ti=>Bins(dayofyear, map(x -> x:x+7, 1:8:370)))) ```` ## Group by Dims. This should include the rasters input sampling. ````@ansi groupby -mean.(groupby(ds, dims(ds, Ti))) +mean.(groupby(A, dims(A, Ti))) ```` ## Apply custom function (i.e. normalization) to grouped output. From 839d0dc9b79c65d9c08f14fa0c6a383a15b60477 Mon Sep 17 00:00:00 2001 From: Rafael Schouten Date: Sun, 18 Feb 2024 21:19:56 +0100 Subject: [PATCH 036/108] Breaking: better dimensional indexing (#615) * better dimensional indexing * up * more fixes * fix ambiguities * show tweaks * comment our BaseInterfaces until its registered * missing kw... * bugfixes * using Test * bugfix --- src/DimensionalData.jl | 4 +- src/Dimensions/dimension.jl | 5 +- src/Dimensions/primitives.jl | 140 ++++++----- src/Dimensions/show.jl | 2 +- src/LookupArrays/LookupArrays.jl | 1 + src/LookupArrays/selector.jl | 64 +++-- src/LookupArrays/set.jl | 1 + src/array/array.jl | 122 ++++++---- src/array/indexing.jl | 149 +++++++++++- src/array/methods.jl | 14 +- src/array/show.jl | 32 ++- src/dimindices.jl | 398 ++++++++++++++++++++++++------- src/groupby.jl | 11 +- src/interface.jl | 39 ++- src/stack/indexing.jl | 158 +++++++++--- src/stack/methods.jl | 28 ++- src/stack/stack.jl | 43 +++- src/tables.jl | 128 ++-------- test/array.jl | 4 + test/dimension.jl | 11 +- test/dimindices.jl | 151 ++++++++---- test/indexing.jl | 173 ++++++++++---- test/interface.jl | 11 +- test/merged.jl | 4 +- test/primitives.jl | 35 ++- test/runtests.jl | 91 ++++--- test/stack.jl | 34 +-- test/tables.jl | 24 +- 28 files changed, 1253 insertions(+), 624 deletions(-) diff --git a/src/DimensionalData.jl b/src/DimensionalData.jl index a01d1aadc..b80745443 100644 --- a/src/DimensionalData.jl +++ b/src/DimensionalData.jl @@ -66,7 +66,7 @@ export AbstractDimStack, DimStack export AbstractDimTable, DimTable -export DimIndices, DimKeys, DimPoints +export DimIndices, DimSelectors, DimPoints, #= deprecated =# DimKeys # getter methods export dims, refdims, metadata, name, lookup, bounds @@ -87,6 +87,7 @@ include("name.jl") # Arrays include("array/array.jl") +include("dimindices.jl") include("array/indexing.jl") include("array/methods.jl") include("array/matmul.jl") @@ -98,7 +99,6 @@ include("stack/indexing.jl") include("stack/methods.jl") include("stack/show.jl") # Other -include("dimindices.jl") include("tables.jl") # Combined (easier to work on these in one file) include("plotrecipes.jl") diff --git a/src/Dimensions/dimension.jl b/src/Dimensions/dimension.jl index c33e3f8b1..2d25f0aad 100644 --- a/src/Dimensions/dimension.jl +++ b/src/Dimensions/dimension.jl @@ -228,7 +228,7 @@ for f in (:val, :index, :lookup, :metadata, :order, :sampling, :span, :locus, :b :name, :label, :units) @eval begin $f(ds::DimTuple) = map($f, ds) - $f(ds::Tuple{}) = () + $f(::Tuple{}) = () $f(ds::DimTuple, i1, I...) = $f(ds, (i1, I...)) $f(ds::DimTuple, I) = $f(dims(ds, key2dim(I))) end @@ -256,7 +256,7 @@ Base.parent(d::Dimension) = val(d) Base.eltype(d::Type{<:Dimension{T}}) where T = T Base.eltype(d::Type{<:Dimension{A}}) where A<:AbstractArray{T} where T = T Base.size(d::Dimension, args...) = size(val(d), args...) -Base.axes(d::Dimension) = (Dimensions.DimUnitRange(axes(val(d), 1), d),) +Base.axes(d::Dimension) = (val(d) isa DimUnitRange ? val(d) : DimUnitRange(axes(val(d), 1), d),) Base.axes(d::Dimension, i) = axes(d)[i] Base.eachindex(d::Dimension) = eachindex(val(d)) Base.length(d::Dimension) = length(val(d)) @@ -278,6 +278,7 @@ function Base.:(==)(d1::Dimension, d2::Dimension) end Base.size(dims::DimTuple) = map(length, dims) +Base.CartesianIndices(dims::DimTuple) = CartesianIndices(map(d -> axes(d, 1), dims)) # Extents.jl function Extents.extent(ds::DimTuple, args...) diff --git a/src/Dimensions/primitives.jl b/src/Dimensions/primitives.jl index 2a5d0895c..184507dbc 100644 --- a/src/Dimensions/primitives.jl +++ b/src/Dimensions/primitives.jl @@ -81,8 +81,8 @@ can be used in `order`. `f` is `<:` by default, but can be `>:` to sort abstract types by concrete types. """ -@inline sortdims(a1, a2) = _call_primitive(_sortdims, MaybeFirst(), a1, a2) -@inline sortdims(f::Function, a1, a2) = _call_primitive(_sortdims, MaybeFirst(), f, a1, a2) +@inline sortdims(a1, a2) = _dim_query(_sortdims, MaybeFirst(), a1, a2) +@inline sortdims(f::Function, a1, a2) = _dim_query(_sortdims, f, MaybeFirst(), a1, a2) @inline _sortdims(f, tosort, order::Tuple{<:Integer,Vararg}) =map(p -> tosort[p], order) @inline _sortdims(f, tosort, order) = _sortdims_gen(f, tosort, order) @@ -139,10 +139,11 @@ X, Y ``` """ -@inline dims(a1, args...) = _call_primitive(_dims, MaybeFirst(), a1, args...) +@inline dims(a1, args...) = _dim_query(_dims, MaybeFirst(), a1, args...) @inline dims(::Tuple{}, ::Tuple{}) = () @inline _dims(f, dims, query) = _remove_nothing(_sortdims(f, dims, query)) +@inline _dims(f, dims, query...) = _remove_nothing(_sortdims(f, dims, query)) """ commondims([f], x, query) => Tuple{Vararg{Dimension}} @@ -171,7 +172,7 @@ julia> commondims(A, Ti) ``` """ -@inline commondims(a1, args...) = _call_primitive(_commondims, AlwaysTuple(), a1, args...) +@inline commondims(a1, args...) = _dim_query(_commondims, AlwaysTuple(), a1, args...) _commondims(f, ds, query) = _dims(f, ds, _dims(_flip_subtype(f), query, ds)) @@ -204,10 +205,10 @@ julia> dimnum(A, Y) """ @inline function dimnum(x, q1, query...) all(hasdim(x, q1, query...)) || _extradimserror(otherdims(x, (q1, query))) - _call_primitive(_dimnum, MaybeFirst(), x, q1, query...) + _dim_query(_dimnum, MaybeFirst(), x, q1, query...) end @inline dimnum(x, query::Function) = - _call_primitive(_dimnum, MaybeFirst(), x, query) + _dim_query(_dimnum, MaybeFirst(), x, query) @inline _dimnum(f::Function, ds::Tuple, query::Tuple{Vararg{Int}}) = query @inline function _dimnum(f::Function, ds::Tuple, query::Tuple) @@ -248,7 +249,7 @@ false ``` """ @inline hasdim(x, a1, args...) = - _call_primitive(_hasdim, MaybeFirst(), x, a1, args...) + _dim_query(_hasdim, MaybeFirst(), x, a1, args...) @inline _hasdim(f, dims, query) = map(d -> !(d isa Nothing), _sortdims(f, _commondims(f, dims, query), query)) @@ -280,9 +281,11 @@ julia> otherdims(A, (Y, Z)) X ``` """ -@inline otherdims(x, query) = - _call_primitive(_otherdims_presort, AlwaysTuple(), x, query) -@inline otherdims(x) = () +@inline otherdims(x, query) = begin + _dim_query(_otherdims_presort, AlwaysTuple(), x, query) +end +@inline otherdims(x, query...) = + _dim_query(_otherdims_presort, AlwaysTuple(), x, query) @inline _otherdims_presort(f, ds, query) = _otherdims(f, ds, _sortdims(_rev_op(f), query, ds)) # Work with a sorted query where the missing dims are `nothing` @@ -388,17 +391,17 @@ previous reference dims attached to the array. - `I`: A tuple of `Integer`, `Colon` or `AbstractArray` """ function slicedims end -@inline slicedims(args...) = slicedims(getindex, args...) -@inline slicedims(f::Function, x, i1, i2, I...) = slicedims(f, x, (i1, i2, I...)) -@inline slicedims(f::Function, x, I::CartesianIndex) = slicedims(f, x, Tuple(I)) -@inline slicedims(f::Function, x, I::Tuple) = _slicedims(f, dims(x), refdims(x), I) -@inline slicedims(f::Function, dims::Tuple, I::Tuple) = _slicedims(f, dims, I) -@inline slicedims(f::Function, dims::Tuple, refdims::Tuple, i1, I...) = slicedims(f, dims, refdims, (i1, I...)) -@inline slicedims(f::Function, dims::Tuple, refdims::Tuple, I) = _slicedims(f, dims, refdims, I) -@inline slicedims(f::Function, dims::Tuple, refdims::Tuple, I::CartesianIndex) = +@propagate_inbounds slicedims(args...) = slicedims(getindex, args...) +@propagate_inbounds slicedims(f::Function, x, i1, i2, I...) = slicedims(f, x, (i1, i2, I...)) +@propagate_inbounds slicedims(f::Function, x, I::CartesianIndex) = slicedims(f, x, Tuple(I)) +@propagate_inbounds slicedims(f::Function, x, I::Tuple) = _slicedims(f, dims(x), refdims(x), I) +@propagate_inbounds slicedims(f::Function, dims::Tuple, I::Tuple) = _slicedims(f, dims, I) +@propagate_inbounds slicedims(f::Function, dims::Tuple, refdims::Tuple, i1, I...) = slicedims(f, dims, refdims, (i1, I...)) +@propagate_inbounds slicedims(f::Function, dims::Tuple, refdims::Tuple, I) = _slicedims(f, dims, refdims, I) +@propagate_inbounds slicedims(f::Function, dims::Tuple, refdims::Tuple, I::CartesianIndex) = slicedims(f, dims, refdims, Tuple(I)) -@inline function _slicedims(f, dims::Tuple, refdims::Tuple, I::Tuple) +@propagate_inbounds function _slicedims(f, dims::Tuple, refdims::Tuple, I::Tuple) # Unnaligned may need grouped slicing newdims, newrefdims = if any(map(d -> lookup(d) isa Unaligned, dims)) # Separate out unalligned dims @@ -416,21 +419,32 @@ function slicedims end return newdims, (refdims..., newrefdims...) end -@inline _slicedims(f, dims::Tuple, refdims::Tuple, I::Tuple{}) = dims, refdims -@inline _slicedims(f, dims::DimTuple, I::Tuple{}) = dims, () -@inline function _slicedims(f, dims::DimTuple, I::Tuple{<:CartesianIndex}) +@propagate_inbounds _slicedims(f, dims::Tuple, refdims::Tuple, I::Tuple{}) = dims, refdims +@propagate_inbounds _slicedims(f, dims::DimTuple, I::Tuple{}) = dims, () +@propagate_inbounds function _slicedims(f, dims::DimTuple, I::Tuple{<:CartesianIndex}) return _slicedims(f, dims, Tuple(I[1])) end -@inline _slicedims(f, dims::DimTuple, I::Tuple) = begin +@propagate_inbounds _slicedims(f, dims::DimTuple, I::Tuple) = begin d = _slicedims(f, first(dims), first(I)) ds = _slicedims(f, tail(dims), tail(I)) (d[1]..., ds[1]...), (d[2]..., ds[2]...) end -@inline _slicedims(f, dims::Tuple{}, I::Tuple) = (), () -@inline _slicedims(f, dims::Tuple{}, I::Tuple{}) = (), () -@inline _slicedims(f, d::Dimension, i::Colon) = (d,), () -@inline _slicedims(f, d::Dimension, i::Integer) = (), (f(d, i:i),) -@inline _slicedims(f, d::Dimension, i) = (f(d, i),), () +# Return an AnonDim where e.g. a trailing Colon was passed +@propagate_inbounds function _slicedims(f, dims::Tuple{}, I::Tuple{Base.Slice,Vararg}) + d = (AnonDim(_unwrapinds(first(I))),), () + ds = _slicedims(f, (), tail(I)) + (d[1]..., ds[1]...), (d[2]..., ds[2]...) +end +# Drop trailing Integers +@propagate_inbounds _slicedims(f, dims::Tuple{}, I::Tuple{Integer,Vararg}) = _slicedims(f, (), tail(I)) +@propagate_inbounds _slicedims(f, dims::Tuple{}, I::Tuple{CartesianIndices{0,Tuple{}},Vararg}) = _slicedims(f, (), tail(I)) +@propagate_inbounds _slicedims(f, dims::Tuple{}, I::Tuple{}) = (), () +@propagate_inbounds _slicedims(f, d::Dimension, i::Colon) = (d,), () +@propagate_inbounds _slicedims(f::F, d::Dimension, i::Integer) where F = (), (f(d, i:i),) +@propagate_inbounds _slicedims(f::F, d::Dimension, i) where F = (f(d, i),), () + +_unwrapinds(s::Base.Slice) = s.indices +_unwrapinds(x) = x # Not sure this can ever be hit? but just in case _unalligned_dims(dims::Tuple) = _unalligned_dims(dims...) _unalligned_dims(dim::Dimension{<:Unaligned}, args...) = (dim, _unalligned_dims(args...)...) @@ -477,7 +491,7 @@ struct _Throw end comparedims(::Type{Bool}, args...; kw...) Check that dimensions or tuples of dimensions passed as each -argument are the same, and return the first valid dimension. +argument are the same, and return the first valid dimension. If `AbstractDimArray`s are passed as arguments their dimensions are compared. Empty tuples and `nothing` dimension values are ignored, @@ -503,8 +517,8 @@ These are all `Bool` flags: """ function comparedims end -@inline comparedims(args...; kw...) = _comparedims(_Throw, args...; kw...) -@inline comparedims(T::Type, args...; kw...) = _comparedims(T, args...; kw...) +@inline comparedims(args...; kw...) = _comparedims(_Throw, args...; kw...) +@inline comparedims(T::Type, args...; kw...) = _comparedims(T, args...; kw...) @inline _comparedims(T::Type, d1::Dimension, ds::Dimension...; kw...) = map(d -> _comparedims(T, d1, d; kw...), (d1, ds...)) @@ -574,7 +588,7 @@ end isnothing(warn) || _orderwarn(a, b, warn) return false end - if ignore_length_one && (Base.length(a) == 1 || Base.length(b) == 1) + if ignore_length_one && (Base.length(a) == 1 || Base.length(b) == 1) return true end if length && Base.length(a) != Base.length(b) @@ -651,47 +665,51 @@ See [`basetypeof`](@ref) # Utils -struct MaybeFirst end -struct AlwaysTuple end +abstract type QueryMode end +struct MaybeFirst <: QueryMode end +struct AlwaysTuple <: QueryMode end + +(::AlwaysTuple)(xs::Tuple) = xs +(::MaybeFirst)(xs::Tuple) = first(xs) +(::MaybeFirst)(::Tuple{}) = nothing # Call the function f with stardardised args # This looks like HELL, but it removes this complexity # from every other method and makes sure they all behave the same way. -@inline _call_primitive(f::Function, t, a1, args...) = - _call_primitive(f, t, <:, _wraparg(a1, args...)...) -@inline _call_primitive(f::Function, t, op::Function, a1, args...) = - _call_primitive1(f, t, op, _wraparg(a1, args...)...) - -@inline _call_primitive1(f, t, op::Function, x, l1, l2, ls...) = _call_primitive1(f, t, op, x, (l1, l2, ls...)) -@inline _call_primitive1(f, t, op::Function, x) = _call_primitive1(f, t, op, dims(x)) -@inline _call_primitive1(f, t, op::Function, x, query) = _call_primitive1(f, t, op, dims(x), query) -@inline _call_primitive1(f, t, op::Function, x::Nothing) = _dimsnotdefinederror() -@inline _call_primitive1(f, t, op::Function, x::Nothing, query) = _dimsnotdefinederror() -@inline function _call_primitive1(f, t, op::Function, ds::Tuple, query::Function) +@inline _dim_query(f::Function, t::QueryMode, args...) = + _dim_query(f, <:, t::QueryMode, args...) +@inline _dim_query(f::Function, t::QueryMode, op::Union{typeof(<:),typeof(>:)}, args...) = + _dim_query(f, op, t::QueryMode, args...) +@inline _dim_query(f::Function, t::QueryMode, op::Union{typeof(<:),typeof(>:)}, a1, args::Tuple) = + _dim_query(f, op, t::QueryMode, a1, args...) +@inline _dim_query(f::Function, op::Function, t::QueryMode, a1, args...) = + _dim_query1(f, op, t, _wraparg(a1, args...)...) +@inline _dim_query(f::Function, op::Function, t::QueryMode, a1, args::Tuple) = + _dim_query1(f, op, t::QueryMode, _wraparg(a1)..., _wraparg(args...)) + +@inline _dim_query1(f, op::Function, t, x, l1, l2, ls...) = _dim_query1(f, op, t, x, (l1, l2, ls...)) +@inline _dim_query1(f, op::Function, t, x) = _dim_query1(f, op, t, dims(x)) +@inline _dim_query1(f, op::Function, t, x, query) = _dim_query1(f, op, t, dims(x), query) +@inline _dim_query1(f, op::Function, t, x::Nothing) = _dimsnotdefinederror() +@inline _dim_query1(f, op::Function, t, x::Nothing, query) = _dimsnotdefinederror() +@inline function _dim_query1(f, op::Function, t, ds::Tuple, query::Function) selection = foldl(ds; init=()) do acc, d query(d) ? (acc..., d) : acc end - _call_primitive1(f, t, op, ds, selection) + _dim_query1(f, op, t, ds, selection) end -@inline function _call_primitive1(f, t, op::Function, d::Tuple, query) +@inline function _dim_query1(f, op::Function, t, d::Tuple, query) ds = dims(query) isnothing(ds) && _dims_are_not_dims() - _call_primitive1(f, t, op, d, ds) + _dim_query1(f, op, t, d, ds) end +@inline _dim_query1(f, op::Function, t::QueryMode, d::Tuple, query::Union{Dimension,DimType,Val,Integer}) = + _dim_query1(f, op, t, d, (query,)) |> t +@inline _dim_query1(f, op::Function, ::QueryMode, d::Tuple, query::Tuple) = map(unwrap, f(op, d, query)) +@inline _dim_query1(f, op::Function, ::QueryMode, d::Tuple) = map(unwrap, f(op, d)) _dims_are_not_dims() = throw(ArgumentError("`dims` are not `Dimension`s")) -@inline _call_primitive1(f, t::AlwaysTuple, op::Function, d::Tuple, query::Union{Dimension,DimType,Val,Integer}) = - _call_primitive1(f, t, op, d, (query,)) -@inline _call_primitive1(f, t::MaybeFirst, op::Function, d::Tuple, query::Union{Dimension,DimType,Val,Integer}) = - _call_primitive1(f, t, op, d, (query,)) |> _maybefirst -@inline _call_primitive1(f, t, op::Function, d::Tuple, query::Tuple) = map(unwrap, f(op, d, query)) -@inline _call_primitive1(f, t, op::Function, d::Tuple) = map(unwrap, f(op, d)) - - -_maybefirst(xs::Tuple) = first(xs) -_maybefirst(::Tuple{}) = nothing - @inline kwdims(kw::Base.Iterators.Pairs) = kwdims(values(kw)) # Convert `Symbol` keyword arguments to a `Tuple` of `Dimension` @inline kwdims(kw::NamedTuple{Keys}) where Keys = kwdims(key2dim(Keys), values(kw)) @@ -722,7 +740,7 @@ _maybefirst(::Tuple{}) = nothing @inline _w(t::Tuple) = _wraparg(t...) @inline _w(x) = x @inline _w(s::Symbol) = key2dim(s) -@inline _w(x::Type{T}) where T = Val{T}() +@inline _w(::Type{T}) where T = Val{T}() @inline _flip_subtype(::typeof(<:)) = >: @inline _flip_subtype(::typeof(>:)) = <: @@ -731,7 +749,7 @@ _astuple(t::Tuple) = t _astuple(x) = (x,) -# Warnings and Error methods. +# Warnings and Error methods. _dimsmismatchmsg(a, b) = "$(basetypeof(a)) and $(basetypeof(b)) dims on the same axis." _valmsg(a, b) = "Lookup values for $(basetypeof(a)) of $(parent(a)) and $(parent(b)) do not match." _dimsizemsg(a, b) = "Found both lengths $(length(a)) and $(length(b)) for $(basetypeof(a))." diff --git a/src/Dimensions/show.jl b/src/Dimensions/show.jl index feff6aee0..34f7622de 100644 --- a/src/Dimensions/show.jl +++ b/src/Dimensions/show.jl @@ -14,7 +14,7 @@ function dimcolors(i) end function show_dims(io::IO, mime::MIME"text/plain", dims::DimTuple; - colors=map(x -> get(io, :dimcolo, dimcolors(x)), ntuple(identity, length(dims))) + colors=map(x -> get(io, :dimcolor, dimcolors(x)), ntuple(identity, length(dims))) ) ctx = IOContext(io, :compact => true) inset = get(io, :inset, "") diff --git a/src/LookupArrays/LookupArrays.jl b/src/LookupArrays/LookupArrays.jl index 7b01f3cda..945a4c885 100644 --- a/src/LookupArrays/LookupArrays.jl +++ b/src/LookupArrays/LookupArrays.jl @@ -52,6 +52,7 @@ export Unaligned, Transformed const StandardIndices = Union{AbstractArray{<:Integer},Colon,Integer,CartesianIndex,CartesianIndices} +# As much as possible keyword rebuild is automatic rebuild(x; kw...) = ConstructionBase.setproperties(x, (; kw...)) include("metadata.jl") diff --git a/src/LookupArrays/selector.jl b/src/LookupArrays/selector.jl index 4ce5ed72c..9db460ef3 100644 --- a/src/LookupArrays/selector.jl +++ b/src/LookupArrays/selector.jl @@ -38,6 +38,7 @@ val(sel::Selector) = sel.val rebuild(sel::Selector, val) = basetypeof(sel)(val) Base.parent(sel::Selector) = sel.val +Base.to_index(sel::Selector) = sel abstract type Selector{T} end @@ -71,7 +72,7 @@ const SelectorOrInterval = Union{Selector,Interval,Not} const SelTuple = Tuple{SelectorOrInterval,Vararg{SelectorOrInterval}} -# `Not` form InvertedIndices.jl +# `Not` form InvertedIndices.jr function selectindices(l::LookupArray, sel::Not; kw...) indices = selectindices(l, sel.skip; kw...) return first(to_indices(l, (Not(indices),))) @@ -112,34 +113,38 @@ struct At{T,A,R} <: IntSelector{T} rtol::R end At(val; atol=nothing, rtol=nothing) = At(val, atol, rtol) +At() = At(nothing) + +rebuild(sel::At, val) = At(val, sel.atol, sel.rtol) -rebvuild(sel::At, val) = At(val, sel.atol, sel.rtol) atol(sel::At) = sel.atol rtol(sel::At) = sel.rtol +Base.show(io::IO, x::At) = print(io, "At(", val(x), ", ", atol(x), ", ", rtol(x), ")") + struct _True end struct _False end selectindices(l::LookupArray, sel::At; kw...) = at(l, sel; kw...) -selectindices(l::LookupArray, sel::At{<:AbstractVector}) = _selectvec(l, sel) +selectindices(l::LookupArray, sel::At{<:AbstractVector}; kw...) = _selectvec(l, sel; kw...) -_selectvec(l, sel) = [selectindices(l, rebuild(sel, v)) for v in val(sel)] +_selectvec(l, sel; kw...) = [selectindices(l, rebuild(sel, v); kw...) for v in val(sel)] function at(lookup::AbstractCyclic{Cycling}, sel::At; kw...) cycled_sel = rebuild(sel, cycle_val(lookup, val(sel))) return at(no_cycling(lookup), cycled_sel; kw...) end -function at(lookup::NoLookup, sel::At; kw...) +function at(lookup::NoLookup, sel::At; err=_True(), kw...) v = val(sel) r = round(Int, v) at = atol(sel) if isnothing(at) - v == r || _selvalnotfound(lookup, v) + v == r || _selnotfound_or_nothing(err, lookup, v) else at >= 0.5 && error("atol must be small than 0.5 for NoLookup") - isapprox(v, r; atol=at) || _selvalnotfound(lookup, v) + isapprox(v, r; atol=at) || _selnotfound_or_nothing(err, lookup, v) end - r in lookup || throw(SelectorError(lookup, sel)) + r in lookup || err isa _False || throw(SelectorError(lookup, sel)) return r end function at(lookup::LookupArray, sel::At; kw...) @@ -165,8 +170,11 @@ function at( ) x = unwrap(selval) i = searchsortedlast(lookup, x; lt=(a, b) -> a.left < b.left) - lookup[i].left == x.left && lookup[i].right == x.right || _selvalnotfound(lookup, selval) - return i + if lookup[i].left == x.left && lookup[i].right == x.right + return i + else + return _selnotfound_or_nothing(err, lookup, selval) + end end function at( ::Ordered, ::Span, lookup::LookupArray{<:Union{Number,Dates.TimeType,AbstractString}}, selval, atol, rtol::Nothing; @@ -206,10 +214,12 @@ end @inline _is_at(x, y, atol) = x == y @inline _is_at(x::Real, y::Real, atol::Real) = abs(x - y) <= atol +@inline _is_at(x::Real, ys::AbstractArray, atol) = any(y -> _is_at(x, y, atol), ys) +@inline _is_at(xs::AbstractArray, y::Real, atol) = any(x -> _is_at(x, y, atol), xs) -_selnotfound_or_nothing(err::_True, lookup, selval) = _selvalnotfound(lookup, selval) +_selnotfound_or_nothing(err::_True, lookup, selval) = _selnotfound(lookup, selval) _selnotfound_or_nothing(err::_False, lookup, selval) = nothing -@noinline _selvalnotfound(lookup, selval) = throw(ArgumentError("$selval not found in $lookup")) +@noinline _selnotfound(lookup, selval) = throw(ArgumentError("$selval for not found in $lookup")) """ Near <: IntSelector @@ -238,10 +248,13 @@ A[X(Near(23)), Y(Near(5.1))] struct Near{T} <: IntSelector{T} val::T end +Near() = Near(nothing) selectindices(l::LookupArray, sel::Near) = near(l, sel) selectindices(l::LookupArray, sel::Near{<:AbstractVector}) = _selectvec(l, sel) +Base.show(io::IO, x::Near) = print(io, "Near(", val(x), ")") + function near(lookup::AbstractCyclic{Cycling}, sel::Near) cycled_sel = rebuild(sel, cycle_val(lookup, val(sel))) near(no_cycling(lookup), cycled_sel) @@ -327,10 +340,13 @@ A[X(Contains(8)), Y(Contains(6.8))] struct Contains{T} <: IntSelector{T} val::T end +Contains() = Contains(nothing) # Filter based on sampling and selector ----------------- -selectindices(l::LookupArray, sel::Contains; kw...) = contains(l, sel) -selectindices(l::LookupArray, sel::Contains{<:AbstractVector}) = _selectvec(l, sel) +selectindices(l::LookupArray, sel::Contains; kw...) = contains(l, sel; kw...) +selectindices(l::LookupArray, sel::Contains{<:AbstractVector}; kw...) = _selectvec(l, sel; kw...) + +Base.show(io::IO, x::Contains) = print(io, "Contains(", val(x), ")") function contains(lookup::AbstractCyclic{Cycling}, sel::Contains; kw...) cycled_sel = rebuild(sel, cycle_val(lookup, val(sel))) @@ -347,17 +363,13 @@ function contains(::NoSampling, l::LookupArray, sel::Contains; kw...) at(l, At(val(sel)); kw...) end # Points -------------------------------------- -function contains(::Points, l::LookupArray, sel::Contains; kw...) - at(l, At(val(sel)); kw...) +function contains(sampling::Points, l::LookupArray, sel::Contains; kw...) + contains(order(l), sampling, l, sel; kw...) end -function contains(::Points, l::LookupArray{<:AbstractArray}, sel::Contains; kw...) - x = unwrap(val(sel)) - i = searchsortedlast(l, x; by=_by) - i >= firstindex(l) && x in l[i] || _selvalnotfound(l, val(sel)) - return i +function contains(::Order, ::Points, l::LookupArray, sel::Contains; kw...) + at(l, At(val(sel)); kw...) end -function contains( - ::Points, l::LookupArray{<:AbstractArray}, sel::Contains{<:AbstractArray}; +function contains(::Order, ::Points, l::LookupArray{<:AbstractArray}, sel::Contains{<:AbstractArray}; kw... ) at(l, At(val(sel)); kw...) @@ -524,6 +536,7 @@ struct Between{T<:Union{<:AbstractVector{<:Tuple{Any,Any}},Tuple{Any,Any},Nothin end Between(args...) = Between(args) +Base.show(io::IO, x::Between) = print(io, "Between(", val(x), ")") Base.first(sel::Between) = first(val(sel)) Base.last(sel::Between) = last(val(sel)) @@ -954,6 +967,8 @@ end val(sel::Where) = sel.f +Base.show(io::IO, x::Where) = print(io, "Where(", repr(val(x)), ")") + # Yes this is everything. `Where` doesn't need lookup specialisation @inline function selectindices(lookup::LookupArray, sel::Where) [i for (i, v) in enumerate(parent(lookup)) if sel.f(v)] @@ -991,11 +1006,14 @@ struct All{S<:Tuple{Vararg{SelectorOrInterval}}} <: Selector{S} end All(args::SelectorOrInterval...) = All(args) +Base.show(io::IO, x::All) = print(io, "All(", x.selectors, ")") + @inline function selectindices(lookup::LookupArray, sel::All) results = map(s -> selectindices(lookup, s), sel.selectors) sort!(union(results...)) end + # selectindices ========================================================================== diff --git a/src/LookupArrays/set.jl b/src/LookupArrays/set.jl index be40636e1..bccb53cae 100644 --- a/src/LookupArrays/set.jl +++ b/src/LookupArrays/set.jl @@ -34,6 +34,7 @@ _set(lookup::LookupArray, 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::LookupArray, newlookup::NoLookup{<:AutoIndex}) = NoLookup(axes(lookup, 1)) _set(lookup::LookupArray, newlookup::NoLookup) = newlookup diff --git a/src/array/array.jl b/src/array/array.jl index ebb382661..c7f32b19a 100644 --- a/src/array/array.jl +++ b/src/array/array.jl @@ -1,5 +1,41 @@ +const IDim = Dimension{<:StandardIndices} + +""" + AbstractBasicDimArray <: AbstractArray + +The abstract supertype for all arrays with a `dims` method that +returns a `Tuple` of `Dimension` + +Only keyword `rebuild` is guaranteed to work with `AbstractBasicDimArray`. +""" +abstract type AbstractBasicDimArray{T,N,D<:Tuple} <: AbstractArray{T,N} end + +const AbstractBasicDimVector = AbstractBasicDimArray{T,1} where T +const AbstractBasicDimMatrix = AbstractBasicDimArray{T,2} where T +const AbstractBasicDimVecOrMat = Union{AbstractBasicDimVector,AbstractBasicDimMatrix} + +# DimensionalData.jl interface methods #################################################### + +for func in (:val, :index, :lookup, :order, :sampling, :span, :locus, :bounds, :intervalbounds) + @eval ($func)(A::AbstractBasicDimArray, args...) = ($func)(dims(A), args...) +end + +Extents.extent(A::AbstractBasicDimArray, args...) = Extents.extent(dims(A), args...) + +Base.size(A::AbstractBasicDimArray) = map(length, dims(A)) +Base.size(A::AbstractBasicDimArray, dims::DimOrDimType) = size(A, dimnum(A, dims)) +Base.axes(A::AbstractBasicDimArray) = map(d -> axes(d, 1), dims(A)) +Base.axes(A::AbstractBasicDimArray, dims::DimOrDimType) = axes(A, dimnum(A, dims)) +# This is too slow using the default, as it calls `axes` and makes DimUnitRanges +Base.CartesianIndices(s::AbstractBasicDimArray) = CartesianIndices(map(first ∘ axes, lookup(s))) + +Base.checkbounds(::Type{Bool}, A::AbstractBasicDimArray, d1::IDim, dims::IDim...) = + Base.checkbounds(Bool, A, dims2indices(A, (d1, dims...))...) +Base.checkbounds(A::AbstractBasicDimArray, d1::IDim, dims::IDim...) = + Base.checkbounds(A, dims2indices(A, (d1, dims...))...) + """ - AbstractDimArray <: AbstractArray + AbstractDimArray <: AbstractBasicArray Abstract supertype for all "dim" arrays. @@ -17,24 +53,14 @@ A [`rebuild`](@ref) method for `AbstractDimArray` must accept Indexing `AbstractDimArray` with non-range `AbstractArray` has undefined effects on the `Dimension` index. Use forward-ordered arrays only" """ -abstract type AbstractDimArray{T,N,D<:Tuple,A} <: AbstractArray{T,N} end +abstract type AbstractDimArray{T,N,D<:Tuple,A} <: AbstractBasicDimArray{T,N,D} end const AbstractDimVector = AbstractDimArray{T,1} where T const AbstractDimMatrix = AbstractDimArray{T,2} where T const AbstractDimVecOrMat = Union{AbstractDimVector,AbstractDimMatrix} - # DimensionalData.jl interface methods #################################################### - -# Standard fields -dims(A::AbstractDimArray) = A.dims -refdims(A::AbstractDimArray) = A.refdims -data(A::AbstractDimArray) = A.data -name(A::AbstractDimArray) = A.name -metadata(A::AbstractDimArray) = A.metadata - -layerdims(A::AbstractDimArray) = basedims(A) - + """ rebuild(A::AbstractDimArray, data, [dims, refdims, name, metadata]) => AbstractDimArray rebuild(A::AbstractDimArray; kw...) => AbstractDimArray @@ -55,16 +81,20 @@ For readability it is preferable to use keyword versions for any more than a few rebuild(A, data, dims, refdims, name, metadata(A)) end -@inline rebuildsliced(A::AbstractDimArray, args...) = rebuildsliced(getindex, A, args...) -@inline rebuildsliced(f::Function, A::AbstractDimArray, data::AbstractArray, I::Tuple, name=name(A)) = - rebuild(A, data, slicedims(f, A, I)..., name) +# Standard fields +dims(A::AbstractDimArray) = A.dims +refdims(A::AbstractDimArray) = A.refdims +data(A::AbstractDimArray) = A.data # Don't use this method directly, use `parent` +name(A::AbstractDimArray) = A.name +metadata(A::AbstractDimArray) = A.metadata -for func in (:val, :index, :lookup, :metadata, :order, :sampling, :span, :locus, :bounds, :intervalbounds) - @eval ($func)(A::AbstractDimArray, args...) = ($func)(dims(A), args...) -end +layerdims(A::AbstractDimArray) = basedims(A) -Extents.extent(A::AbstractDimArray, args...) = Extents.extent(dims(A), args...) - +@inline rebuildsliced(A::AbstractBasicDimArray, args...) = rebuildsliced(getindex, A, args...) +@inline function rebuildsliced(f::Function, A::AbstractBasicDimArray, data::AbstractArray, I::Tuple, name=name(A)) + I1 = to_indices(A, I) + rebuild(A, data, slicedims(f, A, I1)..., name) +end # Array interface methods ###################################################### @@ -74,20 +104,10 @@ Base.iterate(A::AbstractDimArray, args...) = iterate(parent(A), args...) Base.IndexStyle(A::AbstractDimArray) = Base.IndexStyle(parent(A)) Base.parent(A::AbstractDimArray) = data(A) Base.vec(A::AbstractDimArray) = vec(parent(A)) -@inline Base.axes(A::AbstractDimArray, dims::DimOrDimType) = axes(A, dimnum(A, dims)) -@inline Base.size(A::AbstractDimArray, dims::DimOrDimType) = size(A, dimnum(A, dims)) # Only compare data and dim - metadata and refdims can be different Base.:(==)(A1::AbstractDimArray, A2::AbstractDimArray) = parent(A1) == parent(A2) && dims(A1) == dims(A2) -const IDim = Dimension{<:StandardIndices} -function Base.checkbounds(::Type{Bool}, A::AbstractDimArray, dims::IDim...) - Base.checkbounds(Bool, A, dims2indices(A, dims)...) -end -function Base.checkbounds(A::AbstractDimArray, dims::IDim...) - Base.checkbounds(A, dims2indices(A, dims)...) -end - # undef constructor for Array, using dims function Base.Array{T}(x::UndefInitializer, d1::Dimension, dims::Dimension...) where T Base.Array{T}(x, (d1, dims...)) @@ -120,6 +140,12 @@ Base.similar(A::AbstractDimArray) = rebuild(A; data=similar(parent(A)), dims=dims(A), refdims=refdims(A), name=_noname(A), metadata=metadata(A)) Base.similar(A::AbstractDimArray, ::Type{T}) where T = rebuild(A; data=similar(parent(A), T), dims=dims(A), refdims=refdims(A), name=_noname(A), metadata=metadata(A)) + +# We avoid calling `parent` for AbstractBasicDimArray as we don't know what it is/if there is one +Base.similar(A::AbstractBasicDimArray{T}) where T = + rebuild(A; data=similar(typeof(A), T), dims=dims(A), refdims=refdims(A), name=_noname(A), metadata=metadata(A)) +Base.similar(A::AbstractBasicDimArray, ::Type{T}) where T = + rebuild(A; data=similar(typeof(A)), dims=dims(A), refdims=refdims(A), name=_noname(A), metadata=metadata(A)) # We can't resize the dims or add missing dims, so return the unwraped Array type? # An alternative would be to fill missing dims with `Anon`, and keep existing # dims but strip the Lookup? It just seems a little complicated when the methods @@ -142,13 +168,13 @@ const MaybeDimUnitRange = Union{Integer,Base.OneTo,Dimensions.DimUnitRange} for s1 in (:(Dimensions.DimUnitRange), :MaybeDimUnitRange) s1 === :MaybeDimUnitRange || @eval begin function Base.similar( - A::AbstractArray, T::Type, shape::Tuple{$s1,Vararg{MaybeDimUnitRange}} - ) + A::AbstractArray, ::Type{T}, shape::Tuple{$s1,Vararg{MaybeDimUnitRange}} + ) where T _similar(A, T, shape) end function Base.similar( - T::Type{<:AbstractArray}, shape::Tuple{$s1,Vararg{MaybeDimUnitRange}} - ) + ::Type{T}, shape::Tuple{$s1,Vararg{MaybeDimUnitRange}} + ) where T<:AbstractArray _similar(T, shape) end end @@ -367,12 +393,20 @@ function DimArray(; data, dims, refdims=(), name=NoName(), metadata=NoMetadata() end # Construct from another AbstractDimArray function DimArray(A::AbstractDimArray; - data=data(A), dims=dims(A), refdims=refdims(A), name=name(A), metadata=metadata(A) + data=parent(A), dims=dims(A), refdims=refdims(A), name=name(A), metadata=metadata(A) ) DimArray(data, dims; refdims, name, metadata) end DimArray{T}(A::AbstractDimArray; kw...) where T = DimArray(convert.(T, A)) DimArray{T}(A::AbstractDimArray{T}; kw...) where T = DimArray(A; kw...) +# We collect other kinds of AbstractBasicDimArray +# to avoid complicated nesting of dims +function DimArray(A::AbstractBasicDimArray; + data=parent(A), dims=dims(A), refdims=refdims(A), name=name(A), metadata=metadata(A) +) + newdata = collect(data) + DimArray(newdata, format(dims, newdata); refdims, name, metadata) +end """ DimArray(f::Function, dim::Dimension; [name]) @@ -628,7 +662,7 @@ end # Allow customising constructors based on dimension types # Thed default constructor is DimArray dimconstructor(dims::DimTuple) = dimconstructor(tail(dims)) -dimconstructor(dims::Tuple{}) = DimArray +dimconstructor(::Tuple{}) = DimArray """ mergedims(old_dims => new_dim) => Dimension @@ -659,6 +693,12 @@ Dim{:time} MergedLookup{Tuple{Int64}} Tuple{Int64}[(0,), (3,), (4,)] Ti, Dim{:space} MergedLookup{Tuple{Float64, Int64}} Tuple{Float64, Int64}[(0.0, 10), (0.1, 10), …, (0.3, 100), (0.4, 100)] X, Y ```` """ +function mergedims(x, dt1::Tuple, dts::Tuple...) + pairs = map((dt1, dts...)) do ds + ds => Dim{Symbol(map(dim2key, ds)...)}() + end + mergedims(x, pairs...) +end function mergedims(all_dims, dim_pairs::Pair...) # filter out dims completely missing dim_pairs = map(x -> _filter_dims(all_dims, first(x)) => last(x), dim_pairs) @@ -695,8 +735,8 @@ function mergedims(A::AbstractDimArray, dim_pairs::Pair...) dimsmatch(all_dims, dims_new) && return A dims_perm = _unmergedims(dims_new, map(last, dim_pairs)) Aperm = PermutedDimsArray(A, dims_perm) - data_merged = reshape(data(Aperm), map(length, dims_new)) - rebuild(A, data_merged, dims_new) + data_merged = reshape(parent(Aperm), map(length, dims_new)) + return rebuild(A, data_merged, dims_new) end """ @@ -716,10 +756,10 @@ end Return a new array or stack whose dimensions are restored to their original prior to calling [`mergedims(A, dim_pairs)`](@ref). """ -function unmergedims(A::AbstractDimArray, original_dims) +function unmergedims(A::AbstractBasicDimArray, original_dims) merged_dims = dims(A) unmerged_dims = unmergedims(merged_dims) - reshaped = reshape(data(A), size(unmerged_dims)) + reshaped = reshape(parent(A), size(unmerged_dims)) permuted = permutedims(reshaped, dimnum(unmerged_dims, original_dims)) return DimArray(permuted, original_dims) end diff --git a/src/array/indexing.jl b/src/array/indexing.jl index 68103710c..5b24dd382 100644 --- a/src/array/indexing.jl +++ b/src/array/indexing.jl @@ -10,8 +10,12 @@ @propagate_inbounds Base.view(A::AbstractDimArray) = rebuild(A, Base.view(parent(A)), ()) const SelectorOrStandard = Union{SelectorOrInterval,StandardIndices} +const DimensionIndsArrays = Union{AbstractArray{<:Dimension},AbstractArray{<:DimTuple}} +const DimensionalIndices = Union{DimTuple,DimIndices,DimSelectors,Dimension,DimensionIndsArrays} +const _DimIndicesAmb = Union{AbstractArray{Union{}},DimIndices{<:Integer},DimSelectors{<:Integer}} for f in (:getindex, :view, :dotview) + _f = Symbol(:_, f) @eval begin if Base.$f === Base.view @eval @propagate_inbounds function Base.$f(A::AbstractDimArray, i::Union{CartesianIndex,CartesianIndices}) @@ -26,6 +30,7 @@ for f in (:getindex, :view, :dotview) 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 @@ -33,22 +38,38 @@ for f in (:getindex, :view, :dotview) 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{<:Union{<:Integer,<:Bool}}}) = + @propagate_inbounds Base.$f(A::AbstractDimArray, i::Union{Colon,AbstractArray{<:Integer}}) = Base.$f(parent(A), i) # Except 1D DimArrays - @propagate_inbounds Base.$f(A::AbstractDimArray{<:Any,1}, i::Union{Colon,AbstractArray{<:Union{<:Integer,<:Bool}}}) = + @propagate_inbounds Base.$f(A::AbstractDimVector, i::Union{Colon,AbstractArray{<:Integer}}) = rebuildsliced(Base.$f, A, Base.$f(parent(A), i), (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, extent::Extents.Extent) = + + @propagate_inbounds Base.$f(A::AbstractBasicDimArray, extent::Extents.Extent) = Base.$f(A, dims2indices(A, extent)...) - # Dimension indexing. Allows indexing with A[somedim=At(25.0)] for Dim{:somedim} - @propagate_inbounds Base.$f(A::AbstractDimArray, args::Dimension...; kw...) = - Base.$f(A, dims2indices(A, (args..., kwdims(values(kw))...))...) - # Everything else works on the parent array - such as custom indexing types from other packages. - # We can't know what they do so cant handle the potential dimension transformations - @propagate_inbounds Base.$f(A::AbstractDimArray, i1, I...) = Base.$f(parent(A), i1, I...) + # All Dimension indexing modes combined + @propagate_inbounds Base.$f(A::AbstractBasicDimArray, D::DimensionalIndices...; kw...) = + $_f(A, _simplify_dim_indices(D..., kwdims(values(kw))...)...) + # 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...) + + # Use underscore methods to minimise ambiguities + @propagate_inbounds $_f(A::AbstractBasicDimArray, d1::Dimension, ds::Dimension...) = + Base.$f(A, dims2indices(A, (d1, ds...))...) + @propagate_inbounds $_f(A::AbstractBasicDimArray, ds::Dimension...; kw...) = + Base.$f(A, dims2indices(A, ds)...) + @propagate_inbounds function $_f( + A::AbstractBasicDimArray, dims::Union{Dimension,DimensionIndsArrays}... + ) + return merge_and_index($f, A, dims) + end end # Standard indices if f == :view @@ -58,14 +79,116 @@ for f in (:getindex, :view, :dotview) rebuildsliced(Base.$f, A, x, I) end else - @eval @propagate_inbounds function Base.$f(A::AbstractDimArray, i1::StandardIndices, i2::StandardIndices, I::StandardIndices...) - I = to_indices(A, (i1, i2, I...)) + @eval @propagate_inbounds function Base.$f(A::AbstractDimArray, i1::StandardIndices, i2::StandardIndices, Is::StandardIndices...) + I = to_indices(A, (i1, i2, Is...)) x = Base.$f(parent(A), I...) all(i -> i isa Integer, I) ? x : rebuildsliced(Base.$f, A, x, I) end end end + +function merge_and_index(f, A, dims) + dims, inds_arrays = _separate_dims_arrays(_simplify_dim_indices(dims...)...) + # No arrays here, so abort (dispatch is tricky...) + length(inds_arrays) == 0 && return f(A, dims...) + + V = length(dims) > 0 ? view(A, dims...) : A + # We have an array of dims of dim tuples + x = reduce(inds_arrays[1:end-1]; init=V) do A, i + _merge_and_index(view, A, i) + end + + return _merge_and_index(f, x, inds_arrays[end]) +end + +function _merge_and_index(f, A, inds) + # Get any other dimensions not passed in + dims_to_merge = first(inds) + if length(dims_to_merge) > 1 + if inds isa AbstractVector + M = mergedims(A, dims_to_merge) + ods = otherdims(M, DD.dims(A)) + if length(ods) > 0 + mdim = only(ods) + lazylinear = rebuild(mdim, LazyDims2Linear(inds, DD.dims(A, dims_to_merge))) + f(M, lazylinear) + else + # Index anyway with al Colon() just for type consistency + f(M, basedims(M)...) + end + else + minds = CartesianIndex.(dims2indices.(Ref(A), inds)) + f(A, minds) + end + else + d = first(dims_to_merge) + val_array = reinterpret(typeof(val(d)), dims_to_merge) + f(A, rebuild(d, val_array)) + end +end + +# This AbstractArray is for indexing, presenting as Array{CartesianIndex} +# `CartesianIndex` are generated on the fly from `dimtuples`, +# which is e.g. a view into DimIndices +struct LazyDims2Cartesian{T,N,D,A<:AbstractArray{<:Any,N}} <: AbstractArray{T,N} + dimtuples::A + dims::D +end +function LazyDims2Cartesian(dimtuples::A, dims::D) where {A<:AbstractArray{<:DimTuple,N},D<:DimTuple} where N + LazyDims2Cartesian{CartesianIndex{length(dims)},N,D,A}(dimtuples, dims) +end + +dims(A::LazyDims2Cartesian) = A.dims + +Base.size(A::LazyDims2Cartesian) = size(A.dimtuples) +Base.getindex(A::LazyDims2Cartesian, I::Integer...) = + CartesianIndex(dims2indices(DD.dims(A), A.dimtuples[I...])) + +struct LazyDims2Linear{N,D,A<:AbstractArray{<:Any,N}} <: AbstractArray{Int,N} + dimtuples::A + dims::D +end +function LazyDims2Linear(dimtuples::A, dims::D) where {A<:AbstractArray{<:DimTuple,N},D<:DimTuple} where N + LazyDims2Linear{N,D,A}(dimtuples, dims) +end + +dims(A::LazyDims2Linear) = A.dims + +Base.size(A::LazyDims2Linear) = size(A.dimtuples) +Base.getindex(A::LazyDims2Linear, I::Integer...) = + LinearIndices(size(dims(A)))[dims2indices(DD.dims(A), A.dimtuples[I...])...] + +function _separate_dims_arrays(d::Dimension, ds...) + ds, as = _separate_dims_arrays(ds...) + (ds..., d), as +end +function _separate_dims_arrays(a::AbstractArray, ds...) + ds, as = _separate_dims_arrays(ds...) + ds, (a, as...) +end +_separate_dims_arrays() = (), () + +Base.@assume_effects :foldable _simplify_dim_indices(d::Dimension, ds...) = + (d, _simplify_dim_indices(ds)...) +Base.@assume_effects :foldable _simplify_dim_indices(d::Tuple, ds...) = + (d..., _simplify_dim_indices(ds)...) +Base.@assume_effects :foldable _simplify_dim_indices(d::AbstractArray{<:Dimension}, ds...) = + (d, _simplify_dim_indices(ds)...) +Base.@assume_effects :foldable _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...) = + (dims(d)..., _simplify_dim_indices(ds)...) +Base.@assume_effects :foldable 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() = () + @inline _unwrap_cartesian(i1::CartesianIndices, I...) = (Tuple(i1)..., _unwrap_cartesian(I...)...) @inline _unwrap_cartesian(i1::CartesianIndex, I...) = (Tuple(i1)..., _unwrap_cartesian(I...)...) @inline _unwrap_cartesian(i1, I...) = (i1, _unwrap_cartesian(I...)...) @@ -82,7 +205,7 @@ end setindex!(parent(A), x, i1, I...) # For @views macro to work with keywords -Base.maybeview(A::AbstractDimArray, args...; kw...) = +Base.maybeview(A::AbstractDimArray, args...; kw...) = view(A, args...; kw...) -Base.maybeview(A::AbstractDimArray, args::Vararg{Union{Number,Base.AbstractCartesianIndex}}; kw...) = +Base.maybeview(A::AbstractDimArray, args::Vararg{Union{Number,Base.AbstractCartesianIndex}}; kw...) = view(A, args...; kw...) diff --git a/src/array/methods.jl b/src/array/methods.jl index 4cbb98305..ad9d16ea7 100644 --- a/src/array/methods.jl +++ b/src/array/methods.jl @@ -1,7 +1,7 @@ # Array info for (m, f) in ((:Base, :size), (:Base, :axes), (:Base, :firstindex), (:Base, :lastindex)) @eval begin - @inline $m.$f(A::AbstractDimArray, dims::AllDims) = $m.$f(A, dimnum(A, dims)) + @inline $m.$f(A::AbstractBasicDimArray, dims::AllDims) = $m.$f(A, dimnum(A, dims)) end end @@ -165,6 +165,12 @@ else end end +# works for arrays and for stacks +function _eachslice(x, dims::Tuple) + slicedims = Dimensions.dims(x, dims) + return (view(x, d...) for d in DimIndices(slicedims)) +end + # These just return the parent for now function Base.sort(A::AbstractDimVector; kw...) newdims = (set(only(dims(A)), NoLookup()),) @@ -195,12 +201,6 @@ Base.cumsum(A::AbstractDimArray; dims) = rebuild(A, cumsum(parent(A); dims=dimnu Base.cumsum!(B::AbstractArray, A::AbstractDimVector) = cumsum!(B, parent(A)) Base.cumsum!(B::AbstractArray, A::AbstractDimArray; dims) = cumsum!(B, parent(A); dims=dimnum(A, dims)) -# works for arrays and for stacks -function _eachslice(x, dims::Tuple) - slicedims = Dimensions.dims(x, dims) - return (view(x, d...) for d in DimIndices(slicedims)) -end - # Duplicated dims for fname in (:cor, :cov) diff --git a/src/array/show.jl b/src/array/show.jl index 8ae263f9b..c4ac20ed2 100644 --- a/src/array/show.jl +++ b/src/array/show.jl @@ -1,15 +1,15 @@ using DimensionalData.Dimensions: dimcolors, dimsymbols, print_dims # Base show -function Base.summary(io::IO, A::AbstractDimArray{T,N}) where {T,N} +function Base.summary(io::IO, A::AbstractBasicDimArray{T,N}) where {T,N} print_ndims(io, size(A)) print_type(io, A) print_name(io, name(A)) end # Fancy show for text/plain -function Base.show(io::IO, mime::MIME"text/plain", A::AbstractDimArray{T,N}) where {T,N} - lines, maxlen = show_main(io, mime, A::AbstractDimArray) +function Base.show(io::IO, mime::MIME"text/plain", A::AbstractBasicDimArray{T,N}) where {T,N} + lines, maxlen = show_main(io, mime, A::AbstractBasicDimArray) # Printing the array data is optional, subtypes can # show other things here instead. ds = displaysize(io) @@ -34,7 +34,7 @@ print_top(io, mime, A) But read the DimensionalData.jl `show.jl` code for details. """ -function show_main(io, mime, A::AbstractDimArray) +function show_main(io, mime, A::AbstractBasicDimArray) lines_t, maxlen, width = print_top(io, mime, A) lines_m, maxlen = print_metadata_block(io, mime, metadata(A); width, maxlen=min(width, maxlen)) return lines_t + lines_m, maxlen @@ -62,13 +62,12 @@ print_block_close(io, maxlen) But read the DimensionalData.jl `show.jl` code for details. """ -function show_after(io::IO, mime, A::AbstractDimArray; maxlen, kw...) +function show_after(io::IO, mime, A::AbstractBasicDimArray; maxlen, kw...) print_block_close(io, maxlen) ndims(A) > 0 && println(io) print_array(io, mime, A) end - function print_ndims(io, size::Tuple; colors=map(dimcolors, ntuple(identity, length(size))) ) @@ -170,28 +169,28 @@ end # Showing the array is optional for AbstractDimArray # `print_array` must be called from `show_after`. -function print_array(io::IO, mime, A::AbstractDimArray{T,0}) where T +function print_array(io::IO, mime, A::AbstractBasicDimArray{T,0}) where T print(_print_array_ctx(io, T), "\n", A[]) end -function print_array(io::IO, mime, A::AbstractDimArray{T,1}) where T +function print_array(io::IO, mime, A::AbstractBasicDimArray{T,1}) where T Base.print_matrix(_print_array_ctx(io, T), A) end -function print_array(io::IO, mime, A::AbstractDimArray{T,2}) where T +function print_array(io::IO, mime, A::AbstractBasicDimArray{T,2}) where T Base.print_matrix(_print_array_ctx(io, T), A) end -function print_array(io::IO, mime, A::AbstractDimArray{T,3}) where T +function print_array(io::IO, mime, A::AbstractBasicDimArray{T,3}) where T i3 = firstindex(A, 3) - frame = view(parent(A), :, :, i3) + frame = view(A, :, :, i3) _print_indices_vec(io, i3) - _print_matrix(_print_array_ctx(io, T), frame, lookup(A, (1, 2))) + Base.print_matrix(_print_array_ctx(io, T), frame) end -function print_array(io::IO, mime, A::AbstractDimArray{T,N}) where {T,N} +function print_array(io::IO, mime, A::AbstractBasicDimArray{T,N}) where {T,N} o = ntuple(x -> firstindex(A, x + 2), N-2) frame = view(A, :, :, o...) _print_indices_vec(io, o...) - _print_matrix(_print_array_ctx(io, T), frame, lookup(A, (1, 2))) + Base.print_matrix(_print_array_ctx(io, T), frame) end function _print_indices_vec(io, o...) @@ -216,7 +215,7 @@ function print_name(io::IO, name) end end -Base.print_matrix(io::IO, A::AbstractDimArray) = _print_matrix(io, parent(A), lookup(A)) +Base.print_matrix(io::IO, A::AbstractBasicDimArray) = _print_matrix(io, parent(A), lookup(A)) # Labelled matrix printing is modified from AxisKeys.jl, thanks @mcabbot function _print_matrix(io::IO, A::AbstractArray{<:Any,1}, lookups::Tuple) f1, l1, s1 = firstindex(A, 1), lastindex(A, 1), size(A, 1) @@ -242,8 +241,7 @@ function _print_matrix(io::IO, A::AbstractArray{<:Any,1}, lookups::Tuple) end return nothing end -_print_matrix(io::IO, A::AbstractDimArray, lookups::Tuple) = _print_matrix(io, parent(A), lookups) -function _print_matrix(io::IO, A::AbstractArray, lookups::Tuple) +function _print_matrix(io::IO, A::AbstractArray{<:Any,2}, lookups::Tuple) lu1, lu2 = lookups f1, f2 = firstindex(lu1), firstindex(lu2) l1, l2 = lastindex(lu1), lastindex(lu2) diff --git a/src/dimindices.jl b/src/dimindices.jl index d343a308f..98c2239d4 100644 --- a/src/dimindices.jl +++ b/src/dimindices.jl @@ -1,31 +1,46 @@ -abstract type AbstractDimIndices{T,N} <: AbstractArray{T,N} end +abstract type AbstractDimArrayGenerator{T,N,D} <: AbstractBasicDimArray{T,N,D} end -dims(di::AbstractDimIndices) = di.dims +dims(dg::AbstractDimArrayGenerator) = dg.dims -Base.size(di::AbstractDimIndices) = map(length, dims(di)) -Base.axes(di::AbstractDimIndices) = map(d -> axes(d, 1), dims(di)) +Base.size(dg::AbstractDimArrayGenerator) = map(length, dims(dg)) +Base.axes(dg::AbstractDimArrayGenerator) = map(d -> axes(d, 1), dims(dg)) -for f in (:getindex, :view, :dotview) - @eval begin - @propagate_inbounds Base.$f(A::AbstractDimIndices, i1::Selector, I::Selector...) = - Base.$f(A, dims2indices(A, i1, I)...) - @propagate_inbounds function Base.$f(A::AbstractDimIndices, i1::Dimension, I::Dimension...; kw...) - Base.$f(A, dims2indices(A, i1, I..., kwdims(values(kw))...)...) - end +# Indexing that returns a new object with the same number of dims +for f in (:getindex, :dotview, :view) + T = Union{Colon,AbstractRange} + @eval @propagate_inbounds function Base.$f(di::AbstractDimArrayGenerator, i1::$T, i2::$T, Is::$T...) + I = (i1, i2, Is...) + newdims, _ = slicedims(dims(di), I) + rebuild(di; dims=newdims) + end + @eval @propagate_inbounds function Base.$f(di::AbstractDimArrayGenerator{<:Any,1}, i::$T) + rebuild(di; dims=dims(di, 1)[i]) end + @eval @propagate_inbounds Base.$f(dg::AbstractDimArrayGenerator, i::Int) = + Base.$f(dg, Tuple(CartesianIndices(dg)[i])...) +end + +@inline Base.permutedims(A::AbstractDimArrayGenerator{<:Any,2}) = + rebuild(A; dims=reverse(dims(A))) +@inline Base.permutedims(A::AbstractDimArrayGenerator{<:Any,1}) = + rebuild(A; dims=(AnonDim(Base.OneTo(1)), dims(A)...)) +@inline function Base.permutedims(A::AbstractDimArrayGenerator, perm) + length(perm) == length(dims(A) || throw(ArgumentError("permutation must be same length as dims"))) + rebuild(A; dim=sortdims(dims(A), Tuple(perm))) +end + +@inline function Base.PermutedDimsArray(A::AbstractDimArrayGenerator{T,N}, perm) where {T,N} + perm_inds = dimnum(A, Tuple(perm)) + rebuild(A; dims=dims(dims(A), Tuple(perm))) end +abstract type AbstractDimIndices{T,N,D} <: AbstractDimArrayGenerator{T,N,D} end + (::Type{T})(::Nothing; kw...) where T<:AbstractDimIndices = throw(ArgumentError("Object has no `dims` method")) (::Type{T})(x; kw...) where T<:AbstractDimIndices = T(dims(x); kw...) (::Type{T})(dim::Dimension; kw...) where T<:AbstractDimIndices = T((dim,); kw...) -_format(dims::Tuple{}) = () -function _format(dims::Tuple) - ax = map(d -> axes(val(d), 1), dims) - return format(dims, ax) -end - """ DimIndices <: AbstractArray @@ -39,8 +54,55 @@ of `Dimension(i)` for all combinations of the axis indices of `dims`. This can be used to view/index into arbitrary dimensions over an array, and is especially useful when combined with `otherdims`, to iterate over the indices of unknown dimension. + +`DimIndices` can be used directly in `getindex` like `CartesianIndices`, +and freely mixed with individual `Dimension`s or tuples of `Dimension`. + +## Example + +Index a `DimArray` with `DimIndices`. + +Notice that unlike CartesianIndices, it doesn't matter if +the dimensions are not in the same order. Or even if they +are not all contained in each. + +```julia +julia> A = rand(Y(0.0:0.3:1.0), X('a':'f')) +╭─────────────────────────╮ +│ 4×6 DimArray{Float64,2} │ +├─────────────────────────┴─────────────────────────────────── dims ┐ + ↓ Y Sampled{Float64} 0.0:0.3:0.9 ForwardOrdered Regular Points, + → X Categorical{Char} 'a':1:'f' ForwardOrdered +└───────────────────────────────────────────────────────────────────┘ + ↓ → 'a' 'b' 'c' 'd' 'e' 'f' + 0.0 0.513225 0.377972 0.771862 0.666855 0.837314 0.274402 + 0.3 0.13363 0.519241 0.937604 0.288436 0.437421 0.745771 + 0.6 0.837621 0.0987936 0.441426 0.88518 0.551162 0.728571 + 0.9 0.399042 0.750191 0.56436 0.47882 0.54036 0.113656 + +julia> di = DimIndices((X(1:2:4), Y(1:2:4))) +╭──────────────────────────────────────────────╮ +│ 2×2 DimIndices{Tuple{X{Int64}, Y{Int64}},2} │ +├──────────────────────────────────────────────┴── dims ┐ + ↓ X 1:2:3, + → Y 1:2:3 +└───────────────────────────────────────────────────────┘ + ↓ X 1, → Y 1 ↓ X 1, → Y 3 + ↓ X 3, → Y 1 ↓ X 3, → Y 3 + +julia> A[di] # Index A with these indices +dims(d) = (X{StepRange{Int64, Int64}}(1:2:3), Y{StepRange{Int64, Int64}}(1:2:3)) +╭─────────────────────────╮ +│ 2×2 DimArray{Float64,2} │ +├─────────────────────────┴─────────────────────────────────── dims ┐ + ↓ Y Sampled{Float64} 0.0:0.6:0.6 ForwardOrdered Regular Points, + → X Categorical{Char} 'a':2:'c' ForwardOrdered +└───────────────────────────────────────────────────────────────────┘ + ↓ → 'a' 'c' + 0.0 0.513225 0.771862 + 0.6 0.837621 0.441426 """ -struct DimIndices{T,N,D<:Tuple{Vararg{Dimension}}} <: AbstractDimIndices{T,N} +struct DimIndices{T,N,D<:Tuple{Vararg{Dimension}}} <: AbstractDimIndices{T,N,D} dims::D # Manual inner constructor for ambiguity only function DimIndices{T,N,D}(dims::Tuple{Vararg{Dimension}}) where {T,N,D<:Tuple{Vararg{Dimension}}} @@ -50,26 +112,34 @@ end function DimIndices(dims::D) where {D<:Tuple{Vararg{Dimension}}} T = typeof(map(d -> rebuild(d, 1), dims)) N = length(dims) - dims = N > 0 ? _format(dims) : dims + dims = N > 0 ? _dimindices_format(dims) : dims DimIndices{T,N,typeof(dims)}(dims) end +# Forces multiple indices not linear function Base.getindex(di::DimIndices, i1::Int, i2::Int, I::Int...) map(dims(di), (i1, i2, I...)) do d, i - rebuild(d, axes(d, 1)[i]) + rebuild(d, d[i]) end end +# Dispatch to avoid linear indexing in multidimensionsl DimIndices function Base.getindex(di::DimIndices{<:Any,1}, i::Int) d = dims(di, 1) - (rebuild(d, axes(d, 1)[i]),) -end -function Base.getindex(di::DimIndices{<:Any,N}, i::Int) where N - I = Tuple(CartesianIndices(di)[i]) - map(dims(di), I) do d, i - rebuild(d, axes(d, 1)[i]) - end + (rebuild(d, d[i]),) end +_dimindices_format(dims::Tuple{}) = () +_dimindices_format(dims::Tuple) = map(rebuild, dims, map(_dimindices_axis, dims)) + +# Allow only CartesianIndices arguments +_dimindices_axis(x::Integer) = Base.OneTo(x) +_dimindices_axis(x::AbstractRange{<:Integer}) = x +# And LookupArray, which we take the axes from +_dimindices_axis(x::Dimension) = _dimindices_axis(val(x)) +_dimindices_axis(x::LookupArray) = axes(x, 1) +_dimindices_axis(x) = + throw(ArgumentError("`$x` is not a valid input for `DimIndices`. Use `Dimension`s wrapping `Integer`, `AbstractArange{<:Integer}`, or a `LookupArray` (the `axes` will be used)")) + """ DimPoints <: AbstractArray @@ -78,22 +148,23 @@ end DimPoints(dims::Tuple; order) DimPoints(dims::Dimension; order) -Like `CartesianIndices`, but for the point values of the dimension index. +Like `CartesianIndices`, but for the point values of the dimension index. Behaves as an `Array` of `Tuple` lookup values (whatever they are) for all combinations of the lookup values of `dims`. -Either a `Dimension`, a `Tuple` of `Dimension` or an object that defines a -`dims` method can be passed in. +Either a `Dimension`, a `Tuple` of `Dimension` or an object `x` +that defines a `dims` method can be passed in. # Keywords - `order`: determines the order of the points, the same as the order of `dims` by default. """ -struct DimPoints{T,N,D<:DimTuple,O} <: AbstractDimIndices{T,N} +struct DimPoints{T,N,D<:DimTuple,O} <: AbstractDimIndices{T,N,D} dims::D order::O end -function DimPoints(dims::DimTuple; order=dims) +DimPoints(dims::DimTuple; order=dims) = DimPoints(dims, order) +function DimPoints(dims::DimTuple, order::DimTuple) order = map(d -> basetypeof(d)(), order) T = Tuple{map(eltype, dims)...} N = length(dims) @@ -110,87 +181,238 @@ function Base.getindex(dp::DimPoints, i1::Int, i2::Int, I::Int...) return map(val, DD.dims(pointdims, dp.order)) end Base.getindex(di::DimPoints{<:Any,1}, i::Int) = (dims(di, 1)[i],) -Base.getindex(di::DimPoints, i::Int) = di[Tuple(CartesianIndices(di)[i])...] -struct DimViews{T,N,D<:DimTuple,A} <: AbstractDimIndices{T,N} - data::A - dims::D - function DimViews(data::A, dims::D) where {A,D<:DimTuple} - T = typeof(view(data, map(rebuild, dims, map(first, dims))...)) - N = length(dims) - new{T,N,D,A}(data, dims) - end -end - -function Base.getindex(dv::DimViews, i1::Int, i2::Int, I::Int...) - # Get dim-wrapped point values at i1, I... - D = map(dims(dv), (i1, i2, I...)) do d, i - rebuild(d, d[i]) - end - # Return the unwrapped point sorted by `order - return view(dv.data, D...) -end -function Base.getindex(dv::DimViews{<:Any,1}, i::Int) - d = dims(dv, 1) - return view(dv.data, rebuild(d, d[i])) +_format(::Tuple{}) = () +function _format(dims::Tuple) + ax = map(d -> axes(val(d), 1), dims) + return format(dims, ax) end -Base.getindex(dv::DimViews, i::Int) = dv[Tuple(CartesianIndices(dv)[i])...] """ - DimKeys <: AbstractArray + DimSelectors <: AbstractArray + + DimSelectors(x; selectors, atol...) + DimSelectors(dims::Tuple; selectors, atol...) + DimSelectors(dims::Dimension; selectors, atol...) + +Like [`DimIndices`](@ref), but returns `Dimensions` holding +the chosen [`Selector`](@ref)s. + +Indexing into another `AbstractDimArray` with `DimSelectors` +is similar to doing an interpolation. + +## Keywords - DimKeys(x) - DimKeys(dims::Tuple) - DimKeys(dims::Dimension) +- `selectors`: `Near`, `At` or `Contains`, or a mixed tuple of these. + `At` is the default, meaning only exact or within `atol` values are used. +- `atol`: used for `At` selectors only, as the `atol` value. -Like `CartesianIndices`, but for the lookup values of Dimensions. Behaves as an -`Array` of `Tuple` of `Dimension(At(lookupvalue))` for all combinations of the -lookup values of `dims`. +## Example + +Here we can interpolate a `DimArray` to the lookups of another `DimArray` +using `DimSelectors` with `Near`. This is essentially equivalent to +nearest neighbour interpolation. + +```julia +julia> A = rand(X(1.0:3.0:30.0), Y(1.0:5.0:30.0), Ti(1:2)); + +julia> target = rand(X(1.0:10.0:30.0), Y(1.0:10.0:30.0)); + +julia> A[DimSelectors(target; selectors=Near), Ti=2] +╭───────────────────────────╮ +│ 3×3×2 DimArray{Float64,3} │ +├───────────────────────────┴──────────────────────────────────────── dims ┐ + ↓ X Sampled{Float64} [1.0, 10.0, 22.0] ForwardOrdered Irregular Points, + → Y Sampled{Float64} [1.0, 11.0, 21.0] ForwardOrdered Irregular Points, +└──────────────────────────────────────────────────────────────────────────┘ + ↓ → 1.0 11.0 21.0 + 1.0 0.473548 0.773863 0.541381 + 10.0 0.951457 0.176647 0.968292 + 22.0 0.822979 0.980585 0.544853 +``` + +Using `At` would make sure we only use exact interpolation, +while `Contains` with sampleing of `Intervals` would make sure that +each values is taken only from an Interval that is present in the lookups. """ -struct DimKeys{T,N,D<:Tuple{Dimension,Vararg{Dimension}},S} <: AbstractDimIndices{T,N} +struct DimSelectors{T,N,D<:Tuple{Dimension,Vararg{Dimension}},S<:Tuple} <: AbstractDimIndices{T,N,D} dims::D selectors::S end -function DimKeys(dims::DimTuple; atol=nothing, selectors=_selectors(dims, atol)) - DimKeys(dims, selectors) +function DimSelectors(dims::DimTuple; atol=nothing, selectors=At()) + s = _format_selectors(dims, selectors, atol) + DimSelectors(dims, s) end -function DimKeys(dims::DimTuple, selectors) +function DimSelectors(dims::DimTuple, selectors::Tuple) T = typeof(map(rebuild, dims, selectors)) N = length(dims) dims = N > 0 ? _format(dims) : dims - DimKeys{T,N,typeof(dims),typeof(selectors)}(dims, selectors) + DimSelectors{T,N,typeof(dims),typeof(selectors)}(dims, selectors) end -function _selectors(dims, atol) - map(dims) do d - atol1 = _atol(eltype(d), atol) - At{eltype(d),typeof(atol1),Nothing}(first(d), atol1, nothing) - end -end -function _selectors(dims, atol::Tuple) - map(dims, atol) do d, a - atol1 = _atol(eltype(d), a) - At{eltype(d),typeof(atol1),Nothing}(first(d), atol1, nothing) - end -end -function _selectors(dims, atol::Nothing) - map(dims) do d - atolx = _atol(eltype(d), nothing) - v = first(val(d)) - At{typeof(v),typeof(atolx),Nothing}(v, atolx, nothing) - end +@inline _format_selectors(dims::Tuple, selector, atol) = + _format_selectors(dims, map(_ -> selector, dims), atol) +@inline _format_selectors(dims::Tuple, selectors::Tuple, atol) = + _format_selectors(dims, selectors, map(_ -> atol, dims)) +@inline _format_selectors(dims::Tuple, selectors::Tuple, atol::Tuple) = + map(_format_selectors, dims, selectors, atol) + +_format_selectors(d::Dimension, T::Type, atol) = _format_selectors(d, T(), atol) +@inline _format_selectors(d::Dimension, ::Near, atol) = + Near(zero(eltype(d))) +@inline _format_selectors(d::Dimension, ::Contains, atol) = + Contains(zero(eltype(d))) +@inline function _format_selectors(d::Dimension, ::At, atol) + atolx = _atol(eltype(d), atol) + v = first(val(d)) + At{typeof(v),typeof(atolx),Nothing}(v, atolx, nothing) end _atol(::Type, atol) = atol _atol(T::Type{<:AbstractFloat}, atol::Nothing) = eps(T) -function Base.getindex(di::DimKeys, i1::Int, i2::Int, I::Int...) +@propagate_inbounds function Base.getindex(di::DimSelectors, i1::Int, i2::Int, I::Int...) map(dims(di), di.selectors, (i1, i2, I...)) do d, s, i rebuild(d, rebuild(s; val=d[i])) # At selector with the value at i end end -function Base.getindex(di::DimKeys{<:Any,1}, i::Int) +@propagate_inbounds function Base.getindex(di::DimSelectors{<:Any,1}, i::Int) d = dims(di, 1) (rebuild(d, rebuild(di.selectors[1]; val=d[i])),) end -Base.getindex(di::DimKeys, i::Int) = di[Tuple(CartesianIndices(di)[i])...] + +# Depricated +const DimKeys = DimSelectors + +struct DimSlices{T,N,D<:Tuple{Vararg{Dimension}},P} <: AbstractDimArrayGenerator{T,N,D} + _data::P + dims::D +end +DimSlices(x; dims, drop=true) = DimSlices(x, dims; drop) +function DimSlices(x, dims; drop=true) + newdims = length(dims) == 0 ? map(d -> rebuild(d, :), DD.dims(x)) : dims + inds = map(d -> rebuild(d, first(d)), newdims) + T = typeof(view(x, inds...)) + N = length(newdims) + D = typeof(newdims) + return DimSlices{T,N,D,typeof(x)}(x, newdims) +end + +function Base.summary(io::IO, A::DimSlices{T,N}) where {T,N} + print_ndims(io, size(A)) + print(io, string(nameof(typeof(A)), "{$(nameof(T)),$N}")) +end + +@propagate_inbounds function Base.getindex(ds::DimSlices, i1::Int, i2::Int, Is::Int...) + I = (i1, i2, Is...) + @boundscheck checkbounds(ds, I...) + D = map(dims(ds), I) do d, i + rebuild(d, d[i]) + end + return view(ds._data, D...) +end +# Dispatch to avoid linear indexing in multidimensionsl DimIndices +@propagate_inbounds function Base.getindex(ds::DimSlices{<:Any,1}, i::Int) + d = dims(ds, 1) + return view(ds._data, rebuild(d, d[i])) +end + +# function Base.show(io::IO, mime, A::DimSlices) +# parentdims = otherdims(A.data, dims(A)) +# _, width = displaysize(io) +# sorteddims = (dims(A)..., otherdims(parentdims, dims(A))...) +# colordims = dims(map(rebuild, sorteddims, ntuple(dimcolors, Val(length(sorteddims)))), dims(first(A))) +# colors = collect(map(val, colordims)) +# print_dims_block(io, mime, basedims(first(A)); width, maxlen, label="group dims", colors) +# length(A) > 0 || return nothing +# A1 = map(x -> DimSummariser(x, colors), A) +# show_after(io, mime, A1; maxlen) +# map(x -> DimSummariser(x, colors), A) +# Base.printmatrix(io, A) +# end + +# Extends the dimensions of any `AbstractBasicDimArray` +# as if the array assigned into a larger array accross all dimensions, +# but without the copying. Theres is a cost for linear indexing these objects +# as we need to convert to cartesian. +struct DimExtensionArray{T,N,D<:Tuple{Vararg{Dimension}},R<:Tuple{Vararg{Dimension}},A<:AbstractBasicDimArray{T}} <: AbstractDimArrayGenerator{T,N,D} + _data::A + dims::D + refdims::R + function DimExtensionArray(A::AbstractBasicDimArray{T}, dims::Tuple, refdims::Tuple) where T + all(hasdim(dims, DD.dims(A))) || throw(ArgumentError("all dim in array must also be in `dims`")) + comparedims(A, DD.dims(dims, DD.dims(A))) + fdims = format(dims, CartesianIndices(map(length, dims))) + N = length(dims) + new{T,N,typeof(fdims),typeof(refdims),typeof(A)}(A, fdims, refdims) + end +end +DimExtensionArray(A::AbstractBasicDimArray, dims::Tuple; refdims=refdims(A)) = + DimExtensionArray(A, dims, refdims) + +name(A::DimExtensionArray) = name(A._data) +metadata(A::DimExtensionArray) = metadata(A._data) + +# Indexing that returns a new object with the same number of dims +for f in (:getindex, :dotview, :view) + __f = Symbol(:__, f) + T = Union{Colon,AbstractRange} + # For ambiguity + @eval @propagate_inbounds function Base.$f(de::DimExtensionArray, i1::$T, i2::$T, Is::$T...) + $__f(de, i1, i2, Is...) + end + @eval @propagate_inbounds function Base.$f(de::DimExtensionArray, i1::StandardIndices, i2::StandardIndices, Is::StandardIndices...) + $__f(de, i1, i2, Is...) + end + @eval @propagate_inbounds function Base.$f( + de::DimensionalData.DimExtensionArray, + i1::Union{AbstractArray{Union{}}, DimensionalData.DimIndices{<:Integer}, DimensionalData.DimSelectors{<:Integer}}, + i2::Union{AbstractArray{Union{}}, DimensionalData.DimIndices{<:Integer}, DimensionalData.DimSelectors{<:Integer}}, + Is::Vararg{Union{AbstractArray{Union{}}, DimensionalData.DimIndices{<:Integer}, DimensionalData.DimSelectors{<:Integer}}} + ) + $__f(de, i1, i2, Is...) + end + @eval Base.@assume_effects :foldable @propagate_inbounds function $__f(de::DimExtensionArray, i1, i2, Is...) + I = (i1, i2, Is...) + newdims, newrefdims = slicedims(dims(de), refdims(de), I) + D = map(rebuild, dims(de), I) + A = de._data + realdims = dims(D, dims(A)) + if all(map(d -> val(d) isa Colon, realdims)) + rebuild(de; dims=newdims, refdims=newrefdims) + else + newrealparent = begin + x = parent(A)[dims2indices(A, realdims)...] + x isa AbstractArray ? x : fill(x) + end + newrealdims = dims(newdims, realdims) + newdata = rebuild(A; data=newrealparent, dims=newrealdims) + rebuild(de; _data=newdata, dims=newdims, refdims=newrefdims) + end + end + @eval @propagate_inbounds function $__f(de::DimExtensionArray{<:Any,1}, i::$T) + newdims, _ = slicedims(dims(de), (i,)) + A = de._data + D = rebuild(only(dims(de)), i) + rebuild(de; dims=newdims, _data=A[D...]) + end +end +for f in (:getindex, :dotview) + __f = Symbol(:__, f) + @eval function $__f(de::DimExtensionArray, i1::Int, i2::Int, Is::Int...) + D = map(rebuild, dims(de), (i1, i2, Is...)) + A = de._data + return $f(A, dims(D, dims(A))...) + end + @eval $__f(de::DimExtensionArray{<:Any,1}, i::Int) = $f(de._data, rebuild(dims(de, 1), i)) +end + +function mergedims(A::DimExtensionArray, dim_pairs::Pair...) + all_dims = dims(A) + dims_new = mergedims(all_dims, dim_pairs...) + dimsmatch(all_dims, dims_new) && return A + dims_perm = _unmergedims(dims_new, map(last, dim_pairs)) + Aperm = PermutedDimsArray(A, dims_perm) + data_merged = reshape(parent(Aperm), map(length, dims_new)) + return DimArray(data_merged, dims_new) +end diff --git a/src/groupby.jl b/src/groupby.jl index dc66d2005..074453ee2 100644 --- a/src/groupby.jl +++ b/src/groupby.jl @@ -110,7 +110,8 @@ end Bins(bins; labels=nothing, pad=0.001) = Bins(identity, bins, labels, pad) Bins(f, bins; labels=nothing, pad=0.001) = Bins(f, bins, labels, pad) -Base.show(io::IO, bins::Bins) = println(io, nameof(typeof(bins)), "(", bins.f, ", ", bins.bins, ")") +Base.show(io::IO, bins::Bins) = + println(io, nameof(typeof(bins)), "(", bins.f, ", ", bins.bins, ")") abstract type AbstractCyclicBins end struct CyclicBins{F,C,Sta,Ste,L} <: AbstractBins @@ -123,7 +124,7 @@ end CyclicBins(f; cycle, step, start=1, labels=nothing) = CyclicBins(f, cycle, start, step, labels) Base.show(io::IO, bins::CyclicBins) = -println(io, nameof(typeof(bins)), "(", bins.f, "; ", join(map(k -> "$k=$(getproperty(bins, k))", (:cycle, :step, :start)), ", "), ")") + println(io, nameof(typeof(bins)), "(", bins.f, "; ", join(map(k -> "$k=$(getproperty(bins, k))", (:cycle, :step, :start)), ", "), ")") yearhour(x) = year(x), hour(x) @@ -267,7 +268,7 @@ function DataAPI.groupby(A::DimArrayOrStack, dimfuncs::DimTuple) group_dims = map(first, dim_groups_indices) indices = map(rebuild, dimfuncs, map(last, dim_groups_indices)) - views = DimViews(A, indices) + views = DimSlices(A, indices) # Put the groupby query in metadata meta = map(d -> dim2key(d) => val(d), dimfuncs) metadata = Dict{Symbol,Any}(:groupby => length(meta) == 1 ? only(meta) : meta) @@ -294,8 +295,8 @@ function _group_indices(dim::Dimension, group_lookup::LookupArray; labels=nothin orig_lookup = lookup(dim) indices = map(_ -> Int[], 1:length(group_lookup)) for (i, v) in enumerate(orig_lookup) - n = selectindices(group_lookup, Contains(v)) - push!(indices[n], i) + n = selectindices(group_lookup, Contains(v); err=LookupArrays._False()) + isnothing(n) || push!(indices[n], i) end group_dim = if isnothing(labels) rebuild(dim, group_lookup) diff --git a/src/interface.jl b/src/interface.jl index 730eb5d39..e65ec6442 100644 --- a/src/interface.jl +++ b/src/interface.jl @@ -1,7 +1,6 @@ # `rebuild` and `dims` are key methods to add for a new type """ - rebuild(x, args...) rebuild(x; kw...) Rebuild an object struct with updated field values. @@ -11,15 +10,39 @@ Rebuild an object struct with updated field values. This is an abstraction that alows inbuilt and custom types to be rebuilt to update their fields, as most objects in DimensionalData.jl are immutable. -The arguments version can be concise but depends on a fixed order defined for some -DimensionalData objects. It should be defined based on the object type in DimensionalData, -adding the fields specific to your object. - -The keyword version ignores order, and is mostly automated -using `ConstructionBase.setproperties`. It should only be defined if your object has -missing fields or fields with different names to DimensionalData objects. +Rebuild is mostly automated using `ConstructionBase.setproperties`. +It should only be defined if your object has fields with +with different names to DimensionalData objects. Try not to do that! The arguments required are defined for the abstract type that has a `rebuild` method. + +#### `AbstractBasicDimArray`: +- `dims`: a `Tuple` of `Dimension` + +#### `AbstractDimArray`: + +- `data`: the parent object - an `AbstractArray` +- `dims`: a `Tuple` of `Dimension` +- `refdims`: a `Tuple` of `Dimension` +- `name`: A Symbol, or `NoName` and `Name` on GPU. +- `metadata`: A `Dict`-like object + +#### `AbstractDimStack`: + +- `data`: the parent object, often a `NamedTuple` +- `dims`, `refdims`, `metadata` + +#### `Dimension`: + +- `val`: anything. + +#### `LookupArray`: + +- `data`: the parent object, an `AbstractArray` + +* Note: argument `rebuild` is deprecated on `AbstractDimArray` and +`AbstractDimStack` in favour of allways using the keyword version. +In future the argument version will only be used on `Dimension`, which only have one argument. """ function rebuild end diff --git a/src/stack/indexing.jl b/src/stack/indexing.jl index b7df97814..b707943f0 100644 --- a/src/stack/indexing.jl +++ b/src/stack/indexing.jl @@ -4,30 +4,48 @@ # Symbol key for f in (:getindex, :view, :dotview) - @eval @propagate_inbounds Base.$f(s::AbstractDimStack, key::Symbol) = + @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)) end @propagate_inbounds function Base.getindex(s::AbstractDimStack, keys::Tuple) rebuild_from_arrays(s, NamedTuple{keys}(map(k -> s[k], keys))) end -# Array-like indexing for f in (:getindex, :view, :dotview) + _f = Symbol(:_, f) @eval begin - @propagate_inbounds function Base.$f( - s::AbstractDimStack, i1::Union{Integer,CartesianIndex}, Is::Union{Integer,CartesianIndex}... - ) - # Convert to Dimension wrappers to handle mixed size layers - Base.$f(s, DimIndices(s)[i1, Is...]...) + @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) + map(l -> Base.$f(l, i), s) + else + Base.$f(s, DimIndices(s)[i]) + end end - @propagate_inbounds function Base.$f(s::AbstractDimStack, i1, Is...) - I = (i1, Is...) + @propagate_inbounds function Base.$f(s::AbstractDimStack, i::Union{CartesianIndices,CartesianIndex}) + I = to_indices(CartesianIndices(s), (i,)) + Base.$f(s, I...) + end + @propagate_inbounds function Base.$f(s::AbstractDimStack, i::Union{AbstractArray,Colon}) + if length(dims(s)) > 1 + Base.$f(s, view(DimIndices(s), i)) + elseif length(dims(s)) == 1 + Base.$f(s, rebuild(only(dims(s)), i)) + else + checkbounds(s, i) + end + end + @propagate_inbounds function Base.$f(s::AbstractDimStack, i1::SelectorOrStandard, i2, Is::SelectorOrStandard...) + I = to_indices(CartesianIndices(s), (i1, i2, Is...)) + # Check we have the right number of dimensions if length(dims(s)) > length(I) throw(BoundsError(dims(s), I)) elseif length(dims(s)) < length(I) # Allow trailing ones - if all(i -> i isa Integer && i == 1, I[length(dims(s)):end]) - I = I[1:length(dims)] + if all(i -> i isa Integer && i == 1, I[length(dims(s))+1:end]) + I = I[1:length(dims(s))] else throw(BoundsError(dims(s), I)) end @@ -35,49 +53,125 @@ for f in (:getindex, :view, :dotview) # Convert to Dimension wrappers to handle mixed size layers Base.$f(s, map(rebuild, dims(s), I)...) end - @propagate_inbounds function Base.$f(s::AbstractDimStack, i::AbstractArray) - # Multidimensional: return vectors of values - if length(dims(s)) > 1 - Ds = DimIndices(s)[i] - map(s) do A - map(D -> A[D...], Ds) - end - else - map(A -> A[i], s) - end + @propagate_inbounds function Base.$f( + s::AbstractDimStack, D::DimensionalIndices...; kw... + ) + $_f(s, _simplify_dim_indices(D..., kwdims(values(kw))...)...) + end + # Ambiguities + @propagate_inbounds function Base.$f( + ::AbstractDimStack, + ::_DimIndicesAmb, + ::Union{Tuple{Dimension,Vararg{Dimension}},AbstractArray{<:Dimension},AbstractArray{<:Tuple{Dimension,Vararg{Dimension}}},DimIndices,DimSelectors,Dimension}, + ::_DimIndicesAmb... + ) + $_f(s, _simplify_dim_indices(D..., kwdims(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...)) end - @propagate_inbounds function Base.$f(s::AbstractDimStack, D::Dimension...; kw...) - alldims = (D..., kwdims(values(kw))...) - extradims = otherdims(alldims, dims(s)) + @propagate_inbounds function Base.$f( + s::AbstractDimStack, + D::Union{AbstractArray{Union{}},DimIndices{<:Integer},DimSelectors{<:Integer}} + ) + $_f(s, _simplify_dim_indices(D...)) + end + + + @propagate_inbounds function $_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) + map(Base.$f, s) + end + @propagate_inbounds function $_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 - layerdims = dims(alldims, dims(A)) + layerdims = dims(D, dims(A)) I = length(layerdims) > 0 ? layerdims : map(_ -> :, size(A)) Base.$f(A, I...) end + # Dicide to rewrap as an AbstractDimStack, or return a scalar if all(map(v -> v isa AbstractDimArray, newlayers)) - rebuildsliced(Base.$f, s, newlayers, (dims2indices(dims(s), alldims))) + # All arrays, wrap + rebuildsliced(Base.$f, s, newlayers, (dims2indices(dims(s), D))) + elseif 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), ()) + end + rebuildsliced(Base.$f, s, non_scalar_layers, (dims2indices(dims(s), D))) else + # All scalars, return as-is newlayers end end - @propagate_inbounds function Base.$f(s::AbstractDimStack) - map($f, s) - end end end + #### setindex #### @propagate_inbounds Base.setindex!(s::AbstractDimStack, xs, I...; kw...) = map((A, x) -> setindex!(A, x, I...; kw...), layers(s), xs) +@propagate_inbounds Base.setindex!(s::AbstractDimStack, xs::NamedTuple, i::Integer; kw...) = + hassamedims(s) ? _map_setindex!(s, xs, i) : _setindex_mixed(s, xs, i) +@propagate_inbounds Base.setindex!(s::AbstractDimStack, xs::NamedTuple, i::Colon; kw...) = + hassamedims(s) ? _map_setindex!(s, xs, i) : _setindex_mixed(s, xs, i) +@propagate_inbounds Base.setindex!(s::AbstractDimStack, xs::NamedTuple, i::AbstractArray; kw...) = + hassamedims(s) ? _map_setindex!(s, xs, i) : _setindex_mixed(s, xs, i) + @propagate_inbounds function Base.setindex!( - s::AbstractDimStack{<:NamedTuple{K1}}, xs::NamedTuple{K2}, I...; kw... -) where {K1,K2} - K1 == K2 || _keysmismatch(K1, K2) + s::AbstractDimStack, xs::NamedTuple, I...; kw... +) map((A, x) -> setindex!(A, x, I...; kw...), layers(s), xs) end +# For ambiguity +# @propagate_inbounds function Base.setindex!( +# s::AbstractDimStack, xs::NamedTuple, i::Integer +# ) +# setindex!(A, xs, DimIndices(s)[i]) +# end + +_map_setindex!(s, xs, i) = map((A, x) -> setindex!(A, x, i...; kw...), layers(s), xs) + +_setindex_mixed(s::AbstractDimStack, x, i::AbstractArray) = + map(A -> setindex!(A, x, DimIndices(dims(s))[i]), layers(s)) +_setindex_mixed(s::AbstractDimStack, i::Integer) = + map(A -> setindex!(A, x, DimIndices(dims(s))[i]), layers(s)) +function _setindex_mixed!(s::AbstractDimStack, x, i::Colon) + map(DimIndices(dims(s))) do D + map(A -> setindex!(A, D), x, layers(s)) + end +end @noinline _keysmismatch(K1, K2) = throw(ArgumentError("NamedTuple keys $K2 do not mach stack keys $K1")) # For @views macro to work with keywords Base.maybeview(A::AbstractDimStack, args...; kw...) = view(A, args...; kw...) + +function merge_and_index(f, s::AbstractDimStack, ds) + ds, inds_arrays = _separate_dims_arrays(_simplify_dim_indices(ds...)...) + # No arrays here, so abort (dispatch is tricky...) + length(inds_arrays) == 0 && return f(s, ds...) + inds = first(inds_arrays) + + V = length(ds) > 0 ? view(s, ds...) : s + if !(length(dims(first(inds))) == length(dims(V))) + throw(ArgumentError("When indexing an AbstractDimStack with an Array all dimensions must be used")) + end + mdim = only(mergedims(dims(V), dims(V))) + newlayers = map(layers(V)) do l + l1 = all(hasdim(l, dims(V))) ? l : DimExtension(l, dims(V)) + view(l1, inds) + end + return rebuild_from_arrays(s, newlayers) +end diff --git a/src/stack/methods.jl b/src/stack/methods.jl index 4b8ba7ddb..266076723 100644 --- a/src/stack/methods.jl +++ b/src/stack/methods.jl @@ -39,6 +39,13 @@ function Base.copy!(dst::AbstractDimStack, src::AbstractDimStack, keys=keys(dst) end end +function Base.copyto!( + dst::Array{<:DimStack,3}, dstI::CartesianIndices, + src::DimSlices{<:DimStack}, srcI::CartesianIndices +) + dst[dstI] = src[srcI] +end + """ Base.map(f, stacks::AbstractDimStack...) @@ -121,10 +128,23 @@ and 2 layers: :y Float64 dims: Y, Ti (3×5) ``` """ -function Base.eachslice(s::AbstractDimStack; dims) - dimtuple = _astuple(dims) - all(hasdim(s, dimtuple...)) || throw(DimensionMismatch("s doesn't have all dimensions $dims")) - _eachslice(s, dimtuple) +@static if VERSION < v"1.9-alpha1" + function Base.eachslice(s::AbstractDimStack; dims) + dimtuple = _astuple(dims) + all(hasdim(s, dimtuple)) || throw(DimensionMismatch("s doesn't have all dimensions $dims")) + _eachslice(s, dimtuple) + end +else + function Base.eachslice(s::AbstractDimStack; dims, drop=true) + dimtuple = _astuple(dims) + if !(dimtuple == ()) + all(hasdim(s, dimtuple)) || throw(DimensionMismatch("A doesn't have all dimensions $dims")) + end + axisdims = map(DD.dims(s, dimtuple)) do d + rebuild(d, axes(lookup(d), 1)) + end + DimSlices(s; dims=axisdims, drop) + end end """ diff --git a/src/stack/stack.jl b/src/stack/stack.jl index 76067d8d2..fbbf69363 100644 --- a/src/stack/stack.jl +++ b/src/stack/stack.jl @@ -38,12 +38,16 @@ layermetadata(s::AbstractDimStack) = getfield(s, :layermetadata) layermetadata(s::AbstractDimStack, key::Symbol) = layermetadata(s)[key] layers(nt::NamedTuple) = nt -function layers(s::AbstractDimStack{<:NamedTuple{Keys}}) where Keys +Base.@assume_effects :foldable function layers(s::AbstractDimStack{<:NamedTuple{Keys}}) where Keys NamedTuple{Keys}(map(K -> s[K], Keys)) end const DimArrayOrStack = Union{AbstractDimArray,AbstractDimStack} +Base.@assume_effects :foldable function hassamedims(s::AbstractDimStack) + all(map(==(first(layerdims(s))), layerdims(s))) +end + """ rebuild_from_arrays(s::AbstractDimStack, das::NamedTuple{<:Any,<:Tuple{Vararg{AbstractDimArray}}}; kw...) @@ -62,12 +66,12 @@ Keywords are simply the fields of the stack object: - `layermetadata` """ function rebuild_from_arrays( - s::AbstractDimStack{<:NamedTuple{Keys}}, das::Tuple{Vararg{AbstractDimArray}}; kw... + s::AbstractDimStack{<:NamedTuple{Keys}}, das::Tuple{Vararg{AbstractBasicDimArray}}; kw... ) where Keys rebuild_from_arrays(s, NamedTuple{Keys}(das), kw...) end function rebuild_from_arrays( - s::AbstractDimStack, das::NamedTuple{<:Any,<:Tuple{Vararg{AbstractDimArray}}}; + s::AbstractDimStack, das::NamedTuple{<:Any,<:Tuple{Vararg{AbstractBasicDimArray}}}; data=map(parent, das), refdims=refdims(s), metadata=DD.metadata(s), @@ -96,7 +100,7 @@ Base.last(s::AbstractDimStack) = s[last(keys(s))] # Only compare data and dim - metadata and refdims can be different Base.:(==)(s1::AbstractDimStack, s2::AbstractDimStack) = data(s1) == data(s2) && dims(s1) == dims(s2) && layerdims(s1) == layerdims(s2) -Base.getproperty(s::AbstractDimStack, x::Symbol) = s[x] +Base.@assume_effects :foldable Base.getproperty(s::AbstractDimStack, x::Symbol) = s[x] Base.length(s::AbstractDimStack) = length(keys(s)) Base.ndims(s::AbstractDimStack) = length(dims(s)) Base.size(s::AbstractDimStack) = map(length, dims(s)) @@ -109,6 +113,7 @@ Base.similar(s::AbstractDimStack, args...) = map(A -> similar(A, args...), s) Base.eltype(s::AbstractDimStack, args...) = NamedTuple{keys(s),Tuple{map(eltype, s)...}} Base.iterate(s::AbstractDimStack, args...) = iterate(layers(s), args...) Base.read(s::AbstractDimStack) = map(read, s) +Base.CartesianIndices(s::AbstractDimStack) = CartesianIndices(dims(s)) # `merge` for AbstractDimStack and NamedTuple. # One of the first three arguments must be an AbstractDimStack for dispatch to work. Base.merge(s::AbstractDimStack) = s @@ -124,7 +129,7 @@ end function Base.merge(x1::NamedTuple, x2::NamedTuple, x3::AbstractDimStack, xs::Union{AbstractDimStack,NamedTuple}...) merge(map(layers, (x1, x2, x3, xs...))...) end -function Base.setindex(s::AbstractDimStack, val::AbstractDimArray, key) +function Base.setindex(s::AbstractDimStack, val::AbstractBasicDimArray, key) rebuild_from_arrays(s, Base.setindex(layers(s), val, key)) end Base.NamedTuple(s::AbstractDimStack) = layers(s) @@ -160,10 +165,22 @@ for func in (:index, :lookup, :metadata, :sampling, :span, :bounds, :locus, :ord @eval ($func)(s::AbstractDimStack, args...) = ($func)(dims(s), args...) end -function mergedims(ds::AbstractDimStack, dim_pairs::Pair...) - isempty(dim_pairs) && return ds - vals = map(A -> mergedims(A, dim_pairs...), values(ds)) - rebuild_from_arrays(ds, vals) +function mergedims(st::AbstractDimStack, dim_pairs::Pair...) + dim_pairs = map(dim_pairs) do (as, b) + basedims(as) => b + end + isempty(dim_pairs) && return st + # Extend missing dimensions in all layers + extended_layers = map(layers(st)) do layer + if all(map((ds...) -> all(hasdim(layer, ds)), map(first, dim_pairs))) + layer + else + DimExtensionArray(layer, dims(st)) + end + end + + vals = map(A -> mergedims(A, dim_pairs...), extended_layers) + return rebuild_from_arrays(st, vals) end function unmergedims(s::AbstractDimStack, original_dims) @@ -255,9 +272,9 @@ struct DimStack{L,D<:Tuple,R<:Tuple,LD<:Union{NamedTuple,Nothing},M,LM<:NamedTup metadata::M layermetadata::LM end -DimStack(@nospecialize(das::AbstractDimArray...); kw...) = DimStack(collect(das); kw...) -DimStack(@nospecialize(das::Tuple{Vararg{AbstractDimArray}}); kw...) = DimStack(collect(das); kw...) -function DimStack(@nospecialize(das::AbstractArray{<:AbstractDimArray}); +DimStack(@nospecialize(das::AbstractBasicDimArray...); kw...) = DimStack(collect(das); kw...) +DimStack(@nospecialize(das::Tuple{Vararg{AbstractBasicDimArray}}); kw...) = DimStack(collect(das); kw...) +function DimStack(@nospecialize(das::AbstractArray{<:AbstractBasicDimArray}); metadata=NoMetadata(), refdims=(), ) keys_vec = uniquekeys(das) @@ -270,7 +287,7 @@ function DimStack(@nospecialize(das::AbstractArray{<:AbstractDimArray}); DimStack(data, dims, refdims, layerdims, metadata, layermetadata) end -function DimStack(das::NamedTuple{<:Any,<:Tuple{Vararg{AbstractDimArray}}}; +function DimStack(das::NamedTuple{<:Any,<:Tuple{Vararg{AbstractBasicDimArray}}}; data=map(parent, das), dims=combinedims(collect(das)), layerdims=map(basedims, das), refdims=(), metadata=NoMetadata(), layermetadata=map(DD.metadata, das) ) diff --git a/src/tables.jl b/src/tables.jl index c86c6460c..cc336fd00 100644 --- a/src/tables.jl +++ b/src/tables.jl @@ -33,101 +33,8 @@ function _colnames(s::AbstractDimStack) (dimkeys..., keys(s)...) end - -# DimColumn - - -""" - DimColumn{T,D<:Dimension} <: AbstractVector{T} - - DimColumn(dim::Dimension, dims::Tuple{Vararg{DimTuple}}) - DimColumn(dim::DimColumn, length::Int, dimstride::Int) - -A table column based on a `Dimension` and it's relationship with other -`Dimension`s in `dims`. - -`length` is the product of all dim lengths (usually the length of the corresponding -array data), while stride is the product of the preceding dimension lengths, which -may or may not be the real stride of the corresponding array depending on the data type. -For `A isa Array`, the `dimstride` will match the `stride`. - -When the second argument is a `Tuple` of `Dimension`, the `length` and `dimstride` -fields are calculated from the dimensions, relative to the column dimension `dim`. - - -This object will be returned as a column of [`DimTable`](@ref). -""" -struct DimColumn{T,D<:Dimension} <: AbstractVector{T} - dim::D - dimstride::Int - length::Int -end -function DimColumn(dim::D, alldims::DimTuple) where D<:Dimension - # This is the apparent stride for indexing purposes, - # it is not always the real array stride - stride = dimstride(alldims, dim) - len = prod(map(length, alldims)) - DimColumn{eltype(dim),D}(dim, stride, len) -end - -dim(c::DimColumn) = getfield(c, :dim) -dimstride(c::DimColumn) = getfield(c, :dimstride) - -Base.length(c::DimColumn) = getfield(c, :length) -@inline function Base.getindex(c::DimColumn, i::Int) - Base.@boundscheck checkbounds(c, i) - dim(c)[mod((i - 1) ÷ dimstride(c), length(dim(c))) + 1] -end -Base.getindex(c::DimColumn, ::Colon) = vec(c) -Base.getindex(c::DimColumn, A::AbstractArray) = [c[i] for i in A] -Base.size(c::DimColumn) = (length(c),) -Base.axes(c::DimColumn) = (Base.OneTo(length(c)),) -Base.vec(c::DimColumn{T}) where T = [c[i] for i in eachindex(c)] -Base.Array(c::DimColumn) = vec(c) - - -# DimArrayColumn - - -struct DimArrayColumn{T,A<:AbstractDimArray{T},DS,DL,L} <: AbstractVector{T} - data::A - dimstrides::DS - dimlengths::DL - length::L -end -function DimArrayColumn(A::AbstractDimArray{T}, alldims::DimTupleOrEmpty) where T - # This is the apparent stride for indexing purposes, - # it is not always the real array stride - dimstrides = map(d -> dimstride(alldims, d), dims(A)) - dimlengths = map(length, dims(A)) - len = prod(map(length, alldims)) - DimArrayColumn(A, dimstrides, dimlengths, len) -end - -Base.parent(c::DimArrayColumn) = getfield(c, :data) -dimstrides(c::DimArrayColumn) = getfield(c, :dimstrides) -dimlengths(c::DimArrayColumn) = getfield(c, :dimlengths) - -Base.length(c::DimArrayColumn) = getfield(c, :length) -@inline function Base.getindex(c::DimArrayColumn, i::Int) - Base.@boundscheck checkbounds(c, i) - I = map((s, l) -> _strideind(s, l, i), dimstrides(c), dimlengths(c)) - parent(c)[I...] -end - -_strideind(stride, len, i) = mod((i - 1) ÷ stride, len) + 1 - -Base.getindex(c::DimArrayColumn, ::Colon) = vec(c) -Base.getindex(c::DimArrayColumn, A::AbstractArray) = [c[i] for i in A] -Base.size(c::DimArrayColumn) = (length(c),) -Base.axes(c::DimArrayColumn) = (Base.OneTo(length(c)),) -Base.vec(c::DimArrayColumn{T}) where T = [c[i] for i in eachindex(c)] -Base.Array(c::DimArrayColumn) = vec(c) - - # DimTable - """ DimTable <: AbstractDimTable @@ -167,17 +74,26 @@ DimTable with 1024 rows, 4 columns, and schema: struct DimTable <: AbstractDimTable parent::Union{AbstractDimArray,AbstractDimStack} colnames::Vector{Symbol} - dimcolumns::Vector{DimColumn} - dimarraycolumns::Vector{DimArrayColumn} + dimcolumns::Vector{AbstractVector} + dimarraycolumns::Vector{AbstractVector} end function DimTable(s::AbstractDimStack; mergedims=nothing) - s = isnothing(mergedims) ? s : DimensionalData.mergedims(s, mergedims) - dims_ = dims(s) - dimcolumns = map(d -> DimColumn(d, dims_), dims_) - dimarraycolumns = map(A -> DimArrayColumn(A, dims_), s) - keys = _colnames(s) - return DimTable(s, collect(keys), collect(dimcolumns), collect(dimarraycolumns)) + s = isnothing(mergedims) ? s : DD.mergedims(s, mergedims) + dimcolumns = collect(_dimcolumns(s)) + dimarraycolumns = if hassamedims(s) + map(vec, layers(s)) + else + map(A -> vec(DimExtensionArray(A, dims(s))), layers(s)) + end |> collect + keys = collect(_colnames(s)) + return DimTable(s, keys, dimcolumns, dimarraycolumns) +end + +_dimcolumns(x) = map(d -> _dimcolumn(x, d), dims(x)) +function _dimcolumn(x, d::Dimension) + dim_as_dimarray = DimArray(index(d), d) + vec(DimExtensionArray(dim_as_dimarray, dims(x))) end function DimTable(xs::Vararg{AbstractDimArray}; layernames=nothing, mergedims=nothing) @@ -187,17 +103,15 @@ function DimTable(xs::Vararg{AbstractDimArray}; layernames=nothing, mergedims=no # Construct Layer Names layernames = isnothing(layernames) ? [Symbol("layer_$i") for i in eachindex(xs)] : layernames - # Construct DimColumns + # Construct dimwnsion and array columns with DimExtensionArray xs = isnothing(mergedims) ? xs : map(x -> DimensionalData.mergedims(x, mergedims), xs) dims_ = dims(first(xs)) - dimcolumns = collect(map(d -> DimColumn(d, dims_), dims_)) + dimcolumns = collect(_dimcolumns(dims_)) dimnames = collect(map(dim2key, dims_)) - - # Construct DimArrayColumns - dimarraycolumns = collect(map(A -> DimArrayColumn(A, dims_), xs)) + dimarraycolumns = collect(map(vec ∘ parent, xs)) + colnames = vcat(dimnames, layernames) # Return DimTable - colnames = vcat(dimnames, layernames) return DimTable(first(xs), colnames, dimcolumns, dimarraycolumns) end diff --git a/test/array.jl b/test/array.jl index 7e3e2454f..edde93b2b 100644 --- a/test/array.jl +++ b/test/array.jl @@ -246,6 +246,10 @@ end @testset "similar with mixed DimUnitRange and Base.OneTo" begin x = randn(10) T = ComplexF64 + ax1 = Base.OneTo(2) + ax1 = axes(X(1:2), 1) + ax2 = Base.OneTo(2) + ax2 = axes(X(1:2), 1) for ax1 in (Base.OneTo(2), axes(X(1:2), 1)) s11 = @inferred(similar(x, (ax1,))) s12 = @inferred(similar(x, T, (ax1,))) diff --git a/test/dimension.jl b/test/dimension.jl index 2c00a0f77..3631f19b6 100644 --- a/test/dimension.jl +++ b/test/dimension.jl @@ -81,9 +81,14 @@ end da = DimArray(a, (X(LinRange(140, 148, 5)), Y(LinRange(2, 11, 4)))) dimz = dims(da) - for args in ((dimz,), (dimz, (X(), Y())), (dimz, X(), Y()), - (dimz, (X, Y)), (dimz, X, Y), - (dimz, (1, 2)), (dimz, 1, 2)) + for args in ((dimz,), + (dimz, (X(), Y())), + (dimz, X(), Y()), + (dimz, (X, Y)) , + (dimz, X, Y), + (dimz, (1, 2)), + (dimz, 1, 2) + ) @test index(args...) == (LinRange(140, 148, 5), LinRange(2, 11, 4)) @test name(args...) == (:X, :Y) @test units(args...) == (nothing, nothing) diff --git a/test/dimindices.jl b/test/dimindices.jl index 72e3f7187..1a720e432 100644 --- a/test/dimindices.jl +++ b/test/dimindices.jl @@ -1,86 +1,145 @@ -using DimensionalData, Test +using DimensionalData, Test, Dates using DimensionalData.LookupArrays, DimensionalData.Dimensions A = zeros(X(4.0:7.0), Y(10.0:12.0)) -di = DimIndices(A) -di[1] - -ci = CartesianIndices(A) -@test val.(collect(di)) == Tuple.(collect(ci)) @testset "DimIndices" begin - @test di[4, 3] == (X(4), Y(3)) - @test di[2] == (X(2), Y(1)) - @test di[X(1)] == [(X(1), Y(1),), (X(1), Y(2),), (X(1), Y(3),)] + di = @inferred DimIndices(A) + ci = CartesianIndices(A) + @test @inferred val.(collect(di)) == Tuple.(collect(ci)) + @test A[di] == view(A, di) == A + @test @inferred di[4, 3] == (X(4), Y(3)) + @test @inferred di[2] == (X(2), Y(1)) + @test @inferred di[X(1)] == [(X(1), Y(1),), (X(1), Y(2),), (X(1), Y(3),)] @test map(ds -> A[ds...] + 2, di) == fill(2.0, 4, 3) - @test map(ds -> A[ds...], di[X(At(7.0))]) == [0.0, 0.0, 0.0] @test_throws ArgumentError DimIndices(zeros(2, 2)) @test_throws ArgumentError DimIndices(nothing) @test size(di) == (4, 3) - # Vector - @test collect(DimIndices(X(1.0:2.0))) == [(X(1),), (X(2),)] + # Array of indices + @test @inferred collect(DimIndices(X(1:2))) == [(X(1),), (X(2),)] + @test @inferred A[di[:]] == vec(A) + @test @inferred A[di[2:5]] == A[2:5] + @test @inferred A[reverse(di[2:5])] == A[5:-1:2] + @test @inferred A[di[2:4, 1:2]] == A[2:4, 1:2] + A1 = zeros(X(4.0:7.0), Ti(3), Y(10.0:12.0)) + @test @inferred size(A1[di[2:5]]) == (3, 4) + @test @inferred size(A1[di[2:4, 1:2], Ti=1]) == (3, 2) + @test @inferred A1[di] isa DimArray{Float64,3} + @test @inferred A1[X=1][di] isa DimArray{Float64,2} + @test @inferred A1[X=1, Y=1][di] isa DimArray{Float64,1} + # Indexing with no matching dims is like [] (?) + @test @inferred view(A1, X=1, Y=1, Ti=1)[di] == 0.0 + + # Convert to vector of DimTuple + @test @inferred A1[di[:]] isa DimArray{Float64,2} + @test @inferred size(A1[di[:]]) == (3, 12) + @test @inferred A1[X=1][di[:]] isa DimArray{Float64,2} + @test @inferred A1[di[:]] isa DimArray{Float64,2} + @test @inferred A1[X=1][di[:]] isa DimArray{Float64,2} + @test @inferred A1[X=1, Y=1][di[:]] isa DimArray{Float64,1} + # Indexing with no matching dims is like [] (?) + @test @inferred view(A1, X=1, Y=1, Ti=1)[di[:]] == 0.0 end @testset "DimPoints" begin - dp = DimPoints(A) - @test dp[4, 3] == (7.0, 12.0) - @test dp[2] == (5.0, 10.0) - @test dp[X(1)] == [(4.0, 10.0), (4.0, 11.0), (4.0, 12.0)] + dp = @inferred DimPoints(A) + @test @inferred dp[4, 3] == (7.0, 12.0) + @test @inferred dp[:, 3] == [(4.0, 12.0), (5.0, 12.0), (6.0, 12.0), (7.0, 12.0)] + @test @inferred dp[2] == (5.0, 10.0) + @test @inferred dp[X(1)] == [(4.0, 10.0), (4.0, 11.0), (4.0, 12.0)] @test size(dp) == (4, 3) @test_throws ArgumentError DimPoints(zeros(2, 2)) @test_throws ArgumentError DimPoints(nothing) # Vector - @test collect(DimPoints(X(1.0:2.0))) == [(1.0,), (2.0,)] + @test @inferred DimPoints(X(1.0:2.0)) == [(1.0,), (2.0,)] end - -@testset "DimKeys" begin - dk = DimKeys(A) - @test size(dk) == (4, 3) - @test dk[4, 3] == (X(At(7.0; atol=eps(Float64))), Y(At(12.0, atol=eps(Float64)))) - @test dk[2] == (X(At(5.0; atol=eps(Float64))), Y(At(10.0, atol=eps(Float64)))) - @test dk[X(1)] == dk[X(At(4.0))] == +@testset "DimSelectors" begin + ds = @inferred DimSelectors(A) + # The selected array is not identical because + # the lookups will be vectors and Irregular, + # rather than Regular ranges + @test parent(A[DimSelectors(A)]) == parent(view(A, DimSelectors(A))) == A + @test index(A[DimSelectors(A)], 1) == index(view(A, DimSelectors(A)), 1) == index(A, 1) + @test size(ds) == (4, 3) + @test @inferred ds[4, 3] == (X(At(7.0; atol=eps(Float64))), Y(At(12.0, atol=eps(Float64)))) + @test @inferred ds[2] == (X(At(5.0; atol=eps(Float64))), Y(At(10.0, atol=eps(Float64)))) + @test ds[X(1)] == ds[X(At(4.0))] == [(X(At(4.0; atol=eps(Float64))), Y(At(10.0; atol=eps(Float64))),), (X(At(4.0; atol=eps(Float64))), Y(At(11.0; atol=eps(Float64))),), (X(At(4.0; atol=eps(Float64))), Y(At(12.0; atol=eps(Float64))),)] - @test broadcast(ds -> A[ds...] + 2, dk) == fill(2.0, 4, 3) - @test broadcast(ds -> A[ds...], dk[X(At(7.0))]) == [0.0 for i in 1:3] - @test_throws ArgumentError DimKeys(zeros(2, 2)) - @test_throws ArgumentError DimKeys(nothing) + @test broadcast(ds -> A[ds...] + 2, ds) == fill(2.0, 4, 3) + @test broadcast(ds -> A[ds...], ds[X(At(7.0))]) == [0.0 for i in 1:3] + @test_throws ArgumentError DimSelectors(zeros(2, 2)) + @test_throws ArgumentError DimSelectors(nothing) - @test collect(DimKeys(X(1.0:2.0))) == + @test @inferred DimSelectors(X(1.0:2.0)) == [(X(At(1.0; atol=eps(Float64))),), (X(At(2.0; atol=eps(Float64))),)] @testset "atol" begin - dka = DimKeys(A; atol=0.3) + dsa = @inferred DimSelectors(A; atol=0.3) # Mess up the lookups a little... B = zeros(X(4.25:1:7.27), Y(9.95:1:12.27)) - @test dka[4, 3] == (X(At(7.0; atol=0.3)), Y(At(12.0, atol=0.3))) - @test broadcast(ds -> B[ds...] + 2, dka) == fill(2.0, 4, 3) - @test broadcast(ds -> B[ds...], dka[X(At(7.0))]) == [0.0 for i in 1:3] - @test_throws ArgumentError broadcast(ds -> B[ds...] + 2, dk) == fill(2.0, 4, 3) - @test_throws ArgumentError DimKeys(zeros(2, 2)) - @test_throws ArgumentError DimKeys(nothing) + @test dsa[4, 3] == (X(At(7.0; atol=0.3)), Y(At(12.0, atol=0.3))) + @test broadcast(ds -> B[ds...] + 2, dsa) == fill(2.0, 4, 3) + @test broadcast(ds -> B[ds...], dsa[X(At(7.0))]) == [0.0 for i in 1:3] + @test_throws ArgumentError broadcast(ds -> B[ds...] + 2, ds) == fill(2.0, 4, 3) + @test_throws ArgumentError DimSelectors(zeros(2, 2)) + @test_throws ArgumentError DimSelectors(nothing) end - @testset "mixed atol" begin - dka2 = DimKeys(A; atol=(0.1, 0.2)) + dsa2 = @inferred DimSelectors(A; atol=(0.1, 0.2)) # Mess up the lookups again C = zeros(X(4.05:7.05), Y(10.15:12.15)) - @test dka2[4, 3] == (X(At(7.0; atol=0.1)), Y(At(12.0, atol=0.2))) - @test collect(dka2[X(1)]) == [(X(At(4.0; atol=0.1)), Y(At(10.0; atol=0.2)),), + @test @inferred dsa2[4, 3] == (X(At(7.0; atol=0.1)), Y(At(12.0, atol=0.2))) + @test collect(dsa2[X(1)]) == [(X(At(4.0; atol=0.1)), Y(At(10.0; atol=0.2)),), (X(At(4.0; atol=0.1)), Y(At(11.0; atol=0.2)),), (X(At(4.0; atol=0.1)), Y(At(12.0; atol=0.2)),)] - @test broadcast(ds -> C[ds...] + 2, dka2) == fill(2.0, 4, 3) - @test broadcast(ds -> C[ds...], dka2[X(At(7.0))]) == [0.0 for i in 1:3] + @test @inferred broadcast(ds -> C[ds...] + 2, dsa2) == fill(2.0, 4, 3) + @test @inferred broadcast(ds -> C[ds...], dsa2[X(At(7.0))]) == [0.0 for i in 1:3] # without atol it errors - @test_throws ArgumentError broadcast(ds -> C[ds...] + 2, dk) == fill(2.0, 4, 3) + @test_throws ArgumentError broadcast(ds -> C[ds...] + 2, ds) == fill(2.0, 4, 3) # no dims errors - @test_throws ArgumentError DimKeys(zeros(2, 2)) - @test_throws ArgumentError DimKeys(nothing) + @test_throws ArgumentError DimSelectors(zeros(2, 2)) + @test_throws ArgumentError DimSelectors(nothing) # Only Y can handle errors > 0.1 D = zeros(X(4.15:7.15), Y(10.15:12.15)) - @test_throws ArgumentError broadcast(ds -> D[ds...] + 2, dka2) == fill(2.0, 4, 3) + @test_throws ArgumentError broadcast(ds -> D[ds...] + 2, dsa2) == fill(2.0, 4, 3) + end + + @testset "mixed selectors" begin + dsa2 = @inferred DimSelectors(A; selectors=(Near(), At()), atol=0.2) + # Mess up the lookups again + C = zeros(X(4.05:7.05), Y(10.15:12.15)) + @test dsa2[4, 3] == (X(Near(7.0)), Y(At(12.0, atol=0.2))) + @test collect(dsa2[X(1)]) == [(X(Near(4.0)), Y(At(10.0; atol=0.2)),), + (X(Near(4.0)), Y(At(11.0; atol=0.2)),), + (X(Near(4.0)), Y(At(12.0; atol=0.2)),)] + @test @inferred broadcast(ds -> C[ds...] + 2, dsa2) == fill(2.0, 4, 3) + @test @inferred broadcast(ds -> C[ds...], dsa2[X(At(7.0))]) == [0.0 for i in 1:3] + # without atol it errors + @test_throws ArgumentError broadcast(ds -> C[ds...] + 2, ds) == fill(2.0, 4, 3) + # no dims errors + @test_throws ArgumentError DimSelectors(zeros(2, 2)) + @test_throws ArgumentError DimSelectors(nothing) + D = zeros(X(4.15:7.15), Y(10.15:12.15)) + # This works with `Near` + @test @inferred broadcast(ds -> D[ds...] + 2, dsa2) == fill(2.0, 4, 3) end end + +@testset "DimExtensionArray" begin + A = DimArray(((1:4) * (1:3)'), (X(4.0:7.0), Y(10.0:12.0)); name=:foo) + ex = DimensionalData.DimExtensionArray(A, (dims(A)..., Z(1:10), Ti(DateTime(2000):Month(1):DateTime(2000, 12); sampling=Intervals(Start())))) + @test isintervals(dims(ex, Ti)) + @test ispoints(dims(ex, (X, Y, Z))) + @test DimArray(ex) isa DimArray{Int,4,<:Tuple{<:X,<:Y,<:Z,<:Ti},<:Tuple,Array{Int,4}} + @test @inferred DimArray(ex[X=1, Y=1]) isa DimArray{Int,2,<:Tuple{<:Z,<:Ti},<:Tuple,Array{Int,2}} + @test @inferred all(DimArray(ex[X=4, Y=2]) .=== A[X=4, Y=2]) + @test @inferred ex[Z=At(10), Ti=At(DateTime(2000))] == A + @test @inferred vec(ex) == mapreduce(_ -> vec(A), vcat, 1:prod(size(ex[X=1, Y=1]))) + ex1 = DimensionalData.DimExtensionArray(A, (Z(1:10), dims(A)..., Ti(DateTime(2000):Month(1):DateTime(2000, 12); sampling=Intervals(Start())))) + @test vec(ex1) == mapreduce(_ -> mapreduce(i -> map(_ -> A[i], 1:size(ex1, Z)), vcat, 1:prod((size(ex1, X), size(ex1, Y)))), vcat, 1:size(ex1, Ti)) +end + diff --git a/test/indexing.jl b/test/indexing.jl index f1103122c..4554076dc 100644 --- a/test/indexing.jl +++ b/test/indexing.jl @@ -26,6 +26,17 @@ using DimensionalData.LookupArrays, 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 @@ -155,16 +166,16 @@ end @testset "dimension" begin d = X(Sampled(2.0:2.0:10, ForwardOrdered(), Regular(2.0), Points(), nothing)) - @test d[:] == d - @test d[1:5] == d - @test d[1:5] isa typeof(d) + @test @inferred d[:] == d + @test @inferred d[1:5] == d + @test @inferred d[1:5] 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)) - @test d[2] === 4.0 - @test d[CartesianIndex((4,))] == 8.0 - @test d[CartesianIndices((3:4,))] isa X{<:Sampled} - @test d[2:2:4] == X(Sampled(4.0:4.0:8.0, ForwardOrdered(), Regular(4.0), Points(), nothing)) + @test @inferred d[2] === 4.0 + @test @inferred d[CartesianIndex((4,))] == 8.0 + @test @inferred d[CartesianIndices((3:4,))] isa X{<:Sampled} + @test @inferred d[2:2:4] == X(Sampled(4.0:4.0:8.0, ForwardOrdered(), Regular(4.0), Points(), nothing)) d = Y(NoLookup(1:100)) @test d[100] == 100 @test d[1:5] isa typeof(d) @@ -184,26 +195,38 @@ end da = @test_nowarn DimArray(a, dimz; refdims=refdimz, name=:test, metadata=ameta) @testset "getindex for single integers returns values" begin - @test da[X(1), Y(2)] == 2 - @test da[X(2), Y(2)] == 4 - @test da[1, 2] == 2 - @test da[2] == 3 - @inferred getindex(da, X(2), Y(2)) + @test @inferred da[X(1), Y(2)] == 2 + @test @inferred da[X(2), Y(2)] == 4 + @test @inferred da[1, 2] == 2 + @test @inferred da[2] == 3 end - @testset "LinearIndex getindex returns an Array, except Vector" begin - @test da[1:2] isa Array - @test x = da[1, :][1:2] isa DimArray + @test @inferred da[1:2] isa Array + @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) + b = @inferred da[[!iseven(i) for i in 1:length(da)]] + @test b isa Array + @test b == da[1:2:end] + + v = @inferred da[1, :] + @test @inferred v[1:2] isa DimArray + @test @inferred v[rand(Bool, length(v))] isa DimArray + b = v[[!iseven(i) for i in 1:length(v)]] + @test b isa DimArray + # Indexing with a Vector{Bool} returns an irregular lookup, so these are not exactly equal + @test parent(b) == v[1:2:end] end @testset "mixed CartesianIndex and CartesianIndices indexing works" begin da3 = cat(da, 10da; dims=Z) - @test da3[1, CartesianIndex(1, 2)] == 10 - @test view(da3, 1:2, CartesianIndex(1, 2)) == [10, 30] - @test da3[1, CartesianIndices((1:2, 1:1))] isa DimArray - @test da3[CartesianIndices(da3)] isa DimArray - @test da3[CartesianIndices(da3)] == da3 + @test @inferred da3[1, CartesianIndex(1, 2)] == 10 + @test @inferred view(da3, 1:2, CartesianIndex(1, 2)) == [10, 30] + @test @inferred da3[1, CartesianIndices((1:2, 1:1))] isa DimArray + @test @inferred da3[CartesianIndices(da3), 1] isa DimArray + @test @inferred da3[CartesianIndices(da3)] == da3 end @testset "getindex returns DimensionArray slices with the right dimensions" begin @@ -215,12 +238,12 @@ end (Ti(1:1), Y(Sampled(-38.0:2.0:-38.0, ForwardOrdered(), Regular(2.0), Points(), ymeta)),) @test name(a) == :test @test metadata(a) === ameta - @test metadata(a, X) === xmeta + @test metadata(dims(a, X)) === xmeta @test bounds(a) === ((143.0, 145.0),) @test bounds(a, X) === (143.0, 145.0) @test locus(da, X) == Center() - a = da[X(1), Y(1:2)] + a = da[(X(1), Y(1:2))] # Can use a tuple of dimensions like a CartesianIndex @test a == [1, 2] @test typeof(a) <: DimArray{Int,1} @test typeof(parent(a)) <: Array{Int,1} @@ -246,16 +269,19 @@ end @test bounds(a) == ((143.0, 145.0), (-38.0, -36.0)) @test bounds(a, X) == (143.0, 145.0) - # Indexing with array works - a = da[X([2, 1]), Y([2, 1])] + a = da[X([2, 1]), Y([2, 1])] # Indexing with array works @test a == [4 3; 2 1] + + @test da[DimIndices(da)] == da + da[DimIndices(da)[X(1)]] + da[DimSelectors(da)] end @testset "selectors work" begin - @test da[At(143), -38.0..36.0] == [1, 2] - @test da[144.0..146.0, Near(-37.1)] == [3] - @test da[X=At(143), Y=-38.0..36.0] == [1, 2] - @test da[X=144.0..146.0, Y=Near(-37.1)] == [3] + @test @inferred da[At(143), -38.0..36.0] == [1, 2] + @test @inferred da[144.0..146.0, Near(-37.1)] == [3] + @test @inferred da[X=At(143), Y=-38.0..36.0] == [1, 2] + @test @inferred da[X=144.0..146.0, Y=Near(-37.1)] == [3] end @testset "view DimensionArray containing views" begin @@ -362,7 +388,7 @@ end end @testset "logical indexing" begin - A = DimArray(zeros(40, 50), (X, Y)); + A = DimArray(zeros(40, 50), (X, Y)) I = rand(Bool, 40, 50) @test all(A[I] .== 0.0) A[I] .= 3 @@ -444,6 +470,14 @@ end # @code_warntype getindex(da2, a=1, b=2, c=3, d=4, e=5, f=6) end end + + @testset "trailing colon" begin + @test da[X(1), Y(2)] == 2 + @test da[X(2), Y(2)] == 4 + @test da[1, 2] == 2 + @test da[2] == 3 + @inferred getindex(da, X(2), Y(2)) + end end @testset "stack" begin @@ -453,56 +487,95 @@ end da1 = DimArray(A, dimz; name=:one) da2 = DimArray(Float32.(2A), dimz; name=:two) da3 = DimArray(Int.(3A), dimz; name=:three) + da4 = rebuild(Int.(4da1)[Y=1]; name=:four) s = DimStack((da1, da2, da3)) + s_mixed = DimStack((da1, da2, da3, da4)) @testset "getindex" begin - @test s[1, 1] === (one=1.0, two=2.0f0, three=3) - @test s[X(2), Y(3)] === (one=6.0, two=12.0f0, three=18) - @test s[X=At(:b), Y=At(10.0)] === (one=4.0, two=8.0f0, three=12) + @test @inferred s[1, 1] === (one=1.0, two=2.0f0, three=3) + @test @inferred s_mixed[1, 1] === (one=1.0, two=2.0f0, three=3, four=4) + @test @inferred s[X(2), Y(3)] === (one=6.0, two=12.0f0, three=18) + @test @inferred s[X=At(:b), Y=At(10.0)] === (one=4.0, two=8.0f0, three=12) slicedds = s[At(:a), :] @test 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]) + slicedds_mixed = s_mixed[At(:a), :] + @test slicedds_mixed[:one] == [1.0, 2.0, 3.0] + @test parent(slicedds_mixed) == (one=[1.0, 2.0, 3.0], two=[2.0f0, 4.0f0, 6.0f0], three=[3, 6, 9], four=fill(4)) @testset "linear indices" begin - linear2d = s[1:2] + linear2d = @inferred s[1] @test linear2d isa NamedTuple - @test linear2d == (one=[1.0, 4.0], two=[2.0f0, 8.0f0], three=[3, 12]) - linear1d = s[Y(1)][1:2] + @test linear2d == (one=1.0, two=2.0f0, three=3) + @test_broken linear1d = @inferred view(s[X(2)], 1) + linear1d = view(s[X(2)], 1) + @test linear1d isa DimStack + @test parent(linear1d) == (one=fill(4.0), two=fill(8.0f0), three=fill(12)) + @test_broken linear2d = @inferred s[1:2] + linear2d = s[1:2] + @test linear2d isa DimStack + @test NamedTuple(linear2d) == (one=[1.0, 4.0], two=[2.0f0, 8.0f0], three=[3, 12]) + linear1d = @inferred s[X(1)][1:2] + linear1d = @inferred s[X(1)][1:2] @test linear1d isa DimStack - @test parent(linear1d) == (one=[1.0, 4.0], two=[2.0f0, 8.0f0], three=[3, 12]) + @test parent(linear1d) == (one=[1.0, 2.0], two=[2.0f0, 4.0f0], three=[3, 6]) end end @testset "getindex Tuple" begin + @test_broken st1 = @inferred s[(:three, :one)] st1 = s[(:three, :one)] @test keys(st1) === (:three, :one) @test values(st1) == (da3, da1) end + @testset "mixed CartesianIndex and CartesianIndices indexing works" begin + da3d = cat(da1, 10da1; dims=Z) + s3 = merge(s, (; ten=da3d)) + @test @inferred s3[2, CartesianIndex(2, 2)] === (one=5.0, two=10.0f0, three=15, ten=50.0) + @test @inferred view(s3, 1:2, CartesianIndex(1, 2)) isa DimStack + @test @inferred NamedTuple(view(s3, 1:2, CartesianIndex(1, 2))) == (one=[1.0, 4.0], two=Float32[2.0, 8.0], three=[3, 12], ten=[10.0, 40.0]) + @test @inferred s3[2, CartesianIndices((1:2, 1:1))] isa DimStack + @test @inferred s3[CartesianIndex((2,)), CartesianIndices((1:2, 1:1)), 1, 1, 1] isa DimStack + @test @inferred s3[CartesianIndices(s3.one), CartesianIndex(2,)] isa DimStack + @test @inferred NamedTuple(s3[CartesianIndices(s3.one), CartesianIndex(2,)]) == + (one=[1.0 2.0 3.0; 4.0 5.0 6.0], two=Float32[2.0 4.0 6.0; 8.0 10.0 12.0], three=[3 6 9; 12 15 18], ten=[10 20 30; 40 50 60]) + @test @inferred s3[CartesianIndices(s3.one), 2, 1, 1, 1] isa DimStack + @test @inferred s3[CartesianIndices(s3)] == s3 + end + @testset "view" begin - sv = view(s, 1, 1) + sv = @inferred view(s, 1, 1) @test parent(sv) == (one=fill(1.0), two=fill(2.0f0), three=fill(3)) @test dims(sv) == () - sv = view(s, X(1:2), Y(3:3)) + sv = @inferred view(s, X(1:2), Y(3:3)) @test parent(sv) == (one=[3.0 6.0]', two=[6.0f0 12.0f0]', three=[9 18]') slicedds = view(s, X=At(:a), Y=:) - @test slicedds[:one] == [1.0, 2.0, 3.0] + @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 = 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) @test linear1d isa DimStack @test parent(linear1d) == (one=fill(1.0), two=fill(2.0f0), three=fill(3)) + linear2d = view(s, 1:2) + @test linear2d isa DimStack + @test NamedTuple(linear2d) == (one=[1.0, 4.0], two=[2.0f0, 8.0f0], three=[3, 12]) + linear1d = s[X(1)][1:2] + @test linear1d isa DimStack + @test parent(linear1d) == (one=[1.0, 2.0], two=[2.0f0, 4.0f0], three=[3, 6]) end @testset "0-dimensional return layers" begin - ds = view(s, X(1), Y(1)) + ds = @inferred view(s, X(1), Y(1)) @test ds isa DimStack @test dims(ds) === () - @test view(s, X(1), Y(2))[:one] == view(da1, X(1), Y(2)) - @test view(s, X(1), Y(1))[:two] == view(da2, X(1), Y(1)) - @test view(s, X(2), Y(3))[:three] == view(da3, X(2), Y(3)) + @test @inferred view(s, X(1), Y(2))[:one] == view(da1, X(1), Y(2)) + @test @inferred view(s, X(1), Y(1))[:two] == view(da2, X(1), Y(1)) + @test @inferred view(s, X(2), Y(3))[:three] == view(da3, X(2), Y(3)) end @testset "@views macro and maybeview work even with kw syntax" begin sv1 = @views s[X(1:2), Y(3:3)] @@ -526,13 +599,13 @@ end end @testset "Cartesian indices work as usual" begin - @test s[CartesianIndex(2, 2)] == (one=5.0, two=10.0, three=15.0) - @test view(s, CartesianIndex(2, 2)) == map(d -> view(d, 2, 2), s) + @test @inferred s[CartesianIndex(2, 2)] == (one=5.0, two=10.0, three=15.0) + @test @inferred view(s, CartesianIndex(2, 2)) == map(d -> view(d, 2, 2), s) s_set = deepcopy(s) s_set[CartesianIndex(2, 2)] = (one=5, two=6, three=7) - @test s_set[2, 2] === (one=5.0, two=6.0f0, three=7) + @test @inferred s_set[2, 2] === (one=5.0, two=6.0f0, three=7) s_set[CartesianIndex(2, 2)] = (9, 10, 11) - @test s_set[2, 2] === (one=9.0, two=10.0f0, three=11) + @test @inferred s_set[2, 2] === (one=9.0, two=10.0f0, three=11) @test_throws ArgumentError s_set[CartesianIndex(2, 2)] = (seven=5, two=6, three=7) end end @@ -549,7 +622,7 @@ end A2 = DimArray(p, (X, Y, t2)) A3 = DimArray(p, (X, Y, t3)) - @test view(A1, Ti(5)) == permutedims([5]) - @test view(A2, Ti(5)) == permutedims([5]) - @test view(A3, Ti(5)) == permutedims([5]) + @test @inferred view(A1, Ti(5)) == permutedims([5]) + @test @inferred view(A2, Ti(5)) == permutedims([5]) + @test @inferred view(A3, Ti(5)) == permutedims([5]) end diff --git a/test/interface.jl b/test/interface.jl index 900409551..a962c01bd 100644 --- a/test/interface.jl +++ b/test/interface.jl @@ -1,4 +1,4 @@ -using DimensionalData, Interfaces, Test +using DimensionalData, Interfaces, Test, Dates @test name(nothing) == "" @test name(Nothing) == "" @@ -9,3 +9,12 @@ using DimensionalData, Interfaces, Test # @test Interfaces.test(DimensionalData) @test Interfaces.test(DimensionalData.DimArrayInterface) @test Interfaces.test(DimensionalData.DimStackInterface) + +# For when BaseInterfaces registered... +# using BaseInterfaces +# @implements ArrayInterface{(:setindex!,:similar_type,:similar_eltype)} AbstractDimArray [ +# rand(X(10)), +# rand(Y(1:10), Ti(DateTime(2000):Month(1):DateTime(2000, 12))), +# rand(X(1:7), Y(1:8), Z('a':'h')) +# ] +# @test BaseInterfaces.test(AbstractDimArray) diff --git a/test/merged.jl b/test/merged.jl index ada4d9b1d..2b2e4eaa8 100644 --- a/test/merged.jl +++ b/test/merged.jl @@ -7,7 +7,7 @@ da = DimArray(0.1:0.1:0.4, dim) da2 = DimArray((0.1:0.1:0.4) * (1:1:3)', (dim, Ti(1u"s":1u"s":3u"s")); metadata=Dict()) @testset "regular indexing" begin - @test da[Coord()] === da[Coord(:)] === da + @test da[Coord()] == da[Coord(:)] == da @test da[Coord([1, 2])] == [0.1, 0.2] @test da[Coord(4)] == 0.4 @test da2[Coord(4), Ti(3)] ≈ 1.2 @@ -65,7 +65,7 @@ end @testset "unmerge" begin a = DimArray(rand(32, 32, 3), (X,Y,Dim{:band})) - merged = mergedims(a, (X,Y)=>:geometry) + merged = mergedims(a, (X, Y) => :geometry) unmerged = unmergedims(merged, dims(a)) perm_unmerged = unmergedims(permutedims(merged, (2,1)), dims(a)) diff --git a/test/primitives.jl b/test/primitives.jl index d865edf23..fec3b1ac0 100644 --- a/test/primitives.jl +++ b/test/primitives.jl @@ -1,7 +1,7 @@ using DimensionalData, Dates, Test , BenchmarkTools using DimensionalData.LookupArrays, DimensionalData.Dimensions -using .Dimensions: _call_primitive, _wraparg, _reducedims, AlwaysTuple, MaybeFirst +using .Dimensions: _dim_query, _wraparg, _reducedims, AlwaysTuple, MaybeFirst @dim Tst @@ -83,14 +83,14 @@ end DimensionalData.key2dim.(((:x, :y, :z, :a, :b, :c, :d, :e, :f, :g, :h)))...) end -@testset "_call_primitive" begin - @test _call_primitive((f, args...) -> args, AlwaysTuple(), (Z, :a, :b), Ti, XDim, :x) == - _call_primitive((f, args...) -> args, MaybeFirst(), (Z, :a, :b), Ti, XDim, :x) == +@testset "_dim_query" begin + @test _dim_query((f, args...) -> args, AlwaysTuple(), (Z, :a, :b), Ti, XDim, :x) == + _dim_query((f, args...) -> args, MaybeFirst(), (Z, :a, :b), Ti, XDim, :x) == ((Val(Z), Dim{:a}(), Dim{:b}()), (Val(Ti), Val(XDim), Dim{:x}())) - @test _call_primitive((f, args...) -> args, MaybeFirst(), (Z, :a, :b), Ti) == + @test _dim_query((f, args...) -> args, MaybeFirst(), (Z, :a, :b), Ti) == (Val(Z), Dim{:a}(), Dim{:b}()) - @testset "_call_primitive" begin - f1 = t -> _call_primitive((f, args...) -> args, t, (X, :a, :b), (TimeDim, X(), :a, :b, Ti)) + @testset "_dim_query" begin + f1 = t -> _dim_query((f, args...) -> args, t, (X, :a, :b), (TimeDim, X(), :a, :b, Ti)) @inferred f1(AlwaysTuple()) @test (@ballocated $f1(AlwaysTuple())) == 0 end @@ -232,6 +232,7 @@ end @testset "hasdim" begin @test hasdim(da, X()) == true + @test hasdim(da, :X) == true @test hasdim(da, isforward) == (true, true) @test (@ballocated hasdim($da, X())) == 0 @test hasdim(da, Ti) == false @@ -240,6 +241,7 @@ end @ballocated hasdim(dims($da), Y) @test (@ballocated hasdim(dims($da), Y)) == 0 @test hasdim(dims(da), (X, Y)) == (true, true) + @test hasdim(dims(da), (:X, :Y)) == (true, true) f1 = (da) -> hasdim(dims(da), (X, Ti, Y, Z)) @test @inferred f1(da) == (true, false, true, false) @test (@ballocated $f1($da)) == 0 @@ -271,12 +273,16 @@ end @test otherdims(A, X()) == dims(A, (Y, Z)) @test otherdims(A, x -> x isa X) == dims(A, (Y, Z)) @test (@ballocated otherdims($A, X())) == 0 + @test (@ballocated otherdims($A, X)) == 0 + @test (@ballocated otherdims($A, (X, Y))) == 0 + @test (@ballocated otherdims($A, X, Y)) == 0 + @test otherdims(A, X, Y) == dims(A, (Z,)) @test otherdims(A, Y) == dims(A, (X, Z)) @test otherdims(A, Z) == dims(A, (X, Y)) + @test otherdims(A) == dims(A, (X, Y, Z)) @test otherdims(A, DimensionalData.ZDim) == dims(A, (X, Y)) @test otherdims(A, (X, Z)) == dims(A, (Y,)) f1 = A -> otherdims(A, (X, Z)) - @ballocated $f1($A) @test (@ballocated $f1($A)) == 0 f2 = (A) -> otherdims(A, Ti) @test f2(A) == dims(A, (X, Y, Z)) @@ -350,7 +356,8 @@ end @test slicedims(dimz, 2:2, :) == slicedims(dimz, (), 2:2, :) == ((X(Sampled(145:2:145, ForwardOrdered(), Regular(2), Points(), NoMetadata())), Y(Sampled(-20:-1:-22, ReverseOrdered(), Regular(-1), Points(), NoMetadata()))), ()) - @test slicedims((), (1:2, 3)) == slicedims((), (), (1:2, 3)) == + # What is this testing, it should error... + @test_broken slicedims((), (1:2, 3)) == slicedims((), (), (1:2, 3)) == slicedims((), 1:2, 3) == slicedims((), (), 1:2, 3) == ((), ()) @test slicedims(dimz, CartesianIndex(2, 3)) == ((), (X(Sampled(145:2:145, ForwardOrdered(), Regular(2), Points(), NoMetadata())), @@ -370,7 +377,9 @@ end @test slicedims(irreg, (2:2, 1:2)) == slicedims(irreg, 2:2, 1:2) == ((X(Sampled([142.0], ForwardOrdered(), Regular(2.0), Intervals(Start()), NoMetadata())), Y(Sampled([30.0, 20.0], ReverseOrdered(), Regular(-10.0), Intervals(Center()), NoMetadata()))), ()) - @test slicedims((), (1:2, 3)) == slicedims((), (), (1:2, 3)) == ((), ()) + # This should never happen, not sure why it was tested? + @test_broken slicedims((), (1:2, 3)) == slicedims((), (), (1:2, 3)) == ((), ()) + @test slicedims((), (1, 1)) == slicedims((), (), (1, 1)) == ((), ()) end @testset "Irregular Points" begin @@ -383,7 +392,8 @@ end @test slicedims(irreg, (2:2, 1:2)) == slicedims(irreg, 2:2, 1:2) == ((X(Sampled([142.0], ForwardOrdered(), Irregular(142, 142), Points(), NoMetadata())), Y(Sampled([40.0, 20.0], ReverseOrdered(), Irregular(20.0, 40.0), Points(), NoMetadata()))), ()) - @test slicedims((), (1:2, 3)) == slicedims((), (), (1:2, 3)) == ((), ()) + @test_broken slicedims((), (1:2, 3)) == slicedims((), (), (1:2, 3)) == ((), ()) + @test slicedims((), (1, 1)) == slicedims((), (), (1, 1)) == ((), ()) end @testset "Irregular Intervals" begin @@ -396,7 +406,8 @@ end @test slicedims(irreg, (2:2, 1:2)) == slicedims(irreg, 2:2, 1:2) == ((X(Sampled([142.0], ForwardOrdered(), Irregular(142.0, 144.0), Intervals(Start()), NoMetadata())), Y(Sampled([40.0, 20.0], ReverseOrdered(), Irregular(15.0, 60.0), Intervals(Center()), NoMetadata()))), ()) - @test slicedims((), (1:2, 3)) == slicedims((), (), (1:2, 3)) == ((), ()) + @test_broken slicedims((), (1:2, 3)) == slicedims((), (), (1:2, 3)) == ((), ()) + @test slicedims((), (1, 1)) == slicedims((), (), (1, 1)) == ((), ()) end @testset "NoLookup" begin diff --git a/test/runtests.jl b/test/runtests.jl index dff91beb8..830a157a8 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,48 +1,45 @@ -using DimensionalData, Aqua, SafeTestsets - -if VERSION >= v"1.9.0" - Aqua.test_ambiguities([DimensionalData, Base, Core]) - Aqua.test_unbound_args(DimensionalData) - Aqua.test_undefined_exports(DimensionalData) - Aqua.test_project_extras(DimensionalData) - Aqua.test_stale_deps(DimensionalData) - Aqua.test_deps_compat(DimensionalData) - Aqua.test_project_extras(DimensionalData) - Aqua.test_stale_deps(DimensionalData) -end - -@time @safetestset "ecosystem" begin include("ecosystem.jl") end -@time @safetestset "interface" begin include("interface.jl") end -@time @safetestset "metadata" begin include("metadata.jl") end -@time @safetestset "name" begin include("name.jl") end - -@time @safetestset "lookup" begin include("lookup.jl") end -@time @safetestset "selector" begin include("selector.jl") end - -@time @safetestset "merged" begin include("merged.jl") end -@time @safetestset "dimension" begin include("dimension.jl") end -@time @safetestset "DimUnitRange" begin include("dimunitrange.jl") end -@time @safetestset "primitives" begin include("primitives.jl") end -@time @safetestset "format" begin include("format.jl") end - -@time @safetestset "array" begin include("array.jl") end -@time @safetestset "stack" begin include("stack.jl") end -@time @safetestset "indexing" begin include("indexing.jl") end -@time @safetestset "methods" begin include("methods.jl") end -@time @safetestset "broadcast" begin include("broadcast.jl") end -@time @safetestset "matmul" begin include("matmul.jl") end - -@time @safetestset "dimindices" begin include("dimindices.jl") end -@time @safetestset "adapt" begin include("adapt.jl") end -@time @safetestset "set" begin include("set.jl") end -@time @safetestset "show" begin include("show.jl") end -@time @safetestset "tables" begin include("tables.jl") end -@time @safetestset "utils" begin include("utils.jl") end -@time @safetestset "groupby" begin include("groupby.jl") end - - -if Sys.islinux() - # Unfortunately this can hang on other platforms. - # Maybe ram use of all the plots on the small CI machine? idk - @time @safetestset "plotrecipes" begin include("plotrecipes.jl") end +using DimensionalData, Test, Aqua, SafeTestsets + +@testset "DimensionalData" begin + if VERSION >= v"1.9.0" + Aqua.test_ambiguities([DimensionalData, Base, Core]) + Aqua.test_unbound_args(DimensionalData) + Aqua.test_undefined_exports(DimensionalData) + Aqua.test_project_extras(DimensionalData) + Aqua.test_stale_deps(DimensionalData) + Aqua.test_deps_compat(DimensionalData) + Aqua.test_project_extras(DimensionalData) + Aqua.test_stale_deps(DimensionalData) + end + + @time @safetestset "interface" begin include("interface.jl") end + @time @safetestset "metadata" begin include("metadata.jl") end + @time @safetestset "name" begin include("name.jl") end + @time @safetestset "dimension" begin include("dimension.jl") end + @time @safetestset "primitives" begin include("primitives.jl") end + @time @safetestset "lookup" begin include("lookup.jl") end + @time @safetestset "selector" begin include("selector.jl") end + @time @safetestset "merged" begin include("merged.jl") end + @time @safetestset "DimUnitRange" begin include("dimunitrange.jl") end + @time @safetestset "format" begin include("format.jl") end + + @time @safetestset "array" begin include("array.jl") end + @time @safetestset "stack" begin include("stack.jl") end + @time @safetestset "indexing" begin include("indexing.jl") end + @time @safetestset "methods" begin include("methods.jl") end + @time @safetestset "broadcast" begin include("broadcast.jl") end + @time @safetestset "matmul" begin include("matmul.jl") end + @time @safetestset "dimindices" begin include("dimindices.jl") end + @time @safetestset "set" begin include("set.jl") end + @time @safetestset "tables" begin include("tables.jl") end + @time @safetestset "utils" begin include("utils.jl") end + @time @safetestset "groupby" begin include("groupby.jl") end + @time @safetestset "show" begin include("show.jl") end + @time @safetestset "adapt" begin include("adapt.jl") end + @time @safetestset "ecosystem" begin include("ecosystem.jl") end + if Sys.islinux() + # Unfortunately this can hang on other platforms. + # Maybe ram use of all the plots on the small CI machine? idk + @time @safetestset "plotrecipes" begin include("plotrecipes.jl") end + end end diff --git a/test/stack.jl b/test/stack.jl index 0e2796a25..ec16a6891 100644 --- a/test/stack.jl +++ b/test/stack.jl @@ -3,7 +3,7 @@ using DimensionalData, Test, LinearAlgebra, Statistics, ConstructionBase, Random using DimensionalData: data using DimensionalData: Sampled, Categorical, AutoLookup, NoLookup, Transformed, Regular, Irregular, Points, Intervals, Start, Center, End, - Metadata, NoMetadata, ForwardOrdered, ReverseOrdered, Unordered, layers + Metadata, NoMetadata, ForwardOrdered, ReverseOrdered, Unordered, layers, basedims A = [1.0 2.0 3.0; 4.0 5.0 6.0] @@ -159,9 +159,11 @@ end zs2 = (zs..., map(tuple, zs)...) @testset "type-inferrable due to const-propagation" begin - f(x, dims) = eachslice(x; dims=dims) - @testset for dims in (x, y, z, (x,), (y,), (z,), (x, y), (y, z), (x, y, z)) - @inferred f(mixed, dims) + f(x, dims) = eachslice(x; dims) + @testset for ds in (x, y, z, (x,), (y,), (z,), (x, y), (y, z), (x, y, z)) + # @inferred f(mixed, ds) not consistent accross julia 1.10 versions ?? + # I can't reproduce this locally + f(mixed, ds) end end @@ -173,7 +175,6 @@ end @testset "slice over X dimension" begin @testset for dims in xs2 - @test eachslice(mixed; dims=dims) isa Base.Generator slices = map(identity, eachslice(mixed; dims=dims)) @test slices isa DimArray{<:DimStack,1} slices2 = map(l -> view(mixed, X(At(l))), lookup(Dimensions.dims(mixed, x))) @@ -183,7 +184,6 @@ end @testset "slice over Y dimension" begin @testset for dims in ys2 - @test eachslice(mixed; dims=dims) isa Base.Generator slices = map(identity, eachslice(mixed; dims=dims)) @test slices isa DimArray{<:DimStack,1} slices2 = map(l -> view(mixed, Y(At(l))), lookup(y)) @@ -192,33 +192,33 @@ end end @testset "slice over Z dimension" begin - @testset for dims in zs2 - @test eachslice(mixed; dims=dims) isa Base.Generator - slices = map(identity, eachslice(mixed; dims=dims)) + ds = first(zs2) + @testset for ds in zs2 + slices = map(identity, eachslice(mixed; dims=ds)) @test slices isa DimArray{<:DimStack,1} slices2 = map(l -> view(mixed, Z(l)), axes(mixed, z)) - @test slices == slices2 + dims(slices2) + @test all(map(===, slices, slices2)) end end @testset "slice over combinations of Z and Y dimensions" begin - @testset for dims in Iterators.product(zs, ys) + @testset for ds in Iterators.product(zs, ys) # mixtures of integers and dimensions are not supported - rem(sum(d -> isa(d, Int), dims), length(dims)) == 0 || continue - @test eachslice(mixed; dims=dims) isa Base.Generator - slices = map(identity, eachslice(mixed; dims=dims)) - @test slices isa DimArray{<:DimStack,2} + rem(sum(d -> isa(d, Int), ds), length(ds)) == 0 || continue + slices = eachslice(mixed; dims=ds) slices2 = map( l -> view(mixed, Z(l[1]), Y(l[2])), Iterators.product(axes(mixed, z), axes(mixed, y)), ) - @test slices == slices2 + @test basedims(slices) == basedims(slices2) + @test collect(slices) == collect(slices2) end end end @testset "map" begin - @test values(map(a -> a .* 2, s)) == values(DimStack(2da1, 2da2, 2da3)) + @test values(map(a -> a .* 2, s))[1] == values(DimStack(2da1, 2da2, 2da3))[1] @test dims(map(a -> a .* 2, s)) == dims(DimStack(2da1, 2da2, 2da3)) @test map(a -> a[1], s) == (one=1.0, two=2.0, three=3.0) @test values(map(a -> a .* 2, s)) == values(DimStack(2da1, 2da2, 2da3)) diff --git a/test/tables.jl b/test/tables.jl index d2e85af30..d2da48852 100644 --- a/test/tables.jl +++ b/test/tables.jl @@ -1,7 +1,7 @@ using DimensionalData, IteratorInterfaceExtensions, TableTraits, Tables, Test, DataFrames using DimensionalData.LookupArrays, DimensionalData.Dimensions -using DimensionalData: DimTable, DimColumn, DimArrayColumn, dimstride +using DimensionalData: DimTable, DimExtensionArray, dimstride x = X([:a, :b, :c]) y = Y([10.0, 20.0]) @@ -18,8 +18,6 @@ da2 = DimArray(fill(2, (3, 2, 3)), dimz; name=:data2) @test parent(t) === ds @test Tables.columns(t) === t - @test t[:X] isa DimColumn - @test t[:data] isa DimArrayColumn @test length(t[:X]) == length(t[:Y]) == length(t[:test]) == 18 @test Tables.istable(typeof(t)) == Tables.istable(t) == @@ -63,31 +61,13 @@ end ds = DimStack(da) t = DimTable(ds) for x in (da, ds, t) + x = da @test IteratorInterfaceExtensions.isiterable(x) @test TableTraits.isiterabletable(x) @test collect(Tables.namedtupleiterator(x)) == collect(IteratorInterfaceExtensions.getiterator(x)) end end -@testset "DimColumn" begin - c = DimColumn(dims(da, Y), dims(da)) - @test length(c) == length(da) - @test size(c) == (length(da),) - @test axes(c) == (Base.OneTo(length(da)),) - @test vec(c) == Array(c) == Vector(c) == - repeat([10.0, 10.0, 10.0, 20.0, 20.0, 20.0], 3) - @test c[1] == 10.0 - @test c[4] == 20.0 - @test c[7] == 10.0 - @test c[18] == 20.0 - @test c[1:5] == [10.0, 10.0, 10.0, 20.0, 20.0] - @test_throws BoundsError c[-1] - @test_throws BoundsError c[19] - - cX = DimColumn(dims(da, X), dims(da)) - @test vec(cX) == Array(cX) == Vector(cX) == repeat([:a, :b, :c], 6) -end - @testset "DataFrame conversion" begin ds = DimStack(da, da2) @time t = DimTable(ds) From d3f11470e109a021b973744a7faabba237807609 Mon Sep 17 00:00:00 2001 From: Rafael Schouten Date: Mon, 19 Feb 2024 00:43:56 +0100 Subject: [PATCH 037/108] add broadcast_dims for stack (#621) * add broadcast_dims for stack * we already have _firststack --- src/utils.jl | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/utils.jl b/src/utils.jl index 4cb58bfbc..539b0a927 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -117,6 +117,15 @@ function broadcast_dims(f, As::AbstractDimArray...) broadcast_dims!(f, similar(first(As), T, dims), As...) end +function broadcast_dims(f, As::Union{AbstractDimStack,AbstractDimArray}...) + st = _firststack(As...) + nts = _as_nts(NamedTuple(st), As...) + layers = map(nts...) do as... + broadcast_dims(f, as...) + end + rebuild(st, layers) +end + """ broadcast_dims!(f, dest::AbstractDimArray, sources::AbstractDimArray...) => dest @@ -192,3 +201,9 @@ function uniquekeys(keys::Tuple{Symbol,Vararg{Symbol}}) end uniquekeys(t::Tuple) = ntuple(i -> Symbol(:layer, i), length(t)) uniquekeys(nt::NamedTuple) = keys(nt) + +_as_nts(nt::NamedTuple{K}, A::AbstractDimArray, As...) where K = + (NamedTuple{K}(ntuple(x -> A, length(K))), _as_nts(nt, As...)...) +_as_nts(nt::NamedTuple{K}, st::AbstractDimStack, As...) where K = + (NamedTuple(st), _as_nts(nt, As...)...) +_as_nts(nt::NamedTuple) = () From 5deb963e0785dffb35a11763bbb956cf506087eb Mon Sep 17 00:00:00 2001 From: rafaqz Date: Mon, 19 Feb 2024 00:49:00 +0100 Subject: [PATCH 038/108] Revert "add broadcast_dims for stack (#621)" This reverts commit d3f11470e109a021b973744a7faabba237807609. --- src/utils.jl | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/src/utils.jl b/src/utils.jl index 539b0a927..4cb58bfbc 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -117,15 +117,6 @@ function broadcast_dims(f, As::AbstractDimArray...) broadcast_dims!(f, similar(first(As), T, dims), As...) end -function broadcast_dims(f, As::Union{AbstractDimStack,AbstractDimArray}...) - st = _firststack(As...) - nts = _as_nts(NamedTuple(st), As...) - layers = map(nts...) do as... - broadcast_dims(f, as...) - end - rebuild(st, layers) -end - """ broadcast_dims!(f, dest::AbstractDimArray, sources::AbstractDimArray...) => dest @@ -201,9 +192,3 @@ function uniquekeys(keys::Tuple{Symbol,Vararg{Symbol}}) end uniquekeys(t::Tuple) = ntuple(i -> Symbol(:layer, i), length(t)) uniquekeys(nt::NamedTuple) = keys(nt) - -_as_nts(nt::NamedTuple{K}, A::AbstractDimArray, As...) where K = - (NamedTuple{K}(ntuple(x -> A, length(K))), _as_nts(nt, As...)...) -_as_nts(nt::NamedTuple{K}, st::AbstractDimStack, As...) where K = - (NamedTuple(st), _as_nts(nt, As...)...) -_as_nts(nt::NamedTuple) = () From 5612a8400b01ef033fe545c78283abd3c0dd98ab Mon Sep 17 00:00:00 2001 From: Rafael Schouten Date: Mon, 19 Feb 2024 00:48:47 +0100 Subject: [PATCH 039/108] dont break the show_after interface (#636) * dont break the show_after interface * bugfix * more bugfix * some tweaks * typo --- src/array/show.jl | 103 +++++++++++++++++++++++++++------------------- src/dimindices.jl | 14 ------- src/groupby.jl | 27 +++++++----- src/stack/show.jl | 26 ++++++------ 4 files changed, 91 insertions(+), 79 deletions(-) diff --git a/src/array/show.jl b/src/array/show.jl index c4ac20ed2..341b2c594 100644 --- a/src/array/show.jl +++ b/src/array/show.jl @@ -9,20 +9,20 @@ end # Fancy show for text/plain function Base.show(io::IO, mime::MIME"text/plain", A::AbstractBasicDimArray{T,N}) where {T,N} - lines, maxlen = show_main(io, mime, A::AbstractBasicDimArray) + lines, blockwidth = show_main(io, mime, A::AbstractBasicDimArray) # Printing the array data is optional, subtypes can # show other things here instead. ds = displaysize(io) - ioctx = IOContext(io, :displaysize => (ds[1] - lines, ds[2])) - show_after(ioctx, mime, A; maxlen) + ctx = IOContext(io, :blockwidth => blockwidth, :displaysize => (ds[1] - lines, ds[2])) + show_after(ctx, mime, A) return nothing end # Defer simple 2-arg show to the parent array Base.show(io::IO, A::AbstractDimArray) = show(io, parent(A)) """ - show_main(io::IO, mime, A::AbstractDimArray; maxlen, kw...) - show_main(io::IO, mime, A::AbstractDimStack; maxlen, kw...) + show_main(io::IO, mime, A::AbstractDimArray) + show_main(io::IO, mime, A::AbstractDimStack) Interface methods for adding the main part of `show` @@ -35,14 +35,16 @@ print_top(io, mime, A) But read the DimensionalData.jl `show.jl` code for details. """ function show_main(io, mime, A::AbstractBasicDimArray) - lines_t, maxlen, width = print_top(io, mime, A) - lines_m, maxlen = print_metadata_block(io, mime, metadata(A); width, maxlen=min(width, maxlen)) - return lines_t + lines_m, maxlen + lines_t, blockwidth, displaywidth = print_top(io, mime, A) + lines_m, blockwidth = print_metadata_block(io, mime, metadata(A); + blockwidth, displaywidth=min(displaywidth, blockwidth) + ) + return lines_t + lines_m, blockwidth end """ - show_after(io::IO, mime, A::AbstractDimArray; maxlen, kw...) - show_after(io::IO, mime, A::AbstractDimStack; maxlen, kw...) + show_after(io::IO, mime, A::AbstractDimArray) + show_after(io::IO, mime, A::AbstractDimStack) Interface methods for adding addional `show` text for AbstractDimArray/AbstractDimStack subtypes. @@ -51,19 +53,27 @@ for AbstractDimArray/AbstractDimStack subtypes. Additional keywords may be added at any time. + +`blockwidth` is passed in context + +```juli +blockwidth = get(io, :blockwidth, 10000) +``` + Note - a anssi box is left unclosed. This method needs to close it, -or add more. `maxlen` is the maximum length of the inner text. +or add more. `blockwidth` is the maximum length of the inner text. Most likely you always want to at least close the show blocks with: '''julia -print_block_close(io, maxlen) +print_block_close(io, blockwidth) ''' But read the DimensionalData.jl `show.jl` code for details. """ -function show_after(io::IO, mime, A::AbstractBasicDimArray; maxlen, kw...) - print_block_close(io, maxlen) +function show_after(io::IO, mime, A::AbstractBasicDimArray) + blockwidth = get(io, :blockwidth, 0) + print_block_close(io, blockwidth) ndims(A) > 0 && println(io) print_array(io, mime, A) end @@ -85,18 +95,17 @@ print_type(io, x::AbstractArray{T,N}) where {T,N} = print(io, string(nameof(type print_type(io, x) = print(io, string(nameof(typeof(x)))) function print_top(io, mime, A) - lines = 4 - _, width = displaysize(io) - maxlen = min(width - 2, textwidth(sprint(summary, A)) + 2) - printstyled(io, '╭', '─'^maxlen, '╮'; color=:light_black) + _, displaywidth = displaysize(io) + blockwidth = min(displaywidth - 2, textwidth(sprint(summary, A)) + 2) + printstyled(io, '╭', '─'^blockwidth, '╮'; color=:light_black) println(io) printstyled(io, "│ "; color=:light_black) summary(io, A) printstyled(io, " │"; color=:light_black) println(io) - n, maxlen = print_dims_block(io, mime, dims(A); width, maxlen) - lines += n + 1 - return lines, maxlen, width + n, blockwidth = print_dims_block(io, mime, dims(A); displaywidth, blockwidth) + lines = 2 + n + return lines, blockwidth, displaywidth end function print_sizes(io, size; @@ -111,60 +120,68 @@ function print_sizes(io, size; end end -function print_dims_block(io, mime, dims; width, maxlen, label="dims", kw...) +function print_dims_block(io, mime, dims; displaywidth, blockwidth, label="dims", kw...) lines = 0 if isempty(dims) printed = false - newmaxlen = maxlen + new_blockwidth = blockwidth else dim_lines = split(sprint(print_dims, mime, dims), '\n') - newmaxlen = min(width - 2, max(maxlen, maximum(textwidth, dim_lines))) - print_block_top(io, label, maxlen, newmaxlen) + new_blockwidth = min(displaywidth - 2, max(blockwidth, maximum(textwidth, dim_lines))) + lines += print_block_top(io, label, blockwidth, new_blockwidth) lines += print_dims(io, mime, dims; kw...) println(io) - lines += 2 + lines += 1 printed = true end - return lines, newmaxlen, printed + return lines, new_blockwidth, printed end -function print_metadata_block(io, mime, metadata; maxlen=0, width, firstblock=false) +function print_metadata_block(io, mime, metadata; blockwidth=0, displaywidth) lines = 0 if metadata isa NoMetadata - newmaxlen = maxlen + new_blockwidth = blockwidth else metadata_lines = split(sprint(show, mime, metadata), "\n") - newmaxlen = min(width-2, max(maxlen, maximum(length, metadata_lines))) - print_block_separator(io, "metadata", maxlen, newmaxlen) + new_blockwidth = min(displaywidth-2, max(blockwidth, maximum(length, metadata_lines))) + print_block_separator(io, "metadata", blockwidth, new_blockwidth) println(io) print(io, " ") show(io, mime, metadata) println(io) lines += length(metadata_lines) + 2 end - return lines, newmaxlen + return lines, new_blockwidth end # Block lines -function print_block_top(io, label, prevmaxlen, newmaxlen) - corner = (newmaxlen > prevmaxlen) ? '┐' : '┤' - block_line = if newmaxlen > prevmaxlen - string('─'^(prevmaxlen), '┴', '─'^max(0, (newmaxlen - textwidth(label) - 3 - prevmaxlen)), ' ', label, ' ') +function print_block_top(io, label, prev_width, new_width) + corner = (new_width > prev_width) ? '┐' : '┤' + top_line = if new_width > prev_width + string( + '├', '─'^(prev_width), '┴', + '─'^max(0, (new_width - textwidth(label) - 3 - prev_width)), + ' ', label, ' ', corner + ) else - string('─'^max(0, newmaxlen - textwidth(label) - 2), ' ', label, ' ') + string('├', '─'^max(0, new_width - textwidth(label) - 2), ' ', label, ' ', corner) end - printstyled(io, '├', block_line, corner; color=:light_black) + printstyled(io, top_line; color=:light_black) println(io) + lines = 1 + return lines end -function print_block_separator(io, label, prevmaxlen, newmaxlen) - corner = (newmaxlen > prevmaxlen) ? '┐' : '┤' - printstyled(io, '├', '─'^max(0, newmaxlen - textwidth(label) - 2), ' ', label, ' ', corner; color=:light_black) +function print_block_separator(io, label, prev_width, new_width) + corner = (new_width > prev_width) ? '┐' : '┤' + middle_line = string('├', '─'^max(0, new_width - textwidth(label) - 2), ' ', label, ' ', corner) + printstyled(io, middle_line; color=:light_black) end -function print_block_close(io, maxlen) - printstyled(io, '└', '─'^maxlen, '┘'; color=:light_black) +function print_block_close(io, blockwidth) + closing_line = string('└', '─'^blockwidth, '┘') + printstyled(io, closing_line; color=:light_black) end # Showing the array is optional for AbstractDimArray diff --git a/src/dimindices.jl b/src/dimindices.jl index 98c2239d4..f8beec2d3 100644 --- a/src/dimindices.jl +++ b/src/dimindices.jl @@ -317,20 +317,6 @@ end return view(ds._data, rebuild(d, d[i])) end -# function Base.show(io::IO, mime, A::DimSlices) -# parentdims = otherdims(A.data, dims(A)) -# _, width = displaysize(io) -# sorteddims = (dims(A)..., otherdims(parentdims, dims(A))...) -# colordims = dims(map(rebuild, sorteddims, ntuple(dimcolors, Val(length(sorteddims)))), dims(first(A))) -# colors = collect(map(val, colordims)) -# print_dims_block(io, mime, basedims(first(A)); width, maxlen, label="group dims", colors) -# length(A) > 0 || return nothing -# A1 = map(x -> DimSummariser(x, colors), A) -# show_after(io, mime, A1; maxlen) -# map(x -> DimSummariser(x, colors), A) -# Base.printmatrix(io, A) -# end - # Extends the dimensions of any `AbstractBasicDimArray` # as if the array assigned into a larger array accross all dimensions, # but without the copying. Theres is a cost for linear indexing these objects diff --git a/src/groupby.jl b/src/groupby.jl index 074453ee2..0bbbd9400 100644 --- a/src/groupby.jl +++ b/src/groupby.jl @@ -51,15 +51,22 @@ function Base.summary(io::IO, A::DimGroupByArray{T,N}) where {T,N} print(io, string(nameof(typeof(A)), "{$(nameof(T)),$N}")) end -function show_after(io::IO, mime, A::DimGroupByArray; maxlen=0) - _, width = displaysize(io) +function show_after(io::IO, mime, A::DimGroupByArray) + displayheight, displaywidth = displaysize(io) + blockwidth = get(io, :blockwidth, 0) sorteddims = (dims(A)..., otherdims(first(A), dims(A))...) colordims = dims(map(rebuild, sorteddims, ntuple(dimcolors, Val(length(sorteddims)))), dims(first(A))) colors = collect(map(val, colordims)) - print_dims_block(io, mime, basedims(first(A)); width, maxlen, label="group dims", colors) + lines, new_blockwidth, _ = print_dims_block(io, mime, basedims(first(A)); + displaywidth, blockwidth, label="group dims", colors + ) length(A) > 0 || return nothing A1 = map(x -> DimSummariser(x, colors), A) - show_after(io, mime, A1; maxlen) + ctx = IOContext(io, + :blockwidth => blockwidth, + :displaysize => (displayheight - lines, displaywidth) + ) + show_after(ctx, mime, A1) return nothing end @@ -110,7 +117,7 @@ end Bins(bins; labels=nothing, pad=0.001) = Bins(identity, bins, labels, pad) Bins(f, bins; labels=nothing, pad=0.001) = Bins(f, bins, labels, pad) -Base.show(io::IO, bins::Bins) = +Base.show(io::IO, bins::Bins) = println(io, nameof(typeof(bins)), "(", bins.f, ", ", bins.bins, ")") abstract type AbstractCyclicBins end @@ -123,7 +130,7 @@ struct CyclicBins{F,C,Sta,Ste,L} <: AbstractBins end CyclicBins(f; cycle, step, start=1, labels=nothing) = CyclicBins(f, cycle, start, step, labels) -Base.show(io::IO, bins::CyclicBins) = +Base.show(io::IO, bins::CyclicBins) = println(io, nameof(typeof(bins)), "(", bins.f, "; ", join(map(k -> "$k=$(getproperty(bins, k))", (:cycle, :step, :start)), ", "), ")") yearhour(x) = year(x), hour(x) @@ -265,7 +272,7 @@ function DataAPI.groupby(A::DimArrayOrStack, dimfuncs::DimTuple) _group_indices(dims(A, d), DD.val(d)) end # Separate lookups dims from indices - group_dims = map(first, dim_groups_indices) + group_dims = map(first, dim_groups_indices) indices = map(rebuild, dimfuncs, map(last, dim_groups_indices)) views = DimSlices(A, indices) @@ -352,7 +359,7 @@ end # Return the bin for a value # function _choose_bin(b::AbstractBins, groups::LookupArray, val) -# groups[ispoints(groups) ? At(val) : Contains(val)] +# groups[ispoints(groups) ? At(val) : Contains(val)] # end # function _choose_bin(b::AbstractBins, groups, val) # i = findfirst(Base.Fix1(_in, val), groups) @@ -380,11 +387,11 @@ end _maybe_label(vals) = vals _maybe_label(f::Function, vals) = f.(vals) _maybe_label(::Nothing, vals) = vals -function _maybe_label(labels::AbstractArray, vals) +function _maybe_label(labels::AbstractArray, vals) @assert length(labels) == length(vals) return labels end -function _maybe_label(labels::Dict, vals) +function _maybe_label(labels::Dict, vals) map(vals) do val if haskey(labels, val) labels[val] diff --git a/src/stack/show.jl b/src/stack/show.jl index 01cb1c0a1..f000f88d3 100644 --- a/src/stack/show.jl +++ b/src/stack/show.jl @@ -6,41 +6,43 @@ end function Base.show(io::IO, mime::MIME"text/plain", stack::AbstractDimStack) # Show main blocks - summar, dims, layers, metadata - _, maxlen = show_main(io, mime, stack) + _, blockwidth = show_main(io, mime, stack) # Show anything else subtypes want to append - show_after(io, mime, stack; maxlen) + ctx = IOContext(io, :blockwidth => blockwidth) + show_after(ctx, mime, stack) return nothing end # Show customisation interface function show_main(io, mime, stack::AbstractDimStack) - lines, maxlen, width = print_top(io, mime, stack) - maxlen = print_layers_block(io, mime, stack; maxlen, width) - _, maxlen = print_metadata_block(io, mime, metadata(stack); width, maxlen=min(width-2, maxlen)) + lines, blockwidth, displaywidth = print_top(io, mime, stack) + blockwidth = print_layers_block(io, mime, stack; blockwidth, displaywidth) + _, blockwidth = print_metadata_block(io, mime, metadata(stack); displaywidth, blockwidth=min(displaywidth-2, blockwidth)) end -function show_after(io, mime, stack::AbstractDimStack; maxlen) - print_block_close(io, maxlen) +function show_after(io, mime, stack::AbstractDimStack) + blockwidth = get(io, :blockwidth, 0) + print_block_close(io, blockwidth) end # Show blocks -function print_layers_block(io, mime, stack; maxlen, width) +function print_layers_block(io, mime, stack; blockwidth, displaywidth) layers = DD.layers(stack) keylen = if length(keys(layers)) == 0 0 else reduce(max, map(length ∘ string, collect(keys(layers)))) end - newmaxlen = maxlen + newblockwidth = blockwidth for key in keys(layers) - newmaxlen = min(width - 2, max(maxlen, length(sprint(print_layer, stack, key, keylen)))) + newblockwidth = min(displaywidth - 2, max(blockwidth, length(sprint(print_layer, stack, key, keylen)))) end - print_block_separator(io, "layers", maxlen, newmaxlen) + print_block_separator(io, "layers", blockwidth, newblockwidth) println(io) for key in keys(layers) print_layer(io, stack, key, keylen) end - return newmaxlen + return newblockwidth end function print_layer(io, stack, key, keylen) From e5b5f9e60b39c3d50e10cc7a3b0f9d4351ff004e Mon Sep 17 00:00:00 2001 From: rafaqz Date: Tue, 20 Feb 2024 15:01:07 +0100 Subject: [PATCH 040/108] bugfix show --- src/array/show.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/array/show.jl b/src/array/show.jl index 341b2c594..a90bbbd9c 100644 --- a/src/array/show.jl +++ b/src/array/show.jl @@ -248,7 +248,7 @@ function _print_matrix(io::IO, A::AbstractArray{<:Any,1}, lookups::Tuple) copyto!(top, CartesianIndices(top), A, CartesianIndices(itop)) bottom = Array{eltype(A)}(undef, length(ibottom)) copyto!(bottom, CartesianIndices(bottom), A, CartesianIndices(ibottom)) - vals = vcat(A[itop], A[ibottom]) + vals = vcat(parent(A[itop]), parent(A[ibottom])) lu = only(lookups) if lu isa NoLookup Base.print_matrix(io, vals) From d7f8d1f3c5c9f46395c603d00a017c5d9c41f9ee Mon Sep 17 00:00:00 2001 From: Rafael Schouten Date: Tue, 20 Feb 2024 15:01:10 +0100 Subject: [PATCH 041/108] add more docs (#639) --- docs/src/.vitepress/config.mts | 11 +++-- docs/src/cuda.md | 67 ++++++++++++++++++-------- docs/src/dimensions.md | 56 +++++++++------------- docs/src/diskarrays.md | 12 ++--- docs/src/ext_dd.md | 23 ++++++++- docs/src/groupby.md | 6 +-- docs/src/selectors.md | 80 ++++++++++++++++++++++++++++--- docs/src/tables.md | 56 +++++++++++++++++++--- src/Dimensions/primitives.jl | 3 +- src/LookupArrays/lookup_arrays.jl | 5 +- src/array/indexing.jl | 10 ++-- src/groupby.jl | 10 ++-- src/tables.jl | 15 ++++-- 13 files changed, 254 insertions(+), 100 deletions(-) diff --git a/docs/src/.vitepress/config.mts b/docs/src/.vitepress/config.mts index 673faf614..8698a8fad 100644 --- a/docs/src/.vitepress/config.mts +++ b/docs/src/.vitepress/config.mts @@ -45,8 +45,8 @@ export default defineConfig({ items: [ { text: 'Integrations', link: '/integrations' }, { text: 'Tables and DataFrames', link: '/tables' }, - { text: 'Plots with Makie', link: '/plots' }, - { text: 'CUDA & GPUs', link: '/cuda' }, + { text: 'Plots and Makie', link: '/plots' }, + { text: 'CUDA and GPUs', link: '/cuda' }, { text: 'DiskArrays', link: '/diskarrays' }, { text: 'Extending DimensionalData', link: '/ext_dd' }, ], @@ -66,11 +66,12 @@ export default defineConfig({ { text: 'Selectors', link: '/selectors' }, { text: 'GroupBy', link: '/groupby' }, { text: 'Stacks', link: '/stacks' }, - { text: 'Tables and DataFrames', link: '/tables' }, { text: 'Lookup customazation', link: '/lookup_customization' }, + { text: 'Tables and DataFrames', link: '/tables' }, + { text: 'Plots and Makie', link: '/plots' }, + { text: 'CUDA and GPUs', link: '/cuda' }, + { text: 'DiskArrays', link: '/diskarrays' }, { text: 'Extending DimensionalData', link: '/ext_dd' }, - { text: 'Plots with Makie', link: '/plots' }, - { text: 'CUDA & GPUs', link: '/cuda' }, { text: 'API Reference', items: [ { text: 'General Reference', link: '/api/reference' }, diff --git a/docs/src/cuda.md b/docs/src/cuda.md index 839498b08..5971a0bb1 100644 --- a/docs/src/cuda.md +++ b/docs/src/cuda.md @@ -3,7 +3,52 @@ Running regular julia code on GPUs is one of the most amazing things about the language. DimensionalData.jl leans into this as much as possible. -From the beginning DimensionalData.jl has had two GPU-related goals: +```julia +using DimensionalData, CUDA + +# Create a Float32 array to use on the GPU +A = rand(Float32, X(1.0:1000.0), Y(1.0:2000.0)) + +# Move the parent data to the GPU with `modify` and the `CuArray` constructor: +cuA = modify(CuArray, A) +``` + +The result of a broadcast is still be a DimArray: + +```julia +julia> cuA2 = cuA .* 2 +╭───────────────────────────────╮ +│ 1000×2000 DimArray{Float32,2} │ +├───────────────────────────────┴────────────────────────────── dims ┐ + ↓ X Sampled{Float64} 1.0:1.0:1000.0 ForwardOrdered Regular Points, + → Y Sampled{Float64} 1.0:1.0:2000.0 ForwardOrdered Regular Points +└────────────────────────────────────────────────────────────────────┘ + ↓ → 1.0 2.0 3.0 4.0 … 1998.0 1999.0 2000.0 + 1.0 1.69506 1.28405 0.989952 0.900394 1.73623 1.30427 1.98193 + 2.0 1.73591 0.929995 0.665742 0.345501 0.162919 1.81708 0.702944 + 3.0 1.24575 1.80455 1.78028 1.49097 0.45804 0.224375 0.0197492 + 4.0 0.374026 1.91495 1.17645 0.995683 0.835288 1.54822 0.487601 + 5.0 1.17673 0.0557598 0.183637 1.90645 … 0.88058 1.23788 1.59705 + 6.0 1.57019 0.215049 1.9155 0.982762 0.906838 0.1076 0.390081 + ⋮ ⋱ + 995.0 1.48275 0.40409 1.37963 1.66622 0.462981 1.4492 1.26917 + 996.0 1.88869 1.86174 0.298383 0.854739 … 0.778222 1.42151 1.75568 + 997.0 1.88092 1.87436 0.285965 0.304688 1.32669 0.0599431 0.134186 + 998.0 1.18035 1.61025 0.352614 1.75847 0.464554 1.90309 1.30923 + 999.0 1.40584 1.83056 0.0804518 0.177423 1.20779 1.95217 0.881149 + 1000.0 1.41334 0.719974 0.479126 1.92721 0.0649391 0.642908 1.07277 +``` + +But the data is on the GPU: + +```julia +julia> typeof(parent(cuA2)) +CuArray{Float32, 2, CUDA.Mem.DeviceBuffer} +``` + +## GPU Integration goals + +DimensionalData.jl has two GPU-related goals: 1. Work seamlessly with Base julia broadcasts and other operations that already work on GPU. @@ -21,26 +66,6 @@ fields converted to GPU friendly forms whenever required, using [Adapt.jl](https - Metadata dicts need to be stripped, they are often too difficult to convert, and not needed on GPU. - As an example, DynamicGrids.jl uses `AbstractDimArray` for auxiliary model data that are passed into [KernelAbstractions.jl](https://github.com/JuliaGPU/KernelAbstractions.jl)/ [CUDA.jl](https://github.com/JuliaGPU/CUDA.jl) kernels. - - -Note: due to limitations of the machines available in our github actions CI, -we *do not* currently test on GPU. But we should. - -If anyone wants to set us up with CI that has a GPU, please make a PR! - -```julia -using DimensionalData, CUDA - -# Create a Float32 array to use on the GPU -A = rand(Float32, X(1.0:1000.0), Y(1.0:2000.0)) - -# Move the parent data to the GPU with `modify` and the `CuArray` constructor: -cuA = modify(CuArray, A) - -# Broadcast to a new GPU array: it will still be a DimArray! -cuA2 = cuA .* 2 -``` diff --git a/docs/src/dimensions.md b/docs/src/dimensions.md index c3b39eb62..1cf8695d8 100644 --- a/docs/src/dimensions.md +++ b/docs/src/dimensions.md @@ -25,18 +25,21 @@ using DimensionalData.Dimensions ## Examples -## Use in a `DimArray` - -We can use dimensions without a `LookupArray` to simply label the axis. +We can use dimensions to label array axes. A `DimArray` with labelled dimensions can be constructed by: ```@ansi dimensions using DimensionalData +A1 = DimArray(zeros(5, 10), (X, Y)) +``` +Or simply wrap the sizes with the dimensions: + +```@ansi dimensions A1 = zeros(X(5), Y(10)) ``` -And we can access a value with: +We can access a value with the same dimension wrappers: ```@ansi dimensions A1[Y(1), X(2)] @@ -53,7 +56,7 @@ by using `Symbol`s, and indexing with keywords: A2 = DimArray(rand(5, 5), (:a, :b)) ``` -and get a value: +and get a value, here another smaller `DimArray`: ```@ansi dimensions A2[a=3, b=1:3] @@ -65,8 +68,7 @@ Keywords also work with our first example: A1[X=3] ``` -The length of each dimension index has to match the size of the corresponding -array axis. +Here the missing Y was filled in for us. ## Dimensional Indexing @@ -77,38 +79,29 @@ order of our objects axes, or from even keeping it consistent. We can index in whatever order we want to. These are the same: ```@ansi dimensions -A1[X(2), Y(1)] -A1[Y(1), X(2)] +A1[X(2), Y(1)] == A1[Y(1), X(2)] ``` -We can Index with a single dimsions, and the remaining will be filled with colons: - -```@ansi dimensions -A1[Y(1:2:5)] -``` - -We can use Tuples of dimensions like `CartesianIndex`, but they don't have to -be in order or for consecutive axes. +We can use Tuples of dimensions like `CartesianIndex`, but they don't +have to be in order of consecutive axes. ```@ansi dimensions A3 = rand(X(10), Y(7), Z(5)) -# TODO not merged yet A3[(X(3), Z(5))] +A3[(X(3), Z(5))] ``` We can index with `Vector` of `Tuple{Vararg(Dimension}}` like vectors of -`CartesianIndex` +`CartesianIndex`. This will merge the dimensions in the tuples: ```@ansi dimensions -# TODO not merged yet A3[[(X(3), Z(5)), (X(7), Z(x)), (X(8), Z(2))]] -nothing # hide +A3[[(X(3), Z(5)), (X(7), Z(4)), (X(8), Z(2))]] ``` `DimIndices` can be used like `CartesianIndices` but again, without the constraint of consecutive dimensions or known order. ```@ansi dimensions -# TODO not merged yet A3[DimIndices(dims(A3, (X, Z))), Y(3)] -nothing # hide +A3[DimIndices(dims(A3, (X, Z))), Y(3)] ``` All of this indexing can be combined arbitrarily. @@ -118,10 +111,9 @@ and `:f`. Unlike base, we know that `:c` and `:f` are now related and merge the dimensions into a lookup of tuples: ```@ansi dimensions -A4 = DimArray(rand(10, 9, 8, 7, 6, 5), (:a, :b, :c, :d, :e, :f)) +A4 = DimArray(rand(10, 9, 8, 7), (:a, :b, :c, :d)) -# TODO not merged yet A4[e=6, DimIndices(dims(A4, (:d, :b))), a=3, collect(DimIndices(dims(A4, (:c, :f))))] -nothing # hide +A4[d=6, collect(DimIndices(dims(A4, (:b, :a)))), c=5] ``` The `Dimension` indexing layer sits on top of regular indexing and _can not_ be combined @@ -129,9 +121,10 @@ with it! Regular indexing specifies order, so doesn't mix well with our dimensio Mixing them will throw an error: -```@example dimensions -A1[X(3), 4] -# ERROR: ArgumentError: invalid index: X{Int64}(3) of type X{Int64} +```juliarepl +julia> A1[X(3), 4] +ERROR: ArgumentError: invalid index: X{Int64}(3) of type X{Int64} +... ``` ::: info Indexing @@ -153,11 +146,8 @@ A2 = ones(X(3), Y(3)) Lets benchmark it -```@example dimensions -using BenchmarkTools -``` - ```@ansi dimensions +using BenchmarkTools @benchmark $A2[X(1), Y(2)] ``` diff --git a/docs/src/diskarrays.md b/docs/src/diskarrays.md index 3fa62791a..7bab17a61 100644 --- a/docs/src/diskarrays.md +++ b/docs/src/diskarrays.md @@ -46,7 +46,7 @@ DimensionalData.jl and DiskArrays.jl play nice no matter the size of the data. To make this all work in CI we will simulate some huge data by multiplying a huge `BitArray` with a `BigInt`, meant to make it 128 x larger in memory. -```@example diskarray +```@ansi diskarray using DimensionalData, DiskArrays # This holds is a 100_100 * 50_000 `BitArray` @@ -56,7 +56,7 @@ dima = DimArray(diska, (X(0.01:0.01:1000), Y(0.02:0.02:1000))) ``` # How big is this thing? -```@example diskarray +```@ansi diskarray GB = sizeof(A) / 1e9 ``` @@ -65,13 +65,13 @@ Now if we multiply that by 2.0 they will be Float64, ie 64 x larger. But: -```@example diskarray +```@ansi diskarray dimb = view(permutedims(dima .* BigInt(200000000000), (X, Y)); X=1:99999) sizeof(dimb) ``` The size should be: -```@example diskarray +```@ansi diskarray GB = (sizeof(eltype(dimb)) * prod(size(dimb))) / 1e9 ``` @@ -79,7 +79,7 @@ I'm writing this on a laptop with only 32Gb or ram, but this runs instantly. The trick is nothing happens until we index: -```@example diskarray +```@ansi diskarray diska.getindex_count ``` @@ -88,7 +88,7 @@ These are just access for printing in the repl! When we actually get data the calulations happen, and for real disk arrays the chunked reads too: -```@example diskarray +```@ansi diskarray dimb[X=1:100, Y=1:10] ``` diff --git a/docs/src/ext_dd.md b/docs/src/ext_dd.md index 3d0284083..94c15dd4f 100644 --- a/docs/src/ext_dd.md +++ b/docs/src/ext_dd.md @@ -11,7 +11,7 @@ Nearly everything in DimensionalData.jl is designed to be extensible. its coordinate reference system, but otherwise behaves as a regular `Sampled` lookup. -`dims` and `rebuild` are the key interface methods in most of these cases. +`dims`, `rebuild` and `format` are the key interface methods in most of these cases. ## `dims` @@ -71,7 +71,7 @@ updating `data` and `dims`, any more that that is confusing. For `Dimension` and `Selector` the single argument versions are easiest to use, as there is only one argument. -## `rebuild(obj, ...)` argument table +### `rebuild(obj, ...)` argument table | Type | Keywords | Arguments | |------------------|------------------------------------------------------------ |----------------------| @@ -83,3 +83,22 @@ as there is only one argument. You can always add your ownd keywords to `rebuild` calls, but these will only work on your own objects or other objects with those fields. + + +## `format` + +When constructing an `AbstractDimArray` or `AbstractDimStack` +[`DimensionalData.format`](@ref) must be called on the `dims` tuple and the parent array: + +```julia +dims=`format(dims, array)` +``` + +This lets DimensionalData detect the lookup properties, fill in missing fields +of LookupArray, pass keywords from `Dimension` to detected `LookupArray`, and accept +a wider range of dimension inputs like tuples of `Symbol` and `Type`. + +Not calling `format` whille constructing an `AbstractDimArray` has +undefined behaviour. + + diff --git a/docs/src/groupby.md b/docs/src/groupby.md index 2ce43f00f..e99cc7e1e 100644 --- a/docs/src/groupby.md +++ b/docs/src/groupby.md @@ -1,14 +1,14 @@ -# `groupby` +# Group By DimensionalData.jl provides a `groupby` function for dimensional grouping. This guide will cover: - simple grouping with a function - grouping with `Bins` -- grouping with other existing `AbstractDimArry` or `Dimension` +- grouping with another existing `AbstractDimArry` or `Dimension` -# Basics: DateTime operations we can use for grouping +# Grouping functions Lets look at the kind of functions that can be used to group `DateTime`. Other types will follow the same principles, but are usually simpler. diff --git a/docs/src/selectors.md b/docs/src/selectors.md index 5e631904e..9a068ffdf 100644 --- a/docs/src/selectors.md +++ b/docs/src/selectors.md @@ -1,12 +1,22 @@ # Selectors and LookupArrays -http://localhost:5173/DimensionalData.jl/reference#lookuparrays +As well as choosing dimensions by name, we can also select values in them. -DimensionalData.jl [`Dimension`](@ref)s in an `AbstractDimArray` or -`AbstactDimStack` usually hold [`LookupArrays`](@ref). +First, we can create `DimArray` with lookup values as well as +dimension names: -These are `AbstractArray` with added features to facilitate fast and -accurate lookups of their values, using a [`Selector`](@ref) +````@ansi selectors +A = rand(X(1.0:0.1:2.0), Y([:a, :b, :c])) +```` + +Then we can use [`Selector`](@ref) to selctect +values from the array based on its lookup values: + +````@ansi selectors +A[X=Near(1.3), Y=At(:c)] +```` + +There are a range of selectors available: | Selector | Description | Indexing style | | :---------------------- | :--------------------------------------------------------------------------- |------------------ | @@ -24,10 +34,11 @@ Note: `At`, `Near` and `Contains` can wrap either single values or an `AbstractArray` of values, to select one index with an `Int` or multiple indices with a `Vector{Int}`. + Selectors find indices in the `LookupArray`, for each dimension. LookupArrays wrap other `AbstractArray` (often `AbstractRange`) but add aditional traits to facilitate fast lookups or specifing point or interval -behviour. +behviour. These are usually detected automatically. Some common `LookupArray` that are: @@ -38,3 +49,60 @@ Some common `LookupArray` that are: | [`Cyclic(x)`](@ref) | an `AbstractSampled` lookup for cyclical values. | | [`NoLookup(x)`](@ref) | no lookup values provided, so `Selector`s will not work. Not show in repl printing. | + + +````@ansi lookuparrays +using DimensionalData.LookupArrays +```` + +## `Cyclic` lookups + + +Create a `Cyclic` lookup that cycles over 12 months. + +````@ansi lookuparrays +lookup = Cyclic(DateTime(2000):Month(1):DateTime(2000, 12); cycle=Month(12), sampling=Intervals(Start())) +```` + +Make a `DimArray` by apply a funcion to the lookup + +````@ansi lookuparrays +A = DimArray(month, X(lookup)) +```` + +Now we can select any date and get the month: + +```@ansi lookups +A[At(DateTime(2005, 4))] +``` + + +# `DimSelector` + +We can also index with arrays of selectors [`DimSelectors`](@ref). +These are like `CartesianIndices` or [`DimIndices`](@ref) but holding +`Selectors` `At`, `Near` or `Contains`. + +````@ansi dimselectors +A = rand(X(1.0:0.1:2.0), Y(10:2:20)) +```` + +We can define another array with partly matching indices + +````@ansi dimselectors +B = rand(X(1.0:0.02:2.0), Y(20:-1:10)) +```` + +And we can simply select values from `B` with selectors from `A`: + +````@ansi dimselectors +B[DimSelectors(A)] +```` + +If the lookups aren't aligned we can use `Near` instead of `At`, +which like doing a nearest neighor interpolation: + +````@ansi dimselectors +C = rand(X(1.0:0.007:2.0), Y(10.0:0.9:30)) +C[DimSelectors(A; selectors=Near)] +```` diff --git a/docs/src/tables.md b/docs/src/tables.md index 6ebeba319..49b884f10 100644 --- a/docs/src/tables.md +++ b/docs/src/tables.md @@ -1,18 +1,62 @@ # Tables and DataFrames -Tables.jl provides an ecosystem-wide interface to tabular data in julia, -giving interop with DataFrames.jl, CSV.jl and hundreds of other packages -that implement the standard. +[Tables.jl](https://github.com/JuliaData/Tables.jl) provides an +ecosystem-wide interface to tabular data in julia, giving interop with +[DataFrames.jl](https://dataframes.juliadata.org/stable/), +[CSV.jl](https://csv.juliadata.org/stable/) and hundreds of other +packages that implement the standard. -DimensionalData.jl implements the Tables.jl interface for -`AbstractDimArray` and `AbstractDimStack`. `DimStack` layers +DimensionalData.jl implements the Tables.jl interface for +`AbstractDimArray` and `AbstractDimStack`. `DimStack` layers are unrolled so they are all the same size, and dimensions similarly loop over array strides to match the length of the largest layer. Columns are given the `name` or the array or the stack layer key. `Dimension` columns use the `Symbol` version (the result of `DD.dim2key(dimension)`). -Looping of unevenly size dimensions and layers is done _lazily_, +Looping of unevenly size dimensions and layers is done _lazily_, and does not allocate unless collected. +````@ansi dataframe +using DimensionalData, Dates, DataFrames +x = X(1:10) +y = Y(1:10) +c = Dim{:category}('a':'z') +c = Dim{:category}(1:25.0) +A = DimArray(rand(x, y, c); name=:data) +st = DimStack((data1 = rand(x, y), data2=rand(x, y, c))) +```` + +By default this stack will become a table with a column for each +dimension, and one for each layer: + +````@ansi dataframe +DataFrame(st) +```` + +Arrays behave the same way, but with only one data column +````@ansi +DataFrame(A) +```` + +We can also control how the table is created using [`DimTable`](@ref), +here we can merge the spatial dimensions so the column is a point: + +````@ansi dataframe +DataFrame(DimTable(st; mergedims=(:X, :Y)=>:XY)) +```` + +Or, for a `DimArray` we can take columns from one of the layers: + +````@ansi dataframe +DataFrame(DimTable(A; layersfrom=:category)) +```` + +We can also write arrays and stacks directly to CSV.jl, or +any other data type supporting the Tables.jl interface. + +````@ansi dataframe +using CSV +CSV.write("stack_datframe.csv", st) +```` diff --git a/src/Dimensions/primitives.jl b/src/Dimensions/primitives.jl index 184507dbc..dcab474db 100644 --- a/src/Dimensions/primitives.jl +++ b/src/Dimensions/primitives.jl @@ -61,8 +61,9 @@ All other `Dim{S}()` dimensions will generate `Symbol`s `S`. """ @inline dim2key(dims::Tuple) = map(dim2key, dims) @inline dim2key(dim::Dimension) = dim2key(typeof(dim)) -@inline dim2key(dim::Val{D}) where D <: Dimension = dim2key(D) +@inline dim2key(::Val{D}) where D <: Dimension = dim2key(D) @inline dim2key(dt::Type{<:Dimension}) = Symbol(Base.nameof(dt)) +@inline dim2key(s::Symbol) = s # dim2key is defined for concrete instances in dimensions.jl diff --git a/src/LookupArrays/lookup_arrays.jl b/src/LookupArrays/lookup_arrays.jl index e2fb5a462..458bd2e34 100644 --- a/src/LookupArrays/lookup_arrays.jl +++ b/src/LookupArrays/lookup_arrays.jl @@ -418,8 +418,9 @@ function Cyclic(data=AutoIndex(); Cyclic(data, order, span, sampling, metadata, cycle, cycle_status) end -_check_ordered_cyclic(order::Ordered) = nothing -_check_ordered_cyclic(order::Unordered) = throw(ArgumentError("Cyclic lookups must be `Ordered`")) +_check_ordered_cyclic(::AutoOrder) = nothing +_check_ordered_cyclic(::Ordered) = nothing +_check_ordered_cyclic(::Unordered) = throw(ArgumentError("Cyclic lookups must be `Ordered`")) function rebuild(l::Cyclic; data=parent(l), order=order(l), span=span(l), sampling=sampling(l), metadata=metadata(l), diff --git a/src/array/indexing.jl b/src/array/indexing.jl index 5b24dd382..ba34b0ae1 100644 --- a/src/array/indexing.jl +++ b/src/array/indexing.jl @@ -93,13 +93,13 @@ function merge_and_index(f, A, dims) # No arrays here, so abort (dispatch is tricky...) length(inds_arrays) == 0 && return f(A, dims...) - V = length(dims) > 0 ? view(A, dims...) : A + V1 = length(dims) > 0 ? view(A, dims...) : A # We have an array of dims of dim tuples - x = reduce(inds_arrays[1:end-1]; init=V) do A, i + V2 = reduce(inds_arrays[1:end-1]; init=V1) do A, i _merge_and_index(view, A, i) end - return _merge_and_index(f, x, inds_arrays[end]) + return _merge_and_index(f, V2, inds_arrays[end]) end function _merge_and_index(f, A, inds) @@ -118,8 +118,8 @@ function _merge_and_index(f, A, inds) f(M, basedims(M)...) end else - minds = CartesianIndex.(dims2indices.(Ref(A), inds)) - f(A, minds) + m_inds = CartesianIndex.(dims2indices.(Ref(A), inds)) + f(A, m_inds) end else d = first(dims_to_merge) diff --git a/src/groupby.jl b/src/groupby.jl index 0bbbd9400..b6a0a0420 100644 --- a/src/groupby.jl +++ b/src/groupby.jl @@ -86,8 +86,8 @@ abstract type AbstractBins <: Function end (bins::AbstractBins)(x) = bins.f(x) """ - Bins(f, bins; pad) - Bins(bins; pad) + Bins(f, bins; labels, pad) + Bins(bins; labels, pad) Specify bins to reduce groups after applying function `f`. @@ -135,7 +135,7 @@ Base.show(io::IO, bins::CyclicBins) = yearhour(x) = year(x), hour(x) -season(; start=January, kw...) = months(3; start, kw...) +season(; start=December, kw...) = months(3; start, kw...) months(step; start=January, labels=Dict(1:12 .=> monthabbr.(1:12))) = CyclicBins(month; cycle=12, step, start, labels) hours(step; start=0, labels=nothing) = CyclicBins(hour; cycle=24, step, start, labels) yearhours(step; start=0, labels=nothing) = CyclicBins(yearhour; cycle=24, step, start, labels) @@ -313,7 +313,7 @@ function _group_indices(dim::Dimension, group_lookup::LookupArray; labels=nothin end return group_dim, indices end -function _group_indices(dim::Dimension, bins::AbstractBins; labels=nothing) +function _group_indices(dim::Dimension, bins::AbstractBins; labels=bins.labels) l = lookup(dim) # Apply the function first unless its `identity` transformed = bins.f == identity ? parent(l) : map(bins.f, parent(l)) @@ -333,7 +333,7 @@ function _group_indices(dim::Dimension, bins::AbstractBins; labels=nothing) transformed_lookup = rebuild(dim, transformed) # Call the LookupArray version to do the work using selectors - return _group_indices(transformed_lookup, group_lookup) + return _group_indices(transformed_lookup, group_lookup; labels) end diff --git a/src/tables.jl b/src/tables.jl index cc336fd00..5470139a2 100644 --- a/src/tables.jl +++ b/src/tables.jl @@ -56,8 +56,8 @@ To get dimension columns, you can index with `Dimension` (`X()`) or `Dimension` type (`X`) as well as the regular `Int` or `Symbol`. # Keywords -* `mergedims`: Combine two or more dimensions into a new dimension. -* `layersfrom`: Treat a dimension of an `AbstractDimArray` as layers of an `AbstractDimStack`. +- `mergedims`: Combine two or more dimensions into a new dimension. +- `layersfrom`: Treat a dimension of an `AbstractDimArray` as layers of an `AbstractDimStack`. # Example ```jldoctest @@ -117,9 +117,14 @@ end function DimTable(x::AbstractDimArray; layersfrom=nothing, mergedims=nothing) if !isnothing(layersfrom) && any(hasdim(x, layersfrom)) - nlayers = size(x, layersfrom) - layers = [(@view x[layersfrom(i)]) for i in 1:nlayers] - layernames = Symbol.(["$(dim2key(layersfrom))_$i" for i in 1:nlayers]) + d = dims(x, layersfrom) + nlayers = size(x, d) + layers = [view(x, rebuild(d, i)) for i in 1:nlayers] + layernames = if iscategorical(d) + Symbol.(Ref(dim2key(d)), '_', lookup(d)) + else + Symbol.(("$(dim2key(d))_$i" for i in 1:nlayers)) + end return DimTable(layers..., layernames=layernames, mergedims=mergedims) else s = name(x) == NoName() ? DimStack((;value=x)) : DimStack(x) From 93d77e9cbc280764992ec4872d1edecee69cd37d Mon Sep 17 00:00:00 2001 From: Anshul Singhvi Date: Thu, 22 Feb 2024 16:17:25 -0500 Subject: [PATCH 042/108] Allow `Documenter.yml` to clean up its cache (#638) --- .github/workflows/Documenter.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/Documenter.yml b/.github/workflows/Documenter.yml index 386f5ea7e..31d69053d 100644 --- a/.github/workflows/Documenter.yml +++ b/.github/workflows/Documenter.yml @@ -19,6 +19,8 @@ permissions: contents: write pages: write id-token: write + actions: write + # Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. # However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. From e65dcf95db6f75b39f0e0d892ab7c68b240bcd94 Mon Sep 17 00:00:00 2001 From: Rafael Schouten Date: Thu, 22 Feb 2024 23:02:40 +0100 Subject: [PATCH 043/108] Docs again (#644) --- docs/Project.toml | 4 + docs/make.jl | 4 +- docs/src/.vitepress/config.mts | 35 +-- docs/src/api/reference.md | 3 +- docs/src/broadcast_dims.md | 51 ++++ docs/src/cuda.md | 4 +- docs/src/dimarrays.md | 191 +++++++++++++++ docs/src/dimensions.md | 207 ++-------------- docs/src/{ext_dd.md => extending_dd.md} | 31 +-- docs/src/get_info.md | 311 ++++++++++++++++++++++++ docs/src/groupby.md | 207 +++++++++++----- docs/src/integrations.md | 4 +- docs/src/lookup_customization.md | 4 - docs/src/object_modification.md | 189 ++++++++++++++ docs/src/plots.md | 12 +- docs/src/selectors.md | 219 ++++++++++++++--- docs/src/stacks.md | 55 ++++- docs/src/tables.md | 74 ++++-- src/LookupArrays/LookupArrays.jl | 2 +- src/array/show.jl | 2 +- src/utils.jl | 1 + 21 files changed, 1248 insertions(+), 362 deletions(-) create mode 100644 docs/src/broadcast_dims.md create mode 100644 docs/src/dimarrays.md rename docs/src/{ext_dd.md => extending_dd.md} (69%) create mode 100644 docs/src/get_info.md delete mode 100644 docs/src/lookup_customization.md create mode 100644 docs/src/object_modification.md diff --git a/docs/Project.toml b/docs/Project.toml index b570f61ce..3e735f561 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -1,12 +1,16 @@ [deps] BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf" +BetterFileWatching = "c9fd44ac-77b5-486c-9482-9798bd063cc6" +CSV = "336ed68f-0bac-5ca0-87d4-7b16caf5d00b" CairoMakie = "13f3f980-e62b-5c42-98c6-ff1f3baf88f0" Colors = "5ae59095-9a9b-59fe-a467-6f913c188581" +DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" Dates = "ade2ca70-3891-5945-98fb-dc099432e06a" DimensionalData = "0703355e-b756-11e9-17c0-8b28908087d0" DiskArrays = "3c3547ce-8d99-4f5e-a174-61eb10b00ae3" Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" DocumenterVitepress = "4710194d-e776-4893-9690-8d956a29c365" +FileWatching = "7b1f6079-737a-58dc-b8bc-7a2ca5c1b5ee" Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" Unitful = "1986cc42-f94f-5a68-af5c-568840ba703d" diff --git a/docs/make.jl b/docs/make.jl index aa11792f2..bc4424edd 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -36,7 +36,7 @@ folder = deploy_decision.subfolder println("Deploying to $folder") vitepress_config_file = joinpath(@__DIR__, "build", ".vitepress", "config.mts") config = read(vitepress_config_file, String) -new_config = replace(config, "base: 'REPLACE_ME_WITH_DOCUMENTER_VITEPRESS_BASE_URL_WITH_TRAILING_SLASH'" => "base: '/DimensionalData.jl/$folder/'") +new_config = replace(config, "base: 'REPLACE_ME_WITH_DOCUMENTER_VITEPRESS_BASE_URL_WITH_TRAILING_SLASH'" => "base: '/DimensionalData.jl/$folder'") write(vitepress_config_file, new_config) # Build the docs using `npm` - we are assuming it's installed here! @@ -51,4 +51,4 @@ deploydocs(; branch = "gh-pages", devbranch = "main", push_preview = true -) \ No newline at end of file +) diff --git a/docs/src/.vitepress/config.mts b/docs/src/.vitepress/config.mts index 8698a8fad..c7086ed1d 100644 --- a/docs/src/.vitepress/config.mts +++ b/docs/src/.vitepress/config.mts @@ -17,7 +17,7 @@ export default defineConfig({ lastUpdated: true, cleanUrls: true, ignoreDeadLinks: true, - + markdown: { config(md) { md.use(tabsMarkdownPlugin) @@ -40,15 +40,16 @@ export default defineConfig({ { text: 'Home', link: '/' }, { text: 'Getting Started', link: '/basics' }, { text: 'Dimensions', link: '/dimensions' }, + { text: 'DimArrays', link: '/dimarrays' }, { text: 'Selectors', link: '/selectors' }, { text: 'Integrations', items: [ - { text: 'Integrations', link: '/integrations' }, - { text: 'Tables and DataFrames', link: '/tables' }, { text: 'Plots and Makie', link: '/plots' }, + { text: 'Tables and DataFrames', link: '/tables' }, { text: 'CUDA and GPUs', link: '/cuda' }, { text: 'DiskArrays', link: '/diskarrays' }, - { text: 'Extending DimensionalData', link: '/ext_dd' }, + { text: 'Ecosystem', link: '/integrations' }, + { text: 'Extending DimensionalData', link: '/extending_dd' }, ], }, { @@ -64,17 +65,23 @@ export default defineConfig({ { text: 'Getting Started', link: '/basics' }, { text: 'Dimensions', link: '/dimensions' }, { text: 'Selectors', link: '/selectors' }, + { text: 'Dimarrays', link: '/dimarrays' }, + { text: 'DimStacks', link: '/stacks' }, { text: 'GroupBy', link: '/groupby' }, - { text: 'Stacks', link: '/stacks' }, - { text: 'Lookup customazation', link: '/lookup_customization' }, - { text: 'Tables and DataFrames', link: '/tables' }, - { text: 'Plots and Makie', link: '/plots' }, - { text: 'CUDA and GPUs', link: '/cuda' }, - { text: 'DiskArrays', link: '/diskarrays' }, - { text: 'Extending DimensionalData', link: '/ext_dd' }, - { text: 'API Reference', + { text: 'Getting information', link: '/get_info' }, + { text: 'Object modification', link: '/object_modification' }, + { text: 'Integrations', + items: [ + { text: 'Plots and Makie', link: '/plots' }, + { text: 'Tables and DataFrames', link: '/tables' }, + { text: 'CUDA and GPUs', link: '/cuda' }, + { text: 'DiskArrays', link: '/diskarrays' }, + { text: 'Ecosystem', link: '/integrations' }, + { text: 'Extending DimensionalData', link: '/extending_dd' }, + ], + }, + { text: 'API Reference', link: '/api/reference', items: [ - { text: 'General Reference', link: '/api/reference' }, { text: 'Dimensions Reference', link: '/api/dimensions' }, { text: 'LookupArrays Reference', link: '/api/lookuparrays' }, ], @@ -85,7 +92,7 @@ export default defineConfig({ socialLinks: [ { icon: 'github', link: 'https://github.com/rafaqz/DimensionalData.jl' }, - + ], footer: { message: 'Made with DocumenterVitepress.jl by Lazaro Alonso
', diff --git a/docs/src/api/reference.md b/docs/src/api/reference.md index b5dcefbd5..ee4871334 100644 --- a/docs/src/api/reference.md +++ b/docs/src/api/reference.md @@ -37,7 +37,7 @@ DimStack ```@docs DimIndices -DimKeys +DimSelectors DimPoints ``` @@ -73,7 +73,6 @@ Base.copy! Base.eachslice ``` - Most base methods work as expected, using `Dimension` wherever a `dims` keyword is used. They are not allspecifically documented here. diff --git a/docs/src/broadcast_dims.md b/docs/src/broadcast_dims.md new file mode 100644 index 000000000..ca8120427 --- /dev/null +++ b/docs/src/broadcast_dims.md @@ -0,0 +1,51 @@ +# `broadcast_dims` and `broadcast_dims!` + +[`broadcast_dims`](@ref) is like Base julia `broadcast` on dimensional steroids. +Because we know the names of the dimensions, there is ambiguity in which +one we mean, and we can permuted and reshape them so that broadcasta that +would fail with a regular `Array` just work with a `DimArray`. As an added +bonus, `broadcast_dims` even works on `DimStack`s. + +````@ansi bd +using DimensionalData +x, y, t = X(1:100), Y(1:25), Ti(DateTime(2000):Month(1):DateTime(2000, 12)) + +month_scalars = DimArray(month, t) +data = rand(x, y, t) +```` + +A regular broadcast fails: + +````@ansi bd +data .* month_scalars +```` + +But `broadcast_dims` knows to broadcast over the `Ti` dimension: + +````@ansi bd +scaled = broadcast_dims(*, data, month_scalars) +```` + +We can see the means of each month are scaled by the broadcast : + +````@ansi bd +mean(eachslice(data; dims=(X, Y))) +mean(eachslice(scaled; dims=(X, Y))) +```` + +````@ansi bd +data .* month_scalars +```` + +But `broadcast_dims` knows to broadcast over the `Ti` dimension: + +````@ansi bd +scaled = broadcast_dims(*, data, month_scalars) +```` + +We can see the means of each month are scaled by the broadcast : + +````@ansi bd +mean(eachslice(data; dims=(X, Y))) +mean(eachslice(scaled; dims=(X, Y))) +```` diff --git a/docs/src/cuda.md b/docs/src/cuda.md index 5971a0bb1..f417cb2cd 100644 --- a/docs/src/cuda.md +++ b/docs/src/cuda.md @@ -13,7 +13,7 @@ A = rand(Float32, X(1.0:1000.0), Y(1.0:2000.0)) cuA = modify(CuArray, A) ``` -The result of a broadcast is still be a DimArray: +The result of a GPU broadcast is still a DimArray: ```julia julia> cuA2 = cuA .* 2 @@ -66,6 +66,6 @@ fields converted to GPU friendly forms whenever required, using [Adapt.jl](https - Metadata dicts need to be stripped, they are often too difficult to convert, and not needed on GPU. -As an example, DynamicGrids.jl uses `AbstractDimArray` for auxiliary +As an example, [DynamicGrids.jl](https://github.com/cesaraustralia/DynamicGrids.jl) uses `AbstractDimArray` for auxiliary model data that are passed into [KernelAbstractions.jl](https://github.com/JuliaGPU/KernelAbstractions.jl)/ [CUDA.jl](https://github.com/JuliaGPU/CUDA.jl) kernels. diff --git a/docs/src/dimarrays.md b/docs/src/dimarrays.md new file mode 100644 index 000000000..64dba61a7 --- /dev/null +++ b/docs/src/dimarrays.md @@ -0,0 +1,191 @@ +# AbstracDimArray + +`DimArray`s are wrappers for other kinds of `AbstractArray` that +add named dimension lookups. + + +Here we define a `Matrix` of `Float64`, and give it `X` and `Y` dimensions + +```@ansi dimarray +using DimensionalData +A = zeros(5, 10) +da = DimArray(A, (X, Y)) +``` + +We can access a value with the same dimension wrappers: + +```@ansi dimensions +A1[Y(1), X(2)] +``` + +There are shortcuts for creating `DimArray`: + +::::tabs + +== zeros + +```@ansi dimensions +zeros(X(5), Y(10)) +``` + +== ones + +```@ansi dimensions +ones(X(5), Y(10)) +``` + +== rand + +```@ansi dimensions +rand(X(5), Y(10)) +``` + +== fill + +```@ansi dimensions +fill(7, X(5), Y(10)) +``` + +:::: + +For arbitrary names, we can use the `Dim{:name}` dims +by using `Symbol`s, and indexing with keywords: + +```@ansi dimensions +A2 = DimArray(rand(5, 5), (:a, :b)) +``` + +and get a value, here another smaller `DimArray`: + +```@ansi dimensions +A2[a=3, b=1:3] +``` + +## Dimensional Indexing + +When used for indexing, dimension wrappers free us from knowing the +order of our objects axes. These are the same: + +```@ansi dimensions +A1[X(2), Y(1)] == A1[Y(1), X(2)] +``` + +We also can use Tuples of dimensions like `CartesianIndex`, +but they don't have to be in order of consecutive axes. + +```@ansi dimensions +A3 = rand(X(10), Y(7), Z(5)) +A3[(X(3), Z(5))] +``` + +We can index with `Vector` of `Tuple{Vararg(Dimension}}` like vectors of +`CartesianIndex`. This will merge the dimensions in the tuples: + +```@ansi dimensions +A3[[(X(3), Z(5)), (X(7), Z(4)), (X(8), Z(2))]] +``` + +`DimIndices` can be used like `CartesianIndices` but again, without the +constraint of consecutive dimensions or known order. + +```@ansi dimensions +A3[DimIndices(dims(A3, (X, Z))), Y(3)] +``` + +The `Dimension` indexing layer sits on top of regular indexing and _can not_ be combined +with it! Regular indexing specifies order, so doesn't mix well with our dimensions. + +Mixing them will throw an error: + +```julia +julia> A1[X(3), 4] +ERROR: ArgumentError: invalid index: X{Int64}(3) of type X{Int64} +... +``` + +::: info Indexing + +Indexing `AbstractDimArray`s works with `getindex`, `setindex!` and +`view`. The result is still an `AbstracDimArray`, unless using all single +`Int` or `Selector`s that resolve to `Int` inside `Dimension`. + +::: + + +## Indexing Performance + +Indexing with `Dimension`s has no runtime cost: + +```@ansi dimensions +A2 = ones(X(3), Y(3)) +``` + +Lets benchmark it + +```@ansi dimensions +using BenchmarkTools +@benchmark $A2[X(1), Y(2)] +``` + +the same as accessing the parent array directly: + +```@ansi dimensions +@benchmark parent($A2)[1, 2] +``` + + +## `dims` keywords + +In many Julia functions like, `size` or `sum`, you can specify the dimension +along which to perform the operation as an `Int`. It is also possible to do this +using [`Dimension`](@ref) types with `AbstractDimArray`: + +````@ansi dimensions +A3 = rand(X(3), Y(4), Ti(5)) +sum(A3; dims=Ti) +```` + +This also works in methods from `Statistics`: + +````@example dimensions +using Statistics +```` + +````@ansi dimensions +mean(A3; dims=Ti) +```` + +This can be especially useful when you are working with multiple objects. +Here we take the mean of A3 over all dimensions _not in_ A2, using `otherdims`. + +In this case, thats the `Z` dimension. But we don't need to know it the Z +dimension, some other dimensions, or even if it has extra dimensions at all! + +This will work either way, leaveing us with the same dims as A1: + +````@ansi dimensions +d = otherdims(A3, dims(A1)) +dropdims(mean(A3; dims=d); dims=d) +```` + +::: info Dims keywords + +Methods where dims, dim types, or `Symbol`s can be used to indicate the array dimension: + +- `size`, `axes`, `firstindex`, `lastindex` +- `cat`, `reverse`, `dropdims` +- `reduce`, `mapreduce` +- `sum`, `prod`, `maximum`, `minimum` +- `mean`, `median`, `extrema`, `std`, `var`, `cor`, `cov` +- `permutedims`, `adjoint`, `transpose`, `Transpose` +- `mapslices`, `eachslice` + +::: + + +## DimIndices +## Vectors of Dimensions + +## How to name dimensions? +## How to name an array? +## Adding metadata diff --git a/docs/src/dimensions.md b/docs/src/dimensions.md index 1cf8695d8..f037e7a29 100644 --- a/docs/src/dimensions.md +++ b/docs/src/dimensions.md @@ -1,11 +1,21 @@ # Dimensions Dimensions are "wrapper types" that can be used to wrap any -object to associate it with a named dimension. +object to associate it with a named dimension. -The abstract supertype is [`Dimension`](@ref), and the types -that inherit from it aare `Ti`, `X`, `Y`, `Z`, the generic `Dim{:x}`, -or others that you define manually using the [`@dim`](@ref) macro. +`X`, `Y`, `Z`, `Ti` are predefined as types + +```@ansi dimensions +using DimensionalData +X(1) +X(1), Y(2), Z(3) +``` + +You can also make [`Dim`](@ref) dimensions with any name: + +```@ansi dimensions +Dim{:a}(1), Dim{:b}(1) +``` DimensionalData.jl uses `Dimensions` pretty much everywhere: @@ -23,193 +33,4 @@ Dimension-specific methods can be brought into scope with: using DimensionalData.Dimensions ``` -## Examples - -We can use dimensions to label array axes. -A `DimArray` with labelled dimensions can be constructed by: - -```@ansi dimensions -using DimensionalData -A1 = DimArray(zeros(5, 10), (X, Y)) -``` - -Or simply wrap the sizes with the dimensions: - -```@ansi dimensions -A1 = zeros(X(5), Y(10)) -``` - -We can access a value with the same dimension wrappers: - -```@ansi dimensions -A1[Y(1), X(2)] -``` - -As shown above, `Dimension`s can be used to construct arrays in `rand`, `zeros`, -`ones` and `fill`, with either a range for a lookup index or a number for the -dimension length. - -For completely arbitrary names, we can use the `Dim{:name}` dims -by using `Symbol`s, and indexing with keywords: - -```@ansi dimensions -A2 = DimArray(rand(5, 5), (:a, :b)) -``` - -and get a value, here another smaller `DimArray`: - -```@ansi dimensions -A2[a=3, b=1:3] -``` - -Keywords also work with our first example: - -```@ansi dimensions -A1[X=3] -``` - -Here the missing Y was filled in for us. - - -## Dimensional Indexing - -When used in indexing, dimension wrappers free us from knowing the -order of our objects axes, or from even keeping it consistent. - -We can index in whatever order we want to. These are the same: - -```@ansi dimensions -A1[X(2), Y(1)] == A1[Y(1), X(2)] -``` - -We can use Tuples of dimensions like `CartesianIndex`, but they don't -have to be in order of consecutive axes. - -```@ansi dimensions -A3 = rand(X(10), Y(7), Z(5)) -A3[(X(3), Z(5))] -``` - -We can index with `Vector` of `Tuple{Vararg(Dimension}}` like vectors of -`CartesianIndex`. This will merge the dimensions in the tuples: - -```@ansi dimensions -A3[[(X(3), Z(5)), (X(7), Z(4)), (X(8), Z(2))]] -``` - -`DimIndices` can be used like `CartesianIndices` but again, without the -constraint of consecutive dimensions or known order. - -```@ansi dimensions -A3[DimIndices(dims(A3, (X, Z))), Y(3)] -``` - -All of this indexing can be combined arbitrarily. - -This will regurn values for `:e` at 6, `:a` at 3, all of `:d` an `:b`, and a vector of `:c` -and `:f`. Unlike base, we know that `:c` and `:f` are now related and merge the `:c` and `:f` -dimensions into a lookup of tuples: - -```@ansi dimensions -A4 = DimArray(rand(10, 9, 8, 7), (:a, :b, :c, :d)) - -A4[d=6, collect(DimIndices(dims(A4, (:b, :a)))), c=5] -``` - -The `Dimension` indexing layer sits on top of regular indexing and _can not_ be combined -with it! Regular indexing specifies order, so doesn't mix well with our dimensions. - -Mixing them will throw an error: - -```juliarepl -julia> A1[X(3), 4] -ERROR: ArgumentError: invalid index: X{Int64}(3) of type X{Int64} -... -``` - -::: info Indexing - -Indexing `AbstractDimArray`s works with `getindex`, `setindex!` and -`view`. The result is still an `AbstracDimArray`, unless using all single -`Int` or `Selector`s that resolve to `Int` inside `Dimension`. - -::: - - -## Indexing Performance - -Indexing with `Dimension`s has no runtime cost: - -```@ansi dimensions -A2 = ones(X(3), Y(3)) -``` - -Lets benchmark it - -```@ansi dimensions -using BenchmarkTools -@benchmark $A2[X(1), Y(2)] -``` - -the same as accessing the parent array directly: - -```@ansi dimensions -@benchmark parent($A2)[1, 2] -``` - - -## `dims` keywords - -In many Julia functions like, `size` or `sum`, you can specify the dimension -along which to perform the operation as an `Int`. It is also possible to do this -using [`Dimension`](@ref) types with `AbstractDimArray`: - -````@ansi dimensions -A3 = rand(X(3), Y(4), Ti(5)) -sum(A3; dims=Ti) -```` - -This also works in methods from `Statistics`: - -````@example dimensions -using Statistics -```` - -````@ansi dimensions -mean(A3; dims=Ti) -```` - -This can be especially useful when you are working with multiple objects. -Here we take the mean of A3 over all dimensions _not in_ A2, using `otherdims`. - -In this case, thats the `Z` dimension. But we don't need to know it the Z -dimension, some other dimensions, or even if it has extra dimensions at all! - -This will work either way, leaveing us with the same dims as A1: - -````@ansi dimensions -d = otherdims(A3, dims(A1)) -dropdims(mean(A3; dims=d); dims=d) -```` - -::: info Dims keywords - -Methods where dims, dim types, or `Symbol`s can be used to indicate the array dimension: - -- `size`, `axes`, `firstindex`, `lastindex` -- `cat`, `reverse`, `dropdims` -- `reduce`, `mapreduce` -- `sum`, `prod`, `maximum`, `minimum` -- `mean`, `median`, `extrema`, `std`, `var`, `cor`, `cov` -- `permutedims`, `adjoint`, `transpose`, `Transpose` -- `mapslices`, `eachslice` - -::: - - -## DimIndices -## Vectors of Dimensions -## How to name dimensions? -## How to name an array? -## Adding metadata diff --git a/docs/src/ext_dd.md b/docs/src/extending_dd.md similarity index 69% rename from docs/src/ext_dd.md rename to docs/src/extending_dd.md index 94c15dd4f..23c7a76ba 100644 --- a/docs/src/ext_dd.md +++ b/docs/src/extending_dd.md @@ -31,9 +31,10 @@ wraps must also have OffsetArrays.jl axes. ### `dims` keywords -To any `dims` keyword argument that only marks the dimension name, +To any `dims` keyword argument that usually requires the dimension I, objects should accept any `Dimension`, `Type{<:Dimension}`, `Symbol`, -`Val{:Symbol}`, `Val{<:Type{<:Dimension}}` or regular `Integer`. +`Val{:Symbol}`, `Val{<:Type{<:Dimension}}` or also regular `Integer`. + This is easier than it sounds, calling `DD.dims(objs, dims)` will return the matching dimension and `DD.dimnum(obj, dims)` will return the matching `Int` for any of these inputs as long as `dims(obj)` is @@ -71,19 +72,6 @@ updating `data` and `dims`, any more that that is confusing. For `Dimension` and `Selector` the single argument versions are easiest to use, as there is only one argument. -### `rebuild(obj, ...)` argument table - -| Type | Keywords | Arguments | -|------------------|------------------------------------------------------------ |----------------------| -| AbstractDimArray | data, dims, [refdims, name, metadata] | as with kw, in order | -| AbstractDimStack | data, dims, [refdims], layerdims, [metadata, layermetadata] | as with kw, in order | -| Dimension | val | val | -| Selector | val, [atol] | val | -| LookupArray | data, [order, span, sampling, metadata] | keywords only | - -You can always add your ownd keywords to `rebuild` calls, but these will only -work on your own objects or other objects with those fields. - ## `format` @@ -91,14 +79,13 @@ When constructing an `AbstractDimArray` or `AbstractDimStack` [`DimensionalData.format`](@ref) must be called on the `dims` tuple and the parent array: ```julia -dims=`format(dims, array)` +format(dims, array) ``` This lets DimensionalData detect the lookup properties, fill in missing fields -of LookupArray, pass keywords from `Dimension` to detected `LookupArray`, and accept -a wider range of dimension inputs like tuples of `Symbol` and `Type`. - -Not calling `format` whille constructing an `AbstractDimArray` has -undefined behaviour. - +of a `LookupArray`, pass keywords from `Dimension` to detected `LookupArray` +constructors, and accept a wider range of dimension inputs like tuples of `Symbol` +and `Type`. +Not calling `format` in the outer constructors of an `AbstractDimArray` +has undefined behaviour. diff --git a/docs/src/get_info.md b/docs/src/get_info.md new file mode 100644 index 000000000..31d9e1b97 --- /dev/null +++ b/docs/src/get_info.md @@ -0,0 +1,311 @@ +# Getters + +DimensionalData.jl defines consistent methods to retreive information +from objects like `DimArray`, `DimStack`, `Tuple`s of `Dimension` +`Dimension` and `LookupArray`. + +First we will define an example `DimArray`. + +```@example getters +using DimensionalData +using DimensionalData.LookupArrays +x, y = X(10:-1:1), Y(100.0:10:200.0) +A = rand(x, y) +``` + +::: tabs + +== dims + +`dims` retreives dimensions from any object that has them. + +What makes it so useful is you can filter which dimensions +you want in what order, using any `Dimension`, `Type{Dimension}` +or `Symbol`. + +```@ansi getters +dims(A) +dims(A, Y) +dims(A, Y()) +dims(A, :Y) +dims(A, (X,)) +dims(A, (Y, X)) +dims(A, reverse(dims(A))) +dims(A, isregular) +``` + +== otherdims + +`otherdims` is just like `dims` but returns whatever +`dims` would _not_ return from the same query. + +```@ansi getters +otherdims(A, Y) +otherdims(A, Y()) +otherdims(A, :Y) +otherdims(A, (X,)) +otherdims(A, (Y, X)) +otherdims(A, dims(A)) +otherdims(A, isregular) +``` + +== lookup + +Get all the `LookupArray` in an object + +```@ansi getters +lookup(A) +lookup(dims(A)) +lookup(A, X) +lookup(dims(A, Y)) +``` + +== val + +Val is used where there is an unambiguous single value: + +```@ansi getters +val(X(7)) +val(At(10.5)) +``` + +== order + +Get the order of a `LookupArray`, or a `Tuple` +from a `DimArray` or `DimTuple`. + +```@ansi getters +order(A) +order(dims(A)) +order(A, X) +order(lookup(A, Y)) +``` + +== sampling + +Get the sampling of a `LookupArray`, or a `Tuple` +from a `DimArray` or `DimTuple`. + +```@ansi getters +sampling(A) +sampling(dims(A)) +sampling(A, X) +sampling(lookup(A, Y)) +``` + +== span + +Get the span of a `LookupArray`, or a `Tuple` +from a `DimArray` or `DimTuple`. + +```@ansi getters +span(A) +span(dims(A)) +span(A, X) +span(lookup(A, Y)) +``` + +== locus + +Get the locus of a `LookupArray`, or a `Tuple` +from a `DimArray` or `DimTuple`. + +(locus is our term for distiguishing if an lookup value +specifies the start, center or end of an interval) + +```@ansi getters +locus(A) +locus(dims(A)) +locus(A, X) +locus(lookup(A, Y)) +``` + +== bounds + +Get the bounds of each dimension. This is different for `Points` +and `Intervals` - the bounds for points of a `LookupArray` are +simply `(first(l), last(l))`. + +```@ansi getters +bounds(A) +bounds(dims(A)) +bounds(A, X) +bounds(lookup(A, Y)) +``` + +== intervalbounds + +Get the bounds of each interval along a dimension. + +```@ansi getters +intervalbounds(A) +intervalbounds(dims(A)) +intervalbounds(A, X) +intervalbounds(lookup(A, Y)) +``` + +== extent + +[Extents.jl](https://github.com/rafaqz/Extent) provides an `Extent` +object that combines the names of dimensions with their bounds. + +```@ansi getters +using Extents +extent(A) +extent(A, X) +extent(dims(A)) +extent(dims(A, Y)) +``` + +::: + + +# Predicates + +These always return `true` or `false`. With multiple +dimensions, `fale` means `!all` and `true` means `all`. + +`dims` and all other methods listed above can use predicates +to filter the returned dimensions. + +::: tabs + +== issampled + +```@ansi getters +issampled(A) +issampled(dims(A)) +issampled(A, Y) +issampled(lookup(A, Y)) +dims(A, issampled) +otherdims(A, issampled) +lookup(A, issampled) +``` + +== iscategorical + +```@ansi getters +iscategorical(A) +iscategorical(dims(A)) +iscategorical(dims(A, Y)) +iscategorical(lookup(A, Y)) +dims(A, iscategorical) +otherdims(A, iscategorical) +lookup(A, iscategorical) +``` + +== iscyclic + +```@ansi getters +iscyclic(A) +iscyclic(dims(A)) +iscyclic(dims(A, Y)) +iscyclic(lookup(A, Y)) +dims(A, iscyclic) +otherdims(A, iscyclic) +``` + +== isordered + +```@ansi getters +isordered(A) +isordered(dims(A)) +isordered(A, X) +isordered(lookup(A, Y)) +dims(A, isordered) +otherdims(A, isordered) +``` + +== isforward + +```@ansi getters +isforward(A) +isforward(dims(A)) +isforward(A, X) +dims(A, isforward) +otherdims(A, isforward) +``` + +== isreverse + +```@ansi getters +isreverse(A) +isreverse(dims(A)) +isreverse(A, X) +dims(A, isreverse) +otherdims(A, isreverse) +``` + +== isintervals + +```@ansi getters +isintervals(A) +isintervals(dims(A)) +isintervals(A, X) +isintervals(lookup(A, Y)) +dims(A, isintervals) +otherdims(A, isintervals) +``` + +== ispoints + +```@ansi getters +ispoints(A) +ispoints(dims(A)) +ispoints(A, X) +ispoints(lookup(A, Y)) +dims(A, ispoints) +otherdims(A, ispoints) +``` + +== isregular + +```@ansi getters +isregular(A) +isregular(dims(A)) +isregular(A, X) +dims(A, isregular) +otherdims(A, isregular) +``` + +== isexplicit + +```@ansi getters +isexplicit(A) +isexplicit(dims(A)) +isexplicit(A, X) +dims(A, isexplicit) +otherdims(A, isexplicit) +``` + +== isstart + +```@ansi getters +isstart(A) +isstart(dims(A)) +isstart(A, X) +dims(A, isstart) +otherdims(A, isstart) +``` + +== iscenter + +```@ansi getters +iscenter(A) +iscenter(dims(A)) +iscenter(A, X) +dims(A, iscenter) +otherdims(A, iscenter) +``` + +== isend + +```@ansi getters +isend(A) +isend(dims(A)) +isend(A, X) +dims(A, isend) +otherdims(A, isend) +``` + +::: diff --git a/docs/src/groupby.md b/docs/src/groupby.md index e99cc7e1e..f0abf37dd 100644 --- a/docs/src/groupby.md +++ b/docs/src/groupby.md @@ -8,7 +8,7 @@ grouping. This guide will cover: - grouping with another existing `AbstractDimArry` or `Dimension` -# Grouping functions +## Grouping functions Lets look at the kind of functions that can be used to group `DateTime`. Other types will follow the same principles, but are usually simpler. @@ -32,43 +32,59 @@ Lets see how some common functions work. The `hour` function will transform values to hour of the day - the integers `0:23` -## hour +:::tabs + +== hour ````@ansi groupby hour.(tempo) ```` -These do similar things with other time periods - -## dayofweek +== day ````@ansi groupby -dayofweek.(tempo) +day.(tempo) ```` -## month +== month ````@ansi groupby month.(tempo) ```` -## dayofyear +== dayofweek + +````@ansi groupby +dayofweek.(tempo) +```` + +== dayofyear ````@ansi groupby dayofyear.(tempo) ```` -## Tuple grouping +::: -Some functions return a tuple - we can also use tuples for grouping. -They are sorted by the left to right values. -## yearmonth +Tuple groupings + +::: tabs + +== yearmonth ````@ansi groupby yearmonth.(tempo) ```` +== yearmonthday + +````@ansi groupby +yearmonthday.(tempo) +```` + +== custom + We can create our own anonymous function that return tuples ````@example groupby @@ -78,142 +94,217 @@ yearhour(x) = year(x), hour(x) And you can probably guess what they do: +````@ansi groupby +yearhour.(tempo) +```` + +== yearday + ````@ansi groupby yearday.(tempo) ```` -All of these functions can be used in `groupby` on `DateTime` objects. +::: -# Grouping and reducing +## Grouping and reducing -Now lets define an array +Lets define an array with a time dimension of the times used above: ````@ansi groupby A = rand(X(1:0.01:2), Ti(tempo)) ```` -Simple groupbys using the functions from above +::: tabs + +== basic + +And group it by month: ````@ansi groupby -group = groupby(A, Ti=>month) +groups = groupby(A, Ti=>month) ```` -We take the mean of each group by broadcasting over the group +We take the mean of each group by broadcasting over them : ````@ansi groupby -mean.(group) +mean.(groups) ```` -Here are some more examples +== sum dayofyear ````@ansi groupby -sum.(groupby(A, Ti=>dayofyear)) # it will combine the same day from different year. +sum.(groupby(A, Ti=>dayofyear)) ```` +== maximum yearmonthday + ````@ansi groupby -maximum.(groupby(A, Ti=>yearmonthday)) # this does the a daily mean aggregation. +maximum.(groupby(A, Ti=>yearmonthday)) ```` +== minimum yearmonth ````@ansi groupby -minimum.(groupby(A, Ti=>yearmonth)) # this does a monthly mean aggregation +minimum.(groupby(A, Ti=>yearmonth)) ```` +== median hour + ````@ansi groupby -median.(groupby(A, Ti=>Dates.hour12)) +median.(groupby(A, Ti=>hour)) ```` We can also use the function we defined above +== mean yearday + ````@ansi groupby -mean.(groupby(A, Ti=>yearday)) # this does a daily mean aggregation +mean.(groupby(A, Ti=>yearday)) ```` -# Binning +::: + +## Binning Sometimes we want to further aggregate our groups after running a function, -or just bin the raw data directly. We can use the [`Bins`](@ref) wrapper to +or just bin the raw data directly. We can use the [`Bins`](@ref) wrapper to do this. -When our function returns an `Int`, we can just use a range of values we want to keep: +::: tabs + +== evenly spaced + +For quick analysis, we can break our groups into `N` bins. ````@ansi groupby -mean.(groupby(A, Ti=>Bins(month, 1:2))) +groupby(A, Ti=>Bins(month, 4)) ```` -````@ansi groupby -mean.(groupby(A, Ti=>Bins(month, [1, 3, 5]))) +Doing this requires slighly padding the bin edges, so the lookup +of the output is less than ideal. + +== specific values as bins + +When our function returns an `Int`, we can use a range of values we want to keep: + + ````@ansi groupby +mean.(groupby(A, Ti=>Bins(month, 1:2))) ```` -Or an array of arrays +== selected month bins ````@ansi groupby -mean.(groupby(A, Ti => Bins(yearday, [[1,2,3], [4,5,6]], labels=x -> join(string.(x), ',')))) +mean.(groupby(A, Ti=>Bins(month, [1, 3, 5]))) ```` -The `ranges` function is a helper for creating these bin groupings +== bin groups + +We can also specify an `AbstractArray` of grouping `AbstractArray`: +Her we group by month, and bin the summer and winter months: ````@ansi groupby -ranges(1:8:370) +groupby(A, Ti => Bins(month, [[12, 1, 2], [6, 7, 8]]; labels=x -> string.(x))) ```` +== range bins + +First, lets see what [`ranges`](@ref) does: + ````@ansi groupby -mean.(groupby(A, Ti => Bins(dayofyear, ranges(1:8:370)))) +ranges(1:8:370) ```` +We can use this vector of ranges to group into blocks, here 8 days : + ````@ansi groupby -mean.(groupby(A, Ti => season(; start=December))) +groupby(A, Ti => Bins(dayofyear, ranges(1:8:370))) ```` +Note: this only works where our function `dayofyear` returns +values exactly `in` the ranges. `7.5` would not be included! + +== intervals bins + +Intervals is like ranges, but for taking all values in +an interval, not just discrete `Integer`s. + +`intervals` returns closed-open `IntervalSets.Interval`: + ````@ansi groupby -mean.(groupby(A, Ti => hours(12; start=6, labels=x -> 6 in x ? :night : :day))) +intervals(1:0.3:2) ```` -## select by month, days, years and seasons +We can use this to bin the `Float64` values on the `X` axis: -How do we select month 1 or 2, and even a group of them, i.e. [1,3,5]? Same for days, years and seasons. +````@ansi groupby +groups = groupby(A, X => Bins(intervals(1:0.3:2))) +```` -Use three-month bins. The 13 is the open side of the last interval. +The lookup values of our final array are now `IntervalSets.Interval`: ````@ansi groupby -mean.(groupby(A, Ti=>Bins(yearmonth, intervals(1:3:12)))) +mean.(groups) ```` +== seasons + +There is a helper function for grouping by three-month seasons and getting +nice keys for them: `season`. Note you have to call it, not just pass it! + ````@ansi groupby -mean.(groupby(A, Ti=>Bins(month, 4))) # is combining month from different years +groupby(A, Ti => season()) ```` -# Select by [`Dimension`](@ref) +We could also start our seasons in January: ````@ansi groupby -A -B = -A[:, 1:3:100] -C = mean.(groupby(A, B)) -@assert size(A) == size(B) +groupby(A, Ti => season(; start=January)) ```` -How do could we incorporate resample? Let's say if we have hour resolution I want to resample every 3,6,12.. hours? +== months + +We can also use `months` to group into arbitrary +group sizes, starting wherever we like: ````@ansi groupby -mean.(groupby(A, Ti=>Bins(yearhour, intervals(1:3:24)))) # it will combine the same day from different year. +groupby(A, Ti => months(2; start=6)) ```` +== hours + +`hours` works a lot like `months`. Here we groupb into day +and night - two 12 hour blocks starting at 6am: + ````@ansi groupby -mean.(groupby(A, Ti=>Bins(yearhour, 12))) # this does a daily mean aggregation +groupby(A, Ti => hours(12; start=6, labels=x -> 6 in x ? :night : :day)) ```` -Similar to the hourly resample, how do we do it for more than 1 day, let's say 8daily? +::: + +## Select by [`Dimension`](@ref) + +We can also select by `Dimension`s and any objects with `dims` methods. + +::: tabs + +== groupby dims + +Trivially, grouping by an objects own dimension is similar to `eachslice`: ````@ansi groupby -mean.(groupby(A, Ti=>Bins(dayofyear, map(x -> x:x+7, 1:8:370)))) +groupby(A, dims(A, Ti)) ```` -## Group by Dims. -This should include the rasters input sampling. +== groupby AbstractDimArray + +But we can also group by other objects dimensions: ````@ansi groupby -mean.(groupby(A, dims(A, Ti))) +B = A[:, 1:3:100] +C = mean.(groupby(A, B)) +@assert size(C) == size(B) ```` -## Apply custom function (i.e. normalization) to grouped output. +::: + +_TODO: Apply custom function (i.e. normalization) to grouped output._ diff --git a/docs/src/integrations.md b/docs/src/integrations.md index b8c9aef94..637d9819f 100644 --- a/docs/src/integrations.md +++ b/docs/src/integrations.md @@ -72,12 +72,12 @@ synchronisation during simulations. Notably, this all works on GPUs! ### AstroImages.jl -[AstroImages.jl](http://juliaastro.org/dev/modules/AstroImages/) +[AstroImages.jl](http://juliaastro.org/dev/modules/AstroImages) Provides tools to load and visualise astromical images. `AstroImage` is `<: AbstractDimArray`. ### TimeseriesTools.jl -[TimeseriesTools.jl](https://juliahub.com/ui/Packages/General/TimeseriesTools +[TimeseriesTools.jl](https://juliahub.com/ui/Packages/General/TimeseriesTools) Uses `DimArray` for time-series data. diff --git a/docs/src/lookup_customization.md b/docs/src/lookup_customization.md deleted file mode 100644 index b8596abce..000000000 --- a/docs/src/lookup_customization.md +++ /dev/null @@ -1,4 +0,0 @@ -# lookup customization -## Defaults -## custom lookup properties -## modifying existing lookups \ No newline at end of file diff --git a/docs/src/object_modification.md b/docs/src/object_modification.md new file mode 100644 index 000000000..72fbb8292 --- /dev/null +++ b/docs/src/object_modification.md @@ -0,0 +1,189 @@ +# Modifying objects + +DimensionalData.jl objects are all `struct` rather than +`mutable struct`. The only things you can modify in-place +are the values of the contained arrays or metadata `Dict`s if +they exist. + +Everything else must be _rebuilt_ and assigned to a variable. + +## `modify` + +Modify the inner arrays of a `AbstractDimArray` or `AbstractDimStack`, with +[`modify`](@ref). This can be usefule to e.g. replace all arrays with `CuArray` +moving the data to the GPU, `collect` all inner arrays to `Array` without losing +the outer `DimArray` wrappers, and similar things. + +::::tabs + +== array + +````@ansi helpers +using DimensionalData +A = falses(X(3), Y(5)) +parent(A) +A_mod = modify(Array, A) +parent(A_mod) +```` + +== stack + +For a stack this applied to all layers, and is where `modify` +starts to be more powerful: + +````@ansi helpers +st = DimStack((a=falses(X(3), Y(5)), b=falses(X(3), Y(5)))) +parent(st.a) +parent(modify(Array, st).a) +parent(modify(Array, st).b) +```` + +:::: + +## `reorder` + +[`reorder`](@ref) is like reverse but declaritive, rather than +imperitive: we tell it how we want the object to be, not what to do. + +::::tabs + +== specific dimension/s + +Reorder a specific dimension + +````@ansi helpers +using DimensionalData.LookupArrays; +A = rand(X(1.0:3.0), Y('a':'n')); +reorder(A, X => ReverseOrdered()) +```` + +== all dimensions + +````@ansi helpers +reorder(A, ReverseOrdered()) +```` + +:::: + +## `mergedims` + +[`mergedims`](@ref) is like `reshape`, but simultaneously merges multiple +dimensions into a single combined dimension with a lookup holding +`Tuples` of the values of both dimensions. + + +## `rebulid` + +[`rebuild`](@ref) is one of the core functions of DimensionalData.jl. +Basically everything uses it somewhere. And you can to, with a few caveats. + +`reuild` assumes you _know what yo uare doing_. You can quite eaily set +values to things that don't make sense. The constructor may check a few things, +like the number of dimensions matches the axes of the array. But not much else. + +:::: tabs + +== change the name + +````@ansi helpers +A1 = rebuild(A; name=:my_array) +name(A1) +```` + +== change the metadata + +````@ansi helpers +A1 = rebuild(A; metadata=Dict(:a => "foo", :b => "bar")) +metadata(A1) +```` + +:::: + +The most common use internally is the arg version on `Dimension`. +This is _very_ useful in dimension-based algorithmsas a way +to transfrom a dimension wrapper from one object to another: + +```@ansi helpers +d = X(1) +rebuild(d, 1:10) +``` + +`rebuild` applications are listed here. `AbstractDimArray` and +`AbstractDimStack` _always_ accept these keywords or arguments, +but those in [ ] brackes may be thrown away if not needed. +Keywords in ( ) will error if used where they are not accepted. + +| Type | Keywords | Arguments | +|--------------------------- |------------------------------------------------------------ |----------------------| +| [`AbstractDimArray`](@ref) | data, dims, [refdims, name, metadata] | as with kw, in order | +| [`AbstractDimStack`](@ref) | data, dims, [refdims], layerdims, [metadata, layermetadata] | as with kw, in order | +| [`Dimension`](@ref) | val | val | +| [`Selector`](@ref) | val, (atol) | val | +| [`LookupArray`](@ref) | data, (order, span, sampling, metadata) | keywords only | + +### `rebuild` magic + +`rebuild` with keywords will even work on objects DD doesn't know about! + +````@ansi helpers +nt = (a = 1, b = 2) +rebuild(nt, a = 99) +```` + +Really, the keyword version is just `ConstructionBase.setproperties` underneath, +but wrapped so objects can customise the DD interface without changing the +more generic ConstructionBase.jl behaviours and breaking e.g. Accessors.jl in +the process. + +## `set` + +[`set`](@ref) gives us a way to set the values of the immutable objects +in DD, like `Dimension` and `LookupAray`. Unlike `rebuild` it tries its best +to _do the right thing_. You don't have to specify what field you want to set. +just pass in the object you want to be part of the lookup. Usually, there is +no possible ambiguity. + +`set` is still improving. Sometimes itmay not do the right thing. +If you think this is the case, make a github issue. + +:::: tabs + +=== set the dimension wrapper + +````@ansi helpers +set(A, Y => Z) +```` + +=== clear the lookups + +````@ansi helpers +set(A, X => NoLookup, Y => NoLookup) +```` + +=== set different lookup values + +````@ansi helpers +set(A, Y => 10:10:140) +```` + +=== set lookup type as well as values + +Change the values but also set the type to Sampled. TODO: broken + +````@ansi helpers +set(A, Y => Sampled(10:10:140)) +```` + +=== set the points in X to be intervals + +````@ansi helpers +set(A, X => Intervals) +```` + +=== set the categories in Y to be `Unordered` + +````@ansi helpers +set(A, Y => Unordered) +```` + +::: diff --git a/docs/src/plots.md b/docs/src/plots.md index 496152b3c..8562e9a83 100644 --- a/docs/src/plots.md +++ b/docs/src/plots.md @@ -13,21 +13,21 @@ Plots.jl support is deprecated, as development is moving to Makie.jl # Makie.jl -Makie.jl functions also mostly work with `AbstractDimArray` and will `permute` and -`reorder` axes into the right places, especially if `X`/`Y`/`Z`/`Ti` dimensions are used. +Makie.jl functions also mostly work with [`AbstractDimArray`](@ref) and will `permute` and +[`reorder`](@ref) axes into the right places, especially if `X`/`Y`/`Z`/`Ti` dimensions are used. -In makie a `DimMatrix` will plot as a heatmap by defualt, but again it will have labels +In makie a `DimMatrix` will plot as a heatmap by defualt, but it will have labels and axes in the right places: ```@example Makie using DimensionalData, CairoMakie -A = rand(X(1:10), Y([:a, :b, :c])) +A = rand(X(10:10:100), Y([:a, :b, :c])) Makie.plot(A; colormap=:inferno) ``` -Other plots also work, here we ignore the axis order and instead favour the -categorical varable for the X axis: +Other plots also work, here DD ignores the axis order and instead +favours the categorical varable for the X axis: ```@example Makie Makie.rainclouds(A) diff --git a/docs/src/selectors.md b/docs/src/selectors.md index 9a068ffdf..bc51058f3 100644 --- a/docs/src/selectors.md +++ b/docs/src/selectors.md @@ -1,22 +1,117 @@ -# Selectors and LookupArrays +# Selectors As well as choosing dimensions by name, we can also select values in them. First, we can create `DimArray` with lookup values as well as dimension names: +````@example selectors +using DimensionalData +```` + +````@ansi selectors +A = rand(X(1.0:0.2:2.0), Y([:a, :b, :c])) +```` + +Then we can use [`Selector`](@ref) to select values from the array: + +::: tabs + +== At + +These are single value selectors, `At` and `Near`: + +````@ansi selectors +A[X=At(1.2), Y=At(:c)] +```` + +[`At`](@ref) can also take vectors and ranges: + +````@ansi selectors +A[X=At(1.2:0.2:1.5), Y=At([:a, :c])] +```` + +Or specify tolerance: + +````@ansi selectors +A[X=At(0.99:0.201:1.5; atol=0.05)] +```` + +== Near + +[`Near`](@ref) finds the nearest value in the lookup: + ````@ansi selectors -A = rand(X(1.0:0.1:2.0), Y([:a, :b, :c])) +A[X=Near(1.245)] ```` -Then we can use [`Selector`](@ref) to selctect -values from the array based on its lookup values: +`Near` can also take vectors and ranges: ````@ansi selectors -A[X=Near(1.3), Y=At(:c)] +A[X=Near(1.1:0.25:1.5)] ```` -There are a range of selectors available: +== Contains + +[`Contains`](@ref) finds the interval that contains a value. + +First set the `X` axis to be `Interals` + +````@ansi selectors +using DimensionalData.LookupArrays +A_intervals = set(A, X => Intervals(Start())) +intervalbounds(A_intervals, X) +```` + +````@ansi selectors +A_intervals[X=Contains(1.245)] +```` + +`Contains` can also take vectors and ranges: + +````@ansi selectors +A_intervals[X=Contains(1.1:0.25:1.5)] +```` + +== .. + +`..` or `IntervalSets.Interval` selects a range of values: + +````@ansi selectors +A[X=1.2 .. 1.7] +```` + +== Touches + +[`Touches`](@ref) is like `..`, but for `Intervals` it will include +intervals touched by the selected interval, not inside it. + +This usually means including zero, one or two cells more than `..` + +````@ansi selectors +A_intervals[X=Touches(1.1, 1.5)] +A_intervals[X=1.1 .. 1.5] +```` + +== Where + +[`Where`](@ref) uses a function of the lookup values: + +````@ansi selectors +A[X=Where(>=(1.5)), Y=Where(x -> x in (:a, :c))] +```` + +== Not + +[`Not`](@ref) takes the opposite of whatever it wraps: + +````@ansi selectors +A[X=Not(Near(1.3)), Y=Not(Where(in((:a, :c))))] +```` + +::: + +The full set is details in this table. | Selector | Description | Indexing style | | :---------------------- | :--------------------------------------------------------------------------- |------------------ | @@ -24,85 +119,145 @@ There are a range of selectors available: | [`Near(x)`](@ref) | get the closest index to the passed in value(s) | `Int/Vector{Int}` | | [`Contains(x)`](@ref) | get indices where the value x falls within an interval in the lookup | `Int/Vector{Int}` | | [`Where(f)`](@ref) | filter the array axis by a function of the dimension index values. | `Vector{Bool}` | -| [`Not(x)`] | get all indices _not_ selected by `x`, which can be another selector. | `Vector{Bool}` | -| [`a .. b`] | get all indices between two values, inclusively. | `UnitRange` | -| [`OpenInterval(a, b)`] | get all indices between `a` and `b`, exclusively. | `UnitRange` | -| [`Interval{A,B}(a, b)`] | get all indices between `a` and `b`, as `:closed` or `:open`. | `UnitRange` | -| [`Touches(a, b)`] | like `..` but includes all cells touched by the interval, not just inside it | `UnitRange` | +| `Not(x)` | get all indices _not_ selected by `x`, which can be another selector. | `Vector{Bool}` | +| `a .. b` | get all indices between two values, inclusively. | `UnitRange` | +| `OpenInterval(a, b)` | get all indices between `a` and `b`, exclusively. | `UnitRange` | +| `Interval{A,B}(a, b)` | get all indices between `a` and `b`, as `:closed` or `:open`. | `UnitRange` | +| [`Touches(a, b)`](@ref) | like `..` but includes all cells touched by the interval, not just inside it | `UnitRange` | Note: `At`, `Near` and `Contains` can wrap either single values or an `AbstractArray` of values, to select one index with an `Int` or multiple indices with a `Vector{Int}`. -Selectors find indices in the `LookupArray`, for each dimension. +# Lookups + +Selectors find indices in the `LookupArray` of each dimension. LookupArrays wrap other `AbstractArray` (often `AbstractRange`) but add aditional traits to facilitate fast lookups or specifing point or interval behviour. These are usually detected automatically. Some common `LookupArray` that are: -| LookupArray | Description | -| :---------------------- | :----------------------------------------------------------------------------------------------------------- | -| [`Sampled(x)`](@ref) | values sampled along an axis - may be `Ordered`/`Unordered`, `Intervals`/`Points`, and `Regular`/`Irregular` | -| [`Categorical(x)`](@ref) | a categorical lookup that holds categories, and may be ordered | -| [`Cyclic(x)`](@ref) | an `AbstractSampled` lookup for cyclical values. | -| [`NoLookup(x)`](@ref) | no lookup values provided, so `Selector`s will not work. Not show in repl printing. | +| LookupArray | Description | +| :---------------------- | :----------------------------------------------------------------------------------------------------------- | +| [`Sampled(x)`](@ref) | values sampled along an axis - may be `Ordered`/`Unordered`, `Intervals`/`Points`, and `Regular`/`Irregular` | +| [`Categorical(x)`](@ref) | a categorical lookup that holds categories, and may be ordered | +| [`Cyclic(x)`](@ref) | an `AbstractSampled` lookup for cyclical values. | +| [`NoLookup(x)`](@ref) | no lookup values provided, so `Selector`s will not work. Not show in repl printing. | +````@example lookuparrays +using DimensionalData.LookupArrays +```` + +## Lookup autodetection + +When we define an array, extra properties are detected: + +````@ansi lookuparrays +A = DimArray(rand(7, 5), (X(10:10:70), Y([:a, :b, :c, :d, :e]))) +```` + +This array has a `Sampled` lookup with `ForwardOrdered` `Regular` +`Points` for `X`, and a `Categorical` `ForwardOrdered` for `Y`. + +Most lookup types and properties are detected automatically like this +from the arrays and ranges used. + +- Arrays and ranges of `String`, `Symbol` and `Char` are set to `Categorical` lookup. + - `order` is detected as `Unordered`, `ForwardOrdered` or `ReverseOrdered` +- Arrays and ranges of `Number`, `DateTime` and other things are set to `Sampled` lookups. + - `order` is detected as `Unordered`, `ForwardOrdered` or `ReverseOrdered`. + - `sampling` is set to `Points()` unless the values are `IntervalSets.Interval`, + then `Intervals(Center())` is used. + - `span` is detected as `Regular(step(range))` for `AbstractRange` and + `Irregular(nothing, nothing)` for other `AbstractArray`, where `nothing, + nothing` are the unknown outer bounds of the lookup. They are not needed + for `Points` as the outer values are the outer bounds. But they can be + specified manually for `Intervals` + - Emtpy dimensions or dimension types are assigned `NoLookup()` ranges that + can't be used with selectors as they hold no values. + +## Specifying properties + +We can also override properties by adding keywords to a `Dimension` constructor: ````@ansi lookuparrays using DimensionalData.LookupArrays +rand(X(10:20:100; sampling=Intervals(Start())), Y([:a, :b, :c, :d, :e]; order=Unordered())) ```` -## `Cyclic` lookups +And they will be passed to the detected `LookupArray` typed - here `Sampled` and +`Categorical`. Anything we skip will be detected automatically. + +Finally, we can fully lookup properties. You may want to do this in a +package to avoid the type instability and other costs of the automatic checks. +Any skippied fields will still be auto-detected. Here we skip `span` in +`Sampled` as there is no cost to detecting it from a `range`. You can see +`AutoSpan` in the show output after we construct it - this will be replaced +when the `DimArray` is constructed. + +````@ansi lookuparrays +using DimensionalData.LookupArrays +x = X(Sampled(10:20:100; order=ForwardOrdered(), sampling=Intervals(Start()), metadata=NoMetadata())) +y = Y(Categorical([1, 2, 3]; order=ForwardOrdered(), metadata=NoMetadata())) +rand(x, y) +```` +Some other lookup types are never detected, and we have to specify them +manually. + +## `Cyclic` lookups Create a `Cyclic` lookup that cycles over 12 months. ````@ansi lookuparrays -lookup = Cyclic(DateTime(2000):Month(1):DateTime(2000, 12); cycle=Month(12), sampling=Intervals(Start())) +using Dates +l = Cyclic(DateTime(2000):Month(1):DateTime(2000, 12); cycle=Month(12), sampling=Intervals(Start())) ```` -Make a `DimArray` by apply a funcion to the lookup +There is a shorthand to make a `DimArray` frome a `Dimension` with a function +of the lookup values. Here we convert the values to the month names: ````@ansi lookuparrays -A = DimArray(month, X(lookup)) +A = DimArray(monthabbr, X(l)) ```` Now we can select any date and get the month: -```@ansi lookups +````@ansi lookuparrays A[At(DateTime(2005, 4))] -``` - +A[At(DateTime(3047, 9))] +```` -# `DimSelector` +## `DimSelector` We can also index with arrays of selectors [`DimSelectors`](@ref). These are like `CartesianIndices` or [`DimIndices`](@ref) but holding `Selectors` `At`, `Near` or `Contains`. -````@ansi dimselectors -A = rand(X(1.0:0.1:2.0), Y(10:2:20)) +````@ansi lookuparrays +A = rand(X(1.0:0.2:2.0), Y(10:2:20)) ```` We can define another array with partly matching indices -````@ansi dimselectors -B = rand(X(1.0:0.02:2.0), Y(20:-1:10)) +````@ansi lookuparrays +B = rand(X(1.0:0.04:2.0), Y(20:-1:10)) ```` And we can simply select values from `B` with selectors from `A`: -````@ansi dimselectors +````@ansi lookuparrays B[DimSelectors(A)] ```` If the lookups aren't aligned we can use `Near` instead of `At`, which like doing a nearest neighor interpolation: -````@ansi dimselectors +````@ansi lookuparrays C = rand(X(1.0:0.007:2.0), Y(10.0:0.9:30)) C[DimSelectors(A; selectors=Near)] ```` + diff --git a/docs/src/stacks.md b/docs/src/stacks.md index 7c521add2..8ba4f6c20 100644 --- a/docs/src/stacks.md +++ b/docs/src/stacks.md @@ -5,10 +5,53 @@ layers that share some or all dimensions. For any two layers, a dimension of the same name must have the identical lookup - in fact only one is stored for all layers to enforce this consistency. -The behaviour is somewhere ebetween a `NamedTuple` and an `AbstractArray` -Indexing layers by name with `stack[:layer]` or `stack.layer` works as with a -`NamedTuple`, and returns an `AbstractDimArray`. +````@ansi stack +using DimensionalData +x, y = X(1.0:10.0), Y(5.0:10.0) +st = DimStack((a=rand(x, y), b=rand(x, y), c=rand(y), d=rand(x))) +```` + +The behaviour is somewhere ebetween a `NamedTuple` and an `AbstractArray`. + +::::tabs + +== getting layers + +Layers can be accessed with `.name` or `[:name]` + +````@ansi stack +st.a +st[:c] +```` + +== scalars + +Indexing with a scalar returns a `NamedTuple` of values, one for each layer: + +````@ansi stack +st[X=1, Y=4] +```` + +== selectors + +Selectors for single values also return a `NamedTuple` + +````@ansi stack +st[X=At(2.0), Y=Near(20)] +```` + +== partial indexing + +If not all dimensions are scalars, we return another `DimStack`. +The layers without another dimension are now zero-dimensional: + +````@ansi stack +st[X=At(2.0)] +```` + +:::: + Indexing with `Dimensions`, `Selectors` works as with an `AbstractDimArray`, except it indexes for all layers at the same time, returning either a new small `AbstractDimStack` or a scalar value, if all layers are scalars. @@ -26,4 +69,8 @@ Indexing stack is fast - indexing a single value return a `NamedTuple` from all usingally, measures in nanoseconds. There are some compilation overheads to this though, and stacks with very many layers can take a long time to compile. -Hopefully compiler fixes planned for Julia v1.11 will improve this. +````@ansi stack +using BenchmarkTools +@btime $st[X=1, Y=4] +@btime $st[1, 4] +```` diff --git a/docs/src/tables.md b/docs/src/tables.md index 49b884f10..f8d01acae 100644 --- a/docs/src/tables.md +++ b/docs/src/tables.md @@ -11,52 +11,88 @@ DimensionalData.jl implements the Tables.jl interface for are unrolled so they are all the same size, and dimensions similarly loop over array strides to match the length of the largest layer. -Columns are given the `name` or the array or the stack layer key. +Columns are given the [`name`](@ref) or the array or the stack layer key. `Dimension` columns use the `Symbol` version (the result of `DD.dim2key(dimension)`). -Looping of unevenly size dimensions and layers is done _lazily_, +Looping of dimensions and stack layers is done _lazily_, and does not allocate unless collected. -````@ansi dataframe +## Example + +````@example dataframe using DimensionalData, Dates, DataFrames -x = X(1:10) -y = Y(1:10) -c = Dim{:category}('a':'z') -c = Dim{:category}(1:25.0) +```` -A = DimArray(rand(x, y, c); name=:data) -st = DimStack((data1 = rand(x, y), data2=rand(x, y, c))) +Define some dimensions: + +````@ansi dataframe +x, y, c = X(1:10), Y(1:10), Dim{:category}('a':'z') ```` -By default this stack will become a table with a column for each -dimension, and one for each layer: +::::tabs + +== create a `DimArray` ````@ansi dataframe -DataFrame(st) +A = rand(x, y, c; name=:data) ```` -Arrays behave the same way, but with only one data column -````@ansi +== create a `DimStack` + +````@ansi dataframe +st = DimStack((data1 = rand(x, y), data2=rand(x, y, c))) +```` + +:::: + +## Converting to DataFrame + +::::tabs + +== array default + +Arrays will have columns for each dimension, and only one data column + +````@ansi dataframe DataFrame(A) ```` -We can also control how the table is created using [`DimTable`](@ref), -here we can merge the spatial dimensions so the column is a point: +== stack default + +Stacks will become a table with a column for each dimension, +and one for each layer: ````@ansi dataframe -DataFrame(DimTable(st; mergedims=(:X, :Y)=>:XY)) +DataFrame(st) ```` -Or, for a `DimArray` we can take columns from one of the layers: +== layerfrom + +Using [`DimTable`](@ref) we can specify that a `DimArray` +should take columns from one of the dimensions: ````@ansi dataframe DataFrame(DimTable(A; layersfrom=:category)) ```` +== stack `DimTable` + +Using [`DimTable`](@ref) we can merge the spatial +dimensions so the column is a tuple: + +````@ansi mergedims +DataFrame(DimTable(st; mergedims=(:X, :Y)=>:XY)) +```` + +:::: + +## Converting to CSV + We can also write arrays and stacks directly to CSV.jl, or any other data type supporting the Tables.jl interface. ````@ansi dataframe using CSV -CSV.write("stack_datframe.csv", st) +CSV.write("dimstack.csv", st) +readlines("dimstack.csv") ```` diff --git a/src/LookupArrays/LookupArrays.jl b/src/LookupArrays/LookupArrays.jl index 945a4c885..a5712a7ca 100644 --- a/src/LookupArrays/LookupArrays.jl +++ b/src/LookupArrays/LookupArrays.jl @@ -1,7 +1,7 @@ """ LookupArrays -Module for [`LookupArrays`](@ref) and [`Selector`]s used in DimensionalData.jl +Module for [`LookupArrays`](@ref) and [`Selector`](@ref)s used in DimensionalData.jl `LookupArrays` defines traits and `AbstractArray` wrappers that give specific behaviours for a lookup index when indexed with [`Selector`](@ref). diff --git a/src/array/show.jl b/src/array/show.jl index a90bbbd9c..63a27ac84 100644 --- a/src/array/show.jl +++ b/src/array/show.jl @@ -127,7 +127,7 @@ function print_dims_block(io, mime, dims; displaywidth, blockwidth, label="dims" new_blockwidth = blockwidth else dim_lines = split(sprint(print_dims, mime, dims), '\n') - new_blockwidth = min(displaywidth - 2, max(blockwidth, maximum(textwidth, dim_lines))) + new_blockwidth = max(blockwidth, min(displaywidth - 2, maximum(textwidth, dim_lines))) lines += print_block_top(io, label, blockwidth, new_blockwidth) lines += print_dims(io, mime, dims; kw...) println(io) diff --git a/src/utils.jl b/src/utils.jl index 4cb58bfbc..91bba96c6 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -24,6 +24,7 @@ rev = reverse(da, dims=Y) reorder(rev, da) == da # output true +``` """ function reorder end From 7662dc9b5f54342f20c8a338706d7e3611342ef6 Mon Sep 17 00:00:00 2001 From: Anshul Singhvi Date: Sun, 25 Feb 2024 04:56:47 -0500 Subject: [PATCH 044/108] Update doc build for DocumenterVitepress v0.0.4 (#647) * Update config.mts * remove stuff which was moved into `makedocs` from `make.jl` * switch target * update theme * Use the julia-docdeploy action to deploy documentation (#634) * Use the julia-docdeploy action to deploy documentation This gets us the nice status on the CI panel which takes you directly to the preview/rendered doc page. Also removes the need to add DocumenterVitepress independently! * Instantiate the project first * Update logo.jl * Update logo.jl --- .github/workflows/Documenter.yml | 8 ++----- docs/logo.jl | 8 ++++--- docs/make.jl | 41 +++++--------------------------- docs/package-lock.json | 2 +- docs/package.json | 6 ++--- docs/src/.vitepress/config.mts | 37 ++++------------------------ src/array/show.jl | 2 +- 7 files changed, 22 insertions(+), 82 deletions(-) diff --git a/.github/workflows/Documenter.yml b/.github/workflows/Documenter.yml index 31d69053d..04c7d4a79 100644 --- a/.github/workflows/Documenter.yml +++ b/.github/workflows/Documenter.yml @@ -47,18 +47,14 @@ jobs: uses: julia-actions/setup-julia@v1 - name: Pull Julia cache uses: julia-actions/cache@v1 - - name: Install custom documentation dependencies - run: julia --project=docs -e 'using Pkg; pkg"add https://github.com/LuxDL/DocumenterVitepress.jl.git"; pkg"dev ."; Pkg.instantiate(); Pkg.precompile(); Pkg.status()' - name: Instantiate NPM run: cd docs/; npm i; cd .. - name: Generate logo - run: julia --project=docs/ --color=yes docs/logo.jl - - name: Build and deploy + run: julia --project=docs -e "using Pkg; Pkg.instantiate()"; julia --project=docs/ --color=yes docs/logo.jl + - uses: julia-actions/julia-docdeploy@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # For authentication with GitHub Actions token DOCUMENTER_KEY: ${{ secrets.DOCUMENTER_KEY }} # For authentication with SSH deploy key GKSwstype: "100" # https://discourse.julialang.org/t/generation-of-documentation-fails-qt-qpa-xcb-could-not-connect-to-display/60988 JULIA_DEBUG: "Documenter" DATADEPS_ALWAYS_ACCEPT: true - run: | - julia --project=docs/ --color=yes docs/make.jl # this should ideally AUTO-DEPLOY! diff --git a/docs/logo.jl b/docs/logo.jl index e538c459e..79b7598bc 100644 --- a/docs/logo.jl +++ b/docs/logo.jl @@ -33,6 +33,8 @@ lines!(ax, [ Point3f(0.1+7,0.1,8), Point3f(0.1+7,0.1+7,8), color = colors[2], linewidth=2, transparency=true) -save(joinpath(@__DIR__, "src", "public", "logoDD.svg"), fig; pt_per_unit=0.75) -save(joinpath(@__DIR__, "src", "public", "logoDD.png"), fig; px_per_unit=2) -fig \ No newline at end of file +mkpath(joinpath(@__DIR__, "src", "assets")) +save(joinpath(@__DIR__, "src", "assets", "logoDD.svg"), fig; pt_per_unit=0.75) +save(joinpath(@__DIR__, "src", "assets", "logoDD.png"), fig; px_per_unit=2) +save(joinpath(@__DIR__, "src", "assets", "favicon.png"), fig; px_per_unit=0.25) +fig diff --git a/docs/make.jl b/docs/make.jl index bc4424edd..9015d591a 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -5,49 +5,20 @@ using DimensionalData makedocs(; sitename="DimensionalData", authors="Rafael Schouten et al.", # modules=[DimensionalData], # checkdocs=:all, - format=DocumenterVitepress.MarkdownVitepress(), + format=DocumenterVitepress.MarkdownVitepress( + repo = "github.com/rafaqz/DimensionalData.jl", + devbranch = "main", + devurl = "dev", + ), draft=false, source="src", build=joinpath(@__DIR__, "build"), warnonly = true, ) - -# We manually obtain the Documenter deploy configuration, -# so we can use it to set Vitepress's settings. -# TODO: make this better / encapsulate it in `makedocs` -# so the user does not need to know! -deploy_config = Documenter.auto_detect_deploy_system() -deploy_decision = Documenter.deploy_folder( - deploy_config; - repo="github.com/rafaqz/DimensionalData.jl", - devbranch="main", - devurl = "dev", - push_preview=true, -) - -# VitePress relies on its config file in order to understand where files will exist. -# We need to modify this file to reflect the correct base URL, however, Documenter -# only knows about the base URL at the time of deployment. - -# So, after building the Markdown, we need to modify the config file to reflect the -# correct base URL, and then build the VitePress site. -folder = deploy_decision.subfolder -println("Deploying to $folder") -vitepress_config_file = joinpath(@__DIR__, "build", ".vitepress", "config.mts") -config = read(vitepress_config_file, String) -new_config = replace(config, "base: 'REPLACE_ME_WITH_DOCUMENTER_VITEPRESS_BASE_URL_WITH_TRAILING_SLASH'" => "base: '/DimensionalData.jl/$folder'") -write(vitepress_config_file, new_config) - -# Build the docs using `npm` - we are assuming it's installed here! -cd(@__DIR__) do - run(`npm run docs:build`) -end -touch(joinpath(@__DIR__, "build", ".vitepress", "dist", ".nojekyll")) - deploydocs(; repo="github.com/rafaqz/DimensionalData.jl", - target="build/.vitepress/dist", # this is where Vitepress stores its output + target="build", # this is where Vitepress stores its output branch = "gh-pages", devbranch = "main", push_preview = true diff --git a/docs/package-lock.json b/docs/package-lock.json index b870e7fc5..2dc55a5e4 100644 --- a/docs/package-lock.json +++ b/docs/package-lock.json @@ -1,5 +1,5 @@ { - "name": "DimensionalData", + "name": "docs", "lockfileVersion": 3, "requires": true, "packages": { diff --git a/docs/package.json b/docs/package.json index c21d8c433..b9f192247 100644 --- a/docs/package.json +++ b/docs/package.json @@ -1,8 +1,8 @@ { "scripts": { - "docs:dev": "vitepress dev build", - "docs:build": "vitepress build build", - "docs:preview": "vitepress preview build" + "docs:dev": "vitepress dev build/.documenter", + "docs:build": "vitepress build build/.documenter", + "docs:preview": "vitepress preview build/.documenter" }, "devDependencies": { "markdown-it-mathjax3": "^4.3.2", diff --git a/docs/src/.vitepress/config.mts b/docs/src/.vitepress/config.mts index c7086ed1d..df02ffcf0 100644 --- a/docs/src/.vitepress/config.mts +++ b/docs/src/.vitepress/config.mts @@ -11,12 +11,13 @@ const VERSIONS: DefaultTheme.NavItemWithLink[] = [ // https://vitepress.dev/reference/site-config export default defineConfig({ - base: 'REPLACE_ME_WITH_DOCUMENTER_VITEPRESS_BASE_URL_WITH_TRAILING_SLASH', - title: "DimensionalData", + base: 'REPLACE_ME_DOCUMENTER_VITEPRESS', + title: 'REPLACE_ME_DOCUMENTER_VITEPRESS', description: "Datasets with named dimensions", lastUpdated: true, cleanUrls: true, ignoreDeadLinks: true, + outDir: 'REPLACE_ME_DOCUMENTER_VITEPRESS', // This is required for MarkdownVitepress to work correctly... markdown: { config(md) { @@ -58,37 +59,7 @@ export default defineConfig({ }, ], - sidebar: [ - { - text: '', - items: [ - { text: 'Getting Started', link: '/basics' }, - { text: 'Dimensions', link: '/dimensions' }, - { text: 'Selectors', link: '/selectors' }, - { text: 'Dimarrays', link: '/dimarrays' }, - { text: 'DimStacks', link: '/stacks' }, - { text: 'GroupBy', link: '/groupby' }, - { text: 'Getting information', link: '/get_info' }, - { text: 'Object modification', link: '/object_modification' }, - { text: 'Integrations', - items: [ - { text: 'Plots and Makie', link: '/plots' }, - { text: 'Tables and DataFrames', link: '/tables' }, - { text: 'CUDA and GPUs', link: '/cuda' }, - { text: 'DiskArrays', link: '/diskarrays' }, - { text: 'Ecosystem', link: '/integrations' }, - { text: 'Extending DimensionalData', link: '/extending_dd' }, - ], - }, - { text: 'API Reference', link: '/api/reference', - items: [ - { text: 'Dimensions Reference', link: '/api/dimensions' }, - { text: 'LookupArrays Reference', link: '/api/lookuparrays' }, - ], - }, - ] - } - ], + sidebar: 'REPLACE_ME_DOCUMENTER_VITEPRESS', socialLinks: [ { icon: 'github', link: 'https://github.com/rafaqz/DimensionalData.jl' }, diff --git a/src/array/show.jl b/src/array/show.jl index 63a27ac84..f30a611ef 100644 --- a/src/array/show.jl +++ b/src/array/show.jl @@ -56,7 +56,7 @@ Additional keywords may be added at any time. `blockwidth` is passed in context -```juli +```julia blockwidth = get(io, :blockwidth, 10000) ``` From 03e49ba824777af0c2c3007fc53ea5f49a515297 Mon Sep 17 00:00:00 2001 From: Rafael Schouten Date: Sun, 25 Feb 2024 13:33:35 +0100 Subject: [PATCH 045/108] more docs (#649) --- docs/Project.toml | 4 +- docs/src/.vitepress/config.mts | 32 ++++- docs/src/api/reference.md | 1 - docs/src/broadcast_dims.md | 36 +++--- docs/src/dimarrays.md | 156 ++++++++++------------- docs/src/dimensions.md | 18 +-- docs/src/diskarrays.md | 93 ++------------ docs/src/extending_dd.md | 39 ++++++ docs/src/get_info.md | 2 +- docs/src/groupby.md | 15 +-- docs/src/index.md | 9 +- docs/src/integrations.md | 28 ++--- docs/src/object_modification.md | 2 +- docs/src/selectors.md | 217 +++++++++++++++++--------------- docs/src/stacks.md | 152 ++++++++++++++++++++-- docs/src/tables.md | 15 +-- src/DimensionalData.jl | 4 +- src/groupby.jl | 2 +- src/stack/methods.jl | 20 ++- src/stack/show.jl | 2 +- src/stack/stack.jl | 43 +++++-- src/utils.jl | 23 +++- 22 files changed, 535 insertions(+), 378 deletions(-) diff --git a/docs/Project.toml b/docs/Project.toml index 3e735f561..47b5c1477 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -1,6 +1,5 @@ [deps] BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf" -BetterFileWatching = "c9fd44ac-77b5-486c-9482-9798bd063cc6" CSV = "336ed68f-0bac-5ca0-87d4-7b16caf5d00b" CairoMakie = "13f3f980-e62b-5c42-98c6-ff1f3baf88f0" Colors = "5ae59095-9a9b-59fe-a467-6f913c188581" @@ -10,7 +9,8 @@ DimensionalData = "0703355e-b756-11e9-17c0-8b28908087d0" DiskArrays = "3c3547ce-8d99-4f5e-a174-61eb10b00ae3" Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" DocumenterVitepress = "4710194d-e776-4893-9690-8d956a29c365" -FileWatching = "7b1f6079-737a-58dc-b8bc-7a2ca5c1b5ee" +Extents = "411431e0-e8b7-467b-b5e0-f676ba4f2910" +Interfaces = "85a1e053-f937-4924-92a5-1367d23b7b87" Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" Unitful = "1986cc42-f94f-5a68-af5c-568840ba703d" diff --git a/docs/src/.vitepress/config.mts b/docs/src/.vitepress/config.mts index df02ffcf0..d465faf25 100644 --- a/docs/src/.vitepress/config.mts +++ b/docs/src/.vitepress/config.mts @@ -59,7 +59,37 @@ export default defineConfig({ }, ], - sidebar: 'REPLACE_ME_DOCUMENTER_VITEPRESS', + sidebar: [ + { + text: '', + items: [ + { text: 'Getting Started', link: '/basics' }, + { text: 'Dimensions', link: '/dimensions' }, + { text: 'Selectors', link: '/selectors' }, + { text: 'Dimarrays', link: '/dimarrays' }, + { text: 'DimStacks', link: '/stacks' }, + { text: 'GroupBy', link: '/groupby' }, + { text: 'Getting information', link: '/get_info' }, + { text: 'Object modification', link: '/object_modification' }, + { text: 'Integrations', + items: [ + { text: 'Plots and Makie', link: '/plots' }, + { text: 'Tables and DataFrames', link: '/tables' }, + { text: 'CUDA and GPUs', link: '/cuda' }, + { text: 'DiskArrays', link: '/diskarrays' }, + { text: 'Ecosystem', link: '/integrations' }, + { text: 'Extending DimensionalData', link: '/extending_dd' }, + ], + }, + { text: 'API Reference', link: '/api/reference', + items: [ + { text: 'Dimensions Reference', link: '/api/dimensions' }, + { text: 'LookupArrays Reference', link: '/api/lookuparrays' }, + ], + }, + ] + } + ], socialLinks: [ { icon: 'github', link: 'https://github.com/rafaqz/DimensionalData.jl' }, diff --git a/docs/src/api/reference.md b/docs/src/api/reference.md index ee4871334..9fc603e45 100644 --- a/docs/src/api/reference.md +++ b/docs/src/api/reference.md @@ -46,7 +46,6 @@ DimPoints ```@docs DimensionalData.AbstractDimTable DimTable -DimensionalData.DimColumn ``` # Utility methods diff --git a/docs/src/broadcast_dims.md b/docs/src/broadcast_dims.md index ca8120427..c00c2bd05 100644 --- a/docs/src/broadcast_dims.md +++ b/docs/src/broadcast_dims.md @@ -1,40 +1,38 @@ # `broadcast_dims` and `broadcast_dims!` -[`broadcast_dims`](@ref) is like Base julia `broadcast` on dimensional steroids. -Because we know the names of the dimensions, there is ambiguity in which -one we mean, and we can permuted and reshape them so that broadcasta that -would fail with a regular `Array` just work with a `DimArray`. As an added -bonus, `broadcast_dims` even works on `DimStack`s. +[`broadcast_dims`](@ref) is a dimension-aware extension to Base julia `broadcast`. -````@ansi bd -using DimensionalData -x, y, t = X(1:100), Y(1:25), Ti(DateTime(2000):Month(1):DateTime(2000, 12)) +Because we know the names of the dimensions there is no ambiguity in which +one we mean to broadcast together. We can permute and reshape dims so that +broadcasts that would fail with a regular `Array` just work with a `DimArray`. -month_scalars = DimArray(month, t) -data = rand(x, y, t) -```` +As an added bonus, `broadcast_dims` even works on `DimStack`s. -A regular broadcast fails: +## Example: scaling along the time dimension + +Define some dimensions: ````@ansi bd -data .* month_scalars +using DimensionalData, Dates, Statistics +x, y, t = X(1:100), Y(1:25), Ti(DateTime(2000):Month(1):DateTime(2000, 12)) ```` -But `broadcast_dims` knows to broadcast over the `Ti` dimension: +A DimArray from 1:12 to scale with: ````@ansi bd -scaled = broadcast_dims(*, data, month_scalars) +month_scalars = DimArray(month, t) ```` -We can see the means of each month are scaled by the broadcast : +And a larger DimArray for example data: ````@ansi bd -mean(eachslice(data; dims=(X, Y))) -mean(eachslice(scaled; dims=(X, Y))) +data = rand(x, y, t) ```` +A regular broadcast fails: + ````@ansi bd -data .* month_scalars +scaled = data .* month_scalars ```` But `broadcast_dims` knows to broadcast over the `Ti` dimension: diff --git a/docs/src/dimarrays.md b/docs/src/dimarrays.md index 64dba61a7..3b5908ce8 100644 --- a/docs/src/dimarrays.md +++ b/docs/src/dimarrays.md @@ -1,106 +1,119 @@ -# AbstracDimArray +# DimArray -`DimArray`s are wrappers for other kinds of `AbstractArray` that -add named dimension lookups. +`DimArray`s are wrappers for other kinds of `AbstractArray` that +add named dimension lookups. Here we define a `Matrix` of `Float64`, and give it `X` and `Y` dimensions ```@ansi dimarray using DimensionalData -A = zeros(5, 10) +A = rand(5, 10) da = DimArray(A, (X, Y)) ``` We can access a value with the same dimension wrappers: -```@ansi dimensions -A1[Y(1), X(2)] +```@ansi dimarray +da[Y(1), X(2)] ``` There are shortcuts for creating `DimArray`: -::::tabs +::: tabs + +== DimArray + +```@ansi dimarray +A = rand(5, 10) +DimArray(A, (X, Y)) +DimArray(A, (X, Y); name=:DimArray, metadata=Dict()) +``` == zeros -```@ansi dimensions +```@ansi dimarray zeros(X(5), Y(10)) +zeros(X(5), Y(10); name=:zeros, metadata=Dict()) ``` == ones -```@ansi dimensions +```@ansi dimarray ones(X(5), Y(10)) +ones(X(5), Y(10); name=:ones, metadata=Dict()) ``` == rand -```@ansi dimensions +```@ansi dimarray rand(X(5), Y(10)) +rand(X(5), Y(10); name=:rand, metadata=Dict()) ``` -== fill +== fill -```@ansi dimensions +```@ansi dimarray fill(7, X(5), Y(10)) +fill(7, X(5), Y(10); name=:fill, metadata=Dict()) ``` -:::: +::: + +## Constructing DimArray with arbitrary dimension names -For arbitrary names, we can use the `Dim{:name}` dims +For arbitrary names, we can use the `Dim{:name}` dims by using `Symbol`s, and indexing with keywords: -```@ansi dimensions -A2 = DimArray(rand(5, 5), (:a, :b)) +```@ansi dimarray +da1 = DimArray(rand(5, 5), (:a, :b)) ``` and get a value, here another smaller `DimArray`: -```@ansi dimensions -A2[a=3, b=1:3] +```@ansi dimarray +da1[a=3, b=1:3] ``` ## Dimensional Indexing -When used for indexing, dimension wrappers free us from knowing the +When used for indexing, dimension wrappers free us from knowing the order of our objects axes. These are the same: -```@ansi dimensions -A1[X(2), Y(1)] == A1[Y(1), X(2)] +```@ansi dimarray +da[X(2), Y(1)] == da[Y(1), X(2)] ``` -We also can use Tuples of dimensions like `CartesianIndex`, +We also can use Tuples of dimensions like `CartesianIndex`, but they don't have to be in order of consecutive axes. -```@ansi dimensions -A3 = rand(X(10), Y(7), Z(5)) -A3[(X(3), Z(5))] +```@ansi dimarray +da2 = rand(X(10), Y(7), Z(5)) +da2[(X(3), Z(5))] ``` We can index with `Vector` of `Tuple{Vararg(Dimension}}` like vectors of `CartesianIndex`. This will merge the dimensions in the tuples: -```@ansi dimensions -A3[[(X(3), Z(5)), (X(7), Z(4)), (X(8), Z(2))]] +```@ansi dimarray +inds = [(X(3), Z(5)), (X(7), Z(4)), (X(8), Z(2))] +da2[inds] ``` -`DimIndices` can be used like `CartesianIndices` but again, without the +`DimIndices` can be used like `CartesianIndices` but again, without the constraint of consecutive dimensions or known order. -```@ansi dimensions -A3[DimIndices(dims(A3, (X, Z))), Y(3)] +```@ansi dimarray +da2[DimIndices(dims(da2, (X, Z))), Y(3)] ``` -The `Dimension` indexing layer sits on top of regular indexing and _can not_ be combined +The `Dimension` indexing layer sits on top of regular indexing and _can not_ be combined with it! Regular indexing specifies order, so doesn't mix well with our dimensions. Mixing them will throw an error: -```julia -julia> A1[X(3), 4] -ERROR: ArgumentError: invalid index: X{Int64}(3) of type X{Int64} -... +```@ansi dimarray +da1[X(3), 4] ``` ::: info Indexing @@ -111,62 +124,16 @@ Indexing `AbstractDimArray`s works with `getindex`, `setindex!` and ::: - -## Indexing Performance - -Indexing with `Dimension`s has no runtime cost: - -```@ansi dimensions -A2 = ones(X(3), Y(3)) -``` - -Lets benchmark it - -```@ansi dimensions -using BenchmarkTools -@benchmark $A2[X(1), Y(2)] -``` - -the same as accessing the parent array directly: - -```@ansi dimensions -@benchmark parent($A2)[1, 2] -``` - - ## `dims` keywords In many Julia functions like, `size` or `sum`, you can specify the dimension along which to perform the operation as an `Int`. It is also possible to do this using [`Dimension`](@ref) types with `AbstractDimArray`: -````@ansi dimensions -A3 = rand(X(3), Y(4), Ti(5)) -sum(A3; dims=Ti) -```` - -This also works in methods from `Statistics`: - -````@example dimensions -using Statistics -```` - -````@ansi dimensions -mean(A3; dims=Ti) -```` - -This can be especially useful when you are working with multiple objects. -Here we take the mean of A3 over all dimensions _not in_ A2, using `otherdims`. - -In this case, thats the `Z` dimension. But we don't need to know it the Z -dimension, some other dimensions, or even if it has extra dimensions at all! - -This will work either way, leaveing us with the same dims as A1: - -````@ansi dimensions -d = otherdims(A3, dims(A1)) -dropdims(mean(A3; dims=d); dims=d) -```` +```@ansi dimarray +da5 = rand(X(3), Y(4), Ti(5)) +sum(da5; dims=Ti) +``` ::: info Dims keywords @@ -183,9 +150,18 @@ Methods where dims, dim types, or `Symbol`s can be used to indicate the array di ::: -## DimIndices -## Vectors of Dimensions +## Performance + +Indexing with `Dimension`s has no runtime cost. Lets benchmark it: + +```@ansi dimarray +using BenchmarkTools +da4 = ones(X(3), Y(3)) +@benchmark $da4[X(1), Y(2)] +``` + +the same as accessing the parent array directly: -## How to name dimensions? -## How to name an array? -## Adding metadata +```@ansi dimarray +@benchmark parent($da4)[1, 2] +``` diff --git a/docs/src/dimensions.md b/docs/src/dimensions.md index f037e7a29..b7a93b4c6 100644 --- a/docs/src/dimensions.md +++ b/docs/src/dimensions.md @@ -3,7 +3,7 @@ Dimensions are "wrapper types" that can be used to wrap any object to associate it with a named dimension. -`X`, `Y`, `Z`, `Ti` are predefined as types +`X`, `Y`, `Z`, `Ti` are predefined as types : ```@ansi dimensions using DimensionalData @@ -17,14 +17,20 @@ You can also make [`Dim`](@ref) dimensions with any name: Dim{:a}(1), Dim{:b}(1) ``` -DimensionalData.jl uses `Dimensions` pretty much everywhere: +The wrapped value can be retreived with `val`: + +```@ansi dimensions +val(X(1)) +``` + +DimensionalData.jl uses `Dimensions` everywhere: - `Dimension` are returned from `dims` to specify the names of the dimensions of an object - they wrap [`LookupArrays`](@ref) to associate the lookups with those names -- to index into these objects, they can wrap indices like `Int` or a `Selector` +- to index into these objects, they wrap indices like `Int` or a `Selector` -This symmetry means we can just ignore how data is organised, and -just label and access it by name, letting DD work out the details for us. +This symmetry means we can ignore how data is organised, +and label and access it by name, letting DD work out the details for us. Dimensions are defined in the [`Dimensions`](@ref) submodule, some Dimension-specific methods can be brought into scope with: @@ -32,5 +38,3 @@ Dimension-specific methods can be brought into scope with: ```julia using DimensionalData.Dimensions ``` - - diff --git a/docs/src/diskarrays.md b/docs/src/diskarrays.md index 7bab17a61..dea61ecd7 100644 --- a/docs/src/diskarrays.md +++ b/docs/src/diskarrays.md @@ -1,94 +1,21 @@ -### [DiskArrays.jl](https://github.com/meggart/DiskArrays.jl) compatability +# [DiskArrays.jl](https://github.com/meggart/DiskArrays.jl) compatability - -The combination of DiskArrays.jl and DimensionalData.jl is Julias answer to -pythons [xarray](https://xarray.dev/). - -Rasters.jl and YAXArrays.jl are the user-facing tools building on this -combination. - -DiskArrays.jl is rarely used directly by users, but is present in most -disk and cloud based spatial data packages in julia, including: -- ArchGDAL.jl -- NetCDF.jl -- Zarr.jl -- NCDatasets.lj -- GRIBDatasets.jl -- CommonDataModel.jl -- etc... - -So that lazy, chunked data access conforms to julias array -interface but also scales to operating on terrabytes of data. - -DiskArrays enables chunk ordered lazy application of: +DiskArrays enables lazy, chunked application of: - broadcast -- reduce +- reductions - iteration - generators - zip -DimensionalData.jl is a common front-end for accessing DiskArrays.jl -compatible datasets. Wherever An `AbstractDimArray` wraps a disk array we -will do our best to make sure all of the DimensionalData.jl indexing and -DiskArrays.jl lazy/chunked operations work together cleanly. +It is rarely used directly, but is present in most +disk and cloud based spatial data packages in julia, including: +ArchGDAL.jl, NetCDF.jl, Zarr.jl, NCDatasets.lj, GRIBDatasets.jl and CommonDataModel.jl + +The combination of DiskArrays.jl and DimensionalData.jl is Julias answer to +pythons [xarray](https://xarray.dev/). Rasters.jl and YAXArrays.jl are user-facing +tools building on this combination. They have no direct dependency relationships, with but are intentionally designed to integrate via both adherence to julias `AbstractArray` interface, and by coordination during development of both packages. - - -# Example - -Out of the box integration. - -DimensionalData.jl and DiskArrays.jl play nice no matter the size of the data. -To make this all work in CI we will simulate some huge data by multiplying -a huge `BitArray` with a `BigInt`, meant to make it 128 x larger in memory. - -```@ansi diskarray -using DimensionalData, DiskArrays - -# This holds is a 100_100 * 50_000 `BitArray` -A = trues(100_000, 50_000) -diska = DiskArrays.TestTypes.AccessCountDiskArray(A; chunksize=(100, 100)) -dima = DimArray(diska, (X(0.01:0.01:1000), Y(0.02:0.02:1000))) -``` - -# How big is this thing? -```@ansi diskarray -GB = sizeof(A) / 1e9 -``` - - -Now if we multiply that by 2.0 they will be Float64, ie 64 x larger. - -But: - -```@ansi diskarray -dimb = view(permutedims(dima .* BigInt(200000000000), (X, Y)); X=1:99999) -sizeof(dimb) -``` - -The size should be: -```@ansi diskarray -GB = (sizeof(eltype(dimb)) * prod(size(dimb))) / 1e9 -``` - -I'm writing this on a laptop with only 32Gb or ram, but this runs instantly. - -The trick is nothing happens until we index: - -```@ansi diskarray -diska.getindex_count -``` - -These are just access for printing in the repl! - -When we actually get data the calulations happen, -and for real disk arrays the chunked reads too: - -```@ansi diskarray -dimb[X=1:100, Y=1:10] -``` - diff --git a/docs/src/extending_dd.md b/docs/src/extending_dd.md index 23c7a76ba..f293b0b71 100644 --- a/docs/src/extending_dd.md +++ b/docs/src/extending_dd.md @@ -89,3 +89,42 @@ and `Type`. Not calling `format` in the outer constructors of an `AbstractDimArray` has undefined behaviour. + + +## Interfaces.jl interterface testing + +DimensionalData defines explicit, testable Interfaces.jl interfaces: +`DimArrayInterface` and `DimStackInterface`. + +::: tabs + +== array + +This is the implementation definition for `DimArray`: + +````@ansi interfaces +using DimensionalData, Interfaces +@implements DimensionalData.DimArrayInterface{(:refdims,:name,:metadata)} DimArray [rand(X(10), Y(10)), zeros(Z(10))] +```` + +See the [`DimArrayInterface`](@ref) docs for options. We can test it with: + +````@ansi interfaces +Interfaces.test(DimensionalData.DimArrayInterface) +```` + +== stack + +The implementation definition for `DimStack`: + +````@ansi interfaces +@implements DimensionalData.DimStackInterface{(:refdims,:metadata)} DimStack [DimStack(zeros(Z(10))), DimStack(rand(X(10), Y(10))), DimStack(rand(X(10), Y(10)), rand(X(10)))] +```` + +See the [`DimStackInterface`](@ref) docs for options. We can test it with: + +````@ansi interfaces +Interfaces.test(DimensionalData.DimStackInterface) +```` + +::: diff --git a/docs/src/get_info.md b/docs/src/get_info.md index 31d9e1b97..eb713ac22 100644 --- a/docs/src/get_info.md +++ b/docs/src/get_info.md @@ -1,7 +1,7 @@ # Getters DimensionalData.jl defines consistent methods to retreive information -from objects like `DimArray`, `DimStack`, `Tuple`s of `Dimension` +from objects like `DimArray`, `DimStack`, `Tuple`s of `Dimension`, `Dimension` and `LookupArray`. First we will define an example `DimArray`. diff --git a/docs/src/groupby.md b/docs/src/groupby.md index f0abf37dd..5172c0a57 100644 --- a/docs/src/groupby.md +++ b/docs/src/groupby.md @@ -85,20 +85,13 @@ yearmonthday.(tempo) == custom -We can create our own anonymous function that return tuples +We can create our own function that return tuples ````@example groupby yearday(x) = year(x), dayofyear(x) -yearhour(x) = year(x), hour(x) ```` -And you can probably guess what they do: - -````@ansi groupby -yearhour.(tempo) -```` - -== yearday +You can probably guess what it does: ````@ansi groupby yearday.(tempo) @@ -119,13 +112,13 @@ A = rand(X(1:0.01:2), Ti(tempo)) == basic -And group it by month: +Group by month, using the `month` function: ````@ansi groupby groups = groupby(A, Ti=>month) ```` -We take the mean of each group by broadcasting over them : +We can take the mean of each group by broadcasting over them : ````@ansi groupby mean.(groups) diff --git a/docs/src/index.md b/docs/src/index.md index b4c5e6d43..a7878e724 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -5,7 +5,7 @@ layout: home hero: name: "DimensionalData.jl" - text: "Datasets with named dimensions" + text: "Julia Datasets with named dimensions" tagline: High performance name indexing for Julia image: src: 'logoDD.png' @@ -13,12 +13,9 @@ hero: - theme: brand text: Getting Started link: /basics - - theme: alt - text: Examples - link: /api-examples - theme: alt text: API reference - link: /reference + link: /api/reference features: - icon: 3d-scale title: Intelligent Indexing @@ -30,4 +27,4 @@ features: title: Seamlessly integrated with the julia ecosystem details: Works with base methods, extended in many packages blah --- -``` \ No newline at end of file +``` diff --git a/docs/src/integrations.md b/docs/src/integrations.md index 637d9819f..be75b0e6d 100644 --- a/docs/src/integrations.md +++ b/docs/src/integrations.md @@ -1,8 +1,6 @@ -# Integrations +# Ecosystem -## Spatial sciences - -### Rasters.jl +## Rasters.jl [Raster.jl](https://rafaqz.github.io/Rasters.jl/stable) extends DD for geospatial data manipulation, providing file load/save for @@ -18,7 +16,7 @@ GIS tools and dependencies. A `Raster` is a `AbstractDimArray`, a `RasterStack` is a `AbstractDimStack`, and `Projected` and `Mapped` are `AbstractSample` lookups. -### YAXArrays.jl +## YAXArrays.jl [YAXArrays.jl](https://juliadatacubes.github.io/YAXArrays.jl/dev/) is another spatial data package aimmed more at (very) large datasets. It's functionality @@ -27,13 +25,11 @@ and we work closely with the developers. `YAXArray` is a `AbstractDimArray` and inherits its behaviours. -### ClimateBase.jl +## ClimateBase.jl [ClimateBase.jl](https://juliaclimate.github.io/ClimateBase.jl/dev/) Extends DD with methods for analysis of climate data. -## Statistics - ## ArviZ.jl [ArviZ.jl](https://arviz-devs.github.io/ArviZ.jl/dev/) @@ -41,24 +37,20 @@ Is a julia package for exploratory analysis of Bayesian models. An `ArviZ.Dataset` is an `AbstractDimStack`! -## Optimization - -### JuMP.jl +## JuMP.jl [JuMP.jl](https://jump.dev/) is a powerful omptimisation DSL. It defines its own named array types but now accepts any `AbstractDimArray` too, through a package extension. -## Simulations - -### CryoGrid.jl +## CryoGrid.jl [CryoGrid.jl](https://juliahub.com/ui/Packages/General/CryoGrid) A Juia implementation of the CryoGrid permafrost model. `CryoGridOutput` uses `DimArray` for views into output data. -### DynamicGrids.jl +## DynamicGrids.jl [DynamicGrids.jl](https://github.com/cesaraustralia/DynamicGrids.jl) is a spatial simulation engine, for cellular automata and spatial process @@ -68,15 +60,13 @@ All DynamicGrids.jl `Outputs` are `<: AbstractDimArray`, and `AbstractDimArray` are used for auxiliary data to allow temporal synchronisation during simulations. Notably, this all works on GPUs! -## Analysis - -### AstroImages.jl +## AstroImages.jl [AstroImages.jl](http://juliaastro.org/dev/modules/AstroImages) Provides tools to load and visualise astromical images. `AstroImage` is `<: AbstractDimArray`. -### TimeseriesTools.jl +## TimeseriesTools.jl [TimeseriesTools.jl](https://juliahub.com/ui/Packages/General/TimeseriesTools) Uses `DimArray` for time-series data. diff --git a/docs/src/object_modification.md b/docs/src/object_modification.md index 72fbb8292..7065a135d 100644 --- a/docs/src/object_modification.md +++ b/docs/src/object_modification.md @@ -1,7 +1,7 @@ # Modifying objects DimensionalData.jl objects are all `struct` rather than -`mutable struct`. The only things you can modify in-place +`mutable struct`. The only things you can modify in-place are the values of the contained arrays or metadata `Dict`s if they exist. diff --git a/docs/src/selectors.md b/docs/src/selectors.md index bc51058f3..d399d297b 100644 --- a/docs/src/selectors.md +++ b/docs/src/selectors.md @@ -1,4 +1,4 @@ -# Selectors +## Selectors As well as choosing dimensions by name, we can also select values in them. @@ -19,33 +19,34 @@ Then we can use [`Selector`](@ref) to select values from the array: == At -These are single value selectors, `At` and `Near`: +[`At(x)`](@ref) gets the index or indices exactly matching the passed in value/s. ````@ansi selectors A[X=At(1.2), Y=At(:c)] ```` -[`At`](@ref) can also take vectors and ranges: +Or within a tolerance: ````@ansi selectors -A[X=At(1.2:0.2:1.5), Y=At([:a, :c])] +A[X=At(0.99:0.201:1.5; atol=0.05)] ```` -Or specify tolerance: +[`At`](@ref) can also take vectors and ranges: ````@ansi selectors -A[X=At(0.99:0.201:1.5; atol=0.05)] +A[X=At(1.2:0.2:1.5), Y=At([:a, :c])] ```` == Near -[`Near`](@ref) finds the nearest value in the lookup: +[`Near(x)`](@ref) gets the closest index to the passed in value(s), +indexing with an `Int`. ````@ansi selectors A[X=Near(1.245)] ```` -`Near` can also take vectors and ranges: +`Near` can also take vectors and ranges, which indexes with a `Vector{Int}` ````@ansi selectors A[X=Near(1.1:0.25:1.5)] @@ -53,9 +54,9 @@ A[X=Near(1.1:0.25:1.5)] == Contains -[`Contains`](@ref) finds the interval that contains a value. +[`Contains(x)`](@ref) get indices where the value x falls within an interval in the lookup. -First set the `X` axis to be `Interals` +First set the `X` axis to be `Intervals`: ````@ansi selectors using DimensionalData.LookupArrays @@ -63,11 +64,13 @@ A_intervals = set(A, X => Intervals(Start())) intervalbounds(A_intervals, X) ```` +With a single value it is like indexing with `Int` + ````@ansi selectors A_intervals[X=Contains(1.245)] ```` -`Contains` can also take vectors and ranges: +`Contains` can also take vectors and ranges, which is lick indexing with `Vector{Int}` ````@ansi selectors A_intervals[X=Contains(1.1:0.25:1.5)] @@ -76,17 +79,28 @@ A_intervals[X=Contains(1.1:0.25:1.5)] == .. `..` or `IntervalSets.Interval` selects a range of values: +`..` is like indexing with a `UnitRange`: + +````@ansi selectors +A[X=1.2 .. 1.6] +```` ````@ansi selectors -A[X=1.2 .. 1.7] +using IntervalSets +A[X=OpenInterval(1.2 .. 1.6)] +```` + +````@ansi selectors +A[X=Interval{:close,:open}(1.2 .. 1.6)] ```` == Touches [`Touches`](@ref) is like `..`, but for `Intervals` it will include -intervals touched by the selected interval, not inside it. +intervals touched by the selected interval, not inside it. This usually means including zero, one or two cells more than `..` +`Touches` is like indexing with a `UnitRange` ````@ansi selectors A_intervals[X=Touches(1.1, 1.5)] @@ -95,7 +109,8 @@ A_intervals[X=1.1 .. 1.5] == Where -[`Where`](@ref) uses a function of the lookup values: +[`Where(f)`](@ref) filter the array axis by a function of the dimension index values. +`Where` is like indexing with a `Vector{Bool}`: ````@ansi selectors A[X=Where(>=(1.5)), Y=Where(x -> x in (:a, :c))] @@ -103,7 +118,8 @@ A[X=Where(>=(1.5)), Y=Where(x -> x in (:a, :c))] == Not -[`Not`](@ref) takes the opposite of whatever it wraps: +`Not(x)` get all indices _not_ selected by `x`, which can be another selector. +`Not` is like indexing with a `Vector{Bool}`. ````@ansi selectors A[X=Not(Near(1.3)), Y=Not(Where(in((:a, :c))))] @@ -111,108 +127,56 @@ A[X=Not(Near(1.3)), Y=Not(Where(in((:a, :c))))] ::: -The full set is details in this table. - -| Selector | Description | Indexing style | -| :---------------------- | :--------------------------------------------------------------------------- |------------------ | -| [`At(x)`](@ref) | get the index exactly matching the passed in value(s) | `Int/Vector{Int}` | -| [`Near(x)`](@ref) | get the closest index to the passed in value(s) | `Int/Vector{Int}` | -| [`Contains(x)`](@ref) | get indices where the value x falls within an interval in the lookup | `Int/Vector{Int}` | -| [`Where(f)`](@ref) | filter the array axis by a function of the dimension index values. | `Vector{Bool}` | -| `Not(x)` | get all indices _not_ selected by `x`, which can be another selector. | `Vector{Bool}` | -| `a .. b` | get all indices between two values, inclusively. | `UnitRange` | -| `OpenInterval(a, b)` | get all indices between `a` and `b`, exclusively. | `UnitRange` | -| `Interval{A,B}(a, b)` | get all indices between `a` and `b`, as `:closed` or `:open`. | `UnitRange` | -| [`Touches(a, b)`](@ref) | like `..` but includes all cells touched by the interval, not just inside it | `UnitRange` | - -Note: `At`, `Near` and `Contains` can wrap either single values or an -`AbstractArray` of values, to select one index with an `Int` or multiple -indices with a `Vector{Int}`. +## Lookups - -# Lookups - -Selectors find indices in the `LookupArray` of each dimension. +Selectors find indices in the `LookupArray` of each dimension. LookupArrays wrap other `AbstractArray` (often `AbstractRange`) but add aditional traits to facilitate fast lookups or specifing point or interval behviour. These are usually detected automatically. -Some common `LookupArray` that are: - -| LookupArray | Description | -| :---------------------- | :----------------------------------------------------------------------------------------------------------- | -| [`Sampled(x)`](@ref) | values sampled along an axis - may be `Ordered`/`Unordered`, `Intervals`/`Points`, and `Regular`/`Irregular` | -| [`Categorical(x)`](@ref) | a categorical lookup that holds categories, and may be ordered | -| [`Cyclic(x)`](@ref) | an `AbstractSampled` lookup for cyclical values. | -| [`NoLookup(x)`](@ref) | no lookup values provided, so `Selector`s will not work. Not show in repl printing. | - -````@example lookuparrays +````@example selectors using DimensionalData.LookupArrays ```` +::: tabs -## Lookup autodetection - -When we define an array, extra properties are detected: - -````@ansi lookuparrays -A = DimArray(rand(7, 5), (X(10:10:70), Y([:a, :b, :c, :d, :e]))) -```` - -This array has a `Sampled` lookup with `ForwardOrdered` `Regular` -`Points` for `X`, and a `Categorical` `ForwardOrdered` for `Y`. +== Sampled lookups -Most lookup types and properties are detected automatically like this -from the arrays and ranges used. +[`Sampled(x)`](@ref) lookups hold values sampled along an axis. +They may be `Ordered`/`Unordered`, `Intervals`/`Points`, and `Regular`/`Irregular`. -- Arrays and ranges of `String`, `Symbol` and `Char` are set to `Categorical` lookup. - - `order` is detected as `Unordered`, `ForwardOrdered` or `ReverseOrdered` -- Arrays and ranges of `Number`, `DateTime` and other things are set to `Sampled` lookups. - - `order` is detected as `Unordered`, `ForwardOrdered` or `ReverseOrdered`. - - `sampling` is set to `Points()` unless the values are `IntervalSets.Interval`, - then `Intervals(Center())` is used. - - `span` is detected as `Regular(step(range))` for `AbstractRange` and - `Irregular(nothing, nothing)` for other `AbstractArray`, where `nothing, - nothing` are the unknown outer bounds of the lookup. They are not needed - for `Points` as the outer values are the outer bounds. But they can be - specified manually for `Intervals` - - Emtpy dimensions or dimension types are assigned `NoLookup()` ranges that - can't be used with selectors as they hold no values. +Most of these properties are usually detected autoatically, +but here we create a [`Sampled`](@ref) lookup manually: -## Specifying properties +````@ansi selectors +l = Sampled(10.0:10.0:100.0; order=ForwardOrdered(), span=Regular(10.0), sampling=Intervals(Start())) +```` -We can also override properties by adding keywords to a `Dimension` constructor: +TO specify `Irregular` `Intervals` we should include the outer bounds of the +lookup, as we cant determine them from the vector. -````@ansi lookuparrays -using DimensionalData.LookupArrays -rand(X(10:20:100; sampling=Intervals(Start())), Y([:a, :b, :c, :d, :e]; order=Unordered())) +````@ansi selectors +l = Sampled([13, 8, 5, 3, 2, 1]; order=ForwardOrdered(), span=Irregular(1, 21), sampling=Intervals(Start())) ```` -And they will be passed to the detected `LookupArray` typed - here `Sampled` and -`Categorical`. Anything we skip will be detected automatically. +== Categorical lookup -Finally, we can fully lookup properties. You may want to do this in a -package to avoid the type instability and other costs of the automatic checks. -Any skippied fields will still be auto-detected. Here we skip `span` in -`Sampled` as there is no cost to detecting it from a `range`. You can see -`AutoSpan` in the show output after we construct it - this will be replaced -when the `DimArray` is constructed. +[`Categorical(x)`](@ref) a categorical lookup that holds categories, +and may be ordered. -````@ansi lookuparrays -using DimensionalData.LookupArrays -x = X(Sampled(10:20:100; order=ForwardOrdered(), sampling=Intervals(Start()), metadata=NoMetadata())) -y = Y(Categorical([1, 2, 3]; order=ForwardOrdered(), metadata=NoMetadata())) -rand(x, y) +Create a [`Categorical`](@ref) lookup manually + +````@ansi selectors +l = Categorical(["mon", "tue", "weds", "thur", "fri", "sat", "sun"]; order=Unordered()) ```` -Some other lookup types are never detected, and we have to specify them -manually. +== Cyclic lookups -## `Cyclic` lookups +[`Cyclic(x)`](@ref) an `AbstractSampled` lookup for cyclical values. -Create a `Cyclic` lookup that cycles over 12 months. +Create a [`Cyclic`](@ref) lookup that cycles over 12 months. -````@ansi lookuparrays +````@ansi selectors using Dates l = Cyclic(DateTime(2000):Month(1):DateTime(2000, 12); cycle=Month(12), sampling=Intervals(Start())) ```` @@ -220,43 +184,92 @@ l = Cyclic(DateTime(2000):Month(1):DateTime(2000, 12); cycle=Month(12), sampling There is a shorthand to make a `DimArray` frome a `Dimension` with a function of the lookup values. Here we convert the values to the month names: -````@ansi lookuparrays +````@ansi selectors A = DimArray(monthabbr, X(l)) ```` Now we can select any date and get the month: -````@ansi lookuparrays +````@ansi selectors A[At(DateTime(2005, 4))] A[At(DateTime(3047, 9))] ```` +== NoLookup + +[`NoLookup(x)`](@ref) no lookup values provided, so `Selector`s will not work. +Whe you create a `DimArray` without a lookup array, `NoLookup` will be used. +It is also not show in repl printing. + +Here we create a [`NoLookup`](@ref): + +````@ansi selectors +l = NoLookup() +typeof(l) +```` + +Or even fill in the axis: +````@ansi selectors +l = NoLookup(Base.OneTo(10)) +typeof(l) +```` + +::: + +## Lookup autodetection + +When we define an array, extra properties are detected: + +````@ansi selectors +A = DimArray(rand(7, 5), (X(10:10:70), Y([:a, :b, :c, :d, :e]))) +```` + +This array has a `Sampled` lookup with `ForwardOrdered` `Regular` +`Points` for `X`, and a `Categorical` `ForwardOrdered` for `Y`. + +Most lookup types and properties are detected automatically like this +from the arrays and ranges used. + +- Arrays and ranges of `String`, `Symbol` and `Char` are set to `Categorical` lookup. + - `order` is detected as `Unordered`, `ForwardOrdered` or `ReverseOrdered` +- Arrays and ranges of `Number`, `DateTime` and other things are set to `Sampled` lookups. + - `order` is detected as `Unordered`, `ForwardOrdered` or `ReverseOrdered`. + - `sampling` is set to `Points()` unless the values are `IntervalSets.Interval`, + then `Intervals(Center())` is used. + - `span` is detected as `Regular(step(range))` for `AbstractRange` and + `Irregular(nothing, nothing)` for other `AbstractArray`, where `nothing, + nothing` are the unknown outer bounds of the lookup. They are not needed + for `Points` as the outer values are the outer bounds. But they can be + specified manually for `Intervals` + - Emtpy dimensions or dimension types are assigned `NoLookup()` ranges that + can't be used with selectors as they hold no values. + ## `DimSelector` -We can also index with arrays of selectors [`DimSelectors`](@ref). -These are like `CartesianIndices` or [`DimIndices`](@ref) but holding +We can also index with arrays of selectors [`DimSelectors`](@ref). +These are like `CartesianIndices` or [`DimIndices`](@ref) but holding `Selectors` `At`, `Near` or `Contains`. -````@ansi lookuparrays +````@ansi selectors A = rand(X(1.0:0.2:2.0), Y(10:2:20)) ```` We can define another array with partly matching indices -````@ansi lookuparrays +````@ansi selectors B = rand(X(1.0:0.04:2.0), Y(20:-1:10)) ```` And we can simply select values from `B` with selectors from `A`: -````@ansi lookuparrays +````@ansi selectors B[DimSelectors(A)] ```` If the lookups aren't aligned we can use `Near` instead of `At`, which like doing a nearest neighor interpolation: -````@ansi lookuparrays +````@ansi selectors C = rand(X(1.0:0.007:2.0), Y(10.0:0.9:30)) C[DimSelectors(A; selectors=Near)] ```` diff --git a/docs/src/stacks.md b/docs/src/stacks.md index 8ba4f6c20..a006d9a10 100644 --- a/docs/src/stacks.md +++ b/docs/src/stacks.md @@ -1,4 +1,4 @@ -# Stacks +## DimStacks An `AbstractDimStack` represents a collection of `AbstractDimArray` layers that share some or all dimensions. For any two layers, a dimension @@ -14,7 +14,7 @@ st = DimStack((a=rand(x, y), b=rand(x, y), c=rand(y), d=rand(x))) The behaviour is somewhere ebetween a `NamedTuple` and an `AbstractArray`. -::::tabs +::: tabs == getting layers @@ -50,24 +50,156 @@ The layers without another dimension are now zero-dimensional: st[X=At(2.0)] ```` -:::: +::: -Indexing with `Dimensions`, `Selectors` works as with an `AbstractDimArray`, -except it indexes for all layers at the same time, returning either a new -small `AbstractDimStack` or a scalar value, if all layers are scalars. + +## Reducing functions Base functions like `mean`, `maximum`, `reverse` are applied to all layers of the stack. +::: tabs + +== maximum + +````@ansi stack +maximum(st) +maximum(st; dims=Y) +```` + +== minimum + +````@ansi stack +minimum(st) +minimum(st; dims=Y) +```` + +== sum + +````@ansi stack +sum(st) +sum(st; dims=Y) +```` + +== prod + +````@ansi stack +prod(st) +prod(st; dims=Y) +```` + +== mean + +````@ansi stack +mean(st) +mean(st; dims=Y) +```` + +== std + +````@ansi stack +std(st) +std(st; dims=Y) +```` + +== var + +````@ansi stack +var(st) +var(st; dims=Y) +```` + +== reduce + +````@ansi stack +reduce(+, st) +# reduce(+, st; dims=Y) # broken +```` + +== extrema + +````@ansi stack +extrema(st) +extrema(st; dims=Y) # Kind of broken? should :d be tuples too? +```` + +== dropdims + +````@ansi stack +sum_st = sum(st; dims=Y) +dropdims(sum_st; dims=Y) +```` + +::: + `broadcast_dims` broadcasts functions over any mix of `AbstractDimStack` and `AbstractDimArray` returning a new `AbstractDimStack` with layers the size of the largest layer in the broadcast. This will work even if dimension permutation does not match in the objects. -# Performance -Indexing stack is fast - indexing a single value return a `NamedTuple` from all layers -usingally, measures in nanoseconds. There are some compilation overheads to this -though, and stacks with very many layers can take a long time to compile. +::: tabs + +== rotl90 + +Only atrix layers can be rotaed + +````@ansi stack +rotl90(st[(:a, :b)]) +rotl90(st[(:a, :b)], 2) +```` + +== rotr90 + +````@ansi stack +rotr90(st[(:a, :b)]) +rotr90(st[(:a, :b)], 2) +```` + +== rot180 + +````@ansi stack +rot180(st[(:a, :b)]) +rot180(st[(:a, :b)], 2) +```` + +== permutedims + +````@ansi stack +permutedims(st) +permutedims(st, (2, 1)) +permutedims(st, (Y, X)) +```` + +== transpose + +````@ansi stack +transpose(st) +```` + +== adjoint + +````@ansi stack +adjoint(st) +st' +```` + +== PermutedDimsArray + +````@ansi stack +PermutedDimsArray(st, (2, 1)) +PermutedDimsArray(st, (Y, X)) +```` + +::: + +## Performance + +Indexing stack is fast - indexing a single value return a `NamedTuple` from all +layers is usally measures in nanoseconds, and no slower than manually indexing +into each parent array directly. + +There are some compilation overheads to this though, and stacks with very many +layers can take a long time to compile. ````@ansi stack using BenchmarkTools diff --git a/docs/src/tables.md b/docs/src/tables.md index f8d01acae..9b9dd0f0c 100644 --- a/docs/src/tables.md +++ b/docs/src/tables.md @@ -29,7 +29,7 @@ Define some dimensions: x, y, c = X(1:10), Y(1:10), Dim{:category}('a':'z') ```` -::::tabs +::: tabs == create a `DimArray` @@ -43,11 +43,11 @@ A = rand(x, y, c; name=:data) st = DimStack((data1 = rand(x, y), data2=rand(x, y, c))) ```` -:::: +::: ## Converting to DataFrame -::::tabs +::: tabs == array default @@ -66,25 +66,26 @@ and one for each layer: DataFrame(st) ```` -== layerfrom +== layersfrom Using [`DimTable`](@ref) we can specify that a `DimArray` should take columns from one of the dimensions: ````@ansi dataframe DataFrame(DimTable(A; layersfrom=:category)) +DimStack(A; layersfrom=:category) ```` -== stack `DimTable` +== mergedims Using [`DimTable`](@ref) we can merge the spatial dimensions so the column is a tuple: -````@ansi mergedims +````@ansi dataframe DataFrame(DimTable(st; mergedims=(:X, :Y)=>:XY)) ```` -:::: +::: ## Converting to CSV diff --git a/src/DimensionalData.jl b/src/DimensionalData.jl index b80745443..f0970e5ff 100644 --- a/src/DimensionalData.jl +++ b/src/DimensionalData.jl @@ -69,7 +69,7 @@ export AbstractDimTable, DimTable export DimIndices, DimSelectors, DimPoints, #= deprecated =# DimKeys # getter methods -export dims, refdims, metadata, name, lookup, bounds +export dims, refdims, metadata, name, lookup, bounds, val # Dimension/Lookup primitives export dimnum, hasdim, hasselection, otherdims @@ -77,7 +77,7 @@ export dimnum, hasdim, hasselection, otherdims # utils export set, rebuild, reorder, modify, broadcast_dims, broadcast_dims!, mergedims, unmergedims -export groupby, season, months, hours, yeardays, monthdays, intervals, ranges +export groupby, seasons, months, hours, yeardays, monthdays, intervals, ranges const DD = DimensionalData diff --git a/src/groupby.jl b/src/groupby.jl index b6a0a0420..5e0befb4e 100644 --- a/src/groupby.jl +++ b/src/groupby.jl @@ -135,7 +135,7 @@ Base.show(io::IO, bins::CyclicBins) = yearhour(x) = year(x), hour(x) -season(; start=December, kw...) = months(3; start, kw...) +seasons(; start=December, kw...) = months(3; start, kw...) months(step; start=January, labels=Dict(1:12 .=> monthabbr.(1:12))) = CyclicBins(month; cycle=12, step, start, labels) hours(step; start=0, labels=nothing) = CyclicBins(hour; cycle=24, step, start, labels) yearhours(step; start=0, labels=nothing) = CyclicBins(yearhour; cycle=24, step, start, labels) diff --git a/src/stack/methods.jl b/src/stack/methods.jl index 266076723..fffe3a49a 100644 --- a/src/stack/methods.jl +++ b/src/stack/methods.jl @@ -172,17 +172,29 @@ end # Methods with no arguments that return a DimStack for (mod, fnames) in - (:Base => (:inv, :adjoint, :transpose), :LinearAlgebra => (:Transpose,)) + (:Base => (:inv, :adjoint, :transpose, :permutedims, :PermutedDimsArray), :LinearAlgebra => (:Transpose,)) for fname in fnames - @eval ($mod.$fname)(s::AbstractDimStack) = map(A -> ($mod.$fname)(A), s) + @eval function ($mod.$fname)(s::AbstractDimStack) + map(s) do l + ndims(l) > 1 ? ($mod.$fname)(l) : l + end + end end end # Methods with an argument that return a DimStack -for fname in (:rotl90, :rotr90, :rot180, :PermutedDimsArray, :permutedims) +for fname in (:rotl90, :rotr90, :rot180) @eval (Base.$fname)(s::AbstractDimStack, args...) = map(A -> (Base.$fname)(A, args...), s) end +for fname in (:PermutedDimsArray, :permutedims) + @eval function (Base.$fname)(s::AbstractDimStack, perm) + map(s) do l + lperm = dims(l, dims(s, perm)) + length(lperm) > 1 ? (Base.$fname)(l, lperm) : l + end + end +end # Methods with keyword arguments that return a DimStack for (mod, fnames) in @@ -241,7 +253,7 @@ for fname in (:one, :oneunit, :zero, :copy) end end -Base.reverse(s::AbstractDimStack; dims=1) = map(A -> reverse(A; dims=dims), s) +Base.reverse(s::AbstractDimStack; dims=:) = map(A -> reverse(A; dims=dims), s) # Random Random.Sampler(RNG::Type{<:AbstractRNG}, st::AbstractDimStack, n::Random.Repetition) = diff --git a/src/stack/show.jl b/src/stack/show.jl index f000f88d3..8f9de50de 100644 --- a/src/stack/show.jl +++ b/src/stack/show.jl @@ -35,7 +35,7 @@ function print_layers_block(io, mime, stack; blockwidth, displaywidth) end newblockwidth = blockwidth for key in keys(layers) - newblockwidth = min(displaywidth - 2, max(blockwidth, length(sprint(print_layer, stack, key, keylen)))) + newblockwidth = min(displaywidth - 2, max(newblockwidth, length(sprint(print_layer, stack, key, keylen)))) end print_block_separator(io, "layers", blockwidth, newblockwidth) println(io) diff --git a/src/stack/stack.jl b/src/stack/stack.jl index fbbf69363..38f6e2925 100644 --- a/src/stack/stack.jl +++ b/src/stack/stack.jl @@ -191,10 +191,10 @@ end """ DimStack <: AbstractDimStack - DimStack(data::AbstractDimArray...) - DimStack(data::Tuple{Vararg{AbstractDimArray}}) - DimStack(data::NamedTuple{Keys,Vararg{AbstractDimArray}}) - DimStack(data::NamedTuple, dims::DimTuple; metadata=NoMetadata()) + DimStack(data::AbstractDimArray...; kw...) + DimStack(data::Tuple{Vararg{AbstractDimArray}}; kw...) + DimStack(data::NamedTuple{Keys,Vararg{AbstractDimArray}}; kw...) + DimStack(data::NamedTuple, dims::DimTuple; metadata=NoMetadata(); kw...) DimStack holds multiple objects sharing some dimensions, in a `NamedTuple`. @@ -211,6 +211,10 @@ Notably, their behaviour lies somewhere between a `DimArray` and a `NamedTuple`: - many base and `Statistics` methods (`sum`, `mean` etc) will work as for a `DimArray` again removing the need to use `map`. +function DimStack(A::AbstractDimArray; + layersfrom=nothing, name=nothing, metadata=metadata(A), refdims=refdims(A), kw... +) + For example, here we take the mean over the time dimension for all layers : ```julia @@ -272,9 +276,9 @@ struct DimStack{L,D<:Tuple,R<:Tuple,LD<:Union{NamedTuple,Nothing},M,LM<:NamedTup metadata::M layermetadata::LM end -DimStack(@nospecialize(das::AbstractBasicDimArray...); kw...) = DimStack(collect(das); kw...) -DimStack(@nospecialize(das::Tuple{Vararg{AbstractBasicDimArray}}); kw...) = DimStack(collect(das); kw...) -function DimStack(@nospecialize(das::AbstractArray{<:AbstractBasicDimArray}); +DimStack(@nospecialize(das::AbstractDimArray...); kw...) = DimStack(collect(das); kw...) +DimStack(@nospecialize(das::Tuple{Vararg{AbstractDimArray}}); kw...) = DimStack(collect(das); kw...) +function DimStack(@nospecialize(das::AbstractArray{<:AbstractDimArray}); metadata=NoMetadata(), refdims=(), ) keys_vec = uniquekeys(das) @@ -287,7 +291,20 @@ function DimStack(@nospecialize(das::AbstractArray{<:AbstractBasicDimArray}); DimStack(data, dims, refdims, layerdims, metadata, layermetadata) end -function DimStack(das::NamedTuple{<:Any,<:Tuple{Vararg{AbstractBasicDimArray}}}; +function DimStack(A::AbstractDimArray; + layersfrom=nothing, metadata=metadata(A), refdims=refdims(A), kw... +) + layers = if isnothing(layersfrom) + keys = DD.name(A) in (NoName(), Symbol(""), Name(Symbol(""))) ? (:layer1,) : (DD.name(A),) + NamedTuple{keys}((A,)) + else + keys = Tuple(_layerkeysfromdim(A, layersfrom)) + slices = Tuple(eachslice(A; dims=layersfrom)) + NamedTuple{keys}(slices) + end + return DimStack(layers; refdims=refdims, metadata=metadata, kw...) +end +function DimStack(das::NamedTuple{<:Any,<:Tuple{Vararg{AbstractDimArray}}}; data=map(parent, das), dims=combinedims(collect(das)), layerdims=map(basedims, das), refdims=(), metadata=NoMetadata(), layermetadata=map(DD.metadata, das) ) @@ -306,3 +323,13 @@ end @noinline _stack_size_mismatch() = throw(ArgumentError("Arrays must have identical axes. For mixed dimensions, use DimArrays`")) layerdims(s::DimStack{<:Any,<:Any,<:Any,Nothing}, key::Symbol) = dims(s) + +function _layerkeysfromdim(A, dim) + map(index(A, dim)) do x + if x isa Number + Symbol(string(DD.dim2key(dim), "_", x)) + else + Symbol(x) + end + end +end diff --git a/src/utils.jl b/src/utils.jl index 91bba96c6..5bb56d7b3 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -112,12 +112,21 @@ all passed in arrays in the order in which they are found. This is like broadcasting over every slice of `A` if it is sliced by the dimensions of `B`. """ -function broadcast_dims(f, As::AbstractDimArray...) +function broadcast_dims(f, As::AbstractBasicDimArray...) dims = combinedims(As...) T = Base.Broadcast.combine_eltypes(f, As) broadcast_dims!(f, similar(first(As), T, dims), As...) end +function broadcast_dims(f, As::Union{AbstractDimStack,AbstractBasicDimArray}...) + st = _firststack(As...) + nts = _as_extended_nts(NamedTuple(st), As...) + layers = map(nts...) do as... + broadcast_dims(f, as...) + end + rebuild(st, layers) +end + """ broadcast_dims!(f, dest::AbstractDimArray, sources::AbstractDimArray...) => dest @@ -132,7 +141,7 @@ which they are found. - `dest`: `AbstractDimArray` to update. - `sources`: `AbstractDimArrays` to broadcast over with `f`. """ -function broadcast_dims!(f, dest::AbstractDimArray{<:Any,N}, As::AbstractDimArray...) where {N} +function broadcast_dims!(f, dest::AbstractDimArray{<:Any,N}, As::AbstractBasicDimArray...) where {N} As = map(As) do A isempty(otherdims(A, dims(dest))) || throw(DimensionMismatch("Cannot broadcast over dimensions not in the dest array")) # comparedims(dest, dims(A, dims(dest))) @@ -193,3 +202,13 @@ function uniquekeys(keys::Tuple{Symbol,Vararg{Symbol}}) end uniquekeys(t::Tuple) = ntuple(i -> Symbol(:layer, i), length(t)) uniquekeys(nt::NamedTuple) = keys(nt) + +_as_extended_nts(nt::NamedTuple{K}, A::AbstractDimArray, As...) where K = + (NamedTuple{K}(ntuple(x -> A, length(K))), _as_extended_nts(nt, As...)...) +function _as_extended_nts(nt::NamedTuple{K}, st::AbstractDimStack, As...) where K + extended_layers = map(layers(st)) do l + DimExtensionArray(l, dims(st)) + end + return (extended_layers, _as_extended_nts(nt, As...)...) +end +_as_extended_nts(::NamedTuple) = () From 025752f7f1cc6a6af6480237a25da2e55a236ea8 Mon Sep 17 00:00:00 2001 From: Lazaro Alonso Date: Sun, 25 Feb 2024 18:48:00 +0100 Subject: [PATCH 046/108] Improve hero (#650) * more, more, more ... * fixes some docstest, something buggy with printing is happening --- .gitignore | 4 +- docs/Project.toml | 2 + docs/logo.jl | 12 ++++-- docs/make.jl | 18 +++++++-- docs/src/.vitepress/config.mts | 42 +++++++++---------- docs/src/.vitepress/theme/style.css | 6 +-- docs/src/basics.md | 40 +++++++++++++++--- docs/src/get_info.md | 4 +- docs/src/index.md | 15 ++++--- src/Dimensions/dimension.jl | 45 ++++++++++++--------- src/Dimensions/primitives.jl | 24 ++++++----- src/LookupArrays/lookup_arrays.jl | 25 +++++++----- src/LookupArrays/selector.jl | 50 ++++++++++++++--------- src/set.jl | 63 ++++++++++++++++++----------- src/stack/stack.jl | 4 +- src/utils.jl | 4 +- 16 files changed, 227 insertions(+), 131 deletions(-) diff --git a/.gitignore b/.gitignore index 58fe77e9d..f02fc351e 100644 --- a/.gitignore +++ b/.gitignore @@ -22,4 +22,6 @@ docs/.vitepress/dist docs/.vitepress/cache docs/src/.vitepress/dist docs/src/.vitepress/cache -node_modules \ No newline at end of file +node_modules +*.png +*.svg \ No newline at end of file diff --git a/docs/Project.toml b/docs/Project.toml index 47b5c1477..65c49fdcf 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -3,6 +3,7 @@ BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf" CSV = "336ed68f-0bac-5ca0-87d4-7b16caf5d00b" CairoMakie = "13f3f980-e62b-5c42-98c6-ff1f3baf88f0" Colors = "5ae59095-9a9b-59fe-a467-6f913c188581" +CoordinateTransformations = "150eb455-5306-5404-9cee-2592286d6298" DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" Dates = "ade2ca70-3891-5945-98fb-dc099432e06a" DimensionalData = "0703355e-b756-11e9-17c0-8b28908087d0" @@ -12,5 +13,6 @@ DocumenterVitepress = "4710194d-e776-4893-9690-8d956a29c365" Extents = "411431e0-e8b7-467b-b5e0-f676ba4f2910" Interfaces = "85a1e053-f937-4924-92a5-1367d23b7b87" Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" +Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" Unitful = "1986cc42-f94f-5a68-af5c-568840ba703d" diff --git a/docs/logo.jl b/docs/logo.jl index 79b7598bc..78f4b40ed 100644 --- a/docs/logo.jl +++ b/docs/logo.jl @@ -1,7 +1,8 @@ using Colors using CairoMakie +using Random +Random.seed!(13) CairoMakie.activate!() -mkpath(joinpath(@__DIR__, "./src/public/")) rpyz = [Rect3f(Vec3f(0, j-0.8,k), Vec3f(0.1, 0.8,0.8)) for j in 1:7 for k in 1:7] @@ -9,13 +10,15 @@ rmyz = [Rect3f(Vec3f(j-0.8, 0,k), Vec3f(0.8, 0.1,0.8)) for j in 1:7 for k in 1:7] colors = ["#ff875f", "#0087d7", "#5fd7ff", "#ff5f87", "#b2b2b2", "#d75f00", "#00afaf"] +base_points = [Point3f(i,j,0) for i in range(0.5,6.0,7) for j in range(0.5,6.0,7)] +z = 2.0*rand(length(base_points)) fig = Figure(; size=(500,500), backgroundcolor=:transparent, fonts = (; regular = "Barlow")) ax = LScene(fig[1,1]; show_axis=false) wireframe!.(ax, rpyz; color = colors[3], transparency=true) # shading=NoShading # bug! -poly!.(ax, rmyz; color=colors[1], transparency=true, shading=NoShading) +poly!.(ax, rmyz; color=0.85*colorant"#ff875f", transparency=true, shading=NoShading) meshscatter!(ax, [Point3f(0.1,0.1,0.8), Point3f(0.1+7,0.1,0.8), Point3f(0.1,0.1+7,0.8), Point3f(0.1+7,0.1+7,0.8)]; color = colors[4], @@ -32,9 +35,10 @@ lines!(ax, [ Point3f(0.1+7,0.1,8), Point3f(0.1+7,0.1+7,8), ]; color = colors[2], linewidth=2, transparency=true) +meshscatter!(ax, base_points; marker=Rect3f(Vec3f(-0.5,-0.5,0), Vec3f(1,1,1)), + markersize = Vec3f.(0.5,0.5, z), color=z, colormap=tuple.(colors, 1), transparency=false) mkpath(joinpath(@__DIR__, "src", "assets")) save(joinpath(@__DIR__, "src", "assets", "logoDD.svg"), fig; pt_per_unit=0.75) save(joinpath(@__DIR__, "src", "assets", "logoDD.png"), fig; px_per_unit=2) -save(joinpath(@__DIR__, "src", "assets", "favicon.png"), fig; px_per_unit=0.25) -fig +save(joinpath(@__DIR__, "src", "assets", "favicon.png"), fig; px_per_unit=0.25) \ No newline at end of file diff --git a/docs/make.jl b/docs/make.jl index 9015d591a..31a0b791d 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -1,10 +1,18 @@ -using DocumenterVitepress ## add https://github.com/LuxDL/DocumenterVitepress.jl.git +using DocumenterVitepress using Documenter using DimensionalData +# Names are available everywhere so that [`function`](@ref) works. +# ==================== + +DocMeta.setdocmeta!(DimensionalData, :DocTestSetup, :(using DimensionalData, DimensionalData.Dimensions, DimensionalData.Dimensions.LookupArrays); recursive=true) + +# Build documentation. +# ==================== + makedocs(; sitename="DimensionalData", authors="Rafael Schouten et al.", - # modules=[DimensionalData], - # checkdocs=:all, + modules=[DimensionalData], + checkdocs=:all, format=DocumenterVitepress.MarkdownVitepress( repo = "github.com/rafaqz/DimensionalData.jl", devbranch = "main", @@ -12,10 +20,12 @@ makedocs(; sitename="DimensionalData", authors="Rafael Schouten et al.", ), draft=false, source="src", - build=joinpath(@__DIR__, "build"), + build="build", warnonly = true, ) +# Deploy built documentation. +# =========================== deploydocs(; repo="github.com/rafaqz/DimensionalData.jl", target="build", # this is where Vitepress stores its output diff --git a/docs/src/.vitepress/config.mts b/docs/src/.vitepress/config.mts index d465faf25..f86f6dc70 100644 --- a/docs/src/.vitepress/config.mts +++ b/docs/src/.vitepress/config.mts @@ -6,7 +6,6 @@ const version= '0.25.8' const VERSIONS: DefaultTheme.NavItemWithLink[] = [ { text: `v${version} (current)`, link: '/' }, { text: `Release Notes`, link: 'https://github.com/rafaqz/DimensionalData.jl/releases/' }, - // { text: `Contributing`, link: 'https://github.com/twoslashes/twoslash/blob/main/CONTRIBUTING.md' }, ] // https://vitepress.dev/reference/site-config @@ -61,9 +60,8 @@ export default defineConfig({ sidebar: [ { - text: '', + text: 'Getting Started', link: '/basics', items: [ - { text: 'Getting Started', link: '/basics' }, { text: 'Dimensions', link: '/dimensions' }, { text: 'Selectors', link: '/selectors' }, { text: 'Dimarrays', link: '/dimarrays' }, @@ -71,25 +69,27 @@ export default defineConfig({ { text: 'GroupBy', link: '/groupby' }, { text: 'Getting information', link: '/get_info' }, { text: 'Object modification', link: '/object_modification' }, - { text: 'Integrations', - items: [ - { text: 'Plots and Makie', link: '/plots' }, - { text: 'Tables and DataFrames', link: '/tables' }, - { text: 'CUDA and GPUs', link: '/cuda' }, - { text: 'DiskArrays', link: '/diskarrays' }, - { text: 'Ecosystem', link: '/integrations' }, - { text: 'Extending DimensionalData', link: '/extending_dd' }, - ], - }, - { text: 'API Reference', link: '/api/reference', - items: [ - { text: 'Dimensions Reference', link: '/api/dimensions' }, - { text: 'LookupArrays Reference', link: '/api/lookuparrays' }, - ], - }, - ] - } + ]}, + { text: 'Integrations', + items: [ + { text: 'Plots and Makie', link: '/plots' }, + { text: 'Tables and DataFrames', link: '/tables' }, + { text: 'CUDA and GPUs', link: '/cuda' }, + { text: 'DiskArrays', link: '/diskarrays' }, + { text: 'Ecosystem', link: '/integrations' }, + { text: 'Extending DimensionalData', link: '/extending_dd' }, + ], + }, + { text: 'API Reference', link: '/api/reference', + items: [ + { text: 'Dimensions Reference', link: '/api/dimensions' }, + { text: 'LookupArrays Reference', link: '/api/lookuparrays' }, + ], + }, ], + editLink: { + pattern: 'https://github.com/rafaqz/DimensionalData.jl/edit/master/docs/src/:path' + }, socialLinks: [ { icon: 'github', link: 'https://github.com/rafaqz/DimensionalData.jl' }, diff --git a/docs/src/.vitepress/theme/style.css b/docs/src/.vitepress/theme/style.css index bf94c02a5..32ae6a964 100644 --- a/docs/src/.vitepress/theme/style.css +++ b/docs/src/.vitepress/theme/style.css @@ -74,9 +74,9 @@ https://github.com/vuejs/vitepress/blob/main/src/client/theme-default/styles/var --vp-home-hero-image-background-image: linear-gradient( -45deg, - #1f1b1c 5%, - #1c1b1b 5%, - #083f5f + #dce8ee 5%, + #0087d7 35%, + #ff875f ); --vp-home-hero-image-filter: blur(40px); } diff --git a/docs/src/basics.md b/docs/src/basics.md index b784512c6..d33204b5b 100644 --- a/docs/src/basics.md +++ b/docs/src/basics.md @@ -1,19 +1,49 @@ -# Installation +## Installation -````shell -julia>] +If want to use this package you need to install it first. You can do it using the following commands: + +````julia +julia>] # ']' should be pressed pkg> add DimensionalData ```` +or + +````julia +julia> using Pkg +julia> Pkg.add("DimensionalData") +```` -Check the installed version: +Additionally, it is recommended to check the version that you have installed with the status command. -````shell +````julia julia>] pkg> status DimensionalData ```` +## Basics Start using the package: ````@example basics using DimensionalData ```` + +and create your first DimArray + +````@ansi basics +A = DimArray(rand(4,5), (a=1:4, b=1:5)) +```` + +or + +````@ansi basics +C = DimArray(rand(Int8, 10), (alpha='a':'j',)) +```` + +or maybe something a little bit more cumbersome: + +````@ansi basics +data = rand(Int8, 2,10,3) .|> abs; +B = DimArray(data, (channel=[:left, :right], time=1:10, iter=1:3)) +```` + +See details on the [`Dimensions`](/dimensions) section. \ No newline at end of file diff --git a/docs/src/get_info.md b/docs/src/get_info.md index eb713ac22..c5c0dc2f6 100644 --- a/docs/src/get_info.md +++ b/docs/src/get_info.md @@ -1,4 +1,4 @@ -# Getters +## Getters DimensionalData.jl defines consistent methods to retreive information from objects like `DimArray`, `DimStack`, `Tuple`s of `Dimension`, @@ -160,7 +160,7 @@ extent(dims(A, Y)) ::: -# Predicates +## Predicates These always return `true` or `false`. With multiple dimensions, `fale` means `!all` and `true` means `all`. diff --git a/docs/src/index.md b/docs/src/index.md index a7878e724..ec562680c 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -13,18 +13,23 @@ hero: - theme: brand text: Getting Started link: /basics + - theme: alt + text: View on Github + link: https://github.com/rafaqz/DimensionalData.jl - theme: alt text: API reference link: /api/reference features: - icon: 3d-scale title: Intelligent Indexing - details: Use names and values to retrieve data. - - icon: 3d-scale + details: It works with datasets that have named dimensions. It provides no-cost abstractions for named indexing, and fast index lookups. + link: /selectors + - icon: grid title: Powerful Array Manipulation - details: permute, broadcast whatever... - - icon: 3d-scale + details: Permutedims, reduce, mapreduce, adjoint, transpose, broadcasting etc., and groupby operations. + link: /groupby + - icon: layers title: Seamlessly integrated with the julia ecosystem - details: Works with base methods, extended in many packages blah + details: Works with base methods. If a method accepts numeric indices or dims=X in base, you should be able to use DimensionalData.jl dims. --- ``` diff --git a/src/Dimensions/dimension.jl b/src/Dimensions/dimension.jl index 2d25f0aad..73ea853dd 100644 --- a/src/Dimensions/dimension.jl +++ b/src/Dimensions/dimension.jl @@ -33,16 +33,18 @@ A = DimArray(zeros(3, 5, 12), (y, x, ti)) # output -3×5×12 DimArray{Float64,3} with dimensions: - Y Categorical{Char} Char['a', 'b', 'c'] ForwardOrdered, - X Sampled{Int64} 2:2:10 ForwardOrdered Regular Points, - Ti Sampled{DateTime} DateTime("2021-01-01T00:00:00"):Month(1):DateTime("2021-12-01T00:00:00") ForwardOrdered Regular Points +╭────────────────────────────╮ +│ 3×5×12 DimArray{Float64,3} │ +├────────────────────────────┴──────────────────────────────────────────────────────────────────────────────────────────── dims ┐ + ↓ Y Categorical{Char} ['a', 'b', 'c'] ForwardOrdered, + → X Sampled{Int64} 2:2:10 ForwardOrdered Regular Points, + ↗ Ti Sampled{DateTime} DateTime("2021-01-01T00:00:00"):Month(1):DateTime("2021-12-01T00:00:00") ForwardOrdered Regular Points +└───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ [:, :, 1] - 2 4 6 8 10 + ↓ → 2 4 6 8 10 'a' 0.0 0.0 0.0 0.0 0.0 'b' 0.0 0.0 0.0 0.0 0.0 'c' 0.0 0.0 0.0 0.0 0.0 -[and 11 more slices...] ``` For simplicity, the same `Dimension` types are also used as wrappers @@ -53,16 +55,18 @@ x = A[X(2), Y(3)] # output -12-element DimArray{Float64,1} with dimensions: - Ti Sampled{DateTime} DateTime("2021-01-01T00:00:00"):Month(1):DateTime("2021-12-01T00:00:00") ForwardOrdered Regular Points -and reference dimensions: - Y Categorical{Char} Char['c'] ForwardOrdered, - X Sampled{Int64} 4:2:4 ForwardOrdered Regular Points +╭────────────────────────────────╮ +│ 12-element DimArray{Float64,1} │ +├────────────────────────────────┴──────────────────────────────────────────────────────────────────────────────────────── dims ┐ + ↓ Ti Sampled{DateTime} DateTime("2021-01-01T00:00:00"):Month(1):DateTime("2021-12-01T00:00:00") ForwardOrdered Regular Points +└───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ 2021-01-01T00:00:00 0.0 2021-02-01T00:00:00 0.0 2021-03-01T00:00:00 0.0 2021-04-01T00:00:00 0.0 - ⋮ + 2021-05-01T00:00:00 0.0 + ⋮ + 2021-09-01T00:00:00 0.0 2021-10-01T00:00:00 0.0 2021-11-01T00:00:00 0.0 2021-12-01T00:00:00 0.0 @@ -75,13 +79,14 @@ x = A[X(Between(3, 4)), Y(At('b'))] # output -1×12 DimArray{Float64,2} with dimensions: - X Sampled{Int64} 4:2:4 ForwardOrdered Regular Points, - Ti Sampled{DateTime} DateTime("2021-01-01T00:00:00"):Month(1):DateTime("2021-12-01T00:00:00") ForwardOrdered Regular Points -and reference dimensions: - Y Categorical{Char} Char['b'] ForwardOrdered - 2021-01-01T00:00:00 … 2021-12-01T00:00:00 - 4 0.0 0.0 +╭──────────────────────────╮ +│ 1×12 DimArray{Float64,2} │ +├──────────────────────────┴────────────────────────────────────────────────────────────────────────────────────────────── dims ┐ + ↓ X Sampled{Int64} 4:2:4 ForwardOrdered Regular Points, + → Ti Sampled{DateTime} DateTime("2021-01-01T00:00:00"):Month(1):DateTime("2021-12-01T00:00:00") ForwardOrdered Regular Points +└───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ + ↓ → 2021-01-01T00:00:00 2021-02-01T00:00:00 2021-03-01T00:00:00 … 2021-10-01T00:00:00 2021-11-01T00:00:00 2021-12-01T00:00:00 + 4 0.0 0.0 0.0 0.0 0.0 0.0 ``` `Dimension` objects may have [`lookup`](@ref) and [`metadata`](@ref) fields @@ -339,7 +344,7 @@ dim = Dim{:custom}(['a', 'b', 'c']) # output -Dim{:custom} Char['a', 'b', 'c'] +custom ['a', 'b', 'c'] ``` """ struct Dim{S,T} <: Dimension{T} diff --git a/src/Dimensions/primitives.jl b/src/Dimensions/primitives.jl index dcab474db..362105424 100644 --- a/src/Dimensions/primitives.jl +++ b/src/Dimensions/primitives.jl @@ -129,14 +129,17 @@ any combination of either. julia> using DimensionalData julia> A = DimArray(ones(2, 3, 2), (X, Y, Z)) -2×3×2 DimArray{Float64,3} with dimensions: X, Y, Z +╭───────────────────────────╮ +│ 2×3×2 DimArray{Float64,3} │ +├───────────────────── dims ┤ + ↓ X, → Y, ↗ Z +└───────────────────────────┘ [:, :, 1] 1.0 1.0 1.0 1.0 1.0 1.0 -[and 1 more slices...] julia> dims(A, (X, Y)) -X, Y +↓ X, → Y ``` """ @@ -163,10 +166,10 @@ julia> using DimensionalData, .Dimensions julia> A = DimArray(ones(10, 10, 10), (X, Y, Z)); julia> commondims(A, X) -X +↓ X julia> commondims(A, (X, Z)) -X, Z +↓ X, → Z julia> commondims(A, Ti) () @@ -276,10 +279,10 @@ julia> using DimensionalData, DimensionalData.Dimensions julia> A = DimArray(ones(10, 10, 10), (X, Y, Z)); julia> otherdims(A, X) -Y, Z +↓ Y, → Z julia> otherdims(A, (Y, Z)) -X +↓ X ``` """ @inline otherdims(x, query) = begin @@ -353,11 +356,14 @@ A = ones(X(2), Y(4), Z(2)) Dimensions.swapdims(A, (Dim{:a}, Dim{:b}, Dim{:c})) # output -2×4×2 DimArray{Float64,3} with dimensions: Dim{:a}, Dim{:b}, Dim{:c} +╭───────────────────────────╮ +│ 2×4×2 DimArray{Float64,3} │ +├───────────────────── dims ┤ + ↓ a, → b, ↗ c +└───────────────────────────┘ [:, :, 1] 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 -[and 1 more slices...] ``` """ @inline swapdims(x, d1, d2, ds...) = swapdims(x, (d1, d2, ds...)) diff --git a/src/LookupArrays/lookup_arrays.jl b/src/LookupArrays/lookup_arrays.jl index 458bd2e34..a3cbb574a 100644 --- a/src/LookupArrays/lookup_arrays.jl +++ b/src/LookupArrays/lookup_arrays.jl @@ -271,15 +271,18 @@ y = Y(Sampled([1, 4, 7, 10]; span=Regular(3), sampling=Intervals(Start()))) A = ones(x, y) # output -5×4 DimArray{Float64,2} with dimensions: - X Sampled{Int64} 100:-20:20 ReverseOrdered Regular Intervals, - Y Sampled{Int64} Int64[1, 4, 7, 10] ForwardOrdered Regular Intervals - 1 4 7 10 - 100 1.0 1.0 1.0 1.0 - 80 1.0 1.0 1.0 1.0 - 60 1.0 1.0 1.0 1.0 - 40 1.0 1.0 1.0 1.0 - 20 1.0 1.0 1.0 1.0 +╭─────────────────────────╮ +│ 5×4 DimArray{Float64,2} │ +├─────────────────────────┴────────────────────────────────────────── dims ┐ + ↓ X Sampled{Int64} 100:-20:20 ReverseOrdered Regular Intervals{Start}, + → Y Sampled{Int64} [1, 4, 7, 10] ForwardOrdered Regular Intervals{Start} +└──────────────────────────────────────────────────────────────────────────┘ + ↓ → 1 4 7 10 + 100 1.0 1.0 1.0 1.0 + 80 1.0 1.0 1.0 1.0 + 60 1.0 1.0 1.0 1.0 + 40 1.0 1.0 1.0 1.0 + 20 1.0 1.0 1.0 1.0 ``` """ struct Sampled{T,A<:AbstractVector{T},O,Sp,Sa,M} <: AbstractSampled{T,O,Sp,Sa} @@ -490,8 +493,8 @@ Dimensions.lookup(A) # output -Categorical{String} String["one", "two", "three"] Unordered, -Categorical{Symbol} Symbol[:a, :b, :c, :d] ForwardOrdered +Categorical{String} ["one", "two", "three"] Unordered, +Categorical{Symbol} [:a, :b, :c, :d] ForwardOrdered ``` """ struct Categorical{T,A<:AbstractVector{T},O<:Order,M} <: AbstractCategorical{T,O} diff --git a/src/LookupArrays/selector.jl b/src/LookupArrays/selector.jl index 9db460ef3..c2f8216d1 100644 --- a/src/LookupArrays/selector.jl +++ b/src/LookupArrays/selector.jl @@ -524,11 +524,14 @@ A[X(Between(15, 25)), Y(Between(4, 6.5))] # output -1×2 DimArray{Int64,2} with dimensions: - X Sampled{Int64} 20:10:20 ForwardOrdered Regular Points, - Y Sampled{Int64} 5:6 ForwardOrdered Regular Points - 5 6 - 20 4 5 +╭───────────────────────╮ +│ 1×2 DimArray{Int64,2} │ +├───────────────────────┴────────────────────────────── dims ┐ + ↓ X Sampled{Int64} 20:10:20 ForwardOrdered Regular Points, + → Y Sampled{Int64} 5:6 ForwardOrdered Regular Points +└────────────────────────────────────────────────────────────┘ + ↓ → 5 6 + 20 4 5 ``` """ struct Between{T<:Union{<:AbstractVector{<:Tuple{Any,Any}},Tuple{Any,Any},Nothing}} <: ArraySelector{T} @@ -781,11 +784,14 @@ A = DimArray([1 2 3; 4 5 6], (X(10:10:20), Y(5:7))) A[X(Touches(15, 25)), Y(Touches(4, 6.5))] # output -1×2 DimArray{Int64,2} with dimensions: - X Sampled{Int64} 20:10:20 ForwardOrdered Regular Points, - Y Sampled{Int64} 5:6 ForwardOrdered Regular Points - 5 6 - 20 4 5 +╭───────────────────────╮ +│ 1×2 DimArray{Int64,2} │ +├───────────────────────┴────────────────────────────── dims ┐ + ↓ X Sampled{Int64} 20:10:20 ForwardOrdered Regular Points, + → Y Sampled{Int64} 5:6 ForwardOrdered Regular Points +└────────────────────────────────────────────────────────────┘ + ↓ → 5 6 + 20 4 5 ``` """ struct Touches{T<:Union{<:AbstractVector{<:Tuple{Any,Any}},Tuple{Any,Any},Nothing,Extents.Extent}} <: ArraySelector{T} @@ -954,11 +960,14 @@ A[X(Where(x -> x > 15)), Y(Where(x -> x in (19, 21)))] # output -1×2 DimArray{Int64,2} with dimensions: - X Sampled{Int64} Int64[20] ForwardOrdered Regular Points, - Y Sampled{Int64} Int64[19, 21] ForwardOrdered Regular Points - 19 21 - 20 4 6 +╭───────────────────────╮ +│ 1×2 DimArray{Int64,2} │ +├───────────────────────┴─────────────────────────────── dims ┐ + ↓ X Sampled{Int64} [20] ForwardOrdered Irregular Points, + → Y Sampled{Int64} [19, 21] ForwardOrdered Irregular Points +└─────────────────────────────────────────────────────────────┘ + ↓ → 19 21 + 20 4 6 ``` """ struct Where{T} <: ArraySelector{T} @@ -993,10 +1002,13 @@ A[X=All(At(10.0), At(50.0)), Ti=All(1u"s"..10u"s", 90u"s"..100u"s")] # output -2×4 DimArray{Int64,2} with dimensions: - X Sampled{Float64} Float64[10.0, 50.0] ForwardOrdered Regular Points, - Ti Sampled{Quantity{Int64, 𝐓, Unitful.FreeUnits{(s,), 𝐓, nothing}}} Quantity{Int64, 𝐓, Unitful.FreeUnits{(s,), 𝐓, nothing}}[1 s, 6 s, 91 s, 96 s] ForwardOrdered Regular Points - 1 s 6 s 91 s 96 s +╭───────────────────────╮ +│ 2×4 DimArray{Int64,2} │ +├───────────────────────┴──────────────────────────────────────────────────────────────────────────────────────────────── dims ┐ + ↓ X Sampled{Float64} [10.0, 50.0] ForwardOrdered Irregular Points, + → Ti Sampled{Quantity{Int64, 𝐓, Unitful.FreeUnits{(s,), 𝐓, nothing}}} [1 s, 6 s, 91 s, 96 s] ForwardOrdered Irregular Points +└──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ + ↓ → 1 s 6 s 91 s 96 s 10.0 1 2 19 20 50.0 3 6 57 60 ``` diff --git a/src/set.jl b/src/set.jl index 6f2319a19..605391793 100644 --- a/src/set.jl +++ b/src/set.jl @@ -39,48 +39,60 @@ DimensionalData julia> da = DimArray(zeros(3, 4), (custom=10.0:010.0:30.0, Z=-20:010.0:10.0)); julia> set(da, ones(3, 4)) -3×4 DimArray{Float64,2} with dimensions: - Dim{:custom} Sampled{Float64} 10.0:10.0:30.0 ForwardOrdered Regular Points, - Z Sampled{Float64} -20.0:10.0:10.0 ForwardOrdered Regular Points - -20.0 -10.0 0.0 10.0 +╭─────────────────────────╮ +│ 3×4 DimArray{Float64,2} │ +├─────────────────────────┴───────────────────────────────────────── dims ┐ + ↓ custom Sampled{Float64} 10.0:10.0:30.0 ForwardOrdered Regular Points, + → Z Sampled{Float64} -20.0:10.0:10.0 ForwardOrdered Regular Points +└─────────────────────────────────────────────────────────────────────────┘ + ↓ → -20.0 -10.0 0.0 10.0 10.0 1.0 1.0 1.0 1.0 20.0 1.0 1.0 1.0 1.0 - 30.0 1.0 1.0 1.0 1.0 + 30.0 1.0 1.0 1.0 1.0 ``` Change the `Dimension` wrapper type: ```jldoctest set julia> set(da, :Z => Ti, :custom => Z) -3×4 DimArray{Float64,2} with dimensions: - Z Sampled{Float64} 10.0:10.0:30.0 ForwardOrdered Regular Points, - Ti Sampled{Float64} -20.0:10.0:10.0 ForwardOrdered Regular Points - -20.0 -10.0 0.0 10.0 +╭─────────────────────────╮ +│ 3×4 DimArray{Float64,2} │ +├─────────────────────────┴───────────────────────────────────── dims ┐ + ↓ Z Sampled{Float64} 10.0:10.0:30.0 ForwardOrdered Regular Points, + → Ti Sampled{Float64} -20.0:10.0:10.0 ForwardOrdered Regular Points +└─────────────────────────────────────────────────────────────────────┘ + ↓ → -20.0 -10.0 0.0 10.0 10.0 0.0 0.0 0.0 0.0 20.0 0.0 0.0 0.0 0.0 - 30.0 0.0 0.0 0.0 0.0 + 30.0 0.0 0.0 0.0 0.0 ``` Change the lookup `Vector`: ```jldoctest set julia> set(da, Z => [:a, :b, :c, :d], :custom => [4, 5, 6]) -3×4 DimArray{Float64,2} with dimensions: - Dim{:custom} Sampled{Int64} Int64[4, 5, 6] ForwardOrdered Regular Points, - Z Sampled{Symbol} Symbol[:a, :b, :c, :d] ForwardOrdered Regular Points - :a :b :c :d - 4 0.0 0.0 0.0 0.0 - 5 0.0 0.0 0.0 0.0 - 6 0.0 0.0 0.0 0.0 +╭─────────────────────────╮ +│ 3×4 DimArray{Float64,2} │ +├─────────────────────────┴───────────────────────────────────────── dims ┐ + ↓ custom Sampled{Int64} [4, 5, 6] ForwardOrdered Regular Points, + → Z Sampled{Symbol} [:a, :b, :c, :d] ForwardOrdered Regular Points +└─────────────────────────────────────────────────────────────────────────┘ + ↓ → :a :b :c :d + 4 0.0 0.0 0.0 0.0 + 5 0.0 0.0 0.0 0.0 + 6 0.0 0.0 0.0 0.0 ``` Change the `LookupArray` type: ```jldoctest set julia> set(da, Z=DD.NoLookup(), custom=DD.Sampled()) -3×4 DimArray{Float64,2} with dimensions: - Dim{:custom} Sampled{Float64} 10.0:10.0:30.0 ForwardOrdered Regular Points, - Z +╭─────────────────────────╮ +│ 3×4 DimArray{Float64,2} │ +├─────────────────────────┴───────────────────────────────────────── dims ┐ + ↓ custom Sampled{Float64} 10.0:10.0:30.0 ForwardOrdered Regular Points, + → Z +└─────────────────────────────────────────────────────────────────────────┘ 10.0 0.0 0.0 0.0 0.0 20.0 0.0 0.0 0.0 0.0 30.0 0.0 0.0 0.0 0.0 @@ -90,10 +102,13 @@ Change the `Sampling` trait: ```jldoctest set julia> set(da, :custom => DD.Irregular(10, 12), Z => DD.Regular(9.9)) -3×4 DimArray{Float64,2} with dimensions: - Dim{:custom} Sampled{Float64} 10.0:10.0:30.0 ForwardOrdered Irregular Points, - Z Sampled{Float64} -20.0:10.0:10.0 ForwardOrdered Regular Points - -20.0 -10.0 0.0 10.0 +╭─────────────────────────╮ +│ 3×4 DimArray{Float64,2} │ +├─────────────────────────┴─────────────────────────────────────────── dims ┐ + ↓ custom Sampled{Float64} 10.0:10.0:30.0 ForwardOrdered Irregular Points, + → Z Sampled{Float64} -20.0:10.0:10.0 ForwardOrdered Regular Points +└───────────────────────────────────────────────────────────────────────────┘ + ↓ → -20.0 -10.0 0.0 10.0 10.0 0.0 0.0 0.0 0.0 20.0 0.0 0.0 0.0 0.0 30.0 0.0 0.0 0.0 0.0 diff --git a/src/stack/stack.jl b/src/stack/stack.jl index 38f6e2925..205d19dd2 100644 --- a/src/stack/stack.jl +++ b/src/stack/stack.jl @@ -249,8 +249,8 @@ julia> using DimensionalData julia> A = [1.0 2.0 3.0; 4.0 5.0 6.0]; julia> dimz = (X([:a, :b]), Y(10.0:10.0:30.0)) -X Symbol[:a, :b], -Y 10.0:10.0:30.0 +↓ X [:a, :b], +→ Y 10.0:10.0:30.0 julia> da1 = DimArray(1A, dimz; name=:one); diff --git a/src/utils.jl b/src/utils.jl index 5bb56d7b3..67090237c 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -22,7 +22,9 @@ da = DimArray([1 2 3; 4 5 6], (X(10:10:20), Y(300:-100:100))) rev = reverse(da, dims=Y) # using `da` in reorder will return it to the original order reorder(rev, da) == da -# output + +# output + true ``` """ From bd0aef55a0a4974277bc52d325c0bb0713beb94d Mon Sep 17 00:00:00 2001 From: Rafael Schouten Date: Mon, 26 Feb 2024 14:59:58 +0100 Subject: [PATCH 047/108] print_block_separator optional arg --- src/array/show.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/array/show.jl b/src/array/show.jl index f30a611ef..572aa60a9 100644 --- a/src/array/show.jl +++ b/src/array/show.jl @@ -173,7 +173,7 @@ function print_block_top(io, label, prev_width, new_width) return lines end -function print_block_separator(io, label, prev_width, new_width) +function print_block_separator(io, label, prev_width, new_width=prev_width) corner = (new_width > prev_width) ? '┐' : '┤' middle_line = string('├', '─'^max(0, new_width - textwidth(label) - 2), ' ', label, ' ', corner) printstyled(io, middle_line; color=:light_black) From 0fee89530f7238ec398dfbc1cd90847db170ff46 Mon Sep 17 00:00:00 2001 From: Rafael Schouten Date: Wed, 28 Feb 2024 11:13:55 +0100 Subject: [PATCH 048/108] Breaking: document and tweak stack interface (#651) * tweak stacks * bugfix * bugfix indexing * dont double wrap groupby --- docs/src/stacks.md | 64 +++++++++++++++- src/Dimensions/primitives.jl | 2 + src/array/array.jl | 15 ++-- src/array/broadcast.jl | 2 + src/array/indexing.jl | 4 +- src/groupby.jl | 27 ------- src/stack/indexing.jl | 21 +++++- src/stack/methods.jl | 80 +++----------------- src/stack/stack.jl | 139 +++++++++++++++++++++++------------ src/tables.jl | 16 ++-- test/indexing.jl | 34 ++++----- test/stack.jl | 7 +- 12 files changed, 222 insertions(+), 189 deletions(-) diff --git a/docs/src/stacks.md b/docs/src/stacks.md index a006d9a10..37c3436eb 100644 --- a/docs/src/stacks.md +++ b/docs/src/stacks.md @@ -12,7 +12,10 @@ x, y = X(1.0:10.0), Y(5.0:10.0) st = DimStack((a=rand(x, y), b=rand(x, y), c=rand(y), d=rand(x))) ```` -The behaviour is somewhere ebetween a `NamedTuple` and an `AbstractArray`. +The behaviour of a `DimStack` is at times like a `NamedTuple` of +`DimArray` and, others an `AbstractArray` of `NamedTuple`. + +## NamedTuple-like indexing ::: tabs @@ -25,6 +28,52 @@ st.a st[:c] ```` +== subsetting layers + +We can subset layers with a `Tupe` of `Symbol`: + +````@ansi stack +st[(:a, :c)] +```` + +== inverted subsets + +`Not` works on `Symbol` keys just like it does on `Selector`: +It inverts the keys to give you a `DImStack` with all the other layers: + +````@ansi stack +st[Not(:b)] +st[Not((:a, :c))] +```` + +== merging + +We can merge a `DimStack` with another `DimStack`: + +````@ansi stack +st2 = DimStack((m=rand(x, y), n=rand(x, y), o=rand(y))) +merge(st, st2) +```` + +Or merge a `DimStack` with a `NamedTuple` of `DimArray`: + +````@ansi stack +merge(st, (; d = rand(y, x), e = rand(y))) +```` + +Merging only works when dimensions match: + +````@ansi stack +merge(st, (; d = rand(Y('a':'n')))) +```` + +::: + + +## Array-like indexing + +::: tabs + == scalars Indexing with a scalar returns a `NamedTuple` of values, one for each layer: @@ -50,8 +99,15 @@ The layers without another dimension are now zero-dimensional: st[X=At(2.0)] ```` -::: +== linear indexing + +If we index with `:` we get a `Vector{<:NamedTuple}` +````@ansi stack +st[:] +```` + +::: ## Reducing functions @@ -112,14 +168,14 @@ var(st; dims=Y) ````@ansi stack reduce(+, st) -# reduce(+, st; dims=Y) # broken +reduce(+, st; dims=Y) ```` == extrema ````@ansi stack extrema(st) -extrema(st; dims=Y) # Kind of broken? should :d be tuples too? +extrema(st; dims=Y) ```` == dropdims diff --git a/src/Dimensions/primitives.jl b/src/Dimensions/primitives.jl index 362105424..091b5b016 100644 --- a/src/Dimensions/primitives.jl +++ b/src/Dimensions/primitives.jl @@ -699,6 +699,8 @@ struct AlwaysTuple <: QueryMode end @inline _dim_query1(f, op::Function, t, x, query) = _dim_query1(f, op, t, dims(x), query) @inline _dim_query1(f, op::Function, t, x::Nothing) = _dimsnotdefinederror() @inline _dim_query1(f, op::Function, t, x::Nothing, query) = _dimsnotdefinederror() +@inline _dim_query1(f, op::Function, t, ds::Tuple, query::Colon) = + _dim_query1(f, op, t, ds, basedims(ds)) @inline function _dim_query1(f, op::Function, t, ds::Tuple, query::Function) selection = foldl(ds; init=()) do acc, d query(d) ? (acc..., d) : acc diff --git a/src/array/array.jl b/src/array/array.jl index c7f32b19a..bcd503284 100644 --- a/src/array/array.jl +++ b/src/array/array.jl @@ -14,6 +14,10 @@ const AbstractBasicDimVector = AbstractBasicDimArray{T,1} where T const AbstractBasicDimMatrix = AbstractBasicDimArray{T,2} where T const AbstractBasicDimVecOrMat = Union{AbstractBasicDimVector,AbstractBasicDimMatrix} +refdims(::AbstractBasicDimArray) = () +name(::AbstractBasicDimArray) = NoName() +metadata(::AbstractBasicDimArray) = NoMetadata() + # DimensionalData.jl interface methods #################################################### for func in (:val, :index, :lookup, :order, :sampling, :span, :locus, :bounds, :intervalbounds) @@ -142,10 +146,10 @@ Base.similar(A::AbstractDimArray, ::Type{T}) where T = rebuild(A; data=similar(parent(A), T), dims=dims(A), refdims=refdims(A), name=_noname(A), metadata=metadata(A)) # We avoid calling `parent` for AbstractBasicDimArray as we don't know what it is/if there is one -Base.similar(A::AbstractBasicDimArray{T}) where T = - rebuild(A; data=similar(typeof(A), T), dims=dims(A), refdims=refdims(A), name=_noname(A), metadata=metadata(A)) -Base.similar(A::AbstractBasicDimArray, ::Type{T}) where T = - rebuild(A; data=similar(typeof(A)), dims=dims(A), refdims=refdims(A), name=_noname(A), metadata=metadata(A)) +Base.similar(A::AbstractBasicDimArray{T,N}) where {T,N} = + rebuild(A; data=similar(Array{T,N}, size(A)), dims=dims(A), refdims=refdims(A), name=_noname(A), metadata=metadata(A)) +Base.similar(A::AbstractBasicDimArray{<:Any,N}, ::Type{T}) where {T,N} = + rebuild(A; data=similar(Array{T,N}, size(A)), dims=dims(A), refdims=refdims(A), name=_noname(A), metadata=metadata(A)) # We can't resize the dims or add missing dims, so return the unwraped Array type? # An alternative would be to fill missing dims with `Anon`, and keep existing # dims but strip the Lookup? It just seems a little complicated when the methods @@ -247,7 +251,8 @@ Base.similar(A::AbstractDimArray, ::Type{T}, D::Tuple{}) where T = rebuild(A; data=similar(parent(A), T, ()), dims=(), refdims=(), metadata=NoMetadata()) # Keep the same type in `similar` -_noname(A::AbstractDimArray) = _noname(name(A)) +_noname(A::AbstractBasicDimArray) = _noname(name(A)) +_noname(s::String) = @show s _noname(::NoName) = NoName() _noname(::Symbol) = Symbol("") _noname(name::Name) = name # Keep the name so the type doesn't change diff --git a/src/array/broadcast.jl b/src/array/broadcast.jl index 501f6444c..daedc354c 100644 --- a/src/array/broadcast.jl +++ b/src/array/broadcast.jl @@ -39,6 +39,8 @@ function Broadcast.copy(bc::Broadcasted{DimensionalStyle{S}}) where S data = copy(_unwrap_broadcasted(bc)) return if A isa Nothing || _dims isa Nothing || ndims(A) == 0 data + elseif data isa AbstractDimArray + rebuild(A, parent(data), _dims, refdims(A), Symbol("")) else rebuild(A, data, _dims, refdims(A), Symbol("")) end diff --git a/src/array/indexing.jl b/src/array/indexing.jl index ba34b0ae1..9d4d6c2c3 100644 --- a/src/array/indexing.jl +++ b/src/array/indexing.jl @@ -114,7 +114,7 @@ function _merge_and_index(f, A, inds) lazylinear = rebuild(mdim, LazyDims2Linear(inds, DD.dims(A, dims_to_merge))) f(M, lazylinear) else - # Index anyway with al Colon() just for type consistency + # Index anyway with all Colon() just for type consistency f(M, basedims(M)...) end else @@ -122,7 +122,7 @@ function _merge_and_index(f, A, inds) f(A, m_inds) end else - d = first(dims_to_merge) + d = only(dims_to_merge) val_array = reinterpret(typeof(val(d)), dims_to_merge) f(A, rebuild(d, val_array)) end diff --git a/src/groupby.jl b/src/groupby.jl index 5e0befb4e..c275ddb58 100644 --- a/src/groupby.jl +++ b/src/groupby.jl @@ -357,33 +357,6 @@ function _groups_from(_, bins::CyclicBins) end end -# Return the bin for a value -# function _choose_bin(b::AbstractBins, groups::LookupArray, val) -# groups[ispoints(groups) ? At(val) : Contains(val)] -# end -# function _choose_bin(b::AbstractBins, groups, val) -# i = findfirst(Base.Fix1(_in, val), groups) -# isnothing(i) && return nothing -# return groups[i] -# end -# function _choose_bin(b::Bins, groups::AbstractArray{<:Number}, val) -# i = searchsortedlast(groups, val; by=_by) -# i >= firstindex(groups) && val in groups[i] || return nothing -# return groups[i] -# end -# function _choose_bin(b::Bins, groups::AbstractArray{<:Tuple{Vararg{Union{Number,AbstractRange,IntervalSets.Interval}}}}, val::Tuple) -# @assert length(val) == length(first(groups)) -# i = searchsortedlast(groups, val; by=_by) -# i >= firstindex(groups) && last(val) in last(groups[i]) || return nothing -# return groups[i] -# end -# _choose_bin(b::Bins, groups::AbstractArray{<:IntervalSets.Interval}, val::Tuple) = _choose_bin(b::Bins, groups, last(val)) -# function _choose_bin(b::Bins, groups::AbstractArray{<:IntervalSets.Interval}, val) -# i = searchsortedlast(groups, val; by=_by) -# i >= firstindex(groups) && val in groups[i] || return nothing -# return groups[i] -# end - _maybe_label(vals) = vals _maybe_label(f::Function, vals) = f.(vals) _maybe_label(::Nothing, vals) = vals diff --git a/src/stack/indexing.jl b/src/stack/indexing.jl index b707943f0..cc05a21dd 100644 --- a/src/stack/indexing.jl +++ b/src/stack/indexing.jl @@ -6,9 +6,14 @@ 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)) -end -@propagate_inbounds function Base.getindex(s::AbstractDimStack, keys::Tuple) - rebuild_from_arrays(s, NamedTuple{keys}(map(k -> s[k], keys))) + @eval Base.@assume_effects :foldable @propagate_inbounds function Base.$f(s::AbstractDimStack, keys::Tuple) + rebuild_from_arrays(s, NamedTuple{keys}(map(k -> s[k], keys))) + end + @eval Base.@assume_effects :foldable @propagate_inbounds function Base.$f( + s::AbstractDimStack, keys::Union{<:Not{Symbol},<:Not{<:NTuple{<:Any,Symbol}}} + ) + rebuild_from_arrays(s, layers(s)[keys]) + end end for f in (:getindex, :view, :dotview) @@ -30,7 +35,15 @@ for f in (:getindex, :view, :dotview) end @propagate_inbounds function Base.$f(s::AbstractDimStack, i::Union{AbstractArray,Colon}) if length(dims(s)) > 1 - Base.$f(s, view(DimIndices(s), i)) + if $f == getindex + ls = map(A -> vec(DimExtensionArray(A, dims(s))), layers(s)) + i = i isa Colon ? eachindex(first(ls)) : i + map(i) do n + map(Base.Fix2(getindex, n), ls) + end + else + Base.$f(s, view(DimIndices(s), i)) + end elseif length(dims(s)) == 1 Base.$f(s, rebuild(only(dims(s)), i)) else diff --git a/src/stack/methods.jl b/src/stack/methods.jl index fffe3a49a..b8a1bc9f3 100644 --- a/src/stack/methods.jl +++ b/src/stack/methods.jl @@ -46,54 +46,6 @@ function Base.copyto!( dst[dstI] = src[srcI] end -""" - Base.map(f, stacks::AbstractDimStack...) - -Apply function `f` to each layer of the `stacks`. - -If `f` returns `DimArray`s the result will be another `DimStack`. -Other values will be returned in a `NamedTuple`. -""" -function Base.map(f, s::AbstractDimStack) - _maybestack(s,map(f, values(s))) -end -function Base.map(f, x1::Union{AbstractDimStack,NamedTuple}, xs::Union{AbstractDimStack,NamedTuple}...) - stacks = (x1, xs...) - _check_same_names(stacks...) - vals = map(f, map(values, stacks)...) - return _maybestack(_firststack(stacks...), vals) -end - - -_check_same_names(::Union{AbstractDimStack{<:NamedTuple{names}},NamedTuple{names}}, - ::Union{AbstractDimStack{<:NamedTuple{names}},NamedTuple{names}}...) where {names} = nothing -_check_same_names(::Union{AbstractDimStack,NamedTuple}, ::Union{AbstractDimStack,NamedTuple}...) = throw(ArgumentError("Named tuple names do not match.")) - -_firststack(s::AbstractDimStack, args...) = s -_firststack(arg1, args...) = _firststack(args...) -_firststack() = nothing - -_maybestack(s::AbstractDimStack{<:NamedTuple{K}}, xs::Tuple) where K = NamedTuple{K}(xs) -_maybestack(s::AbstractDimStack, xs::Tuple) = NamedTuple{keys(s)}(xs) -# Without the `@nospecialise` here this method is also compile with the above method -# on every call to _maybestack. And `rebuild_from_arrays` is expensive to compile. -function _maybestack( - s::AbstractDimStack, das::Tuple{AbstractDimArray,Vararg{AbstractDimArray}} -) - # Avoid compiling this in the simple cases in the above method - Base.invokelatest() do - rebuild_from_arrays(s, das) - end -end -function _maybestack( - s::AbstractDimStack{<:NamedTuple{K}}, das::Tuple{AbstractDimArray,Vararg{AbstractDimArray}} -) where K - # Avoid compiling this in the simple cases in the above method - Base.invokelatest() do - rebuild_from_arrays(s, das) - end -end - """ Base.eachslice(stack::AbstractDimStack; dims) @@ -203,27 +155,18 @@ for (mod, fnames) in for fname in fnames @eval function ($mod.$fname)(s::AbstractDimStack; dims=:, kw...) map(s) do A - # Ignore dims not found in layer - if dims isa Union{Colon,Int} - ($mod.$fname)(A; dims, kw...) - else - ldims = commondims(DD.dims(A), dims) - # With no matching dims we do nothing - ldims == () ? A : ($mod.$fname)(A; dims=ldims, kw...) - end + layer_dims = dims isa Colon ? dims : commondims(A, dims) + $mod.$fname(A; dims=layer_dims, kw...) end end end end for fname in (:cor, :cov) @eval function (Statistics.$fname)(s::AbstractDimStack; dims=1, kw...) + d = DD.dims(s, dims) map(s) do A - if dims isa Int - (Statistics.$fname)(A; dims, kw...) - else - ldims = only(commondims(DD.dims(A), dims)) - ldims == () ? A : (Statistics.$fname)(A; dims=ldims, kw...) - end + layer_dims = only(commondims(A, d)) + Statistics.$fname(A; dims=layer_dims, kw...) end end end @@ -234,16 +177,11 @@ for (mod, fnames) in (:Base => (:reduce, :sum, :prod, :maximum, :minimum, :extre for fname in fnames _fname = Symbol(:_, fname) @eval function ($mod.$fname)(f::Function, s::AbstractDimStack; dims=Colon()) - map(A -> ($mod.$fname)(f, A; dims=dims), s) + map(s) do A + layer_dims = dims isa Colon ? dims : commondims(A, dims) + $mod.$fname(f, A; dims=layer_dims) + end end - # ($_fname)(f, s, dims) - # Colon returns a NamedTuple - # ($_fname)(f::Function, s::AbstractDimStack, dims::Colon) = - # map(A -> ($mod.$fname)(f, A), data(s)) - # Otherwise maybe return a DimStack - # function ($_fname)(f::Function, s::AbstractDimStack, dims) = - # end - # end end end diff --git a/src/stack/stack.jl b/src/stack/stack.jl index 205d19dd2..0a2ecf0fc 100644 --- a/src/stack/stack.jl +++ b/src/stack/stack.jl @@ -48,6 +48,24 @@ Base.@assume_effects :foldable function hassamedims(s::AbstractDimStack) all(map(==(first(layerdims(s))), layerdims(s))) end +function rebuild( + s::AbstractDimStack, data, dims=dims(s), refdims=refdims(s), + layerdims=layerdims(s), metadata=metadata(s), layermetadata=layermetadata(s) +) + basetypeof(s)(data, dims, refdims, layerdims, metadata, layermetadata) +end +function rebuild(s::AbstractDimStack; data=data(s), dims=dims(s), refdims=refdims(s), + layerdims=layerdims(s), metadata=metadata(s), layermetadata=layermetadata(s) +) + basetypeof(s)(data, dims, refdims, layerdims, metadata, layermetadata) +end + +function rebuildsliced(f::Function, s::AbstractDimStack, layers, I) + layerdims = map(basedims, layers) + dims, refdims = slicedims(f, s, I) + rebuild(s; data=map(parent, layers), dims=dims, refdims=refdims, layerdims=layerdims) +end + """ rebuild_from_arrays(s::AbstractDimStack, das::NamedTuple{<:Any,<:Tuple{Vararg{AbstractDimArray}}}; kw...) @@ -89,19 +107,18 @@ function rebuild_from_arrays( end end +# Dipatch on Tuple of Dimension, and map +for func in (:index, :lookup, :metadata, :sampling, :span, :bounds, :locus, :order) + @eval ($func)(s::AbstractDimStack, args...) = ($func)(dims(s), args...) +end + Base.parent(s::AbstractDimStack) = data(s) -@inline Base.keys(s::AbstractDimStack) = keys(data(s)) -@inline Base.propertynames(s::AbstractDimStack) = keys(data(s)) -Base.haskey(s::AbstractDimStack, k) = k in keys(s) -Base.values(s::AbstractDimStack) = values(layers(s)) -Base.values(s::AbstractDimStack{<:NamedTuple{Keys}}) where Keys = map(K -> s[K], Keys) -Base.first(s::AbstractDimStack) = s[first(keys(s))] -Base.last(s::AbstractDimStack) = s[last(keys(s))] # Only compare data and dim - metadata and refdims can be different Base.:(==)(s1::AbstractDimStack, s2::AbstractDimStack) = data(s1) == data(s2) && dims(s1) == dims(s2) && layerdims(s1) == layerdims(s2) -Base.@assume_effects :foldable Base.getproperty(s::AbstractDimStack, x::Symbol) = s[x] -Base.length(s::AbstractDimStack) = length(keys(s)) +Base.read(s::AbstractDimStack) = map(read, s) + +# Array-like Base.ndims(s::AbstractDimStack) = length(dims(s)) Base.size(s::AbstractDimStack) = map(length, dims(s)) Base.size(s::AbstractDimStack, dims::DimOrDimType) = size(s, dimnum(s, dims)) @@ -111,9 +128,25 @@ Base.axes(s::AbstractDimStack, dims::DimOrDimType) = axes(s, dimnum(s, dims)) Base.axes(s::AbstractDimStack, dims::Integer) = axes(s)[dims] Base.similar(s::AbstractDimStack, args...) = map(A -> similar(A, args...), s) Base.eltype(s::AbstractDimStack, args...) = NamedTuple{keys(s),Tuple{map(eltype, s)...}} -Base.iterate(s::AbstractDimStack, args...) = iterate(layers(s), args...) -Base.read(s::AbstractDimStack) = map(read, s) Base.CartesianIndices(s::AbstractDimStack) = CartesianIndices(dims(s)) +Base.LinearIndices(s::AbstractDimStack) = LinearIndices(CartesianIndices(map(l -> axes(l, 1), lookup(s)))) +function Base.eachindex(s::AbstractDimStack) + li = LinearIndices(s) + first(li):last(li) +end +# all of methods.jl is also Array-like... + +# NamedTuple-like +Base.@assume_effects :foldable Base.getproperty(s::AbstractDimStack, x::Symbol) = s[x] +Base.haskey(s::AbstractDimStack, k) = k in keys(s) +Base.values(s::AbstractDimStack) = values(layers(s)) +@inline Base.keys(s::AbstractDimStack) = keys(data(s)) +@inline Base.propertynames(s::AbstractDimStack) = keys(data(s)) +# Remove these, but explain +Base.iterate(::AbstractDimStack, args...) = error("Use iterate(layers(s)) rather than `iterate(s)`") #iterate(layers(s), args...) +Base.length(::AbstractDimStack) = error("Use length(layers(s)) rather than `length(s)`") # length(keys(s)) +Base.first(::AbstractDimStack) = error("Use first(layers(s)) rather than `first(s)`") +Base.last(::AbstractDimStack) = error("Use last(layers(s)) rather than `last(s)`") # `merge` for AbstractDimStack and NamedTuple. # One of the first three arguments must be an AbstractDimStack for dispatch to work. Base.merge(s::AbstractDimStack) = s @@ -132,7 +165,16 @@ end function Base.setindex(s::AbstractDimStack, val::AbstractBasicDimArray, key) rebuild_from_arrays(s, Base.setindex(layers(s), val, key)) end -Base.NamedTuple(s::AbstractDimStack) = layers(s) +Base.NamedTuple(s::AbstractDimStack) = NamedTuple(layers(s)) +Base.map(f, s::AbstractDimStack) = _maybestack(s,map(f, values(s))) +function Base.map(f, x1::Union{AbstractDimStack,NamedTuple}, xs::Union{AbstractDimStack,NamedTuple}...) + stacks = (x1, xs...) + _check_same_names(stacks...) + vals = map(f, map(values, stacks)...) + return _maybestack(_firststack(stacks...), vals) +end + +# Other interfaces Extents.extent(A::AbstractDimStack, args...) = Extents.extent(dims(A), args...) @@ -140,31 +182,8 @@ ConstructionBase.getproperties(s::AbstractDimStack) = layers(s) ConstructionBase.setproperties(s::AbstractDimStack, patch::NamedTuple) = ConstructionBase.constructorof(typeof(s))(ConstructionBase.setproperties(layers(s), patch)) -function rebuild( - s::AbstractDimStack, data, dims=dims(s), refdims=refdims(s), - layerdims=layerdims(s), metadata=metadata(s), layermetadata=layermetadata(s) -) - basetypeof(s)(data, dims, refdims, layerdims, metadata, layermetadata) -end -function rebuild(s::AbstractDimStack; data=data(s), dims=dims(s), refdims=refdims(s), - layerdims=layerdims(s), metadata=metadata(s), layermetadata=layermetadata(s) -) - basetypeof(s)(data, dims, refdims, layerdims, metadata, layermetadata) -end - -function rebuildsliced(f::Function, s::AbstractDimStack, layers, I) - layerdims = map(basedims, layers) - dims, refdims = slicedims(f, s, I) - rebuild(s; data=map(parent, layers), dims=dims, refdims=refdims, layerdims=layerdims) -end - Adapt.adapt_structure(to, s::AbstractDimStack) = map(A -> Adapt.adapt(to, A), s) -# Dipatch on Tuple of Dimension, and map -for func in (:index, :lookup, :metadata, :sampling, :span, :bounds, :locus, :order) - @eval ($func)(s::AbstractDimStack, args...) = ($func)(dims(s), args...) -end - function mergedims(st::AbstractDimStack, dim_pairs::Pair...) dim_pairs = map(dim_pairs) do (as, b) basedims(as) => b @@ -187,6 +206,43 @@ function unmergedims(s::AbstractDimStack, original_dims) return map(A -> unmergedims(A, original_dims), s) end +@noinline _stack_size_mismatch() = throw(ArgumentError("Arrays must have identical axes. For mixed dimensions, use DimArrays`")) + +function _layerkeysfromdim(A, dim) + map(index(A, dim)) do x + if x isa Number + Symbol(string(DD.dim2key(dim), "_", x)) + else + Symbol(x) + end + end +end + +_check_same_names(::Union{AbstractDimStack{<:NamedTuple{names}},NamedTuple{names}}, + ::Union{AbstractDimStack{<:NamedTuple{names}},NamedTuple{names}}...) where {names} = nothing +_check_same_names(::Union{AbstractDimStack,NamedTuple}, ::Union{AbstractDimStack,NamedTuple}...) = + throw(ArgumentError("Named tuple names do not match.")) + +_firststack(s::AbstractDimStack, args...) = s +_firststack(arg1, args...) = _firststack(args...) +_firststack() = nothing + +_maybestack(s::AbstractDimStack{<:NamedTuple{K}}, xs::Tuple) where K = NamedTuple{K}(xs) +_maybestack(s::AbstractDimStack, xs::Tuple) = NamedTuple{keys(s)}(xs) +# Without the `@nospecialise` here this method is also compile with the above method +# on every call to _maybestack. And `rebuild_from_arrays` is expensive to compile. +function _maybestack( + s::AbstractDimStack, das::Tuple{AbstractDimArray,Vararg{AbstractDimArray}} +) + # Avoid compiling this in the simple cases in the above method + Base.invokelatest(() -> rebuild_from_arrays(s, das)) +end +function _maybestack( + s::AbstractDimStack{<:NamedTuple{K}}, das::Tuple{AbstractDimArray,Vararg{AbstractDimArray}} +) where K + Base.invokelatest(() -> rebuild_from_arrays(s, das)) +end + """ DimStack <: AbstractDimStack @@ -311,6 +367,7 @@ function DimStack(das::NamedTuple{<:Any,<:Tuple{Vararg{AbstractDimArray}}}; DimStack(data, dims, refdims, layerdims, metadata, layermetadata) end # Same sized arrays +DimStack(data::NamedTuple, dim::Dimension; kw...) = DimStack(data::NamedTuple, (dim,); kw...) function DimStack(data::NamedTuple, dims::Tuple; refdims=(), metadata=NoMetadata(), layermetadata=map(_ -> NoMetadata(), data), @@ -320,16 +377,4 @@ function DimStack(data::NamedTuple, dims::Tuple; DimStack(data, format(dims, first(data)), refdims, layerdims, metadata, layermetadata) end -@noinline _stack_size_mismatch() = throw(ArgumentError("Arrays must have identical axes. For mixed dimensions, use DimArrays`")) - layerdims(s::DimStack{<:Any,<:Any,<:Any,Nothing}, key::Symbol) = dims(s) - -function _layerkeysfromdim(A, dim) - map(index(A, dim)) do x - if x isa Number - Symbol(string(DD.dim2key(dim), "_", x)) - else - Symbol(x) - end - end -end diff --git a/src/tables.jl b/src/tables.jl index 5470139a2..04cfc6c1a 100644 --- a/src/tables.jl +++ b/src/tables.jl @@ -89,13 +89,6 @@ function DimTable(s::AbstractDimStack; mergedims=nothing) keys = collect(_colnames(s)) return DimTable(s, keys, dimcolumns, dimarraycolumns) end - -_dimcolumns(x) = map(d -> _dimcolumn(x, d), dims(x)) -function _dimcolumn(x, d::Dimension) - dim_as_dimarray = DimArray(index(d), d) - vec(DimExtensionArray(dim_as_dimarray, dims(x))) -end - function DimTable(xs::Vararg{AbstractDimArray}; layernames=nothing, mergedims=nothing) # Check that dims are compatible comparedims(xs...) @@ -114,7 +107,6 @@ function DimTable(xs::Vararg{AbstractDimArray}; layernames=nothing, mergedims=no # Return DimTable return DimTable(first(xs), colnames, dimcolumns, dimarraycolumns) end - function DimTable(x::AbstractDimArray; layersfrom=nothing, mergedims=nothing) if !isnothing(layersfrom) && any(hasdim(x, layersfrom)) d = dims(x, layersfrom) @@ -132,6 +124,14 @@ function DimTable(x::AbstractDimArray; layersfrom=nothing, mergedims=nothing) end end +_dimcolumns(x) = map(d -> _dimcolumn(x, d), dims(x)) +function _dimcolumn(x, d::Dimension) + dim_as_dimarray = DimArray(index(d), d) + vec(DimExtensionArray(dim_as_dimarray, dims(x))) +end + + + dimcolumns(t::DimTable) = getfield(t, :dimcolumns) dimarraycolumns(t::DimTable) = getfield(t, :dimarraycolumns) colnames(t::DimTable) = Tuple(getfield(t, :colnames)) diff --git a/test/indexing.jl b/test/indexing.jl index 4554076dc..d18d82ad5 100644 --- a/test/indexing.jl +++ b/test/indexing.jl @@ -503,23 +503,23 @@ end slicedds_mixed = s_mixed[At(:a), :] @test slicedds_mixed[:one] == [1.0, 2.0, 3.0] @test parent(slicedds_mixed) == (one=[1.0, 2.0, 3.0], two=[2.0f0, 4.0f0, 6.0f0], three=[3, 6, 9], four=fill(4)) - @testset "linear indices" begin - linear2d = @inferred s[1] - @test linear2d isa NamedTuple - @test linear2d == (one=1.0, two=2.0f0, three=3) - @test_broken linear1d = @inferred view(s[X(2)], 1) - linear1d = view(s[X(2)], 1) - @test linear1d isa DimStack - @test parent(linear1d) == (one=fill(4.0), two=fill(8.0f0), three=fill(12)) - @test_broken linear2d = @inferred s[1:2] - linear2d = s[1:2] - @test linear2d isa DimStack - @test NamedTuple(linear2d) == (one=[1.0, 4.0], two=[2.0f0, 8.0f0], three=[3, 12]) - linear1d = @inferred s[X(1)][1:2] - linear1d = @inferred s[X(1)][1:2] - @test linear1d isa DimStack - @test parent(linear1d) == (one=[1.0, 2.0], two=[2.0f0, 4.0f0], three=[3, 6]) - end + end + + @testset "linear indices" begin + linear2d = @inferred s[1] + @test linear2d isa NamedTuple + @test linear2d == (one=1.0, two=2.0f0, three=3) + @test_broken linear1d = @inferred view(s[X(2)], 1) + linear1d = view(s[X(2)], 1) + @test linear1d isa DimStack + @test parent(linear1d) == (one=fill(4.0), two=fill(8.0f0), three=fill(12)) + @test @inferred s[1:2] isa Array + linear2d = s[1:2] + @test linear2d == [(one=1.0, two=2.0f0, three=3), (one=4.0, two=8.0f0, three=12)] + linear1d = @inferred s[X(1)][1:2] + linear1d = @inferred s[X(1)][1:2] + @test linear1d isa DimStack + @test parent(linear1d) == (one=[1.0, 2.0], two=[2.0f0, 4.0f0], three=[3, 6]) end @testset "getindex Tuple" begin diff --git a/test/stack.jl b/test/stack.jl index ec16a6891..bbe1081f0 100644 --- a/test/stack.jl +++ b/test/stack.jl @@ -22,7 +22,6 @@ mixed = DimStack(da1, da2, da4) DimStack(da1, da2, da3) == DimStack((one=da1, two=da2, three=da3), dimz) == DimStack((one=da1, two=da2, three=da3)) == s - @test length(DimStack(NamedTuple())) == length(DimStack()) == 0 @test dims(DimStack()) == dims(DimStack(NamedTuple())) == () end @@ -66,7 +65,7 @@ end @test eltype(mixed) === @NamedTuple{one::Float64, two::Float32, extradim::Float64} @test haskey(s, :one) == true @test haskey(s, :zero) == false - @test length(s) == 3 # length is as for NamedTuple + @test_throws ErrorException length(s) == 3 @test size(mixed) === (2, 3, 4) # size is as for Array @test size(mixed, Y) === 3 @test size(mixed, 3) === 4 @@ -77,8 +76,8 @@ end @test dims(axes(mixed, X)) == dims(mixed, X) @test axes(mixed, 2) == Base.OneTo(3) @test dims(axes(mixed, 2)) == dims(mixed, 2) - @test first(s) == da1 # first/last are for the NamedTuple - @test last(s) == da3 + @test_throws ErrorException first(s) == da1 # first/last are for the NamedTuple + @test_throws ErrorException last(s) == da3 @test NamedTuple(s) == (one=da1, two=da2, three=da3) end From 55d6f9c34ba17f7802adb478e4e67ca880b33cc2 Mon Sep 17 00:00:00 2001 From: Felix Cremer Date: Wed, 28 Feb 2024 12:13:46 +0100 Subject: [PATCH 049/108] Increase Compat of DataAPI to 1.16 (#652) This is needed because we need the DataAPI.groupby function to be available --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index d65c88542..388801c49 100644 --- a/Project.toml +++ b/Project.toml @@ -39,7 +39,7 @@ ColorTypes = "0.11" Combinatorics = "1" ConstructionBase = "1" CoordinateTransformations = "0.6" -DataAPI = "1" +DataAPI = "1.16" DataFrames = "1" Dates = "1" Distributions = "0.25" From a7e42b3221b09aaa83fc258b467b039617c43032 Mon Sep 17 00:00:00 2001 From: Lazaro Alonso Date: Wed, 28 Feb 2024 19:38:36 +0100 Subject: [PATCH 050/108] Link readme (#654) * small fixes, readme * logo readme * b size * in b * spaces * no breaks * mv up --- README.md | 20 ++++++++++---------- docs/src/.vitepress/theme/style.css | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 721da03b2..049e9e469 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,19 @@ # DimensionalData -[![](https://img.shields.io/badge/docs-dev-blue.svg)](https://rafaqz.github.io/DimensionalData.jl/) +[![](https://img.shields.io/badge/docs-stable-blue.svg)](https://rafaqz.github.io/DimensionalData.jl/stable) +[![](https://img.shields.io/badge/docs-dev-blue.svg)](https://rafaqz.github.io/DimensionalData.jl/dev) [![CI](https://github.com/rafaqz/DimensionalData.jl/actions/workflows/ci.yml/badge.svg)](https://github.com/rafaqz/DimensionalData.jl/actions/workflows/ci.yml) [![Codecov](https://codecov.io/gh/rafaqz/DimensionalData.jl/branch/main/graph/badge.svg)](https://codecov.io/gh/rafaqz/DimensionalData.jl/tree/main) [![Aqua.jl Quality Assurance](https://img.shields.io/badge/Aqua.jl-%F0%9F%8C%A2-aqua.svg)](https://github.com/JuliaTesting/Aqua.jl) -DimensionalData.jl provides tools and abstractions for working with datasets -that have named dimensions, and optionally a lookup index. It provides no-cost -abstractions for named indexing, and fast index lookups. + -DimensionalData is a pluggable, generalised version of -[AxisArrays.jl](https://github.com/JuliaArrays/AxisArrays.jl) with a cleaner -syntax, and additional functionality found in NamedDims.jl. It has similar goals -to pythons [xarray](http://xarray.pydata.org/en/stable/), and is primarily -written for use with spatial data in [Rasters.jl](https://github.com/rafaqz/Rasters.jl). +> [!TIP] +> Visit the latest documentation at https://rafaqz.github.io/DimensionalData.jl/dev/ + +DimensionalData.jl provides tools and abstractions for working with datasets that have named dimensions, and optionally a lookup index. It provides no-cost abstractions for named indexing, and fast index lookups. + +DimensionalData is a pluggable, generalised version of [AxisArrays.jl](https://github.com/JuliaArrays/AxisArrays.jl) with a cleaner syntax, and additional functionality found in NamedDims.jl. It has similar goals to pythons [xarray](http://xarray.pydata.org/en/stable/), and is primarily written for use with spatial data in [Rasters.jl](https://github.com/rafaqz/Rasters.jl). > [!IMPORTANT] > INSTALLATION @@ -126,7 +126,7 @@ using DimensionalData.LookupArrays, DimensionalData.Dimensions ``` > [!IMPORTANT] -> Alternate Packages +> Alternative Packages There are a lot of similar Julia packages in this space. AxisArrays.jl, NamedDims.jl, NamedArrays.jl are registered alternative that each cover some of the functionality provided by DimensionalData.jl. DimensionalData.jl should be able to replicate most of their syntax and functionality. diff --git a/docs/src/.vitepress/theme/style.css b/docs/src/.vitepress/theme/style.css index 32ae6a964..0750b4ccb 100644 --- a/docs/src/.vitepress/theme/style.css +++ b/docs/src/.vitepress/theme/style.css @@ -74,7 +74,7 @@ https://github.com/vuejs/vitepress/blob/main/src/client/theme-default/styles/var --vp-home-hero-image-background-image: linear-gradient( -45deg, - #dce8ee 5%, + #0087d7 35%, #0087d7 35%, #ff875f ); From f5468f2d654d0d6dd9f4c27c9a45efd5222edbf8 Mon Sep 17 00:00:00 2001 From: Rafael Schouten Date: Thu, 29 Feb 2024 01:41:40 +0100 Subject: [PATCH 051/108] get docs checks passing (#653) * get docs checks passing * clean up removing dimstride * add Tables docs dep * fix and test DimSlices * fix DimSlices show and groupby --- docs/Project.toml | 1 + docs/make.jl | 4 +- docs/src/.vitepress/config.mts | 8 ++- docs/src/api/dimensions.md | 39 +++++++------- docs/src/api/lookuparrays.md | 1 + docs/src/api/reference.md | 26 +++++++++- docs/src/basics.md | 7 ++- docs/src/dimarrays.md | 2 +- docs/src/diskarrays.md | 4 +- docs/src/extending_dd.md | 4 +- docs/src/get_info.md | 2 +- docs/src/groupby.md | 2 +- docs/src/index.md | 20 ++++---- docs/src/integrations.md | 2 +- docs/src/selectors.md | 2 +- docs/src/stacks.md | 2 +- docs/src/tables.md | 4 +- src/DimensionalData.jl | 13 ++--- src/Dimensions/Dimensions.jl | 2 +- src/Dimensions/dimension.jl | 26 +++++----- src/Dimensions/merged.jl | 53 ++----------------- src/Dimensions/primitives.jl | 70 ++++++++++++------------- src/Dimensions/set.jl | 4 +- src/LookupArrays/selector.jl | 6 +-- src/array/array.jl | 14 +++-- src/array/indexing.jl | 4 +- src/array/show.jl | 6 +-- src/dimindices.jl | 7 ++- src/groupby.jl | 93 ++++++++++++++++++++++++++-------- src/stack/indexing.jl | 4 +- src/stack/methods.jl | 7 +-- src/stack/stack.jl | 4 ++ src/tables.jl | 37 +++++++++++--- src/utils.jl | 7 ++- test/dimindices.jl | 10 ++++ test/groupby.jl | 4 +- test/merged.jl | 3 +- test/primitives.jl | 10 ---- test/tables.jl | 2 +- 39 files changed, 290 insertions(+), 226 deletions(-) diff --git a/docs/Project.toml b/docs/Project.toml index 65c49fdcf..39672d1aa 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -15,4 +15,5 @@ Interfaces = "85a1e053-f937-4924-92a5-1367d23b7b87" Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" +Tables = "bd369af6-aec1-5ad0-b16a-f7cc5008161c" Unitful = "1986cc42-f94f-5a68-af5c-568840ba703d" diff --git a/docs/make.jl b/docs/make.jl index 31a0b791d..c6dcdc239 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -1,6 +1,8 @@ using DocumenterVitepress using Documenter using DimensionalData +using DimensionalData.Dimensions +using DimensionalData.LookupArrays # Names are available everywhere so that [`function`](@ref) works. # ==================== @@ -21,7 +23,7 @@ makedocs(; sitename="DimensionalData", authors="Rafael Schouten et al.", draft=false, source="src", build="build", - warnonly = true, + # warnonly = true, ) # Deploy built documentation. diff --git a/docs/src/.vitepress/config.mts b/docs/src/.vitepress/config.mts index f86f6dc70..1c711d2bd 100644 --- a/docs/src/.vitepress/config.mts +++ b/docs/src/.vitepress/config.mts @@ -42,13 +42,12 @@ export default defineConfig({ { text: 'Dimensions', link: '/dimensions' }, { text: 'DimArrays', link: '/dimarrays' }, { text: 'Selectors', link: '/selectors' }, - { text: 'Integrations', + { text: 'Integrations', link: '/integrations', items: [ { text: 'Plots and Makie', link: '/plots' }, { text: 'Tables and DataFrames', link: '/tables' }, { text: 'CUDA and GPUs', link: '/cuda' }, { text: 'DiskArrays', link: '/diskarrays' }, - { text: 'Ecosystem', link: '/integrations' }, { text: 'Extending DimensionalData', link: '/extending_dd' }, ], }, @@ -64,19 +63,18 @@ export default defineConfig({ items: [ { text: 'Dimensions', link: '/dimensions' }, { text: 'Selectors', link: '/selectors' }, - { text: 'Dimarrays', link: '/dimarrays' }, + { text: 'DimArrays', link: '/dimarrays' }, { text: 'DimStacks', link: '/stacks' }, { text: 'GroupBy', link: '/groupby' }, { text: 'Getting information', link: '/get_info' }, { text: 'Object modification', link: '/object_modification' }, ]}, - { text: 'Integrations', + { text: 'Integrations', link: '/integrations', items: [ { text: 'Plots and Makie', link: '/plots' }, { text: 'Tables and DataFrames', link: '/tables' }, { text: 'CUDA and GPUs', link: '/cuda' }, { text: 'DiskArrays', link: '/diskarrays' }, - { text: 'Ecosystem', link: '/integrations' }, { text: 'Extending DimensionalData', link: '/extending_dd' }, ], }, diff --git a/docs/src/api/dimensions.md b/docs/src/api/dimensions.md index 9155d5d34..885cea239 100644 --- a/docs/src/api/dimensions.md +++ b/docs/src/api/dimensions.md @@ -23,8 +23,8 @@ Y Z Ti Dim -Coord Dimensions.AnonDim +Dimensions.@dim ``` ### Exported methods @@ -33,18 +33,19 @@ These are widely useful methods for working with dimensions. ```@docs; canonical=false dims +otherdims dimnum hasdim -otherdims ``` ### Non-exported methods ```@docs +Dimensions.lookup Dimensions.label -DimensionalData.format -DimensionalData.dims2indices -DimensionalData.selectindices +Dimensions.format +Dimensions.dims2indices +Dimensions.selectindices ``` ### Primitive methods @@ -55,19 +56,17 @@ can be useful for writing dimensional algorithms. They are not guaranteed to keep their interface, but usually will. ```@docs -DimensionalData.commondims -DimensionalData.dim2key -DimensionalData.key2dim -DimensionalData.reducedims -DimensionalData.swapdims -DimensionalData.slicedims -DimensionalData.comparedims -DimensionalData.combinedims -DimensionalData.sortdims -DimensionalData.basetypeof -DimensionalData.basedims -DimensionalData.setdims -DimensionalData.dimsmatch -DimensionalData.dimstride -DimensionalData.refdims_title +Dimensions.commondims +Dimensions.dim2key +Dimensions.key2dim +Dimensions.reducedims +Dimensions.swapdims +Dimensions.slicedims +Dimensions.comparedims +Dimensions.combinedims +Dimensions.sortdims +Dimensions.basetypeof +Dimensions.basedims +Dimensions.setdims +Dimensions.dimsmatch ``` diff --git a/docs/src/api/lookuparrays.md b/docs/src/api/lookuparrays.md index 316ab9b3b..df4f5e3b1 100644 --- a/docs/src/api/lookuparrays.md +++ b/docs/src/api/lookuparrays.md @@ -107,4 +107,5 @@ LookupArrays.AutoLocus LookupArrays.AbstractMetadata LookupArrays.Metadata LookupArrays.NoMetadata +LookupArrays.units ``` diff --git a/docs/src/api/reference.md b/docs/src/api/reference.md index 9fc603e45..3c866574c 100644 --- a/docs/src/api/reference.md +++ b/docs/src/api/reference.md @@ -4,6 +4,7 @@ ## Arrays ```@docs +DimensionalData.AbstractBasicDimArray AbstractDimArray DimArray ``` @@ -24,6 +25,9 @@ dims refdims metadata name +otherdims +dimnum +hasdim ``` ## Multi-array datasets @@ -48,6 +52,22 @@ DimensionalData.AbstractDimTable DimTable ``` +# Group by methods + +For transforming DimensionalData objects: + +```@docs +groupby +DimensionalData.DimGroupByArray +Bins +ranges +intervals +CyclicBins +seasons +months +hours +``` + # Utility methods For transforming DimensionalData objects: @@ -67,7 +87,6 @@ Base methods ```@docs Base.cat -Base.map Base.copy! Base.eachslice ``` @@ -83,10 +102,13 @@ DimensionalData.Name DimensionalData.NoName ``` -## Internal interface methods +## Internal interface ```@docs +DimensionalData.DimArrayInterface +DimensionalData.DimStackInterface DimensionalData.rebuild_from_arrays DimensionalData.show_main DimensionalData.show_after +DimensionalData.refdims_title ``` diff --git a/docs/src/basics.md b/docs/src/basics.md index d33204b5b..60f9850e3 100644 --- a/docs/src/basics.md +++ b/docs/src/basics.md @@ -21,6 +21,7 @@ pkg> status DimensionalData ```` ## Basics + Start using the package: ````@example basics @@ -39,11 +40,9 @@ or C = DimArray(rand(Int8, 10), (alpha='a':'j',)) ```` -or maybe something a little bit more cumbersome: +or something a little bit more complicated: ````@ansi basics -data = rand(Int8, 2,10,3) .|> abs; +data = rand(Int8, 2, 10, 3) .|> abs B = DimArray(data, (channel=[:left, :right], time=1:10, iter=1:3)) ```` - -See details on the [`Dimensions`](/dimensions) section. \ No newline at end of file diff --git a/docs/src/dimarrays.md b/docs/src/dimarrays.md index 3b5908ce8..b48c666a1 100644 --- a/docs/src/dimarrays.md +++ b/docs/src/dimarrays.md @@ -1,4 +1,4 @@ -# DimArray +# DimArrays `DimArray`s are wrappers for other kinds of `AbstractArray` that add named dimension lookups. diff --git a/docs/src/diskarrays.md b/docs/src/diskarrays.md index dea61ecd7..4eb566d81 100644 --- a/docs/src/diskarrays.md +++ b/docs/src/diskarrays.md @@ -1,6 +1,6 @@ -# [DiskArrays.jl](https://github.com/meggart/DiskArrays.jl) compatability +# DiskArrays.jl compatability -DiskArrays enables lazy, chunked application of: +[DiskArrays.jl](https://github.com/meggart/DiskArrays.jl) enables lazy, chunked application of: - broadcast - reductions diff --git a/docs/src/extending_dd.md b/docs/src/extending_dd.md index f293b0b71..637852291 100644 --- a/docs/src/extending_dd.md +++ b/docs/src/extending_dd.md @@ -107,7 +107,7 @@ using DimensionalData, Interfaces @implements DimensionalData.DimArrayInterface{(:refdims,:name,:metadata)} DimArray [rand(X(10), Y(10)), zeros(Z(10))] ```` -See the [`DimArrayInterface`](@ref) docs for options. We can test it with: +See the [`DimensionalData.DimArrayInterface`](@ref) docs for options. We can test it with: ````@ansi interfaces Interfaces.test(DimensionalData.DimArrayInterface) @@ -121,7 +121,7 @@ The implementation definition for `DimStack`: @implements DimensionalData.DimStackInterface{(:refdims,:metadata)} DimStack [DimStack(zeros(Z(10))), DimStack(rand(X(10), Y(10))), DimStack(rand(X(10), Y(10)), rand(X(10)))] ```` -See the [`DimStackInterface`](@ref) docs for options. We can test it with: +See the [`DimensionalData.DimStackInterface`](@ref) docs for options. We can test it with: ````@ansi interfaces Interfaces.test(DimensionalData.DimStackInterface) diff --git a/docs/src/get_info.md b/docs/src/get_info.md index c5c0dc2f6..5a7086c66 100644 --- a/docs/src/get_info.md +++ b/docs/src/get_info.md @@ -1,4 +1,4 @@ -## Getters +# Getters DimensionalData.jl defines consistent methods to retreive information from objects like `DimArray`, `DimStack`, `Tuple`s of `Dimension`, diff --git a/docs/src/groupby.md b/docs/src/groupby.md index 5172c0a57..8d21f8f78 100644 --- a/docs/src/groupby.md +++ b/docs/src/groupby.md @@ -265,7 +265,7 @@ groupby(A, Ti => months(2; start=6)) == hours -`hours` works a lot like `months`. Here we groupb into day +`hours` works a lot like `months`. Here we group into day and night - two 12 hour blocks starting at 6am: ````@ansi groupby diff --git a/docs/src/index.md b/docs/src/index.md index ec562680c..97b3240a5 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -5,31 +5,31 @@ layout: home hero: name: "DimensionalData.jl" - text: "Julia Datasets with named dimensions" - tagline: High performance name indexing for Julia + text: "Julia datasets with named dimensions" + tagline: High performance named indexing for Julia image: src: 'logoDD.png' actions: - theme: brand text: Getting Started link: /basics - - theme: alt - text: View on Github - link: https://github.com/rafaqz/DimensionalData.jl - theme: alt text: API reference link: /api/reference + - theme: alt + text: View on Github + link: https://github.com/rafaqz/DimensionalData.jl features: - icon: 3d-scale - title: Intelligent Indexing - details: It works with datasets that have named dimensions. It provides no-cost abstractions for named indexing, and fast index lookups. + title: Intelligent indexing + details: DimensionalData.jl provides no-cost abstractions for named indexing, and fast index lookups. link: /selectors - icon: grid - title: Powerful Array Manipulation - details: Permutedims, reduce, mapreduce, adjoint, transpose, broadcasting etc., and groupby operations. + title: Powerful Array manipulation + details: broadcast, reduce, permutedims, and groupby operations. link: /groupby - icon: layers title: Seamlessly integrated with the julia ecosystem - details: Works with base methods. If a method accepts numeric indices or dims=X in base, you should be able to use DimensionalData.jl dims. + details: Works with most methods that accept a regular `Array`. If a method accepts numeric indices or dims=X in base, you should be able to use DimensionalData.jl dims. --- ``` diff --git a/docs/src/integrations.md b/docs/src/integrations.md index be75b0e6d..c0a5f110a 100644 --- a/docs/src/integrations.md +++ b/docs/src/integrations.md @@ -1,4 +1,4 @@ -# Ecosystem +# Integrations ## Rasters.jl diff --git a/docs/src/selectors.md b/docs/src/selectors.md index d399d297b..00995f63c 100644 --- a/docs/src/selectors.md +++ b/docs/src/selectors.md @@ -1,4 +1,4 @@ -## Selectors +# Selectors As well as choosing dimensions by name, we can also select values in them. diff --git a/docs/src/stacks.md b/docs/src/stacks.md index 37c3436eb..8b60b9353 100644 --- a/docs/src/stacks.md +++ b/docs/src/stacks.md @@ -1,4 +1,4 @@ -## DimStacks +# DimStacks An `AbstractDimStack` represents a collection of `AbstractDimArray` layers that share some or all dimensions. For any two layers, a dimension diff --git a/docs/src/tables.md b/docs/src/tables.md index 9b9dd0f0c..0308dc053 100644 --- a/docs/src/tables.md +++ b/docs/src/tables.md @@ -8,8 +8,8 @@ packages that implement the standard. DimensionalData.jl implements the Tables.jl interface for `AbstractDimArray` and `AbstractDimStack`. `DimStack` layers -are unrolled so they are all the same size, and dimensions similarly loop -over array strides to match the length of the largest layer. +are unrolled so they are all the same size, and dimensions +loop to match the length of the largest layer. Columns are given the [`name`](@ref) or the array or the stack layer key. `Dimension` columns use the `Symbol` version (the result of `DD.dim2key(dimension)`). diff --git a/src/DimensionalData.jl b/src/DimensionalData.jl index f0970e5ff..8691145cd 100644 --- a/src/DimensionalData.jl +++ b/src/DimensionalData.jl @@ -1,12 +1,5 @@ module DimensionalData -# Use the README as the module docs -@doc let - path = joinpath(dirname(@__DIR__), "README.md") - include_dependency(path) - read(path, String) -end DimensionalData - # Standard lib using Dates, LinearAlgebra, @@ -46,7 +39,7 @@ using .Dimensions: StandardIndices, DimOrDimType, DimTuple, DimTupleOrEmpty, Dim import .LookupArrays: metadata, set, _set, rebuild, basetypeof, order, span, sampling, locus, val, index, bounds, intervalbounds, hasselection, units, SelectorOrInterval -import .Dimensions: dims, refdims, name, lookup, dimstride, kwdims, hasdim, label, _astuple +import .Dimensions: dims, refdims, name, lookup, kw2dims, hasdim, label, _astuple import DataAPI.groupby @@ -56,7 +49,7 @@ export LookupArrays, Dimensions export X, Y, Z, Ti, Dim, Coord # Selector -export At, Between, Touches, Contains, Near, Where, All, .., Not, Bins +export At, Between, Touches, Contains, Near, Where, All, .., Not, Bins, CyclicBins export AbstractDimArray, DimArray @@ -77,7 +70,7 @@ export dimnum, hasdim, hasselection, otherdims # utils export set, rebuild, reorder, modify, broadcast_dims, broadcast_dims!, mergedims, unmergedims -export groupby, seasons, months, hours, yeardays, monthdays, intervals, ranges +export groupby, seasons, months, hours, intervals, ranges const DD = DimensionalData diff --git a/src/Dimensions/Dimensions.jl b/src/Dimensions/Dimensions.jl index 92d4e33e9..7b01cdcc0 100644 --- a/src/Dimensions/Dimensions.jl +++ b/src/Dimensions/Dimensions.jl @@ -32,7 +32,7 @@ using Base: tail, OneTo, @propagate_inbounds export name, label, dimnum, hasdim, hasselection, otherdims, commondims, combinedims, setdims, swapdims, sortdims, lookup, set, format, rebuild, key2dim, dim2key, - basetypeof, basedims, dimstride, dims2indices, slicedims, dimsmatch, comparedims, reducedims + basetypeof, basedims, dims2indices, slicedims, dimsmatch, comparedims, reducedims export Dimension, IndependentDim, DependentDim, XDim, YDim, ZDim, TimeDim, X, Y, Z, Ti, Dim, AnonDim, Coord, MergedLookup, AutoVal diff --git a/src/Dimensions/dimension.jl b/src/Dimensions/dimension.jl index 73ea853dd..761e92fbc 100644 --- a/src/Dimensions/dimension.jl +++ b/src/Dimensions/dimension.jl @@ -35,11 +35,11 @@ A = DimArray(zeros(3, 5, 12), (y, x, ti)) ╭────────────────────────────╮ │ 3×5×12 DimArray{Float64,3} │ -├────────────────────────────┴──────────────────────────────────────────────────────────────────────────────────────────── dims ┐ +├────────────────────────────┴─────────────────────────────────────────── dims ┐ ↓ Y Categorical{Char} ['a', 'b', 'c'] ForwardOrdered, → X Sampled{Int64} 2:2:10 ForwardOrdered Regular Points, - ↗ Ti Sampled{DateTime} DateTime("2021-01-01T00:00:00"):Month(1):DateTime("2021-12-01T00:00:00") ForwardOrdered Regular Points -└───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ + ↗ Ti Sampled{Dates.DateTime} Dates.DateTime("2021-01-01T00:00:00"):Dates.Month(1):Dates.DateTime("2021-12-01T00:00:00") ForwardOrdered Regular Points +└──────────────────────────────────────────────────────────────────────────────┘ [:, :, 1] ↓ → 2 4 6 8 10 'a' 0.0 0.0 0.0 0.0 0.0 @@ -57,15 +57,17 @@ x = A[X(2), Y(3)] ╭────────────────────────────────╮ │ 12-element DimArray{Float64,1} │ -├────────────────────────────────┴──────────────────────────────────────────────────────────────────────────────────────── dims ┐ - ↓ Ti Sampled{DateTime} DateTime("2021-01-01T00:00:00"):Month(1):DateTime("2021-12-01T00:00:00") ForwardOrdered Regular Points -└───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ +├────────────────────────────────┴─────────────────────────────────────── dims ┐ + ↓ Ti Sampled{Dates.DateTime} Dates.DateTime("2021-01-01T00:00:00"):Dates.Month(1):Dates.DateTime("2021-12-01T00:00:00") ForwardOrdered Regular Points +└──────────────────────────────────────────────────────────────────────────────┘ 2021-01-01T00:00:00 0.0 2021-02-01T00:00:00 0.0 2021-03-01T00:00:00 0.0 2021-04-01T00:00:00 0.0 2021-05-01T00:00:00 0.0 - ⋮ + 2021-06-01T00:00:00 0.0 + 2021-07-01T00:00:00 0.0 + 2021-08-01T00:00:00 0.0 2021-09-01T00:00:00 0.0 2021-10-01T00:00:00 0.0 2021-11-01T00:00:00 0.0 @@ -81,12 +83,12 @@ x = A[X(Between(3, 4)), Y(At('b'))] ╭──────────────────────────╮ │ 1×12 DimArray{Float64,2} │ -├──────────────────────────┴────────────────────────────────────────────────────────────────────────────────────────────── dims ┐ +├──────────────────────────┴───────────────────────────────────────────── dims ┐ ↓ X Sampled{Int64} 4:2:4 ForwardOrdered Regular Points, - → Ti Sampled{DateTime} DateTime("2021-01-01T00:00:00"):Month(1):DateTime("2021-12-01T00:00:00") ForwardOrdered Regular Points -└───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ - ↓ → 2021-01-01T00:00:00 2021-02-01T00:00:00 2021-03-01T00:00:00 … 2021-10-01T00:00:00 2021-11-01T00:00:00 2021-12-01T00:00:00 - 4 0.0 0.0 0.0 0.0 0.0 0.0 + → Ti Sampled{Dates.DateTime} Dates.DateTime("2021-01-01T00:00:00"):Dates.Month(1):Dates.DateTime("2021-12-01T00:00:00") ForwardOrdered Regular Points +└──────────────────────────────────────────────────────────────────────────────┘ + ↓ → 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 diff --git a/src/Dimensions/merged.jl b/src/Dimensions/merged.jl index 174d70bf3..2441e354a 100644 --- a/src/Dimensions/merged.jl +++ b/src/Dimensions/merged.jl @@ -26,8 +26,6 @@ struct MergedLookup{T,A<:AbstractVector{T},D,Me} <: LookupArray{T,1} end MergedLookup(data, dims; metadata=NoMetadata()) = MergedLookup(data, dims, metadata) -@deprecate CoordLookupArray MergedLookup - order(m::MergedLookup) = Unordered() dims(m::MergedLookup) = m.dims dims(d::Dimension{<:MergedLookup}) = dims(val(d)) @@ -89,57 +87,13 @@ _matches(sel::At, x) = x == val(sel) _matches(sel::Colon, x) = true _matches(sel::Nothing, x) = true _matches(sel::Where, x) = sel.f(x) -@noinline _matches(sel::Near, x) = throw(ArgumentError("`Near` is not implemented for coordinates")) +@noinline _matches(::Near, x) = throw(ArgumentError("`Near` is not implemented for coordinates")) # TODO: Do we still need `Coord` as a dimension? -""" - Coord <: Dimension - -A coordinate dimension itself holds dimensions. - -This allows combining point data with other dimensions, such as time. - -# Example - -```julia -julia> using DimensionalData - -julia> dim = Coord([(1.0,1.0,1.0), (1.0,2.0,2.0), (3.0,4.0,4.0), (1.0,3.0,4.0)], (X(), Y(), Z())) -Coord :: - val: Tuple{Float64, Float64, Float64}[(1.0, 1.0, 1.0), (1.0, 2.0, 2.0), (3.0, 4.0, 4.0), (1.0, 3.0, -4.0)] - lookup: MergedLookup -Coord{Vector{Tuple{Float64, Float64, Float64}}, DimensionalData.MergedLookup{Tuple{X{Colon, AutoLookup{Auto -Order}, NoMetadata}, Y{Colon, AutoLookup{AutoOrder}, NoMetadata}, Z{Colon, AutoLookup{AutoOrder}, NoMetada -ta}}}, NoMetadata} - -julia> da = DimArray(0.1:0.1:0.4, dim) -4-element DimArray{Float64,1} with dimensions: - Coord (): Tuple{Float64, Float64, Float64}[(1.0, 1.0, 1.0), (1.0, 2.0, 2.0), (3.0, 4.0, 4.0), (1.0, -3.0, 4.0)] - MergedLookup - 0.1 - 0.2 - 0.3 - 0.4 - -julia> da[Coord(Z(At(1.0)), Y(Between(1, 3)))] -1-element DimArray{Float64,1} with dimensions: - Coord (): Tuple{Float64, Float64, Float64}[(1.0, 1.0, 1.0)] MergedLookup - 0.1 - -julia> da[Coord(4)] == 0.4 -true - -julia> da[Coord(Between(1, 5), :, At(4.0))] -2-element DimArray{Float64,1} with dimensions: - Coord (): Tuple{Float64, Float64, Float64}[(3.0, 4.0, 4.0), (1.0, 3.0, 4.0)] MergedLookup - 0.3 - 0.4 -``` -""" +const SelOrStandard = Union{Selector,StandardIndices} + struct Coord{T} <: Dimension{T} val::T end @@ -148,7 +102,6 @@ function Coord(val::T, dims::Tuple) where {T<:AbstractVector} lookup = MergedLookup(val, key2dim(dims)) Coord(lookup) end -const SelOrStandard = Union{Selector,StandardIndices} Coord(s1::SelOrStandard, s2::SelOrStandard, sels::SelOrStandard...) = Coord((s1, s2, sels...)) Coord(sel::Selector) = Coord((sel,)) Coord(d1::Dimension, dims::Dimension...) = Coord((d1, dims...)) diff --git a/src/Dimensions/primitives.jl b/src/Dimensions/primitives.jl index 091b5b016..f27213bdc 100644 --- a/src/Dimensions/primitives.jl +++ b/src/Dimensions/primitives.jl @@ -10,6 +10,7 @@ or are at least rotations/transformations of the same type. `f` is `<:` by default, but can be `>:` to match abstract types to concrete types. """ +function dimsmatch end @inline dimsmatch(dims, query) = dimsmatch(<:, dims, query) @inline function dimsmatch(f::Function, dims::Tuple, query::Tuple) length(dims) == length(query) || return false @@ -41,6 +42,7 @@ to `X()`, `Y()`, `Ti()`, as with any other dims generated with the [`@dim`](@ref All other `Symbol`s `S` will generate `Dim{S}()` dimensions. """ +function key2dim end @inline key2dim(t::Tuple) = map(key2dim, t) @inline key2dim(s::Symbol) = key2dim(Val{s}()) # Allow other things to pass through @@ -59,6 +61,7 @@ to `:X`, `:Y`, `:Ti`, as with any other dims generated with the [`@dim`](@ref) m All other `Dim{S}()` dimensions will generate `Symbol`s `S`. """ +function dim2key end @inline dim2key(dims::Tuple) = map(dim2key, dims) @inline dim2key(dim::Dimension) = dim2key(typeof(dim)) @inline dim2key(::Val{D}) where D <: Dimension = dim2key(D) @@ -66,9 +69,7 @@ All other `Dim{S}()` dimensions will generate `Symbol`s `S`. @inline dim2key(s::Symbol) = s # dim2key is defined for concrete instances in dimensions.jl - -@inline _asfunc(::Type{typeof(<:)}) = <: -@inline _asfunc(::Type{typeof(>:)}) = >: +# """ sortdims([f], tosort, order) => Tuple @@ -82,9 +83,14 @@ can be used in `order`. `f` is `<:` by default, but can be `>:` to sort abstract types by concrete types. """ +function sortdims end @inline sortdims(a1, a2) = _dim_query(_sortdims, MaybeFirst(), a1, a2) @inline sortdims(f::Function, a1, a2) = _dim_query(_sortdims, f, MaybeFirst(), a1, a2) +# Defined before the @generated function for world age +_asfunc(::Type{typeof(<:)}) = <: +_asfunc(::Type{typeof(>:)}) = >: + @inline _sortdims(f, tosort, order::Tuple{<:Integer,Vararg}) =map(p -> tosort[p], order) @inline _sortdims(f, tosort, order) = _sortdims_gen(f, tosort, order) @@ -143,6 +149,7 @@ julia> dims(A, (X, Y)) ``` """ +function dims end @inline dims(a1, args...) = _dim_query(_dims, MaybeFirst(), a1, args...) @inline dims(::Tuple{}, ::Tuple{}) = () @@ -176,6 +183,7 @@ julia> commondims(A, Ti) ``` """ +function commondims end @inline commondims(a1, args...) = _dim_query(_commondims, AlwaysTuple(), a1, args...) _commondims(f, ds, query) = _dims(f, ds, _dims(_flip_subtype(f), query, ds)) @@ -207,6 +215,7 @@ julia> dimnum(A, Y) 2 ``` """ +function dimnum end @inline function dimnum(x, q1, query...) all(hasdim(x, q1, query...)) || _extradimserror(otherdims(x, (q1, query))) _dim_query(_dimnum, MaybeFirst(), x, q1, query...) @@ -252,6 +261,7 @@ julia> hasdim(A, Ti) false ``` """ +function hasdim end @inline hasdim(x, a1, args...) = _dim_query(_hasdim, MaybeFirst(), x, a1, args...) @@ -285,9 +295,9 @@ julia> otherdims(A, (Y, Z)) ↓ X ``` """ -@inline otherdims(x, query) = begin +function otherdims end +@inline otherdims(x, query) = _dim_query(_otherdims_presort, AlwaysTuple(), x, query) -end @inline otherdims(x, query...) = _dim_query(_otherdims_presort, AlwaysTuple(), x, query) @@ -324,6 +334,7 @@ Categorical{Char} ForwardOrdered wrapping: 'a':1:'j' ``` """ +function setdims end @inline setdims(x, d1, d2, ds...) = setdims(x, (d1, d2, ds...)) @inline setdims(x) = x @inline setdims(x, newdims::Dimension) = rebuild(x; dims=setdims(dims(x), key2dim(newdims))) @@ -366,6 +377,7 @@ Dimensions.swapdims(A, (Dim{:a}, Dim{:b}, Dim{:c})) 1.0 1.0 1.0 1.0 ``` """ +function swapdims end @inline swapdims(x, d1, d2, ds...) = swapdims(x, (d1, d2, ds...)) @inline swapdims(x) = x @inline swapdims(x, newdims::Tuple) = @@ -474,6 +486,7 @@ but the number of dimensions has not changed. `LookupArray` traits are also updated to correspond to the change in cell step, sampling type and order. """ +function reducedims end @inline reducedims(x, dimstoreduce) = _reducedims(x, key2dim(dimstoreduce)) @inline _reducedims(x, dimstoreduce) = _reducedims(x, (dimstoreduce,)) @@ -637,25 +650,6 @@ _combinedims(a::DimTupleOrEmpty, b::DimTupleOrEmpty; check=true, kw...) = begin (a..., otherdims(b, a)...) end -""" - dimstride(x, dim) => Int - -Get the stride of the dimension relative to the other dimensions. - -This may or may not be equal to the stride of the related array, -although it will be for `Array`. - -## Arguments - -- `x` is any object with a `dims` method, or a `Tuple` of `Dimension`. -- `dim` is a `Dimension`, `Dimension` type, or and `Int`. Using an `Int` is not type-stable. -""" -@inline dimstride(x, n) = dimstride(dims(x), n) -@inline dimstride(::Nothing, n) = _dimsnotdefinederror() -@inline dimstride(dims::DimTuple, d::DimOrDimType) = dimstride(dims, dimnum(dims, d)) -@inline dimstride(dims::DimTuple, n::Int) = prod(map(length, dims)[1:n-1]) - - """ basedims(ds::Tuple) basedims(d::Union{Dimension,Symbol,Type}) @@ -664,14 +658,27 @@ Returns `basetypeof(d)()` or a `Tuple` of called on a `Tuple`. See [`basetypeof`](@ref) """ +function basedims end @inline basedims(x) = basedims(dims(x)) @inline basedims(ds::Tuple) = map(basedims, ds) @inline basedims(d::Dimension) = basetypeof(d)() @inline basedims(d::Symbol) = key2dim(d) @inline basedims(T::Type{<:Dimension}) = basetypeof(T)() +@inline pairs2dims(pairs::Pair...) = map(p -> basetypeof(key2dim(first(p)))(last(p)), pairs) + +@inline kw2dims(kw::Base.Iterators.Pairs) = kw2dims(values(kw)) +# Convert `Symbol` keyword arguments to a `Tuple` of `Dimension` +@inline kw2dims(kw::NamedTuple{Keys}) where Keys = kw2dims(key2dim(Keys), values(kw)) +@inline kw2dims(dims::Tuple, vals::Tuple) = + (rebuild(first(dims), first(vals)), kw2dims(tail(dims), tail(vals))...) +@inline kw2dims(::Tuple{}, ::Tuple{}) = () + + +# Queries + +# Most primitives use these for argument handling -# Utils abstract type QueryMode end struct MaybeFirst <: QueryMode end struct AlwaysTuple <: QueryMode end @@ -717,16 +724,8 @@ end @inline _dim_query1(f, op::Function, ::QueryMode, d::Tuple, query::Tuple) = map(unwrap, f(op, d, query)) @inline _dim_query1(f, op::Function, ::QueryMode, d::Tuple) = map(unwrap, f(op, d)) -_dims_are_not_dims() = throw(ArgumentError("`dims` are not `Dimension`s")) -@inline kwdims(kw::Base.Iterators.Pairs) = kwdims(values(kw)) -# Convert `Symbol` keyword arguments to a `Tuple` of `Dimension` -@inline kwdims(kw::NamedTuple{Keys}) where Keys = kwdims(key2dim(Keys), values(kw)) -@inline kwdims(dims::Tuple, vals::Tuple) = - (rebuild(first(dims), first(vals)), kwdims(tail(dims), tail(vals))...) -@inline kwdims(dims::Tuple{}, vals::Tuple{}) = () - -@inline pairdims(pairs::Pair...) = map(p -> basetypeof(key2dim(first(p)))(last(p)), pairs) +# Utils # Remove `nothing` from a `Tuple` @inline _remove_nothing(xs::Tuple) = _remove_nothing(xs...) @@ -759,6 +758,7 @@ _astuple(x) = (x,) # Warnings and Error methods. + _dimsmismatchmsg(a, b) = "$(basetypeof(a)) and $(basetypeof(b)) dims on the same axis." _valmsg(a, b) = "Lookup values for $(basetypeof(a)) of $(parent(a)) and $(parent(b)) do not match." _dimsizemsg(a, b) = "Found both lengths $(length(a)) and $(length(b)) for $(basetypeof(a))." @@ -786,3 +786,5 @@ _typemsg(a, b) = "Lookups do not all have the same type: $(order(a)), $(order(b) @noinline _metadataerror(a, b) = throw(DimensionMismatch(_metadatamsg(a, b))) @noinline _extradimserror(args) = throw(ArgumentError(_extradimsmsg(args))) @noinline _dimsnotdefinederror() = throw(ArgumentError("Object does not define a `dims` method")) + +@noinline _dims_are_not_dims() = throw(ArgumentError("`dims` are not `Dimension`s")) diff --git a/src/Dimensions/set.jl b/src/Dimensions/set.jl index eaca26ddc..1b74ccc71 100644 --- a/src/Dimensions/set.jl +++ b/src/Dimensions/set.jl @@ -4,10 +4,10 @@ set(dim::Dimension, x::DimSetters) = _set(dim, x) set(dims_::DimTuple, args::Union{Dimension,DimTuple,Pair}...; kw...) = _set(dims_, args...; kw...) # Convert args/kw to dims and set -_set(dims_::DimTuple, args::Dimension...; kw...) = _set(dims_, (args..., kwdims(kw)...)) +_set(dims_::DimTuple, args::Dimension...; kw...) = _set(dims_, (args..., kw2dims(kw)...)) # Convert pairs to wrapped dims and set _set(dims_::DimTuple, p::Pair, ps::Vararg{Pair}) = _set(dims_, (p, ps...)) -_set(dims_::DimTuple, ps::Tuple{Vararg{Pair}}) = _set(dims_, pairdims(ps...)) +_set(dims_::DimTuple, ps::Tuple{Vararg{Pair}}) = _set(dims_, pairs2dims(ps...)) _set(dims_::DimTuple, ::Tuple{}) = dims_ # Set dims with (possibly unsorted) wrapper vals _set(dims::DimTuple, wrappers::DimTuple) = begin diff --git a/src/LookupArrays/selector.jl b/src/LookupArrays/selector.jl index c2f8216d1..0827cd92b 100644 --- a/src/LookupArrays/selector.jl +++ b/src/LookupArrays/selector.jl @@ -1004,10 +1004,10 @@ A[X=All(At(10.0), At(50.0)), Ti=All(1u"s"..10u"s", 90u"s"..100u"s")] ╭───────────────────────╮ │ 2×4 DimArray{Int64,2} │ -├───────────────────────┴──────────────────────────────────────────────────────────────────────────────────────────────── dims ┐ +├───────────────────────┴──────────────────────────────────────────────── dims ┐ ↓ X Sampled{Float64} [10.0, 50.0] ForwardOrdered Irregular Points, - → Ti Sampled{Quantity{Int64, 𝐓, Unitful.FreeUnits{(s,), 𝐓, nothing}}} [1 s, 6 s, 91 s, 96 s] ForwardOrdered Irregular Points -└──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ + → Ti Sampled{Unitful.Quantity{Int64, 𝐓, Unitful.FreeUnits{(s,), 𝐓, nothing}}} [1 s, 6 s, 91 s, 96 s] ForwardOrdered Irregular Points +└──────────────────────────────────────────────────────────────────────────────┘ ↓ → 1 s 6 s 91 s 96 s 10.0 1 2 19 20 50.0 3 6 57 60 diff --git a/src/array/array.jl b/src/array/array.jl index bcd503284..da7e94e35 100644 --- a/src/array/array.jl +++ b/src/array/array.jl @@ -692,10 +692,16 @@ placed at the end of `dims_new`. `others` contains other dimension pairs to be m # Example ````jldoctest -julia> ds = (X(0:0.1:0.4), Y(10:10:100), Ti([0, 3, 4])); -julia> mergedims(ds, Ti => :time, (X, Y) => :space) -Dim{:time} MergedLookup{Tuple{Int64}} Tuple{Int64}[(0,), (3,), (4,)] Ti, -Dim{:space} MergedLookup{Tuple{Float64, Int64}} Tuple{Float64, Int64}[(0.0, 10), (0.1, 10), …, (0.3, 100), (0.4, 100)] X, Y +julia> using DimensionalData + +julia> ds = (X(0:0.1:0.4), Y(10:10:100), Ti([0, 3, 4])) +↓ X 0.0:0.1:0.4, +→ Y 10:10:100, +↗ Ti [0, 3, 4] + +julia> mergedims(ds, (X, Y) => :space) +↓ Ti [0, 3, 4], +→ space MergedLookup{Tuple{Float64, Int64}} [(0.0, 10), (0.1, 10), …, (0.3, 100), (0.4, 100)] ↓ X, → Y ```` """ function mergedims(x, dt1::Tuple, dts::Tuple...) diff --git a/src/array/indexing.jl b/src/array/indexing.jl index 9d4d6c2c3..fd225af6c 100644 --- a/src/array/indexing.jl +++ b/src/array/indexing.jl @@ -51,7 +51,7 @@ for f in (:getindex, :view, :dotview) 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..., kwdims(values(kw))...)...) + $_f(A, _simplify_dim_indices(D..., kw2dims(values(kw))...)...) # For ambiguity @propagate_inbounds Base.$f(A::AbstractDimArray, i::DimIndices) = $_f(A, i) @propagate_inbounds Base.$f(A::AbstractDimArray, i::DimSelectors) = $_f(A, i) @@ -198,7 +198,7 @@ Base.@assume_effects :foldable _simplify_dim_indices() = () @propagate_inbounds Base.setindex!(A::AbstractDimArray, x) = setindex!(parent(A), x) @propagate_inbounds Base.setindex!(A::AbstractDimArray, x, args::Dimension...; kw...) = - setindex!(A, x, dims2indices(A, (args..., kwdims(values(kw))...))...) + setindex!(A, x, dims2indices(A, (args..., kw2dims(values(kw))...))...) @propagate_inbounds Base.setindex!(A::AbstractDimArray, x, i, I...) = setindex!(A, x, dims2indices(A, (i, I...))...) @propagate_inbounds Base.setindex!(A::AbstractDimArray, x, i1::StandardIndices, I::StandardIndices...) = diff --git a/src/array/show.jl b/src/array/show.jl index 572aa60a9..35178de4f 100644 --- a/src/array/show.jl +++ b/src/array/show.jl @@ -173,7 +173,7 @@ function print_block_top(io, label, prev_width, new_width) return lines end -function print_block_separator(io, label, prev_width, new_width=prev_width) +function print_block_separator(io, label, prev_width, new_width) corner = (new_width > prev_width) ? '┐' : '┤' middle_line = string('├', '─'^max(0, new_width - textwidth(label) - 2), ' ', label, ' ', corner) printstyled(io, middle_line; color=:light_black) @@ -248,12 +248,12 @@ function _print_matrix(io::IO, A::AbstractArray{<:Any,1}, lookups::Tuple) copyto!(top, CartesianIndices(top), A, CartesianIndices(itop)) bottom = Array{eltype(A)}(undef, length(ibottom)) copyto!(bottom, CartesianIndices(bottom), A, CartesianIndices(ibottom)) - vals = vcat(parent(A[itop]), parent(A[ibottom])) + vals = vcat(parent(A)[itop], parent(A)[ibottom]) lu = only(lookups) if lu isa NoLookup Base.print_matrix(io, vals) else - labels = vcat(map(show1, parent(lu)[itop]), map(show1, parent(lu))[ibottom]) + labels = vcat(map(show1, parent(lu)[itop]), map(show1, parent(lu)[ibottom])) Base.print_matrix(io, hcat(labels, vals)) end return nothing diff --git a/src/dimindices.jl b/src/dimindices.jl index f8beec2d3..8c6e18ac7 100644 --- a/src/dimindices.jl +++ b/src/dimindices.jl @@ -15,7 +15,7 @@ for f in (:getindex, :dotview, :view) rebuild(di; dims=newdims) end @eval @propagate_inbounds function Base.$f(di::AbstractDimArrayGenerator{<:Any,1}, i::$T) - rebuild(di; dims=dims(di, 1)[i]) + rebuild(di; dims=(dims(di, 1)[i],)) end @eval @propagate_inbounds Base.$f(dg::AbstractDimArrayGenerator, i::Int) = Base.$f(dg, Tuple(CartesianIndices(dg)[i])...) @@ -291,13 +291,16 @@ end DimSlices(x; dims, drop=true) = DimSlices(x, dims; drop) function DimSlices(x, dims; drop=true) newdims = length(dims) == 0 ? map(d -> rebuild(d, :), DD.dims(x)) : dims - inds = map(d -> rebuild(d, first(d)), newdims) + inds = map(d -> rebuild(d, first(axes(x, d))), newdims) T = typeof(view(x, inds...)) N = length(newdims) D = typeof(newdims) return DimSlices{T,N,D,typeof(x)}(x, newdims) end +rebuild(ds::A; dims) where {A<:DimSlices{T,N}} where {T,N} = + DimSlices{T,N,typeof(dims),typeof(ds._data)}(ds._data, dims) + function Base.summary(io::IO, A::DimSlices{T,N}) where {T,N} print_ndims(io, size(A)) print(io, string(nameof(typeof(A)), "{$(nameof(T)),$N}")) diff --git a/src/groupby.jl b/src/groupby.jl index c275ddb58..b4d3f923c 100644 --- a/src/groupby.jl +++ b/src/groupby.jl @@ -121,6 +121,26 @@ Base.show(io::IO, bins::Bins) = println(io, nameof(typeof(bins)), "(", bins.f, ", ", bins.bins, ")") abstract type AbstractCyclicBins end + +""" + CyclicBins(f; cycle, start, step, labels) + +Cyclic bins to reduce groups after applying function `f`. Groups can wrap around +the cycle. This is used for grouping in [`seasons`](@ref), [`months`](@ref) +and [`hours`](@ref) but can also be used for custom cycles. + +- `f` a grouping function of the lookup values, by default `identity`. + +## Keywords + +- `cycle`: the length of the cycle, in return values of `f`. +- `start`: the start of the cycle: a return value of `f`. +- `step` the number of sequential values to group. +- `labels`: either a vector of labels matching the number of groups, + or a function that generates labels from `Vector{Int}` of the selected months. + +When the return value of `f` is a tuple, binning is applied to the _last_ value of the tuples. +""" struct CyclicBins{F,C,Sta,Ste,L} <: AbstractBins f::F cycle::C @@ -135,12 +155,50 @@ Base.show(io::IO, bins::CyclicBins) = yearhour(x) = year(x), hour(x) +""" + seasons(; [start=Dates.December, labels]) + +Generates `CyclicBins` for three month periods. + +## Keywords + +- `start`: By default seasons start in December, but any integer `1:12` can be used. +- `labels`: either a vector of four labels, or a function that generates labels + from `Vector{Int}` of the selected months. +""" seasons(; start=December, kw...) = months(3; start, kw...) + +""" + months(step; [start=Dates.January, labels]) + +Generates `CyclicBins` for grouping to arbitrary month periods. +These can wrap around the end of a year. + +- `step` the number of months to group. + +## Keywords + +- `start`: By default months start in January, but any integer `1:12` can be used. +- `labels`: either a vector of labels matching the numver of groups, + or a function that generates labels from `Vector{Int}` of the selected months. +""" months(step; start=January, labels=Dict(1:12 .=> monthabbr.(1:12))) = CyclicBins(month; cycle=12, step, start, labels) + +""" + hours(step; [start=0, labels]) + +Generates `CyclicBins` for grouping to arbitrary hour periods. +These can wrap around the end of the day. + +- `steps` the number of hours to group. + +## Keywords + +- `start`: By default seasons start in December, but any integer `1:12` can be used. +- `labels`: either a vector of four labels, or a function that generates labels + from `Vector{Int}` of the selected months. +""" hours(step; start=0, labels=nothing) = CyclicBins(hour; cycle=24, step, start, labels) -yearhours(step; start=0, labels=nothing) = CyclicBins(yearhour; cycle=24, step, start, labels) -yeardays(step; start=1, labels=nothing) = CyclicBins(dayofyear; cycle=daysinyear, step, start, labels) -monthdays(step; start=1, labels=nothing) = CyclicBins(dayofmonth; cycle=daysinmonth, step, start, labels) """ groupby(A::Union{AbstractDimArray,AbstractDimStack}, dims::Pair...) @@ -374,23 +432,16 @@ function _maybe_label(labels::Dict, vals) end end -# Helpers +""" + intervals(A::AbstractRange) + +Generate a `Vector` of `UnitRange` with length `step(A)` +""" intervals(rng::AbstractRange) = IntervalSets.Interval{:closed,:open}.(rng, rng .+ step(rng)) -function intervals(la::LookupArray) - if ispoints(la) - rebuild(la; data=(x -> IntervalSets.Interval{:closed,:closed}(x, x)).(la)) - else - rebuild(la; data=(x -> IntervalSets.Interval{:closed,:open}(x[1], x[2])).(intervalbounds(la))) - end -end -function intervals(A::AbstractVector{T}; upper::T) where T - is = Vector{IntervalSets.Interval{:closed,:open}}(undef, length(A)) - for i in eachindex(A)[1:end-1] - is[i] = IntervalSets.Interval{:closed,:open}(A[i], A[i + 1]) - end - is[end] = IntervalSets.Interval{:closed,:open}.(A[end], upper) - return is -end -ranges(rng::AbstractRange) = map(x -> x:x+step(rng)-1, rng) -ranges(rng::LookupArray{<:AbstractRange}) = rebuild(rng; data=ranges(parent(rng))) +""" + ranges(A::AbsttactRange{<:Integer}) + +Generate a `Vector` of `UnitRange` with length `step(A)` +""" +ranges(rng::AbstractRange{<:Integer}) = map(x -> x:x+step(rng)-1, rng) diff --git a/src/stack/indexing.jl b/src/stack/indexing.jl index cc05a21dd..764eb0e72 100644 --- a/src/stack/indexing.jl +++ b/src/stack/indexing.jl @@ -69,7 +69,7 @@ for f in (:getindex, :view, :dotview) @propagate_inbounds function Base.$f( s::AbstractDimStack, D::DimensionalIndices...; kw... ) - $_f(s, _simplify_dim_indices(D..., kwdims(values(kw))...)...) + $_f(s, _simplify_dim_indices(D..., kw2dims(values(kw))...)...) end # Ambiguities @propagate_inbounds function Base.$f( @@ -78,7 +78,7 @@ 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..., kwdims(values(kw))...)...) + $_f(s, _simplify_dim_indices(D..., kw2dims(values(kw))...)...) end @propagate_inbounds function Base.$f( s::AbstractDimStack, diff --git a/src/stack/methods.jl b/src/stack/methods.jl index b8a1bc9f3..dcf3b9585 100644 --- a/src/stack/methods.jl +++ b/src/stack/methods.jl @@ -82,7 +82,7 @@ and 2 layers: """ @static if VERSION < v"1.9-alpha1" function Base.eachslice(s::AbstractDimStack; dims) - dimtuple = _astuple(dims) + dimtuple = _astuple(basedims(dims)) all(hasdim(s, dimtuple)) || throw(DimensionMismatch("s doesn't have all dimensions $dims")) _eachslice(s, dimtuple) end @@ -92,10 +92,11 @@ else if !(dimtuple == ()) all(hasdim(s, dimtuple)) || throw(DimensionMismatch("A doesn't have all dimensions $dims")) end + # Avoid getting DimUnitRange from `axes(s)` axisdims = map(DD.dims(s, dimtuple)) do d - rebuild(d, axes(lookup(d), 1)) + rebuild(d, axes(lookup(d), 1)) end - DimSlices(s; dims=axisdims, drop) + return DimSlices(s; dims=axisdims, drop) end end diff --git a/src/stack/stack.jl b/src/stack/stack.jl index 0a2ecf0fc..63fd02c9b 100644 --- a/src/stack/stack.jl +++ b/src/stack/stack.jl @@ -310,12 +310,16 @@ julia> dimz = (X([:a, :b]), Y(10.0:10.0:30.0)) julia> da1 = DimArray(1A, dimz; name=:one); + julia> da2 = DimArray(2A, dimz; name=:two); + julia> da3 = DimArray(3A, dimz; name=:three); + julia> s = DimStack(da1, da2, da3); + julia> s[At(:b), At(10.0)] (one = 4.0, two = 8.0, three = 12.0) diff --git a/src/tables.jl b/src/tables.jl index 04cfc6c1a..8dea867e2 100644 --- a/src/tables.jl +++ b/src/tables.jl @@ -60,15 +60,36 @@ To get dimension columns, you can index with `Dimension` (`X()`) or - `layersfrom`: Treat a dimension of an `AbstractDimArray` as layers of an `AbstractDimStack`. # Example + ```jldoctest -julia> a = DimArray(rand(32,32,3), (X,Y,Dim{:band})); - -julia> DimTable(a, layersfrom=Dim{:band}, mergedims=(X,Y)=>:geometry) -DimTable with 1024 rows, 4 columns, and schema: - :geometry Tuple{Int64, Int64} - :band_1 Float64 - :band_2 Float64 - :band_3 Float64 +julia> using DimensionalData, Tables + +julia> a = DimArray(ones(16, 16, 3), (X, Y, Dim{:band})) +╭─────────────────────────────╮ +│ 16×16×3 DimArray{Float64,3} │ +├─────────────────────── dims ┤ + ↓ X, → Y, ↗ band +└─────────────────────────────┘ +[:, :, 1] + 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 … 1.0 1.0 1.0 1.0 1.0 1.0 1.0 + 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 + 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 + 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 + 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 + 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 … 1.0 1.0 1.0 1.0 1.0 1.0 1.0 + 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 + 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 + 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 + 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 + 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 … 1.0 1.0 1.0 1.0 1.0 1.0 1.0 + 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 + 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 + 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 + 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 + 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 … 1.0 1.0 1.0 1.0 1.0 1.0 1.0 + +julia> + ``` """ struct DimTable <: AbstractDimTable diff --git a/src/utils.jl b/src/utils.jl index 67090237c..76a62a8c5 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -16,15 +16,18 @@ If no axis reversal is required the same objects will be returned, without alloc ## Example ```jldoctest +using DimensionalData + # Create a DimArray da = DimArray([1 2 3; 4 5 6], (X(10:10:20), Y(300:-100:100))) + # Reverse it rev = reverse(da, dims=Y) + # using `da` in reorder will return it to the original order reorder(rev, da) == da # output - true ``` """ @@ -33,7 +36,7 @@ function reorder end reorder(x, A::Union{AbstractDimArray,AbstractDimStack,AbstractDimIndices}) = reorder(x, dims(A)) reorder(x, ::Nothing) = throw(ArgumentError("object has no dimensions")) reorder(x, p::Pair, ps::Vararg{Pair}) = reorder(x, (p, ps...)) -reorder(x, ps::Tuple{Vararg{Pair}}) = reorder(x, Dimensions.pairdims(ps...)) +reorder(x, ps::Tuple{Vararg{Pair}}) = reorder(x, Dimensions.pairs2dims(ps...)) # Reorder specific dims. reorder(x, dimwrappers::Tuple) = _reorder(x, dimwrappers) # Reorder all dims. diff --git a/test/dimindices.jl b/test/dimindices.jl index 1a720e432..ff675de2c 100644 --- a/test/dimindices.jl +++ b/test/dimindices.jl @@ -143,3 +143,13 @@ end @test vec(ex1) == mapreduce(_ -> mapreduce(i -> map(_ -> A[i], 1:size(ex1, Z)), vcat, 1:prod((size(ex1, X), size(ex1, Y)))), vcat, 1:size(ex1, Ti)) end +@testset "DimSlices" begin + A = DimArray(((1:4) * (1:3)'), (X(4.0:7.0), Y(10.0:12.0)); name=:foo) + axisdims = map(dims(A, (X,))) do d + rebuild(d, axes(lookup(d), 1)) + end + ds = DimensionalData.DimSlices(A; dims=axisdims) + @test ds == ds[X=:] + # Works just like Slices + @test sum(ds) == sum(eachslice(A; dims=X)) +end diff --git a/test/groupby.jl b/test/groupby.jl index 6f6d0dcb2..bc7cb6d40 100644 --- a/test/groupby.jl +++ b/test/groupby.jl @@ -67,6 +67,8 @@ end end end @test all(collect(mean.(gb)) .=== manualmeans) - @test all(mean.(gb) .=== manualmeans) + @test all( + mean.(gb) .=== manualmeans + ) end diff --git a/test/merged.jl b/test/merged.jl index 2b2e4eaa8..2d6a38833 100644 --- a/test/merged.jl +++ b/test/merged.jl @@ -1,6 +1,7 @@ using DimensionalData, Test, Unitful using DimensionalData.LookupArrays, DimensionalData.Dimensions using Statistics: mean +using DimensionalData.Dimensions: SelOrStandard dim = Coord([(1.0,1.0,1.0), (1.0,2.0,2.0), (3.0,4.0,4.0), (1.0,3.0,4.0)], (X(), Y(), Z())) da = DimArray(0.1:0.1:0.4, dim) @@ -31,7 +32,7 @@ end @test bounds(da) == (((1.0, 3.0), (1.0, 4.0), (1.0, 4.0)),) @testset "merged named reduction" begin - m = mean(da2; dims = Coord) + m = mean(da2; dims=Coord) @test size(m) == (1,3) @test length(dims(m, Coord)) == 1 @test dims(m, Coord).val == DimensionalData.NoLookup(Base.OneTo(1)) diff --git a/test/primitives.jl b/test/primitives.jl index fec3b1ac0..b3fe73a3f 100644 --- a/test/primitives.jl +++ b/test/primitives.jl @@ -468,14 +468,4 @@ end @test testdim == reduceddim @test step(testdim) == step(reduceddim) end - -end - -@testset "dimstride" begin - dimz = (X(), Y(), Dim{:test}()) - da = DimArray(ones(3, 2, 3), dimz; name=:data) - @test dimstride(da, X()) == 1 - @test dimstride(da, Y()) == 3 - @test dimstride(da, Dim{:test}()) == 6 - @test_throws ArgumentError dimstride(nothing, X()) end diff --git a/test/tables.jl b/test/tables.jl index d2da48852..1e026c266 100644 --- a/test/tables.jl +++ b/test/tables.jl @@ -1,7 +1,7 @@ using DimensionalData, IteratorInterfaceExtensions, TableTraits, Tables, Test, DataFrames using DimensionalData.LookupArrays, DimensionalData.Dimensions -using DimensionalData: DimTable, DimExtensionArray, dimstride +using DimensionalData: DimTable, DimExtensionArray x = X([:a, :b, :c]) y = Y([10.0, 20.0]) From b2241b82882b0cc73105b422d396feb862366d14 Mon Sep 17 00:00:00 2001 From: Rafael Schouten Date: Thu, 29 Feb 2024 02:14:43 +0100 Subject: [PATCH 052/108] LookupArrays => Lookups (#655) --- docs/make.jl | 4 +- docs/src/api/lookuparrays.md | 100 +++++----- docs/src/dimensions.md | 2 +- docs/src/extending_dd.md | 6 +- docs/src/get_info.md | 16 +- docs/src/object_modification.md | 4 +- docs/src/selectors.md | 8 +- src/DimensionalData.jl | 11 +- src/Dimensions/Dimensions.jl | 12 +- src/Dimensions/coord.jl | 34 ++-- src/Dimensions/dimension.jl | 24 +-- src/Dimensions/format.jl | 10 +- src/Dimensions/indexing.jl | 8 +- src/Dimensions/merged.jl | 24 +-- src/Dimensions/predicates.jl | 10 +- src/Dimensions/primitives.jl | 8 +- src/Dimensions/set.jl | 4 +- src/Dimensions/show.jl | 4 +- src/Dimensions/utils.jl | 8 +- .../LookupArrays.jl => Lookups/Lookups.jl} | 16 +- src/{LookupArrays => Lookups}/indexing.jl | 8 +- .../lookup_arrays.jl | 184 +++++++++--------- .../lookup_traits.jl | 38 ++-- src/{LookupArrays => Lookups}/metadata.jl | 0 src/{LookupArrays => Lookups}/methods.jl | 0 src/{LookupArrays => Lookups}/predicates.jl | 16 +- src/{LookupArrays => Lookups}/selector.jl | 128 ++++++------ src/{LookupArrays => Lookups}/set.jl | 30 +-- src/{LookupArrays => Lookups}/show.jl | 6 +- src/{LookupArrays => Lookups}/utils.jl | 30 +-- src/array/methods.jl | 14 +- src/dimindices.jl | 6 +- src/groupby.jl | 6 +- src/interface.jl | 28 +-- src/plotrecipes.jl | 14 +- src/set.jl | 20 +- src/utils.jl | 4 +- test/adapt.jl | 4 +- test/array.jl | 2 +- test/dimension.jl | 2 +- test/dimindices.jl | 2 +- test/ecosystem.jl | 2 +- test/format.jl | 6 +- test/groupby.jl | 2 +- test/indexing.jl | 2 +- test/lookup.jl | 4 +- test/merged.jl | 2 +- test/metadata.jl | 4 +- test/methods.jl | 2 +- test/predicates.jl | 6 +- test/primitives.jl | 2 +- test/selector.jl | 4 +- test/set.jl | 8 +- test/show.jl | 4 +- test/tables.jl | 2 +- test/utils.jl | 4 +- 56 files changed, 457 insertions(+), 452 deletions(-) rename src/{LookupArrays/LookupArrays.jl => Lookups/Lookups.jl} (85%) rename src/{LookupArrays => Lookups}/indexing.jl (55%) rename src/{LookupArrays => Lookups}/lookup_arrays.jl (81%) rename src/{LookupArrays => Lookups}/lookup_traits.jl (84%) rename src/{LookupArrays => Lookups}/metadata.jl (100%) rename src/{LookupArrays => Lookups}/methods.jl (100%) rename src/{LookupArrays => Lookups}/predicates.jl (76%) rename src/{LookupArrays => Lookups}/selector.jl (88%) rename src/{LookupArrays => Lookups}/set.jl (78%) rename src/{LookupArrays => Lookups}/show.jl (96%) rename src/{LookupArrays => Lookups}/utils.jl (80%) diff --git a/docs/make.jl b/docs/make.jl index c6dcdc239..4f482ffb1 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -2,12 +2,12 @@ using DocumenterVitepress using Documenter using DimensionalData using DimensionalData.Dimensions -using DimensionalData.LookupArrays +using DimensionalData.Lookups # Names are available everywhere so that [`function`](@ref) works. # ==================== -DocMeta.setdocmeta!(DimensionalData, :DocTestSetup, :(using DimensionalData, DimensionalData.Dimensions, DimensionalData.Dimensions.LookupArrays); recursive=true) +DocMeta.setdocmeta!(DimensionalData, :DocTestSetup, :(using DimensionalData, DimensionalData.Dimensions, DimensionalData.Dimensions.Lookups); recursive=true) # Build documentation. # ==================== diff --git a/docs/src/api/lookuparrays.md b/docs/src/api/lookuparrays.md index df4f5e3b1..ea8098380 100644 --- a/docs/src/api/lookuparrays.md +++ b/docs/src/api/lookuparrays.md @@ -1,31 +1,31 @@ -# LookupArrays +# Lookups ```@docs -LookupArrays.LookupArrays +Lookups.Lookups ``` ```@docs -LookupArrays.LookupArray -LookupArrays.Aligned -LookupArrays.AbstractSampled -LookupArrays.Sampled -LookupArrays.AbstractCyclic -LookupArrays.Cyclic -LookupArrays.AbstractCategorical -LookupArrays.Categorical -LookupArrays.Unaligned -LookupArrays.Transformed +Lookups.Lookup +Lookups.Aligned +Lookups.AbstractSampled +Lookups.Sampled +Lookups.AbstractCyclic +Lookups.Cyclic +Lookups.AbstractCategorical +Lookups.Categorical +Lookups.Unaligned +Lookups.Transformed Dimensions.MergedLookup -LookupArrays.NoLookup -LookupArrays.AutoLookup -LookupArrays.AutoIndex +Lookups.NoLookup +Lookups.AutoLookup +Lookups.AutoIndex ``` The generic value getter `val` ```@docs -LookupArrays.val +Lookups.val ``` Lookup methods: @@ -33,20 +33,20 @@ Lookup methods: ```@docs bounds hasselection -LookupArrays.index -LookupArrays.sampling -LookupArrays.span -LookupArrays.order -LookupArrays.locus -LookupArrays.shiftlocus +Lookups.index +Lookups.sampling +Lookups.span +Lookups.order +Lookups.locus +Lookups.shiftlocus ``` ## Selectors ```@docs -LookupArrays.Selector -LookupArrays.IntSelector -LookupArrays.ArraySelector +Lookups.Selector +Lookups.IntSelector +Lookups.ArraySelector At Near Between @@ -56,56 +56,56 @@ Where All ``` -## LookupArray traits +## Lookup traits ```@docs -LookupArrays.LookupArrayTrait +Lookups.LookupTrait ``` ### Order ```@docs -LookupArrays.Order -LookupArrays.Ordered -LookupArrays.ForwardOrdered -LookupArrays.ReverseOrdered -LookupArrays.Unordered -LookupArrays.AutoOrder +Lookups.Order +Lookups.Ordered +Lookups.ForwardOrdered +Lookups.ReverseOrdered +Lookups.Unordered +Lookups.AutoOrder ``` ### Span ```@docs -LookupArrays.Span -LookupArrays.Regular -LookupArrays.Irregular -LookupArrays.Explicit -LookupArrays.AutoSpan +Lookups.Span +Lookups.Regular +Lookups.Irregular +Lookups.Explicit +Lookups.AutoSpan ``` ### Sampling ```@docs -LookupArrays.Sampling -LookupArrays.Points -LookupArrays.Intervals +Lookups.Sampling +Lookups.Points +Lookups.Intervals ``` ### Loci ```@docs -LookupArrays.Locus -LookupArrays.Center -LookupArrays.Start -LookupArrays.End -LookupArrays.AutoLocus +Lookups.Locus +Lookups.Center +Lookups.Start +Lookups.End +Lookups.AutoLocus ``` ## Metadata ```@docs -LookupArrays.AbstractMetadata -LookupArrays.Metadata -LookupArrays.NoMetadata -LookupArrays.units +Lookups.AbstractMetadata +Lookups.Metadata +Lookups.NoMetadata +Lookups.units ``` diff --git a/docs/src/dimensions.md b/docs/src/dimensions.md index b7a93b4c6..6408982cd 100644 --- a/docs/src/dimensions.md +++ b/docs/src/dimensions.md @@ -26,7 +26,7 @@ val(X(1)) DimensionalData.jl uses `Dimensions` everywhere: - `Dimension` are returned from `dims` to specify the names of the dimensions of an object -- they wrap [`LookupArrays`](@ref) to associate the lookups with those names +- they wrap [`Lookups`](@ref) to associate the lookups with those names - to index into these objects, they wrap indices like `Int` or a `Selector` This symmetry means we can ignore how data is organised, diff --git a/docs/src/extending_dd.md b/docs/src/extending_dd.md index 637852291..98514dcbc 100644 --- a/docs/src/extending_dd.md +++ b/docs/src/extending_dd.md @@ -6,7 +6,7 @@ Nearly everything in DimensionalData.jl is designed to be extensible. `YAXArray` are examples from other packages. - `AbstractDimStack` are easily extended to custom mixed array dataset. `RasterStack` or `ArViZ.Dataset` are examples. -- `LookupArray` can have new types added, e.g. to `AbstractSampled` or +- `Lookup` can have new types added, e.g. to `AbstractSampled` or `AbstractCategorical`. `Rasters.Projected` is a lookup that knows its coordinate reference system, but otherwise behaves as a regular `Sampled` lookup. @@ -20,7 +20,7 @@ a `Tuple` of constructed `Dimension`s from `dims(obj)`. ### `Dimension` axes -Dimensions return from `dims` should hold a `LookupArray` or in some cases +Dimensions return from `dims` should hold a `Lookup` or in some cases just an `AbstractArray` (like wiht `DimIndices`). When attached to mullti-dimensional objects, lookups must be the _same length_ as the axis of the array it represents, and `eachindex(A, i)` and `eachindex(dim)` must @@ -83,7 +83,7 @@ format(dims, array) ``` This lets DimensionalData detect the lookup properties, fill in missing fields -of a `LookupArray`, pass keywords from `Dimension` to detected `LookupArray` +of a `Lookup`, pass keywords from `Dimension` to detected `Lookup` constructors, and accept a wider range of dimension inputs like tuples of `Symbol` and `Type`. diff --git a/docs/src/get_info.md b/docs/src/get_info.md index 5a7086c66..bdb67f978 100644 --- a/docs/src/get_info.md +++ b/docs/src/get_info.md @@ -2,13 +2,13 @@ DimensionalData.jl defines consistent methods to retreive information from objects like `DimArray`, `DimStack`, `Tuple`s of `Dimension`, -`Dimension` and `LookupArray`. +`Dimension` and `Lookup`. First we will define an example `DimArray`. ```@example getters using DimensionalData -using DimensionalData.LookupArrays +using DimensionalData.Lookups x, y = X(10:-1:1), Y(100.0:10:200.0) A = rand(x, y) ``` @@ -51,7 +51,7 @@ otherdims(A, isregular) == lookup -Get all the `LookupArray` in an object +Get all the `Lookup` in an object ```@ansi getters lookup(A) @@ -71,7 +71,7 @@ val(At(10.5)) == order -Get the order of a `LookupArray`, or a `Tuple` +Get the order of a `Lookup`, or a `Tuple` from a `DimArray` or `DimTuple`. ```@ansi getters @@ -83,7 +83,7 @@ order(lookup(A, Y)) == sampling -Get the sampling of a `LookupArray`, or a `Tuple` +Get the sampling of a `Lookup`, or a `Tuple` from a `DimArray` or `DimTuple`. ```@ansi getters @@ -95,7 +95,7 @@ sampling(lookup(A, Y)) == span -Get the span of a `LookupArray`, or a `Tuple` +Get the span of a `Lookup`, or a `Tuple` from a `DimArray` or `DimTuple`. ```@ansi getters @@ -107,7 +107,7 @@ span(lookup(A, Y)) == locus -Get the locus of a `LookupArray`, or a `Tuple` +Get the locus of a `Lookup`, or a `Tuple` from a `DimArray` or `DimTuple`. (locus is our term for distiguishing if an lookup value @@ -123,7 +123,7 @@ locus(lookup(A, Y)) == bounds Get the bounds of each dimension. This is different for `Points` -and `Intervals` - the bounds for points of a `LookupArray` are +and `Intervals` - the bounds for points of a `Lookup` are simply `(first(l), last(l))`. ```@ansi getters diff --git a/docs/src/object_modification.md b/docs/src/object_modification.md index 7065a135d..77e55025d 100644 --- a/docs/src/object_modification.md +++ b/docs/src/object_modification.md @@ -52,7 +52,7 @@ imperitive: we tell it how we want the object to be, not what to do. Reorder a specific dimension ````@ansi helpers -using DimensionalData.LookupArrays; +using DimensionalData.Lookups; A = rand(X(1.0:3.0), Y('a':'n')); reorder(A, X => ReverseOrdered()) ```` @@ -119,7 +119,7 @@ Keywords in ( ) will error if used where they are not accepted. | [`AbstractDimStack`](@ref) | data, dims, [refdims], layerdims, [metadata, layermetadata] | as with kw, in order | | [`Dimension`](@ref) | val | val | | [`Selector`](@ref) | val, (atol) | val | -| [`LookupArray`](@ref) | data, (order, span, sampling, metadata) | keywords only | +| [`Lookup`](@ref) | data, (order, span, sampling, metadata) | keywords only | ### `rebuild` magic diff --git a/docs/src/selectors.md b/docs/src/selectors.md index 00995f63c..a5b290b9b 100644 --- a/docs/src/selectors.md +++ b/docs/src/selectors.md @@ -59,7 +59,7 @@ A[X=Near(1.1:0.25:1.5)] First set the `X` axis to be `Intervals`: ````@ansi selectors -using DimensionalData.LookupArrays +using DimensionalData.Lookups A_intervals = set(A, X => Intervals(Start())) intervalbounds(A_intervals, X) ```` @@ -129,14 +129,14 @@ A[X=Not(Near(1.3)), Y=Not(Where(in((:a, :c))))] ## Lookups -Selectors find indices in the `LookupArray` of each dimension. -LookupArrays wrap other `AbstractArray` (often `AbstractRange`) but add +Selectors find indices in the `Lookup` of each dimension. +Lookups wrap other `AbstractArray` (often `AbstractRange`) but add aditional traits to facilitate fast lookups or specifing point or interval behviour. These are usually detected automatically. ````@example selectors -using DimensionalData.LookupArrays +using DimensionalData.Lookups ```` ::: tabs diff --git a/src/DimensionalData.jl b/src/DimensionalData.jl index 8691145cd..fa5c7a231 100644 --- a/src/DimensionalData.jl +++ b/src/DimensionalData.jl @@ -34,16 +34,21 @@ using RecipesBase: @recipe include("Dimensions/Dimensions.jl") using .Dimensions -using .Dimensions.LookupArrays +using .Dimensions.Lookups using .Dimensions: StandardIndices, DimOrDimType, DimTuple, DimTupleOrEmpty, DimType, AllDims -import .LookupArrays: metadata, set, _set, rebuild, basetypeof, +import .Lookups: metadata, set, _set, rebuild, basetypeof, order, span, sampling, locus, val, index, bounds, intervalbounds, hasselection, units, SelectorOrInterval import .Dimensions: dims, refdims, name, lookup, kw2dims, hasdim, label, _astuple import DataAPI.groupby -export LookupArrays, Dimensions +export Lookups, Dimensions + +# Deprecated +const LookupArrays = Lookups +const LookupArray = Lookup +export LookupArrays, LookupArray # Dimension export X, Y, Z, Ti, Dim, Coord diff --git a/src/Dimensions/Dimensions.jl b/src/Dimensions/Dimensions.jl index 7b01cdcc0..80cc5bd87 100644 --- a/src/Dimensions/Dimensions.jl +++ b/src/Dimensions/Dimensions.jl @@ -16,17 +16,17 @@ module Dimensions import Adapt, ConstructionBase, Extents, IntervalSets using Dates -include("../LookupArrays/LookupArrays.jl") +include("../Lookups/Lookups.jl") -using .LookupArrays +using .Lookups -const LA = LookupArrays +const LA = Lookups -import .LookupArrays: rebuild, order, span, sampling, locus, val, index, set, _set, +import .Lookups: rebuild, order, span, sampling, locus, val, index, set, _set, metadata, bounds, intervalbounds, units, basetypeof, unwrap, selectindices, hasselection, shiftlocus, maybeshiftlocus, SelectorOrInterval, Interval -using .LookupArrays: StandardIndices, SelTuple, CategoricalEltypes, - LookupArrayTrait, AllMetadata, LookupArraySetters +using .Lookups: StandardIndices, SelTuple, CategoricalEltypes, + LookupTrait, AllMetadata, LookupSetters using Base: tail, OneTo, @propagate_inbounds diff --git a/src/Dimensions/coord.jl b/src/Dimensions/coord.jl index 03bee3cde..7eb68673b 100644 --- a/src/Dimensions/coord.jl +++ b/src/Dimensions/coord.jl @@ -1,12 +1,12 @@ -struct CoordLookupArray{T,A<:AbstractVector{T},D,Me} <: LookupArray{T,1} +struct CoordLookup{T,A<:AbstractVector{T},D,Me} <: Lookup{T,1} data::A dims::D metadata::Me end -CoordLookupArray(data, dims; metadata=NoMetadata()) = CoordLookupArray(data, dims, metadata) +CoordLookup(data, dims; metadata=NoMetadata()) = CoordLookup(data, dims, metadata) -dims(m::CoordLookupArray) = m.dims -order(m::CoordLookupArray) = Unordered() +dims(m::CoordLookup) = m.dims +order(m::CoordLookup) = Unordered() """ Coord <: Dimension @@ -24,8 +24,8 @@ julia> dim = Coord([(1.0,1.0,1.0), (1.0,2.0,2.0), (3.0,4.0,4.0), (1.0,3.0,4.0)], Coord :: val: Tuple{Float64, Float64, Float64}[(1.0, 1.0, 1.0), (1.0, 2.0, 2.0), (3.0, 4.0, 4.0), (1.0, 3.0, 4.0)] - lookup: CoordLookupArray -Coord{Vector{Tuple{Float64, Float64, Float64}}, DimensionalData.CoordLookupArray{Tuple{X{Colon, AutoLookup{Auto + lookup: CoordLookup +Coord{Vector{Tuple{Float64, Float64, Float64}}, DimensionalData.CoordLookup{Tuple{X{Colon, AutoLookup{Auto Order}, NoMetadata}, Y{Colon, AutoLookup{AutoOrder}, NoMetadata}, Z{Colon, AutoLookup{AutoOrder}, NoMetada ta}}}, NoMetadata} @@ -33,7 +33,7 @@ julia> da = DimArray(0.1:0.1:0.4, dim) 4-element DimArray{Float64,1} with dimensions: Coord (): Tuple{Float64, Float64, Float64}[(1.0, 1.0, 1.0), (1.0, 2.0, 2.0), (3.0, 4.0, 4.0), (1.0, 3.0, 4.0)] - CoordLookupArray + CoordLookup 0.1 0.2 0.3 @@ -41,7 +41,7 @@ julia> da = DimArray(0.1:0.1:0.4, dim) julia> da[Coord(Z(At(1.0)), Y(Between(1, 3)))] 1-element DimArray{Float64,1} with dimensions: - Coord (): Tuple{Float64, Float64, Float64}[(1.0, 1.0, 1.0)] CoordLookupArray + Coord (): Tuple{Float64, Float64, Float64}[(1.0, 1.0, 1.0)] CoordLookup 0.1 julia> da[Coord(4)] == 0.4 @@ -49,7 +49,7 @@ true julia> da[Coord(Between(1, 5), :, At(4.0))] 2-element DimArray{Float64,1} with dimensions: - Coord (): Tuple{Float64, Float64, Float64}[(3.0, 4.0, 4.0), (1.0, 3.0, 4.0)] CoordLookupArray + Coord (): Tuple{Float64, Float64, Float64}[(3.0, 4.0, 4.0), (1.0, 3.0, 4.0)] CoordLookup 0.3 0.4 ``` @@ -59,7 +59,7 @@ struct Coord{T} <: Dimension{T} end function Coord(val::T, dims::Tuple) where {T<:AbstractVector} length(dims) == length(first(val)) || throw(ArgumentError("Number of dims must match number of points")) - lookup = CoordLookupArray(val, key2dim(dims)) + lookup = CoordLookup(val, key2dim(dims)) Coord(lookup) end const SelOrStandard = Union{Selector,StandardIndices} @@ -72,7 +72,7 @@ dims(d::Coord) = dims(val(d)) _matches(sel::Dimension, x) = _matches(val(sel), x) _matches(sel::Between, x) = (x >= first(sel)) & (x < last(sel)) -_matches(sel::LookupArrays.AbstractInterval, x) = x in sel +_matches(sel::Lookups.AbstractInterval, x) = x in sel _matches(sel::At, x) = x == val(sel) _matches(sel::Colon, x) = true _matches(sel::Nothing, x) = true @@ -83,15 +83,15 @@ function _format(dim::Coord, axis::AbstractRange) end -LookupArrays.bounds(d::Coord) = ntuple(i -> extrema((x[i] for x in val(d))), length(first(d))) +Lookups.bounds(d::Coord) = ntuple(i -> extrema((x[i] for x in val(d))), length(first(d))) # Return a Vector{Bool} for matching coordinates -LookupArrays.selectindices(lookup::CoordLookupArray, sel::DimTuple) = selectindices(lookup, sortdims(sel, dims(lookup))) -LookupArrays.selectindices(lookup::CoordLookupArray, sel::Tuple) = [all(map(_matches, sel, x)) for x in lookup] -LookupArrays.selectindices(lookup::CoordLookupArray, sel::StandardIndices) = sel +Lookups.selectindices(lookup::CoordLookup, sel::DimTuple) = selectindices(lookup, sortdims(sel, dims(lookup))) +Lookups.selectindices(lookup::CoordLookup, sel::Tuple) = [all(map(_matches, sel, x)) for x in lookup] +Lookups.selectindices(lookup::CoordLookup, sel::StandardIndices) = sel -@inline LookupArrays.reducelookup(l::CoordLookupArray) = NoLookup(OneTo(1)) +@inline Lookups.reducelookup(l::CoordLookup) = NoLookup(OneTo(1)) _tozero(xs) = map(x -> zero(x), xs) -@inline _reducedims(lookup::CoordLookupArray, dim::Dimension) = +@inline _reducedims(lookup::CoordLookup, dim::Dimension) = rebuild(dim, [_tozero(dim.val[1])], dim.lookup) diff --git a/src/Dimensions/dimension.jl b/src/Dimensions/dimension.jl index 761e92fbc..f8bcce963 100644 --- a/src/Dimensions/dimension.jl +++ b/src/Dimensions/dimension.jl @@ -191,23 +191,23 @@ name(dim::Val{D}) where D = name(D) label(x) = string(string(name(x)), (units(x) === nothing ? "" : string(" ", units(x)))) -# LookupArrays methods -LookupArrays.metadata(dim::Dimension) = metadata(lookup(dim)) +# Lookups methods +Lookups.metadata(dim::Dimension) = metadata(lookup(dim)) -LookupArrays.index(dim::Dimension{<:AbstractArray}) = index(val(dim)) -LookupArrays.index(dim::Dimension{<:Val}) = unwrap(index(val(dim))) +Lookups.index(dim::Dimension{<:AbstractArray}) = index(val(dim)) +Lookups.index(dim::Dimension{<:Val}) = unwrap(index(val(dim))) -LookupArrays.bounds(dim::Dimension) = bounds(val(dim)) -LookupArrays.intervalbounds(dim::Dimension, args...) = intervalbounds(val(dim), args...) +Lookups.bounds(dim::Dimension) = bounds(val(dim)) +Lookups.intervalbounds(dim::Dimension, args...) = intervalbounds(val(dim), args...) for f in (:shiftlocus, :maybeshiftlocus) - @eval function LookupArrays.$f(locus::Locus, x; dims=Dimensions.dims(x)) + @eval function Lookups.$f(locus::Locus, x; dims=Dimensions.dims(x)) newdims = map(Dimensions.dims(x, dims)) do d - LookupArrays.$f(locus, d) + Lookups.$f(locus, d) end return setdims(x, newdims) end - @eval LookupArrays.$f(locus::Locus, d::Dimension) = - rebuild(d, LookupArrays.$f(locus, lookup(d))) + @eval Lookups.$f(locus::Locus, d::Dimension) = + rebuild(d, Lookups.$f(locus, lookup(d))) end function hasselection(x, selectors::Union{DimTuple,SelTuple,Selector,Dimension}) @@ -299,7 +299,7 @@ dims(extent::Extents.Extent, ds) = dims(dims(extent), ds) # Produce a 2 * length(dim) matrix of interval bounds from a dim dim2boundsmatrix(dim::Dimension) = dim2boundsmatrix(lookup(dim)) -function dim2boundsmatrix(lookup::LookupArray) +function dim2boundsmatrix(lookup::Lookup) samp = sampling(lookup) samp isa Intervals || error("Cannot create a bounds matrix for $(nameof(typeof(samp)))") _dim2boundsmatrix(locus(lookup), span(lookup), lookup) @@ -309,7 +309,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 = LookupArrays._shiftindexlocus(Start(), lookup) + starts = Lookups._shiftindexlocus(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 11ebea1a6..ef783147e 100644 --- a/src/Dimensions/format.jl +++ b/src/Dimensions/format.jl @@ -10,7 +10,7 @@ Format the passed-in dimension(s) `dims` to match the object `x`. 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 [`LookupArray`](@ref) hasn't been specified, a lookup is chosen +If a [`Lookup`](@ref) hasn't been specified, a lookup is chosen based on the type and element type of the index. """ format(dims, A::AbstractArray) = format((dims,), A) @@ -43,11 +43,11 @@ function _format(dim::Dimension, axis::AbstractRange) end format(val::AbstractArray, D::Type, axis::AbstractRange) = format(AutoLookup(), D, val, axis) -format(m::LookupArray, D::Type, axis::AbstractRange) = format(m, D, parent(m), axis) +format(m::Lookup, D::Type, axis::AbstractRange) = format(m, D, parent(m), axis) format(v::AutoVal, D::Type, axis::AbstractRange) = _valformaterror(val(v), D) format(v, D::Type, axis::AbstractRange) = _valformaterror(v, D) -# Format LookupArrays +# 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) @@ -89,7 +89,7 @@ _format(index::AbstractArray, axis::AbstractRange) = index _format(index::AutoLookup, axis::AbstractRange) = axis # Order _format(order::Order, D::Type, index) = order -_format(order::AutoOrder, D::Type, index) = LookupArrays.orderof(index) +_format(order::AutoOrder, D::Type, index) = Lookups.orderof(index) # Span _format(span::AutoSpan, D::Type, index::Union{AbstractArray,Val}) = _format(Irregular(), D, index) @@ -147,5 +147,5 @@ checkaxis(lookup, axis) = first(axes(lookup)) == axis || _checkaxiserror(lookup, )) @noinline _valformaterror(v, D::Type) = throw(ArgumentError( - "Lookup value of `$v` for dimension $D cannot be converted to a `LookupArray`. Did you mean to pass a range or array?" + "Lookup value of `$v` for dimension $D cannot be converted to a `Lookup`. Did you mean to pass a range or array?" )) diff --git a/src/Dimensions/indexing.jl b/src/Dimensions/indexing.jl index 617ace078..6827e5885 100644 --- a/src/Dimensions/indexing.jl +++ b/src/Dimensions/indexing.jl @@ -42,7 +42,7 @@ Convert a `Dimension` or `Selector` `I` to indices of `Int`, `AbstractArray` or @inline dims2indices(dims::DimTuple, I::Tuple{<:CartesianIndex}) = I @inline dims2indices(dims::DimTuple, sel::Tuple) = - LookupArrays.selectindices(lookup(dims), sel) + Lookups.selectindices(lookup(dims), sel) @inline dims2indices(dims::DimTuple, ::Tuple{}) = () # Otherwise attempt to convert dims to indices @inline function dims2indices(dims::DimTuple, I::DimTuple) @@ -96,7 +96,7 @@ end end end @inline function unalligned_dims2indices(dims::DimTuple, sel::Tuple{Selector,Vararg{Selector}}) - LookupArrays.select_unalligned_indices(lookup(dims), sel) + Lookups.select_unalligned_indices(lookup(dims), sel) end _unalligned_all_selector_error(dims) = @@ -112,11 +112,11 @@ _unwrapdim(x) = x @inline _dims2indices(dim::Dimension, ::Nothing) = Colon() # Simply unwrap dimensions @inline _dims2indices(dim::Dimension, seldim::Dimension) = - LookupArrays.selectindices(val(dim), val(seldim)) + Lookups.selectindices(val(dim), val(seldim)) function _extent_as_intervals(extent::Extents.Extent{Keys}) where Keys map(map(key2dim, Keys), values(extent)) do k, v - rebuild(k, LookupArrays.Interval(v...)) + rebuild(k, Lookups.Interval(v...)) end end diff --git a/src/Dimensions/merged.jl b/src/Dimensions/merged.jl index 2441e354a..bcfc47888 100644 --- a/src/Dimensions/merged.jl +++ b/src/Dimensions/merged.jl @@ -1,9 +1,9 @@ """ - MergedLookup <: LookupArray + MergedLookup <: Lookup MergedLookup(data, dims; [metadata]) -A [`LookupArray`](@ref) that holds multiple combined dimensions. +A [`Lookup`](@ref) that holds multiple combined dimensions. `MergedLookup` can be indexed with [`Selector`](@ref)s like `At`, `Between`, and `Where` although `Near` has undefined meaning. @@ -19,7 +19,7 @@ A [`LookupArray`](@ref) that holds multiple combined dimensions. - `metadata`: a `Dict` or `Metadata` object to attach dimension metadata. """ -struct MergedLookup{T,A<:AbstractVector{T},D,Me} <: LookupArray{T,1} +struct MergedLookup{T,A<:AbstractVector{T},D,Me} <: Lookup{T,1} data::A dims::D metadata::Me @@ -30,20 +30,20 @@ order(m::MergedLookup) = Unordered() dims(m::MergedLookup) = m.dims dims(d::Dimension{<:MergedLookup}) = dims(val(d)) -# LookupArray interface methods +# Lookup interface methods -LookupArrays.bounds(d::Dimension{<:MergedLookup}) = +Lookups.bounds(d::Dimension{<:MergedLookup}) = ntuple(i -> extrema((x[i] for x in val(d))), length(first(d))) # Return an `Int` or Vector{Bool} -LookupArrays.selectindices(lookup::MergedLookup, sel::DimTuple) = +Lookups.selectindices(lookup::MergedLookup, sel::DimTuple) = selectindices(lookup, map(_val_or_nothing, sortdims(sel, dims(lookup)))) -function LookupArrays.selectindices(lookup::MergedLookup, sel::NamedTuple{K}) where K +function Lookups.selectindices(lookup::MergedLookup, sel::NamedTuple{K}) where K dimsel = map(rebuild, map(key2dim, K), values(sel)) selectindices(lookup, dimsel) end -LookupArrays.selectindices(lookup::MergedLookup, sel::StandardIndices) = sel -function LookupArrays.selectindices(lookup::MergedLookup, sel::Tuple) +Lookups.selectindices(lookup::MergedLookup, sel::StandardIndices) = sel +function Lookups.selectindices(lookup::MergedLookup, sel::Tuple) if (length(sel) == length(dims(lookup))) && all(map(s -> s isa At, sel)) i = findfirst(x -> all(map(_matches, sel, x)), lookup) isnothing(i) && _coord_not_found_error(sel) @@ -53,9 +53,9 @@ function LookupArrays.selectindices(lookup::MergedLookup, sel::Tuple) end end -@inline LookupArrays.reducelookup(l::MergedLookup) = NoLookup(OneTo(1)) +@inline Lookups.reducelookup(l::MergedLookup) = NoLookup(OneTo(1)) -function LookupArrays.show_properties(io::IO, mime, lookup::MergedLookup) +function Lookups.show_properties(io::IO, mime, lookup::MergedLookup) print(io, " ") show(IOContext(io, :inset => "", :dimcolor => 244), mime, basedims(lookup)) end @@ -82,7 +82,7 @@ _matches(sel::Tuple, x) = all(map(_matches, sel, x)) _matches(sel::Dimension, x) = _matches(val(sel), x) _matches(sel::Between, x) = _matches(Interval(val(sel)...), x) _matches(sel::Touches, x) = (x >= first(sel)) & (x <= last(sel)) -_matches(sel::LookupArrays.AbstractInterval, x) = x in sel +_matches(sel::Lookups.AbstractInterval, x) = x in sel _matches(sel::At, x) = x == val(sel) _matches(sel::Colon, x) = true _matches(sel::Nothing, x) = true diff --git a/src/Dimensions/predicates.jl b/src/Dimensions/predicates.jl index a9bab9d94..b137888a0 100644 --- a/src/Dimensions/predicates.jl +++ b/src/Dimensions/predicates.jl @@ -14,10 +14,10 @@ for f in ( :isreverse, ) @eval begin - LookupArrays.$f(x::Dimension) = $f(val(x)) - LookupArrays.$f(::Nothing) = false - LookupArrays.$f(xs::DimTuple) = all(map($f, xs)) - LookupArrays.$f(x::Any) = $f(dims(x)) - LookupArrays.$f(x::Any, ds) = $f(dims(x, ds)) + Lookups.$f(x::Dimension) = $f(val(x)) + Lookups.$f(::Nothing) = false + Lookups.$f(xs::DimTuple) = all(map($f, xs)) + Lookups.$f(x::Any) = $f(dims(x)) + Lookups.$f(x::Any, ds) = $f(dims(x, ds)) end end diff --git a/src/Dimensions/primitives.jl b/src/Dimensions/primitives.jl index f27213bdc..d095b9011 100644 --- a/src/Dimensions/primitives.jl +++ b/src/Dimensions/primitives.jl @@ -123,7 +123,7 @@ end Get the dimension(s) matching the type(s) of the query dimension. -LookupArray can be an Int or an Dimension, or a tuple containing +Lookup can be an Int or an Dimension, or a tuple containing any combination of either. ## Arguments @@ -325,7 +325,7 @@ and returns a new object or tuple with the dimension updated. # Example ```jldoctest -using DimensionalData, DimensionalData.Dimensions, DimensionalData.LookupArrays +using DimensionalData, DimensionalData.Dimensions, DimensionalData.Lookups A = ones(X(10), Y(10:10:100)) B = setdims(A, Y(Categorical('a':'j'; order=ForwardOrdered()))) lookup(B, Y) @@ -483,7 +483,7 @@ This is usually to match a new array size where an axis has been reduced with a method like `mean` or `reduce` to a length of 1, but the number of dimensions has not changed. -`LookupArray` traits are also updated to correspond to the change in +`Lookup` traits are also updated to correspond to the change in cell step, sampling type and order. """ function reducedims end @@ -604,7 +604,7 @@ end isnothing(warn) || _valwarn(a, b, warn) return false end - if order && LookupArrays.order(a) != LookupArrays.order(b) + if order && Lookups.order(a) != Lookups.order(b) isnothing(warn) || _orderwarn(a, b, warn) return false end diff --git a/src/Dimensions/set.jl b/src/Dimensions/set.jl index 1b74ccc71..47331a23e 100644 --- a/src/Dimensions/set.jl +++ b/src/Dimensions/set.jl @@ -1,4 +1,4 @@ -const DimSetters = Union{LookupArraySetters,Type,UnionAll,Dimension,Symbol} +const DimSetters = Union{LookupSetters,Type,UnionAll,Dimension,Symbol} set(dim::Dimension, x::DimSetters) = _set(dim, x) set(dims_::DimTuple, args::Union{Dimension,DimTuple,Pair}...; kw...) = @@ -33,7 +33,7 @@ _set(dim::Dimension, dt::DimType) = basetypeof(dt)(val(dim)) _set(dim::Dimension, x) = rebuild(dim; val=_set(val(dim), x)) # Set the lookup # Otherwise pass this on to set fields on the lookup -_set(dim::Dimension, x::LookupArrayTrait) = rebuild(dim, _set(lookup(dim), x)) +_set(dim::Dimension, x::LookupTrait) = rebuild(dim, _set(lookup(dim), x)) # Metadata _set(dim::Dimension, newmetadata::AllMetadata) = rebuild(dim, _set(lookup(dim), newmetadata)) diff --git a/src/Dimensions/show.jl b/src/Dimensions/show.jl index 34f7622de..8c2876f3b 100644 --- a/src/Dimensions/show.jl +++ b/src/Dimensions/show.jl @@ -114,10 +114,10 @@ function print_dimval(io, mime, val, nchars=0) printstyled(io, val; color=get(io, :dimcolor, 1)) end function print_dimval(io, mime, lookup::AbstractArray, nchars=0) - LookupArrays.print_index(io, mime, lookup, nchars) + Lookups.print_index(io, mime, lookup, nchars) end print_dimval(io, mime, lookup::Union{AutoLookup,NoLookup}, nchars=0) = print(io, "") -function print_dimval(io, mime, lookup::LookupArray, nchars=0) +function print_dimval(io, mime, lookup::Lookup, nchars=0) print(io, " ") ctx = IOContext(io, :nchars=>nchars) show(ctx, mime, lookup) diff --git a/src/Dimensions/utils.jl b/src/Dimensions/utils.jl index b9fed25ef..ec70d752d 100644 --- a/src/Dimensions/utils.jl +++ b/src/Dimensions/utils.jl @@ -1,13 +1,13 @@ for f in (:shiftlocus, :maybeshiftlocus) @eval begin - function LookupArrays.$f(locus::Locus, x; dims=Dimensions.dims(x)) + function Lookups.$f(locus::Locus, x; dims=Dimensions.dims(x)) newdims = map(Dimensions.dims(x, dims)) do d - LookupArrays.$f(locus, d) + Lookups.$f(locus, d) end return setdims(x, newdims) end - function LookupArrays.$f(locus::Locus, d::Dimension) - rebuild(d, LookupArrays.$f(locus, lookup(d))) + function Lookups.$f(locus::Locus, d::Dimension) + rebuild(d, Lookups.$f(locus, lookup(d))) end end end diff --git a/src/LookupArrays/LookupArrays.jl b/src/Lookups/Lookups.jl similarity index 85% rename from src/LookupArrays/LookupArrays.jl rename to src/Lookups/Lookups.jl index a5712a7ca..c13768ee1 100644 --- a/src/LookupArrays/LookupArrays.jl +++ b/src/Lookups/Lookups.jl @@ -1,22 +1,22 @@ """ - LookupArrays + Lookups -Module for [`LookupArrays`](@ref) and [`Selector`](@ref)s used in DimensionalData.jl +Module for [`Lookup`](@ref)s and [`Selector`](@ref)s used in DimensionalData.jl -`LookupArrays` defines traits and `AbstractArray` wrappers +`Lookup` defines traits and `AbstractArray` wrappers that give specific behaviours for a lookup index when indexed with [`Selector`](@ref). For example, these allow tracking over array order so fast indexing works evne when the array is reversed. -To load LookupArrays types and methods into scope: +To load `Lookup` types and methods into scope: ```julia using DimensionalData -using DimensionalData.LookupArrays +using DimensionalData.Lookups ``` """ -module LookupArrays +module Lookups using Dates, IntervalSets, Extents import Adapt, ConstructionBase @@ -37,7 +37,7 @@ export At, Between, Touches, Contains, Near, Where, All export .. export Not -export LookupArrayTrait +export LookupTrait export Order, Ordered, ForwardOrdered, ReverseOrdered, Unordered, AutoOrder export Sampling, Points, Intervals, AutoSampling, NoSampling export Span, Regular, Irregular, Explicit, AutoSpan, NoSpan @@ -45,7 +45,7 @@ export Locus, Center, Start, End, AutoLocus export Metadata, NoMetadata export AutoStep, AutoBounds, AutoIndex -export LookupArray +export Lookup export AutoLookup, NoLookup export Aligned, AbstractSampled, Sampled, AbstractCyclic, Cyclic, AbstractCategorical, Categorical export Unaligned, Transformed diff --git a/src/LookupArrays/indexing.jl b/src/Lookups/indexing.jl similarity index 55% rename from src/LookupArrays/indexing.jl rename to src/Lookups/indexing.jl index f230f0281..01c50ac10 100644 --- a/src/LookupArrays/indexing.jl +++ b/src/Lookups/indexing.jl @@ -2,14 +2,14 @@ for f in (:getindex, :view, :dotview) @eval begin # Int and CartesianIndex forward to the parent - @propagate_inbounds Base.$f(l::LookupArray, i::Union{Int,CartesianIndex}) = + @propagate_inbounds Base.$f(l::Lookup, i::Union{Int,CartesianIndex}) = Base.$f(parent(l), i) # AbstractArray, Colon and CartesianIndices: the lookup is rebuilt around a new parent - @propagate_inbounds Base.$f(l::LookupArray, i::Union{AbstractArray,Colon}) = + @propagate_inbounds Base.$f(l::Lookup, i::Union{AbstractArray,Colon}) = rebuild(l; data=Base.$f(parent(l), i)) # Selector gets processed with `selectindices` - @propagate_inbounds Base.$f(l::LookupArray, i::SelectorOrInterval) = Base.$f(l, selectindices(l, i)) + @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::LookupArray, i) = Base.$f(parent(l), i) + @propagate_inbounds Base.$f(l::Lookup, i) = Base.$f(parent(l), i) end end diff --git a/src/LookupArrays/lookup_arrays.jl b/src/Lookups/lookup_arrays.jl similarity index 81% rename from src/LookupArrays/lookup_arrays.jl rename to src/Lookups/lookup_arrays.jl index a3cbb574a..90f8f717f 100644 --- a/src/LookupArrays/lookup_arrays.jl +++ b/src/Lookups/lookup_arrays.jl @@ -1,74 +1,74 @@ """ - LookupArray + Lookup Types defining the behaviour of a lookup index, how it is plotted and how [`Selector`](@ref)s like [`Between`](@ref) work. -A `LookupArray` may be [`NoLookup`](@ref) indicating that the index is just the +A `Lookup` may be [`NoLookup`](@ref) indicating that the index is just the underlying array axis, [`Categorical`](@ref) for ordered or unordered categories, or a [`Sampled`](@ref) index for [`Points`](@ref) or [`Intervals`](@ref). """ -abstract type LookupArray{T,N} <: AbstractArray{T,N} end +abstract type Lookup{T,N} <: AbstractArray{T,N} end -const LookupArrayTuple = Tuple{LookupArray,Vararg{LookupArray}} +const LookupTuple = Tuple{Lookup,Vararg{Lookup}} -span(lookup::LookupArray) = NoSpan() -sampling(lookup::LookupArray) = NoSampling() +span(lookup::Lookup) = NoSpan() +sampling(lookup::Lookup) = NoSampling() -dims(::LookupArray) = nothing -val(l::LookupArray) = parent(l) -index(l::LookupArray) = parent(l) -locus(l::LookupArray) = Center() +dims(::Lookup) = nothing +val(l::Lookup) = parent(l) +index(l::Lookup) = parent(l) +locus(l::Lookup) = Center() -Base.eltype(l::LookupArray{T}) where T = T -Base.parent(l::LookupArray) = l.data -Base.size(l::LookupArray) = size(parent(l)) -Base.axes(l::LookupArray) = axes(parent(l)) -Base.first(l::LookupArray) = first(parent(l)) -Base.last(l::LookupArray) = last(parent(l)) -Base.firstindex(l::LookupArray) = firstindex(parent(l)) -Base.lastindex(l::LookupArray) = lastindex(parent(l)) -function Base.:(==)(l1::LookupArray, l2::LookupArray) +Base.eltype(l::Lookup{T}) where T = T +Base.parent(l::Lookup) = l.data +Base.size(l::Lookup) = size(parent(l)) +Base.axes(l::Lookup) = axes(parent(l)) +Base.first(l::Lookup) = first(parent(l)) +Base.last(l::Lookup) = last(parent(l)) +Base.firstindex(l::Lookup) = firstindex(parent(l)) +Base.lastindex(l::Lookup) = lastindex(parent(l)) +function Base.:(==)(l1::Lookup, l2::Lookup) basetypeof(l1) == basetypeof(l2) && parent(l1) == parent(l2) end -ordered_first(l::LookupArray) = l[ordered_firstindex(l)] -ordered_last(l::LookupArray) = l[ordered_lastindex(l)] +ordered_first(l::Lookup) = l[ordered_firstindex(l)] +ordered_last(l::Lookup) = l[ordered_lastindex(l)] -ordered_firstindex(l::LookupArray) = ordered_firstindex(order(l), l) -ordered_lastindex(l::LookupArray) = ordered_lastindex(order(l), l) -ordered_firstindex(o::ForwardOrdered, l::LookupArray) = firstindex(parent(l)) -ordered_firstindex(o::ReverseOrdered, l::LookupArray) = lastindex(parent(l)) -ordered_firstindex(o::Unordered, l::LookupArray) = firstindex(parent(l)) -ordered_lastindex(o::ForwardOrdered, l::LookupArray) = lastindex(parent(l)) -ordered_lastindex(o::ReverseOrdered, l::LookupArray) = firstindex(parent(l)) -ordered_lastindex(o::Unordered, l::LookupArray) = lastindex(parent(l)) +ordered_firstindex(l::Lookup) = ordered_firstindex(order(l), l) +ordered_lastindex(l::Lookup) = ordered_lastindex(order(l), l) +ordered_firstindex(o::ForwardOrdered, l::Lookup) = firstindex(parent(l)) +ordered_firstindex(o::ReverseOrdered, l::Lookup) = lastindex(parent(l)) +ordered_firstindex(o::Unordered, l::Lookup) = firstindex(parent(l)) +ordered_lastindex(o::ForwardOrdered, l::Lookup) = lastindex(parent(l)) +ordered_lastindex(o::ReverseOrdered, l::Lookup) = firstindex(parent(l)) +ordered_lastindex(o::Unordered, l::Lookup) = lastindex(parent(l)) -function Base.searchsortedfirst(lookup::LookupArray, val; lt=<, kw...) +function Base.searchsortedfirst(lookup::Lookup, val; lt=<, kw...) searchsortedfirst(parent(lookup), unwrap(val); order=ordering(order(lookup)), lt=lt, kw...) end -function Base.searchsortedlast(lookup::LookupArray, val; lt=<, kw...) +function Base.searchsortedlast(lookup::Lookup, val; lt=<, kw...) searchsortedlast(parent(lookup), unwrap(val); order=ordering(order(lookup)), lt=lt, kw...) end -function Adapt.adapt_structure(to, l::LookupArray) +function Adapt.adapt_structure(to, l::Lookup) rebuild(l; data=Adapt.adapt(to, parent(l))) end """ - AutoLookup <: LookupArray + AutoLookup <: Lookup AutoLookup() AutoLookup(index=AutoIndex(); kw...) -Automatic [`LookupArray`](@ref), the default lookup. It will be converted automatically -to another [`LookupArray`](@ref) when it is possible to detect it from the index. +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. -Keywords will be used in the detected `LookupArray` constructor. +Keywords will be used in the detected `Lookup` constructor. """ -struct AutoLookup{T,A<:AbstractVector{T},K} <: LookupArray{T,1} +struct AutoLookup{T,A<:AbstractVector{T},K} <: Lookup{T,1} data::A kw::K end @@ -81,33 +81,33 @@ metadata(lookup::AutoLookup) = hasproperty(lookup.kw, :metadata) ? lookup.kw.met Base.step(lookup::AutoLookup) = Base.step(parent(lookup)) -bounds(lookup::LookupArray) = _bounds(order(lookup), lookup) +bounds(lookup::Lookup) = _bounds(order(lookup), lookup) -_bounds(::ForwardOrdered, l::LookupArray) = first(l), last(l) -_bounds(::ReverseOrdered, l::LookupArray) = last(l), first(l) -_bounds(::Unordered, l::LookupArray) = (nothing, nothing) +_bounds(::ForwardOrdered, l::Lookup) = first(l), last(l) +_bounds(::ReverseOrdered, l::Lookup) = last(l), first(l) +_bounds(::Unordered, l::Lookup) = (nothing, nothing) -@noinline Base.step(lookup::T) where T <: LookupArray = +@noinline Base.step(lookup::T) where T <: Lookup = error("No step provided by $T. Use a `Sampled` with `Regular`") """ - Aligned <: LookupArray + Aligned <: Lookup -Abstract supertype for [`LookupArray`](@ref)s +Abstract supertype for [`Lookup`](@ref)s where the index is aligned with the array axes. -This is by far the most common supertype for `LookupArray`. +This is by far the most common supertype for `Lookup`. """ -abstract type Aligned{T,O} <: LookupArray{T,1} end +abstract type Aligned{T,O} <: Lookup{T,1} end order(lookup::Aligned) = lookup.order """ - NoLookup <: LookupArray + NoLookup <: Lookup NoLookup() -A [`LookupArray`](@ref) that is identical to the array axis. +A [`Lookup`](@ref) that is identical to the array axis. [`Selector`](@ref)s can't be used on this lookup. ## Example @@ -129,7 +129,7 @@ NoLookup, NoLookup Which is identical to: ```jldoctest NoLookup -using .LookupArrays +using .Lookups A = DimArray(rand(3, 3), (X(NoLookup()), Y(NoLookup()))) Dimensions.lookup(A) @@ -153,7 +153,7 @@ Base.step(lookup::NoLookup) = 1 """ AbstractSampled <: Aligned -Abstract supertype for [`LookupArray`](@ref)s where the index is +Abstract supertype for [`Lookup`](@ref)s where the index is aligned with the array, and is independent of other dimensions. [`Sampled`](@ref) is provided by this package. @@ -241,7 +241,7 @@ const SAMPLED_ARGUMENTS_DOC = """ Sampled(data::AbstractVector, order::Order, span::Span, sampling::Sampling, metadata) Sampled(data=AutoIndex(); order=AutoOrder(), span=AutoSpan(), sampling=Points(), metadata=NoMetadata()) -A concrete implementation of the [`LookupArray`](@ref) +A concrete implementation of the [`Lookup`](@ref) [`AbstractSampled`](@ref). It can be used to represent [`Points`](@ref) or [`Intervals`](@ref). @@ -264,7 +264,7 @@ 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. ```jldoctest Sampled -using DimensionalData, DimensionalData.LookupArrays +using DimensionalData, DimensionalData.Lookups x = X(Sampled(100:-20:10; sampling=Intervals(Start()))) y = Y(Sampled([1, 4, 7, 10]; span=Regular(3), sampling=Intervals(Start()))) @@ -435,7 +435,7 @@ end """ AbstractCategorical <: Aligned -[`LookupArray`](@ref)s where the values are categories. +[`Lookup`](@ref)s where the values are categories. [`Categorical`](@ref) is the provided concrete implementation. but this can easily be extended - all methods are defined for `AbstractCategorical`. @@ -461,7 +461,7 @@ end Categorical(o::Order) Categorical(; order=Unordered()) -An LookupArray where the values are categories. +An Lookup where the values are categories. This will be automatically assigned if the index contains `AbstractString`, `Symbol` or `Char`. Otherwise it can be assigned manually. @@ -518,21 +518,21 @@ end """ - Unaligned <: LookupArray + Unaligned <: Lookup -Abstract supertype for [`LookupArray`](@ref) where the index is not aligned to the grid. +Abstract supertype for [`Lookup`](@ref) where the index is not aligned to the grid. Indexing an [`Unaligned`](@ref) with [`Selector`](@ref)s must provide all other [`Unaligned`](@ref) dimensions. """ -abstract type Unaligned{T,N} <: LookupArray{T,N} end +abstract type Unaligned{T,N} <: Lookup{T,N} end """ Transformed <: Unaligned Transformed(f, dim::Dimension; metadata=NoMetadata()) -[`LookupArray`](@ref) that uses an affine transformation to convert +[`Lookup`](@ref) that uses an affine transformation to convert dimensions from `dims(lookup)` to `dims(array)`. This can be useful when the dimensions are e.g. rotated from a more commonly used axis. @@ -551,7 +551,7 @@ from CoordinateTransformations.jl may be useful. ## Example ```jldoctest -using DimensionalData, DimensionalData.LookupArrays, CoordinateTransformations +using DimensionalData, DimensionalData.Lookups, CoordinateTransformations m = LinearMap([0.5 0.0; 0.0 0.5]) A = [1 2 3 4 @@ -595,34 +595,34 @@ Base.:(==)(l1::Transformed, l2::Transformed) = typeof(l1) == typeof(l2) && f(l1) # Shared methods -intervalbounds(l::LookupArray, args...) = _intervalbounds_no_interval_error() +intervalbounds(l::Lookup, args...) = _intervalbounds_no_interval_error() intervalbounds(l::AbstractSampled, args...) = intervalbounds(span(l), sampling(l), l, args...) -intervalbounds(span::Span, ::Points, ls::LookupArray) = map(l -> (l, l), ls) -intervalbounds(span::Span, ::Points, ls::LookupArray, i::Int) = ls[i], ls[i] -intervalbounds(span::Span, sampling::Intervals, l::LookupArray, i::Int) = +intervalbounds(span::Span, ::Points, ls::Lookup) = map(l -> (l, l), ls) +intervalbounds(span::Span, ::Points, ls::Lookup, i::Int) = ls[i], ls[i] +intervalbounds(span::Span, sampling::Intervals, l::Lookup, i::Int) = intervalbounds(order(l), locus(sampling), span, l, i) -function intervalbounds(order::ForwardOrdered, locus::Start, span::Span, l::LookupArray, i::Int) +function intervalbounds(order::ForwardOrdered, locus::Start, span::Span, l::Lookup, i::Int) if i == lastindex(l) (l[i], bounds(l)[2]) else (l[i], l[i+1]) end end -function intervalbounds(order::ForwardOrdered, locus::End, span::Span, l::LookupArray, i::Int) +function intervalbounds(order::ForwardOrdered, locus::End, span::Span, l::Lookup, i::Int) if i == firstindex(l) (bounds(l)[1], l[i]) else (l[i-1], l[i]) end end -function intervalbounds(order::ReverseOrdered, locus::Start, span::Span, l::LookupArray, i::Int) +function intervalbounds(order::ReverseOrdered, locus::Start, span::Span, l::Lookup, i::Int) if i == firstindex(l) (l[i], bounds(l)[2]) else (l[i], l[i-1]) end end -function intervalbounds(order::ReverseOrdered, locus::End, span::Span, l::LookupArray, i::Int) +function intervalbounds(order::ReverseOrdered, locus::End, span::Span, l::Lookup, i::Int) if i == lastindex(l) (bounds(l)[1], l[i]) else @@ -630,36 +630,36 @@ function intervalbounds(order::ReverseOrdered, locus::End, span::Span, l::Lookup end end # Regular Center -function intervalbounds(order::Ordered, locus::Center, span::Regular, l::LookupArray, i::Int) +function intervalbounds(order::Ordered, locus::Center, span::Regular, l::Lookup, i::Int) halfstep = step(span) / 2 x = l[i] bounds = (x - halfstep, x + halfstep) return _maybeflipbounds(order, bounds) end # Irregular Center -function intervalbounds(order::ForwardOrdered, locus::Center, span::Irregular, l::LookupArray, i::Int) +function intervalbounds(order::ForwardOrdered, locus::Center, span::Irregular, l::Lookup, i::Int) x = l[i] low = i == firstindex(l) ? bounds(l)[1] : x + (l[i - 1] - x) / 2 high = i == lastindex(l) ? bounds(l)[2] : x + (l[i + 1] - x) / 2 return (low, high) end -function intervalbounds(order::ReverseOrdered, locus::Center, span::Irregular, l::LookupArray, i::Int) +function intervalbounds(order::ReverseOrdered, locus::Center, span::Irregular, l::Lookup, i::Int) x = l[i] low = i == firstindex(l) ? bounds(l)[2] : x + (l[i - 1] - x) / 2 high = i == lastindex(l) ? bounds(l)[1] : x + (l[i + 1] - x) / 2 return (low, high) end -function intervalbounds(span::Span, sampling::Intervals, l::LookupArray) +function intervalbounds(span::Span, sampling::Intervals, l::Lookup) map(axes(l, 1)) do i intervalbounds(span, sampling, l, i) end end # Explicit -function intervalbounds(span::Explicit, ::Intervals, l::LookupArray, i::Int) +function intervalbounds(span::Explicit, ::Intervals, l::Lookup, i::Int) return (l[1, i], l[2, i]) end # We just reinterpret the bounds matrix rather than allocating -function intervalbounds(span::Explicit, ::Intervals, l::LookupArray) +function intervalbounds(span::Explicit, ::Intervals, l::Lookup) m = val(span) T = eltype(m) return reinterpret(reshape, Tuple{T,T}, m) @@ -668,35 +668,35 @@ end _intervalbounds_no_interval_error() = error("Lookup does not have Intervals, `intervalbounds` cannot be applied") # Slicespan should only be called after `to_indices` has simplified indices -slicespan(l::LookupArray, i::Colon) = span(l) -slicespan(l::LookupArray, i) = _slicespan(span(l), l, i) +slicespan(l::Lookup, i::Colon) = span(l) +slicespan(l::Lookup, i) = _slicespan(span(l), l, i) -_slicespan(span::Regular, l::LookupArray, i::Union{AbstractRange,CartesianIndices}) = Regular(step(l) * step(i)) -_slicespan(span::Regular, l::LookupArray, i::AbstractArray) = _slicespan(Irregular(bounds(l)), l, i) -_slicespan(span::Explicit, l::LookupArray, i::AbstractArray) = Explicit(val(span)[:, i]) -_slicespan(span::Irregular, l::LookupArray, i::AbstractArray) = +_slicespan(span::Regular, l::Lookup, i::Union{AbstractRange,CartesianIndices}) = Regular(step(l) * step(i)) +_slicespan(span::Regular, l::Lookup, i::AbstractArray) = _slicespan(Irregular(bounds(l)), l, i) +_slicespan(span::Explicit, l::Lookup, i::AbstractArray) = Explicit(val(span)[:, i]) +_slicespan(span::Irregular, l::Lookup, i::AbstractArray) = _slicespan(sampling(l), span, l, i) -function _slicespan(span::Irregular, l::LookupArray, i::Base.LogicalIndex) +function _slicespan(span::Irregular, l::Lookup, i::Base.LogicalIndex) i1 = length(i) == 0 ? (1:0) : ((findfirst(i.mask)::Int):(findlast(i.mask)::Int)) _slicespan(sampling(l), span, l, i1) end -function _slicespan(span::Irregular, l::LookupArray, i::InvertedIndices.InvertedIndexIterator) +function _slicespan(span::Irregular, l::Lookup, i::InvertedIndices.InvertedIndexIterator) i1 = collect(i) # We could do something more efficient here, but I'm not sure what _slicespan(sampling(l), span, l, i1) end -function _slicespan(::Points, span::Irregular, l::LookupArray, i::AbstractArray) +function _slicespan(::Points, span::Irregular, l::Lookup, i::AbstractArray) length(i) == 0 && return Irregular(nothing, nothing) fi, la = first(i), last(i) return Irregular(_maybeflipbounds(l, (l[fi], l[la]))) end -_slicespan(::Intervals, span::Irregular, l::LookupArray, i::AbstractArray) = +_slicespan(::Intervals, span::Irregular, l::Lookup, i::AbstractArray) = Irregular(_slicebounds(span, l, i)) -function _slicebounds(span::Irregular, l::LookupArray, i::AbstractArray) +function _slicebounds(span::Irregular, l::Lookup, i::AbstractArray) length(i) == 0 && return (nothing, nothing) _slicebounds(locus(l), span, l, i) end -function _slicebounds(locus::Start, span::Irregular, l::LookupArray, i::AbstractArray) +function _slicebounds(locus::Start, span::Irregular, l::Lookup, i::AbstractArray) fi, la = first(i), last(i) if isforward(l) l[fi], la >= lastindex(l) ? bounds(l)[2] : l[la + 1] @@ -704,7 +704,7 @@ function _slicebounds(locus::Start, span::Irregular, l::LookupArray, i::Abstract l[la], fi <= firstindex(l) ? bounds(l)[2] : l[fi - 1] end end -function _slicebounds(locus::End, span::Irregular, l::LookupArray, i::AbstractArray) +function _slicebounds(locus::End, span::Irregular, l::Lookup, i::AbstractArray) fi, la = first(i), last(i) if isforward(l) fi <= firstindex(l) ? bounds(l)[1] : l[fi - 1], l[la] @@ -712,7 +712,7 @@ function _slicebounds(locus::End, span::Irregular, l::LookupArray, i::AbstractAr la >= lastindex(l) ? bounds(l)[1] : l[la + 1], l[fi] end end -function _slicebounds(locus::Center, span::Irregular, l::LookupArray, i::AbstractArray) +function _slicebounds(locus::Center, span::Irregular, l::Lookup, i::AbstractArray) fi, la = first(i), last(i) a, b = if isforward(l) fi <= firstindex(l) ? bounds(l)[1] : (l[fi - 1] + l[fi]) / 2, @@ -724,7 +724,7 @@ function _slicebounds(locus::Center, span::Irregular, l::LookupArray, i::Abstrac return a, b end # Have to special-case date/time so we work with seconds and add to the original -function _slicebounds(locus::Center, span::Irregular, l::LookupArray{T}, i::AbstractArray) where T<:Dates.AbstractTime +function _slicebounds(locus::Center, span::Irregular, l::Lookup{T}, i::AbstractArray) where T<:Dates.AbstractTime op = T === Date ? div : / frst = if first(i) <= firstindex(l) _maybeflipbounds(l, bounds(l))[1] @@ -790,10 +790,10 @@ end end # Get the index value at the reduced locus. # This is the start, center or end point of the whole index. -@inline _reduceindex(lookup::LookupArray, step=nothing) = _reduceindex(locus(lookup), lookup, step) -@inline _reduceindex(locus::Start, lookup::LookupArray, step) = _mayberange(first(lookup), step) -@inline _reduceindex(locus::End, lookup::LookupArray, step) = _mayberange(last(lookup), step) -@inline _reduceindex(locus::Center, lookup::LookupArray, step) = begin +@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) diff --git a/src/LookupArrays/lookup_traits.jl b/src/Lookups/lookup_traits.jl similarity index 84% rename from src/LookupArrays/lookup_traits.jl rename to src/Lookups/lookup_traits.jl index 2f2e2b55e..d25eade3d 100644 --- a/src/LookupArrays/lookup_traits.jl +++ b/src/Lookups/lookup_traits.jl @@ -1,28 +1,28 @@ """ - LookupArrayTrait + LookupTrait -Abstract supertype of all traits of a [`LookupArray`](@ref). +Abstract supertype of all traits of a [`Lookup`](@ref). These modify the behaviour of the lookup index. The term "Trait" is used loosely - these may be fields of an object of traits hard-coded to specific types. """ -abstract type LookupArrayTrait end +abstract type LookupTrait end """ - Order <: LookupArrayTrait + Order <: LookupTrait -Traits for the order of a [`LookupArray`](@ref). These determine how +Traits for the order of a [`Lookup`](@ref). These determine how `searchsorted` finds values in the index, and how objects are plotted. """ -abstract type Order <: LookupArrayTrait end +abstract type Order <: LookupTrait end """ Ordered <: Order -Supertype for the order of an ordered [`LookupArray`](@ref), +Supertype for the order of an ordered [`Lookup`](@ref), including [`ForwardOrdered`](@ref) and [`ReverseOrdered`](@ref). """ abstract type Ordered <: Order end @@ -32,7 +32,7 @@ abstract type Ordered <: Order end AutoOrder() -Specifies that the `Order` of a `LookupArray` will be found automatically +Specifies that the `Order` of a `Lookup` will be found automatically where possible. """ struct AutoOrder <: Order end @@ -42,7 +42,7 @@ struct AutoOrder <: Order end ForwardOrdered() -Indicates that the `LookupArray` index is in the normal forward order. +Indicates that the `Lookup` index is in the normal forward order. """ struct ForwardOrdered <: Ordered end @@ -51,7 +51,7 @@ struct ForwardOrdered <: Ordered end ReverseOrdered() -Indicates that the `LookupArray` index is in the reverse order. +Indicates that the `Lookup` index is in the reverse order. """ struct ReverseOrdered <: Ordered end @@ -60,7 +60,7 @@ struct ReverseOrdered <: Ordered end Unordered() -Indicates that `LookupArray` is unordered. +Indicates that `Lookup` is unordered. This means the index cannot be searched with `searchsortedfirst` or similar optimised methods - instead it will use `findfirst`. @@ -72,7 +72,7 @@ isrev(::Type{<:ForwardOrdered}) = false isrev(::Type{<:ReverseOrdered}) = true """ - Locus <: LookupArrayTrait + Locus <: LookupTrait Abstract supertype of types that indicate the position of index values where they represent [`Intervals`](@ref). @@ -83,7 +83,7 @@ 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 <: LookupArrayTrait end +abstract type Locus <: LookupTrait end """ Center <: Locus @@ -126,12 +126,12 @@ struct AutoLocus <: Locus end """ - Sampling <: LookupArrayTrait + Sampling <: LookupTrait Indicates the sampling method used by the index: [`Points`](@ref) or [`Intervals`](@ref). """ -abstract type Sampling <: LookupArrayTrait end +abstract type Sampling <: LookupTrait end struct NoSampling <: Sampling end locus(sampling::NoSampling) = Center() @@ -172,12 +172,12 @@ locus(sampling::Intervals) = sampling.locus rebuild(::Intervals, locus) = Intervals(locus) """ - Span <: LookupArrayTrait + Span <: LookupTrait Defines the type of span used in a [`Sampling`](@ref) index. These are [`Regular`](@ref) or [`Irregular`](@ref). """ -abstract type Span <: LookupArrayTrait end +abstract type Span <: LookupTrait end struct NoSpan <: Span end @@ -255,9 +255,9 @@ Adapt.adapt_structure(to, s::Explicit) = Explicit(Adapt.adapt_structure(to, val( """ AutoIndex -Detect a `LookupArray` index from the context. This is used in `NoLookup` to simply +Detect a `Lookup` index 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 `LookupArray` type without changing the index values. +change the `Lookup` type without changing the index values. """ struct AutoIndex <: AbstractVector{Int} end diff --git a/src/LookupArrays/metadata.jl b/src/Lookups/metadata.jl similarity index 100% rename from src/LookupArrays/metadata.jl rename to src/Lookups/metadata.jl diff --git a/src/LookupArrays/methods.jl b/src/Lookups/methods.jl similarity index 100% rename from src/LookupArrays/methods.jl rename to src/Lookups/methods.jl diff --git a/src/LookupArrays/predicates.jl b/src/Lookups/predicates.jl similarity index 76% rename from src/LookupArrays/predicates.jl rename to src/Lookups/predicates.jl index 0ad53d9ef..58059609b 100644 --- a/src/LookupArrays/predicates.jl +++ b/src/Lookups/predicates.jl @@ -4,11 +4,11 @@ isregular(::Sampling) = false isexplicit(::Explicit) = true isexplicit(::Sampling) = false issampled(::AbstractSampled) = true -issampled(::LookupArray) = false +issampled(::Lookup) = false iscategorical(::AbstractCategorical) = true -iscategorical(::LookupArray) = false +iscategorical(::Lookup) = false iscyclic(::AbstractCyclic) = true -iscyclic(::LookupArray) = false +iscyclic(::Lookup) = false isstart(::Start) = true isstart(::Locus) = false iscenter(::Center) = true @@ -28,18 +28,18 @@ ispoints(::Intervals) = false # Forward them from lookups for f in (:isregular, :isexplicit) - @eval $f(l::LookupArray) = $f(span(l)) + @eval $f(l::Lookup) = $f(span(l)) end for f in (:isintervals, :ispoints) - @eval $f(l::LookupArray) = $f(sampling(l)) + @eval $f(l::Lookup) = $f(sampling(l)) end for f in (:isstart, :isend) @eval $f(l::AbstractSampled) = $f(locus(l)) - @eval $f(l::LookupArray) = false + @eval $f(l::Lookup) = false end iscenter(l::AbstractSampled) = iscenter(locus(l)) -iscenter(l::LookupArray) = true +iscenter(l::Lookup) = true for f in (:isordered, :isforward, :isreverse) - @eval $f(l::LookupArray) = $f(order(l)) + @eval $f(l::Lookup) = $f(order(l)) end diff --git a/src/LookupArrays/selector.jl b/src/Lookups/selector.jl similarity index 88% rename from src/LookupArrays/selector.jl rename to src/Lookups/selector.jl index 0827cd92b..ae9a7e5e6 100644 --- a/src/LookupArrays/selector.jl +++ b/src/Lookups/selector.jl @@ -73,7 +73,7 @@ const SelectorOrInterval = Union{Selector,Interval,Not} const SelTuple = Tuple{SelectorOrInterval,Vararg{SelectorOrInterval}} # `Not` form InvertedIndices.jr -function selectindices(l::LookupArray, sel::Not; kw...) +function selectindices(l::Lookup, sel::Not; kw...) indices = selectindices(l, sel.skip; kw...) return first(to_indices(l, (Not(indices),))) end @@ -125,8 +125,8 @@ Base.show(io::IO, x::At) = print(io, "At(", val(x), ", ", atol(x), ", ", rtol(x) struct _True end struct _False end -selectindices(l::LookupArray, sel::At; kw...) = at(l, sel; kw...) -selectindices(l::LookupArray, sel::At{<:AbstractVector}; kw...) = _selectvec(l, sel; kw...) +selectindices(l::Lookup, sel::At; kw...) = at(l, sel; kw...) +selectindices(l::Lookup, sel::At{<:AbstractVector}; kw...) = _selectvec(l, sel; kw...) _selectvec(l, sel; kw...) = [selectindices(l, rebuild(sel, v); kw...) for v in val(sel)] @@ -147,11 +147,11 @@ function at(lookup::NoLookup, sel::At; err=_True(), kw...) r in lookup || err isa _False || throw(SelectorError(lookup, sel)) return r end -function at(lookup::LookupArray, sel::At; kw...) +function at(lookup::Lookup, sel::At; kw...) at(order(lookup), span(lookup), lookup, val(sel), atol(sel), rtol(sel); kw...) end function at( - ::Ordered, span::Regular, lookup::LookupArray{<:Integer}, selval, atol::Nothing, rtol::Nothing; + ::Ordered, span::Regular, lookup::Lookup{<:Integer}, selval, atol::Nothing, rtol::Nothing; err=_True() ) x = unwrap(selval) @@ -165,7 +165,7 @@ function at( end end function at( - ::Ordered, ::Span, lookup::LookupArray{<:IntervalSets.Interval}, selval, atol, rtol::Nothing; + ::Ordered, ::Span, lookup::Lookup{<:IntervalSets.Interval}, selval, atol, rtol::Nothing; err=_True() ) x = unwrap(selval) @@ -177,7 +177,7 @@ function at( end end function at( - ::Ordered, ::Span, lookup::LookupArray{<:Union{Number,Dates.TimeType,AbstractString}}, selval, atol, rtol::Nothing; + ::Ordered, ::Span, lookup::Lookup{<:Union{Number,Dates.TimeType,AbstractString}}, selval, atol, rtol::Nothing; err=_True() ) x = unwrap(selval) @@ -203,7 +203,7 @@ function at( end end # catch-all for an unordered index -function at(::Order, ::Span, lookup::LookupArray, selval, atol, rtol::Nothing; err=_True()) +function at(::Order, ::Span, lookup::Lookup, selval, atol, rtol::Nothing; err=_True()) i = findfirst(x -> _is_at(x, unwrap(selval), atol), parent(lookup)) if i === nothing return _selnotfound_or_nothing(err, lookup, selval) @@ -250,8 +250,8 @@ struct Near{T} <: IntSelector{T} end Near() = Near(nothing) -selectindices(l::LookupArray, sel::Near) = near(l, sel) -selectindices(l::LookupArray, sel::Near{<:AbstractVector}) = _selectvec(l, sel) +selectindices(l::Lookup, sel::Near) = near(l, sel) +selectindices(l::Lookup, sel::Near{<:AbstractVector}) = _selectvec(l, sel) Base.show(io::IO, x::Near) = print(io, "Near(", val(x), ")") @@ -260,13 +260,13 @@ function near(lookup::AbstractCyclic{Cycling}, sel::Near) near(no_cycling(lookup), cycled_sel) end near(lookup::NoLookup, sel::Near{<:Real}) = max(1, min(round(Int, val(sel)), lastindex(lookup))) -function near(lookup::LookupArray, sel::Near) +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")) near(order(lookup), sampling(lookup), lookup, sel) end -near(order::Order, ::NoSampling, lookup::LookupArray, sel::Near) = at(lookup, At(val(sel))) -function near(order::Ordered, ::Union{Intervals,Points}, lookup::LookupArray, sel::Near) +near(order::Order, ::NoSampling, lookup::Lookup, sel::Near) = at(lookup, At(val(sel))) +function near(order::Ordered, ::Union{Intervals,Points}, lookup::Lookup, sel::Near) # Unwrap the selector value and adjust it for # interval locus if neccessary v = unwrap(val(sel)) @@ -297,10 +297,10 @@ function near(order::Ordered, ::Union{Intervals,Points}, lookup::LookupArray, se return closest_i end -function near(order::Ordered, ::Intervals, lookup::LookupArray{<:IntervalSets.Interval}, sel::Near) +function near(order::Ordered, ::Intervals, lookup::Lookup{<:IntervalSets.Interval}, sel::Near) throw(ArgumentError("`Near` is not yet implemented for lookups of `IntervalSets.Interval`")) end -function near(::Unordered, ::Union{Intervals,Points}, lookup::LookupArray, sel::Near) +function near(::Unordered, ::Union{Intervals,Points}, lookup::Lookup, sel::Near) throw(ArgumentError("`Near` has no meaning in an `Unordered` lookup")) end @@ -343,8 +343,8 @@ end Contains() = Contains(nothing) # Filter based on sampling and selector ----------------- -selectindices(l::LookupArray, sel::Contains; kw...) = contains(l, sel; kw...) -selectindices(l::LookupArray, sel::Contains{<:AbstractVector}; kw...) = _selectvec(l, sel; kw...) +selectindices(l::Lookup, sel::Contains; kw...) = contains(l, sel; kw...) +selectindices(l::Lookup, sel::Contains{<:AbstractVector}; kw...) = _selectvec(l, sel; kw...) Base.show(io::IO, x::Contains) = print(io, "Contains(", val(x), ")") @@ -357,30 +357,30 @@ function contains(l::NoLookup, sel::Contains; kw...) i in l || throw(SelectorError(l, i)) return i end -contains(l::LookupArray, sel::Contains; kw...) = contains(sampling(l), l, sel; kw...) +contains(l::Lookup, sel::Contains; kw...) = contains(sampling(l), l, sel; kw...) # NoSampling (e.g. Categorical) just uses `at` -function contains(::NoSampling, l::LookupArray, sel::Contains; kw...) +function contains(::NoSampling, l::Lookup, sel::Contains; kw...) at(l, At(val(sel)); kw...) end # Points -------------------------------------- -function contains(sampling::Points, l::LookupArray, sel::Contains; kw...) +function contains(sampling::Points, l::Lookup, sel::Contains; kw...) contains(order(l), sampling, l, sel; kw...) end -function contains(::Order, ::Points, l::LookupArray, sel::Contains; kw...) +function contains(::Order, ::Points, l::Lookup, sel::Contains; kw...) at(l, At(val(sel)); kw...) end -function contains(::Order, ::Points, l::LookupArray{<:AbstractArray}, sel::Contains{<:AbstractArray}; +function contains(::Order, ::Points, l::Lookup{<:AbstractArray}, sel::Contains{<:AbstractArray}; kw... ) at(l, At(val(sel)); kw...) end # Intervals ----------------------------------- -function contains(sampling::Intervals, l::LookupArray, sel::Contains; err=_True()) +function contains(sampling::Intervals, l::Lookup, sel::Contains; err=_True()) _locus_checkbounds(locus(l), l, sel) || return _selector_error_or_nothing(err, l, sel) contains(order(l), span(l), sampling, locus(l), l, sel; err) end function contains( - sampling::Intervals, l::LookupArray{<:IntervalSets.Interval}, sel::Contains; + sampling::Intervals, l::Lookup{<:IntervalSets.Interval}, sel::Contains; err=_True() ) v = val(sel) @@ -389,7 +389,7 @@ function contains( end function contains( ::Intervals, - l::LookupArray{<:IntervalSets.Interval}, + l::Lookup{<:IntervalSets.Interval}, sel::Contains{<:IntervalSets.Interval}; err=_True() ) @@ -403,7 +403,7 @@ function contains( end # Regular Intervals --------------------------- function contains( - o::Ordered, span::Regular, ::Intervals, locus::Locus, l::LookupArray, sel::Contains; + o::Ordered, span::Regular, ::Intervals, locus::Locus, l::Lookup, sel::Contains; err=_True() ) v = val(sel) @@ -411,7 +411,7 @@ function contains( return check_regular_contains(span, locus, l, v, i, err) end function contains( - o::Ordered, span::Regular, ::Intervals, locus::Center, l::LookupArray, sel::Contains; + o::Ordered, span::Regular, ::Intervals, locus::Center, l::Lookup, sel::Contains; err=_True() ) v = val(sel) + abs(val(span)) / 2 @@ -419,7 +419,7 @@ function contains( return check_regular_contains(span, locus, l, v, i, err) end -function check_regular_contains(span::Span, locus::Locus, l::LookupArray, v, i, err) +function check_regular_contains(span::Span, locus::Locus, l::Lookup, v, i, err) absstep = abs(val(span)) if (parent(l) isa AbstractRange) || _lt(locus)(v, l[i] + absstep) return i @@ -430,7 +430,7 @@ end # Explicit Intervals --------------------------- function contains( - o::Ordered, span::Explicit, ::Intervals, locus, l::LookupArray, sel::Contains; + o::Ordered, span::Explicit, ::Intervals, locus, l::Lookup, sel::Contains; err=_True() ) v = val(sel) @@ -444,13 +444,13 @@ function contains( end # Irregular Intervals ------------------------- function contains( - o::Ordered, span::Irregular, ::Intervals, locus::Locus, l::LookupArray, sel::Contains; + o::Ordered, span::Irregular, ::Intervals, locus::Locus, l::Lookup, sel::Contains; err=_True() ) return _searchfunc(locus, o)(l, val(sel)) end function contains( - o::Ordered, span::Irregular, ::Intervals, locus::Center, l::LookupArray, sel::Contains; + o::Ordered, span::Irregular, ::Intervals, locus::Center, l::Lookup, sel::Contains; err=_True() ) _order_lt(::ForwardOrdered) = (<) @@ -547,8 +547,8 @@ abstract type _Side end struct _Upper <: _Side end struct _Lower <: _Side end -selectindices(l::LookupArray, sel::Union{Between{<:Tuple},Interval}) = between(l, sel) -function selectindices(lookup::LookupArray, sel::Between{<:AbstractVector}) +selectindices(l::Lookup, sel::Union{Between{<:Tuple},Interval}) = between(l, sel) +function selectindices(lookup::Lookup, sel::Between{<:AbstractVector}) inds = Int[] for v in val(sel) append!(inds, selectindices(lookup, rebuild(sel, v))) @@ -557,7 +557,7 @@ end # between # returns a UnitRange from an Interval -function between(l::LookupArray, sel::Between) +function between(l::Lookup, sel::Between) a, b = _sorttuple(sel) return between(l, a..b) end @@ -571,18 +571,18 @@ end # cycled_sel = rebuild(sel; val=) # near(no_cycling(lookup), cycled_sel; kw...) # end -between(l::LookupArray, interval::Interval) = between(sampling(l), l, interval) +between(l::Lookup, interval::Interval) = between(sampling(l), l, interval) # This is the main method called above -function between(sampling::Sampling, l::LookupArray, interval::Interval) +function between(sampling::Sampling, l::Lookup, interval::Interval) isordered(l) || throw(ArgumentError("Cannot use an interval or `Between` with `Unordered`")) between(sampling, order(l), l, interval) end -function between(sampling::NoSampling, o::Ordered, l::LookupArray, interval::Interval) +function between(sampling::NoSampling, o::Ordered, l::Lookup, interval::Interval) between(Points(), o, l, interval) end -function between(sampling, o::Ordered, l::LookupArray, interval::Interval) +function between(sampling, o::Ordered, l::Lookup, interval::Interval) lowerbound, upperbound = bounds(l) lowsel, highsel = endpoints(interval) a = if lowsel > upperbound @@ -753,7 +753,7 @@ _ordscalar(::ReverseOrdered) = -1 _lt(::_Lower) = (<) _lt(::_Upper) = (<=) -_maybeflipbounds(m::LookupArray, bounds) = _maybeflipbounds(order(m), bounds) +_maybeflipbounds(m::Lookup, bounds) = _maybeflipbounds(order(m), bounds) _maybeflipbounds(o::ForwardOrdered, (a, b)) = (a, b) _maybeflipbounds(o::ReverseOrdered, (a, b)) = (b, a) _maybeflipbounds(o::Unordered, (a, b)) = (a, b) @@ -802,8 +802,8 @@ Touches(a, b) = Touches((a, b)) Base.first(sel::Touches) = first(val(sel)) Base.last(sel::Touches) = last(val(sel)) -selectindices(l::LookupArray, sel::Touches) = touches(l, sel) -function selectindices(lookup::LookupArray, sel::Touches{<:AbstractVector}) +selectindices(l::Lookup, sel::Touches) = touches(l, sel) +function selectindices(lookup::Lookup, sel::Touches{<:AbstractVector}) inds = Int[] for v in val(sel) append!(inds, selectindices(lookup, rebuild(sel, v))) @@ -814,19 +814,19 @@ end # returns a UnitRange like Touches/Interval but for cells contained # NoIndex behaves like `Sampled` `ForwardOrdered` `Points` of 1:N Int touches(l::NoLookup, sel::Touches) = between(l, Interval(val(sel)...)) -touches(l::LookupArray, sel::Touches) = touches(sampling(l), l, sel) +touches(l::Lookup, sel::Touches) = touches(sampling(l), l, sel) # This is the main method called above -function touches(sampling::Sampling, l::LookupArray, sel::Touches) +function touches(sampling::Sampling, l::Lookup, sel::Touches) o = order(l) o isa Unordered && throw(ArgumentError("Cannot use an sel or `Between` with Unordered")) touches(sampling, o, l, sel) end -function touches(sampling::NoSampling, o::Ordered, l::LookupArray, sel::Touches) +function touches(sampling::NoSampling, o::Ordered, l::Lookup, sel::Touches) touches(Points(), o, l, sel) end -function touches(sampling, o::Ordered, l::LookupArray, sel::Touches) +function touches(sampling, o::Ordered, l::Lookup, sel::Touches) lowerbound, upperbound = bounds(l) lowsel, highsel = val(sel) a = if lowsel > upperbound @@ -979,7 +979,7 @@ val(sel::Where) = sel.f Base.show(io::IO, x::Where) = print(io, "Where(", repr(val(x)), ")") # Yes this is everything. `Where` doesn't need lookup specialisation -@inline function selectindices(lookup::LookupArray, sel::Where) +@inline function selectindices(lookup::Lookup, sel::Where) [i for (i, v) in enumerate(parent(lookup)) if sel.f(v)] end @@ -1020,7 +1020,7 @@ All(args::SelectorOrInterval...) = All(args) Base.show(io::IO, x::All) = print(io, "All(", x.selectors, ")") -@inline function selectindices(lookup::LookupArray, sel::All) +@inline function selectindices(lookup::Lookup, sel::All) results = map(s -> selectindices(lookup, s), sel.selectors) sort!(union(results...)) end @@ -1035,33 +1035,33 @@ end Converts [`Selector`](@ref) to regular indices. """ function selectindices end -@inline selectindices(lookups::LookupArrayTuple, s1, ss...) = selectindices(lookups, (s1, ss...)) -@inline selectindices(lookups::LookupArrayTuple, selectors::Tuple) = +@inline selectindices(lookups::LookupTuple, s1, ss...) = selectindices(lookups, (s1, ss...)) +@inline selectindices(lookups::LookupTuple, selectors::Tuple) = map((l, s) -> selectindices(l, s), lookups, selectors) -@inline selectindices(lookups::LookupArrayTuple, selectors::Tuple{}) = () -# @inline selectindices(dim::LookupArray, sel::Val) = selectindices(val(dim), At(sel)) +@inline selectindices(lookups::LookupTuple, selectors::Tuple{}) = () +# @inline selectindices(dim::Lookup, sel::Val) = selectindices(val(dim), At(sel)) # Standard indices are just returned. -@inline selectindices(::LookupArray, sel::StandardIndices) = sel -@inline function selectindices(l::LookupArray, sel) +@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::LookupArray, sel::Selector{<:AbstractVector}) = +@inline selectindices(lookup::Lookup, sel::Selector{<:AbstractVector}) = [selectindices(lookup, rebuild(sel; val=v)) for v in val(sel)] -# Unaligned LookupArray ------------------------------------------ +# Unaligned Lookup ------------------------------------------ # select_unalligned_indices is callled directly from dims2indices # We use the transformation from the first unalligned dim. # In practice the others could be empty. -function select_unalligned_indices(lookups::LookupArrayTuple, sel::Tuple{IntSelector,Vararg{IntSelector}}) +function select_unalligned_indices(lookups::LookupTuple, sel::Tuple{IntSelector,Vararg{IntSelector}}) transformed = transformfunc(lookups[1])(map(val, sel)) map(_transform2int, lookups, sel, transformed) end -function select_unalligned_indices(lookups::LookupArrayTuple, sel::Tuple{Selector,Vararg{Selector}}) +function select_unalligned_indices(lookups::LookupTuple, sel::Tuple{Selector,Vararg{Selector}}) throw(ArgumentError("only `Near`, `At` or `Contains` selectors currently work on `Unalligned` lookups")) end @@ -1080,8 +1080,8 @@ end # Shared utils ============================================================================ # Return an inbounds index -_inbounds(is::Tuple, lookup::LookupArray) = map(i -> _inbounds(i, lookup), is) -function _inbounds(i::Int, lookup::LookupArray) +_inbounds(is::Tuple, lookup::Lookup) = map(i -> _inbounds(i, lookup), is) +function _inbounds(i::Int, lookup::Lookup) if i > lastindex(lookup) lastindex(lookup) elseif i <= firstindex(lookup) @@ -1099,7 +1099,7 @@ _lt(::End) = (<=) _gt(::Locus) = (>=) _gt(::End) = (>) -_locus_checkbounds(loc, lookup::LookupArray, sel::Selector) = _locus_checkbounds(loc, bounds(lookup), val(sel)) +_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)) _searchfunc(::ForwardOrdered) = searchsortedfirst @@ -1125,9 +1125,9 @@ _in(needle::Interval{<:Any,:open}, haystack::Interval{:closed,:open}) = needle.l _in(needle::Interval{:open,<:Any}, haystack::Interval{:open,:closed}) = needle.left in haystack && needle.right in haystack _in(needle::OpenInterval, haystack::OpenInterval) = needle.left in haystack && needle.right in haystack -hasselection(lookup::LookupArray, sel::At) = at(lookup, sel; err=_False()) === nothing ? false : true -hasselection(lookup::LookupArray, sel::Contains) = contains(lookup, sel; err=_False()) === nothing ? false : true +hasselection(lookup::Lookup, sel::At) = at(lookup, sel; err=_False()) === nothing ? false : true +hasselection(lookup::Lookup, sel::Contains) = contains(lookup, sel; err=_False()) === nothing ? false : true # Near and Between only fail on Unordered # Otherwise Near returns the nearest index, and Between an empty range -hasselection(lookup::LookupArray, ::Near) = isordered(lookup) ? true : false -hasselection(lookup::LookupArray, ::Union{Interval,Between}) = isordered(lookup) ? true : false +hasselection(lookup::Lookup, ::Near) = isordered(lookup) ? true : false +hasselection(lookup::Lookup, ::Union{Interval,Between}) = isordered(lookup) ? true : false diff --git a/src/LookupArrays/set.jl b/src/Lookups/set.jl similarity index 78% rename from src/LookupArrays/set.jl rename to src/Lookups/set.jl index bccb53cae..5484e1fd3 100644 --- a/src/LookupArrays/set.jl +++ b/src/Lookups/set.jl @@ -1,14 +1,14 @@ -const LookupArraySetters = Union{AllMetadata,LookupArray,LookupArrayTrait,Nothing,AbstractArray} -set(lookup::LookupArray, x::LookupArraySetters) = _set(lookup, x) +const LookupSetters = Union{AllMetadata,Lookup,LookupTrait,Nothing,AbstractArray} +set(lookup::Lookup, x::LookupSetters) = _set(lookup, x) -# _set(lookup::LookupArray, newlookup::LookupArray) = lookup +# _set(lookup::Lookup, newlookup::Lookup) = lookup _set(lookup::AbstractCategorical, newlookup::AutoLookup) = begin lookup = _set(lookup, parent(newlookup)) o = _set(order(lookup), order(newlookup)) md = _set(metadata(lookup), metadata(newlookup)) rebuild(lookup; order=o, metadata=md) end -_set(lookup::LookupArray, newlookup::AbstractCategorical) = begin +_set(lookup::Lookup, newlookup::AbstractCategorical) = begin lookup = _set(lookup, parent(newlookup)) o = _set(order(lookup), order(newlookup)) md = _set(metadata(lookup), metadata(newlookup)) @@ -23,7 +23,7 @@ _set(lookup::AbstractSampled, newlookup::AutoLookup) = begin md = _set(metadata(lookup), metadata(newlookup)) rebuild(lookup; data=parent(lookup), order=o, span=sp, sampling=sa, metadata=md) end -_set(lookup::LookupArray, newlookup::AbstractSampled) = begin +_set(lookup::Lookup, newlookup::AbstractSampled) = begin # Update each field separately. The old lookup may not have these fields, or may have # a subset with the rest being traits. The new lookup may have some auto fields. lookup = _set(lookup, parent(newlookup)) @@ -35,17 +35,17 @@ _set(lookup::LookupArray, newlookup::AbstractSampled) = begin 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::LookupArray, newlookup::NoLookup{<:AutoIndex}) = NoLookup(axes(lookup, 1)) -_set(lookup::LookupArray, newlookup::NoLookup) = newlookup +_set(lookup::Lookup, newlookup::NoLookup{<:AutoIndex}) = NoLookup(axes(lookup, 1)) +_set(lookup::Lookup, newlookup::NoLookup) = newlookup # Set the index -_set(lookup::LookupArray, index::Val) = rebuild(lookup; data=index) -_set(lookup::LookupArray, index::Colon) = lookup -_set(lookup::LookupArray, index::AutoLookup) = lookup -_set(lookup::LookupArray, index::AbstractArray) = rebuild(lookup; data=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(lookup::LookupArray, index::AutoIndex) = lookup -_set(lookup::LookupArray, index::AbstractRange) = +_set(lookup::Lookup, index::AutoIndex) = lookup +_set(lookup::Lookup, index::AbstractRange) = rebuild(lookup; data=_set(parent(lookup), index), order=orderof(index)) # Update the Sampling lookup of Sampled dims - it must match the range. _set(lookup::AbstractSampled, index::AbstractRange) = begin @@ -56,7 +56,7 @@ _set(lookup::AbstractSampled, index::AbstractRange) = begin end # Order -_set(lookup::LookupArray, neworder::Order) = rebuild(lookup; order=_set(order(lookup), neworder)) +_set(lookup::Lookup, neworder::Order) = rebuild(lookup; order=_set(order(lookup), neworder)) _set(lookup::NoLookup, neworder::Order) = lookup _set(order::Order, neworder::Order) = neworder _set(order::Order, neworder::AutoOrder) = order @@ -88,7 +88,7 @@ _set(locus::Locus, newlocus::Locus) = newlocus _set(locus::Locus, newlocus::AutoLocus) = locus # Metadata -_set(lookup::LookupArray, newmetadata::AllMetadata) = rebuild(lookup; metadata=newmetadata) +_set(lookup::Lookup, newmetadata::AllMetadata) = rebuild(lookup; metadata=newmetadata) _set(metadata::AllMetadata, newmetadata::AllMetadata) = newmetadata # Index diff --git a/src/LookupArrays/show.jl b/src/Lookups/show.jl similarity index 96% rename from src/LookupArrays/show.jl rename to src/Lookups/show.jl index 0acccddb1..39f81142d 100644 --- a/src/LookupArrays/show.jl +++ b/src/Lookups/show.jl @@ -11,7 +11,7 @@ function Base.show(io::IO, mime::MIME"text/plain", lookup::Transformed) show(ctx, mime, dim(lookup)) end -function Base.show(io::IO, mime::MIME"text/plain", lookup::LookupArray) +function Base.show(io::IO, mime::MIME"text/plain", lookup::Lookup) show_compact(io, mime, lookup) get(io, :compact, false) && print_index(io, mime, parent(lookup)) show_properties(io, mime, lookup) @@ -37,7 +37,7 @@ function show_properties(io::IO, lookup::AbstractCategorical) print_order(io, lookup) end -function Base.show(io::IO, mime::MIME"text/plain", lookups::Tuple{LookupArray,Vararg{LookupArray}}) +function Base.show(io::IO, mime::MIME"text/plain", lookups::Tuple{Lookup,Vararg{Lookup}}) length(lookups) > 0 || return 0 ctx = IOContext(io, :compact=>true) if all(l -> l isa NoLookup, lookups) @@ -64,7 +64,7 @@ function Base.show(io::IO, mime::MIME"text/plain", lookups::Tuple{LookupArray,Va end end -function show_compact(io, mime, lookup::LookupArray) +function show_compact(io, mime, lookup::Lookup) print(io, nameof(typeof(lookup))) print(io, "{") print(io, string(eltype(lookup))) diff --git a/src/LookupArrays/utils.jl b/src/Lookups/utils.jl similarity index 80% rename from src/LookupArrays/utils.jl rename to src/Lookups/utils.jl index 88f785ebc..2ec4e2301 100644 --- a/src/LookupArrays/utils.jl +++ b/src/Lookups/utils.jl @@ -5,7 +5,7 @@ Shift the index of `x` from the current locus to the new locus. We only shift `Sampled`, `Regular` or `Explicit`, `Intervals`. """ -function shiftlocus(locus::Locus, lookup::LookupArray) +function shiftlocus(locus::Locus, lookup::Lookup) samp = sampling(lookup) samp isa Intervals || error("Cannot shift locus of $(nameof(typeof(samp)))") newindex = _shiftindexlocus(locus, lookup) @@ -14,15 +14,15 @@ function shiftlocus(locus::Locus, lookup::LookupArray) end # Fallback - no shifting -_shiftindexlocus(locus::Locus, lookup::LookupArray) = index(lookup) +_shiftindexlocus(locus::Locus, lookup::Lookup) = index(lookup) # Sampled function _shiftindexlocus(locus::Locus, lookup::AbstractSampled) _shiftindexlocus(locus, span(lookup), sampling(lookup), lookup) end # TODO: -_shiftindexlocus(locus::Locus, span::Irregular, sampling::Sampling, lookup::LookupArray) = index(lookup) +_shiftindexlocus(locus::Locus, span::Irregular, sampling::Sampling, lookup::Lookup) = index(lookup) # Sampled Regular -function _shiftindexlocus(destlocus::Center, span::Regular, sampling::Intervals, dim::LookupArray) +function _shiftindexlocus(destlocus::Center, span::Regular, sampling::Intervals, dim::Lookup) if destlocus === locus(sampling) return index(dim) else @@ -31,17 +31,17 @@ function _shiftindexlocus(destlocus::Center, span::Regular, sampling::Intervals, return index(dim) .+ shift end end -function _shiftindexlocus(destlocus::Locus, span::Regular, sampling::Intervals, lookup::LookupArray) +function _shiftindexlocus(destlocus::Locus, span::Regular, sampling::Intervals, lookup::Lookup) index(lookup) .+ (abs(step(span)) * _offset(locus(sampling), destlocus)) end # Sampled Explicit -_shiftindexlocus(::Start, span::Explicit, sampling::Intervals, lookup::LookupArray) = val(span)[1, :] -_shiftindexlocus(::End, span::Explicit, sampling::Intervals, lookup::LookupArray) = val(span)[2, :] -function _shiftindexlocus(destlocus::Center, span::Explicit, sampling::Intervals, lookup::LookupArray) +_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) end -_shiftindexlocus(::Center, ::Center, span::Explicit, sampling::Intervals, lookup::LookupArray) = index(lookup) -function _shiftindexlocus(::Center, ::Locus, span::Explicit, sampling::Intervals, lookup::LookupArray) +_shiftindexlocus(::Center, ::Center, span::Explicit, sampling::Intervals, lookup::Lookup) = index(lookup) +function _shiftindexlocus(::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 @@ -54,10 +54,10 @@ _offset(::End, ::Start) = -1 _offset(::End, ::Center) = -0.5 _offset(::T, ::T) where T<:Locus = 0 -maybeshiftlocus(locus::Locus, l::LookupArray) = _maybeshiftlocus(locus, sampling(l), l) +maybeshiftlocus(locus::Locus, l::Lookup) = _maybeshiftlocus(locus, sampling(l), l) -_maybeshiftlocus(locus::Locus, sampling::Intervals, l::LookupArray) = shiftlocus(locus, l) -_maybeshiftlocus(locus::Locus, sampling::Sampling, l::LookupArray) = l +_maybeshiftlocus(locus::Locus, sampling::Intervals, l::Lookup) = shiftlocus(locus, l) +_maybeshiftlocus(locus::Locus, sampling::Sampling, l::Lookup) = l """ basetypeof(x) => Type @@ -91,7 +91,7 @@ orderof(A::AbstractUnitRange) = ForwardOrdered() orderof(A::AbstractRange) = _order(A) function orderof(A::AbstractArray{<:IntervalSets.Interval}) indord = _order(A) - sorted = issorted(A; rev=LookupArrays.isrev(indord), by=x -> x.left) + sorted = issorted(A; rev=Lookups.isrev(indord), by=x -> x.left) return sorted ? indord : Unordered() end function orderof(A::AbstractArray) @@ -101,7 +101,7 @@ function orderof(A::AbstractArray) # This may be resolved by: https://github.com/JuliaLang/julia/pull/37239 try indord = _order(A) - sorted = issorted(A; rev=LookupArrays.isrev(indord)) + sorted = issorted(A; rev=Lookups.isrev(indord)) catch sorted = false end diff --git a/src/array/methods.jl b/src/array/methods.jl index ad9d16ea7..4ec87bf88 100644 --- a/src/array/methods.jl +++ b/src/array/methods.jl @@ -390,8 +390,8 @@ end check_cat_lookups(dims::Dimension...) = _check_cat_lookups(basetypeof(first(dims)), lookup(dims)...) -# LookupArrays may need adjustment for `cat` -_check_cat_lookups(D, lookups::LookupArray...) = _check_cat_lookup_order(D, lookups...) +# Lookups may need adjustment for `cat` +_check_cat_lookups(D, lookups::Lookup...) = _check_cat_lookup_order(D, lookups...) _check_cat_lookups(D, l1::NoLookup, lookups::NoLookup...) = true function _check_cat_lookups(D, l1::AbstractSampled, lookups::AbstractSampled...) length(lookups) > 0 || return true @@ -430,7 +430,7 @@ function _check_cat_lookups(D, ::Irregular, lookups...) end |> all end -function _check_cat_lookup_order(D, lookups::LookupArray...) +function _check_cat_lookup_order(D, lookups::Lookup...) l1 = first(lookups) length(l1) == 0 && return _check_cat_lookup_order(D, Base.tail(lookups)...) L = basetypeof(l1) @@ -481,8 +481,8 @@ function _vcat_dims(d1::Dimension, ds::Dimension...) return rebuild(d1, newlookup) end -# LookupArrays may need adjustment for `cat` -function _vcat_lookups(lookups::LookupArray...) +# Lookups may need adjustment for `cat` +function _vcat_lookups(lookups::Lookup...) newindex = _vcat_index(lookups...) return rebuild(lookups[1]; data=newindex) end @@ -520,10 +520,10 @@ end _vcat_index(A1::NoLookup, A::NoLookup...) = OneTo(mapreduce(length, +, (A1, A...))) # TODO: handle vcat OffsetArrays? # Otherwise just vcat. TODO: handle order breaking vcat? -# function _vcat_index(lookup::LookupArray, lookups...) +# function _vcat_index(lookup::Lookup, lookups...) # _vcat_index(span(lookup), lookup, lookups...) # end -function _vcat_index(lookup1::LookupArray, lookups::LookupArray...) +function _vcat_index(lookup1::Lookup, lookups::Lookup...) shifted = map((lookup1, lookups...)) do l parent(maybeshiftlocus(locus(lookup1), l)) end diff --git a/src/dimindices.jl b/src/dimindices.jl index 8c6e18ac7..89346d044 100644 --- a/src/dimindices.jl +++ b/src/dimindices.jl @@ -134,11 +134,11 @@ _dimindices_format(dims::Tuple) = map(rebuild, dims, map(_dimindices_axis, dims) # Allow only CartesianIndices arguments _dimindices_axis(x::Integer) = Base.OneTo(x) _dimindices_axis(x::AbstractRange{<:Integer}) = x -# And LookupArray, which we take the axes from +# And Lookup, which we take the axes from _dimindices_axis(x::Dimension) = _dimindices_axis(val(x)) -_dimindices_axis(x::LookupArray) = axes(x, 1) +_dimindices_axis(x::Lookup) = axes(x, 1) _dimindices_axis(x) = - throw(ArgumentError("`$x` is not a valid input for `DimIndices`. Use `Dimension`s wrapping `Integer`, `AbstractArange{<:Integer}`, or a `LookupArray` (the `axes` will be used)")) + throw(ArgumentError("`$x` is not a valid input for `DimIndices`. Use `Dimension`s wrapping `Integer`, `AbstractArange{<:Integer}`, or a `Lookup` (the `axes` will be used)")) """ diff --git a/src/groupby.jl b/src/groupby.jl index b4d3f923c..25469174e 100644 --- a/src/groupby.jl +++ b/src/groupby.jl @@ -356,11 +356,11 @@ function _group_indices(dim::Dimension, f::Base.Callable; labels=nothing) indices = last.(ps) return group_dim, indices end -function _group_indices(dim::Dimension, group_lookup::LookupArray; labels=nothing) +function _group_indices(dim::Dimension, group_lookup::Lookup; labels=nothing) orig_lookup = lookup(dim) indices = map(_ -> Int[], 1:length(group_lookup)) for (i, v) in enumerate(orig_lookup) - n = selectindices(group_lookup, Contains(v); err=LookupArrays._False()) + n = selectindices(group_lookup, Contains(v); err=Lookups._False()) isnothing(n) || push!(indices[n], i) end group_dim = if isnothing(labels) @@ -390,7 +390,7 @@ function _group_indices(dim::Dimension, bins::AbstractBins; labels=bins.labels) group_lookup = lookup(format(rebuild(dim, groups))) transformed_lookup = rebuild(dim, transformed) - # Call the LookupArray version to do the work using selectors + # Call the Lookup version to do the work using selectors return _group_indices(transformed_lookup, group_lookup; labels) end diff --git a/src/interface.jl b/src/interface.jl index e65ec6442..54d7ab56f 100644 --- a/src/interface.jl +++ b/src/interface.jl @@ -5,7 +5,7 @@ Rebuild an object struct with updated field values. -`x` can be a `AbstractDimArray`, a `Dimension`, `LookupArray` or other custom types. +`x` can be a `AbstractDimArray`, a `Dimension`, `Lookup` or other custom types. This is an abstraction that alows inbuilt and custom types to be rebuilt to update their fields, as most objects in DimensionalData.jl are immutable. @@ -36,7 +36,7 @@ The arguments required are defined for the abstract type that has a `rebuild` me - `val`: anything. -#### `LookupArray`: +#### `Lookup`: - `data`: the parent object, an `AbstractArray` @@ -89,12 +89,12 @@ Objects that don't define a `val` method are returned unaltered. function val end """ - lookup(x::Dimension) => LookupArray - lookup(x, [dims::Tuple]) => Tuple{Vararg{LookupArray}} - lookup(x::Tuple) => Tuple{Vararg{LookupArray}} - lookup(x, dim) => LookupArray + lookup(x::Dimension) => Lookup + lookup(x, [dims::Tuple]) => Tuple{Vararg{Lookup}} + lookup(x::Tuple) => Tuple{Vararg{Lookup}} + lookup(x, dim) => Lookup -Returns the [`LookupArray`](@ref) of a dimension. This dictates +Returns the [`Lookup`](@ref) of a dimension. This dictates properties of the dimension such as array axis and index order, and sampling properties. @@ -102,10 +102,10 @@ and sampling properties. """ function lookup end -# Methods to retreive Object/Dimension/LookupArray properties +# Methods to retreive Object/Dimension/Lookup properties # # These work on AbstractDimStack, AbstractDimArray, Dimension -# LookupArray, and tuples of Dimension/LookupArray. A `dims` argument +# Lookup, and tuples of Dimension/Lookup. A `dims` argument # can be supplied to select a subset of dimensions or a single # Dimension. @@ -186,7 +186,7 @@ function label end bounds(xs, [dims::Tuple]) => Tuple{Vararg{Tuple{T,T}}} bounds(xs::Tuple) => Tuple{Vararg{Tuple{T,T}}} bounds(x, dim) => Tuple{T,T} - bounds(dim::Union{Dimension,LookupArray}) => Tuple{T,T} + bounds(dim::Union{Dimension,Lookup}) => Tuple{T,T} Return the bounds of all dimensions of an object, of a specific dimension, or of a tuple of dimensions. @@ -200,7 +200,7 @@ function bounds end """ order(x, [dims::Tuple]) => Tuple order(xs::Tuple) => Tuple - order(x::Union{Dimension,LookupArray}) => Order + order(x::Union{Dimension,Lookup}) => Order Return the `Ordering` of the dimension index for each dimension: `ForwardOrdered`, `ReverseOrdered`, or [`Unordered`](@ref) @@ -214,7 +214,7 @@ function order end sampling(x, [dims::Tuple]) => Tuple sampling(x, dim) => Sampling sampling(xs::Tuple) => Tuple{Vararg{Sampling}} - sampling(x:Union{Dimension,LookupArray}) => Sampling + sampling(x:Union{Dimension,Lookup}) => Sampling Return the [`Sampling`](@ref) for each dimension. @@ -227,7 +227,7 @@ function sampling end span(x, [dims::Tuple]) => Tuple span(x, dim) => Span span(xs::Tuple) => Tuple{Vararg{Span,N}} - span(x::Union{Dimension,LookupArray}) => Span + span(x::Union{Dimension,Lookup}) => Span Return the [`Span`](@ref) for each dimension. @@ -240,7 +240,7 @@ function span end locus(x, [dims::Tuple]) => Tuple locus(x, dim) => Locus locus(xs::Tuple) => Tuple{Vararg{Locus,N}} - locus(x::Union{Dimension,LookupArray}) => Locus + locus(x::Union{Dimension,Lookup}) => Locus Return the [`Locus`](@ref) for each dimension. diff --git a/src/plotrecipes.jl b/src/plotrecipes.jl index c7a270696..e81fd6ed4 100644 --- a/src/plotrecipes.jl +++ b/src/plotrecipes.jl @@ -134,25 +134,25 @@ end _withaxes(dim::Dimension, A::AbstractDimArray) = _withaxes(lookup(dim), index(dim), parent(A)) _withaxes(::NoLookup, index, A::AbstractArray) = A -_withaxes(::LookupArray, index, A::AbstractArray) = index, A +_withaxes(::Lookup, index, A::AbstractArray) = index, A _withaxes(::Categorical, index, A::AbstractArray) = eachindex(index), A _withaxes(dx::Dimension, dy::Dimension, A::AbstractDimArray) = _withaxes(lookup(dx), lookup(dy), index(dx), index(dy), parent(A)) -_withaxes(::LookupArray, ::LookupArray, ix, iy, A) = ix, iy, A -_withaxes(::NoLookup, ::LookupArray, ix, iy, A) = axes(A, 2), iy, A -_withaxes(::LookupArray, ::NoLookup, ix, iy, A) = ix, axes(A, 1), 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, ::LookupArray, index) = nothing +_xticks!(attr, s, ::Lookup, index) = 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, ::LookupArray, index) = nothing +_yticks!(attr, s, ::Lookup, index) = nothing ### Shared utils @@ -179,7 +179,7 @@ function refdims_title(lookup::AbstractSampled, refdim::Dimension; kw...) "$start to $stop" end end -function refdims_title(lookup::LookupArray, refdim::Dimension; kw...) +function refdims_title(lookup::Lookup, refdim::Dimension; kw...) if parent(refdim) isa AbstractArray string(first(parent(refdim))) else diff --git a/src/set.jl b/src/set.jl index 605391793..a283f114e 100644 --- a/src/set.jl +++ b/src/set.jl @@ -7,20 +7,20 @@ const DimArrayOrStack = Union{AbstractDimArray,AbstractDimStack} set(x, args::Tuple{Vararg{Dimension}}; kw...) => x with updated field/s set(dim::Dimension, index::AbstractArray) => Dimension - set(dim::Dimension, lookup::LookupArray) => Dimension - set(dim::Dimension, lookupcomponent::LookupArrayTrait) => Dimension + set(dim::Dimension, lookup::Lookup) => Dimension + set(dim::Dimension, lookupcomponent::LookupTrait) => Dimension set(dim::Dimension, metadata::AbstractMetadata) => Dimension Set the properties of an object, its internal data or the traits of its dimensions and lookup index. As DimensionalData is so strongly typed you do not need to specify what field -of a [`LookupArray`](@ref) to `set` - there is no ambiguity. +of a [`Lookup`](@ref) to `set` - there is no ambiguity. -To set fields of a `LookupArray` you need to specify the dimension. This can be done +To set fields of a `Lookup` you need to specify the dimension. This can be done using `X => val` pairs, `X = val` keyword arguments, or `X(val)` wrapped arguments. -When a `Dimension` or `LookupArray` is passed to `set` to replace the +When a `Dimension` or `Lookup` is passed to `set` to replace the existing ones, fields that are not set will keep their original values. ## Notes: @@ -83,7 +83,7 @@ julia> set(da, Z => [:a, :b, :c, :d], :custom => [4, 5, 6]) 6 0.0 0.0 0.0 0.0 ``` -Change the `LookupArray` type: +Change the `Lookup` type: ```jldoctest set julia> set(da, Z=DD.NoLookup(), custom=DD.Sampled()) @@ -115,12 +115,12 @@ julia> set(da, :custom => DD.Irregular(10, 12), Z => DD.Regular(9.9)) ``` """ function set end -set(A::DimArrayOrStack, x::T) where {T<:Union{LookupArray,LookupArrayTrait}} = _onlydimerror(x) +set(A::DimArrayOrStack, x::T) where {T<:Union{Lookup,LookupTrait}} = _onlydimerror(x) set(x::DimArrayOrStack, ::Type{T}) where T = set(x, T()) -set(A::AbstractDimStack, x::LookupArray) = LookupArrays._cantseterror(A, x) -set(A::AbstractDimArray, x::LookupArray) = LookupArrays._cantseterror(A, x) -set(A, x) = LookupArrays._cantseterror(A, x) +set(A::AbstractDimStack, x::Lookup) = Lookups._cantseterror(A, x) +set(A::AbstractDimArray, x::Lookup) = Lookups._cantseterror(A, x) +set(A, x) = Lookups._cantseterror(A, x) set(A::DimArrayOrStack, args::Union{Dimension,DimTuple,Pair}...; kw...) = rebuild(A, data(A), set(dims(A), args...; kw...)) set(A::AbstractDimArray, newdata::AbstractArray) = begin diff --git a/src/utils.jl b/src/utils.jl index 76a62a8c5..28762f184 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -53,7 +53,7 @@ end _reorder(x, orderdims::Tuple{}) = x reorder(x, orderdim::Dimension) = _reorder(val(orderdim), x, dims(x, orderdim)) -reorder(x, orderdim::Dimension{<:LookupArray}) = _reorder(order(orderdim), x, dims(x, orderdim)) +reorder(x, orderdim::Dimension{<:Lookup}) = _reorder(order(orderdim), x, dims(x, orderdim)) _reorder(neworder::Order, x, dim::Dimension) = _reorder(basetypeof(neworder), x, dim) # Reverse the dimension index @@ -93,7 +93,7 @@ function modify(f, A::AbstractDimArray) end modify(f, x, dim::DimOrDimType) = set(x, modify(f, dims(x, dim))) modify(f, dim::Dimension) = rebuild(dim, modify(f, val(dim))) -function modify(f, lookup::LookupArray) +function modify(f, lookup::Lookup) newindex = modify(f, parent(lookup)) rebuild(lookup; data=newindex) end diff --git a/test/adapt.jl b/test/adapt.jl index 457c473f6..c3f9e1f62 100644 --- a/test/adapt.jl +++ b/test/adapt.jl @@ -1,6 +1,6 @@ using DimensionalData, Test, Unitful, Adapt -using DimensionalData.LookupArrays, DimensionalData.Dimensions +using DimensionalData.Lookups, DimensionalData.Dimensions struct CustomArray{T,N} <: AbstractArray{T,N} arr::Array @@ -17,7 +17,7 @@ Base.count(x::CustomArray) = count(x.arr) @test adapt(CustomArray, Metadata(:a=>"1", :b=>"2")) == NoMetadata() end -@testset "LookupArray" begin +@testset "Lookup" begin l = Sampled([1:10...]; metadata=Metadata(:a=>"1", :b=>"2")) l1 = Adapt.adapt(CustomArray, l) @test parent(parent(l1)) isa CustomArray diff --git a/test/array.jl b/test/array.jl index edde93b2b..aaf3b2b97 100644 --- a/test/array.jl +++ b/test/array.jl @@ -2,7 +2,7 @@ using DimensionalData, Test, Unitful, SparseArrays, Dates, Random using DimensionalData: layerdims, checkdims using LinearAlgebra -using DimensionalData.LookupArrays, DimensionalData.Dimensions +using DimensionalData.Lookups, DimensionalData.Dimensions a = [1 2; 3 4] a2 = [1 2 3 4 diff --git a/test/dimension.jl b/test/dimension.jl index 3631f19b6..1ff31eab3 100644 --- a/test/dimension.jl +++ b/test/dimension.jl @@ -1,5 +1,5 @@ using DimensionalData, Test, Unitful -using DimensionalData.LookupArrays, DimensionalData.Dimensions +using DimensionalData.Lookups, DimensionalData.Dimensions @dim TestDim "Testname" diff --git a/test/dimindices.jl b/test/dimindices.jl index ff675de2c..06125f317 100644 --- a/test/dimindices.jl +++ b/test/dimindices.jl @@ -1,5 +1,5 @@ using DimensionalData, Test, Dates -using DimensionalData.LookupArrays, DimensionalData.Dimensions +using DimensionalData.Lookups, DimensionalData.Dimensions A = zeros(X(4.0:7.0), Y(10.0:12.0)) diff --git a/test/ecosystem.jl b/test/ecosystem.jl index 9a47a5183..fe97d3147 100644 --- a/test/ecosystem.jl +++ b/test/ecosystem.jl @@ -1,5 +1,5 @@ using OffsetArrays, ImageFiltering, ImageTransformations, ArrayInterface, DimensionalData, Test -using DimensionalData.LookupArrays +using DimensionalData.Lookups @testset "ArrayInterface" begin a = [1 2; 3 4] diff --git a/test/format.jl b/test/format.jl index 36ff80f0d..6f8746734 100644 --- a/test/format.jl +++ b/test/format.jl @@ -1,5 +1,5 @@ using DimensionalData, Test, Unitful -using DimensionalData.LookupArrays, DimensionalData.Dimensions +using DimensionalData.Lookups, DimensionalData.Dimensions using .Dimensions: _format using Base: OneTo @@ -8,7 +8,7 @@ struct Unsortable val::Int end -@testset "format LookupArray" begin +@testset "format Lookup" begin @testset "format Categorical from AutoLookup" begin A = [:a, :b] @test format(AutoLookup(A), X, OneTo(2)) === Categorical(A, ForwardOrdered(), NoMetadata()) @@ -94,7 +94,7 @@ end @test format(l, X, Base.OneTo(2)) === Sampled(l, ForwardOrdered(), Regular(0.0), Points(), NoMetadata()) end - @testset "LookupArray conversion errors" begin + @testset "Lookup conversion errors" begin @test_throws ArgumentError DimArray(rand(5, 4), (X(1), Y(1:4))) @test_throws ArgumentError DimArray(rand(5), X(1; foo=:bar)) end diff --git a/test/groupby.jl b/test/groupby.jl index bc7cb6d40..486511da6 100644 --- a/test/groupby.jl +++ b/test/groupby.jl @@ -1,7 +1,7 @@ using DimensionalData, Test, Dates, Statistics, IntervalSets using DimensionalData.Dimensions -using DimensionalData.LookupArrays +using DimensionalData.Lookups const DD = DimensionalData days = DateTime(2000):Day(1):DateTime(2000, 12, 31) diff --git a/test/indexing.jl b/test/indexing.jl index d18d82ad5..6dc152035 100644 --- a/test/indexing.jl +++ b/test/indexing.jl @@ -1,5 +1,5 @@ using DimensionalData, Test, BenchmarkTools, Dates, Statistics -using DimensionalData.LookupArrays, DimensionalData.Dimensions +using DimensionalData.Lookups, DimensionalData.Dimensions @testset "dims2indices" begin a = [1 2 3; 4 5 6] diff --git a/test/lookup.jl b/test/lookup.jl index 979a7d7a6..db160df38 100644 --- a/test/lookup.jl +++ b/test/lookup.jl @@ -1,6 +1,6 @@ using DimensionalData, Test, Unitful -using DimensionalData.LookupArrays, DimensionalData.Dimensions -using DimensionalData.LookupArrays: _slicespan, isrev, _bounds +using DimensionalData.Lookups, DimensionalData.Dimensions +using DimensionalData.Lookups: _slicespan, isrev, _bounds using DimensionalData.Dimensions: _slicedims @testset "locus" begin diff --git a/test/merged.jl b/test/merged.jl index 2d6a38833..6b373a556 100644 --- a/test/merged.jl +++ b/test/merged.jl @@ -1,5 +1,5 @@ using DimensionalData, Test, Unitful -using DimensionalData.LookupArrays, DimensionalData.Dimensions +using DimensionalData.Lookups, DimensionalData.Dimensions using Statistics: mean using DimensionalData.Dimensions: SelOrStandard diff --git a/test/metadata.jl b/test/metadata.jl index ee5a97ece..afd4eaadc 100644 --- a/test/metadata.jl +++ b/test/metadata.jl @@ -1,6 +1,6 @@ using DimensionalData, Test -using DimensionalData.LookupArrays: Metadata, NoMetadata, units, val +using DimensionalData.Lookups: Metadata, NoMetadata, units, val @testset "Metadata" begin nt = (a="test1", units="km") @@ -59,5 +59,5 @@ end end @testset "metadatadict" begin - @test DimensionalData.LookupArrays.metadatadict(Dict("a"=>"A", "b"=>"B")) == Dict(:a=>"A", :b=>"B") + @test DimensionalData.Lookups.metadatadict(Dict("a"=>"A", "b"=>"B")) == Dict(:a=>"A", :b=>"B") end diff --git a/test/methods.jl b/test/methods.jl index 01bb29fd5..a23cd5438 100644 --- a/test/methods.jl +++ b/test/methods.jl @@ -1,5 +1,5 @@ using DimensionalData, Statistics, Test, Unitful, SparseArrays, Dates -using DimensionalData.LookupArrays, DimensionalData.Dimensions +using DimensionalData.Lookups, DimensionalData.Dimensions using LinearAlgebra: Transpose diff --git a/test/predicates.jl b/test/predicates.jl index f8c4714f0..e6a197823 100644 --- a/test/predicates.jl +++ b/test/predicates.jl @@ -1,9 +1,9 @@ using Test, DimensionalData, Dates -using DimensionalData.LookupArrays, DimensionalData.Dimensions +using DimensionalData.Lookups, DimensionalData.Dimensions const DD = DimensionalData -@testset "predicates on LookupArray" begin +@testset "predicates on Lookup" begin @test DD.issampled(Cyclic(1:10; order=ForwardOrdered(), cycle=10)) == true @test DD.issampled(NoLookup()) == false @test DD.issampled(Categorical(1:10)) == false @@ -17,7 +17,7 @@ const DD = DimensionalData @test DD.iscyclic(Categorical(1:10)) == false end -@testset "predicates on LookupArray traits" begin +@testset "predicates on Lookup traits" begin @test DD.isordered(ForwardOrdered()) == true @test DD.isordered(ReverseOrdered()) == true @test DD.isordered(Unordered()) == false diff --git a/test/primitives.jl b/test/primitives.jl index b3fe73a3f..792609ee1 100644 --- a/test/primitives.jl +++ b/test/primitives.jl @@ -1,5 +1,5 @@ using DimensionalData, Dates, Test , BenchmarkTools -using DimensionalData.LookupArrays, DimensionalData.Dimensions +using DimensionalData.Lookups, DimensionalData.Dimensions using .Dimensions: _dim_query, _wraparg, _reducedims, AlwaysTuple, MaybeFirst diff --git a/test/selector.jl b/test/selector.jl index 196816d71..aab5d05e3 100644 --- a/test/selector.jl +++ b/test/selector.jl @@ -1,6 +1,6 @@ using DimensionalData, Test, Unitful, Combinatorics, Dates, IntervalSets, Extents -using DimensionalData.LookupArrays, DimensionalData.Dimensions -using .LookupArrays: between, touches, at, near, contains, bounds, SelectorError, cycle_val +using DimensionalData.Lookups, DimensionalData.Dimensions +using .Lookups: between, touches, at, near, contains, bounds, SelectorError, cycle_val a = [1 2 3 4 5 6 7 8 diff --git a/test/set.jl b/test/set.jl index 0a16d270b..f22916976 100644 --- a/test/set.jl +++ b/test/set.jl @@ -1,7 +1,7 @@ using DimensionalData, Test -using DimensionalData.LookupArrays, DimensionalData.Dimensions +using DimensionalData.Lookups, DimensionalData.Dimensions -using DimensionalData.LookupArrays: _set +using DimensionalData.Lookups: _set a = [1 2; 3 4] dimz = (X(143.0:2.0:145.0; lookup=Sampled(order=ForwardOrdered()), metadata=Metadata(Dict(:meta => "X"))), @@ -118,8 +118,8 @@ end @test set(Dim{:foo}(2:11), :bar) === Dim{:bar}(2:11) @test set(Dim{:foo}(), Dim{:bar}()) === Dim{:bar}() @test set(Dim{:foo}(2:11), Dim{:bar}()) === Dim{:bar}(2:11) - @test set(Dim{:foo}(LookupArrays.Sampled(2:11)), Dim{:bar}(LookupArrays.Sampled(0:9))) === - set(set(Dim{:foo}(LookupArrays.Sampled(2:11)), :bar), LookupArrays.Sampled(0:9)) + @test set(Dim{:foo}(Lookups.Sampled(2:11)), Dim{:bar}(Lookups.Sampled(0:9))) === + set(set(Dim{:foo}(Lookups.Sampled(2:11)), :bar), Lookups.Sampled(0:9)) @test set((Dim{:foo}(),), :foo => :bar) === (Dim{:bar}(),) @test set((Dim{:foo}(2:11),), :foo => :bar) === (Dim{:bar}(2:11),) @test set(dimz, :X => :foo, :Y => :bar) === diff --git a/test/show.jl b/test/show.jl index 68ebfa4ad..3f1666a0a 100644 --- a/test/show.jl +++ b/test/show.jl @@ -1,5 +1,5 @@ using DimensionalData, Test, Dates -using DimensionalData.LookupArrays, DimensionalData.Dimensions +using DimensionalData.Lookups, DimensionalData.Dimensions # define dims with both long name and Type name @dim Lon "Longitude" @@ -37,7 +37,7 @@ end @test occursin(sprint(show, MIME("text/plain"), parent(sl)), sv) sv = sprint(show, MIME("text/plain"), NoLookup()) @test occursin("NoLookup", sv) - # LookupArray tuple + # Lookup tuple ls = lookup(ds) sv = sprint(show, MIME("text/plain"), ls) @test occursin("Categorical", sv) diff --git a/test/tables.jl b/test/tables.jl index 1e026c266..bb452184f 100644 --- a/test/tables.jl +++ b/test/tables.jl @@ -1,6 +1,6 @@ using DimensionalData, IteratorInterfaceExtensions, TableTraits, Tables, Test, DataFrames -using DimensionalData.LookupArrays, DimensionalData.Dimensions +using DimensionalData.Lookups, DimensionalData.Dimensions using DimensionalData: DimTable, DimExtensionArray x = X([:a, :b, :c]) diff --git a/test/utils.jl b/test/utils.jl index 3e70277b3..f5d7a2ea4 100644 --- a/test/utils.jl +++ b/test/utils.jl @@ -1,6 +1,6 @@ using DimensionalData, Test, Dates -using DimensionalData.LookupArrays, DimensionalData.Dimensions -using .LookupArrays: shiftlocus, maybeshiftlocus +using DimensionalData.Lookups, DimensionalData.Dimensions +using .Lookups: shiftlocus, maybeshiftlocus using DimensionalData: uniquekeys @testset "reverse" begin From a20b10d740ed816f4bf7be576e63b3bdec2fd9c2 Mon Sep 17 00:00:00 2001 From: Rafael Schouten Date: Thu, 29 Feb 2024 08:46:48 +0100 Subject: [PATCH 053/108] test `broadcast_dims` for `DimStack` (#657) * add broadcast_dims for stack * test broadcast_dims on DimStack --- src/array/array.jl | 6 +++--- src/dimindices.jl | 5 +++++ src/utils.jl | 2 +- test/utils.jl | 13 +++++++++++++ 4 files changed, 22 insertions(+), 4 deletions(-) diff --git a/src/array/array.jl b/src/array/array.jl index da7e94e35..010051d06 100644 --- a/src/array/array.jl +++ b/src/array/array.jl @@ -241,9 +241,9 @@ end _parent_range(r::Dimensions.DimUnitRange) = parent(r) _parent_range(r) = r # With Dimensions we can return an `AbstractDimArray` -Base.similar(A::AbstractDimArray, D::DimTuple) = Base.similar(A, eltype(A), D) -Base.similar(A::AbstractDimArray, D::Dimension...) = Base.similar(A, eltype(A), D) -Base.similar(A::AbstractDimArray, ::Type{T}, D::Dimension...) where T = +Base.similar(A::AbstractBasicDimArray, D::DimTuple) = Base.similar(A, eltype(A), D) +Base.similar(A::AbstractBasicDimArray, D::Dimension...) = Base.similar(A, eltype(A), D) +Base.similar(A::AbstractBasicDimArray, ::Type{T}, D::Dimension...) where T = Base.similar(A, T, D) Base.similar(A::AbstractDimArray, ::Type{T}, D::DimTuple) where T = rebuild(A; data=similar(parent(A), T, size(D)), dims=D, refdims=(), metadata=NoMetadata()) diff --git a/src/dimindices.jl b/src/dimindices.jl index 89346d044..d53ea06c0 100644 --- a/src/dimindices.jl +++ b/src/dimindices.jl @@ -6,6 +6,11 @@ dims(dg::AbstractDimArrayGenerator) = dg.dims Base.size(dg::AbstractDimArrayGenerator) = map(length, dims(dg)) Base.axes(dg::AbstractDimArrayGenerator) = map(d -> axes(d, 1), dims(dg)) +Base.similar(A::AbstractDimArrayGenerator, ::Type{T}, D::DimTuple) where T = + dimconstructor(D)(A; data=similar(Array{T}, size(D)), dims=D, refdims=(), metadata=NoMetadata()) +Base.similar(A::AbstractDimArrayGenerator, ::Type{T}, D::Tuple{}) where T = + dimconstructor(D)(A; data=similar(Array{T}, ()), dims=(), refdims=(), metadata=NoMetadata()) + # Indexing that returns a new object with the same number of dims for f in (:getindex, :dotview, :view) T = Union{Colon,AbstractRange} diff --git a/src/utils.jl b/src/utils.jl index 28762f184..d2569bcc7 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -129,7 +129,7 @@ function broadcast_dims(f, As::Union{AbstractDimStack,AbstractBasicDimArray}...) layers = map(nts...) do as... broadcast_dims(f, as...) end - rebuild(st, layers) + rebuild_from_arrays(st, layers) end """ diff --git a/test/utils.jl b/test/utils.jl index f5d7a2ea4..40b6dfcf5 100644 --- a/test/utils.jl +++ b/test/utils.jl @@ -172,6 +172,19 @@ end @test broadcast_dims(*, da4, da3) == parent(da4) .* parent(da3) @test dims(broadcast_dims(*, da4, da3)) == dims(da3) end + + @testset "DimStack" begin + A3 = cat([1 2 3; 4 5 6], [11 12 13; 14 15 16]; dims=3) + da3 = DimArray(A3, (X([20, 30]), Y([:a, :b, :c]), Z(10:10:20))) + db1 = DimArray(B1, (Y([:a, :b, :c]),)) + stack1 = DimStack(da3, db1) + stack2 = DimStack(da3, db1, dc3) + @test broadcast_dims(+, stack1, da3, db1) == broadcast_dims(+, da3, db1, stack1) + @test broadcast_dims(+, stack1, da3, db1).layer1 == broadcast_dims(+, stack1.layer1, da3, db1) + @test broadcast_dims(+, stack1, da3, stack1, db1) == broadcast_dims(+, da3, stack1, db1, stack1) + # Cant mix numvers of stack layers + @test_throws ArgumentError broadcast_dims(+, stack1, da3, db1, stack2) + end end @testset "shiftlocus" begin From a4e37a9648bdc18a94b6a9fc03f7ef31b2c41470 Mon Sep 17 00:00:00 2001 From: Rafael Schouten Date: Thu, 29 Feb 2024 08:47:59 +0100 Subject: [PATCH 054/108] dont unwrap zero dim dimarray on dim getindex (#656) * dont unwrap zero dim dimarray on dim getindex * update test --- src/array/indexing.jl | 14 +++++++++++++- test/dimindices.jl | 4 ++-- test/indexing.jl | 7 +++++++ 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/src/array/indexing.jl b/src/array/indexing.jl index fd225af6c..35e507eac 100644 --- a/src/array/indexing.jl +++ b/src/array/indexing.jl @@ -63,7 +63,7 @@ for f in (:getindex, :view, :dotview) # Use underscore methods to minimise ambiguities @propagate_inbounds $_f(A::AbstractBasicDimArray, d1::Dimension, ds::Dimension...) = Base.$f(A, dims2indices(A, (d1, ds...))...) - @propagate_inbounds $_f(A::AbstractBasicDimArray, ds::Dimension...; kw...) = + @propagate_inbounds $_f(A::AbstractBasicDimArray, ds::Dimension...) = Base.$f(A, dims2indices(A, ds)...) @propagate_inbounds function $_f( A::AbstractBasicDimArray, dims::Union{Dimension,DimensionIndsArrays}... @@ -85,6 +85,18 @@ 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 + 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) + return rebuild(A, fill(A[])) + end + @eval @propagate_inbounds function $_f(A::AbstractDimArray{<:Any,0}, d1::Dimension, ds::Dimension...) + Dimensions._extradimswarn((d1, ds...)) + return rebuild(A, fill(A[])) + end + end end diff --git a/test/dimindices.jl b/test/dimindices.jl index 06125f317..b17bad670 100644 --- a/test/dimindices.jl +++ b/test/dimindices.jl @@ -27,8 +27,8 @@ A = zeros(X(4.0:7.0), Y(10.0:12.0)) @test @inferred A1[di] isa DimArray{Float64,3} @test @inferred A1[X=1][di] isa DimArray{Float64,2} @test @inferred A1[X=1, Y=1][di] isa DimArray{Float64,1} - # Indexing with no matching dims is like [] (?) - @test @inferred view(A1, X=1, Y=1, Ti=1)[di] == 0.0 + # Indexing with no matching dims still returns a DimArray + @test @inferred view(A1, X=1, Y=1, Ti=1)[di] == fill(0.0) # Convert to vector of DimTuple @test @inferred A1[di[:]] isa DimArray{Float64,2} diff --git a/test/indexing.jl b/test/indexing.jl index 6dc152035..c6fce19f4 100644 --- a/test/indexing.jl +++ b/test/indexing.jl @@ -396,6 +396,13 @@ end @test all(view(A, I .== 3.0)) end + @testset "zero dim dim getindex doesn't unwrap" begin + A = DimArray(fill(1), ()) + @test A[notadim=1] isa DimArray{Int,0,Tuple{}} + @test A[X(1)] isa DimArray{Int,0,Tuple{}} + @test A[notadim=1] == A[X(1)] == A + end + @testset "Empty getindedex/view/setindex throws a BoundsError" begin @test_throws BoundsError da[] @test_throws BoundsError view(da) From 05fec570a763bd7e4f4afaf3a8efa53cca41e8b0 Mon Sep 17 00:00:00 2001 From: Rafael Schouten Date: Fri, 1 Mar 2024 10:30:49 +0100 Subject: [PATCH 055/108] dont allow broadcast on mixed order (#658) * dont allow broadcast on mixed order * bugfix --- src/Dimensions/predicates.jl | 1 + src/Dimensions/primitives.jl | 8 +++++--- src/Lookups/Lookups.jl | 2 +- src/Lookups/predicates.jl | 2 ++ src/array/broadcast.jl | 7 ++++--- test/broadcast.jl | 16 ++++++++++++++++ 6 files changed, 29 insertions(+), 7 deletions(-) diff --git a/src/Dimensions/predicates.jl b/src/Dimensions/predicates.jl index b137888a0..814d1b2c6 100644 --- a/src/Dimensions/predicates.jl +++ b/src/Dimensions/predicates.jl @@ -1,4 +1,5 @@ for f in ( + :isnolookup, :issampled, :iscategorical, :iscyclic, diff --git a/src/Dimensions/primitives.jl b/src/Dimensions/primitives.jl index d095b9011..7976f8bbc 100644 --- a/src/Dimensions/primitives.jl +++ b/src/Dimensions/primitives.jl @@ -565,7 +565,9 @@ function comparedims end type && basetypeof(a) != basetypeof(b) && _dimsmismatcherror(a, b) valtype && typeof(parent(a)) != typeof(parent(b)) && _valtypeerror(a, b) val && parent(a) != parent(b) && _valerror(a, b) - order && LA.order(a) != LA.order(b) && _ordererror(a, b) + if order + (isnolookup(a) || isnolookup(b) || LA.order(a) == LA.order(b)) || _ordererror(a, b) + end if ignore_length_one && (Base.length(a) == 1 || Base.length(b) == 1) return Base.length(b) == 1 ? a : b end @@ -604,8 +606,8 @@ end isnothing(warn) || _valwarn(a, b, warn) return false end - if order && Lookups.order(a) != Lookups.order(b) - isnothing(warn) || _orderwarn(a, b, warn) + if order && !(isnolookup(a) || isnolookup(b) || LA.order(a) == LA.order(b)) + _orderwarn(a, b, warn) return false end if ignore_length_one && (Base.length(a) == 1 || Base.length(b) == 1) diff --git a/src/Lookups/Lookups.jl b/src/Lookups/Lookups.jl index c13768ee1..3968e90d0 100644 --- a/src/Lookups/Lookups.jl +++ b/src/Lookups/Lookups.jl @@ -29,7 +29,7 @@ export order, sampling, span, bounds, locus, hasselection, transformdim, metadata, units, sort, selectindices, val, index, reducelookup, shiftlocus, maybeshiftlocus, intervalbounds -export issampled, iscategorical, iscyclic, isintervals, ispoints, isregular, +export issampled, iscategorical, iscyclic, isnolookup, isintervals, ispoints, isregular, isexplicit, isstart, iscenter, isend, isordered, isforward, isreverse export Selector diff --git a/src/Lookups/predicates.jl b/src/Lookups/predicates.jl index 58059609b..2459ab890 100644 --- a/src/Lookups/predicates.jl +++ b/src/Lookups/predicates.jl @@ -9,6 +9,8 @@ iscategorical(::AbstractCategorical) = true iscategorical(::Lookup) = false iscyclic(::AbstractCyclic) = true iscyclic(::Lookup) = false +isnolookup(::Lookup) = false +isnolookup(::NoLookup) = true isstart(::Start) = true isstart(::Locus) = false iscenter(::Center) = true diff --git a/src/array/broadcast.jl b/src/array/broadcast.jl index daedc354c..23d707bac 100644 --- a/src/array/broadcast.jl +++ b/src/array/broadcast.jl @@ -47,7 +47,7 @@ function Broadcast.copy(bc::Broadcasted{DimensionalStyle{S}}) where S end function Base.copyto!(dest::AbstractArray, bc::Broadcasted{DimensionalStyle{S}}) where S - _dims = comparedims(dims(dest), _broadcasted_dims(bc); ignore_length_one=true) + _dims = comparedims(dims(dest), _broadcasted_dims(bc); ignore_length_one=true, order=true) copyto!(dest, _unwrap_broadcasted(bc)) A = _firstdimarray(bc) if A isa Nothing || _dims isa Nothing @@ -57,7 +57,7 @@ function Base.copyto!(dest::AbstractArray, bc::Broadcasted{DimensionalStyle{S}}) end end function Base.copyto!(dest::AbstractDimArray, bc::Broadcasted{DimensionalStyle{S}}) where S - _dims = comparedims(dims(dest), _broadcasted_dims(bc); ignore_length_one=true) + _dims = comparedims(dims(dest), _broadcasted_dims(bc); ignore_length_one=true, order=true) copyto!(parent(dest), _unwrap_broadcasted(bc)) A = _firstdimarray(bc) if A isa Nothing || _dims isa Nothing @@ -99,6 +99,7 @@ _firstdimarray(x::Tuple{}) = nothing # Make sure all arrays have the same dims, and return them _broadcasted_dims(bc::Broadcasted) = _broadcasted_dims(bc.args...) -_broadcasted_dims(a, bs...) = comparedims(_broadcasted_dims(a), _broadcasted_dims(bs...); ignore_length_one=true) +_broadcasted_dims(a, bs...) = + comparedims(_broadcasted_dims(a), _broadcasted_dims(bs...); ignore_length_one=true, order=true) _broadcasted_dims(a::AbstractDimArray) = dims(a) _broadcasted_dims(a) = nothing diff --git a/test/broadcast.jl b/test/broadcast.jl index 372b74d68..4c1b29c89 100644 --- a/test/broadcast.jl +++ b/test/broadcast.jl @@ -51,6 +51,22 @@ end @test (rand(3) .> 1 .> 0 .* da) isa DimArray end +@testset "trailng dimensions" begin + @test zeros(X(10), Y(5)) .* zeros(X(10), Y(1)) == + zeros(X(10), Y(5)) .* zeros(X(1), Y(1)) == + zeros(X(1), Y(1)) .* zeros(X(10), Y(5)) == + zeros(X(10), Y(5)) .* zeros(X(1), Y(5)) == + zeros(X(10), Y(1)) .* zeros(X(1), Y(5)) == + zeros(X(10), Y(5)) .* zeros(X(1)) == + zeros(X(1), Y(5)) .* zeros(X(10)) +end + +@testset "mixed order fails" begin + @test_throws DimensionMismatch zeros(X(1:3), Y(5)) .* zeros(X(3:-1:1), Y(5)) + @test_throws DimensionMismatch zeros(X([1, 3, 2]), Y(5)) .* zeros(X(3:-1:1), Y(5)) + zeros(X([1, 3, 2]), Y(5)) .* zeros(X(3), Y(5)) +end + @testset "broadcasting" begin v = DimArray(zeros(3,), X) m = DimArray(ones(3, 3), (X, Y)) From 2b9f102ad3a4f866e250207248904edc605ce15c Mon Sep 17 00:00:00 2001 From: Rafael Schouten Date: Sun, 3 Mar 2024 18:34:37 +0100 Subject: [PATCH 056/108] delete unused dimsmatch complexity (#619) * delete unused match complexity * broken * fix Transformed with AutoDim * fix * fix doctest * fix show test * bugfix * fix primitives test * fix lookup test --- src/Dimensions/format.jl | 5 +++-- src/Dimensions/primitives.jl | 9 ++------- src/Lookups/lookup_arrays.jl | 14 ++++++++------ src/Lookups/lookup_traits.jl | 1 + test/lookup.jl | 5 +---- test/primitives.jl | 3 --- test/selector.jl | 27 ++++++++------------------- test/show.jl | 3 +-- 8 files changed, 24 insertions(+), 43 deletions(-) diff --git a/src/Dimensions/format.jl b/src/Dimensions/format.jl index ef783147e..ae92e79ae 100644 --- a/src/Dimensions/format.jl +++ b/src/Dimensions/format.jl @@ -81,8 +81,9 @@ function format(m::AbstractSampled, D::Type, index, axis::AbstractRange) return x end # # Transformed -format(m::Transformed, D::Type, index::AutoIndex, axis::AbstractRange) = rebuild(m; data=axis) -format(m::Transformed, D::Type, index, axis::AbstractRange) = m +format(m::Transformed, D::Type, index::AutoIndex, axis::AbstractRange) = + rebuild(m; dim=D(), data=axis) +format(m::Transformed, D::Type, index, axis::AbstractRange) = rebuild(m; dim=D()) # Index _format(index::AbstractArray, axis::AbstractRange) = index diff --git a/src/Dimensions/primitives.jl b/src/Dimensions/primitives.jl index 7976f8bbc..d678bf36b 100644 --- a/src/Dimensions/primitives.jl +++ b/src/Dimensions/primitives.jl @@ -24,13 +24,8 @@ end @inline dimsmatch(f::Function, dim, query::Nothing) = false @inline dimsmatch(f::Function, dim::Nothing, query) = false @inline dimsmatch(f::Function, dim::Nothing, query::Nothing) = false -@inline function dimsmatch(f::Function, dim::Type{D}, match::Type{M}) where {D,M} - # Compare regular dimension types - f(basetypeof(unwrap(D)), basetypeof(unwrap(M))) || - # Compare the transformed dimensions, if they exist - f(basetypeof(unwrap(D)), basetypeof(transformdim(lookuptype(unwrap(M))))) || - f(basetypeof(transformdim(lookuptype(unwrap(D)))), basetypeof(unwrap(M))) -end +@inline dimsmatch(f::Function, dim::Type{D}, match::Type{M}) where {D,M} = + f(basetypeof(unwrap(D)), basetypeof(unwrap(M))) """ key2dim(s::Symbol) => Dimension diff --git a/src/Lookups/lookup_arrays.jl b/src/Lookups/lookup_arrays.jl index 90f8f717f..eb2837e73 100644 --- a/src/Lookups/lookup_arrays.jl +++ b/src/Lookups/lookup_arrays.jl @@ -557,9 +557,9 @@ m = LinearMap([0.5 0.0; 0.0 0.5]) A = [1 2 3 4 5 6 7 8 9 10 11 12]; -da = DimArray(A, (t1=Transformed(m, X), t2=Transformed(m, Y))) +da = DimArray(A, (X(Transformed(m)), Y(Transformed(m)))) -da[X(At(6)), Y(At(2))] +da[X(At(6.0)), Y(At(2.0))] # output 9 @@ -571,17 +571,19 @@ struct Transformed{T,A<:AbstractVector{T},F,D,M} <: Unaligned{T,1} dim::D metadata::M end -function Transformed(f, dim; metadata=NoMetadata()) - Transformed(AutoIndex(), f, basetypeof(dim)(), metadata) +function Transformed(f; metadata=NoMetadata()) + Transformed(AutoIndex(), f, AutoDim(), metadata) +end +function Transformed(f, data::AbstractArray; metadata=NoMetadata()) + Transformed(data, f, AutoDim(), metadata) end function rebuild(l::Transformed; - data=parent(l), f=f(l), dim=dim(l), metadata=metadata(l) + data=parent(l), f=transformfunc(l), dim=dim(l), metadata=metadata(l) ) Transformed(data, f, dim, metadata) end -f(lookup::Transformed) = lookup.f dim(lookup::Transformed) = lookup.dim transformfunc(lookup::Transformed) = lookup.f diff --git a/src/Lookups/lookup_traits.jl b/src/Lookups/lookup_traits.jl index d25eade3d..176f3805d 100644 --- a/src/Lookups/lookup_traits.jl +++ b/src/Lookups/lookup_traits.jl @@ -194,6 +194,7 @@ struct AutoSpan <: Span end struct AutoStep end struct AutoBounds end +struct AutoDim end """ Regular <: Span diff --git a/test/lookup.jl b/test/lookup.jl index db160df38..0cb898c5c 100644 --- a/test/lookup.jl +++ b/test/lookup.jl @@ -271,9 +271,6 @@ end end @testset "dims2indices with Transformed" begin - tdimz = Dim{:trans1}(Transformed(identity, X())), - Dim{:trans2}(Transformed(identity, Y())), - Z(NoLookup(1:1)) + tdimz = X(Transformed(identity)), Y(Transformed(identity)), Z(NoLookup(1:1)) @test dims2indices(tdimz, (X(1), Y(2), Z())) == (1, 2, Colon()) - @test dims2indices(tdimz, (Dim{:trans1}(1), Dim{:trans2}(2), Z())) == (1, 2, Colon()) end diff --git a/test/primitives.jl b/test/primitives.jl index 792609ee1..454aa07db 100644 --- a/test/primitives.jl +++ b/test/primitives.jl @@ -114,9 +114,6 @@ end # Val @inferred sortdims(dimz, (Val{Y}(), Val{Ti}(), Val{Z}(), Val{X}())) @test (@ballocated sortdims($dimz, (Val{Y}(), Val{Ti}(), Val{Z}(), Val{X}()))) == 0 - # Transformed - @test @inferred (sortdims((Y(Transformed(identity, Z())), X(1)), (X(), Z()))) == - (X(1), Y(Transformed(identity, Z()))) # Abstract @test sortdims((Z(), Y(), X()), (XDim, TimeDim)) == (X(), nothing) # Repeating diff --git a/test/selector.jl b/test/selector.jl index aab5d05e3..db65eccb6 100644 --- a/test/selector.jl +++ b/test/selector.jl @@ -1320,38 +1320,27 @@ end using CoordinateTransformations m = LinearMap([0.5 0.0; 0.0 0.5]) - - dimz = Dim{:trans1}(Transformed(m, X())), - Dim{:trans2}(Transformed(m, Y())), Z() - @test DimensionalData.dimsmatch(dimz[1], X()) - @test DimensionalData.dimsmatch(dimz[2], Y()) - - @testset "permutedims works on lookup dimensions" begin - # @test @inferred sortdims((Y(), Z(), X()), dimz) == (X(), Y(), Z()) - end - + dimz = X(Transformed(m)), Y(Transformed(m)), Z() da = DimArray(reshape(a, 3, 4, 1), dimz) view(da, :, :, 1) + @testset "AutoDim attachs the dimension to " begin + Lookups.dim(lookup(da, X)) + end + @testset "Indexing with array dims indexes the array as usual" begin - da2 = da[1:3, 1:1, 1:1] - @test @inferred da2[Dim{:trans1}(3), Dim{:trans2}(1), Z(1)] == 9 - # Using selectors works the same as indexing with lookup - # dims - it applies the transform function. - # It's not clear this should be allowed or makes sense, - # but it works anyway because the permutation is correct either way. - @test @inferred da[Dim{:trans1}(At(6)), Dim{:trans2}(At(2)), Z(1)] == 9 + da2 = da[1:3, 1:1, 1:1]; + @test @inferred da2[X(3), Y(1), Z(1)] == 9 end @testset "Indexing with lookup dims uses the transformation" begin @test @inferred da[X(Near(6.1)), Y(Near(8.5)), Z(1)] == 12 @test @inferred da[X(At(4.0)), Y(At(2.0)), Z(1)] == 5 - @test_throws ArgumentError da[trans1=At(4.0)] + @test_throws ArgumentError da[X=At(4.0)] @test_throws InexactError da[X(At(6.1)), Y(At(8)), Z(1)] # Indexing directly with lookup dims also just works, but maybe shouldn't? @test @inferred da[X(2), Y(2), Z(1)] == 6 end - end @testset "Cyclic lookup" begin diff --git a/test/show.jl b/test/show.jl index 3f1666a0a..cd97ade5d 100644 --- a/test/show.jl +++ b/test/show.jl @@ -42,9 +42,8 @@ end sv = sprint(show, MIME("text/plain"), ls) @test occursin("Categorical", sv) @test occursin("Sampled", sv) - sv = sprint(show, MIME("text/plain"), Transformed(identity, X())) + sv = sprint(show, MIME("text/plain"), Transformed(identity)) @test occursin("Transformed", sv) - @test occursin("X", sv) nds = (X(NoLookup(Base.OneTo(10))), Y(NoLookup(Base.OneTo(5)))) sv = sprint(show, MIME("text/plain"), nds) @test sv == "↓ X, → Y" From 5cb7e07db7b029782ff3e4421cae1ae5095c77f1 Mon Sep 17 00:00:00 2001 From: Rafael Schouten Date: Tue, 5 Mar 2024 20:15:17 +0100 Subject: [PATCH 057/108] printing and interface fixes (#659) * printing and interface fixes for Rasters.jl * export layers * tweaks * only convert base time types * fix exports * bugfix Date --- src/DimensionalData.jl | 4 +- src/Dimensions/Dimensions.jl | 3 +- src/Dimensions/primitives.jl | 4 +- src/Lookups/Lookups.jl | 9 +++-- src/Lookups/lookup_arrays.jl | 5 +-- src/Lookups/selector.jl | 6 +-- src/array/show.jl | 19 +++++---- src/stack/stack.jl | 74 ++++++++++++++++++++++-------------- 8 files changed, 73 insertions(+), 51 deletions(-) diff --git a/src/DimensionalData.jl b/src/DimensionalData.jl index fa5c7a231..2ea87fff9 100644 --- a/src/DimensionalData.jl +++ b/src/DimensionalData.jl @@ -10,7 +10,7 @@ using Dates, using Base.Broadcast: Broadcasted, BroadcastStyle, DefaultArrayStyle, AbstractArrayStyle, Unknown -using Base: tail, OneTo, Callable, @propagate_inbounds +using Base: tail, OneTo, Callable, @propagate_inbounds, @assume_effects # Ecosystem import Adapt, @@ -67,7 +67,7 @@ export AbstractDimTable, DimTable export DimIndices, DimSelectors, DimPoints, #= deprecated =# DimKeys # getter methods -export dims, refdims, metadata, name, lookup, bounds, val +export dims, refdims, metadata, name, lookup, bounds, val, layers # Dimension/Lookup primitives export dimnum, hasdim, hasselection, otherdims diff --git a/src/Dimensions/Dimensions.jl b/src/Dimensions/Dimensions.jl index 80cc5bd87..cfe6d216b 100644 --- a/src/Dimensions/Dimensions.jl +++ b/src/Dimensions/Dimensions.jl @@ -20,7 +20,8 @@ include("../Lookups/Lookups.jl") using .Lookups -const LA = Lookups +const LU = Lookups +const LookupArrays = Lookups import .Lookups: rebuild, order, span, sampling, locus, val, index, set, _set, metadata, bounds, intervalbounds, units, basetypeof, unwrap, selectindices, hasselection, diff --git a/src/Dimensions/primitives.jl b/src/Dimensions/primitives.jl index d678bf36b..3f5a9584e 100644 --- a/src/Dimensions/primitives.jl +++ b/src/Dimensions/primitives.jl @@ -561,7 +561,7 @@ function comparedims end valtype && typeof(parent(a)) != typeof(parent(b)) && _valtypeerror(a, b) val && parent(a) != parent(b) && _valerror(a, b) if order - (isnolookup(a) || isnolookup(b) || LA.order(a) == LA.order(b)) || _ordererror(a, b) + (isnolookup(a) || isnolookup(b) || LU.order(a) == LU.order(b)) || _ordererror(a, b) end if ignore_length_one && (Base.length(a) == 1 || Base.length(b) == 1) return Base.length(b) == 1 ? a : b @@ -601,7 +601,7 @@ end isnothing(warn) || _valwarn(a, b, warn) return false end - if order && !(isnolookup(a) || isnolookup(b) || LA.order(a) == LA.order(b)) + if order && !(isnolookup(a) || isnolookup(b) || LU.order(a) == LU.order(b)) _orderwarn(a, b, warn) return false end diff --git a/src/Lookups/Lookups.jl b/src/Lookups/Lookups.jl index 3968e90d0..8b1e55362 100644 --- a/src/Lookups/Lookups.jl +++ b/src/Lookups/Lookups.jl @@ -25,9 +25,9 @@ import InvertedIndices using InvertedIndices: Not using Base: tail, OneTo, @propagate_inbounds -export order, sampling, span, bounds, locus, hasselection, transformdim, - metadata, units, sort, selectindices, val, index, reducelookup, shiftlocus, - maybeshiftlocus, intervalbounds +export order, sampling, span, bounds, locus, hasselection, dim, + metadata, units, sort, selectindices, val, index, reducelookup, + shiftlocus, maybeshiftlocus, intervalbounds export issampled, iscategorical, iscyclic, isnolookup, isintervals, ispoints, isregular, isexplicit, isstart, iscenter, isend, isordered, isforward, isreverse @@ -50,6 +50,9 @@ export AutoLookup, NoLookup export Aligned, AbstractSampled, Sampled, AbstractCyclic, Cyclic, AbstractCategorical, Categorical export Unaligned, Transformed +# Deprecated +export LookupArray + const StandardIndices = Union{AbstractArray{<:Integer},Colon,Integer,CartesianIndex,CartesianIndices} # As much as possible keyword rebuild is automatic diff --git a/src/Lookups/lookup_arrays.jl b/src/Lookups/lookup_arrays.jl index eb2837e73..52459b2fc 100644 --- a/src/Lookups/lookup_arrays.jl +++ b/src/Lookups/lookup_arrays.jl @@ -11,7 +11,7 @@ or a [`Sampled`](@ref) index for [`Points`](@ref) or [`Intervals`](@ref). """ abstract type Lookup{T,N} <: AbstractArray{T,N} end - +const LookupArray = Lookup const LookupTuple = Tuple{Lookup,Vararg{Lookup}} span(lookup::Lookup) = NoSpan() @@ -587,9 +587,6 @@ end dim(lookup::Transformed) = lookup.dim transformfunc(lookup::Transformed) = lookup.f -transformdim(x) = nothing -transformdim(lookup::Transformed) = lookup.dim -transformdim(::Type{<:Transformed{<:Any,<:Any,<:Any,D}}) where D = D Base.:(==)(l1::Transformed, l2::Transformed) = typeof(l1) == typeof(l2) && f(l1) == f(l2) diff --git a/src/Lookups/selector.jl b/src/Lookups/selector.jl index ae9a7e5e6..bf507232d 100644 --- a/src/Lookups/selector.jl +++ b/src/Lookups/selector.jl @@ -267,10 +267,10 @@ function near(lookup::Lookup, sel::Near) end near(order::Order, ::NoSampling, lookup::Lookup, sel::Near) = at(lookup, At(val(sel))) function near(order::Ordered, ::Union{Intervals,Points}, lookup::Lookup, sel::Near) - # Unwrap the selector value and adjust it for - # interval locus if neccessary + # Unwrap the selector value and adjust it for interval locus if neccessary v = unwrap(val(sel)) - if v isa Dates.TimeType + # Allow Date and DateTime to be used interchangeably + if v isa Union{Dates.DateTime,Dates.Date} v = eltype(lookup)(v) end v_adj = _locus_adjust(locus(lookup), v, lookup) diff --git a/src/array/show.jl b/src/array/show.jl index 35178de4f..25ea992f6 100644 --- a/src/array/show.jl +++ b/src/array/show.jl @@ -36,9 +36,7 @@ But read the DimensionalData.jl `show.jl` code for details. """ function show_main(io, mime, A::AbstractBasicDimArray) lines_t, blockwidth, displaywidth = print_top(io, mime, A) - lines_m, blockwidth = print_metadata_block(io, mime, metadata(A); - blockwidth, displaywidth=min(displaywidth, blockwidth) - ) + lines_m, blockwidth = print_metadata_block(io, mime, metadata(A); blockwidth, displaywidth) return lines_t + lines_m, blockwidth end @@ -143,7 +141,7 @@ function print_metadata_block(io, mime, metadata; blockwidth=0, displaywidth) new_blockwidth = blockwidth else metadata_lines = split(sprint(show, mime, metadata), "\n") - new_blockwidth = min(displaywidth-2, max(blockwidth, maximum(length, metadata_lines))) + new_blockwidth = min(displaywidth-2, max(blockwidth, maximum(length, metadata_lines) + 4)) print_block_separator(io, "metadata", blockwidth, new_blockwidth) println(io) print(io, " ") @@ -173,10 +171,15 @@ function print_block_top(io, label, prev_width, new_width) return lines end -function print_block_separator(io, label, prev_width, new_width) - corner = (new_width > prev_width) ? '┐' : '┤' - middle_line = string('├', '─'^max(0, new_width - textwidth(label) - 2), ' ', label, ' ', corner) - printstyled(io, middle_line; color=:light_black) +function print_block_separator(io, label, prev_width, new_width=prev_width) + if new_width > prev_width + line = string('├', '─'^max(0, prev_width), '┴', '─'^max(0, new_width - prev_width - textwidth(label) - 3) ) + corner = '┐' + else + line = string('├', '─'^max(0, new_width - textwidth(label) - 2)) + corner = '┤' + end + printstyled(io, string(line, ' ', label, ' ', corner); color=:light_black) end function print_block_close(io, blockwidth) diff --git a/src/stack/stack.jl b/src/stack/stack.jl index 63fd02c9b..8caeb469c 100644 --- a/src/stack/stack.jl +++ b/src/stack/stack.jl @@ -9,10 +9,10 @@ Notably, their behaviour lies somewhere between a `DimArray` and a `NamedTuple`: - indexing with a `Symbol` as in `dimstack[:symbol]` returns a `DimArray` layer. - iteration amd `map` are apply over array layers, as indexed with a `Symbol`. -- `getindex` and many base methods are applied as for `DimArray` - to avoid the need +- `getindex` and many base methods are applied as for `DimArray` - to avoid the need to allways use `map`. -This design gives very succinct code when working with many-layered, mixed-dimension objects. +This design gives very succinct code when working with many-layered, mixed-dimension objects. But it may be jarring initially - the most surprising outcome is that `dimstack[1]` will return a `NamedTuple` of values for the first index in all layers, while `first(dimstack)` will return the first value of the iterator - the `DimArray` for the first layer. @@ -20,7 +20,7 @@ the first value of the iterator - the `DimArray` for the first layer. See [`DimStack`](@ref) for the concrete implementation. Most methods are defined on the abstract type. -To extend `AbstractDimStack`, implement argument and keyword version of +To extend `AbstractDimStack`, implement argument and keyword version of [`rebuild`](@ref) and also [`rebuild_from_arrays`](@ref). The constructor of an `AbstractDimStack` must accept a `NamedTuple`. @@ -38,13 +38,14 @@ layermetadata(s::AbstractDimStack) = getfield(s, :layermetadata) layermetadata(s::AbstractDimStack, key::Symbol) = layermetadata(s)[key] layers(nt::NamedTuple) = nt -Base.@assume_effects :foldable function layers(s::AbstractDimStack{<:NamedTuple{Keys}}) where Keys +@assume_effects :foldable layers(s::AbstractDimStack{<:NamedTuple{Keys}}) where Keys = NamedTuple{Keys}(map(K -> s[K], Keys)) -end +@assume_effects :foldable DD.layers(s::AbstractDimStack, i::Integer) = s[keys(s)[i]] +@assume_effects :foldable DD.layers(s::AbstractDimStack, k::Symbol) = s[k] const DimArrayOrStack = Union{AbstractDimArray,AbstractDimStack} -Base.@assume_effects :foldable function hassamedims(s::AbstractDimStack) +@assume_effects :foldable function hassamedims(s::AbstractDimStack) all(map(==(first(layerdims(s))), layerdims(s))) end @@ -130,44 +131,61 @@ Base.similar(s::AbstractDimStack, args...) = map(A -> similar(A, args...), s) Base.eltype(s::AbstractDimStack, args...) = NamedTuple{keys(s),Tuple{map(eltype, s)...}} Base.CartesianIndices(s::AbstractDimStack) = CartesianIndices(dims(s)) Base.LinearIndices(s::AbstractDimStack) = LinearIndices(CartesianIndices(map(l -> axes(l, 1), lookup(s)))) -function Base.eachindex(s::AbstractDimStack) +function Base.eachindex(s::AbstractDimStack) li = LinearIndices(s) first(li):last(li) end # all of methods.jl is also Array-like... # NamedTuple-like -Base.@assume_effects :foldable Base.getproperty(s::AbstractDimStack, x::Symbol) = s[x] +@assume_effects :foldable Base.getproperty(s::AbstractDimStack, x::Symbol) = s[x] Base.haskey(s::AbstractDimStack, k) = k in keys(s) Base.values(s::AbstractDimStack) = values(layers(s)) +Base.checkbounds(s::AbstractDimStack, I...) = checkbounds(CartesianIndices(s), I...) +Base.checkbounds(T::Type, s::AbstractDimStack, I...) = checkbounds(T, CartesianIndices(s), I...) @inline Base.keys(s::AbstractDimStack) = keys(data(s)) @inline Base.propertynames(s::AbstractDimStack) = keys(data(s)) +Base.setindex(s::AbstractDimStack, val::AbstractBasicDimArray, key) = + rebuild_from_arrays(s, Base.setindex(layers(s), val, key)) +Base.NamedTuple(s::AbstractDimStack) = NamedTuple(layers(s)) + # Remove these, but explain Base.iterate(::AbstractDimStack, args...) = error("Use iterate(layers(s)) rather than `iterate(s)`") #iterate(layers(s), args...) -Base.length(::AbstractDimStack) = error("Use length(layers(s)) rather than `length(s)`") # length(keys(s)) +Base.length(::AbstractDimStack) = error("Use length(layers(s)) rather than `length(s)`") # length(keys(s)) Base.first(::AbstractDimStack) = error("Use first(layers(s)) rather than `first(s)`") Base.last(::AbstractDimStack) = error("Use last(layers(s)) rather than `last(s)`") + # `merge` for AbstractDimStack and NamedTuple. # One of the first three arguments must be an AbstractDimStack for dispatch to work. Base.merge(s::AbstractDimStack) = s -function Base.merge(x1::AbstractDimStack, x2::Union{AbstractDimStack,NamedTuple}, xs::Union{AbstractDimStack,NamedTuple}...) - rebuild_from_arrays(x1, merge(map(layers, (x1, x2, xs...))...); refdims=()) +function Base.merge( + x1::AbstractDimStack, + x2::Union{AbstractDimStack,NamedTuple}, + xs::Union{AbstractDimStack,NamedTuple}...; + kw... +) + rebuild_from_arrays(x1, merge(map(layers, (x1, x2, xs...))...); kw...) end -function Base.merge(s::AbstractDimStack, pairs) +function Base.merge(s::AbstractDimStack, pairs; kw...) rebuild_from_arrays(s, merge(layers(s), pairs); refdims=()) end -function Base.merge(x1::NamedTuple, x2::AbstractDimStack, xs::Union{AbstractDimStack,NamedTuple}...) +function Base.merge( + x1::NamedTuple, x2::AbstractDimStack, xs::Union{AbstractDimStack,NamedTuple}...; +) merge(map(layers, (x1, x2, xs...))...) end -function Base.merge(x1::NamedTuple, x2::NamedTuple, x3::AbstractDimStack, xs::Union{AbstractDimStack,NamedTuple}...) +function Base.merge( + x1::NamedTuple, x2::NamedTuple, x3::AbstractDimStack, + xs::Union{AbstractDimStack,NamedTuple}...; + kw... +) merge(map(layers, (x1, x2, x3, xs...))...) end -function Base.setindex(s::AbstractDimStack, val::AbstractBasicDimArray, key) - rebuild_from_arrays(s, Base.setindex(layers(s), val, key)) -end -Base.NamedTuple(s::AbstractDimStack) = NamedTuple(layers(s)) + Base.map(f, s::AbstractDimStack) = _maybestack(s,map(f, values(s))) -function Base.map(f, x1::Union{AbstractDimStack,NamedTuple}, xs::Union{AbstractDimStack,NamedTuple}...) +function Base.map( + f, x1::Union{AbstractDimStack,NamedTuple}, xs::Union{AbstractDimStack,NamedTuple}... +) stacks = (x1, xs...) _check_same_names(stacks...) vals = map(f, map(values, stacks)...) @@ -176,10 +194,10 @@ end # Other interfaces -Extents.extent(A::AbstractDimStack, args...) = Extents.extent(dims(A), args...) +Extents.extent(A::AbstractDimStack, args...) = Extents.extent(dims(A), args...) ConstructionBase.getproperties(s::AbstractDimStack) = layers(s) -ConstructionBase.setproperties(s::AbstractDimStack, patch::NamedTuple) = +ConstructionBase.setproperties(s::AbstractDimStack, patch::NamedTuple) = ConstructionBase.constructorof(typeof(s))(ConstructionBase.setproperties(layers(s), patch)) Adapt.adapt_structure(to, s::AbstractDimStack) = map(A -> Adapt.adapt(to, A), s) @@ -191,7 +209,7 @@ function mergedims(st::AbstractDimStack, dim_pairs::Pair...) isempty(dim_pairs) && return st # Extend missing dimensions in all layers extended_layers = map(layers(st)) do layer - if all(map((ds...) -> all(hasdim(layer, ds)), map(first, dim_pairs))) + if all(map((ds...) -> all(hasdim(layer, ds)), map(first, dim_pairs))) layer else DimExtensionArray(layer, dims(st)) @@ -218,13 +236,13 @@ function _layerkeysfromdim(A, dim) end end -_check_same_names(::Union{AbstractDimStack{<:NamedTuple{names}},NamedTuple{names}}, +_check_same_names(::Union{AbstractDimStack{<:NamedTuple{names}},NamedTuple{names}}, ::Union{AbstractDimStack{<:NamedTuple{names}},NamedTuple{names}}...) where {names} = nothing -_check_same_names(::Union{AbstractDimStack,NamedTuple}, ::Union{AbstractDimStack,NamedTuple}...) = +_check_same_names(::Union{AbstractDimStack,NamedTuple}, ::Union{AbstractDimStack,NamedTuple}...) = throw(ArgumentError("Named tuple names do not match.")) _firststack(s::AbstractDimStack, args...) = s -_firststack(arg1, args...) = _firststack(args...) +_firststack(arg1, args...) = _firststack(args...) _firststack() = nothing _maybestack(s::AbstractDimStack{<:NamedTuple{K}}, xs::Tuple) where K = NamedTuple{K}(xs) @@ -262,7 +280,7 @@ Notably, their behaviour lies somewhere between a `DimArray` and a `NamedTuple`: return a `NamedTuple` of values from each layer in the stack. This has very good performace, and avoids the need to always use `map`. - `getindex` or `view` with a `Vector` or `Colon` will return another `DimStack` where - all data layers have been sliced. + all data layers have been sliced. - `setindex!` must pass a `Tuple` or `NamedTuple` maching the layers. - many base and `Statistics` methods (`sum`, `mean` etc) will work as for a `DimArray` again removing the need to use `map`. @@ -283,7 +301,7 @@ And this equivalent to: map(A -> mean(A; dims=Ti), mydimstack) ``` -This design gives succinct code when working with many-layered, mixed-dimension objects. +This design gives succinct code when working with many-layered, mixed-dimension objects. But it may be jarring initially - the most surprising outcome is that `dimstack[1]` will return a `NamedTuple` of values for the first index in all layers, while `first(dimstack)` will return @@ -373,7 +391,7 @@ end # Same sized arrays DimStack(data::NamedTuple, dim::Dimension; kw...) = DimStack(data::NamedTuple, (dim,); kw...) function DimStack(data::NamedTuple, dims::Tuple; - refdims=(), metadata=NoMetadata(), + refdims=(), metadata=NoMetadata(), layermetadata=map(_ -> NoMetadata(), data), layerdims = map(_ -> basedims(dims), data), ) From 9846bfe6d123b93486983064fb4f211ab951f82a Mon Sep 17 00:00:00 2001 From: Rafael Schouten Date: Tue, 5 Mar 2024 20:56:44 +0100 Subject: [PATCH 058/108] fix 1d mapslices (#661) --- src/array/methods.jl | 2 +- test/methods.jl | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/array/methods.jl b/src/array/methods.jl index 4ec87bf88..22726b59f 100644 --- a/src/array/methods.jl +++ b/src/array/methods.jl @@ -109,7 +109,7 @@ end ds = DD.dims(A, _astuple(dims)) # Run one slice with dimensions to get the transformed dim d_inds = map(d -> rebuild(d, 1), otherdims(A, ds)) - example_dims = DD.dims(f(view(A, d_inds...))) + example_dims = length(d_inds) > 0 ? DD.dims(f(view(A, d_inds...))) : () replacement_dims = if isnothing(example_dims) || length(example_dims) != length(ds) map(d -> rebuild(d, NoLookup()), ds) else diff --git a/test/methods.jl b/test/methods.jl index a23cd5438..5d1750abd 100644 --- a/test/methods.jl +++ b/test/methods.jl @@ -366,6 +366,12 @@ end @test size(y) == size(dims(y)) @test dims(y) == dims(x[2:9, :, :]) end + + @testset "single dim" begin + x = X(1:10) + A = DimArray(ones(10), x) + @test mapslices(sum, A; dims=X) == fill(10.0, X(1)) + end end @testset "cumsum" begin From 338fa266607d874b87f1383a4daa3b1e41e80de9 Mon Sep 17 00:00:00 2001 From: Rafael Schouten Date: Sat, 9 Mar 2024 22:07:18 +0100 Subject: [PATCH 059/108] add Begin End ranges (#585) * add begin end ranges * lazy math * reordanise * tests and fixes for lookups and dimensions * beginend * fix stack indexing stability * test Begin ENd * allow nightly failure * continue-on-error * bugfix * some renaming * add Begin End docs * locus in docs --- .github/workflows/ci.yml | 3 + docs/src/api/lookuparrays.md | 10 +-- docs/src/dimarrays.md | 17 +++++ src/DimensionalData.jl | 4 +- src/Dimensions/Dimensions.jl | 5 +- src/Dimensions/dimension.jl | 24 +++--- src/Dimensions/format.jl | 110 +++++++++++++------------- src/Dimensions/indexing.jl | 15 ++-- src/Lookups/Lookups.jl | 14 ++-- src/Lookups/beginend.jl | 96 +++++++++++++++++++++++ src/Lookups/indexing.jl | 9 ++- src/Lookups/lookup_arrays.jl | 111 ++++++++++++++------------- src/Lookups/lookup_traits.jl | 77 ++++++++++++------- src/Lookups/methods.jl | 2 +- src/Lookups/selector.jl | 60 +++++++-------- src/Lookups/set.jl | 50 ++++++------ src/Lookups/utils.jl | 41 +++++----- src/array/indexing.jl | 144 +++++++++++++++++++++++------------ src/array/methods.jl | 2 +- src/array/show.jl | 6 +- src/interface.jl | 28 ++----- src/plotrecipes.jl | 32 ++++---- src/stack/indexing.jl | 33 ++++---- src/stack/show.jl | 2 +- test/adapt.jl | 2 +- test/indexing.jl | 83 ++++++++++++++------ test/selector.jl | 12 +-- test/set.jl | 2 +- 28 files changed, 605 insertions(+), 389 deletions(-) create mode 100644 src/Lookups/beginend.jl 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 From 6a90cd49215b77307a825edab0bef0b1eb3cf662 Mon Sep 17 00:00:00 2001 From: Rafael Schouten Date: Sun, 10 Mar 2024 00:25:51 +0100 Subject: [PATCH 060/108] fix and test comparedims (#662) * fix and test comparedims * fix tests --- src/Dimensions/primitives.jl | 21 +++++++------ src/array/array.jl | 2 +- test/primitives.jl | 61 +++++++++++++++++++++++++++++++++++- 3 files changed, 72 insertions(+), 12 deletions(-) diff --git a/src/Dimensions/primitives.jl b/src/Dimensions/primitives.jl index 3f5a9584e..5454e84d8 100644 --- a/src/Dimensions/primitives.jl +++ b/src/Dimensions/primitives.jl @@ -559,7 +559,7 @@ function comparedims end ) type && basetypeof(a) != basetypeof(b) && _dimsmismatcherror(a, b) valtype && typeof(parent(a)) != typeof(parent(b)) && _valtypeerror(a, b) - val && parent(a) != parent(b) && _valerror(a, b) + val && parent(lookup(a)) != parent(lookup(b)) && _valerror(a, b) if order (isnolookup(a) || isnolookup(b) || LU.order(a) == LU.order(b)) || _ordererror(a, b) end @@ -587,7 +587,8 @@ end @inline _comparedims(T::Type{Bool}, a::Dimension, b::AnonDim; kw...) = true @inline _comparedims(T::Type{Bool}, a::AnonDim, b::Dimension; kw...) = true @inline function _comparedims(::Type{Bool}, a::Dimension, b::Dimension; - type=true, valtype=false, val=false, length=true, order=false, ignore_length_one=false, warn=nothing, + type=true, valtype=false, val=false, length=true, order=false, ignore_length_one=false, + warn::Union{Nothing,String}=nothing, ) if type && basetypeof(a) != basetypeof(b) isnothing(warn) || _dimsmismatchwarn(a, b, warn) @@ -597,12 +598,12 @@ end isnothing(warn) || _valtypewarn(a, b, warn) return false end - if val && parent(a) != parent(b) + if val && parent(lookup(a)) != parent(lookup(b)) isnothing(warn) || _valwarn(a, b, warn) return false end if order && !(isnolookup(a) || isnolookup(b) || LU.order(a) == LU.order(b)) - _orderwarn(a, b, warn) + isnothing(warn) || _orderwarn(a, b, warn) return false end if ignore_length_one && (Base.length(a) == 1 || Base.length(b) == 1) @@ -767,12 +768,12 @@ _ordermsg(a, b) = "Lookups do not all have the same order: $(order(a)), $(order( _typemsg(a, b) = "Lookups do not all have the same type: $(order(a)), $(order(b))." # Warning: @noinline to avoid allocations when it isn't used -@noinline _dimsmismatchwarn(a, b, msg="") = @warn _dimsmismatchmsg(a, b) * msg -@noinline _valwarn(a, b, msg="") = @warn _valmsg(a, b) * msg -@noinline _dimsizewarn(a, b, msg="") = @warn _dimsizemsg(a, b) * msg -@noinline _valtypewarn(a, b, msg="") = @warn _valtypemsg(a, b) * msg -@noinline _extradimswarn(dims, msg="") = @warn _extradimsmsg(dims) * msg -@noinline _orderwarn(a, b, msg="") = @warn _ordermsg(a, b) * msg +@noinline _dimsmismatchwarn(a, b, msg="") = @warn string(_dimsmismatchmsg(a, b), msg) +@noinline _valwarn(a, b, msg="") = @warn string(_valmsg(a, b), msg) +@noinline _dimsizewarn(a, b, msg="") = @warn string(_dimsizemsg(a, b), msg) +@noinline _valtypewarn(a, b, msg="") = @warn string(_valtypemsg(a, b), msg) +@noinline _extradimswarn(dims, msg="") = @warn string(_extradimsmsg(dims), msg) +@noinline _orderwarn(a, b, msg="") = @warn string(_ordermsg(a, b), msg) # Error @noinline _dimsmismatcherror(a, b) = throw(DimensionMismatch(_dimsmismatchmsg(a, b))) diff --git a/src/array/array.jl b/src/array/array.jl index 010051d06..f7778e5df 100644 --- a/src/array/array.jl +++ b/src/array/array.jl @@ -252,7 +252,7 @@ Base.similar(A::AbstractDimArray, ::Type{T}, D::Tuple{}) where T = # Keep the same type in `similar` _noname(A::AbstractBasicDimArray) = _noname(name(A)) -_noname(s::String) = @show s +_noname(s::String) = "" _noname(::NoName) = NoName() _noname(::Symbol) = Symbol("") _noname(name::Name) = name # Keep the name so the type doesn't change diff --git a/test/primitives.jl b/test/primitives.jl index 454aa07db..660fa8a99 100644 --- a/test/primitives.jl +++ b/test/primitives.jl @@ -1,7 +1,7 @@ using DimensionalData, Dates, Test , BenchmarkTools using DimensionalData.Lookups, DimensionalData.Dimensions -using .Dimensions: _dim_query, _wraparg, _reducedims, AlwaysTuple, MaybeFirst +using .Dimensions: _dim_query, _wraparg, _reducedims, AlwaysTuple, MaybeFirst, comparedims @dim Tst @@ -300,6 +300,65 @@ end @test_throws DimensionMismatch combinedims((X(1:2), Y(1:5)), (X(1:10), Z(3:10))) end +@testset "comparedims" begin + @testset "default keywords" begin + @test comparedims(Bool, X(1:2), X(1:2)) + @test !comparedims(Bool, X(1:2), Y(1:2)) + @test !comparedims(Bool, X(1:2), X(1:3)) + @test_warn "Found both lengths 2 and 3" comparedims(Bool, X(1:2), X(1:3); warn="") + @test_warn "X and Y dims on the same axis" comparedims(Bool, X(1:2), Y(1:2); warn="") + @test comparedims(X(1:2), X(1:2)) == X(1:2) + @test_throws DimensionMismatch comparedims(X(1:2), Y(1:2)) + @test_throws DimensionMismatch comparedims(X(1:2), X(1:3)) + end + @testset "compare type" begin + @test comparedims(Bool, X(1:2), Y(1:2); type=false) + @test !comparedims(Bool, X(1:2), Y(1:2); type=true) + @test_warn "X and Y dims on the same axis" comparedims(Bool, X(1:2), Y(1:2); type=true, warn="") + @test comparedims(X(1:2), Y(1:2); type=false) == X(Sampled(1:2)) + @test_throws DimensionMismatch comparedims(X(Sampled(1:2)), Y(Sampled(1:2)); type=true) + end + @testset "compare val type" begin + @test comparedims(Bool, X(Sampled(1:2)), X(Categorical(1:2)); valtype=false) + @test !comparedims(Bool, X(Sampled(1:2)), X(Categorical(1:2)); valtype=true) + @test comparedims(X(Sampled(1:2)), X(Categorical(1:2)); valtype=false) == X(Sampled(1:2)) + @test_throws DimensionMismatch comparedims(X(Sampled(1:2)), X(Categorical(1:2)); valtype=true) + @test comparedims(Bool, X(Sampled(1:2)), X(Sampled([1, 2])); valtype=false) + @test !comparedims(Bool, X(Sampled(1:2)), X(Sampled([1, 2])); valtype=true) + @test comparedims(X(Sampled(1:2)), X(Sampled([1, 2])); valtype=false) == X(Sampled(1:2)) + @test_throws DimensionMismatch comparedims(X(Sampled([1, 2])), X(Sampled(1:2)); valtype=true) + end + @testset "compare values" begin + @test comparedims(Bool, X(1:2), X(2:3); val=false) + @test !comparedims(Bool, X(1:2), X(2:3); val=true) + @test_warn "do not match" comparedims(Bool, X(1:2), X(2:3); val=true, warn="") + @test comparedims(Bool, X(Sampled(1:2)), X(Sampled(2:3)); val=false) + @test !comparedims(Bool, X(Sampled(1:2)), X(Sampled(2:3)); val=true) + @test comparedims(X(Sampled(1:2)), X(Sampled(2:3)); val=false) == X(Sampled(1:2)) + @test_throws DimensionMismatch comparedims(X(Sampled(1:2)), X(Sampled(2:3)); val=true) + end + @testset "compare length" begin + @test comparedims(Bool, X(1:2), X(1:3); length=false) + @test !comparedims(Bool, X(1:2), X(1:3); length=true) + @test_warn "Found both lengths" comparedims(Bool, X(1:2), X(1:3); length=true, warn="") + @test comparedims(X(1:2), X(1:3); length=false) == X(1:2) + @test_throws DimensionMismatch comparedims(X(1:2), X(1:3); length=true) + @test comparedims(Bool, X(1:2), X(1:1); length=true, ignore_length_one=true) + @test !comparedims(Bool, X(1:2), X(1:1); length=true, ignore_length_one=false) + @test comparedims(X(1:2), X(1:1); length=false, ignore_length_one=true) == X(1:2) + @test_throws DimensionMismatch comparedims(X(1:2), X(1:1); length=true, ignore_length_one=false) + end + @testset "compare order" begin + a, b = X(Sampled(1:2); order=ForwardOrdered()), X(Sampled(1:2); order=ReverseOrdered()) + @test comparedims(Bool, a, b; order=false) + @test !comparedims(Bool, a, b; order=true) + @test comparedims(Bool, a, b; order=false) + @test_nowarn comparedims(Bool, a, b; order=true) + @test_warn "Lookups do not all have the same order" comparedims(Bool, a, b; order=true, warn="") + @test_throws DimensionMismatch comparedims(a, b; order=true) + end +end + @testset "setdims" begin A = setdims(da, X(Sampled(LinRange(150,152,2)))) @test index(A, X()) == LinRange(150,152,2) From d6e43ab0619909f3de129b9785ac6d399cb824c4 Mon Sep 17 00:00:00 2001 From: Rafael Schouten Date: Sun, 10 Mar 2024 14:48:19 +0100 Subject: [PATCH 061/108] bump minor version to 0.26.0 --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 388801c49..450e2cb35 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "DimensionalData" uuid = "0703355e-b756-11e9-17c0-8b28908087d0" authors = ["Rafael Schouten "] -version = "0.25.8" +version = "0.26.0" [deps] Adapt = "79e6a3ab-5dfb-504d-930d-738a2a938a0e" From e599a914c02b538085ce3fcca709bcbb60794ab3 Mon Sep 17 00:00:00 2001 From: Rafael Schouten Date: Sun, 10 Mar 2024 20:33:58 +0100 Subject: [PATCH 062/108] bump julia minimum to 1.10 (#663) * limit to julia 1.10 --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 450e2cb35..9fea4689b 100644 --- a/Project.toml +++ b/Project.toml @@ -66,7 +66,7 @@ TableTraits = "1" Tables = "1" Test = "1" Unitful = "1" -julia = "1.6" +julia = "1.10" [extras] Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595" From 55e8ecf6fd91249caa76af5697eeb66d9f8a40d0 Mon Sep 17 00:00:00 2001 From: Rafael Schouten Date: Sun, 10 Mar 2024 20:34:34 +0100 Subject: [PATCH 063/108] bump minor version to 0.26.1 --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 9fea4689b..8a8e94f93 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "DimensionalData" uuid = "0703355e-b756-11e9-17c0-8b28908087d0" authors = ["Rafael Schouten "] -version = "0.26.0" +version = "0.26.1" [deps] Adapt = "79e6a3ab-5dfb-504d-930d-738a2a938a0e" From be412a178c5f52ff8a29ffcf38cf09a0819e5c00 Mon Sep 17 00:00:00 2001 From: Rafael Schouten Date: Mon, 11 Mar 2024 01:20:44 +0100 Subject: [PATCH 064/108] fix nightly bug (#665) --- src/Dimensions/indexing.jl | 2 +- src/Lookups/beginend.jl | 18 +++++++++++++----- src/Lookups/indexing.jl | 3 --- src/array/indexing.jl | 9 +++++---- src/stack/indexing.jl | 4 ++-- test/indexing.jl | 4 ++++ 6 files changed, 25 insertions(+), 15 deletions(-) diff --git a/src/Dimensions/indexing.jl b/src/Dimensions/indexing.jl index b590e9aaa..9ce4420ec 100644 --- a/src/Dimensions/indexing.jl +++ b/src/Dimensions/indexing.jl @@ -111,7 +111,7 @@ _unwrapdim(x) = x # 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] + to_indices(parent(dim), LU._construct_types(i))[1] @inline _dims2indices(dim::Dimension, ::Nothing) = Colon() @inline _dims2indices(dim::Dimension, x) = Lookups.selectindices(val(dim), x) diff --git a/src/Lookups/beginend.jl b/src/Lookups/beginend.jl index d1227532f..75fc355d9 100644 --- a/src/Lookups/beginend.jl +++ b/src/Lookups/beginend.jl @@ -39,10 +39,10 @@ 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]) +Base.to_indices(A, inds, (r, args...)::Tuple{Begin,Vararg}) = + (first(inds[1]), to_indices(A, Base.tail(inds), args)...) +Base.to_indices(A, inds, (r, args...)::Tuple{End,Vararg}) = + (last(inds[1]), to_indices(A, Base.tail(inds), args)...) _to_index(inds, a::Int) = a _to_index(inds, ::Begin) = first(inds) @@ -89,8 +89,16 @@ _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 T in (UnitRange, AbstractUnitRange, StepRange, StepRangeLen, LinRange, Lookup) for f in (:getindex, :view, :dotview) @eval Base.$f(A::$T, i::AbstractBeginEndRange) = Base.$f(A, to_indices(A, (i,))...) + @eval Base.$f(A::$T, ::Type{Begin}) = Base.$f(A, firstindex(A)) + @eval Base.$f(A::$T, ::Type{End}) = Base.$f(A, lastindex(A)) end end + +# These methods let us use Begin End end as types without constructing them. +@inline _construct_types(::Type{Begin}, I...) = (Begin(), _construct_types(I...)...) +@inline _construct_types(::Type{End}, I...) = (End(), _construct_types(I...)...) +@inline _construct_types(i, I...) = (i, _construct_types(I...)...) +@inline _construct_types() = () diff --git a/src/Lookups/indexing.jl b/src/Lookups/indexing.jl index 87c95b7c1..5c86a1882 100644 --- a/src/Lookups/indexing.jl +++ b/src/Lookups/indexing.jl @@ -13,8 +13,5 @@ for f in (:getindex, :view, :dotview) 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/array/indexing.jl b/src/array/indexing.jl index 0f8ef31fb..8d0da02ff 100644 --- a/src/array/indexing.jl +++ b/src/array/indexing.jl @@ -41,17 +41,18 @@ for f in (:getindex, :view, :dotview) @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) + x = Base.$f(parent(A), Lookups._construct_types(i)) if x isa AbstractArray rebuildsliced(Base.$f, A, x, to_indices(A, (i,))) else x end end - @propagate_inbounds function Base.$f(A::AbstractDimArray, i1, i2, I...) - x = Base.$f(parent(A), i1, i2, I...) + @propagate_inbounds function Base.$f(A::AbstractDimArray, i1, i2, Is...) + I = Lookups._construct_types(i1, i2, Is...) + x = Base.$f(parent(A), I...) if x isa AbstractArray - rebuildsliced(Base.$f, A, x, to_indices(A, (i1, i2, I...))) + rebuildsliced(Base.$f, A, x, to_indices(A, I)) else x end diff --git a/src/stack/indexing.jl b/src/stack/indexing.jl index 84f8a857f..f7f2ff386 100644 --- a/src/stack/indexing.jl +++ b/src/stack/indexing.jl @@ -20,7 +20,7 @@ for f in (:getindex, :view, :dotview) _dim_f = Symbol(:_dim_, f) @eval begin @propagate_inbounds function Base.$f(s::AbstractDimStack, i) - Base.$f(s, to_indices(CartesianIndices(s), (i,))...) + Base.$f(s, to_indices(CartesianIndices(s), Lookups._construct_types(i))...) end @propagate_inbounds function Base.$f(s::AbstractDimStack, i::Union{SelectorOrInterval,Extents.Extent}) Base.$f(s, dims2indices(s, i)...) @@ -54,7 +54,7 @@ for f in (:getindex, :view, :dotview) end end @propagate_inbounds function Base.$f(s::AbstractDimStack, i1, i2, Is...) - I = to_indices(CartesianIndices(s), (i1, i2, Is...)) + I = to_indices(CartesianIndices(s), Lookups._construct_types(i1, i2, Is...)) # Check we have the right number of dimensions if length(dims(s)) > length(I) throw(BoundsError(dims(s), I)) diff --git a/test/indexing.jl b/test/indexing.jl index 781a2b30f..b98cd20c7 100644 --- a/test/indexing.jl +++ b/test/indexing.jl @@ -661,8 +661,12 @@ end end @testset "dimension indexing" begin A = DimArray((1:5)*(6:3:20)', (X, Y)) + @test A[Begin, End] == 18 + @test A[Begin(), End()] == 18 @test A[X=Begin, Y=End] == 18 @test A[X=End(), Y=Begin()] == 30 + @test A[Begin:Begin+1, End] == [18, 36] + @test A[Begin():Begin()+1, End()] == [18, 36] @test A[X=Begin:Begin+1, Y=End] == [18, 36] end end From ca1030ed7e1b2af22147a2a97329b2bfa5a7ecb6 Mon Sep 17 00:00:00 2001 From: Rafael Schouten Date: Mon, 11 Mar 2024 01:22:47 +0100 Subject: [PATCH 065/108] bump patch version to 0.26.2 --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 8a8e94f93..ed2c55043 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "DimensionalData" uuid = "0703355e-b756-11e9-17c0-8b28908087d0" authors = ["Rafael Schouten "] -version = "0.26.1" +version = "0.26.2" [deps] Adapt = "79e6a3ab-5dfb-504d-930d-738a2a938a0e" From b6e9b45b1a0580e36995c559eaafbf57ff051995 Mon Sep 17 00:00:00 2001 From: Rafael Schouten Date: Tue, 12 Mar 2024 22:32:22 +0100 Subject: [PATCH 066/108] fix vector table and vector DimExtensionArray (#667) * fix vector table and vector DimExtensionArray * fix indexing dispatch --- src/dimindices.jl | 38 ++++++++++++++++++++++++-------------- src/tables.jl | 9 +++++++-- test/dimindices.jl | 5 +++++ test/tables.jl | 8 ++++++++ 4 files changed, 44 insertions(+), 16 deletions(-) diff --git a/src/dimindices.jl b/src/dimindices.jl index d53ea06c0..20de8d6c3 100644 --- a/src/dimindices.jl +++ b/src/dimindices.jl @@ -19,10 +19,9 @@ for f in (:getindex, :dotview, :view) newdims, _ = slicedims(dims(di), I) rebuild(di; dims=newdims) end - @eval @propagate_inbounds function Base.$f(di::AbstractDimArrayGenerator{<:Any,1}, i::$T) + @eval @propagate_inbounds Base.$f(di::AbstractDimArrayGenerator{<:Any,1}, i::$T) = rebuild(di; dims=(dims(di, 1)[i],)) - end - @eval @propagate_inbounds Base.$f(dg::AbstractDimArrayGenerator, i::Int) = + @eval @propagate_inbounds Base.$f(dg::AbstractDimArrayGenerator, i::Integer) = Base.$f(dg, Tuple(CartesianIndices(dg)[i])...) end @@ -122,13 +121,13 @@ function DimIndices(dims::D) where {D<:Tuple{Vararg{Dimension}}} end # Forces multiple indices not linear -function Base.getindex(di::DimIndices, i1::Int, i2::Int, I::Int...) +function Base.getindex(di::DimIndices, i1::Integer, i2::Integer, I::Integer...) map(dims(di), (i1, i2, I...)) do d, i rebuild(d, d[i]) end end # Dispatch to avoid linear indexing in multidimensionsl DimIndices -function Base.getindex(di::DimIndices{<:Any,1}, i::Int) +function Base.getindex(di::DimIndices{<:Any,1}, i::Integer) d = dims(di, 1) (rebuild(d, d[i]),) end @@ -177,7 +176,7 @@ function DimPoints(dims::DimTuple, order::DimTuple) DimPoints{T,N,typeof(dims),typeof(order)}(dims, order) end -function Base.getindex(dp::DimPoints, i1::Int, i2::Int, I::Int...) +function Base.getindex(dp::DimPoints, i1::Integer, i2::Integer, I::Integer...) # Get dim-wrapped point values at i1, I... pointdims = map(dims(dp), (i1, i2, I...)) do d, i rebuild(d, d[i]) @@ -185,7 +184,7 @@ function Base.getindex(dp::DimPoints, i1::Int, i2::Int, I::Int...) # Return the unwrapped point sorted by `order return map(val, DD.dims(pointdims, dp.order)) end -Base.getindex(di::DimPoints{<:Any,1}, i::Int) = (dims(di, 1)[i],) +Base.getindex(di::DimPoints{<:Any,1}, i::Integer) = (dims(di, 1)[i],) _format(::Tuple{}) = () function _format(dims::Tuple) @@ -276,12 +275,12 @@ end _atol(::Type, atol) = atol _atol(T::Type{<:AbstractFloat}, atol::Nothing) = eps(T) -@propagate_inbounds function Base.getindex(di::DimSelectors, i1::Int, i2::Int, I::Int...) +@propagate_inbounds function Base.getindex(di::DimSelectors, i1::Integer, i2::Integer, I::Integer...) map(dims(di), di.selectors, (i1, i2, I...)) do d, s, i rebuild(d, rebuild(s; val=d[i])) # At selector with the value at i end end -@propagate_inbounds function Base.getindex(di::DimSelectors{<:Any,1}, i::Int) +@propagate_inbounds function Base.getindex(di::DimSelectors{<:Any,1}, i::Integer) d = dims(di, 1) (rebuild(d, rebuild(di.selectors[1]; val=d[i])),) end @@ -311,7 +310,7 @@ function Base.summary(io::IO, A::DimSlices{T,N}) where {T,N} print(io, string(nameof(typeof(A)), "{$(nameof(T)),$N}")) end -@propagate_inbounds function Base.getindex(ds::DimSlices, i1::Int, i2::Int, Is::Int...) +@propagate_inbounds function Base.getindex(ds::DimSlices, i1::Integer, i2::Integer, Is::Integer...) I = (i1, i2, Is...) @boundscheck checkbounds(ds, I...) D = map(dims(ds), I) do d, i @@ -320,7 +319,7 @@ end return view(ds._data, D...) end # Dispatch to avoid linear indexing in multidimensionsl DimIndices -@propagate_inbounds function Base.getindex(ds::DimSlices{<:Any,1}, i::Int) +@propagate_inbounds function Base.getindex(ds::DimSlices{<:Any,1}, i::Integer) d = dims(ds, 1) return view(ds._data, rebuild(d, d[i])) end @@ -352,6 +351,17 @@ for f in (:getindex, :dotview, :view) __f = Symbol(:__, f) T = Union{Colon,AbstractRange} # For ambiguity + @eval @propagate_inbounds function Base.$f(de::DimExtensionArray{<:Any,1}, i::Integer) + if ndims(parent(de)) == 0 + $f(de._data) + else + $f(de._data, i) + end + end + @eval @propagate_inbounds function Base.$f(di::DimExtensionArray{<:Any,1}, i::Union{AbstractRange,Colon}) + rebuild(di; _data=di.data[i], dims=(dims(di, 1)[i],)) + end + # For ambiguity @eval @propagate_inbounds function Base.$f(de::DimExtensionArray, i1::$T, i2::$T, Is::$T...) $__f(de, i1, i2, Is...) end @@ -359,9 +369,9 @@ for f in (:getindex, :dotview, :view) $__f(de, i1, i2, Is...) end @eval @propagate_inbounds function Base.$f( - de::DimensionalData.DimExtensionArray, - i1::Union{AbstractArray{Union{}}, DimensionalData.DimIndices{<:Integer}, DimensionalData.DimSelectors{<:Integer}}, - i2::Union{AbstractArray{Union{}}, DimensionalData.DimIndices{<:Integer}, DimensionalData.DimSelectors{<:Integer}}, + de::DimensionalData.DimExtensionArray, + i1::Union{AbstractArray{Union{}}, DimensionalData.DimIndices{<:Integer}, DimensionalData.DimSelectors{<:Integer}}, + i2::Union{AbstractArray{Union{}}, DimensionalData.DimIndices{<:Integer}, DimensionalData.DimSelectors{<:Integer}}, Is::Vararg{Union{AbstractArray{Union{}}, DimensionalData.DimIndices{<:Integer}, DimensionalData.DimSelectors{<:Integer}}} ) $__f(de, i1, i2, Is...) diff --git a/src/tables.jl b/src/tables.jl index 8dea867e2..59fdc3462 100644 --- a/src/tables.jl +++ b/src/tables.jl @@ -147,8 +147,13 @@ end _dimcolumns(x) = map(d -> _dimcolumn(x, d), dims(x)) function _dimcolumn(x, d::Dimension) - dim_as_dimarray = DimArray(index(d), d) - vec(DimExtensionArray(dim_as_dimarray, dims(x))) + lookupvals = parent(lookup(d)) + if length(dims(x)) == 1 + lookupvals + else + dim_as_dimarray = DimArray(lookupvals, d) + vec(DimExtensionArray(dim_as_dimarray, dims(x))) + end end diff --git a/test/dimindices.jl b/test/dimindices.jl index b17bad670..01541c1d8 100644 --- a/test/dimindices.jl +++ b/test/dimindices.jl @@ -141,6 +141,11 @@ end @test @inferred vec(ex) == mapreduce(_ -> vec(A), vcat, 1:prod(size(ex[X=1, Y=1]))) ex1 = DimensionalData.DimExtensionArray(A, (Z(1:10), dims(A)..., Ti(DateTime(2000):Month(1):DateTime(2000, 12); sampling=Intervals(Start())))) @test vec(ex1) == mapreduce(_ -> mapreduce(i -> map(_ -> A[i], 1:size(ex1, Z)), vcat, 1:prod((size(ex1, X), size(ex1, Y)))), vcat, 1:size(ex1, Ti)) + + v = DimVector(5:10, X; name=:vec) + @test v[4] == 8 + @test view(v, 4) == fill(8) + @test v[1:3] == 5:7 end @testset "DimSlices" begin diff --git a/test/tables.jl b/test/tables.jl index bb452184f..b5bd416ea 100644 --- a/test/tables.jl +++ b/test/tables.jl @@ -116,6 +116,14 @@ end @test lookup(ds) == lookup(dims(da)) end +@testset "one dimension tables" begin + a = DimVector(1:3, x; name=:a) + b = DimVector(4:6, x; name=:b) + s = DimStack((a, b)) + @test Tables.columntable(a) == (X=[:a, :b, :c], a=1:3,) + @test Tables.columntable(s) == (X=[:a, :b, :c], a=1:3, b=4:6) +end + @testset "zero dimension tables" begin a = DimArray(fill(1), (); name=:a); b = DimArray(fill(2), (); name=:b); From 94f4a3831dc3cc1cb10018b97df8cb2d46e8a27e Mon Sep 17 00:00:00 2001 From: Rafael Schouten Date: Tue, 12 Mar 2024 22:33:28 +0100 Subject: [PATCH 067/108] bump patch version to 0.26.3 --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index ed2c55043..817dc05ff 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "DimensionalData" uuid = "0703355e-b756-11e9-17c0-8b28908087d0" authors = ["Rafael Schouten "] -version = "0.26.2" +version = "0.26.3" [deps] Adapt = "79e6a3ab-5dfb-504d-930d-738a2a938a0e" From 11d048d359b2cbf1e96a5d23e4218812414409d1 Mon Sep 17 00:00:00 2001 From: Rafael Schouten Date: Wed, 13 Mar 2024 23:50:22 +0100 Subject: [PATCH 068/108] try 1.9 (#671) * try 1.9 * remove reinterpret weirdness --- .github/workflows/ci.yml | 1 + Project.toml | 2 +- src/array/indexing.jl | 4 +--- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 409a845d0..7931d47e5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,6 +17,7 @@ jobs: fail-fast: true matrix: version: + - '1.9' - '1' os: - ubuntu-latest diff --git a/Project.toml b/Project.toml index 817dc05ff..99091c80e 100644 --- a/Project.toml +++ b/Project.toml @@ -66,7 +66,7 @@ TableTraits = "1" Tables = "1" Test = "1" Unitful = "1" -julia = "1.10" +julia = "1.9" [extras] Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595" diff --git a/src/array/indexing.jl b/src/array/indexing.jl index 8d0da02ff..f9180e45e 100644 --- a/src/array/indexing.jl +++ b/src/array/indexing.jl @@ -179,9 +179,7 @@ function _merge_and_index(f, A, inds) f(A, m_inds) end else - d = only(dims_to_merge) - val_array = reinterpret(typeof(val(d)), dims_to_merge) - f(A, rebuild(d, val_array)) + f(A, only(dims_to_merge)) end end From fd7e06103edd48ed14b26333f3b3747dbd9ea9bf Mon Sep 17 00:00:00 2001 From: Rafael Schouten Date: Wed, 13 Mar 2024 23:52:39 +0100 Subject: [PATCH 069/108] bump patch version to 0.26.4 --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 99091c80e..3237de5b1 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "DimensionalData" uuid = "0703355e-b756-11e9-17c0-8b28908087d0" authors = ["Rafael Schouten "] -version = "0.26.3" +version = "0.26.4" [deps] Adapt = "79e6a3ab-5dfb-504d-930d-738a2a938a0e" From f3ab38b16a932ea06df0ca064c03c63faed99801 Mon Sep 17 00:00:00 2001 From: Rafael Schouten Date: Sat, 23 Mar 2024 12:48:12 +0100 Subject: [PATCH 070/108] Modify compilation (#674) * shortcut compilation in DimStack modify * tweak modify test * just apply f --- src/array/broadcast.jl | 2 +- src/utils.jl | 4 ++++ test/utils.jl | 4 ++-- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/array/broadcast.jl b/src/array/broadcast.jl index 23d707bac..76bb4f0e0 100644 --- a/src/array/broadcast.jl +++ b/src/array/broadcast.jl @@ -101,5 +101,5 @@ _firstdimarray(x::Tuple{}) = nothing _broadcasted_dims(bc::Broadcasted) = _broadcasted_dims(bc.args...) _broadcasted_dims(a, bs...) = comparedims(_broadcasted_dims(a), _broadcasted_dims(bs...); ignore_length_one=true, order=true) -_broadcasted_dims(a::AbstractDimArray) = dims(a) +_broadcasted_dims(a::AbstractBasicDimArray) = dims(a) _broadcasted_dims(a) = nothing diff --git a/src/utils.jl b/src/utils.jl index d2569bcc7..2bf142d2f 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -86,6 +86,10 @@ This also works for all the data layers in a `DimStack`. """ function modify end modify(f, s::AbstractDimStack) = map(a -> modify(f, a), s) +# Stack optimisation to avoid compilation to build all the `AbstractDimArray` +# layers, and instead just modify the parent data directly. +modify(f, s::AbstractDimStack{<:NamedTuple}) = + rebuild(s; data=map(a -> modify(f, a), parent(s))) function modify(f, A::AbstractDimArray) newdata = f(parent(A)) size(newdata) == size(A) || error("$f returns an array with a different size") diff --git a/test/utils.jl b/test/utils.jl index 40b6dfcf5..8826ce7f3 100644 --- a/test/utils.jl +++ b/test/utils.jl @@ -115,13 +115,13 @@ end @test typeof(parent(mda)) == BitArray{2} @test_throws ErrorException modify(A -> A[1, :], da) end - @testset "dataset" begin + @testset "stack" begin da1 = DimArray(A, dimz; name=:da1) da2 = DimArray(2A, dimz; name=:da2) s = DimStack(da1, da2) ms = modify(A -> A .> 3, s) @test parent(ms) == (da1=[false false false; true true true], - da2=[false true true ; true true true]) + da2=[false true true ; true true true]) @test typeof(parent(ms[:da2])) == BitArray{2} end @testset "dimension" begin From 290acef4187f5747b91b71bf5e823a7a139d8878 Mon Sep 17 00:00:00 2001 From: Rafael Schouten Date: Sat, 23 Mar 2024 12:49:11 +0100 Subject: [PATCH 071/108] Update Project.toml bump patch version to 0.26.5 --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 3237de5b1..6fec5bdd1 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "DimensionalData" uuid = "0703355e-b756-11e9-17c0-8b28908087d0" authors = ["Rafael Schouten "] -version = "0.26.4" +version = "0.26.5" [deps] Adapt = "79e6a3ab-5dfb-504d-930d-738a2a938a0e" From e0900d1059114d8a25ac723b7723a5902cf5aeb9 Mon Sep 17 00:00:00 2001 From: Rafael Schouten Date: Sat, 23 Mar 2024 18:47:22 +0100 Subject: [PATCH 072/108] remove broken version in vitepress (#675) * remove broken version in vitepress * update template * tweaks to match template * Update package.json --------- Co-authored-by: Anshul Singhvi --- docs/package.json | 9 ++++++--- docs/src/.vitepress/config.mts | 24 ++++++++++-------------- 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/docs/package.json b/docs/package.json index b9f192247..d7179f1ba 100644 --- a/docs/package.json +++ b/docs/package.json @@ -4,10 +4,13 @@ "docs:build": "vitepress build build/.documenter", "docs:preview": "vitepress preview build/.documenter" }, - "devDependencies": { + "dependencies": { + "@shikijs/transformers": "^1.1.7", + "markdown-it": "^14.0.0", + "markdown-it-footnote": "^4.0.0", "markdown-it-mathjax3": "^4.3.2", - "vitepress": "^1.0.0-rc.41", + "vitepress": "^1.0.0-rc.43", "vitepress-plugin-tabs": "^0.5.0", - "vitest": "^1.2.0" + "vitest": "^1.3.0" } } diff --git a/docs/src/.vitepress/config.mts b/docs/src/.vitepress/config.mts index 1c711d2bd..ea6ba26d2 100644 --- a/docs/src/.vitepress/config.mts +++ b/docs/src/.vitepress/config.mts @@ -1,12 +1,9 @@ -import type { DefaultTheme } from 'vitepress' import { defineConfig } from 'vitepress' import { tabsMarkdownPlugin } from 'vitepress-plugin-tabs' -const version= '0.25.8' - -const VERSIONS: DefaultTheme.NavItemWithLink[] = [ - { text: `v${version} (current)`, link: '/' }, - { text: `Release Notes`, link: 'https://github.com/rafaqz/DimensionalData.jl/releases/' }, -] +import mathjax3 from "markdown-it-mathjax3"; +import footnote from "markdown-it-footnote"; +// Custom +import type { DefaultTheme } from 'vitepress' // https://vitepress.dev/reference/site-config export default defineConfig({ @@ -15,12 +12,16 @@ export default defineConfig({ description: "Datasets with named dimensions", lastUpdated: true, cleanUrls: true, - ignoreDeadLinks: true, outDir: 'REPLACE_ME_DOCUMENTER_VITEPRESS', // This is required for MarkdownVitepress to work correctly... + head: [], + ignoreDeadLinks: true, markdown: { + math: true, config(md) { - md.use(tabsMarkdownPlugin) + md.use(tabsMarkdownPlugin), + md.use(mathjax3), + md.use(footnote) }, // https://shiki.style/themes theme: { @@ -51,10 +52,6 @@ export default defineConfig({ { text: 'Extending DimensionalData', link: '/extending_dd' }, ], }, - { - text: `v${version}`, - items: VERSIONS, - }, ], sidebar: [ @@ -91,7 +88,6 @@ export default defineConfig({ socialLinks: [ { icon: 'github', link: 'https://github.com/rafaqz/DimensionalData.jl' }, - ], footer: { message: 'Made with
DocumenterVitepress.jl by Lazaro Alonso
', From 4a29819f8eef944fce7cd720ef235e452c54be29 Mon Sep 17 00:00:00 2001 From: Rafael Schouten Date: Sat, 23 Mar 2024 18:49:34 +0100 Subject: [PATCH 073/108] bump patch version to 0.26.6 --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 6fec5bdd1..1c2c7d17e 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "DimensionalData" uuid = "0703355e-b756-11e9-17c0-8b28908087d0" authors = ["Rafael Schouten "] -version = "0.26.5" +version = "0.26.6" [deps] Adapt = "79e6a3ab-5dfb-504d-930d-738a2a938a0e" From 2c3620dcf21844fbc326c85746c021557a8786f7 Mon Sep 17 00:00:00 2001 From: Lazaro Alonso Date: Sun, 31 Mar 2024 16:06:39 +0200 Subject: [PATCH 074/108] Update broadcast.jl (#681) fix typo --- src/array/broadcast.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/array/broadcast.jl b/src/array/broadcast.jl index 76bb4f0e0..de6dec328 100644 --- a/src/array/broadcast.jl +++ b/src/array/broadcast.jl @@ -85,7 +85,7 @@ _unwrap_broadcasted(nda::AbstractDimArray) = parent(nda) # Get the first dimensional array in the broadcast _firstdimarray(x::Broadcasted) = _firstdimarray(x.args) _firstdimarray(x::Tuple{<:AbstractDimArray,Vararg}) = x[1] -_fistdimarray(ext::Base.Broadcast.Extruded) = _firstdimarray(ext.x) +_firstdimarray(ext::Base.Broadcast.Extruded) = _firstdimarray(ext.x) function _firstdimarray(x::Tuple{<:Broadcasted,Vararg}) found = _firstdimarray(x[1]) if found isa Nothing From 4b0fa8598f9a0d1cd0e96cf15697b605d9caf112 Mon Sep 17 00:00:00 2001 From: Lazaro Alonso Date: Sun, 31 Mar 2024 16:07:16 +0200 Subject: [PATCH 075/108] clean up ci docs (#679) * clean up ci docs * no dev for logo --- .github/workflows/Documenter.yml | 8 - .gitignore | 1 + docs/package-lock.json | 2774 ------------------------------ docs/package.json | 16 +- docs/src/.vitepress/config.mts | 2 - 5 files changed, 10 insertions(+), 2791 deletions(-) delete mode 100644 docs/package-lock.json diff --git a/.github/workflows/Documenter.yml b/.github/workflows/Documenter.yml index 04c7d4a79..2d701614d 100644 --- a/.github/workflows/Documenter.yml +++ b/.github/workflows/Documenter.yml @@ -37,18 +37,10 @@ jobs: uses: actions/checkout@v4 with: # Fetches the last commit only fetch-depth: 0 - - name: Setup Node - uses: actions/setup-node@v3 - with: - node-version: 20 - cache: npm # or pnpm / yarn - cache-dependency-path: 'docs/package-lock.json' # this should be a package-lock.json file - name: Setup Julia uses: julia-actions/setup-julia@v1 - name: Pull Julia cache uses: julia-actions/cache@v1 - - name: Instantiate NPM - run: cd docs/; npm i; cd .. - name: Generate logo run: julia --project=docs -e "using Pkg; Pkg.instantiate()"; julia --project=docs/ --color=yes docs/logo.jl - uses: julia-actions/julia-docdeploy@v1 diff --git a/.gitignore b/.gitignore index f02fc351e..754f180d4 100644 --- a/.gitignore +++ b/.gitignore @@ -23,5 +23,6 @@ docs/.vitepress/cache docs/src/.vitepress/dist docs/src/.vitepress/cache node_modules +docs/package-lock.json *.png *.svg \ No newline at end of file diff --git a/docs/package-lock.json b/docs/package-lock.json deleted file mode 100644 index 2dc55a5e4..000000000 --- a/docs/package-lock.json +++ /dev/null @@ -1,2774 +0,0 @@ -{ - "name": "docs", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "devDependencies": { - "markdown-it-mathjax3": "^4.3.2", - "vitepress": "^1.0.0-rc.41", - "vitepress-plugin-tabs": "^0.5.0", - "vitest": "^1.2.0" - } - }, - "node_modules/@algolia/autocomplete-core": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-core/-/autocomplete-core-1.9.3.tgz", - "integrity": "sha512-009HdfugtGCdC4JdXUbVJClA0q0zh24yyePn+KUGk3rP7j8FEe/m5Yo/z65gn6nP/cM39PxpzqKrL7A6fP6PPw==", - "dev": true, - "dependencies": { - "@algolia/autocomplete-plugin-algolia-insights": "1.9.3", - "@algolia/autocomplete-shared": "1.9.3" - } - }, - "node_modules/@algolia/autocomplete-plugin-algolia-insights": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-plugin-algolia-insights/-/autocomplete-plugin-algolia-insights-1.9.3.tgz", - "integrity": "sha512-a/yTUkcO/Vyy+JffmAnTWbr4/90cLzw+CC3bRbhnULr/EM0fGNvM13oQQ14f2moLMcVDyAx/leczLlAOovhSZg==", - "dev": true, - "dependencies": { - "@algolia/autocomplete-shared": "1.9.3" - }, - "peerDependencies": { - "search-insights": ">= 1 < 3" - } - }, - "node_modules/@algolia/autocomplete-preset-algolia": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.9.3.tgz", - "integrity": "sha512-d4qlt6YmrLMYy95n5TB52wtNDr6EgAIPH81dvvvW8UmuWRgxEtY0NJiPwl/h95JtG2vmRM804M0DSwMCNZlzRA==", - "dev": true, - "dependencies": { - "@algolia/autocomplete-shared": "1.9.3" - }, - "peerDependencies": { - "@algolia/client-search": ">= 4.9.1 < 6", - "algoliasearch": ">= 4.9.1 < 6" - } - }, - "node_modules/@algolia/autocomplete-shared": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-shared/-/autocomplete-shared-1.9.3.tgz", - "integrity": "sha512-Wnm9E4Ye6Rl6sTTqjoymD+l8DjSTHsHboVRYrKgEt8Q7UHm9nYbqhN/i0fhUYA3OAEH7WA8x3jfpnmJm3rKvaQ==", - "dev": true, - "peerDependencies": { - "@algolia/client-search": ">= 4.9.1 < 6", - "algoliasearch": ">= 4.9.1 < 6" - } - }, - "node_modules/@algolia/cache-browser-local-storage": { - "version": "4.22.1", - "resolved": "https://registry.npmjs.org/@algolia/cache-browser-local-storage/-/cache-browser-local-storage-4.22.1.tgz", - "integrity": "sha512-Sw6IAmOCvvP6QNgY9j+Hv09mvkvEIDKjYW8ow0UDDAxSXy664RBNQk3i/0nt7gvceOJ6jGmOTimaZoY1THmU7g==", - "dev": true, - "dependencies": { - "@algolia/cache-common": "4.22.1" - } - }, - "node_modules/@algolia/cache-common": { - "version": "4.22.1", - "resolved": "https://registry.npmjs.org/@algolia/cache-common/-/cache-common-4.22.1.tgz", - "integrity": "sha512-TJMBKqZNKYB9TptRRjSUtevJeQVXRmg6rk9qgFKWvOy8jhCPdyNZV1nB3SKGufzvTVbomAukFR8guu/8NRKBTA==", - "dev": true - }, - "node_modules/@algolia/cache-in-memory": { - "version": "4.22.1", - "resolved": "https://registry.npmjs.org/@algolia/cache-in-memory/-/cache-in-memory-4.22.1.tgz", - "integrity": "sha512-ve+6Ac2LhwpufuWavM/aHjLoNz/Z/sYSgNIXsinGofWOysPilQZPUetqLj8vbvi+DHZZaYSEP9H5SRVXnpsNNw==", - "dev": true, - "dependencies": { - "@algolia/cache-common": "4.22.1" - } - }, - "node_modules/@algolia/client-account": { - "version": "4.22.1", - "resolved": "https://registry.npmjs.org/@algolia/client-account/-/client-account-4.22.1.tgz", - "integrity": "sha512-k8m+oegM2zlns/TwZyi4YgCtyToackkOpE+xCaKCYfBfDtdGOaVZCM5YvGPtK+HGaJMIN/DoTL8asbM3NzHonw==", - "dev": true, - "dependencies": { - "@algolia/client-common": "4.22.1", - "@algolia/client-search": "4.22.1", - "@algolia/transporter": "4.22.1" - } - }, - "node_modules/@algolia/client-analytics": { - "version": "4.22.1", - "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-4.22.1.tgz", - "integrity": "sha512-1ssi9pyxyQNN4a7Ji9R50nSdISIumMFDwKNuwZipB6TkauJ8J7ha/uO60sPJFqQyqvvI+px7RSNRQT3Zrvzieg==", - "dev": true, - "dependencies": { - "@algolia/client-common": "4.22.1", - "@algolia/client-search": "4.22.1", - "@algolia/requester-common": "4.22.1", - "@algolia/transporter": "4.22.1" - } - }, - "node_modules/@algolia/client-common": { - "version": "4.22.1", - "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.22.1.tgz", - "integrity": "sha512-IvaL5v9mZtm4k4QHbBGDmU3wa/mKokmqNBqPj0K7lcR8ZDKzUorhcGp/u8PkPC/e0zoHSTvRh7TRkGX3Lm7iOQ==", - "dev": true, - "dependencies": { - "@algolia/requester-common": "4.22.1", - "@algolia/transporter": "4.22.1" - } - }, - "node_modules/@algolia/client-personalization": { - "version": "4.22.1", - "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-4.22.1.tgz", - "integrity": "sha512-sl+/klQJ93+4yaqZ7ezOttMQ/nczly/3GmgZXJ1xmoewP5jmdP/X/nV5U7EHHH3hCUEHeN7X1nsIhGPVt9E1cQ==", - "dev": true, - "dependencies": { - "@algolia/client-common": "4.22.1", - "@algolia/requester-common": "4.22.1", - "@algolia/transporter": "4.22.1" - } - }, - "node_modules/@algolia/client-search": { - "version": "4.22.1", - "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-4.22.1.tgz", - "integrity": "sha512-yb05NA4tNaOgx3+rOxAmFztgMTtGBi97X7PC3jyNeGiwkAjOZc2QrdZBYyIdcDLoI09N0gjtpClcackoTN0gPA==", - "dev": true, - "dependencies": { - "@algolia/client-common": "4.22.1", - "@algolia/requester-common": "4.22.1", - "@algolia/transporter": "4.22.1" - } - }, - "node_modules/@algolia/logger-common": { - "version": "4.22.1", - "resolved": "https://registry.npmjs.org/@algolia/logger-common/-/logger-common-4.22.1.tgz", - "integrity": "sha512-OnTFymd2odHSO39r4DSWRFETkBufnY2iGUZNrMXpIhF5cmFE8pGoINNPzwg02QLBlGSaLqdKy0bM8S0GyqPLBg==", - "dev": true - }, - "node_modules/@algolia/logger-console": { - "version": "4.22.1", - "resolved": "https://registry.npmjs.org/@algolia/logger-console/-/logger-console-4.22.1.tgz", - "integrity": "sha512-O99rcqpVPKN1RlpgD6H3khUWylU24OXlzkavUAMy6QZd1776QAcauE3oP8CmD43nbaTjBexZj2nGsBH9Tc0FVA==", - "dev": true, - "dependencies": { - "@algolia/logger-common": "4.22.1" - } - }, - "node_modules/@algolia/requester-browser-xhr": { - "version": "4.22.1", - "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-4.22.1.tgz", - "integrity": "sha512-dtQGYIg6MteqT1Uay3J/0NDqD+UciHy3QgRbk7bNddOJu+p3hzjTRYESqEnoX/DpEkaNYdRHUKNylsqMpgwaEw==", - "dev": true, - "dependencies": { - "@algolia/requester-common": "4.22.1" - } - }, - "node_modules/@algolia/requester-common": { - "version": "4.22.1", - "resolved": "https://registry.npmjs.org/@algolia/requester-common/-/requester-common-4.22.1.tgz", - "integrity": "sha512-dgvhSAtg2MJnR+BxrIFqlLtkLlVVhas9HgYKMk2Uxiy5m6/8HZBL40JVAMb2LovoPFs9I/EWIoFVjOrFwzn5Qg==", - "dev": true - }, - "node_modules/@algolia/requester-node-http": { - "version": "4.22.1", - "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-4.22.1.tgz", - "integrity": "sha512-JfmZ3MVFQkAU+zug8H3s8rZ6h0ahHZL/SpMaSasTCGYR5EEJsCc8SI5UZ6raPN2tjxa5bxS13BRpGSBUens7EA==", - "dev": true, - "dependencies": { - "@algolia/requester-common": "4.22.1" - } - }, - "node_modules/@algolia/transporter": { - "version": "4.22.1", - "resolved": "https://registry.npmjs.org/@algolia/transporter/-/transporter-4.22.1.tgz", - "integrity": "sha512-kzWgc2c9IdxMa3YqA6TN0NW5VrKYYW/BELIn7vnLyn+U/RFdZ4lxxt9/8yq3DKV5snvoDzzO4ClyejZRdV3lMQ==", - "dev": true, - "dependencies": { - "@algolia/cache-common": "4.22.1", - "@algolia/logger-common": "4.22.1", - "@algolia/requester-common": "4.22.1" - } - }, - "node_modules/@babel/parser": { - "version": "7.23.9", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.9.tgz", - "integrity": "sha512-9tcKgqKbs3xGJ+NtKF2ndOBBLVwPjl1SHxPQkd36r3Dlirw3xWUeGaTbqr7uGZcTaxkVNwc+03SVP7aCdWrTlA==", - "dev": true, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@docsearch/css": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-3.5.2.tgz", - "integrity": "sha512-SPiDHaWKQZpwR2siD0KQUwlStvIAnEyK6tAE2h2Wuoq8ue9skzhlyVQ1ddzOxX6khULnAALDiR/isSF3bnuciA==", - "dev": true - }, - "node_modules/@docsearch/js": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/@docsearch/js/-/js-3.5.2.tgz", - "integrity": "sha512-p1YFTCDflk8ieHgFJYfmyHBki1D61+U9idwrLh+GQQMrBSP3DLGKpy0XUJtPjAOPltcVbqsTjiPFfH7JImjUNg==", - "dev": true, - "dependencies": { - "@docsearch/react": "3.5.2", - "preact": "^10.0.0" - } - }, - "node_modules/@docsearch/react": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/@docsearch/react/-/react-3.5.2.tgz", - "integrity": "sha512-9Ahcrs5z2jq/DcAvYtvlqEBHImbm4YJI8M9y0x6Tqg598P40HTEkX7hsMcIuThI+hTFxRGZ9hll0Wygm2yEjng==", - "dev": true, - "dependencies": { - "@algolia/autocomplete-core": "1.9.3", - "@algolia/autocomplete-preset-algolia": "1.9.3", - "@docsearch/css": "3.5.2", - "algoliasearch": "^4.19.1" - }, - "peerDependencies": { - "@types/react": ">= 16.8.0 < 19.0.0", - "react": ">= 16.8.0 < 19.0.0", - "react-dom": ">= 16.8.0 < 19.0.0", - "search-insights": ">= 1 < 3" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "react": { - "optional": true - }, - "react-dom": { - "optional": true - }, - "search-insights": { - "optional": true - } - } - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.11.tgz", - "integrity": "sha512-FnzU0LyE3ySQk7UntJO4+qIiQgI7KoODnZg5xzXIrFJlKd2P2gwHsHY4927xj9y5PJmJSzULiUCWmv7iWnNa7g==", - "cpu": [ - "ppc64" - ], - "dev": true, - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.11.tgz", - "integrity": "sha512-5OVapq0ClabvKvQ58Bws8+wkLCV+Rxg7tUVbo9xu034Nm536QTII4YzhaFriQ7rMrorfnFKUsArD2lqKbFY4vw==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.11.tgz", - "integrity": "sha512-aiu7K/5JnLj//KOnOfEZ0D90obUkRzDMyqd/wNAUQ34m4YUPVhRZpnqKV9uqDGxT7cToSDnIHsGooyIczu9T+Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.11.tgz", - "integrity": "sha512-eccxjlfGw43WYoY9QgB82SgGgDbibcqyDTlk3l3C0jOVHKxrjdc9CTwDUQd0vkvYg5um0OH+GpxYvp39r+IPOg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.11.tgz", - "integrity": "sha512-ETp87DRWuSt9KdDVkqSoKoLFHYTrkyz2+65fj9nfXsaV3bMhTCjtQfw3y+um88vGRKRiF7erPrh/ZuIdLUIVxQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.11.tgz", - "integrity": "sha512-fkFUiS6IUK9WYUO/+22omwetaSNl5/A8giXvQlcinLIjVkxwTLSktbF5f/kJMftM2MJp9+fXqZ5ezS7+SALp4g==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.11.tgz", - "integrity": "sha512-lhoSp5K6bxKRNdXUtHoNc5HhbXVCS8V0iZmDvyWvYq9S5WSfTIHU2UGjcGt7UeS6iEYp9eeymIl5mJBn0yiuxA==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.11.tgz", - "integrity": "sha512-JkUqn44AffGXitVI6/AbQdoYAq0TEullFdqcMY/PCUZ36xJ9ZJRtQabzMA+Vi7r78+25ZIBosLTOKnUXBSi1Kw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.11.tgz", - "integrity": "sha512-3CRkr9+vCV2XJbjwgzjPtO8T0SZUmRZla+UL1jw+XqHZPkPgZiyWvbDvl9rqAN8Zl7qJF0O/9ycMtjU67HN9/Q==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.11.tgz", - "integrity": "sha512-LneLg3ypEeveBSMuoa0kwMpCGmpu8XQUh+mL8XXwoYZ6Be2qBnVtcDI5azSvh7vioMDhoJFZzp9GWp9IWpYoUg==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.11.tgz", - "integrity": "sha512-caHy++CsD8Bgq2V5CodbJjFPEiDPq8JJmBdeyZ8GWVQMjRD0sU548nNdwPNvKjVpamYYVL40AORekgfIubwHoA==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.11.tgz", - "integrity": "sha512-ppZSSLVpPrwHccvC6nQVZaSHlFsvCQyjnvirnVjbKSHuE5N24Yl8F3UwYUUR1UEPaFObGD2tSvVKbvR+uT1Nrg==", - "cpu": [ - "loong64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.11.tgz", - "integrity": "sha512-B5x9j0OgjG+v1dF2DkH34lr+7Gmv0kzX6/V0afF41FkPMMqaQ77pH7CrhWeR22aEeHKaeZVtZ6yFwlxOKPVFyg==", - "cpu": [ - "mips64el" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.11.tgz", - "integrity": "sha512-MHrZYLeCG8vXblMetWyttkdVRjQlQUb/oMgBNurVEnhj4YWOr4G5lmBfZjHYQHHN0g6yDmCAQRR8MUHldvvRDA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.11.tgz", - "integrity": "sha512-f3DY++t94uVg141dozDu4CCUkYW+09rWtaWfnb3bqe4w5NqmZd6nPVBm+qbz7WaHZCoqXqHz5p6CM6qv3qnSSQ==", - "cpu": [ - "riscv64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.11.tgz", - "integrity": "sha512-A5xdUoyWJHMMlcSMcPGVLzYzpcY8QP1RtYzX5/bS4dvjBGVxdhuiYyFwp7z74ocV7WDc0n1harxmpq2ePOjI0Q==", - "cpu": [ - "s390x" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.11.tgz", - "integrity": "sha512-grbyMlVCvJSfxFQUndw5mCtWs5LO1gUlwP4CDi4iJBbVpZcqLVT29FxgGuBJGSzyOxotFG4LoO5X+M1350zmPA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.11.tgz", - "integrity": "sha512-13jvrQZJc3P230OhU8xgwUnDeuC/9egsjTkXN49b3GcS5BKvJqZn86aGM8W9pd14Kd+u7HuFBMVtrNGhh6fHEQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.11.tgz", - "integrity": "sha512-ysyOGZuTp6SNKPE11INDUeFVVQFrhcNDVUgSQVDzqsqX38DjhPEPATpid04LCoUr2WXhQTEZ8ct/EgJCUDpyNw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.11.tgz", - "integrity": "sha512-Hf+Sad9nVwvtxy4DXCZQqLpgmRTQqyFyhT3bZ4F2XlJCjxGmRFF0Shwn9rzhOYRB61w9VMXUkxlBy56dk9JJiQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.11.tgz", - "integrity": "sha512-0P58Sbi0LctOMOQbpEOvOL44Ne0sqbS0XWHMvvrg6NE5jQ1xguCSSw9jQeUk2lfrXYsKDdOe6K+oZiwKPilYPQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.11.tgz", - "integrity": "sha512-6YOrWS+sDJDmshdBIQU+Uoyh7pQKrdykdefC1avn76ss5c+RN6gut3LZA4E2cH5xUEp5/cA0+YxRaVtRAb0xBg==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.11.tgz", - "integrity": "sha512-vfkhltrjCAb603XaFhqhAF4LGDi2M4OrCRrFusyQ+iTLQ/o60QQXxc9cZC/FFpihBI9N1Grn6SMKVJ4KP7Fuiw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@jest/schemas": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", - "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", - "dev": true, - "dependencies": { - "@sinclair/typebox": "^0.27.8" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.15", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", - "dev": true - }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.9.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.9.5.tgz", - "integrity": "sha512-idWaG8xeSRCfRq9KpRysDHJ/rEHBEXcHuJ82XY0yYFIWnLMjZv9vF/7DOq8djQ2n3Lk6+3qfSH8AqlmHlmi1MA==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.9.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.9.5.tgz", - "integrity": "sha512-f14d7uhAMtsCGjAYwZGv6TwuS3IFaM4ZnGMUn3aCBgkcHAYErhV1Ad97WzBvS2o0aaDv4mVz+syiN0ElMyfBPg==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.9.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.9.5.tgz", - "integrity": "sha512-ndoXeLx455FffL68OIUrVr89Xu1WLzAG4n65R8roDlCoYiQcGGg6MALvs2Ap9zs7AHg8mpHtMpwC8jBBjZrT/w==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.9.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.9.5.tgz", - "integrity": "sha512-UmElV1OY2m/1KEEqTlIjieKfVwRg0Zwg4PLgNf0s3glAHXBN99KLpw5A5lrSYCa1Kp63czTpVll2MAqbZYIHoA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.9.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.9.5.tgz", - "integrity": "sha512-Q0LcU61v92tQB6ae+udZvOyZ0wfpGojtAKrrpAaIqmJ7+psq4cMIhT/9lfV6UQIpeItnq/2QDROhNLo00lOD1g==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.9.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.9.5.tgz", - "integrity": "sha512-dkRscpM+RrR2Ee3eOQmRWFjmV/payHEOrjyq1VZegRUa5OrZJ2MAxBNs05bZuY0YCtpqETDy1Ix4i/hRqX98cA==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.9.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.9.5.tgz", - "integrity": "sha512-QaKFVOzzST2xzY4MAmiDmURagWLFh+zZtttuEnuNn19AiZ0T3fhPyjPPGwLNdiDT82ZE91hnfJsUiDwF9DClIQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.9.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.9.5.tgz", - "integrity": "sha512-HeGqmRJuyVg6/X6MpE2ur7GbymBPS8Np0S/vQFHDmocfORT+Zt76qu+69NUoxXzGqVP1pzaY6QIi0FJWLC3OPA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.9.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.9.5.tgz", - "integrity": "sha512-Dq1bqBdLaZ1Gb/l2e5/+o3B18+8TI9ANlA1SkejZqDgdU/jK/ThYaMPMJpVMMXy2uRHvGKbkz9vheVGdq3cJfA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.9.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.9.5.tgz", - "integrity": "sha512-ezyFUOwldYpj7AbkwyW9AJ203peub81CaAIVvckdkyH8EvhEIoKzaMFJj0G4qYJ5sw3BpqhFrsCc30t54HV8vg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.9.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.9.5.tgz", - "integrity": "sha512-aHSsMnUw+0UETB0Hlv7B/ZHOGY5bQdwMKJSzGfDfvyhnpmVxLMGnQPGNE9wgqkLUs3+gbG1Qx02S2LLfJ5GaRQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.9.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.9.5.tgz", - "integrity": "sha512-AiqiLkb9KSf7Lj/o1U3SEP9Zn+5NuVKgFdRIZkvd4N0+bYrTOovVd0+LmYCPQGbocT4kvFyK+LXCDiXPBF3fyA==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.9.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.9.5.tgz", - "integrity": "sha512-1q+mykKE3Vot1kaFJIDoUFv5TuW+QQVaf2FmTT9krg86pQrGStOSJJ0Zil7CFagyxDuouTepzt5Y5TVzyajOdQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@shikijs/core": { - "version": "1.0.0-beta.5", - "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-1.0.0-beta.5.tgz", - "integrity": "sha512-C/MxtvK3FFCQZSsDq6OfjDHHOmyP1Jc9wO66cnE8VLEyWXzWch7Zpoc2MWuVJTSC0Pz9QxyUlsBCnroplFqoSg==", - "dev": true - }, - "node_modules/@shikijs/transformers": { - "version": "1.0.0-beta.5", - "resolved": "https://registry.npmjs.org/@shikijs/transformers/-/transformers-1.0.0-beta.5.tgz", - "integrity": "sha512-Kd3312yH6sh8Jw0xjBFfGpXTU3Qts1bwuB19wDDoKRvJqjrkffftdSuKzhHPa+DP/L0ZFhq96xMPngzQ15rQmQ==", - "dev": true, - "dependencies": { - "shiki": "1.0.0-beta.5" - } - }, - "node_modules/@sinclair/typebox": { - "version": "0.27.8", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", - "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", - "dev": true - }, - "node_modules/@types/estree": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", - "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", - "dev": true - }, - "node_modules/@types/linkify-it": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-3.0.5.tgz", - "integrity": "sha512-yg6E+u0/+Zjva+buc3EIb+29XEg4wltq7cSmd4Uc2EE/1nUVmxyzpX6gUXD0V8jIrG0r7YeOGVIbYRkxeooCtw==", - "dev": true - }, - "node_modules/@types/markdown-it": { - "version": "13.0.7", - "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-13.0.7.tgz", - "integrity": "sha512-U/CBi2YUUcTHBt5tjO2r5QV/x0Po6nsYwQU4Y04fBS6vfoImaiZ6f8bi3CjTCxBPQSO1LMyUqkByzi8AidyxfA==", - "dev": true, - "dependencies": { - "@types/linkify-it": "*", - "@types/mdurl": "*" - } - }, - "node_modules/@types/mdurl": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-1.0.5.tgz", - "integrity": "sha512-6L6VymKTzYSrEf4Nev4Xa1LCHKrlTlYCBMTlQKFuddo1CvQcE52I0mwfOJayueUC7MJuXOeHTcIU683lzd0cUA==", - "dev": true - }, - "node_modules/@types/web-bluetooth": { - "version": "0.0.20", - "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.20.tgz", - "integrity": "sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==", - "dev": true - }, - "node_modules/@vitejs/plugin-vue": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.0.3.tgz", - "integrity": "sha512-b8S5dVS40rgHdDrw+DQi/xOM9ed+kSRZzfm1T74bMmBDCd8XO87NKlFYInzCtwvtWwXZvo1QxE2OSspTATWrbA==", - "dev": true, - "engines": { - "node": "^18.0.0 || >=20.0.0" - }, - "peerDependencies": { - "vite": "^5.0.0", - "vue": "^3.2.25" - } - }, - "node_modules/@vitest/expect": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.2.0.tgz", - "integrity": "sha512-H+2bHzhyvgp32o7Pgj2h9RTHN0pgYaoi26Oo3mE+dCi1PAqV31kIIVfTbqMO3Bvshd5mIrJLc73EwSRrbol9Lw==", - "dev": true, - "dependencies": { - "@vitest/spy": "1.2.0", - "@vitest/utils": "1.2.0", - "chai": "^4.3.10" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/runner": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.2.0.tgz", - "integrity": "sha512-vaJkDoQaNUTroT70OhM0NPznP7H3WyRwt4LvGwCVYs/llLaqhoSLnlIhUClZpbF5RgAee29KRcNz0FEhYcgxqA==", - "dev": true, - "dependencies": { - "@vitest/utils": "1.2.0", - "p-limit": "^5.0.0", - "pathe": "^1.1.1" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/snapshot": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.2.0.tgz", - "integrity": "sha512-P33EE7TrVgB3HDLllrjK/GG6WSnmUtWohbwcQqmm7TAk9AVHpdgf7M3F3qRHKm6vhr7x3eGIln7VH052Smo6Kw==", - "dev": true, - "dependencies": { - "magic-string": "^0.30.5", - "pathe": "^1.1.1", - "pretty-format": "^29.7.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/spy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.2.0.tgz", - "integrity": "sha512-MNxSAfxUaCeowqyyGwC293yZgk7cECZU9wGb8N1pYQ0yOn/SIr8t0l9XnGRdQZvNV/ZHBYu6GO/W3tj5K3VN1Q==", - "dev": true, - "dependencies": { - "tinyspy": "^2.2.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/utils": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.2.0.tgz", - "integrity": "sha512-FyD5bpugsXlwVpTcGLDf3wSPYy8g541fQt14qtzo8mJ4LdEpDKZ9mQy2+qdJm2TZRpjY5JLXihXCgIxiRJgi5g==", - "dev": true, - "dependencies": { - "diff-sequences": "^29.6.3", - "estree-walker": "^3.0.3", - "loupe": "^2.3.7", - "pretty-format": "^29.7.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vue/compiler-core": { - "version": "3.4.15", - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.15.tgz", - "integrity": "sha512-XcJQVOaxTKCnth1vCxEChteGuwG6wqnUHxAm1DO3gCz0+uXKaJNx8/digSz4dLALCy8n2lKq24jSUs8segoqIw==", - "dev": true, - "dependencies": { - "@babel/parser": "^7.23.6", - "@vue/shared": "3.4.15", - "entities": "^4.5.0", - "estree-walker": "^2.0.2", - "source-map-js": "^1.0.2" - } - }, - "node_modules/@vue/compiler-core/node_modules/estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "dev": true - }, - "node_modules/@vue/compiler-dom": { - "version": "3.4.15", - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.15.tgz", - "integrity": "sha512-wox0aasVV74zoXyblarOM3AZQz/Z+OunYcIHe1OsGclCHt8RsRm04DObjefaI82u6XDzv+qGWZ24tIsRAIi5MQ==", - "dev": true, - "dependencies": { - "@vue/compiler-core": "3.4.15", - "@vue/shared": "3.4.15" - } - }, - "node_modules/@vue/compiler-sfc": { - "version": "3.4.15", - "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.15.tgz", - "integrity": "sha512-LCn5M6QpkpFsh3GQvs2mJUOAlBQcCco8D60Bcqmf3O3w5a+KWS5GvYbrrJBkgvL1BDnTp+e8q0lXCLgHhKguBA==", - "dev": true, - "dependencies": { - "@babel/parser": "^7.23.6", - "@vue/compiler-core": "3.4.15", - "@vue/compiler-dom": "3.4.15", - "@vue/compiler-ssr": "3.4.15", - "@vue/shared": "3.4.15", - "estree-walker": "^2.0.2", - "magic-string": "^0.30.5", - "postcss": "^8.4.33", - "source-map-js": "^1.0.2" - } - }, - "node_modules/@vue/compiler-sfc/node_modules/estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "dev": true - }, - "node_modules/@vue/compiler-ssr": { - "version": "3.4.15", - "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.15.tgz", - "integrity": "sha512-1jdeQyiGznr8gjFDadVmOJqZiLNSsMa5ZgqavkPZ8O2wjHv0tVuAEsw5hTdUoUW4232vpBbL/wJhzVW/JwY1Uw==", - "dev": true, - "dependencies": { - "@vue/compiler-dom": "3.4.15", - "@vue/shared": "3.4.15" - } - }, - "node_modules/@vue/devtools-api": { - "version": "7.0.14", - "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-7.0.14.tgz", - "integrity": "sha512-TluWR9qZ6aO11bwtYK8+fzXxBqLfsE0mWZz1q/EQBmO9k82Cm6deieLwNNXjNFJz7xutazoia5Qa+zTYkPPOfw==", - "dev": true, - "dependencies": { - "@vue/devtools-kit": "^7.0.14" - } - }, - "node_modules/@vue/devtools-kit": { - "version": "7.0.14", - "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-7.0.14.tgz", - "integrity": "sha512-wAAJazr4hI0aVRpgWOCVPw+NzMQdthhnprHHIg4njp1MkKrpCNGQ7MtQbZF1AltAA7xpMCGyyt+0kYH0FqTiPg==", - "dev": true, - "dependencies": { - "@vue/devtools-schema": "^7.0.14", - "@vue/devtools-shared": "^7.0.14", - "hookable": "^5.5.3", - "mitt": "^3.0.1", - "perfect-debounce": "^1.0.0", - "speakingurl": "^14.0.1" - } - }, - "node_modules/@vue/devtools-schema": { - "version": "7.0.14", - "resolved": "https://registry.npmjs.org/@vue/devtools-schema/-/devtools-schema-7.0.14.tgz", - "integrity": "sha512-tpUeCLVrdHX+KzWMLTAwx/vAPFbo6jAUi7sr6Q+0mBIqIVSSIxNr5wEhegiFvYva+OtDeM2OrT+f7/X/5bvZNg==", - "dev": true - }, - "node_modules/@vue/devtools-shared": { - "version": "7.0.14", - "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-7.0.14.tgz", - "integrity": "sha512-79RP1NDakBVWou9rDpVnT1WMjTbL1lJKm6YEOodjQ0dq5ehf0wsRbeYDhgAlnjehWRzTq5GAYFBFUPYBs0/QpA==", - "dev": true, - "dependencies": { - "rfdc": "^1.3.1" - } - }, - "node_modules/@vue/reactivity": { - "version": "3.4.15", - "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.15.tgz", - "integrity": "sha512-55yJh2bsff20K5O84MxSvXKPHHt17I2EomHznvFiJCAZpJTNW8IuLj1xZWMLELRhBK3kkFV/1ErZGHJfah7i7w==", - "dev": true, - "dependencies": { - "@vue/shared": "3.4.15" - } - }, - "node_modules/@vue/runtime-core": { - "version": "3.4.15", - "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.15.tgz", - "integrity": "sha512-6E3by5m6v1AkW0McCeAyhHTw+3y17YCOKG0U0HDKDscV4Hs0kgNT5G+GCHak16jKgcCDHpI9xe5NKb8sdLCLdw==", - "dev": true, - "dependencies": { - "@vue/reactivity": "3.4.15", - "@vue/shared": "3.4.15" - } - }, - "node_modules/@vue/runtime-dom": { - "version": "3.4.15", - "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.15.tgz", - "integrity": "sha512-EVW8D6vfFVq3V/yDKNPBFkZKGMFSvZrUQmx196o/v2tHKdwWdiZjYUBS+0Ez3+ohRyF8Njwy/6FH5gYJ75liUw==", - "dev": true, - "dependencies": { - "@vue/runtime-core": "3.4.15", - "@vue/shared": "3.4.15", - "csstype": "^3.1.3" - } - }, - "node_modules/@vue/server-renderer": { - "version": "3.4.15", - "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.15.tgz", - "integrity": "sha512-3HYzaidu9cHjrT+qGUuDhFYvF/j643bHC6uUN9BgM11DVy+pM6ATsG6uPBLnkwOgs7BpJABReLmpL3ZPAsUaqw==", - "dev": true, - "dependencies": { - "@vue/compiler-ssr": "3.4.15", - "@vue/shared": "3.4.15" - }, - "peerDependencies": { - "vue": "3.4.15" - } - }, - "node_modules/@vue/shared": { - "version": "3.4.15", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.15.tgz", - "integrity": "sha512-KzfPTxVaWfB+eGcGdbSf4CWdaXcGDqckoeXUh7SB3fZdEtzPCK2Vq9B/lRRL3yutax/LWITz+SwvgyOxz5V75g==", - "dev": true - }, - "node_modules/@vueuse/core": { - "version": "10.7.2", - "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-10.7.2.tgz", - "integrity": "sha512-AOyAL2rK0By62Hm+iqQn6Rbu8bfmbgaIMXcE3TSr7BdQ42wnSFlwIdPjInO62onYsEMK/yDMU8C6oGfDAtZ2qQ==", - "dev": true, - "dependencies": { - "@types/web-bluetooth": "^0.0.20", - "@vueuse/metadata": "10.7.2", - "@vueuse/shared": "10.7.2", - "vue-demi": ">=0.14.6" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/@vueuse/core/node_modules/vue-demi": { - "version": "0.14.6", - "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.6.tgz", - "integrity": "sha512-8QA7wrYSHKaYgUxDA5ZC24w+eHm3sYCbp0EzcDwKqN3p6HqtTCGR/GVsPyZW92unff4UlcSh++lmqDWN3ZIq4w==", - "dev": true, - "hasInstallScript": true, - "bin": { - "vue-demi-fix": "bin/vue-demi-fix.js", - "vue-demi-switch": "bin/vue-demi-switch.js" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - }, - "peerDependencies": { - "@vue/composition-api": "^1.0.0-rc.1", - "vue": "^3.0.0-0 || ^2.6.0" - }, - "peerDependenciesMeta": { - "@vue/composition-api": { - "optional": true - } - } - }, - "node_modules/@vueuse/integrations": { - "version": "10.7.2", - "resolved": "https://registry.npmjs.org/@vueuse/integrations/-/integrations-10.7.2.tgz", - "integrity": "sha512-+u3RLPFedjASs5EKPc69Ge49WNgqeMfSxFn+qrQTzblPXZg6+EFzhjarS5edj2qAf6xQ93f95TUxRwKStXj/sQ==", - "dev": true, - "dependencies": { - "@vueuse/core": "10.7.2", - "@vueuse/shared": "10.7.2", - "vue-demi": ">=0.14.6" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - }, - "peerDependencies": { - "async-validator": "*", - "axios": "*", - "change-case": "*", - "drauu": "*", - "focus-trap": "*", - "fuse.js": "*", - "idb-keyval": "*", - "jwt-decode": "*", - "nprogress": "*", - "qrcode": "*", - "sortablejs": "*", - "universal-cookie": "*" - }, - "peerDependenciesMeta": { - "async-validator": { - "optional": true - }, - "axios": { - "optional": true - }, - "change-case": { - "optional": true - }, - "drauu": { - "optional": true - }, - "focus-trap": { - "optional": true - }, - "fuse.js": { - "optional": true - }, - "idb-keyval": { - "optional": true - }, - "jwt-decode": { - "optional": true - }, - "nprogress": { - "optional": true - }, - "qrcode": { - "optional": true - }, - "sortablejs": { - "optional": true - }, - "universal-cookie": { - "optional": true - } - } - }, - "node_modules/@vueuse/integrations/node_modules/vue-demi": { - "version": "0.14.6", - "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.6.tgz", - "integrity": "sha512-8QA7wrYSHKaYgUxDA5ZC24w+eHm3sYCbp0EzcDwKqN3p6HqtTCGR/GVsPyZW92unff4UlcSh++lmqDWN3ZIq4w==", - "dev": true, - "hasInstallScript": true, - "bin": { - "vue-demi-fix": "bin/vue-demi-fix.js", - "vue-demi-switch": "bin/vue-demi-switch.js" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - }, - "peerDependencies": { - "@vue/composition-api": "^1.0.0-rc.1", - "vue": "^3.0.0-0 || ^2.6.0" - }, - "peerDependenciesMeta": { - "@vue/composition-api": { - "optional": true - } - } - }, - "node_modules/@vueuse/metadata": { - "version": "10.7.2", - "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-10.7.2.tgz", - "integrity": "sha512-kCWPb4J2KGrwLtn1eJwaJD742u1k5h6v/St5wFe8Quih90+k2a0JP8BS4Zp34XUuJqS2AxFYMb1wjUL8HfhWsQ==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/@vueuse/shared": { - "version": "10.7.2", - "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-10.7.2.tgz", - "integrity": "sha512-qFbXoxS44pi2FkgFjPvF4h7c9oMDutpyBdcJdMYIMg9XyXli2meFMuaKn+UMgsClo//Th6+beeCgqweT/79BVA==", - "dev": true, - "dependencies": { - "vue-demi": ">=0.14.6" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/@vueuse/shared/node_modules/vue-demi": { - "version": "0.14.6", - "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.6.tgz", - "integrity": "sha512-8QA7wrYSHKaYgUxDA5ZC24w+eHm3sYCbp0EzcDwKqN3p6HqtTCGR/GVsPyZW92unff4UlcSh++lmqDWN3ZIq4w==", - "dev": true, - "hasInstallScript": true, - "bin": { - "vue-demi-fix": "bin/vue-demi-fix.js", - "vue-demi-switch": "bin/vue-demi-switch.js" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - }, - "peerDependencies": { - "@vue/composition-api": "^1.0.0-rc.1", - "vue": "^3.0.0-0 || ^2.6.0" - }, - "peerDependenciesMeta": { - "@vue/composition-api": { - "optional": true - } - } - }, - "node_modules/acorn": { - "version": "8.11.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", - "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-walk": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", - "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/algoliasearch": { - "version": "4.22.1", - "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-4.22.1.tgz", - "integrity": "sha512-jwydKFQJKIx9kIZ8Jm44SdpigFwRGPESaxZBaHSV0XWN2yBJAOT4mT7ppvlrpA4UGzz92pqFnVKr/kaZXrcreg==", - "dev": true, - "dependencies": { - "@algolia/cache-browser-local-storage": "4.22.1", - "@algolia/cache-common": "4.22.1", - "@algolia/cache-in-memory": "4.22.1", - "@algolia/client-account": "4.22.1", - "@algolia/client-analytics": "4.22.1", - "@algolia/client-common": "4.22.1", - "@algolia/client-personalization": "4.22.1", - "@algolia/client-search": "4.22.1", - "@algolia/logger-common": "4.22.1", - "@algolia/logger-console": "4.22.1", - "@algolia/requester-browser-xhr": "4.22.1", - "@algolia/requester-common": "4.22.1", - "@algolia/requester-node-http": "4.22.1", - "@algolia/transporter": "4.22.1" - } - }, - "node_modules/ansi-colors": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", - "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/assertion-error": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", - "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/boolbase": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", - "dev": true - }, - "node_modules/cac": { - "version": "6.7.14", - "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", - "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/chai": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.4.1.tgz", - "integrity": "sha512-13sOfMv2+DWduEU+/xbun3LScLoqN17nBeTLUsmDfKdoiC1fr0n9PU4guu4AhRcOVFk/sW8LyZWHuhWtQZiF+g==", - "dev": true, - "dependencies": { - "assertion-error": "^1.1.0", - "check-error": "^1.0.3", - "deep-eql": "^4.1.3", - "get-func-name": "^2.0.2", - "loupe": "^2.3.6", - "pathval": "^1.1.1", - "type-detect": "^4.0.8" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/check-error": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", - "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==", - "dev": true, - "dependencies": { - "get-func-name": "^2.0.2" - }, - "engines": { - "node": "*" - } - }, - "node_modules/cheerio": { - "version": "1.0.0-rc.10", - "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.10.tgz", - "integrity": "sha512-g0J0q/O6mW8z5zxQ3A8E8J1hUgp4SMOvEoW/x84OwyHKe/Zccz83PVT4y5Crcr530FV6NgmKI1qvGTKVl9XXVw==", - "dev": true, - "dependencies": { - "cheerio-select": "^1.5.0", - "dom-serializer": "^1.3.2", - "domhandler": "^4.2.0", - "htmlparser2": "^6.1.0", - "parse5": "^6.0.1", - "parse5-htmlparser2-tree-adapter": "^6.0.1", - "tslib": "^2.2.0" - }, - "engines": { - "node": ">= 6" - }, - "funding": { - "url": "https://github.com/cheeriojs/cheerio?sponsor=1" - } - }, - "node_modules/cheerio-select": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-1.6.0.tgz", - "integrity": "sha512-eq0GdBvxVFbqWgmCm7M3XGs1I8oLy/nExUnh6oLqmBditPO9AqQJrkslDpMun/hZ0yyTs8L0m85OHp4ho6Qm9g==", - "dev": true, - "dependencies": { - "css-select": "^4.3.0", - "css-what": "^6.0.1", - "domelementtype": "^2.2.0", - "domhandler": "^4.3.1", - "domutils": "^2.8.0" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/commander": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", - "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", - "dev": true, - "engines": { - "node": ">= 6" - } - }, - "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/css-select": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz", - "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==", - "dev": true, - "dependencies": { - "boolbase": "^1.0.0", - "css-what": "^6.0.1", - "domhandler": "^4.3.1", - "domutils": "^2.8.0", - "nth-check": "^2.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/css-what": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", - "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", - "dev": true, - "engines": { - "node": ">= 6" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/csstype": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "dev": true - }, - "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/deep-eql": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", - "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==", - "dev": true, - "dependencies": { - "type-detect": "^4.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/diff-sequences": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", - "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", - "dev": true, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/dom-serializer": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", - "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", - "dev": true, - "dependencies": { - "domelementtype": "^2.0.1", - "domhandler": "^4.2.0", - "entities": "^2.0.0" - }, - "funding": { - "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" - } - }, - "node_modules/dom-serializer/node_modules/entities": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", - "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", - "dev": true, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/domelementtype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", - "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ] - }, - "node_modules/domhandler": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", - "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", - "dev": true, - "dependencies": { - "domelementtype": "^2.2.0" - }, - "engines": { - "node": ">= 4" - }, - "funding": { - "url": "https://github.com/fb55/domhandler?sponsor=1" - } - }, - "node_modules/domutils": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", - "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", - "dev": true, - "dependencies": { - "dom-serializer": "^1.0.1", - "domelementtype": "^2.2.0", - "domhandler": "^4.2.0" - }, - "funding": { - "url": "https://github.com/fb55/domutils?sponsor=1" - } - }, - "node_modules/entities": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "dev": true, - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/esbuild": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.11.tgz", - "integrity": "sha512-HJ96Hev2hX/6i5cDVwcqiJBBtuo9+FeIJOtZ9W1kA5M6AMJRHUZlpYZ1/SbEwtO0ioNAW8rUooVpC/WehY2SfA==", - "dev": true, - "hasInstallScript": true, - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=12" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.19.11", - "@esbuild/android-arm": "0.19.11", - "@esbuild/android-arm64": "0.19.11", - "@esbuild/android-x64": "0.19.11", - "@esbuild/darwin-arm64": "0.19.11", - "@esbuild/darwin-x64": "0.19.11", - "@esbuild/freebsd-arm64": "0.19.11", - "@esbuild/freebsd-x64": "0.19.11", - "@esbuild/linux-arm": "0.19.11", - "@esbuild/linux-arm64": "0.19.11", - "@esbuild/linux-ia32": "0.19.11", - "@esbuild/linux-loong64": "0.19.11", - "@esbuild/linux-mips64el": "0.19.11", - "@esbuild/linux-ppc64": "0.19.11", - "@esbuild/linux-riscv64": "0.19.11", - "@esbuild/linux-s390x": "0.19.11", - "@esbuild/linux-x64": "0.19.11", - "@esbuild/netbsd-x64": "0.19.11", - "@esbuild/openbsd-x64": "0.19.11", - "@esbuild/sunos-x64": "0.19.11", - "@esbuild/win32-arm64": "0.19.11", - "@esbuild/win32-ia32": "0.19.11", - "@esbuild/win32-x64": "0.19.11" - } - }, - "node_modules/escape-goat": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-3.0.0.tgz", - "integrity": "sha512-w3PwNZJwRxlp47QGzhuEBldEqVHHhh8/tIPcl6ecf2Bou99cdAt0knihBV0Ecc7CGxYduXVBDheH1K2oADRlvw==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/esm": { - "version": "3.2.25", - "resolved": "https://registry.npmjs.org/esm/-/esm-3.2.25.tgz", - "integrity": "sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/estree-walker": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", - "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", - "dev": true, - "dependencies": { - "@types/estree": "^1.0.0" - } - }, - "node_modules/execa": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", - "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", - "dev": true, - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^8.0.1", - "human-signals": "^5.0.0", - "is-stream": "^3.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^5.1.0", - "onetime": "^6.0.0", - "signal-exit": "^4.1.0", - "strip-final-newline": "^3.0.0" - }, - "engines": { - "node": ">=16.17" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/focus-trap": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-7.5.4.tgz", - "integrity": "sha512-N7kHdlgsO/v+iD/dMoJKtsSqs5Dz/dXZVebRgJw23LDk+jMi/974zyiOYDziY2JPp8xivq9BmUGwIJMiuSBi7w==", - "dev": true, - "dependencies": { - "tabbable": "^6.2.0" - } - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/get-func-name": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", - "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/get-stream": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", - "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", - "dev": true, - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/hookable": { - "version": "5.5.3", - "resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz", - "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==", - "dev": true - }, - "node_modules/htmlparser2": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz", - "integrity": "sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==", - "dev": true, - "funding": [ - "https://github.com/fb55/htmlparser2?sponsor=1", - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ], - "dependencies": { - "domelementtype": "^2.0.1", - "domhandler": "^4.0.0", - "domutils": "^2.5.2", - "entities": "^2.0.0" - } - }, - "node_modules/htmlparser2/node_modules/entities": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", - "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", - "dev": true, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/human-signals": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", - "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", - "dev": true, - "engines": { - "node": ">=16.17.0" - } - }, - "node_modules/is-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", - "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", - "dev": true, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true - }, - "node_modules/jsonc-parser": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", - "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==", - "dev": true - }, - "node_modules/juice": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/juice/-/juice-8.1.0.tgz", - "integrity": "sha512-FLzurJrx5Iv1e7CfBSZH68dC04EEvXvvVvPYB7Vx1WAuhCp1ZPIMtqxc+WTWxVkpTIC2Ach/GAv0rQbtGf6YMA==", - "dev": true, - "dependencies": { - "cheerio": "1.0.0-rc.10", - "commander": "^6.1.0", - "mensch": "^0.3.4", - "slick": "^1.12.2", - "web-resource-inliner": "^6.0.1" - }, - "bin": { - "juice": "bin/juice" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/local-pkg": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.5.0.tgz", - "integrity": "sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg==", - "dev": true, - "dependencies": { - "mlly": "^1.4.2", - "pkg-types": "^1.0.3" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/loupe": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", - "integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==", - "dev": true, - "dependencies": { - "get-func-name": "^2.0.1" - } - }, - "node_modules/magic-string": { - "version": "0.30.5", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.5.tgz", - "integrity": "sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==", - "dev": true, - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.4.15" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/mark.js": { - "version": "8.11.1", - "resolved": "https://registry.npmjs.org/mark.js/-/mark.js-8.11.1.tgz", - "integrity": "sha512-1I+1qpDt4idfgLQG+BNWmrqku+7/2bi5nLf4YwF8y8zXvmfiTBY3PV3ZibfrjBueCByROpuBjLLFCajqkgYoLQ==", - "dev": true - }, - "node_modules/markdown-it-mathjax3": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/markdown-it-mathjax3/-/markdown-it-mathjax3-4.3.2.tgz", - "integrity": "sha512-TX3GW5NjmupgFtMJGRauioMbbkGsOXAAt1DZ/rzzYmTHqzkO1rNAdiMD4NiruurToPApn2kYy76x02QN26qr2w==", - "dev": true, - "dependencies": { - "juice": "^8.0.0", - "mathjax-full": "^3.2.0" - } - }, - "node_modules/mathjax-full": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/mathjax-full/-/mathjax-full-3.2.2.tgz", - "integrity": "sha512-+LfG9Fik+OuI8SLwsiR02IVdjcnRCy5MufYLi0C3TdMT56L/pjB0alMVGgoWJF8pN9Rc7FESycZB9BMNWIid5w==", - "dev": true, - "dependencies": { - "esm": "^3.2.25", - "mhchemparser": "^4.1.0", - "mj-context-menu": "^0.6.1", - "speech-rule-engine": "^4.0.6" - } - }, - "node_modules/mensch": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/mensch/-/mensch-0.3.4.tgz", - "integrity": "sha512-IAeFvcOnV9V0Yk+bFhYR07O3yNina9ANIN5MoXBKYJ/RLYPurd2d0yw14MDhpr9/momp0WofT1bPUh3hkzdi/g==", - "dev": true - }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true - }, - "node_modules/mhchemparser": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/mhchemparser/-/mhchemparser-4.2.1.tgz", - "integrity": "sha512-kYmyrCirqJf3zZ9t/0wGgRZ4/ZJw//VwaRVGA75C4nhE60vtnIzhl9J9ndkX/h6hxSN7pjg/cE0VxbnNM+bnDQ==", - "dev": true - }, - "node_modules/mime": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", - "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", - "dev": true, - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/mimic-fn": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", - "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/minisearch": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/minisearch/-/minisearch-6.3.0.tgz", - "integrity": "sha512-ihFnidEeU8iXzcVHy74dhkxh/dn8Dc08ERl0xwoMMGqp4+LvRSCgicb+zGqWthVokQKvCSxITlh3P08OzdTYCQ==", - "dev": true - }, - "node_modules/mitt": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", - "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", - "dev": true - }, - "node_modules/mj-context-menu": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/mj-context-menu/-/mj-context-menu-0.6.1.tgz", - "integrity": "sha512-7NO5s6n10TIV96d4g2uDpG7ZDpIhMh0QNfGdJw/W47JswFcosz457wqz/b5sAKvl12sxINGFCn80NZHKwxQEXA==", - "dev": true - }, - "node_modules/mlly": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.5.0.tgz", - "integrity": "sha512-NPVQvAY1xr1QoVeG0cy8yUYC7FQcOx6evl/RjT1wL5FvzPnzOysoqB/jmx/DhssT2dYa8nxECLAaFI/+gVLhDQ==", - "dev": true, - "dependencies": { - "acorn": "^8.11.3", - "pathe": "^1.1.2", - "pkg-types": "^1.0.3", - "ufo": "^1.3.2" - } - }, - "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "node_modules/nanoid": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", - "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", - "dev": true, - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, - "node_modules/npm-run-path": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.2.0.tgz", - "integrity": "sha512-W4/tgAXFqFA0iL7fk0+uQ3g7wkL8xJmx3XdK0VGb4cHW//eZTtKGvFBBoRKVTpY7n6ze4NL9ly7rgXcHufqXKg==", - "dev": true, - "dependencies": { - "path-key": "^4.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/npm-run-path/node_modules/path-key": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", - "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/nth-check": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", - "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", - "dev": true, - "dependencies": { - "boolbase": "^1.0.0" - }, - "funding": { - "url": "https://github.com/fb55/nth-check?sponsor=1" - } - }, - "node_modules/onetime": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", - "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", - "dev": true, - "dependencies": { - "mimic-fn": "^4.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-limit": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-5.0.0.tgz", - "integrity": "sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==", - "dev": true, - "dependencies": { - "yocto-queue": "^1.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/parse5": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", - "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", - "dev": true - }, - "node_modules/parse5-htmlparser2-tree-adapter": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz", - "integrity": "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==", - "dev": true, - "dependencies": { - "parse5": "^6.0.1" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/pathe": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", - "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", - "dev": true - }, - "node_modules/pathval": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", - "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/perfect-debounce": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", - "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", - "dev": true - }, - "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true - }, - "node_modules/pkg-types": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.0.3.tgz", - "integrity": "sha512-nN7pYi0AQqJnoLPC9eHFQ8AcyaixBUOwvqc5TDnIKCMEE6I0y8P7OKA7fPexsXGCGxQDl/cmrLAp26LhcwxZ4A==", - "dev": true, - "dependencies": { - "jsonc-parser": "^3.2.0", - "mlly": "^1.2.0", - "pathe": "^1.1.0" - } - }, - "node_modules/postcss": { - "version": "8.4.33", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.33.tgz", - "integrity": "sha512-Kkpbhhdjw2qQs2O2DGX+8m5OVqEcbB9HRBvuYM9pgrjEFUg30A9LmXNlTAUj4S9kgtGyrMbTzVjH7E+s5Re2yg==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "dependencies": { - "nanoid": "^3.3.7", - "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/preact": { - "version": "10.19.3", - "resolved": "https://registry.npmjs.org/preact/-/preact-10.19.3.tgz", - "integrity": "sha512-nHHTeFVBTHRGxJXKkKu5hT8C/YWBkPso4/Gad6xuj5dbptt9iF9NZr9pHbPhBrnT2klheu7mHTxTZ/LjwJiEiQ==", - "dev": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/preact" - } - }, - "node_modules/pretty-format": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", - "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.6.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", - "dev": true - }, - "node_modules/rfdc": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.1.tgz", - "integrity": "sha512-r5a3l5HzYlIC68TpmYKlxWjmOP6wiPJ1vWv2HeLhNsRZMrCkxeqxiHlQ21oXmQ4F3SiryXBHhAD7JZqvOJjFmg==", - "dev": true - }, - "node_modules/rollup": { - "version": "4.9.5", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.9.5.tgz", - "integrity": "sha512-E4vQW0H/mbNMw2yLSqJyjtkHY9dslf/p0zuT1xehNRqUTBOFMqEjguDvqhXr7N7r/4ttb2jr4T41d3dncmIgbQ==", - "dev": true, - "dependencies": { - "@types/estree": "1.0.5" - }, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.9.5", - "@rollup/rollup-android-arm64": "4.9.5", - "@rollup/rollup-darwin-arm64": "4.9.5", - "@rollup/rollup-darwin-x64": "4.9.5", - "@rollup/rollup-linux-arm-gnueabihf": "4.9.5", - "@rollup/rollup-linux-arm64-gnu": "4.9.5", - "@rollup/rollup-linux-arm64-musl": "4.9.5", - "@rollup/rollup-linux-riscv64-gnu": "4.9.5", - "@rollup/rollup-linux-x64-gnu": "4.9.5", - "@rollup/rollup-linux-x64-musl": "4.9.5", - "@rollup/rollup-win32-arm64-msvc": "4.9.5", - "@rollup/rollup-win32-ia32-msvc": "4.9.5", - "@rollup/rollup-win32-x64-msvc": "4.9.5", - "fsevents": "~2.3.2" - } - }, - "node_modules/search-insights": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/search-insights/-/search-insights-2.13.0.tgz", - "integrity": "sha512-Orrsjf9trHHxFRuo9/rzm0KIWmgzE8RMlZMzuhZOJ01Rnz3D0YBAe+V6473t6/H6c7irs6Lt48brULAiRWb3Vw==", - "dev": true, - "peer": true - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/shiki": { - "version": "1.0.0-beta.5", - "resolved": "https://registry.npmjs.org/shiki/-/shiki-1.0.0-beta.5.tgz", - "integrity": "sha512-S5FV55ZH8zLicVyqlJZj8LYqh/VuUICDDNG/L9eDM9I4d69EX+FbgSnKRIuJIwLrmJfTiPoGVnH1HsHX5whP/g==", - "dev": true, - "dependencies": { - "@shikijs/core": "1.0.0-beta.5" - } - }, - "node_modules/siginfo": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", - "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", - "dev": true - }, - "node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/slick": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/slick/-/slick-1.12.2.tgz", - "integrity": "sha512-4qdtOGcBjral6YIBCWJ0ljFSKNLz9KkhbWtuGvUyRowl1kxfuE1x/Z/aJcaiilpb3do9bl5K7/1h9XC5wWpY/A==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/speakingurl": { - "version": "14.0.1", - "resolved": "https://registry.npmjs.org/speakingurl/-/speakingurl-14.0.1.tgz", - "integrity": "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/speech-rule-engine": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/speech-rule-engine/-/speech-rule-engine-4.0.7.tgz", - "integrity": "sha512-sJrL3/wHzNwJRLBdf6CjJWIlxC04iYKkyXvYSVsWVOiC2DSkHmxsqOhEeMsBA9XK+CHuNcsdkbFDnoUfAsmp9g==", - "dev": true, - "dependencies": { - "commander": "9.2.0", - "wicked-good-xpath": "1.3.0", - "xmldom-sre": "0.1.31" - }, - "bin": { - "sre": "bin/sre" - } - }, - "node_modules/speech-rule-engine/node_modules/commander": { - "version": "9.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-9.2.0.tgz", - "integrity": "sha512-e2i4wANQiSXgnrBlIatyHtP1odfUp0BbV5Y5nEGbxtIrStkEOAAzCUirvLBNXHLr7kwLvJl6V+4V3XV9x7Wd9w==", - "dev": true, - "engines": { - "node": "^12.20.0 || >=14" - } - }, - "node_modules/stackback": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", - "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", - "dev": true - }, - "node_modules/std-env": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.7.0.tgz", - "integrity": "sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==", - "dev": true - }, - "node_modules/strip-final-newline": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", - "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/strip-literal": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-1.3.0.tgz", - "integrity": "sha512-PugKzOsyXpArk0yWmUwqOZecSO0GH0bPoctLcqNDH9J04pVW3lflYE0ujElBGTloevcxF5MofAOZ7C5l2b+wLg==", - "dev": true, - "dependencies": { - "acorn": "^8.10.0" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/tabbable": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz", - "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==", - "dev": true - }, - "node_modules/tinybench": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.5.1.tgz", - "integrity": "sha512-65NKvSuAVDP/n4CqH+a9w2kTlLReS9vhsAP06MWx+/89nMinJyB2icyl58RIcqCmIggpojIGeuJGhjU1aGMBSg==", - "dev": true - }, - "node_modules/tinypool": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.8.1.tgz", - "integrity": "sha512-zBTCK0cCgRROxvs9c0CGK838sPkeokNGdQVUUwHAbynHFlmyJYj825f/oRs528HaIJ97lo0pLIlDUzwN+IorWg==", - "dev": true, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/tinyspy": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-2.2.0.tgz", - "integrity": "sha512-d2eda04AN/cPOR89F7Xv5bK/jrQEhmcLFe6HFldoeO9AJtps+fqEnh486vnT/8y4bw38pSyxDcTCAq+Ks2aJTg==", - "dev": true, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "dev": true - }, - "node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", - "dev": true - }, - "node_modules/type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/ufo": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.3.2.tgz", - "integrity": "sha512-o+ORpgGwaYQXgqGDwd+hkS4PuZ3QnmqMMxRuajK/a38L6fTpcE5GPIfrf+L/KemFzfUpeUQc1rRS1iDBozvnFA==", - "dev": true - }, - "node_modules/valid-data-url": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/valid-data-url/-/valid-data-url-3.0.1.tgz", - "integrity": "sha512-jOWVmzVceKlVVdwjNSenT4PbGghU0SBIizAev8ofZVgivk/TVHXSbNL8LP6M3spZvkR9/QolkyJavGSX5Cs0UA==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/vite": { - "version": "5.0.12", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.0.12.tgz", - "integrity": "sha512-4hsnEkG3q0N4Tzf1+t6NdN9dg/L3BM+q8SWgbSPnJvrgH2kgdyzfVJwbR1ic69/4uMJJ/3dqDZZE5/WwqW8U1w==", - "dev": true, - "dependencies": { - "esbuild": "^0.19.3", - "postcss": "^8.4.32", - "rollup": "^4.2.0" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^18.0.0 || >=20.0.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^18.0.0 || >=20.0.0", - "less": "*", - "lightningcss": "^1.21.0", - "sass": "*", - "stylus": "*", - "sugarss": "*", - "terser": "^5.4.0" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - } - } - }, - "node_modules/vite-node": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.2.0.tgz", - "integrity": "sha512-ETnQTHeAbbOxl7/pyBck9oAPZZZo+kYnFt1uQDD+hPReOc+wCjXw4r4jHriBRuVDB5isHmPXxrfc1yJnfBERqg==", - "dev": true, - "dependencies": { - "cac": "^6.7.14", - "debug": "^4.3.4", - "pathe": "^1.1.1", - "picocolors": "^1.0.0", - "vite": "^5.0.0" - }, - "bin": { - "vite-node": "vite-node.mjs" - }, - "engines": { - "node": "^18.0.0 || >=20.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/vitepress": { - "version": "1.0.0-rc.41", - "resolved": "https://registry.npmjs.org/vitepress/-/vitepress-1.0.0-rc.41.tgz", - "integrity": "sha512-PAEoIIc9J//k/Wg39C6k86hZpXPmLZjRiTBwieDNeYGdevD7xr5Ve8o1W/w+e9dtyQMkuvzgianEamXDX3aj7g==", - "dev": true, - "dependencies": { - "@docsearch/css": "^3.5.2", - "@docsearch/js": "^3.5.2", - "@shikijs/core": "^1.0.0-beta.3", - "@shikijs/transformers": "^1.0.0-beta.3", - "@types/markdown-it": "^13.0.7", - "@vitejs/plugin-vue": "^5.0.3", - "@vue/devtools-api": "^7.0.14", - "@vueuse/core": "^10.7.2", - "@vueuse/integrations": "^10.7.2", - "focus-trap": "^7.5.4", - "mark.js": "8.11.1", - "minisearch": "^6.3.0", - "shiki": "^1.0.0-beta.3", - "vite": "^5.0.12", - "vue": "^3.4.15" - }, - "bin": { - "vitepress": "bin/vitepress.js" - }, - "peerDependencies": { - "markdown-it-mathjax3": "^4.3.2", - "postcss": "^8.4.33" - }, - "peerDependenciesMeta": { - "markdown-it-mathjax3": { - "optional": true - }, - "postcss": { - "optional": true - } - } - }, - "node_modules/vitepress-plugin-tabs": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/vitepress-plugin-tabs/-/vitepress-plugin-tabs-0.5.0.tgz", - "integrity": "sha512-SIhFWwGsUkTByfc2b279ray/E0Jt8vDTsM1LiHxmCOBAEMmvzIBZSuYYT1DpdDTiS3SuJieBheJkYnwCq/yD9A==", - "dev": true, - "peerDependencies": { - "vitepress": "^1.0.0-rc.27", - "vue": "^3.3.8" - } - }, - "node_modules/vitest": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.2.0.tgz", - "integrity": "sha512-Ixs5m7BjqvLHXcibkzKRQUvD/XLw0E3rvqaCMlrm/0LMsA0309ZqYvTlPzkhh81VlEyVZXFlwWnkhb6/UMtcaQ==", - "dev": true, - "dependencies": { - "@vitest/expect": "1.2.0", - "@vitest/runner": "1.2.0", - "@vitest/snapshot": "1.2.0", - "@vitest/spy": "1.2.0", - "@vitest/utils": "1.2.0", - "acorn-walk": "^8.3.1", - "cac": "^6.7.14", - "chai": "^4.3.10", - "debug": "^4.3.4", - "execa": "^8.0.1", - "local-pkg": "^0.5.0", - "magic-string": "^0.30.5", - "pathe": "^1.1.1", - "picocolors": "^1.0.0", - "std-env": "^3.5.0", - "strip-literal": "^1.3.0", - "tinybench": "^2.5.1", - "tinypool": "^0.8.1", - "vite": "^5.0.0", - "vite-node": "1.2.0", - "why-is-node-running": "^2.2.2" - }, - "bin": { - "vitest": "vitest.mjs" - }, - "engines": { - "node": "^18.0.0 || >=20.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "@edge-runtime/vm": "*", - "@types/node": "^18.0.0 || >=20.0.0", - "@vitest/browser": "^1.0.0", - "@vitest/ui": "^1.0.0", - "happy-dom": "*", - "jsdom": "*" - }, - "peerDependenciesMeta": { - "@edge-runtime/vm": { - "optional": true - }, - "@types/node": { - "optional": true - }, - "@vitest/browser": { - "optional": true - }, - "@vitest/ui": { - "optional": true - }, - "happy-dom": { - "optional": true - }, - "jsdom": { - "optional": true - } - } - }, - "node_modules/vue": { - "version": "3.4.15", - "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.15.tgz", - "integrity": "sha512-jC0GH4KkWLWJOEQjOpkqU1bQsBwf4R1rsFtw5GQJbjHVKWDzO6P0nWWBTmjp1xSemAioDFj1jdaK1qa3DnMQoQ==", - "dev": true, - "dependencies": { - "@vue/compiler-dom": "3.4.15", - "@vue/compiler-sfc": "3.4.15", - "@vue/runtime-dom": "3.4.15", - "@vue/server-renderer": "3.4.15", - "@vue/shared": "3.4.15" - }, - "peerDependencies": { - "typescript": "*" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/web-resource-inliner": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/web-resource-inliner/-/web-resource-inliner-6.0.1.tgz", - "integrity": "sha512-kfqDxt5dTB1JhqsCUQVFDj0rmY+4HLwGQIsLPbyrsN9y9WV/1oFDSx3BQ4GfCv9X+jVeQ7rouTqwK53rA/7t8A==", - "dev": true, - "dependencies": { - "ansi-colors": "^4.1.1", - "escape-goat": "^3.0.0", - "htmlparser2": "^5.0.0", - "mime": "^2.4.6", - "node-fetch": "^2.6.0", - "valid-data-url": "^3.0.0" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/web-resource-inliner/node_modules/domhandler": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-3.3.0.tgz", - "integrity": "sha512-J1C5rIANUbuYK+FuFL98650rihynUOEzRLxW+90bKZRWB6A1X1Tf82GxR1qAWLyfNPRvjqfip3Q5tdYlmAa9lA==", - "dev": true, - "dependencies": { - "domelementtype": "^2.0.1" - }, - "engines": { - "node": ">= 4" - }, - "funding": { - "url": "https://github.com/fb55/domhandler?sponsor=1" - } - }, - "node_modules/web-resource-inliner/node_modules/entities": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", - "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", - "dev": true, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/web-resource-inliner/node_modules/htmlparser2": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-5.0.1.tgz", - "integrity": "sha512-vKZZra6CSe9qsJzh0BjBGXo8dvzNsq/oGvsjfRdOrrryfeD9UOBEEQdeoqCRmKZchF5h2zOBMQ6YuQ0uRUmdbQ==", - "dev": true, - "dependencies": { - "domelementtype": "^2.0.1", - "domhandler": "^3.3.0", - "domutils": "^2.4.2", - "entities": "^2.0.0" - }, - "funding": { - "url": "https://github.com/fb55/htmlparser2?sponsor=1" - } - }, - "node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", - "dev": true - }, - "node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "dev": true, - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/why-is-node-running": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.2.2.tgz", - "integrity": "sha512-6tSwToZxTOcotxHeA+qGCq1mVzKR3CwcJGmVcY+QE8SHy6TnpFnh8PAvPNHYr7EcuVeG0QSMxtYCuO1ta/G/oA==", - "dev": true, - "dependencies": { - "siginfo": "^2.0.0", - "stackback": "0.0.2" - }, - "bin": { - "why-is-node-running": "cli.js" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wicked-good-xpath": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/wicked-good-xpath/-/wicked-good-xpath-1.3.0.tgz", - "integrity": "sha512-Gd9+TUn5nXdwj/hFsPVx5cuHHiF5Bwuc30jZ4+ronF1qHK5O7HD0sgmXWSEgwKquT3ClLoKPVbO6qGwVwLzvAw==", - "dev": true - }, - "node_modules/xmldom-sre": { - "version": "0.1.31", - "resolved": "https://registry.npmjs.org/xmldom-sre/-/xmldom-sre-0.1.31.tgz", - "integrity": "sha512-f9s+fUkX04BxQf+7mMWAp5zk61pciie+fFLC9hX9UVvCeJQfNHRHXpeo5MPcR0EUf57PYLdt+ZO4f3Ipk2oZUw==", - "dev": true, - "engines": { - "node": ">=0.1" - } - }, - "node_modules/yocto-queue": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz", - "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==", - "dev": true, - "engines": { - "node": ">=12.20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - } - } -} diff --git a/docs/package.json b/docs/package.json index d7179f1ba..acf8a5876 100644 --- a/docs/package.json +++ b/docs/package.json @@ -1,4 +1,11 @@ { + "devDependencies": { + "markdown-it": "^14.0.0", + "markdown-it-mathjax3": "^4.3.2", + "vitepress": "^1.0.0-rc.43", + "vitepress-plugin-tabs": "^0.5.0", + "vitest": "^1.3.0" + }, "scripts": { "docs:dev": "vitepress dev build/.documenter", "docs:build": "vitepress build build/.documenter", @@ -6,11 +13,6 @@ }, "dependencies": { "@shikijs/transformers": "^1.1.7", - "markdown-it": "^14.0.0", - "markdown-it-footnote": "^4.0.0", - "markdown-it-mathjax3": "^4.3.2", - "vitepress": "^1.0.0-rc.43", - "vitepress-plugin-tabs": "^0.5.0", - "vitest": "^1.3.0" + "markdown-it-footnote": "^4.0.0" } -} +} \ No newline at end of file diff --git a/docs/src/.vitepress/config.mts b/docs/src/.vitepress/config.mts index ea6ba26d2..8d36f5729 100644 --- a/docs/src/.vitepress/config.mts +++ b/docs/src/.vitepress/config.mts @@ -2,8 +2,6 @@ import { defineConfig } from 'vitepress' import { tabsMarkdownPlugin } from 'vitepress-plugin-tabs' import mathjax3 from "markdown-it-mathjax3"; import footnote from "markdown-it-footnote"; -// Custom -import type { DefaultTheme } from 'vitepress' // https://vitepress.dev/reference/site-config export default defineConfig({ From c09557d8ccf58872fd64fb1baacff54bba566283 Mon Sep 17 00:00:00 2001 From: Anshul Singhvi Date: Wed, 3 Apr 2024 17:01:00 -0400 Subject: [PATCH 076/108] Delete docs/src/.vitepress/theme/index.ts (#682) --- docs/src/.vitepress/theme/index.ts | 17 ----------------- 1 file changed, 17 deletions(-) delete mode 100644 docs/src/.vitepress/theme/index.ts diff --git a/docs/src/.vitepress/theme/index.ts b/docs/src/.vitepress/theme/index.ts deleted file mode 100644 index a0086bbcd..000000000 --- a/docs/src/.vitepress/theme/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -// .vitepress/theme/index.ts -import { h } from 'vue' -import Theme from 'vitepress/theme' -import { enhanceAppWithTabs } from 'vitepress-plugin-tabs/client' -import './style.css' - -export default { - extends: Theme, - Layout: () => { - return h(Theme.Layout, null, { - // https://vitepress.dev/guide/extending-default-theme#layout-slots - }) - }, - enhanceApp({ app, router, siteData }) { - enhanceAppWithTabs(app) - } -} \ No newline at end of file From b69b48fdc3d2e79d1ff2fe7f20694cb6a63c3bf9 Mon Sep 17 00:00:00 2001 From: Rafael Schouten Date: Thu, 4 Apr 2024 22:22:50 +0200 Subject: [PATCH 077/108] fix `broadcast_dims` with `groupby` (#684) * fix broadcastdims after broupby * use OpaqueArray --- src/Dimensions/format.jl | 6 ++++++ src/dimindices.jl | 14 +++++++++++--- src/groupby.jl | 22 +++++++++++++++++----- test/groupby.jl | 20 +++++++++++++++++--- 4 files changed, 51 insertions(+), 11 deletions(-) diff --git a/src/Dimensions/format.jl b/src/Dimensions/format.jl index 9aa441d21..436111ec8 100644 --- a/src/Dimensions/format.jl +++ b/src/Dimensions/format.jl @@ -26,6 +26,12 @@ function format(dims::Tuple{<:Pair,Vararg{Pair}}, A::AbstractArray) end return format(dims, A) end +# Make a dummy array that assumes the dims are the correct length and don't hold `Colon`s +function format(dims::DimTuple) + ax = map(parent ∘ first ∘ axes, dims) + A = CartesianIndices(ax) + return format(dims, A) +end format(dims::Tuple{Vararg{Any,N}}, A::AbstractArray{<:Any,N}) where N = format(dims, axes(A)) @noinline format(dims::Tuple{Vararg{Any,M}}, A::AbstractArray{<:Any,N}) where {N,M} = throw(DimensionMismatch("Array A has $N axes, while the number of dims is $M: $(map(basetypeof, dims))")) diff --git a/src/dimindices.jl b/src/dimindices.jl index 20de8d6c3..fbf4d9f27 100644 --- a/src/dimindices.jl +++ b/src/dimindices.jl @@ -294,12 +294,20 @@ struct DimSlices{T,N,D<:Tuple{Vararg{Dimension}},P} <: AbstractDimArrayGenerator end DimSlices(x; dims, drop=true) = DimSlices(x, dims; drop) function DimSlices(x, dims; drop=true) - newdims = length(dims) == 0 ? map(d -> rebuild(d, :), DD.dims(x)) : dims - inds = map(d -> rebuild(d, first(axes(x, d))), newdims) + newdims = if length(dims) == 0 + map(d -> rebuild(d, :), DD.dims(x)) + else + dims + end + inds = map(basedims(newdims)) do d + rebuild(d, first(axes(x, d))) + end + # `getindex` returns these views T = typeof(view(x, inds...)) N = length(newdims) D = typeof(newdims) - return DimSlices{T,N,D,typeof(x)}(x, newdims) + P = typeof(x) + return DimSlices{T,N,D,P}(x, newdims) end rebuild(ds::A; dims) where {A<:DimSlices{T,N}} where {T,N} = diff --git a/src/groupby.jl b/src/groupby.jl index 25469174e..0e4e94c4a 100644 --- a/src/groupby.jl +++ b/src/groupby.jl @@ -46,9 +46,9 @@ end rebuild(A, data, dims, refdims, name, metadata) # Rebuild as a reguilar DimArray end -function Base.summary(io::IO, A::DimGroupByArray{T,N}) where {T,N} +function Base.summary(io::IO, A::DimGroupByArray{T,N}) where {T<:AbstractArray{T1,N1},N} where {T1,N1} print_ndims(io, size(A)) - print(io, string(nameof(typeof(A)), "{$(nameof(T)),$N}")) + print(io, string(nameof(typeof(A)), "{$(nameof(T)){$T1,$N1},$N}")) end function show_after(io::IO, mime, A::DimGroupByArray) @@ -80,6 +80,17 @@ function Base.show(io::IO, s::DimSummariser) end Base.alignment(io::IO, s::DimSummariser) = (textwidth(sprint(show, s)), 0) +# An array that doesn't know what it holds, to simplify dispatch +struct OpaqueArray{T,N,A<:AbstractArray{T,N}} <: AbstractArray{T,N} + parent::A +end +Base.parent(A::OpaqueArray) = A.parent +Base.size(A::OpaqueArray) = size(parent(A)) +for f in (:getindex, :view, :dotview) + @eval Base.$f(A::OpaqueArray, args...) = Base.$f(parent(A), args...) +end +Base.setindex!(A::OpaqueArray, args...) = Base.setindex!(parent(A), args...) + abstract type AbstractBins <: Function end @@ -331,9 +342,11 @@ function DataAPI.groupby(A::DimArrayOrStack, dimfuncs::DimTuple) end # Separate lookups dims from indices group_dims = map(first, dim_groups_indices) - indices = map(rebuild, dimfuncs, map(last, dim_groups_indices)) + # Get indices for each group wrapped with dims for indexing + indices = map(rebuild, group_dims, map(last, dim_groups_indices)) - views = DimSlices(A, indices) + # Hide that the parent is a DimSlices + views = OpaqueArray(DimSlices(A, indices)) # Put the groupby query in metadata meta = map(d -> dim2key(d) => val(d), dimfuncs) metadata = Dict{Symbol,Any}(:groupby => length(meta) == 1 ? only(meta) : meta) @@ -394,7 +407,6 @@ function _group_indices(dim::Dimension, bins::AbstractBins; labels=bins.labels) return _group_indices(transformed_lookup, group_lookup; labels) end - # Get a vector of intervals for the bins _groups_from(_, bins::Bins{<:Any,<:AbstractArray}) = bins.bins function _groups_from(transformed, bins::Bins{<:Any,<:Integer}) diff --git a/test/groupby.jl b/test/groupby.jl index 486511da6..dee4f7440 100644 --- a/test/groupby.jl +++ b/test/groupby.jl @@ -67,8 +67,22 @@ end end end @test all(collect(mean.(gb)) .=== manualmeans) - @test all( - mean.(gb) .=== manualmeans - ) + @test all(mean.(gb) .=== manualmeans) end +@testset "broadcastdims runs after groupby" begin + dimlist = ( + Ti(Date("2021-12-01"):Day(1):Date("2022-12-31")), + X(range(1, 10, length=10)), + Y(range(1, 5, length=15)), + Dim{:Variable}(["var1", "var2"]) + ) + data = rand(396, 10, 15, 2) + A = DimArray(data, dimlist) + month_length = DimArray(daysinmonth, dims(A, Ti)) + g_tempo = DimensionalData.groupby(month_length, Ti=>seasons(; start=December)) + sum_days = sum.(g_tempo, dims=Ti) + weights = map(./, g_tempo, sum_days) + G = DimensionalData.groupby(A, Ti=>seasons(; start=December)) + G_w = broadcast_dims.(*, weights, G) +end From 781dc77a6697ec9ad5a8479bdaab0da8330a76b0 Mon Sep 17 00:00:00 2001 From: Rafael Schouten Date: Thu, 4 Apr 2024 23:16:35 +0200 Subject: [PATCH 078/108] fix LazyMath begin end indexing (#685) * fix LazyMath indexing * fixes and tests --- src/Dimensions/indexing.jl | 2 +- src/Lookups/beginend.jl | 10 ++++------ test/indexing.jl | 5 +++-- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/Dimensions/indexing.jl b/src/Dimensions/indexing.jl index 9ce4420ec..aea6e364b 100644 --- a/src/Dimensions/indexing.jl +++ b/src/Dimensions/indexing.jl @@ -110,7 +110,7 @@ _unwrapdim(x) = x @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}}) = +@inline _dims2indices(dim::Dimension, i::Union{LU.Begin,LU.End,Type{LU.Begin},Type{LU.End},LU.LazyMath}) = to_indices(parent(dim), LU._construct_types(i))[1] @inline _dims2indices(dim::Dimension, ::Nothing) = Colon() @inline _dims2indices(dim::Dimension, x) = Lookups.selectindices(val(dim), x) diff --git a/src/Lookups/beginend.jl b/src/Lookups/beginend.jl index 75fc355d9..86fdf190b 100644 --- a/src/Lookups/beginend.jl +++ b/src/Lookups/beginend.jl @@ -39,10 +39,8 @@ 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_indices(A, inds, (r, args...)::Tuple{Begin,Vararg}) = - (first(inds[1]), to_indices(A, Base.tail(inds), args)...) -Base.to_indices(A, inds, (r, args...)::Tuple{End,Vararg}) = - (last(inds[1]), to_indices(A, Base.tail(inds), args)...) +Base.to_indices(A, inds, (r, args...)::Tuple{<:Union{Begin,End,<:LazyMath},Vararg}) = + (_to_index(inds[1], r), to_indices(A, Base.tail(inds), args)...) _to_index(inds, a::Int) = a _to_index(inds, ::Begin) = first(inds) @@ -92,8 +90,8 @@ _pf(f) = string(f) for T in (UnitRange, AbstractUnitRange, StepRange, StepRangeLen, LinRange, Lookup) for f in (:getindex, :view, :dotview) @eval Base.$f(A::$T, i::AbstractBeginEndRange) = Base.$f(A, to_indices(A, (i,))...) - @eval Base.$f(A::$T, ::Type{Begin}) = Base.$f(A, firstindex(A)) - @eval Base.$f(A::$T, ::Type{End}) = Base.$f(A, lastindex(A)) + @eval Base.$f(A::$T, i::Union{Type{Begin},Type{End},Begin,End,LazyMath}) = + Base.$f(A, to_indices(A, _construct_types(i))...) end end diff --git a/test/indexing.jl b/test/indexing.jl index b98cd20c7..1d8918327 100644 --- a/test/indexing.jl +++ b/test/indexing.jl @@ -31,8 +31,8 @@ end @testset "lookup" begin @testset "Points" begin l = Sampled(2.0:2.0:10, ForwardOrdered(), Regular(2.0), Points(), nothing) - @test l[:] == l[Begin:End] == l - @test l[Begin:5] == l[1:5] == l + @test l[:] == l[Begin:End] == l[1:End] == l[Begin:5] == l[1:5] == l + @test l[Begin+1:End-1] ==l[Begin+1:4] == l[2:End-1] == l[2:4] @test l[Begin:End] isa typeof(l) @test l[1:5] isa typeof(l) @test l[[1, 3, 4]] == view(l, [1, 3, 4]) == @@ -161,6 +161,7 @@ end @test @inferred d[1:5] == d @test d[1:5] isa typeof(d) @test @inferred d[Begin:End] == d + @test d[Begin+1:End-1] == d[2:-1+End] == d[1+Begin:4] == d[2:4] @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)) From a22fc49733e2a8ae26f0dcab1d8d19f7b516826c Mon Sep 17 00:00:00 2001 From: Lazaro Alonso Date: Fri, 5 Apr 2024 00:18:12 +0200 Subject: [PATCH 079/108] fix deploy docs (#686) * fix deploy docs * gen new logo * ignore also ico * rename to ico --- .gitignore | 3 ++- docs/logo.jl | 7 ++++--- docs/make.jl | 2 +- docs/package.json | 16 +++++++--------- docs/src/.vitepress/config.mts | 10 +++++----- docs/src/.vitepress/theme/index.ts | 13 +++++++++++++ docs/src/groupby.md | 3 ++- docs/src/index.md | 2 +- 8 files changed, 35 insertions(+), 21 deletions(-) create mode 100644 docs/src/.vitepress/theme/index.ts diff --git a/.gitignore b/.gitignore index 754f180d4..d74ac92de 100644 --- a/.gitignore +++ b/.gitignore @@ -25,4 +25,5 @@ docs/src/.vitepress/cache node_modules docs/package-lock.json *.png -*.svg \ No newline at end of file +*.svg +*.ico \ No newline at end of file diff --git a/docs/logo.jl b/docs/logo.jl index 78f4b40ed..081d68c4b 100644 --- a/docs/logo.jl +++ b/docs/logo.jl @@ -39,6 +39,7 @@ meshscatter!(ax, base_points; marker=Rect3f(Vec3f(-0.5,-0.5,0), Vec3f(1,1,1)), markersize = Vec3f.(0.5,0.5, z), color=z, colormap=tuple.(colors, 1), transparency=false) mkpath(joinpath(@__DIR__, "src", "assets")) -save(joinpath(@__DIR__, "src", "assets", "logoDD.svg"), fig; pt_per_unit=0.75) -save(joinpath(@__DIR__, "src", "assets", "logoDD.png"), fig; px_per_unit=2) -save(joinpath(@__DIR__, "src", "assets", "favicon.png"), fig; px_per_unit=0.25) \ No newline at end of file +save(joinpath(@__DIR__, "src", "assets", "logo.svg"), fig; pt_per_unit=0.75) +save(joinpath(@__DIR__, "src", "assets", "logo.png"), fig; px_per_unit=2) +save(joinpath(@__DIR__, "src", "assets", "favicon.png"), fig; px_per_unit=0.25) +mv(joinpath(@__DIR__, "src", "assets", "favicon.png"), joinpath(@__DIR__, "src", "assets", "favicon.ico")) \ No newline at end of file diff --git a/docs/make.jl b/docs/make.jl index 4f482ffb1..625fa0606 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -23,7 +23,7 @@ makedocs(; sitename="DimensionalData", authors="Rafael Schouten et al.", draft=false, source="src", build="build", - # warnonly = true, + warnonly = true, ) # Deploy built documentation. diff --git a/docs/package.json b/docs/package.json index acf8a5876..974a69f28 100644 --- a/docs/package.json +++ b/docs/package.json @@ -1,18 +1,16 @@ { - "devDependencies": { - "markdown-it": "^14.0.0", - "markdown-it-mathjax3": "^4.3.2", - "vitepress": "^1.0.0-rc.43", - "vitepress-plugin-tabs": "^0.5.0", - "vitest": "^1.3.0" - }, "scripts": { "docs:dev": "vitepress dev build/.documenter", "docs:build": "vitepress build build/.documenter", "docs:preview": "vitepress preview build/.documenter" }, + "devDependencies": { + "markdown-it-mathjax3": "^4.3.2", + "vitepress": "^1.0.2", + "vitepress-plugin-tabs": "^0.5.0" + }, "dependencies": { - "@shikijs/transformers": "^1.1.7", + "markdown-it": "^14.1.0", "markdown-it-footnote": "^4.0.0" } -} \ No newline at end of file +} diff --git a/docs/src/.vitepress/config.mts b/docs/src/.vitepress/config.mts index 8d36f5729..b71db6788 100644 --- a/docs/src/.vitepress/config.mts +++ b/docs/src/.vitepress/config.mts @@ -6,13 +6,12 @@ import footnote from "markdown-it-footnote"; // https://vitepress.dev/reference/site-config export default defineConfig({ base: 'REPLACE_ME_DOCUMENTER_VITEPRESS', - title: 'REPLACE_ME_DOCUMENTER_VITEPRESS', + title: 'DimensionalData.jl', description: "Datasets with named dimensions", lastUpdated: true, cleanUrls: true, outDir: 'REPLACE_ME_DOCUMENTER_VITEPRESS', // This is required for MarkdownVitepress to work correctly... - head: [], - ignoreDeadLinks: true, + head: [['link', { rel: 'icon', href: '/DimensionalData.jl/favicon.ico' }]], markdown: { math: true, @@ -28,7 +27,7 @@ export default defineConfig({ }, themeConfig: { // https://vitepress.dev/reference/default-theme-config - logo: { src: '/logoDD.png', width: 24, height: 24 }, + logo: { src: '/logo.png', width: 24, height: 24 }, search: { provider: 'local', options: { @@ -41,8 +40,9 @@ export default defineConfig({ { text: 'Dimensions', link: '/dimensions' }, { text: 'DimArrays', link: '/dimarrays' }, { text: 'Selectors', link: '/selectors' }, - { text: 'Integrations', link: '/integrations', + { text: 'Integrations', items: [ + { text: 'Integrations', link: '/integrations'}, { text: 'Plots and Makie', link: '/plots' }, { text: 'Tables and DataFrames', link: '/tables' }, { text: 'CUDA and GPUs', link: '/cuda' }, diff --git a/docs/src/.vitepress/theme/index.ts b/docs/src/.vitepress/theme/index.ts new file mode 100644 index 000000000..86e5219a5 --- /dev/null +++ b/docs/src/.vitepress/theme/index.ts @@ -0,0 +1,13 @@ +// .vitepress/theme/index.ts +import type { Theme } from 'vitepress' +import DefaultTheme from 'vitepress/theme' + +import { enhanceAppWithTabs } from 'vitepress-plugin-tabs/client' +import './style.css' + +export default { + extends: DefaultTheme, + enhanceApp({ app }) { + enhanceAppWithTabs(app) + } +} satisfies Theme diff --git a/docs/src/groupby.md b/docs/src/groupby.md index 8d21f8f78..34ea981c1 100644 --- a/docs/src/groupby.md +++ b/docs/src/groupby.md @@ -274,7 +274,8 @@ groupby(A, Ti => hours(12; start=6, labels=x -> 6 in x ? :night : :day)) ::: -## Select by [`Dimension`](@ref) +## Select by Dimension +- [`Dimension`](@ref) We can also select by `Dimension`s and any objects with `dims` methods. diff --git a/docs/src/index.md b/docs/src/index.md index 97b3240a5..558984611 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -8,7 +8,7 @@ hero: text: "Julia datasets with named dimensions" tagline: High performance named indexing for Julia image: - src: 'logoDD.png' + src: 'logo.png' actions: - theme: brand text: Getting Started From 511734d214fd526bc700576d0cc37d3131c35134 Mon Sep 17 00:00:00 2001 From: Rafael Schouten Date: Fri, 5 Apr 2024 00:18:53 +0200 Subject: [PATCH 080/108] bump patch version to 0.26.7 --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 1c2c7d17e..0db5c6163 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "DimensionalData" uuid = "0703355e-b756-11e9-17c0-8b28908087d0" authors = ["Rafael Schouten "] -version = "0.26.6" +version = "0.26.7" [deps] Adapt = "79e6a3ab-5dfb-504d-930d-738a2a938a0e" From b179f2c47a797b7da216e4275aa705bca5f00636 Mon Sep 17 00:00:00 2001 From: Lazaro Alonso Date: Mon, 8 Apr 2024 23:09:19 +0200 Subject: [PATCH 081/108] rm icon links (#688) * rm icon links * rm more html syntax * set path dev for ico * delete cached files * now, don't delete * fix some doctests, see what happens * fixes doctest issues, parsing, merge --- README.md | 2 +- docs/make.jl | 3 +- docs/package.json | 1 + docs/src/.vitepress/config.mts | 15 +++++- docs/src/index.md | 15 +++--- src/array/array.jl | 90 ++++++++++++++++++++++------------ 6 files changed, 81 insertions(+), 45 deletions(-) diff --git a/README.md b/README.md index 049e9e469..6c80b74d2 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ [![Codecov](https://codecov.io/gh/rafaqz/DimensionalData.jl/branch/main/graph/badge.svg)](https://codecov.io/gh/rafaqz/DimensionalData.jl/tree/main) [![Aqua.jl Quality Assurance](https://img.shields.io/badge/Aqua.jl-%F0%9F%8C%A2-aqua.svg)](https://github.com/JuliaTesting/Aqua.jl) - + > [!TIP] > Visit the latest documentation at https://rafaqz.github.io/DimensionalData.jl/dev/ diff --git a/docs/make.jl b/docs/make.jl index 625fa0606..236dd9121 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -22,8 +22,7 @@ makedocs(; sitename="DimensionalData", authors="Rafael Schouten et al.", ), draft=false, source="src", - build="build", - warnonly = true, + build="build", ) # Deploy built documentation. diff --git a/docs/package.json b/docs/package.json index 974a69f28..960347517 100644 --- a/docs/package.json +++ b/docs/package.json @@ -6,6 +6,7 @@ }, "devDependencies": { "markdown-it-mathjax3": "^4.3.2", + "rollup-plugin-delete": "^2.0.0", "vitepress": "^1.0.2", "vitepress-plugin-tabs": "^0.5.0" }, diff --git a/docs/src/.vitepress/config.mts b/docs/src/.vitepress/config.mts index b71db6788..485a566f6 100644 --- a/docs/src/.vitepress/config.mts +++ b/docs/src/.vitepress/config.mts @@ -2,6 +2,7 @@ import { defineConfig } from 'vitepress' import { tabsMarkdownPlugin } from 'vitepress-plugin-tabs' import mathjax3 from "markdown-it-mathjax3"; import footnote from "markdown-it-footnote"; +// import del from 'rollup-plugin-delete'; // https://vitepress.dev/reference/site-config export default defineConfig({ @@ -11,7 +12,17 @@ export default defineConfig({ lastUpdated: true, cleanUrls: true, outDir: 'REPLACE_ME_DOCUMENTER_VITEPRESS', // This is required for MarkdownVitepress to work correctly... - head: [['link', { rel: 'icon', href: '/DimensionalData.jl/favicon.ico' }]], + head: [['link', { rel: 'icon', href: '/DimensionalData.jl/dev/favicon.ico' }]], + + // vite: { + // build: { + // rollupOptions: { + // plugins: [ + // del({ targets: ['dist/*', 'build/*'], verbose: true }) + // ] + // }, + // }, + // }, markdown: { math: true, @@ -88,7 +99,7 @@ export default defineConfig({ { icon: 'github', link: 'https://github.com/rafaqz/DimensionalData.jl' }, ], footer: { - message: 'Made with
DocumenterVitepress.jl by Lazaro Alonso
', + message: 'Made with
DocumenterVitepress.jl', copyright: `© Copyright ${new Date().getUTCFullYear()}. Released under the MIT License.` } } diff --git a/docs/src/index.md b/docs/src/index.md index 558984611..b94e485a9 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -8,7 +8,7 @@ hero: text: "Julia datasets with named dimensions" tagline: High performance named indexing for Julia image: - src: 'logo.png' + src: '/logo.png' actions: - theme: brand text: Getting Started @@ -20,16 +20,13 @@ hero: text: View on Github link: https://github.com/rafaqz/DimensionalData.jl features: - - icon: 3d-scale - title: Intelligent indexing + - title: Intelligent indexing details: DimensionalData.jl provides no-cost abstractions for named indexing, and fast index lookups. link: /selectors - - icon: grid - title: Powerful Array manipulation - details: broadcast, reduce, permutedims, and groupby operations. + - title: Powerful Array manipulation + details: broadcast, reduce, permutedims, and groupby operations. link: /groupby - - icon: layers - title: Seamlessly integrated with the julia ecosystem - details: Works with most methods that accept a regular `Array`. If a method accepts numeric indices or dims=X in base, you should be able to use DimensionalData.jl dims. + - title: Seamlessly integrated with the julia ecosystem + details: Works with most methods that accept a regular Array. If a method accepts numeric indices or dims=X in base, you should be able to use DimensionalData.jl dims. --- ``` diff --git a/src/array/array.jl b/src/array/array.jl index f7778e5df..712b8b1e2 100644 --- a/src/array/array.jl +++ b/src/array/array.jl @@ -472,14 +472,18 @@ Keywords are the same as for [`DimArray`](@ref). # Example -```@doctest -julia> using DimensionalData - +````@doctest +julia> using DimensionalData, Random; Random.seed!(123) julia> rand(Bool, X(2), Y(4)) -2×4 DimArray{Bool,2} with dimensions: X, Y +╭──────────────────────╮ +│ 2×4 DimArray{Bool,2} │ +├──────────────── dims ┤ + ↓ X, → Y +└──────────────────────┘ + 0 0 0 0 1 0 0 1 - 1 0 1 1 -``` + +```` """ Base.fill @@ -505,17 +509,25 @@ Keywords are the same as for [`DimArray`](@ref). julia> using DimensionalData julia> rand(Bool, X(2), Y(4)) -2×4 DimArray{Bool,2} with dimensions: X, Y - 1 0 0 1 - 1 0 1 1 +╭──────────────────────╮ +│ 2×4 DimArray{Bool,2} │ +├──────────────── dims ┤ + ↓ X, → Y +└──────────────────────┘ + 1 1 0 0 + 0 1 1 0 julia> rand(X([:a, :b, :c]), Y(100.0:50:200.0)) -3×3 DimArray{Float64,2} with dimensions: - X: Symbol[a, b, c] Categorical: Unordered, - Y: 100.0:50.0:200.0 Sampled: Ordered Regular Points - 0.43204 0.835111 0.624231 - 0.752868 0.471638 0.193652 - 0.484558 0.846559 0.455256 +╭─────────────────────────╮ +│ 3×3 DimArray{Float64,2} │ +├─────────────────────────┴───────────────────────────────────── dims ┐ + ↓ X Categorical{Symbol} [:a, :b, :c] ForwardOrdered, + → Y Sampled{Float64} 100.0:50.0:200.0 ForwardOrdered Regular Points +└─────────────────────────────────────────────────────────────────────┘ + ↓ → 100.0 150.0 200.0 + :a 0.624539 0.559166 0.813246 + :b 0.947442 0.664213 0.284669 + :c 0.695604 0.564835 0.156286 ``` """ Base.rand @@ -538,19 +550,27 @@ Keywords are the same as for [`DimArray`](@ref). ```@doctest julia> using DimensionalData - julia> zeros(Bool, X(2), Y(4)) -2×4 DimArray{Bool,2} with dimensions: X, Y +╭──────────────────────╮ +│ 2×4 DimArray{Bool,2} │ +├──────────────── dims ┤ + ↓ X, → Y +└──────────────────────┘ 0 0 0 0 0 0 0 0 julia> zeros(X([:a, :b, :c]), Y(100.0:50:200.0)) -3×3 DimArray{Float64,2} with dimensions: - X: Symbol[a, b, c] Categorical: Unordered, - Y: 100.0:50.0:200.0 Sampled: Ordered Regular Points - 0.0 0.0 0.0 - 0.0 0.0 0.0 - 0.0 0.0 0.0 +╭─────────────────────────╮ +│ 3×3 DimArray{Float64,2} │ +├─────────────────────────┴───────────────────────────────────── dims ┐ + ↓ X Categorical{Symbol} [:a, :b, :c] ForwardOrdered, + → Y Sampled{Float64} 100.0:50.0:200.0 ForwardOrdered Regular Points +└─────────────────────────────────────────────────────────────────────┘ + ↓ → 100.0 150.0 200.0 + :a 0.0 0.0 0.0 + :b 0.0 0.0 0.0 + :c 0.0 0.0 0.0 + ``` """ Base.zeros @@ -573,19 +593,27 @@ Keywords are the same as for [`DimArray`](@ref). ```@doctest julia> using DimensionalData - julia> ones(Bool, X(2), Y(4)) -2×4 DimArray{Bool,2} with dimensions: X, Y +╭──────────────────────╮ +│ 2×4 DimArray{Bool,2} │ +├──────────────── dims ┤ + ↓ X, → Y +└──────────────────────┘ 1 1 1 1 1 1 1 1 julia> ones(X([:a, :b, :c]), Y(100.0:50:200.0)) -3×3 DimArray{Float64,2} with dimensions: - X: Symbol[a, b, c] Categorical: Unordered, - Y: 100.0:50.0:200.0 Sampled: Ordered Regular Points - 1.0 1.0 1.0 - 1.0 1.0 1.0 - 1.0 1.0 1.0 +╭─────────────────────────╮ +│ 3×3 DimArray{Float64,2} │ +├─────────────────────────┴───────────────────────────────────── dims ┐ + ↓ X Categorical{Symbol} [:a, :b, :c] ForwardOrdered, + → Y Sampled{Float64} 100.0:50.0:200.0 ForwardOrdered Regular Points +└─────────────────────────────────────────────────────────────────────┘ + ↓ → 100.0 150.0 200.0 + :a 1.0 1.0 1.0 + :b 1.0 1.0 1.0 + :c 1.0 1.0 1.0 + ``` """ Base.ones From a4b7bb6d821cd4b1e6b4de4501902b3259ccb05c Mon Sep 17 00:00:00 2001 From: tiemvanderdeure Date: Tue, 9 Apr 2024 11:53:41 +0200 Subject: [PATCH 082/108] fix typos --- docs/src/basics.md | 4 +++- docs/src/dimarrays.md | 2 +- docs/src/integrations.md | 12 ++++++------ docs/src/object_modification.md | 14 +++++++------- docs/src/plots.md | 2 +- docs/src/selectors.md | 6 +++--- docs/src/stacks.md | 4 ++-- docs/src/tables.md | 2 +- 8 files changed, 24 insertions(+), 22 deletions(-) diff --git a/docs/src/basics.md b/docs/src/basics.md index 60f9850e3..f307d9670 100644 --- a/docs/src/basics.md +++ b/docs/src/basics.md @@ -1,6 +1,6 @@ ## Installation -If want to use this package you need to install it first. You can do it using the following commands: +If you want to use this package you need to install it first. You can do it using the following commands: ````julia julia>] # ']' should be pressed @@ -46,3 +46,5 @@ or something a little bit more complicated: data = rand(Int8, 2, 10, 3) .|> abs B = DimArray(data, (channel=[:left, :right], time=1:10, iter=1:3)) ```` + + diff --git a/docs/src/dimarrays.md b/docs/src/dimarrays.md index cc73ac43c..66da94c26 100644 --- a/docs/src/dimarrays.md +++ b/docs/src/dimarrays.md @@ -169,7 +169,7 @@ Methods where dims, dim types, or `Symbol`s can be used to indicate the array di ## Performance -Indexing with `Dimension`s has no runtime cost. Lets benchmark it: +Indexing with `Dimension`s has no runtime cost. Let's benchmark it: ```@ansi dimarray using BenchmarkTools diff --git a/docs/src/integrations.md b/docs/src/integrations.md index c0a5f110a..a67d5156a 100644 --- a/docs/src/integrations.md +++ b/docs/src/integrations.md @@ -2,7 +2,7 @@ ## Rasters.jl -[Raster.jl](https://rafaqz.github.io/Rasters.jl/stable) extends DD +[Rasters.jl](https://rafaqz.github.io/Rasters.jl/stable) extends DD for geospatial data manipulation, providing file load/save for a wide range of raster data sources and common GIS tools like polygon rasterization and masking. `Raster` types are aware @@ -19,7 +19,7 @@ and `Projected` and `Mapped` are `AbstractSample` lookups. ## YAXArrays.jl [YAXArrays.jl](https://juliadatacubes.github.io/YAXArrays.jl/dev/) is another -spatial data package aimmed more at (very) large datasets. It's functionality +spatial data package aimed more at (very) large datasets. It's functionality is slowly converging with Rasters.jl (both wrapping DiskArray.jl/DimensionalData.jl) and we work closely with the developers. @@ -33,20 +33,20 @@ Extends DD with methods for analysis of climate data. ## ArviZ.jl [ArviZ.jl](https://arviz-devs.github.io/ArviZ.jl/dev/) -Is a julia package for exploratory analysis of Bayesian models. +Is a Julia package for exploratory analysis of Bayesian models. An `ArviZ.Dataset` is an `AbstractDimStack`! ## JuMP.jl -[JuMP.jl](https://jump.dev/) is a powerful omptimisation DSL. +[JuMP.jl](https://jump.dev/) is a powerful optimization DSL. It defines its own named array types but now accepts any `AbstractDimArray` too, through a package extension. ## CryoGrid.jl [CryoGrid.jl](https://juliahub.com/ui/Packages/General/CryoGrid) -A Juia implementation of the CryoGrid permafrost model. +A Julia implementation of the CryoGrid permafrost model. `CryoGridOutput` uses `DimArray` for views into output data. @@ -63,7 +63,7 @@ synchronisation during simulations. Notably, this all works on GPUs! ## AstroImages.jl [AstroImages.jl](http://juliaastro.org/dev/modules/AstroImages) -Provides tools to load and visualise astromical images. +Provides tools to load and visualise astronomical images. `AstroImage` is `<: AbstractDimArray`. ## TimeseriesTools.jl diff --git a/docs/src/object_modification.md b/docs/src/object_modification.md index 77e55025d..edb77a0e8 100644 --- a/docs/src/object_modification.md +++ b/docs/src/object_modification.md @@ -10,7 +10,7 @@ Everything else must be _rebuilt_ and assigned to a variable. ## `modify` Modify the inner arrays of a `AbstractDimArray` or `AbstractDimStack`, with -[`modify`](@ref). This can be usefule to e.g. replace all arrays with `CuArray` +[`modify`](@ref). This can be useful to e.g. replace all arrays with `CuArray` moving the data to the GPU, `collect` all inner arrays to `Array` without losing the outer `DimArray` wrappers, and similar things. @@ -72,12 +72,12 @@ dimensions into a single combined dimension with a lookup holding `Tuples` of the values of both dimensions. -## `rebulid` +## `rebuild` [`rebuild`](@ref) is one of the core functions of DimensionalData.jl. Basically everything uses it somewhere. And you can to, with a few caveats. -`reuild` assumes you _know what yo uare doing_. You can quite eaily set +`rebuild` assumes you _know what you are doing_. You can quite eaily set values to things that don't make sense. The constructor may check a few things, like the number of dimensions matches the axes of the array. But not much else. @@ -101,7 +101,7 @@ metadata(A1) The most common use internally is the arg version on `Dimension`. This is _very_ useful in dimension-based algorithmsas a way -to transfrom a dimension wrapper from one object to another: +to transform a dimension wrapper from one object to another: ```@ansi helpers d = X(1) @@ -110,7 +110,7 @@ rebuild(d, 1:10) `rebuild` applications are listed here. `AbstractDimArray` and `AbstractDimStack` _always_ accept these keywords or arguments, -but those in [ ] brackes may be thrown away if not needed. +but those in [ ] brackets may be thrown away if not needed. Keywords in ( ) will error if used where they are not accepted. | Type | Keywords | Arguments | @@ -138,12 +138,12 @@ the process. ## `set` [`set`](@ref) gives us a way to set the values of the immutable objects -in DD, like `Dimension` and `LookupAray`. Unlike `rebuild` it tries its best +in DD, like `Dimension` and `LookupArray`. Unlike `rebuild` it tries its best to _do the right thing_. You don't have to specify what field you want to set. just pass in the object you want to be part of the lookup. Usually, there is no possible ambiguity. -`set` is still improving. Sometimes itmay not do the right thing. +`set` is still improving. Sometimes it may not do the right thing. If you think this is the case, make a github issue. :::: tabs diff --git a/docs/src/plots.md b/docs/src/plots.md index 8562e9a83..3a25d44b2 100644 --- a/docs/src/plots.md +++ b/docs/src/plots.md @@ -16,7 +16,7 @@ Plots.jl support is deprecated, as development is moving to Makie.jl Makie.jl functions also mostly work with [`AbstractDimArray`](@ref) and will `permute` and [`reorder`](@ref) axes into the right places, especially if `X`/`Y`/`Z`/`Ti` dimensions are used. -In makie a `DimMatrix` will plot as a heatmap by defualt, but it will have labels +In makie a `DimMatrix` will plot as a heatmap by default, but it will have labels and axes in the right places: ```@example Makie diff --git a/docs/src/selectors.md b/docs/src/selectors.md index a5b290b9b..0e7a07a22 100644 --- a/docs/src/selectors.md +++ b/docs/src/selectors.md @@ -198,7 +198,7 @@ A[At(DateTime(3047, 9))] == NoLookup [`NoLookup(x)`](@ref) no lookup values provided, so `Selector`s will not work. -Whe you create a `DimArray` without a lookup array, `NoLookup` will be used. +When you create a `DimArray` without a lookup array, `NoLookup` will be used. It is also not show in repl printing. Here we create a [`NoLookup`](@ref): @@ -241,7 +241,7 @@ from the arrays and ranges used. nothing` are the unknown outer bounds of the lookup. They are not needed for `Points` as the outer values are the outer bounds. But they can be specified manually for `Intervals` - - Emtpy dimensions or dimension types are assigned `NoLookup()` ranges that + - Empty dimensions or dimension types are assigned `NoLookup()` ranges that can't be used with selectors as they hold no values. ## `DimSelector` @@ -267,7 +267,7 @@ B[DimSelectors(A)] ```` If the lookups aren't aligned we can use `Near` instead of `At`, -which like doing a nearest neighor interpolation: +which like doing a nearest neighbor interpolation: ````@ansi selectors C = rand(X(1.0:0.007:2.0), Y(10.0:0.9:30)) diff --git a/docs/src/stacks.md b/docs/src/stacks.md index 8b60b9353..91d7196b5 100644 --- a/docs/src/stacks.md +++ b/docs/src/stacks.md @@ -30,7 +30,7 @@ st[:c] == subsetting layers -We can subset layers with a `Tupe` of `Symbol`: +We can subset layers with a `Tuple` of `Symbol`: ````@ansi stack st[(:a, :c)] @@ -39,7 +39,7 @@ st[(:a, :c)] == inverted subsets `Not` works on `Symbol` keys just like it does on `Selector`: -It inverts the keys to give you a `DImStack` with all the other layers: +It inverts the keys to give you a `DimStack` with all the other layers: ````@ansi stack st[Not(:b)] diff --git a/docs/src/tables.md b/docs/src/tables.md index 0308dc053..8cb2df1d2 100644 --- a/docs/src/tables.md +++ b/docs/src/tables.md @@ -1,7 +1,7 @@ # Tables and DataFrames [Tables.jl](https://github.com/JuliaData/Tables.jl) provides an -ecosystem-wide interface to tabular data in julia, giving interop with +ecosystem-wide interface to tabular data in Julia, giving interoperability with [DataFrames.jl](https://dataframes.juliadata.org/stable/), [CSV.jl](https://csv.juliadata.org/stable/) and hundreds of other packages that implement the standard. From db9406c281b659936b3c422094c1ef6dc1f74561 Mon Sep 17 00:00:00 2001 From: Tiem van der Deure Date: Wed, 10 Apr 2024 01:30:39 +0200 Subject: [PATCH 083/108] contains checks if index is valid (#691) --- src/Lookups/selector.jl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Lookups/selector.jl b/src/Lookups/selector.jl index 19a3f3a5e..9eebe10ed 100644 --- a/src/Lookups/selector.jl +++ b/src/Lookups/selector.jl @@ -399,7 +399,8 @@ function contains( ) v = val(sel) i = searchsortedlast(l, v; by=_by) - if _in(v, l[i]) + + if i in eachindex(l) && _in(v, l[i]) return i else return _notcontained_or_nothing(err, v) From 4f95701dc70f55418a859032cfc79305cc44d269 Mon Sep 17 00:00:00 2001 From: Rafael Schouten Date: Sun, 14 Apr 2024 21:14:37 +0200 Subject: [PATCH 084/108] fix selectindicies keywords (#695) * fix selectindicies keywords * tweaks --- src/Dimensions/dimension.jl | 12 ++--- src/Lookups/selector.jl | 101 +++++++++++++++++++++--------------- test/selector.jl | 6 ++- 3 files changed, 69 insertions(+), 50 deletions(-) diff --git a/src/Dimensions/dimension.jl b/src/Dimensions/dimension.jl index d229c0bb9..4ce4d09fa 100644 --- a/src/Dimensions/dimension.jl +++ b/src/Dimensions/dimension.jl @@ -234,19 +234,19 @@ for f in (:val, :index, :lookup, :metadata, :order, :sampling, :span, :locus, :b end end -@inline function selectindices(x, selectors) +@inline function selectindices(x, selectors; kw...) if dims(x) isa Nothing # This object has no dimensions and no `selectindices` method. - # Just return whatever it is, maybe the underlying array can use it. + # Just return whatever selectors is, maybe the underlying array can use it. return selectors else # Otherwise select indices based on the object `Dimension`s - return selectindices(dims(x), selectors) + return selectindices(dims(x), selectors; kw...) end end -@inline selectindices(ds::DimTuple, sel...) = selectindices(ds, sel) -@inline selectindices(ds::DimTuple, sel::Tuple) = selectindices(val(ds), sel) -@inline selectindices(dim::Dimension, sel) = selectindices(val(dim), sel) +@inline selectindices(ds::DimTuple, sel...; kw...) = selectindices(ds, sel; kw...) +@inline selectindices(ds::DimTuple, sel::Tuple; kw...) = selectindices(val(ds), sel; kw...) +@inline selectindices(dim::Dimension, sel; kw...) = selectindices(val(dim), sel; kw...) # Deprecated Lookups.index(dim::Dimension{<:AbstractArray}) = index(val(dim)) diff --git a/src/Lookups/selector.jl b/src/Lookups/selector.jl index 9eebe10ed..71e1d2620 100644 --- a/src/Lookups/selector.jl +++ b/src/Lookups/selector.jl @@ -1,4 +1,4 @@ -struct SelectorError{L,S} <: Exception +struct SelectorError{L,S} <: Exception lookup::L selector::S end @@ -10,7 +10,7 @@ function Base.showerror(io::IO, ex::SelectorError) println(io, "SelectorError: attempt to select $(ex.selector) from lookup $(typeof(ex.lookup)) with values $(ex.lookup)") end end -Base.showerror(io::IO, ex::SelectorError{<:Categorical}) = +Base.showerror(io::IO, ex::SelectorError{<:Categorical}) = println(io, "SelectorError: attempt to select $(ex.selector) from lookup $(typeof(ex.lookup)) with categories $(ex.lookup)") """ @@ -124,7 +124,7 @@ rebuild(sel::At, val) = At(val, sel.atol, sel.rtol) atol(sel::At) = sel.atol rtol(sel::At) = sel.rtol -Base.show(io::IO, x::At) = print(io, "At(", val(x), ", ", atol(x), ", ", rtol(x), ")") +Base.show(io::IO, x::At) = print(io, "At(", val(x), ", ", atol(x), ", ", rtol(x), ")") struct _True end struct _False end @@ -134,11 +134,11 @@ selectindices(l::Lookup, sel::At{<:AbstractVector}; kw...) = _selectvec(l, sel; _selectvec(l, sel; kw...) = [selectindices(l, rebuild(sel, v); kw...) for v in val(sel)] -function at(lookup::AbstractCyclic{Cycling}, sel::At; kw...) +function at(lookup::AbstractCyclic{Cycling}, sel::At; kw...) cycled_sel = rebuild(sel, cycle_val(lookup, val(sel))) - return at(no_cycling(lookup), cycled_sel; kw...) + return at(no_cycling(lookup), cycled_sel; kw...) end -function at(lookup::NoLookup, sel::At; err=_True(), kw...) +function at(lookup::NoLookup, sel::At; err=_True(), kw...) v = val(sel) r = round(Int, v) at = atol(sel) @@ -148,8 +148,15 @@ function at(lookup::NoLookup, sel::At; err=_True(), kw...) at >= 0.5 && error("atol must be small than 0.5 for NoLookup") isapprox(v, r; atol=at) || _selnotfound_or_nothing(err, lookup, v) end - r in lookup || err isa _False || throw(SelectorError(lookup, sel)) - return r + if r in lookup + return r + else + if err isa _False + return nothing + else + throw(SelectorError(lookup, sel)) + end + end end function at(lookup::Lookup, sel::At; kw...) at(order(lookup), span(lookup), lookup, val(sel), atol(sel), rtol(sel); kw...) @@ -174,7 +181,7 @@ function at( ) x = unwrap(selval) i = searchsortedlast(lookup, x; lt=(a, b) -> a.left < b.left) - if lookup[i].left == x.left && lookup[i].right == x.right + if lookup[i].left == x.left && lookup[i].right == x.right return i else return _selnotfound_or_nothing(err, lookup, selval) @@ -254,23 +261,25 @@ struct Near{T} <: IntSelector{T} end Near() = Near(nothing) -selectindices(l::Lookup, sel::Near) = near(l, sel) -selectindices(l::Lookup, sel::Near{<:AbstractVector}) = _selectvec(l, sel) +selectindices(l::Lookup, sel::Near; kw...) = near(l, sel) +selectindices(l::Lookup, sel::Near{<:AbstractVector}; kw...) = _selectvec(l, sel) Base.show(io::IO, x::Near) = print(io, "Near(", val(x), ")") -function near(lookup::AbstractCyclic{Cycling}, sel::Near) +function near(lookup::AbstractCyclic{Cycling}, sel::Near; kw...) cycled_sel = rebuild(sel, cycle_val(lookup, val(sel))) - near(no_cycling(lookup), cycled_sel) + near(no_cycling(lookup), cycled_sel; kw...) 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) && +near(lookup::NoLookup, sel::Near{<:Real}; kw...) = max(1, min(round(Int, val(sel)), lastindex(lookup))) +function near(lookup::Lookup, sel::Near; kw...) + # We ignore err keyword in near, as these are a different class of errors + if !isregular(lookup) && !iscenter(lookup) 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 + return near(order(lookup), sampling(lookup), lookup, sel; kw...) end -near(order::Order, ::NoSampling, lookup::Lookup, sel::Near) = at(lookup, At(val(sel))) -function near(order::Ordered, ::Union{Intervals,Points}, lookup::Lookup, sel::Near) +near(order::Order, ::NoSampling, lookup::Lookup, sel::Near; kw...) = at(lookup, At(val(sel)); kw...) +function near(order::Ordered, ::Union{Intervals,Points}, lookup::Lookup, sel::Near; kw...) # Unwrap the selector value and adjust it for interval locus if neccessary v = unwrap(val(sel)) # Allow Date and DateTime to be used interchangeably @@ -301,10 +310,10 @@ function near(order::Ordered, ::Union{Intervals,Points}, lookup::Lookup, sel::Ne return closest_i end -function near(order::Ordered, ::Intervals, lookup::Lookup{<:IntervalSets.Interval}, sel::Near) +function near(order::Ordered, ::Intervals, lookup::Lookup{<:IntervalSets.Interval}, sel::Near; kw...) throw(ArgumentError("`Near` is not yet implemented for lookups of `IntervalSets.Interval`")) end -function near(::Unordered, ::Union{Intervals,Points}, lookup::Lookup, sel::Near) +function near(::Unordered, ::Union{Intervals,Points}, lookup::Lookup, sel::Near; kw...) throw(ArgumentError("`Near` has no meaning in an `Unordered` lookup")) end @@ -352,14 +361,20 @@ selectindices(l::Lookup, sel::Contains{<:AbstractVector}; kw...) = _selectvec(l, Base.show(io::IO, x::Contains) = print(io, "Contains(", val(x), ")") -function contains(lookup::AbstractCyclic{Cycling}, sel::Contains; kw...) +function contains(lookup::AbstractCyclic{Cycling}, sel::Contains; kw...) cycled_sel = rebuild(sel, cycle_val(lookup, val(sel))) - return contains(no_cycling(lookup), cycled_sel; kw...) + return contains(no_cycling(lookup), cycled_sel; kw...) end -function contains(l::NoLookup, sel::Contains; kw...) - i = Int(val(sel)) - i in l || throw(SelectorError(l, i)) - return i +function contains(l::NoLookup, sel::Contains; err=_True(), kw...) + if isinteger(val(sel)) + i = Int(val(sel)) + i in l && return i + end + if err isa _False + return nothing + else + throw(SelectorError(l, val(sel))) + end end contains(l::Lookup, sel::Contains; kw...) = contains(sampling(l), l, sel; kw...) # NoSampling (e.g. Categorical) just uses `at` @@ -373,7 +388,7 @@ end function contains(::Order, ::Points, l::Lookup, sel::Contains; kw...) at(l, At(val(sel)); kw...) end -function contains(::Order, ::Points, l::Lookup{<:AbstractArray}, sel::Contains{<:AbstractArray}; +function contains(::Order, ::Points, l::Lookup{<:AbstractArray}, sel::Contains{<:AbstractArray}; kw... ) at(l, At(val(sel)); kw...) @@ -384,22 +399,22 @@ function contains(sampling::Intervals, l::Lookup, sel::Contains; err=_True()) contains(order(l), span(l), sampling, locus(l), l, sel; err) end function contains( - sampling::Intervals, l::Lookup{<:IntervalSets.Interval}, sel::Contains; - err=_True() + sampling::Intervals, l::Lookup{<:IntervalSets.Interval}, sel::Contains; + kw... ) v = val(sel) interval_sel = Contains(Interval{:closed,:open}(v, v)) - contains(sampling, l, interval_sel; err) + contains(sampling, l, interval_sel; kw...) end function contains( - ::Intervals, - l::Lookup{<:IntervalSets.Interval}, - sel::Contains{<:IntervalSets.Interval}; + ::Intervals, + l::Lookup{<:IntervalSets.Interval}, + sel::Contains{<:IntervalSets.Interval}; err=_True() ) v = val(sel) i = searchsortedlast(l, v; by=_by) - + if i in eachindex(l) && _in(v, l[i]) return i else @@ -569,12 +584,12 @@ end # NoIndex behaves like `Sampled` `ForwardOrdered` `Points` of 1:N Int function between(l::NoLookup, sel::Interval) x = intersect(sel, first(axes(l, 1))..last(axes(l, 1))) - return ceil(Int, x.left):floor(Int, x.right) + return ceil(Int, x.left):floor(Int, x.right) end -# function between(l::AbstractCyclic{Cycling}, sel::Interval) +# function between(l::AbstractCyclic{Cycling}, sel::Interval) # cycle_val(l, sel.x)..cycle_val(l, sel.x) # cycled_sel = rebuild(sel; val=) -# near(no_cycling(lookup), cycled_sel; kw...) +# near(no_cycling(lookup), cycled_sel; kw...) # end between(l::Lookup, interval::Interval) = between(sampling(l), l, interval) # This is the main method called above @@ -668,7 +683,7 @@ end # Irregular Intervals ----------------------- # -# This works a little differently to Regular variants, +# This works a little differently to Regular variants, # as we have to work with unequal step sizes, calculating them # as we find close values. # @@ -685,7 +700,7 @@ function _between_irreg_side(side, locus::Union{Start,End}, o, l, interval, v) i = ordered_lastindex(l) cellbound = v else - s = _ordscalar(o) + s = _ordscalar(o) # Search for the value and offset per order/locus/side i = _searchfunc(o)(l, v; lt=_lt(side)) i -= s * (_posscalar(locus) + _sideshift(side)) @@ -900,7 +915,7 @@ end # Irregular Intervals ----------------------- # -# This works a little differently to Regular variants, +# This works a little differently to Regular variants, # as we have to work with unequal step sizes, calculating them # as we find close values. # @@ -929,7 +944,7 @@ function _touches_irreg_side(side, locus::Center, o, l, sel, v) i = _searchfunc(o)(l, v; lt=_lt(side)) i1 = i - _ordscalar(o) # We are at the start or end, return i - if (i1 < firstindex(l) || i1 > lastindex(l)) + if (i1 < firstindex(l) || i1 > lastindex(l)) i else # Calculate the size of the current step @@ -993,7 +1008,7 @@ end All(selectors::Selector...) -Selector that combines the results of other selectors. +Selector that combines the results of other selectors. The indices used will be the union of all result sorted in ascending order. ## Example diff --git a/test/selector.jl b/test/selector.jl index bf7de7a92..0202991df 100644 --- a/test/selector.jl +++ b/test/selector.jl @@ -41,6 +41,8 @@ A = DimArray([1 2 3; 4 5 6], dims_) @test at(startrev, At(29.9; atol=0.2)) == 1 @test at(startfwd, At(30.1; atol=0.2)) == 20 @test at(startrev, At(30.1; atol=0.2)) == 1 + @test_throws ArgumentError at(startrev, At(0.0; atol=0.2)) + @test at(startrev, At(0.0; atol=0.2); err=Lookups._False()) == nothing end @testset "Start between" begin @@ -220,6 +222,7 @@ A = DimArray([1 2 3; 4 5 6], dims_) @test_throws SelectorError contains(startfwd, Contains(31)) @test_throws SelectorError contains(startrev, Contains(31)) @test_throws SelectorError contains(startrev, Contains(10.9)) + @test contains(startrev, Contains(10.9); err=Lookups._False()) == nothing @test contains(startfwd, Contains(11)) == 1 @test contains(startfwd, Contains(11.9)) == 1 @test contains(startfwd, Contains(12.0)) == 2 @@ -1408,7 +1411,8 @@ end @test selectindices(l, Near(200.1)) == 100 @test selectindices(l, Near(-200.1)) == 1 @test selectindices(l, Contains(20)) == 20 - @test_throws InexactError selectindices(l, Contains(20.1)) + @test_throws SelectorError selectindices(l, Contains(20.1)) + @test selectindices(l, Contains(20.1); err=Lookups._False()) === nothing @test_throws SelectorError selectindices(l, Contains(0)) @test_throws SelectorError selectindices(l, Contains(200)) @test selectindices(l, 20.1..40) == 21:40 From cd5fb48b9c373edac4f40457bbc0ffb92ff2a5bf Mon Sep 17 00:00:00 2001 From: Rafael Schouten Date: Sun, 14 Apr 2024 21:15:20 +0200 Subject: [PATCH 085/108] bump patch version to 0.26.8 --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 0db5c6163..336b040bc 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "DimensionalData" uuid = "0703355e-b756-11e9-17c0-8b28908087d0" authors = ["Rafael Schouten "] -version = "0.26.7" +version = "0.26.8" [deps] Adapt = "79e6a3ab-5dfb-504d-930d-738a2a938a0e" From 9d882d25ee8d6d6117edac5fcef4db1d2f28c558 Mon Sep 17 00:00:00 2001 From: Rafael Schouten Date: Mon, 15 Apr 2024 17:26:23 +0200 Subject: [PATCH 086/108] Breaking: better DimStack indexing, again (#689) * stac indexing fixes * bugfix --- src/array/show.jl | 2 +- src/groupby.jl | 17 ++-- src/interface_tests.jl | 4 +- src/stack/indexing.jl | 116 ++++++++++++----------- src/stack/stack.jl | 66 +++++++++----- test/indexing.jl | 202 ++++++++++++++++++++++++++--------------- test/stack.jl | 10 +- 7 files changed, 258 insertions(+), 159 deletions(-) diff --git a/src/array/show.jl b/src/array/show.jl index 6b7398284..0a2c3e16d 100644 --- a/src/array/show.jl +++ b/src/array/show.jl @@ -253,7 +253,7 @@ function _print_matrix(io::IO, A::AbstractArray{<:Any,1}, lookups::Tuple) copyto!(top, CartesianIndices(top), A, CartesianIndices(itop)) bottom = Array{eltype(A)}(undef, length(ibottom)) copyto!(bottom, CartesianIndices(bottom), A, CartesianIndices(ibottom)) - vals = vcat(parent(A)[itop], parent(A)[ibottom]) + vals = vcat(A[itop], A[ibottom]) lu = only(lookups) if lu isa NoLookup Base.print_matrix(io, vals) diff --git a/src/groupby.jl b/src/groupby.jl index 0e4e94c4a..15551725f 100644 --- a/src/groupby.jl +++ b/src/groupby.jl @@ -81,15 +81,16 @@ end Base.alignment(io::IO, s::DimSummariser) = (textwidth(sprint(show, s)), 0) # An array that doesn't know what it holds, to simplify dispatch -struct OpaqueArray{T,N,A<:AbstractArray{T,N}} <: AbstractArray{T,N} - parent::A +# It can also hole somthing that is not an AbstractArray itself. +struct OpaqueArray{T,N,P} <: AbstractArray{T,N} + parent::P end -Base.parent(A::OpaqueArray) = A.parent -Base.size(A::OpaqueArray) = size(parent(A)) -for f in (:getindex, :view, :dotview) - @eval Base.$f(A::OpaqueArray, args...) = Base.$f(parent(A), args...) -end -Base.setindex!(A::OpaqueArray, args...) = Base.setindex!(parent(A), args...) +OpaqueArray(A::P) where P<:AbstractArray{T,N} where {T,N} = OpaqueArray{T,N,P}(A) +OpaqueArray(st::P) where P<:AbstractDimStack{<:Any,T,N} where {T,N} = OpaqueArray{T,N,P}(st) + +Base.size(A::OpaqueArray) = size(A.parent) +Base.getindex(A::OpaqueArray, args...) = Base.getindex(A.parent, args...) +Base.setindex!(A::OpaqueArray, args...) = Base.setindex!(A.parent, args...) abstract type AbstractBins <: Function end diff --git a/src/interface_tests.jl b/src/interface_tests.jl index fe1cc582a..c2680d89c 100644 --- a/src/interface_tests.jl +++ b/src/interface_tests.jl @@ -82,9 +82,9 @@ stack_tests = (; ), refdims_base = "`refdims` returns a tuple of Dimension or empty" => A -> refdims(A) isa Tuple{Vararg{Dimension}}, - ndims = "number of dims matches dimensions of array" => + ndims = "number of dims matches ndims of stack" => A -> length(dims(A)) == ndims(A), - size = "length of dims matches dimensions of array" => + size = "length of dims matches size of stack" => A -> map(length, dims(A)) == size(A), rebuild_parent = "rebuild parent from args" => A -> parent(rebuild(A, map(a -> reverse(a; dims=1), parent(A)))) == map(a -> reverse(a; dims=1), parent(A)), diff --git a/src/stack/indexing.jl b/src/stack/indexing.jl index f7f2ff386..59f5a42da 100644 --- a/src/stack/indexing.jl +++ b/src/stack/indexing.jl @@ -1,21 +1,62 @@ # getindex/view/setindex! ====================================================== #### getindex #### +# +function _maybe_extented_layers(s) + if hassamedims(s) + values(s) + else + map(A -> DimExtensionArray(A, dims(s)), values(s)) + end +end # Symbol key 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::NTuple{<:Any,Symbol}) + @eval Base.@constprop :aggressive @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.@constprop :aggressive @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( + @eval Base.@constprop :aggressive @propagate_inbounds function Base.$f( s::AbstractDimStack, keys::Union{<:Not{Symbol},<:Not{<:NTuple{<:Any,Symbol}}} ) rebuild_from_arrays(s, layers(s)[keys]) end end +Base.@assume_effects :effect_free @propagate_inbounds function Base.getindex(s::AbstractVectorDimStack, i::Union{AbstractVector,Colon}) + # Use dimensional indexing + Base.getindex(s, rebuild(only(dims(s)), i)) +end +Base.@assume_effects :effect_free @propagate_inbounds function Base.getindex( + s::AbstractDimStack{<:Any,T}, i::Union{AbstractArray,Colon} +) where {T} + ls = _maybe_extented_layers(s) + inds = to_indices(first(ls), (i,))[1] + out = similar(inds, T) + for (i, ind) in enumerate(inds) + out[i] = T(map(v -> v[ind], ls)) + end + return out +end +@propagate_inbounds function Base.getindex(s::AbstractDimStack{<:Any,<:Any,N}, i::Integer) where N + if N == 1 && hassamedims(s) + # This is a few ns faster when possible + map(l -> l[i], s) + else + # Otherwise use dimensional indexing + s[DimIndices(s)[i]] + end +end + +@propagate_inbounds function Base.view(s::AbstractVectorDimStack, i::Union{AbstractVector{<:Integer},Colon,Integer}) + Base.view(s, DimIndices(s)[i]) +end +@propagate_inbounds function Base.view(s::AbstractDimStack, i::Union{AbstractArray{<:Integer},Colon,Integer}) + # Pretend the stack is an AbstractArray so `SubArray` accepts it. + Base.view(OpaqueArray(s), i) +end + for f in (:getindex, :view, :dotview) _dim_f = Symbol(:_dim_, f) @eval begin @@ -25,38 +66,23 @@ for f in (:getindex, :view, :dotview) @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) && length(dims(s)) == 1 - map(l -> Base.$f(l, i), s) - else - Base.$f(s, DimIndices(s)[i]) - end + @propagate_inbounds function Base.$f(s::AbstractVectorDimStack, i::Union{CartesianIndices,CartesianIndex}) + I = to_indices(CartesianIndices(s), (i,)) + Base.$f(s, I...) end @propagate_inbounds function Base.$f(s::AbstractDimStack, i::Union{CartesianIndices,CartesianIndex}) I = to_indices(CartesianIndices(s), (i,)) Base.$f(s, I...) end - @propagate_inbounds function Base.$f(s::AbstractDimStack, i::Union{AbstractArray,Colon}) - if length(dims(s)) > 1 - if $f == getindex - ls = map(A -> vec(DimExtensionArray(A, dims(s))), layers(s)) - i = i isa Colon ? eachindex(first(ls)) : i - map(i) do n - map(Base.Fix2(getindex, n), ls) - end - else - Base.$f(s, view(DimIndices(s), i)) - end - elseif length(dims(s)) == 1 - Base.$f(s, rebuild(only(dims(s)), i)) - else - checkbounds(s, i) - end - end @propagate_inbounds function Base.$f(s::AbstractDimStack, i1, i2, Is...) I = to_indices(CartesianIndices(s), Lookups._construct_types(i1, i2, Is...)) # Check we have the right number of dimensions if length(dims(s)) > length(I) + @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 throw(BoundsError(dims(s), I)) elseif length(dims(s)) < length(I) # Allow trailing ones @@ -75,26 +101,12 @@ for f in (:getindex, :view, :dotview) $_dim_f(s, _simplify_dim_indices(D..., kw2dims(values(kw))...)...) end # Ambiguities - @propagate_inbounds function Base.$f( - ::AbstractDimStack, - ::_DimIndicesAmb, - ::Union{Tuple{Dimension,Vararg{Dimension}},AbstractArray{<:Dimension},AbstractArray{<:Tuple{Dimension,Vararg{Dimension}}},DimIndices,DimSelectors,Dimension}, - ::_DimIndicesAmb... + @propagate_inbounds function Base.$f(s::DimensionalData.AbstractVectorDimStack, + i::Union{AbstractVector{<:DimensionalData.Dimensions.Dimension}, + AbstractVector{<:Tuple{DimensionalData.Dimensions.Dimension, Vararg{DimensionalData.Dimensions.Dimension}}}, + DimensionalData.DimIndices{T,1} where T, DimensionalData.DimSelectors{T,1} where T} ) - $_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}}} - ) - $_dim_f(s, _simplify_dim_indices(d1, D...)) - end - @propagate_inbounds function Base.$f( - s::AbstractDimStack, - D::Union{AbstractArray{Union{}},DimIndices{<:Integer},DimSelectors{<:Integer}} - ) - $_dim_f(s, _simplify_dim_indices(D...)) + $_dim_f(s, _simplify_dim_indices(i)...) end @@ -107,7 +119,7 @@ for f in (:getindex, :view, :dotview) @propagate_inbounds function $_dim_f(s::AbstractDimStack) map(Base.$f, s) end - Base.@assume_effects :foldable @propagate_inbounds function $_dim_f(s::AbstractDimStack, d1::Dimension, ds::Dimension...) + Base.@assume_effects :foldable @propagate_inbounds function $_dim_f(s::AbstractDimStack{K}, d1::Dimension, ds::Dimension...) where K D = (d1, ds...) extradims = otherdims(D, dims(s)) length(extradims) > 0 && Dimensions._extradimswarn(extradims) @@ -116,18 +128,18 @@ for f in (:getindex, :view, :dotview) I = length(layerdims) > 0 ? layerdims : map(_ -> :, size(A)) Base.$f(A, I...) end - newlayers = map(f, layers(s)) + newlayers = map(f, values(s)) # Dicide to rewrap as an AbstractDimStack, or return a scalar 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 + non_scalar_layers = map(values(s), newlayers) do l, nl nl isa AbstractDimArray ? nl : rebuild(l, fill(nl), ()) end - rebuildsliced(Base.$f, s, non_scalar_layers, (dims2indices(dims(s), D))) + rebuildsliced(Base.$f, s, NamedTuple{K}(non_scalar_layers), (dims2indices(dims(s), D))) else # All scalars, return as-is - newlayers - end + NamedTuple{K}(newlayers) + end end end end diff --git a/src/stack/stack.jl b/src/stack/stack.jl index 8caeb469c..44def5749 100644 --- a/src/stack/stack.jl +++ b/src/stack/stack.jl @@ -25,7 +25,9 @@ To extend `AbstractDimStack`, implement argument and keyword version of The constructor of an `AbstractDimStack` must accept a `NamedTuple`. """ -abstract type AbstractDimStack{L} end +abstract type AbstractDimStack{K,T,N,L} end +const AbstractVectorDimStack = AbstractDimStack{K,T,1} where {K,T} +const AbstractMatrixDimStack = AbstractDimStack{K,T,2} where {K,T} data(s::AbstractDimStack) = getfield(s, :data) dims(s::AbstractDimStack) = getfield(s, :dims) @@ -33,16 +35,21 @@ refdims(s::AbstractDimStack) = getfield(s, :refdims) metadata(s::AbstractDimStack) = getfield(s, :metadata) layerdims(s::AbstractDimStack) = getfield(s, :layerdims) -layerdims(s::AbstractDimStack, key::Symbol) = dims(s, layerdims(s)[key]) -layermetadata(s::AbstractDimStack) = getfield(s, :layermetadata) -layermetadata(s::AbstractDimStack, key::Symbol) = layermetadata(s)[key] +@inline layerdims(s::AbstractDimStack, key::Symbol) = dims(s, layerdims(s)[key]) +@inline layermetadata(s::AbstractDimStack) = getfield(s, :layermetadata) +@inline layermetadata(s::AbstractDimStack, key::Symbol) = layermetadata(s)[key] layers(nt::NamedTuple) = nt -@assume_effects :foldable layers(s::AbstractDimStack{<:NamedTuple{Keys}}) where Keys = - NamedTuple{Keys}(map(K -> s[K], Keys)) -@assume_effects :foldable DD.layers(s::AbstractDimStack, i::Integer) = s[keys(s)[i]] +@generated function layers(s::AbstractDimStack{K}) where K + expr = Expr(:tuple, map(k -> :(s[$(QuoteNode(k))]), K)...) + return :(NamedTuple{K}($expr)) +end +@assume_effects :foldable DD.layers(s::AbstractDimStack{K}, i::Integer) where K = s[K[i]] @assume_effects :foldable DD.layers(s::AbstractDimStack, k::Symbol) = s[k] +@assume_effects :foldable data_eltype(nt::NamedTuple{K}) where K = NamedTuple{K,Tuple{map(eltype, Tuple(nt))...}} +stacktype(s, data, dims, layerdims::NamedTuple{K}) where K = basetypeof(s){K,data_eltype(data),length(dims)} + const DimArrayOrStack = Union{AbstractDimArray,AbstractDimStack} @assume_effects :foldable function hassamedims(s::AbstractDimStack) @@ -53,12 +60,12 @@ function rebuild( s::AbstractDimStack, data, dims=dims(s), refdims=refdims(s), layerdims=layerdims(s), metadata=metadata(s), layermetadata=layermetadata(s) ) - basetypeof(s)(data, dims, refdims, layerdims, metadata, layermetadata) + stacktype(s, data, dims, layerdims)(data, dims, refdims, layerdims, metadata, layermetadata) end function rebuild(s::AbstractDimStack; data=data(s), dims=dims(s), refdims=refdims(s), layerdims=layerdims(s), metadata=metadata(s), layermetadata=layermetadata(s) ) - basetypeof(s)(data, dims, refdims, layerdims, metadata, layermetadata) + stacktype(s, data, dims, layerdims)(data, dims, refdims, layerdims, metadata, layermetadata) end function rebuildsliced(f::Function, s::AbstractDimStack, layers, I) @@ -85,7 +92,7 @@ Keywords are simply the fields of the stack object: - `layermetadata` """ function rebuild_from_arrays( - s::AbstractDimStack{<:NamedTuple{Keys}}, das::Tuple{Vararg{AbstractBasicDimArray}}; kw... + s::AbstractDimStack{Keys}, das::Tuple{Vararg{AbstractBasicDimArray}}; kw... ) where Keys rebuild_from_arrays(s, NamedTuple{Keys}(das), kw...) end @@ -120,40 +127,43 @@ Base.:(==)(s1::AbstractDimStack, s2::AbstractDimStack) = Base.read(s::AbstractDimStack) = map(read, s) # Array-like -Base.ndims(s::AbstractDimStack) = length(dims(s)) Base.size(s::AbstractDimStack) = map(length, dims(s)) Base.size(s::AbstractDimStack, dims::DimOrDimType) = size(s, dimnum(s, dims)) Base.size(s::AbstractDimStack, dims::Integer) = size(s)[dims] +Base.length(s::AbstractDimStack) = prod(size(s)) Base.axes(s::AbstractDimStack) = map(first ∘ axes, dims(s)) Base.axes(s::AbstractDimStack, dims::DimOrDimType) = axes(s, dimnum(s, dims)) Base.axes(s::AbstractDimStack, dims::Integer) = axes(s)[dims] Base.similar(s::AbstractDimStack, args...) = map(A -> similar(A, args...), s) -Base.eltype(s::AbstractDimStack, args...) = NamedTuple{keys(s),Tuple{map(eltype, s)...}} +Base.eltype(::AbstractDimStack{<:Any,T}) where T = T +Base.ndims(::AbstractDimStack{<:Any,<:Any,N}) where N = N Base.CartesianIndices(s::AbstractDimStack) = CartesianIndices(dims(s)) Base.LinearIndices(s::AbstractDimStack) = LinearIndices(CartesianIndices(map(l -> axes(l, 1), lookup(s)))) function Base.eachindex(s::AbstractDimStack) li = LinearIndices(s) first(li):last(li) end +Base.firstindex(s::AbstractDimStack) = first(LinearIndices(s)) +Base.lastindex(s::AbstractDimStack) = last(LinearIndices(s)) +Base.first(s::AbstractDimStack) = s[firstindex((s))] +Base.last(s::AbstractDimStack) = s[lastindex(LinearIndices(s))] +Base.copy(s::AbstractDimStack) = modify(copy, s) # all of methods.jl is also Array-like... # NamedTuple-like @assume_effects :foldable Base.getproperty(s::AbstractDimStack, x::Symbol) = s[x] -Base.haskey(s::AbstractDimStack, k) = k in keys(s) +Base.haskey(s::AbstractDimStack{K}, k) where K = k in K Base.values(s::AbstractDimStack) = values(layers(s)) Base.checkbounds(s::AbstractDimStack, I...) = checkbounds(CartesianIndices(s), I...) Base.checkbounds(T::Type, s::AbstractDimStack, I...) = checkbounds(T, CartesianIndices(s), I...) -@inline Base.keys(s::AbstractDimStack) = keys(data(s)) -@inline Base.propertynames(s::AbstractDimStack) = keys(data(s)) +@inline Base.keys(s::AbstractDimStack{K}) where K = K +@inline Base.propertynames(s::AbstractDimStack{K}) where K = K Base.setindex(s::AbstractDimStack, val::AbstractBasicDimArray, key) = rebuild_from_arrays(s, Base.setindex(layers(s), val, key)) Base.NamedTuple(s::AbstractDimStack) = NamedTuple(layers(s)) # Remove these, but explain Base.iterate(::AbstractDimStack, args...) = error("Use iterate(layers(s)) rather than `iterate(s)`") #iterate(layers(s), args...) -Base.length(::AbstractDimStack) = error("Use length(layers(s)) rather than `length(s)`") # length(keys(s)) -Base.first(::AbstractDimStack) = error("Use first(layers(s)) rather than `first(s)`") -Base.last(::AbstractDimStack) = error("Use last(layers(s)) rather than `last(s)`") # `merge` for AbstractDimStack and NamedTuple. # One of the first three arguments must be an AbstractDimStack for dispatch to work. @@ -236,8 +246,8 @@ function _layerkeysfromdim(A, dim) end end -_check_same_names(::Union{AbstractDimStack{<:NamedTuple{names}},NamedTuple{names}}, - ::Union{AbstractDimStack{<:NamedTuple{names}},NamedTuple{names}}...) where {names} = nothing +_check_same_names(::Union{AbstractDimStack{names},NamedTuple{names}}, + ::Union{AbstractDimStack{names},NamedTuple{names}}...) where {names} = nothing _check_same_names(::Union{AbstractDimStack,NamedTuple}, ::Union{AbstractDimStack,NamedTuple}...) = throw(ArgumentError("Named tuple names do not match.")) @@ -346,13 +356,25 @@ true ``` """ -struct DimStack{L,D<:Tuple,R<:Tuple,LD<:Union{NamedTuple,Nothing},M,LM<:NamedTuple} <: AbstractDimStack{L} +struct DimStack{K,T,N,L,D<:Tuple,R<:Tuple,LD<:NamedTuple{K},M,LM<:Union{Nothing,NamedTuple{K}}} <: AbstractDimStack{K,T,N,L} data::L dims::D refdims::R layerdims::LD metadata::M layermetadata::LM + function DimStack( + data, dims, refdims, layerdims::LD, metadata, layermetadata + ) where LD<:NamedTuple{K} where K + T = data_eltype(data) + N = length(dims) + DimStack{K,T,N}(data, dims, refdims, layerdims, metadata, layermetadata) + end + function DimStack{K,T,N}( + data::L, dims::D, refdims::R, layerdims::LD, metadata::M, layermetadata::LM + ) where {K,T,N,L,D,R,LD<:NamedTuple{K},M,LM} + new{K,T,N,L,D,R,LD,M,LM}(data, dims, refdims, layerdims, metadata, layermetadata) + end end DimStack(@nospecialize(das::AbstractDimArray...); kw...) = DimStack(collect(das); kw...) DimStack(@nospecialize(das::Tuple{Vararg{AbstractDimArray}}); kw...) = DimStack(collect(das); kw...) @@ -399,4 +421,4 @@ function DimStack(data::NamedTuple, dims::Tuple; DimStack(data, format(dims, first(data)), refdims, layerdims, metadata, layermetadata) end -layerdims(s::DimStack{<:Any,<:Any,<:Any,Nothing}, key::Symbol) = dims(s) +layerdims(s::DimStack{<:Any,<:Any,<:Any,<:Any,<:Any,<:Any,Nothing}, key::Symbol) = dims(s) diff --git a/test/indexing.jl b/test/indexing.jl index 1d8918327..55ccd251f 100644 --- a/test/indexing.jl +++ b/test/indexing.jl @@ -508,44 +508,118 @@ end s = DimStack((da1, da2, da3)) s_mixed = DimStack((da1, da2, da3, da4)) - @testset "getindex" begin - @test @inferred s[1, 1] === (one=1.0, two=2.0f0, three=3) - @test @inferred s_mixed[1, 1] === (one=1.0, two=2.0f0, three=3, four=4) - @test @inferred s[X(2), Y(3)] === (one=6.0, two=12.0f0, three=18) - @test @inferred s[X=At(:b), Y=At(10.0)] === (one=4.0, two=8.0f0, three=12) - slicedds = s[At(:a), :] - @test 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]) - slicedds_mixed = s_mixed[At(:a), :] - @test slicedds_mixed[:one] == [1.0, 2.0, 3.0] - @test parent(slicedds_mixed) == (one=[1.0, 2.0, 3.0], two=[2.0f0, 4.0f0, 6.0f0], three=[3, 6, 9], four=fill(4)) - end - - @testset "linear indices" begin - linear2d = @inferred s[1] - @test linear2d isa NamedTuple - @test linear2d == (one=1.0, two=2.0f0, three=3) - @test_broken linear1d = @inferred view(s[X(2)], 1) - linear1d = view(s[X(2)], 1) - @test linear1d isa DimStack - @test parent(linear1d) == (one=fill(4.0), two=fill(8.0f0), three=fill(12)) - @test @inferred s[1:2] isa Array - linear2d = s[1:2] - @test linear2d == [(one=1.0, two=2.0f0, three=3), (one=4.0, two=8.0f0, three=12)] - linear1d = @inferred s[X(1)][1:2] - linear1d = @inferred s[X(1)][1:2] - @test linear1d isa DimStack - @test parent(linear1d) == (one=[1.0, 2.0], two=[2.0f0, 4.0f0], three=[3, 6]) - end - - @testset "getindex Tuple" begin - @test_broken st1 = @inferred s[(:three, :one)] - st1 = s[(:three, :one)] - @test keys(st1) === (:three, :one) - @test values(st1) == (da3, da1) + @testset "cartesian Int" begin + @inferred s[1, 1] + @inferred view(s, 1, 1) + @test view(s, Begin, Begin)[] === view(s, 1, 1)[] === + s[Begin, Begin] === s[1, 1] === (one=1.0, two=2.0f0, three=3) + @test view(s_mixed, 1, 1)[] == view(s_mixed, 1, 1)[] == + s_mixed[Begin, Begin] == (one=1.0, two=2.0f0, three=3, four=4) end - @testset "mixed CartesianIndex and CartesianIndices indexing works" begin + @testset "cartesian mixed" begin + @inferred s[At(:a), :] + @inferred view(s, At(:a), :) + @inferred s_mixed[At(:a), :] + @inferred view(s_mixed, At(:a), :) + + @test s[At(:a), :] == view(s, At(:a), :) == + s[1, :] == view(s, 1, :) == + s[Begin, :] == view(s, Begin, :) == + s[1, 1:3] == view(s, 1, 1:3) == + s[1, Begin:End] == view(s, 1, Begin:End) == + s[X=1, Y=Begin:End] == view(s, X=1, Y=Begin:End) == + s[X=At(:a), Y=Begin:End] == view(s, X=At(:a), Y=Begin:End) == + s[Y=Begin:End, X=1] == view(s, Y=Begin:End, X=1) == + DimStack((one=[1.0, 2.0, 3.0], two=[2.0f0, 4.0f0, 6.0f0], three=[3, 6, 9]), (Y(10.0:10:30.0),)) + + y = dims(s, Y) + @test s_mixed[At(:a), :] == view(s_mixed, At(:a), :) == + s_mixed[1, :] == view(s_mixed, 1, :) == + s_mixed[Begin, :] == view(s_mixed, Begin, :) == + s_mixed[1, 1:3] == view(s_mixed, 1, 1:3) == + s_mixed[1, Begin:End] == view(s_mixed, 1, Begin:End) == + s_mixed[X=1, Y=Begin:End] == view(s_mixed, X=1, Y=Begin:End) == + s_mixed[X=At(:a), Y=Begin:End] == view(s_mixed, X=At(:a), Y=Begin:End) == + s_mixed[Y=Begin:End, X=1] == view(s_mixed, Y=Begin:End, X=1) == + DimStack((one=DimArray([1.0, 2.0, 3.0], y), two=DimArray([2.0f0, 4.0f0, 6.0f0], y), three=DimArray([3, 6, 9], y), four=DimArray(fill(4), ()))) + end + + @testset "linear" begin + s1d = s[X(2)] + @inferred s[1] + @inferred s[:] + @inferred s[[1, 2]] + @inferred s[1:2] + @inferred s1d[1] + @inferred s1d[:] + @inferred s1d[1:2] + # @inferred s[[1, 2]] # Irregular bounds are not type-stable + @inferred view(s, 1) + @inferred view(s, :) + @inferred view(s, 1:2) + @inferred view(s, [1, 2]) + @inferred view(s1d, 1) + @inferred view(s1d, :) + @inferred view(s1d, 1:2) + # @inferred view(s1d, [1, 2]) + + @testset "Integer indexing" begin + @test s[1] == view(s, 1)[] == (one=1.0, two=2.0f0, three=3) + @test s1d[1] == view(s1d, 1)[] == (one=4.0, two=8.0f0, three=12) + @test s1d[1] isa NamedTuple + @test s[1] isa NamedTuple + @test view(s1d, 1) isa DimStack + @test view(s, 1) isa SubArray{<:NamedTuple,0} + end + + @testset "Colon and Vector{Int/Bool} indexing" begin + b = [false, false, false, true, false, true] + v = [4, 6] + @test s[:][b] == s[b] == + s[:][v] == s[v] == [s[4], s[6]] == + view(s, :)[b] == view(s, b) == + view(s, :)[v] == view(s, v) == [ + (one = 5.0, two = 10.0, three = 15), + (one = 6.0, two = 12.0, three = 18), + ] + @test s_mixed[:][b] == s_mixed[b] == + s_mixed[:][v] == s_mixed[v] == [s_mixed[4], s_mixed[6]] == + view(s_mixed, :)[b] == view(s_mixed, b) == + view(s_mixed, :)[v] == view(s_mixed, v) == [ + (one = 5.0, two = 10.0, three = 15, four=16), + (one = 6.0, two = 12.0, three = 18, four=16), + ] + m = [false true false; false false true] + @test s[m] == view(s, m) == [ + (one = 2.0, two = 4.0, three = 6) + (one = 6.0, two = 12.0, three = 18) + ] + @test s_mixed[m] == view(s_mixed, m) == [ + (one = 2.0, two = 4.0, three = 6, four=4), + (one = 6.0, two = 12.0, three = 18, four=16), + ] + @test s1d[1:2] isa DimStack + @test s[1:2] isa Vector + end + end + + @testset "CartesianIndex" begin + @inferred s[CartesianIndex(2, 2)] + @inferred view(s, CartesianIndex(2, 2)) + @test s[CartesianIndex(2, 2)] == + view(s, CartesianIndex(2, 2))[] == (one=5.0, two=10.0, three=15.0) + end + + @testset "CartesianIndices" begin + @inferred s[CartesianIndices((1, 2))] + @inferred view(s, CartesianIndices((1, 2))) + @test s[CartesianIndices((1, 2))] == + view(s, CartesianIndices((1, 2))) == + s[X=1:1, Y=1:2] + end + + @testset "Mixed CartesianIndex and CartesianIndices" begin da3d = cat(da1, 10da1; dims=Z) s3 = merge(s, (; ten=da3d)) @test @inferred s3[2, CartesianIndex(2, 2)] === (one=5.0, two=10.0f0, three=15, ten=50.0) @@ -560,32 +634,18 @@ end @test @inferred s3[CartesianIndices(s3)] == s3 end + @testset "getindex Symbol Tuple" begin + @test_broken st1 = @inferred s[(:three, :one)] + st1 = s[(:three, :one)] + @test keys(st1) === (:three, :one) + @test values(st1) == (da3, da1) + end + @testset "view" begin - 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)) - @test parent(sv) == (one=[3.0 6.0]', two=[6.0f0 12.0f0]', three=[9 18]') - slicedds = view(s, X=At(:a), Y=:) - @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 - 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, 1) - @test linear1d isa DimStack - @test parent(linear1d) == (one=fill(1.0), two=fill(2.0f0), three=fill(3)) - linear2d = view(s, 1:2) - @test linear2d isa DimStack - @test NamedTuple(linear2d) == (one=[1.0, 4.0], two=[2.0f0, 8.0f0], three=[3, 12]) - linear1d = s[X(1)][1:2] - @test linear1d isa DimStack - @test parent(linear1d) == (one=[1.0, 2.0], two=[2.0f0, 4.0f0], three=[3, 6]) - end - @testset "0-dimensional return layers" begin + @testset "0-dimensional" begin + sv = @inferred view(s, Begin, Begin) + @test parent(sv) == (one=fill(1.0), two=fill(2.0f0), three=fill(3)) + @test dims(sv) == () ds = @inferred view(s, X(1), Y(1)) @test ds isa DimStack @test dims(ds) === () @@ -606,23 +666,23 @@ end @test s_set[1, 1] === (one=9.0, two=10.0f0, three=11) s_set[X=At(:b), Y=At(10.0)] = (one=7, two=11, three=13) @test s_set[2, 1] === (one=7.0, two=11.0f0, three=13) - end - @testset "Empty getindedex/view/setindex throws a BoundsError" begin - @test_throws BoundsError s[] - @test_throws BoundsError view(s) - @test_throws BoundsError s[] = 1 - end - - @testset "Cartesian indices work as usual" begin - @test @inferred s[CartesianIndex(2, 2)] == (one=5.0, two=10.0, three=15.0) - @test @inferred view(s, CartesianIndex(2, 2)) == map(d -> view(d, 2, 2), s) s_set = deepcopy(s) s_set[CartesianIndex(2, 2)] = (one=5, two=6, three=7) @test @inferred s_set[2, 2] === (one=5.0, two=6.0f0, three=7) s_set[CartesianIndex(2, 2)] = (9, 10, 11) @test @inferred s_set[2, 2] === (one=9.0, two=10.0f0, three=11) @test_throws ArgumentError s_set[CartesianIndex(2, 2)] = (seven=5, two=6, three=7) + + s_set_mixed = deepcopy(s_mixed) + s_set_mixed[1, 1] = (one=9, two=10, three=11, four=12) + @test @inferred s_set_mixed[1, 1] === (one=9.0, two=10.0f0, three=11, four=12) + end + + @testset "Empty getindedex/view/setindex throws a BoundsError" begin + @test_throws BoundsError s[] + @test_throws BoundsError view(s) + @test_throws BoundsError s[] = 1 end end diff --git a/test/stack.jl b/test/stack.jl index bbe1081f0..734ac1ebf 100644 --- a/test/stack.jl +++ b/test/stack.jl @@ -65,19 +65,23 @@ end @test eltype(mixed) === @NamedTuple{one::Float64, two::Float32, extradim::Float64} @test haskey(s, :one) == true @test haskey(s, :zero) == false - @test_throws ErrorException length(s) == 3 @test size(mixed) === (2, 3, 4) # size is as for Array @test size(mixed, Y) === 3 @test size(mixed, 3) === 4 + @test length(mixed) === 24 + @test firstindex(mixed) === 1 + @test lastindex(mixed) === 24 + @test eachindex(mixed) === 1:24 @test axes(mixed) == (Base.OneTo(2), Base.OneTo(3), Base.OneTo(4)) @test eltype(axes(mixed)) <: Dimensions.DimUnitRange @test dims(axes(mixed)) == dims(mixed) @test axes(mixed, X) == Base.OneTo(2) @test dims(axes(mixed, X)) == dims(mixed, X) @test axes(mixed, 2) == Base.OneTo(3) + @test lastindex(mixed, 2) == 3 @test dims(axes(mixed, 2)) == dims(mixed, 2) - @test_throws ErrorException first(s) == da1 # first/last are for the NamedTuple - @test_throws ErrorException last(s) == da3 + @test first(mixed) == mixed[Begin] + @test last(mixed) == mixed[End] @test NamedTuple(s) == (one=da1, two=da2, three=da3) end From ae05e7b020c169aa8bfa28863643ca0b930ccf92 Mon Sep 17 00:00:00 2001 From: Rafael Schouten Date: Mon, 15 Apr 2024 19:40:00 +0200 Subject: [PATCH 087/108] (Slightly) Breaking: make `name` more systematic (#687) * make name more systematic * error for name fallback * bugfix * bugfixes * fix error test * fix dimensions docs * more renames --- docs/src/api/dimensions.md | 3 +- docs/src/tables.md | 2 +- src/Dimensions/Dimensions.jl | 2 +- src/Dimensions/coord.jl | 2 +- src/Dimensions/dimension.jl | 37 +++++++++++++++---------- src/Dimensions/format.jl | 2 +- src/Dimensions/indexing.jl | 6 ++-- src/Dimensions/merged.jl | 4 +-- src/Dimensions/primitives.jl | 53 ++++++++++++------------------------ src/Dimensions/set.jl | 2 +- src/Dimensions/show.jl | 4 +-- src/array/array.jl | 2 +- src/array/methods.jl | 2 +- src/groupby.jl | 2 +- src/name.jl | 2 -- src/stack/stack.jl | 19 +++++++------ src/tables.jl | 10 +++---- test/dimension.jl | 18 ++++++++---- test/interface.jl | 4 +-- test/primitives.jl | 9 ------ test/stack.jl | 5 ++-- 21 files changed, 90 insertions(+), 100 deletions(-) diff --git a/docs/src/api/dimensions.md b/docs/src/api/dimensions.md index 885cea239..2ab856b58 100644 --- a/docs/src/api/dimensions.md +++ b/docs/src/api/dimensions.md @@ -57,8 +57,7 @@ They are not guaranteed to keep their interface, but usually will. ```@docs Dimensions.commondims -Dimensions.dim2key -Dimensions.key2dim +Dimensions.name2dim Dimensions.reducedims Dimensions.swapdims Dimensions.slicedims diff --git a/docs/src/tables.md b/docs/src/tables.md index 8cb2df1d2..98fcd86ec 100644 --- a/docs/src/tables.md +++ b/docs/src/tables.md @@ -12,7 +12,7 @@ are unrolled so they are all the same size, and dimensions loop to match the length of the largest layer. Columns are given the [`name`](@ref) or the array or the stack layer key. -`Dimension` columns use the `Symbol` version (the result of `DD.dim2key(dimension)`). +`Dimension` columns use the `Symbol` version (the result of `DD.name(dimension)`). Looping of dimensions and stack layers is done _lazily_, and does not allocate unless collected. diff --git a/src/Dimensions/Dimensions.jl b/src/Dimensions/Dimensions.jl index a4b3dc89d..570d64f96 100644 --- a/src/Dimensions/Dimensions.jl +++ b/src/Dimensions/Dimensions.jl @@ -33,7 +33,7 @@ using .Lookups: StandardIndices, SelTuple, CategoricalEltypes, using Base: tail, OneTo, @propagate_inbounds export name, label, dimnum, hasdim, hasselection, otherdims, commondims, combinedims, - setdims, swapdims, sortdims, lookup, set, format, rebuild, key2dim, dim2key, + setdims, swapdims, sortdims, lookup, set, format, rebuild, name2dim, basetypeof, basedims, dims2indices, slicedims, dimsmatch, comparedims, reducedims export Dimension, IndependentDim, DependentDim, XDim, YDim, ZDim, TimeDim, diff --git a/src/Dimensions/coord.jl b/src/Dimensions/coord.jl index 7eb68673b..97594d4af 100644 --- a/src/Dimensions/coord.jl +++ b/src/Dimensions/coord.jl @@ -59,7 +59,7 @@ struct Coord{T} <: Dimension{T} end function Coord(val::T, dims::Tuple) where {T<:AbstractVector} length(dims) == length(first(val)) || throw(ArgumentError("Number of dims must match number of points")) - lookup = CoordLookup(val, key2dim(dims)) + lookup = CoordLookup(val, name2dim(dims)) Coord(lookup) end const SelOrStandard = Union{Selector,StandardIndices} diff --git a/src/Dimensions/dimension.jl b/src/Dimensions/dimension.jl index 4ce4d09fa..d1b8c603a 100644 --- a/src/Dimensions/dimension.jl +++ b/src/Dimensions/dimension.jl @@ -184,8 +184,9 @@ lookuptype(x) = NoLookup name(dim::Dimension) = name(typeof(dim)) name(dim::Val{D}) where D = name(D) +name(dim::Type{D}) where D<:Dimension = nameof(D) -label(x) = string(string(name(x)), (units(x) === nothing ? "" : string(" ", units(x)))) +label(x) = string(name(x)) # Lookups methods Lookups.metadata(dim::Dimension) = metadata(lookup(dim)) @@ -227,10 +228,10 @@ end for f in (:val, :index, :lookup, :metadata, :order, :sampling, :span, :locus, :bounds, :intervalbounds, :name, :label, :units) @eval begin - $f(ds::DimTuple) = map($f, ds) + $f(ds::Tuple) = map($f, ds) $f(::Tuple{}) = () - $f(ds::DimTuple, i1, I...) = $f(ds, (i1, I...)) - $f(ds::DimTuple, I) = $f(dims(ds, key2dim(I))) + $f(ds::Tuple, i1, I...) = $f(ds, (i1, I...)) + $f(ds::Tuple, I) = $f(dims(ds, name2dim(I))) end end @@ -287,10 +288,10 @@ Base.CartesianIndices(dims::DimTuple) = CartesianIndices(map(d -> axes(d, 1), di function Extents.extent(ds::DimTuple, args...) extent_dims = _astuple(dims(ds, args...)) extent_bounds = bounds(extent_dims) - return Extents.Extent{dim2key(extent_dims)}(extent_bounds) + return Extents.Extent{name(extent_dims)}(extent_bounds) end -dims(extent::Extents.Extent{K}) where K = map(rebuild, key2dim(K), values(extent)) +dims(extent::Extents.Extent{K}) where K = map(rebuild, name2dim(K), values(extent)) dims(extent::Extents.Extent, ds) = dims(dims(extent), ds) # Produce a 2 * length(dim) matrix of interval bounds from a dim @@ -367,8 +368,7 @@ Dim{S}() where S = Dim{S}(:) name(::Type{<:Dim{S}}) where S = S basetypeof(::Type{<:Dim{S}}) where S = Dim{S} -key2dim(s::Val{S}) where S = Dim{S}() -dim2key(::Type{D}) where D<:Dim{S} where S = S +name2dim(s::Val{S}) where S = Dim{S}() """ AnonDim <: Dimension @@ -385,17 +385,22 @@ AnonDim() = AnonDim(Colon()) AnonDim(val, arg1, args...) = AnonDim(val) metadata(::AnonDim) = NoMetadata() -name(::AnonDim) = :Anon """ - @dim typ [supertype=Dimension] [name::String=string(typ)] + @dim typ [supertype=Dimension] [label::String=string(typ)] -Macro to easily define new dimensions. The supertype will be inserted -into the type of the dim. The default is simply `YourDim <: Dimension`. Making -a Dimesion inherit from `XDim`, `YDim`, `ZDim` or `TimeDim` will affect +Macro to easily define new dimensions. + +The supertype will be inserted into the type of the dim. +The default is simply `YourDim <: Dimension`. + +Making a Dimesion inherit from `XDim`, `YDim`, `ZDim` or `TimeDim` will affect automatic plot layout and other methods that dispatch on these types. `<: YDim` are plotted on the Y axis, `<: XDim` on the X axis, etc. +`label` is used in plots and similar, +if the dimension is short for a longer word. + Example: ```jldoctest using DimensionalData @@ -433,8 +438,9 @@ function dimmacro(typ, supertype, name::String=string(typ)) $typ{typeof(val)}(val) end $typ() = $typ(:) - $Dimensions.name(::Type{<:$typ}) = $(QuoteNode(Symbol(name))) - $Dimensions.key2dim(::Val{$(QuoteNode(typ))}) = $typ() + $Dimensions.name(::Type{<:$typ}) = $(QuoteNode(Symbol(typ))) + $Dimensions.label(::Type{<:$typ}) = $name + $Dimensions.name2dim(::Val{$(QuoteNode(typ))}) = $typ() end |> esc end @@ -448,6 +454,7 @@ end X [`Dimension`](@ref). `X <: XDim <: IndependentDim` ## Example: + ```julia xdim = X(2:2:10) # Or diff --git a/src/Dimensions/format.jl b/src/Dimensions/format.jl index 436111ec8..6b5a0b6c5 100644 --- a/src/Dimensions/format.jl +++ b/src/Dimensions/format.jl @@ -16,7 +16,7 @@ based on the type and element type of the values. format(dims, A::AbstractArray) = format((dims,), A) function format(dims::NamedTuple, A::AbstractArray) dims = map(keys(dims), values(dims)) do k, v - rebuild(key2dim(k), v) + rebuild(name2dim(k), v) end return format(dims, A) end diff --git a/src/Dimensions/indexing.jl b/src/Dimensions/indexing.jl index aea6e364b..7192b7aea 100644 --- a/src/Dimensions/indexing.jl +++ b/src/Dimensions/indexing.jl @@ -98,7 +98,7 @@ end end _unalligned_all_selector_error(dims) = - throw(ArgumentError("Unalligned dims: use selectors for all $(join(map(string ∘ dim2key, dims), ", ")) dims, or none of them")) + throw(ArgumentError("Unalligned dims: use selectors for all $(join(map(name, dims), ", ")) dims, or none of them")) _unwrapdim(dim::Dimension) = val(dim) _unwrapdim(x) = x @@ -116,13 +116,13 @@ _unwrapdim(x) = x @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 + map(map(name2dim, Keys), values(extent)) do k, v rebuild(k, Lookups.Interval(v...)) end end function _extent_as_touches(extent::Extents.Extent{Keys}) where Keys - map(map(key2dim, Keys), values(extent)) do k, v + map(map(name2dim, Keys), values(extent)) do k, v rebuild(k, Touches(v)) end end diff --git a/src/Dimensions/merged.jl b/src/Dimensions/merged.jl index bcfc47888..7580f27a5 100644 --- a/src/Dimensions/merged.jl +++ b/src/Dimensions/merged.jl @@ -39,7 +39,7 @@ Lookups.bounds(d::Dimension{<:MergedLookup}) = Lookups.selectindices(lookup::MergedLookup, sel::DimTuple) = selectindices(lookup, map(_val_or_nothing, sortdims(sel, dims(lookup)))) function Lookups.selectindices(lookup::MergedLookup, sel::NamedTuple{K}) where K - dimsel = map(rebuild, map(key2dim, K), values(sel)) + dimsel = map(rebuild, map(name2dim, K), values(sel)) selectindices(lookup, dimsel) end Lookups.selectindices(lookup::MergedLookup, sel::StandardIndices) = sel @@ -99,7 +99,7 @@ struct Coord{T} <: Dimension{T} end function Coord(val::T, dims::Tuple) where {T<:AbstractVector} length(dims) == length(first(val)) || throw(ArgumentError("Number of dims must match number of points")) - lookup = MergedLookup(val, key2dim(dims)) + lookup = MergedLookup(val, name2dim(dims)) Coord(lookup) end Coord(s1::SelOrStandard, s2::SelOrStandard, sels::SelOrStandard...) = Coord((s1, s2, sels...)) diff --git a/src/Dimensions/primitives.jl b/src/Dimensions/primitives.jl index 5454e84d8..6644acecb 100644 --- a/src/Dimensions/primitives.jl +++ b/src/Dimensions/primitives.jl @@ -28,43 +28,26 @@ end f(basetypeof(unwrap(D)), basetypeof(unwrap(M))) """ - key2dim(s::Symbol) => Dimension - key2dim(dims...) => Tuple{Dimension,Vararg} - key2dim(dims::Tuple) => Tuple{Dimension,Vararg} + name2dim(s::Symbol) => Dimension + name2dim(dims...) => Tuple{Dimension,Vararg} + name2dim(dims::Tuple) => Tuple{Dimension,Vararg} Convert a symbol to a dimension object. `:X`, `:Y`, `:Ti` etc will be converted. to `X()`, `Y()`, `Ti()`, as with any other dims generated with the [`@dim`](@ref) macro. All other `Symbol`s `S` will generate `Dim{S}()` dimensions. """ -function key2dim end -@inline key2dim(t::Tuple) = map(key2dim, t) -@inline key2dim(s::Symbol) = key2dim(Val{s}()) +function name2dim end +@inline name2dim(t::Tuple) = map(name2dim, t) +@inline name2dim(s::Symbol) = name2dim(Val{s}()) # Allow other things to pass through -@inline key2dim(d::Val{<:Dimension}) = d -@inline key2dim(d) = d +@inline name2dim(d::Val{<:Dimension}) = d +@inline name2dim(d) = d -# key2dim is defined for concrete instances in dimensions.jl +# name2dim is defined for concrete instances in dimensions.jl -""" - dim2key(dim::Dimension) => Symbol - dim2key(dims::Type{<:Dimension}) => Symbol - dim2key(dims::Tuple) => Tuple{Symbol,Vararg} - -Convert a dimension object to a simbol. `X()`, `Y()`, `Ti()` etc will be converted. -to `:X`, `:Y`, `:Ti`, as with any other dims generated with the [`@dim`](@ref) macro. - -All other `Dim{S}()` dimensions will generate `Symbol`s `S`. -""" -function dim2key end -@inline dim2key(dims::Tuple) = map(dim2key, dims) -@inline dim2key(dim::Dimension) = dim2key(typeof(dim)) -@inline dim2key(::Val{D}) where D <: Dimension = dim2key(D) -@inline dim2key(dt::Type{<:Dimension}) = Symbol(Base.nameof(dt)) -@inline dim2key(s::Symbol) = s - -# dim2key is defined for concrete instances in dimensions.jl -# +@deprecate key2dim name2dim +@deprecate dim2key name """ sortdims([f], tosort, order) => Tuple @@ -332,8 +315,8 @@ wrapping: 'a':1:'j' function setdims end @inline setdims(x, d1, d2, ds...) = setdims(x, (d1, d2, ds...)) @inline setdims(x) = x -@inline setdims(x, newdims::Dimension) = rebuild(x; dims=setdims(dims(x), key2dim(newdims))) -@inline setdims(x, newdims::Tuple) = rebuild(x; dims=setdims(dims(x), key2dim(newdims))) +@inline setdims(x, newdims::Dimension) = rebuild(x; dims=setdims(dims(x), name2dim(newdims))) +@inline setdims(x, newdims::Tuple) = rebuild(x; dims=setdims(dims(x), name2dim(newdims))) @inline setdims(dims::Tuple, newdim::Dimension) = setdims(dims, (newdim,)) @inline setdims(dims::Tuple, newdims::Tuple) = swapdims(dims, sortdims(newdims, dims)) @inline setdims(dims::Tuple, newdims::Tuple{}) = dims @@ -482,7 +465,7 @@ but the number of dimensions has not changed. cell step, sampling type and order. """ function reducedims end -@inline reducedims(x, dimstoreduce) = _reducedims(x, key2dim(dimstoreduce)) +@inline reducedims(x, dimstoreduce) = _reducedims(x, name2dim(dimstoreduce)) @inline _reducedims(x, dimstoreduce) = _reducedims(x, (dimstoreduce,)) @inline _reducedims(x, dimstoreduce::Tuple) = _reducedims(dims(x), dimstoreduce) @@ -660,14 +643,14 @@ function basedims end @inline basedims(x) = basedims(dims(x)) @inline basedims(ds::Tuple) = map(basedims, ds) @inline basedims(d::Dimension) = basetypeof(d)() -@inline basedims(d::Symbol) = key2dim(d) +@inline basedims(d::Symbol) = name2dim(d) @inline basedims(T::Type{<:Dimension}) = basetypeof(T)() -@inline pairs2dims(pairs::Pair...) = map(p -> basetypeof(key2dim(first(p)))(last(p)), pairs) +@inline pairs2dims(pairs::Pair...) = map(p -> basetypeof(name2dim(first(p)))(last(p)), pairs) @inline kw2dims(kw::Base.Iterators.Pairs) = kw2dims(values(kw)) # Convert `Symbol` keyword arguments to a `Tuple` of `Dimension` -@inline kw2dims(kw::NamedTuple{Keys}) where Keys = kw2dims(key2dim(Keys), values(kw)) +@inline kw2dims(kw::NamedTuple{Keys}) where Keys = kw2dims(name2dim(Keys), values(kw)) @inline kw2dims(dims::Tuple, vals::Tuple) = (rebuild(first(dims), first(vals)), kw2dims(tail(dims), tail(vals))...) @inline kw2dims(::Tuple{}, ::Tuple{}) = () @@ -745,7 +728,7 @@ end @inline _w(t::Tuple) = _wraparg(t...) @inline _w(x) = x -@inline _w(s::Symbol) = key2dim(s) +@inline _w(s::Symbol) = name2dim(s) @inline _w(::Type{T}) where T = Val{T}() @inline _flip_subtype(::typeof(<:)) = >: diff --git a/src/Dimensions/set.jl b/src/Dimensions/set.jl index 47331a23e..18776e4d4 100644 --- a/src/Dimensions/set.jl +++ b/src/Dimensions/set.jl @@ -28,7 +28,7 @@ _set(dim::Dimension, wrapper::Dimension{<:DimSetters}) = _set(dim::Dimension, newdim::Dimension) = _set(newdim, _set(val(dim), val(newdim))) # Construct types _set(dim::Dimension, ::Type{T}) where T = _set(dim, T()) -_set(dim::Dimension, key::Symbol) = _set(dim, key2dim(key)) +_set(dim::Dimension, key::Symbol) = _set(dim, name2dim(key)) _set(dim::Dimension, dt::DimType) = basetypeof(dt)(val(dim)) _set(dim::Dimension, x) = rebuild(dim; val=_set(val(dim), x)) # Set the lookup diff --git a/src/Dimensions/show.jl b/src/Dimensions/show.jl index 8c2876f3b..e4a5bd8b6 100644 --- a/src/Dimensions/show.jl +++ b/src/Dimensions/show.jl @@ -35,7 +35,7 @@ function show_dims(io::IO, mime::MIME"text/plain", dims::DimTuple; lines = 3 dc = colors[1] printstyled(io, dimsymbols(1), ' '; color=dc) - maxname = maximum(length ∘ string ∘ dim2key, dims) + maxname = maximum(length ∘ string ∘ name, dims) dim_ctx = IOContext(ctx, :dimcolor => dc, :dimname_len=> maxname) show(dim_ctx, mime, first(dims)) lines += 1 @@ -104,7 +104,7 @@ end # print a dimension name function print_dimname(io, dim::Dimension) dimname_len = get(io, :dimname_len, 0) - printstyled(io, rpad(dim2key(dim), dimname_len); color=dimcolor(io)) + printstyled(io, rpad(name(dim), dimname_len); color=dimcolor(io)) end diff --git a/src/array/array.jl b/src/array/array.jl index 712b8b1e2..f1bb170e0 100644 --- a/src/array/array.jl +++ b/src/array/array.jl @@ -734,7 +734,7 @@ julia> mergedims(ds, (X, Y) => :space) """ function mergedims(x, dt1::Tuple, dts::Tuple...) pairs = map((dt1, dts...)) do ds - ds => Dim{Symbol(map(dim2key, ds)...)}() + ds => Dim{Symbol(map(name, ds)...)}() end mergedims(x, pairs...) end diff --git a/src/array/methods.jl b/src/array/methods.jl index f343f0709..5f31b23b0 100644 --- a/src/array/methods.jl +++ b/src/array/methods.jl @@ -283,7 +283,7 @@ function _cat(catdims::Tuple, A1::AbstractDimArray, As::AbstractDimArray...) return AnonDim(NoLookup()) # TODO: handle larger dimension extensions, this is half broken end else - catdim = basedims(key2dim(catdim)) + catdim = basedims(name2dim(catdim)) end # Dimension Types and Symbols if all(x -> hasdim(x, catdim), Xin) diff --git a/src/groupby.jl b/src/groupby.jl index 15551725f..50302df7a 100644 --- a/src/groupby.jl +++ b/src/groupby.jl @@ -349,7 +349,7 @@ function DataAPI.groupby(A::DimArrayOrStack, dimfuncs::DimTuple) # Hide that the parent is a DimSlices views = OpaqueArray(DimSlices(A, indices)) # Put the groupby query in metadata - meta = map(d -> dim2key(d) => val(d), dimfuncs) + meta = map(d -> name(d) => val(d), dimfuncs) metadata = Dict{Symbol,Any}(:groupby => length(meta) == 1 ? only(meta) : meta) # Return a DimGroupByArray return DimGroupByArray(views, format(group_dims, views), (), :groupby, metadata) diff --git a/src/name.jl b/src/name.jl index 567d4129d..a7a2aec7b 100644 --- a/src/name.jl +++ b/src/name.jl @@ -39,5 +39,3 @@ Base.Symbol(::Name{X}) where X = X Base.string(::Name{X}) where X = string(X) name(x::Name) = x -name(x) = name(typeof(x)) -name(x::Type) = "" diff --git a/src/stack/stack.jl b/src/stack/stack.jl index 44def5749..2d4c5587c 100644 --- a/src/stack/stack.jl +++ b/src/stack/stack.jl @@ -31,13 +31,15 @@ const AbstractMatrixDimStack = AbstractDimStack{K,T,2} where {K,T} data(s::AbstractDimStack) = getfield(s, :data) dims(s::AbstractDimStack) = getfield(s, :dims) +name(s::AbstractDimStack) = keys(s) refdims(s::AbstractDimStack) = getfield(s, :refdims) metadata(s::AbstractDimStack) = getfield(s, :metadata) layerdims(s::AbstractDimStack) = getfield(s, :layerdims) -@inline layerdims(s::AbstractDimStack, key::Symbol) = dims(s, layerdims(s)[key]) + +@inline layerdims(s::AbstractDimStack, name::Symbol) = dims(s, layerdims(s)[name]) @inline layermetadata(s::AbstractDimStack) = getfield(s, :layermetadata) -@inline layermetadata(s::AbstractDimStack, key::Symbol) = layermetadata(s)[key] +@inline layermetadata(s::AbstractDimStack, name::Symbol) = layermetadata(s)[name] layers(nt::NamedTuple) = nt @generated function layers(s::AbstractDimStack{K}) where K @@ -156,10 +158,11 @@ Base.haskey(s::AbstractDimStack{K}, k) where K = k in K Base.values(s::AbstractDimStack) = values(layers(s)) Base.checkbounds(s::AbstractDimStack, I...) = checkbounds(CartesianIndices(s), I...) Base.checkbounds(T::Type, s::AbstractDimStack, I...) = checkbounds(T, CartesianIndices(s), I...) + @inline Base.keys(s::AbstractDimStack{K}) where K = K @inline Base.propertynames(s::AbstractDimStack{K}) where K = K -Base.setindex(s::AbstractDimStack, val::AbstractBasicDimArray, key) = - rebuild_from_arrays(s, Base.setindex(layers(s), val, key)) +Base.setindex(s::AbstractDimStack, val::AbstractBasicDimArray, name) = + rebuild_from_arrays(s, Base.setindex(layers(s), val, name)) Base.NamedTuple(s::AbstractDimStack) = NamedTuple(layers(s)) # Remove these, but explain @@ -237,9 +240,9 @@ end @noinline _stack_size_mismatch() = throw(ArgumentError("Arrays must have identical axes. For mixed dimensions, use DimArrays`")) function _layerkeysfromdim(A, dim) - map(index(A, dim)) do x + map(lookup(A, dim)) do x if x isa Number - Symbol(string(DD.dim2key(dim), "_", x)) + Symbol(string(name(dim), "_", x)) else Symbol(x) end @@ -395,7 +398,7 @@ function DimStack(A::AbstractDimArray; layersfrom=nothing, metadata=metadata(A), refdims=refdims(A), kw... ) layers = if isnothing(layersfrom) - keys = DD.name(A) in (NoName(), Symbol(""), Name(Symbol(""))) ? (:layer1,) : (DD.name(A),) + keys = name(A) in (NoName(), Symbol(""), Name(Symbol(""))) ? (:layer1,) : (name(A),) NamedTuple{keys}((A,)) else keys = Tuple(_layerkeysfromdim(A, layersfrom)) @@ -421,4 +424,4 @@ function DimStack(data::NamedTuple, dims::Tuple; DimStack(data, format(dims, first(data)), refdims, layerdims, metadata, layermetadata) end -layerdims(s::DimStack{<:Any,<:Any,<:Any,<:Any,<:Any,<:Any,Nothing}, key::Symbol) = dims(s) +layerdims(s::DimStack{<:Any,<:Any,<:Any,<:Any,<:Any,<:Any,Nothing}, name::Symbol) = dims(s) diff --git a/src/tables.jl b/src/tables.jl index 59fdc3462..63ca84969 100644 --- a/src/tables.jl +++ b/src/tables.jl @@ -28,7 +28,7 @@ Tables.schema(s::AbstractDimStack) = Tables.schema(DimTable(s)) Tables.getcolumn(t, dimnum(t, dim)) function _colnames(s::AbstractDimStack) - dimkeys = map(dim2key, dims(s)) + dimkeys = map(name, dims(s)) # The data is always the last column/s (dimkeys..., keys(s)...) end @@ -49,7 +49,7 @@ This table will have columns for the array data and columns for each as required. Column names are converted from the dimension types using -[`DimensionalData.dim2key`](@ref). This means type `Ti` becomes the +[`DimensionalData.name`](@ref). This means type `Ti` becomes the column name `:Ti`, and `Dim{:custom}` becomes `:custom`. To get dimension columns, you can index with `Dimension` (`X()`) or @@ -121,7 +121,7 @@ function DimTable(xs::Vararg{AbstractDimArray}; layernames=nothing, mergedims=no xs = isnothing(mergedims) ? xs : map(x -> DimensionalData.mergedims(x, mergedims), xs) dims_ = dims(first(xs)) dimcolumns = collect(_dimcolumns(dims_)) - dimnames = collect(map(dim2key, dims_)) + dimnames = collect(map(name, dims_)) dimarraycolumns = collect(map(vec ∘ parent, xs)) colnames = vcat(dimnames, layernames) @@ -134,9 +134,9 @@ function DimTable(x::AbstractDimArray; layersfrom=nothing, mergedims=nothing) nlayers = size(x, d) layers = [view(x, rebuild(d, i)) for i in 1:nlayers] layernames = if iscategorical(d) - Symbol.(Ref(dim2key(d)), '_', lookup(d)) + Symbol.((name(d),), '_', lookup(d)) else - Symbol.(("$(dim2key(d))_$i" for i in 1:nlayers)) + Symbol.(("$(name(d))_$i" for i in 1:nlayers)) end return DimTable(layers..., layernames=layernames, mergedims=mergedims) else diff --git a/test/dimension.jl b/test/dimension.jl index 1ff31eab3..2231b07f5 100644 --- a/test/dimension.jl +++ b/test/dimension.jl @@ -1,4 +1,4 @@ -using DimensionalData, Test, Unitful +using DimensionalData, Test, Unitful, BenchmarkTools using DimensionalData.Lookups, DimensionalData.Dimensions @dim TestDim "Testname" @@ -6,8 +6,7 @@ using DimensionalData.Lookups, DimensionalData.Dimensions @testset "dims creation macro" begin @test parent(TestDim(1:10)) == 1:10 @test val(TestDim(1:10)) == 1:10 - @test name(TestDim) == :Testname - @test label(TestDim) == "Testname" + @test name(TestDim) == :TestDim @test val(TestDim(:testval)) == :testval @test metadata(TestDim(Sampled(1:1; metadata=Metadata(a=1)))) == Metadata(a=1) @test units(TestDim) == nothing @@ -41,6 +40,15 @@ using DimensionalData.Lookups, DimensionalData.Dimensions @test TestDim(Sampled(5.0:7.0, ForwardOrdered(), Regular(1.0), Points(), NoMetadata()))[At(6.0)] == 6.0 end +@testset "name" begin + @test name(X()) == :X + @test name(Dim{:x}) == :x + @test name(TestDim()) == :TestDim + @test name(Dim{:test}()) == :test + @test (@ballocated name(Dim{:test}())) == 0 + @test name(Val{TimeDim}()) == :TimeDim +end + @testset "format" begin A = [1 2 3; 4 5 6] @test format((X, Y), A) == (X(NoLookup(Base.OneTo(2))), Y(NoLookup(Base.OneTo(3)))) @@ -55,7 +63,7 @@ end Y(Categorical(10.0:10.0:30.0, ForwardOrdered(), Metadata("metadata"=>1)))), A) == (X(Categorical([:A, :B], ForwardOrdered(), Metadata(a=5))), Y(Categorical(10.0:10.0:30.0, ForwardOrdered(), Metadata("metadata"=>1)))) - @test format((X(Sampled(Base.OneTo(2); order=ForwardOrdered(), span=Regular(), sampling=Points())), Y), A) == + @test format((X(Sampled(Base.OneTo(2); order=ForwardOrdered(), span=Regular(), sampling=Points())), Y), A) == (X(Sampled(Base.OneTo(2), ForwardOrdered(), Regular(1), Points(), NoMetadata())), Y(NoLookup(Base.OneTo(3)))) end @@ -69,7 +77,7 @@ end @test val(AnonDim()) == Colon() @test lookup(AnonDim(NoLookup())) == NoLookup() @test metadata(AnonDim()) == NoMetadata() - @test name(AnonDim()) == :Anon + @test name(AnonDim()) == :AnonDim end @testset "Basic dim and array initialisation and methods" begin diff --git a/test/interface.jl b/test/interface.jl index a962c01bd..0ebccff34 100644 --- a/test/interface.jl +++ b/test/interface.jl @@ -1,7 +1,7 @@ using DimensionalData, Interfaces, Test, Dates -@test name(nothing) == "" -@test name(Nothing) == "" +@test_throws MethodError name(nothing) +@test_throws MethodError name(Nothing) @test dims(1) == nothing @test dims(nothing) == nothing @test refdims(1) == () diff --git a/test/primitives.jl b/test/primitives.jl index 660fa8a99..ab53ebe85 100644 --- a/test/primitives.jl +++ b/test/primitives.jl @@ -48,15 +48,6 @@ end @test key2dim((:test, Ti, Ti())) == (Dim{:test}(), Ti, Ti()) end -@testset "dim2key" begin - @test dim2key(X()) == :X - @test dim2key(Dim{:x}) == :x - @test dim2key(Tst()) == :Tst - @test dim2key(Dim{:test}()) == :test - @test (@ballocated dim2key(Dim{:test}())) == 0 - @test dim2key(Val{TimeDim}()) == :TimeDim -end - @testset "_wraparg" begin @test _wraparg(X()) == (X(),) @test _wraparg((X, Y,), X,) == ((Val(X), Val(Y),), Val(X),) diff --git a/test/stack.jl b/test/stack.jl index 734ac1ebf..81f0d2001 100644 --- a/test/stack.jl +++ b/test/stack.jl @@ -38,6 +38,7 @@ end @test refdims(s) === () @test metadata(mixed) == NoMetadata() @test metadata(mixed, (X, Y, Z)) == (NoMetadata(), Dict(), NoMetadata()) + @test name(s)== (:one, :two, :three) end @testset "symbol key indexing" begin @@ -60,8 +61,8 @@ end end @testset "low level base methods" begin - @test keys(data(s)) == (:one, :two, :three) - @test keys(data(mixed)) == (:one, :two, :extradim) + @test keys(s) == (:one, :two, :three) + @test keys(mixed) == (:one, :two, :extradim) @test eltype(mixed) === @NamedTuple{one::Float64, two::Float32, extradim::Float64} @test haskey(s, :one) == true @test haskey(s, :zero) == false From d720eb293dadfafd95bd9bb8f7d7943b54b4b15e Mon Sep 17 00:00:00 2001 From: Rafael Schouten Date: Wed, 17 Apr 2024 23:16:43 +0200 Subject: [PATCH 088/108] `dimsmatch` on `name` so that `X` matches `Dim{:X}` (#696) * match dims on name as well as type * remove whitespace commits * handle Val * typo * more dimsmatch dispatch * bugfix * bugfix primitives --- src/Dimensions/primitives.jl | 71 +++++++++++++++++++++++------------- src/array/methods.jl | 4 +- src/stack/stack.jl | 2 +- test/primitives.jl | 36 ++++++++++++++++-- 4 files changed, 81 insertions(+), 32 deletions(-) diff --git a/src/Dimensions/primitives.jl b/src/Dimensions/primitives.jl index 6644acecb..fceadbb44 100644 --- a/src/Dimensions/primitives.jl +++ b/src/Dimensions/primitives.jl @@ -11,21 +11,31 @@ or are at least rotations/transformations of the same type. `f` is `<:` by default, but can be `>:` to match abstract types to concrete types. """ function dimsmatch end -@inline dimsmatch(dims, query) = dimsmatch(<:, dims, query) -@inline function dimsmatch(f::Function, dims::Tuple, query::Tuple) +@inline dimsmatch(dims, query)::Bool = dimsmatch(<:, dims, query) +@inline function dimsmatch(f::Function, dims::Tuple, query::Tuple)::Bool length(dims) == length(query) || return false all(map((d, l) -> dimsmatch(f, d, l), dims, query)) end -@inline dimsmatch(f::Function, dim, query) = dimsmatch(f, typeof(dim), typeof(query)) -@inline dimsmatch(f::Function, dim::Type, query) = dimsmatch(f, dim, typeof(query)) -@inline dimsmatch(f::Function, dim, query::Type) = dimsmatch(f, typeof(dim), query) -@inline dimsmatch(f::Function, dim::Nothing, query::Type) = false -@inline dimsmatch(f::Function, dim::Type, ::Nothing) = false -@inline dimsmatch(f::Function, dim, query::Nothing) = false -@inline dimsmatch(f::Function, dim::Nothing, query) = false +@inline dimsmatch(f::Function, dim, query)::Bool = dimsmatch(f, typeof(dim), typeof(query)) +@inline dimsmatch(f::Function, dim::Type, query)::Bool = dimsmatch(f, dim, typeof(query)) +@inline dimsmatch(f::Function, dim, query::Type)::Bool = dimsmatch(f, typeof(dim), query) +@inline dimsmatch(f::Function, dim::Nothing, query::Type)::Bool = false +@inline dimsmatch(f::Function, dim::Type, ::Nothing)::Bool = false +@inline dimsmatch(f::Function, dim, query::Nothing)::Bool = false +@inline dimsmatch(f::Function, dim::Nothing, query)::Bool = false @inline dimsmatch(f::Function, dim::Nothing, query::Nothing) = false -@inline dimsmatch(f::Function, dim::Type{D}, match::Type{M}) where {D,M} = - f(basetypeof(unwrap(D)), basetypeof(unwrap(M))) +@inline dimsmatch(f::Function, dim::Type{Val{D}}, match::Type{Val{M}}) where {D,M} = + dimsmatch(f, D, M) +@inline dimsmatch(f::Function, dim::Type{D}, match::Type{Val{M}}) where {D,M} = + dimsmatch(f, D, M) +@inline dimsmatch(f::Function, dim::Type{Val{D}}, match::Type{M}) where {D,M} = + dimsmatch(f, D, M) +@inline function dimsmatch(f::Function, dim::Type{D}, match::Type{M})::Bool where {D,M} + # Match based on type and inheritance + f(basetypeof(unwrap(D)), basetypeof(unwrap(M))) || + # Or match based on name so that Dim{:X} matches X + isconcretetype(D) && isconcretetype(M) && name(D) === name(M) +end """ name2dim(s::Symbol) => Dimension @@ -69,7 +79,15 @@ function sortdims end _asfunc(::Type{typeof(<:)}) = <: _asfunc(::Type{typeof(>:)}) = >: -@inline _sortdims(f, tosort, order::Tuple{<:Integer,Vararg}) =map(p -> tosort[p], order) +@inline function _sortdims(f, tosort, order::Tuple{<:Integer,Vararg}) + map(order) do i + if i in 1:length(tosort) + tosort[i] + else + nothing + end + end +end @inline _sortdims(f, tosort, order) = _sortdims_gen(f, tosort, order) @generated _sortdims_gen(f, tosort::Tuple, order::Tuple) = begin @@ -195,7 +213,7 @@ julia> dimnum(A, Y) """ function dimnum end @inline function dimnum(x, q1, query...) - all(hasdim(x, q1, query...)) || _extradimserror(otherdims(x, (q1, query))) + all(hasdim(x, q1, query...)) || _extradimserror(otherdims(x, (q1, query...))) _dim_query(_dimnum, MaybeFirst(), x, q1, query...) end @inline dimnum(x, query::Function) = @@ -274,21 +292,22 @@ julia> otherdims(A, (Y, Z)) ``` """ function otherdims end -@inline otherdims(x, query) = - _dim_query(_otherdims_presort, AlwaysTuple(), x, query) @inline otherdims(x, query...) = - _dim_query(_otherdims_presort, AlwaysTuple(), x, query) + _dim_query(_otherdims, AlwaysTuple(), x, query...) -@inline _otherdims_presort(f, ds, query) = _otherdims(f, ds, _sortdims(_rev_op(f), query, ds)) +@inline _otherdims(f, ds) = ds +@inline function _otherdims(f, ds, query) + sorted = sortdims(f, dims(ds, query), ds) + _otherdims_from_nothing(f, ds, sorted) +end # Work with a sorted query where the missing dims are `nothing` -@inline _otherdims(f, ds::Tuple, query::Tuple) = - (_dimifmatching(f, first(ds), first(query))..., _otherdims(f, tail(ds), tail(query))...) -@inline _otherdims(f, dims::Tuple{}, ::Tuple{}) = () +@inline _otherdims_from_nothing(f, ds::Tuple, query::Tuple) = + (_dimifnothing(f, first(ds), first(query))..., _otherdims_from_nothing(f, tail(ds), tail(query))...) +@inline _otherdims_from_nothing(f, ::Tuple{}, ::Tuple{}) = () -@inline _dimifmatching(f, dim, query) = dimsmatch(f, dim, query) ? () : (dim,) +@inline _dimifnothing(f, dim, query) = () +@inline _dimifnothing(f, dim, query::Nothing) = (dim,) -_rev_op(::typeof(<:)) = >: -_rev_op(::typeof(>:)) = <: """ setdims(X, newdims) => AbstractArray @@ -543,7 +562,7 @@ function comparedims end type && basetypeof(a) != basetypeof(b) && _dimsmismatcherror(a, b) valtype && typeof(parent(a)) != typeof(parent(b)) && _valtypeerror(a, b) val && parent(lookup(a)) != parent(lookup(b)) && _valerror(a, b) - if order + if order (isnolookup(a) || isnolookup(b) || LU.order(a) == LU.order(b)) || _ordererror(a, b) end if ignore_length_one && (Base.length(a) == 1 || Base.length(b) == 1) @@ -570,7 +589,7 @@ end @inline _comparedims(T::Type{Bool}, a::Dimension, b::AnonDim; kw...) = true @inline _comparedims(T::Type{Bool}, a::AnonDim, b::Dimension; kw...) = true @inline function _comparedims(::Type{Bool}, a::Dimension, b::Dimension; - type=true, valtype=false, val=false, length=true, order=false, ignore_length_one=false, + type=true, valtype=false, val=false, length=true, order=false, ignore_length_one=false, warn::Union{Nothing,String}=nothing, ) if type && basetypeof(a) != basetypeof(b) @@ -585,7 +604,7 @@ end isnothing(warn) || _valwarn(a, b, warn) return false end - if order && !(isnolookup(a) || isnolookup(b) || LU.order(a) == LU.order(b)) + if order && !(isnolookup(a) || isnolookup(b) || LU.order(a) == LU.order(b)) isnothing(warn) || _orderwarn(a, b, warn) return false end diff --git a/src/array/methods.jl b/src/array/methods.jl index 5f31b23b0..d52598a83 100644 --- a/src/array/methods.jl +++ b/src/array/methods.jl @@ -563,7 +563,9 @@ _unique(A::AbstractDimArray, dims) = unique(parent(A); dims=dimnum(A, dims)) _unique(A::AbstractDimArray, dims::Colon) = unique(parent(A); dims=:) Base.diff(A::AbstractDimVector; dims=1) = _diff(A, dimnum(A, dims)) -Base.diff(A::AbstractDimArray; dims) = _diff(A, dimnum(A, dims)) +Base.diff(A::AbstractDimArray; dims) = begin + _diff(A, dimnum(A, dims)) +end @inline function _diff(A::AbstractDimArray{<:Any,N}, dims::Integer) where {N} r = axes(A) diff --git a/src/stack/stack.jl b/src/stack/stack.jl index 2d4c5587c..1dfacc116 100644 --- a/src/stack/stack.jl +++ b/src/stack/stack.jl @@ -222,7 +222,7 @@ function mergedims(st::AbstractDimStack, dim_pairs::Pair...) isempty(dim_pairs) && return st # Extend missing dimensions in all layers extended_layers = map(layers(st)) do layer - if all(map((ds...) -> all(hasdim(layer, ds)), map(first, dim_pairs))) + if all(map((ds...) -> all(hasdim(layer, ds)), map(first, dim_pairs)...)) layer else DimExtensionArray(layer, dims(st)) diff --git a/test/primitives.jl b/test/primitives.jl index ab53ebe85..49f59b944 100644 --- a/test/primitives.jl +++ b/test/primitives.jl @@ -31,8 +31,14 @@ dimz = dims(da) @test (@inferred dimsmatch(nothing, Z())) == false @test (@inferred dimsmatch(nothing, nothing)) == false + @test (@ballocated dimsmatch((Z(), ZDim), (ZDim, Dimension))) == 0 @test (@ballocated dimsmatch(ZDim, Dimension)) == 0 @test (@ballocated dimsmatch((Z(), ZDim), (ZDim, XDim))) == 0 + + @testset "no type match but name matches" begin + @test (@ballocated dimsmatch(Z, Dim{:Z})) == 0 + @test (@ballocated dimsmatch((Z(), Dim{:Ti}()), (Dim{:Z}(), Ti()))) == 0 + end end @testset "key2dim" begin @@ -146,8 +152,18 @@ end @test dims(A, :two) == Dim{:two}(NoLookup(Base.OneTo(5))) end - # @test_throws ArgumentError dims(da, Ti) - # @test_throws ArgumentError dims(dimz, Ti) + @testset "non matching single queries return nothing" begin + @test dims(da, :Ti) == nothing + @test dims(da, Ti) == nothing + @test dims(da, 3) == nothing + end + + @testset "non matching tuple queries return empty tuples" begin + @test dims(da, (:Ti,)) == () + @test dims(da, (Ti,)) == () + @test dims(da, (3,)) == () + end + @test_throws ArgumentError dims(nothing, X) @test dims(dimz) === dimz @@ -194,7 +210,6 @@ end end @testset "dimnum" begin - dims(da) @test dimnum(da, Y()) == dimnum(da, 2) == 2 @test dimnum(da, Base.Fix2(isa,Y)) == (2,) @test (@ballocated dimnum($da, Y())) == 0 @@ -216,6 +231,12 @@ end dimz = (Dim{:a}(), Y(), Dim{:b}()) @test (@ballocated dimnum($dimz, :b, :a, Y)) == 0 end + + @testset "not present dimensions error" begin + @test_throws ArgumentError dimnum(da, Z()) + @test_throws ArgumentError dimnum(da, 3) + @test_throws ArgumentError dimnum(da, 0) + end end @testset "hasdim" begin @@ -267,7 +288,7 @@ end @test otherdims(A, X, Y) == dims(A, (Z,)) @test otherdims(A, Y) == dims(A, (X, Z)) @test otherdims(A, Z) == dims(A, (X, Y)) - @test otherdims(A) == dims(A, (X, Y, Z)) + @test otherdims(A) == dims(A) @test otherdims(A, DimensionalData.ZDim) == dims(A, (X, Y)) @test otherdims(A, (X, Z)) == dims(A, (Y,)) f1 = A -> otherdims(A, (X, Z)) @@ -283,6 +304,13 @@ end @test otherdims((Dim{:a}(), Dim{:b}(), Ti()), (:a, :c)) == (Dim{:b}(), Ti()) end @test_throws ArgumentError otherdims(nothing, X) + + @testset "non matching single queries return all dims" begin + @test otherdims(da, :Ti) == dims(da) + @test otherdims(da, Ti) == dims(da) + @test otherdims(da, 3) == dims(da) + @test otherdims(da, 0) == dims(da) + end end @testset "combinedims" begin From b2e1aa2af3bc08ca36d436178bdde281322d074e Mon Sep 17 00:00:00 2001 From: Felix Cremer Date: Mon, 22 Apr 2024 16:50:09 +0200 Subject: [PATCH 089/108] Small docstring improvements (#698) * Improve label docs for Bins and CyclicBins * Fix typos * Update src/groupby.jl --------- Co-authored-by: Rafael Schouten --- src/Lookups/Lookups.jl | 2 +- src/groupby.jl | 11 ++++++----- src/stack/stack.jl | 6 +++--- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/Lookups/Lookups.jl b/src/Lookups/Lookups.jl index 306911fe4..4fee63197 100644 --- a/src/Lookups/Lookups.jl +++ b/src/Lookups/Lookups.jl @@ -6,7 +6,7 @@ Module for [`Lookup`](@ref)s and [`Selector`](@ref)s used in DimensionalData.jl `Lookup` defines traits and `AbstractArray` wrappers that give specific behaviours for a lookup index when indexed with [`Selector`](@ref). -For example, these allow tracking over array order so fast indexing works evne when +For example, these allow tracking over array order so fast indexing works even when the array is reversed. To load `Lookup` types and methods into scope: diff --git a/src/groupby.jl b/src/groupby.jl index 50302df7a..011722a43 100644 --- a/src/groupby.jl +++ b/src/groupby.jl @@ -117,6 +117,7 @@ Specify bins to reduce groups after applying function `f`. `Integer`. This avoids losing the edge values. Note this is a messy solution - it will often be prefereble to manually specify a `Vector` of chosen `Interval`s rather than relying on passing an `Integer` and `pad`. +- `labels`: a list of descriptive labels for the bins. The labels need to have the same length as `bins`. When the return value of `f` is a tuple, binning is applied to the _last_ value of the tuples. """ @@ -149,7 +150,7 @@ and [`hours`](@ref) but can also be used for custom cycles. - `start`: the start of the cycle: a return value of `f`. - `step` the number of sequential values to group. - `labels`: either a vector of labels matching the number of groups, - or a function that generates labels from `Vector{Int}` of the selected months. + or a function that generates labels from `Vector{Int}` of the selected bins. When the return value of `f` is a tuple, binning is applied to the _last_ value of the tuples. """ @@ -176,7 +177,7 @@ Generates `CyclicBins` for three month periods. - `start`: By default seasons start in December, but any integer `1:12` can be used. - `labels`: either a vector of four labels, or a function that generates labels - from `Vector{Int}` of the selected months. + from `Vector{Int}` of the selected quartals. """ seasons(; start=December, kw...) = months(3; start, kw...) @@ -191,7 +192,7 @@ These can wrap around the end of a year. ## Keywords - `start`: By default months start in January, but any integer `1:12` can be used. -- `labels`: either a vector of labels matching the numver of groups, +- `labels`: either a vector of labels matching the number of groups, or a function that generates labels from `Vector{Int}` of the selected months. """ months(step; start=January, labels=Dict(1:12 .=> monthabbr.(1:12))) = CyclicBins(month; cycle=12, step, start, labels) @@ -206,9 +207,9 @@ These can wrap around the end of the day. ## Keywords -- `start`: By default seasons start in December, but any integer `1:12` can be used. +- `start`: By default seasons start at `0`, but any integer `1:24` can be used. - `labels`: either a vector of four labels, or a function that generates labels - from `Vector{Int}` of the selected months. + from `Vector{Int}` of the selected hours of the day. """ hours(step; start=0, labels=nothing) = CyclicBins(hour; cycle=24, step, start, labels) diff --git a/src/stack/stack.jl b/src/stack/stack.jl index 1dfacc116..afed49cdc 100644 --- a/src/stack/stack.jl +++ b/src/stack/stack.jl @@ -8,7 +8,7 @@ These have multiple layers of data, but share dimensions. Notably, their behaviour lies somewhere between a `DimArray` and a `NamedTuple`: - indexing with a `Symbol` as in `dimstack[:symbol]` returns a `DimArray` layer. -- iteration amd `map` are apply over array layers, as indexed with a `Symbol`. +- iteration and `map` apply over array layers, as indexed with a `Symbol`. - `getindex` and many base methods are applied as for `DimArray` - to avoid the need to allways use `map`. @@ -288,13 +288,13 @@ DimStack holds multiple objects sharing some dimensions, in a `NamedTuple`. Notably, their behaviour lies somewhere between a `DimArray` and a `NamedTuple`: - indexing with a `Symbol` as in `dimstack[:symbol]` returns a `DimArray` layer. -- iteration amd `map` are apply over array layers, as indexed with a `Symbol`. +- iteration and `map` apply over array layers, as indexed with a `Symbol`. - `getindex` or `view` with `Int`, `Dimension`s or `Selector`s that resolve to `Int` will return a `NamedTuple` of values from each layer in the stack. This has very good performace, and avoids the need to always use `map`. - `getindex` or `view` with a `Vector` or `Colon` will return another `DimStack` where all data layers have been sliced. -- `setindex!` must pass a `Tuple` or `NamedTuple` maching the layers. +- `setindex!` must pass a `Tuple` or `NamedTuple` matching the layers. - many base and `Statistics` methods (`sum`, `mean` etc) will work as for a `DimArray` again removing the need to use `map`. From 219d519f36eaa881157138e60763e3c9bc2d5adb Mon Sep 17 00:00:00 2001 From: Alex Gardner <32276930+alex-s-gardner@users.noreply.github.com> Date: Mon, 22 Apr 2024 09:37:56 -0700 Subject: [PATCH 090/108] Patch 2 (#701) * Update README.md Minor spelling/grammer fixes and update LookupArrays to Lookup * Update README.md rever small change --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 6c80b74d2..17bb7ed65 100644 --- a/README.md +++ b/README.md @@ -76,7 +76,7 @@ A[Y=1:10, X=1] 19.0 0.605331 ``` -We can also subset by lookup, using a `Selector`, lets try `At`: +One can also subset by lookup, using a `Selector`, lets try `At`: ```julia A[Y(At(25))] @@ -117,12 +117,12 @@ And the plot will have the right ticks and labels. > [!NOTE] > Recent changes have greatly reduced the exported API. -Previously exported methods can me brought into global scope by `using` -the sub-modules they have been moved to - `LookupArrays` and `Dimensions`: +Previously exported methods can be brought into global scope by `using` +the sub-modules they have been moved to - `Lookup` and `Dimensions`: ```julia using DimensionalData -using DimensionalData.LookupArrays, DimensionalData.Dimensions +using DimensionalData.Lookup, DimensionalData.Dimensions ``` > [!IMPORTANT] @@ -133,4 +133,4 @@ There are a lot of similar Julia packages in this space. AxisArrays.jl, NamedDim [AxisKeys.jl](https://github.com/mcabbott/AxisKeys.jl) and [AbstractIndices.jl](https://github.com/Tokazama/AbstractIndices.jl) are some other interesting developments. For more detail on why there are so many similar options and where things are headed, read this [thread](https://github.com/JuliaCollections/AxisArraysFuture/issues/1). The main functionality is explained here, but the full list of features is -listed at the [API](https://rafaqz.github.io/DimensionalData.jl/reference) page. \ No newline at end of file +listed at the [API](https://rafaqz.github.io/DimensionalData.jl/reference) page. From dae625580bfc4bb9db693ebf9874c9baee1240b7 Mon Sep 17 00:00:00 2001 From: Rafael Schouten Date: Wed, 24 Apr 2024 21:41:00 +0200 Subject: [PATCH 091/108] bump minor version to 0.27.0 --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 336b040bc..53949c63a 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "DimensionalData" uuid = "0703355e-b756-11e9-17c0-8b28908087d0" authors = ["Rafael Schouten "] -version = "0.26.8" +version = "0.27.0" [deps] Adapt = "79e6a3ab-5dfb-504d-930d-738a2a938a0e" From 37cd1eb53c1f86750318315e1471e7e59a678896 Mon Sep 17 00:00:00 2001 From: Rafael Schouten Date: Sun, 28 Apr 2024 15:33:53 +0200 Subject: [PATCH 092/108] fix custom dim label (#704) --- ext/DimensionalDataMakie.jl | 2 +- src/Dimensions/dimension.jl | 5 +++-- src/plotrecipes.jl | 4 ++-- test/dimension.jl | 1 + 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/ext/DimensionalDataMakie.jl b/ext/DimensionalDataMakie.jl index e9dda35a7..4bf582fe5 100644 --- a/ext/DimensionalDataMakie.jl +++ b/ext/DimensionalDataMakie.jl @@ -97,7 +97,7 @@ function _pointbased1(A, attributes; set_axis_attributes=true) axis_attributes = if set_axis_attributes Attributes(; axis=(; - xlabel=string(name(dims(A, 1))), + xlabel=string(label(dims(A, 1))), ylabel=DD.label(A), title=DD.refdims_title(A), ), diff --git a/src/Dimensions/dimension.jl b/src/Dimensions/dimension.jl index d1b8c603a..297a0e4cf 100644 --- a/src/Dimensions/dimension.jl +++ b/src/Dimensions/dimension.jl @@ -419,7 +419,7 @@ macro dim(typ::Symbol, supertyp::Symbol, args...) dimmacro(typ, supertyp, args...) end -function dimmacro(typ, supertype, name::String=string(typ)) +function dimmacro(typ, supertype, label::String=string(typ)) quote Base.@__doc__ struct $typ{T} <: $supertype{T} val::T @@ -439,8 +439,9 @@ function dimmacro(typ, supertype, name::String=string(typ)) end $typ() = $typ(:) $Dimensions.name(::Type{<:$typ}) = $(QuoteNode(Symbol(typ))) - $Dimensions.label(::Type{<:$typ}) = $name $Dimensions.name2dim(::Val{$(QuoteNode(typ))}) = $typ() + $Dimensions.label(::$typ) = $label + $Dimensions.label(::Type{<:$typ}) = $label end |> esc end diff --git a/src/plotrecipes.jl b/src/plotrecipes.jl index 2ee1627ea..7781045dc 100644 --- a/src/plotrecipes.jl +++ b/src/plotrecipes.jl @@ -48,7 +48,7 @@ end dim = dims(A, 1) :xguide --> label(dim) :yguide --> label(A) - :label --> string(name(A)) + :label --> string(label(A)) _xticks!(plotattributes, s, dim) _withaxes(dim, A) end @@ -169,7 +169,7 @@ function refdims_title(refdims::Tuple; kw...) join(map(rd -> refdims_title(rd; kw...), refdims), ", ") end function refdims_title(refdim::Dimension; kw...) - string(name(refdim), ": ", refdims_title(lookup(refdim), refdim; kw...)) + string(label(refdim), ": ", refdims_title(lookup(refdim), refdim; kw...)) end function refdims_title(lookup::AbstractSampled, refdim::Dimension; kw...) start, stop = map(string, bounds(refdim)) diff --git a/test/dimension.jl b/test/dimension.jl index 2231b07f5..dbcb6b622 100644 --- a/test/dimension.jl +++ b/test/dimension.jl @@ -11,6 +11,7 @@ using DimensionalData.Lookups, DimensionalData.Dimensions @test metadata(TestDim(Sampled(1:1; metadata=Metadata(a=1)))) == Metadata(a=1) @test units(TestDim) == nothing @test label(TestDim) == "Testname" + @test label(TestDim()) == "Testname" @test eltype(TestDim(1)) == Int @test eltype(TestDim([1, 2, 3])) <: Int @test length(TestDim(1)) == 1 From d8a3d4ba312c264743e2fca80684f7d552949649 Mon Sep 17 00:00:00 2001 From: Rafael Schouten Date: Sun, 28 Apr 2024 15:38:02 +0200 Subject: [PATCH 093/108] bump patch version to 0.27.1 --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 53949c63a..c85334ce9 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "DimensionalData" uuid = "0703355e-b756-11e9-17c0-8b28908087d0" authors = ["Rafael Schouten "] -version = "0.27.0" +version = "0.27.1" [deps] Adapt = "79e6a3ab-5dfb-504d-930d-738a2a938a0e" From 1dfd48c068bff4dbda475d53114153830d80a46e Mon Sep 17 00:00:00 2001 From: Rafael Schouten Date: Tue, 30 Apr 2024 19:42:56 +0200 Subject: [PATCH 094/108] Fix selectindices for `nothing` return type (#705) * fix selectindices for tuples and make it fast * test selectindices fixes --- src/Dimensions/dimension.jl | 19 +++++++++++++++++-- src/Lookups/selector.jl | 32 ++++++++++++++++---------------- test/selector.jl | 4 ++++ 3 files changed, 37 insertions(+), 18 deletions(-) diff --git a/src/Dimensions/dimension.jl b/src/Dimensions/dimension.jl index 297a0e4cf..a613de499 100644 --- a/src/Dimensions/dimension.jl +++ b/src/Dimensions/dimension.jl @@ -245,8 +245,23 @@ end return selectindices(dims(x), selectors; kw...) end end -@inline selectindices(ds::DimTuple, sel...; kw...) = selectindices(ds, sel; kw...) -@inline selectindices(ds::DimTuple, sel::Tuple; kw...) = selectindices(val(ds), sel; kw...) +@inline selectindices(ds::Tuple, sel...; kw...) = selectindices(ds, sel; kw...) +# Cant get this to compile away without a generated function +# The nothing handling is for if `err=_False`, and we want to combine +# multiple `nothing` into a single `nothing` return value +@generated function selectindices(ds::Tuple, sel::Tuple; kw...) + tuple_exp = Expr(:tuple) + for i in eachindex(ds.parameters) + expr = quote + x = selectindices(ds[$i], sel[$i]; kw...) + isnothing(x) && return nothing + x + end + push!(tuple_exp.args, expr) + end + return tuple_exp +end +@inline selectindices(ds::Tuple, sel::Tuple{}; kw...) = () @inline selectindices(dim::Dimension, sel; kw...) = selectindices(val(dim), sel; kw...) # Deprecated diff --git a/src/Lookups/selector.jl b/src/Lookups/selector.jl index 71e1d2620..3054ec67c 100644 --- a/src/Lookups/selector.jl +++ b/src/Lookups/selector.jl @@ -73,7 +73,7 @@ const SelectorOrInterval = Union{Selector,Interval,Not} const SelTuple = Tuple{SelectorOrInterval,Vararg{SelectorOrInterval}} # `Not` form InvertedIndices.jr -function selectindices(l::Lookup, sel::Not; kw...) +@inline function selectindices(l::Lookup, sel::Not; kw...) indices = selectindices(l, sel.skip; kw...) return first(to_indices(l, (Not(indices),))) end @@ -129,10 +129,10 @@ Base.show(io::IO, x::At) = print(io, "At(", val(x), ", ", atol(x), ", ", rtol(x) struct _True end struct _False end -selectindices(l::Lookup, sel::At; kw...) = at(l, sel; kw...) -selectindices(l::Lookup, sel::At{<:AbstractVector}; kw...) = _selectvec(l, sel; kw...) +@inline selectindices(l::Lookup, sel::At; kw...) = at(l, sel; kw...) +@inline selectindices(l::Lookup, sel::At{<:AbstractVector}; kw...) = _selectvec(l, sel; kw...) -_selectvec(l, sel; kw...) = [selectindices(l, rebuild(sel, v); kw...) for v in val(sel)] +@inline _selectvec(l, sel; kw...) = [selectindices(l, rebuild(sel, v); kw...) for v in val(sel)] function at(lookup::AbstractCyclic{Cycling}, sel::At; kw...) cycled_sel = rebuild(sel, cycle_val(lookup, val(sel))) @@ -261,8 +261,8 @@ struct Near{T} <: IntSelector{T} end Near() = Near(nothing) -selectindices(l::Lookup, sel::Near; kw...) = near(l, sel) -selectindices(l::Lookup, sel::Near{<:AbstractVector}; kw...) = _selectvec(l, sel) +@inline selectindices(l::Lookup, sel::Near; kw...) = near(l, sel) +@inline selectindices(l::Lookup, sel::Near{<:AbstractVector}; kw...) = _selectvec(l, sel) Base.show(io::IO, x::Near) = print(io, "Near(", val(x), ")") @@ -356,8 +356,8 @@ end Contains() = Contains(nothing) # Filter based on sampling and selector ----------------- -selectindices(l::Lookup, sel::Contains; kw...) = contains(l, sel; kw...) -selectindices(l::Lookup, sel::Contains{<:AbstractVector}; kw...) = _selectvec(l, sel; kw...) +@inline selectindices(l::Lookup, sel::Contains; kw...) = contains(l, sel; kw...) +@inline selectindices(l::Lookup, sel::Contains{<:AbstractVector}; kw...) = _selectvec(l, sel; kw...) Base.show(io::IO, x::Contains) = print(io, "Contains(", val(x), ")") @@ -567,8 +567,8 @@ abstract type _Side end struct _Upper <: _Side end struct _Lower <: _Side end -selectindices(l::Lookup, sel::Union{Between{<:Tuple},Interval}) = between(l, sel) -function selectindices(lookup::Lookup, sel::Between{<:AbstractVector}) +@inline selectindices(l::Lookup, sel::Union{Between{<:Tuple},Interval}) = between(l, sel) +@inline function selectindices(lookup::Lookup, sel::Between{<:AbstractVector}) inds = Int[] for v in val(sel) append!(inds, selectindices(lookup, rebuild(sel, v))) @@ -822,8 +822,8 @@ Touches(a, b) = Touches((a, b)) Base.first(sel::Touches) = first(val(sel)) Base.last(sel::Touches) = last(val(sel)) -selectindices(l::Lookup, sel::Touches) = touches(l, sel) -function selectindices(lookup::Lookup, sel::Touches{<:AbstractVector}) +@inline selectindices(l::Lookup, sel::Touches) = touches(l, sel) +@inline function selectindices(lookup::Lookup, sel::Touches{<:AbstractVector}) inds = Int[] for v in val(sel) append!(inds, selectindices(lookup, rebuild(sel, v))) @@ -1141,9 +1141,9 @@ _in(needle::Interval{<:Any,:open}, haystack::Interval{:closed,:open}) = needle.l _in(needle::Interval{:open,<:Any}, haystack::Interval{:open,:closed}) = needle.left in haystack && needle.right in haystack _in(needle::OpenInterval, haystack::OpenInterval) = needle.left in haystack && needle.right in haystack -hasselection(lookup::Lookup, sel::At) = at(lookup, sel; err=_False()) === nothing ? false : true -hasselection(lookup::Lookup, sel::Contains) = contains(lookup, sel; err=_False()) === nothing ? false : true +@inline hasselection(lookup::Lookup, sel::At) = at(lookup, sel; err=_False()) === nothing ? false : true +@inline hasselection(lookup::Lookup, sel::Contains) = contains(lookup, sel; err=_False()) === nothing ? false : true # Near and Between only fail on Unordered # Otherwise Near returns the nearest index, and Between an empty range -hasselection(lookup::Lookup, ::Near) = isordered(lookup) ? true : false -hasselection(lookup::Lookup, ::Union{Interval,Between}) = isordered(lookup) ? true : false +@inline hasselection(lookup::Lookup, ::Near) = isordered(lookup) ? true : false +@inline hasselection(lookup::Lookup, ::Union{Interval,Between}) = isordered(lookup) ? true : false diff --git a/test/selector.jl b/test/selector.jl index 0202991df..50cbe77f1 100644 --- a/test/selector.jl +++ b/test/selector.jl @@ -1420,7 +1420,11 @@ end @testset "selectindices" begin @test selectindices(A[X(1)], Contains(7)) == (3,) + @test selectindices(A, (At(10), Contains(7))) == (1, 3) @test selectindices(dims_, ()) == () + @test selectindices((), ()) == () + @test selectindices(A, (At(90), Contains(7)); err=Lookups._False()) == nothing + @test selectindices(A[X(1)], Contains(10); err=Lookups._False()) == nothing end @testset "hasselection" begin From 7e6d97a166ff6e5e8de2ca7e39a17f1d565dbdf3 Mon Sep 17 00:00:00 2001 From: Rafael Schouten Date: Tue, 30 Apr 2024 19:43:45 +0200 Subject: [PATCH 095/108] bump patch version to 0.27.2 --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index c85334ce9..910c13d66 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "DimensionalData" uuid = "0703355e-b756-11e9-17c0-8b28908087d0" authors = ["Rafael Schouten "] -version = "0.27.1" +version = "0.27.2" [deps] Adapt = "79e6a3ab-5dfb-504d-930d-738a2a938a0e" From eebd8946a164a29b122792bba8cd2193b367d487 Mon Sep 17 00:00:00 2001 From: Felix Cremer Date: Thu, 16 May 2024 17:39:18 +0200 Subject: [PATCH 096/108] Replace GeoData by Rasters in docstring (#713) --- src/Lookups/lookup_arrays.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Lookups/lookup_arrays.jl b/src/Lookups/lookup_arrays.jl index 0ee18b899..9495a03e4 100644 --- a/src/Lookups/lookup_arrays.jl +++ b/src/Lookups/lookup_arrays.jl @@ -235,7 +235,7 @@ const SAMPLED_ARGUMENTS_DOC = """ to take account for the full size of the interval, rather than the point alone. - `metadata`: a `Dict` or `Metadata` wrapper that holds any metadata object adding more information about the array axis - useful for extending DimensionalData for specific - contexts, like geospatial data in GeoData.jl. By default it is `NoMetadata()`. + contexts, like geospatial data in Rasters.jl. By default it is `NoMetadata()`. """ """ @@ -481,7 +481,7 @@ This will be automatically assigned if the lookup contains `AbstractString`, Can be provided if this is known and performance is important. - `metadata`: a `Dict` or `Metadata` wrapper that holds any metadata object adding more information about the array axis - useful for extending DimensionalData for specific - contexts, like geospatial data in GeoData.jl. By default it is `NoMetadata()`. + contexts, like geospatial data in Rasters.jl. By default it is `NoMetadata()`. ## Example From 969f953cd572d401ec4bf231c51ddcc281811b33 Mon Sep 17 00:00:00 2001 From: Tiem van der Deure Date: Sat, 18 May 2024 20:03:26 +0200 Subject: [PATCH 097/108] extend CategoricalArrays (#716) * extend CategoricalArrays Extend CategoricalArrays, so `categorical(x::DimArray)` returns a `DimArray` with categorical values, and common (documented) operations on categorical arrays work on such categorical DimArrays. * add CategoricalArrays package to test * fix tests --- Project.toml | 6 +- ext/DimensionalDataCategoricalArraysExt.jl | 73 ++++++++++++++ test/categorical.jl | 106 +++++++++++++++++++++ test/runtests.jl | 1 + 4 files changed, 185 insertions(+), 1 deletion(-) create mode 100644 ext/DimensionalDataCategoricalArraysExt.jl create mode 100644 test/categorical.jl diff --git a/Project.toml b/Project.toml index 910c13d66..dd24c7cd2 100644 --- a/Project.toml +++ b/Project.toml @@ -24,9 +24,11 @@ TableTraits = "3783bdb8-4a98-5b6b-af9a-565f29a5fe9c" Tables = "bd369af6-aec1-5ad0-b16a-f7cc5008161c" [weakdeps] +CategoricalArrays = "324d7699-5711-5eae-9e2f-1d82baa6b597" Makie = "ee78f7c6-11fb-53f2-987a-cfe4a2b5a57a" [extensions] +DimensionalDataCategoricalArraysExt = "CategoricalArrays" DimensionalDataMakie = "Makie" [compat] @@ -34,6 +36,7 @@ Adapt = "2, 3.0, 4" Aqua = "0.8" ArrayInterface = "7" BenchmarkTools = "1" +CategoricalArrays = "0.10" CairoMakie = "0.10, 0.11" ColorTypes = "0.11" Combinatorics = "1" @@ -73,6 +76,7 @@ Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595" ArrayInterface = "4fba245c-0d91-5ea0-9b3e-6abc04ee57a9" BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf" CairoMakie = "13f3f980-e62b-5c42-98c6-ff1f3baf88f0" +CategoricalArrays = "324d7699-5711-5eae-9e2f-1d82baa6b597" ColorTypes = "3da002f7-5984-5a60-b8a6-cbb66c0b333f" Combinatorics = "861a8166-3701-5b0c-9a16-15d98fcdc6aa" CoordinateTransformations = "150eb455-5306-5404-9cee-2592286d6298" @@ -91,4 +95,4 @@ Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" Unitful = "1986cc42-f94f-5a68-af5c-568840ba703d" [targets] -test = ["Aqua", "ArrayInterface", "BenchmarkTools", "ColorTypes", "Combinatorics", "CoordinateTransformations", "DataFrames", "Distributions", "Documenter", "ImageFiltering", "ImageTransformations", "CairoMakie", "OffsetArrays", "Plots", "Random", "SafeTestsets", "StatsPlots", "Test", "Unitful"] +test = ["Aqua", "ArrayInterface", "BenchmarkTools", "CategoricalArrays", "ColorTypes", "Combinatorics", "CoordinateTransformations", "DataFrames", "Distributions", "Documenter", "ImageFiltering", "ImageTransformations", "CairoMakie", "OffsetArrays", "Plots", "Random", "SafeTestsets", "StatsPlots", "Test", "Unitful"] diff --git a/ext/DimensionalDataCategoricalArraysExt.jl b/ext/DimensionalDataCategoricalArraysExt.jl new file mode 100644 index 000000000..9f5529dd2 --- /dev/null +++ b/ext/DimensionalDataCategoricalArraysExt.jl @@ -0,0 +1,73 @@ +module DimensionalDataCategoricalArraysExt + +import DimensionalData, CategoricalArrays +const DD = DimensionalData +const CAs = CategoricalArrays +CategoricalDimArray = DD.AbstractDimArray{<:Union{Missing, CAs.CategoricalValue}} + +# categorical and cut take a dimarray and return a categorical dim array +function CAs.categorical(x::DD.AbstractDimArray; kw...) + ca = CAs.categorical(Base.parent(x); kw...) + DD.rebuild(x; data = ca) +end + +# Need to define these separately to avoid ambiguity +CAs.cut(x::DD.AbstractDimArray, ng::Integer; kw...) = DD.rebuild(x; data = CAs.cut(Base.parent(x),ng; kw...)) +CAs.cut(x::DD.AbstractDimArray, breaks::AbstractVector; kw...) = DD.rebuild(x; data = CAs.cut(Base.parent(x),breaks; kw...)) + +CAs.recode(x::DD.AbstractDimArray, pairs::Pair...) = CAs.recode(x, nothing, pairs...) +CAs.recode(x::DD.AbstractDimArray, default::Any, pairs::Pair...) = DD.rebuild(x; data = CAs.recode(Base.parent(x),default, pairs...)) + +# function that mutate in-place +for f in [:levels!, :droplevels!, :fill!, :ordered!] + @eval function CAs.$f(x::CategoricalDimArray, args...; kw...) + CAs.$f(Base.parent(x), args...; kw...) + return x + end +end + +# functions that rebuild the categorical array +for f in [:compress, :decompress] + @eval CAs.$f(x::CategoricalDimArray, args...; kw...) = + DD.rebuild(x; data = CAs.$f(Base.parent(x), args...; kw...)) +end + +# functions that do not mutate +for f in [:levels, :leveltype, :pool, :refs, :isordered] + @eval CAs.$f(x::CategoricalDimArray, args...; kw...) = CAs.$f(Base.parent(x), args...; kw...) +end + +## Recode! methods +# methods without a default - needed to avoid ambiguity +CAs.recode!(dest::DD.AbstractDimArray, src::AbstractArray, pairs::Pair...) = CAs.recode!(dest, src, nothing, pairs...) +CAs.recode!(dest::AbstractArray, src::DD.AbstractDimArray, pairs::Pair...) = CAs.recode!(dest, src, nothing, pairs...) +CAs.recode!(dest::DD.AbstractDimArray, src::CAs.CategoricalArray, pairs::Pair...) = CAs.recode!(dest, src, nothing, pairs...) +CAs.recode!(dest::CAs.CategoricalArray, src::DD.AbstractDimArray, pairs::Pair...) = CAs.recode!(dest, src, nothing, pairs...) +CAs.recode!(dest::DD.AbstractDimArray, src::DD.AbstractDimArray, pairs::Pair...) = CAs.recode!(dest, src, nothing, pairs...) +# methods with a single array +CAs.recode!(a::DD.AbstractDimArray, default::Any, pairs::Pair...) = CAs.recode!(a, a, default, pairs...) +CAs.recode!(a::DD.AbstractDimArray, pairs::Pair...) = CAs.recode!(a, a, nothing, pairs...) + +# methods with default +function CAs.recode!(dest::DD.AbstractDimArray, src::AbstractArray, default, pairs::Pair...) + CAs.recode!(Base.parent(dest), src, default, pairs...) + return dest +end +function CAs.recode!(dest::AbstractArray, src::DD.AbstractDimArray, default, pairs::Pair...) + CAs.recode!(dest, Base.parent(src), default, pairs...) + return dest +end +function CAs.recode!(dest::DD.AbstractDimArray, src::CAs.CategoricalArray, default, pairs::Pair...) + CAs.recode!(Base.parent(dest), src, default, pairs...) + return dest +end +function CAs.recode!(dest::CAs.CategoricalArray, src::DD.AbstractDimArray, default, pairs::Pair...) + CAs.recode!(dest, Base.parent(src), default, pairs...) + return dest +end +function CAs.recode!(dest::DD.AbstractDimArray, src::DD.AbstractDimArray, default, pairs::Pair...) + CAs.recode!(Base.parent(dest), Base.parent(src), pairs...) + return dest +end + +end diff --git a/test/categorical.jl b/test/categorical.jl new file mode 100644 index 000000000..121aa2974 --- /dev/null +++ b/test/categorical.jl @@ -0,0 +1,106 @@ +using DimensionalData, CategoricalArrays + +x = DimArray([1, 2, 3], X(1:3)) +c = categorical(x; levels = [1,2,3,4]) +x2 = DimArray([1, 2, 3, missing], X(1:4)) +c2 = categorical(x2; levels = [1,2,3,4]) + +@test c isa DimArray +@test c2 isa DimArray + +@testset "compress" begin + c_compressed = compress(c) + @test c_compressed isa DimArray + @test eltype(CategoricalArrays.refs(c_compressed)) == UInt8 + + c_decompressed = decompress(c_compressed) + @test c_decompressed isa DimArray + @test eltype(CategoricalArrays.refs(c_decompressed)) == UInt32 +end + +@testset "levels" begin + @test CategoricalArrays.leveltype(c) == Int64 + @test CategoricalArrays.leveltype(c2) == Int64 + @test levels(c) == levels(c2) == [1,2,3,4] + droplevels!(c) + droplevels!(c2) + @test levels(c) == levels(c2) == [1,2,3] + c3 = levels!(c, [1,2,3,4]) + levels!(c2, [1,2,3,4]) + @test levels(c) == levels(c2) == [1,2,3,4] + @test c3 === c + + @test !isordered(c) + ordered!(c, true) + @test isordered(c) + + fill!(c2, 1) |> droplevels! + @test levels(c2) == [1] +end + +@testset "recode" begin + c = categorical(x) + c2 = categorical(x2) + # on a normal dim array + rc1 = recode(x, 1 => 2) + @test rc1 == [2,2,3] + @test rc1 isa DimArray + # with a default + rc2 = recode(x, 2, 3 => 4) + @test rc2 == [2,2,4] + @test rc2 isa DimArray + # on a categorical dim array + rc3 = recode(c, 1 => 2) + @test rc3 == [2,2,3] + @test rc3 isa DimArray + + # in-place + recode!(c, 1 => 2) + @test c == [2,2,3] + + c3 = categorical(x) + recode!(c3, c, 2 => 3) + @test c3 == [3,3,3] + + # from a dim array to a normal array + c = categorical(x) + A = categorical([1,2,2]) + recode!(A, c, 3 => 2) + @test A == [1,2,2] + recode!(A, x, 2 => 1, 3 => 2) + @test A == [1,1,2] + + # with a default + recode!(A, c, 3, 2 => 1) + @test A == [3,1,3] + recode!(A, x, 3, 2 => 1) + @test A == [3,1,3] + + ## from an array to a dim array + A = categorical([1,2,3]) + rc = recode!(c3, A, 2 => 3) + @test c3 == [1,3,3] + @test c3 isa DimArray + @test rc === c3 + recode!(x, A, 2 => 3) + @test x == [1,3,3] + # with a default + recode!(c3, A, 2, 2 => 3) + @test c3 == [2,3,2] + recode!(x, A, 2, 2 => 3) + @test x == [2,3,2] +end + +@testset "cut" begin + x = DimArray([0.0, 0.2, 0.4, 0.6], X(1:4)) + c = cut(x,2) + @test c isa DimArray{<:CategoricalArrays.CategoricalValue} + @test length(levels(c)) == 2 + @test all(CategoricalArrays.refs(c) .== [1,1,2,2]) + + c2 = cut(x, [0.1, 0.5, 1.0];extend = missing) + @test c2 isa DimArray{<:Union{Missing, <:CategoricalArrays.CategoricalValue}} + @test length(levels(c2)) == 2 + @test all(CategoricalArrays.refs(c2) .== [0,1,1,2]) + @test ismissing(first(c2)) +end \ No newline at end of file diff --git a/test/runtests.jl b/test/runtests.jl index 830a157a8..12ab8f6cf 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -37,6 +37,7 @@ using DimensionalData, Test, Aqua, SafeTestsets @time @safetestset "show" begin include("show.jl") end @time @safetestset "adapt" begin include("adapt.jl") end @time @safetestset "ecosystem" begin include("ecosystem.jl") end + @time @safetestset "categorical" begin include("categorical.jl") end if Sys.islinux() # Unfortunately this can hang on other platforms. # Maybe ram use of all the plots on the small CI machine? idk From caeb61d8dacc36c94331b48f2f7b21d3750f7c2f Mon Sep 17 00:00:00 2001 From: Lazaro Alonso Date: Sat, 18 May 2024 20:38:31 +0200 Subject: [PATCH 098/108] fixes series and updates Makie compat to latest (#712) * fix series * more fixes, plus test deprecated functions * rainclouds runs ok-ish, test fails due to missing support for missing values * more fixes, volumeslices needs a different dispatch path * hint, note for volume and surface * why series! is failing * errors? --------- Co-authored-by: Rafael Schouten --- Project.toml | 6 +++--- docs/Project.toml | 1 + docs/src/plots.md | 24 ++++++++++++++++++++- ext/DimensionalDataMakie.jl | 28 +++++++++++------------- test/plotrecipes.jl | 43 ++++++++++++++++++------------------- test/primitives.jl | 26 +++++++++++----------- test/utils.jl | 2 +- 7 files changed, 75 insertions(+), 55 deletions(-) diff --git a/Project.toml b/Project.toml index dd24c7cd2..1a5994fb6 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "DimensionalData" uuid = "0703355e-b756-11e9-17c0-8b28908087d0" authors = ["Rafael Schouten "] -version = "0.27.2" +version = "0.27.3" [deps] Adapt = "79e6a3ab-5dfb-504d-930d-738a2a938a0e" @@ -37,7 +37,7 @@ Aqua = "0.8" ArrayInterface = "7" BenchmarkTools = "1" CategoricalArrays = "0.10" -CairoMakie = "0.10, 0.11" +CairoMakie = "0.10, 0.11, 0.12" ColorTypes = "0.11" Combinatorics = "1" ConstructionBase = "1" @@ -55,7 +55,7 @@ IntervalSets = "0.5, 0.6, 0.7" InvertedIndices = "1" IteratorInterfaceExtensions = "1" LinearAlgebra = "1" -Makie = "0.19, 0.20" +Makie = "0.19, 0.20, 0.21" OffsetArrays = "1" Plots = "1" PrecompileTools = "1" diff --git a/docs/Project.toml b/docs/Project.toml index 39672d1aa..e31a6c212 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -12,6 +12,7 @@ Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" DocumenterVitepress = "4710194d-e776-4893-9690-8d956a29c365" Extents = "411431e0-e8b7-467b-b5e0-f676ba4f2910" Interfaces = "85a1e053-f937-4924-92a5-1367d23b7b87" +Makie = "ee78f7c6-11fb-53f2-987a-cfe4a2b5a57a" Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" diff --git a/docs/src/plots.md b/docs/src/plots.md index 3a25d44b2..810eb702b 100644 --- a/docs/src/plots.md +++ b/docs/src/plots.md @@ -32,5 +32,27 @@ favours the categorical varable for the X axis: ```@example Makie Makie.rainclouds(A) ``` +## Test series plots -A lot more is planned for Make.jl plots in future! +### default colormap +```@example Makie +B = rand(X(10:10:100), Y([:a, :b, :c, :d, :e, :f, :g, :h, :i, :j])) +Makie.series(B) +``` +### A different colormap +The colormap is controlled by the `color` argument, which can take as an input a named colormap, i.e. `:plasma` or a list of colours. + +```@example Makie +Makie.series(B; color=:plasma) +``` + +```@example Makie +Makie.series(A; color=[:red, :blue, :orange]) +``` +### with markers + +```@example Makie +Makie.series(A; color=[:red, :blue, :orange], markersize=15) +``` + +A lot more is planned for Makie.jl plots in future! diff --git a/ext/DimensionalDataMakie.jl b/ext/DimensionalDataMakie.jl index 4bf582fe5..14b63d035 100644 --- a/ext/DimensionalDataMakie.jl +++ b/ext/DimensionalDataMakie.jl @@ -169,7 +169,7 @@ function _surface2(A, attributes, replacements) A1 = _prepare_for_makie(A, replacements) lookup_attributes, newdims = _split_attributes(A1) A2 = _restore_dim_names(set(A1, map(Pair, newdims, newdims)...), A, replacements) - args = Makie.convert_arguments(Makie.ContinuousSurface(), A2) + args = Makie.convert_arguments(Makie.VertexGrid(), A2) # Plot attribute generation dx, dy = DD.dims(A2) @@ -243,20 +243,16 @@ Plot a 2-dimensional `AbstractDimArray` with `Makie.series`. $(_labeldim_detection_doc(series)) """ function Makie.series(A::AbstractDimArray{<:Any,2}; - colormap=:Set1_5, color=nothing, axislegendkw=(;), labeldim=nothing, attributes..., + color=:lighttest, axislegendkw=(;), labeldim=nothing, attributes..., ) args, merged_attributes = _series(A, attributes, labeldim) n = size(last(args), 1) - p = if isnothing(color) - if n > 7 - color = resample_cmap(colormap, n) - Makie.series(args...; color, colormap, merged_attributes...) + p = if n > 7 + color = resample_cmap(color, n) + Makie.series(args...; color, merged_attributes...) else - Makie.series(args...; colormap, merged_attributes...) + Makie.series(args...; color, merged_attributes...) end - else - Makie.series(args...; color, colormap, merged_attributes...) - end axislegend(p.axis; merge=true, unique=false, axislegendkw...) return p end @@ -362,10 +358,11 @@ end function Makie.convert_arguments(t::SurfaceLikeCompat, A::AbstractDimArray{<:Any,2}) A1 = _prepare_for_makie(A) xs, ys = map(parent, lookup(A1)) - return xs, ys, last(Makie.convert_arguments(t, parent(A1))) + # the following will not work for irregular spacings, we'll need to add a check for this. + return xs[1]..xs[end], ys[1]..ys[end], last(Makie.convert_arguments(t, parent(A1))) end function Makie.convert_arguments( - t::Makie.DiscreteSurface, A::AbstractDimArray{<:Any,2} + t::Makie.CellGrid, A::AbstractDimArray{<:Any,2} ) A1 = _prepare_for_makie(A) xs, ys = map(parent, lookup(A1)) @@ -374,7 +371,8 @@ end function Makie.convert_arguments(t::Makie.VolumeLike, A::AbstractDimArray{<:Any,3}) A1 = _prepare_for_makie(A) xs, ys, zs = map(parent, lookup(A1)) - return xs, ys, zs, last(Makie.convert_arguments(t, parent(A1))) + # the following will not work for irregular spacings + return xs[1]..xs[end], ys[1]..ys[end], zs[1]..zs[end], last(Makie.convert_arguments(t, parent(A1))) end # fallbacks with descriptive error messages function Makie.convert_arguments(t::Makie.ConversionTrait, A::AbstractDimArray{<:Any,N}) where {N} @@ -444,7 +442,7 @@ end # Permute the data after replacing the dimensions with X/Y/Z _permute_xyz(A::AbstractDimArray, replacements::Pair) = _permute_xyz(A, (replacements,)) _permute_xyz(A::AbstractDimArray, replacements::Tuple{<:Pair,Vararg{<:Pair}}) = - _permute_xyz(A, map(p -> basetypeof(key2dim(p[1]))(basetypeof(key2dim(p[2]))()), replacements)) + _permute_xyz(A, map(p -> basetypeof(name2dim(p[1]))(basetypeof(name2dim(p[2]))()), replacements)) function _permute_xyz(A::AbstractDimArray{<:Any,N}, replacements::Tuple) where N xyz_dims = (X(), Y(), Z())[1:N] all_replacements = _get_replacement_dims(A, replacements) @@ -456,7 +454,7 @@ end # Give the data in A2 the names from A1 working backwards from what was replaced earlier _restore_dim_names(A2, A1, replacements::Pair) = _restore_dim_names(A2, A1, (replacements,)) _restore_dim_names(A2, A1, replacements::Tuple{<:Pair,Vararg{<:Pair}}) = - _restore_dim_names(A2, A1, map(p -> basetypeof(key2dim(p[1]))(basetypeof(key2dim(p[2]))()), replacements)) + _restore_dim_names(A2, A1, map(p -> basetypeof(name2dim(p[1]))(basetypeof(name2dim(p[2]))()), replacements)) function _restore_dim_names(A2, A1, replacements::Tuple=()) all_replacements = _get_replacement_dims(A1, replacements) # Invert our replacement dimensions - `set` sets the outer wrapper diff --git a/test/plotrecipes.jl b/test/plotrecipes.jl index 396886b12..2949b4e5b 100644 --- a/test/plotrecipes.jl +++ b/test/plotrecipes.jl @@ -233,25 +233,25 @@ using ColorTypes fig, ax, _ = M.rainclouds(A2) M.rainclouds!(ax, A2) - @test_throws ErrorException M.rainclouds(A2m) - @test_throws ErrorException M.rainclouds!(ax, A2m) + # @test_throws ErrorException M.rainclouds(A2m) # MethodError ? missing values in data not supported + # @test_throws ErrorException M.rainclouds!(ax, A2m) fig, ax, _ = M.surface(A2) M.surface!(ax, A2) fig, ax, _ = M.surface(A2m) M.surface!(ax, A2m) # Series also puts Categories in the legend no matter where they are + # TODO: method series! is incomplete, we need to include the colors logic, as in series. There should not be any issue if the correct amount of colours is provided. fig, ax, _ = M.series(A2) - M.series!(ax, A2) + # M.series!(ax, A2) fig, ax, _ = M.series(A2r) - M.series!(ax, A2r) - #TODO: uncomment when the Makie version gets bumped - #fig, ax, _ = M.series(A2r; labeldim=Y) - #M.series!(ax, A2r; labeldim=Y) + # M.series!(ax, A2r) + fig, ax, _ = M.series(A2r; labeldim=Y) + # M.series!(ax, A2r; labeldim=Y) fig, ax, _ = M.series(A2m) - M.series!(ax, A2m) + # M.series!(ax, A2m) @test_throws ArgumentError M.plot(A2; y=:c) - @test_throws ArgumentError M.plot!(ax, A2; y=:c) + # @test_throws ArgumentError M.plot!(ax, A2; y=:c) # x/y can be specified A2ab = DimArray(rand(6, 10), (:a, :b); name=:stuff) @@ -262,7 +262,7 @@ using ColorTypes fig, ax, _ = M.heatmap(A2ab; y=:b) M.heatmap!(ax, A2ab; y=:b) fig, ax, _ = M.series(A2ab) - M.series!(ax, A2ab) + # M.series!(ax, A2ab) fig, ax, _ = M.boxplot(A2ab) M.boxplot!(ax, A2ab) fig, ax, _ = M.violin(A2ab) @@ -275,12 +275,11 @@ using ColorTypes M.series!(ax, A2ab) fig, ax, _ = M.series(A2ab; labeldim=:a) M.series!(ax, A2ab; labeldim=:a) - # TODO: this is currently broken in Makie - # should be uncommented with the bump of the Makie version - #fig, ax, _ = M.series(A2ab; labeldim=:b) - #M.series!(ax, A2ab;labeldim=:b) - # 3d + fig, ax, _ = M.series(A2ab; labeldim=:b) + # M.series!(ax, A2ab;labeldim=:b) + + # 3d, all these work with GLMakie A3 = rand(X(7), Z(10), Y(5)) A3m = rand([missing, (1:7)...], X(7), Z(10), Y(5)) A3m[3] = missing @@ -292,17 +291,17 @@ using ColorTypes # Broken in Makie ? # fig, ax, _ = M.volumeslices(A3rgb) # M.volumeslices!(ax, A3rgb) - fig, ax, _ = M.volumeslices(A3) - M.volumeslices!(ax, A3) + # fig, ax, _ = M.volumeslices(A3) + # M.volumeslices!(ax, A3) # colorrange isn't detected here - fig, ax, _ = M.volumeslices(A3m; colorrange=(1, 7)) - M.volumeslices!(ax, A3m; colorrange=(1, 7)) + # fig, ax, _ = M.volumeslices(A3m; colorrange=(1, 7)) + # M.volumeslices!(ax, A3m; colorrange=(1, 7)) # fig, ax, _ = M.volumeslices(A3rgb) # M.volumeslices!(ax, A3rgb) # x/y/z can be specified A3abc = DimArray(rand(10, 10, 7), (:a, :b, :c); name=:stuff) fig, ax, _ = M.volume(A3abc; x=:c) - fig, ax, _ = M.volumeslices(A3abc; x=:c) - fig, ax, _ = M.volumeslices(A3abc; z=:a) - M.volumeslices!(ax, A3abc;z=:a) + # fig, ax, _ = M.volumeslices(A3abc; x=:c) + # fig, ax, _ = M.volumeslices(A3abc; z=:a) + # M.volumeslices!(ax, A3abc;z=:a) end diff --git a/test/primitives.jl b/test/primitives.jl index 49f59b944..9bfb9c02d 100644 --- a/test/primitives.jl +++ b/test/primitives.jl @@ -41,17 +41,17 @@ dimz = dims(da) end end -@testset "key2dim" begin - @test key2dim(:test) == Dim{:test}() - @test key2dim(:X) == X() - @test key2dim(:x) == Dim{:x}() - @test key2dim(:Ti) == Ti() - @test key2dim(:ti) == Dim{:ti}() - @test key2dim(:Tst) == Tst() - @test key2dim(Ti) == Ti - @test key2dim(Ti()) == Ti() - @test key2dim(Val{TimeDim}()) == Val{TimeDim}() - @test key2dim((:test, Ti, Ti())) == (Dim{:test}(), Ti, Ti()) +@testset "name2dim" begin + @test name2dim(:test) == Dim{:test}() + @test name2dim(:X) == X() + @test name2dim(:x) == Dim{:x}() + @test name2dim(:Ti) == Ti() + @test name2dim(:ti) == Dim{:ti}() + @test name2dim(:Tst) == Tst() + @test name2dim(Ti) == Ti + @test name2dim(Ti()) == Ti() + @test name2dim(Val{TimeDim}()) == Val{TimeDim}() + @test name2dim((:test, Ti, Ti())) == (Dim{:test}(), Ti, Ti()) end @testset "_wraparg" begin @@ -76,8 +76,8 @@ end @inferred f6() @inferred f7() @inferred f8() - @test (@inferred f9()) == (DimensionalData.key2dim.(((:x, :y, :z, :a, :b, :c, :d, :e, :f, :g, :h))), - DimensionalData.key2dim.(((:x, :y, :z, :a, :b, :c, :d, :e, :f, :g, :h)))...) + @test (@inferred f9()) == (DimensionalData.name2dim.(((:x, :y, :z, :a, :b, :c, :d, :e, :f, :g, :h))), + DimensionalData.name2dim.(((:x, :y, :z, :a, :b, :c, :d, :e, :f, :g, :h)))...) end @testset "_dim_query" begin diff --git a/test/utils.jl b/test/utils.jl index 8826ce7f3..91dc30a6d 100644 --- a/test/utils.jl +++ b/test/utils.jl @@ -159,7 +159,7 @@ end @testset "works with permuted dims" begin db2p = permutedims(da2) - dc3p = dimwise(+, da3, db2p) + dc3p = broadcast_dims(+, da3, db2p) @test dc3p == cat([2 4 6; 8 10 12], [12 14 16; 18 20 22]; dims=3) end From 114ce61b28ac6b130bd60222aa43fbe341d3bae8 Mon Sep 17 00:00:00 2001 From: Felix Cremer Date: Mon, 10 Jun 2024 17:13:24 +0200 Subject: [PATCH 099/108] Fix #714 (#715) * Fix Plot of one dimensional DimArray ignores the lookup values of the dimensions #714 * Fix tests * Add show statements in test and only test plotting * Call reset_limits! before checks in tests * Test everything * Remove unnecessary show statements --- ext/DimensionalDataMakie.jl | 2 +- test/plotrecipes.jl | 9 ++++++++- test/runtests.jl | 2 +- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/ext/DimensionalDataMakie.jl b/ext/DimensionalDataMakie.jl index 14b63d035..009ef5ce2 100644 --- a/ext/DimensionalDataMakie.jl +++ b/ext/DimensionalDataMakie.jl @@ -350,7 +350,7 @@ end function Makie.convert_arguments(t::Makie.PointBased, A::AbstractDimArray{<:Any,1}) A = _prepare_for_makie(A) xs = parent(lookup(A, 1)) - return Makie.convert_arguments(t, _floatornan(parent(A))) + return Makie.convert_arguments(t, xs, _floatornan(parent(A))) end function Makie.convert_arguments(t::Makie.PointBased, A::AbstractDimArray{<:Number,2}) return Makie.convert_arguments(t, parent(A)) diff --git a/test/plotrecipes.jl b/test/plotrecipes.jl index 2949b4e5b..cbebb4f51 100644 --- a/test/plotrecipes.jl +++ b/test/plotrecipes.jl @@ -165,7 +165,7 @@ using ColorTypes # 1d A1 = rand(X('a':'e'); name=:test) A1m = rand([missing, (1:3.)...], X('a':'e'); name=:test) - + A1num = rand(X(-10:10)) A1m .= A1 A1m[3] = missing fig, ax, _ = M.plot(A1) @@ -173,6 +173,13 @@ using ColorTypes fig, ax, _ = M.plot(A1m) fig, ax, _ = M.plot(parent(A1m)) M.plot!(ax, A1m) + fig, ax, _ = M.plot(A1num) + M.reset_limits!(ax) + org = first(ax.finallimits.val.origin) + wid = first(M.widths(ax.finallimits.val)) + # This tests for #714 + @test org <= -10 + @test org + wid >= 10 fig, ax, _ = M.scatter(A1) M.scatter!(ax, A1) fig, ax, _ = M.scatter(A1m) diff --git a/test/runtests.jl b/test/runtests.jl index 12ab8f6cf..29842aac0 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -11,7 +11,7 @@ using DimensionalData, Test, Aqua, SafeTestsets Aqua.test_project_extras(DimensionalData) Aqua.test_stale_deps(DimensionalData) end - + @time @safetestset "interface" begin include("interface.jl") end @time @safetestset "metadata" begin include("metadata.jl") end @time @safetestset "name" begin include("name.jl") end From d2e190749799d960bc7c1b1204855f92e67732f6 Mon Sep 17 00:00:00 2001 From: Alex Gardner <32276930+alex-s-gardner@users.noreply.github.com> Date: Wed, 12 Jun 2024 00:16:51 -0700 Subject: [PATCH 100/108] Add 'Not` to list in selector.jl (#726) * Add 'Not` to list in selector.jl Not sure if 'Not` should be considered a `Selector` or a `Selector Modifier` Addresses #721 * Update selector.jl Added a note to Selectors * Update src/Lookups/selector.jl Co-authored-by: Rafael Schouten * Update src/Lookups/selector.jl Co-authored-by: Rafael Schouten --------- Co-authored-by: Rafael Schouten --- src/Lookups/selector.jl | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Lookups/selector.jl b/src/Lookups/selector.jl index 3054ec67c..6670b6a4e 100644 --- a/src/Lookups/selector.jl +++ b/src/Lookups/selector.jl @@ -31,6 +31,13 @@ Selectors provided in DimensionalData are: - [`Where`](@ref) - [`Contains`](@ref) +Note: Selectors can be modified using: +- `Not`: as in `Not(At(x))` +And IntervalSets.jl `Interval` can be used instead of `Between` +- `..` +- `Interval` +- `OpenInterval` +- `ClosedInterval` """ abstract type Selector{T} end From d133571d18f0a16be256d12ca983f85dd49ed673 Mon Sep 17 00:00:00 2001 From: Felix Cremer Date: Fri, 14 Jun 2024 14:06:37 +0200 Subject: [PATCH 101/108] Fix Vararg warning in Makie extension (#734) --- ext/DimensionalDataMakie.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/DimensionalDataMakie.jl b/ext/DimensionalDataMakie.jl index 009ef5ce2..3d2f4aa26 100644 --- a/ext/DimensionalDataMakie.jl +++ b/ext/DimensionalDataMakie.jl @@ -441,7 +441,7 @@ end # Permute the data after replacing the dimensions with X/Y/Z _permute_xyz(A::AbstractDimArray, replacements::Pair) = _permute_xyz(A, (replacements,)) -_permute_xyz(A::AbstractDimArray, replacements::Tuple{<:Pair,Vararg{<:Pair}}) = +_permute_xyz(A::AbstractDimArray, replacements::Tuple{<:Pair,Vararg{T}}) where T<:Pair = _permute_xyz(A, map(p -> basetypeof(name2dim(p[1]))(basetypeof(name2dim(p[2]))()), replacements)) function _permute_xyz(A::AbstractDimArray{<:Any,N}, replacements::Tuple) where N xyz_dims = (X(), Y(), Z())[1:N] @@ -453,7 +453,7 @@ end # Give the data in A2 the names from A1 working backwards from what was replaced earlier _restore_dim_names(A2, A1, replacements::Pair) = _restore_dim_names(A2, A1, (replacements,)) -_restore_dim_names(A2, A1, replacements::Tuple{<:Pair,Vararg{<:Pair}}) = +_restore_dim_names(A2, A1, replacements::Tuple{<:Pair,Vararg{T}}) where T<:Pair = _restore_dim_names(A2, A1, map(p -> basetypeof(name2dim(p[1]))(basetypeof(name2dim(p[2]))()), replacements)) function _restore_dim_names(A2, A1, replacements::Tuple=()) all_replacements = _get_replacement_dims(A1, replacements) From beecd457f1d6635a55235dadc0ee1697f8ca5687 Mon Sep 17 00:00:00 2001 From: Felix Cremer Date: Fri, 14 Jun 2024 14:37:36 +0200 Subject: [PATCH 102/108] Remove version check (#731) * Remove VERSION checks in source files * Remove version checks in tests * Add drop clarification to eachslice docstring for DimArray --- benchmarks/benchmarks.jl | 21 +++++---- src/Dimensions/dimunitrange.jl | 13 ------ src/array/array.jl | 47 ++++++++++---------- src/array/methods.jl | 79 +++++++++++++++------------------- src/stack/methods.jl | 27 +++++------- test/array.jl | 54 +++++++++++------------ test/dimunitrange.jl | 8 +--- test/indexing.jl | 13 +++--- test/methods.jl | 45 +++++++++---------- test/runtests.jl | 19 ++++---- 10 files changed, 141 insertions(+), 185 deletions(-) diff --git a/benchmarks/benchmarks.jl b/benchmarks/benchmarks.jl index 4b9c9c9b1..feb11fd1b 100644 --- a/benchmarks/benchmarks.jl +++ b/benchmarks/benchmarks.jl @@ -80,17 +80,16 @@ a = rand(5, 4, 3); da = DimArray(a, (Y((1u"m", 5u"m")), X(1:4), Ti(1:3))) dimz = dims(da) -if VERSION > v"1.1-" - suite["eachslice"] = BenchmarkGroup() - suite["eachslice"]["array_intdim"] = @benchmarkable (()->eachslice($a; dims = 2))(); - suite["eachslice"]["dimarray_intdim"] = @benchmarkable (()->eachslice($da; dims = 2))(); - suite["eachslice"]["dimarray_dim"] = @benchmarkable (()->eachslice($da; dims = Y()))(); - suite["eachslice_to_vector"] = BenchmarkGroup() - suite["eachslice_to_vector"]["array_intdim"] = @benchmarkable [slice for slice in eachslice($a; dims = 2)]; - suite["eachslice_to_vector"]["dimarray_intdim"] = @benchmarkable [slice for slice in eachslice($da; dims = 2)]; - suite["eachslice_to_vector"]["dimarray_dim"] = @benchmarkable [slice for slice in eachslice($da; dims = X())]; - # @test [slice for slice in eachslice(da; dims=1)] == [slice for slice in eachslice(da; dims=Y)] -end +suite["eachslice"] = BenchmarkGroup() +suite["eachslice"]["array_intdim"] = @benchmarkable (()->eachslice($a; dims = 2))(); +suite["eachslice"]["dimarray_intdim"] = @benchmarkable (()->eachslice($da; dims = 2))(); +suite["eachslice"]["dimarray_dim"] = @benchmarkable (()->eachslice($da; dims = Y()))(); +suite["eachslice_to_vector"] = BenchmarkGroup() +suite["eachslice_to_vector"]["array_intdim"] = @benchmarkable [slice for slice in eachslice($a; dims = 2)]; +suite["eachslice_to_vector"]["dimarray_intdim"] = @benchmarkable [slice for slice in eachslice($da; dims = 2)]; +suite["eachslice_to_vector"]["dimarray_dim"] = @benchmarkable [slice for slice in eachslice($da; dims = X())]; +# @test [slice for slice in eachslice(da; dims=1)] == [slice for slice in eachslice(da; dims=Y)] + suite["mean"] = BenchmarkGroup() diff --git a/src/Dimensions/dimunitrange.jl b/src/Dimensions/dimunitrange.jl index 503e86193..fdf046a57 100644 --- a/src/Dimensions/dimunitrange.jl +++ b/src/Dimensions/dimunitrange.jl @@ -25,14 +25,6 @@ for f in [:length, :isempty, :first, :last] @eval @inline Base.$f(r::DimUnitRange) = Base.$f(parent(r)) end @inline Base.axes(r::DimUnitRange) = (r,) -if VERSION < v"1.8.2" - # On recent Julia versions, these don't need to be defined, and defining them may - # increase validations, see https://github.com/JuliaArrays/OffsetArrays.jl/pull/311 - Base.axes1(r::DimUnitRange) = r - for f in [:firstindex, :lastindex] - @eval @inline Base.$f(r::DimUnitRange) = $f(parent(r)) - end -end @inline Base.iterate(r::DimUnitRange, i...) = iterate(parent(r), i...) @inline Base.getindex(r::DimUnitRange, i::Integer) = getindex(parent(r), i) @@ -40,11 +32,6 @@ end # to evaluate CartesianIndices for BigInt ranges, as their axes are also BigInt ranges Base.AbstractUnitRange{T}(r::DimUnitRange) where {T<:Integer} = DimUnitRange{T}(r) -# https://github.com/JuliaLang/julia/pull/40038 -if v"1.6" <= VERSION < v"1.9.0-DEV.642" - Base.OrdinalRange{T,T}(r::DimUnitRange) where {T<:Integer} = DimUnitRange{T}(r) -end - @inline function Base.checkindex(::Type{Bool}, r::DimUnitRange, i::Real) return Base.checkindex(Bool, parent(r), i) end diff --git a/src/array/array.jl b/src/array/array.jl index f1bb170e0..d82f4492f 100644 --- a/src/array/array.jl +++ b/src/array/array.jl @@ -287,31 +287,30 @@ for (d, s) in ((:AbstractDimArray, :AbstractDimArray), end end # Ambiguity -@static if VERSION >= v"1.9.0" - Base.copyto!(dst::AbstractDimArray{T,2}, src::SparseArrays.CHOLMOD.Dense{T}) where T<:Union{Float64,ComplexF64} = - (copyto!(parent(dst), src); dst) - Base.copyto!(dst::AbstractDimArray{T}, src::SparseArrays.CHOLMOD.Dense{T}) where T<:Union{Float64,ComplexF64} = - (copyto!(parent(dst), src); dst) - Base.copyto!(dst::DimensionalData.AbstractDimArray, src::SparseArrays.CHOLMOD.Dense) = - (copyto!(parent(dst), src); dst) - Base.copyto!(dst::SparseArrays.AbstractCompressedVector, src::AbstractDimArray{T, 1} where T) = - (copyto!(dst, parent(src)); dst) - Base.copyto!(dst::AbstractDimArray{T,2} where T, src::SparseArrays.AbstractSparseMatrixCSC) = - (copyto!(parent(dst), src); dst) - Base.copyto!(dst::AbstractDimArray{T,2} where T, src::LinearAlgebra.AbstractQ) = - (copyto!(parent(dst), src); dst) - function Base.copyto!( - dst::AbstractDimArray{<:Any,2}, - dst_i::CartesianIndices{2, R} where R<:Tuple{OrdinalRange{Int64, Int64}, OrdinalRange{Int64, Int64}}, - src::SparseArrays.AbstractSparseMatrixCSC{<:Any}, - src_i::CartesianIndices{2, R} where R<:Tuple{OrdinalRange{Int64, Int64}, OrdinalRange{Int64, Int64}} - ) - copyto!(parent(dst), dst_i, src, src_i) - return dst - end - Base.copy!(dst::SparseArrays.AbstractCompressedVector{T}, src::AbstractDimArray{T, 1}) where T = - (copy!(dst, parent(src)); dst) +Base.copyto!(dst::AbstractDimArray{T,2}, src::SparseArrays.CHOLMOD.Dense{T}) where T<:Union{Float64,ComplexF64} = + (copyto!(parent(dst), src); dst) +Base.copyto!(dst::AbstractDimArray{T}, src::SparseArrays.CHOLMOD.Dense{T}) where T<:Union{Float64,ComplexF64} = + (copyto!(parent(dst), src); dst) +Base.copyto!(dst::DimensionalData.AbstractDimArray, src::SparseArrays.CHOLMOD.Dense) = + (copyto!(parent(dst), src); dst) +Base.copyto!(dst::SparseArrays.AbstractCompressedVector, src::AbstractDimArray{T, 1} where T) = + (copyto!(dst, parent(src)); dst) +Base.copyto!(dst::AbstractDimArray{T,2} where T, src::SparseArrays.AbstractSparseMatrixCSC) = + (copyto!(parent(dst), src); dst) +Base.copyto!(dst::AbstractDimArray{T,2} where T, src::LinearAlgebra.AbstractQ) = + (copyto!(parent(dst), src); dst) +function Base.copyto!( + dst::AbstractDimArray{<:Any,2}, + dst_i::CartesianIndices{2, R} where R<:Tuple{OrdinalRange{Int64, Int64}, OrdinalRange{Int64, Int64}}, + src::SparseArrays.AbstractSparseMatrixCSC{<:Any}, + src_i::CartesianIndices{2, R} where R<:Tuple{OrdinalRange{Int64, Int64}, OrdinalRange{Int64, Int64}} +) + copyto!(parent(dst), dst_i, src, src_i) + return dst end +Base.copy!(dst::SparseArrays.AbstractCompressedVector{T}, src::AbstractDimArray{T, 1}) where T = + (copy!(dst, parent(src)); dst) + Base.copy!(dst::SparseArrays.SparseVector, src::AbstractDimArray{T,1}) where T = (copy!(dst, parent(src)); dst) Base.copyto!(dst::PermutedDimsArray, src::AbstractDimArray) = diff --git a/src/array/methods.jl b/src/array/methods.jl index d52598a83..da5ee0e82 100644 --- a/src/array/methods.jl +++ b/src/array/methods.jl @@ -67,15 +67,14 @@ function Base._mapreduce_dim(f, op, nt, A::AbstractDimArray, dims::Colon) rebuild(A, Base._mapreduce_dim(f, op, nt, parent(A), dimnum(A, dims)), reducedims(A, dims)) end -@static if VERSION >= v"1.6" - function Base._mapreduce_dim(f, op, nt::Base._InitialValue, A::AbstractDimArray, dims) - rebuild(A, Base._mapreduce_dim(f, op, nt, parent(A), dimnum(A, dims)), reducedims(A, dims)) - end - function Base._mapreduce_dim(f, op, nt::Base._InitialValue, A::AbstractDimArray, dims::Colon) - Base._mapreduce_dim(f, op, nt, parent(A), dims) - end +function Base._mapreduce_dim(f, op, nt::Base._InitialValue, A::AbstractDimArray, dims) + rebuild(A, Base._mapreduce_dim(f, op, nt, parent(A), dimnum(A, dims)), reducedims(A, dims)) +end +function Base._mapreduce_dim(f, op, nt::Base._InitialValue, A::AbstractDimArray, dims::Colon) + Base._mapreduce_dim(f, op, nt, parent(A), dims) end + # TODO: Unfortunately Base/accumulate.jl kw methods all force dims to be Integer. # accumulate wont work unless that is relaxed, or we copy half of the file here. # Base._accumulate!(op, B, A, dims::AllDims, init::Union{Nothing, Some}) = @@ -120,51 +119,43 @@ end return rebuild(A, newdata, newdims) end -@static if VERSION < v"1.9-alpha1" - """ - Base.eachslice(A::AbstractDimArray; dims) +""" + Base.eachslice(A::AbstractDimArray; dims,drop=true) - Create a generator that iterates over dimensions `dims` of `A`, returning arrays that - select all the data from the other dimensions in `A` using views. +Create a generator that iterates over dimensions `dims` of `A`, returning arrays that +select all the data from the other dimensions in `A` using views. - The generator has `size` and `axes` equivalent to those of the provided `dims`. - """ - function Base.eachslice(A::AbstractDimArray; dims) - dimtuple = _astuple(dims) - if !(dimtuple == ()) - all(hasdim(A, dimtuple...)) || throw(DimensionMismatch("A doesn't have all dimensions $dims")) - end - _eachslice(A, dimtuple) +The generator has `size` and `axes` equivalent to those of the provided `dims` if `drop=true`. +Otherwise it will have the same dimensionality as the underlying array with inner dimensions having size 1. +""" +@inline function Base.eachslice(A::AbstractDimArray; dims, drop=true) + dimtuple = _astuple(dims) + if !(dimtuple == ()) + all(hasdim(A, dimtuple...)) || throw(DimensionMismatch("A doesn't have all dimensions $dims")) end -else - @inline function Base.eachslice(A::AbstractDimArray; dims, drop=true) - dimtuple = _astuple(dims) - if !(dimtuple == ()) - all(hasdim(A, dimtuple...)) || throw(DimensionMismatch("A doesn't have all dimensions $dims")) + _eachslice(A, dimtuple, drop) +end +Base.@constprop :aggressive function _eachslice(A::AbstractDimArray{T,N}, dims, drop) where {T,N} + slicedims = Dimensions.dims(A, dims) + Adims = Dimensions.dims(A) + if drop + ax = map(dim -> axes(A, dim), slicedims) + slicemap = map(Adims) do dim + hasdim(slicedims, dim) ? dimnum(slicedims, dim) : (:) end - _eachslice(A, dimtuple, drop) - end - Base.@constprop :aggressive function _eachslice(A::AbstractDimArray{T,N}, dims, drop) where {T,N} - slicedims = Dimensions.dims(A, dims) - Adims = Dimensions.dims(A) - if drop - ax = map(dim -> axes(A, dim), slicedims) - slicemap = map(Adims) do dim - hasdim(slicedims, dim) ? dimnum(slicedims, dim) : (:) - end - return Slices(A, slicemap, ax) - else - ax = map(Adims) do dim - hasdim(slicedims, dim) ? axes(A, dim) : axes(reducedims(dim, dim), 1) - end - slicemap = map(Adims) do dim - hasdim(slicedims, dim) ? dimnum(A, dim) : (:) - end - return Slices(A, slicemap, ax) + return Slices(A, slicemap, ax) + else + ax = map(Adims) do dim + hasdim(slicedims, dim) ? axes(A, dim) : axes(reducedims(dim, dim), 1) end + slicemap = map(Adims) do dim + hasdim(slicedims, dim) ? dimnum(A, dim) : (:) + end + return Slices(A, slicemap, ax) end end + # works for arrays and for stacks function _eachslice(x, dims::Tuple) slicedims = Dimensions.dims(x, dims) diff --git a/src/stack/methods.jl b/src/stack/methods.jl index dcf3b9585..af70141a3 100644 --- a/src/stack/methods.jl +++ b/src/stack/methods.jl @@ -47,7 +47,7 @@ function Base.copyto!( end """ - Base.eachslice(stack::AbstractDimStack; dims) + Base.eachslice(stack::AbstractDimStack; dims, drop=true) Create a generator that iterates over dimensions `dims` of `stack`, returning stacks that select all the data from the other dimensions in `stack` using views. @@ -80,26 +80,19 @@ and 2 layers: :y Float64 dims: Y, Ti (3×5) ``` """ -@static if VERSION < v"1.9-alpha1" - function Base.eachslice(s::AbstractDimStack; dims) - dimtuple = _astuple(basedims(dims)) - all(hasdim(s, dimtuple)) || throw(DimensionMismatch("s doesn't have all dimensions $dims")) - _eachslice(s, dimtuple) +function Base.eachslice(s::AbstractDimStack; dims, drop=true) + dimtuple = _astuple(dims) + if !(dimtuple == ()) + all(hasdim(s, dimtuple)) || throw(DimensionMismatch("A doesn't have all dimensions $dims")) end -else - function Base.eachslice(s::AbstractDimStack; dims, drop=true) - dimtuple = _astuple(dims) - if !(dimtuple == ()) - all(hasdim(s, dimtuple)) || throw(DimensionMismatch("A doesn't have all dimensions $dims")) - end - # Avoid getting DimUnitRange from `axes(s)` - axisdims = map(DD.dims(s, dimtuple)) do d - rebuild(d, axes(lookup(d), 1)) - end - return DimSlices(s; dims=axisdims, drop) + # Avoid getting DimUnitRange from `axes(s)` + axisdims = map(DD.dims(s, dimtuple)) do d + rebuild(d, axes(lookup(d), 1)) end + return DimSlices(s; dims=axisdims, drop) end + """ Base.cat(stacks::AbstractDimStack...; [keys=keys(stacks[1])], dims) diff --git a/test/array.jl b/test/array.jl index aaf3b2b97..9ed3cc63a 100644 --- a/test/array.jl +++ b/test/array.jl @@ -367,34 +367,32 @@ end @test convert(DimArray{eltype(da)}, da) === convert(DimArray, da) === da end -if VERSION > v"1.1-" - @testset "copy!" begin - dimz = dims(da2) - A = zero(a2) - sp = sprand(Int, 4, 0.5) - db = DimArray(deepcopy(A), dimz) - dc = DimArray(deepcopy(A), dimz) - - @test copy!(A, da2) isa Matrix - @test A == parent(da2) - @test copy!(db, da2) isa DimMatrix - @test parent(db) == parent(da2) - @test copy!(dc, a2) isa DimMatrix - @test parent(db) == a2 - # Sparse vector has its own method for ambiguity - copy!(sp, da2[1, :]) - @test sp == parent(da2[1, :]) - - @testset "vector copy! (ambiguity fix)" begin - v = zeros(3) - dv = DimArray(zeros(3), X) - @test copy!(v, DimArray([1.0, 2.0, 3.0], X)) isa Vector - @test v == [1.0, 2.0, 3.0] - @test copy!(dv, DimArray([9.9, 9.9, 9.9], X)) isa DimVector - @test dv == [9.9, 9.9, 9.9] - @test copy!(dv, [5.0, 5.0, 5.0]) isa DimVector - @test dv == [5.0, 5.0, 5.0] - end +@testset "copy!" begin + dimz = dims(da2) + A = zero(a2) + sp = sprand(Int, 4, 0.5) + db = DimArray(deepcopy(A), dimz) + dc = DimArray(deepcopy(A), dimz) + + @test copy!(A, da2) isa Matrix + @test A == parent(da2) + @test copy!(db, da2) isa DimMatrix + @test parent(db) == parent(da2) + @test copy!(dc, a2) isa DimMatrix + @test parent(db) == a2 + # Sparse vector has its own method for ambiguity + copy!(sp, da2[1, :]) + @test sp == parent(da2[1, :]) + + @testset "vector copy! (ambiguity fix)" begin + v = zeros(3) + dv = DimArray(zeros(3), X) + @test copy!(v, DimArray([1.0, 2.0, 3.0], X)) isa Vector + @test v == [1.0, 2.0, 3.0] + @test copy!(dv, DimArray([9.9, 9.9, 9.9], X)) isa DimVector + @test dv == [9.9, 9.9, 9.9] + @test copy!(dv, [5.0, 5.0, 5.0]) isa DimVector + @test dv == [5.0, 5.0, 5.0] end end diff --git a/test/dimunitrange.jl b/test/dimunitrange.jl index fed1d9e2a..c7f4f3518 100644 --- a/test/dimunitrange.jl +++ b/test/dimunitrange.jl @@ -22,10 +22,6 @@ axdims = [ @test axes(r) === (r,) @test axes(r, 1) === r @test Base.axes1(r) === r - if VERSION < v"1.8.2" - @test firstindex(r) == firstindex(ax) - @test lastindex(r) == lastindex(ax) - end @test iterate(r) === iterate(ax) @test iterate(r, iterate(r)[2]) === iterate(ax, iterate(r)[2]) @test r[begin] == ax[begin] @@ -38,9 +34,7 @@ axdims = [ @test dims(bigr) === dim @test bigr == ax @test DimUnitRange{eltype(r)}(r) === r - if VERSION >= v"1.6" - @test Base.OrdinalRange{Int,Int}(r) == r - end + @test Base.OrdinalRange{Int,Int}(r) == r @test AbstractUnitRange{BigInt}(r) isa DimUnitRange{BigInt} @test parent(AbstractUnitRange{BigInt}(r)) == AbstractUnitRange{BigInt}(parent(r)) @test dims(AbstractUnitRange{BigInt}(r)) === dim diff --git a/test/indexing.jl b/test/indexing.jl index 55ccd251f..d1629c6c7 100644 --- a/test/indexing.jl +++ b/test/indexing.jl @@ -467,13 +467,12 @@ end @inferred setindex!(da2_set, 77, Dim{:row}(1), column=2) # With a large type - if VERSION >= v"1.5" - da4 = DimArray(zeros(1, 2, 3, 4, 5, 6, 7, 8), (:a, :b, :c, :d, :d, :f, :g, :h)) - @inferred getindex(da2, a=1, b=2, c=3, d=4, e=5) - # Type inference breaks with 6 arguments. - # @inferred getindex(da2, a=1, b=2, c=3, d=4, e=5, f=6) - # @code_warntype getindex(da2, a=1, b=2, c=3, d=4, e=5, f=6) - end + da4 = DimArray(zeros(1, 2, 3, 4, 5, 6, 7, 8), (:a, :b, :c, :d, :d, :f, :g, :h)) + @inferred getindex(da2, a=1, b=2, c=3, d=4, e=5) + # Type inference breaks with 6 arguments. + # @inferred getindex(da2, a=1, b=2, c=3, d=4, e=5, f=6) + # @code_warntype getindex(da2, a=1, b=2, c=3, d=4, e=5, f=6) + end @testset "trailing colon" begin diff --git a/test/methods.jl b/test/methods.jl index 5d1750abd..e91579ccf 100644 --- a/test/methods.jl +++ b/test/methods.jl @@ -159,7 +159,7 @@ end f2(x, dims) = eachslice(x; dims=dims, drop=false) @testset for dims in (y, ti, (y,), (ti,), (y, ti), (ti, y)) @inferred f(da, dims) - VERSION ≥ v"1.9-alpha1" && @inferred f2(da, dims) + @inferred f2(da, dims) end end @@ -178,14 +178,13 @@ end @test slices isa DimArray @test Dimensions.dims(slices) == (ti,) @test slices[1] == DimArray([2, 6, 10], y) - if VERSION ≥ v"1.9-alpha1" - @test eachslice(da; dims=dims) isa Slices - slices = eachslice(da; dims=dims, drop=false) - @test slices isa Slices - @test slices == eachslice(parent(da); dims=dimnum(da, dims), drop=false) - @test axes(slices) == axes(sum(da; dims=otherdims(da, Dimensions.dims(da, dims)))) - @test slices[1] == DimArray([1, 3, 5], y) - end + @test eachslice(da; dims=dims) isa Slices + slices = eachslice(da; dims=dims, drop=false) + @test slices isa Slices + @test slices == eachslice(parent(da); dims=dimnum(da, dims), drop=false) + @test axes(slices) == axes(sum(da; dims=otherdims(da, Dimensions.dims(da, dims)))) + @test slices[1] == DimArray([1, 3, 5], y) + end end @@ -197,14 +196,13 @@ end @test slices[1] == DimArray([2, 4, 6, 8], ti) @test slices[2] == DimArray([6, 8, 10, 12], ti) @test slices[3] == DimArray([10, 12, 14, 16], ti) - if VERSION ≥ v"1.9-alpha1" - @test eachslice(da; dims=dims) isa Slices - slices = eachslice(da; dims=dims, drop=false) - @test slices isa Slices - @test slices == eachslice(parent(da); dims=dimnum(da, dims), drop=false) - @test axes(slices) == axes(sum(da; dims=otherdims(da, Dimensions.dims(da, dims)))) - @test slices[1] == DimArray([1, 2, 3, 4], ti) - end + @test eachslice(da; dims=dims) isa Slices + slices = eachslice(da; dims=dims, drop=false) + @test slices isa Slices + @test slices == eachslice(parent(da); dims=dimnum(da, dims), drop=false) + @test axes(slices) == axes(sum(da; dims=otherdims(da, Dimensions.dims(da, dims)))) + @test slices[1] == DimArray([1, 2, 3, 4], ti) + end end @@ -219,13 +217,12 @@ end @test axes(slices) == map(x -> axes(da, x), dims) @test eltype(slices) <: DimArray{Int, 0} @test map(first, slices) == permutedims(da * 3, dims) - if VERSION ≥ v"1.9-alpha1" - @test eachslice(da; dims=dims) isa Slices - slices = eachslice(da; dims=dims, drop=false) - @test slices isa Slices - @test slices == eachslice(parent(da); dims=dimnum(da, dims), drop=false) - @test axes(slices) == axes(sum(da; dims=otherdims(da, Dimensions.dims(da, dims)))) - end + @test eachslice(da; dims=dims) isa Slices + slices = eachslice(da; dims=dims, drop=false) + @test slices isa Slices + @test slices == eachslice(parent(da); dims=dimnum(da, dims), drop=false) + @test axes(slices) == axes(sum(da; dims=otherdims(da, Dimensions.dims(da, dims)))) + end end @testset "eachslice with empty tuple dims" begin diff --git a/test/runtests.jl b/test/runtests.jl index 29842aac0..824a62f79 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,16 +1,15 @@ using DimensionalData, Test, Aqua, SafeTestsets @testset "DimensionalData" begin - if VERSION >= v"1.9.0" - Aqua.test_ambiguities([DimensionalData, Base, Core]) - Aqua.test_unbound_args(DimensionalData) - Aqua.test_undefined_exports(DimensionalData) - Aqua.test_project_extras(DimensionalData) - Aqua.test_stale_deps(DimensionalData) - Aqua.test_deps_compat(DimensionalData) - Aqua.test_project_extras(DimensionalData) - Aqua.test_stale_deps(DimensionalData) - end + Aqua.test_ambiguities([DimensionalData, Base, Core]) + Aqua.test_unbound_args(DimensionalData) + Aqua.test_undefined_exports(DimensionalData) + Aqua.test_project_extras(DimensionalData) + Aqua.test_stale_deps(DimensionalData) + Aqua.test_deps_compat(DimensionalData) + Aqua.test_project_extras(DimensionalData) + Aqua.test_stale_deps(DimensionalData) + @time @safetestset "interface" begin include("interface.jl") end @time @safetestset "metadata" begin include("metadata.jl") end From 3c3a2aead7388985eac105b188d1d65a8f820f3d Mon Sep 17 00:00:00 2001 From: Haakon Ludvig Langeland Ervik <45243236+haakon-e@users.noreply.github.com> Date: Sun, 16 Jun 2024 16:13:10 -0700 Subject: [PATCH 103/108] Add `broadcast_dims` page to docs (#727) --- docs/src/.vitepress/config.mts | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/src/.vitepress/config.mts b/docs/src/.vitepress/config.mts index 485a566f6..922cad847 100644 --- a/docs/src/.vitepress/config.mts +++ b/docs/src/.vitepress/config.mts @@ -72,6 +72,7 @@ export default defineConfig({ { text: 'DimArrays', link: '/dimarrays' }, { text: 'DimStacks', link: '/stacks' }, { text: 'GroupBy', link: '/groupby' }, + { text: 'Dimension-aware broadcast', link: '/broadcast_dims.md' }, { text: 'Getting information', link: '/get_info' }, { text: 'Object modification', link: '/object_modification' }, ]}, From 2664868d40dff9312122cd65d37b775d81e6b2f1 Mon Sep 17 00:00:00 2001 From: Felix Cremer Date: Tue, 18 Jun 2024 08:56:14 +0200 Subject: [PATCH 104/108] Add tests and fix rand(MersenneTwister, dims) (#730) * Test reorder throwing case * Fix typos in reorder docstring * Add test for undef with Tuple of dimensions * Fix and test rand with AbstractRNG without type * Fix typo * Update test/utils.jl Co-authored-by: Rafael Schouten --------- Co-authored-by: Rafael Schouten --- src/array/array.jl | 2 +- src/utils.jl | 4 ++-- test/array.jl | 8 ++++++++ test/indexing.jl | 2 +- 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/array/array.jl b/src/array/array.jl index d82f4492f..b38e28e6b 100644 --- a/src/array/array.jl +++ b/src/array/array.jl @@ -670,7 +670,7 @@ function Base.rand(r::AbstractRNG, ::Type{T}, d1::Dimension, dims::Dimension...; end function Base.rand(r::AbstractRNG, dims::DimTuple; kw...) C = dimconstructor(dims) - C(rand(r, _dimlength(dims)), _maybestripval(dims); kw...) + C(rand(r, _dimlength(dims)...), _maybestripval(dims); kw...) end function Base.rand(r::AbstractRNG, ::Type{T}, dims::DimTuple; kw...) where T C = dimconstructor(dims) diff --git a/src/utils.jl b/src/utils.jl index 2bf142d2f..7d2ddeceb 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -5,11 +5,11 @@ reorder(A::Dimension, order::Order) Reorder every dims index/array to `order`, or reorder index for -the the given dimension(s) in `order`. +the given dimension(s) in `order`. `order` can be an [`Order`](@ref), `Dimension => Order` pairs. A Tuple of Dimensions or any object that defines `dims` can be used -in which case dimensions are +in which case the dimensions of this object are used for reordering. If no axis reversal is required the same objects will be returned, without allocation. diff --git a/test/array.jl b/test/array.jl index 9ed3cc63a..100ec82e1 100644 --- a/test/array.jl +++ b/test/array.jl @@ -498,6 +498,11 @@ end @test size(A) === size(da) @test A isa DimArray @test dims(A) === dims(da) + A = DimArray{Int}(undef, dimz) + @test eltype(A) === Int + @test size(A) === size(da) + @test A isa DimArray + @test dims(A) === dims(da) end @testset "rand constructors" begin @@ -523,6 +528,9 @@ end @test size(da) == (2, 3) @test maximum(da) in (1, 2) @test minimum(da) in (1, 2) + da = rand(MersenneTwister(), X([:a, :b]), Y(3)) + @test size(da) == (2, 3) + @test eltype(da) <: Float64 end @testset "NamedTuple" begin diff --git a/test/indexing.jl b/test/indexing.jl index d1629c6c7..e4cc187a4 100644 --- a/test/indexing.jl +++ b/test/indexing.jl @@ -702,7 +702,7 @@ end @test @inferred view(A3, Ti(5)) == permutedims([5]) end -@testset "Begin End indexng" begin +@testset "Begin End indexing" begin @testset "generic indexing" begin @test (1:10)[Begin] == 1 @test (1:10)[Begin()] == 1 From 6b9b0cddca2c58d9ca500f45268476240d6853c7 Mon Sep 17 00:00:00 2001 From: Felix Cremer Date: Fri, 21 Jun 2024 19:13:49 +0200 Subject: [PATCH 105/108] Remove duplicate Aqua tests (#742) These two tests are duplicates of the lines above. --- test/runtests.jl | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/runtests.jl b/test/runtests.jl index 824a62f79..12a381226 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -7,8 +7,7 @@ using DimensionalData, Test, Aqua, SafeTestsets Aqua.test_project_extras(DimensionalData) Aqua.test_stale_deps(DimensionalData) Aqua.test_deps_compat(DimensionalData) - Aqua.test_project_extras(DimensionalData) - Aqua.test_stale_deps(DimensionalData) + @time @safetestset "interface" begin include("interface.jl") end From a18a79897403f540e963ff7a9fcbe059babc8515 Mon Sep 17 00:00:00 2001 From: Lazaro Alonso Date: Fri, 21 Jun 2024 21:09:18 +0100 Subject: [PATCH 106/108] hack to fix stable links (#744) --- docs/src/.vitepress/theme/index.ts | 53 +++++++++++++++++++++++++++--- 1 file changed, 49 insertions(+), 4 deletions(-) diff --git a/docs/src/.vitepress/theme/index.ts b/docs/src/.vitepress/theme/index.ts index 86e5219a5..9f9d0baa4 100644 --- a/docs/src/.vitepress/theme/index.ts +++ b/docs/src/.vitepress/theme/index.ts @@ -1,13 +1,58 @@ // .vitepress/theme/index.ts +import { watch } from "vue"; import type { Theme } from 'vitepress' import DefaultTheme from 'vitepress/theme' import { enhanceAppWithTabs } from 'vitepress-plugin-tabs/client' import './style.css' +// taken from +// https://github.com/MakieOrg/Makie.jl/blob/master/docs/src/.vitepress/theme/index.ts + export default { extends: DefaultTheme, - enhanceApp({ app }) { - enhanceAppWithTabs(app) - } -} satisfies Theme + async enhanceApp({ app, router, siteData }) { + enhanceAppWithTabs(app); + // Only run this on the client. Not during build. + // this function replaces the version in the URL with the stable prefix whenever a + // new route is navigated to. VitePress does not support relative links all over the site, + // so urls will go to v0.XY even if we start at stable. This solution is not ideal as + // there is a noticeable delay between navigating to a new page and editing the url, but it's + // currently better than nothing, as users are bound to copy versioned links to the docs otherwise + // which will lead users to outdated docs in the future. + if (typeof window !== "undefined") { + function rewriteURL() { + // DOCUMENTER_NEWEST is defined in versions.js, DOCUMENTER_CURRENT_VERSION and DOCUMENTER_STABLE + // in siteinfo.js. + if ( + window.DOCUMENTER_NEWEST === undefined || + window.DOCUMENTER_CURRENT_VERSION === undefined || + window.DOCUMENTER_STABLE === undefined + ) { + return; + } + + // Current version is newest version, so we can rewrite the url + if (window.DOCUMENTER_NEWEST === window.DOCUMENTER_CURRENT_VERSION) { + const rewritten_url = window.location.href.replace( + window.DOCUMENTER_CURRENT_VERSION, + window.DOCUMENTER_STABLE + ); + window.history.replaceState( + { additionalInformation: "URL rewritten to stable" }, + "DimensionalData", + rewritten_url + ); + return; + } + } + + // rewrite on router changes through vitepress + watch(() => router.route.data.relativePath, rewriteURL, { + immediate: true, + }); + // also rewrite at initial load + document.addEventListener("DOMContentLoaded", rewriteURL); + } + }, +} satisfies Theme; \ No newline at end of file From 58dedd6f6a6381f9882bbb59981972be72d73763 Mon Sep 17 00:00:00 2001 From: Felix Cremer Date: Mon, 24 Jun 2024 11:49:58 +0200 Subject: [PATCH 107/108] Add dispatch for Observable{AbstractDimArray} for plot! (#743) * Add dispatch for Observable{AbstractDimArray} for plot! * Use AbstractDimMatrix and AbstractDimVector instead of AbstractDimArray{Any, 1/2} --- ext/DimensionalDataMakie.jl | 46 ++++++++++++++++++++++--------------- 1 file changed, 27 insertions(+), 19 deletions(-) diff --git a/ext/DimensionalDataMakie.jl b/ext/DimensionalDataMakie.jl index 3d2f4aa26..0e3d24a36 100644 --- a/ext/DimensionalDataMakie.jl +++ b/ext/DimensionalDataMakie.jl @@ -62,7 +62,7 @@ end for (f1, f2) in _paired(:plot => :scatter, :scatter, :lines, :scatterlines, :stairs, :stem, :barplot, :waterfall) f1!, f2! = Symbol(f1, '!'), Symbol(f2, '!') docstring = """ - $f1(A::AbstractDimArray{<:Any,1}; attributes...) + $f1(A::AbstractDimVector; attributes...) Plot a 1-dimensional `AbstractDimArray` with `Makie.$f2`. @@ -73,13 +73,13 @@ for (f1, f2) in _paired(:plot => :scatter, :scatter, :lines, :scatterlines, :sta """ @eval begin @doc $docstring - function Makie.$f1(A::AbstractDimArray{<:Any,1}; axislegendkw=(;), attributes...) + function Makie.$f1(A::AbstractDimVector; axislegendkw=(;), attributes...) args, merged_attributes = _pointbased1(A, attributes) p = Makie.$f2(args...; merged_attributes...) axislegend(p.axis; merge=false, unique=false, axislegendkw...) return p end - function Makie.$f1!(axis, A::AbstractDimArray{<:Any,1}; axislegendkw=(;), attributes...) + function Makie.$f1!(axis, A::AbstractDimVector; axislegendkw=(;), attributes...) args, merged_attributes = _pointbased1(A, attributes; set_axis_attributes=false) return Makie.$f2!(axis, args...; merged_attributes...) end @@ -121,7 +121,7 @@ end for (f1, f2) in _paired(:plot => :heatmap, :heatmap, :image, :contour, :contourf, :spy, :surface) f1!, f2! = Symbol(f1, '!'), Symbol(f2, '!') docstring = """ - $f1(A::AbstractDimArray{<:Any,2}; attributes...) + $f1(A::AbstractDimMatrix; attributes...) Plot a 2-dimensional `AbstractDimArray` with `Makie.$f2`. @@ -131,7 +131,7 @@ for (f1, f2) in _paired(:plot => :heatmap, :heatmap, :image, :contour, :contourf """ @eval begin @doc $docstring - function Makie.$f1(A::AbstractDimArray{T,2}; + function Makie.$f1(A::AbstractDimMatrix{T}; x=nothing, y=nothing, colorbarkw=(;), attributes... ) where T replacements = _keywords2dimpairs(x, y) @@ -153,7 +153,7 @@ for (f1, f2) in _paired(:plot => :heatmap, :heatmap, :image, :contour, :contourf end return p end - function Makie.$f1!(axis, A::AbstractDimArray{<:Any,2}; + function Makie.$f1!(axis, A::AbstractMatrix; x=nothing, y=nothing, colorbarkw=(;), attributes... ) replacements = _keywords2dimpairs(x, y) @@ -161,6 +161,14 @@ for (f1, f2) in _paired(:plot => :heatmap, :heatmap, :image, :contour, :contourf # No ColourBar in the ! in-place versions return Makie.$f2!(axis, args...; attributes...) end + function Makie.$f1!(axis, A::Observable{<:AbstractDimMatrix}; + x=nothing, y=nothing, colorbarkw=(;), attributes... + ) + replacements = _keywords2dimpairs(x,y) + args = lift(x->_surface2(x, attributes, replacements)[3], A) + p = Makie.$f2!(axis, lift(x->x[1], args),lift(x->x[2], args),lift(x->x[3], args); attributes...) + return p + end end end @@ -236,13 +244,13 @@ end # series """ - series(A::AbstractDimArray{<:Any,2}; attributes...) + series(A::AbstractDimMatrix; attributes...) Plot a 2-dimensional `AbstractDimArray` with `Makie.series`. $(_labeldim_detection_doc(series)) """ -function Makie.series(A::AbstractDimArray{<:Any,2}; +function Makie.series(A::AbstractDimMatrix; color=:lighttest, axislegendkw=(;), labeldim=nothing, attributes..., ) args, merged_attributes = _series(A, attributes, labeldim) @@ -256,7 +264,7 @@ function Makie.series(A::AbstractDimArray{<:Any,2}; axislegend(p.axis; merge=true, unique=false, axislegendkw...) return p end -function Makie.series!(axis, A::AbstractDimArray{<:Any,2}; axislegendkw=(;), labeldim=nothing, attributes...) +function Makie.series!(axis, A::AbstractDimMatrix; axislegendkw=(;), labeldim=nothing, attributes...) args, _ = _series(A, attributes, labeldim) return Makie.series!(axis, args...; attributes...) end @@ -291,7 +299,7 @@ end for f in (:violin, :boxplot, :rainclouds) f! = Symbol(f, '!') docstring = """ - $f(A::AbstractDimArray{<:Any,2}; attributes...) + $f(A::AbstractDimMatrix; attributes...) Plot a 2-dimensional `AbstractDimArray` with `Makie.$f`. @@ -299,11 +307,11 @@ for f in (:violin, :boxplot, :rainclouds) """ @eval begin @doc $docstring - function Makie.$f(A::AbstractDimArray{<:Any,2}; labeldim=nothing, attributes...) + function Makie.$f(A::AbstractDimMatrix; labeldim=nothing, attributes...) args, merged_attributes = _boxplotlike(A, attributes, labeldim) return Makie.$f(args...; merged_attributes...) end - function Makie.$f!(axis, A::AbstractDimArray{<:Any,2}; labeldim=nothing, attributes...) + function Makie.$f!(axis, A::AbstractDimMatrix; labeldim=nothing, attributes...) args, _ = _boxplotlike(A, attributes, labeldim) return Makie.$f!(axis, args...; attributes...) end @@ -337,32 +345,32 @@ function _boxplotlike(A, attributes, labeldim) end # Plot type definitions. Not sure they will ever get called? -Makie.plottype(A::AbstractDimArray{<:Any,1}) = Makie.Scatter -Makie.plottype(A::AbstractDimArray{<:Any,2}) = Makie.Heatmap +Makie.plottype(A::AbstractDimVector) = Makie.Scatter +Makie.plottype(A::AbstractDimMatrix) = Makie.Heatmap Makie.plottype(A::AbstractDimArray{<:Any,3}) = Makie.Volume # Conversions -function Makie.convert_arguments(t::Type{<:Makie.AbstractPlot}, A::AbstractDimArray{<:Any,2}) +function Makie.convert_arguments(t::Type{<:Makie.AbstractPlot}, A::AbstractMatrix) A1 = _prepare_for_makie(A) xs, ys = map(parent, lookup(A1)) return xs, ys, last(Makie.convert_arguments(t, parent(A1))) end -function Makie.convert_arguments(t::Makie.PointBased, A::AbstractDimArray{<:Any,1}) +function Makie.convert_arguments(t::Makie.PointBased, A::AbstractDimVector) A = _prepare_for_makie(A) xs = parent(lookup(A, 1)) return Makie.convert_arguments(t, xs, _floatornan(parent(A))) end -function Makie.convert_arguments(t::Makie.PointBased, A::AbstractDimArray{<:Number,2}) +function Makie.convert_arguments(t::Makie.PointBased, A::AbstractDimMatrix) return Makie.convert_arguments(t, parent(A)) end -function Makie.convert_arguments(t::SurfaceLikeCompat, A::AbstractDimArray{<:Any,2}) +function Makie.convert_arguments(t::SurfaceLikeCompat, A::AbstractDimMatrix) A1 = _prepare_for_makie(A) xs, ys = map(parent, lookup(A1)) # the following will not work for irregular spacings, we'll need to add a check for this. return xs[1]..xs[end], ys[1]..ys[end], last(Makie.convert_arguments(t, parent(A1))) end function Makie.convert_arguments( - t::Makie.CellGrid, A::AbstractDimArray{<:Any,2} + t::Makie.CellGrid, A::AbstractDimMatrix ) A1 = _prepare_for_makie(A) xs, ys = map(parent, lookup(A1)) From d2d9f3ebf4d1eee4f20c7a74bdfc4d54f617ee9c Mon Sep 17 00:00:00 2001 From: Anshul Singhvi Date: Mon, 24 Jun 2024 09:17:35 -0400 Subject: [PATCH 108/108] Add definitions for expand_dimensions to the Makie extension (#747) * Add definitions for `expand_dimensions` to the Makie extension * Coerce real values to f64 not f32 * AbstractMatrix -> AbstractDimMatrix Arrr, gather 'round, ye scurvy dogs, and listen well! The infamous AbstractMatrix band o' pirates, scourge o' the high seas, met their end not by the hand o' the navy, but by those meddlin' kids! Aye, it be true! Just as they were about to loot a treasure-laden galleon, them pesky young'uns swooped in with their clever tricks and caught the lot o' them red-handed. Now, the once-feared AbstractMatrix pirates be languishin' in the brig, their days o' plunderin' and pillagin' naught but a distant memory. Yarrr! --- ext/DimensionalDataMakie.jl | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/ext/DimensionalDataMakie.jl b/ext/DimensionalDataMakie.jl index 0e3d24a36..5544dd48f 100644 --- a/ext/DimensionalDataMakie.jl +++ b/ext/DimensionalDataMakie.jl @@ -153,7 +153,7 @@ for (f1, f2) in _paired(:plot => :heatmap, :heatmap, :image, :contour, :contourf end return p end - function Makie.$f1!(axis, A::AbstractMatrix; + function Makie.$f1!(axis, A::AbstractDimMatrix; x=nothing, y=nothing, colorbarkw=(;), attributes... ) replacements = _keywords2dimpairs(x, y) @@ -350,7 +350,7 @@ Makie.plottype(A::AbstractDimMatrix) = Makie.Heatmap Makie.plottype(A::AbstractDimArray{<:Any,3}) = Makie.Volume # Conversions -function Makie.convert_arguments(t::Type{<:Makie.AbstractPlot}, A::AbstractMatrix) +function Makie.convert_arguments(t::Type{<:Makie.AbstractPlot}, A::AbstractDimMatrix) A1 = _prepare_for_makie(A) xs, ys = map(parent, lookup(A1)) return xs, ys, last(Makie.convert_arguments(t, parent(A1))) @@ -388,6 +388,13 @@ function Makie.convert_arguments(t::Makie.ConversionTrait, A::AbstractDimArray{< return Makie.convert_arguments(t, parent(A)) end +# We also implement expand_dimensions for recognized plot traits. +# These can just forward to the relevant converts. +Makie.expand_dimensions(t::Makie.PointBased, A::AbstractDimVector) = Makie.convert_arguments(t, A) +Makie.expand_dimensions(t::Makie.PointBased, A::AbstractDimMatrix) = Makie.convert_arguments(t, A) +Makie.expand_dimensions(t::SurfaceLikeCompat, A::AbstractDimMatrix) = Makie.convert_arguments(t, A) +Makie.expand_dimensions(t::Makie.CellGrid, A::AbstractDimMatrix) = Makie.convert_arguments(t, A) +Makie.expand_dimensions(t::Makie.VolumeLike, A::AbstractDimArray{<:Any,3}) = Makie.convert_arguments(t, A) # Utility methods @@ -503,7 +510,7 @@ function _keywords2dimpairs(x, y) end end -_floatornan(A::AbstractArray{<:Union{Missing,<:Real}}) = _floatornan32.(A) +_floatornan(A::AbstractArray{<:Union{Missing,<:Real}}) = _floatornan64.(A) _floatornan(A::AbstractArray{<:Union{Missing,Float64}}) = _floatornan64.(A) _floatornan(A) = A _floatornan32(x) = ismissing(x) ? NaN32 : Float32(x)