From c75746d121603556e137cc9cfc97398e0347e1e7 Mon Sep 17 00:00:00 2001 From: Seth Axen Date: Tue, 4 Apr 2023 23:28:45 +0200 Subject: [PATCH 1/7] Add initial implementation of `mergedims` --- src/DimensionalData.jl | 2 +- src/array/array.jl | 72 ++++++++++++++++++++++++++++++++++++++++++ src/stack/stack.jl | 12 +++++++ 3 files changed, 85 insertions(+), 1 deletion(-) diff --git a/src/DimensionalData.jl b/src/DimensionalData.jl index 842596977..51fe7ce31 100644 --- a/src/DimensionalData.jl +++ b/src/DimensionalData.jl @@ -64,7 +64,7 @@ export dims, refdims, metadata, name, lookup, bounds export dimnum, hasdim, hasselection, otherdims # utils -export set, rebuild, reorder, modify, broadcast_dims, broadcast_dims! +export set, rebuild, reorder, mergedims, modify, broadcast_dims, broadcast_dims! const DD = DimensionalData diff --git a/src/array/array.jl b/src/array/array.jl index 5ad4a88cb..19466d23b 100644 --- a/src/array/array.jl +++ b/src/array/array.jl @@ -569,3 +569,75 @@ end # Thed default constructor is DimArray dimconstructor(dims::DimTuple) = dimconstructor(tail(dims)) dimconstructor(dims::Tuple{}) = DimArray + +""" + mergedims(old_dims => new_dim) => Dimension + +Return a dimension `new_dim` whose indices are a [`MergedLookup`](@ref) of the indices of +`old_dims`. +""" +function mergedims((old_dims, new_dim)::Pair) + dims_to_merge = dims(old_dims) + data = vec(DimPoints(dims_to_merge)) + return rebuild(basedims(new_dim), MergedLookup(data, dims_to_merge)) +end + +""" + mergedims(dims, old_dims => new_dim, others::Pair...) => dims_new + +If dimensions `old_dims`, `new_dim`, etc. are found in `dims`, then return new `dims_new` +where all dims in `old_dims` have been combined into a single dim `new_dim`. + +The returned dimension will keep only the name of `new_dim`. Its coords will be a +[`MergedLookup`](@ref) of the coords of the dims in `old_dims`. New dimensions are always +placed at the end of `dims_new`. `others` contains other dimension pairs to be merged. + +# 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 +```` +""" +function mergedims(all_dims, dim_pair::Pair, dim_pairs::Pair...) + old_dims, new_dim = dim_pair + dims_to_merge = dims(all_dims, old_dims) + merged_dim = mergedims(dims_to_merge => new_dim) + all_dims_new = (otherdims(all_dims, dims_to_merge)..., merged_dim) + isempty(dim_pairs) && return all_dims_new + return mergedims(all_dims_new, dim_pairs...) +end + +""" + mergedims(A::AbstractDimArray, dim_pairs::Pair...) => AbstractDimArray + +Return a new array whose dimensions are the result of [`mergedims(dims(A), dim_pairs)`](@ref). +""" +function mergedims(A::AbstractDimArray, dim_pairs::Pair...) + isempty(dim_pairs) && return A + all_dims = dims(A) + dims_to_merge = map(Base.Fix1(dims, A), map(_astuple ∘ first, dim_pairs)) + dims_to_leave = otherdims(all_dims, _cat_tuples(map(_astuple, dims_to_merge)...)) + length(dims_to_leave) == ndims(A) && return A + sizes_unmerged = map(Base.Fix1(size, A), dims_to_leave) + sizes_merged = map(Base.Fix1(prod, Base.Fix1(size, A)), dims_to_merge) + dims_new = mergedims(all_dims, dim_pairs...) + Aperm = PermutedDimsArray(A, _unmergedims(dims_new, map(last, dim_pairs))) + data_merged = reshape(data(Aperm), sizes_unmerged..., sizes_merged...) + rebuild(A, data_merged, dims_new) +end + +function _unmergedims(all_dims, merged_dims) + _merged_dims = dims(all_dims, merged_dims) + unmerged_dims = map(all_dims) do d + hasdim(_merged_dims, d) || return _astuple(d) + return dims(lookup(d)) + end + return _cat_tuples(unmerged_dims...) +end +_unmergedims(all_dims, dim_pairs::Pair...) = _cat_tuples(replace(all_dims, dim_pairs...)) + +_cat_tuples(tuples...) = mapreduce(_astuple, (x, y) -> (x..., y...), tuples) diff --git a/src/stack/stack.jl b/src/stack/stack.jl index 960efbe07..84917a387 100644 --- a/src/stack/stack.jl +++ b/src/stack/stack.jl @@ -159,6 +159,18 @@ for func in (:index, :lookup, :metadata, :sampling, :span, :bounds, :locus, :ord @eval ($func)(s::AbstractDimStack, args...) = ($func)(dims(s), args...) end +""" + mergedims(ds::AbstractDimStack, dim_pairs::Pair...) => AbstractDimStack + +Return a new stack where `mergedims(A, dim_pairs...)` has been applied to each layer `A` of +`ds`. +""" +function mergedims(ds::AbstractDimStack, dim_pairs::Pair...) + isempty(dim_pairs) && return ds + vals = map(da -> mergedims(da, dim_pairs...), layers(ds)) + rebuild_from_arrays(ds, vals) +end + """ DimStack <: AbstractDimStack From 90587981eabff210f00fb97223147c7861b5fdfb Mon Sep 17 00:00:00 2001 From: Seth Axen Date: Thu, 6 Apr 2023 00:34:08 +0200 Subject: [PATCH 2/7] Use values instead of layers --- src/stack/stack.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/stack/stack.jl b/src/stack/stack.jl index 84917a387..0324c187f 100644 --- a/src/stack/stack.jl +++ b/src/stack/stack.jl @@ -167,7 +167,7 @@ Return a new stack where `mergedims(A, dim_pairs...)` has been applied to each l """ function mergedims(ds::AbstractDimStack, dim_pairs::Pair...) isempty(dim_pairs) && return ds - vals = map(da -> mergedims(da, dim_pairs...), layers(ds)) + vals = map(A -> mergedims(A, dim_pairs...), values(ds)) rebuild_from_arrays(ds, vals) end From 63627dcefb6831d3193d48056028c560d8467a78 Mon Sep 17 00:00:00 2001 From: Seth Axen Date: Thu, 6 Apr 2023 00:34:24 +0200 Subject: [PATCH 3/7] Simplify base method --- src/array/array.jl | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/array/array.jl b/src/array/array.jl index 19466d23b..ade742a75 100644 --- a/src/array/array.jl +++ b/src/array/array.jl @@ -577,9 +577,8 @@ Return a dimension `new_dim` whose indices are a [`MergedLookup`](@ref) of the i `old_dims`. """ function mergedims((old_dims, new_dim)::Pair) - dims_to_merge = dims(old_dims) - data = vec(DimPoints(dims_to_merge)) - return rebuild(basedims(new_dim), MergedLookup(data, dims_to_merge)) + data = vec(DimPoints(_astuple(old_dims))) + return rebuild(basedims(new_dim), MergedLookup(data, old_dims)) end """ From ccb0f71e04216d7caa33dbe1e3aefcc03e8b982d Mon Sep 17 00:00:00 2001 From: Seth Axen Date: Thu, 6 Apr 2023 00:34:51 +0200 Subject: [PATCH 4/7] Perform all checks in main method --- src/array/array.jl | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/src/array/array.jl b/src/array/array.jl index ade742a75..2b13c8766 100644 --- a/src/array/array.jl +++ b/src/array/array.jl @@ -601,13 +601,35 @@ 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(all_dims, dim_pair::Pair, dim_pairs::Pair...) +function mergedims(all_dims, dim_pairs::Pair...) + # filter out dims completely missing + dim_pairs_complete = filter(dim_pairs) do (old_dims,) + dims_present = dims(all_dims, _astuple(old_dims)) + isempty(dims_present) && return false + all(hasdim(dims_present, old_dims)) || throw(ArgumentError( + "Not all dimensions $old_dims found in $(map(basetypeof, all_dims))" + )) + return true + end + isempty(dim_pairs_complete) && return all_dims + dim_pairs_concrete = map(dim_pairs_complete) do (old_dims, new_dim) + return dims(all_dims, _astuple(old_dims)) => new_dim + end + # throw error if old dim groups overlap + old_dims_tuples = map(first, dim_pairs_concrete) + if !dimsmatch(_cat_tuples(old_dims_tuples...), combinedims(old_dims_tuples...)) + throw(ArgumentError("Dimensions to be merged are not all unique")) + end + return _mergedims(all_dims, dim_pairs_concrete...) +end + +function _mergedims(all_dims, dim_pair::Pair, dim_pairs::Pair...) old_dims, new_dim = dim_pair - dims_to_merge = dims(all_dims, old_dims) + dims_to_merge = dims(all_dims, _astuple(old_dims)) merged_dim = mergedims(dims_to_merge => new_dim) all_dims_new = (otherdims(all_dims, dims_to_merge)..., merged_dim) isempty(dim_pairs) && return all_dims_new - return mergedims(all_dims_new, dim_pairs...) + return _mergedims(all_dims_new, dim_pairs...) end """ From 61a5a2ad86513ad7a7cd44b0dd13c3c8b985f780 Mon Sep 17 00:00:00 2001 From: Seth Axen Date: Thu, 6 Apr 2023 00:35:04 +0200 Subject: [PATCH 5/7] Simplify mergedims for arrays --- src/array/array.jl | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/array/array.jl b/src/array/array.jl index 2b13c8766..abadcad66 100644 --- a/src/array/array.jl +++ b/src/array/array.jl @@ -640,14 +640,11 @@ Return a new array whose dimensions are the result of [`mergedims(dims(A), dim_p function mergedims(A::AbstractDimArray, dim_pairs::Pair...) isempty(dim_pairs) && return A all_dims = dims(A) - dims_to_merge = map(Base.Fix1(dims, A), map(_astuple ∘ first, dim_pairs)) - dims_to_leave = otherdims(all_dims, _cat_tuples(map(_astuple, dims_to_merge)...)) - length(dims_to_leave) == ndims(A) && return A - sizes_unmerged = map(Base.Fix1(size, A), dims_to_leave) - sizes_merged = map(Base.Fix1(prod, Base.Fix1(size, A)), dims_to_merge) dims_new = mergedims(all_dims, dim_pairs...) - Aperm = PermutedDimsArray(A, _unmergedims(dims_new, map(last, dim_pairs))) - data_merged = reshape(data(Aperm), sizes_unmerged..., sizes_merged...) + 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) end From 8c06a4393bba1758871423a0f5fee6e8137ad650 Mon Sep 17 00:00:00 2001 From: Seth Axen Date: Thu, 6 Apr 2023 00:35:57 +0200 Subject: [PATCH 6/7] Handle case where different numbers of dims provided --- src/Dimensions/primitives.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Dimensions/primitives.jl b/src/Dimensions/primitives.jl index c6c2c1dd5..9a6afcb5f 100644 --- a/src/Dimensions/primitives.jl +++ b/src/Dimensions/primitives.jl @@ -12,6 +12,7 @@ or are at least rotations/transformations of the same type. """ @inline dimsmatch(dims, query) = dimsmatch(<:, dims, query) @inline function dimsmatch(f::Function, dims::Tuple, query::Tuple) + 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)) From 5d8123f718b3abd51c1e31534a8812ab0f0a105e Mon Sep 17 00:00:00 2001 From: Seth Axen Date: Thu, 6 Apr 2023 00:38:34 +0200 Subject: [PATCH 7/7] Add mergedims to docs --- docs/src/api.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/src/api.md b/docs/src/api.md index 5cf88794c..e0673a21c 100644 --- a/docs/src/api.md +++ b/docs/src/api.md @@ -50,6 +50,7 @@ rebuild modify broadcast_dims broadcast_dims! +mergedims reorder Base.cat Base.map