Skip to content
Merged
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
2 changes: 1 addition & 1 deletion docs/src/man/indexmanipulations.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ removeunit(::AbstractTensorMap, ::Val{i}) where {i}
These operations reorder indices and/or move them between domain and codomain by applying the transposing or braiding isomorphisms of the underlying category.
They form a hierarchy from most general to most restricted:

- [`braid`](@ref) is the most general: it accepts any permutation and requires a `levels` argument — a tuple of heights, one per index — that determines whether each index crosses over or under the others it has to pass.
- [`braid`](@ref) is the most general: it accepts any permutation and requires a `levels` argument — a tuple of depths, one per index — that determines whether each index crosses over or under the others it has to pass. In particular, duplicate levels are not allowed when the braiding is anyonic (`BraidingStyle(I) isa Anyonic`), and the length of `levels` must match the number of indices in the tensor.
Comment thread
lkdvos marked this conversation as resolved.
- [`permute`](@ref) is a simpler interface for sector types with a symmetric braiding (`BraidingStyle(I) isa SymmetricBraiding`), where over- and under-crossings are equivalent and `levels` is therefore not needed.
- [`transpose`](@ref) is restricted to *cyclic* permutations (indices do not cross).
- [`repartition`](@ref) only moves the codomain/domain boundary without reordering the indices at all.
Expand Down
16 changes: 16 additions & 0 deletions src/auxiliary/auxiliary.jl
Original file line number Diff line number Diff line change
Expand Up @@ -76,3 +76,19 @@ end
else
_allequal(f, xs) = allequal(f, xs)
end

"""
_check_levels(levels::Tuple) -> Nothing

Check whether all elements of a tuple are distinct, throwing an `ArgumentError` if not.
This is used to validate the `levels` of [`braid`](@ref).
For symmetric braidings, this check is skipped since the levels are irrelevant.
"""
_check_levels(::SymmetricBraiding, levels, s) = nothing
@inline function _check_levels(::BraidingStyle, levels, s)
levels[s] == levels[s + 1] && _throw_ambiguous_levels(levels[s])
return nothing
end
@noinline function _throw_ambiguous_levels(l)
throw(ArgumentError(lazy"ambiguous braid: two indices with equal level $l have to cross"))
end
6 changes: 5 additions & 1 deletion src/fusiontrees/braiding_manipulations.jl
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,8 @@ braid(f::FusionTree{I, N}, p::IndexTuple{N}, levels::IndexTuple{N}) where {I, N}
function braid(f::FusionTree{I, N}, (p, _)::Index2Tuple{N, 0}, (levels, _)::Index2Tuple{N, 0}) where {I, N}
TupleTools.isperm(p) || throw(ArgumentError(lazy"not a valid permutation: $p"))
@assert FusionStyle(I) isa UniqueFusion
if BraidingStyle(I) isa SymmetricBraiding # this assumes Fsymbols are 1!
braid_style = BraidingStyle(I)
if braid_style isa SymmetricBraiding # this assumes Fsymbols are 1!
coeff = one(sectorscalartype(I))
for i in 1:N
for j in 1:(i - 1)
Expand All @@ -236,6 +237,7 @@ function braid(f::FusionTree{I, N}, (p, _)::Index2Tuple{N, 0}, (levels, _)::Inde
T = sectorscalartype(I)
c = one(T)
for s in permutation2swaps(p)
_check_levels(braid_style, levels, s)
inv = levels[s] > levels[s + 1]
f, c′ = artin_braid(f, s; inv)
c *= c′
Expand Down Expand Up @@ -316,7 +318,9 @@ end

dst, U = repartition(src, numind(src))

braid_style = BraidingStyle(I)
for s in permutation2swaps(p)
_check_levels(braid_style, levels, s)
inv = levels[s] > levels[s + 1]
dst, U_tmp = artin_braid(dst, s; inv)
U = U_tmp * U
Expand Down
7 changes: 5 additions & 2 deletions src/tensors/indexmanipulations.jl
Original file line number Diff line number Diff line change
Expand Up @@ -293,8 +293,10 @@ end

Compute `tdst = β * tdst + α * braid(tsrc, (p₁, p₂), levels)`, writing the result into `tdst`.
The codomain and domain of `tdst` correspond to the indices in `p₁` and `p₂` of `tsrc` respectively.
Here, `levels` is a tuple of length `numind(tsrc)` that assigns a level or height to the indices of `tsrc`,
Comment thread
borisdevos marked this conversation as resolved.
Here, `levels` is a tuple of length `numind(tsrc)` that assigns a level or depth to the indices of `tsrc`,
which determines whether they will braid over or under any other index with which they have to change places.
In other words, a smaller value in `levels` means that the corresponding index will be braided over any other index with a higher value.

Optionally specify a `backend` and `allocator` for the underlying array operation.

See also [`braid`](@ref) for creating a new tensor.
Expand Down Expand Up @@ -323,8 +325,9 @@ end

Return tensor `tdst` obtained by braiding the indices of `tsrc`.
The codomain and domain of `tdst` correspond to the indices in `p₁` and `p₂` of `tsrc` respectively.
Here, `levels` is a tuple of length `numind(tsrc)` that assigns a level or height to the indices of `tsrc`,
Here, `levels` is a tuple of length `numind(tsrc)` that assigns a level or depth to the indices of `tsrc`,
which determines whether they will braid over or under any other index with which they have to change places.
In other words, a smaller value in `levels` means that the corresponding index will be braided over any other index with a higher value.

If `copy=false`, `tdst` might share data with `tsrc` whenever possible. Otherwise, a copy is always made.
Optionally specify a `backend` and `allocator` for the underlying array operation.
Expand Down
8 changes: 8 additions & 0 deletions test/symmetries/doubletree.jl
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,14 @@ using TensorKitSectors
end

BraidingStyle(I) isa HasBraiding && @testset "Double fusion tree: permutation and braiding" begin
if !(BraidingStyle(I) isa SymmetricBraiding)
eq = ntuple(Returns(1), N) # potential problematic level 1
err_str = "ambiguous braid: two indices with equal level 1 have to cross"

@test TensorKit.braid(src, (ntuple(identity, N), ntuple(i -> i + N, N)), (eq, eq)) isa Pair # equal levels are fine when the indices never meet
@test_throws ArgumentError(err_str) TensorKit.braid(src, ((2, 1, ntuple(i -> i + 2, N - 2)...), ntuple(i -> i + N, N)), (eq, eq)) # and rejected when they do
end

for n in 0:(2N)
p = (randperm(2 * N)...,)
Comment thread
borisdevos marked this conversation as resolved.
p1, p2 = p[1:n], p[(n + 1):(2N)]
Expand Down
11 changes: 11 additions & 0 deletions test/tensors/indexmanipulations.jl
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,17 @@ for V in spacelist
t2 = braid(copy(t'), p, levels)
@test t1 ≈ t2
end
hasbraiding && !symmetricbraiding && @timedtestset "Braid: invalid levels" begin
t = rand(ComplexF64, V1 ⊗ V2 ← V3)
p = ((2, 1), (3,))
dupe_levels = (2, 2, 1) # level 2 is the duplicate
bad_lengths = (1, 2)
dupe_err_str = "ambiguous braid: two indices with equal level 2 have to cross"
len_err_str = "length of levels should be $(numind(t)), got $(length(bad_lengths))"
@test_throws ArgumentError(dupe_err_str) braid(t, p, dupe_levels) # duplicate levels
@test_throws ArgumentError(len_err_str) braid(t, p, bad_lengths) # wrong length
@test_throws ArgumentError(dupe_err_str) braid!(similar(t, permute(space(t), p)), t, p, dupe_levels)
end
end
TensorKit.empty_globalcaches!()
end