From 146d82523b442472f9e60eb3fccf94dd1f2b0d7d Mon Sep 17 00:00:00 2001 From: Lukas Devos Date: Tue, 19 Nov 2024 20:33:39 -0500 Subject: [PATCH 01/10] Fix conversion of empty tensor --- src/tensors/abstracttensor.jl | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/src/tensors/abstracttensor.jl b/src/tensors/abstracttensor.jl index ebc1788fc..e1db02cb3 100644 --- a/src/tensors/abstracttensor.jl +++ b/src/tensors/abstracttensor.jl @@ -497,21 +497,12 @@ function Base.convert(::Type{Array}, t::AbstractTensorMap) else cod = codomain(t) dom = domain(t) - local A + T = promote_type(scalartype(t), sectortype(t)) + A = zeros(T, dims(cod)..., dims(dom)...) for (f₁, f₂) in fusiontrees(t) F = convert(Array, (f₁, f₂)) - if !(@isdefined A) - if eltype(F) <: Complex - T = complex(float(scalartype(t))) - elseif eltype(F) <: Integer - T = scalartype(t) - else - T = float(scalartype(t)) - end - A = fill(zero(T), (dims(cod)..., dims(dom)...)) - end Aslice = StridedView(A)[axes(cod, f₁.uncoupled)..., axes(dom, f₂.uncoupled)...] - axpy!(1, StridedView(_kron(convert(Array, t[f₁, f₂]), F)), Aslice) + add!(Aslice, StridedView(_kron(convert(Array, t[f₁, f₂]), F))) end return A end From 324da30d1b938ed3a9f59350dca008cd87773c65 Mon Sep 17 00:00:00 2001 From: Lukas Devos Date: Tue, 19 Nov 2024 20:36:10 -0500 Subject: [PATCH 02/10] Add tests for empty tensor conversion --- test/tensors.jl | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/tensors.jl b/test/tensors.jl index 99d73b7c8..1312fe19b 100644 --- a/test/tensors.jl +++ b/test/tensors.jl @@ -126,6 +126,11 @@ for V in spacelist @test t === @constinferred TensorMap(t.data, W) end end + for T in (Int, Float32, ComplexF64) + t = randn(T, W1 ⊗ W2, typeof(W1)()) + a = convert(Array, t) + @test norm(a) == 0 + end end end @timedtestset "Basic linear algebra" begin From 6efd37bcef483bd8b962452ae259942006b1e86e Mon Sep 17 00:00:00 2001 From: Lukas Devos Date: Wed, 20 Nov 2024 06:54:29 -0500 Subject: [PATCH 03/10] Add `zero(::ElementarySpace)` --- src/spaces/cartesianspace.jl | 1 + src/spaces/complexspace.jl | 1 + src/spaces/generalspace.jl | 3 +++ src/spaces/gradedspace.jl | 1 + src/spaces/vectorspaces.jl | 8 ++++++++ test/spaces.jl | 16 +++++++++++----- 6 files changed, 25 insertions(+), 5 deletions(-) diff --git a/src/spaces/cartesianspace.jl b/src/spaces/cartesianspace.jl index c790b9ba6..f29ed263d 100644 --- a/src/spaces/cartesianspace.jl +++ b/src/spaces/cartesianspace.jl @@ -48,6 +48,7 @@ sectors(V::CartesianSpace) = OneOrNoneIterator(dim(V) != 0, Trivial()) sectortype(::Type{CartesianSpace}) = Trivial Base.oneunit(::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) flip(V::CartesianSpace) = V diff --git a/src/spaces/complexspace.jl b/src/spaces/complexspace.jl index 96f99492f..ff05888b8 100644 --- a/src/spaces/complexspace.jl +++ b/src/spaces/complexspace.jl @@ -49,6 +49,7 @@ sectortype(::Type{ComplexSpace}) = Trivial Base.conj(V::ComplexSpace) = ComplexSpace(dim(V), !isdual(V)) Base.oneunit(::Type{ComplexSpace}) = ComplexSpace(1) +Base.zero(::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 4468db443..c72b55e70 100644 --- a/src/spaces/generalspace.jl +++ b/src/spaces/generalspace.jl @@ -35,6 +35,9 @@ sectortype(::Type{<:GeneralSpace}) = Trivial field(::Type{GeneralSpace{𝔽}}) where {𝔽} = 𝔽 InnerProductStyle(::Type{<:GeneralSpace}) = NoInnerProduct() +Base.oneunit(::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)) 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 23c380fda..00f97c962 100644 --- a/src/spaces/gradedspace.jl +++ b/src/spaces/gradedspace.jl @@ -132,6 +132,7 @@ function Base.axes(V::GradedSpace{I}, c::I) where {I<:Sector} end Base.oneunit(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 # `FiniteGradedSpace`, but we don't expect them to be used often in hot loops, so diff --git a/src/spaces/vectorspaces.jl b/src/spaces/vectorspaces.jl index 1468892e5..e2f39007f 100644 --- a/src/spaces/vectorspaces.jl +++ b/src/spaces/vectorspaces.jl @@ -128,6 +128,14 @@ that this is different from `one(V::S)`, which returns the empty product space """ Base.oneunit(V::ElementarySpace) = oneunit(typeof(V)) +""" + zero(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. +""" +Base.zero(V::ElementarySpace) = zero(typeof(V)) + """ ⊕(V₁::S, V₂::S, V₃::S...) where {S<:ElementarySpace} -> S oplus(V₁::S, V₂::S, V₃::S...) where {S<:ElementarySpace} -> S diff --git a/test/spaces.jl b/test/spaces.jl index c3890db0c..57592ca35 100644 --- a/test/spaces.jl +++ b/test/spaces.jl @@ -66,12 +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(typeof(V)())) == 0 - @test (sectors(typeof(V)())...,) == () + @test dim(@constinferred(zero(V))) == 0 + @test (sectors(zero(V))...,) == () @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(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, V, V, V)) == ℝ^(4d) @@ -111,12 +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(typeof(V)())) == 0 - @test (sectors(typeof(V)())...,) == () + @test dim(@constinferred(zero(V))) == 0 + @test (sectors(zero(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(oneunit(V)) == W == oneunit(typeof(V)) + @test @constinferred(zero(V)) == ℂ^0 == zero(typeof(V)) + @test @constinferred(⊕(V, zero(V))) == V @test @constinferred(⊕(V, V)) == ℂ^(2d) @test_throws SpaceMismatch (⊕(V, V')) # promote_except = ErrorException("promotion of types $(typeof(ℝ^d)) and " * @@ -200,11 +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(typeof(V)())) == 0 + @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) # randsector never returns trivial sector, so this cannot error @test_throws ArgumentError GradedSpace(one(I) => 1, randsector(I) => 0, one(I) => 3) @test eval(Meta.parse(sprint(show, W))) == W @@ -226,6 +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, 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))) == From 53a2686f3649bf86928877ead71f99dd921ec34f Mon Sep 17 00:00:00 2001 From: Lukas Devos Date: Wed, 20 Nov 2024 06:58:14 -0500 Subject: [PATCH 04/10] Fix failing tests --- test/tensors.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/tensors.jl b/test/tensors.jl index 1312fe19b..545ac6cc6 100644 --- a/test/tensors.jl +++ b/test/tensors.jl @@ -127,7 +127,7 @@ for V in spacelist end end for T in (Int, Float32, ComplexF64) - t = randn(T, W1 ⊗ W2, typeof(W1)()) + t = randn(T, V1 ⊗ V2 ← zero(V1)) a = convert(Array, t) @test norm(a) == 0 end @@ -471,7 +471,7 @@ for V in spacelist end end @testset "empty tensor" begin - t = randn(T, V1 ⊗ V2, typeof(V1)()) + t = randn(T, V1 ⊗ V2, zero(V1)) @testset "leftorth with $alg" for alg in (TensorKit.QR(), TensorKit.QRpos(), TensorKit.QL(), TensorKit.QLpos(), From 389bcec877b697fc85c080d1780ceb67b5ab1064 Mon Sep 17 00:00:00 2001 From: Lukas Devos Date: Wed, 20 Nov 2024 07:00:08 -0500 Subject: [PATCH 05/10] Add explicit test for issue #178 --- test/bugfixes.jl | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test/bugfixes.jl b/test/bugfixes.jl index d539c01e0..e1b2df1e5 100644 --- a/test/bugfixes.jl +++ b/test/bugfixes.jl @@ -22,4 +22,11 @@ @test w == v @test scalartype(w) == Float64 end + + # https://github.com/Jutho/TensorKit.jl/issues/178 + @testset "Issue #178" begin + t = rand(U1Space(1 => 1) ← U1Space(1 => 1)') + a = convert(Array, t) + @test a == zeros(size(a)) + end end From 88a7fecb6d974e3ec5a5cfeb58b94f2c7365d48b Mon Sep 17 00:00:00 2001 From: Lukas Devos Date: Wed, 20 Nov 2024 09:17:31 -0500 Subject: [PATCH 06/10] Fix wrong type --- src/tensors/abstracttensor.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tensors/abstracttensor.jl b/src/tensors/abstracttensor.jl index e1db02cb3..f476b2d61 100644 --- a/src/tensors/abstracttensor.jl +++ b/src/tensors/abstracttensor.jl @@ -497,7 +497,7 @@ function Base.convert(::Type{Array}, t::AbstractTensorMap) else cod = codomain(t) dom = domain(t) - T = promote_type(scalartype(t), sectortype(t)) + T = promote_type(scalartype(t), sectorscalartype(I)) A = zeros(T, dims(cod)..., dims(dom)...) for (f₁, f₂) in fusiontrees(t) F = convert(Array, (f₁, f₂)) From b143e5b8a92244b4a4f182b1c3223b58b81c432b Mon Sep 17 00:00:00 2001 From: Lukas Devos Date: Wed, 20 Nov 2024 09:56:51 -0500 Subject: [PATCH 07/10] `eltype` should determine precision --- src/tensors/abstracttensor.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tensors/abstracttensor.jl b/src/tensors/abstracttensor.jl index f476b2d61..bb2e29185 100644 --- a/src/tensors/abstracttensor.jl +++ b/src/tensors/abstracttensor.jl @@ -497,7 +497,7 @@ function Base.convert(::Type{Array}, t::AbstractTensorMap) else cod = codomain(t) dom = domain(t) - T = promote_type(scalartype(t), sectorscalartype(I)) + T = isreal(sectorscalartype(I)) ? scalartype(t) : complex(scalartype(t)) A = zeros(T, dims(cod)..., dims(dom)...) for (f₁, f₂) in fusiontrees(t) F = convert(Array, (f₁, f₂)) From 74dacc9bb11dd5d2d02bc246bba8d7b187470d3b Mon Sep 17 00:00:00 2001 From: Lukas Devos Date: Wed, 20 Nov 2024 13:39:07 -0500 Subject: [PATCH 08/10] Add entry to docs --- docs/src/lib/spaces.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/src/lib/spaces.md b/docs/src/lib/spaces.md index 2a2b5696d..554e40c43 100644 --- a/docs/src/lib/spaces.md +++ b/docs/src/lib/spaces.md @@ -90,6 +90,7 @@ dual conj flip ⊕ +zero(::ElementarySpace) oneunit supremum infimum From 6d93ae1abc444ec0fbeffbef8103d3a561e4f612 Mon Sep 17 00:00:00 2001 From: Lukas Devos Date: Wed, 20 Nov 2024 17:55:09 -0500 Subject: [PATCH 09/10] small fix --- src/tensors/abstracttensor.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tensors/abstracttensor.jl b/src/tensors/abstracttensor.jl index bb2e29185..ce9d005b3 100644 --- a/src/tensors/abstracttensor.jl +++ b/src/tensors/abstracttensor.jl @@ -497,7 +497,7 @@ function Base.convert(::Type{Array}, t::AbstractTensorMap) else cod = codomain(t) dom = domain(t) - T = isreal(sectorscalartype(I)) ? scalartype(t) : complex(scalartype(t)) + T = sectorscalartype(I) <: Complex ? complex(scalartype(t)) : scalartype(t) A = zeros(T, dims(cod)..., dims(dom)...) for (f₁, f₂) in fusiontrees(t) F = convert(Array, (f₁, f₂)) From d594decc131b86e61138a76e55c1d98c7d53beca Mon Sep 17 00:00:00 2001 From: Lukas Devos Date: Wed, 20 Nov 2024 19:17:36 -0500 Subject: [PATCH 10/10] small fix attempt II --- src/tensors/abstracttensor.jl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/tensors/abstracttensor.jl b/src/tensors/abstracttensor.jl index ce9d005b3..8c86f81ea 100644 --- a/src/tensors/abstracttensor.jl +++ b/src/tensors/abstracttensor.jl @@ -497,7 +497,8 @@ function Base.convert(::Type{Array}, t::AbstractTensorMap) else cod = codomain(t) dom = domain(t) - T = sectorscalartype(I) <: Complex ? complex(scalartype(t)) : scalartype(t) + T = sectorscalartype(I) <: Complex ? complex(scalartype(t)) : + sectorscalartype(I) <: Integer ? scalartype(t) : float(scalartype(t)) A = zeros(T, dims(cod)..., dims(dom)...) for (f₁, f₂) in fusiontrees(t) F = convert(Array, (f₁, f₂))