diff --git a/Project.toml b/Project.toml index 65904cb..beb6f30 100644 --- a/Project.toml +++ b/Project.toml @@ -1,13 +1,16 @@ name = "MultiTensorKit" uuid = "f0555a46-f681-4ef1-bed5-d64870568636" -authors = ["Boris De Vos, Laurens Lootens and Lukas Devos"] version = "0.1.0" +authors = ["Boris De Vos, Laurens Lootens and Lukas Devos"] [deps] DelimitedFiles = "8bb1440f-4735-579b-a4ab-409b98df4dab" Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" TensorKitSectors = "13a9c161-d5da-41f0-bcbd-e1a08ae0647f" +[weakdeps] +TensorKit = "07d1fe3e-3e46-537d-9eac-e9e13d0d4cec" + [compat] Aqua = "0.8.9" DelimitedFiles = "1.9" @@ -15,7 +18,7 @@ LinearAlgebra = "1" Pkg = "1" Random = "1" SafeTestsets = "0.1" -TensorKit = "0.16, 0.17" +TensorKit = "0.17.2" TensorKitSectors = "0.3.7" TensorOperations = "5" Test = "1.10" @@ -27,10 +30,9 @@ Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" SafeTestsets = "1bc83da4-3b8d-516f-aca4-4fe02f6d838f" -TensorKit = "07d1fe3e-3e46-537d-9eac-e9e13d0d4cec" TensorOperations = "6aa20fa7-93e2-5fca-9bc0-fbd0db3c71a2" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" TestExtras = "5ed8adda-3752-4e41-b88a-e8b09835ee3a" [targets] -test = ["Aqua", "LinearAlgebra", "Random", "SafeTestsets", "TensorKit", "TensorOperations", "Test", "TestExtras"] \ No newline at end of file +test = ["Aqua", "LinearAlgebra", "Random", "SafeTestsets", "TensorOperations", "Test", "TestExtras"] diff --git a/README.md b/README.md index 91ca994..405df25 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,15 @@ # MultiTensorKit.jl -[TensorKit.jl](https://github.com/jutho/TensorKit.jl) extension for multi-fusion categories. +[TensorKit.jl](https://github.com/QuantumKitHub/TensorKit.jl) extension for multi-fusion categories. | **Documentation** | **Digital Object Identifier** | |:-----------------:|:-----------------------------:| | [![][docs-stable-img]][docs-stable-url] [![][docs-dev-img]][docs-dev-url] | [![DOI][doi-img]][doi-url] | -| **Build Status** | **PkgEval** | **Coverage** | -|:----------------:|:------------:|:------------:| -| [![CI][ci-img]][ci-url] | [![PkgEval][pkgeval-img]][pkgeval-url] | [![Codecov][codecov-img]][codecov-url] | +| **Build Status** | **PkgEval** | **Coverage** | **Quality assurance** | +|:----------------:|:------------:|:------------:|:---------------------:| +| [![CI][ci-img]][ci-url] | [![PkgEval][pkgeval-img]][pkgeval-url] | [![Codecov][codecov-img]][codecov-url] | [![Aqua QA][aqua-img]][aqua-url] | [docs-stable-img]: https://img.shields.io/badge/docs-stable-blue.svg [docs-stable-url]: https://QuantumKitHub.github.io/MultiTensorKit.jl/stable @@ -26,6 +26,8 @@ [ci-img]: https://github.com/QuantumKitHub/MultiTensorKit.jl/actions/workflows/Tests.yml/badge.svg [ci-url]: https://github.com/QuantumKitHub/MultiTensorKit.jl/actions/workflows/Tests.yml -[pkgeval-img]: https://JuliaCI.github.io/NanosoldierReports/pkgeval_badges/T/MPSKit.svg -[pkgeval-url]: https://JuliaCI.github.io/NanosoldierReports/pkgeval_badges/T/MPSKit.html +[pkgeval-img]: https://JuliaCI.github.io/NanosoldierReports/pkgeval_badges/M/MultiTensorKit.svg +[pkgeval-url]: https://JuliaCI.github.io/NanosoldierReports/pkgeval_badges/M/MultiTensorKit.html +[aqua-img]: https://raw.githubusercontent.com/JuliaTesting/Aqua.jl/master/badge.svg +[aqua-url]: https://github.com/JuliaTesting/Aqua.jl \ No newline at end of file diff --git a/src/bimodulesector.jl b/src/bimodulesector.jl index 4ea5896..b6c8551 100644 --- a/src/bimodulesector.jl +++ b/src/bimodulesector.jl @@ -1,4 +1,4 @@ -const ImplementedBimoduleSectors = [:A4] +const ImplementedBimoduleSectors = (:A4,) struct BimoduleSector{Name} <: Sector i::Int @@ -7,10 +7,11 @@ struct BimoduleSector{Name} <: Sector function BimoduleSector{Name}(i::Int, j::Int, label::Int) where {Name} Name ∈ ImplementedBimoduleSectors || throw(ArgumentError("BimoduleSector $Name not implemented")) - i <= size(BimoduleSector{Name}) && j <= size(BimoduleSector{Name}) || - throw(DomainError("object outside the matrix $Name")) - return label <= _numlabels(BimoduleSector{Name}, i, j) ? new{Name}(i, j, label) : - throw(DomainError("label outside category $Name($i, $j)")) + r = size(BimoduleSector{Name}) + (1 <= i <= r && 1 <= j <= r) || + throw(DomainError((i, j), "subcategory outside the $r×$r matrix $Name")) + return 1 <= label <= _numlabels(BimoduleSector{Name}, i, j) ? new{Name}(i, j, label) : + throw(DomainError(label, "label outside category $Name($i, $j)")) end end @@ -18,8 +19,6 @@ BimoduleSector{Name}(data::NTuple{3, Int}) where {Name} = BimoduleSector{Name}(d BimoduleSectorName(::Type{BimoduleSector{Name}}) where {Name} = Name const A4Object = BimoduleSector{:A4} -Base.convert(::Type{<:BimoduleSector{Name}}, labels::NTuple{3, Int}) where {Name} = BimoduleSector{Name}(labels...) - function Base.show(io::IO, a::BimoduleSector{Name}) where {Name} if get(io, :typeinfo, nothing) === typeof(a) print(io, (a.i, a.j, a.label)) @@ -39,27 +38,24 @@ function Base.convert(::Type{BimoduleSector{Name}}, d::NTuple{3, Int}) where {Na return BimoduleSector{Name}(d...) end -Base.size(::Type{A4Object}) = 7 +Base.size(I::Type{<:BimoduleSector}) = size(_get_dual_cache(I)[3], 1) Base.IteratorSize(::Type{<:SectorValues{<:BimoduleSector}}) = Base.SizeUnknown() -function Base.iterate(iter::SectorValues{<:BimoduleSector}, (I, label) = (1, 1)) +function Base.iterate(iter::SectorValues{<:BimoduleSector}, (n, label) = (1, 1)) A = eltype(iter) s = size(A) - I > s * s && return nothing - i, j = CartesianIndices((s, s))[I].I + n > s * s && return nothing + i, j = fldmod1(n, s) maxlabel = _numlabels(A, i, j) return if label > maxlabel - iterate(iter, (I + 1, 1)) + iterate(iter, (n + 1, 1)) else - A(i, j, label), (I, label + 1) + A(i, j, label), (n, label + 1) end end -function Base.length(::SectorValues{I}) where {I <: BimoduleSector} - s = size(I) - return sum(_numlabels(I, i, j) for i in 1:s, j in 1:s) -end +Base.length(::SectorValues{I}) where {I <: BimoduleSector} = sum(_get_dual_cache(I)[3]) TensorKitSectors.FusionStyle(::Type{A4Object}) = GenericFusion() TensorKitSectors.BraidingStyle(::Type{<:BimoduleSector}) = NoBraiding() @@ -76,8 +72,9 @@ function TensorKitSectors.:⊗(a::I, b::I) where {I <: BimoduleSector} end function _numlabels(::Type{T}, i, j) where {T <: BimoduleSector} - return length(_get_dual_cache(T)[2][i, j]) + return _get_dual_cache(T)[3][i, j] end +_unitlabel(::Type{I}, i::Int) where {I <: BimoduleSector} = _get_dual_cache(I)[1][i] # User-friendly functions # ------------------- @@ -92,10 +89,12 @@ function extract_Nsymbol(::Type{I}) where {I <: BimoduleSector} filename = joinpath(artifact_path, name, "Nsymbol.txt") isfile(filename) || throw(LoadError(filename, 0, "Nsymbol file not found for $name")) Narray = readdlm(filename) # matrix with 7 columns + size(Narray, 2) == 7 || + throw(ArgumentError("Nsymbol file for $name has wrong number of columns: $(size(Narray, 2)) != 7")) data_dict = Dict{NTuple{3, Int}, Dict{NTuple{3, Int}, Int}}() for row in eachrow(Narray) - i, j, k, a, b, c, N = Int.(@view(row[1:size(I)])) + i, j, k, a, b, c, N = Int.(@view(row[1:7])) colordict = get!(data_dict, (i, j, k), Dict{NTuple{3, Int}, Int}()) push!(colordict, (a, b, c) => N) end @@ -120,7 +119,7 @@ function TensorKitSectors.Nsymbol(a::I, b::I, c::I) where {I <: BimoduleSector} return get(_get_Ncache(I)[i, j, k], (a.label, b.label, c.label), 0) end -const Dualcache = IdDict{Type{<:BimoduleSector}, Tuple{Vector{Int64}, Matrix{Vector{Int64}}}}() +const Dualcache = IdDict{Type{<:BimoduleSector}, Tuple{Vector{Int64}, Matrix{Vector{Int64}}, Matrix{Int64}}}() function _get_dual_cache(::Type{T}) where {T <: BimoduleSector} global Dualcache @@ -131,68 +130,60 @@ end function extract_dual(::Type{I}) where {I <: BimoduleSector} N = _get_Ncache(I) - ncats = size(I) - Is = zeros(Int, ncats) + ncats = maximum(maximum, keys(N)) + nobj = [maximum(first, keys(N[i, j, j])) for i in 1:ncats, j in 1:ncats] - map(1:ncats) do i + Is = map(1:ncats) do i Niii = N[i, i, i] - nobji = maximum(first, keys(N[i, i, i])) # want to return a leftunit and rightunit for each entry in multifusion cat # leftunit/rightunit needs to at least be the unit object within a fusion cat - Is[i] = findfirst(1:nobji) do a + u = findfirst(1:nobj[i, i]) do a get(Niii, (a, a, a), 0) == 1 || return false # I x I -> I - for othera in 1:nobji - get(Niii, (othera, a, othera), 0) == 1 || return false # a x I -> a - get(Niii, (a, othera, othera), 0) == 1 || return false # I x a -> a - end - # check leftunit - map(1:ncats) do j - nobjj = maximum(first, keys(N[j, j, j])) - for b in 1:nobjj - get(N[i, j, j], (a, b, b), 0) == 1 || return false # I = leftunit(b) - end + # I = leftunit(b) for every b in C_ij, I x b -> b in C_ii x C_ij -> C_ij + for j in 1:ncats, b in 1:nobj[i, j] + get(N[i, i, j], (a, b, b), 0) == 1 || return false end - # check rightunit - map(1:ncats) do k - nobjk = maximum(first, keys(N[k, k, k])) - for c in 1:nobjk - get(N[k, i, k], (c, a, c), 0) == 1 || return false # I = rightunit(c) - end + # I = rightunit(c) for every c in C_ki, c x I -> c in C_ki x C_ii -> C_ki + for k in 1:ncats, c in 1:nobj[k, i] + get(N[k, i, i], (c, a, c), 0) == 1 || return false end return true end + isnothing(u) && throw(ArgumentError("no unit object found in subcategory ($i, $i) of $I")) + return u end allduals = Matrix{Vector{Int}}(undef, ncats, ncats) # ncats square matrix of vectors - for i in 1:ncats - nobji = maximum(first, keys(N[i, i, i])) - for j in 1:ncats - allduals[i, j] = Int[] - - nobjj = maximum(first, keys(N[j, j, j])) - # the nested vectors contain the duals of the objects in 𝒞_ij, which are in C_ji - Niji = N[i, j, i] # 𝒞_ij x 𝒞_ji -> C_ii - Njij = N[j, i, j] # 𝒞_ji x 𝒞_ij -> C_jj - for i_ob in 1:nobji, j_ob in 1:nobjj - get(Niji, (i_ob, j_ob, Is[i]), 0) == 1 || continue # leftunit(c_ij) ∈ c_ij x c_ji - get(Njij, (j_ob, i_ob, Is[j]), 0) == 1 || continue # rightunit(c_ij) ∈ c_ji x c_ij - push!(allduals[i, j], j_ob) + for i in 1:ncats, j in 1:ncats + Niji = N[i, j, i] # 𝒞_ij x 𝒞_ji -> 𝒞_ii + Njij = N[j, i, j] # 𝒞_ji x 𝒞_ij -> 𝒞_jj + allduals[i, j] = map(1:nobj[i, j]) do m + mbar = findfirst(1:nobj[j, i]) do n + get(Niji, (m, n, Is[i]), 0) == 1 && # leftunit(m) in m x mbar + get(Njij, (n, m, Is[j]), 0) == 1 # rightunit(m) in mbar x m end + isnothing(mbar) && throw(ArgumentError("no dual found for $I($i, $j, $m)")) + return mbar end end - return Is, allduals + return Is, allduals, nobj end function TensorKitSectors.unit(a::BimoduleSector) a.i == a.j || throw(DomainError("unit of module category ($(a.i), $(a.j)) of $(typeof(a)) is ill-defined")) - return typeof(a)(a.i, a.i, _get_dual_cache(typeof(a))[1][a.i]) + return typeof(a)(a.i, a.i, _unitlabel(typeof(a), a.i)) end +TensorKitSectors.UnitStyle(::Type{<:BimoduleSector}) = GenericUnit() + function TensorKitSectors.allunits(::Type{I}) where {I <: BimoduleSector} - s = size(I) - return I[I(i, i, _get_dual_cache(I)[1][i]) for i in 1:s] + return I[I(i, i, u) for (i, u) in enumerate(_get_dual_cache(I)[1])] +end + +function TensorKitSectors.isunit(a::BimoduleSector) + return a.i == a.j && a.label == _unitlabel(typeof(a), a.i) end function TensorKitSectors.unit(::Type{<:BimoduleSector}) @@ -200,11 +191,11 @@ function TensorKitSectors.unit(::Type{<:BimoduleSector}) end function TensorKitSectors.leftunit(a::BimoduleSector) - return typeof(a)(a.i, a.i, _get_dual_cache(typeof(a))[1][a.i]) + return typeof(a)(a.i, a.i, _unitlabel(typeof(a), a.i)) end function TensorKitSectors.rightunit(a::BimoduleSector) - return typeof(a)(a.j, a.j, _get_dual_cache(typeof(a))[1][a.j]) + return typeof(a)(a.j, a.j, _unitlabel(typeof(a), a.j)) end function TensorKitSectors.dual(a::BimoduleSector) @@ -218,6 +209,7 @@ function extract_Fsymbol(::Type{I}) where {I <: BimoduleSector} @assert isfile(filename) "cannot find $filename" Farray = readdlm(filename) + size(Farray, 2) == 16 || throw(ArgumentError("Fsymbol file for $name has wrong number of columns: $(size(Farray, 2)) != 16")) for ((i, j, k, l), colordict) in convert_Fs(Farray) result[(i, j, k, l)] = Dict{NTuple{6, Int}, Array{ComplexF64, 4}}() for ((a, b, c, d, e, f), Fvals) in colordict @@ -272,11 +264,14 @@ function TensorKitSectors.Fsymbol(a::I, b::I, c::I, d::I, e::I, f::I) where {I < Nbcf = Nsymbol(b, c, f) Nafd = Nsymbol(a, f, d) - zero_array = zeros(sectorscalartype(I), Nabe, Necd, Nbcf, Nafd) + T = sectorscalartype(I) Nabe > 0 && Necd > 0 && Nbcf > 0 && Nafd > 0 || - return zero_array + return zeros(T, Nabe, Necd, Nbcf, Nafd) i, j, k, l = a.i, a.j, b.j, c.j colordict = _get_Fcache(I)[i, j, k, l] - return get!(colordict, (a.label, b.label, c.label, d.label, e.label, f.label), zero_array) + return get!( + () -> zeros(T, Nabe, Necd, Nbcf, Nafd), + colordict, (a.label, b.label, c.label, d.label, e.label, f.label) + ) end diff --git a/test/test_A4.jl b/test/test_A4.jl index 59fa329..fd71523 100644 --- a/test/test_A4.jl +++ b/test/test_A4.jl @@ -9,6 +9,10 @@ using .SectorTestSuite using MultiTensorKit using Test, TestExtras +using Random + +Random.seed!(1234) +rng = Random.Xoshiro(1234) const MTK = MultiTensorKit @@ -23,21 +27,30 @@ println("----------------------") @time SectorTestSuite.test_sector(I) end +@testset "$Istr construction and ordering" begin + @test_throws DomainError I(1, 1, 0) + @test_throws DomainError I(1, 1, -1) + @test_throws DomainError I(0, 1, 1) + @test_throws DomainError I(r + 1, 1, 1) + @test_throws DomainError I(1, 1, MTK._numlabels(I, 1, 1) + 1) + vals = collect(values(I)) + @test issorted(vals) + @test length(vals) == length(values(I)) +end + @testset "$Istr ($i, $j) basic properties" for i in 1:r, j in 1:r - Cii_obs = I.(i, i, MTK._get_dual_cache(I)[2][i, i]) - Cij_obs = I.(i, j, MTK._get_dual_cache(I)[2][i, j]) - Cji_obs = I.(j, i, MTK._get_dual_cache(I)[2][j, i]) - Cjj_obs = I.(j, j, MTK._get_dual_cache(I)[2][j, j]) - c, m, mop, d = rand(Cii_obs), rand(Cij_obs), rand(Cji_obs), rand(Cjj_obs) + Cii_obs = I.(i, i, 1:MTK._numlabels(I, i, i)) + Cij_obs = I.(i, j, 1:MTK._numlabels(I, i, j)) + Cji_obs = I.(j, i, 1:MTK._numlabels(I, j, i)) + Cjj_obs = I.(j, j, 1:MTK._numlabels(I, j, j)) + c, m, mop, d = rand(rng, Cii_obs), rand(rng, Cij_obs), rand(rng, Cji_obs), rand(rng, Cjj_obs) if i == j @testset "Basic fusion properties" begin @test isunit(@testinferred(unit(c))) - u = I.(i, i, MTK._get_dual_cache(I)[1][i]) + u = only(filter(isunit, Cii_obs)) @test u == @testinferred(leftunit(u)) == @testinferred(rightunit(u)) == @testinferred(unit(u)) - @test isunit(@testinferred(unit(c))) - @test dual(c) == I.(i, i, MTK._get_dual_cache(I)[2][i, i][c.label]) end else @testset "Basic module properties" begin @@ -48,7 +61,6 @@ end @test unit(d) == rightunit(m) == leftunit(mop) @test_throws DomainError unit(m) @test_throws DomainError unit(mop) - @test dual(m) == I.(j, i, MTK._get_dual_cache(I)[2][i, j][m.label]) end @testset "$Istr Fusion rules" begin @@ -56,7 +68,7 @@ end # 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..., rand([c, m, mop, d])) + @test_throws argerr Nsymbol(obs..., rand(rng, [c, m, mop, d])) end # allowed fusions @@ -73,65 +85,28 @@ end end end +@testset "$Istr units and duals of all objects" begin + for a in values(I) + ā = dual(a) + @test dual(ā) == a + @test Nsymbol(a, ā, leftunit(a)) == 1 + @test Nsymbol(ā, a, rightunit(a)) == 1 + end +end + println("-----------------------------") println("| F-symbol data tests |") println("-----------------------------") # explicitly test everything related to F-symbols # other option is to edit smallset to sample more -for i in 1:r, j in 1:r - @testset "Unitarity of $Istr F-move ($i, $j)" begin - if i == j - @testset "Unitarity of fusion F-move ($i, $j)" begin - fusion_objects = I.(i, i, MTK._get_dual_cache(I)[2][i, i]) - for a in fusion_objects, b in fusion_objects, c in fusion_objects - @test SectorTestSuite.F_unitarity_test(a, b, c) - end - end - end - - i != j || continue # do this part only when off-diagonal - mod_objects = I.(i, j, MTK._get_dual_cache(I)[2][i, j]) - left_fusion_objects = I.(i, i, MTK._get_dual_cache(I)[2][i, i]) - right_fusion_objects = I.(j, j, MTK._get_dual_cache(I)[2][j, j]) - - # C x C x M -> M or D x D x Mop -> Mop - @testset "Unitarity of left module F-move ($i, $j)" begin - for a in left_fusion_objects, b in left_fusion_objects, A in mod_objects - @test SectorTestSuite.F_unitarity_test(a, b, A) - end - end - - # M x D x D -> M or Mop x C x C -> Mop - @testset "Unitarity of right module F-move ($i, $j)" begin - for A in mod_objects, b in right_fusion_objects, c in right_fusion_objects - @test SectorTestSuite.F_unitarity_test(A, b, c) - end - end - - # C x M x D -> M or D x Mop x C -> Mop - @testset "Unitarity of bimodule F-move ($i, $j)" begin - for a in left_fusion_objects, A in mod_objects, α in right_fusion_objects - @test SectorTestSuite.F_unitarity_test(a, A, α) - end - end - - @testset "Unitarity of mixed module F-move ($i, $j) and opposite ($j, $i)" begin - modop_objects = I.(j, i, MTK._get_dual_cache(I)[2][j, i]) - - # C x M x Mop -> C or D x Mop x M -> D - # M x Mop x C -> C or Mop x M x D -> D - # Mop x C x M -> D or M x D x Mop -> C - for a in left_fusion_objects, A in mod_objects, Aop in modop_objects - @test SectorTestSuite.F_unitarity_test(a, A, Aop) - @test SectorTestSuite.F_unitarity_test(A, Aop, a) - @test SectorTestSuite.F_unitarity_test(Aop, a, A) - end - - # M x Mop x M -> M or Mop x M x Mop -> Mop - for A in mod_objects, Aop in modop_objects - @test SectorTestSuite.F_unitarity_test(A, Aop, A) - end +@testset "Unitarity of $Istr F-move" begin + objs = collect(values(I)) + for a in objs, b in objs + a.j == b.i || continue + for c in objs + b.j == c.i || continue + @test SectorTestSuite.F_unitarity_test(a, b, c; atol = 1.0e-12, rtol = 1.0e-12) end end end @@ -159,5 +134,3 @@ end end end end - -# include("TK_compat.jl") \ No newline at end of file