Skip to content

Commit 23380cd

Browse files
authored
ZNIrrep for larger N (#31)
* generalize ZNIrrep to handle large N * update docstrings * improve modular arithmetic * test some more edge cases * fix printing
1 parent d90f92b commit 23380cd

File tree

3 files changed

+104
-29
lines changed

3 files changed

+104
-29
lines changed

src/TensorKitSectors.jl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ export FermionParity, FermionNumber, FermionSpin
2828
export PlanarTrivial, FibonacciAnyon, IsingAnyon
2929
export IsingBimodule
3030

31+
# accessors
32+
export charge, modulus
33+
3134
# unicode exports
3235
# ---------------
3336
export , , ×

src/irreps/znirrep.jl

Lines changed: 83 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,98 @@
11
# ZNIrrep: irreps of Z_N are labelled by integers mod N; do we ever want N > 64?
22
"""
3-
struct ZNIrrep{N} <: AbstractIrrep{ℤ{N}}
3+
struct ZNIrrep{N, T <: Unsigned} <: AbstractIrrep{ℤ{N}}
44
ZNIrrep{N}(n::Integer)
55
Irrep[ℤ{N}](n::Integer)
66
7-
Represents irreps of the group ``ℤ_N`` for some value of `N<64`. (We need 2*(N-1) <= 127 in
8-
order for a ⊗ b to work correctly.) For `N` equals `2`, `3` or `4`, `ℤ{N}` can be replaced
9-
by `ℤ₂`, `ℤ₃`, `ℤ₄`. An arbitrary `Integer` `n` can be provided to the constructor, but only
10-
the value `mod(n, N)` is relevant.
7+
Represents irreps of the group ``ℤ_N`` for some value of `N`.
8+
For `N` equals `2`, `3` or `4`, `ℤ{N}` can be replaced by [`ℤ₂`](@ref), [`ℤ₃`](@ref), and [`ℤ₄`](@ref).
9+
An arbitrary `Integer` `n` can be provided to the constructor, but only the value `mod(n, N)` is relevant.
10+
11+
The type of the stored integer `T` can either be explicitly provided, or will automatically be determined
12+
to be the smallest unsigned integer type that fits all possible irreps for the given `N`.
13+
14+
See also [`charge`](@ref)` and [`modulus`](@ref) to extract the relevant data.
1115
1216
## Fields
13-
- `n::Int8`: the integer label of the irrep, modulo `N`.
17+
- `n::T`: the integer label of the irrep, modulo `N`.
1418
"""
15-
struct ZNIrrep{N} <: AbstractIrrep{ℤ{N}}
16-
n::Int8
19+
struct ZNIrrep{N, T <: Unsigned} <: AbstractIrrep{ℤ{N}}
20+
n::T
1721
function ZNIrrep{N}(n::Integer) where {N}
18-
@assert N < 64
19-
return new{N}(mod(n, N))
22+
T = _integer_type(N)
23+
return new{N, T}(T(mod(n, N)))
24+
end
25+
function ZNIrrep{N, T}(n::Integer) where {N, T <: Unsigned}
26+
N typemax(T) + 1 ||
27+
throw(TypeError(:ZNIrrep, ZNIrrep{N, T}, ZNIrrep{N, _integer_type(N)}))
28+
return new{N, T}(mod(n, N))
2029
end
2130
end
22-
Base.getindex(::IrrepTable, ::Type{ℤ{N}}) where {N} = ZNIrrep{N}
31+
32+
"""
33+
modulus(c::ZNIrrep{N}) -> N
34+
modulus(::Type{<:ZNIrrep{N}}) -> N
35+
36+
The order of the cyclic group, or the modulus of the charge labels.
37+
"""
38+
modulus(c::ZNIrrep) = modulus(typeof(c))
39+
modulus(::Type{<:ZNIrrep{N}}) where {N} = N
40+
41+
"""
42+
charge(c::ZNIrrep) -> Int
43+
44+
The charge label of the irrep `c`.
45+
"""
46+
charge(c::ZNIrrep) = Int(c.n)
47+
48+
Base.@assume_effects :foldable function _integer_type(N::Integer)
49+
N <= 0 && throw(DomainError(N, "N should be positive"))
50+
for T in (UInt8, UInt16, UInt32, UInt64)
51+
# T needs to fit a
52+
N (typemax(T) + 1) && return T
53+
end
54+
throw(DomainError(N, "N is too large"))
55+
end
56+
57+
Base.getindex(::IrrepTable, ::Type{ℤ{N}}) where {N} = ZNIrrep{N, _integer_type(N)}
2358
Base.convert(Z::Type{<:ZNIrrep}, n::Real) = Z(n)
24-
const Z2Irrep = ZNIrrep{2}
25-
const Z3Irrep = ZNIrrep{3}
26-
const Z4Irrep = ZNIrrep{4}
27-
28-
unit(::Type{ZNIrrep{N}}) where {N} = ZNIrrep{N}(0)
29-
dual(c::ZNIrrep{N}) where {N} = ZNIrrep{N}(-c.n)
30-
(c1::ZNIrrep{N}, c2::ZNIrrep{N}) where {N} = (ZNIrrep{N}(c1.n + c2.n),)
31-
32-
Base.IteratorSize(::Type{SectorValues{ZNIrrep{N}}}) where {N} = HasLength()
33-
Base.length(::SectorValues{ZNIrrep{N}}) where {N} = N
34-
function Base.iterate(::SectorValues{ZNIrrep{N}}, i = 0) where {N}
35-
return i == N ? nothing : (ZNIrrep{N}(i), i + 1)
59+
const Z2Irrep = ZNIrrep{2, UInt8}
60+
const Z3Irrep = ZNIrrep{3, UInt8}
61+
const Z4Irrep = ZNIrrep{4, UInt8}
62+
63+
unit(::Type{ZNIrrep{N, T}}) where {N, T} = ZNIrrep{N, T}(zero(T))
64+
# be careful with `-` for unsigned integers!
65+
dual(c::ZNIrrep{N, T}) where {N, T} = ZNIrrep{N, T}(N - c.n)
66+
(c1::ZNIrrep{N, T}, c2::ZNIrrep{N, T}) where {N, T} = (ZNIrrep{N, T}(modular_add(c1.n, c2.n, Val(N))),)
67+
68+
Base.IteratorSize(::Type{SectorValues{ZNIrrep{N, T}}}) where {N, T} = HasLength()
69+
Base.length(::SectorValues{ZNIrrep{N, T}}) where {N, T} = N
70+
function Base.iterate(::SectorValues{ZNIrrep{N, T}}, i = 0) where {N, T}
71+
return i == N ? nothing : (ZNIrrep{N, T}(i), i + 1)
3672
end
37-
function Base.getindex(::SectorValues{ZNIrrep{N}}, i::Int) where {N}
38-
return 1 <= i <= N ? ZNIrrep{N}(i - 1) : throw(BoundsError(values(ZNIrrep{N}), i))
73+
function Base.getindex(::SectorValues{ZNIrrep{N, T}}, i::Int) where {N, T}
74+
return 1 <= i <= N ? ZNIrrep{N, T}(i - 1) : throw(BoundsError(values(ZNIrrep{N}), i))
3975
end
40-
findindex(::SectorValues{ZNIrrep{N}}, c::ZNIrrep{N}) where {N} = c.n + 1
76+
findindex(::SectorValues{ZNIrrep{N, T}}, c::ZNIrrep{N, T}) where {N, T} = c.n + 1
77+
78+
Base.hash(c::ZNIrrep{N, T}, h::UInt) where {N, T} = hash(c.n, h)
79+
Base.isless(c1::ZNIrrep{N, T}, c2::ZNIrrep{N, T}) where {N, T} = isless(c1.n, c2.n)
4180

42-
Base.hash(c::ZNIrrep{N}, h::UInt) where {N} = hash(c.n, h)
43-
Base.isless(c1::ZNIrrep{N}, c2::ZNIrrep{N}) where {N} = isless(c1.n, c2.n)
81+
# ensure the printing uses `Int`.
82+
function Base.show(io::IO, c::ZNIrrep)
83+
I = typeof(c)
84+
print_type = get(io, :typeinfo, nothing) !== I
85+
print_type && print(io, type_repr(I), '(')
86+
print(io, charge(c))
87+
print_type && print(io, ')')
88+
return nothing
89+
end
90+
91+
# compute x + y mod N, requires 0 <= x < N and 0 <= y < N (unchecked!)
92+
function modular_add(x::T, y::T, ::Val{N}) where {T <: Unsigned, N}
93+
Tmax = typemax(T) + 1
94+
0 < N Tmax || throw(DomainError(N, "N is too large"))
95+
N (typemax(T) + 1) ÷ 2 && return x + y
96+
r, flag = Base.add_with_overflow(x, y)
97+
return ifelse(flag, (Tmax - N) + r, r)
98+
end

test/runtests.jl

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ include("newsectors.jl")
1515
using .NewSectors
1616

1717
const sectorlist = (
18-
Z2Irrep, Z3Irrep, Z4Irrep, U1Irrep,
18+
Z2Irrep, Z3Irrep, Z4Irrep, Irrep[ℤ{200}], U1Irrep,
1919
DNIrrep{3}, DNIrrep{4}, DNIrrep{5}, CU1Irrep,
2020
SU2Irrep, NewSU2Irrep,
2121
FibonacciAnyon, IsingAnyon, FermionParity,
@@ -98,6 +98,23 @@ end
9898
@test repr("text/plain", b b) == "Irrep[D₄](1, false) ⊗ Irrep[D₄](1, false):\n (0, false)\n (0, true)\n (2, false)\n (2, true)"
9999
end
100100

101+
@testset "ZNIrrep edge cases" begin
102+
a = Irrep[Cyclic{255}](254)
103+
@test typeof(a) == ZNIrrep{255, UInt8}
104+
@test charge(dual(a)) == mod(-charge(a), 255)
105+
@test charge(only(a a)) == mod(charge(a) + charge(a), 255)
106+
107+
b = Irrep[Cyclic{256}](255)
108+
@test typeof(b) == ZNIrrep{256, UInt8}
109+
@test charge(dual(b)) == mod(-charge(b), 256)
110+
@test charge(only(b b)) == mod(charge(b) + charge(b), 256)
111+
112+
c = Irrep[Cyclic{257}](256)
113+
@test typeof(c) == ZNIrrep{257, UInt16}
114+
@test charge(dual(c)) == mod(-charge(c), 257)
115+
@test charge(only(c c)) == mod(charge(c) + charge(c), 257)
116+
end
117+
101118
include("multifusion.jl")
102119

103120
@testset "Aqua" begin

0 commit comments

Comments
 (0)