Skip to content

Commit e5892cd

Browse files
committed
Add named product sectors
1 parent 6178240 commit e5892cd

File tree

3 files changed

+153
-93
lines changed

3 files changed

+153
-93
lines changed

src/TensorKitSectors.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export Z2Irrep, Z3Irrep, Z4Irrep, ZNIrrep, U1Irrep
2323
export D3Irrep, D4Irrep, DNIrrep, CU1Irrep
2424
export SU2Irrep
2525
export ZNElement, Z2Element, Z3Element, Z4Element
26-
export ProductSector, TimeReversed
26+
export ProductSector, @NamedProductSector, TimeReversed
2727
export FermionParity, FermionNumber, FermionSpin
2828
export PlanarTrivial, FibonacciAnyon, IsingAnyon
2929
export IsingBimodule

src/product.jl

Lines changed: 138 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -1,64 +1,89 @@
11
# Deligne tensor product of different sectors: ⊠
22
#------------------------------------------------------------------------------#
33
const SectorTuple = Tuple{Vararg{Sector}}
4+
const SectorNamedTuple = NamedTuple{<:Any, <:SectorTuple}
5+
const AnySectorTuple = Union{SectorTuple, SectorNamedTuple}
46

57
"""
6-
ProductSector{T<:SectorTuple}
8+
ProductSector{T <: Union{SectorTuple, SectorNamedTuple}
79
8-
Represents the Deligne tensor product of sectors. The type parameter `T` is a tuple of the
9-
component sectors. The recommended way to construct a `ProductSector` is using the
10+
Represents the Deligne tensor product of sectors. The type parameter `T` is a (named) tuple
11+
of the component sectors. The recommended way to construct a `ProductSector` is using the
1012
[`deligneproduct`](@ref) (`⊠`) operator on the components.
13+
14+
It is also possible to use names for the different factors in the product, either by passing
15+
a `NamedTuple` of `Sector`s to the `ProductSector` constructor, or by making use of the
16+
[`@NamedProductSector`](@ref) macro.
1117
"""
12-
struct ProductSector{T <: SectorTuple} <: Sector
18+
struct ProductSector{T <: AnySectorTuple} <: Sector
1319
sectors::T
20+
ProductSector{T}(x::T) where {T} = new{T}(x)
1421
end
1522

16-
Base.Tuple(a::ProductSector) = a.sectors
23+
ProductSector(x::AnySectorTuple) = ProductSector{typeof(x)}(x)
24+
ProductSector(x, y, z...) = ProductSector((x, y, z...))
25+
ProductSector(x::Sector) = ProductSector((x,))
26+
27+
ProductSector{T}(x) where {T} = ProductSector{T}(convert(T, x))
28+
ProductSector{T}(x, y...) where {T} = ProductSector{T}((x, y...))
29+
ProductSector{T}(x::Sector) where {T} = ProductSector{T}((x,))
30+
31+
const TupleProductSector{T <: SectorTuple} = ProductSector{T}
32+
const NamedProductSector{T <: SectorTuple} = ProductSector{<:NamedTuple{<:Any, T}}
33+
34+
TupleProductSector(s::NamedProductSector) = ProductSector(values(s.sectors))
35+
36+
Base.Tuple(a::ProductSector) = Tuple(a.sectors)
1737

1838
Base.getindex(s::ProductSector, i::Int) = getindex(s.sectors, i)
1939
Base.length(s::ProductSector) = length(s.sectors)
2040
Base.iterate(s::ProductSector, args...) = iterate(s.sectors, args...)
2141
Base.indexed_iterate(s::ProductSector, args...) = Base.indexed_iterate(s.sectors, args...)
2242

43+
Base.@constprop :aggressive function Base.getproperty(s::NamedProductSector, f::Symbol)
44+
return f === :sectors ? getfield(s, f) : getproperty(s.sectors, f)
45+
end
46+
Base.propertynames(s::NamedProductSector) = (:sectors, propertynames(fieldtype(typeof(s), :sectors))...)
47+
48+
_sectors(::Type{NamedTuple{K, V}}) where {K, V} = _sectors(V)
2349
_sectors(::Type{Tuple{}}) = ()
2450
Base.@pure function _sectors(::Type{T}) where {T <: SectorTuple}
2551
return (Base.tuple_type_head(T), _sectors(Base.tuple_type_tail(T))...)
2652
end
2753

28-
function Base.IteratorSize(::Type{SectorValues{ProductSector{T}}}) where {T <: SectorTuple}
54+
function Base.IteratorSize(::Type{SectorValues{ProductSector{T}}}) where {T}
2955
return Base.IteratorSize(Base.Iterators.product(map(values, _sectors(T))...))
3056
end
31-
function Base.size(::SectorValues{ProductSector{T}}) where {T <: SectorTuple}
57+
function Base.size(::SectorValues{ProductSector{T}}) where {T}
3258
return map(s -> length(values(s)), _sectors(T))
3359
end
3460
Base.length(P::SectorValues{<:ProductSector}) = *(size(P)...)
3561

3662
function _length(iter::SectorValues{I}) where {I <: Sector}
3763
return Base.IteratorSize(iter) === Base.IsInfinite() ? typemax(Int) : length(iter)
3864
end
39-
function _size(::SectorValues{ProductSector{T}}) where {T <: SectorTuple}
65+
function _size(::SectorValues{ProductSector{T}}) where {T}
4066
return map(s -> _length(values(s)), _sectors(T))
4167
end
42-
function Base.getindex(P::SectorValues{ProductSector{T}}, i::Int) where {T <: SectorTuple}
68+
function Base.getindex(P::SectorValues{ProductSector{T}}, i::Int) where {T}
4369
I = manhattan_to_multidimensional_index(i, _size(P))
4470
return ProductSector{T}(getindex.(values.(_sectors(T)), I))
4571
end
46-
function findindex(
47-
P::SectorValues{ProductSector{T}},
48-
c::ProductSector{T}
49-
) where {T <: SectorTuple}
50-
return to_manhattan_index(findindex.(values.(_sectors(T)), c.sectors), _size(P))
72+
function findindex(P::SectorValues{ProductSector{T}}, c::ProductSector{T}) where {T}
73+
return to_manhattan_index(findindex.(values.(_sectors(T)), Tuple(c.sectors)), _size(P))
5174
end
5275

53-
function Base.iterate(P::SectorValues{ProductSector{T}}, i = 1) where {T <: SectorTuple}
76+
function Base.iterate(P::SectorValues{<:ProductSector}, i = 1)
5477
Base.IteratorSize(P) != Base.IsInfinite() && i > length(P) && return nothing
5578
return getindex(P, i), i + 1
5679
end
5780

58-
ProductSector{T}(args...) where {T <: SectorTuple} = ProductSector{T}(args)
5981
function Base.convert(::Type{ProductSector{T}}, t::Tuple) where {T <: SectorTuple}
6082
return ProductSector{T}(convert(T, t))
6183
end
84+
function Base.convert(P::Type{<:NamedProductSector{T}}, t::Tuple) where {T <: SectorTuple}
85+
return P(convert(T, t))
86+
end
6287

6388
function unit(::Type{T}) where {T <: ProductSector}
6489
UnitStyle(T) === GenericUnit() && throw_genericunit_error(T)
@@ -83,12 +108,13 @@ function Nsymbol(a::P, b::P, c::P) where {P <: ProductSector}
83108
end
84109

85110
_firstsector(x::ProductSector) = x.sectors[1]
86-
_tailsector(x::ProductSector) = ProductSector(Base.tail(x.sectors))
111+
_tailsector(x::ProductSector) = ProductSector(Base.tail(Tuple(x.sectors)))
87112

88113
function Fsymbol(a::P, b::P, c::P, d::P, e::P, f::P) where {P <: ProductSector}
89114
heads = map(_firstsector, (a, b, c, d, e, f))
90-
tails = map(_tailsector, (a, b, c, d, e, f))
91115
F₁ = Fsymbol(heads...)
116+
length(a) == 1 && return F₁
117+
tails = map(_tailsector, (a, b, c, d, e, f))
92118
F₂ = Fsymbol(tails...)
93119
if F₁ isa Number && F₂ isa Number
94120
return F₁ * F₂
@@ -110,16 +136,12 @@ function Fsymbol(a::P, b::P, c::P, d::P, e::P, f::P) where {P <: ProductSector}
110136
return _kron(F₁, F₂)
111137
end
112138
end
113-
function Fsymbol(
114-
a::P, b::P, c::P, d::P, e::P, f::P
115-
) where {P <: ProductSector{<:Tuple{Sector}}}
116-
return Fsymbol(map(_firstsector, (a, b, c, d, e, f))...)
117-
end
118139

119140
function Rsymbol(a::P, b::P, c::P) where {P <: ProductSector}
120141
heads = map(_firstsector, (a, b, c))
121-
tails = map(_tailsector, (a, b, c))
122142
R₁ = Rsymbol(heads...)
143+
length(a) == 1 && return R₁
144+
tails = map(_tailsector, (a, b, c))
123145
R₂ = Rsymbol(tails...)
124146
if R₁ isa Number && R₂ isa Number
125147
R₁ * R₂
@@ -137,14 +159,12 @@ function Rsymbol(a::P, b::P, c::P) where {P <: ProductSector}
137159
return _kron(R₁, R₂)
138160
end
139161
end
140-
function Rsymbol(a::P, b::P, c::P) where {P <: ProductSector{<:Tuple{Sector}}}
141-
return Rsymbol(map(_firstsector, (a, b, c))...)
142-
end
143162

144163
function Bsymbol(a::P, b::P, c::P) where {P <: ProductSector}
145164
heads = map(_firstsector, (a, b, c))
146-
tails = map(_tailsector, (a, b, c))
147165
B₁ = Bsymbol(heads...)
166+
length(a) == 1 && return B₁
167+
tails = map(_tailsector, (a, b, c))
148168
B₂ = Bsymbol(tails...)
149169
if B₁ isa Number && B₂ isa Number
150170
B₁ * B₂
@@ -162,14 +182,12 @@ function Bsymbol(a::P, b::P, c::P) where {P <: ProductSector}
162182
return _kron(B₁, B₂)
163183
end
164184
end
165-
function Bsymbol(a::P, b::P, c::P) where {P <: ProductSector{<:Tuple{Sector}}}
166-
return Bsymbol(map(_firstsector, (a, b, c))...)
167-
end
168185

169186
function Asymbol(a::P, b::P, c::P) where {P <: ProductSector}
170187
heads = map(_firstsector, (a, b, c))
171-
tails = map(_tailsector, (a, b, c))
172188
A₁ = Asymbol(heads...)
189+
length(a) == 1 && return A₁
190+
tails = map(_tailsector, (a, b, c))
173191
A₂ = Asymbol(tails...)
174192
if A₁ isa Number && A₂ isa Number
175193
A₁ * A₂
@@ -187,46 +205,41 @@ function Asymbol(a::P, b::P, c::P) where {P <: ProductSector}
187205
return _kron(A₁, A₂)
188206
end
189207
end
190-
function Asymbol(a::P, b::P, c::P) where {P <: ProductSector{<:Tuple{Sector}}}
191-
return Asymbol(map(_firstsector, (a, b, c))...)
192-
end
193208

194209
frobenius_schur_phase(p::ProductSector) = prod(frobenius_schur_phase, p.sectors)
195210
frobenius_schur_indicator(p::ProductSector) = prod(frobenius_schur_indicator, p.sectors)
196211

197212
function fusiontensor(a::P, b::P, c::P) where {P <: ProductSector}
198-
return _kron(
199-
fusiontensor(map(_firstsector, (a, b, c))...),
200-
fusiontensor(map(_tailsector, (a, b, c))...)
201-
)
202-
end
203-
204-
function fusiontensor(a::P, b::P, c::P) where {P <: ProductSector{<:Tuple{Sector}}}
205-
return fusiontensor(map(_firstsector, (a, b, c))...)
213+
heads = map(_firstsector, (a, b, c))
214+
C₁ = fusiontensor(heads...)
215+
length(a) == 1 && return C₁
216+
tails = map(_tailsector, (a, b, c))
217+
C₂ = fusiontensor(tails...)
218+
return _kron(C₁, C₂)
206219
end
207220

208-
function FusionStyle(::Type{<:ProductSector{T}}) where {T <: SectorTuple}
221+
function FusionStyle(::Type{<:ProductSector{T}}) where {T}
209222
return mapreduce(FusionStyle, &, _sectors(T))
210223
end
211-
function UnitStyle(::Type{<:ProductSector{T}}) where {T <: SectorTuple}
224+
function UnitStyle(::Type{<:ProductSector{T}}) where {T}
212225
return mapreduce(UnitStyle, &, _sectors(T))
213226
end
214-
function BraidingStyle(::Type{<:ProductSector{T}}) where {T <: SectorTuple}
227+
function BraidingStyle(::Type{<:ProductSector{T}}) where {T}
215228
return mapreduce(BraidingStyle, &, _sectors(T))
216229
end
217-
function Base.isreal(::Type{<:ProductSector{T}}) where {T <: SectorTuple}
230+
function Base.isreal(::Type{<:ProductSector{T}}) where {T}
218231
return mapreduce(isreal, &, _sectors(T))
219232
end
220233

221234
fermionparity(P::ProductSector) = mapreduce(fermionparity, xor, P.sectors)
222235

223-
dim(p::ProductSector) = *(dim.(p.sectors)...)
236+
dim(p::ProductSector) = prod(dim, p.sectors)
224237

225238
Base.isequal(p1::ProductSector, p2::ProductSector) = isequal(p1.sectors, p2.sectors)
226239
Base.hash(p::ProductSector, h::UInt) = hash(p.sectors, h)
227-
function Base.isless(p1::ProductSector{T}, p2::ProductSector{T}) where {T <: SectorTuple}
228-
I1 = findindex.(values.(_sectors(T)), p1.sectors)
229-
I2 = findindex.(values.(_sectors(T)), p2.sectors)
240+
function Base.isless(p1::ProductSector{T}, p2::ProductSector{T}) where {T}
241+
I1 = findindex.(values.(_sectors(T)), Tuple(p1.sectors))
242+
I2 = findindex.(values.(_sectors(T)), Tuple(p2.sectors))
230243
d1 = sum(I1) - length(I1)
231244
d2 = sum(I2) - length(I2)
232245
d1 < d2 && return true
@@ -236,20 +249,23 @@ end
236249

237250
# Default construction from tensor product of sectors
238251
#-----------------------------------------------------
239-
(s1, s2, s3, s4...) = ((s1, s2), s3, s4...)
240-
const deligneproduct =
241252

242-
"""
243-
⊠(s₁::Sector, s₂::Sector)
253+
@doc """
244254
deligneproduct(s₁::Sector, s₂::Sector)
255+
deligneproduct(S₁::Type{<:Sector}, S₂::Type{<:Sector})
256+
s₁ ⊠ s₂
245257
246258
Given two sectors `s₁` and `s₂`, which label an isomorphism class of simple objects in a
247259
fusion category ``C₁`` and ``C₂``, `s1 ⊠ s2` (obtained as `\\boxtimes+TAB`) labels the
248260
isomorphism class of simple objects in the Deligne tensor product category ``C₁ ⊠ C₂``.
249261
250262
The Deligne tensor product also works in the type domain and for spaces and tensors. For
251263
group representations, we have `Irrep[G₁] ⊠ Irrep[G₂] == Irrep[G₁ × G₂]`.
252-
"""
264+
"""
265+
const deligneproduct =
266+
267+
(s1, s2, s3, s4...) = ((s1, s2), s3, s4...)
268+
253269
(s1::Sector, s2::Sector) = ProductSector((s1, s2))
254270
(s1::Trivial, s2::Trivial) = s1
255271
(s1::Sector, s2::Trivial) = s1
@@ -259,34 +275,31 @@ group representations, we have `Irrep[G₁] ⊠ Irrep[G₂] == Irrep[G₁ × G
259275
(s1::Trivial, p2::ProductSector) = p2
260276
(s1::Sector, p2::ProductSector) = ProductSector(tuple(s1, p2.sectors...))
261277
(p1::ProductSector, p2::ProductSector) = ProductSector(tuple(p1.sectors..., p2.sectors...))
262-
263-
(I1::Type{Trivial}, I2::Type{Trivial}) = Trivial
264-
(I1::Type{Trivial}, I2::Type{<:ProductSector}) = I2
265-
(I1::Type{Trivial}, I2::Type{<:Sector}) = I2
266-
267-
(I1::Type{<:ProductSector}, I2::Type{Trivial}) = I1
268-
@static if VERSION >= v"1.8"
269-
Base.@assume_effects :foldable function (
270-
I1::Type{<:ProductSector}, I2::Type{<:ProductSector}
271-
)
272-
T1 = I1.parameters[1]
273-
T2 = I2.parameters[1]
274-
return ProductSector{Tuple{T1.parameters..., T2.parameters...}}
275-
end
276-
else
277-
Base.@pure function (I1::Type{<:ProductSector}, I2::Type{<:ProductSector})
278-
T1 = I1.parameters[1]
279-
T2 = I2.parameters[1]
280-
return ProductSector{Tuple{T1.parameters..., T2.parameters...}}
281-
end
278+
function (p1::NamedProductSector, p2::NamedProductSector)
279+
K = (keys(p1.sectors)..., keys(p2.sectors)...)
280+
V = (values(p1.sectors)..., values(p2.sectors)...)
281+
return ProductSector(NamedTuple{K}(V))
282282
end
283-
(I1::Type{<:ProductSector}, I2::Type{<:Sector}) = I1 ProductSector{Tuple{I2}}
284283

285-
(I1::Type{<:Sector}, I2::Type{Trivial}) = I1
286-
(I1::Type{<:Sector}, I2::Type{<:ProductSector}) = ProductSector{Tuple{I1}} I2
287-
(I1::Type{<:Sector}, I2::Type{<:Sector}) = ProductSector{Tuple{I1, I2}}
284+
Base.@assume_effects :foldable function (::Type{I₁}, ::Type{I₂}) where {I₁<:Sector,I₂<:Sector}
285+
I₁ === Trivial && return I₂
286+
I₂ === Trivial && return I₁
287+
I₁ <: ProductSector || return (ProductSector{Tuple{I₁}}, I₂)
288+
I₂ <: ProductSector || return (I₁, ProductSector{Tuple{I₂}})
289+
return _deligneproduct_impl(I₁, I₂)
290+
end
291+
Base.@assume_effects :foldable function _deligneproduct_impl(
292+
::Type{ProductSector{T₁}}, ::Type{ProductSector{T₂}}
293+
) where {T₁, T₂}
294+
return ProductSector{Tuple{_sectors(T₁)..., _sectors(T₂)...}}
295+
end
296+
Base.@assume_effects :foldable function _deligneproduct_impl(
297+
::Type{ProductSector{NamedTuple{K₁, V₁}}}, ::Type{ProductSector{NamedTuple{K₂, V₂}}}
298+
) where {K₁, V₁, K₂, V₂}
299+
return ProductSector{NamedTuple{(K₁..., K₂...), Tuple{_sectors(V₁)..., _sectors(V₂)...}}}
300+
end
288301

289-
function Base.show(io::IO, P::ProductSector)
302+
function Base.show(io::IO, P::TupleProductSector)
290303
sectors = P.sectors
291304
compact = get(io, :typeinfo, nothing) === typeof(P)
292305
sep = compact ? ", " : ""
@@ -298,8 +311,23 @@ function Base.show(io::IO, P::ProductSector)
298311
end
299312
return print(io, ")")
300313
end
314+
function Base.show(io::IO, P::NamedProductSector)
315+
if get(io, :typeinfo, nothing) !== typeof(P)
316+
print(io, type_repr(typeof(P)))
317+
end
318+
print(io, "(")
319+
first = true
320+
for sector in P.sectors
321+
first || print(io, ", ")
322+
ioc = IOContext(io, :typeinfo => typeof(sector))
323+
show(ioc, sector)
324+
first = false
325+
end
326+
print(io, ")")
327+
return nothing
328+
end
301329

302-
function type_repr(P::Type{<:ProductSector})
330+
function type_repr(P::Type{<:TupleProductSector})
303331
sectors = P.parameters[1].parameters
304332
if length(sectors) == 1
305333
s = "ProductSector{Tuple{" * type_repr(sectors[1]) * "}}"
@@ -316,6 +344,34 @@ function type_repr(P::Type{<:ProductSector})
316344
return s
317345
end
318346

347+
"""
348+
@NamedProductSector{key1::Type1, key2::Type2, ...}
349+
@NamedProductSector begin key1::Type1; key2::Type2; ...; end
350+
351+
This macro gives a more convenient syntax for declaring `ProductSector` types which have
352+
names associated to the components. This is also used when printing `ProductSector` types
353+
to e.g. the REPL.
354+
"""
355+
macro NamedProductSector(ex)
356+
return esc(:(ProductSector{@NamedTuple($ex)}))
357+
end
358+
function type_repr(P::Type{<:NamedProductSector})
359+
names = P.parameters[1].parameters[1]
360+
sectors = P.parameters[1].parameters[2].parameters
361+
iob = IOBuffer()
362+
print(iob, "@NamedProductSector{")
363+
first = true
364+
for (name, sector) in zip(names, sectors)
365+
first || print(iob, ", ")
366+
print(iob, name)
367+
print(iob, "::")
368+
print(iob, type_repr(sector))
369+
first = false
370+
end
371+
print(iob, "}")
372+
return String(take!(iob))
373+
end
374+
319375
#==============================================================================
320376
TODO: the following would implement pretty-printing of product sectors, i.e.
321377
`ProductSector{Tuple{Irrep[G]}}` would be printed as `Irrep[G]`, and

0 commit comments

Comments
 (0)