Skip to content

Commit 6fd09bc

Browse files
borisdevoslkdvos
andauthored
Compatibility with MultiTensorKit (#247)
* This PR extends the `one` functionality to a `leftone` and `rightone` in order to support multifusion categories, and updates the fusion tree manipulations accordingly. * This PR also includes a number of other important fixes, in the docs, in `eltype(::BlockIterator)`, in the scalar type determination in tensor operations, etc. --------- Co-authored-by: Lukas Devos <[email protected]>
1 parent d509e10 commit 6fd09bc

File tree

10 files changed

+43
-34
lines changed

10 files changed

+43
-34
lines changed

docs/src/man/categories.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,7 @@ morphism from ``I`` to ``V``. To map morphisms from ``\mathrm{Hom}(W,V)`` to ele
229229
``V ⊗ W^*``, i.e. morphisms in ``\mathrm{Hom}(I, V ⊗ W^*)``, we use another morphism
230230
``\mathrm{Hom}(I, W ⊗ W^*)`` which can be considered as the inverse of the evaluation map.
231231

232-
Hence, duality in a monoidal category is defined via an *exact paring*, i.e. two families
232+
Hence, duality in a monoidal category is defined via an *exact pairing*, i.e. two families
233233
of non-degenerate morphisms, the evaluation (or co-unit) ``ϵ_V: {}^{∨}V ⊗ V → I`` and the
234234
coevaluation (or unit) ``η_V: I → V ⊗ {}^{∨}V`` which satisfy the "snake rules":
235235

docs/src/man/sectors.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -773,7 +773,7 @@ groups.
773773

774774
Other methods for `ElementarySpace`, such as [`dual`](@ref), [`fuse`](@ref) and
775775
[`flip`](@ref) also work. In fact, `GradedSpace` is the reason `flip` exists, cause
776-
in this case it is different then `dual`. The existence of flip originates from the
776+
in this case it is different than `dual`. The existence of flip originates from the
777777
non-trivial isomorphism between ``R_{\overline{a}}`` and ``R_{a}^*``, i.e. the
778778
representation space of the dual ``\overline{a}`` of sector ``a`` and the dual of the
779779
representation space of sector ``a``. In order for `flip(V)` to be isomorphic to `V`, it is
@@ -898,7 +898,7 @@ for the specific case ``N_1=4`` and ``N_2=3``. We can separate this tree into th
898898
part ``(b_1⊗b_2)⊗b_3 → c`` and the splitting part ``c→(((a_1⊗a_2)⊗a_3)⊗a_4)``. Given that
899899
the fusion tree can be considered to be the adjoint of a corresponding splitting tree
900900
``c→(b_1⊗b_2)⊗b_3``, we now first consider splitting trees in isolation. A splitting tree
901-
which goes from one coupled sectors ``c`` to ``N`` uncoupled sectors ``a_1``, ``a_2``, …,
901+
which goes from one coupled sector ``c`` to ``N`` uncoupled sectors ``a_1``, ``a_2``, …,
902902
``a_N`` needs ``N-2`` additional internal sector labels ``e_1``, …, ``e_{N-2}``, and, if
903903
`FusionStyle(I) isa GenericFusion`, ``N-1`` additional multiplicity labels ``μ_1``,
904904
…, ``μ_{N-1}``. We henceforth refer to them as vertex labels, as they are associated with
@@ -912,7 +912,7 @@ the orthogonality condition
912912
which now forces all internal lines ``e_k`` and vertex labels ``μ_l`` to be the same.
913913

914914
There is one subtle remark that we have so far ignored. Within the specific subtypes of
915-
`Sector`, we do not explicitly distinguish between ``R_a^*`` (simply denoted as ``a`^*``
915+
`Sector`, we do not explicitly distinguish between ``R_a^*`` (simply denoted as ``a^*``
916916
and graphically depicted as an upgoing arrow ``a``) and ``R_{\bar{a}}`` (simply denoted as
917917
``\bar{a}`` and depicted with a downgoing arrow), i.e. between the dual space of ``R_a`` on
918918
which the conjugated irrep acts, or the irrep ``\bar{a}`` to which the complex conjugate of

docs/src/man/spaces.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -297,8 +297,8 @@ corresponding spaces, but in general none of those will be canonical.
297297
There are also a number of convenience functions to create isomorphic spaces. The function
298298
`fuse(V1, V2, ...)` or `fuse(V1 ⊗ V2 ⊗ ...)` returns an elementary space that is isomorphic
299299
to `V1 ⊗ V2 ⊗ ...`. The function `flip(V::ElementarySpace)` returns a space that is
300-
isomorphic to `V` but has `isdual(flip(V)) == isdual(V')`, i.e. if `V` is a normal space
301-
than `flip(V)` is a dual space. `flip(V)` is different from `dual(V)` in the case of
300+
isomorphic to `V` but has `isdual(flip(V)) == isdual(V')`, i.e., if `V` is a normal space,
301+
then `flip(V)` is a dual space. `flip(V)` is different from `dual(V)` in the case of
302302
[`GradedSpace`](@ref). It is useful to flip a tensor index from a ket to a bra (or
303303
vice versa), by contracting that index with a unitary map from `V1` to `flip(V1)`. We refer
304304
to the reference on [vector space methods](@ref s_spacemethods) for further information.

src/fusiontrees/iterator.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ function _fusiontree_iterate(uncoupledsectors::NTuple{N},
157157
nextout = iterate(outiterN, outstateN)
158158
nextout === nothing && return nothing
159159
b, outstateN = nextout
160-
vertexiterN = c dual(b)
160+
vertexiterN = coupled dual(b)
161161
nextline = iterate(vertexiterN)
162162
end
163163
a, vertexstateN = nextline

src/fusiontrees/manipulations.jl

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -165,8 +165,9 @@ operation is the inverse of `insertat` in the sense that if
165165
f₂ = FusionTree{I}(f.uncoupled, f.coupled, isdual2, f.innerlines, f.vertices)
166166
return f₁, f₂
167167
elseif M === 0
168-
f₁ = FusionTree{I}((), one(I), (), ())
169-
uncoupled2 = (one(I), f.uncoupled...)
168+
u = leftone(f.uncoupled[1])
169+
f₁ = FusionTree{I}((), u, (), ())
170+
uncoupled2 = (u, f.uncoupled...)
170171
coupled2 = f.coupled
171172
isdual2 = (false, f.isdual...)
172173
innerlines2 = N >= 2 ? (f.uncoupled[1], f.innerlines...) : ()
@@ -230,7 +231,7 @@ function merge(f₁::FusionTree{I,N₁}, f₂::FusionTree{I,N₂},
230231
return insertat(f, N₁ + 1, f₂)
231232
end
232233
function merge(f₁::FusionTree{I,0}, f₂::FusionTree{I,0}, c::I, μ) where {I}
233-
isone(c) ||
234+
Nsymbol(f₁.coupled, f₂.coupled, c) == μ == 1 ||
234235
throw(SectorMismatch("cannot fuse sectors $(f₁.coupled) and $(f₂.coupled) to $c"))
235236
return fusiontreedict(I)(f₁ => Fsymbol(c, c, c, c, c, c)[1, 1, 1, 1])
236237
end
@@ -289,7 +290,8 @@ function bendright(f₁::FusionTree{I,N₁}, f₂::FusionTree{I,N₂}) where {I<
289290
# map final splitting vertex (a, b)<-c to fusion vertex a<-(c, dual(b))
290291
@assert N₁ > 0
291292
c = f₁.coupled
292-
a = N₁ == 1 ? one(I) : (N₁ == 2 ? f₁.uncoupled[1] : f₁.innerlines[end])
293+
a = N₁ == 1 ? leftone(f₁.uncoupled[1]) :
294+
(N₁ == 2 ? f₁.uncoupled[1] : f₁.innerlines[end])
293295
b = f₁.uncoupled[N₁]
294296

295297
uncoupled1 = TupleTools.front(f₁.uncoupled)
@@ -360,7 +362,7 @@ function foldright(f₁::FusionTree{I,N₁}, f₂::FusionTree{I,N₂}) where {I<
360362
hasmultiplicities = FusionStyle(a) isa GenericFusion
361363
local newtrees
362364
if N₁ == 1
363-
cset = (one(c1),)
365+
cset = (leftone(c1),) # or rightone(a)
364366
elseif N₁ == 2
365367
cset = (f₁.uncoupled[2],)
366368
else
@@ -729,7 +731,8 @@ function elementary_trace(f::FusionTree{I,N}, i) where {I<:Sector,N}
729731
# if trace is zero, return empty dict
730732
(b == dual(b′) && f.isdual[i] != f.isdual[j]) || return newtrees
731733
if i < N
732-
inner_extended = (one(I), f.uncoupled[1], f.innerlines..., f.coupled)
734+
inner_extended = (leftone(f.uncoupled[1]), f.uncoupled[1], f.innerlines...,
735+
f.coupled)
733736
a = inner_extended[i]
734737
d = inner_extended[i + 2]
735738
a == d || return newtrees
@@ -753,11 +756,11 @@ function elementary_trace(f::FusionTree{I,N}, i) where {I<:Sector,N}
753756
if i > 1
754757
c = f.innerlines[i - 1]
755758
if FusionStyle(I) isa MultiplicityFreeFusion
756-
coeff *= Fsymbol(a, b, dual(b), a, c, one(I))
759+
coeff *= Fsymbol(a, b, dual(b), a, c, rightone(a))
757760
else
758761
μ = f.vertices[i - 1]
759762
ν = f.vertices[i]
760-
coeff *= Fsymbol(a, b, dual(b), a, c, one(I))[μ, ν, 1, 1]
763+
coeff *= Fsymbol(a, b, dual(b), a, c, rightone(a))[μ, ν, 1, 1]
761764
end
762765
end
763766
if f.isdual[i]
@@ -766,8 +769,9 @@ function elementary_trace(f::FusionTree{I,N}, i) where {I<:Sector,N}
766769
push!(newtrees, f′ => coeff)
767770
return newtrees
768771
else # i == N
772+
unit = leftone(b)
769773
if N == 2
770-
f′ = FusionTree{I}((), one(I), (), (), ())
774+
f′ = FusionTree{I}((), unit, (), (), ())
771775
coeff = sqrtdim(b)
772776
if !(f.isdual[N])
773777
coeff *= conj(frobeniusschur(b))
@@ -778,18 +782,17 @@ function elementary_trace(f::FusionTree{I,N}, i) where {I<:Sector,N}
778782
uncoupled_ = TupleTools.front(f.uncoupled)
779783
inner_ = TupleTools.front(f.innerlines)
780784
coupled_ = f.innerlines[end]
781-
@assert coupled_ == dual(b)
782785
isdual_ = TupleTools.front(f.isdual)
783786
vertices_ = TupleTools.front(f.vertices)
784787
f_ = FusionTree(uncoupled_, coupled_, isdual_, inner_, vertices_)
785788
fs = FusionTree((b,), b, (!f.isdual[1],), (), ())
786-
for (f_′, coeff) in merge(fs, f_, one(I), 1)
787-
f_′.innerlines[1] == one(I) || continue
789+
for (f_′, coeff) in merge(fs, f_, unit, 1) # coloring gets reversed here, should be the other unit
790+
f_′.innerlines[1] == unit || continue
788791
uncoupled′ = Base.tail(Base.tail(f_′.uncoupled))
789792
isdual′ = Base.tail(Base.tail(f_′.isdual))
790793
inner′ = N <= 4 ? () : Base.tail(Base.tail(f_′.innerlines))
791794
vertices′ = N <= 3 ? () : Base.tail(Base.tail(f_′.vertices))
792-
f′ = FusionTree(uncoupled′, one(I), isdual′, inner′, vertices′)
795+
f′ = FusionTree(uncoupled′, unit, isdual′, inner′, vertices′) # and this one?
793796
coeff *= sqrtdim(b)
794797
if !(f.isdual[N])
795798
coeff *= conj(frobeniusschur(b))

src/spaces/gradedspace.jl

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ isomorphism classes of simple objects of a unitary and pivotal (pre-)fusion cate
1212
1313
Here `dims` represents the degeneracy or multiplicity of every sector.
1414
15-
The data structure `D` of `dims` will depend on the result `Base.IteratorElsize(values(I))`;
15+
The data structure `D` of `dims` will depend on the result `Base.IteratorSize(values(I))`;
1616
if the result is of type `HasLength` or `HasShape`, `dims` will be stored in a
1717
`NTuple{N,Int}` with `N = length(values(I))`. This requires that a sector `s::I` can be
1818
transformed into an index via `s == getindex(values(I), i)` and
@@ -189,16 +189,16 @@ end
189189

190190
function Base.show(io::IO, V::GradedSpace{I}) where {I<:Sector}
191191
print(io, type_repr(typeof(V)), "(")
192-
seperator = ""
192+
separator = ""
193193
comma = ", "
194194
io2 = IOContext(io, :typeinfo => I)
195195
for c in sectors(V)
196196
if isdual(V)
197-
print(io2, seperator, dual(c), "=>", dim(V, c))
197+
print(io2, separator, dual(c), "=>", dim(V, c))
198198
else
199-
print(io2, seperator, c, "=>", dim(V, c))
199+
print(io2, separator, c, "=>", dim(V, c))
200200
end
201-
seperator = comma
201+
separator = comma
202202
end
203203
print(io, ")")
204204
V.dual && print(io, "'")

src/tensors/blockiterator.jl

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,5 @@ end
1010

1111
Base.IteratorSize(::BlockIterator) = Base.HasLength()
1212
Base.IteratorEltype(::BlockIterator) = Base.HasEltype()
13-
Base.eltype(::Type{<:BlockIterator{T}}) where {T} = blocktype(T)
13+
Base.eltype(::Type{<:BlockIterator{T}}) where {T} = Pair{sectortype(T),blocktype(T)}
1414
Base.length(iter::BlockIterator) = length(iter.structure)
15-
Base.isdone(iter::BlockIterator, state...) = Base.isdone(iter.structure, state...)

src/tensors/tensoroperations.jl

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,8 @@ end
5252

5353
function TO.tensoradd_type(TC, A::AbstractTensorMap, ::Index2Tuple{N₁,N₂},
5454
::Bool) where {N₁,N₂}
55-
M = similarstoragetype(A, TC)
55+
I = sectortype(A)
56+
M = similarstoragetype(A, sectorscalartype(I) <: Real ? TC : complex(TC))
5657
return tensormaptype(spacetype(A), N₁, N₂, M)
5758
end
5859

@@ -113,10 +114,11 @@ function TO.tensorcontract_type(TC,
113114
A::AbstractTensorMap, ::Index2Tuple, ::Bool,
114115
B::AbstractTensorMap, ::Index2Tuple, ::Bool,
115116
::Index2Tuple{N₁,N₂}) where {N₁,N₂}
116-
M = similarstoragetype(A, TC)
117-
M == similarstoragetype(B, TC) ||
118-
throw(ArgumentError("incompatible storage types:\n$(M)$(similarstoragetype(B, TC))"))
119117
spacetype(A) == spacetype(B) || throw(SpaceMismatch("incompatible space types"))
118+
I = sectortype(A)
119+
M = similarstoragetype(A, sectorscalartype(I) <: Real ? TC : complex(TC))
120+
MB = similarstoragetype(B, sectorscalartype(I) <: Real ? TC : complex(TC))
121+
M == MB || throw(ArgumentError("incompatible storage types:\n$(M)$(MB)"))
120122
return tensormaptype(spacetype(A), N₁, N₂, M)
121123
end
122124

test/diagonal.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ diagspacelist = ((ℂ^4)', ℂ[Z2Irrep](0 => 2, 1 => 3),
2929
next = @constinferred Nothing iterate(bs, state)
3030
b2 = @constinferred block(t, first(blocksectors(t)))
3131
@test b1 == b2
32-
@test eltype(bs) === typeof(b1) === TensorKit.blocktype(t)
32+
@test eltype(bs) === Pair{typeof(c),typeof(b1)}
33+
@test typeof(b1) === TensorKit.blocktype(t)
3334
# basic linear algebra
3435
@test isa(@constinferred(norm(t)), real(T))
3536
@test norm(t)^2 dot(t, t)

test/tensors.jl

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,9 @@ for V in spacelist
4747
next = @constinferred Nothing iterate(bs, state)
4848
b2 = @constinferred block(t, first(blocksectors(t)))
4949
@test b1 == b2
50-
@test eltype(bs) === typeof(b1) === TensorKit.blocktype(t)
50+
@test eltype(bs) === Pair{typeof(c),typeof(b1)}
51+
@test typeof(b1) === TensorKit.blocktype(t)
52+
@test typeof(c) === sectortype(t)
5153
end
5254
end
5355
@timedtestset "Tensor Dict conversion" begin
@@ -107,7 +109,9 @@ for V in spacelist
107109
next = @constinferred Nothing iterate(bs, state)
108110
b2 = @constinferred block(t', first(blocksectors(t')))
109111
@test b1 == b2
110-
@test eltype(bs) === typeof(b1) === TensorKit.blocktype(t')
112+
@test eltype(bs) === Pair{typeof(c),typeof(b1)}
113+
@test typeof(b1) === TensorKit.blocktype(t')
114+
@test typeof(c) === sectortype(t)
111115
# linear algebra
112116
@test isa(@constinferred(norm(t)), real(T))
113117
@test norm(t)^2 dot(t, t)

0 commit comments

Comments
 (0)