From bfdf4fc60625a47d32f917bf8179fe8e4a552663 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Sat, 3 Oct 2026 17:50:44 -0400 Subject: [PATCH] Index the destination once in `_copy_subblocks!` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `_copy_subblocks!` looped over the fusion-tree blocks of the destination and read every nonzero block of the source with `v[f₁, f₂]`, which goes through TensorKit's lock-protected `degeneracystructure` cache on each call: (#fusion trees) × (#nonzero blocks) cache hits. It now iterates the source blocks with `subblocks(v)` and indexes a single `subblocks(tdst)` iterator by fusion-tree pair, so the structure is looked up once per tensor. The block ranges come from per-leg cumulative dimensions computed once, instead of a `blockedrange` per subblock. Bump version to v0.3.20. Co-Authored-By: Claude Opus 5.5 --- Project.toml | 2 +- src/tensors/abstractblocktensor/conversion.jl | 27 +++++++++---------- 2 files changed, 13 insertions(+), 16 deletions(-) diff --git a/Project.toml b/Project.toml index 903dfc7..4420fde 100644 --- a/Project.toml +++ b/Project.toml @@ -1,6 +1,6 @@ name = "BlockTensorKit" uuid = "5f87ffc2-9cf1-4a46-8172-465d160bd8cd" -version = "0.3.19" +version = "0.3.20" authors = ["Lukas Devos and contributors"] [deps] diff --git a/src/tensors/abstractblocktensor/conversion.jl b/src/tensors/abstractblocktensor/conversion.jl index a356c6d..e7e742b 100644 --- a/src/tensors/abstractblocktensor/conversion.jl +++ b/src/tensors/abstractblocktensor/conversion.jl @@ -2,27 +2,24 @@ # ---------- function _copy_subblocks!(tdst, tsrc) - S = spacetype(tsrc) N₁, N₂ = numout(tsrc), numin(tsrc) - for ((f₁, f₂), arr) in subblocks(tdst) - blockax = ntuple(N₁ + N₂) do i - return if i <= N₁ - blockedrange(map(Base.Fix2(dim, f₁.uncoupled[i]), space(tsrc, i))) - else - blockedrange(map(Base.Fix2(dim, f₂.uncoupled[i - N₁]), space(tsrc, i)')) - end - end - - for (k, v) in nonzero_pairs(tsrc) - indices = getindex.(blockax, Block.(Tuple(k))) - arr_slice = arr[indices...] - # need to check for empty since fusion tree pair might not be present - isempty(arr_slice) || copy!(arr_slice, v[f₁, f₂]) + offsets = ntuple(i -> _sumspace_offsets(i <= N₁ ? codomain(tsrc)[i] : domain(tsrc)[i - N₁]), N₁ + N₂) + # a single `SubblockIterator` holds the structure: `tdst[f₁, f₂]` would look it up in TensorKit's locked cache every time + dstblocks = subblocks(tdst) + for (k, v) in nonzero_pairs(tsrc), ((f₁, f₂), src) in subblocks(v) + ranges = map(offsets, (f₁.uncoupled..., f₂.uncoupled...), Tuple(k)) do o, c, kᵢ + return (o[c][kᵢ] + 1):o[c][kᵢ + 1] end + copy!(dstblocks[(f₁, f₂)][ranges...], src) end return tdst end +# for every sector `c` of `V`, the cumulative dimensions of `c` over the summands of `V` +function _sumspace_offsets(V) + return SectorDict{sectortype(V), Vector{Int}}(c => cumsum(vcat(0, [dim(Vᵢ, c) for Vᵢ in V])) for c in sectors(V)) +end + function Base.convert(::Type{TensorMap}, t::AbstractBlockTensorMap) S = spacetype(t) N₁, N₂ = numout(t), numin(t)