Skip to content
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
611fcde
add bimodule sector
borisdevos Jun 18, 2025
cb2e94f
clean up
borisdevos Jun 18, 2025
ef32b2a
remove abstract type `Bimodule` + remove `vertex_labeltype` and `scal…
borisdevos Jun 18, 2025
b9ab369
add `SectorValues` properties for `IsingBimod`
borisdevos Jun 18, 2025
73ed396
define `getindex` for `CatType` for parsability
borisdevos Jun 18, 2025
00bfc7f
change Nsymbol to check whether fusions at categorical level are allowed
borisdevos Jun 19, 2025
9222c88
add tests for `IsingBimod`
borisdevos Jun 19, 2025
acd63c9
comments on parsability and `CatType`
borisdevos Jun 19, 2025
d85294b
export stuff
borisdevos Jun 19, 2025
345e318
fix tests because i'm a silly goose
borisdevos Jun 19, 2025
e55230c
some cleanup
borisdevos Jun 19, 2025
8199072
fix test error
borisdevos Jun 19, 2025
e20f9a8
potential format fix
borisdevos Jun 19, 2025
a54ed80
other potential format fix
borisdevos Jun 19, 2025
f484f34
replace `isModule` with `ismodulecategory`
borisdevos Jun 19, 2025
cfe287e
change `IteratorSize` of custom iterator and remove its `Base.length`…
borisdevos Jun 19, 2025
036ea10
get rid of braiding properties
borisdevos Jun 19, 2025
e362094
use `ismodulecategory` in `Nsymbol`
borisdevos Jun 19, 2025
92530ba
use lazy construction + change `show` to 3-argument
borisdevos Jun 23, 2025
17038ad
remove explicit conversion in `dim`
borisdevos Jun 23, 2025
73acc95
move tests + add pentagon and unitarity check
borisdevos Jun 23, 2025
fa1e407
update pentagon check
borisdevos Jun 24, 2025
53fa0c6
include new test file
borisdevos Jun 24, 2025
afae67f
refactor `IsingBimod` to get rid of `CatType`
borisdevos Jun 24, 2025
708f2d7
shorten `Base.iterate` and `Nsymbol`
borisdevos Jun 25, 2025
208e254
update pentagon check
borisdevos Jun 25, 2025
be8b265
increase patch coverage via printing and error tests
borisdevos Jun 25, 2025
f8a311c
use `repr` for 3-arg `show` tests
borisdevos Jun 25, 2025
569392e
get rid of `isC` and friends
borisdevos Jun 25, 2025
7b6a56e
Improve checks
lkdvos Jun 25, 2025
2f937d0
Format
lkdvos Jun 25, 2025
56ac007
Update formatter action
lkdvos Jun 25, 2025
b1ce6ef
More formatting updates
lkdvos Jun 25, 2025
8a748c6
Simplify `isless`
lkdvos Jun 25, 2025
dc37053
remove custom `show` and update related tests
borisdevos Jun 26, 2025
2fd7df4
apply suggestions
borisdevos Jun 27, 2025
5d9f14e
remove +0
borisdevos Jun 28, 2025
d45c819
Last small fixes
lkdvos Jun 30, 2025
ccf3067
fix small fixes
lkdvos Jun 30, 2025
adc70f5
Bump v0.1.6
lkdvos Jun 30, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/TensorKitSectors.jl
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export Trivial, Z2Irrep, Z3Irrep, Z4Irrep, ZNIrrep, U1Irrep, SU2Irrep, CU1Irrep
export ProductSector, TimeReversed
export FermionParity, FermionNumber, FermionSpin
export PlanarTrivial, FibonacciAnyon, IsingAnyon
export IsingBimod, CatType, 𝒞, 𝒟, ℳ, ℳᵒᵖ

# unicode exports
# ---------------
Expand Down Expand Up @@ -50,6 +51,7 @@ include("irreps.jl") # irreps of symmetry groups, with bosonic braiding
include("product.jl") # direct 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

# precompile
# ----------
Expand Down
186 changes: 186 additions & 0 deletions src/multifusion.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
# Rep Z2 ⊕ Rep Z2 ≅ Ising is worked out here
# 𝒞 = 𝒟 = RepZ2 ≅ {1, ψ}, while ℳ = Vec ≅ {σ}
# this is mainly meant for testing within TensorKit without relying on MultiTensorKit
#------------------------------------------------------------------------------#

@enum CatType 𝒞 = 1 ℳ = 3 ℳᵒᵖ = 2 𝒟 = 4
# possible TODO: get rid of CatType and use Int instead -> prevents the need to export 𝒞 etc
Comment thread
borisdevos marked this conversation as resolved.
Outdated
function Base.getindex(a::CatType, label::Int)
return IsingBimod(a, label)
end

struct IsingBimod <: Sector
type::CatType
label::Int
function IsingBimod(type::CatType, label::Int)
Comment thread
borisdevos marked this conversation as resolved.
Outdated
if type == 𝒞
0 ≤ label ≤ 1 ||
throw(ArgumentError("Invalid 𝒞 label for Ising bimodule: $(label)"))
elseif type == ℳ
label == 0 ||
throw(ArgumentError("Invalid ℳ label for Ising bimodule: $(label)"))
elseif type == ℳᵒᵖ
label == 0 ||
throw(ArgumentError("Invalid ℳᵒᵖ label for Ising bimodule: $(label)"))
elseif type == 𝒟
0 ≤ label ≤ 1 ||
throw(ArgumentError("Invalid 𝒟 label for Ising bimodule: $(label)"))
end
Comment thread
borisdevos marked this conversation as resolved.
Outdated
return new(type, label)
end
end

isC(x::IsingBimod) = x.type == 𝒞
isM(x::IsingBimod) = x.type == ℳ
isMop(x::IsingBimod) = x.type == ℳᵒᵖ
isD(x::IsingBimod) = x.type == 𝒟

function ismodulecategory(a::IsingBimod)
return a.type in (ℳ, ℳᵒᵖ)
end

const all_isingbimod_objects = (IsingBimod(𝒞, 0), IsingBimod(𝒞, 1), IsingBimod(ℳᵒᵖ, 0),
IsingBimod(ℳ, 0), IsingBimod(𝒟, 0), IsingBimod(𝒟, 1))

Base.IteratorSize(::Type{<:SectorValues{IsingBimod}}) = Base.SizeUnknown()
Comment thread
lkdvos marked this conversation as resolved.
Outdated
Base.iterate(::SectorValues{IsingBimod}, i=1) = iterate(all_isingbimod_objects, i)
Base.length(::SectorValues{IsingBimod}) = length(all_isingbimod_objects)

Check warning on line 47 in src/multifusion.jl

View check run for this annotation

Codecov / codecov/patch

src/multifusion.jl#L47

Added line #L47 was not covered by tests
Comment thread
borisdevos marked this conversation as resolved.
Outdated

⊗(a::IsingBimod, b::IsingBimod) = IsingBimodIterator(a, b)

struct IsingBimodIterator
a::IsingBimod
b::IsingBimod
end

Base.IteratorSize(::Type{IsingBimodIterator}) = Base.SizeUnknown()
Base.IteratorEltype(::Type{IsingBimodIterator}) = Base.HasEltype()
Base.eltype(::Type{IsingBimodIterator}) = IsingBimod

Check warning on line 58 in src/multifusion.jl

View check run for this annotation

Codecov / codecov/patch

src/multifusion.jl#L56-L58

Added lines #L56 - L58 were not covered by tests

function Base.iterate(iter::IsingBimodIterator, state=0)
if isC(iter.a) && isC(iter.b) # 𝒞 × 𝒞 -> 𝒞
return state == 0 ? (IsingBimod(𝒞, mod(iter.a.label + iter.b.label, 2)), 1) :
nothing
end

if isD(iter.a) && isD(iter.b) # 𝒟 × 𝒟 -> 𝒟
return state == 0 ? (IsingBimod(𝒟, mod(iter.a.label + iter.b.label, 2)), 1) :
nothing
end

if isM(iter.a) && isMop(iter.b) # ℳ × ℳop -> 𝒞
return state < 2 ? (IsingBimod(𝒞, state), state + 1) : nothing
end

if isMop(iter.a) && isM(iter.b) # ℳop × ℳ -> 𝒟
return state < 2 ? (IsingBimod(𝒟, state), state + 1) : nothing
end

if isC(iter.a) && isM(iter.b) # 𝒞 × ℳ -> ℳ
return state == 0 ? (iter.b, 1) : nothing
end

if isMop(iter.a) && isC(iter.b) # ℳop x 𝒞 -> ℳop
return state == 0 ? (iter.a, 1) : nothing
end

if isM(iter.a) && isD(iter.b) # ℳ x 𝒟 -> ℳ
return state == 0 ? (iter.a, 1) : nothing
end

if isD(iter.a) && isMop(iter.b) # 𝒟 x ℳop -> ℳop
return state == 0 ? (iter.b, 1) : nothing
end

return nothing
end

function Base.convert(::Type{IsingAnyon}, a::IsingBimod) # identify RepZ2 ⊕ RepZ2 ≅ Ising
ismodulecategory(a) && return IsingAnyon(:σ)
return IsingAnyon(a.label == 0 ? :I : :ψ)
end

FusionStyle(::Type{IsingBimod}) = SimpleFusion() # no multiplicities
BraidingStyle(::Type{IsingBimod}) = NoBraiding() # because of module categories

Check warning on line 104 in src/multifusion.jl

View check run for this annotation

Codecov / codecov/patch

src/multifusion.jl#L104

Added line #L104 was not covered by tests

function Nsymbol(a::IsingBimod, b::IsingBimod, c::IsingBimod)
# if a and b can fuse, then so can dual(a) and c, and c and dual(b)
# only needs to be explicitly checked when CatTypes differ or when there's a module category involved
if a.type != b.type || a.type != c.type || b.type != c.type ||
any(ismodulecategory, (a, b, c))
c ∈ a ⊗ b && dual(b) ∈ dual(c) ⊗ a && dual(a) ∈ b ⊗ dual(c) ||
throw(ArgumentError("invalid fusion channel"))
Comment thread
lkdvos marked this conversation as resolved.
Outdated
end
Comment thread
borisdevos marked this conversation as resolved.
return Nsymbol(convert(IsingAnyon, a), convert(IsingAnyon, b), convert(IsingAnyon, c))
end

function Fsymbol(a::I, b::I, c::I, d::I, e::I, f::I) where {I<:IsingBimod}
Nsymbol(a, b, e) && Nsymbol(e, c, d) &&
Nsymbol(b, c, f) && Nsymbol(a, f, d) || return 0.0
Comment thread
borisdevos marked this conversation as resolved.
Outdated
return Fsymbol(convert(IsingAnyon, a), convert(IsingAnyon, b), convert(IsingAnyon, c),
convert(IsingAnyon, d), convert(IsingAnyon, e), convert(IsingAnyon, f))
end

function Base.conj(a::IsingBimod) # ℳ ↔ ℳop when conjugating elements within these
if isC(a) || isD(a) # self-conjugate within RepZ2
return a
elseif isM(a)
return IsingBimod(ℳᵒᵖ, a.label)
else
return IsingBimod(ℳ, a.label)
end
end

function rightone(a::IsingBimod)
if isC(a) || isD(a)
return IsingBimod(a.type, 0)
elseif isM(a) # ℳ as right-𝒟 module category
return IsingBimod(𝒟, 0)
else
return IsingBimod(𝒞, 0)
end
end

function leftone(a::IsingBimod)
if isC(a) || isD(a)
return IsingBimod(a.type, 0)
elseif isM(a) # ℳ as left-𝒞 module category
return IsingBimod(𝒞, 0)
else
return IsingBimod(𝒟, 0)
end
end

function Base.one(a::IsingBimod)
if isC(a) || isD(a)
return IsingBimod(a.type, 0)
else
throw(DomainError("unit of module category $(a.type) doesn't exist"))

Check warning on line 158 in src/multifusion.jl

View check run for this annotation

Codecov / codecov/patch

src/multifusion.jl#L158

Added line #L158 was not covered by tests
end
end

Base.one(::Type{IsingBimod}) = throw(ArgumentError("one of Type IsingBimod doesn't exist"))

Base.isreal(::Type{IsingBimod}) = false

Check warning on line 164 in src/multifusion.jl

View check run for this annotation

Codecov / codecov/patch

src/multifusion.jl#L164

Added line #L164 was not covered by tests
Comment thread
borisdevos marked this conversation as resolved.
Outdated

function Base.show(io::IO, a::IsingBimod)
if isC(a)
print(io, "𝒞[$(a.label)]")
elseif isM(a)
print(io, "ℳ[$(a.label)]")
elseif isMop(a)
print(io, "ℳᵒᵖ[$(a.label)]")
elseif isD(a)
print(io, "𝒟[$(a.label)]")
end
Comment thread
borisdevos marked this conversation as resolved.
Outdated
end

function Base.isless(a::IsingBimod, b::IsingBimod)
return isless((a.type, a.label), (b.type, b.label)) # order 𝒞 < ℳᵒᵖ < ℳ < 𝒟, and then within each cat according to label
end

function Base.hash(a::IsingBimod, h::UInt)
return hash(a.label, hash(a.type, h))
end

dim(a::IsingBimod) = dim(convert(IsingAnyon, a))
Comment thread
borisdevos marked this conversation as resolved.
Outdated
95 changes: 95 additions & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,101 @@ end
@test size(Rsymbol(a, b, c)) == (0, 0)
end

@testset "IsingBimod sector" begin
Comment thread
borisdevos marked this conversation as resolved.
Outdated
I = IsingBimod
Istr = TensorKitSectors.type_repr(I)
@testset "Basic type properties" begin
@test eval(Meta.parse(sprint(show, I))) == I
@test eval(Meta.parse(TensorKitSectors.type_repr(I))) == I
end

M = IsingBimod(CatType(3), 0)
Mop = IsingBimod(CatType(2), 0)
C0 = IsingBimod(CatType(1), 0)
C1 = IsingBimod(CatType(1), 1)
D0 = IsingBimod(CatType(4), 0)
D1 = IsingBimod(CatType(4), 1)
C = rand([C0, C1])
D = rand([D0, D1])
s = rand((M, Mop, C, D))

@testset "Basic properties" begin
@test @constinferred(one(C1)) == @constinferred(leftone(C1)) ==
@constinferred(rightone(C1))
@test one(D1) == leftone(D1) == rightone(D1)
@test one(C1) == leftone(M) == rightone(Mop)
@test one(D1) == rightone(M) == leftone(Mop)

@test eval(Meta.parse(sprint(show, s))) == s
@test @constinferred(hash(s)) == hash(deepcopy(s))
@constinferred dual(s)
@test dual(dual(s)) == s
@constinferred dim(s)
@constinferred frobeniusschur(s)
@constinferred convert(IsingAnyon, s)

@constinferred Bsymbol(C, C, C)
@constinferred Fsymbol(D, D, D, D, D, D)
end

@testset "Sector $Istr: Value iterator" begin
@test eltype(values(I)) == I
@test_throws ArgumentError one(I)
sprev = C0 # first in SectorValues
for (i, s) in enumerate(values(I))
@test !isless(s, sprev) # confirm compatibility with sort order
@test s == @constinferred (values(I)[i])
@test findindex(values(I), s) == i
sprev = s
i >= 10 && break
end
@test C0 == first(values(I))
@test (@constinferred findindex(values(I), C0)) == 1
for s in smallset(I)
@test (@constinferred values(I)[findindex(values(I), s)]) == s
end
end

@testset "Fusion rules and F-symbols" begin
Comment thread
borisdevos marked this conversation as resolved.
Outdated
argerr = ArgumentError("invalid fusion channel")
# forbidden fusions
for obs in [(C, D), (D, C), (M, M), (Mop, Mop), (D, M), (M, C), (Mop, D), (C, Mop)]
@test isempty(⊗(obs...))
@test_throws argerr Nsymbol(obs..., s)
end

# allowed fusions
for obs in [(C, C), (D, D), (M, Mop), (Mop, M), (C, M), (Mop, C), (M, D), (D, Mop)]
@test !isempty(⊗(obs...))
end

@test Nsymbol(C, C, one(C)) == Nsymbol(D, D, one(D)) == 1
@test Nsymbol(C, M, M) == Nsymbol(Mop, C, Mop) == 1
@test Nsymbol(M, D, M) == Nsymbol(D, Mop, Mop) == 1

@test_throws argerr Nsymbol(M, Mop, D)
@test_throws argerr Nsymbol(Mop, M, C)
@test Nsymbol(M, Mop, C) == Nsymbol(Mop, M, D) == 1

# non-trivial F-symbol checks
@test Fsymbol(Mop, M, D1, D0, D0, M) == 0 # ℳᵒᵖ x ℳ x 𝒟 → ℳ allowed, but 𝒟-labels mismatch
@test Fsymbol(Mop, M, D1, D0, D1, M) == 1
@test Fsymbol(M, Mop, C1, C0, C0, Mop) == 0
@test Fsymbol(M, Mop, C1, C0, C1, Mop) == 1

@test Fsymbol(C, M, D, M, M, M) == (C.label * D.label == 0 ? 1 : -1) # 𝒞 x ℳ x 𝒟 → ℳ allowed
@test_throws argerr Fsymbol(M, Mop, M, Mop, C, D) == 0 # IsingAnyon conversion would give non-zero
@test_throws argerr Fsymbol(Mop, M, Mop, M, D, C) == 0

@test Fsymbol(M, Mop, M, M, C, D) ==
(C.label * D.label == 0 ? inv(sqrt(2)) : -inv(sqrt(2))) # ℳ x ℳᵒᵖ x ℳ → ℳ allowed
@test Fsymbol(Mop, M, Mop, Mop, D, C) ==
(C.label * D.label == 0 ? inv(sqrt(2)) : -inv(sqrt(2))) # ℳᵒᵖ x ℳ x ℳᵒᵖ → ℳᵒᵖ allowed

@test_throws argerr Fsymbol(M, Mop, M, Mop, C, D)
end
end

@testset "Aqua" begin
using Aqua: Aqua
Aqua.test_all(TensorKitSectors)
Expand Down
Loading