From 454574b136a1bcda936a014ecaafe3467de9a7e1 Mon Sep 17 00:00:00 2001 From: BramVancraeynest Date: Wed, 16 Sep 2026 12:21:27 +0200 Subject: [PATCH 01/14] Add TambaraYamagami for ZN --- src/TensorKitSectors.jl | 2 + src/ty.jl | 117 ++++++++++++++++++++++++++++++++++++++++ test/runtests.jl | 7 +++ 3 files changed, 126 insertions(+) create mode 100644 src/ty.jl diff --git a/src/TensorKitSectors.jl b/src/TensorKitSectors.jl index 69a5b54c..5a7f673f 100644 --- a/src/TensorKitSectors.jl +++ b/src/TensorKitSectors.jl @@ -30,6 +30,7 @@ export ProductSector, NamedSector, @NamedSector, TimeReversed export FermionParity, FermionNumber, FermionSpin export PlanarTrivial, FibonacciAnyon, IsingAnyon export IsingBimodule +export TambaraYamagami # accessors export charge, modulus @@ -83,6 +84,7 @@ include("named.jl") # named tuple product of different sectors include("fermions.jl") # irreps with defined fermionparity and fermionic braiding include("anyons.jl") # non-group sectors include("multifusion.jl") # multifusion example, namely Rep Z2 ⊕ Rep Z2 ≅ Ising +include("ty.jl") # Tambara-Yamagami category for ℤ_N # precompile # ---------- diff --git a/src/ty.jl b/src/ty.jl new file mode 100644 index 00000000..e905b74b --- /dev/null +++ b/src/ty.jl @@ -0,0 +1,117 @@ +# Tambara-Yamagami category for the cyclic group ℤ_N +#---------------------------------------------------------------------------------------# +""" + struct TambaraYamagami{N, K} <: Sector + TambaraYamagami{N, K}(n::Integer) + +Represents the Tambara-Yamagami fusion category built from the cyclic group ``ℤ_N``. +The simple objects are the group elements `0, 1, …, N - 1` of ``ℤ_N``, +together with a single non-invertible object `m`. + +The non-trivial fusion rules are given by +```math +g ⊗ h = g + h \\mod N, \\qquad g ⊗ m = m ⊗ g = m, \\qquad m ⊗ m = \\bigoplus_{g ∈ ℤ_N} g. +``` + +The F-symbols are constructed from a non-degenerate symmetric bicharacter +``χ(g, h) = \\exp(2π i g h / N)``, together with a Frobenius-Schur sign for the +non-invertible object; the latter is fixed as the type parameter `K`. For fixed `N`, +the two choices `K = ±1` generally give distinct fusion categories. + +Only the case `N == 2` and `K == 1` admits a braiding, as this case coincides with Ising, but this is not currently implemented. + +## Fields +- `n::Int`: a group element for `0 <= n < N`, or the non-invertible object `m` for `n == N`. + +## References +[1] D. Tambara and S. Yamagami, *Tensor categories with fusion rules of self-duality for + finite abelian groups*, J. Algebra **209**, 692-707 (1998). +[2] M. Barkeshli, P. Bonderson, M. Cheng and Z. Wang, *Symmetry Fractionalization, Defects, + and Gauging of Topological Phases*, Phys. Rev. B **100**, 115147 (2019), + [arXiv:1410.4540](https://arxiv.org/abs/1410.4540). +""" +struct TambaraYamagami{N, K} <: Sector + n::Int + function TambaraYamagami{N, K}(c) where {N, K} + _check_TY_typeparams(N, K) + 0 <= c <= N || throw(DomainError(c, "TambaraYamagami{$N} labels must satisfy 0 <= c <= $N")) + return new{N, K}(c) + end +end +function TambaraYamagami{N, K}(s::Symbol) where {N, K} + s === :m || throw(ArgumentError("Unknown label $s: use an integer or `:m`")) + return TambaraYamagami{N, K}(N) +end + +function _check_TY_typeparams(N, K) + N isa Int && N >= 1 || throw(ArgumentError("N must be a positive integer, got $N")) + K === 1 || K === -1 || throw(ArgumentError("The Frobenius-Schur indicator K must be either 1 or -1, got $K")) + return nothing +end + +_ism(a::TambaraYamagami{N, K}) where {N, K} = a.n == N # Checks whether c is the non-invertible +_chi(N::Int, g::Int, h::Int) = cispi(2 * g * h / N) # Non-degenerate symmetric bicharacter on ℤ_N + +const TambaraYamagamiProdIterator{N, K} = SectorProductIterator{TambaraYamagami{N, K}} + +Base.length(::SectorValues{TambaraYamagami{N, K}}) where {N, K} = N + 1 +Base.IteratorSize(::Type{<:TambaraYamagamiProdIterator}) = HasLength() + +function Base.length(it::TambaraYamagamiProdIterator{N, K}) where {N, K} + return (_ism(it.a) && _ism(it.b)) ? N : 1 +end +function Base.iterate(::SectorValues{TambaraYamagami{N, K}}, i::Int = 0) where {N, K} + return i > N ? nothing : (TambaraYamagami{N, K}(i), i + 1) +end +function Base.iterate(it::TambaraYamagamiProdIterator{N, K}, state::Int = 0) where {N, K} + a, b = it.a, it.b + am, bm = _ism(a), _ism(b) + if am && bm + state == N && return nothing + return TambaraYamagami{N, K}(state), state + 1 + else + state == 0 || return nothing + c = (am || bm) ? TambaraYamagami{N, K}(N) : TambaraYamagami{N, K}(mod(a.n + b.n, N)) + return c, 1 + end +end + +Base.one(::Type{TambaraYamagami{N, K}}) where {N, K} = TambaraYamagami{N, K}(0) +Base.isless(a1::I, a2::I) where {I <: TambaraYamagami} = isless(a1.n, a2.n) +Base.hash(a::Type{<:TambaraYamagami}, h::UInt) = hash(a.n, h) +dim(a::TambaraYamagami{N}) where {N} = _ism(a) ? sqrt(float(N)) : 1.0 +unit(a::Type{<:TambaraYamagami}) = one(a) +dual(a::TambaraYamagami{N, K}) where {N, K} = _ism(a) ? a : TambaraYamagami{N, K}(mod(- a.n, N)) + +FusionStyle(::Type{<:TambaraYamagami}) = SimpleFusion() +BraidingStyle(::Type{<:TambaraYamagami}) = NoBraiding() +fusionscalartype(::Type{<:TambaraYamagami}) = ComplexF64 + +function Nsymbol(a::I, b::I, c::I) where {N, I <: TambaraYamagami{N}} + am, bm, cm = _ism(a), _ism(b), _ism(c) + if am && bm + return !cm + elseif am || bm + return cm + else + return !cm && c.n == mod(a.n + b.n, N) + end +end + +function Fsymbol(a::I, b::I, c::I, d::I, e::I, f::I) where {N, K, I <: TambaraYamagami{N, K}} + T = fusionscalartype(I) + + (Nsymbol(a, b, e) && Nsymbol(e, c, d) && Nsymbol(b, c, f) && Nsymbol(a, f, d)) || return zero(T) + + am, bm, cm = _ism(a), _ism(b), _ism(c) + + if am && bm && cm # F^{mmm}_m + return (K / sqrt(N)) * conj(_chi(N, e.n, f.n)) + elseif !am && bm && !cm # F^{gmh}_{m} + return _chi(N, a.n, c.n) + elseif am && !bm && cm # F^{mgm}_{m} + return _chi(N, b.n, d.n) + else # F^{abc}_{a+b+c} + return one(T) + end +end diff --git a/test/runtests.jl b/test/runtests.jl index 9bb8d59a..a9d1e775 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -30,6 +30,13 @@ const sectorlist = ( FibonacciAnyon ⊠ Z4Element{3}, IsingBimodule, IsingBimodule ⊠ IsingBimodule, IsingBimodule ⊠ Z2Irrep, IsingBimodule ⊠ SU2Irrep, IsingBimodule ⊠ FibonacciAnyon, + TambaraYamagami{1, 1}, TambaraYamagami{1, -1}, + TambaraYamagami{2, 1}, TambaraYamagami{2, -1}, + TambaraYamagami{3, 1}, TambaraYamagami{3, -1}, + TambaraYamagami{4, 1}, TambaraYamagami{4, -1}, + TambaraYamagami{7, 1}, TambaraYamagami{8, -1}, + TambaraYamagami{3, -1} ⊠ TambaraYamagami{2, -1}, TambaraYamagami{2, 1} ⊠ Z3Irrep, + TambaraYamagami{3, -1} ⊠ FibonacciAnyon, TambaraYamagami{5, -1} ⊠ IsingAnyon, TimeReversed{Z2Irrep}, TimeReversed{Z3Irrep}, TimeReversed{Z4Irrep}, TimeReversed{A4Irrep}, TimeReversed{U1Irrep}, TimeReversed{CU1Irrep}, TimeReversed{SU2Irrep}, From dde29479fc81de5c43b865396e14117ff44b8eed Mon Sep 17 00:00:00 2001 From: BramVancraeynest Date: Wed, 16 Sep 2026 15:05:16 +0200 Subject: [PATCH 02/14] Implement suggested changes --- src/TensorKitSectors.jl | 2 +- src/ty.jl | 17 ++++++++--------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/TensorKitSectors.jl b/src/TensorKitSectors.jl index 5a7f673f..1186ab84 100644 --- a/src/TensorKitSectors.jl +++ b/src/TensorKitSectors.jl @@ -29,8 +29,8 @@ export ZNElement, Z2Element, Z3Element, Z4Element export ProductSector, NamedSector, @NamedSector, TimeReversed export FermionParity, FermionNumber, FermionSpin export PlanarTrivial, FibonacciAnyon, IsingAnyon -export IsingBimodule export TambaraYamagami +export IsingBimodule # accessors export charge, modulus diff --git a/src/ty.jl b/src/ty.jl index e905b74b..2383ebb1 100644 --- a/src/ty.jl +++ b/src/ty.jl @@ -31,7 +31,7 @@ Only the case `N == 2` and `K == 1` admits a braiding, as this case coincides wi [arXiv:1410.4540](https://arxiv.org/abs/1410.4540). """ struct TambaraYamagami{N, K} <: Sector - n::Int + n::UInt8 function TambaraYamagami{N, K}(c) where {N, K} _check_TY_typeparams(N, K) 0 <= c <= N || throw(DomainError(c, "TambaraYamagami{$N} labels must satisfy 0 <= c <= $N")) @@ -49,8 +49,8 @@ function _check_TY_typeparams(N, K) return nothing end -_ism(a::TambaraYamagami{N, K}) where {N, K} = a.n == N # Checks whether c is the non-invertible -_chi(N::Int, g::Int, h::Int) = cispi(2 * g * h / N) # Non-degenerate symmetric bicharacter on ℤ_N +_ism(a::TambaraYamagami{N, K}) where {N, K} = a.n == N # Checks whether a is the non-invertible +_chi(a::I, b::I) where {N, I <: TambaraYamagami{N}} = cispi(2 * a.n * b.n / N) # Non-degenerate symmetric bicharacter on ℤ_N const TambaraYamagamiProdIterator{N, K} = SectorProductIterator{TambaraYamagami{N, K}} @@ -76,12 +76,11 @@ function Base.iterate(it::TambaraYamagamiProdIterator{N, K}, state::Int = 0) whe end end -Base.one(::Type{TambaraYamagami{N, K}}) where {N, K} = TambaraYamagami{N, K}(0) Base.isless(a1::I, a2::I) where {I <: TambaraYamagami} = isless(a1.n, a2.n) Base.hash(a::Type{<:TambaraYamagami}, h::UInt) = hash(a.n, h) dim(a::TambaraYamagami{N}) where {N} = _ism(a) ? sqrt(float(N)) : 1.0 -unit(a::Type{<:TambaraYamagami}) = one(a) -dual(a::TambaraYamagami{N, K}) where {N, K} = _ism(a) ? a : TambaraYamagami{N, K}(mod(- a.n, N)) +unit(::Type{I}) where {I <: TambaraYamagami} = I(0) +dual(a::TambaraYamagami{N, K}) where {N, K} = _ism(a) ? a : TambaraYamagami{N, K}(mod(N - a.n, N)) FusionStyle(::Type{<:TambaraYamagami}) = SimpleFusion() BraidingStyle(::Type{<:TambaraYamagami}) = NoBraiding() @@ -106,11 +105,11 @@ function Fsymbol(a::I, b::I, c::I, d::I, e::I, f::I) where {N, K, I <: TambaraYa am, bm, cm = _ism(a), _ism(b), _ism(c) if am && bm && cm # F^{mmm}_m - return (K / sqrt(N)) * conj(_chi(N, e.n, f.n)) + return (K / sqrt(N)) * conj(_chi(e, f)) elseif !am && bm && !cm # F^{gmh}_{m} - return _chi(N, a.n, c.n) + return _chi(a, c) elseif am && !bm && cm # F^{mgm}_{m} - return _chi(N, b.n, d.n) + return _chi(b, d) else # F^{abc}_{a+b+c} return one(T) end From 401ebe97cc29365eaa0aace3803b86bb33c8a1b9 Mon Sep 17 00:00:00 2001 From: BramVancraeynest Date: Wed, 16 Sep 2026 15:12:04 +0200 Subject: [PATCH 03/14] fix: correct Base.show for TambaraYamagami The show method used an invalid `<:TambaraYamagami` expression and always printed the placeholder "(:m)" regardless of whether the sector was the non-invertible object. It now mirrors the printing convention used by other sector types (skips the type name inside an already-typed context) and distinguishes `m` from group elements via `_ism`, producing output that round-trips through `Meta.parse`. Co-Authored-By: Claude Sonnet 5 --- src/ty.jl | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/ty.jl b/src/ty.jl index 2383ebb1..b2e06917 100644 --- a/src/ty.jl +++ b/src/ty.jl @@ -114,3 +114,15 @@ function Fsymbol(a::I, b::I, c::I, d::I, e::I, f::I) where {N, K, I <: TambaraYa return one(T) end end + +function Base.show(io::IO, a::TambaraYamagami) + print_type = get(io, :typeinfo, nothing) !== typeof(a) + print_type && print(io, type_repr(typeof(a)), "(") + if _ism(a) + print(io, ":m") + else + print(io, a.n) + end + print_type && print(io, ")") + return nothing +end From 0b93f66b0ea6ffee00490e166bdf25b52313e472 Mon Sep 17 00:00:00 2001 From: BramVancraeynest Date: Wed, 16 Sep 2026 16:42:59 +0200 Subject: [PATCH 04/14] Add TY to the docs. Note: I put this in a new folder named "Other", since this fusion category is neither an anyon theory (as it is not even braided) or a (representation category of a) group. --- docs/make.jl | 3 +++ docs/src/sectors.md | 1 + docs/src/sectors/other/ty.md | 52 ++++++++++++++++++++++++++++++++++++ src/ty.jl | 2 +- 4 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 docs/src/sectors/other/ty.md diff --git a/docs/make.jl b/docs/make.jl index 35ba3604..71183952 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -58,6 +58,9 @@ makedocs(; "Group Elements" => [ "ℤₙ (Cyclic)" => "sectors/groupelement/znelement.md", ], + "Other" => [ + "ℤₙ-Tambara-Yamagami" => "sectors/other/ty.md", + ], "Composite Sectors" => [ "Product" => "sectors/composite/product.md", "Named" => "sectors/composite/named.md", diff --git a/docs/src/sectors.md b/docs/src/sectors.md index f3f365bc..ad993c1d 100644 --- a/docs/src/sectors.md +++ b/docs/src/sectors.md @@ -17,6 +17,7 @@ This page provides an overview of the concrete sector types implemented in Tenso | [`HeisenbergIrrep`](@ref)| Rep[H_N] | Generic | Bosonic | No | Weyl-Heisenberg symmetry, projective representations | | [`FibonacciAnyon`](@ref) | Fibonacci category | Simple | Anyonic | No | Topological quantum computing | | [`IsingAnyon`](@ref) | Ising category | Simple | Anyonic | No | Majorana fermions, ν=5/2 QHE | +| [`TambaraYamagami`](@ref) | ℤₙ-Tambara-Yamagami category | Simple | No | No | Self-dual spin chains | | [`FermionParity`](@ref) | fℤ₂ | Unique | Fermionic | No | Fermion parity conservation | | [`FermionNumber`](@ref) | fU₁ | Unique | Fermionic | Yes | Fermion number conservation | | [`FermionSpin`](@ref) | fSU₂ | Simple | Fermionic | Yes | Fermions with spin symmetry | diff --git a/docs/src/sectors/other/ty.md b/docs/src/sectors/other/ty.md new file mode 100644 index 00000000..066f24d2 --- /dev/null +++ b/docs/src/sectors/other/ty.md @@ -0,0 +1,52 @@ +# The $\mathbb Z_N$-Tambara-Yamagami categories: `TambaraYamagami` + +`TambaraYamagami{N, K}` represents the Tambara-Yamagami fusion category ${\rm TY}(\mathbb Z_N, K)$ based on the cyclic group of order $N$. + +The $N+1$ simple objects coincide with the group elements of $\mathbb Z_N$ supplemented with one non-invertible object $m$. The type parameter `K` specifies the Frobenius-Schur indicator of $m$ as $\varkappa_m = K=\pm 1$. + +## Sector type + +```@docs; canonical = false +TambaraYamagami +``` +Here, the type parameters `N` and `K` correspond respectively to the order of the underlying cyclic group and the Frobenius-Schur sign of the non-invertible object. + +## Fusion Rules + +The non-trivial fusion rules read + +```math +g ⊗ h = g + h \mod N, \qquad g ⊗ m = m ⊗ g = m, \qquad m ⊗ m = \bigoplus_{g ∈ ℤ_N} g, +``` +for all group elements $g, h \in \mathbb Z_N$. + +Hence `FusionStyle(::Type{<:TambaraYamagami}) = SimpleFusion()`. + +The quantum dimensions are + +```math +d_g = 1, \quad \forall g\in\mathbb Z_N, \quad {\rm and} \quad d_m = \sqrt{N}. +``` + +## Topological Data + +We write $χ(g, h) = \exp(2π i g h / N)$ for a normalised non-degenerate symmetric bicharacter on $\mathbb Z_N$. The nontrivial F-symbols are then given by + +```math +F^{g\,m\,h}_{m} = χ(g, h), \qquad +F^{m\,g\,m}_{h} = χ(g, h), \qquad +\left[F^{m\,m\,m}_{m}\right]_g^h = \frac{κ}{\sqrt{N}}\,\overline{χ(g, h)}, +``` + +for all $g, h \in \mathbb Z_N$. + +Crucially, there exists no braiding on this fusion category, except when $N=2$, in which case it coincides with `IsingAnyon`. + +## Iteration and basis conventions +`values(TambaraYamagami{N,K})` iterates the labels `0, 1, …, N-1,:m` in increasing order. + +## References +[1] D. Tambara and S. Yamagami, *Tensor categories with fusion rules of self-duality for + finite abelian groups*, J. Algebra **209**, 692-707 (1998). + +[2] M. Barkeshli, P. Bonderson, M. Cheng and Z. Wang, *Symmetry Fractionalization, Defects, and Gauging of Topological Phases*, Phys. Rev. B **100**, 115147 (2019), [arXiv:1410.4540](https://arxiv.org/abs/1410.4540). \ No newline at end of file diff --git a/src/ty.jl b/src/ty.jl index b2e06917..33e724bb 100644 --- a/src/ty.jl +++ b/src/ty.jl @@ -108,7 +108,7 @@ function Fsymbol(a::I, b::I, c::I, d::I, e::I, f::I) where {N, K, I <: TambaraYa return (K / sqrt(N)) * conj(_chi(e, f)) elseif !am && bm && !cm # F^{gmh}_{m} return _chi(a, c) - elseif am && !bm && cm # F^{mgm}_{m} + elseif am && !bm && cm # F^{mgm}_{h} return _chi(b, d) else # F^{abc}_{a+b+c} return one(T) From 592a726473e4e4f73d041729a72877617b2a27ef Mon Sep 17 00:00:00 2001 From: BramVancraeynest Date: Thu, 17 Sep 2026 14:46:31 +0200 Subject: [PATCH 05/14] Restrict values of 1<=N<=128, and extra tests for large N cases. --- src/ty.jl | 8 ++++---- test/runtests.jl | 3 ++- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/ty.jl b/src/ty.jl index 33e724bb..d038f84b 100644 --- a/src/ty.jl +++ b/src/ty.jl @@ -32,10 +32,10 @@ Only the case `N == 2` and `K == 1` admits a braiding, as this case coincides wi """ struct TambaraYamagami{N, K} <: Sector n::UInt8 - function TambaraYamagami{N, K}(c) where {N, K} + function TambaraYamagami{N, K}(n) where {N, K} _check_TY_typeparams(N, K) - 0 <= c <= N || throw(DomainError(c, "TambaraYamagami{$N} labels must satisfy 0 <= c <= $N")) - return new{N, K}(c) + 0 <= n <= N|| throw(DomainError(n, "TambaraYamagami{$N} labels must satisfy 0 <= n <= $N")) + return new{N, K}(n) end end function TambaraYamagami{N, K}(s::Symbol) where {N, K} @@ -44,7 +44,7 @@ function TambaraYamagami{N, K}(s::Symbol) where {N, K} end function _check_TY_typeparams(N, K) - N isa Int && N >= 1 || throw(ArgumentError("N must be a positive integer, got $N")) + N isa Int && 1 <= N <= 128 || throw(ArgumentError("N must satisfy 1 <= N <= 128, got $N")) K === 1 || K === -1 || throw(ArgumentError("The Frobenius-Schur indicator K must be either 1 or -1, got $K")) return nothing end diff --git a/test/runtests.jl b/test/runtests.jl index a9d1e775..0cf75a55 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -32,11 +32,12 @@ const sectorlist = ( IsingBimodule ⊠ SU2Irrep, IsingBimodule ⊠ FibonacciAnyon, TambaraYamagami{1, 1}, TambaraYamagami{1, -1}, TambaraYamagami{2, 1}, TambaraYamagami{2, -1}, - TambaraYamagami{3, 1}, TambaraYamagami{3, -1}, TambaraYamagami{4, 1}, TambaraYamagami{4, -1}, TambaraYamagami{7, 1}, TambaraYamagami{8, -1}, + TambaraYamagami{67, 1}, TambaraYamagami{128, 1}, TambaraYamagami{3, -1} ⊠ TambaraYamagami{2, -1}, TambaraYamagami{2, 1} ⊠ Z3Irrep, TambaraYamagami{3, -1} ⊠ FibonacciAnyon, TambaraYamagami{5, -1} ⊠ IsingAnyon, + TambaraYamagami{64, 1} ⊠ Z4Irrep, TambaraYamagami{128, -1} ⊠ SU2Irrep, TimeReversed{Z2Irrep}, TimeReversed{Z3Irrep}, TimeReversed{Z4Irrep}, TimeReversed{A4Irrep}, TimeReversed{U1Irrep}, TimeReversed{CU1Irrep}, TimeReversed{SU2Irrep}, From 8e5731f5087cc732f9fad491ef3fb1ad6b7d5383 Mon Sep 17 00:00:00 2001 From: BramVancraeynest Date: Thu, 17 Sep 2026 15:19:36 +0200 Subject: [PATCH 06/14] Add modulus function for Tambara-Yamagami. Co-Authored-By: Claude Sonnet 5 --- src/ty.jl | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/src/ty.jl b/src/ty.jl index d038f84b..58d945ee 100644 --- a/src/ty.jl +++ b/src/ty.jl @@ -49,29 +49,37 @@ function _check_TY_typeparams(N, K) return nothing end +""" + modulus(n::TambaraYamagami{N, K}) -> N + modulus(::Type{<:TambaraYamagami{N, K}}) -> N + +The order of the cyclic group, or the modulus of the charge labels. +""" +modulus(n::TambaraYamagami) = modulus(typeof(n)) +modulus(::Type{<:TambaraYamagami{N, K}}) where {N, K} = N + _ism(a::TambaraYamagami{N, K}) where {N, K} = a.n == N # Checks whether a is the non-invertible _chi(a::I, b::I) where {N, I <: TambaraYamagami{N}} = cispi(2 * a.n * b.n / N) # Non-degenerate symmetric bicharacter on ℤ_N -const TambaraYamagamiProdIterator{N, K} = SectorProductIterator{TambaraYamagami{N, K}} - -Base.length(::SectorValues{TambaraYamagami{N, K}}) where {N, K} = N + 1 -Base.IteratorSize(::Type{<:TambaraYamagamiProdIterator}) = HasLength() +Base.length(::SectorValues{I}) where {I <: TambaraYamagami} = modulus(I) + 1 +Base.IteratorSize(::Type{<:SectorProductIterator{I}}) where {I <: TambaraYamagami} = HasLength() -function Base.length(it::TambaraYamagamiProdIterator{N, K}) where {N, K} - return (_ism(it.a) && _ism(it.b)) ? N : 1 +function Base.length(it::SectorProductIterator{I}) where {I <: TambaraYamagami} + return (_ism(it.a) && _ism(it.b)) ? modulus(I) : 1 end -function Base.iterate(::SectorValues{TambaraYamagami{N, K}}, i::Int = 0) where {N, K} - return i > N ? nothing : (TambaraYamagami{N, K}(i), i + 1) +function Base.iterate(::SectorValues{I}, i::Int = 0) where {I <: TambaraYamagami} + return i > modulus(I) ? nothing : (I(i), i + 1) end -function Base.iterate(it::TambaraYamagamiProdIterator{N, K}, state::Int = 0) where {N, K} +function Base.iterate(it::SectorProductIterator{I}, state::Int = 0) where {I <: TambaraYamagami} a, b = it.a, it.b am, bm = _ism(a), _ism(b) + N = modulus(I) if am && bm state == N && return nothing - return TambaraYamagami{N, K}(state), state + 1 + return I(state), state + 1 else state == 0 || return nothing - c = (am || bm) ? TambaraYamagami{N, K}(N) : TambaraYamagami{N, K}(mod(a.n + b.n, N)) + c = (am || bm) ? I(N) : I(mod(a.n + b.n, N)) return c, 1 end end From 23b53a05a1cb077f78a0246ebbceb6d5385440c0 Mon Sep 17 00:00:00 2001 From: Bram Vancraeynest-De Cuiper <50153221+BramVancraeynest@users.noreply.github.com> Date: Fri, 18 Sep 2026 15:16:44 +0200 Subject: [PATCH 07/14] Apply batched suggestions from code review Co-authored-by: Lukas Devos --- src/ty.jl | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/ty.jl b/src/ty.jl index 58d945ee..e062c566 100644 --- a/src/ty.jl +++ b/src/ty.jl @@ -43,8 +43,10 @@ function TambaraYamagami{N, K}(s::Symbol) where {N, K} return TambaraYamagami{N, K}(N) end +const SMALL_TY_CUTOFF = (typemax(UInt8) + 1) ÷ 2 + function _check_TY_typeparams(N, K) - N isa Int && 1 <= N <= 128 || throw(ArgumentError("N must satisfy 1 <= N <= 128, got $N")) + N isa Int && 1 <= N <= SMALL_TY_CUTOFF || throw(ArgumentError("N must satisfy 1 <= N <= $SMALL_TY_CUTOFF, got $N")) K === 1 || K === -1 || throw(ArgumentError("The Frobenius-Schur indicator K must be either 1 or -1, got $K")) return nothing end @@ -56,7 +58,7 @@ end The order of the cyclic group, or the modulus of the charge labels. """ modulus(n::TambaraYamagami) = modulus(typeof(n)) -modulus(::Type{<:TambaraYamagami{N, K}}) where {N, K} = N +modulus(::Type{TambaraYamagami{N, K}}) where {N, K} = N _ism(a::TambaraYamagami{N, K}) where {N, K} = a.n == N # Checks whether a is the non-invertible _chi(a::I, b::I) where {N, I <: TambaraYamagami{N}} = cispi(2 * a.n * b.n / N) # Non-degenerate symmetric bicharacter on ℤ_N @@ -86,9 +88,9 @@ end Base.isless(a1::I, a2::I) where {I <: TambaraYamagami} = isless(a1.n, a2.n) Base.hash(a::Type{<:TambaraYamagami}, h::UInt) = hash(a.n, h) -dim(a::TambaraYamagami{N}) where {N} = _ism(a) ? sqrt(float(N)) : 1.0 +dim(a::TambaraYamagami) = _ism(a) ? sqrt(float(modulus(a))) : 1.0 unit(::Type{I}) where {I <: TambaraYamagami} = I(0) -dual(a::TambaraYamagami{N, K}) where {N, K} = _ism(a) ? a : TambaraYamagami{N, K}(mod(N - a.n, N)) +dual(a::TambaraYamagami) = _ism(a) ? a : typeof(a)(modulus(a) - a.n) FusionStyle(::Type{<:TambaraYamagami}) = SimpleFusion() BraidingStyle(::Type{<:TambaraYamagami}) = NoBraiding() @@ -126,11 +128,7 @@ end function Base.show(io::IO, a::TambaraYamagami) print_type = get(io, :typeinfo, nothing) !== typeof(a) print_type && print(io, type_repr(typeof(a)), "(") - if _ism(a) - print(io, ":m") - else - print(io, a.n) - end + print(io, _ism(a) ? ":m" : Int(a.n)) print_type && print(io, ")") return nothing end From eb0c8cf0f75afe46202d1f50ddea82f40c3f0f58 Mon Sep 17 00:00:00 2001 From: BramVancraeynest Date: Fri, 18 Sep 2026 16:22:37 +0200 Subject: [PATCH 08/14] Clean up and fix of the dual. I think the dual requires the use of mod to correctly deal with the case of the identity object. --- src/ty.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ty.jl b/src/ty.jl index e062c566..052fba81 100644 --- a/src/ty.jl +++ b/src/ty.jl @@ -60,8 +60,8 @@ The order of the cyclic group, or the modulus of the charge labels. modulus(n::TambaraYamagami) = modulus(typeof(n)) modulus(::Type{TambaraYamagami{N, K}}) where {N, K} = N -_ism(a::TambaraYamagami{N, K}) where {N, K} = a.n == N # Checks whether a is the non-invertible -_chi(a::I, b::I) where {N, I <: TambaraYamagami{N}} = cispi(2 * a.n * b.n / N) # Non-degenerate symmetric bicharacter on ℤ_N +_ism(a::TambaraYamagami) = a.n == modulus(a) # Checks whether a is the non-invertible +_chi(a::I, b::I) where {I <: TambaraYamagami} = cispi(2 * a.n * b.n / modulus(I)) # Non-degenerate symmetric bicharacter on ℤ_N Base.length(::SectorValues{I}) where {I <: TambaraYamagami} = modulus(I) + 1 Base.IteratorSize(::Type{<:SectorProductIterator{I}}) where {I <: TambaraYamagami} = HasLength() @@ -90,7 +90,7 @@ Base.isless(a1::I, a2::I) where {I <: TambaraYamagami} = isless(a1.n, a2.n) Base.hash(a::Type{<:TambaraYamagami}, h::UInt) = hash(a.n, h) dim(a::TambaraYamagami) = _ism(a) ? sqrt(float(modulus(a))) : 1.0 unit(::Type{I}) where {I <: TambaraYamagami} = I(0) -dual(a::TambaraYamagami) = _ism(a) ? a : typeof(a)(modulus(a) - a.n) +dual(a::TambaraYamagami) = _ism(a) ? a : typeof(a)(mod(- Int(a.n), modulus(a))) FusionStyle(::Type{<:TambaraYamagami}) = SimpleFusion() BraidingStyle(::Type{<:TambaraYamagami}) = NoBraiding() From 047114d38d94fe716a18d7f19d8fa3647be0899e Mon Sep 17 00:00:00 2001 From: Lukas Devos Date: Mon, 21 Sep 2026 12:57:07 -0400 Subject: [PATCH 09/14] small fixes Co-authored-by: Lukas Devos --- src/ty.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ty.jl b/src/ty.jl index 052fba81..235553cf 100644 --- a/src/ty.jl +++ b/src/ty.jl @@ -46,7 +46,7 @@ end const SMALL_TY_CUTOFF = (typemax(UInt8) + 1) ÷ 2 function _check_TY_typeparams(N, K) - N isa Int && 1 <= N <= SMALL_TY_CUTOFF || throw(ArgumentError("N must satisfy 1 <= N <= $SMALL_TY_CUTOFF, got $N")) + N isa Integer && 1 <= N <= SMALL_TY_CUTOFF || throw(ArgumentError("N must satisfy 1 <= N <= $SMALL_TY_CUTOFF, got $N")) K === 1 || K === -1 || throw(ArgumentError("The Frobenius-Schur indicator K must be either 1 or -1, got $K")) return nothing end @@ -58,7 +58,7 @@ end The order of the cyclic group, or the modulus of the charge labels. """ modulus(n::TambaraYamagami) = modulus(typeof(n)) -modulus(::Type{TambaraYamagami{N, K}}) where {N, K} = N +modulus(::Type{<:TambaraYamagami{N, K}}) where {N, K} = N _ism(a::TambaraYamagami) = a.n == modulus(a) # Checks whether a is the non-invertible _chi(a::I, b::I) where {I <: TambaraYamagami} = cispi(2 * a.n * b.n / modulus(I)) # Non-degenerate symmetric bicharacter on ℤ_N From 6aae57ee2d47a503a2d0e4a5495c6146d1a8d42b Mon Sep 17 00:00:00 2001 From: lkdvos Date: Thu, 1 Oct 2026 10:35:04 -0400 Subject: [PATCH 10/14] Use a Bool type parameter for the Frobenius-Schur indicator --- docs/src/sectors/other/ty.md | 6 +++--- src/ty.jl | 11 ++++++----- test/runtests.jl | 16 ++++++++-------- 3 files changed, 17 insertions(+), 16 deletions(-) diff --git a/docs/src/sectors/other/ty.md b/docs/src/sectors/other/ty.md index 066f24d2..78461f08 100644 --- a/docs/src/sectors/other/ty.md +++ b/docs/src/sectors/other/ty.md @@ -2,14 +2,14 @@ `TambaraYamagami{N, K}` represents the Tambara-Yamagami fusion category ${\rm TY}(\mathbb Z_N, K)$ based on the cyclic group of order $N$. -The $N+1$ simple objects coincide with the group elements of $\mathbb Z_N$ supplemented with one non-invertible object $m$. The type parameter `K` specifies the Frobenius-Schur indicator of $m$ as $\varkappa_m = K=\pm 1$. +The $N+1$ simple objects coincide with the group elements of $\mathbb Z_N$ supplemented with one non-invertible object $m$. The type parameter `K` specifies the Frobenius-Schur indicator of $m$ as $\varkappa_m = (-1)^K = \pm 1$, i.e. `K = false` corresponds to $\varkappa_m = 1$ and `K = true` to $\varkappa_m = -1$. ## Sector type ```@docs; canonical = false TambaraYamagami ``` -Here, the type parameters `N` and `K` correspond respectively to the order of the underlying cyclic group and the Frobenius-Schur sign of the non-invertible object. +Here, the type parameters `N` and `K` correspond respectively to the order of the underlying cyclic group and the Frobenius-Schur sign $(-1)^K$ of the non-invertible object. ## Fusion Rules @@ -35,7 +35,7 @@ We write $χ(g, h) = \exp(2π i g h / N)$ for a normalised non-degenerate symmet ```math F^{g\,m\,h}_{m} = χ(g, h), \qquad F^{m\,g\,m}_{h} = χ(g, h), \qquad -\left[F^{m\,m\,m}_{m}\right]_g^h = \frac{κ}{\sqrt{N}}\,\overline{χ(g, h)}, +\left[F^{m\,m\,m}_{m}\right]_g^h = \frac{\varkappa_m}{\sqrt{N}}\,\overline{χ(g, h)}, ``` for all $g, h \in \mathbb Z_N$. diff --git a/src/ty.jl b/src/ty.jl index 235553cf..b49f8bda 100644 --- a/src/ty.jl +++ b/src/ty.jl @@ -15,10 +15,11 @@ g ⊗ h = g + h \\mod N, \\qquad g ⊗ m = m ⊗ g = m, \\qquad m ⊗ m = \\bigo The F-symbols are constructed from a non-degenerate symmetric bicharacter ``χ(g, h) = \\exp(2π i g h / N)``, together with a Frobenius-Schur sign for the -non-invertible object; the latter is fixed as the type parameter `K`. For fixed `N`, -the two choices `K = ±1` generally give distinct fusion categories. +non-invertible object; the latter is fixed as `κ = (-1)^K` through the type parameter +`K::Bool`. For fixed `N`, the two choices `K = false` (`κ = 1`) and `K = true` (`κ = -1`) +generally give distinct fusion categories. -Only the case `N == 2` and `K == 1` admits a braiding, as this case coincides with Ising, but this is not currently implemented. +Only the case `N == 2` and `K == false` admits a braiding, as this case coincides with Ising, but this is not currently implemented. ## Fields - `n::Int`: a group element for `0 <= n < N`, or the non-invertible object `m` for `n == N`. @@ -47,7 +48,7 @@ const SMALL_TY_CUTOFF = (typemax(UInt8) + 1) ÷ 2 function _check_TY_typeparams(N, K) N isa Integer && 1 <= N <= SMALL_TY_CUTOFF || throw(ArgumentError("N must satisfy 1 <= N <= $SMALL_TY_CUTOFF, got $N")) - K === 1 || K === -1 || throw(ArgumentError("The Frobenius-Schur indicator K must be either 1 or -1, got $K")) + K isa Bool || throw(ArgumentError("K must be a Bool, encoding the Frobenius-Schur indicator (-1)^K, got $K")) return nothing end @@ -115,7 +116,7 @@ function Fsymbol(a::I, b::I, c::I, d::I, e::I, f::I) where {N, K, I <: TambaraYa am, bm, cm = _ism(a), _ism(b), _ism(c) if am && bm && cm # F^{mmm}_m - return (K / sqrt(N)) * conj(_chi(e, f)) + return ((1 - 2 * K) / sqrt(N)) * conj(_chi(e, f)) elseif !am && bm && !cm # F^{gmh}_{m} return _chi(a, c) elseif am && !bm && cm # F^{mgm}_{h} diff --git a/test/runtests.jl b/test/runtests.jl index 0cf75a55..1d80582d 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -30,14 +30,14 @@ const sectorlist = ( FibonacciAnyon ⊠ Z4Element{3}, IsingBimodule, IsingBimodule ⊠ IsingBimodule, IsingBimodule ⊠ Z2Irrep, IsingBimodule ⊠ SU2Irrep, IsingBimodule ⊠ FibonacciAnyon, - TambaraYamagami{1, 1}, TambaraYamagami{1, -1}, - TambaraYamagami{2, 1}, TambaraYamagami{2, -1}, - TambaraYamagami{4, 1}, TambaraYamagami{4, -1}, - TambaraYamagami{7, 1}, TambaraYamagami{8, -1}, - TambaraYamagami{67, 1}, TambaraYamagami{128, 1}, - TambaraYamagami{3, -1} ⊠ TambaraYamagami{2, -1}, TambaraYamagami{2, 1} ⊠ Z3Irrep, - TambaraYamagami{3, -1} ⊠ FibonacciAnyon, TambaraYamagami{5, -1} ⊠ IsingAnyon, - TambaraYamagami{64, 1} ⊠ Z4Irrep, TambaraYamagami{128, -1} ⊠ SU2Irrep, + TambaraYamagami{1, false}, TambaraYamagami{1, true}, + TambaraYamagami{2, false}, TambaraYamagami{2, true}, + TambaraYamagami{4, false}, TambaraYamagami{4, true}, + TambaraYamagami{7, false}, TambaraYamagami{8, true}, + TambaraYamagami{67, false}, TambaraYamagami{128, false}, + TambaraYamagami{3, true} ⊠ TambaraYamagami{2, true}, TambaraYamagami{2, false} ⊠ Z3Irrep, + TambaraYamagami{3, true} ⊠ FibonacciAnyon, TambaraYamagami{5, true} ⊠ IsingAnyon, + TambaraYamagami{64, false} ⊠ Z4Irrep, TambaraYamagami{128, true} ⊠ SU2Irrep, TimeReversed{Z2Irrep}, TimeReversed{Z3Irrep}, TimeReversed{Z4Irrep}, TimeReversed{A4Irrep}, TimeReversed{U1Irrep}, TimeReversed{CU1Irrep}, TimeReversed{SU2Irrep}, From 3060778613efef7cf5c26e6fcbf67f68befc09c0 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Thu, 1 Oct 2026 10:42:56 -0400 Subject: [PATCH 11/14] Remove type-level hash override for TambaraYamagami Co-Authored-By: Claude Opus 5.5 --- src/ty.jl | 1 - test/runtests.jl | 7 +++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/ty.jl b/src/ty.jl index b49f8bda..d31cb505 100644 --- a/src/ty.jl +++ b/src/ty.jl @@ -88,7 +88,6 @@ function Base.iterate(it::SectorProductIterator{I}, state::Int = 0) where {I <: end Base.isless(a1::I, a2::I) where {I <: TambaraYamagami} = isless(a1.n, a2.n) -Base.hash(a::Type{<:TambaraYamagami}, h::UInt) = hash(a.n, h) dim(a::TambaraYamagami) = _ism(a) ? sqrt(float(modulus(a))) : 1.0 unit(::Type{I}) where {I <: TambaraYamagami} = I(0) dual(a::TambaraYamagami) = _ism(a) ? a : typeof(a)(mod(- Int(a.n), modulus(a))) diff --git a/test/runtests.jl b/test/runtests.jl index 1d80582d..d82c05fd 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -231,6 +231,13 @@ end end end +@testset "TambaraYamagami edge cases" begin + I = TambaraYamagami{3, true} + @test hash(I) isa UInt + @test hash(I(1)) == hash(I(1)) + @test hash(I(1)) != hash(I(2)) +end + @testset "Converter constructions" begin @test IsingAnyon(:ψ) isa IsingAnyon @test (IsingAnyon ⊠ IsingAnyon)(:ψ, :σ) isa (IsingAnyon ⊠ IsingAnyon) From 0167ed8b592d8eebaec642c257502160c6c184d4 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Thu, 1 Oct 2026 10:43:11 -0400 Subject: [PATCH 12/14] Require TambaraYamagami modulus N to be an Int Co-Authored-By: Claude Opus 5.5 --- src/ty.jl | 2 +- test/runtests.jl | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/ty.jl b/src/ty.jl index d31cb505..d34583b6 100644 --- a/src/ty.jl +++ b/src/ty.jl @@ -47,7 +47,7 @@ end const SMALL_TY_CUTOFF = (typemax(UInt8) + 1) ÷ 2 function _check_TY_typeparams(N, K) - N isa Integer && 1 <= N <= SMALL_TY_CUTOFF || throw(ArgumentError("N must satisfy 1 <= N <= $SMALL_TY_CUTOFF, got $N")) + N isa Int && 1 <= N <= SMALL_TY_CUTOFF || throw(ArgumentError("N must be an Int satisfying 1 <= N <= $SMALL_TY_CUTOFF, got $N::$(typeof(N))")) K isa Bool || throw(ArgumentError("K must be a Bool, encoding the Frobenius-Schur indicator (-1)^K, got $K")) return nothing end diff --git a/test/runtests.jl b/test/runtests.jl index d82c05fd..8c034e07 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -236,6 +236,9 @@ end @test hash(I) isa UInt @test hash(I(1)) == hash(I(1)) @test hash(I(1)) != hash(I(2)) + @test_throws ArgumentError TambaraYamagami{UInt8(3), true}(1) + @test_throws ArgumentError TambaraYamagami{true, false}(0) + @test_throws ArgumentError TambaraYamagami{3, 1}(0) end @testset "Converter constructions" begin From 9d8241468ab647525a5b7c8305819053d8d73f8c Mon Sep 17 00:00:00 2001 From: lkdvos Date: Thu, 1 Oct 2026 10:43:38 -0400 Subject: [PATCH 13/14] Add direct getindex and findindex for TambaraYamagami values Co-Authored-By: Claude Opus 5.5 --- src/ty.jl | 6 ++++++ test/runtests.jl | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/src/ty.jl b/src/ty.jl index d34583b6..45131475 100644 --- a/src/ty.jl +++ b/src/ty.jl @@ -65,6 +65,12 @@ _ism(a::TambaraYamagami) = a.n == modulus(a) # Checks whether a is the non-inver _chi(a::I, b::I) where {I <: TambaraYamagami} = cispi(2 * a.n * b.n / modulus(I)) # Non-degenerate symmetric bicharacter on ℤ_N Base.length(::SectorValues{I}) where {I <: TambaraYamagami} = modulus(I) + 1 +Base.IteratorSize(::Type{SectorValues{I}}) where {I <: TambaraYamagami} = HasLength() +Base.@propagate_inbounds function Base.getindex(v::SectorValues{I}, i::Int) where {I <: TambaraYamagami} + @boundscheck 1 <= i <= length(v) || throw(BoundsError(v, i)) + return I(i - 1) +end +findindex(::SectorValues{I}, c::I) where {I <: TambaraYamagami} = Int(c.n) + 1 Base.IteratorSize(::Type{<:SectorProductIterator{I}}) where {I <: TambaraYamagami} = HasLength() function Base.length(it::SectorProductIterator{I}) where {I <: TambaraYamagami} diff --git a/test/runtests.jl b/test/runtests.jl index 8c034e07..0f50aded 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -239,6 +239,12 @@ end @test_throws ArgumentError TambaraYamagami{UInt8(3), true}(1) @test_throws ArgumentError TambaraYamagami{true, false}(0) @test_throws ArgumentError TambaraYamagami{3, 1}(0) + for (i, c) in enumerate(values(I)) + @test values(I)[i] == c + @test findindex(values(I), c) === i + end + @test_throws BoundsError values(I)[0] + @test_throws BoundsError values(I)[5] end @testset "Converter constructions" begin From 92dcb26485c36953635b1670236a2fae347e96fb Mon Sep 17 00:00:00 2001 From: lkdvos Date: Thu, 1 Oct 2026 10:43:52 -0400 Subject: [PATCH 14/14] Fix TambaraYamagami docstring field type, error message, and document label cutoff Co-Authored-By: Claude Opus 5.5 --- src/ty.jl | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/ty.jl b/src/ty.jl index 45131475..81d2e367 100644 --- a/src/ty.jl +++ b/src/ty.jl @@ -22,7 +22,7 @@ generally give distinct fusion categories. Only the case `N == 2` and `K == false` admits a braiding, as this case coincides with Ising, but this is not currently implemented. ## Fields -- `n::Int`: a group element for `0 <= n < N`, or the non-invertible object `m` for `n == N`. +- `n::UInt8`: a group element for `0 <= n < N`, or the non-invertible object `m` for `n == N`. ## References [1] D. Tambara and S. Yamagami, *Tensor categories with fusion rules of self-duality for @@ -35,7 +35,7 @@ struct TambaraYamagami{N, K} <: Sector n::UInt8 function TambaraYamagami{N, K}(n) where {N, K} _check_TY_typeparams(N, K) - 0 <= n <= N|| throw(DomainError(n, "TambaraYamagami{$N} labels must satisfy 0 <= n <= $N")) + 0 <= n <= N || throw(DomainError(n, "TambaraYamagami{$N, $K} labels must satisfy 0 <= n <= $N")) return new{N, K}(n) end end @@ -44,6 +44,8 @@ function TambaraYamagami{N, K}(s::Symbol) where {N, K} return TambaraYamagami{N, K}(N) end +# Labels are stored as `UInt8`; restricting to `N <= 128` guarantees that the sum `a.n + b.n` +# of two group labels (at most `2(N - 1) = 254`) never overflows before taking it modulo `N`. const SMALL_TY_CUTOFF = (typemax(UInt8) + 1) ÷ 2 function _check_TY_typeparams(N, K)