From a8caa48e5156b59283647c1a64ef9d4db569484b Mon Sep 17 00:00:00 2001 From: Matt Fishman Date: Wed, 25 Dec 2024 11:49:20 -0500 Subject: [PATCH] Rewrite (#9) --- Project.toml | 14 +- README.md | 8 +- TODO.md | 26 + examples/Project.toml | 1 - examples/README.jl | 8 +- .../NamedDimsArraysAdaptExt.jl | 3 - .../adapt_structure.jl | 6 - .../NamedDimsArraysSparseArraysBaseExt.jl | 3 - .../densearray.jl | 8 - .../NamedDimsArraysTensorAlgebraExt.jl | 5 - .../contract.jl | 31 - ext/NamedDimsArraysTensorAlgebraExt/qr.jl | 17 - src/NamedDimsArrays.jl | 26 +- src/abstractnameddimsarray.jl | 556 +++++++++++++----- src/abstractnameddimsmatrix.jl | 1 - src/abstractnameddimsvector.jl | 1 - src/abstractnamedint.jl | 47 -- src/abstractnamedinteger.jl | 106 ++++ src/abstractnamedunitrange.jl | 72 ++- src/adapt.jl | 5 + src/broadcast.jl | 49 -- src/broadcast_shape.jl | 24 - src/constructors.jl | 48 -- src/isnamed.jl | 15 + src/map.jl | 14 - src/name.jl | 2 - src/nameddimsarray.jl | 124 +--- src/namedint.jl | 28 - src/namedinteger.jl | 8 + src/namedunitrange.jl | 9 +- src/permutedims.jl | 4 - src/promote_shape.jl | 12 - src/randname.jl | 14 +- src/similar.jl | 49 -- .../fusedims.jl => src/tensoralgebra.jl | 72 ++- src/traits.jl | 1 - test/basics/test_adapt.jl | 11 + test/basics/test_basic.jl | 250 ++++++++ .../test_tensoralgebra.jl} | 30 +- test/test_NamedDimsArraysAdaptExt.jl | 12 - ...test_NamedDimsArraysSparseArraysBaseExt.jl | 11 - test/test_basic.jl | 106 ---- test/test_tensoralgebra.jl | 78 --- 43 files changed, 1024 insertions(+), 891 deletions(-) create mode 100644 TODO.md delete mode 100644 ext/NamedDimsArraysAdaptExt/NamedDimsArraysAdaptExt.jl delete mode 100644 ext/NamedDimsArraysAdaptExt/adapt_structure.jl delete mode 100644 ext/NamedDimsArraysSparseArraysBaseExt/NamedDimsArraysSparseArraysBaseExt.jl delete mode 100644 ext/NamedDimsArraysSparseArraysBaseExt/densearray.jl delete mode 100644 ext/NamedDimsArraysTensorAlgebraExt/NamedDimsArraysTensorAlgebraExt.jl delete mode 100644 ext/NamedDimsArraysTensorAlgebraExt/contract.jl delete mode 100644 ext/NamedDimsArraysTensorAlgebraExt/qr.jl delete mode 100644 src/abstractnameddimsmatrix.jl delete mode 100644 src/abstractnameddimsvector.jl delete mode 100644 src/abstractnamedint.jl create mode 100644 src/abstractnamedinteger.jl create mode 100644 src/adapt.jl delete mode 100644 src/broadcast.jl delete mode 100644 src/broadcast_shape.jl delete mode 100644 src/constructors.jl create mode 100644 src/isnamed.jl delete mode 100644 src/map.jl delete mode 100644 src/name.jl delete mode 100644 src/namedint.jl create mode 100644 src/namedinteger.jl delete mode 100644 src/permutedims.jl delete mode 100644 src/promote_shape.jl delete mode 100644 src/similar.jl rename ext/NamedDimsArraysTensorAlgebraExt/fusedims.jl => src/tensoralgebra.jl (51%) delete mode 100644 src/traits.jl create mode 100644 test/basics/test_adapt.jl create mode 100644 test/basics/test_basic.jl rename test/{test_NamedDimsArraysTensorAlgebraExt.jl => basics/test_tensoralgebra.jl} (64%) delete mode 100644 test/test_NamedDimsArraysAdaptExt.jl delete mode 100644 test/test_NamedDimsArraysSparseArraysBaseExt.jl delete mode 100644 test/test_basic.jl delete mode 100644 test/test_tensoralgebra.jl diff --git a/Project.toml b/Project.toml index 33d1d1f..dcced62 100644 --- a/Project.toml +++ b/Project.toml @@ -1,25 +1,25 @@ name = "NamedDimsArrays" uuid = "60cbd0c0-df58-4cb7-918c-6f5607b73fde" authors = ["ITensor developers and contributors"] -version = "0.1.0" +version = "0.2.0" [deps] Adapt = "79e6a3ab-5dfb-504d-930d-738a2a938a0e" BroadcastMapConversion = "4a4adec5-520f-4750-bb37-d5e66b4ddeb2" +Derive = "a07dfc7f-7d04-4eb5-84cc-a97f051f655a" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" -NestedPermutedDimsArrays = "2c2a8ec4-3cfc-4276-aa3e-1307b4294e58" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" -SparseArraysBase = "0d5efcca-f356-4864-8770-e1ed8d78f208" +SimpleTraits = "699a6c99-e7fa-54fc-8d76-47d257e15c1d" TensorAlgebra = "68bd88dc-f39d-4e12-b2ca-f046b68fcc6a" TypeParameterAccessors = "7e5a90cf-f82e-492e-a09b-e3e26432c138" [compat] Adapt = "4.1.1" -BroadcastMapConversion = "0.1" +BroadcastMapConversion = "0.1.2" +Derive = "0.3.6" LinearAlgebra = "1.10" -NestedPermutedDimsArrays = "0.1" Random = "1.10" -SparseArraysBase = "0.1" +SimpleTraits = "0.9.4" TensorAlgebra = "0.1" -TypeParameterAccessors = "0.1" +TypeParameterAccessors = "0.2" julia = "1.10" diff --git a/README.md b/README.md index d48acc1..ecc0fd9 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ julia> Pkg.add("NamedDimsArrays") ## Examples ````julia -using NamedDimsArrays: align, dimnames, named, unname +using NamedDimsArrays: aligndims, dename, dimnames, named using TensorAlgebra: contract # Named dimensions @@ -53,11 +53,11 @@ na2 = randn(j, k) na_dest = contract(na1, na2) @show issetequal(dimnames(na_dest), ("i", "k")) -# `unname` removes the names and returns an `Array` -@show unname(na_dest, (i, k)) ≈ unname(na1) * unname(na2) +# `dename` removes the names and returns an `Array` +@show dename(na_dest, (i, k)) ≈ dename(na1) * dename(na2) # Permute dimensions (like `ITensors.permute`) -na1 = align(na1, (j, i)) +na1 = aligndims(na1, (j, i)) @show na1[i => 1, j => 2] == na1[2, 1] ```` diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..5208a5b --- /dev/null +++ b/TODO.md @@ -0,0 +1,26 @@ +- `svd`, `eigen` (including tensor versions) +- `reshape`, `vec` +- `swapdimnames` +- `mapdimnames(f, a::AbstractNamedDimsArray)` (rename `replacedimnames(f, a)` to `mapdimnames(f, a)`, or have both?) +- `cat` (define `CatName` as a combination of the input names?). +- `canonize`/`flatten_array_wrappers` (https://github.com/mcabbott/NamedPlus.jl/blob/v0.0.5/src/permute.jl#L207) + - `nameddims(PermutedDimsArray(a, perm), dimnames)` -> `nameddims(a, dimnames[invperm(perm)])` + - `nameddims(transpose(a), dimnames)` -> `nameddims(a, reverse(dimnames))` + - `Transpose(nameddims(a, dimnames))` -> `nameddims(a, reverse(dimnames))` + - etc. +- `MappedName(old_name, name)`, acts like `Name(name)` but keeps track of the old name. + - `namedmap(a, ::Pair...)`: `namedmap(named(randn(2, 2, 2, 2), i, j, k, l), i => k, j => l)` + represents that the names map back and forth to each other for the sake of `transpose`, + `tr`, `eigen`, etc. Operators are generally `namedmap(named(randn(2, 2), i, i'), i => i')`. +- `prime(:i) = PrimedName(:i)`, `prime(:i, 2) = PrimedName(:i, 2)`, `prime(prime(:i)) = PrimedName(:i, 2)`, + `Name(:i)' = prime(:i)`, etc. +- `transpose`/`adjoint` based on `swapdimnames` and `MappedName(old_name, new_name)`. + - `adjoint` could make use of a lazy `ConjArray`. + - `transpose(a, dimname1 => dimname1′, dimname2 => dimname2′)` like `https://github.com/mcabbott/NamedPlus.jl`. + - Same as `replacedims(a, dimname1 => dimname1′, dimname1′ => dimname1, dimname2 => dimname2′, dimname2′ => dimname2)`. + - `transpose(f, a)` like the function form of `replace`. +- `tr` based on `MappedName(old_name, name)`. +- Slicing: `nameddims(a, "i", "j")[1:2, 1:2] = nameddims(a[1:2, 1:2], Name(named(1:2, "i")), Name(named(1:2, "j")))`, i.e. + the parent gets sliced and the new dimensions names are the named slice. + - Should `NamedDimsArray` store the named axes rather than just the dimension names? + - Should `NamedDimsArray` have special axes types so that `axes(nameddims(a, "i", "j")) == axes(nameddims(a', "j", "i"))`? diff --git a/examples/Project.toml b/examples/Project.toml index 659e750..a8e200e 100644 --- a/examples/Project.toml +++ b/examples/Project.toml @@ -1,7 +1,6 @@ [deps] BroadcastMapConversion = "4a4adec5-520f-4750-bb37-d5e66b4ddeb2" NamedDimsArrays = "60cbd0c0-df58-4cb7-918c-6f5607b73fde" -NestedPermutedDimsArrays = "2c2a8ec4-3cfc-4276-aa3e-1307b4294e58" SparseArraysBase = "0d5efcca-f356-4864-8770-e1ed8d78f208" TensorAlgebra = "68bd88dc-f39d-4e12-b2ca-f046b68fcc6a" TypeParameterAccessors = "7e5a90cf-f82e-492e-a09b-e3e26432c138" diff --git a/examples/README.jl b/examples/README.jl index d9758f8..7d48576 100644 --- a/examples/README.jl +++ b/examples/README.jl @@ -37,7 +37,7 @@ julia> Pkg.add("NamedDimsArrays") # ## Examples -using NamedDimsArrays: align, dimnames, named, unname +using NamedDimsArrays: aligndims, dename, dimnames, named using TensorAlgebra: contract ## Named dimensions @@ -58,9 +58,9 @@ na2 = randn(j, k) na_dest = contract(na1, na2) @show issetequal(dimnames(na_dest), ("i", "k")) -## `unname` removes the names and returns an `Array` -@show unname(na_dest, (i, k)) ≈ unname(na1) * unname(na2) +## `dename` removes the names and returns an `Array` +@show dename(na_dest, (i, k)) ≈ dename(na1) * dename(na2) ## Permute dimensions (like `ITensors.permute`) -na1 = align(na1, (j, i)) +na1 = aligndims(na1, (j, i)) @show na1[i => 1, j => 2] == na1[2, 1] diff --git a/ext/NamedDimsArraysAdaptExt/NamedDimsArraysAdaptExt.jl b/ext/NamedDimsArraysAdaptExt/NamedDimsArraysAdaptExt.jl deleted file mode 100644 index 027dd65..0000000 --- a/ext/NamedDimsArraysAdaptExt/NamedDimsArraysAdaptExt.jl +++ /dev/null @@ -1,3 +0,0 @@ -module NamedDimsArraysAdaptExt -include("adapt_structure.jl") -end diff --git a/ext/NamedDimsArraysAdaptExt/adapt_structure.jl b/ext/NamedDimsArraysAdaptExt/adapt_structure.jl deleted file mode 100644 index 23de652..0000000 --- a/ext/NamedDimsArraysAdaptExt/adapt_structure.jl +++ /dev/null @@ -1,6 +0,0 @@ -using Adapt: Adapt, adapt -using ..NamedDimsArrays: AbstractNamedDimsArray, dimnames, named, unname - -function Adapt.adapt_structure(to, na::AbstractNamedDimsArray) - return named(adapt(to, unname(na)), dimnames(na)) -end diff --git a/ext/NamedDimsArraysSparseArraysBaseExt/NamedDimsArraysSparseArraysBaseExt.jl b/ext/NamedDimsArraysSparseArraysBaseExt/NamedDimsArraysSparseArraysBaseExt.jl deleted file mode 100644 index aef726d..0000000 --- a/ext/NamedDimsArraysSparseArraysBaseExt/NamedDimsArraysSparseArraysBaseExt.jl +++ /dev/null @@ -1,3 +0,0 @@ -module NamedDimsArraysSparseArraysBaseExt -include("densearray.jl") -end diff --git a/ext/NamedDimsArraysSparseArraysBaseExt/densearray.jl b/ext/NamedDimsArraysSparseArraysBaseExt/densearray.jl deleted file mode 100644 index 9370ad0..0000000 --- a/ext/NamedDimsArraysSparseArraysBaseExt/densearray.jl +++ /dev/null @@ -1,8 +0,0 @@ -using ..NamedDimsArrays: AbstractNamedDimsArray, dimnames, named, unname -using SparseArraysBase: SparseArraysBase, densearray - -# TODO: Use `Adapt` or some kind of rewrap function like in -# ArrayInterface.jl (https://github.com/JuliaArrays/ArrayInterface.jl/issues/136) -function SparseArraysBase.densearray(na::AbstractNamedDimsArray) - return named(densearray(unname(na)), dimnames(na)) -end diff --git a/ext/NamedDimsArraysTensorAlgebraExt/NamedDimsArraysTensorAlgebraExt.jl b/ext/NamedDimsArraysTensorAlgebraExt/NamedDimsArraysTensorAlgebraExt.jl deleted file mode 100644 index 42000e0..0000000 --- a/ext/NamedDimsArraysTensorAlgebraExt/NamedDimsArraysTensorAlgebraExt.jl +++ /dev/null @@ -1,5 +0,0 @@ -module NamedDimsArraysTensorAlgebraExt -include("contract.jl") -include("fusedims.jl") -include("qr.jl") -end diff --git a/ext/NamedDimsArraysTensorAlgebraExt/contract.jl b/ext/NamedDimsArraysTensorAlgebraExt/contract.jl deleted file mode 100644 index 598aa3a..0000000 --- a/ext/NamedDimsArraysTensorAlgebraExt/contract.jl +++ /dev/null @@ -1,31 +0,0 @@ -using ..NamedDimsArrays: AbstractNamedDimsArray, dimnames, named, unname -using TensorAlgebra: TensorAlgebra, blockedperms, contract, contract! - -function TensorAlgebra.contract!( - na_dest::AbstractNamedDimsArray, - na1::AbstractNamedDimsArray, - na2::AbstractNamedDimsArray, - α::Number=true, - β::Number=false, -) - contract!( - unname(na_dest), - dimnames(na_dest), - unname(na1), - dimnames(na1), - unname(na2), - dimnames(na2), - α, - β, - ) - return na_dest -end - -function TensorAlgebra.contract( - na1::AbstractNamedDimsArray, na2::AbstractNamedDimsArray, α::Number=true -) - a_dest, dimnames_dest = contract( - unname(na1), dimnames(na1), unname(na2), dimnames(na2), α - ) - return named(a_dest, dimnames_dest) -end diff --git a/ext/NamedDimsArraysTensorAlgebraExt/qr.jl b/ext/NamedDimsArraysTensorAlgebraExt/qr.jl deleted file mode 100644 index 660e245..0000000 --- a/ext/NamedDimsArraysTensorAlgebraExt/qr.jl +++ /dev/null @@ -1,17 +0,0 @@ -using LinearAlgebra: LinearAlgebra, qr -using ..NamedDimsArrays: AbstractNamedDimsArray, dimnames, name, randname, unname - -function LinearAlgebra.qr(na::AbstractNamedDimsArray; positive=nothing) - return qr(na, (dimnames(na, 1),), (dimnames(na, 2),); positive) -end - -function LinearAlgebra.qr( - na::AbstractNamedDimsArray, labels_codomain::Tuple, labels_domain::Tuple; positive=nothing -) - @assert isnothing(positive) || !positive - q, r = qr(unname(na), dimnames(na), name.(labels_codomain), name.(labels_domain)) - name_qr = randname(dimnames(na)[1]) - dimnames_q = (name.(labels_codomain)..., name_qr) - dimnames_r = (name_qr, name.(labels_domain)...) - return named(q, dimnames_q), named(r, dimnames_r) -end diff --git a/src/NamedDimsArrays.jl b/src/NamedDimsArrays.jl index f776a2f..d3fc0b9 100644 --- a/src/NamedDimsArrays.jl +++ b/src/NamedDimsArrays.jl @@ -1,28 +1,14 @@ module NamedDimsArrays -include("traits.jl") -include("name.jl") +include("isnamed.jl") include("randname.jl") -include("abstractnamedint.jl") +include("abstractnamedinteger.jl") +include("namedinteger.jl") include("abstractnamedunitrange.jl") -include("abstractnameddimsarray.jl") -include("abstractnameddimsmatrix.jl") -include("abstractnameddimsvector.jl") -include("namedint.jl") include("namedunitrange.jl") +include("abstractnameddimsarray.jl") +include("adapt.jl") +include("tensoralgebra.jl") include("nameddimsarray.jl") -include("constructors.jl") -include("similar.jl") -include("permutedims.jl") -include("promote_shape.jl") -include("map.jl") -include("broadcast_shape.jl") -include("broadcast.jl") - -# Extensions. -# TODO: Turn these into actual package extensions. -include("../ext/NamedDimsArraysAdaptExt/NamedDimsArraysAdaptExt.jl") -include("../ext/NamedDimsArraysSparseArraysBaseExt/NamedDimsArraysSparseArraysBaseExt.jl") -include("../ext/NamedDimsArraysTensorAlgebraExt/NamedDimsArraysTensorAlgebraExt.jl") end diff --git a/src/abstractnameddimsarray.jl b/src/abstractnameddimsarray.jl index 411d146..f2320a6 100644 --- a/src/abstractnameddimsarray.jl +++ b/src/abstractnameddimsarray.jl @@ -1,82 +1,177 @@ -using TypeParameterAccessors: TypeParameterAccessors, parenttype +using Derive: Derive, @derive, AbstractArrayInterface # Some of the interface is inspired by: +# https://github.com/ITensor/ITensors.jl # https://github.com/invenia/NamedDims.jl # https://github.com/mcabbott/NamedPlus.jl -abstract type AbstractNamedDimsArray{T,N,Parent,Names} <: AbstractArray{T,N} end +abstract type AbstractNamedDimsArrayInterface <: AbstractArrayInterface end -# Required interface +struct NamedDimsArrayInterface <: AbstractNamedDimsArrayInterface end -# Output the names. -# TODO: Define for `AbstractArray`. -dimnames(a::AbstractNamedDimsArray) = error("Not implemented") +abstract type AbstractNamedDimsArray{T,N} <: AbstractArray{T,N} end +const AbstractNamedDimsVector{T} = AbstractNamedDimsArray{T,1} +const AbstractNamedDimsMatrix{T} = AbstractNamedDimsArray{T,2} + +Derive.interface(::Type{<:AbstractNamedDimsArray}) = AbstractNamedDimsArrayInterface() + +# Output the dimension names. +dimnames(a::AbstractArray) = throw(MethodError(dimnames, Tuple{typeof(a)})) # Unwrapping the names -Base.parent(::AbstractNamedDimsArray) = error("Not implemented") +Base.parent(a::AbstractNamedDimsArray) = throw(MethodError(parent, Tuple{typeof(a)})) -## TODO remove TypeParameterAccessors when SetParameters is removed -function TypeParameterAccessors.position( - ::Type{<:AbstractNamedDimsArray}, ::typeof(parenttype) -) - return TypeParameterAccessors.Position(3) -end - -# Set the names of an unnamed AbstractArray -# `ndims(a) == length(names)` -# This is a constructor -## named(a::AbstractArray, names) = error("Not implemented") - -dimnames(a::AbstractNamedDimsArray, i::Int) = dimnames(a)[i] - -# Traits -# TODO: Define for `AbstractArray`. -# TODO: Define a trait type `IsNamed`. -isnamed(::AbstractNamedDimsArray) = true - -# AbstractArray interface -# TODO: Use `unname` instead of `parent`? - -# Helper function, move to `utils.jl`. -named_tuple(t::Tuple, names) = ntuple(i -> named(t[i], names[i]), length(t)) - -# TODO: Should `axes` output named axes or not? -# TODO: Use the proper type, `namedaxistype(a)`. -# Base.axes(a::AbstractNamedDimsArray) = named_tuple(axes(unname(a)), dimnames(a)) -Base.axes(a::AbstractNamedDimsArray) = axes(unname(a)) -namedaxes(a::AbstractNamedDimsArray) = named.(axes(unname(a)), dimnames(a)) -# TODO: Use the proper type, `namedlengthtype(a)`. -Base.size(a::AbstractNamedDimsArray) = size(unname(a)) -namedsize(a::AbstractNamedDimsArray) = named.(size(unname(a)), dimnames(a)) -Base.getindex(a::AbstractNamedDimsArray, I...) = unname(a)[I...] -function Base.setindex!(a::AbstractNamedDimsArray, x, I...) - unname(a)[I...] = x - return a +dimnames(a::AbstractArray, dim::Int) = dimnames(a)[dim] + +dim(a::AbstractArray, n) = findfirst(==(name(n)), dimnames(a)) +dims(a::AbstractArray, ns) = map(n -> dim(a, n), ns) + +# Unwrapping the names (`NamedDimsArrays.jl` interface). +# TODO: Use `IsNamed` trait? +dename(a::AbstractNamedDimsArray) = parent(a) +function dename(a::AbstractNamedDimsArray, dimnames) + return dename(aligndims(a, dimnames)) +end +function denamed(a::AbstractNamedDimsArray, dimnames) + return dename(aligneddims(a, dimnames)) +end + +unname(a::AbstractArray, dimnames) = dename(a, dimnames) +unnamed(a::AbstractArray, dimnames) = denamed(a, dimnames) + +isnamed(::Type{<:AbstractNamedDimsArray}) = true + +# Can overload this to get custom named dims array wrapper +# depending on the dimension name types, for example +# output an `ITensor` if the dimension names are `IndexName`s. +@traitfn function nameddims(a::AbstractArray::!(IsNamed), dims) + dimnames = name.(dims) + # TODO: Check the shape of `dename.(dims)` matches the shape of `a`. + # `mapreduce(typeof, promote_type, xs) == Base.promote_typeof(xs...)`. + return nameddimstype(eltype(dimnames))(a, dimnames) +end +@traitfn function nameddims(a::AbstractArray::IsNamed, dims) + return aligneddims(a, dims) +end + +function Base.view(a::AbstractArray, dimnames::AbstractName...) + return nameddims(a, dimnames) +end +function Base.getindex(a::AbstractArray, dimnames::AbstractName...) + return copy(@view(a[dimnames...])) +end + +Base.copy(a::AbstractNamedDimsArray) = nameddims(copy(dename(a)), dimnames(a)) + +# Can overload this to get custom named dims array wrapper +# depending on the dimension name types, for example +# output an `ITensor` if the dimension names are `IndexName`s. +nameddimstype(dimnametype::Type) = NamedDimsArray + +Base.axes(a::AbstractNamedDimsArray) = map(named, axes(dename(a)), dimnames(a)) +Base.size(a::AbstractNamedDimsArray) = map(named, size(dename(a)), dimnames(a)) + +Base.axes(a::AbstractArray, dimname::AbstractName) = axes(a, dim(a, dimname)) +Base.size(a::AbstractArray, dimname::AbstractName) = size(a, dim(a, dimname)) + +setdimnames(a::AbstractNamedDimsArray, dimnames) = nameddims(dename(a), name.(dimnames)) +function replacedimnames(f, a::AbstractNamedDimsArray) + return setdimnames(a, replace(f, dimnames(a))) +end +function replacedimnames(a::AbstractNamedDimsArray, replacements::Pair...) + replacement_names = map(replacements) do replacement + name(first(replacement)) => name(last(replacement)) + end + new_dimnames = replace(dimnames(a), replacement_names...) + return setdimnames(a, new_dimnames) +end + +# `Base.isempty(a::AbstractArray)` is defined as `length(a) == 0`, +# which involves comparing a named integer to an unnamed integer +# which isn't well defined. +Base.isempty(a::AbstractNamedDimsArray) = isempty(dename(a)) + +# Define this on objects rather than types in case the wrapper type +# isn't known at compile time, like for the ITensor type. +Base.IndexStyle(a::AbstractNamedDimsArray) = IndexStyle(dename(a)) +Base.eachindex(a::AbstractNamedDimsArray) = eachindex(dename(a)) + +# Cartesian indices with names attached. +struct NamedIndexCartesian <: IndexStyle end + +# When multiple named dims arrays are involved, use the named +# dimensions. +function Base.IndexStyle(a1::AbstractNamedDimsArray, a2::AbstractNamedDimsArray) + return NamedIndexCartesian() +end +# Define promotion of index styles. +Base.IndexStyle(s1::NamedIndexCartesian, s2::NamedIndexCartesian) = NamedIndexCartesian() +Base.IndexStyle(s1::IndexStyle, s2::NamedIndexCartesian) = NamedIndexCartesian() +Base.IndexStyle(s1::NamedIndexCartesian, s2::IndexStyle) = NamedIndexCartesian() + +# Like CartesianIndex but with named dimensions. +struct NamedCartesianIndex{N,Index<:Tuple{Vararg{AbstractNamedInteger,N}}} <: + Base.AbstractCartesianIndex{N} + I::Index +end +NamedCartesianIndex(I::AbstractNamedInteger...) = NamedCartesianIndex(I) +Base.Tuple(I::NamedCartesianIndex) = I.I +function Base.show(io::IO, I::NamedCartesianIndex) + print(io, "NamedCartesianIndex") + show(io, Tuple(I)) + return nothing end -# Derived interface +# Like CartesianIndices but with named dimensions. +struct NamedCartesianIndices{ + N, + Indices<:Tuple{Vararg{AbstractNamedUnitRange,N}}, + Index<:Tuple{Vararg{AbstractNamedInteger,N}}, +} <: AbstractNamedDimsArray{NamedCartesianIndex{N,Index},N} + indices::Indices + function NamedCartesianIndices(indices::Tuple{Vararg{AbstractNamedUnitRange}}) + return new{length(indices),typeof(indices),Tuple{eltype.(indices)...}}(indices) + end +end + +Base.axes(I::NamedCartesianIndices) = map(only ∘ axes, I.indices) +Base.size(I::NamedCartesianIndices) = length.(I.indices) + +function Base.getindex(a::NamedCartesianIndices{N}, I::Vararg{Int,N}) where {N} + index = map(a.indices, I) do r, i + return getindex(r, i) + end + return NamedCartesianIndex(index) +end -# Output the names. -# TODO: Define for `AbstractArray`. -dimname(a::AbstractNamedDimsArray, i) = dimnames(a)[i] +dimnames(I::NamedCartesianIndices) = name.(I.indices) +function Base.parent(I::NamedCartesianIndices) + return CartesianIndices(dename.(I.indices)) +end -# Renaming -# Unname and set new naems -# TODO: Define for `AbstractArray`. -rename(a::AbstractNamedDimsArray, names) = named(unname(a), names) +function Base.eachindex(::NamedIndexCartesian, a1::AbstractArray, a_rest::AbstractArray...) + all(a -> issetequal(dimnames(a1), dimnames(a)), a_rest) || + throw(NameMismatch("Dimension name mismatch $(dimnames.((a1, a_rest...))).")) + # TODO: Check the shapes match. + return NamedCartesianIndices(axes(a1)) +end -# replacenames(a, :i => :a, :j => :b) -# `rename` in `NamedPlus.jl`. -# TODO: Define for `AbstractArray`. -function replacenames(na::AbstractNamedDimsArray, replacements::Pair...) - return named(unname(na), replace(dimnames(na), replacements...)) +# Base version ignores dimension names. +# TODO: Use `mapreduce(isequal, &&, a1, a2)`? +function Base.isequal(a1::AbstractNamedDimsArray, a2::AbstractNamedDimsArray) + return all(eachindex(a1, a2)) do I + isequal(a1[I], a2[I]) + end end -# Either define new names or replace names -# TODO: Define for `AbstractArray`, use `isnamed` trait -# to add names or replace names. -setnames(a::AbstractArray, names) = named(a, names) -setnames(a::AbstractNamedDimsArray, names) = rename(a, names) +# Base version ignores dimension names. +# TODO: Use `mapreduce(==, &&, a1, a2)`? +# TODO: Handle `missing` values properly. +function Base.:(==)(a1::AbstractNamedDimsArray, a2::AbstractNamedDimsArray) + return all(eachindex(a1, a2)) do I + a1[I] == a2[I] + end +end # TODO: Move to `utils.jl` file. # TODO: Use `Base.indexin`? @@ -84,91 +179,276 @@ function getperm(x, y) return map(yᵢ -> findfirst(isequal(yᵢ), x), y) end -# TODO: Define for `AbstractArray`, use `isnamed` trait? -function get_name_perm(a::AbstractNamedDimsArray, names::Tuple) - # TODO: Call `getperm(dimnames(a), dimnames(namedints))`. - return getperm(dimnames(a), names) +# Indexing. +function Base.getindex(a::AbstractNamedDimsArray{<:Any,N}, I::Vararg{Int,N}) where {N} + return getindex(dename(a), I...) +end +function Base.getindex( + a::AbstractArray{<:Any,N}, I::Vararg{AbstractNamedInteger,N} +) where {N} + return getindex(a, to_indices(a, I)...) +end +function Base.getindex( + a::AbstractNamedDimsArray{<:Any,N}, I::NamedCartesianIndex{N} +) where {N} + return getindex(a, to_indices(a, (I,))...) +end +function Base.getindex( + a::AbstractNamedDimsArray{<:Any,N}, I::Vararg{Pair{<:Any,Int},N} +) where {N} + return getindex(a, to_indices(a, I)...) +end +function Base.getindex(a::AbstractNamedDimsArray, I::Int) + return getindex(dename(a), I) +end +function Base.setindex!( + a::AbstractNamedDimsArray{<:Any,N}, value, I::Vararg{Int,N} +) where {N} + setindex!(dename(a), value, I...) + return a +end +function Base.setindex!( + a::AbstractArray{<:Any,N}, value, I::Vararg{AbstractNamedInteger,N} +) where {N} + setindex!(a, value, to_indices(a, I)...) + return a +end +function Base.setindex!( + a::AbstractNamedDimsArray{<:Any,N}, value, I::NamedCartesianIndex{N} +) where {N} + setindex!(a, value, to_indices(a, (I,))...) + return a +end +function Base.setindex!( + a::AbstractNamedDimsArray{<:Any,N}, value, I::Vararg{Pair{<:Any,Int},N} +) where {N} + setindex!(a, value, to_indices(a, I)...) + return a +end +function Base.setindex!(a::AbstractNamedDimsArray, value, I::Int) + setindex!(dename(a), value, I) + return a +end +# Handles permutation of indices to align dimension names. +function Base.to_indices( + a::AbstractArray{<:Any,N}, I::Tuple{Vararg{AbstractNamedInteger,N}} +) where {N} + # TODO: Check this permutation is correct (it may be the inverse of what we want). + # We unwrap the names twice in case named axes were passed as indices. + return dename.(map(i -> I[i], getperm(dimnames(a), name.(name.(I))))) +end +function Base.to_indices( + a::AbstractArray{<:Any,N}, I::Tuple{NamedCartesianIndex{N}} +) where {N} + return to_indices(a, Tuple(only(I))) +end +# Support indexing with pairs `a[:i => 1, :j => 2]`. +function Base.to_index(a::AbstractNamedDimsArray, i::Pair{<:Any,Int}) + return named(last(i), first(i)) +end +function Base.isassigned(a::AbstractNamedDimsArray{<:Any,N}, I::Vararg{Int,N}) where {N} + return isassigned(parent(a), I...) +end + +function aligndims(a::AbstractArray, dims) + # TODO: Check this permutation is correct (it may be the inverse of what we want). + perm = getperm(dimnames(a), name.(dims)) + return nameddims(permutedims(dename(a), perm), name.(dims)) +end + +function aligneddims(a::AbstractArray, dims) + # TODO: Check this permutation is correct (it may be the inverse of what we want). + new_dimnames = name.(dims) + perm = getperm(dimnames(a), new_dimnames) + !isperm(perm) && + throw(NameMismatch("Dimension name mismatch $(dimnames(a)), $(new_dimnames).")) + return nameddims(PermutedDimsArray(dename(a), perm), new_dimnames) +end + +using Random: Random, AbstractRNG + +# Convenient constructors +default_eltype() = Float64 +for f in [:rand, :randn] + @eval begin + function Base.$f( + rng::AbstractRNG, + elt::Type{<:Number}, + dims::Tuple{AbstractNamedInteger,Vararg{AbstractNamedInteger}}, + ) + a = $f(rng, elt, unname.(dims)) + return nameddims(a, name.(dims)) + end + function Base.$f( + rng::AbstractRNG, + elt::Type{<:Number}, + dim1::AbstractNamedInteger, + dims::Vararg{AbstractNamedInteger}, + ) + return $f(rng, elt, (dim1, dims...)) + end + Base.$f( + elt::Type{<:Number}, dims::Tuple{AbstractNamedInteger,Vararg{AbstractNamedInteger}} + ) = $f(Random.default_rng(), elt, dims) + Base.$f( + elt::Type{<:Number}, dim1::AbstractNamedInteger, dims::Vararg{AbstractNamedInteger} + ) = $f(elt, (dim1, dims...)) + Base.$f(dims::Tuple{AbstractNamedInteger,Vararg{AbstractNamedInteger}}) = + $f(default_eltype(), dims) + Base.$f(dim1::AbstractNamedInteger, dims::Vararg{AbstractNamedInteger}) = + $f((dim1, dims...)) + end +end +for f in [:zeros, :ones] + @eval begin + function Base.$f( + elt::Type{<:Number}, dims::Tuple{AbstractNamedInteger,Vararg{AbstractNamedInteger}} + ) + a = $f(elt, unname.(dims)) + return nameddims(a, name.(dims)) + end + function Base.$f( + elt::Type{<:Number}, dim1::AbstractNamedInteger, dims::Vararg{AbstractNamedInteger} + ) + return $f(elt, (dim1, dims...)) + end + Base.$f(dims::Tuple{AbstractNamedInteger,Vararg{AbstractNamedInteger}}) = + $f(default_eltype(), dims) + Base.$f(dim1::AbstractNamedInteger, dims::Vararg{AbstractNamedInteger}) = + $f((dim1, dims...)) + end end +function Base.fill(value, dims::Tuple{AbstractNamedInteger,Vararg{AbstractNamedInteger}}) + a = fill(value, unname.(dims)) + return nameddims(a, name.(dims)) +end +function Base.fill(value, dim1::AbstractNamedInteger, dims::Vararg{AbstractNamedInteger}) + return fill(value, (dim1, dims...)) +end + +using Base.Broadcast: + AbstractArrayStyle, + Broadcasted, + broadcast_shape, + broadcasted, + check_broadcast_shape, + combine_axes, + combine_eltypes +using BroadcastMapConversion: Mapped, mapped -# Fixes ambiguity error -# TODO: Define for `AbstractArray`, use `isnamed` trait? -function get_name_perm(a::AbstractNamedDimsArray, names::Tuple{}) - # TODO: Call `getperm(dimnames(a), dimnames(namedints))`. - @assert iszero(ndims(a)) - return () +abstract type AbstractNamedDimsArrayStyle{N} <: AbstractArrayStyle{N} end + +struct NamedDimsArrayStyle{N} <: AbstractNamedDimsArrayStyle{N} end +NamedDimsArrayStyle(::Val{N}) where {N} = NamedDimsArrayStyle{N}() +NamedDimsArrayStyle{M}(::Val{N}) where {M,N} = NamedDimsArrayStyle{N}() + +function Broadcast.BroadcastStyle(arraytype::Type{<:AbstractNamedDimsArray}) + return NamedDimsArrayStyle{ndims(arraytype)}() end -# TODO: Define for `AbstractArray`, use `isnamed` trait? -function get_name_perm( - a::AbstractNamedDimsArray, namedints::Tuple{Vararg{AbstractNamedInt}} +function Broadcast.combine_axes( + a1::AbstractNamedDimsArray, a_rest::AbstractNamedDimsArray... ) - # TODO: Call `getperm(dimnames(a), dimnames(namedints))`. - return getperm(namedsize(a), namedints) + return broadcast_shape(axes(a1), combine_axes(a_rest...)) +end +function Broadcast.combine_axes(a1::AbstractNamedDimsArray, a2::AbstractNamedDimsArray) + return broadcast_shape(axes(a1), axes(a2)) end +Broadcast.combine_axes(a::AbstractNamedDimsArray) = axes(a) -# TODO: Define for `AbstractArray`, use `isnamed` trait? -function get_name_perm( - a::AbstractNamedDimsArray, new_namedaxes::Tuple{Vararg{AbstractNamedUnitRange}} +function Broadcast.broadcast_shape( + ax1::Tuple{Vararg{AbstractNamedUnitRange}}, + ax2::Tuple{Vararg{AbstractNamedUnitRange}}, + ax_rest::Tuple{Vararg{AbstractNamedUnitRange}}..., ) - # TODO: Call `getperm(dimnames(a), dimnames(namedints))`. - return getperm(namedaxes(a), new_namedaxes) -end - -# Indexing -# a[:i => 2, :j => 3] -# TODO: Write a generic version using `dim`. -# TODO: Define a `NamedIndex` or `NamedInt` type for indexing? -# Base.getindex(a::AbstractArray, I::NamedInt...) -function Base.getindex(a::AbstractNamedDimsArray, I::Pair...) - perm = get_name_perm(a, first.(I)) - i = last.(I) - return unname(a)[map(p -> i[p], perm)...] -end - -# a[:i => 2, :j => 3] = 12 -# TODO: Write a generic version using `dim`. -# TODO: Define a `NamedIndex` or `NamedInt` type for indexing? -function Base.setindex!(a::AbstractNamedDimsArray, value, I::Pair...) - perm = get_name_perm(a, first.(I)) - i = last.(I) - unname(a)[map(p -> i[p], perm)...] = value - return a + return broadcast_shape(broadcast_shape(ax1, ax2), ax_rest...) +end + +function Broadcast.broadcast_shape( + ax1::Tuple{Vararg{AbstractNamedUnitRange}}, ax2::Tuple{Vararg{AbstractNamedUnitRange}} +) + return promote_shape(ax1, ax2) end -# Output the dimension of the specified name. -dim(a::AbstractNamedDimsArray, name) = findfirst(==(name), dimnames(a)) +function Base.promote_shape( + ax1::Tuple{AbstractNamedUnitRange,Vararg{AbstractNamedUnitRange,N}}, + ax2::Tuple{AbstractNamedUnitRange,Vararg{AbstractNamedUnitRange,N}}, +) where {N} + perm = getperm(name.(ax1), name.(ax2)) + ax2_aligned = map(i -> ax2[i], perm) + ax_promoted = promote_shape(dename.(ax1), dename.(ax2_aligned)) + return named.(ax_promoted, name.(ax1)) +end -# Output the dimensions of the specified names. -dims(a::AbstractNamedDimsArray, names) = map(name -> dim(a, name), names) +# Avoid comparison of `NamedInteger` against `1`. +function Broadcast.check_broadcast_shape( + ax1::Tuple{AbstractNamedUnitRange,Vararg{AbstractNamedUnitRange,N}}, + ax2::Tuple{AbstractNamedUnitRange,Vararg{AbstractNamedUnitRange,N}}, +) where {N} + perm = getperm(name.(ax1), name.(ax2)) + ax2_aligned = map(i -> ax2[i], perm) + check_broadcast_shape(dename.(ax1), dename.(ax2_aligned)) + return nothing +end -# Unwrapping the names -# TODO: Use `isnamed` trait. -unname(a::AbstractNamedDimsArray) = parent(a) -unname(a::AbstractArray) = a - -# Permute into a certain order. -# align(a, (:j, :k, :i)) -# Like `named(nameless(a, names), names)` -# TODO: Use `isnamed` trait. -function align(na::AbstractNamedDimsArray, names) - perm = get_name_perm(na, names) - # TODO: Avoid permutation if it is a trivial permutation? - # return typeof(a)(permutedims(unname(a), perm), names) - return permutedims(na, perm) -end - -# Unwrapping names and permuting -# nameless(a, (:j, :i)) -# Could just call `unname`? -## nameless(a::AbstractNamedDimsArray, names) = unname(align(a, names)) -# TODO: Use `isnamed` trait. -unname(a::AbstractNamedDimsArray, names) = unname(align(a, names)) - -# In `TensorAlgebra` this this `fuse` and `unfuse`, -# in `NDTensors`/`ITensors` this is `combine` and `uncombine`. -# t = split(g, :n => (j=4, k=5)) -# join(t, (:i, :k) => :χ) - -# TensorAlgebra -# contract, fusedims, unfusedims, qr, eigen, svd, add, etc. -# Some of these can simply wrap `TensorAlgebra.jl` functions. +# Handle scalars. +function Base.promote_shape( + ax1::Tuple{AbstractNamedUnitRange,Vararg{AbstractNamedUnitRange}}, ax2::Tuple{} +) + return ax1 +end + +# Dename and lazily permute the arguments using the reference +# dimension names. +# TODO: Make a version that gets the dimnames from `m`. +function denamed(m::Mapped, dimnames) + return mapped(m.f, map(arg -> denamed(arg, dimnames), m.args)...) +end + +function Base.similar(bc::Broadcasted{<:AbstractNamedDimsArrayStyle}, elt::Type, ax::Tuple) + m′ = denamed(Mapped(bc), name.(ax)) + return nameddims(similar(m′, elt, dename.(ax)), name.(ax)) +end + +function Base.copyto!( + dest::AbstractArray{<:Any,N}, bc::Broadcasted{<:AbstractNamedDimsArrayStyle{N}} +) where {N} + return copyto!(dest, Mapped(bc)) +end + +function Base.map!(f, a_dest::AbstractNamedDimsArray, a_srcs::AbstractNamedDimsArray...) + a′_dest = dename(a_dest) + # TODO: Use `denamed` to do the permutations lazily. + a′_srcs = map(a_src -> dename(a_src, dimnames(a_dest)), a_srcs) + map!(f, a′_dest, a′_srcs...) + return a_dest +end + +function Base.map(f, a_srcs::AbstractNamedDimsArray...) + # copy(mapped(f, a_srcs...)) + return f.(a_srcs...) +end + +function Base.mapreduce(f, op, a::AbstractNamedDimsArray; kwargs...) + return mapreduce(f, op, dename(a); kwargs...) +end + +using LinearAlgebra: LinearAlgebra, norm +function LinearAlgebra.norm(a::AbstractNamedDimsArray; kwargs...) + return norm(dename(a); kwargs...) +end + +# Printing. +function Base.show(io::IO, mime::MIME"text/plain", a::AbstractNamedDimsArray) + summary(io, a) + println(io) + show(io, mime, dename(a)) + return nothing +end + +function Base.show(io::IO, a::AbstractNamedDimsArray) + print(io, "nameddims(") + show(io, dename(a)) + print(io, ", ", dimnames(a), ")") + return nothing +end diff --git a/src/abstractnameddimsmatrix.jl b/src/abstractnameddimsmatrix.jl deleted file mode 100644 index 5090e98..0000000 --- a/src/abstractnameddimsmatrix.jl +++ /dev/null @@ -1 +0,0 @@ -const AbstractNamedDimsMatrix{T,Parent,Names} = AbstractNamedDimsArray{T,2,Parent,Names} diff --git a/src/abstractnameddimsvector.jl b/src/abstractnameddimsvector.jl deleted file mode 100644 index d1f7b67..0000000 --- a/src/abstractnameddimsvector.jl +++ /dev/null @@ -1 +0,0 @@ -const AbstractNamedDimsVector{T,Parent,Names} = AbstractNamedDimsArray{T,1,Parent,Names} diff --git a/src/abstractnamedint.jl b/src/abstractnamedint.jl deleted file mode 100644 index e77b6d2..0000000 --- a/src/abstractnamedint.jl +++ /dev/null @@ -1,47 +0,0 @@ -abstract type AbstractNamedInt{Value,Name} <: Integer end - -# Interface -unname(i::AbstractNamedInt) = error("Not implemented") -name(i::AbstractNamedInt) = error("Not implemented") - -# Derived -unname(::Type{<:AbstractNamedInt{Value}}) where {Value} = Value - -# Integer interface -# TODO: Should this make a random name, or require defining a way -# to combine names? -Base.:*(i1::AbstractNamedInt, i2::AbstractNamedInt) = unname(i1) * unname(i2) -Base.:-(i::AbstractNamedInt) = typeof(i)(-unname(i), name(i)) - -# TODO: Define for `NamedInt`, `NamedUnitRange` fallback? -# Base.OneTo(stop::AbstractNamedInt) = namedoneto(stop) -## nameduniterange_type(::Type{<:AbstractNamedInt}) = error("Not implemented") - -# TODO: Use conversion from `AbstractNamedInt` to `AbstractNamedUnitRange` -# instead of general `named`. -# Base.OneTo(stop::AbstractNamedInt) = namedoneto(stop) -Base.OneTo(stop::AbstractNamedInt) = named(Base.OneTo(unname(stop)), name(stop)) - -# TODO: Is this needed? -# Include the name as well? -Base.:<(i1::AbstractNamedInt, i2::AbstractNamedInt) = unname(i1) < unname(i2) -## Base.zero(type::Type{<:AbstractNamedInt}) = zero(unname(type)) - -function Base.promote_rule(type1::Type{<:AbstractNamedInt}, type2::Type{<:Integer}) - return promote_type(unname(type1), type2) -end -(type::Type{<:Integer})(i::AbstractNamedInt) = type(unname(i)) - -# ambiguity fix -for T in (:Bool, :BigInt, :Integer) - @eval Base.$T(i::AbstractNamedInt) = $T(unname(i)) -end - -# TODO: Use conversion from `AbstractNamedInt` to `AbstractNamedUnitRange` -# instead of general `named`. -function Base.oftype(i1::AbstractNamedInt, i2::Integer) - return named(convert(typeof(unname(i1)), i2), name(i1)) -end - -# Traits -isnamed(::AbstractNamedInt) = true diff --git a/src/abstractnamedinteger.jl b/src/abstractnamedinteger.jl new file mode 100644 index 0000000..c557421 --- /dev/null +++ b/src/abstractnamedinteger.jl @@ -0,0 +1,106 @@ +using TypeParameterAccessors: unspecify_type_parameters + +abstract type AbstractNamedInteger{Value,Name} <: Integer end + +# Minimal interface. +dename(i::AbstractNamedInteger) = throw(MethodError(dename, Tuple{typeof(i)})) +name(i::AbstractNamedInteger) = throw(MethodError(name, Tuple{typeof(i)})) + +# This can be customized to output different named integer types, +# such as `namedinteger(i::Integer, name::IndexName) = IndexInteger(i, name)`. +namedinteger(i::Integer, name) = NamedInteger(i, name) + +# Shorthand. +named(i::Integer, name) = namedinteger(i, name) + +# Derived interface. +# TODO: Use `Accessors.@set`? +setname(i::AbstractNamedInteger, name) = named(dename(i), name) +# TODO: Use `Accessors.@set`? +setvalue(i::AbstractNamedInteger, value) = named(value, name(i)) + +# TODO: Use `TypeParameterAccessors`. +denametype(::Type{<:AbstractNamedInteger{Value}}) where {Value} = Value +nametype(::Type{<:AbstractNamedInteger{<:Any,Name}}) where {Name} = Name + +# Traits +isnamed(::Type{<:AbstractNamedInteger}) = true + +# TODO: Should they also have the same base type? +function Base.:(==)(i1::AbstractNamedInteger, i2::AbstractNamedInteger) + return name(i1) == name(i2) && dename(i1) == dename(i2) +end +function Base.hash(i::AbstractNamedInteger, h::UInt) + h = hash(Symbol(unspecify_type_parameters(typeof(i))), h) + h = hash(dename(i), h) + return hash(name(i), h) +end + +abstract type AbstractName end +name(n::AbstractName) = throw(MethodError(name, Tuple{typeof(n)})) +Base.getindex(n::AbstractName, I) = named(I, name(n)) + +struct Name{Value} <: AbstractName + value::Value +end +name(n::Name) = n.value + +# vcat that works with combinations of tuples +# and vectors. +generic_vcat(v1, v2) = vcat(v1, v2) +generic_vcat(v1::Tuple, v2) = vcat([v1...], v2) +generic_vcat(v1, v2::Tuple) = vcat(v1, [v2...]) +generic_vcat(v1::Tuple, v2::Tuple) = (v1..., v2...) + +struct FusedNames{Names} <: AbstractName + names::Names +end +fusednames(name1, name2) = FusedNames((name1, name2)) +fusednames(name1::FusedNames, name2::FusedNames) = FusedNames(generic_vcat(name1, name2)) +fusednames(name1, name2::FusedNames) = fusednames(FusedNames((name1,)), name2) +fusednames(name1::FusedNames, name2) = fusednames(name1, FusedNames((name2,))) + +# Integer interface +# TODO: Should this make a random name, or require defining a way +# to combine names? +function Base.:*(i1::AbstractNamedInteger, i2::AbstractNamedInteger) + return named(dename(i1) * dename(i2), fusednames(name(i1), name(i2))) +end +Base.:-(i::AbstractNamedInteger) = setvalue(i, -dename(i)) + +## TODO: Support this, we need to define `NamedFloat`, `NamedReal`, `NamedNumber`, etc. +## This is used in `LinearAlgebra.norm`, for now we just overload that directly. +## Here, named numbers are treated as unitful, so multiplying them +## with unnamed numbers means the result inherits the name. +## function Base.:*(i1::AbstractNamedInteger, i2::Number) +## return named(dename(i1) * i2, name(i1)) +## end + +# For the sake of generic code, the name is ignored. +# Used in `AbstractArray` `Base.show`. +# TODO: See if we can delete this. +Base.:+(i1::Int, i2::AbstractNamedInteger) = i1 + dename(i2) + +Base.zero(i::AbstractNamedInteger) = setvalue(i, zero(dename(i))) +Base.one(i::AbstractNamedInteger) = setvalue(i, one(dename(i))) +Base.signbit(i::AbstractNamedInteger) = signbit(dename(i)) +Base.unsigned(i::AbstractNamedInteger) = setvalue(i, unsigned(dename(i))) +function Base.string(i::AbstractNamedInteger; kwargs...) + return "named($(string(dename(i); kwargs...)), $(repr(name(i))))" +end + +struct NameMismatch <: Exception + message::String +end +NameMismatch() = NameMismatch("") + +# Used in bounds checking when indexing with named dimensions. +function Base.:<(i1::AbstractNamedInteger, i2::AbstractNamedInteger) + name(i1) == name(i2) || throw(NameMismatch("Mismatched names $(name(i1)), $(name(i2))")) + return dename(i1) < dename(i2) +end + +function Base.show(io::IO, r::AbstractNamedInteger) + print(io, "named(", dename(r), ", ", repr(name(r)), ")") + return nothing +end diff --git a/src/abstractnamedunitrange.jl b/src/abstractnamedunitrange.jl index 5b66e59..9942b1a 100644 --- a/src/abstractnamedunitrange.jl +++ b/src/abstractnamedunitrange.jl @@ -1,36 +1,60 @@ -abstract type AbstractNamedUnitRange{T,Value<:AbstractUnitRange{T},Name} <: +using TypeParameterAccessors: unspecify_type_parameters + +abstract type AbstractNamedUnitRange{T,Value<:AbstractUnitRange,Name} <: AbstractUnitRange{T} end -# Required interface -unname(::AbstractNamedUnitRange) = error("Not implemented") -name(::AbstractNamedUnitRange) = error("Not implemented") +# Minimal interface. +dename(r::AbstractNamedUnitRange) = throw(MethodError(dename, Tuple{typeof(r)})) +name(r::AbstractNamedUnitRange) = throw(MethodError(name, Tuple{typeof(r)})) + +# This can be customized to output different named integer types, +# such as `namedunitrange(r::AbstractUnitRange, name::IndexName) = Index(r, name)`. +namedunitrange(r::AbstractUnitRange, name) = NamedUnitRange(r, name) -# Traits -isnamed(::AbstractNamedUnitRange) = true +# Shorthand. +named(r::AbstractUnitRange, name) = namedunitrange(r, name) -# Unit range -Base.first(i::AbstractNamedUnitRange) = first(unname(i)) -Base.last(i::AbstractNamedUnitRange) = last(unname(i)) -Base.length(i::AbstractNamedUnitRange) = named(length(unname(i)), name(i)) +# Derived interface. +# TODO: Use `Accessors.@set`? +setname(r::AbstractNamedUnitRange, name) = namedunitrange(dename(r), name) -# TODO: Use `isnamed` trait? -dimnames(a::Tuple{AbstractNamedUnitRange,Vararg{AbstractNamedUnitRange}}) = name.(a) -dimnames(::Tuple{}) = () +# TODO: Use `TypeParameterAccessors`. +denametype(::Type{<:AbstractNamedUnitRange{<:Any,Value}}) where {Value} = Value +nametype(::Type{<:AbstractNamedUnitRange{<:Any,<:Any,Name}}) where {Name} = Name -unname(a::Tuple{Vararg{AbstractNamedUnitRange}}) = unname.(a) -unname(a::Tuple{Vararg{AbstractNamedUnitRange}}, names) = unname(align(a, names)) +# Traits. +isnamed(::Type{<:AbstractNamedUnitRange}) = true -function named(as::Tuple{Vararg{AbstractUnitRange}}, names) - return ntuple(j -> named(as[j], names[j]), length(as)) +# TODO: Should they also have the same base type? +function Base.:(==)(r1::AbstractNamedUnitRange, r2::AbstractNamedUnitRange) + return name(r1) == name(r2) && dename(r1) == dename(r2) +end +function Base.hash(r::AbstractNamedUnitRange, h::UInt) + h = hash(Symbol(unspecify_type_parameters(typeof(r))), h) + # TODO: Double check how this is handling blocking/sector information. + h = hash(dename(r), h) + return hash(name(r), h) end -function get_name_perm(a::Tuple{Vararg{AbstractNamedUnitRange}}, names::Tuple) - return getperm(dimnames(a), names) +# Unit range funcionality. +# TODO: Also customize `Base.getindex` to preserve the name. +Base.first(r::AbstractNamedUnitRange) = named(first(dename(r)), name(r)) +Base.last(r::AbstractNamedUnitRange) = named(last(dename(r)), name(r)) +Base.length(r::AbstractNamedUnitRange) = named(length(dename(r)), name(r)) +Base.size(r::AbstractNamedUnitRange) = (named(length(dename(r)), name(r)),) +Base.axes(r::AbstractNamedUnitRange) = (named(only(axes(dename(r))), name(r)),) +Base.step(r::AbstractNamedUnitRange) = named(step(dename(r)), name(r)) +Base.getindex(r::AbstractNamedUnitRange, i::Int) = named(getindex(dename(r), i), name(r)) +Base.isempty(r::AbstractNamedUnitRange) = isempty(dename(r)) + +Base.iterate(r::AbstractNamedUnitRange) = isempty(r) ? nothing : (first(r), first(r)) +function Base.iterate(r::AbstractNamedUnitRange, i) + i == last(r) && return nothing + next = named(dename(i) + dename(step(r)), name(r)) + return (next, next) end -# Permute into a certain order. -# align(a, (:j, :k, :i)) -function align(a::Tuple{AbstractNamedUnitRange,Vararg{AbstractNamedUnitRange}}, names) - perm = get_name_perm(a, names) - return map(j -> a[j], perm) +function Base.show(io::IO, r::AbstractNamedUnitRange) + print(io, "named(", dename(r), ", ", repr(name(r)), ")") + return nothing end diff --git a/src/adapt.jl b/src/adapt.jl new file mode 100644 index 0000000..799f70d --- /dev/null +++ b/src/adapt.jl @@ -0,0 +1,5 @@ +using Adapt: Adapt, adapt + +function Adapt.adapt_structure(to, a::AbstractNamedDimsArray) + return nameddims(adapt(to, dename(a)), dimnames(a)) +end diff --git a/src/broadcast.jl b/src/broadcast.jl deleted file mode 100644 index 1749a94..0000000 --- a/src/broadcast.jl +++ /dev/null @@ -1,49 +0,0 @@ -using Base.Broadcast: BroadcastStyle, AbstractArrayStyle, DefaultArrayStyle, Broadcasted -using BroadcastMapConversion: map_function, map_args - -struct NamedDimsArrayStyle{N} <: AbstractArrayStyle{N} end - -function Broadcast.BroadcastStyle(::Type{<:AbstractNamedDimsArray{<:Any,N}}) where {N} - return NamedDimsArrayStyle{N}() -end - -NamedDimsArrayStyle(::Val{N}) where {N} = NamedDimsArrayStyle{N}() -NamedDimsArrayStyle{M}(::Val{N}) where {M,N} = NamedDimsArrayStyle{N}() - -Broadcast.BroadcastStyle(a::NamedDimsArrayStyle, ::DefaultArrayStyle{0}) = a -function Broadcast.BroadcastStyle(::NamedDimsArrayStyle{N}, a::DefaultArrayStyle) where {N} - return BroadcastStyle(DefaultArrayStyle{N}(), a) -end -function Broadcast.BroadcastStyle( - ::NamedDimsArrayStyle{N}, ::Broadcast.Style{Tuple} -) where {N} - return DefaultArrayStyle{N}() -end - -# TODO: Is this needed? -# Define `output_names`, like `allocate_output`. -# function dimnames(bc::Broadcasted{<:NamedDimsArrayStyle}) -# return dimnames(first(map_args(bc))) -# end - -function Broadcast.check_broadcast_axes(shp, a::AbstractNamedDimsArray) - # Unles we output named axes from `axes(::NamedDimsArray)`, - # this check won't make sense since it has to check up - # to an unknown permutation. - return nothing -end - -# TODO: Use `allocate_output`, share logic with `map`. -function Base.similar(bc::Broadcasted{<:NamedDimsArrayStyle}, elt::Type) - return similar(first(map_args(bc)), elt) -end - -# Broadcasting implementation -function Base.copyto!( - dest::AbstractNamedDimsArray{<:Any,N}, bc::Broadcasted{NamedDimsArrayStyle{N}} -) where {N} - # convert to map - # flatten and only keep the AbstractArray arguments - map!(map_function(bc), dest, map_args(bc)...) - return dest -end diff --git a/src/broadcast_shape.jl b/src/broadcast_shape.jl deleted file mode 100644 index ad69861..0000000 --- a/src/broadcast_shape.jl +++ /dev/null @@ -1,24 +0,0 @@ -using Base.Broadcast: Broadcast, broadcast_shape, combine_axes - -# TODO: Have `axes` output named axes so Base functions "just work". -function Broadcast.combine_axes(na1::AbstractNamedDimsArray, nas::AbstractNamedDimsArray...) - return broadcast_shape(namedaxes(na1), combine_axes(nas...)) -end -function Broadcast.combine_axes(na1::AbstractNamedDimsArray, na2::AbstractNamedDimsArray) - return broadcast_shape(namedaxes(na1), namedaxes(na2)) -end -Broadcast.combine_axes(na::AbstractNamedDimsArray) = namedaxes(na) - -function Broadcast.broadcast_shape( - na1::Tuple{Vararg{AbstractNamedUnitRange}}, - na2::Tuple{Vararg{AbstractNamedUnitRange}}, - nas::Tuple{Vararg{AbstractNamedUnitRange}}..., -) - return broadcast_shape(broadcast_shape(shape, shape1), shapes...) -end - -function Broadcast.broadcast_shape( - na1::Tuple{Vararg{AbstractNamedUnitRange}}, na2::Tuple{Vararg{AbstractNamedUnitRange}} -) - return promote_shape(na1, na2) -end diff --git a/src/constructors.jl b/src/constructors.jl deleted file mode 100644 index 9e167be..0000000 --- a/src/constructors.jl +++ /dev/null @@ -1,48 +0,0 @@ -using Random: AbstractRNG, default_rng - -# TODO: Use `AbstractNamedUnitRange`, determine the `AbstractNamedDimsArray` -# from a default value. Useful for distinguishing between `NamedDimsArray` -# and `ITensor`. -# Convenient constructors -default_eltype() = Float64 -for f in [:rand, :randn] - @eval begin - function Base.$f( - rng::AbstractRNG, elt::Type{<:Number}, dims::Tuple{NamedInt,Vararg{NamedInt}} - ) - a = $f(rng, elt, unname.(dims)) - return named(a, name.(dims)) - end - function Base.$f( - rng::AbstractRNG, elt::Type{<:Number}, dim1::NamedInt, dims::Vararg{NamedInt} - ) - return $f(rng, elt, (dim1, dims...)) - end - Base.$f(elt::Type{<:Number}, dims::Tuple{NamedInt,Vararg{NamedInt}}) = - $f(default_rng(), elt, dims) - Base.$f(elt::Type{<:Number}, dim1::NamedInt, dims::Vararg{NamedInt}) = - $f(elt, (dim1, dims...)) - Base.$f(dims::Tuple{NamedInt,Vararg{NamedInt}}) = $f(default_eltype(), dims) - Base.$f(dim1::NamedInt, dims::Vararg{NamedInt}) = $f((dim1, dims...)) - end -end -for f in [:zeros, :ones] - @eval begin - function Base.$f(elt::Type{<:Number}, dims::Tuple{NamedInt,Vararg{NamedInt}}) - a = $f(elt, unname.(dims)) - return named(a, name.(dims)) - end - function Base.$f(elt::Type{<:Number}, dim1::NamedInt, dims::Vararg{NamedInt}) - return $f(elt, (dim1, dims...)) - end - Base.$f(dims::Tuple{NamedInt,Vararg{NamedInt}}) = $f(default_eltype(), dims) - Base.$f(dim1::NamedInt, dims::Vararg{NamedInt}) = $f((dim1, dims...)) - end -end -function Base.fill(value, dims::Tuple{NamedInt,Vararg{NamedInt}}) - a = fill(value, unname.(dims)) - return named(a, name.(dims)) -end -function Base.fill(value, dim1::NamedInt, dims::Vararg{NamedInt}) - return fill(value, (dim1, dims...)) -end diff --git a/src/isnamed.jl b/src/isnamed.jl new file mode 100644 index 0000000..1b385cc --- /dev/null +++ b/src/isnamed.jl @@ -0,0 +1,15 @@ +using SimpleTraits: SimpleTraits, @traitdef, @traitfn, @traitimpl, Not + +isnamed(x) = isnamed(typeof(x)) +isnamed(::Type) = false + +# By default, the name of an object is itself. +name(x) = x + +@traitdef IsNamed{X} +#! format: off +@traitimpl IsNamed{X} <- isnamed(X) +#! format: on + +@traitfn unname(x::X) where {X; IsNamed{X}} = dename(x) +@traitfn unname(x::X) where {X; !IsNamed{X}} = x diff --git a/src/map.jl b/src/map.jl deleted file mode 100644 index d7b3857..0000000 --- a/src/map.jl +++ /dev/null @@ -1,14 +0,0 @@ -# TODO: Handle maybe-mutation. -# TODO: Handle permutations more efficiently by fusing with `f`. -function Base.map!(f, na_dest::AbstractNamedDimsArray, nas::AbstractNamedDimsArray...) - a_dest = unname(na_dest) - as = map(na -> unname(na, dimnames(na_dest)), nas) - map!(f, a_dest, as...) - return na_dest -end - -function Base.map(f, nas::AbstractNamedDimsArray...) - na_dest = similar(first(nas)) - map!(f, na_dest, nas...) - return na_dest -end diff --git a/src/name.jl b/src/name.jl deleted file mode 100644 index 07613dd..0000000 --- a/src/name.jl +++ /dev/null @@ -1,2 +0,0 @@ -# In general an object is a name -name(x) = x diff --git a/src/nameddimsarray.jl b/src/nameddimsarray.jl index 5727c53..2702aa1 100644 --- a/src/nameddimsarray.jl +++ b/src/nameddimsarray.jl @@ -1,114 +1,28 @@ -function _NamedDimsArray end +using TypeParameterAccessors: TypeParameterAccessors, parenttype -struct NamedDimsArray{T,N,Arr<:AbstractArray{T,N},Names<:Tuple{Vararg{Any,N}}} <: - AbstractNamedDimsArray{T,N,Arr,Names} - array::Arr - names::Names - global @inline function _NamedDimsArray(array::AbstractArray, names) - elt = eltype(array) - n = ndims(array) - names_tuple = Tuple{Vararg{Any,n}}(names) - arraytype = typeof(array) - namestype = typeof(names_tuple) - return new{elt,n,arraytype,namestype}(array, names_tuple) - end - - # TODO: Delete, maybe this aligns according to the new names? - global @inline function _NamedDimsArray(array::NamedDimsArray, names) - return error("Not implemented, already named.") - end -end - -function NamedDimsArray{T,N,Arr,Names}( - a::AbstractArray, names -) where {T,N,Arr<:AbstractArray{T,N},Names} - return _NamedDimsArray(convert(Arr, a), convert(Names, names)) +struct NamedDimsArray{T,N,Parent<:AbstractArray{T,N},DimNames} <: + AbstractNamedDimsArray{T,N} + parent::Parent + dimnames::DimNames end -# TODO: Combine with other constructor definitions. -function NamedDimsArray{T,N,Arr,Names}( - a::AbstractArray, names::Tuple{} -) where {T,N,Arr<:AbstractArray{T,N},Names} - return _NamedDimsArray(convert(Arr, a), convert(Names, names)) -end - -NamedDimsArray(a::AbstractArray, names) = _NamedDimsArray(a, names) +const NamedDimsVector{T,Parent<:AbstractVector{T},DimNames} = NamedDimsArray{ + T,1,Parent,DimNames +} +const NamedDimsMatrix{T,Parent<:AbstractMatrix{T},DimNames} = NamedDimsArray{ + T,2,Parent,DimNames +} -# TODO: Check size consistency -# TODO: Combine with other constructor definitions. -function NamedDimsArray{T,N,Arr,Names}( - a::AbstractArray, namedsize::Tuple{Vararg{AbstractNamedInt}} -) where {T,N,Arr<:AbstractArray{T,N},Names} - @assert size(a) == unname.(namedsize) - return _NamedDimsArray(convert(Arr, a), convert(Names, name.(namedsize))) +function NamedDimsArray(a::AbstractNamedDimsArray, dimnames) + return NamedDimsArray(denamed(a, dimnames), dimnames) end -# TODO: Check axes consistency -# TODO: Combine with other constructor definitions. -function NamedDimsArray{T,N,Arr,Names}( - a::AbstractArray, namedaxes::Tuple{Vararg{AbstractNamedUnitRange}} -) where {T,N,Arr<:AbstractArray{T,N},Names} - @assert axes(a) == unname.(namedaxes) - return _NamedDimsArray(convert(Arr, a), convert(Names, name.(namedaxes))) -end - -# Required interface - -# Output the names. -dimnames(a::NamedDimsArray) = a.names - -# Unwrapping the names -Base.parent(a::NamedDimsArray) = a.array - -# Set the names of an unnamed AbstractArray -function named(a::AbstractArray, names) - @assert ndims(a) == length(names) - return NamedDimsArray(a, names) -end +# Minimal interface. +dimnames(a::NamedDimsArray) = a.dimnames +Base.parent(a::NamedDimsArray) = a.parent -# TODO: Use `Undefs.jl` instead. -function undefs(arraytype::Type{<:AbstractArray}, axes::Tuple{Vararg{AbstractUnitRange}}) - return arraytype(undef, length.(axes)) -end - -# TODO: Use `AbstractNamedUnitRange`, determine the `AbstractNamedDimsArray` -# from a default value. Useful for distinguishing between `NamedDimsArray` -# and `ITensor`. -function undefs(arraytype::Type{<:AbstractArray}, axes::Tuple{Vararg{NamedUnitRange}}) - array = undefs(arraytype, unname.(axes)) - names = name.(axes) - return named(array, names) -end - -# TODO: Use `AbstractNamedUnitRange`, determine the `AbstractNamedDimsArray` -# from a default value. Useful for distinguishing between `NamedDimsArray` -# and `ITensor`. -function Base.similar( - arraytype::Type{<:AbstractArray}, axes::Tuple{NamedUnitRange,Vararg{NamedUnitRange}} +function TypeParameterAccessors.position( + ::Type{<:AbstractNamedDimsArray}, ::typeof(parenttype) ) - # TODO: Use `unname`? - return undefs(arraytype, axes) + return TypeParameterAccessors.Position(3) end - -# TODO: Define `NamedInt`, `NamedUnitRange`, `NamedVector <: AbstractVector`, etc. -# See https://github.com/mcabbott/NamedPlus.jl/blob/v0.0.5/src/int.jl. - -# TODO: Define `similar_name`, with shorthand `sim`, that makes a random name. -# Used in matrix/tensor factorizations. - -# TODO: Think about how to interact with array wrapper types, see: -# https://github.com/mcabbott/NamedPlus.jl/blob/v0.0.5/src/recursion.jl - -# TODO: What should `size` and `axes` output? Could output tuples -# of `NamedInt` and `NamedUnitRange`. - -# TODO: Construct from `NamedInt` or `NamedUnitRange` in standard -# array constructors, like `zeros`, `rand`, `randn`, `undefs`, etc. -# See https://mkitti.github.io/Undefs.jl/stable/, -# https://github.com/mkitti/ArrayAllocators.jl - -# TODO: Define `ArrayConstructors.randn`, `ArrayConstructors.rand`, -# `ArrayConstructors.zeros`, `ArrayConstructors.fill`, etc. -# for generic constructors accepting `CuArray`, `Array`, etc. -# Also could defign allocator types, `https://github.com/JuliaGPU/KernelAbstractions.jl` -# and `https://docs.juliahub.com/General/HeterogeneousComputing/stable/`. diff --git a/src/namedint.jl b/src/namedint.jl deleted file mode 100644 index ca0b228..0000000 --- a/src/namedint.jl +++ /dev/null @@ -1,28 +0,0 @@ -struct NamedInt{Value,Name} <: AbstractNamedInt{Value,Name} - value::Value - name::Name -end - -## Needs a `default_name(nametype::Type)` function. -## NamedInt{Value,Name}(i::Integer) where {Value,Name} = NamedInt{Value,Name}(i, default_name(Name)) - -# Interface -unname(i::NamedInt) = i.value -name(i::NamedInt) = i.name - -# Convenient constructor -named(i::Integer, name) = NamedInt(i, name) - -# TODO: Use `isnamed` trait? -dimnames(a::Tuple{AbstractNamedInt,Vararg{AbstractNamedInt}}) = name.(a) - -function get_name_perm(a::Tuple{AbstractNamedInt,Vararg{AbstractNamedInt}}, names::Tuple) - return getperm(dimnames(a), names) -end - -# Permute into a certain order. -# align(a, (:j, :k, :i)) -function align(a::Tuple{Vararg{AbstractNamedInt}}, names) - perm = get_name_perm(a, names) - return map(j -> a[j], perm) -end diff --git a/src/namedinteger.jl b/src/namedinteger.jl new file mode 100644 index 0000000..2bcc01d --- /dev/null +++ b/src/namedinteger.jl @@ -0,0 +1,8 @@ +struct NamedInteger{Value<:Integer,Name} <: AbstractNamedInteger{Value,Name} + value::Value + name::Name +end + +# Minimal interface. +dename(i::NamedInteger) = i.value +name(i::NamedInteger) = i.name diff --git a/src/namedunitrange.jl b/src/namedunitrange.jl index 44867ea..f1da60a 100644 --- a/src/namedunitrange.jl +++ b/src/namedunitrange.jl @@ -1,12 +1,9 @@ struct NamedUnitRange{T,Value<:AbstractUnitRange{T},Name} <: - AbstractNamedUnitRange{T,Value,Name} + AbstractNamedUnitRange{NamedInteger{T,Name},Value,Name} value::Value name::Name end -# Interface -unname(i::NamedUnitRange) = i.value +# Minimal interface. +dename(i::NamedUnitRange) = i.value name(i::NamedUnitRange) = i.name - -# Constructor -named(i::AbstractUnitRange, name) = NamedUnitRange(i, name) diff --git a/src/permutedims.jl b/src/permutedims.jl deleted file mode 100644 index 5e81a3b..0000000 --- a/src/permutedims.jl +++ /dev/null @@ -1,4 +0,0 @@ -function Base.permutedims(na::AbstractNamedDimsArray, perm) - names = map(j -> dimnames(na)[j], perm) - return named(permutedims(unname(na), perm), names) -end diff --git a/src/promote_shape.jl b/src/promote_shape.jl deleted file mode 100644 index 84fe517..0000000 --- a/src/promote_shape.jl +++ /dev/null @@ -1,12 +0,0 @@ -function Base.promote_shape(na1::AbstractNamedDimsArray, na2::AbstractNamedDimsArray) - return promote_shape(namedaxes(na1), namedaxes(na2)) -end - -function Base.promote_shape( - na1::Tuple{Vararg{AbstractNamedUnitRange}}, na2::Tuple{Vararg{AbstractNamedUnitRange}} -) - a1 = unname(na1) - a2 = unname(na2, dimnames(na1)) - a_promoted = promote_shape(a1, a2) - return named(a_promoted, dimnames(na1)) -end diff --git a/src/randname.jl b/src/randname.jl index 1143ff4..610b1fc 100644 --- a/src/randname.jl +++ b/src/randname.jl @@ -1,5 +1,13 @@ -using Random: randstring +using Random: Random, AbstractRNG, randstring -randname(::Any) = error("Not implemented") +# Generate a new random name, for example in matrix +# factorizations. +randname(rng::AbstractRNG, type::Type) = rand(rng, type) -randname(::String) = randstring() +randname(name; kwargs...) = randname(Random.default_rng(), name; kwargs...) +randname(rng::AbstractRNG, name; kwargs...) = randname(rng, typeof(name); kwargs...) + +randname(rng::AbstractRNG, ::Type{<:AbstractString}; length=8) = randstring(rng, length) +function randname(rng::AbstractRNG, ::Type{Symbol}; kwargs...) + return Symbol(randname(rng, String; kwargs...)) +end diff --git a/src/similar.jl b/src/similar.jl deleted file mode 100644 index 441afec..0000000 --- a/src/similar.jl +++ /dev/null @@ -1,49 +0,0 @@ -# `similar` - -# Preserve the names -Base.similar(na::AbstractNamedDimsArray) = named(similar(unname(na)), dimnames(na)) -function Base.similar(na::AbstractNamedDimsArray, elt::Type) - return named(similar(unname(na), elt), dimnames(na)) -end - -# Remove the names -# TODO: Make versions taking `NamedUnitRange` and `NamedInt`. -function Base.similar(na::AbstractNamedDimsArray, elt::Type, dims::Tuple{Vararg{Int64}}) - return similar(unname(na), elt, dims) -end - -# Remove the names -# TODO: Make versions taking `NamedUnitRange` and `NamedInt`. -function Base.similar( - na::AbstractNamedDimsArray, elt::Type, dims::Tuple{Integer,Vararg{Integer}} -) - return similar(unname(na), elt, dims) -end - -# Remove the names -# TODO: Make versions taking `NamedUnitRange` and `NamedInt`. -function Base.similar( - na::AbstractNamedDimsArray, - elt::Type, - dims::Tuple{Union{Integer,Base.OneTo},Vararg{Union{Integer,Base.OneTo}}}, -) - return similar(unname(na), elt, dims) -end - -# Remove the names -# TODO: Make versions taking `NamedUnitRange` and `NamedInt`. -function Base.similar( - na::AbstractNamedDimsArray, elt::Type, dims::Union{Integer,AbstractUnitRange}... -) - return similar(unname(na), elt, dims...) -end - -# Remove the names -# TODO: Make versions taking `NamedUnitRange` and `NamedInt`. -Base.similar(na::AbstractNamedDimsArray, dims::Tuple) = similar(unname(na), dims) - -# Remove the names -# TODO: Make versions taking `NamedUnitRange` and `NamedInt`. -function Base.similar(na::AbstractNamedDimsArray, dims::Union{Integer,AbstractUnitRange}...) - return similar(unname(na), dims...) -end diff --git a/ext/NamedDimsArraysTensorAlgebraExt/fusedims.jl b/src/tensoralgebra.jl similarity index 51% rename from ext/NamedDimsArraysTensorAlgebraExt/fusedims.jl rename to src/tensoralgebra.jl index 50d6627..fe993ed 100644 --- a/ext/NamedDimsArraysTensorAlgebraExt/fusedims.jl +++ b/src/tensoralgebra.jl @@ -1,7 +1,49 @@ -using ..NamedDimsArrays: name -using TensorAlgebra: TensorAlgebra, blockedperm, fusedims, splitdims +using LinearAlgebra: LinearAlgebra, qr +using TensorAlgebra: TensorAlgebra, blockedperm, contract, contract!, fusedims, splitdims using TensorAlgebra.BaseExtensions: BaseExtensions +function TensorAlgebra.contract!( + a_dest::AbstractNamedDimsArray, + a1::AbstractNamedDimsArray, + a2::AbstractNamedDimsArray, + α::Number=true, + β::Number=false, +) + contract!( + dename(a_dest), + dimnames(a_dest), + dename(a1), + dimnames(a1), + dename(a2), + dimnames(a2), + α, + β, + ) + return a_dest +end + +function TensorAlgebra.contract( + a1::AbstractNamedDimsArray, a2::AbstractNamedDimsArray, α::Number=true +) + a_dest, dimnames_dest = contract(dename(a1), dimnames(a1), dename(a2), dimnames(a2), α) + return nameddims(a_dest, dimnames_dest) +end + +function Base.:*(a1::AbstractNamedDimsArray, a2::AbstractNamedDimsArray) + return contract(a1, a2) +end + +function LinearAlgebra.mul!( + a_dest::AbstractNamedDimsArray, + a1::AbstractNamedDimsArray, + a2::AbstractNamedDimsArray, + α::Number=true, + β::Number=false, +) + contract!(a_dest, a1, a2, α, β) + return a_dest +end + function TensorAlgebra.blockedperm(na::AbstractNamedDimsArray, nameddim_blocks::Tuple...) # Extract names if named dimensions or axes were passed dimname_blocks = map(group -> name.(group), nameddim_blocks) @@ -28,7 +70,7 @@ function TensorAlgebra.fusedims(na::AbstractNamedDimsArray, fusions::Pair...) end perm = blockedperm(na, dimnames_fuse...) a_fused = fusedims(unname(na), perm) - return named(a_fused, dimnames_fused) + return nameddims(a_fused, dimnames_fused) end function TensorAlgebra.splitdims(na::AbstractNamedDimsArray, splitters::Pair...) @@ -49,5 +91,27 @@ function TensorAlgebra.splitdims(na::AbstractNamedDimsArray, splitters::Pair...) names_split[fused_dim] = split_names end names_split = reduce((x, y) -> (x..., y...), names_split) - return named(a_split, names_split) + return nameddims(a_split, names_split) +end + +function LinearAlgebra.qr( + a::AbstractNamedDimsArray, dimnames_codomain, dimnames_domain; positive=nothing +) + @assert isnothing(positive) || !positive + # TODO: This should be `TensorAlgebra.qr` rather than overloading `LinearAlgebra.qr`. + # TODO: Don't require wrapping in `Tuple`. + q, r = qr( + unname(a), + Tuple(dimnames(a)), + Tuple(name.(dimnames_codomain)), + Tuple(name.(dimnames_domain)), + ) + name_qr = randname(dimnames(a)[1]) + dimnames_q = (name.(dimnames_codomain)..., name_qr) + dimnames_r = (name_qr, name.(dimnames_domain)...) + return nameddims(q, dimnames_q), nameddims(r, dimnames_r) +end + +function LinearAlgebra.qr(a::AbstractNamedDimsArray, dimnames_codomain; kwargs...) + return qr(a, dimnames_codomain, setdiff(dimnames(a), name.(dimnames_codomain)); kwargs...) end diff --git a/src/traits.jl b/src/traits.jl deleted file mode 100644 index c971ae7..0000000 --- a/src/traits.jl +++ /dev/null @@ -1 +0,0 @@ -isnamed(::Any) = false diff --git a/test/basics/test_adapt.jl b/test/basics/test_adapt.jl new file mode 100644 index 0000000..7d553d3 --- /dev/null +++ b/test/basics/test_adapt.jl @@ -0,0 +1,11 @@ +using Adapt: adapt +using NamedDimsArrays: nameddims +using Test: @test, @testset + +@testset "Adapt (eltype=$elt)" for elt in + (Float32, Float64, Complex{Float32}, Complex{Float64}) + na = nameddims(randn(2, 2), ("i", "j")) + na_complex = adapt(Array{complex(elt)}, na) + @test na ≈ na_complex + @test eltype(na_complex) === complex(elt) +end diff --git a/test/basics/test_basic.jl b/test/basics/test_basic.jl new file mode 100644 index 0000000..abe9862 --- /dev/null +++ b/test/basics/test_basic.jl @@ -0,0 +1,250 @@ +using Test: @test, @test_throws, @testset +using NamedDimsArrays: + NamedDimsArrays, + AbstractNamedDimsArray, + AbstractNamedDimsMatrix, + Name, + NameMismatch, + NamedCartesianIndex, + NamedCartesianIndices, + NamedDimsArray, + NamedDimsMatrix, + aligndims, + aligneddims, + dename, + denamed, + dim, + dimnames, + dims, + fusednames, + isnamed, + name, + named, + nameddims, + replacedimnames, + setdimnames, + unname, + unnamed + +@testset "NamedDimsArrays.jl" begin + @testset "Basic functionality" begin + elt = Float64 + a = randn(elt, 3, 4) + @test !isnamed(a) + na = nameddims(a, ("i", "j")) + @test na isa NamedDimsMatrix{elt,Matrix{elt}} + @test na isa AbstractNamedDimsMatrix{elt} + @test na isa NamedDimsArray{elt} + @test na isa AbstractNamedDimsArray{elt} + for na′ in (nameddims(na, ("j", "i")), NamedDimsArray(na, ("j", "i"))) + @test na′ isa NamedDimsMatrix{elt,<:PermutedDimsArray} + @test dimnames(na′) == ("j", "i") + @test na′ == na + end + @test_throws NameMismatch nameddims(na, ("j", "k")) + @test_throws NameMismatch NamedDimsArray(na, ("j", "k")) + @test_throws MethodError dename(a) + @test_throws MethodError dename(a, ("i", "j")) + @test_throws MethodError denamed(a, ("i", "j")) + @test_throws MethodError unname(a, ("i", "j")) + @test_throws MethodError unnamed(a, ("i", "j")) + @test unname(a) == a + @test dename(na) == a + si, sj = size(na) + ai, aj = axes(na) + @test name(si) == "i" + @test name(sj) == "j" + @test name(ai) == "i" + @test name(aj) == "j" + @test isnamed(na) + @test dimnames(na) == ("i", "j") + @test dimnames(na, 1) == "i" + @test dimnames(na, 2) == "j" + @test dim(na, "i") == 1 + @test dim(na, "j") == 2 + @test dims(na, ("j", "i")) == (2, 1) + @test na[1, 1] == a[1, 1] + + # getindex syntax + i = Name("i") + j = Name("j") + @test a[i, j] == na + @test @view(a[i, j]) == na + @test na[j[1], i[2]] == a[2, 1] + @test dimnames(na[j, i]) == ("j", "i") + @test na[j, i] == na + @test @view(na[j, i]) == na + @test i[axes(a, 1)] == ai + @test j[axes(a, 2)] == aj + @test axes(na, i) == ai + @test axes(na, j) == aj + @test size(na, i) == si + @test size(na, j) == sj + + # aliasing + a′ = randn(2, 2) + a′ij = @view a′[i, j] + a′ij[i[1], j[2]] = 12 + @test a′ij[i[1], j[2]] == 12 + @test a′[1, 2] == 12 + a′ji = @view a′ij[j, i] + a′ji[i[2], j[1]] = 21 + @test a′ji[i[2], j[1]] == 21 + @test a′ij[i[2], j[1]] == 21 + @test a′[2, 1] == 21 + + a′ = randn(2, 2) + a′ij = a′[i, j] + a′ij[i[1], j[2]] = 12 + @test a′ij[i[1], j[2]] == 12 + @test a′[1, 2] ≠ 12 + a′ji = a′ij[j, i] + a′ji[i[2], j[1]] = 21 + @test a′ji[i[2], j[1]] == 21 + @test a′ij[i[2], j[1]] ≠ 21 + @test a′[2, 1] ≠ 21 + + a′ = dename(na) + @test a′ isa Matrix{elt} + @test a′ == a + for a′ in (dename(na, ("j", "i")), unname(na, ("j", "i"))) + @test a′ isa Matrix{elt} + @test a′ == a' + end + for a′ in (denamed(na, ("j", "i")), unnamed(na, ("j", "i"))) + @test a′ isa PermutedDimsArray{elt} + @test a′ == a' + end + nb = setdimnames(na, ("k", "j")) + @test dimnames(nb) == ("k", "j") + @test dename(nb) == a + nb = replacedimnames(na, "i" => "k") + @test dimnames(nb) == ("k", "j") + @test dename(nb) == a + nb = replacedimnames(na, named(3, "i") => named(3, "k")) + @test dimnames(nb) == ("k", "j") + @test dename(nb) == a + nb = replacedimnames(n -> n == "i" ? "k" : n, na) + @test dimnames(nb) == ("k", "j") + @test dename(nb) == a + nb = setdimnames(na, named(3, "i") => named(3, "k")) + na[1, 1] = 11 + @test na[1, 1] == 11 + @test size(na) == (named(3, "i"), named(4, "j")) + @test length(na) == named(12, fusednames("i", "j")) + @test axes(na) == (named(1:3, "i"), named(1:4, "j")) + @test randn(named.((3, 4), ("i", "j"))) isa NamedDimsArray + @test na["i" => 1, "j" => 2] == a[1, 2] + @test na["j" => 2, "i" => 1] == a[1, 2] + na["j" => 2, "i" => 1] = 12 + @test na[1, 2] == 12 + @test na[j => 1, i => 2] == a[2, 1] + @test na[aj => 1, ai => 2] == a[2, 1] + na[j => 1, i => 2] = 21 + @test na[2, 1] == 21 + na[aj => 1, ai => 2] = 2211 + @test na[2, 1] == 2211 + na′ = aligndims(na, ("j", "i")) + @test unname(na′) isa Matrix{elt} + @test a == permutedims(unname(na′), (2, 1)) + na′ = aligneddims(na, ("j", "i")) + @test unname(na′) isa PermutedDimsArray{elt} + @test a == permutedims(unname(na′), (2, 1)) + na′ = aligndims(na, (j, i)) + @test unname(na′) isa Matrix{elt} + @test a == permutedims(unname(na′), (2, 1)) + na′ = aligneddims(na, (j, i)) + @test unname(na′) isa PermutedDimsArray{elt} + @test a == permutedims(unname(na′), (2, 1)) + na′ = aligndims(na, (aj, ai)) + @test unname(na′) isa Matrix{elt} + @test a == permutedims(unname(na′), (2, 1)) + na′ = aligneddims(na, (aj, ai)) + @test unname(na′) isa PermutedDimsArray{elt} + @test a == permutedims(unname(na′), (2, 1)) + + na = nameddims(randn(elt, 2, 3), (:i, :j)) + nb = nameddims(randn(elt, 3, 2), (:j, :i)) + nc = zeros(elt, named.((2, 3), (:i, :j))) + Is = eachindex(na, nb) + @test Is isa NamedCartesianIndices{2} + @test issetequal(dimnames(Is), (:i, :j)) + for I in Is + @test I isa NamedCartesianIndex{2} + @test issetequal(name.(Tuple(I)), (:i, :j)) + nc[I] = na[I] + nb[I] + end + @test dename(nc, (:i, :j)) ≈ dename(na, (:i, :j)) + dename(nb, (:i, :j)) + + a = nameddims(randn(elt, 2, 3), (:i, :j)) + b = nameddims(randn(elt, 3, 2), (:j, :i)) + c = a + b + @test dename(c, (:i, :j)) ≈ dename(a, (:i, :j)) + dename(b, (:i, :j)) + c = a .+ b + @test dename(c, (:i, :j)) ≈ dename(a, (:i, :j)) + dename(b, (:i, :j)) + c = map(+, a, b) + @test dename(c, (:i, :j)) ≈ dename(a, (:i, :j)) + dename(b, (:i, :j)) + c = nameddims(Array{elt}(undef, 2, 3), (:i, :j)) + c = map!(+, c, a, b) + @test dename(c, (:i, :j)) ≈ dename(a, (:i, :j)) + dename(b, (:i, :j)) + c = a .+ 2 .* b + @test dename(c, (:i, :j)) ≈ dename(a, (:i, :j)) + 2 * dename(b, (:i, :j)) + c = nameddims(Array{elt}(undef, 2, 3), (:i, :j)) + c .= a .+ 2 .* b + @test dename(c, (:i, :j)) ≈ dename(a, (:i, :j)) + 2 * dename(b, (:i, :j)) + end + @testset "Shorthand constructors (eltype=$elt)" for elt in ( + Float32, ComplexF32, Float64, ComplexF64 + ) + i, j = named.((2, 2), ("i", "j")) + value = rand(elt) + for na in (zeros(elt, i, j), zeros(elt, (i, j))) + @test eltype(na) === elt + @test na isa NamedDimsArray + @test dimnames(na) == ("i", "j") + @test iszero(na) + end + for na in (fill(value, i, j), fill(value, (i, j))) + @test eltype(na) === elt + @test na isa NamedDimsArray + @test dimnames(na) == ("i", "j") + @test all(isequal(value), na) + end + for na in (rand(elt, i, j), rand(elt, (i, j))) + @test eltype(na) === elt + @test na isa NamedDimsArray + @test dimnames(na) == ("i", "j") + @test !iszero(na) + @test all(x -> real(x) > 0, na) + end + for na in (randn(elt, i, j), randn(elt, (i, j))) + @test eltype(na) === elt + @test na isa NamedDimsArray + @test dimnames(na) == ("i", "j") + @test !iszero(na) + end + end + @testset "Shorthand constructors (eltype=unspecified)" begin + i, j = named.((2, 2), ("i", "j")) + default_elt = Float64 + for na in (zeros(i, j), zeros((i, j))) + @test eltype(na) === default_elt + @test na isa NamedDimsArray + @test dimnames(na) == ("i", "j") + @test iszero(na) + end + for na in (rand(i, j), rand((i, j))) + @test eltype(na) === default_elt + @test na isa NamedDimsArray + @test dimnames(na) == ("i", "j") + @test !iszero(na) + @test all(x -> real(x) > 0, na) + end + for na in (randn(i, j), randn((i, j))) + @test eltype(na) === default_elt + @test na isa NamedDimsArray + @test dimnames(na) == ("i", "j") + @test !iszero(na) + end + end +end diff --git a/test/test_NamedDimsArraysTensorAlgebraExt.jl b/test/basics/test_tensoralgebra.jl similarity index 64% rename from test/test_NamedDimsArraysTensorAlgebraExt.jl rename to test/basics/test_tensoralgebra.jl index 5817077..ceaa4fb 100644 --- a/test/test_NamedDimsArraysTensorAlgebraExt.jl +++ b/test/basics/test_tensoralgebra.jl @@ -1,30 +1,30 @@ -using Test: @test, @testset, @test_broken -using NamedDimsArrays: named, unname -using TensorAlgebra: TensorAlgebra, contract, fusedims, splitdims using LinearAlgebra: qr +using NamedDimsArrays: named, dename +using TensorAlgebra: TensorAlgebra, contract, fusedims, splitdims +using Test: @test, @testset, @test_broken elts = (Float32, Float64, Complex{Float32}, Complex{Float64}) -@testset "NamedDimsArraysTensorAlgebraExt (eltype=$(elt))" for elt in elts +@testset "TensorAlgebra (eltype=$(elt))" for elt in elts @testset "contract" begin i = named(2, "i") j = named(2, "j") k = named(2, "k") na1 = randn(elt, i, j) na2 = randn(elt, j, k) - na_dest = TensorAlgebra.contract(na1, na2) + na_dest = contract(na1, na2) @test eltype(na_dest) === elt - @test unname(na_dest, (i, k)) ≈ unname(na1) * unname(na2) + @test dename(na_dest, (i, k)) ≈ dename(na1) * dename(na2) end @testset "fusedims" begin i, j, k, l = named.((2, 3, 4, 5), ("i", "j", "k", "l")) na = randn(elt, i, j, k, l) na_fused = fusedims(na, (k, i) => "a", (j, l) => "b") # Fuse all dimensions. - @test unname(na_fused, ("a", "b")) ≈ - reshape(unname(na, (k, i, j, l)), (unname(k) * unname(i), unname(j) * unname(l))) + @test dename(na_fused, ("a", "b")) ≈ + reshape(dename(na, (k, i, j, l)), (dename(k) * dename(i), dename(j) * dename(l))) na_fused = fusedims(na, (k, i) => "a") # Fuse a subset of dimensions. - @test unname(na_fused, ("a", "j", "l")) ≈ - reshape(unname(na, (k, i, j, l)), (unname(k) * unname(i), unname(j), unname(l))) + @test dename(na_fused, ("a", "j", "l")) ≈ + reshape(dename(na, (k, i, j, l)), (dename(k) * dename(i), dename(j), dename(l))) end @testset "splitdims" begin a, b = named.((6, 20), ("a", "b")) @@ -32,12 +32,12 @@ elts = (Float32, Float64, Complex{Float32}, Complex{Float64}) na = randn(elt, a, b) # Split all dimensions. na_split = splitdims(na, "a" => (k, i), "b" => (j, l)) - @test unname(na_split, ("k", "i", "j", "l")) ≈ - reshape(unname(na, ("a", "b")), (unname(k), unname(i), unname(j), unname(l))) + @test dename(na_split, ("k", "i", "j", "l")) ≈ + reshape(dename(na, ("a", "b")), (dename(k), dename(i), dename(j), dename(l))) # Split a subset of dimensions. na_split = splitdims(na, "a" => (j, i)) - @test unname(na_split, ("j", "i", "b")) ≈ - reshape(unname(na, ("a", "b")), (unname(j), unname(i), unname(b))) + @test dename(na_split, ("j", "i", "b")) ≈ + reshape(dename(na, ("a", "b")), (dename(j), dename(i), dename(b))) end @testset "qr" begin dims = (2, 2, 2, 2) @@ -46,7 +46,7 @@ elts = (Float32, Float64, Complex{Float32}, Complex{Float64}) na = randn(elt, i, j) # TODO: Should this be allowed? # TODO: Add support for specifying new name. - q, r = qr(na) + q, r = qr(na, (i,)) @test q * r ≈ na na = randn(elt, i, j, k, l) diff --git a/test/test_NamedDimsArraysAdaptExt.jl b/test/test_NamedDimsArraysAdaptExt.jl deleted file mode 100644 index 3518acd..0000000 --- a/test/test_NamedDimsArraysAdaptExt.jl +++ /dev/null @@ -1,12 +0,0 @@ -using Test: @test, @testset -using Adapt: adapt -using NamedDimsArrays: named - -@testset "NamedDimsArraysAdaptExt (eltype=$elt)" for elt in ( - Float32, Float64, Complex{Float32}, Complex{Float64} -) - na = named(randn(2, 2), ("i", "j")) - na_complex = adapt(Array{complex(elt)}, na) - @test na ≈ na_complex - @test eltype(na_complex) === complex(elt) -end diff --git a/test/test_NamedDimsArraysSparseArraysBaseExt.jl b/test/test_NamedDimsArraysSparseArraysBaseExt.jl deleted file mode 100644 index ece9338..0000000 --- a/test/test_NamedDimsArraysSparseArraysBaseExt.jl +++ /dev/null @@ -1,11 +0,0 @@ -using LinearAlgebra: Diagonal -using Test: @test, @testset -using SparseArraysBase: densearray -using NamedDimsArrays: named, unname - -@testset "NamedDimsArraysSparseArraysBaseExt (eltype=$elt)" for elt in (Float32, Float64) - na = named(Diagonal(randn(2)), ("i", "j")) - na_dense = densearray(na) - @test na ≈ na_dense - @test unname(na_dense) isa Array -end diff --git a/test/test_basic.jl b/test/test_basic.jl deleted file mode 100644 index f40e09a..0000000 --- a/test/test_basic.jl +++ /dev/null @@ -1,106 +0,0 @@ -using Test: @test, @testset -using NamedDimsArrays: - NamedDimsArrays, - NamedDimsArray, - align, - dimnames, - isnamed, - named, - namedaxes, - namedsize, - unname - -@testset "NamedDimsArrays $(@__FILE__)" begin - @testset "Basic functionality" begin - a = randn(3, 4) - na = named(a, ("i", "j")) - # TODO: Just call this `size`? - i, j = namedsize(na) - # TODO: Just call `namedaxes`? - ai, aj = namedaxes(na) - @test !isnamed(a) - @test isnamed(na) - @test dimnames(na) == ("i", "j") - @test na[1, 1] == a[1, 1] - na[1, 1] = 11 - @test na[1, 1] == 11 - # TODO: Should `size` output `namedsize`? - @test size(na) == (3, 4) - @test namedsize(na) == (named(3, "i"), named(4, "j")) - @test length(na) == 12 - # TODO: Should `axes` output `namedaxes`? - @test axes(na) == (1:3, 1:4) - @test namedaxes(na) == (named(1:3, "i"), named(1:4, "j")) - @test randn(named(3, "i"), named(4, "j")) isa NamedDimsArray - @test na["i" => 1, "j" => 2] == a[1, 2] - @test na["j" => 2, "i" => 1] == a[1, 2] - na["j" => 2, "i" => 1] = 12 - @test na[1, 2] == 12 - @test na[j => 1, i => 2] == a[2, 1] - @test na[aj => 1, ai => 2] == a[2, 1] - na[j => 1, i => 2] = 21 - @test na[2, 1] == 21 - na[aj => 1, ai => 2] = 2211 - @test na[2, 1] == 2211 - na′ = align(na, ("j", "i")) - @test a == permutedims(unname(na′), (2, 1)) - na′ = align(na, (j, i)) - @test a == permutedims(unname(na′), (2, 1)) - na′ = align(na, (aj, ai)) - @test a == permutedims(unname(na′), (2, 1)) - end - @testset "Shorthand constructors (eltype=$elt)" for elt in ( - Float32, ComplexF32, Float64, ComplexF64 - ) - i, j = named.((2, 2), ("i", "j")) - value = rand(elt) - for na in (zeros(elt, i, j), zeros(elt, (i, j))) - @test eltype(na) === elt - @test na isa NamedDimsArray - @test dimnames(na) == ("i", "j") - @test iszero(na) - end - for na in (fill(value, i, j), fill(value, (i, j))) - @test eltype(na) === elt - @test na isa NamedDimsArray - @test dimnames(na) == ("i", "j") - @test all(isequal(value), na) - end - for na in (rand(elt, i, j), rand(elt, (i, j))) - @test eltype(na) === elt - @test na isa NamedDimsArray - @test dimnames(na) == ("i", "j") - @test !iszero(na) - @test all(x -> real(x) > 0, na) - end - for na in (randn(elt, i, j), randn(elt, (i, j))) - @test eltype(na) === elt - @test na isa NamedDimsArray - @test dimnames(na) == ("i", "j") - @test !iszero(na) - end - end - @testset "Shorthand constructors (eltype=unspecified)" begin - i, j = named.((2, 2), ("i", "j")) - default_elt = Float64 - for na in (zeros(i, j), zeros((i, j))) - @test eltype(na) === default_elt - @test na isa NamedDimsArray - @test dimnames(na) == ("i", "j") - @test iszero(na) - end - for na in (rand(i, j), rand((i, j))) - @test eltype(na) === default_elt - @test na isa NamedDimsArray - @test dimnames(na) == ("i", "j") - @test !iszero(na) - @test all(x -> real(x) > 0, na) - end - for na in (randn(i, j), randn((i, j))) - @test eltype(na) === default_elt - @test na isa NamedDimsArray - @test dimnames(na) == ("i", "j") - @test !iszero(na) - end - end -end diff --git a/test/test_tensoralgebra.jl b/test/test_tensoralgebra.jl deleted file mode 100644 index 6335cf9..0000000 --- a/test/test_tensoralgebra.jl +++ /dev/null @@ -1,78 +0,0 @@ -using Test: @test, @testset -using NamedDimsArrays: dimnames, named, unname, isnamed -@testset "NamedDimsArrays $(@__FILE__) (eltype=$elt)" for elt in ( - Float32, ComplexF32, Float64, ComplexF64 -) - a = randn(elt, 2, 3) - na = named(a, ("i", "j")) - b = randn(elt, 3, 2) - nb = named(b, ("j", "i")) - - nc = similar(na) - @test size(nc) == (2, 3) - @test eltype(nc) == elt - @test dimnames(nc) == ("i", "j") - - nc = similar(na, Float32) - @test size(nc) == (2, 3) - @test eltype(nc) == Float32 - @test dimnames(nc) == ("i", "j") - - c = similar(na, (3, 4)) - @test size(c) == (3, 4) - @test eltype(c) == elt - @test c isa typeof(a) - - c = similar(na, 3, 4) - @test size(c) == (3, 4) - @test eltype(c) == elt - @test c isa typeof(a) - - c = similar(na, Float32, (3, 4)) - @test size(c) == (3, 4) - @test eltype(c) == Float32 - @test !isnamed(c) - - c = similar(na, Float32, 3, 4) - @test size(c) == (3, 4) - @test eltype(c) == Float32 - @test !isnamed(c) - - nc = permutedims(na, (2, 1)) - @test unname(nc) ≈ permutedims(unname(na), (2, 1)) - @test dimnames(nc) == ("j", "i") - @test nc ≈ na - - nc = 2 * na - @test unname(nc) ≈ 2 * a - @test eltype(nc) === elt - - nc = 2 .* na - @test unname(nc) ≈ 2 * a - @test eltype(nc) === elt - - nc = na + nb - @test unname(nc, ("i", "j")) ≈ a + permutedims(b, (2, 1)) - @test eltype(nc) === elt - - nc = na .+ nb - @test unname(nc, ("i", "j")) ≈ a + permutedims(b, (2, 1)) - @test eltype(nc) === elt - - nc = map(+, na, nb) - @test unname(nc, ("i", "j")) ≈ a + permutedims(b, (2, 1)) - @test eltype(nc) === elt - - nc = named(randn(elt, 2, 3), ("i", "j")) - map!(+, nc, na, nb) - @test unname(nc, ("i", "j")) ≈ a + permutedims(b, (2, 1)) - @test eltype(nc) === elt - - nc = na - nb - @test unname(nc, ("i", "j")) ≈ a - permutedims(b, (2, 1)) - @test eltype(nc) === elt - - nc = na .- nb - @test unname(nc, ("i", "j")) ≈ a - permutedims(b, (2, 1)) - @test eltype(nc) === elt -end