Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
f4c3a20
remove TK from project.toml
borisdevos Oct 30, 2026
89a7286
add TK as weakdep
borisdevos Oct 30, 2026
817fac3
fix bounds in constructor
borisdevos Oct 30, 2026
b00fcfa
clearer domain errors
borisdevos Oct 30, 2026
d9c2354
iterate over sector values row-major to match isless
borisdevos Oct 30, 2026
3fcb4ac
proper Nsymbol column check
borisdevos Oct 30, 2026
36e3786
correctly search for units, allow to error on time
borisdevos Oct 30, 2026
b57c798
fix ranges of dual calculations
borisdevos Oct 30, 2026
10fbfed
add unitstyle, make allunits allocate less and have isunit not build …
borisdevos Oct 30, 2026
d7a6c69
return number of objects matrix in dual cache, allowing easier counti…
borisdevos Oct 30, 2026
b348ba1
remove duplicate convert
borisdevos Oct 30, 2026
8743fe5
fix not knowing size at compile time
borisdevos Oct 30, 2026
d09aaa2
fix seed for test reproducibility, add constructor test
borisdevos Oct 30, 2026
b44ea5d
be exhaustive with unitarity checks
borisdevos Oct 30, 2026
52ada8a
test duals more rigorously
borisdevos Oct 30, 2026
4b7b94e
clean up readme where possible
borisdevos Oct 30, 2026
2fa08af
remove allocations in Fsymbol call
borisdevos Oct 30, 2026
939ac59
final test cleanup
borisdevos Oct 30, 2026
83a2920
oopsie
borisdevos Oct 30, 2026
0efa99e
make random sampling random at least
borisdevos Oct 30, 2026
866a629
change pkgeval link (doesn't matter for now)
borisdevos Oct 30, 2026
49f46ba
column check for Fsymbol.txt as well
borisdevos Oct 30, 2026
408eb9a
tiny unit label refactor
borisdevos Oct 30, 2026
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
10 changes: 6 additions & 4 deletions Project.toml
Original file line number Diff line number Diff line change
@@ -1,21 +1,24 @@
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"

Comment on lines +11 to +13

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this a weakdep? That category is typically for package extensions, which I don't think we are defining here?

[compat]
Aqua = "0.8.9"
DelimitedFiles = "1.9"
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"
Expand All @@ -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"]
test = ["Aqua", "LinearAlgebra", "Random", "SafeTestsets", "TensorOperations", "Test", "TestExtras"]
14 changes: 8 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
121 changes: 58 additions & 63 deletions src/bimodulesector.jl
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const ImplementedBimoduleSectors = [:A4]
const ImplementedBimoduleSectors = (:A4,)

struct BimoduleSector{Name} <: Sector
i::Int
Expand All @@ -7,19 +7,18 @@ 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

BimoduleSector{Name}(data::NTuple{3, Int}) where {Name} = BimoduleSector{Name}(data...)
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))
Expand All @@ -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)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking at this I no longer really remember why we have this definition and it looks a bit odd to me, typically size(x) returns a tuple so maybe this should be (7,) instead, but do you remember what we use this for?


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()
Expand All @@ -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
# -------------------
Expand All @@ -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]))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess this answers my previous question, does that mean we can remove the Base.size definition?

colordict = get!(data_dict, (i, j, k), Dict{NTuple{3, Int}, Int}())
push!(colordict, (a, b, c) => N)
end
Expand All @@ -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
Expand All @@ -131,80 +130,72 @@ 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})
throw(ArgumentError("unit of Type BimoduleSector doesn't exist"))
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)
Expand All @@ -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
Expand Down Expand Up @@ -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
Loading
Loading