From 3ec800f29defca030575ae332bc7c6bb5a739a97 Mon Sep 17 00:00:00 2001 From: Lukas Devos Date: Sun, 22 Feb 2026 17:47:40 -0500 Subject: [PATCH 01/14] Revert "Adapt extension (#384)" This reverts commit 2db4cb1abd1132c9d5e10990a7fc0fd06ba08c6b. --- Project.toml | 10 +----- ext/MPSKitAdaptExt.jl | 57 -------------------------------- src/operators/jordanmpotensor.jl | 12 +++---- src/states/abstractmps.jl | 1 - src/states/finitemps.jl | 3 ++ test/operators/mpo.jl | 26 +-------------- test/operators/mpohamiltonian.jl | 35 +------------------- 7 files changed, 12 insertions(+), 132 deletions(-) delete mode 100644 ext/MPSKitAdaptExt.jl diff --git a/Project.toml b/Project.toml index 9b39e662f..0ba745a7f 100644 --- a/Project.toml +++ b/Project.toml @@ -23,15 +23,8 @@ TensorKitManifolds = "11fa318c-39cb-4a83-b1ed-cdc7ba1e3684" TensorOperations = "6aa20fa7-93e2-5fca-9bc0-fbd0db3c71a2" VectorInterface = "409d34a3-91d5-4945-b6ec-7529ddf182d8" -[weakdeps] -Adapt = "79e6a3ab-5dfb-504d-930d-738a2a938a0e" - -[extensions] -MPSKitAdaptExt = "Adapt" - [compat] Accessors = "0.1" -Adapt = "4" Aqua = "0.8.9" BlockTensorKit = "0.3.4" Combinatorics = "1" @@ -60,7 +53,6 @@ VectorInterface = "0.2, 0.3, 0.4, 0.5" julia = "1.10" [extras] -Adapt = "79e6a3ab-5dfb-504d-930d-738a2a938a0e" Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595" Combinatorics = "861a8166-3701-5b0c-9a16-15d98fcdc6aa" ParallelTestRunner = "d3525ed8-44d0-4b2c-a655-542cee43accc" @@ -71,4 +63,4 @@ Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" TestExtras = "5ed8adda-3752-4e41-b88a-e8b09835ee3a" [targets] -test = ["Aqua", "Adapt", "Pkg", "Test", "TestExtras", "Plots", "Combinatorics", "ParallelTestRunner", "TensorKitTensors"] +test = ["Aqua", "Pkg", "Test", "TestExtras", "Plots", "Combinatorics", "ParallelTestRunner", "TensorKitTensors"] diff --git a/ext/MPSKitAdaptExt.jl b/ext/MPSKitAdaptExt.jl deleted file mode 100644 index c6e16fd76..000000000 --- a/ext/MPSKitAdaptExt.jl +++ /dev/null @@ -1,57 +0,0 @@ -module MPSKitAdaptExt - -using TensorKit: space, spacetype -using MPSKit -using BlockTensorKit: nonzero_pairs -using Adapt - -function Adapt.adapt_structure(to, mps::FiniteMPS) - ad = adapt(to) - adapt_not_missing(x) = ismissing(x) ? x : ad(x) - - TA = Base.promote_op(ad, MPSKit.site_type(mps)) - TB = Base.promote_op(ad, MPSKit.bond_type(mps)) - - ALs = map!(adapt_not_missing, similar(mps.ALs, Union{Missing, TA}), mps.ALs) - ARs = map!(adapt_not_missing, similar(mps.ARs, Union{Missing, TA}), mps.ARs) - ACs = map!(adapt_not_missing, similar(mps.ACs, Union{Missing, TA}), mps.ACs) - Cs = map!(adapt_not_missing, similar(mps.Cs, Union{Missing, TB}), mps.Cs) - - return FiniteMPS{TA, TB}(ALs, ARs, ACs, Cs) -end - -function Adapt.adapt_structure(to, mps::InfiniteMPS) - ad = adapt(to) - AL = map(ad, mps.AL) - AR = map(ad, mps.AR) - C = map(ad, mps.C) - AC = map(ad, mps.AC) - - return InfiniteMPS{eltype(AL), eltype(C)}(AL, AR, C, AC) -end - -Adapt.adapt_structure(to, mpo::MPO) = MPO(map(adapt(to), mpo.O)) - -function Adapt.adapt_structure(::Type{TorA}, W::MPSKit.JordanMPOTensor) where {TorA <: Union{Number, DenseVector{<:Number}}} - TT = MPSKit.jordanmpotensortype(spacetype(W), TorA) - W′ = TT(undef, space(W)) - ad = adapt(TorA) - - for (k, v) in nonzero_pairs(W.A) - W′.A[k] = ad(v) - end - for (k, v) in nonzero_pairs(W.B) - W′.B[k] = ad(v) - end - for (k, v) in nonzero_pairs(W.C) - W′.C[k] = ad(v) - end - for (k, v) in nonzero_pairs(W.D) - W′.D[k] = ad(v) - end - - return W′ -end -Adapt.adapt_structure(to, mpo::MPOHamiltonian) = MPOHamiltonian(map(adapt(to), mpo.W)) - -end diff --git a/src/operators/jordanmpotensor.jl b/src/operators/jordanmpotensor.jl index 02f36c987..4a3dbe798 100644 --- a/src/operators/jordanmpotensor.jl +++ b/src/operators/jordanmpotensor.jl @@ -121,12 +121,12 @@ function JordanMPOTensor(W::SparseBlockTensorMap{TT, E, S, 2, 2}) where {TT, E, ) end -function jordanmpotensortype(::Type{S}, ::Type{TorA}) where {S <: VectorSpace, TorA} - TA = Union{tensormaptype(S, 2, 2, TorA), BraidingTensor{scalartype(TorA), S}} - TB = tensormaptype(S, 2, 1, TorA) - TC = tensormaptype(S, 1, 2, TorA) - TD = tensormaptype(S, 1, 1, TorA) - return JordanMPOTensor{scalartype(TorA), S, TA, TB, TC, TD} +function jordanmpotensortype(::Type{S}, ::Type{E}) where {S <: VectorSpace, E <: Number} + TA = Union{tensormaptype(S, 2, 2, E), BraidingTensor{E, S}} + TB = tensormaptype(S, 2, 1, E) + TC = tensormaptype(S, 1, 2, E) + TD = tensormaptype(S, 1, 1, E) + return JordanMPOTensor{E, S, TA, TB, TC, TD} end function jordanmpotensortype(::Type{O}) where {O <: MPOTensor} return jordanmpotensortype(spacetype(O), scalartype(O)) diff --git a/src/states/abstractmps.jl b/src/states/abstractmps.jl index b80cdcc1c..3d277aff4 100644 --- a/src/states/abstractmps.jl +++ b/src/states/abstractmps.jl @@ -199,7 +199,6 @@ TensorKit.spacetype(ψ::AbstractMPS) = spacetype(typeof(ψ)) TensorKit.spacetype(ψtype::Type{<:AbstractMPS}) = spacetype(site_type(ψtype)) TensorKit.sectortype(ψ::AbstractMPS) = sectortype(typeof(ψ)) TensorKit.sectortype(ψtype::Type{<:AbstractMPS}) = sectortype(site_type(ψtype)) -TensorKit.storagetype(ψtype::Type{<:AbstractMPS}) = storagetype(site_type(ψtype)) """ left_virtualspace(ψ::AbstractMPS, [pos=1:length(ψ)]) diff --git a/src/states/finitemps.jl b/src/states/finitemps.jl index 1c294ae90..755825c74 100644 --- a/src/states/finitemps.jl +++ b/src/states/finitemps.jl @@ -376,6 +376,9 @@ end site_type(::Type{<:FiniteMPS{A}}) where {A} = A bond_type(::Type{<:FiniteMPS{<:Any, B}}) where {B} = B +function TensorKit.storagetype(::Union{MPS, Type{MPS}}) where {A, MPS <: FiniteMPS{A}} + return storagetype(A) +end function left_virtualspace(ψ::FiniteMPS, n::Integer) checkbounds(ψ, n) diff --git a/test/operators/mpo.jl b/test/operators/mpo.jl index 34f2553db..4f6d2cad9 100644 --- a/test/operators/mpo.jl +++ b/test/operators/mpo.jl @@ -4,8 +4,8 @@ println(" -------------------- ") +using .TestSetup using Test, TestExtras -using Adapt using MPSKit using MPSKit: GeometryStyle, FiniteChainStyle, InfiniteChainStyle, OperatorStyle, MPOStyle using TensorKit @@ -83,7 +83,6 @@ using TensorKit: ℙ end end - @testset "InfiniteMPO" begin P = ℂ^2 T = Float64 @@ -99,26 +98,3 @@ end @test OperatorStyle(typeof(H)) == MPOStyle() @test OperatorStyle(H) == MPOStyle() end - -@testset "Adapt" for V in (ℂ^2, U1Space(-1 => 1, 0 => 1, 1 => 1)) - L = 3 - o = rand(Float32, V^L ← V^L) - mpo1 = FiniteMPO(o) - for T in (Float64, ComplexF64) - mpo2 = @testinferred adapt(Vector{T}, mpo1) - @test mpo2 isa FiniteMPO - @test scalartype(mpo2) == T - @test storagetype(mpo2) == Vector{T} - @test convert(TensorMap, mpo2) ≈ o - end - - mpo3 = InfiniteMPO(mpo1[2:2]) - for T in (Float64, ComplexF64) - mpo4 = @testinferred adapt(Vector{T}, mpo3) - @test mpo4 isa InfiniteMPO - @test scalartype(mpo4) == T - @test storagetype(mpo4) == Vector{T} - @test dot(mpo3, mpo4) ≈ 1 atol = 1.0e-4 - end - -end diff --git a/test/operators/mpohamiltonian.jl b/test/operators/mpohamiltonian.jl index 7eae64ea9..666ab2bee 100644 --- a/test/operators/mpohamiltonian.jl +++ b/test/operators/mpohamiltonian.jl @@ -4,8 +4,8 @@ println(" ---------------------------- ") +using .TestSetup using Test, TestExtras -using Adapt using MPSKit using MPSKit: GeometryStyle, FiniteChainStyle, InfiniteChainStyle, OperatorStyle, HamiltonianStyle using TensorKit @@ -239,36 +239,3 @@ end h4 = H4 * H4 @test real(expectation_value(ψ2, H4)) >= 0 end - -@testset "Adapt" for V in (ℂ^2, U1Space(-1 => 1, 0 => 1, 1 => 1)) - h = rand(Float32, V^2 ← V^2) - h += h' - - L = 4 - H1 = FiniteMPOHamiltonian( - fill(V, L), - ((i, i + 1) => h for i in 1:(L - 1))..., - ((i, i + 2) => h for i in 1:(L - 2))..., - ((i, i + 3) => h for i in 1:(L - 3))..., - ) - mps1 = FiniteMPS(physicalspace(H1), oneunit(V)) - - for T in (Float64, ComplexF64) - H2 = @testinferred adapt(Vector{T}, H1) - @test H2 isa FiniteMPOHamiltonian - @test scalartype(H2) == T - @test storagetype(H2) == Vector{T} - @test expectation_value(mps1, H1) ≈ expectation_value(mps1, H2) - end - - H3 = InfiniteMPOHamiltonian(fill(V, L), (1, 2) => h, (1, 3) => h, (1, 4) => h) - mps2 = InfiniteMPS(physicalspace(H3), [oneunit(V)]) - for T in (Float64, ComplexF64) - H4 = @testinferred adapt(Vector{T}, H3) - @test H4 isa InfiniteMPOHamiltonian - @test scalartype(H4) == T - @test storagetype(H4) == Vector{T} - @test expectation_value(mps2, H3) ≈ expectation_value(mps2, H4) - end - -end From 1b310ff2ec59a1fcdd9ad56456a7432c2e295399 Mon Sep 17 00:00:00 2001 From: Akshay Shankar Date: Mon, 23 Feb 2026 17:58:58 +0100 Subject: [PATCH 02/14] Deep copying MPS with copy (#387) * broadcast copy over arrays * add basic tests for copying mps * fix variable naming mismatch * small fixes * format --------- Co-authored-by: Lukas Devos --- src/states/finitemps.jl | 15 +++++++++++++-- src/states/infinitemps.jl | 2 +- test/operators/mpo.jl | 1 + test/operators/mpohamiltonian.jl | 1 + test/states/finitemps.jl | 30 +++++++++++++++++++++++++++++- test/states/infinitemps.jl | 27 ++++++++++++++++++++++++++- 6 files changed, 71 insertions(+), 5 deletions(-) diff --git a/src/states/finitemps.jl b/src/states/finitemps.jl index 755825c74..c956b9730 100644 --- a/src/states/finitemps.jl +++ b/src/states/finitemps.jl @@ -305,7 +305,6 @@ Utility Base.size(ψ::FiniteMPS, args...) = size(ψ.ALs, args...) Base.length(ψ::FiniteMPS) = length(ψ.ALs) Base.eltype(ψtype::Type{<:FiniteMPS}) = site_type(ψtype) # this might not be true -Base.copy(ψ::FiniteMPS) = FiniteMPS(copy(ψ.ALs), copy(ψ.ARs), copy(ψ.ACs), copy(ψ.Cs)) function Base.similar(ψ::FiniteMPS{A, B}) where {A, B} return FiniteMPS{A, B}(similar(ψ.ALs), similar(ψ.ARs), similar(ψ.ACs), similar(ψ.Cs)) end @@ -338,7 +337,19 @@ end # TODO: check where gauge center is to determine efficient kind AC2(psi::FiniteMPS, site::Int) = psi.AC[site] * _transpose_tail(psi.AR[site + 1]) -_complex_if_not_missing(x) = ismissing(x) ? x : complex(x) +f_if_not_missing(f, x) = ismissing(x) ? x : f(x) +_copy_if_not_missing(x) = f_if_not_missing(copy, x) +_complex_if_not_missing(x) = f_if_not_missing(complex, x) + +function Base.copy(mps::FiniteMPS) + mps2 = similar(mps) + mps2.ALs .= _copy_if_not_missing.(mps.ALs) + mps2.ARs .= _copy_if_not_missing.(mps.ARs) + mps2.ACs .= _copy_if_not_missing.(mps.ACs) + mps2.Cs .= _copy_if_not_missing.(mps.Cs) + return mps2 +end + function Base.complex(mps::FiniteMPS) scalartype(mps) <: Complex && return mps ALs = _complex_if_not_missing.(mps.ALs) diff --git a/src/states/infinitemps.jl b/src/states/infinitemps.jl index 8e3780ad3..1cd07bab4 100644 --- a/src/states/infinitemps.jl +++ b/src/states/infinitemps.jl @@ -244,7 +244,7 @@ Base.eltype(::Type{<:InfiniteMPS{A}}) where {A} = A Base.isfinite(::Type{<:InfiniteMPS}) = false GeometryStyle(::Type{<:InfiniteMPS}) = InfiniteChainStyle() -Base.copy(ψ::InfiniteMPS) = InfiniteMPS(copy(ψ.AL), copy(ψ.AR), copy(ψ.C), copy(ψ.AC)) +Base.copy(ψ::InfiniteMPS) = InfiniteMPS(copy.(ψ.AL), copy.(ψ.AR), copy.(ψ.C), copy.(ψ.AC)) function Base.copy!(ψ::InfiniteMPS, ϕ::InfiniteMPS) ψ.AL .= _copy!!.(ψ.AL, ϕ.AL) ψ.AR .= _copy!!.(ψ.AR, ϕ.AR) diff --git a/test/operators/mpo.jl b/test/operators/mpo.jl index 4f6d2cad9..c73608c6d 100644 --- a/test/operators/mpo.jl +++ b/test/operators/mpo.jl @@ -10,6 +10,7 @@ using MPSKit using MPSKit: GeometryStyle, FiniteChainStyle, InfiniteChainStyle, OperatorStyle, MPOStyle using TensorKit using TensorKit: ℙ +using Adapt @testset "FiniteMPO" begin # start from random operators diff --git a/test/operators/mpohamiltonian.jl b/test/operators/mpohamiltonian.jl index 666ab2bee..9ff79f4c1 100644 --- a/test/operators/mpohamiltonian.jl +++ b/test/operators/mpohamiltonian.jl @@ -10,6 +10,7 @@ using MPSKit using MPSKit: GeometryStyle, FiniteChainStyle, InfiniteChainStyle, OperatorStyle, HamiltonianStyle using TensorKit using TensorKit: ℙ +using Adapt pspaces = (ℙ^4, Rep[U₁](0 => 2), Rep[SU₂](1 => 1)) vspaces = (ℙ^10, Rep[U₁]((0 => 20)), Rep[SU₂](1 // 2 => 10, 3 // 2 => 5, 5 // 2 => 1)) diff --git a/test/states/finitemps.jl b/test/states/finitemps.jl index d23e26343..d45170437 100644 --- a/test/states/finitemps.jl +++ b/test/states/finitemps.jl @@ -5,12 +5,12 @@ println(" ") using Test, TestExtras -using Adapt using MPSKit using MPSKit: _transpose_front, _transpose_tail using MPSKit: GeometryStyle, FiniteChainStyle using TensorKit using TensorKit: ℙ +using Adapt @testset "FiniteMPS ($(sectortype(D)), $elt)" for (D, d, elt) in [ (ℙ^10, ℙ^2, ComplexF64), @@ -113,3 +113,31 @@ end @test ψ.center == 13 / 2 @test ψ[5:7] == [ψ.ALs[5], ψ.ACs[6], ψ.ARs[7]] end + +@testset "FiniteMPS copying" begin + L = 10 + mps1 = FiniteMPS(rand, ComplexF64, L, ℂ^2, ℂ^5) + mps2 = copy(mps1) + + @test mps1 !== mps2 + + # arrays are distinct + @test mps1.ALs !== mps2.ALs + @test mps1.ARs !== mps2.ARs + @test mps1.ACs !== mps2.ACs + @test mps1.Cs !== mps2.Cs + + # tensors are distinct but equal + for i in 1:L + @test (ismissing(mps1.ALs[i]) && ismissing(mps2.ALs[i])) || + (mps1.ALs[i] !== mps2.ALs[i] && mps1.ALs[i] == mps2.ALs[i]) + @test (ismissing(mps1.ARs[i]) && ismissing(mps2.ARs[i])) || + (mps1.ARs[i] !== mps2.ARs[i] && mps1.ARs[i] == mps2.ARs[i]) + @test (ismissing(mps1.ACs[i]) && ismissing(mps2.ACs[i])) || + (mps1.ACs[i] !== mps2.ACs[i] && mps1.ACs[i] == mps2.ACs[i]) + @test (ismissing(mps1.Cs[i]) && ismissing(mps2.Cs[i])) || + (mps1.Cs[i] !== mps2.Cs[i] && mps1.Cs[i] == mps2.Cs[i]) + end + @test (ismissing(mps1.Cs[end]) && ismissing(mps2.Cs[end])) || + mps1.Cs[end] !== mps2.Cs[end] +end diff --git a/test/states/infinitemps.jl b/test/states/infinitemps.jl index 0ac4e269c..ee032dd17 100644 --- a/test/states/infinitemps.jl +++ b/test/states/infinitemps.jl @@ -5,11 +5,11 @@ println(" ") using Test, TestExtras -using Adapt using MPSKit using MPSKit: GeometryStyle, InfiniteChainStyle, TransferMatrix using TensorKit using TensorKit: ℙ +using Adapt @testset "InfiniteMPS ($(sectortype(D)), $elt)" for (D, d, elt) in [(ℙ^10, ℙ^2, ComplexF64), (Rep[U₁](1 => 3), Rep[U₁](0 => 1), ComplexF64)] @@ -46,6 +46,31 @@ using TensorKit: ℙ end end +@testset "InfiniteMPS copying" begin + mps1 = InfiniteMPS(rand, ComplexF64, ℂ^2, ℂ^5) + mps2 = copy(mps1) + + @test mps1 !== mps2 + + # elements are equal + @test mps1.AL[1] == mps2.AL[1] + @test mps1.AR[1] == mps2.AR[1] + @test mps1.AC[1] == mps2.AC[1] + @test mps1.C[1] == mps2.C[1] + + # arrays are distinct + @test mps1.AL !== mps2.AL + @test mps1.AR !== mps2.AR + @test mps1.AC !== mps2.AC + @test mps1.C !== mps2.C + + # tensors are distinct + @test mps1.AL[1] !== mps2.AL[1] + @test mps1.AR[1] !== mps2.AR[1] + @test mps1.AC[1] !== mps2.AC[1] + @test mps1.C[1] !== mps2.C[1] +end + @testset "Adapt" begin for (d, D) in [(ℂ^2, ℂ^4), (ℙ^2, ℙ^4)] mps1 = InfiniteMPS(rand, Float32, d, D) From d9f275e2eec5e1f65b90d68d3b0aad003fbbb837 Mon Sep 17 00:00:00 2001 From: Andreas Feuerpfeil Date: Mon, 23 Feb 2026 20:39:42 +0100 Subject: [PATCH 03/14] Refactor entropy function to also use spectrum directly (#377) * Refactor entropy function to also use spectrum directly * specialize to SectorVector and add test * add infinite test * update docstring --------- Co-authored-by: Lukas Devos --- src/algorithms/toolbox.jl | 21 +++++++++++++++------ test/states/finitemps.jl | 31 +++++++++++++++++++++++++++++++ test/states/infinitemps.jl | 22 ++++++++++++++++++++++ 3 files changed, 68 insertions(+), 6 deletions(-) diff --git a/src/algorithms/toolbox.jl b/src/algorithms/toolbox.jl index 4956d163c..4f548dada 100644 --- a/src/algorithms/toolbox.jl +++ b/src/algorithms/toolbox.jl @@ -1,15 +1,24 @@ """ entropy(state, [site::Int]) + entropy(spectrum::SectorVector) -Calculate the von Neumann entanglement entropy of a given MPS. If an integer `site` is -given, the entropy is across the entanglement cut to the right of site `site`. Otherwise, a -vector of entropies is returned, one for each site. +Calculate the von Neumann entanglement entropy. The entropy can be computed from either an +MPS state or directly from an entanglement spectrum as obtained from +[`entanglement_spectrum`](@ref). + +When called on an MPS with an integer `site`, the entropy is computed across the +entanglement cut to the right of site `site`. For `InfiniteMPS`, omitting `site` returns a +vector of entropies, one for each site. For `FiniteMPS` and `WindowMPS`, `site` is +required. """ entropy(state::InfiniteMPS) = map(Base.Fix1(entropy, state), 1:length(state)) function entropy(state::Union{FiniteMPS, WindowMPS, InfiniteMPS}, loc::Int) - S = zero(real(scalartype(state))) - tol = eps(typeof(S)) - for (c, b) in pairs(entanglement_spectrum(state, loc)) + return entropy(entanglement_spectrum(state, loc)) +end +function entropy(spectrum::TensorKit.SectorVector{T}) where {T} + S = zero(T) + tol = eps(T) + for (c, b) in pairs(spectrum) s = zero(S) for x in b x < tol && break diff --git a/test/states/finitemps.jl b/test/states/finitemps.jl index d45170437..1cc2a4ea2 100644 --- a/test/states/finitemps.jl +++ b/test/states/finitemps.jl @@ -141,3 +141,34 @@ end @test (ismissing(mps1.Cs[end]) && ismissing(mps2.Cs[end])) || mps1.Cs[end] !== mps2.Cs[end] end + +@testset "FiniteMPS entropy ($(sectortype(D)), $elt)" for (D, d, elt) in [ + (ℙ^10, ℙ^2, ComplexF64), + ( + Rep[U₁](-1 => 3, 0 => 3, 1 => 3), + Rep[U₁](-1 => 1, 0 => 1, 1 => 1), + ComplexF64, + ), + ] + L = 6 + ψ = FiniteMPS(rand, elt, L, d, D) + + # entropy is non-negative at all sites + for site in 1:L + @test real(entropy(ψ, site)) >= 0 + end + + # entropy is consistent with entanglement_spectrum + for site in 1:L + @test entropy(ψ, site) ≈ entropy(entanglement_spectrum(ψ, site)) + end + + # entropy is zero at the right boundary (trivial bond) + @test entropy(ψ, L) ≈ 0 atol = 1.0e-10 + + # product state has zero entropy everywhere + ψ_product = FiniteMPS(rand, elt, L, d, oneunit(D)) + for site in 1:L + @test entropy(ψ_product, site) ≈ 0 atol = 1.0e-10 + end +end diff --git a/test/states/infinitemps.jl b/test/states/infinitemps.jl index ee032dd17..7f2cc6b62 100644 --- a/test/states/infinitemps.jl +++ b/test/states/infinitemps.jl @@ -83,3 +83,25 @@ end end end end + +@testset "InfiniteMPS entropy ($(sectortype(D)), $elt)" for (D, d, elt) in + [(ℙ^10, ℙ^2, ComplexF64), (Rep[U₁](1 => 3), Rep[U₁](0 => 1), ComplexF64)] + ψ = InfiniteMPS([d, d], [D, D]) + + # entropy(ψ) returns one value per site, all non-negative + Ss = entropy(ψ) + @test length(Ss) == length(ψ) + @test all(isreal, Ss) + @test all(>=(0), Ss) + + # entropy(ψ, site) is non-negative and consistent with entropy(ψ) + for site in 1:length(ψ) + @test entropy(ψ, site) ≈ Ss[site] + @test entropy(ψ, site) ≈ entropy(entanglement_spectrum(ψ, site)) + end + + # product state has zero entropy everywhere + ψ_product = InfiniteMPS([d, d], [oneunit(D), oneunit(D)]) + Ss_product = entropy(ψ_product) + @test all(S -> isapprox(S, 0; atol = 1.0e-10), Ss_product) +end From 26bfb5acecf6f63e4a70b372e6b75274354aabc7 Mon Sep 17 00:00:00 2001 From: Akshay Shankar Date: Mon, 23 Feb 2026 17:58:58 +0100 Subject: [PATCH 04/14] Deep copying MPS with copy (#387) * broadcast copy over arrays * add basic tests for copying mps * fix variable naming mismatch * small fixes * format --------- Co-authored-by: Lukas Devos --- test/states/infinitemps.jl | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/test/states/infinitemps.jl b/test/states/infinitemps.jl index 7f2cc6b62..f0cc062dc 100644 --- a/test/states/infinitemps.jl +++ b/test/states/infinitemps.jl @@ -105,3 +105,28 @@ end Ss_product = entropy(ψ_product) @test all(S -> isapprox(S, 0; atol = 1.0e-10), Ss_product) end + +@testset "InfiniteMPS copying" begin + mps1 = InfiniteMPS(rand, ComplexF64, ℂ^2, ℂ^5) + mps2 = copy(mps1) + + @test mps1 !== mps2 + + # elements are equal + @test mps1.AL[1] == mps2.AL[1] + @test mps1.AR[1] == mps2.AR[1] + @test mps1.AC[1] == mps2.AC[1] + @test mps1.C[1] == mps2.C[1] + + # arrays are distinct + @test mps1.AL !== mps2.AL + @test mps1.AR !== mps2.AR + @test mps1.AC !== mps2.AC + @test mps1.C !== mps2.C + + # tensors are distinct + @test mps1.AL[1] !== mps2.AL[1] + @test mps1.AR[1] !== mps2.AR[1] + @test mps1.AC[1] !== mps2.AC[1] + @test mps1.C[1] !== mps2.C[1] +end From 948505cfbc1e94271bee98098994f18433883009 Mon Sep 17 00:00:00 2001 From: Lukas Devos Date: Fri, 20 Feb 2026 14:02:50 -0500 Subject: [PATCH 05/14] fix wrong testcase --- test/operators/mpo.jl | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/test/operators/mpo.jl b/test/operators/mpo.jl index c73608c6d..cec8e0145 100644 --- a/test/operators/mpo.jl +++ b/test/operators/mpo.jl @@ -99,3 +99,26 @@ end @test OperatorStyle(typeof(H)) == MPOStyle() @test OperatorStyle(H) == MPOStyle() end + +@testset "Adapt" for V in (ℂ^2, U1Space(-1 => 1, 0 => 1, 1 => 1)) + L = 3 + o = rand(Float32, V^L ← V^L) + mpo1 = FiniteMPO(o) + for T in (Float64, ComplexF64) + mpo2 = @testinferred adapt(Vector{T}, mpo1) + @test mpo2 isa FiniteMPO + @test scalartype(mpo2) == T + @test storagetype(mpo2) == Vector{T} + @test convert(TensorMap, mpo2) ≈ o + end + + mpo3 = InfiniteMPO(mpo1[2:2]) + for T in (Float64, ComplexF64) + mpo4 = @testinferred adapt(Vector{T}, mpo3) + @test mpo4 isa InfiniteMPO + @test scalartype(mpo4) == T + @test storagetype(mpo4) == Vector{T} + @test dot(mpo3, mpo4) ≈ norm(mpo3)^2 atol = 1.0e-4 + end + +end From e00d5b86c128201a361879d890c6090a50d5026f Mon Sep 17 00:00:00 2001 From: Lukas Devos Date: Fri, 20 Feb 2026 15:46:57 -0500 Subject: [PATCH 06/14] alter JordanMPOTensor Adapt implementation --- ext/MPSKitAdaptExt.jl | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 ext/MPSKitAdaptExt.jl diff --git a/ext/MPSKitAdaptExt.jl b/ext/MPSKitAdaptExt.jl new file mode 100644 index 000000000..433a81b92 --- /dev/null +++ b/ext/MPSKitAdaptExt.jl @@ -0,0 +1,40 @@ +module MPSKitAdaptExt + +using TensorKit: space, spacetype +using MPSKit +using BlockTensorKit: nonzero_pairs +using Adapt + +function Adapt.adapt_structure(to, mps::FiniteMPS) + ad = adapt(to) + adapt_not_missing(x) = ismissing(x) ? x : ad(x) + + TA = Base.promote_op(ad, MPSKit.site_type(mps)) + TB = Base.promote_op(ad, MPSKit.bond_type(mps)) + + ALs = map!(adapt_not_missing, similar(mps.ALs, Union{Missing, TA}), mps.ALs) + ARs = map!(adapt_not_missing, similar(mps.ARs, Union{Missing, TA}), mps.ARs) + ACs = map!(adapt_not_missing, similar(mps.ACs, Union{Missing, TA}), mps.ACs) + Cs = map!(adapt_not_missing, similar(mps.Cs, Union{Missing, TB}), mps.Cs) + + return FiniteMPS{TA, TB}(ALs, ARs, ACs, Cs) +end + +function Adapt.adapt_structure(to, mps::InfiniteMPS) + ad = adapt(to) + AL = map(ad, mps.AL) + AR = map(ad, mps.AR) + C = map(ad, mps.C) + AC = map(ad, mps.AC) + + return InfiniteMPS{eltype(AL), eltype(C)}(AL, AR, C, AC) +end + +Adapt.adapt_structure(to, mpo::MPO) = MPO(map(adapt(to), mpo.O)) + +Adapt.adapt_structure(to, W::MPSKit.JordanMPOTensor) = + MPSKit.JordanMPOTensor(space(W), adapt(to, W.A), adapt(to, W.B), adapt(to, W.C), adapt(to, W.D)) + +Adapt.adapt_structure(to, mpo::MPOHamiltonian) = MPOHamiltonian(map(adapt(to), mpo.W)) + +end From c942523e1d44bc8fa881fecb1155102ba20eae92 Mon Sep 17 00:00:00 2001 From: Lukas Devos Date: Fri, 20 Feb 2026 15:55:26 -0500 Subject: [PATCH 07/14] type stability chenanigans --- ext/MPSKitAdaptExt.jl | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/ext/MPSKitAdaptExt.jl b/ext/MPSKitAdaptExt.jl index 433a81b92..4a85a285f 100644 --- a/ext/MPSKitAdaptExt.jl +++ b/ext/MPSKitAdaptExt.jl @@ -26,15 +26,14 @@ function Adapt.adapt_structure(to, mps::InfiniteMPS) AR = map(ad, mps.AR) C = map(ad, mps.C) AC = map(ad, mps.AC) - return InfiniteMPS{eltype(AL), eltype(C)}(AL, AR, C, AC) end -Adapt.adapt_structure(to, mpo::MPO) = MPO(map(adapt(to), mpo.O)) - -Adapt.adapt_structure(to, W::MPSKit.JordanMPOTensor) = +# inline to improve type stability with closures +@inline Adapt.adapt_structure(to, mpo::MPO) = MPO(map(adapt(to), mpo.O)) +@inline Adapt.adapt_structure(to, W::MPSKit.JordanMPOTensor) = MPSKit.JordanMPOTensor(space(W), adapt(to, W.A), adapt(to, W.B), adapt(to, W.C), adapt(to, W.D)) - -Adapt.adapt_structure(to, mpo::MPOHamiltonian) = MPOHamiltonian(map(adapt(to), mpo.W)) +@inline Adapt.adapt_structure(to, mpo::MPOHamiltonian) = + MPOHamiltonian(map(adapt(to), mpo.W)) end From 8c0bfa1d81c024664776d035f71344f7ccd898a6 Mon Sep 17 00:00:00 2001 From: Lukas Devos Date: Mon, 23 Feb 2026 15:20:20 -0500 Subject: [PATCH 08/14] bump TensorKit minimal version --- Project.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Project.toml b/Project.toml index 0ba745a7f..ab58d0aa9 100644 --- a/Project.toml +++ b/Project.toml @@ -26,7 +26,7 @@ VectorInterface = "409d34a3-91d5-4945-b6ec-7529ddf182d8" [compat] Accessors = "0.1" Aqua = "0.8.9" -BlockTensorKit = "0.3.4" +BlockTensorKit = "0.3.8" Combinatorics = "1" Compat = "3.47, 4.10" DocStringExtensions = "0.9.3" @@ -43,7 +43,7 @@ Plots = "1.40" Printf = "1" Random = "1" RecipesBase = "1.1" -TensorKit = "0.16" +TensorKit = "0.16.3" TensorKitManifolds = "0.7" TensorKitTensors = "0.2" TensorOperations = "5" From c5f54b3e9c6b87640e99cc57b5d9632af4b43f6e Mon Sep 17 00:00:00 2001 From: Lukas Devos Date: Mon, 23 Feb 2026 17:53:59 -0500 Subject: [PATCH 09/14] more bypassing of type stability issues... --- ext/MPSKitAdaptExt.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/MPSKitAdaptExt.jl b/ext/MPSKitAdaptExt.jl index 4a85a285f..6f0b0c7f2 100644 --- a/ext/MPSKitAdaptExt.jl +++ b/ext/MPSKitAdaptExt.jl @@ -34,6 +34,6 @@ end @inline Adapt.adapt_structure(to, W::MPSKit.JordanMPOTensor) = 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(adapt(to), mpo.W)) + MPOHamiltonian(map(x -> adapt(to, x), mpo.W)) end From 2db608af103b341d0fa0fe027554c21af2b603cf Mon Sep 17 00:00:00 2001 From: Lukas Devos Date: Tue, 24 Feb 2026 09:38:21 -0500 Subject: [PATCH 10/14] fix some tests --- test/operators/mpo.jl | 1 - test/operators/mpohamiltonian.jl | 34 +++++++++++++++++++++++++++++++- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/test/operators/mpo.jl b/test/operators/mpo.jl index cec8e0145..f93d4a9c2 100644 --- a/test/operators/mpo.jl +++ b/test/operators/mpo.jl @@ -120,5 +120,4 @@ end @test storagetype(mpo4) == Vector{T} @test dot(mpo3, mpo4) ≈ norm(mpo3)^2 atol = 1.0e-4 end - end diff --git a/test/operators/mpohamiltonian.jl b/test/operators/mpohamiltonian.jl index 9ff79f4c1..40e3690eb 100644 --- a/test/operators/mpohamiltonian.jl +++ b/test/operators/mpohamiltonian.jl @@ -4,8 +4,8 @@ println(" ---------------------------- ") -using .TestSetup using Test, TestExtras +using Adapt using MPSKit using MPSKit: GeometryStyle, FiniteChainStyle, InfiniteChainStyle, OperatorStyle, HamiltonianStyle using TensorKit @@ -240,3 +240,35 @@ end h4 = H4 * H4 @test real(expectation_value(ψ2, H4)) >= 0 end + +@testset "Adapt" for V in (ℂ^2, U1Space(-1 => 1, 0 => 1, 1 => 1)) + h = rand(Float32, V^2 ← V^2) + h += h' + + L = 4 + H1 = FiniteMPOHamiltonian( + fill(V, L), + ((i, i + 1) => h for i in 1:(L - 1))..., + ((i, i + 2) => h for i in 1:(L - 2))..., + ((i, i + 3) => h for i in 1:(L - 3))..., + ) + mps1 = FiniteMPS(physicalspace(H1), oneunit(V)) + + for T in (Float64, ComplexF64) + H2 = @testinferred adapt(Vector{T}, H1) + @test H2 isa FiniteMPOHamiltonian + @test scalartype(H2) == T + @test storagetype(H2) == Vector{T} + @test expectation_value(mps1, H1) ≈ expectation_value(mps1, H2) + end + + H3 = InfiniteMPOHamiltonian(fill(V, L), (1, 2) => h, (1, 3) => h, (1, 4) => h) + mps2 = InfiniteMPS(physicalspace(H3), [oneunit(V)]) + for T in (Float64, ComplexF64) + H4 = @testinferred adapt(Vector{T}, H3) + @test H4 isa InfiniteMPOHamiltonian + @test scalartype(H4) == T + @test storagetype(H4) == Vector{T} + @test expectation_value(mps2, H3) ≈ expectation_value(mps2, H4) + end +end From d97fc72ec9a64ea2d572e1503c20f14be90e2d8a Mon Sep 17 00:00:00 2001 From: Lukas Devos Date: Tue, 24 Feb 2026 09:54:28 -0500 Subject: [PATCH 11/14] lost of git struggles --- Project.toml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index ab58d0aa9..e36c838f5 100644 --- a/Project.toml +++ b/Project.toml @@ -23,8 +23,15 @@ TensorKitManifolds = "11fa318c-39cb-4a83-b1ed-cdc7ba1e3684" TensorOperations = "6aa20fa7-93e2-5fca-9bc0-fbd0db3c71a2" VectorInterface = "409d34a3-91d5-4945-b6ec-7529ddf182d8" +[weakdeps] +Adapt = "79e6a3ab-5dfb-504d-930d-738a2a938a0e" + +[extensions] +MPSKitAdaptExt = "Adapt" + [compat] Accessors = "0.1" +Adapt = "4" Aqua = "0.8.9" BlockTensorKit = "0.3.8" Combinatorics = "1" @@ -53,6 +60,7 @@ VectorInterface = "0.2, 0.3, 0.4, 0.5" julia = "1.10" [extras] +Adapt = "79e6a3ab-5dfb-504d-930d-738a2a938a0e" Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595" Combinatorics = "861a8166-3701-5b0c-9a16-15d98fcdc6aa" ParallelTestRunner = "d3525ed8-44d0-4b2c-a655-542cee43accc" @@ -63,4 +71,4 @@ Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" TestExtras = "5ed8adda-3752-4e41-b88a-e8b09835ee3a" [targets] -test = ["Aqua", "Pkg", "Test", "TestExtras", "Plots", "Combinatorics", "ParallelTestRunner", "TensorKitTensors"] +test = ["Aqua", "Adapt", "Pkg", "Test", "TestExtras", "Plots", "Combinatorics", "ParallelTestRunner", "TensorKitTensors"] From df834d2ca7b3974d46f51efefc5a1fc75ab83b4b Mon Sep 17 00:00:00 2001 From: Lukas Devos Date: Tue, 24 Feb 2026 11:39:08 -0500 Subject: [PATCH 12/14] more git misery! --- Project.toml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index ab58d0aa9..e36c838f5 100644 --- a/Project.toml +++ b/Project.toml @@ -23,8 +23,15 @@ TensorKitManifolds = "11fa318c-39cb-4a83-b1ed-cdc7ba1e3684" TensorOperations = "6aa20fa7-93e2-5fca-9bc0-fbd0db3c71a2" VectorInterface = "409d34a3-91d5-4945-b6ec-7529ddf182d8" +[weakdeps] +Adapt = "79e6a3ab-5dfb-504d-930d-738a2a938a0e" + +[extensions] +MPSKitAdaptExt = "Adapt" + [compat] Accessors = "0.1" +Adapt = "4" Aqua = "0.8.9" BlockTensorKit = "0.3.8" Combinatorics = "1" @@ -53,6 +60,7 @@ VectorInterface = "0.2, 0.3, 0.4, 0.5" julia = "1.10" [extras] +Adapt = "79e6a3ab-5dfb-504d-930d-738a2a938a0e" Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595" Combinatorics = "861a8166-3701-5b0c-9a16-15d98fcdc6aa" ParallelTestRunner = "d3525ed8-44d0-4b2c-a655-542cee43accc" @@ -63,4 +71,4 @@ Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" TestExtras = "5ed8adda-3752-4e41-b88a-e8b09835ee3a" [targets] -test = ["Aqua", "Pkg", "Test", "TestExtras", "Plots", "Combinatorics", "ParallelTestRunner", "TensorKitTensors"] +test = ["Aqua", "Adapt", "Pkg", "Test", "TestExtras", "Plots", "Combinatorics", "ParallelTestRunner", "TensorKitTensors"] From c30fc3e9160e6a50f17c0c71a6862fee069c4bed Mon Sep 17 00:00:00 2001 From: Lukas Devos Date: Tue, 24 Feb 2026 12:03:12 -0500 Subject: [PATCH 13/14] handle storagetype better --- src/states/abstractmps.jl | 2 ++ src/states/finitemps.jl | 3 --- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/states/abstractmps.jl b/src/states/abstractmps.jl index 3d277aff4..e442100db 100644 --- a/src/states/abstractmps.jl +++ b/src/states/abstractmps.jl @@ -200,6 +200,8 @@ TensorKit.spacetype(ψtype::Type{<:AbstractMPS}) = spacetype(site_type(ψtype)) TensorKit.sectortype(ψ::AbstractMPS) = sectortype(typeof(ψ)) TensorKit.sectortype(ψtype::Type{<:AbstractMPS}) = sectortype(site_type(ψtype)) +TensorKit.storagetype(ψtype::Type{<:AbstractMPS}) = storagetype(site_type(ψtype)) + """ left_virtualspace(ψ::AbstractMPS, [pos=1:length(ψ)]) diff --git a/src/states/finitemps.jl b/src/states/finitemps.jl index c956b9730..5bfdcba92 100644 --- a/src/states/finitemps.jl +++ b/src/states/finitemps.jl @@ -387,9 +387,6 @@ end site_type(::Type{<:FiniteMPS{A}}) where {A} = A bond_type(::Type{<:FiniteMPS{<:Any, B}}) where {B} = B -function TensorKit.storagetype(::Union{MPS, Type{MPS}}) where {A, MPS <: FiniteMPS{A}} - return storagetype(A) -end function left_virtualspace(ψ::FiniteMPS, n::Integer) checkbounds(ψ, n) From 8682063879e3ad6f32088a10458c78a5f1738164 Mon Sep 17 00:00:00 2001 From: Lukas Devos Date: Tue, 24 Feb 2026 15:15:55 -0500 Subject: [PATCH 14/14] disable lts type stability tests --- test/operators/mpohamiltonian.jl | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/test/operators/mpohamiltonian.jl b/test/operators/mpohamiltonian.jl index fe1c5858c..e199df30f 100644 --- a/test/operators/mpohamiltonian.jl +++ b/test/operators/mpohamiltonian.jl @@ -255,7 +255,11 @@ end mps1 = FiniteMPS(physicalspace(H1), oneunit(V)) for T in (Float64, ComplexF64) - H2 = @testinferred adapt(Vector{T}, H1) + H2 = if VERSION <= v"1.12" + adapt(Vector{T}, H1) + else + @testinferred adapt(Vector{T}, H1) + end @test H2 isa FiniteMPOHamiltonian @test scalartype(H2) == T @test storagetype(H2) == Vector{T} @@ -265,7 +269,12 @@ end H3 = InfiniteMPOHamiltonian(fill(V, L), (1, 2) => h, (1, 3) => h, (1, 4) => h) mps2 = InfiniteMPS(physicalspace(H3), [oneunit(V)]) for T in (Float64, ComplexF64) - H4 = @testinferred adapt(Vector{T}, H3) + H4 = if VERSION <= v"1.12" + # this is type unstable for LTS for some reason + adapt(Vector{T}, H3) + else + @testinferred adapt(Vector{T}, H3) + end @test H4 isa InfiniteMPOHamiltonian @test scalartype(H4) == T @test storagetype(H4) == Vector{T}