diff --git a/.github/workflows/FormatCheck.yml b/.github/workflows/FormatCheck.yml index c3ed120..17bac37 100644 --- a/.github/workflows/FormatCheck.yml +++ b/.github/workflows/FormatCheck.yml @@ -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" diff --git a/Project.toml b/Project.toml index 9c9cde1..c1bbc91 100644 --- a/Project.toml +++ b/Project.toml @@ -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" diff --git a/src/TensorKitSectors.jl b/src/TensorKitSectors.jl index 2c80204..fb4a535 100644 --- a/src/TensorKitSectors.jl +++ b/src/TensorKitSectors.jl @@ -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 # --------------- @@ -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 # ---------- diff --git a/src/multifusion.jl b/src/multifusion.jl new file mode 100644 index 0000000..6939aae --- /dev/null +++ b/src/multifusion.jl @@ -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) + +โŠ—(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 + +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 + +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 diff --git a/test/multifusion.jl b/test/multifusion.jl new file mode 100644 index 0000000..89ecd84 --- /dev/null +++ b/test/multifusion.jl @@ -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 diff --git a/test/runtests.jl b/test/runtests.jl index 736ed15..5d256a0 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -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)