From a312aa4140b7c6e0a2f8d6c40ffbe89a8b7d9a64 Mon Sep 17 00:00:00 2001 From: Lukas Devos Date: Mon, 9 Feb 2026 13:09:16 -0500 Subject: [PATCH 1/9] add `storagetype` implementations for more different tensor types --- src/tensors/abstracttensor.jl | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/src/tensors/abstracttensor.jl b/src/tensors/abstracttensor.jl index 7f068369a..16b5fc902 100644 --- a/src/tensors/abstracttensor.jl +++ b/src/tensors/abstracttensor.jl @@ -38,12 +38,30 @@ function InnerProductStyle(::Type{TT}) where {TT <: AbstractTensorMap} return InnerProductStyle(spacetype(TT)) end +# storage types and promotion system +# ---------------------------------- @doc """ storagetype(t::AbstractTensorMap) -> Type{A<:AbstractVector} storagetype(T::Type{<:AbstractTensorMap}) -> Type{A<:AbstractVector} Return the type of vector that stores the data of a tensor. +If this is not overloaded for a given tensor type, the default value of `storagetype(scalartype(t))` is returned. + +See also [`similarstoragetype`](@ref). """ storagetype +storagetype(t) = storagetype(typeof(t)) +function storagetype(::Type{T}) where {T <: AbstractTensorMap} + if T isa Union + # attempt to be slightly more specific by promoting unions + Ma = storagetype(T.a) + Mb = storagetype(T.b) + return promote_storagetype(Ma, Mb) + else + # fallback definition by using scalartype + return similarstoragetype(scalartype(T)) + end +end +storagetype(T::Type) = throw(MethodError(storagetype, T)) # storage type determination and promotion - hooks for specializing # the default implementation tries to leverarge inference and `similar` @@ -224,8 +242,7 @@ end # tensor characteristics: work on instances and pass to type #------------------------------------------------------------ InnerProductStyle(t::AbstractTensorMap) = InnerProductStyle(typeof(t)) -storagetype(t) = storagetype(typeof(t)) -storagetype(T::Type) = throw(MethodError(storagetype, T)) + blocktype(t::AbstractTensorMap) = blocktype(typeof(t)) numout(t::AbstractTensorMap) = numout(typeof(t)) From ce6354b3a8d55dcc40231a6f626e3db1d844444b Mon Sep 17 00:00:00 2001 From: Lukas Devos Date: Mon, 9 Feb 2026 14:22:01 -0500 Subject: [PATCH 2/9] remove braidingtensor storagetype specialization --- src/tensors/braidingtensor.jl | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/tensors/braidingtensor.jl b/src/tensors/braidingtensor.jl index 9c92c67a1..224049727 100644 --- a/src/tensors/braidingtensor.jl +++ b/src/tensors/braidingtensor.jl @@ -57,9 +57,6 @@ end space(b::BraidingTensor) = b.adjoint ? b.V1 ⊗ b.V2 ← b.V2 ⊗ b.V1 : b.V2 ⊗ b.V1 ← b.V1 ⊗ b.V2 -# TODO: this will probably give issues with GPUs, so we should try to avoid -# calling this method alltogether -storagetype(::Type{BraidingTensor{T, S}}) where {T, S} = Vector{T} function Base.getindex(b::BraidingTensor) sectortype(b) === Trivial || throw(SectorMismatch()) From 8d4b338144aad5c5b9e433dbbcb03defe92b0fa4 Mon Sep 17 00:00:00 2001 From: Lukas Devos Date: Mon, 9 Feb 2026 14:22:41 -0500 Subject: [PATCH 3/9] introduce storagetype promotion system --- src/tensors/abstracttensor.jl | 61 +++++++++++++++++++++++++++++++++ src/tensors/tensoroperations.jl | 5 --- 2 files changed, 61 insertions(+), 5 deletions(-) diff --git a/src/tensors/abstracttensor.jl b/src/tensors/abstracttensor.jl index 16b5fc902..1812eb8a6 100644 --- a/src/tensors/abstracttensor.jl +++ b/src/tensors/abstracttensor.jl @@ -87,6 +87,8 @@ appropriate storage types. Additionally this registers the default storage type used in constructor-like calls, and therefore will return the exact same type for a `DenseVector` input. The latter is used in `similar`-like calls, and therefore will return the type of calling `similar` on the given `DenseVector`, which need not coincide with the original type. + +See also [`promote_storagetype`](@ref). """ similarstoragetype # implement in type domain @@ -120,6 +122,65 @@ similarstoragetype(::Type{D}, ::Type{T}) where {D <: AbstractDict{<:Sector, <:Ab # default storage type for numbers similarstoragetype(::Type{T}) where {T <: Number} = Vector{T} +@doc """ + promote_storagetype([T], A, B) + promote_storagetype([T], TA, TB) + +Determine an appropriate storage type for the combination of tensors `A` and `B`, or tensors of type `TA` and `TB`. +Optionally, a scalartype `T` for the destination can be supplied that might differ from the inputs. +""" promote_storagetype + +promote_storagetype(A::AbstractTensorMap, B::AbstractTensorMap) = + promote_storagetype(typeof(A), typeof(B)) +promote_storagetype(::Type{T}, A::AbstractTensorMap, B::AbstractTensorMap) where {T <: Number} = + promote_storagetype(T, typeof(A), typeof(B)) + +promote_storagetype(::Type{A}, ::Type{B}) where {A <: AbstractTensorMap, B <: AbstractTensorMap} = + promote_storagetype(storagetype(A), storagetype(B)) +promote_storagetype(::Type{T}, ::Type{A}, ::Type{B}) where {T <: Number, A <: AbstractTensorMap, B <: AbstractTensorMap} = + promote_storagetype(similarstoragetype(A, T), similarstoragetype(B, T)) + +# promotion system in the same spirit as base/promotion.jl +promote_storagetype(::Type{Base.Bottom}, ::Type{Base.Bottom}) = Base.Bottom +promote_storagetype(::Type{T}, ::Type{T}) where {T} = T +promote_storagetype(::Type{T}, ::Type{Base.Bottom}) where {T} = T +promote_storagetype(::Type{Base.Bottom}, ::Type{T}) where {T} = T + +function promote_storagetype(::Type{T}, ::Type{S}) where {T, S} + @inline + # Try promote_storage_rule in both orders. Typically only one is defined, + # and there is a fallback returning Bottom below, so the common case is + # promote_storagetype(T, S) => + # promote_storage_result(T, S, result, Bottom) => + # typejoin(result, Bottom) => result + return promote_storage_result(T, S, promote_storage_rule(T, S), promote_storage_rule(S, T)) +end + +@doc """ + promote_storage_rule(type1, type2) + +Specifies what type should be used by [`promote_storagetype`](@ref) when given values of types `type1` and +`type2`. This function should not be called directly, but should have definitions added to +it for new types as appropriate. +""" promote_storage_rule + +promote_storage_rule(::Type, ::Type) = Base.Bottom +# Define some methods to avoid needing to enumerate unrelated possibilities when presented +# with Type{<:T}, and return a value in general accordance with the result given by promote_type +promote_storage_rule(::Type{Base.Bottom}, slurp...) = Base.Bottom +promote_storage_rule(::Type{Base.Bottom}, ::Type{Base.Bottom}, slurp...) = Base.Bottom # not strictly necessary, since the next method would match unambiguously anyways +promote_storage_rule(::Type{Base.Bottom}, ::Type{T}, slurp...) where {T} = T +promote_storage_rule(::Type{T}, ::Type{Base.Bottom}, slurp...) where {T} = T + +promote_storage_result(::Type, ::Type, ::Type{T}, ::Type{S}) where {T, S} = (@inline; promote_storagetype(T, S)) +# If no promote_storage_rule is defined, both directions give Bottom => error +promote_storage_result(T::Type, S::Type, ::Type{Base.Bottom}, ::Type{Base.Bottom}) = + throw(ArgumentError("No promotion rule defined for storagetype `$T` and `$S`")) + +# promotion rules for common vector types +promote_storage_rule(::Type{T}, ::Type{S}) where {T <: DenseVector, S <: DenseVector} = + T === S ? T : throw(ArgumentError("No promotion rule defined for storagetype `$T` and `$S`")) + # tensor characteristics: space and index information #----------------------------------------------------- """ diff --git a/src/tensors/tensoroperations.jl b/src/tensors/tensoroperations.jl index 81894d140..fa7b68dcb 100644 --- a/src/tensors/tensoroperations.jl +++ b/src/tensors/tensoroperations.jl @@ -158,11 +158,6 @@ function TO.tensorcontract_type( return tensormaptype(S, N₁, N₂, M) end -# TODO: handle actual promotion rule system -function promote_storagetype(::Type{M₁}, ::Type{M₂}) where {M₁, M₂} - return M₁ === M₂ ? M₁ : throw(ArgumentError("Cannot determine storage type for combining `$M₁` and `$M₂`")) -end - function TO.tensorcontract_structure( A::AbstractTensorMap, pA::Index2Tuple, conjA::Bool, B::AbstractTensorMap, pB::Index2Tuple, conjB::Bool, From 8287cabfca1ee9f97ec3e48a897c684919484a03 Mon Sep 17 00:00:00 2001 From: Lukas Devos Date: Mon, 9 Feb 2026 14:22:51 -0500 Subject: [PATCH 4/9] use storagetype promotion system --- src/tensors/diagonal.jl | 10 ++++------ src/tensors/linalg.jl | 3 +-- src/tensors/tensor.jl | 3 +-- src/tensors/tensoroperations.jl | 3 +-- 4 files changed, 7 insertions(+), 12 deletions(-) diff --git a/src/tensors/diagonal.jl b/src/tensors/diagonal.jl index 8232710c5..7652a2fba 100644 --- a/src/tensors/diagonal.jl +++ b/src/tensors/diagonal.jl @@ -272,9 +272,8 @@ function TO.tensorcontract_type( ::Index2Tuple{1, 1} ) S = check_spacetype(A, B) - TC′ = promote_permute(TC, sectortype(S)) - M = promote_storagetype(similarstoragetype(A, TC′), similarstoragetype(B, TC′)) - return DiagonalTensorMap{TC, S, M} + M = promote_storagetype(promote_permute(TC, sectortype(S)), A, B) + return DiagonalTensorMap{scalartype(M), S, M} end function TO.tensoralloc( @@ -303,9 +302,8 @@ end function compose_dest(A::DiagonalTensorMap, B::DiagonalTensorMap) S = check_spacetype(A, B) - TC = TO.promote_contract(scalartype(A), scalartype(B), One) - M = promote_storagetype(similarstoragetype(A, TC), similarstoragetype(B, TC)) - TTC = DiagonalTensorMap{TC, S, M} + M = promote_storagetype(TO.promote_contract(scalartype(A), scalartype(B), One), A, B) + TTC = DiagonalTensorMap{scalartype(M), S, M} structure = codomain(A) ← domain(B) return TO.tensoralloc(TTC, structure, Val(false)) end diff --git a/src/tensors/linalg.jl b/src/tensors/linalg.jl index 5ac948802..900ee84fe 100644 --- a/src/tensors/linalg.jl +++ b/src/tensors/linalg.jl @@ -23,8 +23,7 @@ LinearAlgebra.normalize(t::AbstractTensorMap, p::Real = 2) = scale(t, inv(norm(t # permutations, which might require complex scalartypes even if the inputs are real. function compose_dest(A::AbstractTensorMap, B::AbstractTensorMap) S = check_spacetype(A, B) - TC = TO.promote_contract(scalartype(A), scalartype(B), One) - M = promote_storagetype(similarstoragetype(A, TC), similarstoragetype(B, TC)) + M = promote_storagetype(TO.promote_contract(scalartype(A), scalartype(B), One), A, B) TTC = tensormaptype(S, numout(A), numin(B), M) structure = codomain(A) ← domain(B) return TO.tensoralloc(TTC, structure, Val(false)) diff --git a/src/tensors/tensor.jl b/src/tensors/tensor.jl index 12281d758..615c67775 100644 --- a/src/tensors/tensor.jl +++ b/src/tensors/tensor.jl @@ -560,7 +560,6 @@ end function Base.promote_rule( ::Type{<:TT₁}, ::Type{<:TT₂} ) where {S, N₁, N₂, TT₁ <: TensorMap{<:Any, S, N₁, N₂}, TT₂ <: TensorMap{<:Any, S, N₁, N₂}} - T = VectorInterface.promote_add(scalartype(TT₁), scalartype(TT₂)) - A = promote_storagetype(similarstoragetype(TT₁, T), similarstoragetype(TT₂, T)) + A = promote_storagetype(VectorInterface.promote_add(scalartype(TT₁), scalartype(TT₂)), TT₁, TT₂) return tensormaptype(S, N₁, N₂, A) end diff --git a/src/tensors/tensoroperations.jl b/src/tensors/tensoroperations.jl index fa7b68dcb..0820fe1af 100644 --- a/src/tensors/tensoroperations.jl +++ b/src/tensors/tensoroperations.jl @@ -153,8 +153,7 @@ function TO.tensorcontract_type( ::Index2Tuple{N₁, N₂} ) where {N₁, N₂} S = check_spacetype(A, B) - TC′ = promote_permute(TC, sectortype(S)) - M = promote_storagetype(similarstoragetype(A, TC′), similarstoragetype(B, TC′)) + M = promote_storagetype(promote_permute(TC, sectortype(S)), A, B) return tensormaptype(S, N₁, N₂, M) end From 99956cd3434f5dd70b307cbf3439dd3436eab64d Mon Sep 17 00:00:00 2001 From: Lukas Devos Date: Mon, 9 Feb 2026 14:23:05 -0500 Subject: [PATCH 5/9] bypass storagetype promotion system for braidingtensor --- src/tensors/braidingtensor.jl | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/tensors/braidingtensor.jl b/src/tensors/braidingtensor.jl index 224049727..97fb2fbba 100644 --- a/src/tensors/braidingtensor.jl +++ b/src/tensors/braidingtensor.jl @@ -57,6 +57,17 @@ end space(b::BraidingTensor) = b.adjoint ? b.V1 ⊗ b.V2 ← b.V2 ⊗ b.V1 : b.V2 ⊗ b.V1 ← b.V1 ⊗ b.V2 +# specializations to ignore the storagetype of BraidingTensor +promote_storagetype(::Type{A}, ::Type{B}) where {A <: BraidingTensor, B <: AbstractTensorMap} = storagetype(B) +promote_storagetype(::Type{A}, ::Type{B}) where {A <: AbstractTensorMap, B <: BraidingTensor} = storagetype(A) +promote_storagetype(::Type{A}, ::Type{B}) where {A <: BraidingTensor, B <: BraidingTensor} = storagetype(A) + +promote_storagetype(::Type{T}, ::Type{A}, ::Type{B}) where {T <: Number, A <: BraidingTensor, B <: AbstractTensorMap} = + similarstoragetype(B, T) +promote_storagetype(::Type{T}, ::Type{A}, ::Type{B}) where {T <: Number, A <: AbstractTensorMap, B <: BraidingTensor} = + similarstoragetype(A, T) +promote_storagetype(::Type{T}, ::Type{A}, ::Type{B}) where {T <: Number, A <: BraidingTensor, B <: BraidingTensor} = + similarstoragetype(A, T) function Base.getindex(b::BraidingTensor) sectortype(b) === Trivial || throw(SectorMismatch()) From fb3e71f4f1474722aef9f926c161d3c7a6c53e0f Mon Sep 17 00:00:00 2001 From: Lukas Devos Date: Tue, 10 Feb 2026 09:42:27 -0500 Subject: [PATCH 6/9] immediately use storagetype --- src/tensors/abstracttensor.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tensors/abstracttensor.jl b/src/tensors/abstracttensor.jl index 1812eb8a6..582f68699 100644 --- a/src/tensors/abstracttensor.jl +++ b/src/tensors/abstracttensor.jl @@ -131,9 +131,9 @@ Optionally, a scalartype `T` for the destination can be supplied that might diff """ promote_storagetype promote_storagetype(A::AbstractTensorMap, B::AbstractTensorMap) = - promote_storagetype(typeof(A), typeof(B)) + promote_storagetype(storagetype(A), storagetype(B)) promote_storagetype(::Type{T}, A::AbstractTensorMap, B::AbstractTensorMap) where {T <: Number} = - promote_storagetype(T, typeof(A), typeof(B)) + promote_storagetype(T, storagetype(A), storagetype(B)) promote_storagetype(::Type{A}, ::Type{B}) where {A <: AbstractTensorMap, B <: AbstractTensorMap} = promote_storagetype(storagetype(A), storagetype(B)) From a8f6b7c4de47b8d612e25b7b11c29252d8feca48 Mon Sep 17 00:00:00 2001 From: Lukas Devos Date: Tue, 10 Feb 2026 09:49:11 -0500 Subject: [PATCH 7/9] add implementation for n-ary promotion --- src/tensors/abstracttensor.jl | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/src/tensors/abstracttensor.jl b/src/tensors/abstracttensor.jl index 582f68699..ea3cd5fdf 100644 --- a/src/tensors/abstracttensor.jl +++ b/src/tensors/abstracttensor.jl @@ -123,22 +123,28 @@ similarstoragetype(::Type{D}, ::Type{T}) where {D <: AbstractDict{<:Sector, <:Ab similarstoragetype(::Type{T}) where {T <: Number} = Vector{T} @doc """ - promote_storagetype([T], A, B) - promote_storagetype([T], TA, TB) + promote_storagetype([T], A, B, C...) + promote_storagetype([T], TA, TB, TC...) Determine an appropriate storage type for the combination of tensors `A` and `B`, or tensors of type `TA` and `TB`. Optionally, a scalartype `T` for the destination can be supplied that might differ from the inputs. """ promote_storagetype -promote_storagetype(A::AbstractTensorMap, B::AbstractTensorMap) = - promote_storagetype(storagetype(A), storagetype(B)) -promote_storagetype(::Type{T}, A::AbstractTensorMap, B::AbstractTensorMap) where {T <: Number} = - promote_storagetype(T, storagetype(A), storagetype(B)) +@inline promote_storagetype(A::AbstractTensorMap, B::AbstractTensorMap, Cs::AbstractTensorMap...) = + promote_storagetype(storagetype(A), storagetype(B), map(storagetype, Cs)...) +@inline promote_storagetype(::Type{T}, A::AbstractTensorMap, B::AbstractTensorMap, Cs::AbstractTensorMap...) where {T <: Number} = + promote_storagetype(T, storagetype(A), storagetype(B), map(storagetype, Cs)...) -promote_storagetype(::Type{A}, ::Type{B}) where {A <: AbstractTensorMap, B <: AbstractTensorMap} = - promote_storagetype(storagetype(A), storagetype(B)) -promote_storagetype(::Type{T}, ::Type{A}, ::Type{B}) where {T <: Number, A <: AbstractTensorMap, B <: AbstractTensorMap} = - promote_storagetype(similarstoragetype(A, T), similarstoragetype(B, T)) +@inline function promote_storagetype( + ::Type{A}, ::Type{B}, Cs::Type{C}... + ) where {A <: AbstractTensorMap, B <: AbstractTensorMap, C <: AbstractTensorMap} + return promote_storagetype(storagetype(A), storagetype(B), map(storagetype, Cs)...) +end +@inline function promote_storagetype( + ::Type{T}, ::Type{A}, ::Type{B}, Cs::Type{C}... + ) where {T <: Number, A <: AbstractTensorMap, B <: AbstractTensorMap, C <: AbstractTensorMap} + return promote_storagetype(similarstoragetype(A, T), similarstoragetype(B, T), map(Base.Fix2(similarstoragetype, T), Cs)...) +end # promotion system in the same spirit as base/promotion.jl promote_storagetype(::Type{Base.Bottom}, ::Type{Base.Bottom}) = Base.Bottom @@ -156,6 +162,9 @@ function promote_storagetype(::Type{T}, ::Type{S}) where {T, S} return promote_storage_result(T, S, promote_storage_rule(T, S), promote_storage_rule(S, T)) end +@inline promote_storagetype(T, S, U) = promote_storagetype(promote_storagetype(T, S), U) +@inline promote_storagetype(T, S, U, V...) = promote_storagetype(promote_storagetype(T, S), U, V...) + @doc """ promote_storage_rule(type1, type2) From 6d9d48a72b1fc4f453282ffa150ac59ce821e69a Mon Sep 17 00:00:00 2001 From: Lukas Devos Date: Tue, 10 Feb 2026 09:52:44 -0500 Subject: [PATCH 8/9] update Changelog --- docs/src/Changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/src/Changelog.md b/docs/src/Changelog.md index 6fd79f4df..2f5e568f2 100644 --- a/docs/src/Changelog.md +++ b/docs/src/Changelog.md @@ -22,6 +22,7 @@ When releasing a new version, move the "Unreleased" changes to a new version sec ### Added +- A more robust promotion system for `storagetype`s to better handle working with unions and other abstract tensor map types ([#370](https://github.com/QuantumKitHub/TensorKit.jl/pull/370)). ### Changed From 39e48858a83eca083fb2f5ec2559b63d65ce47fd Mon Sep 17 00:00:00 2001 From: Lukas Devos Date: Tue, 10 Feb 2026 10:57:06 -0500 Subject: [PATCH 9/9] make Aqua happy --- src/tensors/abstracttensor.jl | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/tensors/abstracttensor.jl b/src/tensors/abstracttensor.jl index ea3cd5fdf..2d7239460 100644 --- a/src/tensors/abstracttensor.jl +++ b/src/tensors/abstracttensor.jl @@ -133,16 +133,16 @@ Optionally, a scalartype `T` for the destination can be supplied that might diff @inline promote_storagetype(A::AbstractTensorMap, B::AbstractTensorMap, Cs::AbstractTensorMap...) = promote_storagetype(storagetype(A), storagetype(B), map(storagetype, Cs)...) @inline promote_storagetype(::Type{T}, A::AbstractTensorMap, B::AbstractTensorMap, Cs::AbstractTensorMap...) where {T <: Number} = - promote_storagetype(T, storagetype(A), storagetype(B), map(storagetype, Cs)...) + promote_storagetype(similarstoragetype(A, T), similarstoragetype(B, T), map(Base.Fix2(similarstoragetype, T), Cs)...) @inline function promote_storagetype( - ::Type{A}, ::Type{B}, Cs::Type{C}... - ) where {A <: AbstractTensorMap, B <: AbstractTensorMap, C <: AbstractTensorMap} + ::Type{A}, ::Type{B}, Cs::Type{<:AbstractTensorMap}... + ) where {A <: AbstractTensorMap, B <: AbstractTensorMap} return promote_storagetype(storagetype(A), storagetype(B), map(storagetype, Cs)...) end @inline function promote_storagetype( - ::Type{T}, ::Type{A}, ::Type{B}, Cs::Type{C}... - ) where {T <: Number, A <: AbstractTensorMap, B <: AbstractTensorMap, C <: AbstractTensorMap} + ::Type{T}, ::Type{A}, ::Type{B}, Cs::Type{<:AbstractTensorMap}... + ) where {T <: Number, A <: AbstractTensorMap, B <: AbstractTensorMap} return promote_storagetype(similarstoragetype(A, T), similarstoragetype(B, T), map(Base.Fix2(similarstoragetype, T), Cs)...) end