From 4614b88663d0cb0b71337e9c5abcc07f39aca5db Mon Sep 17 00:00:00 2001 From: leburgel Date: Wed, 14 Oct 2026 12:59:06 +0200 Subject: [PATCH 1/2] Cheaper `ProductSector` symbols The sizes needed to promote a scalar symbol of a factor to an array take several `Nsymbol` evaluations; they are now only computed when one of the two symbols is a scalar. A scalar symbol without multiplicities scales the other symbol (which is returned as is for a scalar one), instead of going through `fill` and `_kron`. Co-Authored-By: Claude Opus 5.5 --- src/auxiliary.jl | 18 +++++++++++++++--- src/product.jl | 36 +++++++++++++++++------------------- 2 files changed, 32 insertions(+), 22 deletions(-) diff --git a/src/auxiliary.jl b/src/auxiliary.jl index 6ed53605..92bf52de 100644 --- a/src/auxiliary.jl +++ b/src/auxiliary.jl @@ -13,10 +13,22 @@ function _kron(A, B) end return C end -_kron_promote(A::Number, B::Number, _, _) = A * B -function _kron_promote(A₁, B₁, sz₁, sz₂) - return _kron(A₁ isa Number ? fill(A₁, sz₁) : A₁, B₁ isa Number ? fill(B₁, sz₂) : B₁) +# combine the symbols `A` and `B` of the factors `a` and `b` of product sectors; a scalar symbol +# is promoted to an array with the size given by the multiplicities of its sectors, unless these +# are all one: then it just scales the other symbol, which is returned as is for a scalar one +# (symbols are not mutated, and may already be shared with a cache) +_kron_promote(A::Number, B::Number, a, b) = A * B +function _kron_promote(A::Number, B::AbstractArray, a, b) + sz = _symbol_size(a) + all(isone, sz) || return _kron(fill(A, sz), B) + return isone(A) && promote_type(typeof(A), eltype(B)) === eltype(B) ? B : A .* B end +function _kron_promote(A::AbstractArray, B::Number, a, b) + sz = _symbol_size(b) + all(isone, sz) || return _kron(A, fill(B, sz)) + return isone(B) && promote_type(eltype(A), typeof(B)) === eltype(A) ? A : A .* B +end +_kron_promote(A::AbstractArray, B::AbstractArray, a, b) = _kron(A, B) # Manhattan based distance enumeration: I is supposed to be one-based index # TODO: is there any way to make this faster? diff --git a/src/product.jl b/src/product.jl index b0ed5554..6848b617 100644 --- a/src/product.jl +++ b/src/product.jl @@ -96,82 +96,80 @@ _symbol_size((a, b, c, d, e, f)::NTuple{6, Sector}) = (Nsymbol(a, b, e), Nsymbol @inline function _kron_promote_inputs(sectors) heads = map(_firstsector, sectors) tails = map(_tailsector, sectors) - sz₁ = _symbol_size(heads) - sz₂ = _symbol_size(tails) - return heads, tails, sz₁, sz₂ + return heads, tails end function Fsymbol(a::I, b::I, c::I, d::I, e::I, f::I) where {I <: ProductSector} - heads, tails, sz₁, sz₂ = _kron_promote_inputs((a, b, c, d, e, f)) + heads, tails = _kron_promote_inputs((a, b, c, d, e, f)) V₁ = Fsymbol(heads...) V₂ = Fsymbol(tails...) - return _kron_promote(V₁, V₂, sz₁, sz₂) + return _kron_promote(V₁, V₂, heads, tails) end function Fsymbol(a::I, b::I, c::I, d::I, e::I, f::I) where {I <: ProductSector{<:Tuple{Sector}}} return Fsymbol(map(_firstsector, (a, b, c, d, e, f))...) end function Fsymbol_from_fusiontensor(a::I, b::I, c::I, d::I, e::I, f::I) where {I <: ProductSector} - heads, tails, sz₁, sz₂ = _kron_promote_inputs((a, b, c, d, e, f)) + heads, tails = _kron_promote_inputs((a, b, c, d, e, f)) V₁ = Fsymbol_from_fusiontensor(heads...) V₂ = Fsymbol_from_fusiontensor(tails...) - return _kron_promote(V₁, V₂, sz₁, sz₂) + return _kron_promote(V₁, V₂, heads, tails) end function Fsymbol_from_fusiontensor(a::I, b::I, c::I, d::I, e::I, f::I) where {I <: ProductSector{<:Tuple{Sector}}} return Fsymbol_from_fusiontensor(map(_firstsector, (a, b, c, d, e, f))...) end function Rsymbol(a::I, b::I, c::I) where {I <: ProductSector} - heads, tails, sz₁, sz₂ = _kron_promote_inputs((a, b, c)) + heads, tails = _kron_promote_inputs((a, b, c)) V₁ = Rsymbol(heads...) V₂ = Rsymbol(tails...) - return _kron_promote(V₁, V₂, sz₁, sz₂) + return _kron_promote(V₁, V₂, heads, tails) end function Rsymbol(a::I, b::I, c::I) where {I <: ProductSector{<:Tuple{Sector}}} return Rsymbol(map(_firstsector, (a, b, c))...) end function Rsymbol_from_fusiontensor(a::I, b::I, c::I) where {I <: ProductSector} - heads, tails, sz₁, sz₂ = _kron_promote_inputs((a, b, c)) + heads, tails = _kron_promote_inputs((a, b, c)) V₁ = Rsymbol_from_fusiontensor(heads...) V₂ = Rsymbol_from_fusiontensor(tails...) - return _kron_promote(V₁, V₂, sz₁, sz₂) + return _kron_promote(V₁, V₂, heads, tails) end function Rsymbol_from_fusiontensor(a::I, b::I, c::I) where {I <: ProductSector{<:Tuple{Sector}}} return Rsymbol_from_fusiontensor(map(_firstsector, (a, b, c))...) end function Bsymbol(a::I, b::I, c::I) where {I <: ProductSector} - heads, tails, sz₁, sz₂ = _kron_promote_inputs((a, b, c)) + heads, tails = _kron_promote_inputs((a, b, c)) V₁ = Bsymbol(heads...) V₂ = Bsymbol(tails...) - return _kron_promote(V₁, V₂, sz₁, sz₂) + return _kron_promote(V₁, V₂, heads, tails) end function Bsymbol(a::I, b::I, c::I) where {I <: ProductSector{<:Tuple{Sector}}} return Bsymbol(map(_firstsector, (a, b, c))...) end function Bsymbol_from_fusiontensor(a::I, b::I, c::I) where {I <: ProductSector} - heads, tails, sz₁, sz₂ = _kron_promote_inputs((a, b, c)) + heads, tails = _kron_promote_inputs((a, b, c)) V₁ = Bsymbol_from_fusiontensor(heads...) V₂ = Bsymbol_from_fusiontensor(tails...) - return _kron_promote(V₁, V₂, sz₁, sz₂) + return _kron_promote(V₁, V₂, heads, tails) end function Bsymbol_from_fusiontensor(a::I, b::I, c::I) where {I <: ProductSector{<:Tuple{Sector}}} return Bsymbol_from_fusiontensor(map(_firstsector, (a, b, c))...) end function Asymbol(a::I, b::I, c::I) where {I <: ProductSector} - heads, tails, sz₁, sz₂ = _kron_promote_inputs((a, b, c)) + heads, tails = _kron_promote_inputs((a, b, c)) V₁ = Asymbol(heads...) V₂ = Asymbol(tails...) - return _kron_promote(V₁, V₂, sz₁, sz₂) + return _kron_promote(V₁, V₂, heads, tails) end function Asymbol(a::I, b::I, c::I) where {I <: ProductSector{<:Tuple{Sector}}} return Asymbol(map(_firstsector, (a, b, c))...) end function Asymbol_from_fusiontensor(a::I, b::I, c::I) where {I <: ProductSector} - heads, tails, sz₁, sz₂ = _kron_promote_inputs((a, b, c)) + heads, tails = _kron_promote_inputs((a, b, c)) V₁ = Asymbol_from_fusiontensor(heads...) V₂ = Asymbol_from_fusiontensor(tails...) - return _kron_promote(V₁, V₂, sz₁, sz₂) + return _kron_promote(V₁, V₂, heads, tails) end function Asymbol_from_fusiontensor(a::I, b::I, c::I) where {I <: ProductSector{<:Tuple{Sector}}} return Asymbol_from_fusiontensor(map(_firstsector, (a, b, c))...) From c3b503a5e33fb7fd2b9aada11a8f558c036b84bd Mon Sep 17 00:00:00 2001 From: lkdvos Date: Thu, 15 Oct 2026 11:40:38 -0400 Subject: [PATCH 2/2] Keep the array type of the other symbol when promoting a scalar one Broadcasting a scalar over a `SparseArray` gives a dense `Array`, so the type of a product symbol depended on the value of the scalar factor, which breaks contractions in e.g. the pentagon checks for `CategoryData` sectors. Use scalar multiplication instead, and promote a scalar symbol to an array similar to the other symbol. Co-Authored-By: Claude Opus 5.5 --- src/auxiliary.jl | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/auxiliary.jl b/src/auxiliary.jl index 92bf52de..3d7f0ec5 100644 --- a/src/auxiliary.jl +++ b/src/auxiliary.jl @@ -14,19 +14,20 @@ function _kron(A, B) return C end # combine the symbols `A` and `B` of the factors `a` and `b` of product sectors; a scalar symbol -# is promoted to an array with the size given by the multiplicities of its sectors, unless these -# are all one: then it just scales the other symbol, which is returned as is for a scalar one -# (symbols are not mutated, and may already be shared with a cache) +# is promoted to an array of the same kind as the other symbol, with the size given by the +# multiplicities of its sectors, unless these are all one: then it just scales the other symbol, +# which is returned as is for a scalar one (symbols are not mutated, and may already be shared +# with a cache); the type of the result thus only depends on the types of the symbols _kron_promote(A::Number, B::Number, a, b) = A * B function _kron_promote(A::Number, B::AbstractArray, a, b) sz = _symbol_size(a) - all(isone, sz) || return _kron(fill(A, sz), B) - return isone(A) && promote_type(typeof(A), eltype(B)) === eltype(B) ? B : A .* B + all(isone, sz) || return _kron(fill!(similar(B, typeof(A), sz), A), B) + return isone(A) && promote_type(typeof(A), eltype(B)) === eltype(B) ? B : A * B end function _kron_promote(A::AbstractArray, B::Number, a, b) sz = _symbol_size(b) - all(isone, sz) || return _kron(A, fill(B, sz)) - return isone(B) && promote_type(eltype(A), typeof(B)) === eltype(A) ? A : A .* B + all(isone, sz) || return _kron(A, fill!(similar(A, typeof(B), sz), B)) + return isone(B) && promote_type(eltype(A), typeof(B)) === eltype(A) ? A : A * B end _kron_promote(A::AbstractArray, B::AbstractArray, a, b) = _kron(A, B)