From 6b4eb48607dc363978fcddb3db9c0aa7c200406c Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Thu, 25 Sep 2025 14:37:10 +0200 Subject: [PATCH 01/95] change TensorKitSectors compat --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 7e30fd805..865f4dd0e 100644 --- a/Project.toml +++ b/Project.toml @@ -33,7 +33,7 @@ LinearAlgebra = "1" PackageExtensionCompat = "1" Random = "1" Strided = "2" -TensorKitSectors = "0.1.4, 0.2" +TensorKitSectors = "0.3" TensorOperations = "5.1" Test = "1" TestExtras = "0.2,0.3" From 8d2a3566ce07dc37268ed552339ed925d77f9895 Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Thu, 25 Sep 2025 14:43:28 +0200 Subject: [PATCH 02/95] add `unitspace` --- docs/src/lib/spaces.md | 2 +- docs/src/man/spaces.md | 17 ++++++++--------- src/spaces/cartesianspace.jl | 2 +- src/spaces/complexspace.jl | 2 +- src/spaces/generalspace.jl | 2 +- src/spaces/gradedspace.jl | 4 ++-- src/spaces/productspace.jl | 11 ++++++----- src/spaces/vectorspaces.jl | 10 ++++++---- 8 files changed, 26 insertions(+), 24 deletions(-) diff --git a/docs/src/lib/spaces.md b/docs/src/lib/spaces.md index 83350156d..be433493b 100644 --- a/docs/src/lib/spaces.md +++ b/docs/src/lib/spaces.md @@ -91,7 +91,7 @@ conj flip ⊕ zero(::ElementarySpace) -oneunit +unitspace supremum infimum ``` diff --git a/docs/src/man/spaces.md b/docs/src/man/spaces.md index 503730bfe..ff2d5990f 100644 --- a/docs/src/man/spaces.md +++ b/docs/src/man/spaces.md @@ -316,19 +316,18 @@ flip(ℂ^4) == ℂ^4 ``` We also define the direct sum `V1` and `V2` as `V1 ⊕ V2`, where `⊕` is obtained by typing -`\oplus`+TAB. This is possible only if `isdual(V1) == isdual(V2)`. With a little pun on -Julia Base, `oneunit` applied to an elementary space (in the value or type domain) returns -the one-dimensional space, which is isomorphic to the scalar field of the space itself. Some -examples illustrate this better +`\oplus`+TAB. This is possible only if `isdual(V1) == isdual(V2)`. `unitspace` applied to an elementary space +(in the value or type domain) returns the one-dimensional space, which is isomorphic to the +scalar field of the space itself. Some examples illustrate this better. ```@repl tensorkit ℝ^5 ⊕ ℝ^3 ℂ^5 ⊕ ℂ^3 ℂ^5 ⊕ (ℂ^3)' -oneunit(ℝ^3) -ℂ^5 ⊕ oneunit(ComplexSpace) -oneunit((ℂ^3)') -(ℂ^5) ⊕ oneunit((ℂ^5)) -(ℂ^5)' ⊕ oneunit((ℂ^5)') +unitspace(ℝ^3) +ℂ^5 ⊕ unitspace(ComplexSpace) +unitspace((ℂ^3)') +(ℂ^5) ⊕ unitspace((ℂ^5)) +(ℂ^5)' ⊕ unitspace((ℂ^5)') ``` Finally, while spaces have a partial order, there is no unique infimum or supremum of a two diff --git a/src/spaces/cartesianspace.jl b/src/spaces/cartesianspace.jl index f29ed263d..18fc955e7 100644 --- a/src/spaces/cartesianspace.jl +++ b/src/spaces/cartesianspace.jl @@ -47,7 +47,7 @@ hassector(V::CartesianSpace, ::Trivial) = dim(V) != 0 sectors(V::CartesianSpace) = OneOrNoneIterator(dim(V) != 0, Trivial()) sectortype(::Type{CartesianSpace}) = Trivial -Base.oneunit(::Type{CartesianSpace}) = CartesianSpace(1) +unitspace(::Type{CartesianSpace}) = CartesianSpace(1) Base.zero(::Type{CartesianSpace}) = CartesianSpace(0) ⊕(V₁::CartesianSpace, V₂::CartesianSpace) = CartesianSpace(V₁.d + V₂.d) fuse(V₁::CartesianSpace, V₂::CartesianSpace) = CartesianSpace(V₁.d * V₂.d) diff --git a/src/spaces/complexspace.jl b/src/spaces/complexspace.jl index ff05888b8..1eee1c0b2 100644 --- a/src/spaces/complexspace.jl +++ b/src/spaces/complexspace.jl @@ -48,7 +48,7 @@ sectortype(::Type{ComplexSpace}) = Trivial Base.conj(V::ComplexSpace) = ComplexSpace(dim(V), !isdual(V)) -Base.oneunit(::Type{ComplexSpace}) = ComplexSpace(1) +unitspace(::Type{ComplexSpace}) = ComplexSpace(1) Base.zero(::Type{ComplexSpace}) = ComplexSpace(0) function ⊕(V₁::ComplexSpace, V₂::ComplexSpace) return isdual(V₁) == isdual(V₂) ? diff --git a/src/spaces/generalspace.jl b/src/spaces/generalspace.jl index c72b55e70..dff72ecf3 100644 --- a/src/spaces/generalspace.jl +++ b/src/spaces/generalspace.jl @@ -35,7 +35,7 @@ sectortype(::Type{<:GeneralSpace}) = Trivial field(::Type{GeneralSpace{𝔽}}) where {𝔽} = 𝔽 InnerProductStyle(::Type{<:GeneralSpace}) = NoInnerProduct() -Base.oneunit(::Type{GeneralSpace{𝔽}}) where {𝔽} = GeneralSpace{𝔽}(1, false, false) +unitspace(::Type{GeneralSpace{𝔽}}) where {𝔽} = GeneralSpace{𝔽}(1, false, false) Base.zero(::Type{GeneralSpace{𝔽}}) where {𝔽} = GeneralSpace{𝔽}(0, false, false) dual(V::GeneralSpace{𝔽}) where {𝔽} = GeneralSpace{𝔽}(dim(V), !isdual(V), isconj(V)) diff --git a/src/spaces/gradedspace.jl b/src/spaces/gradedspace.jl index 63903a1ac..25a9b6be1 100644 --- a/src/spaces/gradedspace.jl +++ b/src/spaces/gradedspace.jl @@ -130,8 +130,8 @@ function Base.axes(V::GradedSpace{I}, c::I) where {I<:Sector} end return (offset + 1):(offset + dim(c) * dim(V, c)) end - -Base.oneunit(S::Type{<:GradedSpace{I}}) where {I<:Sector} = S(one(I) => 1) +#TODO: one -> unit +unitspace(S::Type{<:GradedSpace{I}}) where {I<:Sector} = S(one(I) => 1) Base.zero(S::Type{<:GradedSpace{I}}) where {I<:Sector} = S(one(I) => 0) # TODO: the following methods can probably be implemented more efficiently for diff --git a/src/spaces/productspace.jl b/src/spaces/productspace.jl index cda85381e..6edac4779 100644 --- a/src/spaces/productspace.jl +++ b/src/spaces/productspace.jl @@ -232,6 +232,7 @@ end Return a tensor product of zero spaces of type `S`, i.e. this is the unit object under the tensor product operation, such that `V ⊗ one(V) == V`. """ +#TODO: unit(V::S)? Base.one(V::VectorSpace) = one(typeof(V)) Base.one(::Type{<:ProductSpace{S}}) where {S<:ElementarySpace} = ProductSpace{S,0}(()) Base.one(::Type{S}) where {S<:ElementarySpace} = ProductSpace{S,0}(()) @@ -242,7 +243,7 @@ function Base.literal_pow(::typeof(^), V::ElementarySpace, p::Val{N}) where {N} return ProductSpace{typeof(V),N}(ntuple(n -> V, p)) end -fuse(P::ProductSpace{S,0}) where {S<:ElementarySpace} = oneunit(S) +fuse(P::ProductSpace{S,0}) where {S<:ElementarySpace} = unitspace(S) fuse(P::ProductSpace{S}) where {S<:ElementarySpace} = fuse(P.spaces...) """ @@ -256,7 +257,7 @@ See also [`insertrightunit`](@ref insertrightunit(::ProductSpace, ::Val{i}) wher """ function insertleftunit(P::ProductSpace, ::Val{i}=Val(length(P) + 1); conj::Bool=false, dual::Bool=false) where {i} - u = oneunit(spacetype(P)) + u = unitspace(spacetype(P)) if dual u = TensorKit.dual(u) end @@ -277,7 +278,7 @@ See also [`insertleftunit`](@ref insertleftunit(::ProductSpace, ::Val{i}) where """ function insertrightunit(P::ProductSpace, ::Val{i}=Val(length(P)); conj::Bool=false, dual::Bool=false) where {i} - u = oneunit(spacetype(P)) + u = unitspace(spacetype(P)) if dual u = TensorKit.dual(u) end @@ -299,7 +300,7 @@ and [`insertrightunit`](@ref insertrightunit(::ProductSpace, ::Val{i}) where {i} """ function removeunit(P::ProductSpace, ::Val{i}) where {i} 1 ≤ i ≤ length(P) || _boundserror(P, i) - isisomorphic(P[i], oneunit(P[i])) || _nontrivialspaceerror(P, i) + isisomorphic(P[i], unitspace(P[i])) || _nontrivialspaceerror(P, i) return ProductSpace{spacetype(P)}(TupleTools.deleteat(P.spaces, i)) end @@ -326,7 +327,7 @@ function Base.promote_rule(::Type{S}, ::Type{<:ProductSpace{S}}) where {S<:Eleme end # ProductSpace to ElementarySpace -Base.convert(::Type{S}, P::ProductSpace{S,0}) where {S<:ElementarySpace} = oneunit(S) +Base.convert(::Type{S}, P::ProductSpace{S,0}) where {S<:ElementarySpace} = unitspace(S) Base.convert(::Type{S}, P::ProductSpace{S}) where {S<:ElementarySpace} = fuse(P.spaces...) # ElementarySpace to ProductSpace diff --git a/src/spaces/vectorspaces.jl b/src/spaces/vectorspaces.jl index 844da081f..ce92889e0 100644 --- a/src/spaces/vectorspaces.jl +++ b/src/spaces/vectorspaces.jl @@ -80,7 +80,7 @@ Base.adjoint(V::VectorSpace) = dual(V) """ isdual(V::ElementarySpace) -> Bool -Return wether an ElementarySpace `V` is normal or rather a dual space. Always returns +Return whether an ElementarySpace `V` is normal or rather a dual space. Always returns `false` for spaces where `V == dual(V)`. """ function isdual end @@ -120,14 +120,15 @@ Return the sum of all degeneracy dimensions of the vector space `V`. reduceddim(V::ElementarySpace) = sum(Base.Fix1(dim, V), sectors(V); init=0) """ - oneunit(V::S) where {S<:ElementarySpace} -> S + unitspace(V::S) where {S<:ElementarySpace} -> S Return the corresponding vector space of type `S` that represents the trivial one-dimensional space, i.e. the space that is isomorphic to the corresponding field. Note that this is different from `one(V::S)`, which returns the empty product space -`ProductSpace{S,0}(())`. +`ProductSpace{S,0}(())`. `Base.oneunit` falls back to `unitspace`. """ -Base.oneunit(V::ElementarySpace) = oneunit(typeof(V)) +unitspace(V::ElementarySpace) = unitspace(typeof(V)) +Base.oneunit(V::ElementarySpace) = unitspace(V) """ zero(V::S) where {S<:ElementarySpace} -> S @@ -135,6 +136,7 @@ Base.oneunit(V::ElementarySpace) = oneunit(typeof(V)) Return the corresponding vector space of type `S` that represents the zero-dimensional or empty space. This is, with a slight abuse of notation, the zero element of the direct sum of vector spaces. """ +#TODO: zerospace? Base.zero(V::ElementarySpace) = zero(typeof(V)) """ From 1803c59217ab7c493902583214dbb605a9c8de4f Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Thu, 25 Sep 2025 16:01:20 +0200 Subject: [PATCH 03/95] one -> unit + left/rightone -> left/rightunit + isone -> isunit when concerning sectors + some more unitspace --- src/fusiontrees/fusiontrees.jl | 18 ++++++------- src/fusiontrees/iterator.jl | 8 +++--- src/fusiontrees/manipulations.jl | 22 +++++++-------- src/spaces/deligne.jl | 8 +++--- src/spaces/gradedspace.jl | 8 +++--- src/spaces/productspace.jl | 2 +- src/tensors/braidingtensor.jl | 8 +++--- src/tensors/linalg.jl | 4 +-- src/tensors/tensor.jl | 4 +-- test/fusiontrees.jl | 6 ++--- test/runtests.jl | 4 +-- test/spaces.jl | 46 ++++++++++++++++---------------- 12 files changed, 69 insertions(+), 69 deletions(-) diff --git a/src/fusiontrees/fusiontrees.jl b/src/fusiontrees/fusiontrees.jl index 3e694e523..a9dcbeae8 100644 --- a/src/fusiontrees/fusiontrees.jl +++ b/src/fusiontrees/fusiontrees.jl @@ -32,7 +32,7 @@ struct FusionTree{I<:Sector,N,M,L} vertices::NTuple{L,Int}) where {I<:Sector,N,M,L} # if N == 0 - # @assert coupled == one(coupled) + # @assert coupled == unit(coupled) # elseif N == 1 # @assert coupled == uncoupled[1] # elseif N == 2 @@ -78,7 +78,7 @@ function FusionTree(uncoupled::NTuple{N,I}, coupled::I, end end -function FusionTree{I}(uncoupled::NTuple{N}, coupled=one(I), +function FusionTree{I}(uncoupled::NTuple{N}, coupled=unit(I), isdual=ntuple(n -> false, N)) where {I<:Sector,N} FusionStyle(I) isa UniqueFusion || error("fusion tree requires inner lines if `FusionStyle(I) <: MultipleFusion`") @@ -90,7 +90,7 @@ function FusionTree(uncoupled::NTuple{N,I}, coupled::I, isdual=ntuple(n -> false, length(uncoupled))) where {N,I<:Sector} return FusionTree{I}(uncoupled, coupled, isdual) end -FusionTree(uncoupled::Tuple{I,Vararg{I}}) where {I<:Sector} = FusionTree(uncoupled, one(I)) +FusionTree(uncoupled::Tuple{I,Vararg{I}}) where {I<:Sector} = FusionTree(uncoupled, unit(I)) # Properties sectortype(::Type{<:FusionTree{I}}) where {I<:Sector} = I @@ -146,17 +146,17 @@ end # converting to actual array function Base.convert(A::Type{<:AbstractArray}, f::FusionTree{I,0}) where {I} - X = convert(A, fusiontensor(one(I), one(I), one(I)))[1, 1, :] + X = convert(A, fusiontensor(unit(I), unit(I), unit(I)))[1, 1, :] return X end function Base.convert(A::Type{<:AbstractArray}, f::FusionTree{I,1}) where {I} c = f.coupled if f.isdual[1] - sqrtdc = sqrtdim(c) - Zcbartranspose = sqrtdc * convert(A, fusiontensor(conj(c), c, one(c)))[:, :, 1, 1] + sqrtdc = sqrtdim(c) # TODO: change conj to dual + Zcbartranspose = sqrtdc * convert(A, fusiontensor(conj(c), c, unit(c)))[:, :, 1, 1] X = conj!(Zcbartranspose) # we want Zcbar^† else - X = convert(A, fusiontensor(c, one(c), c))[:, 1, :, 1, 1] + X = convert(A, fusiontensor(c, unit(c), c))[:, 1, :, 1, 1] end return X end @@ -234,13 +234,13 @@ include("iterator.jl") # _abelianinner: generate the inner indices for given outer indices in the abelian case _abelianinner(outer::Tuple{}) = () function _abelianinner(outer::Tuple{I}) where {I<:Sector} - return isone(outer[1]) ? () : throw(SectorMismatch()) + return isunit(outer[1]) ? () : throw(SectorMismatch()) end function _abelianinner(outer::Tuple{I,I}) where {I<:Sector} return outer[1] == dual(outer[2]) ? () : throw(SectorMismatch()) end function _abelianinner(outer::Tuple{I,I,I}) where {I<:Sector} - return isone(first(⊗(outer...))) ? () : throw(SectorMismatch()) + return isunit(first(⊗(outer...))) ? () : throw(SectorMismatch()) end function _abelianinner(outer::Tuple{I,I,I,I,Vararg{I}}) where {I<:Sector} c = first(outer[1] ⊗ outer[2]) diff --git a/src/fusiontrees/iterator.jl b/src/fusiontrees/iterator.jl index 7e7106daf..6ebd8d965 100644 --- a/src/fusiontrees/iterator.jl +++ b/src/fusiontrees/iterator.jl @@ -1,6 +1,6 @@ """ fusiontrees(uncoupled::NTuple{N,I}[, - coupled::I=one(I)[, isdual::NTuple{N,Bool}=ntuple(n -> false, length(uncoupled))]]) + coupled::I=unit(I)[, isdual::NTuple{N,Bool}=ntuple(n -> false, length(uncoupled))]]) where {N,I<:Sector} -> FusionTreeIterator{I,N,I} Return an iterator over all fusion trees with a given coupled sector label `coupled` and @@ -16,7 +16,7 @@ function fusiontrees(uncoupled::Tuple{Vararg{I}}, coupled::I) where {I<:Sector} return fusiontrees(uncoupled, coupled, isdual) end function fusiontrees(uncoupled::Tuple{I,Vararg{I}}) where {I<:Sector} - coupled = one(I) + coupled = unit(I) isdual = ntuple(n -> false, length(uncoupled)) return fusiontrees(uncoupled, coupled, isdual) end @@ -37,7 +37,7 @@ Base.IteratorEltype(::FusionTreeIterator) = Base.HasEltype() Base.eltype(::Type{<:FusionTreeIterator{I,N}}) where {I<:Sector,N} = fusiontreetype(I, N) Base.length(iter::FusionTreeIterator) = _fusiondim(iter.uncouplediterators, iter.coupled) -_fusiondim(::Tuple{}, c::I) where {I<:Sector} = Int(isone(c)) +_fusiondim(::Tuple{}, c::I) where {I<:Sector} = Int(isunit(c)) _fusiondim(iters::NTuple{1}, c::I) where {I<:Sector} = Int(c ∈ iters[1]) function _fusiondim(iters::NTuple{2}, c::I) where {I<:Sector} d = 0 @@ -60,7 +60,7 @@ end # * Iterator methods: # Start with special cases: function Base.iterate(it::FusionTreeIterator{I,0}, - state=!isone(it.coupled)) where {I<:Sector} + state=!isunit(it.coupled)) where {I<:Sector} state && return nothing tree = FusionTree{I}((), it.coupled, (), (), ()) return tree, true diff --git a/src/fusiontrees/manipulations.jl b/src/fusiontrees/manipulations.jl index f19bb06ed..cf3d96978 100644 --- a/src/fusiontrees/manipulations.jl +++ b/src/fusiontrees/manipulations.jl @@ -165,7 +165,7 @@ operation is the inverse of `insertat` in the sense that if f₂ = FusionTree{I}(f.uncoupled, f.coupled, isdual2, f.innerlines, f.vertices) return f₁, f₂ elseif M === 0 - u = leftone(f.uncoupled[1]) + u = leftunit(f.uncoupled[1]) f₁ = FusionTree{I}((), u, (), ()) uncoupled2 = (u, f.uncoupled...) coupled2 = f.coupled @@ -290,7 +290,7 @@ function bendright(f₁::FusionTree{I,N₁}, f₂::FusionTree{I,N₂}) where {I< # map final splitting vertex (a, b)<-c to fusion vertex a<-(c, dual(b)) @assert N₁ > 0 c = f₁.coupled - a = N₁ == 1 ? leftone(f₁.uncoupled[1]) : + a = N₁ == 1 ? leftunit(f₁.uncoupled[1]) : (N₁ == 2 ? f₁.uncoupled[1] : f₁.innerlines[end]) b = f₁.uncoupled[N₁] @@ -362,7 +362,7 @@ function foldright(f₁::FusionTree{I,N₁}, f₂::FusionTree{I,N₂}) where {I< hasmultiplicities = FusionStyle(a) isa GenericFusion local newtrees if N₁ == 1 - cset = (leftone(c1),) # or rightone(a) + cset = (leftunit(c1),) # or rightunit(a) elseif N₁ == 2 cset = (f₁.uncoupled[2],) else @@ -373,7 +373,7 @@ function foldright(f₁::FusionTree{I,N₁}, f₂::FusionTree{I,N₂}) where {I< for μ in 1:Nsymbol(c1, c2, c) fc = FusionTree((c1, c2), c, (!isduala, false), (), (μ,)) for (fl′, coeff1) in insertat(fc, 2, f₁) - N₁ > 1 && !isone(fl′.innerlines[1]) && continue + N₁ > 1 && !isunit(fl′.innerlines[1]) && continue coupled = fl′.coupled uncoupled = Base.tail(Base.tail(fl′.uncoupled)) isdual = Base.tail(Base.tail(fl′.isdual)) @@ -718,7 +718,7 @@ corresponding coefficients. function elementary_trace(f::FusionTree{I,N}, i) where {I<:Sector,N} (N > 1 && 1 <= i <= N) || throw(ArgumentError("Cannot trace outputs i=$i and i+1 out of only $N outputs")) - i < N || isone(f.coupled) || + i < N || isunit(f.coupled) || throw(ArgumentError("Cannot trace outputs i=$N and 1 of fusion tree that couples to non-trivial sector")) T = sectorscalartype(I) @@ -731,7 +731,7 @@ function elementary_trace(f::FusionTree{I,N}, i) where {I<:Sector,N} # if trace is zero, return empty dict (b == dual(b′) && f.isdual[i] != f.isdual[j]) || return newtrees if i < N - inner_extended = (leftone(f.uncoupled[1]), f.uncoupled[1], f.innerlines..., + inner_extended = (leftunit(f.uncoupled[1]), f.uncoupled[1], f.innerlines..., f.coupled) a = inner_extended[i] d = inner_extended[i + 2] @@ -756,11 +756,11 @@ function elementary_trace(f::FusionTree{I,N}, i) where {I<:Sector,N} if i > 1 c = f.innerlines[i - 1] if FusionStyle(I) isa MultiplicityFreeFusion - coeff *= Fsymbol(a, b, dual(b), a, c, rightone(a)) + coeff *= Fsymbol(a, b, dual(b), a, c, rightunit(a)) else μ = f.vertices[i - 1] ν = f.vertices[i] - coeff *= Fsymbol(a, b, dual(b), a, c, rightone(a))[μ, ν, 1, 1] + coeff *= Fsymbol(a, b, dual(b), a, c, rightunit(a))[μ, ν, 1, 1] end end if f.isdual[i] @@ -769,7 +769,7 @@ function elementary_trace(f::FusionTree{I,N}, i) where {I<:Sector,N} push!(newtrees, f′ => coeff) return newtrees else # i == N - unit = leftone(b) + unit = leftunit(b) if N == 2 f′ = FusionTree{I}((), unit, (), (), ()) coeff = sqrtdim(b) @@ -835,13 +835,13 @@ function artin_braid(f::FusionTree{I,N}, i; inv::Bool=false) where {I<:Sector,N} vertices = f.vertices oneT = one(sectorscalartype(I)) - if isone(uncoupled[i]) || isone(uncoupled[i + 1]) + if isunit(a) || isunit(b) # braiding with trivial sector: simple and always possible inner′ = inner vertices′ = vertices if i > 1 # we also need to alter innerlines and vertices inner′ = TupleTools.setindex(inner, - inner_extended[isone(a) ? (i + 1) : (i - 1)], + inner_extended[isunit(a) ? (i + 1) : (i - 1)], i - 1) vertices′ = TupleTools.setindex(vertices′, vertices[i], i - 1) vertices′ = TupleTools.setindex(vertices′, vertices[i - 1], i) diff --git a/src/spaces/deligne.jl b/src/spaces/deligne.jl index f9cc51c8e..27d5703be 100644 --- a/src/spaces/deligne.jl +++ b/src/spaces/deligne.jl @@ -23,7 +23,7 @@ function ⊠(V::GradedSpace, P₀::ProductSpace{<:ElementarySpace,0}) field(V) == field(P₀) || throw_incompatible_fields(V, P₀) I₁ = sectortype(V) I₂ = sectortype(P₀) - return Vect[I₁ ⊠ I₂](ifelse(isdual(V), dual(c), c) ⊠ one(I₂) => dim(V, c) + return Vect[I₁ ⊠ I₂](ifelse(isdual(V), dual(c), c) ⊠ unit(I₂) => dim(V, c) for c in sectors(V); dual=isdual(V)) end @@ -31,20 +31,20 @@ function ⊠(P₀::ProductSpace{<:ElementarySpace,0}, V::GradedSpace) field(P₀) == field(V) || throw_incompatible_fields(P₀, V) I₁ = sectortype(P₀) I₂ = sectortype(V) - return Vect[I₁ ⊠ I₂](one(I₁) ⊠ ifelse(isdual(V), dual(c), c) => dim(V, c) + return Vect[I₁ ⊠ I₂](unit(I₁) ⊠ ifelse(isdual(V), dual(c), c) => dim(V, c) for c in sectors(V); dual=isdual(V)) end function ⊠(V::ComplexSpace, P₀::ProductSpace{<:ElementarySpace,0}) field(V) == field(P₀) || throw_incompatible_fields(V, P₀) I₂ = sectortype(P₀) - return Vect[I₂](one(I₂) => dim(V); dual=isdual(V)) + return Vect[I₂](unit(I₂) => dim(V); dual=isdual(V)) end function ⊠(P₀::ProductSpace{<:ElementarySpace,0}, V::ComplexSpace) field(P₀) == field(V) || throw_incompatible_fields(P₀, V) I₁ = sectortype(P₀) - return Vect[I₁](one(I₁) => dim(V); dual=isdual(V)) + return Vect[I₁](unit(I₁) => dim(V); dual=isdual(V)) end function ⊠(P::ProductSpace{<:ElementarySpace,0}, P₀::ProductSpace{<:ElementarySpace,0}) diff --git a/src/spaces/gradedspace.jl b/src/spaces/gradedspace.jl index 25a9b6be1..513dc5a19 100644 --- a/src/spaces/gradedspace.jl +++ b/src/spaces/gradedspace.jl @@ -92,7 +92,7 @@ field(::Type{<:GradedSpace}) = ℂ InnerProductStyle(::Type{<:GradedSpace}) = EuclideanInnerProduct() function dim(V::GradedSpace) return reduce(+, dim(V, c) * dim(c) for c in sectors(V); - init=zero(dim(one(sectortype(V))))) + init=zero(dim(unit(sectortype(V))))) end function dim(V::GradedSpace{I,<:AbstractDict}, c::I) where {I<:Sector} return get(V.dims, isdual(V) ? dual(c) : c, 0) @@ -130,9 +130,9 @@ function Base.axes(V::GradedSpace{I}, c::I) where {I<:Sector} end return (offset + 1):(offset + dim(c) * dim(V, c)) end -#TODO: one -> unit -unitspace(S::Type{<:GradedSpace{I}}) where {I<:Sector} = S(one(I) => 1) -Base.zero(S::Type{<:GradedSpace{I}}) where {I<:Sector} = S(one(I) => 0) + +unitspace(S::Type{<:GradedSpace{I}}) where {I<:Sector} = S(unit(I) => 1) +Base.zero(S::Type{<:GradedSpace{I}}) where {I<:Sector} = S(unit(I) => 0) # TODO: the following methods can probably be implemented more efficiently for # `FiniteGradedSpace`, but we don't expect them to be used often in hot loops, so diff --git a/src/spaces/productspace.jl b/src/spaces/productspace.jl index 6edac4779..41ffc1090 100644 --- a/src/spaces/productspace.jl +++ b/src/spaces/productspace.jl @@ -148,7 +148,7 @@ function blocksectors(P::ProductSpace{S,N}) where {S,N} end bs = Vector{I}() if N == 0 - push!(bs, one(I)) + push!(bs, unit(I)) elseif N == 1 for s in sectors(P) push!(bs, first(s)) diff --git a/src/tensors/braidingtensor.jl b/src/tensors/braidingtensor.jl index a4a184e5f..d10d00c7e 100644 --- a/src/tensors/braidingtensor.jl +++ b/src/tensors/braidingtensor.jl @@ -346,7 +346,7 @@ end # rmul!(C, β) # end # I = sectortype(B) -# u = one(I) +# u = unit(I) # f₀ = FusionTree{I}((), u, (), (), ()) # braidingtensor_levels = A.adjoint ? (1, 2, 2, 1) : (2, 1, 1, 2) # inv_braid = braidingtensor_levels[cindA[2]] > braidingtensor_levels[cindA[3]] @@ -414,7 +414,7 @@ end # rmul!(C, β) # end # I = sectortype(B) -# u = one(I) +# u = unit(I) # f₀ = FusionTree{I}((), u, (), (), ()) # braidingtensor_levels = B.adjoint ? (1, 2, 2, 1) : (2, 1, 1, 2) # inv_braid = braidingtensor_levels[cindB[2]] > braidingtensor_levels[cindB[3]] @@ -481,7 +481,7 @@ end # rmul!(C, β) # end # I = sectortype(B) -# u = one(I) +# u = unit(I) # braidingtensor_levels = A.adjoint ? (1, 2, 2, 1) : (2, 1, 1, 2) # inv_braid = braidingtensor_levels[cindA[2]] > braidingtensor_levels[cindA[3]] # for (f₁, f₂) in fusiontrees(B) @@ -543,7 +543,7 @@ end # rmul!(C, β) # end # I = sectortype(B) -# u = one(I) +# u = unit(I) # braidingtensor_levels = B.adjoint ? (1, 2, 2, 1) : (2, 1, 1, 2) # inv_braid = braidingtensor_levels[cindB[2]] > braidingtensor_levels[cindB[3]] # for (f₁, f₂) in fusiontrees(A) diff --git a/src/tensors/linalg.jl b/src/tensors/linalg.jl index 5eba8414c..35c473a02 100644 --- a/src/tensors/linalg.jl +++ b/src/tensors/linalg.jl @@ -597,13 +597,13 @@ function ⊠(t1::AbstractTensorMap, t2::AbstractTensorMap) dom1 = domain(t1) ⊠ one(S2) t1′ = similar(t1, codom1 ← dom1) for (c, b) in blocks(t1) - copy!(block(t1′, c ⊠ one(I2)), b) + copy!(block(t1′, c ⊠ unit(I2)), b) end codom2 = one(S1) ⊠ codomain(t2) dom2 = one(S1) ⊠ domain(t2) t2′ = similar(t2, codom2 ← dom2) for (c, b) in blocks(t2) - copy!(block(t2′, one(I1) ⊠ c), b) + copy!(block(t2′, unit(I1) ⊠ c), b) end return t1′ ⊗ t2′ end diff --git a/src/tensors/tensor.jl b/src/tensors/tensor.jl index a918478c3..bfad6cf40 100644 --- a/src/tensors/tensor.jl +++ b/src/tensors/tensor.jl @@ -538,9 +538,9 @@ since it assumes a uniquely defined coupled charge. throw(ArgumentError("Number of sectors does not match.")) s₁ = TupleTools.getindices(sectors, codomainind(t)) s₂ = map(dual, TupleTools.getindices(sectors, domainind(t))) - c1 = length(s₁) == 0 ? one(I) : (length(s₁) == 1 ? s₁[1] : first(⊗(s₁...))) + c1 = length(s₁) == 0 ? unit(I) : (length(s₁) == 1 ? s₁[1] : first(⊗(s₁...))) @boundscheck begin - c2 = length(s₂) == 0 ? one(I) : (length(s₂) == 1 ? s₂[1] : first(⊗(s₂...))) + c2 = length(s₂) == 0 ? unit(I) : (length(s₂) == 1 ? s₂[1] : first(⊗(s₂...))) c2 == c1 || throw(SectorMismatch("Not a valid sector for this tensor")) hassector(codomain(t), s₁) && hassector(domain(t), s₂) end diff --git a/test/fusiontrees.jl b/test/fusiontrees.jl index d758f9a83..72cf9a2ea 100644 --- a/test/fusiontrees.jl +++ b/test/fusiontrees.jl @@ -26,7 +26,7 @@ ti = time() @test eval(Meta.parse(sprint(show, f))) == f end @testset "Fusion tree $Istr: constructor properties" begin - u = one(I) + u = unit(I) @constinferred FusionTree((), u, (), (), ()) @constinferred FusionTree((u,), u, (false,), (), ()) @constinferred FusionTree((u, u), u, (false, false), (), (1,)) @@ -125,7 +125,7 @@ ti = time() outgoing = (s, dual(s), s, dual(s), s, dual(s)) for bool in (true, false) isdual = (bool, !bool, bool, !bool, bool, !bool) - for f in fusiontrees(outgoing, one(s), isdual) + for f in fusiontrees(outgoing, unit(s), isdual) af = convert(Array, f) T = eltype(af) @@ -548,7 +548,7 @@ ti = time() @testset "Double fusion tree $Istr: planar trace" begin d1 = transpose(f1, f1, (N + 1, 1:N..., ((2N):-1:(N + 3))...), (N + 2,)) f1front, = TK.split(f1, N - 1) - T = typeof(Fsymbol(one(I), one(I), one(I), one(I), one(I), one(I))[1, 1, 1, 1]) + T = typeof(Fsymbol(unit(I), unit(I), unit(I), unit(I), unit(I), unit(I))[1, 1, 1, 1]) #TODO: change to _fscalartype d2 = Dict{typeof((f1front, f1front)),T}() for ((f1′, f2′), coeff′) in d1 for ((f1′′, f2′′), coeff′′) in diff --git a/test/runtests.jl b/test/runtests.jl index 9fafa1d9f..42523270c 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -33,14 +33,14 @@ end function randsector(::Type{I}) where {I<:Sector} s = collect(smallset(I)) a = rand(s) - while a == one(a) # don't use trivial label + while isunit(a) # don't use trivial label a = rand(s) end return a end function hasfusiontensor(I::Type{<:Sector}) try - fusiontensor(one(I), one(I), one(I)) + fusiontensor(unit(I), unit(I), unit(I)) return true catch e if e isa MethodError diff --git a/test/spaces.jl b/test/spaces.jl index 90d27d74e..5f0859b7d 100644 --- a/test/spaces.jl +++ b/test/spaces.jl @@ -71,11 +71,11 @@ println("------------------------------------") @test @constinferred(TensorKit.axes(V)) == Base.OneTo(d) @test ℝ^d == ℝ[](d) == CartesianSpace(d) == typeof(V)(d) W = @constinferred ℝ^1 - @test @constinferred(oneunit(V)) == W == oneunit(typeof(V)) + @test @constinferred(unitspace(V)) == W == unitspace(typeof(V)) @test @constinferred(zero(V)) == ℝ^0 == zero(typeof(V)) @test @constinferred(⊕(V, zero(V))) == V @test @constinferred(⊕(V, V)) == ℝ^(2d) - @test @constinferred(⊕(V, oneunit(V))) == ℝ^(d + 1) + @test @constinferred(⊕(V, unitspace(V))) == ℝ^(d + 1) @test @constinferred(⊕(V, V, V, V)) == ℝ^(4d) @test @constinferred(fuse(V, V)) == ℝ^(d^2) @test @constinferred(fuse(V, V', V, V')) == ℝ^(d^4) @@ -118,7 +118,7 @@ println("------------------------------------") @test @constinferred(TensorKit.axes(V)) == Base.OneTo(d) @test ℂ^d == Vect[Trivial](d) == Vect[](Trivial() => d) == ℂ[](d) == typeof(V)(d) W = @constinferred ℂ^1 - @test @constinferred(oneunit(V)) == W == oneunit(typeof(V)) + @test @constinferred(unitspace(V)) == W == unitspace(typeof(V)) @test @constinferred(zero(V)) == ℂ^0 == zero(typeof(V)) @test @constinferred(⊕(V, zero(V))) == V @test @constinferred(⊕(V, V)) == ℂ^(2d) @@ -128,7 +128,7 @@ println("------------------------------------") # @test_throws promote_except (⊕(ℝ^d, ℂ^d)) @test_throws ErrorException (⊗(ℝ^d, ℂ^d)) @test @constinferred(⊕(V, V)) == ℂ^(2d) - @test @constinferred(⊕(V, oneunit(V))) == ℂ^(d + 1) + @test @constinferred(⊕(V, unitspace(V))) == ℂ^(d + 1) @test @constinferred(⊕(V, V, V, V)) == ℂ^(4d) @test @constinferred(fuse(V, V)) == ℂ^(d^2) @test @constinferred(fuse(V, V', V, V')) == ℂ^(d^4) @@ -172,7 +172,7 @@ println("------------------------------------") @timedtestset "ElementarySpace: $(TensorKit.type_repr(Vect[I]))" for I in sectorlist if Base.IteratorSize(values(I)) === Base.IsInfinite() - set = unique(vcat(one(I), [randsector(I) for k in 1:10])) + set = unique(vcat(unit(I), [randsector(I) for k in 1:10])) gen = (c => 2 for c in set) else gen = (values(I)[k] => (k + 1) for k in 1:length(values(I))) @@ -206,12 +206,12 @@ println("------------------------------------") # space with no sectors @test dim(@constinferred(zero(V))) == 0 # space with a single sector - W = @constinferred GradedSpace(one(I) => 1) - @test W == GradedSpace(one(I) => 1, randsector(I) => 0) - @test @constinferred(oneunit(V)) == W == oneunit(typeof(V)) - @test @constinferred(zero(V)) == GradedSpace(one(I) => 0) + W = @constinferred GradedSpace(unit(I) => 1) + @test W == GradedSpace(unit(I) => 1, randsector(I) => 0) + @test @constinferred(unitspace(V)) == W == unitspace(typeof(V)) + @test @constinferred(zero(V)) == GradedSpace(unit(I) => 0) # randsector never returns trivial sector, so this cannot error - @test_throws ArgumentError GradedSpace(one(I) => 1, randsector(I) => 0, one(I) => 3) + @test_throws ArgumentError GradedSpace(unit(I) => 1, randsector(I) => 0, unit(I) => 3) @test eval(Meta.parse(sprint(show, W))) == W @test isa(V, VectorSpace) @test isa(V, ElementarySpace) @@ -234,9 +234,9 @@ println("------------------------------------") @test @constinferred(⊕(V, zero(V))) == V @test @constinferred(⊕(V, V)) == Vect[I](c => 2dim(V, c) for c in sectors(V)) @test @constinferred(⊕(V, V, V, V)) == Vect[I](c => 4dim(V, c) for c in sectors(V)) - @test @constinferred(⊕(V, oneunit(V))) == - Vect[I](c => isone(c) + dim(V, c) for c in sectors(V)) - @test @constinferred(fuse(V, oneunit(V))) == V + @test @constinferred(⊕(V, unitspace(V))) == + Vect[I](c => isunit(c) + dim(V, c) for c in sectors(V)) + @test @constinferred(fuse(V, unitspace(V))) == V d = Dict{I,Int}() for a in sectors(V), b in sectors(V) for c in a ⊗ b @@ -253,7 +253,7 @@ println("------------------------------------") @test V == @constinferred infimum(V, ⊕(V, V)) @test V ≺ ⊕(V, V) @test !(V ≻ ⊕(V, V)) - @test infimum(V, GradedSpace(one(I) => 3)) == GradedSpace(one(I) => 2) + @test infimum(V, GradedSpace(unit(I) => 3)) == GradedSpace(unit(I) => 2) @test_throws SpaceMismatch (⊕(V, V')) end @@ -278,10 +278,10 @@ println("------------------------------------") @test @constinferred(⊗(V1 ⊗ V2, V3 ⊗ V4)) == P @test @constinferred(⊗(V1, V2, V3 ⊗ V4)) == P @test @constinferred(⊗(V1, V2 ⊗ V3, V4)) == P - @test V1 * V2 * oneunit(V1) * V3 * V4 == + @test V1 * V2 * unitspace(V1) * V3 * V4 == @constinferred(insertleftunit(P, 3)) == @constinferred(insertrightunit(P, 2)) - @test @constinferred(removeunit(V1 * V2 * oneunit(V1)' * V3 * V4, 3)) == P + @test @constinferred(removeunit(V1 * V2 * unitspace(V1)' * V3 * V4, 3)) == P @test fuse(V1, V2', V3) ≅ V1 ⊗ V2' ⊗ V3 @test fuse(V1, V2', V3) ≾ V1 ⊗ V2' ⊗ V3 @test fuse(V1, V2', V3) ≿ V1 ⊗ V2' ⊗ V3 @@ -342,7 +342,7 @@ println("------------------------------------") @test @constinferred(*(V1, V2, V3)) == P @test @constinferred(⊗(V1, V2, V3)) == P @test @constinferred(adjoint(P)) == dual(P) == V3' ⊗ V2' ⊗ V1' - @test V1 * V2 * oneunit(V1)' * V3 == + @test V1 * V2 * unitspace(V1)' * V3 == @constinferred(insertleftunit(P, 3; conj=true)) == @constinferred(insertrightunit(P, 2; conj=true)) @test P == @constinferred(removeunit(insertleftunit(P, 3), 3)) @@ -424,22 +424,22 @@ println("------------------------------------") @test W == @constinferred permute(W, ((1, 2), (3, 4, 5))) @test permute(W, ((2, 4, 5), (3, 1))) == (V2 ⊗ V4' ⊗ V5' ← V3 ⊗ V1') @test (V1 ⊗ V2 ← V1 ⊗ V2) == @constinferred TensorKit.compose(W, W') - @test (V1 ⊗ V2 ← V3 ⊗ V4 ⊗ V5 ⊗ oneunit(V5)) == + @test (V1 ⊗ V2 ← V3 ⊗ V4 ⊗ V5 ⊗ unitspace(V5)) == @constinferred(insertleftunit(W)) == @constinferred(insertrightunit(W)) @test @constinferred(removeunit(insertleftunit(W), $(numind(W) + 1))) == W - @test (V1 ⊗ V2 ← V3 ⊗ V4 ⊗ V5 ⊗ oneunit(V5)') == + @test (V1 ⊗ V2 ← V3 ⊗ V4 ⊗ V5 ⊗ unitspace(V5)') == @constinferred(insertleftunit(W; conj=true)) == @constinferred(insertrightunit(W; conj=true)) - @test (oneunit(V1) ⊗ V1 ⊗ V2 ← V3 ⊗ V4 ⊗ V5) == + @test (unitspace(V1) ⊗ V1 ⊗ V2 ← V3 ⊗ V4 ⊗ V5) == @constinferred(insertleftunit(W, 1)) == @constinferred(insertrightunit(W, 0)) - @test (V1 ⊗ V2 ⊗ oneunit(V1) ← V3 ⊗ V4 ⊗ V5) == + @test (V1 ⊗ V2 ⊗ unitspace(V1) ← V3 ⊗ V4 ⊗ V5) == @constinferred(insertrightunit(W, 2)) - @test (V1 ⊗ V2 ← oneunit(V1) ⊗ V3 ⊗ V4 ⊗ V5) == + @test (V1 ⊗ V2 ← unitspace(V1) ⊗ V3 ⊗ V4 ⊗ V5) == @constinferred(insertleftunit(W, 3)) @test @constinferred(removeunit(insertleftunit(W, 3), 3)) == W - @test @constinferred(insertrightunit(one(V1) ← V1, 0)) == (oneunit(V1) ← V1) + @test @constinferred(insertrightunit(one(V1) ← V1, 0)) == (unitspace(V1) ← V1) @test_throws BoundsError insertleftunit(one(V1) ← V1, 0) end end From 3dab6bac863d712f43aac347267cc01f6b640818 Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Thu, 25 Sep 2025 16:07:20 +0200 Subject: [PATCH 04/95] change conj to dual for sectors --- src/fusiontrees/fusiontrees.jl | 4 ++-- src/fusiontrees/manipulations.jl | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/fusiontrees/fusiontrees.jl b/src/fusiontrees/fusiontrees.jl index a9dcbeae8..d90e2ec07 100644 --- a/src/fusiontrees/fusiontrees.jl +++ b/src/fusiontrees/fusiontrees.jl @@ -152,8 +152,8 @@ end function Base.convert(A::Type{<:AbstractArray}, f::FusionTree{I,1}) where {I} c = f.coupled if f.isdual[1] - sqrtdc = sqrtdim(c) # TODO: change conj to dual - Zcbartranspose = sqrtdc * convert(A, fusiontensor(conj(c), c, unit(c)))[:, :, 1, 1] + sqrtdc = sqrtdim(c) + Zcbartranspose = sqrtdc * convert(A, fusiontensor(dual(c), c, unit(c)))[:, :, 1, 1] X = conj!(Zcbartranspose) # we want Zcbar^† else X = convert(A, fusiontensor(c, unit(c), c))[:, 1, :, 1, 1] diff --git a/src/fusiontrees/manipulations.jl b/src/fusiontrees/manipulations.jl index cf3d96978..3b428a262 100644 --- a/src/fusiontrees/manipulations.jl +++ b/src/fusiontrees/manipulations.jl @@ -898,7 +898,7 @@ function artin_braid(f::FusionTree{I,N}, i; inv::Bool=false) where {I<:Sector,N} return fusiontreedict(I)(f′ => coeff) elseif FusionStyle(I) isa SimpleFusion local newtrees - for c′ in intersect(a ⊗ d, e ⊗ conj(b)) + for c′ in intersect(a ⊗ d, e ⊗ dual(b)) coeff = oftype(oneT, if inv conj(Rsymbol(d, c, e) * Fsymbol(d, a, b, e, c′, c)) * @@ -919,7 +919,7 @@ function artin_braid(f::FusionTree{I,N}, i; inv::Bool=false) where {I<:Sector,N} return newtrees else # GenericFusion local newtrees - for c′ in intersect(a ⊗ d, e ⊗ conj(b)) + for c′ in intersect(a ⊗ d, e ⊗ dual(b)) Rmat1 = inv ? Rsymbol(d, c, e)' : Rsymbol(c, d, e) Rmat2 = inv ? Rsymbol(d, a, c′)' : Rsymbol(a, d, c′) Fmat = Fsymbol(d, a, b, e, c′, c) From 3a2e67c48588fe07b177c5e252ece9a6f1eb73b5 Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Thu, 25 Sep 2025 16:08:26 +0200 Subject: [PATCH 05/95] export new functions from TensorKitSectors --- src/TensorKit.jl | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/TensorKit.jl b/src/TensorKit.jl index 96a579e25..8b0e3052d 100644 --- a/src/TensorKit.jl +++ b/src/TensorKit.jl @@ -11,13 +11,16 @@ module TensorKit export Sector, AbstractIrrep, Irrep export FusionStyle, UniqueFusion, MultipleFusion, MultiplicityFreeFusion, SimpleFusion, GenericFusion +export UnitStyle, SimpleUnit, GenericUnit export BraidingStyle, SymmetricBraiding, Bosonic, Fermionic, Anyonic, NoBraiding export Trivial, Z2Irrep, Z3Irrep, Z4Irrep, ZNIrrep, U1Irrep, SU2Irrep, CU1Irrep export ProductSector export FermionParity, FermionNumber, FermionSpin export FibonacciAnyon, IsingAnyon +export unit, rightunit, leftunit, allunits, isunit export VectorSpace, Field, ElementarySpace # abstract vector spaces +export unitspace export InnerProductStyle, NoInnerProduct, HasInnerProduct, EuclideanInnerProduct export ComplexSpace, CartesianSpace, GeneralSpace, GradedSpace # concrete spaces export ZNSpace, Z2Space, Z3Space, Z4Space, U1Space, CU1Space, SU2Space From 09cee6df0b27e30f1fb4e5912f6b778520b379b7 Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Thu, 25 Sep 2025 16:33:57 +0200 Subject: [PATCH 06/95] introduce `zerospace` to replace `zero` of a space --- src/TensorKit.jl | 2 +- src/spaces/cartesianspace.jl | 2 +- src/spaces/complexspace.jl | 2 +- src/spaces/generalspace.jl | 2 +- src/spaces/gradedspace.jl | 2 +- src/spaces/homspace.jl | 2 +- src/spaces/vectorspaces.jl | 11 +++++++---- test/spaces.jl | 22 +++++++++++----------- test/tensors.jl | 6 +++--- 9 files changed, 27 insertions(+), 24 deletions(-) diff --git a/src/TensorKit.jl b/src/TensorKit.jl index 8b0e3052d..616cc4f23 100644 --- a/src/TensorKit.jl +++ b/src/TensorKit.jl @@ -20,7 +20,7 @@ export FibonacciAnyon, IsingAnyon export unit, rightunit, leftunit, allunits, isunit export VectorSpace, Field, ElementarySpace # abstract vector spaces -export unitspace +export unitspace, zerospace export InnerProductStyle, NoInnerProduct, HasInnerProduct, EuclideanInnerProduct export ComplexSpace, CartesianSpace, GeneralSpace, GradedSpace # concrete spaces export ZNSpace, Z2Space, Z3Space, Z4Space, U1Space, CU1Space, SU2Space diff --git a/src/spaces/cartesianspace.jl b/src/spaces/cartesianspace.jl index 18fc955e7..7f27e86a6 100644 --- a/src/spaces/cartesianspace.jl +++ b/src/spaces/cartesianspace.jl @@ -48,7 +48,7 @@ sectors(V::CartesianSpace) = OneOrNoneIterator(dim(V) != 0, Trivial()) sectortype(::Type{CartesianSpace}) = Trivial unitspace(::Type{CartesianSpace}) = CartesianSpace(1) -Base.zero(::Type{CartesianSpace}) = CartesianSpace(0) +zerospace(::Type{CartesianSpace}) = CartesianSpace(0) ⊕(V₁::CartesianSpace, V₂::CartesianSpace) = CartesianSpace(V₁.d + V₂.d) fuse(V₁::CartesianSpace, V₂::CartesianSpace) = CartesianSpace(V₁.d * V₂.d) flip(V::CartesianSpace) = V diff --git a/src/spaces/complexspace.jl b/src/spaces/complexspace.jl index 1eee1c0b2..1ecdb46b7 100644 --- a/src/spaces/complexspace.jl +++ b/src/spaces/complexspace.jl @@ -49,7 +49,7 @@ sectortype(::Type{ComplexSpace}) = Trivial Base.conj(V::ComplexSpace) = ComplexSpace(dim(V), !isdual(V)) unitspace(::Type{ComplexSpace}) = ComplexSpace(1) -Base.zero(::Type{ComplexSpace}) = ComplexSpace(0) +zerospace(::Type{ComplexSpace}) = ComplexSpace(0) function ⊕(V₁::ComplexSpace, V₂::ComplexSpace) return isdual(V₁) == isdual(V₂) ? ComplexSpace(dim(V₁) + dim(V₂), isdual(V₁)) : diff --git a/src/spaces/generalspace.jl b/src/spaces/generalspace.jl index dff72ecf3..4459db051 100644 --- a/src/spaces/generalspace.jl +++ b/src/spaces/generalspace.jl @@ -36,7 +36,7 @@ field(::Type{GeneralSpace{𝔽}}) where {𝔽} = 𝔽 InnerProductStyle(::Type{<:GeneralSpace}) = NoInnerProduct() unitspace(::Type{GeneralSpace{𝔽}}) where {𝔽} = GeneralSpace{𝔽}(1, false, false) -Base.zero(::Type{GeneralSpace{𝔽}}) where {𝔽} = GeneralSpace{𝔽}(0, false, false) +zerospace(::Type{GeneralSpace{𝔽}}) where {𝔽} = GeneralSpace{𝔽}(0, false, false) dual(V::GeneralSpace{𝔽}) where {𝔽} = GeneralSpace{𝔽}(dim(V), !isdual(V), isconj(V)) Base.conj(V::GeneralSpace{𝔽}) where {𝔽} = GeneralSpace{𝔽}(dim(V), isdual(V), !isconj(V)) diff --git a/src/spaces/gradedspace.jl b/src/spaces/gradedspace.jl index 513dc5a19..39f97c4b8 100644 --- a/src/spaces/gradedspace.jl +++ b/src/spaces/gradedspace.jl @@ -132,7 +132,7 @@ function Base.axes(V::GradedSpace{I}, c::I) where {I<:Sector} end unitspace(S::Type{<:GradedSpace{I}}) where {I<:Sector} = S(unit(I) => 1) -Base.zero(S::Type{<:GradedSpace{I}}) where {I<:Sector} = S(unit(I) => 0) +zerospace(S::Type{<:GradedSpace{I}}) where {I<:Sector} = S(unit(I) => 0) # TODO: the following methods can probably be implemented more efficiently for # `FiniteGradedSpace`, but we don't expect them to be used often in hot loops, so diff --git a/src/spaces/homspace.jl b/src/spaces/homspace.jl index 92188b9ef..8a46c18a1 100644 --- a/src/spaces/homspace.jl +++ b/src/spaces/homspace.jl @@ -22,7 +22,7 @@ end function HomSpace(codomain::S, domain::S) where {S<:ElementarySpace} return HomSpace(⊗(codomain), ⊗(domain)) end -HomSpace(codomain::VectorSpace) = HomSpace(codomain, zero(codomain)) +HomSpace(codomain::VectorSpace) = HomSpace(codomain, zerospace(codomain)) codomain(W::HomSpace) = W.codomain domain(W::HomSpace) = W.domain diff --git a/src/spaces/vectorspaces.jl b/src/spaces/vectorspaces.jl index ce92889e0..ccf57a1b6 100644 --- a/src/spaces/vectorspaces.jl +++ b/src/spaces/vectorspaces.jl @@ -129,15 +129,18 @@ that this is different from `one(V::S)`, which returns the empty product space """ unitspace(V::ElementarySpace) = unitspace(typeof(V)) Base.oneunit(V::ElementarySpace) = unitspace(V) +#TODO: add for type """ - zero(V::S) where {S<:ElementarySpace} -> S + zerospace(V::S) where {S<:ElementarySpace} -> S Return the corresponding vector space of type `S` that represents the zero-dimensional or empty space. -This is, with a slight abuse of notation, the zero element of the direct sum of vector spaces. +This is, with a slight abuse of notation, the zero element of the direct sum of vector spaces. +`Base.zero` falls back to `zerospace`. """ -#TODO: zerospace? -Base.zero(V::ElementarySpace) = zero(typeof(V)) +zerospace(V::ElementarySpace) = zerospace(typeof(V)) +Base.zero(V::ElementarySpace) = zerospace(V) +Base.zero(::Type{V}) where {V<:ElementarySpace} = zerospace(V) """ ⊕(V₁::S, V₂::S, V₃::S...) where {S<:ElementarySpace} -> S diff --git a/test/spaces.jl b/test/spaces.jl index 5f0859b7d..f04d4af29 100644 --- a/test/spaces.jl +++ b/test/spaces.jl @@ -66,14 +66,14 @@ println("------------------------------------") @test length(sectors(V)) == 1 @test @constinferred(TensorKit.hassector(V, Trivial())) @test @constinferred(dim(V)) == d == @constinferred(dim(V, Trivial())) - @test dim(@constinferred(zero(V))) == 0 - @test (sectors(zero(V))...,) == () + @test dim(@constinferred(zerospace(V))) == 0 + @test (sectors(zerospace(V))...,) == () @test @constinferred(TensorKit.axes(V)) == Base.OneTo(d) @test ℝ^d == ℝ[](d) == CartesianSpace(d) == typeof(V)(d) W = @constinferred ℝ^1 @test @constinferred(unitspace(V)) == W == unitspace(typeof(V)) - @test @constinferred(zero(V)) == ℝ^0 == zero(typeof(V)) - @test @constinferred(⊕(V, zero(V))) == V + @test @constinferred(zerospace(V)) == ℝ^0 == zerospace(typeof(V)) + @test @constinferred(⊕(V, zerospace(V))) == V @test @constinferred(⊕(V, V)) == ℝ^(2d) @test @constinferred(⊕(V, unitspace(V))) == ℝ^(d + 1) @test @constinferred(⊕(V, V, V, V)) == ℝ^(4d) @@ -113,14 +113,14 @@ println("------------------------------------") @test length(sectors(V)) == 1 @test @constinferred(TensorKit.hassector(V, Trivial())) @test @constinferred(dim(V)) == d == @constinferred(dim(V, Trivial())) - @test dim(@constinferred(zero(V))) == 0 - @test (sectors(zero(V))...,) == () + @test dim(@constinferred(zerospace(V))) == 0 + @test (sectors(zerospace(V))...,) == () @test @constinferred(TensorKit.axes(V)) == Base.OneTo(d) @test ℂ^d == Vect[Trivial](d) == Vect[](Trivial() => d) == ℂ[](d) == typeof(V)(d) W = @constinferred ℂ^1 @test @constinferred(unitspace(V)) == W == unitspace(typeof(V)) - @test @constinferred(zero(V)) == ℂ^0 == zero(typeof(V)) - @test @constinferred(⊕(V, zero(V))) == V + @test @constinferred(zerospace(V)) == ℂ^0 == zerospace(typeof(V)) + @test @constinferred(⊕(V, zerospace(V))) == V @test @constinferred(⊕(V, V)) == ℂ^(2d) @test_throws SpaceMismatch (⊕(V, V')) # promote_except = ErrorException("promotion of types $(typeof(ℝ^d)) and " * @@ -204,12 +204,12 @@ println("------------------------------------") @test eval(Meta.parse(sprint(show, V))) == V @test eval(Meta.parse(sprint(show, typeof(V)))) == typeof(V) # space with no sectors - @test dim(@constinferred(zero(V))) == 0 + @test dim(@constinferred(zerospace(V))) == 0 # space with a single sector W = @constinferred GradedSpace(unit(I) => 1) @test W == GradedSpace(unit(I) => 1, randsector(I) => 0) @test @constinferred(unitspace(V)) == W == unitspace(typeof(V)) - @test @constinferred(zero(V)) == GradedSpace(unit(I) => 0) + @test @constinferred(zerospace(V)) == GradedSpace(unit(I) => 0) # randsector never returns trivial sector, so this cannot error @test_throws ArgumentError GradedSpace(unit(I) => 1, randsector(I) => 0, unit(I) => 3) @test eval(Meta.parse(sprint(show, W))) == W @@ -231,7 +231,7 @@ println("------------------------------------") if hasfusiontensor(I) @test @constinferred(TensorKit.axes(V)) == Base.OneTo(dim(V)) end - @test @constinferred(⊕(V, zero(V))) == V + @test @constinferred(⊕(V, zerospace(V))) == V @test @constinferred(⊕(V, V)) == Vect[I](c => 2dim(V, c) for c in sectors(V)) @test @constinferred(⊕(V, V, V, V)) == Vect[I](c => 4dim(V, c) for c in sectors(V)) @test @constinferred(⊕(V, unitspace(V))) == diff --git a/test/tensors.jl b/test/tensors.jl index 30526f2c1..2b9db49aa 100644 --- a/test/tensors.jl +++ b/test/tensors.jl @@ -86,7 +86,7 @@ for V in spacelist end end for T in (Int, Float32, ComplexF64) - t = randn(T, V1 ⊗ V2 ← zero(V1)) + t = randn(T, V1 ⊗ V2 ← zerospace(V1)) a = convert(Array, t) @test norm(a) == 0 end @@ -525,7 +525,7 @@ for V in spacelist end end @testset "empty tensor" begin - t = randn(T, V1 ⊗ V2, zero(V1)) + t = randn(T, V1 ⊗ V2, zerospace(V1)) @testset "leftorth with $alg" for alg in (TensorKit.QR(), TensorKit.QRpos(), TensorKit.QL(), TensorKit.QLpos(), @@ -565,7 +565,7 @@ for V in spacelist end @testset "cond and rank" begin @test rank(t) == 0 - W2 = zero(V1) * zero(V2) + W2 = zerospace(V1) * zerospace(V2) t2 = rand(W2, W2) @test rank(t2) == 0 @test cond(t2) == 0.0 From 9bdd09235a139795f9f47b639822774afb24fc8a Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Thu, 25 Sep 2025 16:35:07 +0200 Subject: [PATCH 07/95] add `oneunit` for type of space --- src/spaces/vectorspaces.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/spaces/vectorspaces.jl b/src/spaces/vectorspaces.jl index ccf57a1b6..280db814b 100644 --- a/src/spaces/vectorspaces.jl +++ b/src/spaces/vectorspaces.jl @@ -129,7 +129,7 @@ that this is different from `one(V::S)`, which returns the empty product space """ unitspace(V::ElementarySpace) = unitspace(typeof(V)) Base.oneunit(V::ElementarySpace) = unitspace(V) -#TODO: add for type +Base.oneunit(::Type{V}) where {V<:ElementarySpace} = unitspace(V) """ zerospace(V::S) where {S<:ElementarySpace} -> S From d2da757ca52d93651d2477d2d6e938804f38cda0 Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Thu, 25 Sep 2025 16:57:45 +0200 Subject: [PATCH 08/95] format --- test/fusiontrees.jl | 4 +++- test/spaces.jl | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/test/fusiontrees.jl b/test/fusiontrees.jl index 72cf9a2ea..5c7a360ed 100644 --- a/test/fusiontrees.jl +++ b/test/fusiontrees.jl @@ -4,6 +4,7 @@ println("------------------------------------") ti = time() @timedtestset "Fusion trees for $(TensorKit.type_repr(I))" verbose = true for I in sectorlist + Istr = TensorKit.type_repr(I) N = 5 out = ntuple(n -> randsector(I), N) @@ -548,7 +549,8 @@ ti = time() @testset "Double fusion tree $Istr: planar trace" begin d1 = transpose(f1, f1, (N + 1, 1:N..., ((2N):-1:(N + 3))...), (N + 2,)) f1front, = TK.split(f1, N - 1) - T = typeof(Fsymbol(unit(I), unit(I), unit(I), unit(I), unit(I), unit(I))[1, 1, 1, 1]) #TODO: change to _fscalartype + T = typeof(Fsymbol(unit(I), unit(I), unit(I), unit(I), unit(I), unit(I))[1, 1, 1, + 1]) #TODO: change to _fscalartype d2 = Dict{typeof((f1front, f1front)),T}() for ((f1′, f2′), coeff′) in d1 for ((f1′′, f2′′), coeff′′) in diff --git a/test/spaces.jl b/test/spaces.jl index f04d4af29..759634972 100644 --- a/test/spaces.jl +++ b/test/spaces.jl @@ -211,7 +211,8 @@ println("------------------------------------") @test @constinferred(unitspace(V)) == W == unitspace(typeof(V)) @test @constinferred(zerospace(V)) == GradedSpace(unit(I) => 0) # randsector never returns trivial sector, so this cannot error - @test_throws ArgumentError GradedSpace(unit(I) => 1, randsector(I) => 0, unit(I) => 3) + @test_throws ArgumentError GradedSpace(unit(I) => 1, randsector(I) => 0, + unit(I) => 3) @test eval(Meta.parse(sprint(show, W))) == W @test isa(V, VectorSpace) @test isa(V, ElementarySpace) From 084ab72e5cedf9e7306b04a9a0c38b9cceb4c6f6 Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Fri, 26 Sep 2025 12:13:28 +0200 Subject: [PATCH 09/95] minor changes from #263 --- src/fusiontrees/manipulations.jl | 4 ++-- test/fusiontrees.jl | 9 ++++----- test/runtests.jl | 2 +- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/fusiontrees/manipulations.jl b/src/fusiontrees/manipulations.jl index 3b428a262..c47deb99d 100644 --- a/src/fusiontrees/manipulations.jl +++ b/src/fusiontrees/manipulations.jl @@ -786,13 +786,13 @@ function elementary_trace(f::FusionTree{I,N}, i) where {I<:Sector,N} vertices_ = TupleTools.front(f.vertices) f_ = FusionTree(uncoupled_, coupled_, isdual_, inner_, vertices_) fs = FusionTree((b,), b, (!f.isdual[1],), (), ()) - for (f_′, coeff) in merge(fs, f_, unit, 1) # coloring gets reversed here, should be the other unit + for (f_′, coeff) in merge(fs, f_, unit, 1) f_′.innerlines[1] == unit || continue uncoupled′ = Base.tail(Base.tail(f_′.uncoupled)) isdual′ = Base.tail(Base.tail(f_′.isdual)) inner′ = N <= 4 ? () : Base.tail(Base.tail(f_′.innerlines)) vertices′ = N <= 3 ? () : Base.tail(Base.tail(f_′.vertices)) - f′ = FusionTree(uncoupled′, unit, isdual′, inner′, vertices′) # and this one? + f′ = FusionTree(uncoupled′, unit, isdual′, inner′, vertices′) coeff *= sqrtdim(b) if !(f.isdual[N]) coeff *= conj(frobeniusschur(b)) diff --git a/test/fusiontrees.jl b/test/fusiontrees.jl index 5c7a360ed..d97a5b830 100644 --- a/test/fusiontrees.jl +++ b/test/fusiontrees.jl @@ -212,7 +212,7 @@ ti = time() end end end - @testset "Fusion tree $Istr: elementy artin braid" begin + @testset "Fusion tree $Istr: elementary artin braid" begin N = length(out) isdual = ntuple(n -> rand(Bool), N) for in in ⊗(out...) @@ -269,7 +269,7 @@ ti = time() end end @testset "Fusion tree $Istr: braiding and permuting" begin - f = rand(collect(fusiontrees(out, in, isdual))) + f = rand(collect(it)) p = tuple(randperm(N)...) ip = invperm(p) @@ -380,7 +380,7 @@ ti = time() f1 = rand(collect(fusiontrees(out, incoming, ntuple(n -> rand(Bool), N)))) f2 = rand(collect(fusiontrees(out[randperm(N)], incoming, ntuple(n -> rand(Bool), N)))) - @testset "Double fusion tree $Istr: repartioning" begin + @testset "Double fusion tree $Istr: repartitioning" begin for n in 0:(2 * N) d = @constinferred TK.repartition(f1, f2, $n) @test dim(incoming) ≈ @@ -549,8 +549,7 @@ ti = time() @testset "Double fusion tree $Istr: planar trace" begin d1 = transpose(f1, f1, (N + 1, 1:N..., ((2N):-1:(N + 3))...), (N + 2,)) f1front, = TK.split(f1, N - 1) - T = typeof(Fsymbol(unit(I), unit(I), unit(I), unit(I), unit(I), unit(I))[1, 1, 1, - 1]) #TODO: change to _fscalartype + T = sectorscalartype(I) d2 = Dict{typeof((f1front, f1front)),T}() for ((f1′, f2′), coeff′) in d1 for ((f1′′, f2′′), coeff′′) in diff --git a/test/runtests.jl b/test/runtests.jl index 42523270c..88dbcd6cb 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -3,7 +3,7 @@ using TestExtras using Random using TensorKit using Combinatorics -using TensorKit: ProductSector, fusiontensor, pentagon_equation, hexagon_equation +using TensorKit: ProductSector, fusiontensor using TensorOperations using Base.Iterators: take, product # using SUNRepresentations: SUNIrrep From d4d6fe7dc6b177cea9e6897a0173f754eadee0e3 Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Fri, 26 Sep 2025 12:22:09 +0200 Subject: [PATCH 10/95] use the const `TK` in tests where appropriate --- test/ad.jl | 28 ++++++++++----------- test/diagonal.jl | 8 +++--- test/fusiontrees.jl | 10 ++++---- test/spaces.jl | 34 ++++++++++++------------- test/tensors.jl | 60 ++++++++++++++++++++++----------------------- 5 files changed, 70 insertions(+), 70 deletions(-) diff --git a/test/ad.jl b/test/ad.jl index e5e2d884d..7474a4def 100644 --- a/test/ad.jl +++ b/test/ad.jl @@ -29,7 +29,7 @@ function ChainRulesTestUtils.test_approx(actual::AbstractTensorMap, end # make sure that norms are computed correctly: -function FiniteDifferences.to_vec(t::TensorKit.SectorDict) +function FiniteDifferences.to_vec(t::TK.SectorDict) T = scalartype(valtype(t)) vec = mapreduce(vcat, t; init=T[]) do (c, b) return reshape(b, :) .* sqrt(dim(c)) @@ -39,7 +39,7 @@ function FiniteDifferences.to_vec(t::TensorKit.SectorDict) function from_vec(x_real) x = T <: Real ? x_real : reinterpret(T, x_real) ctr = 0 - return TensorKit.SectorDict(c => (n = length(b); + return TK.SectorDict(c => (n = length(b); b′ = reshape(view(x, ctr .+ (1:n)), size(b)) ./ sqrt(dim(c)); ctr += n; @@ -61,25 +61,25 @@ end # rrules for functions that destroy inputs # ---------------------------------------- -function ChainRulesCore.rrule(::typeof(TensorKit.tsvd), args...; kwargs...) +function ChainRulesCore.rrule(::typeof(TK.tsvd), args...; kwargs...) return ChainRulesCore.rrule(tsvd!, args...; kwargs...) end function ChainRulesCore.rrule(::typeof(LinearAlgebra.svdvals), args...; kwargs...) return ChainRulesCore.rrule(svdvals!, args...; kwargs...) end -function ChainRulesCore.rrule(::typeof(TensorKit.eig), args...; kwargs...) +function ChainRulesCore.rrule(::typeof(TK.eig), args...; kwargs...) return ChainRulesCore.rrule(eig!, args...; kwargs...) end -function ChainRulesCore.rrule(::typeof(TensorKit.eigh), args...; kwargs...) +function ChainRulesCore.rrule(::typeof(TK.eigh), args...; kwargs...) return ChainRulesCore.rrule(eigh!, args...; kwargs...) end function ChainRulesCore.rrule(::typeof(LinearAlgebra.eigvals), args...; kwargs...) return ChainRulesCore.rrule(eigvals!, args...; kwargs...) end -function ChainRulesCore.rrule(::typeof(TensorKit.leftorth), args...; kwargs...) +function ChainRulesCore.rrule(::typeof(TK.leftorth), args...; kwargs...) return ChainRulesCore.rrule(leftorth!, args...; kwargs...) end -function ChainRulesCore.rrule(::typeof(TensorKit.rightorth), args...; kwargs...) +function ChainRulesCore.rrule(::typeof(TK.rightorth), args...; kwargs...) return ChainRulesCore.rrule(rightorth!, args...; kwargs...) end @@ -134,7 +134,7 @@ Vlist = ((ℂ^2, (ℂ^3)', ℂ^3, ℂ^2, (ℂ^2)'), ℂ[FibonacciAnyon](:I => 2, :τ => 3), ℂ[FibonacciAnyon](:I => 2, :τ => 2))) -@timedtestset "Automatic Differentiation with spacetype $(TensorKit.type_repr(eltype(V)))" verbose = true for V in +@timedtestset "Automatic Differentiation with spacetype $(TK.type_repr(eltype(V)))" verbose = true for V in Vlist eltypes = isreal(sectortype(eltype(V))) ? (Float64, ComplexF64) : (ComplexF64,) symmetricbraiding = BraidingStyle(sectortype(eltype(V))) isa SymmetricBraiding @@ -149,9 +149,9 @@ Vlist = ((ℂ^2, (ℂ^3)', ℂ^3, ℂ^2, (ℂ^2)'), test_rrule(copy, T1) test_rrule(copy, T2) - test_rrule(TensorKit.copy_oftype, T1, ComplexF64) + test_rrule(TK.copy_oftype, T1, ComplexF64) if symmetricbraiding - test_rrule(TensorKit.permutedcopy_oftype, T1, ComplexF64, ((3, 1), (2, 4))) + test_rrule(TK.permutedcopy_oftype, T1, ComplexF64, ((3, 1), (2, 4))) test_rrule(convert, Array, T1) test_rrule(TensorMap, convert(Array, T1), codomain(T1), domain(T1); @@ -364,13 +364,13 @@ Vlist = ((ℂ^2, (ℂ^3)', ℂ^3, ℂ^2, (ℂ^2)'), H = (H + H') / 2 atol = precision(T) - for alg in (TensorKit.QR(), TensorKit.QRpos()) + for alg in (TK.QR(), TK.QRpos()) test_rrule(leftorth, A; fkwargs=(; alg=alg), atol) test_rrule(leftorth, B; fkwargs=(; alg=alg), atol) test_rrule(leftorth, C; fkwargs=(; alg=alg), atol) end - for alg in (TensorKit.LQ(), TensorKit.LQpos()) + for alg in (TK.LQ(), TK.LQpos()) test_rrule(rightorth, A; fkwargs=(; alg=alg), atol) test_rrule(rightorth, B; fkwargs=(; alg=alg), atol) test_rrule(rightorth, C; fkwargs=(; alg=alg), atol) @@ -428,7 +428,7 @@ Vlist = ((ℂ^2, (ℂ^3)', ℂ^3, ℂ^2, (ℂ^2)'), T <: Complex && remove_svdgauge_depence!(ΔU, ΔV, U, S, V) test_rrule(tsvd, B; atol, output_tangent=(ΔU, ΔS, ΔV, 0.0)) - Vtrunc = spacetype(S)(TensorKit.SectorDict(c => ceil(Int, size(b, 1) / 2) + Vtrunc = spacetype(S)(TK.SectorDict(c => ceil(Int, size(b, 1) / 2) for (c, b) in blocks(S))) U, S, V, ϵ = tsvd(B; trunc=truncspace(Vtrunc)) @@ -447,7 +447,7 @@ Vlist = ((ℂ^2, (ℂ^3)', ℂ^3, ℂ^2, (ℂ^2)'), T <: Complex && remove_svdgauge_depence!(ΔU, ΔV, U, S, V) test_rrule(tsvd, C; atol, output_tangent=(ΔU, ΔS, ΔV, 0.0)) - c, = TensorKit.MatrixAlgebra._argmax(x -> sqrt(dim(x[1])) * maximum(diag(x[2])), + c, = TK.MatrixAlgebra._argmax(x -> sqrt(dim(x[1])) * maximum(diag(x[2])), blocks(S)) trunc = truncdim(round(Int, 2 * dim(c))) U, S, V, ϵ = tsvd(C; trunc) diff --git a/test/diagonal.jl b/test/diagonal.jl index e5fcaa430..3eebd2e58 100644 --- a/test/diagonal.jl +++ b/test/diagonal.jl @@ -30,7 +30,7 @@ diagspacelist = ((ℂ^4)', ℂ[Z2Irrep](0 => 2, 1 => 3), b2 = @constinferred block(t, first(blocksectors(t))) @test b1 == b2 @test eltype(bs) === Pair{typeof(c),typeof(b1)} - @test typeof(b1) === TensorKit.blocktype(t) + @test typeof(b1) === TK.blocktype(t) # basic linear algebra @test isa(@constinferred(norm(t)), real(T)) @test norm(t)^2 ≈ dot(t, t) @@ -201,7 +201,7 @@ diagspacelist = ((ℂ^4)', ℂ[Z2Irrep](0 => 2, 1 => 3), zip(values(LinearAlgebra.eigvals(D)), values(LinearAlgebra.eigvals(t)))) end - @testset "leftorth with $alg" for alg in (TensorKit.QR(), TensorKit.QL()) + @testset "leftorth with $alg" for alg in (TK.QR(), TK.QL()) Q, R = @constinferred leftorth(t; alg=alg) QdQ = Q' * Q @test QdQ ≈ one(QdQ) @@ -210,7 +210,7 @@ diagspacelist = ((ℂ^4)', ℂ[Z2Irrep](0 => 2, 1 => 3), @test isposdef(R) end end - @testset "rightorth with $alg" for alg in (TensorKit.RQ(), TensorKit.LQ()) + @testset "rightorth with $alg" for alg in (TK.RQ(), TK.LQ()) L, Q = @constinferred rightorth(t; alg=alg) QQd = Q * Q' @test QQd ≈ one(QQd) @@ -219,7 +219,7 @@ diagspacelist = ((ℂ^4)', ℂ[Z2Irrep](0 => 2, 1 => 3), @test isposdef(L) end end - @testset "tsvd with $alg" for alg in (TensorKit.SVD(), TensorKit.SDD()) + @testset "tsvd with $alg" for alg in (TK.SVD(), TK.SDD()) U, S, Vᴴ = @constinferred tsvd(t; alg=alg) UdU = U' * U @test UdU ≈ one(UdU) diff --git a/test/fusiontrees.jl b/test/fusiontrees.jl index d97a5b830..f7ec5d38e 100644 --- a/test/fusiontrees.jl +++ b/test/fusiontrees.jl @@ -2,10 +2,10 @@ println("------------------------------------") println("Fusion Trees") println("------------------------------------") ti = time() -@timedtestset "Fusion trees for $(TensorKit.type_repr(I))" verbose = true for I in +@timedtestset "Fusion trees for $(TK.type_repr(I))" verbose = true for I in sectorlist - Istr = TensorKit.type_repr(I) + Istr = TK.type_repr(I) N = 5 out = ntuple(n -> randsector(I), N) isdual = ntuple(n -> rand(Bool), N) @@ -433,12 +433,12 @@ ti = time() ip = invperm(p) ip1, ip2 = ip[1:N], ip[(N + 1):(2N)] - d = @constinferred TensorKit.permute(f1, f2, p1, p2) + d = @constinferred TK.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) + d′ = TK.permute(f1′, f2′, ip1, ip2) for ((f1′′, f2′′), coeff2) in d′ d2[(f1′′, f2′′)] = get(d2, (f1′′, f2′′), zero(coeff)) + coeff2 * coeff @@ -567,7 +567,7 @@ ti = time() end end end - TensorKit.empty_globalcaches!() + TK.empty_globalcaches!() end tf = time() printstyled("Finished fusion tree tests in ", diff --git a/test/spaces.jl b/test/spaces.jl index 759634972..5fd77b06b 100644 --- a/test/spaces.jl +++ b/test/spaces.jl @@ -64,11 +64,11 @@ println("------------------------------------") @test @constinferred(sectortype(V)) == Trivial @test ((@constinferred sectors(V))...,) == (Trivial(),) @test length(sectors(V)) == 1 - @test @constinferred(TensorKit.hassector(V, Trivial())) + @test @constinferred(TK.hassector(V, Trivial())) @test @constinferred(dim(V)) == d == @constinferred(dim(V, Trivial())) @test dim(@constinferred(zerospace(V))) == 0 @test (sectors(zerospace(V))...,) == () - @test @constinferred(TensorKit.axes(V)) == Base.OneTo(d) + @test @constinferred(TK.axes(V)) == Base.OneTo(d) @test ℝ^d == ℝ[](d) == CartesianSpace(d) == typeof(V)(d) W = @constinferred ℝ^1 @test @constinferred(unitspace(V)) == W == unitspace(typeof(V)) @@ -111,11 +111,11 @@ println("------------------------------------") @test @constinferred(sectortype(V)) == Trivial @test ((@constinferred sectors(V))...,) == (Trivial(),) @test length(sectors(V)) == 1 - @test @constinferred(TensorKit.hassector(V, Trivial())) + @test @constinferred(TK.hassector(V, Trivial())) @test @constinferred(dim(V)) == d == @constinferred(dim(V, Trivial())) @test dim(@constinferred(zerospace(V))) == 0 @test (sectors(zerospace(V))...,) == () - @test @constinferred(TensorKit.axes(V)) == Base.OneTo(d) + @test @constinferred(TK.axes(V)) == Base.OneTo(d) @test ℂ^d == Vect[Trivial](d) == Vect[](Trivial() => d) == ℂ[](d) == typeof(V)(d) W = @constinferred ℂ^1 @test @constinferred(unitspace(V)) == W == unitspace(typeof(V)) @@ -153,10 +153,10 @@ println("------------------------------------") @test isdual(V') @test !isdual(conj(V)) @test isdual(conj(V')) - @test !TensorKit.isconj(V) - @test !TensorKit.isconj(V') - @test TensorKit.isconj(conj(V)) - @test TensorKit.isconj(conj(V')) + @test !TK.isconj(V) + @test !TK.isconj(V') + @test TK.isconj(conj(V)) + @test TK.isconj(conj(V')) @test isa(V, VectorSpace) @test isa(V, ElementarySpace) @test !isa(InnerProductStyle(V), HasInnerProduct) @@ -165,12 +165,12 @@ println("------------------------------------") @test @constinferred(dual(V)) != @constinferred(conj(V)) != V @test @constinferred(field(V)) == ℂ @test @constinferred(sectortype(V)) == Trivial - @test @constinferred(TensorKit.hassector(V, Trivial())) + @test @constinferred(TK.hassector(V, Trivial())) @test @constinferred(dim(V)) == d == @constinferred(dim(V, Trivial())) - @test @constinferred(TensorKit.axes(V)) == Base.OneTo(d) + @test @constinferred(TK.axes(V)) == Base.OneTo(d) end - @timedtestset "ElementarySpace: $(TensorKit.type_repr(Vect[I]))" for I in sectorlist + @timedtestset "ElementarySpace: $(TK.type_repr(Vect[I]))" for I in sectorlist if Base.IteratorSize(values(I)) === Base.IsInfinite() set = unique(vcat(unit(I), [randsector(I) for k in 1:10])) gen = (c => 2 for c in set) @@ -178,7 +178,7 @@ println("------------------------------------") gen = (values(I)[k] => (k + 1) for k in 1:length(values(I))) end V = GradedSpace(gen) - @test eval(Meta.parse(TensorKit.type_repr(typeof(V)))) == typeof(V) + @test eval(Meta.parse(TK.type_repr(typeof(V)))) == typeof(V) @test eval(Meta.parse(sprint(show, V))) == V @test eval(Meta.parse(sprint(show, V'))) == V' @test V' == GradedSpace(gen; dual=true) @@ -225,12 +225,12 @@ println("------------------------------------") @test @constinferred(field(V)) == ℂ @test @constinferred(sectortype(V)) == I slist = @constinferred sectors(V) - @test @constinferred(TensorKit.hassector(V, first(slist))) + @test @constinferred(TK.hassector(V, first(slist))) @test @constinferred(dim(V)) == sum(dim(s) * dim(V, s) for s in slist) @test @constinferred(reduceddim(V)) == sum(dim(V, s) for s in slist) @constinferred dim(V, first(slist)) if hasfusiontensor(I) - @test @constinferred(TensorKit.axes(V)) == Base.OneTo(dim(V)) + @test @constinferred(TK.axes(V)) == Base.OneTo(dim(V)) end @test @constinferred(⊕(V, zerospace(V))) == V @test @constinferred(⊕(V, V)) == Vect[I](c => 2dim(V, c) for c in sectors(V)) @@ -407,7 +407,7 @@ println("------------------------------------") @timedtestset "HomSpace" begin for (V1, V2, V3, V4, V5) in (Vtr, Vℤ₃, VSU₂) - W = TensorKit.HomSpace(V1 ⊗ V2, V3 ⊗ V4 ⊗ V5) + W = TK.HomSpace(V1 ⊗ V2, V3 ⊗ V4 ⊗ V5) @test W == (V3 ⊗ V4 ⊗ V5 → V1 ⊗ V2) @test W == (V1 ⊗ V2 ← V3 ⊗ V4 ⊗ V5) @test W' == (V1 ⊗ V2 → V3 ⊗ V4 ⊗ V5) @@ -424,7 +424,7 @@ println("------------------------------------") @test W == deepcopy(W) @test W == @constinferred permute(W, ((1, 2), (3, 4, 5))) @test permute(W, ((2, 4, 5), (3, 1))) == (V2 ⊗ V4' ⊗ V5' ← V3 ⊗ V1') - @test (V1 ⊗ V2 ← V1 ⊗ V2) == @constinferred TensorKit.compose(W, W') + @test (V1 ⊗ V2 ← V1 ⊗ V2) == @constinferred TK.compose(W, W') @test (V1 ⊗ V2 ← V3 ⊗ V4 ⊗ V5 ⊗ unitspace(V5)) == @constinferred(insertleftunit(W)) == @constinferred(insertrightunit(W)) @@ -444,5 +444,5 @@ println("------------------------------------") @test_throws BoundsError insertleftunit(one(V1) ← V1, 0) end end - TensorKit.empty_globalcaches!() + TK.empty_globalcaches!() end diff --git a/test/tensors.jl b/test/tensors.jl index 2b9db49aa..26f0bbd8f 100644 --- a/test/tensors.jl +++ b/test/tensors.jl @@ -23,7 +23,7 @@ end for V in spacelist I = sectortype(first(V)) - Istr = TensorKit.type_repr(I) + Istr = TK.type_repr(I) println("---------------------------------------") println("Tensors with symmetry: $Istr") println("---------------------------------------") @@ -48,7 +48,7 @@ for V in spacelist b2 = @constinferred block(t, first(blocksectors(t))) @test b1 == b2 @test eltype(bs) === Pair{typeof(c),typeof(b1)} - @test typeof(b1) === TensorKit.blocktype(t) + @test typeof(b1) === TK.blocktype(t) @test typeof(c) === sectortype(t) end end @@ -110,7 +110,7 @@ for V in spacelist b2 = @constinferred block(t', first(blocksectors(t'))) @test b1 == b2 @test eltype(bs) === Pair{typeof(c),typeof(b1)} - @test typeof(b1) === TensorKit.blocktype(t') + @test typeof(b1) === TK.blocktype(t') @test typeof(c) === sectortype(t) # linear algebra @test isa(@constinferred(norm(t)), real(T)) @@ -446,10 +446,10 @@ for V in spacelist ts = (rand(T, W), rand(T, W)') for t in ts @testset "leftorth with $alg" for alg in - (TensorKit.QR(), TensorKit.QRpos(), - TensorKit.QL(), TensorKit.QLpos(), - TensorKit.Polar(), TensorKit.SVD(), - TensorKit.SDD()) + (TK.QR(), TK.QRpos(), + TK.QL(), TK.QLpos(), + TK.Polar(), TK.SVD(), + TK.SDD()) Q, R = @constinferred leftorth(t, ((3, 4, 2), (1, 5)); alg=alg) QdQ = Q' * Q @test QdQ ≈ one(QdQ) @@ -460,8 +460,8 @@ for V in spacelist end end @testset "leftnull with $alg" for alg in - (TensorKit.QR(), TensorKit.SVD(), - TensorKit.SDD()) + (TK.QR(), TK.SVD(), + TK.SDD()) N = @constinferred leftnull(t, ((3, 4, 2), (1, 5)); alg=alg) NdN = N' * N @test NdN ≈ one(NdN) @@ -469,10 +469,10 @@ for V in spacelist 100 * eps(norm(t)) end @testset "rightorth with $alg" for alg in - (TensorKit.RQ(), TensorKit.RQpos(), - TensorKit.LQ(), TensorKit.LQpos(), - TensorKit.Polar(), TensorKit.SVD(), - TensorKit.SDD()) + (TK.RQ(), TK.RQpos(), + TK.LQ(), TK.LQpos(), + TK.Polar(), TK.SVD(), + TK.SDD()) L, Q = @constinferred rightorth(t, ((3, 4), (2, 1, 5)); alg=alg) QQd = Q * Q' @test QQd ≈ one(QQd) @@ -483,15 +483,15 @@ for V in spacelist end end @testset "rightnull with $alg" for alg in - (TensorKit.LQ(), TensorKit.SVD(), - TensorKit.SDD()) + (TK.LQ(), TK.SVD(), + TK.SDD()) M = @constinferred rightnull(t, ((3, 4), (2, 1, 5)); alg=alg) MMd = M * M' @test MMd ≈ one(MMd) @test norm(permute(t, ((3, 4), (2, 1, 5))) * M') < 100 * eps(norm(t)) end - @testset "tsvd with $alg" for alg in (TensorKit.SVD(), TensorKit.SDD()) + @testset "tsvd with $alg" for alg in (TK.SVD(), TK.SDD()) U, S, V = @constinferred tsvd(t, ((3, 4, 2), (1, 5)); alg=alg) UdU = U' * U @test UdU ≈ one(UdU) @@ -527,38 +527,38 @@ for V in spacelist @testset "empty tensor" begin t = randn(T, V1 ⊗ V2, zerospace(V1)) @testset "leftorth with $alg" for alg in - (TensorKit.QR(), TensorKit.QRpos(), - TensorKit.QL(), TensorKit.QLpos(), - TensorKit.Polar(), TensorKit.SVD(), - TensorKit.SDD()) + (TK.QR(), TK.QRpos(), + TK.QL(), TK.QLpos(), + TK.Polar(), TK.SVD(), + TK.SDD()) Q, R = @constinferred leftorth(t; alg=alg) @test Q == t @test dim(Q) == dim(R) == 0 end @testset "leftnull with $alg" for alg in - (TensorKit.QR(), TensorKit.SVD(), - TensorKit.SDD()) + (TK.QR(), TK.SVD(), + TK.SDD()) N = @constinferred leftnull(t; alg=alg) @test N' * N ≈ id(domain(N)) @test N * N' ≈ id(codomain(N)) end @testset "rightorth with $alg" for alg in - (TensorKit.RQ(), TensorKit.RQpos(), - TensorKit.LQ(), TensorKit.LQpos(), - TensorKit.Polar(), TensorKit.SVD(), - TensorKit.SDD()) + (TK.RQ(), TK.RQpos(), + TK.LQ(), TK.LQpos(), + TK.Polar(), TK.SVD(), + TK.SDD()) L, Q = @constinferred rightorth(copy(t'); alg=alg) @test Q == t' @test dim(Q) == dim(L) == 0 end @testset "rightnull with $alg" for alg in - (TensorKit.LQ(), TensorKit.SVD(), - TensorKit.SDD()) + (TK.LQ(), TK.SVD(), + TK.SDD()) M = @constinferred rightnull(copy(t'); alg=alg) @test M * M' ≈ id(codomain(M)) @test M' * M ≈ id(domain(M)) end - @testset "tsvd with $alg" for alg in (TensorKit.SVD(), TensorKit.SDD()) + @testset "tsvd with $alg" for alg in (TK.SVD(), TK.SDD()) U, S, V = @constinferred tsvd(t; alg=alg) @test U == t @test dim(U) == dim(S) == dim(V) @@ -761,7 +761,7 @@ for V in spacelist @test t3 ≈ t4 end end - TensorKit.empty_globalcaches!() + TK.empty_globalcaches!() end @timedtestset "Deligne tensor product: test via conversion" begin From 26c6c8389e1bf84bc2e04960d26050ccc1bf81cc Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Fri, 26 Sep 2025 12:24:02 +0200 Subject: [PATCH 11/95] format --- test/ad.jl | 16 ++++++++-------- test/fusiontrees.jl | 3 +-- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/test/ad.jl b/test/ad.jl index 7474a4def..efcc1d7bf 100644 --- a/test/ad.jl +++ b/test/ad.jl @@ -40,11 +40,11 @@ function FiniteDifferences.to_vec(t::TK.SectorDict) x = T <: Real ? x_real : reinterpret(T, x_real) ctr = 0 return TK.SectorDict(c => (n = length(b); - b′ = reshape(view(x, ctr .+ (1:n)), size(b)) ./ - sqrt(dim(c)); - ctr += n; - b′) - for (c, b) in t) + b′ = reshape(view(x, ctr .+ (1:n)), size(b)) ./ + sqrt(dim(c)); + ctr += n; + b′) + for (c, b) in t) end return vec_real, from_vec end @@ -135,7 +135,7 @@ Vlist = ((ℂ^2, (ℂ^3)', ℂ^3, ℂ^2, (ℂ^2)'), ℂ[FibonacciAnyon](:I => 2, :τ => 2))) @timedtestset "Automatic Differentiation with spacetype $(TK.type_repr(eltype(V)))" verbose = true for V in - Vlist + Vlist eltypes = isreal(sectortype(eltype(V))) ? (Float64, ComplexF64) : (ComplexF64,) symmetricbraiding = BraidingStyle(sectortype(eltype(V))) isa SymmetricBraiding @@ -429,7 +429,7 @@ Vlist = ((ℂ^2, (ℂ^3)', ℂ^3, ℂ^2, (ℂ^2)'), test_rrule(tsvd, B; atol, output_tangent=(ΔU, ΔS, ΔV, 0.0)) Vtrunc = spacetype(S)(TK.SectorDict(c => ceil(Int, size(b, 1) / 2) - for (c, b) in blocks(S))) + for (c, b) in blocks(S))) U, S, V, ϵ = tsvd(B; trunc=truncspace(Vtrunc)) ΔU = randn(scalartype(U), space(U)) @@ -448,7 +448,7 @@ Vlist = ((ℂ^2, (ℂ^3)', ℂ^3, ℂ^2, (ℂ^2)'), test_rrule(tsvd, C; atol, output_tangent=(ΔU, ΔS, ΔV, 0.0)) c, = TK.MatrixAlgebra._argmax(x -> sqrt(dim(x[1])) * maximum(diag(x[2])), - blocks(S)) + blocks(S)) trunc = truncdim(round(Int, 2 * dim(c))) U, S, V, ϵ = tsvd(C; trunc) ΔU = randn(scalartype(U), space(U)) diff --git a/test/fusiontrees.jl b/test/fusiontrees.jl index f7ec5d38e..f2e71d602 100644 --- a/test/fusiontrees.jl +++ b/test/fusiontrees.jl @@ -3,8 +3,7 @@ println("Fusion Trees") println("------------------------------------") ti = time() @timedtestset "Fusion trees for $(TK.type_repr(I))" verbose = true for I in - sectorlist - + sectorlist Istr = TK.type_repr(I) N = 5 out = ntuple(n -> randsector(I), N) From 21b3bc4a72830dd4c6ada30aa9f34a2e576181bd Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Fri, 26 Sep 2025 12:54:00 +0200 Subject: [PATCH 12/95] `otimes` between tensormaps to account for `sectorscalartype` --- src/tensors/linalg.jl | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/tensors/linalg.jl b/src/tensors/linalg.jl index 35c473a02..5015a6dc0 100644 --- a/src/tensors/linalg.jl +++ b/src/tensors/linalg.jl @@ -558,10 +558,17 @@ function ⊗(t1::AbstractTensorMap, t2::AbstractTensorMap) throw(SpaceMismatch("spacetype(t1) ≠ spacetype(t2)")) cod1, cod2 = codomain(t1), codomain(t2) dom1, dom2 = domain(t1), domain(t2) - cod = cod1 ⊗ cod2 - dom = dom1 ⊗ dom2 + p12 = ((codomainind(t1)..., (codomainind(t2) .+ numind(t1))...), + (domainind(t1)..., (domainind(t2) .+ numind(t1))...)) + T = promote_type(scalartype(t1), scalartype(t2)) - t = zerovector!(similar(t1, T, cod ← dom)) + TC = promote_type(sectorscalartype(sectortype(t1)), T) + t = TO.tensoralloc_contract(TC, + t1, ((codomainind(t1)..., domainind(t1)...), ()), false, + t2, ((), (codomainind(t2)..., domainind(t2)...)), false, + p12, Val(false)) + + zerovector!(t) for (f1l, f1r) in fusiontrees(t1) for (f2l, f2r) in fusiontrees(t2) c1 = f1l.coupled # = f1r.coupled From d159d77acd25dd254a26e5626e764444253554f0 Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Tue, 30 Sep 2025 11:43:30 +0200 Subject: [PATCH 13/95] generalise `unitspace` and `zerospace` --- src/spaces/gradedspace.jl | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/spaces/gradedspace.jl b/src/spaces/gradedspace.jl index 39f97c4b8..ea75b4520 100644 --- a/src/spaces/gradedspace.jl +++ b/src/spaces/gradedspace.jl @@ -131,8 +131,20 @@ function Base.axes(V::GradedSpace{I}, c::I) where {I<:Sector} return (offset + 1):(offset + dim(c) * dim(V, c)) end -unitspace(S::Type{<:GradedSpace{I}}) where {I<:Sector} = S(unit(I) => 1) -zerospace(S::Type{<:GradedSpace{I}}) where {I<:Sector} = S(unit(I) => 0) +function unitspace(S::Type{<:GradedSpace{I}}) where {I<:Sector} + if UnitStyle(I) isa SimpleUnit + return S(unit(I) => 1) + else + return S(unit => 1 for unit in allunits(I)) + end +end +function zerospace(S::Type{<:GradedSpace{I}}) where {I<:Sector} + if UnitStyle(I) isa SimpleUnit + return S(unit(I) => 0) + else + return S(unit => 0 for unit in allunits(I)) + end +end # TODO: the following methods can probably be implemented more efficiently for # `FiniteGradedSpace`, but we don't expect them to be used often in hot loops, so From a0e474faf938c09faa9acc4a4cf602ca5d7f0c1e Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Tue, 30 Sep 2025 11:44:23 +0200 Subject: [PATCH 14/95] have `dim` of graded space depend on sectorscalartype --- src/spaces/gradedspace.jl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/spaces/gradedspace.jl b/src/spaces/gradedspace.jl index ea75b4520..72467bd38 100644 --- a/src/spaces/gradedspace.jl +++ b/src/spaces/gradedspace.jl @@ -91,8 +91,9 @@ Base.hash(V::GradedSpace, h::UInt) = hash(V.dual, hash(V.dims, h)) field(::Type{<:GradedSpace}) = ℂ InnerProductStyle(::Type{<:GradedSpace}) = EuclideanInnerProduct() function dim(V::GradedSpace) + T = Base.promote_op(*, Int, real(sectorscalartype(sectortype(V)))) return reduce(+, dim(V, c) * dim(c) for c in sectors(V); - init=zero(dim(unit(sectortype(V))))) + init=zero(T)) end function dim(V::GradedSpace{I,<:AbstractDict}, c::I) where {I<:Sector} return get(V.dims, isdual(V) ? dual(c) : c, 0) From 732b8d1be49d7a2d567ee669835f33e4cfb12eb2 Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Tue, 30 Sep 2025 11:45:16 +0200 Subject: [PATCH 15/95] introduce `left/rightunitspace` --- src/spaces/gradedspace.jl | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/src/spaces/gradedspace.jl b/src/spaces/gradedspace.jl index 72467bd38..57bcd4180 100644 --- a/src/spaces/gradedspace.jl +++ b/src/spaces/gradedspace.jl @@ -132,6 +132,44 @@ function Base.axes(V::GradedSpace{I}, c::I) where {I<:Sector} return (offset + 1):(offset + dim(c) * dim(V, c)) end +""" + leftunitspace(S::GradedSpace{I}) where {I<:Sector} -> GradedSpace{I} + +Return the corresponding vector space of type `GradedSpace{I}` that represents the trivial +one-dimensional space consisting of the left unit of the objects in `Sector` `I`. +""" +function leftunitspace(S::GradedSpace{I}) where {I<:Sector} + if UnitStyle(I) isa SimpleUnit + return unitspace(typeof(S)) + else + !isempty(sectors(S)) || throw(ArgumentError("Cannot determine type of empty space")) + allequal(a.row for a in sectors(S)) || + throw(ArgumentError("sectors of $S do not have the same left unit")) + + sector = leftunit(first(sectors(S))) + return spacetype(S)(sector => 1) + end +end + +""" + rightunitspace(S::GradedSpace{I}) where {I<:Sector} -> GradedSpace{I} + +Return the corresponding vector space of type `GradedSpace{I}` that represents the trivial +one-dimensional space consisting of the right unit of the objects in `Sector` `I`. +""" +function rightunitspace(S::GradedSpace{I}) where {I<:Sector} + if UnitStyle(I) isa SimpleUnit + return unitspace(typeof(S)) + else + !isempty(sectors(S)) || throw(ArgumentError("Cannot determine type of empty space")) + allequal(a.row for a in sectors(S)) || + throw(ArgumentError("sectors of $S do not have the same right unit")) + + sector = rightunit(first(sectors(S))) + return spacetype(S)(sector => 1) + end +end + function unitspace(S::Type{<:GradedSpace{I}}) where {I<:Sector} if UnitStyle(I) isa SimpleUnit return S(unit(I) => 1) From 28952fcc26ea14a0496909b7f6e07d6ace50f4b6 Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Tue, 30 Sep 2025 11:46:02 +0200 Subject: [PATCH 16/95] generalise `blocksectors` of homspace --- src/spaces/homspace.jl | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/spaces/homspace.jl b/src/spaces/homspace.jl index 8a46c18a1..029b3ca40 100644 --- a/src/spaces/homspace.jl +++ b/src/spaces/homspace.jl @@ -94,11 +94,16 @@ function blocksectors(W::HomSpace) N₁ = length(codom) N₂ = length(dom) I = sectortype(W) - # TODO: is sort! still necessary now that blocksectors of ProductSpace is sorted? - if N₂ <= N₁ - return sort!(filter!(c -> hasblock(codom, c), blocksectors(dom))) + if N₁ == 0 && N₂ == 0 + return allunits(I) + elseif N₁ == 0 + return filter!(isone, collect(blocksectors(dom))) # module space cannot end in empty space + elseif N₂ == 0 + return filter!(isone, collect(blocksectors(codom))) + elseif N₂ <= N₁ + return filter!(c -> hasblock(codom, c), collect(blocksectors(dom))) else - return sort!(filter!(c -> hasblock(dom, c), blocksectors(codom))) + return filter!(c -> hasblock(dom, c), collect(blocksectors(codom))) end end From bde5b9c390a04929cef5d8165d3ce9c28f0ffc56 Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Tue, 30 Sep 2025 11:46:14 +0200 Subject: [PATCH 17/95] generalise `scalar` --- src/tensors/tensoroperations.jl | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/tensors/tensoroperations.jl b/src/tensors/tensoroperations.jl index 6110093b6..1c2e573fa 100644 --- a/src/tensors/tensoroperations.jl +++ b/src/tensors/tensoroperations.jl @@ -336,8 +336,9 @@ end # Scalar implementation #----------------------- -function scalar(t::AbstractTensorMap) - # TODO: should scalar only work if N₁ == N₂ == 0? - return dim(codomain(t)) == dim(domain(t)) == 1 ? - first(blocks(t))[2][1, 1] : throw(DimensionMismatch()) +function scalar(t::AbstractTensorMap{T, S, 0, 0}) where {T, S} + Bs = collect(blocks(t)) + inds = findall(!iszero ∘ last, Bs) + isempty(inds) && return zero(scalartype(t)) + return only(last(Bs[only(inds)])) end From 483805497dd5c3ea9afed66e46fbe862394d7842 Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Tue, 30 Sep 2025 12:02:08 +0200 Subject: [PATCH 18/95] some exports --- src/TensorKit.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/TensorKit.jl b/src/TensorKit.jl index 616cc4f23..de9044243 100644 --- a/src/TensorKit.jl +++ b/src/TensorKit.jl @@ -16,11 +16,11 @@ export BraidingStyle, SymmetricBraiding, Bosonic, Fermionic, Anyonic, NoBraiding export Trivial, Z2Irrep, Z3Irrep, Z4Irrep, ZNIrrep, U1Irrep, SU2Irrep, CU1Irrep export ProductSector export FermionParity, FermionNumber, FermionSpin -export FibonacciAnyon, IsingAnyon +export FibonacciAnyon, IsingAnyon, IsingBimodule export unit, rightunit, leftunit, allunits, isunit export VectorSpace, Field, ElementarySpace # abstract vector spaces -export unitspace, zerospace +export unitspace, zerospace, leftunitspace, rightunitspace export InnerProductStyle, NoInnerProduct, HasInnerProduct, EuclideanInnerProduct export ComplexSpace, CartesianSpace, GeneralSpace, GradedSpace # concrete spaces export ZNSpace, Z2Space, Z3Space, Z4Space, U1Space, CU1Space, SU2Space From 4b68caf2d99798e5d88b1047708b8c0313e94ea2 Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Tue, 30 Sep 2025 12:13:01 +0200 Subject: [PATCH 19/95] rename `insertleft/rightunit` and `removeunit` to `insertleft/rightunitspace` and `removeunitspace` --- Changelog.md | 4 ++-- docs/src/lib/spaces.md | 12 +++++------ docs/src/lib/tensors.md | 6 +++--- src/TensorKit.jl | 2 +- src/auxiliary/deprecate.jl | 2 +- src/spaces/homspace.jl | 36 +++++++++++++++---------------- src/spaces/productspace.jl | 20 ++++++++--------- src/spaces/vectorspaces.jl | 12 +++++------ src/tensors/indexmanipulations.jl | 30 +++++++++++++------------- test/spaces.jl | 36 +++++++++++++++---------------- test/tensors.jl | 22 +++++++++---------- 11 files changed, 91 insertions(+), 91 deletions(-) diff --git a/Changelog.md b/Changelog.md index b6ea4c24f..8a7983a22 100644 --- a/Changelog.md +++ b/Changelog.md @@ -26,8 +26,8 @@ are now explicitly represented as `DiagonalTensorMap` instances. There are is new functionality for manipulating the spaces associated with a tensor: * `flip(t, i)` changes the duality flag of the `i`th index of `t`, in such a way that flipping a pair of contracted indices in an `@tensor` contraction does not affect the result. -* `insertleftunit(t, i)` and `insertrightunit(t, i)` insert a trivial unit space to the left - or to right of index `i`, whereas `removeunit(t, i)` removes such a trivial unit space. +* `insertleftunitspace(t, i)` and `insertrightunitspace(t, i)` insert a trivial unit space to the left + or to right of index `i`, whereas `removeunitspace(t, i)` removes such a trivial unit space. ### SVD truncation change (breaking) There is a subtle but breaking change in the truncation mechanism in SVD, where now it is diff --git a/docs/src/lib/spaces.md b/docs/src/lib/spaces.md index be433493b..0eae17fe7 100644 --- a/docs/src/lib/spaces.md +++ b/docs/src/lib/spaces.md @@ -111,9 +111,9 @@ isisomorphic Inserting trivial space factors or removing such factors for `ProductSpace` instances can be done with the following methods. ```@docs -insertleftunit(::ProductSpace, ::Val{i}) where {i} -insertrightunit(::ProductSpace, ::Val{i}) where {i} -removeunit(::ProductSpace, ::Val{i}) where {i} +insertleftunitspace(::ProductSpace, ::Val{i}) where {i} +insertrightunitspace(::ProductSpace, ::Val{i}) where {i} +removeunitspace(::ProductSpace, ::Val{i}) where {i} ``` There are also specific methods for `HomSpace` instances, that are used in determining @@ -124,7 +124,7 @@ flip(W::HomSpace{S}, I) where {S} TensorKit.permute(::HomSpace{S}, ::Index2Tuple{N₁,N₂}) where {S,N₁,N₂} TensorKit.select(::HomSpace{S}, ::Index2Tuple{N₁,N₂}) where {S,N₁,N₂} TensorKit.compose(::HomSpace{S}, ::HomSpace{S}) where {S} -insertleftunit(::HomSpace, ::Val{i}) where {i} -insertrightunit(::HomSpace, ::Val{i}) where {i} -removeunit(::HomSpace, ::Val{i}) where {i} +insertleftunitspace(::HomSpace, ::Val{i}) where {i} +insertrightunitspace(::HomSpace, ::Val{i}) where {i} +removeunitspace(::HomSpace, ::Val{i}) where {i} ``` diff --git a/docs/src/lib/tensors.md b/docs/src/lib/tensors.md index 51b9bcf13..44f30dc27 100644 --- a/docs/src/lib/tensors.md +++ b/docs/src/lib/tensors.md @@ -184,9 +184,9 @@ transpose(::AbstractTensorMap, ::Index2Tuple) repartition(::AbstractTensorMap, ::Int, ::Int) flip(t::AbstractTensorMap, I) twist(::AbstractTensorMap, ::Int) -insertleftunit(::AbstractTensorMap, ::Val{i}) where {i} -insertrightunit(::AbstractTensorMap, ::Val{i}) where {i} -removeunit(::AbstractTensorMap, ::Val{i}) where {i} +insertleftunitspace(::AbstractTensorMap, ::Val{i}) where {i} +insertrightunitspace(::AbstractTensorMap, ::Val{i}) where {i} +removeunitspace(::AbstractTensorMap, ::Val{i}) where {i} ``` ```@docs diff --git a/src/TensorKit.jl b/src/TensorKit.jl index de9044243..b0cde57b9 100644 --- a/src/TensorKit.jl +++ b/src/TensorKit.jl @@ -35,7 +35,7 @@ export SpaceMismatch, SectorMismatch, IndexError # error types # general vector space methods export space, field, dual, dim, reduceddim, dims, fuse, flip, isdual, oplus, - insertleftunit, insertrightunit, removeunit + insertleftunitspace, insertrightunitspace, removeunitspace # partial order for vector spaces export infimum, supremum, isisomorphic, ismonomorphic, isepimorphic diff --git a/src/auxiliary/deprecate.jl b/src/auxiliary/deprecate.jl index fa7667b2b..efa83f0e7 100644 --- a/src/auxiliary/deprecate.jl +++ b/src/auxiliary/deprecate.jl @@ -56,6 +56,6 @@ end Base.@deprecate EuclideanProduct() EuclideanInnerProduct() -Base.@deprecate insertunit(P::ProductSpace, args...; kwargs...) insertleftunit(args...; kwargs...) +Base.@deprecate insertunit(P::ProductSpace, args...; kwargs...) insertleftunitspace(args...; kwargs...) #! format: on diff --git a/src/spaces/homspace.jl b/src/spaces/homspace.jl index 029b3ca40..04f178770 100644 --- a/src/spaces/homspace.jl +++ b/src/spaces/homspace.jl @@ -192,58 +192,58 @@ function compose(W::HomSpace{S}, V::HomSpace{S}) where {S} end """ - insertleftunit(W::HomSpace, i=numind(W) + 1; conj=false, dual=false) + insertleftunitspace(W::HomSpace, i=numind(W) + 1; conj=false, dual=false) Insert a trivial vector space, isomorphic to the underlying field, at position `i`, which can be specified as an `Int` or as `Val(i)` for improved type stability. More specifically, adds a left monoidal unit or its dual. -See also [`insertrightunit`](@ref insertrightunit(::HomSpace, ::Val{i}) where {i}), -[`removeunit`](@ref removeunit(::HomSpace, ::Val{i}) where {i}). +See also [`insertrightunitspace`](@ref insertrightunitspace(::HomSpace, ::Val{i}) where {i}), +[`removeunitspace`](@ref removeunitspace(::HomSpace, ::Val{i}) where {i}). """ -function insertleftunit(W::HomSpace, ::Val{i}=Val(numind(W) + 1); +function insertleftunitspace(W::HomSpace, ::Val{i}=Val(numind(W) + 1); conj::Bool=false, dual::Bool=false) where {i} if i ≤ numout(W) - return insertleftunit(codomain(W), Val(i); conj, dual) ← domain(W) + return insertleftunitspace(codomain(W), Val(i); conj, dual) ← domain(W) else - return codomain(W) ← insertleftunit(domain(W), Val(i - numout(W)); conj, dual) + return codomain(W) ← insertleftunitspace(domain(W), Val(i - numout(W)); conj, dual) end end """ - insertrightunit(W::HomSpace, i=numind(W); conj=false, dual=false) + insertrightunitspace(W::HomSpace, i=numind(W); conj=false, dual=false) Insert a trivial vector space, isomorphic to the underlying field, after position `i`, which can be specified as an `Int` or as `Val(i)` for improved type stability. More specifically, adds a right monoidal unit or its dual. -See also [`insertleftunit`](@ref insertleftunit(::HomSpace, ::Val{i}) where {i}), -[`removeunit`](@ref removeunit(::HomSpace, ::Val{i}) where {i}). +See also [`insertleftunitspace`](@ref insertleftunitspace(::HomSpace, ::Val{i}) where {i}), +[`removeunitspace`](@ref removeunitspace(::HomSpace, ::Val{i}) where {i}). """ -function insertrightunit(W::HomSpace, ::Val{i}=Val(numind(W)); +function insertrightunitspace(W::HomSpace, ::Val{i}=Val(numind(W)); conj::Bool=false, dual::Bool=false) where {i} if i ≤ numout(W) - return insertrightunit(codomain(W), Val(i); conj, dual) ← domain(W) + return insertrightunitspace(codomain(W), Val(i); conj, dual) ← domain(W) else - return codomain(W) ← insertrightunit(domain(W), Val(i - numout(W)); conj, dual) + return codomain(W) ← insertrightunitspace(domain(W), Val(i - numout(W)); conj, dual) end end """ - removeunit(P::HomSpace, i) + removeunitspace(P::HomSpace, i) This removes a trivial tensor product factor at position `1 ≤ i ≤ N`, where `i` can be specified as an `Int` or as `Val(i)` for improved type stability. For this to work, the space at position `i` has to be isomorphic to the field of scalars. -This operation undoes the work of [`insertleftunit`](@ref insertleftunit(::HomSpace, ::Val{i}) where {i}) -and [`insertrightunit`](@ref insertrightunit(::HomSpace, ::Val{i}) where {i}). +This operation undoes the work of [`insertleftunitspace`](@ref insertleftunitspace(::HomSpace, ::Val{i}) where {i}) +and [`insertrightunitspace`](@ref insertrightunitspace(::HomSpace, ::Val{i}) where {i}). """ -function removeunit(P::HomSpace, ::Val{i}) where {i} +function removeunitspace(P::HomSpace, ::Val{i}) where {i} if i ≤ numout(P) - return removeunit(codomain(P), Val(i)) ← domain(P) + return removeunitspace(codomain(P), Val(i)) ← domain(P) else - return codomain(P) ← removeunit(domain(P), Val(i - numout(P))) + return codomain(P) ← removeunitspace(domain(P), Val(i - numout(P))) end end diff --git a/src/spaces/productspace.jl b/src/spaces/productspace.jl index 41ffc1090..176089eee 100644 --- a/src/spaces/productspace.jl +++ b/src/spaces/productspace.jl @@ -247,15 +247,15 @@ fuse(P::ProductSpace{S,0}) where {S<:ElementarySpace} = unitspace(S) fuse(P::ProductSpace{S}) where {S<:ElementarySpace} = fuse(P.spaces...) """ - insertleftunit(P::ProductSpace, i::Int=length(P) + 1; conj=false, dual=false) + insertleftunitspace(P::ProductSpace, i::Int=length(P) + 1; conj=false, dual=false) Insert a trivial vector space, isomorphic to the underlying field, at position `i`, which can be specified as an `Int` or as `Val(i)` for improved type stability. More specifically, adds a left monoidal unit or its dual. -See also [`insertrightunit`](@ref insertrightunit(::ProductSpace, ::Val{i}) where {i}), [`removeunit`](@ref removeunit(::ProductSpace, ::Val{i}) where {i}). +See also [`insertrightunitspace`](@ref insertrightunitspace(::ProductSpace, ::Val{i}) where {i}), [`removeunitspace`](@ref removeunitspace(::ProductSpace, ::Val{i}) where {i}). """ -function insertleftunit(P::ProductSpace, ::Val{i}=Val(length(P) + 1); +function insertleftunitspace(P::ProductSpace, ::Val{i}=Val(length(P) + 1); conj::Bool=false, dual::Bool=false) where {i} u = unitspace(spacetype(P)) if dual @@ -268,15 +268,15 @@ function insertleftunit(P::ProductSpace, ::Val{i}=Val(length(P) + 1); end """ - insertrightunit(P::ProductSpace, i=lenght(P); conj=false, dual=false) + insertrightunitspace(P::ProductSpace, i=length(P); conj=false, dual=false) Insert a trivial vector space, isomorphic to the underlying field, after position `i`, which can be specified as an `Int` or as `Val(i)` for improved type stability. More specifically, adds a right monoidal unit or its dual. -See also [`insertleftunit`](@ref insertleftunit(::ProductSpace, ::Val{i}) where {i}), [`removeunit`](@ref removeunit(::ProductSpace, ::Val{i}) where {i}). +See also [`insertleftunitspace`](@ref insertleftunitspace(::ProductSpace, ::Val{i}) where {i}), [`removeunitspace`](@ref removeunitspace(::ProductSpace, ::Val{i}) where {i}). """ -function insertrightunit(P::ProductSpace, ::Val{i}=Val(length(P)); +function insertrightunitspace(P::ProductSpace, ::Val{i}=Val(length(P)); conj::Bool=false, dual::Bool=false) where {i} u = unitspace(spacetype(P)) if dual @@ -289,16 +289,16 @@ function insertrightunit(P::ProductSpace, ::Val{i}=Val(length(P)); end """ - removeunit(P::ProductSpace, i::Int) + removeunitspace(P::ProductSpace, i::Int) This removes a trivial tensor product factor at position `1 ≤ i ≤ N`, where `i` can be specified as an `Int` or as `Val(i)` for improved type stability. For this to work, that factor has to be isomorphic to the field of scalars. -This operation undoes the work of [`insertleftunit`](@ref insertleftunit(::ProductSpace, ::Val{i}) where {i}) -and [`insertrightunit`](@ref insertrightunit(::ProductSpace, ::Val{i}) where {i}). +This operation undoes the work of [`insertleftunitspace`](@ref insertleftunitspace(::ProductSpace, ::Val{i}) where {i}) +and [`insertrightunitspace`](@ref insertrightunitspace(::ProductSpace, ::Val{i}) where {i}). """ -function removeunit(P::ProductSpace, ::Val{i}) where {i} +function removeunitspace(P::ProductSpace, ::Val{i}) where {i} 1 ≤ i ≤ length(P) || _boundserror(P, i) isisomorphic(P[i], unitspace(P[i])) || _nontrivialspaceerror(P, i) return ProductSpace{spacetype(P)}(TupleTools.deleteat(P.spaces, i)) diff --git a/src/spaces/vectorspaces.jl b/src/spaces/vectorspaces.jl index 280db814b..5334153a7 100644 --- a/src/spaces/vectorspaces.jl +++ b/src/spaces/vectorspaces.jl @@ -209,14 +209,14 @@ end # In the following, X can be a ProductSpace, a HomSpace or an AbstractTensorMap # TODO: should we deprecate those in the future? -@constprop :aggressive function insertleftunit(X, i::Int; kwargs...) - return insertleftunit(X, Val(i); kwargs...) +@constprop :aggressive function insertleftunitspace(X, i::Int; kwargs...) + return insertleftunitspace(X, Val(i); kwargs...) end -@constprop :aggressive function insertrightunit(X, i::Int; kwargs...) - return insertrightunit(X, Val(i); kwargs...) +@constprop :aggressive function insertrightunitspace(X, i::Int; kwargs...) + return insertrightunitspace(X, Val(i); kwargs...) end -@constprop :aggressive function removeunit(X, i::Int; kwargs...) - return removeunit(X, Val(i); kwargs...) +@constprop :aggressive function removeunitspace(X, i::Int; kwargs...) + return removeunitspace(X, Val(i); kwargs...) end # trait to describe the inner product type of vector spaces diff --git a/src/tensors/indexmanipulations.jl b/src/tensors/indexmanipulations.jl index c4e2b9228..9d972388c 100644 --- a/src/tensors/indexmanipulations.jl +++ b/src/tensors/indexmanipulations.jl @@ -303,7 +303,7 @@ twist(t::AbstractTensorMap, i; inv::Bool=false) = twist!(copy(t), i; inv) # Methods which change the number of indices, implement using `Val(i)` for type inference """ - insertleftunit(tsrc::AbstractTensorMap, i=numind(t) + 1; + insertleftunitspace(tsrc::AbstractTensorMap, i=numind(t) + 1; conj=false, dual=false, copy=false) -> tdst Insert a trivial vector space, isomorphic to the underlying field, at position `i`, @@ -312,12 +312,12 @@ More specifically, adds a left monoidal unit or its dual. If `copy=false`, `tdst` might share data with `tsrc` whenever possible. Otherwise, a copy is always made. -See also [`insertrightunit`](@ref insertrightunit(::AbstractTensorMap, ::Val{i}) where {i}), -[`removeunit`](@ref removeunit(::AbstractTensorMap, ::Val{i}) where {i}). +See also [`insertrightunitspace`](@ref insertrightunitspace(::AbstractTensorMap, ::Val{i}) where {i}), +[`removeunitspace`](@ref removeunitspace(::AbstractTensorMap, ::Val{i}) where {i}). """ -function insertleftunit(t::AbstractTensorMap, ::Val{i}=Val(numind(t) + 1); +function insertleftunitspace(t::AbstractTensorMap, ::Val{i}=Val(numind(t) + 1); copy::Bool=false, conj::Bool=false, dual::Bool=false) where {i} - W = insertleftunit(space(t), Val(i); conj, dual) + W = insertleftunitspace(space(t), Val(i); conj, dual) if t isa TensorMap return TensorMap{scalartype(t)}(copy ? Base.copy(t.data) : t.data, W) else @@ -330,7 +330,7 @@ function insertleftunit(t::AbstractTensorMap, ::Val{i}=Val(numind(t) + 1); end """ - insertrightunit(tsrc::AbstractTensorMap, i=numind(t); + insertrightunitspace(tsrc::AbstractTensorMap, i=numind(t); conj=false, dual=false, copy=false) -> tdst Insert a trivial vector space, isomorphic to the underlying field, after position `i`, @@ -339,12 +339,12 @@ More specifically, adds a right monoidal unit or its dual. If `copy=false`, `tdst` might share data with `tsrc` whenever possible. Otherwise, a copy is always made. -See also [`insertleftunit`](@ref insertleftunit(::AbstractTensorMap, ::Val{i}) where {i}), -[`removeunit`](@ref removeunit(::AbstractTensorMap, ::Val{i}) where {i}). +See also [`insertleftunitspace`](@ref insertleftunitspace(::AbstractTensorMap, ::Val{i}) where {i}), +[`removeunitspace`](@ref removeunitspace(::AbstractTensorMap, ::Val{i}) where {i}). """ -function insertrightunit(t::AbstractTensorMap, ::Val{i}=Val(numind(t)); +function insertrightunitspace(t::AbstractTensorMap, ::Val{i}=Val(numind(t)); copy::Bool=false, conj::Bool=false, dual::Bool=false) where {i} - W = insertrightunit(space(t), Val(i); conj, dual) + W = insertrightunitspace(space(t), Val(i); conj, dual) if t isa TensorMap return TensorMap{scalartype(t)}(copy ? Base.copy(t.data) : t.data, W) else @@ -357,7 +357,7 @@ function insertrightunit(t::AbstractTensorMap, ::Val{i}=Val(numind(t)); end """ - removeunit(tsrc::AbstractTensorMap, i; copy=false) -> tdst + removeunitspace(tsrc::AbstractTensorMap, i; copy=false) -> tdst This removes a trivial tensor product factor at position `1 ≤ i ≤ N`, where `i` can be specified as an `Int` or as `Val(i)` for improved type stability. @@ -365,11 +365,11 @@ For this to work, that factor has to be isomorphic to the field of scalars. If `copy=false`, `tdst` might share data with `tsrc` whenever possible. Otherwise, a copy is always made. -This operation undoes the work of [`insertleftunit`](@ref insertleftunit(::AbstractTensorMap, ::Val{i}) where {i}) -and [`insertrightunit`](@ref insertrightunit(::AbstractTensorMap, ::Val{i}) where {i}). +This operation undoes the work of [`insertleftunitspace`](@ref insertleftunitspace(::AbstractTensorMap, ::Val{i}) where {i}) +and [`insertrightunitspace`](@ref insertrightunitspace(::AbstractTensorMap, ::Val{i}) where {i}). """ -function removeunit(t::AbstractTensorMap, ::Val{i}; copy::Bool=false) where {i} - W = removeunit(space(t), Val(i)) +function removeunitspace(t::AbstractTensorMap, ::Val{i}; copy::Bool=false) where {i} + W = removeunitspace(space(t), Val(i)) if t isa TensorMap return TensorMap{scalartype(t)}(copy ? Base.copy(t.data) : t.data, W) else diff --git a/test/spaces.jl b/test/spaces.jl index 5fd77b06b..050271c8f 100644 --- a/test/spaces.jl +++ b/test/spaces.jl @@ -280,9 +280,9 @@ println("------------------------------------") @test @constinferred(⊗(V1, V2, V3 ⊗ V4)) == P @test @constinferred(⊗(V1, V2 ⊗ V3, V4)) == P @test V1 * V2 * unitspace(V1) * V3 * V4 == - @constinferred(insertleftunit(P, 3)) == - @constinferred(insertrightunit(P, 2)) - @test @constinferred(removeunit(V1 * V2 * unitspace(V1)' * V3 * V4, 3)) == P + @constinferred(insertleftunitspace(P, 3)) == + @constinferred(insertrightunitspace(P, 2)) + @test @constinferred(removeunitspace(V1 * V2 * unitspace(V1)' * V3 * V4, 3)) == P @test fuse(V1, V2', V3) ≅ V1 ⊗ V2' ⊗ V3 @test fuse(V1, V2', V3) ≾ V1 ⊗ V2' ⊗ V3 @test fuse(V1, V2', V3) ≿ V1 ⊗ V2' ⊗ V3 @@ -344,9 +344,9 @@ println("------------------------------------") @test @constinferred(⊗(V1, V2, V3)) == P @test @constinferred(adjoint(P)) == dual(P) == V3' ⊗ V2' ⊗ V1' @test V1 * V2 * unitspace(V1)' * V3 == - @constinferred(insertleftunit(P, 3; conj=true)) == - @constinferred(insertrightunit(P, 2; conj=true)) - @test P == @constinferred(removeunit(insertleftunit(P, 3), 3)) + @constinferred(insertleftunitspace(P, 3; conj=true)) == + @constinferred(insertrightunitspace(P, 2; conj=true)) + @test P == @constinferred(removeunitspace(insertleftunitspace(P, 3), 3)) @test fuse(V1, V2', V3) ≅ V1 ⊗ V2' ⊗ V3 @test fuse(V1, V2', V3) ≾ V1 ⊗ V2' ⊗ V3 ≾ fuse(V1 ⊗ V2' ⊗ V3) @test fuse(V1, V2') ⊗ V3 ≾ V1 ⊗ V2' ⊗ V3 @@ -426,22 +426,22 @@ println("------------------------------------") @test permute(W, ((2, 4, 5), (3, 1))) == (V2 ⊗ V4' ⊗ V5' ← V3 ⊗ V1') @test (V1 ⊗ V2 ← V1 ⊗ V2) == @constinferred TK.compose(W, W') @test (V1 ⊗ V2 ← V3 ⊗ V4 ⊗ V5 ⊗ unitspace(V5)) == - @constinferred(insertleftunit(W)) == - @constinferred(insertrightunit(W)) - @test @constinferred(removeunit(insertleftunit(W), $(numind(W) + 1))) == W + @constinferred(insertleftunitspace(W)) == + @constinferred(insertrightunitspace(W)) + @test @constinferred(removeunitspace(insertleftunitspace(W), $(numind(W) + 1))) == W @test (V1 ⊗ V2 ← V3 ⊗ V4 ⊗ V5 ⊗ unitspace(V5)') == - @constinferred(insertleftunit(W; conj=true)) == - @constinferred(insertrightunit(W; conj=true)) + @constinferred(insertleftunitspace(W; conj=true)) == + @constinferred(insertrightunitspace(W; conj=true)) @test (unitspace(V1) ⊗ V1 ⊗ V2 ← V3 ⊗ V4 ⊗ V5) == - @constinferred(insertleftunit(W, 1)) == - @constinferred(insertrightunit(W, 0)) + @constinferred(insertleftunitspace(W, 1)) == + @constinferred(insertrightunitspace(W, 0)) @test (V1 ⊗ V2 ⊗ unitspace(V1) ← V3 ⊗ V4 ⊗ V5) == - @constinferred(insertrightunit(W, 2)) + @constinferred(insertrightunitspace(W, 2)) @test (V1 ⊗ V2 ← unitspace(V1) ⊗ V3 ⊗ V4 ⊗ V5) == - @constinferred(insertleftunit(W, 3)) - @test @constinferred(removeunit(insertleftunit(W, 3), 3)) == W - @test @constinferred(insertrightunit(one(V1) ← V1, 0)) == (unitspace(V1) ← V1) - @test_throws BoundsError insertleftunit(one(V1) ← V1, 0) + @constinferred(insertleftunitspace(W, 3)) + @test @constinferred(removeunitspace(insertleftunitspace(W, 3), 3)) == W + @test @constinferred(insertrightunitspace(one(V1) ← V1, 0)) == (unitspace(V1) ← V1) + @test_throws BoundsError insertleftunitspace(one(V1) ← V1, 0) end end TK.empty_globalcaches!() diff --git a/test/tensors.jl b/test/tensors.jl index 26f0bbd8f..2f46022b7 100644 --- a/test/tensors.jl +++ b/test/tensors.jl @@ -147,32 +147,32 @@ for V in spacelist W = V1 ⊗ V2 ⊗ V3 ← V4 ⊗ V5 for T in (Float32, ComplexF64) t = @constinferred rand(T, W) - t2 = @constinferred insertleftunit(t) - @test t2 == @constinferred insertrightunit(t) + t2 = @constinferred insertleftunitspace(t) + @test t2 == @constinferred insertrightunitspace(t) @test numind(t2) == numind(t) + 1 - @test space(t2) == insertleftunit(space(t)) + @test space(t2) == insertleftunitspace(space(t)) @test scalartype(t2) === T @test t.data === t2.data - @test @constinferred(removeunit(t2, $(numind(t2)))) == t - t3 = @constinferred insertleftunit(t; copy=true) - @test t3 == @constinferred insertrightunit(t; copy=true) + @test @constinferred(removeunitspace(t2, $(numind(t2)))) == t + t3 = @constinferred insertleftunitspace(t; copy=true) + @test t3 == @constinferred insertrightunitspace(t; copy=true) @test t.data !== t3.data for (c, b) in blocks(t) @test b == block(t3, c) end - @test @constinferred(removeunit(t3, $(numind(t3)))) == t - t4 = @constinferred insertrightunit(t, 3; dual=true) + @test @constinferred(removeunitspace(t3, $(numind(t3)))) == t + t4 = @constinferred insertrightunitspace(t, 3; dual=true) @test numin(t4) == numin(t) && numout(t4) == numout(t) + 1 for (c, b) in blocks(t) @test b == block(t4, c) end - @test @constinferred(removeunit(t4, 4)) == t - t5 = @constinferred insertleftunit(t, 4; dual=true) + @test @constinferred(removeunitspace(t4, 4)) == t + t5 = @constinferred insertleftunitspace(t, 4; dual=true) @test numin(t5) == numin(t) + 1 && numout(t5) == numout(t) for (c, b) in blocks(t) @test b == block(t5, c) end - @test @constinferred(removeunit(t5, 4)) == t + @test @constinferred(removeunitspace(t5, 4)) == t end end if hasfusiontensor(I) From a8046b232e9546602982b5761669f9a40ef161f4 Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Tue, 30 Sep 2025 12:14:54 +0200 Subject: [PATCH 20/95] undo the renaming in the changelog --- Changelog.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Changelog.md b/Changelog.md index 8a7983a22..b6ea4c24f 100644 --- a/Changelog.md +++ b/Changelog.md @@ -26,8 +26,8 @@ are now explicitly represented as `DiagonalTensorMap` instances. There are is new functionality for manipulating the spaces associated with a tensor: * `flip(t, i)` changes the duality flag of the `i`th index of `t`, in such a way that flipping a pair of contracted indices in an `@tensor` contraction does not affect the result. -* `insertleftunitspace(t, i)` and `insertrightunitspace(t, i)` insert a trivial unit space to the left - or to right of index `i`, whereas `removeunitspace(t, i)` removes such a trivial unit space. +* `insertleftunit(t, i)` and `insertrightunit(t, i)` insert a trivial unit space to the left + or to right of index `i`, whereas `removeunit(t, i)` removes such a trivial unit space. ### SVD truncation change (breaking) There is a subtle but breaking change in the truncation mechanism in SVD, where now it is From aacafc131af06bed53fc71e88dcd9223b919aec1 Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Tue, 30 Sep 2025 16:28:38 +0200 Subject: [PATCH 21/95] update `insertleft/rightunitspace` --- src/spaces/productspace.jl | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/src/spaces/productspace.jl b/src/spaces/productspace.jl index 176089eee..42956acec 100644 --- a/src/spaces/productspace.jl +++ b/src/spaces/productspace.jl @@ -257,7 +257,15 @@ See also [`insertrightunitspace`](@ref insertrightunitspace(::ProductSpace, ::Va """ function insertleftunitspace(P::ProductSpace, ::Val{i}=Val(length(P) + 1); conj::Bool=false, dual::Bool=false) where {i} - u = unitspace(spacetype(P)) + N = length(P) + I = sectortype(P) + if UnitStyle(I) isa SimpleUnit + u = unitspace(spacetype(P)) + else + N > 0 || throw(ArgumentError("cannot insert a sensible unit space in the empty product space")) + i > N && throw(DomainError((P, i), "cannot insert a sensible left unit space")) + u = leftunitspace(P[i]) + end if dual u = TensorKit.dual(u) end @@ -278,7 +286,15 @@ See also [`insertleftunitspace`](@ref insertleftunitspace(::ProductSpace, ::Val{ """ function insertrightunitspace(P::ProductSpace, ::Val{i}=Val(length(P)); conj::Bool=false, dual::Bool=false) where {i} - u = unitspace(spacetype(P)) + N = length(P) + I = sectortype(P) + if UnitStyle(I) isa SimpleUnit + u = unitspace(spacetype(P)) + else + N > 0 || throw(ArgumentError("cannot insert a sensible unit space in the empty product space")) + i == 0 && throw(DomainError((P, i), "cannot insert a sensible right unit space")) + u = rightunitspace(P[i]) + end if dual u = TensorKit.dual(u) end From 50e408d4a044bf801cd001a455b4e6cdeb58a454 Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Tue, 30 Sep 2025 16:28:55 +0200 Subject: [PATCH 22/95] add fixme --- src/spaces/gradedspace.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/spaces/gradedspace.jl b/src/spaces/gradedspace.jl index 57bcd4180..97c964361 100644 --- a/src/spaces/gradedspace.jl +++ b/src/spaces/gradedspace.jl @@ -91,7 +91,7 @@ Base.hash(V::GradedSpace, h::UInt) = hash(V.dual, hash(V.dims, h)) field(::Type{<:GradedSpace}) = ℂ InnerProductStyle(::Type{<:GradedSpace}) = EuclideanInnerProduct() function dim(V::GradedSpace) - T = Base.promote_op(*, Int, real(sectorscalartype(sectortype(V)))) + T = Base.promote_op(*, Int, real(sectorscalartype(sectortype(V)))) #FIXME: doesn't work return reduce(+, dim(V, c) * dim(c) for c in sectors(V); init=zero(T)) end From 6cc06181b7652d1e75ee564c4550cc743559312f Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Mon, 3 Nov 2025 12:50:04 +0100 Subject: [PATCH 23/95] fix bad merge conflict choices --- src/TensorKit.jl | 6 ++- src/spaces/homspace.jl | 82 +++++++++++++++++++--------------- src/spaces/productspace.jl | 2 +- test/symmetries/fusiontrees.jl | 2 +- test/symmetries/spaces.jl | 36 +++++++-------- test/tensors/tensors.jl | 12 ++--- 6 files changed, 76 insertions(+), 64 deletions(-) diff --git a/src/TensorKit.jl b/src/TensorKit.jl index 2fe4734a9..6ddb7f5a6 100644 --- a/src/TensorKit.jl +++ b/src/TensorKit.jl @@ -34,8 +34,10 @@ export TruncationScheme export SpaceMismatch, SectorMismatch, IndexError # error types # general vector space methods -export space, field, dual, dim, reduceddim, dims, fuse, flip, isdual, oplus, - insertleftunitspace, insertrightunitspace, removeunitspace +# Export general vector space methods +export space, field, dual, dim, reduceddim, dims, fuse, flip, isdual +export unitspace, zerospace, oplus, ominus +export insertleftunitspace, insertrightunitspace, removeunitspace # partial order for vector spaces export infimum, supremum, isisomorphic, ismonomorphic, isepimorphic diff --git a/src/spaces/homspace.jl b/src/spaces/homspace.jl index 04f178770..01fa3534f 100644 --- a/src/spaces/homspace.jl +++ b/src/spaces/homspace.jl @@ -8,18 +8,18 @@ Represents the linear space of morphisms with codomain of type `P1` and domain o Note that HomSpace is not a subtype of VectorSpace, i.e. we restrict the latter to denote certain categories and their objects, and keep HomSpace distinct. """ -struct HomSpace{S<:ElementarySpace,P1<:CompositeSpace{S},P2<:CompositeSpace{S}} +struct HomSpace{S <: ElementarySpace, P1 <: CompositeSpace{S}, P2 <: CompositeSpace{S}} codomain::P1 domain::P2 end -function HomSpace(codomain::S, domain::CompositeSpace{S}) where {S<:ElementarySpace} +function HomSpace(codomain::S, domain::CompositeSpace{S}) where {S <: ElementarySpace} return HomSpace(⊗(codomain), domain) end -function HomSpace(codomain::CompositeSpace{S}, domain::S) where {S<:ElementarySpace} +function HomSpace(codomain::CompositeSpace{S}, domain::S) where {S <: ElementarySpace} return HomSpace(codomain, ⊗(domain)) end -function HomSpace(codomain::S, domain::S) where {S<:ElementarySpace} +function HomSpace(codomain::S, domain::S) where {S <: ElementarySpace} return HomSpace(⊗(codomain), ⊗(domain)) end HomSpace(codomain::VectorSpace) = HomSpace(codomain, zerospace(codomain)) @@ -45,18 +45,20 @@ numout(W::HomSpace) = length(codomain(W)) numin(W::HomSpace) = length(domain(W)) numind(W::HomSpace) = numin(W) + numout(W) -const TensorSpace{S<:ElementarySpace} = Union{S,ProductSpace{S}} -const TensorMapSpace{S<:ElementarySpace,N₁,N₂} = HomSpace{S,ProductSpace{S,N₁}, - ProductSpace{S,N₂}} +const TensorSpace{S <: ElementarySpace} = Union{S, ProductSpace{S}} +const TensorMapSpace{S <: ElementarySpace, N₁, N₂} = HomSpace{ + S, ProductSpace{S, N₁}, + ProductSpace{S, N₂}, +} -function Base.getindex(W::TensorMapSpace{<:IndexSpace,N₁,N₂}, i) where {N₁,N₂} +function Base.getindex(W::TensorMapSpace{<:IndexSpace, N₁, N₂}, i) where {N₁, N₂} return i <= N₁ ? codomain(W)[i] : dual(domain(W)[i - N₁]) end -function ←(codom::ProductSpace{S}, dom::ProductSpace{S}) where {S<:ElementarySpace} +function ←(codom::ProductSpace{S}, dom::ProductSpace{S}) where {S <: ElementarySpace} return HomSpace(codom, dom) end -function ←(codom::S, dom::S) where {S<:ElementarySpace} +function ←(codom::S, dom::S) where {S <: ElementarySpace} return HomSpace(ProductSpace(codom), ProductSpace(dom)) end ←(codom::VectorSpace, dom::VectorSpace) = ←(promote(codom, dom)...) @@ -69,7 +71,7 @@ function Base.show(io::IO, W::HomSpace) print(io, W.codomain) end print(io, " ← ") - if length(W.domain) == 1 + return if length(W.domain) == 1 print(io, W.domain[1]) else print(io, W.domain) @@ -94,7 +96,7 @@ function blocksectors(W::HomSpace) N₁ = length(codom) N₂ = length(dom) I = sectortype(W) - if N₁ == 0 && N₂ == 0 + if N₁ == 0 && N₂ == 0 return allunits(I) elseif N₁ == 0 return filter!(isone, collect(blocksectors(dom))) # module space cannot end in empty space @@ -130,6 +132,8 @@ function dim(W::HomSpace) return d end +dims(W::HomSpace) = (dims(codomain(W))..., dims(domain(W))...) + """ fusiontrees(W::HomSpace) @@ -145,7 +149,7 @@ fusiontrees(W::HomSpace) = fusionblockstructure(W).fusiontreelist Return the `HomSpace` obtained by permuting the indices of the domain and codomain of `W` according to the permutation `p₁` and `p₂` respectively. """ -function permute(W::HomSpace{S}, (p₁, p₂)::Index2Tuple{N₁,N₂}) where {S,N₁,N₂} +function permute(W::HomSpace{S}, (p₁, p₂)::Index2Tuple{N₁, N₂}) where {S, N₁, N₂} p = (p₁..., p₂...) TupleTools.isperm(p) && length(p) == numind(W) || throw(ArgumentError("$((p₁, p₂)) is not a valid permutation for $(W)")) @@ -158,9 +162,9 @@ end Return the `HomSpace` obtained by a selection from the domain and codomain of `W` according to the indices in `p₁` and `p₂` respectively. """ -function select(W::HomSpace{S}, (p₁, p₂)::Index2Tuple{N₁,N₂}) where {S,N₁,N₂} - cod = ProductSpace{S,N₁}(map(n -> W[n], p₁)) - dom = ProductSpace{S,N₂}(map(n -> dual(W[n]), p₂)) +function select(W::HomSpace{S}, (p₁, p₂)::Index2Tuple{N₁, N₂}) where {S, N₁, N₂} + cod = ProductSpace{S, N₁}(map(n -> W[n], p₁)) + dom = ProductSpace{S, N₂}(map(n -> dual(W[n]), p₂)) return cod ← dom end @@ -201,8 +205,10 @@ More specifically, adds a left monoidal unit or its dual. See also [`insertrightunitspace`](@ref insertrightunitspace(::HomSpace, ::Val{i}) where {i}), [`removeunitspace`](@ref removeunitspace(::HomSpace, ::Val{i}) where {i}). """ -function insertleftunitspace(W::HomSpace, ::Val{i}=Val(numind(W) + 1); - conj::Bool=false, dual::Bool=false) where {i} +function insertleftunitspace( + W::HomSpace, ::Val{i} = Val(numind(W) + 1); + conj::Bool = false, dual::Bool = false + ) where {i} if i ≤ numout(W) return insertleftunitspace(codomain(W), Val(i); conj, dual) ← domain(W) else @@ -220,8 +226,10 @@ More specifically, adds a right monoidal unit or its dual. See also [`insertleftunitspace`](@ref insertleftunitspace(::HomSpace, ::Val{i}) where {i}), [`removeunitspace`](@ref removeunitspace(::HomSpace, ::Val{i}) where {i}). """ -function insertrightunitspace(W::HomSpace, ::Val{i}=Val(numind(W)); - conj::Bool=false, dual::Bool=false) where {i} +function insertrightunitspace( + W::HomSpace, ::Val{i} = Val(numind(W)); + conj::Bool = false, dual::Bool = false + ) where {i} if i ≤ numout(W) return insertrightunitspace(codomain(W), Val(i); conj, dual) ← domain(W) else @@ -251,14 +259,14 @@ end #-------------------------------------------------------------------------- # sizes, strides, offset -const StridedStructure{N} = Tuple{NTuple{N,Int},NTuple{N,Int},Int} +const StridedStructure{N} = Tuple{NTuple{N, Int}, NTuple{N, Int}, Int} -struct FusionBlockStructure{I,N,F₁,F₂} +struct FusionBlockStructure{I, N, F₁, F₂} totaldim::Int - blockstructure::SectorDict{I,Tuple{Tuple{Int,Int},UnitRange{Int}}} - fusiontreelist::Vector{Tuple{F₁,F₂}} + blockstructure::SectorDict{I, Tuple{Tuple{Int, Int}, UnitRange{Int}}} + fusiontreelist::Vector{Tuple{F₁, F₂}} fusiontreestructure::Vector{StridedStructure{N}} - fusiontreeindices::FusionTreeDict{Tuple{F₁,F₂},Int} + fusiontreeindices::FusionTreeDict{Tuple{F₁, F₂}, Int} end function fusionblockstructuretype(W::HomSpace) @@ -268,7 +276,7 @@ function fusionblockstructuretype(W::HomSpace) I = sectortype(W) F₁ = fusiontreetype(I, N₁) F₂ = fusiontreetype(I, N₂) - return FusionBlockStructure{I,N,F₁,F₂} + return FusionBlockStructure{I, N, F₁, F₂} end @cached function fusionblockstructure(W::HomSpace)::fusionblockstructuretype(W) @@ -281,13 +289,13 @@ end F₂ = fusiontreetype(I, N₂) # output structure - blockstructure = SectorDict{I,Tuple{Tuple{Int,Int},UnitRange{Int}}}() # size, range - fusiontreelist = Vector{Tuple{F₁,F₂}}() - fusiontreestructure = Vector{Tuple{NTuple{N₁ + N₂,Int},NTuple{N₁ + N₂,Int},Int}}() # size, strides, offset + blockstructure = SectorDict{I, Tuple{Tuple{Int, Int}, UnitRange{Int}}}() # size, range + fusiontreelist = Vector{Tuple{F₁, F₂}}() + fusiontreestructure = Vector{Tuple{NTuple{N₁ + N₂, Int}, NTuple{N₁ + N₂, Int}, Int}}() # size, strides, offset # temporary data structures splittingtrees = Vector{F₁}() - splittingstructure = Vector{Tuple{Int,Int}}() + splittingstructure = Vector{Tuple{Int, Int}}() # main computational routine blockoffset = 0 @@ -327,15 +335,17 @@ end blockstructure[c] = (blocksize, blockrange) end - fusiontreeindices = sizehint!(FusionTreeDict{Tuple{F₁,F₂},Int}(), - length(fusiontreelist)) + fusiontreeindices = sizehint!( + FusionTreeDict{Tuple{F₁, F₂}, Int}(), + length(fusiontreelist) + ) for (i, f₁₂) in enumerate(fusiontreelist) fusiontreeindices[f₁₂] = i end totaldim = blockoffset - structure = FusionBlockStructure(totaldim, blockstructure, - fusiontreelist, fusiontreestructure, - fusiontreeindices) + structure = FusionBlockStructure( + totaldim, blockstructure, fusiontreelist, fusiontreestructure, fusiontreeindices + ) return structure end @@ -357,7 +367,7 @@ end function diagonalblockstructure(W::HomSpace) ((numin(W) == numout(W) == 1) && domain(W) == codomain(W)) || throw(SpaceMismatch("Diagonal only support on V←V with a single space V")) - structure = SectorDict{sectortype(W),UnitRange{Int}}() # range + structure = SectorDict{sectortype(W), UnitRange{Int}}() # range offset = 0 dom = domain(W)[1] for c in blocksectors(W) diff --git a/src/spaces/productspace.jl b/src/spaces/productspace.jl index b147fcaa8..3b6236120 100644 --- a/src/spaces/productspace.jl +++ b/src/spaces/productspace.jl @@ -234,7 +234,7 @@ end Return a tensor product of zero spaces of type `S`, i.e. this is the unit object under the tensor product operation, such that `V ⊗ one(V) == V`. """ -#TODO: unit(V::S)? + Base.one(V::VectorSpace) = one(typeof(V)) Base.one(::Type{<:ProductSpace{S}}) where {S <: ElementarySpace} = ProductSpace{S, 0}(()) Base.one(::Type{S}) where {S <: ElementarySpace} = ProductSpace{S, 0}(()) diff --git a/test/symmetries/fusiontrees.jl b/test/symmetries/fusiontrees.jl index 71e5a042d..703a36c69 100644 --- a/test/symmetries/fusiontrees.jl +++ b/test/symmetries/fusiontrees.jl @@ -578,7 +578,7 @@ using .TestSetup d1 = transpose(f1, f1, (N + 1, 1:N..., ((2N):-1:(N + 3))...), (N + 2,)) f1front, = TK.split(f1, N - 1) T = sectorscalartype(I) - d2 = Dict{typeof((f1front, f1front)),T}() + d2 = Dict{typeof((f1front, f1front)), T}() for ((f1′, f2′), coeff′) in d1 for ((f1′′, f2′′), coeff′′) in TK.planar_trace( diff --git a/test/symmetries/spaces.jl b/test/symmetries/spaces.jl index a952d4b6b..5a020214f 100644 --- a/test/symmetries/spaces.jl +++ b/test/symmetries/spaces.jl @@ -291,9 +291,9 @@ end @test @constinferred(⊗(V1, V2, V3 ⊗ V4)) == P @test @constinferred(⊗(V1, V2 ⊗ V3, V4)) == P @test V1 * V2 * unitspace(V1) * V3 * V4 == - @constinferred(insertleftunit(P, 3)) == - @constinferred(insertrightunit(P, 2)) - @test @constinferred(removeunit(V1 * V2 * unitspace(V1)' * V3 * V4, 3)) == P + @constinferred(insertleftunitspace(P, 3)) == + @constinferred(insertrightunitspace(P, 2)) + @test @constinferred(removeunitspace(V1 * V2 * unitspace(V1)' * V3 * V4, 3)) == P @test fuse(V1, V2', V3) ≅ V1 ⊗ V2' ⊗ V3 @test fuse(V1, V2', V3) ≾ V1 ⊗ V2' ⊗ V3 @test fuse(V1, V2', V3) ≿ V1 ⊗ V2' ⊗ V3 @@ -352,9 +352,9 @@ end @test @constinferred(⊗(V1, V2, V3)) == P @test @constinferred(adjoint(P)) == dual(P) == V3' ⊗ V2' ⊗ V1' @test V1 * V2 * unitspace(V1)' * V3 == - @constinferred(insertleftunit(P, 3; conj = true)) == - @constinferred(insertrightunit(P, 2; conj = true)) - @test P == @constinferred(removeunit(insertleftunit(P, 3), 3)) + @constinferred(insertleftunitspace(P, 3; conj = true)) == + @constinferred(insertrightunitspace(P, 2; conj = true)) + @test P == @constinferred(removeunitspace(insertleftunitspace(P, 3), 3)) @test fuse(V1, V2', V3) ≅ V1 ⊗ V2' ⊗ V3 @test fuse(V1, V2', V3) ≾ V1 ⊗ V2' ⊗ V3 ≾ fuse(V1 ⊗ V2' ⊗ V3) @test fuse(V1, V2') ⊗ V3 ≾ V1 ⊗ V2' ⊗ V3 @@ -433,22 +433,22 @@ end @test permute(W, ((2, 4, 5), (3, 1))) == (V2 ⊗ V4' ⊗ V5' ← V3 ⊗ V1') @test (V1 ⊗ V2 ← V1 ⊗ V2) == @constinferred TensorKit.compose(W, W') @test (V1 ⊗ V2 ← V3 ⊗ V4 ⊗ V5 ⊗ unitspace(V5)) == - @constinferred(insertleftunit(W)) == - @constinferred(insertrightunit(W)) - @test @constinferred(removeunit(insertleftunit(W), $(numind(W) + 1))) == W + @constinferred(insertleftunitspace(W)) == + @constinferred(insertrightunitspace(W)) + @test @constinferred(removeunitspace(insertleftunitspace(W), $(numind(W) + 1))) == W @test (V1 ⊗ V2 ← V3 ⊗ V4 ⊗ V5 ⊗ unitspace(V5)') == - @constinferred(insertleftunit(W; conj = true)) == - @constinferred(insertrightunit(W; conj = true)) + @constinferred(insertleftunitspace(W; conj = true)) == + @constinferred(insertrightunitspace(W; conj = true)) @test (unitspace(V1) ⊗ V1 ⊗ V2 ← V3 ⊗ V4 ⊗ V5) == - @constinferred(insertleftunit(W, 1)) == - @constinferred(insertrightunit(W, 0)) + @constinferred(insertleftunitspace(W, 1)) == + @constinferred(insertrightunitspace(W, 0)) @test (V1 ⊗ V2 ⊗ unitspace(V1) ← V3 ⊗ V4 ⊗ V5) == - @constinferred(insertrightunit(W, 2)) + @constinferred(insertrightunitspace(W, 2)) @test (V1 ⊗ V2 ← unitspace(V1) ⊗ V3 ⊗ V4 ⊗ V5) == - @constinferred(insertleftunit(W, 3)) - @test @constinferred(removeunit(insertleftunit(W, 3), 3)) == W - @test @constinferred(insertrightunit(one(V1) ← V1, 0)) == (unitspace(V1) ← V1) - @test_throws BoundsError insertleftunit(one(V1) ← V1, 0) + @constinferred(insertleftunitspace(W, 3)) + @test @constinferred(removeunitspace(insertleftunitspace(W, 3), 3)) == W + @test @constinferred(insertrightunitspace(one(V1) ← V1, 0)) == (unitspace(V1) ← V1) + @test_throws BoundsError insertleftunitspace(one(V1) ← V1, 0) end end diff --git a/test/tensors/tensors.jl b/test/tensors/tensors.jl index 18ac9ee05..62b09d679 100644 --- a/test/tensors/tensors.jl +++ b/test/tensors/tensors.jl @@ -50,7 +50,7 @@ for V in spacelist next = @constinferred Nothing iterate(bs, state) b2 = @constinferred block(t, first(blocksectors(t))) @test b1 == b2 - @test eltype(bs) === Pair{typeof(c),typeof(b1)} + @test eltype(bs) === Pair{typeof(c), typeof(b1)} @test typeof(b1) === TK.blocktype(t) @test typeof(c) === sectortype(t) end @@ -112,7 +112,7 @@ for V in spacelist next = @constinferred Nothing iterate(bs, state) b2 = @constinferred block(t', first(blocksectors(t'))) @test b1 == b2 - @test eltype(bs) === Pair{typeof(c),typeof(b1)} + @test eltype(bs) === Pair{typeof(c), typeof(b1)} @test typeof(b1) === TK.blocktype(t') @test typeof(c) === sectortype(t) # linear algebra @@ -156,20 +156,20 @@ for V in spacelist @test scalartype(t2) === T @test t.data === t2.data @test @constinferred(removeunitspace(t2, $(numind(t2)))) == t - t3 = @constinferred insertleftunitspace(t; copy=true) - @test t3 == @constinferred insertrightunitspace(t; copy=true) + t3 = @constinferred insertleftunitspace(t; copy = true) + @test t3 == @constinferred insertrightunitspace(t; copy = true) @test t.data !== t3.data for (c, b) in blocks(t) @test b == block(t3, c) end @test @constinferred(removeunitspace(t3, $(numind(t3)))) == t - t4 = @constinferred insertrightunitspace(t, 3; dual=true) + t4 = @constinferred insertrightunitspace(t, 3; dual = true) @test numin(t4) == numin(t) && numout(t4) == numout(t) + 1 for (c, b) in blocks(t) @test b == block(t4, c) end @test @constinferred(removeunitspace(t4, 4)) == t - t5 = @constinferred insertleftunitspace(t, 4; dual=true) + t5 = @constinferred insertleftunitspace(t, 4; dual = true) @test numin(t5) == numin(t) + 1 && numout(t5) == numout(t) for (c, b) in blocks(t) @test b == block(t5, c) From 1ce960956ef64188bf8ef179644f901ecf51290e Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Mon, 3 Nov 2025 13:50:37 +0100 Subject: [PATCH 24/95] remove TensorKit shortcuts where they don't exist --- test/tensors/tensors.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/tensors/tensors.jl b/test/tensors/tensors.jl index 62b09d679..e0e270fd2 100644 --- a/test/tensors/tensors.jl +++ b/test/tensors/tensors.jl @@ -51,7 +51,7 @@ for V in spacelist b2 = @constinferred block(t, first(blocksectors(t))) @test b1 == b2 @test eltype(bs) === Pair{typeof(c), typeof(b1)} - @test typeof(b1) === TK.blocktype(t) + @test typeof(b1) === TensorKit.blocktype(t) @test typeof(c) === sectortype(t) end end @@ -113,7 +113,7 @@ for V in spacelist b2 = @constinferred block(t', first(blocksectors(t'))) @test b1 == b2 @test eltype(bs) === Pair{typeof(c), typeof(b1)} - @test typeof(b1) === TK.blocktype(t') + @test typeof(b1) === TensorKit.blocktype(t') @test typeof(c) === sectortype(t) # linear algebra @test isa(@constinferred(norm(t)), real(T)) @@ -559,7 +559,7 @@ for V in spacelist @test t3 ≈ t4 end end - TK.empty_globalcaches!() + TensorKit.empty_globalcaches!() end @timedtestset "Deligne tensor product: test via conversion" begin From 617fc6c1c253cf4f3282959b406928c43933e408 Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Mon, 3 Nov 2025 15:25:04 +0100 Subject: [PATCH 25/95] another merge fix --- src/spaces/gradedspace.jl | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/spaces/gradedspace.jl b/src/spaces/gradedspace.jl index 44a8b230c..959263dde 100644 --- a/src/spaces/gradedspace.jl +++ b/src/spaces/gradedspace.jl @@ -92,10 +92,9 @@ Base.hash(V::GradedSpace, h::UInt) = hash(V.dual, hash(V.dims, h)) field(::Type{<:GradedSpace}) = ℂ InnerProductStyle(::Type{<:GradedSpace}) = EuclideanInnerProduct() function dim(V::GradedSpace) - return reduce( - +, dim(V, c) * dim(c) for c in sectors(V); - init = zero(dim(unit(sectortype(V)))) - ) + T = Base.promote_op(*, Int, real(sectorscalartype(sectortype(V)))) + return reduce(+, dim(V, c) * dim(c) for c in sectors(V); + init=zero(T)) end function dim(V::GradedSpace{I, <:AbstractDict}, c::I) where {I <: Sector} return get(V.dims, isdual(V) ? dual(c) : c, 0) From 61fa786c7786985157585e09a517efa24586b0c6 Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Mon, 3 Nov 2025 15:58:18 +0100 Subject: [PATCH 26/95] more merge changes + add `IsingBimodule` to sectorlist --- test/setup.jl | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/test/setup.jl b/test/setup.jl index 0af9ce633..b64a9052a 100644 --- a/test/setup.jl +++ b/test/setup.jl @@ -25,14 +25,15 @@ end function randsector(::Type{I}) where {I <: Sector} s = collect(smallset(I)) a = rand(s) - while a == one(a) # don't use trivial label + while isunit(a) # don't use trivial label a = rand(s) end return a end function hasfusiontensor(I::Type{<:Sector}) + isa(UnitStyle(I), GenericUnit) && return false try - TensorKit.fusiontensor(one(I), one(I), one(I)) + TensorKit.fusiontensor(unit(I), unit(I), unit(I)) return true catch e if e isa MethodError @@ -81,6 +82,7 @@ sectorlist = ( FermionParity ⊠ U1Irrep ⊠ SU2Irrep, FermionParity ⊠ SU2Irrep ⊠ SU2Irrep, # Hubbard-like FibonacciAnyon, IsingAnyon, Z2Irrep ⊠ FibonacciAnyon ⊠ FibonacciAnyon, + IsingBimodule, IsingBimodule ⊠ SU2Irrep, IsingBimodule ⊠ IsingBimodule, ) # spaces @@ -158,5 +160,6 @@ Vfib = ( Vect[FibonacciAnyon](:I => 2, :τ => 3), Vect[FibonacciAnyon](:I => 2, :τ => 2), ) +#TODO: add IsingBimodule spaces end From db80ca88b282e5b5848c638947fb29996f578292 Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Mon, 3 Nov 2025 15:58:40 +0100 Subject: [PATCH 27/95] make spaces tests multifusion-friendly --- test/symmetries/spaces.jl | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/test/symmetries/spaces.jl b/test/symmetries/spaces.jl index 5a020214f..33f895eb7 100644 --- a/test/symmetries/spaces.jl +++ b/test/symmetries/spaces.jl @@ -220,13 +220,21 @@ end @test eval_show(typeof(V)) == typeof(V) # space with no sectors @test dim(@constinferred(zerospace(V))) == 0 - # space with a single sector - W = @constinferred GradedSpace(unit(I) => 1) - @test W == GradedSpace(unit(I) => 1, randsector(I) => 0) + # space with unit(s) + if isa(UnitStyle(I), GenericUnit) + W = @constinferred GradedSpace(unit => 1 for unit in allunits(I)) + dict = Dict((unit => 1 for unit in allunits(I))..., randsector(I) => 0) + @test W == GradedSpace(dict) + @test @constinferred(zerospace(V)) == GradedSpace(unit => 0 for unit in allunits(I)) + # randsector never returns trivial sector, so this cannot error + @test_throws ArgumentError("Sector $(allunits(I)[1]) appears multiple times") GradedSpace(allunits(I)[1] => 1, randsector(I) => 0, allunits(I)[1] => 3) + else + W = @constinferred GradedSpace(unit(I) => 1) + @test W == GradedSpace(unit(I) => 1, randsector(I) => 0) + @test @constinferred(zerospace(V)) == GradedSpace(unit(I) => 0) + @test_throws ArgumentError("Sector $(unit(I)) appears multiple times") GradedSpace(unit(I) => 1, randsector(I) => 0, unit(I) => 3) + end @test @constinferred(unitspace(V)) == W == unitspace(typeof(V)) - @test @constinferred(zerospace(V)) == GradedSpace(unit(I) => 0) - # randsector never returns trivial sector, so this cannot error - @test_throws ArgumentError GradedSpace(unit(I) => 1, randsector(I) => 0, unit(I) => 3) @test eval_show(W) == W @test isa(V, VectorSpace) @test isa(V, ElementarySpace) @@ -265,7 +273,12 @@ end @test V == @constinferred infimum(V, ⊕(V, V)) @test V ≺ ⊕(V, V) @test !(V ≻ ⊕(V, V)) - @test infimum(V, GradedSpace(unit(I) => 3)) == GradedSpace(unit(I) => 2) + if isa(UnitStyle(I), GenericUnit) + u = allunits(I)[1] + else + u = unit(I) + end + @test infimum(V, GradedSpace(u => 3)) == GradedSpace(u => 2) @test_throws SpaceMismatch (⊕(V, V')) end From 6e990e2745487f69fcb29cc0e5cdd2b4820cc1fc Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Mon, 3 Nov 2025 15:59:31 +0100 Subject: [PATCH 28/95] apply tensors test changes from #263 --- test/tensors/tensors.jl | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/test/tensors/tensors.jl b/test/tensors/tensors.jl index e0e270fd2..959d03198 100644 --- a/test/tensors/tensors.jl +++ b/test/tensors/tensors.jl @@ -27,6 +27,7 @@ end for V in spacelist I = sectortype(first(V)) Istr = type_repr(I) + symmetricbraiding = isa(BraidingStyle(I), SymmetricBraiding) println("---------------------------------------") println("Tensors with symmetry: $Istr") println("---------------------------------------") @@ -224,6 +225,7 @@ for V in spacelist @test Base.promote_typeof(tc, t) == typeof(tc + t) end @timedtestset "Permutations: test via inner product invariance" begin + @assert symmetricbraiding W = V1 ⊗ V2 ⊗ V3 ⊗ V4 ⊗ V5 t = rand(ComplexF64, W) t′ = randn!(similar(t)) @@ -237,7 +239,7 @@ for V in spacelist @test dot(t2′, t2) ≈ dot(t′, t) ≈ dot(transpose(t2′), transpose(t2)) end - t3 = VERSION < v"1.7" ? repartition(t, k) : @constinferred repartition(t, $k) + t3 = @constinferred repartition(t, $k) @test norm(t3) ≈ norm(t) t3′ = @constinferred repartition!(similar(t3), t′) @test norm(t3′) ≈ norm(t′) @@ -269,28 +271,27 @@ for V in spacelist end end @timedtestset "Full trace: test self-consistency" begin - t = rand(ComplexF64, V1 ⊗ V2' ⊗ V2 ⊗ V1') - t2 = permute(t, ((1, 2), (4, 3))) - s = @constinferred tr(t2) - @test conj(s) ≈ tr(t2') + t = rand(ComplexF64, V1 ⊗ V2' ← V1 ⊗ V2') + s = @constinferred tr(t) + @test conj(s) ≈ tr(t') if !isdual(V1) - t2 = twist!(t2, 1) + t2 = twist!(t, 1) end if isdual(V2) - t2 = twist!(t2, 2) + t2 = twist!(t, 2) end ss = tr(t2) - @tensor s2 = t[a, b, b, a] - @tensor t3[a, b] := t[a, c, c, b] - @tensor s3 = t3[a, a] + @plansor s2 = t[a b; a b] + @plansor t3[a; b] := t[a c; b c] + @plansor s3 = t3[a; a] @test ss ≈ s2 @test ss ≈ s3 end @timedtestset "Partial trace: test self-consistency" begin - t = rand(ComplexF64, V1 ⊗ V2' ⊗ V3 ⊗ V2 ⊗ V1' ⊗ V3') - @tensor t2[a, b] := t[c, d, b, d, c, a] - @tensor t4[a, b, c, d] := t[d, e, b, e, c, a] - @tensor t5[a, b] := t4[a, b, c, c] + t = rand(ComplexF64, V1 ⊗ V2 ⊗ V3 ← V1 ⊗ V2 ⊗ V3) + @planar t2[a; b] := t[c d b; c d a] + @planar t4[a b; c d] := t[e d c; e b a] + @planar t5[a; b] := t4[a c; b c] @test t2 ≈ t5 end if BraidingStyle(I) isa Bosonic && hasfusiontensor(I) @@ -334,6 +335,7 @@ for V in spacelist end end @timedtestset "Index flipping: test via explicit flip" begin + @assert symmetricbraiding t = rand(ComplexF64, V1 ⊗ V1' ← V1' ⊗ V1) F1 = unitary(flip(V1), V1) @@ -347,6 +349,7 @@ for V in spacelist @test twist!(flip(t, 4), 4) ≈ tf end @timedtestset "Index flipping: test via contraction" begin + @assert symmetricbraiding t1 = rand(ComplexF64, V1 ⊗ V2 ⊗ V3 ← V4) t2 = rand(ComplexF64, V2' ⊗ V5 ← V4' ⊗ V1) @tensor ta[a, b] := t1[x, y, a, z] * t2[y, b, z, x] @@ -529,6 +532,7 @@ for V in spacelist end end @timedtestset "Tensor product: test via tensor contraction" begin + @assert symmetricbraiding for T in (Float32, ComplexF64) t1 = rand(T, V2 ⊗ V3 ⊗ V1) t2 = rand(T, V2 ⊗ V1 ⊗ V3) From b114f649036e749813dfdea2ea4f5a73ddd6aed4 Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Mon, 3 Nov 2025 16:35:54 +0100 Subject: [PATCH 29/95] avoid `one` call in `rank` of tensormap --- src/tensors/linalg.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tensors/linalg.jl b/src/tensors/linalg.jl index d09609edc..3783f42ae 100644 --- a/src/tensors/linalg.jl +++ b/src/tensors/linalg.jl @@ -296,7 +296,7 @@ function LinearAlgebra.rank( t::AbstractTensorMap; atol::Real = 0, rtol::Real = atol > 0 ? 0 : _default_rtol(t) ) - r = dim(one(sectortype(t))) * 0 + r = zero(real(sectorscalartype(sectortype(t)))) dim(t) == 0 && return r S = LinearAlgebra.svdvals(t) tol = max(atol, rtol * maximum(first, values(S))) From 313df5354cd6fbda128a120ada3be6ec45076496 Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Mon, 3 Nov 2025 17:16:50 +0100 Subject: [PATCH 30/95] use `sum` in `dim` of `GradedSpace` --- src/spaces/gradedspace.jl | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/spaces/gradedspace.jl b/src/spaces/gradedspace.jl index 959263dde..5da78e87e 100644 --- a/src/spaces/gradedspace.jl +++ b/src/spaces/gradedspace.jl @@ -92,9 +92,7 @@ Base.hash(V::GradedSpace, h::UInt) = hash(V.dual, hash(V.dims, h)) field(::Type{<:GradedSpace}) = ℂ InnerProductStyle(::Type{<:GradedSpace}) = EuclideanInnerProduct() function dim(V::GradedSpace) - T = Base.promote_op(*, Int, real(sectorscalartype(sectortype(V)))) - return reduce(+, dim(V, c) * dim(c) for c in sectors(V); - init=zero(T)) + return sum(dim(V, c) * dim(c) for c in sectors(V)) end function dim(V::GradedSpace{I, <:AbstractDict}, c::I) where {I <: Sector} return get(V.dims, isdual(V) ? dual(c) : c, 0) From bd97efd21c51648b3e10e434779730ab9803e187 Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Mon, 3 Nov 2025 17:17:26 +0100 Subject: [PATCH 31/95] change one more `oneunit` to `unitspace` --- src/tensors/diagonal.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tensors/diagonal.jl b/src/tensors/diagonal.jl index 904e40cba..7c0976522 100644 --- a/src/tensors/diagonal.jl +++ b/src/tensors/diagonal.jl @@ -306,7 +306,7 @@ function LinearAlgebra.pinv(d::DiagonalTensorMap; kwargs...) if iszero(atol) rtol = get(kwargs, :rtol, zero(real(T))) else - rtol = sqrt(eps(real(float(oneunit(T))))) * length(d.data) + rtol = sqrt(eps(real(float(unitspace(T))))) * length(d.data) end pdata = let tol = max(atol, rtol * maximum(abs, d.data)) map(x -> abs(x) < tol ? zero(x) : pinv(x), d.data) From e24f01d551310024df91f3d6f80cacfef0fa7f17 Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Tue, 4 Nov 2025 16:41:23 +0100 Subject: [PATCH 32/95] changes to `(left/right)unitspace` and `zerospace` --- src/spaces/gradedspace.jl | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/src/spaces/gradedspace.jl b/src/spaces/gradedspace.jl index 5da78e87e..c307fb082 100644 --- a/src/spaces/gradedspace.jl +++ b/src/spaces/gradedspace.jl @@ -142,7 +142,7 @@ function leftunitspace(S::GradedSpace{I}) where {I<:Sector} return unitspace(typeof(S)) else !isempty(sectors(S)) || throw(ArgumentError("Cannot determine type of empty space")) - allequal(a.row for a in sectors(S)) || + allequal(leftunit, sectors(S)) || throw(ArgumentError("sectors of $S do not have the same left unit")) sector = leftunit(first(sectors(S))) @@ -161,7 +161,7 @@ function rightunitspace(S::GradedSpace{I}) where {I<:Sector} return unitspace(typeof(S)) else !isempty(sectors(S)) || throw(ArgumentError("Cannot determine type of empty space")) - allequal(a.row for a in sectors(S)) || + allequal(rightunit, sectors(S)) || throw(ArgumentError("sectors of $S do not have the same right unit")) sector = rightunit(first(sectors(S))) @@ -170,19 +170,9 @@ function rightunitspace(S::GradedSpace{I}) where {I<:Sector} end function unitspace(S::Type{<:GradedSpace{I}}) where {I<:Sector} - if UnitStyle(I) isa SimpleUnit - return S(unit(I) => 1) - else - return S(unit => 1 for unit in allunits(I)) - end -end -function zerospace(S::Type{<:GradedSpace{I}}) where {I<:Sector} - if UnitStyle(I) isa SimpleUnit - return S(unit(I) => 0) - else - return S(unit => 0 for unit in allunits(I)) - end + return S(unit => 1 for unit in allunits(I)) end +zerospace(S::Type{<:GradedSpace}) = S() # TODO: the following methods can probably be implemented more efficiently for # `FiniteGradedSpace`, but we don't expect them to be used often in hot loops, so From 9ffa2dcc5ecf08a65f3d67e54bdadee07477662d Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Wed, 5 Nov 2025 08:40:26 +0100 Subject: [PATCH 33/95] remove module specification --- src/spaces/productspace.jl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/spaces/productspace.jl b/src/spaces/productspace.jl index 3b6236120..6c8c02cf0 100644 --- a/src/spaces/productspace.jl +++ b/src/spaces/productspace.jl @@ -269,10 +269,10 @@ function insertleftunitspace(P::ProductSpace, ::Val{i}=Val(length(P) + 1); u = leftunitspace(P[i]) end if dual - u = TensorKit.dual(u) + u = dual(u) end if conj - u = TensorKit.conj(u) + u = conj(u) end return ProductSpace(TupleTools.insertafter(P.spaces, i - 1, (u,))) end @@ -298,10 +298,10 @@ function insertrightunitspace(P::ProductSpace, ::Val{i}=Val(length(P)); u = rightunitspace(P[i]) end if dual - u = TensorKit.dual(u) + u = dual(u) end if conj - u = TensorKit.conj(u) + u = conj(u) end return ProductSpace(TupleTools.insertafter(P.spaces, i, (u,))) end From 6065d25a2d58ca0e465919f9b7d72899796e57f3 Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Wed, 5 Nov 2025 08:53:16 +0100 Subject: [PATCH 34/95] Revert "remove module specification" This reverts commit 9ffa2dcc5ecf08a65f3d67e54bdadee07477662d. --- src/spaces/productspace.jl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/spaces/productspace.jl b/src/spaces/productspace.jl index 6c8c02cf0..3b6236120 100644 --- a/src/spaces/productspace.jl +++ b/src/spaces/productspace.jl @@ -269,10 +269,10 @@ function insertleftunitspace(P::ProductSpace, ::Val{i}=Val(length(P) + 1); u = leftunitspace(P[i]) end if dual - u = dual(u) + u = TensorKit.dual(u) end if conj - u = conj(u) + u = TensorKit.conj(u) end return ProductSpace(TupleTools.insertafter(P.spaces, i - 1, (u,))) end @@ -298,10 +298,10 @@ function insertrightunitspace(P::ProductSpace, ::Val{i}=Val(length(P)); u = rightunitspace(P[i]) end if dual - u = dual(u) + u = TensorKit.dual(u) end if conj - u = conj(u) + u = TensorKit.conj(u) end return ProductSpace(TupleTools.insertafter(P.spaces, i, (u,))) end From b8a32532b85f0dc6d8cf0b969a42c485e1566778 Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Wed, 5 Nov 2025 09:38:55 +0100 Subject: [PATCH 35/95] keep `init` in `dim` and deal with repercussions in src --- src/spaces/gradedspace.jl | 3 ++- src/tensors/abstracttensor.jl | 2 +- src/tensors/tensor.jl | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/spaces/gradedspace.jl b/src/spaces/gradedspace.jl index c307fb082..23ab43f3b 100644 --- a/src/spaces/gradedspace.jl +++ b/src/spaces/gradedspace.jl @@ -92,7 +92,8 @@ Base.hash(V::GradedSpace, h::UInt) = hash(V.dual, hash(V.dims, h)) field(::Type{<:GradedSpace}) = ℂ InnerProductStyle(::Type{<:GradedSpace}) = EuclideanInnerProduct() function dim(V::GradedSpace) - return sum(dim(V, c) * dim(c) for c in sectors(V)) + T = promote_type(Int, real(sectorscalartype(sectortype(V)))) + return sum(dim(V, c) * dim(c) for c in sectors(V); init = zero(T)) end function dim(V::GradedSpace{I, <:AbstractDict}, c::I) where {I <: Sector} return get(V.dims, isdual(V) ? dual(c) : c, 0) diff --git a/src/tensors/abstracttensor.jl b/src/tensors/abstracttensor.jl index 69fdce784..dac9470ae 100644 --- a/src/tensors/abstracttensor.jl +++ b/src/tensors/abstracttensor.jl @@ -617,7 +617,7 @@ function Base.convert(::Type{Array}, t::AbstractTensorMap) dom = domain(t) T = sectorscalartype(I) <: Complex ? complex(scalartype(t)) : sectorscalartype(I) <: Integer ? scalartype(t) : float(scalartype(t)) - A = zeros(T, dims(t)...) + A = zeros(T, Int.(dims(t))...) for (f₁, f₂) in fusiontrees(t) F = convert(Array, (f₁, f₂)) Aslice = StridedView(A)[axes(cod, f₁.uncoupled)..., axes(dom, f₂.uncoupled)...] diff --git a/src/tensors/tensor.jl b/src/tensors/tensor.jl index fe15d819d..725904472 100644 --- a/src/tensors/tensor.jl +++ b/src/tensors/tensor.jl @@ -333,7 +333,7 @@ function TensorMap( # dimension check codom = codomain(V) dom = domain(V) - arraysize = dims(V) + arraysize = Int.(dims(V)) matsize = (dim(codom), dim(dom)) if !(size(data) == arraysize || size(data) == matsize) From f54f6131ac39252902c7f6fd043489d31d1b6aed Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Wed, 5 Nov 2025 11:26:03 +0100 Subject: [PATCH 36/95] change `removeunitspace` to look for any unit instead of all --- src/spaces/productspace.jl | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/spaces/productspace.jl b/src/spaces/productspace.jl index 3b6236120..2814cde28 100644 --- a/src/spaces/productspace.jl +++ b/src/spaces/productspace.jl @@ -318,7 +318,15 @@ and [`insertrightunitspace`](@ref insertrightunitspace(::ProductSpace, ::Val{i}) """ function removeunitspace(P::ProductSpace, ::Val{i}) where {i} 1 ≤ i ≤ length(P) || _boundserror(P, i) - isisomorphic(P[i], unitspace(P[i])) || _nontrivialspaceerror(P, i) + I = sectortype(P) + if isa(UnitStyle(I), SimpleUnit) + isisomorphic(P[i], unitspace(P[i])) || _nontrivialspaceerror(P, i) + else + isisomorphic(P[i], leftunitspace(P[i])) || + isisomorphic(P[i], rightunitspace(P[i])) || + isisomorphic(P[i], unitspace(P[i])) || + _nontrivialspaceerror(P, i) + end return ProductSpace{spacetype(P)}(TupleTools.deleteat(P.spaces, i)) end From d3a0dd0c33cd7e4b96135eed7a07752f4c1f90bb Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Wed, 5 Nov 2025 11:26:31 +0100 Subject: [PATCH 37/95] add `IsingBimodule` spaces and help functions for fusiontree tests --- test/setup.jl | 53 +++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 51 insertions(+), 2 deletions(-) diff --git a/test/setup.jl b/test/setup.jl index b64a9052a..466fe0198 100644 --- a/test/setup.jl +++ b/test/setup.jl @@ -1,8 +1,9 @@ module TestSetup export smallset, randsector, hasfusiontensor, force_planar +export random_fusiontree, safe_tensor_product export sectorlist -export Vtr, Vℤ₂, Vfℤ₂, Vℤ₃, VU₁, VfU₁, VCU₁, VSU₂, VfSU₂, VSU₂U₁, Vfib +export Vtr, Vℤ₂, Vfℤ₂, Vℤ₃, VU₁, VfU₁, VCU₁, VSU₂, VfSU₂, VSU₂U₁, Vfib, VIB_diag, VIB_M using Random using TensorKit @@ -75,6 +76,34 @@ function force_planar(tsrc::TensorMap{<:Any, <:GradedSpace}) return tdst end +function random_fusiontree(I::Type{<:Sector}, N::Int) # for fusion tree tests + in = nothing + out = nothing + while in === nothing + out = ntuple(n -> randsector(I), N) + try + in = rand(collect(⊗(out...))) + catch e + if isa(e, ArgumentError) + in = nothing + else + rethrow(e) + end + end + end + return out +end + +# for fusion tree merge test +function safe_tensor_product(x::I, y::I) where {I<:Sector} + tp = x ⊗ y + if isempty(tp) + return nothing + else + return tp + end +end + sectorlist = ( Z2Irrep, Z3Irrep, Z4Irrep, Z3Irrep ⊠ Z4Irrep, U1Irrep, CU1Irrep, SU2Irrep, @@ -160,6 +189,26 @@ Vfib = ( Vect[FibonacciAnyon](:I => 2, :τ => 3), Vect[FibonacciAnyon](:I => 2, :τ => 2), ) -#TODO: add IsingBimodule spaces + +C0, C1 = IsingBimodule(1, 1, 0), IsingBimodule(1, 1, 1) +D0, D1 = IsingBimodule(2, 2, 0), IsingBimodule(2, 2, 1) +M, Mop = IsingBimodule(1, 2, 0), IsingBimodule(2, 1, 0) +VIB_diag = ( + Vect[IsingBimodule](C0 => 1, C1 => 2), + Vect[IsingBimodule](C0 => 2, C1 => 1), + Vect[IsingBimodule](C0 => 3, C1 => 1), + Vect[IsingBimodule](C0 => 2, C1 => 3), + Vect[IsingBimodule](C0 => 3, C1 => 2) +) + +# not a random ordering! designed to make V1 ⊗ V2 ← V3 ⊗ V4 ⊗ V5 work (tensors) +# while V1 ⊗ V2 ← V4 isn't empty (factorizations) +VIB_M = ( + Vect[IsingBimodule](C0 => 1, C1 => 2), + Vect[IsingBimodule](M => 3), + Vect[IsingBimodule](C0 => 2, C1 => 3), + Vect[IsingBimodule](M => 4), + Vect[IsingBimodule](D0 => 3, D1 => 4) +) end From 2735ab141efadde23fe8e41a298c4e61f3460046 Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Wed, 5 Nov 2025 11:27:17 +0100 Subject: [PATCH 38/95] rewrite and reorganise fusiontree tests --- test/symmetries/fusiontrees.jl | 691 +++++++++++++++++---------------- 1 file changed, 362 insertions(+), 329 deletions(-) diff --git a/test/symmetries/fusiontrees.jl b/test/symmetries/fusiontrees.jl index 703a36c69..8ecf28b23 100644 --- a/test/symmetries/fusiontrees.jl +++ b/test/symmetries/fusiontrees.jl @@ -10,10 +10,10 @@ using TensorKitSectors @isdefined(TestSetup) || include("../setup.jl") using .TestSetup -@timedtestset "Fusion trees for $(TensorKit.type_repr(I))" verbose = true for I in sectorlist +@timedtestset "Fusion trees for $(TensorKit.type_repr(I))" verbose = true for I in sectorlist # product sectors with multifusion doesn't work because of isunit Istr = TensorKit.type_repr(I) N = 5 - out = ntuple(n -> randsector(I), N) + out = random_fusiontree(I, N) isdual = ntuple(n -> rand(Bool), N) in = rand(collect(⊗(out...))) numtrees = length(fusiontrees(out, in, isdual)) @@ -33,55 +33,71 @@ using .TestSetup @test eval(Meta.parse(sprint(show, f; context = (:module => @__MODULE__)))) == f end @testset "Fusion tree $Istr: constructor properties" begin - u = unit(I) - @constinferred FusionTree((), u, (), (), ()) - @constinferred FusionTree((u,), u, (false,), (), ()) - @constinferred FusionTree((u, u), u, (false, false), (), (1,)) - @constinferred FusionTree((u, u, u), u, (false, false, false), (u,), (1, 1)) - @constinferred FusionTree( - (u, u, u, u), u, (false, false, false, false), (u, u), (1, 1, 1) - ) - @test_throws MethodError FusionTree((u, u, u), u, (false, false), (u,), (1, 1)) - @test_throws MethodError FusionTree( - (u, u, u), u, (false, false, false), (u, u), (1, 1) - ) - @test_throws MethodError FusionTree( - (u, u, u), u, (false, false, false), (u,), (1, 1, 1) - ) - @test_throws MethodError FusionTree((u, u, u), u, (false, false, false), (), (1,)) - - f = FusionTree((u, u, u), u, (false, false, false), (u,), (1, 1)) - @test sectortype(f) == I - @test length(f) == 3 - @test FusionStyle(f) == FusionStyle(I) - @test BraidingStyle(f) == BraidingStyle(I) - - if FusionStyle(I) isa UniqueFusion - @constinferred FusionTree((), u, ()) - @constinferred FusionTree((u,), u, (false,)) - @constinferred FusionTree((u, u), u, (false, false)) - @constinferred FusionTree((u, u, u), u) - @constinferred FusionTree((u, u, u, u)) - @test_throws MethodError FusionTree((u, u), u, (false, false, false)) - else - errstr = "fusion tree requires inner lines if `FusionStyle(I) <: MultipleFusion`" - @test_throws errstr FusionTree((), u, ()) - @test_throws errstr FusionTree((u,), u, (false,)) - @test_throws errstr FusionTree((u, u), u, (false, false)) - @test_throws errstr FusionTree((u, u, u), u) - @test_throws errstr FusionTree((u, u, u, u)) + for u in allunits(I) + @constinferred FusionTree((), u, (), (), ()) + @constinferred FusionTree((u,), u, (false,), (), ()) + @constinferred FusionTree((u, u), u, (false, false), (), (1,)) + @constinferred FusionTree((u, u, u), u, (false, false, false), (u,), (1, 1)) + @constinferred FusionTree( + (u, u, u, u), u, (false, false, false, false), (u, u), (1, 1, 1) + ) + @test_throws MethodError FusionTree((u, u, u), u, (false, false), (u,), (1, 1)) + @test_throws MethodError FusionTree( + (u, u, u), u, (false, false, false), (u, u), (1, 1) + ) + @test_throws MethodError FusionTree( + (u, u, u), u, (false, false, false), (u,), (1, 1, 1) + ) + @test_throws MethodError FusionTree((u, u, u), u, (false, false, false), (), (1,)) + + f = FusionTree((u, u, u), u, (false, false, false), (u,), (1, 1)) + @test sectortype(f) == I + @test length(f) == 3 + @test FusionStyle(f) == FusionStyle(I) + @test BraidingStyle(f) == BraidingStyle(I) + + if FusionStyle(I) isa UniqueFusion + @constinferred FusionTree((), u, ()) + @constinferred FusionTree((u,), u, (false,)) + @constinferred FusionTree((u, u), u, (false, false)) + @constinferred FusionTree((u, u, u), u) + if isa(UnitStyle(I), SimpleUnit) + @constinferred FusionTree((u, u, u, u)) + end + @test_throws MethodError FusionTree((u, u), u, (false, false, false)) + else + errstr = "fusion tree requires inner lines if `FusionStyle(I) <: MultipleFusion`" + @test_throws errstr FusionTree((), u, ()) + @test_throws errstr FusionTree((u,), u, (false,)) + @test_throws errstr FusionTree((u, u), u, (false, false)) + @test_throws errstr FusionTree((u, u, u), u) + if isa(UnitStyle(I), SimpleUnit) + @test_throws errstr FusionTree((u, u, u, u)) + end + end end end @testset "Fusion tree $Istr: insertat" begin N = 4 - out2 = ntuple(n -> randsector(I), N) + out2 = random_fusiontree(I, N) in2 = rand(collect(⊗(out2...))) isdual2 = ntuple(n -> rand(Bool), N) f2 = rand(collect(fusiontrees(out2, in2, isdual2))) for i in 1:N - out1 = ntuple(n -> randsector(I), N) - out1 = Base.setindex(out1, in2, i) - in1 = rand(collect(⊗(out1...))) + out1, in1 = nothing, nothing + while in1 === nothing + try + out1 = random_fusiontree(I, N) # guaranteed good fusion + out1 = Base.setindex(out1, in2, i) # can lead to poor fusion + in1 = rand(collect(⊗(out1...))) + catch e + if isa(e, ArgumentError) + in1 = nothing + else + rethrow(e) + end + end + end isdual1 = ntuple(n -> rand(Bool), N) isdual1 = Base.setindex(isdual1, false, i) f1 = rand(collect(fusiontrees(out1, in1, isdual1))) @@ -93,25 +109,27 @@ using .TestSetup @test length(TK.insertat(f1b, 1, f1a)) == 1 @test first(TK.insertat(f1b, 1, f1a)) == (f1 => 1) - levels = ntuple(identity, N) - function _reinsert_partial_tree(t, f) - (t′, c′) = first(TK.insertat(t, 1, f)) - @test c′ == one(c′) - return t′ - end - braid_i_to_1 = braid(f1, levels, (i, (1:(i - 1))..., ((i + 1):N)...)) - trees2 = Dict(_reinsert_partial_tree(t, f2) => c for (t, c) in braid_i_to_1) - 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 braid(t, levels, p) - trees3[t′] = get(trees3, t′, zero(coeff′)) + coeff * coeff′ + if isa(UnitStyle(I), SimpleUnit) + levels = ntuple(identity, N) + function _reinsert_partial_tree(t, f) + (t′, c′) = first(TK.insertat(t, 1, f)) + @test c′ == one(c′) + return t′ + end + braid_i_to_1 = braid(f1, levels, (i, (1:(i - 1))..., ((i + 1):N)...)) + trees2 = Dict(_reinsert_partial_tree(t, f2) => c for (t, c) in braid_i_to_1) + 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 braid(t, levels, p) + trees3[t′] = get(trees3, t′, zero(coeff′)) + coeff * coeff′ + end + end + for (t, coeff) in trees3 + coeff′ = get(trees, t, zero(coeff)) + @test isapprox(coeff′, coeff; atol = 1.0e-12, rtol = 1.0e-12) end - end - for (t, coeff) in trees3 - coeff′ = get(trees, t, zero(coeff)) - @test isapprox(coeff′, coeff; atol = 1.0e-12, rtol = 1.0e-12) end if (BraidingStyle(I) isa Bosonic) && hasfusiontensor(I) @@ -223,102 +241,113 @@ using .TestSetup end end end - @testset "Fusion tree $Istr: elementary artin braid" begin - N = length(out) - isdual = ntuple(n -> rand(Bool), N) - for in in ⊗(out...) - for i in 1:(N - 1) - for f in fusiontrees(out, in, isdual) - 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 + if isa(UnitStyle(I), SimpleUnit) + @testset "Fusion tree $Istr: elementary artin braid" begin + N = length(out) + isdual = ntuple(n -> rand(Bool), N) + for in in ⊗(out...) + for i in 1:(N - 1) + for f in fusiontrees(out, in, isdual) + d1 = @constinferred TK.artin_braid(f, i) # braid between random objects + @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 - end - for (f2, coeff2) in d2 - if f2 == f - @test coeff2 ≈ 1 - else - @test isapprox(coeff2, 0; atol = 1.0e-12, rtol = 1.0e-12) + for (f2, coeff2) in d2 + if f2 == f + @test coeff2 ≈ 1 + else + @test isapprox(coeff2, 0; atol = 1.0e-12, rtol = 1.0e-12) + end 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 + 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 - 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 + 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 - 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 + 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 - end - d1 = d2 - for (f1, coeff1) in d1 - if f1 == f - @test coeff1 ≈ 1 - else - @test isapprox(coeff1, 0; atol = 1.0e-12, rtol = 1.0e-12) + d1 = d2 + for (f1, coeff1) in d1 + if f1 == f + @test coeff1 ≈ 1 + else + @test isapprox(coeff1, 0; atol = 1.0e-12, rtol = 1.0e-12) + end end end - end - @testset "Fusion tree $Istr: braiding and permuting" begin - f = rand(collect(it)) - p = tuple(randperm(N)...) - ip = invperm(p) - - levels = ntuple(identity, N) - d = @constinferred braid(f, levels, p) - d2 = Dict{typeof(f), valtype(d)}() - levels2 = p - for (f2, coeff) in d - for (f1, coeff2) in braid(f2, levels2, ip) - d2[f1] = get(d2, f1, zero(coeff)) + coeff2 * coeff + @testset "Fusion tree $Istr: braiding and permuting" begin + f = rand(collect(it)) + p = tuple(randperm(N)...) + ip = invperm(p) + + levels = ntuple(identity, N) + d = @constinferred braid(f, levels, p) + d2 = Dict{typeof(f), valtype(d)}() + levels2 = p + for (f2, coeff) in d + for (f1, coeff2) in braid(f2, levels2, ip) + d2[f1] = get(d2, f1, zero(coeff)) + coeff2 * coeff + end end - end - for (f1, coeff2) in d2 - if f1 == f - @test coeff2 ≈ 1 - else - @test isapprox(coeff2, 0; atol = 1.0e-12, rtol = 1.0e-12) + for (f1, coeff2) in d2 + if f1 == f + @test coeff2 ≈ 1 + else + @test isapprox(coeff2, 0; atol = 1.0e-12, rtol = 1.0e-12) + end end - end - if (BraidingStyle(I) isa Bosonic) && hasfusiontensor(I) - Af = convert(Array, f) - Afp = permutedims(Af, (p..., N + 1)) - Afp2 = zero(Afp) - for (f1, coeff) in d - Afp2 .+= coeff .* convert(Array, f1) + if (BraidingStyle(I) isa Bosonic) && hasfusiontensor(I) + Af = convert(Array, f) + Afp = permutedims(Af, (p..., N + 1)) + Afp2 = zero(Afp) + for (f1, coeff) in d + Afp2 .+= coeff .* convert(Array, f1) + end + @test Afp ≈ Afp2 end - @test Afp ≈ Afp2 end end @testset "Fusion tree $Istr: merging" begin N = 3 - out1 = ntuple(n -> randsector(I), N) + out1 = random_fusiontree(I, N) + out2 = random_fusiontree(I, N) in1 = rand(collect(⊗(out1...))) - f1 = rand(collect(fusiontrees(out1, in1))) - out2 = ntuple(n -> randsector(I), N) in2 = rand(collect(⊗(out2...))) + tp = safe_tensor_product(in1, in2) # messy solution but it works + while tp === nothing + out1 = random_fusiontree(I, N) + out2 = random_fusiontree(I, N) + in1 = rand(collect(⊗(out1...))) + in2 = rand(collect(⊗(out2...))) + tp = safe_tensor_product(in1, in2) + end + + f1 = rand(collect(fusiontrees(out1, in1))) f2 = rand(collect(fusiontrees(out2, in2))) @constinferred TK.merge(f1, f2, first(in1 ⊗ in2), 1) @@ -332,136 +361,200 @@ using .TestSetup for (f, coeff) in TK.merge(f1, f2, c, μ) ) - 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[μ, ν] + if !isa(BraidingStyle(I), NoBraiding) + 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 - end - perm = ((N .+ (1:N))..., (1:N)...) - levels = ntuple(identity, 2 * N) - for (t, coeff) in trees1 - for (t′, coeff′) in braid(t, levels, perm) - trees3[t′] = get(trees3, t′, zero(valtype(trees3))) + coeff * coeff′ + perm = ((N .+ (1:N))..., (1:N)...) + levels = ntuple(identity, 2 * N) + for (t, coeff) in trees1 + for (t′, coeff′) in braid(t, levels, perm) + trees3[t′] = get(trees3, t′, zero(valtype(trees3))) + coeff * coeff′ + end + end + for (t, coeff) in trees3 + coeff′ = get(trees2, t, zero(coeff)) + @test isapprox(coeff, coeff′; atol = 1.0e-12, rtol = 1.0e-12) end - end - for (t, coeff) in trees3 - coeff′ = get(trees2, t, zero(coeff)) - @test isapprox(coeff, coeff′; atol = 1.0e-12, rtol = 1.0e-12) - end - # test via conversion - if (BraidingStyle(I) isa Bosonic) && hasfusiontensor(I) - Af1 = convert(Array, f1) - Af2 = convert(Array, f2) - Af0 = convert( - Array, - FusionTree((f1.coupled, f2.coupled), c, (false, false), (), (μ,)) - ) - _Af = TensorOperations.tensorcontract( - 1:(N + 2), Af1, [1:N; -1], Af0, [-1; N + 1; N + 2] - ) - Af = TensorOperations.tensorcontract( - 1:(2N + 1), Af2, [N .+ (1:N); -1], _Af, [1:N; -1; 2N + 1] - ) - Af′ = zero(Af) - for (f, coeff) in trees1 - Af′ .+= coeff .* convert(Array, f) + # test via conversion + if (BraidingStyle(I) isa Bosonic) && hasfusiontensor(I) + Af1 = convert(Array, f1) + Af2 = convert(Array, f2) + Af0 = convert( + Array, + FusionTree((f1.coupled, f2.coupled), c, (false, false), (), (μ,)) + ) + _Af = TensorOperations.tensorcontract( + 1:(N + 2), Af1, [1:N; -1], Af0, [-1; N + 1; N + 2] + ) + Af = TensorOperations.tensorcontract( + 1:(2N + 1), Af2, [N .+ (1:N); -1], _Af, [1:N; -1; 2N + 1] + ) + Af′ = zero(Af) + for (f, coeff) in trees1 + Af′ .+= coeff .* convert(Array, f) + end + @test Af ≈ Af′ end - @test Af ≈ Af′ end end end end - if I <: ProductSector - N = 3 - else - N = 4 - end - out = ntuple(n -> randsector(I), N) - numtrees = count(n -> true, fusiontrees((out..., map(dual, out)...))) - while !(0 < numtrees < 100) - out = ntuple(n -> randsector(I), N) + if isa(UnitStyle(I), SimpleUnit) # could get these to work for GenericUnit, but somewhat hardcoded + if I <: ProductSector + N = 3 + else + N = 4 + end + + out = random_fusiontree(I, N) 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)))) - - @testset "Double fusion tree $Istr: 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 + while !(0 < numtrees < 100) + out = random_fusiontree(I, N) + 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)))) + @testset "Double fusion tree $Istr: 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 - end - for ((f1′, f2′), coeff2) in d2 - if f1 == f1′ && f2 == f2′ - @test coeff2 ≈ 1 - else - @test isapprox(coeff2, 0; atol = 1.0e-12, rtol = 1.0e-12) + for ((f1′, f2′), coeff2) in d2 + if f1 == f1′ && f2 == f2′ + @test coeff2 ≈ 1 + else + @test isapprox(coeff2, 0; atol = 1.0e-12, rtol = 1.0e-12) + end end - end - if (BraidingStyle(I) isa Bosonic) && hasfusiontensor(I) - Af1 = convert(Array, f1) - Af2 = permutedims(convert(Array, 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)]...) - ) - A2 = zero(A) - for ((f1′, f2′), coeff) in d - Af1′ = convert(Array, f1′) - Af2′ = permutedims(convert(Array, 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)]...) + if (BraidingStyle(I) isa Bosonic) && hasfusiontensor(I) + Af1 = convert(Array, f1) + Af2 = permutedims(convert(Array, 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)]...) ) + A2 = zero(A) + for ((f1′, f2′), coeff) in d + Af1′ = convert(Array, f1′) + Af2′ = permutedims(convert(Array, 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)]...) + ) + end + @test A ≈ A2 end - @test A ≈ A2 end end - end - @testset "Double fusion tree $Istr: permutation" begin - if BraidingStyle(I) isa SymmetricBraiding - for n in 0:(2N) - p = (randperm(2 * N)...,) - p1, p2 = p[1:n], p[(n + 1):(2N)] - ip = invperm(p) - ip1, ip2 = ip[1:N], ip[(N + 1):(2N)] + @testset "Double fusion tree $Istr: permutation" begin + if BraidingStyle(I) isa SymmetricBraiding + for n in 0:(2N) + p = (randperm(2 * N)...,) + p1, p2 = p[1:n], p[(n + 1):(2N)] + ip = invperm(p) + ip1, ip2 = ip[1:N], ip[(N + 1):(2N)] + + d = @constinferred TK.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′ = TK.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 + else + @test abs(coeff2) < 1.0e-12 + end + end - d = @constinferred TK.permute(f1, f2, p1, p2) + if (BraidingStyle(I) isa Bosonic) && hasfusiontensor(I) + Af1 = convert(Array, f1) + Af2 = convert(Array, 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(Array, f1′) + Af2′ = convert(Array, 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)]...) + ) + end + @test Ap ≈ A2 + end + end + end + end + @testset "Double fusion tree $Istr: transposition" begin + for n in 0:(2N) + i0 = rand(1:(2N)) + p = mod1.(i0 .+ (1:(2N)), 2N) + ip = mod1.(-i0 .+ (1:(2N)), 2N) + p′ = tuple(getindex.(Ref(vcat(1:N, (2N):-1:(N + 1))), p)...) + p1, p2 = p′[1:n], p′[(2N):-1:(n + 1)] + ip′ = tuple(getindex.(Ref(vcat(1:n, (2N):-1:(n + 1))), ip)...) + ip1, ip2 = ip′[1:N], ip′[(2N):-1:(N + 1)] + + d = @constinferred transpose(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′ = TK.permute(f1′, f2′, ip1, ip2) + d′ = transpose(f1′, f2′, ip1, ip2) for ((f1′′, f2′′), coeff2) in d′ - d2[(f1′′, f2′′)] = get(d2, (f1′′, f2′′), zero(coeff)) + - coeff2 * coeff + d2[(f1′′, f2′′)] = get(d2, (f1′′, f2′′), zero(coeff)) + coeff2 * coeff end end for ((f1′, f2′), coeff2) in d2 @@ -472,6 +565,15 @@ using .TestSetup end end + if BraidingStyle(I) isa Bosonic + d3 = permute(f1, f2, p1, p2) + for (f1′, f2′) in union(keys(d), keys(d3)) + coeff1 = get(d, (f1′, f2′), zero(valtype(d))) + coeff3 = get(d3, (f1′, f2′), zero(valtype(d3))) + @test isapprox(coeff1, coeff3; atol = 1.0e-12) + end + end + if (BraidingStyle(I) isa Bosonic) && hasfusiontensor(I) Af1 = convert(Array, f1) Af2 = convert(Array, f2) @@ -504,97 +606,28 @@ using .TestSetup end end end - end - @testset "Double fusion tree $Istr: transposition" begin - for n in 0:(2N) - i0 = rand(1:(2N)) - p = mod1.(i0 .+ (1:(2N)), 2N) - ip = mod1.(-i0 .+ (1:(2N)), 2N) - p′ = tuple(getindex.(Ref(vcat(1:N, (2N):-1:(N + 1))), p)...) - p1, p2 = p′[1:n], p′[(2N):-1:(n + 1)] - ip′ = tuple(getindex.(Ref(vcat(1:n, (2N):-1:(n + 1))), ip)...) - ip1, ip2 = ip′[1:N], ip′[(2N):-1:(N + 1)] - - d = @constinferred transpose(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′ = transpose(f1′, f2′, ip1, ip2) - for ((f1′′, f2′′), coeff2) in d′ - d2[(f1′′, f2′′)] = get(d2, (f1′′, f2′′), zero(coeff)) + coeff2 * coeff + @testset "Double fusion tree $Istr: planar trace" begin + d1 = transpose(f1, f1, (N + 1, 1:N..., ((2N):-1:(N + 3))...), (N + 2,)) + f1front, = TK.split(f1, N - 1) + T = sectorscalartype(I) + d2 = Dict{typeof((f1front, f1front)), T}() + for ((f1′, f2′), coeff′) in d1 + for ((f1′′, f2′′), coeff′′) in + TK.planar_trace( + f1′, f2′, (2:N...,), (1, ((2N):-1:(N + 3))...), (N + 1,), + (N + 2,) + ) + coeff = coeff′ * coeff′′ + d2[(f1′′, f2′′)] = get(d2, (f1′′, f2′′), zero(coeff)) + coeff end end - for ((f1′, f2′), coeff2) in d2 - if f1 == f1′ && f2 == f2′ - @test coeff2 ≈ 1 + for ((f1_, f2_), coeff) in d2 + if (f1_, f2_) == (f1front, f1front) + @test coeff ≈ dim(f1.coupled) / dim(f1front.coupled) else - @test abs(coeff2) < 1.0e-12 + @test abs(coeff) < 1.0e-12 end end - - if BraidingStyle(I) isa Bosonic - d3 = permute(f1, f2, p1, p2) - for (f1′, f2′) in union(keys(d), keys(d3)) - coeff1 = get(d, (f1′, f2′), zero(valtype(d))) - coeff3 = get(d3, (f1′, f2′), zero(valtype(d3))) - @test isapprox(coeff1, coeff3; atol = 1.0e-12) - end - end - - if (BraidingStyle(I) isa Bosonic) && hasfusiontensor(I) - Af1 = convert(Array, f1) - Af2 = convert(Array, 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(Array, f1′) - Af2′ = convert(Array, 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)]...) - ) - end - @test Ap ≈ A2 - end - end - end - @testset "Double fusion tree $Istr: planar trace" begin - d1 = transpose(f1, f1, (N + 1, 1:N..., ((2N):-1:(N + 3))...), (N + 2,)) - f1front, = TK.split(f1, N - 1) - T = sectorscalartype(I) - d2 = Dict{typeof((f1front, f1front)), T}() - for ((f1′, f2′), coeff′) in d1 - for ((f1′′, f2′′), coeff′′) in - TK.planar_trace( - f1′, f2′, (2:N...,), (1, ((2N):-1:(N + 3))...), (N + 1,), - (N + 2,) - ) - coeff = coeff′ * coeff′′ - d2[(f1′′, f2′′)] = get(d2, (f1′′, f2′′), zero(coeff)) + coeff - end - end - for ((f1_, f2_), coeff) in d2 - if (f1_, f2_) == (f1front, f1front) - @test coeff ≈ dim(f1.coupled) / dim(f1front.coupled) - else - @test abs(coeff) < 1.0e-12 - end end end TK.empty_globalcaches!() From a6eb01b604e6b694a43a2216bbfb36940649611e Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Wed, 5 Nov 2025 11:27:30 +0100 Subject: [PATCH 39/95] rewrite and reorganise factorisation tests --- test/tensors/factorizations.jl | 53 +++++++++++++++++----------------- 1 file changed, 27 insertions(+), 26 deletions(-) diff --git a/test/tensors/factorizations.jl b/test/tensors/factorizations.jl index c5b4e8748..5a8ec87df 100644 --- a/test/tensors/factorizations.jl +++ b/test/tensors/factorizations.jl @@ -9,17 +9,17 @@ spacelist = try if ENV["CI"] == "true" println("Detected running on CI") if Sys.iswindows() - (Vtr, Vℤ₃, VU₁, VfU₁, VCU₁, VSU₂) + (Vtr, Vℤ₃, VU₁, VfU₁, VCU₁, VSU₂, VIB_diag) elseif Sys.isapple() - (Vtr, Vℤ₃, VfU₁, VfSU₂) + (Vtr, Vℤ₃, VfU₁, VfSU₂, VIB_diag) else - (Vtr, VU₁, VCU₁, VSU₂, VfSU₂) + (Vtr, VU₁, VCU₁, VSU₂, VfSU₂, VIB_diag) end else - (Vtr, Vℤ₃, VU₁, VfU₁, VCU₁, VSU₂, VfSU₂) + (Vtr, Vℤ₃, VU₁, VfU₁, VCU₁, VSU₂, VfSU₂, VIB_diag) end catch - (Vtr, Vℤ₃, VU₁, VfU₁, VCU₁, VSU₂, VfSU₂) + (Vtr, Vℤ₃, VU₁, VfU₁, VCU₁, VSU₂, VfSU₂, VIB_diag) end eltypes = (Float32, ComplexF64) @@ -37,7 +37,7 @@ for V in spacelist @testset "QR decomposition" begin for T in eltypes, t in ( - rand(T, W, W), rand(T, W, W)', rand(T, W, V1), rand(T, V1, W)', + rand(T, W, W), rand(T, W, W)', rand(T, W, V4), rand(T, V4, W)', DiagonalTensorMap(rand(T, reduceddim(V1)), V1), ) @@ -64,7 +64,7 @@ for V in spacelist # empty tensor for T in eltypes - t = rand(T, V1 ⊗ V2, zero(V1)) + t = rand(T, V1 ⊗ V2, zerospace(V1)) Q, R = @constinferred qr_full(t) @test Q * R ≈ t @@ -90,7 +90,7 @@ for V in spacelist @testset "LQ decomposition" begin for T in eltypes, t in ( - rand(T, W, W), rand(T, W, W)', rand(T, W, V1), rand(T, V1, W)', + rand(T, W, W), rand(T, W, W)', rand(T, W, V4), rand(T, V4, W)', DiagonalTensorMap(rand(T, reduceddim(V1)), V1), ) @@ -113,7 +113,7 @@ for V in spacelist for T in eltypes # empty tensor - t = rand(T, zero(V1), V1 ⊗ V2) + t = rand(T, zerospace(V1), V1 ⊗ V2) L, Q = @constinferred lq_full(t) @test L * Q ≈ t @@ -139,7 +139,7 @@ for V in spacelist @testset "Polar decomposition" begin for T in eltypes, t in ( - rand(T, W, W), rand(T, W, W)', rand(T, W, V1), rand(T, V1, W)', + rand(T, W, W), rand(T, W, W)', rand(T, W, V4), rand(T, V4, W)', DiagonalTensorMap(rand(T, reduceddim(V1)), V1), ) @@ -155,7 +155,7 @@ for V in spacelist end for T in eltypes, - t in (rand(T, W, W), rand(T, W, W)', rand(T, V1, W), rand(T, W, V1)') + t in (rand(T, W, W), rand(T, W, W)', rand(T, V4, W), rand(T, W, V4)') @assert codomain(t) ≾ domain(t) p, wᴴ = @constinferred right_polar(t) @@ -173,8 +173,8 @@ for V in spacelist for T in eltypes, t in ( rand(T, W, W), rand(T, W, W)', - rand(T, W, V1), rand(T, V1, W), - rand(T, W, V1)', rand(T, V1, W)', + rand(T, W, V4), rand(T, V4, W), + rand(T, W, V4)', rand(T, V4, W)', DiagonalTensorMap(rand(T, reduceddim(V1)), V1), ) @@ -208,7 +208,7 @@ for V in spacelist end # empty tensor - for T in eltypes, t in (rand(T, W, zero(V1)), rand(T, zero(V1), W)) + for T in eltypes, t in (rand(T, W, zerospace(V1)), rand(T, zerospace(V1), W)) U, S, Vᴴ = @constinferred svd_full(t) @test U * S * Vᴴ ≈ t @test isunitary(U) @@ -224,8 +224,8 @@ for V in spacelist for T in eltypes, t in ( randn(T, W, W), randn(T, W, W)', - randn(T, W, V1), randn(T, V1, W), - randn(T, W, V1)', randn(T, V1, W)', + randn(T, W, V4), randn(T, V4, W), + randn(T, W, V4)', randn(T, V4, W)', DiagonalTensorMap(randn(T, reduceddim(V1)), V1), ) @@ -236,7 +236,8 @@ for V in spacelist @test isisometry(U) @test isisometry(Vᴴ; side = :right) - trunc = truncrank(dim(domain(S)) ÷ 2) + #FIXME: dimension of S is a float, might be a real issue if it's a decimal + trunc = truncrank(Int(dim(domain(S)) ÷ 2)) U1, S1, Vᴴ1 = @constinferred svd_trunc(t; trunc) @test t * Vᴴ1' ≈ U1 * S1 @test isisometry(U1) @@ -268,7 +269,7 @@ for V in spacelist @test isisometry(Vᴴ4; side = :right) @test norm(t - U4 * S4 * Vᴴ4) <= 0.5 - trunc = truncrank(dim(domain(S)) ÷ 2) & trunctol(; atol = λ - 10eps(λ)) + trunc = truncrank(Int(dim(domain(S)) ÷ 2)) & trunctol(; atol = λ - 10eps(λ)) U5, S5, Vᴴ5 = @constinferred svd_trunc(t; trunc) @test t * Vᴴ5' ≈ U5 * S5 @test isisometry(U5) @@ -298,7 +299,7 @@ for V in spacelist @test @constinferred isposdef(vdv) t isa DiagonalTensorMap || @test !isposdef(t) # unlikely for non-hermitian map - d, v = @constinferred eig_trunc(t; trunc = truncrank(dim(domain(t)) ÷ 2)) + d, v = @constinferred eig_trunc(t; trunc = truncrank(Int(dim(domain(t)) ÷ 2))) @test t * v ≈ v * d @test dim(domain(d)) ≤ dim(domain(t)) ÷ 2 @@ -329,7 +330,7 @@ for V in spacelist @test isposdef(t - λ * one(t) + 0.1 * one(t)) @test !isposdef(t - λ * one(t) - 0.1 * one(t)) - d, v = @constinferred eigh_trunc(t; trunc = truncrank(dim(domain(t)) ÷ 2)) + d, v = @constinferred eigh_trunc(t; trunc = truncrank(Int(dim(domain(t)) ÷ 2))) @test t * v ≈ v * d @test dim(domain(d)) ≤ dim(domain(t)) ÷ 2 end @@ -339,26 +340,26 @@ for V in spacelist for T in eltypes, t in ( rand(T, W, W), rand(T, W, W)', - rand(T, W, V1), rand(T, V1, W), - rand(T, W, V1)', rand(T, V1, W)', + rand(T, W, V4), rand(T, V4, W), + rand(T, W, V4)', rand(T, V4, W)', DiagonalTensorMap(rand(T, reduceddim(V1)), V1), ) d1, d2 = dim(codomain(t)), dim(domain(t)) @test rank(t) == min(d1, d2) M = left_null(t) - @test @constinferred(rank(M)) + rank(t) == d1 + @test @constinferred(rank(M)) + rank(t) ≈ d1 Mᴴ = right_null(t) - @test rank(Mᴴ) + rank(t) == d2 + @test rank(Mᴴ) + rank(t) ≈ d2 end for T in eltypes u = unitary(T, V1 ⊗ V2, V1 ⊗ V2) @test @constinferred(cond(u)) ≈ one(real(T)) @test @constinferred(rank(u)) == dim(V1 ⊗ V2) - t = rand(T, zero(V1), W) + t = rand(T, zerospace(V1), W) @test rank(t) == 0 - t2 = rand(T, zero(V1) * zero(V2), zero(V1) * zero(V2)) + t2 = rand(T, zerospace(V1) * zerospace(V2), zerospace(V1) * zerospace(V2)) @test rank(t2) == 0 @test cond(t2) == 0.0 end From 3622b92329896c2bfaf6ea4cd1465035376d3cca Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Wed, 5 Nov 2025 11:27:41 +0100 Subject: [PATCH 40/95] rewrite and reorganise tensor tests --- test/tensors/tensors.jl | 298 +++++++++++++++++++++++----------------- 1 file changed, 171 insertions(+), 127 deletions(-) diff --git a/test/tensors/tensors.jl b/test/tensors/tensors.jl index 959d03198..9eda7bab5 100644 --- a/test/tensors/tensors.jl +++ b/test/tensors/tensors.jl @@ -11,17 +11,17 @@ spacelist = try if get(ENV, "CI", "false") == "true" println("Detected running on CI") if Sys.iswindows() - (Vtr, Vℤ₂, Vfℤ₂, Vℤ₃, VU₁, VfU₁, VCU₁, VSU₂) + (Vtr, Vℤ₂, Vfℤ₂, Vℤ₃, VU₁, VfU₁, VCU₁, VSU₂, VIB_diag, VIB_M) elseif Sys.isapple() - (Vtr, Vℤ₂, Vfℤ₂, Vℤ₃, VfU₁, VfSU₂, VSU₂U₁) #, VSU₃) + (Vtr, Vℤ₂, Vfℤ₂, Vℤ₃, VfU₁, VfSU₂, VSU₂U₁, VIB_diag, VIB_M) #, VSU₃) else - (Vtr, Vℤ₂, Vfℤ₂, VU₁, VCU₁, VSU₂, VfSU₂, VSU₂U₁) #, VSU₃) + (Vtr, Vℤ₂, Vfℤ₂, VU₁, VCU₁, VSU₂, VfSU₂, VSU₂U₁, VIB_diag, VIB_M) #, VSU₃) end else - (Vtr, Vℤ₂, Vfℤ₂, Vℤ₃, VU₁, VfU₁, VCU₁, VSU₂, VfSU₂, VSU₂U₁) #, VSU₃) + (Vtr, Vℤ₂, Vfℤ₂, Vℤ₃, VU₁, VfU₁, VCU₁, VSU₂, VfSU₂, VSU₂U₁, VIB_diag, VIB_M) #, VSU₃) end catch - (Vtr, Vℤ₂, Vfℤ₂, Vℤ₃, VU₁, VfU₁, VCU₁, VSU₂, VfSU₂, VSU₂U₁) #, VSU₃) + (Vtr, Vℤ₂, Vfℤ₂, Vℤ₃, VU₁, VfU₁, VCU₁, VSU₂, VfSU₂, VSU₂U₁, VIB_diag, VIB_M) #, VSU₃) end for V in spacelist @@ -46,18 +46,20 @@ for V in spacelist @test typeof(t) == TensorMap{T, spacetype(t), 5, 0, Vector{T}} # blocks bs = @constinferred blocks(t) - (c, b1), state = @constinferred Nothing iterate(bs) - @test c == first(blocksectors(W)) - next = @constinferred Nothing iterate(bs, state) - b2 = @constinferred block(t, first(blocksectors(t))) - @test b1 == b2 - @test eltype(bs) === Pair{typeof(c), typeof(b1)} - @test typeof(b1) === TensorKit.blocktype(t) - @test typeof(c) === sectortype(t) + if !isempty(blocksectors(t)) # multifusion space ending on module gives empty data + (c, b1), state = @constinferred Nothing iterate(bs) + @test c == first(blocksectors(W)) + next = @constinferred Nothing iterate(bs, state) + b2 = @constinferred block(t, first(blocksectors(t))) + @test b1 == b2 + @test eltype(bs) === Pair{typeof(c), typeof(b1)} + @test typeof(b1) === TensorKit.blocktype(t) + @test typeof(c) === sectortype(t) + end end end @timedtestset "Tensor Dict conversion" begin - W = V1 ⊗ V2 ⊗ V3 ← V4 ⊗ V5 + W = V1 ⊗ V2 ← V3 ⊗ V4 ⊗ V5 for T in (Int, Float32, ComplexF64) t = @constinferred rand(T, W) d = convert(Dict, t) @@ -83,21 +85,21 @@ for V in spacelist t = @constinferred randn(T, W) end a = @constinferred convert(Array, t) - b = reshape(a, dim(codomain(W)), dim(domain(W))) + b = reshape(a, Int(dim(codomain(W))), Int(dim(domain(W)))) # no init in dim makes reshape error for su2 @test t ≈ @constinferred TensorMap(a, W) @test t ≈ @constinferred TensorMap(b, W) @test t === @constinferred TensorMap(t.data, W) end end for T in (Int, Float32, ComplexF64) - t = randn(T, V1 ⊗ V2 ← zerospace(V1)) + t = randn(T, V1 ⊗ V2 ← zerospace(V1)) # no init in dim makes zerospace call error for z2 a = convert(Array, t) @test norm(a) == 0 end end end @timedtestset "Basic linear algebra" begin - W = V1 ⊗ V2 ⊗ V3 ← V4 ⊗ V5 + W = V1 ⊗ V2 ← V3 ⊗ V4 ⊗ V5 for T in (Float32, ComplexF64) t = @constinferred rand(T, W) @test scalartype(t) == T @@ -135,41 +137,60 @@ for V in spacelist @test dot(t2, t) ≈ conj(dot(t2', t')) @test dot(t2, t) ≈ dot(t', t2') - i1 = @constinferred(isomorphism(T, V1 ⊗ V2, V2 ⊗ V1)) - i2 = @constinferred(isomorphism(Vector{T}, V2 ⊗ V1, V1 ⊗ V2)) - @test i1 * i2 == @constinferred(id(T, V1 ⊗ V2)) - @test i2 * i1 == @constinferred(id(Vector{T}, V2 ⊗ V1)) + if isa(UnitStyle(I), SimpleUnit) || !isempty(blocksectors(V2 ⊗ V1)) + i1 = @constinferred(isomorphism(T, V1 ⊗ V2, V2 ⊗ V1)) # can't reverse fusion here when modules are involved + i2 = @constinferred(isomorphism(Vector{T}, V2 ⊗ V1, V1 ⊗ V2)) + @test i1 * i2 == @constinferred(id(T, V1 ⊗ V2)) + @test i2 * i1 == @constinferred(id(Vector{T}, V2 ⊗ V1)) + end - w = @constinferred(isometry(T, V1 ⊗ (oneunit(V1) ⊕ oneunit(V1)), V1)) - @test dim(w) == 2 * dim(V1 ← V1) - @test w' * w == id(Vector{T}, V1) - @test w * w' == (w * w')^2 + if isa(UnitStyle(I), SimpleUnit) + # FIXME?: unitspace returns all units, leads to invalid fusion channels + w = @constinferred isometry(T, V1 ⊗ (unitspace(V1) ⊕ unitspace(V1)), V1) + @test dim(w) == 2 * dim(V1 ← V1) + @test w' * w == id(Vector{T}, V1) + @test w * w' == (w * w')^2 + end end end @timedtestset "Trivial space insertion and removal" begin W = V1 ⊗ V2 ⊗ V3 ← V4 ⊗ V5 for T in (Float32, ComplexF64) t = @constinferred rand(T, W) - t2 = @constinferred insertleftunitspace(t) - @test t2 == @constinferred insertrightunitspace(t) + if isa(UnitStyle(I), SimpleUnit) + t2 = @constinferred insertleftunitspace(t) + @test t2 == @constinferred insertrightunitspace(t) + @test space(t2) == insertleftunitspace(space(t)) + @test @constinferred(removeunitspace(t2, $(numind(t2)))) == t + t3 = @constinferred insertleftunitspace(t; copy = true) + @test t3 == @constinferred insertrightunitspace(t; copy = true) + @test @constinferred(removeunitspace(t3, $(numind(t3)))) == t + else + t2 = @constinferred insertleftunitspace(t, 5) + @test t2 == @constinferred insertrightunitspace(t, 4) + @test space(t2) == insertleftunitspace(space(t), 5) + @test @constinferred(removeunitspace(t2, $(numind(t2) - 1))) == t + t3 = @constinferred insertleftunitspace(t, 5; copy = true) + @test t3 == @constinferred insertrightunitspace(t, 4; copy = true) + @test @constinferred(removeunitspace(t3, $(numind(t3) - 1))) == t + end + @test numind(t2) == numind(t) + 1 - @test space(t2) == insertleftunitspace(space(t)) @test scalartype(t2) === T @test t.data === t2.data - @test @constinferred(removeunitspace(t2, $(numind(t2)))) == t - t3 = @constinferred insertleftunitspace(t; copy = true) - @test t3 == @constinferred insertrightunitspace(t; copy = true) + @test t.data !== t3.data for (c, b) in blocks(t) @test b == block(t3, c) end - @test @constinferred(removeunitspace(t3, $(numind(t3)))) == t + t4 = @constinferred insertrightunitspace(t, 3; dual = true) @test numin(t4) == numin(t) && numout(t4) == numout(t) + 1 for (c, b) in blocks(t) @test b == block(t4, c) end @test @constinferred(removeunitspace(t4, 4)) == t + t5 = @constinferred insertleftunitspace(t, 4; dual = true) @test numin(t5) == numin(t) + 1 && numout(t5) == numout(t) for (c, b) in blocks(t) @@ -224,26 +245,27 @@ for V in spacelist @test Base.promote_typeof(t, tc) == typeof(tc) @test Base.promote_typeof(tc, t) == typeof(tc + t) end - @timedtestset "Permutations: test via inner product invariance" begin - @assert symmetricbraiding - W = V1 ⊗ V2 ⊗ V3 ⊗ V4 ⊗ V5 - t = rand(ComplexF64, W) - t′ = randn!(similar(t)) - for k in 0:5 - for p in permutations(1:5) - p1 = ntuple(n -> p[n], k) - p2 = ntuple(n -> p[k + n], 5 - k) - t2 = @constinferred permute(t, (p1, p2)) - @test norm(t2) ≈ norm(t) - t2′ = permute(t′, (p1, p2)) - @test dot(t2′, t2) ≈ dot(t′, t) ≈ dot(transpose(t2′), transpose(t2)) - end + if symmetricbraiding + @timedtestset "Permutations: test via inner product invariance" begin + W = V1 ⊗ V2 ⊗ V3 ⊗ V4 ⊗ V5 + t = rand(ComplexF64, W) + t′ = randn!(similar(t)) + for k in 0:5 + for p in permutations(1:5) + p1 = ntuple(n -> p[n], k) + p2 = ntuple(n -> p[k + n], 5 - k) + t2 = @constinferred permute(t, (p1, p2)) + @test norm(t2) ≈ norm(t) + t2′ = permute(t′, (p1, p2)) + @test dot(t2′, t2) ≈ dot(t′, t) ≈ dot(transpose(t2′), transpose(t2)) + end - t3 = @constinferred repartition(t, $k) - @test norm(t3) ≈ norm(t) - t3′ = @constinferred repartition!(similar(t3), t′) - @test norm(t3′) ≈ norm(t′) - @test dot(t′, t) ≈ dot(t3′, t3) + t3 = @constinferred repartition(t, $k) + @test norm(t3) ≈ norm(t) + t3′ = @constinferred repartition!(similar(t3), t′) + @test norm(t3′) ≈ norm(t′) + @test dot(t′, t) ≈ dot(t3′, t3) + end end end if BraidingStyle(I) isa Bosonic && hasfusiontensor(I) @@ -270,22 +292,24 @@ for V in spacelist end end end - @timedtestset "Full trace: test self-consistency" begin - t = rand(ComplexF64, V1 ⊗ V2' ← V1 ⊗ V2') - s = @constinferred tr(t) - @test conj(s) ≈ tr(t') - if !isdual(V1) - t2 = twist!(t, 1) - end - if isdual(V2) - t2 = twist!(t, 2) + if symmetricbraiding + @timedtestset "Full trace: test self-consistency" begin + t = rand(ComplexF64, V1 ⊗ V2' ← V1 ⊗ V2') + s = @constinferred tr(t) + @test conj(s) ≈ tr(t') + if !isdual(V1) + t2 = twist!(t, 1) + end + if isdual(V2) + t2 = twist!(t, 2) + end + ss = tr(t2) + @planar s2 = t[a b; a b] + @planar t3[a; b] := t[a c; b c] + @planar s3 = t3[a; a] + @test ss ≈ s2 + @test ss ≈ s3 end - ss = tr(t2) - @plansor s2 = t[a b; a b] - @plansor t3[a; b] := t[a c; b c] - @plansor s3 = t3[a; a] - @test ss ≈ s2 - @test ss ≈ s3 end @timedtestset "Partial trace: test self-consistency" begin t = rand(ComplexF64, V1 ⊗ V2 ⊗ V3 ← V1 ⊗ V2 ⊗ V3) @@ -302,13 +326,15 @@ for V in spacelist @test t3 ≈ convert(Array, t2) end end - @timedtestset "Trace and contraction" begin - t1 = rand(ComplexF64, V1 ⊗ V2 ⊗ V3) - t2 = rand(ComplexF64, V2' ⊗ V4 ⊗ V1') - t3 = t1 ⊗ t2 - @tensor ta[a, b] := t1[x, y, a] * t2[y, b, x] - @tensor tb[a, b] := t3[x, y, a, y, b, x] - @test ta ≈ tb + if isa(UnitStyle(I), SimpleUnit) #TODO: find version that works for all multifusion cases + @timedtestset "Trace and contraction" begin + t1 = rand(ComplexF64, V1 ⊗ V2 ⊗ V3) + t2 = rand(ComplexF64, V2' ⊗ V4 ⊗ V1') + t3 = t1 ⊗ t2 + @tensor ta[a, b] := t1[x, y, a] * t2[y, b, x] + @tensor tb[a, b] := t3[x, y, a, y, b, x] + @test ta ≈ tb + end end if BraidingStyle(I) isa Bosonic && hasfusiontensor(I) @timedtestset "Tensor contraction: test via conversion" begin @@ -327,43 +353,45 @@ for V in spacelist @test HrA12array ≈ convert(Array, HrA12) end end - @timedtestset "Index flipping: test flipping inverse" begin - t = rand(ComplexF64, V1 ⊗ V1' ← V1' ⊗ V1) - for i in 1:4 - @test t ≈ flip(flip(t, i), i; inv = true) - @test t ≈ flip(flip(t, i; inv = true), i) + if !isa(BraidingStyle(I), NoBraiding) + @timedtestset "Index flipping: test flipping inverse" begin + t = rand(ComplexF64, V1 ⊗ V1' ← V1' ⊗ V1) + for i in 1:4 + @test t ≈ flip(flip(t, i), i; inv = true) + @test t ≈ flip(flip(t, i; inv = true), i) + end end end - @timedtestset "Index flipping: test via explicit flip" begin - @assert symmetricbraiding - t = rand(ComplexF64, V1 ⊗ V1' ← V1' ⊗ V1) - F1 = unitary(flip(V1), V1) + if symmetricbraiding + @timedtestset "Index flipping: test via explicit flip" begin + t = rand(ComplexF64, V1 ⊗ V1' ← V1' ⊗ V1) + F1 = unitary(flip(V1), V1) - @tensor tf[a, b; c, d] := F1[a, a'] * t[a', b; c, d] - @test flip(t, 1) ≈ tf - @tensor tf[a, b; c, d] := conj(F1[b, b']) * t[a, b'; c, d] - @test twist!(flip(t, 2), 2) ≈ tf - @tensor tf[a, b; c, d] := F1[c, c'] * t[a, b; c', d] - @test flip(t, 3) ≈ tf - @tensor tf[a, b; c, d] := conj(F1[d, d']) * t[a, b; c, d'] - @test twist!(flip(t, 4), 4) ≈ tf - end - @timedtestset "Index flipping: test via contraction" begin - @assert symmetricbraiding - t1 = rand(ComplexF64, V1 ⊗ V2 ⊗ V3 ← V4) - t2 = rand(ComplexF64, V2' ⊗ V5 ← V4' ⊗ V1) - @tensor ta[a, b] := t1[x, y, a, z] * t2[y, b, z, x] - @tensor tb[a, b] := flip(t1, 1)[x, y, a, z] * flip(t2, 4)[y, b, z, x] - @test ta ≈ tb - @tensor tb[a, b] := flip(t1, (2, 4))[x, y, a, z] * flip(t2, (1, 3))[y, b, z, x] - @test ta ≈ tb - @tensor tb[a, b] := flip(t1, (1, 2, 4))[x, y, a, z] * flip(t2, (1, 3, 4))[y, b, z, x] - @tensor tb[a, b] := flip(t1, (1, 3))[x, y, a, z] * flip(t2, (2, 4))[y, b, z, x] - @test flip(ta, (1, 2)) ≈ tb + @tensor tf[a, b; c, d] := F1[a, a'] * t[a', b; c, d] + @test flip(t, 1) ≈ tf + @tensor tf[a, b; c, d] := conj(F1[b, b']) * t[a, b'; c, d] + @test twist!(flip(t, 2), 2) ≈ tf + @tensor tf[a, b; c, d] := F1[c, c'] * t[a, b; c', d] + @test flip(t, 3) ≈ tf + @tensor tf[a, b; c, d] := conj(F1[d, d']) * t[a, b; c, d'] + @test twist!(flip(t, 4), 4) ≈ tf + end + @timedtestset "Index flipping: test via contraction" begin + t1 = rand(ComplexF64, V1 ⊗ V2 ⊗ V3 ← V4) + t2 = rand(ComplexF64, V2' ⊗ V5 ← V4' ⊗ V1) + @tensor ta[a, b] := t1[x, y, a, z] * t2[y, b, z, x] + @tensor tb[a, b] := flip(t1, 1)[x, y, a, z] * flip(t2, 4)[y, b, z, x] + @test ta ≈ tb + @tensor tb[a, b] := flip(t1, (2, 4))[x, y, a, z] * flip(t2, (1, 3))[y, b, z, x] + @test ta ≈ tb + @tensor tb[a, b] := flip(t1, (1, 2, 4))[x, y, a, z] * flip(t2, (1, 3, 4))[y, b, z, x] + @tensor tb[a, b] := flip(t1, (1, 3))[x, y, a, z] * flip(t2, (2, 4))[y, b, z, x] + @test flip(ta, (1, 2)) ≈ tb + end end @timedtestset "Multiplication of isometries: test properties" begin W2 = V4 ⊗ V5 - W1 = W2 ⊗ (oneunit(V1) ⊕ oneunit(V1)) + W1 = W2 ⊗ (unitspace(V1) ⊕ unitspace(V1)) for T in (Float64, ComplexF64) t1 = randisometry(T, W1, W2) t2 = randisometry(T, W2 ← W2) @@ -399,8 +427,8 @@ for V in spacelist t1 = rand(T, W1 ← W1) t2 = rand(T, W2, W2) t = rand(T, W1 ← W2) - d1 = dim(W1) - d2 = dim(W2) + d1 = Int(dim(W1)) + d2 = Int(dim(W2)) At1 = reshape(convert(Array, t1), d1, d1) At2 = reshape(convert(Array, t2), d2, d2) At = reshape(convert(Array, t), d1, d2) @@ -441,7 +469,7 @@ for V in spacelist W = V1 ⊗ V2 for T in (Float64, ComplexF64) t = randn(T, W, W) - s = dim(W) + s = Int(dim(W)) expt = @constinferred exp(t) @test reshape(convert(Array, expt), (s, s)) ≈ exp(reshape(convert(Array, t), (s, s))) @@ -501,15 +529,20 @@ for V in spacelist @test norm(tA * t + t * tB + tC) < (norm(tA) + norm(tB) + norm(tC)) * eps(real(T))^(2 / 3) if BraidingStyle(I) isa Bosonic && hasfusiontensor(I) - matrix(x) = reshape(convert(Array, x), dim(codomain(x)), dim(domain(x))) + matrix(x) = reshape(convert(Array, x), Int(dim(codomain(x))), Int(dim(domain(x)))) @test matrix(t) ≈ sylvester(matrix(tA), matrix(tB), matrix(tC)) end end end @timedtestset "Tensor product: test via norm preservation" begin for T in (Float32, ComplexF64) - t1 = rand(T, V2 ⊗ V3 ⊗ V1, V1 ⊗ V2) - t2 = rand(T, V2 ⊗ V1 ⊗ V3, V1 ⊗ V1) + if isa(UnitStyle(I), SimpleUnit) || !isempty(blocksectors(V2 ⊗ V1)) + t1 = rand(T, V2 ⊗ V3 ⊗ V1, V1 ⊗ V2) + t2 = rand(T, V2 ⊗ V1 ⊗ V3, V1 ⊗ V1) + else + t1 = rand(T, V3 ⊗ V4 ⊗ V5, V1 ⊗ V2) + t2 = rand(T, V5' ⊗ V4' ⊗ V3', V2' ⊗ V1') + end t = @constinferred (t1 ⊗ t2) @test norm(t) ≈ norm(t1) * norm(t2) end @@ -520,10 +553,10 @@ for V in spacelist t1 = rand(T, V2 ⊗ V3 ⊗ V1, V1) t2 = rand(T, V2 ⊗ V1 ⊗ V3, V2) t = @constinferred (t1 ⊗ t2) - d1 = dim(codomain(t1)) - d2 = dim(codomain(t2)) - d3 = dim(domain(t1)) - d4 = dim(domain(t2)) + d1 = Int(dim(codomain(t1))) + d2 = Int(dim(codomain(t2))) + d3 = Int(dim(domain(t1))) + d4 = Int(dim(domain(t2))) At = convert(Array, t) @test reshape(At, (d1, d2, d3, d4)) ≈ reshape(convert(Array, t1), (d1, 1, d3, 1)) .* @@ -531,20 +564,26 @@ for V in spacelist end end end - @timedtestset "Tensor product: test via tensor contraction" begin - @assert symmetricbraiding - for T in (Float32, ComplexF64) - t1 = rand(T, V2 ⊗ V3 ⊗ V1) - t2 = rand(T, V2 ⊗ V1 ⊗ V3) - t = @constinferred (t1 ⊗ t2) - @tensor t′[1, 2, 3, 4, 5, 6] := t1[1, 2, 3] * t2[4, 5, 6] - @test t ≈ t′ + if symmetricbraiding + @timedtestset "Tensor product: test via tensor contraction" begin + for T in (Float32, ComplexF64) + t1 = rand(T, V2 ⊗ V3 ⊗ V1) + t2 = rand(T, V2 ⊗ V1 ⊗ V3) + t = @constinferred (t1 ⊗ t2) + @tensor t′[1, 2, 3, 4, 5, 6] := t1[1, 2, 3] * t2[4, 5, 6] + @test t ≈ t′ + end end end - @timedtestset "Tensor absorpsion" begin + @timedtestset "Tensor absorption" begin # absorbing small into large - t1 = zeros(V1 ⊕ V1, V2 ⊗ V3) - t2 = rand(V1, V2 ⊗ V3) + if isa(UnitStyle(I), SimpleUnit) || !isempty(blocksectors(V2 ⊗ V3)) + t1 = zeros(V1 ⊕ V1, V2 ⊗ V3) + t2 = rand(V1, V2 ⊗ V3) + else + t1 = zeros(V1 ⊕ V2, V3 ⊗ V4 ⊗ V5) + t2 = rand(V1, V3 ⊗ V4 ⊗ V5) + end t3 = @constinferred absorb(t1, t2) @test norm(t3) ≈ norm(t2) @test norm(t1) == 0 @@ -553,8 +592,13 @@ for V in spacelist @test t3 ≈ t4 # absorbing large into small - t1 = rand(V1 ⊕ V1, V2 ⊗ V3) - t2 = zeros(V1, V2 ⊗ V3) + if isa(UnitStyle(I), SimpleUnit) || !isempty(blocksectors(V2 ⊗ V3)) + t1 = rand(V1 ⊕ V1, V2 ⊗ V3) + t2 = zeros(V1, V2 ⊗ V3) + else + t1 = rand(V1 ⊕ V2, V3 ⊗ V4 ⊗ V5) + t2 = zeros(V1, V3 ⊗ V4 ⊗ V5) + end t3 = @constinferred absorb(t2, t1) @test norm(t3) < norm(t1) @test norm(t2) == 0 From d01424437cd96ad256406ee4afaddfcbd4046b78 Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Wed, 5 Nov 2025 12:08:48 +0100 Subject: [PATCH 41/95] another float dim thingie correction --- test/tensors/tensors.jl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/tensors/tensors.jl b/test/tensors/tensors.jl index 9eda7bab5..af691f169 100644 --- a/test/tensors/tensors.jl +++ b/test/tensors/tensors.jl @@ -618,10 +618,10 @@ end t1 = rand(T, V1 ⊗ V2, V3' ⊗ V4) t2 = rand(T, W2, W1 ⊗ W1') t = @constinferred (t1 ⊠ t2) - d1 = dim(codomain(t1)) - d2 = dim(codomain(t2)) - d3 = dim(domain(t1)) - d4 = dim(domain(t2)) + d1 = Int(dim(codomain(t1))) + d2 = Int(dim(codomain(t2))) + d3 = Int(dim(domain(t1))) + d4 = Int(dim(domain(t2))) At = convert(Array, t) @test reshape(At, (d1, d2, d3, d4)) ≈ reshape(convert(Array, t1), (d1, 1, d3, 1)) .* From ae92cc1ece1b84fd37f5a9ec6e7d2e276e9bda8a Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Wed, 5 Nov 2025 15:04:29 +0100 Subject: [PATCH 42/95] remove comment --- test/symmetries/fusiontrees.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/symmetries/fusiontrees.jl b/test/symmetries/fusiontrees.jl index 8ecf28b23..9cd90cee9 100644 --- a/test/symmetries/fusiontrees.jl +++ b/test/symmetries/fusiontrees.jl @@ -10,7 +10,7 @@ using TensorKitSectors @isdefined(TestSetup) || include("../setup.jl") using .TestSetup -@timedtestset "Fusion trees for $(TensorKit.type_repr(I))" verbose = true for I in sectorlist # product sectors with multifusion doesn't work because of isunit +@timedtestset "Fusion trees for $(TensorKit.type_repr(I))" verbose = true for I in sectorlist Istr = TensorKit.type_repr(I) N = 5 out = random_fusiontree(I, N) From 428afeee700e3b550de6ae8ed458994aae8ffaab Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Wed, 5 Nov 2025 15:08:25 +0100 Subject: [PATCH 43/95] bring back `insertleft/rightunit` and `removeunit` --- docs/src/lib/spaces.md | 12 +++++------ docs/src/lib/tensors.md | 6 +++--- src/TensorKit.jl | 2 +- src/auxiliary/deprecate.jl | 2 +- src/spaces/homspace.jl | 36 +++++++++++++++---------------- src/spaces/productspace.jl | 20 ++++++++--------- src/spaces/vectorspaces.jl | 12 +++++------ src/tensors/indexmanipulations.jl | 30 +++++++++++++------------- test/symmetries/spaces.jl | 36 +++++++++++++++---------------- test/tensors/tensors.jl | 36 +++++++++++++++---------------- 10 files changed, 96 insertions(+), 96 deletions(-) diff --git a/docs/src/lib/spaces.md b/docs/src/lib/spaces.md index 7d55cfdbd..10d2bf4f6 100644 --- a/docs/src/lib/spaces.md +++ b/docs/src/lib/spaces.md @@ -112,9 +112,9 @@ isisomorphic Inserting trivial space factors or removing such factors for `ProductSpace` instances can be done with the following methods. ```@docs -insertleftunitspace(::ProductSpace, ::Val{i}) where {i} -insertrightunitspace(::ProductSpace, ::Val{i}) where {i} -removeunitspace(::ProductSpace, ::Val{i}) where {i} +insertleftunit(::ProductSpace, ::Val{i}) where {i} +insertrightunit(::ProductSpace, ::Val{i}) where {i} +removeunit(::ProductSpace, ::Val{i}) where {i} ``` There are also specific methods for `HomSpace` instances, that are used in determining @@ -125,7 +125,7 @@ flip(W::HomSpace{S}, I) where {S} TensorKit.permute(::HomSpace{S}, ::Index2Tuple{N₁,N₂}) where {S,N₁,N₂} TensorKit.select(::HomSpace{S}, ::Index2Tuple{N₁,N₂}) where {S,N₁,N₂} TensorKit.compose(::HomSpace{S}, ::HomSpace{S}) where {S} -insertleftunitspace(::HomSpace, ::Val{i}) where {i} -insertrightunitspace(::HomSpace, ::Val{i}) where {i} -removeunitspace(::HomSpace, ::Val{i}) where {i} +insertleftunit(::HomSpace, ::Val{i}) where {i} +insertrightunit(::HomSpace, ::Val{i}) where {i} +removeunit(::HomSpace, ::Val{i}) where {i} ``` diff --git a/docs/src/lib/tensors.md b/docs/src/lib/tensors.md index b4935dad6..54a67258c 100644 --- a/docs/src/lib/tensors.md +++ b/docs/src/lib/tensors.md @@ -184,9 +184,9 @@ transpose(::AbstractTensorMap, ::Index2Tuple) repartition(::AbstractTensorMap, ::Int, ::Int) flip(t::AbstractTensorMap, I) twist(::AbstractTensorMap, ::Int) -insertleftunitspace(::AbstractTensorMap, ::Val{i}) where {i} -insertrightunitspace(::AbstractTensorMap, ::Val{i}) where {i} -removeunitspace(::AbstractTensorMap, ::Val{i}) where {i} +insertleftunit(::AbstractTensorMap, ::Val{i}) where {i} +insertrightunit(::AbstractTensorMap, ::Val{i}) where {i} +removeunit(::AbstractTensorMap, ::Val{i}) where {i} ``` ```@docs diff --git a/src/TensorKit.jl b/src/TensorKit.jl index 6ddb7f5a6..9c1f8846b 100644 --- a/src/TensorKit.jl +++ b/src/TensorKit.jl @@ -37,7 +37,7 @@ export SpaceMismatch, SectorMismatch, IndexError # error types # Export general vector space methods export space, field, dual, dim, reduceddim, dims, fuse, flip, isdual export unitspace, zerospace, oplus, ominus -export insertleftunitspace, insertrightunitspace, removeunitspace +export insertleftunit, insertrightunit, removeunit # partial order for vector spaces export infimum, supremum, isisomorphic, ismonomorphic, isepimorphic diff --git a/src/auxiliary/deprecate.jl b/src/auxiliary/deprecate.jl index 70569a102..a37c40531 100644 --- a/src/auxiliary/deprecate.jl +++ b/src/auxiliary/deprecate.jl @@ -33,7 +33,7 @@ end Base.@deprecate EuclideanProduct() EuclideanInnerProduct() -Base.@deprecate insertunit(P::ProductSpace, args...; kwargs...) insertleftunitspace(args...; kwargs...) +Base.@deprecate insertunit(P::ProductSpace, args...; kwargs...) insertleftunit(args...; kwargs...) # Factorization structs @deprecate QR() MatrixAlgebraKit.LAPACK_HouseholderQR() diff --git a/src/spaces/homspace.jl b/src/spaces/homspace.jl index 01fa3534f..0937bb9b7 100644 --- a/src/spaces/homspace.jl +++ b/src/spaces/homspace.jl @@ -196,62 +196,62 @@ function compose(W::HomSpace{S}, V::HomSpace{S}) where {S} end """ - insertleftunitspace(W::HomSpace, i=numind(W) + 1; conj=false, dual=false) + insertleftunit(W::HomSpace, i=numind(W) + 1; conj=false, dual=false) Insert a trivial vector space, isomorphic to the underlying field, at position `i`, which can be specified as an `Int` or as `Val(i)` for improved type stability. More specifically, adds a left monoidal unit or its dual. -See also [`insertrightunitspace`](@ref insertrightunitspace(::HomSpace, ::Val{i}) where {i}), -[`removeunitspace`](@ref removeunitspace(::HomSpace, ::Val{i}) where {i}). +See also [`insertrightunit`](@ref insertrightunit(::HomSpace, ::Val{i}) where {i}), +[`removeunit`](@ref removeunit(::HomSpace, ::Val{i}) where {i}). """ -function insertleftunitspace( +function insertleftunit( W::HomSpace, ::Val{i} = Val(numind(W) + 1); conj::Bool = false, dual::Bool = false ) where {i} if i ≤ numout(W) - return insertleftunitspace(codomain(W), Val(i); conj, dual) ← domain(W) + return insertleftunit(codomain(W), Val(i); conj, dual) ← domain(W) else - return codomain(W) ← insertleftunitspace(domain(W), Val(i - numout(W)); conj, dual) + return codomain(W) ← insertleftunit(domain(W), Val(i - numout(W)); conj, dual) end end """ - insertrightunitspace(W::HomSpace, i=numind(W); conj=false, dual=false) + insertrightunit(W::HomSpace, i=numind(W); conj=false, dual=false) Insert a trivial vector space, isomorphic to the underlying field, after position `i`, which can be specified as an `Int` or as `Val(i)` for improved type stability. More specifically, adds a right monoidal unit or its dual. -See also [`insertleftunitspace`](@ref insertleftunitspace(::HomSpace, ::Val{i}) where {i}), -[`removeunitspace`](@ref removeunitspace(::HomSpace, ::Val{i}) where {i}). +See also [`insertleftunit`](@ref insertleftunit(::HomSpace, ::Val{i}) where {i}), +[`removeunit`](@ref removeunit(::HomSpace, ::Val{i}) where {i}). """ -function insertrightunitspace( +function insertrightunit( W::HomSpace, ::Val{i} = Val(numind(W)); conj::Bool = false, dual::Bool = false ) where {i} if i ≤ numout(W) - return insertrightunitspace(codomain(W), Val(i); conj, dual) ← domain(W) + return insertrightunit(codomain(W), Val(i); conj, dual) ← domain(W) else - return codomain(W) ← insertrightunitspace(domain(W), Val(i - numout(W)); conj, dual) + return codomain(W) ← insertrightunit(domain(W), Val(i - numout(W)); conj, dual) end end """ - removeunitspace(P::HomSpace, i) + removeunit(P::HomSpace, i) This removes a trivial tensor product factor at position `1 ≤ i ≤ N`, where `i` can be specified as an `Int` or as `Val(i)` for improved type stability. For this to work, the space at position `i` has to be isomorphic to the field of scalars. -This operation undoes the work of [`insertleftunitspace`](@ref insertleftunitspace(::HomSpace, ::Val{i}) where {i}) -and [`insertrightunitspace`](@ref insertrightunitspace(::HomSpace, ::Val{i}) where {i}). +This operation undoes the work of [`insertleftunit`](@ref insertleftunit(::HomSpace, ::Val{i}) where {i}) +and [`insertrightunit`](@ref insertrightunit(::HomSpace, ::Val{i}) where {i}). """ -function removeunitspace(P::HomSpace, ::Val{i}) where {i} +function removeunit(P::HomSpace, ::Val{i}) where {i} if i ≤ numout(P) - return removeunitspace(codomain(P), Val(i)) ← domain(P) + return removeunit(codomain(P), Val(i)) ← domain(P) else - return codomain(P) ← removeunitspace(domain(P), Val(i - numout(P))) + return codomain(P) ← removeunit(domain(P), Val(i - numout(P))) end end diff --git a/src/spaces/productspace.jl b/src/spaces/productspace.jl index 2814cde28..662e6c8d1 100644 --- a/src/spaces/productspace.jl +++ b/src/spaces/productspace.jl @@ -249,15 +249,15 @@ fuse(P::ProductSpace{S, 0}) where {S <: ElementarySpace} = unitspace(S) fuse(P::ProductSpace{S}) where {S <: ElementarySpace} = fuse(P.spaces...) """ - insertleftunitspace(P::ProductSpace, i::Int=length(P) + 1; conj=false, dual=false) + insertleftunit(P::ProductSpace, i::Int=length(P) + 1; conj=false, dual=false) Insert a trivial vector space, isomorphic to the underlying field, at position `i`, which can be specified as an `Int` or as `Val(i)` for improved type stability. More specifically, adds a left monoidal unit or its dual. -See also [`insertrightunitspace`](@ref insertrightunitspace(::ProductSpace, ::Val{i}) where {i}), [`removeunitspace`](@ref removeunitspace(::ProductSpace, ::Val{i}) where {i}). +See also [`insertrightunit`](@ref insertrightunit(::ProductSpace, ::Val{i}) where {i}), [`removeunit`](@ref removeunit(::ProductSpace, ::Val{i}) where {i}). """ -function insertleftunitspace(P::ProductSpace, ::Val{i}=Val(length(P) + 1); +function insertleftunit(P::ProductSpace, ::Val{i}=Val(length(P) + 1); conj::Bool=false, dual::Bool=false) where {i} N = length(P) I = sectortype(P) @@ -278,15 +278,15 @@ function insertleftunitspace(P::ProductSpace, ::Val{i}=Val(length(P) + 1); end """ - insertrightunitspace(P::ProductSpace, i=length(P); conj=false, dual=false) + insertrightunit(P::ProductSpace, i=length(P); conj=false, dual=false) Insert a trivial vector space, isomorphic to the underlying field, after position `i`, which can be specified as an `Int` or as `Val(i)` for improved type stability. More specifically, adds a right monoidal unit or its dual. -See also [`insertleftunitspace`](@ref insertleftunitspace(::ProductSpace, ::Val{i}) where {i}), [`removeunitspace`](@ref removeunitspace(::ProductSpace, ::Val{i}) where {i}). +See also [`insertleftunit`](@ref insertleftunit(::ProductSpace, ::Val{i}) where {i}), [`removeunit`](@ref removeunit(::ProductSpace, ::Val{i}) where {i}). """ -function insertrightunitspace(P::ProductSpace, ::Val{i}=Val(length(P)); +function insertrightunit(P::ProductSpace, ::Val{i}=Val(length(P)); conj::Bool=false, dual::Bool=false) where {i} N = length(P) I = sectortype(P) @@ -307,16 +307,16 @@ function insertrightunitspace(P::ProductSpace, ::Val{i}=Val(length(P)); end """ - removeunitspace(P::ProductSpace, i::Int) + removeunit(P::ProductSpace, i::Int) This removes a trivial tensor product factor at position `1 ≤ i ≤ N`, where `i` can be specified as an `Int` or as `Val(i)` for improved type stability. For this to work, that factor has to be isomorphic to the field of scalars. -This operation undoes the work of [`insertleftunitspace`](@ref insertleftunitspace(::ProductSpace, ::Val{i}) where {i}) -and [`insertrightunitspace`](@ref insertrightunitspace(::ProductSpace, ::Val{i}) where {i}). +This operation undoes the work of [`insertleftunit`](@ref insertleftunit(::ProductSpace, ::Val{i}) where {i}) +and [`insertrightunit`](@ref insertrightunit(::ProductSpace, ::Val{i}) where {i}). """ -function removeunitspace(P::ProductSpace, ::Val{i}) where {i} +function removeunit(P::ProductSpace, ::Val{i}) where {i} 1 ≤ i ≤ length(P) || _boundserror(P, i) I = sectortype(P) if isa(UnitStyle(I), SimpleUnit) diff --git a/src/spaces/vectorspaces.jl b/src/spaces/vectorspaces.jl index b31b503dd..607666730 100644 --- a/src/spaces/vectorspaces.jl +++ b/src/spaces/vectorspaces.jl @@ -219,14 +219,14 @@ end # In the following, X can be a ProductSpace, a HomSpace or an AbstractTensorMap # TODO: should we deprecate those in the future? -@constprop :aggressive function insertleftunitspace(X, i::Int; kwargs...) - return insertleftunitspace(X, Val(i); kwargs...) +@constprop :aggressive function insertleftunit(X, i::Int; kwargs...) + return insertleftunit(X, Val(i); kwargs...) end -@constprop :aggressive function insertrightunitspace(X, i::Int; kwargs...) - return insertrightunitspace(X, Val(i); kwargs...) +@constprop :aggressive function insertrightunit(X, i::Int; kwargs...) + return insertrightunit(X, Val(i); kwargs...) end -@constprop :aggressive function removeunitspace(X, i::Int; kwargs...) - return removeunitspace(X, Val(i); kwargs...) +@constprop :aggressive function removeunit(X, i::Int; kwargs...) + return removeunit(X, Val(i); kwargs...) end # trait to describe the inner product type of vector spaces diff --git a/src/tensors/indexmanipulations.jl b/src/tensors/indexmanipulations.jl index 4b007475c..243e6656c 100644 --- a/src/tensors/indexmanipulations.jl +++ b/src/tensors/indexmanipulations.jl @@ -306,7 +306,7 @@ twist(t::AbstractTensorMap, i; inv::Bool = false) = twist!(copy(t), i; inv) # Methods which change the number of indices, implement using `Val(i)` for type inference """ - insertleftunitspace(tsrc::AbstractTensorMap, i=numind(t) + 1; + insertleftunit(tsrc::AbstractTensorMap, i=numind(t) + 1; conj=false, dual=false, copy=false) -> tdst Insert a trivial vector space, isomorphic to the underlying field, at position `i`, @@ -315,12 +315,12 @@ More specifically, adds a left monoidal unit or its dual. If `copy=false`, `tdst` might share data with `tsrc` whenever possible. Otherwise, a copy is always made. -See also [`insertrightunitspace`](@ref insertrightunitspace(::AbstractTensorMap, ::Val{i}) where {i}), -[`removeunitspace`](@ref removeunitspace(::AbstractTensorMap, ::Val{i}) where {i}). +See also [`insertrightunit`](@ref insertrightunit(::AbstractTensorMap, ::Val{i}) where {i}), +[`removeunit`](@ref removeunit(::AbstractTensorMap, ::Val{i}) where {i}). """ -function insertleftunitspace(t::AbstractTensorMap, ::Val{i}=Val(numind(t) + 1); +function insertleftunit(t::AbstractTensorMap, ::Val{i}=Val(numind(t) + 1); copy::Bool=false, conj::Bool=false, dual::Bool=false) where {i} - W = insertleftunitspace(space(t), Val(i); conj, dual) + W = insertleftunit(space(t), Val(i); conj, dual) if t isa TensorMap return TensorMap{scalartype(t)}(copy ? Base.copy(t.data) : t.data, W) else @@ -333,7 +333,7 @@ function insertleftunitspace(t::AbstractTensorMap, ::Val{i}=Val(numind(t) + 1); end """ - insertrightunitspace(tsrc::AbstractTensorMap, i=numind(t); + insertrightunit(tsrc::AbstractTensorMap, i=numind(t); conj=false, dual=false, copy=false) -> tdst Insert a trivial vector space, isomorphic to the underlying field, after position `i`, @@ -342,12 +342,12 @@ More specifically, adds a right monoidal unit or its dual. If `copy=false`, `tdst` might share data with `tsrc` whenever possible. Otherwise, a copy is always made. -See also [`insertleftunitspace`](@ref insertleftunitspace(::AbstractTensorMap, ::Val{i}) where {i}), -[`removeunitspace`](@ref removeunitspace(::AbstractTensorMap, ::Val{i}) where {i}). +See also [`insertleftunit`](@ref insertleftunit(::AbstractTensorMap, ::Val{i}) where {i}), +[`removeunit`](@ref removeunit(::AbstractTensorMap, ::Val{i}) where {i}). """ -function insertrightunitspace(t::AbstractTensorMap, ::Val{i}=Val(numind(t)); +function insertrightunit(t::AbstractTensorMap, ::Val{i}=Val(numind(t)); copy::Bool=false, conj::Bool=false, dual::Bool=false) where {i} - W = insertrightunitspace(space(t), Val(i); conj, dual) + W = insertrightunit(space(t), Val(i); conj, dual) if t isa TensorMap return TensorMap{scalartype(t)}(copy ? Base.copy(t.data) : t.data, W) else @@ -360,7 +360,7 @@ function insertrightunitspace(t::AbstractTensorMap, ::Val{i}=Val(numind(t)); end """ - removeunitspace(tsrc::AbstractTensorMap, i; copy=false) -> tdst + removeunit(tsrc::AbstractTensorMap, i; copy=false) -> tdst This removes a trivial tensor product factor at position `1 ≤ i ≤ N`, where `i` can be specified as an `Int` or as `Val(i)` for improved type stability. @@ -368,11 +368,11 @@ For this to work, that factor has to be isomorphic to the field of scalars. If `copy=false`, `tdst` might share data with `tsrc` whenever possible. Otherwise, a copy is always made. -This operation undoes the work of [`insertleftunitspace`](@ref insertleftunitspace(::AbstractTensorMap, ::Val{i}) where {i}) -and [`insertrightunitspace`](@ref insertrightunitspace(::AbstractTensorMap, ::Val{i}) where {i}). +This operation undoes the work of [`insertleftunit`](@ref insertleftunit(::AbstractTensorMap, ::Val{i}) where {i}) +and [`insertrightunit`](@ref insertrightunit(::AbstractTensorMap, ::Val{i}) where {i}). """ -function removeunitspace(t::AbstractTensorMap, ::Val{i}; copy::Bool=false) where {i} - W = removeunitspace(space(t), Val(i)) +function removeunit(t::AbstractTensorMap, ::Val{i}; copy::Bool=false) where {i} + W = removeunit(space(t), Val(i)) if t isa TensorMap return TensorMap{scalartype(t)}(copy ? Base.copy(t.data) : t.data, W) else diff --git a/test/symmetries/spaces.jl b/test/symmetries/spaces.jl index 33f895eb7..93fd7fc71 100644 --- a/test/symmetries/spaces.jl +++ b/test/symmetries/spaces.jl @@ -304,9 +304,9 @@ end @test @constinferred(⊗(V1, V2, V3 ⊗ V4)) == P @test @constinferred(⊗(V1, V2 ⊗ V3, V4)) == P @test V1 * V2 * unitspace(V1) * V3 * V4 == - @constinferred(insertleftunitspace(P, 3)) == - @constinferred(insertrightunitspace(P, 2)) - @test @constinferred(removeunitspace(V1 * V2 * unitspace(V1)' * V3 * V4, 3)) == P + @constinferred(insertleftunit(P, 3)) == + @constinferred(insertrightunit(P, 2)) + @test @constinferred(removeunit(V1 * V2 * unitspace(V1)' * V3 * V4, 3)) == P @test fuse(V1, V2', V3) ≅ V1 ⊗ V2' ⊗ V3 @test fuse(V1, V2', V3) ≾ V1 ⊗ V2' ⊗ V3 @test fuse(V1, V2', V3) ≿ V1 ⊗ V2' ⊗ V3 @@ -365,9 +365,9 @@ end @test @constinferred(⊗(V1, V2, V3)) == P @test @constinferred(adjoint(P)) == dual(P) == V3' ⊗ V2' ⊗ V1' @test V1 * V2 * unitspace(V1)' * V3 == - @constinferred(insertleftunitspace(P, 3; conj = true)) == - @constinferred(insertrightunitspace(P, 2; conj = true)) - @test P == @constinferred(removeunitspace(insertleftunitspace(P, 3), 3)) + @constinferred(insertleftunit(P, 3; conj = true)) == + @constinferred(insertrightunit(P, 2; conj = true)) + @test P == @constinferred(removeunit(insertleftunit(P, 3), 3)) @test fuse(V1, V2', V3) ≅ V1 ⊗ V2' ⊗ V3 @test fuse(V1, V2', V3) ≾ V1 ⊗ V2' ⊗ V3 ≾ fuse(V1 ⊗ V2' ⊗ V3) @test fuse(V1, V2') ⊗ V3 ≾ V1 ⊗ V2' ⊗ V3 @@ -446,22 +446,22 @@ end @test permute(W, ((2, 4, 5), (3, 1))) == (V2 ⊗ V4' ⊗ V5' ← V3 ⊗ V1') @test (V1 ⊗ V2 ← V1 ⊗ V2) == @constinferred TensorKit.compose(W, W') @test (V1 ⊗ V2 ← V3 ⊗ V4 ⊗ V5 ⊗ unitspace(V5)) == - @constinferred(insertleftunitspace(W)) == - @constinferred(insertrightunitspace(W)) - @test @constinferred(removeunitspace(insertleftunitspace(W), $(numind(W) + 1))) == W + @constinferred(insertleftunit(W)) == + @constinferred(insertrightunit(W)) + @test @constinferred(removeunit(insertleftunit(W), $(numind(W) + 1))) == W @test (V1 ⊗ V2 ← V3 ⊗ V4 ⊗ V5 ⊗ unitspace(V5)') == - @constinferred(insertleftunitspace(W; conj = true)) == - @constinferred(insertrightunitspace(W; conj = true)) + @constinferred(insertleftunit(W; conj = true)) == + @constinferred(insertrightunit(W; conj = true)) @test (unitspace(V1) ⊗ V1 ⊗ V2 ← V3 ⊗ V4 ⊗ V5) == - @constinferred(insertleftunitspace(W, 1)) == - @constinferred(insertrightunitspace(W, 0)) + @constinferred(insertleftunit(W, 1)) == + @constinferred(insertrightunit(W, 0)) @test (V1 ⊗ V2 ⊗ unitspace(V1) ← V3 ⊗ V4 ⊗ V5) == - @constinferred(insertrightunitspace(W, 2)) + @constinferred(insertrightunit(W, 2)) @test (V1 ⊗ V2 ← unitspace(V1) ⊗ V3 ⊗ V4 ⊗ V5) == - @constinferred(insertleftunitspace(W, 3)) - @test @constinferred(removeunitspace(insertleftunitspace(W, 3), 3)) == W - @test @constinferred(insertrightunitspace(one(V1) ← V1, 0)) == (unitspace(V1) ← V1) - @test_throws BoundsError insertleftunitspace(one(V1) ← V1, 0) + @constinferred(insertleftunit(W, 3)) + @test @constinferred(removeunit(insertleftunit(W, 3), 3)) == W + @test @constinferred(insertrightunit(one(V1) ← V1, 0)) == (unitspace(V1) ← V1) + @test_throws BoundsError insertleftunit(one(V1) ← V1, 0) end end diff --git a/test/tensors/tensors.jl b/test/tensors/tensors.jl index af691f169..32b3fc47d 100644 --- a/test/tensors/tensors.jl +++ b/test/tensors/tensors.jl @@ -158,21 +158,21 @@ for V in spacelist for T in (Float32, ComplexF64) t = @constinferred rand(T, W) if isa(UnitStyle(I), SimpleUnit) - t2 = @constinferred insertleftunitspace(t) - @test t2 == @constinferred insertrightunitspace(t) - @test space(t2) == insertleftunitspace(space(t)) - @test @constinferred(removeunitspace(t2, $(numind(t2)))) == t - t3 = @constinferred insertleftunitspace(t; copy = true) - @test t3 == @constinferred insertrightunitspace(t; copy = true) - @test @constinferred(removeunitspace(t3, $(numind(t3)))) == t + t2 = @constinferred insertleftunit(t) + @test t2 == @constinferred insertrightunit(t) + @test space(t2) == insertleftunit(space(t)) + @test @constinferred(removeunit(t2, $(numind(t2)))) == t + t3 = @constinferred insertleftunit(t; copy = true) + @test t3 == @constinferred insertrightunit(t; copy = true) + @test @constinferred(removeunit(t3, $(numind(t3)))) == t else - t2 = @constinferred insertleftunitspace(t, 5) - @test t2 == @constinferred insertrightunitspace(t, 4) - @test space(t2) == insertleftunitspace(space(t), 5) - @test @constinferred(removeunitspace(t2, $(numind(t2) - 1))) == t - t3 = @constinferred insertleftunitspace(t, 5; copy = true) - @test t3 == @constinferred insertrightunitspace(t, 4; copy = true) - @test @constinferred(removeunitspace(t3, $(numind(t3) - 1))) == t + t2 = @constinferred insertleftunit(t, 5) + @test t2 == @constinferred insertrightunit(t, 4) + @test space(t2) == insertleftunit(space(t), 5) + @test @constinferred(removeunit(t2, $(numind(t2) - 1))) == t + t3 = @constinferred insertleftunit(t, 5; copy = true) + @test t3 == @constinferred insertrightunit(t, 4; copy = true) + @test @constinferred(removeunit(t3, $(numind(t3) - 1))) == t end @test numind(t2) == numind(t) + 1 @@ -184,19 +184,19 @@ for V in spacelist @test b == block(t3, c) end - t4 = @constinferred insertrightunitspace(t, 3; dual = true) + t4 = @constinferred insertrightunit(t, 3; dual = true) @test numin(t4) == numin(t) && numout(t4) == numout(t) + 1 for (c, b) in blocks(t) @test b == block(t4, c) end - @test @constinferred(removeunitspace(t4, 4)) == t + @test @constinferred(removeunit(t4, 4)) == t - t5 = @constinferred insertleftunitspace(t, 4; dual = true) + t5 = @constinferred insertleftunit(t, 4; dual = true) @test numin(t5) == numin(t) + 1 && numout(t5) == numout(t) for (c, b) in blocks(t) @test b == block(t5, c) end - @test @constinferred(removeunitspace(t5, 4)) == t + @test @constinferred(removeunit(t5, 4)) == t end end if hasfusiontensor(I) From f73b24a9aeb56c29d7b8a583f8ead6d153524204 Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Wed, 5 Nov 2025 15:13:47 +0100 Subject: [PATCH 44/95] remove dupe exports --- src/TensorKit.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/TensorKit.jl b/src/TensorKit.jl index 9c1f8846b..724fd29f5 100644 --- a/src/TensorKit.jl +++ b/src/TensorKit.jl @@ -20,7 +20,7 @@ export unit, rightunit, leftunit, allunits, isunit # Export common vector space, fusion tree and tensor types export VectorSpace, Field, ElementarySpace # abstract vector spaces -export unitspace, zerospace, leftunitspace, rightunitspace +export leftunitspace, rightunitspace export InnerProductStyle, NoInnerProduct, HasInnerProduct, EuclideanInnerProduct export ComplexSpace, CartesianSpace, GeneralSpace, GradedSpace # concrete spaces export ZNSpace, Z2Space, Z3Space, Z4Space, U1Space, CU1Space, SU2Space From 9cc5696a45b4aae6bbd6a81da1dcf63835a0be61 Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Wed, 5 Nov 2025 15:40:20 +0100 Subject: [PATCH 45/95] fix `dim` and revert unnecessary Int converts --- src/spaces/gradedspace.jl | 4 ++-- src/tensors/abstracttensor.jl | 2 +- src/tensors/tensor.jl | 2 +- test/tensors/factorizations.jl | 8 ++++---- test/tensors/tensors.jl | 28 ++++++++++++++-------------- 5 files changed, 22 insertions(+), 22 deletions(-) diff --git a/src/spaces/gradedspace.jl b/src/spaces/gradedspace.jl index 23ab43f3b..702e406d9 100644 --- a/src/spaces/gradedspace.jl +++ b/src/spaces/gradedspace.jl @@ -92,8 +92,8 @@ Base.hash(V::GradedSpace, h::UInt) = hash(V.dual, hash(V.dims, h)) field(::Type{<:GradedSpace}) = ℂ InnerProductStyle(::Type{<:GradedSpace}) = EuclideanInnerProduct() function dim(V::GradedSpace) - T = promote_type(Int, real(sectorscalartype(sectortype(V)))) - return sum(dim(V, c) * dim(c) for c in sectors(V); init = zero(T)) + s = sectors(V) + return isempty(s) ? dim(first(allunits(sectortype(V)))) * 0 : sum(c -> dim(c) * dim(V, c), s) end function dim(V::GradedSpace{I, <:AbstractDict}, c::I) where {I <: Sector} return get(V.dims, isdual(V) ? dual(c) : c, 0) diff --git a/src/tensors/abstracttensor.jl b/src/tensors/abstracttensor.jl index dac9470ae..69fdce784 100644 --- a/src/tensors/abstracttensor.jl +++ b/src/tensors/abstracttensor.jl @@ -617,7 +617,7 @@ function Base.convert(::Type{Array}, t::AbstractTensorMap) dom = domain(t) T = sectorscalartype(I) <: Complex ? complex(scalartype(t)) : sectorscalartype(I) <: Integer ? scalartype(t) : float(scalartype(t)) - A = zeros(T, Int.(dims(t))...) + A = zeros(T, dims(t)...) for (f₁, f₂) in fusiontrees(t) F = convert(Array, (f₁, f₂)) Aslice = StridedView(A)[axes(cod, f₁.uncoupled)..., axes(dom, f₂.uncoupled)...] diff --git a/src/tensors/tensor.jl b/src/tensors/tensor.jl index 725904472..fe15d819d 100644 --- a/src/tensors/tensor.jl +++ b/src/tensors/tensor.jl @@ -333,7 +333,7 @@ function TensorMap( # dimension check codom = codomain(V) dom = domain(V) - arraysize = Int.(dims(V)) + arraysize = dims(V) matsize = (dim(codom), dim(dom)) if !(size(data) == arraysize || size(data) == matsize) diff --git a/test/tensors/factorizations.jl b/test/tensors/factorizations.jl index 5a8ec87df..3f20c8819 100644 --- a/test/tensors/factorizations.jl +++ b/test/tensors/factorizations.jl @@ -237,7 +237,7 @@ for V in spacelist @test isisometry(Vᴴ; side = :right) #FIXME: dimension of S is a float, might be a real issue if it's a decimal - trunc = truncrank(Int(dim(domain(S)) ÷ 2)) + trunc = truncrank(dim(domain(S)) ÷ 2) U1, S1, Vᴴ1 = @constinferred svd_trunc(t; trunc) @test t * Vᴴ1' ≈ U1 * S1 @test isisometry(U1) @@ -269,7 +269,7 @@ for V in spacelist @test isisometry(Vᴴ4; side = :right) @test norm(t - U4 * S4 * Vᴴ4) <= 0.5 - trunc = truncrank(Int(dim(domain(S)) ÷ 2)) & trunctol(; atol = λ - 10eps(λ)) + trunc = truncrank(dim(domain(S)) ÷ 2) & trunctol(; atol = λ - 10eps(λ)) U5, S5, Vᴴ5 = @constinferred svd_trunc(t; trunc) @test t * Vᴴ5' ≈ U5 * S5 @test isisometry(U5) @@ -299,7 +299,7 @@ for V in spacelist @test @constinferred isposdef(vdv) t isa DiagonalTensorMap || @test !isposdef(t) # unlikely for non-hermitian map - d, v = @constinferred eig_trunc(t; trunc = truncrank(Int(dim(domain(t)) ÷ 2))) + d, v = @constinferred eig_trunc(t; trunc = truncrank(dim(domain(t)) ÷ 2)) @test t * v ≈ v * d @test dim(domain(d)) ≤ dim(domain(t)) ÷ 2 @@ -330,7 +330,7 @@ for V in spacelist @test isposdef(t - λ * one(t) + 0.1 * one(t)) @test !isposdef(t - λ * one(t) - 0.1 * one(t)) - d, v = @constinferred eigh_trunc(t; trunc = truncrank(Int(dim(domain(t)) ÷ 2))) + d, v = @constinferred eigh_trunc(t; trunc = truncrank(dim(domain(t)) ÷ 2)) @test t * v ≈ v * d @test dim(domain(d)) ≤ dim(domain(t)) ÷ 2 end diff --git a/test/tensors/tensors.jl b/test/tensors/tensors.jl index 32b3fc47d..ae042d975 100644 --- a/test/tensors/tensors.jl +++ b/test/tensors/tensors.jl @@ -85,14 +85,14 @@ for V in spacelist t = @constinferred randn(T, W) end a = @constinferred convert(Array, t) - b = reshape(a, Int(dim(codomain(W))), Int(dim(domain(W)))) # no init in dim makes reshape error for su2 + b = reshape(a, dim(codomain(W)), dim(domain(W))) @test t ≈ @constinferred TensorMap(a, W) @test t ≈ @constinferred TensorMap(b, W) @test t === @constinferred TensorMap(t.data, W) end end for T in (Int, Float32, ComplexF64) - t = randn(T, V1 ⊗ V2 ← zerospace(V1)) # no init in dim makes zerospace call error for z2 + t = randn(T, V1 ⊗ V2 ← zerospace(V1)) a = convert(Array, t) @test norm(a) == 0 end @@ -427,8 +427,8 @@ for V in spacelist t1 = rand(T, W1 ← W1) t2 = rand(T, W2, W2) t = rand(T, W1 ← W2) - d1 = Int(dim(W1)) - d2 = Int(dim(W2)) + d1 = dim(W1) + d2 = dim(W2) At1 = reshape(convert(Array, t1), d1, d1) At2 = reshape(convert(Array, t2), d2, d2) At = reshape(convert(Array, t), d1, d2) @@ -469,7 +469,7 @@ for V in spacelist W = V1 ⊗ V2 for T in (Float64, ComplexF64) t = randn(T, W, W) - s = Int(dim(W)) + s = dim(W) expt = @constinferred exp(t) @test reshape(convert(Array, expt), (s, s)) ≈ exp(reshape(convert(Array, t), (s, s))) @@ -529,7 +529,7 @@ for V in spacelist @test norm(tA * t + t * tB + tC) < (norm(tA) + norm(tB) + norm(tC)) * eps(real(T))^(2 / 3) if BraidingStyle(I) isa Bosonic && hasfusiontensor(I) - matrix(x) = reshape(convert(Array, x), Int(dim(codomain(x))), Int(dim(domain(x)))) + matrix(x) = reshape(convert(Array, x), dim(codomain(x)), dim(domain(x))) @test matrix(t) ≈ sylvester(matrix(tA), matrix(tB), matrix(tC)) end end @@ -553,10 +553,10 @@ for V in spacelist t1 = rand(T, V2 ⊗ V3 ⊗ V1, V1) t2 = rand(T, V2 ⊗ V1 ⊗ V3, V2) t = @constinferred (t1 ⊗ t2) - d1 = Int(dim(codomain(t1))) - d2 = Int(dim(codomain(t2))) - d3 = Int(dim(domain(t1))) - d4 = Int(dim(domain(t2))) + d1 = dim(codomain(t1)) + d2 = dim(codomain(t2)) + d3 = dim(domain(t1)) + d4 = dim(domain(t2)) At = convert(Array, t) @test reshape(At, (d1, d2, d3, d4)) ≈ reshape(convert(Array, t1), (d1, 1, d3, 1)) .* @@ -618,10 +618,10 @@ end t1 = rand(T, V1 ⊗ V2, V3' ⊗ V4) t2 = rand(T, W2, W1 ⊗ W1') t = @constinferred (t1 ⊠ t2) - d1 = Int(dim(codomain(t1))) - d2 = Int(dim(codomain(t2))) - d3 = Int(dim(domain(t1))) - d4 = Int(dim(domain(t2))) + d1 = dim(codomain(t1)) + d2 = dim(codomain(t2)) + d3 = dim(domain(t1)) + d4 = dim(domain(t2)) At = convert(Array, t) @test reshape(At, (d1, d2, d3, d4)) ≈ reshape(convert(Array, t1), (d1, 1, d3, 1)) .* From 34a40c57698405614aebc66af2654855bbb299a7 Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Wed, 5 Nov 2025 16:22:57 +0100 Subject: [PATCH 46/95] change blocksectors of empty productspace --- src/spaces/productspace.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/spaces/productspace.jl b/src/spaces/productspace.jl index 662e6c8d1..606763e7f 100644 --- a/src/spaces/productspace.jl +++ b/src/spaces/productspace.jl @@ -151,7 +151,7 @@ function blocksectors(P::ProductSpace{S, N}) where {S, N} end bs = Vector{I}() if N == 0 - push!(bs, unit(I)) + return allunits(I) elseif N == 1 for s in sectors(P) push!(bs, first(s)) From 496a701052f5970bd8db68b4ffc5a0492c6532de Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Wed, 5 Nov 2025 17:00:36 +0100 Subject: [PATCH 47/95] fix gradedspace tests for product sectors including multifusion --- test/symmetries/spaces.jl | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/test/symmetries/spaces.jl b/test/symmetries/spaces.jl index 93fd7fc71..6abd9440b 100644 --- a/test/symmetries/spaces.jl +++ b/test/symmetries/spaces.jl @@ -187,7 +187,7 @@ end @timedtestset "ElementarySpace: $(type_repr(Vect[I]))" for I in sectorlist if Base.IteratorSize(values(I)) === Base.IsInfinite() - set = unique(vcat(unit(I), [randsector(I) for k in 1:10])) + set = unique(vcat(allunits(I)..., [randsector(I) for k in 1:10])) gen = (c => 2 for c in set) else gen = (values(I)[k] => (k + 1) for k in 1:length(values(I))) @@ -227,7 +227,8 @@ end @test W == GradedSpace(dict) @test @constinferred(zerospace(V)) == GradedSpace(unit => 0 for unit in allunits(I)) # randsector never returns trivial sector, so this cannot error - @test_throws ArgumentError("Sector $(allunits(I)[1]) appears multiple times") GradedSpace(allunits(I)[1] => 1, randsector(I) => 0, allunits(I)[1] => 3) + randunit = rand(collect(allunits(I))) + @test_throws ArgumentError("Sector $(randunit) appears multiple times") GradedSpace(randunit => 1, randsector(I) => 0, randunit => 3) else W = @constinferred GradedSpace(unit(I) => 1) @test W == GradedSpace(unit(I) => 1, randsector(I) => 0) @@ -274,7 +275,7 @@ end @test V ≺ ⊕(V, V) @test !(V ≻ ⊕(V, V)) if isa(UnitStyle(I), GenericUnit) - u = allunits(I)[1] + u = first(allunits(I)) else u = unit(I) end From d8706441c32ac3520243b8611f0b1f419f53ec73 Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Wed, 5 Nov 2025 17:08:28 +0100 Subject: [PATCH 48/95] bring back `Int` for truncrank dimensions --- test/tensors/factorizations.jl | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/test/tensors/factorizations.jl b/test/tensors/factorizations.jl index 3f20c8819..ecd096dda 100644 --- a/test/tensors/factorizations.jl +++ b/test/tensors/factorizations.jl @@ -9,22 +9,22 @@ spacelist = try if ENV["CI"] == "true" println("Detected running on CI") if Sys.iswindows() - (Vtr, Vℤ₃, VU₁, VfU₁, VCU₁, VSU₂, VIB_diag) + (Vtr, Vℤ₃, VU₁, VfU₁, VCU₁, VSU₂, VIB_diag, VIB_M) elseif Sys.isapple() - (Vtr, Vℤ₃, VfU₁, VfSU₂, VIB_diag) + (Vtr, Vℤ₃, VfU₁, VfSU₂, VIB_diag, VIB_M) else - (Vtr, VU₁, VCU₁, VSU₂, VfSU₂, VIB_diag) + (Vtr, VU₁, VCU₁, VSU₂, VfSU₂, VIB_diag, VIB_M) end else - (Vtr, Vℤ₃, VU₁, VfU₁, VCU₁, VSU₂, VfSU₂, VIB_diag) + (Vtr, Vℤ₃, VU₁, VfU₁, VCU₁, VSU₂, VfSU₂, VIB_diag, VIB_M) end catch - (Vtr, Vℤ₃, VU₁, VfU₁, VCU₁, VSU₂, VfSU₂, VIB_diag) + (Vtr, Vℤ₃, VU₁, VfU₁, VCU₁, VSU₂, VfSU₂, VIB_diag, VIB_M) end eltypes = (Float32, ComplexF64) -for V in spacelist +for V in (VIB_M, VIB_diag) I = sectortype(first(V)) Istr = TensorKit.type_repr(I) println("---------------------------------------") @@ -237,7 +237,7 @@ for V in spacelist @test isisometry(Vᴴ; side = :right) #FIXME: dimension of S is a float, might be a real issue if it's a decimal - trunc = truncrank(dim(domain(S)) ÷ 2) + trunc = truncrank(Int(dim(domain(S)) ÷ 2)) U1, S1, Vᴴ1 = @constinferred svd_trunc(t; trunc) @test t * Vᴴ1' ≈ U1 * S1 @test isisometry(U1) @@ -269,7 +269,7 @@ for V in spacelist @test isisometry(Vᴴ4; side = :right) @test norm(t - U4 * S4 * Vᴴ4) <= 0.5 - trunc = truncrank(dim(domain(S)) ÷ 2) & trunctol(; atol = λ - 10eps(λ)) + trunc = truncrank(Int(dim(domain(S)) ÷ 2)) & trunctol(; atol = λ - 10eps(λ)) U5, S5, Vᴴ5 = @constinferred svd_trunc(t; trunc) @test t * Vᴴ5' ≈ U5 * S5 @test isisometry(U5) @@ -299,7 +299,7 @@ for V in spacelist @test @constinferred isposdef(vdv) t isa DiagonalTensorMap || @test !isposdef(t) # unlikely for non-hermitian map - d, v = @constinferred eig_trunc(t; trunc = truncrank(dim(domain(t)) ÷ 2)) + d, v = @constinferred eig_trunc(t; trunc = truncrank(Int(dim(domain(t)) ÷ 2))) @test t * v ≈ v * d @test dim(domain(d)) ≤ dim(domain(t)) ÷ 2 @@ -330,7 +330,7 @@ for V in spacelist @test isposdef(t - λ * one(t) + 0.1 * one(t)) @test !isposdef(t - λ * one(t) - 0.1 * one(t)) - d, v = @constinferred eigh_trunc(t; trunc = truncrank(dim(domain(t)) ÷ 2)) + d, v = @constinferred eigh_trunc(t; trunc = truncrank(Int(dim(domain(t)) ÷ 2))) @test t * v ≈ v * d @test dim(domain(d)) ≤ dim(domain(t)) ÷ 2 end From 41ec7c5c386d8a1b8f463bb06a6b91f7b079199c Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Thu, 6 Nov 2025 08:39:33 +0100 Subject: [PATCH 49/95] bump TensorKitSectors compat --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 7d754d817..44be51d51 100644 --- a/Project.toml +++ b/Project.toml @@ -43,7 +43,7 @@ Random = "1" SafeTestsets = "0.1" ScopedValues = "1.3.0" Strided = "2" -TensorKitSectors = "=0.3.0, 0.3.2" +TensorKitSectors = "0.3.3" TensorOperations = "5.1" Test = "1" TestExtras = "0.2,0.3" From 31f7e0b866068a8046b76d81980b91b024cd4886 Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Fri, 7 Nov 2025 10:45:49 +0100 Subject: [PATCH 50/95] suggestions to dim and pinv --- src/spaces/gradedspace.jl | 4 ++-- src/tensors/diagonal.jl | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/spaces/gradedspace.jl b/src/spaces/gradedspace.jl index 702e406d9..94b21fac0 100644 --- a/src/spaces/gradedspace.jl +++ b/src/spaces/gradedspace.jl @@ -92,8 +92,8 @@ Base.hash(V::GradedSpace, h::UInt) = hash(V.dual, hash(V.dims, h)) field(::Type{<:GradedSpace}) = ℂ InnerProductStyle(::Type{<:GradedSpace}) = EuclideanInnerProduct() function dim(V::GradedSpace) - s = sectors(V) - return isempty(s) ? dim(first(allunits(sectortype(V)))) * 0 : sum(c -> dim(c) * dim(V, c), s) + init = 0 * dim(first(allunits(sectortype(V)))) + return sum(c -> dim(c) * dim(V, c), sectors(V); init = init) end function dim(V::GradedSpace{I, <:AbstractDict}, c::I) where {I <: Sector} return get(V.dims, isdual(V) ? dual(c) : c, 0) diff --git a/src/tensors/diagonal.jl b/src/tensors/diagonal.jl index 7c0976522..356f5dbf4 100644 --- a/src/tensors/diagonal.jl +++ b/src/tensors/diagonal.jl @@ -306,7 +306,7 @@ function LinearAlgebra.pinv(d::DiagonalTensorMap; kwargs...) if iszero(atol) rtol = get(kwargs, :rtol, zero(real(T))) else - rtol = sqrt(eps(real(float(unitspace(T))))) * length(d.data) + rtol = sqrt(eps(real(float(one(T))))) * length(d.data) end pdata = let tol = max(atol, rtol * maximum(abs, d.data)) map(x -> abs(x) < tol ? zero(x) : pinv(x), d.data) From bfcacdf99b259177cbdce99bb0a4d55feb089b6a Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Mon, 10 Nov 2025 09:34:03 +0100 Subject: [PATCH 51/95] fix doc error --- src/spaces/productspace.jl | 1 - 1 file changed, 1 deletion(-) diff --git a/src/spaces/productspace.jl b/src/spaces/productspace.jl index 606763e7f..19b511b09 100644 --- a/src/spaces/productspace.jl +++ b/src/spaces/productspace.jl @@ -234,7 +234,6 @@ end Return a tensor product of zero spaces of type `S`, i.e. this is the unit object under the tensor product operation, such that `V ⊗ one(V) == V`. """ - Base.one(V::VectorSpace) = one(typeof(V)) Base.one(::Type{<:ProductSpace{S}}) where {S <: ElementarySpace} = ProductSpace{S, 0}(()) Base.one(::Type{S}) where {S <: ElementarySpace} = ProductSpace{S, 0}(()) From e53d9c4fd98be254e8d5b34bce8e9f685f976136 Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Mon, 10 Nov 2025 09:34:11 +0100 Subject: [PATCH 52/95] add docstring to `unitspace` --- src/spaces/gradedspace.jl | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/spaces/gradedspace.jl b/src/spaces/gradedspace.jl index 94b21fac0..ee57f21bb 100644 --- a/src/spaces/gradedspace.jl +++ b/src/spaces/gradedspace.jl @@ -170,6 +170,13 @@ function rightunitspace(S::GradedSpace{I}) where {I<:Sector} end end +""" + unitspace(S::GradedSpace{I}) where {I<:Sector} -> GradedSpace{I} + +Return the corresponding vector space of type `GradedSpace{I}` that represents the + space consisting of the unit(s) of the objects in `Sector` `I`. For `I` with simple unit, +this is a one-dimensional space. +""" function unitspace(S::Type{<:GradedSpace{I}}) where {I<:Sector} return S(unit => 1 for unit in allunits(I)) end From 97eea7e07ce5b944c2a0c8a506ad25fc4d4e641d Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Mon, 10 Nov 2025 09:55:27 +0100 Subject: [PATCH 53/95] fix `allequal` version dep --- src/spaces/gradedspace.jl | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/spaces/gradedspace.jl b/src/spaces/gradedspace.jl index ee57f21bb..64d71c8f7 100644 --- a/src/spaces/gradedspace.jl +++ b/src/spaces/gradedspace.jl @@ -132,6 +132,12 @@ function Base.axes(V::GradedSpace{I}, c::I) where {I <: Sector} return (offset + 1):(offset + dim(c) * dim(V, c)) end +@static if VERSION < v"1.11" + _allequal(f, xs) = allequal(Base.Generator(f, xs)) +else + _allequal(f, xs) = allequal(f, xs) +end + """ leftunitspace(S::GradedSpace{I}) where {I<:Sector} -> GradedSpace{I} @@ -143,7 +149,7 @@ function leftunitspace(S::GradedSpace{I}) where {I<:Sector} return unitspace(typeof(S)) else !isempty(sectors(S)) || throw(ArgumentError("Cannot determine type of empty space")) - allequal(leftunit, sectors(S)) || + _allequal(leftunit, sectors(S)) || throw(ArgumentError("sectors of $S do not have the same left unit")) sector = leftunit(first(sectors(S))) @@ -162,7 +168,7 @@ function rightunitspace(S::GradedSpace{I}) where {I<:Sector} return unitspace(typeof(S)) else !isempty(sectors(S)) || throw(ArgumentError("Cannot determine type of empty space")) - allequal(rightunit, sectors(S)) || + _allequal(rightunit, sectors(S)) || throw(ArgumentError("sectors of $S do not have the same right unit")) sector = rightunit(first(sectors(S))) From d76cb1461a4080b6a4cc67d226f04830ea355084 Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Mon, 10 Nov 2025 09:59:29 +0100 Subject: [PATCH 54/95] format --- src/spaces/gradedspace.jl | 6 +++--- src/spaces/productspace.jl | 12 ++++++++---- src/tensors/indexmanipulations.jl | 14 +++++++++----- test/setup.jl | 22 +++++++++++----------- test/symmetries/fusiontrees.jl | 2 +- 5 files changed, 32 insertions(+), 24 deletions(-) diff --git a/src/spaces/gradedspace.jl b/src/spaces/gradedspace.jl index 64d71c8f7..72de1e14a 100644 --- a/src/spaces/gradedspace.jl +++ b/src/spaces/gradedspace.jl @@ -144,7 +144,7 @@ end Return the corresponding vector space of type `GradedSpace{I}` that represents the trivial one-dimensional space consisting of the left unit of the objects in `Sector` `I`. """ -function leftunitspace(S::GradedSpace{I}) where {I<:Sector} +function leftunitspace(S::GradedSpace{I}) where {I <: Sector} if UnitStyle(I) isa SimpleUnit return unitspace(typeof(S)) else @@ -163,7 +163,7 @@ end Return the corresponding vector space of type `GradedSpace{I}` that represents the trivial one-dimensional space consisting of the right unit of the objects in `Sector` `I`. """ -function rightunitspace(S::GradedSpace{I}) where {I<:Sector} +function rightunitspace(S::GradedSpace{I}) where {I <: Sector} if UnitStyle(I) isa SimpleUnit return unitspace(typeof(S)) else @@ -183,7 +183,7 @@ Return the corresponding vector space of type `GradedSpace{I}` that represents t space consisting of the unit(s) of the objects in `Sector` `I`. For `I` with simple unit, this is a one-dimensional space. """ -function unitspace(S::Type{<:GradedSpace{I}}) where {I<:Sector} +function unitspace(S::Type{<:GradedSpace{I}}) where {I <: Sector} return S(unit => 1 for unit in allunits(I)) end zerospace(S::Type{<:GradedSpace}) = S() diff --git a/src/spaces/productspace.jl b/src/spaces/productspace.jl index 19b511b09..dfc9fbe53 100644 --- a/src/spaces/productspace.jl +++ b/src/spaces/productspace.jl @@ -256,8 +256,10 @@ More specifically, adds a left monoidal unit or its dual. See also [`insertrightunit`](@ref insertrightunit(::ProductSpace, ::Val{i}) where {i}), [`removeunit`](@ref removeunit(::ProductSpace, ::Val{i}) where {i}). """ -function insertleftunit(P::ProductSpace, ::Val{i}=Val(length(P) + 1); - conj::Bool=false, dual::Bool=false) where {i} +function insertleftunit( + P::ProductSpace, ::Val{i} = Val(length(P) + 1); + conj::Bool = false, dual::Bool = false + ) where {i} N = length(P) I = sectortype(P) if UnitStyle(I) isa SimpleUnit @@ -285,8 +287,10 @@ More specifically, adds a right monoidal unit or its dual. See also [`insertleftunit`](@ref insertleftunit(::ProductSpace, ::Val{i}) where {i}), [`removeunit`](@ref removeunit(::ProductSpace, ::Val{i}) where {i}). """ -function insertrightunit(P::ProductSpace, ::Val{i}=Val(length(P)); - conj::Bool=false, dual::Bool=false) where {i} +function insertrightunit( + P::ProductSpace, ::Val{i} = Val(length(P)); + conj::Bool = false, dual::Bool = false + ) where {i} N = length(P) I = sectortype(P) if UnitStyle(I) isa SimpleUnit diff --git a/src/tensors/indexmanipulations.jl b/src/tensors/indexmanipulations.jl index 243e6656c..7465a7bb0 100644 --- a/src/tensors/indexmanipulations.jl +++ b/src/tensors/indexmanipulations.jl @@ -318,8 +318,10 @@ If `copy=false`, `tdst` might share data with `tsrc` whenever possible. Otherwis See also [`insertrightunit`](@ref insertrightunit(::AbstractTensorMap, ::Val{i}) where {i}), [`removeunit`](@ref removeunit(::AbstractTensorMap, ::Val{i}) where {i}). """ -function insertleftunit(t::AbstractTensorMap, ::Val{i}=Val(numind(t) + 1); - copy::Bool=false, conj::Bool=false, dual::Bool=false) where {i} +function insertleftunit( + t::AbstractTensorMap, ::Val{i} = Val(numind(t) + 1); + copy::Bool = false, conj::Bool = false, dual::Bool = false + ) where {i} W = insertleftunit(space(t), Val(i); conj, dual) if t isa TensorMap return TensorMap{scalartype(t)}(copy ? Base.copy(t.data) : t.data, W) @@ -345,8 +347,10 @@ If `copy=false`, `tdst` might share data with `tsrc` whenever possible. Otherwis See also [`insertleftunit`](@ref insertleftunit(::AbstractTensorMap, ::Val{i}) where {i}), [`removeunit`](@ref removeunit(::AbstractTensorMap, ::Val{i}) where {i}). """ -function insertrightunit(t::AbstractTensorMap, ::Val{i}=Val(numind(t)); - copy::Bool=false, conj::Bool=false, dual::Bool=false) where {i} +function insertrightunit( + t::AbstractTensorMap, ::Val{i} = Val(numind(t)); + copy::Bool = false, conj::Bool = false, dual::Bool = false + ) where {i} W = insertrightunit(space(t), Val(i); conj, dual) if t isa TensorMap return TensorMap{scalartype(t)}(copy ? Base.copy(t.data) : t.data, W) @@ -371,7 +375,7 @@ If `copy=false`, `tdst` might share data with `tsrc` whenever possible. Otherwis This operation undoes the work of [`insertleftunit`](@ref insertleftunit(::AbstractTensorMap, ::Val{i}) where {i}) and [`insertrightunit`](@ref insertrightunit(::AbstractTensorMap, ::Val{i}) where {i}). """ -function removeunit(t::AbstractTensorMap, ::Val{i}; copy::Bool=false) where {i} +function removeunit(t::AbstractTensorMap, ::Val{i}; copy::Bool = false) where {i} W = removeunit(space(t), Val(i)) if t isa TensorMap return TensorMap{scalartype(t)}(copy ? Base.copy(t.data) : t.data, W) diff --git a/test/setup.jl b/test/setup.jl index 466fe0198..551bdfffd 100644 --- a/test/setup.jl +++ b/test/setup.jl @@ -88,14 +88,14 @@ function random_fusiontree(I::Type{<:Sector}, N::Int) # for fusion tree tests in = nothing else rethrow(e) - end + end end end return out end # for fusion tree merge test -function safe_tensor_product(x::I, y::I) where {I<:Sector} +function safe_tensor_product(x::I, y::I) where {I <: Sector} tp = x ⊗ y if isempty(tp) return nothing @@ -194,21 +194,21 @@ C0, C1 = IsingBimodule(1, 1, 0), IsingBimodule(1, 1, 1) D0, D1 = IsingBimodule(2, 2, 0), IsingBimodule(2, 2, 1) M, Mop = IsingBimodule(1, 2, 0), IsingBimodule(2, 1, 0) VIB_diag = ( - Vect[IsingBimodule](C0 => 1, C1 => 2), - Vect[IsingBimodule](C0 => 2, C1 => 1), - Vect[IsingBimodule](C0 => 3, C1 => 1), - Vect[IsingBimodule](C0 => 2, C1 => 3), - Vect[IsingBimodule](C0 => 3, C1 => 2) + Vect[IsingBimodule](C0 => 1, C1 => 2), + Vect[IsingBimodule](C0 => 2, C1 => 1), + Vect[IsingBimodule](C0 => 3, C1 => 1), + Vect[IsingBimodule](C0 => 2, C1 => 3), + Vect[IsingBimodule](C0 => 3, C1 => 2), ) # not a random ordering! designed to make V1 ⊗ V2 ← V3 ⊗ V4 ⊗ V5 work (tensors) # while V1 ⊗ V2 ← V4 isn't empty (factorizations) VIB_M = ( Vect[IsingBimodule](C0 => 1, C1 => 2), - Vect[IsingBimodule](M => 3), - Vect[IsingBimodule](C0 => 2, C1 => 3), - Vect[IsingBimodule](M => 4), - Vect[IsingBimodule](D0 => 3, D1 => 4) + Vect[IsingBimodule](M => 3), + Vect[IsingBimodule](C0 => 2, C1 => 3), + Vect[IsingBimodule](M => 4), + Vect[IsingBimodule](D0 => 3, D1 => 4), ) end diff --git a/test/symmetries/fusiontrees.jl b/test/symmetries/fusiontrees.jl index 9cd90cee9..15a14decc 100644 --- a/test/symmetries/fusiontrees.jl +++ b/test/symmetries/fusiontrees.jl @@ -418,7 +418,7 @@ using .TestSetup else N = 4 end - + out = random_fusiontree(I, N) numtrees = count(n -> true, fusiontrees((out..., map(dual, out)...))) while !(0 < numtrees < 100) From 2d2d700dec113644a4019bb59b6929e9e82e4fc4 Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Mon, 10 Nov 2025 17:05:24 +0100 Subject: [PATCH 55/95] move code around + docstring extension --- src/TensorKit.jl | 3 +-- src/auxiliary/auxiliary.jl | 6 +++++ src/spaces/gradedspace.jl | 51 -------------------------------------- src/spaces/vectorspaces.jl | 48 ++++++++++++++++++++++++++++++++++- 4 files changed, 54 insertions(+), 54 deletions(-) diff --git a/src/TensorKit.jl b/src/TensorKit.jl index 724fd29f5..1dd8937f1 100644 --- a/src/TensorKit.jl +++ b/src/TensorKit.jl @@ -16,11 +16,9 @@ export Trivial, Z2Irrep, Z3Irrep, Z4Irrep, ZNIrrep, U1Irrep, SU2Irrep, CU1Irrep export ProductSector export FermionParity, FermionNumber, FermionSpin export FibonacciAnyon, IsingAnyon, IsingBimodule -export unit, rightunit, leftunit, allunits, isunit # Export common vector space, fusion tree and tensor types export VectorSpace, Field, ElementarySpace # abstract vector spaces -export leftunitspace, rightunitspace export InnerProductStyle, NoInnerProduct, HasInnerProduct, EuclideanInnerProduct export ComplexSpace, CartesianSpace, GeneralSpace, GradedSpace # concrete spaces export ZNSpace, Z2Space, Z3Space, Z4Space, U1Space, CU1Space, SU2Space @@ -37,6 +35,7 @@ export SpaceMismatch, SectorMismatch, IndexError # error types # Export general vector space methods export space, field, dual, dim, reduceddim, dims, fuse, flip, isdual export unitspace, zerospace, oplus, ominus +export leftunitspace, rightunitspace export insertleftunit, insertrightunit, removeunit # partial order for vector spaces diff --git a/src/auxiliary/auxiliary.jl b/src/auxiliary/auxiliary.jl index 4b97603ca..48afc9ee1 100644 --- a/src/auxiliary/auxiliary.jl +++ b/src/auxiliary/auxiliary.jl @@ -80,3 +80,9 @@ function _copyto!(A::StridedView{<:Any, 1}, B::StridedView{<:Any, 2}) return A end + +@static if VERSION < v"1.11" # TODO: remove once support for v1.10 is dropped + _allequal(f, xs) = allequal(Base.Generator(f, xs)) +else + _allequal(f, xs) = allequal(f, xs) +end \ No newline at end of file diff --git a/src/spaces/gradedspace.jl b/src/spaces/gradedspace.jl index 72de1e14a..54636175a 100644 --- a/src/spaces/gradedspace.jl +++ b/src/spaces/gradedspace.jl @@ -132,57 +132,6 @@ function Base.axes(V::GradedSpace{I}, c::I) where {I <: Sector} return (offset + 1):(offset + dim(c) * dim(V, c)) end -@static if VERSION < v"1.11" - _allequal(f, xs) = allequal(Base.Generator(f, xs)) -else - _allequal(f, xs) = allequal(f, xs) -end - -""" - leftunitspace(S::GradedSpace{I}) where {I<:Sector} -> GradedSpace{I} - -Return the corresponding vector space of type `GradedSpace{I}` that represents the trivial -one-dimensional space consisting of the left unit of the objects in `Sector` `I`. -""" -function leftunitspace(S::GradedSpace{I}) where {I <: Sector} - if UnitStyle(I) isa SimpleUnit - return unitspace(typeof(S)) - else - !isempty(sectors(S)) || throw(ArgumentError("Cannot determine type of empty space")) - _allequal(leftunit, sectors(S)) || - throw(ArgumentError("sectors of $S do not have the same left unit")) - - sector = leftunit(first(sectors(S))) - return spacetype(S)(sector => 1) - end -end - -""" - rightunitspace(S::GradedSpace{I}) where {I<:Sector} -> GradedSpace{I} - -Return the corresponding vector space of type `GradedSpace{I}` that represents the trivial -one-dimensional space consisting of the right unit of the objects in `Sector` `I`. -""" -function rightunitspace(S::GradedSpace{I}) where {I <: Sector} - if UnitStyle(I) isa SimpleUnit - return unitspace(typeof(S)) - else - !isempty(sectors(S)) || throw(ArgumentError("Cannot determine type of empty space")) - _allequal(rightunit, sectors(S)) || - throw(ArgumentError("sectors of $S do not have the same right unit")) - - sector = rightunit(first(sectors(S))) - return spacetype(S)(sector => 1) - end -end - -""" - unitspace(S::GradedSpace{I}) where {I<:Sector} -> GradedSpace{I} - -Return the corresponding vector space of type `GradedSpace{I}` that represents the - space consisting of the unit(s) of the objects in `Sector` `I`. For `I` with simple unit, -this is a one-dimensional space. -""" function unitspace(S::Type{<:GradedSpace{I}}) where {I <: Sector} return S(unit => 1 for unit in allunits(I)) end diff --git a/src/spaces/vectorspaces.jl b/src/spaces/vectorspaces.jl index 607666730..dc5e5a531 100644 --- a/src/spaces/vectorspaces.jl +++ b/src/spaces/vectorspaces.jl @@ -124,7 +124,9 @@ reduceddim(V::ElementarySpace) = sum(Base.Fix1(dim, V), sectors(V); init = 0) Return the corresponding vector space of type `S` that represents the trivial one-dimensional space, i.e. the space that is isomorphic to the corresponding field. Note that this is different from `one(V::S)`, which returns the empty product space -`ProductSpace{S,0}(())`. `Base.oneunit` falls back to `unitspace`. +`ProductSpace{S,0}(())`. `Base.oneunit` falls back to `unitspace`. For vector spaces +of type `GradedSpace{I}` where `Sector` `I` has a semi-simple unit structure, this +returns a multi-dimensional space corresponding to all unit sectors in `I`. """ unitspace(V::ElementarySpace) = unitspace(typeof(V)) Base.oneunit(V::ElementarySpace) = unitspace(V) @@ -141,6 +143,50 @@ zerospace(V::ElementarySpace) = zerospace(typeof(V)) Base.zero(V::ElementarySpace) = zerospace(V) Base.zero(::Type{V}) where {V <: ElementarySpace} = zerospace(V) +""" + leftunitspace(V::ElementarySpace)-> ElementarySpace + +Return the corresponding vector space of type `ElementarySpace` that represents the trivial +one-dimensional space, i.e. the space that is isomorphic to the corresponding field. For vector spaces +of type `GradedSpace{I}`, this corresponds to the unique left unit of the objects in `Sector` `I` present +in the vector space. +""" +function leftunitspace(S::ElementarySpace) + I = sectortype(S) + if UnitStyle(I) isa SimpleUnit + return unitspace(typeof(S)) + else + !isempty(sectors(S)) || throw(ArgumentError("Cannot determine type of empty space")) + _allequal(leftunit, sectors(S)) || + throw(ArgumentError("sectors of $S do not have the same left unit")) + + sector = leftunit(first(sectors(S))) + return spacetype(S)(sector => 1) + end +end + +""" + rightunitspace(S::ElementarySpace) -> ElementarySpace + +Return the corresponding vector space of type `ElementarySpace` that represents the trivial +one-dimensional space, i.e. the space that is isomorphic to the corresponding field. For vector spaces +of type `GradedSpace{I}`, this corresponds to the right unit of the objects in `Sector` `I` present +in the vector space. +""" +function rightunitspace(S::ElementarySpace) + I = sectortype(S) + if UnitStyle(I) isa SimpleUnit + return unitspace(typeof(S)) + else + !isempty(sectors(S)) || throw(ArgumentError("Cannot determine type of empty space")) + _allequal(rightunit, sectors(S)) || + throw(ArgumentError("sectors of $S do not have the same right unit")) + + sector = rightunit(first(sectors(S))) + return spacetype(S)(sector => 1) + end +end + """ ⊕(V₁::S, V₂::S, V₃::S...) where {S<:ElementarySpace} -> S oplus(V₁::S, V₂::S, V₃::S...) where {S<:ElementarySpace} -> S From b58fb34bb9e6225829629721d1e6484c08293411 Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Mon, 10 Nov 2025 17:29:06 +0100 Subject: [PATCH 56/95] introduce `isunitspace` + use in `removeunit` --- src/TensorKit.jl | 2 +- src/spaces/productspace.jl | 9 +-------- src/spaces/vectorspaces.jl | 27 +++++++++++++++++++++++++++ 3 files changed, 29 insertions(+), 9 deletions(-) diff --git a/src/TensorKit.jl b/src/TensorKit.jl index 1dd8937f1..52a84ba27 100644 --- a/src/TensorKit.jl +++ b/src/TensorKit.jl @@ -35,7 +35,7 @@ export SpaceMismatch, SectorMismatch, IndexError # error types # Export general vector space methods export space, field, dual, dim, reduceddim, dims, fuse, flip, isdual export unitspace, zerospace, oplus, ominus -export leftunitspace, rightunitspace +export leftunitspace, rightunitspace, isunitspace export insertleftunit, insertrightunit, removeunit # partial order for vector spaces diff --git a/src/spaces/productspace.jl b/src/spaces/productspace.jl index dfc9fbe53..c8faf30fd 100644 --- a/src/spaces/productspace.jl +++ b/src/spaces/productspace.jl @@ -322,14 +322,7 @@ and [`insertrightunit`](@ref insertrightunit(::ProductSpace, ::Val{i}) where {i} function removeunit(P::ProductSpace, ::Val{i}) where {i} 1 ≤ i ≤ length(P) || _boundserror(P, i) I = sectortype(P) - if isa(UnitStyle(I), SimpleUnit) - isisomorphic(P[i], unitspace(P[i])) || _nontrivialspaceerror(P, i) - else - isisomorphic(P[i], leftunitspace(P[i])) || - isisomorphic(P[i], rightunitspace(P[i])) || - isisomorphic(P[i], unitspace(P[i])) || - _nontrivialspaceerror(P, i) - end + isunitspace(P[i]) || _nontrivialspaceerror(P, i) return ProductSpace{spacetype(P)}(TupleTools.deleteat(P.spaces, i)) end diff --git a/src/spaces/vectorspaces.jl b/src/spaces/vectorspaces.jl index dc5e5a531..bc24402d0 100644 --- a/src/spaces/vectorspaces.jl +++ b/src/spaces/vectorspaces.jl @@ -187,6 +187,33 @@ function rightunitspace(S::ElementarySpace) end end +""" + isunitspace(S::ElementarySpace) -> Bool + +Return whether the elementary space `S` is a unit space, i.e. is isomorphic to the +trivial one-dimensional space. For vector spaces of type `GradedSpace{I}` where `Sector` `I` has a +semi-simple unit structure, this returns `true` if `S` is isomorphic to either the left, right or +semi-simple unit space. +""" +function isunitspace(S::ElementarySpace) #TODO: add tests for this + I = sectortype(S) + return if isa(UnitStyle(I), SimpleUnit) + isisomorphic(S, unitspace(S)) + else + try + isisomorphic(S, unitspace(S)) || + isisomorphic(S, leftunitspace(S)) || + isisomorphic(S, rightunitspace(S)) + catch e + if isa(e, ArgumentError) + return false + else + rethrow(e) + end + end + end +end + """ ⊕(V₁::S, V₂::S, V₃::S...) where {S<:ElementarySpace} -> S oplus(V₁::S, V₂::S, V₃::S...) where {S<:ElementarySpace} -> S From f178770be16207b8603011023278487946fcdae1 Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Mon, 10 Nov 2025 17:59:33 +0100 Subject: [PATCH 57/95] `isunit` change --- src/spaces/homspace.jl | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/spaces/homspace.jl b/src/spaces/homspace.jl index 0937bb9b7..37aeff5b3 100644 --- a/src/spaces/homspace.jl +++ b/src/spaces/homspace.jl @@ -99,9 +99,9 @@ function blocksectors(W::HomSpace) if N₁ == 0 && N₂ == 0 return allunits(I) elseif N₁ == 0 - return filter!(isone, collect(blocksectors(dom))) # module space cannot end in empty space + return filter!(isunit, collect(blocksectors(dom))) # module space cannot end in empty space elseif N₂ == 0 - return filter!(isone, collect(blocksectors(codom))) + return filter!(isunit, collect(blocksectors(codom))) elseif N₂ <= N₁ return filter!(c -> hasblock(codom, c), collect(blocksectors(dom))) else @@ -336,8 +336,7 @@ end end fusiontreeindices = sizehint!( - FusionTreeDict{Tuple{F₁, F₂}, Int}(), - length(fusiontreelist) + FusionTreeDict{Tuple{F₁, F₂}, Int}(), length(fusiontreelist) ) for (i, f₁₂) in enumerate(fusiontreelist) fusiontreeindices[f₁₂] = i From 3e9698a47976483fac4633fe9fb8431f6b402f91 Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Mon, 10 Nov 2025 18:00:49 +0100 Subject: [PATCH 58/95] clean up some tests + reduce git diff --- test/symmetries/fusiontrees.jl | 25 +---- test/symmetries/spaces.jl | 3 +- test/tensors/factorizations.jl | 10 +- test/tensors/tensors.jl | 163 +++++++++++++++------------------ 4 files changed, 84 insertions(+), 117 deletions(-) diff --git a/test/symmetries/fusiontrees.jl b/test/symmetries/fusiontrees.jl index 15a14decc..fa04ab2c4 100644 --- a/test/symmetries/fusiontrees.jl +++ b/test/symmetries/fusiontrees.jl @@ -505,32 +505,11 @@ using .TestSetup end if (BraidingStyle(I) isa Bosonic) && hasfusiontensor(I) - Af1 = convert(Array, f1) - Af2 = convert(Array, 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)]...) - ) + A = convert(Array, (f1, f2)) Ap = permutedims(A, (p1..., p2...)) A2 = zero(Ap) for ((f1′, f2′), coeff) in d - Af1′ = convert(Array, f1′) - Af2′ = convert(Array, 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)]...) - ) + A2 .+= coeff .* convert(Array, (f1′, f2′)) end @test Ap ≈ A2 end diff --git a/test/symmetries/spaces.jl b/test/symmetries/spaces.jl index 6abd9440b..53c555a13 100644 --- a/test/symmetries/spaces.jl +++ b/test/symmetries/spaces.jl @@ -226,9 +226,8 @@ end dict = Dict((unit => 1 for unit in allunits(I))..., randsector(I) => 0) @test W == GradedSpace(dict) @test @constinferred(zerospace(V)) == GradedSpace(unit => 0 for unit in allunits(I)) - # randsector never returns trivial sector, so this cannot error randunit = rand(collect(allunits(I))) - @test_throws ArgumentError("Sector $(randunit) appears multiple times") GradedSpace(randunit => 1, randsector(I) => 0, randunit => 3) + @test_throws ArgumentError("Sector $(randunit) appears multiple times") GradedSpace(randunit => 1, randunit => 3) else W = @constinferred GradedSpace(unit(I) => 1) @test W == GradedSpace(unit(I) => 1, randsector(I) => 0) diff --git a/test/tensors/factorizations.jl b/test/tensors/factorizations.jl index ecd096dda..b2326a5fd 100644 --- a/test/tensors/factorizations.jl +++ b/test/tensors/factorizations.jl @@ -24,7 +24,7 @@ end eltypes = (Float32, ComplexF64) -for V in (VIB_M, VIB_diag) +for V in spacelist I = sectortype(first(V)) Istr = TensorKit.type_repr(I) println("---------------------------------------") @@ -237,7 +237,7 @@ for V in (VIB_M, VIB_diag) @test isisometry(Vᴴ; side = :right) #FIXME: dimension of S is a float, might be a real issue if it's a decimal - trunc = truncrank(Int(dim(domain(S)) ÷ 2)) + trunc = truncrank(dim(domain(S)) ÷ 2) U1, S1, Vᴴ1 = @constinferred svd_trunc(t; trunc) @test t * Vᴴ1' ≈ U1 * S1 @test isisometry(U1) @@ -269,7 +269,7 @@ for V in (VIB_M, VIB_diag) @test isisometry(Vᴴ4; side = :right) @test norm(t - U4 * S4 * Vᴴ4) <= 0.5 - trunc = truncrank(Int(dim(domain(S)) ÷ 2)) & trunctol(; atol = λ - 10eps(λ)) + trunc = truncrank(dim(domain(S)) ÷ 2) & trunctol(; atol = λ - 10eps(λ)) U5, S5, Vᴴ5 = @constinferred svd_trunc(t; trunc) @test t * Vᴴ5' ≈ U5 * S5 @test isisometry(U5) @@ -299,7 +299,7 @@ for V in (VIB_M, VIB_diag) @test @constinferred isposdef(vdv) t isa DiagonalTensorMap || @test !isposdef(t) # unlikely for non-hermitian map - d, v = @constinferred eig_trunc(t; trunc = truncrank(Int(dim(domain(t)) ÷ 2))) + d, v = @constinferred eig_trunc(t; trunc = truncrank(dim(domain(t)) ÷ 2)) @test t * v ≈ v * d @test dim(domain(d)) ≤ dim(domain(t)) ÷ 2 @@ -330,7 +330,7 @@ for V in (VIB_M, VIB_diag) @test isposdef(t - λ * one(t) + 0.1 * one(t)) @test !isposdef(t - λ * one(t) - 0.1 * one(t)) - d, v = @constinferred eigh_trunc(t; trunc = truncrank(Int(dim(domain(t)) ÷ 2))) + d, v = @constinferred eigh_trunc(t; trunc = truncrank(dim(domain(t)) ÷ 2)) @test t * v ≈ v * d @test dim(domain(d)) ≤ dim(domain(t)) ÷ 2 end diff --git a/test/tensors/tensors.jl b/test/tensors/tensors.jl index ae042d975..236c741c5 100644 --- a/test/tensors/tensors.jl +++ b/test/tensors/tensors.jl @@ -245,27 +245,25 @@ for V in spacelist @test Base.promote_typeof(t, tc) == typeof(tc) @test Base.promote_typeof(tc, t) == typeof(tc + t) end - if symmetricbraiding - @timedtestset "Permutations: test via inner product invariance" begin - W = V1 ⊗ V2 ⊗ V3 ⊗ V4 ⊗ V5 - t = rand(ComplexF64, W) - t′ = randn!(similar(t)) - for k in 0:5 - for p in permutations(1:5) - p1 = ntuple(n -> p[n], k) - p2 = ntuple(n -> p[k + n], 5 - k) - t2 = @constinferred permute(t, (p1, p2)) - @test norm(t2) ≈ norm(t) - t2′ = permute(t′, (p1, p2)) - @test dot(t2′, t2) ≈ dot(t′, t) ≈ dot(transpose(t2′), transpose(t2)) - end - - t3 = @constinferred repartition(t, $k) - @test norm(t3) ≈ norm(t) - t3′ = @constinferred repartition!(similar(t3), t′) - @test norm(t3′) ≈ norm(t′) - @test dot(t′, t) ≈ dot(t3′, t3) + symmetricbraiding && @timedtestset "Permutations: test via inner product invariance" begin + W = V1 ⊗ V2 ⊗ V3 ⊗ V4 ⊗ V5 + t = rand(ComplexF64, W) + t′ = randn!(similar(t)) + for k in 0:5 + for p in permutations(1:5) + p1 = ntuple(n -> p[n], k) + p2 = ntuple(n -> p[k + n], 5 - k) + t2 = @constinferred permute(t, (p1, p2)) + @test norm(t2) ≈ norm(t) + t2′ = permute(t′, (p1, p2)) + @test dot(t2′, t2) ≈ dot(t′, t) ≈ dot(transpose(t2′), transpose(t2)) end + + t3 = @constinferred repartition(t, $k) + @test norm(t3) ≈ norm(t) + t3′ = @constinferred repartition!(similar(t3), t′) + @test norm(t3′) ≈ norm(t′) + @test dot(t′, t) ≈ dot(t3′, t3) end end if BraidingStyle(I) isa Bosonic && hasfusiontensor(I) @@ -292,24 +290,22 @@ for V in spacelist end end end - if symmetricbraiding - @timedtestset "Full trace: test self-consistency" begin - t = rand(ComplexF64, V1 ⊗ V2' ← V1 ⊗ V2') - s = @constinferred tr(t) - @test conj(s) ≈ tr(t') - if !isdual(V1) - t2 = twist!(t, 1) - end - if isdual(V2) - t2 = twist!(t, 2) - end - ss = tr(t2) - @planar s2 = t[a b; a b] - @planar t3[a; b] := t[a c; b c] - @planar s3 = t3[a; a] - @test ss ≈ s2 - @test ss ≈ s3 + symmetricbraiding && @timedtestset "Full trace: test self-consistency" begin + t = rand(ComplexF64, V1 ⊗ V2' ← V1 ⊗ V2') + s = @constinferred tr(t) + @test conj(s) ≈ tr(t') + if !isdual(V1) + t2 = twist!(t, 1) end + if isdual(V2) + t2 = twist!(t, 2) + end + ss = tr(t2) + @planar s2 = t[a b; a b] + @planar t3[a; b] := t[a c; b c] + @planar s3 = t3[a; a] + @test ss ≈ s2 + @test ss ≈ s3 end @timedtestset "Partial trace: test self-consistency" begin t = rand(ComplexF64, V1 ⊗ V2 ⊗ V3 ← V1 ⊗ V2 ⊗ V3) @@ -326,15 +322,14 @@ for V in spacelist @test t3 ≈ convert(Array, t2) end end - if isa(UnitStyle(I), SimpleUnit) #TODO: find version that works for all multifusion cases - @timedtestset "Trace and contraction" begin - t1 = rand(ComplexF64, V1 ⊗ V2 ⊗ V3) - t2 = rand(ComplexF64, V2' ⊗ V4 ⊗ V1') - t3 = t1 ⊗ t2 - @tensor ta[a, b] := t1[x, y, a] * t2[y, b, x] - @tensor tb[a, b] := t3[x, y, a, y, b, x] - @test ta ≈ tb - end + #TODO: find version that works for all multifusion cases + (UnitStyle(I) == SimpleUnit()) && @timedtestset "Trace and contraction" begin + t1 = rand(ComplexF64, V1 ⊗ V2 ⊗ V3) + t2 = rand(ComplexF64, V2' ⊗ V4 ⊗ V1') + t3 = t1 ⊗ t2 + @tensor ta[a, b] := t1[x, y, a] * t2[y, b, x] + @tensor tb[a, b] := t3[x, y, a, y, b, x] + @test ta ≈ tb end if BraidingStyle(I) isa Bosonic && hasfusiontensor(I) @timedtestset "Tensor contraction: test via conversion" begin @@ -353,41 +348,37 @@ for V in spacelist @test HrA12array ≈ convert(Array, HrA12) end end - if !isa(BraidingStyle(I), NoBraiding) - @timedtestset "Index flipping: test flipping inverse" begin - t = rand(ComplexF64, V1 ⊗ V1' ← V1' ⊗ V1) - for i in 1:4 - @test t ≈ flip(flip(t, i), i; inv = true) - @test t ≈ flip(flip(t, i; inv = true), i) - end + (BraidingStyle(I) != NoBraiding()) && @timedtestset "Index flipping: test flipping inverse" begin + t = rand(ComplexF64, V1 ⊗ V1' ← V1' ⊗ V1) + for i in 1:4 + @test t ≈ flip(flip(t, i), i; inv = true) + @test t ≈ flip(flip(t, i; inv = true), i) end end - if symmetricbraiding - @timedtestset "Index flipping: test via explicit flip" begin - t = rand(ComplexF64, V1 ⊗ V1' ← V1' ⊗ V1) - F1 = unitary(flip(V1), V1) + symmetricbraiding && @timedtestset "Index flipping: test via explicit flip" begin + t = rand(ComplexF64, V1 ⊗ V1' ← V1' ⊗ V1) + F1 = unitary(flip(V1), V1) - @tensor tf[a, b; c, d] := F1[a, a'] * t[a', b; c, d] - @test flip(t, 1) ≈ tf - @tensor tf[a, b; c, d] := conj(F1[b, b']) * t[a, b'; c, d] - @test twist!(flip(t, 2), 2) ≈ tf - @tensor tf[a, b; c, d] := F1[c, c'] * t[a, b; c', d] - @test flip(t, 3) ≈ tf - @tensor tf[a, b; c, d] := conj(F1[d, d']) * t[a, b; c, d'] - @test twist!(flip(t, 4), 4) ≈ tf - end - @timedtestset "Index flipping: test via contraction" begin - t1 = rand(ComplexF64, V1 ⊗ V2 ⊗ V3 ← V4) - t2 = rand(ComplexF64, V2' ⊗ V5 ← V4' ⊗ V1) - @tensor ta[a, b] := t1[x, y, a, z] * t2[y, b, z, x] - @tensor tb[a, b] := flip(t1, 1)[x, y, a, z] * flip(t2, 4)[y, b, z, x] - @test ta ≈ tb - @tensor tb[a, b] := flip(t1, (2, 4))[x, y, a, z] * flip(t2, (1, 3))[y, b, z, x] - @test ta ≈ tb - @tensor tb[a, b] := flip(t1, (1, 2, 4))[x, y, a, z] * flip(t2, (1, 3, 4))[y, b, z, x] - @tensor tb[a, b] := flip(t1, (1, 3))[x, y, a, z] * flip(t2, (2, 4))[y, b, z, x] - @test flip(ta, (1, 2)) ≈ tb - end + @tensor tf[a, b; c, d] := F1[a, a'] * t[a', b; c, d] + @test flip(t, 1) ≈ tf + @tensor tf[a, b; c, d] := conj(F1[b, b']) * t[a, b'; c, d] + @test twist!(flip(t, 2), 2) ≈ tf + @tensor tf[a, b; c, d] := F1[c, c'] * t[a, b; c', d] + @test flip(t, 3) ≈ tf + @tensor tf[a, b; c, d] := conj(F1[d, d']) * t[a, b; c, d'] + @test twist!(flip(t, 4), 4) ≈ tf + end + symmetricbraiding && @timedtestset "Index flipping: test via contraction" begin + t1 = rand(ComplexF64, V1 ⊗ V2 ⊗ V3 ← V4) + t2 = rand(ComplexF64, V2' ⊗ V5 ← V4' ⊗ V1) + @tensor ta[a, b] := t1[x, y, a, z] * t2[y, b, z, x] + @tensor tb[a, b] := flip(t1, 1)[x, y, a, z] * flip(t2, 4)[y, b, z, x] + @test ta ≈ tb + @tensor tb[a, b] := flip(t1, (2, 4))[x, y, a, z] * flip(t2, (1, 3))[y, b, z, x] + @test ta ≈ tb + @tensor tb[a, b] := flip(t1, (1, 2, 4))[x, y, a, z] * flip(t2, (1, 3, 4))[y, b, z, x] + @tensor tb[a, b] := flip(t1, (1, 3))[x, y, a, z] * flip(t2, (2, 4))[y, b, z, x] + @test flip(ta, (1, 2)) ≈ tb end @timedtestset "Multiplication of isometries: test properties" begin W2 = V4 ⊗ V5 @@ -564,15 +555,13 @@ for V in spacelist end end end - if symmetricbraiding - @timedtestset "Tensor product: test via tensor contraction" begin - for T in (Float32, ComplexF64) - t1 = rand(T, V2 ⊗ V3 ⊗ V1) - t2 = rand(T, V2 ⊗ V1 ⊗ V3) - t = @constinferred (t1 ⊗ t2) - @tensor t′[1, 2, 3, 4, 5, 6] := t1[1, 2, 3] * t2[4, 5, 6] - @test t ≈ t′ - end + symmetricbraiding && @timedtestset "Tensor product: test via tensor contraction" begin + for T in (Float32, ComplexF64) + t1 = rand(T, V2 ⊗ V3 ⊗ V1) + t2 = rand(T, V2 ⊗ V1 ⊗ V3) + t = @constinferred (t1 ⊗ t2) + @tensor t′[1, 2, 3, 4, 5, 6] := t1[1, 2, 3] * t2[4, 5, 6] + @test t ≈ t′ end end @timedtestset "Tensor absorption" begin From 6710e88e2a6c60c393acf9e068e7394cd5e639f5 Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Mon, 10 Nov 2025 18:04:08 +0100 Subject: [PATCH 59/95] have `blocksectors` always return a vector --- src/spaces/productspace.jl | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/spaces/productspace.jl b/src/spaces/productspace.jl index c8faf30fd..1e54c176e 100644 --- a/src/spaces/productspace.jl +++ b/src/spaces/productspace.jl @@ -151,7 +151,10 @@ function blocksectors(P::ProductSpace{S, N}) where {S, N} end bs = Vector{I}() if N == 0 - return allunits(I) + for u in allunits(I) + push!(bs, u) + end + return bs elseif N == 1 for s in sectors(P) push!(bs, first(s)) From f37421e3ee497460b0c7dd2d6454881341771574 Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Mon, 10 Nov 2025 18:18:19 +0100 Subject: [PATCH 60/95] rename + add todo --- test/setup.jl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/setup.jl b/test/setup.jl index 551bdfffd..38232228e 100644 --- a/test/setup.jl +++ b/test/setup.jl @@ -76,7 +76,8 @@ function force_planar(tsrc::TensorMap{<:Any, <:GradedSpace}) return tdst end -function random_fusiontree(I::Type{<:Sector}, N::Int) # for fusion tree tests +#TODO: make less yucky +function random_fusion(I::Type{<:Sector}, N::Int) # for fusion tree tests in = nothing out = nothing while in === nothing From 534783508759ee8fa1781401bf067dead841623e Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Mon, 10 Nov 2025 18:25:45 +0100 Subject: [PATCH 61/95] rewrite spaces tests to not specialise to fusion or multifusion --- test/symmetries/spaces.jl | 29 ++++++++++------------------- 1 file changed, 10 insertions(+), 19 deletions(-) diff --git a/test/symmetries/spaces.jl b/test/symmetries/spaces.jl index 53c555a13..d1b79d414 100644 --- a/test/symmetries/spaces.jl +++ b/test/symmetries/spaces.jl @@ -220,20 +220,14 @@ end @test eval_show(typeof(V)) == typeof(V) # space with no sectors @test dim(@constinferred(zerospace(V))) == 0 - # space with unit(s) - if isa(UnitStyle(I), GenericUnit) - W = @constinferred GradedSpace(unit => 1 for unit in allunits(I)) - dict = Dict((unit => 1 for unit in allunits(I))..., randsector(I) => 0) - @test W == GradedSpace(dict) - @test @constinferred(zerospace(V)) == GradedSpace(unit => 0 for unit in allunits(I)) - randunit = rand(collect(allunits(I))) - @test_throws ArgumentError("Sector $(randunit) appears multiple times") GradedSpace(randunit => 1, randunit => 3) - else - W = @constinferred GradedSpace(unit(I) => 1) - @test W == GradedSpace(unit(I) => 1, randsector(I) => 0) - @test @constinferred(zerospace(V)) == GradedSpace(unit(I) => 0) - @test_throws ArgumentError("Sector $(unit(I)) appears multiple times") GradedSpace(unit(I) => 1, randsector(I) => 0, unit(I) => 3) - end + # space with unit(s), always test as if multifusion + W = @constinferred GradedSpace(unit => 1 for unit in allunits(I)) + dict = Dict((unit => 1 for unit in allunits(I))...) + @test W == GradedSpace(dict) + @test @constinferred(zerospace(V)) == GradedSpace(unit => 0 for unit in allunits(I)) + randunit = rand(collect(allunits(I))) + @test_throws ArgumentError("Sector $(randunit) appears multiple times") GradedSpace(randunit => 1, randunit => 3) + @test @constinferred(unitspace(V)) == W == unitspace(typeof(V)) @test eval_show(W) == W @test isa(V, VectorSpace) @@ -273,11 +267,8 @@ end @test V == @constinferred infimum(V, ⊕(V, V)) @test V ≺ ⊕(V, V) @test !(V ≻ ⊕(V, V)) - if isa(UnitStyle(I), GenericUnit) - u = first(allunits(I)) - else - u = unit(I) - end + + u = first(allunits(I)) @test infimum(V, GradedSpace(u => 3)) == GradedSpace(u => 2) @test_throws SpaceMismatch (⊕(V, V')) end From 700f65331921c663bf556584d29d344cc44206c9 Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Mon, 10 Nov 2025 18:33:36 +0100 Subject: [PATCH 62/95] potential fix to isometry test --- test/tensors/tensors.jl | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/test/tensors/tensors.jl b/test/tensors/tensors.jl index 236c741c5..4f3555fda 100644 --- a/test/tensors/tensors.jl +++ b/test/tensors/tensors.jl @@ -144,13 +144,10 @@ for V in spacelist @test i2 * i1 == @constinferred(id(Vector{T}, V2 ⊗ V1)) end - if isa(UnitStyle(I), SimpleUnit) - # FIXME?: unitspace returns all units, leads to invalid fusion channels - w = @constinferred isometry(T, V1 ⊗ (unitspace(V1) ⊕ unitspace(V1)), V1) - @test dim(w) == 2 * dim(V1 ← V1) - @test w' * w == id(Vector{T}, V1) - @test w * w' == (w * w')^2 - end + w = @constinferred isometry(T, V1 ⊗ (rightunitspace(V1) ⊕ rightunitspace(V1)), V1) + @test dim(w) == 2 * dim(V1 ← V1) + @test w' * w == id(Vector{T}, V1) + @test w * w' == (w * w')^2 end end @timedtestset "Trivial space insertion and removal" begin From 658a5b1bc7f4334089081dde8e15db391e5e8310 Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Mon, 10 Nov 2025 18:34:14 +0100 Subject: [PATCH 63/95] keep `@tensor` for symmetric braiding test --- test/tensors/tensors.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/tensors/tensors.jl b/test/tensors/tensors.jl index 4f3555fda..5fe45b029 100644 --- a/test/tensors/tensors.jl +++ b/test/tensors/tensors.jl @@ -298,9 +298,9 @@ for V in spacelist t2 = twist!(t, 2) end ss = tr(t2) - @planar s2 = t[a b; a b] - @planar t3[a; b] := t[a c; b c] - @planar s3 = t3[a; a] + @tensor s2 = t[a b; a b] + @tensor t3[a; b] := t[a c; b c] + @tensor s3 = t3[a; a] @test ss ≈ s2 @test ss ≈ s3 end From bdc937fc5b7ff3feba1945a8ebdbd0246fbd1b83 Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Mon, 10 Nov 2025 18:36:13 +0100 Subject: [PATCH 64/95] keep `@tensor` tests + add todo --- test/tensors/tensors.jl | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/test/tensors/tensors.jl b/test/tensors/tensors.jl index 5fe45b029..19c576e56 100644 --- a/test/tensors/tensors.jl +++ b/test/tensors/tensors.jl @@ -305,10 +305,11 @@ for V in spacelist @test ss ≈ s3 end @timedtestset "Partial trace: test self-consistency" begin + # TODO: extend to multifusion but keep these @tensor tests t = rand(ComplexF64, V1 ⊗ V2 ⊗ V3 ← V1 ⊗ V2 ⊗ V3) - @planar t2[a; b] := t[c d b; c d a] - @planar t4[a b; c d] := t[e d c; e b a] - @planar t5[a; b] := t4[a c; b c] + @tensor t2[a; b] := t[c d b; c d a] + @tensor t4[a b; c d] := t[e d c; e b a] + @tensor t5[a; b] := t4[a c; b c] @test t2 ≈ t5 end if BraidingStyle(I) isa Bosonic && hasfusiontensor(I) From 149ce171ab7c5e2a85e2627984b3f0046fbbd05c Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Wed, 12 Nov 2025 10:16:45 +0100 Subject: [PATCH 65/95] fix return type of rank + test --- src/tensors/linalg.jl | 2 +- test/tensors/factorizations.jl | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/tensors/linalg.jl b/src/tensors/linalg.jl index 3783f42ae..747a44320 100644 --- a/src/tensors/linalg.jl +++ b/src/tensors/linalg.jl @@ -296,7 +296,7 @@ function LinearAlgebra.rank( t::AbstractTensorMap; atol::Real = 0, rtol::Real = atol > 0 ? 0 : _default_rtol(t) ) - r = zero(real(sectorscalartype(sectortype(t)))) + r = 0 * dim(first(allunits(sectortype(t)))) dim(t) == 0 && return r S = LinearAlgebra.svdvals(t) tol = max(atol, rtol * maximum(first, values(S))) diff --git a/test/tensors/factorizations.jl b/test/tensors/factorizations.jl index b2326a5fd..9f5efac01 100644 --- a/test/tensors/factorizations.jl +++ b/test/tensors/factorizations.jl @@ -346,11 +346,13 @@ for V in spacelist ) d1, d2 = dim(codomain(t)), dim(domain(t)) - @test rank(t) == min(d1, d2) + r = rank(t) + @test r == min(d1, d2) + @test eltype(r) == eltype(d1) M = left_null(t) - @test @constinferred(rank(M)) + rank(t) ≈ d1 + @test @constinferred(rank(M)) + r ≈ d1 Mᴴ = right_null(t) - @test rank(Mᴴ) + rank(t) ≈ d2 + @test rank(Mᴴ) + r ≈ d2 end for T in eltypes u = unitary(T, V1 ⊗ V2, V1 ⊗ V2) From ee612817e9e9844683361cc2adcdbc3621e52da1 Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Wed, 12 Nov 2025 11:03:56 +0100 Subject: [PATCH 66/95] more isunits --- src/tensors/indexmanipulations.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tensors/indexmanipulations.jl b/src/tensors/indexmanipulations.jl index 7465a7bb0..3ef7a344a 100644 --- a/src/tensors/indexmanipulations.jl +++ b/src/tensors/indexmanipulations.jl @@ -280,7 +280,7 @@ function twist!(t::AbstractTensorMap, is; inv::Bool = false) if BraidingStyle(sectortype(t)) == NoBraiding() for i in is cs = sectors(space(t, i)) - all(isone, cs) || throw(SectorMismatch(lazy"Cannot twist sectors $cs")) + all(isunit, cs) || throw(SectorMismatch(lazy"Cannot twist sectors $cs")) end return t end From 2ba0306b1c10e4ec4693d89aaaa50be3c0d8fb49 Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Wed, 12 Nov 2025 11:25:14 +0100 Subject: [PATCH 67/95] clean up `random_fusion` + remove redundant setup function --- test/setup.jl | 37 ++++++++++-------------------- test/symmetries/fusiontrees.jl | 41 ++++++++++++++-------------------- 2 files changed, 29 insertions(+), 49 deletions(-) diff --git a/test/setup.jl b/test/setup.jl index 38232228e..4283ebf4e 100644 --- a/test/setup.jl +++ b/test/setup.jl @@ -1,7 +1,7 @@ module TestSetup export smallset, randsector, hasfusiontensor, force_planar -export random_fusiontree, safe_tensor_product +export random_fusion export sectorlist export Vtr, Vℤ₂, Vfℤ₂, Vℤ₃, VU₁, VfU₁, VCU₁, VSU₂, VfSU₂, VSU₂U₁, Vfib, VIB_diag, VIB_M @@ -76,33 +76,20 @@ function force_planar(tsrc::TensorMap{<:Any, <:GradedSpace}) return tdst end -#TODO: make less yucky function random_fusion(I::Type{<:Sector}, N::Int) # for fusion tree tests - in = nothing - out = nothing - while in === nothing - out = ntuple(n -> randsector(I), N) - try - in = rand(collect(⊗(out...))) - catch e - if isa(e, ArgumentError) - in = nothing - else - rethrow(e) - end - end - end - return out -end + t = Vector{I}(undef, N) + t[1] = randsector(I) + len = 1 -# for fusion tree merge test -function safe_tensor_product(x::I, y::I) where {I <: Sector} - tp = x ⊗ y - if isempty(tp) - return nothing - else - return tp + while len < N + r = randsector(I) + f = ⊗(t[len], r) + if !isempty(f) + len += 1 + t[len] = r + end end + return tuple(t...) end sectorlist = ( diff --git a/test/symmetries/fusiontrees.jl b/test/symmetries/fusiontrees.jl index fa04ab2c4..afb2660d7 100644 --- a/test/symmetries/fusiontrees.jl +++ b/test/symmetries/fusiontrees.jl @@ -13,7 +13,7 @@ using .TestSetup @timedtestset "Fusion trees for $(TensorKit.type_repr(I))" verbose = true for I in sectorlist Istr = TensorKit.type_repr(I) N = 5 - out = random_fusiontree(I, N) + out = random_fusion(I, N) isdual = ntuple(n -> rand(Bool), N) in = rand(collect(⊗(out...))) numtrees = length(fusiontrees(out, in, isdual)) @@ -79,25 +79,18 @@ using .TestSetup end @testset "Fusion tree $Istr: insertat" begin N = 4 - out2 = random_fusiontree(I, N) + out2 = random_fusion(I, N) in2 = rand(collect(⊗(out2...))) isdual2 = ntuple(n -> rand(Bool), N) f2 = rand(collect(fusiontrees(out2, in2, isdual2))) for i in 1:N - out1, in1 = nothing, nothing - while in1 === nothing - try - out1 = random_fusiontree(I, N) # guaranteed good fusion - out1 = Base.setindex(out1, in2, i) # can lead to poor fusion - in1 = rand(collect(⊗(out1...))) - catch e - if isa(e, ArgumentError) - in1 = nothing - else - rethrow(e) - end - end + out1 = random_fusion(I, N) # guaranteed good fusion + out1 = Base.setindex(out1, in2, i) # can lead to poor fusion + while isempty(⊗(out1...)) + out1 = random_fusion(I, N) + out1 = Base.setindex(out1, in2, i) end + in1 = rand(collect(⊗(out1...))) isdual1 = ntuple(n -> rand(Bool), N) isdual1 = Base.setindex(isdual1, false, i) f1 = rand(collect(fusiontrees(out1, in1, isdual1))) @@ -334,17 +327,17 @@ using .TestSetup @testset "Fusion tree $Istr: merging" begin N = 3 - out1 = random_fusiontree(I, N) - out2 = random_fusiontree(I, N) + out1 = random_fusion(I, N) + out2 = random_fusion(I, N) in1 = rand(collect(⊗(out1...))) in2 = rand(collect(⊗(out2...))) - tp = safe_tensor_product(in1, in2) # messy solution but it works - while tp === nothing - out1 = random_fusiontree(I, N) - out2 = random_fusiontree(I, N) + tp = ⊗(in1, in2) # messy solution but it works + while isempty(tp) + out1 = random_fusion(I, N) + out2 = random_fusion(I, N) in1 = rand(collect(⊗(out1...))) in2 = rand(collect(⊗(out2...))) - tp = safe_tensor_product(in1, in2) + tp = ⊗(in1, in2) end f1 = rand(collect(fusiontrees(out1, in1))) @@ -419,10 +412,10 @@ using .TestSetup N = 4 end - out = random_fusiontree(I, N) + out = random_fusion(I, N) numtrees = count(n -> true, fusiontrees((out..., map(dual, out)...))) while !(0 < numtrees < 100) - out = random_fusiontree(I, N) + out = random_fusion(I, N) numtrees = count(n -> true, fusiontrees((out..., map(dual, out)...))) end incoming = rand(collect(⊗(out...))) From b64ca37b5e0a6d9cf65340c78bf81c8151b4c5bb Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Wed, 12 Nov 2025 12:04:34 +0100 Subject: [PATCH 68/95] assert spaces are suitable for factorization tests + deal with float dim --- test/tensors/factorizations.jl | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/test/tensors/factorizations.jl b/test/tensors/factorizations.jl index 9f5efac01..bba2153a2 100644 --- a/test/tensors/factorizations.jl +++ b/test/tensors/factorizations.jl @@ -33,6 +33,8 @@ for V in spacelist @timedtestset "Factorizations with symmetry: $Istr" verbose = true begin V1, V2, V3, V4, V5 = V W = V1 ⊗ V2 + @assert !isempty(blocksectors(W)) + @assert !isempty(intersect(blocksectors(V4), blocksectors(W))) @testset "QR decomposition" begin for T in eltypes, @@ -236,13 +238,15 @@ for V in spacelist @test isisometry(U) @test isisometry(Vᴴ; side = :right) - #FIXME: dimension of S is a float, might be a real issue if it's a decimal - trunc = truncrank(dim(domain(S)) ÷ 2) + # dimension of S is a float for IsingBimodule + nvals = dim(domain(S)) ÷ 2 + eltype(nvals) <: AbstractFloat && (nvals = Int(nvals)) + trunc = truncrank(nvals) U1, S1, Vᴴ1 = @constinferred svd_trunc(t; trunc) @test t * Vᴴ1' ≈ U1 * S1 @test isisometry(U1) @test isisometry(Vᴴ1; side = :right) - @test dim(domain(S1)) <= trunc.howmany + @test dim(domain(S1)) <= nvals λ = minimum(minimum, values(LinearAlgebra.diag(S1))) trunc = trunctol(; atol = λ - 10eps(λ)) @@ -269,13 +273,13 @@ for V in spacelist @test isisometry(Vᴴ4; side = :right) @test norm(t - U4 * S4 * Vᴴ4) <= 0.5 - trunc = truncrank(dim(domain(S)) ÷ 2) & trunctol(; atol = λ - 10eps(λ)) + trunc = truncrank(nvals) & trunctol(; atol = λ - 10eps(λ)) U5, S5, Vᴴ5 = @constinferred svd_trunc(t; trunc) @test t * Vᴴ5' ≈ U5 * S5 @test isisometry(U5) @test isisometry(Vᴴ5; side = :right) @test minimum(minimum, values(LinearAlgebra.diag(S5))) >= λ - @test dim(domain(S5)) ≤ dim(domain(S)) ÷ 2 + @test dim(domain(S5)) ≤ nvals end end @@ -299,9 +303,11 @@ for V in spacelist @test @constinferred isposdef(vdv) t isa DiagonalTensorMap || @test !isposdef(t) # unlikely for non-hermitian map - d, v = @constinferred eig_trunc(t; trunc = truncrank(dim(domain(t)) ÷ 2)) + nvals = dim(domain(t)) ÷ 2 + eltype(nvals) <: AbstractFloat && (nvals = Int(nvals)) + d, v = @constinferred eig_trunc(t; trunc = truncrank(nvals)) @test t * v ≈ v * d - @test dim(domain(d)) ≤ dim(domain(t)) ÷ 2 + @test dim(domain(d)) ≤ nvals t2 = (t + t') D, V = eigen(t2) @@ -330,9 +336,9 @@ for V in spacelist @test isposdef(t - λ * one(t) + 0.1 * one(t)) @test !isposdef(t - λ * one(t) - 0.1 * one(t)) - d, v = @constinferred eigh_trunc(t; trunc = truncrank(dim(domain(t)) ÷ 2)) + d, v = @constinferred eigh_trunc(t; trunc = truncrank(nvals)) @test t * v ≈ v * d - @test dim(domain(d)) ≤ dim(domain(t)) ÷ 2 + @test dim(domain(d)) ≤ nvals end end From 1eefd43c3495d6a6d3c0b2cf51bde5f5ec0bd893 Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Wed, 12 Nov 2025 14:52:45 +0100 Subject: [PATCH 69/95] get `insertleft/rightunit` working without explicit indices + edit some homspaces --- src/spaces/productspace.jl | 8 +++++--- test/tensors/tensors.jl | 31 +++++++++++-------------------- 2 files changed, 16 insertions(+), 23 deletions(-) diff --git a/src/spaces/productspace.jl b/src/spaces/productspace.jl index 1e54c176e..0c11561ba 100644 --- a/src/spaces/productspace.jl +++ b/src/spaces/productspace.jl @@ -269,8 +269,11 @@ function insertleftunit( u = unitspace(spacetype(P)) else N > 0 || throw(ArgumentError("cannot insert a sensible unit space in the empty product space")) - i > N && throw(DomainError((P, i), "cannot insert a sensible left unit space")) - u = leftunitspace(P[i]) + if i == N + 1 + u = rightunitspace(P[N]) + else + u = leftunitspace(P[i]) + end end if dual u = TensorKit.dual(u) @@ -300,7 +303,6 @@ function insertrightunit( u = unitspace(spacetype(P)) else N > 0 || throw(ArgumentError("cannot insert a sensible unit space in the empty product space")) - i == 0 && throw(DomainError((P, i), "cannot insert a sensible right unit space")) u = rightunitspace(P[i]) end if dual diff --git a/test/tensors/tensors.jl b/test/tensors/tensors.jl index 19c576e56..f68ad4fca 100644 --- a/test/tensors/tensors.jl +++ b/test/tensors/tensors.jl @@ -151,26 +151,16 @@ for V in spacelist end end @timedtestset "Trivial space insertion and removal" begin - W = V1 ⊗ V2 ⊗ V3 ← V4 ⊗ V5 + W = V1 ⊗ V2 ← V3 ⊗ V4 ⊗ V5 for T in (Float32, ComplexF64) t = @constinferred rand(T, W) - if isa(UnitStyle(I), SimpleUnit) - t2 = @constinferred insertleftunit(t) - @test t2 == @constinferred insertrightunit(t) - @test space(t2) == insertleftunit(space(t)) - @test @constinferred(removeunit(t2, $(numind(t2)))) == t - t3 = @constinferred insertleftunit(t; copy = true) - @test t3 == @constinferred insertrightunit(t; copy = true) - @test @constinferred(removeunit(t3, $(numind(t3)))) == t - else - t2 = @constinferred insertleftunit(t, 5) - @test t2 == @constinferred insertrightunit(t, 4) - @test space(t2) == insertleftunit(space(t), 5) - @test @constinferred(removeunit(t2, $(numind(t2) - 1))) == t - t3 = @constinferred insertleftunit(t, 5; copy = true) - @test t3 == @constinferred insertrightunit(t, 4; copy = true) - @test @constinferred(removeunit(t3, $(numind(t3) - 1))) == t - end + t2 = @constinferred insertleftunit(t) + @test t2 == @constinferred insertrightunit(t) + @test space(t2) == insertleftunit(space(t)) + @test @constinferred(removeunit(t2, $(numind(t2)))) == t + t3 = @constinferred insertleftunit(t; copy = true) + @test t3 == @constinferred insertrightunit(t; copy = true) + @test @constinferred(removeunit(t3, $(numind(t3)))) == t @test numind(t2) == numind(t) + 1 @test scalartype(t2) === T @@ -182,7 +172,7 @@ for V in spacelist end t4 = @constinferred insertrightunit(t, 3; dual = true) - @test numin(t4) == numin(t) && numout(t4) == numout(t) + 1 + @test numin(t4) == numin(t) + 1 && numout(t4) == numout(t) for (c, b) in blocks(t) @test b == block(t4, c) end @@ -288,6 +278,7 @@ for V in spacelist end end symmetricbraiding && @timedtestset "Full trace: test self-consistency" begin + # TODO: extend to multifusion but keep these @tensor tests t = rand(ComplexF64, V1 ⊗ V2' ← V1 ⊗ V2') s = @constinferred tr(t) @test conj(s) ≈ tr(t') @@ -446,7 +437,7 @@ for V in spacelist end end @timedtestset "diag/diagm" begin - W = V1 ⊗ V2 ⊗ V3 ← V4 ⊗ V5 + W = V1 ⊗ V2 ← V3 ⊗ V4 ⊗ V5 t = randn(ComplexF64, W) d = LinearAlgebra.diag(t) D = LinearAlgebra.diagm(codomain(t), domain(t), d) From 015fb0f8fd7448fe166768865e21b49dd63db026 Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Wed, 12 Nov 2025 15:13:43 +0100 Subject: [PATCH 70/95] reduce git diff --- test/symmetries/fusiontrees.jl | 144 ++++++++++++++++----------------- 1 file changed, 71 insertions(+), 73 deletions(-) diff --git a/test/symmetries/fusiontrees.jl b/test/symmetries/fusiontrees.jl index afb2660d7..1dc6be9d4 100644 --- a/test/symmetries/fusiontrees.jl +++ b/test/symmetries/fusiontrees.jl @@ -234,94 +234,92 @@ using .TestSetup end end end - if isa(UnitStyle(I), SimpleUnit) - @testset "Fusion tree $Istr: elementary artin braid" begin - N = length(out) - isdual = ntuple(n -> rand(Bool), N) - for in in ⊗(out...) - for i in 1:(N - 1) - for f in fusiontrees(out, in, isdual) - d1 = @constinferred TK.artin_braid(f, i) # braid between random objects - @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 + (UnitStyle(I) == SimpleUnit) && @testset "Fusion tree $Istr: elementary artin braid" begin + N = length(out) + isdual = ntuple(n -> rand(Bool), N) + for in in ⊗(out...) + for i in 1:(N - 1) + for f in fusiontrees(out, in, isdual) + d1 = @constinferred TK.artin_braid(f, i) # braid between random objects + @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 - for (f2, coeff2) in d2 - if f2 == f - @test coeff2 ≈ 1 - else - @test isapprox(coeff2, 0; atol = 1.0e-12, rtol = 1.0e-12) - end + end + for (f2, coeff2) in d2 + if f2 == f + @test coeff2 ≈ 1 + else + @test isapprox(coeff2, 0; atol = 1.0e-12, rtol = 1.0e-12) 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 + 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 - 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, 3; inv = true) + d2[f2] = get(d2, f2, zero(coeff1)) + coeff2 * coeff1 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 + 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 - d1 = d2 - for (f1, coeff1) in d1 - if f1 == f - @test coeff1 ≈ 1 - else - @test isapprox(coeff1, 0; atol = 1.0e-12, rtol = 1.0e-12) - end + end + d1 = d2 + for (f1, coeff1) in d1 + if f1 == f + @test coeff1 ≈ 1 + else + @test isapprox(coeff1, 0; atol = 1.0e-12, rtol = 1.0e-12) end end - @testset "Fusion tree $Istr: braiding and permuting" begin - f = rand(collect(it)) - p = tuple(randperm(N)...) - ip = invperm(p) - - levels = ntuple(identity, N) - d = @constinferred braid(f, levels, p) - d2 = Dict{typeof(f), valtype(d)}() - levels2 = p - for (f2, coeff) in d - for (f1, coeff2) in braid(f2, levels2, ip) - d2[f1] = get(d2, f1, zero(coeff)) + coeff2 * coeff - end + end + (UnitStyle(I) == SimpleUnit) && @testset "Fusion tree $Istr: braiding and permuting" begin + f = rand(collect(it)) + p = tuple(randperm(N)...) + ip = invperm(p) + + levels = ntuple(identity, N) + d = @constinferred braid(f, levels, p) + d2 = Dict{typeof(f), valtype(d)}() + levels2 = p + for (f2, coeff) in d + for (f1, coeff2) in braid(f2, levels2, ip) + d2[f1] = get(d2, f1, zero(coeff)) + coeff2 * coeff end - for (f1, coeff2) in d2 - if f1 == f - @test coeff2 ≈ 1 - else - @test isapprox(coeff2, 0; atol = 1.0e-12, rtol = 1.0e-12) - end + end + for (f1, coeff2) in d2 + if f1 == f + @test coeff2 ≈ 1 + else + @test isapprox(coeff2, 0; atol = 1.0e-12, rtol = 1.0e-12) end + end - if (BraidingStyle(I) isa Bosonic) && hasfusiontensor(I) - Af = convert(Array, f) - Afp = permutedims(Af, (p..., N + 1)) - Afp2 = zero(Afp) - for (f1, coeff) in d - Afp2 .+= coeff .* convert(Array, f1) - end - @test Afp ≈ Afp2 + if (BraidingStyle(I) isa Bosonic) && hasfusiontensor(I) + Af = convert(Array, f) + Afp = permutedims(Af, (p..., N + 1)) + Afp2 = zero(Afp) + for (f1, coeff) in d + Afp2 .+= coeff .* convert(Array, f1) end + @test Afp ≈ Afp2 end end From a548b3617ddaefa4628e6bfaf38f0300d6fe7168 Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Wed, 12 Nov 2025 15:36:01 +0100 Subject: [PATCH 71/95] variable renames --- src/spaces/vectorspaces.jl | 64 +++++++++++++++++++------------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/src/spaces/vectorspaces.jl b/src/spaces/vectorspaces.jl index bc24402d0..af6c25780 100644 --- a/src/spaces/vectorspaces.jl +++ b/src/spaces/vectorspaces.jl @@ -42,8 +42,8 @@ represent objects in 𝕜-linear monoidal categories. abstract type VectorSpace end """ - field(a) -> Type{𝔽<:Field} - field(::Type{T}) -> Type{𝔽<:Field} + field(a) -> Type{𝔽 <: Field} + field(::Type{T}) -> Type{𝔽 <: Field} Return the type of field over which object `a` (e.g. a vector space or a tensor) is defined. Also works in type domain. @@ -119,12 +119,12 @@ Return the sum of all degeneracy dimensions of the vector space `V`. reduceddim(V::ElementarySpace) = sum(Base.Fix1(dim, V), sectors(V); init = 0) """ - unitspace(V::S) where {S<:ElementarySpace} -> S + unitspace(V::S) where {S <: ElementarySpace} -> S Return the corresponding vector space of type `S` that represents the trivial one-dimensional space, i.e. the space that is isomorphic to the corresponding field. Note that this is different from `one(V::S)`, which returns the empty product space -`ProductSpace{S,0}(())`. `Base.oneunit` falls back to `unitspace`. For vector spaces +`ProductSpace{S, 0}(())`. `Base.oneunit` falls back to `unitspace`. For vector spaces of type `GradedSpace{I}` where `Sector` `I` has a semi-simple unit structure, this returns a multi-dimensional space corresponding to all unit sectors in `I`. """ @@ -133,7 +133,7 @@ Base.oneunit(V::ElementarySpace) = unitspace(V) Base.oneunit(::Type{V}) where {V <: ElementarySpace} = unitspace(V) """ - zerospace(V::S) where {S<:ElementarySpace} -> S + zerospace(V::S) where {S <: ElementarySpace} -> S Return the corresponding vector space of type `S` that represents the zero-dimensional or empty space. This is, with a slight abuse of notation, the zero element of the direct sum of vector spaces. @@ -144,66 +144,66 @@ Base.zero(V::ElementarySpace) = zerospace(V) Base.zero(::Type{V}) where {V <: ElementarySpace} = zerospace(V) """ - leftunitspace(V::ElementarySpace)-> ElementarySpace + leftunitspace(V::S) where {S <: ElementarySpace} -> S Return the corresponding vector space of type `ElementarySpace` that represents the trivial one-dimensional space, i.e. the space that is isomorphic to the corresponding field. For vector spaces of type `GradedSpace{I}`, this corresponds to the unique left unit of the objects in `Sector` `I` present in the vector space. """ -function leftunitspace(S::ElementarySpace) - I = sectortype(S) +function leftunitspace(V::ElementarySpace) + I = sectortype(V) if UnitStyle(I) isa SimpleUnit - return unitspace(typeof(S)) + return unitspace(typeof(V)) else - !isempty(sectors(S)) || throw(ArgumentError("Cannot determine type of empty space")) - _allequal(leftunit, sectors(S)) || - throw(ArgumentError("sectors of $S do not have the same left unit")) + !isempty(sectors(V)) || throw(ArgumentError("Cannot determine type of empty space")) + _allequal(leftunit, sectors(V)) || + throw(ArgumentError("sectors of $V do not have the same left unit")) - sector = leftunit(first(sectors(S))) - return spacetype(S)(sector => 1) + sector = leftunit(first(sectors(V))) + return spacetype(V)(sector => 1) end end """ - rightunitspace(S::ElementarySpace) -> ElementarySpace + rightunitspace(V::S) where {S <: ElementarySpace} -> S Return the corresponding vector space of type `ElementarySpace` that represents the trivial one-dimensional space, i.e. the space that is isomorphic to the corresponding field. For vector spaces of type `GradedSpace{I}`, this corresponds to the right unit of the objects in `Sector` `I` present in the vector space. """ -function rightunitspace(S::ElementarySpace) - I = sectortype(S) +function rightunitspace(V::ElementarySpace) + I = sectortype(V) if UnitStyle(I) isa SimpleUnit - return unitspace(typeof(S)) + return unitspace(typeof(V)) else - !isempty(sectors(S)) || throw(ArgumentError("Cannot determine type of empty space")) - _allequal(rightunit, sectors(S)) || - throw(ArgumentError("sectors of $S do not have the same right unit")) + !isempty(sectors(V)) || throw(ArgumentError("Cannot determine type of empty space")) + _allequal(rightunit, sectors(V)) || + throw(ArgumentError("sectors of $V do not have the same right unit")) - sector = rightunit(first(sectors(S))) - return spacetype(S)(sector => 1) + sector = rightunit(first(sectors(V))) + return spacetype(V)(sector => 1) end end """ - isunitspace(S::ElementarySpace) -> Bool + isunitspace(V::S) where {S <: ElementarySpace} -> Bool -Return whether the elementary space `S` is a unit space, i.e. is isomorphic to the +Return whether the elementary space `V` is a unit space, i.e. is isomorphic to the trivial one-dimensional space. For vector spaces of type `GradedSpace{I}` where `Sector` `I` has a -semi-simple unit structure, this returns `true` if `S` is isomorphic to either the left, right or +semi-simple unit structure, this returns `true` if `V` is isomorphic to either the left, right or semi-simple unit space. """ -function isunitspace(S::ElementarySpace) #TODO: add tests for this - I = sectortype(S) +function isunitspace(V::ElementarySpace) #TODO: add tests for this + I = sectortype(V) return if isa(UnitStyle(I), SimpleUnit) - isisomorphic(S, unitspace(S)) + isisomorphic(V, unitspace(V)) else try - isisomorphic(S, unitspace(S)) || - isisomorphic(S, leftunitspace(S)) || - isisomorphic(S, rightunitspace(S)) + isisomorphic(V, unitspace(V)) || + isisomorphic(V, leftunitspace(V)) || + isisomorphic(V, rightunitspace(V)) catch e if isa(e, ArgumentError) return false From 7d7996b445f0c072509cab2ee8b75f74eb169bf0 Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Wed, 12 Nov 2025 15:41:51 +0100 Subject: [PATCH 72/95] tests for `is/left/rightunitspace` --- test/symmetries/spaces.jl | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/test/symmetries/spaces.jl b/test/symmetries/spaces.jl index d1b79d414..3478f26f9 100644 --- a/test/symmetries/spaces.jl +++ b/test/symmetries/spaces.jl @@ -87,7 +87,9 @@ end @test @constinferred(axes(V)) == Base.OneTo(d) @test ℝ^d == ℝ[](d) == CartesianSpace(d) == typeof(V)(d) W = @constinferred ℝ^1 + @test @constinferred(isunitspace(W)) @test @constinferred(unitspace(V)) == W == unitspace(typeof(V)) + @test @constinferred(leftunitspace(V)) == W == @constinferred(rightunitspace(V)) @test @constinferred(zerospace(V)) == ℝ^0 == zerospace(typeof(V)) @test @constinferred(⊕(V, zerospace(V))) == V @test @constinferred(⊕(V, V)) == ℝ^(2d) @@ -133,7 +135,9 @@ end @test @constinferred(axes(V)) == Base.OneTo(d) @test ℂ^d == Vect[Trivial](d) == Vect[](Trivial() => d) == ℂ[](d) == typeof(V)(d) W = @constinferred ℂ^1 + @test @constinferred(isunitspace(W)) @test @constinferred(unitspace(V)) == W == unitspace(typeof(V)) + @test @constinferred(leftunitspace(V)) == W == @constinferred(rightunitspace(V)) @test @constinferred(zerospace(V)) == ℂ^0 == zerospace(typeof(V)) @test @constinferred(⊕(V, zerospace(V))) == V @test @constinferred(⊕(V, V)) == ℂ^(2d) @@ -228,7 +232,14 @@ end randunit = rand(collect(allunits(I))) @test_throws ArgumentError("Sector $(randunit) appears multiple times") GradedSpace(randunit => 1, randunit => 3) + @test isunitspace(W) @test @constinferred(unitspace(V)) == W == unitspace(typeof(V)) + if isa(UnitStyle(I), SimpleUnit) + @test @constinferred(leftunitspace(V)) == W == @constinferred(rightunitspace(V)) + else + @test_throws ArgumentError leftunitspace(V) + @test_throws ArgumentError rightunitspace(V) + end @test eval_show(W) == W @test isa(V, VectorSpace) @test isa(V, ElementarySpace) From 0a840c3fb0970f95a9a485f6cbcbe0e127d07167 Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Wed, 12 Nov 2025 15:42:55 +0100 Subject: [PATCH 73/95] format --- src/auxiliary/auxiliary.jl | 2 +- src/spaces/homspace.jl | 4 ++-- src/spaces/productspace.jl | 22 +++++++++++----------- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/auxiliary/auxiliary.jl b/src/auxiliary/auxiliary.jl index 48afc9ee1..a7105cda6 100644 --- a/src/auxiliary/auxiliary.jl +++ b/src/auxiliary/auxiliary.jl @@ -85,4 +85,4 @@ end _allequal(f, xs) = allequal(Base.Generator(f, xs)) else _allequal(f, xs) = allequal(f, xs) -end \ No newline at end of file +end diff --git a/src/spaces/homspace.jl b/src/spaces/homspace.jl index 37aeff5b3..f231e1278 100644 --- a/src/spaces/homspace.jl +++ b/src/spaces/homspace.jl @@ -196,7 +196,7 @@ function compose(W::HomSpace{S}, V::HomSpace{S}) where {S} end """ - insertleftunit(W::HomSpace, i=numind(W) + 1; conj=false, dual=false) + insertleftunit(W::HomSpace, i = numind(W) + 1; conj = false, dual = false) Insert a trivial vector space, isomorphic to the underlying field, at position `i`, which can be specified as an `Int` or as `Val(i)` for improved type stability. @@ -217,7 +217,7 @@ function insertleftunit( end """ - insertrightunit(W::HomSpace, i=numind(W); conj=false, dual=false) + insertrightunit(W::HomSpace, i = numind(W); conj = false, dual = false) Insert a trivial vector space, isomorphic to the underlying field, after position `i`, which can be specified as an `Int` or as `Val(i)` for improved type stability. diff --git a/src/spaces/productspace.jl b/src/spaces/productspace.jl index 0c11561ba..68394d3d1 100644 --- a/src/spaces/productspace.jl +++ b/src/spaces/productspace.jl @@ -1,8 +1,8 @@ """ - struct ProductSpace{S<:ElementarySpace, N} <: CompositeSpace{S} + struct ProductSpace{S <: ElementarySpace, N} <: CompositeSpace{S} A `ProductSpace` is a tensor product space of `N` vector spaces of type -`S<:ElementarySpace`. Only tensor products between [`ElementarySpace`](@ref) objects of the +`S <: ElementarySpace`. Only tensor products between [`ElementarySpace`](@ref) objects of the same type are allowed. """ struct ProductSpace{S <: ElementarySpace, N} <: CompositeSpace{S} @@ -87,7 +87,7 @@ end # more specific methods """ - sectors(P::ProductSpace{S, N}) where {S<:ElementarySpace} + sectors(P::ProductSpace{S, N}) where {S <: ElementarySpace} Return an iterator over all possible combinations of sectors (represented as an `NTuple{N, sectortype(S)}`) that can appear within the tensor product space `P`. @@ -151,9 +151,9 @@ function blocksectors(P::ProductSpace{S, N}) where {S, N} end bs = Vector{I}() if N == 0 - for u in allunits(I) - push!(bs, u) - end + for u in allunits(I) + push!(bs, u) + end return bs elseif N == 1 for s in sectors(P) @@ -199,7 +199,7 @@ hasblock(P::ProductSpace, c::Sector) = !isempty(fusiontrees(P, c)) blockdim(P::ProductSpace, c::Sector) Return the total dimension of a coupled sector `c` in the product space, by summing over -all `dim(P, s)` for all tuples of sectors `s::NTuple{N, <:Sector}` that can fuse to `c`, +all `dim(P, s)` for all tuples of sectors `s::NTuple{N, <: Sector}` that can fuse to `c`, counted with the correct multiplicity (i.e. number of ways in which `s` can fuse to `c`). See also [`hasblock`](@ref) and [`blocksectors`](@ref). @@ -231,8 +231,8 @@ end # unit element with respect to the monoidal structure of taking tensor products """ - one(::S) where {S<:ElementarySpace} -> ProductSpace{S, 0} - one(::ProductSpace{S}) where {S<:ElementarySpace} -> ProductSpace{S, 0} + one(::S) where {S <: ElementarySpace} -> ProductSpace{S, 0} + one(::ProductSpace{S}) where {S <: ElementarySpace} -> ProductSpace{S, 0} Return a tensor product of zero spaces of type `S`, i.e. this is the unit object under the tensor product operation, such that `V ⊗ one(V) == V`. @@ -251,7 +251,7 @@ fuse(P::ProductSpace{S, 0}) where {S <: ElementarySpace} = unitspace(S) fuse(P::ProductSpace{S}) where {S <: ElementarySpace} = fuse(P.spaces...) """ - insertleftunit(P::ProductSpace, i::Int=length(P) + 1; conj=false, dual=false) + insertleftunit(P::ProductSpace, i::Int = length(P) + 1; conj = false, dual = false) Insert a trivial vector space, isomorphic to the underlying field, at position `i`, which can be specified as an `Int` or as `Val(i)` for improved type stability. @@ -285,7 +285,7 @@ function insertleftunit( end """ - insertrightunit(P::ProductSpace, i=length(P); conj=false, dual=false) + insertrightunit(P::ProductSpace, i = length(P); conj = false, dual = false) Insert a trivial vector space, isomorphic to the underlying field, after position `i`, which can be specified as an `Int` or as `Val(i)` for improved type stability. From e1ebb2c1f510d52509f56307f3aad5e6fa636efb Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Wed, 12 Nov 2025 17:08:16 +0100 Subject: [PATCH 74/95] get some trace tests working for multifusion + remove some todos --- src/spaces/vectorspaces.jl | 2 +- test/tensors/tensors.jl | 55 ++++++++++++++++++++++++-------------- 2 files changed, 36 insertions(+), 21 deletions(-) diff --git a/src/spaces/vectorspaces.jl b/src/spaces/vectorspaces.jl index af6c25780..162ea054c 100644 --- a/src/spaces/vectorspaces.jl +++ b/src/spaces/vectorspaces.jl @@ -195,7 +195,7 @@ trivial one-dimensional space. For vector spaces of type `GradedSpace{I}` where semi-simple unit structure, this returns `true` if `V` is isomorphic to either the left, right or semi-simple unit space. """ -function isunitspace(V::ElementarySpace) #TODO: add tests for this +function isunitspace(V::ElementarySpace) I = sectortype(V) return if isa(UnitStyle(I), SimpleUnit) isisomorphic(V, unitspace(V)) diff --git a/test/tensors/tensors.jl b/test/tensors/tensors.jl index f68ad4fca..9e3755521 100644 --- a/test/tensors/tensors.jl +++ b/test/tensors/tensors.jl @@ -277,30 +277,45 @@ for V in spacelist end end end - symmetricbraiding && @timedtestset "Full trace: test self-consistency" begin - # TODO: extend to multifusion but keep these @tensor tests - t = rand(ComplexF64, V1 ⊗ V2' ← V1 ⊗ V2') - s = @constinferred tr(t) - @test conj(s) ≈ tr(t') - if !isdual(V1) - t2 = twist!(t, 1) - end - if isdual(V2) - t2 = twist!(t, 2) + @timedtestset "Full trace: test self-consistency" begin + if symmetricbraiding + t = rand(ComplexF64, V1 ⊗ V2' ← V1 ⊗ V2') + if !isdual(V1) + t2 = twist!(t, 1) + end + if isdual(V2) + t2 = twist!(t, 2) + end + s = @constinferred tr(t) + ss = tr(t2) + @tensor s2 = t[a b; a b] + @tensor t3[a; b] := t[a c; b c] + @tensor s3 = t3[a; a] + else + t = rand(ComplexF64, V1 ⊗ V2 ← V1 ⊗ V2) # avoid permutes + s = @constinferred tr(t) + ss = s + @planar s2 = t[a b; a b] + @planar t3[a; b] := t[a c; b c] + @planar s3 = t3[a; a] end - ss = tr(t2) - @tensor s2 = t[a b; a b] - @tensor t3[a; b] := t[a c; b c] - @tensor s3 = t3[a; a] + + @test conj(s) ≈ tr(t') @test ss ≈ s2 @test ss ≈ s3 end @timedtestset "Partial trace: test self-consistency" begin - # TODO: extend to multifusion but keep these @tensor tests - t = rand(ComplexF64, V1 ⊗ V2 ⊗ V3 ← V1 ⊗ V2 ⊗ V3) - @tensor t2[a; b] := t[c d b; c d a] - @tensor t4[a b; c d] := t[e d c; e b a] - @tensor t5[a; b] := t4[a c; b c] + if symmetricbraiding + t = rand(ComplexF64, V1 ⊗ V2 ⊗ V3 ← V1 ⊗ V2 ⊗ V3) + @tensor t2[a; b] := t[c d b; c d a] + @tensor t4[a b; c d] := t[e d c; e b a] + @tensor t5[a; b] := t4[a c; b c] + else + t = rand(ComplexF64, V3 ⊗ V4 ⊗ V5 ← V3 ⊗ V4 ⊗ V5) # compatible with module fusion + @planar t2[a; b] := t[c a d; c b d] + @planar t4[a b; c d] := t[e a b; e c d] + @planar t5[a; b] := t4[a c; b c] + end @test t2 ≈ t5 end if BraidingStyle(I) isa Bosonic && hasfusiontensor(I) @@ -311,7 +326,7 @@ for V in spacelist @test t3 ≈ convert(Array, t2) end end - #TODO: find version that works for all multifusion cases + #TODO: find version that works for all multifusion cases (UnitStyle(I) == SimpleUnit()) && @timedtestset "Trace and contraction" begin t1 = rand(ComplexF64, V1 ⊗ V2 ⊗ V3) t2 = rand(ComplexF64, V2' ⊗ V4 ⊗ V1') From d04ba251b2f6e6829168936cb2b48e7549873ba3 Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Thu, 13 Nov 2025 10:59:31 +0100 Subject: [PATCH 75/95] revert `isunit` change to `isone` --- src/tensors/indexmanipulations.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tensors/indexmanipulations.jl b/src/tensors/indexmanipulations.jl index 77fb5a172..276397feb 100644 --- a/src/tensors/indexmanipulations.jl +++ b/src/tensors/indexmanipulations.jl @@ -275,7 +275,7 @@ function has_shared_twist(t, inds) else for i in inds cs = sectors(space(t, i)) - all(isunit ∘ twist, cs) || return false + all(isone ∘ twist, cs) || return false end return true end From 847a9e0e3f7761b6761f2aa54801a4b8caecc711 Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Thu, 13 Nov 2025 11:18:33 +0100 Subject: [PATCH 76/95] fix full trace test for fermions --- test/tensors/tensors.jl | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/test/tensors/tensors.jl b/test/tensors/tensors.jl index 9e3755521..53868d55f 100644 --- a/test/tensors/tensors.jl +++ b/test/tensors/tensors.jl @@ -279,28 +279,29 @@ for V in spacelist end @timedtestset "Full trace: test self-consistency" begin if symmetricbraiding - t = rand(ComplexF64, V1 ⊗ V2' ← V1 ⊗ V2') + t = rand(ComplexF64, V1 ⊗ V2' ⊗ V2 ⊗ V1') + t2 = permute(t, ((1, 2), (4, 3))) + s = @constinferred tr(t2) + @test conj(s) ≈ tr(t2') if !isdual(V1) - t2 = twist!(t, 1) + t2 = twist!(t2, 1) end if isdual(V2) - t2 = twist!(t, 2) + t2 = twist!(t2, 2) end - s = @constinferred tr(t) ss = tr(t2) - @tensor s2 = t[a b; a b] - @tensor t3[a; b] := t[a c; b c] - @tensor s3 = t3[a; a] + @tensor s2 = t[a, b, b, a] + @tensor t3[a, b] := t[a, c, c, b] + @tensor s3 = t3[a, a] else t = rand(ComplexF64, V1 ⊗ V2 ← V1 ⊗ V2) # avoid permutes - s = @constinferred tr(t) - ss = s + ss = @constinferred tr(t) + @test conj(ss) ≈ tr(t') @planar s2 = t[a b; a b] @planar t3[a; b] := t[a c; b c] @planar s3 = t3[a; a] end - @test conj(s) ≈ tr(t') @test ss ≈ s2 @test ss ≈ s3 end From f53b0ef54a644206b409bbb990bb5bec154b7589 Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Fri, 14 Nov 2025 11:43:23 +0100 Subject: [PATCH 77/95] return error for `fusiontrees` without coupled sector for `GenericUnit` --- src/fusiontrees/iterator.jl | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/fusiontrees/iterator.jl b/src/fusiontrees/iterator.jl index 50f0781f6..341929420 100644 --- a/src/fusiontrees/iterator.jl +++ b/src/fusiontrees/iterator.jl @@ -17,6 +17,9 @@ function fusiontrees(uncoupled::Tuple{Vararg{I}}, coupled::I) where {I <: Sector return fusiontrees(uncoupled, coupled, isdual) end function fusiontrees(uncoupled::Tuple{I, Vararg{I}}) where {I <: Sector} + UnitStyle(I) isa GenericUnit && throw( + ArgumentError("Must specify coupled sector when UnitStyle is GenericUnit.") + ) coupled = unit(I) isdual = ntuple(n -> false, length(uncoupled)) return fusiontrees(uncoupled, coupled, isdual) From dbea0018c35bb91235ab8b68dcefd09e0c0cd3ed Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Fri, 14 Nov 2025 11:44:02 +0100 Subject: [PATCH 78/95] get double fusion tree tests working for `GenericUnit` --- test/symmetries/fusiontrees.jl | 313 +++++++++++++++++---------------- 1 file changed, 163 insertions(+), 150 deletions(-) diff --git a/test/symmetries/fusiontrees.jl b/test/symmetries/fusiontrees.jl index 1dc6be9d4..61e14c17c 100644 --- a/test/symmetries/fusiontrees.jl +++ b/test/symmetries/fusiontrees.jl @@ -403,13 +403,12 @@ using .TestSetup end end - if isa(UnitStyle(I), SimpleUnit) # could get these to work for GenericUnit, but somewhat hardcoded - if I <: ProductSector - N = 3 - else - N = 4 - end - + if I <: ProductSector + N = 3 + else + N = 4 + end + if isa(UnitStyle(I), SimpleUnit) out = random_fusion(I, N) numtrees = count(n -> true, fusiontrees((out..., map(dual, out)...))) while !(0 < numtrees < 100) @@ -419,112 +418,87 @@ using .TestSetup 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)))) - @testset "Double fusion tree $Istr: 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 - else - @test isapprox(coeff2, 0; atol = 1.0e-12, rtol = 1.0e-12) - end + else + out = random_fusion(I, N) + out2 = random_fusion(I, N) + tp = ⊗(out...) + tp2 = ⊗(out2...) + while isempty(intersect(tp, tp2)) # guarantee fusion to same coloring + out2 = random_fusion(I, N) + tp2 = ⊗(out2...) + end + @test_throws ArgumentError fusiontrees((out..., map(dual, out)...)) + incoming = rand(collect(intersect(tp, tp2))) + f1 = rand(collect(fusiontrees(out, incoming, ntuple(n -> rand(Bool), N)))) + f2 = rand(collect(fusiontrees(out2, incoming, ntuple(n -> rand(Bool), N)))) # no permuting + end + + @testset "Double fusion tree $Istr: 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 - if (BraidingStyle(I) isa Bosonic) && hasfusiontensor(I) - Af1 = convert(Array, f1) - Af2 = permutedims(convert(Array, 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)]...) - ) - A2 = zero(A) - for ((f1′, f2′), coeff) in d - Af1′ = convert(Array, f1′) - Af2′ = permutedims(convert(Array, 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)]...) - ) - end - @test A ≈ A2 + end + for ((f1′, f2′), coeff2) in d2 + if f1 == f1′ && f2 == f2′ + @test coeff2 ≈ 1 + else + @test isapprox(coeff2, 0; atol = 1.0e-12, rtol = 1.0e-12) end end - end - @testset "Double fusion tree $Istr: permutation" begin - if BraidingStyle(I) isa SymmetricBraiding - for n in 0:(2N) - p = (randperm(2 * N)...,) - p1, p2 = p[1:n], p[(n + 1):(2N)] - ip = invperm(p) - ip1, ip2 = ip[1:N], ip[(N + 1):(2N)] - - d = @constinferred TK.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′ = TK.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 - else - @test abs(coeff2) < 1.0e-12 - end - end - - if (BraidingStyle(I) isa Bosonic) && hasfusiontensor(I) - A = convert(Array, (f1, f2)) - Ap = permutedims(A, (p1..., p2...)) - A2 = zero(Ap) - for ((f1′, f2′), coeff) in d - A2 .+= coeff .* convert(Array, (f1′, f2′)) - end - @test Ap ≈ A2 - end + if (BraidingStyle(I) isa Bosonic) && hasfusiontensor(I) + Af1 = convert(Array, f1) + Af2 = permutedims(convert(Array, 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)]...) + ) + A2 = zero(A) + for ((f1′, f2′), coeff) in d + Af1′ = convert(Array, f1′) + Af2′ = permutedims(convert(Array, 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)]...) + ) end + @test A ≈ A2 end end - @testset "Double fusion tree $Istr: transposition" begin + end + @testset "Double fusion tree $Istr: permutation" begin + if BraidingStyle(I) isa SymmetricBraiding for n in 0:(2N) - i0 = rand(1:(2N)) - p = mod1.(i0 .+ (1:(2N)), 2N) - ip = mod1.(-i0 .+ (1:(2N)), 2N) - p′ = tuple(getindex.(Ref(vcat(1:N, (2N):-1:(N + 1))), p)...) - p1, p2 = p′[1:n], p′[(2N):-1:(n + 1)] - ip′ = tuple(getindex.(Ref(vcat(1:n, (2N):-1:(n + 1))), ip)...) - ip1, ip2 = ip′[1:N], ip′[(2N):-1:(N + 1)] - - d = @constinferred transpose(f1, f2, p1, p2) + p = (randperm(2 * N)...,) + p1, p2 = p[1:n], p[(n + 1):(2N)] + ip = invperm(p) + ip1, ip2 = ip[1:N], ip[(N + 1):(2N)] + + d = @constinferred TK.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′ = transpose(f1′, f2′, ip1, ip2) + d′ = TK.permute(f1′, f2′, ip1, ip2) for ((f1′′, f2′′), coeff2) in d′ - d2[(f1′′, f2′′)] = get(d2, (f1′′, f2′′), zero(coeff)) + coeff2 * coeff + d2[(f1′′, f2′′)] = get(d2, (f1′′, f2′′), zero(coeff)) + + coeff2 * coeff end end for ((f1′, f2′), coeff2) in d2 @@ -535,69 +509,108 @@ using .TestSetup end end - if BraidingStyle(I) isa Bosonic - d3 = permute(f1, f2, p1, p2) - for (f1′, f2′) in union(keys(d), keys(d3)) - coeff1 = get(d, (f1′, f2′), zero(valtype(d))) - coeff3 = get(d3, (f1′, f2′), zero(valtype(d3))) - @test isapprox(coeff1, coeff3; atol = 1.0e-12) - end - end - if (BraidingStyle(I) isa Bosonic) && hasfusiontensor(I) - Af1 = convert(Array, f1) - Af2 = convert(Array, 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)]...) - ) + A = convert(Array, (f1, f2)) Ap = permutedims(A, (p1..., p2...)) A2 = zero(Ap) for ((f1′, f2′), coeff) in d - Af1′ = convert(Array, f1′) - Af2′ = convert(Array, 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)]...) - ) + A2 .+= coeff .* convert(Array, (f1′, f2′)) end @test Ap ≈ A2 end end end - @testset "Double fusion tree $Istr: planar trace" begin - d1 = transpose(f1, f1, (N + 1, 1:N..., ((2N):-1:(N + 3))...), (N + 2,)) - f1front, = TK.split(f1, N - 1) - T = sectorscalartype(I) - d2 = Dict{typeof((f1front, f1front)), T}() - for ((f1′, f2′), coeff′) in d1 - for ((f1′′, f2′′), coeff′′) in - TK.planar_trace( - f1′, f2′, (2:N...,), (1, ((2N):-1:(N + 3))...), (N + 1,), - (N + 2,) - ) - coeff = coeff′ * coeff′′ - d2[(f1′′, f2′′)] = get(d2, (f1′′, f2′′), zero(coeff)) + coeff + end + @testset "Double fusion tree $Istr: transposition" begin + for n in 0:(2N) + i0 = rand(1:(2N)) + p = mod1.(i0 .+ (1:(2N)), 2N) + ip = mod1.(-i0 .+ (1:(2N)), 2N) + p′ = tuple(getindex.(Ref(vcat(1:N, (2N):-1:(N + 1))), p)...) + p1, p2 = p′[1:n], p′[(2N):-1:(n + 1)] + ip′ = tuple(getindex.(Ref(vcat(1:n, (2N):-1:(n + 1))), ip)...) + ip1, ip2 = ip′[1:N], ip′[(2N):-1:(N + 1)] + + d = @constinferred transpose(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′ = transpose(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_), coeff) in d2 - if (f1_, f2_) == (f1front, f1front) - @test coeff ≈ dim(f1.coupled) / dim(f1front.coupled) + for ((f1′, f2′), coeff2) in d2 + if f1 == f1′ && f2 == f2′ + @test coeff2 ≈ 1 else - @test abs(coeff) < 1.0e-12 + @test abs(coeff2) < 1.0e-12 end end + + if BraidingStyle(I) isa Bosonic + d3 = permute(f1, f2, p1, p2) + for (f1′, f2′) in union(keys(d), keys(d3)) + coeff1 = get(d, (f1′, f2′), zero(valtype(d))) + coeff3 = get(d3, (f1′, f2′), zero(valtype(d3))) + @test isapprox(coeff1, coeff3; atol = 1.0e-12) + end + end + + if (BraidingStyle(I) isa Bosonic) && hasfusiontensor(I) + Af1 = convert(Array, f1) + Af2 = convert(Array, 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(Array, f1′) + Af2′ = convert(Array, 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)]...) + ) + end + @test Ap ≈ A2 + end + end + end + @testset "Double fusion tree $Istr: planar trace" begin + d1 = transpose(f1, f1, (N + 1, 1:N..., ((2N):-1:(N + 3))...), (N + 2,)) + f1front, = TK.split(f1, N - 1) + T = sectorscalartype(I) + d2 = Dict{typeof((f1front, f1front)), T}() + for ((f1′, f2′), coeff′) in d1 + for ((f1′′, f2′′), coeff′′) in + TK.planar_trace( + f1′, f2′, (2:N...,), (1, ((2N):-1:(N + 3))...), (N + 1,), + (N + 2,) + ) + coeff = coeff′ * coeff′′ + d2[(f1′′, f2′′)] = get(d2, (f1′′, f2′′), zero(coeff)) + coeff + end + end + for ((f1_, f2_), coeff) in d2 + if (f1_, f2_) == (f1front, f1front) + @test coeff ≈ dim(f1.coupled) / dim(f1front.coupled) + else + @test abs(coeff) < 1.0e-12 + end end end TK.empty_globalcaches!() From 4fceb033f2a6ffdcee457b09286d599c5daf32e0 Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Sun, 16 Nov 2025 16:16:57 +0100 Subject: [PATCH 79/95] suggested source changes --- src/TensorKit.jl | 1 - src/spaces/homspace.jl | 2 +- src/spaces/productspace.jl | 7 ++----- src/spaces/vectorspaces.jl | 4 ++-- 4 files changed, 5 insertions(+), 9 deletions(-) diff --git a/src/TensorKit.jl b/src/TensorKit.jl index 52a84ba27..fa6af01a5 100644 --- a/src/TensorKit.jl +++ b/src/TensorKit.jl @@ -31,7 +31,6 @@ export DiagonalTensorMap, BraidingTensor export TruncationScheme export SpaceMismatch, SectorMismatch, IndexError # error types -# general vector space methods # Export general vector space methods export space, field, dual, dim, reduceddim, dims, fuse, flip, isdual export unitspace, zerospace, oplus, ominus diff --git a/src/spaces/homspace.jl b/src/spaces/homspace.jl index f231e1278..46884fc91 100644 --- a/src/spaces/homspace.jl +++ b/src/spaces/homspace.jl @@ -96,7 +96,7 @@ function blocksectors(W::HomSpace) N₁ = length(codom) N₂ = length(dom) I = sectortype(W) - if N₁ == 0 && N₂ == 0 + if N₁ == N₂ == 0 return allunits(I) elseif N₁ == 0 return filter!(isunit, collect(blocksectors(dom))) # module space cannot end in empty space diff --git a/src/spaces/productspace.jl b/src/spaces/productspace.jl index 68394d3d1..cdfe198a3 100644 --- a/src/spaces/productspace.jl +++ b/src/spaces/productspace.jl @@ -151,10 +151,7 @@ function blocksectors(P::ProductSpace{S, N}) where {S, N} end bs = Vector{I}() if N == 0 - for u in allunits(I) - push!(bs, u) - end - return bs + return collect(allunits(I)) elseif N == 1 for s in sectors(P) push!(bs, first(s)) @@ -199,7 +196,7 @@ hasblock(P::ProductSpace, c::Sector) = !isempty(fusiontrees(P, c)) blockdim(P::ProductSpace, c::Sector) Return the total dimension of a coupled sector `c` in the product space, by summing over -all `dim(P, s)` for all tuples of sectors `s::NTuple{N, <: Sector}` that can fuse to `c`, +all `dim(P, s)` for all tuples of sectors `s::NTuple{N, Sector}` that can fuse to `c`, counted with the correct multiplicity (i.e. number of ways in which `s` can fuse to `c`). See also [`hasblock`](@ref) and [`blocksectors`](@ref). diff --git a/src/spaces/vectorspaces.jl b/src/spaces/vectorspaces.jl index 162ea054c..0745873a6 100644 --- a/src/spaces/vectorspaces.jl +++ b/src/spaces/vectorspaces.jl @@ -146,9 +146,9 @@ Base.zero(::Type{V}) where {V <: ElementarySpace} = zerospace(V) """ leftunitspace(V::S) where {S <: ElementarySpace} -> S -Return the corresponding vector space of type `ElementarySpace` that represents the trivial +Return the corresponding vector space of type `S` that represents the trivial one-dimensional space, i.e. the space that is isomorphic to the corresponding field. For vector spaces -of type `GradedSpace{I}`, this corresponds to the unique left unit of the objects in `Sector` `I` present +of type `GradedSpace{I}`, this one-dimensional space contains the unique left unit of the objects in `Sector` `I` present in the vector space. """ function leftunitspace(V::ElementarySpace) From 9e90f78fe6716d7579cd866dea9a9660b0e9b1cd Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Sun, 16 Nov 2025 16:18:39 +0100 Subject: [PATCH 80/95] factorisations test changes --- test/tensors/factorizations.jl | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/test/tensors/factorizations.jl b/test/tensors/factorizations.jl index bba2153a2..6705d831c 100644 --- a/test/tensors/factorizations.jl +++ b/test/tensors/factorizations.jl @@ -239,8 +239,7 @@ for V in spacelist @test isisometry(Vᴴ; side = :right) # dimension of S is a float for IsingBimodule - nvals = dim(domain(S)) ÷ 2 - eltype(nvals) <: AbstractFloat && (nvals = Int(nvals)) + nvals = round(Int, dim(domain(S)) / 2) trunc = truncrank(nvals) U1, S1, Vᴴ1 = @constinferred svd_trunc(t; trunc) @test t * Vᴴ1' ≈ U1 * S1 @@ -303,9 +302,7 @@ for V in spacelist @test @constinferred isposdef(vdv) t isa DiagonalTensorMap || @test !isposdef(t) # unlikely for non-hermitian map - nvals = dim(domain(t)) ÷ 2 - eltype(nvals) <: AbstractFloat && (nvals = Int(nvals)) - d, v = @constinferred eig_trunc(t; trunc = truncrank(nvals)) + nvals = round(Int, dim(domain(t)) / 2) @test t * v ≈ v * d @test dim(domain(d)) ≤ nvals From 14698c4f286fa469157f995f2578318bfb2d8ecc Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Sun, 16 Nov 2025 16:24:16 +0100 Subject: [PATCH 81/95] let full and partial trace tests run for every sector --- test/tensors/tensors.jl | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/test/tensors/tensors.jl b/test/tensors/tensors.jl index 53868d55f..f2a803a22 100644 --- a/test/tensors/tensors.jl +++ b/test/tensors/tensors.jl @@ -293,14 +293,15 @@ for V in spacelist @tensor s2 = t[a, b, b, a] @tensor t3[a, b] := t[a, c, c, b] @tensor s3 = t3[a, a] - else - t = rand(ComplexF64, V1 ⊗ V2 ← V1 ⊗ V2) # avoid permutes - ss = @constinferred tr(t) - @test conj(ss) ≈ tr(t') - @planar s2 = t[a b; a b] - @planar t3[a; b] := t[a c; b c] - @planar s3 = t3[a; a] + @test ss ≈ s2 + @test ss ≈ s3 end + t = rand(ComplexF64, V1 ⊗ V2 ← V1 ⊗ V2) # avoid permutes + ss = @constinferred tr(t) + @test conj(ss) ≈ tr(t') + @planar s2 = t[a b; a b] + @planar t3[a; b] := t[a c; b c] + @planar s3 = t3[a; a] @test ss ≈ s2 @test ss ≈ s3 @@ -311,12 +312,12 @@ for V in spacelist @tensor t2[a; b] := t[c d b; c d a] @tensor t4[a b; c d] := t[e d c; e b a] @tensor t5[a; b] := t4[a c; b c] - else - t = rand(ComplexF64, V3 ⊗ V4 ⊗ V5 ← V3 ⊗ V4 ⊗ V5) # compatible with module fusion - @planar t2[a; b] := t[c a d; c b d] - @planar t4[a b; c d] := t[e a b; e c d] - @planar t5[a; b] := t4[a c; b c] + @test t2 ≈ t5 end + t = rand(ComplexF64, V3 ⊗ V4 ⊗ V5 ← V3 ⊗ V4 ⊗ V5) # compatible with module fusion + @planar t2[a; b] := t[c a d; c b d] + @planar t4[a b; c d] := t[e a b; e c d] + @planar t5[a; b] := t4[a c; b c] @test t2 ≈ t5 end if BraidingStyle(I) isa Bosonic && hasfusiontensor(I) From 6aaa460b873c37784555be28abada3fd7077b91f Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Sun, 16 Nov 2025 16:50:13 +0100 Subject: [PATCH 82/95] accidently removed part of test --- test/tensors/factorizations.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/test/tensors/factorizations.jl b/test/tensors/factorizations.jl index 6705d831c..92a29672a 100644 --- a/test/tensors/factorizations.jl +++ b/test/tensors/factorizations.jl @@ -303,6 +303,7 @@ for V in spacelist t isa DiagonalTensorMap || @test !isposdef(t) # unlikely for non-hermitian map nvals = round(Int, dim(domain(t)) / 2) + d, v = @constinferred eig_trunc(t; trunc = truncrank(nvals)) @test t * v ≈ v * d @test dim(domain(d)) ≤ nvals From f125a4a4e4b5917b59b6180f90845230cf4d8709 Mon Sep 17 00:00:00 2001 From: Boris De Vos <143942306+borisdevos@users.noreply.github.com> Date: Sun, 16 Nov 2025 21:28:04 +0100 Subject: [PATCH 83/95] Update test/tensors/factorizations.jl Co-authored-by: Lukas Devos --- test/tensors/factorizations.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/tensors/factorizations.jl b/test/tensors/factorizations.jl index 92a29672a..024961433 100644 --- a/test/tensors/factorizations.jl +++ b/test/tensors/factorizations.jl @@ -352,7 +352,7 @@ for V in spacelist d1, d2 = dim(codomain(t)), dim(domain(t)) r = rank(t) @test r == min(d1, d2) - @test eltype(r) == eltype(d1) + @test typeof(r) == typeof(d1) M = left_null(t) @test @constinferred(rank(M)) + r ≈ d1 Mᴴ = right_null(t) From f548497a88667766dbbb049e0b28fea154e9b843 Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Wed, 26 Nov 2025 09:27:19 +0100 Subject: [PATCH 84/95] code suggestions --- src/spaces/productspace.jl | 2 +- src/spaces/vectorspaces.jl | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/spaces/productspace.jl b/src/spaces/productspace.jl index 51ebe275f..4b75ce737 100644 --- a/src/spaces/productspace.jl +++ b/src/spaces/productspace.jl @@ -151,7 +151,7 @@ function blocksectors(P::ProductSpace{S, N}) where {S, N} end bs = Vector{I}() if N == 0 - return collect(allunits(I)) + append!(bs, allunits(I)) elseif N == 1 for s in sectors(P) push!(bs, first(s)) diff --git a/src/spaces/vectorspaces.jl b/src/spaces/vectorspaces.jl index e08154eef..d6d2a8372 100644 --- a/src/spaces/vectorspaces.jl +++ b/src/spaces/vectorspaces.jl @@ -134,8 +134,9 @@ Always returns `false` for spaces where `V == conj(V)`, i.e. vector spaces over Return the corresponding vector space of type `S` that represents the trivial one-dimensional space, i.e. the space that is isomorphic to the corresponding field. -For vector spaces of type `GradedSpace{I}` where `Sector` `I` has a semi-simple unit -structure, this returns a multi-dimensional space corresponding to all unit sectors in `I`. +For vector spaces where `I = sectortype(S)` has a semi-simple unit structure +(`UnitStyle(I) == GenericUnit()`), this returns a multi-dimensional space corresponding to all unit sectors: +`dim(unitspace(V), s) == 1` for all `s in allunits(I)`. !!! note `unitspace(V)`is different from `one(V)`. The latter returns the empty product space @@ -169,7 +170,7 @@ function leftunitspace(V::ElementarySpace) if UnitStyle(I) isa SimpleUnit return unitspace(typeof(V)) else - !isempty(sectors(V)) || throw(ArgumentError("Cannot determine type of empty space")) + !isempty(sectors(V)) || throw(ArgumentError("Cannot determine the left unit of an empty space")) _allequal(leftunit, sectors(V)) || throw(ArgumentError("sectors of $V do not have the same left unit")) @@ -191,7 +192,7 @@ function rightunitspace(V::ElementarySpace) if UnitStyle(I) isa SimpleUnit return unitspace(typeof(V)) else - !isempty(sectors(V)) || throw(ArgumentError("Cannot determine type of empty space")) + !isempty(sectors(V)) || throw(ArgumentError("Cannot determine the right unit of an empty space")) _allequal(rightunit, sectors(V)) || throw(ArgumentError("sectors of $V do not have the same right unit")) From c009bcc5be66336570f3d0a2a7cfecc243222bd7 Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Wed, 26 Nov 2025 15:24:40 +0100 Subject: [PATCH 85/95] get rid of try-catch block --- src/spaces/vectorspaces.jl | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/src/spaces/vectorspaces.jl b/src/spaces/vectorspaces.jl index d6d2a8372..5ef171c31 100644 --- a/src/spaces/vectorspaces.jl +++ b/src/spaces/vectorspaces.jl @@ -214,17 +214,8 @@ function isunitspace(V::ElementarySpace) return if isa(UnitStyle(I), SimpleUnit) isisomorphic(V, unitspace(V)) else - try - isisomorphic(V, unitspace(V)) || - isisomorphic(V, leftunitspace(V)) || - isisomorphic(V, rightunitspace(V)) - catch e - if isa(e, ArgumentError) - return false - else - rethrow(e) - end - end + (dim(V) == 0 || !all(isunit, sectors(V))) && return false + return true end end From c9f8010b78b498e2737844d880d2c3d6fcd133a4 Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Thu, 27 Nov 2025 09:58:12 +0100 Subject: [PATCH 86/95] code suggestions --- src/fusiontrees/fusiontrees.jl | 2 +- test/setup.jl | 20 ++++++----------- test/symmetries/fusiontrees.jl | 41 +++++++++++++++++----------------- 3 files changed, 28 insertions(+), 35 deletions(-) diff --git a/src/fusiontrees/fusiontrees.jl b/src/fusiontrees/fusiontrees.jl index 1665cdb27..0618590a6 100644 --- a/src/fusiontrees/fusiontrees.jl +++ b/src/fusiontrees/fusiontrees.jl @@ -92,7 +92,7 @@ function FusionTree{I}( uncoupled::NTuple{N}, coupled = unit(I), isdual = ntuple(Returns(false), N) ) where {I <: Sector, N} FusionStyle(I) isa UniqueFusion || - error("fusion tree requires inner lines if `FusionStyle(I) <: MultipleFusion`") + throw(ArgumentError("fusion tree requires inner lines if `FusionStyle(I) <: MultipleFusion`")) return FusionTree{I}( map(s -> convert(I, s), uncoupled), convert(I, coupled), isdual, _abelianinner(map(s -> convert(I, s), (uncoupled..., dual(coupled)))) diff --git a/test/setup.jl b/test/setup.jl index 4283ebf4e..90bc54d54 100644 --- a/test/setup.jl +++ b/test/setup.jl @@ -76,20 +76,14 @@ function force_planar(tsrc::TensorMap{<:Any, <:GradedSpace}) return tdst end -function random_fusion(I::Type{<:Sector}, N::Int) # for fusion tree tests - t = Vector{I}(undef, N) - t[1] = randsector(I) - len = 1 - - while len < N - r = randsector(I) - f = ⊗(t[len], r) - if !isempty(f) - len += 1 - t[len] = r - end +function random_fusion(I::Type{<:Sector}, ::Val{N}) where {N} # for fusion tree tests + N == 1 && return (randsector(I),) + tail = random_fusion(I, Val(N-1)) + s = randsector(I) + while isempty(⊗(s, first(tail))) + s = randsector(I) end - return tuple(t...) + return (s, tail...) end sectorlist = ( diff --git a/test/symmetries/fusiontrees.jl b/test/symmetries/fusiontrees.jl index 61e14c17c..302488744 100644 --- a/test/symmetries/fusiontrees.jl +++ b/test/symmetries/fusiontrees.jl @@ -13,7 +13,7 @@ using .TestSetup @timedtestset "Fusion trees for $(TensorKit.type_repr(I))" verbose = true for I in sectorlist Istr = TensorKit.type_repr(I) N = 5 - out = random_fusion(I, N) + out = random_fusion(I, Val(N)) isdual = ntuple(n -> rand(Bool), N) in = rand(collect(⊗(out...))) numtrees = length(fusiontrees(out, in, isdual)) @@ -63,31 +63,30 @@ using .TestSetup @constinferred FusionTree((u, u, u), u) if isa(UnitStyle(I), SimpleUnit) @constinferred FusionTree((u, u, u, u)) + else + @test_throws ArgumentError FusionTree((u, u, u, u)) end @test_throws MethodError FusionTree((u, u), u, (false, false, false)) else - errstr = "fusion tree requires inner lines if `FusionStyle(I) <: MultipleFusion`" - @test_throws errstr FusionTree((), u, ()) - @test_throws errstr FusionTree((u,), u, (false,)) - @test_throws errstr FusionTree((u, u), u, (false, false)) - @test_throws errstr FusionTree((u, u, u), u) - if isa(UnitStyle(I), SimpleUnit) - @test_throws errstr FusionTree((u, u, u, u)) - end + @test_throws ArgumentError FusionTree((), u, ()) + @test_throws ArgumentError FusionTree((u,), u, (false,)) + @test_throws ArgumentError FusionTree((u, u), u, (false, false)) + @test_throws ArgumentError FusionTree((u, u, u), u) + @test_throws ArgumentError FusionTree((u, u, u, u)) end end end @testset "Fusion tree $Istr: insertat" begin N = 4 - out2 = random_fusion(I, N) + out2 = random_fusion(I, Val(N)) in2 = rand(collect(⊗(out2...))) isdual2 = ntuple(n -> rand(Bool), N) f2 = rand(collect(fusiontrees(out2, in2, isdual2))) for i in 1:N - out1 = random_fusion(I, N) # guaranteed good fusion + out1 = random_fusion(I, Val(N)) # guaranteed good fusion out1 = Base.setindex(out1, in2, i) # can lead to poor fusion while isempty(⊗(out1...)) - out1 = random_fusion(I, N) + out1 = random_fusion(I, Val(N)) out1 = Base.setindex(out1, in2, i) end in1 = rand(collect(⊗(out1...))) @@ -325,14 +324,14 @@ using .TestSetup @testset "Fusion tree $Istr: merging" begin N = 3 - out1 = random_fusion(I, N) - out2 = random_fusion(I, N) + out1 = random_fusion(I, Val(N)) + out2 = random_fusion(I, Val(N)) in1 = rand(collect(⊗(out1...))) in2 = rand(collect(⊗(out2...))) tp = ⊗(in1, in2) # messy solution but it works while isempty(tp) - out1 = random_fusion(I, N) - out2 = random_fusion(I, N) + out1 = random_fusion(I, Val(N)) + out2 = random_fusion(I, Val(N)) in1 = rand(collect(⊗(out1...))) in2 = rand(collect(⊗(out2...))) tp = ⊗(in1, in2) @@ -409,22 +408,22 @@ using .TestSetup N = 4 end if isa(UnitStyle(I), SimpleUnit) - out = random_fusion(I, N) + out = random_fusion(I, Val(N)) numtrees = count(n -> true, fusiontrees((out..., map(dual, out)...))) while !(0 < numtrees < 100) - out = random_fusion(I, N) + out = random_fusion(I, Val(N)) 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)))) else - out = random_fusion(I, N) - out2 = random_fusion(I, N) + out = random_fusion(I, Val(N)) + out2 = random_fusion(I, Val(N)) tp = ⊗(out...) tp2 = ⊗(out2...) while isempty(intersect(tp, tp2)) # guarantee fusion to same coloring - out2 = random_fusion(I, N) + out2 = random_fusion(I, Val(N)) tp2 = ⊗(out2...) end @test_throws ArgumentError fusiontrees((out..., map(dual, out)...)) From faf683737c92760811cda2ad6c81400ef375217f Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Thu, 27 Nov 2025 10:01:22 +0100 Subject: [PATCH 87/95] format --- test/setup.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/setup.jl b/test/setup.jl index 90bc54d54..45b6b68e8 100644 --- a/test/setup.jl +++ b/test/setup.jl @@ -78,7 +78,7 @@ end function random_fusion(I::Type{<:Sector}, ::Val{N}) where {N} # for fusion tree tests N == 1 && return (randsector(I),) - tail = random_fusion(I, Val(N-1)) + tail = random_fusion(I, Val(N - 1)) s = randsector(I) while isempty(⊗(s, first(tail))) s = randsector(I) From 7917adcdb87c0cfa234927e2554f451268ba4c02 Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Thu, 27 Nov 2025 14:12:17 +0100 Subject: [PATCH 88/95] deal with different error type thrown --- test/symmetries/fusiontrees.jl | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/test/symmetries/fusiontrees.jl b/test/symmetries/fusiontrees.jl index 302488744..16f6cfa07 100644 --- a/test/symmetries/fusiontrees.jl +++ b/test/symmetries/fusiontrees.jl @@ -72,7 +72,11 @@ using .TestSetup @test_throws ArgumentError FusionTree((u,), u, (false,)) @test_throws ArgumentError FusionTree((u, u), u, (false, false)) @test_throws ArgumentError FusionTree((u, u, u), u) - @test_throws ArgumentError FusionTree((u, u, u, u)) + if I <: ProductSector && isa(UnitStyle(I), GenericUnit) + @test_throws DomainError FusionTree((u, u, u, u)) + else + @test_throws ArgumentError FusionTree((u, u, u, u)) + end end end end From baa5991f99fb4bf2df53bf18c01e44b5335c520b Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Mon, 1 Dec 2025 11:45:55 +0100 Subject: [PATCH 89/95] make spaces compatible in isometric projections --- test/tensors/factorizations.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/tensors/factorizations.jl b/test/tensors/factorizations.jl index e4f5b09a8..a46c3c252 100644 --- a/test/tensors/factorizations.jl +++ b/test/tensors/factorizations.jl @@ -432,7 +432,7 @@ for V in spacelist for T in eltypes, t in ( randn(T, W, W), randn(T, W, W)', - randn(T, W, V1), randn(T, V1, W)', + randn(T, W, V4), randn(T, V4, W)', ) t2 = project_isometric(t) @test isisometric(t2) From 7a1698705e293b123e35ebd436f1169df4238d27 Mon Sep 17 00:00:00 2001 From: Boris De Vos <143942306+borisdevos@users.noreply.github.com> Date: Mon, 1 Dec 2025 17:53:25 +0100 Subject: [PATCH 90/95] Update test/symmetries/spaces.jl Co-authored-by: Jutho --- test/symmetries/spaces.jl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/symmetries/spaces.jl b/test/symmetries/spaces.jl index b6291a33e..c05b66d41 100644 --- a/test/symmetries/spaces.jl +++ b/test/symmetries/spaces.jl @@ -226,8 +226,9 @@ end @test dim(@constinferred(zerospace(V))) == 0 # space with unit(s), always test as if multifusion W = @constinferred GradedSpace(unit => 1 for unit in allunits(I)) - dict = Dict((unit => 1 for unit in allunits(I))...) + dict = Dict(unit => 1 for unit in allunits(I)) @test W == GradedSpace(dict) + @test W == GradedSpace(push!(dict, randsector(I) => 0)) @test @constinferred(zerospace(V)) == GradedSpace(unit => 0 for unit in allunits(I)) randunit = rand(collect(allunits(I))) @test_throws ArgumentError("Sector $(randunit) appears multiple times") GradedSpace(randunit => 1, randunit => 3) From cbf541bb7c564572e5aa796875f0406fc77d011a Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Tue, 2 Dec 2025 08:55:10 +0100 Subject: [PATCH 91/95] help `random_fusion` in while loop --- test/setup.jl | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test/setup.jl b/test/setup.jl index 45b6b68e8..719f3ad36 100644 --- a/test/setup.jl +++ b/test/setup.jl @@ -80,8 +80,10 @@ function random_fusion(I::Type{<:Sector}, ::Val{N}) where {N} # for fusion tree N == 1 && return (randsector(I),) tail = random_fusion(I, Val(N - 1)) s = randsector(I) - while isempty(⊗(s, first(tail))) - s = randsector(I) + counter = 0 + while isempty(⊗(s, first(tail))) && counter < 20 + counter += 1 + s = (counter < 20) ? randsector(I) : leftunit(first(tail)) end return (s, tail...) end From 1f2cf55f87cb42c99625c79f14b8704dac8740cf Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Wed, 3 Dec 2025 09:49:17 +0100 Subject: [PATCH 92/95] `isa`'s, CI friendliness, braiding conditions and todos --- test/symmetries/fusiontrees.jl | 16 ++++++++-------- test/symmetries/spaces.jl | 2 +- test/tensors/factorizations.jl | 4 ++-- test/tensors/tensors.jl | 18 +++++++++--------- 4 files changed, 20 insertions(+), 20 deletions(-) diff --git a/test/symmetries/fusiontrees.jl b/test/symmetries/fusiontrees.jl index 16f6cfa07..e5dbf8132 100644 --- a/test/symmetries/fusiontrees.jl +++ b/test/symmetries/fusiontrees.jl @@ -61,7 +61,7 @@ using .TestSetup @constinferred FusionTree((u,), u, (false,)) @constinferred FusionTree((u, u), u, (false, false)) @constinferred FusionTree((u, u, u), u) - if isa(UnitStyle(I), SimpleUnit) + if UnitStyle(I) isa SimpleUnit @constinferred FusionTree((u, u, u, u)) else @test_throws ArgumentError FusionTree((u, u, u, u)) @@ -72,7 +72,7 @@ using .TestSetup @test_throws ArgumentError FusionTree((u,), u, (false,)) @test_throws ArgumentError FusionTree((u, u), u, (false, false)) @test_throws ArgumentError FusionTree((u, u, u), u) - if I <: ProductSector && isa(UnitStyle(I), GenericUnit) + if I <: ProductSector && UnitStyle(I) isa GenericUnit @test_throws DomainError FusionTree((u, u, u, u)) else @test_throws ArgumentError FusionTree((u, u, u, u)) @@ -89,7 +89,7 @@ using .TestSetup for i in 1:N out1 = random_fusion(I, Val(N)) # guaranteed good fusion out1 = Base.setindex(out1, in2, i) # can lead to poor fusion - while isempty(⊗(out1...)) + while isempty(⊗(out1...)) # TODO: better way to do this? out1 = random_fusion(I, Val(N)) out1 = Base.setindex(out1, in2, i) end @@ -105,7 +105,7 @@ using .TestSetup @test length(TK.insertat(f1b, 1, f1a)) == 1 @test first(TK.insertat(f1b, 1, f1a)) == (f1 => 1) - if isa(UnitStyle(I), SimpleUnit) + if UnitStyle(I) isa SimpleUnit levels = ntuple(identity, N) function _reinsert_partial_tree(t, f) (t′, c′) = first(TK.insertat(t, 1, f)) @@ -237,7 +237,7 @@ using .TestSetup end end end - (UnitStyle(I) == SimpleUnit) && @testset "Fusion tree $Istr: elementary artin braid" begin + (BraidingStyle(I) isa HasBraiding) && @testset "Fusion tree $Istr: elementary artin braid" begin N = length(out) isdual = ntuple(n -> rand(Bool), N) for in in ⊗(out...) @@ -293,7 +293,7 @@ using .TestSetup end end end - (UnitStyle(I) == SimpleUnit) && @testset "Fusion tree $Istr: braiding and permuting" begin + (BraidingStyle(I) isa HasBraiding) && @testset "Fusion tree $Istr: braiding and permuting" begin f = rand(collect(it)) p = tuple(randperm(N)...) ip = invperm(p) @@ -355,7 +355,7 @@ using .TestSetup for (f, coeff) in TK.merge(f1, f2, c, μ) ) - if !isa(BraidingStyle(I), NoBraiding) + if BraidingStyle(I) isa HasBraiding for c in in1 ⊗ in2 R = Rsymbol(in1, in2, c) for μ in 1:Nsymbol(in1, in2, c) @@ -411,7 +411,7 @@ using .TestSetup else N = 4 end - if isa(UnitStyle(I), SimpleUnit) + if UnitStyle(I) isa SimpleUnit out = random_fusion(I, Val(N)) numtrees = count(n -> true, fusiontrees((out..., map(dual, out)...))) while !(0 < numtrees < 100) diff --git a/test/symmetries/spaces.jl b/test/symmetries/spaces.jl index c05b66d41..75e9fd0b2 100644 --- a/test/symmetries/spaces.jl +++ b/test/symmetries/spaces.jl @@ -235,7 +235,7 @@ end @test isunitspace(W) @test @constinferred(unitspace(V)) == W == unitspace(typeof(V)) - if isa(UnitStyle(I), SimpleUnit) + if UnitStyle(I) isa SimpleUnit @test @constinferred(leftunitspace(V)) == W == @constinferred(rightunitspace(V)) else @test_throws ArgumentError leftunitspace(V) diff --git a/test/tensors/factorizations.jl b/test/tensors/factorizations.jl index a46c3c252..b25a9dbbd 100644 --- a/test/tensors/factorizations.jl +++ b/test/tensors/factorizations.jl @@ -9,9 +9,9 @@ spacelist = try if ENV["CI"] == "true" println("Detected running on CI") if Sys.iswindows() - (Vtr, Vℤ₃, VU₁, VfU₁, VCU₁, VSU₂, VIB_diag, VIB_M) + (Vtr, Vℤ₃, VU₁, VfU₁, VCU₁, VSU₂, VIB_diag) elseif Sys.isapple() - (Vtr, Vℤ₃, VfU₁, VfSU₂, VIB_diag, VIB_M) + (Vtr, Vℤ₃, VfU₁, VfSU₂, VIB_M) else (Vtr, VU₁, VCU₁, VSU₂, VfSU₂, VIB_diag, VIB_M) end diff --git a/test/tensors/tensors.jl b/test/tensors/tensors.jl index 2fbb39573..43481e7d1 100644 --- a/test/tensors/tensors.jl +++ b/test/tensors/tensors.jl @@ -11,9 +11,9 @@ spacelist = try if get(ENV, "CI", "false") == "true" println("Detected running on CI") if Sys.iswindows() - (Vtr, Vℤ₂, Vfℤ₂, Vℤ₃, VU₁, VfU₁, VCU₁, VSU₂, VIB_diag, VIB_M) + (Vtr, Vℤ₂, Vfℤ₂, Vℤ₃, VU₁, VfU₁, VCU₁, VSU₂, VIB_diag) elseif Sys.isapple() - (Vtr, Vℤ₂, Vfℤ₂, Vℤ₃, VfU₁, VfSU₂, VSU₂U₁, VIB_diag, VIB_M) #, VSU₃) + (Vtr, Vℤ₂, Vfℤ₂, Vℤ₃, VfU₁, VfSU₂, VSU₂U₁, VIB_M) #, VSU₃) else (Vtr, Vℤ₂, Vfℤ₂, VU₁, VCU₁, VSU₂, VfSU₂, VSU₂U₁, VIB_diag, VIB_M) #, VSU₃) end @@ -27,7 +27,7 @@ end for V in spacelist I = sectortype(first(V)) Istr = type_repr(I) - symmetricbraiding = isa(BraidingStyle(I), SymmetricBraiding) + symmetricbraiding = BraidingStyle(I) isa SymmetricBraiding println("---------------------------------------") println("Tensors with symmetry: $Istr") println("---------------------------------------") @@ -137,7 +137,7 @@ for V in spacelist @test dot(t2, t) ≈ conj(dot(t2', t')) @test dot(t2, t) ≈ dot(t', t2') - if isa(UnitStyle(I), SimpleUnit) || !isempty(blocksectors(V2 ⊗ V1)) + if UnitStyle(I) isa SimpleUnit || !isempty(blocksectors(V2 ⊗ V1)) i1 = @constinferred(isomorphism(T, V1 ⊗ V2, V2 ⊗ V1)) # can't reverse fusion here when modules are involved i2 = @constinferred(isomorphism(Vector{T}, V2 ⊗ V1, V1 ⊗ V2)) @test i1 * i2 == @constinferred(id(T, V1 ⊗ V2)) @@ -329,7 +329,7 @@ for V in spacelist end end #TODO: find version that works for all multifusion cases - (UnitStyle(I) == SimpleUnit()) && @timedtestset "Trace and contraction" begin + symmetricbraiding && @timedtestset "Trace and contraction" begin t1 = rand(ComplexF64, V1 ⊗ V2 ⊗ V3) t2 = rand(ComplexF64, V2' ⊗ V4 ⊗ V1') t3 = t1 ⊗ t2 @@ -354,7 +354,7 @@ for V in spacelist @test HrA12array ≈ convert(Array, HrA12) end end - (BraidingStyle(I) != NoBraiding()) && @timedtestset "Index flipping: test flipping inverse" begin + (BraidingStyle(I) isa HasBraiding) && @timedtestset "Index flipping: test flipping inverse" begin t = rand(ComplexF64, V1 ⊗ V1' ← V1' ⊗ V1) for i in 1:4 @test t ≈ flip(flip(t, i), i; inv = true) @@ -533,7 +533,7 @@ for V in spacelist end @timedtestset "Tensor product: test via norm preservation" begin for T in (Float32, ComplexF64) - if isa(UnitStyle(I), SimpleUnit) || !isempty(blocksectors(V2 ⊗ V1)) + if UnitStyle(I) isa SimpleUnit || !isempty(blocksectors(V2 ⊗ V1)) t1 = rand(T, V2 ⊗ V3 ⊗ V1, V1 ⊗ V2) t2 = rand(T, V2 ⊗ V1 ⊗ V3, V1 ⊗ V1) else @@ -572,7 +572,7 @@ for V in spacelist end @timedtestset "Tensor absorption" begin # absorbing small into large - if isa(UnitStyle(I), SimpleUnit) || !isempty(blocksectors(V2 ⊗ V3)) + if UnitStyle(I) isa SimpleUnit || !isempty(blocksectors(V2 ⊗ V3)) t1 = zeros(V1 ⊕ V1, V2 ⊗ V3) t2 = rand(V1, V2 ⊗ V3) else @@ -587,7 +587,7 @@ for V in spacelist @test t3 ≈ t4 # absorbing large into small - if isa(UnitStyle(I), SimpleUnit) || !isempty(blocksectors(V2 ⊗ V3)) + if UnitStyle(I) isa SimpleUnit || !isempty(blocksectors(V2 ⊗ V3)) t1 = rand(V1 ⊕ V1, V2 ⊗ V3) t2 = zeros(V1, V2 ⊗ V3) else From 9fc7aa872a7b370d757cf64b8de4aef3015d32fc Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Wed, 3 Dec 2025 11:01:42 +0100 Subject: [PATCH 93/95] import `HasBraiding` manually --- test/symmetries/fusiontrees.jl | 1 + test/tensors/tensors.jl | 1 + 2 files changed, 2 insertions(+) diff --git a/test/symmetries/fusiontrees.jl b/test/symmetries/fusiontrees.jl index e5dbf8132..8cbab8485 100644 --- a/test/symmetries/fusiontrees.jl +++ b/test/symmetries/fusiontrees.jl @@ -6,6 +6,7 @@ using TensorOperations # TODO: remove this once type_repr works for all included types using TensorKitSectors +using TensorKitSectors: HasBraiding # TODO: remove once exported from TKS @isdefined(TestSetup) || include("../setup.jl") using .TestSetup diff --git a/test/tensors/tensors.jl b/test/tensors/tensors.jl index 43481e7d1..11482936c 100644 --- a/test/tensors/tensors.jl +++ b/test/tensors/tensors.jl @@ -3,6 +3,7 @@ using TensorKit using TensorKit: type_repr using Combinatorics: permutations using LinearAlgebra: LinearAlgebra +using TensorKitSectors: HasBraiding # TODO: remove once exported from TKS @isdefined(TestSetup) || include("../setup.jl") using .TestSetup From 41e73c16ad7cfa65f06c9611932b8a7d431c64b0 Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Wed, 3 Dec 2025 11:08:56 +0100 Subject: [PATCH 94/95] Revert "import `HasBraiding` manually" This reverts commit 9fc7aa872a7b370d757cf64b8de4aef3015d32fc. --- test/symmetries/fusiontrees.jl | 1 - test/tensors/tensors.jl | 1 - 2 files changed, 2 deletions(-) diff --git a/test/symmetries/fusiontrees.jl b/test/symmetries/fusiontrees.jl index 8cbab8485..e5dbf8132 100644 --- a/test/symmetries/fusiontrees.jl +++ b/test/symmetries/fusiontrees.jl @@ -6,7 +6,6 @@ using TensorOperations # TODO: remove this once type_repr works for all included types using TensorKitSectors -using TensorKitSectors: HasBraiding # TODO: remove once exported from TKS @isdefined(TestSetup) || include("../setup.jl") using .TestSetup diff --git a/test/tensors/tensors.jl b/test/tensors/tensors.jl index 11482936c..43481e7d1 100644 --- a/test/tensors/tensors.jl +++ b/test/tensors/tensors.jl @@ -3,7 +3,6 @@ using TensorKit using TensorKit: type_repr using Combinatorics: permutations using LinearAlgebra: LinearAlgebra -using TensorKitSectors: HasBraiding # TODO: remove once exported from TKS @isdefined(TestSetup) || include("../setup.jl") using .TestSetup From b33bc6794fb962cffd05106a21f570e57f9e4412 Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Wed, 3 Dec 2025 11:09:01 +0100 Subject: [PATCH 95/95] export `HasBraiding` in TensorKit --- src/TensorKit.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/TensorKit.jl b/src/TensorKit.jl index 39ee1dbb5..32e7ae577 100644 --- a/src/TensorKit.jl +++ b/src/TensorKit.jl @@ -11,7 +11,7 @@ module TensorKit export Sector, AbstractIrrep, Irrep export FusionStyle, UniqueFusion, MultipleFusion, MultiplicityFreeFusion, SimpleFusion, GenericFusion export UnitStyle, SimpleUnit, GenericUnit -export BraidingStyle, SymmetricBraiding, Bosonic, Fermionic, Anyonic, NoBraiding +export BraidingStyle, SymmetricBraiding, Bosonic, Fermionic, Anyonic, NoBraiding, HasBraiding export Trivial, Z2Irrep, Z3Irrep, Z4Irrep, ZNIrrep, U1Irrep, SU2Irrep, CU1Irrep export ProductSector export FermionParity, FermionNumber, FermionSpin