Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
43 changes: 6 additions & 37 deletions .github/workflows/FormatCheck.yml
Original file line number Diff line number Diff line change
@@ -1,47 +1,16 @@
name: FormatCheck
name: "Format Check"

on:
push:
branches:
- 'main'
- 'master'
- 'release-'
tags: '*'
pull_request:

jobs:
build:
runs-on: ${{ matrix.os }}
strategy:
matrix:
version:
- '1' # automatically expands to the latest stable 1.x release of Julia
os:
- ubuntu-latest
arch:
- x64
steps:
- uses: julia-actions/setup-julia@latest
with:
version: ${{ matrix.version }}
arch: ${{ matrix.arch }}

- uses: actions/checkout@v4
- name: Install JuliaFormatter and format
# This will use the latest version by default but you can set the version like so:
#
# julia -e 'using Pkg; Pkg.add(PackageSpec(name="JuliaFormatter", version="0.13.0"))'
run: |
julia -e 'using Pkg; Pkg.add(PackageSpec(name="JuliaFormatter"))'
julia -e 'using JuliaFormatter; format(".", verbose=true)'
- name: Format check
run: |
julia -e '
out = Cmd(`git diff --name-only`) |> read |> String
if out == ""
exit(0)
else
@error "Some files have not been formatted !!!"
write(stdout, out)
exit(1)
end'
format-check:
name: "Format Check"
uses: "QuantumKitHub/.github/.github/workflows/formatcheck.yml@main"
with:
juliaformatter-version: "2"
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "TensorKitSectors"
uuid = "13a9c161-d5da-41f0-bcbd-e1a08ae0647f"
authors = ["Lukas Devos", "Jutho Haegeman"]
version = "0.1.5"
version = "0.1.6"

[deps]
HalfIntegers = "f0d1745a-41c9-11e9-1dd9-e5d34d218721"
Expand Down
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

# 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
91 changes: 91 additions & 0 deletions src/multifusion.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
# 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
#------------------------------------------------------------------------------#

# 𝒞 ℳ
# ℳᵒᵖ 𝒟
struct IsingBimod <: Sector
row::Int
col::Int
label::Int
function IsingBimod(row::Int, col::Int, label::Int)
1 <= row <= 2 && 1 <= col <= 2 ||
throw(DomainError(lazy"Invalid subcategory ($row, $col)"))
0 <= label <= (row == col) ||
throw(ArgumentError(lazy"Invalid label $label for IsingBimod subcategory ($row, $col)"))
return new(row, col, label)
end
end

const all_isingbimod_objects = (IsingBimod(1, 1, 0), IsingBimod(1, 1, 1),
IsingBimod(2, 1, 0), IsingBimod(1, 2, 0),
IsingBimod(2, 2, 0), IsingBimod(2, 2, 1))

Base.IteratorSize(::Type{SectorValues{IsingBimod}}) = Base.SizeUnknown()
Base.iterate(::SectorValues{IsingBimod}, i=1) = iterate(all_isingbimod_objects, i)
Base.length(::SectorValues{IsingBimod}) = length(all_isingbimod_objects)

Check warning on line 27 in src/multifusion.jl

View check run for this annotation

Codecov / codecov/patch

src/multifusion.jl#L27

Added line #L27 was not covered by tests

⊗(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()

Check warning on line 37 in src/multifusion.jl

View check run for this annotation

Codecov / codecov/patch

src/multifusion.jl#L37

Added line #L37 was not covered by tests
Base.eltype(::Type{IsingBimodIterator}) = IsingBimod

function Base.iterate(iter::IsingBimodIterator, state=0)
a, b = iter.a, iter.b
a.col == b.row || return nothing

_state = (a.row == b.col == a.col) ? mod(a.label + b.label, 2) : state
return state < (1 + (a.row == b.col && a.row != a.col)) ?
(IsingBimod(a.row, b.col, _state), state + 1) : nothing
end

function Base.convert(::Type{IsingAnyon}, a::IsingBimod) # identify RepZ2 ⊕ RepZ2 ≅ Ising
(a.row != a.col) && 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 55 in src/multifusion.jl

View check run for this annotation

Codecov / codecov/patch

src/multifusion.jl#L55

Added line #L55 was not covered by tests

function Nsymbol(a::IsingBimod, b::IsingBimod, c::IsingBimod)
if (a.row != c.row) || (a.col != b.row) || (b.col != c.col)
throw(ArgumentError("invalid fusion channel"))
end
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
return Fsymbol(convert(IsingAnyon, a), convert(IsingAnyon, b), convert(IsingAnyon, c),
convert(IsingAnyon, d), convert(IsingAnyon, e), convert(IsingAnyon, f))
end

# ℳ ↔ ℳop when conjugating elements within these
Base.conj(a::IsingBimod) = IsingBimod(a.col, a.row, a.label)

rightone(a::IsingBimod) = IsingBimod(a.col, a.col, 0)
leftone(a::IsingBimod) = IsingBimod(a.row, a.row, 0)

function Base.one(a::IsingBimod)
a.row == a.col ||
throw(DomainError("unit of module category ($(a.row), $(a.col)) doesn't exist"))
return IsingBimod(a.row, a.col, 0)
end

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

function Base.isless(a::IsingBimod, b::IsingBimod)
return isless((a.col, a.row, a.label), (b.col, b.row, b.label))
end

function Base.hash(a::IsingBimod, h::UInt)
return hash(a.label, hash(a.row, hash(a.col, h)))
end
124 changes: 124 additions & 0 deletions test/multifusion.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
I = IsingBimod
Istr = TensorKitSectors.type_repr(I)
@testset "$Istr sector" begin
@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(1, 2, 0)
Mop = IsingBimod(2, 1, 0)
C0 = IsingBimod(1, 1, 0)
C1 = IsingBimod(1, 1, 1)
D0 = IsingBimod(2, 2, 0)
D1 = IsingBimod(2, 2, 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 "$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 collect(values(I))
@test (@constinferred values(I)[findindex(values(I), s)]) == s
end
end

@testset "$Istr: Printing and errors" begin
@test eval(Meta.parse(sprint(show, C))) == C
@test eval(Meta.parse(sprint(show, M))) == M
@test eval(Meta.parse(sprint(show, Mop))) == Mop
@test eval(Meta.parse(sprint(show, D))) == D
@test_throws DomainError one(M)
@test_throws DomainError one(Mop)
end

@testset "$Istr Fusion rules and F-symbols" begin
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

@testset "$Istr: Unitarity of F-move" begin
objects = collect(values(I))
for a in objects, b in objects, c in objects
for d in ⊗(a, b, c)
es = collect(intersect(⊗(a, b), map(dual, ⊗(c, dual(d)))))
fs = collect(intersect(⊗(b, c), map(dual, ⊗(dual(d), a))))
@test length(es) == length(fs)
F = [Fsymbol(a, b, c, d, e, f) for e in es, f in fs]
@test isapprox(F' * F, one(F); atol=1e-12, rtol=1e-12)
end
end
end

@testset "$Istr: Pentagon equation" begin
objects = collect(values(I))
for a in objects, b in objects, c in objects, d in objects
# compatibility checks built in Fsymbol
@test pentagon_equation(a, b, c, d; atol=1e-12, rtol=1e-12)
end
end
end
2 changes: 2 additions & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ end
@test size(Rsymbol(a, b, c)) == (0, 0)
end

include("multifusion.jl")

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