From 7c520a59d6e09793928c6fffee8d4c524ed850b9 Mon Sep 17 00:00:00 2001 From: Matthew Fishman Date: Mon, 29 Jun 2026 10:13:18 -0400 Subject: [PATCH] Make NamedTensor the primary tensor type, ITensor an IndexName alias Make the name-generic types the primary tensor types and turn the `ITensor` family into `IndexName`-specialized aliases, mirroring how `Index` is already `NamedUnitRange{IndexName}`. `AbstractITensor{DimName}` becomes `AbstractNamedTensor{DimName}` and `ITensor{DimName}` becomes `NamedTensor{DimName}`, with `const AbstractITensor = AbstractNamedTensor{IndexName}` and `const ITensor = NamedTensor{IndexName}`. `ITensorOperator`, `LazyITensor`, and `SymbolicITensor` are renamed to their `NamedTensor*` counterparts, each keeping the old name as an `IndexName` alias. The internal broadcast-style and layout helpers take the `NamedTensor*` prefix without an alias, and the source files are renamed to match the new type names. `NamedTensor`, `AbstractNamedTensor`, and the `ITensor`-family aliases are all exported, so existing `ITensor` / `AbstractITensor` / `Index` code and signatures keep working, and `show` still prints an `IndexName` tensor as `ITensor`. `IndexName`, the dimension-name type carried by `Index` and behind the `ITensor` aliases, is now `public` with a docstring. This is breaking: `ITensor` is now `NamedTensor{IndexName}`, so a tensor with non-`IndexName` dimension names has to be built through `NamedTensor` or `nameddims` rather than `ITensor`. The ITensorNetworksNext layer will follow the same flip in a later PR, dispatching on `AbstractNamedTensor` and renaming its network hierarchy to name-generic primaries with `IndexName` aliases. Co-Authored-By: Claude Opus 4.8 (1M context) --- Project.toml | 2 +- docs/make.jl | 6 +- docs/src/dev_interface.md | 17 +- .../ITensorBaseAdaptExt.jl | 4 +- .../ITensorBaseBlockArraysExt.jl | 18 +- .../ITensorBaseMooncakeExt.jl | 10 +- src/ITensorBase.jl | 15 +- src/abstractnamedarray.jl | 2 +- ...tractitensor.jl => abstractnamedtensor.jl} | 378 ++++++++++-------- src/broadcast.jl | 85 ++-- src/index.jl | 40 +- src/itensor.jl | 57 --- src/lazyitensors/evaluation_order.jl | 8 +- src/lazyitensors/itensorbaseextensions.jl | 10 +- src/lazyitensors/lazybroadcast.jl | 20 +- src/lazyitensors/lazyinterface.jl | 4 +- src/lazyitensors/lazyitensor.jl | 109 ++--- src/lazyitensors/symbolicitensor.jl | 47 ++- src/linearalgebra.jl | 8 +- src/named.jl | 2 +- src/namedtensor.jl | 58 +++ ...nsoroperator.jl => namedtensoroperator.jl} | 106 ++--- src/quirks.jl | 2 +- src/tensoralgebra.jl | 108 ++--- test/test_basics.jl | 20 +- test/test_exports.jl | 6 +- test/test_lazyitensors.jl | 24 +- test/test_mooncakeext.jl | 4 +- test/test_nameddims_basics.jl | 49 +-- test/test_operator.jl | 50 +-- test/test_vectorinterface.jl | 2 +- 31 files changed, 678 insertions(+), 593 deletions(-) rename src/{abstractitensor.jl => abstractnamedtensor.jl} (71%) delete mode 100644 src/itensor.jl create mode 100644 src/namedtensor.jl rename src/{itensoroperator.jl => namedtensoroperator.jl} (82%) diff --git a/Project.toml b/Project.toml index 12115db5..b34ba28d 100644 --- a/Project.toml +++ b/Project.toml @@ -1,6 +1,6 @@ name = "ITensorBase" uuid = "4795dd04-0d67-49bb-8f44-b89c448a1dc7" -version = "0.9.0-DEV" +version = "0.9.0" authors = ["ITensor developers and contributors"] [workspace] diff --git a/docs/make.jl b/docs/make.jl index 45334c07..ae95e9c7 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -3,9 +3,9 @@ using ITensorBase using ITensorFormatter: ITensorFormatter # `using ITensorBase` (rather than `using ITensorBase: ITensorBase`) binds the exported -# names in `Main`. The tensor `show` qualifies the element type relative to the active -# module, so without it the doctests and `@example` blocks render `ITensorBase.ITensor` -# instead of `ITensor`. +# names in `Main`. The tensor `show` qualifies the type relative to the active module, so +# without it the doctests and `@example` blocks render `ITensorBase.NamedTensor` instead +# of `NamedTensor`. DocMeta.setdocmeta!(ITensorBase, :DocTestSetup, :(using ITensorBase); recursive = true) ITensorFormatter.make_index!(pkgdir(ITensorBase)) diff --git a/docs/src/dev_interface.md b/docs/src/dev_interface.md index bd2254cf..7346fb7b 100644 --- a/docs/src/dev_interface.md +++ b/docs/src/dev_interface.md @@ -10,16 +10,17 @@ stable user-facing API. For the stable user-facing API, see the [User Interface] ## Named array types -A concrete tensor type subtypes [`AbstractITensor`](@ref); [`ITensor`](@ref) is the built-in -dense implementation. Its `ITensor(array, dimnames)` constructor pairs an array with dimension -names directly; user code usually builds an `ITensor` 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). +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 +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). ```@docs; canonical=false -AbstractITensor -ITensor +AbstractNamedTensor +NamedTensor NamedUnitRange ``` diff --git a/ext/ITensorBaseAdaptExt/ITensorBaseAdaptExt.jl b/ext/ITensorBaseAdaptExt/ITensorBaseAdaptExt.jl index f874b33c..5b9d653a 100644 --- a/ext/ITensorBaseAdaptExt/ITensorBaseAdaptExt.jl +++ b/ext/ITensorBaseAdaptExt/ITensorBaseAdaptExt.jl @@ -1,9 +1,9 @@ module ITensorBaseAdaptExt using Adapt: Adapt, adapt -using ITensorBase: AbstractITensor, dimnames, nameddims, unnamed +using ITensorBase: AbstractNamedTensor, dimnames, nameddims, unnamed -function Adapt.adapt_structure(to, a::AbstractITensor) +function Adapt.adapt_structure(to, a::AbstractNamedTensor) return nameddims(adapt(to, unnamed(a)), dimnames(a)) end diff --git a/ext/ITensorBaseBlockArraysExt/ITensorBaseBlockArraysExt.jl b/ext/ITensorBaseBlockArraysExt/ITensorBaseBlockArraysExt.jl index ac926f83..743d407e 100644 --- a/ext/ITensorBaseBlockArraysExt/ITensorBaseBlockArraysExt.jl +++ b/ext/ITensorBaseBlockArraysExt/ITensorBaseBlockArraysExt.jl @@ -1,7 +1,7 @@ module ITensorBaseBlockArraysExt using ArrayLayouts: ArrayLayouts using BlockArrays: Block, BlockRange -using ITensorBase: AbstractITensor, NamedUnitRange, getindex_named, view_nameddims +using ITensorBase: AbstractNamedTensor, NamedUnitRange, getindex_named, view_nameddims # These methods disambiguate named-range block indexing from `BlockArrays`' generic # `AbstractArray` block-indexing methods. @@ -17,39 +17,39 @@ end const BlockIndex{N} = Union{Block{N}, BlockRange{N}, AbstractVector{<:Block{N}}} -function Base.view(a::AbstractITensor, I1::Block{1}, Irest::BlockIndex{1}...) +function Base.view(a::AbstractNamedTensor, I1::Block{1}, Irest::BlockIndex{1}...) # TODO: Use `Derive.@interface ITensorInterface() r[I]` instead. return view_nameddims(a, I1, Irest...) end -function Base.view(a::AbstractITensor, I::Block) +function Base.view(a::AbstractNamedTensor, I::Block) # TODO: Use `Derive.@interface ITensorInterface() r[I]` instead. return view_nameddims(a, Tuple(I)...) end -function Base.view(a::AbstractITensor, I1::BlockIndex{1}, Irest::BlockIndex{1}...) +function Base.view(a::AbstractNamedTensor, I1::BlockIndex{1}, Irest::BlockIndex{1}...) # TODO: Use `Derive.@interface ITensorInterface() r[I]` instead. return view_nameddims(a, I1, Irest...) end # Fix ambiguity error. function Base.getindex( - a::AbstractITensor, I1::BlockRange{1}, Irest::BlockRange{1}... + a::AbstractNamedTensor, I1::BlockRange{1}, Irest::BlockRange{1}... ) return ArrayLayouts.layout_getindex(a, I1, Irest...) end # Fix ambiguity errors. -function Base.getindex(a::AbstractITensor, I1::Block{1}, Irest...) +function Base.getindex(a::AbstractNamedTensor, I1::Block{1}, Irest...) return copy(view(a, I1, Irest...)) end -function Base.getindex(a::AbstractITensor, I1::AbstractVector, I2::Block{1}) +function Base.getindex(a::AbstractNamedTensor, I1::AbstractVector, I2::Block{1}) return copy(view(a, I1, I2)) end -function Base.getindex(a::AbstractITensor, I1::Block{1}, I2::AbstractVector) +function Base.getindex(a::AbstractNamedTensor, I1::Block{1}, I2::AbstractVector) return copy(view(a, I1, I2)) end -function Base.getindex(a::AbstractITensor, I::Block{N}) where {N} +function Base.getindex(a::AbstractNamedTensor, I::Block{N}) where {N} return copy(view(a, I)) end diff --git a/ext/ITensorBaseMooncakeExt/ITensorBaseMooncakeExt.jl b/ext/ITensorBaseMooncakeExt/ITensorBaseMooncakeExt.jl index aebc80fd..8b52725c 100644 --- a/ext/ITensorBaseMooncakeExt/ITensorBaseMooncakeExt.jl +++ b/ext/ITensorBaseMooncakeExt/ITensorBaseMooncakeExt.jl @@ -1,13 +1,13 @@ module ITensorBaseMooncakeExt -using ITensorBase: AbstractITensor, NamedUnitRange, dimnames, dimnames_setdiff, inds, name, - nameperm, to_inds, uniquename +using ITensorBase: AbstractNamedTensor, NamedUnitRange, dimnames, dimnames_setdiff, inds, + name, nameperm, 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(::ITensor)` returns the stored names `Vector` directly, so its output +# `dimnames(::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). @@ -20,9 +20,9 @@ Mooncake.tangent_type(::Type{<:NamedUnitRange}) = Mooncake.NoTangent @zero_derivative DefaultCtx Tuple{typeof(uniquename), Any, Any} @zero_derivative DefaultCtx Tuple{typeof(to_inds), Any, Any} -using ITensorBase: AbstractITensor, ITensor, unnamed +using ITensorBase: AbstractNamedTensor, NamedTensor, unnamed using Mooncake: Tangent -function Base.copyto!(dest::ITensor, src::Tangent) +function Base.copyto!(dest::NamedTensor, src::Tangent) # TODO: Account for the `inds` of the Tangent? In other words, is the tangent data # aligned with the `dest` data? copyto!(unnamed(dest), src.fields.parent) diff --git a/src/ITensorBase.jl b/src/ITensorBase.jl index 74499cae..72cb9b7f 100644 --- a/src/ITensorBase.jl +++ b/src/ITensorBase.jl @@ -1,11 +1,12 @@ module ITensorBase -export AbstractITensor, ITensor, Index, NamedUnitRange, aligndims, aligneddims, - apply, codomainnames, dimnames, dimnametype, domainnames, inds, named, - nameddims, noprime, operator, prime, similar_operator, state, uniquename +export AbstractNamedTensor, NamedTensor, AbstractITensor, ITensor, Index, + NamedUnitRange, aligndims, aligneddims, apply, codomainnames, dimnames, + dimnametype, domainnames, inds, named, nameddims, noprime, operator, prime, + similar_operator, state, uniquename using Compat: @compat @compat public @names -@compat public name, nametype, replacedimnames, setname, unnamed, unnamedtype +@compat public IndexName, name, nametype, replacedimnames, setname, unnamed, unnamedtype # Named-array machinery (relocated from NamedDimsArrays.jl). include("isnamed.jl") @@ -15,12 +16,12 @@ include("named.jl") include("abstractnamedarray.jl") include("namedarray.jl") include("namedunitrange.jl") -include("abstractitensor.jl") +include("abstractnamedtensor.jl") include("broadcast.jl") include("tensoralgebra.jl") include("linearalgebra.jl") -include("itensor.jl") -include("itensoroperator.jl") +include("namedtensor.jl") +include("namedtensoroperator.jl") # `IndexName` dimname flavor and the `Index` named unit range. include("sorteddict.jl") diff --git a/src/abstractnamedarray.jl b/src/abstractnamedarray.jl index 0bec3dab..acc37f1f 100644 --- a/src/abstractnamedarray.jl +++ b/src/abstractnamedarray.jl @@ -1,4 +1,4 @@ -# `Name` leads (matching `AbstractITensor{DimName}`); `UnnamedT` is the unwrapped +# `Name` leads (matching `AbstractNamedTensor{DimName}`); `UnnamedT` is the unwrapped # element type and `N` the rank. The element type is always `Named{Name, UnnamedT}`, # so it is hardcoded in the `AbstractArray` supertype rather than carried as a # parameter. The wrapped-container type lives only on the concrete subtypes. diff --git a/src/abstractitensor.jl b/src/abstractnamedtensor.jl similarity index 71% rename from src/abstractitensor.jl rename to src/abstractnamedtensor.jl index 37d0eb85..581fcb21 100644 --- a/src/abstractitensor.jl +++ b/src/abstractnamedtensor.jl @@ -3,33 +3,33 @@ using Random: Random using TensorAlgebra: TensorAlgebra, permuteddims, zero! # Some of the interface is inspired by: -# https://github.com/ITensor/ITensors.jl +# https://github.com/NamedTensor/ITensors.jl # https://github.com/invenia/NamedDims.jl # https://github.com/mcabbott/NamedPlus.jl # https://pytorch.org/docs/stable/named_tensor.html """ - AbstractITensor{DimName} + AbstractNamedTensor{DimName} Supertype for tensors whose dimensions are labeled by names of type `DimName` rather -than ordered by position. Subtypes such as [`ITensor`](@ref) line their dimensions up +than ordered by position. Subtypes such as [`NamedTensor`](@ref) line their dimensions up by name under contraction, addition, and indexing. Unlike an `AbstractArray`, the rank and element type live in the data rather than the type, so `ndims` and `eltype` are not fixed at the type level. -See also [`ITensor`](@ref), [`dimnames`](@ref), [`inds`](@ref). +See also [`NamedTensor`](@ref), [`dimnames`](@ref), [`inds`](@ref). """ -abstract type AbstractITensor{DimName} end +abstract type AbstractNamedTensor{DimName} end # Rank and element type live in the data, not the type, so the type-level `ndims` -# is `Any` (like `eltype(Array)`). `AbstractITensor` is not an `AbstractArray`: the +# is `Any` (like `eltype(Array)`). `AbstractNamedTensor` is not an `AbstractArray`: the # array-like surface it needs (indexing, broadcasting, arithmetic, iteration) is # supplied directly below rather than inherited. -Base.ndims(::Type{<:AbstractITensor}) = Any +Base.ndims(::Type{<:AbstractNamedTensor}) = Any """ - dimnames(a::AbstractITensor) - dimnames(a::AbstractITensor, dim::Int) + dimnames(a::AbstractNamedTensor) + dimnames(a::AbstractNamedTensor, dim::Int) The dimension names of `a`, as a collection in dimension order. The second form returns the name of dimension `dim`. @@ -51,18 +51,18 @@ julia> dimnames(a, 2) See also [`inds`](@ref), [`nameddims`](@ref). """ function dimnames end -dimnames(a::AbstractITensor) = throw(MethodError(dimnames, a)) -function dimnames(a::AbstractITensor, dim::Int) +dimnames(a::AbstractNamedTensor) = throw(MethodError(dimnames, a)) +function dimnames(a::AbstractNamedTensor, dim::Int) return dimnames(a)[dim] end """ - dimnametype(a::AbstractITensor) - dimnametype(type::Type{<:AbstractITensor}) + 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 `ITensor`) +type that does not fix its dimname flavor (such as the unparameterized `NamedTensor`) returns `Any`, the same way `eltype(Array)` is `Any`. # Examples @@ -78,18 +78,18 @@ Symbol ``` """ function dimnametype end -dimnametype(a::AbstractITensor) = dimnametype(typeof(a)) -dimnametype(type::Type{<:AbstractITensor}) = Any +dimnametype(a::AbstractNamedTensor) = dimnametype(typeof(a)) +dimnametype(type::Type{<:AbstractNamedTensor}) = Any # Unwrapping the names (named-array interface). # TODO: Use `IsNamed` trait? -unnamed(a::AbstractITensor) = throw(MethodError(unnamed, a)) -unnamed(a::AbstractITensor, inds) = unnamed(aligneddims(a, inds)) -unname(a::AbstractITensor, inds) = unnamed(aligndims(a, inds)) +unnamed(a::AbstractNamedTensor) = throw(MethodError(unnamed, a)) +unnamed(a::AbstractNamedTensor, inds) = unnamed(aligneddims(a, inds)) +unname(a::AbstractNamedTensor, inds) = unnamed(aligndims(a, inds)) """ - inds(a::AbstractITensor) - inds(a::AbstractITensor, dim::Int) + inds(a::AbstractNamedTensor) + inds(a::AbstractNamedTensor, dim::Int) 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 @@ -113,15 +113,15 @@ named(Base.OneTo(2), :i) ``` """ function inds end -inds(a::AbstractITensor) = collect(axes(a)) -inds(a::AbstractITensor, dim::Int) = axes(a)[dim] +inds(a::AbstractNamedTensor) = collect(axes(a)) +inds(a::AbstractNamedTensor, dim::Int) = axes(a)[dim] -isnamed(::Type{<:AbstractITensor}) = true +isnamed(::Type{<:AbstractNamedTensor}) = true -function dim(a::AbstractITensor, n) +function dim(a::AbstractNamedTensor, n) return findfirst(==(name(n)), dimnames(a)) end -dims(a::AbstractITensor, ns) = Base.Fix1(dim, a).(ns) +dims(a::AbstractNamedTensor, ns) = Base.Fix1(dim, a).(ns) dimname_isequal(x) = Base.Fix1(dimname_isequal, x) dimname_isequal(x, y) = isequal(x, y) @@ -140,7 +140,7 @@ dimname_isequal(r1, r2::NamedUnitRange) = r1 == name(r2) dimname_isequal(r1::NamedUnitRange, r2::Name) = name(r1) == name(r2) dimname_isequal(r1::Name, r2::NamedUnitRange) = name(r1) == name(r2) -function to_inds(a::AbstractITensor, dims) +function to_inds(a::AbstractNamedTensor, dims) is = Base.Fix1(dim, a).(name.(dims)) return Base.Fix1(inds, a).(is) end @@ -156,25 +156,25 @@ Construct a named dimensions array from an unnamed array `a` and named dimension ```jldoctest julia> nameddims(zeros(2, 3), (:i, :j)) -named(Base.OneTo(2), :i)×named(Base.OneTo(3), :j) ITensor{Symbol}: +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 [`ITensor`](@ref), [`named`](@ref). +See also [`NamedTensor`](@ref), [`named`](@ref). """ function nameddims(a::AbstractArray, inds) - return ITensor(a, inds) + return NamedTensor(a, inds) end #= - nameddimsof(a::AbstractITensor, b::AbstractArray) + nameddimsof(a::AbstractNamedTensor, b::AbstractArray) Construct a named dimensions array with the dimension names of `a` and with the data from `b`. =# -function nameddimsof(a::AbstractITensor, b::AbstractArray) +function nameddimsof(a::AbstractNamedTensor, b::AbstractArray) return nameddims(b, dimnames(a)) end @@ -198,50 +198,50 @@ function checked_indexin(x::AbstractUnitRange, y::AbstractUnitRange) return findfirst(==(first(x)), y):findfirst(==(last(x)), y) end -Base.copy(a::AbstractITensor) = nameddimsof(a, copy(unnamed(a))) -Base.zero(a::AbstractITensor) = nameddimsof(a, zero(unnamed(a))) +Base.copy(a::AbstractNamedTensor) = nameddimsof(a, copy(unnamed(a))) +Base.zero(a::AbstractNamedTensor) = nameddimsof(a, zero(unnamed(a))) # `CartesianIndices` of a named tensor is the parent's, via the named axes (as the # `AbstractArray` fallback did through `axes`). -Base.CartesianIndices(a::AbstractITensor) = CartesianIndices(axes(a)) +Base.CartesianIndices(a::AbstractNamedTensor) = CartesianIndices(axes(a)) # Forward `conj` to the underlying so that graded axes flip their sector arrows. # The default `AbstractArray` fallback would broadcast `conj` over elements without # touching the axes, which silently changes the contraction convention for tensors # with graded (dual-tagged) axes. -Base.conj(a::AbstractITensor) = nameddimsof(a, conj(unnamed(a))) +Base.conj(a::AbstractNamedTensor) = nameddimsof(a, conj(unnamed(a))) # `LinearAlgebra.normalize` infers result eltype via `typeof(first(a)/nrm)`, which # scalar-indexes block-structured storage. `a / norm(a, p)` already preserves names. -function LinearAlgebra.normalize(a::AbstractITensor, p::Real = 2) +function LinearAlgebra.normalize(a::AbstractNamedTensor, p::Real = 2) return a / LinearAlgebra.norm(a, p) end -function LinearAlgebra.normalize!(a::AbstractITensor, p::Real = 2) +function LinearAlgebra.normalize!(a::AbstractNamedTensor, p::Real = 2) LinearAlgebra.normalize!(unnamed(a), p) return a end # Elementwise and scalar arithmetic. `AbstractArray` routes these through # broadcasting; supply them directly now that the supertype is gone. -Base.:+(a1::AbstractITensor, a2::AbstractITensor) = a1 .+ a2 -Base.:-(a1::AbstractITensor, a2::AbstractITensor) = a1 .- a2 -Base.:-(a::AbstractITensor) = broadcast(-, a) -Base.:*(a::AbstractITensor, x::Number) = a .* x -Base.:*(x::Number, a::AbstractITensor) = x .* a -Base.:/(a::AbstractITensor, x::Number) = a ./ x +Base.:+(a1::AbstractNamedTensor, a2::AbstractNamedTensor) = a1 .+ a2 +Base.:-(a1::AbstractNamedTensor, a2::AbstractNamedTensor) = a1 .- a2 +Base.:-(a::AbstractNamedTensor) = broadcast(-, a) +Base.:*(a::AbstractNamedTensor, x::Number) = a .* x +Base.:*(x::Number, a::AbstractNamedTensor) = x .* a +Base.:/(a::AbstractNamedTensor, x::Number) = a ./ x # Forward `Random.randn!` / `Random.rand!` to the concrete storage so they # see the runtime eltype. -function Random.randn!(rng::Random.AbstractRNG, a::AbstractITensor) +function Random.randn!(rng::Random.AbstractRNG, a::AbstractNamedTensor) Random.randn!(rng, unnamed(a)) return a end -function Random.rand!(rng::Random.AbstractRNG, a::AbstractITensor) +function Random.rand!(rng::Random.AbstractRNG, a::AbstractNamedTensor) Random.rand!(rng, unnamed(a)) return a end -function Base.copyto!(a_dest::AbstractITensor, a_src::AbstractITensor) +function Base.copyto!(a_dest::AbstractNamedTensor, a_src::AbstractNamedTensor) a′_dest = unnamed(a_dest) # TODO: Use `unnamed` to do the permutations lazily. a′_src = unname(a_src, axes(a_dest)) @@ -264,98 +264,126 @@ end # These are defined since the Base versions assume the eltype and ndims are known # at compile time, which isn't true for ITensors. -Base.Array(a::AbstractITensor) = Array(unnamed(a)) -Base.Array{T}(a::AbstractITensor) where {T} = Array{T}(unnamed(a)) -Base.Array{T, N}(a::AbstractITensor) where {T, N} = Array{T, N}(unnamed(a)) -Base.AbstractArray{T}(a::AbstractITensor) where {T} = AbstractArray{T, ndims(a)}(a) -function Base.AbstractArray{T, N}(a::AbstractITensor) where {T, N} +Base.Array(a::AbstractNamedTensor) = Array(unnamed(a)) +Base.Array{T}(a::AbstractNamedTensor) where {T} = Array{T}(unnamed(a)) +Base.Array{T, N}(a::AbstractNamedTensor) where {T, N} = Array{T, N}(unnamed(a)) +Base.AbstractArray{T}(a::AbstractNamedTensor) where {T} = AbstractArray{T, ndims(a)}(a) +function Base.AbstractArray{T, N}(a::AbstractNamedTensor) where {T, N} dest = similar(a, T) copyto_axcheck!(unnamed(dest), unnamed(a)) return dest end -function Base.axes(a::AbstractITensor) +function Base.axes(a::AbstractNamedTensor) return named.(axes(unnamed(a)), Tuple(dimnames(a))) end -function Base.size(a::AbstractITensor) +function Base.size(a::AbstractNamedTensor) return length.(axes(a)) end -# An ITensor has no single name, so `length` is the plain element count. It is the +# An NamedTensor has no single name, so `length` is the plain element count. It is the # product of the (now plain `Int`) per-dimension sizes. -Base.length(a::AbstractITensor) = prod(size(a)) +Base.length(a::AbstractNamedTensor) = prod(size(a)) # Circumvent issue when ndims isn't known at compile time. -Base.axes(a::AbstractITensor, d) = axes(a)[d] +Base.axes(a::AbstractNamedTensor, d) = axes(a)[d] # Circumvent issue when ndims isn't known at compile time. -Base.size(a::AbstractITensor, d) = size(a)[d] +Base.size(a::AbstractNamedTensor, d) = size(a)[d] # Circumvent issue when ndims isn't known at compile time. -Base.ndims(a::AbstractITensor) = ndims(unnamed(a)) +Base.ndims(a::AbstractNamedTensor) = ndims(unnamed(a)) # Circumvent issue when eltype isn't known at compile time. -Base.eltype(a::AbstractITensor) = eltype(unnamed(a)) +Base.eltype(a::AbstractNamedTensor) = eltype(unnamed(a)) -# In-place zero of an ITensor, delegating to its unnamed parent array. -TensorAlgebra.zero!(a::AbstractITensor) = (zero!(unnamed(a)); a) +# In-place zero of an NamedTensor, delegating to its unnamed parent array. +TensorAlgebra.zero!(a::AbstractNamedTensor) = (zero!(unnamed(a)); a) # Name-aware `VectorInterface` methods so that ITensors can be used directly as the vectors # in iterative solvers such as `KrylovKit.eigsolve`, which drive their Krylov vectors through # `VectorInterface`; the generic `AbstractArray` fallbacks are not name-aware. The `!` methods # operate in place via broadcasting; each `!!` method does so too when the result fits the # destination's element type, and otherwise allocates. `scalartype` is computed in the value -# domain because an ITensor's element type is not encoded in its type. +# domain because an NamedTensor's element type is not encoded in its type. using VectorInterface: VectorInterface, add, add!, scalartype, scale, scale!, zerovector! -VectorInterface.scalartype(a::AbstractITensor) = scalartype(unnamed(a)) -function VectorInterface.scalartype(a::AbstractArray{<:AbstractITensor}) +VectorInterface.scalartype(a::AbstractNamedTensor) = scalartype(unnamed(a)) +function VectorInterface.scalartype(a::AbstractArray{<:AbstractNamedTensor}) return mapreduce(scalartype, promote_type, a; init = Bool) end -function VectorInterface.zerovector(a::AbstractITensor, ::Type{S}) where {S <: Number} +function VectorInterface.zerovector( + a::AbstractNamedTensor, + ::Type{S} + ) where {S <: Number} return zerovector!(similar(a, S)) end -VectorInterface.zerovector!(a::AbstractITensor) = zero!(a) -VectorInterface.zerovector!!(a::AbstractITensor) = zerovector!(a) +VectorInterface.zerovector!(a::AbstractNamedTensor) = zero!(a) +VectorInterface.zerovector!!(a::AbstractNamedTensor) = zerovector!(a) -VectorInterface.scale(a::AbstractITensor, α::Number) = a * α -function VectorInterface.scale!(a::AbstractITensor, α::Number) +VectorInterface.scale(a::AbstractNamedTensor, α::Number) = a * α +function VectorInterface.scale!(a::AbstractNamedTensor, α::Number) @. a = a * α return a end -function VectorInterface.scale!(b::AbstractITensor, a::AbstractITensor, α::Number) +function VectorInterface.scale!( + b::AbstractNamedTensor, + a::AbstractNamedTensor, + α::Number + ) @. b = a * α return b end -function VectorInterface.scale!!(a::AbstractITensor, α::Number) +function VectorInterface.scale!!(a::AbstractNamedTensor, α::Number) promote_type(scalartype(a), typeof(α)) <: scalartype(a) || return scale(a, α) return scale!(a, α) end -function VectorInterface.scale!!(b::AbstractITensor, a::AbstractITensor, α::Number) +function VectorInterface.scale!!( + b::AbstractNamedTensor, + a::AbstractNamedTensor, + α::Number + ) promote_type(scalartype(b), scalartype(a), typeof(α)) <: scalartype(b) || return scale(a, α) return scale!(b, a, α) end -function VectorInterface.add(y::AbstractITensor, x::AbstractITensor, α::Number, β::Number) +function VectorInterface.add( + y::AbstractNamedTensor, + x::AbstractNamedTensor, + α::Number, + β::Number + ) return @. y * β + x * α end -function VectorInterface.add!(y::AbstractITensor, x::AbstractITensor, α::Number, β::Number) +function VectorInterface.add!( + y::AbstractNamedTensor, + x::AbstractNamedTensor, + α::Number, + β::Number + ) @. y = y * β + x * α return y end -function VectorInterface.add!!(y::AbstractITensor, x::AbstractITensor, α::Number, β::Number) +function VectorInterface.add!!( + y::AbstractNamedTensor, + x::AbstractNamedTensor, + α::Number, + β::Number + ) promote_type(scalartype(y), scalartype(x), typeof(α), typeof(β)) <: scalartype(y) || return add(y, x, α, β) return add!(y, x, α, β) end -VectorInterface.inner(x::AbstractITensor, y::AbstractITensor) = (conj(x) * y)[] +function VectorInterface.inner(x::AbstractNamedTensor, y::AbstractNamedTensor) + return (conj(x) * y)[] +end -Base.axes(a::AbstractITensor, dimname::Name) = axes(a, dim(a, dimname)) -Base.size(a::AbstractITensor, dimname::Name) = size(a, dim(a, dimname)) +Base.axes(a::AbstractNamedTensor, dimname::Name) = axes(a, dim(a, dimname)) +Base.size(a::AbstractNamedTensor, dimname::Name) = size(a, dim(a, dimname)) -function similar_nameddims(a::AbstractITensor, elt::Type, ax) +function similar_nameddims(a::AbstractNamedTensor, elt::Type, ax) return nameddims( similar(unnamed(a), elt, unnamed.(Tuple(ax))), name.(ax) @@ -366,11 +394,11 @@ function similar_nameddims(a::AbstractArray, elt::Type, ax) end # Base.similar gets the eltype at compile time. -Base.similar(a::AbstractITensor) = similar(a, eltype(a)) -function Base.similar(a::AbstractITensor, elt::Type) +Base.similar(a::AbstractNamedTensor) = similar(a, eltype(a)) +function Base.similar(a::AbstractNamedTensor, elt::Type) return similar_nameddims(a, elt) end -function similar_nameddims(a::AbstractITensor, elt::Type) +function similar_nameddims(a::AbstractNamedTensor, elt::Type) return nameddimsof(a, similar(unnamed(a), elt)) end @@ -389,28 +417,28 @@ function Base.similar( return similar_nameddims(a, elt, inds) end -# Same entry points with a named-tensor prototype. An `AbstractITensor` is no longer +# Same entry points with a named-tensor prototype. An `AbstractNamedTensor` is no longer # an `AbstractArray`, so the methods above (which build a named tensor from a plain # array prototype) no longer cover it. function Base.similar( - a::AbstractITensor, + a::AbstractNamedTensor, inds::Tuple{NamedUnitRange, Vararg{NamedUnitRange}} ) return similar(a, eltype(a), inds) end function Base.similar( - a::AbstractITensor, elt::Type, + a::AbstractNamedTensor, elt::Type, inds::Tuple{NamedUnitRange, Vararg{NamedUnitRange}} ) return similar_nameddims(a, elt, inds) end -function setdimnames(a::AbstractITensor, dimnames) +function setdimnames(a::AbstractNamedTensor, dimnames) return nameddims(unnamed(a), dimnames) end """ - replacedimnames(a::AbstractITensor, replacements::Pair...) - replacedimnames(f, a::AbstractITensor) + replacedimnames(a::AbstractNamedTensor, replacements::Pair...) + replacedimnames(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 @@ -432,49 +460,49 @@ julia> dimnames(replacedimnames(a, :i => :k)) See also [`dimnames`](@ref). """ function replacedimnames end -function replacedimnames(a::AbstractITensor, replacements::Pair...) +function replacedimnames(a::AbstractNamedTensor, replacements::Pair...) new_dimnames = replace(dimnames(a), replacements...) return nameddims(unnamed(a), new_dimnames) end -function replacedimnames(f, a::AbstractITensor) +function replacedimnames(f, a::AbstractNamedTensor) new_dimnames = replace(f, dimnames(a)) return nameddims(unnamed(a), new_dimnames) end -mapdimnames(f, a::AbstractITensor) = replacedimnames(f, a) +mapdimnames(f, a::AbstractNamedTensor) = replacedimnames(f, a) # Replace over `axes` (a `Tuple`) rather than `inds` (a `Vector`): `replace` on a `Vector` # is homogeneous and would fail to convert a replacement index backed by a different range # type (e.g. `UnitRange` into a `OneTo`-backed vector), whereas a `Tuple` admits the mixed # element types. The result is splatted into `getindex`, so only the order matters. -function replaceinds(a::AbstractITensor, replacements::Pair...) +function replaceinds(a::AbstractNamedTensor, replacements::Pair...) new_inds = replace(axes(a), replacements...) return unnamed(a)[new_inds...] end -function replaceinds(f, a::AbstractITensor) +function replaceinds(f, a::AbstractNamedTensor) new_inds = replace(f, axes(a)) return unnamed(a)[new_inds...] end -mapinds(f, a::AbstractITensor) = replaceinds(f, a) +mapinds(f, a::AbstractNamedTensor) = replaceinds(f, a) # `Base.isempty(a::AbstractArray)` is defined as `length(a) == 0`, # which involves comparing a named integer to an unnamed integer # which isn't well defined. -Base.isempty(a::AbstractITensor) = isempty(unnamed(a)) +Base.isempty(a::AbstractNamedTensor) = isempty(unnamed(a)) # Define this on objects rather than types in case the wrapper type -# isn't known at compile time, like for the ITensor type. -Base.IndexStyle(a::AbstractITensor) = IndexStyle(unnamed(a)) -Base.eachindex(a::AbstractITensor) = eachindex(unnamed(a)) +# isn't known at compile time, like for the NamedTensor type. +Base.IndexStyle(a::AbstractNamedTensor) = IndexStyle(unnamed(a)) +Base.eachindex(a::AbstractNamedTensor) = eachindex(unnamed(a)) # Iteration, keys, and pairs forward to the parent (these were previously inherited # from `AbstractArray`). -Base.iterate(a::AbstractITensor, state...) = iterate(unnamed(a), state...) -Base.keys(a::AbstractITensor) = keys(unnamed(a)) -Base.pairs(a::AbstractITensor) = pairs(unnamed(a)) +Base.iterate(a::AbstractNamedTensor, state...) = iterate(unnamed(a), state...) +Base.keys(a::AbstractNamedTensor) = keys(unnamed(a)) +Base.pairs(a::AbstractNamedTensor) = pairs(unnamed(a)) # Multi-argument `eachindex` dispatches on the named index style, as the # `AbstractArray` version did. -function Base.eachindex(a1::AbstractITensor, a_rest::AbstractITensor...) +function Base.eachindex(a1::AbstractNamedTensor, a_rest::AbstractNamedTensor...) return eachindex(IndexStyle(a1, a_rest...), a1, a_rest...) end @@ -483,7 +511,7 @@ struct NamedIndexCartesian <: IndexStyle end # When multiple named dims arrays are involved, use the named # dimensions. -function Base.IndexStyle(a1::AbstractITensor, a2::AbstractITensor) +function Base.IndexStyle(a1::AbstractNamedTensor, a2::AbstractNamedTensor) return NamedIndexCartesian() end # Define promotion of index styles. @@ -510,7 +538,7 @@ struct NamedDimsCartesianIndices{ DimName, Indices <: Tuple{Vararg{NamedUnitRange, N}}, Index <: Tuple{Vararg{NamedInteger, N}}, - } <: AbstractITensor{DimName} + } <: AbstractNamedTensor{DimName} indices::Indices function NamedDimsCartesianIndices(indices::Tuple{Vararg{NamedUnitRange}}) dimname = eltype(name.(indices)) @@ -541,7 +569,7 @@ function unnamed(I::NamedDimsCartesianIndices) return CartesianIndices(unnamed.(I.indices)) end -# Iterating yields `NamedDimsCartesianIndex`es. The generic `AbstractITensor` +# Iterating yields `NamedDimsCartesianIndex`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...) @@ -553,8 +581,8 @@ end function Base.eachindex( ::NamedIndexCartesian, - a1::AbstractITensor, - a_rest::AbstractITensor... + a1::AbstractNamedTensor, + a_rest::AbstractNamedTensor... ) all(a -> issetequal(dimnames(a1), dimnames(a)), a_rest) || throw(NameMismatch("Dimension name mismatch $(dimnames.((a1, a_rest...))).")) @@ -567,7 +595,7 @@ end # Base version ignores dimension names. # TODO: Use `mapreduce(isequal, &&, a1, a2)`? -function Base.isequal(a1::AbstractITensor, a2::AbstractITensor) +function Base.isequal(a1::AbstractNamedTensor, a2::AbstractNamedTensor) issetequal(dimnames(a1), dimnames(a2)) || return false return isequal(unnamed(a1), unname(a2, dimnames(a1))) end @@ -575,13 +603,13 @@ end # Base version ignores dimension names. # TODO: Use `mapreduce(==, &&, a1, a2)`? # TODO: Handle `missing` values properly. -function Base.:(==)(a1::AbstractITensor, a2::AbstractITensor) +function Base.:(==)(a1::AbstractNamedTensor, a2::AbstractNamedTensor) issetequal(dimnames(a1), dimnames(a2)) || return false return unnamed(a1) == unname(a2, dimnames(a1)) end # Base version ignores dimension names. -function Base.isapprox(a1::AbstractITensor, a2::AbstractITensor; kwargs...) +function Base.isapprox(a1::AbstractNamedTensor, a2::AbstractNamedTensor; kwargs...) issetequal(dimnames(a1), dimnames(a2)) || return false return isapprox(unnamed(a1), unname(a2, dimnames(a1)); kwargs...) end @@ -591,8 +619,8 @@ end _sort(x; kwargs...) = sort(x; kwargs...) _sort(x::NTuple{N}; kwargs...) where {N} = NTuple{N}(sort(collect(x); kwargs...)) -function Base.hash(a::AbstractITensor, h::UInt64) - h = hash(:ITensor, h) +function Base.hash(a::AbstractNamedTensor, h::UInt64) + h = hash(:NamedTensor, h) a′ = aligneddims(a, _sort(dimnames(a))) h = hash(unnamed(a′), h) for i in axes(a′) @@ -605,35 +633,35 @@ end # Scalar indexing -Base.firstindex(a::AbstractITensor) = firstindex(unnamed(a)) -Base.lastindex(a::AbstractITensor) = lastindex(unnamed(a)) +Base.firstindex(a::AbstractNamedTensor) = firstindex(unnamed(a)) +Base.lastindex(a::AbstractNamedTensor) = lastindex(unnamed(a)) -function Base.firstindex(a::AbstractITensor, d) +function Base.firstindex(a::AbstractNamedTensor, d) return FirstIndex(a, d) end -function Base.lastindex(a::AbstractITensor, d) +function Base.lastindex(a::AbstractNamedTensor, d) return LastIndex(a, d) end # Redefine generic definition which expects `axes(a)` to # return a Tuple. -function Base.to_indices(a::AbstractITensor, I::Tuple) +function Base.to_indices(a::AbstractNamedTensor, I::Tuple) return to_indices(a, Tuple(axes(a)), I) end # Fix ambiguity error with Base. function Base.to_indices( - a::AbstractITensor, + a::AbstractNamedTensor, I::Tuple{Union{Integer, CartesianIndex}} ) return to_indices(a, Tuple(axes(a)), I) end -function Base.checkbounds(::Type{Bool}, a::AbstractITensor, I::Int...) +function Base.checkbounds(::Type{Bool}, a::AbstractNamedTensor, I::Int...) return checkbounds(Bool, unnamed(a), I...) end function Base.to_indices( - a::AbstractITensor, I::Tuple{NamedInteger, Vararg{NamedInteger}} + a::AbstractNamedTensor, I::Tuple{NamedInteger, Vararg{NamedInteger}} ) perm = getperm(name.(I), dimnames(a)) # TODO: Throw a `NameMismatch` error. @@ -644,76 +672,76 @@ function Base.to_indices( end end function Base.to_indices( - a::AbstractITensor, I::Tuple{Pair{<:Any, Int}, Vararg{Pair{<:Any, Int}}} + a::AbstractNamedTensor, I::Tuple{Pair{<:Any, Int}, Vararg{Pair{<:Any, Int}}} ) inds = to_inds(a, first.(I)) return to_indices(a, map((i, name) -> name[i], last.(I), inds)) end -function Base.to_indices(a::AbstractITensor, I::Tuple{Pair, Vararg{Pair}}) +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::AbstractITensor, I::Tuple{NamedDimsCartesianIndex}) +function Base.to_indices(a::AbstractNamedTensor, I::Tuple{NamedDimsCartesianIndex}) return to_indices(a, Tuple(only(I))) end -function Base.getindex(a::AbstractITensor, I...) +function Base.getindex(a::AbstractNamedTensor, I...) return getindex(a, to_indices(a, I)...) end -function Base.getindex(a::AbstractITensor, I1::Int, Irest::Int...) +function Base.getindex(a::AbstractNamedTensor, I1::Int, Irest::Int...) return getindex(unnamed(a), I1, Irest...) end function Base.getindex( - a::AbstractITensor, I1::NamedInteger, Irest::NamedInteger... + a::AbstractNamedTensor, I1::NamedInteger, Irest::NamedInteger... ) return getindex(a, to_indices(a, (I1, Irest...))...) end -function Base.getindex(a::AbstractITensor) +function Base.getindex(a::AbstractNamedTensor) return getindex(unnamed(a)) end # Linear indexing. -function Base.getindex(a::AbstractITensor, I::Int) +function Base.getindex(a::AbstractNamedTensor, I::Int) return getindex(unnamed(a), I) end -function Base.setindex!(a::AbstractITensor, value, I1::Int, Irest::Int...) +function Base.setindex!(a::AbstractNamedTensor, value, I1::Int, Irest::Int...) setindex!(unnamed(a), value, I1, Irest...) return a end -function Base.setindex!(a::AbstractITensor, value, I::CartesianIndex) +function Base.setindex!(a::AbstractNamedTensor, value, I::CartesianIndex) setindex!(a, value, to_indices(a, (I,))...) return a end function Base.setindex!( - a::AbstractITensor, value, I1::NamedInteger, + a::AbstractNamedTensor, value, I1::NamedInteger, Irest::NamedInteger... ) setindex!(a, value, to_indices(a, (I1, Irest...))...) return a end -function Base.setindex!(a::AbstractITensor, value, I::NamedDimsCartesianIndex) +function Base.setindex!(a::AbstractNamedTensor, value, I::NamedDimsCartesianIndex) setindex!(a, value, to_indices(a, (I,))...) return a end -function Base.setindex!(a::AbstractITensor, value, I1::Pair, Irest::Pair...) +function Base.setindex!(a::AbstractNamedTensor, value, I1::Pair, Irest::Pair...) setindex!(a, value, to_indices(a, (I1, Irest...))...) return a end -function Base.setindex!(a::AbstractITensor, value) +function Base.setindex!(a::AbstractNamedTensor, value) setindex!(unnamed(a), value) return a end # Linear indexing. -function Base.setindex!(a::AbstractITensor, value, I::Int) +function Base.setindex!(a::AbstractNamedTensor, value, I::Int) setindex!(unnamed(a), value, I) return a end -function Base.isassigned(a::AbstractITensor, I::Int...) +function Base.isassigned(a::AbstractNamedTensor, I::Int...) return isassigned(unnamed(a), I...) end @@ -725,18 +753,18 @@ const NamedViewIndex = using ArrayLayouts: ArrayLayouts, MemoryLayout -# Parent array type. Methods are defined per concrete type (`ITensor`, -# `ITensorOperator`); declared here since `MemoryLayout` below dispatches on it. +# Parent array type. Methods are defined per concrete type (`NamedTensor`, +# `NamedTensorOperator`); declared here since `MemoryLayout` below dispatches on it. function parenttype end -abstract type AbstractITensorLayout <: MemoryLayout end -struct ITensorLayout{ParentLayout} <: AbstractITensorLayout end +abstract type AbstractNamedTensorLayout <: MemoryLayout end +struct NamedTensorLayout{ParentLayout} <: AbstractNamedTensorLayout end -function ArrayLayouts.MemoryLayout(arrtype::Type{<:AbstractITensor}) - return ITensorLayout{typeof(MemoryLayout(parenttype(arrtype)))}() +function ArrayLayouts.MemoryLayout(arrtype::Type{<:AbstractNamedTensor}) + return NamedTensorLayout{typeof(MemoryLayout(parenttype(arrtype)))}() end -function ArrayLayouts.sub_materialize(::ITensorLayout, a, ax) +function ArrayLayouts.sub_materialize(::NamedTensorLayout, a, ax) return copy(a) end @@ -763,10 +791,10 @@ function Base.view(a::AbstractArray, I1::NamedViewIndex, Irest::NamedViewIndex.. end # TODO: Should this be a view? -function Base.getindex(a::AbstractITensor, I1::Name, Irest::Name...) +function Base.getindex(a::AbstractNamedTensor, I1::Name, Irest::Name...) return copy(view(a, I1, Irest...)) end -function Base.view(a::AbstractITensor, I1::Name, Irest::Name...) +function Base.view(a::AbstractNamedTensor, I1::Name, Irest::Name...) issetequal(dimnames(a), name.((I1, Irest...))) || throw( NameMismatch( @@ -776,21 +804,21 @@ function Base.view(a::AbstractITensor, I1::Name, Irest::Name...) return a end -function Base.getindex(a::AbstractITensor, I1::Pair, Irest::Pair...) +function Base.getindex(a::AbstractNamedTensor, I1::Pair, Irest::Pair...) return getindex(a, to_indices(a, (I1, Irest...))...) end -function Base.view(a::AbstractITensor, I1::Pair, Irest::Pair...) +function Base.view(a::AbstractNamedTensor, I1::Pair, Irest::Pair...) I = (I1, Irest...) inds = to_inds(a, first.(I)) return view(a, map((i, name) -> name[i], last.(I), inds)...) end function Base.getindex( - a::AbstractITensor, I1::NamedViewIndex, Irest::NamedViewIndex... + a::AbstractNamedTensor, I1::NamedViewIndex, Irest::NamedViewIndex... ) return copy(view(a, I1, Irest...)) end -function Base.view(a::AbstractITensor, I1::NamedViewIndex, Irest::NamedViewIndex...) +function Base.view(a::AbstractNamedTensor, I1::NamedViewIndex, Irest::NamedViewIndex...) I = (I1, Irest...) perm = getperm(name.(I), dimnames(a)) isperm(perm) || throw( @@ -812,29 +840,29 @@ isscalarindex(I) = false isscalarindex(I::Real) = true # Slicing with unnamed indices, such as: -# a = ITensor(rand(3,4), (:x, :y)) +# a = NamedTensor(rand(3,4), (:x, :y)) # b = view(a, 1:2, 2) -function view_nameddims(a::AbstractITensor, I...) +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) end -function Base.view(a::AbstractITensor, I::ViewIndex...) +function Base.view(a::AbstractNamedTensor, I::ViewIndex...) return view_nameddims(a, I...) end -function getindex_nameddims(a::AbstractITensor, I...) +function getindex_nameddims(a::AbstractNamedTensor, I...) return copy(view(a, I...)) end -function Base.getindex(a::AbstractITensor, I::ViewIndex...) +function Base.getindex(a::AbstractNamedTensor, I::ViewIndex...) return getindex_nameddims(a, I...) end function Base.setindex!( - a::AbstractITensor, - value::AbstractITensor, + a::AbstractNamedTensor, + value::AbstractNamedTensor, I1::NamedViewIndex, Irest::NamedViewIndex... ) @@ -842,7 +870,7 @@ function Base.setindex!( return a end function Base.setindex!( - a::AbstractITensor, + a::AbstractNamedTensor, value::AbstractArray, I1::NamedViewIndex, Irest::NamedViewIndex... @@ -852,8 +880,8 @@ function Base.setindex!( return a end function Base.setindex!( - a::AbstractITensor, - value::AbstractITensor, + a::AbstractNamedTensor, + value::AbstractNamedTensor, I1::ViewIndex, Irest::ViewIndex... ) @@ -861,7 +889,7 @@ function Base.setindex!( return a end function Base.setindex!( - a::AbstractITensor, value::AbstractArray, I1::ViewIndex, Irest::ViewIndex... + a::AbstractNamedTensor, value::AbstractArray, I1::ViewIndex, Irest::ViewIndex... ) setindex!(unnamed(a), value, I1, Irest...) return a @@ -870,7 +898,7 @@ end # Permute/align dimensions """ - aligndims(a::AbstractITensor, dims) + aligndims(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 @@ -882,14 +910,14 @@ throws a `NameMismatch` if `dims` is not a permutation of `a`'s dimension names. julia> a = nameddims(zeros(2, 3), (:i, :j)); julia> aligndims(a, (:j, :i)) -named(Base.OneTo(3), :j)×named(Base.OneTo(2), :i) ITensor{Symbol}: +named(Base.OneTo(3), :j)×named(Base.OneTo(2), :i) NamedTensor{Symbol}: 3×2 Matrix{Float64}: 0.0 0.0 0.0 0.0 0.0 0.0 ``` """ -function aligndims(a::AbstractITensor, dims) +function aligndims(a::AbstractNamedTensor, dims) new_dimnames = name.(dims) perm = Tuple(getperm(dimnames(a), new_dimnames)) isperm(perm) || throw( @@ -901,7 +929,7 @@ function aligndims(a::AbstractITensor, dims) end """ - aligneddims(a::AbstractITensor, dims) + aligneddims(a::AbstractNamedTensor, dims) Like [`aligndims`](@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 @@ -920,7 +948,7 @@ julia> dimnames(aligneddims(a, (:j, :i))) See also [`aligndims`](@ref). """ -function aligneddims(a::AbstractITensor, dims) +function aligneddims(a::AbstractNamedTensor, dims) new_dimnames = name.(dims) perm = getperm(dimnames(a), new_dimnames) isperm(perm) || throw( @@ -1020,12 +1048,12 @@ for dimtype in [:NamedInteger, :NamedUnitRange] end end -function Base.fill!(a::AbstractITensor, v) +function Base.fill!(a::AbstractNamedTensor, v) fill!(unnamed(a), v) return a end -function Base.map!(f, a_dest::AbstractITensor, a_srcs::AbstractITensor...) +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? @@ -1034,12 +1062,12 @@ function Base.map!(f, a_dest::AbstractITensor, a_srcs::AbstractITensor...) return a_dest end -function Base.map(f, a_srcs::AbstractITensor...) +function Base.map(f, a_srcs::AbstractNamedTensor...) # copy(mapped(f, a_srcs...)) return f.(a_srcs...) end -function Base.mapreduce(f, op, a::AbstractITensor; kwargs...) +function Base.mapreduce(f, op, a::AbstractNamedTensor; kwargs...) return mapreduce(f, op, unnamed(a); kwargs...) end @@ -1047,11 +1075,11 @@ end # `mapreduce` method above because some array types (such as graded arrays) define # `Base.sum` directly but not the general `mapreduce`, so the unnamed `sum` is the # path that works for them. -function Base.sum(a::AbstractITensor; kwargs...) +function Base.sum(a::AbstractNamedTensor; kwargs...) return sum(unnamed(a); kwargs...) end -function LinearAlgebra.promote_leaf_eltypes(a::AbstractITensor) +function LinearAlgebra.promote_leaf_eltypes(a::AbstractNamedTensor) return LinearAlgebra.promote_leaf_eltypes(unnamed(a)) end @@ -1093,21 +1121,21 @@ function concretetype_to_string_truncated( return str end -function Base.summary(io::IO, a::AbstractITensor) +function Base.summary(io::IO, a::AbstractNamedTensor) print(io, dims_to_string(inds(a))) print(io, ' ') print(io, concretetype_to_string_truncated(typeof(a); param_truncation_length = 40)) return nothing end -function Base.show(io::IO, mime::MIME"text/plain", a::AbstractITensor) +function Base.show(io::IO, mime::MIME"text/plain", a::AbstractNamedTensor) summary(io, a) println(io, ":") show(io, mime, unnamed(a)) return nothing end -function Base.show(io::IO, a::AbstractITensor) +function Base.show(io::IO, a::AbstractNamedTensor) show(io, unnamed(a)) print(io, "[", join(inds(a), ", "), "]") return nothing diff --git a/src/broadcast.jl b/src/broadcast.jl index 66bc6b28..b857dae2 100644 --- a/src/broadcast.jl +++ b/src/broadcast.jl @@ -1,36 +1,36 @@ -using ..ITensorBase: AbstractITensor, ITensorBase, NamedUnitRange, getperm, inds, name, +using ..ITensorBase: AbstractNamedTensor, ITensorBase, NamedUnitRange, getperm, inds, name, named, nameddims, unname, unnamed using Base.Broadcast: Broadcast as BC, Broadcasted, broadcast_shape, broadcasted, check_broadcast_shape, combine_axes using TensorAlgebra: TensorAlgebra as TA -abstract type AbstractITensorStyle{N} <: BC.AbstractArrayStyle{N} end +abstract type AbstractNamedTensorStyle{N} <: BC.AbstractArrayStyle{N} end -# Both `ITensorStyle` and `ITensorOperatorStyle` are dynamically-ranked -# (`ndims(::AbstractITensor) === Any`), so the rank parameter `N` is `Any`. The +# Both `NamedTensorStyle` and `NamedTensorOperatorStyle` are dynamically-ranked +# (`ndims(::AbstractNamedTensor) === Any`), so the rank parameter `N` is `Any`. The # `Val{N}` constructors below are required by `Base.Broadcast` for ranked styles; # they preserve the style and ignore the inferred rank. -struct ITensorStyle{N} <: AbstractITensorStyle{N} end -ITensorStyle(::Val{N}) where {N} = ITensorStyle{N}() -ITensorStyle{M}(::Val{N}) where {M, N} = ITensorStyle{N}() +struct NamedTensorStyle{N} <: AbstractNamedTensorStyle{N} end +NamedTensorStyle(::Val{N}) where {N} = NamedTensorStyle{N}() +NamedTensorStyle{M}(::Val{N}) where {M, N} = NamedTensorStyle{N}() -function BC.BroadcastStyle(arraytype::Type{<:AbstractITensor}) - return ITensorStyle{ndims(arraytype)}() +function BC.BroadcastStyle(arraytype::Type{<:AbstractNamedTensor}) + return NamedTensorStyle{ndims(arraytype)}() end -# An `AbstractITensor` broadcasts as itself (previously inherited from +# An `AbstractNamedTensor` broadcasts as itself (previously inherited from # `AbstractArray`); without this the default `broadcastable` wraps it in a `Ref`. -BC.broadcastable(a::AbstractITensor) = a +BC.broadcastable(a::AbstractNamedTensor) = a function BC.combine_axes( - a1::AbstractITensor, a_rest::AbstractITensor... + a1::AbstractNamedTensor, a_rest::AbstractNamedTensor... ) return broadcast_shape(axes(a1), combine_axes(a_rest...)) end -function BC.combine_axes(a1::AbstractITensor, a2::AbstractITensor) +function BC.combine_axes(a1::AbstractNamedTensor, a2::AbstractNamedTensor) return broadcast_shape(axes(a1), axes(a2)) end -BC.combine_axes(a::AbstractITensor) = axes(a) +BC.combine_axes(a::AbstractNamedTensor) = axes(a) # The named axes are a `Tuple` of `NamedUnitRange`s. Dispatch the # name-aware shape combination on that tuple form (the elements are not @@ -80,8 +80,8 @@ function set_promote_shape( end # Handle operations like `randn() + randn(2, 2)[i, j]``. -# TODO: Decide if this should be a general definition for `AbstractITensor`, -# or just for `AbstractITensor`. +# TODO: Decide if this should be a general definition for `AbstractNamedTensor`, +# or just for `AbstractNamedTensor`. function set_promote_shape( ax1::Tuple{}, ax2::Tuple{NamedUnitRange, Vararg{NamedUnitRange}} ) @@ -89,8 +89,8 @@ function set_promote_shape( end # Handle operations like `randn(2, 2)[i, j] + randn()`. -# TODO: Decide if this should be a general definition for `AbstractITensor`, -# or just for `AbstractITensor`. +# TODO: Decide if this should be a general definition for `AbstractNamedTensor`, +# or just for `AbstractNamedTensor`. function set_promote_shape( ax1::Tuple{NamedUnitRange, Vararg{NamedUnitRange}}, ax2::Tuple{} ) @@ -116,7 +116,7 @@ end set_check_broadcast_shape(ax1::Tuple{}, ax2::Tuple{}) = nothing broadcasted_unnamed(x::Number, inds) = x -broadcasted_unnamed(a::AbstractITensor, inds) = unnamed(a, inds) +broadcasted_unnamed(a::AbstractNamedTensor, inds) = unnamed(a, inds) function broadcasted_unnamed(bc::Broadcasted, inds) return broadcasted(bc.f, Base.Fix2(broadcasted_unnamed, inds).(bc.args)...) end @@ -125,11 +125,11 @@ end # result inherits the operands' backend (e.g. graded) rather than a lazy permuted # wrapper's `similar` (which can drop the backend). unnamed_prototype(bc::Broadcasted) = unnamed_prototype(bc.args...) -unnamed_prototype(arg::AbstractITensor, args...) = unnamed(arg) +unnamed_prototype(arg::AbstractNamedTensor, args...) = unnamed(arg) unnamed_prototype(arg::Broadcasted, args...) = unnamed_prototype(arg.args..., args...) unnamed_prototype(arg, args...) = unnamed_prototype(args...) -function Base.similar(bc::Broadcasted{<:AbstractITensorStyle}, elt::Type, ax) +function Base.similar(bc::Broadcasted{<:AbstractNamedTensorStyle}, elt::Type, ax) inds_a = name.(ax) bc_unnamed = broadcasted_unnamed(bc, inds_a) a_unnamed = similar(bc_unnamed, elt) @@ -137,7 +137,7 @@ function Base.similar(bc::Broadcasted{<:AbstractITensorStyle}, elt::Type, ax) end inds(bc::Broadcasted) = name.(axes(bc)) -function Base.copy(bc::Broadcasted{<:AbstractITensorStyle}) +function Base.copy(bc::Broadcasted{<:AbstractNamedTensorStyle}) # We could use: # ```julia # elt = combine_eltypes(bc.f, bc.args) @@ -163,7 +163,10 @@ function Base.copy(bc::Broadcasted{<:AbstractITensorStyle}) return nameddims(dest_unnamed, inds_dest) end -function Base.copyto!(dest::AbstractITensor, bc::Broadcasted{<:AbstractITensorStyle}) +function Base.copyto!( + dest::AbstractNamedTensor, + bc::Broadcasted{<:AbstractNamedTensorStyle} + ) dest_unnamed = unnamed(dest) inds_dest = axes(dest) bc_unnamed = broadcasted_unnamed(bc, inds_dest) @@ -180,49 +183,49 @@ end # Operator-preserving broadcasting. # -# An `ITensorOperator` broadcasts as itself (it does not peel to its `state`), so -# `op .+ op`, `2 .* op`, etc. carry the `ITensorOperatorStyle`. The style-combination +# An `NamedTensorOperator` broadcasts as itself (it does not peel to its `state`), so +# `op .+ op`, `2 .* op`, etc. carry the `NamedTensorOperatorStyle`. The style-combination # rules below enforce the input rules declaratively: # - operator ⊗ operator → operator (preserved), # - operator ⊗ scalar → operator (`2 .* op` stays an operator), # - operator ⊗ non-operator tensor → error. -# The `BroadcastStyle(::Type{<:ITensorOperator})` mapping and the operator-specific -# `copy` / `similar` (which unwrap, delegate to `ITensorStyle`, then rewrap) live in -# `itensoroperator.jl`, where `ITensorOperator` is defined. `*` (contraction) is +# The `BroadcastStyle(::Type{<:NamedTensorOperator})` mapping and the operator-specific +# `copy` / `similar` (which unwrap, delegate to `NamedTensorStyle`, then rewrap) live in +# `itensoroperator.jl`, where `NamedTensorOperator` is defined. `*` (contraction) is # unchanged and still decays to `state`. -struct ITensorOperatorStyle{N} <: AbstractITensorStyle{N} end -ITensorOperatorStyle(::Val{N}) where {N} = ITensorOperatorStyle{N}() -ITensorOperatorStyle{M}(::Val{N}) where {M, N} = ITensorOperatorStyle{N}() +struct NamedTensorOperatorStyle{N} <: AbstractNamedTensorStyle{N} end +NamedTensorOperatorStyle(::Val{N}) where {N} = NamedTensorOperatorStyle{N}() +NamedTensorOperatorStyle{M}(::Val{N}) where {M, N} = NamedTensorOperatorStyle{N}() # operator ⊗ operator stays an operator. function BC.BroadcastStyle( - ::ITensorOperatorStyle{M}, - ::ITensorOperatorStyle{N} + ::NamedTensorOperatorStyle{M}, + ::NamedTensorOperatorStyle{N} ) where {M, N} - return ITensorOperatorStyle{M}() + return NamedTensorOperatorStyle{M}() end # operator ⊗ scalar (`DefaultArrayStyle{0}`, e.g. `2 .* op`) stays an operator. function BC.BroadcastStyle( - style::ITensorOperatorStyle, ::BC.DefaultArrayStyle{0} + style::NamedTensorOperatorStyle, ::BC.DefaultArrayStyle{0} ) return style end # operator ⊗ non-operator named tensor is type-nonsense and is rejected. -function BC.BroadcastStyle(::ITensorOperatorStyle, ::ITensorStyle) +function BC.BroadcastStyle(::NamedTensorOperatorStyle, ::NamedTensorStyle) return throw( ArgumentError( - "Cannot broadcast an `ITensorOperator` together with a non-operator " * + "Cannot broadcast an `NamedTensorOperator` together with a non-operator " * "tensor. Wrap the tensor as an operator first, or unwrap the " * "operator with `state`." ) ) end -# Reinterpret an operator-style `Broadcasted` under `ITensorStyle`, the broadcast -# over the operators' states, so the shared `ITensorStyle` implementation runs (its +# Reinterpret an operator-style `Broadcasted` under `NamedTensorStyle`, the broadcast +# over the operators' states, so the shared `NamedTensorStyle` implementation runs (its # `broadcasted_unnamed` already peels each operator operand to its `state` via # `unnamed`). -function statebroadcasted(bc::Broadcasted{<:ITensorOperatorStyle}) - return Broadcasted{ITensorStyle{Any}}(bc.f, bc.args, bc.axes) +function statebroadcasted(bc::Broadcasted{<:NamedTensorOperatorStyle}) + return Broadcasted{NamedTensorStyle{Any}}(bc.f, bc.args, bc.axes) end diff --git a/src/index.jl b/src/index.jl index 1b927d5f..d65e5915 100644 --- a/src/index.jl +++ b/src/index.jl @@ -11,6 +11,16 @@ function tagsstring(tags) end end +""" + IndexName + +The name carried by an [`Index`](@ref): a freshly minted unique identifier together with a set +of tags and an integer prime level. Two `IndexName`s compare equal only when their +identifier, tags, and prime level all match, so independently constructed indices stay +distinct. [`prime`](@ref) raises the prime level and [`noprime`](@ref) resets it. `IndexName` +is the dimension-name type behind the legacy ITensor surface, where `Index` is +`NamedUnitRange{IndexName}` and [`ITensor`](@ref) is `NamedTensor{IndexName}`. +""" struct IndexName <: AbstractName id::UUID tags::SortedDict{Symbol, Symbol} @@ -167,8 +177,8 @@ end """ Index(space) -An index of an `ITensor`: a named unit range whose name is a freshly minted, unique -identifier carrying tags and a prime level. The argument is a space that is converted to a +An index of an [`ITensor`](@ref): a named unit range whose name is an [`IndexName`](@ref), a +freshly minted, unique identifier carrying tags and a prime level. The argument is a space that is converted to a range: `Index(2)` makes an index of length `2` over `Base.OneTo(2)`, `Index(1:3)` makes one over an explicit range, and (with GradedArrays loaded) `Index([U1(0) => 2, U1(1) => 3])` makes one over a graded range. Each call mints a new @@ -186,6 +196,32 @@ julia> length(i) """ const Index = NamedUnitRange{IndexName} +# `IndexName`-specialized aliases for the named-dims tensor hierarchy. The +# name-generic primaries are defined earlier (`abstractnamedtensor.jl`, +# `namedtensor.jl`, `namedtensoroperator.jl`); these fix the dimname flavor to +# `IndexName`, recovering the legacy ITensor surface. They live here because they +# reference `IndexName`, just like `Index` itself. + +""" + AbstractITensor + +Alias for `AbstractNamedTensor{IndexName}`: the [`AbstractNamedTensor`](@ref) +supertype with dimension names fixed to [`IndexName`](@ref) (the names carried by +[`Index`](@ref)). +""" +const AbstractITensor = AbstractNamedTensor{IndexName} + +""" + ITensor + +Alias for `NamedTensor{IndexName}`: a [`NamedTensor`](@ref) whose dimension +names are [`IndexName`](@ref)s, the names carried by [`Index`](@ref). This is the legacy +ITensor type. Use [`NamedTensor`](@ref) for the dimname-flavor-generic type. +""" +const ITensor = NamedTensor{IndexName} + +const ITensorOperator = NamedTensorOperator{IndexName} + # TODO: Define for `NamedViewIndex`. id(i::Index) = id(name(i)) tags_stored(i::Index) = tags_stored(name(i)) diff --git a/src/itensor.jl b/src/itensor.jl deleted file mode 100644 index ff9fc11d..00000000 --- a/src/itensor.jl +++ /dev/null @@ -1,57 +0,0 @@ -""" - ITensor(array::AbstractArray, dimnames) - -A dense tensor whose dimensions are labeled by names instead of ordered by position. It pairs -an underlying `array` with one name per dimension (`dimnames`), so contraction, addition, and -indexing line dimensions up by name. An `ITensor` is usually built by calling `randn`, `zeros`, -and the like on indices, or through [`nameddims`](@ref), rather than constructed directly. - -# Examples - -```jldoctest -julia> ITensor(zeros(2, 3), (:i, :j)) -named(Base.OneTo(2), :i)×named(Base.OneTo(3), :j) ITensor{Symbol}: -2×3 Matrix{Float64}: - 0.0 0.0 0.0 - 0.0 0.0 0.0 -``` -""" -struct ITensor{DimName} <: AbstractITensor{DimName} - unnamed::AbstractArray - dimnames::Vector{DimName} - function ITensor{DimName}(unnamed::AbstractArray, dimnames) where {DimName} - dimnames = collect(DimName, dimnames) - # Catch the common ITensors.jl-style mistake of passing indices as the names. - any(dimname -> dimname isa NamedUnitRange, dimnames) && throw( - ArgumentError( - "The `ITensor` constructor takes dimension names only, not indices \ - (`NamedUnitRange`s), got $(dimnames). To build an `ITensor` from an array \ - and indices, index the array instead, as in `array[i, j]`." - ) - ) - ndims(unnamed) == length(dimnames) || - 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) - end -end - -ITensor(unnamed::AbstractArray, dimnames) = ITensor{eltype(dimnames)}(unnamed, dimnames) -ITensor(a::AbstractITensor, inds) = throw(ArgumentError("Already named.")) -ITensor(a::AbstractITensor) = ITensor(unnamed(a), dimnames(a)) - -# Minimal interface. The dimnames are stored as (and returned as) a `Vector`. -dimnames(a::ITensor) = a.dimnames -unnamed(a::ITensor) = a.unnamed -Base.parent(a::ITensor) = unnamed(a) - -dimnametype(::Type{<:ITensor{DimName}}) where {DimName} = DimName - -# The parent array is erased at the field level, so its concrete type is not part -# of `ITensor`'s signature. An instance still carries the parent, so the instance -# methods recover the concrete type while the type methods report `AbstractArray`. -unnamedtype(a::ITensor) = typeof(unnamed(a)) -unnamedtype(::Type{<:ITensor}) = AbstractArray -parenttype(a::ITensor) = typeof(parent(a)) -parenttype(::Type{<:ITensor}) = AbstractArray diff --git a/src/lazyitensors/evaluation_order.jl b/src/lazyitensors/evaluation_order.jl index 40c28c91..c0596c73 100644 --- a/src/lazyitensors/evaluation_order.jl +++ b/src/lazyitensors/evaluation_order.jl @@ -14,20 +14,20 @@ function input_space_complexity(f, args...) end function time_complexity( - ::typeof(*), t1::AbstractITensor, t2::AbstractITensor + ::typeof(*), t1::AbstractNamedTensor, t2::AbstractNamedTensor ) return prod(length, (inds(t1) ∪ inds(t2))) end function time_complexity( - ::typeof(+), t1::AbstractITensor, t2::AbstractITensor + ::typeof(+), t1::AbstractNamedTensor, t2::AbstractNamedTensor ) @assert issetequal(dimnames(t1), dimnames(t2)) return prod(size(t1)) end -function time_complexity(::typeof(*), c::Number, t::AbstractITensor) +function time_complexity(::typeof(*), c::Number, t::AbstractNamedTensor) return prod(size(t)) end -function time_complexity(::typeof(*), t::AbstractITensor, c::Number) +function time_complexity(::typeof(*), t::AbstractNamedTensor, c::Number) return time_complexity(*, c, t) end diff --git a/src/lazyitensors/itensorbaseextensions.jl b/src/lazyitensors/itensorbaseextensions.jl index b31c656a..248b0f98 100644 --- a/src/lazyitensors/itensorbaseextensions.jl +++ b/src/lazyitensors/itensorbaseextensions.jl @@ -2,8 +2,8 @@ # TODO: Define a proper hash function # in ITensorBase.jl, maybe one that is # independent of the order of dimensions. -function _hash(a::ITensor, h::UInt64) - h = hash(:ITensor, h) +function _hash(a::NamedTensor, h::UInt64) + h = hash(:NamedTensor, h) h = hash(unnamed(a), h) for i in inds(a) h = hash(i, h) @@ -16,14 +16,14 @@ end using AbstractTrees: AbstractTrees # Only print the dimension names when printing with `AbstractTrees.print_tree`. -function AbstractTrees.printnode(io::IO, a::AbstractITensor) +function AbstractTrees.printnode(io::IO, a::AbstractNamedTensor) dimnames_a = "{" * join(map(s -> "\"$s\"", dimnames(a)), ", ") * "}" print(io, dimnames_a) return nothing end # Custom version of `AbstractTrees.printnode` to -# avoid type piracy when overloading on `AbstractITensor`. -# Method specializations (`LazyITensor`, `SymbolicITensor`) live in +# 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) diff --git a/src/lazyitensors/lazybroadcast.jl b/src/lazyitensors/lazybroadcast.jl index 157747fe..0149ed20 100644 --- a/src/lazyitensors/lazybroadcast.jl +++ b/src/lazyitensors/lazybroadcast.jl @@ -1,13 +1,13 @@ # Lazy broadcasting. -struct LazyITensorStyle <: Base.Broadcast.AbstractArrayStyle{Any} end -function Broadcast.broadcasted(::LazyITensorStyle, f, as...) - return error("Arbitrary broadcasting not supported for LazyITensor.") +struct LazyNamedTensorStyle <: Base.Broadcast.AbstractArrayStyle{Any} end +function Broadcast.broadcasted(::LazyNamedTensorStyle, f, as...) + return error("Arbitrary broadcasting not supported for LazyNamedTensor.") end # Linear operations. -Broadcast.broadcasted(::LazyITensorStyle, ::typeof(+), a1, a2) = a1 + a2 -Broadcast.broadcasted(::LazyITensorStyle, ::typeof(-), a1, a2) = a1 - a2 -Broadcast.broadcasted(::LazyITensorStyle, ::typeof(*), c::Number, a) = c * a -Broadcast.broadcasted(::LazyITensorStyle, ::typeof(*), a, c::Number) = a * c -Broadcast.broadcasted(::LazyITensorStyle, ::typeof(*), a::Number, b::Number) = a * b -Broadcast.broadcasted(::LazyITensorStyle, ::typeof(/), a, c::Number) = a / c -Broadcast.broadcasted(::LazyITensorStyle, ::typeof(-), a) = -a +Broadcast.broadcasted(::LazyNamedTensorStyle, ::typeof(+), a1, a2) = a1 + a2 +Broadcast.broadcasted(::LazyNamedTensorStyle, ::typeof(-), a1, a2) = a1 - a2 +Broadcast.broadcasted(::LazyNamedTensorStyle, ::typeof(*), c::Number, a) = c * a +Broadcast.broadcasted(::LazyNamedTensorStyle, ::typeof(*), a, c::Number) = a * c +Broadcast.broadcasted(::LazyNamedTensorStyle, ::typeof(*), a::Number, b::Number) = a * b +Broadcast.broadcasted(::LazyNamedTensorStyle, ::typeof(/), a, c::Number) = a / c +Broadcast.broadcasted(::LazyNamedTensorStyle, ::typeof(-), a) = -a diff --git a/src/lazyitensors/lazyinterface.jl b/src/lazyitensors/lazyinterface.jl index ed56db81..a76ca0fc 100644 --- a/src/lazyitensors/lazyinterface.jl +++ b/src/lazyitensors/lazyinterface.jl @@ -118,7 +118,7 @@ function isequal_lazy(a1, a2) end function hash_lazy(a, h::UInt64) h = hash(Symbol(Base.typename(typeof(a)).wrapper), h) - # Use `_hash`, which defines a custom hash for ITensor. + # Use `_hash`, which defines a custom hash for NamedTensor. return _hash(unwrap(a), h) end function map_arguments_lazy(f, a) @@ -141,7 +141,7 @@ substitute_lazy(a, substitutions) = substitute(a, Dict(substitutions)) using AbstractTrees: printnode function printnode_lazy(io, a) # Use `printnode_nameddims` to avoid type piracy, - # since it overloads on `AbstractITensor`. + # since it overloads on `AbstractNamedTensor`. return printnode_nameddims(io, unwrap(a)) end function show_lazy(io::IO, a) diff --git a/src/lazyitensors/lazyitensor.jl b/src/lazyitensors/lazyitensor.jl index 0e7be9be..6046b6aa 100644 --- a/src/lazyitensors/lazyitensor.jl +++ b/src/lazyitensors/lazyitensor.jl @@ -1,69 +1,74 @@ using WrappedUnions: @wrapped -@wrapped struct LazyITensor{ - DimName, A <: AbstractITensor{DimName}, - } <: AbstractITensor{DimName} - union::Union{A, Mul{LazyITensor{DimName, A}}} +@wrapped struct LazyNamedTensor{ + DimName, A <: AbstractNamedTensor{DimName}, + } <: AbstractNamedTensor{DimName} + union::Union{A, Mul{LazyNamedTensor{DimName, A}}} end -parenttype(::Type{LazyITensor{DimName, A}}) where {DimName, A} = A -parenttype(::Type{LazyITensor{DimName}}) where {DimName} = AbstractITensor{DimName} -parenttype(::Type{LazyITensor}) = AbstractITensor +parenttype(::Type{LazyNamedTensor{DimName, A}}) where {DimName, A} = A +function parenttype(::Type{LazyNamedTensor{DimName}}) where {DimName} + return AbstractNamedTensor{DimName} +end +parenttype(::Type{LazyNamedTensor}) = AbstractNamedTensor -function LazyITensor(a::AbstractITensor) - return LazyITensor{dimnametype(typeof(a)), typeof(a)}(a) +function LazyNamedTensor(a::AbstractNamedTensor) + return LazyNamedTensor{dimnametype(typeof(a)), typeof(a)}(a) end -function LazyITensor(a::Mul{L}) where {L <: LazyITensor} - return LazyITensor{dimnametype(L), parenttype(L)}(a) +function LazyNamedTensor(a::Mul{L}) where {L <: LazyNamedTensor} + return LazyNamedTensor{dimnametype(L), parenttype(L)}(a) end -lazy(a::LazyITensor) = a -lazy(a::AbstractITensor) = LazyITensor(a) -lazy(a::Mul{<:LazyITensor}) = LazyITensor(a) +lazy(a::LazyNamedTensor) = a +lazy(a::AbstractNamedTensor) = LazyNamedTensor(a) +lazy(a::Mul{<:LazyNamedTensor}) = LazyNamedTensor(a) -dimnames(a::LazyITensor) = dimnames_lazy(a) -inds(a::LazyITensor) = inds_lazy(a) +dimnames(a::LazyNamedTensor) = dimnames_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. -Base.axes(a::LazyITensor) = Tuple(inds_lazy(a)) -unnamed(a::LazyITensor) = unnamed_lazy(a) +Base.axes(a::LazyNamedTensor) = Tuple(inds_lazy(a)) +unnamed(a::LazyNamedTensor) = unnamed_lazy(a) # Broadcasting -function Base.BroadcastStyle(::Type{<:LazyITensor}) - return LazyITensorStyle() +function Base.BroadcastStyle(::Type{<:LazyNamedTensor}) + return LazyNamedTensorStyle() end # Derived functionality. -function TermInterface.maketerm(type::Type{LazyITensor}, head, args, metadata) +function TermInterface.maketerm(type::Type{LazyNamedTensor}, head, args, metadata) return maketerm_lazy(type, head, args, metadata) end -Base.getindex(a::LazyITensor, I::Int...) = getindex_lazy(a, I...) -TermInterface.arguments(a::LazyITensor) = arguments_lazy(a) -TermInterface.children(a::LazyITensor) = children_lazy(a) -TermInterface.head(a::LazyITensor) = head_lazy(a) -TermInterface.iscall(a::LazyITensor) = iscall_lazy(a) -TermInterface.isexpr(a::LazyITensor) = isexpr_lazy(a) -TermInterface.operation(a::LazyITensor) = operation_lazy(a) -TermInterface.sorted_arguments(a::LazyITensor) = sorted_arguments_lazy(a) -AbstractTrees.children(a::LazyITensor) = abstracttrees_children_lazy(a) -TermInterface.sorted_children(a::LazyITensor) = sorted_children_lazy(a) -ismul(a::LazyITensor) = ismul_lazy(a) -AbstractTrees.nodevalue(a::LazyITensor) = nodevalue_lazy(a) -Base.Broadcast.materialize(a::LazyITensor) = materialize_lazy(a) -Base.copy(a::LazyITensor) = copy_lazy(a) -Base.:(==)(a1::LazyITensor, a2::LazyITensor) = equals_lazy(a1, a2) -Base.isequal(a1::LazyITensor, a2::LazyITensor) = isequal_lazy(a1, a2) -Base.hash(a::LazyITensor, h::UInt64) = hash_lazy(a, h) -map_arguments(f, a::LazyITensor) = map_arguments_lazy(f, a) -substitute(a::LazyITensor, substitutions) = substitute_lazy(a, substitutions) -AbstractTrees.printnode(io::IO, a::LazyITensor) = printnode_lazy(io, a) -printnode_nameddims(io::IO, a::LazyITensor) = printnode_lazy(io, a) -Base.show(io::IO, a::LazyITensor) = show_lazy(io, a) -Base.show(io::IO, mime::MIME"text/plain", a::LazyITensor) = show_lazy(io, mime, a) -Base.:*(a::LazyITensor) = mul_lazy(a) -Base.:*(a1::LazyITensor, a2::LazyITensor) = mul_lazy(a1, a2) -Base.:+(a1::LazyITensor, a2::LazyITensor) = add_lazy(a1, a2) -Base.:-(a1::LazyITensor, a2::LazyITensor) = sub_lazy(a1, a2) -Base.:*(a1::Number, a2::LazyITensor) = mul_lazy(a1, a2) -Base.:*(a1::LazyITensor, a2::Number) = mul_lazy(a1, a2) -Base.:/(a1::LazyITensor, a2::Number) = div_lazy(a1, a2) -Base.:-(a::LazyITensor) = sub_lazy(a) +Base.getindex(a::LazyNamedTensor, I::Int...) = getindex_lazy(a, I...) +TermInterface.arguments(a::LazyNamedTensor) = arguments_lazy(a) +TermInterface.children(a::LazyNamedTensor) = children_lazy(a) +TermInterface.head(a::LazyNamedTensor) = head_lazy(a) +TermInterface.iscall(a::LazyNamedTensor) = iscall_lazy(a) +TermInterface.isexpr(a::LazyNamedTensor) = isexpr_lazy(a) +TermInterface.operation(a::LazyNamedTensor) = operation_lazy(a) +TermInterface.sorted_arguments(a::LazyNamedTensor) = sorted_arguments_lazy(a) +AbstractTrees.children(a::LazyNamedTensor) = abstracttrees_children_lazy(a) +TermInterface.sorted_children(a::LazyNamedTensor) = sorted_children_lazy(a) +ismul(a::LazyNamedTensor) = ismul_lazy(a) +AbstractTrees.nodevalue(a::LazyNamedTensor) = nodevalue_lazy(a) +Base.Broadcast.materialize(a::LazyNamedTensor) = materialize_lazy(a) +Base.copy(a::LazyNamedTensor) = copy_lazy(a) +Base.:(==)(a1::LazyNamedTensor, a2::LazyNamedTensor) = equals_lazy(a1, a2) +Base.isequal(a1::LazyNamedTensor, a2::LazyNamedTensor) = isequal_lazy(a1, a2) +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) +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) +Base.:*(a1::LazyNamedTensor, a2::LazyNamedTensor) = mul_lazy(a1, a2) +Base.:+(a1::LazyNamedTensor, a2::LazyNamedTensor) = add_lazy(a1, a2) +Base.:-(a1::LazyNamedTensor, a2::LazyNamedTensor) = sub_lazy(a1, a2) +Base.:*(a1::Number, a2::LazyNamedTensor) = mul_lazy(a1, a2) +Base.:*(a1::LazyNamedTensor, a2::Number) = mul_lazy(a1, a2) +Base.:/(a1::LazyNamedTensor, a2::Number) = div_lazy(a1, a2) +Base.:-(a::LazyNamedTensor) = sub_lazy(a) + +# `IndexName`-specialized alias, paralleling `ITensor = NamedTensor{IndexName}`. +const LazyITensor = LazyNamedTensor{IndexName} diff --git a/src/lazyitensors/symbolicitensor.jl b/src/lazyitensors/symbolicitensor.jl index 5472a9e5..cc6ebad4 100644 --- a/src/lazyitensors/symbolicitensor.jl +++ b/src/lazyitensors/symbolicitensor.jl @@ -5,71 +5,74 @@ # reconstructed as plain ranges of those sizes. Storing sizes and dimnames 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 SymbolicITensor{DimName, Name} <: AbstractITensor{DimName} +struct SymbolicNamedTensor{DimName, Name} <: AbstractNamedTensor{DimName} name::Name size::Vector{Int} dimnames::Vector{DimName} end -function SymbolicITensor(symname, inds) +function SymbolicNamedTensor(symname, inds) dnames = collect(name.(inds)) DimName = isempty(inds) ? typeof(symname) : eltype(dnames) sizes = Int[length(i) for i in inds] - return SymbolicITensor{DimName, typeof(symname)}(symname, sizes, dnames) + return SymbolicNamedTensor{DimName, typeof(symname)}(symname, sizes, dnames) end -symname(a::SymbolicITensor) = getfield(a, :name) +symname(a::SymbolicNamedTensor) = getfield(a, :name) -dimnames(a::SymbolicITensor) = getfield(a, :dimnames) -function Base.axes(a::SymbolicITensor) +dimnames(a::SymbolicNamedTensor) = getfield(a, :dimnames) +function Base.axes(a::SymbolicNamedTensor) return named.(Tuple(Base.OneTo.(getfield(a, :size))), Tuple(getfield(a, :dimnames))) end -dimnametype(::Type{<:SymbolicITensor{DimName}}) where {DimName} = DimName -Base.ndims(a::SymbolicITensor) = length(getfield(a, :dimnames)) +dimnametype(::Type{<:SymbolicNamedTensor{DimName}}) where {DimName} = DimName +Base.ndims(a::SymbolicNamedTensor) = length(getfield(a, :dimnames)) -function Base.:(==)(a::SymbolicITensor, b::SymbolicITensor) +function Base.:(==)(a::SymbolicNamedTensor, b::SymbolicNamedTensor) return symname(a) == symname(b) && dimnames(a) == dimnames(b) end -Base.isequal(a::SymbolicITensor, b::SymbolicITensor) = a == b -function Base.hash(a::SymbolicITensor, h::UInt64) - h = hash(:SymbolicITensor, h) +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) end # Products build lazy expressions rather than contracting numerically. -Base.:*(a::SymbolicITensor, b::SymbolicITensor) = lazy(a) * lazy(b) -Base.:*(a::SymbolicITensor, b::LazyITensor) = lazy(a) * b -Base.:*(a::LazyITensor, b::SymbolicITensor) = a * lazy(b) +Base.:*(a::SymbolicNamedTensor, b::SymbolicNamedTensor) = lazy(a) * lazy(b) +Base.:*(a::SymbolicNamedTensor, b::LazyNamedTensor) = lazy(a) * b +Base.:*(a::LazyNamedTensor, b::SymbolicNamedTensor) = a * lazy(b) -issymbolic(a) = a isa SymbolicITensor -issymbolic(a::LazyITensor) = !iscall(a) && issymbolic(unwrap(a)) +issymbolic(a) = a isa SymbolicNamedTensor +issymbolic(a::LazyNamedTensor) = !iscall(a) && issymbolic(unwrap(a)) -function Base.show(io::IO, a::SymbolicITensor) +function Base.show(io::IO, a::SymbolicNamedTensor) print(io, symname(a)) if ndims(a) > 0 print(io, "[", join(dimnames(a), ","), "]") end return nothing end -function Base.show(io::IO, mime::MIME"text/plain", a::SymbolicITensor) +function Base.show(io::IO, mime::MIME"text/plain", a::SymbolicNamedTensor) summary(io, a) println(io, ":") show(io, a) return nothing end +# `IndexName`-specialized alias, paralleling `ITensor = NamedTensor{IndexName}`. +const SymbolicITensor = SymbolicNamedTensor{IndexName} + using AbstractTrees: AbstractTrees -function AbstractTrees.printnode(io::IO, a::SymbolicITensor) +function AbstractTrees.printnode(io::IO, a::SymbolicNamedTensor) show(io, a) return nothing end function symnameddims(symname, dims) - return lazy(SymbolicITensor(symname, dims)) + return lazy(SymbolicNamedTensor(symname, dims)) end symnameddims(name) = symnameddims(name, ()) -function printnode_nameddims(io::IO, a::SymbolicITensor) +function printnode_nameddims(io::IO, a::SymbolicNamedTensor) AbstractTrees.printnode(io, a) return nothing end diff --git a/src/linearalgebra.jl b/src/linearalgebra.jl index f20aa8e7..2f315f85 100644 --- a/src/linearalgebra.jl +++ b/src/linearalgebra.jl @@ -6,7 +6,7 @@ using LinearAlgebra: LinearAlgebra as LA # which isn't friendly for named arrays wrapping GPU arrays. # This implicitly helps with defining `LA.normalize[!]` as well (though note that # uses `LinearAlgebra.rmul!` as well). -function LA.norm(a::AbstractITensor, p::Real = 2; kwargs...) +function LA.norm(a::AbstractNamedTensor, p::Real = 2; kwargs...) return LA.norm(unnamed(a), p; kwargs...) end @@ -18,11 +18,11 @@ for f! in [:mul!, :div!] lf! = Symbol(:l, f!) rf! = Symbol(:r, f!) @eval begin - function LA.$rf!(a::AbstractITensor, α::Number) + function LA.$rf!(a::AbstractNamedTensor, α::Number) LA.$rf!(unnamed(a), α) return a end - function LA.$lf!(α::Number, a::AbstractITensor) + function LA.$lf!(α::Number, a::AbstractNamedTensor) LA.$lf!(α, unnamed(a)) return a end @@ -33,6 +33,6 @@ end # uses scalar indexing: # https://github.com/JuliaLang/LinearAlgebra.jl/blob/3a4fdad7f608928ecb4b41e76b1e9ecacd058444/src/generic.jl#L919-L1009 # which isn't friendly for named arrays wrapping GPU arrays. -function LA.dot(a1::AbstractITensor, a2::AbstractITensor) +function LA.dot(a1::AbstractNamedTensor, a2::AbstractNamedTensor) return (conj(a1) * a2)[] end diff --git a/src/named.jl b/src/named.jl index 79dd9aee..872c1fef 100644 --- a/src/named.jl +++ b/src/named.jl @@ -39,7 +39,7 @@ function name end 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 [`AbstractITensor`](@ref) it returns the underlying +`unnamed` recovers the value. On an [`AbstractNamedTensor`](@ref) it returns the underlying unnamed array. # Examples diff --git a/src/namedtensor.jl b/src/namedtensor.jl new file mode 100644 index 00000000..9d1fc503 --- /dev/null +++ b/src/namedtensor.jl @@ -0,0 +1,58 @@ +""" + NamedTensor(array::AbstractArray, dims) + +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 +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. +[`ITensor`](@ref) is the `NamedTensor` with dimension names that are [`IndexName`](@ref)s. + +# Examples + +```jldoctest +julia> NamedTensor(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 +``` +""" +struct NamedTensor{DimName} <: AbstractNamedTensor{DimName} + unnamed::AbstractArray + dimnames::Vector{DimName} + function NamedTensor{DimName}(unnamed::AbstractArray, dimnames) where {DimName} + dimnames = collect(DimName, dimnames) + # Catch the common ITensors.jl-style mistake of passing indices as the names. + any(dimname -> dimname isa NamedUnitRange, dimnames) && throw( + ArgumentError( + "The `NamedTensor` constructor takes dimension names only, not indices \ + (`NamedUnitRange`s), got $(dimnames). To build a `NamedTensor` from an \ + array and indices, index the array instead, as in `array[i, j]`." + ) + ) + ndims(unnamed) == length(dimnames) || + 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) + end +end + +NamedTensor(unnamed::AbstractArray, dims) = NamedTensor{eltype(dims)}(unnamed, dims) +NamedTensor(a::AbstractNamedTensor, inds) = throw(ArgumentError("Already named.")) +NamedTensor(a::AbstractNamedTensor) = NamedTensor(unnamed(a), dimnames(a)) + +# Minimal interface. The dimnames are stored as (and returned as) a `Vector`. +dimnames(a::NamedTensor) = a.dimnames +unnamed(a::NamedTensor) = a.unnamed +Base.parent(a::NamedTensor) = unnamed(a) + +dimnametype(::Type{<:NamedTensor{DimName}}) where {DimName} = DimName + +# The parent array is erased at the field level, so its concrete type is not part +# of `NamedTensor`'s signature. An instance still carries the parent, so the instance +# methods recover the concrete type while the type methods report `AbstractArray`. +unnamedtype(a::NamedTensor) = typeof(unnamed(a)) +unnamedtype(::Type{<:NamedTensor}) = AbstractArray +parenttype(a::NamedTensor) = typeof(parent(a)) +parenttype(::Type{<:NamedTensor}) = AbstractArray diff --git a/src/itensoroperator.jl b/src/namedtensoroperator.jl similarity index 82% rename from src/itensoroperator.jl rename to src/namedtensoroperator.jl index 7d624fb1..b7571400 100644 --- a/src/itensoroperator.jl +++ b/src/namedtensoroperator.jl @@ -80,7 +80,7 @@ get_codomain_name(a, i) = throw(MethodError(get_codomain_name, (a, i))) get_domain_name(a, i) = throw(MethodError(get_domain_name, (a, i))) """ - apply(x::AbstractITensor, y::AbstractITensor) + apply(x::AbstractNamedTensor, y::AbstractNamedTensor) Apply the operator `x` to `y`. This contracts the state tensors of `x` and `y` over their shared names, then renames each surviving codomain name of `x` back to its paired @@ -101,14 +101,14 @@ true See also [`operator`](@ref), [`state`](@ref), [`codomainnames`](@ref), [`domainnames`](@ref). """ -function apply(x::AbstractITensor, y::AbstractITensor) +function apply(x::AbstractNamedTensor, y::AbstractNamedTensor) xy = state(x) * state(y) return mapdimnames(xy) do i return get_domain_name(x, i) end end -function apply_dag(x::AbstractITensor, y::AbstractITensor) +function apply_dag(x::AbstractNamedTensor, y::AbstractNamedTensor) xy = state(x) * state(y) return mapdimnames(xy) do i return get_codomain_name(y, i) @@ -117,7 +117,7 @@ end # TODO: Define versions that accept codomain and domain names, # i.e. `transpose(a, codomain, domain)` and `adjoint(a, codomain, domain)` (?). -function Base.transpose(a::AbstractITensor) +function Base.transpose(a::AbstractNamedTensor) c = codomainnames(a) d = domainnames(a) a_map = merge(Dict(c .=> d), Dict(d .=> c)) @@ -126,11 +126,11 @@ function Base.transpose(a::AbstractITensor) end return operator(a′, c, d) end -function Base.adjoint(a::AbstractITensor) +function Base.adjoint(a::AbstractNamedTensor) return transpose(conj(a)) end -function product(x::AbstractITensor, y::AbstractITensor) +function product(x::AbstractNamedTensor, y::AbstractNamedTensor) c = codomainnames(x) d = domainnames(x) c′ = uniquename.(c) @@ -177,36 +177,36 @@ Base.iterate(b::Bijection) = iterate(b.domain_to_codomain) Base.iterate(b::Bijection, state) = iterate(b.domain_to_codomain, state) Base.length(b::Bijection) = length(b.domain_to_codomain) -struct ITensorOperator{DimName, P <: AbstractITensor{DimName}, D, C} <: - AbstractITensor{DimName} +struct NamedTensorOperator{DimName, P <: AbstractNamedTensor{DimName}, D, C} <: + AbstractNamedTensor{DimName} parent::P dimnames_bijection::Bijection{D, C} end -state(a::AbstractITensor) = a -state(a::ITensorOperator) = a.parent -Base.parent(a::ITensorOperator) = state(a) -unnamed(a::ITensorOperator) = unnamed(state(a)) -dimnames(a::ITensorOperator) = dimnames(state(a)) +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)) -function ITensorOperator(a::AbstractITensor, codomainnames, domainnames) - return ITensorOperator(a, Bijection(domainnames, codomainnames)) +function NamedTensorOperator(a::AbstractNamedTensor, codomainnames, domainnames) + return NamedTensorOperator(a, Bijection(domainnames, codomainnames)) end -parenttype(type::Type{<:ITensorOperator}) = fieldtype(type, :parent) -statetype(type::Type{<:ITensorOperator}) = parenttype(type) +parenttype(type::Type{<:NamedTensorOperator}) = fieldtype(type, :parent) +statetype(type::Type{<:NamedTensorOperator}) = parenttype(type) -function nameddimsof(a::ITensorOperator, b::AbstractArray) - return ITensorOperator(nameddimsof(state(a), b), a.dimnames_bijection) +function nameddimsof(a::NamedTensorOperator, b::AbstractArray) + return NamedTensorOperator(nameddimsof(state(a), b), a.dimnames_bijection) end -codomainnames(a::ITensorOperator) = codomain(a.dimnames_bijection) -domainnames(a::ITensorOperator) = domain(a.dimnames_bijection) +codomainnames(a::NamedTensorOperator) = codomain(a.dimnames_bijection) +domainnames(a::NamedTensorOperator) = domain(a.dimnames_bijection) -function get_codomain_name(a::ITensorOperator, i) +function get_codomain_name(a::NamedTensorOperator, i) return get(a.dimnames_bijection, i, i) end -function get_domain_name(a::ITensorOperator, i) +function get_domain_name(a::NamedTensorOperator, i) return get(inverse(a.dimnames_bijection), i, i) end @@ -248,18 +248,18 @@ function operator(a::AbstractArray, codomain, domain) na = nameddims(a, (codomain..., domain...)) return operator(na, codomain, domain) end -function operator(a::AbstractITensor, codomain, domain) - return ITensorOperator(a, name.(codomain), name.(domain)) +function operator(a::AbstractNamedTensor, codomain, domain) + return NamedTensorOperator(a, name.(codomain), name.(domain)) end # Operator-preserving contraction. Contracting two named arrays sums over their # shared names, so the result keeps each operand's surviving codomain/domain # structure. A non-operator tensor contributes no pairs (all its names are # dangling from the operator point of view). The result is always an -# `ITensorOperator`, even when its codomain and domain both come out empty, so +# `NamedTensorOperator`, even when its codomain and domain both come out empty, so # the product type does not depend on the runtime names being contracted. -operator_pairs(a::ITensorOperator) = a.dimnames_bijection.domain_to_codomain -operator_pairs(a::AbstractITensor) = () +operator_pairs(a::NamedTensorOperator) = a.dimnames_bijection.domain_to_codomain +operator_pairs(a::AbstractNamedTensor) = () # Compose the codomain/domain of `a * b`. The `domain => codomain` pairs of both # operands form a graph of maximum degree two (each name is paired at most once @@ -267,7 +267,7 @@ operator_pairs(a::AbstractITensor) = () # name reaches its surviving codomain partner by following the pairing through # 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_codomain_domain(a::AbstractITensor, b::AbstractITensor) +function product_codomain_domain(a::AbstractNamedTensor, b::AbstractNamedTensor) shared = intersect(dimnames(a), dimnames(b)) pairs = collect(Iterators.flatten((operator_pairs(a), operator_pairs(b)))) forward = Dict(pairs) @@ -285,31 +285,33 @@ function product_codomain_domain(a::AbstractITensor, b::AbstractITensor) return codomain, domain end -function operator_product(a::AbstractITensor, b::AbstractITensor) +function operator_product(a::AbstractNamedTensor, b::AbstractNamedTensor) ab = state(a) * state(b) codomain, domain = product_codomain_domain(a, b) return operator(ab, codomain, domain) end -Base.:*(a::ITensorOperator, b::ITensorOperator) = operator_product(a, b) -Base.:*(a::ITensorOperator, b::AbstractITensor) = operator_product(a, b) -Base.:*(a::AbstractITensor, b::ITensorOperator) = operator_product(a, b) +Base.:*(a::NamedTensorOperator, b::NamedTensorOperator) = operator_product(a, b) +Base.:*(a::NamedTensorOperator, b::AbstractNamedTensor) = operator_product(a, b) +Base.:*(a::AbstractNamedTensor, b::NamedTensorOperator) = operator_product(a, b) # Operator-preserving broadcasting (the style struct and style-combination rules -# live in `broadcast.jl`). An `ITensorOperator` broadcasts as itself, so `op .+ op`, -# `2 .* op`, etc. carry `ITensorOperatorStyle`; `+` / `-` / scalar `*` inherit +# live in `broadcast.jl`). An `NamedTensorOperator` broadcasts as itself, so `op .+ op`, +# `2 .* op`, etc. carry `NamedTensorOperatorStyle`; `+` / `-` / scalar `*` inherit # preservation since they lower to broadcasting. `copy` / `similar` unwrap each -# operator operand to its `state` (the shared `ITensorStyle` machinery does this via -# `unnamed`), build the `ITensor` result, then rewrap as an operator using the +# operator operand to its `state` (the shared `NamedTensorStyle` machinery does this via +# `unnamed`), build the `NamedTensor` result, then rewrap as an operator using the # codomain/domain split recovered from the operands. -function BC.BroadcastStyle(arraytype::Type{<:ITensorOperator}) - return ITensorOperatorStyle{ndims(arraytype)}() +function BC.BroadcastStyle(arraytype::Type{<:NamedTensorOperator}) + return NamedTensorOperatorStyle{ndims(arraytype)}() end # Recover the codomain/domain split shared by all operator operands of `bc`, # erroring if any two operators disagree. operator_operands(bc::Broadcasted) = operator_operands(bc.args...) -operator_operands(arg::ITensorOperator, args...) = (arg, operator_operands(args...)...) +function operator_operands(arg::NamedTensorOperator, args...) + return (arg, operator_operands(args...)...) +end function operator_operands(arg::Broadcasted, args...) return (operator_operands(arg.args...)..., operator_operands(args...)...) end @@ -334,13 +336,13 @@ function broadcast_operator_codomain_domain(bc::Broadcasted) return cod1, dom1 end -function Base.copy(bc::Broadcasted{<:ITensorOperatorStyle}) +function Base.copy(bc::Broadcasted{<:NamedTensorOperatorStyle}) cod, dom = broadcast_operator_codomain_domain(bc) result = copy(statebroadcasted(bc)) return operator(result, cod, dom) end -function Base.similar(bc::Broadcasted{<:ITensorOperatorStyle}, elt::Type, ax) +function Base.similar(bc::Broadcasted{<:NamedTensorOperatorStyle}, elt::Type, ax) cod, dom = broadcast_operator_codomain_domain(bc) result = similar(statebroadcasted(bc), elt, ax) return operator(result, cod, dom) @@ -348,7 +350,7 @@ end for f in MATRIX_FUNCTIONS @eval begin - function Base.$f(a::ITensorOperator) + function Base.$f(a::NamedTensorOperator) c = codomainnames(a) d = domainnames(a) return operator($f(state(a), c, d), c, d) @@ -357,7 +359,7 @@ for f in MATRIX_FUNCTIONS end # Operator entries for the gram factorizations defined in `tensoralgebra.jl`. -# Placed here because `ITensorOperator` is defined in this file, which comes +# Placed here because `NamedTensorOperator` is defined in this file, which comes # after `tensoralgebra.jl` in the include order. # # Per-method docstrings are factored out into `const` strings and attached @@ -366,7 +368,7 @@ end # don't share enough structure to warrant `$($f)`-interpolation. const _gram_eigh_full_operator_docstring = """ - TensorAlgebra.MatrixAlgebra.gram_eigh_full(a::ITensorOperator; kwargs...) -> x + TensorAlgebra.MatrixAlgebra.gram_eigh_full(a::NamedTensorOperator; kwargs...) -> x Gram factorization of a Hermitian positive semi-definite named operator `a`, returning `x` such that `x * x_cod ≈ state(a)`, where `x_cod` is @@ -399,7 +401,7 @@ true """ const _gram_eigh_full_with_pinv_operator_docstring = """ - TensorAlgebra.MatrixAlgebra.gram_eigh_full_with_pinv(a::ITensorOperator; kwargs...) -> x, y + TensorAlgebra.MatrixAlgebra.gram_eigh_full_with_pinv(a::NamedTensorOperator; kwargs...) -> x, y Like `TensorAlgebra.MatrixAlgebra.gram_eigh_full`, but additionally returns a named array `y` that is a left inverse of `x`: `y * x ≈ I` on the @@ -435,14 +437,14 @@ true for f in (:gram_eigh_full, :gram_eigh_full_with_pinv) doc_sym = Symbol("_", f, "_operator_docstring") @eval begin - @doc $doc_sym function MA.$f(a::ITensorOperator; kwargs...) + @doc $doc_sym function MA.$f(a::NamedTensorOperator; kwargs...) return MA.$f(state(a), codomainnames(a), domainnames(a); kwargs...) end end end """ - Base.one(op::ITensorOperator) -> Id + Base.one(op::NamedTensorOperator) -> Id Return the identity operator with the same codomain/domain names and shape as `op`. `op` is treated as a shape prototype and is not mutated. @@ -468,7 +470,7 @@ julia> apply(Id, v) ≈ v true ``` """ -function Base.one(op::ITensorOperator) +function Base.one(op::NamedTensorOperator) co, dom = codomainnames(op), domainnames(op) return operator(one(state(op), co, dom), co, dom) end @@ -539,15 +541,15 @@ function similar_operator(prototype, named_domain_axes) end # Forward `Random.randn!` / `Random.rand!` to the operator's state, which -# itself peels to the concrete storage via the generic AbstractITensor +# itself peels to the concrete storage via the generic AbstractNamedTensor # method. -function Random.randn!(rng::Random.AbstractRNG, op::ITensorOperator) +function Random.randn!(rng::Random.AbstractRNG, op::NamedTensorOperator) Random.randn!(rng, state(op)) return op end -function Random.rand!(rng::Random.AbstractRNG, op::ITensorOperator) +function Random.rand!(rng::Random.AbstractRNG, op::NamedTensorOperator) Random.rand!(rng, state(op)) return op end diff --git a/src/quirks.jl b/src/quirks.jl index 3094ac22..eae0fe51 100644 --- a/src/quirks.jl +++ b/src/quirks.jl @@ -1,3 +1,3 @@ # This seems to be needed to get broadcasting working. # TODO: Investigate this and see if we can get rid of it. -Base.Broadcast.extrude(a::AbstractITensor) = a +Base.Broadcast.extrude(a::AbstractNamedTensor) = a diff --git a/src/tensoralgebra.jl b/src/tensoralgebra.jl index 05a7c62a..27bcd88c 100644 --- a/src/tensoralgebra.jl +++ b/src/tensoralgebra.jl @@ -7,8 +7,8 @@ using TupleTools: TupleTools # This layer is used to define derivative rules (to skip differentiating `setdiff`). dimnames_setdiff(s1, s2) = setdiff(s1, s2) -Base.:*(a1::AbstractITensor, a2::AbstractITensor) = mul_nameddims(a1, a2) -function mul_nameddims(a1::AbstractITensor, a2::AbstractITensor) +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) ) @@ -23,28 +23,28 @@ end # ``` # that optimize matrix multiplication sequence. function Base.:*( - a1::AbstractITensor, a2::AbstractITensor, - a3::AbstractITensor, a_rest::AbstractITensor... + a1::AbstractNamedTensor, a2::AbstractNamedTensor, + a3::AbstractNamedTensor, a_rest::AbstractNamedTensor... ) return mul_nameddims(a1, a2, a3, a_rest...) end function mul_nameddims( - a1::AbstractITensor, a2::AbstractITensor, - a3::AbstractITensor, a_rest::AbstractITensor... + a1::AbstractNamedTensor, a2::AbstractNamedTensor, + a3::AbstractNamedTensor, a_rest::AbstractNamedTensor... ) return *(*(a1, a2), a3, a_rest...) end function LA.mul!( - a_dest::AbstractITensor, - a1::AbstractITensor, a2::AbstractITensor, + a_dest::AbstractNamedTensor, + a1::AbstractNamedTensor, a2::AbstractNamedTensor, α::Number, β::Number ) return mul!_nameddims(a_dest, a1, a2, α, β) end function mul!_nameddims( - a_dest::AbstractITensor, - a1::AbstractITensor, a2::AbstractITensor, + a_dest::AbstractNamedTensor, + a1::AbstractNamedTensor, a2::AbstractNamedTensor, α::Number, β::Number ) TA.contractadd!( @@ -57,14 +57,14 @@ function mul!_nameddims( end function LA.mul!( - a_dest::AbstractITensor, - a1::AbstractITensor, a2::AbstractITensor + a_dest::AbstractNamedTensor, + a1::AbstractNamedTensor, a2::AbstractNamedTensor ) return mul!_nameddims(a_dest, a1, a2) end function mul!_nameddims( - a_dest::AbstractITensor, - a1::AbstractITensor, a2::AbstractITensor + a_dest::AbstractNamedTensor, + a1::AbstractNamedTensor, a2::AbstractNamedTensor ) TA.contract!( unnamed(a_dest), dimnames(a_dest), @@ -76,27 +76,27 @@ end # Locate the named-dimension groups `group1`, `group2` within `a`, returning their two # positional index groups. -function nameperm(a::AbstractITensor, group1, group2) +function nameperm(a::AbstractNamedTensor, group1, group2) return TA.biperm(dimnames(a), name.(group1), name.(group2)) end # i, j, k, l = named.((2, 2, 2, 2), ("i", "j", "k", "l")) # a = randn(i, j, k, l) # matricize(a, (i, k) => "a", (j, l) => "b") -function TA.matricize(a::AbstractITensor, fusions::Vararg{Pair, 2}) +function TA.matricize(a::AbstractNamedTensor, fusions::Vararg{Pair, 2}) return matricize_nameddims(a, fusions...) end -function matricize_nameddims(na::AbstractITensor, fusions::Vararg{Pair, 2}) +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::AbstractITensor, splitters::Vararg{Pair, 2}) +function TA.unmatricize(na::AbstractNamedTensor, splitters::Vararg{Pair, 2}) return unmatricize_nameddims(na, splitters...) end -function unmatricize_nameddims(na::AbstractITensor, splitters::Vararg{Pair, 2}) +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 @@ -125,12 +125,12 @@ for f in [ f_nameddims = Symbol(f, "_nameddims") @eval begin function MAK.$f( - a::AbstractITensor, dimnames_codomain, dimnames_domain; kwargs... + a::AbstractNamedTensor, dimnames_codomain, dimnames_domain; kwargs... ) return $f_nameddims(a, dimnames_codomain, dimnames_domain; kwargs...) end function $f_nameddims( - a::AbstractITensor, dimnames_codomain, dimnames_domain; kwargs... + a::AbstractNamedTensor, dimnames_codomain, dimnames_domain; kwargs... ) codomain = name.(dimnames_codomain) domain = name.(dimnames_domain) @@ -144,10 +144,10 @@ for f in [ y = nameddims(y_unnamed, dimnames_y) return x, y end - function MAK.$f(a::AbstractITensor, dimnames_codomain; kwargs...) + function MAK.$f(a::AbstractNamedTensor, dimnames_codomain; kwargs...) return $f_nameddims(a, dimnames_codomain; kwargs...) end - function $f_nameddims(a::AbstractITensor, dimnames_codomain; kwargs...) + function $f_nameddims(a::AbstractNamedTensor, dimnames_codomain; kwargs...) codomain = name.(dimnames_codomain) domain = dimnames_setdiff(dimnames(a), codomain) return MAK.$f(a, codomain, domain; kwargs...) @@ -163,12 +163,12 @@ for f in [:svd_compact, :svd_full, :svd_trunc] f_nameddims = Symbol(f, "_nameddims") @eval begin function MAK.$f( - a::AbstractITensor, dimnames_codomain, dimnames_domain; kwargs... + a::AbstractNamedTensor, dimnames_codomain, dimnames_domain; kwargs... ) return $f_nameddims(a, dimnames_codomain, dimnames_domain; kwargs...) end function $f_nameddims( - a::AbstractITensor, dimnames_codomain, dimnames_domain; kwargs... + a::AbstractNamedTensor, dimnames_codomain, dimnames_domain; kwargs... ) codomain = name.(dimnames_codomain) domain = name.(dimnames_domain) @@ -185,10 +185,10 @@ for f in [:svd_compact, :svd_full, :svd_trunc] v = nameddims(v_unnamed, dimnames_v) return u, s, v end - function MAK.$f(a::AbstractITensor, dimnames_codomain; kwargs...) + function MAK.$f(a::AbstractNamedTensor, dimnames_codomain; kwargs...) return $f_nameddims(a, dimnames_codomain; kwargs...) end - function $f_nameddims(a::AbstractITensor, dimnames_codomain; kwargs...) + function $f_nameddims(a::AbstractNamedTensor, dimnames_codomain; kwargs...) return MAK.$f( a, dimnames_codomain, @@ -204,12 +204,12 @@ end # function MAK.svd_vals( - a::AbstractITensor, dimnames_codomain, dimnames_domain; kwargs... + a::AbstractNamedTensor, dimnames_codomain, dimnames_domain; kwargs... ) return svd_vals_nameddims(a, dimnames_codomain, dimnames_domain; kwargs...) end function svd_vals_nameddims( - a::AbstractITensor, dimnames_codomain, dimnames_domain; kwargs... + a::AbstractNamedTensor, dimnames_codomain, dimnames_domain; kwargs... ) return TA.svd_vals( unnamed(a), @@ -220,10 +220,10 @@ function svd_vals_nameddims( ) end -function MAK.svd_vals(a::AbstractITensor, dimnames_codomain; kwargs...) +function MAK.svd_vals(a::AbstractNamedTensor, dimnames_codomain; kwargs...) return svd_vals_nameddims(a, dimnames_codomain; kwargs...) end -function svd_vals_nameddims(a::AbstractITensor, dimnames_codomain; kwargs...) +function svd_vals_nameddims(a::AbstractNamedTensor, dimnames_codomain; kwargs...) codomain = name.(dimnames_codomain) domain = dimnames_setdiff(dimnames(a), codomain) return MAK.svd_vals(a, codomain, domain; kwargs...) @@ -237,12 +237,12 @@ for f in [:eigh_full, :eig_full, :eigh_trunc, :eig_trunc] f_nameddims = Symbol(f, "_nameddims") @eval begin function MAK.$f( - a::AbstractITensor, dimnames_codomain, dimnames_domain; kwargs... + a::AbstractNamedTensor, dimnames_codomain, dimnames_domain; kwargs... ) return $f_nameddims(a, dimnames_codomain, dimnames_domain; kwargs...) end function $f_nameddims( - a::AbstractITensor, dimnames_codomain, dimnames_domain; kwargs... + a::AbstractNamedTensor, dimnames_codomain, dimnames_domain; kwargs... ) codomain = name.(dimnames_codomain) domain = name.(dimnames_domain) @@ -269,12 +269,12 @@ for f in [:eigh_vals, :eig_vals] f_nameddims = Symbol(f, "_nameddims") @eval begin function MAK.$f( - a::AbstractITensor, dimnames_codomain, dimnames_domain; kwargs... + a::AbstractNamedTensor, dimnames_codomain, dimnames_domain; kwargs... ) return $f_nameddims(a, dimnames_codomain, dimnames_domain; kwargs...) end function $f_nameddims( - a::AbstractITensor, dimnames_codomain, dimnames_domain; kwargs... + a::AbstractNamedTensor, dimnames_codomain, dimnames_domain; kwargs... ) codomain = name.(dimnames_codomain) domain = name.(dimnames_domain) @@ -284,12 +284,12 @@ for f in [:eigh_vals, :eig_vals] end function MAK.left_null( - a::AbstractITensor, dimnames_codomain, dimnames_domain; kwargs... + a::AbstractNamedTensor, dimnames_codomain, dimnames_domain; kwargs... ) return left_null_nameddims(a, dimnames_codomain, dimnames_domain; kwargs...) end function left_null_nameddims( - a::AbstractITensor, dimnames_codomain, dimnames_domain; kwargs... + a::AbstractNamedTensor, dimnames_codomain, dimnames_domain; kwargs... ) codomain = name.(dimnames_codomain) domain = name.(dimnames_domain) @@ -299,22 +299,22 @@ function left_null_nameddims( return nameddims(n_unnamed, dimnames_n) end -function MAK.left_null(a::AbstractITensor, dimnames_codomain; kwargs...) +function MAK.left_null(a::AbstractNamedTensor, dimnames_codomain; kwargs...) return left_null_nameddims(a, dimnames_codomain; kwargs...) end -function left_null_nameddims(a::AbstractITensor, dimnames_codomain; kwargs...) +function left_null_nameddims(a::AbstractNamedTensor, dimnames_codomain; kwargs...) codomain = name.(dimnames_codomain) domain = dimnames_setdiff(dimnames(a), codomain) return MAK.left_null(a, codomain, domain; kwargs...) end function MAK.right_null( - a::AbstractITensor, dimnames_codomain, dimnames_domain; kwargs... + a::AbstractNamedTensor, dimnames_codomain, dimnames_domain; kwargs... ) return right_null_nameddims(a, dimnames_codomain, dimnames_domain; kwargs...) end function right_null_nameddims( - a::AbstractITensor, dimnames_codomain, dimnames_domain; kwargs... + a::AbstractNamedTensor, dimnames_codomain, dimnames_domain; kwargs... ) codomain = name.(dimnames_codomain) domain = name.(dimnames_domain) @@ -324,17 +324,17 @@ function right_null_nameddims( return nameddims(n_unnamed, dimnames_n) end -function MAK.right_null(a::AbstractITensor, dimnames_codomain; kwargs...) +function MAK.right_null(a::AbstractNamedTensor, dimnames_codomain; kwargs...) return right_null_nameddims(a, dimnames_codomain; kwargs...) end -function right_null_nameddims(a::AbstractITensor, dimnames_codomain; kwargs...) +function right_null_nameddims(a::AbstractNamedTensor, dimnames_codomain; kwargs...) codomain = name.(dimnames_codomain) domain = dimnames_setdiff(dimnames(a), codomain) return MAK.right_null(a, codomain, domain; kwargs...) end """ - TensorAlgebra.MatrixAlgebra.gram_eigh_full(a::AbstractITensor, dimnames_codomain, dimnames_domain; kwargs...) -> x + TensorAlgebra.MatrixAlgebra.gram_eigh_full(a::AbstractNamedTensor, dimnames_codomain, dimnames_domain; kwargs...) -> x Gram factorization of a Hermitian positive semi-definite named array `a`, returning `x` such that `a ≈ x * x_cod`, where `x_cod` is `conj(x)` with @@ -366,12 +366,12 @@ true ``` """ function MA.gram_eigh_full( - a::AbstractITensor, dimnames_codomain, dimnames_domain; kwargs... + a::AbstractNamedTensor, dimnames_codomain, dimnames_domain; kwargs... ) return gram_eigh_full_nameddims(a, dimnames_codomain, dimnames_domain; kwargs...) end function gram_eigh_full_nameddims( - a::AbstractITensor, dimnames_codomain, dimnames_domain; kwargs... + a::AbstractNamedTensor, dimnames_codomain, dimnames_domain; kwargs... ) codomain = name.(dimnames_codomain) domain = name.(dimnames_domain) @@ -382,7 +382,7 @@ function gram_eigh_full_nameddims( end """ - TensorAlgebra.MatrixAlgebra.gram_eigh_full_with_pinv(a::AbstractITensor, dimnames_codomain, dimnames_domain; kwargs...) -> x, y + TensorAlgebra.MatrixAlgebra.gram_eigh_full_with_pinv(a::AbstractNamedTensor, dimnames_codomain, dimnames_domain; kwargs...) -> x, y Like `TensorAlgebra.MatrixAlgebra.gram_eigh_full`, but additionally returns a named array `y` that is a left inverse of `x`: `y * x ≈ I` on the rank @@ -418,14 +418,14 @@ true ``` """ function MA.gram_eigh_full_with_pinv( - a::AbstractITensor, dimnames_codomain, dimnames_domain; kwargs... + a::AbstractNamedTensor, dimnames_codomain, dimnames_domain; kwargs... ) return gram_eigh_full_with_pinv_nameddims( a, dimnames_codomain, dimnames_domain; kwargs... ) end function gram_eigh_full_with_pinv_nameddims( - a::AbstractITensor, dimnames_codomain, dimnames_domain; kwargs... + a::AbstractNamedTensor, dimnames_codomain, dimnames_domain; kwargs... ) codomain = name.(dimnames_codomain) domain = name.(dimnames_domain) @@ -439,7 +439,7 @@ function gram_eigh_full_with_pinv_nameddims( end """ - Base.one(a::AbstractITensor, dimnames_codomain, dimnames_domain) -> Id + Base.one(a::AbstractNamedTensor, dimnames_codomain, dimnames_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 @@ -467,12 +467,12 @@ true ``` """ function Base.one( - a::AbstractITensor, dimnames_codomain, dimnames_domain + a::AbstractNamedTensor, dimnames_codomain, dimnames_domain ) return one_nameddims(a, dimnames_codomain, dimnames_domain) end function one_nameddims( - a::AbstractITensor, dimnames_codomain, dimnames_domain + a::AbstractNamedTensor, dimnames_codomain, dimnames_domain ) codomain = name.(dimnames_codomain) domain = name.(dimnames_domain) @@ -492,12 +492,12 @@ for f in MATRIX_FUNCTIONS f_nameddims = Symbol(f, "_nameddims") @eval begin function Base.$f( - a::AbstractITensor, dimnames_codomain, dimnames_domain; kwargs... + a::AbstractNamedTensor, dimnames_codomain, dimnames_domain; kwargs... ) return $f_nameddims(a, dimnames_codomain, dimnames_domain; kwargs...) end function $f_nameddims( - a::AbstractITensor, dimnames_codomain, dimnames_domain; kwargs... + a::AbstractNamedTensor, dimnames_codomain, dimnames_domain; kwargs... ) codomain = name.(dimnames_codomain) domain = name.(dimnames_domain) diff --git a/test/test_basics.jl b/test/test_basics.jl index d6bb6674..a4122a11 100644 --- a/test/test_basics.jl +++ b/test/test_basics.jl @@ -1,5 +1,5 @@ -using ITensorBase: ITensorBase, ITensor, Index, IndexName, dimnametype, gettag, hastag, id, - inds, mapinds, name, named, plev, prime, setplev, settag, tags, unname, unnamed, +using ITensorBase: ITensorBase, Index, IndexName, NamedTensor, dimnametype, gettag, hastag, + id, inds, mapinds, name, named, plev, prime, setplev, settag, tags, unname, unnamed, unsettag using Test: @test, @test_broken, @test_throws, @testset using UUIDs: UUID @@ -85,7 +85,7 @@ using UUIDs: UUID @test plev(i) == 0 @test length(tags(i)) == 1 end - @testset "ITensor basics" begin + @testset "NamedTensor basics" begin elt = Float64 i, j = Index.((2, 2)) x = randn(elt, 2, 2) @@ -100,12 +100,12 @@ using UUIDs: UUID # The number of dimnames must match the array's `ndims`, and the dimnames are # passed as a single collection. - @test_throws ArgumentError ITensor(randn(elt, 4), (:i, :j)) - @test_throws MethodError ITensor(randn(elt, 2, 2), :i, :j) + @test_throws ArgumentError NamedTensor(randn(elt, 4), (:i, :j)) + @test_throws MethodError NamedTensor(randn(elt, 2, 2), :i, :j) # The constructor takes names only, not indices, so passing indices (the # ITensors.jl idiom) errors. - @test_throws ArgumentError ITensor(randn(elt, 2, 2), Index.((2, 2))) + @test_throws ArgumentError NamedTensor(randn(elt, 2, 2), Index.((2, 2))) i, j = Index.((3, 4)) a = randn(elt, i, j) @@ -133,12 +133,12 @@ using UUIDs: UUID @testset "dimnametype" begin i, j = Index.((2, 3)) a = randn(Float64, i, j) - @test a isa ITensor + @test a isa NamedTensor @test dimnametype(a) === IndexName @test dimnametype(typeof(a)) === IndexName - @test dimnametype(ITensor{IndexName}) === IndexName - # Unparameterized `ITensor` does not fix its dimname flavor, like `eltype(Array)`. - @test dimnametype(ITensor) === Any + @test dimnametype(NamedTensor{IndexName}) === IndexName + # Unparameterized `NamedTensor` does not fix its dimname flavor, like `eltype(Array)`. + @test dimnametype(NamedTensor) === Any end @testset "show" begin i = Index(2) diff --git a/test/test_exports.jl b/test/test_exports.jl index b90b5c77..9a890c35 100644 --- a/test/test_exports.jl +++ b/test/test_exports.jl @@ -2,14 +2,16 @@ using ITensorBase: ITensorBase using Test: @test, @testset @testset "Test exports" begin exports = [ - :ITensorBase, :AbstractITensor, :ITensor, :Index, :NamedUnitRange, + :ITensorBase, :AbstractNamedTensor, :NamedTensor, :AbstractITensor, :ITensor, + :Index, :NamedUnitRange, :aligndims, :aligneddims, :apply, :codomainnames, :dimnames, :dimnametype, :domainnames, :inds, :named, :nameddims, :noprime, :operator, :prime, :similar_operator, :state, :uniquename, ] publics = [ - :name, :nametype, :replacedimnames, :setname, :unnamed, :unnamedtype, + :IndexName, :name, :nametype, :replacedimnames, :setname, :unnamed, + :unnamedtype, Symbol("@names"), ] if VERSION ≥ v"1.11-" diff --git a/test/test_lazyitensors.jl b/test/test_lazyitensors.jl index 9cd129ae..1bca3840 100644 --- a/test/test_lazyitensors.jl +++ b/test/test_lazyitensors.jl @@ -1,15 +1,15 @@ using AbstractTrees: AbstractTrees, print_tree, printnode using Base.Broadcast: materialize -using ITensorBase: @names, Greedy, ITensor, LazyITensor, Mul, Optimal, SymbolicITensor, - dimnames, inds, ismul, lazy, nameddims, namedoneto, optimize_evaluation_order, - substitute, symnameddims +using ITensorBase: @names, Greedy, LazyNamedTensor, Mul, NamedTensor, Optimal, + SymbolicNamedTensor, dimnames, inds, ismul, lazy, nameddims, namedoneto, + optimize_evaluation_order, substitute, symnameddims using TensorOperations: TensorOperations using TermInterface: arguments, arity, children, head, iscall, isexpr, maketerm, operation, sorted_arguments, sorted_children using Test: @test, @test_broken, @test_throws, @testset using WrappedUnions: unwrap -@testset "LazyITensors" begin +@testset "LazyNamedTensors" begin @testset "Basics" begin i, j, k, l = namedoneto.(2, (:i, :j, :k, :l)) a1 = randn(i, j) @@ -17,8 +17,8 @@ using WrappedUnions: unwrap a3 = randn(k, l) l1, l2, l3 = lazy.((a1, a2, a3)) for li in (l1, l2, l3) - @test li isa LazyITensor - @test unwrap(li) isa ITensor + @test li isa LazyNamedTensor + @test unwrap(li) isa NamedTensor @test inds(li) == inds(unwrap(li)) @test copy(li) == unwrap(li) @test materialize(li) == unwrap(li) @@ -64,7 +64,7 @@ using WrappedUnions: unwrap @test head(l) ≡ * @test iscall(l) @test isexpr(l) - @test l == maketerm(LazyITensor, *, [l1 * l2, l3], nothing) + @test l == maketerm(LazyNamedTensor, *, [l1 * l2, l3], nothing) @test operation(l) ≡ * @test sorted_arguments(l) == [l1 * l2, l3] @test sorted_children(l) == [l1 * l2, l3] @@ -81,10 +81,10 @@ using WrappedUnions: unwrap @testset "symnameddims" begin a1, a2, a3 = symnameddims.((:a1, :a2, :a3)) - @test a1 isa LazyITensor - @test unwrap(a1) isa SymbolicITensor - @test unwrap(a1) == SymbolicITensor(:a1, ()) - @test isequal(unwrap(a1), SymbolicITensor(:a1, ())) + @test a1 isa LazyNamedTensor + @test unwrap(a1) isa SymbolicNamedTensor + @test unwrap(a1) == SymbolicNamedTensor(:a1, ()) + @test isequal(unwrap(a1), SymbolicNamedTensor(:a1, ())) @test isempty(inds(a1)) @test isempty(dimnames(a1)) @@ -112,7 +112,7 @@ using WrappedUnions: unwrap 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 LazyITensor + @test ordered isa LazyNamedTensor @test ismul(ordered) # Reordering nests the flat product into binary contractions and preserves # the open indices. diff --git a/test/test_mooncakeext.jl b/test/test_mooncakeext.jl index b54fd85b..4faf1bee 100644 --- a/test/test_mooncakeext.jl +++ b/test/test_mooncakeext.jl @@ -1,5 +1,5 @@ -using ITensorBase: ITensor, Name, NamedUnitRange, dimnames, dimnames_setdiff, inds, name, - nameperm, to_inds, uniquename +using ITensorBase: Name, NamedTensor, NamedUnitRange, dimnames, dimnames_setdiff, inds, + name, nameperm, 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 474bbb0d..4d96ce90 100644 --- a/test/test_nameddims_basics.jl +++ b/test/test_nameddims_basics.jl @@ -1,8 +1,8 @@ using Combinatorics: Combinatorics -using ITensorBase: @names, AbstractITensor, ITensor, Name, NameMismatch, - NamedDimsCartesianIndex, NamedDimsCartesianIndices, 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, 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 LinearAlgebra: LinearAlgebra using Test: @test, @test_throws, @testset using VectorInterface: scalartype @@ -16,8 +16,8 @@ end a = randn(elt, 3, 4) @test !isnamed(a) na = nameddims(a, ("i", "j")) - @test na isa ITensor{String} - @test na isa AbstractITensor{String} + @test na isa NamedTensor{String} + @test na isa AbstractNamedTensor{String} @test eltype(na) === elt @test ndims(na) == 2 @test_throws MethodError unnamed(a) @@ -73,8 +73,11 @@ end @test CartesianIndices(na) == CartesianIndices(a) @test collect(pairs(na)) == (CartesianIndices(a) .=> a) - @test_throws ArgumentError ITensor(randn(4), namedoneto.((2, 2), ("i", "j"))) - ## @test_throws ErrorException ITensor(randn(2, 2), namedoneto.((2, 3), ("i", "j"))) + @test_throws ArgumentError NamedTensor( + randn(4), + namedoneto.((2, 2), ("i", "j")) + ) + ## @test_throws ErrorException NamedTensor(randn(2, 2), namedoneto.((2, 3), ("i", "j"))) a = randn(elt, 3, 4) na = nameddims(a, ("i", "j")) @@ -151,13 +154,13 @@ end i = namedoneto(2, "i") a = randn(elt, 2) na = a[i] - @test na isa ITensor{String} + @test na isa NamedTensor{String} @test dimnames(na) == ["i"] @test unnamed(na) == a # slicing a = randn(elt, 3, 3) - na = ITensor(a, ("i", "j")) + 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")] @test unnamed(na′) == a[2:3, 2:3] @@ -166,7 +169,7 @@ end # view slicing a = randn(elt, 3, 3) - na = ITensor(a, ("i", "j")) + 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")] @@ -231,10 +234,10 @@ end na[1, 1] = 11 @test na[1, 1] == 11 @test size(na) == (3, 4) - # An ITensor's `length` is the plain element count (product of its size). + # 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 ITensor + @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 @@ -367,26 +370,26 @@ end value = rand(elt) for na in (zeros(elt, i, j), zeros(elt, (i, j))) @test eltype(na) ≡ elt - @test na isa ITensor + @test na isa NamedTensor @test inds(na) == Base.oneto.([i, j]) @test iszero(na) end for na in (fill(value, i, j), fill(value, (i, j))) @test eltype(na) ≡ elt - @test na isa ITensor + @test na isa NamedTensor @test inds(na) == Base.oneto.([i, j]) @test all(isequal(value), na) end for na in (rand(elt, i, j), rand(elt, (i, j))) @test eltype(na) ≡ elt - @test na isa ITensor + @test na isa NamedTensor @test inds(na) == Base.oneto.([i, j]) @test !iszero(na) @test all(x -> real(x) > 0, na) end for na in (randn(elt, i, j), randn(elt, (i, j))) @test eltype(na) ≡ elt - @test na isa ITensor + @test na isa NamedTensor @test inds(na) == Base.oneto.([i, j]) @test !iszero(na) end @@ -396,32 +399,32 @@ end default_elt = Float64 for na in (zeros(i, j), zeros((i, j))) @test eltype(na) ≡ default_elt - @test na isa ITensor + @test na isa NamedTensor @test inds(na) == Base.oneto.([i, j]) @test iszero(na) end for na in (rand(i, j), rand((i, j))) @test eltype(na) ≡ default_elt - @test na isa ITensor + @test na isa NamedTensor @test inds(na) == Base.oneto.([i, j]) @test !iszero(na) @test all(x -> real(x) > 0, na) end for na in (randn(i, j), randn((i, j))) @test eltype(na) ≡ default_elt - @test na isa ITensor + @test na isa NamedTensor @test inds(na) == Base.oneto.([i, j]) @test !iszero(na) end end @testset "show" begin - a = ITensor([1 2; 3 4], ("i", "j")) + a = NamedTensor([1 2; 3 4], ("i", "j")) @test sprint(show, "text/plain", a) == "named(Base.OneTo(2), \"i\")×named(Base.OneTo(2), \"j\") " * - "$ITensor{String}:\n" * + "$NamedTensor{String}:\n" * "2×2 Matrix{Int64}:\n 1 2\n 3 4" - a = ITensor([1 2; 3 4], ("i", "j")) + 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\")]" end diff --git a/test/test_operator.jl b/test/test_operator.jl index dd3b8bb6..5341db92 100644 --- a/test/test_operator.jl +++ b/test/test_operator.jl @@ -1,6 +1,6 @@ -using ITensorBase: ITensorBase as NDA, ITensor, ITensorOperator, apply, codomainnames, - dimnames, domainnames, nameddims, namedoneto, operator, product, replacedimnames, - similar_operator, state, unname, unnamed +using ITensorBase: ITensorBase as NDA, NamedTensor, NamedTensorOperator, apply, + codomainnames, dimnames, domainnames, nameddims, namedoneto, operator, product, + replacedimnames, similar_operator, state, unname, unnamed using LinearAlgebra: I, norm using Random: Random using StableRNGs: StableRNG @@ -10,27 +10,27 @@ using Test: @test, @test_throws, @testset @testset "operator" begin o = operator(randn(2, 2, 2, 2), ("i'", "j'"), ("i", "j")) - @test o isa ITensorOperator{String} + @test o isa NamedTensorOperator{String} @test eltype(o) ≡ Float64 @test issetequal(NDA.codomainnames(o), ("i'", "j'")) @test issetequal(NDA.domainnames(o), ("i", "j")) o = operator(randn(2, 2, 2, 2), ("i'", "j'"), ("i", "j")) õ = similar(o) - @test õ isa ITensorOperator{String} + @test õ isa NamedTensorOperator{String} @test eltype(õ) ≡ Float64 @test issetequal(NDA.codomainnames(õ), ("i'", "j'")) @test issetequal(NDA.domainnames(õ), ("i", "j")) o = operator(randn(2, 2, 2, 2), ("i'", "j'"), ("i", "j")) õ = similar(o, Float32) - @test õ isa ITensorOperator{String} + @test õ isa NamedTensorOperator{String} @test eltype(õ) ≡ Float32 @test issetequal(NDA.codomainnames(õ), ("i'", "j'")) @test issetequal(NDA.domainnames(õ), ("i", "j")) o = operator(randn(2, 2, 2, 2), ("i'", "j'"), ("i", "j")) - @test o isa ITensorOperator + @test o isa NamedTensorOperator o² = product(o, o) @test issetequal(dimnames(o²), ("i'", "j'", "i", "j")) õ = replacedimnames( @@ -40,7 +40,7 @@ using Test: @test, @test_throws, @testset @test state(o²) ≈ o²′ o = operator(randn(2, 2, 2, 2), ("i'", "j'"), ("i", "j")) - v = ITensor(randn(2, 2), ("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") @@ -50,24 +50,24 @@ end # Codomain/domain may be given as named ranges, not just names. i, ip = namedoneto(2, "i"), namedoneto(2, "i'") o = operator(randn(2, 2), [ip], [i]) - @test o isa ITensorOperator{String} + @test o isa NamedTensorOperator{String} @test issetequal(codomainnames(o), ("i'",)) @test issetequal(domainnames(o), ("i",)) end -@testset "one(::ITensorOperator)" begin +@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")) op = operator(randn(i, j, k, l), ("i", "j"), ("k", "l")) Id = one(op) - @test Id isa ITensorOperator{String} + @test Id isa NamedTensorOperator{String} @test codomainnames(Id) == codomainnames(op) @test domainnames(Id) == domainnames(op) Id_mat = matricize(state(Id), (i, j) => "row", (k, l) => "col") @test unname(Id_mat, ("row", "col")) ≈ I(6) end -@testset "one(::AbstractITensor, codomain, domain)" begin +@testset "one(::AbstractNamedTensor, codomain, domain)" begin # Trivial codomain/domain layout. i, j, k, l = namedoneto.((2, 3, 2, 3), ("i", "j", "k", "l")) a = randn(i, j, k, l) @@ -87,13 +87,13 @@ end @testset "similar_operator" begin # Five-arg canonical: explicit element type, axes, codomain, domain names. op = similar_operator(randn(3, 3), Float32, (Base.OneTo(3),), ("i'",), ("i",)) - @test op isa ITensorOperator{String} + @test op isa NamedTensorOperator{String} @test issetequal(codomainnames(op), ("i'",)) @test issetequal(domainnames(op), ("i",)) # Codomain names default to fresh `uniquename` outputs. op = similar_operator(randn(3, 3), Float64, (Base.OneTo(3),), ("i",)) - @test op isa ITensorOperator{String} + @test op isa NamedTensorOperator{String} @test issetequal(domainnames(op), ("i",)) @test only(codomainnames(op)) != "i" @@ -108,7 +108,7 @@ end @test eltype(op) === ComplexF32 end -@testset "randn!(::ITensorOperator) / rand!" begin +@testset "randn!(::NamedTensorOperator) / rand!" begin op = operator(zeros(3, 3), ("i'",), ("i",)) rng = StableRNG(123) Random.randn!(rng, op) @@ -122,14 +122,14 @@ end @testset "operator-preserving broadcasting" begin # `+`, `-`, and scalar multiplication lower to broadcasting. An operator # broadcasts as itself (it is not peeled to its `state`), so these operations - # preserve the `ITensorOperator` wrapper and its codomain/domain bijection. + # preserve the `NamedTensorOperator` wrapper and its codomain/domain bijection. # (Contraction `*` is operator-preserving too, in its own testset below.) o = operator(randn(2, 2), ("i'",), ("i",)) s = state(o) nms = ("i'", "i") for r in (o + o, o - o, -o, 2 * o, o * 2, 2 .* o, o .* 2, o ./ 2) - @test r isa ITensorOperator + @test r isa NamedTensorOperator @test issetequal(codomainnames(r), ("i'",)) @test issetequal(domainnames(r), ("i",)) end @@ -144,16 +144,16 @@ end @test unname(state(o ./ 2), nms) ≈ unname(s, nms) ./ 2 # `o` shares both its names with itself, so `o * o` fully contracts to a - # scalar with no surviving codomain/domain. It is still an `ITensorOperator` + # scalar with no surviving codomain/domain. It is still an `NamedTensorOperator` # (with empty codomain/domain), so the product type does not depend on which # names happen to contract. oo = o * o - @test oo isa ITensorOperator + @test oo isa NamedTensorOperator @test isempty(codomainnames(oo)) @test isempty(domainnames(oo)) # Operator combined with a non-operator tensor is rejected. - plain = ITensor(randn(2, 2), ("i'", "i")) + plain = NamedTensor(randn(2, 2), ("i'", "i")) @test_throws ArgumentError o .+ plain # Two operators whose name sets match but whose codomain/domain split differs @@ -169,7 +169,7 @@ end a = operator(nameddims(randn(2, 2, 3), ("i'", "i", "aux")), ["i'"], ["i"]) b = operator(nameddims(randn(2, 2, 3), ("j'", "j", "aux")), ["j'"], ["j"]) ab = a * b - @test ab isa ITensorOperator + @test ab isa NamedTensorOperator @test issetequal(codomainnames(ab), ("i'", "j'")) @test issetequal(domainnames(ab), ("i", "j")) @test !("aux" in dimnames(ab)) @@ -180,22 +180,22 @@ end A = operator(randn(2, 2), ("a'",), ("m",)) B = operator(randn(2, 2), ("m",), ("b",)) AB = A * B - @test AB isa ITensorOperator + @test AB isa NamedTensorOperator @test issetequal(codomainnames(AB), ("a'",)) @test issetequal(domainnames(AB), ("b",)) # Applying an operator to a plain state contracts the operator's domain and - # leaves its output dangling. The result stays an `ITensorOperator` with empty + # leaves its output dangling. The result stays an `NamedTensorOperator` with empty # codomain/domain (the surviving `a'` leg is dangling, in neither). v = nameddims(randn(2), ("m",)) Av = operator(randn(2, 2), ("a'",), ("m",)) * v - @test Av isa ITensorOperator + @test Av isa NamedTensorOperator @test isempty(codomainnames(Av)) @test isempty(domainnames(Av)) @test issetequal(dimnames(Av), ("a'",)) end -@testset "gram_eigh_full on ITensorOperator" begin +@testset "gram_eigh_full on NamedTensorOperator" begin n = 5 B = randn(n, n) A = B * B' # Hermitian PSD diff --git a/test/test_vectorinterface.jl b/test/test_vectorinterface.jl index 9e5603c8..a1fdde93 100644 --- a/test/test_vectorinterface.jl +++ b/test/test_vectorinterface.jl @@ -2,7 +2,7 @@ import VectorInterface as VI using ITensorBase: dimnames, named, unnamed using Test: @test, @testset -# These name-aware methods are what let an ITensor be used directly as a vector in +# These name-aware methods are what let an NamedTensor be used directly as a vector in # iterative solvers such as `KrylovKit.eigsolve`, which drive their Krylov vectors # through `VectorInterface`. @testset "VectorInterface (eltype=$(elt))" for elt in