Skip to content

Commit 6a66179

Browse files
borisdevoslkdvos
andauthored
Add multifusion sector: IsingBimod (#14)
* add bimodule sector * clean up * remove abstract type `Bimodule` + remove `vertex_labeltype` and `scalartype` * add `SectorValues` properties for `IsingBimod` * define `getindex` for `CatType` for parsability * change Nsymbol to check whether fusions at categorical level are allowed * add tests for `IsingBimod` * comments on parsability and `CatType` * export stuff * fix tests because i'm a silly goose * some cleanup * fix test error * potential format fix * other potential format fix * replace `isModule` with `ismodulecategory` * change `IteratorSize` of custom iterator and remove its `Base.length` method * get rid of braiding properties * use `ismodulecategory` in `Nsymbol` * use lazy construction + change `show` to 3-argument * remove explicit conversion in `dim` * move tests + add pentagon and unitarity check * update pentagon check * include new test file * refactor `IsingBimod` to get rid of `CatType` * shorten `Base.iterate` and `Nsymbol` * update pentagon check * increase patch coverage via printing and error tests * use `repr` for 3-arg `show` tests * get rid of `isC` and friends * Improve checks * Format * Update formatter action * More formatting updates * Simplify `isless` * remove custom `show` and update related tests * apply suggestions * remove +0 * Last small fixes * fix small fixes * Bump v0.1.6 --------- Co-authored-by: Lukas Devos <[email protected]>
1 parent 1bfd67f commit 6a66179

File tree

6 files changed

+226
-38
lines changed

6 files changed

+226
-38
lines changed

.github/workflows/FormatCheck.yml

Lines changed: 6 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,16 @@
1-
name: FormatCheck
1+
name: "Format Check"
22

33
on:
44
push:
55
branches:
66
- 'main'
77
- 'master'
8-
- 'release-'
98
tags: '*'
109
pull_request:
1110

1211
jobs:
13-
build:
14-
runs-on: ${{ matrix.os }}
15-
strategy:
16-
matrix:
17-
version:
18-
- '1' # automatically expands to the latest stable 1.x release of Julia
19-
os:
20-
- ubuntu-latest
21-
arch:
22-
- x64
23-
steps:
24-
- uses: julia-actions/setup-julia@latest
25-
with:
26-
version: ${{ matrix.version }}
27-
arch: ${{ matrix.arch }}
28-
29-
- uses: actions/checkout@v4
30-
- name: Install JuliaFormatter and format
31-
# This will use the latest version by default but you can set the version like so:
32-
#
33-
# julia -e 'using Pkg; Pkg.add(PackageSpec(name="JuliaFormatter", version="0.13.0"))'
34-
run: |
35-
julia -e 'using Pkg; Pkg.add(PackageSpec(name="JuliaFormatter"))'
36-
julia -e 'using JuliaFormatter; format(".", verbose=true)'
37-
- name: Format check
38-
run: |
39-
julia -e '
40-
out = Cmd(`git diff --name-only`) |> read |> String
41-
if out == ""
42-
exit(0)
43-
else
44-
@error "Some files have not been formatted !!!"
45-
write(stdout, out)
46-
exit(1)
47-
end'
12+
format-check:
13+
name: "Format Check"
14+
uses: "QuantumKitHub/.github/.github/workflows/formatcheck.yml@main"
15+
with:
16+
juliaformatter-version: "2"

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "TensorKitSectors"
22
uuid = "13a9c161-d5da-41f0-bcbd-e1a08ae0647f"
33
authors = ["Lukas Devos", "Jutho Haegeman"]
4-
version = "0.1.5"
4+
version = "0.1.6"
55

66
[deps]
77
HalfIntegers = "f0d1745a-41c9-11e9-1dd9-e5d34d218721"

src/TensorKitSectors.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export Trivial, Z2Irrep, Z3Irrep, Z4Irrep, ZNIrrep, U1Irrep, SU2Irrep, CU1Irrep
2121
export ProductSector, TimeReversed
2222
export FermionParity, FermionNumber, FermionSpin
2323
export PlanarTrivial, FibonacciAnyon, IsingAnyon
24+
export IsingBimod
2425

2526
# unicode exports
2627
# ---------------
@@ -50,6 +51,7 @@ include("irreps.jl") # irreps of symmetry groups, with bosonic braiding
5051
include("product.jl") # direct product of different sectors
5152
include("fermions.jl") # irreps with defined fermionparity and fermionic braiding
5253
include("anyons.jl") # non-group sectors
54+
include("multifusion.jl") # multifusion example, namely Rep Z2 ⊕ Rep Z2 ≅ Ising
5355

5456
# precompile
5557
# ----------

src/multifusion.jl

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
# Rep Z2 ⊕ Rep Z2 ≅ Ising is worked out here
2+
# 𝒞 = 𝒟 = RepZ2 ≅ {1, ψ}, while ℳ = Vec ≅ {σ}
3+
# this is mainly meant for testing within TensorKit without relying on MultiTensorKit
4+
#------------------------------------------------------------------------------#
5+
6+
# 𝒞 ℳ
7+
# ℳᵒᵖ 𝒟
8+
struct IsingBimod <: Sector
9+
row::Int
10+
col::Int
11+
label::Int
12+
function IsingBimod(row::Int, col::Int, label::Int)
13+
1 <= row <= 2 && 1 <= col <= 2 ||
14+
throw(DomainError(lazy"Invalid subcategory ($row, $col)"))
15+
0 <= label <= (row == col) ||
16+
throw(ArgumentError(lazy"Invalid label $label for IsingBimod subcategory ($row, $col)"))
17+
return new(row, col, label)
18+
end
19+
end
20+
21+
const all_isingbimod_objects = (IsingBimod(1, 1, 0), IsingBimod(1, 1, 1),
22+
IsingBimod(2, 1, 0), IsingBimod(1, 2, 0),
23+
IsingBimod(2, 2, 0), IsingBimod(2, 2, 1))
24+
25+
Base.IteratorSize(::Type{SectorValues{IsingBimod}}) = Base.SizeUnknown()
26+
Base.iterate(::SectorValues{IsingBimod}, i=1) = iterate(all_isingbimod_objects, i)
27+
Base.length(::SectorValues{IsingBimod}) = length(all_isingbimod_objects)
28+
29+
(a::IsingBimod, b::IsingBimod) = IsingBimodIterator(a, b)
30+
31+
struct IsingBimodIterator
32+
a::IsingBimod
33+
b::IsingBimod
34+
end
35+
36+
Base.IteratorSize(::Type{IsingBimodIterator}) = Base.SizeUnknown()
37+
Base.IteratorEltype(::Type{IsingBimodIterator}) = Base.HasEltype()
38+
Base.eltype(::Type{IsingBimodIterator}) = IsingBimod
39+
40+
function Base.iterate(iter::IsingBimodIterator, state=0)
41+
a, b = iter.a, iter.b
42+
a.col == b.row || return nothing
43+
44+
_state = (a.row == b.col == a.col) ? mod(a.label + b.label, 2) : state
45+
return state < (1 + (a.row == b.col && a.row != a.col)) ?
46+
(IsingBimod(a.row, b.col, _state), state + 1) : nothing
47+
end
48+
49+
function Base.convert(::Type{IsingAnyon}, a::IsingBimod) # identify RepZ2 ⊕ RepZ2 ≅ Ising
50+
(a.row != a.col) && return IsingAnyon()
51+
return IsingAnyon(a.label == 0 ? :I : )
52+
end
53+
54+
FusionStyle(::Type{IsingBimod}) = SimpleFusion() # no multiplicities
55+
BraidingStyle(::Type{IsingBimod}) = NoBraiding() # because of module categories
56+
57+
function Nsymbol(a::IsingBimod, b::IsingBimod, c::IsingBimod)
58+
if (a.row != c.row) || (a.col != b.row) || (b.col != c.col)
59+
throw(ArgumentError("invalid fusion channel"))
60+
end
61+
return Nsymbol(convert(IsingAnyon, a), convert(IsingAnyon, b), convert(IsingAnyon, c))
62+
end
63+
64+
function Fsymbol(a::I, b::I, c::I, d::I, e::I, f::I) where {I<:IsingBimod}
65+
Nsymbol(a, b, e) && Nsymbol(e, c, d) &&
66+
Nsymbol(b, c, f) && Nsymbol(a, f, d) || return 0.0
67+
return Fsymbol(convert(IsingAnyon, a), convert(IsingAnyon, b), convert(IsingAnyon, c),
68+
convert(IsingAnyon, d), convert(IsingAnyon, e), convert(IsingAnyon, f))
69+
end
70+
71+
# ℳ ↔ ℳop when conjugating elements within these
72+
Base.conj(a::IsingBimod) = IsingBimod(a.col, a.row, a.label)
73+
74+
rightone(a::IsingBimod) = IsingBimod(a.col, a.col, 0)
75+
leftone(a::IsingBimod) = IsingBimod(a.row, a.row, 0)
76+
77+
function Base.one(a::IsingBimod)
78+
a.row == a.col ||
79+
throw(DomainError("unit of module category ($(a.row), $(a.col)) doesn't exist"))
80+
return IsingBimod(a.row, a.col, 0)
81+
end
82+
83+
Base.one(::Type{IsingBimod}) = throw(ArgumentError("one of Type IsingBimod doesn't exist"))
84+
85+
function Base.isless(a::IsingBimod, b::IsingBimod)
86+
return isless((a.col, a.row, a.label), (b.col, b.row, b.label))
87+
end
88+
89+
function Base.hash(a::IsingBimod, h::UInt)
90+
return hash(a.label, hash(a.row, hash(a.col, h)))
91+
end

test/multifusion.jl

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
I = IsingBimod
2+
Istr = TensorKitSectors.type_repr(I)
3+
@testset "$Istr sector" begin
4+
@testset "Basic type properties" begin
5+
@test eval(Meta.parse(sprint(show, I))) == I
6+
@test eval(Meta.parse(TensorKitSectors.type_repr(I))) == I
7+
end
8+
9+
M = IsingBimod(1, 2, 0)
10+
Mop = IsingBimod(2, 1, 0)
11+
C0 = IsingBimod(1, 1, 0)
12+
C1 = IsingBimod(1, 1, 1)
13+
D0 = IsingBimod(2, 2, 0)
14+
D1 = IsingBimod(2, 2, 1)
15+
C = rand([C0, C1])
16+
D = rand([D0, D1])
17+
s = rand((M, Mop, C, D))
18+
19+
@testset "Basic properties" begin
20+
@test @constinferred(one(C1)) == @constinferred(leftone(C1)) ==
21+
@constinferred(rightone(C1))
22+
@test one(D1) == leftone(D1) == rightone(D1)
23+
@test one(C1) == leftone(M) == rightone(Mop)
24+
@test one(D1) == rightone(M) == leftone(Mop)
25+
26+
@test eval(Meta.parse(sprint(show, s))) == s
27+
@test @constinferred(hash(s)) == hash(deepcopy(s))
28+
@constinferred dual(s)
29+
@test dual(dual(s)) == s
30+
@constinferred dim(s)
31+
@constinferred frobeniusschur(s)
32+
@constinferred convert(IsingAnyon, s)
33+
34+
@constinferred Bsymbol(C, C, C)
35+
@constinferred Fsymbol(D, D, D, D, D, D)
36+
end
37+
38+
@testset "$Istr: Value iterator" begin
39+
@test eltype(values(I)) == I
40+
@test_throws ArgumentError one(I)
41+
sprev = C0 # first in SectorValues
42+
for (i, s) in enumerate(values(I))
43+
@test !isless(s, sprev) # confirm compatibility with sort order
44+
@test s == @constinferred (values(I)[i])
45+
@test findindex(values(I), s) == i
46+
sprev = s
47+
i >= 10 && break
48+
end
49+
@test C0 == first(values(I))
50+
@test (@constinferred findindex(values(I), C0)) == 1
51+
for s in collect(values(I))
52+
@test (@constinferred values(I)[findindex(values(I), s)]) == s
53+
end
54+
end
55+
56+
@testset "$Istr: Printing and errors" begin
57+
@test eval(Meta.parse(sprint(show, C))) == C
58+
@test eval(Meta.parse(sprint(show, M))) == M
59+
@test eval(Meta.parse(sprint(show, Mop))) == Mop
60+
@test eval(Meta.parse(sprint(show, D))) == D
61+
@test_throws DomainError one(M)
62+
@test_throws DomainError one(Mop)
63+
end
64+
65+
@testset "$Istr Fusion rules and F-symbols" begin
66+
argerr = ArgumentError("invalid fusion channel")
67+
# forbidden fusions
68+
for obs in [(C, D), (D, C), (M, M), (Mop, Mop), (D, M), (M, C), (Mop, D), (C, Mop)]
69+
@test isempty((obs...))
70+
@test_throws argerr Nsymbol(obs..., s)
71+
end
72+
73+
# allowed fusions
74+
for obs in [(C, C), (D, D), (M, Mop), (Mop, M), (C, M), (Mop, C), (M, D), (D, Mop)]
75+
@test !isempty((obs...))
76+
end
77+
78+
@test Nsymbol(C, C, one(C)) == Nsymbol(D, D, one(D)) == 1
79+
@test Nsymbol(C, M, M) == Nsymbol(Mop, C, Mop) == 1
80+
@test Nsymbol(M, D, M) == Nsymbol(D, Mop, Mop) == 1
81+
82+
@test_throws argerr Nsymbol(M, Mop, D)
83+
@test_throws argerr Nsymbol(Mop, M, C)
84+
@test Nsymbol(M, Mop, C) == Nsymbol(Mop, M, D) == 1
85+
86+
# non-trivial F-symbol checks
87+
@test Fsymbol(Mop, M, D1, D0, D0, M) == 0 # ℳᵒᵖ x ℳ x 𝒟 → ℳ allowed, but 𝒟-labels mismatch
88+
@test Fsymbol(Mop, M, D1, D0, D1, M) == 1
89+
@test Fsymbol(M, Mop, C1, C0, C0, Mop) == 0
90+
@test Fsymbol(M, Mop, C1, C0, C1, Mop) == 1
91+
92+
@test Fsymbol(C, M, D, M, M, M) == (C.label * D.label == 0 ? 1 : -1) # 𝒞 x ℳ x 𝒟 → ℳ allowed
93+
@test_throws argerr Fsymbol(M, Mop, M, Mop, C, D) == 0 # IsingAnyon conversion would give non-zero
94+
@test_throws argerr Fsymbol(Mop, M, Mop, M, D, C) == 0
95+
96+
@test Fsymbol(M, Mop, M, M, C, D) ==
97+
(C.label * D.label == 0 ? inv(sqrt(2)) : -inv(sqrt(2))) # ℳ x ℳᵒᵖ x ℳ → ℳ allowed
98+
@test Fsymbol(Mop, M, Mop, Mop, D, C) ==
99+
(C.label * D.label == 0 ? inv(sqrt(2)) : -inv(sqrt(2))) # ℳᵒᵖ x ℳ x ℳᵒᵖ → ℳᵒᵖ allowed
100+
101+
@test_throws argerr Fsymbol(M, Mop, M, Mop, C, D)
102+
end
103+
104+
@testset "$Istr: Unitarity of F-move" begin
105+
objects = collect(values(I))
106+
for a in objects, b in objects, c in objects
107+
for d in (a, b, c)
108+
es = collect(intersect((a, b), map(dual, (c, dual(d)))))
109+
fs = collect(intersect((b, c), map(dual, (dual(d), a))))
110+
@test length(es) == length(fs)
111+
F = [Fsymbol(a, b, c, d, e, f) for e in es, f in fs]
112+
@test isapprox(F' * F, one(F); atol=1e-12, rtol=1e-12)
113+
end
114+
end
115+
end
116+
117+
@testset "$Istr: Pentagon equation" begin
118+
objects = collect(values(I))
119+
for a in objects, b in objects, c in objects, d in objects
120+
# compatibility checks built in Fsymbol
121+
@test pentagon_equation(a, b, c, d; atol=1e-12, rtol=1e-12)
122+
end
123+
end
124+
end

test/runtests.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,8 @@ end
7575
@test size(Rsymbol(a, b, c)) == (0, 0)
7676
end
7777

78+
include("multifusion.jl")
79+
7880
@testset "Aqua" begin
7981
using Aqua: Aqua
8082
Aqua.test_all(TensorKitSectors)

0 commit comments

Comments
 (0)