diff --git a/Project.toml b/Project.toml index d95c3d7..28af608 100644 --- a/Project.toml +++ b/Project.toml @@ -34,7 +34,7 @@ Random = "1" RationalRoots = "0.1 - 0.2" Scratch = "1" SparseArrayKit = "0.4" -TensorKit = "0.15, 0.16" +TensorKit = "0.16.4" TensorKitSectors = "0.3.5" TensorOperations = "5" Test = "1" diff --git a/test/fusiontrees.jl b/test/fusiontrees.jl index fac33e3..06aa822 100644 --- a/test/fusiontrees.jl +++ b/test/fusiontrees.jl @@ -16,93 +16,21 @@ for I in sectorlist it = @constinferred fusiontrees(out, in, isdual) @constinferred Nothing iterate(it) f = @constinferred first(it) + @testset "Fusion tree $I: printing" begin @test eval(Meta.parse(sprint(show, f))) == f end - @timedtestset "Fusion tree $I: braiding" begin - for in in ⊗(out...) - for f in fusiontrees(out, in, isdual) - for i in 1:(N - 1) - d1 = @constinferred TK.artin_braid(f, i) - @test norm(values(d1)) ≈ 1 - d2 = empty(d1) - for (f1, coeff1) in d1 - for (f2, coeff2) in TK.artin_braid(f1, i; inv = true) - d2[f2] = get(d2, f2, zero(coeff1)) + coeff2 * coeff1 - end - end - @test norm(values(d2)) ≈ 1 - for (f2, coeff2) in d2 - if f2 == f - @test coeff2 ≈ 1 - else - @test isapprox(coeff2, 0; atol = 1000 * eps()) - end - end - end - end - end - f = rand(collect(it)) - d1 = TK.artin_braid(f, 2) - d2 = empty(d1) - for (f1, coeff1) in d1 - for (f2, coeff2) in TK.artin_braid(f1, 3) - d2[f2] = get(d2, f2, zero(coeff1)) + coeff2 * coeff1 - end - end - d1 = d2 - d2 = empty(d1) - for (f1, coeff1) in d1 - for (f2, coeff2) in TK.artin_braid(f1, 3; inv = true) - d2[f2] = get(d2, f2, zero(coeff1)) + coeff2 * coeff1 - end - end - d1 = d2 - d2 = empty(d1) - for (f1, coeff1) in d1 - for (f2, coeff2) in TK.artin_braid(f1, 2; inv = true) - d2[f2] = get(d2, f2, zero(coeff1)) + coeff2 * coeff1 - end - end - d1 = d2 - for (f1, coeff1) in d1 - if f1 == f - @test coeff1 ≈ 1 - else - @test isapprox(coeff1, 0; atol = 1000 * eps()) - end + @timedtestset "Fusion tree $I: braiding" begin + src = FusionTreeBlock{I}((out, ()), (isdual, ())) + for i in 1:(N - 1) + dst, U = @constinferred TK.artin_braid(src, i) + dst2, U2 = @constinferred TK.artin_braid(dst, i; inv = true) + @test src == dst2 + @test _isone(U2 * U) end end - @timedtestset "Fusion tree $I: braiding and permuting" begin - p = tuple(randperm(N)...) - ip = invperm(p) - - levels = ntuple(identity, N) - d = @constinferred TK.braid(f, levels, p) - d2 = Dict{typeof(f), valtype(d)}() - levels2 = p - for (f2, coeff) in d - for (f1, coeff2) in TK.braid(f2, levels2, ip) - d2[f1] = get(d2, f1, zero(coeff)) + coeff2 * coeff - end - end - for (f1, coeff2) in d2 - if f1 == f - @test coeff2 ≈ 1 - else - @test isapprox(coeff2, 0; atol = 1000 * eps()) - end - end - Af = convert(SparseArray, f) - Afp = permutedims(Af, (p..., N + 1)) - Afp2 = zero(Afp) - for (f1, coeff) in d - Afp2 .+= coeff .* convert(SparseArray, f1) - end - @test Afp ≈ Afp2 - end @timedtestset "Fusion tree $I: insertat" begin N = 3 out2 = ntuple(n -> randsector(I), N) @@ -124,37 +52,11 @@ for I in sectorlist @test length(TK.insertat(f1b, 1, f1a)) == 1 @test first(TK.insertat(f1b, 1, f1a)) == (f1 => 1) - levels = ntuple(identity, N) - p = (i, (1:(i - 1))..., ((i + 1):N)...) - gen = Base.Generator(TK.braid(f1, levels, p)) do (t, c) - (t′, c′) = first(TK.insertat(t, 1, f2)) - @test c′ == one(c′) - return t′ => c - end - trees2 = Dict(gen) - trees3 = empty(trees2) - p = (((N + 1):(N + i - 1))..., (1:N)..., ((N + i):(2N - 1))...) - levels = ((i:(N + i - 1))..., (1:(i - 1))..., ((i + N):(2N - 1))...) - for (t, coeff) in trees2 - for (t′, coeff′) in TK.braid(t, levels, p) - trees3[t′] = get(trees3, t′, zero(coeff′)) + coeff * coeff′ - end - end - for (t, coeff) in trees3 - @test isapprox( - get(trees, t, zero(coeff)), coeff; - atol = 1000 * eps(), rtol = 1000 * eps() - ) - end - Af1 = convert(SparseArray, f1) Af2 = convert(SparseArray, f2) Af = TensorOperations.tensorcontract( - 1:(2N), Af1, - [ - 1:(i - 1); -1; - N - 1 .+ ((i + 1):(N + 1)) - ], + 1:(2N), + Af1, [1:(i - 1); -1; N - 1 .+ ((i + 1):(N + 1))], Af2, [i - 1 .+ (1:N); -1] ) Af′ = zero(Af) @@ -164,6 +66,7 @@ for I in sectorlist @test Af ≈ Af′ end end + @timedtestset "Fusion tree $I: merging" begin N = 3 out1 = ntuple(n -> randsector(I), N) @@ -181,33 +84,9 @@ for I in sectorlist ) for c in in1 ⊗ in2 - R = Rsymbol(in1, in2, c) for μ in 1:Nsymbol(in1, in2, c) trees1 = TK.merge(f1, f2, c, μ) - # test merge and braid interplay - trees2 = Dict{keytype(trees1), complex(valtype(trees1))}() - trees3 = Dict{keytype(trees1), complex(valtype(trees1))}() - for ν in 1:Nsymbol(in2, in1, c) - for (t, coeff) in TK.merge(f2, f1, c, ν) - trees2[t] = get(trees2, t, zero(valtype(trees2))) + coeff * R[μ, ν] - end - end - perm = ((N .+ (1:N))..., (1:N)...) - levels = ntuple(identity, 2 * N) - for (t, coeff) in trees1 - for (t′, coeff′) in TK.braid(t, levels, perm) - trees3[t′] = get(trees3, t′, zero(valtype(trees3))) + coeff * coeff′ - end - end - for (t, coeff) in trees3 - @test isapprox( - coeff, get(trees2, t, zero(coeff)); - atol = 1000 * eps(), rtol = 1000 * eps() - ) - end - - # test via conversion Af1 = convert(SparseArray, f1) Af2 = convert(SparseArray, f2) Af0 = convert( @@ -237,59 +116,32 @@ for I in sectorlist numtrees = count(n -> true, fusiontrees((out..., map(dual, out)...))) end incoming = rand(collect(⊗(out...))) - f1 = rand(collect(fusiontrees(out, incoming, ntuple(n -> rand(Bool), N)))) - f2 = rand(collect(fusiontrees(out[randperm(N)], incoming, ntuple(n -> rand(Bool), N)))) + isdual1 = ntuple(n -> rand(Bool), N) + isdual2 = ntuple(n -> rand(Bool), N) + src = FusionTreeBlock{I}((out, out), (isdual1, isdual2)) + A = map(fusiontensor, fusiontrees(src)) - @timedtestset "Double fusion tree $I: repartioning" begin + @timedtestset "Double fusion tree $I: repartitioning" begin for n in 0:(2 * N) - d = @constinferred TK.repartition(f1, f2, $n) - @test dim(incoming) ≈ - sum(abs2(coef) * dim(f1.coupled) for ((f1, f2), coef) in d) - d2 = Dict{typeof((f1, f2)), valtype(d)}() - for ((f1′, f2′), coeff) in d - for ((f1′′, f2′′), coeff2) in TK.repartition(f1′, f2′, N) - d2[(f1′′, f2′′)] = get(d2, (f1′′, f2′′), zero(coeff)) + coeff2 * coeff - end - end - for ((f1′, f2′), coeff2) in d2 - if f1 == f1′ && f2 == f2′ - @test coeff2 ≈ 1 - if !(coeff2 ≈ 1) - @show f1, f2, n - end - else - @test isapprox(coeff2, 0; atol = 1000 * eps()) - end - end - Af1 = convert(SparseArray, f1) - Af2 = permutedims(convert(SparseArray, f2), [N:-1:1; N + 1]) - sz1 = size(Af1) - sz2 = size(Af2) - d1 = prod(sz1[1:(end - 1)]) - d2 = prod(sz2[1:(end - 1)]) - dc = sz1[end] - A = reshape( - reshape(Af1, (d1, dc)) * reshape(Af2, (d2, dc))', - (sz1[1:(end - 1)]..., sz2[1:(end - 1)]...) + dst, U = @constinferred TK.repartition(src, $n) + + dst′, U′ = TK.repartition(dst, N) + @test _isone(U′ * U) + + all_inds = ( + ntuple(identity, N)..., + reverse(ntuple(i -> i + N, N))..., ) - A2 = zero(A) - for ((f1′, f2′), coeff) in d - Af1′ = convert(SparseArray, f1′) - Af2′ = permutedims(convert(SparseArray, f2′), [(2N - n):-1:1; 2N - n + 1]) - sz1′ = size(Af1′) - sz2′ = size(Af2′) - d1′ = prod(sz1′[1:(end - 1)]) - d2′ = prod(sz2′[1:(end - 1)]) - dc′ = sz1′[end] - A2 += coeff * - reshape( - reshape(Af1′, (d1′, dc′)) * reshape(Af2′, (d2′, dc′))', - (sz1′[1:(end - 1)]..., sz2′[1:(end - 1)]...) - ) + p₁ = ntuple(i -> all_inds[i], n) + p₂ = reverse(ntuple(i -> all_inds[i + n], 2N - n)) + A′ = map(Base.Fix2(permutedims, (p₁..., p₂...)), A) + A″ = map(fusiontensor, fusiontrees(dst)) + for (i, Ai) in enumerate(A′) + @test Ai ≈ sum(A″ .* U[:, i]) end - @test A ≈ A2 end end + @timedtestset "Double fusion tree $I: permutation" begin if BraidingStyle(I) isa SymmetricBraiding for n in 0:(2N) @@ -298,55 +150,16 @@ for I in sectorlist ip = invperm(p) ip1, ip2 = ip[1:N], ip[(N + 1):(2N)] - d = @constinferred TensorKit.permute(f1, f2, p1, p2) - @test dim(incoming) ≈ - sum(abs2(coef) * dim(f1.coupled) for ((f1, f2), coef) in d) - d2 = Dict{typeof((f1, f2)), valtype(d)}() - for ((f1′, f2′), coeff) in d - d′ = TensorKit.permute(f1′, f2′, ip1, ip2) - for ((f1′′, f2′′), coeff2) in d′ - d2[(f1′′, f2′′)] = get(d2, (f1′′, f2′′), zero(coeff)) + - coeff2 * coeff - end - end - for ((f1′, f2′), coeff2) in d2 - if f1 == f1′ && f2 == f2′ - @test coeff2 ≈ 1 - if !(coeff2 ≈ 1) - @show f1, f2, p - end - else - @test abs(coeff2) < 1000 * eps() - end - end - Af1 = convert(SparseArray, f1) - Af2 = convert(SparseArray, f2) - sz1 = size(Af1) - sz2 = size(Af2) - d1 = prod(sz1[1:(end - 1)]) - d2 = prod(sz2[1:(end - 1)]) - dc = sz1[end] - A = reshape( - reshape(Af1, (d1, dc)) * reshape(Af2, (d2, dc))', - (sz1[1:(end - 1)]..., sz2[1:(end - 1)]...) - ) - Ap = permutedims(A, (p1..., p2...)) - A2 = zero(Ap) - for ((f1′, f2′), coeff) in d - Af1′ = convert(SparseArray, f1′) - Af2′ = convert(SparseArray, f2′) - sz1′ = size(Af1′) - sz2′ = size(Af2′) - d1′ = prod(sz1′[1:(end - 1)]) - d2′ = prod(sz2′[1:(end - 1)]) - dc′ = sz1′[end] - A2 += coeff * reshape( - reshape(Af1′, (d1′, dc′)) * - reshape(Af2′, (d2′, dc′))', - (sz1′[1:(end - 1)]..., sz2′[1:(end - 1)]...) - ) + dst, U = @constinferred TensorKit.permute(src, (p1, p2)) + + dst′, U′ = @constinferred TensorKit.permute(dst, (ip1, ip2)) + @test _isone(U′ * U) + + A′ = map(Base.Fix2(permutedims, (p1..., p2...)), A) + A″ = map(fusiontensor, fusiontrees(dst)) + for (i, Ai) in enumerate(A′) + @test Ai ≈ sum(A″ .* U[:, i]) end - @test isapprox(Ap, A2; atol = 1000 * eps(), rtol = 1000 * eps()) end end end diff --git a/test/runtests.jl b/test/runtests.jl index a882ca9..f4ee108 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -6,7 +6,8 @@ using TensorKit using SUNRepresentations using Combinatorics using TensorKit -using TensorKit: ProductSector, fusiontensor, pentagon_equation, hexagon_equation +using TensorKit: ProductSector, fusiontensor, pentagon_equation, hexagon_equation, + FusionTreeBlock using TensorOperations using Base.Iterators: take, product using SparseArrayKit: SparseArray @@ -14,6 +15,8 @@ using LinearAlgebra: LinearAlgebra const TK = TensorKit +_isone(x; kwargs...) = isapprox(x, one(x); kwargs...) + Random.seed!(1234) smallset(::Type{I}) where {I <: Sector} = take(values(I), 5)