diff --git a/Project.toml b/Project.toml index 8fc4a3d6b..a7f6177e5 100644 --- a/Project.toml +++ b/Project.toml @@ -12,6 +12,7 @@ HalfIntegers = "f0d1745a-41c9-11e9-1dd9-e5d34d218721" KrylovKit = "0b1a1467-8014-51b9-945f-bf0ae24f4b77" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" LoggingExtras = "e6f89c97-d47a-5376-807f-9c37f3926c36" +MPSKitModels = "ca635005-6f8c-4cd1-b51d-8491250ef2ab" MatrixAlgebraKit = "6c742aac-3347-4629-af66-fc926824e5e4" OhMyThreads = "67456a42-1dca-4109-a031-0a68de7e3ad5" OptimKit = "77e91f04-9b3b-57a6-a776-40b61faaebe0" @@ -20,6 +21,7 @@ Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" RecipesBase = "3cdcf5f2-1ef4-517c-9805-6587b60abb01" TensorKit = "07d1fe3e-3e46-537d-9eac-e9e13d0d4cec" TensorKitManifolds = "11fa318c-39cb-4a83-b1ed-cdc7ba1e3684" +TensorKitTensors = "41b62e7d-e9d1-4e23-942c-79a97adf954b" TensorOperations = "6aa20fa7-93e2-5fca-9bc0-fbd0db3c71a2" VectorInterface = "409d34a3-91d5-4945-b6ec-7529ddf182d8" @@ -35,7 +37,7 @@ projects = ["test", "docs"] [compat] Accessors = "0.1" Adapt = "4" -BlockTensorKit = "0.3.11" +BlockTensorKit = "0.3" Compat = "3.47, 4.10" DocStringExtensions = "0.9.3" HalfIntegers = "1.6.0" @@ -43,13 +45,21 @@ KrylovKit = "0.8.3, 0.9.2, 0.10" LinearAlgebra = "1.6" LoggingExtras = "~1.0" MatrixAlgebraKit = "0.6.5" +MPSKitModels = "0.4" OhMyThreads = "0.7, 0.8" OptimKit = "0.3.1, 0.4" Printf = "1" Random = "1" RecipesBase = "1.1" -TensorKit = "0.16.5" +TensorKit = "0.16, 0.17" TensorKitManifolds = "0.7, 0.8" TensorOperations = "5.5.1" VectorInterface = "0.2, 0.3, 0.4, 0.5" julia = "1.10" + +[sources] +MPSKitModels = {url = "https://github.com/QuantumKitHub/MPSKitModels.jl", rev = "ksh/bump"} +TensorKit = {url = "https://github.com/QuantumKitHub/TensorKit.jl", rev = "main"} +TensorKitManifolds = {url = "https://github.com/QuantumKitHub/TensorKitManifolds.jl", rev = "ksh/bump"} +TensorKitTensors = {url = "https://github.com/QuantumKitHub/TensorKitTensors.jl", rev = "ksh/compat"} +BlockTensorKit = {url = "https://github.com/QuantumKitHub/BlockTensorKit.jl", rev = "ksh/mps"} diff --git a/ext/MPSKitAdaptExt.jl b/ext/MPSKitAdaptExt.jl index 6f0b0c7f2..78f4b41af 100644 --- a/ext/MPSKitAdaptExt.jl +++ b/ext/MPSKitAdaptExt.jl @@ -35,5 +35,7 @@ end MPSKit.JordanMPOTensor(space(W), adapt(to, W.A), adapt(to, W.B), adapt(to, W.C), adapt(to, W.D)) @inline Adapt.adapt_structure(to, mpo::MPOHamiltonian) = MPOHamiltonian(map(x -> adapt(to, x), mpo.W)) +@inline Adapt.adapt_structure(to, ml::MPSKit.Multiline) = MPSKit.Multiline(map(adapt(to), ml.data)) +@inline Adapt.adapt_structure(to, pa::MPSKit.PeriodicArray) = MPSKit.PeriodicArray(map(adapt(to), pa.data)) end diff --git a/src/operators/abstractmpo.jl b/src/operators/abstractmpo.jl index 54ecf0358..94ad49b80 100644 --- a/src/operators/abstractmpo.jl +++ b/src/operators/abstractmpo.jl @@ -99,7 +99,7 @@ Base.:\(α::Number, mpo::AbstractMPO) = scale(mpo, inv(α)) function VectorInterface.scale(mpo::AbstractMPO, α::Number) T = VectorInterface.promote_scale(scalartype(mpo), scalartype(α)) - dst = similar(mpo, T) + dst = similar(mpo, TensorKit.similarstoragetype(storagetype(mpo), T)) return scale!(dst, mpo, α) end @@ -132,7 +132,8 @@ function _fuse_mpo_mpo(O1::MPOTensor, O2::MPOTensor, Fₗ, Fᵣ) T = promote_type(scalartype(O1), scalartype(O2)) V = fuse(left_virtualspace(O2) ⊗ left_virtualspace(O1)) ⊗ physicalspace(O1) ← physicalspace(O2) ⊗ fuse(right_virtualspace(O2) ⊗ right_virtualspace(O1)) - return BraidingTensor{T}(V) + A = promote_storagetype(storagetype(O1), storagetype(O2), T) + return BraidingTensor{T, typeof(V), A}(V) elseif O1 isa BraidingTensor @plansor O′[-1 -2; -3 -4] := Fₗ[-1; 1 2] * O2[1 3; -3 5] * τ[2 -2; 3 4] * conj(Fᵣ[-4; 5 4]) @@ -152,16 +153,21 @@ Compute the mpo tensor that arises from multiplying MPOs. """ function fuse_mul_mpo(O1, O2) TT = promote_type(scalartype(O1), scalartype(O2)) - T = TensorKit.similarstoragetype(storagetype(O1), TT) + T = if O1 isa BraidingTensor + TensorKit.similarstoragetype(storagetype(O2), TT) + else + TensorKit.similarstoragetype(storagetype(O1), TT) + end F_left = fuser(T, left_virtualspace(O2), left_virtualspace(O1)) F_right = fuser(T, right_virtualspace(O2), right_virtualspace(O1)) return _fuse_mpo_mpo(O1, O2, F_left, F_right) end -function fuse_mul_mpo(O1::BraidingTensor, O2::BraidingTensor) - T = promote_type(scalartype(O1), scalartype(O2)) +function fuse_mul_mpo(O1::BraidingTensor{T1, S1, A1}, O2::BraidingTensor{T2, S2, A2}) where {T1, T2, S1, S2, A1, A2} + T = promote_type(T1, T2) + A = TensorKit.promote_storagetype(A1, A2) V = fuse(left_virtualspace(O2) ⊗ left_virtualspace(O1)) ⊗ physicalspace(O1) ← physicalspace(O2) ⊗ fuse(right_virtualspace(O2) ⊗ right_virtualspace(O1)) - return BraidingTensor{T}(V) + return BraidingTensor{T, spacetype(V), A}(V) end function fuse_mul_mpo( O1::AbstractBlockTensorMap{T₁, S, 2, 2}, O2::AbstractBlockTensorMap{T₂, S, 2, 2} @@ -198,7 +204,7 @@ function add_physical_charge(O::BraidingTensor, charge::Sector) auxspace = Vect[typeof(charge)](charge => 1)' V = left_virtualspace(O) ⊗ fuse(physicalspace(O), auxspace) ← fuse(physicalspace(O), auxspace) ⊗ right_virtualspace(O) - return BraidingTensor{scalartype(O)}(V) + return BraidingTensor{scalartype(O), spacetype(V), storagetype(O)}(V) end function add_physical_charge(O::AbstractBlockTensorMap{<:Any, <:Any, 2, 2}, charge::Sector) sectortype(O) == typeof(charge) || throw(SectorMismatch()) diff --git a/src/operators/jordanmpotensor.jl b/src/operators/jordanmpotensor.jl index 8131f90fb..c815e3276 100644 --- a/src/operators/jordanmpotensor.jl +++ b/src/operators/jordanmpotensor.jl @@ -60,7 +60,7 @@ end const JordanMPOTensorMap{T, S, A <: DenseVector{T}} = JordanMPOTensor{ T, S, - Union{TensorMap{T, S, 2, 2, A}, BraidingTensor{T, S}}, + Union{TensorMap{T, S, 2, 2, A}, BraidingTensor{T, S, A}}, TensorMap{T, S, 2, 1, A}, TensorMap{T, S, 1, 2, A}, TensorMap{T, S, 1, 1, A}, @@ -124,7 +124,8 @@ end function jordanmpotensortype(::Type{S}, ::Type{E}) where {S <: VectorSpace, E} TA = tensormaptype(S, 2, 2, E) T = scalartype(TA) - Tτ = BraidingTensor{T, S} + TT = storagetype(TA) + Tτ = BraidingTensor{T, S, TT} TB = tensormaptype(S, 2, 1, E) TC = tensormaptype(S, 1, 2, E) TD = tensormaptype(S, 1, 1, E) @@ -135,6 +136,10 @@ function jordanmpotensortype(::Type{O}) where {O <: AbstractTensorMap} end Base.similar(W::JordanMPOTensor, ::Type{TorA}) where {TorA} = jordanmpotensortype(spacetype(W), TorA)(undef, space(W)) +Base.similar(W::JordanMPOTensor, V::VectorSpace) = + jordanmpotensortype(spacetype(V), storagetype(W))(undef, V) +Base.similar(W::JordanMPOTensor, V::VectorSpace, ::Type{TorA}) where {TorA} = + jordanmpotensortype(spacetype(V), TorA)(undef, V) # Properties # ---------- @@ -172,7 +177,7 @@ BlockTensorKit.issparse(W::JordanMPOTensor) = true # Converters # ---------- function BlockTensorKit.SparseBlockTensorMap(W::JordanMPOTensor) - τ = BraidingTensor{scalartype(W)}(eachspace(W)[1]) + τ = BraidingTensor{scalartype(W), spacetype(eachspace(W)[1]), storagetype(W)}(eachspace(W)[1]) W′ = SparseBlockTensorMap{AbstractTensorMap{scalartype(W), spacetype(W), 2, 2}}( undef_blocks, space(W) ) @@ -205,7 +210,8 @@ end for f in (:real, :complex) @eval function Base.$f(W::JordanMPOTensor) E = $f(scalartype(W)) - W′ = JordanMPOTensor{E}(undef, space(W)) + TE = TensorKit.similarstoragetype(TensorKit.storagetype(W), E) + W′ = similar(W, TE) for (I, v) in nonzero_pairs(W.A) W′.A[I] = $f(v) end @@ -232,7 +238,10 @@ end j = I[4] if (size(W, 4) > 1 && i == 1 && j == 1) || (size(W, 1) > 1 && i == size(W, 1) && j == size(W, 4)) - return BraidingTensor{scalartype(W)}(eachspace(W)[1]) + T = scalartype(W) + TA = storagetype(W) + S = spacetype(eachspace(W)[1]) + return BraidingTensor{T, S, TA}(eachspace(W)[1]) elseif i == 1 && j == size(W, 4) return insertrightunit(insertleftunit(only(W.D), 1), 3) elseif i == 1 @@ -242,7 +251,7 @@ end elseif 1 < i < size(W, 1) && 1 < j < size(W, 4) return W.A[i - 1, 1, 1, j - 1] else - return zeros(scalartype(W), eachspace(W)[i, 1, 1, j]) + return zeros(storagetype(W), eachspace(W)[i, 1, 1, j]) end end @@ -357,7 +366,7 @@ function add_physical_charge(O::JordanMPOTensor, charge::Sector) Vdst = left_virtualspace(O) ⊗ fuse(physicalspace(O), auxspace) ← fuse(physicalspace(O), auxspace) ⊗ right_virtualspace(O) - Odst = JordanMPOTensor{scalartype(O)}(undef, Vdst) + Odst = similar(O, Vdst) for (I, v) in nonzero_pairs(O) Odst[I] = add_physical_charge(v, charge) end diff --git a/src/operators/mpo.jl b/src/operators/mpo.jl index 8630a0d56..7bf0d16ca 100644 --- a/src/operators/mpo.jl +++ b/src/operators/mpo.jl @@ -62,7 +62,7 @@ eachsite(mpo::InfiniteMPO) = PeriodicArray(eachindex(mpo)) function Base.similar(mpo::MPO{<:MPOTensor}, ::Type{O}, L::Int) where {O <: MPOTensor} return MPO(similar(parent(mpo), O, L)) end -function Base.similar(mpo::MPO, ::Type{T}) where {T <: Number} +function Base.similar(mpo::MPO, ::Type{T}) where {T <: Union{Number, DenseVector}} return MPO(similar.(parent(mpo), T)) end diff --git a/src/operators/mpohamiltonian.jl b/src/operators/mpohamiltonian.jl index daf7eb6a5..1ddf60a4a 100644 --- a/src/operators/mpohamiltonian.jl +++ b/src/operators/mpohamiltonian.jl @@ -59,19 +59,19 @@ function InfiniteMPOHamiltonian(Ws::AbstractVector{O}) where {O <: MPOTensor} end """ - FiniteMPOHamiltonian(Ws::Vector{<:Matrix}) + FiniteMPOHamiltonian(Ws::Vector{<:AbstractMatrix}) Create a `FiniteMPOHamiltonian` from a vector of matrices, such that `Ws[i][j, k]` represents the operator at site `i`, left level `j` and right level `k`. Here, the entries can be either `MPOTensor`, `Missing` or `Number`. """ -function FiniteMPOHamiltonian(Ws::Vector{<:Matrix}) +function FiniteMPOHamiltonian(Ws::Vector{<:AbstractMatrix}) T = promote_type(_split_mpoham_types.(Ws)...) W = jordanmpotensortype(T) return FiniteMPOHamiltonian{W}(Ws) end -function FiniteMPOHamiltonian{O}(W_mats::Vector{<:Matrix}) where {O <: JordanMPOTensor} - T = scalartype(O) +function FiniteMPOHamiltonian{O}(W_mats::Vector{<:AbstractMatrix}) where {O <: JordanMPOTensor} + T = storagetype(O) L = length(W_mats) # initialize sumspaces S = spacetype(O) @@ -140,7 +140,8 @@ function FiniteMPOHamiltonian{O}(W_mats::Vector{<:Matrix}) where {O <: JordanMPO if v isa MPOTensor W[I] = v elseif !iszero(v) - τ = BraidingTensor{T}(eachspace(W)[I]) + A = TensorKit.similarstoragetype(T, eltype(T)) + τ = BraidingTensor{eltype(T), spacetype(eachspace(W)[I]), A}(eachspace(W)[I]) W[I] = isone(v) ? τ : τ * v end end @@ -157,12 +158,12 @@ Create a `InfiniteMPOHamiltonian` from a vector of matrices, such that `Ws[i][j, the the operator at site `i`, left level `j` and right level `k`. Here, the entries can be either `MPOTensor`, `Missing` or `Number`. """ -function InfiniteMPOHamiltonian(Ws::Vector{<:Matrix}) +function InfiniteMPOHamiltonian(Ws::Vector{<:AbstractMatrix}) T = promote_type(_split_mpoham_types.(Ws)...) TW = jordanmpotensortype(T) return InfiniteMPOHamiltonian{TW}(Ws) end -function InfiniteMPOHamiltonian{O}(W_mats::Vector{<:Matrix}) where {O <: MPOTensor} +function InfiniteMPOHamiltonian{O}(W_mats::Vector{<:AbstractMatrix}) where {O <: MPOTensor} # InfiniteMPOHamiltonian only works for square matrices: for W_mat in W_mats size(W_mat, 1) == size(W_mat, 2) || @@ -172,7 +173,7 @@ function InfiniteMPOHamiltonian{O}(W_mats::Vector{<:Matrix}) where {O <: MPOTens throw(ArgumentError("matrices should have the same size")) nlvls = size(W_mats[1], 1) - T = scalartype(O) + T = storagetype(O) L = length(W_mats) # initialize sumspaces S = spacetype(O) @@ -261,7 +262,8 @@ function InfiniteMPOHamiltonian{O}(W_mats::Vector{<:Matrix}) where {O <: MPOTens if v isa MPOTensor W[I] = v elseif !iszero(v) - τ = BraidingTensor{T}(eachspace(W)[I]) + A = similarstoragetype(T, eltype(T)) + τ = BraidingTensor{eltype(T), typeof(eachspace(W)[I]), A}(eachspace(W)[I]) W[I] = isone(v) ? τ : τ * v end end @@ -477,7 +479,8 @@ function FiniteMPOHamiltonian(lattice::AbstractArray{<:VectorSpace}, local_opera key_R = key_R′ == 0 ? length(virtualsumspaces[site + 1]) : key_R′ O[key_L, 1, 1, key_R] += if o isa Number iszero(o) && continue - τ = BraidingTensor{scalartype(TW)}(eachspace(O)[key_L, 1, 1, key_R]) + S = spacetype(eachspace(O)[key_L, 1, 1, key_R]) + τ = BraidingTensor{scalartype(TW), S, storagetype(TW)}(eachspace(O)[key_L, 1, 1, key_R]) isone(o) ? τ : τ * o else o @@ -504,10 +507,8 @@ function InfiniteMPOHamiltonian(lattice′::AbstractArray{<:VectorSpace}, local_ end # partial sort by interaction range - local_mpos = sort!( - map(Base.Fix1(instantiate_operator, lattice), collect(local_operators)); - by = x -> length(x[1]) - ) + unsorted_mpos = map(Base.Fix1(instantiate_operator, lattice), [local_operators...]) + local_mpos = sort!(unsorted_mpos; by = x -> length(x[1])) for (sites, local_mpo) in local_mpos local key_R # trick to define key_R before the first iteration @@ -600,7 +601,8 @@ function InfiniteMPOHamiltonian(lattice′::AbstractArray{<:VectorSpace}, local_ key_R = key_R′ == 0 ? length(virtualspaces[site]) : key_R′ O[key_L, 1, 1, key_R] += if o isa Number iszero(o) && continue - τ = BraidingTensor{scalartype(TW)}(eachspace(O)[key_L, 1, 1, key_R]) + S = typeof(eachspace(O)[key_L, 1, 1, key_R]) + τ = BraidingTensor{scalartype(TW), S, storagetype(TW)}(eachspace(O)[key_L, 1, 1, key_R]) isone(o) ? τ : τ * o else o @@ -703,8 +705,8 @@ end function Base.similar(H::MPOHamiltonian, ::Type{O}, L::Int) where {O <: MPOTensor} return MPOHamiltonian(similar(parent(H), O, L)) end -function Base.similar(H::MPOHamiltonian, ::Type{T}) where {T <: Number} - return MPOHamiltonian(similar.(parent(H), T)) +function Base.similar(H::MPOHamiltonian, ::Type{TorA}) where {TorA <: Union{Number, DenseVector}} + return MPOHamiltonian(similar.(parent(H), TorA)) end # Linear Algebra @@ -729,7 +731,7 @@ function Base.:+( ⊞(Vtriv, right_virtualspace(A), Vtriv) V = Vleft ⊗ physicalspace(A) ← physicalspace(A) ⊗ Vright - H[i] = eltype(H)(V, A, B, C, D) + H[i] = JordanMPOTensor(V, A, B, C, D) end return FiniteMPOHamiltonian(H) end @@ -751,7 +753,7 @@ function Base.:+( Vright = ⊞(Vtriv, right_virtualspace(A), Vtriv) V = Vleft ⊗ physicalspace(A) ← physicalspace(A) ⊗ Vright - H[i] = eltype(H)(V, A, B, C, D) + H[i] = JordanMPOTensor(V, A, B, C, D) end return InfiniteMPOHamiltonian(H) end @@ -821,7 +823,7 @@ function Base.:*(H::FiniteMPOHamiltonian, mps::FiniteMPS) A, tensormaptype( spacetype(mps), numout(eltype(mps)), numin(eltype(mps)), - promote_type(scalartype(H), scalartype(mps)) + promote_type(storagetype(H), storagetype(mps)) ) ) # left to middle diff --git a/src/operators/ortho.jl b/src/operators/ortho.jl index b48d12ef1..b5c5ef180 100644 --- a/src/operators/ortho.jl +++ b/src/operators/ortho.jl @@ -11,7 +11,14 @@ function left_canonicalize!( d = sqrt(dim(P)) # orthogonalize second column against first - WI = removeunit(W[1, 1, 1, 1], 1) + Wi = W[1, 1, 1, 1] + if Wi isa BraidingTensor + Wi′ = removeunit(Wi, 1) + WI = similar(Wi′, TensorKit.storagetype(H)) + copy!(WI, Wi′) + else + WI = removeunit(Wi, 1) + end @plansor t[l; r] := conj(WI[p; p' l]) * W.C[p; p' r] # TODO: the following is currently broken due to a TensorKit bug # @plansor C′[p; p' r] := W.C[p; p' r] - WI[p; p' l] * t[l; r] @@ -99,7 +106,14 @@ function right_canonicalize!( d = sqrt(dim(P)) # orthogonalize second row against last - WI = removeunit(W[end, 1, 1, end], 4) + Wi = W[end, 1, 1, end] + if Wi isa BraidingTensor + Wi′ = removeunit(Wi, 4) + WI = similar(Wi′, TensorKit.storagetype(H)) + copy!(WI, Wi′) + else + WI = removeunit(Wi, 4) + end @plansor t[l; r] := conj(WI[r p; p']) * W.B[l p; p'] # TODO: the following is currently broken due to a TensorKit bug # @plansor B′[l p; p'] := W.B[l p; p'] - WI[r p; p'] * t[l; r] @@ -124,7 +138,8 @@ function right_canonicalize!( end H[i] = JordanMPOTensor(V ⊗ P ← domain(W), Q1, Q2, W.C, W.D) else - tmp = transpose(cat(insertleftunit(B′, 4), W.A; dims = 4), ((1,), (3, 4, 2))) + B′′ = insertleftunit(B′, 4) + tmp = transpose(cat(B′′, W.A; dims = 4), ((1,), (3, 4, 2))) R, Q = right_orth!(tmp; alg) if dim(R) == 0 V = _rightunit ⊞ _rightunit diff --git a/test/Project.toml b/test/Project.toml index da4cf3f70..569eefa1b 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -24,7 +24,7 @@ MPSKit = {path = ".."} [compat] Aqua = "0.8.9" -CUDA = "5.9" +CUDA = "5, 6" Combinatorics = "1" ParallelTestRunner = "2" Plots = "1.40" @@ -32,4 +32,4 @@ Pkg = "1" TensorKitTensors = "0.2" Test = "1" TestExtras = "0.3" -cuTENSOR = "2.3" +cuTENSOR = "1, 6" diff --git a/test/gpu/cuda/operators.jl b/test/gpu/cuda/operators.jl index cf42a4e7b..030fd74a0 100644 --- a/test/gpu/cuda/operators.jl +++ b/test/gpu/cuda/operators.jl @@ -83,3 +83,189 @@ using Adapt, CUDA, cuTENSOR @test dot(mpomps₁, mpomps₁) ≈ dot(mpo₁, mpo₁) end + +@testset "Finite CuMPOHamiltonian T ($T), V ($(spacetype(V)))" for T in (Float64, ComplexF64), V in (ℂ^2, U1Space(-1 => 1, 0 => 1, 1 => 1)) + L = 3 + lattice = fill(V, L) + O₁ = randn(T, V, V) + O₁ += O₁' + E = id(CuVector{T, CUDA.DeviceMemory}, domain(O₁)) + O₂ = randn(T, V^2 ← V^2) + O₂ += O₂' + + dO₁ = adapt(CuVector{T, CUDA.DeviceMemory}, O₁) + dO₂ = adapt(CuVector{T, CUDA.DeviceMemory}, O₂) + hH1 = FiniteMPOHamiltonian(lattice, i => O₁ for i in 1:L) + hH2 = FiniteMPOHamiltonian(lattice, (i, i + 1) => O₂ for i in 1:(L - 1)) + hH3 = FiniteMPOHamiltonian(lattice, 1 => O₁, (2, 3) => O₂, (1, 3) => O₂) + H1 = adapt(CuVector{T, CUDA.DeviceMemory}, hH1) + H2 = adapt(CuVector{T, CUDA.DeviceMemory}, hH2) + H3 = adapt(CuVector{T, CUDA.DeviceMemory}, hH3) + @test TensorKit.storagetype(H1) == CuVector{T, CUDA.DeviceMemory} + @test TensorKit.storagetype(H2) == CuVector{T, CUDA.DeviceMemory} + @test TensorKit.storagetype(H3) == CuVector{T, CUDA.DeviceMemory} + + @test scalartype(H1) == scalartype(H2) == scalartype(H3) == T + if !(T <: Complex) + for H in (H1, H2, H3) + Hc = @constinferred complex(H) + @test scalartype(Hc) == complex(T) + @test TensorKit.storagetype(Hc) == CuVector{complex(T), CUDA.DeviceMemory} + end + end + + # check if constructor works by converting back to tensormap + hH1_tm = convert(TensorMap, hH1) + H1_tm = convert(TensorMap, H1) + @test TensorKit.storagetype(H1_tm) == CuVector{T, CUDA.DeviceMemory} + operators = vcat(fill(E, L - 1), dO₁) + @test H1_tm ≈ mapreduce(+, 1:L) do i + return reduce(⊗, circshift(operators, i)) + end + operators = vcat(fill(E, L - 2), dO₂) + @test convert(TensorMap, H2) ≈ mapreduce(+, 1:(L - 1)) do i + return reduce(⊗, circshift(operators, i)) + end + @test convert(TensorMap, H3) ≈ + dO₁ ⊗ E ⊗ E + E ⊗ dO₂ + permute(dO₂ ⊗ E, ((1, 3, 2), (4, 6, 5))) + + # check if adding terms on the same site works + single_terms = Iterators.flatten(Iterators.repeated((i => O₁ / 2 for i in 1:L), 2)) + H4 = adapt(CuVector{T, CUDA.DeviceMemory}, FiniteMPOHamiltonian(lattice, single_terms)) + @test TensorKit.storagetype(H4) == CuVector{T, CUDA.DeviceMemory} + @test H4 ≈ H1 atol = 1.0e-6 + double_terms = Iterators.flatten( + Iterators.repeated(((i, i + 1) => O₂ / 2 for i in 1:(L - 1)), 2) + ) + H5 = adapt(CuVector{T, CUDA.DeviceMemory}, FiniteMPOHamiltonian(lattice, double_terms)) + @test TensorKit.storagetype(H5) == CuVector{T, CUDA.DeviceMemory} + @test H5 ≈ H2 atol = 1.0e-6 + + # test linear algebra + @test H1 ≈ + adapt(CuVector{T, CUDA.DeviceMemory}, FiniteMPOHamiltonian(lattice, 1 => O₁)) + + adapt(CuVector{T, CUDA.DeviceMemory}, FiniteMPOHamiltonian(lattice, 2 => O₁)) + + adapt(CuVector{T, CUDA.DeviceMemory}, FiniteMPOHamiltonian(lattice, 3 => O₁)) + @test TensorKit.storagetype(H1) == CuVector{T, CUDA.DeviceMemory} + + H1′a = 0.8 * H1 + @test TensorKit.storagetype(H1′a) == CuVector{T, CUDA.DeviceMemory} + H1′b = 0.2 * H1 + @test TensorKit.storagetype(H1′b) == CuVector{T, CUDA.DeviceMemory} + H1′ = H1′a + H1′b + @test TensorKit.storagetype(H1′) == CuVector{T, CUDA.DeviceMemory} + @test H1′ ≈ H1 atol = 1.0e-6 + @test convert(TensorMap, H1 + H2) ≈ convert(TensorMap, H1) + convert(TensorMap, H2) atol = 1.0e-6 + # failing due to truncation problem in TensorKit? + # H1_trunc = changebonds(H1, SvdCut(; trscheme = truncrank(0))) + # @test H1_trunc ≈ H1 + # @test all(left_virtualspace(H1_trunc) .== left_virtualspace(H1)) + + # test dot and application + hstate = rand(T, prod(lattice)) + state = adapt(CuVector{T, CUDA.DeviceMemory}, hstate) + @test TensorKit.storagetype(state) == CuVector{T, CUDA.DeviceMemory} + hmps = FiniteMPS(hstate) + mps = adapt(CuVector{T, CUDA.DeviceMemory}, hmps) + @test TensorKit.storagetype(mps) == CuVector{T, CUDA.DeviceMemory} + @test norm(mps) ≈ norm(state) + @test norm(H1) ≈ norm(H1_tm) + + @test TensorKit.storagetype(H1) == CuVector{T, CUDA.DeviceMemory} + hH1mps = hH1 * hmps + H1mps = H1 * mps + @test TensorKit.storagetype(H1mps) == CuVector{T, CUDA.DeviceMemory} + hH1tmstate = hH1_tm * hstate + H1tmstate = H1_tm * state + @test TensorKit.storagetype(H1tmstate) == CuVector{T, CUDA.DeviceMemory} + @test norm(H1mps) ≈ norm(H1tmstate) + + H1mpstm = convert(TensorMap, H1mps) + @test TensorKit.storagetype(H1mpstm) == CuVector{T, CUDA.DeviceMemory} + @test H1mpstm ≈ H1tmstate + @test H1 * state ≈ H1tmstate + + H2mps = H2 * mps + hH2mps = hH2 * hmps + @test TensorKit.storagetype(H2mps) == CuVector{T, CUDA.DeviceMemory} + @test dot(mps, H2, mps) ≈ dot(mps, H2mps) + @test dot(hmps, hH2mps) ≈ dot(mps, H2mps) + @test dot(mps, H2, mps) ≈ dot(hmps, hH2mps) + + # test constructor from dictionary with mixed linear and Cartesian lattice indices as keys + grid = square = fill(V, 3, 3) + + local_operators = Dict((I,) => O₁ for I in eachindex(grid)) + I_vertical = CartesianIndex(1, 0) + vertical_operators = Dict( + (I, I + I_vertical) => O₂ for I in eachindex(IndexCartesian(), square) if I[1] < size(square, 1) + ) + I_horizontal = CartesianIndex(0, 1) + horizontal_operators = Dict( + (I, I + I_horizontal) => O₂ for I in eachindex(IndexCartesian(), square) if I[2] < size(square, 1) + ) + operators = merge(local_operators, vertical_operators, horizontal_operators) + H4 = adapt(CuVector{T, CUDA.DeviceMemory}, FiniteMPOHamiltonian(grid, operators)) + @test TensorKit.storagetype(H4) == CuVector{T, CUDA.DeviceMemory} + + @test H4 ≈ + adapt(CuVector{T, CUDA.DeviceMemory}, FiniteMPOHamiltonian(grid, local_operators)) + + adapt(CuVector{T, CUDA.DeviceMemory}, FiniteMPOHamiltonian(grid, vertical_operators)) + + adapt(CuVector{T, CUDA.DeviceMemory}, FiniteMPOHamiltonian(grid, horizontal_operators)) atol = 1.0e-4 + @test TensorKit.storagetype(H4) == CuVector{T, CUDA.DeviceMemory} + + H4′ = H4 / 3 + 2H4 / 3 + @test TensorKit.storagetype(H4′) == CuVector{T, CUDA.DeviceMemory} + H5 = changebonds(H4′, SvdCut(; trscheme = trunctol(; atol = 1.0e-12))) + @test TensorKit.storagetype(H5) == CuVector{T, CUDA.DeviceMemory} + psi = adapt(CuArray, FiniteMPS(physicalspace(H5), V ⊕ rightunitspace(V))) + @test expectation_value(psi, H4) ≈ expectation_value(psi, H5) +end + +@testset "CuInfiniteMPOHamiltonian $(sectortype(pspace))" for (pspace, Dspace) in zip(pspaces, vspaces) + # generate a 1-2-3 body interaction + operators = ntuple(3) do i + O = rand(ComplexF64, pspace^i, pspace^i) + return O += O' + end + + H1 = adapt(CuVector{ComplexF64, CUDA.DeviceMemory}, InfiniteMPOHamiltonian(operators[1])) + H2 = adapt(CuVector{ComplexF64, CUDA.DeviceMemory}, InfiniteMPOHamiltonian(operators[2])) + H3 = adapt(CuVector{ComplexF64, CUDA.DeviceMemory}, repeat(InfiniteMPOHamiltonian(operators[3]), 2)) + + @test TensorKit.storagetype(H1) == CuVector{ComplexF64, CUDA.DeviceMemory} + @test TensorKit.storagetype(H2) == CuVector{ComplexF64, CUDA.DeviceMemory} + @test TensorKit.storagetype(H3) == CuVector{ComplexF64, CUDA.DeviceMemory} + # make a teststate to measure expectation values for + ψ1 = adapt(CuVector{ComplexF64, CUDA.DeviceMemory}, InfiniteMPS([pspace], [Dspace])) + ψ2 = adapt(CuVector{ComplexF64, CUDA.DeviceMemory}, InfiniteMPS([pspace, pspace], [Dspace, Dspace])) + @test TensorKit.storagetype(ψ1) == CuVector{ComplexF64, CUDA.DeviceMemory} + @test TensorKit.storagetype(ψ2) == CuVector{ComplexF64, CUDA.DeviceMemory} + + e1 = expectation_value(ψ1, H1) + e2 = expectation_value(ψ1, H2) + + H1 = 2 * H1 + @test TensorKit.storagetype(H1) == CuVector{ComplexF64, CUDA.DeviceMemory} + H1 -= [1] + @test TensorKit.storagetype(H1) == CuVector{ComplexF64, CUDA.DeviceMemory} + @test e1 * 2 - 1 ≈ expectation_value(ψ1, H1) atol = 1.0e-10 + + H1 = H1 + H2 + @test TensorKit.storagetype(H1) == CuVector{ComplexF64, CUDA.DeviceMemory} + @test e1 * 2 + e2 - 1 ≈ expectation_value(ψ1, H1) atol = 1.0e-10 + + H1 = repeat(H1, 2) + @test TensorKit.storagetype(H1) == CuVector{ComplexF64, CUDA.DeviceMemory} + + e1 = expectation_value(ψ2, H1) + e3 = expectation_value(ψ2, H3) + + @test e1 + e3 ≈ expectation_value(ψ2, H1 + H3) atol = 1.0e-10 + + H4 = H1 + H3 + @test TensorKit.storagetype(H4) == CuVector{ComplexF64, CUDA.DeviceMemory} + h4 = H4 * H4 + @test TensorKit.storagetype(h4) == CuVector{ComplexF64, CUDA.DeviceMemory} + @test real(expectation_value(ψ2, H4)) >= 0 +end diff --git a/test/gpu/cuda/states.jl b/test/gpu/cuda/states.jl index 39627ac41..a37712209 100644 --- a/test/gpu/cuda/states.jl +++ b/test/gpu/cuda/states.jl @@ -4,7 +4,7 @@ using MPSKit: GeometryStyle, InfiniteChainStyle, TransferMatrix using TensorKit using TensorKit: ℙ using Adapt, CUDA, cuTENSOR - +#= @testset "CuMPS ($(sectortype(D)), $elt)" for (D, d, elt) in [(ℙ^10, ℙ^2, ComplexF64), (Rep[U₁](1 => 3), Rep[U₁](0 => 1), ComplexF64)] tol = Float64(eps(real(elt)) * 100) @@ -50,3 +50,44 @@ using Adapt, CUDA, cuTENSOR @test norm(2 * ψ + ψ - 3 * ψ) ≈ 0.0 atol = sqrt(eps(real(ComplexF64))) end + +@testset "CuMultilineMPS ($(sectortype(D)), $elt)" for (D, d, elt) in + [(ℙ^10, ℙ^2, ComplexF64), (Rep[U₁](1 => 3), Rep[U₁](0 => 1), ComplexF32)] + tol = Float64(eps(real(elt)) * 100) + ψ = adapt( + CuVector{elt, CUDA.DeviceMemory}, MultilineMPS( + [ + rand(elt, D * d, D) rand(elt, D * d, D) + rand(elt, D * d, D) rand(elt, D * d, D) + ]; tol + ) + ) + + @test GeometryStyle(typeof(ψ)) == InfiniteChainStyle() + @test GeometryStyle(ψ) == InfiniteChainStyle() + @test TensorKit.storagetype(ψ) == CuVector{elt, CUDA.DeviceMemory} + @test TensorKit.sectortype(ψ) == sectortype(D) + + @test !isfinite(typeof(ψ)) + + @test physicalspace(ψ) == fill(d, 2, 2) + @test all(x -> x ≾ D, left_virtualspace(ψ)) + @test all(x -> x ≾ D, right_virtualspace(ψ)) + + for i in 1:size(ψ, 1), j in 1:size(ψ, 2) + @plansor difference[-1 -2; -3] := ψ.AL[i, j][-1 -2; 1] * ψ.C[i, j][1; -3] - + ψ.C[i, j - 1][-1; 1] * ψ.AR[i, j][1 -2; -3] + @test norm(difference, Inf) < tol * 10 + + @test l_LL(ψ, i, j) * TransferMatrix(ψ.AL[i, j], ψ.AL[i, j]) ≈ l_LL(ψ, i, j + 1) + @test l_LR(ψ, i, j) * TransferMatrix(ψ.AL[i, j], ψ.AR[i, j]) ≈ l_LR(ψ, i, j + 1) + @test l_RL(ψ, i, j) * TransferMatrix(ψ.AR[i, j], ψ.AL[i, j]) ≈ l_RL(ψ, i, j + 1) + @test l_RR(ψ, i, j) * TransferMatrix(ψ.AR[i, j], ψ.AR[i, j]) ≈ l_RR(ψ, i, j + 1) + + @test TransferMatrix(ψ.AL[i, j], ψ.AL[i, j]) * r_LL(ψ, i, j) ≈ r_LL(ψ, i, j + 1) + @test TransferMatrix(ψ.AL[i, j], ψ.AR[i, j]) * r_LR(ψ, i, j) ≈ r_LR(ψ, i, j + 1) + @test TransferMatrix(ψ.AR[i, j], ψ.AL[i, j]) * r_RL(ψ, i, j) ≈ r_RL(ψ, i, j + 1) + @test TransferMatrix(ψ.AR[i, j], ψ.AR[i, j]) * r_RR(ψ, i, j) ≈ r_RR(ψ, i, j + 1) + end +end +=# diff --git a/test/runtests.jl b/test/runtests.jl index 81d497c5e..b2b237cb7 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -16,7 +16,7 @@ is_buildkite && filter!(startswith("gpu") ∘ first, testsuite) # only run CUDA/cuTENSOR if available using CUDA, cuTENSOR -(CUDA.functional() && cuTENSOR.has_cutensor()) || +(CUDA.functional() && cuTENSOR.functional()) || filter!(!(startswith("gpu/cuda") ∘ first), testsuite) # parse arguments