From ad58615140ad4923e512f2fd74aed6a7a3f1ec7a Mon Sep 17 00:00:00 2001 From: Ben Arthur Date: Tue, 6 Oct 2026 21:10:00 +0000 Subject: [PATCH] Carry dimension types through the axis interface: dimtype and a 6-arg yaxcreate yaxconvert reduces a source array to dimension names and values, so a target that models dimensions as types, DimensionalData, has to rebuild every axis as Dim{name}(values): a DimArray with a user's `@dim D1` converts to one with Dim{:D1} (JuliaDataCubes/YAXArrays.jl#362). Add an optional interface function `dimtype(x, i)`, the dimension type of axis i or `nothing` when the source has only a name (the fallback), and a six-argument `yaxcreate(T, data, dnames, dtypes, dvals, attributes)` that yaxconvert now calls; its default forwards to the five-argument method, so targets that do not model dimension types are unchanged. The DimensionalData extension defines dimtype(::DimArray, i) as basetypeof(dims(x)[i]) and builds axes as dtypes[i](dvals[i]) when a type is given. This is the interface-level alternative to the direct DimArray <-> YAXArray methods proposed in JuliaDataCubes/YAXArrays.jl#626. Co-Authored-By: Claude Fable 5.1 --- ext/DimensionalDataExt.jl | 14 +++++++++++++- src/YAXArrayBase.jl | 2 +- src/axisarrays/axisinterface.jl | 20 ++++++++++++++++++-- test/arrays.jl | 9 +++++++++ 4 files changed, 41 insertions(+), 4 deletions(-) diff --git a/ext/DimensionalDataExt.jl b/ext/DimensionalDataExt.jl index 8472175..d076d44 100644 --- a/ext/DimensionalDataExt.jl +++ b/ext/DimensionalDataExt.jl @@ -1,6 +1,6 @@ module DimensionalDataExt using DimensionalData: DimArray, DimensionalData, data, Dim, metadata -import YAXArrayBase: dimname, dimnames, dimvals, iscontdim, getattributes, getdata, yaxcreate +import YAXArrayBase: dimname, dimnames, dimvals, dimtype, iscontdim, getattributes, getdata, yaxcreate _dname(::DimensionalData.Dim{N}) where N = N _dname(d::DimensionalData.Dimension) = DimensionalData.name(d) dimname(x::DimArray, i) = _dname(DimensionalData.dims(x)[i]) @@ -8,6 +8,9 @@ dimname(x::DimArray, i) = _dname(DimensionalData.dims(x)[i]) dimvals(x::DimArray,i) = DimensionalData.dims(x)[i].val +# the dimension's type with its parameters stripped: `D1` for a `D1`, `Dim{:lon}` for a `Dim{:lon}` +dimtype(x::DimArray, i) = DimensionalData.basetypeof(DimensionalData.dims(x)[i]) + getdata(x::DimArray) = data(x) getattributes(x::DimArray) = metadata(x) @@ -18,4 +21,13 @@ function yaxcreate(::Type{<:DimArray},data,dnames,dvals,atts) end DimArray(data,d,metadata = atts) end + +# with dimension types from the source where it has them +function yaxcreate(::Type{<:DimArray},data,dnames,dtypes,dvals,atts) + d = ntuple(ndims(data)) do i + T = dtypes[i] + T === nothing ? Dim{Symbol(dnames[i])}(dvals[i]) : T(dvals[i]) + end + DimArray(data,d,metadata = atts) +end end \ No newline at end of file diff --git a/src/YAXArrayBase.jl b/src/YAXArrayBase.jl index 3733948..b2d76d4 100644 --- a/src/YAXArrayBase.jl +++ b/src/YAXArrayBase.jl @@ -26,7 +26,7 @@ end export dimvals, dimname, dimnames, iscontdim, iscompressed, -getattributes, getdata, yaxconvert +getattributes, getdata, yaxconvert, dimtype export get_var_handle, get_varnames, get_var_dims, get_var_attrs, create_empty, add_var, allow_parallel_write, to_dataset, allow_missings diff --git a/src/axisarrays/axisinterface.jl b/src/axisarrays/axisinterface.jl index f380c4b..c767b2a 100644 --- a/src/axisarrays/axisinterface.jl +++ b/src/axisarrays/axisinterface.jl @@ -19,6 +19,16 @@ dimname(::AbstractArray,i) = Symbol("Dim_",i) #Mandatory interface ends here # Optional methods +""" + dimtype(x, i) + +Returns the dimension type of the i-th dimension of the array `x`, for targets that model +dimensions as types (DimensionalData's `X` and `Ti`, or a user's `@dim D1`), or `nothing` +when the array has only a name for it, in which case such targets use `Dim{dimname(x, i)}`. +The fallback returns `nothing`. +""" +dimtype(x, i) = nothing + """ iscontdim(x, i) @@ -78,12 +88,18 @@ Converts an AbstractArray x that implements the interface to type T. """ function yaxconvert(T::Type{<:Any},x) data = getdata(x) - yaxcreate(T, data, dimnames(x),dimvals.(Ref(x),1:ndims(data)),getattributes(x)) + n = ndims(data) + yaxcreate(T, data, dimnames(x), ntuple(i -> dimtype(x, i), n), dimvals.(Ref(x), 1:n), getattributes(x)) end """ yaxcreate(T::Type,data,dnames,dvals,attributes) + yaxcreate(T::Type,data,dnames,dtypes,dvals,attributes) -Creates a new array with the given dimension names, values and attributes. +Creates a new array with the given dimension names, values and attributes. In the +six-argument form `dtypes[i]` is `dimtype(x, i)` of the source: a dimension type to +construct the axis with, or `nothing`. Targets that do not model dimension types need only +implement the five-argument form; the six-argument default forwards to it. """ function yaxcreate(T,data,dname,dvals,attributes) end +yaxcreate(T, data, dnames, dtypes, dvals, attributes) = yaxcreate(T, data, dnames, dvals, attributes) diff --git a/test/arrays.jl b/test/arrays.jl index 0cd12d5..ed3c59e 100644 --- a/test/arrays.jl +++ b/test/arrays.jl @@ -53,6 +53,15 @@ end @test dimvals(d,1) == 0.5:1.0:2.5 @test dimvals(d,2) == 1.5:0.5:3.0 @test getattributes(d) == Dict{String,Any}("a1"=>5, "a2"=>"att") + # a source without dimension types gives `nothing`, and the target then uses Dim{name} + @test dimtype(M(), 1) === nothing + @test typeof(dims(d, 1)) <: Dim{:x} + # a DimArray source keeps its dimension types through the conversion pipeline + DimensionalData.@dim D1 + dd = DimArray(rand(2, 3), (D1(["a", "b"]), X(1:3)); metadata = Dict{String,Any}()) + @test dimtype(dd, 1) === D1 && dimtype(dd, 2) === X + back = yaxconvert(DimArray, dd) + @test typeof(dims(back)) == typeof(dims(dd)) && back == dd end @testitem "ArchGDAL" begin