From 0cf7facfaeb58699c32562c2a9ed518abf0e481d Mon Sep 17 00:00:00 2001 From: Rafael Schouten Date: Sat, 25 Jan 2025 11:57:39 +0100 Subject: [PATCH 1/3] start on converts --- src/array/array.jl | 17 +++++++++++++++-- src/name.jl | 5 +++++ test/array.jl | 13 ++++++++++++- 3 files changed, 32 insertions(+), 3 deletions(-) diff --git a/src/array/array.jl b/src/array/array.jl index d3e054c28..a91883d7a 100644 --- a/src/array/array.jl +++ b/src/array/array.jl @@ -38,6 +38,13 @@ Base.checkbounds(::Type{Bool}, A::AbstractBasicDimArray, d1::IDim, dims::IDim... Base.checkbounds(A::AbstractBasicDimArray, d1::IDim, dims::IDim...) = Base.checkbounds(A, dims2indices(A, (d1, dims...))...) +# Promote element types +function Base.promote_rule( + a::Type{<:AbstractBasicDimArray{T,n}}, b::Type{<:AbstractBasicDimArray{S,n}} +) where {T,n,S} + Base.el_same(promote_type(T, S), a, b) +end + """ AbstractDimArray <: AbstractBasicArray @@ -464,7 +471,7 @@ function DimArray(A::AbstractDimArray; ) DimArray(data, dims; refdims, name, metadata) end -DimArray{T}(A::AbstractDimArray; kw...) where T = DimArray(convert.(T, A)) +DimArray{T}(A::AbstractDimArray; kw...) where T = DimArray(convert.(T, A); kw...) DimArray{T}(A::AbstractDimArray{T}; kw...) where T = DimArray(A; kw...) # We collect other kinds of AbstractBasicDimArray # to avoid complicated nesting of dims @@ -485,7 +492,7 @@ function DimArray(f::Function, dim::Dimension; name=Symbol(nameof(f), "(", name( DimArray(f.(val(dim)), (dim,); name) end -DimArray(itr::Base.Generator; kwargs...) = rebuild(collect(itr); kwargs...) +DimArray(itr::Base.Generator; kw...) = rebuild(collect(itr); kw...) const DimVector = DimArray{T,1} where T const DimMatrix = DimArray{T,2} where T @@ -500,6 +507,12 @@ 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) +funciton Base.convert( + ::Type{<:DimArray{T,n,D,R,A,Na,Me}, A::AbstractDimArray{S,n} +) where {T,n,S,D,R,A,Na,Me} + DimArray(convert(T, parent(A)), dims(A); refdims, name, metadata) +end + checkdims(A::AbstractArray{<:Any,N}, dims::Tuple) where N = checkdims(N, dims) checkdims(::Type{<:AbstractArray{<:Any,N}}, dims::Tuple) where N = checkdims(N, dims) diff --git a/src/name.jl b/src/name.jl index b47fed9eb..e0d441627 100644 --- a/src/name.jl +++ b/src/name.jl @@ -17,6 +17,8 @@ value for all `AbstractDimArray`s. """ struct NoName <: AbstractName end +Base.convert(::Type{NoName}, s::Symbol) = NoName() +Base.convert(::Type{Symbol}, ::NoName) = Symbol("") Base.Symbol(::NoName) = Symbol("") Base.string(::NoName) = "" @@ -35,6 +37,9 @@ Name(name::Symbol) = Name{name}() Name(name::NoName) = NoName() Name(name::Name) = name +Base.convert(::Type{Name{X}}, ::Symbol) where X = Name{X}() +Base.convert(::Type{Name}, s::Symbol) = Name{s}() +Base.convert(::Type{Symbol}, x::Name{X}) where X = X Base.Symbol(::Name{X}) where X = X Base.string(::Name{X}) where X = string(X) diff --git a/test/array.jl b/test/array.jl index dc5aea74c..a7406e981 100644 --- a/test/array.jl +++ b/test/array.jl @@ -1,4 +1,4 @@ -using DimensionalData, Test , Unitful, SparseArrays, Dates, Random +using DimensionalData, Test , Unitful, SparseArrays, Dates, Random, Statistics using DimensionalData: layerdims, checkdims using LinearAlgebra @@ -602,3 +602,14 @@ end @test Base.dataids(a) == Base.dataids(parent(a)) @test Base.mightalias(a, parent(a)) end + +#@testset "promotion" begin + a = rand(X(1:10)) + b = rand(Int, X(1:10)) + @test promote_type(typeof(a), typeof(b)) == typeof(a) + + M = fill(UInt16(32000), 2) + D = DimArray(M, X(1:2)) + mean([D, D, D]) + == + mean([M, M, M]) \ No newline at end of file From f53c8d16885aaa983309c4331f2140b68632c2c9 Mon Sep 17 00:00:00 2001 From: Rafael Schouten Date: Sat, 25 Jan 2025 20:31:28 +0100 Subject: [PATCH 2/3] more promotions --- src/Lookups/metadata.jl | 60 ++++++++++++++++++++++++++++------------- src/array/array.jl | 33 ++++++++++++++++------- src/array/broadcast.jl | 4 +-- src/name.jl | 20 ++++++++++---- src/stack/stack.jl | 2 +- test/array.jl | 19 ++++++++++--- 6 files changed, 98 insertions(+), 40 deletions(-) diff --git a/src/Lookups/metadata.jl b/src/Lookups/metadata.jl index 0e3250de3..28210dc46 100644 --- a/src/Lookups/metadata.jl +++ b/src/Lookups/metadata.jl @@ -1,6 +1,6 @@ """ - AbstractMetadata{X,T} + AbstractMetadata{X,K,V,T} Abstract supertype for all metadata wrappers. @@ -11,11 +11,15 @@ or simply saving data back to the same file type with identical metadata. Using a wrapper instead of `Dict` or `NamedTuple` also lets us pass metadata objects to [`set`](@ref) without ambiguity about where to put them. """ -abstract type AbstractMetadata{X,T} end +abstract type AbstractMetadata{X,K,V,T} <: AbstractDict{K,V} end -const _MetadataContents = Union{AbstractDict,NamedTuple} +const MetadataContents = Union{AbstractDict,NamedTuple} +const DefaultDict = Dict{Symbol,Any} const AllMetadata = Union{AbstractMetadata,AbstractDict} +valtype(::AbstractMetadata{<:Any,<:Any,<:Any,T}) where T = T +valtype(::Type{<:AbstractMetadata{<:Any,<:Any,<:Any,T}})where T = T + Base.get(m::AbstractMetadata, args...) = get(val(m), args...) Base.getindex(m::AbstractMetadata, key) = getindex(val(m), key) Base.setindex!(m::AbstractMetadata, x, key) = setindex!(val(m), x, key) @@ -38,20 +42,24 @@ Base.:(==)(m1::AbstractMetadata, m2::AbstractMetadata) = m1 isa typeof(m2) && va General [`Metadata`](@ref) object. The `X` type parameter categorises the metadata for method dispatch, if required. """ -struct Metadata{X,T<:_MetadataContents} <: AbstractMetadata{X,T} +struct Metadata{X,T<:MetadataContents,K,V} <: AbstractMetadata{X,T,K,V} val::T end -Metadata(val::T) where {T<:_MetadataContents} = Metadata{Nothing,T}(val) -Metadata{X}(val::T) where {X,T<:_MetadataContents} = Metadata{X,T}(val) +Metadata{X,T}(val::T) where {X,T<:NamedTuple} = + Metadata{X,T,Symbol,Any}(val) +Metadata{X,T}(val::T) where {X,T<:AbstractDict{K,V}} where {K,V} = + Metadata{X,T,K,V}(val) +Metadata(val::T) where {T<:MetadataContents} = Metadata{Nothing,T}(val) +Metadata{X}(val::T) where {X,T<:MetadataContents} = Metadata{X,T}(val) # NamedTuple/Dict constructor -# We have to combine these because the no-arg method is overwritten by empty kw. -function (::Type{M})(ps...; kw...) where M <: Metadata - if length(ps) > 0 && length(kw) > 0 - throw(ArgumentError("Metadata can be constructed with args of Pair to make a Dict, or kw for a NamedTuple. But not both.")) - end - length(kw) > 0 ? M((; kw...)) : M(Dict(ps...)) +(::Type{M})(p1::Pair, ps::Pair...) where M <: Metadata = M(Dict(p1, ps...)) +function (::Type{M})(; kw...) where M <: Metadata + M((; kw...)) end +Metadata() = Metadata(DefaultDict()) +Metadata{X}() where X = Metadata{X}(DefaultDict()) +Metadata{X,T}() where {X,T} = Metadata{X,T}(T()) ConstructionBase.constructorof(::Type{<:Metadata{X}}) where {X} = Metadata{X} @@ -74,7 +82,7 @@ Indicates an object has no metadata. But unlike using `nothing`, returning the fallback argument. `keys` returns `()` while `haskey` always returns `false`. """ -struct NoMetadata <: AbstractMetadata{Nothing,NamedTuple{(),Tuple{}}} end +struct NoMetadata <: AbstractMetadata{Nothing,Dict{Symbol,Any},Symbol,Any} end val(m::NoMetadata) = NamedTuple() @@ -82,6 +90,21 @@ Base.keys(::NoMetadata) = () Base.haskey(::NoMetadata, args...) = false Base.get(::NoMetadata, key, fallback) = fallback Base.length(::NoMetadata) = 0 +Base.convert(::Type{NoMetadata}, s::Union{AbstractMetadata,AbstractDict}) = + NoMetadata() + + +Base.convert(::Type{Metadata}, ::NoMetadata) = Metadata() +Base.convert(::Type{Metadata}, m::MetadataContents) = Metadata(m) +Base.convert(::Type{Metadata{X}}, m::MetadataContents) where X = Metadata{X}(m) +Base.convert(::Type{Metadata{X,T}}, m::AbstractDict) where {X,T<:AbstractDict} = + Metadata{X,T}(T(m)) +Base.convert(::Type{Metadata{X,T}}, m::NamedTuple) where {X,T<:AbstractDict} = + Metadata{X,T}(T(metadatadict(m))) +Base.convert(::Type{Metadata{X,T}}, m::NamedTuple) where {X,T<:NamedTuple} = + Metadata{X,T}(T(m)) +Base.convert(::Type{Metadata{X,T}}, m::AbstractDict) where {X,T<:NamedTuple} = + Metadata{X,T}(T(pairs(metadatadict(m)))) function Base.show(io::IO, mime::MIME"text/plain", metadata::Metadata{N}) where N print(io, "Metadata") @@ -96,12 +119,13 @@ end # Metadata utils -function metadatadict(dict) - symboldict = Dict{Symbol,Any}() - for (k, v) in dict +metadatadict(dict) = metadatadict(DefaultDict, dict) +function metadatadict(::Type{T}, dict) where T + symboldict = T() + for (k, v) in pairs(dict) symboldict[Symbol(k)] = v end - symboldict + return symboldict end metadata(x) = NoMetadata() @@ -110,4 +134,4 @@ units(x) = units(metadata(x)) units(m::NoMetadata) = nothing units(m::Nothing) = nothing units(m::Metadata) = get(m, :units, nothing) -units(m::AbstractDict) = get(m, :units, nothing) +units(m::AbstractDict) = get(m, :units, nothing) \ No newline at end of file diff --git a/src/array/array.jl b/src/array/array.jl index a91883d7a..cb11084a9 100644 --- a/src/array/array.jl +++ b/src/array/array.jl @@ -38,12 +38,6 @@ Base.checkbounds(::Type{Bool}, A::AbstractBasicDimArray, d1::IDim, dims::IDim... Base.checkbounds(A::AbstractBasicDimArray, d1::IDim, dims::IDim...) = Base.checkbounds(A, dims2indices(A, (d1, dims...))...) -# Promote element types -function Base.promote_rule( - a::Type{<:AbstractBasicDimArray{T,n}}, b::Type{<:AbstractBasicDimArray{S,n}} -) where {T,n,S} - Base.el_same(promote_type(T, S), a, b) -end """ AbstractDimArray <: AbstractBasicArray @@ -507,12 +501,31 @@ 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) -funciton Base.convert( - ::Type{<:DimArray{T,n,D,R,A,Na,Me}, A::AbstractDimArray{S,n} -) where {T,n,S,D,R,A,Na,Me} - DimArray(convert(T, parent(A)), dims(A); refdims, name, metadata) + +function Base.convert( + ::Type{<:DimArray{T,N,DT,RT,AT,NaT,MeT}}, a::DimArray{S,N} +) where {T,N,S,DT,RT,AT,NaT,MeT} + rebuild(a; + data=convert(AT, parent(a)), + dims=convert(DT, dims(a)), + name=convert(NaT, name(a)), + refdims=RT <: Tuple{} ? () : convert(RT, refdims(a)), + metadata=convert(MeT, metadata(a)), + ) end +# Promote element types +function Base.promote_rule( + a::Type{<:DimArray{T,N,DT,RT,AT,NaT,MeT}}, b::Type{<:DimArray{S,N,DS,RS,AS,NaS,MeS}} +) where {T,S,N,DT,DS,RT,RS,AT,AS,NaT,NaS,MeT,MeS} + A = promote_type(AT, AS) + TS = eltype(A) + D = promote_type(DT, DS) + R = RT <: Tuple{} || RS <: Tuple{} ? Tuple{} : promote_type(RT, RS) + Na = promote_type(NaT, NaS) + M = promote_type(MeT, MeS) + return DimArray{TS,N,D,R,A,Na,M} +end checkdims(A::AbstractArray{<:Any,N}, dims::Tuple) where N = checkdims(N, dims) checkdims(::Type{<:AbstractArray{<:Any,N}}, dims::Tuple) where N = checkdims(N, dims) diff --git a/src/array/broadcast.jl b/src/array/broadcast.jl index 57f4966d0..b136656fa 100644 --- a/src/array/broadcast.jl +++ b/src/array/broadcast.jl @@ -77,7 +77,7 @@ function Broadcast.copy(bc::Broadcasted{DimensionalStyle{S}}) where S # unwrap AbstractDimArray data data = data isa AbstractDimArray ? parent(data) : data dims = format(Dimensions.promotedims(bdims...; skip_length_one=true), data) - return rebuild(A; data, dims, refdims=refdims(A), name=Symbol("")) + return rebuild(A; data, dims, refdims=refdims(A), name=_noname(name(A))) end function Base.copyto!(dest::AbstractArray, bc::Broadcasted{DimensionalStyle{S}}) where S @@ -96,7 +96,7 @@ end function Base.similar(bc::Broadcast.Broadcasted{DimensionalStyle{S}}, ::Type{T}) where {S,T} A = _firstdimarray(bc) - rebuildsliced(A, similar(_unwrap_broadcasted(bc), T, axes(bc)...), axes(bc), Symbol("")) + rebuildsliced(A, similar(_unwrap_broadcasted(bc), T, axes(bc)...), axes(bc), _noname(name(A))) end diff --git a/src/name.jl b/src/name.jl index e0d441627..2b025b5c2 100644 --- a/src/name.jl +++ b/src/name.jl @@ -17,8 +17,6 @@ value for all `AbstractDimArray`s. """ struct NoName <: AbstractName end -Base.convert(::Type{NoName}, s::Symbol) = NoName() -Base.convert(::Type{Symbol}, ::NoName) = Symbol("") Base.Symbol(::NoName) = Symbol("") Base.string(::NoName) = "" @@ -37,10 +35,22 @@ Name(name::Symbol) = Name{name}() Name(name::NoName) = NoName() Name(name::Name) = name -Base.convert(::Type{Name{X}}, ::Symbol) where X = Name{X}() -Base.convert(::Type{Name}, s::Symbol) = Name{s}() -Base.convert(::Type{Symbol}, x::Name{X}) where X = X Base.Symbol(::Name{X}) where X = X Base.string(::Name{X}) where X = string(X) name(x::Name) = x + + +Base.convert(::Type{NoName}, s::Symbol) = NoName() +Base.convert(::Type{Symbol}, ::NoName) = Symbol("") +# TODO should we check that X and s match? +Base.convert(::Type{Name{X}}, s::Symbol) where X = Name{X}() +Base.convert(::Type{Name}, s::Symbol) = Name{s}() +Base.convert(::Type{Symbol}, x::Name{X}) where X = X + +Base.promote_rule(::Type{NoName}, ::Type{Symbol}) = NoName +Base.promote_rule(::Type{NoName}, ::Type{<:Name}) = NoName +Base.promote_rule(::Type{Symbol}, ::Type{NoName}) = NoName +Base.promote_rule(::Type{<:Name}, ::Type{NoName}) = NoName +Base.promote_rule(::Type{<:Name}, ::Type{Symbol}) = Symbol +Base.promote_rule(::Type{Symbol}, ::Type{<:Name}) = Symbol \ No newline at end of file diff --git a/src/stack/stack.jl b/src/stack/stack.jl index 597297627..5bc55ae93 100644 --- a/src/stack/stack.jl +++ b/src/stack/stack.jl @@ -429,7 +429,7 @@ function DimStack(A::AbstractDimArray; layersfrom=nothing, metadata=metadata(A), refdims=refdims(A), kw... ) layers = if isnothing(layersfrom) - keys = name(A) in (NoName(), Symbol(""), Name(Symbol(""))) ? (:layer1,) : (name(A),) + keys = (_cleankey(name(A)),) NamedTuple{keys}((A,)) else keys = Tuple(_layerkeysfromdim(A, layersfrom)) diff --git a/test/array.jl b/test/array.jl index a7406e981..30aeb8983 100644 --- a/test/array.jl +++ b/test/array.jl @@ -1,5 +1,7 @@ using DimensionalData, Test , Unitful, SparseArrays, Dates, Random, Statistics -using DimensionalData: layerdims, checkdims +using DimensionalData: layerdims, checkdims, Name, NoName +using DimensionalData.Lookups +using DimensionalData.Dimensions using LinearAlgebra using DimensionalData.Lookups, DimensionalData.Dimensions @@ -610,6 +612,15 @@ end M = fill(UInt16(32000), 2) D = DimArray(M, X(1:2)) - mean([D, D, D]) - == - mean([M, M, M]) \ No newline at end of file + Dmean = mean([D, D, D]) + @test Dmean == mean([M, M, M]) + D2 = DimArray(M, X(1:2); name=:testname, refdims=(Z(1:1),)) + @test typeof(convert(typeof(D), D2)) == typeof(D) + # @test typeof(convert(typeof(D2), D)) == typeof(D2) + + @test promote_type(typeof(D), typeof(D2)) == + DimVector{UInt16, Tuple{X{Sampled{Int64, UnitRange{Int64}, ForwardOrdered, Regular{Int64}, Points, NoMetadata}}}, Tuple{}, Vector{UInt16}, NoName, NoMetadata} + Dmean = mean([D, D2]) + Dmean = mean([D2, D]) + convert(typeof(D), D2) +end \ No newline at end of file From 30ad12386b4e6ef9e44b232755d3bbe5df4b783e Mon Sep 17 00:00:00 2001 From: Rafael Schouten Date: Sun, 2 Feb 2025 12:16:30 +0100 Subject: [PATCH 3/3] more convert --- src/Dimensions/dimension.jl | 17 ++++++++ src/Lookups/lookup_arrays.jl | 79 +++++++++++++++++++++++++++++++----- src/Lookups/lookup_traits.jl | 15 +++++++ src/Lookups/metadata.jl | 5 +-- test/array.jl | 52 ++++++++++++++++++++---- 5 files changed, 146 insertions(+), 22 deletions(-) diff --git a/src/Dimensions/dimension.jl b/src/Dimensions/dimension.jl index 38d07a631..74cdc620b 100644 --- a/src/Dimensions/dimension.jl +++ b/src/Dimensions/dimension.jl @@ -554,3 +554,20 @@ mean(A; dims=Ti) @dim Ti TimeDim "Time" const Time = Ti # For some backwards compat + +function Base.convert(::Type{D1}, dim::D2) where {D1<:Dimension{T},D2} where T + basetypeof(D2) <: basetypeof(D1) || + throw(ArgumentError("Cannot convert $D1 to $D2")) + rebuild(dim, convert(T, val(dim))) +end + +function Base.promote_rule( + ::Type{D1}, ::Type{D2} +) where {D1<:Dimension{T1},D2<:Dimension{T2}} where {T1,T2} + T = promote_type(T1, T2) + if basetypeof(D1) == basetypeof(D2) + basetypeof(D1){T} + else + Dimension{T} + end +end \ No newline at end of file diff --git a/src/Lookups/lookup_arrays.jl b/src/Lookups/lookup_arrays.jl index 9f69dc1cb..1d2a0b25e 100644 --- a/src/Lookups/lookup_arrays.jl +++ b/src/Lookups/lookup_arrays.jl @@ -1,4 +1,3 @@ - """ Lookup @@ -159,11 +158,15 @@ NoLookup() = NoLookup(AutoValues()) rebuild(l::NoLookup; data=parent(l), kw...) = NoLookup(data) +Base.convert(::Type{L1}, lookup::Lookup) where {L1<:NoLookup{A}} where A = + NoLookup(convert(A, axes(lookup, 1))) + # Used in @d broadcasts struct Length1NoLookup <: AbstractNoLookup end Length1NoLookup(::AbstractVector) = Length1NoLookup() rebuild(l::Length1NoLookup; kw...) = Length1NoLookup() + Base.parent(::Length1NoLookup) = Base.OneTo(1) """ @@ -176,7 +179,7 @@ is provided by this package. `AbstractSampled` must have `order`, `span` and `sampling` fields, or a `rebuild` method that accepts them as keyword arguments. """ -abstract type AbstractSampled{T,O<:Order,Sp<:Span,Sa<:Sampling} <: Aligned{T,O} end +abstract type AbstractSampled{T,A,O<:Order,Sp<:Span,Sa<:Sampling,M} <: Aligned{T,O} end span(lookup::AbstractSampled) = lookup.span sampling(lookup::AbstractSampled) = lookup.sampling @@ -192,6 +195,24 @@ function Base.:(==)(l1::AbstractSampled, l2::AbstractSampled) parent(l1) == parent(l2) end +function Base.promote_rule( + ::Type{S1}, ::Type{S2} +) where {S1<:AbstractSampled{T1,A1,O1,Sp1,Sa1,M1}, + S2<:AbstractSampled{T2,A2,O2,Sp2,Sa2,M2} +} where {T1,A1,O1,Sp1,Sa1,M1,T2,A2,O2,Sp2,Sa2,M2} + A = promote_type(A1, A2) + T = eltype(A) + O = promote_type(O1, O2) + Sp = promote_type(Sp1, Sp2) + Sa = promote_type(Sa1, Sa2) + M = promote_type(M1, M2) + if basetypeof(S1) == basetypeof(S2) + basetypeof(S1){T,A,O,Sp,Sa,M} + else + AbstractSampled{T,A,O,Sp,Sa,M} + end +end + for f in (:getindex, :view, :dotview) @eval begin # span may need its step size or bounds updated @@ -306,7 +327,7 @@ A = ones(x, y) 20 1.0 1.0 1.0 1.0 ``` """ -struct Sampled{T,A<:AbstractVector{T},O,Sp,Sa,M} <: AbstractSampled{T,O,Sp,Sa} +struct Sampled{T,A<:AbstractVector{T},O,Sp,Sa,M} <: AbstractSampled{T,A,O,Sp,Sa,M} data::A order::O span::Sp @@ -326,6 +347,17 @@ function rebuild(l::Sampled; Sampled(data, order, span, sampling, metadata) end +function Base.convert( + ::Type{S1}, lookup::AbstractSampled +) where {S1<:Sampled{T,A,O,Sp,Sa,M}} where {T,A,O,Sp,Sa,M} + Sampled(convert(A, parent(lookup)); + order=order(lookup), + span=span(lookup), + sampling=sampling(lookup), + metadata=convert(M, metadata(lookup)), + ) +end + # These are used to specialise dispatch: # When Cycling, we need to modify any `Selector`. After that # we switch to `NotCycling` and use `AbstractSampled` fallbacks. @@ -342,7 +374,7 @@ An abstract supertype for cyclic lookups. These are `AbstractSampled` lookups that are cyclic for `Selectors`. """ -abstract type AbstractCyclic{X,T,O,Sp,Sa} <: AbstractSampled{T,O,Sp,Sa} end +abstract type AbstractCyclic{X,T,A,O,Sp,Sa,M} <: AbstractSampled{T,A,O,Sp,Sa,M} end cycle(l::AbstractCyclic) = l.cycle cycle_status(l::AbstractCyclic) = l.cycle_status @@ -418,7 +450,7 @@ $SAMPLED_ARGUMENTS_DOC leap years breaking correct date cycling of a single year. If you actually need this behaviour, please make a GitHub issue. """ -struct Cyclic{X,T,A<:AbstractVector{T},O,Sp,Sa,M,C} <: AbstractCyclic{X,T,O,Sp,Sa} +struct Cyclic{X,T,A<:AbstractVector{T},O,Sp,Sa,M,C} <: AbstractCyclic{X,T,A,O,Sp,Sa,M} data::A order::O span::Sp @@ -464,18 +496,31 @@ But this can easily be extended, all methods are defined for `AbstractCategorica All `AbstractCategorical` must provide a `rebuild` method with `data`, `order` and `metadata` keyword arguments. """ -abstract type AbstractCategorical{T,O} <: Aligned{T,O} end +abstract type AbstractCategorical{T,A,O,M} <: Aligned{T,O} end order(lookup::AbstractCategorical) = lookup.order metadata(lookup::AbstractCategorical) = lookup.metadata const CategoricalEltypes = Union{AbstractChar,Symbol,AbstractString} +function Base.:(==)(l1::AbstractCategorical, l2::AbstractCategorical) + order(l1) == order(l2) && parent(l1) == parent(l2) +end + +function Base.promote_rule(::Type{S1}, ::Type{S2}) where { + S1<:AbstractCategorical{T1,A1,O1,M1}, S2<:AbstractCategorical{T2,A2,O2,M2} +} where {T1,A1,O1,M1,T2,A2,O2,M2} + T = promote_type(T1, T2) + A = promote_type(A1, A2) + O = promote_type(O1, O2) + M = promote_type(M1, M2) + AbstractCategorical{T,A,O,M} +end + function Adapt.adapt_structure(to, l::AbstractCategorical) rebuild(l; data=Adapt.adapt(to, parent(l)), metadata=NoMetadata()) end - """ Categorical <: AbstractCategorical @@ -533,10 +578,22 @@ function rebuild(l::Categorical; Categorical(data, order, metadata) end -function Base.:(==)(l1::AbstractCategorical, l2::AbstractCategorical) - order(l1) == order(l2) && parent(l1) == parent(l2) +function Base.promote_rule(::Type{S1}, ::Type{S2}) where { + S1<:Categorical{T1,A1,O1,M1}, S2<:Categorical{T2,A2,O2,M2} +} where {T1,A1,O1,M1,T2,A2,O2,M2} + T = promote_type(T1, T2) + A = promote_type(A1, A2) + O = promote_type(O1, O2) + M = promote_type(M1, M2) + Categorical{T,A,O,M} +end +function Base.convert(::Type{S1}, lookup::AbstractCategorical) where { + S1<:Categorical{T,A,O,M} +} where {T,A,O,M} + Categorical(convert(A, parent(lookup)); + order=order(lookup), metadata=convert(M, metadata(lookup)), + ) end - """ Unaligned <: Lookup @@ -971,4 +1028,4 @@ function promote_first(a1::AbstractArray, as::AbstractArray...) end return convert(C, a1) -end +end \ No newline at end of file diff --git a/src/Lookups/lookup_traits.jl b/src/Lookups/lookup_traits.jl index 1325072a5..b33e1fbed 100644 --- a/src/Lookups/lookup_traits.jl +++ b/src/Lookups/lookup_traits.jl @@ -234,6 +234,10 @@ val(span::Regular) = span.step Base.step(span::Regular) = span.step Base.:(==)(l1::Regular, l2::Regular) = val(l1) == val(l2) +Base.promote_rule(::Type{<:Regular{T1}}, ::Type{<:Regular{T2}}) where {T1,T2}= + Regular{promote_type(T1, T2)} +Base.convert(::Type{<:Regular{T1}}, span::Regular{T2}) where {T1,T2} = + Regular(convert(T1, val(span))) """ Irregular <: Span @@ -256,6 +260,13 @@ bounds(span::Irregular) = span.bounds val(span::Irregular) = span.bounds Base.:(==)(l1::Irregular, l2::Irregular) = val(l1) == val(l2) +function Base.promote_rule( + ::Type{<:Irregular{<:Tuple{T1,T2}}}, ::Type{<:Irregular{<:Tuple{T3,T4}}} +) where {T1,T2,T3,T4} + Irregular{Tuple{promote_type(T1, T3), promote_type(T2, T4)}} +end +Base.convert(::Type{Irregular{Tuple{T1,T2}}}, s::Irregular{Tuple{<:Any,<:Any}}) where {T1,T2} = + Irregular(convert(Tuple{T1,T2}, val(s))) """ Explicit(bounds::AbstractMatrix) @@ -272,6 +283,10 @@ Explicit() = Explicit(AutoBounds()) val(span::Explicit) = span.val Base.:(==)(l1::Explicit, l2::Explicit) = val(l1) == val(l2) +Base.promote_rule(::Type{<:Explicit{<:B1}}, ::Type{<:Explicit{<:B2}}) where {B1,B2} = + Explicit{promote_type(B1, B2)} +Base.convert(::Type{<:Explicit{<:B1}}, ::Explicit{<:B2}) where {B1,B2} = + Explicit{promote_type(B1, B2)} Adapt.adapt_structure(to, s::Explicit) = Explicit(Adapt.adapt_structure(to, val(s))) diff --git a/src/Lookups/metadata.jl b/src/Lookups/metadata.jl index 28210dc46..5664d9a6b 100644 --- a/src/Lookups/metadata.jl +++ b/src/Lookups/metadata.jl @@ -87,13 +87,11 @@ struct NoMetadata <: AbstractMetadata{Nothing,Dict{Symbol,Any},Symbol,Any} end val(m::NoMetadata) = NamedTuple() Base.keys(::NoMetadata) = () -Base.haskey(::NoMetadata, args...) = false Base.get(::NoMetadata, key, fallback) = fallback Base.length(::NoMetadata) = 0 -Base.convert(::Type{NoMetadata}, s::Union{AbstractMetadata,AbstractDict}) = +Base.convert(::Type{NoMetadata}, s::Union{NamedTuple,AbstractMetadata,AbstractDict}) = NoMetadata() - Base.convert(::Type{Metadata}, ::NoMetadata) = Metadata() Base.convert(::Type{Metadata}, m::MetadataContents) = Metadata(m) Base.convert(::Type{Metadata{X}}, m::MetadataContents) where X = Metadata{X}(m) @@ -106,6 +104,7 @@ Base.convert(::Type{Metadata{X,T}}, m::NamedTuple) where {X,T<:NamedTuple} = Base.convert(::Type{Metadata{X,T}}, m::AbstractDict) where {X,T<:NamedTuple} = Metadata{X,T}(T(pairs(metadatadict(m)))) + function Base.show(io::IO, mime::MIME"text/plain", metadata::Metadata{N}) where N print(io, "Metadata") if N !== Nothing diff --git a/test/array.jl b/test/array.jl index 30aeb8983..82c04c87f 100644 --- a/test/array.jl +++ b/test/array.jl @@ -611,16 +611,52 @@ end @test promote_type(typeof(a), typeof(b)) == typeof(a) M = fill(UInt16(32000), 2) - D = DimArray(M, X(1:2)) - Dmean = mean([D, D, D]) - @test Dmean == mean([M, M, M]) - D2 = DimArray(M, X(1:2); name=:testname, refdims=(Z(1:1),)) + + x1 = X(1:2) + x2 = X(1.0:2.0) + T = promote_type(typeof(x1), typeof(x2)) + @test convert(T, x2) isa T + @test convert(T, x2) isa T + @test val(convert(T, x2)) === 1.0:1.0:2.0 + f1 = format(x1) + f2 = format(x2) + + P = promote_type(typeof(f1), typeof(f2)) + @test + typeof(convert(P, f1)) + typeof(f2) + @test convert(P, f2) isa typeof(f2) + + @test x2 isa promote_type(typeof(format(x1)), typeof(format(x2))) + + D = DimArray(M, x1) + D2 = DimArray(M, x1; name=:testname) + D3 = DimArray(M, x1; metadata=Metadata(:test => "test")) + D4 = DimArray(M, x2; metadata=Metadata(:test => "test")) + @test mean([D, D]) == + mean([D2, D2]) == + mean([D3, D3]) == + mean([D4, D4]) == + mean([M, M]) + @test mean([D, D]) == mean([D, D2]) == mean([D2, D]) + @test typeof(convert(typeof(D), D2)) == typeof(D) - # @test typeof(convert(typeof(D2), D)) == typeof(D2) + @test typeof(convert(typeof(D), D3)) == typeof(D) + D isa promote_type(typeof(D), typeof(D3)) + # @test + P = promote_type(typeof(D3), typeof(D)) + typeof(convert(P, D4)) <: P + typeof(D4) + <: P + @test typeof(convert(typeof(D2), D)) == typeof(D2) + @test typeof(convert(typeof(D3), D)) == typeof(D3) + @test + typeof(convert(typeof(D4), D)) + == + promote_type(typeof(D4), typeof(D)) + typeof(convert(typeof(D4), D)) + typeof(D4) @test promote_type(typeof(D), typeof(D2)) == - DimVector{UInt16, Tuple{X{Sampled{Int64, UnitRange{Int64}, ForwardOrdered, Regular{Int64}, Points, NoMetadata}}}, Tuple{}, Vector{UInt16}, NoName, NoMetadata} - Dmean = mean([D, D2]) - Dmean = mean([D2, D]) convert(typeof(D), D2) end \ No newline at end of file