From 8f3e8d0506af907c6d7d9bdaffdb3f6f9e145802 Mon Sep 17 00:00:00 2001 From: Matthew Fishman Date: Sat, 26 Sep 2026 13:22:18 -0400 Subject: [PATCH 1/7] Rename the named-dimension interface and make construction strict Brings the named-dimension interface onto plainer names, with `dimnames` becoming `Base.names` and the lowercase constructors giving way to the CamelCase ones. Breaking: a dimension given as an index now asserts its space on construction, and `matricize` returns a plain matrix. Co-Authored-By: Claude Opus 5 (1M context) --- Project.toml | 4 +- docs/Project.toml | 2 +- docs/src/dev_interface.md | 27 +- examples/Project.toml | 2 +- .../ITensorBaseAdaptExt.jl | 4 +- ext/ITensorBaseGradedArraysExt.jl | 8 +- .../ITensorBaseMooncakeExt.jl | 10 +- ext/ITensorBaseTensorKitExt.jl | 7 - src/ITensorBase.jl | 12 +- src/abstractnamedarray.jl | 32 +- src/abstractnamedtensor.jl | 292 +++++-------- src/broadcast.jl | 50 ++- src/index.jl | 8 +- src/lazyitensors/evaluation_order.jl | 4 +- src/lazyitensors/itensorbaseextensions.jl | 4 +- src/lazyitensors/lazyinterface.jl | 6 +- src/lazyitensors/lazyitensor.jl | 6 +- src/lazyitensors/symbolicitensor.jl | 21 +- src/name.jl | 4 +- src/named.jl | 89 ++-- src/namedtensor.jl | 157 +++++-- src/namedtensoroperator.jl | 47 +-- src/namedunitrange.jl | 55 +-- src/tensoralgebra.jl | 396 +++++++++--------- test/Project.toml | 2 +- test/test_abstracttrees.jl | 4 +- test/test_adapt.jl | 4 +- test/test_basics.jl | 51 ++- test/test_exports.jl | 12 +- test/test_gradedarraysext.jl | 60 ++- test/test_lazyitensors.jl | 30 +- test/test_linearalgebra.jl | 6 +- test/test_mooncakeext.jl | 10 +- test/test_nameddims_basics.jl | 184 ++++---- test/test_namedinteger.jl | 14 +- test/test_operator.jl | 76 ++-- test/test_tensoralgebra.jl | 93 ++-- test/test_tensorkitext.jl | 61 ++- test/test_vectorinterface.jl | 18 +- 39 files changed, 980 insertions(+), 892 deletions(-) diff --git a/Project.toml b/Project.toml index 0d7532fc..d8db8390 100644 --- a/Project.toml +++ b/Project.toml @@ -1,6 +1,6 @@ name = "ITensorBase" uuid = "4795dd04-0d67-49bb-8f44-b89c448a1dc7" -version = "0.14.2" +version = "0.15.0" authors = ["ITensor developers and contributors"] [workspace] @@ -18,7 +18,6 @@ Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" SimpleTraits = "699a6c99-e7fa-54fc-8d76-47d257e15c1d" TensorAlgebra = "68bd88dc-f39d-4e12-b2ca-f046b68fcc6a" TermInterface = "8ea1fca8-c5ef-4a55-8b96-4e9afe9c9a3c" -TupleTools = "9d95972d-f1c8-5527-a6e0-b4b365fa01f6" UUIDs = "cf7118a7-6976-5b1a-9a39-7adc72f591a4" VectorInterface = "409d34a3-91d5-4945-b6ec-7529ddf182d8" WrappedUnions = "325db55a-9c6c-5b90-b1a2-ec87e7a38c44" @@ -56,7 +55,6 @@ TensorAlgebra = "0.21" TensorKit = "0.17" TensorKitSectors = "0.3.9" TermInterface = "2" -TupleTools = "1.6" UUIDs = "1.10" VectorInterface = "0.6" WrappedUnions = "0.3" diff --git a/docs/Project.toml b/docs/Project.toml index 570b05ff..cc54569c 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -12,7 +12,7 @@ path = ".." [compat] Documenter = "1" -ITensorBase = "0.14" +ITensorBase = "0.15" ITensorFormatter = "0.2.27" Literate = "2" MatrixAlgebraKit = "0.2, 0.3, 0.4, 0.5, 0.6" diff --git a/docs/src/dev_interface.md b/docs/src/dev_interface.md index 56a11350..47c30397 100644 --- a/docs/src/dev_interface.md +++ b/docs/src/dev_interface.md @@ -12,8 +12,11 @@ stable user-facing API. For the stable user-facing API, see the [User Interface] A concrete tensor type subtypes [`AbstractNamedTensor`](@ref). [`NamedTensor`](@ref) is the built-in implementation, and [`ITensor`](@ref) is the `NamedTensor` with dimension -names that are [`IndexName`](@ref)s. Its `NamedTensor(array, dimnames)` constructor pairs an array of -any kind with its dimension names directly. User code usually builds one by calling an array constructor on indices or by +names that are [`IndexName`](@ref)s. Its `NamedTensor(array, names)` constructor pairs an array of +any kind with its dimension names directly, and a name given as an index also asserts that +dimension's space. A second form, `NamedTensor(array, codomain_names, domain_names)`, splits the +dimensions into a codomain and a domain group, as a map from the domain to the codomain. +User code usually builds one by calling an array constructor on indices or by indexing an array (see [Constructors](@ref)) rather than calling it. The underlying named-range model has [`NamedUnitRange`](@ref) as the named-range type that a tensor's dimensions are ([`Index`](@ref) is the flavor keyed by an index name). @@ -26,25 +29,23 @@ NamedUnitRange ## Named array operations -Construct named objects with [`named`](@ref) and [`nameddims`](@ref), recover their parts -with [`name`](@ref), [`unnamed`](@ref), and [`dimnames`](@ref), and query their types with -[`dimnametype`](@ref), [`nametype`](@ref), and [`unnamedtype`](@ref). [`setname`](@ref) and -[`replacedimnames`](@ref) rename, and [`aligndims`](@ref) and [`aligneddims`](@ref) reorder a +Construct named objects with the [`NamedTensor`](@ref) and [`NamedUnitRange`](@ref) +constructors, recover their parts with [`name`](@ref), [`unnamed`](@ref), and +[`Base.names`](@ref), and query their types with [`nametype`](@ref) and +[`unnamedtype`](@ref). [`setname`](@ref) and +[`rename`](@ref) change names, and [`align`](@ref) and [`aligned`](@ref) reorder a tensor's dimensions by name (a copy and a view, respectively). ```@docs; canonical=false -named -nameddims name unnamed -dimnames -dimnametype +Base.names nametype unnamedtype setname -replacedimnames -aligndims -aligneddims +rename +align +aligned ``` ## Experimental diff --git a/examples/Project.toml b/examples/Project.toml index 4d459436..c219fcf2 100644 --- a/examples/Project.toml +++ b/examples/Project.toml @@ -6,5 +6,5 @@ MatrixAlgebraKit = "6c742aac-3347-4629-af66-fc926824e5e4" path = ".." [compat] -ITensorBase = "0.14" +ITensorBase = "0.15" MatrixAlgebraKit = "0.2, 0.3, 0.4, 0.5, 0.6" diff --git a/ext/ITensorBaseAdaptExt/ITensorBaseAdaptExt.jl b/ext/ITensorBaseAdaptExt/ITensorBaseAdaptExt.jl index 5b9d653a..ae8e014a 100644 --- a/ext/ITensorBaseAdaptExt/ITensorBaseAdaptExt.jl +++ b/ext/ITensorBaseAdaptExt/ITensorBaseAdaptExt.jl @@ -1,10 +1,10 @@ module ITensorBaseAdaptExt using Adapt: Adapt, adapt -using ITensorBase: AbstractNamedTensor, dimnames, nameddims, unnamed +using ITensorBase: AbstractNamedTensor, NamedTensor, unnamed function Adapt.adapt_structure(to, a::AbstractNamedTensor) - return nameddims(adapt(to, unnamed(a)), dimnames(a)) + return NamedTensor(adapt(to, unnamed(a)), names(a)) end end diff --git a/ext/ITensorBaseGradedArraysExt.jl b/ext/ITensorBaseGradedArraysExt.jl index 59d9343b..07fc1531 100644 --- a/ext/ITensorBaseGradedArraysExt.jl +++ b/ext/ITensorBaseGradedArraysExt.jl @@ -1,7 +1,7 @@ module ITensorBaseGradedArraysExt using GradedArrays: SectorRange -using ITensorBase: ITensorBase, name, nameddims, uniquename, unnamed +using ITensorBase: ITensorBase, NamedTensor, name, uniquename, unnamed using Random: AbstractRNG, default_rng using TensorKitSectors: Sector @@ -15,9 +15,9 @@ const NamedUnitRange = ITensorBase.NamedUnitRange # Name the delegated result: the physical-leg names followed by a fresh name for the dangling aux # leg, minted of the legs' name type (not hardcoded to `IndexName`). function nameddims_aux(a, codomain, domain) - dimnames = name.((codomain..., domain...)) - aux_name = uniquename(eltype(dimnames)) - return nameddims(a, (dimnames..., aux_name)) + names = name.((codomain..., domain...)) + aux_name = uniquename(eltype(names)) + return NamedTensor(a, (names..., aux_name)) end # Three signature groups, each carrying a named physical axis so overloading `Base` is not piracy: diff --git a/ext/ITensorBaseMooncakeExt/ITensorBaseMooncakeExt.jl b/ext/ITensorBaseMooncakeExt/ITensorBaseMooncakeExt.jl index 8b52725c..cdaed173 100644 --- a/ext/ITensorBaseMooncakeExt/ITensorBaseMooncakeExt.jl +++ b/ext/ITensorBaseMooncakeExt/ITensorBaseMooncakeExt.jl @@ -1,18 +1,18 @@ module ITensorBaseMooncakeExt -using ITensorBase: AbstractNamedTensor, NamedUnitRange, dimnames, dimnames_setdiff, inds, - name, nameperm, to_inds, uniquename +using ITensorBase: AbstractNamedTensor, NamedUnitRange, inds, name, nameperm, names_setdiff, + to_inds, uniquename using Mooncake: Mooncake, @zero_derivative, DefaultCtx Mooncake.tangent_type(::Type{<:NamedUnitRange}) = Mooncake.NoTangent @zero_derivative DefaultCtx Tuple{typeof(nameperm), Any, Any, Any} -# `dimnames(::NamedTensor)` returns the stored names `Vector` directly, so its output +# `names(::NamedTensor)` returns the stored names `Vector` directly, so its output # aliases a field, where `@zero_derivative` is documented to be incorrect. Let # Mooncake differentiate it through the underlying `getfield`, whose built-in rule # preserves the aliasing (the names are non-differentiable, so the result is zero). -@zero_derivative DefaultCtx Tuple{typeof(dimnames), Any, Any} -@zero_derivative DefaultCtx Tuple{typeof(dimnames_setdiff), Any, Any} +@zero_derivative DefaultCtx Tuple{typeof(names), Any, Any} +@zero_derivative DefaultCtx Tuple{typeof(names_setdiff), Any, Any} @zero_derivative DefaultCtx Tuple{typeof(inds), Any} @zero_derivative DefaultCtx Tuple{typeof(inds), Any, Any} @zero_derivative DefaultCtx Tuple{typeof(name), Any} diff --git a/ext/ITensorBaseTensorKitExt.jl b/ext/ITensorBaseTensorKitExt.jl index 58782c97..8f15e6c7 100644 --- a/ext/ITensorBaseTensorKitExt.jl +++ b/ext/ITensorBaseTensorKitExt.jl @@ -15,13 +15,6 @@ function ITensorBase.NamedUnitRange(unnamed::ElementarySpace, name) return ITensorBase.NamedUnitRange{typeof(name), Int, typeof(unnamed)}(unnamed, name) end -# `conj(index)` lowers to `named(conj(space), name)`, and `trivialrange` mints a fresh axis via -# `namedunitrange(space, name)`, so both must rebuild a `NamedUnitRange` over the space rather than -# fall through to the generic (array-shaped) `Named`. `named` delegates to `namedunitrange`, as the -# core `AbstractUnitRange` path does. -ITensorBase.namedunitrange(r::ElementarySpace, name) = ITensorBase.NamedUnitRange(r, name) -ITensorBase.named(r::ElementarySpace, name) = ITensorBase.namedunitrange(r, name) - # The flat length of a space-backed axis is its total (dense) dimension, so `size`/`length` # of a `TensorMap`-backed ITensor report dense dimensions. function Base.length(r::NamedUnitRange{<:Any, <:Any, <:ElementarySpace}) diff --git a/src/ITensorBase.jl b/src/ITensorBase.jl index 6e3de08f..a2fb2d52 100644 --- a/src/ITensorBase.jl +++ b/src/ITensorBase.jl @@ -1,19 +1,17 @@ module ITensorBase export AbstractNamedTensor, NamedTensor, AbstractITensor, ITensor, Index, - NamedUnitRange, aligndims, aligneddims, apply, commonind, commoninds, - dimnames, dimnametype, hascommoninds, id, inds, inputaxes, inputinds, inputnames, - mapinds, - named, nameddims, noncommonind, noncommoninds, noprime, operator, outputaxes, - outputinds, - outputnames, prime, replaceinds, sim, + NamedUnitRange, align, aligned, apply, commonind, commoninds, + hascommoninds, id, inds, inputaxes, inputinds, inputnames, + nametype, noncommonind, noncommoninds, noprime, operator, + outputaxes, outputinds, outputnames, prime, sim, similar_operator, state, trycommonind, trynoncommonind, tryuniqueind, uniqueind, uniqueinds, unioninds, uniquename if VERSION >= v"1.11.0-DEV.469" eval( Meta.parse( - "public @names, IndexName, name, nametype, replacedimnames, setname, space, unnamed, unnamedtype, decoration, emptytags, gettag, gettags, hastag, plev, settags, tags, unsettags" + "public @names, IndexName, name, rename, setname, space, unnamed, unnamedtype, decoration, emptytags, gettag, gettags, hastag, plev, settags, tags, unsettags" ) ) end diff --git a/src/abstractnamedarray.jl b/src/abstractnamedarray.jl index acc37f1f..b319e5b9 100644 --- a/src/abstractnamedarray.jl +++ b/src/abstractnamedarray.jl @@ -12,16 +12,11 @@ const AbstractNamedMatrix{Name, UnnamedT} = AbstractNamedArray{Name, UnnamedT, 2 unnamed(a::AbstractNamedArray) = throw(MethodError(unnamed, Tuple{typeof(a)})) name(a::AbstractNamedArray) = throw(MethodError(name, Tuple{typeof(a)})) -# This can be customized to output different named array types, -# such as `namedarray(a::AbstractArray, name::IndexName) = Index(a, name)`. -namedarray(a::AbstractArray, name) = NamedArray(a, name) - -# Shorthand. -named(a::AbstractArray, name) = namedarray(a, name) +to_named(a::AbstractArray, name) = NamedArray(a, name) # Derived interface. # TODO: Use `Accessors.@set`? -setname(a::AbstractNamedArray, name) = namedarray(unnamed(a), name) +setname(a::AbstractNamedArray, name) = NamedArray(unnamed(a), name) # `Name` leads, so `nametype` reads it from the abstract type. The wrapped # container type lives only on the concrete subtypes, so `unnamedtype` is defined @@ -42,7 +37,9 @@ function Base.:(==)(a1::AbstractNamedArray, a2::AbstractNamedArray) end Base.hash(a::AbstractNamedArray, h::UInt) = hash_named(:NamedArray, a, h) -getindex_named(a::AbstractArray, I...) = named(getindex(unnamed(a), I...), name(a)) +# The slice can be an element, a range, or a general array depending on `I` and on what +# the parent is, so the named type it takes is only known at runtime. +getindex_named(a::AbstractArray, I...) = to_named(getindex(unnamed(a), I...), name(a)) # Array funcionality. Base.size(a::AbstractNamedArray) = size(unnamed(a)) @@ -69,20 +66,31 @@ Base.isempty(a::AbstractNamedArray) = isempty(unnamed(a)) ## Base.iterate(a::AbstractNamedArray) = isempty(a) ? nothing : (first(a), first(a)) ## function Base.iterate(a::AbstractNamedArray, i) ## i == last(a) && return nothing -## next = named(unnamed(i) + unnamed(step(a)), name(a)) +## next = Named(unnamed(i) + unnamed(step(a)), name(a)) ## return (next, next) ## end function uniquename(rng::AbstractRNG, a::AbstractNamedArray) - return named(unnamed(a), uniquename(rng, name(a))) + return setname(a, uniquename(rng, name(a))) end +# Show as the constructor call that rebuilds the value, so the output round-trips. A +# `NamedOneTo` (the common case, since it is what a tensor dimension of a plain array is) +# prints through `NamedOneTo`, the spelling that builds one from a length. function Base.show(io::IO, a::AbstractNamedArray) - print(io, "named(", unnamed(a), ", ", repr(name(a)), ")") + if a isa NamedOneTo + print(io, "NamedOneTo(", length(unnamed(a)), ", ", repr(name(a)), ")") + else + print(io, nameof(typeof(a)), "(", unnamed(a), ", ", repr(name(a)), ")") + end return nothing end function Base.show(io::IO, mime::MIME"text/plain", a::AbstractNamedArray) - print(io, "named(\n") + if a isa NamedOneTo + show(io, a) + return nothing + end + print(io, nameof(typeof(a)), "(\n") show(io, mime, unnamed(a)) print(io, ",\n ", repr(name(a)), ")") return nothing diff --git a/src/abstractnamedtensor.jl b/src/abstractnamedtensor.jl index d4566317..897c556c 100644 --- a/src/abstractnamedtensor.jl +++ b/src/abstractnamedtensor.jl @@ -17,7 +17,7 @@ by name under contraction, addition, and indexing. Unlike an `AbstractArray`, th and element type live in the data rather than the type, so `ndims` and `eltype` are not fixed at the type level. -See also [`NamedTensor`](@ref), [`dimnames`](@ref), [`inds`](@ref). +See also [`NamedTensor`](@ref), [`Base.names`](@ref), [`inds`](@ref). """ abstract type AbstractNamedTensor{DimName} end @@ -28,8 +28,8 @@ abstract type AbstractNamedTensor{DimName} end Base.ndims(::Type{<:AbstractNamedTensor}) = Any """ - dimnames(a::AbstractNamedTensor) - dimnames(a::AbstractNamedTensor, dim::Int) + names(a::AbstractNamedTensor) + names(a::AbstractNamedTensor, dim::Int) The dimension names of `a`, as a collection in dimension order. The second form returns the name of dimension `dim`. @@ -37,56 +37,36 @@ the name of dimension `dim`. # Examples ```jldoctest -julia> a = nameddims(zeros(2, 3), (:i, :j)); +julia> a = NamedTensor(zeros(2, 3), (:i, :j)); -julia> dimnames(a) +julia> names(a) 2-element Vector{Symbol}: :i :j -julia> dimnames(a, 2) +julia> names(a, 2) :j ``` -See also [`inds`](@ref), [`nameddims`](@ref). +See also [`inds`](@ref), [`NamedTensor`](@ref). """ -function dimnames end -dimnames(a::AbstractNamedTensor) = throw(MethodError(dimnames, a)) -function dimnames(a::AbstractNamedTensor, dim::Int) - return dimnames(a)[dim] +Base.names(a::AbstractNamedTensor) = throw(MethodError(names, a)) +function Base.names(a::AbstractNamedTensor, dim::Int) + return names(a)[dim] end -""" - dimnametype(a::AbstractNamedTensor) - dimnametype(type::Type{<:AbstractNamedTensor}) - -The type of an individual dimension name of `a`. The primary method dispatches -on the array type, and `dimnametype(a)` forwards to `dimnametype(typeof(a))`. A -type that does not fix its dimname flavor (such as the unparameterized `NamedTensor`) -returns `Any`, the same way `eltype(Array)` is `Any`. - -# Examples - -```jldoctest -julia> a = nameddims(zeros(2, 3), (:i, :j)); - -julia> dimnametype(a) -Symbol - -julia> dimnametype(typeof(a)) -Symbol -``` -""" -function dimnametype end -dimnametype(a::AbstractNamedTensor) = dimnametype(typeof(a)) -dimnametype(::Type{<:AbstractNamedTensor{DimName}}) where {DimName} = DimName -dimnametype(::Type{<:AbstractNamedTensor}) = Any +# `nametype` (documented with the named-array methods in `named.jl`) reports the type of an +# individual dimension name. `AbstractNamedTensor` is a separate hierarchy from +# `AbstractNamedArray`, so it needs its own methods. +nametype(a::AbstractNamedTensor) = nametype(typeof(a)) +nametype(::Type{<:AbstractNamedTensor{DimName}}) where {DimName} = DimName +nametype(::Type{<:AbstractNamedTensor}) = Any # Unwrapping the names (named-array interface). # TODO: Use `IsNamed` trait? unnamed(a::AbstractNamedTensor) = throw(MethodError(unnamed, a)) -function unnamed(a::AbstractNamedTensor, names) - return _permuteddims_to(unnamed(a), getperm(dimnames(a), names)) +function unnamed(a::AbstractNamedTensor, nms) + return _permuteddims_to(unnamed(a), getperm(names(a), nms)) end # Function barrier: `unnamed(a)` is abstractly typed, so dispatching on the concrete array here # makes `ndims` a compile-time constant. Building the permutation as an `ntuple(…, Val(ndims))` @@ -98,7 +78,7 @@ end @noinline function _permuteddims_to(array, perm) return permuteddims(array, ntuple(i -> perm[i], Val(TensorAlgebra.ndims(array)))) end -unname(a::AbstractNamedTensor, inds) = unnamed(aligndims(a, inds)) +unname(a::AbstractNamedTensor, inds) = unnamed(align(a, inds)) """ inds(a::AbstractNamedTensor) @@ -106,7 +86,7 @@ unname(a::AbstractNamedTensor, inds) = unnamed(aligndims(a, inds)) The named axes (indices) of `a`, as a `Vector` with one entry per dimension. Each entry pairs a dimension's axis with its name. The second form returns the index of dimension -`dim`. Compare with [`dimnames`](@ref), which returns just the names without the axes. The +`dim`. Compare with [`Base.names`](@ref), which returns just the names without the axes. The `axes` function returns the same indices as a `Tuple`, which the `AbstractArray` interface relies on; `inds` returns a `Vector` because the indices are most often manipulated as a collection (`filter`, `setdiff`, `union`). @@ -114,15 +94,15 @@ collection (`filter`, `setdiff`, `union`). # Examples ```jldoctest -julia> a = nameddims(zeros(2, 3), (:i, :j)); +julia> a = NamedTensor(zeros(2, 3), (:i, :j)); julia> inds(a) 2-element Vector{NamedUnitRange{Symbol, Int64, Base.OneTo{Int64}}}: - named(Base.OneTo(2), :i) - named(Base.OneTo(3), :j) + NamedOneTo(2, :i) + NamedOneTo(3, :j) julia> inds(a, 1) -named(Base.OneTo(2), :i) +NamedOneTo(2, :i) ``` """ function inds end @@ -132,7 +112,7 @@ inds(a::AbstractNamedTensor, dim::Int) = axes(a)[dim] isnamed(::Type{<:AbstractNamedTensor}) = true function dim(a::AbstractNamedTensor, n) - return findfirst(==(name(n)), dimnames(a)) + return findfirst(==(name(n)), names(a)) end dims(a::AbstractNamedTensor, ns) = Base.Fix1(dim, a).(ns) @@ -158,31 +138,6 @@ function to_inds(a::AbstractNamedTensor, dims) return Base.Fix1(inds, a).(is) end -# Generic construction of named dims arrays. - -""" - nameddims(a, dimnames) - -Construct a named dimensions array from an unnamed parent `a` and named dimensions -`dimnames`. The parent is usually an `AbstractArray`, but any object that a `NamedTensor` -can wrap works (e.g. a TensorKit `TensorMap`). - -# Examples - -```jldoctest -julia> nameddims(zeros(2, 3), (:i, :j)) -named(Base.OneTo(2), :i)×named(Base.OneTo(3), :j) NamedTensor{Symbol}: -2×3 Matrix{Float64}: - 0.0 0.0 0.0 - 0.0 0.0 0.0 -``` - -See also [`NamedTensor`](@ref), [`named`](@ref). -""" -function nameddims(a, dimnames) - return NamedTensor(a, dimnames) -end - #= nameddimsof(a::AbstractNamedTensor, b) @@ -192,7 +147,7 @@ object a `NamedTensor` can wrap (e.g. a TensorKit `TensorMap`), so `copy`/`zero` named tensor round-trip through whatever backend `unnamed(a)` uses. =# function nameddimsof(a::AbstractNamedTensor, b) - return nameddims(b, dimnames(a)) + return NamedTensor(b, names(a)) end # TODO: Move to `utils.jl` file. @@ -290,7 +245,7 @@ Base.Array{<:Any, N}(a::AbstractNamedTensor) where {N} = Array{eltype(a), N}(a) # Read the parent's axes through TensorAlgebra's interface (not `Base.axes`) so a non-array # backend like a TensorMap, whose axes are its native spaces, is supported. function Base.axes(a::AbstractNamedTensor) - return named.(TensorAlgebra.axes(unnamed(a)), Tuple(dimnames(a))) + return NamedUnitRange.(TensorAlgebra.axes(unnamed(a)), Tuple(names(a))) end function Base.size(a::AbstractNamedTensor) return length.(axes(a)) @@ -325,7 +280,7 @@ end function TensorAlgebra.add!( y::AbstractNamedTensor, x::AbstractNamedTensor, α::Number, β::Number ) - TensorAlgebra.add!(unnamed(y), unnamed(x, dimnames(y)), α, β) + TensorAlgebra.add!(unnamed(y), unnamed(x, names(y)), α, β) return y end @@ -394,13 +349,16 @@ Base.size(a::AbstractNamedTensor, dimname::Name) = size(a, dim(a, dimname)) # `similar(parent, elt, axes)` for dense) so non-`AbstractArray` backends whose `similar` # wants a map-shaped space (e.g. a `TensorMap`) allocate through their own overload. function similar_nameddims(a::AbstractNamedTensor, elt::Type, ax) - return nameddims( + return NamedTensor( TensorAlgebra.similar_map(unnamed(a), elt, unnamed.(Tuple(ax)), ()), name.(ax) ) end function similar_nameddims(a::AbstractArray, elt::Type, ax) - return nameddims(TensorAlgebra.similar_map(a, elt, unnamed.(Tuple(ax)), ()), name.(ax)) + return NamedTensor( + TensorAlgebra.similar_map(a, elt, unnamed.(Tuple(ax)), ()), + name.(ax) + ) end # Base.similar gets the eltype at compile time. @@ -463,16 +421,16 @@ function Base.similar(a::AbstractNamedTensor, codomain, domain) end function Base.similar(a::AbstractNamedTensor, elt::Type, codomain, domain) raw = TensorAlgebra.similar_map(unnamed(a), elt, unnamed.(codomain), unnamed.(domain)) - return nameddims(raw, (name.(codomain)..., name.(domain)...)) + return NamedTensor(raw, name.(codomain), name.(domain)) end -function setdimnames(a::AbstractNamedTensor, dimnames) - return nameddims(unnamed(a), dimnames) +function setnames(a::AbstractNamedTensor, names) + return NamedTensor(unnamed(a), names) end """ - replacedimnames(a::AbstractNamedTensor, replacements::Pair...) - replacedimnames(f, a::AbstractNamedTensor) + rename(a::AbstractNamedTensor, replacements::Pair...) + rename(f, a::AbstractNamedTensor) Return a tensor with the same data as `a` but with its dimension names replaced. The first form takes `old => new` pairs, replacing matching names and leaving the rest @@ -481,84 +439,35 @@ unchanged. The second form replaces each name with `f(name)`. # Examples ```jldoctest -julia> using ITensorBase: replacedimnames +julia> using ITensorBase: rename -julia> a = nameddims(zeros(2, 3), (:i, :j)); +julia> a = NamedTensor(zeros(2, 3), (:i, :j)); -julia> dimnames(replacedimnames(a, :i => :k)) +julia> names(rename(a, :i => :k)) 2-element Vector{Symbol}: :k :j ``` -See also [`dimnames`](@ref). +See also [`Base.names`](@ref). """ -function replacedimnames end +function rename end # `name` strips an `Index`/`NamedUnitRange` to its dimension name and passes a bare name # through unchanged, so an index-keyed pair (`i => j`) relabels like the name-keyed pair -# (`name(i) => name(j)`). `dimnames(a)` holds names, so a raw-index key would never match and +# (`name(i) => name(j)`). `names(a)` holds names, so a raw-index key would never match and # silently no-op. -function replacedimnames(a::AbstractNamedTensor, replacements::Pair...) +function rename(a::AbstractNamedTensor, replacements::Pair...) # Base's `replace(::AbstractArray)` with no pairs throws (unlike `replace(::AbstractString)`), # so short-circuit the empty case: with no replacements there is nothing to relabel. isempty(replacements) && return a replacements = map(p -> name(first(p)) => name(last(p)), replacements) - new_dimnames = replace(dimnames(a), replacements...) - return nameddims(unnamed(a), new_dimnames) + new_names = replace(names(a), replacements...) + return NamedTensor(unnamed(a), new_names) end -function replacedimnames(f, a::AbstractNamedTensor) - new_dimnames = replace(f, dimnames(a)) - return nameddims(unnamed(a), new_dimnames) +function rename(f, a::AbstractNamedTensor) + new_names = replace(f, names(a)) + return NamedTensor(unnamed(a), new_names) end -mapdimnames(f, a::AbstractNamedTensor) = replacedimnames(f, a) - -""" - replaceinds(a::AbstractNamedTensor, replacements::Pair...) - replaceinds(f, a::AbstractNamedTensor) - -Return a tensor with the same data as `a`, with its indices relabeled to the ones specified. -The pair form takes `old => new` index pairs, and the function form relabels each index `i` -using `f(i)`. - -# Examples - -```jldoctest -julia> i, j, k = Index.((2, 3, 2)); - -julia> t = randn(i, j); - -julia> inds(replaceinds(t, i => k)) == [k, j] -true -``` - -See also [`mapinds`](@ref), [`replacedimnames`](@ref). -""" -function replaceinds(a::AbstractNamedTensor, replacements::Pair...) - return replacedimnames(a, replacements...) -end -replaceinds(f, a::AbstractNamedTensor) = replaceinds(a, map(i -> i => f(i), inds(a))...) - -""" - mapinds(f, a::AbstractNamedTensor) - -Return a tensor with the same data as `a`, with each index `i` relabeled using `f(i)`. This is -the function form of [`replaceinds`](@ref), taking a function input instead of `old => new` -pairs. - -# Examples - -```jldoctest -julia> i, j = Index.((2, 3)); - -julia> t = randn(i, j); - -julia> inds(mapinds(prime, t)) == [prime(i), prime(j)] -true -``` - -See also [`replaceinds`](@ref). -""" -mapinds(f, a::AbstractNamedTensor) = replaceinds(f, a) # Name-based index-set algebra (the `commoninds`/etc. surface). Layered in three: # small order-preserving set ops on arbitrary collections, the same ops keyed by index @@ -936,8 +845,8 @@ function Base.eachindex( a1::AbstractNamedTensor, a_rest::AbstractNamedTensor... ) - all(a -> issetequal(dimnames(a1), dimnames(a)), a_rest) || - throw(NameMismatch("Dimension name mismatch $(dimnames.((a1, a_rest...))).")) + all(a -> issetequal(names(a1), names(a)), a_rest) || + throw(NameMismatch("Dimension name mismatch $(names.((a1, a_rest...))).")) # TODO: Check the shapes match. return NamedDimsCartesianIndices(axes(a1)) end @@ -948,22 +857,22 @@ end # Base version ignores dimension names. # TODO: Use `mapreduce(isequal, &&, a1, a2)`? function Base.isequal(a1::AbstractNamedTensor, a2::AbstractNamedTensor) - issetequal(dimnames(a1), dimnames(a2)) || return false - return isequal(unnamed(a1), unname(a2, dimnames(a1))) + issetequal(names(a1), names(a2)) || return false + return isequal(unnamed(a1), unname(a2, names(a1))) end # Base version ignores dimension names. # TODO: Use `mapreduce(==, &&, a1, a2)`? # TODO: Handle `missing` values properly. function Base.:(==)(a1::AbstractNamedTensor, a2::AbstractNamedTensor) - issetequal(dimnames(a1), dimnames(a2)) || return false - return unnamed(a1) == unname(a2, dimnames(a1)) + issetequal(names(a1), names(a2)) || return false + return unnamed(a1) == unname(a2, names(a1)) end # Base version ignores dimension names. function Base.isapprox(a1::AbstractNamedTensor, a2::AbstractNamedTensor; kwargs...) - issetequal(dimnames(a1), dimnames(a2)) || return false - return isapprox(unnamed(a1), unname(a2, dimnames(a1)); kwargs...) + issetequal(names(a1), names(a2)) || return false + return isapprox(unnamed(a1), unname(a2, names(a1)); kwargs...) end # Generalization of `Base.sort` to Tuples for Julia v1.10 compatibility. @@ -973,7 +882,7 @@ _sort(x::NTuple{N}; kwargs...) where {N} = NTuple{N}(sort(collect(x); kwargs...) function Base.hash(a::AbstractNamedTensor, h::UInt64) h = hash(:NamedTensor, h) - a′ = aligneddims(a, _sort(dimnames(a))) + a′ = aligned(a, _sort(names(a))) h = hash(unnamed(a′), h) for i in axes(a′) h = hash(i, h) @@ -1015,7 +924,7 @@ end function Base.to_indices( a::AbstractNamedTensor, I::Tuple{NamedInteger, Vararg{NamedInteger}} ) - perm = getperm(name.(I), dimnames(a)) + perm = getperm(name.(I), names(a)) # TODO: Throw a `NameMismatch` error. @assert isperm(perm) I = map(p -> I[p], perm) @@ -1032,7 +941,6 @@ end function Base.to_indices(a::AbstractNamedTensor, I::Tuple{Pair, Vararg{Pair}}) inds = to_inds(a, first.(I)) return map((i, name) -> name[i], last.(I), inds) - return to_indices(a, named.(last.(I), first.(I))) end function Base.to_indices(a::AbstractNamedTensor, I::Tuple{NamedDimsCartesianIndex}) @@ -1130,8 +1038,10 @@ end # TODO: Should this be a view? Base.getindex(a, I1::Name, Irest::Name...) = copy(view(a, I1, Irest...)) Base.getindex(a::AbstractArray, I1::Name, Irest::Name...) = copy(view(a, I1, Irest...)) -Base.view(a, I1::Name, Irest::Name...) = nameddims(a, name.((I1, Irest...))) -Base.view(a::AbstractArray, I1::Name, Irest::Name...) = nameddims(a, name.((I1, Irest...))) +Base.view(a, I1::Name, Irest::Name...) = NamedTensor(a, name.((I1, Irest...))) +function Base.view(a::AbstractArray, I1::Name, Irest::Name...) + return NamedTensor(a, name.((I1, Irest...))) +end function Base.getindex(a::AbstractArray, I1::NamedViewIndex, Irest::NamedViewIndex...) return copy(view(a, I1, Irest...)) @@ -1144,7 +1054,7 @@ function Base.getindex(a::Array, I1::NamedUnitRange) end function Base.view(a::AbstractArray, I1::NamedViewIndex, Irest::NamedViewIndex...) I = (I1, Irest...) - return nameddims(view(a, unnamed.(I)...), name.(I)) + return NamedTensor(view(a, unnamed.(I)...), name.(I)) end # TODO: Should this be a view? @@ -1152,10 +1062,10 @@ function Base.getindex(a::AbstractNamedTensor, I1::Name, Irest::Name...) return copy(view(a, I1, Irest...)) end function Base.view(a::AbstractNamedTensor, I1::Name, Irest::Name...) - issetequal(dimnames(a), name.((I1, Irest...))) || + issetequal(names(a), name.((I1, Irest...))) || throw( NameMismatch( - "Dimension name mismatch $(dimnames(a)), $(name.((I1, Irest...)))." + "Dimension name mismatch $(names(a)), $(name.((I1, Irest...)))." ) ) return a @@ -1177,10 +1087,10 @@ function Base.getindex( end function Base.view(a::AbstractNamedTensor, I1::NamedViewIndex, Irest::NamedViewIndex...) I = (I1, Irest...) - perm = getperm(name.(I), dimnames(a)) + perm = getperm(name.(I), names(a)) isperm(perm) || throw( NameMismatch( - "Dimension name mismatch $(dimnames(a)), $(name.(I))." + "Dimension name mismatch $(names(a)), $(name.(I))." ) ) Ip = map(p -> unnamed(I[p]), perm) @@ -1201,8 +1111,8 @@ isscalarindex(I::Real) = true # b = view(a, 1:2, 2) function view_nameddims(a::AbstractNamedTensor, I...) nonscalar_dims = filter(dim -> !isscalarindex(I[dim]), ntuple(identity, ndims(a))) - nonscalar_dimnames = map(dim -> dimnames(a, dim), nonscalar_dims) - return nameddims(view(unnamed(a), I...), nonscalar_dimnames) + nonscalar_names = map(dim -> names(a, dim), nonscalar_dims) + return NamedTensor(view(unnamed(a), I...), nonscalar_names) end function Base.view(a::AbstractNamedTensor, I::ViewIndex...) @@ -1233,7 +1143,7 @@ function Base.setindex!( Irest::NamedViewIndex... ) I = (I1, Irest...) - a[I...] = nameddims(value, name.(I)) + a[I...] = NamedTensor(value, name.(I)) return a end function Base.setindex!( @@ -1255,7 +1165,7 @@ end # Permute/align dimensions """ - aligndims(a::AbstractNamedTensor, dims) + align(a::AbstractNamedTensor, dims) Reorder the dimensions of `a` into the order given by `dims`, matched by name. Returns a tensor with the same data and dimension names as `a` but with the dimensions permuted, and @@ -1264,29 +1174,29 @@ throws a `NameMismatch` if `dims` is not a permutation of `a`'s dimension names. # Examples ```jldoctest -julia> a = nameddims(zeros(2, 3), (:i, :j)); +julia> a = NamedTensor(zeros(2, 3), (:i, :j)); -julia> aligndims(a, (:j, :i)) -named(Base.OneTo(3), :j)×named(Base.OneTo(2), :i) NamedTensor{Symbol}: +julia> align(a, (:j, :i)) +NamedOneTo(3, :j)×NamedOneTo(2, :i) NamedTensor{Symbol}: 3×2 Matrix{Float64}: 0.0 0.0 0.0 0.0 0.0 0.0 ``` """ -function aligndims(a::AbstractNamedTensor, dims) - new_dimnames = name.(dims) - perm = Tuple(getperm(dimnames(a), new_dimnames)) +function align(a::AbstractNamedTensor, dims) + new_names = name.(dims) + perm = Tuple(getperm(names(a), new_names)) isperm(perm) || throw( NameMismatch( - "Dimension name mismatch $(dimnames(a)), $(new_dimnames)." + "Dimension name mismatch $(names(a)), $(new_names)." ) ) - return nameddims(TensorAlgebra.permutedims(unnamed(a), perm), new_dimnames) + return NamedTensor(TensorAlgebra.permutedims(unnamed(a), perm), new_names) end """ - aligndims(a::AbstractNamedTensor, codomain, domain) + align(a::AbstractNamedTensor, codomain, domain) Reorder the dimensions of `a` into `(codomain..., domain...)`, matched by name, and forward the codomain/domain split to the underlying storage. Like the two-argument form, the result @@ -1295,51 +1205,51 @@ has the same data and dimension names as `a`, and a `NameMismatch` is thrown if that supports a bipartition (such as a TensorKit `TensorMap`) uses it, while a dense backend stores the result flat. """ -function aligndims(a::AbstractNamedTensor, codomain, domain) - new_dimnames = (name.(codomain)..., name.(domain)...) - perm = Tuple(getperm(dimnames(a), new_dimnames)) +function align(a::AbstractNamedTensor, codomain, domain) + new_names = (name.(codomain)..., name.(domain)...) + perm = Tuple(getperm(names(a), new_names)) isperm(perm) || throw( NameMismatch( - "Dimension name mismatch $(dimnames(a)), $(new_dimnames)." + "Dimension name mismatch $(names(a)), $(new_names)." ) ) perm_codomain = perm[1:length(codomain)] perm_domain = perm[(length(codomain) + 1):end] - return nameddims( - TensorAlgebra.permutedims(unnamed(a), perm_codomain, perm_domain), new_dimnames + return NamedTensor( + TensorAlgebra.permutedims(unnamed(a), perm_codomain, perm_domain), new_names ) end """ - aligneddims(a::AbstractNamedTensor, dims) + aligned(a::AbstractNamedTensor, dims) -Like [`aligndims`](@ref), but returns a lazily-permuted view that shares data with `a` +Like [`align`](@ref), but returns a lazily-permuted view that shares data with `a` instead of copying. Reorders the dimensions of `a` into the order given by `dims`, matched by name, and throws a `NameMismatch` if `dims` is not a permutation of `a`'s dimension names. # Examples ```jldoctest -julia> a = nameddims(reshape(1:6, 2, 3), (:i, :j)); +julia> a = NamedTensor(reshape(1:6, 2, 3), (:i, :j)); -julia> dimnames(aligneddims(a, (:j, :i))) +julia> names(aligned(a, (:j, :i))) 2-element Vector{Symbol}: :j :i ``` -See also [`aligndims`](@ref). +See also [`align`](@ref). """ -function aligneddims(a::AbstractNamedTensor, dims) - new_dimnames = name.(dims) - perm = getperm(dimnames(a), new_dimnames) +function aligned(a::AbstractNamedTensor, dims) + new_names = name.(dims) + perm = getperm(names(a), new_names) isperm(perm) || throw( NameMismatch( - "Dimension name mismatch $(dimnames(a)), $(new_dimnames)." + "Dimension name mismatch $(names(a)), $(new_names)." ) ) - return nameddims( - permuteddims(unnamed(a), perm), new_dimnames + return NamedTensor( + permuteddims(unnamed(a), perm), new_names ) end @@ -1547,8 +1457,8 @@ end function Base.map!(f, a_dest::AbstractNamedTensor, a_srcs::AbstractNamedTensor...) a′_dest = unnamed(a_dest) # TODO: Use `unnamed` to do the permutations lazily. - # TODO: Define `unname[d](dimnames) = Base.Fix1(unname[d], dimnames)` and use it here? - a′_srcs = Base.Fix2(unname, dimnames(a_dest)).(a_srcs) + # TODO: Define `unname[d](names) = Base.Fix1(unname[d], names)` and use it here? + a′_srcs = Base.Fix2(unname, names(a_dest)).(a_srcs) map!(f, a′_dest, a′_srcs...) return a_dest end diff --git a/src/broadcast.jl b/src/broadcast.jl index 2e11211c..73dff0bd 100644 --- a/src/broadcast.jl +++ b/src/broadcast.jl @@ -1,5 +1,4 @@ -using ..ITensorBase: - AbstractNamedTensor, ITensorBase, dimnames, getperm, named, nameddims, unnamed +using ..ITensorBase: AbstractNamedTensor, ITensorBase, getperm, unnamed using Base.Broadcast: Broadcast as BC, Broadcasted, broadcasted using TensorAlgebra: TensorAlgebra as TA @@ -25,8 +24,8 @@ BC.broadcastable(a::AbstractNamedTensor) = a # the output names, so no permutation is needed and its codomain/domain split is kept; only a sum needs # its addends aligned. (Flattening distributes scaling/conjugation over `+`, so a `Scaled`/`Conj` node # never wraps an `Add`, and the no-permutation recursion below never reaches one.) -unnamed_linear(a::TA.LinearBroadcasted, names) = unnamed_linear(a) -unnamed_linear(a::TA.AddBroadcasted, names) = unnamed_linear_aligned(a, names) +unnamed_linear(a::TA.LinearBroadcasted, nms) = unnamed_linear(a) +unnamed_linear(a::TA.AddBroadcasted, nms) = unnamed_linear_aligned(a, nms) # No permutation: strip names down the expression tree via the `operation`/`arguments` term interface. function unnamed_linear(a::TA.LinearBroadcasted) @@ -35,30 +34,30 @@ end unnamed_linear(a::AbstractNamedTensor) = unnamed(a) unnamed_linear(a::Number) = a -# Align every leaf to `names` through the `PermutedDims` wrapper (all-codomain output). Used for a sum's +# Align every leaf to `nms` through the `PermutedDims` wrapper (all-codomain output). Used for a sum's # addends and for every in-place `copyto!` (aligned to the destination). -function unnamed_linear_aligned(a::TA.LinearBroadcasted, names) +function unnamed_linear_aligned(a::TA.LinearBroadcasted, nms) return TA.linearbroadcasted( - TA.operation(a), map(x -> unnamed_linear_aligned(x, names), TA.arguments(a))... + TA.operation(a), map(x -> unnamed_linear_aligned(x, nms), TA.arguments(a))... ) end -function unnamed_linear_aligned(a::AbstractNamedTensor, names) - return _broadcast_permuteddims(unnamed(a), getperm(dimnames(a), names)) +function unnamed_linear_aligned(a::AbstractNamedTensor, nms) + return _broadcast_permuteddims(unnamed(a), getperm(names(a), nms)) end -unnamed_linear_aligned(a::Number, names) = a +unnamed_linear_aligned(a::Number, nms) = a -# Non-linear fallback: unname a general `Broadcasted` by aligning each operand to `names`, so Base's +# Non-linear fallback: unname a general `Broadcasted` by aligning each operand to `nms`, so Base's # generic broadcast can run (all-codomain output). Only the linear path preserves the split. -unnamed_broadcasted(x::Number, names) = x -function unnamed_broadcasted(a::AbstractNamedTensor, names) - # An operand already aligned to `names` needs no permutation, skipping the identity wrapper. - dimnames(a) == names && return unnamed(a) - return _broadcast_permuteddims(unnamed(a), getperm(dimnames(a), names)) +unnamed_broadcasted(x::Number, nms) = x +function unnamed_broadcasted(a::AbstractNamedTensor, nms) + # An operand already aligned to `nms` needs no permutation, skipping the identity wrapper. + names(a) == nms && return unnamed(a) + return _broadcast_permuteddims(unnamed(a), getperm(names(a), nms)) end -function unnamed_broadcasted(bc::Broadcasted, names) - return broadcasted(bc.f, Base.Fix2(unnamed_broadcasted, names).(bc.args)...) +function unnamed_broadcasted(bc::Broadcasted, nms) + return broadcasted(bc.f, Base.Fix2(unnamed_broadcasted, nms).(bc.args)...) end -# Broadcasting-only alignment: unlike the public `unnamed(a, names)` (which returns a +# Broadcasting-only alignment: unlike the public `unnamed(a, nms)` (which returns a # `Base.PermutedDimsArray`, a full array), this wraps in `TensorAlgebra.PermutedDims`, which stores # the permutation in a field rather than a type parameter, so it builds cheaply and type-stably # from the runtime permutation and is a broadcast leaf the linear-combination fold absorbs via @@ -77,14 +76,13 @@ BC.instantiate(bc::Broadcasted{<:AbstractNamedTensorStyle}) = bc # The destination dimension names of a broadcast are those of its first named operand. # Sourcing them here (rather than from `axes(bc)`) keeps the named axes off the hot path. -_dimnames(a::AbstractNamedTensor, args...) = dimnames(a) -_dimnames(bc::Broadcasted, args...) = _dimnames(bc.args..., args...) -_dimnames(_, args...) = _dimnames(args...) -dimnames(bc::Broadcasted) = _dimnames(bc.args...) +_names(a::AbstractNamedTensor, args...) = names(a) +_names(bc::Broadcasted, args...) = _names(bc.args..., args...) +_names(_, args...) = _names(args...) function Base.copy(bc::Broadcasted{<:AbstractNamedTensorStyle}) - nms = dimnames(bc) - return nameddims(_copy_unnamed(bc, nms), nms) + nms = _names(bc) + return ITensorBase.NamedTensor(_copy_unnamed(bc, nms), nms) end # Function barrier: `bc`'s named leaves are abstractly typed, so re-dispatching on the concrete `bc` @@ -115,7 +113,7 @@ function Base.copyto!( dest::AbstractNamedTensor, bc::Broadcasted{<:AbstractNamedTensorStyle} ) - _copyto_unnamed!(unnamed(dest), bc, dimnames(dest)) + _copyto_unnamed!(unnamed(dest), bc, names(dest)) return dest end diff --git a/src/index.jl b/src/index.jl index e7301f11..baa12491 100644 --- a/src/index.jl +++ b/src/index.jl @@ -405,11 +405,11 @@ prime(i::Index) = setname(i, prime(name(i))) noprime(i::Index) = setname(i, noprime(name(i))) sim(i::Index) = setname(i, sim(name(i))) -# Whole-tensor index manipulation: relabel every index name-only via `mapinds`, leaving the +# Whole-tensor index manipulation: relabel every index name-only via `rename`, leaving the # data and spaces untouched. -prime(a::AbstractNamedTensor) = mapinds(prime, a) -noprime(a::AbstractNamedTensor) = mapinds(noprime, a) -sim(a::AbstractNamedTensor) = mapinds(sim, a) +prime(a::AbstractNamedTensor) = rename(prime, a) +noprime(a::AbstractNamedTensor) = rename(noprime, a) +sim(a::AbstractNamedTensor) = rename(sim, a) function primestring(plev) if plev < 0 diff --git a/src/lazyitensors/evaluation_order.jl b/src/lazyitensors/evaluation_order.jl index e54283b3..639aa086 100644 --- a/src/lazyitensors/evaluation_order.jl +++ b/src/lazyitensors/evaluation_order.jl @@ -21,7 +21,7 @@ end function time_complexity( ::typeof(+), t1::AbstractNamedTensor, t2::AbstractNamedTensor ) - @assert issetequal(dimnames(t1), dimnames(t2)) + @assert issetequal(names(t1), names(t2)) return prod(size(t1)) end function time_complexity(::typeof(*), c::Number, t::AbstractNamedTensor) @@ -103,7 +103,7 @@ function optimize_contraction_order(alg::Greedy, a) # Penalize outer product contractions. # TODO: Still order the outer products by time complexity, # say by checking if there are only outer products left. - isdisjoint(dimnames(args[i1]), dimnames(args[i2])) && return typemax(Int) + isdisjoint(names(args[i1]), names(args[i2])) && return typemax(Int) return time_complexity(*, args[i1], args[i2]) end rest = [arg for (i, arg) in pairs(args) if i ∉ (i1, i2)] diff --git a/src/lazyitensors/itensorbaseextensions.jl b/src/lazyitensors/itensorbaseextensions.jl index 248b0f98..b5b0bc0b 100644 --- a/src/lazyitensors/itensorbaseextensions.jl +++ b/src/lazyitensors/itensorbaseextensions.jl @@ -17,8 +17,8 @@ end using AbstractTrees: AbstractTrees # Only print the dimension names when printing with `AbstractTrees.print_tree`. function AbstractTrees.printnode(io::IO, a::AbstractNamedTensor) - dimnames_a = "{" * join(map(s -> "\"$s\"", dimnames(a)), ", ") * "}" - print(io, dimnames_a) + names_a = "{" * join(map(s -> "\"$s\"", names(a)), ", ") * "}" + print(io, names_a) return nothing end diff --git a/src/lazyitensors/lazyinterface.jl b/src/lazyitensors/lazyinterface.jl index 057e859b..3f7d8d04 100644 --- a/src/lazyitensors/lazyinterface.jl +++ b/src/lazyitensors/lazyinterface.jl @@ -183,12 +183,12 @@ mul_lazy(a1::Number, a2::Number) = a1 * a2 div_lazy(a1, a2::Number) = error("Not implemented.") # ITensorBase.jl named-tensor interface. -function dimnames_lazy(a) +function names_lazy(a) u = unwrap(a) if !iscall(u) - return dimnames(u) + return names(u) elseif ismul(u) - return mapreduce(dimnames, symdiff, arguments(u)) + return mapreduce(names, symdiff, arguments(u)) else return error("Variant not supported.") end diff --git a/src/lazyitensors/lazyitensor.jl b/src/lazyitensors/lazyitensor.jl index 975e11f4..bab7aa7e 100644 --- a/src/lazyitensors/lazyitensor.jl +++ b/src/lazyitensors/lazyitensor.jl @@ -13,10 +13,10 @@ end parenttype(::Type{LazyNamedTensor}) = AbstractNamedTensor function LazyNamedTensor(a::AbstractNamedTensor) - return LazyNamedTensor{dimnametype(typeof(a)), typeof(a)}(a) + return LazyNamedTensor{nametype(typeof(a)), typeof(a)}(a) end function LazyNamedTensor(a::Mul{L}) where {L <: LazyNamedTensor} - return LazyNamedTensor{dimnametype(L), parenttype(L)}(a) + return LazyNamedTensor{nametype(L), parenttype(L)}(a) end lazy(a::LazyNamedTensor) = a lazy(a::AbstractNamedTensor) = LazyNamedTensor(a) @@ -50,7 +50,7 @@ function Base.convert( return lazy(Mul(map(arg -> convert(LazyNamedTensor{D, A2}, arg), arguments(a)))) end -dimnames(a::LazyNamedTensor) = dimnames_lazy(a) +Base.names(a::LazyNamedTensor) = names_lazy(a) inds(a::LazyNamedTensor) = inds_lazy(a) # `axes` is computed from `inds_lazy` rather than the generic `unnamed`-based fallback # because a `Mul` expression has no materialized `unnamed` array to take axes of. diff --git a/src/lazyitensors/symbolicitensor.jl b/src/lazyitensors/symbolicitensor.jl index 6ea2f2bc..fd04353a 100644 --- a/src/lazyitensors/symbolicitensor.jl +++ b/src/lazyitensors/symbolicitensor.jl @@ -1,14 +1,14 @@ # Expression leaf with no array payload, so it defines no `unnamed`/`getindex`. # A symbolic tensor is a placeholder substituted with a real tensor before # contraction, so it only needs what drives contraction-order selection: the -# `dimnames` and the index `size`s (the cost model uses lengths). Its `axes` are -# reconstructed as plain ranges of those sizes. Storing sizes and dimnames as +# `names` and the index `size`s (the cost model uses lengths). Its `axes` are +# reconstructed as plain ranges of those sizes. Storing sizes and names as # fields rather than type parameters lets symbolic tensors of different rank # share one concrete type so a flat `Mul` over them stays concretely typed. struct SymbolicNamedTensor{DimName, Name} <: AbstractNamedTensor{DimName} name::Name size::Vector{Int} - dimnames::Vector{DimName} + names::Vector{DimName} end function SymbolicNamedTensor(symname, inds) dnames = collect(name.(inds)) @@ -19,20 +19,23 @@ end symname(a::SymbolicNamedTensor) = getfield(a, :name) -dimnames(a::SymbolicNamedTensor) = getfield(a, :dimnames) +Base.names(a::SymbolicNamedTensor) = getfield(a, :names) function Base.axes(a::SymbolicNamedTensor) - return named.(Tuple(Base.OneTo.(getfield(a, :size))), Tuple(getfield(a, :dimnames))) + return NamedUnitRange.( + Tuple(Base.OneTo.(getfield(a, :size))), + Tuple(getfield(a, :names)) + ) end -Base.ndims(a::SymbolicNamedTensor) = length(getfield(a, :dimnames)) +Base.ndims(a::SymbolicNamedTensor) = length(getfield(a, :names)) function Base.:(==)(a::SymbolicNamedTensor, b::SymbolicNamedTensor) - return symname(a) == symname(b) && dimnames(a) == dimnames(b) + return symname(a) == symname(b) && names(a) == names(b) end Base.isequal(a::SymbolicNamedTensor, b::SymbolicNamedTensor) = a == b function Base.hash(a::SymbolicNamedTensor, h::UInt64) h = hash(:SymbolicNamedTensor, h) h = hash(symname(a), h) - return hash(dimnames(a), h) + return hash(names(a), h) end # Products build lazy expressions rather than contracting numerically. @@ -46,7 +49,7 @@ issymbolic(a::LazyNamedTensor) = !iscall(a) && issymbolic(unwrap(a)) function Base.show(io::IO, a::SymbolicNamedTensor) print(io, symname(a)) if ndims(a) > 0 - print(io, "[", join(dimnames(a), ","), "]") + print(io, "[", join(names(a), ","), "]") end return nothing end diff --git a/src/name.jl b/src/name.jl index b2a5fbb2..145b033f 100644 --- a/src/name.jl +++ b/src/name.jl @@ -1,7 +1,9 @@ abstract type AbstractName end # TODO: Decide if this is a good definition, probably not. # name(n::AbstractName) = throw(MethodError(name, Tuple{typeof(n)})) -Base.getindex(n::AbstractName, I) = named(I, name(n)) +# `I` can be a position, a range, or `:`, so the named type it takes is only known at +# runtime. +Base.getindex(n::AbstractName, I) = to_named(I, name(n)) struct Name{Value} <: AbstractName value::Value diff --git a/src/named.jl b/src/named.jl index 78e3ae2a..49f715b4 100644 --- a/src/named.jl +++ b/src/named.jl @@ -18,40 +18,38 @@ const NamedInteger{Name, Unnamed <: Integer} = Named{Name, Unnamed} name(a) The name attached to a named object `a`, such as a `Named` scalar, a named array, or a -named unit range. This is the inverse of the name component of [`named`](@ref): `name` -recovers the name, [`unnamed`](@ref) recovers the value. +named unit range. `name` recovers the name, [`unnamed`](@ref) recovers the value. # Examples ```jldoctest -julia> using ITensorBase: name +julia> using ITensorBase: Named, name -julia> name(named(2, :i)) +julia> name(Named(2, :i)) :i ``` -See also [`named`](@ref), [`unnamed`](@ref), [`setname`](@ref). +See also [`unnamed`](@ref), [`setname`](@ref). """ function name end """ unnamed(a) -The underlying value of a named object `a`, with its name stripped off. This is the -inverse of the value component of [`named`](@ref): [`name`](@ref) recovers the name, -`unnamed` recovers the value. On an [`AbstractNamedTensor`](@ref) it returns the underlying -unnamed array. +The underlying value of a named object `a`, with its name stripped off. [`name`](@ref) +recovers the name, `unnamed` recovers the value. On an [`AbstractNamedTensor`](@ref) it +returns the underlying unnamed array. # Examples ```jldoctest -julia> using ITensorBase: unnamed +julia> using ITensorBase: Named, unnamed -julia> unnamed(named(2, :i)) +julia> unnamed(Named(2, :i)) 2 ``` -See also [`named`](@ref), [`name`](@ref). +See also [`name`](@ref). """ function unnamed end @@ -64,28 +62,44 @@ underlying value unchanged. # Examples ```jldoctest -julia> using ITensorBase: setname +julia> using ITensorBase: Named, setname -julia> setname(named(2, :i), :j) -named(2, :j) +julia> setname(Named(2, :i), :j) +Named(2, :j) ``` -See also [`named`](@ref), [`name`](@ref). +See also [`name`](@ref). """ function setname end """ nametype(type::Type) + nametype(a::AbstractNamedTensor) + nametype(type::Type{<:AbstractNamedTensor}) -The type of the name carried by a named type, such as a `Named` scalar type, a named -array type, or a named unit range type. +The type of the name carried by a named object. For a `Named` scalar type, a named array +type, or a named unit range type this is the type of its single name; for a named tensor it +is the type of an individual dimension name. The primary methods dispatch on the type, and +`nametype(a::AbstractNamedTensor)` forwards to `nametype(typeof(a))`. A named tensor type +that does not fix its dimension-name flavor (such as the unparameterized `NamedTensor`) +returns `Any`, the same way `eltype(Array)` is `Any`. # Examples ```jldoctest -julia> using ITensorBase: nametype +julia> using ITensorBase: Named -julia> nametype(typeof(named(2, :i))) +julia> nametype(typeof(Named(2, :i))) +Symbol +``` + +```jldoctest +julia> a = NamedTensor(zeros(2, 3), (:i, :j)); + +julia> nametype(a) +Symbol + +julia> nametype(typeof(a)) Symbol ``` @@ -101,9 +115,9 @@ The type of the underlying (unnamed) value carried by a named type. # Examples ```jldoctest -julia> using ITensorBase: unnamedtype +julia> using ITensorBase: Named, unnamedtype -julia> unnamedtype(typeof(named(2, :i))) +julia> unnamedtype(typeof(Named(2, :i))) Int64 ``` @@ -115,24 +129,15 @@ function unnamedtype end unnamed(i::Named) = i.unnamed name(i::Named) = i.name -""" - named(value, name) - -Attach `name` to `value`, pairing them into a single named object. On a scalar this produces -a `Named`. Arrays and unit ranges have their own more specific methods. - -# Examples - -```jldoctest -julia> named(2, :i) -named(2, :i) -``` -""" -named(value, name) = Named(value, name) +# Attach a name to a value whose type is only known at runtime, picking the named type that +# matches its shape. Call sites that know what they are building use the constructor +# (`Named`, `NamedUnitRange`, `NamedArray`, `NamedColon`) directly; the per-type methods live +# alongside those types. +to_named(value, name) = Named(value, name) # Derived interface. -setname(i::Named, name) = named(unnamed(i), name) -setunnamed(i::Named, unnamed) = named(unnamed, name(i)) +setname(i::Named, name) = Named(unnamed(i), name) +setunnamed(i::Named, unnamed) = Named(unnamed, name(i)) unnamedtype(::Type{<:Named{<:Any, Unnamed}}) where {Unnamed} = Unnamed nametype(::Type{<:Named{Name}}) where {Name} = Name @@ -155,14 +160,14 @@ end Base.hash(i::Named, h::UInt) = hash_named(:Named, i, h) function uniquename(rng::AbstractRNG, i::Named) - return named(unnamed(i), uniquename(name(i))) + return Named(unnamed(i), uniquename(name(i))) end function Base.string(i::Named; kwargs...) - return "named($(string(unnamed(i); kwargs...)), $(repr(name(i))))" + return "Named($(string(unnamed(i); kwargs...)), $(repr(name(i))))" end function Base.show(io::IO, i::Named) - print(io, "named(", unnamed(i), ", ", repr(name(i)), ")") + print(io, "Named(", unnamed(i), ", ", repr(name(i)), ")") return nothing end @@ -174,7 +179,7 @@ Base.:-(i::NamedInteger) = setunnamed(i, -unnamed(i)) ## Here, named numbers are treated as unitful, so multiplying them ## with unnamed numbers means the result inherits the name. ## function Base.:*(i1::NamedInteger, i2::Number) -## return named(unnamed(i1) * i2, name(i1)) +## return Named(unnamed(i1) * i2, name(i1)) ## end Base.zero(i::NamedInteger) = setunnamed(i, zero(unnamed(i))) diff --git a/src/namedtensor.jl b/src/namedtensor.jl index c8ad9773..0450bbf7 100644 --- a/src/namedtensor.jl +++ b/src/namedtensor.jl @@ -1,19 +1,27 @@ using TensorAlgebra: TensorAlgebra """ - NamedTensor(array::AbstractArray, dims) + NamedTensor(array::AbstractArray, names) A tensor whose dimensions are labeled by names instead of ordered by position. It pairs -an underlying `array` with one name per dimension (`dims`), so contraction, addition, and +an underlying `array` with one name per dimension (`names`), so contraction, addition, and indexing line dimensions up by name. A `NamedTensor` is usually built by calling `randn`, `zeros`, -and the like on indices, or through [`nameddims`](@ref), rather than constructed directly. +and the like on indices, or by indexing an array by name, rather than constructed directly. [`ITensor`](@ref) is the `NamedTensor` with dimension names that are [`IndexName`](@ref)s. +A dimension is given either as a plain name or as an index (a [`NamedUnitRange`](@ref) such as +an [`Index`](@ref)). An index also asserts a space, which has to match the array's corresponding +axis, duality included, and an `ArgumentError` is thrown if it does not. A plain name asserts +nothing, so the array's axis stands. + +See also the `NamedTensor(unnamed, codomain_names, domain_names)` method for the map-shaped +form. + # Examples ```jldoctest julia> NamedTensor(zeros(2, 3), (:i, :j)) -named(Base.OneTo(2), :i)×named(Base.OneTo(3), :j) NamedTensor{Symbol}: +NamedOneTo(2, :i)×NamedOneTo(3, :j) NamedTensor{Symbol}: 2×3 Matrix{Float64}: 0.0 0.0 0.0 0.0 0.0 0.0 @@ -24,48 +32,143 @@ struct NamedTensor{DimName} <: AbstractNamedTensor{DimName} # tensor backend (e.g. a TensorKit `TensorMap`, reached through TensorAlgebra's `ndims`/ # `axes`/algebra interface) can be the parent directly. See the TensorKit extension. unnamed::Any - dimnames::Vector{DimName} + names::Vector{DimName} # The sole inner constructor: enforces the representation invariants (one name per dimension, # names distinct) on already-collected names. The outer constructors below normalize the # inputs (strip index names, fix the eltype) and funnel through here. - global function _NamedTensor(unnamed, dimnames::Vector{DimName}) where {DimName} - TensorAlgebra.ndims(unnamed) == length(dimnames) || + global function _NamedTensor(unnamed, names::Vector{DimName}) where {DimName} + TensorAlgebra.ndims(unnamed) == length(names) || throw(ArgumentError("Number of named dims must match ndims.")) - allunique(dimnames) || - throw(ArgumentError("Dimension names must be distinct, got $(dimnames).")) - return new{DimName}(unnamed, dimnames) + allunique(names) || + throw(ArgumentError("Dimension names must be distinct, got $(names).")) + return new{DimName}(unnamed, names) + end +end + +# A dimension given as an index asserts a space, so it has to agree with the array's axis; a +# bare name asserts nothing, so only the index case is checked. The comparison is on the +# underlying ranges (`space`) rather than on the indices, because `==` on an `Index` ignores +# duality and would pass a dual/non-dual mismatch. +function checkspaces(unnamed, names) + # A count mismatch is the inner constructor's error to report, so skip rather than compare + # against a padded axis. + length(names) == TensorAlgebra.ndims(unnamed) || return nothing + for (d, n) in enumerate(names) + checkspace(unnamed, d, n, identity) + end + return nothing +end + +# Codomain/domain form: the domain names are given codomain-facing while the storage holds them +# dualized (the convention of `TensorAlgebra.similar_map` and `TensorAlgebra.unmatricize`), so a +# domain index asserts the dual of its own space. +function checkspaces(unnamed, codomain_names, domain_names) + ncodomain = length(codomain_names) + # A count mismatch is the inner constructor's error to report, so skip rather than compare + # against a padded axis. + ncodomain + length(domain_names) == TensorAlgebra.ndims(unnamed) || return nothing + for (d, n) in enumerate(codomain_names) + checkspace(unnamed, d, n, identity) + end + for (d, n) in enumerate(domain_names) + checkspace(unnamed, ncodomain + d, n, conj) end + return nothing end -# `dimnames` can hold plain names or indices (`NamedUnitRange`s such as `Index`): `name` maps an -# index to its name and is the identity on a plain name, so an index's space is ignored (the array -# carries the axes). A single bare index is rejected, since it is ambiguous as `dimnames` (a -# `NamedUnitRange` is itself an iterable of its range values). The two methods repeat this -# normalization rather than one delegating to the other, so each strips names exactly once. -function NamedTensor{DimName}(unnamed, dimnames) where {DimName} - dimnames isa NamedUnitRange && throw( +# `dualize` maps a dimension's space to the space the storage holds at that position (`identity` +# in the codomain, `conj` in the domain). It is taken as a function rather than as a precomputed +# space because `space` is only defined once `n` is known to be an index. +function checkspace(unnamed, d, n, dualize) + n isa NamedUnitRange || return nothing + expected = dualize(space(n)) + ax = TensorAlgebra.axes(unnamed, d) + ax == expected && return nothing + asserted = if dualize === identity + "whose space $(expected)" + else + "whose space dualized for its domain position, $(expected)," + end + throw( ArgumentError( - "Got a single index (`NamedUnitRange` such as `Index`) as the dimension names. \ - Pass a tuple or vector, e.g. `ITensor(array, (i, j))`." + "Dimension $(d) was given the index $(n), $(asserted) does not match the \ + corresponding axis $(ax) of the array." ) ) - return _NamedTensor(unnamed, collect(DimName, name.(dimnames))) end -# The dimension-name type is inferred from the names, so indices infer `IndexName`, not their type. -function NamedTensor(unnamed, dimnames) - dimnames isa NamedUnitRange && throw( + +# A lone index is ambiguous as a group of dimensions (a `NamedUnitRange` is itself an iterable +# of its range values), so it is rejected rather than splatted into its elements. +function checknotindex(names) + names isa NamedUnitRange && throw( ArgumentError( "Got a single index (`NamedUnitRange` such as `Index`) as the dimension names. \ Pass a tuple or vector, e.g. `ITensor(array, (i, j))`." ) ) - return _NamedTensor(unnamed, collect(name.(dimnames))) + return nothing +end + +# `names` can hold plain names or indices (`NamedUnitRange`s such as `Index`): `name` maps an +# index to its name and is the identity on a plain name, so only an index's name is stored (the +# array carries the axes), after `checkspaces` has checked that the space it asserts agrees. +# The methods below repeat this normalization rather than delegating to one another, so each +# strips names exactly once. +function NamedTensor{DimName}(unnamed, names) where {DimName} + checknotindex(names) + checkspaces(unnamed, names) + return _NamedTensor(unnamed, collect(DimName, name.(names))) +end +# The dimension-name type is inferred from the names, so indices infer `IndexName`, not their type. +function NamedTensor(unnamed, names) + checknotindex(names) + checkspaces(unnamed, names) + return _NamedTensor(unnamed, collect(name.(names))) +end + +""" + NamedTensor(unnamed, codomain_names, domain_names) + +A tensor whose dimensions are split into a codomain group and a domain group, as a map from +the domain to the codomain. The storage holds the codomain dimensions first and the domain +dimensions last. `codomain_names` and `domain_names` hold names or indices, and the domain +indices are given codomain-facing: an index `n` in `domain_names` asserts that the storage's +axis is the dual `conj(space(n))`, matching how `TensorAlgebra.similar_map` and +`TensorAlgebra.unmatricize` build map-shaped storage. + +# Examples + +```jldoctest +julia> i, j = NamedUnitRange(1:2, :i), NamedUnitRange(1:3, :j); + +julia> NamedTensor(zeros(2, 3), (i,), (j,)) +NamedOneTo(2, :i)×NamedOneTo(3, :j) NamedTensor{Symbol}: +2×3 Matrix{Float64}: + 0.0 0.0 0.0 + 0.0 0.0 0.0 +``` +""" +function NamedTensor(unnamed, codomain_names, domain_names) + checknotindex(codomain_names) + checknotindex(domain_names) + checkspaces(unnamed, codomain_names, domain_names) + return _NamedTensor( + unnamed, collect((name.(codomain_names)..., name.(domain_names)...)) + ) +end +function NamedTensor{DimName}(unnamed, codomain_names, domain_names) where {DimName} + checknotindex(codomain_names) + checknotindex(domain_names) + checkspaces(unnamed, codomain_names, domain_names) + return _NamedTensor( + unnamed, collect(DimName, (name.(codomain_names)..., name.(domain_names)...)) + ) end NamedTensor(a::AbstractNamedTensor, inds) = throw(ArgumentError("Already named.")) -NamedTensor(a::AbstractNamedTensor) = NamedTensor(unnamed(a), dimnames(a)) +NamedTensor(a::AbstractNamedTensor) = NamedTensor(unnamed(a), names(a)) -# Minimal interface. The dimnames are stored as (and returned as) a `Vector`. -dimnames(a::NamedTensor) = a.dimnames +# Minimal interface. The names are stored as (and returned as) a `Vector`. +Base.names(a::NamedTensor) = a.names unnamed(a::NamedTensor) = a.unnamed Base.parent(a::NamedTensor) = unnamed(a) diff --git a/src/namedtensoroperator.jl b/src/namedtensoroperator.jl index 87591b9a..9d6a5079 100644 --- a/src/namedtensoroperator.jl +++ b/src/namedtensoroperator.jl @@ -17,7 +17,7 @@ on its own. For a plain tensor that is not an operator, `state` returns it uncha # Examples ```jldoctest -julia> a = nameddims(zeros(2), (:i,)); +julia> a = NamedTensor(zeros(2), (:i,)); julia> state(a) == a true @@ -90,7 +90,7 @@ julia> op = operator(zeros(2, 2), ("i",), ("j",)); julia> outputinds(op) 1-element Vector{NamedUnitRange{String, Int64, Base.OneTo{Int64}}}: - named(Base.OneTo(2), "i") + NamedOneTo(2, "i") ``` See also [`outputnames`](@ref), [`inputinds`](@ref), [`outputaxes`](@ref), [`operator`](@ref). @@ -127,7 +127,7 @@ julia> op = operator(zeros(2, 2), ("i",), ("j",)); julia> inputinds(op) 1-element Vector{NamedUnitRange{String, Int64, Base.OneTo{Int64}}}: - named(Base.OneTo(2), "j") + NamedOneTo(2, "j") ``` See also [`inputnames`](@ref), [`outputinds`](@ref), [`inputaxes`](@ref), [`operator`](@ref). @@ -172,7 +172,7 @@ end # every name shared between `x` and `y` must be an input of `x` that is not also an input of # `y`. Landing an input of `x` on an input of `y`, or sharing any other name, is rejected. function check_apply(x::AbstractNamedTensor, y::AbstractNamedTensor) - for s in intersect(dimnames(x), dimnames(y)) + for s in intersect(names(x), names(y)) if !(s in inputnames(x)) || (s in inputnames(y)) throw( ArgumentError( @@ -200,7 +200,7 @@ unchanged; applying it to another operator gives an operator. ```jldoctest julia> op = operator(reshape(Float64[1, 0, 0, 1], 2, 2), ("i",), ("j",)); -julia> v = nameddims([3.0, 4.0], ("j",)); +julia> v = NamedTensor([3.0, 4.0], ("j",)); julia> apply(op, v) == v true @@ -213,9 +213,9 @@ function apply(x::AbstractNamedTensor, y::AbstractNamedTensor) check_apply(x, y) xy = x * y relabels = [ - ox => ix for (ox, ix) in zip(outputnames(x), inputnames(x)) if ix in dimnames(y) + ox => ix for (ox, ix) in zip(outputnames(x), inputnames(x)) if ix in names(y) ] - result = replacedimnames(xy, relabels...) + result = rename(xy, relabels...) # A result with no surviving pairing is a plain state (e.g. an operator applied to a # bare state), so return it unwrapped. return isempty(outputnames(result)) ? state(result) : result @@ -227,7 +227,7 @@ function Base.transpose(a::AbstractNamedTensor) out = outputnames(a) inp = inputnames(a) a_map = merge(Dict(out .=> inp), Dict(inp .=> out)) - a′ = mapdimnames(state(a)) do i + a′ = rename(state(a)) do i return get(a_map, i, i) end return operator(a′, out, inp) @@ -269,8 +269,8 @@ function product(a::AbstractNamedTensor, b::AbstractNamedTensor) a′, b′ = a, b for s in intersect(inputnames(a), inputnames(b)) bond = uniquename(s) - a′ = replacedimnames(a′, s => bond) # a's input site → bond - b′ = replacedimnames(b′, outputname(b, s, s) => bond) # b's matching output → bond + a′ = rename(a′, s => bond) # a's input site → bond + b′ = rename(b′, outputname(b, s, s) => bond) # b's matching output → bond end return operator_product(a′, b′) end @@ -306,7 +306,7 @@ state(a::AbstractNamedTensor) = a state(a::NamedTensorOperator) = a.parent Base.parent(a::NamedTensorOperator) = state(a) unnamed(a::NamedTensorOperator) = unnamed(state(a)) -dimnames(a::NamedTensorOperator) = dimnames(state(a)) +Base.names(a::NamedTensorOperator) = names(state(a)) parenttype(type::Type{<:NamedTensorOperator}) = fieldtype(type, :parent) statetype(type::Type{<:NamedTensorOperator}) = parenttype(type) @@ -319,21 +319,20 @@ outputnames(a::NamedTensorOperator) = a.outputnames inputnames(a::NamedTensorOperator) = a.inputnames # Relabeling an operator's dimension names updates both its state and its pairing (the -# generic `AbstractNamedTensor` methods reconstruct via `nameddims` and would drop the -# pairing). `mapdimnames(f, op)` routes through the function form via the generic -# `mapdimnames(f, ::AbstractNamedTensor) = replacedimnames(f, ...)`. -function replacedimnames(op::NamedTensorOperator, replacements::Pair...) +# generic `AbstractNamedTensor` methods reconstruct via `NamedTensor` and would drop the +# pairing). +function rename(op::NamedTensorOperator, replacements::Pair...) isempty(replacements) && return op ps = map(p -> name(first(p)) => name(last(p)), replacements) return operator( - replacedimnames(state(op), ps...), + rename(state(op), ps...), replace(outputnames(op), ps...), replace(inputnames(op), ps...) ) end -function replacedimnames(f, op::NamedTensorOperator) +function rename(f, op::NamedTensorOperator) return operator( - replacedimnames(f, state(op)), map(f, outputnames(op)), map(f, inputnames(op)) + rename(f, state(op)), map(f, outputnames(op)), map(f, inputnames(op)) ) end @@ -383,7 +382,7 @@ function operator end # TODO: Unify these two functions. function operator(a::AbstractArray, output, input) output, input = name.(output), name.(input) - na = nameddims(a, (output..., input...)) + na = NamedTensor(a, (output..., input...)) return operator(na, output, input) end function operator(a::AbstractNamedTensor, output, input) @@ -425,7 +424,7 @@ operator_pairs(a::AbstractNamedTensor) = () # any contracted (shared) names. A name whose chain dead-ends on a contracted # index is left dangling, so the result is well defined for any contraction. function product_output_input(a::AbstractNamedTensor, b::AbstractNamedTensor) - shared = intersect(dimnames(a), dimnames(b)) + shared = intersect(names(a), names(b)) pairs = collect(Iterators.flatten((operator_pairs(a), operator_pairs(b)))) forward = Dict(pairs) input = eltype(keys(forward))[] @@ -550,9 +549,9 @@ the input names, leaving the input unchanged. # Examples ```jldoctest -julia> using ITensorBase: apply, namedoneto, operator +julia> using ITensorBase: NamedOneTo, apply, operator -julia> i, j, k, l = namedoneto.((2, 3, 2, 3), ("i", "j", "k", "l")); +julia> i, j, k, l = NamedOneTo.((2, 3, 2, 3), ("i", "j", "k", "l")); julia> op = operator(randn(i, j, k, l), ("i", "j"), ("k", "l")); @@ -617,8 +616,8 @@ See also [`operator`](@ref), [`uniquename`](@ref). function similar_operator( prototype, ::Type{T}, unnamed_input_axes, outputnames, inputnames ) where {T} - output_axes = named.(unnamed_input_axes, outputnames) - input_axes = named.(unnamed_input_axes, inputnames) + output_axes = NamedUnitRange.(unnamed_input_axes, outputnames) + input_axes = NamedUnitRange.(unnamed_input_axes, inputnames) raw = TA.similar_map(prototype, T, output_axes, input_axes) return operator(raw, outputnames, inputnames) end diff --git a/src/namedunitrange.jl b/src/namedunitrange.jl index 8974111c..f8352cc2 100644 --- a/src/namedunitrange.jl +++ b/src/namedunitrange.jl @@ -5,17 +5,17 @@ using TensorAlgebra: TensorAlgebra, dual, isdual, to_range, trivialrange, ungrad A unit range with a name attached, used as a named dimension (axis) of a tensor. It pairs an underlying integer unit range with a name of type `Name`. [`Index`](@ref) is -the `NamedUnitRange` flavor whose name is an `IndexName`. Build one by calling -[`named`](@ref) on a range, or use `Index` to mint a fresh unique name. +the `NamedUnitRange` flavor whose name is an `IndexName`. Build one from a range and a +name, or use `Index` to mint a fresh unique name. # Examples ```jldoctest -julia> named(1:3, :i) -named(1:3, :i) +julia> NamedUnitRange(1:3, :i) +NamedUnitRange(1:3, :i) ``` -See also [`Index`](@ref), [`named`](@ref). +See also [`Index`](@ref). """ struct NamedUnitRange{Name, UnnamedT, Unnamed} <: AbstractNamedVector{Name, UnnamedT} # The `unnamed` value is usually an integer `AbstractUnitRange`, but the bound is left open @@ -70,28 +70,30 @@ function NamedUnitRange(space, name) return NamedUnitRange(to_range(space), name) end -# This can be customized to output different named unit range types. -namedunitrange(r::AbstractUnitRange, name) = NamedUnitRange(r, name) +to_named(r::AbstractUnitRange, name) = NamedUnitRange(r, name) + +# A named range over `Base.OneTo`, i.e. a tensor dimension given by a plain length. This is +# the common case, since it is what a dimension of an unnamed `AbstractArray` is. The +# `Integer` constructor is unambiguous with the range constructors above because an +# `Integer` is not an `AbstractUnitRange`. +const NamedOneTo{Name, S} = NamedUnitRange{Name, S, Base.OneTo{S}} +NamedOneTo(length::Integer, name) = NamedUnitRange(Base.oneto(length), name) # Mint a fresh trivial *named* range matching `r`'s backend: the trivial range of the # underlying (unnamed) axis, carrying a fresh unique name of `r`'s name type. function TensorAlgebra.trivialrange(r::NamedUnitRange{Name}) where {Name} - return namedunitrange(trivialrange(unnamed(r)), uniquename(Name)) + return NamedUnitRange(trivialrange(unnamed(r)), uniquename(Name)) end function TensorAlgebra.trivialrange(r::NamedUnitRange{Name}, n::Integer) where {Name} - return namedunitrange(trivialrange(unnamed(r), n), uniquename(Name)) + return NamedUnitRange(trivialrange(unnamed(r), n), uniquename(Name)) end -# Shorthand: attach an existing name to a range. -named(r::AbstractUnitRange, name) = namedunitrange(r, name) - # Derived interface. `setname` differs from the `AbstractNamedArray` method: it -# rebuilds through `named` so the result stays a named unit range, not a named -# array. The rest of the named interface (`isnamed`, `unnamedtype`, `nametype`, -# `uniquename`, `show`, `isempty`) is inherited from `AbstractNamedArray`; `==`, -# `isequal`, and `hash` are overridden just below. +# rebuilds a named unit range, not a named array. The rest of the named interface +# (`isnamed`, `unnamedtype`, `nametype`, `uniquename`, `show`, `isempty`) is inherited +# from `AbstractNamedArray`; `==`, `isequal`, and `hash` are overridden just below. # TODO: Use `Accessors.@set`? -setname(r::NamedUnitRange, name) = named(unnamed(r), name) +setname(r::NamedUnitRange, name) = NamedUnitRange(unnamed(r), name) # Equality and hashing answer identity ("is this the same leg?"), keyed on the name plus the # axis's ungraded extent (via `TensorAlgebra.ungrade`). Conjugation preserves the name and the @@ -107,25 +109,25 @@ Base.isequal(r1::NamedUnitRange, r2::NamedUnitRange) = r1 == r2 # through the shared `hash_named(:NamedArray, ...)` path on the ungraded range. That keeps `hash` # consistent with `==` above and with a named array of equal values (Base's `[1, 2, 3] == 1:3` # and `hash([1, 2, 3]) == hash(1:3)` contract). -TensorAlgebra.ungrade(r::NamedUnitRange) = named(ungrade(unnamed(r)), name(r)) +TensorAlgebra.ungrade(r::NamedUnitRange) = NamedUnitRange(ungrade(unnamed(r)), name(r)) Base.hash(r::NamedUnitRange, h::UInt) = hash_named(:NamedArray, ungrade(r), h) # Forward `conj` to the underlying range so graded axes flip their sector # arrows. The `Base.conj(::AbstractArray{<:Real}) = x` fallback would # otherwise short-circuit before the inner range is touched. -Base.conj(r::NamedUnitRange) = named(conj(unnamed(r)), name(r)) +Base.conj(r::NamedUnitRange) = NamedUnitRange(conj(unnamed(r)), name(r)) # Forward `dual`/`isdual` to the underlying range so an index answers its duality directly. -TensorAlgebra.dual(r::NamedUnitRange) = named(dual(unnamed(r)), name(r)) +TensorAlgebra.dual(r::NamedUnitRange) = NamedUnitRange(dual(unnamed(r)), name(r)) TensorAlgebra.isdual(r::NamedUnitRange) = isdual(unnamed(r)) # Unit range functionality. -Base.first(r::NamedUnitRange) = named(first(unnamed(r)), name(r)) -Base.last(r::NamedUnitRange) = named(last(unnamed(r)), name(r)) +Base.first(r::NamedUnitRange) = Named(first(unnamed(r)), name(r)) +Base.last(r::NamedUnitRange) = Named(last(unnamed(r)), name(r)) # `length`, `size`, and `axes` are inherited from the `AbstractNamedArray` generic: # the count and the positional axes are plain (unnamed). The element-layer methods # (`first`, `last`, `step`, indexing, iteration) stay named. -Base.step(r::NamedUnitRange) = named(step(unnamed(r)), name(r)) +Base.step(r::NamedUnitRange) = Named(step(unnamed(r)), name(r)) Base.getindex(r::NamedUnitRange, I::Int) = getindex_named(r, I) # Fix ambiguity error. function Base.getindex(r::NamedUnitRange, I::AbstractUnitRange{<:Integer}) @@ -161,12 +163,11 @@ function Base.AbstractUnitRange{Int}(r::NamedUnitRange) return AbstractUnitRange{Int}(unnamed(r)) end -Base.oneto(length::NamedInteger) = named(Base.OneTo(unnamed(length)), name(length)) -namedoneto(length::Integer, name) = Base.oneto(named(length, name)) +Base.oneto(length::NamedInteger) = NamedUnitRange(Base.OneTo(unnamed(length)), name(length)) Base.iterate(r::NamedUnitRange) = isempty(r) ? nothing : (first(r), first(r)) function Base.iterate(r::NamedUnitRange, i) i == last(r) && return nothing - next = named(unnamed(i) + unnamed(step(r)), name(r)) + next = Named(unnamed(i) + unnamed(step(r)), name(r)) return (next, next) end @@ -175,7 +176,7 @@ struct NamedColon{Name} <: Function end unnamed(c::NamedColon) = Colon() name(c::NamedColon) = c.name -named(::Colon, name) = NamedColon(name) +to_named(::Colon, name) = NamedColon(name) struct FirstIndex{Arr, Dim} array::Arr diff --git a/src/tensoralgebra.jl b/src/tensoralgebra.jl index 38255ed4..ffabe301 100644 --- a/src/tensoralgebra.jl +++ b/src/tensoralgebra.jl @@ -2,17 +2,16 @@ using LinearAlgebra: LinearAlgebra as LA using MatrixAlgebraKit: MatrixAlgebraKit as MAK using TensorAlgebra.MatrixAlgebra: MatrixAlgebra as MA using TensorAlgebra: TensorAlgebra as TA -using TupleTools: TupleTools # This layer is used to define derivative rules (to skip differentiating `setdiff`). -dimnames_setdiff(s1, s2) = setdiff(s1, s2) +names_setdiff(s1, s2) = setdiff(s1, s2) Base.:*(a1::AbstractNamedTensor, a2::AbstractNamedTensor) = mul_nameddims(a1, a2) function mul_nameddims(a1::AbstractNamedTensor, a2::AbstractNamedTensor) - a_dest, dimnames_dest = TA.contract( - unnamed(a1), dimnames(a1), unnamed(a2), dimnames(a2) + a_dest, names_dest = TA.contract( + unnamed(a1), names(a1), unnamed(a2), names(a2) ) - return nameddims(a_dest, dimnames_dest) + return NamedTensor(a_dest, names_dest) end # Left associative fold/reduction. @@ -48,9 +47,9 @@ function mul!_nameddims( α::Number, β::Number ) TA.contractadd!( - unnamed(a_dest), dimnames(a_dest), - unnamed(a1), dimnames(a1), - unnamed(a2), dimnames(a2), + unnamed(a_dest), names(a_dest), + unnamed(a1), names(a1), + unnamed(a2), names(a2), α, β ) return a_dest @@ -67,9 +66,9 @@ function mul!_nameddims( a1::AbstractNamedTensor, a2::AbstractNamedTensor ) TA.contract!( - unnamed(a_dest), dimnames(a_dest), - unnamed(a1), dimnames(a1), - unnamed(a2), dimnames(a2) + unnamed(a_dest), names(a_dest), + unnamed(a1), names(a1), + unnamed(a2), names(a2) ) return a_dest end @@ -77,24 +76,20 @@ end # Locate the named-dimension groups `group1`, `group2` within `a`, returning their two # positional index groups. function nameperm(a::AbstractNamedTensor, group1, group2) - return TA.biperm(dimnames(a), name.(Tuple(group1)), name.(Tuple(group2))) + return TA.biperm(names(a), name.(Tuple(group1)), name.(Tuple(group2))) end """ - TensorAlgebra.matricize(a::AbstractNamedTensor, codomain => rowname, domain => colname) TensorAlgebra.matricize(a::AbstractNamedTensor, codomain, domain) -Reshape the named tensor `a` into a matrix, fusing the `codomain` dimension group into the -rows and the `domain` group into the columns. `codomain` and `domain` are each any iterable -of dimensions (or dimension names) of `a`, and together they must cover all of `a`'s -dimensions. The pair form labels the two fused dimensions with the given `rowname` and -`colname`; the positional form generates fresh unique names for them. +Reshape the named tensor `a` into an unnamed matrix, fusing the `codomain` dimension group +into the rows and the `domain` group into the columns. `codomain` and `domain` are each any +iterable of dimensions (or dimension names) of `a`, and together they must cover all of `a`'s +dimensions. # Examples ```jldoctest -julia> using ITensorBase: Index - julia> using TensorAlgebra: matricize julia> i, j, k, l = Index.((2, 3, 2, 3)); @@ -103,50 +98,41 @@ julia> a = randn(i, j, k, l); julia> size(matricize(a, (i, k), (j, l))) (4, 9) - -julia> Array(matricize(a, (i, k), (j, l))) == - Array(matricize(a, (i, k) => "rows", (j, l) => "cols")) -true ``` """ -function TA.matricize(a::AbstractNamedTensor, fusions::Vararg{Pair, 2}) - return matricize_nameddims(a, fusions...) -end function TA.matricize(a::AbstractNamedTensor, codomain, domain) - row_name = uniquename(dimnametype(a)) - col_name = uniquename(dimnametype(a)) - return TA.matricize(a, codomain => row_name, domain => col_name) -end -function matricize_nameddims(na::AbstractNamedTensor, fusions::Vararg{Pair, 2}) - group1, group2 = first.(fusions) - perm_codomain, perm_domain = nameperm(na, group1, group2) - a_fused = TA.matricize(unnamed(na), perm_codomain, perm_domain) - return nameddims(a_fused, last.(fusions)) -end - -function TA.unmatricize(na::AbstractNamedTensor, splitters::Vararg{Pair, 2}) - return unmatricize_nameddims(na, splitters...) -end -function unmatricize_nameddims(na::AbstractNamedTensor, splitters::Vararg{Pair, 2}) - splitters = name.(first.(splitters)) .=> last.(splitters) - split_namedlengths = last.(splitters) - splitters_unnamed = map(splitters) do splitter - fused_name, split_namedlengths = splitter - fused_dim = findfirst(isequal(fused_name), dimnames(na)) - split_lengths = unnamed.(split_namedlengths) - return fused_dim => split_lengths - end - blocked_axes = last.(TupleTools.sort(splitters_unnamed; by = first)) - a_split = TA.unmatricize(unnamed(na), blocked_axes...) - names_split = Any[tuple.(dimnames(na))...] - for splitter in splitters - fused_name, split_namedlengths = splitter - fused_dim = findfirst(isequal(fused_name), dimnames(na)) - split_names = name.(split_namedlengths) - names_split[fused_dim] = split_names - end - names_split = reduce((x, y) -> (x..., y...), names_split) - return nameddims(a_split, names_split) + perm_codomain, perm_domain = nameperm(a, codomain, domain) + return TA.matricize(unnamed(a), perm_codomain, perm_domain) +end + +# Unmatricize an unnamed matrix into the named `codomain`/`domain` axes, giving a named tensor. +# `Tuple{Vararg{NamedUnitRange}}` also matches an empty tuple, so demanding at least one named +# axis across the two groups takes three methods: one per group, plus the both-nonempty case +# that resolves the ambiguity between them. +function TA.unmatricize( + m, + codomain::Tuple{NamedUnitRange, Vararg{NamedUnitRange}}, + domain::Tuple{Vararg{NamedUnitRange}} + ) + return unmatricize_nameddims(m, codomain, domain) +end +function TA.unmatricize( + m, + codomain::Tuple{Vararg{NamedUnitRange}}, + domain::Tuple{NamedUnitRange, Vararg{NamedUnitRange}} + ) + return unmatricize_nameddims(m, codomain, domain) +end +function TA.unmatricize( + m, + codomain::Tuple{NamedUnitRange, Vararg{NamedUnitRange}}, + domain::Tuple{NamedUnitRange, Vararg{NamedUnitRange}} + ) + return unmatricize_nameddims(m, codomain, domain) +end +function unmatricize_nameddims(m, codomain, domain) + a = TA.unmatricize(m, space.(codomain), space.(domain)) + return NamedTensor(a, name.(codomain), name.(domain)) end """ @@ -188,14 +174,14 @@ function TA.directsum( ps = (pair1, pairs...) shared = namesetdiff(inds(first(pair1)), last(pair1)) summed_dims = length(shared) .+ eachindex(last(pair1)) - aligned = map(p -> unname(first(p), [shared; collect(last(p))]), ps) - a = TA.directsum(summed_dims, aligned...) - return nameddims(a, [name.(shared); name.(collect(out_inds))]) + aligned_arrays = map(p -> unname(first(p), [shared; collect(last(p))]), ps) + a = TA.directsum(summed_dims, aligned_arrays...) + return NamedTensor(a, [name.(shared); name.(collect(out_inds))]) end function TA.directsum( pair1::Pair{<:AbstractNamedTensor}, pairs::Pair{<:AbstractNamedTensor}... ) - out_names = [uniquename(dimnametype(first(pair1))) for _ in last(pair1)] + out_names = [uniquename(nametype(first(pair1))) for _ in last(pair1)] s = TA.directsum(out_names, pair1, pairs...) return s => last(inds(s), length(out_names)) end @@ -218,33 +204,33 @@ for f in [ f_nameddims = Symbol(f, "_nameddims") @eval begin function MAK.$f( - a::AbstractNamedTensor, dimnames_codomain, dimnames_domain; kwargs... + a::AbstractNamedTensor, names_codomain, names_domain; kwargs... ) - return $f_nameddims(a, dimnames_codomain, dimnames_domain; kwargs...) + return $f_nameddims(a, names_codomain, names_domain; kwargs...) end function $f_nameddims( - a::AbstractNamedTensor, dimnames_codomain, dimnames_domain; + a::AbstractNamedTensor, names_codomain, names_domain; name = (;), kwargs... ) # `name` is a keyword here, so reach the `name` function through the module. - codomain = ITensorBase.name.(dimnames_codomain) - domain = ITensorBase.name.(dimnames_domain) + codomain = ITensorBase.name.(names_codomain) + domain = ITensorBase.name.(names_domain) x_unnamed, y_unnamed = - TA.$f(unnamed(a), dimnames(a), codomain, domain; kwargs...) - name_x = to_uniquename_function(name)(dimnametype(a)) + TA.$f(unnamed(a), names(a), codomain, domain; kwargs...) + name_x = to_uniquename_function(name)(nametype(a)) name_y = name_x - dimnames_x = (codomain..., name_x) - dimnames_y = (name_y, domain...) - x = nameddims(x_unnamed, dimnames_x) - y = nameddims(y_unnamed, dimnames_y) + names_x = (codomain..., name_x) + names_y = (name_y, domain...) + x = NamedTensor(x_unnamed, names_x) + y = NamedTensor(y_unnamed, names_y) return x, y end - function MAK.$f(a::AbstractNamedTensor, dimnames_codomain; kwargs...) - return $f_nameddims(a, dimnames_codomain; kwargs...) + function MAK.$f(a::AbstractNamedTensor, names_codomain; kwargs...) + return $f_nameddims(a, names_codomain; kwargs...) end - function $f_nameddims(a::AbstractNamedTensor, dimnames_codomain; kwargs...) - codomain = name.(dimnames_codomain) - domain = dimnames_setdiff(dimnames(a), codomain) + function $f_nameddims(a::AbstractNamedTensor, names_codomain; kwargs...) + codomain = name.(names_codomain) + domain = names_setdiff(names(a), codomain) return MAK.$f(a, codomain, domain; kwargs...) end end @@ -258,37 +244,37 @@ for f in [:svd_compact, :svd_full] f_nameddims = Symbol(f, "_nameddims") @eval begin function MAK.$f( - a::AbstractNamedTensor, dimnames_codomain, dimnames_domain; kwargs... + a::AbstractNamedTensor, names_codomain, names_domain; kwargs... ) - return $f_nameddims(a, dimnames_codomain, dimnames_domain; kwargs...) + return $f_nameddims(a, names_codomain, names_domain; kwargs...) end function $f_nameddims( - a::AbstractNamedTensor, dimnames_codomain, dimnames_domain; + a::AbstractNamedTensor, names_codomain, names_domain; leftname = (;), rightname = (;), kwargs... ) - codomain = name.(dimnames_codomain) - domain = name.(dimnames_domain) + codomain = name.(names_codomain) + domain = name.(names_domain) u_unnamed, s_unnamed, v_unnamed = TA.$f( - unnamed(a), dimnames(a), codomain, domain; kwargs... + unnamed(a), names(a), codomain, domain; kwargs... ) - name_u = to_uniquename_function(leftname)(dimnametype(a)) - name_v = to_uniquename_function(rightname)(dimnametype(a)) - dimnames_u = (codomain..., name_u) - dimnames_s = (name_u, name_v) - dimnames_v = (name_v, domain...) - u = nameddims(u_unnamed, dimnames_u) - s = nameddims(s_unnamed, dimnames_s) - v = nameddims(v_unnamed, dimnames_v) + name_u = to_uniquename_function(leftname)(nametype(a)) + name_v = to_uniquename_function(rightname)(nametype(a)) + names_u = (codomain..., name_u) + names_s = (name_u, name_v) + names_v = (name_v, domain...) + u = NamedTensor(u_unnamed, names_u) + s = NamedTensor(s_unnamed, names_s) + v = NamedTensor(v_unnamed, names_v) return u, s, v end - function MAK.$f(a::AbstractNamedTensor, dimnames_codomain; kwargs...) - return $f_nameddims(a, dimnames_codomain; kwargs...) + function MAK.$f(a::AbstractNamedTensor, names_codomain; kwargs...) + return $f_nameddims(a, names_codomain; kwargs...) end - function $f_nameddims(a::AbstractNamedTensor, dimnames_codomain; kwargs...) + function $f_nameddims(a::AbstractNamedTensor, names_codomain; kwargs...) return MAK.$f( a, - dimnames_codomain, - dimnames_setdiff(dimnames(a), name.(dimnames_codomain)); + names_codomain, + names_setdiff(names(a), name.(names_codomain)); kwargs... ) end @@ -299,33 +285,33 @@ end # (the 2-norm of the discarded singular values), matching MatrixAlgebraKit's four-output # `svd_trunc`, so it is spelled out here rather than sharing the loop. function MAK.svd_trunc( - a::AbstractNamedTensor, dimnames_codomain, dimnames_domain; kwargs... + a::AbstractNamedTensor, names_codomain, names_domain; kwargs... ) - return svd_trunc_nameddims(a, dimnames_codomain, dimnames_domain; kwargs...) + return svd_trunc_nameddims(a, names_codomain, names_domain; kwargs...) end function svd_trunc_nameddims( - a::AbstractNamedTensor, dimnames_codomain, dimnames_domain; kwargs... + a::AbstractNamedTensor, names_codomain, names_domain; kwargs... ) - codomain = name.(dimnames_codomain) - domain = name.(dimnames_domain) + codomain = name.(names_codomain) + domain = name.(names_domain) u_unnamed, s_unnamed, v_unnamed, ϵ = TA.svd_trunc( - unnamed(a), dimnames(a), codomain, domain; kwargs... + unnamed(a), names(a), codomain, domain; kwargs... ) - name_u = uniquename(dimnametype(a)) - name_v = uniquename(dimnametype(a)) - u = nameddims(u_unnamed, (codomain..., name_u)) - s = nameddims(s_unnamed, (name_u, name_v)) - v = nameddims(v_unnamed, (name_v, domain...)) + name_u = uniquename(nametype(a)) + name_v = uniquename(nametype(a)) + u = NamedTensor(u_unnamed, (codomain..., name_u)) + s = NamedTensor(s_unnamed, (name_u, name_v)) + v = NamedTensor(v_unnamed, (name_v, domain...)) return u, s, v, ϵ end -function MAK.svd_trunc(a::AbstractNamedTensor, dimnames_codomain; kwargs...) - return svd_trunc_nameddims(a, dimnames_codomain; kwargs...) +function MAK.svd_trunc(a::AbstractNamedTensor, names_codomain; kwargs...) + return svd_trunc_nameddims(a, names_codomain; kwargs...) end -function svd_trunc_nameddims(a::AbstractNamedTensor, dimnames_codomain; kwargs...) +function svd_trunc_nameddims(a::AbstractNamedTensor, names_codomain; kwargs...) return MAK.svd_trunc( a, - dimnames_codomain, - dimnames_setdiff(dimnames(a), name.(dimnames_codomain)); + names_codomain, + names_setdiff(names(a), name.(names_codomain)); kwargs... ) end @@ -335,28 +321,28 @@ end # function MAK.svd_vals( - a::AbstractNamedTensor, dimnames_codomain, dimnames_domain; kwargs... + a::AbstractNamedTensor, names_codomain, names_domain; kwargs... ) - return svd_vals_nameddims(a, dimnames_codomain, dimnames_domain; kwargs...) + return svd_vals_nameddims(a, names_codomain, names_domain; kwargs...) end function svd_vals_nameddims( - a::AbstractNamedTensor, dimnames_codomain, dimnames_domain; kwargs... + a::AbstractNamedTensor, names_codomain, names_domain; kwargs... ) return TA.svd_vals( unnamed(a), - dimnames(a), - name.(dimnames_codomain), - name.(dimnames_domain); + names(a), + name.(names_codomain), + name.(names_domain); kwargs... ) end -function MAK.svd_vals(a::AbstractNamedTensor, dimnames_codomain; kwargs...) - return svd_vals_nameddims(a, dimnames_codomain; kwargs...) +function MAK.svd_vals(a::AbstractNamedTensor, names_codomain; kwargs...) + return svd_vals_nameddims(a, names_codomain; kwargs...) end -function svd_vals_nameddims(a::AbstractNamedTensor, dimnames_codomain; kwargs...) - codomain = name.(dimnames_codomain) - domain = dimnames_setdiff(dimnames(a), codomain) +function svd_vals_nameddims(a::AbstractNamedTensor, names_codomain; kwargs...) + codomain = name.(names_codomain) + domain = names_setdiff(names(a), codomain) return MAK.svd_vals(a, codomain, domain; kwargs...) end @@ -368,26 +354,26 @@ for f in [:eigh_full, :eig_full, :eigh_trunc, :eig_trunc] f_nameddims = Symbol(f, "_nameddims") @eval begin function MAK.$f( - a::AbstractNamedTensor, dimnames_codomain, dimnames_domain; kwargs... + a::AbstractNamedTensor, names_codomain, names_domain; kwargs... ) - return $f_nameddims(a, dimnames_codomain, dimnames_domain; kwargs...) + return $f_nameddims(a, names_codomain, names_domain; kwargs...) end function $f_nameddims( - a::AbstractNamedTensor, dimnames_codomain, dimnames_domain; + a::AbstractNamedTensor, names_codomain, names_domain; leftname = (;), rightname = (;), kwargs... ) - codomain = name.(dimnames_codomain) - domain = name.(dimnames_domain) + codomain = name.(names_codomain) + domain = name.(names_domain) d_unnamed, v_unnamed = TA.$f( - unnamed(a), dimnames(a), codomain, domain; kwargs... + unnamed(a), names(a), codomain, domain; kwargs... ) - name_d = to_uniquename_function(rightname)(dimnametype(a)) - name_d′ = to_uniquename_function(leftname)(dimnametype(a)) + name_d = to_uniquename_function(rightname)(nametype(a)) + name_d′ = to_uniquename_function(leftname)(nametype(a)) name_v = name_d - dimnames_d = (name_d′, name_d) - dimnames_v = (domain..., name_v) - d = nameddims(d_unnamed, dimnames_d) - v = nameddims(v_unnamed, dimnames_v) + names_d = (name_d′, name_d) + names_v = (domain..., name_v) + d = NamedTensor(d_unnamed, names_d) + v = NamedTensor(v_unnamed, names_v) return d, v end end @@ -401,74 +387,74 @@ for f in [:eigh_vals, :eig_vals] f_nameddims = Symbol(f, "_nameddims") @eval begin function MAK.$f( - a::AbstractNamedTensor, dimnames_codomain, dimnames_domain; kwargs... + a::AbstractNamedTensor, names_codomain, names_domain; kwargs... ) - return $f_nameddims(a, dimnames_codomain, dimnames_domain; kwargs...) + return $f_nameddims(a, names_codomain, names_domain; kwargs...) end function $f_nameddims( - a::AbstractNamedTensor, dimnames_codomain, dimnames_domain; kwargs... + a::AbstractNamedTensor, names_codomain, names_domain; kwargs... ) - codomain = name.(dimnames_codomain) - domain = name.(dimnames_domain) - return TA.$f(unnamed(a), dimnames(a), codomain, domain; kwargs...) + codomain = name.(names_codomain) + domain = name.(names_domain) + return TA.$f(unnamed(a), names(a), codomain, domain; kwargs...) end end end function MAK.left_null( - a::AbstractNamedTensor, dimnames_codomain, dimnames_domain; kwargs... + a::AbstractNamedTensor, names_codomain, names_domain; kwargs... ) - return left_null_nameddims(a, dimnames_codomain, dimnames_domain; kwargs...) + return left_null_nameddims(a, names_codomain, names_domain; kwargs...) end function left_null_nameddims( - a::AbstractNamedTensor, dimnames_codomain, dimnames_domain; name = (;), kwargs... + a::AbstractNamedTensor, names_codomain, names_domain; name = (;), kwargs... ) # `name` is a keyword here, so reach the `name` function through the module. - codomain = ITensorBase.name.(dimnames_codomain) - domain = ITensorBase.name.(dimnames_domain) - n_unnamed = TA.left_null(unnamed(a), dimnames(a), codomain, domain; kwargs...) - name_n = to_uniquename_function(name)(dimnametype(a)) - dimnames_n = (codomain..., name_n) - return nameddims(n_unnamed, dimnames_n) + codomain = ITensorBase.name.(names_codomain) + domain = ITensorBase.name.(names_domain) + n_unnamed = TA.left_null(unnamed(a), names(a), codomain, domain; kwargs...) + name_n = to_uniquename_function(name)(nametype(a)) + names_n = (codomain..., name_n) + return NamedTensor(n_unnamed, names_n) end -function MAK.left_null(a::AbstractNamedTensor, dimnames_codomain; kwargs...) - return left_null_nameddims(a, dimnames_codomain; kwargs...) +function MAK.left_null(a::AbstractNamedTensor, names_codomain; kwargs...) + return left_null_nameddims(a, names_codomain; kwargs...) end -function left_null_nameddims(a::AbstractNamedTensor, dimnames_codomain; kwargs...) - codomain = name.(dimnames_codomain) - domain = dimnames_setdiff(dimnames(a), codomain) +function left_null_nameddims(a::AbstractNamedTensor, names_codomain; kwargs...) + codomain = name.(names_codomain) + domain = names_setdiff(names(a), codomain) return MAK.left_null(a, codomain, domain; kwargs...) end function MAK.right_null( - a::AbstractNamedTensor, dimnames_codomain, dimnames_domain; kwargs... + a::AbstractNamedTensor, names_codomain, names_domain; kwargs... ) - return right_null_nameddims(a, dimnames_codomain, dimnames_domain; kwargs...) + return right_null_nameddims(a, names_codomain, names_domain; kwargs...) end function right_null_nameddims( - a::AbstractNamedTensor, dimnames_codomain, dimnames_domain; name = (;), kwargs... + a::AbstractNamedTensor, names_codomain, names_domain; name = (;), kwargs... ) # `name` is a keyword here, so reach the `name` function through the module. - codomain = ITensorBase.name.(dimnames_codomain) - domain = ITensorBase.name.(dimnames_domain) - n_unnamed = TA.right_null(unnamed(a), dimnames(a), codomain, domain; kwargs...) - name_n = to_uniquename_function(name)(dimnametype(a)) - dimnames_n = (name_n, domain...) - return nameddims(n_unnamed, dimnames_n) + codomain = ITensorBase.name.(names_codomain) + domain = ITensorBase.name.(names_domain) + n_unnamed = TA.right_null(unnamed(a), names(a), codomain, domain; kwargs...) + name_n = to_uniquename_function(name)(nametype(a)) + names_n = (name_n, domain...) + return NamedTensor(n_unnamed, names_n) end -function MAK.right_null(a::AbstractNamedTensor, dimnames_codomain; kwargs...) - return right_null_nameddims(a, dimnames_codomain; kwargs...) +function MAK.right_null(a::AbstractNamedTensor, names_codomain; kwargs...) + return right_null_nameddims(a, names_codomain; kwargs...) end -function right_null_nameddims(a::AbstractNamedTensor, dimnames_codomain; kwargs...) - codomain = name.(dimnames_codomain) - domain = dimnames_setdiff(dimnames(a), codomain) +function right_null_nameddims(a::AbstractNamedTensor, names_codomain; kwargs...) + codomain = name.(names_codomain) + domain = names_setdiff(names(a), codomain) return MAK.right_null(a, codomain, domain; kwargs...) end """ - TensorAlgebra.MatrixAlgebra.sqrth_safe(a::AbstractNamedTensor, dimnames_codomain, dimnames_domain; kwargs...) -> p + TensorAlgebra.MatrixAlgebra.sqrth_safe(a::AbstractNamedTensor, names_codomain, names_domain; kwargs...) -> p Square root of a named array `a`, interpreting it as a Hermitian positive semi-definite linear map from the domain to the codomain dimension names. @@ -486,7 +472,7 @@ See also [`TensorAlgebra.MatrixAlgebra.invsqrth_safe`](@ref) and MA.sqrth_safe """ - TensorAlgebra.MatrixAlgebra.invsqrth_safe(a::AbstractNamedTensor, dimnames_codomain, dimnames_domain; kwargs...) -> p + TensorAlgebra.MatrixAlgebra.invsqrth_safe(a::AbstractNamedTensor, names_codomain, names_domain; kwargs...) -> p Pseudo-inverse square root of a named array `a`, interpreting it as a Hermitian positive semi-definite linear map from the domain to the codomain @@ -505,7 +491,7 @@ See also [`TensorAlgebra.MatrixAlgebra.sqrth_safe`](@ref) and MA.invsqrth_safe """ - TensorAlgebra.MatrixAlgebra.sqrth_invsqrth_safe(a::AbstractNamedTensor, dimnames_codomain, dimnames_domain; kwargs...) -> p, pinv + TensorAlgebra.MatrixAlgebra.sqrth_invsqrth_safe(a::AbstractNamedTensor, names_codomain, names_domain; kwargs...) -> p, pinv Square root and pseudo-inverse square root of a named array `a` (see `TensorAlgebra.MatrixAlgebra.sqrth_safe` and @@ -518,7 +504,7 @@ unnamed array (e.g. `atol`, `rtol`). MA.sqrth_invsqrth_safe """ - MatrixAlgebraKit.project_hermitian(a::AbstractNamedTensor, dimnames_codomain, dimnames_domain; kwargs...) -> h + MatrixAlgebraKit.project_hermitian(a::AbstractNamedTensor, names_codomain, names_domain; kwargs...) -> h Hermitian part `(m + m') / 2` of a named array `a`, interpreting it as a linear map `m` from the domain to the codomain dimension names. The result @@ -531,28 +517,28 @@ MAK.project_hermitian # only in fanning the names out over its result pair. for (M, f) in ((MA, :sqrth_safe), (MA, :invsqrth_safe), (MAK, :project_hermitian)) @eval function $M.$f( - a::AbstractNamedTensor, dimnames_codomain, dimnames_domain; kwargs... + a::AbstractNamedTensor, names_codomain, names_domain; kwargs... ) - codomain = name.(dimnames_codomain) - domain = name.(dimnames_domain) - p_unnamed = TA.$f(unnamed(a), dimnames(a), codomain, domain; kwargs...) - return nameddims(p_unnamed, (codomain..., domain...)) + codomain = name.(names_codomain) + domain = name.(names_domain) + p_unnamed = TA.$f(unnamed(a), names(a), codomain, domain; kwargs...) + return NamedTensor(p_unnamed, (codomain..., domain...)) end end function MA.sqrth_invsqrth_safe( - a::AbstractNamedTensor, dimnames_codomain, dimnames_domain; kwargs... + a::AbstractNamedTensor, names_codomain, names_domain; kwargs... ) - codomain = name.(dimnames_codomain) - domain = name.(dimnames_domain) + codomain = name.(names_codomain) + domain = name.(names_domain) p_unnamed, pinv_unnamed = TA.sqrth_invsqrth_safe( - unnamed(a), dimnames(a), codomain, domain; kwargs... + unnamed(a), names(a), codomain, domain; kwargs... ) - dimnames_p = (codomain..., domain...) - return nameddims(p_unnamed, dimnames_p), nameddims(pinv_unnamed, dimnames_p) + names_p = (codomain..., domain...) + return NamedTensor(p_unnamed, names_p), NamedTensor(pinv_unnamed, names_p) end """ - Base.one(a::AbstractNamedTensor, dimnames_codomain, dimnames_domain) -> Id + Base.one(a::AbstractNamedTensor, names_codomain, names_domain) -> Id Return an identity-operator-shaped named array sharing `a`'s dimension names, codomain/domain partition, and element type. The fused codomain and domain sizes @@ -581,17 +567,17 @@ julia> tr(one(a, (i, j), (k, l)), (i, j), (k, l)) ``` """ function Base.one( - a::AbstractNamedTensor, dimnames_codomain, dimnames_domain + a::AbstractNamedTensor, names_codomain, names_domain ) - return one_nameddims(a, dimnames_codomain, dimnames_domain) + return one_nameddims(a, names_codomain, names_domain) end function one_nameddims( - a::AbstractNamedTensor, dimnames_codomain, dimnames_domain + a::AbstractNamedTensor, names_codomain, names_domain ) - codomain = name.(dimnames_codomain) - domain = name.(dimnames_domain) - raw = TA.one(unnamed(a), dimnames(a), codomain, domain) - return nameddims(raw, (codomain..., domain...)) + codomain = name.(names_codomain) + domain = name.(names_domain) + raw = TA.one(unnamed(a), names(a), codomain, domain) + return NamedTensor(raw, (codomain..., domain...)) end """ @@ -652,7 +638,7 @@ julia> tr(fill(2.0, (i, j, k, l)), (i, j), (k, l)) """ function LA.tr(a::AbstractNamedTensor, codomain, domain) codomain, domain = Tuple(codomain), Tuple(domain) - return TA.tr(unnamed(a), dimnames(a), name.(codomain), name.(domain)) + return TA.tr(unnamed(a), names(a), name.(codomain), name.(domain)) end const MATRIX_FUNCTIONS = [ @@ -667,19 +653,19 @@ for f in MATRIX_FUNCTIONS f_nameddims = Symbol(f, "_nameddims") @eval begin function Base.$f( - a::AbstractNamedTensor, dimnames_codomain, dimnames_domain; kwargs... + a::AbstractNamedTensor, names_codomain, names_domain; kwargs... ) - return $f_nameddims(a, dimnames_codomain, dimnames_domain; kwargs...) + return $f_nameddims(a, names_codomain, names_domain; kwargs...) end function $f_nameddims( - a::AbstractNamedTensor, dimnames_codomain, dimnames_domain; kwargs... + a::AbstractNamedTensor, names_codomain, names_domain; kwargs... ) - codomain = name.(dimnames_codomain) - domain = name.(dimnames_domain) + codomain = name.(names_codomain) + domain = name.(names_domain) fa_unnamed = TA.$f( - unnamed(a), dimnames(a), codomain, domain; kwargs... + unnamed(a), names(a), codomain, domain; kwargs... ) - return nameddims(fa_unnamed, (codomain..., domain...)) + return NamedTensor(fa_unnamed, (codomain..., domain...)) end end end @@ -699,7 +685,7 @@ function name_projected(projected, input_names) _ -> uniquename(eltype(input_names)), TA.ndims(projected) - length(input_names) ) - return nameddims(projected, (input_names..., aux_names...)) + return NamedTensor(projected, (input_names..., aux_names...)) end # Each `_nameddims` runs the named-index layer of a `TensorAlgebra` verb: strip the axes to diff --git a/test/Project.toml b/test/Project.toml index 912ae1bf..daa35d13 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -33,7 +33,7 @@ Adapt = "4" Aqua = "0.8.9" Combinatorics = "1" GradedArrays = "0.16.4" -ITensorBase = "0.14" +ITensorBase = "0.15" ITensorPkgSkeleton = "0.3.42" JLArrays = "0.2, 0.3" LinearAlgebra = "1.10" diff --git a/test/test_abstracttrees.jl b/test/test_abstracttrees.jl index bce94e9b..54fc911b 100644 --- a/test/test_abstracttrees.jl +++ b/test/test_abstracttrees.jl @@ -1,9 +1,9 @@ using AbstractTrees: printnode -using ITensorBase: nameddims +using ITensorBase: NamedTensor using Test: @test, @testset @testset "AbstractTrees" begin a = randn(3, 4) - na = nameddims(a, ("i", "j")) + na = NamedTensor(a, ("i", "j")) @test sprint(printnode, na) == "{\"i\", \"j\"}" end diff --git a/test/test_adapt.jl b/test/test_adapt.jl index 8a85368a..3df149c0 100644 --- a/test/test_adapt.jl +++ b/test/test_adapt.jl @@ -1,10 +1,10 @@ using Adapt: adapt -using ITensorBase: nameddims +using ITensorBase: NamedTensor 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 = NamedTensor(randn(2, 2), ("i", "j")) na_complex = adapt(Array{complex(elt)}, na) @test na ≈ na_complex @test eltype(na_complex) ≡ complex(elt) diff --git a/test/test_basics.jl b/test/test_basics.jl index 3d69f5f5..dad03b35 100644 --- a/test/test_basics.jl +++ b/test/test_basics.jl @@ -1,8 +1,8 @@ using ITensorBase: ITensorBase, AbstractNamedTensor, ITensor, Index, IndexName, NamedTensor, - commonind, commoninds, dimnametype, gettag, hascommoninds, hastag, id, inds, mapinds, - name, named, noncommonind, noncommoninds, noprime, operator, plev, prime, replaceinds, - setplev, settag, sim, tags, trycommonind, trynoncommonind, tryuniqueind, unioninds, - uniqueind, uniqueinds, uniquename, unname, unnamed, unsettag, uuid + commonind, commoninds, gettag, hascommoninds, hastag, id, inds, name, nametype, + noncommonind, noncommoninds, noprime, operator, plev, prime, rename, setplev, settag, + sim, tags, trycommonind, trynoncommonind, tryuniqueind, unioninds, uniqueind, + uniqueinds, uniquename, unname, unnamed, unsettag, uuid using Test: @test, @test_broken, @test_throws, @testset using UUIDs: UUID @@ -115,17 +115,17 @@ using UUIDs: UUID @test plev(i) == 0 @test plev(prime(i)) == 1 @test length(tags(i)) == 0 - a′ = mapinds(prime, a) + a′ = rename(prime, a) @test unnamed(a′) == x @test issetequal(inds(a′), (prime(i), prime(j))) - # The number of dimnames must match the array's `ndims`, and the dimnames are + # The number of names must match the array's `ndims`, and the names are # passed as a single collection. @test_throws ArgumentError NamedTensor(randn(elt, 4), (:i, :j)) @test_throws MethodError NamedTensor(randn(elt, 2, 2), :i, :j) - # Passing indices as a tuple or vector builds the tensor, using only their names and - # taking the space from the array. A single bare index still errors (it is ambiguous). + # Passing indices as a tuple or vector builds the tensor from their names, keeping only + # the names. A single bare index still errors (it is ambiguous). i, j = Index.((2, 3)) @test NamedTensor(randn(elt, 2, 3), (i, j)) isa ITensor @test ITensor(randn(elt, 2, 3), (i, j)) isa ITensor @@ -133,8 +133,21 @@ using UUIDs: UUID t = ITensor(randn(elt, 2, 3), (i, j)) @test issetequal(name.(inds(t)), name.((i, j))) @test_throws ArgumentError ITensor(randn(elt, 2), i) - # The space is taken from the array, not from the index (a mismatched index dim is ignored). - @test size(unnamed(ITensor(randn(elt, 2, 3), (i, Index(9))))) == (2, 3) + # A dimension given as an index asserts a space, which has to match the array's axis. + @test_throws ArgumentError ITensor(randn(elt, 2, 3), (i, Index(9))) + @test_throws ArgumentError NamedTensor(randn(elt, 2, 3), (i, Index(9))) + # A bare name asserts no space, so it is unaffected by the check. + @test ITensor(randn(elt, 2, 3), (name(i), name(Index(9)))) isa ITensor + + # The codomain/domain form takes the two dimension groups separately, with the same + # space check on each group and the same rejection of a lone index. + @test NamedTensor(randn(elt, 2, 3), (i,), (j,)) isa ITensor + @test names(ITensor(randn(elt, 2, 3), (i,), (j,))) == name.([i, j]) + @test ITensor(randn(elt, 2, 3), (), (i, j)) isa ITensor + @test_throws ArgumentError ITensor(randn(elt, 2, 3), i, (j,)) + @test_throws ArgumentError ITensor(randn(elt, 2, 3), (i,), j) + @test_throws ArgumentError ITensor(randn(elt, 2, 3), (i,), (Index(9),)) + @test_throws ArgumentError ITensor(randn(elt, 2), (i,), (j,)) # The other supported constructions: index the array (inherit the space from the # indices), or attach only the names (take the space from the array). @test randn(elt, 2, 3)[i, j] isa ITensor @@ -165,19 +178,19 @@ using UUIDs: UUID @test unnamed(a) == unname(b, (i, j, k)) @test unnamed(a) == permutedims(unnamed(b), (2, 3, 1)) end - @testset "dimnametype" begin + @testset "nametype" begin i, j = Index.((2, 3)) a = randn(Float64, i, j) @test a isa NamedTensor - @test dimnametype(a) === IndexName - @test dimnametype(typeof(a)) === IndexName - @test dimnametype(NamedTensor{IndexName}) === IndexName + @test nametype(a) === IndexName + @test nametype(typeof(a)) === IndexName + @test nametype(NamedTensor{IndexName}) === IndexName # An operator reports the dimname flavor of its underlying tensor. op = operator(a, (name(i),), (name(j),)) - @test dimnametype(op) === IndexName - @test dimnametype(typeof(op)) === IndexName + @test nametype(op) === IndexName + @test nametype(typeof(op)) === IndexName # Unparameterized `NamedTensor` does not fix its dimname flavor, like `eltype(Array)`. - @test dimnametype(NamedTensor) === Any + @test nametype(NamedTensor) === Any end @testset "show" begin i = Index(2) @@ -200,9 +213,9 @@ using UUIDs: UUID @test noprime(a′) == a @test issetequal(inds(noprime(prime(a′))), (i, j)) - # `replaceinds` is a name-only synonym for the pair-based relabel. + # `rename` takes index-keyed pairs, relabeling name-only. k, l = Index.((2, 3)) - a_r = replaceinds(a, i => k, j => l) + a_r = rename(a, i => k, j => l) @test unnamed(a_r) == unnamed(a) @test issetequal(inds(a_r), (k, l)) diff --git a/test/test_exports.jl b/test/test_exports.jl index f0c5e100..e295545f 100644 --- a/test/test_exports.jl +++ b/test/test_exports.jl @@ -4,17 +4,17 @@ using Test: @test, @testset exports = [ :ITensorBase, :AbstractNamedTensor, :NamedTensor, :AbstractITensor, :ITensor, :Index, :NamedUnitRange, - :aligndims, :aligneddims, :apply, :commonind, :commoninds, - :dimnames, :dimnametype, :hascommoninds, :id, - :inds, :inputaxes, :inputinds, :inputnames, :mapinds, :named, :nameddims, - :noncommonind, :noncommoninds, + :align, :aligned, :apply, :commonind, :commoninds, + :hascommoninds, :id, + :inds, :inputaxes, :inputinds, :inputnames, + :nametype, :noncommonind, :noncommoninds, :noprime, :operator, :outputaxes, :outputinds, :outputnames, :prime, - :replaceinds, :sim, :similar_operator, :state, :trycommonind, :trynoncommonind, + :sim, :similar_operator, :state, :trycommonind, :trynoncommonind, :tryuniqueind, :uniqueind, :uniqueinds, :unioninds, :uniquename, ] publics = [ - :IndexName, :name, :nametype, :replacedimnames, :setname, :space, :unnamed, + :IndexName, :name, :rename, :setname, :space, :unnamed, :unnamedtype, :decoration, :emptytags, :gettag, :gettags, :hastag, :plev, :settags, :tags, :unsettags, diff --git a/test/test_gradedarraysext.jl b/test/test_gradedarraysext.jl index 43867cf1..dcdcae47 100644 --- a/test/test_gradedarraysext.jl +++ b/test/test_gradedarraysext.jl @@ -1,8 +1,8 @@ using GradedArrays: U1, sectors -using ITensorBase: ITensorBase, Index, aligndims, inds, prime, space, unnamed +using ITensorBase: ITensorBase, ITensor, Index, align, inds, prime, space, unnamed using StableRNGs: StableRNG -using TensorAlgebra: TensorAlgebra, isdual, project, project_aux, tryproject, - tryproject_aux, unchecked_project, unchecked_project_aux +using TensorAlgebra: TensorAlgebra, dual, isdual, matricize, project, project_aux, + tryproject, tryproject_aux, unchecked_project, unchecked_project_aux, unmatricize using TensorKitSectors: FermionNumber using Test: @test, @test_throws, @testset @@ -78,7 +78,7 @@ end # Broadcasting over graded (GradedArrays.jl) indices routes the named expression through the # `GradedArray` / matricized `FusedGradedMatrix` backend. Linear combinations add block-wise; a sum -# flattens all-codomain, so a within-split reorder is compared at a common split via `aligndims`. +# flattens all-codomain, so a within-split reorder is compared at a common split via `align`. @testset "GradedArraysExt broadcasting (eltype = $elt)" for elt in (Float64, ComplexF64) rng = StableRNG(1234) i = Index([U1(0) => 2, U1(1) => 3]; tags = "i") @@ -97,11 +97,35 @@ end n = randn(rng, elt, (i,), (j,)) @test unnamed(m .+ n) ≈ unnamed(m) + unnamed(n) - # Within-split reorder still adds correctly (the sum is all-codomain, compared via `aligndims`). + # Within-split reorder still adds correctly (the sum is all-codomain, compared via `align`). mr1 = randn(rng, elt, (i, j), (k,)) mr2 = randn(rng, elt, (j, i), (k,)) - @test unnamed(aligndims(mr1 .+ mr2, (i, j), (k,))) ≈ - unnamed(mr1) + unnamed(aligndims(mr2, (i, j), (k,))) + @test unnamed(align(mr1 .+ mr2, (i, j), (k,))) ≈ + unnamed(mr1) + unnamed(align(mr2, (i, j), (k,))) +end + +# `matricize` fuses a tensor's codomain/domain split into an unnamed matrix and `unmatricize` +# splits one back out over named indices. The domain index is stored dualized while it is given +# to `unmatricize` codomain-facing, so a graded backend is where that convention is visible. +@testset "GradedArraysExt matricize/unmatricize (eltype = $elt)" for elt in + ( + Float64, + ComplexF64, + ) + rng = StableRNG(1234) + i = Index([U1(0) => 2, U1(1) => 3]; tags = "i") + j = Index([U1(0) => 1, U1(1) => 2]; tags = "j") + k = Index([U1(-1) => 1, U1(0) => 2]; tags = "k") + + a = randn(rng, elt, (i, j), (k,)) + @test isdual(inds(a)[3]) + m = matricize(a, (i, j), (k,)) + @test m isa AbstractMatrix{elt} + @test size(m) == (length(i) * length(j), length(k)) + rt = unmatricize(m, (i, j), (k,)) + @test names(rt) == names(a) + @test isdual(inds(rt)[3]) + @test unnamed(rt) ≈ unnamed(a) end # `project_aux` and its siblings derive a named auxiliary leg carrying the operator's flux, so a @@ -136,3 +160,25 @@ end end end end + +# A dimension given as an index asserts its whole space, not just its length: over graded +# indices that means the sectors and the duality, both of which a length-only check would miss. +@testset "GradedArraysExt constructor space check" begin + rng = StableRNG(1234) + i = Index([U1(0) => 1, U1(1) => 2]; tags = "i") + j = Index([U1(0) => 2, U1(1) => 1]; tags = "j") + a = unnamed(randn(rng, (i, j))) + @test ITensor(a, (i, j)) isa ITensor + # Same length, opposite duality. + @test length(dual(j)) == length(j) + @test_throws ArgumentError ITensor(a, (i, dual(j))) + # Same length, different sectors. + k = Index([U1(0) => 1, U1(2) => 2]; tags = "k") + @test length(k) == length(i) + @test_throws ArgumentError ITensor(a, (k, j)) + # The codomain/domain form takes the domain index codomain-facing while the storage holds + # it dualized, so the domain index is checked against the dual of the stored axis. + m = unnamed(randn(rng, (i,), (j,))) + @test ITensor(m, (i,), (j,)) isa ITensor + @test_throws ArgumentError ITensor(m, (i,), (dual(j),)) +end diff --git a/test/test_lazyitensors.jl b/test/test_lazyitensors.jl index edc7fe51..5e3577bf 100644 --- a/test/test_lazyitensors.jl +++ b/test/test_lazyitensors.jl @@ -1,8 +1,8 @@ using AbstractTrees: AbstractTrees, print_tree, printnode using Base.Broadcast: materialize -using ITensorBase: @names, Greedy, LazyNamedTensor, Mul, NamedTensor, NamedTensorOperator, - SymbolicNamedTensor, dimnames, inds, inputnames, ismul, lazy, nameddims, namedoneto, - operator, optimize_evaluation_order, outputnames, state, substitute, symnameddims +using ITensorBase: @names, Greedy, LazyNamedTensor, Mul, NamedOneTo, NamedTensor, + NamedTensorOperator, SymbolicNamedTensor, inds, inputnames, ismul, lazy, operator, + optimize_evaluation_order, outputnames, state, substitute, symnameddims using OMEinsumContractionOrders: ExhaustiveSearch, GreedyMethod, TreeSA using TermInterface: arguments, arity, children, head, iscall, isexpr, maketerm, operation, sorted_arguments, sorted_children @@ -11,7 +11,7 @@ using WrappedUnions: unwrap @testset "LazyNamedTensors" begin @testset "Basics" begin - i, j, k, l = namedoneto.(2, (:i, :j, :k, :l)) + i, j, k, l = NamedOneTo.(2, (:i, :j, :k, :l)) a1 = randn(i, j) a2 = randn(j, k) a3 = randn(k, l) @@ -36,9 +36,9 @@ using WrappedUnions: unwrap end @testset "TermInterface" begin - a1 = nameddims(randn(2, 2), (:i, :j)) - a2 = nameddims(randn(2, 2), (:j, :k)) - a3 = nameddims(randn(2, 2), (:k, :l)) + a1 = NamedTensor(randn(2, 2), (:i, :j)) + a2 = NamedTensor(randn(2, 2), (:j, :k)) + a3 = NamedTensor(randn(2, 2), (:k, :l)) l1, l2, l3 = lazy.((a1, a2, a3)) @test_throws ErrorException arguments(l1) @@ -86,7 +86,7 @@ using WrappedUnions: unwrap @test unwrap(a1) == SymbolicNamedTensor(:a1, ()) @test isequal(unwrap(a1), SymbolicNamedTensor(:a1, ())) @test isempty(inds(a1)) - @test isempty(dimnames(a1)) + @test isempty(names(a1)) ex = a1 * a2 * a3 @test copy(ex) == ex @@ -108,7 +108,7 @@ using WrappedUnions: unwrap end @testset "optimize_evaluation_order ($alg)" for alg in (Greedy(),) - i, j, k, l = namedoneto.((2, 3, 4, 5), (:i, :j, :k, :l)) + i, j, k, l = NamedOneTo.((2, 3, 4, 5), (:i, :j, :k, :l)) s = [symnameddims(:a, (i, j)), symnameddims(:b, (j, k)), symnameddims(:c, (k, l))] flat = lazy(Mul(s)) ordered = optimize_evaluation_order(flat; alg) @@ -117,12 +117,12 @@ using WrappedUnions: unwrap # Reordering nests the flat product into binary contractions and preserves # the open indices. @test arity(ordered) == 2 - @test issetequal(dimnames(ordered), dimnames(flat)) + @test issetequal(names(ordered), names(flat)) end @testset "optimize_evaluation_order with repeated arguments ($alg)" for alg in (Greedy(),) - i, j = namedoneto.((2, 3), (:i, :j)) + i, j = NamedOneTo.((2, 3), (:i, :j)) a = randn(i, j) # Three equal arguments: the optimizer must contract them pairwise rather than # treating the repeats as one argument. @@ -130,7 +130,7 @@ using WrappedUnions: unwrap ordered = optimize_evaluation_order(flat; alg) @test ismul(ordered) @test arity(ordered) == 2 - @test issetequal(dimnames(ordered), dimnames(flat)) + @test issetequal(names(ordered), names(flat)) @test materialize(ordered) ≈ (a * a) * a end @@ -140,19 +140,19 @@ using WrappedUnions: unwrap GreedyMethod(), TreeSA(), ) - i, j, k, l = namedoneto.((2, 3, 4, 5), (:i, :j, :k, :l)) + i, j, k, l = NamedOneTo.((2, 3, 4, 5), (:i, :j, :k, :l)) s = [symnameddims(:a, (i, j)), symnameddims(:b, (j, k)), symnameddims(:c, (k, l))] flat = lazy(Mul(s)) ordered = optimize_evaluation_order(flat; alg) @test ordered isa LazyNamedTensor @test ismul(ordered) @test arity(ordered) == 2 - @test issetequal(dimnames(ordered), dimnames(flat)) + @test issetequal(names(ordered), names(flat)) end end @testset "lazy operator promotion" begin - i, j = namedoneto.(2, (:i, :j)) + i, j = NamedOneTo.(2, (:i, :j)) p = randn(i, j) # eager plain o = operator(randn(i, j), (i,), (j,)) # eager operator lp = lazy(p) # lazy plain diff --git a/test/test_linearalgebra.jl b/test/test_linearalgebra.jl index a3603451..100bf4ad 100644 --- a/test/test_linearalgebra.jl +++ b/test/test_linearalgebra.jl @@ -1,10 +1,10 @@ import LinearAlgebra as LA -using ITensorBase: dimnames, named, unname, unnamed +using ITensorBase: Named, unname, unnamed using Test: @test, @testset @testset "LinearAlgebra (eltype=$(elt))" for elt in (Float32, Float64, Complex{Float32}) - i, j = named.(2, (:i, :j)) + i, j = Named.(2, (:i, :j)) a = randn(elt, i, j) b = randn(elt, j, i) @test LA.norm(a) ≈ LA.norm(unnamed(a)) @@ -14,5 +14,5 @@ using Test: @test, @testset @test unnamed(LA.lmul!(2, copy(a))) ≈ 2 * unnamed(a) @test unnamed(LA.rdiv!(copy(a), 2)) ≈ unnamed(a) / 2 @test unnamed(LA.ldiv!(2, copy(a))) ≈ 2 \ unnamed(a) - @test LA.dot(a, b) ≈ LA.dot(unnamed(a), unname(b, dimnames(a))) + @test LA.dot(a, b) ≈ LA.dot(unnamed(a), unname(b, names(a))) end diff --git a/test/test_mooncakeext.jl b/test/test_mooncakeext.jl index befd7e73..4f07e6e9 100644 --- a/test/test_mooncakeext.jl +++ b/test/test_mooncakeext.jl @@ -1,5 +1,5 @@ -using ITensorBase: Name, NamedTensor, NamedUnitRange, dimnames, dimnames_setdiff, inds, - name, nameperm, to_inds, uniquename +using ITensorBase: Name, NamedTensor, NamedUnitRange, inds, name, nameperm, names_setdiff, + to_inds, uniquename using LinearAlgebra: mul! using Mooncake: Mooncake using Random: Random @@ -23,13 +23,13 @@ using Test: @test, @testset Mooncake.TestUtils.test_rule( rng, nameperm, a1, (i,), (j,); mode, is_primitive ) - Mooncake.TestUtils.test_rule(rng, dimnames, a1; mode, is_primitive) - Mooncake.TestUtils.test_rule(rng, dimnames, a1, 1; mode, is_primitive) + Mooncake.TestUtils.test_rule(rng, names, a1; mode, is_primitive) + Mooncake.TestUtils.test_rule(rng, names, a1, 1; mode, is_primitive) Mooncake.TestUtils.test_rule(rng, inds, a1; mode, is_primitive) Mooncake.TestUtils.test_rule(rng, inds, a1, 1; mode, is_primitive) Mooncake.TestUtils.test_rule( rng, - dimnames_setdiff, + names_setdiff, (i, j), (j, k); mode, diff --git a/test/test_nameddims_basics.jl b/test/test_nameddims_basics.jl index e2059bf9..b2172756 100644 --- a/test/test_nameddims_basics.jl +++ b/test/test_nameddims_basics.jl @@ -1,8 +1,8 @@ using Combinatorics: Combinatorics -using ITensorBase: @names, AbstractNamedTensor, Name, NameMismatch, NamedDimsCartesianIndex, - NamedDimsCartesianIndices, NamedTensor, aligndims, aligneddims, apply, dim, dimnames, - dimnametype, dims, inds, isnamed, mapinds, name, named, nameddims, namedoneto, product, - replacedimnames, replaceinds, setdimnames, unname, unnamed, unnamedtype +using ITensorBase: @names, AbstractNamedTensor, Name, NameMismatch, Named, + NamedDimsCartesianIndex, NamedDimsCartesianIndices, NamedOneTo, NamedTensor, + NamedUnitRange, align, aligned, apply, dim, dims, inds, isnamed, name, nametype, + product, rename, setnames, unname, unnamed, unnamedtype using LinearAlgebra: LinearAlgebra using Random: default_rng using TensorAlgebra: datatype @@ -17,7 +17,7 @@ end @testset "Basic functionality (eltype=$elt)" for elt in TestBasicsUtils.elts a = randn(elt, 3, 4) @test !isnamed(a) - na = nameddims(a, ("i", "j")) + na = NamedTensor(a, ("i", "j")) @test na isa NamedTensor{String} @test na isa AbstractNamedTensor{String} @test eltype(na) === elt @@ -28,8 +28,8 @@ end @test unnamed(na) == a si, sj = size(na) ai, aj = axes(na) - i = namedoneto(3, "i") - j = namedoneto(4, "j") + i = NamedOneTo(3, "i") + j = NamedOneTo(4, "j") @test si == 3 @test sj == 4 @test name(ai) == "i" @@ -41,9 +41,9 @@ end @test axes(na) isa Tuple @test inds(na, 1) == i @test inds(na, 2) == j - @test dimnames(na) == ["i", "j"] - @test dimnames(na, 1) == "i" - @test dimnames(na, 2) == "j" + @test names(na) == ["i", "j"] + @test names(na, 1) == "i" + @test names(na, 2) == "j" @test dim(na, "i") == 1 @test dim(na, "j") == 2 @test dims(na, ("j", "i")) == (2, 1) @@ -52,37 +52,37 @@ end # recoverable from an instance. @test unnamedtype(na) === typeof(a) @test unnamedtype(typeof(na)) === AbstractArray - @test dimnametype(typeof(na)) === String - @test dimnametype(na) === String + @test nametype(typeof(na)) === String + @test nametype(na) === String # equals (==)/isequal a = randn(elt, 3, 4) - na = nameddims(a, ("i", "j")) + na = NamedTensor(a, ("i", "j")) @test na == na - @test na == aligndims(na, ("j", "i")) + @test na == align(na, ("j", "i")) @test isequal(na, na) - @test isequal(na, aligndims(na, ("j", "i"))) - @test hash(na) == hash(aligndims(na, ("j", "i"))) + @test isequal(na, align(na, ("j", "i"))) + @test hash(na) == hash(align(na, ("j", "i"))) # Regression test that ITensorBase # with different names are not equal (as opposed to # erroring). - @test na ≠ nameddims(a, ("j", "k")) - @test !isequal(na, nameddims(a, ("j", "k"))) - @test hash(na) ≠ hash(nameddims(a, ("j", "k"))) + @test na ≠ NamedTensor(a, ("j", "k")) + @test !isequal(na, NamedTensor(a, ("j", "k"))) + @test hash(na) ≠ hash(NamedTensor(a, ("j", "k"))) a = randn(elt, 2, 2) - na = nameddims(a, ("i", "j")) + na = NamedTensor(a, ("i", "j")) @test CartesianIndices(na) == CartesianIndices(a) @test collect(pairs(na)) == (CartesianIndices(a) .=> a) @test_throws ArgumentError NamedTensor( randn(4), - namedoneto.((2, 2), ("i", "j")) + NamedOneTo.((2, 2), ("i", "j")) ) - ## @test_throws ErrorException NamedTensor(randn(2, 2), namedoneto.((2, 3), ("i", "j"))) + ## @test_throws ErrorException NamedTensor(randn(2, 2), NamedOneTo.((2, 3), ("i", "j"))) a = randn(elt, 3, 4) - na = nameddims(a, ("i", "j")) + na = NamedTensor(a, ("i", "j")) @test eltype(na) ≡ elt @test scalartype(na) ≡ elt @test datatype(na) ≡ typeof(a) @@ -95,14 +95,14 @@ end @test m isa Matrix{elt} @test m == a @test_throws Exception Vector(na) # two legs, not a vector - v = nameddims(randn(elt, 4), ("i",)) + v = NamedTensor(randn(elt, 4), ("i",)) @test Vector(v) isa Vector{elt} @test Vector(v) == unnamed(v) @test_throws Exception Matrix(v) # one leg, not a matrix if elt <: Real a = randn(elt, 3, 4) - na = nameddims(a, ("i", "j")) + na = NamedTensor(a, ("i", "j")) for a′ in (Array{Float32}(na), Matrix{Float32}(na)) @test eltype(a′) ≡ Float32 @test a′ isa Matrix{Float32} @@ -111,18 +111,18 @@ end end a = randn(elt, 2, 2, 2) - na = nameddims(a, ("i", "j", "k")) + na = NamedTensor(a, ("i", "j", "k")) b = randn(elt, 2, 2, 2) - nb = nameddims(b, ("k", "i", "j")) + nb = NamedTensor(b, ("k", "i", "j")) copyto!(na, nb) @test na == nb @test unnamed(na) == unname(nb, ("i", "j", "k")) @test unnamed(na) == permutedims(unnamed(nb), (2, 3, 1)) a = randn(elt, 3, 4) - na = nameddims(a, ("i", "j")) - i = namedoneto(3, "i") - j = namedoneto(4, "j") + na = NamedTensor(a, ("i", "j")) + i = NamedOneTo(3, "i") + j = NamedOneTo(4, "j") for na′ in ( similar(na, Float32, (j, i)), similar(a, Float32, (j, i)), @@ -133,9 +133,9 @@ end end a = randn(elt, 3, 4) - na = nameddims(a, ("i", "j")) - i = namedoneto(3, "i") - j = namedoneto(4, "j") + na = NamedTensor(a, ("i", "j")) + i = NamedOneTo(3, "i") + j = NamedOneTo(4, "j") for na′ in ( similar(na, (j, i)), similar(a, (j, i)), @@ -151,11 +151,11 @@ end @test a[i, j] == na @test @view(a[i, j]) == na @test na[j[1], i[2]] == a[2, 1] - @test inds(na[j, i]) == [named(1:3, "i"), named(1:4, "j")] + @test inds(na[j, i]) == [NamedUnitRange(1:3, "i"), NamedUnitRange(1:4, "j")] @test na[j, i] == na @test @view(na[j, i]) == na - @test i[axes(a, 1)] == named(1:3, "i") - @test j[axes(a, 2)] == named(1:4, "j") + @test i[axes(a, 1)] == NamedUnitRange(1:3, "i") + @test j[axes(a, 2)] == NamedUnitRange(1:4, "j") @test axes(na, i) == ai @test axes(na, j) == aj @test size(na, i) == si @@ -163,18 +163,21 @@ end # Regression test for ambiguity error with # `Base.getindex(A::Array, I::AbstractUnitRange{<:Integer})`. - i = namedoneto(2, "i") + i = NamedOneTo(2, "i") a = randn(elt, 2) na = a[i] @test na isa NamedTensor{String} - @test dimnames(na) == ["i"] + @test names(na) == ["i"] @test unnamed(na) == a # slicing a = randn(elt, 3, 3) na = NamedTensor(a, ("i", "j")) - for na′ in (na[named(2:3, "i"), named(2:3, "j")], na["i" => 2:3, "j" => 2:3]) - @test inds(na′) == [named(1:2, "i"), named(1:2, "j")] + for na′ in ( + na[NamedUnitRange(2:3, "i"), NamedUnitRange(2:3, "j")], + na["i" => 2:3, "j" => 2:3], + ) + @test inds(na′) == [NamedUnitRange(1:2, "i"), NamedUnitRange(1:2, "j")] @test unnamed(na′) == a[2:3, 2:3] @test unnamed(na′) isa typeof(a) end @@ -183,8 +186,11 @@ end a = randn(elt, 3, 3) na = NamedTensor(a, ("i", "j")) for na′ in - (@view(na[named(2:3, "i"), named(2:3, "j")]), @view(na["i" => 2:3, "j" => 2:3])) - @test inds(na′) == [named(1:2, "i"), named(1:2, "j")] + ( + @view(na[NamedUnitRange(2:3, "i"), NamedUnitRange(2:3, "j")]), + @view(na["i" => 2:3, "j" => 2:3]), + ) + @test inds(na′) == [NamedUnitRange(1:2, "i"), NamedUnitRange(1:2, "j")] @test copy(unnamed(na′)) == a[2:3, 2:3] @test unnamed(na′) ≡ @view(a[2:3, 2:3]) @test unnamed(na′) isa SubArray{elt, 2} @@ -218,7 +224,7 @@ end @test a′[2, 1] ≠ 21 a = randn(elt, 3, 4) - na = nameddims(a, ("i", "j")) + na = NamedTensor(a, ("i", "j")) a′ = unnamed(na) @test a′ isa Matrix{elt} @test a′ == a @@ -228,26 +234,22 @@ end a′ = unnamed(na, ("j", "i")) @test a′ isa PermutedDimsArray{elt} @test a′ == transpose(a) - nb = setdimnames(na, ("k", "j")) - @test inds(nb) == [named(1:3, "k"), named(1:4, "j")] + nb = setnames(na, ("k", "j")) + @test inds(nb) == [NamedUnitRange(1:3, "k"), NamedUnitRange(1:4, "j")] @test unnamed(nb) == a - nb = replacedimnames(na, "i" => "k") - @test inds(nb) == [named(1:3, "k"), named(1:4, "j")] + nb = rename(na, "i" => "k") + @test inds(nb) == [NamedUnitRange(1:3, "k"), NamedUnitRange(1:4, "j")] @test unnamed(nb) == a - nb = replaceinds(na, named(1:3, "i") => named(1:3, "k")) - @test inds(nb) == [named(1:3, "k"), named(1:4, "j")] + # An index-keyed pair relabels by the index's name. + nb = rename(na, NamedUnitRange(1:3, "i") => NamedUnitRange(1:3, "k")) + @test inds(nb) == [NamedUnitRange(1:3, "k"), NamedUnitRange(1:4, "j")] @test unnamed(nb) == a - nb = replaceinds(n -> n == named(1:3, "i") ? named(1:3, "k") : n, na) - @test inds(nb) == [named(1:3, "k"), named(1:4, "j")] - @test unnamed(nb) == a - nb = mapinds(n -> n == named(1:3, "i") ? named(1:3, "k") : n, na) - @test inds(nb) == [named(1:3, "k"), named(1:4, "j")] + # The function form maps over the names. + nb = rename(n -> n == "i" ? "k" : n, na) + @test inds(nb) == [NamedUnitRange(1:3, "k"), NamedUnitRange(1:4, "j")] @test unnamed(nb) == a # An empty replacement set relabels nothing. - nb = replacedimnames(na) - @test inds(nb) == inds(na) - @test unnamed(nb) == a - nb = replaceinds(na) + nb = rename(na) @test inds(nb) == inds(na) @test unnamed(nb) == a na[1, 1] = 11 @@ -255,8 +257,8 @@ end @test size(na) == (3, 4) # An NamedTensor's `length` is the plain element count (product of its size). @test length(na) == 12 - @test Tuple(axes(na)) == (named(1:3, "i"), named(1:4, "j")) - @test randn(named.((3, 4), ("i", "j"))) isa NamedTensor + @test Tuple(axes(na)) == (NamedUnitRange(1:3, "i"), NamedUnitRange(1:4, "j")) + @test randn(Named.((3, 4), ("i", "j"))) isa NamedTensor @test na["i" => 1, "j" => 2] == a[1, 2] @test na["j" => 2, "i" => 1] == a[1, 2] na["j" => 2, "i" => 1] = 12 @@ -264,56 +266,56 @@ end @test na[j => 1, i => 2] == a[2, 1] na[j => 1, i => 2] = 21 @test na[2, 1] == 21 - na′ = aligndims(na, ("j", "i")) + na′ = align(na, ("j", "i")) @test unnamed(na′) isa Matrix{elt} @test a == permutedims(unnamed(na′), (2, 1)) - na′ = aligneddims(na, ("j", "i")) + na′ = aligned(na, ("j", "i")) @test unnamed(na′) isa PermutedDimsArray{elt} @test a == permutedims(unnamed(na′), (2, 1)) - na′ = aligndims(na, (j, i)) + na′ = align(na, (j, i)) @test unnamed(na′) isa Matrix{elt} @test a == permutedims(unnamed(na′), (2, 1)) - na′ = aligneddims(na, (j, i)) + na′ = aligned(na, (j, i)) @test unnamed(na′) isa PermutedDimsArray{elt} @test a == permutedims(unnamed(na′), (2, 1)) - # The map form of `aligndims` takes a codomain and a domain tuple. A dense backend + # The map form of `align` takes a codomain and a domain tuple. A dense backend # ignores the split and stores the reordered result flat, matching the flat form. - na′ = aligndims(na, (j,), (i,)) + na′ = align(na, (j,), (i,)) @test unnamed(na′) isa Matrix{elt} @test a == permutedims(unnamed(na′), (2, 1)) # Two-tuple `randn`/`zeros` take a codomain and a domain index tuple; a dense backend # ignores the split and stores flat, named by the codomain then the domain. - ci, cj = namedoneto(3, "i"), namedoneto(4, "j") + ci, cj = NamedOneTo(3, "i"), NamedOneTo(4, "j") nab = randn(elt, (ci,), (cj,)) @test unnamed(nab) isa Matrix{elt} - @test dimnames(nab) == [name(ci), name(cj)] + @test names(nab) == [name(ci), name(cj)] @test unnamed(zeros(elt, (ci,), (cj,))) == zeros(elt, 3, 4) # The rng-first forms (mirroring `Base.randn(rng, dims...)`, default eltype) accept flat # axes as varargs or a tuple, and the split codomain/domain. rng = default_rng() - @test dimnames(randn(rng, ci, cj)) == [name(ci), name(cj)] - @test dimnames(randn(rng, (ci, cj))) == [name(ci), name(cj)] - @test dimnames(randn(rng, (ci,), (cj,))) == [name(ci), name(cj)] + @test names(randn(rng, ci, cj)) == [name(ci), name(cj)] + @test names(randn(rng, (ci, cj))) == [name(ci), name(cj)] + @test names(randn(rng, (ci,), (cj,))) == [name(ci), name(cj)] # An empty codomain lands every index in the domain, the mirror of an empty domain. A # dense backend ignores the split, so these match the flat forms. - na′ = aligndims(na, (), (j, i)) + na′ = align(na, (), (j, i)) @test unnamed(na′) isa Matrix{elt} @test a == permutedims(unnamed(na′), (2, 1)) nbra = randn(elt, (), (cj,)) @test unnamed(nbra) isa Vector{elt} - @test dimnames(nbra) == [name(cj)] + @test names(nbra) == [name(cj)] @test unnamed(zeros(elt, (), (cj,))) == zeros(elt, 4) # An all-empty split has no map meaning, so it errors rather than recursing or # silently building a scalar. @test_throws MethodError randn(elt, (), ()) @test_throws MethodError zeros(elt, (), ()) - na = nameddims(randn(elt, 2, 3), (:i, :j)) - nb = nameddims(randn(elt, 3, 2), (:j, :i)) - nc = zeros(elt, named.((2, 3), (:i, :j))) + na = NamedTensor(randn(elt, 2, 3), (:i, :j)) + nb = NamedTensor(randn(elt, 3, 2), (:j, :i)) + nc = zeros(elt, Named.((2, 3), (:i, :j))) Is = eachindex(na, nb) @test Is isa NamedDimsCartesianIndices{2} - @test issetequal(Is.indices, (named(1:2, :i), named(1:3, :j))) + @test issetequal(Is.indices, (NamedUnitRange(1:2, :i), NamedUnitRange(1:3, :j))) for I in Is @test I isa NamedDimsCartesianIndex{2} @test issetequal(name.(Tuple(I)), (:i, :j)) @@ -321,25 +323,25 @@ end end @test unname(nc, (:i, :j)) ≈ unname(na, (:i, :j)) + unname(nb, (:i, :j)) - a = nameddims(randn(elt, 2, 3), (:i, :j)) - b = nameddims(randn(elt, 3, 2), (:j, :i)) + a = NamedTensor(randn(elt, 2, 3), (:i, :j)) + b = NamedTensor(randn(elt, 3, 2), (:j, :i)) c = a + b @test unname(c, (:i, :j)) ≈ unname(a, (:i, :j)) + unname(b, (:i, :j)) c = a .+ b @test unname(c, (:i, :j)) ≈ unname(a, (:i, :j)) + unname(b, (:i, :j)) c = map(+, a, b) @test unname(c, (:i, :j)) ≈ unname(a, (:i, :j)) + unname(b, (:i, :j)) - c = nameddims(Array{elt}(undef, 2, 3), (:i, :j)) + c = NamedTensor(Array{elt}(undef, 2, 3), (:i, :j)) c = map!(+, c, a, b) @test unname(c, (:i, :j)) ≈ unname(a, (:i, :j)) + unname(b, (:i, :j)) c = a .+ 2 .* b @test unname(c, (:i, :j)) ≈ unname(a, (:i, :j)) + 2 * unname(b, (:i, :j)) - c = nameddims(Array{elt}(undef, 2, 3), (:i, :j)) + c = NamedTensor(Array{elt}(undef, 2, 3), (:i, :j)) c .= a .+ 2 .* b @test unname(c, (:i, :j)) ≈ unname(a, (:i, :j)) + 2 * unname(b, (:i, :j)) # Regression test for proper permutations. - a = nameddims(randn(elt, 2, 3, 4), (:i, :j, :k)) + a = NamedTensor(randn(elt, 2, 3, 4), (:i, :j, :k)) I = (:i => 2, :j => 3, :k => 4) for I′ in Combinatorics.permutations(I) @test a[I′...] == a[2, 3, 4] @@ -356,7 +358,7 @@ end end end @testset "conj/fill! (eltype=$elt)" for elt in TestBasicsUtils.elts - i, j = namedoneto.((2, 3), ("i", "j")) + i, j = NamedOneTo.((2, 3), ("i", "j")) a = randn(elt, i, j) # `conj` forwards to the underlying so that, on graded backends, sector @@ -364,7 +366,7 @@ end # no-op and the test reduces to element-wise conjugation. ca = conj(a) @test unnamed(ca) == conj(unnamed(a)) - @test dimnames(ca) == dimnames(a) + @test names(ca) == names(a) # `fill!` forwards to the underlying storage. b = randn(elt, i, j) @@ -376,7 +378,7 @@ end # generic fallback iterates elements, which is expensive (or unsupported) # on block-structured backends. The named-array override delegates to # the underlying storage. - i, j = namedoneto.((2, 3), ("i", "j")) + i, j = NamedOneTo.((2, 3), ("i", "j")) a = randn(elt, i, j) @test LinearAlgebra.promote_leaf_eltypes(a) === LinearAlgebra.promote_leaf_eltypes(unnamed(a)) @@ -386,13 +388,13 @@ end # rather than left to the generic `mapreduce` fallback because some backends # (such as graded arrays) define `Base.sum` without the general `mapreduce`, # so summing the unnamed data is the path that works for them. - i, j = namedoneto.((2, 3), ("i", "j")) + i, j = NamedOneTo.((2, 3), ("i", "j")) a = randn(elt, i, j) @test sum(a) == sum(unnamed(a)) @test mapreduce(identity, +, a) == mapreduce(identity, +, unnamed(a)) end @testset "begin/end (eltype=$elt)" for elt in TestBasicsUtils.elts - i, j = namedoneto.((2, 3), ("i", "j")) + i, j = NamedOneTo.((2, 3), ("i", "j")) a = randn(elt, i, j) @test a[begin, begin] == a[1, 1] @test a[2, begin] == a[2, 1] @@ -416,7 +418,7 @@ end @test a[j[end], i[end]] == a[2, 3] end @testset "Shorthand constructors (eltype=$elt)" for elt in TestBasicsUtils.elts - i, j = named.((2, 2), ("i", "j")) + 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 @@ -445,7 +447,7 @@ end end end @testset "Shorthand constructors (eltype=unspecified)" begin - i, j = named.((2, 2), ("i", "j")) + i, j = Named.((2, 2), ("i", "j")) default_elt = Float64 for na in (zeros(i, j), zeros((i, j))) @test eltype(na) ≡ default_elt @@ -470,13 +472,13 @@ end @testset "show" begin a = NamedTensor([1 2; 3 4], ("i", "j")) @test sprint(show, "text/plain", a) == - "named(Base.OneTo(2), \"i\")×named(Base.OneTo(2), \"j\") " * + "NamedOneTo(2, \"i\")×NamedOneTo(2, \"j\") " * "$NamedTensor{String}:\n" * "2×2 Matrix{Int64}:\n 1 2\n 3 4" a = NamedTensor([1 2; 3 4], ("i", "j")) @test sprint(show, a) == - "[1 2; 3 4][named(Base.OneTo(2), \"i\"), named(Base.OneTo(2), \"j\")]" + "[1 2; 3 4][NamedOneTo(2, \"i\"), NamedOneTo(2, \"j\")]" end @testset "@names" begin diff --git a/test/test_namedinteger.jl b/test/test_namedinteger.jl index 0f6b8d0d..5c2ffa69 100644 --- a/test/test_namedinteger.jl +++ b/test/test_namedinteger.jl @@ -1,8 +1,8 @@ -using ITensorBase: Named, NamedInteger, name, named, namedoneto, unnamed +using ITensorBase: Named, NamedArray, NamedInteger, NamedOneTo, name, unnamed using Test: @test, @testset @testset "Named integer" begin - i = named(3, :i) + i = Named(3, :i) @test i isa Named @test i isa NamedInteger @test unnamed(i) ≡ 3 @@ -14,12 +14,12 @@ end # Base's array convention (`[1, 2, 3] == 1:3` and they hash equally). A named # array and a named unit range with equal unnamed values and names are equal, so # they must hash equally too. - na = named([1, 2, 3], "x") - nr = namedoneto(3, "x") + na = NamedArray([1, 2, 3], "x") + nr = NamedOneTo(3, "x") @test na == nr @test hash(na) == hash(nr) # Differing value or name stays distinct. - @test named([1, 2, 4], "x") != na - @test named([1, 2, 3], "y") != na - @test hash(named([1, 2, 3], "y")) != hash(na) + @test NamedArray([1, 2, 4], "x") != na + @test NamedArray([1, 2, 3], "y") != na + @test hash(NamedArray([1, 2, 3], "y")) != hash(na) end diff --git a/test/test_operator.jl b/test/test_operator.jl index f1720472..f117c281 100644 --- a/test/test_operator.jl +++ b/test/test_operator.jl @@ -1,8 +1,8 @@ using GradedArrays: U1, gradedrange, isdual -using ITensorBase: ITensorBase as NDA, Index, NamedTensor, NamedTensorOperator, apply, - dimnames, id, inds, inputaxes, inputinds, inputname, inputnames, nameddims, namedoneto, - operator, outputaxes, outputinds, outputname, outputnames, product, replacedimnames, - similar_operator, state, unname, unnamed +using ITensorBase: ITensorBase as NDA, Index, NamedOneTo, NamedTensor, NamedTensorOperator, + apply, id, inds, inputaxes, inputinds, inputname, inputnames, operator, outputaxes, + outputinds, outputname, outputnames, product, rename, similar_operator, state, unname, + unnamed using LinearAlgebra: I, norm using MatrixAlgebraKit: project_hermitian using Random: Random, randn @@ -35,18 +35,18 @@ using Test: @test, @test_throws, @testset o = operator(randn(2, 2, 2, 2), ("i'", "j'"), ("i", "j")) @test o isa NamedTensorOperator o² = product(o, o) - @test issetequal(dimnames(o²), ("i'", "j'", "i", "j")) - õ = replacedimnames( + @test issetequal(names(o²), ("i'", "j'", "i", "j")) + õ = rename( state(o), "i" => "i'", "j" => "j'", "i'" => "x", "j'" => "y" ) - o²′ = replacedimnames(õ * o, "x" => "i'", "y" => "j'") + o²′ = rename(õ * o, "x" => "i'", "y" => "j'") @test state(o²) ≈ o²′ o = operator(randn(2, 2, 2, 2), ("i'", "j'"), ("i", "j")) v = NamedTensor(randn(2, 2), ("i", "j")) ov = apply(o, v) - @test issetequal(dimnames(ov), ("i", "j")) - @test ov ≈ replacedimnames(o * v, "i'" => "i", "j'" => "j") + @test issetequal(names(ov), ("i", "j")) + @test ov ≈ rename(o * v, "i'" => "i", "j'" => "j") end @testset "wire lookups" begin @@ -118,7 +118,7 @@ end v = NamedTensor(randn(2, 2), ("j", "i")) Av = apply(A, v) @test Av isa NamedTensor - @test issetequal(dimnames(Av), ("j", "i")) + @test issetequal(names(Av), ("j", "i")) @test unname(Av, ("j", "i")) ≈ Am * unname(v, ("j", "i")) # Disjoint apply tensors, like `*`/product. @@ -162,12 +162,12 @@ end # Partial overlap: A on sites (1, 2), B on sites (2, 3), sharing site 2 → # a three-site operator composed on 2 and tensored on 1 and 3. A3 = operator( - nameddims(randn(2, 2, 2, 2), ("1'", "2'", "1", "2")), + NamedTensor(randn(2, 2, 2, 2), ("1'", "2'", "1", "2")), ("1'", "2'"), ("1", "2") ) B3 = operator( - nameddims(randn(2, 2, 2, 2), ("2'", "3'", "2", "3")), + NamedTensor(randn(2, 2, 2, 2), ("2'", "3'", "2", "3")), ("2'", "3'"), ("2", "3") ) @@ -176,8 +176,8 @@ end @test issetequal(inputnames(AB3), ("1", "2", "3")) # Value: weld A's input 2 to B's output 2' and contract. manual = - replacedimnames(state(A3), "2" => "bond") * - replacedimnames(state(B3), "2'" => "bond") + rename(state(A3), "2" => "bond") * + rename(state(B3), "2'" => "bond") order = ("1'", "2'", "3'", "1", "2", "3") @test unname(state(AB3), order) ≈ unname(manual, order) @@ -186,7 +186,7 @@ end Aop = operator(randn(2, 2), ("i'",), ("i",)) v = NamedTensor(randn(2, 3), ("i", "j")) Av = product(Aop, v) - @test issetequal(dimnames(Av), ("i'", "j")) + @test issetequal(names(Av), ("i'", "j")) @test isempty(outputnames(Av)) @test isempty(inputnames(Av)) @test unname(state(Av), ("i'", "j")) ≈ @@ -194,11 +194,11 @@ end # Kraus: composing two operators that share both the i-wire and a dangling Kraus # index `j` welds the wire and sums over `j`. - K = operator(nameddims(randn(2, 2, 3), ("i'", "i", "j")), ("i'",), ("i",)) + K = operator(NamedTensor(randn(2, 2, 3), ("i'", "i", "j")), ("i'",), ("i",)) KK = product(K, K) @test issetequal(outputnames(KK), ("i'",)) @test issetequal(inputnames(KK), ("i",)) - @test issetequal(dimnames(KK), ("i'", "i")) # j contracted away + @test issetequal(names(KK), ("i'", "i")) # j contracted away Km = unname(state(K), ("i'", "i", "j")) manual_kraus = sum(Km[:, :, jj] * Km[:, :, jj] for jj in axes(Km, 3)) @test unname(state(KK), ("i'", "i")) ≈ manual_kraus @@ -225,7 +225,7 @@ end @testset "operator from named ranges" begin # Output/input may be given as named ranges, not just names. - i, ip = namedoneto(2, "i"), namedoneto(2, "i'") + i, ip = NamedOneTo(2, "i"), NamedOneTo(2, "i'") o = operator(randn(2, 2), [ip], [i]) @test o isa NamedTensorOperator{String} @test issetequal(outputnames(o), ("i'",)) @@ -234,41 +234,41 @@ end @testset "one(::NamedTensorOperator)" begin # Identity-operator construction: matricized form is the identity matrix. - i, j, k, l = namedoneto.((2, 3, 2, 3), ("i", "j", "k", "l")) + i, j, k, l = NamedOneTo.((2, 3, 2, 3), ("i", "j", "k", "l")) op = operator(randn(i, j, k, l), ("i", "j"), ("k", "l")) Id = one(op) @test Id isa NamedTensorOperator{String} @test outputnames(Id) == outputnames(op) @test inputnames(Id) == inputnames(op) - Id_mat = matricize(state(Id), (i, j) => "row", (k, l) => "col") - @test unname(Id_mat, ("row", "col")) ≈ I(6) + Id_mat = matricize(state(Id), (i, j), (k, l)) + @test Id_mat ≈ I(6) end @testset "one(::AbstractNamedTensor, codomain, domain)" begin # Trivial codomain/domain layout. - i, j, k, l = namedoneto.((2, 3, 2, 3), ("i", "j", "k", "l")) + i, j, k, l = NamedOneTo.((2, 3, 2, 3), ("i", "j", "k", "l")) a = randn(i, j, k, l) Id = one(a, (i, j), (k, l)) - Id_mat = matricize(Id, (i, j) => "row", (k, l) => "col") - @test unname(Id_mat, ("row", "col")) ≈ I(6) + Id_mat = matricize(Id, (i, j), (k, l)) + @test Id_mat ≈ I(6) # Non-trivial axis ordering: codomain/domain are interleaved in `a`. - p, q, r, s = namedoneto.((2, 4, 2, 4), ("p", "q", "r", "s")) + p, q, r, s = NamedOneTo.((2, 4, 2, 4), ("p", "q", "r", "s")) a = randn(p, r, q, s) # storage order interleaves codomain (p, q) and domain (r, s) Id = one(a, (p, q), (r, s)) - @test issetequal(dimnames(Id), ("p", "r", "q", "s")) - Id_mat = matricize(Id, (p, q) => "row", (r, s) => "col") - @test unname(Id_mat, ("row", "col")) ≈ I(8) + @test issetequal(names(Id), ("p", "r", "q", "s")) + Id_mat = matricize(Id, (p, q), (r, s)) + @test Id_mat ≈ I(8) end @testset "id(elt, codomain, domain)" begin # From-scratch identity map (no prototype): matricized form is the identity matrix. - i, j, k, l = namedoneto.((2, 3, 2, 3), ("i", "j", "k", "l")) + i, j, k, l = NamedOneTo.((2, 3, 2, 3), ("i", "j", "k", "l")) Id = id(Float64, (i, j), (k, l)) @test eltype(Id) === Float64 - @test issetequal(dimnames(Id), ("i", "j", "k", "l")) - Id_mat = matricize(Id, (i, j) => "row", (k, l) => "col") - @test unname(Id_mat, ("row", "col")) ≈ I(6) + @test issetequal(names(Id), ("i", "j", "k", "l")) + Id_mat = matricize(Id, (i, j), (k, l)) + @test Id_mat ≈ I(6) # The requested element type is honored. @test eltype(id(ComplexF64, (i, j), (k, l))) === ComplexF64 @@ -288,7 +288,7 @@ end @test only(outputnames(op)) != "i" # Named-axes form reuses each axis's name as the input. - i = namedoneto(3, "i") + i = NamedOneTo(3, "i") op = similar_operator(randn(3, 3), Float64, (i,)) @test issetequal(inputnames(op), ("i",)) @test only(outputnames(op)) != "i" @@ -390,13 +390,13 @@ end # A shared *dangling* leg (in neither pairing) is summed away, and the # surviving output/input of each operand combine. This is the `c† * c` # hopping pattern: two operators paired over an auxiliary link. - a = operator(nameddims(randn(2, 2, 3), ("i'", "i", "aux")), ["i'"], ["i"]) - b = operator(nameddims(randn(2, 2, 3), ("j'", "j", "aux")), ["j'"], ["j"]) + a = operator(NamedTensor(randn(2, 2, 3), ("i'", "i", "aux")), ["i'"], ["i"]) + b = operator(NamedTensor(randn(2, 2, 3), ("j'", "j", "aux")), ["j'"], ["j"]) ab = a * b @test ab isa NamedTensorOperator @test issetequal(outputnames(ab), ("i'", "j'")) @test issetequal(inputnames(ab), ("i", "j")) - @test !("aux" in dimnames(ab)) + @test !("aux" in names(ab)) @test state(ab) ≈ state(a) * state(b) # A shared *paired* index (a's input equals b's output) chains through the @@ -411,12 +411,12 @@ end # Applying an operator to a plain state contracts the operator's input and # leaves its output dangling. The result stays an `NamedTensorOperator` with empty # output/input (the surviving `a'` leg is dangling, in neither). - v = nameddims(randn(2), ("m",)) + v = NamedTensor(randn(2), ("m",)) Av = operator(randn(2, 2), ("a'",), ("m",)) * v @test Av isa NamedTensorOperator @test isempty(outputnames(Av)) @test isempty(inputnames(Av)) - @test issetequal(dimnames(Av), ("a'",)) + @test issetequal(names(Av), ("a'",)) end @testset "Hermitian square roots on NamedTensorOperator" begin diff --git a/test/test_tensoralgebra.jl b/test/test_tensoralgebra.jl index 54b4ddfd..5a2620d8 100644 --- a/test/test_tensoralgebra.jl +++ b/test/test_tensoralgebra.jl @@ -1,5 +1,5 @@ -using ITensorBase: ITensorBase, Index, dimnames, id, inds, name, namedoneto, operator, - prime, replacedimnames, unname, unnamed +using ITensorBase: + ITensorBase, Index, NamedOneTo, id, inds, name, operator, prime, rename, unname, unnamed using LinearAlgebra: norm, tr using MatrixAlgebraKit: left_null, left_orth, left_polar, lq_compact, lq_full, qr_compact, qr_full, right_null, right_orth, right_polar, svd_compact, svd_trunc, svd_vals @@ -16,9 +16,9 @@ using Test: @test, @test_broken, @testset Complex{Float64}, ) @testset "contract" begin - i = namedoneto(2, "i") - j = namedoneto(2, "j") - k = namedoneto(2, "k") + i = NamedOneTo(2, "i") + j = NamedOneTo(2, "j") + k = NamedOneTo(2, "k") na1 = randn(elt, i, j) na2 = randn(elt, j, k) na_dest = na1 * na2 @@ -26,43 +26,38 @@ using Test: @test, @test_broken, @testset @test unname(na_dest, (i, k)) ≈ unnamed(na1) * unnamed(na2) end @testset "matricize" begin - i, j, k, l = namedoneto.((2, 3, 4, 5), ("i", "j", "k", "l")) + i, j, k, l = NamedOneTo.((2, 3, 4, 5), ("i", "j", "k", "l")) na = randn(elt, i, j, k, l) - na_fused = matricize(na, (k, i) => "a", (j, l) => "b") - # Fuse all dimensions. - @test unname(na_fused, ("a", "b")) ≈ reshape( + # The two dimension groups fuse into the rows and the columns of an unnamed matrix. + m = matricize(na, (k, i), (j, l)) + @test m isa AbstractMatrix{elt} + @test m ≈ reshape( unname(na, (k, i, j, l)), ( length(k) * length(i), length(j) * length(l), ) ) - # Positional form auto-generates the two fused dimension names, matching the - # pair form's data. - na_pos = matricize(na, (k, i), (j, l)) - @test ndims(na_pos) == 2 - @test Array(na_pos) == Array(na_fused) # Groups may be any iterable of dimensions, not only tuples (no `Tuple` wrapping - # needed), in both the pair and positional forms. - @test Array(matricize(na, [k, i] => "a", [j, l] => "b")) == Array(na_fused) - @test Array(matricize(na, [k, i], [j, l])) == Array(na_pos) + # needed). + @test matricize(na, [k, i], [j, l]) == m end @testset "unmatricize" begin - a, b = namedoneto.((8, 15), ("a", "b")) - i, j, k, l = namedoneto.((2, 3, 4, 5), ("i", "j", "k", "l")) - na = randn(elt, a, b) - # Split all dimensions. - na_split = unmatricize(na, "a" => (k, i), "b" => (j, l)) + i, j, k, l = NamedOneTo.((2, 3, 4, 5), ("i", "j", "k", "l")) + m = randn(elt, length(k) * length(i), length(j) * length(l)) + # An unnamed matrix splits back into a named tensor over the given codomain and + # domain indices. + na_split = unmatricize(m, (k, i), (j, l)) @test unname(na_split, ("k", "i", "j", "l")) ≈ - reshape( - unname(na, ("a", "b")), - (unnamed(k), unnamed(i), unnamed(j), unnamed(l)) - ) + reshape(m, (unnamed(k), unnamed(i), unnamed(j), unnamed(l))) + # Round trip through the matrix. + na = randn(elt, i, j, k, l) + @test unmatricize(matricize(na, (k, i), (j, l)), (k, i), (j, l)) ≈ na end @testset "directsum" begin - i = namedoneto(2, "i") # shared index, carried through unchanged - j1, j2 = namedoneto.((2, 3), ("j1", "j2")) - k1, k2 = namedoneto.((2, 3), ("k1", "k2")) + i = NamedOneTo(2, "i") # shared index, carried through unchanged + j1, j2 = NamedOneTo.((2, 3), ("j1", "j2")) + k1, k2 = NamedOneTo.((2, 3), ("k1", "k2")) a = randn(elt, i, j1, k1) b = randn(elt, i, j2, k2) ref = cat(unname(a, (i, j1, k1)), unname(b, (i, j2, k2)); dims = (2, 3)) @@ -75,12 +70,12 @@ using Test: @test, @test_broken, @testset @test sort(length.(summed)) == [5, 5] @test unname(s, (i, summed...)) == ref # Explicit output indices name the summed dimensions. - o1, o2 = namedoneto.((5, 5), ("o1", "o2")) + o1, o2 = NamedOneTo.((5, 5), ("o1", "o2")) s2 = directsum((o1, o2), a => (j1, k1), b => (j2, k2)) @test issetequal(inds(s2), (i, o1, o2)) @test unname(s2, (i, o1, o2)) == ref # A single summed dimension. - u1, u2 = namedoneto.((2, 3), ("u1", "u2")) + u1, u2 = NamedOneTo.((2, 3), ("u1", "u2")) c = randn(elt, i, u1) d = randn(elt, i, u2) sc, (su,) = directsum(c => (u1,), d => (u2,)) @@ -92,19 +87,19 @@ using Test: @test, @test_broken, @testset f == :cbrt && elt <: Complex && continue f == :cbrt && VERSION < v"1.11-" && continue @eval begin - i, j, k, l = namedoneto.((2, 2, 2, 2), ("i", "j", "k", "l")) + i, j, k, l = NamedOneTo.((2, 2, 2, 2), ("i", "j", "k", "l")) rng = StableRNG(123) a = randn(rng, $elt, (i, j, k, l)) fa = $f(a, (j, l), (k, i)) - m = unname(matricize(a, (j, l) => "a", (k, i) => "b"), ("a", "b")) - fm = unname(matricize(fa, (j, l) => "a", (k, i) => "b"), ("a", "b")) + m = matricize(a, (j, l), (k, i)) + fm = matricize(fa, (j, l), (k, i)) @test fm ≈ $f(m) end end end @testset "qr/lq" begin dims = (2, 2, 2, 2) - i, j, k, l = namedoneto.(dims, ("i", "j", "k", "l")) + i, j, k, l = NamedOneTo.(dims, ("i", "j", "k", "l")) a = randn(elt, i, j) # TODO: Should this be allowed? @@ -129,7 +124,7 @@ using Test: @test, @test_broken, @testset end @testset "svd" begin dims = (2, 2, 2, 2) - i, j, k, l = namedoneto.(dims, ("i", "j", "k", "l")) + i, j, k, l = NamedOneTo.(dims, ("i", "j", "k", "l")) a = randn(elt, i, j) # TODO: Should this be allowed? @@ -158,7 +153,7 @@ using Test: @test, @test_broken, @testset end @testset "left_null/right_null" begin dims = (2, 2, 2, 2) - i, j, k, l = namedoneto.(dims, ("i", "j", "k", "l")) + i, j, k, l = NamedOneTo.(dims, ("i", "j", "k", "l")) a = randn(elt, i, j, k, l) # TODO: Add support for specifying new name. @@ -190,33 +185,33 @@ using Test: @test, @test_broken, @testset # the three-argument form builds an operator from the codomain/domain split top = project(Sz, (prime(i),), (i,)) @test eltype(top) === elt - @test Set(dimnames(top)) == Set(name.((prime(i), i))) + @test Set(names(top)) == Set(name.((prime(i), i))) @test unname(top, (prime(i), i)) == Sz # `unchecked_project` skips the (for dense, always exact) verification @test unname(unchecked_project(Sz, (prime(i),), (i,)), (prime(i), i)) == Sz # the two-argument form builds a state (empty domain) v = elt[1, 0] s = project(v, (i,)) - @test dimnames(s) == [name(i)] + @test names(s) == [name(i)] @test unname(s, (i,)) == v # the empty-codomain form builds an all-domain tensor (mirror of the state) bra = project(v, (), (i,)) - @test dimnames(bra) == [name(i)] + @test names(bra) == [name(i)] @test unname(bra, (i,)) == v end - @testset "replacedimnames with index keys" begin - i, j, k = namedoneto.((2, 3, 2), ("i", "j", "k")) + @testset "rename with index keys" begin + i, j, k = NamedOneTo.((2, 3, 2), ("i", "j", "k")) a = randn(elt, i, j) # An `Index`-keyed pair relabels like the name-keyed pair rather than silently # no-opping, and the result stays an `ITensor` (not `NamedTensor{Any}`). - @test dimnames(replacedimnames(a, i => k)) == - dimnames(replacedimnames(a, "i" => "k")) - @test replacedimnames(a, i => k) isa typeof(a) + @test names(rename(a, i => k)) == + names(rename(a, "i" => "k")) + @test rename(a, i => k) isa typeof(a) # Mixed index/name keys and values are accepted. - @test dimnames(replacedimnames(a, i => "k")) == - dimnames(replacedimnames(a, "i" => "k")) - @test dimnames(replacedimnames(a, "i" => k)) == - dimnames(replacedimnames(a, "i" => "k")) + @test names(rename(a, i => "k")) == + names(rename(a, "i" => "k")) + @test names(rename(a, "i" => k)) == + names(rename(a, "i" => "k")) end @testset "trivialrange on named ranges" begin i = Index(3) diff --git a/test/test_tensorkitext.jl b/test/test_tensorkitext.jl index c4394542..e3511673 100644 --- a/test/test_tensorkitext.jl +++ b/test/test_tensorkitext.jl @@ -1,8 +1,8 @@ -using ITensorBase: ITensorBase, Index, aligndims, dimnames, name, prime, unnamed +using ITensorBase: ITensorBase, ITensor, Index, align, name, prime, unnamed using LinearAlgebra: norm using MatrixAlgebraKit: qr_compact, svd_compact using StableRNGs: StableRNG -using TensorAlgebra: TensorAlgebra, project, unchecked_project +using TensorAlgebra: TensorAlgebra, matricize, project, unchecked_project, unmatricize using TensorKit: TensorKit as TK, @tensor, AbstractTensorMap, SU2Irrep, U1Irrep, Vect, ←, ⊗ using Test: @test, @test_throws, @testset @@ -60,7 +60,7 @@ using Test: @test, @test_throws, @testset # Contraction over the shared (dualized) leg matches a direct TensorKit reference. b = randn(rng, elt, conj(j), k) c = a * b - @test Set(dimnames(c)) == Set(name.((i, k))) + @test Set(names(c)) == Set(name.((i, k))) ta, tb, gc = unnamed(a), unnamed(b), unnamed(c) @tensor ref[vi; vk] := ta[vi, vj] * tb[vj, vk] @test TK.space(ref) == TK.space(gc) @@ -75,11 +75,11 @@ using Test: @test, @test_throws, @testset @test_throws ErrorException sin.(a) # Named broadcasting aligns operands by name within their codomain/domain split, so a within-split - # reorder of a multi-leg operand still adds correctly (compared at a common split via `aligndims`). + # reorder of a multi-leg operand still adds correctly (compared at a common split via `align`). mr1 = randn(rng, elt, (i, j), (k,)) mr2 = randn(rng, elt, (j, i), (k,)) - @test unnamed(aligndims(mr1 .+ mr2, (i, j), (k,))) ≈ - unnamed(mr1) + unnamed(aligndims(mr2, (i, j), (k,))) + @test unnamed(align(mr1 .+ mr2, (i, j), (k,))) ≈ + unnamed(mr1) + unnamed(align(mr2, (i, j), (k,))) # Adding across an incompatible split (a shared leg in the codomain of one operand and the domain # of the other) has mismatched axes and errors. cs1 = randn(rng, elt, (i,), (j,)) @@ -101,7 +101,8 @@ using Test: @test, @test_throws, @testset @test Array(a) == convert(Array, unnamed(a)) # `trivialrange` mints a fresh trivial axis over the native space (used e.g. by the - # boundary-MPS setup), routing through the `namedunitrange(::ElementarySpace, name)` overload. + # boundary-MPS setup), routing through the `NamedUnitRange(::ElementarySpace, name)` + # constructor. t1 = TensorAlgebra.trivialrange(i) @test t1 isa Index @test length(t1) == 1 @@ -130,25 +131,37 @@ using Test: @test, @test_throws, @testset cd = randn(rng, elt, (), (j,)) @test unnamed(cd) isa AbstractTensorMap @test TK.space(unnamed(cd)) == (one(Vj) ← Vj) - @test dimnames(cd) == [name(j)] + @test names(cd) == [name(j)] @test TK.space(unnamed(zeros(elt, (), (j,)))) == (one(Vj) ← Vj) @test_throws MethodError randn(rng, elt, (), ()) - # `aligndims` reorders a `TensorMap`-backed tensor. The flat form gives an all-codomain + # `matricize` fuses the codomain/domain split into the unnamed matrix (here a + # two-leg `TensorMap`), and `unmatricize` splits one back out over named indices, + # taking the domain index codomain-facing while storing it dualized. + ma = randn(rng, elt, (i, j), (k,)) + mm = matricize(ma, (i, j), (k,)) + @test mm isa AbstractTensorMap + @test TK.space(mm) == ((Vi ⊗ Vj) ← Vk) + rt = unmatricize(mm, (i, j), (k,)) + @test names(rt) == names(ma) + @test TK.space(unnamed(rt)) == TK.space(unnamed(ma)) + @test unnamed(rt) ≈ unnamed(ma) + + # `align` reorders a `TensorMap`-backed tensor. The flat form gives an all-codomain # result and the map form re-expresses the requested codomain/domain split, both # carrying each index with its arrow to the new position. - mf = aligndims(m, (j, i)) - @test dimnames(mf) == [name(j), name(i)] + mf = align(m, (j, i)) + @test names(mf) == [name(j), name(i)] @test TK.space(unnamed(mf), 1) == TK.dual(Vj) @test TK.space(unnamed(mf), 2) == Vi - md = aligndims(m, (j,), (i,)) - @test dimnames(md) == [name(j), name(i)] + md = align(m, (j,), (i,)) + @test names(md) == [name(j), name(i)] @test TK.space(unnamed(md)) == (TK.dual(Vj) ← TK.dual(Vi)) @test TK.space(unnamed(md), 1) == TK.dual(Vj) @test TK.space(unnamed(md), 2) == Vi # An empty codomain moves both indices into the domain, preserving the outward axes. - me = aligndims(m, (), (i, j)) - @test dimnames(me) == [name(i), name(j)] + me = align(m, (), (i, j)) + @test names(me) == [name(i), name(j)] @test TK.space(unnamed(me)) == (one(Vi) ← (TK.dual(Vi) ⊗ Vj)) @test TK.space(unnamed(me), 1) == Vi @test TK.space(unnamed(me), 2) == TK.dual(Vj) @@ -166,7 +179,7 @@ using Test: @test, @test_throws, @testset top = project(Sz, (prime(w),), (w,)) @test unnamed(top) isa AbstractTensorMap @test TK.space(unnamed(top)) == (W ← W) - @test Set(dimnames(top)) == Set(name.((prime(w), w))) + @test Set(names(top)) == Set(name.((prime(w), w))) # a charge-breaking operator is projected to zero by `unchecked_project`; the checked # `project` rejects the discard @@ -184,6 +197,20 @@ using Test: @test, @test_throws, @testset cobra = project(elt[1, 0], (), (w,)) @test unnamed(cobra) isa AbstractTensorMap @test TK.space(unnamed(cobra)) == (one(W) ← W) - @test Set(dimnames(cobra)) == Set((name(w),)) + @test Set(names(cobra)) == Set((name(w),)) end end + +# A dimension given as an index asserts its whole space. Over a `TensorMap` backend the axes are +# TensorKit spaces, so the check compares spaces and catches a dual mismatch that has the same +# total dimension. +@testset "TensorKitExt constructor space check" begin + rng = StableRNG(1234) + Vi = Vect[U1Irrep](0 => 2, 1 => 3) + Vj = Vect[U1Irrep](0 => 1, 1 => 2) + i, j = Index(Vi), Index(Vj) + t = TK.randn(rng, Float64, Vi ⊗ Vj) + @test ITensor(t, (i, j)) isa ITensor + @test TK.dim(TK.dual(Vj)) == TK.dim(Vj) + @test_throws ArgumentError ITensor(t, (i, Index(TK.dual(Vj)))) +end diff --git a/test/test_vectorinterface.jl b/test/test_vectorinterface.jl index 8242f244..4e40f54f 100644 --- a/test/test_vectorinterface.jl +++ b/test/test_vectorinterface.jl @@ -1,5 +1,5 @@ import VectorInterface as VI -using ITensorBase: dimnames, named, unnamed +using ITensorBase: Named, unnamed using Test: @test, @testset # These name-aware methods are what let an NamedTensor be used directly as a vector in @@ -7,11 +7,11 @@ using Test: @test, @testset # through `VectorInterface`. @testset "VectorInterface (eltype=$(elt))" for elt in (Float32, Float64, Complex{Float32}) - i, j = named.(2, (:i, :j)) + i, j = Named.(2, (:i, :j)) a = randn(elt, i, j) b = randn(elt, j, i) ua = unnamed(a) - ub = unnamed(b, dimnames(a)) + ub = unnamed(b, names(a)) @test VI.scalartype(a) === elt @test VI.scalartype([a, b]) === elt @@ -21,7 +21,7 @@ using Test: @test, @testset z = VI.zerovector(a, ComplexF64) @test VI.scalartype(z) === ComplexF64 @test iszero(unnamed(z)) - @test dimnames(z) == dimnames(a) + @test names(z) == names(a) z = VI.zerovector!(copy(a)) @test VI.scalartype(z) === elt @test iszero(unnamed(z)) @@ -42,13 +42,13 @@ using Test: @test, @testset @test unnamed(s) ≈ 2im * ua # add / add! / add!! - @test unnamed(VI.add(b, a), dimnames(a)) ≈ ub + ua - @test unnamed(VI.add(b, a, 2, 3), dimnames(a)) ≈ 3 * ub + 2 * ua - @test unnamed(VI.add!(copy(b), a, 2, 3), dimnames(a)) ≈ 3 * ub + 2 * ua - @test unnamed(VI.add!!(copy(b), a, 2, 3), dimnames(a)) ≈ 3 * ub + 2 * ua + @test unnamed(VI.add(b, a), names(a)) ≈ ub + ua + @test unnamed(VI.add(b, a, 2, 3), names(a)) ≈ 3 * ub + 2 * ua + @test unnamed(VI.add!(copy(b), a, 2, 3), names(a)) ≈ 3 * ub + 2 * ua + @test unnamed(VI.add!!(copy(b), a, 2, 3), names(a)) ≈ 3 * ub + 2 * ua r = VI.add!!(copy(b), a, 2im, 3) @test VI.scalartype(r) === complex(elt) - @test unnamed(r, dimnames(a)) ≈ 3 * ub + 2im * ua + @test unnamed(r, names(a)) ≈ 3 * ub + 2im * ua @test VI.inner(a, b) ≈ VI.inner(ua, ub) end From 78ca861fadb9fae38ce29aec64ee8e3bb643d58a Mon Sep 17 00:00:00 2001 From: Matthew Fishman Date: Sat, 26 Sep 2026 13:43:59 -0400 Subject: [PATCH 2/7] Make ITensorBase.names the overload point for dimension names A new tensor type overloads `names`, and `Base.names` forwards to it, so both spellings work while there is one place to implement. Co-Authored-By: Claude Opus 5 (1M context) --- docs/src/dev_interface.md | 4 ++-- ext/ITensorBaseAdaptExt/ITensorBaseAdaptExt.jl | 2 +- .../ITensorBaseMooncakeExt.jl | 4 ++-- src/ITensorBase.jl | 2 +- src/abstractnamedtensor.jl | 18 ++++++++++++------ src/lazyitensors/lazyitensor.jl | 2 +- src/lazyitensors/symbolicitensor.jl | 2 +- src/namedtensor.jl | 2 +- src/namedtensoroperator.jl | 2 +- test/test_basics.jl | 2 +- test/test_exports.jl | 2 +- test/test_gradedarraysext.jl | 2 +- test/test_lazyitensors.jl | 4 ++-- test/test_linearalgebra.jl | 2 +- test/test_mooncakeext.jl | 4 ++-- test/test_nameddims_basics.jl | 5 ++++- test/test_operator.jl | 6 +++--- test/test_tensoralgebra.jl | 4 ++-- test/test_tensorkitext.jl | 2 +- test/test_vectorinterface.jl | 2 +- 20 files changed, 41 insertions(+), 32 deletions(-) diff --git a/docs/src/dev_interface.md b/docs/src/dev_interface.md index 47c30397..c47ef3cc 100644 --- a/docs/src/dev_interface.md +++ b/docs/src/dev_interface.md @@ -31,7 +31,7 @@ NamedUnitRange Construct named objects with the [`NamedTensor`](@ref) and [`NamedUnitRange`](@ref) constructors, recover their parts with [`name`](@ref), [`unnamed`](@ref), and -[`Base.names`](@ref), and query their types with [`nametype`](@ref) and +[`names`](@ref), and query their types with [`nametype`](@ref) and [`unnamedtype`](@ref). [`setname`](@ref) and [`rename`](@ref) change names, and [`align`](@ref) and [`aligned`](@ref) reorder a tensor's dimensions by name (a copy and a view, respectively). @@ -39,7 +39,7 @@ tensor's dimensions by name (a copy and a view, respectively). ```@docs; canonical=false name unnamed -Base.names +names nametype unnamedtype setname diff --git a/ext/ITensorBaseAdaptExt/ITensorBaseAdaptExt.jl b/ext/ITensorBaseAdaptExt/ITensorBaseAdaptExt.jl index ae8e014a..d66ee08a 100644 --- a/ext/ITensorBaseAdaptExt/ITensorBaseAdaptExt.jl +++ b/ext/ITensorBaseAdaptExt/ITensorBaseAdaptExt.jl @@ -1,7 +1,7 @@ module ITensorBaseAdaptExt using Adapt: Adapt, adapt -using ITensorBase: AbstractNamedTensor, NamedTensor, unnamed +using ITensorBase: AbstractNamedTensor, NamedTensor, names, unnamed function Adapt.adapt_structure(to, a::AbstractNamedTensor) return NamedTensor(adapt(to, unnamed(a)), names(a)) diff --git a/ext/ITensorBaseMooncakeExt/ITensorBaseMooncakeExt.jl b/ext/ITensorBaseMooncakeExt/ITensorBaseMooncakeExt.jl index cdaed173..34d7cea0 100644 --- a/ext/ITensorBaseMooncakeExt/ITensorBaseMooncakeExt.jl +++ b/ext/ITensorBaseMooncakeExt/ITensorBaseMooncakeExt.jl @@ -1,7 +1,7 @@ module ITensorBaseMooncakeExt -using ITensorBase: AbstractNamedTensor, NamedUnitRange, inds, name, nameperm, names_setdiff, - to_inds, uniquename +using ITensorBase: AbstractNamedTensor, NamedUnitRange, inds, name, nameperm, names, + names_setdiff, to_inds, uniquename using Mooncake: Mooncake, @zero_derivative, DefaultCtx Mooncake.tangent_type(::Type{<:NamedUnitRange}) = Mooncake.NoTangent diff --git a/src/ITensorBase.jl b/src/ITensorBase.jl index a2fb2d52..b3cc80e2 100644 --- a/src/ITensorBase.jl +++ b/src/ITensorBase.jl @@ -11,7 +11,7 @@ export AbstractNamedTensor, NamedTensor, AbstractITensor, ITensor, Index, if VERSION >= v"1.11.0-DEV.469" eval( Meta.parse( - "public @names, IndexName, name, rename, setname, space, unnamed, unnamedtype, decoration, emptytags, gettag, gettags, hastag, plev, settags, tags, unsettags" + "public @names, IndexName, name, names, rename, setname, space, unnamed, unnamedtype, decoration, emptytags, gettag, gettags, hastag, plev, settags, tags, unsettags" ) ) end diff --git a/src/abstractnamedtensor.jl b/src/abstractnamedtensor.jl index 897c556c..10f5d180 100644 --- a/src/abstractnamedtensor.jl +++ b/src/abstractnamedtensor.jl @@ -17,7 +17,7 @@ by name under contraction, addition, and indexing. Unlike an `AbstractArray`, th and element type live in the data rather than the type, so `ndims` and `eltype` are not fixed at the type level. -See also [`NamedTensor`](@ref), [`Base.names`](@ref), [`inds`](@ref). +See also [`NamedTensor`](@ref), [`names`](@ref), [`inds`](@ref). """ abstract type AbstractNamedTensor{DimName} end @@ -32,7 +32,8 @@ Base.ndims(::Type{<:AbstractNamedTensor}) = Any names(a::AbstractNamedTensor, dim::Int) The dimension names of `a`, as a collection in dimension order. The second form returns -the name of dimension `dim`. +the name of dimension `dim`. `Base.names` is an equivalent spelling that forwards here, +so either can be called; a new tensor type overloads `ITensorBase.names`. # Examples @@ -50,11 +51,16 @@ julia> names(a, 2) See also [`inds`](@ref), [`NamedTensor`](@ref). """ -Base.names(a::AbstractNamedTensor) = throw(MethodError(names, a)) -function Base.names(a::AbstractNamedTensor, dim::Int) +function names end + +names(a::AbstractNamedTensor) = throw(MethodError(names, a)) +function names(a::AbstractNamedTensor, dim::Int) return names(a)[dim] end +Base.names(a::AbstractNamedTensor) = names(a) +Base.names(a::AbstractNamedTensor, dim::Int) = names(a, dim) + # `nametype` (documented with the named-array methods in `named.jl`) reports the type of an # individual dimension name. `AbstractNamedTensor` is a separate hierarchy from # `AbstractNamedArray`, so it needs its own methods. @@ -86,7 +92,7 @@ unname(a::AbstractNamedTensor, inds) = unnamed(align(a, inds)) The named axes (indices) of `a`, as a `Vector` with one entry per dimension. Each entry pairs a dimension's axis with its name. The second form returns the index of dimension -`dim`. Compare with [`Base.names`](@ref), which returns just the names without the axes. The +`dim`. Compare with [`names`](@ref), which returns just the names without the axes. The `axes` function returns the same indices as a `Tuple`, which the `AbstractArray` interface relies on; `inds` returns a `Vector` because the indices are most often manipulated as a collection (`filter`, `setdiff`, `union`). @@ -449,7 +455,7 @@ julia> names(rename(a, :i => :k)) :j ``` -See also [`Base.names`](@ref). +See also [`names`](@ref). """ function rename end # `name` strips an `Index`/`NamedUnitRange` to its dimension name and passes a bare name diff --git a/src/lazyitensors/lazyitensor.jl b/src/lazyitensors/lazyitensor.jl index bab7aa7e..6425ab94 100644 --- a/src/lazyitensors/lazyitensor.jl +++ b/src/lazyitensors/lazyitensor.jl @@ -50,7 +50,7 @@ function Base.convert( return lazy(Mul(map(arg -> convert(LazyNamedTensor{D, A2}, arg), arguments(a)))) end -Base.names(a::LazyNamedTensor) = names_lazy(a) +names(a::LazyNamedTensor) = names_lazy(a) inds(a::LazyNamedTensor) = inds_lazy(a) # `axes` is computed from `inds_lazy` rather than the generic `unnamed`-based fallback # because a `Mul` expression has no materialized `unnamed` array to take axes of. diff --git a/src/lazyitensors/symbolicitensor.jl b/src/lazyitensors/symbolicitensor.jl index fd04353a..279e16be 100644 --- a/src/lazyitensors/symbolicitensor.jl +++ b/src/lazyitensors/symbolicitensor.jl @@ -19,7 +19,7 @@ end symname(a::SymbolicNamedTensor) = getfield(a, :name) -Base.names(a::SymbolicNamedTensor) = getfield(a, :names) +names(a::SymbolicNamedTensor) = getfield(a, :names) function Base.axes(a::SymbolicNamedTensor) return NamedUnitRange.( Tuple(Base.OneTo.(getfield(a, :size))), diff --git a/src/namedtensor.jl b/src/namedtensor.jl index 0450bbf7..f1a23a3a 100644 --- a/src/namedtensor.jl +++ b/src/namedtensor.jl @@ -168,7 +168,7 @@ NamedTensor(a::AbstractNamedTensor, inds) = throw(ArgumentError("Already named." NamedTensor(a::AbstractNamedTensor) = NamedTensor(unnamed(a), names(a)) # Minimal interface. The names are stored as (and returned as) a `Vector`. -Base.names(a::NamedTensor) = a.names +names(a::NamedTensor) = a.names unnamed(a::NamedTensor) = a.unnamed Base.parent(a::NamedTensor) = unnamed(a) diff --git a/src/namedtensoroperator.jl b/src/namedtensoroperator.jl index 9d6a5079..1fa8779f 100644 --- a/src/namedtensoroperator.jl +++ b/src/namedtensoroperator.jl @@ -306,7 +306,7 @@ state(a::AbstractNamedTensor) = a state(a::NamedTensorOperator) = a.parent Base.parent(a::NamedTensorOperator) = state(a) unnamed(a::NamedTensorOperator) = unnamed(state(a)) -Base.names(a::NamedTensorOperator) = names(state(a)) +names(a::NamedTensorOperator) = names(state(a)) parenttype(type::Type{<:NamedTensorOperator}) = fieldtype(type, :parent) statetype(type::Type{<:NamedTensorOperator}) = parenttype(type) diff --git a/test/test_basics.jl b/test/test_basics.jl index dad03b35..f32d0de9 100644 --- a/test/test_basics.jl +++ b/test/test_basics.jl @@ -1,5 +1,5 @@ using ITensorBase: ITensorBase, AbstractNamedTensor, ITensor, Index, IndexName, NamedTensor, - commonind, commoninds, gettag, hascommoninds, hastag, id, inds, name, nametype, + commonind, commoninds, gettag, hascommoninds, hastag, id, inds, name, names, nametype, noncommonind, noncommoninds, noprime, operator, plev, prime, rename, setplev, settag, sim, tags, trycommonind, trynoncommonind, tryuniqueind, unioninds, uniqueind, uniqueinds, uniquename, unname, unnamed, unsettag, uuid diff --git a/test/test_exports.jl b/test/test_exports.jl index e295545f..36745fc6 100644 --- a/test/test_exports.jl +++ b/test/test_exports.jl @@ -14,7 +14,7 @@ using Test: @test, @testset :tryuniqueind, :uniqueind, :uniqueinds, :unioninds, :uniquename, ] publics = [ - :IndexName, :name, :rename, :setname, :space, :unnamed, + :IndexName, :name, :names, :rename, :setname, :space, :unnamed, :unnamedtype, :decoration, :emptytags, :gettag, :gettags, :hastag, :plev, :settags, :tags, :unsettags, diff --git a/test/test_gradedarraysext.jl b/test/test_gradedarraysext.jl index dcdcae47..f199e3db 100644 --- a/test/test_gradedarraysext.jl +++ b/test/test_gradedarraysext.jl @@ -1,5 +1,5 @@ using GradedArrays: U1, sectors -using ITensorBase: ITensorBase, ITensor, Index, align, inds, prime, space, unnamed +using ITensorBase: ITensorBase, ITensor, Index, align, inds, names, prime, space, unnamed using StableRNGs: StableRNG using TensorAlgebra: TensorAlgebra, dual, isdual, matricize, project, project_aux, tryproject, tryproject_aux, unchecked_project, unchecked_project_aux, unmatricize diff --git a/test/test_lazyitensors.jl b/test/test_lazyitensors.jl index 5e3577bf..2c434c54 100644 --- a/test/test_lazyitensors.jl +++ b/test/test_lazyitensors.jl @@ -1,8 +1,8 @@ using AbstractTrees: AbstractTrees, print_tree, printnode using Base.Broadcast: materialize using ITensorBase: @names, Greedy, LazyNamedTensor, Mul, NamedOneTo, NamedTensor, - NamedTensorOperator, SymbolicNamedTensor, inds, inputnames, ismul, lazy, operator, - optimize_evaluation_order, outputnames, state, substitute, symnameddims + NamedTensorOperator, SymbolicNamedTensor, inds, inputnames, ismul, lazy, names, + operator, optimize_evaluation_order, outputnames, state, substitute, symnameddims using OMEinsumContractionOrders: ExhaustiveSearch, GreedyMethod, TreeSA using TermInterface: arguments, arity, children, head, iscall, isexpr, maketerm, operation, sorted_arguments, sorted_children diff --git a/test/test_linearalgebra.jl b/test/test_linearalgebra.jl index 100bf4ad..d59a96df 100644 --- a/test/test_linearalgebra.jl +++ b/test/test_linearalgebra.jl @@ -1,5 +1,5 @@ import LinearAlgebra as LA -using ITensorBase: Named, unname, unnamed +using ITensorBase: Named, names, unname, unnamed using Test: @test, @testset @testset "LinearAlgebra (eltype=$(elt))" for elt in diff --git a/test/test_mooncakeext.jl b/test/test_mooncakeext.jl index 4f07e6e9..b926b947 100644 --- a/test/test_mooncakeext.jl +++ b/test/test_mooncakeext.jl @@ -1,5 +1,5 @@ -using ITensorBase: Name, NamedTensor, NamedUnitRange, inds, name, nameperm, names_setdiff, - to_inds, uniquename +using ITensorBase: Name, NamedTensor, NamedUnitRange, inds, name, nameperm, names, + names_setdiff, to_inds, uniquename using LinearAlgebra: mul! using Mooncake: Mooncake using Random: Random diff --git a/test/test_nameddims_basics.jl b/test/test_nameddims_basics.jl index b2172756..4de22777 100644 --- a/test/test_nameddims_basics.jl +++ b/test/test_nameddims_basics.jl @@ -1,7 +1,7 @@ using Combinatorics: Combinatorics using ITensorBase: @names, AbstractNamedTensor, Name, NameMismatch, Named, NamedDimsCartesianIndex, NamedDimsCartesianIndices, NamedOneTo, NamedTensor, - NamedUnitRange, align, aligned, apply, dim, dims, inds, isnamed, name, nametype, + NamedUnitRange, align, aligned, apply, dim, dims, inds, isnamed, name, names, nametype, product, rename, setnames, unname, unnamed, unnamedtype using LinearAlgebra: LinearAlgebra using Random: default_rng @@ -44,6 +44,9 @@ end @test names(na) == ["i", "j"] @test names(na, 1) == "i" @test names(na, 2) == "j" + # `Base.names` is an alternative spelling that forwards to `ITensorBase.names`. + @test Base.names(na) == names(na) + @test Base.names(na, 1) == names(na, 1) @test dim(na, "i") == 1 @test dim(na, "j") == 2 @test dims(na, ("j", "i")) == (2, 1) diff --git a/test/test_operator.jl b/test/test_operator.jl index f117c281..b680abeb 100644 --- a/test/test_operator.jl +++ b/test/test_operator.jl @@ -1,8 +1,8 @@ using GradedArrays: U1, gradedrange, isdual using ITensorBase: ITensorBase as NDA, Index, NamedOneTo, NamedTensor, NamedTensorOperator, - apply, id, inds, inputaxes, inputinds, inputname, inputnames, operator, outputaxes, - outputinds, outputname, outputnames, product, rename, similar_operator, state, unname, - unnamed + apply, id, inds, inputaxes, inputinds, inputname, inputnames, names, operator, + outputaxes, outputinds, outputname, outputnames, product, rename, similar_operator, + state, unname, unnamed using LinearAlgebra: I, norm using MatrixAlgebraKit: project_hermitian using Random: Random, randn diff --git a/test/test_tensoralgebra.jl b/test/test_tensoralgebra.jl index 5a2620d8..8130f694 100644 --- a/test/test_tensoralgebra.jl +++ b/test/test_tensoralgebra.jl @@ -1,5 +1,5 @@ -using ITensorBase: - ITensorBase, Index, NamedOneTo, id, inds, name, operator, prime, rename, unname, unnamed +using ITensorBase: ITensorBase, Index, NamedOneTo, id, inds, name, names, operator, prime, + rename, unname, unnamed using LinearAlgebra: norm, tr using MatrixAlgebraKit: left_null, left_orth, left_polar, lq_compact, lq_full, qr_compact, qr_full, right_null, right_orth, right_polar, svd_compact, svd_trunc, svd_vals diff --git a/test/test_tensorkitext.jl b/test/test_tensorkitext.jl index e3511673..1ea09985 100644 --- a/test/test_tensorkitext.jl +++ b/test/test_tensorkitext.jl @@ -1,4 +1,4 @@ -using ITensorBase: ITensorBase, ITensor, Index, align, name, prime, unnamed +using ITensorBase: ITensorBase, ITensor, Index, align, name, names, prime, unnamed using LinearAlgebra: norm using MatrixAlgebraKit: qr_compact, svd_compact using StableRNGs: StableRNG diff --git a/test/test_vectorinterface.jl b/test/test_vectorinterface.jl index 4e40f54f..11724aa4 100644 --- a/test/test_vectorinterface.jl +++ b/test/test_vectorinterface.jl @@ -1,5 +1,5 @@ import VectorInterface as VI -using ITensorBase: Named, unnamed +using ITensorBase: Named, names, unnamed using Test: @test, @testset # These name-aware methods are what let an NamedTensor be used directly as a vector in From 4bddd98610b18a68e05eb96ed32a385d4fd96fa8 Mon Sep 17 00:00:00 2001 From: Matthew Fishman Date: Sat, 26 Sep 2026 14:19:18 -0400 Subject: [PATCH 3/7] Follow the public renames through the internal helper names The internal helpers and the `NamedDimsCartesianIndex` types still read in the `nameddims` vocabulary the public surface moved off, so they take the `namedtensor` stem matching `NamedTensor`. `rename` also moves to `export`, since it replaces `replaceinds` and `mapinds`, which were both exported. Co-Authored-By: Claude Opus 5 (1M context) --- docs/src/dev_interface.md | 7 +- docs/src/user_interface.md | 3 + ext/ITensorBaseGradedArraysExt.jl | 14 +- src/ITensorBase.jl | 4 +- src/abstractnamedtensor.jl | 96 +++++++------- src/lazyitensors/itensorbaseextensions.jl | 2 +- src/lazyitensors/lazyinterface.jl | 4 +- src/lazyitensors/lazyitensor.jl | 2 +- src/lazyitensors/symbolicitensor.jl | 6 +- src/namedtensoroperator.jl | 4 +- src/tensoralgebra.jl | 120 +++++++++--------- test/test_exports.jl | 4 +- test/test_lazyitensors.jl | 20 ++- ...s_basics.jl => test_namedtensor_basics.jl} | 12 +- 14 files changed, 153 insertions(+), 145 deletions(-) rename test/{test_nameddims_basics.jl => test_namedtensor_basics.jl} (98%) diff --git a/docs/src/dev_interface.md b/docs/src/dev_interface.md index c47ef3cc..72e6eb0c 100644 --- a/docs/src/dev_interface.md +++ b/docs/src/dev_interface.md @@ -32,9 +32,9 @@ NamedUnitRange Construct named objects with the [`NamedTensor`](@ref) and [`NamedUnitRange`](@ref) constructors, recover their parts with [`name`](@ref), [`unnamed`](@ref), and [`names`](@ref), and query their types with [`nametype`](@ref) and -[`unnamedtype`](@ref). [`setname`](@ref) and -[`rename`](@ref) change names, and [`align`](@ref) and [`aligned`](@ref) reorder a -tensor's dimensions by name (a copy and a view, respectively). +[`unnamedtype`](@ref). [`setname`](@ref) changes a single object's name (the whole-tensor +form is [`rename`](@ref), on the [User Interface](@ref) page), and [`align`](@ref) and +[`aligned`](@ref) reorder a tensor's dimensions by name (a copy and a view, respectively). ```@docs; canonical=false name @@ -43,7 +43,6 @@ names nametype unnamedtype setname -rename align aligned ``` diff --git a/docs/src/user_interface.md b/docs/src/user_interface.md index 1c0713c5..273e4fa9 100644 --- a/docs/src/user_interface.md +++ b/docs/src/user_interface.md @@ -14,12 +14,15 @@ see the [Reference](@ref). An [`ITensor`](@ref) labels its dimensions by name, and an [`Index`](@ref) is a named dimension. Get a tensor's indices with [`inds`](@ref), make distinct copies of an index with [`prime`](@ref) and [`noprime`](@ref), and mint a fresh unique name with [`uniquename`](@ref). +Relabel a tensor's indices with [`rename`](@ref), which takes either a set of replacements or a +function to apply to every name, and leaves the data and the spaces untouched. ```@docs; canonical=false Index inds prime noprime +rename uniquename ``` diff --git a/ext/ITensorBaseGradedArraysExt.jl b/ext/ITensorBaseGradedArraysExt.jl index 07fc1531..008806fb 100644 --- a/ext/ITensorBaseGradedArraysExt.jl +++ b/ext/ITensorBaseGradedArraysExt.jl @@ -14,7 +14,7 @@ const NamedUnitRange = ITensorBase.NamedUnitRange # Name the delegated result: the physical-leg names followed by a fresh name for the dangling aux # leg, minted of the legs' name type (not hardcoded to `IndexName`). -function nameddims_aux(a, codomain, domain) +function namedtensor_aux(a, codomain, domain) names = name.((codomain..., domain...)) aux_name = uniquename(eltype(names)) return NamedTensor(a, (names..., aux_name)) @@ -33,7 +33,7 @@ for S in (Sector, SectorRange) domain::Tuple{Vararg{NamedUnitRange}} ) a = Base.$f(rng, elt, c, unnamed.(codomain), unnamed.(domain)) - return nameddims_aux(a, codomain, domain) + return namedtensor_aux(a, codomain, domain) end function Base.$f( rng::AbstractRNG, c::$S, @@ -71,7 +71,7 @@ for S in (Sector, SectorRange) domain::Tuple{Vararg{NamedUnitRange}} ) a = Base.$f(elt, c, unnamed.(codomain), unnamed.(domain)) - return nameddims_aux(a, codomain, domain) + return namedtensor_aux(a, codomain, domain) end function Base.$f( c::$S, codomain::Tuple{NamedUnitRange, Vararg{NamedUnitRange}}, @@ -86,7 +86,7 @@ for S in (Sector, SectorRange) domain::Tuple{Vararg{NamedUnitRange}} ) a = Base.fill(value, c, unnamed.(codomain), unnamed.(domain)) - return nameddims_aux(a, codomain, domain) + return namedtensor_aux(a, codomain, domain) end # Codomain-only: the domain-omitted form, equivalent to an empty domain. for f in (:rand, :randn) @@ -140,7 +140,7 @@ for S in (Sector, SectorRange) codomain::Tuple{}, domain::Tuple{NamedUnitRange, Vararg{NamedUnitRange}} ) a = Base.$f(rng, elt, c, unnamed.(codomain), unnamed.(domain)) - return nameddims_aux(a, codomain, domain) + return namedtensor_aux(a, codomain, domain) end function Base.$f( rng::AbstractRNG, c::$S, @@ -175,7 +175,7 @@ for S in (Sector, SectorRange) codomain::Tuple{}, domain::Tuple{NamedUnitRange, Vararg{NamedUnitRange}} ) a = Base.$f(elt, c, unnamed.(codomain), unnamed.(domain)) - return nameddims_aux(a, codomain, domain) + return namedtensor_aux(a, codomain, domain) end function Base.$f( c::$S, codomain::Tuple{}, @@ -190,7 +190,7 @@ for S in (Sector, SectorRange) domain::Tuple{NamedUnitRange, Vararg{NamedUnitRange}} ) a = Base.fill(value, c, unnamed.(codomain), unnamed.(domain)) - return nameddims_aux(a, codomain, domain) + return namedtensor_aux(a, codomain, domain) end end diff --git a/src/ITensorBase.jl b/src/ITensorBase.jl index b3cc80e2..04471b3e 100644 --- a/src/ITensorBase.jl +++ b/src/ITensorBase.jl @@ -4,14 +4,14 @@ export AbstractNamedTensor, NamedTensor, AbstractITensor, ITensor, Index, NamedUnitRange, align, aligned, apply, commonind, commoninds, hascommoninds, id, inds, inputaxes, inputinds, inputnames, nametype, noncommonind, noncommoninds, noprime, operator, - outputaxes, outputinds, outputnames, prime, sim, + outputaxes, outputinds, outputnames, prime, rename, sim, similar_operator, state, trycommonind, trynoncommonind, tryuniqueind, uniqueind, uniqueinds, unioninds, uniquename if VERSION >= v"1.11.0-DEV.469" eval( Meta.parse( - "public @names, IndexName, name, names, rename, setname, space, unnamed, unnamedtype, decoration, emptytags, gettag, gettags, hastag, plev, settags, tags, unsettags" + "public @names, IndexName, name, names, setname, space, unnamed, unnamedtype, decoration, emptytags, gettag, gettags, hastag, plev, settags, tags, unsettags" ) ) end diff --git a/src/abstractnamedtensor.jl b/src/abstractnamedtensor.jl index 10f5d180..589e9a49 100644 --- a/src/abstractnamedtensor.jl +++ b/src/abstractnamedtensor.jl @@ -145,14 +145,14 @@ function to_inds(a::AbstractNamedTensor, dims) end #= - nameddimsof(a::AbstractNamedTensor, b) + namedtensorof(a::AbstractNamedTensor, b) -Construct a named dimensions array with the dimension names of `a` +Construct a named tensor with the dimension names of `a` and with the data from `b`. The parent `b` is usually an `AbstractArray` but may be any object a `NamedTensor` can wrap (e.g. a TensorKit `TensorMap`), so `copy`/`zero` of a named tensor round-trip through whatever backend `unnamed(a)` uses. =# -function nameddimsof(a::AbstractNamedTensor, b) +function namedtensorof(a::AbstractNamedTensor, b) return NamedTensor(b, names(a)) end @@ -176,8 +176,8 @@ function checked_indexin(x::AbstractUnitRange, y::AbstractUnitRange) return findfirst(==(first(x)), y):findfirst(==(last(x)), y) end -Base.copy(a::AbstractNamedTensor) = nameddimsof(a, copy(unnamed(a))) -Base.zero(a::AbstractNamedTensor) = nameddimsof(a, zero(unnamed(a))) +Base.copy(a::AbstractNamedTensor) = namedtensorof(a, copy(unnamed(a))) +Base.zero(a::AbstractNamedTensor) = namedtensorof(a, zero(unnamed(a))) # `CartesianIndices` of a named tensor is the parent's, via the named axes (as the # `AbstractArray` fallback did through `axes`). @@ -354,13 +354,13 @@ Base.size(a::AbstractNamedTensor, dimname::Name) = size(a, dim(a, dimname)) # Lowered through `TensorAlgebra.similar_map` (all-codomain, so identical to # `similar(parent, elt, axes)` for dense) so non-`AbstractArray` backends whose `similar` # wants a map-shaped space (e.g. a `TensorMap`) allocate through their own overload. -function similar_nameddims(a::AbstractNamedTensor, elt::Type, ax) +function similar_namedtensor(a::AbstractNamedTensor, elt::Type, ax) return NamedTensor( TensorAlgebra.similar_map(unnamed(a), elt, unnamed.(Tuple(ax)), ()), name.(ax) ) end -function similar_nameddims(a::AbstractArray, elt::Type, ax) +function similar_namedtensor(a::AbstractArray, elt::Type, ax) return NamedTensor( TensorAlgebra.similar_map(a, elt, unnamed.(Tuple(ax)), ()), name.(ax) @@ -370,10 +370,10 @@ end # Base.similar gets the eltype at compile time. Base.similar(a::AbstractNamedTensor) = similar(a, eltype(a)) function Base.similar(a::AbstractNamedTensor, elt::Type) - return similar_nameddims(a, elt) + return similar_namedtensor(a, elt) end -function similar_nameddims(a::AbstractNamedTensor, elt::Type) - return nameddimsof(a, similar(unnamed(a), elt)) +function similar_namedtensor(a::AbstractNamedTensor, elt::Type) + return namedtensorof(a, similar(unnamed(a), elt)) end # This is defined explicitly since the Base version expects the eltype @@ -388,7 +388,7 @@ function Base.similar( a::AbstractArray, elt::Type, inds::Tuple{NamedUnitRange, Vararg{NamedUnitRange}} ) - return similar_nameddims(a, elt, inds) + return similar_namedtensor(a, elt, inds) end # Same entry points with a named-tensor prototype. An `AbstractNamedTensor` is no longer @@ -404,7 +404,7 @@ function Base.similar( a::AbstractNamedTensor, elt::Type, inds::Tuple{NamedUnitRange, Vararg{NamedUnitRange}} ) - return similar_nameddims(a, elt, inds) + return similar_namedtensor(a, elt, inds) end # Rank-0 (empty named axes): a scalar tensor on `a`'s backend, e.g. a backend-matched unit @@ -412,7 +412,7 @@ end # least one `NamedUnitRange`. Base.similar(a::AbstractNamedTensor, inds::Tuple{}) = similar(a, eltype(a), inds) function Base.similar(a::AbstractNamedTensor, elt::Type, inds::Tuple{}) - return similar_nameddims(a, elt, inds) + return similar_namedtensor(a, elt, inds) end # Map-shaped allocator: a shell over the `codomain`/`domain` named axes following `a`'s @@ -445,8 +445,6 @@ unchanged. The second form replaces each name with `f(name)`. # Examples ```jldoctest -julia> using ITensorBase: rename - julia> a = NamedTensor(zeros(2, 3), (:i, :j)); julia> names(rename(a, :i => :k)) @@ -787,27 +785,27 @@ Base.IndexStyle(s1::IndexStyle, s2::NamedIndexCartesian) = NamedIndexCartesian() Base.IndexStyle(s1::NamedIndexCartesian, s2::IndexStyle) = NamedIndexCartesian() # Like CartesianIndex but with named dimensions. -struct NamedDimsCartesianIndex{N, Index <: Tuple{Vararg{NamedInteger, N}}} <: +struct NamedTensorCartesianIndex{N, Index <: Tuple{Vararg{NamedInteger, N}}} <: Base.AbstractCartesianIndex{N} I::Index end -NamedDimsCartesianIndex(I::NamedInteger...) = NamedDimsCartesianIndex(I) -Base.Tuple(I::NamedDimsCartesianIndex) = I.I -function Base.show(io::IO, I::NamedDimsCartesianIndex) - print(io, "NamedDimsCartesianIndex") +NamedTensorCartesianIndex(I::NamedInteger...) = NamedTensorCartesianIndex(I) +Base.Tuple(I::NamedTensorCartesianIndex) = I.I +function Base.show(io::IO, I::NamedTensorCartesianIndex) + print(io, "NamedTensorCartesianIndex") show(io, Tuple(I)) return nothing end # Like CartesianIndices but with named dimensions. -struct NamedDimsCartesianIndices{ +struct NamedTensorCartesianIndices{ N, DimName, Indices <: Tuple{Vararg{NamedUnitRange, N}}, Index <: Tuple{Vararg{NamedInteger, N}}, } <: AbstractNamedTensor{DimName} indices::Indices - function NamedDimsCartesianIndices(indices::Tuple{Vararg{NamedUnitRange}}) + function NamedTensorCartesianIndices(indices::Tuple{Vararg{NamedUnitRange}}) dimname = eltype(name.(indices)) return new{length(indices), dimname, typeof(indices), Tuple{eltype.(indices)...}}( indices @@ -817,29 +815,29 @@ end # The element type is no longer carried by the (rank-erased) supertype, so recover # it from the stored index-tuple parameter. function Base.eltype( - ::Type{<:NamedDimsCartesianIndices{N, <:Any, <:Any, Index}} + ::Type{<:NamedTensorCartesianIndices{N, <:Any, <:Any, Index}} ) where {N, Index} - return NamedDimsCartesianIndex{N, Index} + return NamedTensorCartesianIndex{N, Index} end -Base.eltype(I::NamedDimsCartesianIndices) = eltype(typeof(I)) -Base.axes(I::NamedDimsCartesianIndices) = (only ∘ axes).(I.indices) -Base.size(I::NamedDimsCartesianIndices) = length.(I.indices) +Base.eltype(I::NamedTensorCartesianIndices) = eltype(typeof(I)) +Base.axes(I::NamedTensorCartesianIndices) = (only ∘ axes).(I.indices) +Base.size(I::NamedTensorCartesianIndices) = length.(I.indices) -function Base.getindex(a::NamedDimsCartesianIndices{N}, I::Vararg{Int, N}) where {N} +function Base.getindex(a::NamedTensorCartesianIndices{N}, I::Vararg{Int, N}) where {N} index = map(a.indices, I) do r, i return r[i] end - return NamedDimsCartesianIndex(index) + return NamedTensorCartesianIndex(index) end -function unnamed(I::NamedDimsCartesianIndices) +function unnamed(I::NamedTensorCartesianIndices) return CartesianIndices(unnamed.(I.indices)) end -# Iterating yields `NamedDimsCartesianIndex`es. The generic `AbstractNamedTensor` +# Iterating yields `NamedTensorCartesianIndex`es. The generic `AbstractNamedTensor` # iteration forwards to `unnamed`, which here is a plain `CartesianIndices`, so # convert each parent index back through `getindex`. -function Base.iterate(I::NamedDimsCartesianIndices, state...) +function Base.iterate(I::NamedTensorCartesianIndices, state...) y = iterate(unnamed(I), state...) isnothing(y) && return nothing cartesian, next_state = y @@ -854,7 +852,7 @@ function Base.eachindex( all(a -> issetequal(names(a1), names(a)), a_rest) || throw(NameMismatch("Dimension name mismatch $(names.((a1, a_rest...))).")) # TODO: Check the shapes match. - return NamedDimsCartesianIndices(axes(a1)) + return NamedTensorCartesianIndices(axes(a1)) end # `unname` (eager), not `unnamed` (lazy view): reducing over a lazy permuted view @@ -949,7 +947,7 @@ function Base.to_indices(a::AbstractNamedTensor, I::Tuple{Pair, Vararg{Pair}}) return map((i, name) -> name[i], last.(I), inds) end -function Base.to_indices(a::AbstractNamedTensor, I::Tuple{NamedDimsCartesianIndex}) +function Base.to_indices(a::AbstractNamedTensor, I::Tuple{NamedTensorCartesianIndex}) return to_indices(a, Tuple(only(I))) end @@ -991,7 +989,7 @@ function Base.setindex!( setindex!(a, value, to_indices(a, (I1, Irest...))...) return a end -function Base.setindex!(a::AbstractNamedTensor, value, I::NamedDimsCartesianIndex) +function Base.setindex!(a::AbstractNamedTensor, value, I::NamedTensorCartesianIndex) setindex!(a, value, to_indices(a, (I,))...) return a end @@ -1100,7 +1098,7 @@ function Base.view(a::AbstractNamedTensor, I1::NamedViewIndex, Irest::NamedViewI ) ) Ip = map(p -> unnamed(I[p]), perm) - return view_nameddims(a, Ip...) + return view_namedtensor(a, Ip...) end # Repeated definition of `Base.ViewIndex`. @@ -1115,22 +1113,22 @@ isscalarindex(I::Real) = true # Slicing with unnamed indices, such as: # a = NamedTensor(rand(3,4), (:x, :y)) # b = view(a, 1:2, 2) -function view_nameddims(a::AbstractNamedTensor, I...) +function view_namedtensor(a::AbstractNamedTensor, I...) nonscalar_dims = filter(dim -> !isscalarindex(I[dim]), ntuple(identity, ndims(a))) nonscalar_names = map(dim -> names(a, dim), nonscalar_dims) return NamedTensor(view(unnamed(a), I...), nonscalar_names) end function Base.view(a::AbstractNamedTensor, I::ViewIndex...) - return view_nameddims(a, I...) + return view_namedtensor(a, I...) end -function getindex_nameddims(a::AbstractNamedTensor, I...) +function getindex_namedtensor(a::AbstractNamedTensor, I...) return copy(view(a, I...)) end function Base.getindex(a::AbstractNamedTensor, I::ViewIndex...) - return getindex_nameddims(a, I...) + return getindex_namedtensor(a, I...) end function Base.setindex!( @@ -1340,7 +1338,7 @@ end # and the result is named with the codomain names followed by the domain names. The # `rand`/`randn`/`zeros` two-tuple forms (`randn((i,), (j,))`) forward to these. # -# Each constructor is a shared `*_nameddims` builder (strip the names, call the map hook on the +# Each constructor is a shared `*_namedtensor` builder (strip the names, call the map hook on the # raw axes, reattach the names) plus two forwarding methods: one for a nonempty codomain and one # for an empty codomain with a nonempty domain. The two-way split (rather than a single # `Tuple{Vararg{NamedUnitRange}}` on both sides) reads the index type from whichever side is @@ -1349,8 +1347,8 @@ end # error rather than recurse. for f in [:rand, :randn] f_map = Symbol(f, :_map) - f_nameddims = Symbol(f, :_nameddims) - @eval function $f_nameddims(rng::AbstractRNG, elt::Type{<:Number}, codomain, domain) + f_namedtensor = Symbol(f, :_namedtensor) + @eval function $f_namedtensor(rng::AbstractRNG, elt::Type{<:Number}, codomain, domain) a = TensorAlgebra.$f_map(rng, elt, unnamed.(codomain), unnamed.(domain)) return a[Name.(name.((codomain..., domain...)))...] end @@ -1366,7 +1364,7 @@ for f in [:rand, :randn] rng::AbstractRNG, elt::Type{<:Number}, codomain::$codomain_type, domain::$domain_type ) - return $f_nameddims(rng, elt, codomain, domain) + return $f_namedtensor(rng, elt, codomain, domain) end function Base.$f( rng::AbstractRNG, elt::Type{<:Number}, @@ -1391,8 +1389,8 @@ for f in [:rand, :randn] end end for (f, f_map) in [(:zeros, :zeros_map), (:ones, :ones_map)] - f_nameddims = Symbol(f, :_nameddims) - @eval function $f_nameddims(elt::Type{<:Number}, codomain, domain) + f_namedtensor = Symbol(f, :_namedtensor) + @eval function $f_namedtensor(elt::Type{<:Number}, codomain, domain) a = TensorAlgebra.$f_map(elt, unnamed.(codomain), unnamed.(domain)) return a[Name.(name.((codomain..., domain...)))...] end @@ -1407,7 +1405,7 @@ for (f, f_map) in [(:zeros, :zeros_map), (:ones, :ones_map)] function TensorAlgebra.$f_map( elt::Type{<:Number}, codomain::$codomain_type, domain::$domain_type ) - return $f_nameddims(elt, codomain, domain) + return $f_namedtensor(elt, codomain, domain) end function Base.$f( elt::Type{<:Number}, codomain::$codomain_type, domain::$domain_type @@ -1422,7 +1420,7 @@ for (f, f_map) in [(:zeros, :zeros_map), (:ones, :ones_map)] end # `fill` takes the fill value first, so it does not fit the eltype-leading forms above; it gets # the same map-shaped split via `fill_map`. -function fill_nameddims(value, codomain, domain) +function fill_namedtensor(value, codomain, domain) a = TensorAlgebra.fill_map(value, unnamed.(codomain), unnamed.(domain)) return a[Name.(name.((codomain..., domain...)))...] end @@ -1436,7 +1434,7 @@ for (codomain_type, domain_type) in [ codomain::$codomain_type, domain::$domain_type ) - return fill_nameddims(value, codomain, domain) + return fill_namedtensor(value, codomain, domain) end function Base.fill(value, codomain::$codomain_type, domain::$domain_type) return TensorAlgebra.fill_map(value, codomain, domain) diff --git a/src/lazyitensors/itensorbaseextensions.jl b/src/lazyitensors/itensorbaseextensions.jl index b5b0bc0b..f8bea02e 100644 --- a/src/lazyitensors/itensorbaseextensions.jl +++ b/src/lazyitensors/itensorbaseextensions.jl @@ -26,4 +26,4 @@ end # avoid type piracy when overloading on `AbstractNamedTensor`. # Method specializations (`LazyNamedTensor`, `SymbolicNamedTensor`) live in # `lazyitensor.jl` and `symbolicitensor.jl`. -printnode_nameddims(io::IO, x) = AbstractTrees.printnode(io, x) +printnode_namedtensor(io::IO, x) = AbstractTrees.printnode(io, x) diff --git a/src/lazyitensors/lazyinterface.jl b/src/lazyitensors/lazyinterface.jl index 3f7d8d04..abb97f73 100644 --- a/src/lazyitensors/lazyinterface.jl +++ b/src/lazyitensors/lazyinterface.jl @@ -140,9 +140,9 @@ end substitute_lazy(a, substitutions) = substitute(a, Dict(substitutions)) using AbstractTrees: printnode function printnode_lazy(io, a) - # Use `printnode_nameddims` to avoid type piracy, + # Use `printnode_namedtensor` to avoid type piracy, # since it overloads on `AbstractNamedTensor`. - return printnode_nameddims(io, unwrap(a)) + return printnode_namedtensor(io, unwrap(a)) end function show_lazy(io::IO, a) if !iscall(a) diff --git a/src/lazyitensors/lazyitensor.jl b/src/lazyitensors/lazyitensor.jl index 6425ab94..fc897296 100644 --- a/src/lazyitensors/lazyitensor.jl +++ b/src/lazyitensors/lazyitensor.jl @@ -86,7 +86,7 @@ Base.hash(a::LazyNamedTensor, h::UInt64) = hash_lazy(a, h) map_arguments(f, a::LazyNamedTensor) = map_arguments_lazy(f, a) substitute(a::LazyNamedTensor, substitutions) = substitute_lazy(a, substitutions) AbstractTrees.printnode(io::IO, a::LazyNamedTensor) = printnode_lazy(io, a) -printnode_nameddims(io::IO, a::LazyNamedTensor) = printnode_lazy(io, a) +printnode_namedtensor(io::IO, a::LazyNamedTensor) = printnode_lazy(io, a) Base.show(io::IO, a::LazyNamedTensor) = show_lazy(io, a) Base.show(io::IO, mime::MIME"text/plain", a::LazyNamedTensor) = show_lazy(io, mime, a) Base.:*(a::LazyNamedTensor) = mul_lazy(a) diff --git a/src/lazyitensors/symbolicitensor.jl b/src/lazyitensors/symbolicitensor.jl index 279e16be..ed9da5b0 100644 --- a/src/lazyitensors/symbolicitensor.jl +++ b/src/lazyitensors/symbolicitensor.jl @@ -69,12 +69,12 @@ function AbstractTrees.printnode(io::IO, a::SymbolicNamedTensor) return nothing end -function symnameddims(symname, dims) +function symnamedtensor(symname, dims) return lazy(SymbolicNamedTensor(symname, dims)) end -symnameddims(name) = symnameddims(name, ()) +symnamedtensor(name) = symnamedtensor(name, ()) -function printnode_nameddims(io::IO, a::SymbolicNamedTensor) +function printnode_namedtensor(io::IO, a::SymbolicNamedTensor) AbstractTrees.printnode(io, a) return nothing end diff --git a/src/namedtensoroperator.jl b/src/namedtensoroperator.jl index 1fa8779f..64dddae6 100644 --- a/src/namedtensoroperator.jl +++ b/src/namedtensoroperator.jl @@ -311,8 +311,8 @@ names(a::NamedTensorOperator) = names(state(a)) parenttype(type::Type{<:NamedTensorOperator}) = fieldtype(type, :parent) statetype(type::Type{<:NamedTensorOperator}) = parenttype(type) -function nameddimsof(a::NamedTensorOperator, b::AbstractArray) - return NamedTensorOperator(nameddimsof(state(a), b), a.outputnames, a.inputnames) +function namedtensorof(a::NamedTensorOperator, b::AbstractArray) + return NamedTensorOperator(namedtensorof(state(a), b), a.outputnames, a.inputnames) end outputnames(a::NamedTensorOperator) = a.outputnames diff --git a/src/tensoralgebra.jl b/src/tensoralgebra.jl index ffabe301..1380cc71 100644 --- a/src/tensoralgebra.jl +++ b/src/tensoralgebra.jl @@ -6,8 +6,8 @@ using TensorAlgebra: TensorAlgebra as TA # This layer is used to define derivative rules (to skip differentiating `setdiff`). names_setdiff(s1, s2) = setdiff(s1, s2) -Base.:*(a1::AbstractNamedTensor, a2::AbstractNamedTensor) = mul_nameddims(a1, a2) -function mul_nameddims(a1::AbstractNamedTensor, a2::AbstractNamedTensor) +Base.:*(a1::AbstractNamedTensor, a2::AbstractNamedTensor) = mul_namedtensor(a1, a2) +function mul_namedtensor(a1::AbstractNamedTensor, a2::AbstractNamedTensor) a_dest, names_dest = TA.contract( unnamed(a1), names(a1), unnamed(a2), names(a2) ) @@ -25,9 +25,9 @@ function Base.:*( a1::AbstractNamedTensor, a2::AbstractNamedTensor, a3::AbstractNamedTensor, a_rest::AbstractNamedTensor... ) - return mul_nameddims(a1, a2, a3, a_rest...) + return mul_namedtensor(a1, a2, a3, a_rest...) end -function mul_nameddims( +function mul_namedtensor( a1::AbstractNamedTensor, a2::AbstractNamedTensor, a3::AbstractNamedTensor, a_rest::AbstractNamedTensor... ) @@ -39,9 +39,9 @@ function LA.mul!( a1::AbstractNamedTensor, a2::AbstractNamedTensor, α::Number, β::Number ) - return mul!_nameddims(a_dest, a1, a2, α, β) + return mul!_namedtensor(a_dest, a1, a2, α, β) end -function mul!_nameddims( +function mul!_namedtensor( a_dest::AbstractNamedTensor, a1::AbstractNamedTensor, a2::AbstractNamedTensor, α::Number, β::Number @@ -59,9 +59,9 @@ function LA.mul!( a_dest::AbstractNamedTensor, a1::AbstractNamedTensor, a2::AbstractNamedTensor ) - return mul!_nameddims(a_dest, a1, a2) + return mul!_namedtensor(a_dest, a1, a2) end -function mul!_nameddims( +function mul!_namedtensor( a_dest::AbstractNamedTensor, a1::AbstractNamedTensor, a2::AbstractNamedTensor ) @@ -114,23 +114,23 @@ function TA.unmatricize( codomain::Tuple{NamedUnitRange, Vararg{NamedUnitRange}}, domain::Tuple{Vararg{NamedUnitRange}} ) - return unmatricize_nameddims(m, codomain, domain) + return unmatricize_namedtensor(m, codomain, domain) end function TA.unmatricize( m, codomain::Tuple{Vararg{NamedUnitRange}}, domain::Tuple{NamedUnitRange, Vararg{NamedUnitRange}} ) - return unmatricize_nameddims(m, codomain, domain) + return unmatricize_namedtensor(m, codomain, domain) end function TA.unmatricize( m, codomain::Tuple{NamedUnitRange, Vararg{NamedUnitRange}}, domain::Tuple{NamedUnitRange, Vararg{NamedUnitRange}} ) - return unmatricize_nameddims(m, codomain, domain) + return unmatricize_namedtensor(m, codomain, domain) end -function unmatricize_nameddims(m, codomain, domain) +function unmatricize_namedtensor(m, codomain, domain) a = TA.unmatricize(m, space.(codomain), space.(domain)) return NamedTensor(a, name.(codomain), name.(domain)) end @@ -201,14 +201,14 @@ for f in [ :left_orth, :left_polar, :lq_compact, :lq_full, :qr_compact, :qr_full, :right_orth, :right_polar, ] - f_nameddims = Symbol(f, "_nameddims") + f_namedtensor = Symbol(f, "_namedtensor") @eval begin function MAK.$f( a::AbstractNamedTensor, names_codomain, names_domain; kwargs... ) - return $f_nameddims(a, names_codomain, names_domain; kwargs...) + return $f_namedtensor(a, names_codomain, names_domain; kwargs...) end - function $f_nameddims( + function $f_namedtensor( a::AbstractNamedTensor, names_codomain, names_domain; name = (;), kwargs... ) @@ -226,9 +226,9 @@ for f in [ return x, y end function MAK.$f(a::AbstractNamedTensor, names_codomain; kwargs...) - return $f_nameddims(a, names_codomain; kwargs...) + return $f_namedtensor(a, names_codomain; kwargs...) end - function $f_nameddims(a::AbstractNamedTensor, names_codomain; kwargs...) + function $f_namedtensor(a::AbstractNamedTensor, names_codomain; kwargs...) codomain = name.(names_codomain) domain = names_setdiff(names(a), codomain) return MAK.$f(a, codomain, domain; kwargs...) @@ -241,14 +241,14 @@ end # for f in [:svd_compact, :svd_full] - f_nameddims = Symbol(f, "_nameddims") + f_namedtensor = Symbol(f, "_namedtensor") @eval begin function MAK.$f( a::AbstractNamedTensor, names_codomain, names_domain; kwargs... ) - return $f_nameddims(a, names_codomain, names_domain; kwargs...) + return $f_namedtensor(a, names_codomain, names_domain; kwargs...) end - function $f_nameddims( + function $f_namedtensor( a::AbstractNamedTensor, names_codomain, names_domain; leftname = (;), rightname = (;), kwargs... ) @@ -268,9 +268,9 @@ for f in [:svd_compact, :svd_full] return u, s, v end function MAK.$f(a::AbstractNamedTensor, names_codomain; kwargs...) - return $f_nameddims(a, names_codomain; kwargs...) + return $f_namedtensor(a, names_codomain; kwargs...) end - function $f_nameddims(a::AbstractNamedTensor, names_codomain; kwargs...) + function $f_namedtensor(a::AbstractNamedTensor, names_codomain; kwargs...) return MAK.$f( a, names_codomain, @@ -287,9 +287,9 @@ end function MAK.svd_trunc( a::AbstractNamedTensor, names_codomain, names_domain; kwargs... ) - return svd_trunc_nameddims(a, names_codomain, names_domain; kwargs...) + return svd_trunc_namedtensor(a, names_codomain, names_domain; kwargs...) end -function svd_trunc_nameddims( +function svd_trunc_namedtensor( a::AbstractNamedTensor, names_codomain, names_domain; kwargs... ) codomain = name.(names_codomain) @@ -305,9 +305,9 @@ function svd_trunc_nameddims( return u, s, v, ϵ end function MAK.svd_trunc(a::AbstractNamedTensor, names_codomain; kwargs...) - return svd_trunc_nameddims(a, names_codomain; kwargs...) + return svd_trunc_namedtensor(a, names_codomain; kwargs...) end -function svd_trunc_nameddims(a::AbstractNamedTensor, names_codomain; kwargs...) +function svd_trunc_namedtensor(a::AbstractNamedTensor, names_codomain; kwargs...) return MAK.svd_trunc( a, names_codomain, @@ -323,9 +323,9 @@ end function MAK.svd_vals( a::AbstractNamedTensor, names_codomain, names_domain; kwargs... ) - return svd_vals_nameddims(a, names_codomain, names_domain; kwargs...) + return svd_vals_namedtensor(a, names_codomain, names_domain; kwargs...) end -function svd_vals_nameddims( +function svd_vals_namedtensor( a::AbstractNamedTensor, names_codomain, names_domain; kwargs... ) return TA.svd_vals( @@ -338,9 +338,9 @@ function svd_vals_nameddims( end function MAK.svd_vals(a::AbstractNamedTensor, names_codomain; kwargs...) - return svd_vals_nameddims(a, names_codomain; kwargs...) + return svd_vals_namedtensor(a, names_codomain; kwargs...) end -function svd_vals_nameddims(a::AbstractNamedTensor, names_codomain; kwargs...) +function svd_vals_namedtensor(a::AbstractNamedTensor, names_codomain; kwargs...) codomain = name.(names_codomain) domain = names_setdiff(names(a), codomain) return MAK.svd_vals(a, codomain, domain; kwargs...) @@ -351,14 +351,14 @@ end # for f in [:eigh_full, :eig_full, :eigh_trunc, :eig_trunc] - f_nameddims = Symbol(f, "_nameddims") + f_namedtensor = Symbol(f, "_namedtensor") @eval begin function MAK.$f( a::AbstractNamedTensor, names_codomain, names_domain; kwargs... ) - return $f_nameddims(a, names_codomain, names_domain; kwargs...) + return $f_namedtensor(a, names_codomain, names_domain; kwargs...) end - function $f_nameddims( + function $f_namedtensor( a::AbstractNamedTensor, names_codomain, names_domain; leftname = (;), rightname = (;), kwargs... ) @@ -384,14 +384,14 @@ end # for f in [:eigh_vals, :eig_vals] - f_nameddims = Symbol(f, "_nameddims") + f_namedtensor = Symbol(f, "_namedtensor") @eval begin function MAK.$f( a::AbstractNamedTensor, names_codomain, names_domain; kwargs... ) - return $f_nameddims(a, names_codomain, names_domain; kwargs...) + return $f_namedtensor(a, names_codomain, names_domain; kwargs...) end - function $f_nameddims( + function $f_namedtensor( a::AbstractNamedTensor, names_codomain, names_domain; kwargs... ) codomain = name.(names_codomain) @@ -404,9 +404,9 @@ end function MAK.left_null( a::AbstractNamedTensor, names_codomain, names_domain; kwargs... ) - return left_null_nameddims(a, names_codomain, names_domain; kwargs...) + return left_null_namedtensor(a, names_codomain, names_domain; kwargs...) end -function left_null_nameddims( +function left_null_namedtensor( a::AbstractNamedTensor, names_codomain, names_domain; name = (;), kwargs... ) # `name` is a keyword here, so reach the `name` function through the module. @@ -419,9 +419,9 @@ function left_null_nameddims( end function MAK.left_null(a::AbstractNamedTensor, names_codomain; kwargs...) - return left_null_nameddims(a, names_codomain; kwargs...) + return left_null_namedtensor(a, names_codomain; kwargs...) end -function left_null_nameddims(a::AbstractNamedTensor, names_codomain; kwargs...) +function left_null_namedtensor(a::AbstractNamedTensor, names_codomain; kwargs...) codomain = name.(names_codomain) domain = names_setdiff(names(a), codomain) return MAK.left_null(a, codomain, domain; kwargs...) @@ -430,9 +430,9 @@ end function MAK.right_null( a::AbstractNamedTensor, names_codomain, names_domain; kwargs... ) - return right_null_nameddims(a, names_codomain, names_domain; kwargs...) + return right_null_namedtensor(a, names_codomain, names_domain; kwargs...) end -function right_null_nameddims( +function right_null_namedtensor( a::AbstractNamedTensor, names_codomain, names_domain; name = (;), kwargs... ) # `name` is a keyword here, so reach the `name` function through the module. @@ -445,9 +445,9 @@ function right_null_nameddims( end function MAK.right_null(a::AbstractNamedTensor, names_codomain; kwargs...) - return right_null_nameddims(a, names_codomain; kwargs...) + return right_null_namedtensor(a, names_codomain; kwargs...) end -function right_null_nameddims(a::AbstractNamedTensor, names_codomain; kwargs...) +function right_null_namedtensor(a::AbstractNamedTensor, names_codomain; kwargs...) codomain = name.(names_codomain) domain = names_setdiff(names(a), codomain) return MAK.right_null(a, codomain, domain; kwargs...) @@ -569,9 +569,9 @@ julia> tr(one(a, (i, j), (k, l)), (i, j), (k, l)) function Base.one( a::AbstractNamedTensor, names_codomain, names_domain ) - return one_nameddims(a, names_codomain, names_domain) + return one_namedtensor(a, names_codomain, names_domain) end -function one_nameddims( +function one_namedtensor( a::AbstractNamedTensor, names_codomain, names_domain ) codomain = name.(names_codomain) @@ -650,14 +650,14 @@ const MATRIX_FUNCTIONS = [ ] for f in MATRIX_FUNCTIONS - f_nameddims = Symbol(f, "_nameddims") + f_namedtensor = Symbol(f, "_namedtensor") @eval begin function Base.$f( a::AbstractNamedTensor, names_codomain, names_domain; kwargs... ) - return $f_nameddims(a, names_codomain, names_domain; kwargs...) + return $f_namedtensor(a, names_codomain, names_domain; kwargs...) end - function $f_nameddims( + function $f_namedtensor( a::AbstractNamedTensor, names_codomain, names_domain; kwargs... ) codomain = name.(names_codomain) @@ -688,20 +688,20 @@ function name_projected(projected, input_names) return NamedTensor(projected, (input_names..., aux_names...)) end -# Each `_nameddims` runs the named-index layer of a `TensorAlgebra` verb: strip the axes to +# Each `_namedtensor` runs the named-index layer of a `TensorAlgebra` verb: strip the axes to # their unnamed ranges, lower to the unnamed verb, and reattach the names (the `*_aux` verbs also # name the derived auxiliary leg, see `name_projected`). The one body also covers an empty codomain, # since `unnamed.(())` and `name.(())` are both `()`, so the all-domain (co-state) case needs no # separate path. -function project_nameddims(a, codomain_inds, domain_inds; kwargs...) +function project_namedtensor(a, codomain_inds, domain_inds; kwargs...) projected = TA.project(a, unnamed.(codomain_inds), unnamed.(domain_inds); kwargs...) return name_projected(projected, (name.(codomain_inds)..., name.(domain_inds)...)) end -function tryproject_nameddims(a, codomain_inds, domain_inds; kwargs...) +function tryproject_namedtensor(a, codomain_inds, domain_inds; kwargs...) projected = TA.tryproject(a, unnamed.(codomain_inds), unnamed.(domain_inds); kwargs...) return name_projected(projected, (name.(codomain_inds)..., name.(domain_inds)...)) end -function unchecked_project_nameddims(a, codomain_inds, domain_inds; kwargs...) +function unchecked_project_namedtensor(a, codomain_inds, domain_inds; kwargs...) projected = TA.unchecked_project(a, unnamed.(codomain_inds), unnamed.(domain_inds); kwargs...) return name_projected(projected, (name.(codomain_inds)..., name.(domain_inds)...)) @@ -709,16 +709,16 @@ end # The `*_aux` workers derive and append the flux-carrying auxiliary leg, which `name_projected` # names. Same named-index layer as the strict workers above, lowered to the `*_aux` unnamed verbs. -function project_aux_nameddims(a, codomain_inds, domain_inds; kwargs...) +function project_aux_namedtensor(a, codomain_inds, domain_inds; kwargs...) projected = TA.project_aux(a, unnamed.(codomain_inds), unnamed.(domain_inds); kwargs...) return name_projected(projected, (name.(codomain_inds)..., name.(domain_inds)...)) end -function tryproject_aux_nameddims(a, codomain_inds, domain_inds; kwargs...) +function tryproject_aux_namedtensor(a, codomain_inds, domain_inds; kwargs...) projected = TA.tryproject_aux(a, unnamed.(codomain_inds), unnamed.(domain_inds); kwargs...) return name_projected(projected, (name.(codomain_inds)..., name.(domain_inds)...)) end -function unchecked_project_aux_nameddims(a, codomain_inds, domain_inds; kwargs...) +function unchecked_project_aux_namedtensor(a, codomain_inds, domain_inds; kwargs...) projected = TA.unchecked_project_aux( a, @@ -832,7 +832,7 @@ for f in ( :project, :tryproject, :unchecked_project, :project_aux, :tryproject_aux, :unchecked_project_aux, ) - fnamed = Symbol(f, :_nameddims) + f_namedtensor = Symbol(f, :_namedtensor) doc = Symbol("_", f, "_named_docstring") @eval begin @doc $doc function TA.$f( @@ -840,20 +840,20 @@ for f in ( codomain_inds::Tuple{NamedUnitRange, Vararg{NamedUnitRange}}, domain_inds::Tuple{Vararg{NamedUnitRange}}; kwargs... ) - return $fnamed(a, codomain_inds, domain_inds; kwargs...) + return $f_namedtensor(a, codomain_inds, domain_inds; kwargs...) end function TA.$f( a::AbstractArray, codomain_inds::Tuple{}, domain_inds::Tuple{NamedUnitRange, Vararg{NamedUnitRange}}; kwargs... ) - return $fnamed(a, codomain_inds, domain_inds; kwargs...) + return $f_namedtensor(a, codomain_inds, domain_inds; kwargs...) end function TA.$f( a::AbstractArray, inds::Tuple{NamedUnitRange, Vararg{NamedUnitRange}}; kwargs... ) - return $fnamed(a, inds, (); kwargs...) + return $f_namedtensor(a, inds, (); kwargs...) end end end diff --git a/test/test_exports.jl b/test/test_exports.jl index 36745fc6..0c529b48 100644 --- a/test/test_exports.jl +++ b/test/test_exports.jl @@ -9,12 +9,12 @@ using Test: @test, @testset :inds, :inputaxes, :inputinds, :inputnames, :nametype, :noncommonind, :noncommoninds, :noprime, :operator, :outputaxes, :outputinds, :outputnames, - :prime, + :prime, :rename, :sim, :similar_operator, :state, :trycommonind, :trynoncommonind, :tryuniqueind, :uniqueind, :uniqueinds, :unioninds, :uniquename, ] publics = [ - :IndexName, :name, :names, :rename, :setname, :space, :unnamed, + :IndexName, :name, :names, :setname, :space, :unnamed, :unnamedtype, :decoration, :emptytags, :gettag, :gettags, :hastag, :plev, :settags, :tags, :unsettags, diff --git a/test/test_lazyitensors.jl b/test/test_lazyitensors.jl index 2c434c54..23eb5bff 100644 --- a/test/test_lazyitensors.jl +++ b/test/test_lazyitensors.jl @@ -2,7 +2,7 @@ using AbstractTrees: AbstractTrees, print_tree, printnode using Base.Broadcast: materialize using ITensorBase: @names, Greedy, LazyNamedTensor, Mul, NamedOneTo, NamedTensor, NamedTensorOperator, SymbolicNamedTensor, inds, inputnames, ismul, lazy, names, - operator, optimize_evaluation_order, outputnames, state, substitute, symnameddims + operator, optimize_evaluation_order, outputnames, state, substitute, symnamedtensor using OMEinsumContractionOrders: ExhaustiveSearch, GreedyMethod, TreeSA using TermInterface: arguments, arity, children, head, iscall, isexpr, maketerm, operation, sorted_arguments, sorted_children @@ -79,8 +79,8 @@ using WrappedUnions: unwrap "└─ {\"k\", \"l\"}\n" end - @testset "symnameddims" begin - a1, a2, a3 = symnameddims.((:a1, :a2, :a3)) + @testset "symnamedtensor" begin + a1, a2, a3 = symnamedtensor.((:a1, :a2, :a3)) @test a1 isa LazyNamedTensor @test unwrap(a1) isa SymbolicNamedTensor @test unwrap(a1) == SymbolicNamedTensor(:a1, ()) @@ -96,7 +96,7 @@ using WrappedUnions: unwrap end @testset "substitute" begin - s = symnameddims.((:a1, :a2, :a3)) + s = symnamedtensor.((:a1, :a2, :a3)) i = @names i[1:4] a = (randn(2, 2)[i[1], i[2]], randn(2, 2)[i[2], i[3]], randn(2, 2)[i[3], i[4]]) l = lazy.(a) @@ -109,7 +109,11 @@ using WrappedUnions: unwrap @testset "optimize_evaluation_order ($alg)" for alg in (Greedy(),) i, j, k, l = NamedOneTo.((2, 3, 4, 5), (:i, :j, :k, :l)) - s = [symnameddims(:a, (i, j)), symnameddims(:b, (j, k)), symnameddims(:c, (k, l))] + s = [ + symnamedtensor(:a, (i, j)), + symnamedtensor(:b, (j, k)), + symnamedtensor(:c, (k, l)), + ] flat = lazy(Mul(s)) ordered = optimize_evaluation_order(flat; alg) @test ordered isa LazyNamedTensor @@ -141,7 +145,11 @@ using WrappedUnions: unwrap TreeSA(), ) i, j, k, l = NamedOneTo.((2, 3, 4, 5), (:i, :j, :k, :l)) - s = [symnameddims(:a, (i, j)), symnameddims(:b, (j, k)), symnameddims(:c, (k, l))] + s = [ + symnamedtensor(:a, (i, j)), + symnamedtensor(:b, (j, k)), + symnamedtensor(:c, (k, l)), + ] flat = lazy(Mul(s)) ordered = optimize_evaluation_order(flat; alg) @test ordered isa LazyNamedTensor diff --git a/test/test_nameddims_basics.jl b/test/test_namedtensor_basics.jl similarity index 98% rename from test/test_nameddims_basics.jl rename to test/test_namedtensor_basics.jl index 4de22777..7dc7c575 100644 --- a/test/test_nameddims_basics.jl +++ b/test/test_namedtensor_basics.jl @@ -1,8 +1,8 @@ using Combinatorics: Combinatorics -using ITensorBase: @names, AbstractNamedTensor, Name, NameMismatch, Named, - NamedDimsCartesianIndex, NamedDimsCartesianIndices, NamedOneTo, NamedTensor, - NamedUnitRange, align, aligned, apply, dim, dims, inds, isnamed, name, names, nametype, - product, rename, setnames, unname, unnamed, unnamedtype +using ITensorBase: @names, AbstractNamedTensor, Name, NameMismatch, Named, NamedOneTo, + NamedTensor, NamedTensorCartesianIndex, NamedTensorCartesianIndices, NamedUnitRange, + align, aligned, apply, dim, dims, inds, isnamed, name, names, nametype, product, rename, + setnames, unname, unnamed, unnamedtype using LinearAlgebra: LinearAlgebra using Random: default_rng using TensorAlgebra: datatype @@ -317,10 +317,10 @@ end nb = NamedTensor(randn(elt, 3, 2), (:j, :i)) nc = zeros(elt, Named.((2, 3), (:i, :j))) Is = eachindex(na, nb) - @test Is isa NamedDimsCartesianIndices{2} + @test Is isa NamedTensorCartesianIndices{2} @test issetequal(Is.indices, (NamedUnitRange(1:2, :i), NamedUnitRange(1:3, :j))) for I in Is - @test I isa NamedDimsCartesianIndex{2} + @test I isa NamedTensorCartesianIndex{2} @test issetequal(name.(Tuple(I)), (:i, :j)) nc[I] = na[I] + nb[I] end From 00110820ad234524a5d9d743d5c58b8ab9088d1e Mon Sep 17 00:00:00 2001 From: Matthew Fishman Date: Sat, 26 Sep 2026 14:28:48 -0400 Subject: [PATCH 4/7] Name the named CartesianIndex types after the Base type they mirror `NamedCartesianIndex` subtypes `Base.AbstractCartesianIndex` and is not a tensor, so it takes the `Named` plus Base-type form used by `NamedInteger`, `NamedUnitRange` and the `NamedIndexCartesian` style beside it. Co-Authored-By: Claude Opus 5 (1M context) --- src/abstractnamedtensor.jl | 40 ++++++++++++++++----------------- test/test_namedtensor_basics.jl | 8 +++---- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/src/abstractnamedtensor.jl b/src/abstractnamedtensor.jl index 589e9a49..c36352c9 100644 --- a/src/abstractnamedtensor.jl +++ b/src/abstractnamedtensor.jl @@ -785,27 +785,27 @@ Base.IndexStyle(s1::IndexStyle, s2::NamedIndexCartesian) = NamedIndexCartesian() Base.IndexStyle(s1::NamedIndexCartesian, s2::IndexStyle) = NamedIndexCartesian() # Like CartesianIndex but with named dimensions. -struct NamedTensorCartesianIndex{N, Index <: Tuple{Vararg{NamedInteger, N}}} <: +struct NamedCartesianIndex{N, Index <: Tuple{Vararg{NamedInteger, N}}} <: Base.AbstractCartesianIndex{N} I::Index end -NamedTensorCartesianIndex(I::NamedInteger...) = NamedTensorCartesianIndex(I) -Base.Tuple(I::NamedTensorCartesianIndex) = I.I -function Base.show(io::IO, I::NamedTensorCartesianIndex) - print(io, "NamedTensorCartesianIndex") +NamedCartesianIndex(I::NamedInteger...) = 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 # Like CartesianIndices but with named dimensions. -struct NamedTensorCartesianIndices{ +struct NamedCartesianIndices{ N, DimName, Indices <: Tuple{Vararg{NamedUnitRange, N}}, Index <: Tuple{Vararg{NamedInteger, N}}, } <: AbstractNamedTensor{DimName} indices::Indices - function NamedTensorCartesianIndices(indices::Tuple{Vararg{NamedUnitRange}}) + function NamedCartesianIndices(indices::Tuple{Vararg{NamedUnitRange}}) dimname = eltype(name.(indices)) return new{length(indices), dimname, typeof(indices), Tuple{eltype.(indices)...}}( indices @@ -815,29 +815,29 @@ end # The element type is no longer carried by the (rank-erased) supertype, so recover # it from the stored index-tuple parameter. function Base.eltype( - ::Type{<:NamedTensorCartesianIndices{N, <:Any, <:Any, Index}} + ::Type{<:NamedCartesianIndices{N, <:Any, <:Any, Index}} ) where {N, Index} - return NamedTensorCartesianIndex{N, Index} + return NamedCartesianIndex{N, Index} end -Base.eltype(I::NamedTensorCartesianIndices) = eltype(typeof(I)) -Base.axes(I::NamedTensorCartesianIndices) = (only ∘ axes).(I.indices) -Base.size(I::NamedTensorCartesianIndices) = length.(I.indices) +Base.eltype(I::NamedCartesianIndices) = eltype(typeof(I)) +Base.axes(I::NamedCartesianIndices) = (only ∘ axes).(I.indices) +Base.size(I::NamedCartesianIndices) = length.(I.indices) -function Base.getindex(a::NamedTensorCartesianIndices{N}, I::Vararg{Int, N}) where {N} +function Base.getindex(a::NamedCartesianIndices{N}, I::Vararg{Int, N}) where {N} index = map(a.indices, I) do r, i return r[i] end - return NamedTensorCartesianIndex(index) + return NamedCartesianIndex(index) end -function unnamed(I::NamedTensorCartesianIndices) +function unnamed(I::NamedCartesianIndices) return CartesianIndices(unnamed.(I.indices)) end -# Iterating yields `NamedTensorCartesianIndex`es. The generic `AbstractNamedTensor` +# Iterating yields `NamedCartesianIndex`es. The generic `AbstractNamedTensor` # iteration forwards to `unnamed`, which here is a plain `CartesianIndices`, so # convert each parent index back through `getindex`. -function Base.iterate(I::NamedTensorCartesianIndices, state...) +function Base.iterate(I::NamedCartesianIndices, state...) y = iterate(unnamed(I), state...) isnothing(y) && return nothing cartesian, next_state = y @@ -852,7 +852,7 @@ function Base.eachindex( all(a -> issetequal(names(a1), names(a)), a_rest) || throw(NameMismatch("Dimension name mismatch $(names.((a1, a_rest...))).")) # TODO: Check the shapes match. - return NamedTensorCartesianIndices(axes(a1)) + return NamedCartesianIndices(axes(a1)) end # `unname` (eager), not `unnamed` (lazy view): reducing over a lazy permuted view @@ -947,7 +947,7 @@ function Base.to_indices(a::AbstractNamedTensor, I::Tuple{Pair, Vararg{Pair}}) return map((i, name) -> name[i], last.(I), inds) end -function Base.to_indices(a::AbstractNamedTensor, I::Tuple{NamedTensorCartesianIndex}) +function Base.to_indices(a::AbstractNamedTensor, I::Tuple{NamedCartesianIndex}) return to_indices(a, Tuple(only(I))) end @@ -989,7 +989,7 @@ function Base.setindex!( setindex!(a, value, to_indices(a, (I1, Irest...))...) return a end -function Base.setindex!(a::AbstractNamedTensor, value, I::NamedTensorCartesianIndex) +function Base.setindex!(a::AbstractNamedTensor, value, I::NamedCartesianIndex) setindex!(a, value, to_indices(a, (I,))...) return a end diff --git a/test/test_namedtensor_basics.jl b/test/test_namedtensor_basics.jl index 7dc7c575..5ac77b1e 100644 --- a/test/test_namedtensor_basics.jl +++ b/test/test_namedtensor_basics.jl @@ -1,6 +1,6 @@ using Combinatorics: Combinatorics -using ITensorBase: @names, AbstractNamedTensor, Name, NameMismatch, Named, NamedOneTo, - NamedTensor, NamedTensorCartesianIndex, NamedTensorCartesianIndices, NamedUnitRange, +using ITensorBase: @names, AbstractNamedTensor, Name, NameMismatch, Named, + NamedCartesianIndex, NamedCartesianIndices, NamedOneTo, NamedTensor, NamedUnitRange, align, aligned, apply, dim, dims, inds, isnamed, name, names, nametype, product, rename, setnames, unname, unnamed, unnamedtype using LinearAlgebra: LinearAlgebra @@ -317,10 +317,10 @@ end nb = NamedTensor(randn(elt, 3, 2), (:j, :i)) nc = zeros(elt, Named.((2, 3), (:i, :j))) Is = eachindex(na, nb) - @test Is isa NamedTensorCartesianIndices{2} + @test Is isa NamedCartesianIndices{2} @test issetequal(Is.indices, (NamedUnitRange(1:2, :i), NamedUnitRange(1:3, :j))) for I in Is - @test I isa NamedTensorCartesianIndex{2} + @test I isa NamedCartesianIndex{2} @test issetequal(name.(Tuple(I)), (:i, :j)) nc[I] = na[I] + nb[I] end From 60e91b3ad63ab53b9ac1702d8ec597017443bcc1 Mon Sep 17 00:00:00 2001 From: Matthew Fishman Date: Sun, 27 Sep 2026 12:34:29 -0400 Subject: [PATCH 5/7] Check the codomain/domain bipartition on construction `NamedTensor(array, codomain_inds, domain_inds)` now rejects a bipartition the array contradicts, which `TensorAlgebra.has_bipartition` makes possible to check. Raises the floors to TensorAlgebra 0.21.1 and GradedArrays 0.16.5. Co-Authored-By: Claude Opus 5 (1M context) --- Project.toml | 4 +- docs/Project.toml | 2 +- docs/src/dev_interface.md | 2 +- src/namedtensor.jl | 159 +++++++++++++++++++---------------- test/Project.toml | 4 +- test/test_basics.jl | 1 + test/test_gradedarraysext.jl | 5 ++ 7 files changed, 99 insertions(+), 78 deletions(-) diff --git a/Project.toml b/Project.toml index d8db8390..8989a4ab 100644 --- a/Project.toml +++ b/Project.toml @@ -44,14 +44,14 @@ Adapt = "4.1.1" ArrayLayouts = "1.11" Combinatorics = "1" ConstructionBase = "1.6" -GradedArrays = "0.16.4" +GradedArrays = "0.16.5" LinearAlgebra = "1.10" MatrixAlgebraKit = "0.2, 0.3, 0.4, 0.5, 0.6" Mooncake = "0.4.202, 0.5" OMEinsumContractionOrders = "1.3" Random = "1.10" SimpleTraits = "0.9.4" -TensorAlgebra = "0.21" +TensorAlgebra = "0.21.1" TensorKit = "0.17" TensorKitSectors = "0.3.9" TermInterface = "2" diff --git a/docs/Project.toml b/docs/Project.toml index cc54569c..132948e4 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -16,5 +16,5 @@ ITensorBase = "0.15" ITensorFormatter = "0.2.27" Literate = "2" MatrixAlgebraKit = "0.2, 0.3, 0.4, 0.5, 0.6" -TensorAlgebra = "0.21" +TensorAlgebra = "0.21.1" Test = "1.10" diff --git a/docs/src/dev_interface.md b/docs/src/dev_interface.md index 72e6eb0c..97ac54eb 100644 --- a/docs/src/dev_interface.md +++ b/docs/src/dev_interface.md @@ -14,7 +14,7 @@ A concrete tensor type subtypes [`AbstractNamedTensor`](@ref). [`NamedTensor`](@ is the built-in implementation, and [`ITensor`](@ref) is the `NamedTensor` with dimension names that are [`IndexName`](@ref)s. Its `NamedTensor(array, names)` constructor pairs an array of any kind with its dimension names directly, and a name given as an index also asserts that -dimension's space. A second form, `NamedTensor(array, codomain_names, domain_names)`, splits the +dimension's space. A second form, `NamedTensor(array, codomain_inds, domain_inds)`, splits the dimensions into a codomain and a domain group, as a map from the domain to the codomain. User code usually builds one by calling an array constructor on indices or by indexing an array (see [Constructors](@ref)) rather than calling it. The underlying diff --git a/src/namedtensor.jl b/src/namedtensor.jl index f1a23a3a..4c78d9b4 100644 --- a/src/namedtensor.jl +++ b/src/namedtensor.jl @@ -14,7 +14,7 @@ an [`Index`](@ref)). An index also asserts a space, which has to match the array axis, duality included, and an `ArgumentError` is thrown if it does not. A plain name asserts nothing, so the array's axis stands. -See also the `NamedTensor(unnamed, codomain_names, domain_names)` method for the map-shaped +See also the `NamedTensor(unnamed, codomain_inds, domain_inds)` method for the map-shaped form. # Examples @@ -45,96 +45,114 @@ struct NamedTensor{DimName} <: AbstractNamedTensor{DimName} end end -# A dimension given as an index asserts a space, so it has to agree with the array's axis; a -# bare name asserts nothing, so only the index case is checked. The comparison is on the -# underlying ranges (`space`) rather than on the indices, because `==` on an `Index` ignores -# duality and would pass a dual/non-dual mismatch. -function checkspaces(unnamed, names) - # A count mismatch is the inner constructor's error to report, so skip rather than compare - # against a padded axis. - length(names) == TensorAlgebra.ndims(unnamed) || return nothing - for (d, n) in enumerate(names) - checkspace(unnamed, d, n, identity) - end - return nothing -end - -# Codomain/domain form: the domain names are given codomain-facing while the storage holds them -# dualized (the convention of `TensorAlgebra.similar_map` and `TensorAlgebra.unmatricize`), so a -# domain index asserts the dual of its own space. -function checkspaces(unnamed, codomain_names, domain_names) - ncodomain = length(codomain_names) - # A count mismatch is the inner constructor's error to report, so skip rather than compare - # against a padded axis. - ncodomain + length(domain_names) == TensorAlgebra.ndims(unnamed) || return nothing - for (d, n) in enumerate(codomain_names) - checkspace(unnamed, d, n, identity) - end - for (d, n) in enumerate(domain_names) - checkspace(unnamed, ncodomain + d, n, conj) - end - return nothing -end - -# `dualize` maps a dimension's space to the space the storage holds at that position (`identity` -# in the codomain, `conj` in the domain). It is taken as a function rather than as a precomputed -# space because `space` is only defined once `n` is known to be an index. -function checkspace(unnamed, d, n, dualize) - n isa NamedUnitRange || return nothing - expected = dualize(space(n)) - ax = TensorAlgebra.axes(unnamed, d) - ax == expected && return nothing - asserted = if dualize === identity - "whose space $(expected)" - else - "whose space dualized for its domain position, $(expected)," - end +# A dimension given as an index asserts a space, so it has to agree with the corresponding axis; +# a bare name asserts nothing, so only the index case is checked. The comparison is on the +# underlying range (`space`) rather than on the index, because `==` on an `Index` ignores duality +# and would pass a dual/non-dual mismatch. +checkspace(ax, n) = nothing +function checkspace(ax, n::NamedUnitRange) + ax == space(n) && return nothing throw( ArgumentError( - "Dimension $(d) was given the index $(n), $(asserted) does not match the \ + "The index $(n) asserts the space $(space(n)), which does not match the \ corresponding axis $(ax) of the array." ) ) end -# A lone index is ambiguous as a group of dimensions (a `NamedUnitRange` is itself an iterable -# of its range values), so it is rejected rather than splatted into its elements. -function checknotindex(names) - names isa NamedUnitRange && throw( +# A `NamedUnitRange` is itself an iterable of its range values, so a lone index passed where the +# dimension names were expected would splat into integers rather than name one dimension. +checknotind(names) = nothing +function checknotind(names::NamedUnitRange) + throw( ArgumentError( "Got a single index (`NamedUnitRange` such as `Index`) as the dimension names. \ Pass a tuple or vector, e.g. `ITensor(array, (i, j))`." ) ) +end + +# `TensorAlgebra.ndims_codomain` defaults to `ndims`, so a plain `Array` reports all-codomain and +# an arity assertion on its own would reject the ordinary dense case. `has_bipartition` is what +# separates a bipartition the storage genuinely carries from that default, so the claimed one is +# only checkable against storage that says it has one. +function checkbipartition(unnamed, codomain_inds, domain_inds) + TensorAlgebra.has_bipartition(unnamed) || return nothing + ncodomain = TensorAlgebra.ndims_codomain(unnamed) + ncodomain == length(codomain_inds) && return nothing + throw( + ArgumentError( + "Got $(length(codomain_inds)) codomain and $(length(domain_inds)) domain \ + dimensions, but the array is a map from $(TensorAlgebra.ndims_domain(unnamed)) \ + dimensions to $(ncodomain)." + ) + ) +end + +# Each dimension is checked against the axis the storage holds at its position. `zip` stops at the +# shorter of its arguments, so a count mismatch checks fewer dimensions rather than running off +# the end, leaving the arity to the inner constructor to report. +function checkspaces(unnamed, names) + foreach(checkspace, TensorAlgebra.axes(unnamed), names) + return nothing +end +# The storage holds the domain axes dualized (the convention of `TensorAlgebra.similar_map` and +# `TensorAlgebra.unmatricize`) while the domain inds are given codomain-facing, so `conj` puts +# that half back in the form the inds are written in. It is a no-op on a dense axis. +function checkspaces(unnamed, codomain_inds, domain_inds) + ncodomain = length(codomain_inds) + axes = TensorAlgebra.axes(unnamed) + foreach(checkspace, Iterators.take(axes, ncodomain), codomain_inds) + foreach(checkspace, Iterators.map(conj, Iterators.drop(axes, ncodomain)), domain_inds) + return nothing +end + +# The constructors' input check, keyed on the constructor the way TensorAlgebra keys its other +# validation hooks (`check_input(unmatricize, m, axes_codomain, axes_domain)`), and taking the +# constructor's own arguments. +function TensorAlgebra.check_input(::Type{<:NamedTensor}, unnamed, names) + checknotind(names) + checkspaces(unnamed, names) + return nothing +end +function TensorAlgebra.check_input( + ::Type{<:NamedTensor}, + unnamed, + codomain_inds, + domain_inds + ) + checknotind(codomain_inds) + checknotind(domain_inds) + checkbipartition(unnamed, codomain_inds, domain_inds) + checkspaces(unnamed, codomain_inds, domain_inds) return nothing end # `names` can hold plain names or indices (`NamedUnitRange`s such as `Index`): `name` maps an # index to its name and is the identity on a plain name, so only an index's name is stored (the -# array carries the axes), after `checkspaces` has checked that the space it asserts agrees. -# The methods below repeat this normalization rather than delegating to one another, so each -# strips names exactly once. +# array carries the axes), after `check_input` has checked that the space it asserts agrees. function NamedTensor{DimName}(unnamed, names) where {DimName} - checknotindex(names) - checkspaces(unnamed, names) + TensorAlgebra.check_input(NamedTensor, unnamed, names) return _NamedTensor(unnamed, collect(DimName, name.(names))) end # The dimension-name type is inferred from the names, so indices infer `IndexName`, not their type. function NamedTensor(unnamed, names) - checknotindex(names) - checkspaces(unnamed, names) + TensorAlgebra.check_input(NamedTensor, unnamed, names) return _NamedTensor(unnamed, collect(name.(names))) end """ - NamedTensor(unnamed, codomain_names, domain_names) + NamedTensor(unnamed, codomain_inds, domain_inds) A tensor whose dimensions are split into a codomain group and a domain group, as a map from the domain to the codomain. The storage holds the codomain dimensions first and the domain -dimensions last. `codomain_names` and `domain_names` hold names or indices, and the domain -indices are given codomain-facing: an index `n` in `domain_names` asserts that the storage's -axis is the dual `conj(space(n))`, matching how `TensorAlgebra.similar_map` and -`TensorAlgebra.unmatricize` build map-shaped storage. +dimensions last. `codomain_inds` and `domain_inds` hold indices or plain names, and the domain +indices are given codomain-facing: the storage holds the domain axes dualized, following +`TensorAlgebra.similar_map` and `TensorAlgebra.unmatricize`, so an index in `domain_inds` asserts +the undualized space. + +When the array carries a bipartition of its own, the claimed one has to agree with it. Dense +storage carries none, so any bipartition may be claimed over it. # Examples @@ -148,20 +166,17 @@ NamedOneTo(2, :i)×NamedOneTo(3, :j) NamedTensor{Symbol}: 0.0 0.0 0.0 ``` """ -function NamedTensor(unnamed, codomain_names, domain_names) - checknotindex(codomain_names) - checknotindex(domain_names) - checkspaces(unnamed, codomain_names, domain_names) +function NamedTensor(unnamed, codomain_inds, domain_inds) + TensorAlgebra.check_input(NamedTensor, unnamed, codomain_inds, domain_inds) return _NamedTensor( - unnamed, collect((name.(codomain_names)..., name.(domain_names)...)) + unnamed, + collect((name.(codomain_inds)..., name.(domain_inds)...)) ) end -function NamedTensor{DimName}(unnamed, codomain_names, domain_names) where {DimName} - checknotindex(codomain_names) - checknotindex(domain_names) - checkspaces(unnamed, codomain_names, domain_names) +function NamedTensor{DimName}(unnamed, codomain_inds, domain_inds) where {DimName} + TensorAlgebra.check_input(NamedTensor, unnamed, codomain_inds, domain_inds) return _NamedTensor( - unnamed, collect(DimName, (name.(codomain_names)..., name.(domain_names)...)) + unnamed, collect(DimName, (name.(codomain_inds)..., name.(domain_inds)...)) ) end NamedTensor(a::AbstractNamedTensor, inds) = throw(ArgumentError("Already named.")) diff --git a/test/Project.toml b/test/Project.toml index daa35d13..df91a543 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -32,7 +32,7 @@ AbstractTrees = "0.4.5" Adapt = "4" Aqua = "0.8.9" Combinatorics = "1" -GradedArrays = "0.16.4" +GradedArrays = "0.16.5" ITensorBase = "0.15" ITensorPkgSkeleton = "0.3.42" JLArrays = "0.2, 0.3" @@ -44,7 +44,7 @@ Random = "1.10" SafeTestsets = "0.1" StableRNGs = "1" Suppressor = "0.2" -TensorAlgebra = "0.21" +TensorAlgebra = "0.21.1" TensorKit = "0.17" TensorKitSectors = "0.3.9" TermInterface = "2" diff --git a/test/test_basics.jl b/test/test_basics.jl index f32d0de9..f5069243 100644 --- a/test/test_basics.jl +++ b/test/test_basics.jl @@ -143,6 +143,7 @@ using UUIDs: UUID # space check on each group and the same rejection of a lone index. @test NamedTensor(randn(elt, 2, 3), (i,), (j,)) isa ITensor @test names(ITensor(randn(elt, 2, 3), (i,), (j,))) == name.([i, j]) + # Dense storage carries no split of its own, so either grouping is accepted. @test ITensor(randn(elt, 2, 3), (), (i, j)) isa ITensor @test_throws ArgumentError ITensor(randn(elt, 2, 3), i, (j,)) @test_throws ArgumentError ITensor(randn(elt, 2, 3), (i,), j) diff --git a/test/test_gradedarraysext.jl b/test/test_gradedarraysext.jl index f199e3db..13eb0ad5 100644 --- a/test/test_gradedarraysext.jl +++ b/test/test_gradedarraysext.jl @@ -181,4 +181,9 @@ end m = unnamed(randn(rng, (i,), (j,))) @test ITensor(m, (i,), (j,)) isa ITensor @test_throws ArgumentError ITensor(m, (i,), (dual(j),)) + # A graded array carries its own codomain/domain split, so a claimed split that disagrees + # with it is rejected even when every space matches: `m` is a map of one dimension to one. + @test_throws ArgumentError ITensor(m, (i, dual(j)), ()) + # Naming the dimensions flat claims no split, so it stays available. + @test ITensor(m, (i, dual(j))) isa ITensor end From 465da989789c57031d291a9c9149819350350d31 Mon Sep 17 00:00:00 2001 From: Matthew Fishman Date: Sun, 27 Sep 2026 13:11:30 -0400 Subject: [PATCH 6/7] Make named broadcasting linear-only A broadcast that is not a sum, a scalar multiple or `conj` now throws rather than falling back to a generic element-wise broadcast over the unnamed operands. `map` broadcasts, so it goes the same way. Co-Authored-By: Claude Opus 5 (1M context) --- docs/src/user_interface.md | 6 ++++-- src/broadcast.jl | 30 +++++++++--------------------- test/test_namedtensor_basics.jl | 15 +++++++++++++++ test/test_tensorkitext.jl | 6 +++--- 4 files changed, 31 insertions(+), 26 deletions(-) diff --git a/docs/src/user_interface.md b/docs/src/user_interface.md index 273e4fa9..c1b073f6 100644 --- a/docs/src/user_interface.md +++ b/docs/src/user_interface.md @@ -96,8 +96,10 @@ intermediates: 2 .* a .+ 3 .* c ``` -Non-linear broadcasting (functions of one or more tensors, such as `sin.(a)` or `a .^ 2`) is -experimental and incompletely supported, and is subject to change. +Broadcasting is linear-only. A sum of tensors, a scalar multiple and `conj` are supported, +and anything else throws, including `sin.(a)`, `a .^ 2`, `a .* b` and the scalar shift `a .+ 1`. +The same applies to `map`, which broadcasts. To apply a general function, unname the tensor, +broadcast over the array, and name the result. ## Factorizations diff --git a/src/broadcast.jl b/src/broadcast.jl index 73dff0bd..aabea2e7 100644 --- a/src/broadcast.jl +++ b/src/broadcast.jl @@ -26,6 +26,9 @@ BC.broadcastable(a::AbstractNamedTensor) = a # never wraps an `Add`, and the no-permutation recursion below never reaches one.) unnamed_linear(a::TA.LinearBroadcasted, nms) = unnamed_linear(a) unnamed_linear(a::TA.AddBroadcasted, nms) = unnamed_linear_aligned(a, nms) +# `identity.(a)` folds to the operand itself, and the output names are taken from it, so it needs +# no alignment. The aligned path below already had this case. +unnamed_linear(a::AbstractNamedTensor, nms) = unnamed_linear(a) # No permutation: strip names down the expression tree via the `operation`/`arguments` term interface. function unnamed_linear(a::TA.LinearBroadcasted) @@ -46,17 +49,6 @@ function unnamed_linear_aligned(a::AbstractNamedTensor, nms) end unnamed_linear_aligned(a::Number, nms) = a -# Non-linear fallback: unname a general `Broadcasted` by aligning each operand to `nms`, so Base's -# generic broadcast can run (all-codomain output). Only the linear path preserves the split. -unnamed_broadcasted(x::Number, nms) = x -function unnamed_broadcasted(a::AbstractNamedTensor, nms) - # An operand already aligned to `nms` needs no permutation, skipping the identity wrapper. - names(a) == nms && return unnamed(a) - return _broadcast_permuteddims(unnamed(a), getperm(names(a), nms)) -end -function unnamed_broadcasted(bc::Broadcasted, nms) - return broadcasted(bc.f, Base.Fix2(unnamed_broadcasted, nms).(bc.args)...) -end # Broadcasting-only alignment: unlike the public `unnamed(a, nms)` (which returns a # `Base.PermutedDimsArray`, a full array), this wraps in `TensorAlgebra.PermutedDims`, which stores # the permutation in a field rather than a type parameter, so it builds cheaply and type-stably @@ -89,12 +81,11 @@ end # here keeps the flatten/unname/materialize below type-stable. A linear expression folds to a # `LinearBroadcasted` and materializes through `copy(lb)`, whose allocation (`similar(lb)`) is the # unnamed backend's own broadcast-style `similar`, so the result inherits the backend (dense, graded, -# ...) and `unnamed_linear` keeps a single scaled/conjugated operand's codomain/domain split. A -# non-linear expression falls back to unnaming the raw `Broadcasted` and Base's generic broadcast. +# ...) and `unnamed_linear` keeps a single scaled/conjugated operand's codomain/domain split. +# `flattenlinear` throws on anything else: named broadcasting is linear-only, since aligning +# operands by name is only meaningful for an expression the fold can rewrite. @noinline function _copy_unnamed(bc, nms) - lb = TA.tryflattenlinear(bc) - isnothing(lb) && return copy(unnamed_broadcasted(bc, nms)) - return copy(unnamed_linear(lb, nms)) + return copy(unnamed_linear(TA.flattenlinear(bc), nms)) end # `Base.Broadcast.materialize!` otherwise reconstructs the broadcast over `axes(dest)` and @@ -117,12 +108,9 @@ function Base.copyto!( return dest end -# Function barrier mirroring `_copy_unnamed`. In place, so every operand aligns to `dest`; non-linear -# falls back to Base's generic in-place broadcast. +# Function barrier mirroring `_copy_unnamed`. In place, so every operand aligns to `dest`. @noinline function _copyto_unnamed!(dest_unnamed, bc, nms) - lb = TA.tryflattenlinear(bc) - isnothing(lb) && return copyto!(dest_unnamed, unnamed_broadcasted(bc, nms)) - return copyto!(dest_unnamed, unnamed_linear_aligned(lb, nms)) + return copyto!(dest_unnamed, unnamed_linear_aligned(TA.flattenlinear(bc), nms)) end # Operator-preserving broadcasting. diff --git a/test/test_namedtensor_basics.jl b/test/test_namedtensor_basics.jl index 5ac77b1e..0c9b9515 100644 --- a/test/test_namedtensor_basics.jl +++ b/test/test_namedtensor_basics.jl @@ -342,6 +342,21 @@ end c = NamedTensor(Array{elt}(undef, 2, 3), (:i, :j)) c .= a .+ 2 .* b @test unname(c, (:i, :j)) ≈ unname(a, (:i, :j)) + 2 * unname(b, (:i, :j)) + # `identity.(a)` folds to the operand itself, which the out-of-place path also handles. + @test unname(identity.(a), (:i, :j)) ≈ unname(a, (:i, :j)) + + # Broadcasting is linear-only: a sum, a scalar multiple and `conj` fold, and everything + # else is rejected rather than falling back to a generic element-wise broadcast. `map` + # broadcasts, so it is rejected on the same expressions. + @test_throws ArgumentError a .* b + @test_throws ArgumentError a ./ b + @test_throws ArgumentError sqrt.(abs.(a)) + # A scalar shift is affine rather than linear, so it goes too. + @test_throws ArgumentError a .+ 1 + @test_throws ArgumentError map(sqrt, a) + @test_throws ArgumentError map(*, a, b) + c = NamedTensor(Array{elt}(undef, 2, 3), (:i, :j)) + @test_throws ArgumentError c .= a .* b # Regression test for proper permutations. a = NamedTensor(randn(elt, 2, 3, 4), (:i, :j, :k)) diff --git a/test/test_tensorkitext.jl b/test/test_tensorkitext.jl index 1ea09985..f179661c 100644 --- a/test/test_tensorkitext.jl +++ b/test/test_tensorkitext.jl @@ -66,13 +66,13 @@ using Test: @test, @test_throws, @testset @test TK.space(ref) == TK.space(gc) @test ref ≈ gc - # Linear-combination broadcast lowers to `bipermutedimsopadd!`; a non-linear element-wise - # `f.(a)` on a graded tensor errors (graded broadcasting is linear-only). + # Linear-combination broadcast lowers to `bipermutedimsopadd!`. A non-linear element-wise + # `f.(a)` is rejected, since named broadcasting is linear-only. b2 = randn(rng, elt, i, j) @test unnamed(a + b2) ≈ unnamed(a) + unnamed(b2) @test unnamed(2 * a) ≈ 2 * unnamed(a) @test unnamed(a .- 3 .* b2) ≈ unnamed(a) - 3 * unnamed(b2) - @test_throws ErrorException sin.(a) + @test_throws ArgumentError sin.(a) # Named broadcasting aligns operands by name within their codomain/domain split, so a within-split # reorder of a multi-leg operand still adds correctly (compared at a common split via `align`). From ef7fdd70807e5b0efb745371122c8fff3d0e7a57 Mon Sep 17 00:00:00 2001 From: Matthew Fishman Date: Sun, 27 Sep 2026 13:11:44 -0400 Subject: [PATCH 7/7] Move the construction invariants into check_input `check_input` is now the whole argument contract for building a `NamedTensor`. The dimension count and the distinctness of names had been checked in the inner constructor instead, so the contract read in two places. Co-Authored-By: Claude Opus 5 (1M context) --- src/namedtensor.jl | 36 ++++++++++++++++++++++++++---------- 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/src/namedtensor.jl b/src/namedtensor.jl index 4c78d9b4..8dd90eff 100644 --- a/src/namedtensor.jl +++ b/src/namedtensor.jl @@ -33,14 +33,11 @@ struct NamedTensor{DimName} <: AbstractNamedTensor{DimName} # `axes`/algebra interface) can be the parent directly. See the TensorKit extension. unnamed::Any names::Vector{DimName} - # The sole inner constructor: enforces the representation invariants (one name per dimension, - # names distinct) on already-collected names. The outer constructors below normalize the - # inputs (strip index names, fix the eltype) and funnel through here. + # The sole inner constructor, and unchecked: it takes already-collected names and wraps them. + # Validating the arguments is `TensorAlgebra.check_input`'s job, which every outer constructor + # below calls before normalizing the inputs (stripping index names, fixing the eltype) and + # funnelling through here, so the whole contract reads in one place. global function _NamedTensor(unnamed, names::Vector{DimName}) where {DimName} - TensorAlgebra.ndims(unnamed) == length(names) || - throw(ArgumentError("Number of named dims must match ndims.")) - allunique(names) || - throw(ArgumentError("Dimension names must be distinct, got $(names).")) return new{DimName}(unnamed, names) end end @@ -60,6 +57,22 @@ function checkspace(ax, n::NamedUnitRange) ) end +# One name per dimension, and no name used twice. `name` is the identity on a plain name, so +# these compare what actually gets stored: an index and its own bare name collide. +function checkndims(unnamed, nnames) + TensorAlgebra.ndims(unnamed) == nnames || + throw(ArgumentError("Number of named dims must match ndims.")) + return nothing +end +function checkdistinct(names) + allunique(Iterators.map(name, names)) || throw( + ArgumentError( + "Dimension names must be distinct, got $(collect(Iterators.map(name, names)))." + ) + ) + return nothing +end + # A `NamedUnitRange` is itself an iterable of its range values, so a lone index passed where the # dimension names were expected would splat into integers rather than name one dimension. checknotind(names) = nothing @@ -89,9 +102,8 @@ function checkbipartition(unnamed, codomain_inds, domain_inds) ) end -# Each dimension is checked against the axis the storage holds at its position. `zip` stops at the -# shorter of its arguments, so a count mismatch checks fewer dimensions rather than running off -# the end, leaving the arity to the inner constructor to report. +# Each dimension is checked against the axis the storage holds at its position. `checkndims` has +# already established that the counts agree, so these walk every dimension. function checkspaces(unnamed, names) foreach(checkspace, TensorAlgebra.axes(unnamed), names) return nothing @@ -112,6 +124,8 @@ end # constructor's own arguments. function TensorAlgebra.check_input(::Type{<:NamedTensor}, unnamed, names) checknotind(names) + checkndims(unnamed, length(names)) + checkdistinct(names) checkspaces(unnamed, names) return nothing end @@ -123,6 +137,8 @@ function TensorAlgebra.check_input( ) checknotind(codomain_inds) checknotind(domain_inds) + checkndims(unnamed, length(codomain_inds) + length(domain_inds)) + checkdistinct(Iterators.flatten((codomain_inds, domain_inds))) checkbipartition(unnamed, codomain_inds, domain_inds) checkspaces(unnamed, codomain_inds, domain_inds) return nothing