Required Methods

The following methods must be implemented for any new sector type I <: Sector. These methods are grouped by functionality to help understand their purpose.

Defining the Set of Sectors

A sector type I <: Sector represents the set of all labels that can be used to grade a vector space. This corresponds to all irreducible representations of a group, or all simple objects in a fusion category. The first requirement is making this set enumerable through the iterator interface.

The set of all sector values is obtained via values(I), which returns a SectorValues{I}() singleton type by default. This SectorValues{I}() must be iterable, enabling enumeration of all sectors of type I. To do so, one needs to implement the Iteration interface. In particular, we require the following methods to be defined:

  • Base.iterate(::SectorValues{I}, state...) — Iterate over all possible values of sector type I.
  • Base.IteratorSize(::Type{SectorValues{I}}) — Specify whether the number of sector values is known, finite, or infinite.

Here the IteratorSize is either HasLength(), SizeUnknown() or IsInfinite(). If the length is known (HasLength()), we additionally require:

  • Base.length(::SectorValues{I}): Return the number of sectors

TODO: this is technically optional!

  • Base.getindex(::SectorValues{I}, i::Int): Access the i-th sector value
  • findindex(::SectorValues{I}, c::I): Find the index of sector c
Note

The choice of IteratorSize determines how associative containers are constructed in e.g. a GradedSpace. In the case of HasLength(), an implicit mapping between the values and the position is used to enable storage through Tuples or Vectors. In the other cases one has to resort to AbstractDict-like containers. If the set of simple objects is sufficiently large, it might be beneficial to register its length as SizeUnknown() to avoid putting too much pressure on the compiler.

Example: Z3Irrep

# ℤ₃ has exactly 3 irreps labeled by 0, 1, 2
Base.iterate(::SectorValues{Z3Irrep}, i = 0) = i > 2 ? nothing : (Z2Irrep(i), i+1)
Base.IteratorSize(::Type{SectorValues{Z3Irrep}}) = HasLength()
Base.length(::SectorValues{Z3Irrep}) = 3
Base.getindex(::SectorValues{Z3Irrep}, i::Int) = Z3Irrep(i-1)
findindex(::SectorValues{Z3Irrep}, c::Z3Irrep) = c.n + 1

Example: U1Irrep

# U₁ an irrep for each integer: 0, 1, -1, 2, -2, ...
Base.iterate(::SectorValues{U1Irrep}, i = 0) = (U1Irrep(i), i <= 0 ? (-i + 1) : -i - 1)
Base.IteratorSize(::Type{SectorValues{U1Irrep}}) = IsInfinite()

Fusion Structure

The fusion structure describes how sectors combine when taking tensor products. In code, a ⊗ b returns the allowed output labels, and Nsymbol(a, b, c) tells you whether (or how many times) c appears.

The formal decomposition is:

\[a ⊗ b = \bigoplus_c N^{ab}_c \, c\]

where $N^{ab}_c$ is the fusion multiplicity.

The fusion structure is defined through three related methods:

TensorKitSectors.:⊗Function
⊗(a::I, b::I...) where {I <: Sector}
otimes(a::I, b::I...) where {I <: Sector}

Return an iterable of elements of c::I that appear in the fusion product a ⊗ b. Each sector c should appear at most once in this iteration, even if the multiplicity $N_c^{ab} > 1$. The actual multiplicities are accessed separately through Nsymbol.

The return type is typically SectorProductIterator{I} which provides a type-stable iterable that supports pretty-printing, but could also be any custom iterable.

See also FusionStyle for the trait associated to the fusion behavior of a given sector type.

source
TensorKitSectors.NsymbolFunction
Nsymbol(a::I, b::I, c::I) where {I <: Sector} -> Integer

The fusion multiplicity $N_c^{ab}$, indicating how many times sector c appears in the fusion product a ⊗ b.

The return type depends on the [FusionStyle](@ref), where UniqueFusion and SimpleFusion return Bool values, while [GenericFusion] returns Int.

See also to obtain the set of sectors c that appear in a ⊗ b.

source
TensorKitSectors.FusionStyleType
abstract type FusionStyle
FusionStyle(::Sector)
FusionStyle(I::Type{<:Sector})

Trait to describe the fusion behavior of sectors of type I, which can be either

  • UniqueFusion: each fusion a ⊗ b has exactly one output c.
  • SimpleFusion: fusing a ⊗ b can lead to multiple values c, but each appears at most once.
  • GenericFusion: fusing a ⊗ b can lead to multiple values c that could appear multiple times.

There is an abstract supertype MultipleFusion of which both SimpleFusion and GenericFusion are subtypes. Furthermore, there is a type alias MultiplicityFreeFusion for those fusion types which do not require muliplicity labels.

source

Example: U₁ (Unique Fusion)

⊗(c1::U1Irrep, c2::U1Irrep) = (U1Irrep(charge(c1) + charge(c2)),)
Nsymbol(c1::U1Irrep, c2::U1Irrep, c3::U1Irrep) = charge(c1) + charge(c2) == charge(c3)
FusionStyle(::Type{U1Irrep}) = UniqueFusion()

Example: SU₂ (Simple Fusion) TODO: finish this example

function ⊗(s1::SU2Irrep, s2::SU2Irrep)
    return SectorProductIterator(s1, s2)  # Yields SU2Irrep(j) for j ∈ [|j1-j2|, j1+j2]
end

function Nsymbol(s1::SU2Irrep, s2::SU2Irrep, s3::SU2Irrep)
    j1, j2, j3 = spin(s1), spin(s2), spin(s3)
    return abs(j1 - j2) <= j3 <= j1 + j2 && isinteger(j1 + j2 + j3)
end

FusionStyle(::Type{SU2Irrep}) = SimpleFusion()

Identity and Duality

Every sector type has a unit (identity) label and a notion of dual (conjugate). unit returns the identity label, and dual returns the label that fuses with a to give the unit.

The unit $\mathbb{1}$ acts as the identity under fusion:

\[\mathbb{1} ⊗ a ≅ a ≅ a ⊗ \mathbb{1}\]

The dual of a sector $a$ is the unique sector $\bar{a}$ such that:

\[N^{a\bar{a}}_{\mathbb{1}} = 1 \quad \text{and} \quad N^{\bar{a}a}_{\mathbb{1}} = 1\]

TensorKitSectors.unitFunction
unit(::Sector) -> Sector
unit(::Type{<:Sector}) -> Sector

Return the unit element of this type of sector, provided it is unique.

source
TensorKitSectors.dualFunction
dual(a::Sector) -> Sector

Return the dual label of a, i.e. the unique label ā = dual(a) such that Nsymbol(a, ā, leftunit(a)) == 1 and Nsymbol(ā, a, rightunit(a)) == 1.

source

Example: U₁

unit(::Type{U1Irrep}) = U1Irrep(0)         # charge 0 is the unit
dual(c::U1Irrep) = U1Irrep(-charge(c))    # opposite charge

Example: SU₂

unit(::Type{SU2Irrep}) = SU2Irrep(0)      # spin 0 is the unit
dual(s::SU2Irrep) = s                      # self-dual

Multifusion categories can have multiple units and may distinguish between left and right units. For such cases, additional methods are available:

TensorKitSectors.allunitsFunction
allunits(I::Type{<:Sector}) -> Tuple{I}

Return a tuple with all units of the sector type I. For fusion categories, this will contain only one element.

source
TensorKitSectors.leftunitFunction
leftunit(a::Sector) -> Sector

Return the left unit element corresponding to a; this is necessary for multifusion categories, where the unit may not be unique. See also rightunit and unit.

source
TensorKitSectors.rightunitFunction
rightunit(a::Sector) -> Sector

Return the right unit element corresponding to a; this is necessary for multifusion categories, where the unit may not be unique. See also leftunit and unit.

source

For regular fusion categories the unit object is unique, such that unit, leftunit and rightunit all coincide.

Associativity

The associativity of the fusion tensor product tells us how to relate the basis states $|(a ⊗ b → e) ⊗ c → d\rangle$ to the states $|a ⊗ (b ⊗ c → f) → d\rangle$. This is encoded in the F-symbols, which give the coefficients to transform the different ways of fusing three sectors to one.

TensorKitSectors.FsymbolFunction
Fsymbol(a::I, b::I, c::I, d::I, e::I, f::I) where {I <: Sector}

Return the F-symbol $F^{abc}_d$ that associates the two different fusion orders of sectors a, b and c into an ouput sector d, using either an intermediate sector $a ⊗ b → e$ or $b ⊗ c → f$:

a-<-μ-<-e-<-ν-<-d                                     a-<-λ-<-d
    ∨       ∨       -> Fsymbol(a,b,c,d,e,f)[μ,ν,κ,λ]      ∨
    b       c                                             f
                                                          v
                                                      b-<-κ
                                                          ∨
                                                          c

If FusionStyle(I) is UniqueFusion or SimpleFusion, the F-symbol is a number. Otherwise it is a rank 4 array of size (Nsymbol(a, b, e), Nsymbol(e, c, d), Nsymbol(b, c, f), Nsymbol(a, f, d)).

source

Formally, the F-symbol $F^{abc}_d$ with intermediate sectors $e$ and $f$ is a linear transformation between the two different parenthesizations:

\[(F_{abc}^d)^e_f : (a ⊗ b → e) ⊗ c → d \quad \longrightarrow \quad a ⊗ (b ⊗ c → f) → d\]

For sectors with UniqueFusion or SimpleFusion, the F-symbol is a scalar <:Number. For GenericFusion, it is a rank-4 tensor with indices corresponding to the multiplicity labels of each fusion vertex.

The F-symbols must satisfy the pentagon equation for every choice of sectors:

\[(F_{fcd}^e)^g_h (F_{abh}^e)^f_i = (F_{abc}^g)^f_j (F_{ajd}^e)^g_i (F_{bcd}^i)^j_h\]

This ensures that all ways of reassociating four tensor factors $(((a ⊗ b) ⊗ c) ⊗ d)$ to $(a ⊗ (b ⊗ (c ⊗ d)))$ give the same result, regardless of the sequence of reassociations.

Examples: TODO: fix these examples

# Trivial category: all F-symbols are 1
Fsymbol(::Trivial, ::Trivial, ::Trivial, ::Trivial, ::Trivial, ::Trivial) = 1

# U₁ irreps: all F-symbols are 1 (canonical gauge choice)
Fsymbol(a::U1Irrep, b::U1Irrep, c::U1Irrep, d::U1Irrep, e::U1Irrep, f::U1Irrep) = 1

# SU₂ irreps: computed from 6j-symbols
Fsymbol(a::SU2Irrep, b::SU2Irrep, c::SU2Irrep, d::SU2Irrep, e::SU2Irrep, f::SU2Irrep) = ...

Braiding

Sectors can have a braiding structure that describes the effect of exchanging two tensor factors. The braiding is encoded in the R-symbol $R^{ab}_c$, which is a linear transformation between the fusion channels $a ⊗ b → c$ and $b ⊗ a → c$. For sectors with UniqueFusion or SimpleFusion, the R-symbol is a complex phase. For GenericFusion, it is a square matrix relating the multiplicity spaces of the two fusion orders.

The R-symbols must satisfy the hexagon equations together with the F-symbols:

\[R^{cd}_e (\overline{F}_{dab}^e)^g_c \overline{R}^{da}_g = (F_{abd}^e)^c_f R^{bd}_f (\overline{F}_{adb}^e)^g_f\]

and the analogous equation with $a$ and $b$ swapped. These ensure that the braiding is compatible with the associativity encoded by F-symbols.

The BraidingStyle trait categorizes behavior into four classes:

  • NoBraiding() for planar categories where braiding is undefined
  • Bosonic() for symmetric braiding with trivial twist (all R-symbols square to identity, all twists equal +1)
  • Fermionic() for symmetric braiding with fermion parity (twists can be ±1)
  • Anyonic() for general braiding with arbitrary phases or non-symmetric exchange
TensorKitSectors.RsymbolFunction
Rsymbol(a::I, b::I, c::I) where {I <: Sector}

Returns the R-symbol $R^{ab}_c$ that maps between $c → a ⊗ b$ and $c → b ⊗ a$ as in

a -<-μ-<- c                                 b -<-ν-<- c
     ∨        -> Rsymbol(a, b, c)[μ, ν]          v
     b                                           a

If FusionStyle(I) is UniqueFusion() or SimpleFusion(), the R-symbol is a number. Otherwise it is a square matrix with row and column size Nsymbol(a, b, c) == Nsymbol(b, a, c).

source
TensorKitSectors.BraidingStyleType
abstract type BraidingStyle
BraidingStyle(::Sector) -> ::BraidingStyle
BraidingStyle(I::Type{<:Sector}) -> ::BraidingStyle

Trait to describe the braiding behavior of sectors of type I, which can be either

  • NoBraiding: no braiding structure defined.
  • Bosonic: symmetric braiding structure with a trivial twist.
  • Fermionic: symmetric braiding structure with a non-trivial twist that squares to identity.
  • Anyonic: general braiding structure and arbitrary twists.

There is an abstract supertype HasBraiding that includes all styles that define Rsymbol (everything but NoBraiding). Furthermore, the abstract supertype SymmetricBraiding denotes the cases where braidings are equivalent to crossings, i.e. braiding twice is an identity operation. This includes the Bosonic and Fermionic styles, for which we can uniquely define permutations.

source

Examples: TODO: fix these examples

# Trivial category: bosonic braiding, all R-symbols are 1
BraidingStyle(::Type{Trivial}) = Bosonic()
Rsymbol(::Trivial, ::Trivial, ::Trivial) = 1

# Fermion parity: fermionic braiding
BraidingStyle(::Type{FermionParity}) = Fermionic()
Rsymbol(a::FermionParity, b::FermionParity, c::FermionParity) = 
    iseven(a) || iseven(b) ? 1 : -1

# Fibonacci anyons: anyonic braiding
BraidingStyle(::Type{FibonacciAnyon}) = Anyonic()
Rsymbol(::FibonacciAnyon, ::FibonacciAnyon, ::FibonacciAnyon) = exp(4π*im/5)

# Planar trivial: no braiding
BraidingStyle(::Type{PlanarTrivial}) = NoBraiding()

Utility Methods

Sectors must support a deterministic ordering and hashing so they can be used as dictionary keys, sorted collections, and canonical fusion outputs. One should keep the order consistent with values(I), i.e. the enumeration of objects should happen in a sorted fashion. To achieve this, we must have

  • Base.isless(::Sector, ::Sector) — Define an order on the sectors.
  • Base.hash(::Sector, h::UInt) — Associate a hash value with a sector.

Example: U₁

# U₁: order by absolute charge, then prefer positive over negative to comply with `values(U1Irrep)`
function Base.isless(c1::U1Irrep, c2::U1Irrep)
    q1, q2 = charge(c1), charge(c2)
    return abs(q1) < abs(q2) || (abs(q1) == abs(q2) && q1 > q2 > 0)
end

# Hash consistent with equality
Base.hash(c::U1Irrep, h::UInt) = hash(c.charge, h)