From 7529167d1a14ee3f4b7c2072fa170967eab92303 Mon Sep 17 00:00:00 2001 From: leburgel Date: Sun, 26 Oct 2025 12:39:32 +0100 Subject: [PATCH 1/8] Avoid copy in `twist` for tensors with bosonic braiding --- src/tensors/indexmanipulations.jl | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/tensors/indexmanipulations.jl b/src/tensors/indexmanipulations.jl index 7465a7bb0..7bbbff5e0 100644 --- a/src/tensors/indexmanipulations.jl +++ b/src/tensors/indexmanipulations.jl @@ -302,7 +302,10 @@ If `inv=true`, use the inverse twist. See [`twist!`](@ref) for storing the result in place. """ -twist(t::AbstractTensorMap, i; inv::Bool = false) = twist!(copy(t), i; inv) +function twist(t::AbstractTensorMap, i; inv::Bool = false) + (BraidingStyle(sectortype(t)) == Bosonic() || isempty(is)) && return t + return twist!(copy(t), i; inv) +end # Methods which change the number of indices, implement using `Val(i)` for type inference """ From 35facfa34c3018694d34e5c2b5d47f69f52887d1 Mon Sep 17 00:00:00 2001 From: leburgel Date: Sun, 26 Oct 2025 13:03:13 +0100 Subject: [PATCH 2/8] Fix typo --- src/tensors/indexmanipulations.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tensors/indexmanipulations.jl b/src/tensors/indexmanipulations.jl index 7bbbff5e0..09bc92a41 100644 --- a/src/tensors/indexmanipulations.jl +++ b/src/tensors/indexmanipulations.jl @@ -302,9 +302,9 @@ If `inv=true`, use the inverse twist. See [`twist!`](@ref) for storing the result in place. """ -function twist(t::AbstractTensorMap, i; inv::Bool = false) +function twist(t::AbstractTensorMap, is; inv::Bool = false) (BraidingStyle(sectortype(t)) == Bosonic() || isempty(is)) && return t - return twist!(copy(t), i; inv) + return twist!(copy(t), is; inv) end # Methods which change the number of indices, implement using `Val(i)` for type inference From 48568879d3ca1339a8e14146108c9a3c638a532a Mon Sep 17 00:00:00 2001 From: Lukas Devos Date: Mon, 27 Oct 2025 10:46:11 -0400 Subject: [PATCH 3/8] add `copy` keyword argument --- src/tensors/indexmanipulations.jl | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/tensors/indexmanipulations.jl b/src/tensors/indexmanipulations.jl index 09bc92a41..593db8289 100644 --- a/src/tensors/indexmanipulations.jl +++ b/src/tensors/indexmanipulations.jl @@ -294,16 +294,17 @@ function twist!(t::AbstractTensorMap, is; inv::Bool = false) end """ - twist(tsrc::AbstractTensorMap, i::Int; inv::Bool=false) -> tdst - twist(tsrc::AbstractTensorMap, is; inv::Bool=false) -> tdst + twist(tsrc::AbstractTensorMap, i::Int; inv::Bool = false, copy::Bool = false) -> tdst + twist(tsrc::AbstractTensorMap, is; inv::Bool = false, copy::Bool = false) -> tdst Apply a twist to the `i`th index of `tsrc` and return the result as a new tensor. -If `inv=true`, use the inverse twist. +If `inv = true`, use the inverse twist. +If `copy = false`, `tdst` might share data with `tsrc` whenever possible. Otherwise, a copy is always made. See [`twist!`](@ref) for storing the result in place. """ -function twist(t::AbstractTensorMap, is; inv::Bool = false) - (BraidingStyle(sectortype(t)) == Bosonic() || isempty(is)) && return t +function twist(t::AbstractTensorMap, is; inv::Bool = false, copy::Bool = false) + !copy && (BraidingStyle(sectortype(t)) == Bosonic() || isempty(is)) && return t return twist!(copy(t), is; inv) end From 767cad720d8944806e419805c63b9826884b0946 Mon Sep 17 00:00:00 2001 From: Lukas Devos Date: Mon, 27 Oct 2025 16:22:52 -0400 Subject: [PATCH 4/8] handle kwargs in `twist` rrule --- ext/TensorKitChainRulesCoreExt/linalg.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ext/TensorKitChainRulesCoreExt/linalg.jl b/ext/TensorKitChainRulesCoreExt/linalg.jl index 9c12af7a6..9db147a92 100644 --- a/ext/TensorKitChainRulesCoreExt/linalg.jl +++ b/ext/TensorKitChainRulesCoreExt/linalg.jl @@ -79,9 +79,9 @@ function ChainRulesCore.rrule(::typeof(adjoint), A::AbstractTensorMap) return adjoint(A), adjoint_pullback end -function ChainRulesCore.rrule(::typeof(twist), A::AbstractTensorMap, is; inv::Bool = false) - tA = twist(A, is; inv) - twist_pullback(ΔA) = NoTangent(), twist(unthunk(ΔA), is; inv = !inv), NoTangent() +function ChainRulesCore.rrule(::typeof(twist), A::AbstractTensorMap, is; inv::Bool = false, kwargs...) + tA = twist(A, is; inv, kwargs...) + twist_pullback(ΔA) = NoTangent(), twist(unthunk(ΔA), is; inv = !inv, kwargs...), NoTangent() return tA, twist_pullback end From 31dee1179c99249bd5916b7cef01ab9ab3505bf4 Mon Sep 17 00:00:00 2001 From: Lukas Devos Date: Mon, 27 Oct 2025 17:33:13 -0400 Subject: [PATCH 5/8] share more code --- src/tensors/indexmanipulations.jl | 44 ++++++++++++++++++------------- 1 file changed, 26 insertions(+), 18 deletions(-) diff --git a/src/tensors/indexmanipulations.jl b/src/tensors/indexmanipulations.jl index 593db8289..33337a864 100644 --- a/src/tensors/indexmanipulations.jl +++ b/src/tensors/indexmanipulations.jl @@ -262,40 +262,48 @@ To repartition into an existing destination, see [repartition!](@ref). end # Twist +function has_shared_twist(t, inds) + I = sectortype(t) + if BraidingStyle(I) == NoBraiding() + for i in inds + cs = sectors(space(t, i)) + all(isone, cs) || throw(SectorMismatch(lazy"Cannot twist sectors $cs")) + end + return true + elseif BraidingStyle(I) == Bosonic() + return true + else + return isempty(inds) + end +end + """ twist!(t::AbstractTensorMap, i::Int; inv::Bool=false) -> t - twist!(t::AbstractTensorMap, is; inv::Bool=false) -> t + twist!(t::AbstractTensorMap, inds; inv::Bool=false) -> t -Apply a twist to the `i`th index of `t`, or all indices in `is`, storing the result in `t`. +Apply a twist to the `i`th index of `t`, or all indices in `inds`, storing the result in `t`. If `inv=true`, use the inverse twist. See [`twist`](@ref) for creating a new tensor. """ -function twist!(t::AbstractTensorMap, is; inv::Bool = false) - if !all(in(allind(t)), is) - msg = "Can't twist indices $is of a tensor with only $(numind(t)) indices." +function twist!(t::AbstractTensorMap, inds; inv::Bool = false) + if !all(in(allind(t)), inds) + msg = "Can't twist indices $inds of a tensor with only $(numind(t)) indices." throw(ArgumentError(msg)) end - (BraidingStyle(sectortype(t)) == Bosonic() || isempty(is)) && return t - if BraidingStyle(sectortype(t)) == NoBraiding() - for i in is - cs = sectors(space(t, i)) - all(isone, cs) || throw(SectorMismatch(lazy"Cannot twist sectors $cs")) - end - return t - end + has_shared_twist(t, inds) && return t N₁ = numout(t) for (f₁, f₂) in fusiontrees(t) - θ = prod(i -> i <= N₁ ? twist(f₁.uncoupled[i]) : twist(f₂.uncoupled[i - N₁]), is) + θ = prod(i -> i <= N₁ ? twist(f₁.uncoupled[i]) : twist(f₂.uncoupled[i - N₁]), inds) inv && (θ = θ') - rmul!(t[f₁, f₂], θ) + scale!(t[f₁, f₂], θ) end return t end """ twist(tsrc::AbstractTensorMap, i::Int; inv::Bool = false, copy::Bool = false) -> tdst - twist(tsrc::AbstractTensorMap, is; inv::Bool = false, copy::Bool = false) -> tdst + twist(tsrc::AbstractTensorMap, inds; inv::Bool = false, copy::Bool = false) -> tdst Apply a twist to the `i`th index of `tsrc` and return the result as a new tensor. If `inv = true`, use the inverse twist. @@ -303,8 +311,8 @@ If `copy = false`, `tdst` might share data with `tsrc` whenever possible. Otherw See [`twist!`](@ref) for storing the result in place. """ -function twist(t::AbstractTensorMap, is; inv::Bool = false, copy::Bool = false) - !copy && (BraidingStyle(sectortype(t)) == Bosonic() || isempty(is)) && return t +function twist(t::AbstractTensorMap, inds; inv::Bool = false, copy::Bool = false) + !copy && has_shared_twist(t, inds) && return t return twist!(copy(t), is; inv) end From 6a7c8acfb61f1d2062684f4a6eda92461e577bf7 Mon Sep 17 00:00:00 2001 From: Lukas Devos Date: Mon, 10 Nov 2025 11:25:07 -0500 Subject: [PATCH 6/8] add code suggestion --- src/tensors/indexmanipulations.jl | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/tensors/indexmanipulations.jl b/src/tensors/indexmanipulations.jl index 33337a864..555df680c 100644 --- a/src/tensors/indexmanipulations.jl +++ b/src/tensors/indexmanipulations.jl @@ -273,7 +273,11 @@ function has_shared_twist(t, inds) elseif BraidingStyle(I) == Bosonic() return true else - return isempty(inds) + for i in inds + cs = sectors(space(t, i)) + all(isone ∘ twist, cs) || return false + end + return true end end From b489de79b4d7aa063a34176107cf30a64ed65db3 Mon Sep 17 00:00:00 2001 From: Lukas Devos Date: Mon, 10 Nov 2025 11:26:43 -0500 Subject: [PATCH 7/8] fix name alias --- src/tensors/indexmanipulations.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tensors/indexmanipulations.jl b/src/tensors/indexmanipulations.jl index 555df680c..5a9978842 100644 --- a/src/tensors/indexmanipulations.jl +++ b/src/tensors/indexmanipulations.jl @@ -317,7 +317,7 @@ See [`twist!`](@ref) for storing the result in place. """ function twist(t::AbstractTensorMap, inds; inv::Bool = false, copy::Bool = false) !copy && has_shared_twist(t, inds) && return t - return twist!(copy(t), is; inv) + return twist!(Base.copy(t), is; inv) end # Methods which change the number of indices, implement using `Val(i)` for type inference From b7e2c70a6ce453693f92bc07216b9867094a4a29 Mon Sep 17 00:00:00 2001 From: Lukas Devos Date: Mon, 10 Nov 2025 13:01:37 -0500 Subject: [PATCH 8/8] fix variable rename --- src/tensors/indexmanipulations.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tensors/indexmanipulations.jl b/src/tensors/indexmanipulations.jl index 5a9978842..408de09f0 100644 --- a/src/tensors/indexmanipulations.jl +++ b/src/tensors/indexmanipulations.jl @@ -317,7 +317,7 @@ See [`twist!`](@ref) for storing the result in place. """ function twist(t::AbstractTensorMap, inds; inv::Bool = false, copy::Bool = false) !copy && has_shared_twist(t, inds) && return t - return twist!(Base.copy(t), is; inv) + return twist!(Base.copy(t), inds; inv) end # Methods which change the number of indices, implement using `Val(i)` for type inference