From b30f2055eb4661e87c97024fad03beb62f0f8d58 Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Tue, 11 Aug 2026 08:26:39 +0200 Subject: [PATCH 01/12] specialise dim to storage type --- src/TensorKit.jl | 2 +- src/spaces/gradedspace.jl | 17 ++++++++++++++--- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/TensorKit.jl b/src/TensorKit.jl index 87a3a2380..929dd804e 100644 --- a/src/TensorKit.jl +++ b/src/TensorKit.jl @@ -44,7 +44,7 @@ export infimum, supremum, isisomorphic, ismonomorphic, isepimorphic export sectortype, sectors, hassector export unit, rightunit, leftunit, allunits, isunit, otimes, deligneproduct, timereversed export Nsymbol, Fsymbol, Rsymbol, Bsymbol, frobenius_schur_phase, frobenius_schur_indicator, twist, fusiontensor -export sectorscalartype, fusionscalartype, braidingscalartype +export sectorscalartype, fusionscalartype, braidingscalartype, dimscalartype # Export methods for fusion trees export fusiontrees, braid, permute, transpose diff --git a/src/spaces/gradedspace.jl b/src/spaces/gradedspace.jl index 53f48dafd..d469648cc 100644 --- a/src/spaces/gradedspace.jl +++ b/src/spaces/gradedspace.jl @@ -89,9 +89,20 @@ GradedSpace(g::AbstractDict; dual::Bool = false) = GradedSpace(g...; dual = dual field(::Type{<:GradedSpace}) = ℂ InnerProductStyle(::Type{<:GradedSpace}) = EuclideanInnerProduct() -function dim(V::GradedSpace) - init = 0 * dim(first(allunits(sectortype(V)))) - return sum(c -> dim(c) * dim(V, c), sectors(V); init = init) +function dim(V::GradedSpace{I, <:AbstractDict}) where {I <: Sector} + init = zero(dimscalartype(I)) + return sum(((c, d),) -> dim(c) * d, V.dims; init) +end +function dim(V::GradedSpace{I, NTuple{N, Int}}) where {I <: Sector, N} + init = zero(dimscalartype(I)) + D = init + vals = values(I) + @inbounds for n in 1:N + d = V.dims[n] + iszero(d) && continue + D += dim(vals[n]) * d # dim(c) = dim(dual(c)) + end + return D end function dim(V::GradedSpace{I, <:AbstractDict}, c::I) where {I <: Sector} return get(V.dims, isdual(V) ? dual(c) : c, 0) From 41f43fdf997580b4128ee22cf9291570967f826e Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Tue, 11 Aug 2026 12:13:17 +0200 Subject: [PATCH 02/12] oplus and ominus --- src/spaces/gradedspace.jl | 72 +++++++++++++++++++++++++++++++-------- 1 file changed, 57 insertions(+), 15 deletions(-) diff --git a/src/spaces/gradedspace.jl b/src/spaces/gradedspace.jl index d469648cc..6064eeaef 100644 --- a/src/spaces/gradedspace.jl +++ b/src/spaces/gradedspace.jl @@ -137,25 +137,67 @@ function unitspace(S::Type{<:GradedSpace{I}}) where {I <: Sector} end zerospace(S::Type{<:GradedSpace}) = S() -# TODO: the following methods can probably be implemented more efficiently for -# `FiniteGradedSpace`, but we don't expect them to be used often in hot loops, so -# these generic definitions (which are still quite efficient) are good for now. -function ⊕(V₁::GradedSpace{I}, V₂::GradedSpace{I}) where {I <: Sector} +function ⊕(V₁::GradedSpace{I, <:SectorDict}, V₂::GradedSpace{I, <:SectorDict}) where {I <: Sector} + dual1 = isdual(V₁) + dual1 == isdual(V₂) || throw(SpaceMismatch("Direct sum of a vector space and a dual space does not exist")) + k1, k2 = V₁.dims.keys, V₂.dims.keys # already sorted + v1, v2 = V₁.dims.values, V₂.dims.values + n1, n2 = length(k1), length(k2) + ks, vs = Vector{I}(), Vector{Int}() + sizehint!(ks, n1 + n2) + sizehint!(vs, n1 + n2) + i, j = 1, 1 + @inbounds while i <= n1 && j <= n2 + if k1[i] == k2[j] + push!(ks, k1[i]); push!(vs, v1[i] + v2[j]); i += 1; j += 1 + elseif k1[i] < k2[j] + push!(ks, k1[i]); push!(vs, v1[i]); i += 1 + else + push!(ks, k2[j]); push!(vs, v2[j]); j += 1 + end + end + @inbounds while i <= n1 + push!(ks, k1[i]); push!(vs, v1[i]); i += 1 + end + @inbounds while j <= n2 + push!(ks, k2[j]); push!(vs, v2[j]); j += 1 + end + return typeof(V₁)(SectorDict{I, Int}(ks, vs), dual1) +end +function ⊕(V₁::GradedSpace{I, <:Tuple}, V₂::GradedSpace{I, <:Tuple}) where {I <: Sector} dual1 = isdual(V₁) dual1 == isdual(V₂) || throw(SpaceMismatch("Direct sum of a vector space and a dual space does not exist")) - dims = SectorDict{I, Int}() - for c in union(sectors(V₁), sectors(V₂)) - cout = ifelse(dual1, dual(c), c) - dims[cout] = dim(V₁, c) + dim(V₂, c) + newdims = map(+, V₁.dims, V₂.dims) + return typeof(V₁)(newdims, dual1) +end +function ⊖(V::GradedSpace{I, <: Tuple}, W::GradedSpace{I, <: Tuple}) where {I <: Sector} + dualV = isdual(V) + V ≿ W && dualV == isdual(W) || throw(SpaceMismatch("$(W) is not a subspace of $(V)")) + newdims = map(-, V.dims, W.dims) + return typeof(V)(newdims, dualV) +end +function ⊖(V::GradedSpace{I, <:SectorDict}, W::GradedSpace{I, <:SectorDict}) where {I <: Sector} + dualV = isdual(V) + V ≿ W && dualV == isdual(W) || throw(SpaceMismatch("$(W) is not a subspace of $(V)")) + kv, kw = V.dims.keys, W.dims.keys # already sorted + vv, vw = V.dims.values, W.dims.values + ks, vs = Vector{I}(), Vector{Int}() + nv, nw = length(kv), length(kw) + sizehint!(ks, nv) + sizehint!(vs, nv) + j = 1 + @inbounds for i in eachindex(kv) # keys(W) ⊆ keys(V) + d = vv[i] + if j <= nw && kw[j] == kv[i] + d -= vw[j] + j += 1 + end + if !iszero(d) + push!(ks, kv[i]); push!(vs, d) + end end - return typeof(V₁)(dims; dual = dual1) -end -function ⊖(V::GradedSpace{I}, W::GradedSpace{I}) where {I <: Sector} - dual = isdual(V) - V ≿ W && dual == isdual(W) || - throw(SpaceMismatch("$(W) is not a subspace of $(V)")) - return typeof(V)(c => dim(V, c) - dim(W, c) for c in sectors(V); dual) + return typeof(V)(SectorDict{I, Int}(ks, vs), dualV) end function fuse(V₁::GradedSpace{I}, V₂::GradedSpace{I}) where {I <: Sector} From 619804db43336769f4031ba76413944befa24d87 Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Tue, 11 Aug 2026 12:21:28 +0200 Subject: [PATCH 03/12] infimum and supremum --- src/spaces/gradedspace.jl | 76 ++++++++++++++++++++++++++++++--------- 1 file changed, 60 insertions(+), 16 deletions(-) diff --git a/src/spaces/gradedspace.jl b/src/spaces/gradedspace.jl index 6064eeaef..46c94eaf7 100644 --- a/src/spaces/gradedspace.jl +++ b/src/spaces/gradedspace.jl @@ -171,7 +171,7 @@ function ⊕(V₁::GradedSpace{I, <:Tuple}, V₂::GradedSpace{I, <:Tuple}) where newdims = map(+, V₁.dims, V₂.dims) return typeof(V₁)(newdims, dual1) end -function ⊖(V::GradedSpace{I, <: Tuple}, W::GradedSpace{I, <: Tuple}) where {I <: Sector} +function ⊖(V::GradedSpace{I, <:Tuple}, W::GradedSpace{I, <:Tuple}) where {I <: Sector} dualV = isdual(V) V ≿ W && dualV == isdual(W) || throw(SpaceMismatch("$(W) is not a subspace of $(V)")) newdims = map(-, V.dims, W.dims) @@ -210,23 +210,67 @@ function fuse(V₁::GradedSpace{I}, V₂::GradedSpace{I}) where {I <: Sector} return typeof(V₁)(dims) end -function infimum(V₁::GradedSpace{I}, V₂::GradedSpace{I}) where {I <: Sector} +function infimum(V₁::GradedSpace{I, <:Tuple}, V₂::GradedSpace{I, <:Tuple}) where {I <: Sector} Visdual = isdual(V₁) - Visdual == isdual(V₂) || - throw(SpaceMismatch("Infimum of space and dual space does not exist")) - return typeof(V₁)( - (Visdual ? dual(c) : c) => min(dim(V₁, c), dim(V₂, c)) - for c in intersect(sectors(V₁), sectors(V₂)); dual = Visdual - ) -end -function supremum(V₁::GradedSpace{I}, V₂::GradedSpace{I}) where {I <: Sector} + Visdual == isdual(V₂) || throw(SpaceMismatch("Infimum of space and dual space does not exist")) + newdims = map(min, V₁.dims, V₂.dims) + return typeof(V₁)(newdims, Visdual) +end +function infimum(V₁::GradedSpace{I, <:SectorDict}, V₂::GradedSpace{I, <:SectorDict}) where {I <: Sector} + Visdual = isdual(V₁) + Visdual == isdual(V₂) || throw(SpaceMismatch("Infimum of space and dual space does not exist")) + k1, k2 = V₁.dims.keys, V₂.dims.keys + v1, v2 = V₁.dims.values, V₂.dims.values + n1, n2 = length(k1), length(k2) + ks, vs = Vector{I}(), Vector{Int}() + i, j = 1, 1 + @inbounds while i <= n1 && j <= n2 + if k1[i] == k2[j] + m = min(v1[i], v2[j]) + if !iszero(m) + push!(ks, k1[i]); push!(vs, m) + end + i += 1; j += 1 + elseif k1[i] < k2[j] + i += 1 + else + j += 1 + end + end + return typeof(V₁)(SectorDict{I, Int}(ks, vs), Visdual) +end +function supremum(V₁::GradedSpace{I, <:Tuple}, V₂::GradedSpace{I, <:Tuple}) where {I <: Sector} Visdual = isdual(V₁) - Visdual == isdual(V₂) || - throw(SpaceMismatch("Supremum of space and dual space does not exist")) - return typeof(V₁)( - (Visdual ? dual(c) : c) => max(dim(V₁, c), dim(V₂, c)) - for c in union(sectors(V₁), sectors(V₂)); dual = Visdual - ) + Visdual == isdual(V₂) || throw(SpaceMismatch("Supremum of space and dual space does not exist")) + newdims = map(max, V₁.dims, V₂.dims) + return typeof(V₁)(newdims, Visdual) +end +function supremum(V₁::GradedSpace{I, <:SectorDict}, V₂::GradedSpace{I, <:SectorDict}) where {I <: Sector} + Visdual = isdual(V₁) + Visdual == isdual(V₂) || throw(SpaceMismatch("Supremum of space and dual space does not exist")) + k1, k2 = V₁.dims.keys, V₂.dims.keys + v1, v2 = V₁.dims.values, V₂.dims.values + n1, n2 = length(k1), length(k2) + ks, vs = Vector{I}(), Vector{Int}() + sizehint!(ks, n1 + n2) + sizehint!(vs, n1 + n2) + i, j = 1, 1 + @inbounds while i <= n1 && j <= n2 + if k1[i] == k2[j] + push!(ks, k1[i]); push!(vs, max(v1[i], v2[j])); i += 1; j += 1 + elseif k1[i] < k2[j] + push!(ks, k1[i]); push!(vs, v1[i]); i += 1 + else + push!(ks, k2[j]); push!(vs, v2[j]); j += 1 + end + end + @inbounds while i <= n1 + push!(ks, k1[i]); push!(vs, v1[i]); i += 1 + end + @inbounds while j <= n2 + push!(ks, k2[j]); push!(vs, v2[j]); j += 1 + end + return typeof(V₁)(SectorDict{I, Int}(ks, vs), Visdual) end hassector(V::GradedSpace{I}, s::I) where {I <: Sector} = dim(V, s) != 0 From 0967bd071455860e189687dfb347f267736d9e1e Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Tue, 11 Aug 2026 12:52:02 +0200 Subject: [PATCH 04/12] fuse --- src/spaces/gradedspace.jl | 47 ++++++++++++++++++++++++++++++++++----- 1 file changed, 41 insertions(+), 6 deletions(-) diff --git a/src/spaces/gradedspace.jl b/src/spaces/gradedspace.jl index 46c94eaf7..d32358405 100644 --- a/src/spaces/gradedspace.jl +++ b/src/spaces/gradedspace.jl @@ -200,14 +200,49 @@ function ⊖(V::GradedSpace{I, <:SectorDict}, W::GradedSpace{I, <:SectorDict}) w return typeof(V)(SectorDict{I, Int}(ks, vs), dualV) end -function fuse(V₁::GradedSpace{I}, V₂::GradedSpace{I}) where {I <: Sector} - dims = SectorDict{I, Int}() - for a in sectors(V₁), b in sectors(V₂) - for c in a ⊗ b - dims[c] = get(dims, c, 0) + Nsymbol(a, b, c) * dim(V₁, a) * dim(V₂, b) +function fuse(V₁::GradedSpace{I, <:SectorDict}, V₂::GradedSpace{I, <:SectorDict}) where {I <: Sector} + dual1, dual2 = isdual(V₁), isdual(V₂) + acc = Dict{I, Int}() # SectorDict `get` within the double for loop accumulates O(N^2) ` findindex` calls -> sort afterwards + k1, k2 = V₁.dims.keys, V₂.dims.keys + v1, v2 = V₁.dims.values, V₂.dims.values + @inbounds for n1 in eachindex(k1) + a0 = k1[n1]; d1 = v1[n1] + a = dual1 ? dual(a0) : a0 + for n2 in eachindex(k2) + b0 = k2[n2]; d2 = v2[n2] + b = dual2 ? dual(b0) : b0 + dab = d1 * d2 + for c in a ⊗ b + acc[c] = get(acc, c, 0) + Nsymbol(a, b, c) * dab + end + end + end + ks = sort!(collect(keys(acc))) + vs = [acc[k] for k in ks] + return typeof(V₁)(SectorDict{I, Int}(ks, vs), false) +end +function fuse(V₁::GradedSpace{I, NTuple{N, Int}}, V₂::GradedSpace{I, NTuple{N, Int}}) where {I <: Sector, N} + vals = values(I) + dual1, dual2 = isdual(V₁), isdual(V₂) + newdims = zeros(Int, N) #TODO: is there a way to avoid dense storage even for sparse results? + @inbounds for n1 in 1:N + d1 = V₁.dims[n1] + iszero(d1) && continue + a0 = vals[n1] # avoid call to sectors(V₁) + a = dual1 ? dual(a0) : a0 + for n2 in 1:N + d2 = V₂.dims[n2] + iszero(d2) && continue + b0 = vals[n2] # idem for V₂ + b = dual2 ? dual(b0) : b0 + dab = d1 * d2 + for c in a ⊗ b + nc = findindex(vals, c) + newdims[nc] += Nsymbol(a, b, c) * dab + end end end - return typeof(V₁)(dims) + return typeof(V₁)(ntuple(i -> newdims[i], Val(N)), false) end function infimum(V₁::GradedSpace{I, <:Tuple}, V₂::GradedSpace{I, <:Tuple}) where {I <: Sector} From 60186d55213b442c3f46c1d0c3753e2c660e1050 Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Tue, 11 Aug 2026 13:14:49 +0200 Subject: [PATCH 05/12] truncate_space --- src/factorizations/truncation.jl | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/factorizations/truncation.jl b/src/factorizations/truncation.jl index 2facfd2e9..67dac8e56 100644 --- a/src/factorizations/truncation.jl +++ b/src/factorizations/truncation.jl @@ -37,6 +37,31 @@ _blocklength(ax::Base.OneTo, ind::AbstractVector{Bool}) = count(ind) function truncate_space(V::ElementarySpace, inds) return spacetype(V)(c => _blocklength(dim(V, c), ind) for (c, ind) in pairs(inds)) end +function truncate_space(V::GradedSpace{I, NTuple{N, Int}}, inds) where {I <: Sector, N} + vals = values(I) + dualV = isdual(V) + newdims = zeros(Int, N) + for (c, ind) in pairs(inds) + n_read = findindex(vals, dualV ? dual(c) : c) # dual-adjusted index for reading V.dims + n_write = findindex(vals, c) # output is never dual, so c is fine as-is + newdims[n_write] = _blocklength(V.dims[n_read], ind) # dim(c) = dim(dual(c)) + end + return typeof(V)(ntuple(i -> newdims[i], Val(N)), false) +end +function truncate_space(V::GradedSpace{I, <:SectorDict}, inds) where {I <: Sector} + dualV = isdual(V) + ks, vs = Vector{I}(), Vector{Int}() # accumulate and sort once at the end + for (c, ind) in pairs(inds) + d = get(V.dims, dualV ? dual(c) : c, 0) + len = _blocklength(d, ind) + if !iszero(len) + push!(ks, c) + push!(vs, len) + end + end + perm = sortperm(ks) + return typeof(V)(SectorDict{I, Int}(ks[perm], vs[perm]), false) +end function truncate_domain!(tdst::AbstractTensorMap, tsrc::AbstractTensorMap, inds) for (c, b) in blocks(tdst) From 8f90be2ddaf82a2d25de10c88abcc859d5d904d2 Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Tue, 11 Aug 2026 13:20:36 +0200 Subject: [PATCH 06/12] restore binary search for sectordicts --- src/auxiliary/dicts.jl | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/auxiliary/dicts.jl b/src/auxiliary/dicts.jl index 626599c01..d495103eb 100644 --- a/src/auxiliary/dicts.jl +++ b/src/auxiliary/dicts.jl @@ -89,14 +89,14 @@ end Base.empty(::SortedVectorDict, ::Type{K}, ::Type{V}) where {K, V} = SortedVectorDict{K, V}() Base.empty!(d::SortedVectorDict) = (empty!(d.keys); empty!(d.values); return d) -# _searchsortedfirst(v::Vector, k) = searchsortedfirst(v, k) -function _searchsortedfirst(v::Vector, k) - i = 1 - @inbounds while i <= length(v) && isless(v[i], k) - i += 1 - end - return i -end +_searchsortedfirst(v::Vector, k) = searchsortedfirst(v, k) +# function _searchsortedfirst(v::Vector, k) +# i = 1 +# @inbounds while i <= length(v) && isless(v[i], k) +# i += 1 +# end +# return i +# end function Base.delete!(d::SortedVectorDict{K}, k) where {K} key = convert(K, k) From ae10f5b565cc93c26caa33eab00d4c4ca8635e2f Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Tue, 11 Aug 2026 13:54:36 +0200 Subject: [PATCH 07/12] refactor sorted merge procedure --- src/auxiliary/dicts.jl | 49 ++++++++++++++++++++++ src/spaces/gradedspace.jl | 87 +++------------------------------------ 2 files changed, 55 insertions(+), 81 deletions(-) diff --git a/src/auxiliary/dicts.jl b/src/auxiliary/dicts.jl index d495103eb..17297177e 100644 --- a/src/auxiliary/dicts.jl +++ b/src/auxiliary/dicts.jl @@ -186,6 +186,55 @@ function Base.:(==)(d1::SortedVectorDict, d2::SortedVectorDict) return true end +# merge over two SORTED vector pairs representing keys and values +# - combine(v1,v2): value for a key present in both operands +# - only1(v1) / only2(v2): value for a key present in only one operand; +# pass `nothing` to drop such keys entirely (e.g. for an intersection) +# zero results are dropped (either from `combine` or `only1`/`only2`), matching how GradedSpace never stores an explicit zero dimension +# k1 and k2 originate from GradedSpace.dims.keys, which are guaranteed to be sorted +function _sortedmerge(k1::Vector{I}, v1::Vector{Int}, k2::Vector{I}, v2::Vector{Int}, combine, only1, only2) where {I} + n1, n2 = length(k1), length(k2) + ks, vs = Vector{I}(), Vector{Int}() + sizehint!(ks, n1 + n2) + sizehint!(vs, n1 + n2) + i, j = 1, 1 + @inbounds while i <= n1 && j <= n2 + if k1[i] == k2[j] + d = combine(v1[i], v2[j]) + if !iszero(d) + push!(ks, k1[i]) + push!(vs, d) + end + i += 1 + j += 1 + elseif k1[i] < k2[j] + _mergeonly!(ks, vs, k1[i], v1[i], only1) + i += 1 + else + _mergeonly!(ks, vs, k2[j], v2[j], only2) + j += 1 + end + end + @inbounds while i <= n1 + _mergeonly!(ks, vs, k1[i], v1[i], only1) + i += 1 + end + @inbounds while j <= n2 + _mergeonly!(ks, vs, k2[j], v2[j], only2) + j += 1 + end + return ks, vs +end +@inline _mergeonly!(ks, vs, k, v, ::Nothing) = nothing +@inline function _mergeonly!(ks, vs, k, v, f) + d = f(v) + if !iszero(d) + push!(ks, k) + push!(vs, d) + end + return nothing +end + """ Hashed(value, hashfunction = Base.hash, isequal = Base.isequal) diff --git a/src/spaces/gradedspace.jl b/src/spaces/gradedspace.jl index d32358405..5ca39f912 100644 --- a/src/spaces/gradedspace.jl +++ b/src/spaces/gradedspace.jl @@ -140,28 +140,7 @@ zerospace(S::Type{<:GradedSpace}) = S() function ⊕(V₁::GradedSpace{I, <:SectorDict}, V₂::GradedSpace{I, <:SectorDict}) where {I <: Sector} dual1 = isdual(V₁) dual1 == isdual(V₂) || throw(SpaceMismatch("Direct sum of a vector space and a dual space does not exist")) - k1, k2 = V₁.dims.keys, V₂.dims.keys # already sorted - v1, v2 = V₁.dims.values, V₂.dims.values - n1, n2 = length(k1), length(k2) - ks, vs = Vector{I}(), Vector{Int}() - sizehint!(ks, n1 + n2) - sizehint!(vs, n1 + n2) - i, j = 1, 1 - @inbounds while i <= n1 && j <= n2 - if k1[i] == k2[j] - push!(ks, k1[i]); push!(vs, v1[i] + v2[j]); i += 1; j += 1 - elseif k1[i] < k2[j] - push!(ks, k1[i]); push!(vs, v1[i]); i += 1 - else - push!(ks, k2[j]); push!(vs, v2[j]); j += 1 - end - end - @inbounds while i <= n1 - push!(ks, k1[i]); push!(vs, v1[i]); i += 1 - end - @inbounds while j <= n2 - push!(ks, k2[j]); push!(vs, v2[j]); j += 1 - end + ks, vs = _sortedmerge(V₁.dims.keys, V₁.dims.values, V₂.dims.keys, V₂.dims.values, +, identity, identity) return typeof(V₁)(SectorDict{I, Int}(ks, vs), dual1) end function ⊕(V₁::GradedSpace{I, <:Tuple}, V₂::GradedSpace{I, <:Tuple}) where {I <: Sector} @@ -180,23 +159,7 @@ end function ⊖(V::GradedSpace{I, <:SectorDict}, W::GradedSpace{I, <:SectorDict}) where {I <: Sector} dualV = isdual(V) V ≿ W && dualV == isdual(W) || throw(SpaceMismatch("$(W) is not a subspace of $(V)")) - kv, kw = V.dims.keys, W.dims.keys # already sorted - vv, vw = V.dims.values, W.dims.values - ks, vs = Vector{I}(), Vector{Int}() - nv, nw = length(kv), length(kw) - sizehint!(ks, nv) - sizehint!(vs, nv) - j = 1 - @inbounds for i in eachindex(kv) # keys(W) ⊆ keys(V) - d = vv[i] - if j <= nw && kw[j] == kv[i] - d -= vw[j] - j += 1 - end - if !iszero(d) - push!(ks, kv[i]); push!(vs, d) - end - end + ks, vs = _sortedmerge(V.dims.keys, V.dims.values, W.dims.keys, W.dims.values, -, identity, nothing) return typeof(V)(SectorDict{I, Int}(ks, vs), dualV) end @@ -217,14 +180,14 @@ function fuse(V₁::GradedSpace{I, <:SectorDict}, V₂::GradedSpace{I, <:SectorD end end end - ks = sort!(collect(keys(acc))) + ks = sort!(collect(keys(acc))) #TODO: sortperm? vs = [acc[k] for k in ks] return typeof(V₁)(SectorDict{I, Int}(ks, vs), false) end function fuse(V₁::GradedSpace{I, NTuple{N, Int}}, V₂::GradedSpace{I, NTuple{N, Int}}) where {I <: Sector, N} vals = values(I) dual1, dual2 = isdual(V₁), isdual(V₂) - newdims = zeros(Int, N) #TODO: is there a way to avoid dense storage even for sparse results? + newdims = zeros(Int, N) @inbounds for n1 in 1:N d1 = V₁.dims[n1] iszero(d1) && continue @@ -254,24 +217,7 @@ end function infimum(V₁::GradedSpace{I, <:SectorDict}, V₂::GradedSpace{I, <:SectorDict}) where {I <: Sector} Visdual = isdual(V₁) Visdual == isdual(V₂) || throw(SpaceMismatch("Infimum of space and dual space does not exist")) - k1, k2 = V₁.dims.keys, V₂.dims.keys - v1, v2 = V₁.dims.values, V₂.dims.values - n1, n2 = length(k1), length(k2) - ks, vs = Vector{I}(), Vector{Int}() - i, j = 1, 1 - @inbounds while i <= n1 && j <= n2 - if k1[i] == k2[j] - m = min(v1[i], v2[j]) - if !iszero(m) - push!(ks, k1[i]); push!(vs, m) - end - i += 1; j += 1 - elseif k1[i] < k2[j] - i += 1 - else - j += 1 - end - end + ks, vs = _sortedmerge(V₁.dims.keys, V₁.dims.values, V₂.dims.keys, V₂.dims.values, min, nothing, nothing) return typeof(V₁)(SectorDict{I, Int}(ks, vs), Visdual) end function supremum(V₁::GradedSpace{I, <:Tuple}, V₂::GradedSpace{I, <:Tuple}) where {I <: Sector} @@ -283,28 +229,7 @@ end function supremum(V₁::GradedSpace{I, <:SectorDict}, V₂::GradedSpace{I, <:SectorDict}) where {I <: Sector} Visdual = isdual(V₁) Visdual == isdual(V₂) || throw(SpaceMismatch("Supremum of space and dual space does not exist")) - k1, k2 = V₁.dims.keys, V₂.dims.keys - v1, v2 = V₁.dims.values, V₂.dims.values - n1, n2 = length(k1), length(k2) - ks, vs = Vector{I}(), Vector{Int}() - sizehint!(ks, n1 + n2) - sizehint!(vs, n1 + n2) - i, j = 1, 1 - @inbounds while i <= n1 && j <= n2 - if k1[i] == k2[j] - push!(ks, k1[i]); push!(vs, max(v1[i], v2[j])); i += 1; j += 1 - elseif k1[i] < k2[j] - push!(ks, k1[i]); push!(vs, v1[i]); i += 1 - else - push!(ks, k2[j]); push!(vs, v2[j]); j += 1 - end - end - @inbounds while i <= n1 - push!(ks, k1[i]); push!(vs, v1[i]); i += 1 - end - @inbounds while j <= n2 - push!(ks, k2[j]); push!(vs, v2[j]); j += 1 - end + ks, vs = _sortedmerge(V₁.dims.keys, V₁.dims.values, V₂.dims.keys, V₂.dims.values, max, identity, identity) return typeof(V₁)(SectorDict{I, Int}(ks, vs), Visdual) end From 47a5f3437054d7533c454276ca95f604899e6b0a Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Tue, 11 Aug 2026 15:21:40 +0200 Subject: [PATCH 08/12] speed up fuse slightly with sortperm --- src/spaces/gradedspace.jl | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/spaces/gradedspace.jl b/src/spaces/gradedspace.jl index 5ca39f912..0a79293c2 100644 --- a/src/spaces/gradedspace.jl +++ b/src/spaces/gradedspace.jl @@ -180,9 +180,10 @@ function fuse(V₁::GradedSpace{I, <:SectorDict}, V₂::GradedSpace{I, <:SectorD end end end - ks = sort!(collect(keys(acc))) #TODO: sortperm? - vs = [acc[k] for k in ks] - return typeof(V₁)(SectorDict{I, Int}(ks, vs), false) + ks0 = collect(keys(acc)) + vs0 = collect(values(acc)) + perm = sortperm(ks0) + return typeof(V₁)(SectorDict{I, Int}(ks0[perm], vs0[perm]), false) end function fuse(V₁::GradedSpace{I, NTuple{N, Int}}, V₂::GradedSpace{I, NTuple{N, Int}}) where {I <: Sector, N} vals = values(I) From 411dc4472b20d214bb851e53b1468a5938a7c29d Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Tue, 11 Aug 2026 15:21:55 +0200 Subject: [PATCH 09/12] import thing --- src/factorizations/factorizations.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/factorizations/factorizations.jl b/src/factorizations/factorizations.jl index fbe87a63a..80d96ff9a 100644 --- a/src/factorizations/factorizations.jl +++ b/src/factorizations/factorizations.jl @@ -6,7 +6,7 @@ module Factorizations export copy_oftype, factorisation_scalartype, one!, truncspace using ..TensorKit -using ..TensorKit: AdjointTensorMap, SectorDict, SectorVector, +using ..TensorKit: AdjointTensorMap, SectorDict, SectorVector, findindex, blocktype, foreachblock, one!, similar_diagonal, similarstoragetype From 0adec3ba4357537db2857dac9c25125673302a0d Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Wed, 12 Aug 2026 11:47:24 +0200 Subject: [PATCH 10/12] splat with type annotation above Val --- src/factorizations/truncation.jl | 2 +- src/spaces/gradedspace.jl | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/factorizations/truncation.jl b/src/factorizations/truncation.jl index 67dac8e56..f3c64ed7c 100644 --- a/src/factorizations/truncation.jl +++ b/src/factorizations/truncation.jl @@ -46,7 +46,7 @@ function truncate_space(V::GradedSpace{I, NTuple{N, Int}}, inds) where {I <: Sec n_write = findindex(vals, c) # output is never dual, so c is fine as-is newdims[n_write] = _blocklength(V.dims[n_read], ind) # dim(c) = dim(dual(c)) end - return typeof(V)(ntuple(i -> newdims[i], Val(N)), false) + return typeof(V)((newdims...,)::NTuple{N, Int}, false) end function truncate_space(V::GradedSpace{I, <:SectorDict}, inds) where {I <: Sector} dualV = isdual(V) diff --git a/src/spaces/gradedspace.jl b/src/spaces/gradedspace.jl index 0a79293c2..3e363d19c 100644 --- a/src/spaces/gradedspace.jl +++ b/src/spaces/gradedspace.jl @@ -30,17 +30,17 @@ end sectortype(::Type{<:GradedSpace{I}}) where {I <: Sector} = I function GradedSpace{I, NTuple{N, Int}}(dims; dual::Bool = false) where {I, N} - d = ntuple(n -> 0, N) - isset = ntuple(n -> false, N) + d = zeros(Int, N) + isset = falses(N) for (c, dc) in dims k = convert(I, c) i = findindex(values(I), k) - k = dc < 0 && throw(ArgumentError(lazy"Sector $k has negative dimension $dc")) + dc < 0 && throw(ArgumentError(lazy"Sector $k has negative dimension $dc")) isset[i] && throw(ArgumentError(lazy"Sector $c appears multiple times")) - isset = TupleTools.setindex(isset, true, i) - d = TupleTools.setindex(d, dc, i) + isset[i] = true + d[i] = dc end - return GradedSpace{I, NTuple{N, Int}}(d, dual) + return GradedSpace{I, NTuple{N, Int}}((d...,)::NTuple{N, Int}, dual) end function GradedSpace{I, NTuple{N, Int}}(dims::Pair; dual::Bool = false) where {I, N} return GradedSpace{I, NTuple{N, Int}}((dims,); dual = dual) @@ -206,7 +206,7 @@ function fuse(V₁::GradedSpace{I, NTuple{N, Int}}, V₂::GradedSpace{I, NTuple{ end end end - return typeof(V₁)(ntuple(i -> newdims[i], Val(N)), false) + return typeof(V₁)((newdims...,)::NTuple{N, Int}, false) end function infimum(V₁::GradedSpace{I, <:Tuple}, V₂::GradedSpace{I, <:Tuple}) where {I <: Sector} From 13677f3129b19757c7ce4fd72a0129b577c64e88 Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Fri, 21 Aug 2026 15:33:03 +0200 Subject: [PATCH 11/12] actually don't splat, but construct directly where previously a vector was made --- src/factorizations/truncation.jl | 2 +- src/spaces/gradedspace.jl | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/factorizations/truncation.jl b/src/factorizations/truncation.jl index f3c64ed7c..3ec0457a9 100644 --- a/src/factorizations/truncation.jl +++ b/src/factorizations/truncation.jl @@ -46,7 +46,7 @@ function truncate_space(V::GradedSpace{I, NTuple{N, Int}}, inds) where {I <: Sec n_write = findindex(vals, c) # output is never dual, so c is fine as-is newdims[n_write] = _blocklength(V.dims[n_read], ind) # dim(c) = dim(dual(c)) end - return typeof(V)((newdims...,)::NTuple{N, Int}, false) + return typeof(V)(NTuple{N, Int}(newdims), false) end function truncate_space(V::GradedSpace{I, <:SectorDict}, inds) where {I <: Sector} dualV = isdual(V) diff --git a/src/spaces/gradedspace.jl b/src/spaces/gradedspace.jl index 3e363d19c..ede620e62 100644 --- a/src/spaces/gradedspace.jl +++ b/src/spaces/gradedspace.jl @@ -40,7 +40,7 @@ function GradedSpace{I, NTuple{N, Int}}(dims; dual::Bool = false) where {I, N} isset[i] = true d[i] = dc end - return GradedSpace{I, NTuple{N, Int}}((d...,)::NTuple{N, Int}, dual) + return GradedSpace{I, NTuple{N, Int}}(NTuple{N, Int}(d), dual) end function GradedSpace{I, NTuple{N, Int}}(dims::Pair; dual::Bool = false) where {I, N} return GradedSpace{I, NTuple{N, Int}}((dims,); dual = dual) @@ -206,7 +206,7 @@ function fuse(V₁::GradedSpace{I, NTuple{N, Int}}, V₂::GradedSpace{I, NTuple{ end end end - return typeof(V₁)((newdims...,)::NTuple{N, Int}, false) + return typeof(V₁)(NTuple{N, Int}(newdims), false) end function infimum(V₁::GradedSpace{I, <:Tuple}, V₂::GradedSpace{I, <:Tuple}) where {I <: Sector} From 399e121f677afbd330df396feaed54b97918c2c4 Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Fri, 21 Aug 2026 16:17:06 +0200 Subject: [PATCH 12/12] make slightly more readable maybe perhaps --- src/spaces/gradedspace.jl | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/spaces/gradedspace.jl b/src/spaces/gradedspace.jl index ede620e62..bdea064e1 100644 --- a/src/spaces/gradedspace.jl +++ b/src/spaces/gradedspace.jl @@ -168,13 +168,13 @@ function fuse(V₁::GradedSpace{I, <:SectorDict}, V₂::GradedSpace{I, <:SectorD acc = Dict{I, Int}() # SectorDict `get` within the double for loop accumulates O(N^2) ` findindex` calls -> sort afterwards k1, k2 = V₁.dims.keys, V₂.dims.keys v1, v2 = V₁.dims.values, V₂.dims.values - @inbounds for n1 in eachindex(k1) - a0 = k1[n1]; d1 = v1[n1] - a = dual1 ? dual(a0) : a0 - for n2 in eachindex(k2) - b0 = k2[n2]; d2 = v2[n2] - b = dual2 ? dual(b0) : b0 - dab = d1 * d2 + @inbounds for na in eachindex(k1) + a₀, da = k1[na], v1[na] + a = dual1 ? dual(a₀) : a₀ + for nb in eachindex(k2) + b₀, db = k2[nb], v2[nb] + b = dual2 ? dual(b₀) : b₀ + dab = da * db for c in a ⊗ b acc[c] = get(acc, c, 0) + Nsymbol(a, b, c) * dab end @@ -189,17 +189,17 @@ function fuse(V₁::GradedSpace{I, NTuple{N, Int}}, V₂::GradedSpace{I, NTuple{ vals = values(I) dual1, dual2 = isdual(V₁), isdual(V₂) newdims = zeros(Int, N) - @inbounds for n1 in 1:N - d1 = V₁.dims[n1] - iszero(d1) && continue - a0 = vals[n1] # avoid call to sectors(V₁) - a = dual1 ? dual(a0) : a0 - for n2 in 1:N - d2 = V₂.dims[n2] - iszero(d2) && continue - b0 = vals[n2] # idem for V₂ - b = dual2 ? dual(b0) : b0 - dab = d1 * d2 + @inbounds for na in 1:N + da = V₁.dims[na] + iszero(da) && continue + a₀ = vals[na] # avoid call to sectors(V₁) + a = dual1 ? dual(a₀) : a₀ + for nb in 1:N + db = V₂.dims[nb] + iszero(db) && continue + b₀ = vals[nb] # idem for V₂ + b = dual2 ? dual(b₀) : b₀ + dab = da * db for c in a ⊗ b nc = findindex(vals, c) newdims[nc] += Nsymbol(a, b, c) * dab