Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ const all_parameters = TOML.parsefile(joinpath(@__DIR__, "benchparams.toml"))

# permute!
# --------
function init_permute_tensors(T, W, p)
function init_permute_tensors(T, W, p, adjoint::Bool)
C = randn(T, permute(W, p))
A = randn(T, W)
A = adjoint ? randn(T, W')' : randn(T, W)
return C, A
end
function benchmark_permute!(benchgroup, params::Dict)
Expand All @@ -25,7 +25,9 @@ function benchmark_permute!(benchgroup, params::Dict)
end
return nothing
end
function benchmark_permute!(bench; sigmas = nothing, T = "Float64", I = "Trivial", dims, p)
function benchmark_permute!(
bench; sigmas = nothing, T = "Float64", I = "Trivial", dims, p, adjoint = false
)
T_ = parse_type(T)
I_ = parse_type(I)

Expand All @@ -34,9 +36,10 @@ function benchmark_permute!(bench; sigmas = nothing, T = "Float64", I = "Trivial

codomain = mapreduce(Base.Fix1(getindex, Vs), ⊗, p_[1]; init = one(eltype(Vs)))
domain = mapreduce(Base.Fix1(getindex, Vs), ⊗, p_[2]; init = one(eltype(Vs)))
init() = init_permute_tensors(T_, codomain ← domain, p_)
init() = init_permute_tensors(T_, codomain ← domain, p_, adjoint)

bench[T, I, dims, sigmas, p] = @benchmarkable permute!(C, A, $p_) setup = ((C, A) = $init())
key = adjoint ? (T, I, dims, sigmas, p, "adjoint") : (T, I, dims, sigmas, p)
bench[key...] = @benchmarkable permute!(C, A, $p_) setup = ((C, A) = $init())
return nothing
end

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ I = "Z2Irrep"
p = [[[2, 1], []]]
dims = [[7264, 7264], [43408, 1216]]
sigmas = [[0.5, 0.5]]
adjoint = [false, true]

[[permute]]
T = ["Float64"]
Expand All @@ -24,3 +25,4 @@ I = "SU2Irrep"
p = [[[1, 3], [2, 4]], [[4, 2, 3], [1]]]
dims = [[48, 48, 48, 48]]
sigmas = [[1.0, 1.0, 1.0, 1.0]]
adjoint = [false, true]
6 changes: 6 additions & 0 deletions docs/src/Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,20 @@ When releasing a new version, move the "Unreleased" changes to a new version sec

### Changed

- Index manipulations use a single kernel operating on subblocks addressed by position: `StridedSubblocks` (sector-independent views into the flat data of a `TensorMap`) or `TreeSubblocks` (any `AbstractTensorMap`, through `subblock`), both able to carry a lazy conjugation. The `TreeTransformer`s store the mapping between subblock positions and recoupling coefficients and are cached for every tensor type; conjugated and adjoint operands are handled through this mechanism instead of through `AdjointTensorMap` wrappers (internal) ([#516](https://github.com/QuantumKitHub/TensorKit.jl/issues/516))

### Deprecated

### Removed

### Fixed

- `braid!`, `permute!` and `transpose!` with a `BraidingTensor` source now use the cached fusion tree transformers ([#516](https://github.com/QuantumKitHub/TensorKit.jl/issues/516))

### Performance

- In-place `permute!`, `braid!` and `transpose!` with `AdjointTensorMap` sources or destinations, as well as `@tensor` expressions with `conj`, now use the same cached and sector-independent kernel as plain `TensorMap`s; other tensor types (e.g. `DiagonalTensorMap`) also use the cached fusion tree transformers, and `subblocks(::TensorMap)` iterates without hashing fusion trees ([#516](https://github.com/QuantumKitHub/TensorKit.jl/issues/516), [#519](https://github.com/QuantumKitHub/TensorKit.jl/pull/519), [#520](https://github.com/QuantumKitHub/TensorKit.jl/pull/520))

## [0.17.1](https://github.com/QuantumKitHub/TensorKit.jl/compare/v0.17.0...v0.17.1) - 2026-07-13

### Added
Expand Down
1 change: 1 addition & 0 deletions ext/TensorKitEnzymeExt/utility.jl
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ pullback_dC!(ΔC, β::Number) = scale!(ΔC, conj(β))
@inline EnzymeRules.inactive_type(::Type{<:TensorKit.FusionTreeDict}) = true
@inline EnzymeRules.inactive_type(::Type{<:TensorKit.FusionTreeBlock}) = true
@inline EnzymeRules.inactive_type(::Type{<:TensorKit.GenericTreeTransformer}) = true
@inline EnzymeRules.inactive_type(::Type{<:TensorKit.AbelianTreeTransformer}) = true
@inline EnzymeRules.inactive_type(::Type{<:TensorKit.VectorSpace}) = true
@inline EnzymeRules.inactive_type(::Type{<:TensorKit.LRU}) = true

Expand Down
14 changes: 14 additions & 0 deletions src/spaces/homspace.jl
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,20 @@ See also [`degeneracystructure`](@ref), [`blockstructure`](@ref).
"""
subblockstructure(W::HomSpace) = Dictionary(fusiontrees(W), degeneracystructure(W).subblockstructure)

"""
fusiontreeindices(W::HomSpace) -> Dictionary

Return a `Dictionary` mapping each fusion tree pair `(f₁, f₂)` to its position in
[`fusiontrees`](@ref)`(W)`, which coincides with its position in
[`subblockstructure`](@ref)`(W)` and in the subblocks of a `TensorMap` on `W`.

See also [`fusiontrees`](@ref), [`subblockstructure`](@ref).
"""
function fusiontreeindices(W::HomSpace)
trees = fusiontrees(W)
return Dictionary(trees, 1:length(trees))
end

"""
fusionblocks(W::HomSpace)

Expand Down
70 changes: 70 additions & 0 deletions src/tensors/blockiterator.jl
Original file line number Diff line number Diff line change
Expand Up @@ -171,3 +171,73 @@ function Base.show(io::IO, mime::MIME"text/plain", iter::SubblockIterator)
show_subblocks(io, mime, iter)
return nothing
end

"""
struct StridedSubblocks{A <: DenseVector, N, F}
StridedSubblocks(t::TensorMap, [op = identity])

Sector-independent, integer-indexable collection of the subblocks of a `TensorMap`, as
`StridedView`s into its flat data vector. Subblock `i` corresponds to the `i`th fusion tree pair
in the canonical order of `fusiontrees(space(t))`, see also [`fusiontreeindices`](@ref).
The operation `op` (`identity` or `conj`) is applied lazily to every view, which allows
representing the subblocks of a conjugated tensor without materializing it.

This is the data structure consumed by the index manipulation kernels, whose type does not
depend on the sectortype of `t`.
"""
const SubblockOp = Union{typeof(identity), typeof(conj)}
struct StridedSubblocks{A <: DenseVector, N, F <: SubblockOp}
data::A
structure::Vector{StridedStructure{N}}
op::F
end
Base.length(s::StridedSubblocks) = length(s.structure)
Base.firstindex(s::StridedSubblocks) = 1
Base.lastindex(s::StridedSubblocks) = length(s)
Base.eltype(::Type{S}) where {S <: StridedSubblocks} = Core.Compiler.return_type(getindex, Tuple{S, Int})

Base.@propagate_inbounds function Base.getindex(s::StridedSubblocks, i::Int)
sz, str, offset = s.structure[i]
return StridedView(s.data, sz, str, offset, s.op)
end

function Base.iterate(s::StridedSubblocks, i::Int = 1)
i > length(s) && return nothing
return @inbounds(s[i]), i + 1
end

storagetype(::Type{StridedSubblocks{A, N, F}}) where {A, N, F} = A

"""
struct TreeSubblocks{TT <: AbstractTensorMap, I, F}
TreeSubblocks(t::AbstractTensorMap, [op = identity])

Integer-indexable collection of the subblocks of an arbitrary tensor `t`, where position `i`
refers to the `i`th fusion tree pair of `fusiontrees(space(t))` and the data is retrieved through
[`subblock`](@ref), with `op` (`identity` or `conj`) applied. This is the generic counterpart of
[`StridedSubblocks`](@ref) for tensor types that do not store their data in a flat vector.
"""
struct TreeSubblocks{TT <: AbstractTensorMap, I, F <: SubblockOp}
t::TT
trees::I
op::F
end
function TreeSubblocks(t::AbstractTensorMap, op::SubblockOp = identity)
return TreeSubblocks(t, fusiontrees(t), scalartype(t) <: Real ? identity : op)
end

storagetype(::Type{TreeSubblocks{TT, I, F}}) where {TT, I, F} = storagetype(TT)

Base.length(s::TreeSubblocks) = length(s.trees)
Base.firstindex(s::TreeSubblocks) = 1
Base.lastindex(s::TreeSubblocks) = length(s)
Base.eltype(::Type{S}) where {S <: TreeSubblocks} = Core.Compiler.return_type(getindex, Tuple{S, Int})

Base.@propagate_inbounds function Base.getindex(s::TreeSubblocks, i::Int)
return s.op(subblock(s.t, gettokenvalue(s.trees, i)))
end

function Base.iterate(s::TreeSubblocks, i::Int = 1)
i > length(s) && return nothing
return @inbounds(s[i]), i + 1
end
12 changes: 2 additions & 10 deletions src/tensors/braidingtensor.jl
Original file line number Diff line number Diff line change
Expand Up @@ -195,16 +195,8 @@ end
# Index manipulations
# -------------------
has_shared_permute(t::BraidingTensor, ::Index2Tuple) = false
function add_transform!(
tdst::AbstractTensorMap,
tsrc::BraidingTensor, (p₁, p₂)::Index2Tuple,
fusiontreetransform,
α::Number, β::Number, backend::AbstractBackend...
)
return add_transform!(
tdst, TensorMap(tsrc), (p₁, p₂), fusiontreetransform, α, β,
backend...
)
function unwrap_adjoints(tdst, tsrc::BraidingTensor, p::Index2Tuple, levels, conjsrc::Bool, α, β)
return unwrap_adjoints(tdst, TensorMap(tsrc), p, levels, conjsrc, α, β)
end

function planarcontract!(
Expand Down
Loading
Loading