From 5923e9a183402e3ed902cafeff3b4105b4344534 Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Fri, 31 Jul 2026 14:20:45 +0200 Subject: [PATCH 1/9] add long-range hamiltonians to test setup --- test/setup/testsetup.jl | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/test/setup/testsetup.jl b/test/setup/testsetup.jl index 86b82bd2d..107675c05 100644 --- a/test/setup/testsetup.jl +++ b/test/setup/testsetup.jl @@ -21,6 +21,7 @@ export f_plus_f_min, f_min_f_plus, f_num, f_hopping export force_planar export symm_mul_mpo export transverse_field_ising, heisenberg_XXX, bilinear_biquadratic_model, XY_model, kitaev_model +export long_range_ising, long_range_ising_infinite export classical_ising_tensors, classical_ising, sixvertex export bad_initial_state export SCHEDULERS, with_scheduler @@ -204,6 +205,23 @@ function kitaev_model( end end +function long_range_ising( + T::Type{<:Number} = Float64, sym::Type{<:Sector} = Trivial; g = 4.0, L = 4 + ) + X = S_x(T, sym; spin = 1 // 2) * 2 + ZZ = S_z_S_z(T, sym; spin = 1 // 2) * 4 + lattice = fill(space(X, 1), L) + return FiniteMPOHamiltonian(lattice, ((i,) => -g * X for i in 1:L)..., (1, L) => -ZZ) +end +function long_range_ising_infinite( + T::Type{<:Number} = Float64, sym::Type{<:Sector} = Trivial; g = 4.0, L = 3 + ) + X = S_x(T, sym; spin = 1 // 2) * 2 + ZZ = S_z_S_z(T, sym; spin = 1 // 2) * 4 + lattice = PeriodicArray(fill(space(X, 1), L)) + return InfiniteMPOHamiltonian(lattice, ((i,) => -g * X for i in 1:L)..., (1, L) => -ZZ) +end + function ising_bond_tensor(β) J = 1.0 K = β * J From 79ed55b03032ab2f0e06632bfa0f9818d080fe71 Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Fri, 31 Jul 2026 14:21:22 +0200 Subject: [PATCH 2/9] test long-range hamiltonians ground state search --- test/groundstate/groundstate.jl | 40 +++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/test/groundstate/groundstate.jl b/test/groundstate/groundstate.jl index 816c04809..4d6e5a8ad 100644 --- a/test/groundstate/groundstate.jl +++ b/test/groundstate/groundstate.jl @@ -323,6 +323,46 @@ end end end +@testset "Long-range Hamiltonian with real scalartype" verbose = true begin + # A real `H` optimised over a complex MPS sends the derivative operators through their + # converting constructors, and a long-range bond leaves sites with no ending (`B`) block + tol = 1.0e-8 + D = 8 + + @testset "FiniteMPS" begin + L = 10 + H = long_range_ising(Float64; L) + @test scalartype(H) <: Real + + ψ₀ = FiniteMPS(randn, ComplexF64, L, ℂ^2, ℂ^D) + # `complex(H)` takes the non-converting construction path + E_ref = expectation_value( + find_groundstate(ψ₀, complex(H), DMRG(; tol, verbosity = verbosity_conv))[1], H + ) + for alg in ( + DMRG(; tol, verbosity = verbosity_conv), + DMRG2(; tol, verbosity = verbosity_conv, trunc = truncrank(D)), + ) + ψ, envs, δ = find_groundstate(ψ₀, H, alg) + @test expectation_value(ψ, H, envs) ≈ E_ref atol = 1.0e-6 + end + end + + @testset "InfiniteMPS" begin + H = long_range_ising_infinite(Float64; L = 3) + @test scalartype(H) <: Real + + ψ₀ = InfiniteMPS(randn, ComplexF64, fill(ℂ^2, 3), fill(ℂ^D, 3)) + E_ref = expectation_value( + find_groundstate(ψ₀, complex(H), VUMPS(; tol, verbosity = verbosity_conv))[1], H + ) + for alg in (VUMPS(; tol, verbosity = verbosity_conv), IDMRG(; tol, verbosity = verbosity_conv)) + ψ, envs, δ = find_groundstate(ψ₀, H, alg) + @test expectation_value(ψ, H, envs) ≈ E_ref atol = 1.0e-6 + end + end +end + @testset "LazySum FiniteMPS ground state" verbose = true begin tol = 1.0e-8 D = 15 From 77f9ac2c17b7e4c3014618b85dde203b478a60a6 Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Fri, 7 Aug 2026 09:32:06 +0200 Subject: [PATCH 3/9] clarifying comment --- test/groundstate/groundstate.jl | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/groundstate/groundstate.jl b/test/groundstate/groundstate.jl index 4d6e5a8ad..f1b2439d0 100644 --- a/test/groundstate/groundstate.jl +++ b/test/groundstate/groundstate.jl @@ -324,8 +324,9 @@ end end @testset "Long-range Hamiltonian with real scalartype" verbose = true begin - # A real `H` optimised over a complex MPS sends the derivative operators through their - # converting constructors, and a long-range bond leaves sites with no ending (`B`) block + # force `JordanMPO_AC_Hamiltonian` to pass through fallback outer constructor + # through an onsite block `D` with scalartype != that of the MPS + # as `D`'s scalartype doesn't get promoted to the MPS's scalartype through some contraction with environments tol = 1.0e-8 D = 8 From 0fa2107d8b1aeae7cc0fbdbdc7ef78d58767ab72 Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Fri, 31 Jul 2026 14:37:47 +0200 Subject: [PATCH 4/9] explicitly test properties of derivative operators --- test/operators/derivatives.jl | 122 ++++++++++++++++++++++++++++++++++ 1 file changed, 122 insertions(+) create mode 100644 test/operators/derivatives.jl diff --git a/test/operators/derivatives.jl b/test/operators/derivatives.jl new file mode 100644 index 000000000..246637444 --- /dev/null +++ b/test/operators/derivatives.jl @@ -0,0 +1,122 @@ +println(" +---------------------------- +| Derivative operators | +---------------------------- +") + +using .TestSetup +using Test, TestExtras +using MPSKit +using MPSKit: C_hamiltonian, AC_hamiltonian, AC2_hamiltonian +using MPSKit: _transpose_front, _transpose_tail +using TensorKit +using BlockTensorKit: nonzero_length +using Random + +Random.seed!(1234) + +# `JordanMPO_AC(2)_Hamiltonian` has two construction paths: a fast one, taken when every +# block already has the storagetype of the environments, and a converting one +# this converting path is reached when `scalartype(H) != scalartype(ψ)` and an on-site D block is passed +# nearest-neighbour models have an ending B block on every site (but the first) + +@testset "Jordan block structure of the long-range models" begin + H = long_range_ising(Float64; L = 4) + Hi = long_range_ising_infinite(Float64; L = 3) + + # nothing ends before the far end of the (long-range) bond + @test all(i -> nonzero_length(H[i].B) == 0, 1:3) + @test nonzero_length(H[4].B) == 1 + @test all(i -> nonzero_length(Hi[i].B) == 0, 1:2) + @test nonzero_length(Hi[3].B) == 1 + + # while the left virtual space is non-trivial everywhere but on the first site of + # the finite chain, so `E` is present exactly where `B` is not + @test size(H[1], 1) == 1 + @test all(i -> size(H[i], 1) > 1, 2:4) + @test all(i -> size(Hi[i], 1) > 1, 1:3) + + # every site carries an on-site term, which is what forces the converting constructor + @test all(i -> nonzero_length(H[i].D) == 1, 1:4) + @test all(i -> nonzero_length(Hi[i].D) == 1, 1:3) +end + +@testset "MPOHamiltonian derivatives: real operator, complex state" verbose = true begin + D = 8 + L_inf, L_fin = 3, 4 + ψ_inf = InfiniteMPS(randn, ComplexF64, fill(ℂ^2, L_inf), fill(ℂ^D, L_inf)) + ψ_fin = FiniteMPS(randn, ComplexF64, L_fin, ℂ^2, ℂ^D) + models = [ + "FiniteMPS, long-range" => (long_range_ising(Float64; L = L_fin), ψ_fin), + "FiniteMPS, nearest-neighbour" => (transverse_field_ising(Float64; g = 4.0, L = L_fin), ψ_fin), + "InfiniteMPS, long-range" => (long_range_ising_infinite(Float64; L = L_inf), ψ_inf), + "InfiniteMPS, nearest-neighbour" => (repeat(transverse_field_ising(Float64; g = 4.0), 3), ψ_inf), + ] + + @testset "$name" for (name, (H, ψ)) in models + @test scalartype(H) <: Real + @test scalartype(ψ) <: Complex + + # `complex(H)` takes the fast path, `H` the converting one: both must agree + Hc = complex(H) + @test scalartype(Hc) <: Complex + envs = environments(ψ, H, ψ) + envs_c = environments(ψ, Hc, ψ) + @test expectation_value(ψ, H, envs) ≈ expectation_value(ψ, Hc, envs_c) + + L = length(ψ) + bonds = isfinite(H) ? (1:(L - 1)) : (1:L) + + @testset "AC" begin + for site in 1:L + AC = ψ.AC[site] + @test AC_hamiltonian(site, ψ, H, ψ, envs)(AC) ≈ + AC_hamiltonian(site, ψ, Hc, ψ, envs_c)(AC) + end + end + + @testset "AC2" begin + for site in bonds + AC2 = _transpose_front(ψ.AC[site]) * _transpose_tail(ψ.AR[site + 1]) + @test AC2_hamiltonian(site, ψ, H, ψ, envs)(AC2) ≈ + AC2_hamiltonian(site, ψ, Hc, ψ, envs_c)(AC2) + end + end + + @testset "C" begin + for site in bonds + C = ψ.C[site] + @test C_hamiltonian(site, ψ, H, ψ, envs)(C) ≈ + C_hamiltonian(site, ψ, Hc, ψ, envs_c)(C) + end + end + end +end + +@testset "FiniteMPS derivatives reproduce the expectation value" verbose = true begin + # in mixed gauge the derivative operators contract everything but the center site(s), + # so their expectation value is the full energy, independent of where we sit + D = 8 + L = 10 + models = [ + "long-range" => long_range_ising(Float64; L = L), + "nearest-neighbour" => transverse_field_ising(Float64; g = 4.0, L = L), + ] + + @testset "$name" for (name, H) in models + for T in (Float64, ComplexF64) + ψ = normalize!(FiniteMPS(randn, T, L, ℂ^2, ℂ^D)) + envs = environments(ψ, H, ψ) + E = expectation_value(ψ, H, envs) + + for site in 1:L + AC = ψ.AC[site] + @test dot(AC, AC_hamiltonian(site, ψ, H, ψ, envs)(AC)) ≈ E + end + for site in 1:(L - 1) + AC2 = _transpose_front(ψ.AC[site]) * _transpose_tail(ψ.AR[site + 1]) + @test dot(AC2, AC2_hamiltonian(site, ψ, H, ψ, envs)(AC2)) ≈ E + end + end + end +end From 50f1f9d7f277929c7c201cc7a10ea892c6de8202 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Wed, 23 Sep 2026 09:19:42 -0400 Subject: [PATCH 5/9] move derivatives test to test/hamiltonian/ to match reorganized test suite --- test/{operators => hamiltonian}/derivatives.jl | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename test/{operators => hamiltonian}/derivatives.jl (100%) diff --git a/test/operators/derivatives.jl b/test/hamiltonian/derivatives.jl similarity index 100% rename from test/operators/derivatives.jl rename to test/hamiltonian/derivatives.jl From 848af9493c6c83cf25ffa944c5c5ea0162d1e966 Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Fri, 31 Jul 2026 14:44:02 +0200 Subject: [PATCH 6/9] discover and add to the changelog --- docs/src/changelog.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/src/changelog.md b/docs/src/changelog.md index 464097003..08b51e1c1 100644 --- a/docs/src/changelog.md +++ b/docs/src/changelog.md @@ -123,6 +123,11 @@ When releasing a new version, move the "Unreleased" changes to a new version sec return value regardless, so this adds no extra cost. ([#509](https://github.com/QuantumKitHub/MPSKit.jl/pull/509)) - `make_time_mpo` with `TaylorCluster` on a Hamiltonian whose virtual bond dimension varies along the chain are now correctly handled. ([#511](https://github.com/QuantumKitHub/MPSKit.jl/pull/511)) +- The converting constructor of `JordanMPO_AC_Hamiltonian` assigned the "finished" block `E` to + the "ending" field `B` whenever `B` was absent, raising a `convert` `MethodError` from deep + inside `AC_hamiltonian`. This is reached when an `MPOHamiltonian` whose scalartype differs from + the state's has an on-site term on a site where no interaction ends, such as a long-range one. + ([#493](https://github.com/QuantumKitHub/MPSKit.jl/pull/493)) ### Performance From 196b1db0f49327cdf75246185257879ed6cbc67c Mon Sep 17 00:00:00 2001 From: lkdvos Date: Wed, 23 Sep 2026 11:44:47 -0400 Subject: [PATCH 7/9] Bump BlockTensorKit compat to 0.3.19 Versions <= 0.3.18 silently drop data on permuted contractions into sparse block tensors under TensorKit 0.17.2 (fixed upstream in BlockTensorKit#78 / v0.3.19), which showed up as spurious failures in the newly-added derivative-operator regression tests near FiniteMPS chain boundaries. --- docs/src/changelog.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/src/changelog.md b/docs/src/changelog.md index 08b51e1c1..6d8f5ff89 100644 --- a/docs/src/changelog.md +++ b/docs/src/changelog.md @@ -128,6 +128,10 @@ When releasing a new version, move the "Unreleased" changes to a new version sec inside `AC_hamiltonian`. This is reached when an `MPOHamiltonian` whose scalartype differs from the state's has an on-site term on a site where no interaction ends, such as a long-range one. ([#493](https://github.com/QuantumKitHub/MPSKit.jl/pull/493)) +- Raised the `BlockTensorKit` compat lower bound to 0.3.19, which fixes a silent data-loss bug: with + `TensorKit` 0.17.2 and `BlockTensorKit` <= 0.3.18, a permuted contraction into a sparse block + tensor (e.g. an environment sweep near a `FiniteMPS` chain boundary when the operator and state + have different `scalartype`s) could silently drop data instead of erroring. ### Performance From 874fbaeddec8d5cea471f1f0d7541fff88e2a1f0 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Thu, 24 Sep 2026 13:47:20 -0400 Subject: [PATCH 8/9] Fold long-range Hamiltonian coverage into groundstate.jl model/algorithm loops Instead of a separate "Long-range Hamiltonian with real scalartype" testset, add the long-range, real-scalartype model as an extra entry in the existing FiniteMPS/InfiniteMPS ground state model lists, so every algorithm gets exercised against the scalartype-mismatch code path the converting JordanMPO_AC_Hamiltonian constructor takes. Reusing the same sectortype keeps the added wall time small (~40s vs ~2.5min for the nearest-neighbour model). GradientGrassmann needs an explicit `tol`: without it the optimizer can overshoot past the point where the gradient is floating-point noise, and the CG line search's step-size formula hits a 0/0 that produces a NaN tangent, which the SVD correctly rejects. variance() throws a SpaceMismatch for a real, long-range InfiniteMPOHamiltonian against a complex state (QuantumKitHub/MPSKit.jl#524); the affected checks are marked @test_broken until that's fixed upstream. Co-Authored-By: Claude Sonnet 5 --- test/groundstate/groundstate.jl | 337 ++++++++++++++++---------------- 1 file changed, 170 insertions(+), 167 deletions(-) diff --git a/test/groundstate/groundstate.jl b/test/groundstate/groundstate.jl index f1b2439d0..b9d976816 100644 --- a/test/groundstate/groundstate.jl +++ b/test/groundstate/groundstate.jl @@ -20,132 +20,159 @@ verbosity_conv = 1 D = 6 L = 10 - H = force_planar(transverse_field_ising(; g, L)) - - @testset "DMRG" begin - ψ₀ = FiniteMPS(randn, ComplexF64, L, ℙ^2, ℙ^D) - v₀ = variance(ψ₀, H) + models = [ + "nearest-neighbour" => force_planar(transverse_field_ising(; g, L)), + "long-range, real scalartype" => force_planar(long_range_ising(Float64; g, L)), + ] - # test logging - ψ, envs, δ = find_groundstate( - ψ₀, H, DMRG(; verbosity = verbosity_full, maxiter = 2) - ) + @testset "$name" for (name, H) in models + @testset "DMRG" begin + ψ₀ = FiniteMPS(randn, ComplexF64, L, ℙ^2, ℙ^D) + v₀ = variance(ψ₀, H) - ψ, envs, δ = find_groundstate( - ψ, H, DMRG(; verbosity = verbosity_conv, maxiter = 10), envs - ) - v = variance(ψ, H) + # test logging + ψ, envs, δ = find_groundstate( + ψ₀, H, DMRG(; verbosity = verbosity_full, maxiter = 2) + ) - # test using low variance - @test sum(δ) ≈ 0 atol = 1.0e-3 - @test v < v₀ - @test v < 1.0e-2 + ψ, envs, δ = find_groundstate( + ψ, H, DMRG(; verbosity = verbosity_conv, maxiter = 10), envs + ) + v = variance(ψ, H) + + # test using low variance + @test sum(δ) ≈ 0 atol = 1.0e-3 + @test v < v₀ + @test v < 1.0e-2 + + # the algorithm object carries no scratch space of its own - the sweep's allocator is + # obtained per solve - so re-using one across solves has to reproduce the answer + alg = DMRG(; verbosity = verbosity_conv, maxiter = 10) + ψ1, = find_groundstate(ψ₀, H, alg) + ψ2, = find_groundstate(ψ₀, H, alg) + @test expectation_value(ψ1, H) ≈ expectation_value(ψ2, H) atol = 1.0e-10 + end - # the algorithm object carries no scratch space of its own - the sweep's allocator is - # obtained per solve - so re-using one across solves has to reproduce the answer - alg = DMRG(; verbosity = verbosity_conv, maxiter = 10) - ψ1, = find_groundstate(ψ₀, H, alg) - ψ2, = find_groundstate(ψ₀, H, alg) - @test expectation_value(ψ1, H) ≈ expectation_value(ψ2, H) atol = 1.0e-10 - end + @testset "DMRG2" begin + ψ₀ = FiniteMPS(randn, ComplexF64, L, ℙ^2, ℙ^D) + v₀ = variance(ψ₀, H) + trunc = truncrank(floor(Int, D * 1.5)) + # test logging + ψ, envs, δ = find_groundstate( + ψ₀, H, DMRG2(; verbosity = verbosity_full, maxiter = 2, trunc) + ) - @testset "DMRG2" begin - ψ₀ = FiniteMPS(randn, ComplexF64, 10, ℙ^2, ℙ^D) - v₀ = variance(ψ₀, H) - trunc = truncrank(floor(Int, D * 1.5)) - # test logging - ψ, envs, δ = find_groundstate( - ψ₀, H, DMRG2(; verbosity = verbosity_full, maxiter = 2, trunc) - ) + ψ, envs, δ = find_groundstate( + ψ, H, DMRG2(; verbosity = verbosity_conv, maxiter = 10, trunc), envs + ) + v = variance(ψ, H) - ψ, envs, δ = find_groundstate( - ψ, H, DMRG2(; verbosity = verbosity_conv, maxiter = 10, trunc), envs - ) - v = variance(ψ, H) + # test using low variance + @test sum(δ) ≈ 0 atol = 1.0e-3 + @test v < v₀ + @test v < 1.0e-2 + end - # test using low variance - @test sum(δ) ≈ 0 atol = 1.0e-3 - @test v < v₀ - @test v < 1.0e-2 - end + @testset "CBEDMRG" begin + # start from a small bond so the bond expansion is exercised + ψ₀ = FiniteMPS(randn, ComplexF64, L, ℙ^2, ℙ^(D ÷ 2)) + v₀ = variance(ψ₀, H) + expand = OptimalExpand(; trunc = truncrank(D ÷ 2)) + trunc = truncrank(D) - @testset "CBEDMRG" begin - # start from a small bond so the bond expansion is exercised - ψ₀ = FiniteMPS(randn, ComplexF64, L, ℙ^2, ℙ^(D ÷ 2)) - v₀ = variance(ψ₀, H) - expand = OptimalExpand(; trunc = truncrank(D ÷ 2)) - trunc = truncrank(D) + # test logging + ψ, envs, δ = find_groundstate( + ψ₀, H, DMRG(; verbosity = verbosity_full, maxiter = 2, alg_expand = expand, trunc) + ) - # test logging - ψ, envs, δ = find_groundstate( - ψ₀, H, DMRG(; verbosity = verbosity_full, maxiter = 2, alg_expand = expand, trunc) - ) + ψ, envs, δ = find_groundstate( + ψ, H, DMRG(; verbosity = verbosity_conv, maxiter = 10, alg_expand = expand, trunc), envs + ) + v = variance(ψ, H) + + # test using low variance + @test sum(δ) ≈ 0 atol = 1.0e-3 + @test v < v₀ + @test v < 1.0e-2 + # the bond should have grown to the truncation target + @test dim(left_virtualspace(ψ, L ÷ 2)) == D + end - ψ, envs, δ = find_groundstate( - ψ, H, DMRG(; verbosity = verbosity_conv, maxiter = 10, alg_expand = expand, trunc), envs - ) - v = variance(ψ, H) + @testset "CBEDMRG (SketchedExpand)" begin + # randomized bond expansion at single-site cost. The sketch is redrawn every sweep, so an + # aggressive expansion (a large fraction of the bond) keeps the single-site Galerkin error + # noisy; a gentle per-sweep increment lets it converge like the deterministic expanders. + Random.seed!(1234) + ψ₀ = FiniteMPS(randn, ComplexF64, L, ℙ^2, ℙ^(D ÷ 2)) + v₀ = variance(ψ₀, H) + expand = SketchedExpand(; trunc = truncrank(2), oversampling = 4) + trunc = truncrank(D) - # test using low variance - @test sum(δ) ≈ 0 atol = 1.0e-3 - @test v < v₀ - @test v < 1.0e-2 - # the bond should have grown to the truncation target - @test dim(left_virtualspace(ψ, L ÷ 2)) == D - end + # test logging + ψ, envs, δ = find_groundstate( + ψ₀, H, DMRG(; verbosity = verbosity_full, maxiter = 2, alg_expand = expand, trunc) + ) - @testset "CBEDMRG (SketchedExpand)" begin - # randomized bond expansion at single-site cost. The sketch is redrawn every sweep, so an - # aggressive expansion (a large fraction of the bond) keeps the single-site Galerkin error - # noisy; a gentle per-sweep increment lets it converge like the deterministic expanders. - Random.seed!(1234) - ψ₀ = FiniteMPS(randn, ComplexF64, L, ℙ^2, ℙ^(D ÷ 2)) - v₀ = variance(ψ₀, H) - expand = SketchedExpand(; trunc = truncrank(2), oversampling = 4) - trunc = truncrank(D) + ψ, envs, δ = find_groundstate( + ψ, H, DMRG(; verbosity = verbosity_conv, maxiter = 15, alg_expand = expand, trunc), envs + ) + v = variance(ψ, H) + + # test using low variance + @test sum(δ) ≈ 0 atol = 1.0e-3 + @test v < v₀ + @test v < 1.0e-2 + # the bond should have grown to the truncation target + @test dim(left_virtualspace(ψ, L ÷ 2)) == D + end - # test logging - ψ, envs, δ = find_groundstate( - ψ₀, H, DMRG(; verbosity = verbosity_full, maxiter = 2, alg_expand = expand, trunc) - ) + @testset "DMRG3S" begin + # start from a small bond so the post-expansion is exercised, mirroring CBEDMRG above + Random.seed!(1234) + ψ₀ = FiniteMPS(randn, ComplexF64, L, ℙ^2, ℙ^(D ÷ 2)) + v₀ = variance(ψ₀, H) + alg_gauge = DMRG3S(0.1, ExponentialDecay(0.7)) # TODO: match final constructor API + trunc = truncrank(D) - ψ, envs, δ = find_groundstate( - ψ, H, DMRG(; verbosity = verbosity_conv, maxiter = 15, alg_expand = expand, trunc), envs - ) - v = variance(ψ, H) + # test logging + ψ, envs, δ = find_groundstate( + ψ₀, H, DMRG(; verbosity = verbosity_full, maxiter = 2, alg_gauge, trunc) + ) - # test using low variance - @test sum(δ) ≈ 0 atol = 1.0e-3 - @test v < v₀ - @test v < 1.0e-2 - # the bond should have grown to the truncation target - @test dim(left_virtualspace(ψ, L ÷ 2)) == D - end + ψ, envs, δ = find_groundstate( + ψ, H, DMRG(; verbosity = verbosity_conv, maxiter = 10, alg_gauge, trunc), envs + ) + v = variance(ψ, H) + + # test using low variance + @test sum(δ) ≈ 0 atol = 1.0e-3 + @test v < v₀ + @test v < 1.0e-2 + # the bond should have grown to the truncation target + @test dim(left_virtualspace(ψ, L ÷ 2)) == D + end - @testset "DMRG3S" begin - # start from a small bond so the post-expansion is exercised, mirroring CBEDMRG above - Random.seed!(1234) - ψ₀ = FiniteMPS(randn, ComplexF64, L, ℙ^2, ℙ^(D ÷ 2)) - v₀ = variance(ψ₀, H) - alg_gauge = DMRG3S(0.1, ExponentialDecay(0.7)) # TODO: match final constructor API - trunc = truncrank(D) + @testset "GradientGrassmann" begin + ψ₀ = FiniteMPS(randn, ComplexF64, L, ℙ^2, ℙ^D) + v₀ = variance(ψ₀, H) - # test logging - ψ, envs, δ = find_groundstate( - ψ₀, H, DMRG(; verbosity = verbosity_full, maxiter = 2, alg_gauge, trunc) - ) + # test logging + ψ, envs, δ = find_groundstate( + ψ₀, H, GradientGrassmann(; verbosity = verbosity_full, maxiter = 2) + ) - ψ, envs, δ = find_groundstate( - ψ, H, DMRG(; verbosity = verbosity_conv, maxiter = 10, alg_gauge, trunc), envs - ) - v = variance(ψ, H) + # an explicit `tol` keeps the optimizer from overshooting past the point where the + # gradient is floating-point noise: pushed further, the CG line search can hit a + # 0/0 in its step-size formula and feed a NaN tangent into the Grassmann retraction + ψ, envs, δ = find_groundstate( + ψ, H, GradientGrassmann(; tol, verbosity = verbosity_conv, maxiter = 50), envs + ) + v = variance(ψ, H) - # test using low variance - @test sum(δ) ≈ 0 atol = 1.0e-3 - @test v < v₀ - @test v < 1.0e-2 - # the bond should have grown to the truncation target - @test dim(left_virtualspace(ψ, L ÷ 2)) == D + # test using low variance + @test sum(δ) ≈ 0 atol = 1.0e-3 + @test v < v₀ && v < 1.0e-2 + end end fast_tests || @testset "DMRG3S escapes local minimum (Hubig et al. 2015, Sec. VII A)" begin @@ -173,25 +200,6 @@ verbosity_conv = 1 @test E_escape < E_stuck - 1.0 @test isapprox(E_escape, -8.6824724; atol = 1.0e-4) end - - @testset "GradientGrassmann" begin - ψ₀ = FiniteMPS(randn, ComplexF64, 10, ℙ^2, ℙ^D) - v₀ = variance(ψ₀, H) - - # test logging - ψ, envs, δ = find_groundstate( - ψ₀, H, GradientGrassmann(; verbosity = verbosity_full, maxiter = 2) - ) - - ψ, envs, δ = find_groundstate( - ψ, H, GradientGrassmann(; verbosity = verbosity_conv, maxiter = 50), envs - ) - v = variance(ψ, H) - - # test using low variance - @test sum(δ) ≈ 0 atol = 1.0e-3 - @test v < v₀ && v < 1.0e-2 - end end @testset "InfiniteMPS ground state" verbose = true begin @@ -227,6 +235,26 @@ end @test v < 1.0e-2 end + # the long-range model forces `JordanMPO_AC_Hamiltonian` through its converting outer + # constructor (real `H`, complex `ψ`), exercising a code path the nearest-neighbour model + # never reaches. Its `(1, L)` coupling needs an explicit unit cell, so it does not fit the + # `unit_cell_size`/scheduler sweep above and is checked on its own instead. + @testset "VUMPS (long-range, real scalartype)" begin + H = force_planar(long_range_ising_infinite(Float64; g, L = 3)) + ψ₀ = InfiniteMPS(fill(ℙ^2, 3), fill(ℙ^D, 3)) + + ψ′, envs, δ = find_groundstate(ψ₀, H, VUMPS(; tol, verbosity = verbosity_conv, maxiter = 20)) + + @test sum(δ) ≈ 0 atol = 1.0e-3 + # `variance` throws a `SpaceMismatch` for a real, long-range `InfiniteMPOHamiltonian` + # against a complex state: https://github.com/QuantumKitHub/MPSKit.jl/issues/524 + @test_broken try + variance(ψ₀, H) > variance(ψ′, H, envs) + catch + false + end + end + @testset "IDMRG" for unit_cell_size in [1, 3] ψ = unit_cell_size == 1 ? InfiniteMPS(ℙ^2, ℙ^D) : repeat(ψ, unit_cell_size) H = repeat(H_ref, unit_cell_size) @@ -245,6 +273,22 @@ end @test v < 1.0e-2 end + @testset "IDMRG (long-range, real scalartype)" begin + H = force_planar(long_range_ising_infinite(Float64; g, L = 3)) + ψ₀ = InfiniteMPS(fill(ℙ^2, 3), fill(ℙ^D, 3)) + + ψ, envs, δ = find_groundstate(ψ₀, H, IDMRG(; tol, verbosity = verbosity_conv, maxiter = 20)) + + @test sum(δ) ≈ 0 atol = 1.0e-3 + # `variance` throws a `SpaceMismatch` for a real, long-range `InfiniteMPOHamiltonian` + # against a complex state: https://github.com/QuantumKitHub/MPSKit.jl/issues/524 + @test_broken try + variance(ψ₀, H) > variance(ψ, H, envs) + catch + false + end + end + @testset "IDMRG2" begin ψ = repeat(InfiniteMPS(ℙ^2, ℙ^D), 2) H = repeat(H_ref, 2) @@ -323,47 +367,6 @@ end end end -@testset "Long-range Hamiltonian with real scalartype" verbose = true begin - # force `JordanMPO_AC_Hamiltonian` to pass through fallback outer constructor - # through an onsite block `D` with scalartype != that of the MPS - # as `D`'s scalartype doesn't get promoted to the MPS's scalartype through some contraction with environments - tol = 1.0e-8 - D = 8 - - @testset "FiniteMPS" begin - L = 10 - H = long_range_ising(Float64; L) - @test scalartype(H) <: Real - - ψ₀ = FiniteMPS(randn, ComplexF64, L, ℂ^2, ℂ^D) - # `complex(H)` takes the non-converting construction path - E_ref = expectation_value( - find_groundstate(ψ₀, complex(H), DMRG(; tol, verbosity = verbosity_conv))[1], H - ) - for alg in ( - DMRG(; tol, verbosity = verbosity_conv), - DMRG2(; tol, verbosity = verbosity_conv, trunc = truncrank(D)), - ) - ψ, envs, δ = find_groundstate(ψ₀, H, alg) - @test expectation_value(ψ, H, envs) ≈ E_ref atol = 1.0e-6 - end - end - - @testset "InfiniteMPS" begin - H = long_range_ising_infinite(Float64; L = 3) - @test scalartype(H) <: Real - - ψ₀ = InfiniteMPS(randn, ComplexF64, fill(ℂ^2, 3), fill(ℂ^D, 3)) - E_ref = expectation_value( - find_groundstate(ψ₀, complex(H), VUMPS(; tol, verbosity = verbosity_conv))[1], H - ) - for alg in (VUMPS(; tol, verbosity = verbosity_conv), IDMRG(; tol, verbosity = verbosity_conv)) - ψ, envs, δ = find_groundstate(ψ₀, H, alg) - @test expectation_value(ψ, H, envs) ≈ E_ref atol = 1.0e-6 - end - end -end - @testset "LazySum FiniteMPS ground state" verbose = true begin tol = 1.0e-8 D = 15 From 4b52e55f67f8cd364475662371d541994c709119 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Fri, 25 Sep 2026 21:35:02 -0400 Subject: [PATCH 9/9] Simplify groundstate.jl comments and drop redundant DMRG-reuse check Co-Authored-By: Claude Sonnet 5 --- test/groundstate/groundstate.jl | 36 ++++++++++----------------------- 1 file changed, 11 insertions(+), 25 deletions(-) diff --git a/test/groundstate/groundstate.jl b/test/groundstate/groundstate.jl index b9d976816..b31b1512a 100644 --- a/test/groundstate/groundstate.jl +++ b/test/groundstate/groundstate.jl @@ -44,13 +44,6 @@ verbosity_conv = 1 @test sum(δ) ≈ 0 atol = 1.0e-3 @test v < v₀ @test v < 1.0e-2 - - # the algorithm object carries no scratch space of its own - the sweep's allocator is - # obtained per solve - so re-using one across solves has to reproduce the answer - alg = DMRG(; verbosity = verbosity_conv, maxiter = 10) - ψ1, = find_groundstate(ψ₀, H, alg) - ψ2, = find_groundstate(ψ₀, H, alg) - @test expectation_value(ψ1, H) ≈ expectation_value(ψ2, H) atol = 1.0e-10 end @testset "DMRG2" begin @@ -214,6 +207,7 @@ end H_ref_realT = force_planar(transverse_field_ising(Float64; g)) @test variance(ψ, H_ref_realT) ≈ v₀ atol = 1.0e-10 + @testset "VUMPS (unit cell $unit_cell_size, $schedname)" for unit_cell_size in [1, 3], (schedname, scheduler) in SCHEDULERS @@ -235,24 +229,18 @@ end @test v < 1.0e-2 end - # the long-range model forces `JordanMPO_AC_Hamiltonian` through its converting outer - # constructor (real `H`, complex `ψ`), exercising a code path the nearest-neighbour model - # never reaches. Its `(1, L)` coupling needs an explicit unit cell, so it does not fit the - # `unit_cell_size`/scheduler sweep above and is checked on its own instead. @testset "VUMPS (long-range, real scalartype)" begin H = force_planar(long_range_ising_infinite(Float64; g, L = 3)) ψ₀ = InfiniteMPS(fill(ℙ^2, 3), fill(ℙ^D, 3)) + v₀ = variance(ψ₀, H) ψ′, envs, δ = find_groundstate(ψ₀, H, VUMPS(; tol, verbosity = verbosity_conv, maxiter = 20)) + v = variance(ψ′, H, envs) + # test using low variance @test sum(δ) ≈ 0 atol = 1.0e-3 - # `variance` throws a `SpaceMismatch` for a real, long-range `InfiniteMPOHamiltonian` - # against a complex state: https://github.com/QuantumKitHub/MPSKit.jl/issues/524 - @test_broken try - variance(ψ₀, H) > variance(ψ′, H, envs) - catch - false - end + @test v < v₀ + @test v < 1.0e-2 end @testset "IDMRG" for unit_cell_size in [1, 3] @@ -276,17 +264,15 @@ end @testset "IDMRG (long-range, real scalartype)" begin H = force_planar(long_range_ising_infinite(Float64; g, L = 3)) ψ₀ = InfiniteMPS(fill(ℙ^2, 3), fill(ℙ^D, 3)) + v₀ = variance(ψ₀, H) ψ, envs, δ = find_groundstate(ψ₀, H, IDMRG(; tol, verbosity = verbosity_conv, maxiter = 20)) + v = variance(ψ, H, envs) + # test using low variance @test sum(δ) ≈ 0 atol = 1.0e-3 - # `variance` throws a `SpaceMismatch` for a real, long-range `InfiniteMPOHamiltonian` - # against a complex state: https://github.com/QuantumKitHub/MPSKit.jl/issues/524 - @test_broken try - variance(ψ₀, H) > variance(ψ, H, envs) - catch - false - end + @test v < v₀ + @test v < 1.0e-2 end @testset "IDMRG2" begin