From f60b2ac22c8ac65dc8f12bf965227d932ce08c10 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Wed, 13 May 2026 13:48:11 +0200 Subject: [PATCH] Initial steps to disentangle tests --- ext/TensorKitAMDGPUExt/roctensormap.jl | 17 ++ test/Project.toml | 2 + test/amd/tensors.jl | 262 +------------------------ test/cuda/tensors.jl | 253 ------------------------ test/runtests.jl | 3 + test/tensors/construction.jl | 139 ++++++------- test/tensors/linalg.jl | 221 +++++---------------- test/testsuite/TestSuite.jl | 66 +++++++ test/testsuite/tensors/construction.jl | 77 ++++++++ test/testsuite/tensors/linalg.jl | 248 +++++++++++++++++++++++ 10 files changed, 539 insertions(+), 749 deletions(-) create mode 100644 test/testsuite/TestSuite.jl create mode 100644 test/testsuite/tensors/construction.jl create mode 100644 test/testsuite/tensors/linalg.jl diff --git a/ext/TensorKitAMDGPUExt/roctensormap.jl b/ext/TensorKitAMDGPUExt/roctensormap.jl index f2f094c60..a6d3ad5fd 100644 --- a/ext/TensorKitAMDGPUExt/roctensormap.jl +++ b/ext/TensorKitAMDGPUExt/roctensormap.jl @@ -162,3 +162,20 @@ for f in (:sqrt, :log, :asin, :acos, :acosh, :atanh, :acoth) return tf end end + +# lu dispatches to the LAPACK wrapper, which is not what we need +function Base.inv(t::ROCTensorMap) + cod = codomain(t) + dom = domain(t) + cod ≅ dom || + throw(SpaceMismatch("codomain $cod and domain $dom are not isomorphic: no inverse")) + T = float(scalartype(t)) + tinv = similar(t, T, dom ← cod) + for (c, b) in blocks(t) + binv = one!(block(tinv, c)) + lpt = rocSOLVER.getrf!(copy(b)) + lub = LinearAlgebra.LU(lpt[1], lpt[2], Int(lpt[3])) + ldiv!(lub, binv) + end + return tinv +end diff --git a/test/Project.toml b/test/Project.toml index 18af8af80..828347993 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -17,6 +17,7 @@ MatrixAlgebraKit = "6c742aac-3347-4629-af66-fc926824e5e4" Mooncake = "da2b9cff-9c12-43a0-ae48-6db2b0edb7d6" ParallelTestRunner = "d3525ed8-44d0-4b2c-a655-542cee43accc" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" +StableRNGs = "860ef19b-820b-49d6-a774-d7a799459cd3" TensorKit = "07d1fe3e-3e46-537d-9eac-e9e13d0d4cec" TensorKitSectors = "13a9c161-d5da-41f0-bcbd-e1a08ae0647f" TensorOperations = "6aa20fa7-93e2-5fca-9bc0-fbd0db3c71a2" @@ -38,6 +39,7 @@ Combinatorics = "1" GPUArrays = "11.3.1" JET = "0.9, 0.10, 0.11" ParallelTestRunner = "2" +StableRNGs = "1" Test = "1" TestExtras = "0.2,0.3" Zygote = "0.7" diff --git a/test/amd/tensors.jl b/test/amd/tensors.jl index 0c6898405..a9d4d5c50 100644 --- a/test/amd/tensors.jl +++ b/test/amd/tensors.jl @@ -49,28 +49,6 @@ for V in spacelist @test domain(t) == one(W) @test typeof(t) == TensorMap{Float64, spacetype(t), 5, 0, ROCVector{Float64, AMDGPU.Mem.HIPBuffer}} end - for T in (Int, Float32, Float64, ComplexF32, ComplexF64) - t = @constinferred AMDGPU.zeros(T, W) - AMDGPU.@allowscalar begin - @test @constinferred(hash(t)) == hash(deepcopy(t)) - end - @test scalartype(t) == T - @test norm(t) == 0 - @test codomain(t) == W - @test space(t) == (W ← one(W)) - @test domain(t) == one(W) - @test typeof(t) == TensorMap{T, spacetype(t), 5, 0, ROCVector{T, AMDGPU.Mem.HIPBuffer}} - # 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) - end end @timedtestset "Conversion to/from host" begin W = V1 ⊗ V2 ⊗ V3 ⊗ V4 ⊗ V5 @@ -92,62 +70,18 @@ for V in spacelist @test domain(t2) == one(W) end end - @timedtestset "Tensor Dict conversion" begin - W = V1 ⊗ V2 ⊗ V3 ← V4 ⊗ V5 + @timedtestset "Adapt" begin + W = V1 ⊗ V2 ⊗ V3 ← (V4 ⊗ V5)' for T in (Int, Float32, ComplexF64) - t = @constinferred AMDGPU.rand(T, W) - d = convert(Dict, t) - @test TensorKit.to_cpu(t) == convert(TensorMap, d) - end - end - symmetricbraiding && @timedtestset "Basic linear algebra" begin - W = V1 ⊗ V2 ⊗ V3 ← V4 ⊗ V5 - for T in (Float32, ComplexF64) - t = @constinferred AMDGPU.rand(T, W) - @test scalartype(t) == T - @test space(t) == W - @test space(t') == W' - @test dim(t) == dim(space(t)) - @test codomain(t) == codomain(W) - @test domain(t) == domain(W) - # blocks for adjoint - 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) - # linear algebra - @test isa(@constinferred(norm(t)), real(T)) - @test norm(t)^2 ≈ dot(t, t) - α = rand(T) - @test norm(α * t) ≈ abs(α) * norm(t) - @test norm(t + t, 2) ≈ 2 * norm(t, 2) - @test norm(t + t, 1) ≈ 2 * norm(t, 1) - @test norm(t + t, Inf) ≈ 2 * norm(t, Inf) - p = 3 * rand(Float64) - @test norm(t + t, p) ≈ 2 * norm(t, p) - @test norm(t) ≈ norm(t') - - t2 = @constinferred rand!(similar(t)) - β = rand(T) - #@test @constinferred(dot(β * t2, α * t)) ≈ conj(β) * α * conj(dot(t, t2)) # broken for Irrep[CU₁] - @test dot(β * t2, α * t) ≈ conj(β) * α * conj(dot(t, t2)) - @test dot(t2, t) ≈ conj(dot(t, t2)) - @test dot(t2, t) ≈ conj(dot(t2', t')) - @test dot(t2, t) ≈ dot(t', t2') + t = rand(T, W) + t_gpu = @constinferred adapt(ROCArray, t) + @test storagetype(t_gpu) <: ROCArray{T} + @test scalartype(t_gpu) === scalartype(t) + @test collect(t_gpu.data) == t.data - i1 = @constinferred(isomorphism(ROCVector{T, AMDGPU.Mem.HIPBuffer}, V1 ⊗ V2, V2 ⊗ V1)) - i2 = @constinferred(isomorphism(ROCVector{T, AMDGPU.Mem.HIPBuffer}, V2 ⊗ V1, V1 ⊗ V2)) - @test i1 * i2 == @constinferred(id(ROCVector{T, AMDGPU.Mem.HIPBuffer}, V1 ⊗ V2)) - @test i2 * i1 == @constinferred(id(ROCVector{T, AMDGPU.Mem.HIPBuffer}, V2 ⊗ V1)) - w = @constinferred(isometry(ROCVector{T, AMDGPU.Mem.HIPBuffer}, V1 ⊗ (oneunit(V1) ⊕ oneunit(V1)), V1)) - @test dim(w) == 2 * dim(V1 ← V1) - @test w' * w == id(ROCVector{T, AMDGPU.Mem.HIPBuffer}, V1) - @test w * w' == (w * w')^2 + t_cpu = @constinferred adapt(Array, t_gpu) + @test t_cpu == t + @test storagetype(t_cpu) <: Array{T} end end @timedtestset "Trivial space insertion and removal" begin @@ -182,45 +116,6 @@ for V in spacelist @test @constinferred(removeunit(t5, 4)) == t end end - if hasfusiontensor(I) - @timedtestset "Basic linear algebra: test via CPU" begin - W = V1 ⊗ V2 ⊗ V3 ← V4 ⊗ V5 - for T in (Float32, ComplexF64) - t = AMDGPU.rand(T, W) - t2 = @constinferred AMDGPU.rand!(similar(t)) - α = rand(T) - @test norm(t, 2) ≈ norm(TensorKit.to_cpu(t), 2) - @test dot(t2, t) ≈ dot(TensorKit.to_cpu(t2), TensorKit.to_cpu(t)) - @test TensorKit.to_cpu(α * t) ≈ α * TensorKit.to_cpu(t) - @test TensorKit.to_cpu(t + t) ≈ 2 * TensorKit.to_cpu(t) - end - end - @timedtestset "Real and imaginary parts" begin - W = V1 ⊗ V2 - for T in (Float64, ComplexF64, ComplexF32) - t = @constinferred AMDGPU.randn(T, W, W) - - tr = @constinferred real(t) - @test scalartype(tr) <: Real - @test real(TensorKit.to_cpu(t)) == TensorKit.to_cpu(tr) - @test storagetype(tr) == ROCVector{real(T), AMDGPU.Mem.HIPBuffer} - - ti = @constinferred imag(t) - @test scalartype(ti) <: Real - @test imag(TensorKit.to_cpu(t)) == TensorKit.to_cpu(ti) - @test storagetype(ti) == ROCVector{real(T), AMDGPU.Mem.HIPBuffer} - - tc = @inferred complex(t) - @test scalartype(tc) <: Complex - @test complex(TensorKit.to_cpu(t)) == TensorKit.to_cpu(tc) - @test storagetype(tc) == ROCVector{complex(T), AMDGPU.Mem.HIPBuffer} - - tc2 = @inferred complex(tr, ti) - @test tc2 ≈ tc - @test storagetype(tc2) == ROCVector{complex(T), AMDGPU.Mem.HIPBuffer} - end - end - end @timedtestset "Tensor conversion" begin # TODO adjoint conversion methods don't work yet W = V1 ⊗ V2 t = @constinferred AMDGPU.randn(W ← W) @@ -232,15 +127,6 @@ for V in spacelist @test Base.promote_typeof(t, tc) == typeof(tc) @test Base.promote_typeof(tc, t) == typeof(tc + t) end - #=@timedtestset "diag/diagm" begin - W = V1 ⊗ V2 ⊗ V3 ← V4 ⊗ V5 - t = AMDGPU.randn(ComplexF64, W) - d = LinearAlgebra.diag(t) - # TODO find a way to use AMDGPU here - D = LinearAlgebra.diagm(codomain(t), domain(t), d) - @test LinearAlgebra.isdiag(D) - @test LinearAlgebra.diag(D) == d - end=# symmetricbraiding && @timedtestset "Permutations: test via inner product invariance" begin W = V1 ⊗ V2 ⊗ V3 ⊗ V4 ⊗ V5 t = AMDGPU.rand(ComplexF64, W) @@ -383,134 +269,6 @@ for V in spacelist @test flip(ta, (1, 2)) ≈ tb end end=# # TODO =# # None of this works due to lack of HIPTensor support - @timedtestset "Multiplication of isometries: test properties" begin - W2 = V4 ⊗ V5 - W1 = W2 ⊗ (oneunit(V1) ⊕ oneunit(V1)) - for T in (Float64, ComplexF64) - t1 = randisometry(ROCMatrix{T, AMDGPU.Mem.HIPBuffer}, W1, W2) - t2 = randisometry(ROCMatrix{T, AMDGPU.Mem.HIPBuffer}, W2 ← W2) - @test isisometric(t1) - @test isunitary(t2) - P = t1 * t1' - @test P * P ≈ P - end - end - @timedtestset "Multiplication and inverse: test compatibility" begin - W1 = V1 ⊗ V2 ⊗ V3 - W2 = V4 ⊗ V5 - for T in (Float64, ComplexF64) - t1 = AMDGPU.rand(T, W1, W1) - t2 = AMDGPU.rand(T, W2, W2) - t = AMDGPU.rand(T, W1, W2) - #=@test t1 * (t1 \ t) ≈ t - @test (t / t2) * t2 ≈ t - AMDGPU.@allowscalar begin - @test t1 \ one(t1) ≈ inv(t1) - @test one(t1) / t1 ≈ pinv(t1) - tp = pinv(t) * t - @test tp ≈ tp * tp - end=# - @test_throws SpaceMismatch inv(t) - @test_throws SpaceMismatch t2 \ t - @test_throws SpaceMismatch t / t1 - end - end - @timedtestset "Multiplication and inverse: test via CPU" begin - W1 = V1 ⊗ V2 ⊗ V3 - W2 = V4 ⊗ V5 - for T in (Float32, Float64, ComplexF32, ComplexF64) - t1 = AMDGPU.rand(T, W1, W1) - t2 = AMDGPU.rand(T, W2, W2) - t = AMDGPU.rand(T, W1, W2) - ht1 = TensorKit.to_cpu(t1) - ht2 = TensorKit.to_cpu(t2) - ht = TensorKit.to_cpu(t) - @test TensorKit.to_cpu(t1 * t) ≈ ht1 * ht - @test TensorKit.to_cpu(t1' * t) ≈ ht1' * ht - @test TensorKit.to_cpu(t2 * t') ≈ ht2 * ht' - @test TensorKit.to_cpu(t2' * t') ≈ ht2' * ht' - - #=AMDGPU.@allowscalar begin - @test TensorKit.to_cpu(inv(t1)) ≈ inv(ht1) - @test TensorKit.to_cpu(pinv(t)) ≈ pinv(ht) - - if T == Float32 || T == ComplexF32 - continue - end - - @test TensorKit.to_cpu(t1 \ t) ≈ ht1 \ ht - @test TensorKit.to_cpu(t1' \ t) ≈ ht1' \ ht - @test TensorKit.to_cpu(t2 \ t') ≈ ht2 \ ht' - @test TensorKit.to_cpu(t2' \ t') ≈ ht2' \ ht' - - @test TensorKit.to_cpu(t2 / t) ≈ ht2 / ht - @test TensorKit.to_cpu(t2' / t) ≈ ht2' / ht - @test TensorKit.to_cpu(t1 / t') ≈ ht1 / ht' - @test TensorKit.to_cpu(t1' / t') ≈ ht1' / ht' - end=# - end - end - symmetricbraiding && @timedtestset "Tensor functions" begin - W = V1 ⊗ V2 - for T in (Float64, ComplexF64) - #=t = project_hermitian!(AMDGPU.randn(T, W, W)) - s = dim(W) - @test (@constinferred sqrt(t))^2 ≈ t - @test TensorKit.to_cpu(sqrt(t)) ≈ sqrt(TensorKit.to_cpu(t)) - expt = @constinferred exp(t) - @test TensorKit.to_cpu(expt) ≈ exp(TensorKit.to_cpu(t)) - @test exp(@constinferred log(project_hermitian!(expt))) ≈ expt - @test TensorKit.to_cpu(log(project_hermitian!(expt))) ≈ log(TensorKit.to_cpu(expt)) - - @test (@constinferred cos(t))^2 + (@constinferred sin(t))^2 ≈ - id(storagetype(t), W) - @test (@constinferred tan(t)) ≈ sin(t) / cos(t) - @test (@constinferred cot(t)) ≈ cos(t) / sin(t) - @test (@constinferred cosh(t))^2 - (@constinferred sinh(t))^2 ≈ - id(storagetype(t), W) - @test (@constinferred tanh(t)) ≈ sinh(t) / cosh(t) - @test (@constinferred coth(t)) ≈ cosh(t) / sinh(t)=# # TODO in AMDGPU - - #=t1 = sin(t) - @test sin(@constinferred asin(t1)) ≈ t1 - t2 = cos(t) - @test cos(@constinferred acos(t2)) ≈ t2 - t3 = sinh(t) - @test sinh(@constinferred asinh(t3)) ≈ t3 - t4 = cosh(t) - @test cosh(@constinferred acosh(t4)) ≈ t4 - t5 = tan(t) - @test tan(@constinferred atan(t5)) ≈ t5 - t6 = cot(t) - @test cot(@constinferred acot(t6)) ≈ t6 - t7 = tanh(t) - @test tanh(@constinferred atanh(t7)) ≈ t7 - t8 = coth(t) - @test coth(@constinferred acoth(t8)) ≈ t8=# - # TODO in AMDGPU - end - end - # Sylvester not defined for AMDGPU - # @timedtestset "Sylvester equation" begin - # for T in (Float32, ComplexF64) - # tA = AMDGPU.rand(T, V1 ⊗ V3, V1 ⊗ V3) - # tB = AMDGPU.rand(T, V2 ⊗ V4, V2 ⊗ V4) - # tA = 3 // 2 * leftorth(tA; alg=Polar())[1] - # tB = 1 // 5 * leftorth(tB; alg=Polar())[1] - # tC = AMDGPU.rand(T, V1 ⊗ V3, V2 ⊗ V4) - # t = @constinferred sylvester(tA, tB, tC) - # @test codomain(t) == V1 ⊗ V3 - # @test domain(t) == V2 ⊗ V4 - # @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))) - # @test matrix(t) ≈ sylvester(matrix(tA), matrix(tB), matrix(tC)) - # end - # end - # end - # - # TODO #=@timedtestset "Tensor product: test via norm preservation" begin for T in (Float32, ComplexF64) t1 = AMDGPU.rand(T, V2 ⊗ V3 ⊗ V1, V1 ⊗ V2) diff --git a/test/cuda/tensors.jl b/test/cuda/tensors.jl index 738440bef..84ace391a 100644 --- a/test/cuda/tensors.jl +++ b/test/cuda/tensors.jl @@ -53,28 +53,6 @@ for V in spacelist @test domain(t) == one(W) @test typeof(t) == TensorMap{Float64, spacetype(t), 5, 0, CuVector{Float64, CUDA.DeviceMemory}} end - for T in (Int, Float32, Float64, ComplexF32, ComplexF64) - t = @constinferred CUDA.zeros(T, W) - CUDA.@allowscalar begin - @test @constinferred(hash(t)) == hash(deepcopy(t)) - end - @test scalartype(t) == T - @test norm(t) == 0 - @test codomain(t) == W - @test space(t) == (W ← one(W)) - @test domain(t) == one(W) - @test typeof(t) == TensorMap{T, spacetype(t), 5, 0, CuVector{T, CUDA.DeviceMemory}} - # 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) - end end @timedtestset "Conversion to/from host" begin W = V1 ⊗ V2 ⊗ V3 ⊗ V4 ⊗ V5 @@ -110,64 +88,6 @@ for V in spacelist @test storagetype(t_cpu) <: Array{T} end end - @timedtestset "Tensor Dict conversion" begin - W = V1 ⊗ V2 ← (V3 ⊗ V4 ⊗ V5)' - for T in (Int, Float32, ComplexF64) - t = @constinferred cuRAND.rand(T, W) - d = convert(Dict, t) - @test convert(Dict, TensorKit.to_cpu(t)) == d - end - end - symmetricbraiding && @timedtestset "Basic linear algebra" begin - W = V1 ⊗ V2 ← (V3 ⊗ V4 ⊗ V5)' - for T in (Float32, ComplexF64) - t = @constinferred cuRAND.rand(T, W) - @test scalartype(t) == T - @test space(t) == W - @test space(t') == W' - @test dim(t) == dim(space(t)) - @test codomain(t) == codomain(W) - @test domain(t) == domain(W) - # blocks for adjoint - 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) - # linear algebra - @test isa(@constinferred(norm(t)), real(T)) - @test norm(t)^2 ≈ dot(t, t) - α = rand(T) - @test norm(α * t) ≈ abs(α) * norm(t) - @test norm(t + t, 2) ≈ 2 * norm(t, 2) - @test norm(t + t, 1) ≈ 2 * norm(t, 1) - @test norm(t + t, Inf) ≈ 2 * norm(t, Inf) - p = 3 * rand(Float64) - @test norm(t + t, p) ≈ 2 * norm(t, p) - @test norm(t) ≈ norm(t') - - t2 = @constinferred rand!(similar(t)) - β = rand(T) - #@test @constinferred(dot(β * t2, α * t)) ≈ conj(β) * α * conj(dot(t, t2)) # broken for Irrep[CU₁] - @test dot(β * t2, α * t) ≈ conj(β) * α * conj(dot(t, t2)) - @test dot(t2, t) ≈ conj(dot(t, t2)) - @test dot(t2, t) ≈ conj(dot(t2', t')) - @test dot(t2, t) ≈ dot(t', t2') - - i1 = @constinferred(isomorphism(CuVector{T, CUDA.DeviceMemory}, V1 ⊗ V2, V2 ⊗ V1)) - i2 = @constinferred(isomorphism(CuVector{T, CUDA.DeviceMemory}, V2 ⊗ V1, V1 ⊗ V2)) - @test i1 * i2 == @constinferred(id(CuVector{T, CUDA.DeviceMemory}, V1 ⊗ V2)) - @test i2 * i1 == @constinferred(id(CuVector{T, CUDA.DeviceMemory}, V2 ⊗ V1)) - w = @constinferred(isometry(CuVector{T, CUDA.DeviceMemory}, V1 ⊗ (oneunit(V1) ⊕ oneunit(V1)), V1)) - @test dim(w) == 2 * dim(V1 ← V1) - @test w' * w == id(CuVector{T, CUDA.DeviceMemory}, V1) - @test w * w' == (w * w')^2 - end - end @timedtestset "Trivial space insertion and removal" begin W = V1 ⊗ V2 ← (V3 ⊗ V4 ⊗ V5)' for T in (Float32, ComplexF64) @@ -213,53 +133,7 @@ for V in spacelist @test TensorKit.to_cpu(t + t) ≈ 2 * TensorKit.to_cpu(t) end end - @timedtestset "Real and imaginary parts" begin - W = V1 ⊗ V2 - for T in (Float64, ComplexF64, ComplexF32) - t = @constinferred cuRAND.randn(T, W, W) - - tr = @constinferred real(t) - @test scalartype(tr) <: Real - @test real(TensorKit.to_cpu(t)) == TensorKit.to_cpu(tr) - @test storagetype(tr) == CuVector{real(T), CUDA.DeviceMemory} - - ti = @constinferred imag(t) - @test scalartype(ti) <: Real - @test imag(TensorKit.to_cpu(t)) == TensorKit.to_cpu(ti) - @test storagetype(ti) == CuVector{real(T), CUDA.DeviceMemory} - - tc = @inferred complex(t) - @test scalartype(tc) <: Complex - @test complex(TensorKit.to_cpu(t)) == TensorKit.to_cpu(tc) - @test storagetype(tc) == CuVector{complex(T), CUDA.DeviceMemory} - - tc2 = @inferred complex(tr, ti) - @test tc2 ≈ tc - @test storagetype(tc2) == CuVector{complex(T), CUDA.DeviceMemory} - end - end end - @timedtestset "Tensor conversion" begin - W = V1 ⊗ V2 - t = @constinferred cuRAND.randn(W ← W) - @test typeof(convert(typeof(t), t')) == typeof(t) - @test typeof(TensorKit.to_cpu(t')) == typeof(TensorKit.to_cpu(t)') - tc = complex(t) - @test convert(typeof(tc), t) == tc - @test typeof(convert(typeof(tc), t)) == typeof(tc) - @test typeof(convert(typeof(tc), t')) == typeof(tc) - @test Base.promote_typeof(t, tc) == typeof(tc) - @test Base.promote_typeof(tc, t) == typeof(tc + t) - end - #=@timedtestset "diag/diagm" begin - W = V1 ⊗ V2 ⊗ V3 ← V4 ⊗ V5 - t = cuRAND.randn(ComplexF64, W) - d = LinearAlgebra.diag(t) - # TODO find a way to use CUDA here - D = LinearAlgebra.diagm(codomain(t), domain(t), d) - @test LinearAlgebra.isdiag(D) - @test LinearAlgebra.diag(D) == d - end=# symmetricbraiding && @timedtestset "Permutations: test via inner product invariance" begin W = V1 ⊗ V2 ⊗ V3 ⊗ V4 ⊗ V5 t = cuRAND.rand(ComplexF64, W) @@ -398,133 +272,6 @@ for V in spacelist @test flip(ta, (1, 2)) ≈ tb end end=# # TODO - @timedtestset "Multiplication of isometries: test properties" begin - W1 = V1 ⊗ V2 ⊗ V3 - W2 = (V4 ⊗ V5)' - for T in (Float64, ComplexF64) - t1 = randisometry(CuMatrix{T}, W1, W2) - t2 = randisometry(CuMatrix{T}, W2 ← W2) - @test isisometric(t1) - @test isunitary(t2) - P = t1 * t1' - @test P * P ≈ P - end - end - @timedtestset "Multiplication and inverse: test compatibility" begin - W1 = V1 ⊗ V2 ⊗ V3 - W2 = (V4 ⊗ V5)' - for T in (Float64, ComplexF64) - t1 = cuRAND.rand(T, W1, W1) - t2 = cuRAND.rand(T, W2, W2) - t = cuRAND.rand(T, W1, W2) - @test t1 * (t1 \ t) ≈ t - @test (t / t2) * t2 ≈ t - @test t1 \ one(t1) ≈ inv(t1) - @test one(t1) / t1 ≈ pinv(t1) - @test_throws SpaceMismatch inv(t) - @test_throws SpaceMismatch t2 \ t - @test_throws SpaceMismatch t / t1 - tp = pinv(t) * t - @test tp ≈ tp * tp - end - end - @timedtestset "Multiplication and inverse: test via CPU" begin - W1 = V1 ⊗ V2 ⊗ V3 - W2 = (V4 ⊗ V5)' - for T in (Float32, Float64, ComplexF32, ComplexF64) - t1 = cuRAND.rand(T, W1, W1) - t2 = cuRAND.rand(T, W2, W2) - t = cuRAND.rand(T, W1, W2) - ht1 = TensorKit.to_cpu(t1) - ht2 = TensorKit.to_cpu(t2) - ht = TensorKit.to_cpu(t) - @test TensorKit.to_cpu(t1 * t) ≈ ht1 * ht - @test TensorKit.to_cpu(t1' * t) ≈ ht1' * ht - @test TensorKit.to_cpu(t2 * t') ≈ ht2 * ht' - @test TensorKit.to_cpu(t2' * t') ≈ ht2' * ht' - - @test TensorKit.to_cpu(inv(t1)) ≈ inv(ht1) - @test TensorKit.to_cpu(pinv(t)) ≈ pinv(ht) - - if T == Float32 || T == ComplexF32 - continue - end - - @test TensorKit.to_cpu(t1 \ t) ≈ ht1 \ ht - @test TensorKit.to_cpu(t1' \ t) ≈ ht1' \ ht - @test TensorKit.to_cpu(t2 \ t') ≈ ht2 \ ht' - @test TensorKit.to_cpu(t2' \ t') ≈ ht2' \ ht' - - @test TensorKit.to_cpu(t2 / t) ≈ ht2 / ht - @test TensorKit.to_cpu(t2' / t) ≈ ht2' / ht - @test TensorKit.to_cpu(t1 / t') ≈ ht1 / ht' - @test TensorKit.to_cpu(t1' / t') ≈ ht1' / ht' - end - end - symmetricbraiding && @timedtestset "Tensor functions" begin - W = V1 ⊗ V2 - for T in (Float64, ComplexF64) - t = project_hermitian!(cuRAND.randn(T, W, W)) - s = dim(W) - #@test (@constinferred sqrt(t))^2 ≈ t - #@test TensorKit.to_cpu(sqrt(t)) ≈ sqrt(TensorKit.to_cpu(t)) - - expt = @constinferred exp(t) - @test TensorKit.to_cpu(expt) ≈ exp(TensorKit.to_cpu(t)) - - # log doesn't work on CUDA yet (scalar indexing) - #@test exp(@constinferred log(project_hermitian!(expt))) ≈ expt - #@test TensorKit.to_cpu(log(project_hermitian!(expt))) ≈ log(TensorKit.to_cpu(expt)) - - #=@test (@constinferred cos(t))^2 + (@constinferred sin(t))^2 ≈ - id(storagetype(t), W) - @test (@constinferred tan(t)) ≈ sin(t) / cos(t) - @test (@constinferred cot(t)) ≈ cos(t) / sin(t) - @test (@constinferred cosh(t))^2 - (@constinferred sinh(t))^2 ≈ - id(storagetype(t), W) - @test (@constinferred tanh(t)) ≈ sinh(t) / cosh(t) - @test (@constinferred coth(t)) ≈ cosh(t) / sinh(t)=# # TODO in CUDA - - #=t1 = sin(t) - @test sin(@constinferred asin(t1)) ≈ t1 - t2 = cos(t) - @test cos(@constinferred acos(t2)) ≈ t2 - t3 = sinh(t) - @test sinh(@constinferred asinh(t3)) ≈ t3 - t4 = cosh(t) - @test cosh(@constinferred acosh(t4)) ≈ t4 - t5 = tan(t) - @test tan(@constinferred atan(t5)) ≈ t5 - t6 = cot(t) - @test cot(@constinferred acot(t6)) ≈ t6 - t7 = tanh(t) - @test tanh(@constinferred atanh(t7)) ≈ t7 - t8 = coth(t) - @test coth(@constinferred acoth(t8)) ≈ t8=# - # TODO in CUDA - end - end - # Sylvester not defined for CUDA - # @timedtestset "Sylvester equation" begin - # for T in (Float32, ComplexF64) - # tA = cuRAND.rand(T, V1 ⊗ V3, V1 ⊗ V3) - # tB = cuRAND.rand(T, V2 ⊗ V4, V2 ⊗ V4) - # tA = 3 // 2 * leftorth(tA; alg=Polar())[1] - # tB = 1 // 5 * leftorth(tB; alg=Polar())[1] - # tC = cuRAND.rand(T, V1 ⊗ V3, V2 ⊗ V4) - # t = @constinferred sylvester(tA, tB, tC) - # @test codomain(t) == V1 ⊗ V3 - # @test domain(t) == V2 ⊗ V4 - # @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))) - # @test matrix(t) ≈ sylvester(matrix(tA), matrix(tB), matrix(tC)) - # end - # end - # end - # - # TODO @timedtestset "Tensor product: test via norm preservation" begin for T in (ComplexF64,) # Float32 case broken because of cuTENSOR t1 = cuRAND.rand(T, V1, V5') diff --git a/test/runtests.jl b/test/runtests.jl index 881d538a8..3af158d25 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -3,6 +3,9 @@ using TensorKit testsuite = ParallelTestRunner.find_tests(@__DIR__) +# remove testsuite +filter!(!(startswith("testsuite") ∘ first), testsuite) + # Exclude non-test files delete!(testsuite, "setup") # shared setup module diff --git a/test/tensors/construction.jl b/test/tensors/construction.jl index c6047b8d8..c63877ffa 100644 --- a/test/tensors/construction.jl +++ b/test/tensors/construction.jl @@ -1,10 +1,13 @@ -using Test, TestExtras using TensorKit -using TensorKit: type_repr - +using TensorKit: type_repr, sectortype +using CUDA, CUDA.cuRAND, cuTENSOR, AMDGPU +using TestExtras spacelist = default_spacelist(fast_tests) +@isdefined(TestSuite) || include("../testsuite/TestSuite.jl") +using .TestSuite + for V in spacelist I = sectortype(first(V)) Istr = type_repr(I) @@ -16,43 +19,32 @@ for V in spacelist @timedtestset "Basic tensor properties" begin W = V1 ⊗ V2 ⊗ V3 ⊗ V4 ⊗ V5 for T in (fast_tests ? (Float64, ComplexF64) : (Int, Float32, Float64, ComplexF32, ComplexF64, BigFloat)) - t = @constinferred zeros(T, W) - @test @constinferred(hash(t)) == hash(deepcopy(t)) - @test scalartype(t) == T - @test norm(t) == 0 - @test codomain(t) == W - @test space(t) == (W ← one(W)) - @test domain(t) == one(W) - @test typeof(t) == TensorMap{T, spacetype(t), 5, 0, Vector{T}} - # Array type input - t = @constinferred zeros(Vector{T}, W) - @test @constinferred(hash(t)) == hash(deepcopy(t)) - @test scalartype(t) == T - @test norm(t) == 0 - @test codomain(t) == W - @test space(t) == (W ← one(W)) - @test domain(t) == one(W) - @test typeof(t) == TensorMap{T, spacetype(t), 5, 0, Vector{T}} - # blocks - bs = @constinferred blocks(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) + ATs = [Vector{T}] + if T ∈ TestSuite.BLASFloats + CUDA.functional() && push!(ATs, CuVector{T, CUDA.DeviceMemory}) + AMDGPU.functional() && push!(ATs, ROCVector{T, AMDGPU.Mem.HIPBuffer}) + end + for AT in ATs + t = @constinferred zero_f(AT)(T, W) + TestSuite.basic_tensor_properties(t, W, T, AT) + t = @constinferred zeros(AT, W) + TestSuite.basic_tensor_properties(t, W, T, AT) + if !isempty(blocksectors(t)) # multifusion space ending on module gives empty data + TestSuite.basic_blocks_properties(t, W) + end end end end @timedtestset "Tensor Dict conversion" begin W = V1 ⊗ V2 ← (V3 ⊗ V4 ⊗ V5)' for T in (Int, Float32, ComplexF64) - t = @constinferred rand(T, W) - d = convert(Dict, t) - @test t == convert(TensorMap, d) + ATs = [Vector{T}] + CUDA.functional() && push!(ATs, CuVector{T, CUDA.DeviceMemory}) + AMDGPU.functional() && push!(ATs, ROCVector{T, AMDGPU.Mem.HIPBuffer}) + for AT in ATs + t = @constinferred rand_f(AT)(T, W) + TestSuite.tensor_dict_conversion(t) + end end end if hasfusiontensor(I) || I == Trivial @@ -65,61 +57,60 @@ for V in spacelist W6 = V1 ⊗ V2 ⊗ V3 ← V4 ⊗ V5 for W in (W1, W2, W3, W4, W5, W6) for T in (Int, Float32, ComplexF64) - if T == Int - t = TensorMap{T}(undef, W) - for (_, b) in blocks(t) - rand!(b, -20:20) + ATs = [Vector{T}] + if T ∈ TestSuite.BLASFloats + CUDA.functional() && push!(ATs, CuVector{T, CUDA.DeviceMemory}) + AMDGPU.functional() && push!(ATs, ROCVector{T, AMDGPU.Mem.HIPBuffer}) + end + for AT in ATs + if T == Int + t = TensorKit.TensorMapWithStorage{T, AT}(undef, W) + for (_, b) in blocks(t) + rand!(b, -20:20) + end + else + t = @constinferred randn_f(AT)(T, W) end - else - t = @constinferred randn(T, W) + TestSuite.tensor_array_conversion(t, W) end - a = @constinferred convert(Array, t) - 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)) - a = convert(Array, t) - @test norm(a) == 0 + ATs = [Vector{T}] + CUDA.functional() && push!(ATs, CuVector{T, CUDA.DeviceMemory}) + AMDGPU.functional() && push!(ATs, ROCVector{T, AMDGPU.Mem.HIPBuffer}) + for AT in ATs + t = randn_f(AT)(T, V1 ⊗ V2 ← zerospace(V1)) + TestSuite.empty_tensor_array_conversion(t, AT) + end end end end if hasfusiontensor(I) @timedtestset "Real and imaginary parts" begin - W = V1 ⊗ V2 for T in (Float64, ComplexF64, ComplexF32) - t = @constinferred randn(T, W, W) - - tr = @constinferred real(t) - @test scalartype(tr) <: Real - @test real(convert(Array, t)) == convert(Array, tr) - - ti = @constinferred imag(t) - @test scalartype(ti) <: Real - @test imag(convert(Array, t)) == convert(Array, ti) - - tc = @inferred complex(t) - @test scalartype(tc) <: Complex - @test complex(convert(Array, t)) == convert(Array, tc) - - tc2 = @inferred complex(tr, ti) - @test tc2 ≈ tc + ATs = [Vector{T}] + CUDA.functional() && push!(ATs, CuVector{T, CUDA.DeviceMemory}) + AMDGPU.functional() && push!(ATs, ROCVector{T, AMDGPU.Mem.HIPBuffer}) + for AT in ATs + W = V1 ⊗ V2 + t = @constinferred randn_f(AT)(T, W, W) + TestSuite.real_and_imaginary_parts(t) + end end end end @timedtestset "Tensor conversion" begin - W = V1 ⊗ V2 - t = @constinferred randn(W ← W) - @test typeof(convert(TensorMap, t')) == typeof(t) - tc = complex(t) - @test convert(typeof(tc), t) == tc - @test typeof(convert(typeof(tc), t)) == typeof(tc) - @test typeof(convert(typeof(tc), t')) == typeof(tc) - @test Base.promote_typeof(t, tc) == typeof(tc) - @test Base.promote_typeof(tc, t) == typeof(tc + t) + for T in (Float64, ComplexF64, ComplexF32) + ATs = [Vector{T}] + CUDA.functional() && push!(ATs, CuVector{T, CUDA.DeviceMemory}) + AMDGPU.functional() && push!(ATs, ROCVector{T, AMDGPU.Mem.HIPBuffer}) + for AT in ATs + W = V1 ⊗ V2 + t = @constinferred randn_f(AT)(W ← W) + TestSuite.tensor_conversion(t) + end + end end end TensorKit.empty_globalcaches!() diff --git a/test/tensors/linalg.jl b/test/tensors/linalg.jl index 738e85939..f1f95c008 100644 --- a/test/tensors/linalg.jl +++ b/test/tensors/linalg.jl @@ -2,9 +2,21 @@ using Test, TestExtras using TensorKit using TensorKit: type_repr using LinearAlgebra: LinearAlgebra +using CUDA, AMDGPU spacelist = default_spacelist(fast_tests) +@isdefined(TestSuite) || include("../testsuite/TestSuite.jl") +using .TestSuite + +rand_fs = Any[rand_f(Vector)] +CUDA.functional() && push!(rand_fs, rand_f(CuVector)) +AMDGPU.functional() && push!(rand_fs, rand_f(ROCVector)) + +randn_fs = Any[randn_f(Vector)] +CUDA.functional() && push!(randn_fs, randn_f(CuVector)) +AMDGPU.functional() && push!(randn_fs, randn_f(ROCVector)) + for V in spacelist I = sectortype(first(V)) Istr = type_repr(I) @@ -12,71 +24,33 @@ for V in spacelist println("---------------------------------------") println("Tensor linear algebra with symmetry: $Istr") println("---------------------------------------") + @timedtestset "Tensor linear algebra with symmetry: $Istr" verbose = true begin V1, V2, V3, V4, V5 = V @timedtestset "Basic linear algebra" begin W = V1 ⊗ V2 ← (V3 ⊗ V4 ⊗ V5)' + for T in (Float32, ComplexF64), rand_f in rand_fs + TestSuite.basic_linear_algebra(rand_f, T, W) + TestSuite.tensor_norm(rand_f, T, W) + TestSuite.tensor_dot(rand_f, T, W) + end for T in (Float32, ComplexF64) - t = @constinferred rand(T, W) - @test scalartype(t) == T - @test space(t) == W - @test space(t') == W' - @test dim(t) == dim(space(t)) - @test codomain(t) == codomain(W) - @test domain(t) == domain(W) - # blocks for adjoint - 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) - # linear algebra - @test isa(@constinferred(norm(t)), real(T)) - @test norm(t)^2 ≈ dot(t, t) - α = rand(T) - @test norm(α * t) ≈ abs(α) * norm(t) - @test norm(t + t, 2) ≈ 2 * norm(t, 2) - @test norm(t + t, 1) ≈ 2 * norm(t, 1) - @test norm(t + t, Inf) ≈ 2 * norm(t, Inf) - p = 3 * rand(Float64) - @test norm(t + t, p) ≈ 2 * norm(t, p) - @test norm(t) ≈ norm(t') - - t2 = @constinferred rand!(similar(t)) - β = rand(T) - @test @constinferred(dot(β * t2, α * t)) ≈ conj(β) * α * conj(dot(t, t2)) - @test dot(t2, t) ≈ conj(dot(t, t2)) - @test dot(t2, t) ≈ conj(dot(t2', t')) - @test dot(t2, t) ≈ dot(t', t2') - - 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)) - @test i2 * i1 == @constinferred(id(Vector{T}, V2 ⊗ V1)) + ATs = [Vector{T}] + if T ∈ TestSuite.BLASFloats + CUDA.functional() && push!(ATs, CuVector{T, CUDA.DeviceMemory}) + AMDGPU.functional() && push!(ATs, ROCVector{T, AMDGPU.Mem.HIPBuffer}) + end + for AT in ATs + TestSuite.isomorphism_test(T, AT, V1, V2) + TestSuite.isometry_test(T, AT, V1) 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 if hasfusiontensor(I) @timedtestset "Basic linear algebra: test via conversion" begin W = V1 ⊗ V2 ⊗ V3 ← (V4 ⊗ V5)' - for T in (Float32, ComplexF64) - t = rand(T, W) - t2 = @constinferred rand!(similar(t)) - @test norm(t, 2) ≈ norm(convert(Array, t), 2) - @test dot(t2, t) ≈ dot(convert(Array, t2), convert(Array, t)) - α = rand(T) - @test convert(Array, α * t) ≈ α * convert(Array, t) - @test convert(Array, t + t) ≈ 2 * convert(Array, t) + for T in (Float32, ComplexF64), rand_f in rand_fs + TestSuite.linalg_via_conversion(rand_f, T, W) end end end @@ -84,145 +58,52 @@ for V in spacelist W1 = V1 ⊗ V2 ⊗ V3 W2 = (V4 ⊗ V5)' for T in (Float64, ComplexF64) - t1 = randisometry(T, W1, W2) - t2 = randisometry(T, W2 ← W2) - @test isisometric(t1) - @test isunitary(t2) - P = t1 * t1' - @test P * P ≈ P + ATs = [Vector{T}] + if T ∈ TestSuite.BLASFloats + CUDA.functional() && push!(ATs, CuVector{T, CUDA.DeviceMemory}) + AMDGPU.functional() && push!(ATs, ROCVector{T, AMDGPU.Mem.HIPBuffer}) + end + for AT in ATs + TestSuite.multiplying_isometries(AT, W1, W2) + end end end @timedtestset "Multiplication and inverse: test compatibility" begin W1 = V1 ⊗ V2 ⊗ V3 W2 = (V4 ⊗ V5)' - for T in (Float64, ComplexF64) - t1 = rand(T, W1, W1) - t2 = rand(T, W2 ← W2) - t = rand(T, W1, W2) - @test t1 * (t1 \ t) ≈ t - @test (t / t2) * t2 ≈ t - @test t1 \ one(t1) ≈ inv(t1) - @test one(t1) / t1 ≈ pinv(t1) - @test_throws SpaceMismatch inv(t) - @test_throws SpaceMismatch t2 \ t - @test_throws SpaceMismatch t / t1 - tp = pinv(t) * t - @test tp ≈ tp * tp + for T in (Float64, ComplexF64), rand_f in rand_fs + TestSuite.tensor_multiplication_and_inverse(rand_f, T, W1, W2) end end if BraidingStyle(I) isa Bosonic && hasfusiontensor(I) @timedtestset "Multiplication and inverse: test via conversion" begin W1 = V1 ⊗ V2 ⊗ V3 W2 = (V4 ⊗ V5)' - for T in (Float32, Float64, ComplexF32, ComplexF64) - t1 = rand(T, W1 ← W1) - t2 = rand(T, W2, W2) - t = rand(T, W1 ← 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) - @test reshape(convert(Array, t1 * t), d1, d2) ≈ At1 * At - @test reshape(convert(Array, t1' * t), d1, d2) ≈ At1' * At - @test reshape(convert(Array, t2 * t'), d2, d1) ≈ At2 * At' - @test reshape(convert(Array, t2' * t'), d2, d1) ≈ At2' * At' - - @test reshape(convert(Array, inv(t1)), d1, d1) ≈ inv(At1) - @test reshape(convert(Array, pinv(t)), d2, d1) ≈ pinv(At) - - if T == Float32 || T == ComplexF32 - continue - end - - @test reshape(convert(Array, t1 \ t), d1, d2) ≈ At1 \ At - @test reshape(convert(Array, t1' \ t), d1, d2) ≈ At1' \ At - @test reshape(convert(Array, t2 \ t'), d2, d1) ≈ At2 \ At' - @test reshape(convert(Array, t2' \ t'), d2, d1) ≈ At2' \ At' - - @test reshape(convert(Array, t2 / t), d2, d1) ≈ At2 / At - @test reshape(convert(Array, t2' / t), d2, d1) ≈ At2' / At - @test reshape(convert(Array, t1 / t'), d1, d2) ≈ At1 / At' - @test reshape(convert(Array, t1' / t'), d1, d2) ≈ At1' / At' + for T in (Float32, Float64, ComplexF32, ComplexF64), rand_f in rand_fs + TestSuite.tensor_multiplication_and_inverse_conversion(rand_f, T, W1, W2) end end end @timedtestset "diag/diagm" begin W = V1 ⊗ V2 ← (V3 ⊗ V4 ⊗ V5)' - t = randn(ComplexF64, W) - d = LinearAlgebra.diag(t) - D = LinearAlgebra.diagm(codomain(t), domain(t), d) - @test LinearAlgebra.isdiag(D) - @test LinearAlgebra.diag(D) == d + for T in (ComplexF64,), randn_f in randn_fs + TestSuite.diag_diagm(randn_f, T, W) + end end if BraidingStyle(I) isa Bosonic && hasfusiontensor(I) @timedtestset "Tensor functions" begin - W = V1 ⊗ V2 - for T in (Float64, ComplexF64) - t = randn(T, W, W) - s = dim(W) - expt = @constinferred exp(t) - @test reshape(convert(Array, expt), (s, s)) ≈ - exp(reshape(convert(Array, t), (s, s))) - - @test (@constinferred sqrt(t))^2 ≈ t - @test reshape(convert(Array, sqrt(t^2)), (s, s)) ≈ - sqrt(reshape(convert(Array, t^2), (s, s))) - - @test exp(@constinferred log(expt)) ≈ expt - @test reshape(convert(Array, log(expt)), (s, s)) ≈ - log(reshape(convert(Array, expt), (s, s))) - - @test (@constinferred cos(t))^2 + (@constinferred sin(t))^2 ≈ id(W) - @test (@constinferred tan(t)) ≈ sin(t) / cos(t) - @test (@constinferred cot(t)) ≈ cos(t) / sin(t) - @test (@constinferred cosh(t))^2 - (@constinferred sinh(t))^2 ≈ id(W) - @test (@constinferred tanh(t)) ≈ sinh(t) / cosh(t) - @test (@constinferred coth(t)) ≈ cosh(t) / sinh(t) - - t1 = sin(t) - @test sin(@constinferred asin(t1)) ≈ t1 - t2 = cos(t) - @test cos(@constinferred acos(t2)) ≈ t2 - t3 = sinh(t) - @test sinh(@constinferred asinh(t3)) ≈ t3 - t4 = cosh(t) - @test cosh(@constinferred acosh(t4)) ≈ t4 - t5 = tan(t) - @test tan(@constinferred atan(t5)) ≈ t5 - t6 = cot(t) - @test cot(@constinferred acot(t6)) ≈ t6 - t7 = tanh(t) - @test tanh(@constinferred atanh(t7)) ≈ t7 - t8 = coth(t) - @test coth(@constinferred acoth(t8)) ≈ t8 - t = randn(T, W, V1) # not square - for f in - ( - cos, sin, tan, cot, cosh, sinh, tanh, coth, atan, acot, asinh, - sqrt, log, asin, acos, acosh, atanh, acoth, - ) - @test_throws SpaceMismatch f(t) - end + # TODO need better tensor function support for AMDGPU + tf_randn_fs = Any[randn_f(Vector)] + CUDA.functional() && push!(tf_randn_fs, randn_f(CuVector)) + for T in (Float64, ComplexF64), randn_f in tf_randn_fs + TestSuite.tensor_functions(randn_f, T, V1, V2) end end end @timedtestset "Sylvester equation" begin - for T in (Float32, ComplexF64) - tA = rand(T, V1 ⊗ V2, V1 ⊗ V2) - tB = rand(T, (V3 ⊗ V4 ⊗ V5)', (V3 ⊗ V4 ⊗ V5)') - tA = 3 // 2 * left_polar(tA)[1] - tB = 1 // 5 * left_polar(tB)[1] - tC = rand(T, V1 ⊗ V2, (V3 ⊗ V4 ⊗ V5)') - t = @constinferred sylvester(tA, tB, tC) - @test codomain(t) == V1 ⊗ V2 - @test domain(t) == (V3 ⊗ V4 ⊗ V5)' - @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))) - @test matrix(t) ≈ sylvester(matrix(tA), matrix(tB), matrix(tC)) - end + # TODO schur not defined for GPU arrays + for T in (Float32, ComplexF64), rand_f in [rand] #rand_fs + TestSuite.sylvester_test(rand_f, T, V) end end end diff --git a/test/testsuite/TestSuite.jl b/test/testsuite/TestSuite.jl new file mode 100644 index 000000000..6fa3488e8 --- /dev/null +++ b/test/testsuite/TestSuite.jl @@ -0,0 +1,66 @@ +# Based on the design of GPUArrays.jl + +""" + TestSuite + +Suite of tests that may be used for all packages inheriting from TensorKit. + +""" +module TestSuite + +export zero_f, rand_f, randn_f + +using Test +using TensorKit, TestExtras +using LinearAlgebra, Random, StableRNGs +using Adapt, AMDGPU, CUDA + +const tests = Dict() + +macro testsuite(name, ex) + safe_name = lowercase(replace(replace(name, " " => "_"), "/" => "_")) + fn = Symbol("test_", safe_name) + return quote + $(esc(fn))(AT; eltypes = supported_eltypes(AT, $(esc(fn)))) = $(esc(ex))(AT, eltypes) + @assert !haskey(tests, $name) "testsuite already exists" + tests[$name] = $fn + end +end + +testargs_summary(args...) = string(args) + +const rng = StableRNG(123) +seed_rng!(seed) = Random.seed!(rng, seed) + +zero_f(::Type{<:Vector}) = zeros +zero_f(::Type{<:CuVector}) = CUDA.zeros +zero_f(::Type{<:ROCVector}) = AMDGPU.zeros + +rand_f(::Type{<:Vector}) = rand +rand_f(::Type{<:CuVector}) = cuRAND.rand +rand_f(::Type{<:ROCVector}) = AMDGPU.rand + +randn_f(::Type{<:Vector}) = randn +randn_f(::Type{<:CuVector}) = cuRAND.randn +randn_f(::Type{<:ROCVector}) = AMDGPU.randn + +BLASFloats = (Float32, Float64, ComplexF32, ComplexF64) + +function hasfusiontensor(I::Type{<:Sector}) + try + u = first(allunits(I)) + fusiontensor(u, u, u) + return true + catch e + if e isa MethodError + return false + else + rethrow(e) + end + end +end + +include("tensors/construction.jl") +include("tensors/linalg.jl") + +end diff --git a/test/testsuite/tensors/construction.jl b/test/testsuite/tensors/construction.jl new file mode 100644 index 000000000..37459a04a --- /dev/null +++ b/test/testsuite/tensors/construction.jl @@ -0,0 +1,77 @@ +function basic_tensor_properties(t, W, T::Type, AT::Type) + return @testset "Basic tensor properties $AT" begin + #@test @testinferred(hash(t)) == hash(deepcopy(t)) + @test scalartype(t) == T + @test norm(t) == 0 + @test codomain(t) == W + @test space(t) == (W ← one(W)) + @test domain(t) == one(W) + @test typeof(t) == TensorMap{T, spacetype(t), 5, 0, AT} + end +end + +function basic_blocks_properties(t, W) + bs = @testinferred blocks(t) + (c, b1), state = @testinferred Nothing iterate(bs) + @test c == first(blocksectors(W)) + next = @testinferred Nothing iterate(bs, state) + b2 = @testinferred block(t, first(blocksectors(t))) + @test b1 == b2 + @test eltype(bs) === Pair{typeof(c), typeof(b1)} + @test typeof(b1) === TensorKit.blocktype(t) + return @test typeof(c) === sectortype(t) +end + +function tensor_dict_conversion(t) + return @testset "Tensor Dict conversion" begin + @test adapt(Vector{scalartype(t)}, t) ≈ convert(TensorMap, convert(Dict, t)) + end +end + +function tensor_array_conversion(t, W) + return @testset "Tensor Array conversion" begin + a = @testinferred convert(Array, t) + b = reshape(a, dim(codomain(W)), dim(domain(W))) + @test adapt(Vector{scalartype(t)}, t) ≈ @testinferred TensorMap(a, W) + @test adapt(Vector{scalartype(t)}, t) ≈ @testinferred TensorMap(b, W) + @test t === @testinferred TensorMap(t.data, W) + end +end + +function empty_tensor_array_conversion(t, AT) + return @testset "Empty tensor array conversion" begin + a = convert(Array, t) + @test norm(a) == 0 + end +end + +function real_and_imaginary_parts(t) + return @testset "Real and imaginary parts" begin + tr = @testinferred real(t) + @test scalartype(tr) <: Real + @test real(convert(Array, t)) == convert(Array, tr) + + ti = @testinferred imag(t) + @test scalartype(ti) <: Real + @test imag(convert(Array, t)) == convert(Array, ti) + + tc = @inferred complex(t) + @test scalartype(tc) <: Complex + @test complex(convert(Array, t)) == convert(Array, tc) + + tc2 = @inferred complex(tr, ti) + @test tc2 ≈ tc + end +end + +function tensor_conversion(t) + return @testset "Tensor conversion" begin + @test typeof(convert(TensorMap, t')) == typeof(t) + tc = complex(t) + @test convert(typeof(tc), t) == tc + @test typeof(convert(typeof(tc), t)) == typeof(tc) + @test typeof(convert(typeof(tc), t')) == typeof(tc) + @test Base.promote_typeof(t, tc) == typeof(tc) + @test Base.promote_typeof(tc, t) == typeof(tc + t) + end +end diff --git a/test/testsuite/tensors/linalg.jl b/test/testsuite/tensors/linalg.jl new file mode 100644 index 000000000..71e057bf8 --- /dev/null +++ b/test/testsuite/tensors/linalg.jl @@ -0,0 +1,248 @@ +function basic_linear_algebra(rand_f, T, W) + t = @testinferred rand_f(T, W) + @test scalartype(t) == T + @test space(t) == W + @test space(t') == W' + @test dim(t) == dim(space(t)) + @test codomain(t) == codomain(W) + @test domain(t) == domain(W) + + # blocks for adjoint + bs = @testinferred blocks(t') + (c, b1), state = @testinferred Nothing iterate(bs) + @test c == first(blocksectors(W')) + next = @testinferred Nothing iterate(bs, state) + b2 = @testinferred block(t', first(blocksectors(t'))) + @test b1 == b2 + @test eltype(bs) === Pair{typeof(c), typeof(b1)} + @test typeof(b1) === TensorKit.blocktype(t') + return @test typeof(c) === sectortype(t) +end + +function tensor_norm(rand_f, T, W) + return @testset "Tensor norm" begin + t = @testinferred rand_f(T, W) + @test scalartype(t) == T + @test space(t) == W + @test space(t') == W' + @test dim(t) == dim(space(t)) + @test codomain(t) == codomain(W) + @test domain(t) == domain(W) + + @test isa(@testinferred(norm(t)), real(T)) + @test norm(t)^2 ≈ dot(t, t) + α = rand(T) + @test norm(α * t) ≈ abs(α) * norm(t) + @test norm(t + t, 2) ≈ 2 * norm(t, 2) + @test norm(t + t, 1) ≈ 2 * norm(t, 1) + @test norm(t + t, Inf) ≈ 2 * norm(t, Inf) + p = 3 * rand(Float64) + @test norm(t + t, p) ≈ 2 * norm(t, p) + @test norm(t) ≈ norm(t') + end +end + +function tensor_dot(rand_f, T, W) + return @testset "Tensor dot" begin + t = @testinferred rand_f(T, W) + @test scalartype(t) == T + @test space(t) == W + @test space(t') == W' + @test dim(t) == dim(space(t)) + @test codomain(t) == codomain(W) + @test domain(t) == domain(W) + + t2 = @testinferred rand_f(T, W) + α = rand(T) + β = rand(T) + @test @testinferred(dot(β * t2, α * t)) ≈ conj(β) * α * conj(dot(t, t2)) + @test dot(t2, t) ≈ conj(dot(t, t2)) + @test dot(t2, t) ≈ conj(dot(t2', t')) + @test dot(t2, t) ≈ dot(t', t2') + end +end + +function isomorphism_test(T, AT, V1, V2) + return @testset "Isomorphism" begin + I = TensorKit.sectortype(V1) + if UnitStyle(I) isa SimpleUnit || !isempty(blocksectors(V2 ⊗ V1)) + i1 = @testinferred(isomorphism(AT, V1 ⊗ V2, V2 ⊗ V1)) # can't reverse fusion here when modules are involved + i2 = @testinferred(isomorphism(AT, V2 ⊗ V1, V1 ⊗ V2)) + @test i1 * i2 == @testinferred(id(AT, V1 ⊗ V2)) + @test i2 * i1 == @testinferred(id(AT, V2 ⊗ V1)) + end + end +end + +function isometry_test(T, AT, V1) + return @testset "Isometry" begin + w = @testinferred isometry(AT, V1 ⊗ (rightunitspace(V1) ⊕ rightunitspace(V1)), V1) + @test dim(w) == 2 * dim(V1 ← V1) + @test w' * w == id(AT, V1) + @test w * w' == (w * w')^2 + end +end + +function linalg_via_conversion(rand_f, T, W) + return @testset "Linalg via conversion" begin + t = rand_f(T, W) + t2 = rand_f(T, W) + @test norm(t, 2) ≈ norm(convert(Array, t), 2) + @test dot(t2, t) ≈ dot(convert(Array, t2), convert(Array, t)) + α = rand(T) + @test convert(Array, α * t) ≈ α * convert(Array, t) + @test convert(Array, t + t) ≈ 2 * convert(Array, t) + end +end + +function multiplying_isometries(AT, W1, W2) + return @testset "Multiplication of isometries" begin + t1 = randisometry(AT, W1, W2) + t2 = randisometry(AT, W2 ← W2) + @test isisometric(t1) + @test isunitary(t2) + P = t1 * t1' + @test P * P ≈ P + end +end + +function tensor_multiplication_and_inverse(rand_f, T, W1, W2) + return @testset "Multiplication and inverse -- compatibility" begin + t1 = rand_f(T, W1, W1) + t2 = rand_f(T, W2 ← W2) + t = rand_f(T, W1, W2) + @test t1 * (t1 \ t) ≈ t + @test (t / t2) * t2 ≈ t + @test t1 \ one(t1) ≈ inv(t1) + # TODO pinv doesn't work for GPUArrays yet + if TensorKit.storagetype(t1) <: Vector + @test one(t1) / t1 ≈ pinv(t1) + end + @test_throws SpaceMismatch inv(t) + @test_throws SpaceMismatch t2 \ t + @test_throws SpaceMismatch t / t1 + # TODO pinv doesn't work for GPUArrays yet + if TensorKit.storagetype(t) <: Vector + tp = pinv(t) * t + @test tp ≈ tp * tp + end + end +end + +function tensor_multiplication_and_inverse_conversion(rand_f, T, W1, W2) + return @testset "Multiplication and inverse -- conversion" begin + t1 = rand_f(T, W1 ← W1) + t2 = rand_f(T, W2, W2) + t = rand_f(T, W1 ← 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) + @test reshape(convert(Array, t1 * t), d1, d2) ≈ At1 * At + @test reshape(convert(Array, t1' * t), d1, d2) ≈ At1' * At + @test reshape(convert(Array, t2 * t'), d2, d1) ≈ At2 * At' + @test reshape(convert(Array, t2' * t'), d2, d1) ≈ At2' * At' + + @test reshape(convert(Array, inv(t1)), d1, d1) ≈ inv(At1) + # TODO pinv doesn't work for GPUArrays yet + if TensorKit.storagetype(t1) <: Vector + @test reshape(convert(Array, pinv(t)), d2, d1) ≈ pinv(At) + end + + if !(T == Float32 || T == ComplexF32) + @test reshape(convert(Array, t1 \ t), d1, d2) ≈ At1 \ At + @test reshape(convert(Array, t1' \ t), d1, d2) ≈ At1' \ At + @test reshape(convert(Array, t2 \ t'), d2, d1) ≈ At2 \ At' + @test reshape(convert(Array, t2' \ t'), d2, d1) ≈ At2' \ At' + + @test reshape(convert(Array, t2 / t), d2, d1) ≈ At2 / At + @test reshape(convert(Array, t2' / t), d2, d1) ≈ At2' / At + @test reshape(convert(Array, t1 / t'), d1, d2) ≈ At1 / At' + @test reshape(convert(Array, t1' / t'), d1, d2) ≈ At1' / At' + end + end +end + +function diag_diagm(randn_f, T, W) + return @testset "diag/diagm" begin + t = randn_f(T, W) + d = LinearAlgebra.diag(t) + D = LinearAlgebra.diagm(codomain(t), domain(t), d) + @test LinearAlgebra.isdiag(D) + @test LinearAlgebra.diag(D) == d + end +end + +function tensor_functions(randn_f, T, V1, V2) + return @testset "Tensor functions" begin + W = V1 ⊗ V2 + t = randn_f(T, W, W) + s = dim(W) + expt = @testinferred exp(t) + @test reshape(convert(Array, expt), (s, s)) ≈ + exp(reshape(convert(Array, t), (s, s))) + + @test (@testinferred sqrt(t))^2 ≈ t + @test reshape(convert(Array, sqrt(t^2)), (s, s)) ≈ + sqrt(reshape(convert(Array, t^2), (s, s))) + + @test exp(@testinferred log(expt)) ≈ expt + @test reshape(convert(Array, log(expt)), (s, s)) ≈ + log(reshape(convert(Array, expt), (s, s))) + + @test (@testinferred cos(t))^2 + (@testinferred sin(t))^2 ≈ id(W) + @test (@testinferred tan(t)) ≈ sin(t) / cos(t) + @test (@testinferred cot(t)) ≈ cos(t) / sin(t) + @test (@testinferred cosh(t))^2 - (@testinferred sinh(t))^2 ≈ id(W) + @test (@testinferred tanh(t)) ≈ sinh(t) / cosh(t) + @test (@testinferred coth(t)) ≈ cosh(t) / sinh(t) + + t1 = sin(t) + @test sin(@testinferred asin(t1)) ≈ t1 + t2 = cos(t) + @test cos(@testinferred acos(t2)) ≈ t2 + t3 = sinh(t) + @test sinh(@testinferred asinh(t3)) ≈ t3 + t4 = cosh(t) + @test cosh(@testinferred acosh(t4)) ≈ t4 + t5 = tan(t) + @test tan(@testinferred atan(t5)) ≈ t5 + t6 = cot(t) + @test cot(@testinferred acot(t6)) ≈ t6 + t7 = tanh(t) + @test tanh(@testinferred atanh(t7)) ≈ t7 + t8 = coth(t) + @test coth(@testinferred acoth(t8)) ≈ t8 + t = randn(T, W, V1) # not square + for f in + ( + cos, sin, tan, cot, cosh, sinh, tanh, coth, atan, acot, asinh, + sqrt, log, asin, acos, acosh, atanh, acoth, + ) + @test_throws SpaceMismatch f(t) + end + end +end + +function sylvester_test(rand_f, T, V) + return @testset "Sylvester" begin + V1, V2, V3, V4, V5 = V + tA = rand_f(T, V1 ⊗ V2, V1 ⊗ V2) + tB = rand_f(T, (V3 ⊗ V4 ⊗ V5)', (V3 ⊗ V4 ⊗ V5)') + tA = 3 // 2 * left_polar(tA)[1] + tB = 1 // 5 * left_polar(tB)[1] + tC = rand_f(T, V1 ⊗ V2, (V3 ⊗ V4 ⊗ V5)') + t = @testinferred sylvester(tA, tB, tC) + @test codomain(t) == V1 ⊗ V2 + @test domain(t) == (V3 ⊗ V4 ⊗ V5)' + lnorm = norm(tA * t + t * tB + tC) + rnorm = (norm(tA) + norm(tB) + norm(tC)) + @test lnorm < rnorm * eps(real(T))^(2 / 3) + I = TensorKit.sectortype(first(V)) + if BraidingStyle(I) isa Bosonic && hasfusiontensor(I) + matrix(x) = reshape(convert(Array, x), dim(codomain(x)), dim(domain(x))) + @test matrix(t) ≈ sylvester(matrix(tA), matrix(tB), matrix(tC)) + end + end +end