From c390beca2cc54a102e4ce5353ab6857b17107cb1 Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Mon, 4 Mar 2024 19:06:37 +0100 Subject: [PATCH 001/213] Add truncated SVD adjoint with wrapper for KrylovKit iterative SVD, add small test script --- Project.toml | 1 + examples/test_svd_adjoint.jl | 99 ++++++++++++++++++++++++ src/PEPSKit.jl | 4 +- src/utility/svd.jl | 143 +++++++++++++++++++++++++++++++++++ 4 files changed, 246 insertions(+), 1 deletion(-) create mode 100644 examples/test_svd_adjoint.jl create mode 100644 src/utility/svd.jl diff --git a/Project.toml b/Project.toml index 8028adf6..d5e3a930 100644 --- a/Project.toml +++ b/Project.toml @@ -15,6 +15,7 @@ Parameters = "d96e819e-fc66-5662-9728-84c9c7592b0a" Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7" TensorKit = "07d1fe3e-3e46-537d-9eac-e9e13d0d4cec" VectorInterface = "409d34a3-91d5-4945-b6ec-7529ddf182d8" +Zygote = "e88e6eb3-aa80-5325-afca-941959d7151f" [compat] MPSKit = "0.10" diff --git a/examples/test_svd_adjoint.jl b/examples/test_svd_adjoint.jl new file mode 100644 index 00000000..e11f0eff --- /dev/null +++ b/examples/test_svd_adjoint.jl @@ -0,0 +1,99 @@ +using LinearAlgebra +using TensorKit +using ChainRulesCore, Zygote +using PEPSKit + +# Non-proper truncated SVD with outdated adjoint +oldsvd(t::AbstractTensorMap, χ::Int; kwargs...) = itersvd(t, χ; kwargs...) + +# Outdated adjoint not taking truncated part into account +function ChainRulesCore.rrule( + ::typeof(oldsvd), t::AbstractTensorMap, χ::Int; εbroad=0, kwargs... +) + U, S, V = oldsvd(t, χ; kwargs...) + + function oldsvd_pullback((ΔU, ΔS, ΔV)) + ∂t = similar(t) + for (c, b) in blocks(∂t) + copyto!( + b, + oldsvd_rev( + block(U, c), + block(S, c), + block(V, c), + block(ΔU, c), + block(ΔS, c), + block(ΔV, c); + εbroad, + ), + ) + end + return NoTangent(), ∂t, NoTangent() + end + + return (U, S, V), oldsvd_pullback +end + +function oldsvd_rev( + U::AbstractMatrix, + S::AbstractMatrix, + V::AbstractMatrix, + ΔU, + ΔS, + ΔV; + εbroad=0, + atol::Real=0, + rtol::Real=atol > 0 ? 0 : eps(scalartype(S))^(3 / 4), +) + S = diagm(S) + V = copy(V') + + tol = atol > 0 ? atol : rtol * S[1, 1] + F = PEPSKit.invert_S²(S, tol; εbroad) # Includes Lorentzian broadening + S⁻¹ = pinv(S; atol=tol) + + # dS contribution + term = ΔS isa ZeroTangent ? ΔS : Diagonal(diag(ΔS)) + + # dU₁ and dV₁ off-diagonal contribution + J = F .* (U' * ΔU) + term += (J + J') * S + VΔV = (V * ΔV') + K = F .* VΔV + term += S * (K + K') + + # dV₁ diagonal contribution (diagonal of dU₁ is gauged away) + if scalartype(U) <: Complex && !(ΔV isa ZeroTangent) && !(ΔU isa ZeroTangent) + L = Diagonal(diag(VΔV)) + term += 0.5 * S⁻¹ * (L' - L) + end + ΔA = U * term * V + + # Projector contribution for non-square A + UUd = U * U' + VdV = V' * V + Uproj = one(UUd) - UUd + Vproj = one(VdV) - VdV + ΔA += Uproj * ΔU * S⁻¹ * V + U * S⁻¹ * ΔV * Vproj # Old wrong stuff + + return ΔA +end + +# Loss function taking the nfirst first singular vectors into account +function nfirst_loss(A, svdfunc; nfirst=1) + U, _, V = svdfunc(A) + U = convert(Array, U) + V = convert(Array, V) + return real(sum([U[i, i] * V[i, i] for i in 1:nfirst])) +end + +m, n = 30, 20 +dtype = ComplexF64 +χ = 15 +r = TensorMap(randn, dtype, ℂ^m ← ℂ^n) + +ltensorkit, gtensorkit = withgradient(A -> nfirst_loss(A, x -> oldsvd(x, χ); nfirst=3), r) +litersvd, gitersvd = withgradient(A -> nfirst_loss(A, x -> itersvd(x, χ); nfirst=3), r) + +@show ltensorkit ≈ litersvd +@show gtensorkit ≈ gitersvd \ No newline at end of file diff --git a/src/PEPSKit.jl b/src/PEPSKit.jl index b2dcebbe..456482e0 100644 --- a/src/PEPSKit.jl +++ b/src/PEPSKit.jl @@ -6,12 +6,13 @@ using TensorKit, KrylovKit, MPSKit, OptimKit, Base.Threads, Base.Iterators, Parameters, Printf using ChainRulesCore -using LinearAlgebra: LinearAlgebra +using LinearAlgebra export CTMRG, CTMRG2 export leading_boundary include("utility/util.jl") +include("utility/svd.jl") include("states/abstractpeps.jl") include("states/infinitepeps.jl") @@ -46,5 +47,6 @@ export InfinitePEPO, InfiniteTransferPEPO export initializeMPS, initializePEPS export PEPOOptimize, pepo_opt_environments export symmetrize, None, Depth, Full +export itersvd end # module diff --git a/src/utility/svd.jl b/src/utility/svd.jl new file mode 100644 index 00000000..d3b149d0 --- /dev/null +++ b/src/utility/svd.jl @@ -0,0 +1,143 @@ +# Computation of F in SVD adjoint, including Lorentzian broadening +function invert_S²(S::AbstractMatrix{T}, tol::Real; εbroad=0) where {T<:Real} + F = similar(S) + @inbounds for i in axes(F, 1), j in axes(F, 2) + F[i, j] = if i == j + zero(T) + else + sᵢ, sⱼ = S[i, i], S[j, j] + Δs = abs(sⱼ - sᵢ) < tol ? tol : sⱼ^2 - sᵢ^2 + εbroad > 0 && (Δs = lorentz_broaden(Δs, εbroad)) + 1 / Δs + end + end + return F +end + +# Lorentzian broadening for SVD adjoint singularities +function lorentz_broaden(x::Real, ε=1e-12) + x′ = 1 / x + return x′ / (x′^2 + ε) +end + +# Proper truncated SVD using iterative solver +function itersvd( + t::AbstractTensorMap, + χ::Int; + εbroad=0, + solverkwargs=(; krylovdim=χ + 5, tol=1e2eps(real(scalartype(t)))), +) + vals, lvecs, rvecs, info = svdsolve(t.data, dim(codomain(t)), χ; solverkwargs...) + truncspace = field(t)^χ + if info.converged < χ # Fall back to dense SVD + @warn "falling back to dense SVD solver since length(S) < χ" + return tsvd(t; trunc=truncdim(χ), alg=TensorKit.SVD()) + else + vals = @view(vals[1:χ]) + lvecs = @view(lvecs[1:χ]) + rvecs = @view(rvecs[1:χ]) + end + U = TensorMap(hcat(lvecs...), codomain(t) ← truncspace) + S = TensorMap(diagm(vals), truncspace ← truncspace) + V = TensorMap(copy(hcat(rvecs...)'), truncspace ← domain(t)) + return U, S, V +end + +# Reverse rule adopted from tsvd! rrule as found in TensorKit.jl +function ChainRulesCore.rrule( + ::typeof(itersvd), t::AbstractTensorMap, χ::Int; εbroad=0, kwargs... +) + U, S, V = itersvd(t, χ; kwargs...) + + function itersvd_pullback((ΔU, ΔS, ΔV)) + ∂t = similar(t) + for (c, b) in blocks(∂t) + copyto!( + b, + itersvd_rev( + block(t, c), + block(U, c), + block(S, c), + block(V, c), + block(ΔU, c), + block(ΔS, c), + block(ΔV, c); + εbroad, + ), + ) + end + return NoTangent(), ∂t, NoTangent() + end + + return (U, S, V), itersvd_pullback +end + +# SVD adjoint with proper truncation +function itersvd_rev( + A::AbstractMatrix, + U::AbstractMatrix, + S::AbstractMatrix, + V::AbstractMatrix, + ΔU, + ΔS, + ΔV; + εbroad=0, + atol::Real=0, + rtol::Real=atol > 0 ? 0 : eps(scalartype(S))^(3 / 4), +) + Ad = copy(A') + tol = atol > 0 ? atol : rtol * S[1, 1] + F = invert_S²(S, tol; εbroad) # Includes Lorentzian broadening + S⁻¹ = pinv(S; atol=tol) + + # dS contribution + term = ΔS isa ZeroTangent ? ΔS : Diagonal(real.(ΔS)) # Implicitly performs 𝕀 ∘ dS + + # dU₁ and dV₁ off-diagonal contribution + J = F .* (U' * ΔU) + term += (J + J') * S + VΔV = (V * ΔV') + K = F .* VΔV + term += S * (K + K') + + # dV₁ diagonal contribution (diagonal of dU₁ is gauged away) + if scalartype(U) <: Complex && !(ΔV isa ZeroTangent) && !(ΔU isa ZeroTangent) + L = Diagonal(VΔV) # Implicitly performs 𝕀 ∘ dV + term += 0.5 * S⁻¹ * (L' - L) + end + ΔA = U * term * V + + # Projector contribution for non-square A and dU₂ and dV₂ + UUd = U * U' + VdV = V' * V + Uproj = one(UUd) - UUd + Vproj = one(VdV) - VdV + m, k, n = size(U, 1), size(U, 2), size(V, 2) + dimγ = k * m # Vectorized dimension of γ-matrix + + # Truncation contribution from dU₂ and dV₂ + # TODO: Use KrylovKit instead of IterativeSolvers + Sop = LinearMap(k * m + k * n) do v # Left-preconditioned linear problem + γ = reshape(@view(v[1:dimγ]), (k, m)) + γd = reshape(@view(v[(dimγ + 1):end]), (k, n)) + Γ1 = γ - S⁻¹ * γd * Vproj * Ad + Γ2 = γd - S⁻¹ * γ * Uproj * A + vcat(reshape(Γ1, :), reshape(Γ2, :)) + end + if ΔU isa ZeroTangent && ΔV isa ZeroTangent + γ = gmres(Sop, zeros(eltype(A), k * m + k * n)) + else + # Explicit left-preconditioning + # Set relative tolerance to machine precision to converge SVD gradient error properly + γ = gmres( + Sop, + vcat(reshape(S⁻¹ * ΔU' * Uproj, :), reshape(S⁻¹ * ΔV * Vproj, :)); + reltol=eps(real(eltype(A))), + ) + end + γA = reshape(@view(γ[1:dimγ]), k, m) + γAd = reshape(@view(γ[(dimγ + 1):end]), k, n) + ΔA += Uproj * γA' * V + U * γAd * Vproj + + return ΔA +end From 51507ce14eae2fff8cfaa94089e58eb009e28b26 Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Tue, 5 Mar 2024 11:16:41 +0100 Subject: [PATCH 002/213] Use KrylovKit.linsolve for truncation linear problem, make loss function differentiable --- Project.toml | 1 + examples/test_svd_adjoint.jl | 34 +++++++++++++++++++--------------- src/utility/svd.jl | 28 ++++++++++++---------------- 3 files changed, 32 insertions(+), 31 deletions(-) diff --git a/Project.toml b/Project.toml index d5e3a930..2826518e 100644 --- a/Project.toml +++ b/Project.toml @@ -6,6 +6,7 @@ version = "0.1.0" [deps] Accessors = "7d9f7c33-5ae7-4f3b-8dc6-eff91059b697" ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" +ChainRulesTestUtils = "cdddcdb0-9152-4a09-a978-84456f9df70a" KrylovKit = "0b1a1467-8014-51b9-945f-bf0ae24f4b77" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" MPSKit = "bb1c41ca-d63c-52ed-829e-0820dda26502" diff --git a/examples/test_svd_adjoint.jl b/examples/test_svd_adjoint.jl index e11f0eff..fcd50f47 100644 --- a/examples/test_svd_adjoint.jl +++ b/examples/test_svd_adjoint.jl @@ -1,6 +1,6 @@ using LinearAlgebra using TensorKit -using ChainRulesCore, Zygote +using ChainRulesCore, ChainRulesTestUtils, Zygote using PEPSKit # Non-proper truncated SVD with outdated adjoint @@ -45,9 +45,6 @@ function oldsvd_rev( atol::Real=0, rtol::Real=atol > 0 ? 0 : eps(scalartype(S))^(3 / 4), ) - S = diagm(S) - V = copy(V') - tol = atol > 0 ? atol : rtol * S[1, 1] F = PEPSKit.invert_S²(S, tol; εbroad) # Includes Lorentzian broadening S⁻¹ = pinv(S; atol=tol) @@ -74,26 +71,33 @@ function oldsvd_rev( VdV = V' * V Uproj = one(UUd) - UUd Vproj = one(VdV) - VdV - ΔA += Uproj * ΔU * S⁻¹ * V + U * S⁻¹ * ΔV * Vproj # Old wrong stuff + ΔA += Uproj * ΔU * S⁻¹ * V + U * S⁻¹ * ΔV * Vproj # Wrong truncation contribution return ΔA end -# Loss function taking the nfirst first singular vectors into account -function nfirst_loss(A, svdfunc; nfirst=1) +# Gauge-invariant loss function +function lossfun(A, svdfunc) U, _, V = svdfunc(A) - U = convert(Array, U) - V = convert(Array, V) - return real(sum([U[i, i] * V[i, i] for i in 1:nfirst])) + # return real(sum((U * V).data)) # TODO: code up sum for AbstractTensorMap with rrule + return real(tr(U * V)) # trace only allows for m=n end -m, n = 30, 20 +m, n = 30, 30 dtype = ComplexF64 -χ = 15 +χ = 20 r = TensorMap(randn, dtype, ℂ^m ← ℂ^n) -ltensorkit, gtensorkit = withgradient(A -> nfirst_loss(A, x -> oldsvd(x, χ); nfirst=3), r) -litersvd, gitersvd = withgradient(A -> nfirst_loss(A, x -> itersvd(x, χ); nfirst=3), r) +println("Non-truncated SVD") +ltensorkit, gtensorkit = withgradient(A -> lossfun(A, x -> oldsvd(x, min(m, n))), r) +litersvd, gitersvd = withgradient(A -> lossfun(A, x -> itersvd(x, min(m, n))), r) +@show ltensorkit ≈ litersvd +@show norm(gtensorkit[1] - gitersvd[1]) +println("\nTruncated SVD to χ=$χ:") +ltensorkit, gtensorkit = withgradient(A -> lossfun(A, x -> oldsvd(x, χ)), r) +litersvd, gitersvd = withgradient(A -> lossfun(A, x -> itersvd(x, χ)), r) @show ltensorkit ≈ litersvd -@show gtensorkit ≈ gitersvd \ No newline at end of file +@show norm(gtensorkit[1] - gitersvd[1]) + +# TODO: Finite-difference check via test_rrule diff --git a/src/utility/svd.jl b/src/utility/svd.jl index d3b149d0..ee3cbd4b 100644 --- a/src/utility/svd.jl +++ b/src/utility/svd.jl @@ -116,28 +116,24 @@ function itersvd_rev( dimγ = k * m # Vectorized dimension of γ-matrix # Truncation contribution from dU₂ and dV₂ - # TODO: Use KrylovKit instead of IterativeSolvers - Sop = LinearMap(k * m + k * n) do v # Left-preconditioned linear problem - γ = reshape(@view(v[1:dimγ]), (k, m)) - γd = reshape(@view(v[(dimγ + 1):end]), (k, n)) - Γ1 = γ - S⁻¹ * γd * Vproj * Ad - Γ2 = γd - S⁻¹ * γ * Uproj * A - vcat(reshape(Γ1, :), reshape(Γ2, :)) + function svdlinprob(v) # Left-preconditioned linear problem + γ1 = reshape(@view(v[1:dimγ]), (k, m)) + γ2 = reshape(@view(v[(dimγ + 1):end]), (k, n)) + Γ1 = γ1 - S⁻¹ * γ2 * Vproj * Ad + Γ2 = γ2 - S⁻¹ * γ1 * Uproj * A + return vcat(reshape(Γ1, :), reshape(Γ2, :)) end if ΔU isa ZeroTangent && ΔV isa ZeroTangent - γ = gmres(Sop, zeros(eltype(A), k * m + k * n)) + γ = linsolve(Sop, zeros(eltype(A), k * m + k * n)) else # Explicit left-preconditioning # Set relative tolerance to machine precision to converge SVD gradient error properly - γ = gmres( - Sop, - vcat(reshape(S⁻¹ * ΔU' * Uproj, :), reshape(S⁻¹ * ΔV * Vproj, :)); - reltol=eps(real(eltype(A))), - ) + y = vcat(reshape(S⁻¹ * ΔU' * Uproj, :), reshape(S⁻¹ * ΔV * Vproj, :)) + γ, = linsolve(svdlinprob, y; rtol=eps(real(eltype(A)))) end - γA = reshape(@view(γ[1:dimγ]), k, m) - γAd = reshape(@view(γ[(dimγ + 1):end]), k, n) - ΔA += Uproj * γA' * V + U * γAd * Vproj + γA1 = reshape(@view(γ[1:dimγ]), k, m) + γA2 = reshape(@view(γ[(dimγ + 1):end]), k, n) + ΔA += Uproj * γA1' * V + U * γA2 * Vproj return ΔA end From d3a31fb55d1661d8cd0c01778f53fd64714b933d Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Tue, 5 Mar 2024 16:09:22 +0100 Subject: [PATCH 003/213] Improve loss function, compare SVD gradient with TensorKit.tsvd gradient --- examples/test_svd_adjoint.jl | 38 +++++++++++++++++++++--------------- src/utility/svd.jl | 1 + 2 files changed, 23 insertions(+), 16 deletions(-) diff --git a/examples/test_svd_adjoint.jl b/examples/test_svd_adjoint.jl index fcd50f47..69124870 100644 --- a/examples/test_svd_adjoint.jl +++ b/examples/test_svd_adjoint.jl @@ -3,7 +3,7 @@ using TensorKit using ChainRulesCore, ChainRulesTestUtils, Zygote using PEPSKit -# Non-proper truncated SVD with outdated adjoint +# Truncated SVD with outdated adjoint oldsvd(t::AbstractTensorMap, χ::Int; kwargs...) = itersvd(t, χ; kwargs...) # Outdated adjoint not taking truncated part into account @@ -77,27 +77,33 @@ function oldsvd_rev( end # Gauge-invariant loss function -function lossfun(A, svdfunc) +function lossfun(A, R=TensorMap(randn, space(A)), svdfunc=tsvd) U, _, V = svdfunc(A) - # return real(sum((U * V).data)) # TODO: code up sum for AbstractTensorMap with rrule - return real(tr(U * V)) # trace only allows for m=n + return real(dot(R, U * V)) # Overlap with random tensor R is gauge-invariant and differentiable, also for m≠n end -m, n = 30, 30 +m, n = 20, 30 dtype = ComplexF64 -χ = 20 +χ = 15 r = TensorMap(randn, dtype, ℂ^m ← ℂ^n) +R = TensorMap(randn, space(r)) -println("Non-truncated SVD") -ltensorkit, gtensorkit = withgradient(A -> lossfun(A, x -> oldsvd(x, min(m, n))), r) -litersvd, gitersvd = withgradient(A -> lossfun(A, x -> itersvd(x, min(m, n))), r) -@show ltensorkit ≈ litersvd +println("Non-truncated SVD:") +loldsvd, goldsvd = withgradient(A -> lossfun(A, R, x -> oldsvd(x, min(m, n))), r) +ltensorkit, gtensorkit = withgradient( + A -> lossfun(A, R, x -> tsvd(x; trunc=truncdim(min(m, n)))), r +) +litersvd, gitersvd = withgradient(A -> lossfun(A, R, x -> itersvd(x, min(m, n))), r) +@show loldsvd ≈ ltensorkit ≈ litersvd +@show norm(gtensorkit[1] - goldsvd[1]) @show norm(gtensorkit[1] - gitersvd[1]) -println("\nTruncated SVD to χ=$χ:") -ltensorkit, gtensorkit = withgradient(A -> lossfun(A, x -> oldsvd(x, χ)), r) -litersvd, gitersvd = withgradient(A -> lossfun(A, x -> itersvd(x, χ)), r) -@show ltensorkit ≈ litersvd +println("\nTruncated SVD with χ=$χ:") +loldsvd, goldsvd = withgradient(A -> lossfun(A, R, x -> oldsvd(x, χ)), r) +ltensorkit, gtensorkit = withgradient( + A -> lossfun(A, R, x -> tsvd(x; trunc=truncdim(χ))), r +) +litersvd, gitersvd = withgradient(A -> lossfun(A, R, x -> itersvd(x, χ)), r) +@show loldsvd ≈ ltensorkit ≈ litersvd +@show norm(gtensorkit[1] - goldsvd[1]) @show norm(gtensorkit[1] - gitersvd[1]) - -# TODO: Finite-difference check via test_rrule diff --git a/src/utility/svd.jl b/src/utility/svd.jl index ee3cbd4b..975c72cb 100644 --- a/src/utility/svd.jl +++ b/src/utility/svd.jl @@ -117,6 +117,7 @@ function itersvd_rev( # Truncation contribution from dU₂ and dV₂ function svdlinprob(v) # Left-preconditioned linear problem + # TODO: make v a Tuple instead of concatening two vectors γ1 = reshape(@view(v[1:dimγ]), (k, m)) γ2 = reshape(@view(v[(dimγ + 1):end]), (k, n)) Γ1 = γ1 - S⁻¹ * γ2 * Vproj * Ad From 8ac307e490b6fd7c68793f12670b478e2277e999 Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Tue, 26 Mar 2024 14:46:50 +0100 Subject: [PATCH 004/213] Update SVD adjoint linear problem to use Tuple and remove reshapes --- src/utility/svd.jl | 22 +++++++--------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/src/utility/svd.jl b/src/utility/svd.jl index 975c72cb..ccca32d4 100644 --- a/src/utility/svd.jl +++ b/src/utility/svd.jl @@ -112,29 +112,21 @@ function itersvd_rev( VdV = V' * V Uproj = one(UUd) - UUd Vproj = one(VdV) - VdV - m, k, n = size(U, 1), size(U, 2), size(V, 2) - dimγ = k * m # Vectorized dimension of γ-matrix # Truncation contribution from dU₂ and dV₂ function svdlinprob(v) # Left-preconditioned linear problem - # TODO: make v a Tuple instead of concatening two vectors - γ1 = reshape(@view(v[1:dimγ]), (k, m)) - γ2 = reshape(@view(v[(dimγ + 1):end]), (k, n)) - Γ1 = γ1 - S⁻¹ * γ2 * Vproj * Ad - Γ2 = γ2 - S⁻¹ * γ1 * Uproj * A - return vcat(reshape(Γ1, :), reshape(Γ2, :)) + Γ1 = v[1] - S⁻¹ * v[2] * Vproj * Ad + Γ2 = v[2] - S⁻¹ * v[1] * Uproj * A + return (Γ1, Γ2) end if ΔU isa ZeroTangent && ΔV isa ZeroTangent - γ = linsolve(Sop, zeros(eltype(A), k * m + k * n)) + m, k, n = size(U, 1), size(U, 2), size(V, 2) + γ = linsolve(Sop, (zeros(eltype(A), k * m), zeros(eltype(A), k * n))) else - # Explicit left-preconditioning - # Set relative tolerance to machine precision to converge SVD gradient error properly - y = vcat(reshape(S⁻¹ * ΔU' * Uproj, :), reshape(S⁻¹ * ΔV * Vproj, :)) + y = (S⁻¹ * ΔU' * Uproj, S⁻¹ * ΔV * Vproj) γ, = linsolve(svdlinprob, y; rtol=eps(real(eltype(A)))) end - γA1 = reshape(@view(γ[1:dimγ]), k, m) - γA2 = reshape(@view(γ[(dimγ + 1):end]), k, n) - ΔA += Uproj * γA1' * V + U * γA2 * Vproj + ΔA += Uproj * γ[1]' * V + U * γ[2] * Vproj return ΔA end From 80db1c05d5d388dd73d48e1cc888030f4684e2ce Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Thu, 11 Apr 2024 12:23:23 +0200 Subject: [PATCH 005/213] Fix ZeroTangent case for linear problem --- src/utility/svd.jl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/utility/svd.jl b/src/utility/svd.jl index ccca32d4..af9baa2a 100644 --- a/src/utility/svd.jl +++ b/src/utility/svd.jl @@ -121,7 +121,8 @@ function itersvd_rev( end if ΔU isa ZeroTangent && ΔV isa ZeroTangent m, k, n = size(U, 1), size(U, 2), size(V, 2) - γ = linsolve(Sop, (zeros(eltype(A), k * m), zeros(eltype(A), k * n))) + y = (zeros(eltype(A), k * m), zeros(eltype(A), k * n)) + γ, = linsolve(svdlinprob, y; rtol=eps(real(eltype(A)))) else y = (S⁻¹ * ΔU' * Uproj, S⁻¹ * ΔV * Vproj) γ, = linsolve(svdlinprob, y; rtol=eps(real(eltype(A)))) From ae96298e0b1201b2d39d8d3dc6129d99449fc9d9 Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Thu, 20 Jun 2024 17:16:31 +0200 Subject: [PATCH 006/213] Add SVD wrapper structs and function, utilize tsvd machinery, convert SVD adjoint script to test --- examples/test_svd_adjoint.jl | 109 ------------------ src/PEPSKit.jl | 1 + src/utility/svd.jl | 210 +++++++++++++++++++++++------------ test/svdwrap.jl | 63 +++++++++++ 4 files changed, 206 insertions(+), 177 deletions(-) delete mode 100644 examples/test_svd_adjoint.jl create mode 100644 test/svdwrap.jl diff --git a/examples/test_svd_adjoint.jl b/examples/test_svd_adjoint.jl deleted file mode 100644 index 69124870..00000000 --- a/examples/test_svd_adjoint.jl +++ /dev/null @@ -1,109 +0,0 @@ -using LinearAlgebra -using TensorKit -using ChainRulesCore, ChainRulesTestUtils, Zygote -using PEPSKit - -# Truncated SVD with outdated adjoint -oldsvd(t::AbstractTensorMap, χ::Int; kwargs...) = itersvd(t, χ; kwargs...) - -# Outdated adjoint not taking truncated part into account -function ChainRulesCore.rrule( - ::typeof(oldsvd), t::AbstractTensorMap, χ::Int; εbroad=0, kwargs... -) - U, S, V = oldsvd(t, χ; kwargs...) - - function oldsvd_pullback((ΔU, ΔS, ΔV)) - ∂t = similar(t) - for (c, b) in blocks(∂t) - copyto!( - b, - oldsvd_rev( - block(U, c), - block(S, c), - block(V, c), - block(ΔU, c), - block(ΔS, c), - block(ΔV, c); - εbroad, - ), - ) - end - return NoTangent(), ∂t, NoTangent() - end - - return (U, S, V), oldsvd_pullback -end - -function oldsvd_rev( - U::AbstractMatrix, - S::AbstractMatrix, - V::AbstractMatrix, - ΔU, - ΔS, - ΔV; - εbroad=0, - atol::Real=0, - rtol::Real=atol > 0 ? 0 : eps(scalartype(S))^(3 / 4), -) - tol = atol > 0 ? atol : rtol * S[1, 1] - F = PEPSKit.invert_S²(S, tol; εbroad) # Includes Lorentzian broadening - S⁻¹ = pinv(S; atol=tol) - - # dS contribution - term = ΔS isa ZeroTangent ? ΔS : Diagonal(diag(ΔS)) - - # dU₁ and dV₁ off-diagonal contribution - J = F .* (U' * ΔU) - term += (J + J') * S - VΔV = (V * ΔV') - K = F .* VΔV - term += S * (K + K') - - # dV₁ diagonal contribution (diagonal of dU₁ is gauged away) - if scalartype(U) <: Complex && !(ΔV isa ZeroTangent) && !(ΔU isa ZeroTangent) - L = Diagonal(diag(VΔV)) - term += 0.5 * S⁻¹ * (L' - L) - end - ΔA = U * term * V - - # Projector contribution for non-square A - UUd = U * U' - VdV = V' * V - Uproj = one(UUd) - UUd - Vproj = one(VdV) - VdV - ΔA += Uproj * ΔU * S⁻¹ * V + U * S⁻¹ * ΔV * Vproj # Wrong truncation contribution - - return ΔA -end - -# Gauge-invariant loss function -function lossfun(A, R=TensorMap(randn, space(A)), svdfunc=tsvd) - U, _, V = svdfunc(A) - return real(dot(R, U * V)) # Overlap with random tensor R is gauge-invariant and differentiable, also for m≠n -end - -m, n = 20, 30 -dtype = ComplexF64 -χ = 15 -r = TensorMap(randn, dtype, ℂ^m ← ℂ^n) -R = TensorMap(randn, space(r)) - -println("Non-truncated SVD:") -loldsvd, goldsvd = withgradient(A -> lossfun(A, R, x -> oldsvd(x, min(m, n))), r) -ltensorkit, gtensorkit = withgradient( - A -> lossfun(A, R, x -> tsvd(x; trunc=truncdim(min(m, n)))), r -) -litersvd, gitersvd = withgradient(A -> lossfun(A, R, x -> itersvd(x, min(m, n))), r) -@show loldsvd ≈ ltensorkit ≈ litersvd -@show norm(gtensorkit[1] - goldsvd[1]) -@show norm(gtensorkit[1] - gitersvd[1]) - -println("\nTruncated SVD with χ=$χ:") -loldsvd, goldsvd = withgradient(A -> lossfun(A, R, x -> oldsvd(x, χ)), r) -ltensorkit, gtensorkit = withgradient( - A -> lossfun(A, R, x -> tsvd(x; trunc=truncdim(χ))), r -) -litersvd, gitersvd = withgradient(A -> lossfun(A, R, x -> itersvd(x, χ)), r) -@show loldsvd ≈ ltensorkit ≈ litersvd -@show norm(gtensorkit[1] - goldsvd[1]) -@show norm(gtensorkit[1] - gitersvd[1]) diff --git a/src/PEPSKit.jl b/src/PEPSKit.jl index 456482e0..f195b882 100644 --- a/src/PEPSKit.jl +++ b/src/PEPSKit.jl @@ -42,6 +42,7 @@ module Defaults const tol = 1e-12 end +export FullSVD, IterSVD, OldSVD, svdwrap export InfinitePEPS, InfiniteTransferPEPS export InfinitePEPO, InfiniteTransferPEPO export initializeMPS, initializePEPS diff --git a/src/utility/svd.jl b/src/utility/svd.jl index af9baa2a..d06b27c8 100644 --- a/src/utility/svd.jl +++ b/src/utility/svd.jl @@ -1,97 +1,164 @@ -# Computation of F in SVD adjoint, including Lorentzian broadening -function invert_S²(S::AbstractMatrix{T}, tol::Real; εbroad=0) where {T<:Real} - F = similar(S) - @inbounds for i in axes(F, 1), j in axes(F, 2) - F[i, j] = if i == j - zero(T) - else - sᵢ, sⱼ = S[i, i], S[j, j] - Δs = abs(sⱼ - sᵢ) < tol ? tol : sⱼ^2 - sᵢ^2 - εbroad > 0 && (Δs = lorentz_broaden(Δs, εbroad)) - 1 / Δs +import TensorKit: + SectorDict, + _empty_svdtensors, + _compute_svddata!, + _truncate!, + _implement_svdtruncation!, + _create_svdtensors + +# Plain copy of tsvd!(...) from TensorKit to lift alg type restriction +function _tensorkit_svd!( + t::TensorMap; + trunc::TruncationScheme=TensorKit.NoTruncation(), + p::Real=2, + alg=TensorKit.SVD(), +) + #early return + if isempty(blocksectors(t)) + truncerr = zero(real(scalartype(t))) + return _empty_svdtensors(t)..., truncerr + end + + S = spacetype(t) + Udata, Σdata, Vdata, dims = _compute_svddata!(t, alg) + if !isa(trunc, TensorKit.NoTruncation) + Σdata, truncerr = _truncate!(Σdata, trunc, p) + Udata, Σdata, Vdata, dims = _implement_svdtruncation!(t, Udata, Σdata, Vdata, dims) + W = S(dims) + else + truncerr = abs(zero(scalartype(t))) + W = S(dims) + if length(domain(t)) == 1 && domain(t)[1] ≅ W + W = domain(t)[1] + elseif length(codomain(t)) == 1 && codomain(t)[1] ≅ W + W = codomain(t)[1] end end - return F + return _create_svdtensors(t, Udata, Σdata, Vdata, W)..., truncerr end -# Lorentzian broadening for SVD adjoint singularities -function lorentz_broaden(x::Real, ε=1e-12) - x′ = 1 / x - return x′ / (x′^2 + ε) +# Wrapper struct around TensorKit's SVD algorithms +@kwdef struct FullSVD + alg::Union{<:TensorKit.SVD,<:TensorKit.SDD} = TensorKit.SVD() + lorentz_broad::Float64 = 0.0 +end + +function svdwrap(t::AbstractTensorMap, alg::FullSVD; trunc=notrunc(), kwargs...) + # TODO: Replace _tensorkit_svd! with just tsvd eventually to use the full TensorKit machinery + return _tensorkit_svd!(copy(t); trunc, alg.alg) end -# Proper truncated SVD using iterative solver -function itersvd( - t::AbstractTensorMap, - χ::Int; - εbroad=0, - solverkwargs=(; krylovdim=χ + 5, tol=1e2eps(real(scalartype(t)))), +function ChainRulesCore.rrule( + ::typeof(svdwrap), t::AbstractTensorMap, alg::FullSVD; trunc=notrunc(), kwargs... ) - vals, lvecs, rvecs, info = svdsolve(t.data, dim(codomain(t)), χ; solverkwargs...) - truncspace = field(t)^χ - if info.converged < χ # Fall back to dense SVD - @warn "falling back to dense SVD solver since length(S) < χ" - return tsvd(t; trunc=truncdim(χ), alg=TensorKit.SVD()) - else - vals = @view(vals[1:χ]) - lvecs = @view(lvecs[1:χ]) - rvecs = @view(rvecs[1:χ]) + tsvd_return, tsvd!_pullback = ChainRulesCore.rrule(tsvd!, t; alg=TensorKit.SVD(), trunc) + function svdwrap_fullsvd_pullback(Δ) + return tsvd!_pullback(Δ)..., NoTangent() + end + return tsvd_return, svdwrap_fullsvd_pullback +end + +# Wrapper around Krylov Kit's GKL iterative SVD solver +@kwdef struct IterSVD + alg::KrylovKit.GKL = KrylovKit.GKL(; tol=1e-14, krylovdim=25) + howmany::Int = 20 + lorentz_broad::Float64 = 0.0 + alg_rrule::Union{GMRES,BiCGStab,Arnoldi} = GMRES(; tol=1e-14) +end + +function svdwrap(t::AbstractTensorMap, alg::IterSVD; trunc=notrunc(), kwargs...) + U, S, V, = _tensorkit_svd!(copy(t); trunc, alg) # TODO: Also replace this with tsvd eventually + ϵ = norm(t - U * S * V) # Compute truncation error separately + return U, S, V, ϵ +end + +# Compute SVD data block-wise using KrylovKit algorithm +function TensorKit._compute_svddata!(t::TensorMap, alg::IterSVD) + InnerProductStyle(t) === EuclideanProduct() || throw_invalid_innerproduct(:tsvd!) + I = sectortype(t) + A = storagetype(t) + Udata = SectorDict{I,A}() + Vdata = SectorDict{I,A}() + dims = SectorDict{I,Int}() + local Σdata + for (c, b) in blocks(t) + x₀ = randn(eltype(b), size(b, 1)) + Σ, lvecs, rvecs, info = svdsolve(b, x₀, alg.howmany, :LR, alg.alg) + if info.converged < alg.howmany # Fall back to dense SVD if not properly converged + U, Σ, V = TensorKit.MatrixAlgebra.svd!(b, TensorKit.SVD()) + Udata[c] = U + Vdata[c] = V + else + Udata[c] = stack(lvecs) + Vdata[c] = stack(rvecs)' + end + if @isdefined Σdata # cannot easily infer the type of Σ, so use this construction + Σdata[c] = Σ + else + Σdata = SectorDict(c => Σ) + end + dims[c] = length(Σ) end - U = TensorMap(hcat(lvecs...), codomain(t) ← truncspace) - S = TensorMap(diagm(vals), truncspace ← truncspace) - V = TensorMap(copy(hcat(rvecs...)'), truncspace ← domain(t)) - return U, S, V + return Udata, Σdata, Vdata, dims +end + +# TODO: IterSVD adjoint utilizing KryloVKit svdsolve adjoint + +# Full SVD with old adjoint that doesn't account for truncation properly +@kwdef struct OldSVD{A<:Union{FullSVD,IterSVD}} + alg::A = FullSVD() + lorentz_broad::Float64 = 0.0 +end + +function svdwrap(t::AbstractTensorMap, alg::OldSVD; kwargs...) + return svdwrap(t, alg.alg; kwargs...) end -# Reverse rule adopted from tsvd! rrule as found in TensorKit.jl +# Outdated adjoint not taking truncated part into account for testing purposes function ChainRulesCore.rrule( - ::typeof(itersvd), t::AbstractTensorMap, χ::Int; εbroad=0, kwargs... + ::typeof(svdwrap), t::AbstractTensorMap, alg::OldSVD; kwargs... ) - U, S, V = itersvd(t, χ; kwargs...) + U, S, V, ϵ = svdwrap(t, alg; kwargs...) - function itersvd_pullback((ΔU, ΔS, ΔV)) + function svdwrap_oldsvd_pullback((ΔU, ΔS, ΔV, Δϵ)) ∂t = similar(t) for (c, b) in blocks(∂t) copyto!( b, - itersvd_rev( - block(t, c), + oldsvd_rev( block(U, c), block(S, c), block(V, c), block(ΔU, c), block(ΔS, c), block(ΔV, c); - εbroad, + lorentz_broad=alg.lorentz_broad, ), ) end return NoTangent(), ∂t, NoTangent() end - return (U, S, V), itersvd_pullback + return (U, S, V, ϵ), svdwrap_oldsvd_pullback end -# SVD adjoint with proper truncation -function itersvd_rev( - A::AbstractMatrix, +function oldsvd_rev( U::AbstractMatrix, S::AbstractMatrix, V::AbstractMatrix, ΔU, ΔS, ΔV; - εbroad=0, + lorentz_broad=0, atol::Real=0, rtol::Real=atol > 0 ? 0 : eps(scalartype(S))^(3 / 4), ) - Ad = copy(A') tol = atol > 0 ? atol : rtol * S[1, 1] - F = invert_S²(S, tol; εbroad) # Includes Lorentzian broadening + F = _invert_S²(S, tol, lorentz_broad) # Includes Lorentzian broadening S⁻¹ = pinv(S; atol=tol) # dS contribution - term = ΔS isa ZeroTangent ? ΔS : Diagonal(real.(ΔS)) # Implicitly performs 𝕀 ∘ dS + term = ΔS isa ZeroTangent ? ΔS : Diagonal(diag(ΔS)) # dU₁ and dV₁ off-diagonal contribution J = F .* (U' * ΔU) @@ -102,32 +169,39 @@ function itersvd_rev( # dV₁ diagonal contribution (diagonal of dU₁ is gauged away) if scalartype(U) <: Complex && !(ΔV isa ZeroTangent) && !(ΔU isa ZeroTangent) - L = Diagonal(VΔV) # Implicitly performs 𝕀 ∘ dV + L = Diagonal(diag(VΔV)) term += 0.5 * S⁻¹ * (L' - L) end ΔA = U * term * V - # Projector contribution for non-square A and dU₂ and dV₂ + # Projector contribution for non-square A UUd = U * U' VdV = V' * V Uproj = one(UUd) - UUd Vproj = one(VdV) - VdV - - # Truncation contribution from dU₂ and dV₂ - function svdlinprob(v) # Left-preconditioned linear problem - Γ1 = v[1] - S⁻¹ * v[2] * Vproj * Ad - Γ2 = v[2] - S⁻¹ * v[1] * Uproj * A - return (Γ1, Γ2) - end - if ΔU isa ZeroTangent && ΔV isa ZeroTangent - m, k, n = size(U, 1), size(U, 2), size(V, 2) - y = (zeros(eltype(A), k * m), zeros(eltype(A), k * n)) - γ, = linsolve(svdlinprob, y; rtol=eps(real(eltype(A)))) - else - y = (S⁻¹ * ΔU' * Uproj, S⁻¹ * ΔV * Vproj) - γ, = linsolve(svdlinprob, y; rtol=eps(real(eltype(A)))) - end - ΔA += Uproj * γ[1]' * V + U * γ[2] * Vproj + ΔA += Uproj * ΔU * S⁻¹ * V + U * S⁻¹ * ΔV * Vproj # Wrong truncation contribution return ΔA end + +# Computation of F in SVD adjoint, including Lorentzian broadening +function _invert_S²(S::AbstractMatrix{T}, tol::Real, ε=0) where {T<:Real} + F = similar(S) + @inbounds for i in axes(F, 1), j in axes(F, 2) + F[i, j] = if i == j + zero(T) + else + sᵢ, sⱼ = S[i, i], S[j, j] + Δs = abs(sⱼ - sᵢ) < tol ? tol : sⱼ^2 - sᵢ^2 + ε > 0 && (Δs = _lorentz_broaden(Δs, ε)) + 1 / Δs + end + end + return F +end + +# Lorentzian broadening for SVD adjoint F-singularities +function _lorentz_broaden(x::Real, ε=1e-12) + x′ = 1 / x + return x′ / (x′^2 + ε) +end \ No newline at end of file diff --git a/test/svdwrap.jl b/test/svdwrap.jl new file mode 100644 index 00000000..3c3a0748 --- /dev/null +++ b/test/svdwrap.jl @@ -0,0 +1,63 @@ +using Test +using Random +using LinearAlgebra +using TensorKit +using KrylovKit +using ChainRulesCore, Zygote +using PEPSKit + +# Gauge-invariant loss function +function lossfun(A, alg, R=TensorMap(randn, space(A)); trunc=notrunc()) + U, _, V, = svdwrap(A, alg; trunc) + return real(dot(R, U * V)) # Overlap with random tensor R is gauge-invariant and differentiable, also for m≠n +end + +m, n = 20, 30 +dtype = ComplexF64 +χ = 12 +trunc = truncdim(χ) +# lorentz_broad = 1e-12 +adjoint_tol = 1e-16 +rtol = 1e-9 +r = TensorMap(randn, dtype, ℂ^m ← ℂ^n) +R = TensorMap(randn, space(r)) + +@testset "Non-truncacted SVD" begin + l_fullsvd, g_fullsvd = withgradient(A -> lossfun(A, FullSVD(), R), r) + l_oldsvd, g_oldsvd = withgradient(A -> lossfun(A, OldSVD(), R), r) + # l_itersvd, g_itersvd = withgradient( + # A -> lossfun(A, IterSVD(; howmany=min(m, n), adjoint_tol), R), r + # ) + + @test l_oldsvd ≈ l_fullsvd + # @test l_oldsvd ≈ l_itersvd ≈ l_fullsvd + @test norm(g_fullsvd[1] - g_oldsvd[1]) / norm(g_fullsvd[1]) < rtol + # @test norm(g_tsvd[1] - g_itersvd[1]) / norm(g_tsvd[1]) < rtol +end + +@testset "Truncated SVD with χ=$χ" begin + l_fullsvd, g_fullsvd = withgradient(A -> lossfun(A, FullSVD(), R; trunc), r) + l_oldsvd, g_oldsvd = withgradient(A -> lossfun(A, OldSVD(), R; trunc), r) + # l_itersvd, g_itersvd = withgradient( + # A -> lossfun(A, IterSVD(; howmany=χ, adjoint_tol), R; trunc), r + # ) + + @test l_oldsvd ≈ l_fullsvd + # @test l_oldsvd ≈ l_itersvd ≈ l_fullsvd + @test norm(g_fullsvd[1] - g_oldsvd[1]) / norm(g_fullsvd[1]) > rtol + # @test norm(g_tsvd[1] - g_itersvd[1]) / norm(g_tsvd[1]) < rtol +end + +# @testset "Truncated SVD with χ=$χ and ε=$lorentz_broad broadening" begin +# l_fullsvd, g_fullsvd = withgradient( +# A -> lossfun(A, FullSVD(; lorentz_broad, R; trunc), r +# ) +# l_oldsvd, g_oldsvd = withgradient(A -> lossfun(A, OldSVD(; lorentz_broad), R; trunc), r) +# l_itersvd, g_itersvd = withgradient( +# A -> lossfun(A, IterSVD(; howmany=χ, lorentz_broad), R; trunc), r +# ) + +# @test l_oldsvd ≈ l_itersvd ≈ l_fullsvd +# @test norm(g_fullsvd[1] - g_oldsvd[1]) / norm(g_fullsvd[1]) > rtol +# @test norm(g_fullsvd[1] - g_itersvd[1]) / norm(g_fullsvd[1]) < rtol +# end From 78ab1527b74c14e5df0ce3cd8db6e1a17b99ab53 Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Thu, 20 Jun 2024 17:46:25 +0200 Subject: [PATCH 007/213] Copy ctmrg.jl from master, add svdalg field to CTMRG, use svdwrap in left_move --- src/algorithms/ctmrg.jl | 653 +++++++++++++++++++++++++--------------- 1 file changed, 403 insertions(+), 250 deletions(-) diff --git a/src/algorithms/ctmrg.jl b/src/algorithms/ctmrg.jl index 932ed866..65312e0c 100644 --- a/src/algorithms/ctmrg.jl +++ b/src/algorithms/ctmrg.jl @@ -1,172 +1,381 @@ -@with_kw struct CTMRG #<: Algorithm +# TODO: add abstract Algorithm type? +""" + struct CTMRG(; trscheme = TensorKit.notrunc(), tol = Defaults.ctmrg_tol, + maxiter = Defaults.ctmrg_maxiter, miniter = Defaults.ctmrg_miniter, + verbosity = 0, fixedspace = false) + +Algorithm struct that represents the CTMRG algorithm for contracting infinite PEPS. +The projector bond dimensions are set via `trscheme` which controls the truncation +properties inside of `TensorKit.tsvd`. Each CTMRG run is converged up to `tol` +where the singular value convergence of the corners as well as the norm is checked. +The maximal and minimal number of CTMRG iterations is set with `maxiter` and `miniter`. +Different levels of output information are printed depending on `verbosity` (0, 1 or 2). +Regardless of the truncation scheme, the space can be kept fixed with `fixedspace`. +""" +@kwdef struct CTMRG{S} + tol::Float64 = Defaults.ctmrg_tol + maxiter::Int = Defaults.ctmrg_maxiter + miniter::Int = Defaults.ctmrg_miniter + verbosity::Int = 0 + svdalg::S = FullSVD() trscheme::TruncationScheme = TensorKit.notrunc() - tol::Float64 = Defaults.tol - maxiter::Integer = Defaults.maxiter - miniter::Integer = 4 - verbose::Integer = 0 fixedspace::Bool = false end -@with_kw struct CTMRG2 #<: Algorithm - trscheme::TruncationScheme = TensorKit.notrunc() - tol::Float64 = Defaults.tol - maxiter::Integer = Defaults.maxiter - miniter::Integer = 4 - verbose::Integer = 0 +""" + MPSKit.leading_boundary([envinit], state, alg::CTMRG) + +Contract `state` using CTMRG and return the CTM environment. +Per default, a random initial environment is used. +""" +function MPSKit.leading_boundary(state, alg::CTMRG) + return MPSKit.leading_boundary(CTMRGEnv(state), state, alg) end +function MPSKit.leading_boundary(envinit, state, alg::CTMRG) + normold = 1.0 + CSold = map(x -> tsvd(x; alg=TensorKit.SVD())[2], envinit.corners) + TSold = map(x -> tsvd(x; alg=TensorKit.SVD())[2], envinit.edges) + ϵold = 1.0 + env = deepcopy(envinit) + + for i in 1:(alg.maxiter) + env, ϵ = ctmrg_iter(state, env, alg) # Grow and renormalize in all 4 directions + + # Compute convergence criteria and take max (TODO: How should we handle logging all of this?) + Δϵ = abs((ϵold - ϵ) / ϵold) + normnew = norm(state, env) + Δnorm = abs(normold - normnew) / abs(normold) + CSnew = map(c -> tsvd(c; alg=TensorKit.SVD())[2], env.corners) + ΔCS = maximum(zip(CSold, CSnew)) do (c_old, c_new) + # only compute the difference on the smallest part of the spaces + smallest = infimum(MPSKit._firstspace(c_old), MPSKit._firstspace(c_new)) + e_old = isometry(MPSKit._firstspace(c_old), smallest) + e_new = isometry(MPSKit._firstspace(c_new), smallest) + return norm(e_new' * c_new * e_new - e_old' * c_old * e_old) + end + TSnew = map(t -> tsvd(t; alg=TensorKit.SVD())[2], env.edges) -function MPSKit.leading_boundary(peps::InfinitePEPS, alg::CTMRG, envs=CTMRGEnv(peps)) - return MPSKit.leading_boundary(peps, peps, alg, envs) -end; + ΔTS = maximum(zip(TSold, TSnew)) do (t_old, t_new) + MPSKit._firstspace(t_old) == MPSKit._firstspace(t_new) || + return scalartype(t_old)(Inf) + # TODO: implement when spaces aren't the same + return norm(t_new - t_old) + end -function MPSKit.leading_boundary( - peps_above::InfinitePEPS, - peps_below::InfinitePEPS, - alg::CTMRG, - envs=CTMRGEnv(peps_above, peps_below), -) - err = Inf - iter = 1 - - #for convergence criterium we use the on site contracted boundary - #this convergences, though the value depends on the bond dimension χ - old_norm = 1.0 - new_norm = old_norm - ϵ₁ = 1.0 - while (err > alg.tol && iter <= alg.maxiter) || iter <= alg.miniter - ϵ = 0.0 - for i in 1:4 - envs, ϵ₀ = left_move(peps_above, peps_below, alg, envs) - ϵ = max(ϵ, ϵ₀) - envs = rotate_north(envs, EAST) - peps_above = envs.peps_above - peps_below = envs.peps_below + conv_condition = max(Δnorm, ΔCS, ΔTS) < alg.tol && i > alg.miniter + ignore_derivatives() do # Print verbose info + if alg.verbosity > 1 || (alg.verbosity == 1 && (i == 1 || conv_condition)) + @printf( + "CTMRG iter: %3d norm: %.2e Δnorm: %.2e ΔCS: %.2e ΔTS: %.2e ϵ: %.2e Δϵ: %.2e\n", + i, + abs(normnew), + Δnorm, + ΔCS, + ΔTS, + ϵ, + Δϵ + ) + end + alg.verbosity > 0 && + i == alg.maxiter && + @warn( + "CTMRG reached maximal number of iterations at (Δnorm=$Δnorm, ΔCS=$ΔCS, ΔTS=$ΔTS)" + ) + end + conv_condition && break # Converge if maximal Δ falls below tolerance + + # Update convergence criteria + normold = normnew + CSold = CSnew + TSold = TSnew + ϵold = ϵ + end + + # Do one final iteration that does not change the spaces + alg_fixed = CTMRG(; + alg.trscheme, alg.tol, alg.maxiter, alg.miniter, alg.verbosity, fixedspace=true + ) + env′, = ctmrg_iter(state, env, alg_fixed) + envfix = gauge_fix(env, env′) + check_elementwise_convergence(env, envfix; atol=alg.tol^(3 / 4)) || + @warn "CTMRG did not converge elementwise." + return envfix +end + +""" + gauge_fix(envprev::CTMRGEnv{C,T}, envfinal::CTMRGEnv{C,T}) where {C,T} + +Fix the gauge of `envfinal` based on the previous environment `envprev`. +This assumes that the `envfinal` is the result of one CTMRG iteration on `envprev`. +Given that the CTMRG run is converged, the returned environment will be +element-wise converged to `envprev`. +""" +function gauge_fix(envprev::CTMRGEnv{C,T}, envfinal::CTMRGEnv{C,T}) where {C,T} + # Check if spaces in envprev and envfinal are the same + same_spaces = map(Iterators.product(axes(envfinal.edges)...)) do (dir, r, c) + space(envfinal.edges[dir, r, c]) == space(envprev.edges[dir, r, c]) && + space(envfinal.corners[dir, r, c]) == space(envprev.corners[dir, r, c]) + end + @assert all(same_spaces) "Spaces of envprev and envfinal are not the same" + + # Try the "general" algorithm from https://arxiv.org/abs/2311.11894 + signs = map(Iterators.product(axes(envfinal.edges)...)) do (dir, r, c) + # Gather edge tensors and pretend they're InfiniteMPSs + if dir == NORTH + Tsprev = circshift(envprev.edges[dir, r, :], 1 - c) + Tsfinal = circshift(envfinal.edges[dir, r, :], 1 - c) + elseif dir == EAST + Tsprev = circshift(envprev.edges[dir, :, c], 1 - r) + Tsfinal = circshift(envfinal.edges[dir, :, c], 1 - r) + elseif dir == SOUTH + Tsprev = circshift(reverse(envprev.edges[dir, r, :]), c) + Tsfinal = circshift(reverse(envfinal.edges[dir, r, :]), c) + elseif dir == WEST + Tsprev = circshift(reverse(envprev.edges[dir, :, c]), r) + Tsfinal = circshift(reverse(envfinal.edges[dir, :, c]), r) end - new_norm = contract_ctrmg(envs) - - err = abs(old_norm - new_norm) - dϵ = abs((ϵ₁ - ϵ) / ϵ₁) - @ignore_derivatives alg.verbose > 1 && @printf( - "CTMRG: \titeration: %4d\t\terror: %.2e\t\tnorm: %.10e\t\tϵ: %.2e\t\tdϵ: %.2e\n", - iter, - err, - abs(new_norm), - ϵ, - dϵ + # Random MPS of same bond dimension + M = map(Tsfinal) do t + TensorMap(randn, scalartype(t), codomain(t) ← domain(t)) + end + + # Find right fixed points of mixed transfer matrices + ρinit = TensorMap( + randn, + scalartype(T), + MPSKit._lastspace(Tsfinal[end])' ← MPSKit._lastspace(M[end])', ) + ρprev = eigsolve(TransferMatrix(Tsprev, M), ρinit, 1, :LM)[2][1] + ρfinal = eigsolve(TransferMatrix(Tsfinal, M), ρinit, 1, :LM)[2][1] + + # Decompose and multiply + Up, _, Vp = tsvd(ρprev) + Uf, _, Vf = tsvd(ρfinal) + Qprev = Up * Vp + Qfinal = Uf * Vf + σ = Qprev * Qfinal' - old_norm = new_norm - ϵ₁ = ϵ - iter += 1 + return σ end - #@ignore_derivatives @show iter, new_norm, err - @ignore_derivatives iter > alg.maxiter && - alg.verbose > 0 && - @warn "maxiter $(alg.maxiter) reached: error was $(err)" + cornersfix, edgesfix = fix_relative_phases(envfinal, signs) - return envs + # Fix global phase + cornersgfix = map(zip(envprev.corners, cornersfix)) do (Cprev, Cfix) + φ = dot(Cprev, Cfix) + φ' * Cfix + end + edgesgfix = map(zip(envprev.edges, edgesfix)) do (Tprev, Tfix) + φ = dot(Tprev, Tfix) + φ' * Tfix + end + envfix = CTMRGEnv(cornersgfix, edgesgfix) + + return envfix end -function left_move( - peps_above::InfinitePEPS{PType}, - peps_below::InfinitePEPS{PType}, - alg::CTMRG, - envs::CTMRGEnv, -) where {PType} - corners::typeof(envs.corners) = copy(envs.corners) - edges::typeof(envs.edges) = copy(envs.edges) - - above_projector_type = tensormaptype(spacetype(PType), 1, 3, storagetype(PType)) - below_projector_type = tensormaptype(spacetype(PType), 3, 1, storagetype(PType)) +# Explicit fixing of relative phases (doing this compactly in a loop is annoying) +function fix_relative_phases(envfinal::CTMRGEnv, signs) + C1 = map(Iterators.product(axes(envfinal.corners)[2:3]...)) do (r, c) + @tensor Cfix[-1; -2] := + signs[WEST, _prev(r, end), c][-1 1] * + envfinal.corners[NORTHWEST, r, c][1; 2] * + conj(signs[NORTH, r, c][-2 2]) + end + T1 = map(Iterators.product(axes(envfinal.edges)[2:3]...)) do (r, c) + @tensor Tfix[-1 -2 -3; -4] := + signs[NORTH, r, c][-1 1] * + envfinal.edges[NORTH, r, c][1 -2 -3; 2] * + conj(signs[NORTH, r, _next(c, end)][-4 2]) + end + + C2 = map(Iterators.product(axes(envfinal.corners)[2:3]...)) do (r, c) + @tensor Cfix[-1; -2] := + signs[NORTH, r, _next(c, end)][-1 1] * + envfinal.corners[NORTHEAST, r, c][1; 2] * + conj(signs[EAST, r, c][-2 2]) + end + T2 = map(Iterators.product(axes(envfinal.edges)[2:3]...)) do (r, c) + @tensor Tfix[-1 -2 -3; -4] := + signs[EAST, r, c][-1 1] * + envfinal.edges[EAST, r, c][1 -2 -3; 2] * + conj(signs[EAST, _next(r, end), c][-4 2]) + end + + C3 = map(Iterators.product(axes(envfinal.corners)[2:3]...)) do (r, c) + @tensor Cfix[-1; -2] := + signs[EAST, _next(r, end), c][-1 1] * + envfinal.corners[SOUTHEAST, r, c][1; 2] * + conj(signs[SOUTH, r, c][-2 2]) + end + T3 = map(Iterators.product(axes(envfinal.edges)[2:3]...)) do (r, c) + @tensor Tfix[-1 -2 -3; -4] := + signs[SOUTH, r, c][-1 1] * + envfinal.edges[SOUTH, r, c][1 -2 -3; 2] * + conj(signs[SOUTH, r, _prev(c, end)][-4 2]) + end + + C4 = map(Iterators.product(axes(envfinal.corners)[2:3]...)) do (r, c) + @tensor Cfix[-1; -2] := + signs[SOUTH, r, _prev(c, end)][-1 1] * + envfinal.corners[SOUTHWEST, r, c][1; 2] * + conj(signs[WEST, r, c][-2 2]) + end + T4 = map(Iterators.product(axes(envfinal.edges)[2:3]...)) do (r, c) + @tensor Tfix[-1 -2 -3; -4] := + signs[WEST, r, c][-1 1] * + envfinal.edges[WEST, r, c][1 -2 -3; 2] * + conj(signs[WEST, _prev(r, end), c][-4 2]) + end + + return stack([C1, C2, C3, C4]; dims=1), stack([T1, T2, T3, T4]; dims=1) +end + +""" + check_elementwise_convergence(envfinal, envfix; atol=1e-6) + +Check if the element-wise difference of the corner and edge tensors of the final and fixed +CTMRG environments are below some tolerance. +""" +function check_elementwise_convergence( + envfinal::CTMRGEnv, envfix::CTMRGEnv; atol::Real=1e-6 +) + ΔC = envfinal.corners .- envfix.corners + ΔCmax = norm(ΔC, Inf) + ΔCmean = norm(ΔC) + @debug "maxᵢⱼ|Cⁿ⁺¹ - Cⁿ|ᵢⱼ = $ΔCmax mean |Cⁿ⁺¹ - Cⁿ|ᵢⱼ = $ΔCmean" + + ΔT = envfinal.edges .- envfix.edges + ΔTmax = norm(ΔT, Inf) + ΔTmean = norm(ΔT) + @debug "maxᵢⱼ|Tⁿ⁺¹ - Tⁿ|ᵢⱼ = $ΔTmax mean |Tⁿ⁺¹ - Tⁿ|ᵢⱼ = $ΔTmean" + + # Check differences for all tensors in unit cell to debug properly + for (dir, r, c) in Iterators.product(axes(envfinal.edges)...) + @debug( + "$((dir, r, c)): all |Cⁿ⁺¹ - Cⁿ|ᵢⱼ < ϵ: ", + all(x -> abs(x) < atol, convert(Array, ΔC[dir, r, c])), + ) + @debug( + "$((dir, r, c)): all |Tⁿ⁺¹ - Tⁿ|ᵢⱼ < ϵ: ", + all(x -> abs(x) < atol, convert(Array, ΔT[dir, r, c])), + ) + end + + return isapprox(ΔCmax, 0; atol) && isapprox(ΔTmax, 0; atol) +end + +@non_differentiable check_elementwise_convergence(args...) + +""" + ctmrg_iter(state, env::CTMRGEnv{C,T}, alg::CTMRG) where {C,T} + +Perform one iteration of CTMRG that maps the `state` and `env` to a new environment, +and also return the truncation error. +One CTMRG iteration consists of four `left_move` calls and 90 degree rotations, +such that the environment is grown and renormalized in all four directions. +""" +function ctmrg_iter(state, env::CTMRGEnv{C,T}, alg::CTMRG) where {C,T} ϵ = 0.0 - n0 = 1.0 - n1 = 1.0 - for col in 1:size(peps_above, 2) - cop = mod1(col + 1, size(peps_above, 2)) - com = mod1(col - 1, size(peps_above, 2)) - - above_projs = Vector{above_projector_type}(undef, size(peps_above, 1)) - below_projs = Vector{below_projector_type}(undef, size(peps_above, 1)) - - # find all projectors - for row in 1:size(peps_above, 1) - rop = mod1(row + 1, size(peps_above, 1)) - peps_above_nw = peps_above[row, col] - peps_above_sw = rotate_north(peps_above[rop, col], WEST) - peps_below_nw = peps_below[row, col] - peps_below_sw = rotate_north(peps_below[rop, col], WEST) - - Q1 = northwest_corner( - envs.edges[SOUTH, mod1(row + 1, end), col], - envs.corners[SOUTHWEST, mod1(row + 1, end), col], - envs.edges[WEST, mod1(row + 1, end), col], - peps_above_sw, - peps_below_sw, + + for _ in 1:4 + env, _, _, ϵ₀ = left_move(state, env, alg) + state = rotate_north(state, EAST) + env = rotate_north(env, EAST) + ϵ = max(ϵ, ϵ₀) + end + + return env, ϵ +end + +""" + left_move(state, env::CTMRGEnv{C,T}, alg::CTMRG) where {C,T} + +Grow, project and renormalize the environment `env` in west direction. +Return the updated environment as well as the projectors and truncation error. +""" +function left_move(state, env::CTMRGEnv{C,T}, alg::CTMRG) where {C,T} + corners::typeof(env.corners) = copy(env.corners) + edges::typeof(env.edges) = copy(env.edges) + ϵ = 0.0 + Pleft, Pright = Zygote.Buffer.(projector_type(T, size(state))) # Use Zygote.Buffer instead of @diffset to avoid ZeroTangent errors in _setindex + + for col in 1:size(state, 2) + cnext = _next(col, size(state, 2)) + + # Compute projectors + for row in 1:size(state, 1) + rnext = _next(row, size(state, 1)) + state_nw = state[row, col] + state_sw = rotate_north(state[rnext, col], WEST) + + # Enlarged corners + Q_sw = northwest_corner( + env.edges[SOUTH, _next(row, end), col], + env.corners[SOUTHWEST, _next(row, end), col], + env.edges[WEST, _next(row, end), col], + state_sw, ) - Q2 = northwest_corner( - envs.edges[WEST, row, col], - envs.corners[NORTHWEST, row, col], - envs.edges[NORTH, row, col], - peps_above_nw, - peps_below_nw, + Q_nw = northwest_corner( + env.edges[WEST, row, col], + env.corners[NORTHWEST, row, col], + env.edges[NORTH, row, col], + state_nw, ) + # SVD half-infinite environment trscheme = if alg.fixedspace == true - truncspace(space(envs.edges[WEST, row, cop], 1)) + truncspace(space(env.edges[WEST, row, cnext], 1)) else alg.trscheme end - #@ignore_derivatives @show norm(Q1*Q2) - - (U, S, V) = tsvd(Q1 * Q2; trunc=trscheme, alg=SVD()) - - @ignore_derivatives n0 = norm(Q1 * Q2)^2 - @ignore_derivatives n1 = norm(U * S * V)^2 - @ignore_derivatives ϵ = max(ϵ, (n0 - n1) / n0) - - isqS = sdiag_inv_sqrt(S) - #Q = isqS*U'*Q1; - #P = Q2*V'*isqS; - @tensor Q[-1; -2 -3 -4] := isqS[-1; 1] * conj(U[2 3 4; 1]) * Q1[2 3 4; -2 -3 -4] - @tensor P[-1 -2 -3; -4] := Q2[-1 -2 -3; 1 2 3] * conj(V[4; 1 2 3]) * isqS[4; -4] + U, S, V, ϵ_local = svdwrap(Q_sw * Q_nw, alg.svdalg; trunc=trscheme) + ϵ = max(ϵ, ϵ_local / norm(S)) + # TODO: check if we can just normalize enlarged corners s.t. trunc behaves a bit better + + # Compute SVD truncation error and check for degenerate singular values + ignore_derivatives() do + if alg.verbosity > 0 && is_degenerate_spectrum(S) + svals = TensorKit.SectorDict(c => diag(b) for (c, b) in blocks(S)) + @warn("degenerate singular values detected: ", svals) + end + end - @diffset above_projs[row] = Q - @diffset below_projs[row] = P + # Compute projectors + Pl, Pr = build_projectors(U, S, V, Q_sw, Q_nw) + Pleft[row, col] = Pl + Pright[row, col] = Pr end - #use the projectors to grow the corners/edges - for row in 1:size(peps_above, 1) - Q = above_projs[row] - P = below_projs[mod1(row - 1, end)] - rop = mod1(row + 1, size(peps_above, 1)) - rom = mod1(row - 1, size(peps_above, 1)) - - @diffset @tensor corners[NORTHWEST, rop, cop][-1; -2] := - envs.corners[NORTHWEST, rop, col][1, 2] * - envs.edges[NORTH, rop, col][2, 3, 4, -2] * - Q[-1; 1 3 4] - @diffset @tensor corners[SOUTHWEST, rom, cop][-1; -2] := - envs.corners[SOUTHWEST, rom, col][1, 4] * - envs.edges[SOUTH, rom, col][-1, 2, 3, 1] * - P[4 2 3; -2] - @diffset @tensor edges[WEST, row, cop][-1 -2 -3; -4] := - envs.edges[WEST, row, col][1 2 3; 4] * - peps_above[row, col][9; 5 -2 7 2] * - conj(peps_below[row, col][9; 6 -3 8 3]) * - P[4 5 6; -4] * - Q[-1; 1 7 8] + # Use projectors to grow the corners & edges + for row in 1:size(state, 1) + rprev = _prev(row, size(state, 1)) + rnext = _next(row, size(state, 1)) + C_sw, C_nw, T_w = grow_env_left( + state[row, col], + Pleft[_prev(row, end), col], + Pright[row, col], + env.corners[SOUTHWEST, rprev, col], + env.corners[NORTHWEST, rnext, col], + env.edges[SOUTH, rprev, col], + env.edges[WEST, row, col], + env.edges[NORTH, rnext, col], + ) + @diffset corners[SOUTHWEST, rprev, cnext] = C_sw + @diffset corners[NORTHWEST, rnext, cnext] = C_nw + @diffset edges[WEST, row, cnext] = T_w end - @diffset corners[NORTHWEST, :, cop] ./= norm.(corners[NORTHWEST, :, cop]) - @diffset edges[WEST, :, cop] ./= norm.(edges[WEST, :, cop]) - @diffset corners[SOUTHWEST, :, cop] ./= norm.(corners[SOUTHWEST, :, cop]) + @diffset corners[SOUTHWEST, :, cnext] ./= norm.(corners[SOUTHWEST, :, cnext]) + @diffset corners[NORTHWEST, :, cnext] ./= norm.(corners[NORTHWEST, :, cnext]) + @diffset edges[WEST, :, cnext] ./= norm.(edges[WEST, :, cnext]) end - return CTMRGEnv(peps_above, peps_below, corners, edges), ϵ + return CTMRGEnv(corners, edges), copy(Pleft), copy(Pright), ϵ end +# Compute enlarged NW corner function northwest_corner(E4, C1, E1, peps_above, peps_below=peps_above) @tensor corner[-1 -2 -3; -4 -5 -6] := E4[-1 1 2; 3] * @@ -175,6 +384,8 @@ function northwest_corner(E4, C1, E1, peps_above, peps_below=peps_above) peps_above[7; 5 -5 -2 1] * conj(peps_below[7; 6 -6 -3 2]) end + +# Compute enlarged NE corner function northeast_corner(E1, C2, E2, peps_above, peps_below=peps_above) @tensor corner[-1 -2 -3; -4 -5 -6] := E1[-1 1 2; 3] * @@ -183,6 +394,8 @@ function northeast_corner(E1, C2, E2, peps_above, peps_below=peps_above) peps_above[7; 1 5 -5 -2] * conj(peps_below[7; 2 6 -6 -3]) end + +# Compute enlarged SE corner function southeast_corner(E2, C3, E3, peps_above, peps_below=peps_above) @tensor corner[-1 -2 -3; -4 -5 -6] := E2[-1 1 2; 3] * @@ -192,130 +405,70 @@ function southeast_corner(E2, C3, E3, peps_above, peps_below=peps_above) conj(peps_below[7; -3 2 6 -6]) end -#= - -function MPSKit.leading_boundary(peps::InfinitePEPS,alg::CTMRG2,envs = CTMRGEnv(peps)) - err = Inf - iter = 1 - - old_norm = 1.0 - - while (err > alg.tol && iter <= alg.maxiter) || iter<4 - - for dir in 1:4 - envs = left_move(peps,alg,envs); - - envs = rotate_north(envs,EAST); - peps = envs.peps; - end - new_norm = abs(contract_ctrmg(peps,envs,1,1)) - #@show new_norm - err = abs(old_norm-new_norm) - @ignore_derivatives mod(alg.verbose,alg.miniter)==0 && mod(iter,alg.verbose+1)==0 && @info "$(iter) $(err) $(new_norm)" - - old_norm = new_norm - iter += 1 - end - @ignore_derivatives iter > alg.maxiter && @warn "maxiter $(alg.maxiter) reached: error was $(err)" +# Build projectors from SVD and enlarged SW & NW corners +function build_projectors( + U::AbstractTensorMap{E,3,1}, S, V::AbstractTensorMap{E,1,3}, Q_sw, Q_nw +) where {E<:ElementarySpace} + isqS = sdiag_inv_sqrt(S) + @tensor Pl[-1 -2 -3; -4] := Q_nw[-1 -2 -3; 1 2 3] * conj(V[4; 1 2 3]) * isqS[4; -4] + @tensor Pr[-1; -2 -3 -4] := isqS[-1; 1] * conj(U[2 3 4; 1]) * Q_sw[2 3 4; -2 -3 -4] + return Pl, Pr +end - envs +# Apply projectors to entire left half-environment to grow SW & NW corners, and W edge +function grow_env_left(peps, Pl, Pr, C_sw, C_nw, T_s, T_w, T_n) + @tensor C_sw′[-1; -2] := C_sw[1; 4] * T_s[-1 2 3; 1] * Pl[4 2 3; -2] + @tensor C_nw′[-1; -2] := C_nw[1; 2] * T_n[2 3 4; -2] * Pr[-1; 1 3 4] + @tensor T_w′[-1 -2 -3; -4] := + T_w[1 2 3; 4] * + peps[9; 5 -2 7 2] * + conj(peps[9; 6 -3 8 3]) * + Pl[4 5 6; -4] * + Pr[-1; 1 7 8] + return C_sw′, C_nw′, T_w′ end -# the actual left_move is dependent on the type of ctmrg, so this seems natural -function left_move(peps::InfinitePEPS{PType},alg::CTMRG2,envs::CTMRGEnv) where PType - corners::typeof(envs.corners) = copy(envs.corners); - edges::typeof(envs.edges) = copy(envs.edges); - - above_projector_type = tensormaptype(spacetype(PType),1,3,storagetype(PType)); - below_projector_type = tensormaptype(spacetype(PType),3,1,storagetype(PType)); - - for col in 1:size(peps,2) - cop = mod1(col+1, size(peps,2)) - above_projs = Vector{above_projector_type}(undef,size(peps,1)); - below_projs = Vector{below_projector_type}(undef,size(peps,1)); - - # find all projectors - for row in 1:size(peps,1) - rop = mod1(row+1, size(peps,1)) - peps_nw = peps[row,col]; - peps_sw = rotate_north(peps[rop,col],WEST); - - Q1 = northwest_corner(envs.edges[WEST,row,col],envs.corners[NORTHWEST,row,col], envs.edges[NORTH,row,col],peps_nw); - Q2 = northeast_corner(envs.edges[NORTH,row,cop],envs.corners[NORTHEAST,row,cop],envs.edges[EAST,row,cop],peps[row,cop]) - Q3 = southeast_corner(envs.edges[EAST,rop,cop],envs.corners[SOUTHEAST,rop,cop],envs.edges[SOUTH,rop,cop],peps[rop,cop]) - Q4 = northwest_corner(envs.edges[SOUTH,rop,col],envs.corners[SOUTHWEST,rop,col],envs.edges[WEST,rop,col],peps_sw); - Qnorth = Q1*Q2 - Qsouth = Q3*Q4 - (U,S,V) = tsvd(Qsouth*Qnorth, trunc = alg.trscheme); - #@ignore_derivatives @show ϵ = real(norm(Qsouth*Qnorth)^2-norm(U*S*V)^2) - #@ignore_derivatives @info ϵ - isqS = sdiag_inv_sqrt(S); - Q = isqS*U'*Qsouth; - P = Qnorth*V'*isqS; - - @diffset above_projs[row] = Q; - @diffset below_projs[row] = P; - end +@doc """ + LinearAlgebra.norm(peps::InfinitePEPS, env::CTMRGEnv) - #use the projectors to grow the corners/edges - for row in 1:size(peps,1) - Q = above_projs[row]; - P = below_projs[mod1(row-1,end)]; - - @diffset @tensor corners[NORTHWEST,row+1,col+1][-1;-2] := envs.corners[NORTHWEST,row+1,col][1,2] * envs.edges[NORTH,row+1,col][2,3,4,-2]*Q[-1;1 3 4] - @diffset @tensor corners[SOUTHWEST,row-1,col+1][-1;-2] := envs.corners[SOUTHWEST,row-1,col][1,4] * envs.edges[SOUTH,row-1,col][-1,2,3,1]*P[4 2 3;-2] - @diffset @tensor edges[WEST,row,col+1][-1 -2 -3;-4] := envs.edges[WEST,row,col][1 2 3;4]* - peps[row,col][9;5 -2 7 2]* - conj(peps[row,col][9;6 -3 8 3])* - P[4 5 6;-4]* - Q[-1;1 7 8] - end +Compute the norm of a PEPS contracted with a CTM environment. +""" - @diffset corners[NORTHWEST,:,col+1]./=norm.(corners[NORTHWEST,:,col+1]); - @diffset corners[SOUTHWEST,:,col+1]./=norm.(corners[SOUTHWEST,:,col+1]); - @diffset edges[WEST,:,col+1]./=norm.(edges[WEST,:,col+1]); - end +function LinearAlgebra.norm(peps::InfinitePEPS, env::CTMRGEnv) + total = one(scalartype(peps)) - CTMRGEnv(peps,corners,edges); -end -=# + for r in 1:size(peps, 1), c in 1:size(peps, 2) + total *= @tensor env.edges[WEST, r, c][1 2 3; 4] * + env.corners[NORTHWEST, r, c][4; 5] * + env.edges[NORTH, r, c][5 6 7; 8] * + env.corners[NORTHEAST, r, c][8; 9] * + env.edges[EAST, r, c][9 10 11; 12] * + env.corners[SOUTHEAST, r, c][12; 13] * + env.edges[SOUTH, r, c][13 14 15; 16] * + env.corners[SOUTHWEST, r, c][16; 1] * + peps[r, c][17; 6 10 14 2] * + conj(peps[r, c][17; 7 11 15 3]) -function contract_ctrmg( - envs::CTMRGEnv, peps_above=envs.peps_above, peps_below=envs.peps_below -) - total = 1.0 + 0im - - for r in 1:size(peps_above, 1), c in 1:size(peps_above, 2) - total *= @tensor envs.edges[WEST, r, c][1 2 3; 4] * - envs.corners[NORTHWEST, r, c][4; 5] * - envs.edges[NORTH, r, c][5 6 7; 8] * - envs.corners[NORTHEAST, r, c][8; 9] * - envs.edges[EAST, r, c][9 10 11; 12] * - envs.corners[SOUTHEAST, r, c][12; 13] * - envs.edges[SOUTH, r, c][13 14 15; 16] * - envs.corners[SOUTHWEST, r, c][16; 1] * - peps_above[r, c][17; 6 10 14 2] * - conj(peps_below[r, c][17; 7 11 15 3]) total *= tr( - envs.corners[NORTHWEST, r, c] * - envs.corners[NORTHEAST, r, mod1(c - 1, end)] * - envs.corners[SOUTHEAST, mod1(r - 1, end), mod1(c - 1, end)] * - envs.corners[SOUTHWEST, mod1(r - 1, end), c], + env.corners[NORTHWEST, r, c] * + env.corners[NORTHEAST, r, mod1(c - 1, end)] * + env.corners[SOUTHEAST, mod1(r - 1, end), mod1(c - 1, end)] * + env.corners[SOUTHWEST, mod1(r - 1, end), c], ) - total /= @tensor envs.edges[WEST, r, c][1 10 11; 4] * - envs.corners[NORTHWEST, r, c][4; 5] * - envs.corners[NORTHEAST, r, mod1(c - 1, end)][5; 6] * - envs.edges[EAST, r, mod1(c - 1, end)][6 10 11; 7] * - envs.corners[SOUTHEAST, r, mod1(c - 1, end)][7; 8] * - envs.corners[SOUTHWEST, r, c][8; 1] - - total /= @tensor envs.corners[NORTHWEST, r, c][1; 2] * - envs.edges[NORTH, r, c][2 10 11; 3] * - envs.corners[NORTHEAST, r, c][3; 4] * - envs.corners[SOUTHEAST, mod1(r - 1, end), c][4; 5] * - envs.edges[SOUTH, mod1(r - 1, end), c][5 10 11; 6] * - envs.corners[SOUTHWEST, mod1(r - 1, end), c][6; 1] + total /= @tensor env.edges[WEST, r, c][1 10 11; 4] * + env.corners[NORTHWEST, r, c][4; 5] * + env.corners[NORTHEAST, r, mod1(c - 1, end)][5; 6] * + env.edges[EAST, r, mod1(c - 1, end)][6 10 11; 7] * + env.corners[SOUTHEAST, r, mod1(c - 1, end)][7; 8] * + env.corners[SOUTHWEST, r, c][8; 1] + + total /= @tensor env.corners[NORTHWEST, r, c][1; 2] * + env.edges[NORTH, r, c][2 10 11; 3] * + env.corners[NORTHEAST, r, c][3; 4] * + env.corners[SOUTHEAST, mod1(r - 1, end), c][4; 5] * + env.edges[SOUTH, mod1(r - 1, end), c][5 10 11; 6] * + env.corners[SOUTHWEST, mod1(r - 1, end), c][6; 1] end return total From fc6600ecc6d7ef5bd3d3f1f3497e8eb147212d68 Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Thu, 4 Jul 2024 18:03:00 +0200 Subject: [PATCH 008/213] Use KrylovKit implementation of eigsolve instead of eigsolve.jl, delete svdwrap wrapper and instead directly use tsvd, add KrylovKit compat entry --- Project.toml | 2 +- src/PEPSKit.jl | 3 +- src/algorithms/ctmrg.jl | 4 +- src/utility/eigsolve.jl | 251 ---------------------- src/utility/svd.jl | 155 +++++-------- test/{svdwrap.jl => ctmrg/svd_wrapper.jl} | 24 +-- test/runtests.jl | 3 + 7 files changed, 72 insertions(+), 370 deletions(-) delete mode 100644 src/utility/eigsolve.jl rename test/{svdwrap.jl => ctmrg/svd_wrapper.jl} (74%) diff --git a/Project.toml b/Project.toml index f8b9f357..98a70c11 100644 --- a/Project.toml +++ b/Project.toml @@ -22,7 +22,7 @@ Zygote = "e88e6eb3-aa80-5325-afca-941959d7151f" Accessors = "0.1" ChainRulesCore = "1.0" Compat = "3.46, 4.2" -KrylovKit = "0.6, 0.7, 0.8" +KrylovKit = "0.8" LinearAlgebra = "1" MPSKit = "0.10" OptimKit = "0.3" diff --git a/src/PEPSKit.jl b/src/PEPSKit.jl index f0cbc7fb..1ee02e1f 100644 --- a/src/PEPSKit.jl +++ b/src/PEPSKit.jl @@ -10,7 +10,6 @@ using ChainRulesCore, Zygote include("utility/util.jl") include("utility/svd.jl") -include("utility/eigsolve.jl") include("utility/rotations.jl") include("utility/hook_pullback.jl") @@ -59,7 +58,7 @@ module Defaults const fpgrad_tol = 1e-6 end -export FullSVD, IterSVD, OldSVD, svdwrap, itersvd +export FullSVD, IterSVD, OldSVD export CTMRG, CTMRGEnv export NLocalOperator, AnisotropicNNOperator, OnSite, NearestNeighbor export expectation_value, costfun diff --git a/src/algorithms/ctmrg.jl b/src/algorithms/ctmrg.jl index a69d6da9..70cbe133 100644 --- a/src/algorithms/ctmrg.jl +++ b/src/algorithms/ctmrg.jl @@ -17,7 +17,7 @@ Regardless of the truncation scheme, the space can be kept fixed with `fixedspac maxiter::Int = Defaults.ctmrg_maxiter miniter::Int = Defaults.ctmrg_miniter verbosity::Int = 0 - svdalg::S = FullSVD() + svdalg::S = TensorKit.SVD() trscheme::TruncationScheme = TensorKit.notrunc() fixedspace::Bool = false end @@ -336,7 +336,7 @@ function left_move(state, env::CTMRGEnv{C,T}, alg::CTMRG) where {C,T} alg.trscheme end @tensor QQ[-1 -2 -3; -4 -5 -6] := Q_sw[-1 -2 -3; 1 2 3] * Q_nw[1 2 3; -4 -5 -6] - U, S, V, ϵ_local = svdwrap(QQ, alg.svdalg; trunc=trscheme) + U, S, V, ϵ_local = tsvd(QQ; trunc=trscheme, alg=alg.svdalg) ϵ = max(ϵ, ϵ_local / norm(S)) # TODO: check if we can just normalize enlarged corners s.t. trunc behaves a bit better diff --git a/src/utility/eigsolve.jl b/src/utility/eigsolve.jl deleted file mode 100644 index 975adf49..00000000 --- a/src/utility/eigsolve.jl +++ /dev/null @@ -1,251 +0,0 @@ -# Copied from Jutho/KrylovKit.jl/pull/56, with minor tweaks - -function ChainRulesCore.rrule( - ::typeof(eigsolve), A::AbstractMatrix, x₀, howmany, which, alg -) - (vals, vecs, info) = eigsolve(A, x₀, howmany, which, alg) - project_A = ProjectTo(A) - T = eltype(vecs[1]) # will be real for real symmetric problems and complex otherwise - - function eigsolve_pullback(ΔX) - _Δvals = unthunk(ΔX[1]) - _Δvecs = unthunk(ΔX[2]) - - ∂self = NoTangent() - ∂x₀ = ZeroTangent() - ∂howmany = NoTangent() - ∂which = NoTangent() - ∂alg = NoTangent() - if _Δvals isa AbstractZero && _Δvecs isa AbstractZero - ∂A = ZeroTangent() - return ∂self, ∂A, ∂x₀, ∂howmany, ∂which, ∂alg - end - - if _Δvals isa AbstractZero - Δvals = fill(NoTangent(), length(Δvecs)) - else - Δvals = _Δvals - end - if _Δvecs isa AbstractZero - Δvecs = fill(NoTangent(), length(Δvals)) - else - Δvecs = _Δvecs - end - - @assert length(Δvals) == length(Δvecs) - @assert length(Δvals) <= length(vals) - - # Determine algorithm to solve linear problem - # TODO: Is there a better choice? Should we make this user configurable? - linalg = GMRES(; - tol=alg.tol, krylovdim=alg.krylovdim, maxiter=alg.maxiter, orth=alg.orth - ) - - ws = similar(vecs, length(Δvecs)) - for i in 1:length(Δvecs) - Δλ = Δvals[i] - Δv = Δvecs[i] - λ = vals[i] - v = vecs[i] - - # First threat special cases - if isa(Δv, AbstractZero) && isa(Δλ, AbstractZero) # no contribution - ws[i] = Δv # some kind of zero - continue - end - if isa(Δv, AbstractZero) && isa(alg, Lanczos) # simple contribution - ws[i] = Δλ * v - continue - end - - # General case : - if isa(Δv, AbstractZero) - b = RecursiveVec(zero(T) * v, T[Δλ]) - else - @assert isa(Δv, typeof(v)) - b = RecursiveVec(Δv, T[Δλ]) - end - - if i > 1 && - eltype(A) <: Real && - vals[i] == conj(vals[i - 1]) && - Δvals[i] == conj(Δvals[i - 1]) && - vecs[i] == conj(vecs[i - 1]) && - Δvecs[i] == conj(Δvecs[i - 1]) - ws[i] = conj(ws[i - 1]) - continue - end - - w, reverse_info = let λ = λ, v = v, Aᴴ = A' - linsolve(b, zero(T) * b, linalg) do x - x1, x2 = x - γ = 1 - # γ can be chosen freely and does not affect the solution theoretically - # The current choice guarantees that the extended matrix is Hermitian if A is - # TODO: is this the best choice in all cases? - y1 = axpy!(-γ * x2[], v, axpy!(-conj(λ), x1, A' * x1)) - y2 = T[-dot(v, x1)] - return RecursiveVec(y1, y2) - end - end - if info.converged >= i && reverse_info.converged == 0 - @warn "The cotangent linear problem did not converge, whereas the primal eigenvalue problem did." - end - ws[i] = w[1] - end - - if A isa StridedMatrix - ∂A = InplaceableThunk( - Ā -> _buildĀ!(Ā, ws, vecs), @thunk(_buildĀ!(zero(A), ws, vecs)) - ) - else - ∂A = @thunk(project_A(_buildĀ!(zero(A), ws, vecs))) - end - return ∂self, ∂A, ∂x₀, ∂howmany, ∂which, ∂alg - end - return (vals, vecs, info), eigsolve_pullback -end - -function _buildĀ!(Ā, ws, vs) - for i in 1:length(ws) - w = ws[i] - v = vs[i] - if !(w isa AbstractZero) - if eltype(Ā) <: Real && eltype(w) <: Complex - mul!(Ā, _realview(w), _realview(v)', -1, 1) - mul!(Ā, _imagview(w), _imagview(v)', -1, 1) - else - mul!(Ā, w, v', -1, 1) - end - end - end - return Ā -end -function _realview(v::AbstractVector{Complex{T}}) where {T} - return view(reinterpret(T, v), 2 * (1:length(v)) .- 1) -end -function _imagview(v::AbstractVector{Complex{T}}) where {T} - return view(reinterpret(T, v), 2 * (1:length(v))) -end - -function ChainRulesCore.rrule( - config::RuleConfig{>:HasReverseMode}, - ::typeof(eigsolve), - A::AbstractMatrix, - x₀, - howmany, - which, - alg, -) - return ChainRulesCore.rrule(eigsolve, A, x₀, howmany, which, alg) -end - -function ChainRulesCore.rrule( - config::RuleConfig{>:HasReverseMode}, ::typeof(eigsolve), f, x₀, howmany, which, alg -) - (vals, vecs, info) = eigsolve(f, x₀, howmany, which, alg) - resize!(vecs, howmany) - resize!(vals, howmany) - T = typeof(dot(vecs[1], vecs[1])) - f_pullbacks = map(x -> rrule_via_ad(config, f, x)[2], vecs) - function eigsolve_pullback(ΔX) - _Δvals = unthunk(ΔX[1]) - _Δvecs = unthunk(ΔX[2]) - - ∂self = NoTangent() - ∂x₀ = ZeroTangent() - ∂howmany = NoTangent() - ∂which = NoTangent() - ∂alg = NoTangent() - if _Δvals isa AbstractZero && _Δvecs isa AbstractZero - ∂A = ZeroTangent() - return (∂self, ∂A, ∂x₀, ∂howmany, ∂which, ∂alg) - end - - if _Δvals isa AbstractZero - Δvals = fill(NoTangent(), howmany) - else - Δvals = _Δvals - end - if _Δvecs isa AbstractZero - Δvecs = fill(NoTangent(), howmany) - else - Δvecs = _Δvecs - end - - # filter ZeroTangents, added compared to Jutho/KrylovKit.jl/pull/56 - Δvecs = filter(x -> !(x isa AbstractZero), Δvecs) - @assert length(Δvals) == length(Δvecs) - - # Determine algorithm to solve linear problem - # TODO: Is there a better choice? Should we make this user configurable? - linalg = GMRES(; - tol=alg.tol, - krylovdim=alg.krylovdim + 10, - maxiter=alg.maxiter * 10, - orth=alg.orth, - ) - # linalg = BiCGStab(; - # tol = alg.tol, - # maxiter = alg.maxiter*alg.krylovdim, - # ) - - ws = similar(Δvecs) - for i in 1:length(Δvecs) - Δλ = Δvals[i] - Δv = Δvecs[i] - λ = vals[i] - v = vecs[i] - - # First threat special cases - if isa(Δv, AbstractZero) && isa(Δλ, AbstractZero) # no contribution - ws[i] = 0 * v # some kind of zero - continue - end - if isa(Δv, AbstractZero) && isa(alg, Lanczos) # simple contribution - ws[i] = Δλ * v - continue - end - - # General case : - if isa(Δv, AbstractZero) - b = RecursiveVec(zero(T) * v, T[-Δλ]) - else - @assert isa(Δv, typeof(v)) - b = RecursiveVec(-Δv, T[-Δλ]) - end - - # TODO: is there any analogy to this for general vector-like user types - # if i > 1 && eltype(A) <: Real && - # vals[i] == conj(vals[i-1]) && Δvals[i] == conj(Δvals[i-1]) && - # vecs[i] == conj(vecs[i-1]) && Δvecs[i] == conj(Δvecs[i-1]) - # - # ws[i] = conj(ws[i-1]) - # continue - # end - - w, reverse_info = let λ = λ, v = v, fᴴ = x -> f_pullbacks[i](x)[2] - linsolve(b, zero(T) * b, linalg) do x - x1, x2 = x - γ = 1 - # γ can be chosen freely and does not affect the solution theoretically - # The current choice guarantees that the extended matrix is Hermitian if A is - # TODO: is this the best choice in all cases? - y1 = axpy!(-γ * x2[], v, axpy!(-conj(λ), x1, fᴴ(x1))) - y2 = T[-dot(v, x1)] - return RecursiveVec(y1, y2) - end - end - if info.converged >= i && reverse_info.converged == 0 - @warn "The cotangent linear problem ($i) did not converge, whereas the primal eigenvalue problem did." reverse_info b - end - ws[i] = w[1] - end - ∂f = f_pullbacks[1](ws[1])[1] - for i in 2:length(ws) - ∂f = VectorInterface.add!!(∂f, f_pullbacks[i](ws[i])[1]) - end - return ∂self, ∂f, ∂x₀, ∂howmany, ∂which, ∂alg - end - return (vals, vecs, info), eigsolve_pullback -end diff --git a/src/utility/svd.jl b/src/utility/svd.jl index d06b27c8..ebcc9784 100644 --- a/src/utility/svd.jl +++ b/src/utility/svd.jl @@ -1,108 +1,56 @@ -import TensorKit: - SectorDict, - _empty_svdtensors, - _compute_svddata!, - _truncate!, - _implement_svdtruncation!, - _create_svdtensors - -# Plain copy of tsvd!(...) from TensorKit to lift alg type restriction -function _tensorkit_svd!( - t::TensorMap; - trunc::TruncationScheme=TensorKit.NoTruncation(), - p::Real=2, - alg=TensorKit.SVD(), -) - #early return - if isempty(blocksectors(t)) - truncerr = zero(real(scalartype(t))) - return _empty_svdtensors(t)..., truncerr - end - - S = spacetype(t) - Udata, Σdata, Vdata, dims = _compute_svddata!(t, alg) - if !isa(trunc, TensorKit.NoTruncation) - Σdata, truncerr = _truncate!(Σdata, trunc, p) - Udata, Σdata, Vdata, dims = _implement_svdtruncation!(t, Udata, Σdata, Vdata, dims) - W = S(dims) - else - truncerr = abs(zero(scalartype(t))) - W = S(dims) - if length(domain(t)) == 1 && domain(t)[1] ≅ W - W = domain(t)[1] - elseif length(codomain(t)) == 1 && codomain(t)[1] ≅ W - W = codomain(t)[1] - end - end - return _create_svdtensors(t, Udata, Σdata, Vdata, W)..., truncerr -end - -# Wrapper struct around TensorKit's SVD algorithms -@kwdef struct FullSVD - alg::Union{<:TensorKit.SVD,<:TensorKit.SDD} = TensorKit.SVD() - lorentz_broad::Float64 = 0.0 -end - -function svdwrap(t::AbstractTensorMap, alg::FullSVD; trunc=notrunc(), kwargs...) - # TODO: Replace _tensorkit_svd! with just tsvd eventually to use the full TensorKit machinery - return _tensorkit_svd!(copy(t); trunc, alg.alg) -end - -function ChainRulesCore.rrule( - ::typeof(svdwrap), t::AbstractTensorMap, alg::FullSVD; trunc=notrunc(), kwargs... -) - tsvd_return, tsvd!_pullback = ChainRulesCore.rrule(tsvd!, t; alg=TensorKit.SVD(), trunc) - function svdwrap_fullsvd_pullback(Δ) - return tsvd!_pullback(Δ)..., NoTangent() - end - return tsvd_return, svdwrap_fullsvd_pullback -end +using TensorKit: SectorDict # Wrapper around Krylov Kit's GKL iterative SVD solver @kwdef struct IterSVD alg::KrylovKit.GKL = KrylovKit.GKL(; tol=1e-14, krylovdim=25) - howmany::Int = 20 lorentz_broad::Float64 = 0.0 alg_rrule::Union{GMRES,BiCGStab,Arnoldi} = GMRES(; tol=1e-14) end -function svdwrap(t::AbstractTensorMap, alg::IterSVD; trunc=notrunc(), kwargs...) - U, S, V, = _tensorkit_svd!(copy(t); trunc, alg) # TODO: Also replace this with tsvd eventually - ϵ = norm(t - U * S * V) # Compute truncation error separately - return U, S, V, ϵ +# Compute SVD data block-wise using KrylovKit algorithm +function TensorKit._tsvd!(t, alg::IterSVD, trunc::TruncationScheme, p::Real=2) + # TODO end -# Compute SVD data block-wise using KrylovKit algorithm -function TensorKit._compute_svddata!(t::TensorMap, alg::IterSVD) - InnerProductStyle(t) === EuclideanProduct() || throw_invalid_innerproduct(:tsvd!) - I = sectortype(t) - A = storagetype(t) - Udata = SectorDict{I,A}() - Vdata = SectorDict{I,A}() - dims = SectorDict{I,Int}() - local Σdata - for (c, b) in blocks(t) - x₀ = randn(eltype(b), size(b, 1)) - Σ, lvecs, rvecs, info = svdsolve(b, x₀, alg.howmany, :LR, alg.alg) - if info.converged < alg.howmany # Fall back to dense SVD if not properly converged - U, Σ, V = TensorKit.MatrixAlgebra.svd!(b, TensorKit.SVD()) - Udata[c] = U - Vdata[c] = V - else - Udata[c] = stack(lvecs) - Vdata[c] = stack(rvecs)' - end - if @isdefined Σdata # cannot easily infer the type of Σ, so use this construction - Σdata[c] = Σ - else - Σdata = SectorDict(c => Σ) - end - dims[c] = length(Σ) - end - return Udata, Σdata, Vdata, dims +# function TensorKit._compute_svddata!(t::TensorMap, alg::IterSVD) +# InnerProductStyle(t) === EuclideanProduct() || throw_invalid_innerproduct(:tsvd!) +# I = sectortype(t) +# A = storagetype(t) +# Udata = SectorDict{I,A}() +# Vdata = SectorDict{I,A}() +# dims = SectorDict{I,Int}() +# local Σdata +# for (c, b) in blocks(t) +# x₀ = randn(eltype(b), size(b, 1)) +# Σ, lvecs, rvecs, info = KrylovKit.svdsolve(b, x₀, alg.howmany, :LR, alg.alg) +# if info.converged < alg.howmany # Fall back to dense SVD if not properly converged +# U, Σ, V = TensorKit.MatrixAlgebra.svd!(b, TensorKit.SVD()) +# Udata[c] = U +# Vdata[c] = V +# else +# Udata[c] = stack(lvecs) +# Vdata[c] = stack(rvecs)' +# end +# if @isdefined Σdata # cannot easily infer the type of Σ, so use this construction +# Σdata[c] = Σ +# else +# Σdata = SectorDict(c => Σ) +# end +# dims[c] = length(Σ) +# end +# return Udata, Σdata, Vdata, dims +# end + +function ChainRulesCore.rrule( + ::typeof(TensorKit.tsvd), + t::AbstractTensorMap; + trunc::TruncationScheme=notrunc(), + p::Real=2, + alg::IterSVD=IterSVD(), +) + # TODO: IterSVD adjoint utilizing KryloVKit svdsolve adjoint end -# TODO: IterSVD adjoint utilizing KryloVKit svdsolve adjoint # Full SVD with old adjoint that doesn't account for truncation properly @kwdef struct OldSVD{A<:Union{FullSVD,IterSVD}} @@ -110,17 +58,22 @@ end lorentz_broad::Float64 = 0.0 end -function svdwrap(t::AbstractTensorMap, alg::OldSVD; kwargs...) - return svdwrap(t, alg.alg; kwargs...) +# Perform TensorKit.SVD in forward pass +function TensorKit._tsvd!(t, ::OldSVD, trunc::TruncationScheme, p::Real=2) + return TensorKit._tsvd(t, TensorKit.SVD(), trunc, p) end -# Outdated adjoint not taking truncated part into account for testing purposes +# Use outdated adjoint in reverse pass (not taking truncated part into account for testing purposes) function ChainRulesCore.rrule( - ::typeof(svdwrap), t::AbstractTensorMap, alg::OldSVD; kwargs... + ::typeof(TensorKit.tsvd), + t::AbstractTensorMap; + trunc::TruncationScheme=notrunc(), + p::Real=2, + alg::OldSVD=OldSVD(), ) - U, S, V, ϵ = svdwrap(t, alg; kwargs...) + U, S, V, ϵ = tsvd(t; trunc, p, alg) - function svdwrap_oldsvd_pullback((ΔU, ΔS, ΔV, Δϵ)) + function tsvd_oldsvd_pullback((ΔU, ΔS, ΔV, Δϵ)) ∂t = similar(t) for (c, b) in blocks(∂t) copyto!( @@ -139,7 +92,7 @@ function ChainRulesCore.rrule( return NoTangent(), ∂t, NoTangent() end - return (U, S, V, ϵ), svdwrap_oldsvd_pullback + return (U, S, V, ϵ), tsvd_oldsvd_pullback end function oldsvd_rev( @@ -204,4 +157,4 @@ end function _lorentz_broaden(x::Real, ε=1e-12) x′ = 1 / x return x′ / (x′^2 + ε) -end \ No newline at end of file +end diff --git a/test/svdwrap.jl b/test/ctmrg/svd_wrapper.jl similarity index 74% rename from test/svdwrap.jl rename to test/ctmrg/svd_wrapper.jl index 3c3a0748..ab0989df 100644 --- a/test/svdwrap.jl +++ b/test/ctmrg/svd_wrapper.jl @@ -8,7 +8,7 @@ using PEPSKit # Gauge-invariant loss function function lossfun(A, alg, R=TensorMap(randn, space(A)); trunc=notrunc()) - U, _, V, = svdwrap(A, alg; trunc) + U, _, V, = tsvd(A; trunc, alg) return real(dot(R, U * V)) # Overlap with random tensor R is gauge-invariant and differentiable, also for m≠n end @@ -25,27 +25,25 @@ R = TensorMap(randn, space(r)) @testset "Non-truncacted SVD" begin l_fullsvd, g_fullsvd = withgradient(A -> lossfun(A, FullSVD(), R), r) l_oldsvd, g_oldsvd = withgradient(A -> lossfun(A, OldSVD(), R), r) - # l_itersvd, g_itersvd = withgradient( - # A -> lossfun(A, IterSVD(; howmany=min(m, n), adjoint_tol), R), r - # ) + l_itersvd, g_itersvd = withgradient( + A -> lossfun(A, IterSVD(; howmany=min(m, n), adjoint_tol), R), r + ) - @test l_oldsvd ≈ l_fullsvd - # @test l_oldsvd ≈ l_itersvd ≈ l_fullsvd + @test l_oldsvd ≈ l_itersvd ≈ l_fullsvd @test norm(g_fullsvd[1] - g_oldsvd[1]) / norm(g_fullsvd[1]) < rtol - # @test norm(g_tsvd[1] - g_itersvd[1]) / norm(g_tsvd[1]) < rtol + @test norm(g_fullsvd[1] - g_itersvd[1]) / norm(g_fullsvd[1]) < rtol end @testset "Truncated SVD with χ=$χ" begin l_fullsvd, g_fullsvd = withgradient(A -> lossfun(A, FullSVD(), R; trunc), r) l_oldsvd, g_oldsvd = withgradient(A -> lossfun(A, OldSVD(), R; trunc), r) - # l_itersvd, g_itersvd = withgradient( - # A -> lossfun(A, IterSVD(; howmany=χ, adjoint_tol), R; trunc), r - # ) + l_itersvd, g_itersvd = withgradient( + A -> lossfun(A, IterSVD(; howmany=χ, adjoint_tol), R; trunc), r + ) - @test l_oldsvd ≈ l_fullsvd - # @test l_oldsvd ≈ l_itersvd ≈ l_fullsvd + @test l_oldsvd ≈ l_itersvd ≈ l_fullsvd @test norm(g_fullsvd[1] - g_oldsvd[1]) / norm(g_fullsvd[1]) > rtol - # @test norm(g_tsvd[1] - g_itersvd[1]) / norm(g_tsvd[1]) < rtol + @test norm(g_fullsvd[1] - g_itersvd[1]) / norm(g_fullsvd[1]) < rtol end # @testset "Truncated SVD with χ=$χ and ε=$lorentz_broad broadening" begin diff --git a/test/runtests.jl b/test/runtests.jl index af2023e1..33e2fc42 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -10,6 +10,9 @@ const GROUP = get(ENV, "GROUP", "All") @time @safetestset "Gradients" begin include("ctmrg/gradients.jl") end + @time @safetestset "Gradients" begin + include("ctmrg/svd_wrapper.jl") + end end if GROUP == "All" || GROUP == "MPS" @time @safetestset "VUMPS" begin From 823f52ef331e761e5831a79d3a1b30882d6b9e5f Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Fri, 5 Jul 2024 12:18:30 +0200 Subject: [PATCH 009/213] Add IterSVD _tsvd! method and adjoint using KrylovKit.svdsolve adjoint --- src/utility/svd.jl | 131 +++++++++++++++++++++++++++++++-------------- 1 file changed, 92 insertions(+), 39 deletions(-) diff --git a/src/utility/svd.jl b/src/utility/svd.jl index ebcc9784..6995fc94 100644 --- a/src/utility/svd.jl +++ b/src/utility/svd.jl @@ -8,53 +8,106 @@ using TensorKit: SectorDict end # Compute SVD data block-wise using KrylovKit algorithm -function TensorKit._tsvd!(t, alg::IterSVD, trunc::TruncationScheme, p::Real=2) - # TODO -end +function _tsvd!( + t, + alg::IterSVD, + trunc::Union{TensorKit.NoTruncation,TensorKit.TruncationSpace}, + p::Real=2, +) + # early return + if isempty(blocksectors(t)) + truncerr = zero(real(scalartype(t))) + return _empty_svdtensors(t)..., truncerr + end + + Udata, Σdata, Vdata, dims = _compute_svddata!(t, alg, trunc) + U, S, V = _create_svdtensors(t, Udata, Σdata, Vdata, spacetype(t)(dims)) + truncerr = trunc isa NoTruncation ? abs(zero(scalartype(t))) : norm(U * S * V - t, p) -# function TensorKit._compute_svddata!(t::TensorMap, alg::IterSVD) -# InnerProductStyle(t) === EuclideanProduct() || throw_invalid_innerproduct(:tsvd!) -# I = sectortype(t) -# A = storagetype(t) -# Udata = SectorDict{I,A}() -# Vdata = SectorDict{I,A}() -# dims = SectorDict{I,Int}() -# local Σdata -# for (c, b) in blocks(t) -# x₀ = randn(eltype(b), size(b, 1)) -# Σ, lvecs, rvecs, info = KrylovKit.svdsolve(b, x₀, alg.howmany, :LR, alg.alg) -# if info.converged < alg.howmany # Fall back to dense SVD if not properly converged -# U, Σ, V = TensorKit.MatrixAlgebra.svd!(b, TensorKit.SVD()) -# Udata[c] = U -# Vdata[c] = V -# else -# Udata[c] = stack(lvecs) -# Vdata[c] = stack(rvecs)' -# end -# if @isdefined Σdata # cannot easily infer the type of Σ, so use this construction -# Σdata[c] = Σ -# else -# Σdata = SectorDict(c => Σ) -# end -# dims[c] = length(Σ) -# end -# return Udata, Σdata, Vdata, dims -# end + return U, S, V, truncerr +end +function TensorKit._compute_svddata!( + t::TensorMap, + alg::IterSVD, + trunc::Union{TensorKit.NoTruncation,TensorKit.TruncationSpace}, +) + InnerProductStyle(t) === EuclideanProduct() || throw_invalid_innerproduct(:tsvd!) + I = sectortype(t) + A = storagetype(t) + Udata = SectorDict{I,A}() + Vdata = SectorDict{I,A}() + dims = SectorDict{I,Int}() + local Σdata + for (c, b) in blocks(t) + x₀ = randn(eltype(b), size(b, 1)) + howmany = trunc isa NoTruncation ? minimum(size(b)) : blockdim(trunc.space, c) + Σ, lvecs, rvecs, info = KrylovKit.svdsolve(b, x₀, howmany, :LR, alg.alg) + if info.converged < howmany # Fall back to dense SVD if not properly converged + U, Σ, V = TensorKit.MatrixAlgebra.svd!(b, TensorKit.SVD()) + Udata[c] = U + Vdata[c] = V + else + Udata[c] = stack(lvecs) + Vdata[c] = stack(rvecs)' + end + if @isdefined Σdata # cannot easily infer the type of Σ, so use this construction + Σdata[c] = Σ + else + Σdata = SectorDict(c => Σ) + end + dims[c] = length(Σ) + end + return Udata, Σdata, Vdata, dims +end +# IterSVD adjoint for tsvd! using KrylovKit.svdsolve adjoint machinery for each block function ChainRulesCore.rrule( - ::typeof(TensorKit.tsvd), + ::typeof(TensorKit.tsvd!), t::AbstractTensorMap; trunc::TruncationScheme=notrunc(), p::Real=2, alg::IterSVD=IterSVD(), ) - # TODO: IterSVD adjoint utilizing KryloVKit svdsolve adjoint -end + U, S, V, ϵ = tsvd(t; trunc, p, alg) + function tsvd!_itersvd_pullback((ΔU, ΔS, ΔV, Δϵ)) + Δt = similar(t) + for (c, b) in blocks(Δt) + Uc, Sc, Vc = block(U, c), block(S, c), block(V, c) + ΔUc, ΔSc, ΔVc = block(ΔU, c), block(ΔS, c), block(ΔV, c) + Sdc = view(Sc, diagind(Sc)) + ΔSdc = (ΔSc isa AbstractZero) ? ΔSc : view(ΔSc, diagind(ΔSc)) + + lvecs = eachcol(Uc) + rvecs = eachrow(Vc) + minimal_info = KrylovKit.ConvergenceInfo(length(Sdc), nothing, nothing, -1, -1) # Just supply converged to SVD pullback + xs, ys = compute_svdsolve_pullback_data( + ΔSdc, + eachcol(ΔUc), + eachrow(ΔVc), + Sdc, + lvecs, + rvecs, + minimal_info, + b, + :LR, + alg.alg, + alg.alg_rrule, + ) + copyto!(b, construct∂f_svd(config, b, lvecs, rvecs, xs, ys)) + end + return NoTangent(), Δt + end + function tsvd!_itersvd_pullback(::Tuple{ZeroTangent,ZeroTangent,ZeroTangent}) + return NoTangent(), ZeroTangent() + end + + return (U, S, V, ϵ), tsvd!_itersvd_pullback +end # Full SVD with old adjoint that doesn't account for truncation properly -@kwdef struct OldSVD{A<:Union{FullSVD,IterSVD}} - alg::A = FullSVD() +@kwdef struct OldSVD{A<:Union{TensorKit.SDD,TensorKit.SVD,IterSVD}} + alg::A = TensorKit.SVD() lorentz_broad::Float64 = 0.0 end @@ -65,7 +118,7 @@ end # Use outdated adjoint in reverse pass (not taking truncated part into account for testing purposes) function ChainRulesCore.rrule( - ::typeof(TensorKit.tsvd), + ::typeof(TensorKit.tsvd!), t::AbstractTensorMap; trunc::TruncationScheme=notrunc(), p::Real=2, @@ -73,7 +126,7 @@ function ChainRulesCore.rrule( ) U, S, V, ϵ = tsvd(t; trunc, p, alg) - function tsvd_oldsvd_pullback((ΔU, ΔS, ΔV, Δϵ)) + function tsvd!_oldsvd_pullback((ΔU, ΔS, ΔV, Δϵ)) ∂t = similar(t) for (c, b) in blocks(∂t) copyto!( @@ -92,7 +145,7 @@ function ChainRulesCore.rrule( return NoTangent(), ∂t, NoTangent() end - return (U, S, V, ϵ), tsvd_oldsvd_pullback + return (U, S, V, ϵ), tsvd!_oldsvd_pullback end function oldsvd_rev( From a615b4a22744093e594acb842ae650669444b27f Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Fri, 5 Jul 2024 16:44:21 +0200 Subject: [PATCH 010/213] Add PEPSKit.tsvd wrapper, fix IterSVD adjoint --- src/algorithms/ctmrg.jl | 2 +- src/utility/svd.jl | 133 +++++++++++++++++++++----------------- test/ctmrg/svd_wrapper.jl | 23 +++---- 3 files changed, 88 insertions(+), 70 deletions(-) diff --git a/src/algorithms/ctmrg.jl b/src/algorithms/ctmrg.jl index 70cbe133..5b4d65dc 100644 --- a/src/algorithms/ctmrg.jl +++ b/src/algorithms/ctmrg.jl @@ -336,7 +336,7 @@ function left_move(state, env::CTMRGEnv{C,T}, alg::CTMRG) where {C,T} alg.trscheme end @tensor QQ[-1 -2 -3; -4 -5 -6] := Q_sw[-1 -2 -3; 1 2 3] * Q_nw[1 2 3; -4 -5 -6] - U, S, V, ϵ_local = tsvd(QQ; trunc=trscheme, alg=alg.svdalg) + U, S, V, ϵ_local = PEPSKit.tsvd(QQ, alg.svdalg; trunc=trscheme) ϵ = max(ϵ, ϵ_local / norm(S)) # TODO: check if we can just normalize enlarged corners s.t. trunc behaves a bit better diff --git a/src/utility/svd.jl b/src/utility/svd.jl index 6995fc94..1dae6534 100644 --- a/src/utility/svd.jl +++ b/src/utility/svd.jl @@ -1,4 +1,19 @@ -using TensorKit: SectorDict +using TensorKit: + SectorDict, + _tsvd!, + _empty_svdtensors, + _compute_svddata!, + _create_svdtensors, + NoTruncation, + TruncationSpace +CRCExt = Base.get_extension(KrylovKit, :KrylovKitChainRulesCoreExt) + +# SVD wrapper for TensorKit.tsvd that dispatches on the alg type +function PEPSKit.tsvd( + t::AbstractTensorMap, alg; trunc::TruncationScheme=notrunc(), p::Real=2 +) + return TensorKit.tsvd(t; alg, trunc, p) +end # Wrapper around Krylov Kit's GKL iterative SVD solver @kwdef struct IterSVD @@ -8,11 +23,8 @@ using TensorKit: SectorDict end # Compute SVD data block-wise using KrylovKit algorithm -function _tsvd!( - t, - alg::IterSVD, - trunc::Union{TensorKit.NoTruncation,TensorKit.TruncationSpace}, - p::Real=2, +function TensorKit._tsvd!( + t, alg::IterSVD, trunc::Union{NoTruncation,TruncationSpace}, p::Real=2 ) # early return if isempty(blocksectors(t)) @@ -27,9 +39,7 @@ function _tsvd!( return U, S, V, truncerr end function TensorKit._compute_svddata!( - t::TensorMap, - alg::IterSVD, - trunc::Union{TensorKit.NoTruncation,TensorKit.TruncationSpace}, + t::TensorMap, alg::IterSVD, trunc::Union{NoTruncation,TruncationSpace} ) InnerProductStyle(t) === EuclideanProduct() || throw_invalid_innerproduct(:tsvd!) I = sectortype(t) @@ -37,72 +47,83 @@ function TensorKit._compute_svddata!( Udata = SectorDict{I,A}() Vdata = SectorDict{I,A}() dims = SectorDict{I,Int}() - local Σdata + local Sdata for (c, b) in blocks(t) x₀ = randn(eltype(b), size(b, 1)) howmany = trunc isa NoTruncation ? minimum(size(b)) : blockdim(trunc.space, c) - Σ, lvecs, rvecs, info = KrylovKit.svdsolve(b, x₀, howmany, :LR, alg.alg) + S, lvecs, rvecs, info = KrylovKit.svdsolve(b, x₀, howmany, :LR, alg.alg) if info.converged < howmany # Fall back to dense SVD if not properly converged - U, Σ, V = TensorKit.MatrixAlgebra.svd!(b, TensorKit.SVD()) + U, S, V = TensorKit.MatrixAlgebra.svd!(b, TensorKit.SVD()) Udata[c] = U Vdata[c] = V - else - Udata[c] = stack(lvecs) - Vdata[c] = stack(rvecs)' + else # Slice in case more values were converged than requested + Udata[c] = stack(lvecs[1:howmany]) + Vdata[c] = stack(rvecs[1:howmany])' + S = S[1:howmany] end - if @isdefined Σdata # cannot easily infer the type of Σ, so use this construction - Σdata[c] = Σ + if @isdefined Sdata # cannot easily infer the type of Σ, so use this construction + Sdata[c] = S else - Σdata = SectorDict(c => Σ) + Sdata = SectorDict(c => S) end - dims[c] = length(Σ) + dims[c] = length(S) end - return Udata, Σdata, Vdata, dims + return Udata, Sdata, Vdata, dims end # IterSVD adjoint for tsvd! using KrylovKit.svdsolve adjoint machinery for each block function ChainRulesCore.rrule( - ::typeof(TensorKit.tsvd!), - t::AbstractTensorMap; + ::typeof(PEPSKit.tsvd), + t::AbstractTensorMap, + alg::IterSVD; trunc::TruncationScheme=notrunc(), p::Real=2, - alg::IterSVD=IterSVD(), ) - U, S, V, ϵ = tsvd(t; trunc, p, alg) + U, S, V, ϵ = PEPSKit.tsvd(t, alg; trunc, p) - function tsvd!_itersvd_pullback((ΔU, ΔS, ΔV, Δϵ)) + function tsvd_itersvd_pullback((ΔU, ΔS, ΔV, Δϵ)) Δt = similar(t) for (c, b) in blocks(Δt) Uc, Sc, Vc = block(U, c), block(S, c), block(V, c) ΔUc, ΔSc, ΔVc = block(ΔU, c), block(ΔS, c), block(ΔV, c) Sdc = view(Sc, diagind(Sc)) - ΔSdc = (ΔSc isa AbstractZero) ? ΔSc : view(ΔSc, diagind(ΔSc)) + ΔSdc = ΔSc isa AbstractZero ? ΔSc : view(ΔSc, diagind(ΔSc)) - lvecs = eachcol(Uc) - rvecs = eachrow(Vc) + n_vals = length(Sdc) + lvecs = Vector{Vector{scalartype(t)}}(eachcol(Uc)) + rvecs = Vector{Vector{scalartype(t)}}(eachcol(Vc')) minimal_info = KrylovKit.ConvergenceInfo(length(Sdc), nothing, nothing, -1, -1) # Just supply converged to SVD pullback - xs, ys = compute_svdsolve_pullback_data( - ΔSdc, - eachcol(ΔUc), - eachrow(ΔVc), + + if ΔUc isa AbstractZero && ΔVc isa AbstractZero # Handle ZeroTangent singular vectors + Δlvecs = fill(ZeroTangent(), n_vals) + Δrvecs = fill(ZeroTangent(), n_vals) + else + Δlvecs = Vector{Vector{scalartype(t)}}(eachcol(ΔUc)) + Δrvecs = Vector{Vector{scalartype(t)}}(eachcol(ΔVc')) + end + + xs, ys = CRCExt.compute_svdsolve_pullback_data( + ΔSc isa AbstractZero ? fill(zero(Sc[1]), n_vals) : ΔSdc, + Δlvecs, + Δrvecs, Sdc, lvecs, rvecs, minimal_info, - b, + block(t, c), :LR, alg.alg, alg.alg_rrule, ) - copyto!(b, construct∂f_svd(config, b, lvecs, rvecs, xs, ys)) + copyto!(b, CRCExt.construct∂f_svd(HasReverseMode(), block(t, c), lvecs, rvecs, xs, ys)) end - return NoTangent(), Δt + return NoTangent(), Δt, NoTangent() end - function tsvd!_itersvd_pullback(::Tuple{ZeroTangent,ZeroTangent,ZeroTangent}) - return NoTangent(), ZeroTangent() + function tsvd_itersvd_pullback(::Tuple{ZeroTangent,ZeroTangent,ZeroTangent}) + return NoTangent(), ZeroTangent(), NoTangent() end - return (U, S, V, ϵ), tsvd!_itersvd_pullback + return (U, S, V, ϵ), tsvd_itersvd_pullback end # Full SVD with old adjoint that doesn't account for truncation properly @@ -113,39 +134,35 @@ end # Perform TensorKit.SVD in forward pass function TensorKit._tsvd!(t, ::OldSVD, trunc::TruncationScheme, p::Real=2) - return TensorKit._tsvd(t, TensorKit.SVD(), trunc, p) + return _tsvd!(t, TensorKit.SVD(), trunc, p) end # Use outdated adjoint in reverse pass (not taking truncated part into account for testing purposes) function ChainRulesCore.rrule( - ::typeof(TensorKit.tsvd!), - t::AbstractTensorMap; + ::typeof(PEPSKit.tsvd), + t::AbstractTensorMap, + alg::OldSVD; trunc::TruncationScheme=notrunc(), p::Real=2, - alg::OldSVD=OldSVD(), ) - U, S, V, ϵ = tsvd(t; trunc, p, alg) + U, S, V, ϵ = PEPSKit.tsvd(t, alg; trunc, p) - function tsvd!_oldsvd_pullback((ΔU, ΔS, ΔV, Δϵ)) - ∂t = similar(t) - for (c, b) in blocks(∂t) + function tsvd_oldsvd_pullback((ΔU, ΔS, ΔV, Δϵ)) + Δt = similar(t) + for (c, b) in blocks(Δt) + Uc, Sc, Vc = block(U, c), block(S, c), block(V, c) + ΔUc, ΔSc, ΔVc = block(ΔU, c), block(ΔS, c), block(ΔV, c) copyto!( - b, - oldsvd_rev( - block(U, c), - block(S, c), - block(V, c), - block(ΔU, c), - block(ΔS, c), - block(ΔV, c); - lorentz_broad=alg.lorentz_broad, - ), + b, oldsvd_rev(Uc, Sc, Vc, ΔUc, ΔSc, ΔVc; lorentz_broad=alg.lorentz_broad) ) end - return NoTangent(), ∂t, NoTangent() + return NoTangent(), Δt, NoTangent() + end + function tsvd_oldsvd_pullback(::Tuple{ZeroTangent,ZeroTangent,ZeroTangent}) + return NoTangent(), ZeroTangent(), NoTangent() end - return (U, S, V, ϵ), tsvd!_oldsvd_pullback + return (U, S, V, ϵ), tsvd_oldsvd_pullback end function oldsvd_rev( diff --git a/test/ctmrg/svd_wrapper.jl b/test/ctmrg/svd_wrapper.jl index ab0989df..c725a6b1 100644 --- a/test/ctmrg/svd_wrapper.jl +++ b/test/ctmrg/svd_wrapper.jl @@ -7,45 +7,46 @@ using ChainRulesCore, Zygote using PEPSKit # Gauge-invariant loss function -function lossfun(A, alg, R=TensorMap(randn, space(A)); trunc=notrunc()) - U, _, V, = tsvd(A; trunc, alg) +function lossfun(A, alg, R=TensorMap(randn, space(A)), trunc=notrunc()) + U, _, V, = PEPSKit.tsvd(A, alg; trunc) return real(dot(R, U * V)) # Overlap with random tensor R is gauge-invariant and differentiable, also for m≠n end m, n = 20, 30 dtype = ComplexF64 χ = 12 -trunc = truncdim(χ) +trunc = truncspace(ℂ^χ) # lorentz_broad = 1e-12 -adjoint_tol = 1e-16 +adjoint_tol = 1e-14 # Don't make this too small, g_itersvd will be weird rtol = 1e-9 r = TensorMap(randn, dtype, ℂ^m ← ℂ^n) R = TensorMap(randn, space(r)) @testset "Non-truncacted SVD" begin - l_fullsvd, g_fullsvd = withgradient(A -> lossfun(A, FullSVD(), R), r) + l_fullsvd, g_fullsvd = withgradient(A -> lossfun(A, TensorKit.SVD(), R), r) l_oldsvd, g_oldsvd = withgradient(A -> lossfun(A, OldSVD(), R), r) l_itersvd, g_itersvd = withgradient( - A -> lossfun(A, IterSVD(; howmany=min(m, n), adjoint_tol), R), r + A -> lossfun(A, IterSVD(; alg_rrule=GMRES(; tol=adjoint_tol)), R, truncspace(ℂ^min(m, n))), r ) - @test l_oldsvd ≈ l_itersvd ≈ l_fullsvd + @test l_oldsvd ≈ l_itersvd ≈ l_fullsvd @test norm(g_fullsvd[1] - g_oldsvd[1]) / norm(g_fullsvd[1]) < rtol @test norm(g_fullsvd[1] - g_itersvd[1]) / norm(g_fullsvd[1]) < rtol end @testset "Truncated SVD with χ=$χ" begin - l_fullsvd, g_fullsvd = withgradient(A -> lossfun(A, FullSVD(), R; trunc), r) - l_oldsvd, g_oldsvd = withgradient(A -> lossfun(A, OldSVD(), R; trunc), r) + l_fullsvd, g_fullsvd = withgradient(A -> lossfun(A, TensorKit.SVD(), R, trunc), r) + l_oldsvd, g_oldsvd = withgradient(A -> lossfun(A, OldSVD(), R, trunc), r) l_itersvd, g_itersvd = withgradient( - A -> lossfun(A, IterSVD(; howmany=χ, adjoint_tol), R; trunc), r + A -> lossfun(A, IterSVD(; alg_rrule=GMRES(; tol=adjoint_tol)), R, trunc), r ) - @test l_oldsvd ≈ l_itersvd ≈ l_fullsvd + @test l_oldsvd ≈ l_itersvd ≈ l_fullsvd @test norm(g_fullsvd[1] - g_oldsvd[1]) / norm(g_fullsvd[1]) > rtol @test norm(g_fullsvd[1] - g_itersvd[1]) / norm(g_fullsvd[1]) < rtol end +# TODO: Add when Lorentzian broadening is implemented # @testset "Truncated SVD with χ=$χ and ε=$lorentz_broad broadening" begin # l_fullsvd, g_fullsvd = withgradient( # A -> lossfun(A, FullSVD(; lorentz_broad, R; trunc), r From a815e41846e7aa80f4a9f69ed559afc675c9ed10 Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Fri, 5 Jul 2024 16:48:37 +0200 Subject: [PATCH 011/213] Add TensorKit compat entry for softened tsvd type restrictions --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 98a70c11..95074716 100644 --- a/Project.toml +++ b/Project.toml @@ -28,7 +28,7 @@ MPSKit = "0.10" OptimKit = "0.3" Printf = "1" Statistics = "1" -TensorKit = "0.12" +TensorKit = "0.12.5" VectorInterface = "0.4" Zygote = "0.6" julia = "1.6" From 447bfd83fece742475044c513ac8ceb91142e897 Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Fri, 5 Jul 2024 17:40:23 +0200 Subject: [PATCH 012/213] Add ProjectorAlg and refactor all tests and examples --- examples/boundary_mps.jl | 8 ++++-- examples/heisenberg.jl | 11 ++++---- src/PEPSKit.jl | 2 +- src/algorithms/ctmrg.jl | 59 ++++++++++++++++++++++++++++----------- src/utility/svd.jl | 14 ++++++++-- test/boundarymps/vumps.jl | 8 ++++-- test/ctmrg/gaugefix.jl | 16 +++++------ test/ctmrg/gradients.jl | 5 ++-- test/ctmrg/gradparts.jl | 5 ++-- test/ctmrg/svd_wrapper.jl | 2 +- test/heisenberg.jl | 3 +- test/pwave.jl | 5 ++-- 12 files changed, 89 insertions(+), 49 deletions(-) diff --git a/examples/boundary_mps.jl b/examples/boundary_mps.jl index abd52e17..558b0de2 100644 --- a/examples/boundary_mps.jl +++ b/examples/boundary_mps.jl @@ -33,7 +33,9 @@ N = abs(prod(expectation_value(mps, T))) # This can be compared to the result obtained using the CTMRG algorithm ctm = leading_boundary( - peps, CTMRG(; verbosity=1, fixedspace=true), CTMRGEnv(peps; Venv=ComplexSpace(20)) + peps, + CTMRG(; verbosity=1, projector_alg=ProjectorAlg(; fixedspace=true)), + CTMRGEnv(peps; Venv=ComplexSpace(20)), ) N´ = abs(norm(peps, ctm)) @@ -56,7 +58,9 @@ mps2, envs2, ϵ = leading_boundary(mps2, T2, VUMPS()) N2 = abs(prod(expectation_value(mps2, T2))) ctm2 = leading_boundary( - peps2, CTMRG(; verbosity=1, fixedspace=true), CTMRGEnv(peps2; Venv=ComplexSpace(20)) + peps2, + CTMRG(; verbosity=1, projector_alg=ProjectorAlg(; fixedspace=true)), + CTMRGEnv(peps2; Venv=ComplexSpace(20)), ) N2´ = abs(norm(peps2, ctm2)) diff --git a/examples/heisenberg.jl b/examples/heisenberg.jl index 0cfd8c06..65cf65e5 100644 --- a/examples/heisenberg.jl +++ b/examples/heisenberg.jl @@ -11,9 +11,10 @@ H = square_lattice_heisenberg(; Jx=-1, Jy=1, Jz=-1) # Parameters χbond = 2 χenv = 20 -ctmalg = CTMRG(; trscheme=truncdim(χenv), tol=1e-10, miniter=4, maxiter=100, verbosity=1) -alg = PEPSOptimize(; - boundary_alg=ctmalg, +projector_alg = ProjectorAlg(; trscheme=truncdim(χenv)) +ctm_alg = CTMRG(; tol=1e-10, miniter=4, maxiter=100, verbosity=1, projector_alg) +opt_alg = PEPSOptimize(; + boundary_alg=ctm_alg, optimizer=LBFGS(4; maxiter=100, gradtol=1e-4, verbosity=2), gradient_alg=GMRES(; tol=1e-6, maxiter=100), reuse_env=true, @@ -27,6 +28,6 @@ alg = PEPSOptimize(; # E/N = −0.6694421, which is a QMC estimate from https://arxiv.org/abs/1101.3281. # Of course there is a noticable bias for small χbond and χenv. ψ₀ = InfinitePEPS(2, χbond) -env₀ = leading_boundary(CTMRGEnv(ψ₀; Venv=ℂ^χenv), ψ₀, ctmalg) -result = fixedpoint(ψ₀, H, alg, env₀) +env₀ = leading_boundary(CTMRGEnv(ψ₀; Venv=ℂ^χenv), ψ₀, ctm_alg) +result = fixedpoint(ψ₀, H, opt_alg, env₀) @show result.E diff --git a/src/PEPSKit.jl b/src/PEPSKit.jl index 1ee02e1f..b10f10c8 100644 --- a/src/PEPSKit.jl +++ b/src/PEPSKit.jl @@ -59,7 +59,7 @@ module Defaults end export FullSVD, IterSVD, OldSVD -export CTMRG, CTMRGEnv +export ProjectorAlg, CTMRG, CTMRGEnv export NLocalOperator, AnisotropicNNOperator, OnSite, NearestNeighbor export expectation_value, costfun export leading_boundary diff --git a/src/algorithms/ctmrg.jl b/src/algorithms/ctmrg.jl index 5b4d65dc..46ed8186 100644 --- a/src/algorithms/ctmrg.jl +++ b/src/algorithms/ctmrg.jl @@ -1,25 +1,46 @@ +# TODO: add option for different projector styles (half-infinite, full-infinite, etc.) +""" + struct ProjectorAlg{S}(; svd_alg = TensorKit.SVD(), trscheme = TensorKit.notrunc(), + fixedspace = false, verbosity = 0) + +Algorithm struct collecting all projector related parameters. The truncation scheme has to be +a `TensorKit.TruncationScheme`, and some SVD algorithms might have further restrictions on what +kind of truncation scheme can be used. If `fixedspace` is true, the truncation scheme is set to +`truncspace(V)` where `V` is the environment bond space, adjusted to the corresponding +environment direction/unit cell entry. +""" +@kwdef struct ProjectorAlg{S} + svd_alg::S = TensorKit.SVD() + trscheme::TruncationScheme = TensorKit.notrunc() + fixedspace::Bool = false + verbosity::Int = 0 +end + +function fix_spaces(alg::ProjectorAlg) + return ProjectorAlg(; + svd_alg=alg.svd_alg, trscheme=alg.trscheme, fixedspace=true, verbosity=alg.verbosity + ) +end + # TODO: add abstract Algorithm type? """ - struct CTMRG(; trscheme = TensorKit.notrunc(), tol = Defaults.ctmrg_tol, - maxiter = Defaults.ctmrg_maxiter, miniter = Defaults.ctmrg_miniter, - verbosity = 0, fixedspace = false) + struct CTMRG(; tol = Defaults.ctmrg_tol, maxiter = Defaults.ctmrg_maxiter, + miniter = Defaults.ctmrg_miniter, verbosity = 0, + projector_alg = ProjectorAlg()) Algorithm struct that represents the CTMRG algorithm for contracting infinite PEPS. -The projector bond dimensions are set via `trscheme` which controls the truncation -properties inside of `TensorKit.tsvd`. Each CTMRG run is converged up to `tol` -where the singular value convergence of the corners as well as the norm is checked. -The maximal and minimal number of CTMRG iterations is set with `maxiter` and `miniter`. -Different levels of output information are printed depending on `verbosity` (0, 1 or 2). -Regardless of the truncation scheme, the space can be kept fixed with `fixedspace`. +Each CTMRG run is converged up to `tol` where the singular value convergence of the +corners as well as the norm is checked. The maximal and minimal number of CTMRG iterations +is set with `maxiter` and `miniter`. Different levels of output information are printed +depending on `verbosity` (0, 1 or 2). All projector related properties are set using the +`ProjectorAlg` struct. """ -@kwdef struct CTMRG{S} +@kwdef struct CTMRG tol::Float64 = Defaults.ctmrg_tol maxiter::Int = Defaults.ctmrg_maxiter miniter::Int = Defaults.ctmrg_miniter verbosity::Int = 0 - svdalg::S = TensorKit.SVD() - trscheme::TruncationScheme = TensorKit.notrunc() - fixedspace::Bool = false + projector_alg::ProjectorAlg = ProjectorAlg() end """ @@ -89,7 +110,11 @@ function MPSKit.leading_boundary(envinit, state, alg::CTMRG) # Do one final iteration that does not change the spaces alg_fixed = CTMRG(; - alg.trscheme, alg.tol, alg.maxiter, alg.miniter, alg.verbosity, fixedspace=true + tol=alg.tol, + maxiter=alg.maxiter, + miniter=alg.miniter, + verbosity=alg.verbosity, + projector_alg=fix_spaces(alg.projector_alg), ) env′, = ctmrg_iter(state, env, alg_fixed) envfix = gauge_fix(env, env′) @@ -285,7 +310,7 @@ function ctmrg_iter(state, env::CTMRGEnv{C,T}, alg::CTMRG) where {C,T} ϵ = 0.0 for _ in 1:4 - env, _, _, ϵ₀ = left_move(state, env, alg) + env, _, _, ϵ₀ = left_move(state, env, alg.projector_alg) state = rotate_north(state, EAST) env = rotate_north(env, EAST) ϵ = max(ϵ, ϵ₀) @@ -300,7 +325,7 @@ end Grow, project and renormalize the environment `env` in west direction. Return the updated environment as well as the projectors and truncation error. """ -function left_move(state, env::CTMRGEnv{C,T}, alg::CTMRG) where {C,T} +function left_move(state, env::CTMRGEnv{C,T}, alg::ProjectorAlg) where {C,T} corners::typeof(env.corners) = copy(env.corners) edges::typeof(env.edges) = copy(env.edges) ϵ = 0.0 @@ -336,7 +361,7 @@ function left_move(state, env::CTMRGEnv{C,T}, alg::CTMRG) where {C,T} alg.trscheme end @tensor QQ[-1 -2 -3; -4 -5 -6] := Q_sw[-1 -2 -3; 1 2 3] * Q_nw[1 2 3; -4 -5 -6] - U, S, V, ϵ_local = PEPSKit.tsvd(QQ, alg.svdalg; trunc=trscheme) + U, S, V, ϵ_local = PEPSKit.tsvd(QQ, alg.svd_alg; trunc=trscheme) ϵ = max(ϵ, ϵ_local / norm(S)) # TODO: check if we can just normalize enlarged corners s.t. trunc behaves a bit better diff --git a/src/utility/svd.jl b/src/utility/svd.jl index 1dae6534..23473bef 100644 --- a/src/utility/svd.jl +++ b/src/utility/svd.jl @@ -8,7 +8,14 @@ using TensorKit: TruncationSpace CRCExt = Base.get_extension(KrylovKit, :KrylovKitChainRulesCoreExt) -# SVD wrapper for TensorKit.tsvd that dispatches on the alg type +""" + PEPSKit.tsvd(t::AbstractTensorMap, alg; trunc=notrunc(), p=2) + +Wrapper around `TensorKit.tsvd` which dispatches on the `alg` argument. +This is needed since a custom adjoint for `PEPSKit.tsvd` may be defined, +depending on the algorithm. E.g., for `IterSVD` the adjoint for a truncated +SVD from `KrylovKit.svdsolve` is used. +""" function PEPSKit.tsvd( t::AbstractTensorMap, alg; trunc::TruncationScheme=notrunc(), p::Real=2 ) @@ -115,7 +122,10 @@ function ChainRulesCore.rrule( alg.alg, alg.alg_rrule, ) - copyto!(b, CRCExt.construct∂f_svd(HasReverseMode(), block(t, c), lvecs, rvecs, xs, ys)) + copyto!( + b, + CRCExt.construct∂f_svd(HasReverseMode(), block(t, c), lvecs, rvecs, xs, ys), + ) end return NoTangent(), Δt, NoTangent() end diff --git a/test/boundarymps/vumps.jl b/test/boundarymps/vumps.jl index 260f40b3..f5d10216 100644 --- a/test/boundarymps/vumps.jl +++ b/test/boundarymps/vumps.jl @@ -17,7 +17,9 @@ Random.seed!(29384293742893) N = abs(sum(expectation_value(mps, T))) ctm = leading_boundary( - CTMRGEnv(psi; Venv=ComplexSpace(20)), psi, CTMRG(; verbosity=1, fixedspace=true) + CTMRGEnv(psi; Venv=ComplexSpace(20)), + psi, + CTMRG(; verbosity=1, projector_alg=ProjectorAlg(; fixedspace=true)), ) N´ = abs(norm(psi, ctm)) @@ -33,7 +35,9 @@ end N = abs(prod(expectation_value(mps, T))) ctm = leading_boundary( - CTMRGEnv(psi; Venv=ComplexSpace(20)), psi, CTMRG(; verbosity=1, fixedspace=true) + CTMRGEnv(psi; Venv=ComplexSpace(20)), + psi, + CTMRG(; verbosity=1, projector_alg=ProjectorAlg(; fixedspace=true)), ) N´ = abs(norm(psi, ctm)) diff --git a/test/ctmrg/gaugefix.jl b/test/ctmrg/gaugefix.jl index 45bee5d5..b5a7d8e6 100644 --- a/test/ctmrg/gaugefix.jl +++ b/test/ctmrg/gaugefix.jl @@ -3,7 +3,7 @@ using Random using PEPSKit using TensorKit -using PEPSKit: ctmrg_iter, gauge_fix, check_elementwise_convergence +using PEPSKit: ctmrg_iter, gauge_fix, check_elementwise_convergence, fix_space scalartypes = [Float64, ComplexF64] unitcells = [(1, 1), (2, 2), (3, 2)] @@ -45,10 +45,9 @@ end ctm = CTMRGEnv(psi; Venv=ctm_space) verbosity = 1 - alg = CTMRG(; - trscheme=truncdim(dim(ctm_space)), tol=1e-10, miniter=4, maxiter=400, verbosity - ) - alg_fixed = CTMRG(; trscheme=truncdim(dim(ctm_space)), verbosity, fixedspace=true) + projector_alg = ProjectorAlg(; trscheme=truncdim(dim(ctm_space))) + alg = CTMRG(; tol=1e-10, miniter=4, maxiter=400, verbosity, projector_alg) + alg_fixed = CTMRG(; verbosity, projector_alg=fix_spaces(projector_alg)) ctm = leading_boundary(ctm, psi, alg) ctm2, = ctmrg_iter(psi, ctm, alg_fixed) @@ -70,10 +69,9 @@ end ctm = CTMRGEnv(psi; Venv=ctm_space) verbosity = 1 - alg = CTMRG(; - trscheme=truncspace(ctm_space), tol=1e-10, miniter=4, maxiter=400, verbosity - ) - alg_fixed = CTMRG(; trscheme=truncspace(ctm_space), verbosity, fixedspace=true) + projector_alg = ProjectorAlg(; trscheme=truncdim(dim(ctm_space))) + alg = CTMRG(; tol=1e-10, miniter=4, maxiter=400, verbosity, projector_alg) + alg_fixed = CTMRG(; verbosity, projector_alg=fix_spaces(projector_alg)) ctm = leading_boundary(ctm, psi, alg) ctm2, = ctmrg_iter(psi, ctm, alg_fixed) diff --git a/test/ctmrg/gradients.jl b/test/ctmrg/gradients.jl index 2c9b86f9..7ae40c3e 100644 --- a/test/ctmrg/gradients.jl +++ b/test/ctmrg/gradients.jl @@ -17,9 +17,8 @@ models = [square_lattice_heisenberg(), square_lattice_pwave()] names = ["Heisenberg", "p-wave superconductor"] Random.seed!(42039482030) tol = 1e-8 -boundary_alg = CTMRG(; - trscheme=truncdim(χenv), tol=tol, miniter=4, maxiter=100, fixedspace=true, verbosity=0 -) +projector_alg = ProjectorAlg(; trscheme=truncdim(χenv), fixedspace=true) +boundary_alg = CTMRG(; tol=tol, miniter=4, maxiter=100, verbosity=0, projector_alg) gradmodes = [ nothing, GeomSum(; tol), ManualIter(; tol), KrylovKit.GMRES(; tol=tol, maxiter=10) ] diff --git a/test/ctmrg/gradparts.jl b/test/ctmrg/gradparts.jl index c21a2420..380bfb9e 100644 --- a/test/ctmrg/gradparts.jl +++ b/test/ctmrg/gradparts.jl @@ -30,9 +30,8 @@ Vspaces = [ComplexSpace(χbond), Vect[FermionParity](0 => χbond / 2, 1 => χbon Espaces = [ComplexSpace(χenv), Vect[FermionParity](0 => χenv / 2, 1 => χenv / 2)] functions = [left_move, ctmrg_iter, leading_boundary] tol = 1e-8 -boundary_alg = CTMRG(; - trscheme=truncdim(χenv), tol=tol, miniter=4, maxiter=100, fixedspace=true, verbosity=0 -) +projector_alg = ProjectorAlg(; trscheme=truncdim(χenv), fixedspace=true) +boundary_alg = CTMRG(; tol=tol, miniter=4, maxiter=100, verbosity=0, projector_alg) ## Gauge invariant function of the environment # -------------------------------------------- diff --git a/test/ctmrg/svd_wrapper.jl b/test/ctmrg/svd_wrapper.jl index c725a6b1..bae40eb8 100644 --- a/test/ctmrg/svd_wrapper.jl +++ b/test/ctmrg/svd_wrapper.jl @@ -26,7 +26,7 @@ R = TensorMap(randn, space(r)) l_fullsvd, g_fullsvd = withgradient(A -> lossfun(A, TensorKit.SVD(), R), r) l_oldsvd, g_oldsvd = withgradient(A -> lossfun(A, OldSVD(), R), r) l_itersvd, g_itersvd = withgradient( - A -> lossfun(A, IterSVD(; alg_rrule=GMRES(; tol=adjoint_tol)), R, truncspace(ℂ^min(m, n))), r + A -> lossfun(A, IterSVD(; alg_rrule=GMRES(; tol=adjoint_tol)), R), r ) @test l_oldsvd ≈ l_itersvd ≈ l_fullsvd diff --git a/test/heisenberg.jl b/test/heisenberg.jl index 35e12ef7..2f569b9d 100644 --- a/test/heisenberg.jl +++ b/test/heisenberg.jl @@ -9,7 +9,8 @@ using OptimKit H = square_lattice_heisenberg() χbond = 2 χenv = 16 -ctm_alg = CTMRG(; trscheme=truncdim(χenv), tol=1e-10, miniter=4, maxiter=100, verbosity=1) +projector_alg = ProjectorAlg(; trscheme=truncdim(χenv)) +ctm_alg = CTMRG(; tol=1e-10, miniter=4, maxiter=100, verbosity=1, projector_alg) opt_alg = PEPSOptimize(; boundary_alg=ctm_alg, optimizer=LBFGS(4; maxiter=100, gradtol=1e-3, verbosity=2), diff --git a/test/pwave.jl b/test/pwave.jl index 4ebb9c85..afde9514 100644 --- a/test/pwave.jl +++ b/test/pwave.jl @@ -9,9 +9,8 @@ using OptimKit H = square_lattice_pwave() χbond = 2 χenv = 16 -ctm_alg = CTMRG(; - trscheme=truncdim(χenv), tol=1e-10, miniter=4, maxiter=50, fixedspace=true, verbosity=1 -) +projector_alg = ProjectorAlg(; trscheme=truncdim(χenv), fixedspace=true) +ctm_alg = CTMRG(; tol=1e-10, miniter=4, maxiter=50, verbosity=1, projector_alg) opt_alg = PEPSOptimize(; boundary_alg=ctm_alg, optimizer=LBFGS(4; maxiter=10, gradtol=1e-3, verbosity=2), From bbd132d79f44a692af719d1bf649c2dadc49df1f Mon Sep 17 00:00:00 2001 From: Lukas <37111893+lkdvos@users.noreply.github.com> Date: Fri, 5 Jul 2024 18:04:08 +0200 Subject: [PATCH 013/213] Update MPSKit compat --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 95074716..e913be9f 100644 --- a/Project.toml +++ b/Project.toml @@ -24,7 +24,7 @@ ChainRulesCore = "1.0" Compat = "3.46, 4.2" KrylovKit = "0.8" LinearAlgebra = "1" -MPSKit = "0.10" +MPSKit = "0.11" OptimKit = "0.3" Printf = "1" Statistics = "1" From 0c03cd43ad924468cac7024159b0452fb68a2e59 Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Mon, 8 Jul 2024 12:12:08 +0200 Subject: [PATCH 014/213] Replace tsvd with tsvd!, add views to IterSVD adjoint --- Project.toml | 2 +- src/algorithms/ctmrg.jl | 11 ++++++++++- src/utility/svd.jl | 13 +++++++------ 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/Project.toml b/Project.toml index e913be9f..ef730624 100644 --- a/Project.toml +++ b/Project.toml @@ -31,7 +31,7 @@ Statistics = "1" TensorKit = "0.12.5" VectorInterface = "0.4" Zygote = "0.6" -julia = "1.6" +julia = "1.8" [extras] SafeTestsets = "1bc83da4-3b8d-516f-aca4-4fe02f6d838f" diff --git a/src/algorithms/ctmrg.jl b/src/algorithms/ctmrg.jl index 46ed8186..97b8bd26 100644 --- a/src/algorithms/ctmrg.jl +++ b/src/algorithms/ctmrg.jl @@ -1,3 +1,12 @@ +""" + FixedSpaceTruncation <: TensorKit.TruncationScheme + +CTMRG specific truncation scheme for `tsvd` which keeps the bond space on which the SVD +is performed fixed. Since different environment directions and unit cell entries might +have different spaces, this truncation style is different from `TruncationSpace`. +""" +struct FixedSpaceTruncation <: TensorKit.TruncationScheme end + # TODO: add option for different projector styles (half-infinite, full-infinite, etc.) """ struct ProjectorAlg{S}(; svd_alg = TensorKit.SVD(), trscheme = TensorKit.notrunc(), @@ -361,7 +370,7 @@ function left_move(state, env::CTMRGEnv{C,T}, alg::ProjectorAlg) where {C,T} alg.trscheme end @tensor QQ[-1 -2 -3; -4 -5 -6] := Q_sw[-1 -2 -3; 1 2 3] * Q_nw[1 2 3; -4 -5 -6] - U, S, V, ϵ_local = PEPSKit.tsvd(QQ, alg.svd_alg; trunc=trscheme) + U, S, V, ϵ_local = PEPSKit.tsvd!(QQ, alg.svd_alg; trunc=trscheme) ϵ = max(ϵ, ϵ_local / norm(S)) # TODO: check if we can just normalize enlarged corners s.t. trunc behaves a bit better diff --git a/src/utility/svd.jl b/src/utility/svd.jl index 23473bef..6055cde4 100644 --- a/src/utility/svd.jl +++ b/src/utility/svd.jl @@ -16,10 +16,11 @@ This is needed since a custom adjoint for `PEPSKit.tsvd` may be defined, depending on the algorithm. E.g., for `IterSVD` the adjoint for a truncated SVD from `KrylovKit.svdsolve` is used. """ -function PEPSKit.tsvd( +PEPSKit.tsvd(t::AbstractTensorMap, alg; kwargs...) = PEPSKit.tsvd!(copy(t), alg; kwargs...) +function PEPSKit.tsvd!( t::AbstractTensorMap, alg; trunc::TruncationScheme=notrunc(), p::Real=2 ) - return TensorKit.tsvd(t; alg, trunc, p) + return TensorKit.tsvd!(t; alg, trunc, p) end # Wrapper around Krylov Kit's GKL iterative SVD solver @@ -64,9 +65,9 @@ function TensorKit._compute_svddata!( Udata[c] = U Vdata[c] = V else # Slice in case more values were converged than requested - Udata[c] = stack(lvecs[1:howmany]) - Vdata[c] = stack(rvecs[1:howmany])' - S = S[1:howmany] + Udata[c] = stack(view(lvecs, 1:howmany)) + Vdata[c] = stack(view(rvecs, 1:howmany))' + S = @view S[1:howmany] end if @isdefined Sdata # cannot easily infer the type of Σ, so use this construction Sdata[c] = S @@ -80,7 +81,7 @@ end # IterSVD adjoint for tsvd! using KrylovKit.svdsolve adjoint machinery for each block function ChainRulesCore.rrule( - ::typeof(PEPSKit.tsvd), + ::typeof(PEPSKit.tsvd!), t::AbstractTensorMap, alg::IterSVD; trunc::TruncationScheme=notrunc(), From 14f0065b4f61ca77a4c21d0325ef0e005f5193c2 Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Mon, 8 Jul 2024 15:12:07 +0200 Subject: [PATCH 015/213] Improve IterSVD allocation, implement CTMRG convenience constructor, update tests, implement FixedSpaceTruncation --- examples/boundary_mps.jl | 12 ++------- examples/heisenberg.jl | 3 +-- src/PEPSKit.jl | 2 +- src/algorithms/ctmrg.jl | 57 +++++++++++++++++++-------------------- src/utility/svd.jl | 2 +- test/boundarymps/vumps.jl | 12 ++------- test/ctmrg/gaugefix.jl | 12 ++++----- test/ctmrg/gradients.jl | 3 +-- test/ctmrg/gradparts.jl | 3 +-- test/heisenberg.jl | 3 +-- test/pwave.jl | 3 +-- 11 files changed, 44 insertions(+), 68 deletions(-) diff --git a/examples/boundary_mps.jl b/examples/boundary_mps.jl index 558b0de2..8893f63e 100644 --- a/examples/boundary_mps.jl +++ b/examples/boundary_mps.jl @@ -32,11 +32,7 @@ mps, envs, ϵ = leading_boundary(mps, T, VUMPS()) N = abs(prod(expectation_value(mps, T))) # This can be compared to the result obtained using the CTMRG algorithm -ctm = leading_boundary( - peps, - CTMRG(; verbosity=1, projector_alg=ProjectorAlg(; fixedspace=true)), - CTMRGEnv(peps; Venv=ComplexSpace(20)), -) +ctm = leading_boundary(peps, CTMRG(; verbosity=1), CTMRGEnv(peps; Venv=ComplexSpace(20))) N´ = abs(norm(peps, ctm)) @show abs(N - N´) / N @@ -57,11 +53,7 @@ mps2 = PEPSKit.initializeMPS(T2, fill(ComplexSpace(20), 2, 2)) mps2, envs2, ϵ = leading_boundary(mps2, T2, VUMPS()) N2 = abs(prod(expectation_value(mps2, T2))) -ctm2 = leading_boundary( - peps2, - CTMRG(; verbosity=1, projector_alg=ProjectorAlg(; fixedspace=true)), - CTMRGEnv(peps2; Venv=ComplexSpace(20)), -) +ctm2 = leading_boundary(peps2, CTMRG(; verbosity=1), CTMRGEnv(peps2; Venv=ComplexSpace(20))) N2´ = abs(norm(peps2, ctm2)) @show abs(N2 - N2´) / N2 diff --git a/examples/heisenberg.jl b/examples/heisenberg.jl index 65cf65e5..a43f313c 100644 --- a/examples/heisenberg.jl +++ b/examples/heisenberg.jl @@ -11,8 +11,7 @@ H = square_lattice_heisenberg(; Jx=-1, Jy=1, Jz=-1) # Parameters χbond = 2 χenv = 20 -projector_alg = ProjectorAlg(; trscheme=truncdim(χenv)) -ctm_alg = CTMRG(; tol=1e-10, miniter=4, maxiter=100, verbosity=1, projector_alg) +ctm_alg = CTMRG(; tol=1e-10, miniter=4, maxiter=100, verbosity=1, trscheme=truncdim(χenv)) opt_alg = PEPSOptimize(; boundary_alg=ctm_alg, optimizer=LBFGS(4; maxiter=100, gradtol=1e-4, verbosity=2), diff --git a/src/PEPSKit.jl b/src/PEPSKit.jl index b10f10c8..5d341885 100644 --- a/src/PEPSKit.jl +++ b/src/PEPSKit.jl @@ -59,7 +59,7 @@ module Defaults end export FullSVD, IterSVD, OldSVD -export ProjectorAlg, CTMRG, CTMRGEnv +export FixedSpaceTruncation, ProjectorAlg, CTMRG, CTMRGEnv export NLocalOperator, AnisotropicNNOperator, OnSite, NearestNeighbor export expectation_value, costfun export leading_boundary diff --git a/src/algorithms/ctmrg.jl b/src/algorithms/ctmrg.jl index 97b8bd26..1b424790 100644 --- a/src/algorithms/ctmrg.jl +++ b/src/algorithms/ctmrg.jl @@ -5,7 +5,7 @@ CTMRG specific truncation scheme for `tsvd` which keeps the bond space on which is performed fixed. Since different environment directions and unit cell entries might have different spaces, this truncation style is different from `TruncationSpace`. """ -struct FixedSpaceTruncation <: TensorKit.TruncationScheme end +struct FixedSpaceTruncation <: TensorKit.TruncationScheme end # TODO: add option for different projector styles (half-infinite, full-infinite, etc.) """ @@ -18,38 +18,43 @@ kind of truncation scheme can be used. If `fixedspace` is true, the truncation s `truncspace(V)` where `V` is the environment bond space, adjusted to the corresponding environment direction/unit cell entry. """ -@kwdef struct ProjectorAlg{S} +@kwdef struct ProjectorAlg{S,T} svd_alg::S = TensorKit.SVD() - trscheme::TruncationScheme = TensorKit.notrunc() - fixedspace::Bool = false + trscheme::T = FixedSpaceTruncation() verbosity::Int = 0 end -function fix_spaces(alg::ProjectorAlg) - return ProjectorAlg(; - svd_alg=alg.svd_alg, trscheme=alg.trscheme, fixedspace=true, verbosity=alg.verbosity - ) -end - # TODO: add abstract Algorithm type? """ - struct CTMRG(; tol = Defaults.ctmrg_tol, maxiter = Defaults.ctmrg_maxiter, - miniter = Defaults.ctmrg_miniter, verbosity = 0, - projector_alg = ProjectorAlg()) + CTMRG(; tol=Defaults.ctmrg_tol, maxiter=Defaults.ctmrg_maxiter, + miniter=Defaults.ctmrg_miniter, verbosity=0, + svd_alg=TensorKit.SVD(), trscheme=FixedSpaceTruncation()) Algorithm struct that represents the CTMRG algorithm for contracting infinite PEPS. Each CTMRG run is converged up to `tol` where the singular value convergence of the corners as well as the norm is checked. The maximal and minimal number of CTMRG iterations is set with `maxiter` and `miniter`. Different levels of output information are printed -depending on `verbosity` (0, 1 or 2). All projector related properties are set using the -`ProjectorAlg` struct. +depending on `verbosity` (0, 1 or 2). The projectors are computed from `svd_alg` SVDs +where the truncation scheme is set via `trscheme`. """ -@kwdef struct CTMRG - tol::Float64 = Defaults.ctmrg_tol - maxiter::Int = Defaults.ctmrg_maxiter - miniter::Int = Defaults.ctmrg_miniter - verbosity::Int = 0 - projector_alg::ProjectorAlg = ProjectorAlg() +struct CTMRG + tol::Float64 + maxiter::Int + miniter::Int + verbosity::Int + projector_alg::ProjectorAlg +end +function CTMRG(; + tol=Defaults.ctmrg_tol, + maxiter=Defaults.ctmrg_maxiter, + miniter=Defaults.ctmrg_miniter, + verbosity=0, + svd_alg=TensorKit.SVD(), + trscheme=FixedSpaceTruncation(), +) + return CTMRG( + tol, maxiter, miniter, verbosity, ProjectorAlg(; svd_alg, trscheme, verbosity) + ) end """ @@ -118,13 +123,7 @@ function MPSKit.leading_boundary(envinit, state, alg::CTMRG) end # Do one final iteration that does not change the spaces - alg_fixed = CTMRG(; - tol=alg.tol, - maxiter=alg.maxiter, - miniter=alg.miniter, - verbosity=alg.verbosity, - projector_alg=fix_spaces(alg.projector_alg), - ) + alg_fixed = @set alg.projector_alg.trscheme = FixedSpaceTruncation() env′, = ctmrg_iter(state, env, alg_fixed) envfix = gauge_fix(env, env′) check_elementwise_convergence(env, envfix; atol=alg.tol^(1 / 2)) || @@ -364,7 +363,7 @@ function left_move(state, env::CTMRGEnv{C,T}, alg::ProjectorAlg) where {C,T} ) # SVD half-infinite environment - trscheme = if alg.fixedspace == true + trscheme = if alg.trscheme isa FixedSpaceTruncation truncspace(space(env.edges[WEST, row, cnext], 1)) else alg.trscheme diff --git a/src/utility/svd.jl b/src/utility/svd.jl index 6055cde4..1c24b6f5 100644 --- a/src/utility/svd.jl +++ b/src/utility/svd.jl @@ -66,7 +66,7 @@ function TensorKit._compute_svddata!( Vdata[c] = V else # Slice in case more values were converged than requested Udata[c] = stack(view(lvecs, 1:howmany)) - Vdata[c] = stack(view(rvecs, 1:howmany))' + Vdata[c] = stack(conj, view(rvecs, 1:howmany); dims=1) S = @view S[1:howmany] end if @isdefined Sdata # cannot easily infer the type of Σ, so use this construction diff --git a/test/boundarymps/vumps.jl b/test/boundarymps/vumps.jl index f5d10216..f8709028 100644 --- a/test/boundarymps/vumps.jl +++ b/test/boundarymps/vumps.jl @@ -16,11 +16,7 @@ Random.seed!(29384293742893) mps, envs, ϵ = leading_boundary(mps, T, VUMPS()) N = abs(sum(expectation_value(mps, T))) - ctm = leading_boundary( - CTMRGEnv(psi; Venv=ComplexSpace(20)), - psi, - CTMRG(; verbosity=1, projector_alg=ProjectorAlg(; fixedspace=true)), - ) + ctm = leading_boundary(CTMRGEnv(psi; Venv=ComplexSpace(20)), psi, CTMRG(; verbosity=1)) N´ = abs(norm(psi, ctm)) @test N ≈ N´ atol = 1e-3 @@ -34,11 +30,7 @@ end mps, envs, ϵ = leading_boundary(mps, T, VUMPS()) N = abs(prod(expectation_value(mps, T))) - ctm = leading_boundary( - CTMRGEnv(psi; Venv=ComplexSpace(20)), - psi, - CTMRG(; verbosity=1, projector_alg=ProjectorAlg(; fixedspace=true)), - ) + ctm = leading_boundary(CTMRGEnv(psi; Venv=ComplexSpace(20)), psi, CTMRG(; verbosity=1)) N´ = abs(norm(psi, ctm)) @test N ≈ N´ rtol = 1e-3 diff --git a/test/ctmrg/gaugefix.jl b/test/ctmrg/gaugefix.jl index b5a7d8e6..268e579e 100644 --- a/test/ctmrg/gaugefix.jl +++ b/test/ctmrg/gaugefix.jl @@ -3,7 +3,7 @@ using Random using PEPSKit using TensorKit -using PEPSKit: ctmrg_iter, gauge_fix, check_elementwise_convergence, fix_space +using PEPSKit: ctmrg_iter, gauge_fix, check_elementwise_convergence scalartypes = [Float64, ComplexF64] unitcells = [(1, 1), (2, 2), (3, 2)] @@ -45,9 +45,8 @@ end ctm = CTMRGEnv(psi; Venv=ctm_space) verbosity = 1 - projector_alg = ProjectorAlg(; trscheme=truncdim(dim(ctm_space))) - alg = CTMRG(; tol=1e-10, miniter=4, maxiter=400, verbosity, projector_alg) - alg_fixed = CTMRG(; verbosity, projector_alg=fix_spaces(projector_alg)) + alg = CTMRG(; tol=1e-10, miniter=4, maxiter=400, verbosity, trscheme=truncdim(dim(ctm_space))) + alg_fixed = @set alg.projector_alg.trscheme = FixedSpaceTruncation() ctm = leading_boundary(ctm, psi, alg) ctm2, = ctmrg_iter(psi, ctm, alg_fixed) @@ -69,9 +68,8 @@ end ctm = CTMRGEnv(psi; Venv=ctm_space) verbosity = 1 - projector_alg = ProjectorAlg(; trscheme=truncdim(dim(ctm_space))) - alg = CTMRG(; tol=1e-10, miniter=4, maxiter=400, verbosity, projector_alg) - alg_fixed = CTMRG(; verbosity, projector_alg=fix_spaces(projector_alg)) + alg = CTMRG(; tol=1e-10, miniter=4, maxiter=400, verbosity, trscheme=truncdim(dim(ctm_space))) + alg_fixed = @set alg.projector_alg.trscheme = FixedSpaceTruncation() ctm = leading_boundary(ctm, psi, alg) ctm2, = ctmrg_iter(psi, ctm, alg_fixed) diff --git a/test/ctmrg/gradients.jl b/test/ctmrg/gradients.jl index 7ae40c3e..d7a842d6 100644 --- a/test/ctmrg/gradients.jl +++ b/test/ctmrg/gradients.jl @@ -17,8 +17,7 @@ models = [square_lattice_heisenberg(), square_lattice_pwave()] names = ["Heisenberg", "p-wave superconductor"] Random.seed!(42039482030) tol = 1e-8 -projector_alg = ProjectorAlg(; trscheme=truncdim(χenv), fixedspace=true) -boundary_alg = CTMRG(; tol=tol, miniter=4, maxiter=100, verbosity=0, projector_alg) +boundary_alg = CTMRG(; tol=tol, miniter=4, maxiter=100, verbosity=0) gradmodes = [ nothing, GeomSum(; tol), ManualIter(; tol), KrylovKit.GMRES(; tol=tol, maxiter=10) ] diff --git a/test/ctmrg/gradparts.jl b/test/ctmrg/gradparts.jl index 380bfb9e..e1720db8 100644 --- a/test/ctmrg/gradparts.jl +++ b/test/ctmrg/gradparts.jl @@ -30,8 +30,7 @@ Vspaces = [ComplexSpace(χbond), Vect[FermionParity](0 => χbond / 2, 1 => χbon Espaces = [ComplexSpace(χenv), Vect[FermionParity](0 => χenv / 2, 1 => χenv / 2)] functions = [left_move, ctmrg_iter, leading_boundary] tol = 1e-8 -projector_alg = ProjectorAlg(; trscheme=truncdim(χenv), fixedspace=true) -boundary_alg = CTMRG(; tol=tol, miniter=4, maxiter=100, verbosity=0, projector_alg) +boundary_alg = CTMRG(; tol=tol, miniter=4, maxiter=100, verbosity=0) ## Gauge invariant function of the environment # -------------------------------------------- diff --git a/test/heisenberg.jl b/test/heisenberg.jl index 2f569b9d..0891fea2 100644 --- a/test/heisenberg.jl +++ b/test/heisenberg.jl @@ -9,8 +9,7 @@ using OptimKit H = square_lattice_heisenberg() χbond = 2 χenv = 16 -projector_alg = ProjectorAlg(; trscheme=truncdim(χenv)) -ctm_alg = CTMRG(; tol=1e-10, miniter=4, maxiter=100, verbosity=1, projector_alg) +ctm_alg = CTMRG(; tol=1e-10, miniter=4, maxiter=100, verbosity=1, trscheme=truncdim(χenv)) opt_alg = PEPSOptimize(; boundary_alg=ctm_alg, optimizer=LBFGS(4; maxiter=100, gradtol=1e-3, verbosity=2), diff --git a/test/pwave.jl b/test/pwave.jl index afde9514..a94c1d0d 100644 --- a/test/pwave.jl +++ b/test/pwave.jl @@ -9,8 +9,7 @@ using OptimKit H = square_lattice_pwave() χbond = 2 χenv = 16 -projector_alg = ProjectorAlg(; trscheme=truncdim(χenv), fixedspace=true) -ctm_alg = CTMRG(; tol=1e-10, miniter=4, maxiter=50, verbosity=1, projector_alg) +ctm_alg = CTMRG(; tol=1e-10, miniter=4, maxiter=50, verbosity=1) opt_alg = PEPSOptimize(; boundary_alg=ctm_alg, optimizer=LBFGS(4; maxiter=10, gradtol=1e-3, verbosity=2), From 9b2c4b7531181fbe980ad22fa47842a44f178525 Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Mon, 8 Jul 2024 16:20:16 +0200 Subject: [PATCH 016/213] Fix tests --- test/boundarymps/vumps.jl | 2 +- test/ctmrg/gaugefix.jl | 1 + test/ctmrg/gradparts.jl | 6 ++---- test/pwave.jl | 4 ++-- test/runtests.jl | 2 +- 5 files changed, 7 insertions(+), 8 deletions(-) diff --git a/test/boundarymps/vumps.jl b/test/boundarymps/vumps.jl index f8709028..fa4b434d 100644 --- a/test/boundarymps/vumps.jl +++ b/test/boundarymps/vumps.jl @@ -33,7 +33,7 @@ end ctm = leading_boundary(CTMRGEnv(psi; Venv=ComplexSpace(20)), psi, CTMRG(; verbosity=1)) N´ = abs(norm(psi, ctm)) - @test N ≈ N´ rtol = 1e-3 + @test N ≈ N´ rtol = 1e-2 end @testset "PEPO runthrough" begin diff --git a/test/ctmrg/gaugefix.jl b/test/ctmrg/gaugefix.jl index 268e579e..195df266 100644 --- a/test/ctmrg/gaugefix.jl +++ b/test/ctmrg/gaugefix.jl @@ -2,6 +2,7 @@ using Test using Random using PEPSKit using TensorKit +using Accessors using PEPSKit: ctmrg_iter, gauge_fix, check_elementwise_convergence diff --git a/test/ctmrg/gradparts.jl b/test/ctmrg/gradparts.jl index e1720db8..9c2e3442 100644 --- a/test/ctmrg/gradparts.jl +++ b/test/ctmrg/gradparts.jl @@ -49,10 +49,8 @@ end ## Tests # ------ -@testset "Reverse rules for composite parts of the CTMRG fixed point with spacetype $(Vspaces[i])" for i in - eachindex( - Pspaces -) +title = "Reverse rules for composite parts of the CTMRG fixed point with spacetype" +@testset title * "$(Vspaces[i])" for i in eachindex(Pspaces) psi = InfinitePEPS(Pspaces[i], Vspaces[i], Vspaces[i]) env = CTMRGEnv(psi; Venv=Espaces[i]) diff --git a/test/pwave.jl b/test/pwave.jl index a94c1d0d..e9acf179 100644 --- a/test/pwave.jl +++ b/test/pwave.jl @@ -9,11 +9,11 @@ using OptimKit H = square_lattice_pwave() χbond = 2 χenv = 16 -ctm_alg = CTMRG(; tol=1e-10, miniter=4, maxiter=50, verbosity=1) +ctm_alg = CTMRG(; tol=1e-10, miniter=4, maxiter=100, verbosity=1) opt_alg = PEPSOptimize(; boundary_alg=ctm_alg, optimizer=LBFGS(4; maxiter=10, gradtol=1e-3, verbosity=2), - gradient_alg=GMRES(; tol=1e-3, maxiter=2, krylovdim=50, verbosity=2), + gradient_alg=GMRES(; tol=1e-3, maxiter=2, krylovdim=50), reuse_env=true, verbosity=2, ) diff --git a/test/runtests.jl b/test/runtests.jl index 33e2fc42..cd7d81d6 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -10,7 +10,7 @@ const GROUP = get(ENV, "GROUP", "All") @time @safetestset "Gradients" begin include("ctmrg/gradients.jl") end - @time @safetestset "Gradients" begin + @time @safetestset "SVD wrapper" begin include("ctmrg/svd_wrapper.jl") end end From 0c13d47a1c357ed6aae81385f686de4751044667 Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Mon, 8 Jul 2024 17:03:04 +0200 Subject: [PATCH 017/213] Add block-wise dense fallback option --- src/utility/svd.jl | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/src/utility/svd.jl b/src/utility/svd.jl index 1c24b6f5..ded85014 100644 --- a/src/utility/svd.jl +++ b/src/utility/svd.jl @@ -26,13 +26,14 @@ end # Wrapper around Krylov Kit's GKL iterative SVD solver @kwdef struct IterSVD alg::KrylovKit.GKL = KrylovKit.GKL(; tol=1e-14, krylovdim=25) + fallback_threshold::Float64 = Inf lorentz_broad::Float64 = 0.0 alg_rrule::Union{GMRES,BiCGStab,Arnoldi} = GMRES(; tol=1e-14) end # Compute SVD data block-wise using KrylovKit algorithm function TensorKit._tsvd!( - t, alg::IterSVD, trunc::Union{NoTruncation,TruncationSpace}, p::Real=2 + t, alg::Union{IterSVD}, trunc::Union{NoTruncation,TruncationSpace}, p::Real=2 ) # early return if isempty(blocksectors(t)) @@ -59,16 +60,24 @@ function TensorKit._compute_svddata!( for (c, b) in blocks(t) x₀ = randn(eltype(b), size(b, 1)) howmany = trunc isa NoTruncation ? minimum(size(b)) : blockdim(trunc.space, c) - S, lvecs, rvecs, info = KrylovKit.svdsolve(b, x₀, howmany, :LR, alg.alg) - if info.converged < howmany # Fall back to dense SVD if not properly converged + + if howmany / minimum(size(b)) > alg.fallback_threshold # Use dense SVD for small blocks U, S, V = TensorKit.MatrixAlgebra.svd!(b, TensorKit.SVD()) - Udata[c] = U - Vdata[c] = V - else # Slice in case more values were converged than requested - Udata[c] = stack(view(lvecs, 1:howmany)) - Vdata[c] = stack(conj, view(rvecs, 1:howmany); dims=1) - S = @view S[1:howmany] + Udata[c] = @view U[:, 1:howmany] + Vdata[c] = @view V[1:howmany, :] + else + S, lvecs, rvecs, info = KrylovKit.svdsolve(b, x₀, howmany, :LR, alg.alg) + if info.converged < howmany # Fall back to dense SVD if not properly converged + U, S, V = TensorKit.MatrixAlgebra.svd!(b, TensorKit.SVD()) + Udata[c] = @view U[:, 1:howmany] + Vdata[c] = @view V[1:howmany, :] + else # Slice in case more values were converged than requested + Udata[c] = stack(view(lvecs, 1:howmany)) + Vdata[c] = stack(conj, view(rvecs, 1:howmany); dims=1) + end end + + S = @view S[1:howmany] if @isdefined Sdata # cannot easily infer the type of Σ, so use this construction Sdata[c] = S else From 538652d2074255eb8d656904a8f255b80b4a1476 Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Mon, 8 Jul 2024 18:40:38 +0200 Subject: [PATCH 018/213] Add SVDrrule wrapper, add separate adjoint structs and rrules, update SVD test, add docs --- src/PEPSKit.jl | 2 +- src/algorithms/ctmrg.jl | 6 +- src/utility/svd.jl | 119 +++++++++++++++++++++++++++----------- test/ctmrg/svd_wrapper.jl | 36 ++++++------ 4 files changed, 108 insertions(+), 55 deletions(-) diff --git a/src/PEPSKit.jl b/src/PEPSKit.jl index 5d341885..a95366b9 100644 --- a/src/PEPSKit.jl +++ b/src/PEPSKit.jl @@ -58,7 +58,7 @@ module Defaults const fpgrad_tol = 1e-6 end -export FullSVD, IterSVD, OldSVD +export SVDrrule, IterSVD, OldSVD, CompleteSVDAdjoint, SparseSVDAdjoint, NonTruncSVDAdjoint export FixedSpaceTruncation, ProjectorAlg, CTMRG, CTMRGEnv export NLocalOperator, AnisotropicNNOperator, OnSite, NearestNeighbor export expectation_value, costfun diff --git a/src/algorithms/ctmrg.jl b/src/algorithms/ctmrg.jl index 1b424790..3cd745df 100644 --- a/src/algorithms/ctmrg.jl +++ b/src/algorithms/ctmrg.jl @@ -18,8 +18,8 @@ kind of truncation scheme can be used. If `fixedspace` is true, the truncation s `truncspace(V)` where `V` is the environment bond space, adjusted to the corresponding environment direction/unit cell entry. """ -@kwdef struct ProjectorAlg{S,T} - svd_alg::S = TensorKit.SVD() +@kwdef struct ProjectorAlg{S<:SVDrrule,T} + svd_alg::S = SVDrrule() trscheme::T = FixedSpaceTruncation() verbosity::Int = 0 end @@ -49,7 +49,7 @@ function CTMRG(; maxiter=Defaults.ctmrg_maxiter, miniter=Defaults.ctmrg_miniter, verbosity=0, - svd_alg=TensorKit.SVD(), + svd_alg=SVDrrule(), trscheme=FixedSpaceTruncation(), ) return CTMRG( diff --git a/src/utility/svd.jl b/src/utility/svd.jl index ded85014..9a2651ff 100644 --- a/src/utility/svd.jl +++ b/src/utility/svd.jl @@ -8,6 +8,16 @@ using TensorKit: TruncationSpace CRCExt = Base.get_extension(KrylovKit, :KrylovKitChainRulesCoreExt) +""" + struct SVDrrule(; svd_alg = TensorKit.SVD(), rrule_alg = CompleteSVDAdjoint()) + +Wrapper for a SVD algorithm `svd_alg` with a defined reverse rule `rrule_alg`. +""" +@kwdef struct SVDrrule{S,R} + svd_alg::S = TensorKit.SVD() + rrule_alg::R = CompleteSVDAdjoint() # TODO: should contain Lorentzian broadening eventually +end # Keep truncation algorithm separate to be able to specify CTMRG dependent information + """ PEPSKit.tsvd(t::AbstractTensorMap, alg; trunc=notrunc(), p=2) @@ -18,22 +28,28 @@ SVD from `KrylovKit.svdsolve` is used. """ PEPSKit.tsvd(t::AbstractTensorMap, alg; kwargs...) = PEPSKit.tsvd!(copy(t), alg; kwargs...) function PEPSKit.tsvd!( - t::AbstractTensorMap, alg; trunc::TruncationScheme=notrunc(), p::Real=2 + t::AbstractTensorMap, alg::SVDrrule; trunc::TruncationScheme=notrunc(), p::Real=2 ) - return TensorKit.tsvd!(t; alg, trunc, p) + return TensorKit.tsvd!(t; alg=alg.svd_alg, trunc, p) end -# Wrapper around Krylov Kit's GKL iterative SVD solver +""" + struct IterSVD(; alg = KrylovKit.GKL(), fallback_threshold = Inf) + +Iterative SVD solver based on KrylovKit's GKL algorithm, adapted to (symmmetric) tensors. +The number of targeted singular values is set via the `TruncationSpace` in `ProjectorAlg`. +In particular, this make it possible to specify the targeted singular values block-wise. +In case the symmetry block is too small as compared to the number of singular values, or +the iterative SVD didn't converge, the algorithm falls back to a dense SVD. +""" @kwdef struct IterSVD alg::KrylovKit.GKL = KrylovKit.GKL(; tol=1e-14, krylovdim=25) fallback_threshold::Float64 = Inf - lorentz_broad::Float64 = 0.0 - alg_rrule::Union{GMRES,BiCGStab,Arnoldi} = GMRES(; tol=1e-14) end # Compute SVD data block-wise using KrylovKit algorithm function TensorKit._tsvd!( - t, alg::Union{IterSVD}, trunc::Union{NoTruncation,TruncationSpace}, p::Real=2 + t, alg::IterSVD, trunc::Union{NoTruncation,TruncationSpace}, p::Real=2 ) # early return if isempty(blocksectors(t)) @@ -63,14 +79,14 @@ function TensorKit._compute_svddata!( if howmany / minimum(size(b)) > alg.fallback_threshold # Use dense SVD for small blocks U, S, V = TensorKit.MatrixAlgebra.svd!(b, TensorKit.SVD()) - Udata[c] = @view U[:, 1:howmany] - Vdata[c] = @view V[1:howmany, :] + Udata[c] = U[:, 1:howmany] + Vdata[c] = V[1:howmany, :] else S, lvecs, rvecs, info = KrylovKit.svdsolve(b, x₀, howmany, :LR, alg.alg) if info.converged < howmany # Fall back to dense SVD if not properly converged U, S, V = TensorKit.MatrixAlgebra.svd!(b, TensorKit.SVD()) - Udata[c] = @view U[:, 1:howmany] - Vdata[c] = @view V[1:howmany, :] + Udata[c] = U[:, 1:howmany] + Vdata[c] = V[1:howmany, :] else # Slice in case more values were converged than requested Udata[c] = stack(view(lvecs, 1:howmany)) Vdata[c] = stack(conj, view(rvecs, 1:howmany); dims=1) @@ -88,17 +104,45 @@ function TensorKit._compute_svddata!( return Udata, Sdata, Vdata, dims end -# IterSVD adjoint for tsvd! using KrylovKit.svdsolve adjoint machinery for each block +""" + struct CompleteSVDAdjoint(; lorentz_broadening = 0.0) + +Wrapper around the complete `TensorKit.tsvd!` rrule which requires computing the full SVD. +""" +@kwdef struct CompleteSVDAdjoint + lorentz_broadening::Float64 = 0.0 +end + function ChainRulesCore.rrule( ::typeof(PEPSKit.tsvd!), t::AbstractTensorMap, - alg::IterSVD; + alg::SVDrrule{A,CompleteSVDAdjoint}; trunc::TruncationScheme=notrunc(), p::Real=2, -) +) where {A} + return rrule(TensorKit.tsvd!; trunc, p, alg=alg.svd_alg) +end + +""" + struct SparseSVDAdjoint(; lorentz_broadening = 0.0) + +Wrapper around the `KrylovKit.svdsolve` rrule where only the truncated decomposition is required. +""" +@kwdef struct SparseSVDAdjoint + alg::Union{GMRES,BiCGStab,Arnoldi} = GMRES() + lorentz_broadening::Float64 = 0.0 +end + +function ChainRulesCore.rrule( + ::typeof(PEPSKit.tsvd!), + t::AbstractTensorMap, + alg::SVDrrule{A,SparseSVDAdjoint}; + trunc::TruncationScheme=notrunc(), + p::Real=2, +) where {A} U, S, V, ϵ = PEPSKit.tsvd(t, alg; trunc, p) - function tsvd_itersvd_pullback((ΔU, ΔS, ΔV, Δϵ)) + function tsvd_sparsesvd_pullback((ΔU, ΔS, ΔV, Δϵ)) Δt = similar(t) for (c, b) in blocks(Δt) Uc, Sc, Vc = block(U, c), block(S, c), block(V, c) @@ -129,8 +173,8 @@ function ChainRulesCore.rrule( minimal_info, block(t, c), :LR, - alg.alg, - alg.alg_rrule, + alg.svd_alg.alg, + alg.rrule_alg.alg, ) copyto!( b, @@ -139,50 +183,57 @@ function ChainRulesCore.rrule( end return NoTangent(), Δt, NoTangent() end - function tsvd_itersvd_pullback(::Tuple{ZeroTangent,ZeroTangent,ZeroTangent}) + function tsvd_sparsesvd_pullback(::Tuple{ZeroTangent,ZeroTangent,ZeroTangent}) return NoTangent(), ZeroTangent(), NoTangent() end - return (U, S, V, ϵ), tsvd_itersvd_pullback + return (U, S, V, ϵ), tsvd_sparsesvd_pullback end -# Full SVD with old adjoint that doesn't account for truncation properly -@kwdef struct OldSVD{A<:Union{TensorKit.SDD,TensorKit.SVD,IterSVD}} - alg::A = TensorKit.SVD() - lorentz_broad::Float64 = 0.0 -end +""" + struct NonTruncAdjoint(; lorentz_broadening = 0.0) -# Perform TensorKit.SVD in forward pass -function TensorKit._tsvd!(t, ::OldSVD, trunc::TruncationScheme, p::Real=2) - return _tsvd!(t, TensorKit.SVD(), trunc, p) +Old SVD adjoint that does not account for the truncated part of truncated SVDs. +""" +@kwdef struct NonTruncSVDAdjoint + lorentz_broadening::Float64 = 0.0 end # Use outdated adjoint in reverse pass (not taking truncated part into account for testing purposes) function ChainRulesCore.rrule( ::typeof(PEPSKit.tsvd), t::AbstractTensorMap, - alg::OldSVD; + alg::SVDrrule{A,NonTruncSVDAdjoint}; trunc::TruncationScheme=notrunc(), p::Real=2, -) +) where {A} U, S, V, ϵ = PEPSKit.tsvd(t, alg; trunc, p) - function tsvd_oldsvd_pullback((ΔU, ΔS, ΔV, Δϵ)) + function tsvd_nontruncsvd_pullback((ΔU, ΔS, ΔV, Δϵ)) Δt = similar(t) for (c, b) in blocks(Δt) Uc, Sc, Vc = block(U, c), block(S, c), block(V, c) ΔUc, ΔSc, ΔVc = block(ΔU, c), block(ΔS, c), block(ΔV, c) copyto!( - b, oldsvd_rev(Uc, Sc, Vc, ΔUc, ΔSc, ΔVc; lorentz_broad=alg.lorentz_broad) + b, + oldsvd_rev( + Uc, + Sc, + Vc, + ΔUc, + ΔSc, + ΔVc; + lorentz_broadening=alg.rrule_alg.lorentz_broadening, + ), ) end return NoTangent(), Δt, NoTangent() end - function tsvd_oldsvd_pullback(::Tuple{ZeroTangent,ZeroTangent,ZeroTangent}) + function tsvd_nontruncsvd_pullback(::Tuple{ZeroTangent,ZeroTangent,ZeroTangent}) return NoTangent(), ZeroTangent(), NoTangent() end - return (U, S, V, ϵ), tsvd_oldsvd_pullback + return (U, S, V, ϵ), tsvd_nontruncsvd_pullback end function oldsvd_rev( @@ -192,12 +243,12 @@ function oldsvd_rev( ΔU, ΔS, ΔV; - lorentz_broad=0, + lorentz_broadening=0, atol::Real=0, rtol::Real=atol > 0 ? 0 : eps(scalartype(S))^(3 / 4), ) tol = atol > 0 ? atol : rtol * S[1, 1] - F = _invert_S²(S, tol, lorentz_broad) # Includes Lorentzian broadening + F = _invert_S²(S, tol, lorentz_broadening) # Includes Lorentzian broadening S⁻¹ = pinv(S; atol=tol) # dS contribution diff --git a/test/ctmrg/svd_wrapper.jl b/test/ctmrg/svd_wrapper.jl index bae40eb8..0f3bd1da 100644 --- a/test/ctmrg/svd_wrapper.jl +++ b/test/ctmrg/svd_wrapper.jl @@ -16,18 +16,22 @@ m, n = 20, 30 dtype = ComplexF64 χ = 12 trunc = truncspace(ℂ^χ) -# lorentz_broad = 1e-12 -adjoint_tol = 1e-14 # Don't make this too small, g_itersvd will be weird +# lorentz_broadening = 1e-12 rtol = 1e-9 r = TensorMap(randn, dtype, ℂ^m ← ℂ^n) R = TensorMap(randn, space(r)) +full_alg = SVDrrule(; svd_alg=TensorKit.SVD(), rrule_alg=CompleteSVDAdjoint()) +old_alg = SVDrrule(; svd_alg=TensorKit.SVD(), rrule_alg=NonTruncSVDAdjoint()) +iter_alg = SVDrrule(; # Don't make adjoint tolerance too small, g_itersvd will be weird + svd_alg=IterSVD(), + rrule_alg=SparseSVDAdjoint(; alg=GMRES(; tol=1e-14)), +) + @testset "Non-truncacted SVD" begin - l_fullsvd, g_fullsvd = withgradient(A -> lossfun(A, TensorKit.SVD(), R), r) - l_oldsvd, g_oldsvd = withgradient(A -> lossfun(A, OldSVD(), R), r) - l_itersvd, g_itersvd = withgradient( - A -> lossfun(A, IterSVD(; alg_rrule=GMRES(; tol=adjoint_tol)), R), r - ) + l_fullsvd, g_fullsvd = withgradient(A -> lossfun(A, full_alg, R), r) + l_oldsvd, g_oldsvd = withgradient(A -> lossfun(A, old_alg, R), r) + l_itersvd, g_itersvd = withgradient(A -> lossfun(A, iter_alg, R), r) @test l_oldsvd ≈ l_itersvd ≈ l_fullsvd @test norm(g_fullsvd[1] - g_oldsvd[1]) / norm(g_fullsvd[1]) < rtol @@ -35,11 +39,9 @@ R = TensorMap(randn, space(r)) end @testset "Truncated SVD with χ=$χ" begin - l_fullsvd, g_fullsvd = withgradient(A -> lossfun(A, TensorKit.SVD(), R, trunc), r) - l_oldsvd, g_oldsvd = withgradient(A -> lossfun(A, OldSVD(), R, trunc), r) - l_itersvd, g_itersvd = withgradient( - A -> lossfun(A, IterSVD(; alg_rrule=GMRES(; tol=adjoint_tol)), R, trunc), r - ) + l_fullsvd, g_fullsvd = withgradient(A -> lossfun(A, full_alg, R, trunc), r) + l_oldsvd, g_oldsvd = withgradient(A -> lossfun(A, old_alg, R, trunc), r) + l_itersvd, g_itersvd = withgradient(A -> lossfun(A, iter_alg, R, trunc), r) @test l_oldsvd ≈ l_itersvd ≈ l_fullsvd @test norm(g_fullsvd[1] - g_oldsvd[1]) / norm(g_fullsvd[1]) > rtol @@ -47,16 +49,16 @@ end end # TODO: Add when Lorentzian broadening is implemented -# @testset "Truncated SVD with χ=$χ and ε=$lorentz_broad broadening" begin +# @testset "Truncated SVD with χ=$χ and ε=$lorentz_broadening broadening" begin # l_fullsvd, g_fullsvd = withgradient( -# A -> lossfun(A, FullSVD(; lorentz_broad, R; trunc), r +# A -> lossfun(A, FullSVD(; lorentz_broadening, R; trunc), r # ) -# l_oldsvd, g_oldsvd = withgradient(A -> lossfun(A, OldSVD(; lorentz_broad), R; trunc), r) +# l_oldsvd, g_oldsvd = withgradient(A -> lossfun(A, OldSVD(; lorentz_broadening), R; trunc), r) # l_itersvd, g_itersvd = withgradient( -# A -> lossfun(A, IterSVD(; howmany=χ, lorentz_broad), R; trunc), r +# A -> lossfun(A, IterSVD(; howmany=χ, lorentz_broadening), R; trunc), r # ) # @test l_oldsvd ≈ l_itersvd ≈ l_fullsvd # @test norm(g_fullsvd[1] - g_oldsvd[1]) / norm(g_fullsvd[1]) > rtol # @test norm(g_fullsvd[1] - g_itersvd[1]) / norm(g_fullsvd[1]) < rtol -# end +# end \ No newline at end of file From 7032ed0575df0d998417be7a17b7b1c1a91fb09d Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Tue, 9 Jul 2024 10:45:36 +0200 Subject: [PATCH 019/213] Add IterSVD test for symmetric tensor with fallback --- src/utility/svd.jl | 18 ++++++++++-------- test/ctmrg/svd_wrapper.jl | 32 ++++++++++++++++++++++++++++---- 2 files changed, 38 insertions(+), 12 deletions(-) diff --git a/src/utility/svd.jl b/src/utility/svd.jl index 9a2651ff..26b35b04 100644 --- a/src/utility/svd.jl +++ b/src/utility/svd.jl @@ -120,7 +120,9 @@ function ChainRulesCore.rrule( trunc::TruncationScheme=notrunc(), p::Real=2, ) where {A} - return rrule(TensorKit.tsvd!; trunc, p, alg=alg.svd_alg) + fwd, tsvd!_pullback = rrule(TensorKit.tsvd!, t; trunc, p, alg=alg.svd_alg) + tsvd!_completesvd_pullback(Δsvd) = tsvd!_pullback(Δsvd)..., NoTangent() + return fwd, tsvd!_completesvd_pullback end """ @@ -142,7 +144,7 @@ function ChainRulesCore.rrule( ) where {A} U, S, V, ϵ = PEPSKit.tsvd(t, alg; trunc, p) - function tsvd_sparsesvd_pullback((ΔU, ΔS, ΔV, Δϵ)) + function tsvd!_sparsesvd_pullback((ΔU, ΔS, ΔV, Δϵ)) Δt = similar(t) for (c, b) in blocks(Δt) Uc, Sc, Vc = block(U, c), block(S, c), block(V, c) @@ -183,11 +185,11 @@ function ChainRulesCore.rrule( end return NoTangent(), Δt, NoTangent() end - function tsvd_sparsesvd_pullback(::Tuple{ZeroTangent,ZeroTangent,ZeroTangent}) + function tsvd!_sparsesvd_pullback(::Tuple{ZeroTangent,ZeroTangent,ZeroTangent}) return NoTangent(), ZeroTangent(), NoTangent() end - return (U, S, V, ϵ), tsvd_sparsesvd_pullback + return (U, S, V, ϵ), tsvd!_sparsesvd_pullback end """ @@ -201,7 +203,7 @@ end # Use outdated adjoint in reverse pass (not taking truncated part into account for testing purposes) function ChainRulesCore.rrule( - ::typeof(PEPSKit.tsvd), + ::typeof(PEPSKit.tsvd!), t::AbstractTensorMap, alg::SVDrrule{A,NonTruncSVDAdjoint}; trunc::TruncationScheme=notrunc(), @@ -209,7 +211,7 @@ function ChainRulesCore.rrule( ) where {A} U, S, V, ϵ = PEPSKit.tsvd(t, alg; trunc, p) - function tsvd_nontruncsvd_pullback((ΔU, ΔS, ΔV, Δϵ)) + function tsvd!_nontruncsvd_pullback((ΔU, ΔS, ΔV, Δϵ)) Δt = similar(t) for (c, b) in blocks(Δt) Uc, Sc, Vc = block(U, c), block(S, c), block(V, c) @@ -229,11 +231,11 @@ function ChainRulesCore.rrule( end return NoTangent(), Δt, NoTangent() end - function tsvd_nontruncsvd_pullback(::Tuple{ZeroTangent,ZeroTangent,ZeroTangent}) + function tsvd!_nontruncsvd_pullback(::Tuple{ZeroTangent,ZeroTangent,ZeroTangent}) return NoTangent(), ZeroTangent(), NoTangent() end - return (U, S, V, ϵ), tsvd_nontruncsvd_pullback + return (U, S, V, ϵ), tsvd!_nontruncsvd_pullback end function oldsvd_rev( diff --git a/test/ctmrg/svd_wrapper.jl b/test/ctmrg/svd_wrapper.jl index 0f3bd1da..1a65da04 100644 --- a/test/ctmrg/svd_wrapper.jl +++ b/test/ctmrg/svd_wrapper.jl @@ -4,6 +4,7 @@ using LinearAlgebra using TensorKit using KrylovKit using ChainRulesCore, Zygote +using Accessors using PEPSKit # Gauge-invariant loss function @@ -18,14 +19,14 @@ dtype = ComplexF64 trunc = truncspace(ℂ^χ) # lorentz_broadening = 1e-12 rtol = 1e-9 -r = TensorMap(randn, dtype, ℂ^m ← ℂ^n) +r = TensorMap(randn, dtype, ℂ^m, ℂ^n) R = TensorMap(randn, space(r)) full_alg = SVDrrule(; svd_alg=TensorKit.SVD(), rrule_alg=CompleteSVDAdjoint()) old_alg = SVDrrule(; svd_alg=TensorKit.SVD(), rrule_alg=NonTruncSVDAdjoint()) iter_alg = SVDrrule(; # Don't make adjoint tolerance too small, g_itersvd will be weird - svd_alg=IterSVD(), - rrule_alg=SparseSVDAdjoint(; alg=GMRES(; tol=1e-14)), + svd_alg=IterSVD(; alg=GKL(; krylovdim=50)), + rrule_alg=SparseSVDAdjoint(; alg=GMRES(; tol=1e-13)), ) @testset "Non-truncacted SVD" begin @@ -61,4 +62,27 @@ end # @test l_oldsvd ≈ l_itersvd ≈ l_fullsvd # @test norm(g_fullsvd[1] - g_oldsvd[1]) / norm(g_fullsvd[1]) > rtol # @test norm(g_fullsvd[1] - g_itersvd[1]) / norm(g_fullsvd[1]) < rtol -# end \ No newline at end of file +# end + +symm_m, symm_n = 18, 24 +symm_space = Z2Space(0 => symm_m, 1 => symm_n) +symm_trspace = truncspace(Z2Space(0 => symm_m ÷ 2, 1 => symm_n ÷ 3)) +symm_r = TensorMap(randn, dtype, symm_space, symm_space) +symm_R = TensorMap(randn, dtype, space(symm_r)) + +@testset "IterSVD of symmetric tensors" begin + l_fullsvd, g_fullsvd = withgradient(A -> lossfun(A, full_alg, symm_R), symm_r) + l_itersvd, g_itersvd = withgradient(A -> lossfun(A, iter_alg, symm_R), symm_r) + @test l_itersvd ≈ l_fullsvd + @test norm(g_fullsvd[1] - g_itersvd[1]) / norm(g_fullsvd[1]) < rtol + + l_fullsvd_tr, g_fullsvd_tr = withgradient(A -> lossfun(A, full_alg, symm_R, symm_trspace), symm_r) + l_itersvd_tr, g_itersvd_tr = withgradient(A -> lossfun(A, iter_alg, symm_R, symm_trspace), symm_r) + @test l_itersvd_tr ≈ l_fullsvd_tr + @test norm(g_fullsvd_tr[1] - g_itersvd_tr[1]) / norm(g_fullsvd_tr[1]) < rtol + + iter_alg_fallback = @set iter_alg.svd_alg.fallback_threshold = 0.4 # Do dense SVD in one block, sparse SVD in the other + l_itersvd_fb, g_itersvd_fb = withgradient(A -> lossfun(A, iter_alg_fallback, symm_R, symm_trspace), symm_r) + @test l_itersvd_fb ≈ l_fullsvd_tr + @test norm(g_fullsvd_tr[1] - g_itersvd_fb[1]) / norm(g_fullsvd_tr[1]) < rtol +end From d09561f9262871e9b85a6c2d589993aac9f4ca89 Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Tue, 9 Jul 2024 15:43:53 +0200 Subject: [PATCH 020/213] Formatting --- test/ctmrg/gaugefix.jl | 8 ++++++-- test/ctmrg/svd_wrapper.jl | 12 +++++++++--- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/test/ctmrg/gaugefix.jl b/test/ctmrg/gaugefix.jl index 195df266..a6883474 100644 --- a/test/ctmrg/gaugefix.jl +++ b/test/ctmrg/gaugefix.jl @@ -46,7 +46,9 @@ end ctm = CTMRGEnv(psi; Venv=ctm_space) verbosity = 1 - alg = CTMRG(; tol=1e-10, miniter=4, maxiter=400, verbosity, trscheme=truncdim(dim(ctm_space))) + alg = CTMRG(; + tol=1e-10, miniter=4, maxiter=400, verbosity, trscheme=truncdim(dim(ctm_space)) + ) alg_fixed = @set alg.projector_alg.trscheme = FixedSpaceTruncation() ctm = leading_boundary(ctm, psi, alg) @@ -69,7 +71,9 @@ end ctm = CTMRGEnv(psi; Venv=ctm_space) verbosity = 1 - alg = CTMRG(; tol=1e-10, miniter=4, maxiter=400, verbosity, trscheme=truncdim(dim(ctm_space))) + alg = CTMRG(; + tol=1e-10, miniter=4, maxiter=400, verbosity, trscheme=truncdim(dim(ctm_space)) + ) alg_fixed = @set alg.projector_alg.trscheme = FixedSpaceTruncation() ctm = leading_boundary(ctm, psi, alg) diff --git a/test/ctmrg/svd_wrapper.jl b/test/ctmrg/svd_wrapper.jl index 1a65da04..b21c2af7 100644 --- a/test/ctmrg/svd_wrapper.jl +++ b/test/ctmrg/svd_wrapper.jl @@ -76,13 +76,19 @@ symm_R = TensorMap(randn, dtype, space(symm_r)) @test l_itersvd ≈ l_fullsvd @test norm(g_fullsvd[1] - g_itersvd[1]) / norm(g_fullsvd[1]) < rtol - l_fullsvd_tr, g_fullsvd_tr = withgradient(A -> lossfun(A, full_alg, symm_R, symm_trspace), symm_r) - l_itersvd_tr, g_itersvd_tr = withgradient(A -> lossfun(A, iter_alg, symm_R, symm_trspace), symm_r) + l_fullsvd_tr, g_fullsvd_tr = withgradient( + A -> lossfun(A, full_alg, symm_R, symm_trspace), symm_r + ) + l_itersvd_tr, g_itersvd_tr = withgradient( + A -> lossfun(A, iter_alg, symm_R, symm_trspace), symm_r + ) @test l_itersvd_tr ≈ l_fullsvd_tr @test norm(g_fullsvd_tr[1] - g_itersvd_tr[1]) / norm(g_fullsvd_tr[1]) < rtol iter_alg_fallback = @set iter_alg.svd_alg.fallback_threshold = 0.4 # Do dense SVD in one block, sparse SVD in the other - l_itersvd_fb, g_itersvd_fb = withgradient(A -> lossfun(A, iter_alg_fallback, symm_R, symm_trspace), symm_r) + l_itersvd_fb, g_itersvd_fb = withgradient( + A -> lossfun(A, iter_alg_fallback, symm_R, symm_trspace), symm_r + ) @test l_itersvd_fb ≈ l_fullsvd_tr @test norm(g_fullsvd_tr[1] - g_itersvd_fb[1]) / norm(g_fullsvd_tr[1]) < rtol end From b3a0726491d46702808b74ae9f12e861d34e5a85 Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Tue, 9 Jul 2024 16:04:46 +0200 Subject: [PATCH 021/213] Fix missing cnext in ctmrg, update README example --- README.md | 13 +++---------- docs/src/index.md | 13 +++---------- src/algorithms/ctmrg.jl | 1 + 3 files changed, 7 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index a45818d6..e069d7a2 100644 --- a/README.md +++ b/README.md @@ -36,19 +36,12 @@ For example, in order to obtain the groundstate of the 2D Heisenberg model, we c using TensorKit, PEPSKit, KrylovKit, OptimKit # constructing the Hamiltonian: -Jx, Jy, Jz = (-1, 1, -1) # sublattice rotation to obtain single-site unit cell -physical_space = ComplexSpace(2) -T = ComplexF64 -σx = TensorMap(T[0 1; 1 0], physical_space, physical_space) -σy = TensorMap(T[0 im; -im 0], physical_space, physical_space) -σz = TensorMap(T[1 0; 0 -1], physical_space, physical_space) -H = (Jx * σx ⊗ σx) + (Jy * σy ⊗ σy) + (Jz * σz ⊗ σz) -Heisenberg_hamiltonian = NLocalOperator{NearestNeighbor}(H / 4) +H = square_lattice_heisenberg(; Jx=-1, Jy=1, Jz=-1) # sublattice rotation to obtain single-site unit cell # configuring the parameters D = 2 chi = 20 -ctm_alg = CTMRG(; trscheme = truncdim(chi), tol=1e-20, miniter=4, maxiter=100, verbosity=1) +ctm_alg = CTMRG(; tol=1e-10, miniter=4, maxiter=100, verbosity=1, trscheme=truncdim(chi)) opt_alg = PEPSOptimize(; boundary_alg=ctm_alg, optimizer=LBFGS(4; maxiter=100, gradtol=1e-4, verbosity=2), @@ -60,7 +53,7 @@ opt_alg = PEPSOptimize(; # ground state search state = InfinitePEPS(2, D) ctm = leading_boundary(CTMRGEnv(state; Venv=ComplexSpace(chi)), state, ctm_alg) -result = fixedpoint(state, Heisenberg_hamiltonian, opt_alg, ctm) +result = fixedpoint(state, H, opt_alg, ctm) @show result.E # -0.6625... ``` diff --git a/docs/src/index.md b/docs/src/index.md index 305f6d84..ea76186c 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -21,19 +21,12 @@ For example, in order to obtain the groundstate of the 2D Heisenberg model, we c using TensorKit, PEPSKit, KrylovKit, OptimKit # constructing the Hamiltonian: -Jx, Jy, Jz = (-1, 1, -1) # sublattice rotation to obtain single-site unit cell -physical_space = ComplexSpace(2) -T = ComplexF64 -σx = TensorMap(T[0 1; 1 0], physical_space, physical_space) -σy = TensorMap(T[0 im; -im 0], physical_space, physical_space) -σz = TensorMap(T[1 0; 0 -1], physical_space, physical_space) -H = (Jx * σx ⊗ σx) + (Jy * σy ⊗ σy) + (Jz * σz ⊗ σz) -Heisenberg_hamiltonian = NLocalOperator{NearestNeighbor}(H / 4) +H = square_lattice_heisenberg(; Jx=-1, Jy=1, Jz=-1) # sublattice rotation to obtain single-site unit cell # configuring the parameters D = 2 chi = 20 -ctm_alg = CTMRG(; trscheme = truncdim(chi), tol=1e-20, miniter=4, maxiter=100, verbosity=1) +ctm_alg = CTMRG(; tol=1e-10, miniter=4, maxiter=100, verbosity=1, trscheme=truncdim(chi)) opt_alg = PEPSOptimize(; boundary_alg=ctm_alg, optimizer=LBFGS(4; maxiter=100, gradtol=1e-4, verbosity=2), @@ -45,7 +38,7 @@ opt_alg = PEPSOptimize(; # ground state search state = InfinitePEPS(2, D) ctm = leading_boundary(CTMRGEnv(state; Venv=ComplexSpace(chi)), state, ctm_alg) -result = fixedpoint(state, Heisenberg_hamiltonian, opt_alg, ctm) +result = fixedpoint(state, H, opt_alg, ctm) @show result.E # -0.6625... ``` diff --git a/src/algorithms/ctmrg.jl b/src/algorithms/ctmrg.jl index d0073465..69d4a465 100644 --- a/src/algorithms/ctmrg.jl +++ b/src/algorithms/ctmrg.jl @@ -346,6 +346,7 @@ function left_move(state, env::CTMRGEnv{C,T}, alg::ProjectorAlg) where {C,T} for col in 1:size(state, 2) cprev = _prev(col, size(state, 2)) + cnext = _next(col, size(state, 2)) # Compute projectors for row in 1:size(state, 1) From 89ae0a4e48909a69b3508b1271f8acf784e86149 Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Tue, 9 Jul 2024 16:35:36 +0200 Subject: [PATCH 022/213] Rename DenseSVDAdjoint, update svd_wrapper test --- src/PEPSKit.jl | 2 +- src/utility/svd.jl | 16 ++++++++-------- test/ctmrg/svd_wrapper.jl | 16 ++++++++-------- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/PEPSKit.jl b/src/PEPSKit.jl index 650195c5..5fbbf739 100644 --- a/src/PEPSKit.jl +++ b/src/PEPSKit.jl @@ -59,7 +59,7 @@ module Defaults const fpgrad_tol = 1e-6 end -export SVDrrule, IterSVD, OldSVD, CompleteSVDAdjoint, SparseSVDAdjoint, NonTruncSVDAdjoint +export SVDrrule, IterSVD, OldSVD, DenseSVDAdjoint, SparseSVDAdjoint, NonTruncSVDAdjoint export FixedSpaceTruncation, ProjectorAlg, CTMRG, CTMRGEnv export LocalOperator export expectation_value, costfun diff --git a/src/utility/svd.jl b/src/utility/svd.jl index 26b35b04..b86df78d 100644 --- a/src/utility/svd.jl +++ b/src/utility/svd.jl @@ -9,13 +9,13 @@ using TensorKit: CRCExt = Base.get_extension(KrylovKit, :KrylovKitChainRulesCoreExt) """ - struct SVDrrule(; svd_alg = TensorKit.SVD(), rrule_alg = CompleteSVDAdjoint()) + struct SVDrrule(; svd_alg = TensorKit.SVD(), rrule_alg = DenseSVDAdjoint()) Wrapper for a SVD algorithm `svd_alg` with a defined reverse rule `rrule_alg`. """ @kwdef struct SVDrrule{S,R} svd_alg::S = TensorKit.SVD() - rrule_alg::R = CompleteSVDAdjoint() # TODO: should contain Lorentzian broadening eventually + rrule_alg::R = DenseSVDAdjoint() # TODO: should contain Lorentzian broadening eventually end # Keep truncation algorithm separate to be able to specify CTMRG dependent information """ @@ -105,18 +105,18 @@ function TensorKit._compute_svddata!( end """ - struct CompleteSVDAdjoint(; lorentz_broadening = 0.0) + struct DenseSVDAdjoint(; lorentz_broadening = 0.0) Wrapper around the complete `TensorKit.tsvd!` rrule which requires computing the full SVD. """ -@kwdef struct CompleteSVDAdjoint +@kwdef struct DenseSVDAdjoint lorentz_broadening::Float64 = 0.0 end function ChainRulesCore.rrule( ::typeof(PEPSKit.tsvd!), t::AbstractTensorMap, - alg::SVDrrule{A,CompleteSVDAdjoint}; + alg::SVDrrule{A,DenseSVDAdjoint}; trunc::TruncationScheme=notrunc(), p::Real=2, ) where {A} @@ -130,15 +130,15 @@ end Wrapper around the `KrylovKit.svdsolve` rrule where only the truncated decomposition is required. """ -@kwdef struct SparseSVDAdjoint - alg::Union{GMRES,BiCGStab,Arnoldi} = GMRES() +@kwdef struct SparseSVDAdjoint{A} + alg::A = GMRES() lorentz_broadening::Float64 = 0.0 end function ChainRulesCore.rrule( ::typeof(PEPSKit.tsvd!), t::AbstractTensorMap, - alg::SVDrrule{A,SparseSVDAdjoint}; + alg::SVDrrule{A,<:SparseSVDAdjoint}; trunc::TruncationScheme=notrunc(), p::Real=2, ) where {A} diff --git a/test/ctmrg/svd_wrapper.jl b/test/ctmrg/svd_wrapper.jl index b21c2af7..7a69afea 100644 --- a/test/ctmrg/svd_wrapper.jl +++ b/test/ctmrg/svd_wrapper.jl @@ -22,7 +22,7 @@ rtol = 1e-9 r = TensorMap(randn, dtype, ℂ^m, ℂ^n) R = TensorMap(randn, space(r)) -full_alg = SVDrrule(; svd_alg=TensorKit.SVD(), rrule_alg=CompleteSVDAdjoint()) +full_alg = SVDrrule(; svd_alg=TensorKit.SVD(), rrule_alg=DenseSVDAdjoint()) old_alg = SVDrrule(; svd_alg=TensorKit.SVD(), rrule_alg=NonTruncSVDAdjoint()) iter_alg = SVDrrule(; # Don't make adjoint tolerance too small, g_itersvd will be weird svd_alg=IterSVD(; alg=GKL(; krylovdim=50)), @@ -35,8 +35,8 @@ iter_alg = SVDrrule(; # Don't make adjoint tolerance too small, g_itersvd will l_itersvd, g_itersvd = withgradient(A -> lossfun(A, iter_alg, R), r) @test l_oldsvd ≈ l_itersvd ≈ l_fullsvd - @test norm(g_fullsvd[1] - g_oldsvd[1]) / norm(g_fullsvd[1]) < rtol - @test norm(g_fullsvd[1] - g_itersvd[1]) / norm(g_fullsvd[1]) < rtol + @test g_fullsvd[1] ≈ g_oldsvd[1] rtol = rtol + @test g_fullsvd[1] ≈ g_itersvd[1] rtol = rtol end @testset "Truncated SVD with χ=$χ" begin @@ -45,8 +45,8 @@ end l_itersvd, g_itersvd = withgradient(A -> lossfun(A, iter_alg, R, trunc), r) @test l_oldsvd ≈ l_itersvd ≈ l_fullsvd - @test norm(g_fullsvd[1] - g_oldsvd[1]) / norm(g_fullsvd[1]) > rtol - @test norm(g_fullsvd[1] - g_itersvd[1]) / norm(g_fullsvd[1]) < rtol + @test !isapprox(g_fullsvd[1], g_oldsvd[1]; rtol) + @test g_fullsvd[1] ≈ g_itersvd[1] rtol = rtol end # TODO: Add when Lorentzian broadening is implemented @@ -74,7 +74,7 @@ symm_R = TensorMap(randn, dtype, space(symm_r)) l_fullsvd, g_fullsvd = withgradient(A -> lossfun(A, full_alg, symm_R), symm_r) l_itersvd, g_itersvd = withgradient(A -> lossfun(A, iter_alg, symm_R), symm_r) @test l_itersvd ≈ l_fullsvd - @test norm(g_fullsvd[1] - g_itersvd[1]) / norm(g_fullsvd[1]) < rtol + @test g_fullsvd[1] ≈ g_itersvd[1] rtol = rtol l_fullsvd_tr, g_fullsvd_tr = withgradient( A -> lossfun(A, full_alg, symm_R, symm_trspace), symm_r @@ -83,12 +83,12 @@ symm_R = TensorMap(randn, dtype, space(symm_r)) A -> lossfun(A, iter_alg, symm_R, symm_trspace), symm_r ) @test l_itersvd_tr ≈ l_fullsvd_tr - @test norm(g_fullsvd_tr[1] - g_itersvd_tr[1]) / norm(g_fullsvd_tr[1]) < rtol + @test g_fullsvd_tr[1] ≈ g_itersvd_tr[1] rtol = rtol iter_alg_fallback = @set iter_alg.svd_alg.fallback_threshold = 0.4 # Do dense SVD in one block, sparse SVD in the other l_itersvd_fb, g_itersvd_fb = withgradient( A -> lossfun(A, iter_alg_fallback, symm_R, symm_trspace), symm_r ) @test l_itersvd_fb ≈ l_fullsvd_tr - @test norm(g_fullsvd_tr[1] - g_itersvd_fb[1]) / norm(g_fullsvd_tr[1]) < rtol + @test g_fullsvd_tr[1] ≈ g_itersvd_fb[1] rtol = rtol end From 25d198cdc0c296046f11d008f03c3e950be5e572 Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Tue, 9 Jul 2024 16:50:01 +0200 Subject: [PATCH 023/213] Make CRCExt extension backwards compatible with v1.8 --- src/utility/svd.jl | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/utility/svd.jl b/src/utility/svd.jl index b86df78d..8305ac9d 100644 --- a/src/utility/svd.jl +++ b/src/utility/svd.jl @@ -6,7 +6,13 @@ using TensorKit: _create_svdtensors, NoTruncation, TruncationSpace -CRCExt = Base.get_extension(KrylovKit, :KrylovKitChainRulesCoreExt) + +# Enables backwards compatibility without package extensions +CRCExt = @static if isdefined(Base, :get_extension) + Base.get_extension(KrylovKit, :KrylovKitChainRulesCoreExt) +else + KrylovKit.KrylovKitChainRulesCoreExt +end """ struct SVDrrule(; svd_alg = TensorKit.SVD(), rrule_alg = DenseSVDAdjoint()) From 6b818e7008f5f7571412ef3308b02c78e25af7ac Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Tue, 9 Jul 2024 17:55:28 +0200 Subject: [PATCH 024/213] Replace SVDrrule with SVDAdjoint, clean up adjoint algorithms --- src/PEPSKit.jl | 2 +- src/algorithms/ctmrg.jl | 6 +-- src/utility/svd.jl | 83 +++++++++++---------------------------- test/ctmrg/svd_wrapper.jl | 11 +++--- 4 files changed, 32 insertions(+), 70 deletions(-) diff --git a/src/PEPSKit.jl b/src/PEPSKit.jl index 5fbbf739..8d8815ec 100644 --- a/src/PEPSKit.jl +++ b/src/PEPSKit.jl @@ -59,7 +59,7 @@ module Defaults const fpgrad_tol = 1e-6 end -export SVDrrule, IterSVD, OldSVD, DenseSVDAdjoint, SparseSVDAdjoint, NonTruncSVDAdjoint +export SVDAdjoint, IterSVD, NonTruncSVDAdjoint export FixedSpaceTruncation, ProjectorAlg, CTMRG, CTMRGEnv export LocalOperator export expectation_value, costfun diff --git a/src/algorithms/ctmrg.jl b/src/algorithms/ctmrg.jl index 69d4a465..69bef21a 100644 --- a/src/algorithms/ctmrg.jl +++ b/src/algorithms/ctmrg.jl @@ -18,8 +18,8 @@ kind of truncation scheme can be used. If `fixedspace` is true, the truncation s `truncspace(V)` where `V` is the environment bond space, adjusted to the corresponding environment direction/unit cell entry. """ -@kwdef struct ProjectorAlg{S<:SVDrrule,T} - svd_alg::S = SVDrrule() +@kwdef struct ProjectorAlg{S<:SVDAdjoint,T} + svd_alg::S = SVDAdjoint() trscheme::T = FixedSpaceTruncation() verbosity::Int = 0 end @@ -49,7 +49,7 @@ function CTMRG(; maxiter=Defaults.ctmrg_maxiter, miniter=Defaults.ctmrg_miniter, verbosity=0, - svd_alg=SVDrrule(), + svd_alg=SVDAdjoint(), trscheme=FixedSpaceTruncation(), ) return CTMRG( diff --git a/src/utility/svd.jl b/src/utility/svd.jl index 8305ac9d..c76ddb9f 100644 --- a/src/utility/svd.jl +++ b/src/utility/svd.jl @@ -15,13 +15,18 @@ else end """ - struct SVDrrule(; svd_alg = TensorKit.SVD(), rrule_alg = DenseSVDAdjoint()) + struct SVDAdjoint(; fwd_alg = TensorKit.SVD(), rrule_alg = nothing, + broadening = nothing) -Wrapper for a SVD algorithm `svd_alg` with a defined reverse rule `rrule_alg`. +Wrapper for a SVD algorithm `fwd_alg` with a defined reverse rule `rrule_alg`. +If `isnothing(rrule_alg)`, Zygote differentiates the forward call automatically. +In case of degenerate singular values, one might need a `broadening` scheme which +removes the divergences from the adjoint. """ -@kwdef struct SVDrrule{S,R} - svd_alg::S = TensorKit.SVD() - rrule_alg::R = DenseSVDAdjoint() # TODO: should contain Lorentzian broadening eventually +@kwdef struct SVDAdjoint{F,R,B} + fwd_alg::F = TensorKit.SVD() + rrule_alg::R = nothing + broadening::B = nothing end # Keep truncation algorithm separate to be able to specify CTMRG dependent information """ @@ -34,9 +39,9 @@ SVD from `KrylovKit.svdsolve` is used. """ PEPSKit.tsvd(t::AbstractTensorMap, alg; kwargs...) = PEPSKit.tsvd!(copy(t), alg; kwargs...) function PEPSKit.tsvd!( - t::AbstractTensorMap, alg::SVDrrule; trunc::TruncationScheme=notrunc(), p::Real=2 + t::AbstractTensorMap, alg::SVDAdjoint; trunc::TruncationScheme=notrunc(), p::Real=2 ) - return TensorKit.tsvd!(t; alg=alg.svd_alg, trunc, p) + return TensorKit.tsvd!(t; alg=alg.fwd_alg, trunc, p) end """ @@ -110,47 +115,16 @@ function TensorKit._compute_svddata!( return Udata, Sdata, Vdata, dims end -""" - struct DenseSVDAdjoint(; lorentz_broadening = 0.0) - -Wrapper around the complete `TensorKit.tsvd!` rrule which requires computing the full SVD. -""" -@kwdef struct DenseSVDAdjoint - lorentz_broadening::Float64 = 0.0 -end - function ChainRulesCore.rrule( ::typeof(PEPSKit.tsvd!), t::AbstractTensorMap, - alg::SVDrrule{A,DenseSVDAdjoint}; + alg::SVDAdjoint{IterSVD,R,B}; trunc::TruncationScheme=notrunc(), p::Real=2, -) where {A} - fwd, tsvd!_pullback = rrule(TensorKit.tsvd!, t; trunc, p, alg=alg.svd_alg) - tsvd!_completesvd_pullback(Δsvd) = tsvd!_pullback(Δsvd)..., NoTangent() - return fwd, tsvd!_completesvd_pullback -end - -""" - struct SparseSVDAdjoint(; lorentz_broadening = 0.0) - -Wrapper around the `KrylovKit.svdsolve` rrule where only the truncated decomposition is required. -""" -@kwdef struct SparseSVDAdjoint{A} - alg::A = GMRES() - lorentz_broadening::Float64 = 0.0 -end - -function ChainRulesCore.rrule( - ::typeof(PEPSKit.tsvd!), - t::AbstractTensorMap, - alg::SVDrrule{A,<:SparseSVDAdjoint}; - trunc::TruncationScheme=notrunc(), - p::Real=2, -) where {A} +) where {R,B} U, S, V, ϵ = PEPSKit.tsvd(t, alg; trunc, p) - function tsvd!_sparsesvd_pullback((ΔU, ΔS, ΔV, Δϵ)) + function tsvd!_itersvd_pullback((ΔU, ΔS, ΔV, Δϵ)) Δt = similar(t) for (c, b) in blocks(Δt) Uc, Sc, Vc = block(U, c), block(S, c), block(V, c) @@ -181,8 +155,8 @@ function ChainRulesCore.rrule( minimal_info, block(t, c), :LR, - alg.svd_alg.alg, - alg.rrule_alg.alg, + alg.fwd_alg.alg, + alg.rrule_alg, ) copyto!( b, @@ -191,11 +165,11 @@ function ChainRulesCore.rrule( end return NoTangent(), Δt, NoTangent() end - function tsvd!_sparsesvd_pullback(::Tuple{ZeroTangent,ZeroTangent,ZeroTangent}) + function tsvd!_itersvd_pullback(::Tuple{ZeroTangent,ZeroTangent,ZeroTangent}) return NoTangent(), ZeroTangent(), NoTangent() end - return (U, S, V, ϵ), tsvd!_sparsesvd_pullback + return (U, S, V, ϵ), tsvd!_itersvd_pullback end """ @@ -203,18 +177,16 @@ end Old SVD adjoint that does not account for the truncated part of truncated SVDs. """ -@kwdef struct NonTruncSVDAdjoint - lorentz_broadening::Float64 = 0.0 -end +struct NonTruncSVDAdjoint end # Use outdated adjoint in reverse pass (not taking truncated part into account for testing purposes) function ChainRulesCore.rrule( ::typeof(PEPSKit.tsvd!), t::AbstractTensorMap, - alg::SVDrrule{A,NonTruncSVDAdjoint}; + alg::SVDAdjoint{F,NonTruncSVDAdjoint,B}; trunc::TruncationScheme=notrunc(), p::Real=2, -) where {A} +) where {F,B} U, S, V, ϵ = PEPSKit.tsvd(t, alg; trunc, p) function tsvd!_nontruncsvd_pullback((ΔU, ΔS, ΔV, Δϵ)) @@ -223,16 +195,7 @@ function ChainRulesCore.rrule( Uc, Sc, Vc = block(U, c), block(S, c), block(V, c) ΔUc, ΔSc, ΔVc = block(ΔU, c), block(ΔS, c), block(ΔV, c) copyto!( - b, - oldsvd_rev( - Uc, - Sc, - Vc, - ΔUc, - ΔSc, - ΔVc; - lorentz_broadening=alg.rrule_alg.lorentz_broadening, - ), + b, oldsvd_rev(Uc, Sc, Vc, ΔUc, ΔSc, ΔVc; lorentz_broadening=alg.broadening) ) end return NoTangent(), Δt, NoTangent() diff --git a/test/ctmrg/svd_wrapper.jl b/test/ctmrg/svd_wrapper.jl index 7a69afea..3c17dc7d 100644 --- a/test/ctmrg/svd_wrapper.jl +++ b/test/ctmrg/svd_wrapper.jl @@ -22,12 +22,11 @@ rtol = 1e-9 r = TensorMap(randn, dtype, ℂ^m, ℂ^n) R = TensorMap(randn, space(r)) -full_alg = SVDrrule(; svd_alg=TensorKit.SVD(), rrule_alg=DenseSVDAdjoint()) -old_alg = SVDrrule(; svd_alg=TensorKit.SVD(), rrule_alg=NonTruncSVDAdjoint()) -iter_alg = SVDrrule(; # Don't make adjoint tolerance too small, g_itersvd will be weird - svd_alg=IterSVD(; alg=GKL(; krylovdim=50)), - rrule_alg=SparseSVDAdjoint(; alg=GMRES(; tol=1e-13)), +full_alg = SVDAdjoint(; fwd_alg=TensorKit.SVD(), rrule_alg=nothing) +old_alg = SVDAdjoint(; + fwd_alg=TensorKit.SVD(), rrule_alg=NonTruncSVDAdjoint(), broadening=0.0 ) +iter_alg = SVDAdjoint(; fwd_alg=IterSVD(), rrule_alg=GMRES(; tol=1e-13)) # Don't make adjoint tolerance too small, g_itersvd will be weird @testset "Non-truncacted SVD" begin l_fullsvd, g_fullsvd = withgradient(A -> lossfun(A, full_alg, R), r) @@ -85,7 +84,7 @@ symm_R = TensorMap(randn, dtype, space(symm_r)) @test l_itersvd_tr ≈ l_fullsvd_tr @test g_fullsvd_tr[1] ≈ g_itersvd_tr[1] rtol = rtol - iter_alg_fallback = @set iter_alg.svd_alg.fallback_threshold = 0.4 # Do dense SVD in one block, sparse SVD in the other + iter_alg_fallback = @set iter_alg.fwd_alg.fallback_threshold = 0.4 # Do dense SVD in one block, sparse SVD in the other l_itersvd_fb, g_itersvd_fb = withgradient( A -> lossfun(A, iter_alg_fallback, symm_R, symm_trspace), symm_r ) From 32f7b9f1bd9fa810213755f60e0ab7bb70547dfe Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Tue, 9 Jul 2024 18:21:17 +0200 Subject: [PATCH 025/213] Separate CTMRG gauge fixing from main file, add correlation length --- src/PEPSKit.jl | 4 +- src/algorithms/ctmrg.jl | 220 ++++++------------------------ src/algorithms/ctmrg_all_sides.jl | 0 src/algorithms/ctmrg_gauge_fix.jl | 179 ++++++++++++++++++++++++ 4 files changed, 223 insertions(+), 180 deletions(-) create mode 100644 src/algorithms/ctmrg_all_sides.jl create mode 100644 src/algorithms/ctmrg_gauge_fix.jl diff --git a/src/PEPSKit.jl b/src/PEPSKit.jl index 8d8815ec..c9f929df 100644 --- a/src/PEPSKit.jl +++ b/src/PEPSKit.jl @@ -29,7 +29,9 @@ include("environments/ctmrgenv.jl") include("operators/localoperator.jl") include("operators/models.jl") +include("algorithms/ctmrg_gauge_fix.jl") include("algorithms/ctmrg.jl") +include("algorithms/ctmrg_all_sides.jl") include("algorithms/peps_opt.jl") include("utility/symmetrization.jl") @@ -60,7 +62,7 @@ module Defaults end export SVDAdjoint, IterSVD, NonTruncSVDAdjoint -export FixedSpaceTruncation, ProjectorAlg, CTMRG, CTMRGEnv +export FixedSpaceTruncation, ProjectorAlg, CTMRG, CTMRGEnv, correlation_length export LocalOperator export expectation_value, costfun export leading_boundary diff --git a/src/algorithms/ctmrg.jl b/src/algorithms/ctmrg.jl index 69bef21a..7a0f4e83 100644 --- a/src/algorithms/ctmrg.jl +++ b/src/algorithms/ctmrg.jl @@ -131,185 +131,6 @@ function MPSKit.leading_boundary(envinit, state, alg::CTMRG) return envfix end -""" - gauge_fix(envprev::CTMRGEnv{C,T}, envfinal::CTMRGEnv{C,T}) where {C,T} - -Fix the gauge of `envfinal` based on the previous environment `envprev`. -This assumes that the `envfinal` is the result of one CTMRG iteration on `envprev`. -Given that the CTMRG run is converged, the returned environment will be -element-wise converged to `envprev`. -""" -function gauge_fix(envprev::CTMRGEnv{C,T}, envfinal::CTMRGEnv{C,T}) where {C,T} - # Check if spaces in envprev and envfinal are the same - same_spaces = map(Iterators.product(axes(envfinal.edges)...)) do (dir, r, c) - space(envfinal.edges[dir, r, c]) == space(envprev.edges[dir, r, c]) && - space(envfinal.corners[dir, r, c]) == space(envprev.corners[dir, r, c]) - end - @assert all(same_spaces) "Spaces of envprev and envfinal are not the same" - - # Try the "general" algorithm from https://arxiv.org/abs/2311.11894 - signs = map(Iterators.product(axes(envfinal.edges)...)) do (dir, r, c) - # Gather edge tensors and pretend they're InfiniteMPSs - if dir == NORTH - Tsprev = circshift(envprev.edges[dir, r, :], 1 - c) - Tsfinal = circshift(envfinal.edges[dir, r, :], 1 - c) - elseif dir == EAST - Tsprev = circshift(envprev.edges[dir, :, c], 1 - r) - Tsfinal = circshift(envfinal.edges[dir, :, c], 1 - r) - elseif dir == SOUTH - Tsprev = circshift(reverse(envprev.edges[dir, r, :]), c) - Tsfinal = circshift(reverse(envfinal.edges[dir, r, :]), c) - elseif dir == WEST - Tsprev = circshift(reverse(envprev.edges[dir, :, c]), r) - Tsfinal = circshift(reverse(envfinal.edges[dir, :, c]), r) - end - - # Random MPS of same bond dimension - M = map(Tsfinal) do t - TensorMap(randn, scalartype(t), codomain(t) ← domain(t)) - end - - # Find right fixed points of mixed transfer matrices - ρinit = TensorMap( - randn, - scalartype(T), - MPSKit._lastspace(Tsfinal[end])' ← MPSKit._lastspace(M[end])', - ) - ρprev = transfermatrix_fixedpoint(Tsprev, M, ρinit) - ρfinal = transfermatrix_fixedpoint(Tsfinal, M, ρinit) - - # Decompose and multiply - Qprev, = leftorth(ρprev) - Qfinal, = leftorth(ρfinal) - - return Qprev * Qfinal' - end - - cornersfix, edgesfix = fix_relative_phases(envfinal, signs) - - # Fix global phase - cornersgfix = map(envprev.corners, cornersfix) do Cprev, Cfix - return dot(Cfix, Cprev) * Cfix - end - edgesgfix = map(envprev.edges, edgesfix) do Tprev, Tfix - return dot(Tfix, Tprev) * Tfix - end - return CTMRGEnv(cornersgfix, edgesgfix) -end - -# this is a bit of a hack to get the fixed point of the mixed transfer matrix -# because MPSKit is not compatible with AD -function transfermatrix_fixedpoint(tops, bottoms, ρinit) - _, vecs, info = eigsolve(ρinit, 1, :LM, Arnoldi()) do ρ - return foldr(zip(tops, bottoms); init=ρ) do (top, bottom), ρ - return @tensor ρ′[-1; -2] := top[-1 4 3; 1] * conj(bottom[-2 4 3; 2]) * ρ[1; 2] - end - end - info.converged > 0 || @warn "eigsolve did not converge" - return first(vecs) -end - -# Explicit fixing of relative phases (doing this compactly in a loop is annoying) -function _contract_gauge_corner(corner, σ_in, σ_out) - @autoopt @tensor corner_fix[χ_in; χ_out] := - σ_in[χ_in; χ1] * corner[χ1; χ2] * conj(σ_out[χ_out; χ2]) -end -function _contract_gauge_edge(edge, σ_in, σ_out) - @autoopt @tensor edge_fix[χ_in D_above D_below; χ_out] := - σ_in[χ_in; χ1] * edge[χ1 D_above D_below; χ2] * conj(σ_out[χ_out; χ2]) -end -function fix_relative_phases(envfinal::CTMRGEnv, signs) - C1 = map(Iterators.product(axes(envfinal.corners)[2:3]...)) do (r, c) - _contract_gauge_corner( - envfinal.corners[NORTHWEST, r, c], - signs[WEST, r, c], - signs[NORTH, r, _next(c, end)], - ) - end - T1 = map(Iterators.product(axes(envfinal.edges)[2:3]...)) do (r, c) - _contract_gauge_edge( - envfinal.edges[NORTH, r, c], - signs[NORTH, r, c], - signs[NORTH, r, _next(c, end)], - ) - end - C2 = map(Iterators.product(axes(envfinal.corners)[2:3]...)) do (r, c) - _contract_gauge_corner( - envfinal.corners[NORTHEAST, r, c], - signs[NORTH, r, c], - signs[EAST, _next(r, end), c], - ) - end - T2 = map(Iterators.product(axes(envfinal.edges)[2:3]...)) do (r, c) - _contract_gauge_edge( - envfinal.edges[EAST, r, c], signs[EAST, r, c], signs[EAST, _next(r, end), c] - ) - end - C3 = map(Iterators.product(axes(envfinal.corners)[2:3]...)) do (r, c) - _contract_gauge_corner( - envfinal.corners[SOUTHEAST, r, c], - signs[EAST, r, c], - signs[SOUTH, r, _prev(c, end)], - ) - end - T3 = map(Iterators.product(axes(envfinal.edges)[2:3]...)) do (r, c) - _contract_gauge_edge( - envfinal.edges[SOUTH, r, c], - signs[SOUTH, r, c], - signs[SOUTH, r, _prev(c, end)], - ) - end - C4 = map(Iterators.product(axes(envfinal.corners)[2:3]...)) do (r, c) - _contract_gauge_corner( - envfinal.corners[SOUTHWEST, r, c], - signs[SOUTH, r, c], - signs[WEST, _prev(r, end), c], - ) - end - T4 = map(Iterators.product(axes(envfinal.edges)[2:3]...)) do (r, c) - _contract_gauge_edge( - envfinal.edges[WEST, r, c], signs[WEST, r, c], signs[WEST, _prev(r, end), c] - ) - end - - return stack([C1, C2, C3, C4]; dims=1), stack([T1, T2, T3, T4]; dims=1) -end - -""" - check_elementwise_convergence(envfinal, envfix; atol=1e-6) - -Check if the element-wise difference of the corner and edge tensors of the final and fixed -CTMRG environments are below some tolerance. -""" -function check_elementwise_convergence( - envfinal::CTMRGEnv, envfix::CTMRGEnv; atol::Real=1e-6 -) - ΔC = envfinal.corners .- envfix.corners - ΔCmax = norm(ΔC, Inf) - ΔCmean = norm(ΔC) - @debug "maxᵢⱼ|Cⁿ⁺¹ - Cⁿ|ᵢⱼ = $ΔCmax mean |Cⁿ⁺¹ - Cⁿ|ᵢⱼ = $ΔCmean" - - ΔT = envfinal.edges .- envfix.edges - ΔTmax = norm(ΔT, Inf) - ΔTmean = norm(ΔT) - @debug "maxᵢⱼ|Tⁿ⁺¹ - Tⁿ|ᵢⱼ = $ΔTmax mean |Tⁿ⁺¹ - Tⁿ|ᵢⱼ = $ΔTmean" - - # Check differences for all tensors in unit cell to debug properly - for (dir, r, c) in Iterators.product(axes(envfinal.edges)...) - @debug( - "$((dir, r, c)): all |Cⁿ⁺¹ - Cⁿ|ᵢⱼ < ϵ: ", - all(x -> abs(x) < atol, convert(Array, ΔC[dir, r, c])), - ) - @debug( - "$((dir, r, c)): all |Tⁿ⁺¹ - Tⁿ|ᵢⱼ < ϵ: ", - all(x -> abs(x) < atol, convert(Array, ΔT[dir, r, c])), - ) - end - - return isapprox(ΔCmax, 0; atol) && isapprox(ΔTmax, 0; atol) -end - -@non_differentiable check_elementwise_convergence(args...) """ ctmrg_iter(state, env::CTMRGEnv{C,T}, alg::CTMRG) where {C,T} @@ -524,3 +345,44 @@ function LinearAlgebra.norm(peps::InfinitePEPS, env::CTMRGEnv) return total end + +""" + correlation_length(peps::InfinitePEPS, env::CTMRGEnv; howmany=2) + +Compute the PEPS correlation length based on the horizontal and vertical +transfer matrices. Additionally the (normalized) eigenvalue spectrum is +returned. Specify the number of computed eigenvalues with `howmany`. +""" +function correlation_length(peps::InfinitePEPS, env::CTMRGEnv; howmany=2) + ξ = Array{Float64,3}(undef, (2, size(peps)...)) # First index picks horizontal or vertical direction + λ = Array{ComplexF64,4}(undef, (2, howmany, size(peps)...)) + for r in 1:size(peps, 1), c in 1:size(peps, 2) + @tensor transferh[-1 -2 -3 -4; -5 -6 -7 -8] := + env.edges[NORTH, _prev(r, end), c][-1 1 2; -5] * + peps[r, c][5; 1 -6 3 -2] * + conj(peps[r, c][5; 2 -7 4 -3]) * + env.edges[SOUTH, _next(r, end), c][-8 3 4; -4] + @tensor transferv[-1 -2 -3 -4; -5 -6 -7 -8] := + env.edges[EAST, r, _next(c, end)][-5 1 2; -1] * + peps[r, c][5; -6 1 -2 3] * + conj(peps[r, c][5; -7 2 -3 4]) * + env.edges[WEST, r, _prev(c, end)][-4 3 4; -8] + + function lintransfer(v, t) + @tensor v′[-1 -2 -3 -4] := t[-1 -2 -3 -4; 1 2 3 4] * v[1 2 3 4] + return v′ + end + + v₀h = Tensor(randn, scalartype(transferh), domain(transferh)) + valsh, = eigsolve(v -> lintransfer(v, transferh), v₀h, howmany, :LM) + λ[1, :, r, c] = valsh[1:howmany] / abs(valsh[1]) # Normalize largest eigenvalue to 1 + ξ[1, r, c] = -1 / log(abs(λ[1, 2, r, c])) + + v₀v = Tensor(rand, scalartype(transferv), domain(transferv)) + valsv, = eigsolve(v -> lintransfer(v, transferv), v₀v, howmany, :LM) + λ[2, :, r, c] = valsv[1:howmany] / abs(valsv[1]) # Normalize largest eigenvalue to 1 + ξ[2, r, c] = -1 / log(abs(λ[2, 2, r, c])) + end + + return ξ, λ +end diff --git a/src/algorithms/ctmrg_all_sides.jl b/src/algorithms/ctmrg_all_sides.jl new file mode 100644 index 00000000..e69de29b diff --git a/src/algorithms/ctmrg_gauge_fix.jl b/src/algorithms/ctmrg_gauge_fix.jl new file mode 100644 index 00000000..723fbb21 --- /dev/null +++ b/src/algorithms/ctmrg_gauge_fix.jl @@ -0,0 +1,179 @@ +""" + gauge_fix(envprev::CTMRGEnv{C,T}, envfinal::CTMRGEnv{C,T}) where {C,T} + +Fix the gauge of `envfinal` based on the previous environment `envprev`. +This assumes that the `envfinal` is the result of one CTMRG iteration on `envprev`. +Given that the CTMRG run is converged, the returned environment will be +element-wise converged to `envprev`. +""" +function gauge_fix(envprev::CTMRGEnv{C,T}, envfinal::CTMRGEnv{C,T}) where {C,T} + # Check if spaces in envprev and envfinal are the same + same_spaces = map(Iterators.product(axes(envfinal.edges)...)) do (dir, r, c) + space(envfinal.edges[dir, r, c]) == space(envprev.edges[dir, r, c]) && + space(envfinal.corners[dir, r, c]) == space(envprev.corners[dir, r, c]) + end + @assert all(same_spaces) "Spaces of envprev and envfinal are not the same" + + # Try the "general" algorithm from https://arxiv.org/abs/2311.11894 + signs = map(Iterators.product(axes(envfinal.edges)...)) do (dir, r, c) + # Gather edge tensors and pretend they're InfiniteMPSs + if dir == NORTH + Tsprev = circshift(envprev.edges[dir, r, :], 1 - c) + Tsfinal = circshift(envfinal.edges[dir, r, :], 1 - c) + elseif dir == EAST + Tsprev = circshift(envprev.edges[dir, :, c], 1 - r) + Tsfinal = circshift(envfinal.edges[dir, :, c], 1 - r) + elseif dir == SOUTH + Tsprev = circshift(reverse(envprev.edges[dir, r, :]), c) + Tsfinal = circshift(reverse(envfinal.edges[dir, r, :]), c) + elseif dir == WEST + Tsprev = circshift(reverse(envprev.edges[dir, :, c]), r) + Tsfinal = circshift(reverse(envfinal.edges[dir, :, c]), r) + end + + # Random MPS of same bond dimension + M = map(Tsfinal) do t + TensorMap(randn, scalartype(t), codomain(t) ← domain(t)) + end + + # Find right fixed points of mixed transfer matrices + ρinit = TensorMap( + randn, + scalartype(T), + MPSKit._lastspace(Tsfinal[end])' ← MPSKit._lastspace(M[end])', + ) + ρprev = transfermatrix_fixedpoint(Tsprev, M, ρinit) + ρfinal = transfermatrix_fixedpoint(Tsfinal, M, ρinit) + + # Decompose and multiply + Qprev, = leftorth(ρprev) + Qfinal, = leftorth(ρfinal) + + return Qprev * Qfinal' + end + + cornersfix, edgesfix = fix_relative_phases(envfinal, signs) + + # Fix global phase + cornersgfix = map(envprev.corners, cornersfix) do Cprev, Cfix + return dot(Cfix, Cprev) * Cfix + end + edgesgfix = map(envprev.edges, edgesfix) do Tprev, Tfix + return dot(Tfix, Tprev) * Tfix + end + return CTMRGEnv(cornersgfix, edgesgfix) +end + +# this is a bit of a hack to get the fixed point of the mixed transfer matrix +# because MPSKit is not compatible with AD +function transfermatrix_fixedpoint(tops, bottoms, ρinit) + _, vecs, info = eigsolve(ρinit, 1, :LM, Arnoldi()) do ρ + return foldr(zip(tops, bottoms); init=ρ) do (top, bottom), ρ + return @tensor ρ′[-1; -2] := top[-1 4 3; 1] * conj(bottom[-2 4 3; 2]) * ρ[1; 2] + end + end + info.converged > 0 || @warn "eigsolve did not converge" + return first(vecs) +end + +# Explicit fixing of relative phases (doing this compactly in a loop is annoying) +function _contract_gauge_corner(corner, σ_in, σ_out) + @autoopt @tensor corner_fix[χ_in; χ_out] := + σ_in[χ_in; χ1] * corner[χ1; χ2] * conj(σ_out[χ_out; χ2]) +end +function _contract_gauge_edge(edge, σ_in, σ_out) + @autoopt @tensor edge_fix[χ_in D_above D_below; χ_out] := + σ_in[χ_in; χ1] * edge[χ1 D_above D_below; χ2] * conj(σ_out[χ_out; χ2]) +end +function fix_relative_phases(envfinal::CTMRGEnv, signs) + C1 = map(Iterators.product(axes(envfinal.corners)[2:3]...)) do (r, c) + _contract_gauge_corner( + envfinal.corners[NORTHWEST, r, c], + signs[WEST, r, c], + signs[NORTH, r, _next(c, end)], + ) + end + T1 = map(Iterators.product(axes(envfinal.edges)[2:3]...)) do (r, c) + _contract_gauge_edge( + envfinal.edges[NORTH, r, c], + signs[NORTH, r, c], + signs[NORTH, r, _next(c, end)], + ) + end + C2 = map(Iterators.product(axes(envfinal.corners)[2:3]...)) do (r, c) + _contract_gauge_corner( + envfinal.corners[NORTHEAST, r, c], + signs[NORTH, r, c], + signs[EAST, _next(r, end), c], + ) + end + T2 = map(Iterators.product(axes(envfinal.edges)[2:3]...)) do (r, c) + _contract_gauge_edge( + envfinal.edges[EAST, r, c], signs[EAST, r, c], signs[EAST, _next(r, end), c] + ) + end + C3 = map(Iterators.product(axes(envfinal.corners)[2:3]...)) do (r, c) + _contract_gauge_corner( + envfinal.corners[SOUTHEAST, r, c], + signs[EAST, r, c], + signs[SOUTH, r, _prev(c, end)], + ) + end + T3 = map(Iterators.product(axes(envfinal.edges)[2:3]...)) do (r, c) + _contract_gauge_edge( + envfinal.edges[SOUTH, r, c], + signs[SOUTH, r, c], + signs[SOUTH, r, _prev(c, end)], + ) + end + C4 = map(Iterators.product(axes(envfinal.corners)[2:3]...)) do (r, c) + _contract_gauge_corner( + envfinal.corners[SOUTHWEST, r, c], + signs[SOUTH, r, c], + signs[WEST, _prev(r, end), c], + ) + end + T4 = map(Iterators.product(axes(envfinal.edges)[2:3]...)) do (r, c) + _contract_gauge_edge( + envfinal.edges[WEST, r, c], signs[WEST, r, c], signs[WEST, _prev(r, end), c] + ) + end + + return stack([C1, C2, C3, C4]; dims=1), stack([T1, T2, T3, T4]; dims=1) +end + +""" + check_elementwise_convergence(envfinal, envfix; atol=1e-6) + +Check if the element-wise difference of the corner and edge tensors of the final and fixed +CTMRG environments are below some tolerance. +""" +function check_elementwise_convergence( + envfinal::CTMRGEnv, envfix::CTMRGEnv; atol::Real=1e-6 +) + ΔC = envfinal.corners .- envfix.corners + ΔCmax = norm(ΔC, Inf) + ΔCmean = norm(ΔC) + @debug "maxᵢⱼ|Cⁿ⁺¹ - Cⁿ|ᵢⱼ = $ΔCmax mean |Cⁿ⁺¹ - Cⁿ|ᵢⱼ = $ΔCmean" + + ΔT = envfinal.edges .- envfix.edges + ΔTmax = norm(ΔT, Inf) + ΔTmean = norm(ΔT) + @debug "maxᵢⱼ|Tⁿ⁺¹ - Tⁿ|ᵢⱼ = $ΔTmax mean |Tⁿ⁺¹ - Tⁿ|ᵢⱼ = $ΔTmean" + + # Check differences for all tensors in unit cell to debug properly + for (dir, r, c) in Iterators.product(axes(envfinal.edges)...) + @debug( + "$((dir, r, c)): all |Cⁿ⁺¹ - Cⁿ|ᵢⱼ < ϵ: ", + all(x -> abs(x) < atol, convert(Array, ΔC[dir, r, c])), + ) + @debug( + "$((dir, r, c)): all |Tⁿ⁺¹ - Tⁿ|ᵢⱼ < ϵ: ", + all(x -> abs(x) < atol, convert(Array, ΔT[dir, r, c])), + ) + end + + return isapprox(ΔCmax, 0; atol) && isapprox(ΔTmax, 0; atol) +end + +@non_differentiable check_elementwise_convergence(args...) \ No newline at end of file From 6afda3410e7302521634f7e8fce037e048146429 Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Tue, 9 Jul 2024 18:51:22 +0200 Subject: [PATCH 026/213] Add AllSides scheme --- src/algorithms/ctmrg.jl | 20 ++-- src/algorithms/ctmrg_all_sides.jl | 192 ++++++++++++++++++++++++++++++ src/utility/svd.jl | 6 +- 3 files changed, 204 insertions(+), 14 deletions(-) diff --git a/src/algorithms/ctmrg.jl b/src/algorithms/ctmrg.jl index 7a0f4e83..b243aebd 100644 --- a/src/algorithms/ctmrg.jl +++ b/src/algorithms/ctmrg.jl @@ -28,7 +28,8 @@ end """ CTMRG(; tol=Defaults.ctmrg_tol, maxiter=Defaults.ctmrg_maxiter, miniter=Defaults.ctmrg_miniter, verbosity=0, - svd_alg=TensorKit.SVD(), trscheme=FixedSpaceTruncation()) + svd_alg=TensorKit.SVD(), trscheme=FixedSpaceTruncation(), + ctmrgscheme=:AllSides) Algorithm struct that represents the CTMRG algorithm for contracting infinite PEPS. Each CTMRG run is converged up to `tol` where the singular value convergence of the @@ -37,7 +38,7 @@ is set with `maxiter` and `miniter`. Different levels of output information are depending on `verbosity` (0, 1 or 2). The projectors are computed from `svd_alg` SVDs where the truncation scheme is set via `trscheme`. """ -struct CTMRG +struct CTMRG{S} tol::Float64 maxiter::Int miniter::Int @@ -51,8 +52,9 @@ function CTMRG(; verbosity=0, svd_alg=SVDAdjoint(), trscheme=FixedSpaceTruncation(), + ctmrgscheme=:AllSides ) - return CTMRG( + return CTMRG{ctmrgscheme}( tol, maxiter, miniter, verbosity, ProjectorAlg(; svd_alg, trscheme, verbosity) ) end @@ -74,11 +76,11 @@ function MPSKit.leading_boundary(envinit, state, alg::CTMRG) env = deepcopy(envinit) for i in 1:(alg.maxiter) - env, ϵ = ctmrg_iter(state, env, alg) # Grow and renormalize in all 4 directions + env, info = ctmrg_iter(state, env, alg) # Grow and renormalize in all 4 directions conv_condition, normold, CSold, TSold, ϵ = ignore_derivatives() do # Compute convergence criteria and take max (TODO: How should we handle logging all of this?) - Δϵ = abs((ϵold - ϵ) / ϵold) + Δϵ = abs((ϵold - info.ϵ) / ϵold) normnew = norm(state, env) Δnorm = abs(normold - normnew) / abs(normold) CSnew = map(c -> tsvd(c; alg=TensorKit.SVD())[2], env.corners) @@ -108,7 +110,7 @@ function MPSKit.leading_boundary(envinit, state, alg::CTMRG) Δnorm, ΔCS, ΔTS, - ϵ, + info.ϵ, Δϵ ) end @@ -117,7 +119,7 @@ function MPSKit.leading_boundary(envinit, state, alg::CTMRG) @warn( "CTMRG reached maximal number of iterations at (Δnorm=$Δnorm, ΔCS=$ΔCS, ΔTS=$ΔTS)" ) - return conv_condition, normnew, CSnew, TSnew, ϵ + return conv_condition, normnew, CSnew, TSnew, info.ϵ end conv_condition && break # Converge if maximal Δ falls below tolerance end @@ -150,7 +152,7 @@ function ctmrg_iter(state, env::CTMRGEnv{C,T}, alg::CTMRG) where {C,T} ϵ = max(ϵ, ϵ₀) end - return env, ϵ + return env, (; ϵ) end """ @@ -235,7 +237,7 @@ function left_move(state, env::CTMRGEnv{C,T}, alg::ProjectorAlg) where {C,T} end end - return CTMRGEnv(corners, edges), copy(Pleft), copy(Pright), ϵ + return CTMRGEnv(corners, edges), (; Pleft=copy(Pleft), Pright=copy(Pright), ϵ) end # Compute enlarged corners diff --git a/src/algorithms/ctmrg_all_sides.jl b/src/algorithms/ctmrg_all_sides.jl index e69de29b..f18d2044 100644 --- a/src/algorithms/ctmrg_all_sides.jl +++ b/src/algorithms/ctmrg_all_sides.jl @@ -0,0 +1,192 @@ +# One CTMRG iteration with both-sided application of projectors +function ctmrg_iter(state, env::CTMRGEnv, alg::CTMRG{:AllSides}) + # Compute enlarged corners + Q = enlarge_corners_edges(state, env) + + # Compute projectors if none are supplied + Pleft, Pright, info = build_projectors(Q, env, alg) + + # Apply projectors and normalize + corners, edges = renormalize_corners_edges(state, env, Q, Pleft, Pright) + + return CTMRGEnv(corners, edges), (; Pleft, Pright, info...) +end + +# Compute enlarged corners and edges for all directions and unit cell entries +function enlarge_corners_edges(state, env::CTMRGEnv) + map(Iterators.product(axes(env.corners)...)) do (dir, r, c) + rprev = _prev(r, size(state, 1)) + rnext = _next(r, size(state, 1)) + cprev = _prev(c, size(state, 2)) + cnext = _next(c, size(state, 2)) + if dir == NORTHWEST + return northwest_corner( + env.edges[WEST, r, cprev], + env.corners[NORTHWEST, rprev, cprev], + env.edges[NORTH, rprev, c], + state[r, c], + ) + elseif dir == NORTHEAST + return northeast_corner( + env.edges[NORTH, rprev, c], + env.corners[NORTHEAST, rprev, cnext], + env.edges[EAST, r, cnext], + state[r, c], + ) + elseif dir == SOUTHEAST + return southeast_corner( + env.edges[EAST, r, cnext], + env.corners[SOUTHEAST, rnext, cnext], + env.edges[SOUTH, rnext, c], + state[r, c], + ) + elseif dir == SOUTHWEST + return southwest_corner( + env.edges[SOUTH, rnext, c], + env.corners[SOUTHWEST, rnext, cprev], + env.edges[WEST, r, cprev], + state[r, c], + ) + end + end +end + +# Build projectors from SVD and enlarged corners +function build_projectors(Q, env::CTMRGEnv, alg::ProjectorAlg) # TODO: Add projector type annotations + Pleft, Pright = Zygote.Buffer.(projector_type(env.edges)) + U, V = Zygote.Buffer.(projector_type(env.edges)) + S = Zygote.Buffer(env.corners) + ϵ1, ϵ2, ϵ3, ϵ4 = 0, 0, 0, 0 + for c in 1:size(env.corners, 3), r in 1:size(env.corners, 2) + Pl1, Pr1, info1 = build_projectors( + Q[1, r, c], + Q[2, r, _next(c, end)], + alg; + trunc=truncspace(space(env.edges[1, r, c], 1)), + envindex=(1, r, c), + ) + Pl2, Pr2, info2 = build_projectors( + Q[2, r, c], + Q[3, _next(r, end), c], + alg; + trunc=truncspace(space(env.edges[2, r, c], 1)), + envindex=(2, r, c), + ) + Pl3, Pr3, info3 = build_projectors( + Q[3, r, c], + Q[4, r, _prev(c, end)], + alg; + trunc=truncspace(space(env.edges[3, r, c], 1)), + envindex=(3, r, c), + ) + Pl4, Pr4, info4 = build_projectors( + Q[4, r, c], + Q[1, _prev(r, end), c], + alg; + trunc=truncspace(space(env.edges[4, r, c], 1)), + envindex=(4, r, c), + ) + + Pleft[NORTH, r, c] = Pl1 + Pright[NORTH, r, c] = Pr1 + U[NORTH, r, c] = info1.U + S[NORTH, r, c] = info1.S + V[NORTH, r, c] = info1.V + + Pleft[EAST, r, c] = Pl2 + Pright[EAST, r, c] = Pr2 + U[EAST, r, c] = info2.U + S[EAST, r, c] = info2.S + V[EAST, r, c] = info2.V + + Pleft[SOUTH, r, c] = Pl3 + Pright[SOUTH, r, c] = Pr3 + U[SOUTH, r, c] = info3.U + S[SOUTH, r, c] = info3.S + V[SOUTH, r, c] = info3.V + + Pleft[WEST, r, c] = Pl4 + Pright[WEST, r, c] = Pr4 + U[WEST, r, c] = info4.U + S[WEST, r, c] = info4.S + V[WEST, r, c] = info4.V + end + return copy(Pleft), copy(Pright), (; ϵ=max(ϵ1, ϵ2, ϵ3, ϵ4), U=copy(U), S=copy(S), V=copy(V)) +end +function build_projectors(Qleft, Qright, alg::ProjectorAlg; kwargs...) + # SVD half-infinite environment + U, S, V, ϵ = PEPSKit.tsvd!(Qleft * Qright, alg.svd_alg; kwargs...) + ϵ /= norm(S) + + # Compute SVD truncation error and check for degenerate singular values + ignore_derivatives() do + if alg.verbosity > 1 && is_degenerate_spectrum(S) + svals = TensorKit.SectorDict(c => diag(b) for (c, b) in blocks(S)) + @warn("degenerate singular values detected: ", svals) + end + end + + # Compute projectors + isqS = sdiag_inv_sqrt(S) + @tensor Pl[-1 -2 -3; -4] := Qright[-1 -2 -3; 1 2 3] * conj(V[4; 1 2 3]) * isqS[4; -4] + @tensor Pr[-1; -2 -3 -4] := isqS[-1; 1] * conj(U[2 3 4; 1]) * Qleft[2 3 4; -2 -3 -4] + + return Pl, Pr, (; ϵ, U, S, V) +end + +# Apply projectors to renormalize corners and edges +function renormalize_corners_edges(state, env::CTMRGEnv, Q, Pleft, Pright) + corners::typeof(env.corners) = copy(env.corners) + edges::typeof(env.edges) = copy(env.edges) + for c in 1:size(state, 2), r in 1:size(state, 1) + rprev = _prev(r, size(state, 1)) + rnext = _next(r, size(state, 1)) + cprev = _prev(c, size(state, 2)) + cnext = _next(c, size(state, 2)) + @diffset @tensor corners[NORTHWEST, r, c][-1; -2] := + Pright[WEST, rnext, c][-1; 1 2 3] * + Q[NORTHWEST, r, c][1 2 3; 4 5 6] * + Pleft[NORTH, r, c][4 5 6; -2] + @diffset @tensor corners[NORTHEAST, r, c][-1; -2] := + Pright[NORTH, r, cprev][-1; 1 2 3] * + Q[NORTHEAST, r, c][1 2 3; 4 5 6] * + Pleft[EAST, r, c][4 5 6; -2] + @diffset @tensor corners[SOUTHEAST, r, c][-1; -2] := + Pright[EAST, rprev, c][-1; 1 2 3] * + Q[SOUTHEAST, r, c][1 2 3; 4 5 6] * + Pleft[SOUTH, r, c][4 5 6; -2] + @diffset @tensor corners[SOUTHWEST, r, c][-1; -2] := + Pright[SOUTH, r, cnext][-1; 1 2 3] * + Q[SOUTHWEST, r, c][1 2 3; 4 5 6] * + Pleft[WEST, r, c][4 5 6; -2] + + @diffset @tensor edges[NORTH, r, c][-1 -2 -3; -4] := + env.edges[NORTH, rprev, c][1 2 3; 4] * + state[r, c][9; 2 5 -2 7] * + conj(state[r, c][9; 3 6 -3 8]) * + Pleft[NORTH, r, c][4 5 6; -4] * + Pright[NORTH, r, cprev][-1; 1 7 8] + @diffset @tensor edges[EAST, r, c][-1 -2 -3; -4] := + env.edges[EAST, r, _next(c, end)][1 2 3; 4] * + state[r, c][9; 7 2 5 -2] * + conj(state[r, c][9; 8 3 6 -3]) * + Pleft[EAST, r, c][4 5 6; -4] * + Pright[EAST, rprev, c][-1; 1 7 8] + @diffset @tensor edges[SOUTH, r, c][-1 -2 -3; -4] := + env.edges[SOUTH, _next(r, end), c][1 2 3; 4] * + state[r, c][9; -2 7 2 5] * + conj(state[r, c][9; -3 8 3 6]) * + Pleft[SOUTH, r, c][4 5 6; -4] * + Pright[SOUTH, r, cnext][-1; 1 7 8] + @diffset @tensor edges[WEST, r, c][-1 -2 -3; -4] := + env.edges[WEST, r, _prev(c, end)][1 2 3; 4] * + state[r, c][9; 5 -2 7 2] * + conj(state[r, c][9; 6 -3 8 3]) * + Pleft[WEST, r, c][4 5 6; -4] * + Pright[WEST, rnext, c][-1; 1 7 8] + end + + @diffset corners[:, :, :] ./= norm.(corners[:, :, :]) + @diffset edges[:, :, :] ./= norm.(edges[:, :, :]) + return corners, edges +end diff --git a/src/utility/svd.jl b/src/utility/svd.jl index c76ddb9f..3c0afdaa 100644 --- a/src/utility/svd.jl +++ b/src/utility/svd.jl @@ -8,11 +8,7 @@ using TensorKit: TruncationSpace # Enables backwards compatibility without package extensions -CRCExt = @static if isdefined(Base, :get_extension) - Base.get_extension(KrylovKit, :KrylovKitChainRulesCoreExt) -else - KrylovKit.KrylovKitChainRulesCoreExt -end +CRCExt = Base.get_extension(KrylovKit, :KrylovKitChainRulesCoreExt) """ struct SVDAdjoint(; fwd_alg = TensorKit.SVD(), rrule_alg = nothing, From 14f8ac4f8fe81377c9a3be39f9b276a25102d6e6 Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Tue, 9 Jul 2024 19:01:48 +0200 Subject: [PATCH 027/213] Adjust gauge fixing --- src/algorithms/ctmrg.jl | 2 +- src/algorithms/ctmrg_gauge_fix.jl | 56 +++++++++++++++++++++++++------ 2 files changed, 47 insertions(+), 11 deletions(-) diff --git a/src/algorithms/ctmrg.jl b/src/algorithms/ctmrg.jl index b243aebd..331c7415 100644 --- a/src/algorithms/ctmrg.jl +++ b/src/algorithms/ctmrg.jl @@ -127,7 +127,7 @@ function MPSKit.leading_boundary(envinit, state, alg::CTMRG) # Do one final iteration that does not change the spaces alg_fixed = @set alg.projector_alg.trscheme = FixedSpaceTruncation() env′, = ctmrg_iter(state, env, alg_fixed) - envfix = gauge_fix(env, env′) + envfix, = gauge_fix(env, env′) check_elementwise_convergence(env, envfix; atol=alg.tol^(1 / 2)) || @warn "CTMRG did not converge elementwise." return envfix diff --git a/src/algorithms/ctmrg_gauge_fix.jl b/src/algorithms/ctmrg_gauge_fix.jl index 723fbb21..5663bdc2 100644 --- a/src/algorithms/ctmrg_gauge_fix.jl +++ b/src/algorithms/ctmrg_gauge_fix.jl @@ -14,7 +14,7 @@ function gauge_fix(envprev::CTMRGEnv{C,T}, envfinal::CTMRGEnv{C,T}) where {C,T} end @assert all(same_spaces) "Spaces of envprev and envfinal are not the same" - # Try the "general" algorithm from https://arxiv.org/abs/2311.11894 + # "general" algorithm from https://arxiv.org/abs/2311.11894 signs = map(Iterators.product(axes(envfinal.edges)...)) do (dir, r, c) # Gather edge tensors and pretend they're InfiniteMPSs if dir == NORTH @@ -53,15 +53,7 @@ function gauge_fix(envprev::CTMRGEnv{C,T}, envfinal::CTMRGEnv{C,T}) where {C,T} end cornersfix, edgesfix = fix_relative_phases(envfinal, signs) - - # Fix global phase - cornersgfix = map(envprev.corners, cornersfix) do Cprev, Cfix - return dot(Cfix, Cprev) * Cfix - end - edgesgfix = map(envprev.edges, edgesfix) do Tprev, Tfix - return dot(Tfix, Tprev) * Tfix - end - return CTMRGEnv(cornersgfix, edgesgfix) + return fix_global_phases(envfinal, CTMRGEnv(cornersfix, edgesfix)), signs end # this is a bit of a hack to get the fixed point of the mixed transfer matrix @@ -141,6 +133,50 @@ function fix_relative_phases(envfinal::CTMRGEnv, signs) return stack([C1, C2, C3, C4]; dims=1), stack([T1, T2, T3, T4]; dims=1) end +function fix_relative_phases( + U::Array{<:AbstractTensorMap,3}, V::Array{<:AbstractTensorMap,3}, signs +) + U1 = map(Iterators.product(axes(U)[2:3]...)) do (r, c) + return U[NORTH, r, c] * signs[NORTH, r, _next(c, end)] + end + V1 = map(Iterators.product(axes(V)[2:3]...)) do (r, c) + return signs[NORTH, r, _next(c, end)]' * V[NORTH, r, c] + end + U2 = map(Iterators.product(axes(U)[2:3]...)) do (r, c) + return U[EAST, r, c] * signs[EAST, _next(r, end), c] + end + V2 = map(Iterators.product(axes(V)[2:3]...)) do (r, c) + return signs[EAST, _next(r, end), c]' * V[EAST, r, c] + end + U3 = map(Iterators.product(axes(U)[2:3]...)) do (r, c) + return U[SOUTH, r, c] * signs[SOUTH, r, _prev(c, end)] + end + V3 = map(Iterators.product(axes(V)[2:3]...)) do (r, c) + return signs[SOUTH, r, _prev(c, end)]' * V[SOUTH, r, c] + end + U4 = map(Iterators.product(axes(U)[2:3]...)) do (r, c) + return U[WEST, r, c] * signs[WEST, _prev(r, end), c] + end + V4 = map(Iterators.product(axes(V)[2:3]...)) do (r, c) + return signs[WEST, _prev(r, end), c]' * V[WEST, r, c] + end + + return stack([U1, U2, U3, U4]; dims=1), stack([V1, V2, V3, V4]; dims=1) +end + +# Fix global phases of corners and edges via dot product (to ensure compatibility with symm. tensors) +function fix_global_phases(envprev::CTMRGEnv, envfix::CTMRGEnv) + cornersgfix = map(zip(envprev.corners, envfix.corners)) do (Cprev, Cfix) + φ = dot(Cprev, Cfix) + φ' * Cfix + end + edgesgfix = map(zip(envprev.edges, envfix.edges)) do (Tprev, Tfix) + φ = dot(Tprev, Tfix) + φ' * Tfix + end + return CTMRGEnv(cornersgfix, edgesgfix) +end + """ check_elementwise_convergence(envfinal, envfix; atol=1e-6) From 8fe91832f4096f438c77ccf8d4950f59807253de Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Wed, 10 Jul 2024 11:35:04 +0200 Subject: [PATCH 028/213] Add FixedSVD, refactor build_projectors, improve SVD passing --- src/algorithms/ctmrg.jl | 28 +++-- src/algorithms/ctmrg_all_sides.jl | 167 +++++++++++++----------------- src/utility/svd.jl | 18 ++++ src/utility/util.jl | 14 ++- 4 files changed, 116 insertions(+), 111 deletions(-) diff --git a/src/algorithms/ctmrg.jl b/src/algorithms/ctmrg.jl index 331c7415..8306f3c8 100644 --- a/src/algorithms/ctmrg.jl +++ b/src/algorithms/ctmrg.jl @@ -52,7 +52,7 @@ function CTMRG(; verbosity=0, svd_alg=SVDAdjoint(), trscheme=FixedSpaceTruncation(), - ctmrgscheme=:AllSides + ctmrgscheme=:AllSides, ) return CTMRG{ctmrgscheme}( tol, maxiter, miniter, verbosity, ProjectorAlg(; svd_alg, trscheme, verbosity) @@ -133,7 +133,6 @@ function MPSKit.leading_boundary(envinit, state, alg::CTMRG) return envfix end - """ ctmrg_iter(state, env::CTMRGEnv{C,T}, alg::CTMRG) where {C,T} @@ -165,7 +164,7 @@ function left_move(state, env::CTMRGEnv{C,T}, alg::ProjectorAlg) where {C,T} corners::typeof(env.corners) = copy(env.corners) edges::typeof(env.edges) = copy(env.edges) ϵ = 0.0 - Pleft, Pright = Zygote.Buffer.(projector_type(T, size(state))) # Use Zygote.Buffer instead of @diffset to avoid ZeroTangent errors in _setindex + P_top, P_bottom = Zygote.Buffer.(projector_type(T, size(state))) # Use Zygote.Buffer instead of @diffset to avoid ZeroTangent errors in _setindex for col in 1:size(state, 2) cprev = _prev(col, size(state, 2)) @@ -192,11 +191,10 @@ function left_move(state, env::CTMRGEnv{C,T}, alg::ProjectorAlg) where {C,T} # SVD half-infinite environment trscheme = if alg.trscheme isa FixedSpaceTruncation - truncspace(space(env.edges[WEST, row, cnext], 1)) + truncspace(space(env.edges[WEST, row, col], 1)) else alg.trscheme end - @tensor QQ[-1 -2 -3; -4 -5 -6] := Q_sw[-1 -2 -3; 1 2 3] * Q_nw[1 2 3; -4 -5 -6] @autoopt @tensor QQ[χ_EB D_EBabove D_EBbelow; χ_ET D_ETabove D_ETbelow] := Q_sw[χ_EB D_EBabove D_EBbelow; χ D1 D2] * Q_nw[χ D1 D2; χ_ET D_ETabove D_ETbelow] @@ -213,9 +211,9 @@ function left_move(state, env::CTMRGEnv{C,T}, alg::ProjectorAlg) where {C,T} end # Compute projectors - Pl, Pr = build_projectors(U, S, V, Q_sw, Q_nw) - Pleft[row, col] = Pl - Pright[row, col] = Pr + Pt, Pb = build_projectors(U, S, V, Q_sw, Q_nw) + P_top[row, col] = Pt + P_bottom[row, col] = Pb end # Use projectors to grow the corners & edges @@ -223,8 +221,8 @@ function left_move(state, env::CTMRGEnv{C,T}, alg::ProjectorAlg) where {C,T} rprev = _prev(row, size(state, 1)) C_sw, C_nw, T_w = grow_env_left( state[row, col], - Pleft[rprev, col], - Pright[row, col], + P_left[rprev, col], + P_right[row, col], env.corners[SOUTHWEST, row, cprev], env.corners[NORTHWEST, row, cprev], env.edges[SOUTH, row, col], @@ -237,7 +235,7 @@ function left_move(state, env::CTMRGEnv{C,T}, alg::ProjectorAlg) where {C,T} end end - return CTMRGEnv(corners, edges), (; Pleft=copy(Pleft), Pright=copy(Pright), ϵ) + return CTMRGEnv(corners, edges), (; P_left=copy(P_top), P_right=copy(P_bottom), ϵ) end # Compute enlarged corners @@ -276,12 +274,12 @@ end # Build projectors from SVD and enlarged SW & NW corners function build_projectors( - U::AbstractTensorMap{E,3,1}, S, V::AbstractTensorMap{E,1,3}, Q_SW, Q_NW + U::AbstractTensorMap{E,3,1}, S, V::AbstractTensorMap{E,1,3}, Q, Q_next ) where {E<:ElementarySpace} isqS = sdiag_inv_sqrt(S) - P_bottom = Q_NW * V' * isqS - P_top = isqS * U' * Q_SW - return P_bottom, P_top + P_left = Q_next * V' * isqS + P_right = isqS * U' * Q + return P_left, P_right end # Apply projectors to entire left half-environment to grow SW & NW corners, and W edge diff --git a/src/algorithms/ctmrg_all_sides.jl b/src/algorithms/ctmrg_all_sides.jl index f18d2044..636a8322 100644 --- a/src/algorithms/ctmrg_all_sides.jl +++ b/src/algorithms/ctmrg_all_sides.jl @@ -1,15 +1,20 @@ + +struct IndexSelector{N} <: TensorKit.TruncationScheme + index::NTuple{N,Int} +end + # One CTMRG iteration with both-sided application of projectors function ctmrg_iter(state, env::CTMRGEnv, alg::CTMRG{:AllSides}) # Compute enlarged corners Q = enlarge_corners_edges(state, env) # Compute projectors if none are supplied - Pleft, Pright, info = build_projectors(Q, env, alg) + P_left, P_right, info = build_projectors(Q, env, alg) # Apply projectors and normalize - corners, edges = renormalize_corners_edges(state, env, Q, Pleft, Pright) + corners, edges = renormalize_corners_edges(state, env, Q, P_left, P_right) - return CTMRGEnv(corners, edges), (; Pleft, Pright, info...) + return CTMRGEnv(corners, edges), (; P_left, P_right, info...) end # Compute enlarged corners and edges for all directions and unit cell entries @@ -52,90 +57,66 @@ function enlarge_corners_edges(state, env::CTMRGEnv) end # Build projectors from SVD and enlarged corners -function build_projectors(Q, env::CTMRGEnv, alg::ProjectorAlg) # TODO: Add projector type annotations - Pleft, Pright = Zygote.Buffer.(projector_type(env.edges)) +function build_projectors(Q, env::CTMRGEnv, alg::ProjectorAlg{A,T}) where {A,T} # TODO: Add projector type annotations + P_left, P_right = Zygote.Buffer.(projector_type(env.edges)) U, V = Zygote.Buffer.(projector_type(env.edges)) S = Zygote.Buffer(env.corners) - ϵ1, ϵ2, ϵ3, ϵ4 = 0, 0, 0, 0 - for c in 1:size(env.corners, 3), r in 1:size(env.corners, 2) - Pl1, Pr1, info1 = build_projectors( - Q[1, r, c], - Q[2, r, _next(c, end)], - alg; - trunc=truncspace(space(env.edges[1, r, c], 1)), - envindex=(1, r, c), - ) - Pl2, Pr2, info2 = build_projectors( - Q[2, r, c], - Q[3, _next(r, end), c], - alg; - trunc=truncspace(space(env.edges[2, r, c], 1)), - envindex=(2, r, c), - ) - Pl3, Pr3, info3 = build_projectors( - Q[3, r, c], - Q[4, r, _prev(c, end)], - alg; - trunc=truncspace(space(env.edges[3, r, c], 1)), - envindex=(3, r, c), - ) - Pl4, Pr4, info4 = build_projectors( - Q[4, r, c], - Q[1, _prev(r, end), c], - alg; - trunc=truncspace(space(env.edges[4, r, c], 1)), - envindex=(4, r, c), - ) - - Pleft[NORTH, r, c] = Pl1 - Pright[NORTH, r, c] = Pr1 - U[NORTH, r, c] = info1.U - S[NORTH, r, c] = info1.S - V[NORTH, r, c] = info1.V - - Pleft[EAST, r, c] = Pl2 - Pright[EAST, r, c] = Pr2 - U[EAST, r, c] = info2.U - S[EAST, r, c] = info2.S - V[EAST, r, c] = info2.V - - Pleft[SOUTH, r, c] = Pl3 - Pright[SOUTH, r, c] = Pr3 - U[SOUTH, r, c] = info3.U - S[SOUTH, r, c] = info3.S - V[SOUTH, r, c] = info3.V + ϵ = 0.0 + rsize, csize = size(env.corners)[2:3] + for dir in 1:4, r in 1:rsize, c in 1:csize + # Row-column index of next enlarged corner + next_rc in if dir == 1 + (r, _next(c, csize)) + elseif dir == 2 + (_next(r, rsize), c) + elseif dir == 3 + (r, _prev(c, csize)) + elseif dir == 4 + (_prev(r, rsize), c) + end - Pleft[WEST, r, c] = Pl4 - Pright[WEST, r, c] = Pr4 - U[WEST, r, c] = info4.U - S[WEST, r, c] = info4.S - V[WEST, r, c] = info4.V - end - return copy(Pleft), copy(Pright), (; ϵ=max(ϵ1, ϵ2, ϵ3, ϵ4), U=copy(U), S=copy(S), V=copy(V)) -end -function build_projectors(Qleft, Qright, alg::ProjectorAlg; kwargs...) - # SVD half-infinite environment - U, S, V, ϵ = PEPSKit.tsvd!(Qleft * Qright, alg.svd_alg; kwargs...) - ϵ /= norm(S) + # SVD half-infinite environment + trscheme = if T <: FixedSpaceTruncation + truncspace(space(env.edges[dir, r, c], 1)) + else + alg.trscheme + end + svd_alg = if A <: SVDAdjoint{<:FixedSVD} + idx = (dir, r, c) + fwd_alg = alg.svd_alg.fwd_alg + fix_svd = FixedSVD(fwd_alg.U[idx...], fwd_alg.S[idx...], fwd_alg.V[idx...]) + return SVDAdjoint(; fwd_alg=fix_svd, rrule_alg=nothing, broadening=nothing) + else + alg.svd_alg + end + @autoopt @tensor QQ[χ_EB D_EBabove D_EBbelow; χ_ET D_ETabove D_ETbelow] := + Q[dir, r, c][χ_EB D_EBabove D_EBbelow; χ D1 D2] * + Q[_next(dir, 4), next_rc...][χ D1 D2; χ_ET D_ETabove D_ETbelow] + U, S, V, ϵ_local = PEPSKit.tsvd!(QQ, svd_alg; trunc=trscheme) + ϵ = max(ϵ, ϵ_local / norm(S)) - # Compute SVD truncation error and check for degenerate singular values - ignore_derivatives() do - if alg.verbosity > 1 && is_degenerate_spectrum(S) - svals = TensorKit.SectorDict(c => diag(b) for (c, b) in blocks(S)) - @warn("degenerate singular values detected: ", svals) + # Compute SVD truncation error and check for degenerate singular values + ignore_derivatives() do + if alg.verbosity > 0 && is_degenerate_spectrum(S) + svals = TensorKit.SectorDict(c => diag(b) for (c, b) in blocks(S)) + @warn("degenerate singular values detected: ", svals) + end end - end - # Compute projectors - isqS = sdiag_inv_sqrt(S) - @tensor Pl[-1 -2 -3; -4] := Qright[-1 -2 -3; 1 2 3] * conj(V[4; 1 2 3]) * isqS[4; -4] - @tensor Pr[-1; -2 -3 -4] := isqS[-1; 1] * conj(U[2 3 4; 1]) * Qleft[2 3 4; -2 -3 -4] + # Compute projectors + Pl, Pr = build_projectors(U, S, V, Q_sw, Q_nw) + P_left[dir, r, c] = Pl + P_right[dir, r, c] = Pr + U[dir, r, c] = info.U + S[dir, r, c] = info.S + V[dir, r, c] = info.V + end - return Pl, Pr, (; ϵ, U, S, V) + return copy(P_left), copy(P_right), (; ϵ, U=copy(U), S=copy(S), V=copy(V)) end # Apply projectors to renormalize corners and edges -function renormalize_corners_edges(state, env::CTMRGEnv, Q, Pleft, Pright) +function renormalize_corners_edges(state, env::CTMRGEnv, Q, P_left, P_right) corners::typeof(env.corners) = copy(env.corners) edges::typeof(env.edges) = copy(env.edges) for c in 1:size(state, 2), r in 1:size(state, 1) @@ -144,46 +125,46 @@ function renormalize_corners_edges(state, env::CTMRGEnv, Q, Pleft, Pright) cprev = _prev(c, size(state, 2)) cnext = _next(c, size(state, 2)) @diffset @tensor corners[NORTHWEST, r, c][-1; -2] := - Pright[WEST, rnext, c][-1; 1 2 3] * + P_right[WEST, rnext, c][-1; 1 2 3] * Q[NORTHWEST, r, c][1 2 3; 4 5 6] * - Pleft[NORTH, r, c][4 5 6; -2] + P_left[NORTH, r, c][4 5 6; -2] @diffset @tensor corners[NORTHEAST, r, c][-1; -2] := - Pright[NORTH, r, cprev][-1; 1 2 3] * + P_right[NORTH, r, cprev][-1; 1 2 3] * Q[NORTHEAST, r, c][1 2 3; 4 5 6] * - Pleft[EAST, r, c][4 5 6; -2] + P_left[EAST, r, c][4 5 6; -2] @diffset @tensor corners[SOUTHEAST, r, c][-1; -2] := - Pright[EAST, rprev, c][-1; 1 2 3] * + P_right[EAST, rprev, c][-1; 1 2 3] * Q[SOUTHEAST, r, c][1 2 3; 4 5 6] * - Pleft[SOUTH, r, c][4 5 6; -2] + P_left[SOUTH, r, c][4 5 6; -2] @diffset @tensor corners[SOUTHWEST, r, c][-1; -2] := - Pright[SOUTH, r, cnext][-1; 1 2 3] * + P_right[SOUTH, r, cnext][-1; 1 2 3] * Q[SOUTHWEST, r, c][1 2 3; 4 5 6] * - Pleft[WEST, r, c][4 5 6; -2] + P_left[WEST, r, c][4 5 6; -2] @diffset @tensor edges[NORTH, r, c][-1 -2 -3; -4] := env.edges[NORTH, rprev, c][1 2 3; 4] * state[r, c][9; 2 5 -2 7] * conj(state[r, c][9; 3 6 -3 8]) * - Pleft[NORTH, r, c][4 5 6; -4] * - Pright[NORTH, r, cprev][-1; 1 7 8] + P_left[NORTH, r, c][4 5 6; -4] * + P_right[NORTH, r, cprev][-1; 1 7 8] @diffset @tensor edges[EAST, r, c][-1 -2 -3; -4] := env.edges[EAST, r, _next(c, end)][1 2 3; 4] * state[r, c][9; 7 2 5 -2] * conj(state[r, c][9; 8 3 6 -3]) * - Pleft[EAST, r, c][4 5 6; -4] * - Pright[EAST, rprev, c][-1; 1 7 8] + P_left[EAST, r, c][4 5 6; -4] * + P_right[EAST, rprev, c][-1; 1 7 8] @diffset @tensor edges[SOUTH, r, c][-1 -2 -3; -4] := env.edges[SOUTH, _next(r, end), c][1 2 3; 4] * state[r, c][9; -2 7 2 5] * conj(state[r, c][9; -3 8 3 6]) * - Pleft[SOUTH, r, c][4 5 6; -4] * - Pright[SOUTH, r, cnext][-1; 1 7 8] + P_left[SOUTH, r, c][4 5 6; -4] * + P_right[SOUTH, r, cnext][-1; 1 7 8] @diffset @tensor edges[WEST, r, c][-1 -2 -3; -4] := env.edges[WEST, r, _prev(c, end)][1 2 3; 4] * state[r, c][9; 5 -2 7 2] * conj(state[r, c][9; 6 -3 8 3]) * - Pleft[WEST, r, c][4 5 6; -4] * - Pright[WEST, rnext, c][-1; 1 7 8] + P_left[WEST, r, c][4 5 6; -4] * + P_right[WEST, rnext, c][-1; 1 7 8] end @diffset corners[:, :, :] ./= norm.(corners[:, :, :]) diff --git a/src/utility/svd.jl b/src/utility/svd.jl index 3c0afdaa..2e9618a2 100644 --- a/src/utility/svd.jl +++ b/src/utility/svd.jl @@ -168,6 +168,24 @@ function ChainRulesCore.rrule( return (U, S, V, ϵ), tsvd!_itersvd_pullback end +""" + struct FixedSVD + +SVD struct containing a pre-computed decomposition or even multiple ones. +The call to `tsvd` just returns the pre-computed U, S and V. In the reverse +pass, the SVD adjoint is computed with these exact U, S, and V. +""" +struct FixedSVD{Ut,St,Vt} + U::Ut + S::St + V::Vt +end + +# Return pre-computed SVD +function TensorKit._tsvd!(_, alg::FixedSVD, ::NoTruncation, ::Real=2) + return alg.U, alg.S, alg.V, 0 +end + """ struct NonTruncAdjoint(; lorentz_broadening = 0.0) diff --git a/src/utility/util.jl b/src/utility/util.jl index 0797a412..e66b059b 100644 --- a/src/utility/util.jl +++ b/src/utility/util.jl @@ -51,16 +51,24 @@ end """ projector_type(T::DataType, size) + projector_type(edges::Array{<:AbstractTensorMap}) Create two arrays of specified `size` that contain undefined tensors representing left and right acting projectors, respectively. The projector types are inferred from the TensorMap type `T` which avoids having to recompute transpose tensors. +Alternatively, supply an array of edge tensors from which left and right projectors +are intialized explicitly with zeros. """ function projector_type(T::DataType, size) - Pleft = Array{T,length(size)}(undef, size) + P_left = Array{T,length(size)}(undef, size) Prtype = tensormaptype(spacetype(T), numin(T), numout(T), storagetype(T)) - Pright = Array{Prtype,length(size)}(undef, size) - return Pleft, Pright + P_right = Array{Prtype,length(size)}(undef, size) + return P_left, P_right +end +function projector_type(edges::Array{<:AbstractTensorMap}) + P_left = map(e -> TensorMap(zeros, scalartype(e), space(e)), edges) + P_right = map(e -> TensorMap(zeros, scalartype(e), domain(e), codomain(e)), edges) + return P_left, P_right end # There are no rrules for rotl90 and rotr90 in ChainRules.jl From 08a907e0e7ebe63ee3347eb3fe37af5199c3964a Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Wed, 10 Jul 2024 14:10:36 +0200 Subject: [PATCH 029/213] Make runnable and add leftmoves/allsides test --- src/PEPSKit.jl | 4 +-- src/algorithms/ctmrg.jl | 28 +++++++++++------- src/algorithms/ctmrg_all_sides.jl | 27 ++++++++--------- src/algorithms/ctmrg_gauge_fix.jl | 2 +- src/utility/svd.jl | 2 +- test/ctmrg/gaugefix.jl | 4 +-- test/ctmrg/leftmoves_allsides.jl | 49 +++++++++++++++++++++++++++++++ 7 files changed, 84 insertions(+), 32 deletions(-) create mode 100644 test/ctmrg/leftmoves_allsides.jl diff --git a/src/PEPSKit.jl b/src/PEPSKit.jl index c9f929df..3005a152 100644 --- a/src/PEPSKit.jl +++ b/src/PEPSKit.jl @@ -56,8 +56,8 @@ Module containing default values that represent typical algorithm parameters. module Defaults const ctmrg_maxiter = 100 const ctmrg_miniter = 4 - const ctmrg_tol = 1e-12 - const fpgrad_maxiter = 100 + const ctmrg_tol = 1e-10 + const fpgrad_maxiter = 20 const fpgrad_tol = 1e-6 end diff --git a/src/algorithms/ctmrg.jl b/src/algorithms/ctmrg.jl index 8306f3c8..019ba6f6 100644 --- a/src/algorithms/ctmrg.jl +++ b/src/algorithms/ctmrg.jl @@ -49,7 +49,7 @@ function CTMRG(; tol=Defaults.ctmrg_tol, maxiter=Defaults.ctmrg_maxiter, miniter=Defaults.ctmrg_miniter, - verbosity=0, + verbosity=1, svd_alg=SVDAdjoint(), trscheme=FixedSpaceTruncation(), ctmrgscheme=:AllSides, @@ -68,7 +68,7 @@ Per default, a random initial environment is used. function MPSKit.leading_boundary(state, alg::CTMRG) return MPSKit.leading_boundary(CTMRGEnv(state), state, alg) end -function MPSKit.leading_boundary(envinit, state, alg::CTMRG) +function MPSKit.leading_boundary(envinit, state, alg::CTMRG{S}) where {S} normold = 1.0 CSold = map(x -> tsvd(x; alg=TensorKit.SVD())[2], envinit.corners) TSold = map(x -> tsvd(x; alg=TensorKit.SVD())[2], envinit.edges) @@ -119,13 +119,20 @@ function MPSKit.leading_boundary(envinit, state, alg::CTMRG) @warn( "CTMRG reached maximal number of iterations at (Δnorm=$Δnorm, ΔCS=$ΔCS, ΔTS=$ΔTS)" ) + flush(stdout) # Flush output to enable live printing on HPC + flush(stderr) # Same for @info, @warn, ... return conv_condition, normnew, CSnew, TSnew, info.ϵ end conv_condition && break # Converge if maximal Δ falls below tolerance end # Do one final iteration that does not change the spaces - alg_fixed = @set alg.projector_alg.trscheme = FixedSpaceTruncation() + alg_fixed = CTMRG(; + verbosity=alg.verbosity, + svd_alg=alg.projector_alg.svd_alg, + trscheme=FixedSpaceTruncation(), + ctmrgscheme=S, + ) env′, = ctmrg_iter(state, env, alg_fixed) envfix, = gauge_fix(env, env′) check_elementwise_convergence(env, envfix; atol=alg.tol^(1 / 2)) || @@ -145,10 +152,10 @@ function ctmrg_iter(state, env::CTMRGEnv{C,T}, alg::CTMRG) where {C,T} ϵ = 0.0 for _ in 1:4 - env, _, _, ϵ₀ = left_move(state, env, alg.projector_alg) + env, info = left_move(state, env, alg.projector_alg) state = rotate_north(state, EAST) env = rotate_north(env, EAST) - ϵ = max(ϵ, ϵ₀) + ϵ = max(ϵ, info.ϵ) end return env, (; ϵ) @@ -164,11 +171,10 @@ function left_move(state, env::CTMRGEnv{C,T}, alg::ProjectorAlg) where {C,T} corners::typeof(env.corners) = copy(env.corners) edges::typeof(env.edges) = copy(env.edges) ϵ = 0.0 - P_top, P_bottom = Zygote.Buffer.(projector_type(T, size(state))) # Use Zygote.Buffer instead of @diffset to avoid ZeroTangent errors in _setindex + P_bottom, P_top = Zygote.Buffer.(projector_type(T, size(state))) # Use Zygote.Buffer instead of @diffset to avoid ZeroTangent errors in _setindex for col in 1:size(state, 2) cprev = _prev(col, size(state, 2)) - cnext = _next(col, size(state, 2)) # Compute projectors for row in 1:size(state, 1) @@ -211,9 +217,9 @@ function left_move(state, env::CTMRGEnv{C,T}, alg::ProjectorAlg) where {C,T} end # Compute projectors - Pt, Pb = build_projectors(U, S, V, Q_sw, Q_nw) - P_top[row, col] = Pt + Pb, Pt = build_projectors(U, S, V, Q_sw, Q_nw) P_bottom[row, col] = Pb + P_top[row, col] = Pt end # Use projectors to grow the corners & edges @@ -221,8 +227,8 @@ function left_move(state, env::CTMRGEnv{C,T}, alg::ProjectorAlg) where {C,T} rprev = _prev(row, size(state, 1)) C_sw, C_nw, T_w = grow_env_left( state[row, col], - P_left[rprev, col], - P_right[row, col], + P_bottom[rprev, col], + P_top[row, col], env.corners[SOUTHWEST, row, cprev], env.corners[NORTHWEST, row, cprev], env.edges[SOUTH, row, col], diff --git a/src/algorithms/ctmrg_all_sides.jl b/src/algorithms/ctmrg_all_sides.jl index 636a8322..abc1ded5 100644 --- a/src/algorithms/ctmrg_all_sides.jl +++ b/src/algorithms/ctmrg_all_sides.jl @@ -1,15 +1,10 @@ - -struct IndexSelector{N} <: TensorKit.TruncationScheme - index::NTuple{N,Int} -end - # One CTMRG iteration with both-sided application of projectors function ctmrg_iter(state, env::CTMRGEnv, alg::CTMRG{:AllSides}) # Compute enlarged corners Q = enlarge_corners_edges(state, env) # Compute projectors if none are supplied - P_left, P_right, info = build_projectors(Q, env, alg) + P_left, P_right, info = build_projectors(Q, env, alg.projector_alg) # Apply projectors and normalize corners, edges = renormalize_corners_edges(state, env, Q, P_left, P_right) @@ -65,7 +60,7 @@ function build_projectors(Q, env::CTMRGEnv, alg::ProjectorAlg{A,T}) where {A,T} rsize, csize = size(env.corners)[2:3] for dir in 1:4, r in 1:rsize, c in 1:csize # Row-column index of next enlarged corner - next_rc in if dir == 1 + next_rc = if dir == 1 (r, _next(c, csize)) elseif dir == 2 (_next(r, rsize), c) @@ -92,24 +87,26 @@ function build_projectors(Q, env::CTMRGEnv, alg::ProjectorAlg{A,T}) where {A,T} @autoopt @tensor QQ[χ_EB D_EBabove D_EBbelow; χ_ET D_ETabove D_ETbelow] := Q[dir, r, c][χ_EB D_EBabove D_EBbelow; χ D1 D2] * Q[_next(dir, 4), next_rc...][χ D1 D2; χ_ET D_ETabove D_ETbelow] - U, S, V, ϵ_local = PEPSKit.tsvd!(QQ, svd_alg; trunc=trscheme) - ϵ = max(ϵ, ϵ_local / norm(S)) + U_local, S_local, V_local, ϵ_local = PEPSKit.tsvd!(QQ, svd_alg; trunc=trscheme) + U[dir, r, c] = U_local + S[dir, r, c] = S_local + V[dir, r, c] = V_local + ϵ = max(ϵ, ϵ_local / norm(S_local)) # Compute SVD truncation error and check for degenerate singular values ignore_derivatives() do - if alg.verbosity > 0 && is_degenerate_spectrum(S) - svals = TensorKit.SectorDict(c => diag(b) for (c, b) in blocks(S)) + if alg.verbosity > 0 && is_degenerate_spectrum(S_local) + svals = TensorKit.SectorDict(c => diag(b) for (c, b) in blocks(S_local)) @warn("degenerate singular values detected: ", svals) end end # Compute projectors - Pl, Pr = build_projectors(U, S, V, Q_sw, Q_nw) + Pl, Pr = build_projectors( + U_local, S_local, V_local, Q[dir, r, c], Q[_next(dir, 4), next_rc...] + ) P_left[dir, r, c] = Pl P_right[dir, r, c] = Pr - U[dir, r, c] = info.U - S[dir, r, c] = info.S - V[dir, r, c] = info.V end return copy(P_left), copy(P_right), (; ϵ, U=copy(U), S=copy(S), V=copy(V)) diff --git a/src/algorithms/ctmrg_gauge_fix.jl b/src/algorithms/ctmrg_gauge_fix.jl index 5663bdc2..fd9d4bea 100644 --- a/src/algorithms/ctmrg_gauge_fix.jl +++ b/src/algorithms/ctmrg_gauge_fix.jl @@ -53,7 +53,7 @@ function gauge_fix(envprev::CTMRGEnv{C,T}, envfinal::CTMRGEnv{C,T}) where {C,T} end cornersfix, edgesfix = fix_relative_phases(envfinal, signs) - return fix_global_phases(envfinal, CTMRGEnv(cornersfix, edgesfix)), signs + return fix_global_phases(envprev, CTMRGEnv(cornersfix, edgesfix)), signs end # this is a bit of a hack to get the fixed point of the mixed transfer matrix diff --git a/src/utility/svd.jl b/src/utility/svd.jl index 2e9618a2..b49579f1 100644 --- a/src/utility/svd.jl +++ b/src/utility/svd.jl @@ -182,7 +182,7 @@ struct FixedSVD{Ut,St,Vt} end # Return pre-computed SVD -function TensorKit._tsvd!(_, alg::FixedSVD, ::NoTruncation, ::Real=2) +function TensorKit._tsvd!(t, alg::FixedSVD, ::NoTruncation, ::Real=2) return alg.U, alg.S, alg.V, 0 end diff --git a/test/ctmrg/gaugefix.jl b/test/ctmrg/gaugefix.jl index a6883474..9e477e21 100644 --- a/test/ctmrg/gaugefix.jl +++ b/test/ctmrg/gaugefix.jl @@ -53,7 +53,7 @@ end ctm = leading_boundary(ctm, psi, alg) ctm2, = ctmrg_iter(psi, ctm, alg_fixed) - ctm_fixed = gauge_fix(ctm, ctm2) + ctm_fixed, = gauge_fix(ctm, ctm2) @test PEPSKit.check_elementwise_convergence(ctm, ctm_fixed; atol=1e-4) end @@ -78,6 +78,6 @@ end ctm = leading_boundary(ctm, psi, alg) ctm2, = ctmrg_iter(psi, ctm, alg_fixed) - ctm_fixed = gauge_fix(ctm, ctm2) + ctm_fixed, = gauge_fix(ctm, ctm2) @test PEPSKit.check_elementwise_convergence(ctm, ctm_fixed; atol=1e-4) end diff --git a/test/ctmrg/leftmoves_allsides.jl b/test/ctmrg/leftmoves_allsides.jl new file mode 100644 index 00000000..e697a44b --- /dev/null +++ b/test/ctmrg/leftmoves_allsides.jl @@ -0,0 +1,49 @@ +using Test +using Random +using TensorKit +using MPSKit +using PEPSKit + +# initialize parameters +χbond = 2 +χenv = 16 +ctm_alg_leftmoves = CTMRG(; tol=1e-10, verbosity=1, ctmrgscheme=:LeftMoves) +ctm_alg_allsides = CTMRG(; tol=1e-10, verbosity=1, ctmrgscheme=:AllSides) + +# compute environments +Random.seed!(32350283290358) +psi = InfinitePEPS(2, χbond) +env_leftmoves = leading_boundary( + CTMRGEnv(psi; Venv=ComplexSpace(χenv)), psi, ctm_alg_leftmoves +) +env_allsides = leading_boundary( + CTMRGEnv(psi; Venv=ComplexSpace(χenv)), psi, ctm_alg_allsides +) + +# compare norms +@test abs(norm(psi, env_leftmoves)) ≈ abs(norm(psi, env_allsides)) rtol = 1e-6 + +# compare singular values +CS_leftmoves = map(c -> tsvd(c; alg=TensorKit.SVD())[2], env_leftmoves.corners) +CS_allsides = map(c -> tsvd(c; alg=TensorKit.SVD())[2], env_allsides.corners) +ΔCS = maximum(zip(CS_leftmoves, CS_allsides)) do (c_lm, c_as) + smallest = infimum(MPSKit._firstspace(c_lm), MPSKit._firstspace(c_as)) + e_old = isometry(MPSKit._firstspace(c_lm), smallest) + e_new = isometry(MPSKit._firstspace(c_as), smallest) + return norm(e_new' * c_as * e_new - e_old' * c_lm * e_old) +end +@test ΔCS < 1e-3 + +ΔTS = maximum(zip(TS_leftmoves, TS_allsides)) do (t_lm, t_as) + MPSKit._firstspace(t_lm) == MPSKit._firstspace(t_as) || return scalartype(t_lm)(Inf) + return norm(t_as - t_lm) +end +TS_leftmoves = map(t -> tsvd(t; alg=TensorKit.SVD())[2], env_leftmoves.edges) +TS_allsides = map(t -> tsvd(t; alg=TensorKit.SVD())[2], env_allsides.edges) +@test ΔTS < 1e-3 + +# compare Heisenberg energies +H = square_lattice_heisenberg() +E_leftmoves = costfun(psi, env_leftmoves, H) +E_allsides = costfun(psi, env_allsides, H) +@test E_leftmoves ≈ E_allsides rtol=1e-6 From 50cb911b918f5e913c42e5e1055c510fa602ef22 Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Wed, 10 Jul 2024 15:12:48 +0200 Subject: [PATCH 030/213] Add GradMode{F} type parameter for iterscheme, fix runtests.jl --- src/algorithms/ctmrg.jl | 12 +++- src/algorithms/peps_opt.jl | 113 +++++++++++++++++++++++++++++-------- test/runtests.jl | 6 ++ 3 files changed, 107 insertions(+), 24 deletions(-) diff --git a/src/algorithms/ctmrg.jl b/src/algorithms/ctmrg.jl index 24b46df6..a02cdc68 100644 --- a/src/algorithms/ctmrg.jl +++ b/src/algorithms/ctmrg.jl @@ -35,8 +35,16 @@ Algorithm struct that represents the CTMRG algorithm for contracting infinite PE Each CTMRG run is converged up to `tol` where the singular value convergence of the corners as well as the norm is checked. The maximal and minimal number of CTMRG iterations is set with `maxiter` and `miniter`. Different levels of output information are printed -depending on `verbosity` (0, 1 or 2). The projectors are computed from `svd_alg` SVDs -where the truncation scheme is set via `trscheme`. +depending on `verbosity` (0, 1 or 2). + +The projectors are computed from `svd_alg` SVDs where the truncation scheme is set via +`trscheme`. + +In general, two different schemes can be selected with `ctmrgscheme` which determine how +CTMRG is implemented. It can either be `:LeftMoves`, where the projectors are succesively +computed on the western side, and then applied and rotated. Or with `AllSides`, all projectors +are computed and applied simultaneously on all sides, where in particular the corners get +contracted with two projectors at the same time. """ struct CTMRG{S} tol::Float64 diff --git a/src/algorithms/peps_opt.jl b/src/algorithms/peps_opt.jl index cdee687f..1ec77a08 100644 --- a/src/algorithms/peps_opt.jl +++ b/src/algorithms/peps_opt.jl @@ -1,32 +1,59 @@ -abstract type GradMode end +abstract type GradMode{F} end """ - struct NaiveAD <: GradMode + struct GeomSum(; maxiter=Defaults.fpgrad_maxiter, tol=Defaults.fpgrad_tol, + verbosity=0, iterscheme=:FixedIter) <: GradMode -Gradient mode for CTMRG using AD. +Gradient mode for CTMRG using explicit evaluation of the geometric sum. """ -struct NaiveAD <: GradMode end +struct GeomSum{F} <: GradMode{F} + maxiter::Int + tol::Real + verbosity::Int +end +function GeomSum(; + maxiter=Defaults.fpgrad_maxiter, + tol=Defaults.fpgrad_tol, + verbosity=0, + iterscheme=:FixedIter, +) + return GeomSum{iterscheme}(maxiter, tol, verbosity) +end """ - struct GeomSum <: GradMode + struct ManualIter(; maxiter=Defaults.fpgrad_maxiter, tol=Defaults.fpgrad_tol, + verbosity=0, iterscheme=:FixedIter) <: GradMode -Gradient mode for CTMRG using explicit evaluation of the geometric sum. +Gradient mode for CTMRG using manual iteration to solve the linear problem. """ -@kwdef struct GeomSum <: GradMode - maxiter::Int = Defaults.fpgrad_maxiter - tol::Real = Defaults.fpgrad_tol - verbosity::Int = 0 +struct ManualIter{F} <: GradMode{F} + maxiter::Int + tol::Real + verbosity::Int +end +function ManualIter(; + maxiter=Defaults.fpgrad_maxiter, + tol=Defaults.fpgrad_tol, + verbosity=0, + iterscheme=:FixedIter, +) + return ManualIter{iterscheme}(maxiter, tol, verbosity) end """ - struct ManualIter <: GradMode + struct LinSolver(; solver=KrylovKit.GMRES(), iterscheme=:FixedIter) <: GradMode -Gradient mode for CTMRG using manual iteration to solve the linear problem. +Gradient mode wrapper around `KrylovKit.LinearSolver` for solving the gradient linear +problem using iterative solvers. """ -@kwdef struct ManualIter <: GradMode - maxiter::Int = Defaults.fpgrad_maxiter - tol::Real = Defaults.fpgrad_tol - verbosity::Int = 0 +struct LinSolver{F} <: GradMode{F} + solver::KrylovKit.LinearSolver +end +function LinSolver(; + solver=KrylovKit.GMRES(; maxiter=Defaults.fpgrad_maxiter, tol=Defaults.fpgrad_tol), + iterscheme=:FixedIter, +) + return LinSolver{iterscheme}(solver) end """ @@ -103,24 +130,66 @@ Evaluating the gradient of the cost function for CTMRG: - With explicit evaluation of the geometric sum, the gradient is computed by differentiating the cost function with the environment kept fixed, and then manually adding the gradient contributions from the environments. =# +# function _rrule( +# gradmode::Union{GradMode,KrylovKit.LinearSolver}, +# ::RuleConfig, +# ::typeof(MPSKit.leading_boundary), +# envinit, +# state, +# alg::CTMRG, +# ) + function _rrule( - gradmode::Union{GradMode,KrylovKit.LinearSolver}, + gradmode::GradMode{:DiffGauge}, ::RuleConfig, ::typeof(MPSKit.leading_boundary), envinit, state, alg::CTMRG, ) - envs = leading_boundary(envinit, state, alg) - #TODO: fixed space for unit cells + envs = leading_boundary(envinit, state, alg) #TODO: fixed space for unit cells - function leading_boundary_pullback(Δenvs′) + function leading_boundary_diffgauge_pullback(Δenvs′) Δenvs = unthunk(Δenvs′) # find partial gradients of gauge_fixed single CTMRG iteration # TODO: make this rrule_via_ad so it's zygote-agnostic _, env_vjp = pullback(state, envs) do A, x - return gauge_fix(x, ctmrg_iter(A, x, alg)[1]) + return gauge_fix(x, ctmrg_iter(A, x, alg)[1])[1] + end + + # evaluate the geometric sum + ∂f∂A(x)::typeof(state) = env_vjp(x)[1] + ∂f∂x(x)::typeof(envs) = env_vjp(x)[2] + ∂F∂envs = fpgrad(Δenvs, ∂f∂x, ∂f∂A, Δenvs, gradmode) + + return NoTangent(), ZeroTangent(), ∂F∂envs, NoTangent() + end + + return envs, leading_boundary_diffgauge_pullback +end + +# Here f is differentiated from an pre-computed SVD with fixed U, S and V +function _rrule( + gradmode::GradMode{:FixedIter}, + ::RuleConfig, + ::typeof(MPSKit.leading_boundary), + envinit, + state, + alg::CTMRG, +) + @assert alg.ctmrgscheme isa AllSides + envs = leading_boundary(envinit, state, alg) + envsconv, info = ctmrg_iter(state, envs, alg) + envsfix, signs = gauge_fix(envs, envsconv) + svd_alg_fix = fix_svd(alg, info.U, info.S, info.V, signs) + + function leading_boundary_fixediter_pullback(Δenvs′) + Δenvs = unthunk(Δenvs′) + + _, env_vjp = pullback(state, envsfix) do A, x + e, = ctmrg_iter(A, x, svd_alg_fix) + return fix_global_phases(x, e) end # evaluate the geometric sum @@ -131,7 +200,7 @@ function _rrule( return NoTangent(), ZeroTangent(), ∂F∂envs, NoTangent() end - return envs, leading_boundary_pullback + return envsfix, leading_boundary_fixediter_pullback end @doc """ diff --git a/test/runtests.jl b/test/runtests.jl index e80e627c..c0c48f62 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -20,6 +20,12 @@ end @time @safetestset "SVD wrapper" begin include("ctmrg/svd_wrapper.jl") end + @time @safetestset "SVD wrapper" begin + include("ctmrg/unitcell.jl") + end + @time @safetestset "SVD wrapper" begin + include("ctmrg/leftmoves_allsides.jl") + end end if GROUP == "ALL" || GROUP == "MPS" @time @safetestset "VUMPS" begin From 8eb3f7ce69e37fbc67d3e8a7bc9531ff23cf3b84 Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Wed, 10 Jul 2024 16:56:11 +0200 Subject: [PATCH 031/213] Add iterscheme flag to GradMode, add GradMode wrapper for KrylovKit.LinearSolver --- src/PEPSKit.jl | 4 +-- src/algorithms/ctmrg.jl | 1 - src/algorithms/ctmrg_all_sides.jl | 7 +++-- src/algorithms/ctmrg_gauge_fix.jl | 5 ++-- src/algorithms/peps_opt.jl | 31 +++++++++---------- src/utility/svd.jl | 50 ++++++++++++++++++------------- test/heisenberg.jl | 2 +- 7 files changed, 54 insertions(+), 46 deletions(-) diff --git a/src/PEPSKit.jl b/src/PEPSKit.jl index 3005a152..1e1ec392 100644 --- a/src/PEPSKit.jl +++ b/src/PEPSKit.jl @@ -61,12 +61,12 @@ module Defaults const fpgrad_tol = 1e-6 end -export SVDAdjoint, IterSVD, NonTruncSVDAdjoint +export SVDAdjoint, IterSVD, FixedSVD, NonTruncSVDAdjoint export FixedSpaceTruncation, ProjectorAlg, CTMRG, CTMRGEnv, correlation_length export LocalOperator export expectation_value, costfun export leading_boundary -export PEPSOptimize, GeomSum, ManualIter, LinSolve +export PEPSOptimize, GeomSum, ManualIter, LinSolver export fixedpoint export InfinitePEPS, InfiniteTransferPEPS diff --git a/src/algorithms/ctmrg.jl b/src/algorithms/ctmrg.jl index a02cdc68..9ed6a71e 100644 --- a/src/algorithms/ctmrg.jl +++ b/src/algorithms/ctmrg.jl @@ -100,7 +100,6 @@ function MPSKit.leading_boundary(envinit, state, alg::CTMRG{S}) where {S} return norm(e_new' * c_new * e_new - e_old' * c_old * e_old) end TSnew = map(t -> tsvd(t; alg=TensorKit.SVD())[2], env.edges) - ΔTS = maximum(zip(TSold, TSnew)) do (t_old, t_new) MPSKit._firstspace(t_old) == MPSKit._firstspace(t_new) || return scalartype(t_old)(Inf) diff --git a/src/algorithms/ctmrg_all_sides.jl b/src/algorithms/ctmrg_all_sides.jl index abc1ded5..20f9c334 100644 --- a/src/algorithms/ctmrg_all_sides.jl +++ b/src/algorithms/ctmrg_all_sides.jl @@ -52,7 +52,7 @@ function enlarge_corners_edges(state, env::CTMRGEnv) end # Build projectors from SVD and enlarged corners -function build_projectors(Q, env::CTMRGEnv, alg::ProjectorAlg{A,T}) where {A,T} # TODO: Add projector type annotations +function build_projectors(Q, env::CTMRGEnv, alg::ProjectorAlg{A,T}) where {A,T} P_left, P_right = Zygote.Buffer.(projector_type(env.edges)) U, V = Zygote.Buffer.(projector_type(env.edges)) S = Zygote.Buffer(env.corners) @@ -78,9 +78,12 @@ function build_projectors(Q, env::CTMRGEnv, alg::ProjectorAlg{A,T}) where {A,T} end svd_alg = if A <: SVDAdjoint{<:FixedSVD} idx = (dir, r, c) + # svd_alg = alg.svd_alg fwd_alg = alg.svd_alg.fwd_alg fix_svd = FixedSVD(fwd_alg.U[idx...], fwd_alg.S[idx...], fwd_alg.V[idx...]) - return SVDAdjoint(; fwd_alg=fix_svd, rrule_alg=nothing, broadening=nothing) + # return @set svd_alg.fwd_alg = fix_svd + # return SVDAdjoint(; fwd_alg=fix_svd, rrule_alg=alg.svd_alg.rrule_alg) + # return SVDAdjoint() else alg.svd_alg end diff --git a/src/algorithms/ctmrg_gauge_fix.jl b/src/algorithms/ctmrg_gauge_fix.jl index fd9d4bea..640d2ff0 100644 --- a/src/algorithms/ctmrg_gauge_fix.jl +++ b/src/algorithms/ctmrg_gauge_fix.jl @@ -134,8 +134,8 @@ function fix_relative_phases(envfinal::CTMRGEnv, signs) return stack([C1, C2, C3, C4]; dims=1), stack([T1, T2, T3, T4]; dims=1) end function fix_relative_phases( - U::Array{<:AbstractTensorMap,3}, V::Array{<:AbstractTensorMap,3}, signs -) + U::Array{Ut,3}, V::Array{Vt,3}, signs +) where {Ut<:AbstractTensorMap,Vt<:AbstractTensorMap} U1 = map(Iterators.product(axes(U)[2:3]...)) do (r, c) return U[NORTH, r, c] * signs[NORTH, r, _next(c, end)] end @@ -177,7 +177,6 @@ function fix_global_phases(envprev::CTMRGEnv, envfix::CTMRGEnv) return CTMRGEnv(cornersgfix, edgesgfix) end - """ check_elementwise_convergence(envfinal, envfix; atol=1e-6) diff --git a/src/algorithms/peps_opt.jl b/src/algorithms/peps_opt.jl index 1ec77a08..c6d724ae 100644 --- a/src/algorithms/peps_opt.jl +++ b/src/algorithms/peps_opt.jl @@ -130,15 +130,6 @@ Evaluating the gradient of the cost function for CTMRG: - With explicit evaluation of the geometric sum, the gradient is computed by differentiating the cost function with the environment kept fixed, and then manually adding the gradient contributions from the environments. =# -# function _rrule( -# gradmode::Union{GradMode,KrylovKit.LinearSolver}, -# ::RuleConfig, -# ::typeof(MPSKit.leading_boundary), -# envinit, -# state, -# alg::CTMRG, -# ) - function _rrule( gradmode::GradMode{:DiffGauge}, ::RuleConfig, @@ -176,19 +167,25 @@ function _rrule( ::typeof(MPSKit.leading_boundary), envinit, state, - alg::CTMRG, -) - @assert alg.ctmrgscheme isa AllSides + alg::CTMRG{C}, +) where {C} + @assert C == :AllSides envs = leading_boundary(envinit, state, alg) envsconv, info = ctmrg_iter(state, envs, alg) envsfix, signs = gauge_fix(envs, envsconv) - svd_alg_fix = fix_svd(alg, info.U, info.S, info.V, signs) + + # Fix SVD + Ufix, Vfix = fix_relative_phases(info.U, info.V, signs) + svd_alg_fixed = SVDAdjoint(; + fwd_alg=FixedSVD(Ufix, info.S, Vfix), rrule_alg=alg.projector_alg.svd_alg.rrule_alg + ) + alg_fixed = CTMRG(; svd_alg=svd_alg_fixed, trscheme=notrunc(), ctmrgscheme=:AllSides) function leading_boundary_fixediter_pullback(Δenvs′) Δenvs = unthunk(Δenvs′) _, env_vjp = pullback(state, envsfix) do A, x - e, = ctmrg_iter(A, x, svd_alg_fix) + e, = ctmrg_iter(A, x, alg_fixed) return fix_global_phases(x, e) end @@ -267,9 +264,9 @@ function fpgrad(∂F∂x, ∂f∂x, ∂f∂A, y₀, alg::ManualIter) return dx end -function fpgrad(∂F∂x, ∂f∂x, ∂f∂A, y₀, alg::KrylovKit.LinearSolver) - y, info = linsolve(∂f∂x, ∂F∂x, y₀, alg, 1, -1) - if alg.verbosity > 0 && info.converged != 1 +function fpgrad(∂F∂x, ∂f∂x, ∂f∂A, y₀, alg::LinSolver) + y, info = linsolve(∂f∂x, ∂F∂x, y₀, alg.solver, 1, -1) + if alg.solver.verbosity > 0 && info.converged != 1 @warn("gradient fixed-point iteration reached maximal number of iterations:", info) end diff --git a/src/utility/svd.jl b/src/utility/svd.jl index 77625d47..5880b30d 100644 --- a/src/utility/svd.jl +++ b/src/utility/svd.jl @@ -24,6 +24,16 @@ removes the divergences from the adjoint. broadening::B = nothing end # Keep truncation algorithm separate to be able to specify CTMRG dependent information +# function ChainRulesCore.rrule( +# ::Type{SVDAdjoint{F,R,B}}, +# fwd_alg::F=TensorKit.SVD(), +# rrule_alg::R=nothing, +# broadening::B=nothing, +# ) where {F,R,B} +# svdadjoint_pullback(_) = NoTangent() +# return SVDAdjoint(; fwd_alg, rrule_alg, broadening), svdadjoint_pullback +# end + """ PEPSKit.tsvd(t::AbstractTensorMap, alg; trunc=notrunc(), p=2) @@ -39,6 +49,24 @@ function PEPSKit.tsvd!( return TensorKit.tsvd!(t; alg=alg.fwd_alg, trunc, p) end +""" + struct FixedSVD + +SVD struct containing a pre-computed decomposition or even multiple ones. +The call to `tsvd` just returns the pre-computed U, S and V. In the reverse +pass, the SVD adjoint is computed with these exact U, S, and V. +""" +struct FixedSVD{Ut,St,Vt} + U::Ut + S::St + V::Vt +end + +# Return pre-computed SVD +function TensorKit._tsvd!(t, alg::FixedSVD, ::NoTruncation, ::Real=2) + return alg.U, alg.S, alg.V, 0 +end + """ struct IterSVD(; alg = KrylovKit.GKL(), fallback_threshold = Inf) @@ -114,10 +142,10 @@ end function ChainRulesCore.rrule( ::typeof(PEPSKit.tsvd!), t::AbstractTensorMap, - alg::SVDAdjoint{IterSVD,R,B}; + alg::SVDAdjoint{F,R,B}; trunc::TruncationScheme=notrunc(), p::Real=2, -) where {R,B} +) where {F<:Union{IterSVD,FixedSVD},R,B} U, S, V, ϵ = PEPSKit.tsvd(t, alg; trunc, p) function tsvd!_itersvd_pullback((ΔU, ΔS, ΔV, Δϵ)) @@ -168,24 +196,6 @@ function ChainRulesCore.rrule( return (U, S, V, ϵ), tsvd!_itersvd_pullback end -""" - struct FixedSVD - -SVD struct containing a pre-computed decomposition or even multiple ones. -The call to `tsvd` just returns the pre-computed U, S and V. In the reverse -pass, the SVD adjoint is computed with these exact U, S, and V. -""" -struct FixedSVD{Ut,St,Vt} - U::Ut - S::St - V::Vt -end - -# Return pre-computed SVD -function TensorKit._tsvd!(t, alg::FixedSVD, ::NoTruncation, ::Real=2) - return alg.U, alg.S, alg.V, 0 -end - """ struct NonTruncAdjoint(; lorentz_broadening = 0.0) diff --git a/test/heisenberg.jl b/test/heisenberg.jl index 0be5774c..8bdeb2f9 100644 --- a/test/heisenberg.jl +++ b/test/heisenberg.jl @@ -12,7 +12,7 @@ ctm_alg = CTMRG(; tol=1e-10, miniter=4, maxiter=100, verbosity=1, trscheme=trunc opt_alg = PEPSOptimize(; boundary_alg=ctm_alg, optimizer=LBFGS(4; maxiter=100, gradtol=1e-3, verbosity=2), - gradient_alg=GMRES(; tol=1e-6, maxiter=100), + gradient_alg=LinSolver(solver=GMRES(; tol=1e-6, maxiter=100)), reuse_env=true, verbosity=2, ) From c3ad83ada69de31694d9dea1598b17f495889992 Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Wed, 10 Jul 2024 17:50:52 +0200 Subject: [PATCH 032/213] Fix AllSides mode, add element-wise convergence test for FixedSVD --- src/algorithms/ctmrg_all_sides.jl | 10 ++++----- src/utility/svd.jl | 16 ++++---------- test/ctmrg/fixedsvd.jl | 35 +++++++++++++++++++++++++++++++ test/heisenberg.jl | 11 ++++++++-- test/runtests.jl | 3 +++ 5 files changed, 56 insertions(+), 19 deletions(-) create mode 100644 test/ctmrg/fixedsvd.jl diff --git a/src/algorithms/ctmrg_all_sides.jl b/src/algorithms/ctmrg_all_sides.jl index 20f9c334..1a34ac45 100644 --- a/src/algorithms/ctmrg_all_sides.jl +++ b/src/algorithms/ctmrg_all_sides.jl @@ -55,7 +55,10 @@ end function build_projectors(Q, env::CTMRGEnv, alg::ProjectorAlg{A,T}) where {A,T} P_left, P_right = Zygote.Buffer.(projector_type(env.edges)) U, V = Zygote.Buffer.(projector_type(env.edges)) - S = Zygote.Buffer(env.corners) + Stype = tensormaptype( # Corner type but with real numbers + spacetype(env.corners[1]), 1, 1, Matrix{real(scalartype(env.corners[1]))} + ) + S = Zygote.Buffer(Array{Stype,3}(undef, size(env.corners))) ϵ = 0.0 rsize, csize = size(env.corners)[2:3] for dir in 1:4, r in 1:rsize, c in 1:csize @@ -78,12 +81,9 @@ function build_projectors(Q, env::CTMRGEnv, alg::ProjectorAlg{A,T}) where {A,T} end svd_alg = if A <: SVDAdjoint{<:FixedSVD} idx = (dir, r, c) - # svd_alg = alg.svd_alg fwd_alg = alg.svd_alg.fwd_alg fix_svd = FixedSVD(fwd_alg.U[idx...], fwd_alg.S[idx...], fwd_alg.V[idx...]) - # return @set svd_alg.fwd_alg = fix_svd - # return SVDAdjoint(; fwd_alg=fix_svd, rrule_alg=alg.svd_alg.rrule_alg) - # return SVDAdjoint() + SVDAdjoint(; fwd_alg=fix_svd, rrule_alg=alg.svd_alg.rrule_alg) else alg.svd_alg end diff --git a/src/utility/svd.jl b/src/utility/svd.jl index 5880b30d..a848eadc 100644 --- a/src/utility/svd.jl +++ b/src/utility/svd.jl @@ -24,16 +24,6 @@ removes the divergences from the adjoint. broadening::B = nothing end # Keep truncation algorithm separate to be able to specify CTMRG dependent information -# function ChainRulesCore.rrule( -# ::Type{SVDAdjoint{F,R,B}}, -# fwd_alg::F=TensorKit.SVD(), -# rrule_alg::R=nothing, -# broadening::B=nothing, -# ) where {F,R,B} -# svdadjoint_pullback(_) = NoTangent() -# return SVDAdjoint(; fwd_alg, rrule_alg, broadening), svdadjoint_pullback -# end - """ PEPSKit.tsvd(t::AbstractTensorMap, alg; trunc=notrunc(), p=2) @@ -159,7 +149,9 @@ function ChainRulesCore.rrule( n_vals = length(Sdc) lvecs = Vector{Vector{scalartype(t)}}(eachcol(Uc)) rvecs = Vector{Vector{scalartype(t)}}(eachcol(Vc')) - minimal_info = KrylovKit.ConvergenceInfo(length(Sdc), nothing, nothing, -1, -1) # Just supply converged to SVD pullback + minimal_info = KrylovKit.ConvergenceInfo(length(Sdc), nothing, nothing, -1, -1) # Only num. converged is used + minimal_alg = GKL(; tol=1e-6) # Only tolerance is used for gauge sensitivity + # TODO: How do we not hard-code this tolerance? if ΔUc isa AbstractZero && ΔVc isa AbstractZero # Handle ZeroTangent singular vectors Δlvecs = fill(ZeroTangent(), n_vals) @@ -179,7 +171,7 @@ function ChainRulesCore.rrule( minimal_info, block(t, c), :LR, - alg.fwd_alg.alg, + minimal_alg, alg.rrule_alg, ) copyto!( diff --git a/test/ctmrg/fixedsvd.jl b/test/ctmrg/fixedsvd.jl new file mode 100644 index 00000000..f038b8e2 --- /dev/null +++ b/test/ctmrg/fixedsvd.jl @@ -0,0 +1,35 @@ +using Test +using Random +using TensorKit +using PEPSKit +using PEPSKit: + ctmrg_iter, + gauge_fix, + fix_relative_phases, + fix_global_phases, + check_elementwise_convergence + +# initialize parameters +χbond = 2 +χenv = 16 +ctm_alg = CTMRG(; tol=1e-12, miniter=4, maxiter=100, verbosity=1, ctmrgscheme=:AllSides) + +# initialize states +Random.seed!(91283219347) +psi = InfinitePEPS(2, χbond) +env_conv1 = leading_boundary(CTMRGEnv(psi; Venv=ComplexSpace(χenv)), psi, ctm_alg) + +# do extra iteration to get SVD +env_conv2, info = ctmrg_iter(psi, env_conv1, ctm_alg) +env_fix, signs = gauge_fix(env_conv1, env_conv2) +@test check_elementwise_convergence(env_conv1, env_fix) + +# fix gauge of SVD +U_fix, V_fix = fix_relative_phases(info.U, info.V, signs) +svd_alg_fix = SVDAdjoint(; fwd_alg=FixedSVD(U_fix, info.S, V_fix)) +ctm_alg_fix = CTMRG(; svd_alg=svd_alg_fix, trscheme=notrunc(), ctmrgscheme=:AllSides) + +# do iteration with FixedSVD +env_fixedsvd, = ctmrg_iter(psi, env_conv1, ctm_alg_fix) +env_fixedsvd = fix_global_phases(env_conv1, env_fixedsvd) +@test check_elementwise_convergence(env_conv1, env_fixedsvd) diff --git a/test/heisenberg.jl b/test/heisenberg.jl index 8bdeb2f9..54c40972 100644 --- a/test/heisenberg.jl +++ b/test/heisenberg.jl @@ -8,11 +8,18 @@ using OptimKit # initialize parameters χbond = 2 χenv = 16 -ctm_alg = CTMRG(; tol=1e-10, miniter=4, maxiter=100, verbosity=1, trscheme=truncdim(χenv)) +ctm_alg = CTMRG(; + tol=1e-10, + miniter=4, + maxiter=100, + verbosity=1, + trscheme=truncdim(χenv), + svd_alg=SVDAdjoint(; fwd_alg=TensorKit.SVD(), rrule_alg=GMRES(; tol=1e-12)), +) opt_alg = PEPSOptimize(; boundary_alg=ctm_alg, optimizer=LBFGS(4; maxiter=100, gradtol=1e-3, verbosity=2), - gradient_alg=LinSolver(solver=GMRES(; tol=1e-6, maxiter=100)), + gradient_alg=LinSolver(; solver=GMRES(; tol=1e-6, maxiter=100)), reuse_env=true, verbosity=2, ) diff --git a/test/runtests.jl b/test/runtests.jl index c0c48f62..99810728 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -20,6 +20,9 @@ end @time @safetestset "SVD wrapper" begin include("ctmrg/svd_wrapper.jl") end + @time @safetestset "SVD wrapper" begin + include("ctmrg/fixedsvd.jl") + end @time @safetestset "SVD wrapper" begin include("ctmrg/unitcell.jl") end From 584380c18a8171e17e1ed8036b449530982a3e31 Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Wed, 10 Jul 2024 19:39:14 +0200 Subject: [PATCH 033/213] Write new contractions in new index convention, add `@autoopt` --- src/algorithms/ctmrg.jl | 20 +++---- src/algorithms/ctmrg_all_sides.jl | 91 ++++++++++++++++--------------- 2 files changed, 57 insertions(+), 54 deletions(-) diff --git a/src/algorithms/ctmrg.jl b/src/algorithms/ctmrg.jl index 9ed6a71e..d9144827 100644 --- a/src/algorithms/ctmrg.jl +++ b/src/algorithms/ctmrg.jl @@ -371,16 +371,16 @@ function correlation_length(peps::InfinitePEPS, env::CTMRGEnv; howmany=2) ξ = Array{Float64,3}(undef, (2, size(peps)...)) # First index picks horizontal or vertical direction λ = Array{ComplexF64,4}(undef, (2, howmany, size(peps)...)) for r in 1:size(peps, 1), c in 1:size(peps, 2) - @tensor transferh[-1 -2 -3 -4; -5 -6 -7 -8] := - env.edges[NORTH, _prev(r, end), c][-1 1 2; -5] * - peps[r, c][5; 1 -6 3 -2] * - conj(peps[r, c][5; 2 -7 4 -3]) * - env.edges[SOUTH, _next(r, end), c][-8 3 4; -4] - @tensor transferv[-1 -2 -3 -4; -5 -6 -7 -8] := - env.edges[EAST, r, _next(c, end)][-5 1 2; -1] * - peps[r, c][5; -6 1 -2 3] * - conj(peps[r, c][5; -7 2 -3 4]) * - env.edges[WEST, r, _prev(c, end)][-4 3 4; -8] + @autoopt @tensor transferh[χ_LT D_Lab D_Lbe χ_LB; χ_RT D_Rab D_Rbe χ_RB] := + env.edges[NORTH, _prev(r, end), c][χ_LT D1 D2; χ_RT] * + peps[r, c][d; D1 D_Rab D3 D_Lab] * + conj(peps[r, c][d; D2 D_Rbe D4 D_Lbe]) * + env.edges[SOUTH, _next(r, end), c][χ_RB D3 D4; χ_LB] + @autoopt @tensor transferv[χ_TL D_Tab D_Tbe χ_TL; χ_BL D_Bab D_Bbe χ_BR] := + env.edges[EAST, r, _next(c, end)][χ_TR D1 D2; χ_BR] * + peps[r, c][d; D_Tab D1 D_Bab D3] * + conj(peps[r, c][d; D_Tbe D2 D_Bbe D4]) * + env.edges[WEST, r, _prev(c, end)][χ_BL D3 D4; χ_TL] function lintransfer(v, t) @tensor v′[-1 -2 -3 -4] := t[-1 -2 -3 -4; 1 2 3 4] * v[1 2 3 4] diff --git a/src/algorithms/ctmrg_all_sides.jl b/src/algorithms/ctmrg_all_sides.jl index 1a34ac45..2d87fb9b 100644 --- a/src/algorithms/ctmrg_all_sides.jl +++ b/src/algorithms/ctmrg_all_sides.jl @@ -56,7 +56,10 @@ function build_projectors(Q, env::CTMRGEnv, alg::ProjectorAlg{A,T}) where {A,T} P_left, P_right = Zygote.Buffer.(projector_type(env.edges)) U, V = Zygote.Buffer.(projector_type(env.edges)) Stype = tensormaptype( # Corner type but with real numbers - spacetype(env.corners[1]), 1, 1, Matrix{real(scalartype(env.corners[1]))} + spacetype(env.corners[1]), + 1, + 1, + Matrix{real(scalartype(env.corners[1]))}, ) S = Zygote.Buffer(Array{Stype,3}(undef, size(env.corners))) ϵ = 0.0 @@ -87,9 +90,9 @@ function build_projectors(Q, env::CTMRGEnv, alg::ProjectorAlg{A,T}) where {A,T} else alg.svd_alg end - @autoopt @tensor QQ[χ_EB D_EBabove D_EBbelow; χ_ET D_ETabove D_ETbelow] := - Q[dir, r, c][χ_EB D_EBabove D_EBbelow; χ D1 D2] * - Q[_next(dir, 4), next_rc...][χ D1 D2; χ_ET D_ETabove D_ETbelow] + @autoopt @tensor QQ[χ_in D_inabove D_inbelow; χ_out D_outabove D_outbelow] := + Q[dir, r, c][χ_in D_inabove D_inbelow; χ D1 D2] * + Q[_next(dir, 4), next_rc...][χ D1 D2; χ_out D_outabove D_outbelow] U_local, S_local, V_local, ϵ_local = PEPSKit.tsvd!(QQ, svd_alg; trunc=trscheme) U[dir, r, c] = U_local S[dir, r, c] = S_local @@ -124,47 +127,47 @@ function renormalize_corners_edges(state, env::CTMRGEnv, Q, P_left, P_right) rnext = _next(r, size(state, 1)) cprev = _prev(c, size(state, 2)) cnext = _next(c, size(state, 2)) - @diffset @tensor corners[NORTHWEST, r, c][-1; -2] := - P_right[WEST, rnext, c][-1; 1 2 3] * - Q[NORTHWEST, r, c][1 2 3; 4 5 6] * - P_left[NORTH, r, c][4 5 6; -2] - @diffset @tensor corners[NORTHEAST, r, c][-1; -2] := - P_right[NORTH, r, cprev][-1; 1 2 3] * - Q[NORTHEAST, r, c][1 2 3; 4 5 6] * - P_left[EAST, r, c][4 5 6; -2] - @diffset @tensor corners[SOUTHEAST, r, c][-1; -2] := - P_right[EAST, rprev, c][-1; 1 2 3] * - Q[SOUTHEAST, r, c][1 2 3; 4 5 6] * - P_left[SOUTH, r, c][4 5 6; -2] - @diffset @tensor corners[SOUTHWEST, r, c][-1; -2] := - P_right[SOUTH, r, cnext][-1; 1 2 3] * - Q[SOUTHWEST, r, c][1 2 3; 4 5 6] * - P_left[WEST, r, c][4 5 6; -2] + @diffset @autoopt @tensor corners[NORTHWEST, r, c][χ_S; χ_E] := + P_right[WEST, rnext, c][χ_S; χ1 D1 D2] * + Q[NORTHWEST, r, c][χ1 D1 D2; χ2 D3 D4] * + P_left[NORTH, r, c][χ2 D3 D4; χ_E] + @diffset @autoopt @tensor corners[NORTHEAST, r, c][χ_W; χ_S] := + P_right[NORTH, r, cprev][χ_W; χ1 D1 D2] * + Q[NORTHEAST, r, c][χ1 D1 D2; χ2 D3 D4] * + P_left[EAST, r, c][χ2 D3 D4; χ_S] + @diffset @autoopt @tensor corners[SOUTHEAST, r, c][χ_N; χ_W] := + P_right[EAST, rprev, c][χ_N; χ1 D1 D2] * + Q[SOUTHEAST, r, c][χ1 D1 D2; χ2 D3 D4] * + P_left[SOUTH, r, c][χ2 D3 D4; χ_W] + @diffset @autoopt @tensor corners[SOUTHWEST, r, c][χ_E; χ_N] := + P_right[SOUTH, r, cnext][χ_E; χ1 D1 D2] * + Q[SOUTHWEST, r, c][χ1 D1 D2; χ2 D3 D4] * + P_left[WEST, r, c][χ2 D3 D4; χ_N] - @diffset @tensor edges[NORTH, r, c][-1 -2 -3; -4] := - env.edges[NORTH, rprev, c][1 2 3; 4] * - state[r, c][9; 2 5 -2 7] * - conj(state[r, c][9; 3 6 -3 8]) * - P_left[NORTH, r, c][4 5 6; -4] * - P_right[NORTH, r, cprev][-1; 1 7 8] - @diffset @tensor edges[EAST, r, c][-1 -2 -3; -4] := - env.edges[EAST, r, _next(c, end)][1 2 3; 4] * - state[r, c][9; 7 2 5 -2] * - conj(state[r, c][9; 8 3 6 -3]) * - P_left[EAST, r, c][4 5 6; -4] * - P_right[EAST, rprev, c][-1; 1 7 8] - @diffset @tensor edges[SOUTH, r, c][-1 -2 -3; -4] := - env.edges[SOUTH, _next(r, end), c][1 2 3; 4] * - state[r, c][9; -2 7 2 5] * - conj(state[r, c][9; -3 8 3 6]) * - P_left[SOUTH, r, c][4 5 6; -4] * - P_right[SOUTH, r, cnext][-1; 1 7 8] - @diffset @tensor edges[WEST, r, c][-1 -2 -3; -4] := - env.edges[WEST, r, _prev(c, end)][1 2 3; 4] * - state[r, c][9; 5 -2 7 2] * - conj(state[r, c][9; 6 -3 8 3]) * - P_left[WEST, r, c][4 5 6; -4] * - P_right[WEST, rnext, c][-1; 1 7 8] + @diffset @autoopt @tensor edges[NORTH, r, c][χ_W D_Sab D_Sbe; χ_E] := + env.edges[NORTH, rprev, c][χ1 D1 D2; χ2] * + state[r, c][d; D1 D3 D_Sab D5] * + conj(state[r, c][d; D2 D4 D_Sbe D6]) * + P_left[NORTH, r, c][χ2 D3 D4; χ_E] * + P_right[NORTH, r, cprev][χ_W; χ1 D5 D6] + @diffset @autoopt @tensor edges[EAST, r, c][χ_N D_Wab D_Wbe; χ_S] := + env.edges[EAST, r, _next(c, end)][χ1 D1 D2; χ2] * + state[r, c][d; D5 D1 D3 D_Wab] * + conj(state[r, c][d; D6 D2 D4 D_Wbe]) * + P_left[EAST, r, c][χ2 D3 D4; χ_S] * + P_right[EAST, rprev, c][χ_N; χ1 D5 D6] + @diffset @autoopt @tensor edges[SOUTH, r, c][χ_E D_Nab D_Nbe; χ_W] := + env.edges[SOUTH, _next(r, end), c][χ1 D1 D2; χ2] * + state[r, c][d; D_Nab D5 D1 D3] * + conj(state[r, c][d; D_Nbe D6 D2 D4]) * + P_left[SOUTH, r, c][χ2 D3 D4; χ_W] * + P_right[SOUTH, r, cnext][χ_E; χ1 D5 D6] + @diffset @autoopt @tensor edges[WEST, r, c][χ_S D_Eab D_Ebe; χ_N] := + env.edges[WEST, r, _prev(c, end)][χ1 D1 D2; χ2] * + state[r, c][d; D3 D_Eab D5 D1] * + conj(state[r, c][d; D4 D_Ebe D6 D2]) * + P_left[WEST, r, c][χ2 D3 D4; χ_N] * + P_right[WEST, rnext, c][χ_S; χ1 D5 D6] end @diffset corners[:, :, :] ./= norm.(corners[:, :, :]) From 3de5685e39d4b8b115b841d425bf4e26905fb337 Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Thu, 11 Jul 2024 11:21:51 +0200 Subject: [PATCH 034/213] Add :FixedIter and :LeftMoves safeguard, pdate tests --- src/PEPSKit.jl | 2 +- src/algorithms/peps_opt.jl | 66 ++++++++++++++++++++++++++++++-------- test/ctmrg/fixedsvd.jl | 63 +++++++++++++++++++++++++----------- test/heisenberg.jl | 5 +-- 4 files changed, 101 insertions(+), 35 deletions(-) diff --git a/src/PEPSKit.jl b/src/PEPSKit.jl index 1e1ec392..07ee4f40 100644 --- a/src/PEPSKit.jl +++ b/src/PEPSKit.jl @@ -61,7 +61,7 @@ module Defaults const fpgrad_tol = 1e-6 end -export SVDAdjoint, IterSVD, FixedSVD, NonTruncSVDAdjoint +export SVDAdjoint, IterSVD, NonTruncSVDAdjoint export FixedSpaceTruncation, ProjectorAlg, CTMRG, CTMRGEnv, correlation_length export LocalOperator export expectation_value, costfun diff --git a/src/algorithms/peps_opt.jl b/src/algorithms/peps_opt.jl index c6d724ae..507e0924 100644 --- a/src/algorithms/peps_opt.jl +++ b/src/algorithms/peps_opt.jl @@ -2,9 +2,15 @@ abstract type GradMode{F} end """ struct GeomSum(; maxiter=Defaults.fpgrad_maxiter, tol=Defaults.fpgrad_tol, - verbosity=0, iterscheme=:FixedIter) <: GradMode + verbosity=0, iterscheme=:FixedIter) <: GradMode{iterscheme} Gradient mode for CTMRG using explicit evaluation of the geometric sum. + +With `iterscheme` the style of CTMRG iteration which is being differentiated can be chosen. +If set to `:FixedIter`, the differentiated CTMRG iteration is assumed to have a pre-computed +SVD of the environments with a fixed set of gauges. Alternatively, if set to `:DiffGauge`, +the differentiated iteration consists of a CTMRG iteration and a subsequent gauge fixing step, +such that `gauge_fix` will also be differentiated everytime a CTMRG derivative is computed. """ struct GeomSum{F} <: GradMode{F} maxiter::Int @@ -22,9 +28,15 @@ end """ struct ManualIter(; maxiter=Defaults.fpgrad_maxiter, tol=Defaults.fpgrad_tol, - verbosity=0, iterscheme=:FixedIter) <: GradMode + verbosity=0, iterscheme=:FixedIter) <: GradMode{iterscheme} Gradient mode for CTMRG using manual iteration to solve the linear problem. + +With `iterscheme` the style of CTMRG iteration which is being differentiated can be chosen. +If set to `:FixedIter`, the differentiated CTMRG iteration is assumed to have a pre-computed +SVD of the environments with a fixed set of gauges. Alternatively, if set to `:DiffGauge`, +the differentiated iteration consists of a CTMRG iteration and a subsequent gauge fixing step, +such that `gauge_fix` will also be differentiated everytime a CTMRG derivative is computed. """ struct ManualIter{F} <: GradMode{F} maxiter::Int @@ -41,10 +53,16 @@ function ManualIter(; end """ - struct LinSolver(; solver=KrylovKit.GMRES(), iterscheme=:FixedIter) <: GradMode + struct LinSolver(; solver=KrylovKit.GMRES(), iterscheme=:FixedIter) <: GradMode{iterscheme} Gradient mode wrapper around `KrylovKit.LinearSolver` for solving the gradient linear problem using iterative solvers. + +With `iterscheme` the style of CTMRG iteration which is being differentiated can be chosen. +If set to `:FixedIter`, the differentiated CTMRG iteration is assumed to have a pre-computed +SVD of the environments with a fixed set of gauges. Alternatively, if set to `:DiffGauge`, +the differentiated iteration consists of a CTMRG iteration and a subsequent gauge fixing step, +such that `gauge_fix` will also be differentiated everytime a CTMRG derivative is computed. """ struct LinSolver{F} <: GradMode{F} solver::KrylovKit.LinearSolver @@ -57,8 +75,8 @@ function LinSolver(; end """ - PEPSOptimize{G}(; boundary_alg = CTMRG(), optimizer::OptimKit.OptimizationAlgorithm = LBFGS() - reuse_env::Bool = true, gradient_alg::G, verbosity::Int = 0) + PEPSOptimize{G}(; boundary_alg=CTMRG(), optimizer::OptimKit.OptimizationAlgorithm=LBFGS() + reuse_env::Bool=true, gradient_alg::G=LinSolver(), verbosity::Int=0) Algorithm struct that represent PEPS ground-state optimization using AD. Set the algorithm to contract the infinite PEPS in `boundary_alg`; @@ -69,14 +87,36 @@ step by setting `reuse_env` to true. Otherwise a random environment is used at e step. The CTMRG gradient itself is computed using the `gradient_alg` algorithm. Different levels of output verbosity can be activated using `verbosity` (0, 1 or 2). """ -@kwdef struct PEPSOptimize{G} - boundary_alg::CTMRG = CTMRG() # Algorithm to find boundary environment - optimizer::OptimKit.OptimizationAlgorithm = LBFGS( - 4; maxiter=100, gradtol=1e-4, verbosity=2 - ) - reuse_env::Bool = true # Reuse environment of previous optimization as initial guess for next - gradient_alg::G = GeomSum() # Algorithm to solve gradient linear problem - verbosity::Int = 0 +struct PEPSOptimize{G} + boundary_alg::CTMRG + optimizer::OptimKit.OptimizationAlgorithm + reuse_env::Bool + gradient_alg::G + verbosity::Int + + function PEPSOptimize( # Inner constructor to prohibit illegal setting combinations + boundary_alg::CTMRG{S}, + optimizer, + reuse_env, + gradient_alg::G, + verbosity, + ) where {S,G} + if gradient_alg isa GradMode + if S == :LeftMoves && G.parameters[1] == :FixedIter + throw(ArgumentError(":LeftMoves and :FixedIter are not compatible")) + end + end + return new{G}(boundary_alg, optimizer, reuse_env, gradient_alg, verbosity) + end +end +function PEPSOptimize(; + boundary_alg=CTMRG(), + optimizer=LBFGS(4; maxiter=100, gradtol=1e-4, verbosity=2), + reuse_env=true, + gradient_alg=LinSolver(), + verbosity=0, +) + return PEPSOptimize(boundary_alg, optimizer, reuse_env, gradient_alg, verbosity) end """ diff --git a/test/ctmrg/fixedsvd.jl b/test/ctmrg/fixedsvd.jl index f038b8e2..9b0ec4d1 100644 --- a/test/ctmrg/fixedsvd.jl +++ b/test/ctmrg/fixedsvd.jl @@ -3,6 +3,7 @@ using Random using TensorKit using PEPSKit using PEPSKit: + FixedSVD, ctmrg_iter, gauge_fix, fix_relative_phases, @@ -14,22 +15,46 @@ using PEPSKit: χenv = 16 ctm_alg = CTMRG(; tol=1e-12, miniter=4, maxiter=100, verbosity=1, ctmrgscheme=:AllSides) -# initialize states -Random.seed!(91283219347) -psi = InfinitePEPS(2, χbond) -env_conv1 = leading_boundary(CTMRGEnv(psi; Venv=ComplexSpace(χenv)), psi, ctm_alg) - -# do extra iteration to get SVD -env_conv2, info = ctmrg_iter(psi, env_conv1, ctm_alg) -env_fix, signs = gauge_fix(env_conv1, env_conv2) -@test check_elementwise_convergence(env_conv1, env_fix) - -# fix gauge of SVD -U_fix, V_fix = fix_relative_phases(info.U, info.V, signs) -svd_alg_fix = SVDAdjoint(; fwd_alg=FixedSVD(U_fix, info.S, V_fix)) -ctm_alg_fix = CTMRG(; svd_alg=svd_alg_fix, trscheme=notrunc(), ctmrgscheme=:AllSides) - -# do iteration with FixedSVD -env_fixedsvd, = ctmrg_iter(psi, env_conv1, ctm_alg_fix) -env_fixedsvd = fix_global_phases(env_conv1, env_fixedsvd) -@test check_elementwise_convergence(env_conv1, env_fixedsvd) +@testset "(1, 1) unit cell" begin + # initialize states + Random.seed!(91283219347) + psi = InfinitePEPS(2, χbond) + env_conv1 = leading_boundary(CTMRGEnv(psi; Venv=ComplexSpace(χenv)), psi, ctm_alg) + + # do extra iteration to get SVD + env_conv2, info = ctmrg_iter(psi, env_conv1, ctm_alg) + env_fix, signs = gauge_fix(env_conv1, env_conv2) + @test check_elementwise_convergence(env_conv1, env_fix) + + # fix gauge of SVD + U_fix, V_fix = fix_relative_phases(info.U, info.V, signs) + svd_alg_fix = SVDAdjoint(; fwd_alg=FixedSVD(U_fix, info.S, V_fix)) + ctm_alg_fix = CTMRG(; svd_alg=svd_alg_fix, trscheme=notrunc(), ctmrgscheme=:AllSides) + + # do iteration with FixedSVD + env_fixedsvd, = ctmrg_iter(psi, env_conv1, ctm_alg_fix) + env_fixedsvd = fix_global_phases(env_conv1, env_fixedsvd) + @test check_elementwise_convergence(env_conv1, env_fixedsvd) +end + +@testset "(3, 4) unit cell" begin + # initialize states + Random.seed!(91283219347) + psi = InfinitePEPS(2, χbond; unitcell=(3, 4)) + env_conv1 = leading_boundary(CTMRGEnv(psi; Venv=ComplexSpace(χenv)), psi, ctm_alg) + + # do extra iteration to get SVD + env_conv2, info = ctmrg_iter(psi, env_conv1, ctm_alg) + env_fix, signs = gauge_fix(env_conv1, env_conv2) + @test check_elementwise_convergence(env_conv1, env_fix) + + # fix gauge of SVD + U_fix, V_fix = fix_relative_phases(info.U, info.V, signs) + svd_alg_fix = SVDAdjoint(; fwd_alg=FixedSVD(U_fix, info.S, V_fix)) + ctm_alg_fix = CTMRG(; svd_alg=svd_alg_fix, trscheme=notrunc(), ctmrgscheme=:AllSides) + + # do iteration with FixedSVD + env_fixedsvd, = ctmrg_iter(psi, env_conv1, ctm_alg_fix) + env_fixedsvd = fix_global_phases(env_conv1, env_fixedsvd) + @test check_elementwise_convergence(env_conv1, env_fixedsvd) +end diff --git a/test/heisenberg.jl b/test/heisenberg.jl index 54c40972..352caaa9 100644 --- a/test/heisenberg.jl +++ b/test/heisenberg.jl @@ -14,12 +14,13 @@ ctm_alg = CTMRG(; maxiter=100, verbosity=1, trscheme=truncdim(χenv), - svd_alg=SVDAdjoint(; fwd_alg=TensorKit.SVD(), rrule_alg=GMRES(; tol=1e-12)), + svd_alg=SVDAdjoint(; fwd_alg=TensorKit.SVD(), rrule_alg=GMRES(; tol=1e-10)), + ctmrgscheme=:AllSides ) opt_alg = PEPSOptimize(; boundary_alg=ctm_alg, optimizer=LBFGS(4; maxiter=100, gradtol=1e-3, verbosity=2), - gradient_alg=LinSolver(; solver=GMRES(; tol=1e-6, maxiter=100)), + gradient_alg=LinSolver(; solver=GMRES(; tol=1e-6, maxiter=100), iterscheme=:FixedIter), reuse_env=true, verbosity=2, ) From 8e9ca875e4264cd11c19e6c708837a4c04df24d5 Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Thu, 11 Jul 2024 11:45:43 +0200 Subject: [PATCH 035/213] Update tests --- test/ctmrg/fixediter.jl | 75 +++++++++++++++++++++++++++++++++++++++++ test/ctmrg/fixedsvd.jl | 60 --------------------------------- test/ctmrg/gradients.jl | 10 ++++-- test/runtests.jl | 2 +- 4 files changed, 84 insertions(+), 63 deletions(-) create mode 100644 test/ctmrg/fixediter.jl delete mode 100644 test/ctmrg/fixedsvd.jl diff --git a/test/ctmrg/fixediter.jl b/test/ctmrg/fixediter.jl new file mode 100644 index 00000000..3e8a634c --- /dev/null +++ b/test/ctmrg/fixediter.jl @@ -0,0 +1,75 @@ +using Test +using Random +using TensorKit +using PEPSKit +using PEPSKit: + FixedSVD, + ctmrg_iter, + gauge_fix, + fix_relative_phases, + fix_global_phases, + check_elementwise_convergence + +# initialize parameters +χbond = 2 +χenv = 16 +svd_algs = [SVDAdjoint(; fwd_alg=TensorKit.SVD()), SVDAdjoint(; fwd_alg=IterSVD())] +unitcells = [(1, 1), (3, 4)] + +# test for element-wise convergence after application of FixedIter step +@testset "$unitcell unit cell with $(typeof(svd_alg.fwd_alg))" for (unitcell, svd_alg) in + Iterators.product( + unitcells, svd_algs +) + ctm_alg = CTMRG(; tol=1e-12, verbosity=1, ctmrgscheme=:AllSides, svd_alg) + + # initialize states + Random.seed!(91283219347) + psi = InfinitePEPS(2, χbond; unitcell) + env_conv1 = leading_boundary(CTMRGEnv(psi; Venv=ComplexSpace(χenv)), psi, ctm_alg) + + # do extra iteration to get SVD + env_conv2, info = ctmrg_iter(psi, env_conv1, ctm_alg) + env_fix, signs = gauge_fix(env_conv1, env_conv2) + @test check_elementwise_convergence(env_conv1, env_fix) + + # fix gauge of SVD + U_fix, V_fix = fix_relative_phases(info.U, info.V, signs) + svd_alg_fix = SVDAdjoint(; fwd_alg=FixedSVD(U_fix, info.S, V_fix)) + ctm_alg_fix = CTMRG(; svd_alg=svd_alg_fix, trscheme=notrunc(), ctmrgscheme=:AllSides) + + # do iteration with FixedSVD + env_fixedsvd, = ctmrg_iter(psi, env_conv1, ctm_alg_fix) + env_fixedsvd = fix_global_phases(env_conv1, env_fixedsvd) + @test check_elementwise_convergence(env_conv1, env_fixedsvd) +end + +## +# ctm_alg = CTMRG(; +# tol=1e-12, +# miniter=4, +# maxiter=100, +# verbosity=1, +# ctmrgscheme=:AllSides, +# svd_alg=SVDAdjoint(; fwd_alg=IterSVD()), +# ) + +# # initialize states +# Random.seed!(91283219347) +# psi = InfinitePEPS(2, χbond) +# env_conv1 = leading_boundary(CTMRGEnv(psi; Venv=ComplexSpace(χenv)), psi, ctm_alg); + +# # do extra iteration to get SVD +# env_conv2, info = ctmrg_iter(psi, env_conv1, ctm_alg); +# env_fix, signs = gauge_fix(env_conv1, env_conv2); +# @test check_elementwise_convergence(env_conv1, env_fix) + +# # fix gauge of SVD +# U_fix, V_fix = fix_relative_phases(info.U, info.V, signs); +# svd_alg_fix = SVDAdjoint(; fwd_alg=FixedSVD(U_fix, info.S, V_fix)); +# ctm_alg_fix = CTMRG(; svd_alg=svd_alg_fix, trscheme=notrunc(), ctmrgscheme=:AllSides); + +# # do iteration with FixedSVD +# env_fixedsvd, = ctmrg_iter(psi, env_conv1, ctm_alg_fix); +# env_fixedsvd = fix_global_phases(env_conv1, env_fixedsvd); +# @test check_elementwise_convergence(env_conv1, env_fixedsvd) \ No newline at end of file diff --git a/test/ctmrg/fixedsvd.jl b/test/ctmrg/fixedsvd.jl deleted file mode 100644 index 9b0ec4d1..00000000 --- a/test/ctmrg/fixedsvd.jl +++ /dev/null @@ -1,60 +0,0 @@ -using Test -using Random -using TensorKit -using PEPSKit -using PEPSKit: - FixedSVD, - ctmrg_iter, - gauge_fix, - fix_relative_phases, - fix_global_phases, - check_elementwise_convergence - -# initialize parameters -χbond = 2 -χenv = 16 -ctm_alg = CTMRG(; tol=1e-12, miniter=4, maxiter=100, verbosity=1, ctmrgscheme=:AllSides) - -@testset "(1, 1) unit cell" begin - # initialize states - Random.seed!(91283219347) - psi = InfinitePEPS(2, χbond) - env_conv1 = leading_boundary(CTMRGEnv(psi; Venv=ComplexSpace(χenv)), psi, ctm_alg) - - # do extra iteration to get SVD - env_conv2, info = ctmrg_iter(psi, env_conv1, ctm_alg) - env_fix, signs = gauge_fix(env_conv1, env_conv2) - @test check_elementwise_convergence(env_conv1, env_fix) - - # fix gauge of SVD - U_fix, V_fix = fix_relative_phases(info.U, info.V, signs) - svd_alg_fix = SVDAdjoint(; fwd_alg=FixedSVD(U_fix, info.S, V_fix)) - ctm_alg_fix = CTMRG(; svd_alg=svd_alg_fix, trscheme=notrunc(), ctmrgscheme=:AllSides) - - # do iteration with FixedSVD - env_fixedsvd, = ctmrg_iter(psi, env_conv1, ctm_alg_fix) - env_fixedsvd = fix_global_phases(env_conv1, env_fixedsvd) - @test check_elementwise_convergence(env_conv1, env_fixedsvd) -end - -@testset "(3, 4) unit cell" begin - # initialize states - Random.seed!(91283219347) - psi = InfinitePEPS(2, χbond; unitcell=(3, 4)) - env_conv1 = leading_boundary(CTMRGEnv(psi; Venv=ComplexSpace(χenv)), psi, ctm_alg) - - # do extra iteration to get SVD - env_conv2, info = ctmrg_iter(psi, env_conv1, ctm_alg) - env_fix, signs = gauge_fix(env_conv1, env_conv2) - @test check_elementwise_convergence(env_conv1, env_fix) - - # fix gauge of SVD - U_fix, V_fix = fix_relative_phases(info.U, info.V, signs) - svd_alg_fix = SVDAdjoint(; fwd_alg=FixedSVD(U_fix, info.S, V_fix)) - ctm_alg_fix = CTMRG(; svd_alg=svd_alg_fix, trscheme=notrunc(), ctmrgscheme=:AllSides) - - # do iteration with FixedSVD - env_fixedsvd, = ctmrg_iter(psi, env_conv1, ctm_alg_fix) - env_fixedsvd = fix_global_phases(env_conv1, env_fixedsvd) - @test check_elementwise_convergence(env_conv1, env_fixedsvd) -end diff --git a/test/ctmrg/gradients.jl b/test/ctmrg/gradients.jl index 978d65e7..3e3a7070 100644 --- a/test/ctmrg/gradients.jl +++ b/test/ctmrg/gradients.jl @@ -17,9 +17,15 @@ models = [square_lattice_heisenberg(), square_lattice_pwave()] names = ["Heisenberg", "p-wave superconductor"] Random.seed!(42039482030) tol = 1e-8 -boundary_alg = CTMRG(; tol=tol, miniter=4, maxiter=100, verbosity=0) +boundary_alg = CTMRG(; tol=tol, miniter=4, maxiter=100, verbosity=0, ctmrgscheme=:AllSides) gradmodes = [ - nothing, GeomSum(; tol), ManualIter(; tol), KrylovKit.GMRES(; tol=tol, maxiter=10) + nothing, + GeomSum(; tol, iterscheme=:FixedIter), + GeomSum(; tol, iterscheme=:DiffGauge), + ManualIter(; tol, iterscheme=:FixedIter), + ManualIter(; tol, iterscheme=:DiffGauge), + LinSolve(; alg=KrylovKit.GMRES(; tol=tol, maxiter=10), iterscheme=:FixedIter), + LinSolve(; alg=KrylovKit.GMRES(; tol=tol, maxiter=10), iterscheme=:DiffGauge), ] steps = -0.01:0.005:0.01 diff --git a/test/runtests.jl b/test/runtests.jl index 99810728..143e188b 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -21,7 +21,7 @@ end include("ctmrg/svd_wrapper.jl") end @time @safetestset "SVD wrapper" begin - include("ctmrg/fixedsvd.jl") + include("ctmrg/fixediter.jl") end @time @safetestset "SVD wrapper" begin include("ctmrg/unitcell.jl") From eaa850646cf487ebb534163376f378613efe9751 Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Thu, 11 Jul 2024 15:39:55 +0200 Subject: [PATCH 036/213] Parallelize AllSides CTMRG using hacky differentiable @fwdthreads macro, separate diffset into a new file --- src/PEPSKit.jl | 1 + src/algorithms/ctmrg_all_sides.jl | 43 ++++++++++--------- src/utility/diffset.jl | 47 ++++++++++++++++++++ src/utility/util.jl | 71 ++++++++++--------------------- 4 files changed, 93 insertions(+), 69 deletions(-) create mode 100644 src/utility/diffset.jl diff --git a/src/PEPSKit.jl b/src/PEPSKit.jl index 07ee4f40..2dd1057a 100644 --- a/src/PEPSKit.jl +++ b/src/PEPSKit.jl @@ -11,6 +11,7 @@ using ChainRulesCore, Zygote include("utility/util.jl") include("utility/svd.jl") include("utility/rotations.jl") +include("utility/diffset.jl") include("utility/hook_pullback.jl") include("utility/autoopt.jl") diff --git a/src/algorithms/ctmrg_all_sides.jl b/src/algorithms/ctmrg_all_sides.jl index 2d87fb9b..046ae3ee 100644 --- a/src/algorithms/ctmrg_all_sides.jl +++ b/src/algorithms/ctmrg_all_sides.jl @@ -13,35 +13,38 @@ function ctmrg_iter(state, env::CTMRGEnv, alg::CTMRG{:AllSides}) end # Compute enlarged corners and edges for all directions and unit cell entries -function enlarge_corners_edges(state, env::CTMRGEnv) - map(Iterators.product(axes(env.corners)...)) do (dir, r, c) +function enlarge_corners_edges(state, env::CTMRGEnv{C,T}) where {C,T} + Qtype = tensormaptype(spacetype(C), 3, 3, storagetype(C)) + Q = Zygote.Buffer(Array{Qtype,3}(undef, size(env.corners))) + drc_combinations = collect(Iterators.product(axes(env.corners)...)) + @fwdthreads for (dir, r, c) in drc_combinations rprev = _prev(r, size(state, 1)) rnext = _next(r, size(state, 1)) cprev = _prev(c, size(state, 2)) cnext = _next(c, size(state, 2)) - if dir == NORTHWEST - return northwest_corner( + Q[dir, r, c] = if dir == NORTHWEST + northwest_corner( env.edges[WEST, r, cprev], env.corners[NORTHWEST, rprev, cprev], env.edges[NORTH, rprev, c], state[r, c], ) elseif dir == NORTHEAST - return northeast_corner( + northeast_corner( env.edges[NORTH, rprev, c], env.corners[NORTHEAST, rprev, cnext], env.edges[EAST, r, cnext], state[r, c], ) elseif dir == SOUTHEAST - return southeast_corner( + southeast_corner( env.edges[EAST, r, cnext], env.corners[SOUTHEAST, rnext, cnext], env.edges[SOUTH, rnext, c], state[r, c], ) elseif dir == SOUTHWEST - return southwest_corner( + southwest_corner( env.edges[SOUTH, rnext, c], env.corners[SOUTHWEST, rnext, cprev], env.edges[WEST, r, cprev], @@ -49,31 +52,28 @@ function enlarge_corners_edges(state, env::CTMRGEnv) ) end end + + return copy(Q) end # Build projectors from SVD and enlarged corners -function build_projectors(Q, env::CTMRGEnv, alg::ProjectorAlg{A,T}) where {A,T} +function build_projectors(Q, env::CTMRGEnv{C,E}, alg::ProjectorAlg{A,T}) where {C,E,A,T} P_left, P_right = Zygote.Buffer.(projector_type(env.edges)) U, V = Zygote.Buffer.(projector_type(env.edges)) - Stype = tensormaptype( # Corner type but with real numbers - spacetype(env.corners[1]), - 1, - 1, - Matrix{real(scalartype(env.corners[1]))}, - ) + Stype = tensormaptype(spacetype(C), 1, 1, Matrix{real(scalartype(E))}) # Corner type but with real numbers S = Zygote.Buffer(Array{Stype,3}(undef, size(env.corners))) ϵ = 0.0 - rsize, csize = size(env.corners)[2:3] - for dir in 1:4, r in 1:rsize, c in 1:csize + drc_combinations = collect(Iterators.product(axes(env.corners)...)) + @fwdthreads for (dir, r, c) in drc_combinations # Row-column index of next enlarged corner next_rc = if dir == 1 - (r, _next(c, csize)) + (r, _next(c, size(env.corners, 3))) elseif dir == 2 - (_next(r, rsize), c) + (_next(r, size(env.corners, 2)), c) elseif dir == 3 - (r, _prev(c, csize)) + (r, _prev(c, size(env.corners, 3))) elseif dir == 4 - (_prev(r, rsize), c) + (_prev(r, size(env.corners, 2)), c) end # SVD half-infinite environment @@ -122,7 +122,8 @@ end function renormalize_corners_edges(state, env::CTMRGEnv, Q, P_left, P_right) corners::typeof(env.corners) = copy(env.corners) edges::typeof(env.edges) = copy(env.edges) - for c in 1:size(state, 2), r in 1:size(state, 1) + rc_combinations = collect(Iterators.product(axes(state)...)) + @fwdthreads for (r, c) in rc_combinations rprev = _prev(r, size(state, 1)) rnext = _next(r, size(state, 1)) cprev = _prev(c, size(state, 2)) diff --git a/src/utility/diffset.jl b/src/utility/diffset.jl new file mode 100644 index 00000000..80fc14cf --- /dev/null +++ b/src/utility/diffset.jl @@ -0,0 +1,47 @@ +""" + @diffset assign + +Helper macro which allows in-place operations in the forward-pass of Zygote, but +resorts to non-mutating operations in the backwards-pass. The expression `assign` +should assign an object to an pre-existing `AbstractArray` and the use of updating +operators is also possible. This is especially needed when in-place assigning +tensors to unit-cell arrays of environments. +""" +macro diffset(ex) + return esc(parse_ex(ex)) +end +parse_ex(ex) = ex +function parse_ex(ex::Expr) + oppheads = (:(./=), :(.*=), :(.+=), :(.-=)) + opprep = (:(./), :(.*), :(.+), :(.-)) + if ex.head == :macrocall + parse_ex(macroexpand(PEPSKit, ex)) + elseif ex.head in (:(.=), :(=)) && length(ex.args) == 2 && is_indexing(ex.args[1]) + lhs = ex.args[1] + rhs = ex.args[2] + + vname = lhs.args[1] + args = lhs.args[2:end] + quote + $vname = _setindex($vname, $rhs, $(args...)) + end + elseif ex.head in oppheads && length(ex.args) == 2 && is_indexing(ex.args[1]) + hit = findfirst(x -> x == ex.head, oppheads) + rep = opprep[hit] + + lhs = ex.args[1] + rhs = ex.args[2] + + vname = lhs.args[1] + args = lhs.args[2:end] + + quote + $vname = _setindex($vname, $(rep)($lhs, $rhs), $(args...)) + end + else + return Expr(ex.head, parse_ex.(ex.args)...) + end +end + +is_indexing(ex) = false +is_indexing(ex::Expr) = ex.head == :ref diff --git a/src/utility/util.jl b/src/utility/util.jl index e66b059b..3d31c90b 100644 --- a/src/utility/util.jl +++ b/src/utility/util.jl @@ -142,54 +142,6 @@ function ChainRulesCore.rrule(::typeof(_setindex), a::AbstractArray, tv, args... return t, _setindex_pullback end -""" - @diffset assign - -Helper macro which allows in-place operations in the forward-pass of Zygote, but -resorts to non-mutating operations in the backwards-pass. The expression `assign` -should assign an object to an pre-existing `AbstractArray` and the use of updating -operators is also possible. This is especially needed when in-place assigning -tensors to unit-cell arrays of environments. -""" -macro diffset(ex) - return esc(parse_ex(ex)) -end -parse_ex(ex) = ex -function parse_ex(ex::Expr) - oppheads = (:(./=), :(.*=), :(.+=), :(.-=)) - opprep = (:(./), :(.*), :(.+), :(.-)) - if ex.head == :macrocall - parse_ex(macroexpand(PEPSKit, ex)) - elseif ex.head in (:(.=), :(=)) && length(ex.args) == 2 && is_indexing(ex.args[1]) - lhs = ex.args[1] - rhs = ex.args[2] - - vname = lhs.args[1] - args = lhs.args[2:end] - quote - $vname = _setindex($vname, $rhs, $(args...)) - end - elseif ex.head in oppheads && length(ex.args) == 2 && is_indexing(ex.args[1]) - hit = findfirst(x -> x == ex.head, oppheads) - rep = opprep[hit] - - lhs = ex.args[1] - rhs = ex.args[2] - - vname = lhs.args[1] - args = lhs.args[2:end] - - quote - $vname = _setindex($vname, $(rep)($lhs, $rhs), $(args...)) - end - else - return Expr(ex.head, parse_ex.(ex.args)...) - end -end - -is_indexing(ex) = false -is_indexing(ex::Expr) = ex.head == :ref - """ @showtypeofgrad(x) @@ -205,3 +157,26 @@ macro showtypeofgrad(x) end ) end + +""" + @fwdthreads(ex) + +Apply `Threads.@threads` only in the forward pass of the program. + +It works by wrapping the for-loop expression in an if statement where in the forward pass +the loop in computed in parallel using `Threads.@threads`, whereas in the backwards pass +the `Threads.@threads` is omitted in order to make the expression differentiable. +""" +macro fwdthreads(ex) + @assert ex.head === :for "@fwdthreads expects a for loop:\n$ex" + + diffable_ex = quote + if Zygote.isderiving() + $ex + else + Threads.@threads $ex + end + end + + return esc(diffable_ex) +end From bd774122e52d6bcc09a9bedf786836306d62d6b5 Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Thu, 11 Jul 2024 16:37:00 +0200 Subject: [PATCH 037/213] Fix tests some more --- src/algorithms/ctmrg_gauge_fix.jl | 6 ++- src/utility/svd.jl | 2 +- test/ctmrg/fixediter.jl | 1 + test/ctmrg/gaugefix.jl | 12 ++++-- test/ctmrg/gradients.jl | 13 ++++-- test/ctmrg/leftmoves_allsides.jl | 69 ++++++++++++++++--------------- test/ctmrg/unitcell.jl | 2 +- 7 files changed, 61 insertions(+), 44 deletions(-) diff --git a/src/algorithms/ctmrg_gauge_fix.jl b/src/algorithms/ctmrg_gauge_fix.jl index 640d2ff0..fdf3dd91 100644 --- a/src/algorithms/ctmrg_gauge_fix.jl +++ b/src/algorithms/ctmrg_gauge_fix.jl @@ -189,12 +189,14 @@ function check_elementwise_convergence( ΔC = envfinal.corners .- envfix.corners ΔCmax = norm(ΔC, Inf) ΔCmean = norm(ΔC) - @debug "maxᵢⱼ|Cⁿ⁺¹ - Cⁿ|ᵢⱼ = $ΔCmax mean |Cⁿ⁺¹ - Cⁿ|ᵢⱼ = $ΔCmean" + # @debug "maxᵢⱼ|Cⁿ⁺¹ - Cⁿ|ᵢⱼ = $ΔCmax mean |Cⁿ⁺¹ - Cⁿ|ᵢⱼ = $ΔCmean" + println("maxᵢⱼ|Cⁿ⁺¹ - Cⁿ|ᵢⱼ = $ΔCmax mean |Cⁿ⁺¹ - Cⁿ|ᵢⱼ = $ΔCmean") ΔT = envfinal.edges .- envfix.edges ΔTmax = norm(ΔT, Inf) ΔTmean = norm(ΔT) - @debug "maxᵢⱼ|Tⁿ⁺¹ - Tⁿ|ᵢⱼ = $ΔTmax mean |Tⁿ⁺¹ - Tⁿ|ᵢⱼ = $ΔTmean" + # @debug "maxᵢⱼ|Tⁿ⁺¹ - Tⁿ|ᵢⱼ = $ΔTmax mean |Tⁿ⁺¹ - Tⁿ|ᵢⱼ = $ΔTmean" + println("maxᵢⱼ|Tⁿ⁺¹ - Tⁿ|ᵢⱼ = $ΔTmax mean |Tⁿ⁺¹ - Tⁿ|ᵢⱼ = $ΔTmean") # Check differences for all tensors in unit cell to debug properly for (dir, r, c) in Iterators.product(axes(envfinal.edges)...) diff --git a/src/utility/svd.jl b/src/utility/svd.jl index a848eadc..fac11254 100644 --- a/src/utility/svd.jl +++ b/src/utility/svd.jl @@ -135,7 +135,7 @@ function ChainRulesCore.rrule( alg::SVDAdjoint{F,R,B}; trunc::TruncationScheme=notrunc(), p::Real=2, -) where {F<:Union{IterSVD,FixedSVD},R,B} +) where {F<:Union{IterSVD,FixedSVD},R<:Union{GMRES,BiCGStab,Arnoldi},B} U, S, V, ϵ = PEPSKit.tsvd(t, alg; trunc, p) function tsvd!_itersvd_pullback((ΔU, ΔS, ΔV, Δϵ)) diff --git a/test/ctmrg/fixediter.jl b/test/ctmrg/fixediter.jl index 3e8a634c..b5d894cc 100644 --- a/test/ctmrg/fixediter.jl +++ b/test/ctmrg/fixediter.jl @@ -44,6 +44,7 @@ unitcells = [(1, 1), (3, 4)] @test check_elementwise_convergence(env_conv1, env_fixedsvd) end +# TODO: Why doesn't FixedIter work with IterSVD? ## # ctm_alg = CTMRG(; # tol=1e-12, diff --git a/test/ctmrg/gaugefix.jl b/test/ctmrg/gaugefix.jl index 9e477e21..10a5e17e 100644 --- a/test/ctmrg/gaugefix.jl +++ b/test/ctmrg/gaugefix.jl @@ -8,6 +8,8 @@ using PEPSKit: ctmrg_iter, gauge_fix, check_elementwise_convergence scalartypes = [Float64, ComplexF64] unitcells = [(1, 1), (2, 2), (3, 2)] +# scalartypes = [ComplexF64] +# unitcells = [(2, 2)] χ = 6 function _make_symmetric(psi) @@ -43,16 +45,16 @@ end psi = _make_symmetric(psi) Random.seed!(987654321) # Seed RNG to make random environment consistent + # psi = InfinitePEPS(physical_space, peps_space; unitcell) ctm = CTMRGEnv(psi; Venv=ctm_space) verbosity = 1 alg = CTMRG(; - tol=1e-10, miniter=4, maxiter=400, verbosity, trscheme=truncdim(dim(ctm_space)) + tol=1e-10, miniter=4, maxiter=5000, verbosity, trscheme=FixedSpaceTruncation() ) - alg_fixed = @set alg.projector_alg.trscheme = FixedSpaceTruncation() ctm = leading_boundary(ctm, psi, alg) - ctm2, = ctmrg_iter(psi, ctm, alg_fixed) + ctm2, = ctmrg_iter(psi, ctm, alg) ctm_fixed, = gauge_fix(ctm, ctm2) @test PEPSKit.check_elementwise_convergence(ctm, ctm_fixed; atol=1e-4) end @@ -74,7 +76,9 @@ end alg = CTMRG(; tol=1e-10, miniter=4, maxiter=400, verbosity, trscheme=truncdim(dim(ctm_space)) ) - alg_fixed = @set alg.projector_alg.trscheme = FixedSpaceTruncation() + alg_fixed = CTMRG(; + verbosity, svd_alg=alg.projector_alg.svd_alg, trscheme=FixedSpaceTruncation() + ) ctm = leading_boundary(ctm, psi, alg) ctm2, = ctmrg_iter(psi, ctm, alg_fixed) diff --git a/test/ctmrg/gradients.jl b/test/ctmrg/gradients.jl index 3e3a7070..283a1493 100644 --- a/test/ctmrg/gradients.jl +++ b/test/ctmrg/gradients.jl @@ -17,15 +17,22 @@ models = [square_lattice_heisenberg(), square_lattice_pwave()] names = ["Heisenberg", "p-wave superconductor"] Random.seed!(42039482030) tol = 1e-8 -boundary_alg = CTMRG(; tol=tol, miniter=4, maxiter=100, verbosity=0, ctmrgscheme=:AllSides) +boundary_alg = CTMRG(; + tol=tol, + miniter=4, + maxiter=100, + verbosity=0, + ctmrgscheme=:AllSides, + svd_alg=SVDAdjoint(; fwd_alg=TensorKit.SVD(), rrule_alg=GMRES(; tol=1e-10)), +) gradmodes = [ nothing, GeomSum(; tol, iterscheme=:FixedIter), GeomSum(; tol, iterscheme=:DiffGauge), ManualIter(; tol, iterscheme=:FixedIter), ManualIter(; tol, iterscheme=:DiffGauge), - LinSolve(; alg=KrylovKit.GMRES(; tol=tol, maxiter=10), iterscheme=:FixedIter), - LinSolve(; alg=KrylovKit.GMRES(; tol=tol, maxiter=10), iterscheme=:DiffGauge), + LinSolver(; solver=KrylovKit.GMRES(; tol=tol, maxiter=10), iterscheme=:FixedIter), + LinSolver(; solver=KrylovKit.GMRES(; tol=tol, maxiter=10), iterscheme=:DiffGauge), ] steps = -0.01:0.005:0.01 diff --git a/test/ctmrg/leftmoves_allsides.jl b/test/ctmrg/leftmoves_allsides.jl index e697a44b..50ab2926 100644 --- a/test/ctmrg/leftmoves_allsides.jl +++ b/test/ctmrg/leftmoves_allsides.jl @@ -9,41 +9,44 @@ using PEPSKit χenv = 16 ctm_alg_leftmoves = CTMRG(; tol=1e-10, verbosity=1, ctmrgscheme=:LeftMoves) ctm_alg_allsides = CTMRG(; tol=1e-10, verbosity=1, ctmrgscheme=:AllSides) +unitcells = [(1, 1), (3, 4)] -# compute environments -Random.seed!(32350283290358) -psi = InfinitePEPS(2, χbond) -env_leftmoves = leading_boundary( - CTMRGEnv(psi; Venv=ComplexSpace(χenv)), psi, ctm_alg_leftmoves -) -env_allsides = leading_boundary( - CTMRGEnv(psi; Venv=ComplexSpace(χenv)), psi, ctm_alg_allsides -) +@testset "$(unitcell) unit cell" for unitcell in unitcells + # compute environments + Random.seed!(32350283290358) + psi = InfinitePEPS(2, χbond; unitcell) + env_leftmoves = leading_boundary( + CTMRGEnv(psi; Venv=ComplexSpace(χenv)), psi, ctm_alg_leftmoves + ) + env_allsides = leading_boundary( + CTMRGEnv(psi; Venv=ComplexSpace(χenv)), psi, ctm_alg_allsides + ) -# compare norms -@test abs(norm(psi, env_leftmoves)) ≈ abs(norm(psi, env_allsides)) rtol = 1e-6 + # compare norms + @test abs(norm(psi, env_leftmoves)) ≈ abs(norm(psi, env_allsides)) rtol = 1e-6 -# compare singular values -CS_leftmoves = map(c -> tsvd(c; alg=TensorKit.SVD())[2], env_leftmoves.corners) -CS_allsides = map(c -> tsvd(c; alg=TensorKit.SVD())[2], env_allsides.corners) -ΔCS = maximum(zip(CS_leftmoves, CS_allsides)) do (c_lm, c_as) - smallest = infimum(MPSKit._firstspace(c_lm), MPSKit._firstspace(c_as)) - e_old = isometry(MPSKit._firstspace(c_lm), smallest) - e_new = isometry(MPSKit._firstspace(c_as), smallest) - return norm(e_new' * c_as * e_new - e_old' * c_lm * e_old) -end -@test ΔCS < 1e-3 + # compare singular values + CS_leftmoves = map(c -> tsvd(c; alg=TensorKit.SVD())[2], env_leftmoves.corners) + CS_allsides = map(c -> tsvd(c; alg=TensorKit.SVD())[2], env_allsides.corners) + ΔCS = maximum(zip(CS_leftmoves, CS_allsides)) do (c_lm, c_as) + smallest = infimum(MPSKit._firstspace(c_lm), MPSKit._firstspace(c_as)) + e_old = isometry(MPSKit._firstspace(c_lm), smallest) + e_new = isometry(MPSKit._firstspace(c_as), smallest) + return norm(e_new' * c_as * e_new - e_old' * c_lm * e_old) + end + @test ΔCS < 1e-2 -ΔTS = maximum(zip(TS_leftmoves, TS_allsides)) do (t_lm, t_as) - MPSKit._firstspace(t_lm) == MPSKit._firstspace(t_as) || return scalartype(t_lm)(Inf) - return norm(t_as - t_lm) -end -TS_leftmoves = map(t -> tsvd(t; alg=TensorKit.SVD())[2], env_leftmoves.edges) -TS_allsides = map(t -> tsvd(t; alg=TensorKit.SVD())[2], env_allsides.edges) -@test ΔTS < 1e-3 + TS_leftmoves = map(t -> tsvd(t; alg=TensorKit.SVD())[2], env_leftmoves.edges) + TS_allsides = map(t -> tsvd(t; alg=TensorKit.SVD())[2], env_allsides.edges) + ΔTS = maximum(zip(TS_leftmoves, TS_allsides)) do (t_lm, t_as) + MPSKit._firstspace(t_lm) == MPSKit._firstspace(t_as) || return scalartype(t_lm)(Inf) + return norm(t_as - t_lm) + end + @test ΔTS < 1e-2 -# compare Heisenberg energies -H = square_lattice_heisenberg() -E_leftmoves = costfun(psi, env_leftmoves, H) -E_allsides = costfun(psi, env_allsides, H) -@test E_leftmoves ≈ E_allsides rtol=1e-6 + # compare Heisenberg energies + H = square_lattice_heisenberg(; unitcell) + E_leftmoves = costfun(psi, env_leftmoves, H) + E_allsides = costfun(psi, env_allsides, H) + @test E_leftmoves ≈ E_allsides rtol=1e-4 +end diff --git a/test/ctmrg/unitcell.jl b/test/ctmrg/unitcell.jl index 9d8cad8a..69b4d10b 100644 --- a/test/ctmrg/unitcell.jl +++ b/test/ctmrg/unitcell.jl @@ -39,7 +39,7 @@ end env = CTMRGEnv(corners, edges) # apply one CTMRG iteration with fixeds -ctm_alg = CTMRG(; fixedspace=true) +ctm_alg = CTMRG(; trscheme=FixedSpaceTruncation()) env′, = ctmrg_iter(peps, env, ctm_alg) # compute random expecation value to test matching bonds From 023d90e4c02f751f5cb97e618b163eb74024c556 Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Fri, 12 Jul 2024 14:32:14 +0200 Subject: [PATCH 038/213] Stabilize gaugefix test --- src/algorithms/ctmrg.jl | 1 - src/algorithms/ctmrg_all_sides.jl | 6 +-- src/algorithms/ctmrg_gauge_fix.jl | 8 ++-- test/ctmrg/gaugefix.jl | 69 +++++++++---------------------- 4 files changed, 26 insertions(+), 58 deletions(-) diff --git a/src/algorithms/ctmrg.jl b/src/algorithms/ctmrg.jl index d9144827..f31eebad 100644 --- a/src/algorithms/ctmrg.jl +++ b/src/algorithms/ctmrg.jl @@ -182,7 +182,6 @@ function left_move(state, env::CTMRGEnv{C,T}, alg::ProjectorAlg) where {C,T} for col in 1:size(state, 2) cprev = _prev(col, size(state, 2)) - cnext = _next(col, size(state, 2)) # Compute projectors for row in 1:size(state, 1) diff --git a/src/algorithms/ctmrg_all_sides.jl b/src/algorithms/ctmrg_all_sides.jl index 046ae3ee..7a48ba1e 100644 --- a/src/algorithms/ctmrg_all_sides.jl +++ b/src/algorithms/ctmrg_all_sides.jl @@ -152,19 +152,19 @@ function renormalize_corners_edges(state, env::CTMRGEnv, Q, P_left, P_right) P_left[NORTH, r, c][χ2 D3 D4; χ_E] * P_right[NORTH, r, cprev][χ_W; χ1 D5 D6] @diffset @autoopt @tensor edges[EAST, r, c][χ_N D_Wab D_Wbe; χ_S] := - env.edges[EAST, r, _next(c, end)][χ1 D1 D2; χ2] * + env.edges[EAST, r, cnext][χ1 D1 D2; χ2] * state[r, c][d; D5 D1 D3 D_Wab] * conj(state[r, c][d; D6 D2 D4 D_Wbe]) * P_left[EAST, r, c][χ2 D3 D4; χ_S] * P_right[EAST, rprev, c][χ_N; χ1 D5 D6] @diffset @autoopt @tensor edges[SOUTH, r, c][χ_E D_Nab D_Nbe; χ_W] := - env.edges[SOUTH, _next(r, end), c][χ1 D1 D2; χ2] * + env.edges[SOUTH, rnext, c][χ1 D1 D2; χ2] * state[r, c][d; D_Nab D5 D1 D3] * conj(state[r, c][d; D_Nbe D6 D2 D4]) * P_left[SOUTH, r, c][χ2 D3 D4; χ_W] * P_right[SOUTH, r, cnext][χ_E; χ1 D5 D6] @diffset @autoopt @tensor edges[WEST, r, c][χ_S D_Eab D_Ebe; χ_N] := - env.edges[WEST, r, _prev(c, end)][χ1 D1 D2; χ2] * + env.edges[WEST, r, cprev][χ1 D1 D2; χ2] * state[r, c][d; D3 D_Eab D5 D1] * conj(state[r, c][d; D4 D_Ebe D6 D2]) * P_left[WEST, r, c][χ2 D3 D4; χ_N] * diff --git a/src/algorithms/ctmrg_gauge_fix.jl b/src/algorithms/ctmrg_gauge_fix.jl index fdf3dd91..b7925c58 100644 --- a/src/algorithms/ctmrg_gauge_fix.jl +++ b/src/algorithms/ctmrg_gauge_fix.jl @@ -6,7 +6,7 @@ This assumes that the `envfinal` is the result of one CTMRG iteration on `envpre Given that the CTMRG run is converged, the returned environment will be element-wise converged to `envprev`. """ -function gauge_fix(envprev::CTMRGEnv{C,T}, envfinal::CTMRGEnv{C,T}) where {C,T} +function gauge_fix(envprev::CTMRGEnv{C,T}, envfinal::CTMRGEnv{C′,T′}) where {C,C′,T,T′} # Check if spaces in envprev and envfinal are the same same_spaces = map(Iterators.product(axes(envfinal.edges)...)) do (dir, r, c) space(envfinal.edges[dir, r, c]) == space(envprev.edges[dir, r, c]) && @@ -189,14 +189,12 @@ function check_elementwise_convergence( ΔC = envfinal.corners .- envfix.corners ΔCmax = norm(ΔC, Inf) ΔCmean = norm(ΔC) - # @debug "maxᵢⱼ|Cⁿ⁺¹ - Cⁿ|ᵢⱼ = $ΔCmax mean |Cⁿ⁺¹ - Cⁿ|ᵢⱼ = $ΔCmean" - println("maxᵢⱼ|Cⁿ⁺¹ - Cⁿ|ᵢⱼ = $ΔCmax mean |Cⁿ⁺¹ - Cⁿ|ᵢⱼ = $ΔCmean") + @debug "maxᵢⱼ|Cⁿ⁺¹ - Cⁿ|ᵢⱼ = $ΔCmax mean |Cⁿ⁺¹ - Cⁿ|ᵢⱼ = $ΔCmean" ΔT = envfinal.edges .- envfix.edges ΔTmax = norm(ΔT, Inf) ΔTmean = norm(ΔT) - # @debug "maxᵢⱼ|Tⁿ⁺¹ - Tⁿ|ᵢⱼ = $ΔTmax mean |Tⁿ⁺¹ - Tⁿ|ᵢⱼ = $ΔTmean" - println("maxᵢⱼ|Tⁿ⁺¹ - Tⁿ|ᵢⱼ = $ΔTmax mean |Tⁿ⁺¹ - Tⁿ|ᵢⱼ = $ΔTmean") + @debug "maxᵢⱼ|Tⁿ⁺¹ - Tⁿ|ᵢⱼ = $ΔTmax mean |Tⁿ⁺¹ - Tⁿ|ᵢⱼ = $ΔTmean" # Check differences for all tensors in unit cell to debug properly for (dir, r, c) in Iterators.product(axes(envfinal.edges)...) diff --git a/test/ctmrg/gaugefix.jl b/test/ctmrg/gaugefix.jl index 10a5e17e..f56ab00d 100644 --- a/test/ctmrg/gaugefix.jl +++ b/test/ctmrg/gaugefix.jl @@ -2,86 +2,57 @@ using Test using Random using PEPSKit using TensorKit -using Accessors using PEPSKit: ctmrg_iter, gauge_fix, check_elementwise_convergence scalartypes = [Float64, ComplexF64] unitcells = [(1, 1), (2, 2), (3, 2)] -# scalartypes = [ComplexF64] -# unitcells = [(2, 2)] -χ = 6 - -function _make_symmetric(psi) - if ==(size(psi)...) - return PEPSKit.symmetrize(psi, PEPSKit.Full()) - else - return PEPSKit.symmetrize(PEPSKit.symmetrize(psi, PEPSKit.Depth()), PEPSKit.Width()) - end -end - -# If I can't make the rng seed behave, I'll just randomly define a peps somehow -function semi_random_peps!(psi::InfinitePEPS) - i = 0 - A′ = map(psi.A) do a - for (_, b) in blocks(a) - l = length(b) - b .= reshape(collect((1:l) .+ i), size(b)) - i += l - end - return a - end - return InfinitePEPS(A′) -end +χ = Dict([(1, 1) => 8, (2, 2) => 26, (3, 2) => 26]) # Increase χ to converge non-symmetric environments @testset "Trivial symmetry ($T) - ($unitcell)" for (T, unitcell) in Iterators.product(scalartypes, unitcells) physical_space = ComplexSpace(2) peps_space = ComplexSpace(2) - ctm_space = ComplexSpace(χ) + ctm_space = ComplexSpace(χ[unitcell]) - psi = InfinitePEPS(undef, T, physical_space, peps_space; unitcell) - semi_random_peps!(psi) - psi = _make_symmetric(psi) - - Random.seed!(987654321) # Seed RNG to make random environment consistent - # psi = InfinitePEPS(physical_space, peps_space; unitcell) + Random.seed!(2938293852938) # Seed RNG to make random environment consistent + psi = InfinitePEPS(randn, T, physical_space, peps_space; unitcell) ctm = CTMRGEnv(psi; Venv=ctm_space) - verbosity = 1 alg = CTMRG(; - tol=1e-10, miniter=4, maxiter=5000, verbosity, trscheme=FixedSpaceTruncation() + tol=1e-10, + maxiter=100, + verbosity=1, + trscheme=FixedSpaceTruncation(), + ctmrgscheme=:AllSides, # In general :AllSides is faster ) ctm = leading_boundary(ctm, psi, alg) ctm2, = ctmrg_iter(psi, ctm, alg) ctm_fixed, = gauge_fix(ctm, ctm2) - @test PEPSKit.check_elementwise_convergence(ctm, ctm_fixed; atol=1e-4) + @test PEPSKit.check_elementwise_convergence(ctm, ctm_fixed; atol=1e-6) end @testset "Z2 symmetry ($T) - ($unitcell)" for (T, unitcell) in Iterators.product(scalartypes, unitcells) physical_space = Z2Space(0 => 1, 1 => 1) peps_space = Z2Space(0 => 1, 1 => 1) - ctm_space = Z2Space(0 => χ ÷ 2, 1 => χ ÷ 2) - - psi = InfinitePEPS(undef, T, physical_space, peps_space; unitcell) - semi_random_peps!(psi) - psi = _make_symmetric(psi) + ctm_space = Z2Space(0 => χ[(1, 1)] ÷ 2, 1 => χ[(1, 1)] ÷ 2) - Random.seed!(123456789) # Seed RNG to make random environment consistent + Random.seed!(2938293852938) # Seed RNG to make random environment consistent + psi = InfinitePEPS(physical_space, peps_space; unitcell) ctm = CTMRGEnv(psi; Venv=ctm_space) - verbosity = 1 alg = CTMRG(; - tol=1e-10, miniter=4, maxiter=400, verbosity, trscheme=truncdim(dim(ctm_space)) - ) - alg_fixed = CTMRG(; - verbosity, svd_alg=alg.projector_alg.svd_alg, trscheme=FixedSpaceTruncation() + tol=1e-10, + maxiter=400, + verbosity=1, + trscheme=FixedSpaceTruncation(), + ctmrgscheme=:LeftMoves, # Weirdly, only :LeftMoves converges ) ctm = leading_boundary(ctm, psi, alg) - ctm2, = ctmrg_iter(psi, ctm, alg_fixed) + ctm2, = ctmrg_iter(psi, ctm, alg) ctm_fixed, = gauge_fix(ctm, ctm2) - @test PEPSKit.check_elementwise_convergence(ctm, ctm_fixed; atol=1e-4) + @test PEPSKit.check_elementwise_convergence(ctm, ctm_fixed; atol=1e-6) end From eb8c5bcbf5d8f7edea553fe5f7c658b22afb9c7e Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Fri, 12 Jul 2024 17:12:48 +0200 Subject: [PATCH 039/213] Refactor enlarged corners and AllSides corner contractions --- src/algorithms/ctmrg.jl | 87 +++++++++++++++---------------- src/algorithms/ctmrg_all_sides.jl | 68 +++++++++--------------- src/algorithms/ctmrg_gauge_fix.jl | 4 +- test/ctmrg/gaugefix.jl | 1 + test/ctmrg/svd_wrapper.jl | 1 + test/heisenberg.jl | 2 +- 6 files changed, 70 insertions(+), 93 deletions(-) diff --git a/src/algorithms/ctmrg.jl b/src/algorithms/ctmrg.jl index f31eebad..45d1701f 100644 --- a/src/algorithms/ctmrg.jl +++ b/src/algorithms/ctmrg.jl @@ -126,8 +126,6 @@ function MPSKit.leading_boundary(envinit, state, alg::CTMRG{S}) where {S} @warn( "CTMRG reached maximal number of iterations at (Δnorm=$Δnorm, ΔCS=$ΔCS, ΔTS=$ΔTS)" ) - flush(stdout) # Flush output to enable live printing on HPC - flush(stderr) # Same for @info, @warn, ... return conv_condition, normnew, CSnew, TSnew, info.ϵ end conv_condition && break # Converge if maximal Δ falls below tolerance @@ -185,22 +183,9 @@ function left_move(state, env::CTMRGEnv{C,T}, alg::ProjectorAlg) where {C,T} # Compute projectors for row in 1:size(state, 1) - rprev = _prev(row, size(state, 1)) - rnext = _next(row, size(state, 1)) - # Enlarged corners - Q_sw = southwest_corner( - env.edges[SOUTH, _next(rnext, end), col], - env.corners[SOUTHWEST, _next(rnext, end), cprev], - env.edges[WEST, rnext, cprev], - state[rnext, col], - ) - Q_nw = northwest_corner( - env.edges[WEST, row, cprev], - env.corners[NORTHWEST, rprev, cprev], - env.edges[NORTH, rprev, col], - state[row, col], - ) + Q_sw = southwest_corner((_next(row, size(state, 1)), col), state, env) + Q_nw = northwest_corner((row, col), state, env) # SVD half-infinite environment trscheme = if alg.trscheme isa FixedSpaceTruncation @@ -251,38 +236,48 @@ function left_move(state, env::CTMRGEnv{C,T}, alg::ProjectorAlg) where {C,T} return CTMRGEnv(corners, edges), (; P_left=copy(P_top), P_right=copy(P_bottom), ϵ) end -# Compute enlarged corners -function northwest_corner(edge_W, corner_NW, edge_N, peps_above, peps_below=peps_above) - @autoopt @tensor corner[χ_S D_Sabove D_Sbelow; χ_E D_Eabove D_Ebelow] := - edge_W[χ_S D1 D2; χ1] * - corner_NW[χ1; χ2] * - edge_N[χ2 D3 D4; χ_E] * - peps_above[d; D3 D_Eabove D_Sabove D1] * - conj(peps_below[d; D4 D_Ebelow D_Sbelow D2]) +# Generic enlarged corner contraction +function enlarged_corner(edge_in, corner, edge_out, peps_above, peps_below=peps_above) + return @autoopt @tensor Q[χ_in D_inabove D_inbelow; χ_out D_outabove D_outbelow] := + edge_in[χ_in D1 D2; χ1] * + corner[χ1; χ2] * + edge_out[χ2 D3 D4; χ_out] * + peps_above[d; D3 D_outabove D_inabove D1] * + conj(peps_below[d; D4 D_outbelow D_inbelow D2]) end -function northeast_corner(edge_N, corner_NE, edge_E, peps_above, peps_below=peps_above) - @autoopt @tensor corner[χ_W D_Wabove D_Wbelow; χ_S D_Sabove D_Sbelow] := - edge_N[χ_W D1 D2; χ1] * - corner_NE[χ1; χ2] * - edge_E[χ2 D3 D4; χ_S] * - peps_above[d; D1 D3 D_Sabove D_Wabove] * - conj(peps_below[d; D2 D4 D_Sbelow D_Wbelow]) + +# Direction specific methods +function northwest_corner((row, col), state, env) + return enlarged_corner( + env.edges[WEST, row, _prev(col, end)], + env.corners[NORTHWEST, _prev(row, end), _prev(col, end)], + env.edges[NORTH, _prev(row, end), col], + state[row, col], + ) +end +function northeast_corner((row, col), state, env) + return enlarged_corner( + env.edges[NORTH, _prev(row, end), col], + env.corners[NORTHEAST, _prev(row, end), _next(col, end)], + env.edges[EAST, row, _next(col, end)], + rotate_north(state[row, col], EAST), + ) end -function southeast_corner(edge_E, corner_SE, edge_S, peps_above, peps_below=peps_above) - @autoopt @tensor corner[χ_N D_Nabove D_Nbelow; χ_W D_Wabove D_Wbelow] := - edge_E[χ_N D1 D2; χ1] * - corner_SE[χ1; χ2] * - edge_S[χ2 D3 D4; χ_W] * - peps_above[d; D_Nabove D1 D3 D_Wabove] * - conj(peps_below[d; D_Nbelow D2 D4 D_Wbelow]) +function southeast_corner((row, col), state, env) + return enlarged_corner( + env.edges[EAST, row, _next(col, end)], + env.corners[SOUTHEAST, _next(row, end), _next(col, end)], + env.edges[SOUTH, _next(row, end), col], + rotate_north(state[row, col], SOUTH), + ) end -function southwest_corner(edge_S, corner_SW, edge_W, peps_above, peps_below=peps_above) - @autoopt @tensor corner[χ_E D_Eabove D_Ebelow; χ_N D_Nabove D_Nbelow] := - edge_S[χ_E D1 D2; χ1] * - corner_SW[χ1; χ2] * - edge_W[χ2 D3 D4; χ_N] * - peps_above[d; D_Nabove D_Eabove D1 D3] * - conj(peps_below[d; D_Nbelow D_Ebelow D2 D4]) +function southwest_corner((row, col), state, env) + return enlarged_corner( + env.edges[SOUTH, _next(row, end), col], + env.corners[SOUTHWEST, _next(row, end), _prev(col, end)], + env.edges[WEST, row, _prev(col, end)], + rotate_north(state[row, col], WEST), + ) end # Build projectors from SVD and enlarged SW & NW corners diff --git a/src/algorithms/ctmrg_all_sides.jl b/src/algorithms/ctmrg_all_sides.jl index 7a48ba1e..5eaab07e 100644 --- a/src/algorithms/ctmrg_all_sides.jl +++ b/src/algorithms/ctmrg_all_sides.jl @@ -18,38 +18,14 @@ function enlarge_corners_edges(state, env::CTMRGEnv{C,T}) where {C,T} Q = Zygote.Buffer(Array{Qtype,3}(undef, size(env.corners))) drc_combinations = collect(Iterators.product(axes(env.corners)...)) @fwdthreads for (dir, r, c) in drc_combinations - rprev = _prev(r, size(state, 1)) - rnext = _next(r, size(state, 1)) - cprev = _prev(c, size(state, 2)) - cnext = _next(c, size(state, 2)) Q[dir, r, c] = if dir == NORTHWEST - northwest_corner( - env.edges[WEST, r, cprev], - env.corners[NORTHWEST, rprev, cprev], - env.edges[NORTH, rprev, c], - state[r, c], - ) + northwest_corner((r, c), state, env) elseif dir == NORTHEAST - northeast_corner( - env.edges[NORTH, rprev, c], - env.corners[NORTHEAST, rprev, cnext], - env.edges[EAST, r, cnext], - state[r, c], - ) + northeast_corner((r, c), state, env) elseif dir == SOUTHEAST - southeast_corner( - env.edges[EAST, r, cnext], - env.corners[SOUTHEAST, rnext, cnext], - env.edges[SOUTH, rnext, c], - state[r, c], - ) + southeast_corner((r, c), state, env) elseif dir == SOUTHWEST - southwest_corner( - env.edges[SOUTH, rnext, c], - env.corners[SOUTHWEST, rnext, cprev], - env.edges[WEST, r, cprev], - state[r, c], - ) + southwest_corner((r, c), state, env) end end @@ -119,6 +95,10 @@ function build_projectors(Q, env::CTMRGEnv{C,E}, alg::ProjectorAlg{A,T}) where { end # Apply projectors to renormalize corners and edges +function _contract_new_corner(P_right, Q, P_left) + return @autoopt @tensor corner[χ_in; χ_out] := + P_right[χ_in; χ1 D1 D2] * Q[χ1 D1 D2; χ2 D3 D4] * P_left[χ2 D3 D4; χ_out] +end function renormalize_corners_edges(state, env::CTMRGEnv, Q, P_left, P_right) corners::typeof(env.corners) = copy(env.corners) edges::typeof(env.edges) = copy(env.edges) @@ -128,22 +108,22 @@ function renormalize_corners_edges(state, env::CTMRGEnv, Q, P_left, P_right) rnext = _next(r, size(state, 1)) cprev = _prev(c, size(state, 2)) cnext = _next(c, size(state, 2)) - @diffset @autoopt @tensor corners[NORTHWEST, r, c][χ_S; χ_E] := - P_right[WEST, rnext, c][χ_S; χ1 D1 D2] * - Q[NORTHWEST, r, c][χ1 D1 D2; χ2 D3 D4] * - P_left[NORTH, r, c][χ2 D3 D4; χ_E] - @diffset @autoopt @tensor corners[NORTHEAST, r, c][χ_W; χ_S] := - P_right[NORTH, r, cprev][χ_W; χ1 D1 D2] * - Q[NORTHEAST, r, c][χ1 D1 D2; χ2 D3 D4] * - P_left[EAST, r, c][χ2 D3 D4; χ_S] - @diffset @autoopt @tensor corners[SOUTHEAST, r, c][χ_N; χ_W] := - P_right[EAST, rprev, c][χ_N; χ1 D1 D2] * - Q[SOUTHEAST, r, c][χ1 D1 D2; χ2 D3 D4] * - P_left[SOUTH, r, c][χ2 D3 D4; χ_W] - @diffset @autoopt @tensor corners[SOUTHWEST, r, c][χ_E; χ_N] := - P_right[SOUTH, r, cnext][χ_E; χ1 D1 D2] * - Q[SOUTHWEST, r, c][χ1 D1 D2; χ2 D3 D4] * - P_left[WEST, r, c][χ2 D3 D4; χ_N] + @diffset C_NW = _contract_new_corner( + P_right[WEST, rnext, c], Q[NORTHWEST, r, c], P_left[NORTH, r, c] + ) + @diffset C_NE = _contract_new_corner( + P_right[NORTH, r, cprev], Q[NORTHEAST, r, c], P_left[EAST, r, c] + ) + @diffset C_SE = _contract_new_corner( + P_right[EAST, rprev, c], Q[SOUTHEAST, r, c], P_left[SOUTH, r, c] + ) + @diffset C_SW = _contract_new_corner( + P_right[SOUTH, r, cnext], Q[SOUTHWEST, r, c], P_left[WEST, r, c] + ) + corners[NORTHWEST, r, c] = C_NW # For some reason @diffset can't directly asign to corners[...] + corners[NORTHEAST, r, c] = C_NE + corners[SOUTHEAST, r, c] = C_SE + corners[SOUTHWEST, r, c] = C_SW @diffset @autoopt @tensor edges[NORTH, r, c][χ_W D_Sab D_Sbe; χ_E] := env.edges[NORTH, rprev, c][χ1 D1 D2; χ2] * diff --git a/src/algorithms/ctmrg_gauge_fix.jl b/src/algorithms/ctmrg_gauge_fix.jl index b7925c58..9faa950e 100644 --- a/src/algorithms/ctmrg_gauge_fix.jl +++ b/src/algorithms/ctmrg_gauge_fix.jl @@ -46,8 +46,8 @@ function gauge_fix(envprev::CTMRGEnv{C,T}, envfinal::CTMRGEnv{C′,T′}) where ρfinal = transfermatrix_fixedpoint(Tsfinal, M, ρinit) # Decompose and multiply - Qprev, = leftorth(ρprev) - Qfinal, = leftorth(ρfinal) + Qprev, = leftorth!(ρprev) + Qfinal, = leftorth!(ρfinal) return Qprev * Qfinal' end diff --git a/test/ctmrg/gaugefix.jl b/test/ctmrg/gaugefix.jl index f56ab00d..b7dc2ec4 100644 --- a/test/ctmrg/gaugefix.jl +++ b/test/ctmrg/gaugefix.jl @@ -16,6 +16,7 @@ unitcells = [(1, 1), (2, 2), (3, 2)] ctm_space = ComplexSpace(χ[unitcell]) Random.seed!(2938293852938) # Seed RNG to make random environment consistent + # Random.seed!(928329384) # Seed RNG to make random environment consistent psi = InfinitePEPS(randn, T, physical_space, peps_space; unitcell) ctm = CTMRGEnv(psi; Venv=ctm_space) diff --git a/test/ctmrg/svd_wrapper.jl b/test/ctmrg/svd_wrapper.jl index 3c17dc7d..47e1b9cf 100644 --- a/test/ctmrg/svd_wrapper.jl +++ b/test/ctmrg/svd_wrapper.jl @@ -19,6 +19,7 @@ dtype = ComplexF64 trunc = truncspace(ℂ^χ) # lorentz_broadening = 1e-12 rtol = 1e-9 +Random.seed!(123456789) r = TensorMap(randn, dtype, ℂ^m, ℂ^n) R = TensorMap(randn, space(r)) diff --git a/test/heisenberg.jl b/test/heisenberg.jl index 352caaa9..1648ed9a 100644 --- a/test/heisenberg.jl +++ b/test/heisenberg.jl @@ -41,7 +41,7 @@ H_2x2 = square_lattice_heisenberg(; unitcell=(2, 2)) psi_init_2x2 = InfinitePEPS(2, χbond; unitcell=(2, 2)) env_init_2x2 = leading_boundary( CTMRGEnv(psi_init_2x2; Venv=ComplexSpace(χenv)), psi_init_2x2, ctm_alg -) +); result_2x2 = fixedpoint(psi_init_2x2, H_2x2, opt_alg, env_init_2x2) @test result_2x2.E ≈ 4 * result.E atol = 1e-2 From 86b204d068c4c34de184ed17de99d9e6280084f8 Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Fri, 12 Jul 2024 17:16:11 +0200 Subject: [PATCH 040/213] Fix small `@diffset` switch up --- src/algorithms/ctmrg_all_sides.jl | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/algorithms/ctmrg_all_sides.jl b/src/algorithms/ctmrg_all_sides.jl index 5eaab07e..290917e3 100644 --- a/src/algorithms/ctmrg_all_sides.jl +++ b/src/algorithms/ctmrg_all_sides.jl @@ -108,22 +108,22 @@ function renormalize_corners_edges(state, env::CTMRGEnv, Q, P_left, P_right) rnext = _next(r, size(state, 1)) cprev = _prev(c, size(state, 2)) cnext = _next(c, size(state, 2)) - @diffset C_NW = _contract_new_corner( + C_NW = _contract_new_corner( P_right[WEST, rnext, c], Q[NORTHWEST, r, c], P_left[NORTH, r, c] ) - @diffset C_NE = _contract_new_corner( + C_NE = _contract_new_corner( P_right[NORTH, r, cprev], Q[NORTHEAST, r, c], P_left[EAST, r, c] ) - @diffset C_SE = _contract_new_corner( + C_SE = _contract_new_corner( P_right[EAST, rprev, c], Q[SOUTHEAST, r, c], P_left[SOUTH, r, c] ) - @diffset C_SW = _contract_new_corner( + C_SW = _contract_new_corner( P_right[SOUTH, r, cnext], Q[SOUTHWEST, r, c], P_left[WEST, r, c] ) - corners[NORTHWEST, r, c] = C_NW # For some reason @diffset can't directly asign to corners[...] - corners[NORTHEAST, r, c] = C_NE - corners[SOUTHEAST, r, c] = C_SE - corners[SOUTHWEST, r, c] = C_SW + @diffset corners[NORTHWEST, r, c] = C_NW # For some reason @diffset can't directly assign to corners[...] + @diffset corners[NORTHEAST, r, c] = C_NE + @diffset corners[SOUTHEAST, r, c] = C_SE + @diffset corners[SOUTHWEST, r, c] = C_SW @diffset @autoopt @tensor edges[NORTH, r, c][χ_W D_Sab D_Sbe; χ_E] := env.edges[NORTH, rprev, c][χ1 D1 D2; χ2] * From 8b16c18f07c2b4cfdb20f9daf352f1c71dabfa15 Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Fri, 12 Jul 2024 18:11:17 +0200 Subject: [PATCH 041/213] Fix `@diffset` race condition in AllSides CTMRG, update gaugefix.jl test to test both AllSides and LeftMoves --- src/algorithms/ctmrg.jl | 2 +- src/algorithms/ctmrg_all_sides.jl | 31 +++++++++++++++---------------- test/ctmrg/gaugefix.jl | 29 +++++++++++++---------------- 3 files changed, 29 insertions(+), 33 deletions(-) diff --git a/src/algorithms/ctmrg.jl b/src/algorithms/ctmrg.jl index 45d1701f..513f7dbe 100644 --- a/src/algorithms/ctmrg.jl +++ b/src/algorithms/ctmrg.jl @@ -361,7 +361,7 @@ Compute the PEPS correlation length based on the horizontal and vertical transfer matrices. Additionally the (normalized) eigenvalue spectrum is returned. Specify the number of computed eigenvalues with `howmany`. """ -function correlation_length(peps::InfinitePEPS, env::CTMRGEnv; howmany=2) +function MPSKit.correlation_length(peps::InfinitePEPS, env::CTMRGEnv; howmany=2) ξ = Array{Float64,3}(undef, (2, size(peps)...)) # First index picks horizontal or vertical direction λ = Array{ComplexF64,4}(undef, (2, howmany, size(peps)...)) for r in 1:size(peps, 1), c in 1:size(peps, 2) diff --git a/src/algorithms/ctmrg_all_sides.jl b/src/algorithms/ctmrg_all_sides.jl index 290917e3..ecfee329 100644 --- a/src/algorithms/ctmrg_all_sides.jl +++ b/src/algorithms/ctmrg_all_sides.jl @@ -100,50 +100,47 @@ function _contract_new_corner(P_right, Q, P_left) P_right[χ_in; χ1 D1 D2] * Q[χ1 D1 D2; χ2 D3 D4] * P_left[χ2 D3 D4; χ_out] end function renormalize_corners_edges(state, env::CTMRGEnv, Q, P_left, P_right) - corners::typeof(env.corners) = copy(env.corners) - edges::typeof(env.edges) = copy(env.edges) + corners = Zygote.Buffer(copy(env.corners)) + edges = Zygote.Buffer(copy(env.edges)) rc_combinations = collect(Iterators.product(axes(state)...)) @fwdthreads for (r, c) in rc_combinations rprev = _prev(r, size(state, 1)) rnext = _next(r, size(state, 1)) cprev = _prev(c, size(state, 2)) cnext = _next(c, size(state, 2)) - C_NW = _contract_new_corner( + + corners[NORTHWEST, r, c] = _contract_new_corner( P_right[WEST, rnext, c], Q[NORTHWEST, r, c], P_left[NORTH, r, c] ) - C_NE = _contract_new_corner( + corners[NORTHEAST, r, c] = _contract_new_corner( P_right[NORTH, r, cprev], Q[NORTHEAST, r, c], P_left[EAST, r, c] ) - C_SE = _contract_new_corner( + corners[SOUTHEAST, r, c] = _contract_new_corner( P_right[EAST, rprev, c], Q[SOUTHEAST, r, c], P_left[SOUTH, r, c] ) - C_SW = _contract_new_corner( + corners[SOUTHWEST, r, c] = _contract_new_corner( P_right[SOUTH, r, cnext], Q[SOUTHWEST, r, c], P_left[WEST, r, c] ) - @diffset corners[NORTHWEST, r, c] = C_NW # For some reason @diffset can't directly assign to corners[...] - @diffset corners[NORTHEAST, r, c] = C_NE - @diffset corners[SOUTHEAST, r, c] = C_SE - @diffset corners[SOUTHWEST, r, c] = C_SW - @diffset @autoopt @tensor edges[NORTH, r, c][χ_W D_Sab D_Sbe; χ_E] := + @autoopt @tensor edges[NORTH, r, c][χ_W D_Sab D_Sbe; χ_E] := env.edges[NORTH, rprev, c][χ1 D1 D2; χ2] * state[r, c][d; D1 D3 D_Sab D5] * conj(state[r, c][d; D2 D4 D_Sbe D6]) * P_left[NORTH, r, c][χ2 D3 D4; χ_E] * P_right[NORTH, r, cprev][χ_W; χ1 D5 D6] - @diffset @autoopt @tensor edges[EAST, r, c][χ_N D_Wab D_Wbe; χ_S] := + @autoopt @tensor edges[EAST, r, c][χ_N D_Wab D_Wbe; χ_S] := env.edges[EAST, r, cnext][χ1 D1 D2; χ2] * state[r, c][d; D5 D1 D3 D_Wab] * conj(state[r, c][d; D6 D2 D4 D_Wbe]) * P_left[EAST, r, c][χ2 D3 D4; χ_S] * P_right[EAST, rprev, c][χ_N; χ1 D5 D6] - @diffset @autoopt @tensor edges[SOUTH, r, c][χ_E D_Nab D_Nbe; χ_W] := + @autoopt @tensor edges[SOUTH, r, c][χ_E D_Nab D_Nbe; χ_W] := env.edges[SOUTH, rnext, c][χ1 D1 D2; χ2] * state[r, c][d; D_Nab D5 D1 D3] * conj(state[r, c][d; D_Nbe D6 D2 D4]) * P_left[SOUTH, r, c][χ2 D3 D4; χ_W] * P_right[SOUTH, r, cnext][χ_E; χ1 D5 D6] - @diffset @autoopt @tensor edges[WEST, r, c][χ_S D_Eab D_Ebe; χ_N] := + @autoopt @tensor edges[WEST, r, c][χ_S D_Eab D_Ebe; χ_N] := env.edges[WEST, r, cprev][χ1 D1 D2; χ2] * state[r, c][d; D3 D_Eab D5 D1] * conj(state[r, c][d; D4 D_Ebe D6 D2]) * @@ -151,7 +148,9 @@ function renormalize_corners_edges(state, env::CTMRGEnv, Q, P_left, P_right) P_right[WEST, rnext, c][χ_S; χ1 D5 D6] end - @diffset corners[:, :, :] ./= norm.(corners[:, :, :]) - @diffset edges[:, :, :] ./= norm.(edges[:, :, :]) + corners = copy(corners) + edges = copy(edges) + corners[:, :, :] ./= norm.(corners[:, :, :]) + edges[:, :, :] ./= norm.(edges[:, :, :]) return corners, edges end diff --git a/test/ctmrg/gaugefix.jl b/test/ctmrg/gaugefix.jl index b7dc2ec4..e3edff45 100644 --- a/test/ctmrg/gaugefix.jl +++ b/test/ctmrg/gaugefix.jl @@ -7,25 +7,24 @@ using PEPSKit: ctmrg_iter, gauge_fix, check_elementwise_convergence scalartypes = [Float64, ComplexF64] unitcells = [(1, 1), (2, 2), (3, 2)] +schemes = [:AllSides, :LeftMoves] χ = Dict([(1, 1) => 8, (2, 2) => 26, (3, 2) => 26]) # Increase χ to converge non-symmetric environments -@testset "Trivial symmetry ($T) - ($unitcell)" for (T, unitcell) in - Iterators.product(scalartypes, unitcells) +@testset "Trivial symmetry ($T) - ($unitcell) - ($ctmrgscheme)" for ( + T, unitcell, ctmrgscheme +) in Iterators.product( + scalartypes, unitcells, schemes +) physical_space = ComplexSpace(2) peps_space = ComplexSpace(2) ctm_space = ComplexSpace(χ[unitcell]) - Random.seed!(2938293852938) # Seed RNG to make random environment consistent - # Random.seed!(928329384) # Seed RNG to make random environment consistent + Random.seed!(29358293852) # Seed RNG to make random environment consistent psi = InfinitePEPS(randn, T, physical_space, peps_space; unitcell) ctm = CTMRGEnv(psi; Venv=ctm_space) alg = CTMRG(; - tol=1e-10, - maxiter=100, - verbosity=1, - trscheme=FixedSpaceTruncation(), - ctmrgscheme=:AllSides, # In general :AllSides is faster + tol=1e-10, maxiter=100, verbosity=1, trscheme=FixedSpaceTruncation(), ctmrgscheme ) ctm = leading_boundary(ctm, psi, alg) @@ -34,8 +33,10 @@ unitcells = [(1, 1), (2, 2), (3, 2)] @test PEPSKit.check_elementwise_convergence(ctm, ctm_fixed; atol=1e-6) end -@testset "Z2 symmetry ($T) - ($unitcell)" for (T, unitcell) in - Iterators.product(scalartypes, unitcells) +@testset "Z2 symmetry ($T) - ($unitcell) - ($ctmrgscheme)" for (T, unitcell, ctmrgscheme) in + Iterators.product( + scalartypes, unitcells, schemes +) physical_space = Z2Space(0 => 1, 1 => 1) peps_space = Z2Space(0 => 1, 1 => 1) ctm_space = Z2Space(0 => χ[(1, 1)] ÷ 2, 1 => χ[(1, 1)] ÷ 2) @@ -45,11 +46,7 @@ end ctm = CTMRGEnv(psi; Venv=ctm_space) alg = CTMRG(; - tol=1e-10, - maxiter=400, - verbosity=1, - trscheme=FixedSpaceTruncation(), - ctmrgscheme=:LeftMoves, # Weirdly, only :LeftMoves converges + tol=1e-10, maxiter=400, verbosity=1, trscheme=FixedSpaceTruncation(), ctmrgscheme ) ctm = leading_boundary(ctm, psi, alg) From 2e2a4725e55f40008bc0b472faccdf7406587793 Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Fri, 12 Jul 2024 18:48:36 +0200 Subject: [PATCH 042/213] Rename :sequential, :simultaneous, :fixed, :diffgauge --- src/algorithms/ctmrg.jl | 9 ++-- src/algorithms/ctmrg_all_sides.jl | 6 +-- src/algorithms/peps_opt.jl | 40 +++++++------- test/ctmrg/ctmrgschemes.jl | 52 +++++++++++++++++++ .../{fixediter.jl => fixed_iterscheme.jl} | 18 +++---- test/ctmrg/gaugefix.jl | 2 +- test/ctmrg/gradients.jl | 14 ++--- test/ctmrg/leftmoves_allsides.jl | 52 ------------------- test/heisenberg.jl | 4 +- test/runtests.jl | 10 ++-- 10 files changed, 104 insertions(+), 103 deletions(-) create mode 100644 test/ctmrg/ctmrgschemes.jl rename test/ctmrg/{fixediter.jl => fixed_iterscheme.jl} (82%) delete mode 100644 test/ctmrg/leftmoves_allsides.jl diff --git a/src/algorithms/ctmrg.jl b/src/algorithms/ctmrg.jl index 513f7dbe..4ee06972 100644 --- a/src/algorithms/ctmrg.jl +++ b/src/algorithms/ctmrg.jl @@ -29,7 +29,7 @@ end CTMRG(; tol=Defaults.ctmrg_tol, maxiter=Defaults.ctmrg_maxiter, miniter=Defaults.ctmrg_miniter, verbosity=0, svd_alg=TensorKit.SVD(), trscheme=FixedSpaceTruncation(), - ctmrgscheme=:AllSides) + ctmrgscheme=:simultaneous) Algorithm struct that represents the CTMRG algorithm for contracting infinite PEPS. Each CTMRG run is converged up to `tol` where the singular value convergence of the @@ -41,8 +41,8 @@ The projectors are computed from `svd_alg` SVDs where the truncation scheme is s `trscheme`. In general, two different schemes can be selected with `ctmrgscheme` which determine how -CTMRG is implemented. It can either be `:LeftMoves`, where the projectors are succesively -computed on the western side, and then applied and rotated. Or with `AllSides`, all projectors +CTMRG is implemented. It can either be `:sequential`, where the projectors are succesively +computed on the western side, and then applied and rotated. Or with `simultaneous` all projectors are computed and applied simultaneously on all sides, where in particular the corners get contracted with two projectors at the same time. """ @@ -60,7 +60,7 @@ function CTMRG(; verbosity=1, svd_alg=SVDAdjoint(), trscheme=FixedSpaceTruncation(), - ctmrgscheme=:AllSides, + ctmrgscheme=:simultaneous, ) return CTMRG{ctmrgscheme}( tol, maxiter, miniter, verbosity, ProjectorAlg(; svd_alg, trscheme, verbosity) @@ -361,6 +361,7 @@ Compute the PEPS correlation length based on the horizontal and vertical transfer matrices. Additionally the (normalized) eigenvalue spectrum is returned. Specify the number of computed eigenvalues with `howmany`. """ +# TODO: Rewrite this similar to gauge_fix using transfermatrix_fixedpoint function MPSKit.correlation_length(peps::InfinitePEPS, env::CTMRGEnv; howmany=2) ξ = Array{Float64,3}(undef, (2, size(peps)...)) # First index picks horizontal or vertical direction λ = Array{ComplexF64,4}(undef, (2, howmany, size(peps)...)) diff --git a/src/algorithms/ctmrg_all_sides.jl b/src/algorithms/ctmrg_all_sides.jl index ecfee329..ae2252f5 100644 --- a/src/algorithms/ctmrg_all_sides.jl +++ b/src/algorithms/ctmrg_all_sides.jl @@ -1,5 +1,5 @@ # One CTMRG iteration with both-sided application of projectors -function ctmrg_iter(state, env::CTMRGEnv, alg::CTMRG{:AllSides}) +function ctmrg_iter(state, env::CTMRGEnv, alg::CTMRG{:simultaneous}) # Compute enlarged corners Q = enlarge_corners_edges(state, env) @@ -150,7 +150,7 @@ function renormalize_corners_edges(state, env::CTMRGEnv, Q, P_left, P_right) corners = copy(corners) edges = copy(edges) - corners[:, :, :] ./= norm.(corners[:, :, :]) - edges[:, :, :] ./= norm.(edges[:, :, :]) + @diffset corners[:, :, :] ./= norm.(corners[:, :, :]) + @diffset edges[:, :, :] ./= norm.(edges[:, :, :]) return corners, edges end diff --git a/src/algorithms/peps_opt.jl b/src/algorithms/peps_opt.jl index 507e0924..858d87d5 100644 --- a/src/algorithms/peps_opt.jl +++ b/src/algorithms/peps_opt.jl @@ -2,13 +2,13 @@ abstract type GradMode{F} end """ struct GeomSum(; maxiter=Defaults.fpgrad_maxiter, tol=Defaults.fpgrad_tol, - verbosity=0, iterscheme=:FixedIter) <: GradMode{iterscheme} + verbosity=0, iterscheme=:fixed) <: GradMode{iterscheme} Gradient mode for CTMRG using explicit evaluation of the geometric sum. With `iterscheme` the style of CTMRG iteration which is being differentiated can be chosen. -If set to `:FixedIter`, the differentiated CTMRG iteration is assumed to have a pre-computed -SVD of the environments with a fixed set of gauges. Alternatively, if set to `:DiffGauge`, +If set to `:fixed`, the differentiated CTMRG iteration is assumed to have a pre-computed +SVD of the environments with a fixed set of gauges. Alternatively, if set to `:diffgauge`, the differentiated iteration consists of a CTMRG iteration and a subsequent gauge fixing step, such that `gauge_fix` will also be differentiated everytime a CTMRG derivative is computed. """ @@ -21,20 +21,20 @@ function GeomSum(; maxiter=Defaults.fpgrad_maxiter, tol=Defaults.fpgrad_tol, verbosity=0, - iterscheme=:FixedIter, + iterscheme=:fixed, ) return GeomSum{iterscheme}(maxiter, tol, verbosity) end """ struct ManualIter(; maxiter=Defaults.fpgrad_maxiter, tol=Defaults.fpgrad_tol, - verbosity=0, iterscheme=:FixedIter) <: GradMode{iterscheme} + verbosity=0, iterscheme=:fixed) <: GradMode{iterscheme} Gradient mode for CTMRG using manual iteration to solve the linear problem. With `iterscheme` the style of CTMRG iteration which is being differentiated can be chosen. -If set to `:FixedIter`, the differentiated CTMRG iteration is assumed to have a pre-computed -SVD of the environments with a fixed set of gauges. Alternatively, if set to `:DiffGauge`, +If set to `:fixed`, the differentiated CTMRG iteration is assumed to have a pre-computed +SVD of the environments with a fixed set of gauges. Alternatively, if set to `:diffgauge`, the differentiated iteration consists of a CTMRG iteration and a subsequent gauge fixing step, such that `gauge_fix` will also be differentiated everytime a CTMRG derivative is computed. """ @@ -47,20 +47,20 @@ function ManualIter(; maxiter=Defaults.fpgrad_maxiter, tol=Defaults.fpgrad_tol, verbosity=0, - iterscheme=:FixedIter, + iterscheme=:fixed, ) return ManualIter{iterscheme}(maxiter, tol, verbosity) end """ - struct LinSolver(; solver=KrylovKit.GMRES(), iterscheme=:FixedIter) <: GradMode{iterscheme} + struct LinSolver(; solver=KrylovKit.GMRES(), iterscheme=:fixed) <: GradMode{iterscheme} Gradient mode wrapper around `KrylovKit.LinearSolver` for solving the gradient linear problem using iterative solvers. With `iterscheme` the style of CTMRG iteration which is being differentiated can be chosen. -If set to `:FixedIter`, the differentiated CTMRG iteration is assumed to have a pre-computed -SVD of the environments with a fixed set of gauges. Alternatively, if set to `:DiffGauge`, +If set to `:fixed`, the differentiated CTMRG iteration is assumed to have a pre-computed +SVD of the environments with a fixed set of gauges. Alternatively, if set to `:diffgauge`, the differentiated iteration consists of a CTMRG iteration and a subsequent gauge fixing step, such that `gauge_fix` will also be differentiated everytime a CTMRG derivative is computed. """ @@ -69,7 +69,7 @@ struct LinSolver{F} <: GradMode{F} end function LinSolver(; solver=KrylovKit.GMRES(; maxiter=Defaults.fpgrad_maxiter, tol=Defaults.fpgrad_tol), - iterscheme=:FixedIter, + iterscheme=:fixed, ) return LinSolver{iterscheme}(solver) end @@ -102,8 +102,8 @@ struct PEPSOptimize{G} verbosity, ) where {S,G} if gradient_alg isa GradMode - if S == :LeftMoves && G.parameters[1] == :FixedIter - throw(ArgumentError(":LeftMoves and :FixedIter are not compatible")) + if S == :sequential && G.parameters[1] == :fixed + throw(ArgumentError(":sequential and :fixed are not compatible")) end end return new{G}(boundary_alg, optimizer, reuse_env, gradient_alg, verbosity) @@ -171,7 +171,7 @@ Evaluating the gradient of the cost function for CTMRG: =# function _rrule( - gradmode::GradMode{:DiffGauge}, + gradmode::GradMode{:diffgauge}, ::RuleConfig, ::typeof(MPSKit.leading_boundary), envinit, @@ -202,14 +202,14 @@ end # Here f is differentiated from an pre-computed SVD with fixed U, S and V function _rrule( - gradmode::GradMode{:FixedIter}, + gradmode::GradMode{:fixed}, ::RuleConfig, ::typeof(MPSKit.leading_boundary), envinit, state, alg::CTMRG{C}, ) where {C} - @assert C == :AllSides + @assert C == :simultaneous envs = leading_boundary(envinit, state, alg) envsconv, info = ctmrg_iter(state, envs, alg) envsfix, signs = gauge_fix(envs, envsconv) @@ -219,9 +219,9 @@ function _rrule( svd_alg_fixed = SVDAdjoint(; fwd_alg=FixedSVD(Ufix, info.S, Vfix), rrule_alg=alg.projector_alg.svd_alg.rrule_alg ) - alg_fixed = CTMRG(; svd_alg=svd_alg_fixed, trscheme=notrunc(), ctmrgscheme=:AllSides) + alg_fixed = CTMRG(; svd_alg=svd_alg_fixed, trscheme=notrunc(), ctmrgscheme=:simultaneous) - function leading_boundary_fixediter_pullback(Δenvs′) + function leading_boundary_fixed_pullback(Δenvs′) Δenvs = unthunk(Δenvs′) _, env_vjp = pullback(state, envsfix) do A, x @@ -237,7 +237,7 @@ function _rrule( return NoTangent(), ZeroTangent(), ∂F∂envs, NoTangent() end - return envsfix, leading_boundary_fixediter_pullback + return envsfix, leading_boundary_fixed_pullback end @doc """ diff --git a/test/ctmrg/ctmrgschemes.jl b/test/ctmrg/ctmrgschemes.jl new file mode 100644 index 00000000..b31532f0 --- /dev/null +++ b/test/ctmrg/ctmrgschemes.jl @@ -0,0 +1,52 @@ +using Test +using Random +using TensorKit +using MPSKit +using PEPSKit + +# initialize parameters +χbond = 2 +χenv = 16 +ctm_alg_sequential = CTMRG(; tol=1e-10, verbosity=1, ctmrgscheme=:sequential) +ctm_alg_simultaneous = CTMRG(; tol=1e-10, verbosity=1, ctmrgscheme=:simultaneous) +unitcells = [(1, 1), (3, 4)] + +@testset "$(unitcell) unit cell" for unitcell in unitcells + # compute environments + Random.seed!(32350283290358) + psi = InfinitePEPS(2, χbond; unitcell) + env_sequential = leading_boundary( + CTMRGEnv(psi; Venv=ComplexSpace(χenv)), psi, ctm_alg_sequential + ) + env_simultaneous = leading_boundary( + CTMRGEnv(psi; Venv=ComplexSpace(χenv)), psi, ctm_alg_simultaneous + ) + + # compare norms + @test abs(norm(psi, env_sequential)) ≈ abs(norm(psi, env_simultaneous)) rtol = 1e-6 + + # compare singular values + CS_sequential = map(c -> tsvd(c; alg=TensorKit.SVD())[2], env_sequential.corners) + CS_simultaneous = map(c -> tsvd(c; alg=TensorKit.SVD())[2], env_simultaneous.corners) + ΔCS = maximum(zip(CS_sequential, CS_simultaneous)) do (c_lm, c_as) + smallest = infimum(MPSKit._firstspace(c_lm), MPSKit._firstspace(c_as)) + e_old = isometry(MPSKit._firstspace(c_lm), smallest) + e_new = isometry(MPSKit._firstspace(c_as), smallest) + return norm(e_new' * c_as * e_new - e_old' * c_lm * e_old) + end + @test ΔCS < 1e-2 + + TS_sequential = map(t -> tsvd(t; alg=TensorKit.SVD())[2], env_sequential.edges) + TS_simultaneous = map(t -> tsvd(t; alg=TensorKit.SVD())[2], env_simultaneous.edges) + ΔTS = maximum(zip(TS_sequential, TS_simultaneous)) do (t_lm, t_as) + MPSKit._firstspace(t_lm) == MPSKit._firstspace(t_as) || return scalartype(t_lm)(Inf) + return norm(t_as - t_lm) + end + @test ΔTS < 1e-2 + + # compare Heisenberg energies + H = square_lattice_heisenberg(; unitcell) + E_sequential = costfun(psi, env_sequential, H) + E_simultaneous = costfun(psi, env_simultaneous, H) + @test E_sequential ≈ E_simultaneous rtol=1e-4 +end diff --git a/test/ctmrg/fixediter.jl b/test/ctmrg/fixed_iterscheme.jl similarity index 82% rename from test/ctmrg/fixediter.jl rename to test/ctmrg/fixed_iterscheme.jl index b5d894cc..f6c3ed79 100644 --- a/test/ctmrg/fixediter.jl +++ b/test/ctmrg/fixed_iterscheme.jl @@ -16,42 +16,42 @@ using PEPSKit: svd_algs = [SVDAdjoint(; fwd_alg=TensorKit.SVD()), SVDAdjoint(; fwd_alg=IterSVD())] unitcells = [(1, 1), (3, 4)] -# test for element-wise convergence after application of FixedIter step +# test for element-wise convergence after application of fixed step @testset "$unitcell unit cell with $(typeof(svd_alg.fwd_alg))" for (unitcell, svd_alg) in Iterators.product( unitcells, svd_algs ) - ctm_alg = CTMRG(; tol=1e-12, verbosity=1, ctmrgscheme=:AllSides, svd_alg) + ctm_alg = CTMRG(; tol=1e-12, verbosity=1, ctmrgscheme=:simultaneous, svd_alg) # initialize states - Random.seed!(91283219347) + Random.seed!(2394823842) psi = InfinitePEPS(2, χbond; unitcell) env_conv1 = leading_boundary(CTMRGEnv(psi; Venv=ComplexSpace(χenv)), psi, ctm_alg) # do extra iteration to get SVD env_conv2, info = ctmrg_iter(psi, env_conv1, ctm_alg) env_fix, signs = gauge_fix(env_conv1, env_conv2) - @test check_elementwise_convergence(env_conv1, env_fix) + @test check_elementwise_convergence(env_conv1, env_fix; atol=1e-6) # fix gauge of SVD U_fix, V_fix = fix_relative_phases(info.U, info.V, signs) svd_alg_fix = SVDAdjoint(; fwd_alg=FixedSVD(U_fix, info.S, V_fix)) - ctm_alg_fix = CTMRG(; svd_alg=svd_alg_fix, trscheme=notrunc(), ctmrgscheme=:AllSides) + ctm_alg_fix = CTMRG(; svd_alg=svd_alg_fix, trscheme=notrunc(), ctmrgscheme=:simultaneous) # do iteration with FixedSVD env_fixedsvd, = ctmrg_iter(psi, env_conv1, ctm_alg_fix) env_fixedsvd = fix_global_phases(env_conv1, env_fixedsvd) - @test check_elementwise_convergence(env_conv1, env_fixedsvd) + @test check_elementwise_convergence(env_conv1, env_fixedsvd; atol=1e-6) end -# TODO: Why doesn't FixedIter work with IterSVD? +# TODO: Why doesn't fixed work with IterSVD? ## # ctm_alg = CTMRG(; # tol=1e-12, # miniter=4, # maxiter=100, # verbosity=1, -# ctmrgscheme=:AllSides, +# ctmrgscheme=:simultaneous, # svd_alg=SVDAdjoint(; fwd_alg=IterSVD()), # ) @@ -68,7 +68,7 @@ end # # fix gauge of SVD # U_fix, V_fix = fix_relative_phases(info.U, info.V, signs); # svd_alg_fix = SVDAdjoint(; fwd_alg=FixedSVD(U_fix, info.S, V_fix)); -# ctm_alg_fix = CTMRG(; svd_alg=svd_alg_fix, trscheme=notrunc(), ctmrgscheme=:AllSides); +# ctm_alg_fix = CTMRG(; svd_alg=svd_alg_fix, trscheme=notrunc(), ctmrgscheme=:simultaneous); # # do iteration with FixedSVD # env_fixedsvd, = ctmrg_iter(psi, env_conv1, ctm_alg_fix); diff --git a/test/ctmrg/gaugefix.jl b/test/ctmrg/gaugefix.jl index e3edff45..ec694b2b 100644 --- a/test/ctmrg/gaugefix.jl +++ b/test/ctmrg/gaugefix.jl @@ -7,7 +7,7 @@ using PEPSKit: ctmrg_iter, gauge_fix, check_elementwise_convergence scalartypes = [Float64, ComplexF64] unitcells = [(1, 1), (2, 2), (3, 2)] -schemes = [:AllSides, :LeftMoves] +schemes = [:simultaneous, :sequential] χ = Dict([(1, 1) => 8, (2, 2) => 26, (3, 2) => 26]) # Increase χ to converge non-symmetric environments @testset "Trivial symmetry ($T) - ($unitcell) - ($ctmrgscheme)" for ( diff --git a/test/ctmrg/gradients.jl b/test/ctmrg/gradients.jl index 283a1493..ce02754e 100644 --- a/test/ctmrg/gradients.jl +++ b/test/ctmrg/gradients.jl @@ -22,17 +22,17 @@ boundary_alg = CTMRG(; miniter=4, maxiter=100, verbosity=0, - ctmrgscheme=:AllSides, + ctmrgscheme=:simultaneous, svd_alg=SVDAdjoint(; fwd_alg=TensorKit.SVD(), rrule_alg=GMRES(; tol=1e-10)), ) gradmodes = [ nothing, - GeomSum(; tol, iterscheme=:FixedIter), - GeomSum(; tol, iterscheme=:DiffGauge), - ManualIter(; tol, iterscheme=:FixedIter), - ManualIter(; tol, iterscheme=:DiffGauge), - LinSolver(; solver=KrylovKit.GMRES(; tol=tol, maxiter=10), iterscheme=:FixedIter), - LinSolver(; solver=KrylovKit.GMRES(; tol=tol, maxiter=10), iterscheme=:DiffGauge), + GeomSum(; tol, iterscheme=:fixed), + GeomSum(; tol, iterscheme=:diffgauge), + ManualIter(; tol, iterscheme=:fixed), + ManualIter(; tol, iterscheme=:diffgauge), + LinSolver(; solver=KrylovKit.GMRES(; tol=tol, maxiter=10), iterscheme=:fixed), + LinSolver(; solver=KrylovKit.GMRES(; tol=tol, maxiter=10), iterscheme=:diffgauge), ] steps = -0.01:0.005:0.01 diff --git a/test/ctmrg/leftmoves_allsides.jl b/test/ctmrg/leftmoves_allsides.jl deleted file mode 100644 index 50ab2926..00000000 --- a/test/ctmrg/leftmoves_allsides.jl +++ /dev/null @@ -1,52 +0,0 @@ -using Test -using Random -using TensorKit -using MPSKit -using PEPSKit - -# initialize parameters -χbond = 2 -χenv = 16 -ctm_alg_leftmoves = CTMRG(; tol=1e-10, verbosity=1, ctmrgscheme=:LeftMoves) -ctm_alg_allsides = CTMRG(; tol=1e-10, verbosity=1, ctmrgscheme=:AllSides) -unitcells = [(1, 1), (3, 4)] - -@testset "$(unitcell) unit cell" for unitcell in unitcells - # compute environments - Random.seed!(32350283290358) - psi = InfinitePEPS(2, χbond; unitcell) - env_leftmoves = leading_boundary( - CTMRGEnv(psi; Venv=ComplexSpace(χenv)), psi, ctm_alg_leftmoves - ) - env_allsides = leading_boundary( - CTMRGEnv(psi; Venv=ComplexSpace(χenv)), psi, ctm_alg_allsides - ) - - # compare norms - @test abs(norm(psi, env_leftmoves)) ≈ abs(norm(psi, env_allsides)) rtol = 1e-6 - - # compare singular values - CS_leftmoves = map(c -> tsvd(c; alg=TensorKit.SVD())[2], env_leftmoves.corners) - CS_allsides = map(c -> tsvd(c; alg=TensorKit.SVD())[2], env_allsides.corners) - ΔCS = maximum(zip(CS_leftmoves, CS_allsides)) do (c_lm, c_as) - smallest = infimum(MPSKit._firstspace(c_lm), MPSKit._firstspace(c_as)) - e_old = isometry(MPSKit._firstspace(c_lm), smallest) - e_new = isometry(MPSKit._firstspace(c_as), smallest) - return norm(e_new' * c_as * e_new - e_old' * c_lm * e_old) - end - @test ΔCS < 1e-2 - - TS_leftmoves = map(t -> tsvd(t; alg=TensorKit.SVD())[2], env_leftmoves.edges) - TS_allsides = map(t -> tsvd(t; alg=TensorKit.SVD())[2], env_allsides.edges) - ΔTS = maximum(zip(TS_leftmoves, TS_allsides)) do (t_lm, t_as) - MPSKit._firstspace(t_lm) == MPSKit._firstspace(t_as) || return scalartype(t_lm)(Inf) - return norm(t_as - t_lm) - end - @test ΔTS < 1e-2 - - # compare Heisenberg energies - H = square_lattice_heisenberg(; unitcell) - E_leftmoves = costfun(psi, env_leftmoves, H) - E_allsides = costfun(psi, env_allsides, H) - @test E_leftmoves ≈ E_allsides rtol=1e-4 -end diff --git a/test/heisenberg.jl b/test/heisenberg.jl index 1648ed9a..38781695 100644 --- a/test/heisenberg.jl +++ b/test/heisenberg.jl @@ -15,12 +15,12 @@ ctm_alg = CTMRG(; verbosity=1, trscheme=truncdim(χenv), svd_alg=SVDAdjoint(; fwd_alg=TensorKit.SVD(), rrule_alg=GMRES(; tol=1e-10)), - ctmrgscheme=:AllSides + ctmrgscheme=:simultaneous ) opt_alg = PEPSOptimize(; boundary_alg=ctm_alg, optimizer=LBFGS(4; maxiter=100, gradtol=1e-3, verbosity=2), - gradient_alg=LinSolver(; solver=GMRES(; tol=1e-6, maxiter=100), iterscheme=:FixedIter), + gradient_alg=LinSolver(; solver=GMRES(; tol=1e-6, maxiter=100), iterscheme=:fixed), reuse_env=true, verbosity=2, ) diff --git a/test/runtests.jl b/test/runtests.jl index 143e188b..ee3e5657 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -20,14 +20,14 @@ end @time @safetestset "SVD wrapper" begin include("ctmrg/svd_wrapper.jl") end - @time @safetestset "SVD wrapper" begin - include("ctmrg/fixediter.jl") + @time @safetestset ":fixed CTMRG iteration scheme" begin + include("ctmrg/fixed_iterscheme.jl") end - @time @safetestset "SVD wrapper" begin + @time @safetestset "Unit cells" begin include("ctmrg/unitcell.jl") end - @time @safetestset "SVD wrapper" begin - include("ctmrg/leftmoves_allsides.jl") + @time @safetestset "CTMRG schemes" begin + include("ctmrg/ctmrgschemes.jl") end end if GROUP == "ALL" || GROUP == "MPS" From 2f4db74ecc0cf7528af04f0350edc1b2e6033f78 Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Fri, 12 Jul 2024 19:15:16 +0200 Subject: [PATCH 043/213] Fix old CTMRGEnv constructor --- test/ctmrg/ctmrgschemes.jl | 4 ++-- test/ctmrg/fixed_iterscheme.jl | 4 ++-- test/ctmrg/gaugefix.jl | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/test/ctmrg/ctmrgschemes.jl b/test/ctmrg/ctmrgschemes.jl index b31532f0..8a7af7eb 100644 --- a/test/ctmrg/ctmrgschemes.jl +++ b/test/ctmrg/ctmrgschemes.jl @@ -16,10 +16,10 @@ unitcells = [(1, 1), (3, 4)] Random.seed!(32350283290358) psi = InfinitePEPS(2, χbond; unitcell) env_sequential = leading_boundary( - CTMRGEnv(psi; Venv=ComplexSpace(χenv)), psi, ctm_alg_sequential + CTMRGEnv(psi, ComplexSpace(χenv)), psi, ctm_alg_sequential ) env_simultaneous = leading_boundary( - CTMRGEnv(psi; Venv=ComplexSpace(χenv)), psi, ctm_alg_simultaneous + CTMRGEnv(psi, ComplexSpace(χenv)), psi, ctm_alg_simultaneous ) # compare norms diff --git a/test/ctmrg/fixed_iterscheme.jl b/test/ctmrg/fixed_iterscheme.jl index f6c3ed79..05d654ab 100644 --- a/test/ctmrg/fixed_iterscheme.jl +++ b/test/ctmrg/fixed_iterscheme.jl @@ -26,7 +26,7 @@ unitcells = [(1, 1), (3, 4)] # initialize states Random.seed!(2394823842) psi = InfinitePEPS(2, χbond; unitcell) - env_conv1 = leading_boundary(CTMRGEnv(psi; Venv=ComplexSpace(χenv)), psi, ctm_alg) + env_conv1 = leading_boundary(CTMRGEnv(psi, ComplexSpace(χenv)), psi, ctm_alg) # do extra iteration to get SVD env_conv2, info = ctmrg_iter(psi, env_conv1, ctm_alg) @@ -58,7 +58,7 @@ end # # initialize states # Random.seed!(91283219347) # psi = InfinitePEPS(2, χbond) -# env_conv1 = leading_boundary(CTMRGEnv(psi; Venv=ComplexSpace(χenv)), psi, ctm_alg); +# env_conv1 = leading_boundary(CTMRGEnv(psi, ComplexSpace(χenv)), psi, ctm_alg); # # do extra iteration to get SVD # env_conv2, info = ctmrg_iter(psi, env_conv1, ctm_alg); diff --git a/test/ctmrg/gaugefix.jl b/test/ctmrg/gaugefix.jl index ec694b2b..359bbfd2 100644 --- a/test/ctmrg/gaugefix.jl +++ b/test/ctmrg/gaugefix.jl @@ -21,7 +21,7 @@ schemes = [:simultaneous, :sequential] Random.seed!(29358293852) # Seed RNG to make random environment consistent psi = InfinitePEPS(randn, T, physical_space, peps_space; unitcell) - ctm = CTMRGEnv(psi; Venv=ctm_space) + ctm = CTMRGEnv(psi, ctm_space) alg = CTMRG(; tol=1e-10, maxiter=100, verbosity=1, trscheme=FixedSpaceTruncation(), ctmrgscheme @@ -43,7 +43,7 @@ end Random.seed!(2938293852938) # Seed RNG to make random environment consistent psi = InfinitePEPS(physical_space, peps_space; unitcell) - ctm = CTMRGEnv(psi; Venv=ctm_space) + ctm = CTMRGEnv(psi, ctm_space) alg = CTMRG(; tol=1e-10, maxiter=400, verbosity=1, trscheme=FixedSpaceTruncation(), ctmrgscheme From 4c0ba28e4129a5fdcbec53d6a157aa0eeea062fc Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Fri, 12 Jul 2024 19:18:09 +0200 Subject: [PATCH 044/213] Formatting --- src/algorithms/peps_opt.jl | 14 +++++--------- test/ctmrg/ctmrgschemes.jl | 2 +- test/ctmrg/fixed_iterscheme.jl | 4 +++- test/heisenberg.jl | 2 +- 4 files changed, 10 insertions(+), 12 deletions(-) diff --git a/src/algorithms/peps_opt.jl b/src/algorithms/peps_opt.jl index 2a0b747d..f17612df 100644 --- a/src/algorithms/peps_opt.jl +++ b/src/algorithms/peps_opt.jl @@ -18,10 +18,7 @@ struct GeomSum{F} <: GradMode{F} verbosity::Int end function GeomSum(; - maxiter=Defaults.fpgrad_maxiter, - tol=Defaults.fpgrad_tol, - verbosity=0, - iterscheme=:fixed, + maxiter=Defaults.fpgrad_maxiter, tol=Defaults.fpgrad_tol, verbosity=0, iterscheme=:fixed ) return GeomSum{iterscheme}(maxiter, tol, verbosity) end @@ -44,10 +41,7 @@ struct ManualIter{F} <: GradMode{F} verbosity::Int end function ManualIter(; - maxiter=Defaults.fpgrad_maxiter, - tol=Defaults.fpgrad_tol, - verbosity=0, - iterscheme=:fixed, + maxiter=Defaults.fpgrad_maxiter, tol=Defaults.fpgrad_tol, verbosity=0, iterscheme=:fixed ) return ManualIter{iterscheme}(maxiter, tol, verbosity) end @@ -219,7 +213,9 @@ function _rrule( svd_alg_fixed = SVDAdjoint(; fwd_alg=FixedSVD(Ufix, info.S, Vfix), rrule_alg=alg.projector_alg.svd_alg.rrule_alg ) - alg_fixed = CTMRG(; svd_alg=svd_alg_fixed, trscheme=notrunc(), ctmrgscheme=:simultaneous) + alg_fixed = CTMRG(; + svd_alg=svd_alg_fixed, trscheme=notrunc(), ctmrgscheme=:simultaneous + ) function leading_boundary_fixed_pullback(Δenvs′) Δenvs = unthunk(Δenvs′) diff --git a/test/ctmrg/ctmrgschemes.jl b/test/ctmrg/ctmrgschemes.jl index 8a7af7eb..8b35b3ac 100644 --- a/test/ctmrg/ctmrgschemes.jl +++ b/test/ctmrg/ctmrgschemes.jl @@ -48,5 +48,5 @@ unitcells = [(1, 1), (3, 4)] H = square_lattice_heisenberg(; unitcell) E_sequential = costfun(psi, env_sequential, H) E_simultaneous = costfun(psi, env_simultaneous, H) - @test E_sequential ≈ E_simultaneous rtol=1e-4 + @test E_sequential ≈ E_simultaneous rtol = 1e-4 end diff --git a/test/ctmrg/fixed_iterscheme.jl b/test/ctmrg/fixed_iterscheme.jl index 05d654ab..fa381b38 100644 --- a/test/ctmrg/fixed_iterscheme.jl +++ b/test/ctmrg/fixed_iterscheme.jl @@ -36,7 +36,9 @@ unitcells = [(1, 1), (3, 4)] # fix gauge of SVD U_fix, V_fix = fix_relative_phases(info.U, info.V, signs) svd_alg_fix = SVDAdjoint(; fwd_alg=FixedSVD(U_fix, info.S, V_fix)) - ctm_alg_fix = CTMRG(; svd_alg=svd_alg_fix, trscheme=notrunc(), ctmrgscheme=:simultaneous) + ctm_alg_fix = CTMRG(; + svd_alg=svd_alg_fix, trscheme=notrunc(), ctmrgscheme=:simultaneous + ) # do iteration with FixedSVD env_fixedsvd, = ctmrg_iter(psi, env_conv1, ctm_alg_fix) diff --git a/test/heisenberg.jl b/test/heisenberg.jl index 1602683f..64de5e06 100644 --- a/test/heisenberg.jl +++ b/test/heisenberg.jl @@ -15,7 +15,7 @@ ctm_alg = CTMRG(; verbosity=1, trscheme=truncdim(χenv), svd_alg=SVDAdjoint(; fwd_alg=TensorKit.SVD(), rrule_alg=GMRES(; tol=1e-10)), - ctmrgscheme=:simultaneous + ctmrgscheme=:simultaneous, ) opt_alg = PEPSOptimize(; boundary_alg=ctm_alg, From 47a74a047ab1d6b5532cfa1e90fbd6eff54e23d2 Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Mon, 15 Jul 2024 13:44:55 +0200 Subject: [PATCH 045/213] Move correlation_length out of PR (add separate PR later) --- src/PEPSKit.jl | 2 +- src/algorithms/ctmrg.jl | 42 ----------------------------------------- 2 files changed, 1 insertion(+), 43 deletions(-) diff --git a/src/PEPSKit.jl b/src/PEPSKit.jl index 2dd1057a..52d8cdec 100644 --- a/src/PEPSKit.jl +++ b/src/PEPSKit.jl @@ -63,7 +63,7 @@ module Defaults end export SVDAdjoint, IterSVD, NonTruncSVDAdjoint -export FixedSpaceTruncation, ProjectorAlg, CTMRG, CTMRGEnv, correlation_length +export FixedSpaceTruncation, ProjectorAlg, CTMRG, CTMRGEnv export LocalOperator export expectation_value, costfun export leading_boundary diff --git a/src/algorithms/ctmrg.jl b/src/algorithms/ctmrg.jl index 288e2766..c985fc2f 100644 --- a/src/algorithms/ctmrg.jl +++ b/src/algorithms/ctmrg.jl @@ -353,45 +353,3 @@ function LinearAlgebra.norm(peps::InfinitePEPS, env::CTMRGEnv) return total end - -""" - correlation_length(peps::InfinitePEPS, env::CTMRGEnv; howmany=2) - -Compute the PEPS correlation length based on the horizontal and vertical -transfer matrices. Additionally the (normalized) eigenvalue spectrum is -returned. Specify the number of computed eigenvalues with `howmany`. -""" -# TODO: Rewrite this similar to gauge_fix using transfermatrix_fixedpoint -function MPSKit.correlation_length(peps::InfinitePEPS, env::CTMRGEnv; howmany=2) - ξ = Array{Float64,3}(undef, (2, size(peps)...)) # First index picks horizontal or vertical direction - λ = Array{ComplexF64,4}(undef, (2, howmany, size(peps)...)) - for r in 1:size(peps, 1), c in 1:size(peps, 2) - @autoopt @tensor transferh[χ_LT D_Lab D_Lbe χ_LB; χ_RT D_Rab D_Rbe χ_RB] := - env.edges[NORTH, _prev(r, end), c][χ_LT D1 D2; χ_RT] * - peps[r, c][d; D1 D_Rab D3 D_Lab] * - conj(peps[r, c][d; D2 D_Rbe D4 D_Lbe]) * - env.edges[SOUTH, _next(r, end), c][χ_RB D3 D4; χ_LB] - @autoopt @tensor transferv[χ_TL D_Tab D_Tbe χ_TL; χ_BL D_Bab D_Bbe χ_BR] := - env.edges[EAST, r, _next(c, end)][χ_TR D1 D2; χ_BR] * - peps[r, c][d; D_Tab D1 D_Bab D3] * - conj(peps[r, c][d; D_Tbe D2 D_Bbe D4]) * - env.edges[WEST, r, _prev(c, end)][χ_BL D3 D4; χ_TL] - - function lintransfer(v, t) - @tensor v′[-1 -2 -3 -4] := t[-1 -2 -3 -4; 1 2 3 4] * v[1 2 3 4] - return v′ - end - - v₀h = Tensor(randn, scalartype(transferh), domain(transferh)) - valsh, = eigsolve(v -> lintransfer(v, transferh), v₀h, howmany, :LM) - λ[1, :, r, c] = valsh[1:howmany] / abs(valsh[1]) # Normalize largest eigenvalue to 1 - ξ[1, r, c] = -1 / log(abs(λ[1, 2, r, c])) - - v₀v = Tensor(rand, scalartype(transferv), domain(transferv)) - valsv, = eigsolve(v -> lintransfer(v, transferv), v₀v, howmany, :LM) - λ[2, :, r, c] = valsv[1:howmany] / abs(valsv[1]) # Normalize largest eigenvalue to 1 - ξ[2, r, c] = -1 / log(abs(λ[2, 2, r, c])) - end - - return ξ, λ -end From 4c65c8c8b20aa1a195b2e4846bef3165d5611459 Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Mon, 15 Jul 2024 14:52:11 +0200 Subject: [PATCH 046/213] Refactor enlarged corners again, update Defaults, clean up docstrings --- src/PEPSKit.jl | 6 +++ src/algorithms/ctmrg.jl | 78 ++++++++++++++----------------- src/algorithms/ctmrg_all_sides.jl | 12 ++--- src/algorithms/peps_opt.jl | 21 ++++++--- src/utility/svd.jl | 13 +++--- test/ctmrg/fixed_iterscheme.jl | 46 +++++++++--------- test/heisenberg.jl | 3 +- 7 files changed, 91 insertions(+), 88 deletions(-) diff --git a/src/PEPSKit.jl b/src/PEPSKit.jl index 52d8cdec..a30251c5 100644 --- a/src/PEPSKit.jl +++ b/src/PEPSKit.jl @@ -55,11 +55,17 @@ Module containing default values that represent typical algorithm parameters. - `fpgrad_tol = 1e-6`: Convergence tolerance for the fixed-point gradient iteration """ module Defaults + using TensorKit, KrylovKit, OptimKit const ctmrg_maxiter = 100 const ctmrg_miniter = 4 const ctmrg_tol = 1e-10 const fpgrad_maxiter = 20 const fpgrad_tol = 1e-6 + const ctmrgscheme = :simultaneous + const iterscheme = :fixed + const fwd_alg = TensorKit.SVD() + const rrule_alg = GMRES(; tol=ctmrg_tol) + const optimizer = LBFGS(10; maxiter=100, gradtol=1e-4, verbosity=2) end export SVDAdjoint, IterSVD, NonTruncSVDAdjoint diff --git a/src/algorithms/ctmrg.jl b/src/algorithms/ctmrg.jl index c985fc2f..aba76324 100644 --- a/src/algorithms/ctmrg.jl +++ b/src/algorithms/ctmrg.jl @@ -28,8 +28,8 @@ end """ CTMRG(; tol=Defaults.ctmrg_tol, maxiter=Defaults.ctmrg_maxiter, miniter=Defaults.ctmrg_miniter, verbosity=0, - svd_alg=TensorKit.SVD(), trscheme=FixedSpaceTruncation(), - ctmrgscheme=:simultaneous) + svd_alg=SVDAdjoint(), trscheme=FixedSpaceTruncation(), + ctmrgscheme=Defaults.ctmrgscheme) Algorithm struct that represents the CTMRG algorithm for contracting infinite PEPS. Each CTMRG run is converged up to `tol` where the singular value convergence of the @@ -60,7 +60,7 @@ function CTMRG(; verbosity=1, svd_alg=SVDAdjoint(), trscheme=FixedSpaceTruncation(), - ctmrgscheme=:simultaneous, + ctmrgscheme=Defaults.ctmrgscheme, ) return CTMRG{ctmrgscheme}( tol, maxiter, miniter, verbosity, ProjectorAlg(; svd_alg, trscheme, verbosity) @@ -184,8 +184,8 @@ function left_move(state, env::CTMRGEnv{C,T}, alg::ProjectorAlg) where {C,T} # Compute projectors for row in 1:size(state, 1) # Enlarged corners - Q_sw = southwest_corner((_next(row, size(state, 1)), col), state, env) - Q_nw = northwest_corner((row, col), state, env) + Q_sw = southwest_corner((_next(row, size(state, 1)), col), env, state) + Q_nw = northwest_corner((row, col), env, state) # SVD half-infinite environment trscheme = if alg.trscheme isa FixedSpaceTruncation @@ -236,48 +236,38 @@ function left_move(state, env::CTMRGEnv{C,T}, alg::ProjectorAlg) where {C,T} return CTMRGEnv(corners, edges), (; P_left=copy(P_top), P_right=copy(P_bottom), ϵ) end -# Generic enlarged corner contraction -function enlarged_corner(edge_in, corner, edge_out, peps_above, peps_below=peps_above) - return @autoopt @tensor Q[χ_in D_inabove D_inbelow; χ_out D_outabove D_outbelow] := - edge_in[χ_in D1 D2; χ1] * - corner[χ1; χ2] * - edge_out[χ2 D3 D4; χ_out] * - peps_above[d; D3 D_outabove D_inabove D1] * - conj(peps_below[d; D4 D_outbelow D_inbelow D2]) +# Enlarged corner contractions (need direction specific methods to avoid PEPS rotations) +function northwest_corner((row, col), env, peps_above, peps_below=peps_above) + return @autoopt @tensor corner[χ_S D_Sabove D_Sbelow; χ_E D_Eabove D_Ebelow] := + env.edges[WEST, row, _prev(col, end)][χ_S D1 D2; χ1] * + env.corners[NORTHWEST, _prev(row, end), _prev(col, end)][χ1; χ2] * + env.edges[NORTH, _prev(row, end), col][χ2 D3 D4; χ_E] * + peps_above[row, col][d; D3 D_Eabove D_Sabove D1] * + conj(peps_below[row, col][d; D4 D_Ebelow D_Sbelow D2]) end - -# Direction specific methods -function northwest_corner((row, col), state, env) - return enlarged_corner( - env.edges[WEST, row, _prev(col, end)], - env.corners[NORTHWEST, _prev(row, end), _prev(col, end)], - env.edges[NORTH, _prev(row, end), col], - state[row, col], - ) -end -function northeast_corner((row, col), state, env) - return enlarged_corner( - env.edges[NORTH, _prev(row, end), col], - env.corners[NORTHEAST, _prev(row, end), _next(col, end)], - env.edges[EAST, row, _next(col, end)], - rotate_north(state[row, col], EAST), - ) +function northeast_corner((row, col), env, peps_above, peps_below=peps_above) + return @autoopt @tensor corner[χ_W D_Wabove D_Wbelow; χ_S D_Sabove D_Sbelow] := + env.edges[NORTH, _prev(row, end), col][χ_W D1 D2; χ1] * + env.corners[NORTHEAST, _prev(row, end), _next(col, end)][χ1; χ2] * + env.edges[EAST, row, _next(col, end)][χ2 D3 D4; χ_S] * + peps_above[row, col][d; D1 D3 D_Sabove D_Wabove] * + conj(peps_below[row, col][d; D2 D4 D_Sbelow D_Wbelow]) end -function southeast_corner((row, col), state, env) - return enlarged_corner( - env.edges[EAST, row, _next(col, end)], - env.corners[SOUTHEAST, _next(row, end), _next(col, end)], - env.edges[SOUTH, _next(row, end), col], - rotate_north(state[row, col], SOUTH), - ) +function southeast_corner((row, col), env, peps_above, peps_below=peps_above) + return @autoopt @tensor corner[χ_N D_Nabove D_Nbelow; χ_W D_Wabove D_Wbelow] := + env.edges[EAST, row, _next(col, end)][χ_N D1 D2; χ1] * + env.corners[SOUTHEAST, _next(row, end), _next(col, end)][χ1; χ2] * + env.edges[SOUTH, _next(row, end), col][χ2 D3 D4; χ_W] * + peps_above[row, col][d; D_Nabove D1 D3 D_Wabove] * + conj(peps_below[row, col][d; D_Nbelow D2 D4 D_Wbelow]) end -function southwest_corner((row, col), state, env) - return enlarged_corner( - env.edges[SOUTH, _next(row, end), col], - env.corners[SOUTHWEST, _next(row, end), _prev(col, end)], - env.edges[WEST, row, _prev(col, end)], - rotate_north(state[row, col], WEST), - ) +function southwest_corner((row, col), env, peps_above, peps_below=peps_above) + return @autoopt @tensor corner[χ_E D_Eabove D_Ebelow; χ_N D_Nabove D_Nbelow] := + env.edges[SOUTH, _next(row, end), col][χ_E D1 D2; χ1] * + env.corners[SOUTHWEST, _next(row, end), _prev(col, end)][χ1; χ2] * + env.edges[WEST, row, _prev(col, end)][χ2 D3 D4; χ_N] * + peps_above[row, col][d; D_Nabove D_Eabove D1 D3] * + conj(peps_below[row, col][d; D_Nbelow D_Ebelow D2 D4]) end # Build projectors from SVD and enlarged SW & NW corners diff --git a/src/algorithms/ctmrg_all_sides.jl b/src/algorithms/ctmrg_all_sides.jl index ae2252f5..4faf4d5f 100644 --- a/src/algorithms/ctmrg_all_sides.jl +++ b/src/algorithms/ctmrg_all_sides.jl @@ -19,13 +19,13 @@ function enlarge_corners_edges(state, env::CTMRGEnv{C,T}) where {C,T} drc_combinations = collect(Iterators.product(axes(env.corners)...)) @fwdthreads for (dir, r, c) in drc_combinations Q[dir, r, c] = if dir == NORTHWEST - northwest_corner((r, c), state, env) + northwest_corner((r, c), env, state) elseif dir == NORTHEAST - northeast_corner((r, c), state, env) - elseif dir == SOUTHEAST - southeast_corner((r, c), state, env) - elseif dir == SOUTHWEST - southwest_corner((r, c), state, env) + northeast_corner((r, c), env, state) + elseif dir == SOUTHEAST + southeast_corner((r, c), env, state) + elseif dir == SOUTHWEST + southwest_corner((r, c), env, state) end end diff --git a/src/algorithms/peps_opt.jl b/src/algorithms/peps_opt.jl index f17612df..91d897ca 100644 --- a/src/algorithms/peps_opt.jl +++ b/src/algorithms/peps_opt.jl @@ -2,7 +2,7 @@ abstract type GradMode{F} end """ struct GeomSum(; maxiter=Defaults.fpgrad_maxiter, tol=Defaults.fpgrad_tol, - verbosity=0, iterscheme=:fixed) <: GradMode{iterscheme} + verbosity=0, iterscheme=Defaults.iterscheme) <: GradMode{iterscheme} Gradient mode for CTMRG using explicit evaluation of the geometric sum. @@ -18,14 +18,17 @@ struct GeomSum{F} <: GradMode{F} verbosity::Int end function GeomSum(; - maxiter=Defaults.fpgrad_maxiter, tol=Defaults.fpgrad_tol, verbosity=0, iterscheme=:fixed + maxiter=Defaults.fpgrad_maxiter, + tol=Defaults.fpgrad_tol, + verbosity=0, + iterscheme=Defaults.iterscheme, ) return GeomSum{iterscheme}(maxiter, tol, verbosity) end """ struct ManualIter(; maxiter=Defaults.fpgrad_maxiter, tol=Defaults.fpgrad_tol, - verbosity=0, iterscheme=:fixed) <: GradMode{iterscheme} + verbosity=0, iterscheme=Defaults.iterscheme) <: GradMode{iterscheme} Gradient mode for CTMRG using manual iteration to solve the linear problem. @@ -41,13 +44,16 @@ struct ManualIter{F} <: GradMode{F} verbosity::Int end function ManualIter(; - maxiter=Defaults.fpgrad_maxiter, tol=Defaults.fpgrad_tol, verbosity=0, iterscheme=:fixed + maxiter=Defaults.fpgrad_maxiter, + tol=Defaults.fpgrad_tol, + verbosity=0, + iterscheme=Defaults.iterscheme, ) return ManualIter{iterscheme}(maxiter, tol, verbosity) end """ - struct LinSolver(; solver=KrylovKit.GMRES(), iterscheme=:fixed) <: GradMode{iterscheme} + struct LinSolver(; solver=KrylovKit.GMRES(), iterscheme=Defaults.iterscheme) <: GradMode{iterscheme} Gradient mode wrapper around `KrylovKit.LinearSolver` for solving the gradient linear problem using iterative solvers. @@ -63,7 +69,7 @@ struct LinSolver{F} <: GradMode{F} end function LinSolver(; solver=KrylovKit.GMRES(; maxiter=Defaults.fpgrad_maxiter, tol=Defaults.fpgrad_tol), - iterscheme=:fixed, + iterscheme=Defaults.iterscheme, ) return LinSolver{iterscheme}(solver) end @@ -105,7 +111,7 @@ struct PEPSOptimize{G} end function PEPSOptimize(; boundary_alg=CTMRG(), - optimizer=LBFGS(4; maxiter=100, gradtol=1e-4, verbosity=2), + optimizer=Defaults.optimizer, reuse_env=true, gradient_alg=LinSolver(), verbosity=0, @@ -204,6 +210,7 @@ function _rrule( alg::CTMRG{C}, ) where {C} @assert C == :simultaneous + @assert alg.projector_alg.svd_alg.rrule_alg isa Union{KrylovKit.LinearSolver,Arnoldi} envs = leading_boundary(envinit, state, alg) envsconv, info = ctmrg_iter(state, envs, alg) envsfix, signs = gauge_fix(envs, envsconv) diff --git a/src/utility/svd.jl b/src/utility/svd.jl index fac11254..dafdccbb 100644 --- a/src/utility/svd.jl +++ b/src/utility/svd.jl @@ -10,7 +10,7 @@ using TensorKit: const CRCExt = Base.get_extension(KrylovKit, :KrylovKitChainRulesCoreExt) """ - struct SVDAdjoint(; fwd_alg = TensorKit.SVD(), rrule_alg = nothing, + struct SVDAdjoint(; fwd_alg = Defaults.fwd_alg, rrule_alg = Defaults.rrule_alg, broadening = nothing) Wrapper for a SVD algorithm `fwd_alg` with a defined reverse rule `rrule_alg`. @@ -19,8 +19,8 @@ In case of degenerate singular values, one might need a `broadening` scheme whic removes the divergences from the adjoint. """ @kwdef struct SVDAdjoint{F,R,B} - fwd_alg::F = TensorKit.SVD() - rrule_alg::R = nothing + fwd_alg::F = Defaults.fwd_alg + rrule_alg::R = Defaults.rrule_alg broadening::B = nothing end # Keep truncation algorithm separate to be able to specify CTMRG dependent information @@ -149,9 +149,10 @@ function ChainRulesCore.rrule( n_vals = length(Sdc) lvecs = Vector{Vector{scalartype(t)}}(eachcol(Uc)) rvecs = Vector{Vector{scalartype(t)}}(eachcol(Vc')) - minimal_info = KrylovKit.ConvergenceInfo(length(Sdc), nothing, nothing, -1, -1) # Only num. converged is used - minimal_alg = GKL(; tol=1e-6) # Only tolerance is used for gauge sensitivity - # TODO: How do we not hard-code this tolerance? + + # Dummy objects only used for warnings + minimal_info = KrylovKit.ConvergenceInfo(n_vals, nothing, nothing, -1, -1) # Only num. converged is used + minimal_alg = GKL(; tol=1e-6) # Only tolerance is used for gauge sensitivity (# TODO: How do we not hard-code this tolerance?) if ΔUc isa AbstractZero && ΔVc isa AbstractZero # Handle ZeroTangent singular vectors Δlvecs = fill(ZeroTangent(), n_vals) diff --git a/test/ctmrg/fixed_iterscheme.jl b/test/ctmrg/fixed_iterscheme.jl index fa381b38..91dab322 100644 --- a/test/ctmrg/fixed_iterscheme.jl +++ b/test/ctmrg/fixed_iterscheme.jl @@ -48,31 +48,31 @@ end # TODO: Why doesn't fixed work with IterSVD? ## -# ctm_alg = CTMRG(; -# tol=1e-12, -# miniter=4, -# maxiter=100, -# verbosity=1, -# ctmrgscheme=:simultaneous, -# svd_alg=SVDAdjoint(; fwd_alg=IterSVD()), -# ) +ctm_alg = CTMRG(; + tol=1e-12, + miniter=4, + maxiter=100, + verbosity=1, + ctmrgscheme=:simultaneous, + svd_alg=SVDAdjoint(; fwd_alg=IterSVD()), +) -# # initialize states -# Random.seed!(91283219347) -# psi = InfinitePEPS(2, χbond) -# env_conv1 = leading_boundary(CTMRGEnv(psi, ComplexSpace(χenv)), psi, ctm_alg); +# initialize states +Random.seed!(91283219347) +psi = InfinitePEPS(2, χbond) +env_conv1 = leading_boundary(CTMRGEnv(psi, ComplexSpace(χenv)), psi, ctm_alg); # # do extra iteration to get SVD -# env_conv2, info = ctmrg_iter(psi, env_conv1, ctm_alg); -# env_fix, signs = gauge_fix(env_conv1, env_conv2); -# @test check_elementwise_convergence(env_conv1, env_fix) +env_conv2, info = ctmrg_iter(psi, env_conv1, ctm_alg); +env_fix, signs = gauge_fix(env_conv1, env_conv2); +@test check_elementwise_convergence(env_conv1, env_fix) -# # fix gauge of SVD -# U_fix, V_fix = fix_relative_phases(info.U, info.V, signs); -# svd_alg_fix = SVDAdjoint(; fwd_alg=FixedSVD(U_fix, info.S, V_fix)); -# ctm_alg_fix = CTMRG(; svd_alg=svd_alg_fix, trscheme=notrunc(), ctmrgscheme=:simultaneous); +# fix gauge of SVD +U_fix, V_fix = fix_relative_phases(info.U, info.V, signs); +svd_alg_fix = SVDAdjoint(; fwd_alg=FixedSVD(U_fix, info.S, V_fix)); +ctm_alg_fix = CTMRG(; svd_alg=svd_alg_fix, trscheme=notrunc(), ctmrgscheme=:simultaneous); -# # do iteration with FixedSVD -# env_fixedsvd, = ctmrg_iter(psi, env_conv1, ctm_alg_fix); -# env_fixedsvd = fix_global_phases(env_conv1, env_fixedsvd); -# @test check_elementwise_convergence(env_conv1, env_fixedsvd) \ No newline at end of file +# do iteration with FixedSVD +env_fixedsvd, = ctmrg_iter(psi, env_conv1, ctm_alg_fix); +env_fixedsvd = fix_global_phases(env_conv1, env_fixedsvd); +@test check_elementwise_convergence(env_conv1, env_fixedsvd) \ No newline at end of file diff --git a/test/heisenberg.jl b/test/heisenberg.jl index 64de5e06..3ce0b8d2 100644 --- a/test/heisenberg.jl +++ b/test/heisenberg.jl @@ -13,8 +13,7 @@ ctm_alg = CTMRG(; miniter=4, maxiter=100, verbosity=1, - trscheme=truncdim(χenv), - svd_alg=SVDAdjoint(; fwd_alg=TensorKit.SVD(), rrule_alg=GMRES(; tol=1e-10)), + svd_alg=SVDAdjoint(; fwd_alg=TensorKit.SVD(), rrule_alg=GMRES(tol=1e-10)), ctmrgscheme=:simultaneous, ) opt_alg = PEPSOptimize(; From ccb1f95f644aa814ef3131c9d23b5eadff28482a Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Mon, 15 Jul 2024 15:10:39 +0200 Subject: [PATCH 047/213] Clean up and format --- src/algorithms/ctmrg.jl | 4 +-- src/algorithms/ctmrg_all_sides.jl | 4 +-- src/algorithms/peps_opt.jl | 2 +- src/utility/svd.jl | 8 +++--- test/ctmrg/fixed_iterscheme.jl | 46 +++++++++++++++---------------- test/heisenberg.jl | 2 +- 6 files changed, 33 insertions(+), 33 deletions(-) diff --git a/src/algorithms/ctmrg.jl b/src/algorithms/ctmrg.jl index aba76324..65b10029 100644 --- a/src/algorithms/ctmrg.jl +++ b/src/algorithms/ctmrg.jl @@ -9,8 +9,8 @@ struct FixedSpaceTruncation <: TensorKit.TruncationScheme end # TODO: add option for different projector styles (half-infinite, full-infinite, etc.) """ - struct ProjectorAlg{S}(; svd_alg = TensorKit.SVD(), trscheme = TensorKit.notrunc(), - fixedspace = false, verbosity = 0) + struct ProjectorAlg{S}(; svd_alg=TensorKit.SVD(), trscheme=TensorKit.notrunc(), + fixedspace=false, verbosity=0) Algorithm struct collecting all projector related parameters. The truncation scheme has to be a `TensorKit.TruncationScheme`, and some SVD algorithms might have further restrictions on what diff --git a/src/algorithms/ctmrg_all_sides.jl b/src/algorithms/ctmrg_all_sides.jl index 4faf4d5f..14b6b24c 100644 --- a/src/algorithms/ctmrg_all_sides.jl +++ b/src/algorithms/ctmrg_all_sides.jl @@ -22,9 +22,9 @@ function enlarge_corners_edges(state, env::CTMRGEnv{C,T}) where {C,T} northwest_corner((r, c), env, state) elseif dir == NORTHEAST northeast_corner((r, c), env, state) - elseif dir == SOUTHEAST + elseif dir == SOUTHEAST southeast_corner((r, c), env, state) - elseif dir == SOUTHWEST + elseif dir == SOUTHWEST southwest_corner((r, c), env, state) end end diff --git a/src/algorithms/peps_opt.jl b/src/algorithms/peps_opt.jl index 91d897ca..6ac567a8 100644 --- a/src/algorithms/peps_opt.jl +++ b/src/algorithms/peps_opt.jl @@ -75,7 +75,7 @@ function LinSolver(; end """ - PEPSOptimize{G}(; boundary_alg=CTMRG(), optimizer::OptimKit.OptimizationAlgorithm=LBFGS() + PEPSOptimize{G}(; boundary_alg=CTMRG(), optimizer::OptimKit.OptimizationAlgorithm=Defaults.optimizer reuse_env::Bool=true, gradient_alg::G=LinSolver(), verbosity::Int=0) Algorithm struct that represent PEPS ground-state optimization using AD. diff --git a/src/utility/svd.jl b/src/utility/svd.jl index dafdccbb..94edcd52 100644 --- a/src/utility/svd.jl +++ b/src/utility/svd.jl @@ -10,8 +10,8 @@ using TensorKit: const CRCExt = Base.get_extension(KrylovKit, :KrylovKitChainRulesCoreExt) """ - struct SVDAdjoint(; fwd_alg = Defaults.fwd_alg, rrule_alg = Defaults.rrule_alg, - broadening = nothing) + struct SVDAdjoint(; fwd_alg=Defaults.fwd_alg, rrule_alg=Defaults.rrule_alg, + broadening=nothing) Wrapper for a SVD algorithm `fwd_alg` with a defined reverse rule `rrule_alg`. If `isnothing(rrule_alg)`, Zygote differentiates the forward call automatically. @@ -58,7 +58,7 @@ function TensorKit._tsvd!(t, alg::FixedSVD, ::NoTruncation, ::Real=2) end """ - struct IterSVD(; alg = KrylovKit.GKL(), fallback_threshold = Inf) + struct IterSVD(; alg=KrylovKit.GKL(), fallback_threshold = Inf) Iterative SVD solver based on KrylovKit's GKL algorithm, adapted to (symmmetric) tensors. The number of targeted singular values is set via the `TruncationSpace` in `ProjectorAlg`. @@ -190,7 +190,7 @@ function ChainRulesCore.rrule( end """ - struct NonTruncAdjoint(; lorentz_broadening = 0.0) + struct NonTruncAdjoint Old SVD adjoint that does not account for the truncated part of truncated SVDs. """ diff --git a/test/ctmrg/fixed_iterscheme.jl b/test/ctmrg/fixed_iterscheme.jl index 91dab322..fa381b38 100644 --- a/test/ctmrg/fixed_iterscheme.jl +++ b/test/ctmrg/fixed_iterscheme.jl @@ -48,31 +48,31 @@ end # TODO: Why doesn't fixed work with IterSVD? ## -ctm_alg = CTMRG(; - tol=1e-12, - miniter=4, - maxiter=100, - verbosity=1, - ctmrgscheme=:simultaneous, - svd_alg=SVDAdjoint(; fwd_alg=IterSVD()), -) +# ctm_alg = CTMRG(; +# tol=1e-12, +# miniter=4, +# maxiter=100, +# verbosity=1, +# ctmrgscheme=:simultaneous, +# svd_alg=SVDAdjoint(; fwd_alg=IterSVD()), +# ) -# initialize states -Random.seed!(91283219347) -psi = InfinitePEPS(2, χbond) -env_conv1 = leading_boundary(CTMRGEnv(psi, ComplexSpace(χenv)), psi, ctm_alg); +# # initialize states +# Random.seed!(91283219347) +# psi = InfinitePEPS(2, χbond) +# env_conv1 = leading_boundary(CTMRGEnv(psi, ComplexSpace(χenv)), psi, ctm_alg); # # do extra iteration to get SVD -env_conv2, info = ctmrg_iter(psi, env_conv1, ctm_alg); -env_fix, signs = gauge_fix(env_conv1, env_conv2); -@test check_elementwise_convergence(env_conv1, env_fix) +# env_conv2, info = ctmrg_iter(psi, env_conv1, ctm_alg); +# env_fix, signs = gauge_fix(env_conv1, env_conv2); +# @test check_elementwise_convergence(env_conv1, env_fix) -# fix gauge of SVD -U_fix, V_fix = fix_relative_phases(info.U, info.V, signs); -svd_alg_fix = SVDAdjoint(; fwd_alg=FixedSVD(U_fix, info.S, V_fix)); -ctm_alg_fix = CTMRG(; svd_alg=svd_alg_fix, trscheme=notrunc(), ctmrgscheme=:simultaneous); +# # fix gauge of SVD +# U_fix, V_fix = fix_relative_phases(info.U, info.V, signs); +# svd_alg_fix = SVDAdjoint(; fwd_alg=FixedSVD(U_fix, info.S, V_fix)); +# ctm_alg_fix = CTMRG(; svd_alg=svd_alg_fix, trscheme=notrunc(), ctmrgscheme=:simultaneous); -# do iteration with FixedSVD -env_fixedsvd, = ctmrg_iter(psi, env_conv1, ctm_alg_fix); -env_fixedsvd = fix_global_phases(env_conv1, env_fixedsvd); -@test check_elementwise_convergence(env_conv1, env_fixedsvd) \ No newline at end of file +# # do iteration with FixedSVD +# env_fixedsvd, = ctmrg_iter(psi, env_conv1, ctm_alg_fix); +# env_fixedsvd = fix_global_phases(env_conv1, env_fixedsvd); +# @test check_elementwise_convergence(env_conv1, env_fixedsvd) \ No newline at end of file diff --git a/test/heisenberg.jl b/test/heisenberg.jl index 3ce0b8d2..d72b5568 100644 --- a/test/heisenberg.jl +++ b/test/heisenberg.jl @@ -13,7 +13,7 @@ ctm_alg = CTMRG(; miniter=4, maxiter=100, verbosity=1, - svd_alg=SVDAdjoint(; fwd_alg=TensorKit.SVD(), rrule_alg=GMRES(tol=1e-10)), + svd_alg=SVDAdjoint(; fwd_alg=TensorKit.SVD(), rrule_alg=GMRES(; tol=1e-10)), ctmrgscheme=:simultaneous, ) opt_alg = PEPSOptimize(; From fe17ed7057d48fe08065f2b221ebe21a7fa837f2 Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Mon, 15 Jul 2024 15:49:36 +0200 Subject: [PATCH 048/213] Fix left over merge issues, update default verbosities --- README.md | 1 - docs/src/index.md | 1 - examples/heisenberg.jl | 3 +- src/algorithms/ctmrg.jl | 215 ++---------------------------- src/algorithms/ctmrg_gauge_fix.jl | 43 ++++-- src/algorithms/peps_opt.jl | 2 +- test/ctmrg/ctmrgschemes.jl | 4 +- test/ctmrg/fixed_iterscheme.jl | 12 +- test/ctmrg/gaugefix.jl | 8 +- test/heisenberg.jl | 4 +- test/pwave.jl | 2 +- test/tf_ising.jl | 2 +- 12 files changed, 60 insertions(+), 237 deletions(-) diff --git a/README.md b/README.md index d5a41ca5..9260813b 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,6 @@ opt_alg = PEPSOptimize(; optimizer=LBFGS(4; maxiter=100, gradtol=1e-4, verbosity=2), gradient_alg=GMRES(; tol=1e-6, maxiter=100), reuse_env=true, - verbosity=2, ) # ground state search diff --git a/docs/src/index.md b/docs/src/index.md index 5ceb20c7..787520b1 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -32,7 +32,6 @@ opt_alg = PEPSOptimize(; optimizer=LBFGS(4; maxiter=100, gradtol=1e-4, verbosity=2), gradient_alg=GMRES(; tol=1e-6, maxiter=100), reuse_env=true, - verbosity=2, ) # ground state search diff --git a/examples/heisenberg.jl b/examples/heisenberg.jl index 9dd8dbe3..fb966180 100644 --- a/examples/heisenberg.jl +++ b/examples/heisenberg.jl @@ -11,13 +11,12 @@ H = square_lattice_heisenberg(; Jx=-1, Jy=1, Jz=-1) # Parameters χbond = 2 χenv = 20 -ctm_alg = CTMRG(; tol=1e-10, miniter=4, maxiter=100, verbosity=1, trscheme=truncdim(χenv)) +ctm_alg = CTMRG(; tol=1e-10, miniter=4, maxiter=100, verbosity=2) opt_alg = PEPSOptimize(; boundary_alg=ctm_alg, optimizer=LBFGS(4; maxiter=100, gradtol=1e-4, verbosity=2), gradient_alg=GMRES(; tol=1e-6, maxiter=100), reuse_env=true, - verbosity=2, ) # Ground state search diff --git a/src/algorithms/ctmrg.jl b/src/algorithms/ctmrg.jl index deb46990..73846323 100644 --- a/src/algorithms/ctmrg.jl +++ b/src/algorithms/ctmrg.jl @@ -58,7 +58,7 @@ function CTMRG(; tol=Defaults.ctmrg_tol, maxiter=Defaults.ctmrg_maxiter, miniter=Defaults.ctmrg_miniter, - verbosity=1, + verbosity=2, svd_alg=SVDAdjoint(), trscheme=FixedSpaceTruncation(), ctmrgscheme=Defaults.ctmrgscheme, @@ -77,7 +77,7 @@ Per default, a random initial environment is used. function MPSKit.leading_boundary(state, alg::CTMRG) return MPSKit.leading_boundary(CTMRGEnv(state, oneunit(spacetype(state))), state, alg) end -function MPSKit.leading_boundary(envinit, state, alg::CTMRG) +function MPSKit.leading_boundary(envinit, state, alg::CTMRG{S}) where {S} CS = map(x -> tsvd(x; alg=TensorKit.SVD())[2], envinit.corners) TS = map(x -> tsvd(x; alg=TensorKit.SVD())[2], envinit.edges) @@ -99,9 +99,14 @@ function MPSKit.leading_boundary(envinit, state, alg::CTMRG) end # Do one final iteration that does not change the spaces - alg_fixed = @set alg.projector_alg.trscheme = FixedSpaceTruncation() + alg_fixed = CTMRG(; + verbosity=alg.verbosity, + svd_alg=alg.projector_alg.svd_alg, + trscheme=FixedSpaceTruncation(), + ctmrgscheme=S, + ) env′, = ctmrg_iter(state, env, alg_fixed) - envfix = gauge_fix(env, env′) + envfix, = gauge_fix(env, env′) η = calc_elementwise_convergence(envfix, env; atol=alg.tol^(1 / 2)) N = norm(state, envfix) @@ -125,208 +130,6 @@ ctmrg_logcancel!(log, iter, η, N) = @warnv 1 logcancel!(log, iter, η, N) @non_differentiable ctmrg_logfinish!(args...) @non_differentiable ctmrg_logcancel!(args...) -""" - gauge_fix(envprev::CTMRGEnv{C,T}, envfinal::CTMRGEnv{C,T}) where {C,T} - -Fix the gauge of `envfinal` based on the previous environment `envprev`. -This assumes that the `envfinal` is the result of one CTMRG iteration on `envprev`. -Given that the CTMRG run is converged, the returned environment will be -element-wise converged to `envprev`. -""" -function gauge_fix(envprev::CTMRGEnv{C,T}, envfinal::CTMRGEnv{C,T}) where {C,T} - # Check if spaces in envprev and envfinal are the same - same_spaces = map(Iterators.product(axes(envfinal.edges)...)) do (dir, r, c) - space(envfinal.edges[dir, r, c]) == space(envprev.edges[dir, r, c]) && - space(envfinal.corners[dir, r, c]) == space(envprev.corners[dir, r, c]) - end - @assert all(same_spaces) "Spaces of envprev and envfinal are not the same" - - # Try the "general" algorithm from https://arxiv.org/abs/2311.11894 - signs = map(Iterators.product(axes(envfinal.edges)...)) do (dir, r, c) - # Gather edge tensors and pretend they're InfiniteMPSs - if dir == NORTH - Tsprev = circshift(envprev.edges[dir, r, :], 1 - c) - Tsfinal = circshift(envfinal.edges[dir, r, :], 1 - c) - elseif dir == EAST - Tsprev = circshift(envprev.edges[dir, :, c], 1 - r) - Tsfinal = circshift(envfinal.edges[dir, :, c], 1 - r) - elseif dir == SOUTH - Tsprev = circshift(reverse(envprev.edges[dir, r, :]), c) - Tsfinal = circshift(reverse(envfinal.edges[dir, r, :]), c) - elseif dir == WEST - Tsprev = circshift(reverse(envprev.edges[dir, :, c]), r) - Tsfinal = circshift(reverse(envfinal.edges[dir, :, c]), r) - end - - # Random MPS of same bond dimension - M = map(Tsfinal) do t - TensorMap(randn, scalartype(t), codomain(t) ← domain(t)) - end - - # Find right fixed points of mixed transfer matrices - ρinit = TensorMap( - randn, - scalartype(T), - MPSKit._lastspace(Tsfinal[end])' ← MPSKit._lastspace(M[end])', - ) - ρprev = transfermatrix_fixedpoint(Tsprev, M, ρinit) - ρfinal = transfermatrix_fixedpoint(Tsfinal, M, ρinit) - - # Decompose and multiply - Qprev, = leftorth(ρprev) - Qfinal, = leftorth(ρfinal) - - return Qprev * Qfinal' - end - - cornersfix, edgesfix = fix_relative_phases(envfinal, signs) - - # Fix global phase - cornersgfix = map(envprev.corners, cornersfix) do Cprev, Cfix - return dot(Cfix, Cprev) * Cfix - end - edgesgfix = map(envprev.edges, edgesfix) do Tprev, Tfix - return dot(Tfix, Tprev) * Tfix - end - return CTMRGEnv(cornersgfix, edgesgfix) -end - -# this is a bit of a hack to get the fixed point of the mixed transfer matrix -# because MPSKit is not compatible with AD -function transfermatrix_fixedpoint(tops, bottoms, ρinit) - _, vecs, info = eigsolve(ρinit, 1, :LM, Arnoldi()) do ρ - return foldr(zip(tops, bottoms); init=ρ) do (top, bottom), ρ - return @tensor ρ′[-1; -2] := top[-1 4 3; 1] * conj(bottom[-2 4 3; 2]) * ρ[1; 2] - end - end - info.converged > 0 || @warn "eigsolve did not converge" - return first(vecs) -end - -# Explicit fixing of relative phases (doing this compactly in a loop is annoying) -function _contract_gauge_corner(corner, σ_in, σ_out) - @autoopt @tensor corner_fix[χ_in; χ_out] := - σ_in[χ_in; χ1] * corner[χ1; χ2] * conj(σ_out[χ_out; χ2]) -end -function _contract_gauge_edge(edge, σ_in, σ_out) - @autoopt @tensor edge_fix[χ_in D_above D_below; χ_out] := - σ_in[χ_in; χ1] * edge[χ1 D_above D_below; χ2] * conj(σ_out[χ_out; χ2]) -end -function fix_relative_phases(envfinal::CTMRGEnv, signs) - C1 = map(Iterators.product(axes(envfinal.corners)[2:3]...)) do (r, c) - _contract_gauge_corner( - envfinal.corners[NORTHWEST, r, c], - signs[WEST, r, c], - signs[NORTH, r, _next(c, end)], - ) - end - T1 = map(Iterators.product(axes(envfinal.edges)[2:3]...)) do (r, c) - _contract_gauge_edge( - envfinal.edges[NORTH, r, c], - signs[NORTH, r, c], - signs[NORTH, r, _next(c, end)], - ) - end - C2 = map(Iterators.product(axes(envfinal.corners)[2:3]...)) do (r, c) - _contract_gauge_corner( - envfinal.corners[NORTHEAST, r, c], - signs[NORTH, r, c], - signs[EAST, _next(r, end), c], - ) - end - T2 = map(Iterators.product(axes(envfinal.edges)[2:3]...)) do (r, c) - _contract_gauge_edge( - envfinal.edges[EAST, r, c], signs[EAST, r, c], signs[EAST, _next(r, end), c] - ) - end - C3 = map(Iterators.product(axes(envfinal.corners)[2:3]...)) do (r, c) - _contract_gauge_corner( - envfinal.corners[SOUTHEAST, r, c], - signs[EAST, r, c], - signs[SOUTH, r, _prev(c, end)], - ) - end - T3 = map(Iterators.product(axes(envfinal.edges)[2:3]...)) do (r, c) - _contract_gauge_edge( - envfinal.edges[SOUTH, r, c], - signs[SOUTH, r, c], - signs[SOUTH, r, _prev(c, end)], - ) - end - C4 = map(Iterators.product(axes(envfinal.corners)[2:3]...)) do (r, c) - _contract_gauge_corner( - envfinal.corners[SOUTHWEST, r, c], - signs[SOUTH, r, c], - signs[WEST, _prev(r, end), c], - ) - end - T4 = map(Iterators.product(axes(envfinal.edges)[2:3]...)) do (r, c) - _contract_gauge_edge( - envfinal.edges[WEST, r, c], signs[WEST, r, c], signs[WEST, _prev(r, end), c] - ) - end - - return stack([C1, C2, C3, C4]; dims=1), stack([T1, T2, T3, T4]; dims=1) -end - -function calc_convergence(envs, CSold, TSold) - CSnew = map(x -> tsvd(x; alg=TensorKit.SVD())[2], envs.corners) - ΔCS = maximum(zip(CSold, CSnew)) do (c_old, c_new) - # only compute the difference on the smallest part of the spaces - smallest = infimum(MPSKit._firstspace(c_old), MPSKit._firstspace(c_new)) - e_old = isometry(MPSKit._firstspace(c_old), smallest) - e_new = isometry(MPSKit._firstspace(c_new), smallest) - return norm(e_new' * c_new * e_new - e_old' * c_old * e_old) - end - - TSnew = map(x -> tsvd(x; alg=TensorKit.SVD())[2], envs.edges) - ΔTS = maximum(zip(TSold, TSnew)) do (t_old, t_new) - MPSKit._firstspace(t_old) == MPSKit._firstspace(t_new) || - return scalartype(t_old)(Inf) - return norm(t_new - t_old) - end - - @debug "maxᵢ|Cⁿ⁺¹ - Cⁿ|ᵢ = $ΔCS maxᵢ|Tⁿ⁺¹ - Tⁿ|ᵢ = $ΔTS" - - return max(ΔCS, ΔTS), CSnew, TSnew -end - -@non_differentiable calc_convergence(args...) - -""" - calc_elementwise_convergence(envfinal, envfix; atol=1e-6) - -Check if the element-wise difference of the corner and edge tensors of the final and fixed -CTMRG environments are below some tolerance. -""" -function calc_elementwise_convergence(envfinal::CTMRGEnv, envfix::CTMRGEnv; atol::Real=1e-6) - ΔC = envfinal.corners .- envfix.corners - ΔCmax = norm(ΔC, Inf) - ΔCmean = norm(ΔC) - @debug "maxᵢⱼ|Cⁿ⁺¹ - Cⁿ|ᵢⱼ = $ΔCmax mean |Cⁿ⁺¹ - Cⁿ|ᵢⱼ = $ΔCmean" - - ΔT = envfinal.edges .- envfix.edges - ΔTmax = norm(ΔT, Inf) - ΔTmean = norm(ΔT) - @debug "maxᵢⱼ|Tⁿ⁺¹ - Tⁿ|ᵢⱼ = $ΔTmax mean |Tⁿ⁺¹ - Tⁿ|ᵢⱼ = $ΔTmean" - - # Check differences for all tensors in unit cell to debug properly - for (dir, r, c) in Iterators.product(axes(envfinal.edges)...) - @debug( - "$((dir, r, c)): all |Cⁿ⁺¹ - Cⁿ|ᵢⱼ < ϵ: ", - all(x -> abs(x) < atol, convert(Array, ΔC[dir, r, c])), - ) - @debug( - "$((dir, r, c)): all |Tⁿ⁺¹ - Tⁿ|ᵢⱼ < ϵ: ", - all(x -> abs(x) < atol, convert(Array, ΔT[dir, r, c])), - ) - end - - return max(ΔCmax, ΔTmax) -end - -@non_differentiable calc_elementwise_convergence(args...) - """ ctmrg_iter(state, env::CTMRGEnv{C,T}, alg::CTMRG) where {C,T} diff --git a/src/algorithms/ctmrg_gauge_fix.jl b/src/algorithms/ctmrg_gauge_fix.jl index 9faa950e..1c548d64 100644 --- a/src/algorithms/ctmrg_gauge_fix.jl +++ b/src/algorithms/ctmrg_gauge_fix.jl @@ -6,7 +6,7 @@ This assumes that the `envfinal` is the result of one CTMRG iteration on `envpre Given that the CTMRG run is converged, the returned environment will be element-wise converged to `envprev`. """ -function gauge_fix(envprev::CTMRGEnv{C,T}, envfinal::CTMRGEnv{C′,T′}) where {C,C′,T,T′} +function gauge_fix(envprev::CTMRGEnv{C,T}, envfinal::CTMRGEnv{C,T}) where {C,T} # Check if spaces in envprev and envfinal are the same same_spaces = map(Iterators.product(axes(envfinal.edges)...)) do (dir, r, c) space(envfinal.edges[dir, r, c]) == space(envprev.edges[dir, r, c]) && @@ -14,7 +14,7 @@ function gauge_fix(envprev::CTMRGEnv{C,T}, envfinal::CTMRGEnv{C′,T′}) where end @assert all(same_spaces) "Spaces of envprev and envfinal are not the same" - # "general" algorithm from https://arxiv.org/abs/2311.11894 + # Try the "general" algorithm from https://arxiv.org/abs/2311.11894 signs = map(Iterators.product(axes(envfinal.edges)...)) do (dir, r, c) # Gather edge tensors and pretend they're InfiniteMPSs if dir == NORTH @@ -46,8 +46,8 @@ function gauge_fix(envprev::CTMRGEnv{C,T}, envfinal::CTMRGEnv{C′,T′}) where ρfinal = transfermatrix_fixedpoint(Tsfinal, M, ρinit) # Decompose and multiply - Qprev, = leftorth!(ρprev) - Qfinal, = leftorth!(ρfinal) + Qprev, = leftorth(ρprev) + Qfinal, = leftorth(ρfinal) return Qprev * Qfinal' end @@ -177,15 +177,38 @@ function fix_global_phases(envprev::CTMRGEnv, envfix::CTMRGEnv) return CTMRGEnv(cornersgfix, edgesgfix) end + +function calc_convergence(envs, CSold, TSold) + CSnew = map(x -> tsvd(x; alg=TensorKit.SVD())[2], envs.corners) + ΔCS = maximum(zip(CSold, CSnew)) do (c_old, c_new) + # only compute the difference on the smallest part of the spaces + smallest = infimum(MPSKit._firstspace(c_old), MPSKit._firstspace(c_new)) + e_old = isometry(MPSKit._firstspace(c_old), smallest) + e_new = isometry(MPSKit._firstspace(c_new), smallest) + return norm(e_new' * c_new * e_new - e_old' * c_old * e_old) + end + + TSnew = map(x -> tsvd(x; alg=TensorKit.SVD())[2], envs.edges) + ΔTS = maximum(zip(TSold, TSnew)) do (t_old, t_new) + MPSKit._firstspace(t_old) == MPSKit._firstspace(t_new) || + return scalartype(t_old)(Inf) + return norm(t_new - t_old) + end + + @debug "maxᵢ|Cⁿ⁺¹ - Cⁿ|ᵢ = $ΔCS maxᵢ|Tⁿ⁺¹ - Tⁿ|ᵢ = $ΔTS" + + return max(ΔCS, ΔTS), CSnew, TSnew +end + +@non_differentiable calc_convergence(args...) + """ - check_elementwise_convergence(envfinal, envfix; atol=1e-6) + calc_elementwise_convergence(envfinal, envfix; atol=1e-6) Check if the element-wise difference of the corner and edge tensors of the final and fixed CTMRG environments are below some tolerance. """ -function check_elementwise_convergence( - envfinal::CTMRGEnv, envfix::CTMRGEnv; atol::Real=1e-6 -) +function calc_elementwise_convergence(envfinal::CTMRGEnv, envfix::CTMRGEnv; atol::Real=1e-6) ΔC = envfinal.corners .- envfix.corners ΔCmax = norm(ΔC, Inf) ΔCmean = norm(ΔC) @@ -208,7 +231,7 @@ function check_elementwise_convergence( ) end - return isapprox(ΔCmax, 0; atol) && isapprox(ΔTmax, 0; atol) + return max(ΔCmax, ΔTmax) end -@non_differentiable check_elementwise_convergence(args...) \ No newline at end of file +@non_differentiable calc_elementwise_convergence(args...) diff --git a/src/algorithms/peps_opt.jl b/src/algorithms/peps_opt.jl index c517f6ff..b8310a5e 100644 --- a/src/algorithms/peps_opt.jl +++ b/src/algorithms/peps_opt.jl @@ -76,7 +76,7 @@ end """ PEPSOptimize{G}(; boundary_alg=CTMRG(), optimizer::OptimKit.OptimizationAlgorithm=Defaults.optimizer - reuse_env::Bool=true, gradient_alg::G=LinSolver(), verbosity::Int=0) + reuse_env::Bool=true, gradient_alg::G=LinSolver()) Algorithm struct that represent PEPS ground-state optimization using AD. Set the algorithm to contract the infinite PEPS in `boundary_alg`; diff --git a/test/ctmrg/ctmrgschemes.jl b/test/ctmrg/ctmrgschemes.jl index 8b35b3ac..39185905 100644 --- a/test/ctmrg/ctmrgschemes.jl +++ b/test/ctmrg/ctmrgschemes.jl @@ -7,8 +7,8 @@ using PEPSKit # initialize parameters χbond = 2 χenv = 16 -ctm_alg_sequential = CTMRG(; tol=1e-10, verbosity=1, ctmrgscheme=:sequential) -ctm_alg_simultaneous = CTMRG(; tol=1e-10, verbosity=1, ctmrgscheme=:simultaneous) +ctm_alg_sequential = CTMRG(; tol=1e-10, verbosity=2, ctmrgscheme=:sequential) +ctm_alg_simultaneous = CTMRG(; tol=1e-10, verbosity=2, ctmrgscheme=:simultaneous) unitcells = [(1, 1), (3, 4)] @testset "$(unitcell) unit cell" for unitcell in unitcells diff --git a/test/ctmrg/fixed_iterscheme.jl b/test/ctmrg/fixed_iterscheme.jl index fa381b38..57194674 100644 --- a/test/ctmrg/fixed_iterscheme.jl +++ b/test/ctmrg/fixed_iterscheme.jl @@ -8,7 +8,7 @@ using PEPSKit: gauge_fix, fix_relative_phases, fix_global_phases, - check_elementwise_convergence + calc_elementwise_convergence # initialize parameters χbond = 2 @@ -21,7 +21,7 @@ unitcells = [(1, 1), (3, 4)] Iterators.product( unitcells, svd_algs ) - ctm_alg = CTMRG(; tol=1e-12, verbosity=1, ctmrgscheme=:simultaneous, svd_alg) + ctm_alg = CTMRG(; tol=1e-12, verbosity=2, ctmrgscheme=:simultaneous, svd_alg) # initialize states Random.seed!(2394823842) @@ -31,7 +31,7 @@ unitcells = [(1, 1), (3, 4)] # do extra iteration to get SVD env_conv2, info = ctmrg_iter(psi, env_conv1, ctm_alg) env_fix, signs = gauge_fix(env_conv1, env_conv2) - @test check_elementwise_convergence(env_conv1, env_fix; atol=1e-6) + @test calc_elementwise_convergence(env_conv1, env_fix) ≈ 0 atol=1e-6 # fix gauge of SVD U_fix, V_fix = fix_relative_phases(info.U, info.V, signs) @@ -43,7 +43,7 @@ unitcells = [(1, 1), (3, 4)] # do iteration with FixedSVD env_fixedsvd, = ctmrg_iter(psi, env_conv1, ctm_alg_fix) env_fixedsvd = fix_global_phases(env_conv1, env_fixedsvd) - @test check_elementwise_convergence(env_conv1, env_fixedsvd; atol=1e-6) + @test calc_elementwise_convergence(env_conv1, env_fixedsvd) ≈ 0 atol=1e-6 end # TODO: Why doesn't fixed work with IterSVD? @@ -65,7 +65,7 @@ end # # do extra iteration to get SVD # env_conv2, info = ctmrg_iter(psi, env_conv1, ctm_alg); # env_fix, signs = gauge_fix(env_conv1, env_conv2); -# @test check_elementwise_convergence(env_conv1, env_fix) +# @test calc_elementwise_convergence(env_conv1, env_fix) ≈ 0 atol=1e-6 # # fix gauge of SVD # U_fix, V_fix = fix_relative_phases(info.U, info.V, signs); @@ -75,4 +75,4 @@ end # # do iteration with FixedSVD # env_fixedsvd, = ctmrg_iter(psi, env_conv1, ctm_alg_fix); # env_fixedsvd = fix_global_phases(env_conv1, env_fixedsvd); -# @test check_elementwise_convergence(env_conv1, env_fixedsvd) \ No newline at end of file +# @test calc_elementwise_convergence(env_conv1, env_fixedsvd) ≈ 0 atol=1e-6 \ No newline at end of file diff --git a/test/ctmrg/gaugefix.jl b/test/ctmrg/gaugefix.jl index 71644ffc..724b1261 100644 --- a/test/ctmrg/gaugefix.jl +++ b/test/ctmrg/gaugefix.jl @@ -24,13 +24,13 @@ schemes = [:simultaneous, :sequential] ctm = CTMRGEnv(psi, ctm_space) alg = CTMRG(; - tol=1e-10, maxiter=100, verbosity=1, trscheme=FixedSpaceTruncation(), ctmrgscheme + tol=1e-10, maxiter=100, verbosity=2, trscheme=FixedSpaceTruncation(), ctmrgscheme ) ctm = leading_boundary(ctm, psi, alg) ctm2, = ctmrg_iter(psi, ctm, alg) ctm_fixed, = gauge_fix(ctm, ctm2) - @test PEPSKit.check_elementwise_convergence(ctm, ctm_fixed; atol=1e-6) + @test calc_elementwise_convergence(ctm, ctm_fixed) ≈ 0 atol=1e-6 end @testset "Z2 symmetry ($T) - ($unitcell) - ($ctmrgscheme)" for (T, unitcell, ctmrgscheme) in @@ -46,11 +46,11 @@ end ctm = CTMRGEnv(psi, ctm_space) alg = CTMRG(; - tol=1e-10, maxiter=400, verbosity=1, trscheme=FixedSpaceTruncation(), ctmrgscheme + tol=1e-10, maxiter=400, verbosity=2, trscheme=FixedSpaceTruncation(), ctmrgscheme ) ctm = leading_boundary(ctm, psi, alg) ctm2, = ctmrg_iter(psi, ctm, alg) ctm_fixed, = gauge_fix(ctm, ctm2) - @test PEPSKit.check_elementwise_convergence(ctm, ctm_fixed; atol=1e-6) + @test calc_elementwise_convergence(ctm, ctm_fixed) ≈ 0 atol=1e-6 end diff --git a/test/heisenberg.jl b/test/heisenberg.jl index e97f7de1..b99181d4 100644 --- a/test/heisenberg.jl +++ b/test/heisenberg.jl @@ -12,7 +12,7 @@ ctm_alg = CTMRG(; tol=1e-10, miniter=4, maxiter=100, - verbosity=1, + verbosity=2, svd_alg=SVDAdjoint(; fwd_alg=TensorKit.SVD(), rrule_alg=GMRES(; tol=1e-10)), ctmrgscheme=:simultaneous, ) @@ -27,7 +27,7 @@ opt_alg = PEPSOptimize(; Random.seed!(91283219347) H = square_lattice_heisenberg() psi_init = InfinitePEPS(2, χbond) -env_init = leading_boundary(CTMRGEnv(psi_init, ComplexSpace(χenv)), psi_init, ctm_alg) +env_init = leading_boundary(CTMRGEnv(psi_init, ComplexSpace(χenv)), psi_init, ctm_alg); # find fixedpoint result = fixedpoint(psi_init, H, opt_alg, env_init) diff --git a/test/pwave.jl b/test/pwave.jl index 092aa1d2..fd681370 100644 --- a/test/pwave.jl +++ b/test/pwave.jl @@ -10,7 +10,7 @@ unitcell = (2, 2) H = square_lattice_pwave(; unitcell) χbond = 2 χenv = 16 -ctm_alg = CTMRG(; tol=1e-10, miniter=4, maxiter=100, verbosity=1) +ctm_alg = CTMRG(; tol=1e-10, miniter=4, maxiter=100, verbosity=2) opt_alg = PEPSOptimize(; boundary_alg=ctm_alg, optimizer=LBFGS(4; maxiter=10, gradtol=1e-3, verbosity=2), diff --git a/test/tf_ising.jl b/test/tf_ising.jl index 1a3a16dd..f28aad83 100644 --- a/test/tf_ising.jl +++ b/test/tf_ising.jl @@ -19,7 +19,7 @@ mᶻ = 0.98 # initialize parameters χbond = 2 χenv = 16 -ctm_alg = CTMRG(; trscheme=truncdim(χenv), tol=1e-10, miniter=4, maxiter=100, verbosity=1) +ctm_alg = CTMRG(; trscheme=truncdim(χenv), tol=1e-10, miniter=4, maxiter=100, verbosity=2) opt_alg = PEPSOptimize(; boundary_alg=ctm_alg, optimizer=LBFGS(4; maxiter=100, gradtol=1e-3, verbosity=2), From 9b00244431c73c4091cdc06906ea49dc8210ff3e Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Mon, 15 Jul 2024 16:53:11 +0200 Subject: [PATCH 049/213] Fix calc_elementwise_convergence in tests, add consistency test between TensorKit.SVD and IterSVD, add safeguard --- src/algorithms/ctmrg_gauge_fix.jl | 1 - src/algorithms/peps_opt.jl | 3 + test/ctmrg/fixed_iterscheme.jl | 123 ++++++++++++++++++++++-------- test/ctmrg/gaugefix.jl | 4 +- 4 files changed, 95 insertions(+), 36 deletions(-) diff --git a/src/algorithms/ctmrg_gauge_fix.jl b/src/algorithms/ctmrg_gauge_fix.jl index 1c548d64..980ec185 100644 --- a/src/algorithms/ctmrg_gauge_fix.jl +++ b/src/algorithms/ctmrg_gauge_fix.jl @@ -177,7 +177,6 @@ function fix_global_phases(envprev::CTMRGEnv, envfix::CTMRGEnv) return CTMRGEnv(cornersgfix, edgesgfix) end - function calc_convergence(envs, CSold, TSold) CSnew = map(x -> tsvd(x; alg=TensorKit.SVD())[2], envs.corners) ΔCS = maximum(zip(CSold, CSnew)) do (c_old, c_new) diff --git a/src/algorithms/peps_opt.jl b/src/algorithms/peps_opt.jl index b8310a5e..1dca18ef 100644 --- a/src/algorithms/peps_opt.jl +++ b/src/algorithms/peps_opt.jl @@ -101,6 +101,9 @@ struct PEPSOptimize{G} if gradient_alg isa GradMode if S == :sequential && G.parameters[1] == :fixed throw(ArgumentError(":sequential and :fixed are not compatible")) + elseif boundary_alg.projector_alg.svd_alg.fwd_alg isa IterSVD && + G.parameters[1] == :fixed + throw(ArgumentError("IterSVD and :fixed are currently not compatible")) end end return new{G}(boundary_alg, optimizer, reuse_env, gradient_alg) diff --git a/test/ctmrg/fixed_iterscheme.jl b/test/ctmrg/fixed_iterscheme.jl index 57194674..1cfc9f83 100644 --- a/test/ctmrg/fixed_iterscheme.jl +++ b/test/ctmrg/fixed_iterscheme.jl @@ -13,7 +13,7 @@ using PEPSKit: # initialize parameters χbond = 2 χenv = 16 -svd_algs = [SVDAdjoint(; fwd_alg=TensorKit.SVD()), SVDAdjoint(; fwd_alg=IterSVD())] +svd_algs = [SVDAdjoint(; fwd_alg=TensorKit.SVD())] #, SVDAdjoint(; fwd_alg=IterSVD())] unitcells = [(1, 1), (3, 4)] # test for element-wise convergence after application of fixed step @@ -31,7 +31,7 @@ unitcells = [(1, 1), (3, 4)] # do extra iteration to get SVD env_conv2, info = ctmrg_iter(psi, env_conv1, ctm_alg) env_fix, signs = gauge_fix(env_conv1, env_conv2) - @test calc_elementwise_convergence(env_conv1, env_fix) ≈ 0 atol=1e-6 + @test calc_elementwise_convergence(env_conv1, env_fix) ≈ 0 atol = 1e-6 # fix gauge of SVD U_fix, V_fix = fix_relative_phases(info.U, info.V, signs) @@ -43,36 +43,93 @@ unitcells = [(1, 1), (3, 4)] # do iteration with FixedSVD env_fixedsvd, = ctmrg_iter(psi, env_conv1, ctm_alg_fix) env_fixedsvd = fix_global_phases(env_conv1, env_fixedsvd) - @test calc_elementwise_convergence(env_conv1, env_fixedsvd) ≈ 0 atol=1e-6 + @test calc_elementwise_convergence(env_conv1, env_fixedsvd) ≈ 0 atol = 1e-6 end -# TODO: Why doesn't fixed work with IterSVD? -## -# ctm_alg = CTMRG(; -# tol=1e-12, -# miniter=4, -# maxiter=100, -# verbosity=1, -# ctmrgscheme=:simultaneous, -# svd_alg=SVDAdjoint(; fwd_alg=IterSVD()), -# ) - -# # initialize states -# Random.seed!(91283219347) -# psi = InfinitePEPS(2, χbond) -# env_conv1 = leading_boundary(CTMRGEnv(psi, ComplexSpace(χenv)), psi, ctm_alg); - -# # do extra iteration to get SVD -# env_conv2, info = ctmrg_iter(psi, env_conv1, ctm_alg); -# env_fix, signs = gauge_fix(env_conv1, env_conv2); -# @test calc_elementwise_convergence(env_conv1, env_fix) ≈ 0 atol=1e-6 - -# # fix gauge of SVD -# U_fix, V_fix = fix_relative_phases(info.U, info.V, signs); -# svd_alg_fix = SVDAdjoint(; fwd_alg=FixedSVD(U_fix, info.S, V_fix)); -# ctm_alg_fix = CTMRG(; svd_alg=svd_alg_fix, trscheme=notrunc(), ctmrgscheme=:simultaneous); - -# # do iteration with FixedSVD -# env_fixedsvd, = ctmrg_iter(psi, env_conv1, ctm_alg_fix); -# env_fixedsvd = fix_global_phases(env_conv1, env_fixedsvd); -# @test calc_elementwise_convergence(env_conv1, env_fixedsvd) ≈ 0 atol=1e-6 \ No newline at end of file +# TODO: Why doesn't FixedSVD work with previous U, S and V from IterSVD? +@testset "Element-wise consistency of TensorKit.SVD and IterSVD" begin + ctm_alg_iter = CTMRG(; + tol=1e-12, + verbosity=2, + ctmrgscheme=:simultaneous, + svd_alg=SVDAdjoint(; fwd_alg=IterSVD()), + ) + ctm_alg_full = CTMRG(; + tol=1e-12, + verbosity=2, + ctmrgscheme=:simultaneous, + svd_alg=SVDAdjoint(; fwd_alg=TensorKit.SVD()), + ) + + # initialize states + Random.seed!(91283219347) + psi = InfinitePEPS(2, χbond) + env_init = CTMRGEnv(psi, ComplexSpace(χenv)) + env_conv1 = leading_boundary(env_init, psi, ctm_alg_full) + + # do extra iteration to get SVD + env_conv2_iter, info_iter = ctmrg_iter(psi, env_conv1, ctm_alg_iter) + env_fix_iter, signs_iter = gauge_fix(env_conv1, env_conv2_iter) + @test calc_elementwise_convergence(env_conv1, env_fix_iter) ≈ 0 atol = 1e-6 + + env_conv2_full, info_full = ctmrg_iter(psi, env_conv1, ctm_alg_full) + env_fix_full, signs_full = gauge_fix(env_conv1, env_conv2_full) + @test calc_elementwise_convergence(env_conv1, env_fix_full) ≈ 0 atol = 1e-6 + + # fix gauge of SVD + U_fix_iter, V_fix_iter = fix_relative_phases(info_iter.U, info_iter.V, signs_iter) + svd_alg_fix_iter = SVDAdjoint(; fwd_alg=FixedSVD(U_fix_iter, info_iter.S, V_fix_iter)) + ctm_alg_fix_iter = CTMRG(; + svd_alg=svd_alg_fix_iter, trscheme=notrunc(), ctmrgscheme=:simultaneous + ) + + U_fix_full, V_fix_full = fix_relative_phases(info_full.U, info_full.V, signs_full) + svd_alg_fix_full = SVDAdjoint(; fwd_alg=FixedSVD(U_fix_full, info_full.S, V_fix_full)) + ctm_alg_fix_full = CTMRG(; + svd_alg=svd_alg_fix_full, trscheme=notrunc(), ctmrgscheme=:simultaneous + ) + + # do iteration with FixedSVD + env_fixedsvd_iter, = ctmrg_iter(psi, env_conv1, ctm_alg_fix_iter) + env_fixedsvd_iter = fix_global_phases(env_conv1, env_fixedsvd_iter) + # @test calc_elementwise_convergence(env_conv1, env_fixedsvd_iter) ≈ 0 atol = 1e-6 # This should work, but doesn't! + + env_fixedsvd_full, = ctmrg_iter(psi, env_conv1, ctm_alg_fix_full) + env_fixedsvd_full = fix_global_phases(env_conv1, env_fixedsvd_full) + @test calc_elementwise_convergence(env_conv1, env_fixedsvd_full) ≈ 0 atol = 1e-6 + + # check matching decompositions + atol = 1e-12 + decomposition_check = all( + zip(info_iter.U, info_iter.S, info_iter.V, info_full.U, info_full.S, info_full.V), + ) do (U_iter, S_iter, V_iter, U_full, S_full, V_full) + diff = U_iter * S_iter * V_iter - U_full * S_full * V_full + all(x -> isapprox(abs(x), 0; atol), diff.data) + end + @test decomposition_check + + # check matching singular values + svalues_check = all(zip(info_iter.S, info_full.S)) do (S_iter, S_full) + diff = S_iter - S_full + all(x -> isapprox(abs(x), 0; atol), diff.data) + end + @test svalues_check + + # check normalization of U's and V's + Us = [info_iter.U, U_fix_iter, info_full.U, U_fix_full] + Vs = [info_iter.V, V_fix_iter, info_full.V, V_fix_full] + for (U, V) in zip(Us, Vs) + U_check = all(U) do u + uu = u' * u + diff = uu - id(space(uu, 1)) + all(x -> isapprox(abs(x), 0; atol), diff.data) + end + @test U_check + V_check = all(V) do v + vv = v * v' + diff = vv - id(space(vv, 1)) + all(x -> isapprox(abs(x), 0; atol), diff.data) + end + @test V_check + end +end diff --git a/test/ctmrg/gaugefix.jl b/test/ctmrg/gaugefix.jl index 724b1261..122e42fc 100644 --- a/test/ctmrg/gaugefix.jl +++ b/test/ctmrg/gaugefix.jl @@ -30,7 +30,7 @@ schemes = [:simultaneous, :sequential] ctm = leading_boundary(ctm, psi, alg) ctm2, = ctmrg_iter(psi, ctm, alg) ctm_fixed, = gauge_fix(ctm, ctm2) - @test calc_elementwise_convergence(ctm, ctm_fixed) ≈ 0 atol=1e-6 + @test calc_elementwise_convergence(ctm, ctm_fixed) ≈ 0 atol = 1e-6 end @testset "Z2 symmetry ($T) - ($unitcell) - ($ctmrgscheme)" for (T, unitcell, ctmrgscheme) in @@ -52,5 +52,5 @@ end ctm = leading_boundary(ctm, psi, alg) ctm2, = ctmrg_iter(psi, ctm, alg) ctm_fixed, = gauge_fix(ctm, ctm2) - @test calc_elementwise_convergence(ctm, ctm_fixed) ≈ 0 atol=1e-6 + @test calc_elementwise_convergence(ctm, ctm_fixed) ≈ 0 atol = 1e-6 end From bcd27980a5c7f84d0144fae3b580777b4690709a Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Mon, 15 Jul 2024 17:29:05 +0200 Subject: [PATCH 050/213] Revert gaugefix.jl test back to old state --- test/ctmrg/gaugefix.jl | 51 +++++++++++++++++++++++++++++++++--------- 1 file changed, 41 insertions(+), 10 deletions(-) diff --git a/test/ctmrg/gaugefix.jl b/test/ctmrg/gaugefix.jl index 122e42fc..ff51d0a7 100644 --- a/test/ctmrg/gaugefix.jl +++ b/test/ctmrg/gaugefix.jl @@ -8,7 +8,31 @@ using PEPSKit: ctmrg_iter, gauge_fix, calc_elementwise_convergence scalartypes = [Float64, ComplexF64] unitcells = [(1, 1), (2, 2), (3, 2)] schemes = [:simultaneous, :sequential] -χ = Dict([(1, 1) => 8, (2, 2) => 26, (3, 2) => 26]) # Increase χ to converge non-symmetric environments +χ = 6 +atol = 1e-4 +verbosity = 2 + +function _make_symmetric(psi) + if ==(size(psi)...) + return PEPSKit.symmetrize(psi, PEPSKit.Full()) + else + return PEPSKit.symmetrize(PEPSKit.symmetrize(psi, PEPSKit.Depth()), PEPSKit.Width()) + end +end + +# If I can't make the rng seed behave, I'll just randomly define a peps somehow +function semi_random_peps!(psi::InfinitePEPS) + i = 0 + A′ = map(psi.A) do a + for (_, b) in blocks(a) + l = length(b) + b .= reshape(collect((1:l) .+ i), size(b)) + i += l + end + return a + end + return InfinitePEPS(A′) +end @testset "Trivial symmetry ($T) - ($unitcell) - ($ctmrgscheme)" for ( T, unitcell, ctmrgscheme @@ -17,20 +41,23 @@ schemes = [:simultaneous, :sequential] ) physical_space = ComplexSpace(2) peps_space = ComplexSpace(2) - ctm_space = ComplexSpace(χ[unitcell]) + ctm_space = ComplexSpace(χ) + + psi = InfinitePEPS(undef, T, physical_space, peps_space; unitcell) + semi_random_peps!(psi) + psi = _make_symmetric(psi) - Random.seed!(29358293852) # Seed RNG to make random environment consistent - psi = InfinitePEPS(randn, T, physical_space, peps_space; unitcell) + Random.seed!(987654321) # Seed RNG to make random environment consistent ctm = CTMRGEnv(psi, ctm_space) alg = CTMRG(; - tol=1e-10, maxiter=100, verbosity=2, trscheme=FixedSpaceTruncation(), ctmrgscheme + tol=1e-10, maxiter=200, verbosity, trscheme=FixedSpaceTruncation(), ctmrgscheme ) ctm = leading_boundary(ctm, psi, alg) ctm2, = ctmrg_iter(psi, ctm, alg) ctm_fixed, = gauge_fix(ctm, ctm2) - @test calc_elementwise_convergence(ctm, ctm_fixed) ≈ 0 atol = 1e-6 + @test calc_elementwise_convergence(ctm, ctm_fixed) ≈ 0 atol = atol end @testset "Z2 symmetry ($T) - ($unitcell) - ($ctmrgscheme)" for (T, unitcell, ctmrgscheme) in @@ -39,18 +66,22 @@ end ) physical_space = Z2Space(0 => 1, 1 => 1) peps_space = Z2Space(0 => 1, 1 => 1) - ctm_space = Z2Space(0 => χ[(1, 1)] ÷ 2, 1 => χ[(1, 1)] ÷ 2) + ctm_space = Z2Space(0 => χ ÷ 2, 1 => χ ÷ 2) + + psi = InfinitePEPS(undef, T, physical_space, peps_space; unitcell) + semi_random_peps!(psi) + psi = _make_symmetric(psi) - Random.seed!(2938293852938) # Seed RNG to make random environment consistent + Random.seed!(987654321) # Seed RNG to make random environment consistent psi = InfinitePEPS(physical_space, peps_space; unitcell) ctm = CTMRGEnv(psi, ctm_space) alg = CTMRG(; - tol=1e-10, maxiter=400, verbosity=2, trscheme=FixedSpaceTruncation(), ctmrgscheme + tol=1e-10, maxiter=200, verbosity, trscheme=FixedSpaceTruncation(), ctmrgscheme ) ctm = leading_boundary(ctm, psi, alg) ctm2, = ctmrg_iter(psi, ctm, alg) ctm_fixed, = gauge_fix(ctm, ctm2) - @test calc_elementwise_convergence(ctm, ctm_fixed) ≈ 0 atol = 1e-6 + @test calc_elementwise_convergence(ctm, ctm_fixed) ≈ 0 atol = atol end From 4713c11bd406eae6b2fe1fba8d40395b4d6a82b7 Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Mon, 15 Jul 2024 17:56:26 +0200 Subject: [PATCH 051/213] Fix new PEPSOptimize constructor in tests --- README.md | 2 +- docs/src/index.md | 2 +- examples/heisenberg.jl | 2 +- test/pwave.jl | 2 +- test/tf_ising.jl | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 9260813b..697c94b6 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ ctm_alg = CTMRG(; tol=1e-10, miniter=4, maxiter=100, verbosity=1, trscheme=trunc opt_alg = PEPSOptimize(; boundary_alg=ctm_alg, optimizer=LBFGS(4; maxiter=100, gradtol=1e-4, verbosity=2), - gradient_alg=GMRES(; tol=1e-6, maxiter=100), + gradient_alg=LinSolver(), reuse_env=true, ) diff --git a/docs/src/index.md b/docs/src/index.md index 787520b1..24efebff 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -30,7 +30,7 @@ ctm_alg = CTMRG(; tol=1e-10, miniter=4, maxiter=100, verbosity=1, trscheme=trunc opt_alg = PEPSOptimize(; boundary_alg=ctm_alg, optimizer=LBFGS(4; maxiter=100, gradtol=1e-4, verbosity=2), - gradient_alg=GMRES(; tol=1e-6, maxiter=100), + gradient_alg=LinSolver(), reuse_env=true, ) diff --git a/examples/heisenberg.jl b/examples/heisenberg.jl index fb966180..3134f7f1 100644 --- a/examples/heisenberg.jl +++ b/examples/heisenberg.jl @@ -15,7 +15,7 @@ ctm_alg = CTMRG(; tol=1e-10, miniter=4, maxiter=100, verbosity=2) opt_alg = PEPSOptimize(; boundary_alg=ctm_alg, optimizer=LBFGS(4; maxiter=100, gradtol=1e-4, verbosity=2), - gradient_alg=GMRES(; tol=1e-6, maxiter=100), + gradient_alg=LinSolver(; solver=GMRES(; tol=1e-6, maxiter=100)), reuse_env=true, ) diff --git a/test/pwave.jl b/test/pwave.jl index fd681370..caa47b25 100644 --- a/test/pwave.jl +++ b/test/pwave.jl @@ -14,7 +14,7 @@ ctm_alg = CTMRG(; tol=1e-10, miniter=4, maxiter=100, verbosity=2) opt_alg = PEPSOptimize(; boundary_alg=ctm_alg, optimizer=LBFGS(4; maxiter=10, gradtol=1e-3, verbosity=2), - gradient_alg=GMRES(; tol=1e-3, maxiter=2, krylovdim=50), + gradient_alg=LinSolver(; GMRES(; tol=1e-3, maxiter=2, krylovdim=50)), reuse_env=true, ) diff --git a/test/tf_ising.jl b/test/tf_ising.jl index f28aad83..42304c03 100644 --- a/test/tf_ising.jl +++ b/test/tf_ising.jl @@ -23,7 +23,7 @@ ctm_alg = CTMRG(; trscheme=truncdim(χenv), tol=1e-10, miniter=4, maxiter=100, v opt_alg = PEPSOptimize(; boundary_alg=ctm_alg, optimizer=LBFGS(4; maxiter=100, gradtol=1e-3, verbosity=2), - gradient_alg=GMRES(; tol=1e-6, maxiter=100), + gradient_alg=LinSolver(; solver=GMRES(; tol=1e-6, maxiter=100)) reuse_env=true, ) From df4fa10bc725288bffc13ce8a1b6a2a6b8211c15 Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Mon, 15 Jul 2024 18:14:08 +0200 Subject: [PATCH 052/213] Fix formatting, fix convention of Ising model --- src/operators/models.jl | 4 ++-- test/tf_ising.jl | 28 +++++++++++++++++----------- 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/src/operators/models.jl b/src/operators/models.jl index 0ea12807..5dad5456 100644 --- a/src/operators/models.jl +++ b/src/operators/models.jl @@ -13,9 +13,9 @@ function square_lattice_tf_ising( lattice = fill(physical_space, 1, 1) σx = TensorMap(T[0 1; 1 0], physical_space, physical_space) σz = TensorMap(T[1 0; 0 -1], physical_space, physical_space) - hzz = nearest_neighbour_hamiltonian(lattice, -J / 4 * σz ⊗ σz) + hzz = nearest_neighbour_hamiltonian(lattice, -J * σz ⊗ σz) return repeat( - LocalOperator(lattice, hzz.terms..., (CartesianIndex(1, 1),) => -J * h / 2 * σx), + LocalOperator(lattice, hzz.terms..., (CartesianIndex(1, 1),) => -J * h * σx), unitcell..., ) end diff --git a/test/tf_ising.jl b/test/tf_ising.jl index 42304c03..3b85a8a5 100644 --- a/test/tf_ising.jl +++ b/test/tf_ising.jl @@ -11,19 +11,25 @@ using OptimKit # J. Jordan, R. Orús, G. Vidal, F. Verstraete, and J. I. Cirac # Phys. Rev. Lett. 101, 250602 – Published 18 December 2008 # (values estimated from plots) -# (factor of 2 in the energy and magnetisation due to convention differences) -h = 0.5 -e = -0.525 -mᶻ = 0.98 +# (factor of 2 in the energy due to convention differences) +h = 3.1 +e = -1.6417 * 2 +mˣ = 0.91 # initialize parameters χbond = 2 χenv = 16 -ctm_alg = CTMRG(; trscheme=truncdim(χenv), tol=1e-10, miniter=4, maxiter=100, verbosity=2) +ctm_alg = CTMRG(; + tol=1e-10, + miniter=4, + maxiter=100, + verbosity=2, + svd_alg=SVDAdjoint(; fwd_alg=TensorKit.SVD(), rrule_alg=GMRES(; tol=1e-10)), +) opt_alg = PEPSOptimize(; boundary_alg=ctm_alg, - optimizer=LBFGS(4; maxiter=100, gradtol=1e-3, verbosity=2), - gradient_alg=LinSolver(; solver=GMRES(; tol=1e-6, maxiter=100)) + optimizer=LBFGS(4; maxiter=100, gradtol=1e-4, verbosity=2), + gradient_alg=LinSolver(; solver=GMRES(; tol=1e-6, maxiter=100)), reuse_env=true, ) @@ -36,10 +42,10 @@ env_init = leading_boundary(CTMRGEnv(psi_init, ComplexSpace(χenv)), psi_init, c result = fixedpoint(psi_init, H, opt_alg, env_init) # compute magnetization -σz = TensorMap(scalartype(psi_init)[1 0; 0 -1], ℂ^2, ℂ^2) -M = LocalOperator(H.lattice, (CartesianIndex(1, 1),) => σz) +σx = TensorMap(scalartype(psi_init)[0 1; 1 0], ℂ^2, ℂ^2) +M = LocalOperator(H.lattice, (CartesianIndex(1, 1),) => σx) magn = expectation_value(result.peps, M, result.env) -@test result.E ≈ e atol = 5e-2 +@test result.E ≈ e atol = 1e-3 @test imag(magn) ≈ 0 atol = 1e-6 -@test abs(magn) ≈ mᶻ atol = 5e-2 +@test abs(magn) ≈ mˣ atol = 5e-2 From 9a6b40960619edae88f754515fc9d89413b44f44 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 16 Jul 2024 09:42:43 +0200 Subject: [PATCH 053/213] CompatHelper: add new compat entry for LoggingExtras at version 1, (keep existing compat) (#55) Co-authored-by: CompatHelper Julia --- Project.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Project.toml b/Project.toml index b5be59ea..70b0807e 100644 --- a/Project.toml +++ b/Project.toml @@ -26,6 +26,7 @@ ChainRulesCore = "1.0" Compat = "3.46, 4.2" KrylovKit = "0.8" LinearAlgebra = "1" +LoggingExtras = "1" MPSKit = "0.11" OptimKit = "0.3" Printf = "1" From ec1ca7d5c78da914e23aae0ae8afe01ef338bb71 Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Tue, 16 Jul 2024 10:56:29 +0200 Subject: [PATCH 054/213] Implement review comments --- src/algorithms/ctmrg.jl | 6 +++--- src/algorithms/ctmrg_all_sides.jl | 2 +- src/algorithms/ctmrg_gauge_fix.jl | 4 ++-- src/algorithms/peps_opt.jl | 8 +++++--- 4 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/algorithms/ctmrg.jl b/src/algorithms/ctmrg.jl index 73846323..860257fb 100644 --- a/src/algorithms/ctmrg.jl +++ b/src/algorithms/ctmrg.jl @@ -145,10 +145,10 @@ function ctmrg_iter(state, env::CTMRGEnv{C,T}, alg::CTMRG) where {C,T} env, info = left_move(state, env, alg.projector_alg) state = rotate_north(state, EAST) env = rotate_north(env, EAST) - ϵ = max(ϵ, info.ϵ) + ϵ = max(ϵ, info.err) end - return env, (; ϵ) + return env, (; err=ϵ) end """ @@ -218,7 +218,7 @@ function left_move(state, env::CTMRGEnv{C,T}, alg::ProjectorAlg) where {C,T} end end - return CTMRGEnv(corners, edges), (; P_left=copy(P_top), P_right=copy(P_bottom), ϵ) + return CTMRGEnv(corners, edges), (; err=ϵ, P_left=copy(P_top), P_right=copy(P_bottom)) end # Enlarged corner contractions (need direction specific methods to avoid PEPS rotations) diff --git a/src/algorithms/ctmrg_all_sides.jl b/src/algorithms/ctmrg_all_sides.jl index 14b6b24c..1b513c00 100644 --- a/src/algorithms/ctmrg_all_sides.jl +++ b/src/algorithms/ctmrg_all_sides.jl @@ -91,7 +91,7 @@ function build_projectors(Q, env::CTMRGEnv{C,E}, alg::ProjectorAlg{A,T}) where { P_right[dir, r, c] = Pr end - return copy(P_left), copy(P_right), (; ϵ, U=copy(U), S=copy(S), V=copy(V)) + return copy(P_left), copy(P_right), (; err=ϵ, U=copy(U), S=copy(S), V=copy(V)) end # Apply projectors to renormalize corners and edges diff --git a/src/algorithms/ctmrg_gauge_fix.jl b/src/algorithms/ctmrg_gauge_fix.jl index 980ec185..5879e7f9 100644 --- a/src/algorithms/ctmrg_gauge_fix.jl +++ b/src/algorithms/ctmrg_gauge_fix.jl @@ -46,8 +46,8 @@ function gauge_fix(envprev::CTMRGEnv{C,T}, envfinal::CTMRGEnv{C,T}) where {C,T} ρfinal = transfermatrix_fixedpoint(Tsfinal, M, ρinit) # Decompose and multiply - Qprev, = leftorth(ρprev) - Qfinal, = leftorth(ρfinal) + Qprev, = leftorth!(ρprev) + Qfinal, = leftorth!(ρfinal) return Qprev * Qfinal' end diff --git a/src/algorithms/peps_opt.jl b/src/algorithms/peps_opt.jl index 1dca18ef..5d8319a2 100644 --- a/src/algorithms/peps_opt.jl +++ b/src/algorithms/peps_opt.jl @@ -1,5 +1,7 @@ abstract type GradMode{F} end +iterscheme(::GradMode{F}) where {F} = F + """ struct GeomSum(; maxiter=Defaults.fpgrad_maxiter, tol=Defaults.fpgrad_tol, verbosity=0, iterscheme=Defaults.iterscheme) <: GradMode{iterscheme} @@ -99,10 +101,10 @@ struct PEPSOptimize{G} gradient_alg::G, ) where {S,G} if gradient_alg isa GradMode - if S == :sequential && G.parameters[1] == :fixed + if S == :sequential && iterscheme(gradient_alg) == :fixed throw(ArgumentError(":sequential and :fixed are not compatible")) elseif boundary_alg.projector_alg.svd_alg.fwd_alg isa IterSVD && - G.parameters[1] == :fixed + iterscheme(gradient_alg) == :fixed throw(ArgumentError("IterSVD and :fixed are currently not compatible")) end end @@ -208,7 +210,7 @@ function _rrule( state, alg::CTMRG{C}, ) where {C} - @assert C == :simultaneous + @assert C === :simultaneous @assert alg.projector_alg.svd_alg.rrule_alg isa Union{KrylovKit.LinearSolver,Arnoldi} envs = leading_boundary(envinit, state, alg) envsconv, info = ctmrg_iter(state, envs, alg) From 90cab74baff3a53179d9ce1cbd5c040f354e2695 Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Tue, 16 Jul 2024 12:38:09 +0200 Subject: [PATCH 055/213] Stabilize tests --- test/ctmrg/gradients.jl | 29 ++++++++++++++--------------- test/ctmrg/gradparts.jl | 29 ++++++++++++----------------- test/heisenberg.jl | 4 ++-- test/pwave.jl | 5 +++-- test/runtests.jl | 3 +++ test/tf_ising.jl | 7 ++++--- 6 files changed, 38 insertions(+), 39 deletions(-) diff --git a/test/ctmrg/gradients.jl b/test/ctmrg/gradients.jl index 45bb2beb..36d10e9e 100644 --- a/test/ctmrg/gradients.jl +++ b/test/ctmrg/gradients.jl @@ -10,29 +10,27 @@ using KrylovKit # ------------------------------------------- χbond = 2 χenv = 4 -Pspaces = [ComplexSpace(2), Vect[FermionParity](0 => 1, 1 => 1)] -Vspaces = [ComplexSpace(χbond), Vect[FermionParity](0 => χbond / 2, 1 => χbond / 2)] -Espaces = [ComplexSpace(χenv), Vect[FermionParity](0 => χenv / 2, 1 => χenv / 2)] -models = [square_lattice_heisenberg(), square_lattice_pwave()] -names = ["Heisenberg", "p-wave superconductor"] +Pspaces = [ComplexSpace(2)] #, Vect[FermionParity](0 => 1, 1 => 1)] +Vspaces = [ComplexSpace(χbond)] #, Vect[FermionParity](0 => χbond / 2, 1 => χbond / 2)] +Espaces = [ComplexSpace(χenv)] #, Vect[FermionParity](0 => χenv / 2, 1 => χenv / 2)] +models = [square_lattice_heisenberg()] #, square_lattice_pwave()] +names = ["Heisenberg"] #, "p-wave superconductor"] Random.seed!(42039482030) -tol = 1e-8 +gradtol = 1e-4 boundary_alg = CTMRG(; - tol=tol, - miniter=4, - maxiter=100, + tol=1e-10, verbosity=0, ctmrgscheme=:simultaneous, svd_alg=SVDAdjoint(; fwd_alg=TensorKit.SVD(), rrule_alg=GMRES(; tol=1e-10)), ) gradmodes = [ nothing, - GeomSum(; tol, iterscheme=:fixed), - GeomSum(; tol, iterscheme=:diffgauge), - ManualIter(; tol, iterscheme=:fixed), - ManualIter(; tol, iterscheme=:diffgauge), - LinSolver(; solver=KrylovKit.GMRES(; tol=tol, maxiter=10), iterscheme=:fixed), - LinSolver(; solver=KrylovKit.GMRES(; tol=tol, maxiter=10), iterscheme=:diffgauge), + GeomSum(; tol=gradtol, iterscheme=:fixed), + GeomSum(; tol=gradtol, iterscheme=:diffgauge), + ManualIter(; tol=gradtol, iterscheme=:fixed), + ManualIter(; tol=gradtol, iterscheme=:diffgauge), + LinSolver(; solver=KrylovKit.GMRES(; tol=gradtol, maxiter=10), iterscheme=:fixed), + LinSolver(; solver=KrylovKit.GMRES(; tol=gradtol, maxiter=10), iterscheme=:diffgauge), ] steps = -0.01:0.005:0.01 @@ -47,6 +45,7 @@ steps = -0.01:0.005:0.01 Espace = Espaces[i] psi_init = InfinitePEPS(Pspace, Vspace, Vspace) @testset "$alg_rrule" for alg_rrule in gradmodes + @info "optimtest of $alg_rrule on $(names[i])" dir = InfinitePEPS(Pspace, Vspace, Vspace) psi = InfinitePEPS(Pspace, Vspace, Vspace) env = leading_boundary(CTMRGEnv(psi, Espace), psi, boundary_alg) diff --git a/test/ctmrg/gradparts.jl b/test/ctmrg/gradparts.jl index 406aa8bc..98337d59 100644 --- a/test/ctmrg/gradparts.jl +++ b/test/ctmrg/gradparts.jl @@ -28,9 +28,12 @@ include(joinpath("..", "utility.jl")) Pspaces = [ComplexSpace(2), Vect[FermionParity](0 => 1, 1 => 1)] Vspaces = [ComplexSpace(χbond), Vect[FermionParity](0 => χbond / 2, 1 => χbond / 2)] Espaces = [ComplexSpace(χenv), Vect[FermionParity](0 => χenv / 2, 1 => χenv / 2)] -functions = [left_move, ctmrg_iter, leading_boundary] -tol = 1e-8 -boundary_alg = CTMRG(; tol=tol, miniter=4, maxiter=100, verbosity=0) +tol = 1e-10 +atol = 1e-6 +boundary_algs = [ + CTMRG(; tol, verbosity=0, ctmrgscheme=:simultaneous), + CTMRG(; tol, verbosity=0, ctmrgscheme=:sequential), +] ## Gauge invariant function of the environment # -------------------------------------------- @@ -49,27 +52,19 @@ end ## Tests # ------ -title = "Reverse rules for composite parts of the CTMRG fixed point with spacetype" -@testset title * "$(Vspaces[i])" for i in eachindex(Pspaces) +@testset "Reverse rules for ctmrg_iter with spacetype $(Vspaces[i])" for i in + eachindex(Pspaces) psi = InfinitePEPS(Pspaces[i], Vspaces[i], Vspaces[i]) env = CTMRGEnv(psi, Espaces[i]) - @testset "$f" for f in functions - atol = f == leading_boundary ? sqrt(tol) : tol - g = if f == leading_boundary - function (state, env) - return rho(f(env, state, boundary_alg)) - end - else - function (state, env) - return rho(f(state, env, boundary_alg)[1]) - end - end + @testset "$alg" for alg in boundary_algs + @info "$(typeof(alg)) on $(Vspaces[i])" + f(state, env) = rho(ctmrg_iter(state, env, boundary_alg)[1]) # use rrule testing functionality but compute rrule via Zygote test_rrule( Zygote.ZygoteRuleConfig(), - g, + f, psi, env; check_inferred=false, diff --git a/test/heisenberg.jl b/test/heisenberg.jl index b99181d4..44a85b43 100644 --- a/test/heisenberg.jl +++ b/test/heisenberg.jl @@ -19,7 +19,7 @@ ctm_alg = CTMRG(; opt_alg = PEPSOptimize(; boundary_alg=ctm_alg, optimizer=LBFGS(4; maxiter=100, gradtol=1e-3, verbosity=2), - gradient_alg=LinSolver(; solver=GMRES(; tol=1e-6, maxiter=100), iterscheme=:fixed), + gradient_alg=LinSolver(; solver=GMRES(; tol=1e-6), iterscheme=:fixed), reuse_env=true, ) @@ -27,7 +27,7 @@ opt_alg = PEPSOptimize(; Random.seed!(91283219347) H = square_lattice_heisenberg() psi_init = InfinitePEPS(2, χbond) -env_init = leading_boundary(CTMRGEnv(psi_init, ComplexSpace(χenv)), psi_init, ctm_alg); +env_init = leading_boundary(CTMRGEnv(psi_init, ComplexSpace(χenv)), psi_init, ctm_alg) # find fixedpoint result = fixedpoint(psi_init, H, opt_alg, env_init) diff --git a/test/pwave.jl b/test/pwave.jl index caa47b25..39b45c51 100644 --- a/test/pwave.jl +++ b/test/pwave.jl @@ -10,11 +10,11 @@ unitcell = (2, 2) H = square_lattice_pwave(; unitcell) χbond = 2 χenv = 16 -ctm_alg = CTMRG(; tol=1e-10, miniter=4, maxiter=100, verbosity=2) +ctm_alg = CTMRG(; tol=1e-8, maxiter=150, verbosity=2, ctmrgscheme=:sequential) opt_alg = PEPSOptimize(; boundary_alg=ctm_alg, optimizer=LBFGS(4; maxiter=10, gradtol=1e-3, verbosity=2), - gradient_alg=LinSolver(; GMRES(; tol=1e-3, maxiter=2, krylovdim=50)), + gradient_alg=LinSolver(; solver=GMRES(; tol=1e-3, maxiter=2), iterscheme=:diffgauge), reuse_env=true, ) @@ -22,6 +22,7 @@ opt_alg = PEPSOptimize(; Pspace = Vect[FermionParity](0 => 1, 1 => 1) Vspace = Vect[FermionParity](0 => χbond ÷ 2, 1 => χbond ÷ 2) Envspace = Vect[FermionParity](0 => χenv ÷ 2, 1 => χenv ÷ 2) +Random.seed!(91283219347) psi_init = InfinitePEPS(Pspace, Vspace, Vspace; unitcell) env_init = leading_boundary(CTMRGEnv(psi_init, Envspace), psi_init, ctm_alg); diff --git a/test/runtests.jl b/test/runtests.jl index 5655a517..60d9d708 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -14,6 +14,9 @@ end @time @safetestset "Gauge Fixing" begin include("ctmrg/gaugefix.jl") end + @time @safetestset "Gradient parts" begin + include("ctmrg/gradparts.jl") + end @time @safetestset "Gradients" begin include("ctmrg/gradients.jl") end diff --git a/test/tf_ising.jl b/test/tf_ising.jl index 3b85a8a5..3b3284c3 100644 --- a/test/tf_ising.jl +++ b/test/tf_ising.jl @@ -28,13 +28,14 @@ ctm_alg = CTMRG(; ) opt_alg = PEPSOptimize(; boundary_alg=ctm_alg, - optimizer=LBFGS(4; maxiter=100, gradtol=1e-4, verbosity=2), - gradient_alg=LinSolver(; solver=GMRES(; tol=1e-6, maxiter=100)), + optimizer=LBFGS(4; maxiter=100, gradtol=1e-3, verbosity=2), + gradient_alg=LinSolver(; solver=GMRES(; tol=1e-6)), reuse_env=true, ) # initialize states H = square_lattice_tf_ising(; h) +Random.seed!(91283219347) psi_init = InfinitePEPS(2, χbond) env_init = leading_boundary(CTMRGEnv(psi_init, ComplexSpace(χenv)), psi_init, ctm_alg) @@ -46,6 +47,6 @@ result = fixedpoint(psi_init, H, opt_alg, env_init) M = LocalOperator(H.lattice, (CartesianIndex(1, 1),) => σx) magn = expectation_value(result.peps, M, result.env) -@test result.E ≈ e atol = 1e-3 +@test result.E ≈ e atol = 1e-2 @test imag(magn) ≈ 0 atol = 1e-6 @test abs(magn) ≈ mˣ atol = 5e-2 From 0fcad186d28039c85cb064658838380b1818d7e7 Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Tue, 16 Jul 2024 14:46:51 +0200 Subject: [PATCH 056/213] Reintroduce p-wave to gradients test (only with :sequential) --- test/ctmrg/gradients.jl | 56 +++++++++++++++++++++++++---------------- test/ctmrg/gradparts.jl | 3 ++- 2 files changed, 37 insertions(+), 22 deletions(-) diff --git a/test/ctmrg/gradients.jl b/test/ctmrg/gradients.jl index 36d10e9e..5e0bdb11 100644 --- a/test/ctmrg/gradients.jl +++ b/test/ctmrg/gradients.jl @@ -10,27 +10,38 @@ using KrylovKit # ------------------------------------------- χbond = 2 χenv = 4 -Pspaces = [ComplexSpace(2)] #, Vect[FermionParity](0 => 1, 1 => 1)] -Vspaces = [ComplexSpace(χbond)] #, Vect[FermionParity](0 => χbond / 2, 1 => χbond / 2)] -Espaces = [ComplexSpace(χenv)] #, Vect[FermionParity](0 => χenv / 2, 1 => χenv / 2)] -models = [square_lattice_heisenberg()] #, square_lattice_pwave()] -names = ["Heisenberg"] #, "p-wave superconductor"] -Random.seed!(42039482030) +Pspaces = [ComplexSpace(2), Vect[FermionParity](0 => 1, 1 => 1)] +Vspaces = [ComplexSpace(χbond), Vect[FermionParity](0 => χbond / 2, 1 => χbond / 2)] +Espaces = [ComplexSpace(χenv), Vect[FermionParity](0 => χenv / 2, 1 => χenv / 2)] +models = [square_lattice_heisenberg(), square_lattice_pwave()] +names = ["Heisenberg", "p-wave superconductor"] + gradtol = 1e-4 -boundary_alg = CTMRG(; - tol=1e-10, - verbosity=0, - ctmrgscheme=:simultaneous, - svd_alg=SVDAdjoint(; fwd_alg=TensorKit.SVD(), rrule_alg=GMRES(; tol=1e-10)), -) +boundary_algs = [ + CTMRG(; + tol=1e-10, + verbosity=0, + ctmrgscheme=:simultaneous, + svd_alg=SVDAdjoint(; fwd_alg=TensorKit.SVD(), rrule_alg=GMRES(; tol=1e-10)), + ), + CTMRG(; tol=1e-10, verbosity=0, ctmrgscheme=:sequential), +] gradmodes = [ - nothing, - GeomSum(; tol=gradtol, iterscheme=:fixed), - GeomSum(; tol=gradtol, iterscheme=:diffgauge), - ManualIter(; tol=gradtol, iterscheme=:fixed), - ManualIter(; tol=gradtol, iterscheme=:diffgauge), - LinSolver(; solver=KrylovKit.GMRES(; tol=gradtol, maxiter=10), iterscheme=:fixed), - LinSolver(; solver=KrylovKit.GMRES(; tol=gradtol, maxiter=10), iterscheme=:diffgauge), + [ + nothing, + GeomSum(; tol=gradtol, iterscheme=:fixed), + GeomSum(; tol=gradtol, iterscheme=:diffgauge), + ManualIter(; tol=gradtol, iterscheme=:fixed), + ManualIter(; tol=gradtol, iterscheme=:diffgauge), + LinSolver(; solver=KrylovKit.GMRES(; tol=gradtol), iterscheme=:fixed), + LinSolver(; solver=KrylovKit.GMRES(; tol=gradtol), iterscheme=:diffgauge), + ], + [ + nothing, + GeomSum(; tol=gradtol, iterscheme=:diffgauge), + ManualIter(; tol=gradtol, iterscheme=:diffgauge), + LinSolver(; solver=KrylovKit.GMRES(; tol=gradtol), iterscheme=:diffgauge), + ], ] steps = -0.01:0.005:0.01 @@ -43,9 +54,12 @@ steps = -0.01:0.005:0.01 Pspace = Pspaces[i] Vspace = Pspaces[i] Espace = Espaces[i] + gms = gradmodes[i] + boundary_alg = boundary_algs[i] psi_init = InfinitePEPS(Pspace, Vspace, Vspace) - @testset "$alg_rrule" for alg_rrule in gradmodes + @testset "$alg_rrule" for alg_rrule in gms @info "optimtest of $alg_rrule on $(names[i])" + Random.seed!(42039482030) dir = InfinitePEPS(Pspace, Vspace, Vspace) psi = InfinitePEPS(Pspace, Vspace, Vspace) env = leading_boundary(CTMRGEnv(psi, Espace), psi, boundary_alg) @@ -58,7 +72,7 @@ steps = -0.01:0.005:0.01 ) do (peps, envs) E, g = Zygote.withgradient(peps) do psi envs2 = PEPSKit.hook_pullback( - leading_boundary, envs, psi, boundary_alg, ; alg_rrule + leading_boundary, envs, psi, boundary_alg; alg_rrule ) return costfun(psi, envs2, models[i]) end diff --git a/test/ctmrg/gradparts.jl b/test/ctmrg/gradparts.jl index 98337d59..846ea90a 100644 --- a/test/ctmrg/gradparts.jl +++ b/test/ctmrg/gradparts.jl @@ -54,12 +54,13 @@ end # ------ @testset "Reverse rules for ctmrg_iter with spacetype $(Vspaces[i])" for i in eachindex(Pspaces) + Random.seed!(42039482030) psi = InfinitePEPS(Pspaces[i], Vspaces[i], Vspaces[i]) env = CTMRGEnv(psi, Espaces[i]) @testset "$alg" for alg in boundary_algs @info "$(typeof(alg)) on $(Vspaces[i])" - f(state, env) = rho(ctmrg_iter(state, env, boundary_alg)[1]) + f(state, env) = rho(ctmrg_iter(state, env, alg)[1]) # use rrule testing functionality but compute rrule via Zygote test_rrule( From 677bbeb50cf5e9aa09ecd13912d6ce388069b5e2 Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Tue, 16 Jul 2024 15:19:05 +0200 Subject: [PATCH 057/213] Disable gradparts.jl test --- test/runtests.jl | 3 --- 1 file changed, 3 deletions(-) diff --git a/test/runtests.jl b/test/runtests.jl index 60d9d708..5655a517 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -14,9 +14,6 @@ end @time @safetestset "Gauge Fixing" begin include("ctmrg/gaugefix.jl") end - @time @safetestset "Gradient parts" begin - include("ctmrg/gradparts.jl") - end @time @safetestset "Gradients" begin include("ctmrg/gradients.jl") end From 4208b5ef3a73ae729a14003e69f94aea107750dc Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Wed, 17 Jul 2024 16:53:27 +0200 Subject: [PATCH 058/213] Add `MPSKit.correlation_length` method for PEPS (#57) * Add `MPSKit.transfer_spectrum` and `MPSKit.correlation_length` method for PEPS types * Add small comments regarding inconsistent row/column indexing * Add small `correlation_length` checks to Heisenberg and TF Ising tests --- src/PEPSKit.jl | 2 +- src/algorithms/ctmrg.jl | 39 ++++++++++++++++++++ src/mpskit_glue/transferpepo_environments.jl | 4 +- src/mpskit_glue/transferpeps_environments.jl | 32 +++++++++++++++- test/heisenberg.jl | 4 ++ test/tf_ising.jl | 8 ++++ 6 files changed, 84 insertions(+), 5 deletions(-) diff --git a/src/PEPSKit.jl b/src/PEPSKit.jl index 1a3c941b..5c3695ba 100644 --- a/src/PEPSKit.jl +++ b/src/PEPSKit.jl @@ -71,7 +71,7 @@ module Defaults end export SVDAdjoint, IterSVD, NonTruncSVDAdjoint -export FixedSpaceTruncation, ProjectorAlg, CTMRG, CTMRGEnv +export FixedSpaceTruncation, ProjectorAlg, CTMRG, CTMRGEnv, correlation_length export LocalOperator export expectation_value, costfun export leading_boundary diff --git a/src/algorithms/ctmrg.jl b/src/algorithms/ctmrg.jl index 860257fb..585e9b5f 100644 --- a/src/algorithms/ctmrg.jl +++ b/src/algorithms/ctmrg.jl @@ -328,3 +328,42 @@ function LinearAlgebra.norm(peps::InfinitePEPS, env::CTMRGEnv) return total end + +""" + correlation_length(peps::InfinitePEPS, env::CTMRGEnv; howmany=2) + +Compute the PEPS correlation length based on the horizontal and vertical +transfer matrices. Additionally the (normalized) eigenvalue spectrum is +returned. Specify the number of computed eigenvalues with `howmany`. +""" +function MPSKit.correlation_length(peps::InfinitePEPS, env::CTMRGEnv; num_vals=2) + T = scalartype(peps) + ξ_h = Vector{real(T)}(undef, size(peps, 1)) + ξ_v = Vector{real(T)}(undef, size(peps, 2)) + λ_h = Vector{Vector{T}}(undef, size(peps, 1)) + λ_v = Vector{Vector{T}}(undef, size(peps, 2)) + + # Horizontal + above_h = MPSMultiline(map(r -> InfiniteMPS(env.edges[1, r, :]), 1:size(peps, 1))) + respaced_edges_h = map(zip(space.(env.edges)[1, :, :], env.edges[3, :, :])) do (V1, T3) + return TensorMap(T3.data, V1) + end + below_h = MPSMultiline(map(r -> InfiniteMPS(respaced_edges_h[r, :]), 1:size(peps, 1))) + transfer_peps_h = TransferPEPSMultiline(peps, NORTH) + vals_h = MPSKit.transfer_spectrum(above_h, transfer_peps_h, below_h; num_vals) + λ_h = map(λ_row -> λ_row / abs(λ_row[1]), vals_h) # Normalize largest eigenvalue + ξ_h = map(λ_row -> -1 / log(abs(λ_row[2])), λ_h) + + # Vertical + above_v = MPSMultiline(map(c -> InfiniteMPS(env.edges[2, :, c]), 1:size(peps, 2))) + respaced_edges_v = map(zip(space.(env.edges)[2, :, :], env.edges[4, :, :])) do (V2, T4) + return TensorMap(T4.data, V2) + end + below_v = MPSMultiline(map(c -> InfiniteMPS(respaced_edges_v[:, c]), 1:size(peps, 2))) + transfer_peps_v = TransferPEPSMultiline(peps, EAST) + vals_v = MPSKit.transfer_spectrum(above_v, transfer_peps_v, below_v; num_vals) + λ_v = map(λ_row -> λ_row / abs(λ_row[1]), vals_v) # Normalize largest eigenvalue + ξ_v = map(λ_row -> -1 / log(abs(λ_row[2])), λ_v) + + return ξ_h, ξ_v, λ_h, λ_v +end diff --git a/src/mpskit_glue/transferpepo_environments.jl b/src/mpskit_glue/transferpepo_environments.jl index e6f00161..f79d641b 100644 --- a/src/mpskit_glue/transferpepo_environments.jl +++ b/src/mpskit_glue/transferpepo_environments.jl @@ -30,7 +30,7 @@ function MPSKit.mixed_fixpoints( righties = PeriodicArray{envtype,2}(undef, numrows, numcols) @threads for cr in 1:numrows - c_above = above[cr] + c_above = above[cr] # TODO: Update index convention to above[cr - 1] c_below = below[cr + 1] (L0, R0) = init[cr] @@ -90,7 +90,7 @@ function gen_init_fps(above::MPSMultiline, O::TransferPEPOMultiline, below::MPSM rand, scalartype(T), left_virtualspace(below, cr + 1, 0) * prod(adjoint.(west_spaces(O[cr], 1))), - left_virtualspace(above, cr, 0), + left_virtualspace(above, cr, 0), # TODO: Update index convention to above[cr - 1] ) R0::T = TensorMap( rand, diff --git a/src/mpskit_glue/transferpeps_environments.jl b/src/mpskit_glue/transferpeps_environments.jl index fb4c12ad..ccc6ff8b 100644 --- a/src/mpskit_glue/transferpeps_environments.jl +++ b/src/mpskit_glue/transferpeps_environments.jl @@ -32,7 +32,7 @@ function MPSKit.mixed_fixpoints( righties = PeriodicArray{envtype,2}(undef, numrows, numcols) @threads for cr in 1:numrows - c_above = above[cr] + c_above = above[cr] # TODO: Update index convention to above[cr - 1] c_below = below[cr + 1] (L0, R0) = init[cr] @@ -94,7 +94,7 @@ function gen_init_fps(above::MPSMultiline, O::TransferPEPSMultiline, below::MPSM left_virtualspace(below, cr + 1, 0) * space(O[cr].top[1], 5)' * space(O[cr].bot[1], 5), - left_virtualspace(above, cr, 0), + left_virtualspace(above, cr, 0), # TODO: Update index convention to above[cr - 1] ) R0::T = TensorMap( rand, @@ -107,3 +107,31 @@ function gen_init_fps(above::MPSMultiline, O::TransferPEPSMultiline, below::MPSM (L0, R0) end end + +function MPSKit.transfer_spectrum( + above::MPSMultiline, + O::TransferPEPSMultiline, + below::MPSMultiline, + init=gen_init_fps(above, O, below); + num_vals=2, + solver=MPSKit.Defaults.eigsolver, +) + @assert size(above) == size(O) + @assert size(below) == size(O) + + numrows = size(above, 2) + envtype = eltype(init[1]) + eigenvals = Vector{Vector{scalartype(envtype)}}(undef, numrows) + + @threads for cr in 1:numrows + L0, = init[cr] + + E_LL = TransferMatrix(above[cr - 1].AL, O[cr], below[cr + 1].AL) # Note that this index convention is different from above! + λ, _, convhist = eigsolve(flip(E_LL), L0, num_vals, :LM, solver) + convhist.converged < num_vals && + @warn "correlation length failed to converge: normres = $(convhist.normres)" + eigenvals[cr] = λ + end + + return eigenvals +end diff --git a/test/heisenberg.jl b/test/heisenberg.jl index 44a85b43..c95ab664 100644 --- a/test/heisenberg.jl +++ b/test/heisenberg.jl @@ -31,8 +31,10 @@ env_init = leading_boundary(CTMRGEnv(psi_init, ComplexSpace(χenv)), psi_init, c # find fixedpoint result = fixedpoint(psi_init, H, opt_alg, env_init) +λ_h, λ_v, = correlation_length(result.peps, result.env) @test result.E ≈ -0.6694421 atol = 1e-2 +@test all(@. λ_h > 0 && λ_v > 0) # same test but for 2x2 unit cell H_2x2 = square_lattice_heisenberg(; unitcell=(2, 2)) @@ -41,5 +43,7 @@ env_init_2x2 = leading_boundary( CTMRGEnv(psi_init_2x2, ComplexSpace(χenv)), psi_init_2x2, ctm_alg ) result_2x2 = fixedpoint(psi_init_2x2, H_2x2, opt_alg, env_init_2x2) +λ_h_2x2, λ_v_2x2, = correlation_length(result_2x2.peps, result_2x2.env) @test result_2x2.E ≈ 4 * result.E atol = 1e-2 +@test all(@. λ_h_2x2 > 0 && λ_v_2x2 > 0) diff --git a/test/tf_ising.jl b/test/tf_ising.jl index 3b3284c3..f8094391 100644 --- a/test/tf_ising.jl +++ b/test/tf_ising.jl @@ -41,6 +41,7 @@ env_init = leading_boundary(CTMRGEnv(psi_init, ComplexSpace(χenv)), psi_init, c # find fixedpoint result = fixedpoint(psi_init, H, opt_alg, env_init) +λ_h, λ_v, = correlation_length(result.peps, result.env) # compute magnetization σx = TensorMap(scalartype(psi_init)[0 1; 1 0], ℂ^2, ℂ^2) @@ -50,3 +51,10 @@ magn = expectation_value(result.peps, M, result.env) @test result.E ≈ e atol = 1e-2 @test imag(magn) ≈ 0 atol = 1e-6 @test abs(magn) ≈ mˣ atol = 5e-2 + +# find fixedpoint in polarized phase and compute correlations lengths +H_polar = square_lattice_tf_ising(; h=4.5) +result_polar = fixedpoint(psi_init, H_polar, opt_alg, env_init) +λ_h_polar, λ_v_polar, = correlation_length(result_polar.peps, result_polar.env) +@test λ_h_polar < λ_h +@test λ_v_polar < λ_v From 022a1eb7d173817433d446dacc50f444d53db7e7 Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Thu, 18 Jul 2024 10:46:41 +0200 Subject: [PATCH 059/213] Add ctmrgscheme function, clean up return values of fixedpoint and add docs --- src/algorithms/ctmrg.jl | 2 ++ src/algorithms/peps_opt.jl | 21 +++++++++++++++++++-- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/algorithms/ctmrg.jl b/src/algorithms/ctmrg.jl index 585e9b5f..d5e845c3 100644 --- a/src/algorithms/ctmrg.jl +++ b/src/algorithms/ctmrg.jl @@ -68,6 +68,8 @@ function CTMRG(; ) end +ctmrgscheme(::CTMRG{S}) where {S} = S + """ MPSKit.leading_boundary([envinit], state, alg::CTMRG) diff --git a/src/algorithms/peps_opt.jl b/src/algorithms/peps_opt.jl index 5d8319a2..1bd28450 100644 --- a/src/algorithms/peps_opt.jl +++ b/src/algorithms/peps_opt.jl @@ -126,11 +126,20 @@ end Optimize `ψ₀` with respect to the Hamiltonian `H` according to the parameters supplied in `alg`. The initial environment `env₀` serves as an initial guess for the first CTMRG run. By default, a random initial environment is used. + +The function returns a `NamedTuple` which contains the following entries: +- `peps`: final `InfinitePEPS` +- `env`: `CTMRGEnv` corresponding to the final PEPS +- `E`: final energy +- `E_history`: convergence history of the energy function +- `grad`: final energy gradient +- `gradnorm_history`: convergence history of the energy gradient norms +- `numfg`: total number of calls to the energy function """ function fixedpoint( ψ₀::InfinitePEPS{T}, H, alg::PEPSOptimize, env₀::CTMRGEnv=CTMRGEnv(ψ₀, field(T)^20) ) where {T} - (peps, env), E, ∂E, info = optimize( + (peps, env), E, ∂E, numfg, convhistory = optimize( (ψ₀, env₀), alg.optimizer; retract=my_retract, inner=my_inner ) do (peps, envs) E, g = withgradient(peps) do ψ @@ -149,7 +158,15 @@ function fixedpoint( # withgradient returns tuple of gradients `g` return E, only(g) end - return (; peps, env, E, ∂E, info) + return (; + peps, + env, + E, + E_history=convhistory[:, 1], + grad=∂E, + gradnorm_history=convhistory[:, 2], + numfg, + ) end # Update PEPS unit cell in non-mutating way From 610d3add81dd784e1ffb8657ad70b138ea905f30 Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Thu, 18 Jul 2024 15:22:35 +0200 Subject: [PATCH 060/213] Fix docstring typo --- src/utility/svd.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utility/svd.jl b/src/utility/svd.jl index 94edcd52..8f0abb73 100644 --- a/src/utility/svd.jl +++ b/src/utility/svd.jl @@ -60,7 +60,7 @@ end """ struct IterSVD(; alg=KrylovKit.GKL(), fallback_threshold = Inf) -Iterative SVD solver based on KrylovKit's GKL algorithm, adapted to (symmmetric) tensors. +Iterative SVD solver based on KrylovKit's GKL algorithm, adapted to (symmetric) tensors. The number of targeted singular values is set via the `TruncationSpace` in `ProjectorAlg`. In particular, this make it possible to specify the targeted singular values block-wise. In case the symmetry block is too small as compared to the number of singular values, or From a554d8ff4a4d539003c6b9ecbf6bcdd2bbb2d235 Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Thu, 18 Jul 2024 15:59:01 +0200 Subject: [PATCH 061/213] Remove assert which unnecessarily forbids user-defined adjoint for :fixed GradMode --- src/algorithms/peps_opt.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/algorithms/peps_opt.jl b/src/algorithms/peps_opt.jl index 1bd28450..c1359a73 100644 --- a/src/algorithms/peps_opt.jl +++ b/src/algorithms/peps_opt.jl @@ -228,7 +228,7 @@ function _rrule( alg::CTMRG{C}, ) where {C} @assert C === :simultaneous - @assert alg.projector_alg.svd_alg.rrule_alg isa Union{KrylovKit.LinearSolver,Arnoldi} + @assert !isnothing(alg.projector_alg.svd_alg.rrule_alg) envs = leading_boundary(envinit, state, alg) envsconv, info = ctmrg_iter(state, envs, alg) envsfix, signs = gauge_fix(envs, envsconv) From 1791a80f4f6908b6d9cd37dd3ea7aa7d78730d0b Mon Sep 17 00:00:00 2001 From: lkdvos Date: Thu, 18 Jul 2024 16:47:29 +0200 Subject: [PATCH 062/213] First re-organisation of files --- src/PEPSKit.jl | 16 ++++++++-------- src/algorithms/{ => ctmrg}/ctmrg.jl | 0 src/algorithms/{ => ctmrg}/ctmrg_all_sides.jl | 0 .../{ctmrg_gauge_fix.jl => ctmrg/gaugefix.jl} | 0 .../{ctmrgenv.jl => ctmrg environments.jl} | 0 .../transferpepo_environments.jl | 0 .../transferpeps_environments.jl | 0 7 files changed, 8 insertions(+), 8 deletions(-) rename src/algorithms/{ => ctmrg}/ctmrg.jl (100%) rename src/algorithms/{ => ctmrg}/ctmrg_all_sides.jl (100%) rename src/algorithms/{ctmrg_gauge_fix.jl => ctmrg/gaugefix.jl} (100%) rename src/environments/{ctmrgenv.jl => ctmrg environments.jl} (100%) rename src/{mpskit_glue => environments}/transferpepo_environments.jl (100%) rename src/{mpskit_glue => environments}/transferpeps_environments.jl (100%) diff --git a/src/PEPSKit.jl b/src/PEPSKit.jl index 5c3695ba..e6ef3229 100644 --- a/src/PEPSKit.jl +++ b/src/PEPSKit.jl @@ -24,17 +24,17 @@ include("operators/transferpeps.jl") include("operators/infinitepepo.jl") include("operators/transferpepo.jl") include("operators/derivatives.jl") - -include("mpskit_glue/transferpeps_environments.jl") -include("mpskit_glue/transferpepo_environments.jl") - -include("environments/ctmrgenv.jl") include("operators/localoperator.jl") include("operators/models.jl") -include("algorithms/ctmrg_gauge_fix.jl") -include("algorithms/ctmrg.jl") -include("algorithms/ctmrg_all_sides.jl") +include("environments/ctmrg environments.jl") +include("environments/transferpeps_environments.jl") +include("environments/transferpepo_environments.jl") + +include("algorithms/ctmrg/ctmrg.jl") +include("algorithms/ctmrg/gaugefix.jl") +include("algorithms/ctmrg/ctmrg_all_sides.jl") + include("algorithms/peps_opt.jl") include("utility/symmetrization.jl") diff --git a/src/algorithms/ctmrg.jl b/src/algorithms/ctmrg/ctmrg.jl similarity index 100% rename from src/algorithms/ctmrg.jl rename to src/algorithms/ctmrg/ctmrg.jl diff --git a/src/algorithms/ctmrg_all_sides.jl b/src/algorithms/ctmrg/ctmrg_all_sides.jl similarity index 100% rename from src/algorithms/ctmrg_all_sides.jl rename to src/algorithms/ctmrg/ctmrg_all_sides.jl diff --git a/src/algorithms/ctmrg_gauge_fix.jl b/src/algorithms/ctmrg/gaugefix.jl similarity index 100% rename from src/algorithms/ctmrg_gauge_fix.jl rename to src/algorithms/ctmrg/gaugefix.jl diff --git a/src/environments/ctmrgenv.jl b/src/environments/ctmrg environments.jl similarity index 100% rename from src/environments/ctmrgenv.jl rename to src/environments/ctmrg environments.jl diff --git a/src/mpskit_glue/transferpepo_environments.jl b/src/environments/transferpepo_environments.jl similarity index 100% rename from src/mpskit_glue/transferpepo_environments.jl rename to src/environments/transferpepo_environments.jl diff --git a/src/mpskit_glue/transferpeps_environments.jl b/src/environments/transferpeps_environments.jl similarity index 100% rename from src/mpskit_glue/transferpeps_environments.jl rename to src/environments/transferpeps_environments.jl From fc49026c0cef39173ff7b63407889e5b031144d0 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Fri, 19 Jul 2024 18:55:50 +0200 Subject: [PATCH 063/213] work towards cleaning --- src/PEPSKit.jl | 16 +- src/algorithms/contractions/localoperator.jl | 451 ++++++++++++++++ src/algorithms/ctmrg/ctmrg.jl | 490 ++++++++++++------ src/algorithms/ctmrg/ctmrg_all_sides.jl | 156 ------ src/algorithms/toolbox.jl | 96 ++++ ... environments.jl => ctmrg_environments.jl} | 0 src/operators/localoperator.jl | 468 ----------------- 7 files changed, 890 insertions(+), 787 deletions(-) create mode 100644 src/algorithms/contractions/localoperator.jl delete mode 100644 src/algorithms/ctmrg/ctmrg_all_sides.jl create mode 100644 src/algorithms/toolbox.jl rename src/environments/{ctmrg environments.jl => ctmrg_environments.jl} (100%) diff --git a/src/PEPSKit.jl b/src/PEPSKit.jl index e6ef3229..2e55ee98 100644 --- a/src/PEPSKit.jl +++ b/src/PEPSKit.jl @@ -17,9 +17,11 @@ include("utility/diffset.jl") include("utility/hook_pullback.jl") include("utility/autoopt.jl") + include("states/abstractpeps.jl") include("states/infinitepeps.jl") + include("operators/transferpeps.jl") include("operators/infinitepepo.jl") include("operators/transferpepo.jl") @@ -27,16 +29,26 @@ include("operators/derivatives.jl") include("operators/localoperator.jl") include("operators/models.jl") -include("environments/ctmrg environments.jl") + +include("environments/ctmrg_environments.jl") include("environments/transferpeps_environments.jl") include("environments/transferpepo_environments.jl") + +include("algorithms/contractions/localoperator.jl") + include("algorithms/ctmrg/ctmrg.jl") include("algorithms/ctmrg/gaugefix.jl") -include("algorithms/ctmrg/ctmrg_all_sides.jl") + +include("algorithms/toolbox.jl") include("algorithms/peps_opt.jl") + + +# include("algorithms/ctmrg/ctmrg_all_sides.jl") + + include("utility/symmetrization.jl") """ diff --git a/src/algorithms/contractions/localoperator.jl b/src/algorithms/contractions/localoperator.jl new file mode 100644 index 00000000..fef30e20 --- /dev/null +++ b/src/algorithms/contractions/localoperator.jl @@ -0,0 +1,451 @@ +# Contraction of local operators on arbitrary lattice locations +# ------------------------------------------------------------- +import MPSKit: tensorexpr + +# currently need this because MPSKit restricts tensor names to symbols +MPSKit.tensorexpr(ex::Expr, inds) = Expr(:ref, ex, inds...) +function MPSKit.tensorexpr(ex::Expr, indout, indin) + return Expr(:typed_vcat, ex, Expr(:row, indout...), Expr(:row, indin...)) +end + +""" + contract_localoperator(inds, O, peps, env) + +Contract a local operator `O` on the PEPS `peps` at the indices `inds` using the environment `env`. +""" +function contract_localoperator( + inds::NTuple{N,CartesianIndex{2}}, + O::AbstractTensorMap{S,N,N}, + ket::InfinitePEPS, + bra::InfinitePEPS, + env::CTMRGEnv, +) where {S,N} + static_inds = Val.(inds) + return _contract_localoperator(static_inds, O, ket, bra, env) +end +function contract_localoperator( + inds::NTuple{N,Tuple{Int,Int}}, + O::AbstractTensorMap{S,N,N}, + ket::InfinitePEPS, + bra::InfinitePEPS, + env::CTMRGEnv, +) where {S,N} + return contract_localoperator(CartesianIndex.(inds), O, ket, bra, env) +end + +# This implements the contraction of an operator acting on sites `inds`. +# The generated function ensures that we can use @tensor to write dynamic contractions (and maximize performance). +@generated function _contract_localoperator( + inds::NTuple{N,Val}, + O::AbstractTensorMap{S,N,N}, + ket::InfinitePEPS, + bra::InfinitePEPS, + env::CTMRGEnv, +) where {S,N} + cartesian_inds = collect(CartesianIndex{2}, map(x -> x.parameters[1], inds.parameters)) # weird hack to extract information from Val + if !allunique(cartesian_inds) + throw(ArgumentError("Indices should not overlap: $cartesian_inds.")) + end + + rmin, rmax = extrema(getindex.(cartesian_inds, 1)) + cmin, cmax = extrema(getindex.(cartesian_inds, 2)) + + gridsize = (rmax - rmin + 1, cmax - cmin + 1) + + corner_NW = tensorexpr( + :(env.corners[ + NORTHWEST, mod1($(rmin - 1), size(ket, 1)), mod1($(cmin - 1), size(ket, 2)) + ]), + (:C_NW_1,), + (:C_NW_2,), + ) + corner_NE = tensorexpr( + :(env.corners[ + NORTHEAST, mod1($(rmin - 1), size(ket, 1)), mod1($(cmax + 1), size(ket, 2)) + ]), + (:C_NE_1,), + (:C_NE_2,), + ) + corner_SE = tensorexpr( + :(env.corners[ + SOUTHEAST, mod1($(rmax + 1), size(ket, 1)), mod1($(cmax + 1), size(ket, 2)) + ]), + (:C_SE_1,), + (:C_SE_2,), + ) + corner_SW = tensorexpr( + :(env.corners[ + SOUTHWEST, mod1($(rmax + 1), size(ket, 1)), mod1($(cmin - 1), size(ket, 2)) + ]), + (:C_SW_1,), + (:C_SW_2,), + ) + + edges_N = map(1:gridsize[2]) do i + return tensorexpr( + :(env.edges[ + NORTH, + mod1($(rmin - 1), size(ket, 1)), + mod1($(cmin + i - 1), size(ket, 2)), + ]), + ( + (i == 1 ? :C_NW_2 : Symbol(:E_N_virtual, i - 1)), + Symbol(:E_N_top, i), + Symbol(:E_N_bot, i), + ), + ((i == gridsize[2] ? :C_NE_1 : Symbol(:E_N_virtual, i)),), + ) + end + + edges_E = map(1:gridsize[1]) do i + return tensorexpr( + :(env.edges[ + EAST, + mod1($(rmin + i - 1), size(ket, 1)), + mod1($(cmax + 1), size(ket, 2)), + ]), + ( + (i == 1 ? :C_NE_2 : Symbol(:E_E_virtual, i - 1)), + Symbol(:E_E_top, i), + Symbol(:E_E_bot, i), + ), + ((i == gridsize[1] ? :C_SE_1 : Symbol(:E_E_virtual, i)),), + ) + end + + edges_S = map(1:gridsize[2]) do i + return tensorexpr( + :(env.edges[ + SOUTH, + mod1($(rmax + 1), size(ket, 1)), + mod1($(cmin + i - 1), size(ket, 2)), + ]), + ( + (i == gridsize[2] ? :C_SE_2 : Symbol(:E_S_virtual, i)), + Symbol(:E_S_top, i), + Symbol(:E_S_bot, i), + ), + ((i == 1 ? :C_SW_1 : Symbol(:E_S_virtual, i - 1)),), + ) + end + + edges_W = map(1:gridsize[1]) do i + return tensorexpr( + :(env.edges[ + WEST, + mod1($(rmin + i - 1), size(ket, 1)), + mod1($(cmin - 1), size(ket, 2)), + ]), + ( + (i == gridsize[1] ? :C_SW_2 : Symbol(:E_W_virtual, i)), + Symbol(:E_W_top, i), + Symbol(:E_W_bot, i), + ), + ((i == 1 ? :C_NW_1 : Symbol(:E_W_virtual, i - 1)),), + ) + end + + operator = tensorexpr( + :O, ntuple(i -> Symbol(:O_out_, i), N), ntuple(i -> Symbol(:O_in_, i), N) + ) + + bra = map(Iterators.product(1:gridsize[1], 1:gridsize[2])) do (i, j) + inds_id = findfirst(==(CartesianIndex(rmin + i - 1, cmin + j - 1)), cartesian_inds) + physical_label = + isnothing(inds_id) ? Symbol(:physical, i, "_", j) : Symbol(:O_out_, inds_id) + return tensorexpr( + :(bra[ + mod1($(rmin + i - 1), size(bra, 1)), mod1($(cmin + j - 1), size(bra, 2)) + ]), + (physical_label,), + ( + (i == 1 ? Symbol(:E_N_bot, j) : Symbol(:bra_vertical, i - 1, "_", j)), + ( + if j == gridsize[2] + Symbol(:E_E_bot, i) + else + Symbol(:bra_horizontal, i, "_", j) + end + ), + ( + if i == gridsize[1] + Symbol(:E_S_bot, j) + else + Symbol(:bra_vertical, i, "_", j) + end + ), + (j == 1 ? Symbol(:E_W_bot, i) : Symbol(:bra_horizontal, i, "_", j - 1)), + ), + ) + end + + ket = map(Iterators.product(1:gridsize[1], 1:gridsize[2])) do (i, j) + inds_id = findfirst(==(CartesianIndex(rmin + i - 1, cmin + j - 1)), cartesian_inds) + physical_label = + isnothing(inds_id) ? Symbol(:physical, i, "_", j) : Symbol(:O_in_, inds_id) + return tensorexpr( + :(ket[ + mod1($(rmin + i - 1), size(ket, 1)), mod1($(cmin + j - 1), size(ket, 2)) + ]), + (physical_label,), + ( + (i == 1 ? Symbol(:E_N_top, j) : Symbol(:ket_vertical, i - 1, "_", j)), + ( + if j == gridsize[2] + Symbol(:E_E_top, i) + else + Symbol(:ket_horizontal, i, "_", j) + end + ), + ( + if i == gridsize[1] + Symbol(:E_S_top, j) + else + Symbol(:ket_vertical, i, "_", j) + end + ), + (j == 1 ? Symbol(:E_W_top, i) : Symbol(:ket_horizontal, i, "_", j - 1)), + ), + ) + end + + multiplication_ex = Expr( + :call, + :*, + corner_NW, + corner_NE, + corner_SE, + corner_SW, + edges_N..., + edges_E..., + edges_S..., + edges_W..., + ket..., + map(x -> Expr(:call, :conj, x), bra)..., + operator, + ) + + opt_ex = Expr(:tuple) + allinds = TensorOperations.getallindices(multiplication_ex) + for label in allinds + if startswith(String(label), "physical") || startswith(String(label), "O") + push!(opt_ex.args, :($label => $PEPS_PHYSICALDIM)) + elseif startswith(String(label), "ket") || startswith(String(label), "bra") + push!(opt_ex.args, :($label => $PEPS_BONDDIM)) + else + push!(opt_ex.args, :($label => $PEPS_ENVBONDDIM)) + end + end + + return quote + @tensor opt = $opt_ex $multiplication_ex + end +end + +""" + contract_localnorm(inds, peps, env) + +Contract a local norm of the PEPS `peps` around indices `inds`. +""" +function contract_localnorm( + inds::NTuple{N,CartesianIndex{2}}, ket::InfinitePEPS, bra::InfinitePEPS, env::CTMRGEnv +) where {N} + static_inds = Val.(inds) + return _contract_localnorm(static_inds, ket, bra, env) +end +function contract_localnorm( + inds::NTuple{N,Tuple{Int,Int}}, ket::InfinitePEPS, bra::InfinitePEPS, env::CTMRGEnv +) where {N} + return contract_localnorm(CartesianIndex.(inds), ket, bra, env) +end +@generated function _contract_localnorm( + inds::NTuple{N,Val}, ket::InfinitePEPS, bra::InfinitePEPS, env::CTMRGEnv +) where {N} + cartesian_inds = collect(CartesianIndex{2}, map(x -> x.parameters[1], inds.parameters)) # weird hack to extract information from Val + if !allunique(cartesian_inds) + throw(ArgumentError("Indices should not overlap: $cartesian_inds.")) + end + + rmin, rmax = extrema(getindex.(cartesian_inds, 1)) + cmin, cmax = extrema(getindex.(cartesian_inds, 2)) + + gridsize = (rmax - rmin + 1, cmax - cmin + 1) + + corner_NW = tensorexpr( + :(env.corners[ + NORTHWEST, mod1($(rmin - 1), size(ket, 1)), mod1($(cmin - 1), size(ket, 2)) + ]), + (:C_NW_1,), + (:C_NW_2,), + ) + corner_NE = tensorexpr( + :(env.corners[ + NORTHEAST, mod1($(rmin - 1), size(ket, 1)), mod1($(cmax + 1), size(ket, 2)) + ]), + (:C_NE_1,), + (:C_NE_2,), + ) + corner_SE = tensorexpr( + :(env.corners[ + SOUTHEAST, mod1($(rmax + 1), size(ket, 1)), mod1($(cmax + 1), size(ket, 2)) + ]), + (:C_SE_1,), + (:C_SE_2,), + ) + corner_SW = tensorexpr( + :(env.corners[ + SOUTHWEST, mod1($(rmax + 1), size(ket, 1)), mod1($(cmin - 1), size(ket, 2)) + ]), + (:C_SW_1,), + (:C_SW_2,), + ) + + edges_N = map(1:gridsize[2]) do i + return tensorexpr( + :(env.edges[ + NORTH, + mod1($(rmin - 1), size(ket, 1)), + mod1($(cmin + i - 1), size(ket, 2)), + ]), + ( + (i == 1 ? :C_NW_2 : Symbol(:E_N_virtual, i - 1)), + Symbol(:E_N_top, i), + Symbol(:E_N_bot, i), + ), + ((i == gridsize[2] ? :C_NE_1 : Symbol(:E_N_virtual, i)),), + ) + end + + edges_E = map(1:gridsize[1]) do i + return tensorexpr( + :(env.edges[ + EAST, + mod1($(rmin + i - 1), size(ket, 1)), + mod1($(cmax + 1), size(ket, 2)), + ]), + ( + (i == 1 ? :C_NE_2 : Symbol(:E_E_virtual, i - 1)), + Symbol(:E_E_top, i), + Symbol(:E_E_bot, i), + ), + ((i == gridsize[1] ? :C_SE_1 : Symbol(:E_E_virtual, i)),), + ) + end + + edges_S = map(1:gridsize[2]) do i + return tensorexpr( + :(env.edges[ + SOUTH, + mod1($(rmax + 1), size(ket, 1)), + mod1($(cmin + i - 1), size(ket, 2)), + ]), + ( + (i == gridsize[2] ? :C_SE_2 : Symbol(:E_S_virtual, i)), + Symbol(:E_S_top, i), + Symbol(:E_S_bot, i), + ), + ((i == 1 ? :C_SW_1 : Symbol(:E_S_virtual, i - 1)),), + ) + end + + edges_W = map(1:gridsize[1]) do i + return tensorexpr( + :(env.edges[ + WEST, + mod1($(rmin + i - 1), size(ket, 1)), + mod1($(cmin - 1), size(ket, 2)), + ]), + ( + (i == gridsize[1] ? :C_SW_2 : Symbol(:E_W_virtual, i)), + Symbol(:E_W_top, i), + Symbol(:E_W_bot, i), + ), + ((i == 1 ? :C_NW_1 : Symbol(:E_W_virtual, i - 1)),), + ) + end + + bra = map(Iterators.product(1:gridsize[1], 1:gridsize[2])) do (i, j) + return tensorexpr( + :(bra[ + mod1($(rmin + i - 1), size(ket, 1)), mod1($(cmin + j - 1), size(ket, 2)) + ]), + (Symbol(:physical, i, "_", j),), + ( + (i == 1 ? Symbol(:E_N_bot, j) : Symbol(:bra_vertical, i - 1, "_", j)), + ( + if j == gridsize[2] + Symbol(:E_E_bot, i) + else + Symbol(:bra_horizontal, i, "_", j) + end + ), + ( + if i == gridsize[1] + Symbol(:E_S_bot, j) + else + Symbol(:bra_vertical, i, "_", j) + end + ), + (j == 1 ? Symbol(:E_W_bot, i) : Symbol(:bra_horizontal, i, "_", j - 1)), + ), + ) + end + + ket = map(Iterators.product(1:gridsize[1], 1:gridsize[2])) do (i, j) + return tensorexpr( + :(ket[ + mod1($(rmin + i - 1), size(ket, 1)), mod1($(cmin + j - 1), size(ket, 2)) + ]), + (Symbol(:physical, i, "_", j),), + ( + (i == 1 ? Symbol(:E_N_top, j) : Symbol(:ket_vertical, i - 1, "_", j)), + ( + if j == gridsize[2] + Symbol(:E_E_top, i) + else + Symbol(:ket_horizontal, i, "_", j) + end + ), + ( + if i == gridsize[1] + Symbol(:E_S_top, j) + else + Symbol(:ket_vertical, i, "_", j) + end + ), + (j == 1 ? Symbol(:E_W_top, i) : Symbol(:ket_horizontal, i, "_", j - 1)), + ), + ) + end + + multiplication_ex = Expr( + :call, + :*, + corner_NW, + corner_NE, + corner_SE, + corner_SW, + edges_N..., + edges_E..., + edges_S..., + edges_W..., + ket..., + map(x -> Expr(:call, :conj, x), bra)..., + ) + + opt_ex = Expr(:tuple) + allinds = TensorOperations.getallindices(multiplication_ex) + for label in allinds + if startswith(String(label), "physical") + push!(opt_ex.args, :($label => $PEPS_PHYSICALDIM)) + elseif startswith(String(label), "ket") || startswith(String(label), "bra") + push!(opt_ex.args, :($label => $PEPS_BONDDIM)) + else + push!(opt_ex.args, :($label => $PEPS_ENVBONDDIM)) + end + end + + return quote + @tensor opt = $opt_ex $multiplication_ex + end +end diff --git a/src/algorithms/ctmrg/ctmrg.jl b/src/algorithms/ctmrg/ctmrg.jl index d5e845c3..80dc8e65 100644 --- a/src/algorithms/ctmrg/ctmrg.jl +++ b/src/algorithms/ctmrg/ctmrg.jl @@ -7,7 +7,6 @@ have different spaces, this truncation style is different from `TruncationSpace` """ struct FixedSpaceTruncation <: TensorKit.TruncationScheme end -# TODO: add option for different projector styles (half-infinite, full-infinite, etc.) """ struct ProjectorAlg{S}(; svd_alg=TensorKit.SVD(), trscheme=TensorKit.notrunc(), fixedspace=false, verbosity=0) @@ -23,8 +22,26 @@ environment direction/unit cell entry. trscheme::T = FixedSpaceTruncation() verbosity::Int = 0 end +# TODO: add option for different projector styles (half-infinite, full-infinite, etc.) + +function truncation_scheme(alg::ProjectorAlg, edge) + if alg.trscheme isa FixedSpaceTruncation + return truncspace(space(edge, 1)) + else + return alg.trscheme + end +end + +function svd_algorithm(alg::ProjectorAlg, (dir, r, c)) + if alg.svd_alg isa SVDAdjoint{<:FixedSVD} + fwd_alg = alg.svd_alg.fwd_alg + fix_svd = FixedSVD(fwd_alg.U[dir, r, c], fwd_alg.S[dir, r, c], fwd_alg.V[dir, r, c]) + return SVDAdjoint(; fwd_alg=fix_svd, rrule_alg=alg.svd_alg.rrule_alg) + else + return alg.svd_alg + end +end -# TODO: add abstract Algorithm type? """ CTMRG(; tol=Defaults.ctmrg_tol, maxiter=Defaults.ctmrg_maxiter, miniter=Defaults.ctmrg_miniter, verbosity=0, @@ -61,7 +78,7 @@ function CTMRG(; verbosity=2, svd_alg=SVDAdjoint(), trscheme=FixedSpaceTruncation(), - ctmrgscheme=Defaults.ctmrgscheme, + ctmrgscheme::Symbol=Defaults.ctmrgscheme, ) return CTMRG{ctmrgscheme}( tol, maxiter, miniter, verbosity, ProjectorAlg(; svd_alg, trscheme, verbosity) @@ -70,6 +87,11 @@ end ctmrgscheme(::CTMRG{S}) where {S} = S +# aliases for the different CTMRG schemes +const SequentialCTMRG = CTMRG{:sequential} +const SimultaneousCTMRG = CTMRG{:simultaneous} + + """ MPSKit.leading_boundary([envinit], state, alg::CTMRG) @@ -79,7 +101,7 @@ Per default, a random initial environment is used. function MPSKit.leading_boundary(state, alg::CTMRG) return MPSKit.leading_boundary(CTMRGEnv(state, oneunit(spacetype(state))), state, alg) end -function MPSKit.leading_boundary(envinit, state, alg::CTMRG{S}) where {S} +function MPSKit.leading_boundary(envinit, state, alg::CTMRG) CS = map(x -> tsvd(x; alg=TensorKit.SVD())[2], envinit.corners) TS = map(x -> tsvd(x; alg=TensorKit.SVD())[2], envinit.edges) @@ -105,7 +127,7 @@ function MPSKit.leading_boundary(envinit, state, alg::CTMRG{S}) where {S} verbosity=alg.verbosity, svd_alg=alg.projector_alg.svd_alg, trscheme=FixedSpaceTruncation(), - ctmrgscheme=S, + ctmrgscheme=ctmrgscheme(alg), ) env′, = ctmrg_iter(state, env, alg_fixed) envfix, = gauge_fix(env, env′) @@ -122,6 +144,37 @@ function MPSKit.leading_boundary(envinit, state, alg::CTMRG{S}) where {S} end end +""" + ctmrg_iter(state, env::CTMRGEnv, alg::CTMRG{:sequential}) + +Perform one iteration of CTMRG that maps the `state` and `env` to a new environment, +and also return the truncation error. +One CTMRG iteration consists of four `left_move` calls and 90 degree rotations, +such that the environment is grown and renormalized in all four directions. +""" +function ctmrg_iter(state, envs::CTMRGEnv, alg::SequentialCTMRG) + ϵ = 0.0 + for _ in 1:4 + # left move + enlarged_envs = ctmrg_expand(state, envs, alg) + projectors, info = ctmrg_projectors(enlarged_envs, envs, alg) + envs = ctmrg_renormalize(enlarged_envs, projectors, state, envs, alg) + + # rotate + state = rotate_north(state, EAST) + envs = rotate_north(envs, EAST) + ϵ = max(ϵ, info.err) + end + + return envs, (; err=ϵ) +end +function ctmrg_iter(state, envs::CTMRGEnv, alg::SimultaneousCTMRG) + enlarged_envs = ctmrg_expand(state, envs, alg) + projectors, info = ctmrg_projectors(enlarged_envs, envs, alg) + envs′ = ctmrg_renormalize(enlarged_envs, projectors, state, envs, alg) + return envs′, info +end + ctmrg_loginit!(log, η, N) = @infov 2 loginit!(log, η, N) ctmrg_logiter!(log, iter, η, N) = @infov 3 logiter!(log, iter, η, N) ctmrg_logfinish!(log, iter, η, N) = @infov 2 logfinish!(log, iter, η, N) @@ -132,95 +185,291 @@ ctmrg_logcancel!(log, iter, η, N) = @warnv 1 logcancel!(log, iter, η, N) @non_differentiable ctmrg_logfinish!(args...) @non_differentiable ctmrg_logcancel!(args...) +# ======================================================================================== # +# Expansion step +# ======================================================================================== # + """ - ctmrg_iter(state, env::CTMRGEnv{C,T}, alg::CTMRG) where {C,T} - -Perform one iteration of CTMRG that maps the `state` and `env` to a new environment, -and also return the truncation error. -One CTMRG iteration consists of four `left_move` calls and 90 degree rotations, -such that the environment is grown and renormalized in all four directions. + ctmrg_expand(state, envs, alg::CTMRG{M}) + +Expand the environment by absorbing a new PEPS tensor. +There are two modes of expansion: `M = :sequential` and `M = :simultaneous`. +The first mode expands the environment in one direction at a time, for convenience towards +the left. The second mode expands the environment in all four directions simultaneously. """ -function ctmrg_iter(state, env::CTMRGEnv{C,T}, alg::CTMRG) where {C,T} - ϵ = 0.0 +function ctmrg_expand(state, envs::CTMRGEnv{C,T}, ::SequentialCTMRG) where {C,T} + Qtype = tensormaptype(spacetype(C), 3, 3, storagetype(C)) + Q_nw = Zygote.Buffer(envs.corners, Qtype, axes(state)...) + Q_sw = Zygote.Buffer(envs.corners, Qtype, axes(state)...) + + directions = collect(Iterators.product(axes(state)...)) + @fwdthreads for (r, c) in directions + r′ = _next(r, size(state, 1)) + Q_nw[r, c] = northwest_corner((r, c), envs, state) + Q_sw[r, c] = southwest_corner((r′, c), envs, state) + end - for _ in 1:4 - env, info = left_move(state, env, alg.projector_alg) - state = rotate_north(state, EAST) - env = rotate_north(env, EAST) - ϵ = max(ϵ, info.err) + return copy(Q_nw), copy(Q_sw) +end +function ctmrg_expand(state, envs::CTMRGEnv{C,T}, ::SimultaneousCTMRG) where {C,T} + Qtype = tensormaptype(spacetype(C), 3, 3, storagetype(C)) + Q = Zygote.Buffer(Array{Qtype,3}(undef, size(envs.corners))) + drc_combinations = collect(Iterators.product(axes(envs.corners)...)) + @fwdthreads for (dir, r, c) in drc_combinations + Q[dir, r, c] = if dir == NORTHWEST + northwest_corner((r, c), envs, state) + elseif dir == NORTHEAST + northeast_corner((r, c), envs, state) + elseif dir == SOUTHEAST + southeast_corner((r, c), envs, state) + elseif dir == SOUTHWEST + southwest_corner((r, c), envs, state) + end end - return env, (; err=ϵ) + return copy(Q) end -""" - left_move(state, env::CTMRGEnv{C,T}, alg::CTMRG) where {C,T} +# ======================================================================================== # +# Projector step +# ======================================================================================== # -Grow, project and renormalize the environment `env` in west direction. -Return the updated environment as well as the projectors and truncation error. """ -function left_move(state, env::CTMRGEnv{C,T}, alg::ProjectorAlg) where {C,T} - corners::typeof(env.corners) = copy(env.corners) - edges::typeof(env.edges) = copy(env.edges) + ctmrg_projectors(Q, env, alg::CTMRG{M}) +""" +function ctmrg_projectors( + enlarged_envs, envs::CTMRGEnv{C,E}, alg::SequentialCTMRG +) where {C,E} + projector_alg = alg.projector_alg + # pre-allocation + P_bottom, P_top = Zygote.Buffer.(projector_type(envs.edges)) + ϵ = 0.0 + + directions = collect(Iterators.product(axes(envs.corners, 2), axes(envs.corners, 3))) + @fwdthreads for (r, c) in directions + # SVD half-infinite environment + @autoopt @tensor QQ[χ_EB D_EBabove D_EBbelow; χ_ET D_ETabove D_ETbelow] := + enlarged_envs[2][r, c][χ_EB D_EBabove D_EBbelow; χ D1 D2] * + enlarged_envs[1][r, c][χ D1 D2; χ_ET D_ETabove D_ETbelow] + + trscheme = truncation_scheme(projector_alg, envs.edges[dir, r, c]) + svd_alg = svd_algorithm(projector_alg, (dir, r, c)) + U, S, V, ϵ_local = PEPSKit.tsvd!(QQ, svd_alg; trunc=trscheme) + ϵ = max(ϵ, ϵ_local / norm(S)) + + # Compute SVD truncation error and check for degenerate singular values + ignore_derivatives() do + if alg.verbosity > 0 && is_degenerate_spectrum(S) + svals = TensorKit.SectorDict(c => diag(b) for (c, b) in blocks(S)) + @warn("degenerate singular values detected: ", svals) + end + end + + # Compute projectors + P_bottom[r, c], P_top[r, c] = build_projectors( + U, + S, + V, + enlarged_envs[2][r, c], + enlarged_envs[1][r, c], + ) + end + + return (copy(P_bottom), copy(P_top)), (; err=ϵ) +end +function ctmrg_projectors( + enlarged_envs, envs::CTMRGEnv{C,E}, alg::SimultaneousCTMRG +) where {C,E} + projector_alg = alg.projector_alg + # pre-allocation + P_left, P_right = Zygote.Buffer.(projector_type(envs.edges)) + U, V = Zygote.Buffer.(projector_type(envs.edges)) + # Corner type but with real numbers + S = Zygote.Buffer(U.data, tensormaptype(spacetype(C), 1, 1, real(scalartype(E)))) + ϵ = 0.0 - P_bottom, P_top = Zygote.Buffer.(projector_type(T, size(state))) # Use Zygote.Buffer instead of @diffset to avoid ZeroTangent errors in _setindex + drc_combinations = collect(Iterators.product(axes(envs.corners)...)) + @fwdthreads for (dir, r, c) in drc_combinations + # Row-column index of next enlarged corner + next_rc = if dir == 1 + (r, _next(c, size(envs.corners, 3))) + elseif dir == 2 + (_next(r, size(envs.corners, 2)), c) + elseif dir == 3 + (r, _prev(c, size(envs.corners, 3))) + elseif dir == 4 + (_prev(r, size(envs.corners, 2)), c) + end - for col in 1:size(state, 2) - cprev = _prev(col, size(state, 2)) + # SVD half-infinite environment + @autoopt @tensor QQ[χ_in D_inabove D_inbelow; χ_out D_outabove D_outbelow] := + enlarged_envs[dir, r, c][χ_in D_inabove D_inbelow; χ D1 D2] * + enlarged_envs[_next(dir, 4), next_rc...][χ D1 D2; χ_out D_outabove D_outbelow] + + trscheme = truncation_scheme(projector_alg, envs.edges[dir, r, c]) + svd_alg = svd_algorithm(projector_alg, (dir, r, c)) + U_local, S_local, V_local, ϵ_local = PEPSKit.tsvd!(QQ, svd_alg; trunc=trscheme) + U[dir, r, c] = U_local + S[dir, r, c] = S_local + V[dir, r, c] = V_local + ϵ = max(ϵ, ϵ_local / norm(S_local)) + + # Compute SVD truncation error and check for degenerate singular values + ignore_derivatives() do + if alg.verbosity > 0 && is_degenerate_spectrum(S_local) + svals = TensorKit.SectorDict(c => diag(b) for (c, b) in blocks(S_local)) + @warn("degenerate singular values detected: ", svals) + end + end # Compute projectors - for row in 1:size(state, 1) - # Enlarged corners - Q_sw = southwest_corner((_next(row, size(state, 1)), col), env, state) - Q_nw = northwest_corner((row, col), env, state) - - # SVD half-infinite environment - trscheme = if alg.trscheme isa FixedSpaceTruncation - truncspace(space(env.edges[WEST, row, col], 1)) - else - alg.trscheme - end - @autoopt @tensor QQ[χ_EB D_EBabove D_EBbelow; χ_ET D_ETabove D_ETbelow] := - Q_sw[χ_EB D_EBabove D_EBbelow; χ D1 D2] * - Q_nw[χ D1 D2; χ_ET D_ETabove D_ETbelow] - U, S, V, ϵ_local = PEPSKit.tsvd!(QQ, alg.svd_alg; trunc=trscheme) - ϵ = max(ϵ, ϵ_local / norm(S)) - # TODO: check if we can just normalize enlarged corners s.t. trunc behaves a bit better - - # Compute SVD truncation error and check for degenerate singular values - ignore_derivatives() do - if alg.verbosity > 0 && is_degenerate_spectrum(S) - svals = TensorKit.SectorDict(c => diag(b) for (c, b) in blocks(S)) - @warn("degenerate singular values detected: ", svals) - end - end + P_left[dir, r, c], P_right[dir, r, c] = build_projectors( + U_local, + S_local, + V_local, + enlarged_envs[dir, r, c], + enlarged_envs[_next(dir, 4), next_rc...], + ) + end + + return (copy(P_left), copy(P_right)), (; err=ϵ, U=copy(U), S=copy(S), V=copy(V)) +end + +# ======================================================================================== # +# Renormalization step +# ======================================================================================== # - # Compute projectors - Pb, Pt = build_projectors(U, S, V, Q_sw, Q_nw) - P_bottom[row, col] = Pb - P_top[row, col] = Pt +""" + ctmrg_renormalize(enlarged_envs, projectors, state, envs, alg::CTMRG{M}) + +Apply projectors to renormalize corners and edges. +""" +function ctmrg_renormalize(enlarged_envs, projectors, state, envs, alg::SequentialCTMRG) + corners = Zygote.Buffer(envs.corners) + edges = Zygote.Buffer(envs.edges) + + # copy environments that do not participate + for dir in (NORTHEAST, SOUTHEAST) + for r in axes(envs.corners, 2), c in axes(envs.corners, 3) + corners[dir, r, c] = envs.corners[dir, r, c] end + end + for r in axes(envs.corners, 2), c in axes(envs.corners, 3) + edges[EAST, r, c] = envs.edges[EAST, r, c] + end + + # Apply projectors to renormalize corners and edges + coordinates = collect(Iterators.product(axes(state)...)) + @fwdthreads for (r, c) in coordinates + r′ = _prev(r, size(state, 1)) + c′ = _prev(col, size(state, 2)) + C_sw, C_nw, T_w = grow_env_left( + state[r, c], + projectors[1][r′, c], + projectors[2][r, c], + envs.corners[SOUTHWEST, r, c′], + envs.corners[NORTHWEST, r, c′], + envs.edges[SOUTH, r, c], + envs.edges[WEST, r, c′], + envs.edges[NORTH, r, c], + ) + corners[SOUTHWEST, r, c] = C_sw / norm(C_sw) + corners[NORTHWEST, r, c] = C_nw / norm(C_nw) + edges[WEST, r, c] = T_w / norm(T_w) + end + + return CTMRGEnv(copy(corners), copy(edges)) +end +function ctmrg_renormalize(enlarged_envs, projectors, state, envs, alg::SimultaneousCTMRG) + corners = Zygote.Buffer(envs.corners) + edges = Zygote.Buffer(envs.edges) + P_left, P_right = projectors + + coordinates = collect(Iterators.product(axes(state)...)) + @fwdthreads for (r, c) in coordinates + rprev = _prev(r, size(state, 1)) + rnext = _next(r, size(state, 1)) + cprev = _prev(c, size(state, 2)) + cnext = _next(c, size(state, 2)) + + C_northwest = _contract_new_corner( + P_right[WEST, rnext, c], enlarged_envs[NORTHWEST, r, c], P_left[NORTH, r, c] + ) + corners[NORTHWEST, r, c] = C_northwest / norm(C_northwest) + + C_northeast = _contract_new_corner( + P_right[NORTH, r, cprev], enlarged_envs[NORTHEAST, r, c], P_left[EAST, r, c] + ) + corners[NORTHEAST, r, c] = C_northeast / norm(C_northeast) + + C_southeast = _contract_new_corner( + P_right[EAST, rprev, c], enlarged_envs[SOUTHEAST, r, c], P_left[SOUTH, r, c] + ) + corners[SOUTHEAST, r, c] = C_southeast / norm(C_southeast) + + C_southwest = _contract_new_corner( + P_right[SOUTH, r, cnext], enlarged_envs[SOUTHWEST, r, c], P_left[WEST, r, c] + ) + corners[SOUTHWEST, r, c] = C_southwest / norm(C_southwest) + + @autoopt @tensor E_north[χ_W D_Sab D_Sbe; χ_E] := + envs.edges[NORTH, rprev, c][χ1 D1 D2; χ2] * + state[r, c][d; D1 D3 D_Sab D5] * + conj(state[r, c][d; D2 D4 D_Sbe D6]) * + P_left[NORTH, r, c][χ2 D3 D4; χ_E] * + P_right[NORTH, r, cprev][χ_W; χ1 D5 D6] + edges[NORTH, r, c] = E_north / norm(E_north) + + @autoopt @tensor E_east[χ_N D_Wab D_Wbe; χ_S] := + envs.edges[EAST, r, cnext][χ1 D1 D2; χ2] * + state[r, c][d; D5 D1 D3 D_Wab] * + conj(state[r, c][d; D6 D2 D4 D_Wbe]) * + P_left[EAST, r, c][χ2 D3 D4; χ_S] * + P_right[EAST, rprev, c][χ_N; χ1 D5 D6] + edges[EAST, r, c] = E_east / norm(E_east) + + @autoopt @tensor E_south[χ_E D_Nab D_Nbe; χ_W] := + envs.edges[SOUTH, rnext, c][χ1 D1 D2; χ2] * + state[r, c][d; D_Nab D5 D1 D3] * + conj(state[r, c][d; D_Nbe D6 D2 D4]) * + P_left[SOUTH, r, c][χ2 D3 D4; χ_W] * + P_right[SOUTH, r, cnext][χ_E; χ1 D5 D6] + edges[SOUTH, r, c] = E_south / norm(E_south) + + @autoopt @tensor E_west[χ_S D_Eab D_Ebe; χ_N] := + envs.edges[WEST, r, cprev][χ1 D1 D2; χ2] * + state[r, c][d; D3 D_Eab D5 D1] * + conj(state[r, c][d; D4 D_Ebe D6 D2]) * + P_left[WEST, r, c][χ2 D3 D4; χ_N] * + P_right[WEST, rnext, c][χ_S; χ1 D5 D6] + edges[WEST, r, c] = E_west / norm(E_west) + end + + return CTMRGEnv(copy(corners), copy(edges)) +end - # Use projectors to grow the corners & edges - for row in 1:size(state, 1) - rprev = _prev(row, size(state, 1)) - C_sw, C_nw, T_w = grow_env_left( - state[row, col], - P_bottom[rprev, col], - P_top[row, col], - env.corners[SOUTHWEST, row, cprev], - env.corners[NORTHWEST, row, cprev], - env.edges[SOUTH, row, col], - env.edges[WEST, row, cprev], - env.edges[NORTH, row, col], - ) - @diffset corners[SOUTHWEST, row, col] = C_sw / norm(C_sw) - @diffset corners[NORTHWEST, row, col] = C_nw / norm(C_nw) - @diffset edges[WEST, row, col] = T_w / norm(T_w) +# ======================================================================================== # +# Auxiliary routines +# ======================================================================================== # + +# Compute enlarged corners and edges for all directions and unit cell entries +function enlarge_corners_edges(state, env::CTMRGEnv{C,T}) where {C,T} + Qtype = tensormaptype(spacetype(C), 3, 3, storagetype(C)) + Q = Zygote.Buffer(Array{Qtype,3}(undef, size(env.corners))) + drc_combinations = collect(Iterators.product(axes(env.corners)...)) + @fwdthreads for (dir, r, c) in drc_combinations + Q[dir, r, c] = if dir == NORTHWEST + northwest_corner((r, c), env, state) + elseif dir == NORTHEAST + northeast_corner((r, c), env, state) + elseif dir == SOUTHEAST + southeast_corner((r, c), env, state) + elseif dir == SOUTHWEST + southwest_corner((r, c), env, state) end end - return CTMRGEnv(corners, edges), (; err=ϵ, P_left=copy(P_top), P_right=copy(P_bottom)) + return copy(Q) end # Enlarged corner contractions (need direction specific methods to avoid PEPS rotations) @@ -257,6 +506,11 @@ function southwest_corner((row, col), env, peps_above, peps_below=peps_above) conj(peps_below[row, col][d; D_Nbelow D_Ebelow D2 D4]) end +function _contract_new_corner(P_right, Q, P_left) + return @autoopt @tensor corner[χ_in; χ_out] := + P_right[χ_in; χ1 D1 D2] * Q[χ1 D1 D2; χ2 D3 D4] * P_left[χ2 D3 D4; χ_out] +end + # Build projectors from SVD and enlarged SW & NW corners function build_projectors( U::AbstractTensorMap{E,3,1}, S, V::AbstractTensorMap{E,1,3}, Q, Q_next @@ -283,89 +537,3 @@ function grow_env_left( P_top[χ_S; χ1 D5 D6] return corner_SW′, corner_NW′, edge_W′ end - -@doc """ - LinearAlgebra.norm(peps::InfinitePEPS, env::CTMRGEnv) - -Compute the norm of a PEPS contracted with a CTM environment. -""" - -function LinearAlgebra.norm(peps::InfinitePEPS, env::CTMRGEnv) - total = one(scalartype(peps)) - - for r in 1:size(peps, 1), c in 1:size(peps, 2) - rprev = _prev(r, size(peps, 1)) - rnext = _next(r, size(peps, 1)) - cprev = _prev(c, size(peps, 2)) - cnext = _next(c, size(peps, 2)) - total *= @autoopt @tensor env.edges[WEST, r, cprev][χ1 D1 D2; χ2] * - env.corners[NORTHWEST, rprev, cprev][χ2; χ3] * - env.edges[NORTH, rprev, c][χ3 D3 D4; χ4] * - env.corners[NORTHEAST, rprev, cnext][χ4; χ5] * - env.edges[EAST, r, cnext][χ5 D5 D6; χ6] * - env.corners[SOUTHEAST, rnext, cnext][χ6; χ7] * - env.edges[SOUTH, rnext, c][χ7 D7 D8; χ8] * - env.corners[SOUTHWEST, rnext, cprev][χ8; χ1] * - peps[r, c][d; D3 D5 D7 D1] * - conj(peps[r, c][d; D4 D6 D8 D2]) - total *= tr( - env.corners[NORTHWEST, rprev, cprev] * - env.corners[NORTHEAST, rprev, c] * - env.corners[SOUTHEAST, r, c] * - env.corners[SOUTHWEST, r, cprev], - ) - total /= @autoopt @tensor env.edges[WEST, r, cprev][χ1 D1 D2; χ2] * - env.corners[NORTHWEST, rprev, cprev][χ2; χ3] * - env.corners[NORTHEAST, rprev, c][χ3; χ4] * - env.edges[EAST, r, c][χ4 D1 D2; χ5] * - env.corners[SOUTHEAST, rnext, c][χ5; χ6] * - env.corners[SOUTHWEST, rnext, cprev][χ6; χ1] - total /= @autoopt @tensor env.corners[NORTHWEST, rprev, cprev][χ1; χ2] * - env.edges[NORTH, rprev, c][χ2 D1 D2; χ3] * - env.corners[NORTHEAST, rprev, cnext][χ3; χ4] * - env.corners[SOUTHEAST, r, cnext][χ4; χ5] * - env.edges[SOUTH, r, c][χ5 D1 D2; χ6] * - env.corners[SOUTHWEST, r, cprev][χ6; χ1] - end - - return total -end - -""" - correlation_length(peps::InfinitePEPS, env::CTMRGEnv; howmany=2) - -Compute the PEPS correlation length based on the horizontal and vertical -transfer matrices. Additionally the (normalized) eigenvalue spectrum is -returned. Specify the number of computed eigenvalues with `howmany`. -""" -function MPSKit.correlation_length(peps::InfinitePEPS, env::CTMRGEnv; num_vals=2) - T = scalartype(peps) - ξ_h = Vector{real(T)}(undef, size(peps, 1)) - ξ_v = Vector{real(T)}(undef, size(peps, 2)) - λ_h = Vector{Vector{T}}(undef, size(peps, 1)) - λ_v = Vector{Vector{T}}(undef, size(peps, 2)) - - # Horizontal - above_h = MPSMultiline(map(r -> InfiniteMPS(env.edges[1, r, :]), 1:size(peps, 1))) - respaced_edges_h = map(zip(space.(env.edges)[1, :, :], env.edges[3, :, :])) do (V1, T3) - return TensorMap(T3.data, V1) - end - below_h = MPSMultiline(map(r -> InfiniteMPS(respaced_edges_h[r, :]), 1:size(peps, 1))) - transfer_peps_h = TransferPEPSMultiline(peps, NORTH) - vals_h = MPSKit.transfer_spectrum(above_h, transfer_peps_h, below_h; num_vals) - λ_h = map(λ_row -> λ_row / abs(λ_row[1]), vals_h) # Normalize largest eigenvalue - ξ_h = map(λ_row -> -1 / log(abs(λ_row[2])), λ_h) - - # Vertical - above_v = MPSMultiline(map(c -> InfiniteMPS(env.edges[2, :, c]), 1:size(peps, 2))) - respaced_edges_v = map(zip(space.(env.edges)[2, :, :], env.edges[4, :, :])) do (V2, T4) - return TensorMap(T4.data, V2) - end - below_v = MPSMultiline(map(c -> InfiniteMPS(respaced_edges_v[:, c]), 1:size(peps, 2))) - transfer_peps_v = TransferPEPSMultiline(peps, EAST) - vals_v = MPSKit.transfer_spectrum(above_v, transfer_peps_v, below_v; num_vals) - λ_v = map(λ_row -> λ_row / abs(λ_row[1]), vals_v) # Normalize largest eigenvalue - ξ_v = map(λ_row -> -1 / log(abs(λ_row[2])), λ_v) - - return ξ_h, ξ_v, λ_h, λ_v -end diff --git a/src/algorithms/ctmrg/ctmrg_all_sides.jl b/src/algorithms/ctmrg/ctmrg_all_sides.jl deleted file mode 100644 index 1b513c00..00000000 --- a/src/algorithms/ctmrg/ctmrg_all_sides.jl +++ /dev/null @@ -1,156 +0,0 @@ -# One CTMRG iteration with both-sided application of projectors -function ctmrg_iter(state, env::CTMRGEnv, alg::CTMRG{:simultaneous}) - # Compute enlarged corners - Q = enlarge_corners_edges(state, env) - - # Compute projectors if none are supplied - P_left, P_right, info = build_projectors(Q, env, alg.projector_alg) - - # Apply projectors and normalize - corners, edges = renormalize_corners_edges(state, env, Q, P_left, P_right) - - return CTMRGEnv(corners, edges), (; P_left, P_right, info...) -end - -# Compute enlarged corners and edges for all directions and unit cell entries -function enlarge_corners_edges(state, env::CTMRGEnv{C,T}) where {C,T} - Qtype = tensormaptype(spacetype(C), 3, 3, storagetype(C)) - Q = Zygote.Buffer(Array{Qtype,3}(undef, size(env.corners))) - drc_combinations = collect(Iterators.product(axes(env.corners)...)) - @fwdthreads for (dir, r, c) in drc_combinations - Q[dir, r, c] = if dir == NORTHWEST - northwest_corner((r, c), env, state) - elseif dir == NORTHEAST - northeast_corner((r, c), env, state) - elseif dir == SOUTHEAST - southeast_corner((r, c), env, state) - elseif dir == SOUTHWEST - southwest_corner((r, c), env, state) - end - end - - return copy(Q) -end - -# Build projectors from SVD and enlarged corners -function build_projectors(Q, env::CTMRGEnv{C,E}, alg::ProjectorAlg{A,T}) where {C,E,A,T} - P_left, P_right = Zygote.Buffer.(projector_type(env.edges)) - U, V = Zygote.Buffer.(projector_type(env.edges)) - Stype = tensormaptype(spacetype(C), 1, 1, Matrix{real(scalartype(E))}) # Corner type but with real numbers - S = Zygote.Buffer(Array{Stype,3}(undef, size(env.corners))) - ϵ = 0.0 - drc_combinations = collect(Iterators.product(axes(env.corners)...)) - @fwdthreads for (dir, r, c) in drc_combinations - # Row-column index of next enlarged corner - next_rc = if dir == 1 - (r, _next(c, size(env.corners, 3))) - elseif dir == 2 - (_next(r, size(env.corners, 2)), c) - elseif dir == 3 - (r, _prev(c, size(env.corners, 3))) - elseif dir == 4 - (_prev(r, size(env.corners, 2)), c) - end - - # SVD half-infinite environment - trscheme = if T <: FixedSpaceTruncation - truncspace(space(env.edges[dir, r, c], 1)) - else - alg.trscheme - end - svd_alg = if A <: SVDAdjoint{<:FixedSVD} - idx = (dir, r, c) - fwd_alg = alg.svd_alg.fwd_alg - fix_svd = FixedSVD(fwd_alg.U[idx...], fwd_alg.S[idx...], fwd_alg.V[idx...]) - SVDAdjoint(; fwd_alg=fix_svd, rrule_alg=alg.svd_alg.rrule_alg) - else - alg.svd_alg - end - @autoopt @tensor QQ[χ_in D_inabove D_inbelow; χ_out D_outabove D_outbelow] := - Q[dir, r, c][χ_in D_inabove D_inbelow; χ D1 D2] * - Q[_next(dir, 4), next_rc...][χ D1 D2; χ_out D_outabove D_outbelow] - U_local, S_local, V_local, ϵ_local = PEPSKit.tsvd!(QQ, svd_alg; trunc=trscheme) - U[dir, r, c] = U_local - S[dir, r, c] = S_local - V[dir, r, c] = V_local - ϵ = max(ϵ, ϵ_local / norm(S_local)) - - # Compute SVD truncation error and check for degenerate singular values - ignore_derivatives() do - if alg.verbosity > 0 && is_degenerate_spectrum(S_local) - svals = TensorKit.SectorDict(c => diag(b) for (c, b) in blocks(S_local)) - @warn("degenerate singular values detected: ", svals) - end - end - - # Compute projectors - Pl, Pr = build_projectors( - U_local, S_local, V_local, Q[dir, r, c], Q[_next(dir, 4), next_rc...] - ) - P_left[dir, r, c] = Pl - P_right[dir, r, c] = Pr - end - - return copy(P_left), copy(P_right), (; err=ϵ, U=copy(U), S=copy(S), V=copy(V)) -end - -# Apply projectors to renormalize corners and edges -function _contract_new_corner(P_right, Q, P_left) - return @autoopt @tensor corner[χ_in; χ_out] := - P_right[χ_in; χ1 D1 D2] * Q[χ1 D1 D2; χ2 D3 D4] * P_left[χ2 D3 D4; χ_out] -end -function renormalize_corners_edges(state, env::CTMRGEnv, Q, P_left, P_right) - corners = Zygote.Buffer(copy(env.corners)) - edges = Zygote.Buffer(copy(env.edges)) - rc_combinations = collect(Iterators.product(axes(state)...)) - @fwdthreads for (r, c) in rc_combinations - rprev = _prev(r, size(state, 1)) - rnext = _next(r, size(state, 1)) - cprev = _prev(c, size(state, 2)) - cnext = _next(c, size(state, 2)) - - corners[NORTHWEST, r, c] = _contract_new_corner( - P_right[WEST, rnext, c], Q[NORTHWEST, r, c], P_left[NORTH, r, c] - ) - corners[NORTHEAST, r, c] = _contract_new_corner( - P_right[NORTH, r, cprev], Q[NORTHEAST, r, c], P_left[EAST, r, c] - ) - corners[SOUTHEAST, r, c] = _contract_new_corner( - P_right[EAST, rprev, c], Q[SOUTHEAST, r, c], P_left[SOUTH, r, c] - ) - corners[SOUTHWEST, r, c] = _contract_new_corner( - P_right[SOUTH, r, cnext], Q[SOUTHWEST, r, c], P_left[WEST, r, c] - ) - - @autoopt @tensor edges[NORTH, r, c][χ_W D_Sab D_Sbe; χ_E] := - env.edges[NORTH, rprev, c][χ1 D1 D2; χ2] * - state[r, c][d; D1 D3 D_Sab D5] * - conj(state[r, c][d; D2 D4 D_Sbe D6]) * - P_left[NORTH, r, c][χ2 D3 D4; χ_E] * - P_right[NORTH, r, cprev][χ_W; χ1 D5 D6] - @autoopt @tensor edges[EAST, r, c][χ_N D_Wab D_Wbe; χ_S] := - env.edges[EAST, r, cnext][χ1 D1 D2; χ2] * - state[r, c][d; D5 D1 D3 D_Wab] * - conj(state[r, c][d; D6 D2 D4 D_Wbe]) * - P_left[EAST, r, c][χ2 D3 D4; χ_S] * - P_right[EAST, rprev, c][χ_N; χ1 D5 D6] - @autoopt @tensor edges[SOUTH, r, c][χ_E D_Nab D_Nbe; χ_W] := - env.edges[SOUTH, rnext, c][χ1 D1 D2; χ2] * - state[r, c][d; D_Nab D5 D1 D3] * - conj(state[r, c][d; D_Nbe D6 D2 D4]) * - P_left[SOUTH, r, c][χ2 D3 D4; χ_W] * - P_right[SOUTH, r, cnext][χ_E; χ1 D5 D6] - @autoopt @tensor edges[WEST, r, c][χ_S D_Eab D_Ebe; χ_N] := - env.edges[WEST, r, cprev][χ1 D1 D2; χ2] * - state[r, c][d; D3 D_Eab D5 D1] * - conj(state[r, c][d; D4 D_Ebe D6 D2]) * - P_left[WEST, r, c][χ2 D3 D4; χ_N] * - P_right[WEST, rnext, c][χ_S; χ1 D5 D6] - end - - corners = copy(corners) - edges = copy(edges) - @diffset corners[:, :, :] ./= norm.(corners[:, :, :]) - @diffset edges[:, :, :] ./= norm.(edges[:, :, :]) - return corners, edges -end diff --git a/src/algorithms/toolbox.jl b/src/algorithms/toolbox.jl new file mode 100644 index 00000000..7d7463d6 --- /dev/null +++ b/src/algorithms/toolbox.jl @@ -0,0 +1,96 @@ +function MPSKit.expectation_value(peps::InfinitePEPS, O::LocalOperator, envs::CTMRGEnv) + checklattice(peps, O) + return sum(O.terms) do (inds, operator) + contract_localoperator(inds, operator, peps, peps, envs) / + contract_localnorm(inds, peps, peps, envs) + end +end + +function costfun(peps::InfinitePEPS, envs::CTMRGEnv, O::LocalOperator) + E = MPSKit.expectation_value(peps, O, envs) + ignore_derivatives() do + isapprox(imag(E), 0; atol=sqrt(eps(real(E)))) || + @warn "Expectation value is not real: $E." + end + return real(E) +end + +function LinearAlgebra.norm(peps::InfinitePEPS, env::CTMRGEnv) + total = one(scalartype(peps)) + + for r in 1:size(peps, 1), c in 1:size(peps, 2) + rprev = _prev(r, size(peps, 1)) + rnext = _next(r, size(peps, 1)) + cprev = _prev(c, size(peps, 2)) + cnext = _next(c, size(peps, 2)) + total *= @autoopt @tensor env.edges[WEST, r, cprev][χ1 D1 D2; χ2] * + env.corners[NORTHWEST, rprev, cprev][χ2; χ3] * + env.edges[NORTH, rprev, c][χ3 D3 D4; χ4] * + env.corners[NORTHEAST, rprev, cnext][χ4; χ5] * + env.edges[EAST, r, cnext][χ5 D5 D6; χ6] * + env.corners[SOUTHEAST, rnext, cnext][χ6; χ7] * + env.edges[SOUTH, rnext, c][χ7 D7 D8; χ8] * + env.corners[SOUTHWEST, rnext, cprev][χ8; χ1] * + peps[r, c][d; D3 D5 D7 D1] * + conj(peps[r, c][d; D4 D6 D8 D2]) + total *= tr( + env.corners[NORTHWEST, rprev, cprev] * + env.corners[NORTHEAST, rprev, c] * + env.corners[SOUTHEAST, r, c] * + env.corners[SOUTHWEST, r, cprev], + ) + total /= @autoopt @tensor env.edges[WEST, r, cprev][χ1 D1 D2; χ2] * + env.corners[NORTHWEST, rprev, cprev][χ2; χ3] * + env.corners[NORTHEAST, rprev, c][χ3; χ4] * + env.edges[EAST, r, c][χ4 D1 D2; χ5] * + env.corners[SOUTHEAST, rnext, c][χ5; χ6] * + env.corners[SOUTHWEST, rnext, cprev][χ6; χ1] + total /= @autoopt @tensor env.corners[NORTHWEST, rprev, cprev][χ1; χ2] * + env.edges[NORTH, rprev, c][χ2 D1 D2; χ3] * + env.corners[NORTHEAST, rprev, cnext][χ3; χ4] * + env.corners[SOUTHEAST, r, cnext][χ4; χ5] * + env.edges[SOUTH, r, c][χ5 D1 D2; χ6] * + env.corners[SOUTHWEST, r, cprev][χ6; χ1] + end + + return total +end + +""" + correlation_length(peps::InfinitePEPS, env::CTMRGEnv; howmany=2) + +Compute the PEPS correlation length based on the horizontal and vertical +transfer matrices. Additionally the (normalized) eigenvalue spectrum is +returned. Specify the number of computed eigenvalues with `howmany`. +""" +function MPSKit.correlation_length(peps::InfinitePEPS, env::CTMRGEnv; num_vals=2) + T = scalartype(peps) + ξ_h = Vector{real(T)}(undef, size(peps, 1)) + ξ_v = Vector{real(T)}(undef, size(peps, 2)) + λ_h = Vector{Vector{T}}(undef, size(peps, 1)) + λ_v = Vector{Vector{T}}(undef, size(peps, 2)) + + # Horizontal + above_h = MPSMultiline(map(r -> InfiniteMPS(env.edges[1, r, :]), 1:size(peps, 1))) + respaced_edges_h = map(zip(space.(env.edges)[1, :, :], env.edges[3, :, :])) do (V1, T3) + return TensorMap(T3.data, V1) + end + below_h = MPSMultiline(map(r -> InfiniteMPS(respaced_edges_h[r, :]), 1:size(peps, 1))) + transfer_peps_h = TransferPEPSMultiline(peps, NORTH) + vals_h = MPSKit.transfer_spectrum(above_h, transfer_peps_h, below_h; num_vals) + λ_h = map(λ_row -> λ_row / abs(λ_row[1]), vals_h) # Normalize largest eigenvalue + ξ_h = map(λ_row -> -1 / log(abs(λ_row[2])), λ_h) + + # Vertical + above_v = MPSMultiline(map(c -> InfiniteMPS(env.edges[2, :, c]), 1:size(peps, 2))) + respaced_edges_v = map(zip(space.(env.edges)[2, :, :], env.edges[4, :, :])) do (V2, T4) + return TensorMap(T4.data, V2) + end + below_v = MPSMultiline(map(c -> InfiniteMPS(respaced_edges_v[:, c]), 1:size(peps, 2))) + transfer_peps_v = TransferPEPSMultiline(peps, EAST) + vals_v = MPSKit.transfer_spectrum(above_v, transfer_peps_v, below_v; num_vals) + λ_v = map(λ_row -> λ_row / abs(λ_row[1]), vals_v) # Normalize largest eigenvalue + ξ_v = map(λ_row -> -1 / log(abs(λ_row[2])), λ_v) + + return ξ_h, ξ_v, λ_h, λ_v +end diff --git a/src/environments/ctmrg environments.jl b/src/environments/ctmrg_environments.jl similarity index 100% rename from src/environments/ctmrg environments.jl rename to src/environments/ctmrg_environments.jl diff --git a/src/operators/localoperator.jl b/src/operators/localoperator.jl index 929f82b0..ffe9e34a 100644 --- a/src/operators/localoperator.jl +++ b/src/operators/localoperator.jl @@ -1,454 +1,3 @@ -# Contraction of local operators on arbitrary lattice locations -# ------------------------------------------------------------- -import MPSKit: tensorexpr - -# currently need this because MPSKit restricts tensor names to symbols -MPSKit.tensorexpr(ex::Expr, inds) = Expr(:ref, ex, inds...) -function MPSKit.tensorexpr(ex::Expr, indout, indin) - return Expr(:typed_vcat, ex, Expr(:row, indout...), Expr(:row, indin...)) -end - -""" - contract_localoperator(inds, O, peps, env) - -Contract a local operator `O` on the PEPS `peps` at the indices `inds` using the environment `env`. -""" -function contract_localoperator( - inds::NTuple{N,CartesianIndex{2}}, - O::AbstractTensorMap{S,N,N}, - ket::InfinitePEPS, - bra::InfinitePEPS, - env::CTMRGEnv, -) where {S,N} - static_inds = Val.(inds) - return _contract_localoperator(static_inds, O, ket, bra, env) -end -function contract_localoperator( - inds::NTuple{N,Tuple{Int,Int}}, - O::AbstractTensorMap{S,N,N}, - ket::InfinitePEPS, - bra::InfinitePEPS, - env::CTMRGEnv, -) where {S,N} - return contract_localoperator(CartesianIndex.(inds), O, ket, bra, env) -end - -# This implements the contraction of an operator acting on sites `inds`. -# The generated function ensures that we can use @tensor to write dynamic contractions (and maximize performance). -@generated function _contract_localoperator( - inds::NTuple{N,Val}, - O::AbstractTensorMap{S,N,N}, - ket::InfinitePEPS, - bra::InfinitePEPS, - env::CTMRGEnv, -) where {S,N} - cartesian_inds = collect(CartesianIndex{2}, map(x -> x.parameters[1], inds.parameters)) # weird hack to extract information from Val - if !allunique(cartesian_inds) - throw(ArgumentError("Indices should not overlap: $cartesian_inds.")) - end - - rmin, rmax = extrema(getindex.(cartesian_inds, 1)) - cmin, cmax = extrema(getindex.(cartesian_inds, 2)) - - gridsize = (rmax - rmin + 1, cmax - cmin + 1) - - corner_NW = tensorexpr( - :(env.corners[ - NORTHWEST, mod1($(rmin - 1), size(ket, 1)), mod1($(cmin - 1), size(ket, 2)) - ]), - (:C_NW_1,), - (:C_NW_2,), - ) - corner_NE = tensorexpr( - :(env.corners[ - NORTHEAST, mod1($(rmin - 1), size(ket, 1)), mod1($(cmax + 1), size(ket, 2)) - ]), - (:C_NE_1,), - (:C_NE_2,), - ) - corner_SE = tensorexpr( - :(env.corners[ - SOUTHEAST, mod1($(rmax + 1), size(ket, 1)), mod1($(cmax + 1), size(ket, 2)) - ]), - (:C_SE_1,), - (:C_SE_2,), - ) - corner_SW = tensorexpr( - :(env.corners[ - SOUTHWEST, mod1($(rmax + 1), size(ket, 1)), mod1($(cmin - 1), size(ket, 2)) - ]), - (:C_SW_1,), - (:C_SW_2,), - ) - - edges_N = map(1:gridsize[2]) do i - return tensorexpr( - :(env.edges[ - NORTH, - mod1($(rmin - 1), size(ket, 1)), - mod1($(cmin + i - 1), size(ket, 2)), - ]), - ( - (i == 1 ? :C_NW_2 : Symbol(:E_N_virtual, i - 1)), - Symbol(:E_N_top, i), - Symbol(:E_N_bot, i), - ), - ((i == gridsize[2] ? :C_NE_1 : Symbol(:E_N_virtual, i)),), - ) - end - - edges_E = map(1:gridsize[1]) do i - return tensorexpr( - :(env.edges[ - EAST, - mod1($(rmin + i - 1), size(ket, 1)), - mod1($(cmax + 1), size(ket, 2)), - ]), - ( - (i == 1 ? :C_NE_2 : Symbol(:E_E_virtual, i - 1)), - Symbol(:E_E_top, i), - Symbol(:E_E_bot, i), - ), - ((i == gridsize[1] ? :C_SE_1 : Symbol(:E_E_virtual, i)),), - ) - end - - edges_S = map(1:gridsize[2]) do i - return tensorexpr( - :(env.edges[ - SOUTH, - mod1($(rmax + 1), size(ket, 1)), - mod1($(cmin + i - 1), size(ket, 2)), - ]), - ( - (i == gridsize[2] ? :C_SE_2 : Symbol(:E_S_virtual, i)), - Symbol(:E_S_top, i), - Symbol(:E_S_bot, i), - ), - ((i == 1 ? :C_SW_1 : Symbol(:E_S_virtual, i - 1)),), - ) - end - - edges_W = map(1:gridsize[1]) do i - return tensorexpr( - :(env.edges[ - WEST, - mod1($(rmin + i - 1), size(ket, 1)), - mod1($(cmin - 1), size(ket, 2)), - ]), - ( - (i == gridsize[1] ? :C_SW_2 : Symbol(:E_W_virtual, i)), - Symbol(:E_W_top, i), - Symbol(:E_W_bot, i), - ), - ((i == 1 ? :C_NW_1 : Symbol(:E_W_virtual, i - 1)),), - ) - end - - operator = tensorexpr( - :O, ntuple(i -> Symbol(:O_out_, i), N), ntuple(i -> Symbol(:O_in_, i), N) - ) - - bra = map(Iterators.product(1:gridsize[1], 1:gridsize[2])) do (i, j) - inds_id = findfirst(==(CartesianIndex(rmin + i - 1, cmin + j - 1)), cartesian_inds) - physical_label = - isnothing(inds_id) ? Symbol(:physical, i, "_", j) : Symbol(:O_out_, inds_id) - return tensorexpr( - :(bra[ - mod1($(rmin + i - 1), size(bra, 1)), mod1($(cmin + j - 1), size(bra, 2)) - ]), - (physical_label,), - ( - (i == 1 ? Symbol(:E_N_bot, j) : Symbol(:bra_vertical, i - 1, "_", j)), - ( - if j == gridsize[2] - Symbol(:E_E_bot, i) - else - Symbol(:bra_horizontal, i, "_", j) - end - ), - ( - if i == gridsize[1] - Symbol(:E_S_bot, j) - else - Symbol(:bra_vertical, i, "_", j) - end - ), - (j == 1 ? Symbol(:E_W_bot, i) : Symbol(:bra_horizontal, i, "_", j - 1)), - ), - ) - end - - ket = map(Iterators.product(1:gridsize[1], 1:gridsize[2])) do (i, j) - inds_id = findfirst(==(CartesianIndex(rmin + i - 1, cmin + j - 1)), cartesian_inds) - physical_label = - isnothing(inds_id) ? Symbol(:physical, i, "_", j) : Symbol(:O_in_, inds_id) - return tensorexpr( - :(ket[ - mod1($(rmin + i - 1), size(ket, 1)), mod1($(cmin + j - 1), size(ket, 2)) - ]), - (physical_label,), - ( - (i == 1 ? Symbol(:E_N_top, j) : Symbol(:ket_vertical, i - 1, "_", j)), - ( - if j == gridsize[2] - Symbol(:E_E_top, i) - else - Symbol(:ket_horizontal, i, "_", j) - end - ), - ( - if i == gridsize[1] - Symbol(:E_S_top, j) - else - Symbol(:ket_vertical, i, "_", j) - end - ), - (j == 1 ? Symbol(:E_W_top, i) : Symbol(:ket_horizontal, i, "_", j - 1)), - ), - ) - end - - multiplication_ex = Expr( - :call, - :*, - corner_NW, - corner_NE, - corner_SE, - corner_SW, - edges_N..., - edges_E..., - edges_S..., - edges_W..., - ket..., - map(x -> Expr(:call, :conj, x), bra)..., - operator, - ) - - opt_ex = Expr(:tuple) - allinds = TensorOperations.getallindices(multiplication_ex) - for label in allinds - if startswith(String(label), "physical") || startswith(String(label), "O") - push!(opt_ex.args, :($label => $PEPS_PHYSICALDIM)) - elseif startswith(String(label), "ket") || startswith(String(label), "bra") - push!(opt_ex.args, :($label => $PEPS_BONDDIM)) - else - push!(opt_ex.args, :($label => $PEPS_ENVBONDDIM)) - end - end - - return quote - @tensor opt = $opt_ex $multiplication_ex - end -end - -""" - contract_localnorm(inds, peps, env) - -Contract a local norm of the PEPS `peps` around indices `inds`. -""" -function contract_localnorm( - inds::NTuple{N,CartesianIndex{2}}, ket::InfinitePEPS, bra::InfinitePEPS, env::CTMRGEnv -) where {N} - static_inds = Val.(inds) - return _contract_localnorm(static_inds, ket, bra, env) -end -function contract_localnorm( - inds::NTuple{N,Tuple{Int,Int}}, ket::InfinitePEPS, bra::InfinitePEPS, env::CTMRGEnv -) where {N} - return contract_localnorm(CartesianIndex.(inds), ket, bra, env) -end -@generated function _contract_localnorm( - inds::NTuple{N,Val}, ket::InfinitePEPS, bra::InfinitePEPS, env::CTMRGEnv -) where {N} - cartesian_inds = collect(CartesianIndex{2}, map(x -> x.parameters[1], inds.parameters)) # weird hack to extract information from Val - if !allunique(cartesian_inds) - throw(ArgumentError("Indices should not overlap: $cartesian_inds.")) - end - - rmin, rmax = extrema(getindex.(cartesian_inds, 1)) - cmin, cmax = extrema(getindex.(cartesian_inds, 2)) - - gridsize = (rmax - rmin + 1, cmax - cmin + 1) - - corner_NW = tensorexpr( - :(env.corners[ - NORTHWEST, mod1($(rmin - 1), size(ket, 1)), mod1($(cmin - 1), size(ket, 2)) - ]), - (:C_NW_1,), - (:C_NW_2,), - ) - corner_NE = tensorexpr( - :(env.corners[ - NORTHEAST, mod1($(rmin - 1), size(ket, 1)), mod1($(cmax + 1), size(ket, 2)) - ]), - (:C_NE_1,), - (:C_NE_2,), - ) - corner_SE = tensorexpr( - :(env.corners[ - SOUTHEAST, mod1($(rmax + 1), size(ket, 1)), mod1($(cmax + 1), size(ket, 2)) - ]), - (:C_SE_1,), - (:C_SE_2,), - ) - corner_SW = tensorexpr( - :(env.corners[ - SOUTHWEST, mod1($(rmax + 1), size(ket, 1)), mod1($(cmin - 1), size(ket, 2)) - ]), - (:C_SW_1,), - (:C_SW_2,), - ) - - edges_N = map(1:gridsize[2]) do i - return tensorexpr( - :(env.edges[ - NORTH, - mod1($(rmin - 1), size(ket, 1)), - mod1($(cmin + i - 1), size(ket, 2)), - ]), - ( - (i == 1 ? :C_NW_2 : Symbol(:E_N_virtual, i - 1)), - Symbol(:E_N_top, i), - Symbol(:E_N_bot, i), - ), - ((i == gridsize[2] ? :C_NE_1 : Symbol(:E_N_virtual, i)),), - ) - end - - edges_E = map(1:gridsize[1]) do i - return tensorexpr( - :(env.edges[ - EAST, - mod1($(rmin + i - 1), size(ket, 1)), - mod1($(cmax + 1), size(ket, 2)), - ]), - ( - (i == 1 ? :C_NE_2 : Symbol(:E_E_virtual, i - 1)), - Symbol(:E_E_top, i), - Symbol(:E_E_bot, i), - ), - ((i == gridsize[1] ? :C_SE_1 : Symbol(:E_E_virtual, i)),), - ) - end - - edges_S = map(1:gridsize[2]) do i - return tensorexpr( - :(env.edges[ - SOUTH, - mod1($(rmax + 1), size(ket, 1)), - mod1($(cmin + i - 1), size(ket, 2)), - ]), - ( - (i == gridsize[2] ? :C_SE_2 : Symbol(:E_S_virtual, i)), - Symbol(:E_S_top, i), - Symbol(:E_S_bot, i), - ), - ((i == 1 ? :C_SW_1 : Symbol(:E_S_virtual, i - 1)),), - ) - end - - edges_W = map(1:gridsize[1]) do i - return tensorexpr( - :(env.edges[ - WEST, - mod1($(rmin + i - 1), size(ket, 1)), - mod1($(cmin - 1), size(ket, 2)), - ]), - ( - (i == gridsize[1] ? :C_SW_2 : Symbol(:E_W_virtual, i)), - Symbol(:E_W_top, i), - Symbol(:E_W_bot, i), - ), - ((i == 1 ? :C_NW_1 : Symbol(:E_W_virtual, i - 1)),), - ) - end - - bra = map(Iterators.product(1:gridsize[1], 1:gridsize[2])) do (i, j) - return tensorexpr( - :(bra[ - mod1($(rmin + i - 1), size(ket, 1)), mod1($(cmin + j - 1), size(ket, 2)) - ]), - (Symbol(:physical, i, "_", j),), - ( - (i == 1 ? Symbol(:E_N_bot, j) : Symbol(:bra_vertical, i - 1, "_", j)), - ( - if j == gridsize[2] - Symbol(:E_E_bot, i) - else - Symbol(:bra_horizontal, i, "_", j) - end - ), - ( - if i == gridsize[1] - Symbol(:E_S_bot, j) - else - Symbol(:bra_vertical, i, "_", j) - end - ), - (j == 1 ? Symbol(:E_W_bot, i) : Symbol(:bra_horizontal, i, "_", j - 1)), - ), - ) - end - - ket = map(Iterators.product(1:gridsize[1], 1:gridsize[2])) do (i, j) - return tensorexpr( - :(ket[ - mod1($(rmin + i - 1), size(ket, 1)), mod1($(cmin + j - 1), size(ket, 2)) - ]), - (Symbol(:physical, i, "_", j),), - ( - (i == 1 ? Symbol(:E_N_top, j) : Symbol(:ket_vertical, i - 1, "_", j)), - ( - if j == gridsize[2] - Symbol(:E_E_top, i) - else - Symbol(:ket_horizontal, i, "_", j) - end - ), - ( - if i == gridsize[1] - Symbol(:E_S_top, j) - else - Symbol(:ket_vertical, i, "_", j) - end - ), - (j == 1 ? Symbol(:E_W_top, i) : Symbol(:ket_horizontal, i, "_", j - 1)), - ), - ) - end - - multiplication_ex = Expr( - :call, - :*, - corner_NW, - corner_NE, - corner_SE, - corner_SW, - edges_N..., - edges_E..., - edges_S..., - edges_W..., - ket..., - map(x -> Expr(:call, :conj, x), bra)..., - ) - - opt_ex = Expr(:tuple) - allinds = TensorOperations.getallindices(multiplication_ex) - for label in allinds - if startswith(String(label), "physical") - push!(opt_ex.args, :($label => $PEPS_PHYSICALDIM)) - elseif startswith(String(label), "ket") || startswith(String(label), "bra") - push!(opt_ex.args, :($label => $PEPS_BONDDIM)) - else - push!(opt_ex.args, :($label => $PEPS_ENVBONDDIM)) - end - end - - return quote - @tensor opt = $opt_ex $multiplication_ex - end -end # Hamiltonian consisting of local terms # ------------------------------------- @@ -508,20 +57,3 @@ function Base.repeat(O::LocalOperator, m::Int, n::Int) end return LocalOperator(lattice, terms...) end - -function MPSKit.expectation_value(peps::InfinitePEPS, O::LocalOperator, envs::CTMRGEnv) - checklattice(peps, O) - return sum(O.terms) do (inds, operator) - contract_localoperator(inds, operator, peps, peps, envs) / - contract_localnorm(inds, peps, peps, envs) - end -end - -function costfun(peps::InfinitePEPS, envs::CTMRGEnv, O::LocalOperator) - E = MPSKit.expectation_value(peps, O, envs) - ignore_derivatives() do - isapprox(imag(E), 0; atol=sqrt(eps(real(E)))) || - @warn "Expectation value is not real: $E." - end - return real(E) -end From 56f320401634278f40957e946b2757bca6d87a02 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Sun, 21 Jul 2024 12:28:14 +0200 Subject: [PATCH 064/213] Fix and format --- src/PEPSKit.jl | 7 ----- src/algorithms/ctmrg/ctmrg.jl | 57 +++++++++++++++++------------------ 2 files changed, 28 insertions(+), 36 deletions(-) diff --git a/src/PEPSKit.jl b/src/PEPSKit.jl index 2e55ee98..07a1d05d 100644 --- a/src/PEPSKit.jl +++ b/src/PEPSKit.jl @@ -17,11 +17,9 @@ include("utility/diffset.jl") include("utility/hook_pullback.jl") include("utility/autoopt.jl") - include("states/abstractpeps.jl") include("states/infinitepeps.jl") - include("operators/transferpeps.jl") include("operators/infinitepepo.jl") include("operators/transferpepo.jl") @@ -29,12 +27,10 @@ include("operators/derivatives.jl") include("operators/localoperator.jl") include("operators/models.jl") - include("environments/ctmrg_environments.jl") include("environments/transferpeps_environments.jl") include("environments/transferpepo_environments.jl") - include("algorithms/contractions/localoperator.jl") include("algorithms/ctmrg/ctmrg.jl") @@ -44,11 +40,8 @@ include("algorithms/toolbox.jl") include("algorithms/peps_opt.jl") - - # include("algorithms/ctmrg/ctmrg_all_sides.jl") - include("utility/symmetrization.jl") """ diff --git a/src/algorithms/ctmrg/ctmrg.jl b/src/algorithms/ctmrg/ctmrg.jl index 80dc8e65..586ad819 100644 --- a/src/algorithms/ctmrg/ctmrg.jl +++ b/src/algorithms/ctmrg/ctmrg.jl @@ -91,7 +91,6 @@ ctmrgscheme(::CTMRG{S}) where {S} = S const SequentialCTMRG = CTMRG{:sequential} const SimultaneousCTMRG = CTMRG{:simultaneous} - """ MPSKit.leading_boundary([envinit], state, alg::CTMRG) @@ -240,23 +239,25 @@ end function ctmrg_projectors( enlarged_envs, envs::CTMRGEnv{C,E}, alg::SequentialCTMRG ) where {C,E} - projector_alg = alg.projector_alg + projector_alg = alg.projector_alg # pre-allocation - P_bottom, P_top = Zygote.Buffer.(projector_type(envs.edges)) + Prtype = tensormaptype(spacetype(E), numin(E), numout(E), storagetype(E)) + P_bottom = Zygote.Buffer(envs.edges, axes(envs.corners, 2), axes(envs.corners, 3)) + P_top = Zygote.Buffer(envs.edges, Prtype, axes(envs.corners, 2), axes(envs.corners, 3)) ϵ = 0.0 - + directions = collect(Iterators.product(axes(envs.corners, 2), axes(envs.corners, 3))) @fwdthreads for (r, c) in directions # SVD half-infinite environment @autoopt @tensor QQ[χ_EB D_EBabove D_EBbelow; χ_ET D_ETabove D_ETbelow] := enlarged_envs[2][r, c][χ_EB D_EBabove D_EBbelow; χ D1 D2] * enlarged_envs[1][r, c][χ D1 D2; χ_ET D_ETabove D_ETbelow] - - trscheme = truncation_scheme(projector_alg, envs.edges[dir, r, c]) - svd_alg = svd_algorithm(projector_alg, (dir, r, c)) + + trscheme = truncation_scheme(projector_alg, envs.edges[WEST, r, c]) + svd_alg = svd_algorithm(projector_alg, (WEST, r, c)) U, S, V, ϵ_local = PEPSKit.tsvd!(QQ, svd_alg; trunc=trscheme) ϵ = max(ϵ, ϵ_local / norm(S)) - + # Compute SVD truncation error and check for degenerate singular values ignore_derivatives() do if alg.verbosity > 0 && is_degenerate_spectrum(S) @@ -264,17 +265,13 @@ function ctmrg_projectors( @warn("degenerate singular values detected: ", svals) end end - + # Compute projectors P_bottom[r, c], P_top[r, c] = build_projectors( - U, - S, - V, - enlarged_envs[2][r, c], - enlarged_envs[1][r, c], + U, S, V, enlarged_envs[2][r, c], enlarged_envs[1][r, c] ) end - + return (copy(P_bottom), copy(P_top)), (; err=ϵ) end function ctmrg_projectors( @@ -347,22 +344,24 @@ Apply projectors to renormalize corners and edges. function ctmrg_renormalize(enlarged_envs, projectors, state, envs, alg::SequentialCTMRG) corners = Zygote.Buffer(envs.corners) edges = Zygote.Buffer(envs.edges) - + # copy environments that do not participate for dir in (NORTHEAST, SOUTHEAST) for r in axes(envs.corners, 2), c in axes(envs.corners, 3) corners[dir, r, c] = envs.corners[dir, r, c] end end - for r in axes(envs.corners, 2), c in axes(envs.corners, 3) - edges[EAST, r, c] = envs.edges[EAST, r, c] + for dir in (NORTH, EAST, SOUTH) + for r in axes(envs.corners, 2), c in axes(envs.corners, 3) + edges[dir, r, c] = envs.edges[dir, r, c] + end end - + # Apply projectors to renormalize corners and edges coordinates = collect(Iterators.product(axes(state)...)) @fwdthreads for (r, c) in coordinates r′ = _prev(r, size(state, 1)) - c′ = _prev(col, size(state, 2)) + c′ = _prev(c, size(state, 2)) C_sw, C_nw, T_w = grow_env_left( state[r, c], projectors[1][r′, c], @@ -377,14 +376,14 @@ function ctmrg_renormalize(enlarged_envs, projectors, state, envs, alg::Sequenti corners[NORTHWEST, r, c] = C_nw / norm(C_nw) edges[WEST, r, c] = T_w / norm(T_w) end - + return CTMRGEnv(copy(corners), copy(edges)) end function ctmrg_renormalize(enlarged_envs, projectors, state, envs, alg::SimultaneousCTMRG) corners = Zygote.Buffer(envs.corners) edges = Zygote.Buffer(envs.edges) P_left, P_right = projectors - + coordinates = collect(Iterators.product(axes(state)...)) @fwdthreads for (r, c) in coordinates rprev = _prev(r, size(state, 1)) @@ -396,17 +395,17 @@ function ctmrg_renormalize(enlarged_envs, projectors, state, envs, alg::Simultan P_right[WEST, rnext, c], enlarged_envs[NORTHWEST, r, c], P_left[NORTH, r, c] ) corners[NORTHWEST, r, c] = C_northwest / norm(C_northwest) - + C_northeast = _contract_new_corner( P_right[NORTH, r, cprev], enlarged_envs[NORTHEAST, r, c], P_left[EAST, r, c] ) corners[NORTHEAST, r, c] = C_northeast / norm(C_northeast) - + C_southeast = _contract_new_corner( P_right[EAST, rprev, c], enlarged_envs[SOUTHEAST, r, c], P_left[SOUTH, r, c] ) corners[SOUTHEAST, r, c] = C_southeast / norm(C_southeast) - + C_southwest = _contract_new_corner( P_right[SOUTH, r, cnext], enlarged_envs[SOUTHWEST, r, c], P_left[WEST, r, c] ) @@ -419,7 +418,7 @@ function ctmrg_renormalize(enlarged_envs, projectors, state, envs, alg::Simultan P_left[NORTH, r, c][χ2 D3 D4; χ_E] * P_right[NORTH, r, cprev][χ_W; χ1 D5 D6] edges[NORTH, r, c] = E_north / norm(E_north) - + @autoopt @tensor E_east[χ_N D_Wab D_Wbe; χ_S] := envs.edges[EAST, r, cnext][χ1 D1 D2; χ2] * state[r, c][d; D5 D1 D3 D_Wab] * @@ -427,7 +426,7 @@ function ctmrg_renormalize(enlarged_envs, projectors, state, envs, alg::Simultan P_left[EAST, r, c][χ2 D3 D4; χ_S] * P_right[EAST, rprev, c][χ_N; χ1 D5 D6] edges[EAST, r, c] = E_east / norm(E_east) - + @autoopt @tensor E_south[χ_E D_Nab D_Nbe; χ_W] := envs.edges[SOUTH, rnext, c][χ1 D1 D2; χ2] * state[r, c][d; D_Nab D5 D1 D3] * @@ -435,7 +434,7 @@ function ctmrg_renormalize(enlarged_envs, projectors, state, envs, alg::Simultan P_left[SOUTH, r, c][χ2 D3 D4; χ_W] * P_right[SOUTH, r, cnext][χ_E; χ1 D5 D6] edges[SOUTH, r, c] = E_south / norm(E_south) - + @autoopt @tensor E_west[χ_S D_Eab D_Ebe; χ_N] := envs.edges[WEST, r, cprev][χ1 D1 D2; χ2] * state[r, c][d; D3 D_Eab D5 D1] * @@ -444,7 +443,7 @@ function ctmrg_renormalize(enlarged_envs, projectors, state, envs, alg::Simultan P_right[WEST, rnext, c][χ_S; χ1 D5 D6] edges[WEST, r, c] = E_west / norm(E_west) end - + return CTMRGEnv(copy(corners), copy(edges)) end From 30fe9d8ed7ef526b1353aebb82ee0bb367f50de4 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Mon, 22 Jul 2024 10:19:26 +0200 Subject: [PATCH 065/213] === symbol comparison --- src/algorithms/peps_opt.jl | 4 ++-- src/utility/diffset.jl | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/algorithms/peps_opt.jl b/src/algorithms/peps_opt.jl index c1359a73..2d6c93ff 100644 --- a/src/algorithms/peps_opt.jl +++ b/src/algorithms/peps_opt.jl @@ -101,10 +101,10 @@ struct PEPSOptimize{G} gradient_alg::G, ) where {S,G} if gradient_alg isa GradMode - if S == :sequential && iterscheme(gradient_alg) == :fixed + if S === :sequential && iterscheme(gradient_alg) === :fixed throw(ArgumentError(":sequential and :fixed are not compatible")) elseif boundary_alg.projector_alg.svd_alg.fwd_alg isa IterSVD && - iterscheme(gradient_alg) == :fixed + iterscheme(gradient_alg) === :fixed throw(ArgumentError("IterSVD and :fixed are currently not compatible")) end end diff --git a/src/utility/diffset.jl b/src/utility/diffset.jl index 80fc14cf..b093fa84 100644 --- a/src/utility/diffset.jl +++ b/src/utility/diffset.jl @@ -14,7 +14,7 @@ parse_ex(ex) = ex function parse_ex(ex::Expr) oppheads = (:(./=), :(.*=), :(.+=), :(.-=)) opprep = (:(./), :(.*), :(.+), :(.-)) - if ex.head == :macrocall + if ex.head=== :macrocall parse_ex(macroexpand(PEPSKit, ex)) elseif ex.head in (:(.=), :(=)) && length(ex.args) == 2 && is_indexing(ex.args[1]) lhs = ex.args[1] @@ -44,4 +44,4 @@ function parse_ex(ex::Expr) end is_indexing(ex) = false -is_indexing(ex::Expr) = ex.head == :ref +is_indexing(ex::Expr) = ex.head=== :ref From 55e01e62ce8d9c605223625458367b3466640547 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Mon, 22 Jul 2024 10:24:58 +0200 Subject: [PATCH 066/213] improve type stability --- src/algorithms/ctmrg/ctmrg.jl | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/src/algorithms/ctmrg/ctmrg.jl b/src/algorithms/ctmrg/ctmrg.jl index 586ad819..06355b2c 100644 --- a/src/algorithms/ctmrg/ctmrg.jl +++ b/src/algorithms/ctmrg/ctmrg.jl @@ -144,15 +144,13 @@ function MPSKit.leading_boundary(envinit, state, alg::CTMRG) end """ - ctmrg_iter(state, env::CTMRGEnv, alg::CTMRG{:sequential}) - -Perform one iteration of CTMRG that maps the `state` and `env` to a new environment, -and also return the truncation error. -One CTMRG iteration consists of four `left_move` calls and 90 degree rotations, -such that the environment is grown and renormalized in all four directions. + ctmrg_iter(state, envs::CTMRGEnv, alg::CTMRG) -> envs′, info + +Perform one iteration of CTMRG that maps the `state` and `envs` to a new environment, +and also returns the truncation error. """ function ctmrg_iter(state, envs::CTMRGEnv, alg::SequentialCTMRG) - ϵ = 0.0 + ϵ = zero(real(scalartype(state))) for _ in 1:4 # left move enlarged_envs = ctmrg_expand(state, envs, alg) @@ -244,7 +242,7 @@ function ctmrg_projectors( Prtype = tensormaptype(spacetype(E), numin(E), numout(E), storagetype(E)) P_bottom = Zygote.Buffer(envs.edges, axes(envs.corners, 2), axes(envs.corners, 3)) P_top = Zygote.Buffer(envs.edges, Prtype, axes(envs.corners, 2), axes(envs.corners, 3)) - ϵ = 0.0 + ϵ = zero(real(scalartype(envs))) directions = collect(Iterators.product(axes(envs.corners, 2), axes(envs.corners, 3))) @fwdthreads for (r, c) in directions @@ -284,7 +282,7 @@ function ctmrg_projectors( # Corner type but with real numbers S = Zygote.Buffer(U.data, tensormaptype(spacetype(C), 1, 1, real(scalartype(E)))) - ϵ = 0.0 + ϵ = zero(real(scalartype(envs))) drc_combinations = collect(Iterators.product(axes(envs.corners)...)) @fwdthreads for (dir, r, c) in drc_combinations # Row-column index of next enlarged corner @@ -341,7 +339,7 @@ end Apply projectors to renormalize corners and edges. """ -function ctmrg_renormalize(enlarged_envs, projectors, state, envs, alg::SequentialCTMRG) +function ctmrg_renormalize(enlarged_envs, projectors, state, envs, ::SequentialCTMRG) corners = Zygote.Buffer(envs.corners) edges = Zygote.Buffer(envs.edges) @@ -379,7 +377,7 @@ function ctmrg_renormalize(enlarged_envs, projectors, state, envs, alg::Sequenti return CTMRGEnv(copy(corners), copy(edges)) end -function ctmrg_renormalize(enlarged_envs, projectors, state, envs, alg::SimultaneousCTMRG) +function ctmrg_renormalize(enlarged_envs, projectors, state, envs, ::SimultaneousCTMRG) corners = Zygote.Buffer(envs.corners) edges = Zygote.Buffer(envs.edges) P_left, P_right = projectors From 20fe3f666ca715a337d3bc53f1839e7b87900242 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Mon, 22 Jul 2024 11:45:07 +0200 Subject: [PATCH 067/213] Start working on separating contractions --- src/PEPSKit.jl | 1 + .../contractions/ctmrg_contractions.jl | 168 ++++++++++++++++++ src/algorithms/ctmrg/ctmrg.jl | 55 ++---- 3 files changed, 181 insertions(+), 43 deletions(-) create mode 100644 src/algorithms/contractions/ctmrg_contractions.jl diff --git a/src/PEPSKit.jl b/src/PEPSKit.jl index 07a1d05d..0843a81e 100644 --- a/src/PEPSKit.jl +++ b/src/PEPSKit.jl @@ -32,6 +32,7 @@ include("environments/transferpeps_environments.jl") include("environments/transferpepo_environments.jl") include("algorithms/contractions/localoperator.jl") +include("algorithms/contractions/ctmrg_contractions.jl") include("algorithms/ctmrg/ctmrg.jl") include("algorithms/ctmrg/gaugefix.jl") diff --git a/src/algorithms/contractions/ctmrg_contractions.jl b/src/algorithms/contractions/ctmrg_contractions.jl new file mode 100644 index 00000000..792d5404 --- /dev/null +++ b/src/algorithms/contractions/ctmrg_contractions.jl @@ -0,0 +1,168 @@ +const CTMRGEdgeTensor{S} = AbstractTensorMap{S,3,1} +const CTMRGCornerTensor{S} = AbstractTensorMap{S,1,1} + +# Enlarged corner contractions +# ---------------------------- + +""" + enlarge_northwest_corner((row, col), envs, ket, bra) + enlarge_northwest_corner(E_west, C_northwest, E_north, ket, bra) + +Contract the enlarged northwest corner of the CTMRG environment, either by specifying the +coordinates, environments and state, or by directly providing the tensors. + +``` + C_northwest -- E_north -- + | || + E_west == ket-bra == + | || +``` +""" +function enlarge_northwest_corner( + (row, col), envs::CTMRGEnv, ket::InfinitePEPS, bra::InfinitePEPS=ket +) + E_west = envs.edges[WEST, row, _prev(col, end)] + C_northwest = envs.corners[NORTHWEST, _prev(row, end), _prev(col, end)] + E_north = envs.edges[NORTH, _prev(row, end), col] + return enlarge_northwest_corner( + E_west, C_northwest, E_north, ket[row, col], bra[row, col] + ) +end +function enlarge_northwest_corner( + E_west::CTMRGEdgeTensor, + C_northwest::CTMRGCornerTensor, + E_north::CTMRGEdgeTensor, + ket::PEPSTensor, + bra::PEPSTensor=ket, +) + return @autoopt @tensor corner[χ_S D_Sabove D_Sbelow; χ_E D_Eabove D_Ebelow] := + E_west[χ_S D1 D2; χ1] * + C_northwest[χ1; χ2] * + E_north[χ2 D3 D4; χ_E] * + ket[d; D3 D_Eabove D_Sabove D1] * + conj(bra[d; D4 D_Ebelow D_Sbelow D2]) +end + +""" + enlarge_northeast_corner((row, col), envs, ket, bra) + enlarge_northeast_corner(E_north, C_northeast, E_east, ket, bra) + +Contract the enlarged northeast corner of the CTMRG environment, either by specifying the +coordinates, environments and state, or by directly providing the tensors. + +``` + -- E_north -- C_northeast + || | + == ket-bra == E_east + || | +``` +""" +function enlarge_northeast_corner( + (row, col), envs::CTMRGEnv, ket::InfinitePEPS, bra::InfinitePEPS=ket +) + E_north = envs.edges[NORTH, _prev(row, end), col] + C_northeast = envs.corners[NORTHEAST, _prev(row, end), _next(col, end)] + E_east = envs.edges[EAST, row, _next(col, end)] + return enlarge_northeast_corner( + E_north, C_northeast, E_east, ket[row, col], bra[row, col] + ) +end +function enlarge_northeast_corner( + E_north::CTMRGEdgeTensor, + C_northeast::CTMRGCornerTensor, + E_east::CTMRGEdgeTensor, + ket::PEPSTensor, + bra::PEPSTensor=ket, +) + return @autoopt @tensor corner[χ_W D_Wabove D_Wbelow; χ_S D_Sabove D_Sbelow] := + E_north[χ_W D1 D2; χ1] * + C_northeast[χ1; χ2] * + E_east[χ2 D3 D4; χ_S] * + ket[d; D1 D3 D_Sabove D_Wabove] * + conj(bra[d; D2 D4 D_Sbelow D_Wbelow]) +end + +# TODO: also bring other corners in same form +function southeast_corner((row, col), env, peps_above, peps_below=peps_above) + return @autoopt @tensor corner[χ_N D_Nabove D_Nbelow; χ_W D_Wabove D_Wbelow] := + env.edges[EAST, row, _next(col, end)][χ_N D1 D2; χ1] * + env.corners[SOUTHEAST, _next(row, end), _next(col, end)][χ1; χ2] * + env.edges[SOUTH, _next(row, end), col][χ2 D3 D4; χ_W] * + peps_above[row, col][d; D_Nabove D1 D3 D_Wabove] * + conj(peps_below[row, col][d; D_Nbelow D2 D4 D_Wbelow]) +end +function southwest_corner((row, col), env, peps_above, peps_below=peps_above) + return @autoopt @tensor corner[χ_E D_Eabove D_Ebelow; χ_N D_Nabove D_Nbelow] := + env.edges[SOUTH, _next(row, end), col][χ_E D1 D2; χ1] * + env.corners[SOUTHWEST, _next(row, end), _prev(col, end)][χ1; χ2] * + env.edges[WEST, row, _prev(col, end)][χ2 D3 D4; χ_N] * + peps_above[row, col][d; D_Nabove D_Eabove D1 D3] * + conj(peps_below[row, col][d; D_Nbelow D_Ebelow D2 D4]) +end + +# Projector contractions +# ---------------------- + +function halfinfinite_environment(quadrant1::AbstractTensorMap{S,3,3}, quadrant2::AbstractTensorMap{S,3,3}) + return @autoopt @tensor half[χ_in D_inabove D_inbelow; χ_out D_outabove D_outbelow] := + quadrant1[χ_in D_inabove D_inbelow; χ D1 D2] * + quadrant2[χ D1 D2; χ_out D_outabove D_outbelow] +end + +# Renormalization contractions +# ---------------------------- + +# corner normalizations are the same contractions everywhere +function renormalize_corner(quadrant, P_left, P_right) + return @autoopt @tensor corner[χ_in; χ_out] := + P_right[χ_in; χ1 D1 D2] * quadrant[χ1 D1 D2; χ2 D3 D4] * P_left[χ2 D3 D4; χ_out] +end +function rightrenormalize_corner(C, E, P) + return @autoopt @tensor corner[χ_in; χ_out] := + E[χ_in D1 D2; χ1] * C[χ1; χ2] * P[χ2 D1 D2; χ_out] +end +function leftrenormalize_corner(C, E, P) + return @autoopt @tensor corner[χ_in; χ_out] := + P[χ_in D1 D2; χ1] * C[χ1; χ2] * E[χ2 D1 D2; χ_out] +end + +""" + renormalize_west_edge((row, col), envs, P_top, P_bottom, ket, bra) + renormalize_west_edge(E_west, P_top, P_bottom, ket, bra) + +Absorb a bra-ket pair into the west edge using the given projectors and environment tensors. + +``` + TODO: diagram +``` +""" +function renormalize_west_edge((row, col), envs, P_top, P_bottom, ket, bra) + return renormalize_west_edge(envs.edges[WEST, row, _prev(col, end)], + P_top[WEST, row, col], P_bottom[WEST, _next(row, end), col], + ket[row, col], bra[row, col]) +end +function renormalize_west_edge(E_west, P_top, P_bottom, ket, bra) + return @autoopt @tensor edge[χ_S D_Eab D_Ebe; χ_N] := + E_west[χ1 D1 D2; χ2] * + ket[d; D3 D_Eab D5 D1] * + conj(bra[d; D4 D_Ebe D6 D2]) * + P_bottom[χ2 D3 D4; χ_N] * + P_top[χ_S; χ1 D5 D6] +end + +# TODO: docstring +function renormalize_north_edge((row, col), envs, P_left, P_right, ket, bra) + return renormalize_north_edge(envs.edges[NORTH, _prev(row, end), col], + P_left[NORTH, row, col], P_right[NORTH, row, _prev(col, end)], + ket[row, col], bra[row, col]) +end +function renormalize_north_edge(E_north, P_left, P_right, ket, bra) + return @autoopt @tensor edge[χ_W D_Eab D_Ebe; χ_E] := + E_north[χ1 D1 D2; χ2] * + ket[d; D3 D_Eab D5 D1] * + conj(bra[d; D4 D_Ebe D6 D2]) * + P_right[χ2 D3 D4; χ_E] * + P_left[χ_W; χ1 D5 D6] +end + +# TODO: add other contractions diff --git a/src/algorithms/ctmrg/ctmrg.jl b/src/algorithms/ctmrg/ctmrg.jl index 06355b2c..0e83258e 100644 --- a/src/algorithms/ctmrg/ctmrg.jl +++ b/src/algorithms/ctmrg/ctmrg.jl @@ -202,7 +202,7 @@ function ctmrg_expand(state, envs::CTMRGEnv{C,T}, ::SequentialCTMRG) where {C,T} directions = collect(Iterators.product(axes(state)...)) @fwdthreads for (r, c) in directions r′ = _next(r, size(state, 1)) - Q_nw[r, c] = northwest_corner((r, c), envs, state) + Q_nw[r, c] = enlarge_northwest_corner((r, c), envs, state) Q_sw[r, c] = southwest_corner((r′, c), envs, state) end @@ -214,9 +214,9 @@ function ctmrg_expand(state, envs::CTMRGEnv{C,T}, ::SimultaneousCTMRG) where {C, drc_combinations = collect(Iterators.product(axes(envs.corners)...)) @fwdthreads for (dir, r, c) in drc_combinations Q[dir, r, c] = if dir == NORTHWEST - northwest_corner((r, c), envs, state) + enlarge_northwest_corner((r, c), envs, state) elseif dir == NORTHEAST - northeast_corner((r, c), envs, state) + enlarge_northeast_corner((r, c), envs, state) elseif dir == SOUTHEAST southeast_corner((r, c), envs, state) elseif dir == SOUTHWEST @@ -247,9 +247,7 @@ function ctmrg_projectors( directions = collect(Iterators.product(axes(envs.corners, 2), axes(envs.corners, 3))) @fwdthreads for (r, c) in directions # SVD half-infinite environment - @autoopt @tensor QQ[χ_EB D_EBabove D_EBbelow; χ_ET D_ETabove D_ETbelow] := - enlarged_envs[2][r, c][χ_EB D_EBabove D_EBbelow; χ D1 D2] * - enlarged_envs[1][r, c][χ D1 D2; χ_ET D_ETabove D_ETbelow] + QQ = halfinfinite_environment(enlarged_envs[2][r, c], enlarged_envs[1][r, c]) trscheme = truncation_scheme(projector_alg, envs.edges[WEST, r, c]) svd_alg = svd_algorithm(projector_alg, (WEST, r, c)) @@ -297,9 +295,9 @@ function ctmrg_projectors( end # SVD half-infinite environment - @autoopt @tensor QQ[χ_in D_inabove D_inbelow; χ_out D_outabove D_outbelow] := - enlarged_envs[dir, r, c][χ_in D_inabove D_inbelow; χ D1 D2] * - enlarged_envs[_next(dir, 4), next_rc...][χ D1 D2; χ_out D_outabove D_outbelow] + QQ = halfinfinite_environment( + enlarged_envs[dir, r, c], enlarged_envs[_next(dir, 4), next_rc...] + ) trscheme = truncation_scheme(projector_alg, envs.edges[dir, r, c]) svd_alg = svd_algorithm(projector_alg, (dir, r, c)) @@ -360,6 +358,7 @@ function ctmrg_renormalize(enlarged_envs, projectors, state, envs, ::SequentialC @fwdthreads for (r, c) in coordinates r′ = _prev(r, size(state, 1)) c′ = _prev(c, size(state, 2)) + # TODO: switch this to use contractions C_sw, C_nw, T_w = grow_env_left( state[r, c], projectors[1][r′, c], @@ -389,6 +388,7 @@ function ctmrg_renormalize(enlarged_envs, projectors, state, envs, ::Simultaneou cprev = _prev(c, size(state, 2)) cnext = _next(c, size(state, 2)) + # TODO: switch this to use contractions C_northwest = _contract_new_corner( P_right[WEST, rnext, c], enlarged_envs[NORTHWEST, r, c], P_left[NORTH, r, c] ) @@ -441,6 +441,8 @@ function ctmrg_renormalize(enlarged_envs, projectors, state, envs, ::Simultaneou P_right[WEST, rnext, c][χ_S; χ1 D5 D6] edges[WEST, r, c] = E_west / norm(E_west) end + + # TODO: here we do not normalize, is this on purpose? return CTMRGEnv(copy(corners), copy(edges)) end @@ -469,40 +471,6 @@ function enlarge_corners_edges(state, env::CTMRGEnv{C,T}) where {C,T} return copy(Q) end -# Enlarged corner contractions (need direction specific methods to avoid PEPS rotations) -function northwest_corner((row, col), env, peps_above, peps_below=peps_above) - return @autoopt @tensor corner[χ_S D_Sabove D_Sbelow; χ_E D_Eabove D_Ebelow] := - env.edges[WEST, row, _prev(col, end)][χ_S D1 D2; χ1] * - env.corners[NORTHWEST, _prev(row, end), _prev(col, end)][χ1; χ2] * - env.edges[NORTH, _prev(row, end), col][χ2 D3 D4; χ_E] * - peps_above[row, col][d; D3 D_Eabove D_Sabove D1] * - conj(peps_below[row, col][d; D4 D_Ebelow D_Sbelow D2]) -end -function northeast_corner((row, col), env, peps_above, peps_below=peps_above) - return @autoopt @tensor corner[χ_W D_Wabove D_Wbelow; χ_S D_Sabove D_Sbelow] := - env.edges[NORTH, _prev(row, end), col][χ_W D1 D2; χ1] * - env.corners[NORTHEAST, _prev(row, end), _next(col, end)][χ1; χ2] * - env.edges[EAST, row, _next(col, end)][χ2 D3 D4; χ_S] * - peps_above[row, col][d; D1 D3 D_Sabove D_Wabove] * - conj(peps_below[row, col][d; D2 D4 D_Sbelow D_Wbelow]) -end -function southeast_corner((row, col), env, peps_above, peps_below=peps_above) - return @autoopt @tensor corner[χ_N D_Nabove D_Nbelow; χ_W D_Wabove D_Wbelow] := - env.edges[EAST, row, _next(col, end)][χ_N D1 D2; χ1] * - env.corners[SOUTHEAST, _next(row, end), _next(col, end)][χ1; χ2] * - env.edges[SOUTH, _next(row, end), col][χ2 D3 D4; χ_W] * - peps_above[row, col][d; D_Nabove D1 D3 D_Wabove] * - conj(peps_below[row, col][d; D_Nbelow D2 D4 D_Wbelow]) -end -function southwest_corner((row, col), env, peps_above, peps_below=peps_above) - return @autoopt @tensor corner[χ_E D_Eabove D_Ebelow; χ_N D_Nabove D_Nbelow] := - env.edges[SOUTH, _next(row, end), col][χ_E D1 D2; χ1] * - env.corners[SOUTHWEST, _next(row, end), _prev(col, end)][χ1; χ2] * - env.edges[WEST, row, _prev(col, end)][χ2 D3 D4; χ_N] * - peps_above[row, col][d; D_Nabove D_Eabove D1 D3] * - conj(peps_below[row, col][d; D_Nbelow D_Ebelow D2 D4]) -end - function _contract_new_corner(P_right, Q, P_left) return @autoopt @tensor corner[χ_in; χ_out] := P_right[χ_in; χ1 D1 D2] * Q[χ1 D1 D2; χ2 D3 D4] * P_left[χ2 D3 D4; χ_out] @@ -522,6 +490,7 @@ end function grow_env_left( peps, P_bottom, P_top, corners_SW, corners_NW, edge_S, edge_W, edge_N ) + # TODO: switch to use contractions: renormalize_west_edge, leftrenornalize... @autoopt @tensor corner_SW′[χ_E; χ_N] := corners_SW[χ1; χ2] * edge_S[χ_E D1 D2; χ1] * P_bottom[χ2 D1 D2; χ_N] @autoopt @tensor corner_NW′[χ_S; χ_E] := From b8fbebb9d7c0d8405cb4db2b11abf3ac79587aba Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Mon, 22 Jul 2024 18:18:24 +0200 Subject: [PATCH 068/213] Move out contractions from ctmrg.jl and gaugefix.jl, add docstrings --- .../contractions/ctmrg_contractions.jl | 164 +++++++++++++++--- src/algorithms/ctmrg/ctmrg.jl | 98 +++-------- src/algorithms/ctmrg/gaugefix.jl | 24 +-- 3 files changed, 177 insertions(+), 109 deletions(-) diff --git a/src/algorithms/contractions/ctmrg_contractions.jl b/src/algorithms/contractions/ctmrg_contractions.jl index 792d5404..0d85c874 100644 --- a/src/algorithms/contractions/ctmrg_contractions.jl +++ b/src/algorithms/contractions/ctmrg_contractions.jl @@ -103,10 +103,12 @@ end # Projector contractions # ---------------------- -function halfinfinite_environment(quadrant1::AbstractTensorMap{S,3,3}, quadrant2::AbstractTensorMap{S,3,3}) +function halfinfinite_environment( + quadrant1::AbstractTensorMap{S,3,3}, quadrant2::AbstractTensorMap{S,3,3} +) return @autoopt @tensor half[χ_in D_inabove D_inbelow; χ_out D_outabove D_outbelow] := - quadrant1[χ_in D_inabove D_inbelow; χ D1 D2] * - quadrant2[χ D1 D2; χ_out D_outabove D_outbelow] + quadrant1[χ_in D_inabove D_inbelow; χ D1 D2] * + quadrant2[χ D1 D2; χ_out D_outabove D_outbelow] end # Renormalization contractions @@ -126,6 +128,102 @@ function leftrenormalize_corner(C, E, P) P[χ_in D1 D2; χ1] * C[χ1; χ2] * E[χ2 D1 D2; χ_out] end +""" + renormalize_north_edge((row, col), envs, P_left, P_right, ket, bra) + renormalize_north_edge(E_north, P_left, P_right, ket, bra) + +Absorb a bra-ket pair into the north edge using the given projectors and environment tensors. + +``` + |~~~~~~~| -- E_north -- |~~~~~~~| + -- |P_right| || |P_right| -- + |~~~~~~~| == ket-bra == |~~~~~~~| + +``` +""" +function renormalize_north_edge((row, col), envs, P_left, P_right, ket, bra) + return renormalize_north_edge( + envs.edges[NORTH, _prev(row, end), col], + P_left[NORTH, row, col], + P_right[NORTH, row, _prev(col, end)], + ket[row, col], + bra[row, col], + ) +end +function renormalize_north_edge(E_north, P_left, P_right, ket, bra) + return @autoopt @tensor edge[χ_W D_Eab D_Ebe; χ_E] := + E_north[χ1 D1 D2; χ2] * + ket[d; D3 D_Eab D5 D1] * + conj(bra[d; D4 D_Ebe D6 D2]) * + P_right[χ2 D3 D4; χ_E] * + P_left[χ_W; χ1 D5 D6] +end + +""" + renormalize_east_edge((row, col), envs, P_top, P_bottom, ket, bra) + renormalize_east_edge(E_east, P_top, P_bottom, ket, bra) + +Absorb a bra-ket pair into the east edge using the given projectors and environment tensors. + +``` + | + [~~P_bottom~~] + | || + E_east == ket-bra + | || + [~~~~P_top~~~] + | +``` +""" +function renormalize_east_edge((row, col), envs, P_top, P_bottom, ket, bra) + return renormalize_east_edge( + envs.edges[EAST, row, _next(col, end)], + P_top[EAST, row, col], + P_bottom[EAST, _prev(row, end), col, end], + ket[row, col], + bra[row, col], + ) +end +function renormalize_east_edge(E_east, P_top, P_bottom, ket, bra) + return @autoopt @tensor edge[χ_N D_Wab D_Wbe; χ_S] := + E_east[χ1 D1 D2; χ2] * + ket[d; D5 D1 D3 D_Wab] * + conj(bra[d; D6 D2 D4 D_Wbe]) * + P_top[χ2 D3 D4; χ_S] * + P_bottom[χ_N; χ1 D5 D6] +end + +""" + renormalize_south_edge((row, col), envs, P_left, P_right, ket, bra) + renormalize_south_edge(E_south, P_left, P_right, ket, bra) + +Absorb a bra-ket pair into the south edge using the given projectors and environment tensors. + +``` + |~~~~~~~| == ket-bra == |~~~~~~| + -- |P_right| || |P_left| -- + |~~~~~~~| -- E_south -- |~~~~~~| + +``` +""" +function renormalize_south_edge((row, col), envs, P_left, P_right, ket, bra) + return renormalize_north_edge( + envs.edges[SOUTH, _next(row, end), col], + P_left[NORTH, row, col], + P_right[SOUTH, row, _next(col, end)], + ket[row, col], + bra[row, col], + ) +end +function renormalize_south_edge(E_south, P_left, P_right, ket, bra) + return @autoopt @tensor edge[χ_E D_Nab D_Nbe; χ_W] := + E_south[χ1 D1 D2; χ2] * + bra[d; D_Nab D5 D1 D3] * + conj(ket[d; D_Nbe D6 D2 D4]) * + P_left[χ2 D3 D4; χ_W] * + P_right[χ_E; χ1 D5 D6] +end + """ renormalize_west_edge((row, col), envs, P_top, P_bottom, ket, bra) renormalize_west_edge(E_west, P_top, P_bottom, ket, bra) @@ -133,13 +231,23 @@ end Absorb a bra-ket pair into the west edge using the given projectors and environment tensors. ``` - TODO: diagram + | + [~~P_bottom~~] + | || + E_west == ket-bra + | || + [~~~~P_top~~~] + | ``` """ function renormalize_west_edge((row, col), envs, P_top, P_bottom, ket, bra) - return renormalize_west_edge(envs.edges[WEST, row, _prev(col, end)], - P_top[WEST, row, col], P_bottom[WEST, _next(row, end), col], - ket[row, col], bra[row, col]) + return renormalize_west_edge( + envs.edges[WEST, row, _prev(col, end)], + P_top[WEST, row, col], + P_bottom[WEST, _next(row, end), col], + ket[row, col], + bra[row, col], + ) end function renormalize_west_edge(E_west, P_top, P_bottom, ket, bra) return @autoopt @tensor edge[χ_S D_Eab D_Ebe; χ_N] := @@ -150,19 +258,33 @@ function renormalize_west_edge(E_west, P_top, P_bottom, ket, bra) P_top[χ_S; χ1 D5 D6] end -# TODO: docstring -function renormalize_north_edge((row, col), envs, P_left, P_right, ket, bra) - return renormalize_north_edge(envs.edges[NORTH, _prev(row, end), col], - P_left[NORTH, row, col], P_right[NORTH, row, _prev(col, end)], - ket[row, col], bra[row, col]) -end -function renormalize_north_edge(E_north, P_left, P_right, ket, bra) - return @autoopt @tensor edge[χ_W D_Eab D_Ebe; χ_E] := - E_north[χ1 D1 D2; χ2] * - ket[d; D3 D_Eab D5 D1] * - conj(bra[d; D4 D_Ebe D6 D2]) * - P_right[χ2 D3 D4; χ_E] * - P_left[χ_W; χ1 D5 D6] +""" + contract_gauge_corner(corner, σ_in, σ_out) + +Multiplication of the corner tensor with incoming and outgoing gauge signs. + +``` + corner -- σ_out -- + | + σ_in + | +``` +""" +function contract_gauge_corner(corner, σ_in, σ_out) + @autoopt @tensor corner_fix[χ_in; χ_out] := + σ_in[χ_in; χ1] * corner[χ1; χ2] * conj(σ_out[χ_out; χ2]) end -# TODO: add other contractions +""" + contract_gauge_edge(edge, σ_in, σ_out) + +Multiplication of the edge tensor with incoming and outgoing gauge signs. + +``` + -- σ_in -- edge -- σ_out -- +``` +""" +function contract_gauge_edge(edge, σ_in, σ_out) + @autoopt @tensor edge_fix[χ_in D_above D_below; χ_out] := + σ_in[χ_in; χ1] * edge[χ1 D_above D_below; χ2] * conj(σ_out[χ_out; χ2]) +end diff --git a/src/algorithms/ctmrg/ctmrg.jl b/src/algorithms/ctmrg/ctmrg.jl index 0e83258e..8367bcf6 100644 --- a/src/algorithms/ctmrg/ctmrg.jl +++ b/src/algorithms/ctmrg/ctmrg.jl @@ -356,22 +356,22 @@ function ctmrg_renormalize(enlarged_envs, projectors, state, envs, ::SequentialC # Apply projectors to renormalize corners and edges coordinates = collect(Iterators.product(axes(state)...)) @fwdthreads for (r, c) in coordinates - r′ = _prev(r, size(state, 1)) c′ = _prev(c, size(state, 2)) - # TODO: switch this to use contractions - C_sw, C_nw, T_w = grow_env_left( - state[r, c], - projectors[1][r′, c], - projectors[2][r, c], - envs.corners[SOUTHWEST, r, c′], - envs.corners[NORTHWEST, r, c′], - envs.edges[SOUTH, r, c], - envs.edges[WEST, r, c′], - envs.edges[NORTH, r, c], + + C_southwest = rightrenormalize_corner( + envs.corners[SOUTHWEST, r, c′], envs.edges[SOUTH, r, c], projectors[2][r, c] + ) + corners[SOUTHWEST, r, c] = C_southwest / norm(C_southwest) + + C_northwest = leftrenormalize_corner( + envs.corners[NORTHWEST, r, c′], envs.edges[NORTH, r, c], projectors[1][r, c] + ) + corners[NORTHWEST, r, c] = C_northwest / norm(C_northwest) + + E_west = renormalize_west_edge( + (r, c), envs, projectors[1], projectors[2], state, state ) - corners[SOUTHWEST, r, c] = C_sw / norm(C_sw) - corners[NORTHWEST, r, c] = C_nw / norm(C_nw) - edges[WEST, r, c] = T_w / norm(T_w) + edges[WEST, r, c] = E_west / norm(E_west) end return CTMRGEnv(copy(corners), copy(edges)) @@ -388,61 +388,38 @@ function ctmrg_renormalize(enlarged_envs, projectors, state, envs, ::Simultaneou cprev = _prev(c, size(state, 2)) cnext = _next(c, size(state, 2)) - # TODO: switch this to use contractions - C_northwest = _contract_new_corner( - P_right[WEST, rnext, c], enlarged_envs[NORTHWEST, r, c], P_left[NORTH, r, c] + C_northwest = renormalize_corner( + enlarged_envs[NORTHWEST, r, c], P_left[NORTH, r, c], P_right[WEST, rnext, c] ) corners[NORTHWEST, r, c] = C_northwest / norm(C_northwest) - C_northeast = _contract_new_corner( - P_right[NORTH, r, cprev], enlarged_envs[NORTHEAST, r, c], P_left[EAST, r, c] + C_northeast = renormalize_corner( + enlarged_envs[NORTHEAST, r, c], P_left[EAST, r, c], P_right[NORTH, r, cprev] ) corners[NORTHEAST, r, c] = C_northeast / norm(C_northeast) - C_southeast = _contract_new_corner( - P_right[EAST, rprev, c], enlarged_envs[SOUTHEAST, r, c], P_left[SOUTH, r, c] + C_southeast = renormalize_corner( + enlarged_envs[SOUTHEAST, r, c], P_left[SOUTH, r, c], P_right[EAST, rprev, c] ) corners[SOUTHEAST, r, c] = C_southeast / norm(C_southeast) - C_southwest = _contract_new_corner( - P_right[SOUTH, r, cnext], enlarged_envs[SOUTHWEST, r, c], P_left[WEST, r, c] + C_southwest = renormalize_corner( + enlarged_envs[SOUTHWEST, r, c], P_left[WEST, r, c], P_right[SOUTH, r, cnext] ) corners[SOUTHWEST, r, c] = C_southwest / norm(C_southwest) - @autoopt @tensor E_north[χ_W D_Sab D_Sbe; χ_E] := - envs.edges[NORTH, rprev, c][χ1 D1 D2; χ2] * - state[r, c][d; D1 D3 D_Sab D5] * - conj(state[r, c][d; D2 D4 D_Sbe D6]) * - P_left[NORTH, r, c][χ2 D3 D4; χ_E] * - P_right[NORTH, r, cprev][χ_W; χ1 D5 D6] + E_north = renormalize_north_edge((r, c), envs, P_left, P_right, state, state) edges[NORTH, r, c] = E_north / norm(E_north) - @autoopt @tensor E_east[χ_N D_Wab D_Wbe; χ_S] := - envs.edges[EAST, r, cnext][χ1 D1 D2; χ2] * - state[r, c][d; D5 D1 D3 D_Wab] * - conj(state[r, c][d; D6 D2 D4 D_Wbe]) * - P_left[EAST, r, c][χ2 D3 D4; χ_S] * - P_right[EAST, rprev, c][χ_N; χ1 D5 D6] + E_east = renormalize_east_edge((r, c), envs, P_left, P_right, state, state) edges[EAST, r, c] = E_east / norm(E_east) - @autoopt @tensor E_south[χ_E D_Nab D_Nbe; χ_W] := - envs.edges[SOUTH, rnext, c][χ1 D1 D2; χ2] * - state[r, c][d; D_Nab D5 D1 D3] * - conj(state[r, c][d; D_Nbe D6 D2 D4]) * - P_left[SOUTH, r, c][χ2 D3 D4; χ_W] * - P_right[SOUTH, r, cnext][χ_E; χ1 D5 D6] + E_south = renormalize_south_edge((r, c), envs, P_left, P_right, state, state) edges[SOUTH, r, c] = E_south / norm(E_south) - @autoopt @tensor E_west[χ_S D_Eab D_Ebe; χ_N] := - envs.edges[WEST, r, cprev][χ1 D1 D2; χ2] * - state[r, c][d; D3 D_Eab D5 D1] * - conj(state[r, c][d; D4 D_Ebe D6 D2]) * - P_left[WEST, r, c][χ2 D3 D4; χ_N] * - P_right[WEST, rnext, c][χ_S; χ1 D5 D6] + E_west = renormalize_west_edge((r, c), envs, P_left, P_right, state, state) edges[WEST, r, c] = E_west / norm(E_west) end - - # TODO: here we do not normalize, is this on purpose? return CTMRGEnv(copy(corners), copy(edges)) end @@ -471,11 +448,6 @@ function enlarge_corners_edges(state, env::CTMRGEnv{C,T}) where {C,T} return copy(Q) end -function _contract_new_corner(P_right, Q, P_left) - return @autoopt @tensor corner[χ_in; χ_out] := - P_right[χ_in; χ1 D1 D2] * Q[χ1 D1 D2; χ2 D3 D4] * P_left[χ2 D3 D4; χ_out] -end - # Build projectors from SVD and enlarged SW & NW corners function build_projectors( U::AbstractTensorMap{E,3,1}, S, V::AbstractTensorMap{E,1,3}, Q, Q_next @@ -485,21 +457,3 @@ function build_projectors( P_right = isqS * U' * Q return P_left, P_right end - -# Apply projectors to entire left half-environment to grow SW & NW corners, and W edge -function grow_env_left( - peps, P_bottom, P_top, corners_SW, corners_NW, edge_S, edge_W, edge_N -) - # TODO: switch to use contractions: renormalize_west_edge, leftrenornalize... - @autoopt @tensor corner_SW′[χ_E; χ_N] := - corners_SW[χ1; χ2] * edge_S[χ_E D1 D2; χ1] * P_bottom[χ2 D1 D2; χ_N] - @autoopt @tensor corner_NW′[χ_S; χ_E] := - corners_NW[χ1; χ2] * edge_N[χ2 D1 D2; χ_E] * P_top[χ_S; χ1 D1 D2] - @autoopt @tensor edge_W′[χ_S D_Eabove D_Ebelow; χ_N] := - edge_W[χ1 D1 D2; χ2] * - peps[d; D3 D_Eabove D5 D1] * - conj(peps[d; D4 D_Ebelow D6 D2]) * - P_bottom[χ2 D3 D4; χ_N] * - P_top[χ_S; χ1 D5 D6] - return corner_SW′, corner_NW′, edge_W′ -end diff --git a/src/algorithms/ctmrg/gaugefix.jl b/src/algorithms/ctmrg/gaugefix.jl index 5879e7f9..33a02671 100644 --- a/src/algorithms/ctmrg/gaugefix.jl +++ b/src/algorithms/ctmrg/gaugefix.jl @@ -69,64 +69,56 @@ function transfermatrix_fixedpoint(tops, bottoms, ρinit) end # Explicit fixing of relative phases (doing this compactly in a loop is annoying) -function _contract_gauge_corner(corner, σ_in, σ_out) - @autoopt @tensor corner_fix[χ_in; χ_out] := - σ_in[χ_in; χ1] * corner[χ1; χ2] * conj(σ_out[χ_out; χ2]) -end -function _contract_gauge_edge(edge, σ_in, σ_out) - @autoopt @tensor edge_fix[χ_in D_above D_below; χ_out] := - σ_in[χ_in; χ1] * edge[χ1 D_above D_below; χ2] * conj(σ_out[χ_out; χ2]) -end function fix_relative_phases(envfinal::CTMRGEnv, signs) C1 = map(Iterators.product(axes(envfinal.corners)[2:3]...)) do (r, c) - _contract_gauge_corner( + contract_gauge_corner( envfinal.corners[NORTHWEST, r, c], signs[WEST, r, c], signs[NORTH, r, _next(c, end)], ) end T1 = map(Iterators.product(axes(envfinal.edges)[2:3]...)) do (r, c) - _contract_gauge_edge( + contract_gauge_edge( envfinal.edges[NORTH, r, c], signs[NORTH, r, c], signs[NORTH, r, _next(c, end)], ) end C2 = map(Iterators.product(axes(envfinal.corners)[2:3]...)) do (r, c) - _contract_gauge_corner( + contract_gauge_corner( envfinal.corners[NORTHEAST, r, c], signs[NORTH, r, c], signs[EAST, _next(r, end), c], ) end T2 = map(Iterators.product(axes(envfinal.edges)[2:3]...)) do (r, c) - _contract_gauge_edge( + contract_gauge_edge( envfinal.edges[EAST, r, c], signs[EAST, r, c], signs[EAST, _next(r, end), c] ) end C3 = map(Iterators.product(axes(envfinal.corners)[2:3]...)) do (r, c) - _contract_gauge_corner( + contract_gauge_corner( envfinal.corners[SOUTHEAST, r, c], signs[EAST, r, c], signs[SOUTH, r, _prev(c, end)], ) end T3 = map(Iterators.product(axes(envfinal.edges)[2:3]...)) do (r, c) - _contract_gauge_edge( + contract_gauge_edge( envfinal.edges[SOUTH, r, c], signs[SOUTH, r, c], signs[SOUTH, r, _prev(c, end)], ) end C4 = map(Iterators.product(axes(envfinal.corners)[2:3]...)) do (r, c) - _contract_gauge_corner( + contract_gauge_corner( envfinal.corners[SOUTHWEST, r, c], signs[SOUTH, r, c], signs[WEST, _prev(r, end), c], ) end T4 = map(Iterators.product(axes(envfinal.edges)[2:3]...)) do (r, c) - _contract_gauge_edge( + contract_gauge_edge( envfinal.edges[WEST, r, c], signs[WEST, r, c], signs[WEST, _prev(r, end), c] ) end From 23f22c238d1bee35c44da84830830983ff24c0c6 Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Tue, 23 Jul 2024 16:06:51 +0200 Subject: [PATCH 069/213] Add docstrings, add type annotations, add more row/column based methods --- .../contractions/ctmrg_contractions.jl | 280 ++++++++++++++++-- src/algorithms/ctmrg/ctmrg.jl | 8 +- src/algorithms/ctmrg/gaugefix.jl | 73 ++--- 3 files changed, 277 insertions(+), 84 deletions(-) diff --git a/src/algorithms/contractions/ctmrg_contractions.jl b/src/algorithms/contractions/ctmrg_contractions.jl index 0d85c874..bab231e5 100644 --- a/src/algorithms/contractions/ctmrg_contractions.jl +++ b/src/algorithms/contractions/ctmrg_contractions.jl @@ -82,27 +82,99 @@ function enlarge_northeast_corner( conj(bra[d; D2 D4 D_Sbelow D_Wbelow]) end -# TODO: also bring other corners in same form -function southeast_corner((row, col), env, peps_above, peps_below=peps_above) +""" + enlarge_southeast_corner((row, col), envs, ket, bra) + enlarge_southeast_corner(E_east, C_southeast, E_south, ket, bra) + +Contract the enlarged southeast corner of the CTMRG environment, either by specifying the +coordinates, environments and state, or by directly providing the tensors. + +``` + || | + == ket-bra == E_east + || | + -- E_south -- C_southeast +``` +""" +function enlarge_southeast_corner( + (row, col), envs::CTMRGEnv, ket::InfinitePEPS, bra::InfinitePEPS=ket +) + E_east = env.edges[EAST, row, _next(col, end)] + C_southeast = env.corners[SOUTHEAST, _next(row, end), _next(col, end)] + E_south = env.edges[SOUTH, _next(row, end), col] + return enlarge_southeast_corner( + E_east, C_southeast, E_south, ket[row, col], bra[row, col] + ) +end +function enlarge_southeast_corner( + E_east::CTMRGEdgeTensor, + C_southeast::CTMRGCornerTensor, + E_south::CTMRGEdgeTensor, + ket::PEPSTensor, + bra::PEPSTensor=ket, +) return @autoopt @tensor corner[χ_N D_Nabove D_Nbelow; χ_W D_Wabove D_Wbelow] := - env.edges[EAST, row, _next(col, end)][χ_N D1 D2; χ1] * - env.corners[SOUTHEAST, _next(row, end), _next(col, end)][χ1; χ2] * - env.edges[SOUTH, _next(row, end), col][χ2 D3 D4; χ_W] * - peps_above[row, col][d; D_Nabove D1 D3 D_Wabove] * - conj(peps_below[row, col][d; D_Nbelow D2 D4 D_Wbelow]) + E_east[χ_N D1 D2; χ1] * + C_southeast[χ1; χ2] * + E_south[χ2 D3 D4; χ_W] * + ket[d; D_Nabove D1 D3 D_Wabove] * + conj(bra[d; D_Nbelow D2 D4 D_Wbelow]) +end + +""" + enlarge_southwest_corner((row, col), envs, ket, bra) + enlarge_southwest_corner(E_south, C_southwest, E_west, ket, bra) + +Contract the enlarged southwest corner of the CTMRG environment, either by specifying the +coordinates, environments and state, or by directly providing the tensors. + +``` + | || + E_west == ket-bra == + | || + C_southwest -- E_south -- +``` +""" +function enlarge_southwest_corner( + (row, col), envs::CTMRGEnv, ket::InfinitePEPS, bra::InfinitePEPS=ket +) + E_south = env.edges[SOUTH, _next(row, end), col] + C_southwest = env.corners[SOUTHWEST, _next(row, end), _prev(col, end)] + E_west = env.edges[WEST, row, _prev(col, end)] + return enlarge_southwest_corner( + E_south, C_southwest, E_west, ket[row, col], bra[row, col] + ) end -function southwest_corner((row, col), env, peps_above, peps_below=peps_above) +function enlarge_southwest_corner( + E_south::CTMRGEdgeTensor, + C_southwest::CTMRGCornerTensor, + E_west::CTMRGEdgeTensor, + ket::PEPSTensor, + bra::PEPSTensor=ket, +) return @autoopt @tensor corner[χ_E D_Eabove D_Ebelow; χ_N D_Nabove D_Nbelow] := - env.edges[SOUTH, _next(row, end), col][χ_E D1 D2; χ1] * - env.corners[SOUTHWEST, _next(row, end), _prev(col, end)][χ1; χ2] * - env.edges[WEST, row, _prev(col, end)][χ2 D3 D4; χ_N] * - peps_above[row, col][d; D_Nabove D_Eabove D1 D3] * - conj(peps_below[row, col][d; D_Nbelow D_Ebelow D2 D4]) + E_south[χ_E D1 D2; χ1] * + C_southwest[χ1; χ2] * + E_west[χ2 D3 D4; χ_N] * + ket[d; D_Nabove D_Eabove D1 D3] * + conj(bra[d; D_Nbelow D_Ebelow D2 D4]) end # Projector contractions # ---------------------- +""" + halfinfinite_environment(quadrant1::AbstractTensorMap{S,3,3}, quadrant2::AbstractTensorMap{S,3,3}) + +Contract two quadrants (enlarged corners) to form a half-infinite environment. + +``` + |~~~~~~~~~| -- |~~~~~~~~~| + |quadrant1| |quadrant2| + |~~~~~~~~~| == |~~~~~~~~~| + | || || | +``` +""" function halfinfinite_environment( quadrant1::AbstractTensorMap{S,3,3}, quadrant2::AbstractTensorMap{S,3,3} ) @@ -114,16 +186,55 @@ end # Renormalization contractions # ---------------------------- -# corner normalizations are the same contractions everywhere -function renormalize_corner(quadrant, P_left, P_right) +""" + renormalize_corner(quadrant, P_left, P_right) + +Apply projectors to each side of a quadrant. + +``` + |~~~~~~~~| -- |~~~~~~| + |quadrant| |P_left| -- + |~~~~~~~~| == |~~~~~~| + | || + [P_right] + | +``` +""" +function renormalize_corner(quadrant::AbstractTensorMap{S,3,3}, P_left, P_right) where {S} return @autoopt @tensor corner[χ_in; χ_out] := P_right[χ_in; χ1 D1 D2] * quadrant[χ1 D1 D2; χ2 D3 D4] * P_left[χ2 D3 D4; χ_out] end -function rightrenormalize_corner(C, E, P) + +""" + rightrenormalize_corner(C, E, P) + +Apply outgoing projector to a corner and edge. +``` + out + | + [~~P~~] + | || + C -- E -- in +``` +""" +function rightrenormalize_corner(C::CTMRGCornerTensor, E::CTMRGEdgeTensor, P) return @autoopt @tensor corner[χ_in; χ_out] := E[χ_in D1 D2; χ1] * C[χ1; χ2] * P[χ2 D1 D2; χ_out] end -function leftrenormalize_corner(C, E, P) + +""" + leftrenormalize_corner(C, E, P) + +Apply ingoing projector to a corner and edge. +``` + C -- E -- out + | || + [~~P~~] + | + in +``` +""" +function leftrenormalize_corner(C::CTMRGCornerTensor, E::CTMRGEdgeTensor, P) return @autoopt @tensor corner[χ_in; χ_out] := P[χ_in D1 D2; χ1] * C[χ1; χ2] * E[χ2 D1 D2; χ_out] end @@ -141,7 +252,9 @@ Absorb a bra-ket pair into the north edge using the given projectors and environ ``` """ -function renormalize_north_edge((row, col), envs, P_left, P_right, ket, bra) +function renormalize_north_edge( + (row, col), envs::CTMRGEnv, P_left, P_right, ket::InfinitePEPS, bra::InfinitePEPS=ket +) return renormalize_north_edge( envs.edges[NORTH, _prev(row, end), col], P_left[NORTH, row, col], @@ -150,7 +263,9 @@ function renormalize_north_edge((row, col), envs, P_left, P_right, ket, bra) bra[row, col], ) end -function renormalize_north_edge(E_north, P_left, P_right, ket, bra) +function renormalize_north_edge( + E_north::CTMRGEdgeTensor, P_left, P_right, ket::InfinitePEPS, bra::InfinitePEPS=ket +) return @autoopt @tensor edge[χ_W D_Eab D_Ebe; χ_E] := E_north[χ1 D1 D2; χ2] * ket[d; D3 D_Eab D5 D1] * @@ -175,7 +290,9 @@ Absorb a bra-ket pair into the east edge using the given projectors and environm | ``` """ -function renormalize_east_edge((row, col), envs, P_top, P_bottom, ket, bra) +function renormalize_east_edge( + (row, col), envs::CTMRGEnv, P_top, P_bottom, ket::InfinitePEPS, bra::InfinitePEPS=ket +) return renormalize_east_edge( envs.edges[EAST, row, _next(col, end)], P_top[EAST, row, col], @@ -184,7 +301,9 @@ function renormalize_east_edge((row, col), envs, P_top, P_bottom, ket, bra) bra[row, col], ) end -function renormalize_east_edge(E_east, P_top, P_bottom, ket, bra) +function renormalize_east_edge( + E_east::CTMRGEdgeTensor, P_top, P_bottom, ket::InfinitePEPS, bra::InfinitePEPS=ket +) return @autoopt @tensor edge[χ_N D_Wab D_Wbe; χ_S] := E_east[χ1 D1 D2; χ2] * ket[d; D5 D1 D3 D_Wab] * @@ -206,7 +325,9 @@ Absorb a bra-ket pair into the south edge using the given projectors and environ ``` """ -function renormalize_south_edge((row, col), envs, P_left, P_right, ket, bra) +function renormalize_south_edge( + (row, col), envs::CTMRGEnv, P_left, P_right, ket::InfinitePEPS, bra::InfinitePEPS=ket +) return renormalize_north_edge( envs.edges[SOUTH, _next(row, end), col], P_left[NORTH, row, col], @@ -215,7 +336,9 @@ function renormalize_south_edge((row, col), envs, P_left, P_right, ket, bra) bra[row, col], ) end -function renormalize_south_edge(E_south, P_left, P_right, ket, bra) +function renormalize_south_edge( + E_south::CTMRGEdgeTensor, P_left, P_right, ket::InfinitePEPS, bra::InfinitePEPS=ket +) return @autoopt @tensor edge[χ_E D_Nab D_Nbe; χ_W] := E_south[χ1 D1 D2; χ2] * bra[d; D_Nab D5 D1 D3] * @@ -240,7 +363,9 @@ Absorb a bra-ket pair into the west edge using the given projectors and environm | ``` """ -function renormalize_west_edge((row, col), envs, P_top, P_bottom, ket, bra) +function renormalize_west_edge( + (row, col), envs::CTMRGEnv, P_top, P_bottom, ket::InfinitePEPS, bra::InfinitePEPS=ket +) return renormalize_west_edge( envs.edges[WEST, row, _prev(col, end)], P_top[WEST, row, col], @@ -249,7 +374,9 @@ function renormalize_west_edge((row, col), envs, P_top, P_bottom, ket, bra) bra[row, col], ) end -function renormalize_west_edge(E_west, P_top, P_bottom, ket, bra) +function renormalize_west_edge( + E_west::CTMRGEdgeTensor, P_top, P_bottom, ket::InfinitePEPS, bra::InfinitePEPS=ket +) return @autoopt @tensor edge[χ_S D_Eab D_Ebe; χ_N] := E_west[χ1 D1 D2; χ2] * ket[d; D3 D_Eab D5 D1] * @@ -258,8 +385,11 @@ function renormalize_west_edge(E_west, P_top, P_bottom, ket, bra) P_top[χ_S; χ1 D5 D6] end +# Gauge fixing contractions +# ------------------------- + """ - contract_gauge_corner(corner, σ_in, σ_out) + fix_gauge_corner(corner, σ_in, σ_out) Multiplication of the corner tensor with incoming and outgoing gauge signs. @@ -270,13 +400,61 @@ Multiplication of the corner tensor with incoming and outgoing gauge signs. | ``` """ -function contract_gauge_corner(corner, σ_in, σ_out) +function fix_gauge_corner( + corner::CTMRGCornerTensor, σ_in::CTMRGCornerTensor, σ_out::CTMRGCornerTensor +) @autoopt @tensor corner_fix[χ_in; χ_out] := σ_in[χ_in; χ1] * corner[χ1; χ2] * conj(σ_out[χ_out; χ2]) end """ - contract_gauge_edge(edge, σ_in, σ_out) + fix_gauge_northwest_corner((row, col), envs, signs) + +Application of `fix_gauge_corner` to the northwest corner with appropriate row and column indices. +""" +function fix_gauge_northwest_corner((row, col), envs::CTMRGEnv, signs) + return fix_gauge_corner( + envs.corners[NORTHWEST, row, col], + signs[WEST, row, col], + signs[NORTH, row, _next(col, end)], + ) +end + +""" + fix_gauge_northeast_corner((row, col), envs, signs) + +Application of `fix_gauge_corner` to the northeast corner with appropriate row and column indices. +""" +function fix_gauge_northeast_corner((row, col), envs::CTMRGEnv, signs) + return fix_gauge_corner( + envs.corners[NORTHEAST, r, c], signs[NORTH, r, c], signs[EAST, _next(r, end), c] + ) +end + +""" + fix_gauge_southeast_corner((row, col), envs, signs) + +Application of `fix_gauge_corner` to the southeast corner with appropriate row and column indices. +""" +function fix_gauge_southeast_corner((row, col), envs::CTMRGEnv, signs) + return fix_gauge_corner( + envs.corners[SOUTHEAST, r, c], signs[EAST, r, c], signs[SOUTH, r, _prev(c, end)] + ) +end + +""" + fix_gauge_southwest_corner((row, col), envs, signs) + +Application of `fix_gauge_corner` to the southwest corner with appropriate row and column indices. +""" +function fix_gauge_southwest_corner((row, col), envs::CTMRGEnv, signs) + return fix_gauge_corner( + envs.corners[SOUTHWEST, r, c], signs[SOUTH, r, c], signs[WEST, _prev(r, end), c] + ) +end + +""" + fix_gauge_edge(edge, σ_in, σ_out) Multiplication of the edge tensor with incoming and outgoing gauge signs. @@ -284,7 +462,51 @@ Multiplication of the edge tensor with incoming and outgoing gauge signs. -- σ_in -- edge -- σ_out -- ``` """ -function contract_gauge_edge(edge, σ_in, σ_out) +function fix_gauge_edge(edge::CTMRGEdgeTensor, σ_in::CTMRGCornerTensor, σ_out::CTMRGCornerTensor) @autoopt @tensor edge_fix[χ_in D_above D_below; χ_out] := σ_in[χ_in; χ1] * edge[χ1 D_above D_below; χ2] * conj(σ_out[χ_out; χ2]) end + +""" + fix_gauge_north_edge((row, col), envs, signs) + +Application of `fix_gauge_edge` to the north edge with appropriate row and column indices. +""" +function fix_gauge_north_edge((row, col), envs::CTMRGEnv, signs) + return fix_gauge_edge( + envs.edges[NORTH, r, c], signs[NORTH, r, c], signs[NORTH, r, _next(c, end)] + ) +end + +""" + fix_gauge_east_edge((row, col), envs, signs) + +Application of `fix_gauge_edge` to the east edge with appropriate row and column indices. +""" +function fix_gauge_east_edge((row, col), envs::CTMRGEnv, signs) + return fix_gauge_edge( + envs.edges[EAST, r, c], signs[EAST, r, c], signs[EAST, _next(r, end), c] + ) +end + +""" + fix_gauge_south_edge((row, col), envs, signs) + +Application of `fix_gauge_edge` to the south edge with appropriate row and column indices. +""" +function fix_gauge_south_edge((row, col), envs::CTMRGEnv, signs) + return fix_gauge_edge( + envs.edges[SOUTH, r, c], signs[SOUTH, r, c], signs[SOUTH, r, _prev(c, end)] + ) +end + +""" + fix_gauge_south_edge((row, col), envs, signs) + +Application of `fix_gauge_edge` to the west edge with appropriate row and column indices. +""" +function fix_gauge_west_edge((row, col), envs::CTMRGEnv, signs) + return fix_gauge_edge( + envs.edges[WEST, r, c], signs[WEST, r, c], signs[WEST, _prev(r, end), c] + ) +end diff --git a/src/algorithms/ctmrg/ctmrg.jl b/src/algorithms/ctmrg/ctmrg.jl index 8367bcf6..1e12f94f 100644 --- a/src/algorithms/ctmrg/ctmrg.jl +++ b/src/algorithms/ctmrg/ctmrg.jl @@ -60,7 +60,7 @@ The projectors are computed from `svd_alg` SVDs where the truncation scheme is s In general, two different schemes can be selected with `ctmrgscheme` which determine how CTMRG is implemented. It can either be `:sequential`, where the projectors are succesively -computed on the western side, and then applied and rotated. Or with `simultaneous` all projectors +computed on the western side, and then applied and rotated. Or with `:simultaneous` all projectors are computed and applied simultaneously on all sides, where in particular the corners get contracted with two projectors at the same time. """ @@ -203,7 +203,7 @@ function ctmrg_expand(state, envs::CTMRGEnv{C,T}, ::SequentialCTMRG) where {C,T} @fwdthreads for (r, c) in directions r′ = _next(r, size(state, 1)) Q_nw[r, c] = enlarge_northwest_corner((r, c), envs, state) - Q_sw[r, c] = southwest_corner((r′, c), envs, state) + Q_sw[r, c] = enlarge_southwest_corner((r′, c), envs, state) end return copy(Q_nw), copy(Q_sw) @@ -218,9 +218,9 @@ function ctmrg_expand(state, envs::CTMRGEnv{C,T}, ::SimultaneousCTMRG) where {C, elseif dir == NORTHEAST enlarge_northeast_corner((r, c), envs, state) elseif dir == SOUTHEAST - southeast_corner((r, c), envs, state) + enlarge_southeast_corner((r, c), envs, state) elseif dir == SOUTHWEST - southwest_corner((r, c), envs, state) + enlarge_southwest_corner((r, c), envs, state) end end diff --git a/src/algorithms/ctmrg/gaugefix.jl b/src/algorithms/ctmrg/gaugefix.jl index 33a02671..59111d21 100644 --- a/src/algorithms/ctmrg/gaugefix.jl +++ b/src/algorithms/ctmrg/gaugefix.jl @@ -70,60 +70,31 @@ end # Explicit fixing of relative phases (doing this compactly in a loop is annoying) function fix_relative_phases(envfinal::CTMRGEnv, signs) - C1 = map(Iterators.product(axes(envfinal.corners)[2:3]...)) do (r, c) - contract_gauge_corner( - envfinal.corners[NORTHWEST, r, c], - signs[WEST, r, c], - signs[NORTH, r, _next(c, end)], - ) - end - T1 = map(Iterators.product(axes(envfinal.edges)[2:3]...)) do (r, c) - contract_gauge_edge( - envfinal.edges[NORTH, r, c], - signs[NORTH, r, c], - signs[NORTH, r, _next(c, end)], - ) - end - C2 = map(Iterators.product(axes(envfinal.corners)[2:3]...)) do (r, c) - contract_gauge_corner( - envfinal.corners[NORTHEAST, r, c], - signs[NORTH, r, c], - signs[EAST, _next(r, end), c], - ) - end - T2 = map(Iterators.product(axes(envfinal.edges)[2:3]...)) do (r, c) - contract_gauge_edge( - envfinal.edges[EAST, r, c], signs[EAST, r, c], signs[EAST, _next(r, end), c] - ) - end - C3 = map(Iterators.product(axes(envfinal.corners)[2:3]...)) do (r, c) - contract_gauge_corner( - envfinal.corners[SOUTHEAST, r, c], - signs[EAST, r, c], - signs[SOUTH, r, _prev(c, end)], - ) - end - T3 = map(Iterators.product(axes(envfinal.edges)[2:3]...)) do (r, c) - contract_gauge_edge( - envfinal.edges[SOUTH, r, c], - signs[SOUTH, r, c], - signs[SOUTH, r, _prev(c, end)], - ) - end - C4 = map(Iterators.product(axes(envfinal.corners)[2:3]...)) do (r, c) - contract_gauge_corner( - envfinal.corners[SOUTHWEST, r, c], - signs[SOUTH, r, c], - signs[WEST, _prev(r, end), c], - ) + corners_fixed = map(Iterators.product(axes(envfinal.corners)[2:3]...)) do (r, c) + if dir == NORTHWEST + fix_gauge_northwest_corner((r, c), envfinal, signs) + elseif dir == NORTHEAST + fix_gauge_northeast_corner((r, c), envfinal, signs) + elseif dir == SOUTHEAST + fix_gauge_southeast_corner((r, c), envfinal, signs) + elseif dir == SOUTHWEST + fix_gauge_southwest_corner((r, c), envfinal, signs) + end end - T4 = map(Iterators.product(axes(envfinal.edges)[2:3]...)) do (r, c) - contract_gauge_edge( - envfinal.edges[WEST, r, c], signs[WEST, r, c], signs[WEST, _prev(r, end), c] - ) + + edges_fixed = map(Iterators.product(axes(envfinal.corners)[2:3]...)) do (r, c) + if dir == NORTHWEST + fix_gauge_north_edge((r, c), envfinal, signs) + elseif dir == NORTHEAST + fix_gauge_east_edge((r, c), envfinal, signs) + elseif dir == SOUTHEAST + fix_gauge_south_edge((r, c), envfinal, signs) + elseif dir == SOUTHWEST + fix_gauge_west_edge((r, c), envfinal, signs) + end end - return stack([C1, C2, C3, C4]; dims=1), stack([T1, T2, T3, T4]; dims=1) + return corners_fixed, edges_fixed end function fix_relative_phases( U::Array{Ut,3}, V::Array{Vt,3}, signs From 6d52b3a2fca00c958e4244b4f3c857435482fee1 Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Tue, 23 Jul 2024 16:28:26 +0200 Subject: [PATCH 070/213] Add remaining gaugefix contractions --- .../contractions/ctmrg_contractions.jl | 100 ++++++++++++++++-- src/algorithms/ctmrg/gaugefix.jl | 30 +++++- 2 files changed, 120 insertions(+), 10 deletions(-) diff --git a/src/algorithms/contractions/ctmrg_contractions.jl b/src/algorithms/contractions/ctmrg_contractions.jl index bab231e5..a09cc4bc 100644 --- a/src/algorithms/contractions/ctmrg_contractions.jl +++ b/src/algorithms/contractions/ctmrg_contractions.jl @@ -427,7 +427,9 @@ Application of `fix_gauge_corner` to the northeast corner with appropriate row a """ function fix_gauge_northeast_corner((row, col), envs::CTMRGEnv, signs) return fix_gauge_corner( - envs.corners[NORTHEAST, r, c], signs[NORTH, r, c], signs[EAST, _next(r, end), c] + envs.corners[NORTHEAST, row, col], + signs[NORTH, row, col], + signs[EAST, _next(row, end), col], ) end @@ -438,7 +440,9 @@ Application of `fix_gauge_corner` to the southeast corner with appropriate row a """ function fix_gauge_southeast_corner((row, col), envs::CTMRGEnv, signs) return fix_gauge_corner( - envs.corners[SOUTHEAST, r, c], signs[EAST, r, c], signs[SOUTH, r, _prev(c, end)] + envs.corners[SOUTHEAST, row, col], + signs[EAST, row, col], + signs[SOUTH, row, _prev(col, end)], ) end @@ -449,7 +453,9 @@ Application of `fix_gauge_corner` to the southwest corner with appropriate row a """ function fix_gauge_southwest_corner((row, col), envs::CTMRGEnv, signs) return fix_gauge_corner( - envs.corners[SOUTHWEST, r, c], signs[SOUTH, r, c], signs[WEST, _prev(r, end), c] + envs.corners[SOUTHWEST, row, col], + signs[SOUTH, row, col], + signs[WEST, _prev(row, end), col], ) end @@ -462,7 +468,9 @@ Multiplication of the edge tensor with incoming and outgoing gauge signs. -- σ_in -- edge -- σ_out -- ``` """ -function fix_gauge_edge(edge::CTMRGEdgeTensor, σ_in::CTMRGCornerTensor, σ_out::CTMRGCornerTensor) +function fix_gauge_edge( + edge::CTMRGEdgeTensor, σ_in::CTMRGCornerTensor, σ_out::CTMRGCornerTensor +) @autoopt @tensor edge_fix[χ_in D_above D_below; χ_out] := σ_in[χ_in; χ1] * edge[χ1 D_above D_below; χ2] * conj(σ_out[χ_out; χ2]) end @@ -474,7 +482,9 @@ Application of `fix_gauge_edge` to the north edge with appropriate row and colum """ function fix_gauge_north_edge((row, col), envs::CTMRGEnv, signs) return fix_gauge_edge( - envs.edges[NORTH, r, c], signs[NORTH, r, c], signs[NORTH, r, _next(c, end)] + envs.edges[NORTH, row, col], + signs[NORTH, row, col], + signs[NORTH, row, _next(col, end)], ) end @@ -485,7 +495,7 @@ Application of `fix_gauge_edge` to the east edge with appropriate row and column """ function fix_gauge_east_edge((row, col), envs::CTMRGEnv, signs) return fix_gauge_edge( - envs.edges[EAST, r, c], signs[EAST, r, c], signs[EAST, _next(r, end), c] + envs.edges[EAST, row, col], signs[EAST, row, col], signs[EAST, _next(row, end), col] ) end @@ -496,7 +506,9 @@ Application of `fix_gauge_edge` to the south edge with appropriate row and colum """ function fix_gauge_south_edge((row, col), envs::CTMRGEnv, signs) return fix_gauge_edge( - envs.edges[SOUTH, r, c], signs[SOUTH, r, c], signs[SOUTH, r, _prev(c, end)] + envs.edges[SOUTH, row, col], + signs[SOUTH, row, col], + signs[SOUTH, row, _prev(col, end)], ) end @@ -507,6 +519,78 @@ Application of `fix_gauge_edge` to the west edge with appropriate row and column """ function fix_gauge_west_edge((row, col), envs::CTMRGEnv, signs) return fix_gauge_edge( - envs.edges[WEST, r, c], signs[WEST, r, c], signs[WEST, _prev(r, end), c] + envs.edges[WEST, row, col], signs[WEST, row, col], signs[WEST, _prev(row, end), col] ) end + +""" + fix_gauge_north_left_vecs((row, col), U, signs) + +Multiply north left singular vectors with gauge signs from the right. +""" +function fix_gauge_north_left_vecs((row, col), U, signs) + return U[NORTH, row, col] * signs[NORTH, row, _next(col, end)] +end + +""" + fix_gauge_east_left_vecs((row, col), U, signs) + +Multiply east left singular vectors with gauge signs from the right. +""" +function fix_gauge_east_left_vecs((row, col), U, signs) + return U[EAST, row, col] * signs[EAST, _next(row, end), col] +end + +""" + fix_gauge_south_left_vecs((row, col), U, signs) + +Multiply south left singular vectors with gauge signs from the right. +""" +function fix_gauge_south_left_vecs((row, col), U, signs) + return U[SOUTH, row, col] * signs[SOUTH, row, _prev(col, end)] +end + +""" + fix_gauge_west_left_vecs((row, col), U, signs) + +Multiply west left singular vectors with gauge signs from the right. +""" +function fix_gauge_west_left_vecs((row, col), U, signs) + return U[WEST, row, col] * signs[WEST, _prev(row, end), col] +end + +""" + fix_gauge_north_right_vecs((row, col), V, signs) + +Multiply north right singular vectors with gauge signs from the left. +""" +function fix_gauge_north_right_vecs((row, col), V, signs) + return signs[NORTH, row, _next(col, end)]' * V[NORTH, row, col] +end + +""" + fix_gauge_east_right_vecs((row, col), V, signs) + +Multiply east right singular vectors with gauge signs from the left. +""" +function fix_gauge_east_right_vecs((row, col), V, signs) + return signs[EAST, _next(row, end), col]' * V[EAST, row, col] +end + +""" + fix_gauge_south_right_vecs((row, col), V, signs) + +Multiply south right singular vectors with gauge signs from the left. +""" +function fix_gauge_south_right_vecs((row, col), V, signs) + return signs[SOUTH, row, _prev(col, end)]' * V[SOUTH, row, col] +end + +""" + fix_gauge_west((row, col), V, signs) + +Multiply west right singular vectors with gauge signs from the left. +""" +function fix_gauge_west_right_vecs((row, col), V, signs) + return signs[WEST, _prev(row, end), col]' * V[WEST, row, col] +end diff --git a/src/algorithms/ctmrg/gaugefix.jl b/src/algorithms/ctmrg/gaugefix.jl index 59111d21..ff90fdb7 100644 --- a/src/algorithms/ctmrg/gaugefix.jl +++ b/src/algorithms/ctmrg/gaugefix.jl @@ -70,7 +70,7 @@ end # Explicit fixing of relative phases (doing this compactly in a loop is annoying) function fix_relative_phases(envfinal::CTMRGEnv, signs) - corners_fixed = map(Iterators.product(axes(envfinal.corners)[2:3]...)) do (r, c) + corners_fixed = map(Iterators.product(axes(envfinal.corners)...)) do (dir, r, c) if dir == NORTHWEST fix_gauge_northwest_corner((r, c), envfinal, signs) elseif dir == NORTHEAST @@ -82,7 +82,7 @@ function fix_relative_phases(envfinal::CTMRGEnv, signs) end end - edges_fixed = map(Iterators.product(axes(envfinal.corners)[2:3]...)) do (r, c) + edges_fixed = map(Iterators.product(axes(envfinal.corners)[2:3]...)) do (dir, r, c) if dir == NORTHWEST fix_gauge_north_edge((r, c), envfinal, signs) elseif dir == NORTHEAST @@ -125,6 +125,32 @@ function fix_relative_phases( end return stack([U1, U2, U3, U4]; dims=1), stack([V1, V2, V3, V4]; dims=1) + + U_fixed = map(Iterators.product(axes(U)...)) do (dir, r, c) + if dir == NORTHWEST + fix_gauge_north_left_vecs((r, c), U, signs) + elseif dir == NORTHEAST + fix_gauge_east_left_vecs((r, c), U, signs) + elseif dir == SOUTHEAST + fix_gauge_south_left_vecs((r, c), U, signs) + elseif dir == SOUTHWEST + fix_gauge_west_left_vecs((r, c), U, signs) + end + end + + V_fixed = map(Iterators.product(axes(V)...)) do (dir, r, c) + if dir == NORTHWEST + fix_gauge_north_right_vecs((r, c), V, signs) + elseif dir == NORTHEAST + fix_gauge_east_right_vecs((r, c), V, signs) + elseif dir == SOUTHEAST + fix_gauge_south_right_vecs((r, c), V, signs) + elseif dir == SOUTHWEST + fix_gauge_west_right_vecs((r, c), V, signs) + end + end + + return U_fixed, V_fixed end # Fix global phases of corners and edges via dot product (to ensure compatibility with symm. tensors) From 68ce4749d48963c4cef511db86c01e84157d18bc Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Tue, 23 Jul 2024 17:09:23 +0200 Subject: [PATCH 071/213] Add row/column methods for enlarged corners, fix directional multithreading --- .../contractions/ctmrg_contractions.jl | 84 ++++++++++++++++--- src/algorithms/ctmrg/ctmrg.jl | 55 ++++-------- src/algorithms/ctmrg/gaugefix.jl | 27 ------ 3 files changed, 91 insertions(+), 75 deletions(-) diff --git a/src/algorithms/contractions/ctmrg_contractions.jl b/src/algorithms/contractions/ctmrg_contractions.jl index a09cc4bc..ac70dbe1 100644 --- a/src/algorithms/contractions/ctmrg_contractions.jl +++ b/src/algorithms/contractions/ctmrg_contractions.jl @@ -186,6 +186,8 @@ end # Renormalization contractions # ---------------------------- +# corners + """ renormalize_corner(quadrant, P_left, P_right) @@ -205,6 +207,58 @@ function renormalize_corner(quadrant::AbstractTensorMap{S,3,3}, P_left, P_right) P_right[χ_in; χ1 D1 D2] * quadrant[χ1 D1 D2; χ2 D3 D4] * P_left[χ2 D3 D4; χ_out] end +""" + renormalize_northwest_corner((row, col), enlarged_envs::CTMRGEnv, P_left, P_right) + +Apply `renormalize_corner` to the enlarged northwest corner. +""" +function renormalize_northwest_corner((row, col), enlarged_envs::CTMRGEnv, P_left, P_right) + return renormalize_corner( + enlarged_envs[NORTHWEST, row, col], + P_left[NORTH, row, col], + P_right[WEST, _next(r, end), col], + ) +end + +""" + renormalize_northeast_corner((row, col), enlarged_envs::CTMRGEnv, P_left, P_right) + +Apply `renormalize_corner` to the enlarged northeast corner. +""" +function renormalize_northeast_corner((row, col), enlarged_envs::CTMRGEnv, P_left, P_right) + return renormalize_corner( + enlarged_envs[NORTHEAST, row, col], + P_left[EAST, row, col], + P_right[NORTH, row, _prev(c, end)], + ) +end + +""" + renormalize_southeast_corner((row, col), enlarged_envs::CTMRGEnv, P_left, P_right) + +Apply `renormalize_corner` to the enlarged southeast corner. +""" +function renormalize_southeast_corner((row, col), enlarged_envs::CTMRGEnv, P_left, P_right) + return renormalize_corner( + enlarged_envs[SOUTHEAST, row, col], + P_left[SOUTH, row, col], + P_right[EAST, _prev(r, end), col], + ) +end + +""" + renormalize_southwest_corner((row, col), enlarged_envs::CTMRGEnv, P_left, P_right) + +Apply `renormalize_corner` to the enlarged southwest corner. +""" +function renormalize_southwest_corner((row, col), enlarged_envs::CTMRGEnv, P_left, P_right) + return renormalize_corner( + enlarged_envs[SOUTHWEST, row, col], + P_left[WEST, row, col], + P_right[SOUTH, row, _next(col, end)], + ) +end + """ rightrenormalize_corner(C, E, P) @@ -239,6 +293,8 @@ function leftrenormalize_corner(C::CTMRGCornerTensor, E::CTMRGEdgeTensor, P) P[χ_in D1 D2; χ1] * C[χ1; χ2] * E[χ2 D1 D2; χ_out] end +# edges + """ renormalize_north_edge((row, col), envs, P_left, P_right, ket, bra) renormalize_north_edge(E_north, P_left, P_right, ket, bra) @@ -388,10 +444,12 @@ end # Gauge fixing contractions # ------------------------- +# corners + """ fix_gauge_corner(corner, σ_in, σ_out) -Multiplication of the corner tensor with incoming and outgoing gauge signs. +Multiply corner tensor with incoming and outgoing gauge signs. ``` corner -- σ_out -- @@ -410,7 +468,7 @@ end """ fix_gauge_northwest_corner((row, col), envs, signs) -Application of `fix_gauge_corner` to the northwest corner with appropriate row and column indices. +Apply `fix_gauge_corner` to the northwest corner with appropriate row and column indices. """ function fix_gauge_northwest_corner((row, col), envs::CTMRGEnv, signs) return fix_gauge_corner( @@ -423,7 +481,7 @@ end """ fix_gauge_northeast_corner((row, col), envs, signs) -Application of `fix_gauge_corner` to the northeast corner with appropriate row and column indices. +Apply `fix_gauge_corner` to the northeast corner with appropriate row and column indices. """ function fix_gauge_northeast_corner((row, col), envs::CTMRGEnv, signs) return fix_gauge_corner( @@ -436,7 +494,7 @@ end """ fix_gauge_southeast_corner((row, col), envs, signs) -Application of `fix_gauge_corner` to the southeast corner with appropriate row and column indices. +Apply `fix_gauge_corner` to the southeast corner with appropriate row and column indices. """ function fix_gauge_southeast_corner((row, col), envs::CTMRGEnv, signs) return fix_gauge_corner( @@ -449,7 +507,7 @@ end """ fix_gauge_southwest_corner((row, col), envs, signs) -Application of `fix_gauge_corner` to the southwest corner with appropriate row and column indices. +Apply `fix_gauge_corner` to the southwest corner with appropriate row and column indices. """ function fix_gauge_southwest_corner((row, col), envs::CTMRGEnv, signs) return fix_gauge_corner( @@ -459,10 +517,12 @@ function fix_gauge_southwest_corner((row, col), envs::CTMRGEnv, signs) ) end +# edges + """ fix_gauge_edge(edge, σ_in, σ_out) -Multiplication of the edge tensor with incoming and outgoing gauge signs. +Multiply edge tensor with incoming and outgoing gauge signs. ``` -- σ_in -- edge -- σ_out -- @@ -478,7 +538,7 @@ end """ fix_gauge_north_edge((row, col), envs, signs) -Application of `fix_gauge_edge` to the north edge with appropriate row and column indices. +Apply `fix_gauge_edge` to the north edge with appropriate row and column indices. """ function fix_gauge_north_edge((row, col), envs::CTMRGEnv, signs) return fix_gauge_edge( @@ -491,7 +551,7 @@ end """ fix_gauge_east_edge((row, col), envs, signs) -Application of `fix_gauge_edge` to the east edge with appropriate row and column indices. +Apply `fix_gauge_edge` to the east edge with appropriate row and column indices. """ function fix_gauge_east_edge((row, col), envs::CTMRGEnv, signs) return fix_gauge_edge( @@ -502,7 +562,7 @@ end """ fix_gauge_south_edge((row, col), envs, signs) -Application of `fix_gauge_edge` to the south edge with appropriate row and column indices. +Apply `fix_gauge_edge` to the south edge with appropriate row and column indices. """ function fix_gauge_south_edge((row, col), envs::CTMRGEnv, signs) return fix_gauge_edge( @@ -515,7 +575,7 @@ end """ fix_gauge_south_edge((row, col), envs, signs) -Application of `fix_gauge_edge` to the west edge with appropriate row and column indices. +Apply `fix_gauge_edge` to the west edge with appropriate row and column indices. """ function fix_gauge_west_edge((row, col), envs::CTMRGEnv, signs) return fix_gauge_edge( @@ -523,6 +583,8 @@ function fix_gauge_west_edge((row, col), envs::CTMRGEnv, signs) ) end +# left singular vectors + """ fix_gauge_north_left_vecs((row, col), U, signs) @@ -559,6 +621,8 @@ function fix_gauge_west_left_vecs((row, col), U, signs) return U[WEST, row, col] * signs[WEST, _prev(row, end), col] end +# right singular vectors + """ fix_gauge_north_right_vecs((row, col), V, signs) diff --git a/src/algorithms/ctmrg/ctmrg.jl b/src/algorithms/ctmrg/ctmrg.jl index 1e12f94f..9fea5978 100644 --- a/src/algorithms/ctmrg/ctmrg.jl +++ b/src/algorithms/ctmrg/ctmrg.jl @@ -381,44 +381,23 @@ function ctmrg_renormalize(enlarged_envs, projectors, state, envs, ::Simultaneou edges = Zygote.Buffer(envs.edges) P_left, P_right = projectors - coordinates = collect(Iterators.product(axes(state)...)) - @fwdthreads for (r, c) in coordinates - rprev = _prev(r, size(state, 1)) - rnext = _next(r, size(state, 1)) - cprev = _prev(c, size(state, 2)) - cnext = _next(c, size(state, 2)) - - C_northwest = renormalize_corner( - enlarged_envs[NORTHWEST, r, c], P_left[NORTH, r, c], P_right[WEST, rnext, c] - ) - corners[NORTHWEST, r, c] = C_northwest / norm(C_northwest) - - C_northeast = renormalize_corner( - enlarged_envs[NORTHEAST, r, c], P_left[EAST, r, c], P_right[NORTH, r, cprev] - ) - corners[NORTHEAST, r, c] = C_northeast / norm(C_northeast) - - C_southeast = renormalize_corner( - enlarged_envs[SOUTHEAST, r, c], P_left[SOUTH, r, c], P_right[EAST, rprev, c] - ) - corners[SOUTHEAST, r, c] = C_southeast / norm(C_southeast) - - C_southwest = renormalize_corner( - enlarged_envs[SOUTHWEST, r, c], P_left[WEST, r, c], P_right[SOUTH, r, cnext] - ) - corners[SOUTHWEST, r, c] = C_southwest / norm(C_southwest) - - E_north = renormalize_north_edge((r, c), envs, P_left, P_right, state, state) - edges[NORTH, r, c] = E_north / norm(E_north) - - E_east = renormalize_east_edge((r, c), envs, P_left, P_right, state, state) - edges[EAST, r, c] = E_east / norm(E_east) - - E_south = renormalize_south_edge((r, c), envs, P_left, P_right, state, state) - edges[SOUTH, r, c] = E_south / norm(E_south) - - E_west = renormalize_west_edge((r, c), envs, P_left, P_right, state, state) - edges[WEST, r, c] = E_west / norm(E_west) + drc_combinations = collect(Iterators.product(axes(envs.corners)...)) + @fwdthreads for (dir, r, c) in drc_combinations + if dir == NORTH + corner = renormalize_northwest_corner((r, c), envs, P_left, P_right) + edge = renormalize_north_edge((r, c), envs, P_left, P_right, state) + elseif dir == EAST + corner = renormalize_northeast_corner((r, c), envs, P_left, P_right) + edge = renormalize_east_edge((r, c), envs, P_left, P_right, state) + elseif dir == SOUTH + corner = renormalize_southeast_corner((r, c), envs, P_left, P_right) + edge = renormalize_south_edge((r, c), envs, P_left, P_right, state) + elseif dir == WEST + corner = renormalize_west_corner((r, c), envs, P_left, P_right) + edge = renormalize_west_edge((r, c), envs, P_left, P_right, state) + end + corners[dir, r, c] = corner / norm(corner) + edges[dir, r, c] = edge / norm(edge) end return CTMRGEnv(copy(corners), copy(edges)) diff --git a/src/algorithms/ctmrg/gaugefix.jl b/src/algorithms/ctmrg/gaugefix.jl index ff90fdb7..d72819e7 100644 --- a/src/algorithms/ctmrg/gaugefix.jl +++ b/src/algorithms/ctmrg/gaugefix.jl @@ -99,33 +99,6 @@ end function fix_relative_phases( U::Array{Ut,3}, V::Array{Vt,3}, signs ) where {Ut<:AbstractTensorMap,Vt<:AbstractTensorMap} - U1 = map(Iterators.product(axes(U)[2:3]...)) do (r, c) - return U[NORTH, r, c] * signs[NORTH, r, _next(c, end)] - end - V1 = map(Iterators.product(axes(V)[2:3]...)) do (r, c) - return signs[NORTH, r, _next(c, end)]' * V[NORTH, r, c] - end - U2 = map(Iterators.product(axes(U)[2:3]...)) do (r, c) - return U[EAST, r, c] * signs[EAST, _next(r, end), c] - end - V2 = map(Iterators.product(axes(V)[2:3]...)) do (r, c) - return signs[EAST, _next(r, end), c]' * V[EAST, r, c] - end - U3 = map(Iterators.product(axes(U)[2:3]...)) do (r, c) - return U[SOUTH, r, c] * signs[SOUTH, r, _prev(c, end)] - end - V3 = map(Iterators.product(axes(V)[2:3]...)) do (r, c) - return signs[SOUTH, r, _prev(c, end)]' * V[SOUTH, r, c] - end - U4 = map(Iterators.product(axes(U)[2:3]...)) do (r, c) - return U[WEST, r, c] * signs[WEST, _prev(r, end), c] - end - V4 = map(Iterators.product(axes(V)[2:3]...)) do (r, c) - return signs[WEST, _prev(r, end), c]' * V[WEST, r, c] - end - - return stack([U1, U2, U3, U4]; dims=1), stack([V1, V2, V3, V4]; dims=1) - U_fixed = map(Iterators.product(axes(U)...)) do (dir, r, c) if dir == NORTHWEST fix_gauge_north_left_vecs((r, c), U, signs) From 83e0c6a827dc31855857f0a973b851b78e2e6595 Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Tue, 23 Jul 2024 18:32:17 +0200 Subject: [PATCH 072/213] Fix bunch of typos and contraction switch ups --- .../contractions/ctmrg_contractions.jl | 126 +++++++++++------- src/algorithms/ctmrg/ctmrg.jl | 31 ++--- src/algorithms/ctmrg/gaugefix.jl | 2 +- src/utility/diffset.jl | 4 +- test/heisenberg.jl | 4 +- 5 files changed, 92 insertions(+), 75 deletions(-) diff --git a/src/algorithms/contractions/ctmrg_contractions.jl b/src/algorithms/contractions/ctmrg_contractions.jl index ac70dbe1..8d11747a 100644 --- a/src/algorithms/contractions/ctmrg_contractions.jl +++ b/src/algorithms/contractions/ctmrg_contractions.jl @@ -99,9 +99,9 @@ coordinates, environments and state, or by directly providing the tensors. function enlarge_southeast_corner( (row, col), envs::CTMRGEnv, ket::InfinitePEPS, bra::InfinitePEPS=ket ) - E_east = env.edges[EAST, row, _next(col, end)] - C_southeast = env.corners[SOUTHEAST, _next(row, end), _next(col, end)] - E_south = env.edges[SOUTH, _next(row, end), col] + E_east = envs.edges[EAST, row, _next(col, end)] + C_southeast = envs.corners[SOUTHEAST, _next(row, end), _next(col, end)] + E_south = envs.edges[SOUTH, _next(row, end), col] return enlarge_southeast_corner( E_east, C_southeast, E_south, ket[row, col], bra[row, col] ) @@ -138,9 +138,9 @@ coordinates, environments and state, or by directly providing the tensors. function enlarge_southwest_corner( (row, col), envs::CTMRGEnv, ket::InfinitePEPS, bra::InfinitePEPS=ket ) - E_south = env.edges[SOUTH, _next(row, end), col] - C_southwest = env.corners[SOUTHWEST, _next(row, end), _prev(col, end)] - E_west = env.edges[WEST, row, _prev(col, end)] + E_south = envs.edges[SOUTH, _next(row, end), col] + C_southwest = envs.corners[SOUTHWEST, _next(row, end), _prev(col, end)] + E_west = envs.edges[WEST, row, _prev(col, end)] return enlarge_southwest_corner( E_south, C_southwest, E_west, ket[row, col], bra[row, col] ) @@ -177,7 +177,7 @@ Contract two quadrants (enlarged corners) to form a half-infinite environment. """ function halfinfinite_environment( quadrant1::AbstractTensorMap{S,3,3}, quadrant2::AbstractTensorMap{S,3,3} -) +) where {S} return @autoopt @tensor half[χ_in D_inabove D_inbelow; χ_out D_outabove D_outbelow] := quadrant1[χ_in D_inabove D_inbelow; χ D1 D2] * quadrant2[χ D1 D2; χ_out D_outabove D_outbelow] @@ -212,11 +212,11 @@ end Apply `renormalize_corner` to the enlarged northwest corner. """ -function renormalize_northwest_corner((row, col), enlarged_envs::CTMRGEnv, P_left, P_right) +function renormalize_northwest_corner((row, col), enlarged_envs, P_left, P_right) return renormalize_corner( enlarged_envs[NORTHWEST, row, col], P_left[NORTH, row, col], - P_right[WEST, _next(r, end), col], + P_right[WEST, _next(row, end), col], ) end @@ -225,11 +225,11 @@ end Apply `renormalize_corner` to the enlarged northeast corner. """ -function renormalize_northeast_corner((row, col), enlarged_envs::CTMRGEnv, P_left, P_right) +function renormalize_northeast_corner((row, col), enlarged_envs, P_left, P_right) return renormalize_corner( enlarged_envs[NORTHEAST, row, col], P_left[EAST, row, col], - P_right[NORTH, row, _prev(c, end)], + P_right[NORTH, row, _prev(col, end)], ) end @@ -238,11 +238,11 @@ end Apply `renormalize_corner` to the enlarged southeast corner. """ -function renormalize_southeast_corner((row, col), enlarged_envs::CTMRGEnv, P_left, P_right) +function renormalize_southeast_corner((row, col), enlarged_envs, P_left, P_right) return renormalize_corner( enlarged_envs[SOUTHEAST, row, col], P_left[SOUTH, row, col], - P_right[EAST, _prev(r, end), col], + P_right[EAST, _prev(row, end), col], ) end @@ -251,7 +251,7 @@ end Apply `renormalize_corner` to the enlarged southwest corner. """ -function renormalize_southwest_corner((row, col), enlarged_envs::CTMRGEnv, P_left, P_right) +function renormalize_southwest_corner((row, col), enlarged_envs, P_left, P_right) return renormalize_corner( enlarged_envs[SOUTHWEST, row, col], P_left[WEST, row, col], @@ -260,37 +260,41 @@ function renormalize_southwest_corner((row, col), enlarged_envs::CTMRGEnv, P_lef end """ - rightrenormalize_corner(C, E, P) + renormalize_bottom_corner((r, c), envs, projectors) -Apply outgoing projector to a corner and edge. +Apply bottom projector to southwest corner and south edge. ``` - out - | - [~~P~~] - | || - C -- E -- in + | + [P_bottom] + | || + C -- E -- in ``` """ -function rightrenormalize_corner(C::CTMRGCornerTensor, E::CTMRGEdgeTensor, P) +function renormalize_bottom_corner((row, col), envs::CTMRGEnv, projectors) + C_southwest = envs.corners[SOUTHWEST, row, _prev(col, end)] + E_south = envs.edges[SOUTH, row, col] + P_bottom = projectors[1][_prev(row, end), col] return @autoopt @tensor corner[χ_in; χ_out] := - E[χ_in D1 D2; χ1] * C[χ1; χ2] * P[χ2 D1 D2; χ_out] + E_south[χ_in D1 D2; χ1] * C_southwest[χ1; χ2] * P_bottom[χ2 D1 D2; χ_out] end """ - leftrenormalize_corner(C, E, P) + renormalize_top_corner((row, col), envs::CTMRGEnv, projectors) -Apply ingoing projector to a corner and edge. +Apply top projector to northwest corner and north edge. ``` - C -- E -- out - | || - [~~P~~] - | - in + C -- E -- + | || + [~P_top~] + | ``` """ -function leftrenormalize_corner(C::CTMRGCornerTensor, E::CTMRGEdgeTensor, P) +function renormalize_top_corner((row, col), envs::CTMRGEnv, projectors) + C_northwest = envs.corners[NORTHWEST, row, _prev(col, end)] + E_north = envs.edges[NORTH, row, col] + P_top = projectors[2][row, col] return @autoopt @tensor corner[χ_in; χ_out] := - P[χ_in D1 D2; χ1] * C[χ1; χ2] * E[χ2 D1 D2; χ_out] + P_top[χ_in; χ1 D1 D2] * C_northwest[χ1; χ2] * E_north[χ2 D1 D2; χ_out] end # edges @@ -302,9 +306,9 @@ end Absorb a bra-ket pair into the north edge using the given projectors and environment tensors. ``` - |~~~~~~~| -- E_north -- |~~~~~~~| - -- |P_right| || |P_right| -- - |~~~~~~~| == ket-bra == |~~~~~~~| + |~~~~~~| -- E_north -- |~~~~~~~| + -- |P_left| || |P_right| -- + |~~~~~~| == ket-bra == |~~~~~~~| ``` """ @@ -320,14 +324,14 @@ function renormalize_north_edge( ) end function renormalize_north_edge( - E_north::CTMRGEdgeTensor, P_left, P_right, ket::InfinitePEPS, bra::InfinitePEPS=ket + E_north::CTMRGEdgeTensor, P_left, P_right, ket::PEPSTensor, bra::PEPSTensor=ket ) - return @autoopt @tensor edge[χ_W D_Eab D_Ebe; χ_E] := + return @autoopt @tensor edge[χ_W D_Sab D_Sbe; χ_E] := E_north[χ1 D1 D2; χ2] * - ket[d; D3 D_Eab D5 D1] * - conj(bra[d; D4 D_Ebe D6 D2]) * - P_right[χ2 D3 D4; χ_E] * - P_left[χ_W; χ1 D5 D6] + ket[d; D1 D3 D_Sab D5] * + conj(bra[d; D2 D4 D_Sbe D6]) * + P_left[χ2 D3 D4; χ_E] * + P_right[χ_W; χ1 D5 D6] end """ @@ -358,7 +362,7 @@ function renormalize_east_edge( ) end function renormalize_east_edge( - E_east::CTMRGEdgeTensor, P_top, P_bottom, ket::InfinitePEPS, bra::InfinitePEPS=ket + E_east::CTMRGEdgeTensor, P_top, P_bottom, ket::PEPSTensor, bra::PEPSTensor=ket ) return @autoopt @tensor edge[χ_N D_Wab D_Wbe; χ_S] := E_east[χ1 D1 D2; χ2] * @@ -384,16 +388,16 @@ Absorb a bra-ket pair into the south edge using the given projectors and environ function renormalize_south_edge( (row, col), envs::CTMRGEnv, P_left, P_right, ket::InfinitePEPS, bra::InfinitePEPS=ket ) - return renormalize_north_edge( + return renormalize_south_edge( envs.edges[SOUTH, _next(row, end), col], - P_left[NORTH, row, col], + P_left[SOUTH, row, col], P_right[SOUTH, row, _next(col, end)], ket[row, col], bra[row, col], ) end function renormalize_south_edge( - E_south::CTMRGEdgeTensor, P_left, P_right, ket::InfinitePEPS, bra::InfinitePEPS=ket + E_south::CTMRGEdgeTensor, P_left, P_right, ket::PEPSTensor, bra::PEPSTensor=ket ) return @autoopt @tensor edge[χ_E D_Nab D_Nbe; χ_W] := E_south[χ1 D1 D2; χ2] * @@ -419,9 +423,14 @@ Absorb a bra-ket pair into the west edge using the given projectors and environm | ``` """ -function renormalize_west_edge( - (row, col), envs::CTMRGEnv, P_top, P_bottom, ket::InfinitePEPS, bra::InfinitePEPS=ket -) +function renormalize_west_edge( # For simultaneous CTMRG scheme + (row, col), + envs::CTMRGEnv, + P_top::Array{Pt,3}, + P_bottom::Array{Pb,3}, + ket::InfinitePEPS, + bra::InfinitePEPS=ket, +) where {Pt,Pb} return renormalize_west_edge( envs.edges[WEST, row, _prev(col, end)], P_top[WEST, row, col], @@ -430,15 +439,30 @@ function renormalize_west_edge( bra[row, col], ) end +function renormalize_west_edge( # For sequential CTMRG scheme + (row, col), + envs::CTMRGEnv, + projectors, + ket::InfinitePEPS, + bra::InfinitePEPS=ket, +) + return renormalize_west_edge( + envs.edges[WEST, row, _prev(col, end)], + projectors[1][row, col], + projectors[2][_next(row, end), col], + ket[row, col], + bra[row, col], + ) +end function renormalize_west_edge( - E_west::CTMRGEdgeTensor, P_top, P_bottom, ket::InfinitePEPS, bra::InfinitePEPS=ket + E_west::CTMRGEdgeTensor, P_top, P_bottom, ket::PEPSTensor, bra::PEPSTensor=ket ) return @autoopt @tensor edge[χ_S D_Eab D_Ebe; χ_N] := E_west[χ1 D1 D2; χ2] * ket[d; D3 D_Eab D5 D1] * conj(bra[d; D4 D_Ebe D6 D2]) * - P_bottom[χ2 D3 D4; χ_N] * - P_top[χ_S; χ1 D5 D6] + P_top[χ2 D3 D4; χ_N] * + P_bottom[χ_S; χ1 D5 D6] end # Gauge fixing contractions diff --git a/src/algorithms/ctmrg/ctmrg.jl b/src/algorithms/ctmrg/ctmrg.jl index 9fea5978..c4b04590 100644 --- a/src/algorithms/ctmrg/ctmrg.jl +++ b/src/algorithms/ctmrg/ctmrg.jl @@ -155,7 +155,7 @@ function ctmrg_iter(state, envs::CTMRGEnv, alg::SequentialCTMRG) # left move enlarged_envs = ctmrg_expand(state, envs, alg) projectors, info = ctmrg_projectors(enlarged_envs, envs, alg) - envs = ctmrg_renormalize(enlarged_envs, projectors, state, envs, alg) + envs = ctmrg_renormalize(projectors, state, envs, alg) # rotate state = rotate_north(state, EAST) @@ -337,7 +337,7 @@ end Apply projectors to renormalize corners and edges. """ -function ctmrg_renormalize(enlarged_envs, projectors, state, envs, ::SequentialCTMRG) +function ctmrg_renormalize(projectors, state, envs, ::SequentialCTMRG) corners = Zygote.Buffer(envs.corners) edges = Zygote.Buffer(envs.edges) @@ -355,22 +355,15 @@ function ctmrg_renormalize(enlarged_envs, projectors, state, envs, ::SequentialC # Apply projectors to renormalize corners and edges coordinates = collect(Iterators.product(axes(state)...)) - @fwdthreads for (r, c) in coordinates - c′ = _prev(c, size(state, 2)) - - C_southwest = rightrenormalize_corner( - envs.corners[SOUTHWEST, r, c′], envs.edges[SOUTH, r, c], projectors[2][r, c] - ) + # @fwdthreads for (r, c) in coordinates + for (r, c) in coordinates + C_southwest = renormalize_bottom_corner((r, c), envs, projectors) corners[SOUTHWEST, r, c] = C_southwest / norm(C_southwest) - C_northwest = leftrenormalize_corner( - envs.corners[NORTHWEST, r, c′], envs.edges[NORTH, r, c], projectors[1][r, c] - ) + C_northwest = renormalize_top_corner((r, c), envs, projectors) corners[NORTHWEST, r, c] = C_northwest / norm(C_northwest) - E_west = renormalize_west_edge( - (r, c), envs, projectors[1], projectors[2], state, state - ) + E_west = renormalize_west_edge((r, c), envs, projectors, state) edges[WEST, r, c] = E_west / norm(E_west) end @@ -383,17 +376,17 @@ function ctmrg_renormalize(enlarged_envs, projectors, state, envs, ::Simultaneou drc_combinations = collect(Iterators.product(axes(envs.corners)...)) @fwdthreads for (dir, r, c) in drc_combinations - if dir == NORTH - corner = renormalize_northwest_corner((r, c), envs, P_left, P_right) + if dir == NORTH + corner = renormalize_northwest_corner((r, c), enlarged_envs, P_left, P_right) edge = renormalize_north_edge((r, c), envs, P_left, P_right, state) elseif dir == EAST - corner = renormalize_northeast_corner((r, c), envs, P_left, P_right) + corner = renormalize_northeast_corner((r, c), enlarged_envs, P_left, P_right) edge = renormalize_east_edge((r, c), envs, P_left, P_right, state) elseif dir == SOUTH - corner = renormalize_southeast_corner((r, c), envs, P_left, P_right) + corner = renormalize_southeast_corner((r, c), enlarged_envs, P_left, P_right) edge = renormalize_south_edge((r, c), envs, P_left, P_right, state) elseif dir == WEST - corner = renormalize_west_corner((r, c), envs, P_left, P_right) + corner = renormalize_southwest_corner((r, c), enlarged_envs, P_left, P_right) edge = renormalize_west_edge((r, c), envs, P_left, P_right, state) end corners[dir, r, c] = corner / norm(corner) diff --git a/src/algorithms/ctmrg/gaugefix.jl b/src/algorithms/ctmrg/gaugefix.jl index d72819e7..778c2dce 100644 --- a/src/algorithms/ctmrg/gaugefix.jl +++ b/src/algorithms/ctmrg/gaugefix.jl @@ -82,7 +82,7 @@ function fix_relative_phases(envfinal::CTMRGEnv, signs) end end - edges_fixed = map(Iterators.product(axes(envfinal.corners)[2:3]...)) do (dir, r, c) + edges_fixed = map(Iterators.product(axes(envfinal.corners)...)) do (dir, r, c) if dir == NORTHWEST fix_gauge_north_edge((r, c), envfinal, signs) elseif dir == NORTHEAST diff --git a/src/utility/diffset.jl b/src/utility/diffset.jl index b093fa84..d9fb5c7b 100644 --- a/src/utility/diffset.jl +++ b/src/utility/diffset.jl @@ -14,7 +14,7 @@ parse_ex(ex) = ex function parse_ex(ex::Expr) oppheads = (:(./=), :(.*=), :(.+=), :(.-=)) opprep = (:(./), :(.*), :(.+), :(.-)) - if ex.head=== :macrocall + if ex.head === :macrocall parse_ex(macroexpand(PEPSKit, ex)) elseif ex.head in (:(.=), :(=)) && length(ex.args) == 2 && is_indexing(ex.args[1]) lhs = ex.args[1] @@ -44,4 +44,4 @@ function parse_ex(ex::Expr) end is_indexing(ex) = false -is_indexing(ex::Expr) = ex.head=== :ref +is_indexing(ex::Expr) = ex.head === :ref diff --git a/test/heisenberg.jl b/test/heisenberg.jl index c95ab664..8c5ece36 100644 --- a/test/heisenberg.jl +++ b/test/heisenberg.jl @@ -14,12 +14,12 @@ ctm_alg = CTMRG(; maxiter=100, verbosity=2, svd_alg=SVDAdjoint(; fwd_alg=TensorKit.SVD(), rrule_alg=GMRES(; tol=1e-10)), - ctmrgscheme=:simultaneous, + ctmrgscheme=:sequential, ) opt_alg = PEPSOptimize(; boundary_alg=ctm_alg, optimizer=LBFGS(4; maxiter=100, gradtol=1e-3, verbosity=2), - gradient_alg=LinSolver(; solver=GMRES(; tol=1e-6), iterscheme=:fixed), + gradient_alg=LinSolver(; solver=GMRES(; tol=1e-6), iterscheme=:diffgauge), reuse_env=true, ) From 33d3c9463dd16a8bbc93e55f9a798da546919e26 Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Wed, 24 Jul 2024 15:37:05 +0200 Subject: [PATCH 073/213] Fix unit cell indices for sequential scheme --- .../contractions/ctmrg_contractions.jl | 28 +++++----- src/algorithms/ctmrg/ctmrg.jl | 54 +++++++++---------- test/heisenberg.jl | 8 +-- 3 files changed, 44 insertions(+), 46 deletions(-) diff --git a/src/algorithms/contractions/ctmrg_contractions.jl b/src/algorithms/contractions/ctmrg_contractions.jl index 8d11747a..af537fe6 100644 --- a/src/algorithms/contractions/ctmrg_contractions.jl +++ b/src/algorithms/contractions/ctmrg_contractions.jl @@ -351,25 +351,25 @@ Absorb a bra-ket pair into the east edge using the given projectors and environm ``` """ function renormalize_east_edge( - (row, col), envs::CTMRGEnv, P_top, P_bottom, ket::InfinitePEPS, bra::InfinitePEPS=ket + (row, col), envs::CTMRGEnv, P_bottom, P_top, ket::InfinitePEPS, bra::InfinitePEPS=ket ) return renormalize_east_edge( envs.edges[EAST, row, _next(col, end)], - P_top[EAST, row, col], - P_bottom[EAST, _prev(row, end), col, end], + P_bottom[EAST, row, col, end], + P_top[EAST, _prev(row, end), col], ket[row, col], bra[row, col], ) end function renormalize_east_edge( - E_east::CTMRGEdgeTensor, P_top, P_bottom, ket::PEPSTensor, bra::PEPSTensor=ket + E_east::CTMRGEdgeTensor, P_bottom, P_top, ket::PEPSTensor, bra::PEPSTensor=ket ) return @autoopt @tensor edge[χ_N D_Wab D_Wbe; χ_S] := E_east[χ1 D1 D2; χ2] * ket[d; D5 D1 D3 D_Wab] * conj(bra[d; D6 D2 D4 D_Wbe]) * - P_top[χ2 D3 D4; χ_S] * - P_bottom[χ_N; χ1 D5 D6] + P_bottom[χ2 D3 D4; χ_S] * + P_top[χ_N; χ1 D5 D6] end """ @@ -426,15 +426,15 @@ Absorb a bra-ket pair into the west edge using the given projectors and environm function renormalize_west_edge( # For simultaneous CTMRG scheme (row, col), envs::CTMRGEnv, - P_top::Array{Pt,3}, P_bottom::Array{Pb,3}, + P_top::Array{Pt,3}, ket::InfinitePEPS, bra::InfinitePEPS=ket, ) where {Pt,Pb} return renormalize_west_edge( envs.edges[WEST, row, _prev(col, end)], - P_top[WEST, row, col], - P_bottom[WEST, _next(row, end), col], + P_bottom[WEST, row, col], + P_top[WEST, _next(row, end), col], ket[row, col], bra[row, col], ) @@ -448,21 +448,21 @@ function renormalize_west_edge( # For sequential CTMRG scheme ) return renormalize_west_edge( envs.edges[WEST, row, _prev(col, end)], - projectors[1][row, col], - projectors[2][_next(row, end), col], + projectors[1][_prev(row, end), col], + projectors[2][row, col], ket[row, col], bra[row, col], ) end function renormalize_west_edge( - E_west::CTMRGEdgeTensor, P_top, P_bottom, ket::PEPSTensor, bra::PEPSTensor=ket + E_west::CTMRGEdgeTensor, P_bottom, P_top, ket::PEPSTensor, bra::PEPSTensor=ket ) return @autoopt @tensor edge[χ_S D_Eab D_Ebe; χ_N] := E_west[χ1 D1 D2; χ2] * ket[d; D3 D_Eab D5 D1] * conj(bra[d; D4 D_Ebe D6 D2]) * - P_top[χ2 D3 D4; χ_N] * - P_bottom[χ_S; χ1 D5 D6] + P_bottom[χ2 D3 D4; χ_N] * + P_top[χ_S; χ1 D5 D6] end # Gauge fixing contractions diff --git a/src/algorithms/ctmrg/ctmrg.jl b/src/algorithms/ctmrg/ctmrg.jl index c4b04590..ed8d3dd2 100644 --- a/src/algorithms/ctmrg/ctmrg.jl +++ b/src/algorithms/ctmrg/ctmrg.jl @@ -196,17 +196,17 @@ the left. The second mode expands the environment in all four directions simulta """ function ctmrg_expand(state, envs::CTMRGEnv{C,T}, ::SequentialCTMRG) where {C,T} Qtype = tensormaptype(spacetype(C), 3, 3, storagetype(C)) - Q_nw = Zygote.Buffer(envs.corners, Qtype, axes(state)...) Q_sw = Zygote.Buffer(envs.corners, Qtype, axes(state)...) + Q_nw = Zygote.Buffer(envs.corners, Qtype, axes(state)...) directions = collect(Iterators.product(axes(state)...)) - @fwdthreads for (r, c) in directions - r′ = _next(r, size(state, 1)) + # @fwdthreads for (r, c) in directions + for (r, c) in directions + Q_sw[r, c] = enlarge_southwest_corner((r, c), envs, state) Q_nw[r, c] = enlarge_northwest_corner((r, c), envs, state) - Q_sw[r, c] = enlarge_southwest_corner((r′, c), envs, state) end - return copy(Q_nw), copy(Q_sw) + return copy(Q_sw), copy(Q_nw) end function ctmrg_expand(state, envs::CTMRGEnv{C,T}, ::SimultaneousCTMRG) where {C,T} Qtype = tensormaptype(spacetype(C), 3, 3, storagetype(C)) @@ -245,9 +245,11 @@ function ctmrg_projectors( ϵ = zero(real(scalartype(envs))) directions = collect(Iterators.product(axes(envs.corners, 2), axes(envs.corners, 3))) - @fwdthreads for (r, c) in directions + # @fwdthreads for (r, c) in directions + for (r, c) in directions # SVD half-infinite environment - QQ = halfinfinite_environment(enlarged_envs[2][r, c], enlarged_envs[1][r, c]) + r′ = _next(r, size(envs.corners, 2)) + QQ = halfinfinite_environment(enlarged_envs[1][r′, c], enlarged_envs[2][r, c]) trscheme = truncation_scheme(projector_alg, envs.edges[WEST, r, c]) svd_alg = svd_algorithm(projector_alg, (WEST, r, c)) @@ -264,7 +266,7 @@ function ctmrg_projectors( # Compute projectors P_bottom[r, c], P_top[r, c] = build_projectors( - U, S, V, enlarged_envs[2][r, c], enlarged_envs[1][r, c] + U, S, V, enlarged_envs[1][r′, c], enlarged_envs[2][r, c] ) end @@ -369,6 +371,22 @@ function ctmrg_renormalize(projectors, state, envs, ::SequentialCTMRG) return CTMRGEnv(copy(corners), copy(edges)) end +# Apply projectors to entire left half-environment to grow SW & NW corners, and W edge +function grow_env_left( + peps, P_bottom, P_top, corners_SW, corners_NW, edge_S, edge_W, edge_N +) + @autoopt @tensor corner_SW′[χ_E; χ_N] := + corners_SW[χ1; χ2] * edge_S[χ_E D1 D2; χ1] * P_bottom[χ2 D1 D2; χ_N] + @autoopt @tensor corner_NW′[χ_S; χ_E] := + corners_NW[χ1; χ2] * edge_N[χ2 D1 D2; χ_E] * P_top[χ_S; χ1 D1 D2] + @autoopt @tensor edge_W′[χ_S D_Eabove D_Ebelow; χ_N] := + edge_W[χ1 D1 D2; χ2] * + peps[d; D3 D_Eabove D5 D1] * + conj(peps[d; D4 D_Ebelow D6 D2]) * + P_bottom[χ2 D3 D4; χ_N] * + P_top[χ_S; χ1 D5 D6] + return corner_SW′, corner_NW′, edge_W′ +end function ctmrg_renormalize(enlarged_envs, projectors, state, envs, ::SimultaneousCTMRG) corners = Zygote.Buffer(envs.corners) edges = Zygote.Buffer(envs.edges) @@ -400,26 +418,6 @@ end # Auxiliary routines # ======================================================================================== # -# Compute enlarged corners and edges for all directions and unit cell entries -function enlarge_corners_edges(state, env::CTMRGEnv{C,T}) where {C,T} - Qtype = tensormaptype(spacetype(C), 3, 3, storagetype(C)) - Q = Zygote.Buffer(Array{Qtype,3}(undef, size(env.corners))) - drc_combinations = collect(Iterators.product(axes(env.corners)...)) - @fwdthreads for (dir, r, c) in drc_combinations - Q[dir, r, c] = if dir == NORTHWEST - northwest_corner((r, c), env, state) - elseif dir == NORTHEAST - northeast_corner((r, c), env, state) - elseif dir == SOUTHEAST - southeast_corner((r, c), env, state) - elseif dir == SOUTHWEST - southwest_corner((r, c), env, state) - end - end - - return copy(Q) -end - # Build projectors from SVD and enlarged SW & NW corners function build_projectors( U::AbstractTensorMap{E,3,1}, S, V::AbstractTensorMap{E,1,3}, Q, Q_next diff --git a/test/heisenberg.jl b/test/heisenberg.jl index 8c5ece36..51a8f981 100644 --- a/test/heisenberg.jl +++ b/test/heisenberg.jl @@ -14,12 +14,12 @@ ctm_alg = CTMRG(; maxiter=100, verbosity=2, svd_alg=SVDAdjoint(; fwd_alg=TensorKit.SVD(), rrule_alg=GMRES(; tol=1e-10)), - ctmrgscheme=:sequential, + ctmrgscheme=:simultaneous, ) opt_alg = PEPSOptimize(; boundary_alg=ctm_alg, optimizer=LBFGS(4; maxiter=100, gradtol=1e-3, verbosity=2), - gradient_alg=LinSolver(; solver=GMRES(; tol=1e-6), iterscheme=:diffgauge), + gradient_alg=LinSolver(; solver=GMRES(; tol=1e-6), iterscheme=:fixed), reuse_env=true, ) @@ -37,8 +37,8 @@ result = fixedpoint(psi_init, H, opt_alg, env_init) @test all(@. λ_h > 0 && λ_v > 0) # same test but for 2x2 unit cell -H_2x2 = square_lattice_heisenberg(; unitcell=(2, 2)) -psi_init_2x2 = InfinitePEPS(2, χbond; unitcell=(2, 2)) +H_2x2 = square_lattice_heisenberg(; unitcell=(3, 2)) +psi_init_2x2 = InfinitePEPS(2, χbond; unitcell=(3, 2)) env_init_2x2 = leading_boundary( CTMRGEnv(psi_init_2x2, ComplexSpace(χenv)), psi_init_2x2, ctm_alg ) From 7edf42e0cf1e5b55e0cb62325f031ba6bf426024 Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Thu, 25 Jul 2024 11:42:14 +0200 Subject: [PATCH 074/213] Fix transfer PEPS/PEPO bugs for asymmetric unit cells, test Heisenberg for 1x2 cell instead --- src/environments/transferpeps_environments.jl | 2 +- src/operators/transferpepo.jl | 3 ++- src/operators/transferpeps.jl | 3 ++- test/heisenberg.jl | 18 +++++++++--------- 4 files changed, 14 insertions(+), 12 deletions(-) diff --git a/src/environments/transferpeps_environments.jl b/src/environments/transferpeps_environments.jl index ccc6ff8b..4332c232 100644 --- a/src/environments/transferpeps_environments.jl +++ b/src/environments/transferpeps_environments.jl @@ -119,7 +119,7 @@ function MPSKit.transfer_spectrum( @assert size(above) == size(O) @assert size(below) == size(O) - numrows = size(above, 2) + numrows = size(above, 1) envtype = eltype(init[1]) eigenvals = Vector{Vector{scalartype(envtype)}}(undef, numrows) diff --git a/src/operators/transferpepo.jl b/src/operators/transferpepo.jl index 92a74fae..5215834c 100644 --- a/src/operators/transferpepo.jl +++ b/src/operators/transferpepo.jl @@ -86,7 +86,8 @@ the expectation value of `O` for the state `T`. The partition function is first that the direction `dir` faces north. """ function TransferPEPOMultiline(T::InfinitePEPS, O::InfinitePEPO, dir) - return MPSKit.Multiline(map(cr -> InfiniteTransferPEPO(T, O, dir, cr), 1:size(T, 1))) + rowsize = size(T, mod1(dir, 2)) # depends on dir + return MPSKit.Multiline(map(cr -> InfiniteTransferPEPO(T, O, dir, cr), 1:rowsize)) end # specialize simple case diff --git a/src/operators/transferpeps.jl b/src/operators/transferpeps.jl index a191a0b7..4706c293 100644 --- a/src/operators/transferpeps.jl +++ b/src/operators/transferpeps.jl @@ -52,7 +52,8 @@ the norm of the state `T`. The partition function is first rotated such that the direction `dir` faces north. """ function TransferPEPSMultiline(T::InfinitePEPS, dir) - return MPSKit.Multiline(map(cr -> InfiniteTransferPEPS(T, dir, cr), 1:size(T, 1))) + rowsize = size(T, mod1(dir, 2)) # depends on dir + return MPSKit.Multiline(map(cr -> InfiniteTransferPEPS(T, dir, cr), 1:rowsize)) end """ diff --git a/test/heisenberg.jl b/test/heisenberg.jl index 51a8f981..c7da1642 100644 --- a/test/heisenberg.jl +++ b/test/heisenberg.jl @@ -36,14 +36,14 @@ result = fixedpoint(psi_init, H, opt_alg, env_init) @test result.E ≈ -0.6694421 atol = 1e-2 @test all(@. λ_h > 0 && λ_v > 0) -# same test but for 2x2 unit cell -H_2x2 = square_lattice_heisenberg(; unitcell=(3, 2)) -psi_init_2x2 = InfinitePEPS(2, χbond; unitcell=(3, 2)) -env_init_2x2 = leading_boundary( - CTMRGEnv(psi_init_2x2, ComplexSpace(χenv)), psi_init_2x2, ctm_alg +# same test but for 1x2 unit cell +H_1x2 = square_lattice_heisenberg(; unitcell=(1, 2)) +psi_init_1x2 = InfinitePEPS(2, χbond; unitcell=(1, 2)) +env_init_1x2 = leading_boundary( + CTMRGEnv(psi_init_1x2, ComplexSpace(χenv)), psi_init_1x2, ctm_alg ) -result_2x2 = fixedpoint(psi_init_2x2, H_2x2, opt_alg, env_init_2x2) -λ_h_2x2, λ_v_2x2, = correlation_length(result_2x2.peps, result_2x2.env) +result_1x2 = fixedpoint(psi_init_1x2, H_1x2, opt_alg, env_init_1x2) +λ_h_1x2, λ_v_1x2, = correlation_length(result_1x2.peps, result_1x2.env) -@test result_2x2.E ≈ 4 * result.E atol = 1e-2 -@test all(@. λ_h_2x2 > 0 && λ_v_2x2 > 0) +@test result_1x2.E ≈ 2 * result.E atol = 1e-2 +@test all(@. λ_h_1x2 > 0 && λ_v_1x2 > 0) From 725c53d4c63ea0951bca54ee129ca77278788a4f Mon Sep 17 00:00:00 2001 From: lkdvos Date: Thu, 25 Jul 2024 14:40:30 +0200 Subject: [PATCH 075/213] Remove stray commented-out code --- src/PEPSKit.jl | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/PEPSKit.jl b/src/PEPSKit.jl index 0843a81e..a509c570 100644 --- a/src/PEPSKit.jl +++ b/src/PEPSKit.jl @@ -41,8 +41,6 @@ include("algorithms/toolbox.jl") include("algorithms/peps_opt.jl") -# include("algorithms/ctmrg/ctmrg_all_sides.jl") - include("utility/symmetrization.jl") """ From e52f88fe294bf0fba372c92bc4245ed155cefbac Mon Sep 17 00:00:00 2001 From: Lukas <37111893+lkdvos@users.noreply.github.com> Date: Thu, 25 Jul 2024 14:57:33 +0200 Subject: [PATCH 076/213] Bump version v0.2 (#59) --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 70b0807e..0e0219f4 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "PEPSKit" uuid = "52969e89-939e-4361-9b68-9bc7cde4bdeb" authors = ["Maarten Vandamme", "Paul Brehmer", "Lander Burgelman", "Rui-Zhen Huang", "Daan Maertens", "Lukas Devos Date: Thu, 8 Aug 2024 17:50:35 +0200 Subject: [PATCH 077/213] Clean up symmetrization.jl a bit --- src/utility/symmetrization.jl | 65 +++++++++++++++++------------------ 1 file changed, 31 insertions(+), 34 deletions(-) diff --git a/src/utility/symmetrization.jl b/src/utility/symmetrization.jl index 9b9a3b91..31f2f0a9 100644 --- a/src/utility/symmetrization.jl +++ b/src/utility/symmetrization.jl @@ -1,8 +1,5 @@ -# some basic symmetrization routines for PEPS - abstract type SymmetrizationStyle end -struct None <: SymmetrizationStyle end struct Depth <: SymmetrizationStyle end struct Width <: SymmetrizationStyle end struct Rot <: SymmetrizationStyle end @@ -32,7 +29,7 @@ end # make two TensorMap's have the same spaces, by force if necessary # this is definitely not what you would want to do, but it circumvents having to think # about what hermiticity means at the level of transfer operators, which is something -function _make_it_fit( +function _fit_spaces( y::AbstractTensorMap{S,N₁,N₂}, x::AbstractTensorMap{S,N₁,N₂} ) where {S<:IndexSpace,N₁,N₂} for i in 1:(N₁ + N₂) @@ -48,15 +45,15 @@ function _make_it_fit( end function herm_depth_inv(x::Union{PEPSTensor,PEPOTensor}) - return 0.5 * (x + _make_it_fit(herm_depth(x), x)) + return 0.5 * (x + _fit_spaces(herm_depth(x), x)) end function herm_width_inv(x::Union{PEPSTensor,PEPOTensor}) - return 0.5 * (x + _make_it_fit(herm_width(x), x)) + return 0.5 * (x + _fit_spaces(herm_width(x), x)) end -function herm_height_inv(x::Union{PEPSTensor,PEPOTensor}) - return 0.5 * (x + _make_it_fit(herm_height(x), x)) +function herm_height_inv(x::PEPOTensor) + return 0.5 * (x + _fit_spaces(herm_height(x), x)) end # rotation invariance @@ -64,19 +61,19 @@ end function rot_inv(x) return 0.25 * ( x + - _make_it_fit(rotl90(x), x) + - _make_it_fit(rot180(x), x) + - _make_it_fit(rotr90(x), x) + _fit_spaces(rotl90(x), x) + + _fit_spaces(rot180(x), x) + + _fit_spaces(rotr90(x), x) ) end ## PEPS unit cell symmetrization -PEPSLike = Union{InfinitePEPS,AbstractArray{<:PEPSTensor,2}} +const PEPSLike = Union{InfinitePEPS,AbstractArray{<:PEPSTensor,2}} -symmetrize(p::PEPSLike, ::None) = p +symmetrize!(p::PEPSLike, ::Nothing) = p -function symmetrize(p::PEPSLike, ::Depth) +function symmetrize!(p::PEPSLike, ::Depth) depth, width = size(p) if mod(depth, 2) == 1 for w in 1:width @@ -85,13 +82,13 @@ function symmetrize(p::PEPSLike, ::Depth) end for d in 1:floor(Int, depth / 2) for w in 1:width - p[depth - d + 1, w] = _make_it_fit(herm_depth(p[d, w]), p[depth - d + 1, w]) + p[depth - d + 1, w] = _fit_spaces(herm_depth(p[d, w]), p[depth - d + 1, w]) end end return p end -function symmetrize(p::PEPSLike, ::Width) +function symmetrize!(p::PEPSLike, ::Width) depth, width = size(p) if mod(width, 2) == 1 for d in 1:depth @@ -100,26 +97,26 @@ function symmetrize(p::PEPSLike, ::Width) end for w in 1:floor(Int, width / 2) for d in 1:depth - p[d, width - w + 1] = _make_it_fit(herm_width(p[d, w]), p[d, width - w + 1]) + p[d, width - w + 1] = _fit_spaces(herm_width(p[d, w]), p[d, width - w + 1]) end end return p end -function symmetrize(p::PEPSLike, ::Rot) +function symmetrize!(p::PEPSLike, ::Rot) return error("TODO") end -function symmetrize(p::PEPSLike, ::Full) +function symmetrize!(p::PEPSLike, ::Full) # TODO: clean up this mess... # some auxiliary transformations function symmetrize_corner(x::PEPSTensor) - return 0.5 * (x + _make_it_fit(permute(x', ((5,), (4, 3, 2, 1))), x)) + return 0.5 * (x + _fit_spaces(permute(x', ((5,), (4, 3, 2, 1))), x)) end symmetrize_center(x::PEPSTensor) = herm_depth_inv(rot_inv(x)) function symmetrize_mid_depth(x::PEPSTensor) - return x + _make_it_fit(permute(x', ((5,), (3, 2, 1, 4))), x) + return x + _fit_spaces(permute(x', ((5,), (3, 2, 1, 4))), x) end depth, width = size(p) @@ -135,34 +132,34 @@ function symmetrize(p::PEPSLike, ::Full) for w in 1:floor(Int, width / 2) if d == w p[d, w] = symmetrize_corner(p[d, w]) - p[d, width - w + 1] = _make_it_fit(rotr90(p[d, w]), p[d, width - w + 1]) - p[depth - d + 1, w] = _make_it_fit(herm_depth(p[d, w]), p[depth - d + 1, w]) - p[depth - d + 1, width - w + 1] = _make_it_fit( + p[d, width - w + 1] = _fit_spaces(rotr90(p[d, w]), p[d, width - w + 1]) + p[depth - d + 1, w] = _fit_spaces(herm_depth(p[d, w]), p[depth - d + 1, w]) + p[depth - d + 1, width - w + 1] = _fit_spaces( herm_depth(rotr90(p[d, w])), p[depth - d + 1, width - w + 1] ) elseif odd == 1 && d == ceil(Int, depth / 2) p[d, w] = symmetrize_mid_depth(p[d, w]) - p[w, d] = _make_it_fit(rotr90(p[d, w]), p[w, d]) - p[d, width - w + 1] = _make_it_fit(rot180(p[d, w]), p[d, width - w + 1]) - p[width - w + 1, d] = _make_it_fit( + p[w, d] = _fit_spaces(rotr90(p[d, w]), p[w, d]) + p[d, width - w + 1] = _fit_spaces(rot180(p[d, w]), p[d, width - w + 1]) + p[width - w + 1, d] = _fit_spaces( herm_depth(rotr90(p[d, w])), p[width - w + 1, d] ) else - p[depth - d + 1, w] = _make_it_fit(herm_depth(p[d, w]), p[depth - d + 1, w]) - p[w, depth - d + 1] = _make_it_fit(rotr90(p[d, w]), p[w, depth - d + 1]) - p[width - w + 1, depth - d + 1] = _make_it_fit( + p[depth - d + 1, w] = _fit_spaces(herm_depth(p[d, w]), p[depth - d + 1, w]) + p[w, depth - d + 1] = _fit_spaces(rotr90(p[d, w]), p[w, depth - d + 1]) + p[width - w + 1, depth - d + 1] = _fit_spaces( herm_depth(rotr90(p[d, w])), [width - w + 1, depth - d + 1] ) - p[w, d] = _make_it_fit(rotr90(herm_depth(p[d, w])), p[w, d]) - p[width - w + 1, d] = _make_it_fit( + p[w, d] = _fit_spaces(rotr90(herm_depth(p[d, w])), p[w, d]) + p[width - w + 1, d] = _fit_spaces( herm_depth(rotr90(herm_depth(p[d, w]))), p[width - w + 1, d] ) - p[d, width - w + 1] = _make_it_fit( + p[d, width - w + 1] = _fit_spaces( rotr90(rotr90(herm_depth(p[d, w]))), p[d, width - w + 1] ) - p[depth - d + 1, width - w + 1] = _make_it_fit( + p[depth - d + 1, width - w + 1] = _fit_spaces( herm_depth(rotr90(rotr90(herm_depth(p[d, w])))), p[depth - d + 1, width - w + 1], ) From 17642262bdeb7d663c57ab2495d3213439df5d20 Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Fri, 9 Aug 2024 13:44:13 +0200 Subject: [PATCH 078/213] Add J1-J2 model, add callback function to fixedpoint optimization, add J1-J2 test --- src/PEPSKit.jl | 3 ++- src/algorithms/peps_opt.jl | 13 +++++++++---- src/operators/models.jl | 32 ++++++++++++++++++++++++++++++++ test/heisenberg.jl | 8 ++++---- test/j1j2_model.jl | 37 +++++++++++++++++++++++++++++++++++++ test/tf_ising.jl | 8 ++++---- 6 files changed, 88 insertions(+), 13 deletions(-) create mode 100644 test/j1j2_model.jl diff --git a/src/PEPSKit.jl b/src/PEPSKit.jl index a509c570..9dce09d0 100644 --- a/src/PEPSKit.jl +++ b/src/PEPSKit.jl @@ -87,6 +87,7 @@ export InfinitePEPO, InfiniteTransferPEPO export initializeMPS, initializePEPS export symmetrize, None, Depth, Full export showtypeofgrad -export square_lattice_tf_ising, square_lattice_heisenberg, square_lattice_pwave +export square_lattice_tf_ising, square_lattice_heisenberg, square_lattice_j1j2 +export square_lattice_pwave end # module diff --git a/src/algorithms/peps_opt.jl b/src/algorithms/peps_opt.jl index 2d6c93ff..d04bd305 100644 --- a/src/algorithms/peps_opt.jl +++ b/src/algorithms/peps_opt.jl @@ -121,7 +121,7 @@ function PEPSOptimize(; end """ - fixedpoint(ψ₀::InfinitePEPS{T}, H, alg::PEPSOptimize, [env₀::CTMRGEnv]) where {T} + fixedpoint(ψ₀::InfinitePEPS{T}, H, alg::PEPSOptimize, [env₀::CTMRGEnv]; callback=identity) where {T} Optimize `ψ₀` with respect to the Hamiltonian `H` according to the parameters supplied in `alg`. The initial environment `env₀` serves as an initial guess for the first CTMRG run. @@ -137,7 +137,11 @@ The function returns a `NamedTuple` which contains the following entries: - `numfg`: total number of calls to the energy function """ function fixedpoint( - ψ₀::InfinitePEPS{T}, H, alg::PEPSOptimize, env₀::CTMRGEnv=CTMRGEnv(ψ₀, field(T)^20) + ψ₀::InfinitePEPS{T}, + H, + alg::PEPSOptimize, + env₀::CTMRGEnv=CTMRGEnv(ψ₀, field(T)^20); + callback=(args...) -> identity(args), ) where {T} (peps, env), E, ∂E, numfg, convhistory = optimize( (ψ₀, env₀), alg.optimizer; retract=my_retract, inner=my_inner @@ -155,8 +159,9 @@ function fixedpoint( end return costfun(ψ, envs´, H) end - # withgradient returns tuple of gradients `g` - return E, only(g) + g = only(g) # withgradient returns tuple of gradients `g` + peps, envs, E, g = callback(peps, envs, E, g) + return E, g end return (; peps, diff --git a/src/operators/models.jl b/src/operators/models.jl index 5dad5456..21e7bdc6 100644 --- a/src/operators/models.jl +++ b/src/operators/models.jl @@ -38,6 +38,38 @@ function square_lattice_heisenberg( return repeat(nearest_neighbour_hamiltonian(lattice, H / 4), unitcell...) end +""" + square_lattice_j1j2(::Type{T}=ComplexF64; J1=1, J2=1, unitcell=(1, 1), sublattice=true) + + +Square lattice J₁-J₂ model. The `sublattice` kwarg enables a single site unit cell via a +sublattice rotation. +""" +function square_lattice_j1j2( + ::Type{T}=ComplexF64; J1=1, J2=1, unitcell::Tuple{Int,Int}=(1, 1), sublattice=true +) where {T<:Number} + physical_space = ComplexSpace(2) + lattice = fill(physical_space, 1, 1) + σx = TensorMap(T[0 1; 1 0], physical_space, physical_space) + σy = TensorMap(T[0 im; -im 0], physical_space, physical_space) + σz = TensorMap(T[1 0; 0 -1], physical_space, physical_space) + h_AA = σx ⊗ σx + σy ⊗ σy + σz ⊗ σz + h_AB = sublattice ? -σx ⊗ σx + σy ⊗ σy - σz ⊗ σz : h_AA # Apply sublattice rotation + + terms = [] + for I in eachindex(IndexCartesian(), lattice) + nearest_x = I + CartesianIndex(1, 0) + nearest_y = I + CartesianIndex(0, 1) + next_xy = I + CartesianIndex(1, 1) + push!(terms, (I, nearest_x) => J1 / 4 * h_AB) + push!(terms, (I, nearest_y) => J1 / 4 * h_AB) + push!(terms, (I, next_xy) => J2 / 4 * h_AA) + push!(terms, (nearest_x, nearest_y) => J2 / 4 * h_AA) + end + + return repeat(LocalOperator(lattice, terms...), unitcell...) +end + """ square_lattice_pwave(::Type{T}=ComplexF64; t=1, μ=2, Δ=1, unitcell=(1, 1)) diff --git a/test/heisenberg.jl b/test/heisenberg.jl index c7da1642..c299e31a 100644 --- a/test/heisenberg.jl +++ b/test/heisenberg.jl @@ -31,10 +31,10 @@ env_init = leading_boundary(CTMRGEnv(psi_init, ComplexSpace(χenv)), psi_init, c # find fixedpoint result = fixedpoint(psi_init, H, opt_alg, env_init) -λ_h, λ_v, = correlation_length(result.peps, result.env) +ξ_h, ξ_v, = correlation_length(result.peps, result.env) @test result.E ≈ -0.6694421 atol = 1e-2 -@test all(@. λ_h > 0 && λ_v > 0) +@test all(@. ξ_h > 0 && ξ_v > 0) # same test but for 1x2 unit cell H_1x2 = square_lattice_heisenberg(; unitcell=(1, 2)) @@ -43,7 +43,7 @@ env_init_1x2 = leading_boundary( CTMRGEnv(psi_init_1x2, ComplexSpace(χenv)), psi_init_1x2, ctm_alg ) result_1x2 = fixedpoint(psi_init_1x2, H_1x2, opt_alg, env_init_1x2) -λ_h_1x2, λ_v_1x2, = correlation_length(result_1x2.peps, result_1x2.env) +ξ_h_1x2, ξ_v_1x2, = correlation_length(result_1x2.peps, result_1x2.env) @test result_1x2.E ≈ 2 * result.E atol = 1e-2 -@test all(@. λ_h_1x2 > 0 && λ_v_1x2 > 0) +@test all(@. ξ_h_1x2 > 0 && ξ_v_1x2 > 0) diff --git a/test/j1j2_model.jl b/test/j1j2_model.jl new file mode 100644 index 00000000..23df808f --- /dev/null +++ b/test/j1j2_model.jl @@ -0,0 +1,37 @@ +using Test +using Random +using PEPSKit +using TensorKit +using KrylovKit +using OptimKit + +# initialize parameters +χbond = 2 +χenv = 32 +ctm_alg = CTMRG(; + tol=1e-10, + miniter=4, + maxiter=100, + verbosity=2, + svd_alg=SVDAdjoint(; fwd_alg=TensorKit.SVD(), rrule_alg=GMRES(; tol=1e-10)), + ctmrgscheme=:simultaneous, +) +opt_alg = PEPSOptimize(; + boundary_alg=ctm_alg, + optimizer=LBFGS(4; maxiter=100, gradtol=1e-3, verbosity=2), + gradient_alg=LinSolver(; solver=GMRES(; tol=1e-6), iterscheme=:fixed), + reuse_env=true, +) + +# initialize states +Random.seed!(91283219347) +H = square_lattice_j1j2(; J2=0.25) +psi_init = InfinitePEPS(2, χbond) +env_init = leading_boundary(CTMRGEnv(psi_init, ComplexSpace(χenv)), psi_init, ctm_alg) + +# find fixedpoint +result = fixedpoint(psi_init, H, opt_alg, env_init) +ξ_h, ξ_v, = correlation_length(result.peps, result.env) + +@test result.E ≈ -0.5618837021945925 atol = 1e-3 +@test all(@. ξ_h > 0 && ξ_v > 0) diff --git a/test/tf_ising.jl b/test/tf_ising.jl index f8094391..781f8e63 100644 --- a/test/tf_ising.jl +++ b/test/tf_ising.jl @@ -41,7 +41,7 @@ env_init = leading_boundary(CTMRGEnv(psi_init, ComplexSpace(χenv)), psi_init, c # find fixedpoint result = fixedpoint(psi_init, H, opt_alg, env_init) -λ_h, λ_v, = correlation_length(result.peps, result.env) +ξ_h, ξ_v, = correlation_length(result.peps, result.env) # compute magnetization σx = TensorMap(scalartype(psi_init)[0 1; 1 0], ℂ^2, ℂ^2) @@ -55,6 +55,6 @@ magn = expectation_value(result.peps, M, result.env) # find fixedpoint in polarized phase and compute correlations lengths H_polar = square_lattice_tf_ising(; h=4.5) result_polar = fixedpoint(psi_init, H_polar, opt_alg, env_init) -λ_h_polar, λ_v_polar, = correlation_length(result_polar.peps, result_polar.env) -@test λ_h_polar < λ_h -@test λ_v_polar < λ_v +ξ_h_polar, ξ_v_polar, = correlation_length(result_polar.peps, result_polar.env) +@test ξ_h_polar < ξ_h +@test ξ_v_polar < ξ_v From 0009d5361f37714458706a71120619d337aa8057 Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Fri, 9 Aug 2024 16:12:48 +0200 Subject: [PATCH 079/213] Export symmetrization, add symmetrize_callback --- src/PEPSKit.jl | 2 +- src/algorithms/peps_opt.jl | 6 +++++- src/utility/symmetrization.jl | 24 +++++++++++++++--------- test/ctmrg/gaugefix.jl | 16 ++++++++-------- test/j1j2_model.jl | 16 ++++++++++++---- 5 files changed, 41 insertions(+), 23 deletions(-) diff --git a/src/PEPSKit.jl b/src/PEPSKit.jl index 9dce09d0..10795503 100644 --- a/src/PEPSKit.jl +++ b/src/PEPSKit.jl @@ -85,7 +85,7 @@ export fixedpoint export InfinitePEPS, InfiniteTransferPEPS export InfinitePEPO, InfiniteTransferPEPO export initializeMPS, initializePEPS -export symmetrize, None, Depth, Full +export symmetrize!, ReflectDepth, ReflectWidth, RotateReflect, symmetrize_callback export showtypeofgrad export square_lattice_tf_ising, square_lattice_heisenberg, square_lattice_j1j2 export square_lattice_pwave diff --git a/src/algorithms/peps_opt.jl b/src/algorithms/peps_opt.jl index d04bd305..9bb4daae 100644 --- a/src/algorithms/peps_opt.jl +++ b/src/algorithms/peps_opt.jl @@ -121,12 +121,16 @@ function PEPSOptimize(; end """ - fixedpoint(ψ₀::InfinitePEPS{T}, H, alg::PEPSOptimize, [env₀::CTMRGEnv]; callback=identity) where {T} + fixedpoint(ψ₀::InfinitePEPS{T}, H, alg::PEPSOptimize, [env₀::CTMRGEnv]; + callback=(args...) -> identity(args)) where {T} Optimize `ψ₀` with respect to the Hamiltonian `H` according to the parameters supplied in `alg`. The initial environment `env₀` serves as an initial guess for the first CTMRG run. By default, a random initial environment is used. +The `callback` kwarg can be used to insert a function call after each optimization step +which maps `peps, envs, E, grad = callback(peps, envs, E, grad)`. + The function returns a `NamedTuple` which contains the following entries: - `peps`: final `InfinitePEPS` - `env`: `CTMRGEnv` corresponding to the final PEPS diff --git a/src/utility/symmetrization.jl b/src/utility/symmetrization.jl index 31f2f0a9..948d9dd8 100644 --- a/src/utility/symmetrization.jl +++ b/src/utility/symmetrization.jl @@ -1,9 +1,9 @@ abstract type SymmetrizationStyle end -struct Depth <: SymmetrizationStyle end -struct Width <: SymmetrizationStyle end -struct Rot <: SymmetrizationStyle end -struct Full <: SymmetrizationStyle end +struct ReflectDepth <: SymmetrizationStyle end +struct ReflectWidth <: SymmetrizationStyle end +struct Rotate <: SymmetrizationStyle end +struct RotateReflect <: SymmetrizationStyle end # some rather shady definitions for 'hermitian conjugate' at the level of a single tensor function herm_depth(x::PEPSTensor) @@ -73,7 +73,7 @@ const PEPSLike = Union{InfinitePEPS,AbstractArray{<:PEPSTensor,2}} symmetrize!(p::PEPSLike, ::Nothing) = p -function symmetrize!(p::PEPSLike, ::Depth) +function symmetrize!(p::PEPSLike, ::ReflectDepth) depth, width = size(p) if mod(depth, 2) == 1 for w in 1:width @@ -88,11 +88,11 @@ function symmetrize!(p::PEPSLike, ::Depth) return p end -function symmetrize!(p::PEPSLike, ::Width) +function symmetrize!(p::PEPSLike, ::ReflectWidth) depth, width = size(p) if mod(width, 2) == 1 for d in 1:depth - p[d, ceil(Int, width / 2)] = herm_width_inv(p[d, ceil(Int, width / 2), h]) + p[d, ceil(Int, width / 2)] = herm_width_inv(p[d, ceil(Int, width / 2)]) end end for w in 1:floor(Int, width / 2) @@ -103,11 +103,11 @@ function symmetrize!(p::PEPSLike, ::Width) return p end -function symmetrize!(p::PEPSLike, ::Rot) +function symmetrize!(p::PEPSLike, ::Rotate) return error("TODO") end -function symmetrize!(p::PEPSLike, ::Full) +function symmetrize!(p::PEPSLike, ::RotateReflect) # TODO: clean up this mess... # some auxiliary transformations @@ -168,3 +168,9 @@ function symmetrize!(p::PEPSLike, ::Full) end return p end + +function symmetrize_callback(peps, envs, E, grad, symm::SymmetrizationStyle) + peps_symm = symmetrize!(deepcopy(peps), symm) + grad_symm = symmetrize!(deepcopy(grad), symm) + return peps_symm, envs, E, grad_symm +end diff --git a/test/ctmrg/gaugefix.jl b/test/ctmrg/gaugefix.jl index ff51d0a7..21ec5b48 100644 --- a/test/ctmrg/gaugefix.jl +++ b/test/ctmrg/gaugefix.jl @@ -12,16 +12,16 @@ schemes = [:simultaneous, :sequential] atol = 1e-4 verbosity = 2 -function _make_symmetric(psi) +function _make_symmetric!(psi) if ==(size(psi)...) - return PEPSKit.symmetrize(psi, PEPSKit.Full()) + return symmetrize!(psi, RotateReflect()) else - return PEPSKit.symmetrize(PEPSKit.symmetrize(psi, PEPSKit.Depth()), PEPSKit.Width()) + return symmetrize!(symmetrize!(psi, ReflectDepth()), ReflectWidth()) end end # If I can't make the rng seed behave, I'll just randomly define a peps somehow -function semi_random_peps!(psi::InfinitePEPS) +function _semi_random_peps!(psi::InfinitePEPS) i = 0 A′ = map(psi.A) do a for (_, b) in blocks(a) @@ -44,8 +44,8 @@ end ctm_space = ComplexSpace(χ) psi = InfinitePEPS(undef, T, physical_space, peps_space; unitcell) - semi_random_peps!(psi) - psi = _make_symmetric(psi) + _semi_random_peps!(psi) + _make_symmetric!(psi) Random.seed!(987654321) # Seed RNG to make random environment consistent ctm = CTMRGEnv(psi, ctm_space) @@ -69,8 +69,8 @@ end ctm_space = Z2Space(0 => χ ÷ 2, 1 => χ ÷ 2) psi = InfinitePEPS(undef, T, physical_space, peps_space; unitcell) - semi_random_peps!(psi) - psi = _make_symmetric(psi) + _semi_random_peps!(psi) + _make_symmetric!(psi) Random.seed!(987654321) # Seed RNG to make random environment consistent psi = InfinitePEPS(physical_space, peps_space; unitcell) diff --git a/test/j1j2_model.jl b/test/j1j2_model.jl index 23df808f..0ea26951 100644 --- a/test/j1j2_model.jl +++ b/test/j1j2_model.jl @@ -7,7 +7,7 @@ using OptimKit # initialize parameters χbond = 2 -χenv = 32 +χenv = 16 ctm_alg = CTMRG(; tol=1e-10, miniter=4, @@ -19,7 +19,7 @@ ctm_alg = CTMRG(; opt_alg = PEPSOptimize(; boundary_alg=ctm_alg, optimizer=LBFGS(4; maxiter=100, gradtol=1e-3, verbosity=2), - gradient_alg=LinSolver(; solver=GMRES(; tol=1e-6), iterscheme=:fixed), + gradient_alg=LinSolver(; solver=GMRES(; tol=1e-6), iterscheme=:diffgauge), reuse_env=true, ) @@ -27,11 +27,19 @@ opt_alg = PEPSOptimize(; Random.seed!(91283219347) H = square_lattice_j1j2(; J2=0.25) psi_init = InfinitePEPS(2, χbond) +psi_init = PEPSKit.symmetrize!(psi_init, PEPSKit.Full()) env_init = leading_boundary(CTMRGEnv(psi_init, ComplexSpace(χenv)), psi_init, ctm_alg) # find fixedpoint -result = fixedpoint(psi_init, H, opt_alg, env_init) +cb = (args...) -> PEPSKit.symmetrize_callback(args..., RotateReflect()) +result = fixedpoint(psi_init, H, opt_alg, env_init)#; callback=cb) ξ_h, ξ_v, = correlation_length(result.peps, result.env) +p = symmetrize!(deepcopy(result.peps), RotateReflect()) +ξ_h, ξ_v, = correlation_length(p, leading_boundary(result.env, p, ctm_alg)) + +# compare against Juraj Hasik's data: +# https://github.com/jurajHasik/j1j2_ipeps_states/blob/main/single-site_pg-C4v-A1/j20.25/state_1s_A1_j20.25_D2_chi_opt48.dat +ξ_ref = -1 / log(0.2723596743547324) @test result.E ≈ -0.5618837021945925 atol = 1e-3 -@test all(@. ξ_h > 0 && ξ_v > 0) +@test all(@. isapprox(ξ_h, ξ_ref; atol=1e-1) && isapprox(ξ_v, ξ_ref; atol=1e-1)) From ab5f16afc48547db02c3f3e2b89d9715a4934447 Mon Sep 17 00:00:00 2001 From: Lukas <37111893+lkdvos@users.noreply.github.com> Date: Tue, 13 Aug 2024 16:14:52 +0200 Subject: [PATCH 080/213] Fix virtual space error for larger unit cells (#60) * Add test with varying spaces * Fix off-by-one error * Remove gaugefix from regular ctmrg * Remove debug code * Fix trscheme name * fix discrepancy between sequential and simultaneous ctmrg * Remove unused function * One more fix in the indexing --- .../contractions/ctmrg_contractions.jl | 8 +-- src/algorithms/ctmrg/ctmrg.jl | 61 ++++++------------- test/ctmrg/ctmrgschemes.jl | 20 ++++++ 3 files changed, 41 insertions(+), 48 deletions(-) diff --git a/src/algorithms/contractions/ctmrg_contractions.jl b/src/algorithms/contractions/ctmrg_contractions.jl index af537fe6..69e4c9e4 100644 --- a/src/algorithms/contractions/ctmrg_contractions.jl +++ b/src/algorithms/contractions/ctmrg_contractions.jl @@ -273,7 +273,7 @@ Apply bottom projector to southwest corner and south edge. function renormalize_bottom_corner((row, col), envs::CTMRGEnv, projectors) C_southwest = envs.corners[SOUTHWEST, row, _prev(col, end)] E_south = envs.edges[SOUTH, row, col] - P_bottom = projectors[1][_prev(row, end), col] + P_bottom = projectors[1][row, col] return @autoopt @tensor corner[χ_in; χ_out] := E_south[χ_in D1 D2; χ1] * C_southwest[χ1; χ2] * P_bottom[χ2 D1 D2; χ_out] end @@ -292,7 +292,7 @@ Apply top projector to northwest corner and north edge. function renormalize_top_corner((row, col), envs::CTMRGEnv, projectors) C_northwest = envs.corners[NORTHWEST, row, _prev(col, end)] E_north = envs.edges[NORTH, row, col] - P_top = projectors[2][row, col] + P_top = projectors[2][_next(row, end), col] return @autoopt @tensor corner[χ_in; χ_out] := P_top[χ_in; χ1 D1 D2] * C_northwest[χ1; χ2] * E_north[χ2 D1 D2; χ_out] end @@ -448,8 +448,8 @@ function renormalize_west_edge( # For sequential CTMRG scheme ) return renormalize_west_edge( envs.edges[WEST, row, _prev(col, end)], - projectors[1][_prev(row, end), col], - projectors[2][row, col], + projectors[1][row, col], + projectors[2][_next(row, end), col], ket[row, col], bra[row, col], ) diff --git a/src/algorithms/ctmrg/ctmrg.jl b/src/algorithms/ctmrg/ctmrg.jl index ed8d3dd2..4c99a6c2 100644 --- a/src/algorithms/ctmrg/ctmrg.jl +++ b/src/algorithms/ctmrg/ctmrg.jl @@ -111,35 +111,24 @@ function MPSKit.leading_boundary(envinit, state, alg::CTMRG) return LoggingExtras.withlevel(; alg.verbosity) do ctmrg_loginit!(log, η, N) - local iter - for outer iter in 1:(alg.maxiter) + for iter in 1:(alg.maxiter) env, = ctmrg_iter(state, env, alg) # Grow and renormalize in all 4 directions + η, CS, TS = calc_convergence(env, CS, TS) N = norm(state, env) ctmrg_logiter!(log, iter, η, N) - (iter > alg.miniter && η <= alg.tol) && break - end - - # Do one final iteration that does not change the spaces - alg_fixed = CTMRG(; - verbosity=alg.verbosity, - svd_alg=alg.projector_alg.svd_alg, - trscheme=FixedSpaceTruncation(), - ctmrgscheme=ctmrgscheme(alg), - ) - env′, = ctmrg_iter(state, env, alg_fixed) - envfix, = gauge_fix(env, env′) - - η = calc_elementwise_convergence(envfix, env; atol=alg.tol^(1 / 2)) - N = norm(state, envfix) - - if η < alg.tol^(1 / 2) - ctmrg_logfinish!(log, iter, η, N) - else - ctmrg_logcancel!(log, iter, η, N) + if η ≤ alg.tol + ctmrg_logfinish!(log, iter, η, N) + break + end + if iter == alg.maxiter + ctmrg_logcancel!(log, iter, η, N) + else + ctmrg_logiter!(log, iter, η, N) + end end - return envfix + return env end end @@ -248,10 +237,10 @@ function ctmrg_projectors( # @fwdthreads for (r, c) in directions for (r, c) in directions # SVD half-infinite environment - r′ = _next(r, size(envs.corners, 2)) - QQ = halfinfinite_environment(enlarged_envs[1][r′, c], enlarged_envs[2][r, c]) + r′ = _prev(r, size(envs.corners, 2)) + QQ = halfinfinite_environment(enlarged_envs[1][r, c], enlarged_envs[2][r′, c]) - trscheme = truncation_scheme(projector_alg, envs.edges[WEST, r, c]) + trscheme = truncation_scheme(projector_alg, envs.edges[WEST, r′, c]) svd_alg = svd_algorithm(projector_alg, (WEST, r, c)) U, S, V, ϵ_local = PEPSKit.tsvd!(QQ, svd_alg; trunc=trscheme) ϵ = max(ϵ, ϵ_local / norm(S)) @@ -266,7 +255,7 @@ function ctmrg_projectors( # Compute projectors P_bottom[r, c], P_top[r, c] = build_projectors( - U, S, V, enlarged_envs[1][r′, c], enlarged_envs[2][r, c] + U, S, V, enlarged_envs[1][r, c], enlarged_envs[2][r′, c] ) end @@ -301,7 +290,7 @@ function ctmrg_projectors( enlarged_envs[dir, r, c], enlarged_envs[_next(dir, 4), next_rc...] ) - trscheme = truncation_scheme(projector_alg, envs.edges[dir, r, c]) + trscheme = truncation_scheme(projector_alg, envs.edges[dir, next_rc...]) svd_alg = svd_algorithm(projector_alg, (dir, r, c)) U_local, S_local, V_local, ϵ_local = PEPSKit.tsvd!(QQ, svd_alg; trunc=trscheme) U[dir, r, c] = U_local @@ -371,22 +360,6 @@ function ctmrg_renormalize(projectors, state, envs, ::SequentialCTMRG) return CTMRGEnv(copy(corners), copy(edges)) end -# Apply projectors to entire left half-environment to grow SW & NW corners, and W edge -function grow_env_left( - peps, P_bottom, P_top, corners_SW, corners_NW, edge_S, edge_W, edge_N -) - @autoopt @tensor corner_SW′[χ_E; χ_N] := - corners_SW[χ1; χ2] * edge_S[χ_E D1 D2; χ1] * P_bottom[χ2 D1 D2; χ_N] - @autoopt @tensor corner_NW′[χ_S; χ_E] := - corners_NW[χ1; χ2] * edge_N[χ2 D1 D2; χ_E] * P_top[χ_S; χ1 D1 D2] - @autoopt @tensor edge_W′[χ_S D_Eabove D_Ebelow; χ_N] := - edge_W[χ1 D1 D2; χ2] * - peps[d; D3 D_Eabove D5 D1] * - conj(peps[d; D4 D_Ebelow D6 D2]) * - P_bottom[χ2 D3 D4; χ_N] * - P_top[χ_S; χ1 D5 D6] - return corner_SW′, corner_NW′, edge_W′ -end function ctmrg_renormalize(enlarged_envs, projectors, state, envs, ::SimultaneousCTMRG) corners = Zygote.Buffer(envs.corners) edges = Zygote.Buffer(envs.edges) diff --git a/test/ctmrg/ctmrgschemes.jl b/test/ctmrg/ctmrgschemes.jl index 39185905..5f969ce9 100644 --- a/test/ctmrg/ctmrgschemes.jl +++ b/test/ctmrg/ctmrgschemes.jl @@ -50,3 +50,23 @@ unitcells = [(1, 1), (3, 4)] E_simultaneous = costfun(psi, env_simultaneous, H) @test E_sequential ≈ E_simultaneous rtol = 1e-4 end + +# test fixedspace actually fixes space +@testset "Fixedspace truncation ($scheme)" for scheme in [:sequential, :simultaneous] + ctm_alg = CTMRG(; + tol=1e-6, + maxiter=1, + verbosity=0, + ctmrgscheme=scheme, + trscheme=FixedSpaceTruncation(), + ) + Ds = fill(2, 3, 3) + χs = [16 17 18; 15 20 21; 14 19 22] + psi = InfinitePEPS(Ds, Ds, Ds) + env = CTMRGEnv(psi, rand(10:20, 3, 3), rand(10:20, 3, 3)) + env2 = leading_boundary(env, psi, ctm_alg) + + # check that the space is fixed + @test all(space.(env.corners) .== space.(env2.corners)) + @test all(space.(env.edges) .== space.(env2.edges)) +end From ea0427d7438ce16a4bb73f8e0b816bf8f1c79f5a Mon Sep 17 00:00:00 2001 From: Lukas <37111893+lkdvos@users.noreply.github.com> Date: Wed, 14 Aug 2024 13:39:26 +0200 Subject: [PATCH 081/213] Bump version v0.2.1 --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 0e0219f4..236d8e5a 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "PEPSKit" uuid = "52969e89-939e-4361-9b68-9bc7cde4bdeb" authors = ["Maarten Vandamme", "Paul Brehmer", "Lander Burgelman", "Rui-Zhen Huang", "Daan Maertens", "Lukas Devos Date: Wed, 14 Aug 2024 15:45:21 +0200 Subject: [PATCH 082/213] Add docstrings, clean up J1-J2 test --- src/PEPSKit.jl | 2 +- src/utility/symmetrization.jl | 127 +++++++++++++++++++++++----------- test/j1j2_model.jl | 11 ++- 3 files changed, 90 insertions(+), 50 deletions(-) diff --git a/src/PEPSKit.jl b/src/PEPSKit.jl index 10795503..b9a6a8df 100644 --- a/src/PEPSKit.jl +++ b/src/PEPSKit.jl @@ -85,7 +85,7 @@ export fixedpoint export InfinitePEPS, InfiniteTransferPEPS export InfinitePEPO, InfiniteTransferPEPO export initializeMPS, initializePEPS -export symmetrize!, ReflectDepth, ReflectWidth, RotateReflect, symmetrize_callback +export ReflectDepth, ReflectWidth, RotateReflect, symmetrize!, symmetrize_callback export showtypeofgrad export square_lattice_tf_ising, square_lattice_heisenberg, square_lattice_j1j2 export square_lattice_pwave diff --git a/src/utility/symmetrization.jl b/src/utility/symmetrization.jl index 948d9dd8..e175233f 100644 --- a/src/utility/symmetrization.jl +++ b/src/utility/symmetrization.jl @@ -1,11 +1,32 @@ abstract type SymmetrizationStyle end +""" + struct ReflectDepth <: SymmetrizationStyle + +Reflection symmmetrization along the horizontal axis, such that north and south are mirrored. +""" struct ReflectDepth <: SymmetrizationStyle end + +""" + struct ReflectWidth <: SymmetrizationStyle + +Reflection symmmetrization along the vertical axis, such that east and west are mirrored. +""" struct ReflectWidth <: SymmetrizationStyle end + +# TODO struct Rotate <: SymmetrizationStyle end + +""" + struct RotateReflect <: SymmetrizationStyle + +Full reflection and rotation symmmetrization, such that reflection along the horizontal and +vertical axis as well as π/2 rotations leave the object invariant. +""" struct RotateReflect <: SymmetrizationStyle end # some rather shady definitions for 'hermitian conjugate' at the level of a single tensor + function herm_depth(x::PEPSTensor) return permute(x', ((5,), (3, 2, 1, 4))) end @@ -67,47 +88,54 @@ function rot_inv(x) ) end -## PEPS unit cell symmetrization +# PEPS unit cell symmetrization -const PEPSLike = Union{InfinitePEPS,AbstractArray{<:PEPSTensor,2}} +""" + symmetrize!(peps::InfinitePEPS, ::SymmetrizationStyle) -symmetrize!(p::PEPSLike, ::Nothing) = p +Symmetrize a PEPS using the given `SymmetrizationStyle` in-place. +""" +symmetrize!(peps::InfinitePEPS, ::Nothing) = peps -function symmetrize!(p::PEPSLike, ::ReflectDepth) - depth, width = size(p) +function symmetrize!(peps::InfinitePEPS, ::ReflectDepth) + depth, width = size(peps) if mod(depth, 2) == 1 for w in 1:width - p[ceil(Int, depth / 2), w] = herm_depth_inv(p[ceil(Int, depth / 2), w]) + peps[ceil(Int, depth / 2), w] = herm_depth_inv(peps[ceil(Int, depth / 2), w]) end end for d in 1:floor(Int, depth / 2) for w in 1:width - p[depth - d + 1, w] = _fit_spaces(herm_depth(p[d, w]), p[depth - d + 1, w]) + peps[depth - d + 1, w] = _fit_spaces( + herm_depth(peps[d, w]), peps[depth - d + 1, w] + ) end end - return p + return peps end -function symmetrize!(p::PEPSLike, ::ReflectWidth) - depth, width = size(p) +function symmetrize!(peps::InfinitePEPS, ::ReflectWidth) + depth, width = size(peps) if mod(width, 2) == 1 for d in 1:depth - p[d, ceil(Int, width / 2)] = herm_width_inv(p[d, ceil(Int, width / 2)]) + peps[d, ceil(Int, width / 2)] = herm_width_inv(peps[d, ceil(Int, width / 2)]) end end for w in 1:floor(Int, width / 2) for d in 1:depth - p[d, width - w + 1] = _fit_spaces(herm_width(p[d, w]), p[d, width - w + 1]) + peps[d, width - w + 1] = _fit_spaces( + herm_width(peps[d, w]), peps[d, width - w + 1] + ) end end - return p + return peps end -function symmetrize!(p::PEPSLike, ::Rotate) +function symmetrize!(peps::InfinitePEPS, ::Rotate) return error("TODO") end -function symmetrize!(p::PEPSLike, ::RotateReflect) +function symmetrize!(peps::InfinitePEPS, symm::RotateReflect) # TODO: clean up this mess... # some auxiliary transformations @@ -119,56 +147,71 @@ function symmetrize!(p::PEPSLike, ::RotateReflect) return x + _fit_spaces(permute(x', ((5,), (3, 2, 1, 4))), x) end - depth, width = size(p) - depth == width || error("This only works for square unit cells.") + depth, width = size(peps) + depth == width || ArgumentError("$symm can only be applied to square unit cells") odd = mod(depth, 2) if odd == 1 - p[ceil(Int, depth / 2), ceil(Int, width / 2)] = symmetrize_center( - p[ceil(Int, depth / 2), ceil(Int, width / 2)] + peps[ceil(Int, depth / 2), ceil(Int, width / 2)] = symmetrize_center( + peps[ceil(Int, depth / 2), ceil(Int, width / 2)] ) end for d in 1:ceil(Int, depth / 2) for w in 1:floor(Int, width / 2) if d == w - p[d, w] = symmetrize_corner(p[d, w]) - p[d, width - w + 1] = _fit_spaces(rotr90(p[d, w]), p[d, width - w + 1]) - p[depth - d + 1, w] = _fit_spaces(herm_depth(p[d, w]), p[depth - d + 1, w]) - p[depth - d + 1, width - w + 1] = _fit_spaces( - herm_depth(rotr90(p[d, w])), p[depth - d + 1, width - w + 1] + peps[d, w] = symmetrize_corner(peps[d, w]) + peps[d, width - w + 1] = _fit_spaces( + rotr90(peps[d, w]), peps[d, width - w + 1] + ) + peps[depth - d + 1, w] = _fit_spaces( + herm_depth(peps[d, w]), peps[depth - d + 1, w] + ) + peps[depth - d + 1, width - w + 1] = _fit_spaces( + herm_depth(rotr90(peps[d, w])), peps[depth - d + 1, width - w + 1] ) elseif odd == 1 && d == ceil(Int, depth / 2) - p[d, w] = symmetrize_mid_depth(p[d, w]) - p[w, d] = _fit_spaces(rotr90(p[d, w]), p[w, d]) - p[d, width - w + 1] = _fit_spaces(rot180(p[d, w]), p[d, width - w + 1]) - p[width - w + 1, d] = _fit_spaces( - herm_depth(rotr90(p[d, w])), p[width - w + 1, d] + peps[d, w] = symmetrize_mid_depth(peps[d, w]) + peps[w, d] = _fit_spaces(rotr90(peps[d, w]), peps[w, d]) + peps[d, width - w + 1] = _fit_spaces( + rot180(peps[d, w]), peps[d, width - w + 1] + ) + peps[width - w + 1, d] = _fit_spaces( + herm_depth(rotr90(peps[d, w])), peps[width - w + 1, d] ) else - p[depth - d + 1, w] = _fit_spaces(herm_depth(p[d, w]), p[depth - d + 1, w]) - p[w, depth - d + 1] = _fit_spaces(rotr90(p[d, w]), p[w, depth - d + 1]) - p[width - w + 1, depth - d + 1] = _fit_spaces( - herm_depth(rotr90(p[d, w])), [width - w + 1, depth - d + 1] + peps[depth - d + 1, w] = _fit_spaces( + herm_depth(peps[d, w]), peps[depth - d + 1, w] ) - p[w, d] = _fit_spaces(rotr90(herm_depth(p[d, w])), p[w, d]) - p[width - w + 1, d] = _fit_spaces( - herm_depth(rotr90(herm_depth(p[d, w]))), p[width - w + 1, d] + peps[w, depth - d + 1] = _fit_spaces( + rotr90(peps[d, w]), peps[w, depth - d + 1] ) - p[d, width - w + 1] = _fit_spaces( - rotr90(rotr90(herm_depth(p[d, w]))), p[d, width - w + 1] + peps[width - w + 1, depth - d + 1] = _fit_spaces( + herm_depth(rotr90(peps[d, w])), [width - w + 1, depth - d + 1] ) - p[depth - d + 1, width - w + 1] = _fit_spaces( - herm_depth(rotr90(rotr90(herm_depth(p[d, w])))), - p[depth - d + 1, width - w + 1], + peps[w, d] = _fit_spaces(rotr90(herm_depth(peps[d, w])), peps[w, d]) + peps[width - w + 1, d] = _fit_spaces( + herm_depth(rotr90(herm_depth(peps[d, w]))), peps[width - w + 1, d] + ) + peps[d, width - w + 1] = _fit_spaces( + rotr90(rotr90(herm_depth(peps[d, w]))), peps[d, width - w + 1] + ) + peps[depth - d + 1, width - w + 1] = _fit_spaces( + herm_depth(rotr90(rotr90(herm_depth(peps[d, w])))), + peps[depth - d + 1, width - w + 1], ) end end end - return p + return peps end +""" + symmetrize_callback(peps, envs, E, grad, symm::SymmetrizationStyle) + +Callback function symmetrizing both the `peps` and `grad` tensors. +""" function symmetrize_callback(peps, envs, E, grad, symm::SymmetrizationStyle) peps_symm = symmetrize!(deepcopy(peps), symm) grad_symm = symmetrize!(deepcopy(grad), symm) diff --git a/test/j1j2_model.jl b/test/j1j2_model.jl index 0ea26951..5c7a182b 100644 --- a/test/j1j2_model.jl +++ b/test/j1j2_model.jl @@ -27,17 +27,14 @@ opt_alg = PEPSOptimize(; Random.seed!(91283219347) H = square_lattice_j1j2(; J2=0.25) psi_init = InfinitePEPS(2, χbond) -psi_init = PEPSKit.symmetrize!(psi_init, PEPSKit.Full()) -env_init = leading_boundary(CTMRGEnv(psi_init, ComplexSpace(χenv)), psi_init, ctm_alg) +psi_init = PEPSKit.symmetrize!(psi_init, PEPSKit.RotateReflect()) +env_init = leading_boundary(CTMRGEnv(psi_init, ComplexSpace(χenv)), psi_init, ctm_alg); # find fixedpoint -cb = (args...) -> PEPSKit.symmetrize_callback(args..., RotateReflect()) -result = fixedpoint(psi_init, H, opt_alg, env_init)#; callback=cb) +callback = (args...) -> PEPSKit.symmetrize_callback(args..., RotateReflect()) +result = fixedpoint(psi_init, H, opt_alg, env_init; callback) ξ_h, ξ_v, = correlation_length(result.peps, result.env) -p = symmetrize!(deepcopy(result.peps), RotateReflect()) -ξ_h, ξ_v, = correlation_length(p, leading_boundary(result.env, p, ctm_alg)) - # compare against Juraj Hasik's data: # https://github.com/jurajHasik/j1j2_ipeps_states/blob/main/single-site_pg-C4v-A1/j20.25/state_1s_A1_j20.25_D2_chi_opt48.dat ξ_ref = -1 / log(0.2723596743547324) From fdaa7bceffbaa51696f69403e6a2295ef10b9c96 Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Fri, 16 Aug 2024 10:06:01 +0200 Subject: [PATCH 083/213] Rename callback to finalize --- src/PEPSKit.jl | 2 +- src/algorithms/peps_opt.jl | 10 +++++----- src/utility/symmetrization.jl | 4 ++-- test/j1j2_model.jl | 4 ++-- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/PEPSKit.jl b/src/PEPSKit.jl index b9a6a8df..3aed965c 100644 --- a/src/PEPSKit.jl +++ b/src/PEPSKit.jl @@ -85,7 +85,7 @@ export fixedpoint export InfinitePEPS, InfiniteTransferPEPS export InfinitePEPO, InfiniteTransferPEPO export initializeMPS, initializePEPS -export ReflectDepth, ReflectWidth, RotateReflect, symmetrize!, symmetrize_callback +export ReflectDepth, ReflectWidth, RotateReflect, symmetrize!, symmetrize_finalize export showtypeofgrad export square_lattice_tf_ising, square_lattice_heisenberg, square_lattice_j1j2 export square_lattice_pwave diff --git a/src/algorithms/peps_opt.jl b/src/algorithms/peps_opt.jl index 9bb4daae..aa9307e7 100644 --- a/src/algorithms/peps_opt.jl +++ b/src/algorithms/peps_opt.jl @@ -122,14 +122,14 @@ end """ fixedpoint(ψ₀::InfinitePEPS{T}, H, alg::PEPSOptimize, [env₀::CTMRGEnv]; - callback=(args...) -> identity(args)) where {T} + finalize=(args...) -> identity(args)) where {T} Optimize `ψ₀` with respect to the Hamiltonian `H` according to the parameters supplied in `alg`. The initial environment `env₀` serves as an initial guess for the first CTMRG run. By default, a random initial environment is used. -The `callback` kwarg can be used to insert a function call after each optimization step -which maps `peps, envs, E, grad = callback(peps, envs, E, grad)`. +The `finalize` kwarg can be used to insert a function call after each optimization step +which maps `peps, envs, E, grad = finalize(peps, envs, E, grad)`. The function returns a `NamedTuple` which contains the following entries: - `peps`: final `InfinitePEPS` @@ -145,7 +145,7 @@ function fixedpoint( H, alg::PEPSOptimize, env₀::CTMRGEnv=CTMRGEnv(ψ₀, field(T)^20); - callback=(args...) -> identity(args), + finalize=(args...) -> identity(args), ) where {T} (peps, env), E, ∂E, numfg, convhistory = optimize( (ψ₀, env₀), alg.optimizer; retract=my_retract, inner=my_inner @@ -164,7 +164,7 @@ function fixedpoint( return costfun(ψ, envs´, H) end g = only(g) # withgradient returns tuple of gradients `g` - peps, envs, E, g = callback(peps, envs, E, g) + peps, envs, E, g = finalize(peps, envs, E, g) return E, g end return (; diff --git a/src/utility/symmetrization.jl b/src/utility/symmetrization.jl index e175233f..6cd67483 100644 --- a/src/utility/symmetrization.jl +++ b/src/utility/symmetrization.jl @@ -208,11 +208,11 @@ function symmetrize!(peps::InfinitePEPS, symm::RotateReflect) end """ - symmetrize_callback(peps, envs, E, grad, symm::SymmetrizationStyle) + symmetrize_finalize(peps, envs, E, grad, symm::SymmetrizationStyle) Callback function symmetrizing both the `peps` and `grad` tensors. """ -function symmetrize_callback(peps, envs, E, grad, symm::SymmetrizationStyle) +function symmetrize_finalize(peps, envs, E, grad, symm::SymmetrizationStyle) peps_symm = symmetrize!(deepcopy(peps), symm) grad_symm = symmetrize!(deepcopy(grad), symm) return peps_symm, envs, E, grad_symm diff --git a/test/j1j2_model.jl b/test/j1j2_model.jl index 5c7a182b..f0e56744 100644 --- a/test/j1j2_model.jl +++ b/test/j1j2_model.jl @@ -31,8 +31,8 @@ psi_init = PEPSKit.symmetrize!(psi_init, PEPSKit.RotateReflect()) env_init = leading_boundary(CTMRGEnv(psi_init, ComplexSpace(χenv)), psi_init, ctm_alg); # find fixedpoint -callback = (args...) -> PEPSKit.symmetrize_callback(args..., RotateReflect()) -result = fixedpoint(psi_init, H, opt_alg, env_init; callback) +finalize = (args...) -> PEPSKit.symmetrize_finalize(args..., RotateReflect()) +result = fixedpoint(psi_init, H, opt_alg, env_init; finalize) ξ_h, ξ_v, = correlation_length(result.peps, result.env) # compare against Juraj Hasik's data: From b9aad690fbeaf1d50ee7c550c8b77c2ef3b071a7 Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Fri, 16 Aug 2024 10:08:37 +0200 Subject: [PATCH 084/213] Include J1J2 test in runtest.jl --- test/runtests.jl | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/runtests.jl b/test/runtests.jl index 5655a517..eb799bc8 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -45,6 +45,9 @@ end @time @safetestset "Heisenberg model" begin include("heisenberg.jl") end + @time @safetestset "Heisenberg model" begin + include("j1j2_model.jl") + end @time @safetestset "P-wave superconductor" begin include("pwave.jl") end From 3767d5d67a39ef064c0c1458ba57a53a69804054 Mon Sep 17 00:00:00 2001 From: Gertian Date: Mon, 19 Aug 2024 12:19:30 +0300 Subject: [PATCH 085/213] Define + for LocalOperators I defined additional for Localoperators --- src/operators/localoperator.jl | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/operators/localoperator.jl b/src/operators/localoperator.jl index ffe9e34a..b9473c17 100644 --- a/src/operators/localoperator.jl +++ b/src/operators/localoperator.jl @@ -57,3 +57,17 @@ function Base.repeat(O::LocalOperator, m::Int, n::Int) end return LocalOperator(lattice, terms...) end + +function Base.:+(O1::LocalOperator, O2::LocalOperator) + if O1.lattice != O2.lattice + throw("lattices should match") + end + terms = [] + for (inds, operator) in O1.terms + push!(terms, inds => operator) + end + for (inds, operator) in O2.terms + push!(terms, inds => operator) + end + return LocalOperator(O1.lattice, terms...) +end From 8394d5c1bf36292226ef93aa71493b995dee6b21 Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Mon, 19 Aug 2024 14:19:25 +0200 Subject: [PATCH 086/213] Use finalize! kwarg of OptimKit.optimization, make symmetrize_finalize! mutating --- src/PEPSKit.jl | 2 +- src/algorithms/peps_opt.jl | 12 ++++++------ src/utility/symmetrization.jl | 15 +++++++++------ test/j1j2_model.jl | 6 +++--- 4 files changed, 19 insertions(+), 16 deletions(-) diff --git a/src/PEPSKit.jl b/src/PEPSKit.jl index 3aed965c..3de03819 100644 --- a/src/PEPSKit.jl +++ b/src/PEPSKit.jl @@ -85,7 +85,7 @@ export fixedpoint export InfinitePEPS, InfiniteTransferPEPS export InfinitePEPO, InfiniteTransferPEPO export initializeMPS, initializePEPS -export ReflectDepth, ReflectWidth, RotateReflect, symmetrize!, symmetrize_finalize +export ReflectDepth, ReflectWidth, RotateReflect, symmetrize!, symmetrize_finalize! export showtypeofgrad export square_lattice_tf_ising, square_lattice_heisenberg, square_lattice_j1j2 export square_lattice_pwave diff --git a/src/algorithms/peps_opt.jl b/src/algorithms/peps_opt.jl index aa9307e7..3ceb6500 100644 --- a/src/algorithms/peps_opt.jl +++ b/src/algorithms/peps_opt.jl @@ -122,14 +122,15 @@ end """ fixedpoint(ψ₀::InfinitePEPS{T}, H, alg::PEPSOptimize, [env₀::CTMRGEnv]; - finalize=(args...) -> identity(args)) where {T} + finalize!=OptimKit._finalize!) where {T} Optimize `ψ₀` with respect to the Hamiltonian `H` according to the parameters supplied in `alg`. The initial environment `env₀` serves as an initial guess for the first CTMRG run. By default, a random initial environment is used. -The `finalize` kwarg can be used to insert a function call after each optimization step -which maps `peps, envs, E, grad = finalize(peps, envs, E, grad)`. +The `finalize!` kwarg can be used to insert a function call after each optimization step +by utilizing the `finalize!` kwarg of `OptimKit.optimize`. +The function maps `(peps, envs), f, g = finalize!((peps, envs), f, g, numiter)`. The function returns a `NamedTuple` which contains the following entries: - `peps`: final `InfinitePEPS` @@ -145,10 +146,10 @@ function fixedpoint( H, alg::PEPSOptimize, env₀::CTMRGEnv=CTMRGEnv(ψ₀, field(T)^20); - finalize=(args...) -> identity(args), + (finalize!)=OptimKit._finalize!, ) where {T} (peps, env), E, ∂E, numfg, convhistory = optimize( - (ψ₀, env₀), alg.optimizer; retract=my_retract, inner=my_inner + (ψ₀, env₀), alg.optimizer; retract=my_retract, inner=my_inner, finalize! ) do (peps, envs) E, g = withgradient(peps) do ψ envs´ = hook_pullback( @@ -164,7 +165,6 @@ function fixedpoint( return costfun(ψ, envs´, H) end g = only(g) # withgradient returns tuple of gradients `g` - peps, envs, E, g = finalize(peps, envs, E, g) return E, g end return (; diff --git a/src/utility/symmetrization.jl b/src/utility/symmetrization.jl index 6cd67483..aa8bbfdf 100644 --- a/src/utility/symmetrization.jl +++ b/src/utility/symmetrization.jl @@ -208,12 +208,15 @@ function symmetrize!(peps::InfinitePEPS, symm::RotateReflect) end """ - symmetrize_finalize(peps, envs, E, grad, symm::SymmetrizationStyle) + symmetrize_finalize!(symm::SymmetrizationStyle) -Callback function symmetrizing both the `peps` and `grad` tensors. +Return `finalize!` function for symmetrizing the `peps` and `grad` tensors in-place, +which maps `(peps_symm, envs), E, grad_symm = symmetrize_finalize!((peps, envs), E, grad, numiter)`. """ -function symmetrize_finalize(peps, envs, E, grad, symm::SymmetrizationStyle) - peps_symm = symmetrize!(deepcopy(peps), symm) - grad_symm = symmetrize!(deepcopy(grad), symm) - return peps_symm, envs, E, grad_symm +function symmetrize_finalize!(symm::SymmetrizationStyle) + function symmetrize_finalize!((peps, envs), E, grad, _) + peps_symm = symmetrize!(peps, symm) + grad_symm = symmetrize!(grad, symm) + return (peps_symm, envs), E, grad_symm + end end diff --git a/test/j1j2_model.jl b/test/j1j2_model.jl index f0e56744..997d14d3 100644 --- a/test/j1j2_model.jl +++ b/test/j1j2_model.jl @@ -27,12 +27,12 @@ opt_alg = PEPSOptimize(; Random.seed!(91283219347) H = square_lattice_j1j2(; J2=0.25) psi_init = InfinitePEPS(2, χbond) -psi_init = PEPSKit.symmetrize!(psi_init, PEPSKit.RotateReflect()) +psi_init = symmetrize!(psi_init, RotateReflect()) env_init = leading_boundary(CTMRGEnv(psi_init, ComplexSpace(χenv)), psi_init, ctm_alg); # find fixedpoint -finalize = (args...) -> PEPSKit.symmetrize_finalize(args..., RotateReflect()) -result = fixedpoint(psi_init, H, opt_alg, env_init; finalize) +finalize! = symmetrize_finalize!(RotateReflect()) +result = fixedpoint(psi_init, H, opt_alg, env_init; finalize!) ξ_h, ξ_v, = correlation_length(result.peps, result.env) # compare against Juraj Hasik's data: From e12e973dbe326a64108d93732d0132068bd89ada Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Mon, 19 Aug 2024 15:13:29 +0200 Subject: [PATCH 087/213] Add symmetrization test --- src/utility/symmetrization.jl | 2 -- test/ctmrg/symmetrization.jl | 46 +++++++++++++++++++++++++++++++++++ test/runtests.jl | 3 +++ 3 files changed, 49 insertions(+), 2 deletions(-) create mode 100644 test/ctmrg/symmetrization.jl diff --git a/src/utility/symmetrization.jl b/src/utility/symmetrization.jl index aa8bbfdf..5ea49494 100644 --- a/src/utility/symmetrization.jl +++ b/src/utility/symmetrization.jl @@ -169,7 +169,6 @@ function symmetrize!(peps::InfinitePEPS, symm::RotateReflect) peps[depth - d + 1, width - w + 1] = _fit_spaces( herm_depth(rotr90(peps[d, w])), peps[depth - d + 1, width - w + 1] ) - elseif odd == 1 && d == ceil(Int, depth / 2) peps[d, w] = symmetrize_mid_depth(peps[d, w]) peps[w, d] = _fit_spaces(rotr90(peps[d, w]), peps[w, d]) @@ -179,7 +178,6 @@ function symmetrize!(peps::InfinitePEPS, symm::RotateReflect) peps[width - w + 1, d] = _fit_spaces( herm_depth(rotr90(peps[d, w])), peps[width - w + 1, d] ) - else peps[depth - d + 1, w] = _fit_spaces( herm_depth(peps[d, w]), peps[depth - d + 1, w] diff --git a/test/ctmrg/symmetrization.jl b/test/ctmrg/symmetrization.jl new file mode 100644 index 00000000..fa624237 --- /dev/null +++ b/test/ctmrg/symmetrization.jl @@ -0,0 +1,46 @@ +using Test +using PEPSKit +using TensorKit + +function _test_elementwise_equal(peps1::InfinitePEPS, peps2::InfinitePEPS) + return @test all(zip(peps1.A, peps2.A)) do (p1, p2) + p1.data == p2.data + end +end + +@testset "RotateReflect" for unitcell in [(1, 1), (2, 2), (3, 3)] + peps = InfinitePEPS(2, 2; unitcell) + + peps_rotatereflect = symmetrize!(deepcopy(peps), RotateReflect()) + _test_elementwise_equal(peps_full, rotl90(peps_full)) + _test_elementwise_equal(peps_full, rot180(peps_full)) + _test_elementwise_equal(peps_full, rotr90(peps_full)) + _test_elementwise_equal( + peps_full, + InfinitePEPS(reverse(map(copy ∘ PEPSKit.herm_depth, peps_full.A); dims=1)), + ) + _test_elementwise_equal( + peps_full, + InfinitePEPS(reverse(map(copy ∘ PEPSKit.herm_width, peps_full.A); dims=2)), + ) +end + +@testset "ReflectDepth" for unitcell in [(1, 1), (2, 2), (3, 3)] + peps = InfinitePEPS(2, 2; unitcell) + + peps_reflectdepth = symmetrize!(deepcopy(peps), ReflectDepth()) + _test_elementwise_equal( + peps_full, + InfinitePEPS(reverse(map(copy ∘ PEPSKit.herm_depth, peps_full.A); dims=1)), + ) +end + +@testset "ReflectWidth" for unitcell in [(1, 1), (2, 2), (3, 3)] + peps = InfinitePEPS(2, 2; unitcell) + + peps_reflectdepth = symmetrize!(deepcopy(peps), ReflectDepth()) + _test_elementwise_equal( + peps_full, + InfinitePEPS(reverse(map(copy ∘ PEPSKit.herm_width, peps_full.A); dims=2)), + ) +end diff --git a/test/runtests.jl b/test/runtests.jl index eb799bc8..93bafe43 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -32,6 +32,9 @@ end @time @safetestset "CTMRG schemes" begin include("ctmrg/ctmrgschemes.jl") end + @time @safetestset "CTMRG schemes" begin + include("ctmrg/symmetrization.jl") + end end if GROUP == "ALL" || GROUP == "MPS" @time @safetestset "VUMPS" begin From e248adf37db637ce531e26ef640226fdc7c63f41 Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Mon, 19 Aug 2024 15:14:46 +0200 Subject: [PATCH 088/213] Update symmetrization test --- test/ctmrg/symmetrization.jl | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/test/ctmrg/symmetrization.jl b/test/ctmrg/symmetrization.jl index fa624237..843ec62e 100644 --- a/test/ctmrg/symmetrization.jl +++ b/test/ctmrg/symmetrization.jl @@ -1,5 +1,6 @@ using Test using PEPSKit +using PEPSKit: herm_depth, herm_width using TensorKit function _test_elementwise_equal(peps1::InfinitePEPS, peps2::InfinitePEPS) @@ -16,12 +17,10 @@ end _test_elementwise_equal(peps_full, rot180(peps_full)) _test_elementwise_equal(peps_full, rotr90(peps_full)) _test_elementwise_equal( - peps_full, - InfinitePEPS(reverse(map(copy ∘ PEPSKit.herm_depth, peps_full.A); dims=1)), + peps_full, InfinitePEPS(reverse(map(copy ∘ herm_depth, peps_full.A); dims=1)) ) _test_elementwise_equal( - peps_full, - InfinitePEPS(reverse(map(copy ∘ PEPSKit.herm_width, peps_full.A); dims=2)), + peps_full, InfinitePEPS(reverse(map(copy ∘ herm_width, peps_full.A); dims=2)) ) end @@ -30,8 +29,7 @@ end peps_reflectdepth = symmetrize!(deepcopy(peps), ReflectDepth()) _test_elementwise_equal( - peps_full, - InfinitePEPS(reverse(map(copy ∘ PEPSKit.herm_depth, peps_full.A); dims=1)), + peps_full, InfinitePEPS(reverse(map(copy ∘ herm_depth, peps_full.A); dims=1)) ) end @@ -40,7 +38,6 @@ end peps_reflectdepth = symmetrize!(deepcopy(peps), ReflectDepth()) _test_elementwise_equal( - peps_full, - InfinitePEPS(reverse(map(copy ∘ PEPSKit.herm_width, peps_full.A); dims=2)), + peps_full, InfinitePEPS(reverse(map(copy ∘ herm_width, peps_full.A); dims=2)) ) end From 1b785ea460a8674a6cb3c081afd5799b573c52da Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Mon, 19 Aug 2024 15:42:40 +0200 Subject: [PATCH 089/213] Fix symmetrization test --- test/ctmrg/symmetrization.jl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/ctmrg/symmetrization.jl b/test/ctmrg/symmetrization.jl index 843ec62e..cfbdf40a 100644 --- a/test/ctmrg/symmetrization.jl +++ b/test/ctmrg/symmetrization.jl @@ -27,17 +27,17 @@ end @testset "ReflectDepth" for unitcell in [(1, 1), (2, 2), (3, 3)] peps = InfinitePEPS(2, 2; unitcell) - peps_reflectdepth = symmetrize!(deepcopy(peps), ReflectDepth()) + peps_depth = symmetrize!(deepcopy(peps), ReflectDepth()) _test_elementwise_equal( - peps_full, InfinitePEPS(reverse(map(copy ∘ herm_depth, peps_full.A); dims=1)) + peps_depth, InfinitePEPS(reverse(map(copy ∘ herm_depth, peps_depth.A); dims=1)) ) end @testset "ReflectWidth" for unitcell in [(1, 1), (2, 2), (3, 3)] peps = InfinitePEPS(2, 2; unitcell) - peps_reflectdepth = symmetrize!(deepcopy(peps), ReflectDepth()) + peps_width = symmetrize!(deepcopy(peps), ReflectWidth()) _test_elementwise_equal( - peps_full, InfinitePEPS(reverse(map(copy ∘ herm_width, peps_full.A); dims=2)) + peps_width, InfinitePEPS(reverse(map(copy ∘ herm_width, peps_width.A); dims=2)) ) end From 76b11631ea7703e1476bc96e65408f5472f2d8f2 Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Mon, 19 Aug 2024 16:24:26 +0200 Subject: [PATCH 090/213] Fix symmetrization test, attempt 2 --- test/ctmrg/symmetrization.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/ctmrg/symmetrization.jl b/test/ctmrg/symmetrization.jl index cfbdf40a..65ba35ae 100644 --- a/test/ctmrg/symmetrization.jl +++ b/test/ctmrg/symmetrization.jl @@ -12,7 +12,7 @@ end @testset "RotateReflect" for unitcell in [(1, 1), (2, 2), (3, 3)] peps = InfinitePEPS(2, 2; unitcell) - peps_rotatereflect = symmetrize!(deepcopy(peps), RotateReflect()) + peps_full = symmetrize!(deepcopy(peps), RotateReflect()) _test_elementwise_equal(peps_full, rotl90(peps_full)) _test_elementwise_equal(peps_full, rot180(peps_full)) _test_elementwise_equal(peps_full, rotr90(peps_full)) From be1493667375a7068f11d29071fd1d6849285115 Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Tue, 20 Aug 2024 10:09:28 +0200 Subject: [PATCH 091/213] Add == and isapprox for InfinitePEPS, fix symmetrization test for real now --- src/states/infinitepeps.jl | 12 ++++++++++++ test/ctmrg/symmetrization.jl | 37 +++++++++++++++++++++--------------- 2 files changed, 34 insertions(+), 15 deletions(-) diff --git a/src/states/infinitepeps.jl b/src/states/infinitepeps.jl index 2661c072..b6d11a18 100644 --- a/src/states/infinitepeps.jl +++ b/src/states/infinitepeps.jl @@ -133,6 +133,18 @@ Base.:*(α::Number, ψ::InfinitePEPS) = InfinitePEPS(α * ψ.A) LinearAlgebra.dot(ψ₁::InfinitePEPS, ψ₂::InfinitePEPS) = dot(ψ₁.A, ψ₂.A) LinearAlgebra.norm(ψ::InfinitePEPS) = norm(ψ.A) +## (Approximate) equality +function Base.:(==)(ψ₁::InfinitePEPS, ψ₂::InfinitePEPS) + return all(zip(ψ₁.A, ψ₂.A)) do (p₁, p₂) + return p₁ == p₂ + end +end +function Base.isapprox(ψ₁::InfinitePEPS, ψ₂::InfinitePEPS; kwargs...) + return all(zip(ψ₁.A, ψ₂.A)) do (p₁, p₂) + return isapprox(p₁, p₂; kwargs...) + end +end + # Used in _scale during OptimKit.optimize function LinearAlgebra.rmul!(ψ::InfinitePEPS, α::Number) rmul!(ψ.A, α) diff --git a/test/ctmrg/symmetrization.jl b/test/ctmrg/symmetrization.jl index 65ba35ae..4f8ed52d 100644 --- a/test/ctmrg/symmetrization.jl +++ b/test/ctmrg/symmetrization.jl @@ -1,43 +1,50 @@ using Test using PEPSKit -using PEPSKit: herm_depth, herm_width +using PEPSKit: herm_depth, herm_width, _fit_spaces using TensorKit -function _test_elementwise_equal(peps1::InfinitePEPS, peps2::InfinitePEPS) - return @test all(zip(peps1.A, peps2.A)) do (p1, p2) - p1.data == p2.data +function PEPSKit._fit_spaces(data_peps::InfinitePEPS, space_peps::InfinitePEPS) + fitted_tensors = map(zip(data_peps.A, space_peps.A)) do (pd, ps) + PEPSKit._fit_spaces(pd, ps) end + return InfinitePEPS(fitted_tensors) end @testset "RotateReflect" for unitcell in [(1, 1), (2, 2), (3, 3)] peps = InfinitePEPS(2, 2; unitcell) peps_full = symmetrize!(deepcopy(peps), RotateReflect()) - _test_elementwise_equal(peps_full, rotl90(peps_full)) - _test_elementwise_equal(peps_full, rot180(peps_full)) - _test_elementwise_equal(peps_full, rotr90(peps_full)) - _test_elementwise_equal( - peps_full, InfinitePEPS(reverse(map(copy ∘ herm_depth, peps_full.A); dims=1)) + @test peps_full ≈ _fit_spaces(rotl90(peps_full), peps_full) + @test peps_full ≈ _fit_spaces(rot180(peps_full), peps_full) + @test peps_full ≈ _fit_spaces(rotr90(peps_full), peps_full) + + peps_reflect_depth = _fit_spaces( + InfinitePEPS(reverse(map(herm_depth, peps_full.A); dims=1)), peps_full ) - _test_elementwise_equal( - peps_full, InfinitePEPS(reverse(map(copy ∘ herm_width, peps_full.A); dims=2)) + @test peps_full ≈ peps_reflect_depth + + peps_reflect_width = _fit_spaces( + InfinitePEPS(reverse(map(herm_width, peps_full.A); dims=2)), peps_full ) + @test peps_full ≈ peps_reflect_width end @testset "ReflectDepth" for unitcell in [(1, 1), (2, 2), (3, 3)] peps = InfinitePEPS(2, 2; unitcell) peps_depth = symmetrize!(deepcopy(peps), ReflectDepth()) - _test_elementwise_equal( - peps_depth, InfinitePEPS(reverse(map(copy ∘ herm_depth, peps_depth.A); dims=1)) + peps_reflect = _fit_spaces( + InfinitePEPS(reverse(map(herm_depth, peps_depth.A); dims=1)), peps_depth ) + @test peps_depth ≈ peps_reflect end @testset "ReflectWidth" for unitcell in [(1, 1), (2, 2), (3, 3)] peps = InfinitePEPS(2, 2; unitcell) peps_width = symmetrize!(deepcopy(peps), ReflectWidth()) - _test_elementwise_equal( - peps_width, InfinitePEPS(reverse(map(copy ∘ herm_width, peps_width.A); dims=2)) + peps_reflect = _fit_spaces( + InfinitePEPS(reverse(map(herm_width, peps_width.A); dims=2)), peps_width ) + @test peps_width ≈ peps_reflect end From 82b4f3be7f3d6a9f8ea87d5bc587f98239d10e4b Mon Sep 17 00:00:00 2001 From: Gertian Date: Tue, 20 Aug 2024 13:46:58 +0300 Subject: [PATCH 092/213] Update localoperator.jl 1) Overloaded checklattice for two LocalOperators 2)Changed LocalOperator+LocalOperator such that its more efficient when duplicate indices appear 3)Defined Number*LocalOperator 4)Defined - using + and * --- src/operators/localoperator.jl | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/src/operators/localoperator.jl b/src/operators/localoperator.jl index b9473c17..272b94ca 100644 --- a/src/operators/localoperator.jl +++ b/src/operators/localoperator.jl @@ -27,6 +27,9 @@ while the second version throws an error if the lattices do not match. function checklattice(args...) return checklattice(Bool, args...) || throw(ArgumentError("Lattice mismatch.")) end +function checklattice(::Type{Bool}, H1::LocalOperator, H2::LocalOperator) + return H1.lattice == H2.lattice +end function checklattice(::Type{Bool}, peps::InfinitePEPS, O::LocalOperator) return size(peps) == size(O.lattice) end @@ -58,16 +61,24 @@ function Base.repeat(O::LocalOperator, m::Int, n::Int) return LocalOperator(lattice, terms...) end +function Base.:*(α::Number, O2::LocalOperator) + return LocalOperator(O2.lattice, map(t -> (t[1] => α * t[2]), O2.terms)...) +end + function Base.:+(O1::LocalOperator, O2::LocalOperator) - if O1.lattice != O2.lattice - throw("lattices should match") - end - terms = [] - for (inds, operator) in O1.terms - push!(terms, inds => operator) - end + checklattice(O1, O2) + terms = [O1.terms...] for (inds, operator) in O2.terms - push!(terms, inds => operator) + i = findfirst(existing_inds -> existing_inds == inds, map(first, terms)) + if !isnothing(i) + terms[i] = (inds => terms[i][2] + operator) + else + push!(terms, inds => operator) + end end return LocalOperator(O1.lattice, terms...) end + +function Base.:-(O1::LocalOperator, O2::LocalOperator) + return O1 + (-1) * O2 +end From 3049fc415d68a913bbbd935ea094c7178a631c90 Mon Sep 17 00:00:00 2001 From: Gertian Date: Tue, 20 Aug 2024 17:53:50 +0300 Subject: [PATCH 093/213] + - *for localOperators 1) Extended LocalOperator constructor so that it eliminates operators with norm < tolerance. 2) Used this to make + - * implementations more streamlined --- src/operators/localoperator.jl | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/src/operators/localoperator.jl b/src/operators/localoperator.jl index 272b94ca..f438513f 100644 --- a/src/operators/localoperator.jl +++ b/src/operators/localoperator.jl @@ -5,16 +5,31 @@ struct LocalOperator{T<:Tuple,S} lattice::Matrix{S} terms::T end -function LocalOperator(lattice::Matrix{S}, terms::Pair...) where {S} +function LocalOperator(lattice::Matrix{S}, terms::Pair... ; min_norm_operators = eps()^(3/4)) where {S} lattice′ = PeriodicArray(lattice) + relevant_terms = [] for (inds, operator) in terms + # Check if the indices of the operator are valid with themselves and the lattice @assert operator isa AbstractTensorMap @assert numout(operator) == numin(operator) == length(inds) for i in 1:length(inds) @assert space(operator, i) == lattice′[inds[i]] + end + # Check if we already have an operator acting on the coordinates + i = findfirst(existing_inds -> existing_inds == inds, map(first, relevant_terms)) + if !isnothing(i) # We are adding to an existing operator + new_operator = relevant_terms[i][2] + operator + if norm(new_operator) > min_norm_operators + relevant_terms[i] = (inds => new_operator) + else + deleteat!(relevant_terms, i) + end + else # It's a new operator, add it if its norm is large enough + norm(operator) > min_norm_operators && push!(relevant_terms, inds => operator) end end - return LocalOperator{typeof(terms),S}(lattice, terms) + relevant_terms = Tuple(relevant_terms) + return LocalOperator{typeof(relevant_terms), S}(lattice, relevant_terms) end """ @@ -67,16 +82,7 @@ end function Base.:+(O1::LocalOperator, O2::LocalOperator) checklattice(O1, O2) - terms = [O1.terms...] - for (inds, operator) in O2.terms - i = findfirst(existing_inds -> existing_inds == inds, map(first, terms)) - if !isnothing(i) - terms[i] = (inds => terms[i][2] + operator) - else - push!(terms, inds => operator) - end - end - return LocalOperator(O1.lattice, terms...) + return LocalOperator(O1.lattice, O1.terms..., O2.terms...) end function Base.:-(O1::LocalOperator, O2::LocalOperator) From 84b638f1ceb827bef2c54270538f9b89cf4f34f3 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Wed, 21 Aug 2024 10:19:19 +0200 Subject: [PATCH 094/213] Add docstring LocalOperator --- src/operators/localoperator.jl | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/operators/localoperator.jl b/src/operators/localoperator.jl index f438513f..e5bad1b5 100644 --- a/src/operators/localoperator.jl +++ b/src/operators/localoperator.jl @@ -1,6 +1,29 @@ # Hamiltonian consisting of local terms # ------------------------------------- +""" + struct LocalOperator{T<:Tuple,S} + +A sum of local operators acting on a lattice. The lattice is stored as a matrix of vector spaces, +and the terms are stored as a tuple of pairs of indices and operators. + +# Fields + +- `lattice::Matrix{S}`: The lattice on which the operator acts. +- `terms::T`: The terms of the operator, stored as a tuple of pairs of indices and operators. + +# Constructors + + LocalOperator(lattice::Matrix{S}, terms::Pair...) + LocalOperator{T,S}(lattice::Matrix{S}, terms::T) where {T,S} # expert mode + +# Examples + +```julia +lattice = fill(ℂ^2, 1, 1) # single-site unitcell +O1 = LocalOperator(lattice, ((1, 1),) => σx, ((1, 1), (1, 2)) => σx ⊗ σx, ((1, 1), (2, 1)) => σx ⊗ σx) +``` +""" struct LocalOperator{T<:Tuple,S} lattice::Matrix{S} terms::T From 2c9abb9db9283765601d19816673a85d53ffdf25 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Wed, 21 Aug 2024 10:19:53 +0200 Subject: [PATCH 095/213] Update LocalOperator constructor --- src/operators/localoperator.jl | 47 ++++++++++++++++++---------------- 1 file changed, 25 insertions(+), 22 deletions(-) diff --git a/src/operators/localoperator.jl b/src/operators/localoperator.jl index e5bad1b5..4b2fd29f 100644 --- a/src/operators/localoperator.jl +++ b/src/operators/localoperator.jl @@ -27,32 +27,35 @@ O1 = LocalOperator(lattice, ((1, 1),) => σx, ((1, 1), (1, 2)) => σx ⊗ σx, ( struct LocalOperator{T<:Tuple,S} lattice::Matrix{S} terms::T -end -function LocalOperator(lattice::Matrix{S}, terms::Pair... ; min_norm_operators = eps()^(3/4)) where {S} - lattice′ = PeriodicArray(lattice) - relevant_terms = [] - for (inds, operator) in terms + function LocalOperator{T,S}(lattice::Matrix{S}, terms::T) where {T,S} + plattice = PeriodicArray(lattice) # Check if the indices of the operator are valid with themselves and the lattice - @assert operator isa AbstractTensorMap - @assert numout(operator) == numin(operator) == length(inds) - for i in 1:length(inds) - @assert space(operator, i) == lattice′[inds[i]] - end - # Check if we already have an operator acting on the coordinates - i = findfirst(existing_inds -> existing_inds == inds, map(first, relevant_terms)) - if !isnothing(i) # We are adding to an existing operator - new_operator = relevant_terms[i][2] + operator - if norm(new_operator) > min_norm_operators - relevant_terms[i] = (inds => new_operator) - else - deleteat!(relevant_terms, i) + for (inds, operator) in terms + @assert operator isa AbstractTensorMap + @assert numout(operator) == numin(operator) == length(inds) + @assert spacetype(operator) == S + + for i in 1:length(inds) + @assert space(operator, i) == plattice[inds[i]] end - else # It's a new operator, add it if its norm is large enough - norm(operator) > min_norm_operators && push!(relevant_terms, inds => operator) end + return new{T,S}(lattice, terms) + end +end +function LocalOperator( + lattice::Matrix, terms::Pair...; atol=maximum(x -> eps(scalartype(x[2]))^(3 / 4), terms) +) + allinds = getindex.(terms, 1) + alloperators = getindex.(terms, 2) + + relevant_terms = [] + for inds in unique(allinds) + operator = sum(alloperators[findall(==(inds), allinds)]) + norm(operator) > atol && push!(relevant_terms, inds => operator) end - relevant_terms = Tuple(relevant_terms) - return LocalOperator{typeof(relevant_terms), S}(lattice, relevant_terms) + + terms_tuple = Tuple(relevant_terms) + return LocalOperator{typeof(terms_tuple),eltype(lattice)}(lattice, terms_tuple) end """ From 44bfe069fae90db58a824b9ed27a5282ee2a7eb9 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Wed, 21 Aug 2024 10:20:02 +0200 Subject: [PATCH 096/213] Formatter --- src/operators/localoperator.jl | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/operators/localoperator.jl b/src/operators/localoperator.jl index 4b2fd29f..32931611 100644 --- a/src/operators/localoperator.jl +++ b/src/operators/localoperator.jl @@ -1,4 +1,3 @@ - # Hamiltonian consisting of local terms # ------------------------------------- """ @@ -107,7 +106,7 @@ function Base.:*(α::Number, O2::LocalOperator) end function Base.:+(O1::LocalOperator, O2::LocalOperator) - checklattice(O1, O2) + checklattice(O1, O2) return LocalOperator(O1.lattice, O1.terms..., O2.terms...) end From 7d8a839c59d861c5e7f658a9a662e97ce58fc585 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Wed, 21 Aug 2024 10:25:56 +0200 Subject: [PATCH 097/213] Clean-up linear algebra localoperator --- src/operators/localoperator.jl | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/operators/localoperator.jl b/src/operators/localoperator.jl index 32931611..8ebbe8a5 100644 --- a/src/operators/localoperator.jl +++ b/src/operators/localoperator.jl @@ -101,15 +101,21 @@ function Base.repeat(O::LocalOperator, m::Int, n::Int) return LocalOperator(lattice, terms...) end -function Base.:*(α::Number, O2::LocalOperator) - return LocalOperator(O2.lattice, map(t -> (t[1] => α * t[2]), O2.terms)...) +# Linear Algebra +# -------------- +function Base.:*(α::Number, O::LocalOperator) + scaled_terms = map(((inds, operator),) -> (inds => α * operator), O.terms) + return LocalOperator{typeof(scaled_terms),eltype(O.lattice)}(O.lattice, scaled_terms) end +Base.:*(O::LocalOperator, α::Number) = α * O + +Base.:/(O::LocalOperator, α::Number) = O * inv(α) +Base.:\(α::Number, O::LocalOperator) = inv(α) * O function Base.:+(O1::LocalOperator, O2::LocalOperator) checklattice(O1, O2) return LocalOperator(O1.lattice, O1.terms..., O2.terms...) end -function Base.:-(O1::LocalOperator, O2::LocalOperator) - return O1 + (-1) * O2 -end +Base.:-(O::LocalOperator) = -1 * O +Base.:-(O1::LocalOperator, O2::LocalOperator) = O1 + (-O2) From bdd03b7da8eadfb4cf04a7e2ef436870e12247da Mon Sep 17 00:00:00 2001 From: lkdvos Date: Wed, 21 Aug 2024 10:33:17 +0200 Subject: [PATCH 098/213] Move utility function to main package --- src/utility/symmetrization.jl | 1 + test/ctmrg/symmetrization.jl | 7 ------- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/src/utility/symmetrization.jl b/src/utility/symmetrization.jl index 5ea49494..737d5717 100644 --- a/src/utility/symmetrization.jl +++ b/src/utility/symmetrization.jl @@ -64,6 +64,7 @@ function _fit_spaces( end return y end +_fit_spaces(y::InfinitePEPS, x::InfinitePEPS) = InfinitePEPS(map(_fit_spaces, y.A, x.A)) function herm_depth_inv(x::Union{PEPSTensor,PEPOTensor}) return 0.5 * (x + _fit_spaces(herm_depth(x), x)) diff --git a/test/ctmrg/symmetrization.jl b/test/ctmrg/symmetrization.jl index 4f8ed52d..7010d781 100644 --- a/test/ctmrg/symmetrization.jl +++ b/test/ctmrg/symmetrization.jl @@ -3,13 +3,6 @@ using PEPSKit using PEPSKit: herm_depth, herm_width, _fit_spaces using TensorKit -function PEPSKit._fit_spaces(data_peps::InfinitePEPS, space_peps::InfinitePEPS) - fitted_tensors = map(zip(data_peps.A, space_peps.A)) do (pd, ps) - PEPSKit._fit_spaces(pd, ps) - end - return InfinitePEPS(fitted_tensors) -end - @testset "RotateReflect" for unitcell in [(1, 1), (2, 2), (3, 3)] peps = InfinitePEPS(2, 2; unitcell) From f4c2d50952bcab98a72e246ba26c8048df723d2e Mon Sep 17 00:00:00 2001 From: lkdvos Date: Wed, 21 Aug 2024 10:34:51 +0200 Subject: [PATCH 099/213] Avoid compiler confusion with small variable rename --- src/algorithms/peps_opt.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/algorithms/peps_opt.jl b/src/algorithms/peps_opt.jl index 3ceb6500..a1afce8d 100644 --- a/src/algorithms/peps_opt.jl +++ b/src/algorithms/peps_opt.jl @@ -151,7 +151,7 @@ function fixedpoint( (peps, env), E, ∂E, numfg, convhistory = optimize( (ψ₀, env₀), alg.optimizer; retract=my_retract, inner=my_inner, finalize! ) do (peps, envs) - E, g = withgradient(peps) do ψ + E, gs = withgradient(peps) do ψ envs´ = hook_pullback( leading_boundary, envs, @@ -164,7 +164,7 @@ function fixedpoint( end return costfun(ψ, envs´, H) end - g = only(g) # withgradient returns tuple of gradients `g` + g = only(gs) # `withgradient` returns tuple of gradients `gs` return E, g end return (; From 51e4b96aca8885048fc355c4e51eb352ef4649bc Mon Sep 17 00:00:00 2001 From: lkdvos Date: Wed, 21 Aug 2024 10:56:20 +0200 Subject: [PATCH 100/213] Fix eps(real(scalartype))) error --- src/operators/localoperator.jl | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/operators/localoperator.jl b/src/operators/localoperator.jl index 8ebbe8a5..65796b88 100644 --- a/src/operators/localoperator.jl +++ b/src/operators/localoperator.jl @@ -42,7 +42,9 @@ struct LocalOperator{T<:Tuple,S} end end function LocalOperator( - lattice::Matrix, terms::Pair...; atol=maximum(x -> eps(scalartype(x[2]))^(3 / 4), terms) + lattice::Matrix, + terms::Pair...; + atol=maximum(x -> eps(real(scalartype(x[2])))^(3 / 4), terms), ) allinds = getindex.(terms, 1) alloperators = getindex.(terms, 2) From ab0d21555e2191e274ebac4382db1d5f75d5bf21 Mon Sep 17 00:00:00 2001 From: Lukas <37111893+lkdvos@users.noreply.github.com> Date: Thu, 29 Aug 2024 19:21:04 +0200 Subject: [PATCH 101/213] Improve CTMRG error computation (#65) * Add non-fixedspace error calculation * Refactor error calculation --- src/algorithms/ctmrg/gaugefix.jl | 34 +++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/src/algorithms/ctmrg/gaugefix.jl b/src/algorithms/ctmrg/gaugefix.jl index 778c2dce..4e496d42 100644 --- a/src/algorithms/ctmrg/gaugefix.jl +++ b/src/algorithms/ctmrg/gaugefix.jl @@ -139,22 +139,32 @@ function fix_global_phases(envprev::CTMRGEnv, envfix::CTMRGEnv) return CTMRGEnv(cornersgfix, edgesgfix) end +#= +In order to compute an error measure, we compare the singular values of the current iteration with the previous one. +However, when the virtual spaces change, this comparison is not directly possible. +Instead, we project both tensors into the smaller space and then compare the difference. + +TODO: we might want to consider embedding the smaller tensor into the larger space and then compute the difference +=# +function _singular_value_distance((S₁, S₂)) + V₁ = space(S₁, 1) + V₂ = space(S₂, 1) + if V₁ == V₂ + return norm(S₁ - S₂) + else + V = infimum(V₁, V₂) + e1 = isometry(V₁, V) + e2 = isometry(V₂, V) + return norm(e1' * S₁ * e1 - e2' * S₂ * e2) + end +end + function calc_convergence(envs, CSold, TSold) CSnew = map(x -> tsvd(x; alg=TensorKit.SVD())[2], envs.corners) - ΔCS = maximum(zip(CSold, CSnew)) do (c_old, c_new) - # only compute the difference on the smallest part of the spaces - smallest = infimum(MPSKit._firstspace(c_old), MPSKit._firstspace(c_new)) - e_old = isometry(MPSKit._firstspace(c_old), smallest) - e_new = isometry(MPSKit._firstspace(c_new), smallest) - return norm(e_new' * c_new * e_new - e_old' * c_old * e_old) - end + ΔCS = maximum(_singular_value_distance, zip(CSold, CSnew)) TSnew = map(x -> tsvd(x; alg=TensorKit.SVD())[2], envs.edges) - ΔTS = maximum(zip(TSold, TSnew)) do (t_old, t_new) - MPSKit._firstspace(t_old) == MPSKit._firstspace(t_new) || - return scalartype(t_old)(Inf) - return norm(t_new - t_old) - end + ΔTS = maximum(_singular_value_distance, zip(TSold, TSnew)) @debug "maxᵢ|Cⁿ⁺¹ - Cⁿ|ᵢ = $ΔCS maxᵢ|Tⁿ⁺¹ - Tⁿ|ᵢ = $ΔTS" From 479be4aa13480734b251a35f20031a93f9346d6c Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Wed, 18 Sep 2024 11:06:05 +0200 Subject: [PATCH 102/213] Improve defaults, throw error in symmetrize! --- src/PEPSKit.jl | 8 ++++---- src/algorithms/peps_opt.jl | 2 +- src/algorithms/toolbox.jl | 2 +- src/utility/symmetrization.jl | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/PEPSKit.jl b/src/PEPSKit.jl index 3de03819..1f57c445 100644 --- a/src/PEPSKit.jl +++ b/src/PEPSKit.jl @@ -64,14 +64,14 @@ module Defaults using TensorKit, KrylovKit, OptimKit const ctmrg_maxiter = 100 const ctmrg_miniter = 4 - const ctmrg_tol = 1e-10 - const fpgrad_maxiter = 20 + const ctmrg_tol = 1e-8 + const fpgrad_maxiter = 30 const fpgrad_tol = 1e-6 const ctmrgscheme = :simultaneous const iterscheme = :fixed const fwd_alg = TensorKit.SVD() - const rrule_alg = GMRES(; tol=ctmrg_tol) - const optimizer = LBFGS(10; maxiter=100, gradtol=1e-4, verbosity=2) + const rrule_alg = GMRES(; tol=1e1ctmrg_tol) + const optimizer = LBFGS(32; maxiter=100, gradtol=1e-4, verbosity=2) end export SVDAdjoint, IterSVD, NonTruncSVDAdjoint diff --git a/src/algorithms/peps_opt.jl b/src/algorithms/peps_opt.jl index a1afce8d..74d01ec0 100644 --- a/src/algorithms/peps_opt.jl +++ b/src/algorithms/peps_opt.jl @@ -70,7 +70,7 @@ struct LinSolver{F} <: GradMode{F} solver::KrylovKit.LinearSolver end function LinSolver(; - solver=KrylovKit.GMRES(; maxiter=Defaults.fpgrad_maxiter, tol=Defaults.fpgrad_tol), + solver=KrylovKit.BiCGStab(; maxiter=Defaults.fpgrad_maxiter, tol=Defaults.fpgrad_tol), iterscheme=Defaults.iterscheme, ) return LinSolver{iterscheme}(solver) diff --git a/src/algorithms/toolbox.jl b/src/algorithms/toolbox.jl index 7d7463d6..23259708 100644 --- a/src/algorithms/toolbox.jl +++ b/src/algorithms/toolbox.jl @@ -1,6 +1,6 @@ function MPSKit.expectation_value(peps::InfinitePEPS, O::LocalOperator, envs::CTMRGEnv) checklattice(peps, O) - return sum(O.terms) do (inds, operator) + return sum(O.terms) do (inds, operator) # TODO: parallelize this map, especially for the backwards pass contract_localoperator(inds, operator, peps, peps, envs) / contract_localnorm(inds, peps, peps, envs) end diff --git a/src/utility/symmetrization.jl b/src/utility/symmetrization.jl index 737d5717..9c526eb6 100644 --- a/src/utility/symmetrization.jl +++ b/src/utility/symmetrization.jl @@ -149,7 +149,7 @@ function symmetrize!(peps::InfinitePEPS, symm::RotateReflect) end depth, width = size(peps) - depth == width || ArgumentError("$symm can only be applied to square unit cells") + depth == width || throw(ArgumentError("$symm can only be applied to square unit cells")) odd = mod(depth, 2) if odd == 1 From 7bcfc0f90cb0c908a57f27e075f74fb2a027cf0b Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Wed, 18 Sep 2024 15:15:45 +0200 Subject: [PATCH 103/213] Add Rotate symmetrize! --- src/PEPSKit.jl | 2 +- src/utility/symmetrization.jl | 11 +++++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/PEPSKit.jl b/src/PEPSKit.jl index 1f57c445..26f00638 100644 --- a/src/PEPSKit.jl +++ b/src/PEPSKit.jl @@ -85,7 +85,7 @@ export fixedpoint export InfinitePEPS, InfiniteTransferPEPS export InfinitePEPO, InfiniteTransferPEPO export initializeMPS, initializePEPS -export ReflectDepth, ReflectWidth, RotateReflect, symmetrize!, symmetrize_finalize! +export ReflectDepth, ReflectWidth, Rotate, RotateReflect, symmetrize!, symmetrize_finalize! export showtypeofgrad export square_lattice_tf_ising, square_lattice_heisenberg, square_lattice_j1j2 export square_lattice_pwave diff --git a/src/utility/symmetrization.jl b/src/utility/symmetrization.jl index 9c526eb6..76d2b247 100644 --- a/src/utility/symmetrization.jl +++ b/src/utility/symmetrization.jl @@ -132,8 +132,15 @@ function symmetrize!(peps::InfinitePEPS, ::ReflectWidth) return peps end -function symmetrize!(peps::InfinitePEPS, ::Rotate) - return error("TODO") +function symmetrize!(peps::InfinitePEPS, symm::Rotate) + if !isequal(size(peps)...) + throw(ArgumentError("$symm can only be applied to square unit cells")) + end + peps_l90 = PEPSKit._fit_spaces(rotl90(peps), peps) + peps_180 = PEPSKit._fit_spaces(rot180(peps), peps) + peps_r90 = PEPSKit._fit_spaces(rotr90(peps), peps) + peps.A .= 0.25 * (peps.A + peps_l90.A + peps_180.A + peps_r90.A) + return peps end function symmetrize!(peps::InfinitePEPS, symm::RotateReflect) From e9ee9b92b041d3995d9fceb874e2d514f3169951 Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Wed, 18 Sep 2024 16:31:03 +0200 Subject: [PATCH 104/213] Update model function to accept symmetric sectors using MPSKitModels --- Project.toml | 1 + src/operators/models.jl | 63 +++++++++++++++++++++++------------------ 2 files changed, 36 insertions(+), 28 deletions(-) diff --git a/Project.toml b/Project.toml index 236d8e5a..9ed7faf6 100644 --- a/Project.toml +++ b/Project.toml @@ -11,6 +11,7 @@ KrylovKit = "0b1a1467-8014-51b9-945f-bf0ae24f4b77" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" LoggingExtras = "e6f89c97-d47a-5376-807f-9c37f3926c36" MPSKit = "bb1c41ca-d63c-52ed-829e-0820dda26502" +MPSKitModels = "ca635005-6f8c-4cd1-b51d-8491250ef2ab" OptimKit = "77e91f04-9b3b-57a6-a776-40b61faaebe0" Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" diff --git a/src/operators/models.jl b/src/operators/models.jl index 21e7bdc6..2e95836a 100644 --- a/src/operators/models.jl +++ b/src/operators/models.jl @@ -1,3 +1,5 @@ +using MPSKitModels + ## Model Hamiltonians # ------------------- @@ -7,16 +9,18 @@ Square lattice transverse field Ising model. """ function square_lattice_tf_ising( - ::Type{T}=ComplexF64; J=1, h=1, unitcell::Tuple{Int,Int}=(1, 1) + elt::Type{T}=ComplexF64, + symm::Type{<:Sector}=Trivial; + J=1, + h=1, + unitcell::Tuple{Int,Int}=(1, 1), ) where {T<:Number} - physical_space = ComplexSpace(2) - lattice = fill(physical_space, 1, 1) - σx = TensorMap(T[0 1; 1 0], physical_space, physical_space) - σz = TensorMap(T[1 0; 0 -1], physical_space, physical_space) - hzz = nearest_neighbour_hamiltonian(lattice, -J * σz ⊗ σz) + term_zz = rmul!(σᶻᶻ(elt, symm), -J) + term_x = rmul!(σˣ(elt, symm), -J * h) + lattice = fill(domain(term_x)[1], 1, 1) + hzz = nearest_neighbour_hamiltonian(lattice, term_zz) return repeat( - LocalOperator(lattice, hzz.terms..., (CartesianIndex(1, 1),) => -J * h * σx), - unitcell..., + LocalOperator(lattice, hzz.terms..., (CartesianIndex(1, 1),) => term_x), unitcell... ) end @@ -27,15 +31,17 @@ Square lattice Heisenberg model. By default, this implements a single site unit cell via a sublattice rotation. """ function square_lattice_heisenberg( - ::Type{T}=ComplexF64; Jx=-1, Jy=1, Jz=-1, unitcell::Tuple{Int,Int}=(1, 1) + elt::Type{T}=ComplexF64, + symm::Type{<:Sector}=Trivial; + Jx=-1, + Jy=1, + Jz=-1, + unitcell::Tuple{Int,Int}=(1, 1), ) where {T<:Number} - physical_space = ComplexSpace(2) - lattice = fill(physical_space, 1, 1) - σx = TensorMap(T[0 1; 1 0], physical_space, physical_space) - σy = TensorMap(T[0 im; -im 0], physical_space, physical_space) - σz = TensorMap(T[1 0; 0 -1], physical_space, physical_space) - H = (Jx * σx ⊗ σx) + (Jy * σy ⊗ σy) + (Jz * σz ⊗ σz) - return repeat(nearest_neighbour_hamiltonian(lattice, H / 4), unitcell...) + term = + rmul!(S_xx(elt, symm), Jx) + rmul!(S_yy(elt, symm), Jy) + rmul!(S_zz(elt, symm), Jz) + lattice = fill(domain(term)[1], 1, 1) + return repeat(nearest_neighbour_hamiltonian(lattice, term), unitcell...) end """ @@ -46,25 +52,26 @@ Square lattice J₁-J₂ model. The `sublattice` kwarg enables a single site uni sublattice rotation. """ function square_lattice_j1j2( - ::Type{T}=ComplexF64; J1=1, J2=1, unitcell::Tuple{Int,Int}=(1, 1), sublattice=true + elt::Type{T}=ComplexF64, + symm::Type{<:Sector}=Trivial; + J1=1, + J2=1, + unitcell::Tuple{Int,Int}=(1, 1), + sublattice=true, ) where {T<:Number} - physical_space = ComplexSpace(2) - lattice = fill(physical_space, 1, 1) - σx = TensorMap(T[0 1; 1 0], physical_space, physical_space) - σy = TensorMap(T[0 im; -im 0], physical_space, physical_space) - σz = TensorMap(T[1 0; 0 -1], physical_space, physical_space) - h_AA = σx ⊗ σx + σy ⊗ σy + σz ⊗ σz - h_AB = sublattice ? -σx ⊗ σx + σy ⊗ σy - σz ⊗ σz : h_AA # Apply sublattice rotation + term_AA = S_xx(elt, symm) + S_yy(elt, symm) + S_zz(elt, symm) + term_AB = sublattice ? -S_xx(elt, symm) + S_yy(elt, symm) - S_zz(elt, symm) : term_AA # Apply sublattice rotation + lattice = fill(domain(term_AA)[1], 1, 1) terms = [] for I in eachindex(IndexCartesian(), lattice) nearest_x = I + CartesianIndex(1, 0) nearest_y = I + CartesianIndex(0, 1) next_xy = I + CartesianIndex(1, 1) - push!(terms, (I, nearest_x) => J1 / 4 * h_AB) - push!(terms, (I, nearest_y) => J1 / 4 * h_AB) - push!(terms, (I, next_xy) => J2 / 4 * h_AA) - push!(terms, (nearest_x, nearest_y) => J2 / 4 * h_AA) + push!(terms, (I, nearest_x) => J1 * term_AB) + push!(terms, (I, nearest_y) => J1 * term_AB) + push!(terms, (I, next_xy) => J2 * term_AA) + push!(terms, (nearest_x, nearest_y) => J2 * term_AA) end return repeat(LocalOperator(lattice, terms...), unitcell...) From 4021e88bd49d1c9d37e3c1d916e180e2b38d0d13 Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Wed, 18 Sep 2024 17:10:12 +0200 Subject: [PATCH 105/213] Make type annotations parametric --- src/operators/models.jl | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/operators/models.jl b/src/operators/models.jl index 2e95836a..6f53db93 100644 --- a/src/operators/models.jl +++ b/src/operators/models.jl @@ -10,11 +10,11 @@ Square lattice transverse field Ising model. """ function square_lattice_tf_ising( elt::Type{T}=ComplexF64, - symm::Type{<:Sector}=Trivial; + symm::Type{S}=Trivial; J=1, h=1, unitcell::Tuple{Int,Int}=(1, 1), -) where {T<:Number} +) where {T<:Number,S<:Sector} term_zz = rmul!(σᶻᶻ(elt, symm), -J) term_x = rmul!(σˣ(elt, symm), -J * h) lattice = fill(domain(term_x)[1], 1, 1) @@ -32,12 +32,12 @@ By default, this implements a single site unit cell via a sublattice rotation. """ function square_lattice_heisenberg( elt::Type{T}=ComplexF64, - symm::Type{<:Sector}=Trivial; + symm::Type{S}=Trivial; Jx=-1, Jy=1, Jz=-1, unitcell::Tuple{Int,Int}=(1, 1), -) where {T<:Number} +) where {T<:Number,S<:Sector} term = rmul!(S_xx(elt, symm), Jx) + rmul!(S_yy(elt, symm), Jy) + rmul!(S_zz(elt, symm), Jz) lattice = fill(domain(term)[1], 1, 1) @@ -53,12 +53,12 @@ sublattice rotation. """ function square_lattice_j1j2( elt::Type{T}=ComplexF64, - symm::Type{<:Sector}=Trivial; + symm::Type{S}=Trivial; J1=1, J2=1, unitcell::Tuple{Int,Int}=(1, 1), sublattice=true, -) where {T<:Number} +) where {T<:Number,S<:Sector} term_AA = S_xx(elt, symm) + S_yy(elt, symm) + S_zz(elt, symm) term_AB = sublattice ? -S_xx(elt, symm) + S_yy(elt, symm) - S_zz(elt, symm) : term_AA # Apply sublattice rotation lattice = fill(domain(term_AA)[1], 1, 1) From ac7b3f00ade0b295fb200f47fc76201a3163007f Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Thu, 19 Sep 2024 14:24:20 +0200 Subject: [PATCH 106/213] Add Rotate symmetrization test --- test/ctmrg/symmetrization.jl | 47 +++++++++++++++++++++--------------- 1 file changed, 28 insertions(+), 19 deletions(-) diff --git a/test/ctmrg/symmetrization.jl b/test/ctmrg/symmetrization.jl index 7010d781..f221d39a 100644 --- a/test/ctmrg/symmetrization.jl +++ b/test/ctmrg/symmetrization.jl @@ -3,25 +3,6 @@ using PEPSKit using PEPSKit: herm_depth, herm_width, _fit_spaces using TensorKit -@testset "RotateReflect" for unitcell in [(1, 1), (2, 2), (3, 3)] - peps = InfinitePEPS(2, 2; unitcell) - - peps_full = symmetrize!(deepcopy(peps), RotateReflect()) - @test peps_full ≈ _fit_spaces(rotl90(peps_full), peps_full) - @test peps_full ≈ _fit_spaces(rot180(peps_full), peps_full) - @test peps_full ≈ _fit_spaces(rotr90(peps_full), peps_full) - - peps_reflect_depth = _fit_spaces( - InfinitePEPS(reverse(map(herm_depth, peps_full.A); dims=1)), peps_full - ) - @test peps_full ≈ peps_reflect_depth - - peps_reflect_width = _fit_spaces( - InfinitePEPS(reverse(map(herm_width, peps_full.A); dims=2)), peps_full - ) - @test peps_full ≈ peps_reflect_width -end - @testset "ReflectDepth" for unitcell in [(1, 1), (2, 2), (3, 3)] peps = InfinitePEPS(2, 2; unitcell) @@ -41,3 +22,31 @@ end ) @test peps_width ≈ peps_reflect end + +@testset "Rotate" for unitcell in [(1, 1), (2, 2), (3, 3)] + peps = InfinitePEPS(2, 2; unitcell) + + peps_rot = symmetrize!(deepcopy(peps), Rotate()) + @test peps_rot ≈ _fit_spaces(rotl90(peps_rot), peps_rot) + @test peps_rot ≈ _fit_spaces(rot180(peps_rot), peps_rot) + @test peps_rot ≈ _fit_spaces(rotr90(peps_rot), peps_rot) +end + +@testset "RotateReflect" for unitcell in [(1, 1), (2, 2), (3, 3)] + peps = InfinitePEPS(2, 2; unitcell) + + peps_full = symmetrize!(deepcopy(peps), RotateReflect()) + @test peps_full ≈ _fit_spaces(rotl90(peps_full), peps_full) + @test peps_full ≈ _fit_spaces(rot180(peps_full), peps_full) + @test peps_full ≈ _fit_spaces(rotr90(peps_full), peps_full) + + peps_reflect_depth = _fit_spaces( + InfinitePEPS(reverse(map(herm_depth, peps_full.A); dims=1)), peps_full + ) + @test peps_full ≈ peps_reflect_depth + + peps_reflect_width = _fit_spaces( + InfinitePEPS(reverse(map(herm_width, peps_full.A); dims=2)), peps_full + ) + @test peps_full ≈ peps_reflect_width +end From ca9af447bfdff6fa7273c11b594117a65dea9e9a Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Thu, 19 Sep 2024 14:33:57 +0200 Subject: [PATCH 107/213] Update model docstrings --- src/operators/models.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/operators/models.jl b/src/operators/models.jl index 6f53db93..535cf8fa 100644 --- a/src/operators/models.jl +++ b/src/operators/models.jl @@ -4,7 +4,7 @@ using MPSKitModels # ------------------- """ - square_lattice_tf_ising(::Type{T}=ComplexF64; J=1, h=1, unitcell=(1, 1)) + square_lattice_tf_ising(elt::Type{T}=ComplexF64, symm::Type{S}=Trivial; J=1, h=1, unitcell=(1, 1)) Square lattice transverse field Ising model. """ @@ -25,7 +25,7 @@ function square_lattice_tf_ising( end """ - square_lattice_heisenberg(::Type{T}=ComplexF64; Jx=-1, Jy=1, Jz=-1, unitcell=(1, 1)) + square_lattice_heisenberg(elt::Type{T}=ComplexF64, symm::Type{S}=Trivial; Jx=-1, Jy=1, Jz=-1, unitcell=(1, 1)) Square lattice Heisenberg model. By default, this implements a single site unit cell via a sublattice rotation. @@ -45,7 +45,7 @@ function square_lattice_heisenberg( end """ - square_lattice_j1j2(::Type{T}=ComplexF64; J1=1, J2=1, unitcell=(1, 1), sublattice=true) + square_lattice_j1j2(elt::Type{T}=ComplexF64, symm::Type{S}=Trivial; J1=1, J2=1, unitcell=(1, 1), sublattice=true) Square lattice J₁-J₂ model. The `sublattice` kwarg enables a single site unit cell via a From 3a77631615b39d8bfd60867bf3c8867151a8c80d Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Tue, 24 Sep 2024 17:14:08 +0200 Subject: [PATCH 108/213] Adjust default hyperparameters --- src/PEPSKit.jl | 8 ++++---- src/algorithms/peps_opt.jl | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/PEPSKit.jl b/src/PEPSKit.jl index 3de03819..1f57c445 100644 --- a/src/PEPSKit.jl +++ b/src/PEPSKit.jl @@ -64,14 +64,14 @@ module Defaults using TensorKit, KrylovKit, OptimKit const ctmrg_maxiter = 100 const ctmrg_miniter = 4 - const ctmrg_tol = 1e-10 - const fpgrad_maxiter = 20 + const ctmrg_tol = 1e-8 + const fpgrad_maxiter = 30 const fpgrad_tol = 1e-6 const ctmrgscheme = :simultaneous const iterscheme = :fixed const fwd_alg = TensorKit.SVD() - const rrule_alg = GMRES(; tol=ctmrg_tol) - const optimizer = LBFGS(10; maxiter=100, gradtol=1e-4, verbosity=2) + const rrule_alg = GMRES(; tol=1e1ctmrg_tol) + const optimizer = LBFGS(32; maxiter=100, gradtol=1e-4, verbosity=2) end export SVDAdjoint, IterSVD, NonTruncSVDAdjoint diff --git a/src/algorithms/peps_opt.jl b/src/algorithms/peps_opt.jl index a1afce8d..74d01ec0 100644 --- a/src/algorithms/peps_opt.jl +++ b/src/algorithms/peps_opt.jl @@ -70,7 +70,7 @@ struct LinSolver{F} <: GradMode{F} solver::KrylovKit.LinearSolver end function LinSolver(; - solver=KrylovKit.GMRES(; maxiter=Defaults.fpgrad_maxiter, tol=Defaults.fpgrad_tol), + solver=KrylovKit.BiCGStab(; maxiter=Defaults.fpgrad_maxiter, tol=Defaults.fpgrad_tol), iterscheme=Defaults.iterscheme, ) return LinSolver{iterscheme}(solver) From ecc423aa2bcab31856ce3c77db5e406b7300f4a6 Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Tue, 24 Sep 2024 17:32:52 +0200 Subject: [PATCH 109/213] Add more defaults --- src/PEPSKit.jl | 8 ++++++++ src/algorithms/ctmrg/ctmrg.jl | 8 ++++---- src/algorithms/peps_opt.jl | 4 ++-- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/PEPSKit.jl b/src/PEPSKit.jl index 1f57c445..2685458a 100644 --- a/src/PEPSKit.jl +++ b/src/PEPSKit.jl @@ -62,16 +62,24 @@ Module containing default values that represent typical algorithm parameters. """ module Defaults using TensorKit, KrylovKit, OptimKit + using PEPSKit: LinSolver, FixedSpaceTruncation, SVDAdjoint const ctmrg_maxiter = 100 const ctmrg_miniter = 4 const ctmrg_tol = 1e-8 const fpgrad_maxiter = 30 const fpgrad_tol = 1e-6 const ctmrgscheme = :simultaneous + const reuse_env = true + const trscheme = FixedSpaceTruncation() const iterscheme = :fixed const fwd_alg = TensorKit.SVD() const rrule_alg = GMRES(; tol=1e1ctmrg_tol) + const svd_alg = SVDAdjoint(; fwd_alg, rrule_alg) const optimizer = LBFGS(32; maxiter=100, gradtol=1e-4, verbosity=2) + const gradient_linsolver = KrylovKit.BiCGStab(; + maxiter=Defaults.fpgrad_maxiter, tol=Defaults.fpgrad_tol + ) + const gradient_alg = LinSolver(; solver=gradient_linsolver, iterscheme) end export SVDAdjoint, IterSVD, NonTruncSVDAdjoint diff --git a/src/algorithms/ctmrg/ctmrg.jl b/src/algorithms/ctmrg/ctmrg.jl index 4c99a6c2..bba37f25 100644 --- a/src/algorithms/ctmrg/ctmrg.jl +++ b/src/algorithms/ctmrg/ctmrg.jl @@ -18,8 +18,8 @@ kind of truncation scheme can be used. If `fixedspace` is true, the truncation s environment direction/unit cell entry. """ @kwdef struct ProjectorAlg{S<:SVDAdjoint,T} - svd_alg::S = SVDAdjoint() - trscheme::T = FixedSpaceTruncation() + svd_alg::S = Defaults.svd_alg + trscheme::T = Defaults.trscheme verbosity::Int = 0 end # TODO: add option for different projector styles (half-infinite, full-infinite, etc.) @@ -76,8 +76,8 @@ function CTMRG(; maxiter=Defaults.ctmrg_maxiter, miniter=Defaults.ctmrg_miniter, verbosity=2, - svd_alg=SVDAdjoint(), - trscheme=FixedSpaceTruncation(), + svd_alg=Defaults.svd_alg, + trscheme=Defaults.trscheme, ctmrgscheme::Symbol=Defaults.ctmrgscheme, ) return CTMRG{ctmrgscheme}( diff --git a/src/algorithms/peps_opt.jl b/src/algorithms/peps_opt.jl index 74d01ec0..d54c354e 100644 --- a/src/algorithms/peps_opt.jl +++ b/src/algorithms/peps_opt.jl @@ -114,8 +114,8 @@ end function PEPSOptimize(; boundary_alg=CTMRG(), optimizer=Defaults.optimizer, - reuse_env=true, - gradient_alg=LinSolver(), + reuse_env=Defaults.reuse_env, + gradient_alg=Defaults.gradient_alg, ) return PEPSOptimize(boundary_alg, optimizer, reuse_env, gradient_alg) end From 71881b41107b3e4926b854f3c977c00c793bacf5 Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Wed, 25 Sep 2024 11:52:38 +0200 Subject: [PATCH 110/213] Add function for creating product state PEPS with noise --- src/PEPSKit.jl | 2 +- src/algorithms/toolbox.jl | 20 ++++++++++++++++++++ src/states/infinitepeps.jl | 1 + 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/PEPSKit.jl b/src/PEPSKit.jl index 3de03819..9a0f390f 100644 --- a/src/PEPSKit.jl +++ b/src/PEPSKit.jl @@ -77,7 +77,7 @@ end export SVDAdjoint, IterSVD, NonTruncSVDAdjoint export FixedSpaceTruncation, ProjectorAlg, CTMRG, CTMRGEnv, correlation_length export LocalOperator -export expectation_value, costfun +export expectation_value, costfun, product_peps export leading_boundary export PEPSOptimize, GeomSum, ManualIter, LinSolver export fixedpoint diff --git a/src/algorithms/toolbox.jl b/src/algorithms/toolbox.jl index 7d7463d6..ced763db 100644 --- a/src/algorithms/toolbox.jl +++ b/src/algorithms/toolbox.jl @@ -94,3 +94,23 @@ function MPSKit.correlation_length(peps::InfinitePEPS, env::CTMRGEnv; num_vals=2 return ξ_h, ξ_v, λ_h, λ_v end + +""" + product_peps(peps_args...; unitcell=(1, 1), noise_amp=1e-2) + +Initialize a normalized random product PEPS with noise. The given arguments are passed on to +the `InfinitePEPS` constructor. The noise intensity can be tuned with `noise_amp`. +""" +function product_peps(peps_args...; unitcell=(1, 1), noise_amp=1e-2) + noise_peps = InfinitePEPS(peps_args...; unitcell) + typeof(spacetype(noise_peps[1])) <: GradedSpace && + error("symmetric tensors not generically supported") + prod_tensors = map(noise_peps.A) do t + pt = zero(t) + pt[rand(1:dim(space(t, 1))), 1, 1, 1, 1] = randn(scalartype(noise_peps)) + return pt + end + prod_peps = InfinitePEPS(prod_tensors) + ψ = prod_peps + noise_amp * noise_peps + return ψ / norm(ψ) +end diff --git a/src/states/infinitepeps.jl b/src/states/infinitepeps.jl index b6d11a18..654be260 100644 --- a/src/states/infinitepeps.jl +++ b/src/states/infinitepeps.jl @@ -130,6 +130,7 @@ TensorKit.space(t::InfinitePEPS, i, j) = space(t[i, j], 1) Base.:+(ψ₁::InfinitePEPS, ψ₂::InfinitePEPS) = InfinitePEPS(ψ₁.A + ψ₂.A) Base.:-(ψ₁::InfinitePEPS, ψ₂::InfinitePEPS) = InfinitePEPS(ψ₁.A - ψ₂.A) Base.:*(α::Number, ψ::InfinitePEPS) = InfinitePEPS(α * ψ.A) +Base.:/(ψ::InfinitePEPS, α::Number) = InfinitePEPS(ψ.A / α) LinearAlgebra.dot(ψ₁::InfinitePEPS, ψ₂::InfinitePEPS) = dot(ψ₁.A, ψ₂.A) LinearAlgebra.norm(ψ::InfinitePEPS) = norm(ψ.A) From 4317b38372a0253b8de7e495826a35e27a4aeb70 Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Wed, 25 Sep 2024 12:08:55 +0200 Subject: [PATCH 111/213] Finally add similar again --- src/states/infinitepeps.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/states/infinitepeps.jl b/src/states/infinitepeps.jl index 654be260..0a7d63aa 100644 --- a/src/states/infinitepeps.jl +++ b/src/states/infinitepeps.jl @@ -118,7 +118,7 @@ VectorInterface.scalartype(T::InfinitePEPS) = scalartype(T.A) ## Copy Base.copy(T::InfinitePEPS) = InfinitePEPS(copy(T.A)) -# Base.similar(T::InfinitePEPS) = InfinitePEPS(similar(T.A)) # TODO: This is incompatible with inner constructor +Base.similar(T::InfinitePEPS, args...) = InfinitePEPS(similar.(T.A, args...)) Base.repeat(T::InfinitePEPS, counts...) = InfinitePEPS(repeat(T.A, counts...)) Base.getindex(T::InfinitePEPS, args...) = Base.getindex(T.A, args...) From 3c117bea151f731c2c9f7b01dd4d7858ef6cd1aa Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Wed, 25 Sep 2024 12:32:12 +0200 Subject: [PATCH 112/213] Add kwargs for basis element selection and element generation --- src/algorithms/toolbox.jl | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/algorithms/toolbox.jl b/src/algorithms/toolbox.jl index ced763db..a16f3366 100644 --- a/src/algorithms/toolbox.jl +++ b/src/algorithms/toolbox.jl @@ -96,18 +96,23 @@ function MPSKit.correlation_length(peps::InfinitePEPS, env::CTMRGEnv; num_vals=2 end """ - product_peps(peps_args...; unitcell=(1, 1), noise_amp=1e-2) + product_peps(peps_args...; unitcell=(1, 1), noise_amp=1e-2, basis_fun=ran, element_fun=randn) Initialize a normalized random product PEPS with noise. The given arguments are passed on to -the `InfinitePEPS` constructor. The noise intensity can be tuned with `noise_amp`. +the `InfinitePEPS` constructor. + +The noise intensity can be tuned with `noise_amp` and `basis_fun` selects the physical index to +which the element generated from `element_fun` is assigned. """ -function product_peps(peps_args...; unitcell=(1, 1), noise_amp=1e-2) +function product_peps( + peps_args...; unitcell=(1, 1), noise_amp=1e-2, basis_fun=rand, element_fun=randn +) noise_peps = InfinitePEPS(peps_args...; unitcell) typeof(spacetype(noise_peps[1])) <: GradedSpace && error("symmetric tensors not generically supported") prod_tensors = map(noise_peps.A) do t pt = zero(t) - pt[rand(1:dim(space(t, 1))), 1, 1, 1, 1] = randn(scalartype(noise_peps)) + pt[basis_fun(1:dim(space(t, 1))), 1, 1, 1, 1] = element_fun(scalartype(noise_peps)) return pt end prod_peps = InfinitePEPS(prod_tensors) From a4be73d4540e314470c9a42364ad1440daef4ec1 Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Wed, 25 Sep 2024 16:04:07 +0200 Subject: [PATCH 113/213] Use product state PEPS in tests --- test/j1j2_model.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/j1j2_model.jl b/test/j1j2_model.jl index 997d14d3..d3da7e0a 100644 --- a/test/j1j2_model.jl +++ b/test/j1j2_model.jl @@ -26,7 +26,7 @@ opt_alg = PEPSOptimize(; # initialize states Random.seed!(91283219347) H = square_lattice_j1j2(; J2=0.25) -psi_init = InfinitePEPS(2, χbond) +psi_init = product_peps(2, χbond; noise_amp=1e-1) psi_init = symmetrize!(psi_init, RotateReflect()) env_init = leading_boundary(CTMRGEnv(psi_init, ComplexSpace(χenv)), psi_init, ctm_alg); From 505f76de1085bfbfc9df5872ef900074ac5719ad Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Wed, 25 Sep 2024 17:13:25 +0200 Subject: [PATCH 114/213] Fix symmetrization optimization by retracting with symmetrized PEPS --- src/PEPSKit.jl | 3 ++- src/algorithms/peps_opt.jl | 10 ++++++---- src/utility/symmetrization.jl | 20 +++++++++++++------- test/j1j2_model.jl | 4 ++-- 4 files changed, 23 insertions(+), 14 deletions(-) diff --git a/src/PEPSKit.jl b/src/PEPSKit.jl index 26f00638..feaf29f7 100644 --- a/src/PEPSKit.jl +++ b/src/PEPSKit.jl @@ -85,7 +85,8 @@ export fixedpoint export InfinitePEPS, InfiniteTransferPEPS export InfinitePEPO, InfiniteTransferPEPO export initializeMPS, initializePEPS -export ReflectDepth, ReflectWidth, Rotate, RotateReflect, symmetrize!, symmetrize_finalize! +export ReflectDepth, ReflectWidth, Rotate, RotateReflect +export symmetrize!, symmetrize_retract_and_finalize! export showtypeofgrad export square_lattice_tf_ising, square_lattice_heisenberg, square_lattice_j1j2 export square_lattice_pwave diff --git a/src/algorithms/peps_opt.jl b/src/algorithms/peps_opt.jl index 74d01ec0..3de728c0 100644 --- a/src/algorithms/peps_opt.jl +++ b/src/algorithms/peps_opt.jl @@ -122,7 +122,7 @@ end """ fixedpoint(ψ₀::InfinitePEPS{T}, H, alg::PEPSOptimize, [env₀::CTMRGEnv]; - finalize!=OptimKit._finalize!) where {T} + retract=peps_retract, inner=real_inner, finalize!=OptimKit._finalize!) where {T} Optimize `ψ₀` with respect to the Hamiltonian `H` according to the parameters supplied in `alg`. The initial environment `env₀` serves as an initial guess for the first CTMRG run. @@ -146,10 +146,12 @@ function fixedpoint( H, alg::PEPSOptimize, env₀::CTMRGEnv=CTMRGEnv(ψ₀, field(T)^20); + retract=peps_retract, + inner=real_inner, (finalize!)=OptimKit._finalize!, ) where {T} (peps, env), E, ∂E, numfg, convhistory = optimize( - (ψ₀, env₀), alg.optimizer; retract=my_retract, inner=my_inner, finalize! + (ψ₀, env₀), alg.optimizer; retract, inner, finalize! ) do (peps, envs) E, gs = withgradient(peps) do ψ envs´ = hook_pullback( @@ -180,7 +182,7 @@ end # Update PEPS unit cell in non-mutating way # Note: Both x and η are InfinitePEPS during optimization -function my_retract(x, η, α) +function peps_retract(x, η, α) peps = deepcopy(x[1]) peps.A .+= η.A .* α env = deepcopy(x[2]) @@ -188,7 +190,7 @@ function my_retract(x, η, α) end # Take real valued part of dot product -my_inner(_, η₁, η₂) = real(dot(η₁, η₂)) +real_inner(_, η₁, η₂) = real(dot(η₁, η₂)) #= Evaluating the gradient of the cost function for CTMRG: diff --git a/src/utility/symmetrization.jl b/src/utility/symmetrization.jl index 76d2b247..51349963 100644 --- a/src/utility/symmetrization.jl +++ b/src/utility/symmetrization.jl @@ -214,15 +214,21 @@ function symmetrize!(peps::InfinitePEPS, symm::RotateReflect) end """ - symmetrize_finalize!(symm::SymmetrizationStyle) + symmetrize_retract_and_finalize!(symm::SymmetrizationStyle) -Return `finalize!` function for symmetrizing the `peps` and `grad` tensors in-place, -which maps `(peps_symm, envs), E, grad_symm = symmetrize_finalize!((peps, envs), E, grad, numiter)`. +Return the `retract` and `finalize!` function for symmetrizing the `peps` and `grad` tensors. """ -function symmetrize_finalize!(symm::SymmetrizationStyle) - function symmetrize_finalize!((peps, envs), E, grad, _) - peps_symm = symmetrize!(peps, symm) +function symmetrize_retract_and_finalize!(symm::SymmetrizationStyle) + finf = function symmetrize_finalize!((peps, envs), E, grad, _) grad_symm = symmetrize!(grad, symm) - return (peps_symm, envs), E, grad_symm + return (peps, envs), E, grad_symm end + retf = function symmetrize_retract((peps, envs), η, α) + peps_symm = deepcopy(peps) + peps_symm.A .+= η.A .* α + e = deepcopy(envs) + symmetrize!(peps_symm, symm) + return (peps_symm, e), η + end + return retf, finf end diff --git a/test/j1j2_model.jl b/test/j1j2_model.jl index 997d14d3..a43f3316 100644 --- a/test/j1j2_model.jl +++ b/test/j1j2_model.jl @@ -31,8 +31,8 @@ psi_init = symmetrize!(psi_init, RotateReflect()) env_init = leading_boundary(CTMRGEnv(psi_init, ComplexSpace(χenv)), psi_init, ctm_alg); # find fixedpoint -finalize! = symmetrize_finalize!(RotateReflect()) -result = fixedpoint(psi_init, H, opt_alg, env_init; finalize!) +retract, finalize! = symmetrize_retract_and_finalize!(RotateReflect()) +result = fixedpoint(psi_init, H, opt_alg, env_init; retract, finalize!) ξ_h, ξ_v, = correlation_length(result.peps, result.env) # compare against Juraj Hasik's data: From 62c644fa94553bc6d969e1f026c98ee5c63c4be1 Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Wed, 25 Sep 2024 17:21:39 +0200 Subject: [PATCH 115/213] Fix formatting and retract/inner renaming --- src/algorithms/peps_opt.jl | 33 ++++++++++++++++----------------- test/ctmrg/gradients.jl | 4 ++-- 2 files changed, 18 insertions(+), 19 deletions(-) diff --git a/src/algorithms/peps_opt.jl b/src/algorithms/peps_opt.jl index 3de728c0..184bf606 100644 --- a/src/algorithms/peps_opt.jl +++ b/src/algorithms/peps_opt.jl @@ -150,25 +150,24 @@ function fixedpoint( inner=real_inner, (finalize!)=OptimKit._finalize!, ) where {T} - (peps, env), E, ∂E, numfg, convhistory = optimize( - (ψ₀, env₀), alg.optimizer; retract, inner, finalize! - ) do (peps, envs) - E, gs = withgradient(peps) do ψ - envs´ = hook_pullback( - leading_boundary, - envs, - ψ, - alg.boundary_alg; - alg_rrule=alg.gradient_alg, - ) - ignore_derivatives() do - alg.reuse_env && update!(envs, envs´) + (peps, env), E, ∂E, numfg, convhistory = + optimize((ψ₀, env₀), alg.optimizer; retract, inner, finalize!) do (peps, envs) + E, gs = withgradient(peps) do ψ + envs´ = hook_pullback( + leading_boundary, + envs, + ψ, + alg.boundary_alg; + alg_rrule=alg.gradient_alg, + ) + ignore_derivatives() do + alg.reuse_env && update!(envs, envs´) + end + return costfun(ψ, envs´, H) end - return costfun(ψ, envs´, H) + g = only(gs) # `withgradient` returns tuple of gradients `gs` + return E, g end - g = only(gs) # `withgradient` returns tuple of gradients `gs` - return E, g - end return (; peps, env, diff --git a/test/ctmrg/gradients.jl b/test/ctmrg/gradients.jl index 5e0bdb11..27df862e 100644 --- a/test/ctmrg/gradients.jl +++ b/test/ctmrg/gradients.jl @@ -67,8 +67,8 @@ steps = -0.01:0.005:0.01 (psi, env), dir; alpha=steps, - retract=PEPSKit.my_retract, - inner=PEPSKit.my_inner, + retract=PEPSKit.peps_retract, + inner=PEPSKit.real_inner, ) do (peps, envs) E, g = Zygote.withgradient(peps) do psi envs2 = PEPSKit.hook_pullback( From f1ee7493ca30569bc55a7fdea2fea433cd10bce4 Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Thu, 26 Sep 2024 11:47:18 +0200 Subject: [PATCH 116/213] Add InfiniteSquare lattice type --- src/PEPSKit.jl | 5 ++++ src/operators/lattices/squarelattice.jl | 35 +++++++++++++++++++++++++ src/operators/localoperator.jl | 13 --------- src/operators/models.jl | 13 +++++++++ src/utility/symmetrization.jl | 6 ++++- 5 files changed, 58 insertions(+), 14 deletions(-) create mode 100644 src/operators/lattices/squarelattice.jl diff --git a/src/PEPSKit.jl b/src/PEPSKit.jl index feaf29f7..e69e4c26 100644 --- a/src/PEPSKit.jl +++ b/src/PEPSKit.jl @@ -9,6 +9,8 @@ using TensorKit, KrylovKit, MPSKit, OptimKit, TensorOperations using ChainRulesCore, Zygote using LoggingExtras using MPSKit: loginit!, logiter!, logfinish!, logcancel! +using MPSKitModels + include("utility/util.jl") include("utility/svd.jl") @@ -25,6 +27,7 @@ include("operators/infinitepepo.jl") include("operators/transferpepo.jl") include("operators/derivatives.jl") include("operators/localoperator.jl") +include("operators/lattices/squarelattice.jl") include("operators/models.jl") include("environments/ctmrg_environments.jl") @@ -88,6 +91,8 @@ export initializeMPS, initializePEPS export ReflectDepth, ReflectWidth, Rotate, RotateReflect export symmetrize!, symmetrize_retract_and_finalize! export showtypeofgrad +export SquareLattice, vertices, nearest_neighbours, next_nearest_neighbours +export transverse_field_ising, heisenberg_XYZ export square_lattice_tf_ising, square_lattice_heisenberg, square_lattice_j1j2 export square_lattice_pwave diff --git a/src/operators/lattices/squarelattice.jl b/src/operators/lattices/squarelattice.jl new file mode 100644 index 00000000..d222c8c0 --- /dev/null +++ b/src/operators/lattices/squarelattice.jl @@ -0,0 +1,35 @@ +""" + InfiniteSquare(Nrows::Integer=1, Ncols::Integer=1) + +Infinite square lattice with a unit cell of size `(Nrows, Ncols)`. +""" +struct InfiniteSquare <: AbstractLattice + Nrows::Int + Ncols::int + function InfiniteSquare(Nrows::Integer=1, Ncols::Integer=1) + Nrows > 0 && Ncols > 0 || error("unit cell size needs to be positive") + return new(Nrows, Ncols) + end +end + +function vertices(lattice::InfiniteSquare) + return CartesianIndices((1:(lattice.Nrows), 1:(lattice.Ncols))) +end + +function nearest_neighbours(lattice::InfiniteSquare) + neighbors = Tuple{CartesianIndex,CartesianIndex}[] + for idx in vertices(lattice) + push!(neighbors, (idx, idx + CartesianIndex(0, 1))) + push!(neighbors, (idx, idx + CartesianIndex(1, 0))) + end + return neighbors +end + +function next_nearest_neighbours(lattice::InfiniteSquare) + neighbors = Tuple{CartesianIndex,CartesianIndex}[] + for idx in vertices(lattice) + push!(neighbors, (idx, idx + CartesianIndex(1, 1))) + push!(neighbors, (idx + CartesianIndex(0, 1), idx + CartesianIndex(1, 0))) + end + return neighbors +end diff --git a/src/operators/localoperator.jl b/src/operators/localoperator.jl index 65796b88..2698ce8c 100644 --- a/src/operators/localoperator.jl +++ b/src/operators/localoperator.jl @@ -80,19 +80,6 @@ function checklattice(::Type{Bool}, H::LocalOperator, peps::InfinitePEPS) end @non_differentiable checklattice(args...) -function nearest_neighbour_hamiltonian( - lattice::Matrix{S}, h::AbstractTensorMap{S,2,2} -) where {S} - terms = [] - for I in eachindex(IndexCartesian(), lattice) - J1 = I + CartesianIndex(1, 0) - J2 = I + CartesianIndex(0, 1) - push!(terms, (I, J1) => h) - push!(terms, (I, J2) => h) - end - return LocalOperator(lattice, terms...) -end - function Base.repeat(O::LocalOperator, m::Int, n::Int) lattice = repeat(O.lattice, m, n) terms = [] diff --git a/src/operators/models.jl b/src/operators/models.jl index 535cf8fa..3fc780f1 100644 --- a/src/operators/models.jl +++ b/src/operators/models.jl @@ -24,6 +24,19 @@ function square_lattice_tf_ising( ) end +function MPSKitModels.transverse_field_ising( + T::Type{<:Number}=ComplexF64, + S::Union{Type{Trivial},Type{Z2Irrep}}=Trivial, + lattice::SquareLattice=SquareLattice(1, 1); + J=1.0, + g=1.0, +) + ZZ = rmul!(σᶻᶻ(T, S), -J) + X = rmul!(σˣ(T, S), g * -J) + # TODO + # LocalOperator(spaces, (i, j) => O for (i, j) in nearest_neighbors(lattice)) +end + """ square_lattice_heisenberg(elt::Type{T}=ComplexF64, symm::Type{S}=Trivial; Jx=-1, Jy=1, Jz=-1, unitcell=(1, 1)) diff --git a/src/utility/symmetrization.jl b/src/utility/symmetrization.jl index 51349963..5bec6a96 100644 --- a/src/utility/symmetrization.jl +++ b/src/utility/symmetrization.jl @@ -14,7 +14,11 @@ Reflection symmmetrization along the vertical axis, such that east and west are """ struct ReflectWidth <: SymmetrizationStyle end -# TODO +""" + struct Rotate <: SymmetrizationStyle + +Rotation symmmetrization leaving the object invariant under π/2 rotations. +""" struct Rotate <: SymmetrizationStyle end """ From 27b9551aeeb4ff8fa672a81638c97575448d9530 Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Thu, 26 Sep 2024 15:36:36 +0200 Subject: [PATCH 117/213] Update pwave, rename Hamiltonians in all files --- README.md | 2 +- docs/src/index.md | 2 +- examples/heisenberg.jl | 2 +- src/PEPSKit.jl | 6 +- src/operators/lattices/squarelattice.jl | 4 +- src/operators/models.jl | 162 +++++++++++------------- test/ctmrg/ctmrgschemes.jl | 2 +- test/ctmrg/gradients.jl | 2 +- test/heisenberg.jl | 7 +- test/j1j2_model.jl | 2 +- test/pwave.jl | 3 +- test/tf_ising.jl | 6 +- 12 files changed, 95 insertions(+), 105 deletions(-) diff --git a/README.md b/README.md index 697c94b6..60ce588f 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ For example, in order to obtain the groundstate of the 2D Heisenberg model, we c using TensorKit, PEPSKit, KrylovKit, OptimKit # constructing the Hamiltonian: -H = square_lattice_heisenberg(; Jx=-1, Jy=1, Jz=-1) # sublattice rotation to obtain single-site unit cell +H = heisenberg_XYZ(; Jx=-1, Jy=1, Jz=-1) # sublattice rotation to obtain single-site unit cell # configuring the parameters D = 2 diff --git a/docs/src/index.md b/docs/src/index.md index 24efebff..cf5f0703 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -21,7 +21,7 @@ For example, in order to obtain the groundstate of the 2D Heisenberg model, we c using TensorKit, PEPSKit, KrylovKit, OptimKit # constructing the Hamiltonian: -H = square_lattice_heisenberg(; Jx=-1, Jy=1, Jz=-1) # sublattice rotation to obtain single-site unit cell +H = heisenberg_XYZ(; Jx=-1, Jy=1, Jz=-1) # sublattice rotation to obtain single-site unit cell # configuring the parameters D = 2 diff --git a/examples/heisenberg.jl b/examples/heisenberg.jl index 3134f7f1..473e9cab 100644 --- a/examples/heisenberg.jl +++ b/examples/heisenberg.jl @@ -6,7 +6,7 @@ using PEPSKit, KrylovKit # We use the parameters (J₁, J₂, J₃) = (-1, 1, -1) by default to capture # the ground state in a single-site unit cell. This can be seen from # sublattice rotating H from parameters (1, 1, 1) to (-1, 1, -1). -H = square_lattice_heisenberg(; Jx=-1, Jy=1, Jz=-1) +H = heisenberg_XYZ(; Jx=-1, Jy=1, Jz=-1) # Parameters χbond = 2 diff --git a/src/PEPSKit.jl b/src/PEPSKit.jl index e69e4c26..6c2472c2 100644 --- a/src/PEPSKit.jl +++ b/src/PEPSKit.jl @@ -91,9 +91,7 @@ export initializeMPS, initializePEPS export ReflectDepth, ReflectWidth, Rotate, RotateReflect export symmetrize!, symmetrize_retract_and_finalize! export showtypeofgrad -export SquareLattice, vertices, nearest_neighbours, next_nearest_neighbours -export transverse_field_ising, heisenberg_XYZ -export square_lattice_tf_ising, square_lattice_heisenberg, square_lattice_j1j2 -export square_lattice_pwave +export InfiniteSquare, vertices, nearest_neighbours, next_nearest_neighbours +export transverse_field_ising, heisenberg_XYZ, j1_j2, pwave_superconductor end # module diff --git a/src/operators/lattices/squarelattice.jl b/src/operators/lattices/squarelattice.jl index d222c8c0..097a4f7b 100644 --- a/src/operators/lattices/squarelattice.jl +++ b/src/operators/lattices/squarelattice.jl @@ -3,9 +3,9 @@ Infinite square lattice with a unit cell of size `(Nrows, Ncols)`. """ -struct InfiniteSquare <: AbstractLattice +struct InfiniteSquare <: AbstractLattice{2} Nrows::Int - Ncols::int + Ncols::Int function InfiniteSquare(Nrows::Integer=1, Ncols::Integer=1) Nrows > 0 && Ncols > 0 || error("unit cell size needs to be positive") return new(Nrows, Ncols) diff --git a/src/operators/models.jl b/src/operators/models.jl index 3fc780f1..002784de 100644 --- a/src/operators/models.jl +++ b/src/operators/models.jl @@ -1,109 +1,102 @@ -using MPSKitModels - ## Model Hamiltonians # ------------------- - -""" - square_lattice_tf_ising(elt::Type{T}=ComplexF64, symm::Type{S}=Trivial; J=1, h=1, unitcell=(1, 1)) - -Square lattice transverse field Ising model. -""" -function square_lattice_tf_ising( - elt::Type{T}=ComplexF64, - symm::Type{S}=Trivial; - J=1, - h=1, - unitcell::Tuple{Int,Int}=(1, 1), -) where {T<:Number,S<:Sector} - term_zz = rmul!(σᶻᶻ(elt, symm), -J) - term_x = rmul!(σˣ(elt, symm), -J * h) - lattice = fill(domain(term_x)[1], 1, 1) - hzz = nearest_neighbour_hamiltonian(lattice, term_zz) - return repeat( - LocalOperator(lattice, hzz.terms..., (CartesianIndex(1, 1),) => term_x), unitcell... - ) +function nearest_neighbour_hamiltonian( + lattice::Matrix{S}, h::AbstractTensorMap{S,2,2} +) where {S} + terms = [] + for I in eachindex(IndexCartesian(), lattice) + J1 = I + CartesianIndex(1, 0) + J2 = I + CartesianIndex(0, 1) + push!(terms, (I, J1) => h) + push!(terms, (I, J2) => h) + end + return LocalOperator(lattice, terms...) end function MPSKitModels.transverse_field_ising( - T::Type{<:Number}=ComplexF64, - S::Union{Type{Trivial},Type{Z2Irrep}}=Trivial, - lattice::SquareLattice=SquareLattice(1, 1); + T::Type{<:Number}, + S::Union{Type{Trivial},Type{Z2Irrep}}, + lattice::InfiniteSquare=InfiniteSquare(1, 1); J=1.0, g=1.0, ) ZZ = rmul!(σᶻᶻ(T, S), -J) X = rmul!(σˣ(T, S), g * -J) - # TODO - # LocalOperator(spaces, (i, j) => O for (i, j) in nearest_neighbors(lattice)) + spaces = fill(domain(X)[1], (lattice.Nrows, lattice.Ncols)) + return LocalOperator( + spaces, + (neighbor => ZZ for neighbor in nearest_neighbours(lattice))..., + ((idx,) => X for idx in vertices(lattice))..., + ) end -""" - square_lattice_heisenberg(elt::Type{T}=ComplexF64, symm::Type{S}=Trivial; Jx=-1, Jy=1, Jz=-1, unitcell=(1, 1)) - -Square lattice Heisenberg model. -By default, this implements a single site unit cell via a sublattice rotation. -""" -function square_lattice_heisenberg( - elt::Type{T}=ComplexF64, - symm::Type{S}=Trivial; - Jx=-1, - Jy=1, - Jz=-1, - unitcell::Tuple{Int,Int}=(1, 1), -) where {T<:Number,S<:Sector} +function MPSKitModels.heisenberg_XYZ( + lattice::InfiniteSquare=InfiniteSquare(1, 1); kwargs... +) + return heisenberg_XYZ(ComplexF64, Trivial, lattice; kwargs...) +end +function MPSKitModels.heisenberg_XYZ( + T::Type{<:Number}, + S::Type{<:Sector}, + lattice::InfiniteSquare; + Jx=-1.0, + Jy=1.0, + Jz=-1.0, + spin=1//2, +) term = - rmul!(S_xx(elt, symm), Jx) + rmul!(S_yy(elt, symm), Jy) + rmul!(S_zz(elt, symm), Jz) - lattice = fill(domain(term)[1], 1, 1) - return repeat(nearest_neighbour_hamiltonian(lattice, term), unitcell...) + rmul!(S_xx(T, S; spin=spin), Jx) + + rmul!(S_yy(T, S; spin=spin), Jy) + + rmul!(S_zz(T, S; spin=spin), Jz) + spaces = fill(domain(term)[1], (lattice.Nrows, lattice.Ncols)) + return LocalOperator( + spaces, (neighbor => term for neighbor in nearest_neighbours(lattice))... + ) end """ - square_lattice_j1j2(elt::Type{T}=ComplexF64, symm::Type{S}=Trivial; J1=1, J2=1, unitcell=(1, 1), sublattice=true) - + j1_j2([elt::Type{T}], [symm::Type{S}], [lattice::InfiniteSquare]; + J1=1.0, J2=1.0, spin=1//2, sublattice=true) Square lattice J₁-J₂ model. The `sublattice` kwarg enables a single site unit cell via a sublattice rotation. """ -function square_lattice_j1j2( - elt::Type{T}=ComplexF64, - symm::Type{S}=Trivial; - J1=1, - J2=1, - unitcell::Tuple{Int,Int}=(1, 1), +function j1_j2( + T::Type{<:Number}=ComplexF64, + S::Type{<:Sector}=Trivial, + lattice::InfiniteSquare=InfiniteSquare(1, 1); + J1=1.0, + J2=1.0, + spin=1//2, sublattice=true, -) where {T<:Number,S<:Sector} - term_AA = S_xx(elt, symm) + S_yy(elt, symm) + S_zz(elt, symm) - term_AB = sublattice ? -S_xx(elt, symm) + S_yy(elt, symm) - S_zz(elt, symm) : term_AA # Apply sublattice rotation - lattice = fill(domain(term_AA)[1], 1, 1) - - terms = [] - for I in eachindex(IndexCartesian(), lattice) - nearest_x = I + CartesianIndex(1, 0) - nearest_y = I + CartesianIndex(0, 1) - next_xy = I + CartesianIndex(1, 1) - push!(terms, (I, nearest_x) => J1 * term_AB) - push!(terms, (I, nearest_y) => J1 * term_AB) - push!(terms, (I, next_xy) => J2 * term_AA) - push!(terms, (nearest_x, nearest_y) => J2 * term_AA) +) + term_AA = S_xx(T, S; spin) + S_yy(T, S; spin) + S_zz(T, S; spin) + term_AB = if sublattice + -S_xx(T, S; spin) + S_yy(T, S; spin) - S_zz(T, S; spin) # Apply sublattice rotation + else + term_AA end - - return repeat(LocalOperator(lattice, terms...), unitcell...) + spaces = fill(domain(term_AA)[1], (lattice.Nrows, lattice.Ncols)) + return LocalOperator( + spaces, + (neighbor => J1 * term_AB for neighbor in nearest_neighbours(lattice))..., + (neighbor => J2 * term_AA for neighbor in next_nearest_neighbours(lattice))..., + ) end """ - square_lattice_pwave(::Type{T}=ComplexF64; t=1, μ=2, Δ=1, unitcell=(1, 1)) + pwave_superconductor(::Type{T}=ComplexF64; t=1, μ=2, Δ=1, unitcell=(1, 1)) Square lattice p-wave superconductor model. """ -function square_lattice_pwave( - ::Type{T}=ComplexF64; - t::Number=1, - μ::Number=2, - Δ::Number=1, - unitcell::Tuple{Int,Int}=(1, 1), -) where {T<:Number} +function pwave_superconductor(lattice::InfiniteSquare=InfiniteSquare(1, 1); kwargs...) + return pwave_superconductor(ComplexF64, lattice; kwargs...) +end +function pwave_superconductor( + T::Type{<:Number}, lattice::InfiniteSquare; t::Number=1, μ::Number=2, Δ::Number=1 +) physical_space = Vect[FermionParity](0 => 1, 1 => 1) - lattice = fill(physical_space, 1, 1) + spaces = fill(physical_space, (lattice.Nrows, lattice.Ncols)) # on-site h0 = TensorMap(zeros, T, physical_space ← physical_space) @@ -119,13 +112,12 @@ function square_lattice_pwave( block(hy, FermionParity(0)) .= [0 Δ*im; -Δ*im 0] block(hy, FermionParity(1)) .= [0 -t; -t 0] - return repeat( - LocalOperator( - lattice, - (CartesianIndex(1, 1),) => h0, - (CartesianIndex(1, 1), CartesianIndex(1, 2)) => hx, - (CartesianIndex(1, 1), CartesianIndex(2, 1)) => hy, - ), - unitcell..., + x_neighbors = filter(n -> n[2].I[2] > n[1].I[2], nearest_neighbours(lattice)) + y_neighbors = filter(n -> n[2].I[1] > n[1].I[1], nearest_neighbours(lattice)) + return LocalOperator( + spaces, + ((idx,) => h0 for idx in vertices(lattice))..., + (neighbor => hx for neighbor in x_neighbors)..., + (neighbor => hy for neighbor in y_neighbors)..., ) end diff --git a/test/ctmrg/ctmrgschemes.jl b/test/ctmrg/ctmrgschemes.jl index 5f969ce9..4641a9bc 100644 --- a/test/ctmrg/ctmrgschemes.jl +++ b/test/ctmrg/ctmrgschemes.jl @@ -45,7 +45,7 @@ unitcells = [(1, 1), (3, 4)] @test ΔTS < 1e-2 # compare Heisenberg energies - H = square_lattice_heisenberg(; unitcell) + H = heisenberg_XYZ(; unitcell) E_sequential = costfun(psi, env_sequential, H) E_simultaneous = costfun(psi, env_simultaneous, H) @test E_sequential ≈ E_simultaneous rtol = 1e-4 diff --git a/test/ctmrg/gradients.jl b/test/ctmrg/gradients.jl index 27df862e..b095d7e2 100644 --- a/test/ctmrg/gradients.jl +++ b/test/ctmrg/gradients.jl @@ -13,7 +13,7 @@ using KrylovKit Pspaces = [ComplexSpace(2), Vect[FermionParity](0 => 1, 1 => 1)] Vspaces = [ComplexSpace(χbond), Vect[FermionParity](0 => χbond / 2, 1 => χbond / 2)] Espaces = [ComplexSpace(χenv), Vect[FermionParity](0 => χenv / 2, 1 => χenv / 2)] -models = [square_lattice_heisenberg(), square_lattice_pwave()] +models = [heisenberg_XYZ(), pwave_superconductor()] names = ["Heisenberg", "p-wave superconductor"] gradtol = 1e-4 diff --git a/test/heisenberg.jl b/test/heisenberg.jl index c299e31a..0290cd53 100644 --- a/test/heisenberg.jl +++ b/test/heisenberg.jl @@ -25,7 +25,7 @@ opt_alg = PEPSOptimize(; # initialize states Random.seed!(91283219347) -H = square_lattice_heisenberg() +H = heisenberg_XYZ() psi_init = InfinitePEPS(2, χbond) env_init = leading_boundary(CTMRGEnv(psi_init, ComplexSpace(χenv)), psi_init, ctm_alg) @@ -37,8 +37,9 @@ result = fixedpoint(psi_init, H, opt_alg, env_init) @test all(@. ξ_h > 0 && ξ_v > 0) # same test but for 1x2 unit cell -H_1x2 = square_lattice_heisenberg(; unitcell=(1, 2)) -psi_init_1x2 = InfinitePEPS(2, χbond; unitcell=(1, 2)) +unitcell = (1, 2) +H_1x2 = heisenberg_XYZ(InfiniteSquare(unitcell...)) +psi_init_1x2 = InfinitePEPS(2, χbond; unitcell) env_init_1x2 = leading_boundary( CTMRGEnv(psi_init_1x2, ComplexSpace(χenv)), psi_init_1x2, ctm_alg ) diff --git a/test/j1j2_model.jl b/test/j1j2_model.jl index a43f3316..83e7a6e0 100644 --- a/test/j1j2_model.jl +++ b/test/j1j2_model.jl @@ -25,7 +25,7 @@ opt_alg = PEPSOptimize(; # initialize states Random.seed!(91283219347) -H = square_lattice_j1j2(; J2=0.25) +H = j1_j2(; J2=0.25) psi_init = InfinitePEPS(2, χbond) psi_init = symmetrize!(psi_init, RotateReflect()) env_init = leading_boundary(CTMRGEnv(psi_init, ComplexSpace(χenv)), psi_init, ctm_alg); diff --git a/test/pwave.jl b/test/pwave.jl index 39b45c51..307f1bf6 100644 --- a/test/pwave.jl +++ b/test/pwave.jl @@ -6,8 +6,7 @@ using KrylovKit using OptimKit # Initialize parameters -unitcell = (2, 2) -H = square_lattice_pwave(; unitcell) +H = pwave_superconductor(InfiniteSquare(2, 2)) χbond = 2 χenv = 16 ctm_alg = CTMRG(; tol=1e-8, maxiter=150, verbosity=2, ctmrgscheme=:sequential) diff --git a/test/tf_ising.jl b/test/tf_ising.jl index 781f8e63..53a87913 100644 --- a/test/tf_ising.jl +++ b/test/tf_ising.jl @@ -12,7 +12,7 @@ using OptimKit # Phys. Rev. Lett. 101, 250602 – Published 18 December 2008 # (values estimated from plots) # (factor of 2 in the energy due to convention differences) -h = 3.1 +g = 3.1 e = -1.6417 * 2 mˣ = 0.91 @@ -34,7 +34,7 @@ opt_alg = PEPSOptimize(; ) # initialize states -H = square_lattice_tf_ising(; h) +H = transverse_field_ising(; g) Random.seed!(91283219347) psi_init = InfinitePEPS(2, χbond) env_init = leading_boundary(CTMRGEnv(psi_init, ComplexSpace(χenv)), psi_init, ctm_alg) @@ -53,7 +53,7 @@ magn = expectation_value(result.peps, M, result.env) @test abs(magn) ≈ mˣ atol = 5e-2 # find fixedpoint in polarized phase and compute correlations lengths -H_polar = square_lattice_tf_ising(; h=4.5) +H_polar = transverse_field_ising(; g=4.5) result_polar = fixedpoint(psi_init, H_polar, opt_alg, env_init) ξ_h_polar, ξ_v_polar, = correlation_length(result_polar.peps, result_polar.env) @test ξ_h_polar < ξ_h From 10f786883cb0c09dacdd539740b6348e6496aa02 Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Thu, 26 Sep 2024 15:52:46 +0200 Subject: [PATCH 118/213] Fix formatting --- src/PEPSKit.jl | 1 - src/operators/models.jl | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/PEPSKit.jl b/src/PEPSKit.jl index 6c2472c2..36f5bae8 100644 --- a/src/PEPSKit.jl +++ b/src/PEPSKit.jl @@ -11,7 +11,6 @@ using LoggingExtras using MPSKit: loginit!, logiter!, logfinish!, logcancel! using MPSKitModels - include("utility/util.jl") include("utility/svd.jl") include("utility/rotations.jl") diff --git a/src/operators/models.jl b/src/operators/models.jl index 002784de..4dd1e3ac 100644 --- a/src/operators/models.jl +++ b/src/operators/models.jl @@ -112,8 +112,8 @@ function pwave_superconductor( block(hy, FermionParity(0)) .= [0 Δ*im; -Δ*im 0] block(hy, FermionParity(1)) .= [0 -t; -t 0] - x_neighbors = filter(n -> n[2].I[2] > n[1].I[2], nearest_neighbours(lattice)) - y_neighbors = filter(n -> n[2].I[1] > n[1].I[1], nearest_neighbours(lattice)) + x_neighbors = filter(n -> n[2].I[2] > n[1].I[2], nearest_neighbours(lattice)) + y_neighbors = filter(n -> n[2].I[1] > n[1].I[1], nearest_neighbours(lattice)) return LocalOperator( spaces, ((idx,) => h0 for idx in vertices(lattice))..., From 63c85ceae06be4882d70c9f91af153433198f48b Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Thu, 26 Sep 2024 16:58:56 +0200 Subject: [PATCH 119/213] Fix ctmrgschemes test --- test/ctmrg/ctmrgschemes.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/ctmrg/ctmrgschemes.jl b/test/ctmrg/ctmrgschemes.jl index 4641a9bc..be8a909b 100644 --- a/test/ctmrg/ctmrgschemes.jl +++ b/test/ctmrg/ctmrgschemes.jl @@ -45,7 +45,7 @@ unitcells = [(1, 1), (3, 4)] @test ΔTS < 1e-2 # compare Heisenberg energies - H = heisenberg_XYZ(; unitcell) + H = heisenberg_XYZ(InfiniteSquare(unitcell...)) E_sequential = costfun(psi, env_sequential, H) E_simultaneous = costfun(psi, env_simultaneous, H) @test E_sequential ≈ E_simultaneous rtol = 1e-4 From 199aaf2b1903b12b2819ad8fe90b49281914da2c Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Fri, 27 Sep 2024 09:57:29 +0200 Subject: [PATCH 120/213] Fix tests attempt 2 --- test/pwave.jl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/pwave.jl b/test/pwave.jl index 307f1bf6..1edeb449 100644 --- a/test/pwave.jl +++ b/test/pwave.jl @@ -6,7 +6,8 @@ using KrylovKit using OptimKit # Initialize parameters -H = pwave_superconductor(InfiniteSquare(2, 2)) +unitcell = (2, 2) +H = pwave_superconductor(InfiniteSquare(unitcell...)) χbond = 2 χenv = 16 ctm_alg = CTMRG(; tol=1e-8, maxiter=150, verbosity=2, ctmrgscheme=:sequential) From 524bc3087b5946f131a1c6388b0907e44ba0b0af Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Sat, 28 Sep 2024 00:43:49 +0200 Subject: [PATCH 121/213] Replace product_peps kwargs with one single site function --- src/algorithms/toolbox.jl | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/algorithms/toolbox.jl b/src/algorithms/toolbox.jl index a16f3366..a18b9d52 100644 --- a/src/algorithms/toolbox.jl +++ b/src/algorithms/toolbox.jl @@ -96,23 +96,24 @@ function MPSKit.correlation_length(peps::InfinitePEPS, env::CTMRGEnv; num_vals=2 end """ - product_peps(peps_args...; unitcell=(1, 1), noise_amp=1e-2, basis_fun=ran, element_fun=randn) + product_peps(peps_args...; unitcell=(1, 1), noise_amp=1e-2, single_vector_fun=randn) Initialize a normalized random product PEPS with noise. The given arguments are passed on to the `InfinitePEPS` constructor. -The noise intensity can be tuned with `noise_amp` and `basis_fun` selects the physical index to -which the element generated from `element_fun` is assigned. +The noise intensity can be tuned with `noise_amp` and `single_site_fun(::Type{<:Number}, ::Int)` +returns a vector with the product state coefficients. """ function product_peps( - peps_args...; unitcell=(1, 1), noise_amp=1e-2, basis_fun=rand, element_fun=randn + peps_args...; unitcell=(1, 1), noise_amp=1e-2, single_vector_fun=randn ) noise_peps = InfinitePEPS(peps_args...; unitcell) typeof(spacetype(noise_peps[1])) <: GradedSpace && error("symmetric tensors not generically supported") prod_tensors = map(noise_peps.A) do t pt = zero(t) - pt[basis_fun(1:dim(space(t, 1))), 1, 1, 1, 1] = element_fun(scalartype(noise_peps)) + ptdata = block(pt, Trivial()) + ptdata[:, 1, 1, 1, 1] .= single_vector_fun(scalartype(noise_peps), dim(space(t, 1))) return pt end prod_peps = InfinitePEPS(prod_tensors) From cb56b6c6a7c1b356ed785464e5996f442a7f02a7 Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Mon, 30 Sep 2024 10:00:48 +0200 Subject: [PATCH 122/213] Replace single site function with explicit vector and nothing as default kwarg --- src/algorithms/toolbox.jl | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/src/algorithms/toolbox.jl b/src/algorithms/toolbox.jl index a18b9d52..93660aef 100644 --- a/src/algorithms/toolbox.jl +++ b/src/algorithms/toolbox.jl @@ -96,24 +96,32 @@ function MPSKit.correlation_length(peps::InfinitePEPS, env::CTMRGEnv; num_vals=2 end """ - product_peps(peps_args...; unitcell=(1, 1), noise_amp=1e-2, single_vector_fun=randn) + product_peps(peps_args...; unitcell=(1, 1), noise_amp=1e-2, state_vector=nothing) Initialize a normalized random product PEPS with noise. The given arguments are passed on to the `InfinitePEPS` constructor. -The noise intensity can be tuned with `noise_amp` and `single_site_fun(::Type{<:Number}, ::Int)` -returns a vector with the product state coefficients. +The noise intensity can be tuned with `noise_amp`. The product state coefficients can be +specified using the `state_vector` kwarg in the form of a matrix of size `unitcell` +containing vectors that match the PEPS physical dimensions. If `nothing` is provided, +random Gaussian coefficients are used. """ -function product_peps( - peps_args...; unitcell=(1, 1), noise_amp=1e-2, single_vector_fun=randn -) +function product_peps(peps_args...; unitcell=(1, 1), noise_amp=1e-2, state_vector=nothing) noise_peps = InfinitePEPS(peps_args...; unitcell) typeof(spacetype(noise_peps[1])) <: GradedSpace && error("symmetric tensors not generically supported") - prod_tensors = map(noise_peps.A) do t + if isnothing(state_vector) + state_vector = map(noise_peps.A) do t + randn(scalartype(t), dim(space(t, 1))) + end + else + all(dim.(space.(noise_peps.A, 1)) .== length.(state_vector)) || + throw(ArgumentError("state vectors must match the physical dimension")) + end + prod_tensors = map(zip(noise_peps.A, state_vector)) do (t, v) pt = zero(t) ptdata = block(pt, Trivial()) - ptdata[:, 1, 1, 1, 1] .= single_vector_fun(scalartype(noise_peps), dim(space(t, 1))) + ptdata[:, 1, 1, 1, 1] .= v return pt end prod_peps = InfinitePEPS(prod_tensors) From 3cd85912c4649b42fa945cb49f5a253fc883ef03 Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Mon, 30 Sep 2024 11:10:22 +0200 Subject: [PATCH 123/213] Add symmetrization kwarg to fixedpoint --- src/algorithms/peps_opt.jl | 45 +++++++++++++++++++++++--------------- test/j1j2_model.jl | 4 ++-- 2 files changed, 29 insertions(+), 20 deletions(-) diff --git a/src/algorithms/peps_opt.jl b/src/algorithms/peps_opt.jl index 184bf606..7451f69b 100644 --- a/src/algorithms/peps_opt.jl +++ b/src/algorithms/peps_opt.jl @@ -146,28 +146,37 @@ function fixedpoint( H, alg::PEPSOptimize, env₀::CTMRGEnv=CTMRGEnv(ψ₀, field(T)^20); - retract=peps_retract, - inner=real_inner, (finalize!)=OptimKit._finalize!, + symmetrization=nothing, ) where {T} - (peps, env), E, ∂E, numfg, convhistory = - optimize((ψ₀, env₀), alg.optimizer; retract, inner, finalize!) do (peps, envs) - E, gs = withgradient(peps) do ψ - envs´ = hook_pullback( - leading_boundary, - envs, - ψ, - alg.boundary_alg; - alg_rrule=alg.gradient_alg, - ) - ignore_derivatives() do - alg.reuse_env && update!(envs, envs´) - end - return costfun(ψ, envs´, H) + if isnothing(symmetrization) + retract = peps_retract + else + retract, symm_finalize! = symmetrize_retract_and_finalize!(symmetrization) + fin! = finalize! # Previous finalize! + finalize! = (x, f, g, numiter) -> fin!(symm_finalize!(x, f, g, numiter)..., numiter) + end + + (peps, env), E, ∂E, numfg, convhistory = optimize( + (ψ₀, env₀), alg.optimizer; retract, inner=real_inner, finalize! + ) do (peps, envs) + E, gs = withgradient(peps) do ψ + envs´ = hook_pullback( + leading_boundary, + envs, + ψ, + alg.boundary_alg; + alg_rrule=alg.gradient_alg, + ) + ignore_derivatives() do + alg.reuse_env && update!(envs, envs´) end - g = only(gs) # `withgradient` returns tuple of gradients `gs` - return E, g + return costfun(ψ, envs´, H) end + g = only(gs) # `withgradient` returns tuple of gradients `gs` + return E, g + end + return (; peps, env, diff --git a/test/j1j2_model.jl b/test/j1j2_model.jl index 83e7a6e0..8c391254 100644 --- a/test/j1j2_model.jl +++ b/test/j1j2_model.jl @@ -31,8 +31,7 @@ psi_init = symmetrize!(psi_init, RotateReflect()) env_init = leading_boundary(CTMRGEnv(psi_init, ComplexSpace(χenv)), psi_init, ctm_alg); # find fixedpoint -retract, finalize! = symmetrize_retract_and_finalize!(RotateReflect()) -result = fixedpoint(psi_init, H, opt_alg, env_init; retract, finalize!) +result = fixedpoint(psi_init, H, opt_alg, env_init; symmetrization=RotateReflect()) ξ_h, ξ_v, = correlation_length(result.peps, result.env) # compare against Juraj Hasik's data: @@ -40,3 +39,4 @@ result = fixedpoint(psi_init, H, opt_alg, env_init; retract, finalize!) ξ_ref = -1 / log(0.2723596743547324) @test result.E ≈ -0.5618837021945925 atol = 1e-3 @test all(@. isapprox(ξ_h, ξ_ref; atol=1e-1) && isapprox(ξ_v, ξ_ref; atol=1e-1)) +@test ξ_h ≈ ξ_v atol = 1e-6 # Test symmetrization of optimized PEPS and environment From 9aff173c6c80cb0fe8398153221702287abad168 Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Mon, 30 Sep 2024 11:16:16 +0200 Subject: [PATCH 124/213] Update fixedpoint docstring --- src/algorithms/peps_opt.jl | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/algorithms/peps_opt.jl b/src/algorithms/peps_opt.jl index 7451f69b..86322594 100644 --- a/src/algorithms/peps_opt.jl +++ b/src/algorithms/peps_opt.jl @@ -122,7 +122,7 @@ end """ fixedpoint(ψ₀::InfinitePEPS{T}, H, alg::PEPSOptimize, [env₀::CTMRGEnv]; - retract=peps_retract, inner=real_inner, finalize!=OptimKit._finalize!) where {T} + finalize!=OptimKit._finalize!, symmetrization=nothing) where {T} Optimize `ψ₀` with respect to the Hamiltonian `H` according to the parameters supplied in `alg`. The initial environment `env₀` serves as an initial guess for the first CTMRG run. @@ -131,6 +131,9 @@ By default, a random initial environment is used. The `finalize!` kwarg can be used to insert a function call after each optimization step by utilizing the `finalize!` kwarg of `OptimKit.optimize`. The function maps `(peps, envs), f, g = finalize!((peps, envs), f, g, numiter)`. +The `symmetrization` kwarg accepts `nothing` or a `SymmetrizationStyle`, in which case the +PEPS and PEPS gradient are symmetrized after each optimization iteration. Note that this +requires a symmmetric `ψ₀` and `env₀` to converge properly. The function returns a `NamedTuple` which contains the following entries: - `peps`: final `InfinitePEPS` From 342cba3bcf5a0d970149525ea27319f620d936e2 Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Mon, 30 Sep 2024 11:26:17 +0200 Subject: [PATCH 125/213] Remove InfiniteSquare as default model lattice --- README.md | 2 +- docs/src/index.md | 2 +- examples/heisenberg.jl | 2 +- src/operators/models.jl | 17 +++++++++-------- test/ctmrg/gradients.jl | 2 +- test/heisenberg.jl | 2 +- test/j1j2_model.jl | 2 +- test/tf_ising.jl | 4 ++-- 8 files changed, 17 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 60ce588f..38d173eb 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ For example, in order to obtain the groundstate of the 2D Heisenberg model, we c using TensorKit, PEPSKit, KrylovKit, OptimKit # constructing the Hamiltonian: -H = heisenberg_XYZ(; Jx=-1, Jy=1, Jz=-1) # sublattice rotation to obtain single-site unit cell +H = heisenberg_XYZ(InfiniteSquare(); Jx=-1, Jy=1, Jz=-1) # sublattice rotation to obtain single-site unit cell # configuring the parameters D = 2 diff --git a/docs/src/index.md b/docs/src/index.md index cf5f0703..465b150f 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -21,7 +21,7 @@ For example, in order to obtain the groundstate of the 2D Heisenberg model, we c using TensorKit, PEPSKit, KrylovKit, OptimKit # constructing the Hamiltonian: -H = heisenberg_XYZ(; Jx=-1, Jy=1, Jz=-1) # sublattice rotation to obtain single-site unit cell +H = heisenberg_XYZ(InfiniteSquare(); Jx=-1, Jy=1, Jz=-1) # sublattice rotation to obtain single-site unit cell # configuring the parameters D = 2 diff --git a/examples/heisenberg.jl b/examples/heisenberg.jl index 473e9cab..25b30c02 100644 --- a/examples/heisenberg.jl +++ b/examples/heisenberg.jl @@ -6,7 +6,7 @@ using PEPSKit, KrylovKit # We use the parameters (J₁, J₂, J₃) = (-1, 1, -1) by default to capture # the ground state in a single-site unit cell. This can be seen from # sublattice rotating H from parameters (1, 1, 1) to (-1, 1, -1). -H = heisenberg_XYZ(; Jx=-1, Jy=1, Jz=-1) +H = heisenberg_XYZ(InfiniteSquare(); Jx=-1, Jy=1, Jz=-1) # Parameters χbond = 2 diff --git a/src/operators/models.jl b/src/operators/models.jl index 4dd1e3ac..324397ff 100644 --- a/src/operators/models.jl +++ b/src/operators/models.jl @@ -16,7 +16,7 @@ end function MPSKitModels.transverse_field_ising( T::Type{<:Number}, S::Union{Type{Trivial},Type{Z2Irrep}}, - lattice::InfiniteSquare=InfiniteSquare(1, 1); + lattice::InfiniteSquare; J=1.0, g=1.0, ) @@ -30,9 +30,7 @@ function MPSKitModels.transverse_field_ising( ) end -function MPSKitModels.heisenberg_XYZ( - lattice::InfiniteSquare=InfiniteSquare(1, 1); kwargs... -) +function MPSKitModels.heisenberg_XYZ(lattice::InfiniteSquare; kwargs...) return heisenberg_XYZ(ComplexF64, Trivial, lattice; kwargs...) end function MPSKitModels.heisenberg_XYZ( @@ -61,10 +59,13 @@ end Square lattice J₁-J₂ model. The `sublattice` kwarg enables a single site unit cell via a sublattice rotation. """ +function j1_j2(lattice::InfiniteSquare; kwargs...) + return j1_j2(ComplexF64, Trivial, lattice; kwargs...) +end function j1_j2( - T::Type{<:Number}=ComplexF64, - S::Type{<:Sector}=Trivial, - lattice::InfiniteSquare=InfiniteSquare(1, 1); + T::Type{<:Number}, + S::Type{<:Sector}, + lattice::InfiniteSquare; J1=1.0, J2=1.0, spin=1//2, @@ -89,7 +90,7 @@ end Square lattice p-wave superconductor model. """ -function pwave_superconductor(lattice::InfiniteSquare=InfiniteSquare(1, 1); kwargs...) +function pwave_superconductor(lattice::InfiniteSquare; kwargs...) return pwave_superconductor(ComplexF64, lattice; kwargs...) end function pwave_superconductor( diff --git a/test/ctmrg/gradients.jl b/test/ctmrg/gradients.jl index b095d7e2..a5864390 100644 --- a/test/ctmrg/gradients.jl +++ b/test/ctmrg/gradients.jl @@ -13,7 +13,7 @@ using KrylovKit Pspaces = [ComplexSpace(2), Vect[FermionParity](0 => 1, 1 => 1)] Vspaces = [ComplexSpace(χbond), Vect[FermionParity](0 => χbond / 2, 1 => χbond / 2)] Espaces = [ComplexSpace(χenv), Vect[FermionParity](0 => χenv / 2, 1 => χenv / 2)] -models = [heisenberg_XYZ(), pwave_superconductor()] +models = [heisenberg_XYZ(InfiniteSquare()), pwave_superconductor(InfiniteSquare())] names = ["Heisenberg", "p-wave superconductor"] gradtol = 1e-4 diff --git a/test/heisenberg.jl b/test/heisenberg.jl index 0290cd53..4fa67362 100644 --- a/test/heisenberg.jl +++ b/test/heisenberg.jl @@ -25,7 +25,7 @@ opt_alg = PEPSOptimize(; # initialize states Random.seed!(91283219347) -H = heisenberg_XYZ() +H = heisenberg_XYZ(InfiniteSquare()) psi_init = InfinitePEPS(2, χbond) env_init = leading_boundary(CTMRGEnv(psi_init, ComplexSpace(χenv)), psi_init, ctm_alg) diff --git a/test/j1j2_model.jl b/test/j1j2_model.jl index 8c391254..2fcb4a58 100644 --- a/test/j1j2_model.jl +++ b/test/j1j2_model.jl @@ -25,7 +25,7 @@ opt_alg = PEPSOptimize(; # initialize states Random.seed!(91283219347) -H = j1_j2(; J2=0.25) +H = j1_j2(InfiniteSquare(); J2=0.25) psi_init = InfinitePEPS(2, χbond) psi_init = symmetrize!(psi_init, RotateReflect()) env_init = leading_boundary(CTMRGEnv(psi_init, ComplexSpace(χenv)), psi_init, ctm_alg); diff --git a/test/tf_ising.jl b/test/tf_ising.jl index 53a87913..f0c725eb 100644 --- a/test/tf_ising.jl +++ b/test/tf_ising.jl @@ -34,7 +34,7 @@ opt_alg = PEPSOptimize(; ) # initialize states -H = transverse_field_ising(; g) +H = transverse_field_ising(InfiniteSquare(); g) Random.seed!(91283219347) psi_init = InfinitePEPS(2, χbond) env_init = leading_boundary(CTMRGEnv(psi_init, ComplexSpace(χenv)), psi_init, ctm_alg) @@ -53,7 +53,7 @@ magn = expectation_value(result.peps, M, result.env) @test abs(magn) ≈ mˣ atol = 5e-2 # find fixedpoint in polarized phase and compute correlations lengths -H_polar = transverse_field_ising(; g=4.5) +H_polar = transverse_field_ising(InfiniteSquare(); g=4.5) result_polar = fixedpoint(psi_init, H_polar, opt_alg, env_init) ξ_h_polar, ξ_v_polar, = correlation_length(result_polar.peps, result_polar.env) @test ξ_h_polar < ξ_h From 3a585fd7e4bdca96410b82d720e064f7c3ffb7dd Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Mon, 30 Sep 2024 13:36:47 +0200 Subject: [PATCH 126/213] Make product coefficient indexing more explicit --- src/algorithms/toolbox.jl | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/algorithms/toolbox.jl b/src/algorithms/toolbox.jl index 93660aef..49a20453 100644 --- a/src/algorithms/toolbox.jl +++ b/src/algorithms/toolbox.jl @@ -120,8 +120,7 @@ function product_peps(peps_args...; unitcell=(1, 1), noise_amp=1e-2, state_vecto end prod_tensors = map(zip(noise_peps.A, state_vector)) do (t, v) pt = zero(t) - ptdata = block(pt, Trivial()) - ptdata[:, 1, 1, 1, 1] .= v + pt[][:, 1, 1, 1, 1] .= v return pt end prod_peps = InfinitePEPS(prod_tensors) From c3e9a2976b31554ab97fb5c95516bfae02e7805b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 1 Oct 2024 07:24:37 +0100 Subject: [PATCH 127/213] CompatHelper: add new compat entry for MPSKitModels at version 0.3, (keep existing compat) (#71) Co-authored-by: CompatHelper Julia --- Project.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Project.toml b/Project.toml index 9ed7faf6..aa1fc857 100644 --- a/Project.toml +++ b/Project.toml @@ -29,6 +29,7 @@ KrylovKit = "0.8" LinearAlgebra = "1" LoggingExtras = "1" MPSKit = "0.11" +MPSKitModels = "0.3" OptimKit = "0.3" Printf = "1" Random = "1" From ab1c672cb187a18f6827dd7dfa87ce997885b29c Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Wed, 2 Oct 2024 15:03:52 +0200 Subject: [PATCH 128/213] Change x0 for IterSVD to fix element-wise convergence with U,S,V from IterSVDs --- src/utility/svd.jl | 6 +++++- test/ctmrg/fixed_iterscheme.jl | 13 ++++++++----- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/utility/svd.jl b/src/utility/svd.jl index 8f0abb73..eea8d04c 100644 --- a/src/utility/svd.jl +++ b/src/utility/svd.jl @@ -105,7 +105,11 @@ function TensorKit._compute_svddata!( Udata[c] = U[:, 1:howmany] Vdata[c] = V[1:howmany, :] else - x₀ = randn(eltype(b), size(b, 1)) + # x₀ = randn(eltype(b), size(b, 1)) # Leads to erroneous gauge fixing of U, S, V and thus failing element-wise conv. + # u, = TensorKit.MatrixAlgebra.svd!(deepcopy(b), TensorKit.SVD()) + # x₀ = sum(u[:, i] for i in 1:howmany) # Element-wise convergence works fine + # x₀ = dropdims(sum(b[:, 1:3]; dims=2); dims=2) # Summing too many columns also makes gauge fixing fail + x₀ = b[:, 1] # Leads so slower convergence of SVD than randn, but correct element-wise convergence S, lvecs, rvecs, info = KrylovKit.svdsolve(b, x₀, howmany, :LR, alg.alg) if info.converged < howmany # Fall back to dense SVD if not properly converged @warn "Iterative SVD did not converge for block $c, falling back to dense SVD" diff --git a/test/ctmrg/fixed_iterscheme.jl b/test/ctmrg/fixed_iterscheme.jl index 1cfc9f83..4d9c0218 100644 --- a/test/ctmrg/fixed_iterscheme.jl +++ b/test/ctmrg/fixed_iterscheme.jl @@ -1,6 +1,6 @@ using Test using Random -using TensorKit +using TensorKit, KrylovKit using PEPSKit using PEPSKit: FixedSVD, @@ -46,13 +46,13 @@ unitcells = [(1, 1), (3, 4)] @test calc_elementwise_convergence(env_conv1, env_fixedsvd) ≈ 0 atol = 1e-6 end -# TODO: Why doesn't FixedSVD work with previous U, S and V from IterSVD? @testset "Element-wise consistency of TensorKit.SVD and IterSVD" begin ctm_alg_iter = CTMRG(; tol=1e-12, + maxiter=200, verbosity=2, ctmrgscheme=:simultaneous, - svd_alg=SVDAdjoint(; fwd_alg=IterSVD()), + svd_alg=SVDAdjoint(; fwd_alg=IterSVD(; alg=GKL(; tol=1e-14, krylovdim=χenv + 10))), ) ctm_alg_full = CTMRG(; tol=1e-12, @@ -65,7 +65,8 @@ end Random.seed!(91283219347) psi = InfinitePEPS(2, χbond) env_init = CTMRGEnv(psi, ComplexSpace(χenv)) - env_conv1 = leading_boundary(env_init, psi, ctm_alg_full) + env_conv1 = leading_boundary(env_init, psi, ctm_alg_iter) + # env_conv1 = leading_boundary(env_init, psi, ctm_alg_full); # do extra iteration to get SVD env_conv2_iter, info_iter = ctmrg_iter(psi, env_conv1, ctm_alg_iter) @@ -92,11 +93,13 @@ end # do iteration with FixedSVD env_fixedsvd_iter, = ctmrg_iter(psi, env_conv1, ctm_alg_fix_iter) env_fixedsvd_iter = fix_global_phases(env_conv1, env_fixedsvd_iter) - # @test calc_elementwise_convergence(env_conv1, env_fixedsvd_iter) ≈ 0 atol = 1e-6 # This should work, but doesn't! + @test calc_elementwise_convergence(env_conv1, env_fixedsvd_iter) ≈ 0 atol = 1e-6 # This doesn't work for x₀ = rand(size(b, 1))? + # @show calc_elementwise_convergence(env_conv1, env_fixedsvd_iter) env_fixedsvd_full, = ctmrg_iter(psi, env_conv1, ctm_alg_fix_full) env_fixedsvd_full = fix_global_phases(env_conv1, env_fixedsvd_full) @test calc_elementwise_convergence(env_conv1, env_fixedsvd_full) ≈ 0 atol = 1e-6 + # @show calc_elementwise_convergence(env_conv1, env_fixedsvd_full) # check matching decompositions atol = 1e-12 From 922c0f07b66eca6060e30cabf27fd8374b0216b5 Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Wed, 2 Oct 2024 15:24:38 +0200 Subject: [PATCH 129/213] Remove setting restriction from PEPSOptimize --- src/algorithms/peps_opt.jl | 3 --- src/utility/svd.jl | 1 + 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/algorithms/peps_opt.jl b/src/algorithms/peps_opt.jl index 3b569e6b..b84467da 100644 --- a/src/algorithms/peps_opt.jl +++ b/src/algorithms/peps_opt.jl @@ -103,9 +103,6 @@ struct PEPSOptimize{G} if gradient_alg isa GradMode if S === :sequential && iterscheme(gradient_alg) === :fixed throw(ArgumentError(":sequential and :fixed are not compatible")) - elseif boundary_alg.projector_alg.svd_alg.fwd_alg isa IterSVD && - iterscheme(gradient_alg) === :fixed - throw(ArgumentError("IterSVD and :fixed are currently not compatible")) end end return new{G}(boundary_alg, optimizer, reuse_env, gradient_alg) diff --git a/src/utility/svd.jl b/src/utility/svd.jl index eea8d04c..94849daa 100644 --- a/src/utility/svd.jl +++ b/src/utility/svd.jl @@ -105,6 +105,7 @@ function TensorKit._compute_svddata!( Udata[c] = U[:, 1:howmany] Vdata[c] = V[1:howmany, :] else + # TODO: find better initial guess that leads to element-wise convergence # x₀ = randn(eltype(b), size(b, 1)) # Leads to erroneous gauge fixing of U, S, V and thus failing element-wise conv. # u, = TensorKit.MatrixAlgebra.svd!(deepcopy(b), TensorKit.SVD()) # x₀ = sum(u[:, i] for i in 1:howmany) # Element-wise convergence works fine From 826563ca6157dc7cdf18f70052ab07a930f2d681 Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Wed, 2 Oct 2024 17:37:56 +0200 Subject: [PATCH 130/213] Implement parts of function handle CTMRG --- src/PEPSKit.jl | 1 + .../contractions/ctmrg_contractions.jl | 6 ++ src/algorithms/ctmrg/ctmrg.jl | 14 ++-- src/algorithms/ctmrg/sparse_environments.jl | 79 +++++++++++++++++++ test/ctmrg/fixed_iterscheme.jl | 2 +- 5 files changed, 96 insertions(+), 6 deletions(-) create mode 100644 src/algorithms/ctmrg/sparse_environments.jl diff --git a/src/PEPSKit.jl b/src/PEPSKit.jl index 5a4fdb0a..72bb6da6 100644 --- a/src/PEPSKit.jl +++ b/src/PEPSKit.jl @@ -37,6 +37,7 @@ include("algorithms/contractions/localoperator.jl") include("algorithms/contractions/ctmrg_contractions.jl") include("algorithms/ctmrg/ctmrg.jl") +include("algorithms/ctmrg/sparse_environments.jl") include("algorithms/ctmrg/gaugefix.jl") include("algorithms/toolbox.jl") diff --git a/src/algorithms/contractions/ctmrg_contractions.jl b/src/algorithms/contractions/ctmrg_contractions.jl index 69e4c9e4..a7ac85ca 100644 --- a/src/algorithms/contractions/ctmrg_contractions.jl +++ b/src/algorithms/contractions/ctmrg_contractions.jl @@ -165,6 +165,7 @@ end """ halfinfinite_environment(quadrant1::AbstractTensorMap{S,3,3}, quadrant2::AbstractTensorMap{S,3,3}) + halfinfinite_environment(quadrant1::EnlargedCorner{A,C,E}, quadrant2::EnlargedCorner{A,C,E}) Contract two quadrants (enlarged corners) to form a half-infinite environment. @@ -182,6 +183,11 @@ function halfinfinite_environment( quadrant1[χ_in D_inabove D_inbelow; χ D1 D2] * quadrant2[χ D1 D2; χ_out D_outabove D_outbelow] end +function halfinfinite_environment( + quadrant1::EnlargedCorner{A,C,E}, quadrant2::EnlargedCorner{A,C,E} +) where {A,C,E} + return HalfInfiniteEnv(quadrant1, quadrant2)() +end # Renormalization contractions # ---------------------------- diff --git a/src/algorithms/ctmrg/ctmrg.jl b/src/algorithms/ctmrg/ctmrg.jl index bba37f25..3ebb0ac1 100644 --- a/src/algorithms/ctmrg/ctmrg.jl +++ b/src/algorithms/ctmrg/ctmrg.jl @@ -195,7 +195,7 @@ function ctmrg_expand(state, envs::CTMRGEnv{C,T}, ::SequentialCTMRG) where {C,T} Q_nw[r, c] = enlarge_northwest_corner((r, c), envs, state) end - return copy(Q_sw), copy(Q_nw) + return stack((copy(Q_sw), copy(Q_nw)); dims=1) end function ctmrg_expand(state, envs::CTMRGEnv{C,T}, ::SimultaneousCTMRG) where {C,T} Qtype = tensormaptype(spacetype(C), 3, 3, storagetype(C)) @@ -238,7 +238,7 @@ function ctmrg_projectors( for (r, c) in directions # SVD half-infinite environment r′ = _prev(r, size(envs.corners, 2)) - QQ = halfinfinite_environment(enlarged_envs[1][r, c], enlarged_envs[2][r′, c]) + QQ = halfinfinite_environment(enlarged_envs[1, r, c], enlarged_envs[2, r′, c]) trscheme = truncation_scheme(projector_alg, envs.edges[WEST, r′, c]) svd_alg = svd_algorithm(projector_alg, (WEST, r, c)) @@ -255,7 +255,7 @@ function ctmrg_projectors( # Compute projectors P_bottom[r, c], P_top[r, c] = build_projectors( - U, S, V, enlarged_envs[1][r, c], enlarged_envs[2][r′, c] + U, S, V, enlarged_envs[1, r, c], enlarged_envs[2, r′, c] ) end @@ -391,9 +391,13 @@ end # Auxiliary routines # ======================================================================================== # -# Build projectors from SVD and enlarged SW & NW corners +# Build projectors from SVD and dense enlarged corners function build_projectors( - U::AbstractTensorMap{E,3,1}, S, V::AbstractTensorMap{E,1,3}, Q, Q_next + U::AbstractTensorMap{E,3,1}, + S::AbstractTensorMap{E,1,1}, + V::AbstractTensorMap{E,1,3}, + Q::AbstractTensorMap{E,3,3}, + Q_next::AbstractTensorMap{E,3,3}, ) where {E<:ElementarySpace} isqS = sdiag_inv_sqrt(S) P_left = Q_next * V' * isqS diff --git a/src/algorithms/ctmrg/sparse_environments.jl b/src/algorithms/ctmrg/sparse_environments.jl new file mode 100644 index 00000000..9f5b0c26 --- /dev/null +++ b/src/algorithms/ctmrg/sparse_environments.jl @@ -0,0 +1,79 @@ +""" + struct HalfInfiniteEnv{A,C,E} + +Half-infinite CTMRG environment tensor storage. + +``` + C_2 -- E_2 -- E_3 -- C_3 + | || || | + E_1 == ket_bra_1 == ket_bra_2 == E_4 + | || || | +``` +""" +struct HalfInfiniteEnv{A,A′,C,E} + ket_1::A + bra_1::A′ + ket_2::A + bra_2::A′ + C_1::C + C_2::C + E_1::E + E_2::E + E_3::E + E_4::E +end + +# Construct environment from two enlarged corners +function HalfInfiniteEnv(quadrant1::EnlargedCorner, quadrant2::EnlargedCorner) + return HalfInfiniteEnv( + quadrant1.ket_bra, + quadrant2.ket_bra, + quadrant1.C, + quadrant2.C, + quadrant1.E_1, + quadrant1.E_2, + quadrant2.E_1, + quadrant2.E_2, + ) +end + +# Contract half-infinite environment +function (env::HalfInfiniteEnv)() end + +""" + struct EnlargedCorner{A,C,E} + +Enlarged CTMRG corner tensor storage. + +``` + C -- E_2 -- + | || + E_1 == ket-bra == + | || +``` +""" +struct EnlargedCorner{A,A′,Ct,E} + ket::A + bra::A′ + C::Ct + E_1::E + E_2::E +end + +# Contract enlarged corner +function (Q::EnlargedCorner)() + return enlarge_northwest_corner(Q.E_1, Q.C, Q.E_2, Q.ket, Q.bra) +end + +# Compute left & right projectors from enlarged corner struct +function build_projectors( + U::AbstractTensorMap{E,3,1}, + S::AbstractTensorMap{E,1,1}, + V::AbstractTensorMap{E,1,3}, + Q::EnlargedCorner, + Q_next::EnlargedCorner, +) where {E<:ElementarySpace} + isqS = sdiag_inv_sqrt(S) + # TODO + return P_left, P_right +end \ No newline at end of file diff --git a/test/ctmrg/fixed_iterscheme.jl b/test/ctmrg/fixed_iterscheme.jl index 4d9c0218..871e8711 100644 --- a/test/ctmrg/fixed_iterscheme.jl +++ b/test/ctmrg/fixed_iterscheme.jl @@ -13,7 +13,7 @@ using PEPSKit: # initialize parameters χbond = 2 χenv = 16 -svd_algs = [SVDAdjoint(; fwd_alg=TensorKit.SVD())] #, SVDAdjoint(; fwd_alg=IterSVD())] +svd_algs = [SVDAdjoint(; fwd_alg=TensorKit.SVD()), SVDAdjoint(; fwd_alg=IterSVD())] unitcells = [(1, 1), (3, 4)] # test for element-wise convergence after application of fixed step From 0afafe6597fb7c1d8db1783e1635d4008b3515c5 Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Thu, 3 Oct 2024 11:12:12 +0200 Subject: [PATCH 131/213] Add left and right projector contractions --- .../contractions/ctmrg_contractions.jl | 47 +++++++++++++++++++ src/algorithms/ctmrg/sparse_environments.jl | 9 ++-- 2 files changed, 53 insertions(+), 3 deletions(-) diff --git a/src/algorithms/contractions/ctmrg_contractions.jl b/src/algorithms/contractions/ctmrg_contractions.jl index a7ac85ca..636a3b85 100644 --- a/src/algorithms/contractions/ctmrg_contractions.jl +++ b/src/algorithms/contractions/ctmrg_contractions.jl @@ -163,6 +163,53 @@ end # Projector contractions # ---------------------- +""" + left_projector(E_1, C, E_2, V, isqS, ket::PEPSTensor, bra::PEPSTensor=ket) + +Contract the CTMRG left projector with the higher-dimensional subspace facing to the left. + +``` + C -- E_2 -- |~~| + | || |V'| -- isqS -- + E_1 == ket-bra == |~~| + | || +``` +""" +function left_projector(E_1, C, E_2, V, isqS, ket::PEPSTensor, bra::PEPSTensor=ket) + return @autoopt @tensor P_left[χ_in D_inabove D_inbelow; χ_out] := + E_1[χ_in D1 D2; χ1] * + C[χ1; χ2] * + E_2[χ2 D3 D4; χ3] * + ket[d; D3 D5 D_inabove D1] * + conj(bra[d; D4 D6 D_inbelow D2]) * + conj(V[χ4; χ3 D5 D6]) * + isqS[χ4; χ_out] +end + +""" + right_projector(E_1, C, E_2, U, isqS, ket::PEPSTensor, bra::PEPSTensor=ket) + +Contract the CTMRG right projector with the higher-dimensional subspace facing to the right. + +``` + |~~| -- E_2 -- C + -- isqS -- |U'| || | + |~~| == ket-bra == E_1 + || | +``` +""" +function right_projector(E_1, C, E_2, U, isqS, ket::PEPSTensor, bra::PEPSTensor=ket) + return @autoopt @tensor P_right[χ_in; χ_out D_outabove D_outbelow] := + isqS[χ_in; χ1] * + conj(U[χ1; χ2 D1 D2]) * + ket[d; D1 D5 D_outabove D1] * + conj(bra[d; D2 D6 D_outbelow D2]) * + E_2[χ2 D3 D4; χ3] * + C[χ3; χ4] * + E_1[χ4 D5 D6; χ_out] +end + + """ halfinfinite_environment(quadrant1::AbstractTensorMap{S,3,3}, quadrant2::AbstractTensorMap{S,3,3}) halfinfinite_environment(quadrant1::EnlargedCorner{A,C,E}, quadrant2::EnlargedCorner{A,C,E}) diff --git a/src/algorithms/ctmrg/sparse_environments.jl b/src/algorithms/ctmrg/sparse_environments.jl index 9f5b0c26..ec086e87 100644 --- a/src/algorithms/ctmrg/sparse_environments.jl +++ b/src/algorithms/ctmrg/sparse_environments.jl @@ -60,7 +60,7 @@ struct EnlargedCorner{A,A′,Ct,E} E_2::E end -# Contract enlarged corner +# Contract enlarged corner (use NW corner as convention for connecting environment to PEPS tensor) function (Q::EnlargedCorner)() return enlarge_northwest_corner(Q.E_1, Q.C, Q.E_2, Q.ket, Q.bra) end @@ -74,6 +74,9 @@ function build_projectors( Q_next::EnlargedCorner, ) where {E<:ElementarySpace} isqS = sdiag_inv_sqrt(S) - # TODO + P_left = left_projector(Q.E_1, Q.C, Q.E_2, V, isqS, Q.ket, Q.bra) + P_right = right_projector( + Q_next.E_1, Q_next.C, Q_next.E_2, U, isqS, Q_next.ket, Q_next.bra + ) return P_left, P_right -end \ No newline at end of file +end From 4fa2cd35a0db7ed28c77790badbd135c591800ab Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Thu, 3 Oct 2024 11:56:34 +0200 Subject: [PATCH 132/213] Add half-infinite environment contractions --- .../contractions/ctmrg_contractions.jl | 65 ++++++++++++++++--- src/algorithms/ctmrg/sparse_environments.jl | 44 ++++++++++--- 2 files changed, 90 insertions(+), 19 deletions(-) diff --git a/src/algorithms/contractions/ctmrg_contractions.jl b/src/algorithms/contractions/ctmrg_contractions.jl index 636a3b85..1e533c78 100644 --- a/src/algorithms/contractions/ctmrg_contractions.jl +++ b/src/algorithms/contractions/ctmrg_contractions.jl @@ -177,9 +177,9 @@ Contract the CTMRG left projector with the higher-dimensional subspace facing to """ function left_projector(E_1, C, E_2, V, isqS, ket::PEPSTensor, bra::PEPSTensor=ket) return @autoopt @tensor P_left[χ_in D_inabove D_inbelow; χ_out] := - E_1[χ_in D1 D2; χ1] * - C[χ1; χ2] * - E_2[χ2 D3 D4; χ3] * + E_1[χ_in D1 D2; χ1] * + C[χ1; χ2] * + E_2[χ2 D3 D4; χ3] * ket[d; D3 D5 D_inabove D1] * conj(bra[d; D4 D6 D_inbelow D2]) * conj(V[χ4; χ3 D5 D6]) * @@ -204,15 +204,14 @@ function right_projector(E_1, C, E_2, U, isqS, ket::PEPSTensor, bra::PEPSTensor= conj(U[χ1; χ2 D1 D2]) * ket[d; D1 D5 D_outabove D1] * conj(bra[d; D2 D6 D_outbelow D2]) * - E_2[χ2 D3 D4; χ3] * - C[χ3; χ4] * + E_2[χ2 D3 D4; χ3] * + C[χ3; χ4] * E_1[χ4 D5 D6; χ_out] end - """ halfinfinite_environment(quadrant1::AbstractTensorMap{S,3,3}, quadrant2::AbstractTensorMap{S,3,3}) - halfinfinite_environment(quadrant1::EnlargedCorner{A,C,E}, quadrant2::EnlargedCorner{A,C,E}) + Contract two quadrants (enlarged corners) to form a half-infinite environment. @@ -222,6 +221,26 @@ Contract two quadrants (enlarged corners) to form a half-infinite environment. |~~~~~~~~~| == |~~~~~~~~~| | || || | ``` + +The environment can also be contracted directly from all its constituent tensors. + +``` + C_1 -- E_2 -- E_3 -- C_2 + | || || | + E_1 == ket_bra_1 == ket_bra_2 == E_4 + | || || | +``` + +Alternatively, contract environment with a vector `x` acting on it, e.g. as needed for iterative solvers. + +``` + C_1 -- E_2 -- E_3 -- C_2 + | || || | + E_1 == ket_bra_1 == ket_bra_2 == E_4 + | || || | + [~~~~~~x~~~~~~] + || | +``` """ function halfinfinite_environment( quadrant1::AbstractTensorMap{S,3,3}, quadrant2::AbstractTensorMap{S,3,3} @@ -231,9 +250,35 @@ function halfinfinite_environment( quadrant2[χ D1 D2; χ_out D_outabove D_outbelow] end function halfinfinite_environment( - quadrant1::EnlargedCorner{A,C,E}, quadrant2::EnlargedCorner{A,C,E} -) where {A,C,E} - return HalfInfiniteEnv(quadrant1, quadrant2)() + E_1, C_1, E_2, E_3, C_2, E_4, ket_1::P, ket_2::P, bra_1::P=ket_1, bra_2::P=ket_2 +) where {P<:PEPSTensor} + return @autoopt @tensor half[χ_in D_inabove D_inbelow; χ_out D_outabove D_outbelow] := + E_1[χ_in D1 D2; χ1] * + C_1[χ1; χ2] * + E_2[χ2 D3 D4; χ3] * + ket_1[d1; D3 D9 D_inabove D1] * + conj(bra_1[d1; D4 D10 D_inbelow D2]) * + ket_2[d2; D5 D7 D_outabove D9] * + conj(bra_2[d2; D6 D8 D_outbelow D10]) * + E_3[χ3 D5 D6; χ4] * + C_2[χ4; χ5] * + E_4[χ5 D7 D8; χ_out] +end +function halfinfinite_environment( + E_1, C_1, E_2, E_3, C_2, E_4, x, ket_1::P, ket_2::P, bra_1::P=ket_1, bra_2::P=ket_2 +) where {P<:PEPSTensor} + return @autoopt @tensor half[χ_in D_inabove D_inbelow; χ_out D_outabove D_outbelow] := + E_1[χ_in D1 D2; χ1] * + C_1[χ1; χ2] * + E_2[χ2 D3 D4; χ3] * + ket_1[d1; D3 D9 D_inabove D1] * + conj(bra_1[d1; D4 D10 D_inbelow D2]) * + ket_2[d2; D5 D7 D11 D9] * + conj(bra_2[d2; D6 D8 D12 D10]) * + E_3[χ3 D5 D6; χ4] * + C_2[χ4; χ5] * + E_4[χ5 D7 D8; χ6] * + x[χ6 D11 D12; χ_out D_outabove D_outbelow] end # Renormalization contractions diff --git a/src/algorithms/ctmrg/sparse_environments.jl b/src/algorithms/ctmrg/sparse_environments.jl index ec086e87..0db09838 100644 --- a/src/algorithms/ctmrg/sparse_environments.jl +++ b/src/algorithms/ctmrg/sparse_environments.jl @@ -2,13 +2,6 @@ struct HalfInfiniteEnv{A,C,E} Half-infinite CTMRG environment tensor storage. - -``` - C_2 -- E_2 -- E_3 -- C_3 - | || || | - E_1 == ket_bra_1 == ket_bra_2 == E_4 - | || || | -``` """ struct HalfInfiniteEnv{A,A′,C,E} ket_1::A @@ -37,8 +30,41 @@ function HalfInfiniteEnv(quadrant1::EnlargedCorner, quadrant2::EnlargedCorner) ) end -# Contract half-infinite environment -function (env::HalfInfiniteEnv)() end +""" + (env::HalfInfiniteEnv)() + (env::HalfInfiniteEnv)(x) + +Contract half-infinite environment without or with a vector `x`. +""" +function (env::HalfInfiniteEnv)() + return halfinfinite_environment( + env.E_1, + env.C_1, + env.E_2, + env.E_3, + env.C_2, + env.E_4, + env.ket_1, + env.ket_2, + env.bra_1, + env.bra_2, + ) +end +function (env::HalfInfiniteEnv)(x) + return halfinfinite_environment( + env.E_1, + env.C_1, + env.E_2, + env.E_3, + env.C_2, + env.E_4, + x, + env.ket_1, + env.ket_2, + env.bra_1, + env.bra_2, + ) +end """ struct EnlargedCorner{A,C,E} From 58b92421adbf0995130ca8947716389707092be9 Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Thu, 3 Oct 2024 12:06:34 +0200 Subject: [PATCH 133/213] Add EnlargedCorner directional contraction methods, fix docstrings --- src/algorithms/ctmrg/sparse_environments.jl | 25 ++++++++++----------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/src/algorithms/ctmrg/sparse_environments.jl b/src/algorithms/ctmrg/sparse_environments.jl index 0db09838..3840a24e 100644 --- a/src/algorithms/ctmrg/sparse_environments.jl +++ b/src/algorithms/ctmrg/sparse_environments.jl @@ -1,5 +1,5 @@ """ - struct HalfInfiniteEnv{A,C,E} + struct HalfInfiniteEnv{A,A′,C,E} Half-infinite CTMRG environment tensor storage. """ @@ -67,16 +67,9 @@ function (env::HalfInfiniteEnv)(x) end """ - struct EnlargedCorner{A,C,E} + struct EnlargedCorner{A,A′,Ct,E} Enlarged CTMRG corner tensor storage. - -``` - C -- E_2 -- - | || - E_1 == ket-bra == - | || -``` """ struct EnlargedCorner{A,A′,Ct,E} ket::A @@ -86,10 +79,16 @@ struct EnlargedCorner{A,A′,Ct,E} E_2::E end -# Contract enlarged corner (use NW corner as convention for connecting environment to PEPS tensor) -function (Q::EnlargedCorner)() - return enlarge_northwest_corner(Q.E_1, Q.C, Q.E_2, Q.ket, Q.bra) -end +""" + (Q::EnlargedCorner)(::Val{<:Int}) + +Contract enlarged corner where `Val(1)` dispatches the north-west, `Val(2)` the north-east +`Val(3)` the south-east and `Val(4)` the south-west contraction. +""" +(Q::EnlargedCorner)(::Val{1}) = enlarge_northwest_corner(Q.E_1, Q.C, Q.E_2, Q.ket, Q.bra) +(Q::EnlargedCorner)(::Val{2}) = enlarge_northeast_corner(Q.E_1, Q.C, Q.E_2, Q.ket, Q.bra) +(Q::EnlargedCorner)(::Val{3}) = enlarge_southeast_corner(Q.E_1, Q.C, Q.E_2, Q.ket, Q.bra) +(Q::EnlargedCorner)(::Val{4}) = enlarge_southwest_corner(Q.E_1, Q.C, Q.E_2, Q.ket, Q.bra) # Compute left & right projectors from enlarged corner struct function build_projectors( From 1d91891a75a8fce7a2d8f2c07589ea1a4008e68b Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Thu, 3 Oct 2024 18:54:05 +0200 Subject: [PATCH 134/213] Further implement function handle details --- .../contractions/ctmrg_contractions.jl | 9 +- src/algorithms/ctmrg/ctmrg.jl | 4 + src/algorithms/ctmrg/sparse_environments.jl | 137 ++++++++++++------ src/utility/svd.jl | 66 ++++++--- test/ctmrg/svd_wrapper.jl | 31 ++++ 5 files changed, 177 insertions(+), 70 deletions(-) diff --git a/src/algorithms/contractions/ctmrg_contractions.jl b/src/algorithms/contractions/ctmrg_contractions.jl index 1e533c78..942f292a 100644 --- a/src/algorithms/contractions/ctmrg_contractions.jl +++ b/src/algorithms/contractions/ctmrg_contractions.jl @@ -211,7 +211,10 @@ end """ halfinfinite_environment(quadrant1::AbstractTensorMap{S,3,3}, quadrant2::AbstractTensorMap{S,3,3}) - + function halfinfinite_environment(C_1, C_2, E_1, E_2, E_3, E_4, + ket_1::P, ket_2::P, bra_1::P=ket_1, bra_2::P=ket_2) where {P<:PEPSTensor} + function halfinfinite_environment(C_1, C_2, E_1, E_2, E_3, E_4, x, + ket_1::P, ket_2::P, bra_1::P=ket_1, bra_2::P=ket_2) where {P<:PEPSTensor} Contract two quadrants (enlarged corners) to form a half-infinite environment. @@ -250,7 +253,7 @@ function halfinfinite_environment( quadrant2[χ D1 D2; χ_out D_outabove D_outbelow] end function halfinfinite_environment( - E_1, C_1, E_2, E_3, C_2, E_4, ket_1::P, ket_2::P, bra_1::P=ket_1, bra_2::P=ket_2 + C_1, C_2, E_1, E_2, E_3, E_4, ket_1::P, ket_2::P, bra_1::P=ket_1, bra_2::P=ket_2 ) where {P<:PEPSTensor} return @autoopt @tensor half[χ_in D_inabove D_inbelow; χ_out D_outabove D_outbelow] := E_1[χ_in D1 D2; χ1] * @@ -265,7 +268,7 @@ function halfinfinite_environment( E_4[χ5 D7 D8; χ_out] end function halfinfinite_environment( - E_1, C_1, E_2, E_3, C_2, E_4, x, ket_1::P, ket_2::P, bra_1::P=ket_1, bra_2::P=ket_2 + C_1, C_2, E_1, E_2, E_3, E_4, x, ket_1::P, ket_2::P, bra_1::P=ket_1, bra_2::P=ket_2 ) where {P<:PEPSTensor} return @autoopt @tensor half[χ_in D_inabove D_inbelow; χ_out D_outabove D_outbelow] := E_1[χ_in D1 D2; χ1] * diff --git a/src/algorithms/ctmrg/ctmrg.jl b/src/algorithms/ctmrg/ctmrg.jl index 3ebb0ac1..5520673e 100644 --- a/src/algorithms/ctmrg/ctmrg.jl +++ b/src/algorithms/ctmrg/ctmrg.jl @@ -202,6 +202,7 @@ function ctmrg_expand(state, envs::CTMRGEnv{C,T}, ::SimultaneousCTMRG) where {C, Q = Zygote.Buffer(Array{Qtype,3}(undef, size(envs.corners))) drc_combinations = collect(Iterators.product(axes(envs.corners)...)) @fwdthreads for (dir, r, c) in drc_combinations + # TODO: generalize the corner computation to make it compatible with EnlargedCorners Q[dir, r, c] = if dir == NORTHWEST enlarge_northwest_corner((r, c), envs, state) elseif dir == NORTHEAST @@ -216,6 +217,9 @@ function ctmrg_expand(state, envs::CTMRGEnv{C,T}, ::SimultaneousCTMRG) where {C, return copy(Q) end +function enlarge_corner((dir, r, c), envs, state, alg::CTMRG) # TODO: find a way to dispatch on CTMRG struct to switch on function handles +end + # ======================================================================================== # # Projector step # ======================================================================================== # diff --git a/src/algorithms/ctmrg/sparse_environments.jl b/src/algorithms/ctmrg/sparse_environments.jl index 3840a24e..783ef429 100644 --- a/src/algorithms/ctmrg/sparse_environments.jl +++ b/src/algorithms/ctmrg/sparse_environments.jl @@ -1,32 +1,73 @@ + +""" + struct EnlargedCorner{Ct,E,A,A′} + +Enlarged CTMRG corner tensor storage. +""" +struct EnlargedCorner{Ct,E,A,A′} + C::Ct + E_1::E + E_2::E + ket::A + bra::A′ +end + """ - struct HalfInfiniteEnv{A,A′,C,E} + (Q::EnlargedCorner)(::Val{<:Int}) + +Contract enlarged corner where `Val(1)` dispatches the north-west, `Val(2)` the north-east +`Val(3)` the south-east and `Val(4)` the south-west contraction. +""" +(Q::EnlargedCorner)(::Val{1}) = enlarge_northwest_corner(Q.E_1, Q.C, Q.E_2, Q.ket, Q.bra) +(Q::EnlargedCorner)(::Val{2}) = enlarge_northeast_corner(Q.E_1, Q.C, Q.E_2, Q.ket, Q.bra) +(Q::EnlargedCorner)(::Val{3}) = enlarge_southeast_corner(Q.E_1, Q.C, Q.E_2, Q.ket, Q.bra) +(Q::EnlargedCorner)(::Val{4}) = enlarge_southwest_corner(Q.E_1, Q.C, Q.E_2, Q.ket, Q.bra) + +# Compute left & right projectors from enlarged corner struct +function build_projectors( + U::AbstractTensorMap{E,3,1}, + S::AbstractTensorMap{E,1,1}, + V::AbstractTensorMap{E,1,3}, + Q::EnlargedCorner, + Q_next::EnlargedCorner, +) where {E<:ElementarySpace} + isqS = sdiag_inv_sqrt(S) + P_left = left_projector(Q.E_1, Q.C, Q.E_2, V, isqS, Q.ket, Q.bra) + P_right = right_projector( + Q_next.E_1, Q_next.C, Q_next.E_2, U, isqS, Q_next.ket, Q_next.bra + ) + return P_left, P_right +end + +""" + struct HalfInfiniteEnv{C,E,A,A′} Half-infinite CTMRG environment tensor storage. """ -struct HalfInfiniteEnv{A,A′,C,E} - ket_1::A - bra_1::A′ - ket_2::A - bra_2::A′ +struct HalfInfiniteEnv{C,E,A,A′} C_1::C C_2::C E_1::E E_2::E E_3::E E_4::E + ket_1::A + bra_1::A′ + ket_2::A + bra_2::A′ end # Construct environment from two enlarged corners function HalfInfiniteEnv(quadrant1::EnlargedCorner, quadrant2::EnlargedCorner) return HalfInfiniteEnv( - quadrant1.ket_bra, - quadrant2.ket_bra, quadrant1.C, quadrant2.C, quadrant1.E_1, quadrant1.E_2, quadrant2.E_1, quadrant2.E_2, + quadrant1.ket_bra, + quadrant2.ket_bra, ) end @@ -38,11 +79,11 @@ Contract half-infinite environment without or with a vector `x`. """ function (env::HalfInfiniteEnv)() return halfinfinite_environment( - env.E_1, env.C_1, + env.C_2, + env.E_1, env.E_2, env.E_3, - env.C_2, env.E_4, env.ket_1, env.ket_2, @@ -52,11 +93,11 @@ function (env::HalfInfiniteEnv)() end function (env::HalfInfiniteEnv)(x) return halfinfinite_environment( - env.E_1, env.C_1, + env.C_2, + env.E_1, env.E_2, env.E_3, - env.C_2, env.E_4, x, env.ket_1, @@ -66,42 +107,50 @@ function (env::HalfInfiniteEnv)(x) ) end -""" - struct EnlargedCorner{A,A′,Ct,E} +# TensorKit methods to make struct compatible with sparse SVD +TensorKit.InnerProductStyle(::HalfInfiniteEnv) = EuclideanProduct() +TensorKit.sectortype(::HalfInfiniteEnv) = Trivial +TensorKit.storagetype(env::HalfInfiniteEnv) = storagetype(env.ket_1) +TensorKit.spacetype(env::HalfInfiniteEnv) = spacetype(env.ket_1) -Enlarged CTMRG corner tensor storage. -""" -struct EnlargedCorner{A,A′,Ct,E} - ket::A - bra::A′ - C::Ct - E_1::E - E_2::E +function TensorKit.blocks(env::HalfInfiniteEnv) + return TensorKit.SingletonDict(Trivial() => env) +end +function TensorKit.blocksectors(::HalfInfiniteEnv) + return TensorKit.OneOrNoneIterator{Trivial}(true, Trivial()) end -""" - (Q::EnlargedCorner)(::Val{<:Int}) +function TensorKit.MatrixAlgebra.svd!(env::HalfInfiniteEnv, args...) + return TensorKit.MatrixAlgebra.svd!(env(), args...) +end -Contract enlarged corner where `Val(1)` dispatches the north-west, `Val(2)` the north-east -`Val(3)` the south-east and `Val(4)` the south-west contraction. -""" -(Q::EnlargedCorner)(::Val{1}) = enlarge_northwest_corner(Q.E_1, Q.C, Q.E_2, Q.ket, Q.bra) -(Q::EnlargedCorner)(::Val{2}) = enlarge_northeast_corner(Q.E_1, Q.C, Q.E_2, Q.ket, Q.bra) -(Q::EnlargedCorner)(::Val{3}) = enlarge_southeast_corner(Q.E_1, Q.C, Q.E_2, Q.ket, Q.bra) -(Q::EnlargedCorner)(::Val{4}) = enlarge_southwest_corner(Q.E_1, Q.C, Q.E_2, Q.ket, Q.bra) +Base.eltype(env::HalfInfiniteEnv) = eltype(env.ket_1) +function Base.size(env::HalfInfiniteEnv) # Treat environment as matrix + χ_in = dim(space(env.E_1, 1)) + D_inabove = dim(space(env.ket_1, 2)) + D_inbelow = dim(space(env.bra_1, 2)) + χ_out = dim(space(env.E_4, 1)) + D_outabove = dim(space(env.ket_2, 2)) + D_outbelow = dim(space(env.bra_2, 2)) + return (χ_in * D_inabove * D_inbelow, χ_out * D_outabove * D_outbelow) +end +Base.size(env::HalfInfiniteEnv, i::Int) = size(env)[i] -# Compute left & right projectors from enlarged corner struct -function build_projectors( - U::AbstractTensorMap{E,3,1}, - S::AbstractTensorMap{E,1,1}, - V::AbstractTensorMap{E,1,3}, - Q::EnlargedCorner, - Q_next::EnlargedCorner, -) where {E<:ElementarySpace} - isqS = sdiag_inv_sqrt(S) - P_left = left_projector(Q.E_1, Q.C, Q.E_2, V, isqS, Q.ket, Q.bra) - P_right = right_projector( - Q_next.E_1, Q_next.C, Q_next.E_2, U, isqS, Q_next.ket, Q_next.bra +# TODO: implement VectorInterface +VectorInterface.scalartype(env::HalfInfiniteEnv) = scalartype(env.ket_1) + +# Wrapper around halfinfinite_environment contraction using EnlargedCorners (used in ctmrg_projectors) +function halfinfinite_environment(ec_1::EnlargedCorner, ec_2::EnlargedCorner) + return HalfInfiniteEnv( + ec_1.C, + ec_2.C, + ec_1.E_1, + ec_1.E_2, + ec_2.E_1, + ec_2.E_2, + ec_1.ket, + ec_2.ket, + ec_1.bra, + ec_2.bra, ) - return P_left, P_right end diff --git a/src/utility/svd.jl b/src/utility/svd.jl index 94849daa..54765e20 100644 --- a/src/utility/svd.jl +++ b/src/utility/svd.jl @@ -25,19 +25,20 @@ removes the divergences from the adjoint. end # Keep truncation algorithm separate to be able to specify CTMRG dependent information """ - PEPSKit.tsvd(t::AbstractTensorMap, alg; trunc=notrunc(), p=2) + PEPSKit.tsvd(t, alg; trunc=notrunc(), p=2) Wrapper around `TensorKit.tsvd` which dispatches on the `alg` argument. This is needed since a custom adjoint for `PEPSKit.tsvd` may be defined, depending on the algorithm. E.g., for `IterSVD` the adjoint for a truncated SVD from `KrylovKit.svdsolve` is used. """ -PEPSKit.tsvd(t::AbstractTensorMap, alg; kwargs...) = PEPSKit.tsvd!(copy(t), alg; kwargs...) -function PEPSKit.tsvd!( - t::AbstractTensorMap, alg::SVDAdjoint; trunc::TruncationScheme=notrunc(), p::Real=2 -) +PEPSKit.tsvd(t, alg; kwargs...) = PEPSKit.tsvd!(copy(t), alg; kwargs...) +function PEPSKit.tsvd!(t, alg::SVDAdjoint; trunc::TruncationScheme=notrunc(), p::Real=2) return TensorKit.tsvd!(t; alg=alg.fwd_alg, trunc, p) end +function TensorKit.tsvd!(f::HalfInfiniteEnv; trunc=NoTruncation(), p::Real=2, alg=IterSVD()) + return _tsvd!(f, alg, trunc, p) +end """ struct FixedSVD @@ -69,35 +70,45 @@ the iterative SVD didn't converge, the algorithm falls back to a dense SVD. @kwdef struct IterSVD alg::KrylovKit.GKL = KrylovKit.GKL(; tol=1e-14, krylovdim=25) fallback_threshold::Float64 = Inf + start_vector = random_start_vector +end + +# TODO: find better initial guess that leads to element-wise convergence and is compatible with function handles +function random_start_vector(f) + return randn(eltype(f), size(f, 1)) # Leads to erroneous gauge fixing of U, S, V and thus failing element-wise conv. + # u, = TensorKit.MatrixAlgebra.svd!(deepcopy(t), TensorKit.SVD()) + # return sum(u[:, i] for i in 1:howmany) # Element-wise convergence works fine + # return dropdims(sum(t[:, 1:3]; dims=2); dims=2) # Summing too many columns also makes gauge fixing fail + # return t[:, 1] # Leads so slower convergence of SVD than randn, but correct element-wise convergence end # Compute SVD data block-wise using KrylovKit algorithm function TensorKit._tsvd!( - t, alg::IterSVD, trunc::Union{NoTruncation,TruncationSpace}, p::Real=2 + f, alg::IterSVD, trunc::Union{NoTruncation,TruncationSpace}, p::Real=2 ) # early return - if isempty(blocksectors(t)) - truncerr = zero(real(scalartype(t))) - return _empty_svdtensors(t)..., truncerr + if isempty(blocksectors(f)) + truncerr = zero(real(scalartype(f))) + return _empty_svdtensors(f)..., truncerr end - Udata, Σdata, Vdata, dims = _compute_svddata!(t, alg, trunc) - U, S, V = _create_svdtensors(t, Udata, Σdata, Vdata, spacetype(t)(dims)) - truncerr = trunc isa NoTruncation ? abs(zero(scalartype(t))) : norm(U * S * V - t, p) + Udata, Σdata, Vdata, dims = _compute_svddata!(f, alg, trunc) + U, S, V = _create_svdtensors(f, Udata, Σdata, Vdata, spacetype(f)(dims)) + truncerr = trunc isa NoTruncation ? abs(zero(scalartype(f))) : norm(U * S * V - f, p) return U, S, V, truncerr end function TensorKit._compute_svddata!( - t::TensorMap, alg::IterSVD, trunc::Union{NoTruncation,TruncationSpace} + f, alg::IterSVD, trunc::Union{NoTruncation,TruncationSpace} ) - InnerProductStyle(t) === EuclideanProduct() || throw_invalid_innerproduct(:tsvd!) - I = sectortype(t) - A = storagetype(t) + InnerProductStyle(f) === EuclideanProduct() || throw_invalid_innerproduct(:tsvd!) + I = sectortype(f) + A = storagetype(f) Udata = SectorDict{I,A}() Vdata = SectorDict{I,A}() dims = SectorDict{I,Int}() local Sdata - for (c, b) in blocks(t) + for (c, b) in blocks(f) howmany = trunc isa NoTruncation ? minimum(size(b)) : blockdim(trunc.space, c) if howmany / minimum(size(b)) > alg.fallback_threshold # Use dense SVD for small blocks @@ -105,12 +116,7 @@ function TensorKit._compute_svddata!( Udata[c] = U[:, 1:howmany] Vdata[c] = V[1:howmany, :] else - # TODO: find better initial guess that leads to element-wise convergence - # x₀ = randn(eltype(b), size(b, 1)) # Leads to erroneous gauge fixing of U, S, V and thus failing element-wise conv. - # u, = TensorKit.MatrixAlgebra.svd!(deepcopy(b), TensorKit.SVD()) - # x₀ = sum(u[:, i] for i in 1:howmany) # Element-wise convergence works fine - # x₀ = dropdims(sum(b[:, 1:3]; dims=2); dims=2) # Summing too many columns also makes gauge fixing fail - x₀ = b[:, 1] # Leads so slower convergence of SVD than randn, but correct element-wise convergence + x₀ = alg.start_vector(b) S, lvecs, rvecs, info = KrylovKit.svdsolve(b, x₀, howmany, :LR, alg.alg) if info.converged < howmany # Fall back to dense SVD if not properly converged @warn "Iterative SVD did not converge for block $c, falling back to dense SVD" @@ -134,6 +140,7 @@ function TensorKit._compute_svddata!( return Udata, Sdata, Vdata, dims end +# Rrule with custom pullback to make KrylovKit rrule compatible with TensorMap symmetry blocks function ChainRulesCore.rrule( ::typeof(PEPSKit.tsvd!), t::AbstractTensorMap, @@ -194,6 +201,19 @@ function ChainRulesCore.rrule( return (U, S, V, ϵ), tsvd!_itersvd_pullback end +# Separate rule for SVD with function handle that uses KrylovKit.make_svdsolve_pullback +# but in turn cannot handle symmetric blocks +function ChainRulesCore.rrule( + ::typeof(PEPSKit.tsvd!), + f, + alg::SVDAdjoint{F,R,B}; + trunc::TruncationScheme=notrunc(), + p::Real=2, +) where {F<:Union{IterSVD,FixedSVD},R<:Union{GMRES,BiCGStab,Arnoldi},B} + return U, S, V, ϵ = PEPSKit.tsvd(t, alg; trunc, p) + # TODO: implement function handle adjoint wrapper with KrylovKit.make_svdsolve_pullback +end + """ struct NonTruncAdjoint diff --git a/test/ctmrg/svd_wrapper.jl b/test/ctmrg/svd_wrapper.jl index 47e1b9cf..927a1ed3 100644 --- a/test/ctmrg/svd_wrapper.jl +++ b/test/ctmrg/svd_wrapper.jl @@ -6,6 +6,7 @@ using KrylovKit using ChainRulesCore, Zygote using Accessors using PEPSKit +using PEPSKit: HalfInfiniteEnv # Gauge-invariant loss function function lossfun(A, alg, R=TensorMap(randn, space(A)), trunc=notrunc()) @@ -92,3 +93,33 @@ symm_R = TensorMap(randn, dtype, space(symm_r)) @test l_itersvd_fb ≈ l_fullsvd_tr @test g_fullsvd_tr[1] ≈ g_itersvd_fb[1] rtol = rtol end + +χbond = 2 +χenv = 6 +ctm_alg = CTMRG(; tol=1e-10, verbosity=2, svd_alg=SVDAdjoint()) +Random.seed!(91283219347) +H = heisenberg_XYZ(InfiniteSquare()) +psi = InfinitePEPS(2, χbond) +env = leading_boundary(CTMRGEnv(psi, ComplexSpace(χenv)), psi, ctm_alg) +hienv = HalfInfiniteEnv( + env.corners[1], + env.corners[2], + env.edges[4], + env.edges[1], + env.edges[1], + env.edges[2], + psi[1], + psi[1], + psi[1], + psi[1], +) +hienv_dense = hienv() +env_R = TensorMap(randn, space(hienv_dense)) +PEPSKit.tsvd!(hienv, iter_alg) + +@testset "IterSVD with HalfInfiniteEnv function handle" begin + l_fullsvd, g_fullsvd = withgradient(A -> lossfun(A, full_alg, env_R), hienv_dense) + l_itersvd, g_itersvd = withgradient(A -> lossfun(A, iter_alg, env_R), hienv) + @test l_itersvd ≈ l_fullsvd + @test g_fullsvd[1] ≈ g_itersvd[1] rtol = rtol +end From 0cb1ccfaf6355bbf445b25321ab569d904f25408 Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Fri, 4 Oct 2024 10:34:23 +0200 Subject: [PATCH 135/213] Add enlarge_corner function to enable sparse CTMRG dispatch, add renormalize_corner contraction --- .../contractions/ctmrg_contractions.jl | 26 +++++++- src/algorithms/ctmrg/ctmrg.jl | 61 ++++++++----------- src/algorithms/ctmrg/sparse_environments.jl | 46 ++++++++++++++ 3 files changed, 98 insertions(+), 35 deletions(-) diff --git a/src/algorithms/contractions/ctmrg_contractions.jl b/src/algorithms/contractions/ctmrg_contractions.jl index 942f292a..67317ed6 100644 --- a/src/algorithms/contractions/ctmrg_contractions.jl +++ b/src/algorithms/contractions/ctmrg_contractions.jl @@ -290,7 +290,8 @@ end # corners """ - renormalize_corner(quadrant, P_left, P_right) + renormalize_corner(quadrant::AbstractTensorMap{S,3,3}, P_left, P_right) + renormalize_corner(E_1, C, E_2, P_left, P_right, ket::PEPSTensor, bra::PEPSTensor=ket) Apply projectors to each side of a quadrant. @@ -302,11 +303,34 @@ Apply projectors to each side of a quadrant. [P_right] | ``` + +Alternatively, provide the constituent tensors and perform the complete contraction. + +``` + C -- E_2 -- |~~~~~~| + | | |P_left| -- + E_1 == ket-bra == |~~~~~~| + | || + [~~P_right~~] + | +``` """ function renormalize_corner(quadrant::AbstractTensorMap{S,3,3}, P_left, P_right) where {S} return @autoopt @tensor corner[χ_in; χ_out] := P_right[χ_in; χ1 D1 D2] * quadrant[χ1 D1 D2; χ2 D3 D4] * P_left[χ2 D3 D4; χ_out] end +function renormalize_corner( + E_1, C, E_2, P_left, P_right, ket::PEPSTensor, bra::PEPSTensor=ket +) + return @autoopt @tensor corner[χ_in; χ_out] := + P_right[χ_in; χ1 D1 D2] * + E_1[χ1 D3 D4; χ2] * + C[χ2; χ3] * + E_2[χ3 D5 D6; χ4] * + ket[d; D5 D7 D1 D3] * + conj(bra[d; D6 D8 D2 D4]) * + P_left[χ4 D7 D8; χ_out] +end """ renormalize_northwest_corner((row, col), enlarged_envs::CTMRGEnv, P_left, P_right) diff --git a/src/algorithms/ctmrg/ctmrg.jl b/src/algorithms/ctmrg/ctmrg.jl index 5520673e..af84e95f 100644 --- a/src/algorithms/ctmrg/ctmrg.jl +++ b/src/algorithms/ctmrg/ctmrg.jl @@ -175,6 +175,27 @@ ctmrg_logcancel!(log, iter, η, N) = @warnv 1 logcancel!(log, iter, η, N) # Expansion step # ======================================================================================== # +# TODO: find a way to dispatch on CTMRG struct to switch on function handles +""" + enlarge_corner(::Val{<:Int}, (r, c), envs, state, alg::CTMRG) + +Enlarge a CTMRG corner into the one of four directions `Val(1)` (NW), `Val(2)` (NE), +`Val(3)` (SE) or `Val(4)` (SW). This serves as a wrapper that can dispatch on the +directional index and different `CTMRG` algorithms. +""" +function enlarge_corner(::Val{1}, (r, c), envs, state, alg::CTMRG) + return enlarge_northwest_corner((r, c), envs, state) +end +function enlarge_corner(::Val{2}, (r, c), envs, state, alg::CTMRG) + return enlarge_northeast_corner((r, c), envs, state) +end +function enlarge_corner(::Val{3}, (r, c), envs, state, alg::CTMRG) + return enlarge_southeast_corner((r, c), envs, state) +end +function enlarge_corner(::Val{4}, (r, c), envs, state, alg::CTMRG) + return enlarge_southwest_corner((r, c), envs, state) +end + """ ctmrg_expand(state, envs, alg::CTMRG{M}) @@ -183,41 +204,13 @@ There are two modes of expansion: `M = :sequential` and `M = :simultaneous`. The first mode expands the environment in one direction at a time, for convenience towards the left. The second mode expands the environment in all four directions simultaneously. """ -function ctmrg_expand(state, envs::CTMRGEnv{C,T}, ::SequentialCTMRG) where {C,T} - Qtype = tensormaptype(spacetype(C), 3, 3, storagetype(C)) - Q_sw = Zygote.Buffer(envs.corners, Qtype, axes(state)...) - Q_nw = Zygote.Buffer(envs.corners, Qtype, axes(state)...) - - directions = collect(Iterators.product(axes(state)...)) - # @fwdthreads for (r, c) in directions - for (r, c) in directions - Q_sw[r, c] = enlarge_southwest_corner((r, c), envs, state) - Q_nw[r, c] = enlarge_northwest_corner((r, c), envs, state) - end - - return stack((copy(Q_sw), copy(Q_nw)); dims=1) +function ctmrg_expand(state, envs::CTMRGEnv, ::SequentialCTMRG) + drc_combinations = collect(Iterators.product([4, 1], axes(state)...)) + return map(idx -> enlarge_corner(idx, envs, state, alg), drc_combinations) end -function ctmrg_expand(state, envs::CTMRGEnv{C,T}, ::SimultaneousCTMRG) where {C,T} - Qtype = tensormaptype(spacetype(C), 3, 3, storagetype(C)) - Q = Zygote.Buffer(Array{Qtype,3}(undef, size(envs.corners))) - drc_combinations = collect(Iterators.product(axes(envs.corners)...)) - @fwdthreads for (dir, r, c) in drc_combinations - # TODO: generalize the corner computation to make it compatible with EnlargedCorners - Q[dir, r, c] = if dir == NORTHWEST - enlarge_northwest_corner((r, c), envs, state) - elseif dir == NORTHEAST - enlarge_northeast_corner((r, c), envs, state) - elseif dir == SOUTHEAST - enlarge_southeast_corner((r, c), envs, state) - elseif dir == SOUTHWEST - enlarge_southwest_corner((r, c), envs, state) - end - end - - return copy(Q) -end - -function enlarge_corner((dir, r, c), envs, state, alg::CTMRG) # TODO: find a way to dispatch on CTMRG struct to switch on function handles +function ctmrg_expand(state, envs::CTMRGEnv, ::SimultaneousCTMRG) + drc_combinations = collect(Iterators.product(1:4, axes(state)...)) + return map(idx -> enlarge_corner(idx, envs, state, alg), drc_combinations) end # ======================================================================================== # diff --git a/src/algorithms/ctmrg/sparse_environments.jl b/src/algorithms/ctmrg/sparse_environments.jl index 783ef429..f0dc2689 100644 --- a/src/algorithms/ctmrg/sparse_environments.jl +++ b/src/algorithms/ctmrg/sparse_environments.jl @@ -23,6 +23,48 @@ Contract enlarged corner where `Val(1)` dispatches the north-west, `Val(2)` the (Q::EnlargedCorner)(::Val{3}) = enlarge_southeast_corner(Q.E_1, Q.C, Q.E_2, Q.ket, Q.bra) (Q::EnlargedCorner)(::Val{4}) = enlarge_southwest_corner(Q.E_1, Q.C, Q.E_2, Q.ket, Q.bra) +""" + enlarge_corner(::Val{<:Int}, (r, c), envs, state, alg::SparseCTMRG) + +Enlarge corner but return as a `EnlargedCorner` struct used in sparse CTMRG. +""" +function enlarge_corner(::Val{1}, (r, c), envs, state, alg::SparseCTMRG) + return EnlargedCorner( + envs.corners[NORTHWEST, _prev(r, end), _prev(c, end)], + envs.edges[WEST, r, _prev(c, end)], + envs.edges[NORTH, _prev(r, end), c], + state[r, c], + state[r, c], + ) +end +function enlarge_corner(::Val{2}, (r, c), envs, state, alg::SparseCTMRG) + return EnlargedCorner( + envs.corners[NORTHEAST, _prev(r, end), _next(c, end)], + envs.edges[NORTH, _prev(r, end), c], + envs.edges[EAST, r, _next(c, end)], + state[r, c], + state[r, c], + ) +end +function enlarge_corner(::Val{3}, (r, c), envs, state, alg::SparseCTMRG) + return EnlargedCorner( + envs.corners[SOUTHEAST, _next(r, end), _next(c, end)], + envs.edges[EAST, r, _next(c, end)], + envs.edges[SOUTH, _next(r, end), c], + state[r, c], + state[r, c], + ) +end +function enlarge_corner(::Val{4}, (r, c), envs, state, alg::SparseCTMRG) + return EnlargedCorner( + envs.corners[SOUTHWEST, _next(r, end), _prev(c, end)], + envs.edges[SOUTH, _next(r, end), c], + envs.edges[WEST, r, _prev(c, end)], + state[r, c], + state[r, c], + ) +end + # Compute left & right projectors from enlarged corner struct function build_projectors( U::AbstractTensorMap{E,3,1}, @@ -39,6 +81,10 @@ function build_projectors( return P_left, P_right end +function renormalize_corner(ec::EnlargedCorner, P_left, P_right) + return renormalize_corner(ec.E_1, ec.C, ec.E_2, P_left, P_right, ec.ket, ec.bra) +end + """ struct HalfInfiniteEnv{C,E,A,A′} From 7bf94afaf5a4ab5f5d7458e4e90c27d111662f83 Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Fri, 4 Oct 2024 11:41:38 +0200 Subject: [PATCH 136/213] Add sparse_env type parameter to CTMRG --- src/PEPSKit.jl | 1 + src/algorithms/ctmrg/ctmrg.jl | 70 ++++++++++++--------- src/algorithms/ctmrg/sparse_environments.jl | 1 + 3 files changed, 43 insertions(+), 29 deletions(-) diff --git a/src/PEPSKit.jl b/src/PEPSKit.jl index 72bb6da6..1f87ace5 100644 --- a/src/PEPSKit.jl +++ b/src/PEPSKit.jl @@ -72,6 +72,7 @@ module Defaults const fpgrad_maxiter = 30 const fpgrad_tol = 1e-6 const ctmrgscheme = :simultaneous + const sparse_env = false const reuse_env = true const trscheme = FixedSpaceTruncation() const iterscheme = :fixed diff --git a/src/algorithms/ctmrg/ctmrg.jl b/src/algorithms/ctmrg/ctmrg.jl index af84e95f..1844d836 100644 --- a/src/algorithms/ctmrg/ctmrg.jl +++ b/src/algorithms/ctmrg/ctmrg.jl @@ -46,7 +46,7 @@ end CTMRG(; tol=Defaults.ctmrg_tol, maxiter=Defaults.ctmrg_maxiter, miniter=Defaults.ctmrg_miniter, verbosity=0, svd_alg=SVDAdjoint(), trscheme=FixedSpaceTruncation(), - ctmrgscheme=Defaults.ctmrgscheme) + ctmrgscheme=Defaults.ctmrgscheme, sparse_env=Defaults.sparse_env) Algorithm struct that represents the CTMRG algorithm for contracting infinite PEPS. Each CTMRG run is converged up to `tol` where the singular value convergence of the @@ -63,13 +63,17 @@ CTMRG is implemented. It can either be `:sequential`, where the projectors are s computed on the western side, and then applied and rotated. Or with `:simultaneous` all projectors are computed and applied simultaneously on all sides, where in particular the corners get contracted with two projectors at the same time. + +The contraction and SVD of the CTMRG environments can be performed densely, or sparsely +if `sparse_env` is `true`. If sparsity is enabled, then the SVD will use only function handles +and the full enlarged corners and environments are never explicitly constructed. """ -struct CTMRG{S} +struct CTMRG{M,S} tol::Float64 maxiter::Int miniter::Int verbosity::Int - projector_alg::ProjectorAlg + projector_alg::P end function CTMRG(; tol=Defaults.ctmrg_tol, @@ -79,13 +83,15 @@ function CTMRG(; svd_alg=Defaults.svd_alg, trscheme=Defaults.trscheme, ctmrgscheme::Symbol=Defaults.ctmrgscheme, + sparse_env::Bool=Defaults.sparse_env, ) - return CTMRG{ctmrgscheme}( + return CTMRG{ctmrgscheme,sparse_env}( tol, maxiter, miniter, verbosity, ProjectorAlg(; svd_alg, trscheme, verbosity) ) end -ctmrgscheme(::CTMRG{S}) where {S} = S +ctmrgscheme(::CTMRG{M,S}) where {M,S} = M +sparse_env(::CTMRG{M,S}) where {M,S} = S # aliases for the different CTMRG schemes const SequentialCTMRG = CTMRG{:sequential} @@ -175,27 +181,6 @@ ctmrg_logcancel!(log, iter, η, N) = @warnv 1 logcancel!(log, iter, η, N) # Expansion step # ======================================================================================== # -# TODO: find a way to dispatch on CTMRG struct to switch on function handles -""" - enlarge_corner(::Val{<:Int}, (r, c), envs, state, alg::CTMRG) - -Enlarge a CTMRG corner into the one of four directions `Val(1)` (NW), `Val(2)` (NE), -`Val(3)` (SE) or `Val(4)` (SW). This serves as a wrapper that can dispatch on the -directional index and different `CTMRG` algorithms. -""" -function enlarge_corner(::Val{1}, (r, c), envs, state, alg::CTMRG) - return enlarge_northwest_corner((r, c), envs, state) -end -function enlarge_corner(::Val{2}, (r, c), envs, state, alg::CTMRG) - return enlarge_northeast_corner((r, c), envs, state) -end -function enlarge_corner(::Val{3}, (r, c), envs, state, alg::CTMRG) - return enlarge_southeast_corner((r, c), envs, state) -end -function enlarge_corner(::Val{4}, (r, c), envs, state, alg::CTMRG) - return enlarge_southwest_corner((r, c), envs, state) -end - """ ctmrg_expand(state, envs, alg::CTMRG{M}) @@ -206,11 +191,15 @@ the left. The second mode expands the environment in all four directions simulta """ function ctmrg_expand(state, envs::CTMRGEnv, ::SequentialCTMRG) drc_combinations = collect(Iterators.product([4, 1], axes(state)...)) - return map(idx -> enlarge_corner(idx, envs, state, alg), drc_combinations) + return map(drc_combinations) do (dir, r, c) + enlarge_corner(Val(dir), (r, c), envs, state, alg) + end end function ctmrg_expand(state, envs::CTMRGEnv, ::SimultaneousCTMRG) drc_combinations = collect(Iterators.product(1:4, axes(state)...)) - return map(idx -> enlarge_corner(idx, envs, state, alg), drc_combinations) + return map(drc_combinations) do (dir, r, c) + enlarge_corner(Val(dir), (r, c), envs, state, alg) + end end # ======================================================================================== # @@ -218,7 +207,10 @@ end # ======================================================================================== # """ - ctmrg_projectors(Q, env, alg::CTMRG{M}) + ctmrg_projectors(enlarged_envs, env, alg::CTMRG{M}) + +Compute the CTMRG projectors based from enlarged environments. +In the `:simultaneous` mode, the environment SVD is run in parallel. """ function ctmrg_projectors( enlarged_envs, envs::CTMRGEnv{C,E}, alg::SequentialCTMRG @@ -388,6 +380,26 @@ end # Auxiliary routines # ======================================================================================== # +""" + enlarge_corner(::Val{<:Int}, (r, c), envs, state, alg::CTMRG) + +Enlarge a CTMRG corner into the one of four directions `Val(1)` (NW), `Val(2)` (NE), +`Val(3)` (SE) or `Val(4)` (SW). This serves as a wrapper that can dispatch on the +directional index and different `CTMRG` algorithms. +""" +function enlarge_corner(::Val{1}, (r, c), envs, state, alg::CTMRG) + return enlarge_northwest_corner((r, c), envs, state) +end +function enlarge_corner(::Val{2}, (r, c), envs, state, alg::CTMRG) + return enlarge_northeast_corner((r, c), envs, state) +end +function enlarge_corner(::Val{3}, (r, c), envs, state, alg::CTMRG) + return enlarge_southeast_corner((r, c), envs, state) +end +function enlarge_corner(::Val{4}, (r, c), envs, state, alg::CTMRG) + return enlarge_southwest_corner((r, c), envs, state) +end + # Build projectors from SVD and dense enlarged corners function build_projectors( U::AbstractTensorMap{E,3,1}, diff --git a/src/algorithms/ctmrg/sparse_environments.jl b/src/algorithms/ctmrg/sparse_environments.jl index f0dc2689..abf4358e 100644 --- a/src/algorithms/ctmrg/sparse_environments.jl +++ b/src/algorithms/ctmrg/sparse_environments.jl @@ -1,3 +1,4 @@ +const SparseCTMRG = CTMRG{M,true} where {M} # TODO: Is this really a good way to dispatch on the sparse CTMRG methods? """ struct EnlargedCorner{Ct,E,A,A′} From c931def01846e492e90ecb656a5f0c23029b7ff6 Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Fri, 4 Oct 2024 17:42:36 +0200 Subject: [PATCH 137/213] Implement normal and adjoint linear map (svdsolve does not yet work) --- .../contractions/ctmrg_contractions.jl | 48 ++++++++-- src/algorithms/ctmrg/ctmrg.jl | 6 +- src/algorithms/ctmrg/sparse_environments.jl | 92 ++++++++++++++----- src/utility/svd.jl | 7 +- test/ctmrg/svd_wrapper.jl | 29 ++++-- 5 files changed, 137 insertions(+), 45 deletions(-) diff --git a/src/algorithms/contractions/ctmrg_contractions.jl b/src/algorithms/contractions/ctmrg_contractions.jl index 67317ed6..1883b07c 100644 --- a/src/algorithms/contractions/ctmrg_contractions.jl +++ b/src/algorithms/contractions/ctmrg_contractions.jl @@ -248,14 +248,14 @@ Alternatively, contract environment with a vector `x` acting on it, e.g. as need function halfinfinite_environment( quadrant1::AbstractTensorMap{S,3,3}, quadrant2::AbstractTensorMap{S,3,3} ) where {S} - return @autoopt @tensor half[χ_in D_inabove D_inbelow; χ_out D_outabove D_outbelow] := + return @autoopt @tensor env[χ_in D_inabove D_inbelow; χ_out D_outabove D_outbelow] := quadrant1[χ_in D_inabove D_inbelow; χ D1 D2] * quadrant2[χ D1 D2; χ_out D_outabove D_outbelow] end function halfinfinite_environment( C_1, C_2, E_1, E_2, E_3, E_4, ket_1::P, ket_2::P, bra_1::P=ket_1, bra_2::P=ket_2 ) where {P<:PEPSTensor} - return @autoopt @tensor half[χ_in D_inabove D_inbelow; χ_out D_outabove D_outbelow] := + return @autoopt @tensor env[χ_in D_inabove D_inbelow; χ_out D_outabove D_outbelow] := E_1[χ_in D1 D2; χ1] * C_1[χ1; χ2] * E_2[χ2 D3 D4; χ3] * @@ -268,9 +268,19 @@ function halfinfinite_environment( E_4[χ5 D7 D8; χ_out] end function halfinfinite_environment( - C_1, C_2, E_1, E_2, E_3, E_4, x, ket_1::P, ket_2::P, bra_1::P=ket_1, bra_2::P=ket_2 -) where {P<:PEPSTensor} - return @autoopt @tensor half[χ_in D_inabove D_inbelow; χ_out D_outabove D_outbelow] := + C_1, + C_2, + E_1, + E_2, + E_3, + E_4, + x::AbstractTensor{S,3}, + ket_1::P, + ket_2::P, + bra_1::P=ket_1, + bra_2::P=ket_2, +) where {S,P<:PEPSTensor} + return @autoopt @tensor env_x[χ_in D_inabove D_inbelow] := E_1[χ_in D1 D2; χ1] * C_1[χ1; χ2] * E_2[χ2 D3 D4; χ3] * @@ -281,7 +291,33 @@ function halfinfinite_environment( E_3[χ3 D5 D6; χ4] * C_2[χ4; χ5] * E_4[χ5 D7 D8; χ6] * - x[χ6 D11 D12; χ_out D_outabove D_outbelow] + x[χ6 D11 D12] +end +function halfinfinite_environment( + x::AbstractTensor{S,3}, + C_1, + C_2, + E_1, + E_2, + E_3, + E_4, + ket_1::P, + ket_2::P, + bra_1::P=ket_1, + bra_2::P=ket_2, +) where {S,P<:PEPSTensor} + return @autoopt @tensor env_x[χ_in D_inabove D_inbelow] := + x[χ1 D1 D2] * + conj(E_1[χ1 D3 D4; χ2]) * + conj(C_1[χ2; χ3]) * + conj(E_2[χ3 D5 D6; χ4]) * + conj(ket_1[d1; D5 D11 D1 D3]) * + bra_1[d1; D6 D12 D2 D4] * + conj(ket_2[d2; D7 D9 D_inabove D11]) * + bra_2[d2; D8 D10 D_inbelow D12] * + conj(E_3[χ4 D7 D8; χ5]) * + conj(C_2[χ5; χ6]) * + conj(E_4[χ6 D9 D10; χ_in]) end # Renormalization contractions diff --git a/src/algorithms/ctmrg/ctmrg.jl b/src/algorithms/ctmrg/ctmrg.jl index 1844d836..f95c2317 100644 --- a/src/algorithms/ctmrg/ctmrg.jl +++ b/src/algorithms/ctmrg/ctmrg.jl @@ -73,7 +73,7 @@ struct CTMRG{M,S} maxiter::Int miniter::Int verbosity::Int - projector_alg::P + projector_alg::ProjectorAlg end function CTMRG(; tol=Defaults.ctmrg_tol, @@ -189,13 +189,13 @@ There are two modes of expansion: `M = :sequential` and `M = :simultaneous`. The first mode expands the environment in one direction at a time, for convenience towards the left. The second mode expands the environment in all four directions simultaneously. """ -function ctmrg_expand(state, envs::CTMRGEnv, ::SequentialCTMRG) +function ctmrg_expand(state, envs::CTMRGEnv, alg::SequentialCTMRG) drc_combinations = collect(Iterators.product([4, 1], axes(state)...)) return map(drc_combinations) do (dir, r, c) enlarge_corner(Val(dir), (r, c), envs, state, alg) end end -function ctmrg_expand(state, envs::CTMRGEnv, ::SimultaneousCTMRG) +function ctmrg_expand(state, envs::CTMRGEnv, alg::SimultaneousCTMRG) drc_combinations = collect(Iterators.product(1:4, axes(state)...)) return map(drc_combinations) do (dir, r, c) enlarge_corner(Val(dir), (r, c), envs, state, alg) diff --git a/src/algorithms/ctmrg/sparse_environments.jl b/src/algorithms/ctmrg/sparse_environments.jl index abf4358e..b102b70a 100644 --- a/src/algorithms/ctmrg/sparse_environments.jl +++ b/src/algorithms/ctmrg/sparse_environments.jl @@ -1,5 +1,9 @@ const SparseCTMRG = CTMRG{M,true} where {M} # TODO: Is this really a good way to dispatch on the sparse CTMRG methods? +# -------------------------------------------------------- +# Sparse enlarged corner as building block for environment +# -------------------------------------------------------- + """ struct EnlargedCorner{Ct,E,A,A′} @@ -24,6 +28,10 @@ Contract enlarged corner where `Val(1)` dispatches the north-west, `Val(2)` the (Q::EnlargedCorner)(::Val{3}) = enlarge_southeast_corner(Q.E_1, Q.C, Q.E_2, Q.ket, Q.bra) (Q::EnlargedCorner)(::Val{4}) = enlarge_southwest_corner(Q.E_1, Q.C, Q.E_2, Q.ket, Q.bra) +# ------------------------- +# CTMRG compatibility layer +# ------------------------- + """ enlarge_corner(::Val{<:Int}, (r, c), envs, state, alg::SparseCTMRG) @@ -66,7 +74,6 @@ function enlarge_corner(::Val{4}, (r, c), envs, state, alg::SparseCTMRG) ) end -# Compute left & right projectors from enlarged corner struct function build_projectors( U::AbstractTensorMap{E,3,1}, S::AbstractTensorMap{E,1,1}, @@ -86,6 +93,10 @@ function renormalize_corner(ec::EnlargedCorner, P_left, P_right) return renormalize_corner(ec.E_1, ec.C, ec.E_2, P_left, P_right, ec.ket, ec.bra) end +# ------------------ +# Sparse environment +# ------------------ + """ struct HalfInfiniteEnv{C,E,A,A′} @@ -120,11 +131,13 @@ end """ (env::HalfInfiniteEnv)() - (env::HalfInfiniteEnv)(x) + (env::HalfInfiniteEnv)(x, ::Val{false}) + (env::HalfInfiniteEnv)(x, ::Val{true}) -Contract half-infinite environment without or with a vector `x`. +Contract half-infinite environment. If a vector `x` is provided, the environment acts as a +linear map or adjoint linear map on `x` if `Val(true)` or `Val(false)` is passed, respectively. """ -function (env::HalfInfiniteEnv)() +function (env::HalfInfiniteEnv)() # Dense operator return halfinfinite_environment( env.C_1, env.C_2, @@ -138,7 +151,7 @@ function (env::HalfInfiniteEnv)() env.bra_2, ) end -function (env::HalfInfiniteEnv)(x) +function (env::HalfInfiniteEnv)(x, ::Val{false}) # Linear map: env() * x return halfinfinite_environment( env.C_1, env.C_2, @@ -153,22 +166,67 @@ function (env::HalfInfiniteEnv)(x) env.bra_2, ) end +function (env::HalfInfiniteEnv)(x, ::Val{true}) # Adjoint linear map: env()' * x + return halfinfinite_environment( + x, + env.C_1, + env.C_2, + env.E_1, + env.E_2, + env.E_3, + env.E_4, + env.ket_1, + env.ket_2, + env.bra_1, + env.bra_2, + ) +end + +# Wrapper around halfinfinite_environment contraction using EnlargedCorners (used in ctmrg_projectors) +function halfinfinite_environment(ec_1::EnlargedCorner, ec_2::EnlargedCorner) + return HalfInfiniteEnv( + ec_1.C, + ec_2.C, + ec_1.E_1, + ec_1.E_2, + ec_2.E_1, + ec_2.E_2, + ec_1.ket, + ec_2.ket, + ec_1.bra, + ec_2.bra, + ) +end + +# ------------------------------------------------------------- +# TensorKit methods to make environment compatible with IterSVD +# ------------------------------------------------------------- -# TensorKit methods to make struct compatible with sparse SVD TensorKit.InnerProductStyle(::HalfInfiniteEnv) = EuclideanProduct() TensorKit.sectortype(::HalfInfiniteEnv) = Trivial TensorKit.storagetype(env::HalfInfiniteEnv) = storagetype(env.ket_1) TensorKit.spacetype(env::HalfInfiniteEnv) = spacetype(env.ket_1) +function TensorKit.domain(env::HalfInfiniteEnv) + return domain(env.E_4) * domain(env.ket_2)[3] * domain(env.bra_2)[3]' +end +function TensorKit.codomain(env::HalfInfiniteEnv) + return codomain(env.E_1)[1] * domain(env.ket_1)[3]' * domain(env.bra_1)[3] +end +function TensorKit.space(env::HalfInfiniteEnv) + return codomain(env) ← domain(env) +end function TensorKit.blocks(env::HalfInfiniteEnv) return TensorKit.SingletonDict(Trivial() => env) end function TensorKit.blocksectors(::HalfInfiniteEnv) return TensorKit.OneOrNoneIterator{Trivial}(true, Trivial()) end - +function TensorKit.tsvd!(f::HalfInfiniteEnv; trunc=NoTruncation(), p::Real=2, alg=IterSVD()) + return _tsvd!(f, alg, trunc, p) +end function TensorKit.MatrixAlgebra.svd!(env::HalfInfiniteEnv, args...) - return TensorKit.MatrixAlgebra.svd!(env(), args...) + return TensorKit.MatrixAlgebra.svd!(env(), args...) # Construct environment densely as fallback end Base.eltype(env::HalfInfiniteEnv) = eltype(env.ket_1) @@ -182,22 +240,8 @@ function Base.size(env::HalfInfiniteEnv) # Treat environment as matrix return (χ_in * D_inabove * D_inbelow, χ_out * D_outabove * D_outbelow) end Base.size(env::HalfInfiniteEnv, i::Int) = size(env)[i] - -# TODO: implement VectorInterface VectorInterface.scalartype(env::HalfInfiniteEnv) = scalartype(env.ket_1) -# Wrapper around halfinfinite_environment contraction using EnlargedCorners (used in ctmrg_projectors) -function halfinfinite_environment(ec_1::EnlargedCorner, ec_2::EnlargedCorner) - return HalfInfiniteEnv( - ec_1.C, - ec_2.C, - ec_1.E_1, - ec_1.E_2, - ec_2.E_1, - ec_2.E_2, - ec_1.ket, - ec_2.ket, - ec_1.bra, - ec_2.bra, - ) +function random_start_vector(env::HalfInfiniteEnv) + return Tensor(randn, domain(env)) end diff --git a/src/utility/svd.jl b/src/utility/svd.jl index 54765e20..96c1af40 100644 --- a/src/utility/svd.jl +++ b/src/utility/svd.jl @@ -36,9 +36,6 @@ PEPSKit.tsvd(t, alg; kwargs...) = PEPSKit.tsvd!(copy(t), alg; kwargs...) function PEPSKit.tsvd!(t, alg::SVDAdjoint; trunc::TruncationScheme=notrunc(), p::Real=2) return TensorKit.tsvd!(t; alg=alg.fwd_alg, trunc, p) end -function TensorKit.tsvd!(f::HalfInfiniteEnv; trunc=NoTruncation(), p::Real=2, alg=IterSVD()) - return _tsvd!(f, alg, trunc, p) -end """ struct FixedSVD @@ -74,8 +71,8 @@ the iterative SVD didn't converge, the algorithm falls back to a dense SVD. end # TODO: find better initial guess that leads to element-wise convergence and is compatible with function handles -function random_start_vector(f) - return randn(eltype(f), size(f, 1)) # Leads to erroneous gauge fixing of U, S, V and thus failing element-wise conv. +function random_start_vector(t::Matrix) + return randn(eltype(t), size(t, 1)) # Leads to erroneous gauge fixing of U, S, V and thus failing element-wise conv. # u, = TensorKit.MatrixAlgebra.svd!(deepcopy(t), TensorKit.SVD()) # return sum(u[:, i] for i in 1:howmany) # Element-wise convergence works fine # return dropdims(sum(t[:, 1:3]; dims=2); dims=2) # Summing too many columns also makes gauge fixing fail diff --git a/test/ctmrg/svd_wrapper.jl b/test/ctmrg/svd_wrapper.jl index 927a1ed3..68a53aa8 100644 --- a/test/ctmrg/svd_wrapper.jl +++ b/test/ctmrg/svd_wrapper.jl @@ -100,7 +100,7 @@ ctm_alg = CTMRG(; tol=1e-10, verbosity=2, svd_alg=SVDAdjoint()) Random.seed!(91283219347) H = heisenberg_XYZ(InfiniteSquare()) psi = InfinitePEPS(2, χbond) -env = leading_boundary(CTMRGEnv(psi, ComplexSpace(χenv)), psi, ctm_alg) +env = leading_boundary(CTMRGEnv(psi, ComplexSpace(χenv)), psi, ctm_alg); hienv = HalfInfiniteEnv( env.corners[1], env.corners[2], @@ -114,12 +114,27 @@ hienv = HalfInfiniteEnv( psi[1], ) hienv_dense = hienv() -env_R = TensorMap(randn, space(hienv_dense)) -PEPSKit.tsvd!(hienv, iter_alg) +env_R = TensorMap(randn, space(hienv)) + +PEPSKit.tsvd!(hienv, iter_alg) # TODO: make the space mismatches work @testset "IterSVD with HalfInfiniteEnv function handle" begin - l_fullsvd, g_fullsvd = withgradient(A -> lossfun(A, full_alg, env_R), hienv_dense) - l_itersvd, g_itersvd = withgradient(A -> lossfun(A, iter_alg, env_R), hienv) - @test l_itersvd ≈ l_fullsvd - @test g_fullsvd[1] ≈ g_itersvd[1] rtol = rtol + # Equivalence of dense and sparse contractions + x₀ = PEPSKit.random_start_vector(hienv) + x′ = hienv(x₀, Val(false)) + x″ = hienv(x′, Val(true)) + x‴ = hienv(x″, Val(false)) + + a = hienv_dense * x₀ + b = hienv_dense' * a + c = hienv_dense * b + @test a ≈ x′ + @test b ≈ x″ + @test c ≈ x‴ + + # TODO: code up pullback + # l_fullsvd, g_fullsvd = withgradient(A -> lossfun(A, full_alg, env_R), hienv_dense) + # l_itersvd, g_itersvd = withgradient(A -> lossfun(A, iter_alg, env_R), hienv) + # @test l_itersvd ≈ l_fullsvd + # @test g_fullsvd[1] ≈ g_itersvd[1] rtol = rtol end From 9e033dc7c3c78c85b04b4af12caf68ff7a5a7cbe Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Fri, 4 Oct 2024 18:09:40 +0200 Subject: [PATCH 138/213] Add tsvd! rrule compatibility layer for HalfInfiniteEnv --- src/algorithms/ctmrg/sparse_environments.jl | 24 +++++++++++-- src/utility/svd.jl | 37 +++++++-------------- 2 files changed, 33 insertions(+), 28 deletions(-) diff --git a/src/algorithms/ctmrg/sparse_environments.jl b/src/algorithms/ctmrg/sparse_environments.jl index b102b70a..e79c5d84 100644 --- a/src/algorithms/ctmrg/sparse_environments.jl +++ b/src/algorithms/ctmrg/sparse_environments.jl @@ -198,9 +198,9 @@ function halfinfinite_environment(ec_1::EnlargedCorner, ec_2::EnlargedCorner) ) end -# ------------------------------------------------------------- -# TensorKit methods to make environment compatible with IterSVD -# ------------------------------------------------------------- +# ------------------------------------------------------------------------ +# Methods to make environment compatible with IterSVD and its reverse-rule +# ------------------------------------------------------------------------ TensorKit.InnerProductStyle(::HalfInfiniteEnv) = EuclideanProduct() TensorKit.sectortype(::HalfInfiniteEnv) = Trivial @@ -222,6 +222,9 @@ end function TensorKit.blocksectors(::HalfInfiniteEnv) return TensorKit.OneOrNoneIterator{Trivial}(true, Trivial()) end +function TensorKit.block(env::HalfInfiniteEnv, c::Sector) + return env +end function TensorKit.tsvd!(f::HalfInfiniteEnv; trunc=NoTruncation(), p::Real=2, alg=IterSVD()) return _tsvd!(f, alg, trunc, p) end @@ -245,3 +248,18 @@ VectorInterface.scalartype(env::HalfInfiniteEnv) = scalartype(env.ket_1) function random_start_vector(env::HalfInfiniteEnv) return Tensor(randn, domain(env)) end + +function Base.similar(env::HalfInfiniteEnv) + return HalfInfiniteEnv( + (similar(getfield(env, field)) for field in fieldnames(HalfInfiniteEnv))... + ) +end + +function Base.copyto!(dest::HalfInfiniteEnv, src::HalfInfiniteEnv) + for field in fieldnames(HalfInfiniteEnv) + for (bd, bs) in zip(blocks(getfield(dest, field)), blocks(getfield(src, field))) + copyto!(bd[2], bs[2]) + end + end + return dest +end diff --git a/src/utility/svd.jl b/src/utility/svd.jl index 96c1af40..d439b4f6 100644 --- a/src/utility/svd.jl +++ b/src/utility/svd.jl @@ -137,27 +137,27 @@ function TensorKit._compute_svddata!( return Udata, Sdata, Vdata, dims end -# Rrule with custom pullback to make KrylovKit rrule compatible with TensorMap symmetry blocks +# Rrule with custom pullback to make KrylovKit rrule compatible with TensorMaps & function handles function ChainRulesCore.rrule( ::typeof(PEPSKit.tsvd!), - t::AbstractTensorMap, + f, alg::SVDAdjoint{F,R,B}; trunc::TruncationScheme=notrunc(), p::Real=2, ) where {F<:Union{IterSVD,FixedSVD},R<:Union{GMRES,BiCGStab,Arnoldi},B} - U, S, V, ϵ = PEPSKit.tsvd(t, alg; trunc, p) + U, S, V, ϵ = PEPSKit.tsvd(f, alg; trunc, p) function tsvd!_itersvd_pullback((ΔU, ΔS, ΔV, Δϵ)) - Δt = similar(t) - for (c, b) in blocks(Δt) + Δf = similar(f) + for (c, b) in blocks(Δf) Uc, Sc, Vc = block(U, c), block(S, c), block(V, c) ΔUc, ΔSc, ΔVc = block(ΔU, c), block(ΔS, c), block(ΔV, c) Sdc = view(Sc, diagind(Sc)) ΔSdc = ΔSc isa AbstractZero ? ΔSc : view(ΔSc, diagind(ΔSc)) n_vals = length(Sdc) - lvecs = Vector{Vector{scalartype(t)}}(eachcol(Uc)) - rvecs = Vector{Vector{scalartype(t)}}(eachcol(Vc')) + lvecs = Vector{Vector{scalartype(f)}}(eachcol(Uc)) + rvecs = Vector{Vector{scalartype(f)}}(eachcol(Vc')) # Dummy objects only used for warnings minimal_info = KrylovKit.ConvergenceInfo(n_vals, nothing, nothing, -1, -1) # Only num. converged is used @@ -167,8 +167,8 @@ function ChainRulesCore.rrule( Δlvecs = fill(ZeroTangent(), n_vals) Δrvecs = fill(ZeroTangent(), n_vals) else - Δlvecs = Vector{Vector{scalartype(t)}}(eachcol(ΔUc)) - Δrvecs = Vector{Vector{scalartype(t)}}(eachcol(ΔVc')) + Δlvecs = Vector{Vector{scalartype(f)}}(eachcol(ΔUc)) + Δrvecs = Vector{Vector{scalartype(f)}}(eachcol(ΔVc')) end xs, ys = CRCExt.compute_svdsolve_pullback_data( @@ -179,17 +179,17 @@ function ChainRulesCore.rrule( lvecs, rvecs, minimal_info, - block(t, c), + block(f, c), :LR, minimal_alg, alg.rrule_alg, ) copyto!( b, - CRCExt.construct∂f_svd(HasReverseMode(), block(t, c), lvecs, rvecs, xs, ys), + CRCExt.construct∂f_svd(HasReverseMode(), block(f, c), lvecs, rvecs, xs, ys), ) end - return NoTangent(), Δt, NoTangent() + return NoTangent(), Δf, NoTangent() end function tsvd!_itersvd_pullback(::Tuple{ZeroTangent,ZeroTangent,ZeroTangent}) return NoTangent(), ZeroTangent(), NoTangent() @@ -198,19 +198,6 @@ function ChainRulesCore.rrule( return (U, S, V, ϵ), tsvd!_itersvd_pullback end -# Separate rule for SVD with function handle that uses KrylovKit.make_svdsolve_pullback -# but in turn cannot handle symmetric blocks -function ChainRulesCore.rrule( - ::typeof(PEPSKit.tsvd!), - f, - alg::SVDAdjoint{F,R,B}; - trunc::TruncationScheme=notrunc(), - p::Real=2, -) where {F<:Union{IterSVD,FixedSVD},R<:Union{GMRES,BiCGStab,Arnoldi},B} - return U, S, V, ϵ = PEPSKit.tsvd(t, alg; trunc, p) - # TODO: implement function handle adjoint wrapper with KrylovKit.make_svdsolve_pullback -end - """ struct NonTruncAdjoint From c3a88227fbe41f77575cb2697228324137dd1290 Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Mon, 7 Oct 2024 11:42:36 +0200 Subject: [PATCH 139/213] Add sparse as kwarg of ProjectorAlg, use EnlargedCorners per default, fix some docstrings --- src/PEPSKit.jl | 2 +- .../contractions/ctmrg_contractions.jl | 14 +- src/algorithms/ctmrg/ctmrg.jl | 172 +++++++++++------- src/algorithms/ctmrg/sparse_environments.jl | 83 ++------- 4 files changed, 134 insertions(+), 137 deletions(-) diff --git a/src/PEPSKit.jl b/src/PEPSKit.jl index 1f87ace5..7dee4ebb 100644 --- a/src/PEPSKit.jl +++ b/src/PEPSKit.jl @@ -72,7 +72,7 @@ module Defaults const fpgrad_maxiter = 30 const fpgrad_tol = 1e-6 const ctmrgscheme = :simultaneous - const sparse_env = false + const sparse = false const reuse_env = true const trscheme = FixedSpaceTruncation() const iterscheme = :fixed diff --git a/src/algorithms/contractions/ctmrg_contractions.jl b/src/algorithms/contractions/ctmrg_contractions.jl index 1883b07c..10a569e7 100644 --- a/src/algorithms/contractions/ctmrg_contractions.jl +++ b/src/algorithms/contractions/ctmrg_contractions.jl @@ -211,10 +211,12 @@ end """ halfinfinite_environment(quadrant1::AbstractTensorMap{S,3,3}, quadrant2::AbstractTensorMap{S,3,3}) - function halfinfinite_environment(C_1, C_2, E_1, E_2, E_3, E_4, - ket_1::P, ket_2::P, bra_1::P=ket_1, bra_2::P=ket_2) where {P<:PEPSTensor} - function halfinfinite_environment(C_1, C_2, E_1, E_2, E_3, E_4, x, - ket_1::P, ket_2::P, bra_1::P=ket_1, bra_2::P=ket_2) where {P<:PEPSTensor} + halfinfinite_environment(C_1, C_2, E_1, E_2, E_3, E_4, + ket_1::P, ket_2::P, bra_1::P=ket_1, bra_2::P=ket_2) where {P<:PEPSTensor} + halfinfinite_environment(C_1, C_2, E_1, E_2, E_3, E_4, x, + ket_1::P, ket_2::P, bra_1::P=ket_1, bra_2::P=ket_2) where {P<:PEPSTensor} + halfinfinite_environment(x, C_1, C_2, E_1, E_2, E_3, E_4, + ket_1::P, ket_2::P, bra_1::P=ket_1, bra_2::P=ket_2) where {P<:PEPSTensor} Contract two quadrants (enlarged corners) to form a half-infinite environment. @@ -234,7 +236,7 @@ The environment can also be contracted directly from all its constituent tensors | || || | ``` -Alternatively, contract environment with a vector `x` acting on it, e.g. as needed for iterative solvers. +Alternatively, contract environment with a vector `x` acting on it ``` C_1 -- E_2 -- E_3 -- C_2 @@ -244,6 +246,8 @@ Alternatively, contract environment with a vector `x` acting on it, e.g. as need [~~~~~~x~~~~~~] || | ``` + +or contract the adjoint environment with `x`, e.g. as needed for iterative solvers. """ function halfinfinite_environment( quadrant1::AbstractTensorMap{S,3,3}, quadrant2::AbstractTensorMap{S,3,3} diff --git a/src/algorithms/ctmrg/ctmrg.jl b/src/algorithms/ctmrg/ctmrg.jl index f95c2317..0cf3166c 100644 --- a/src/algorithms/ctmrg/ctmrg.jl +++ b/src/algorithms/ctmrg/ctmrg.jl @@ -8,18 +8,20 @@ have different spaces, this truncation style is different from `TruncationSpace` struct FixedSpaceTruncation <: TensorKit.TruncationScheme end """ - struct ProjectorAlg{S}(; svd_alg=TensorKit.SVD(), trscheme=TensorKit.notrunc(), - fixedspace=false, verbosity=0) - -Algorithm struct collecting all projector related parameters. The truncation scheme has to be -a `TensorKit.TruncationScheme`, and some SVD algorithms might have further restrictions on what -kind of truncation scheme can be used. If `fixedspace` is true, the truncation scheme is set to -`truncspace(V)` where `V` is the environment bond space, adjusted to the corresponding -environment direction/unit cell entry. + struct ProjectorAlg{S}(; svd_alg=Defaults.svd_alg, trscheme=Defaults.trscheme, + sparse=Defaults.sparse, verbosity=0) + +Algorithm struct collecting all projector related parameters. + +The `svd_alg` sets the SVD algorithm for decomposing the CTM environment. The truncation scheme +has to be a `TensorKit.TruncationScheme`, and some SVD algorithms might have further restrictions +on what kind of truncation scheme can be used. If `sparse` is `true`, then the enlarged corners and +CTM environment are never computed densely and function handles are using for the SVD. """ @kwdef struct ProjectorAlg{S<:SVDAdjoint,T} svd_alg::S = Defaults.svd_alg trscheme::T = Defaults.trscheme + sparse::Bool = Defaults.sparse verbosity::Int = 0 end # TODO: add option for different projector styles (half-infinite, full-infinite, etc.) @@ -46,7 +48,7 @@ end CTMRG(; tol=Defaults.ctmrg_tol, maxiter=Defaults.ctmrg_maxiter, miniter=Defaults.ctmrg_miniter, verbosity=0, svd_alg=SVDAdjoint(), trscheme=FixedSpaceTruncation(), - ctmrgscheme=Defaults.ctmrgscheme, sparse_env=Defaults.sparse_env) + ctmrgscheme=Defaults.ctmrgscheme, sparse=Defaults.sparse) Algorithm struct that represents the CTMRG algorithm for contracting infinite PEPS. Each CTMRG run is converged up to `tol` where the singular value convergence of the @@ -65,10 +67,10 @@ are computed and applied simultaneously on all sides, where in particular the co contracted with two projectors at the same time. The contraction and SVD of the CTMRG environments can be performed densely, or sparsely -if `sparse_env` is `true`. If sparsity is enabled, then the SVD will use only function handles +if `sparse` is `true`. If sparsity is enabled, then the SVD will use only function handles and the full enlarged corners and environments are never explicitly constructed. """ -struct CTMRG{M,S} +struct CTMRG{S} tol::Float64 maxiter::Int miniter::Int @@ -83,15 +85,18 @@ function CTMRG(; svd_alg=Defaults.svd_alg, trscheme=Defaults.trscheme, ctmrgscheme::Symbol=Defaults.ctmrgscheme, - sparse_env::Bool=Defaults.sparse_env, + sparse::Bool=Defaults.sparse, ) - return CTMRG{ctmrgscheme,sparse_env}( - tol, maxiter, miniter, verbosity, ProjectorAlg(; svd_alg, trscheme, verbosity) + return CTMRG{ctmrgscheme}( + tol, + maxiter, + miniter, + verbosity, + ProjectorAlg(; svd_alg, trscheme, sparse, verbosity), ) end -ctmrgscheme(::CTMRG{M,S}) where {M,S} = M -sparse_env(::CTMRG{M,S}) where {M,S} = S +ctmrgscheme(::CTMRG{S}) where {S} = S # aliases for the different CTMRG schemes const SequentialCTMRG = CTMRG{:sequential} @@ -189,16 +194,53 @@ There are two modes of expansion: `M = :sequential` and `M = :simultaneous`. The first mode expands the environment in one direction at a time, for convenience towards the left. The second mode expands the environment in all four directions simultaneously. """ -function ctmrg_expand(state, envs::CTMRGEnv, alg::SequentialCTMRG) +function ctmrg_expand(state, envs::CTMRGEnv, ::SequentialCTMRG) drc_combinations = collect(Iterators.product([4, 1], axes(state)...)) - return map(drc_combinations) do (dir, r, c) - enlarge_corner(Val(dir), (r, c), envs, state, alg) - end + return map(idx -> enlarge_corner(idx, envs, state), drc_combinations) end -function ctmrg_expand(state, envs::CTMRGEnv, alg::SimultaneousCTMRG) +function ctmrg_expand(state, envs::CTMRGEnv, ::SimultaneousCTMRG) drc_combinations = collect(Iterators.product(1:4, axes(state)...)) - return map(drc_combinations) do (dir, r, c) - enlarge_corner(Val(dir), (r, c), envs, state, alg) + return map(idx -> enlarge_corner(idx, envs, state), drc_combinations) +end + +""" + enlarge_corner((dir, r, c), envs, state) + +Enlarge corner by constructing the corresponding `EnlargedCorner` struct. +""" +function enlarge_corner((dir, r, c), envs, state) + if dir == NORTHWEST + return EnlargedCorner( + envs.corners[NORTHWEST, _prev(r, end), _prev(c, end)], + envs.edges[WEST, r, _prev(c, end)], + envs.edges[NORTH, _prev(r, end), c], + state[r, c], + state[r, c], + ) + elseif dir == NORTHEAST + return EnlargedCorner( + envs.corners[NORTHEAST, _prev(r, end), _next(c, end)], + envs.edges[NORTH, _prev(r, end), c], + envs.edges[EAST, r, _next(c, end)], + state[r, c], + state[r, c], + ) + elseif dir == SOUTHEAST + return EnlargedCorner( + envs.corners[SOUTHEAST, _next(r, end), _next(c, end)], + envs.edges[EAST, r, _next(c, end)], + envs.edges[SOUTH, _next(r, end), c], + state[r, c], + state[r, c], + ) + elseif dir == SOUTHWEST + return EnlargedCorner( + envs.corners[SOUTHWEST, _next(r, end), _prev(c, end)], + envs.edges[SOUTH, _next(r, end), c], + envs.edges[WEST, r, _prev(c, end)], + state[r, c], + state[r, c], + ) end end @@ -222,6 +264,11 @@ function ctmrg_projectors( P_top = Zygote.Buffer(envs.edges, Prtype, axes(envs.corners, 2), axes(envs.corners, 3)) ϵ = zero(real(scalartype(envs))) + # If CTMRG is not sparse, construct all enlarged corners densely + sparse || enlarged_envs = map(Iterators.product(axes(enlarged_envs)...)) do (dir, r, c) + return enlarged_envs[dir, r, c](dir) + end + directions = collect(Iterators.product(axes(envs.corners, 2), axes(envs.corners, 3))) # @fwdthreads for (r, c) in directions for (r, c) in directions @@ -260,6 +307,11 @@ function ctmrg_projectors( # Corner type but with real numbers S = Zygote.Buffer(U.data, tensormaptype(spacetype(C), 1, 1, real(scalartype(E)))) + # If CTMRG is not sparse, construct all enlarged corners densely + sparse || enlarged_envs = map(Iterators.product(axes(enlarged_envs)...)) do (dir, r, c) + return enlarged_envs[dir, r, c](dir) + end + ϵ = zero(real(scalartype(envs))) drc_combinations = collect(Iterators.product(axes(envs.corners)...)) @fwdthreads for (dir, r, c) in drc_combinations @@ -308,6 +360,42 @@ function ctmrg_projectors( return (copy(P_left), copy(P_right)), (; err=ϵ, U=copy(U), S=copy(S), V=copy(V)) end +""" + build_projectors(U::AbstractTensorMap{E,3,1}, S::AbstractTensorMap{E,1,1}, V::AbstractTensorMap{E,1,3}, + Q::AbstractTensorMap{E,3,3}, Q_next::AbstractTensorMap{E,3,3}) where {E<:ElementarySpace} + + build_projectors(U::AbstractTensorMap{E,3,1}, S::AbstractTensorMap{E,1,1}, V::AbstractTensorMap{E,1,3}, + Q::EnlargedCorner, Q_next::EnlargedCorner) where {E<:ElementarySpace} + +Construct left and right projectors where the higher-dimensional is facing left and right, respectively. +""" +function build_projectors( + U::AbstractTensorMap{E,3,1}, + S::AbstractTensorMap{E,1,1}, + V::AbstractTensorMap{E,1,3}, + Q::AbstractTensorMap{E,3,3}, + Q_next::AbstractTensorMap{E,3,3}, +) where {E<:ElementarySpace} + isqS = sdiag_inv_sqrt(S) + P_left = Q_next * V' * isqS + P_right = isqS * U' * Q + return P_left, P_right +end +function build_projectors( + U::AbstractTensorMap{E,3,1}, + S::AbstractTensorMap{E,1,1}, + V::AbstractTensorMap{E,1,3}, + Q::EnlargedCorner, + Q_next::EnlargedCorner, +) where {E<:ElementarySpace} + isqS = sdiag_inv_sqrt(S) + P_left = left_projector(Q.E_1, Q.C, Q.E_2, V, isqS, Q.ket, Q.bra) + P_right = right_projector( + Q_next.E_1, Q_next.C, Q_next.E_2, U, isqS, Q_next.ket, Q_next.bra + ) + return P_left, P_right +end + # ======================================================================================== # # Renormalization step # ======================================================================================== # @@ -375,41 +463,3 @@ function ctmrg_renormalize(enlarged_envs, projectors, state, envs, ::Simultaneou return CTMRGEnv(copy(corners), copy(edges)) end - -# ======================================================================================== # -# Auxiliary routines -# ======================================================================================== # - -""" - enlarge_corner(::Val{<:Int}, (r, c), envs, state, alg::CTMRG) - -Enlarge a CTMRG corner into the one of four directions `Val(1)` (NW), `Val(2)` (NE), -`Val(3)` (SE) or `Val(4)` (SW). This serves as a wrapper that can dispatch on the -directional index and different `CTMRG` algorithms. -""" -function enlarge_corner(::Val{1}, (r, c), envs, state, alg::CTMRG) - return enlarge_northwest_corner((r, c), envs, state) -end -function enlarge_corner(::Val{2}, (r, c), envs, state, alg::CTMRG) - return enlarge_northeast_corner((r, c), envs, state) -end -function enlarge_corner(::Val{3}, (r, c), envs, state, alg::CTMRG) - return enlarge_southeast_corner((r, c), envs, state) -end -function enlarge_corner(::Val{4}, (r, c), envs, state, alg::CTMRG) - return enlarge_southwest_corner((r, c), envs, state) -end - -# Build projectors from SVD and dense enlarged corners -function build_projectors( - U::AbstractTensorMap{E,3,1}, - S::AbstractTensorMap{E,1,1}, - V::AbstractTensorMap{E,1,3}, - Q::AbstractTensorMap{E,3,3}, - Q_next::AbstractTensorMap{E,3,3}, -) where {E<:ElementarySpace} - isqS = sdiag_inv_sqrt(S) - P_left = Q_next * V' * isqS - P_right = isqS * U' * Q - return P_left, P_right -end diff --git a/src/algorithms/ctmrg/sparse_environments.jl b/src/algorithms/ctmrg/sparse_environments.jl index e79c5d84..f32842a3 100644 --- a/src/algorithms/ctmrg/sparse_environments.jl +++ b/src/algorithms/ctmrg/sparse_environments.jl @@ -1,5 +1,3 @@ -const SparseCTMRG = CTMRG{M,true} where {M} # TODO: Is this really a good way to dispatch on the sparse CTMRG methods? - # -------------------------------------------------------- # Sparse enlarged corner as building block for environment # -------------------------------------------------------- @@ -18,77 +16,22 @@ struct EnlargedCorner{Ct,E,A,A′} end """ - (Q::EnlargedCorner)(::Val{<:Int}) + (Q::EnlargedCorner)(dir::Int) -Contract enlarged corner where `Val(1)` dispatches the north-west, `Val(2)` the north-east -`Val(3)` the south-east and `Val(4)` the south-west contraction. +Contract enlarged corner where `dir` selects the correct contraction direction, +i.e. the way the environment and PEPS tensors connect. """ -(Q::EnlargedCorner)(::Val{1}) = enlarge_northwest_corner(Q.E_1, Q.C, Q.E_2, Q.ket, Q.bra) -(Q::EnlargedCorner)(::Val{2}) = enlarge_northeast_corner(Q.E_1, Q.C, Q.E_2, Q.ket, Q.bra) -(Q::EnlargedCorner)(::Val{3}) = enlarge_southeast_corner(Q.E_1, Q.C, Q.E_2, Q.ket, Q.bra) -(Q::EnlargedCorner)(::Val{4}) = enlarge_southwest_corner(Q.E_1, Q.C, Q.E_2, Q.ket, Q.bra) - -# ------------------------- -# CTMRG compatibility layer -# ------------------------- - -""" - enlarge_corner(::Val{<:Int}, (r, c), envs, state, alg::SparseCTMRG) - -Enlarge corner but return as a `EnlargedCorner` struct used in sparse CTMRG. -""" -function enlarge_corner(::Val{1}, (r, c), envs, state, alg::SparseCTMRG) - return EnlargedCorner( - envs.corners[NORTHWEST, _prev(r, end), _prev(c, end)], - envs.edges[WEST, r, _prev(c, end)], - envs.edges[NORTH, _prev(r, end), c], - state[r, c], - state[r, c], - ) -end -function enlarge_corner(::Val{2}, (r, c), envs, state, alg::SparseCTMRG) - return EnlargedCorner( - envs.corners[NORTHEAST, _prev(r, end), _next(c, end)], - envs.edges[NORTH, _prev(r, end), c], - envs.edges[EAST, r, _next(c, end)], - state[r, c], - state[r, c], - ) -end -function enlarge_corner(::Val{3}, (r, c), envs, state, alg::SparseCTMRG) - return EnlargedCorner( - envs.corners[SOUTHEAST, _next(r, end), _next(c, end)], - envs.edges[EAST, r, _next(c, end)], - envs.edges[SOUTH, _next(r, end), c], - state[r, c], - state[r, c], - ) -end -function enlarge_corner(::Val{4}, (r, c), envs, state, alg::SparseCTMRG) - return EnlargedCorner( - envs.corners[SOUTHWEST, _next(r, end), _prev(c, end)], - envs.edges[SOUTH, _next(r, end), c], - envs.edges[WEST, r, _prev(c, end)], - state[r, c], - state[r, c], - ) -end - -function build_projectors( - U::AbstractTensorMap{E,3,1}, - S::AbstractTensorMap{E,1,1}, - V::AbstractTensorMap{E,1,3}, - Q::EnlargedCorner, - Q_next::EnlargedCorner, -) where {E<:ElementarySpace} - isqS = sdiag_inv_sqrt(S) - P_left = left_projector(Q.E_1, Q.C, Q.E_2, V, isqS, Q.ket, Q.bra) - P_right = right_projector( - Q_next.E_1, Q_next.C, Q_next.E_2, U, isqS, Q_next.ket, Q_next.bra - ) - return P_left, P_right +function (Q::EnlargedCorner)(dir::Int) + if dir == NORTHWEST + return enlarge_northwest_corner(Q.E_1, Q.C, Q.E_2, Q.ket, Q.bra) + elseif dir == NORTHWEST + return enlarge_northwest_corner(Q.E_1, Q.C, Q.E_2, Q.ket, Q.bra) + elseif dir == NORTHWEST + return enlarge_northwest_corner(Q.E_1, Q.C, Q.E_2, Q.ket, Q.bra) + elseif dir == NORTHWEST + return enlarge_northwest_corner(Q.E_1, Q.C, Q.E_2, Q.ket, Q.bra) + end end - function renormalize_corner(ec::EnlargedCorner, P_left, P_right) return renormalize_corner(ec.E_1, ec.C, ec.E_2, P_left, P_right, ec.ket, ec.bra) end From 812481408cad3a88e33acdbbc03edc234215ad99 Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Mon, 7 Oct 2024 16:44:52 +0200 Subject: [PATCH 140/213] Fix renormalize methods to make CTMRG run --- src/PEPSKit.jl | 2 +- .../contractions/ctmrg_contractions.jl | 146 ++++++++++++++---- src/algorithms/ctmrg/ctmrg.jl | 31 ++-- src/algorithms/ctmrg/sparse_environments.jl | 43 +++++- test/heisenberg.jl | 2 +- 5 files changed, 171 insertions(+), 53 deletions(-) diff --git a/src/PEPSKit.jl b/src/PEPSKit.jl index 7dee4ebb..3973c6ab 100644 --- a/src/PEPSKit.jl +++ b/src/PEPSKit.jl @@ -36,8 +36,8 @@ include("environments/transferpepo_environments.jl") include("algorithms/contractions/localoperator.jl") include("algorithms/contractions/ctmrg_contractions.jl") -include("algorithms/ctmrg/ctmrg.jl") include("algorithms/ctmrg/sparse_environments.jl") +include("algorithms/ctmrg/ctmrg.jl") include("algorithms/ctmrg/gaugefix.jl") include("algorithms/toolbox.jl") diff --git a/src/algorithms/contractions/ctmrg_contractions.jl b/src/algorithms/contractions/ctmrg_contractions.jl index 10a569e7..f4d32d0b 100644 --- a/src/algorithms/contractions/ctmrg_contractions.jl +++ b/src/algorithms/contractions/ctmrg_contractions.jl @@ -331,7 +331,6 @@ end """ renormalize_corner(quadrant::AbstractTensorMap{S,3,3}, P_left, P_right) - renormalize_corner(E_1, C, E_2, P_left, P_right, ket::PEPSTensor, bra::PEPSTensor=ket) Apply projectors to each side of a quadrant. @@ -343,78 +342,154 @@ Apply projectors to each side of a quadrant. [P_right] | ``` - -Alternatively, provide the constituent tensors and perform the complete contraction. - -``` - C -- E_2 -- |~~~~~~| - | | |P_left| -- - E_1 == ket-bra == |~~~~~~| - | || - [~~P_right~~] - | -``` """ function renormalize_corner(quadrant::AbstractTensorMap{S,3,3}, P_left, P_right) where {S} return @autoopt @tensor corner[χ_in; χ_out] := P_right[χ_in; χ1 D1 D2] * quadrant[χ1 D1 D2; χ2 D3 D4] * P_left[χ2 D3 D4; χ_out] end -function renormalize_corner( - E_1, C, E_2, P_left, P_right, ket::PEPSTensor, bra::PEPSTensor=ket -) - return @autoopt @tensor corner[χ_in; χ_out] := - P_right[χ_in; χ1 D1 D2] * - E_1[χ1 D3 D4; χ2] * - C[χ2; χ3] * - E_2[χ3 D5 D6; χ4] * - ket[d; D5 D7 D1 D3] * - conj(bra[d; D6 D8 D2 D4]) * - P_left[χ4 D7 D8; χ_out] -end """ renormalize_northwest_corner((row, col), enlarged_envs::CTMRGEnv, P_left, P_right) + renormalize_northwest_corner(quadrant::AbstractTensorMap{S,3,3}, P_left, P_right) where {S} + renormalize_northwest_corner(E_west, C_northwest, E_north, P_left, P_right, ket::PEPSTensor, bra::PEPSTensor=ket) Apply `renormalize_corner` to the enlarged northwest corner. +Alternatively, provide the constituent tensors and perform the complete contraction. + +``` + C_northwest -- E_north -- |~~~~~~| + | || |P_left| -- + E_west= == ket-bra == |~~~~~~| + | || + [~~~~~P_right~~~~~] + | +``` """ function renormalize_northwest_corner((row, col), enlarged_envs, P_left, P_right) - return renormalize_corner( + return renormalize_northwest_corner( enlarged_envs[NORTHWEST, row, col], P_left[NORTH, row, col], P_right[WEST, _next(row, end), col], ) end +function renormalize_northwest_corner( + quadrant::AbstractTensorMap{S,3,3}, P_left, P_right +) where {S} + return renormalize_corner(quadrant, P_left, P_right) +end +function renormalize_northwest_corner( + E_west, C_northwest, E_north, P_left, P_right, ket::PEPSTensor, bra::PEPSTensor=ket +) + return @autoopt @tensor corner[χ_in; χ_out] := + P_right[χ_in; χ1 D1 D2] * + E_west[χ1 D3 D4; χ2] * + C_northwest[χ2; χ3] * + E_north[χ3 D5 D6; χ4] * + ket[d; D5 D7 D1 D3] * + conj(bra[d; D6 D8 D2 D4]) * + P_left[χ4 D7 D8; χ_out] +end """ renormalize_northeast_corner((row, col), enlarged_envs::CTMRGEnv, P_left, P_right) + renormalize_northwest_corner(quadrant::AbstractTensorMap{S,3,3}, P_left, P_right) where {S} + renormalize_northeast_corner(E_north, C_northeast, E_east, P_left, P_right, ket::PEPSTensor, bra::PEPSTensor=ket) Apply `renormalize_corner` to the enlarged northeast corner. +Alternatively, provide the constituent tensors and perform the complete contraction. + +``` + |~~~~~~~| -- E_north -- C_northeast + -- |P_right| || | + |~~~~~~~| == ket-bra == E_east + || | + [~~~~~~P_left~~~~~~] + | +``` """ function renormalize_northeast_corner((row, col), enlarged_envs, P_left, P_right) - return renormalize_corner( + return renormalize_northeast_corner( enlarged_envs[NORTHEAST, row, col], P_left[EAST, row, col], P_right[NORTH, row, _prev(col, end)], ) end +function renormalize_northeast_corner( + quadrant::AbstractTensorMap{S,3,3}, P_left, P_right +) where {S} + return renormalize_corner(quadrant, P_left, P_right) +end +function renormalize_northeast_corner( + E_north, C_northeast, E_east, P_left, P_right, ket::PEPSTensor, bra::PEPSTensor=ket +) + return @autoopt @tensor corner[χ_in; χ_out] := + P_right[χ_in; χ1 D1 D2] * + E_north[χ1 D3 D4; χ2] * + C_northeast[χ2; χ3] * + E_east[χ3 D5 D6; χ4] * + ket[d; D3 D5 D7 D1] * + conj(bra[d; D4 D6 D8 D2]) * + P_left[χ4 D7 D8; χ_out] +end """ renormalize_southeast_corner((row, col), enlarged_envs::CTMRGEnv, P_left, P_right) + renormalize_southeast_corner(quadrant::AbstractTensorMap{S,3,3}, P_left, P_right) where {S} + renormalize_southeast_corner(E_east, C_southeast, E_south, P_left, P_right, ket::PEPSTensor, bra::PEPSTensor=ket) Apply `renormalize_corner` to the enlarged southeast corner. +Alternatively, provide the constituent tensors and perform the complete contraction. + +``` + | + [~~~~~P_right~~~~~] + || | + |~~~~~~| == ket-bra == E_east + -- |P_left| || | + |~~~~~~| -- E_south -- C_southeast +``` """ function renormalize_southeast_corner((row, col), enlarged_envs, P_left, P_right) - return renormalize_corner( + return renormalize_southeast_corner( enlarged_envs[SOUTHEAST, row, col], P_left[SOUTH, row, col], P_right[EAST, _prev(row, end), col], ) end +function renormalize_southeast_corner( + quadrant::AbstractTensorMap{S,3,3}, P_left, P_right +) where {S} + return renormalize_corner(quadrant, P_left, P_right) +end +function renormalize_southeast_corner( + E_east, C_southeast, E_south, P_left, P_right, ket::PEPSTensor, bra::PEPSTensor=ket +) + return @autoopt @tensor corner[χ_in; χ_out] := + P_right[χ_in; χ1 D1 D2] * + E_east[χ1 D3 D4; χ2] * + C_southeast[χ2; χ3] * + E_south[χ3 D5 D6; χ4] * + ket[d; D1 D3 D5 D7] * + conj(bra[d; D2 D4 D6 D8]) * + P_left[χ4 D7 D8; χ_out] +end """ renormalize_southwest_corner((row, col), enlarged_envs::CTMRGEnv, P_left, P_right) + renormalize_southwest_corner(quadrant::AbstractTensorMap{S,3,3}, P_left, P_right) where {S} + renormalize_southwest_corner(E_south, C_southwest, E_west, P_left, P_right, ket::PEPSTensor, bra::PEPSTensor=ket) Apply `renormalize_corner` to the enlarged southwest corner. +Alternatively, provide the constituent tensors and perform the complete contraction. + +``` + | + [~~~~~P_right~~~~~] + || | + E_west == ket-bra == |~~~~~~| + | || |P_left| -- + C_southwest -- E_south -- |~~~~~~| +``` """ function renormalize_southwest_corner((row, col), enlarged_envs, P_left, P_right) return renormalize_corner( @@ -423,6 +498,23 @@ function renormalize_southwest_corner((row, col), enlarged_envs, P_left, P_right P_right[SOUTH, row, _next(col, end)], ) end +function renormalize_southwest_corner( + quadrant::AbstractTensorMap{S,3,3}, P_left, P_right +) where {S} + return renormalize_southwest_corner(quadrant, P_left, P_right) +end +function renormalize_southwest_corner( + E_south, C_southwest, E_west, P_left, P_right, ket::PEPSTensor, bra::PEPSTensor=ket +) + return @autoopt @tensor corner[χ_in; χ_out] := + P_right[χ_in; χ1 D1 D2] * + E_south[χ1 D3 D4; χ2] * + C_southwest[χ2; χ3] * + E_west[χ3 D5 D6; χ4] * + ket[d; D7 D1 D3 D5] * + conj(bra[d; D8 D2 D4 D6]) * + P_left[χ4 D7 D8; χ_out] +end """ renormalize_bottom_corner((r, c), envs, projectors) diff --git a/src/algorithms/ctmrg/ctmrg.jl b/src/algorithms/ctmrg/ctmrg.jl index 0cf3166c..20d2584d 100644 --- a/src/algorithms/ctmrg/ctmrg.jl +++ b/src/algorithms/ctmrg/ctmrg.jl @@ -188,19 +188,27 @@ ctmrg_logcancel!(log, iter, η, N) = @warnv 1 logcancel!(log, iter, η, N) """ ctmrg_expand(state, envs, alg::CTMRG{M}) + ctmrg_expand(dirs, state, envs::CTMRGEnv, alg::CTMRG) Expand the environment by absorbing a new PEPS tensor. There are two modes of expansion: `M = :sequential` and `M = :simultaneous`. The first mode expands the environment in one direction at a time, for convenience towards the left. The second mode expands the environment in all four directions simultaneously. +Alternatively, one can provide directly the `dirs` in which the environment is grown. """ -function ctmrg_expand(state, envs::CTMRGEnv, ::SequentialCTMRG) - drc_combinations = collect(Iterators.product([4, 1], axes(state)...)) - return map(idx -> enlarge_corner(idx, envs, state), drc_combinations) +function ctmrg_expand(state, envs::CTMRGEnv, alg::SequentialCTMRG) + return ctmrg_expand([4, 1], state, envs, alg) end -function ctmrg_expand(state, envs::CTMRGEnv, ::SimultaneousCTMRG) - drc_combinations = collect(Iterators.product(1:4, axes(state)...)) - return map(idx -> enlarge_corner(idx, envs, state), drc_combinations) +function ctmrg_expand(state, envs::CTMRGEnv, alg::SimultaneousCTMRG) + return ctmrg_expand(1:4, state, envs, alg) +end +function ctmrg_expand(dirs, state, envs::CTMRGEnv, alg::CTMRG) + drc_combinations = collect(Iterators.product(dirs, axes(state)...)) + if alg.projector_alg.sparse + return map(idx -> enlarge_corner(idx, envs, state), drc_combinations) + else # Construct quadrant densely if alg is not sparse + return map(idx -> enlarge_corner(idx, envs, state)(idx[1]), drc_combinations) + end end """ @@ -264,11 +272,6 @@ function ctmrg_projectors( P_top = Zygote.Buffer(envs.edges, Prtype, axes(envs.corners, 2), axes(envs.corners, 3)) ϵ = zero(real(scalartype(envs))) - # If CTMRG is not sparse, construct all enlarged corners densely - sparse || enlarged_envs = map(Iterators.product(axes(enlarged_envs)...)) do (dir, r, c) - return enlarged_envs[dir, r, c](dir) - end - directions = collect(Iterators.product(axes(envs.corners, 2), axes(envs.corners, 3))) # @fwdthreads for (r, c) in directions for (r, c) in directions @@ -307,11 +310,6 @@ function ctmrg_projectors( # Corner type but with real numbers S = Zygote.Buffer(U.data, tensormaptype(spacetype(C), 1, 1, real(scalartype(E)))) - # If CTMRG is not sparse, construct all enlarged corners densely - sparse || enlarged_envs = map(Iterators.product(axes(enlarged_envs)...)) do (dir, r, c) - return enlarged_envs[dir, r, c](dir) - end - ϵ = zero(real(scalartype(envs))) drc_combinations = collect(Iterators.product(axes(envs.corners)...)) @fwdthreads for (dir, r, c) in drc_combinations @@ -363,7 +361,6 @@ end """ build_projectors(U::AbstractTensorMap{E,3,1}, S::AbstractTensorMap{E,1,1}, V::AbstractTensorMap{E,1,3}, Q::AbstractTensorMap{E,3,3}, Q_next::AbstractTensorMap{E,3,3}) where {E<:ElementarySpace} - build_projectors(U::AbstractTensorMap{E,3,1}, S::AbstractTensorMap{E,1,1}, V::AbstractTensorMap{E,1,3}, Q::EnlargedCorner, Q_next::EnlargedCorner) where {E<:ElementarySpace} diff --git a/src/algorithms/ctmrg/sparse_environments.jl b/src/algorithms/ctmrg/sparse_environments.jl index f32842a3..5d076a6d 100644 --- a/src/algorithms/ctmrg/sparse_environments.jl +++ b/src/algorithms/ctmrg/sparse_environments.jl @@ -24,16 +24,45 @@ i.e. the way the environment and PEPS tensors connect. function (Q::EnlargedCorner)(dir::Int) if dir == NORTHWEST return enlarge_northwest_corner(Q.E_1, Q.C, Q.E_2, Q.ket, Q.bra) - elseif dir == NORTHWEST - return enlarge_northwest_corner(Q.E_1, Q.C, Q.E_2, Q.ket, Q.bra) - elseif dir == NORTHWEST - return enlarge_northwest_corner(Q.E_1, Q.C, Q.E_2, Q.ket, Q.bra) - elseif dir == NORTHWEST + elseif dir == NORTHEAST + return enlarge_northeast_corner(Q.E_1, Q.C, Q.E_2, Q.ket, Q.bra) + elseif dir == SOUTHEAST + return enlarge_southeast_corner(Q.E_1, Q.C, Q.E_2, Q.ket, Q.bra) + elseif dir == SOUTHWEST + return enlarge_southwest_corner(Q.E_1, Q.C, Q.E_2, Q.ket, Q.bra) + end +end +function construct_corner(Q::EnlargedCorner, dir::Int) + if dir == NORTHWEST return enlarge_northwest_corner(Q.E_1, Q.C, Q.E_2, Q.ket, Q.bra) + elseif dir == NORTHEAST + return enlarge_northeast_corner(Q.E_1, Q.C, Q.E_2, Q.ket, Q.bra) + elseif dir == SOUTHEAST + return enlarge_southeast_corner(Q.E_1, Q.C, Q.E_2, Q.ket, Q.bra) + elseif dir == SOUTHWEST + return enlarge_southwest_corner(Q.E_1, Q.C, Q.E_2, Q.ket, Q.bra) end end -function renormalize_corner(ec::EnlargedCorner, P_left, P_right) - return renormalize_corner(ec.E_1, ec.C, ec.E_2, P_left, P_right, ec.ket, ec.bra) + +function renormalize_northwest_corner(ec::EnlargedCorner, P_left, P_right) + return renormalize_northwest_corner( + ec.E_1, ec.C, ec.E_2, P_left, P_right, ec.ket, ec.bra + ) +end +function renormalize_northeast_corner(ec::EnlargedCorner, P_left, P_right) + return renormalize_northeast_corner( + ec.E_1, ec.C, ec.E_2, P_left, P_right, ec.ket, ec.bra + ) +end +function renormalize_southeast_corner(ec::EnlargedCorner, P_left, P_right) + return renormalize_southeast_corner( + ec.E_1, ec.C, ec.E_2, P_left, P_right, ec.ket, ec.bra + ) +end +function renormalize_southwest_corner(ec::EnlargedCorner, P_left, P_right) + return renormalize_southwest_corner( + ec.E_1, ec.C, ec.E_2, P_left, P_right, ec.ket, ec.bra + ) end # ------------------ diff --git a/test/heisenberg.jl b/test/heisenberg.jl index 4fa67362..8e80944e 100644 --- a/test/heisenberg.jl +++ b/test/heisenberg.jl @@ -27,7 +27,7 @@ opt_alg = PEPSOptimize(; Random.seed!(91283219347) H = heisenberg_XYZ(InfiniteSquare()) psi_init = InfinitePEPS(2, χbond) -env_init = leading_boundary(CTMRGEnv(psi_init, ComplexSpace(χenv)), psi_init, ctm_alg) +env_init = leading_boundary(CTMRGEnv(psi_init, ComplexSpace(χenv)), psi_init, ctm_alg); # find fixedpoint result = fixedpoint(psi_init, H, opt_alg, env_init) From 883781140eb153658be18b7d348eebcab060df98 Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Wed, 9 Oct 2024 11:17:37 +0200 Subject: [PATCH 141/213] Fix CTMRG miniter condition --- src/algorithms/ctmrg/ctmrg.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/algorithms/ctmrg/ctmrg.jl b/src/algorithms/ctmrg/ctmrg.jl index bba37f25..1d6b94e4 100644 --- a/src/algorithms/ctmrg/ctmrg.jl +++ b/src/algorithms/ctmrg/ctmrg.jl @@ -118,7 +118,7 @@ function MPSKit.leading_boundary(envinit, state, alg::CTMRG) N = norm(state, env) ctmrg_logiter!(log, iter, η, N) - if η ≤ alg.tol + if η ≤ alg.tol && iter ≥ alg.miniter ctmrg_logfinish!(log, iter, η, N) break end From d9b8af5c486b2ae8edf0ec095ac17934874e1c80 Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Wed, 9 Oct 2024 11:52:57 +0200 Subject: [PATCH 142/213] Fix Accessors.@set for CTMRG struct --- src/algorithms/ctmrg/ctmrg.jl | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/algorithms/ctmrg/ctmrg.jl b/src/algorithms/ctmrg/ctmrg.jl index 1d6b94e4..ee1c5262 100644 --- a/src/algorithms/ctmrg/ctmrg.jl +++ b/src/algorithms/ctmrg/ctmrg.jl @@ -91,6 +91,9 @@ ctmrgscheme(::CTMRG{S}) where {S} = S const SequentialCTMRG = CTMRG{:sequential} const SimultaneousCTMRG = CTMRG{:simultaneous} +# supply correct constructor for Accessors.@set +Accessors.constructorof(::Type{CTMRG{S}}) where {S} = CTMRG{S} + """ MPSKit.leading_boundary([envinit], state, alg::CTMRG) From 3690c0f4fac53da84ae0a7e5f2747e24c5014c47 Mon Sep 17 00:00:00 2001 From: Lukas <37111893+lkdvos@users.noreply.github.com> Date: Tue, 15 Oct 2024 16:49:53 +0200 Subject: [PATCH 143/213] FiniteDifferences `to_vec` support (#67) * Add FiniteDifferences * Remove legacy finitedifferences code --- Project.toml | 1 + src/PEPSKit.jl | 1 + src/states/infinitepeps.jl | 10 ++++++++++ test/utility.jl | 23 ----------------------- 4 files changed, 12 insertions(+), 23 deletions(-) diff --git a/Project.toml b/Project.toml index aa1fc857..73c8a1c9 100644 --- a/Project.toml +++ b/Project.toml @@ -7,6 +7,7 @@ version = "0.2.1" Accessors = "7d9f7c33-5ae7-4f3b-8dc6-eff91059b697" ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" Compat = "34da2185-b29b-5c13-b0c7-acf172513d20" +FiniteDifferences = "26cc04aa-876d-5657-8c51-4c34ba976000" KrylovKit = "0b1a1467-8014-51b9-945f-bf0ae24f4b77" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" LoggingExtras = "e6f89c97-d47a-5376-807f-9c37f3926c36" diff --git a/src/PEPSKit.jl b/src/PEPSKit.jl index 5a4fdb0a..c2ffeea6 100644 --- a/src/PEPSKit.jl +++ b/src/PEPSKit.jl @@ -10,6 +10,7 @@ using ChainRulesCore, Zygote using LoggingExtras using MPSKit: loginit!, logiter!, logfinish!, logcancel! using MPSKitModels +using FiniteDifferences include("utility/util.jl") include("utility/svd.jl") diff --git a/src/states/infinitepeps.jl b/src/states/infinitepeps.jl index 0a7d63aa..a57af615 100644 --- a/src/states/infinitepeps.jl +++ b/src/states/infinitepeps.jl @@ -203,3 +203,13 @@ function ChainRulesCore.rrule(::typeof(rotr90), peps::InfinitePEPS) end return peps′, rotr90_pullback end + +# FiniteDifferences +# Makes use of tensors already having a to_vec method +function FiniteDifferences.to_vec(state::InfinitePEPS) + vec, back = FiniteDifferences.to_vec(state.A) + function state_from_vec(vec) + return InfinitePEPS(back(vec)) + end + return vec, state_from_vec +end diff --git a/test/utility.jl b/test/utility.jl index 383113da..1ca9bd69 100644 --- a/test/utility.jl +++ b/test/utility.jl @@ -53,26 +53,3 @@ function ChainRulesTestUtils.test_approx( ) end end - -# TODO: remove these functions once TensorKit is updated -function FiniteDifferences.to_vec(t::T) where {T<:TensorKit.TrivialTensorMap} - vec, from_vec = to_vec(t.data) - return vec, x -> T(from_vec(x), codomain(t), domain(t)) -end -function FiniteDifferences.to_vec(t::AbstractTensorMap) - # convert to vector of vectors to make use of existing functionality - vec_of_vecs = [b * sqrtdim(c) for (c, b) in blocks(t)] - vec, back = FiniteDifferences.to_vec(vec_of_vecs) - - function from_vec(x) - t′ = similar(t) - xvec_of_vecs = back(x) - for (i, (c, b)) in enumerate(blocks(t′)) - scale!(b, xvec_of_vecs[i], isqrtdim(c)) - end - return t′ - end - - return vec, from_vec -end -FiniteDifferences.to_vec(t::TensorKit.AdjointTensorMap) = to_vec(copy(t)) From 15256d090541285fcd361379a58f4f55a382088e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 16 Oct 2024 08:58:26 +0200 Subject: [PATCH 144/213] CompatHelper: add new compat entry for FiniteDifferences at version 0.12, (keep existing compat) (#75) Co-authored-by: CompatHelper Julia --- Project.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Project.toml b/Project.toml index 73c8a1c9..231e5f0c 100644 --- a/Project.toml +++ b/Project.toml @@ -26,6 +26,7 @@ Zygote = "e88e6eb3-aa80-5325-afca-941959d7151f" Accessors = "0.1" ChainRulesCore = "1.0" Compat = "3.46, 4.2" +FiniteDifferences = "0.12" KrylovKit = "0.8" LinearAlgebra = "1" LoggingExtras = "1" From ee68ea449aea4595d2dd6eedec71fca1ddb25277 Mon Sep 17 00:00:00 2001 From: Lukas <37111893+lkdvos@users.noreply.github.com> Date: Wed, 16 Oct 2024 10:27:26 +0200 Subject: [PATCH 145/213] Bump version 0.2.2 --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 231e5f0c..5450bbf3 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "PEPSKit" uuid = "52969e89-939e-4361-9b68-9bc7cde4bdeb" authors = ["Maarten Vandamme", "Paul Brehmer", "Lander Burgelman", "Rui-Zhen Huang", "Daan Maertens", "Lukas Devos Date: Wed, 16 Oct 2024 16:11:35 +0200 Subject: [PATCH 146/213] Zenodo link and citation file (#76) * Add zenodo links * Add citation file --- CITATION.cff | 18 ++++++++++++++++++ README.md | 5 ++++- 2 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 CITATION.cff diff --git a/CITATION.cff b/CITATION.cff new file mode 100644 index 00000000..e62dcfde --- /dev/null +++ b/CITATION.cff @@ -0,0 +1,18 @@ +cff-version: 1.2.0 +message: "If you use this software, please cite it as below." +authors: + - family-names: Brehmer + given-names: Paul + orcid: https://orcid.org/0009-0007-9953-2929 + - family-names: "Burgelman" + given-names: "Lander" + orcid: "https://orcid.org/0000-0003-1724-5330" + - family-names: "Devos" + given-names: "Lukas" + orcid: "https://orcid.org/0000-0002-0256-4200" +title: "PEPSKit" +version: 0.2.2 +identifiers: + - type: doi + value: 10.5281/zenodo.13938737 +date-released: 2021-10-16 diff --git a/README.md b/README.md index 38d173eb..e44a3ac3 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ # PEPSKit.jl -[![docs][docs-dev-img]][docs-dev-url] ![CI][ci-url] [![codecov][codecov-img]][codecov-url] +[![docs][docs-dev-img]][docs-dev-url] ![CI][ci-url] [![codecov][codecov-img]][codecov-url] [![DOI][doi-img]](doi-url) [docs-dev-img]: https://img.shields.io/badge/docs-dev-blue.svg [docs-dev-url]: https://QuantumKitHub.github.io/PEPSKit.jl/dev/ @@ -15,6 +15,9 @@ [ci-url]: https://github.com/QuantumKitHub/PEPSKit.jl/workflows/CI/badge.svg +[doi-url]: https://doi.org/10.5281/zenodo.13938736 +[doi-img]: https://zenodo.org/badge/DOI/10.5281/zenodo.13938737.svg + **Tools for working with projected entangled-pair states** It contracts, it optimizes, it may break. From 4f553508bce2c77f479fca5400e08b9a6c1f905d Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Thu, 17 Oct 2024 00:35:42 +0200 Subject: [PATCH 147/213] Fix Zenodo link (#77) * Add zenodo links * Add citation file * Fix Zenodo DOI link --------- Co-authored-by: lkdvos --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e44a3ac3..ebb55861 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ # PEPSKit.jl -[![docs][docs-dev-img]][docs-dev-url] ![CI][ci-url] [![codecov][codecov-img]][codecov-url] [![DOI][doi-img]](doi-url) +[![docs][docs-dev-img]][docs-dev-url] ![CI][ci-url] [![codecov][codecov-img]][codecov-url] [![DOI][doi-img]][doi-url] [docs-dev-img]: https://img.shields.io/badge/docs-dev-blue.svg [docs-dev-url]: https://QuantumKitHub.github.io/PEPSKit.jl/dev/ From 0f2ca223fa7fbb86497de915c655dcc98ead01a8 Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Thu, 24 Oct 2024 10:56:54 +0200 Subject: [PATCH 148/213] Remove sparse args and fix AD --- src/algorithms/ctmrg/ctmrg.jl | 49 +++++++++++++++++++---------------- 1 file changed, 27 insertions(+), 22 deletions(-) diff --git a/src/algorithms/ctmrg/ctmrg.jl b/src/algorithms/ctmrg/ctmrg.jl index 20d2584d..3568c4bd 100644 --- a/src/algorithms/ctmrg/ctmrg.jl +++ b/src/algorithms/ctmrg/ctmrg.jl @@ -8,20 +8,17 @@ have different spaces, this truncation style is different from `TruncationSpace` struct FixedSpaceTruncation <: TensorKit.TruncationScheme end """ - struct ProjectorAlg{S}(; svd_alg=Defaults.svd_alg, trscheme=Defaults.trscheme, - sparse=Defaults.sparse, verbosity=0) + struct ProjectorAlg{S}(; svd_alg=Defaults.svd_alg, trscheme=Defaults.trscheme, verbosity=0) Algorithm struct collecting all projector related parameters. The `svd_alg` sets the SVD algorithm for decomposing the CTM environment. The truncation scheme has to be a `TensorKit.TruncationScheme`, and some SVD algorithms might have further restrictions -on what kind of truncation scheme can be used. If `sparse` is `true`, then the enlarged corners and -CTM environment are never computed densely and function handles are using for the SVD. +on what kind of truncation scheme can be used. """ @kwdef struct ProjectorAlg{S<:SVDAdjoint,T} svd_alg::S = Defaults.svd_alg trscheme::T = Defaults.trscheme - sparse::Bool = Defaults.sparse verbosity::Int = 0 end # TODO: add option for different projector styles (half-infinite, full-infinite, etc.) @@ -48,7 +45,7 @@ end CTMRG(; tol=Defaults.ctmrg_tol, maxiter=Defaults.ctmrg_maxiter, miniter=Defaults.ctmrg_miniter, verbosity=0, svd_alg=SVDAdjoint(), trscheme=FixedSpaceTruncation(), - ctmrgscheme=Defaults.ctmrgscheme, sparse=Defaults.sparse) + ctmrgscheme=Defaults.ctmrgscheme) Algorithm struct that represents the CTMRG algorithm for contracting infinite PEPS. Each CTMRG run is converged up to `tol` where the singular value convergence of the @@ -65,10 +62,6 @@ CTMRG is implemented. It can either be `:sequential`, where the projectors are s computed on the western side, and then applied and rotated. Or with `:simultaneous` all projectors are computed and applied simultaneously on all sides, where in particular the corners get contracted with two projectors at the same time. - -The contraction and SVD of the CTMRG environments can be performed densely, or sparsely -if `sparse` is `true`. If sparsity is enabled, then the SVD will use only function handles -and the full enlarged corners and environments are never explicitly constructed. """ struct CTMRG{S} tol::Float64 @@ -85,14 +78,13 @@ function CTMRG(; svd_alg=Defaults.svd_alg, trscheme=Defaults.trscheme, ctmrgscheme::Symbol=Defaults.ctmrgscheme, - sparse::Bool=Defaults.sparse, ) return CTMRG{ctmrgscheme}( tol, maxiter, miniter, verbosity, - ProjectorAlg(; svd_alg, trscheme, sparse, verbosity), + ProjectorAlg(; svd_alg, trscheme, verbosity), ) end @@ -188,7 +180,7 @@ ctmrg_logcancel!(log, iter, η, N) = @warnv 1 logcancel!(log, iter, η, N) """ ctmrg_expand(state, envs, alg::CTMRG{M}) - ctmrg_expand(dirs, state, envs::CTMRGEnv, alg::CTMRG) + ctmrg_expand(dirs, state, envs::CTMRGEnv) Expand the environment by absorbing a new PEPS tensor. There are two modes of expansion: `M = :sequential` and `M = :simultaneous`. @@ -196,19 +188,32 @@ The first mode expands the environment in one direction at a time, for convenien the left. The second mode expands the environment in all four directions simultaneously. Alternatively, one can provide directly the `dirs` in which the environment is grown. """ -function ctmrg_expand(state, envs::CTMRGEnv, alg::SequentialCTMRG) - return ctmrg_expand([4, 1], state, envs, alg) +function ctmrg_expand(state, envs::CTMRGEnv, ::SequentialCTMRG) + return ctmrg_expand([4, 1], state, envs) end -function ctmrg_expand(state, envs::CTMRGEnv, alg::SimultaneousCTMRG) - return ctmrg_expand(1:4, state, envs, alg) +function ctmrg_expand(state, envs::CTMRGEnv, ::SimultaneousCTMRG) + return ctmrg_expand(1:4, state, envs) end -function ctmrg_expand(dirs, state, envs::CTMRGEnv, alg::CTMRG) +# function ctmrg_expand(dirs, state, envs::CTMRGEnv) # TODO: This doesn't AD due to length(::Nothing)... +# drc_combinations = collect(Iterators.product(dirs, axes(state)...)) +# return map(idx -> enlarge_corner(idx, envs, state)(idx[1]), drc_combinations) +# end +function ctmrg_expand(dirs, state, envs::CTMRGEnv{C,T}) where {C,T} + Qtype = tensormaptype(spacetype(C), 3, 3, storagetype(C)) + Q = Zygote.Buffer(Array{Qtype,3}(undef, size(envs.corners))) drc_combinations = collect(Iterators.product(dirs, axes(state)...)) - if alg.projector_alg.sparse - return map(idx -> enlarge_corner(idx, envs, state), drc_combinations) - else # Construct quadrant densely if alg is not sparse - return map(idx -> enlarge_corner(idx, envs, state)(idx[1]), drc_combinations) + @fwdthreads for (dir, r, c) in drc_combinations + Q[dir, r, c] = if dir == NORTHWEST + enlarge_northwest_corner((r, c), envs, state) + elseif dir == NORTHEAST + enlarge_northeast_corner((r, c), envs, state) + elseif dir == SOUTHEAST + enlarge_southeast_corner((r, c), envs, state) + elseif dir == SOUTHWEST + enlarge_southwest_corner((r, c), envs, state) + end end + return copy(Q) end """ From c2c37c3454fbbab236d792724d868f3a1b335ee2 Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Thu, 24 Oct 2024 11:24:59 +0200 Subject: [PATCH 149/213] Use enlarge_corner in ctmrg_expand --- src/algorithms/ctmrg/ctmrg.jl | 16 +++++----------- src/algorithms/ctmrg/sparse_environments.jl | 11 ----------- 2 files changed, 5 insertions(+), 22 deletions(-) diff --git a/src/algorithms/ctmrg/ctmrg.jl b/src/algorithms/ctmrg/ctmrg.jl index 3568c4bd..69c82ac2 100644 --- a/src/algorithms/ctmrg/ctmrg.jl +++ b/src/algorithms/ctmrg/ctmrg.jl @@ -200,28 +200,22 @@ end # end function ctmrg_expand(dirs, state, envs::CTMRGEnv{C,T}) where {C,T} Qtype = tensormaptype(spacetype(C), 3, 3, storagetype(C)) + # Qtype = EnlargedCorner{C,T,eltype(state),eltype(state)} Q = Zygote.Buffer(Array{Qtype,3}(undef, size(envs.corners))) drc_combinations = collect(Iterators.product(dirs, axes(state)...)) @fwdthreads for (dir, r, c) in drc_combinations - Q[dir, r, c] = if dir == NORTHWEST - enlarge_northwest_corner((r, c), envs, state) - elseif dir == NORTHEAST - enlarge_northeast_corner((r, c), envs, state) - elseif dir == SOUTHEAST - enlarge_southeast_corner((r, c), envs, state) - elseif dir == SOUTHWEST - enlarge_southwest_corner((r, c), envs, state) - end + ec = enlarge_corner((dir, r, c), state, envs) + Q[dir, r, c] = ec(dir) # Explicitly construct EnlargedCorner for now end return copy(Q) end """ - enlarge_corner((dir, r, c), envs, state) + enlarge_corner((dir, r, c), state, envs) Enlarge corner by constructing the corresponding `EnlargedCorner` struct. """ -function enlarge_corner((dir, r, c), envs, state) +function enlarge_corner((dir, r, c), state, envs) if dir == NORTHWEST return EnlargedCorner( envs.corners[NORTHWEST, _prev(r, end), _prev(c, end)], diff --git a/src/algorithms/ctmrg/sparse_environments.jl b/src/algorithms/ctmrg/sparse_environments.jl index 5d076a6d..ca99f2a3 100644 --- a/src/algorithms/ctmrg/sparse_environments.jl +++ b/src/algorithms/ctmrg/sparse_environments.jl @@ -32,17 +32,6 @@ function (Q::EnlargedCorner)(dir::Int) return enlarge_southwest_corner(Q.E_1, Q.C, Q.E_2, Q.ket, Q.bra) end end -function construct_corner(Q::EnlargedCorner, dir::Int) - if dir == NORTHWEST - return enlarge_northwest_corner(Q.E_1, Q.C, Q.E_2, Q.ket, Q.bra) - elseif dir == NORTHEAST - return enlarge_northeast_corner(Q.E_1, Q.C, Q.E_2, Q.ket, Q.bra) - elseif dir == SOUTHEAST - return enlarge_southeast_corner(Q.E_1, Q.C, Q.E_2, Q.ket, Q.bra) - elseif dir == SOUTHWEST - return enlarge_southwest_corner(Q.E_1, Q.C, Q.E_2, Q.ket, Q.bra) - end -end function renormalize_northwest_corner(ec::EnlargedCorner, P_left, P_right) return renormalize_northwest_corner( From ec9195a47663e1cf02e1cc1b5ed90ecb0f5a072c Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Thu, 24 Oct 2024 14:20:01 +0200 Subject: [PATCH 150/213] Format and remove HalfInfiniteEnv TensorKit methods --- src/algorithms/ctmrg/ctmrg.jl | 7 +-- src/algorithms/ctmrg/sparse_environments.jl | 60 ++------------------- 2 files changed, 6 insertions(+), 61 deletions(-) diff --git a/src/algorithms/ctmrg/ctmrg.jl b/src/algorithms/ctmrg/ctmrg.jl index 69c82ac2..59f4b969 100644 --- a/src/algorithms/ctmrg/ctmrg.jl +++ b/src/algorithms/ctmrg/ctmrg.jl @@ -80,11 +80,7 @@ function CTMRG(; ctmrgscheme::Symbol=Defaults.ctmrgscheme, ) return CTMRG{ctmrgscheme}( - tol, - maxiter, - miniter, - verbosity, - ProjectorAlg(; svd_alg, trscheme, verbosity), + tol, maxiter, miniter, verbosity, ProjectorAlg(; svd_alg, trscheme, verbosity) ) end @@ -200,7 +196,6 @@ end # end function ctmrg_expand(dirs, state, envs::CTMRGEnv{C,T}) where {C,T} Qtype = tensormaptype(spacetype(C), 3, 3, storagetype(C)) - # Qtype = EnlargedCorner{C,T,eltype(state),eltype(state)} Q = Zygote.Buffer(Array{Qtype,3}(undef, size(envs.corners))) drc_combinations = collect(Iterators.product(dirs, axes(state)...)) @fwdthreads for (dir, r, c) in drc_combinations diff --git a/src/algorithms/ctmrg/sparse_environments.jl b/src/algorithms/ctmrg/sparse_environments.jl index ca99f2a3..c6b826b4 100644 --- a/src/algorithms/ctmrg/sparse_environments.jl +++ b/src/algorithms/ctmrg/sparse_environments.jl @@ -63,7 +63,7 @@ end Half-infinite CTMRG environment tensor storage. """ -struct HalfInfiniteEnv{C,E,A,A′} +struct HalfInfiniteEnv{C,E,A,A′} # TODO: subtype as AbstractTensorMap once TensorKit is updated C_1::C C_2::C E_1::E @@ -159,68 +159,18 @@ function halfinfinite_environment(ec_1::EnlargedCorner, ec_2::EnlargedCorner) ) end -# ------------------------------------------------------------------------ -# Methods to make environment compatible with IterSVD and its reverse-rule -# ------------------------------------------------------------------------ - -TensorKit.InnerProductStyle(::HalfInfiniteEnv) = EuclideanProduct() -TensorKit.sectortype(::HalfInfiniteEnv) = Trivial -TensorKit.storagetype(env::HalfInfiniteEnv) = storagetype(env.ket_1) -TensorKit.spacetype(env::HalfInfiniteEnv) = spacetype(env.ket_1) +# ----------------------------------------------------- +# AbstractTensorMap subtyping and IterSVD compatibility +# ----------------------------------------------------- function TensorKit.domain(env::HalfInfiniteEnv) return domain(env.E_4) * domain(env.ket_2)[3] * domain(env.bra_2)[3]' end + function TensorKit.codomain(env::HalfInfiniteEnv) return codomain(env.E_1)[1] * domain(env.ket_1)[3]' * domain(env.bra_1)[3] end -function TensorKit.space(env::HalfInfiniteEnv) - return codomain(env) ← domain(env) -end -function TensorKit.blocks(env::HalfInfiniteEnv) - return TensorKit.SingletonDict(Trivial() => env) -end -function TensorKit.blocksectors(::HalfInfiniteEnv) - return TensorKit.OneOrNoneIterator{Trivial}(true, Trivial()) -end -function TensorKit.block(env::HalfInfiniteEnv, c::Sector) - return env -end -function TensorKit.tsvd!(f::HalfInfiniteEnv; trunc=NoTruncation(), p::Real=2, alg=IterSVD()) - return _tsvd!(f, alg, trunc, p) -end -function TensorKit.MatrixAlgebra.svd!(env::HalfInfiniteEnv, args...) - return TensorKit.MatrixAlgebra.svd!(env(), args...) # Construct environment densely as fallback -end - -Base.eltype(env::HalfInfiniteEnv) = eltype(env.ket_1) -function Base.size(env::HalfInfiniteEnv) # Treat environment as matrix - χ_in = dim(space(env.E_1, 1)) - D_inabove = dim(space(env.ket_1, 2)) - D_inbelow = dim(space(env.bra_1, 2)) - χ_out = dim(space(env.E_4, 1)) - D_outabove = dim(space(env.ket_2, 2)) - D_outbelow = dim(space(env.bra_2, 2)) - return (χ_in * D_inabove * D_inbelow, χ_out * D_outabove * D_outbelow) -end -Base.size(env::HalfInfiniteEnv, i::Int) = size(env)[i] -VectorInterface.scalartype(env::HalfInfiniteEnv) = scalartype(env.ket_1) function random_start_vector(env::HalfInfiniteEnv) return Tensor(randn, domain(env)) end - -function Base.similar(env::HalfInfiniteEnv) - return HalfInfiniteEnv( - (similar(getfield(env, field)) for field in fieldnames(HalfInfiniteEnv))... - ) -end - -function Base.copyto!(dest::HalfInfiniteEnv, src::HalfInfiniteEnv) - for field in fieldnames(HalfInfiniteEnv) - for (bd, bs) in zip(blocks(getfield(dest, field)), blocks(getfield(src, field))) - copyto!(bd[2], bs[2]) - end - end - return dest -end From 77259d7edfab792cf763b89c04303ca16269b6d6 Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Thu, 24 Oct 2024 17:21:24 +0200 Subject: [PATCH 151/213] Fix wrong daggers in relative gauge fix contractions --- .../contractions/ctmrg_contractions.jl | 16 ++++++++-------- src/utility/svd.jl | 7 +------ test/ctmrg/fixed_iterscheme.jl | 4 +--- 3 files changed, 10 insertions(+), 17 deletions(-) diff --git a/src/algorithms/contractions/ctmrg_contractions.jl b/src/algorithms/contractions/ctmrg_contractions.jl index f4d32d0b..e53bf2ac 100644 --- a/src/algorithms/contractions/ctmrg_contractions.jl +++ b/src/algorithms/contractions/ctmrg_contractions.jl @@ -872,7 +872,7 @@ end Multiply north left singular vectors with gauge signs from the right. """ function fix_gauge_north_left_vecs((row, col), U, signs) - return U[NORTH, row, col] * signs[NORTH, row, _next(col, end)] + return U[NORTH, row, col] * signs[NORTH, row, _next(col, end)]' end """ @@ -881,7 +881,7 @@ end Multiply east left singular vectors with gauge signs from the right. """ function fix_gauge_east_left_vecs((row, col), U, signs) - return U[EAST, row, col] * signs[EAST, _next(row, end), col] + return U[EAST, row, col] * signs[EAST, _next(row, end), col]' end """ @@ -890,7 +890,7 @@ end Multiply south left singular vectors with gauge signs from the right. """ function fix_gauge_south_left_vecs((row, col), U, signs) - return U[SOUTH, row, col] * signs[SOUTH, row, _prev(col, end)] + return U[SOUTH, row, col] * signs[SOUTH, row, _prev(col, end)]' end """ @@ -899,7 +899,7 @@ end Multiply west left singular vectors with gauge signs from the right. """ function fix_gauge_west_left_vecs((row, col), U, signs) - return U[WEST, row, col] * signs[WEST, _prev(row, end), col] + return U[WEST, row, col] * signs[WEST, _prev(row, end), col]' end # right singular vectors @@ -910,7 +910,7 @@ end Multiply north right singular vectors with gauge signs from the left. """ function fix_gauge_north_right_vecs((row, col), V, signs) - return signs[NORTH, row, _next(col, end)]' * V[NORTH, row, col] + return signs[NORTH, row, _next(col, end)] * V[NORTH, row, col] end """ @@ -919,7 +919,7 @@ end Multiply east right singular vectors with gauge signs from the left. """ function fix_gauge_east_right_vecs((row, col), V, signs) - return signs[EAST, _next(row, end), col]' * V[EAST, row, col] + return signs[EAST, _next(row, end), col] * V[EAST, row, col] end """ @@ -928,7 +928,7 @@ end Multiply south right singular vectors with gauge signs from the left. """ function fix_gauge_south_right_vecs((row, col), V, signs) - return signs[SOUTH, row, _prev(col, end)]' * V[SOUTH, row, col] + return signs[SOUTH, row, _prev(col, end)] * V[SOUTH, row, col] end """ @@ -937,5 +937,5 @@ end Multiply west right singular vectors with gauge signs from the left. """ function fix_gauge_west_right_vecs((row, col), V, signs) - return signs[WEST, _prev(row, end), col]' * V[WEST, row, col] + return signs[WEST, _prev(row, end), col] * V[WEST, row, col] end diff --git a/src/utility/svd.jl b/src/utility/svd.jl index d439b4f6..8b946f51 100644 --- a/src/utility/svd.jl +++ b/src/utility/svd.jl @@ -70,13 +70,8 @@ the iterative SVD didn't converge, the algorithm falls back to a dense SVD. start_vector = random_start_vector end -# TODO: find better initial guess that leads to element-wise convergence and is compatible with function handles function random_start_vector(t::Matrix) - return randn(eltype(t), size(t, 1)) # Leads to erroneous gauge fixing of U, S, V and thus failing element-wise conv. - # u, = TensorKit.MatrixAlgebra.svd!(deepcopy(t), TensorKit.SVD()) - # return sum(u[:, i] for i in 1:howmany) # Element-wise convergence works fine - # return dropdims(sum(t[:, 1:3]; dims=2); dims=2) # Summing too many columns also makes gauge fixing fail - # return t[:, 1] # Leads so slower convergence of SVD than randn, but correct element-wise convergence + return randn(eltype(t), size(t, 1)) end # Compute SVD data block-wise using KrylovKit algorithm diff --git a/test/ctmrg/fixed_iterscheme.jl b/test/ctmrg/fixed_iterscheme.jl index 871e8711..49a1568a 100644 --- a/test/ctmrg/fixed_iterscheme.jl +++ b/test/ctmrg/fixed_iterscheme.jl @@ -1,5 +1,6 @@ using Test using Random +using LinearAlgebra using TensorKit, KrylovKit using PEPSKit using PEPSKit: @@ -66,7 +67,6 @@ end psi = InfinitePEPS(2, χbond) env_init = CTMRGEnv(psi, ComplexSpace(χenv)) env_conv1 = leading_boundary(env_init, psi, ctm_alg_iter) - # env_conv1 = leading_boundary(env_init, psi, ctm_alg_full); # do extra iteration to get SVD env_conv2_iter, info_iter = ctmrg_iter(psi, env_conv1, ctm_alg_iter) @@ -94,12 +94,10 @@ end env_fixedsvd_iter, = ctmrg_iter(psi, env_conv1, ctm_alg_fix_iter) env_fixedsvd_iter = fix_global_phases(env_conv1, env_fixedsvd_iter) @test calc_elementwise_convergence(env_conv1, env_fixedsvd_iter) ≈ 0 atol = 1e-6 # This doesn't work for x₀ = rand(size(b, 1))? - # @show calc_elementwise_convergence(env_conv1, env_fixedsvd_iter) env_fixedsvd_full, = ctmrg_iter(psi, env_conv1, ctm_alg_fix_full) env_fixedsvd_full = fix_global_phases(env_conv1, env_fixedsvd_full) @test calc_elementwise_convergence(env_conv1, env_fixedsvd_full) ≈ 0 atol = 1e-6 - # @show calc_elementwise_convergence(env_conv1, env_fixedsvd_full) # check matching decompositions atol = 1e-12 From bb4af77bfe9dea62de3b30dc3c078a17dfdd166a Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Thu, 24 Oct 2024 17:45:29 +0200 Subject: [PATCH 152/213] Fix :sequential mode in ctmrg_expand --- src/algorithms/ctmrg/ctmrg.jl | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/algorithms/ctmrg/ctmrg.jl b/src/algorithms/ctmrg/ctmrg.jl index 59f4b969..6e708857 100644 --- a/src/algorithms/ctmrg/ctmrg.jl +++ b/src/algorithms/ctmrg/ctmrg.jl @@ -196,11 +196,12 @@ end # end function ctmrg_expand(dirs, state, envs::CTMRGEnv{C,T}) where {C,T} Qtype = tensormaptype(spacetype(C), 3, 3, storagetype(C)) - Q = Zygote.Buffer(Array{Qtype,3}(undef, size(envs.corners))) - drc_combinations = collect(Iterators.product(dirs, axes(state)...)) - @fwdthreads for (dir, r, c) in drc_combinations - ec = enlarge_corner((dir, r, c), state, envs) - Q[dir, r, c] = ec(dir) # Explicitly construct EnlargedCorner for now + Q = Zygote.Buffer(Array{Qtype,3}(undef, length(dirs), size(state)...)) + dirs_enum = [(i, dir) for (i, dir) in enumerate(dirs)] + drc_combinations = collect(Iterators.product(dirs_enum, axes(state)...)) + @fwdthreads for (d, r, c) in drc_combinations + ec = enlarge_corner((d[2], r, c), state, envs) + Q[d[1], r, c] = ec(d[2]) # Explicitly construct EnlargedCorner for now end return copy(Q) end From 6e1a32f8570133d5e1e569eb30cfe00ee7d81ffa Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Thu, 24 Oct 2024 18:38:38 +0200 Subject: [PATCH 153/213] Add _safe_inv to sdiag_inv_sqrt and add field to ProjectorAlg --- src/PEPSKit.jl | 2 ++ src/algorithms/ctmrg/ctmrg.jl | 13 ++++++++++--- src/utility/util.jl | 17 ++++++++++++----- 3 files changed, 24 insertions(+), 8 deletions(-) diff --git a/src/PEPSKit.jl b/src/PEPSKit.jl index c2ffeea6..12f430b1 100644 --- a/src/PEPSKit.jl +++ b/src/PEPSKit.jl @@ -62,6 +62,7 @@ Module containing default values that represent typical algorithm parameters. - `ctmrg_tol = 1e-12`: Tolerance checking singular value and norm convergence - `fpgrad_maxiter = 100`: Maximal number of iterations for computing the CTMRG fixed-point gradient - `fpgrad_tol = 1e-6`: Convergence tolerance for the fixed-point gradient iteration +- `inv_sqrt_tol = 1e-2ctmrg_tol`: Tolerance for cutting small values before sqrt-inverting """ module Defaults using TensorKit, KrylovKit, OptimKit @@ -83,6 +84,7 @@ module Defaults maxiter=Defaults.fpgrad_maxiter, tol=Defaults.fpgrad_tol ) const gradient_alg = LinSolver(; solver=gradient_linsolver, iterscheme) + const inv_sqrt_tol = 1e-2ctmrg_tol end export SVDAdjoint, IterSVD, NonTruncSVDAdjoint diff --git a/src/algorithms/ctmrg/ctmrg.jl b/src/algorithms/ctmrg/ctmrg.jl index ee1c5262..36ca6b08 100644 --- a/src/algorithms/ctmrg/ctmrg.jl +++ b/src/algorithms/ctmrg/ctmrg.jl @@ -20,6 +20,7 @@ environment direction/unit cell entry. @kwdef struct ProjectorAlg{S<:SVDAdjoint,T} svd_alg::S = Defaults.svd_alg trscheme::T = Defaults.trscheme + inv_sqrt_tol::Float64 = Defaults.inv_sqrt_tol verbosity::Int = 0 end # TODO: add option for different projector styles (half-infinite, full-infinite, etc.) @@ -258,7 +259,7 @@ function ctmrg_projectors( # Compute projectors P_bottom[r, c], P_top[r, c] = build_projectors( - U, S, V, enlarged_envs[1][r, c], enlarged_envs[2][r′, c] + U, S, V, enlarged_envs[1][r, c], enlarged_envs[2][r′, c], projector_alg ) end @@ -316,6 +317,7 @@ function ctmrg_projectors( V_local, enlarged_envs[dir, r, c], enlarged_envs[_next(dir, 4), next_rc...], + projector_alg, ) end @@ -396,9 +398,14 @@ end # Build projectors from SVD and enlarged SW & NW corners function build_projectors( - U::AbstractTensorMap{E,3,1}, S, V::AbstractTensorMap{E,1,3}, Q, Q_next + U::AbstractTensorMap{E,3,1}, + S, + V::AbstractTensorMap{E,1,3}, + Q, + Q_next, + alg::ProjectorAlg, ) where {E<:ElementarySpace} - isqS = sdiag_inv_sqrt(S) + isqS = sdiag_inv_sqrt(S; tol=alg.inv_sqrt_tol) P_left = Q_next * V' * isqS P_right = isqS * U' * Q return P_left, P_right diff --git a/src/utility/util.jl b/src/utility/util.jl index 3d31c90b..66f4be07 100644 --- a/src/utility/util.jl +++ b/src/utility/util.jl @@ -12,15 +12,20 @@ function _elementwise_mult(a::AbstractTensorMap, b::AbstractTensorMap) end # Compute √S⁻¹ for diagonal TensorMaps -function sdiag_inv_sqrt(S::AbstractTensorMap) +_safe_inv(a, tol) = abs(a) < tol ? zero(a) : inv(a) +function sdiag_inv_sqrt(S::AbstractTensorMap; tol=Defaults.inv_sqrt_tol) invsq = similar(S) if sectortype(S) == Trivial - copyto!(invsq.data, LinearAlgebra.diagm(LinearAlgebra.diag(S.data) .^ (-1 / 2))) + copyto!( + invsq.data, + LinearAlgebra.diagm(_safe_inv.(LinearAlgebra.diag(S.data), tol) .^ (1 / 2)), + ) else for (k, b) in blocks(S) copyto!( - blocks(invsq)[k], LinearAlgebra.diagm(LinearAlgebra.diag(b) .^ (-1 / 2)) + blocks(invsq)[k], + LinearAlgebra.diagm(_safe_inv.(LinearAlgebra.diag(b), tol) .^ (1 / 2)), ) end end @@ -28,8 +33,10 @@ function sdiag_inv_sqrt(S::AbstractTensorMap) return invsq end -function ChainRulesCore.rrule(::typeof(sdiag_inv_sqrt), S::AbstractTensorMap) - invsq = sdiag_inv_sqrt(S) +function ChainRulesCore.rrule( + ::typeof(sdiag_inv_sqrt), S::AbstractTensorMap; tol=Defaults.inv_sqrt_tol +) + invsq = sdiag_inv_sqrt(S; tol) function sdiag_inv_sqrt_pullback(c̄) return (ChainRulesCore.NoTangent(), -1 / 2 * _elementwise_mult(c̄, invsq'^3)) end From e86a162d12cbe8f02e2943e4d78c0b02874d015c Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Thu, 24 Oct 2024 19:22:38 +0200 Subject: [PATCH 154/213] Change default rrule_alg to Arnoldi --- src/PEPSKit.jl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/PEPSKit.jl b/src/PEPSKit.jl index c2ffeea6..7f84822b 100644 --- a/src/PEPSKit.jl +++ b/src/PEPSKit.jl @@ -62,6 +62,7 @@ Module containing default values that represent typical algorithm parameters. - `ctmrg_tol = 1e-12`: Tolerance checking singular value and norm convergence - `fpgrad_maxiter = 100`: Maximal number of iterations for computing the CTMRG fixed-point gradient - `fpgrad_tol = 1e-6`: Convergence tolerance for the fixed-point gradient iteration +- `rrule_alg = Arnoldi(; tol=1e1ctmrg_tol, krylovdim=48, verbosity=-1)`: Default cotangent linear problem algorithm """ module Defaults using TensorKit, KrylovKit, OptimKit @@ -76,7 +77,7 @@ module Defaults const trscheme = FixedSpaceTruncation() const iterscheme = :fixed const fwd_alg = TensorKit.SVD() - const rrule_alg = GMRES(; tol=1e1ctmrg_tol) + const rrule_alg = Arnoldi(; tol=1e-2fpgrad_tol, krylovdim=48, verbosity=-1) const svd_alg = SVDAdjoint(; fwd_alg, rrule_alg) const optimizer = LBFGS(32; maxiter=100, gradtol=1e-4, verbosity=2) const gradient_linsolver = KrylovKit.BiCGStab(; From 899b10b61452f225425866ae1bde0974d31e5d4a Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Fri, 25 Oct 2024 10:07:01 +0200 Subject: [PATCH 155/213] Comment out HalfInfiniteEnv SVD test --- test/ctmrg/svd_wrapper.jl | 90 +++++++++++++++++++-------------------- 1 file changed, 45 insertions(+), 45 deletions(-) diff --git a/test/ctmrg/svd_wrapper.jl b/test/ctmrg/svd_wrapper.jl index 68a53aa8..01c4f39e 100644 --- a/test/ctmrg/svd_wrapper.jl +++ b/test/ctmrg/svd_wrapper.jl @@ -6,7 +6,7 @@ using KrylovKit using ChainRulesCore, Zygote using Accessors using PEPSKit -using PEPSKit: HalfInfiniteEnv +# using PEPSKit: HalfInfiniteEnv # Gauge-invariant loss function function lossfun(A, alg, R=TensorMap(randn, space(A)), trunc=notrunc()) @@ -94,47 +94,47 @@ symm_R = TensorMap(randn, dtype, space(symm_r)) @test g_fullsvd_tr[1] ≈ g_itersvd_fb[1] rtol = rtol end -χbond = 2 -χenv = 6 -ctm_alg = CTMRG(; tol=1e-10, verbosity=2, svd_alg=SVDAdjoint()) -Random.seed!(91283219347) -H = heisenberg_XYZ(InfiniteSquare()) -psi = InfinitePEPS(2, χbond) -env = leading_boundary(CTMRGEnv(psi, ComplexSpace(χenv)), psi, ctm_alg); -hienv = HalfInfiniteEnv( - env.corners[1], - env.corners[2], - env.edges[4], - env.edges[1], - env.edges[1], - env.edges[2], - psi[1], - psi[1], - psi[1], - psi[1], -) -hienv_dense = hienv() -env_R = TensorMap(randn, space(hienv)) - -PEPSKit.tsvd!(hienv, iter_alg) # TODO: make the space mismatches work - -@testset "IterSVD with HalfInfiniteEnv function handle" begin - # Equivalence of dense and sparse contractions - x₀ = PEPSKit.random_start_vector(hienv) - x′ = hienv(x₀, Val(false)) - x″ = hienv(x′, Val(true)) - x‴ = hienv(x″, Val(false)) - - a = hienv_dense * x₀ - b = hienv_dense' * a - c = hienv_dense * b - @test a ≈ x′ - @test b ≈ x″ - @test c ≈ x‴ - - # TODO: code up pullback - # l_fullsvd, g_fullsvd = withgradient(A -> lossfun(A, full_alg, env_R), hienv_dense) - # l_itersvd, g_itersvd = withgradient(A -> lossfun(A, iter_alg, env_R), hienv) - # @test l_itersvd ≈ l_fullsvd - # @test g_fullsvd[1] ≈ g_itersvd[1] rtol = rtol -end +# TODO: Add when IterSVD is implemented for HalfInfiniteEnv +# χbond = 2 +# χenv = 6 +# ctm_alg = CTMRG(; tol=1e-10, verbosity=2, svd_alg=SVDAdjoint()) +# Random.seed!(91283219347) +# H = heisenberg_XYZ(InfiniteSquare()) +# psi = InfinitePEPS(2, χbond) +# env = leading_boundary(CTMRGEnv(psi, ComplexSpace(χenv)), psi, ctm_alg); +# hienv = HalfInfiniteEnv( +# env.corners[1], +# env.corners[2], +# env.edges[4], +# env.edges[1], +# env.edges[1], +# env.edges[2], +# psi[1], +# psi[1], +# psi[1], +# psi[1], +# ) +# hienv_dense = hienv() +# env_R = TensorMap(randn, space(hienv)) + +# PEPSKit.tsvd!(hienv, iter_alg) + +# @testset "IterSVD with HalfInfiniteEnv function handle" begin +# # Equivalence of dense and sparse contractions +# x₀ = PEPSKit.random_start_vector(hienv) +# x′ = hienv(x₀, Val(false)) +# x″ = hienv(x′, Val(true)) +# x‴ = hienv(x″, Val(false)) + +# a = hienv_dense * x₀ +# b = hienv_dense' * a +# c = hienv_dense * b +# @test a ≈ x′ +# @test b ≈ x″ +# @test c ≈ x‴ + +# # l_fullsvd, g_fullsvd = withgradient(A -> lossfun(A, full_alg, env_R), hienv_dense) +# # l_itersvd, g_itersvd = withgradient(A -> lossfun(A, iter_alg, env_R), hienv) +# # @test l_itersvd ≈ l_fullsvd +# # @test g_fullsvd[1] ≈ g_itersvd[1] rtol = rtol +# end From e31dea04f2c03f374b925fab34ba083b60a615f1 Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Mon, 28 Oct 2024 11:05:37 +0100 Subject: [PATCH 156/213] Replace enlarge_corner with constructor --- src/algorithms/ctmrg/ctmrg.jl | 45 +-------------------- src/algorithms/ctmrg/sparse_environments.jl | 43 ++++++++++++++++++++ 2 files changed, 45 insertions(+), 43 deletions(-) diff --git a/src/algorithms/ctmrg/ctmrg.jl b/src/algorithms/ctmrg/ctmrg.jl index 6e708857..8167f37c 100644 --- a/src/algorithms/ctmrg/ctmrg.jl +++ b/src/algorithms/ctmrg/ctmrg.jl @@ -192,7 +192,7 @@ function ctmrg_expand(state, envs::CTMRGEnv, ::SimultaneousCTMRG) end # function ctmrg_expand(dirs, state, envs::CTMRGEnv) # TODO: This doesn't AD due to length(::Nothing)... # drc_combinations = collect(Iterators.product(dirs, axes(state)...)) -# return map(idx -> enlarge_corner(idx, envs, state)(idx[1]), drc_combinations) +# return map(idx -> EnlargedCorner(state, envs, idx)(idx[1]), drc_combinations) # end function ctmrg_expand(dirs, state, envs::CTMRGEnv{C,T}) where {C,T} Qtype = tensormaptype(spacetype(C), 3, 3, storagetype(C)) @@ -200,53 +200,12 @@ function ctmrg_expand(dirs, state, envs::CTMRGEnv{C,T}) where {C,T} dirs_enum = [(i, dir) for (i, dir) in enumerate(dirs)] drc_combinations = collect(Iterators.product(dirs_enum, axes(state)...)) @fwdthreads for (d, r, c) in drc_combinations - ec = enlarge_corner((d[2], r, c), state, envs) + ec = EnlargedCorner(state, envs, (d[2], r, c)) Q[d[1], r, c] = ec(d[2]) # Explicitly construct EnlargedCorner for now end return copy(Q) end -""" - enlarge_corner((dir, r, c), state, envs) - -Enlarge corner by constructing the corresponding `EnlargedCorner` struct. -""" -function enlarge_corner((dir, r, c), state, envs) - if dir == NORTHWEST - return EnlargedCorner( - envs.corners[NORTHWEST, _prev(r, end), _prev(c, end)], - envs.edges[WEST, r, _prev(c, end)], - envs.edges[NORTH, _prev(r, end), c], - state[r, c], - state[r, c], - ) - elseif dir == NORTHEAST - return EnlargedCorner( - envs.corners[NORTHEAST, _prev(r, end), _next(c, end)], - envs.edges[NORTH, _prev(r, end), c], - envs.edges[EAST, r, _next(c, end)], - state[r, c], - state[r, c], - ) - elseif dir == SOUTHEAST - return EnlargedCorner( - envs.corners[SOUTHEAST, _next(r, end), _next(c, end)], - envs.edges[EAST, r, _next(c, end)], - envs.edges[SOUTH, _next(r, end), c], - state[r, c], - state[r, c], - ) - elseif dir == SOUTHWEST - return EnlargedCorner( - envs.corners[SOUTHWEST, _next(r, end), _prev(c, end)], - envs.edges[SOUTH, _next(r, end), c], - envs.edges[WEST, r, _prev(c, end)], - state[r, c], - state[r, c], - ) - end -end - # ======================================================================================== # # Projector step # ======================================================================================== # diff --git a/src/algorithms/ctmrg/sparse_environments.jl b/src/algorithms/ctmrg/sparse_environments.jl index c6b826b4..44eca222 100644 --- a/src/algorithms/ctmrg/sparse_environments.jl +++ b/src/algorithms/ctmrg/sparse_environments.jl @@ -15,6 +15,49 @@ struct EnlargedCorner{Ct,E,A,A′} bra::A′ end +""" + EnlargedCorner(state, envs, coordinates) + +Construct an enlarged corner with the correct row and column indices based on the given +`coordinates` which are of the form `(dir, row, col)`. +""" +function EnlargedCorner(state, envs, coordinates) + dir, r, c = coordinates + if dir == NORTHWEST + return EnlargedCorner( + envs.corners[NORTHWEST, _prev(r, end), _prev(c, end)], + envs.edges[WEST, r, _prev(c, end)], + envs.edges[NORTH, _prev(r, end), c], + state[r, c], + state[r, c], + ) + elseif dir == NORTHEAST + return EnlargedCorner( + envs.corners[NORTHEAST, _prev(r, end), _next(c, end)], + envs.edges[NORTH, _prev(r, end), c], + envs.edges[EAST, r, _next(c, end)], + state[r, c], + state[r, c], + ) + elseif dir == SOUTHEAST + return EnlargedCorner( + envs.corners[SOUTHEAST, _next(r, end), _next(c, end)], + envs.edges[EAST, r, _next(c, end)], + envs.edges[SOUTH, _next(r, end), c], + state[r, c], + state[r, c], + ) + elseif dir == SOUTHWEST + return EnlargedCorner( + envs.corners[SOUTHWEST, _next(r, end), _prev(c, end)], + envs.edges[SOUTH, _next(r, end), c], + envs.edges[WEST, r, _prev(c, end)], + state[r, c], + state[r, c], + ) + end +end + """ (Q::EnlargedCorner)(dir::Int) From 1a8f7889c7e3d5d0a41d8cb7a1acdb23375addcb Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Mon, 28 Oct 2024 19:59:15 +0100 Subject: [PATCH 157/213] Replace function call with TensorMap constructor --- src/algorithms/ctmrg/ctmrg.jl | 4 ++-- src/algorithms/ctmrg/sparse_environments.jl | 25 ++++++++++++--------- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/src/algorithms/ctmrg/ctmrg.jl b/src/algorithms/ctmrg/ctmrg.jl index 8167f37c..1dd07c09 100644 --- a/src/algorithms/ctmrg/ctmrg.jl +++ b/src/algorithms/ctmrg/ctmrg.jl @@ -192,7 +192,7 @@ function ctmrg_expand(state, envs::CTMRGEnv, ::SimultaneousCTMRG) end # function ctmrg_expand(dirs, state, envs::CTMRGEnv) # TODO: This doesn't AD due to length(::Nothing)... # drc_combinations = collect(Iterators.product(dirs, axes(state)...)) -# return map(idx -> EnlargedCorner(state, envs, idx)(idx[1]), drc_combinations) +# return map(idx -> TensorMap(EnlargedCorner(state, envs, idx), idx[1]), drc_combinations) # end function ctmrg_expand(dirs, state, envs::CTMRGEnv{C,T}) where {C,T} Qtype = tensormaptype(spacetype(C), 3, 3, storagetype(C)) @@ -201,7 +201,7 @@ function ctmrg_expand(dirs, state, envs::CTMRGEnv{C,T}) where {C,T} drc_combinations = collect(Iterators.product(dirs_enum, axes(state)...)) @fwdthreads for (d, r, c) in drc_combinations ec = EnlargedCorner(state, envs, (d[2], r, c)) - Q[d[1], r, c] = ec(d[2]) # Explicitly construct EnlargedCorner for now + Q[d[1], r, c] = TensorMap(ec, d[2]) # Explicitly construct EnlargedCorner for now end return copy(Q) end diff --git a/src/algorithms/ctmrg/sparse_environments.jl b/src/algorithms/ctmrg/sparse_environments.jl index 44eca222..9243067e 100644 --- a/src/algorithms/ctmrg/sparse_environments.jl +++ b/src/algorithms/ctmrg/sparse_environments.jl @@ -59,12 +59,12 @@ function EnlargedCorner(state, envs, coordinates) end """ - (Q::EnlargedCorner)(dir::Int) + TensorKit.TensorMap(Q::EnlargedCorner, dir::Int) -Contract enlarged corner where `dir` selects the correct contraction direction, -i.e. the way the environment and PEPS tensors connect. +Instantiate enlarged corner as `TensorMap` where `dir` selects the correct contraction +direction, i.e. the way the environment and PEPS tensors connect. """ -function (Q::EnlargedCorner)(dir::Int) +function TensorKit.TensorMap(Q::EnlargedCorner, dir::Int) if dir == NORTHWEST return enlarge_northwest_corner(Q.E_1, Q.C, Q.E_2, Q.ket, Q.bra) elseif dir == NORTHEAST @@ -134,14 +134,11 @@ function HalfInfiniteEnv(quadrant1::EnlargedCorner, quadrant2::EnlargedCorner) end """ - (env::HalfInfiniteEnv)() - (env::HalfInfiniteEnv)(x, ::Val{false}) - (env::HalfInfiniteEnv)(x, ::Val{true}) + TensorKit.TensorMap(env::HalfInfiniteEnv) -Contract half-infinite environment. If a vector `x` is provided, the environment acts as a -linear map or adjoint linear map on `x` if `Val(true)` or `Val(false)` is passed, respectively. +Instantiate half-infinite environment as `TensorMap` explicitly. """ -function (env::HalfInfiniteEnv)() # Dense operator +function TensorKit.TensorMap(env::HalfInfiniteEnv) # Dense operator return halfinfinite_environment( env.C_1, env.C_2, @@ -155,6 +152,14 @@ function (env::HalfInfiniteEnv)() # Dense operator env.bra_2, ) end + +""" + (env::HalfInfiniteEnv)(x, ::Val{false}) + (env::HalfInfiniteEnv)(x, ::Val{true}) + +Contract half-infinite environment with a vector `x`, such that the environment acts as a +linear map or adjoint linear map on `x` if `Val(true)` or `Val(false)` is passed, respectively. +""" function (env::HalfInfiniteEnv)(x, ::Val{false}) # Linear map: env() * x return halfinfinite_environment( env.C_1, From 840cf5af1ec425ddf9ebf85c8d88e03f29e6c122 Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Mon, 28 Oct 2024 20:22:18 +0100 Subject: [PATCH 158/213] Add TensorKit-like rtol and revert inv_sqrt_tol changes --- src/PEPSKit.jl | 2 -- src/algorithms/ctmrg/ctmrg.jl | 13 +++---------- src/utility/util.jl | 6 ++++-- 3 files changed, 7 insertions(+), 14 deletions(-) diff --git a/src/PEPSKit.jl b/src/PEPSKit.jl index 12f430b1..c2ffeea6 100644 --- a/src/PEPSKit.jl +++ b/src/PEPSKit.jl @@ -62,7 +62,6 @@ Module containing default values that represent typical algorithm parameters. - `ctmrg_tol = 1e-12`: Tolerance checking singular value and norm convergence - `fpgrad_maxiter = 100`: Maximal number of iterations for computing the CTMRG fixed-point gradient - `fpgrad_tol = 1e-6`: Convergence tolerance for the fixed-point gradient iteration -- `inv_sqrt_tol = 1e-2ctmrg_tol`: Tolerance for cutting small values before sqrt-inverting """ module Defaults using TensorKit, KrylovKit, OptimKit @@ -84,7 +83,6 @@ module Defaults maxiter=Defaults.fpgrad_maxiter, tol=Defaults.fpgrad_tol ) const gradient_alg = LinSolver(; solver=gradient_linsolver, iterscheme) - const inv_sqrt_tol = 1e-2ctmrg_tol end export SVDAdjoint, IterSVD, NonTruncSVDAdjoint diff --git a/src/algorithms/ctmrg/ctmrg.jl b/src/algorithms/ctmrg/ctmrg.jl index 36ca6b08..ee1c5262 100644 --- a/src/algorithms/ctmrg/ctmrg.jl +++ b/src/algorithms/ctmrg/ctmrg.jl @@ -20,7 +20,6 @@ environment direction/unit cell entry. @kwdef struct ProjectorAlg{S<:SVDAdjoint,T} svd_alg::S = Defaults.svd_alg trscheme::T = Defaults.trscheme - inv_sqrt_tol::Float64 = Defaults.inv_sqrt_tol verbosity::Int = 0 end # TODO: add option for different projector styles (half-infinite, full-infinite, etc.) @@ -259,7 +258,7 @@ function ctmrg_projectors( # Compute projectors P_bottom[r, c], P_top[r, c] = build_projectors( - U, S, V, enlarged_envs[1][r, c], enlarged_envs[2][r′, c], projector_alg + U, S, V, enlarged_envs[1][r, c], enlarged_envs[2][r′, c] ) end @@ -317,7 +316,6 @@ function ctmrg_projectors( V_local, enlarged_envs[dir, r, c], enlarged_envs[_next(dir, 4), next_rc...], - projector_alg, ) end @@ -398,14 +396,9 @@ end # Build projectors from SVD and enlarged SW & NW corners function build_projectors( - U::AbstractTensorMap{E,3,1}, - S, - V::AbstractTensorMap{E,1,3}, - Q, - Q_next, - alg::ProjectorAlg, + U::AbstractTensorMap{E,3,1}, S, V::AbstractTensorMap{E,1,3}, Q, Q_next ) where {E<:ElementarySpace} - isqS = sdiag_inv_sqrt(S; tol=alg.inv_sqrt_tol) + isqS = sdiag_inv_sqrt(S) P_left = Q_next * V' * isqS P_right = isqS * U' * Q return P_left, P_right diff --git a/src/utility/util.jl b/src/utility/util.jl index 66f4be07..fdf3d131 100644 --- a/src/utility/util.jl +++ b/src/utility/util.jl @@ -13,7 +13,8 @@ end # Compute √S⁻¹ for diagonal TensorMaps _safe_inv(a, tol) = abs(a) < tol ? zero(a) : inv(a) -function sdiag_inv_sqrt(S::AbstractTensorMap; tol=Defaults.inv_sqrt_tol) +function sdiag_inv_sqrt(S::AbstractTensorMap; tol::Real=eps(eltype(S))^(3 / 4)) + tol *= S[1] invsq = similar(S) if sectortype(S) == Trivial @@ -34,8 +35,9 @@ function sdiag_inv_sqrt(S::AbstractTensorMap; tol=Defaults.inv_sqrt_tol) end function ChainRulesCore.rrule( - ::typeof(sdiag_inv_sqrt), S::AbstractTensorMap; tol=Defaults.inv_sqrt_tol + ::typeof(sdiag_inv_sqrt), S::AbstractTensorMap; tol::Real=eps(eltype(S))^(3 / 4) ) + tol *= S[1] invsq = sdiag_inv_sqrt(S; tol) function sdiag_inv_sqrt_pullback(c̄) return (ChainRulesCore.NoTangent(), -1 / 2 * _elementwise_mult(c̄, invsq'^3)) From 6ba61dc3384744079697d78626246eb3b856bc12 Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Mon, 28 Oct 2024 20:32:13 +0100 Subject: [PATCH 159/213] Fix gauge-fix test --- src/utility/util.jl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/utility/util.jl b/src/utility/util.jl index fdf3d131..abbe5fac 100644 --- a/src/utility/util.jl +++ b/src/utility/util.jl @@ -14,16 +14,17 @@ end # Compute √S⁻¹ for diagonal TensorMaps _safe_inv(a, tol) = abs(a) < tol ? zero(a) : inv(a) function sdiag_inv_sqrt(S::AbstractTensorMap; tol::Real=eps(eltype(S))^(3 / 4)) - tol *= S[1] invsq = similar(S) if sectortype(S) == Trivial + tol *= S[1] copyto!( invsq.data, LinearAlgebra.diagm(_safe_inv.(LinearAlgebra.diag(S.data), tol) .^ (1 / 2)), ) else for (k, b) in blocks(S) + tol *= b[1] copyto!( blocks(invsq)[k], LinearAlgebra.diagm(_safe_inv.(LinearAlgebra.diag(b), tol) .^ (1 / 2)), From 2bfcad63c4d5af78791ede02e11524b1643fce4e Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Tue, 29 Oct 2024 10:40:35 +0100 Subject: [PATCH 160/213] Replace first S element with Inf-norm, update pwave tests --- src/utility/util.jl | 5 ++--- test/ctmrg/gradients.jl | 11 +++-------- test/pwave.jl | 8 +++++++- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/utility/util.jl b/src/utility/util.jl index abbe5fac..ff881e79 100644 --- a/src/utility/util.jl +++ b/src/utility/util.jl @@ -14,17 +14,16 @@ end # Compute √S⁻¹ for diagonal TensorMaps _safe_inv(a, tol) = abs(a) < tol ? zero(a) : inv(a) function sdiag_inv_sqrt(S::AbstractTensorMap; tol::Real=eps(eltype(S))^(3 / 4)) + tol *= norm(S, Inf) # Relative tol w.r.t. largest singular value (use norm(∘, Inf) to make differentiable) invsq = similar(S) if sectortype(S) == Trivial - tol *= S[1] copyto!( invsq.data, LinearAlgebra.diagm(_safe_inv.(LinearAlgebra.diag(S.data), tol) .^ (1 / 2)), ) else for (k, b) in blocks(S) - tol *= b[1] copyto!( blocks(invsq)[k], LinearAlgebra.diagm(_safe_inv.(LinearAlgebra.diag(b), tol) .^ (1 / 2)), @@ -38,7 +37,7 @@ end function ChainRulesCore.rrule( ::typeof(sdiag_inv_sqrt), S::AbstractTensorMap; tol::Real=eps(eltype(S))^(3 / 4) ) - tol *= S[1] + tol *= norm(S, Inf) invsq = sdiag_inv_sqrt(S; tol) function sdiag_inv_sqrt_pullback(c̄) return (ChainRulesCore.NoTangent(), -1 / 2 * _elementwise_mult(c̄, invsq'^3)) diff --git a/test/ctmrg/gradients.jl b/test/ctmrg/gradients.jl index a5864390..bf86c0cb 100644 --- a/test/ctmrg/gradients.jl +++ b/test/ctmrg/gradients.jl @@ -18,12 +18,7 @@ names = ["Heisenberg", "p-wave superconductor"] gradtol = 1e-4 boundary_algs = [ - CTMRG(; - tol=1e-10, - verbosity=0, - ctmrgscheme=:simultaneous, - svd_alg=SVDAdjoint(; fwd_alg=TensorKit.SVD(), rrule_alg=GMRES(; tol=1e-10)), - ), + CTMRG(; tol=1e-10, verbosity=0, ctmrgscheme=:simultaneous), CTMRG(; tol=1e-10, verbosity=0, ctmrgscheme=:sequential), ] gradmodes = [ @@ -36,8 +31,8 @@ gradmodes = [ LinSolver(; solver=KrylovKit.GMRES(; tol=gradtol), iterscheme=:fixed), LinSolver(; solver=KrylovKit.GMRES(; tol=gradtol), iterscheme=:diffgauge), ], - [ - nothing, + [ # Only use :diffgauge due to high gauge-sensitivity (perhaps due to small χenv?) + # nothing, GeomSum(; tol=gradtol, iterscheme=:diffgauge), ManualIter(; tol=gradtol, iterscheme=:diffgauge), LinSolver(; solver=KrylovKit.GMRES(; tol=gradtol), iterscheme=:diffgauge), diff --git a/test/pwave.jl b/test/pwave.jl index 1edeb449..6bba5fb8 100644 --- a/test/pwave.jl +++ b/test/pwave.jl @@ -10,7 +10,13 @@ unitcell = (2, 2) H = pwave_superconductor(InfiniteSquare(unitcell...)) χbond = 2 χenv = 16 -ctm_alg = CTMRG(; tol=1e-8, maxiter=150, verbosity=2, ctmrgscheme=:sequential) +ctm_alg = CTMRG(; + tol=1e-8, + maxiter=150, + verbosity=2, + ctmrgscheme=:simultaneous, + svd_alg=SVDAdjoint(; rrule_alg=Arnoldi(; tol=1e-9, krylovdim=χenv+30)), +) opt_alg = PEPSOptimize(; boundary_alg=ctm_alg, optimizer=LBFGS(4; maxiter=10, gradtol=1e-3, verbosity=2), From 150abeb1fb54cff08f1c67af7dfb09a578a648c8 Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Tue, 29 Oct 2024 10:44:39 +0100 Subject: [PATCH 161/213] Fix formatting --- test/pwave.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/pwave.jl b/test/pwave.jl index 6bba5fb8..ea69deb9 100644 --- a/test/pwave.jl +++ b/test/pwave.jl @@ -15,7 +15,7 @@ ctm_alg = CTMRG(; maxiter=150, verbosity=2, ctmrgscheme=:simultaneous, - svd_alg=SVDAdjoint(; rrule_alg=Arnoldi(; tol=1e-9, krylovdim=χenv+30)), + svd_alg=SVDAdjoint(; rrule_alg=Arnoldi(; tol=1e-9, krylovdim=χenv + 30)), ) opt_alg = PEPSOptimize(; boundary_alg=ctm_alg, From 6bcb4e4814b935cce43939191d91f99562e559bb Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Tue, 29 Oct 2024 15:18:18 +0100 Subject: [PATCH 162/213] Add back nothing to gradients tests --- test/ctmrg/gradients.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/ctmrg/gradients.jl b/test/ctmrg/gradients.jl index bf86c0cb..6611a879 100644 --- a/test/ctmrg/gradients.jl +++ b/test/ctmrg/gradients.jl @@ -32,7 +32,7 @@ gradmodes = [ LinSolver(; solver=KrylovKit.GMRES(; tol=gradtol), iterscheme=:diffgauge), ], [ # Only use :diffgauge due to high gauge-sensitivity (perhaps due to small χenv?) - # nothing, + nothing, GeomSum(; tol=gradtol, iterscheme=:diffgauge), ManualIter(; tol=gradtol, iterscheme=:diffgauge), LinSolver(; solver=KrylovKit.GMRES(; tol=gradtol), iterscheme=:diffgauge), From 9be14e7e614006c81e1551c688b480bc98a7f029 Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Thu, 31 Oct 2024 14:20:21 +0100 Subject: [PATCH 163/213] Set TensorKit.SDD as default SVD algorithm --- src/PEPSKit.jl | 2 +- src/algorithms/ctmrg/ctmrg.jl | 4 ++-- src/algorithms/ctmrg/gaugefix.jl | 4 ++-- src/utility/svd.jl | 4 ++-- test/ctmrg/ctmrgschemes.jl | 8 ++++---- test/ctmrg/fixed_iterscheme.jl | 6 +++--- test/ctmrg/svd_wrapper.jl | 6 ++---- test/heisenberg.jl | 1 - test/j1j2_model.jl | 1 - test/tf_ising.jl | 1 - 10 files changed, 16 insertions(+), 21 deletions(-) diff --git a/src/PEPSKit.jl b/src/PEPSKit.jl index 23ac0fbf..f4bb9c83 100644 --- a/src/PEPSKit.jl +++ b/src/PEPSKit.jl @@ -78,7 +78,7 @@ module Defaults const reuse_env = true const trscheme = FixedSpaceTruncation() const iterscheme = :fixed - const fwd_alg = TensorKit.SVD() + const fwd_alg = TensorKit.SDD() const rrule_alg = Arnoldi(; tol=1e-2fpgrad_tol, krylovdim=48, verbosity=-1) const svd_alg = SVDAdjoint(; fwd_alg, rrule_alg) const optimizer = LBFGS(32; maxiter=100, gradtol=1e-4, verbosity=2) diff --git a/src/algorithms/ctmrg/ctmrg.jl b/src/algorithms/ctmrg/ctmrg.jl index 13f9087b..79d3f033 100644 --- a/src/algorithms/ctmrg/ctmrg.jl +++ b/src/algorithms/ctmrg/ctmrg.jl @@ -103,8 +103,8 @@ function MPSKit.leading_boundary(state, alg::CTMRG) return MPSKit.leading_boundary(CTMRGEnv(state, oneunit(spacetype(state))), state, alg) end function MPSKit.leading_boundary(envinit, state, alg::CTMRG) - CS = map(x -> tsvd(x; alg=TensorKit.SVD())[2], envinit.corners) - TS = map(x -> tsvd(x; alg=TensorKit.SVD())[2], envinit.edges) + CS = map(x -> tsvd(x)[2], envinit.corners) + TS = map(x -> tsvd(x)[2], envinit.edges) η = one(real(scalartype(state))) N = norm(state, envinit) diff --git a/src/algorithms/ctmrg/gaugefix.jl b/src/algorithms/ctmrg/gaugefix.jl index 4e496d42..849a8a89 100644 --- a/src/algorithms/ctmrg/gaugefix.jl +++ b/src/algorithms/ctmrg/gaugefix.jl @@ -160,10 +160,10 @@ function _singular_value_distance((S₁, S₂)) end function calc_convergence(envs, CSold, TSold) - CSnew = map(x -> tsvd(x; alg=TensorKit.SVD())[2], envs.corners) + CSnew = map(x -> tsvd(x)[2], envs.corners) ΔCS = maximum(_singular_value_distance, zip(CSold, CSnew)) - TSnew = map(x -> tsvd(x; alg=TensorKit.SVD())[2], envs.edges) + TSnew = map(x -> tsvd(x)[2], envs.edges) ΔTS = maximum(_singular_value_distance, zip(TSold, TSnew)) @debug "maxᵢ|Cⁿ⁺¹ - Cⁿ|ᵢ = $ΔCS maxᵢ|Tⁿ⁺¹ - Tⁿ|ᵢ = $ΔTS" diff --git a/src/utility/svd.jl b/src/utility/svd.jl index 8b946f51..a590c87a 100644 --- a/src/utility/svd.jl +++ b/src/utility/svd.jl @@ -104,7 +104,7 @@ function TensorKit._compute_svddata!( howmany = trunc isa NoTruncation ? minimum(size(b)) : blockdim(trunc.space, c) if howmany / minimum(size(b)) > alg.fallback_threshold # Use dense SVD for small blocks - U, S, V = TensorKit.MatrixAlgebra.svd!(b, TensorKit.SVD()) + U, S, V = TensorKit.MatrixAlgebra.svd!(b, TensorKit.SDD()) Udata[c] = U[:, 1:howmany] Vdata[c] = V[1:howmany, :] else @@ -112,7 +112,7 @@ function TensorKit._compute_svddata!( S, lvecs, rvecs, info = KrylovKit.svdsolve(b, x₀, howmany, :LR, alg.alg) if info.converged < howmany # Fall back to dense SVD if not properly converged @warn "Iterative SVD did not converge for block $c, falling back to dense SVD" - U, S, V = TensorKit.MatrixAlgebra.svd!(b, TensorKit.SVD()) + U, S, V = TensorKit.MatrixAlgebra.svd!(b, TensorKit.SDD()) Udata[c] = U[:, 1:howmany] Vdata[c] = V[1:howmany, :] else # Slice in case more values were converged than requested diff --git a/test/ctmrg/ctmrgschemes.jl b/test/ctmrg/ctmrgschemes.jl index be8a909b..82bd66c1 100644 --- a/test/ctmrg/ctmrgschemes.jl +++ b/test/ctmrg/ctmrgschemes.jl @@ -26,8 +26,8 @@ unitcells = [(1, 1), (3, 4)] @test abs(norm(psi, env_sequential)) ≈ abs(norm(psi, env_simultaneous)) rtol = 1e-6 # compare singular values - CS_sequential = map(c -> tsvd(c; alg=TensorKit.SVD())[2], env_sequential.corners) - CS_simultaneous = map(c -> tsvd(c; alg=TensorKit.SVD())[2], env_simultaneous.corners) + CS_sequential = map(c -> tsvd(c)[2], env_sequential.corners) + CS_simultaneous = map(c -> tsvd(c)[2], env_simultaneous.corners) ΔCS = maximum(zip(CS_sequential, CS_simultaneous)) do (c_lm, c_as) smallest = infimum(MPSKit._firstspace(c_lm), MPSKit._firstspace(c_as)) e_old = isometry(MPSKit._firstspace(c_lm), smallest) @@ -36,8 +36,8 @@ unitcells = [(1, 1), (3, 4)] end @test ΔCS < 1e-2 - TS_sequential = map(t -> tsvd(t; alg=TensorKit.SVD())[2], env_sequential.edges) - TS_simultaneous = map(t -> tsvd(t; alg=TensorKit.SVD())[2], env_simultaneous.edges) + TS_sequential = map(t -> tsvd(t)[2], env_sequential.edges) + TS_simultaneous = map(t -> tsvd(t)[2], env_simultaneous.edges) ΔTS = maximum(zip(TS_sequential, TS_simultaneous)) do (t_lm, t_as) MPSKit._firstspace(t_lm) == MPSKit._firstspace(t_as) || return scalartype(t_lm)(Inf) return norm(t_as - t_lm) diff --git a/test/ctmrg/fixed_iterscheme.jl b/test/ctmrg/fixed_iterscheme.jl index 49a1568a..355ec659 100644 --- a/test/ctmrg/fixed_iterscheme.jl +++ b/test/ctmrg/fixed_iterscheme.jl @@ -14,7 +14,7 @@ using PEPSKit: # initialize parameters χbond = 2 χenv = 16 -svd_algs = [SVDAdjoint(; fwd_alg=TensorKit.SVD()), SVDAdjoint(; fwd_alg=IterSVD())] +svd_algs = [SVDAdjoint(; fwd_alg=TensorKit.SDD()), SVDAdjoint(; fwd_alg=IterSVD())] unitcells = [(1, 1), (3, 4)] # test for element-wise convergence after application of fixed step @@ -47,7 +47,7 @@ unitcells = [(1, 1), (3, 4)] @test calc_elementwise_convergence(env_conv1, env_fixedsvd) ≈ 0 atol = 1e-6 end -@testset "Element-wise consistency of TensorKit.SVD and IterSVD" begin +@testset "Element-wise consistency of TensorKit.SDD and IterSVD" begin ctm_alg_iter = CTMRG(; tol=1e-12, maxiter=200, @@ -59,7 +59,7 @@ end tol=1e-12, verbosity=2, ctmrgscheme=:simultaneous, - svd_alg=SVDAdjoint(; fwd_alg=TensorKit.SVD()), + svd_alg=SVDAdjoint(; fwd_alg=TensorKit.SDD()), ) # initialize states diff --git a/test/ctmrg/svd_wrapper.jl b/test/ctmrg/svd_wrapper.jl index 01c4f39e..1824aeae 100644 --- a/test/ctmrg/svd_wrapper.jl +++ b/test/ctmrg/svd_wrapper.jl @@ -24,10 +24,8 @@ Random.seed!(123456789) r = TensorMap(randn, dtype, ℂ^m, ℂ^n) R = TensorMap(randn, space(r)) -full_alg = SVDAdjoint(; fwd_alg=TensorKit.SVD(), rrule_alg=nothing) -old_alg = SVDAdjoint(; - fwd_alg=TensorKit.SVD(), rrule_alg=NonTruncSVDAdjoint(), broadening=0.0 -) +full_alg = SVDAdjoint(; rrule_alg=nothing) +old_alg = SVDAdjoint(; rrule_alg=NonTruncSVDAdjoint(), broadening=0.0) iter_alg = SVDAdjoint(; fwd_alg=IterSVD(), rrule_alg=GMRES(; tol=1e-13)) # Don't make adjoint tolerance too small, g_itersvd will be weird @testset "Non-truncacted SVD" begin diff --git a/test/heisenberg.jl b/test/heisenberg.jl index 8e80944e..3ed7538a 100644 --- a/test/heisenberg.jl +++ b/test/heisenberg.jl @@ -13,7 +13,6 @@ ctm_alg = CTMRG(; miniter=4, maxiter=100, verbosity=2, - svd_alg=SVDAdjoint(; fwd_alg=TensorKit.SVD(), rrule_alg=GMRES(; tol=1e-10)), ctmrgscheme=:simultaneous, ) opt_alg = PEPSOptimize(; diff --git a/test/j1j2_model.jl b/test/j1j2_model.jl index b024b831..66a429a1 100644 --- a/test/j1j2_model.jl +++ b/test/j1j2_model.jl @@ -13,7 +13,6 @@ ctm_alg = CTMRG(; miniter=4, maxiter=100, verbosity=2, - svd_alg=SVDAdjoint(; fwd_alg=TensorKit.SVD(), rrule_alg=GMRES(; tol=1e-10)), ctmrgscheme=:simultaneous, ) opt_alg = PEPSOptimize(; diff --git a/test/tf_ising.jl b/test/tf_ising.jl index f0c725eb..d051e4d6 100644 --- a/test/tf_ising.jl +++ b/test/tf_ising.jl @@ -24,7 +24,6 @@ ctm_alg = CTMRG(; miniter=4, maxiter=100, verbosity=2, - svd_alg=SVDAdjoint(; fwd_alg=TensorKit.SVD(), rrule_alg=GMRES(; tol=1e-10)), ) opt_alg = PEPSOptimize(; boundary_alg=ctm_alg, From 91cac7c4411881c0813b0e42df790823c440e8c9 Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Thu, 31 Oct 2024 15:03:47 +0100 Subject: [PATCH 164/213] Update tests to use Defaults --- test/ctmrg/ctmrgschemes.jl | 4 ++-- test/ctmrg/fixed_iterscheme.jl | 24 +++++------------------- test/ctmrg/gaugefix.jl | 10 +++------- test/ctmrg/gradients.jl | 10 +++++----- test/ctmrg/svd_wrapper.jl | 2 +- test/heisenberg.jl | 15 +++------------ test/j1j2_model.jl | 15 ++++----------- test/pwave.jl | 13 ++----------- test/tf_ising.jl | 12 ++---------- 9 files changed, 27 insertions(+), 78 deletions(-) diff --git a/test/ctmrg/ctmrgschemes.jl b/test/ctmrg/ctmrgschemes.jl index 82bd66c1..cf556be2 100644 --- a/test/ctmrg/ctmrgschemes.jl +++ b/test/ctmrg/ctmrgschemes.jl @@ -7,8 +7,8 @@ using PEPSKit # initialize parameters χbond = 2 χenv = 16 -ctm_alg_sequential = CTMRG(; tol=1e-10, verbosity=2, ctmrgscheme=:sequential) -ctm_alg_simultaneous = CTMRG(; tol=1e-10, verbosity=2, ctmrgscheme=:simultaneous) +ctm_alg_sequential = CTMRG(; ctmrgscheme=:sequential) +ctm_alg_simultaneous = CTMRG(; ctmrgscheme=:simultaneous) unitcells = [(1, 1), (3, 4)] @testset "$(unitcell) unit cell" for unitcell in unitcells diff --git a/test/ctmrg/fixed_iterscheme.jl b/test/ctmrg/fixed_iterscheme.jl index 355ec659..3f005986 100644 --- a/test/ctmrg/fixed_iterscheme.jl +++ b/test/ctmrg/fixed_iterscheme.jl @@ -22,7 +22,7 @@ unitcells = [(1, 1), (3, 4)] Iterators.product( unitcells, svd_algs ) - ctm_alg = CTMRG(; tol=1e-12, verbosity=2, ctmrgscheme=:simultaneous, svd_alg) + ctm_alg = CTMRG(; svd_alg) # initialize states Random.seed!(2394823842) @@ -37,9 +37,7 @@ unitcells = [(1, 1), (3, 4)] # fix gauge of SVD U_fix, V_fix = fix_relative_phases(info.U, info.V, signs) svd_alg_fix = SVDAdjoint(; fwd_alg=FixedSVD(U_fix, info.S, V_fix)) - ctm_alg_fix = CTMRG(; - svd_alg=svd_alg_fix, trscheme=notrunc(), ctmrgscheme=:simultaneous - ) + ctm_alg_fix = CTMRG(; svd_alg=svd_alg_fix, trscheme=notrunc()) # do iteration with FixedSVD env_fixedsvd, = ctmrg_iter(psi, env_conv1, ctm_alg_fix) @@ -49,18 +47,10 @@ end @testset "Element-wise consistency of TensorKit.SDD and IterSVD" begin ctm_alg_iter = CTMRG(; - tol=1e-12, maxiter=200, - verbosity=2, - ctmrgscheme=:simultaneous, svd_alg=SVDAdjoint(; fwd_alg=IterSVD(; alg=GKL(; tol=1e-14, krylovdim=χenv + 10))), ) - ctm_alg_full = CTMRG(; - tol=1e-12, - verbosity=2, - ctmrgscheme=:simultaneous, - svd_alg=SVDAdjoint(; fwd_alg=TensorKit.SDD()), - ) + ctm_alg_full = CTMRG(; svd_alg=SVDAdjoint(; fwd_alg=TensorKit.SDD())) # initialize states Random.seed!(91283219347) @@ -80,15 +70,11 @@ end # fix gauge of SVD U_fix_iter, V_fix_iter = fix_relative_phases(info_iter.U, info_iter.V, signs_iter) svd_alg_fix_iter = SVDAdjoint(; fwd_alg=FixedSVD(U_fix_iter, info_iter.S, V_fix_iter)) - ctm_alg_fix_iter = CTMRG(; - svd_alg=svd_alg_fix_iter, trscheme=notrunc(), ctmrgscheme=:simultaneous - ) + ctm_alg_fix_iter = CTMRG(; svd_alg=svd_alg_fix_iter, trscheme=notrunc()) U_fix_full, V_fix_full = fix_relative_phases(info_full.U, info_full.V, signs_full) svd_alg_fix_full = SVDAdjoint(; fwd_alg=FixedSVD(U_fix_full, info_full.S, V_fix_full)) - ctm_alg_fix_full = CTMRG(; - svd_alg=svd_alg_fix_full, trscheme=notrunc(), ctmrgscheme=:simultaneous - ) + ctm_alg_fix_full = CTMRG(; svd_alg=svd_alg_fix_full, trscheme=notrunc()) # do iteration with FixedSVD env_fixedsvd_iter, = ctmrg_iter(psi, env_conv1, ctm_alg_fix_iter) diff --git a/test/ctmrg/gaugefix.jl b/test/ctmrg/gaugefix.jl index 21ec5b48..ef36943c 100644 --- a/test/ctmrg/gaugefix.jl +++ b/test/ctmrg/gaugefix.jl @@ -7,10 +7,10 @@ using PEPSKit: ctmrg_iter, gauge_fix, calc_elementwise_convergence scalartypes = [Float64, ComplexF64] unitcells = [(1, 1), (2, 2), (3, 2)] +maxiter = 200 schemes = [:simultaneous, :sequential] χ = 6 atol = 1e-4 -verbosity = 2 function _make_symmetric!(psi) if ==(size(psi)...) @@ -50,9 +50,7 @@ end Random.seed!(987654321) # Seed RNG to make random environment consistent ctm = CTMRGEnv(psi, ctm_space) - alg = CTMRG(; - tol=1e-10, maxiter=200, verbosity, trscheme=FixedSpaceTruncation(), ctmrgscheme - ) + alg = CTMRG(; maxiter, ctmrgscheme) ctm = leading_boundary(ctm, psi, alg) ctm2, = ctmrg_iter(psi, ctm, alg) @@ -76,9 +74,7 @@ end psi = InfinitePEPS(physical_space, peps_space; unitcell) ctm = CTMRGEnv(psi, ctm_space) - alg = CTMRG(; - tol=1e-10, maxiter=200, verbosity, trscheme=FixedSpaceTruncation(), ctmrgscheme - ) + alg = CTMRG(; maxiter, ctmrgscheme) ctm = leading_boundary(ctm, psi, alg) ctm2, = ctmrg_iter(psi, ctm, alg) diff --git a/test/ctmrg/gradients.jl b/test/ctmrg/gradients.jl index 6611a879..2c18ef90 100644 --- a/test/ctmrg/gradients.jl +++ b/test/ctmrg/gradients.jl @@ -18,8 +18,8 @@ names = ["Heisenberg", "p-wave superconductor"] gradtol = 1e-4 boundary_algs = [ - CTMRG(; tol=1e-10, verbosity=0, ctmrgscheme=:simultaneous), - CTMRG(; tol=1e-10, verbosity=0, ctmrgscheme=:sequential), + CTMRG(; verbosity=0, ctmrgscheme=:simultaneous), + CTMRG(; verbosity=0, ctmrgscheme=:sequential), ] gradmodes = [ [ @@ -28,14 +28,14 @@ gradmodes = [ GeomSum(; tol=gradtol, iterscheme=:diffgauge), ManualIter(; tol=gradtol, iterscheme=:fixed), ManualIter(; tol=gradtol, iterscheme=:diffgauge), - LinSolver(; solver=KrylovKit.GMRES(; tol=gradtol), iterscheme=:fixed), - LinSolver(; solver=KrylovKit.GMRES(; tol=gradtol), iterscheme=:diffgauge), + LinSolver(; solver=KrylovKit.BiCGStab(; tol=gradtol), iterscheme=:fixed), + LinSolver(; solver=KrylovKit.BiCGStab(; tol=gradtol), iterscheme=:diffgauge), ], [ # Only use :diffgauge due to high gauge-sensitivity (perhaps due to small χenv?) nothing, GeomSum(; tol=gradtol, iterscheme=:diffgauge), ManualIter(; tol=gradtol, iterscheme=:diffgauge), - LinSolver(; solver=KrylovKit.GMRES(; tol=gradtol), iterscheme=:diffgauge), + LinSolver(; solver=KrylovKit.BiCGStab(; tol=gradtol), iterscheme=:diffgauge), ], ] steps = -0.01:0.005:0.01 diff --git a/test/ctmrg/svd_wrapper.jl b/test/ctmrg/svd_wrapper.jl index 1824aeae..1de240cc 100644 --- a/test/ctmrg/svd_wrapper.jl +++ b/test/ctmrg/svd_wrapper.jl @@ -26,7 +26,7 @@ R = TensorMap(randn, space(r)) full_alg = SVDAdjoint(; rrule_alg=nothing) old_alg = SVDAdjoint(; rrule_alg=NonTruncSVDAdjoint(), broadening=0.0) -iter_alg = SVDAdjoint(; fwd_alg=IterSVD(), rrule_alg=GMRES(; tol=1e-13)) # Don't make adjoint tolerance too small, g_itersvd will be weird +iter_alg = SVDAdjoint(; fwd_alg=IterSVD()) @testset "Non-truncacted SVD" begin l_fullsvd, g_fullsvd = withgradient(A -> lossfun(A, full_alg, R), r) diff --git a/test/heisenberg.jl b/test/heisenberg.jl index 3ed7538a..1c05b084 100644 --- a/test/heisenberg.jl +++ b/test/heisenberg.jl @@ -8,25 +8,16 @@ using OptimKit # initialize parameters χbond = 2 χenv = 16 -ctm_alg = CTMRG(; - tol=1e-10, - miniter=4, - maxiter=100, - verbosity=2, - ctmrgscheme=:simultaneous, -) +ctm_alg = CTMRG() opt_alg = PEPSOptimize(; - boundary_alg=ctm_alg, - optimizer=LBFGS(4; maxiter=100, gradtol=1e-3, verbosity=2), - gradient_alg=LinSolver(; solver=GMRES(; tol=1e-6), iterscheme=:fixed), - reuse_env=true, + boundary_alg=ctm_alg, optimizer=LBFGS(4; gradtol=1e-3, verbosity=2) ) # initialize states Random.seed!(91283219347) H = heisenberg_XYZ(InfiniteSquare()) psi_init = InfinitePEPS(2, χbond) -env_init = leading_boundary(CTMRGEnv(psi_init, ComplexSpace(χenv)), psi_init, ctm_alg); +env_init = leading_boundary(CTMRGEnv(psi_init, ComplexSpace(χenv)), psi_init, ctm_alg) # find fixedpoint result = fixedpoint(psi_init, H, opt_alg, env_init) diff --git a/test/j1j2_model.jl b/test/j1j2_model.jl index 66a429a1..45acd5b1 100644 --- a/test/j1j2_model.jl +++ b/test/j1j2_model.jl @@ -7,19 +7,12 @@ using OptimKit # initialize parameters χbond = 2 -χenv = 16 -ctm_alg = CTMRG(; - tol=1e-10, - miniter=4, - maxiter=100, - verbosity=2, - ctmrgscheme=:simultaneous, -) +χenv = 12 +ctm_alg = CTMRG() opt_alg = PEPSOptimize(; boundary_alg=ctm_alg, - optimizer=LBFGS(4; maxiter=100, gradtol=1e-3, verbosity=2), - gradient_alg=LinSolver(; solver=GMRES(; tol=1e-6), iterscheme=:diffgauge), - reuse_env=true, + optimizer=LBFGS(4; gradtol=1e-3, verbosity=2), + gradient_alg=LinSolver(; iterscheme=:diffgauge), ) # initialize states diff --git a/test/pwave.jl b/test/pwave.jl index ea69deb9..d663ec0d 100644 --- a/test/pwave.jl +++ b/test/pwave.jl @@ -10,18 +10,9 @@ unitcell = (2, 2) H = pwave_superconductor(InfiniteSquare(unitcell...)) χbond = 2 χenv = 16 -ctm_alg = CTMRG(; - tol=1e-8, - maxiter=150, - verbosity=2, - ctmrgscheme=:simultaneous, - svd_alg=SVDAdjoint(; rrule_alg=Arnoldi(; tol=1e-9, krylovdim=χenv + 30)), -) +ctm_alg = CTMRG(; maxiter=150) opt_alg = PEPSOptimize(; - boundary_alg=ctm_alg, - optimizer=LBFGS(4; maxiter=10, gradtol=1e-3, verbosity=2), - gradient_alg=LinSolver(; solver=GMRES(; tol=1e-3, maxiter=2), iterscheme=:diffgauge), - reuse_env=true, + boundary_alg=ctm_alg, optimizer=LBFGS(4; maxiter=10, gradtol=1e-3, verbosity=2) ) # initialize states diff --git a/test/tf_ising.jl b/test/tf_ising.jl index d051e4d6..0b4d72f5 100644 --- a/test/tf_ising.jl +++ b/test/tf_ising.jl @@ -19,17 +19,9 @@ mˣ = 0.91 # initialize parameters χbond = 2 χenv = 16 -ctm_alg = CTMRG(; - tol=1e-10, - miniter=4, - maxiter=100, - verbosity=2, -) +ctm_alg = CTMRG() opt_alg = PEPSOptimize(; - boundary_alg=ctm_alg, - optimizer=LBFGS(4; maxiter=100, gradtol=1e-3, verbosity=2), - gradient_alg=LinSolver(; solver=GMRES(; tol=1e-6)), - reuse_env=true, + boundary_alg=ctm_alg, optimizer=LBFGS(4; gradtol=1e-3, verbosity=2) ) # initialize states From 63e03d1f7e4f5d8d1fa52221b58efc71856ca4cb Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Fri, 1 Nov 2024 23:38:27 +0800 Subject: [PATCH 165/213] remove CTMRG redundant logging (#84) --- src/algorithms/ctmrg/ctmrg.jl | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/algorithms/ctmrg/ctmrg.jl b/src/algorithms/ctmrg/ctmrg.jl index 79d3f033..00fc4597 100644 --- a/src/algorithms/ctmrg/ctmrg.jl +++ b/src/algorithms/ctmrg/ctmrg.jl @@ -115,10 +115,8 @@ function MPSKit.leading_boundary(envinit, state, alg::CTMRG) ctmrg_loginit!(log, η, N) for iter in 1:(alg.maxiter) env, = ctmrg_iter(state, env, alg) # Grow and renormalize in all 4 directions - η, CS, TS = calc_convergence(env, CS, TS) N = norm(state, env) - ctmrg_logiter!(log, iter, η, N) if η ≤ alg.tol && iter ≥ alg.miniter ctmrg_logfinish!(log, iter, η, N) From 43c6735a188ec0ed2a395901789342ca565282e1 Mon Sep 17 00:00:00 2001 From: Lukas Devos Date: Fri, 1 Nov 2024 12:31:54 -0400 Subject: [PATCH 166/213] CI updates (#85) * Update minimal julia version to `lts` * Improve parallelization in CI * update gitignore --- .DS_Store | Bin 0 -> 6148 bytes .github/workflows/CI.yml | 26 +++++++++++++++++++------- .gitignore | 3 ++- Project.toml | 2 +- test/runtests.jl | 2 +- 5 files changed, 23 insertions(+), 10 deletions(-) create mode 100644 .DS_Store diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..900f40f7ff956b21d24f23dbfe96eca4e4dda795 GIT binary patch literal 6148 zcmeHKyG|QH6g`7C!U~NGM?xr(_6wZ&4;JKfG(eZi@go?;)>^hfNHqHaDL_I3iH0(D zr2Im@0KOohr=#WEnaO&_YrC`%nk&tm)qU(edv!ha^O5zK_oYK7ZH#>;A1~dQ%VGCNu1Q|& zsxdF__q~~b6g&yUpbG$xm-TW@_b&Ogjb38W`wq$`S$97is4@K$j^v60KDlr)5pg>T-S3tgKX%*T3`AxIv z|6eDCDJT#Wc%2HUsBzHP;kDVlwQ!ZP*Tx)84l&8g4C)e2c01MwvK4P~Xk#pv17cz^ SGe{3Dd Date: Mon, 4 Nov 2024 19:47:07 +0100 Subject: [PATCH 167/213] Implement multi-threading using OhMyThreads and make it differentiable (#70) Implementation of parallelization over unit cells, both in forwards and in backwards passes, using [OhMyThreads.jl](https://github.com/JuliaFolds2/OhMyThreads.jl). Additional changes to CI to fix various things. --- Co-authored-by: Lukas Devos --- .github/workflows/CI.yml | 51 ++++--------- .github/workflows/FormatCheck.yml | 41 +--------- Project.toml | 1 + src/PEPSKit.jl | 91 ++++++++++++++++++++--- src/algorithms/ctmrg/ctmrg.jl | 76 +++++++------------ src/algorithms/ctmrg/gaugefix.jl | 17 +++-- src/algorithms/toolbox.jl | 3 +- src/environments/ctmrg_environments.jl | 10 ++- src/operators/infinitepepo.jl | 3 +- src/states/infinitepeps.jl | 6 ++ src/utility/diffable_threads.jl | 57 ++++++++++++++ src/utility/util.jl | 33 +++----- test/runtests.jl | 17 +++-- test/{utility.jl => test_utils.jl} | 0 test/utility/diff_maps.jl | 7 ++ test/{ctmrg => utility}/svd_wrapper.jl | 0 test/{ctmrg => utility}/symmetrization.jl | 0 17 files changed, 242 insertions(+), 171 deletions(-) create mode 100644 src/utility/diffable_threads.jl rename test/{utility.jl => test_utils.jl} (100%) create mode 100644 test/utility/diff_maps.jl rename test/{ctmrg => utility}/svd_wrapper.jl (100%) rename test/{ctmrg => utility}/symmetrization.jl (100%) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 6df16f38..85c75900 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -6,6 +6,8 @@ on: - 'main' - 'release-' tags: '*' + paths-ignore: + - 'docs/**' pull_request: workflow_dispatch: @@ -15,47 +17,26 @@ concurrency: cancel-in-progress: ${{ startsWith(github.ref, 'refs/pull/') }} jobs: - test: - name: Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.group }} - runs-on: ${{ matrix.os }} + tests: + name: "Tests" strategy: fail-fast: false matrix: version: - 'lts' - - '1' # automatically expands to the latest stable 1.x release of Julia - os: - - ubuntu-latest - - macOS-latest - - windows-latest + - '1' group: - ctmrg - boundarymps - examples - steps: - - uses: actions/checkout@v4 - - uses: julia-actions/setup-julia@v2 - with: - version: ${{ matrix.version }} - - uses: actions/cache@v4 - env: - cache-name: cache-artifacts - with: - path: ~/.julia/artifacts - key: ${{ runner.os }}-test-${{ env.cache-name }}-${{ hashFiles('**/Project.toml') }} - restore-keys: | - ${{ runner.os }}-test-${{ env.cache-name }}- - ${{ runner.os }}-test- - ${{ runner.os }}- - - uses: julia-actions/julia-buildpkg@latest - - uses: julia-actions/julia-runtest@latest - env: - JULIA_NUM_THREADS: 4 - GROUP: ${{ matrix.group }} - - uses: julia-actions/julia-processcoverage@v1 - - uses: codecov/codecov-action@v4 - if: always() - env: - CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} - with: - file: lcov.info + - utility + os: + - ubuntu-latest + - macOS-latest + - windows-latest + uses: "QuantumKitHub/.github/.github/workflows/tests.yml@main" + with: + group: "${{ matrix.group }}" + julia-version: "${{ matrix.version }}" + os: "${{ matrix.os }}" + secrets: inherit \ No newline at end of file diff --git a/.github/workflows/FormatCheck.yml b/.github/workflows/FormatCheck.yml index 1f0d0515..f58700c1 100644 --- a/.github/workflows/FormatCheck.yml +++ b/.github/workflows/FormatCheck.yml @@ -1,47 +1,14 @@ -name: FormatCheck +name: "Format Check" on: push: branches: - 'main' - 'master' - - 'release-' tags: '*' pull_request: jobs: - build: - runs-on: ${{ matrix.os }} - strategy: - matrix: - version: - - '1' # automatically expands to the latest stable 1.x release of Julia - os: - - ubuntu-latest - arch: - - x64 - steps: - - uses: julia-actions/setup-julia@latest - with: - version: ${{ matrix.version }} - arch: ${{ matrix.arch }} - - - uses: actions/checkout@v4 - - name: Install JuliaFormatter and format - # This will use the latest version by default but you can set the version like so: - # - # julia -e 'using Pkg; Pkg.add(PackageSpec(name="JuliaFormatter", version="0.13.0"))' - run: | - julia -e 'using Pkg; Pkg.add(PackageSpec(name="JuliaFormatter"))' - julia -e 'using JuliaFormatter; format(".", verbose=true)' - - name: Format check - run: | - julia -e ' - out = Cmd(`git diff --name-only`) |> read |> String - if out == "" - exit(0) - else - @error "Some files have not been formatted !!!" - write(stdout, out) - exit(1) - end' \ No newline at end of file + format-check: + name: "Format Check" + uses: "QuantumKitHub/.github/.github/workflows/formatcheck.yml@main" \ No newline at end of file diff --git a/Project.toml b/Project.toml index 2ebfd483..3c143ce1 100644 --- a/Project.toml +++ b/Project.toml @@ -13,6 +13,7 @@ LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" LoggingExtras = "e6f89c97-d47a-5376-807f-9c37f3926c36" MPSKit = "bb1c41ca-d63c-52ed-829e-0820dda26502" MPSKitModels = "ca635005-6f8c-4cd1-b51d-8491250ef2ab" +OhMyThreads = "67456a42-1dca-4109-a031-0a68de7e3ad5" OptimKit = "77e91f04-9b3b-57a6-a776-40b61faaebe0" Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" diff --git a/src/PEPSKit.jl b/src/PEPSKit.jl index f4bb9c83..c8a6b032 100644 --- a/src/PEPSKit.jl +++ b/src/PEPSKit.jl @@ -11,8 +11,10 @@ using LoggingExtras using MPSKit: loginit!, logiter!, logfinish!, logcancel! using MPSKitModels using FiniteDifferences +using OhMyThreads include("utility/util.jl") +include("utility/diffable_threads.jl") include("utility/svd.jl") include("utility/rotations.jl") include("utility/diffset.jl") @@ -51,22 +53,45 @@ include("utility/symmetrization.jl") module Defaults const ctmrg_maxiter = 100 const ctmrg_miniter = 4 - const ctmrg_tol = 1e-12 - const fpgrad_maxiter = 100 + const ctmrg_tol = 1e-8 + const fpgrad_maxiter = 30 const fpgrad_tol = 1e-6 + const ctmrgscheme = :simultaneous + const reuse_env = true + const trscheme = FixedSpaceTruncation() + const fwd_alg = TensorKit.SVD() + const rrule_alg = Arnoldi(; tol=1e-2fpgrad_tol, krylovdim=48, verbosity=-1) + const svd_alg = SVDAdjoint(; fwd_alg, rrule_alg) + const optimizer = LBFGS(32; maxiter=100, gradtol=1e-4, verbosity=2) + const gradient_linsolver = KrylovKit.BiCGStab(; + maxiter=Defaults.fpgrad_maxiter, tol=Defaults.fpgrad_tol + ) + const iterscheme = :fixed + const gradient_alg = LinSolver(; solver=gradient_linsolver, iterscheme) + const scheduler = Ref{Scheduler}(Threads.nthreads() == 1 ? SerialScheduler() : DynamicScheduler()) end Module containing default values that represent typical algorithm parameters. -- `ctmrg_maxiter = 100`: Maximal number of CTMRG iterations per run -- `ctmrg_miniter = 4`: Minimal number of CTMRG carried out -- `ctmrg_tol = 1e-12`: Tolerance checking singular value and norm convergence -- `fpgrad_maxiter = 100`: Maximal number of iterations for computing the CTMRG fixed-point gradient -- `fpgrad_tol = 1e-6`: Convergence tolerance for the fixed-point gradient iteration -- `rrule_alg = Arnoldi(; tol=1e1ctmrg_tol, krylovdim=48, verbosity=-1)`: Default cotangent linear problem algorithm +- `ctmrg_maxiter`: Maximal number of CTMRG iterations per run +- `ctmrg_miniter`: Minimal number of CTMRG carried out +- `ctmrg_tol`: Tolerance checking singular value and norm convergence +- `fpgrad_maxiter`: Maximal number of iterations for computing the CTMRG fixed-point gradient +- `fpgrad_tol`: Convergence tolerance for the fixed-point gradient iteration +- `ctmrgscheme`: Scheme for growing, projecting and renormalizing CTMRG environments +- `reuse_env`: If `true`, the current optimization step is initialized on the previous environment +- `trscheme`: Truncation scheme for SVDs and other decompositions +- `fwd_alg`: SVD algorithm that is used in the forward pass +- `rrule_alg`: Reverse-rule for differentiating that SVD +- `svd_alg`: Combination of `fwd_alg` and `rrule_alg` +- `optimizer`: Optimization algorithm for PEPS ground-state optimization +- `gradient_linsolver`: Default linear solver for the `LinSolver` gradient algorithm +- `iterscheme`: Scheme for differentiating one CTMRG iteration +- `gradient_alg`: Algorithm to compute the gradient fixed-point +- `scheduler`: Multi-threading scheduler which can be accessed via `set_scheduler!` """ module Defaults - using TensorKit, KrylovKit, OptimKit + using TensorKit, KrylovKit, OptimKit, OhMyThreads using PEPSKit: LinSolver, FixedSpaceTruncation, SVDAdjoint const ctmrg_maxiter = 100 const ctmrg_miniter = 4 @@ -77,7 +102,6 @@ module Defaults const sparse = false const reuse_env = true const trscheme = FixedSpaceTruncation() - const iterscheme = :fixed const fwd_alg = TensorKit.SDD() const rrule_alg = Arnoldi(; tol=1e-2fpgrad_tol, krylovdim=48, verbosity=-1) const svd_alg = SVDAdjoint(; fwd_alg, rrule_alg) @@ -85,9 +109,56 @@ module Defaults const gradient_linsolver = KrylovKit.BiCGStab(; maxiter=Defaults.fpgrad_maxiter, tol=Defaults.fpgrad_tol ) + const iterscheme = :fixed const gradient_alg = LinSolver(; solver=gradient_linsolver, iterscheme) + + # OhMyThreads scheduler defaults + const scheduler = Ref{Scheduler}() + """ + set_scheduler!([scheduler]; kwargs...) + + Set `OhMyThreads` multi-threading scheduler parameters. + + The function either accepts a `scheduler` as an `OhMyThreads.Scheduler` or + as a symbol where the corresponding parameters are specificed as keyword arguments. + For instance, a static scheduler that uses four tasks with chunking enabled + can be set via + ``` + set_scheduler!(StaticScheduler(; ntasks=4, chunking=true)) + ``` + or equivalently with + ``` + set_scheduler!(:static; ntasks=4, chunking=true) + ``` + For a detailed description of all schedulers and their keyword arguments consult the + [`OhMyThreads` documentation](https://juliafolds2.github.io/OhMyThreads.jl/stable/refs/api/#Schedulers). + + If no `scheduler` is passed and only kwargs are provided, the `DynamicScheduler` + constructor is used with the provided kwargs. + + To reset the scheduler to its default value, one calls `set_scheduler!` without passing + arguments which then uses the default `DynamicScheduler()`. If the number of used threads is + just one it falls back to `SerialScheduler()`. + """ + function set_scheduler!(sc=OhMyThreads.Implementation.NotGiven(); kwargs...) + if isempty(kwargs) && sc isa OhMyThreads.Implementation.NotGiven + scheduler[] = Threads.nthreads() == 1 ? SerialScheduler() : DynamicScheduler() + else + scheduler[] = OhMyThreads.Implementation._scheduler_from_userinput( + sc; kwargs... + ) + end + return nothing + end + export set_scheduler! + + function __init__() + return set_scheduler!() + end end +using .Defaults: set_scheduler! +export set_scheduler! export SVDAdjoint, IterSVD, NonTruncSVDAdjoint export FixedSpaceTruncation, ProjectorAlg, CTMRG, CTMRGEnv, correlation_length export LocalOperator diff --git a/src/algorithms/ctmrg/ctmrg.jl b/src/algorithms/ctmrg/ctmrg.jl index 00fc4597..04b8651b 100644 --- a/src/algorithms/ctmrg/ctmrg.jl +++ b/src/algorithms/ctmrg/ctmrg.jl @@ -8,13 +8,14 @@ have different spaces, this truncation style is different from `TruncationSpace` struct FixedSpaceTruncation <: TensorKit.TruncationScheme end """ - struct ProjectorAlg{S}(; svd_alg=Defaults.svd_alg, trscheme=Defaults.trscheme, verbosity=0) - -Algorithm struct collecting all projector related parameters. - -The `svd_alg` sets the SVD algorithm for decomposing the CTM environment. The truncation scheme -has to be a `TensorKit.TruncationScheme`, and some SVD algorithms might have further restrictions -on what kind of truncation scheme can be used. + struct ProjectorAlg{S}(; svd_alg=TensorKit.SVD(), trscheme=TensorKit.notrunc(), + fixedspace=false, verbosity=0) + +Algorithm struct collecting all projector related parameters. The truncation scheme has to be +a `TensorKit.TruncationScheme`, and some SVD algorithms might have further restrictions on what +kind of truncation scheme can be used. If `fixedspace` is true, the truncation scheme is set to +`truncspace(V)` where `V` is the environment bond space, adjusted to the corresponding +environment direction/unit cell entry. """ @kwdef struct ProjectorAlg{S<:SVDAdjoint,T} svd_alg::S = Defaults.svd_alg @@ -177,13 +178,11 @@ ctmrg_logcancel!(log, iter, η, N) = @warnv 1 logcancel!(log, iter, η, N) """ ctmrg_expand(state, envs, alg::CTMRG{M}) - ctmrg_expand(dirs, state, envs::CTMRGEnv) Expand the environment by absorbing a new PEPS tensor. There are two modes of expansion: `M = :sequential` and `M = :simultaneous`. The first mode expands the environment in one direction at a time, for convenience towards the left. The second mode expands the environment in all four directions simultaneously. -Alternatively, one can provide directly the `dirs` in which the environment is grown. """ function ctmrg_expand(state, envs::CTMRGEnv, ::SequentialCTMRG) return ctmrg_expand([4, 1], state, envs) @@ -191,20 +190,9 @@ end function ctmrg_expand(state, envs::CTMRGEnv, ::SimultaneousCTMRG) return ctmrg_expand(1:4, state, envs) end -# function ctmrg_expand(dirs, state, envs::CTMRGEnv) # TODO: This doesn't AD due to length(::Nothing)... -# drc_combinations = collect(Iterators.product(dirs, axes(state)...)) -# return map(idx -> TensorMap(EnlargedCorner(state, envs, idx), idx[1]), drc_combinations) -# end -function ctmrg_expand(dirs, state, envs::CTMRGEnv{C,T}) where {C,T} - Qtype = tensormaptype(spacetype(C), 3, 3, storagetype(C)) - Q = Zygote.Buffer(Array{Qtype,3}(undef, length(dirs), size(state)...)) - dirs_enum = [(i, dir) for (i, dir) in enumerate(dirs)] - drc_combinations = collect(Iterators.product(dirs_enum, axes(state)...)) - @fwdthreads for (d, r, c) in drc_combinations - ec = EnlargedCorner(state, envs, (d[2], r, c)) - Q[d[1], r, c] = TensorMap(ec, d[2]) # Explicitly construct EnlargedCorner for now - end - return copy(Q) +function ctmrg_expand(dirs, state, envs::CTMRGEnv) + coordinates = eachcoordinate(state, dirs) + return dtmap(idx -> TensorMap(EnlargedCorner(state, envs, idx), idx[1]), coordinates) end # ======================================================================================== # @@ -221,15 +209,10 @@ function ctmrg_projectors( enlarged_envs, envs::CTMRGEnv{C,E}, alg::SequentialCTMRG ) where {C,E} projector_alg = alg.projector_alg - # pre-allocation - Prtype = tensormaptype(spacetype(E), numin(E), numout(E), storagetype(E)) - P_bottom = Zygote.Buffer(envs.edges, axes(envs.corners, 2), axes(envs.corners, 3)) - P_top = Zygote.Buffer(envs.edges, Prtype, axes(envs.corners, 2), axes(envs.corners, 3)) ϵ = zero(real(scalartype(envs))) - directions = collect(Iterators.product(axes(envs.corners, 2), axes(envs.corners, 3))) - # @fwdthreads for (r, c) in directions - for (r, c) in directions + coordinates = eachcoordinate(envs) + projectors = dtmap(coordinates) do (r, c) # SVD half-infinite environment r′ = _prev(r, size(envs.corners, 2)) QQ = halfinfinite_environment(enlarged_envs[1, r, c], enlarged_envs[2, r′, c]) @@ -248,26 +231,23 @@ function ctmrg_projectors( end # Compute projectors - P_bottom[r, c], P_top[r, c] = build_projectors( - U, S, V, enlarged_envs[1, r, c], enlarged_envs[2, r′, c] - ) + return build_projectors(U, S, V, enlarged_envs[1, r, c], enlarged_envs[2, r′, c]) end - return (copy(P_bottom), copy(P_top)), (; err=ϵ) + return (map(first, projectors), map(last, projectors)), (; err=ϵ) end function ctmrg_projectors( enlarged_envs, envs::CTMRGEnv{C,E}, alg::SimultaneousCTMRG ) where {C,E} projector_alg = alg.projector_alg # pre-allocation - P_left, P_right = Zygote.Buffer.(projector_type(envs.edges)) U, V = Zygote.Buffer.(projector_type(envs.edges)) # Corner type but with real numbers S = Zygote.Buffer(U.data, tensormaptype(spacetype(C), 1, 1, real(scalartype(E)))) ϵ = zero(real(scalartype(envs))) - drc_combinations = collect(Iterators.product(axes(envs.corners)...)) - @fwdthreads for (dir, r, c) in drc_combinations + coordinates = eachcoordinate(envs, 1:4) + projectors = dtmap(coordinates) do (dir, r, c) # Row-column index of next enlarged corner next_rc = if dir == 1 (r, _next(c, size(envs.corners, 3))) @@ -301,7 +281,7 @@ function ctmrg_projectors( end # Compute projectors - P_left[dir, r, c], P_right[dir, r, c] = build_projectors( + return build_projectors( U_local, S_local, V_local, @@ -310,7 +290,9 @@ function ctmrg_projectors( ) end - return (copy(P_left), copy(P_right)), (; err=ϵ, U=copy(U), S=copy(S), V=copy(V)) + P_left = map(first, projectors) + P_right = map(last, projectors) + return (P_left, P_right), (; err=ϵ, U=copy(U), S=copy(S), V=copy(V)) end """ @@ -374,9 +356,7 @@ function ctmrg_renormalize(projectors, state, envs, ::SequentialCTMRG) end # Apply projectors to renormalize corners and edges - coordinates = collect(Iterators.product(axes(state)...)) - # @fwdthreads for (r, c) in coordinates - for (r, c) in coordinates + for (r, c) in eachcoordinate(state) C_southwest = renormalize_bottom_corner((r, c), envs, projectors) corners[SOUTHWEST, r, c] = C_southwest / norm(C_southwest) @@ -390,12 +370,9 @@ function ctmrg_renormalize(projectors, state, envs, ::SequentialCTMRG) return CTMRGEnv(copy(corners), copy(edges)) end function ctmrg_renormalize(enlarged_envs, projectors, state, envs, ::SimultaneousCTMRG) - corners = Zygote.Buffer(envs.corners) - edges = Zygote.Buffer(envs.edges) P_left, P_right = projectors - - drc_combinations = collect(Iterators.product(axes(envs.corners)...)) - @fwdthreads for (dir, r, c) in drc_combinations + coordinates = eachcoordinate(envs, 1:4) + corners_edges = dtmap(coordinates) do (dir, r, c) if dir == NORTH corner = renormalize_northwest_corner((r, c), enlarged_envs, P_left, P_right) edge = renormalize_north_edge((r, c), envs, P_left, P_right, state) @@ -409,9 +386,8 @@ function ctmrg_renormalize(enlarged_envs, projectors, state, envs, ::Simultaneou corner = renormalize_southwest_corner((r, c), enlarged_envs, P_left, P_right) edge = renormalize_west_edge((r, c), envs, P_left, P_right, state) end - corners[dir, r, c] = corner / norm(corner) - edges[dir, r, c] = edge / norm(edge) + return corner / norm(corner), edge / norm(edge) end - return CTMRGEnv(copy(corners), copy(edges)) + return CTMRGEnv(map(first, corners_edges), map(last, corners_edges)) end diff --git a/src/algorithms/ctmrg/gaugefix.jl b/src/algorithms/ctmrg/gaugefix.jl index 849a8a89..5e63d5ef 100644 --- a/src/algorithms/ctmrg/gaugefix.jl +++ b/src/algorithms/ctmrg/gaugefix.jl @@ -8,14 +8,14 @@ element-wise converged to `envprev`. """ function gauge_fix(envprev::CTMRGEnv{C,T}, envfinal::CTMRGEnv{C,T}) where {C,T} # Check if spaces in envprev and envfinal are the same - same_spaces = map(Iterators.product(axes(envfinal.edges)...)) do (dir, r, c) + same_spaces = map(eachcoordinate(envfinal, 1:4)) do (dir, r, c) space(envfinal.edges[dir, r, c]) == space(envprev.edges[dir, r, c]) && space(envfinal.corners[dir, r, c]) == space(envprev.corners[dir, r, c]) end @assert all(same_spaces) "Spaces of envprev and envfinal are not the same" # Try the "general" algorithm from https://arxiv.org/abs/2311.11894 - signs = map(Iterators.product(axes(envfinal.edges)...)) do (dir, r, c) + signs = map(eachcoordinate(envfinal, 1:4)) do (dir, r, c) # Gather edge tensors and pretend they're InfiniteMPSs if dir == NORTH Tsprev = circshift(envprev.edges[dir, r, :], 1 - c) @@ -70,7 +70,7 @@ end # Explicit fixing of relative phases (doing this compactly in a loop is annoying) function fix_relative_phases(envfinal::CTMRGEnv, signs) - corners_fixed = map(Iterators.product(axes(envfinal.corners)...)) do (dir, r, c) + corners_fixed = map(eachcoordinate(envfinal, 1:4)) do (dir, r, c) if dir == NORTHWEST fix_gauge_northwest_corner((r, c), envfinal, signs) elseif dir == NORTHEAST @@ -82,7 +82,7 @@ function fix_relative_phases(envfinal::CTMRGEnv, signs) end end - edges_fixed = map(Iterators.product(axes(envfinal.corners)...)) do (dir, r, c) + edges_fixed = map(eachcoordinate(envfinal, 1:4)) do (dir, r, c) if dir == NORTHWEST fix_gauge_north_edge((r, c), envfinal, signs) elseif dir == NORTHEAST @@ -99,7 +99,8 @@ end function fix_relative_phases( U::Array{Ut,3}, V::Array{Vt,3}, signs ) where {Ut<:AbstractTensorMap,Vt<:AbstractTensorMap} - U_fixed = map(Iterators.product(axes(U)...)) do (dir, r, c) + U_fixed = map(CartesianIndices(U)) do I + dir, r, c = I.I if dir == NORTHWEST fix_gauge_north_left_vecs((r, c), U, signs) elseif dir == NORTHEAST @@ -111,7 +112,8 @@ function fix_relative_phases( end end - V_fixed = map(Iterators.product(axes(V)...)) do (dir, r, c) + V_fixed = map(CartesianIndices(V)) do I + dir, r, c = I.I if dir == NORTHWEST fix_gauge_north_right_vecs((r, c), V, signs) elseif dir == NORTHEAST @@ -191,7 +193,8 @@ function calc_elementwise_convergence(envfinal::CTMRGEnv, envfix::CTMRGEnv; atol @debug "maxᵢⱼ|Tⁿ⁺¹ - Tⁿ|ᵢⱼ = $ΔTmax mean |Tⁿ⁺¹ - Tⁿ|ᵢⱼ = $ΔTmean" # Check differences for all tensors in unit cell to debug properly - for (dir, r, c) in Iterators.product(axes(envfinal.edges)...) + for I in CartesianIndices(ΔT) + dir, r, c = I.I @debug( "$((dir, r, c)): all |Cⁿ⁺¹ - Cⁿ|ᵢⱼ < ϵ: ", all(x -> abs(x) < atol, convert(Array, ΔC[dir, r, c])), diff --git a/src/algorithms/toolbox.jl b/src/algorithms/toolbox.jl index 4f48cdf4..13e725e3 100644 --- a/src/algorithms/toolbox.jl +++ b/src/algorithms/toolbox.jl @@ -1,9 +1,10 @@ function MPSKit.expectation_value(peps::InfinitePEPS, O::LocalOperator, envs::CTMRGEnv) checklattice(peps, O) - return sum(O.terms) do (inds, operator) # TODO: parallelize this map, especially for the backwards pass + term_vals = dtmap([O.terms...]) do (inds, operator) # OhMyThreads can't iterate over O.terms directly contract_localoperator(inds, operator, peps, peps, envs) / contract_localnorm(inds, peps, peps, envs) end + return sum(term_vals) end function costfun(peps::InfinitePEPS, envs::CTMRGEnv, O::LocalOperator) diff --git a/src/environments/ctmrg_environments.jl b/src/environments/ctmrg_environments.jl index cf90e2f0..460faf5b 100644 --- a/src/environments/ctmrg_environments.jl +++ b/src/environments/ctmrg_environments.jl @@ -105,7 +105,8 @@ function CTMRGEnv( corners = Array{C_type}(undef, 4, size(Ds_north)...) edges = Array{T_type}(undef, 4, size(Ds_north)...) - for (r, c) in Iterators.product(axes(Ds_north)...) + for I in CartesianIndices(Ds_north) + r, c = I.I edges[NORTH, r, c] = _edge_tensor( f, T, @@ -371,6 +372,13 @@ function Base.rotl90(env::CTMRGEnv{C,T}) where {C,T} end Base.eltype(env::CTMRGEnv) = eltype(env.corners[1]) +Base.axes(x::CTMRGEnv, args...) = axes(x.corners, args...) +function eachcoordinate(x::CTMRGEnv) + return collect(Iterators.product(axes(x, 2), axes(x, 3))) +end +function eachcoordinate(x::CTMRGEnv, dirs) + return collect(Iterators.product(dirs, axes(x, 2), axes(x, 3))) +end # In-place update of environment function update!(env::CTMRGEnv{C,T}, env´::CTMRGEnv{C,T}) where {C,T} diff --git a/src/operators/infinitepepo.jl b/src/operators/infinitepepo.jl index a33c7e2c..e136aa70 100644 --- a/src/operators/infinitepepo.jl +++ b/src/operators/infinitepepo.jl @@ -125,7 +125,8 @@ function initializePEPS( T::InfinitePEPO{<:PEPOTensor{S}}, vspace::S ) where {S<:ElementarySpace} Pspaces = Array{S,2}(undef, size(T, 1), size(T, 2)) - for (i, j) in product(1:size(T, 1), 1:size(T, 2)) + for i in axes(T, 1) + j in axes(T, 2) Pspaces[i, j] = space(T, i, j) end Nspaces = repeat([vspace], size(T, 1), size(T, 2)) diff --git a/src/states/infinitepeps.jl b/src/states/infinitepeps.jl index a57af615..51b353d4 100644 --- a/src/states/infinitepeps.jl +++ b/src/states/infinitepeps.jl @@ -124,6 +124,12 @@ Base.repeat(T::InfinitePEPS, counts...) = InfinitePEPS(repeat(T.A, counts...)) Base.getindex(T::InfinitePEPS, args...) = Base.getindex(T.A, args...) Base.setindex!(T::InfinitePEPS, args...) = (Base.setindex!(T.A, args...); T) Base.axes(T::InfinitePEPS, args...) = axes(T.A, args...) +function eachcoordinate(x::InfinitePEPS) + return collect(Iterators.product(axes(x)...)) +end +function eachcoordinate(x::InfinitePEPS, dirs) + return collect(Iterators.product(dirs, axes(x, 1), axes(x, 2))) +end TensorKit.space(t::InfinitePEPS, i, j) = space(t[i, j], 1) ## Math diff --git a/src/utility/diffable_threads.jl b/src/utility/diffable_threads.jl new file mode 100644 index 00000000..c03937d9 --- /dev/null +++ b/src/utility/diffable_threads.jl @@ -0,0 +1,57 @@ +""" + dtmap(args...; kwargs...) + +Differentiable wrapper around `OhMyThreads.tmap`. + +All calls of `dtmap` inside of PEPSKit use the threading scheduler stored inside +`Defaults.scheduler` which can be modified using `set_scheduler!`. +""" +dtmap(args...; scheduler=Defaults.scheduler[]) = tmap(args...; scheduler) + +# Follows the `map` rrule from ChainRules.jl but specified for the case of one AbstractArray that is being mapped +# https://github.com/JuliaDiff/ChainRules.jl/blob/e245d50a1ae56ce46fc8c1f0fe9b925964f1146e/src/rulesets/Base/base.jl#L243 +function ChainRulesCore.rrule( + config::RuleConfig{>:HasReverseMode}, ::typeof(dtmap), f, A::AbstractArray; kwargs... +) + el_rrules = tmap(A; kwargs...) do a + rrule_via_ad(config, f, a) + end + y = map(first, el_rrules) + f_projector = ProjectTo(f) + A_projectors = map(ProjectTo, A) + + function dtmap_pullback(dy_raw) + dys = unthunk(dy_raw) + backevals = tmap(el_rrules, dys; kwargs...) do el_rrule, dy + last(el_rrule)(dy) + end + df = f_projector(sum(first, backevals)) + dA = map((Pa, backeval) -> Pa(last(backeval)), A_projectors, backevals) + return NoTangent(), df, dA + end + + return y, dtmap_pullback +end + +""" + @fwdthreads(ex) + +Apply `Threads.@threads` only in the forward pass of the program. + +It works by wrapping the for-loop expression in an if statement where in the forward pass +the loop in computed in parallel using `Threads.@threads`, whereas in the backwards pass +the `Threads.@threads` is omitted in order to make the expression differentiable. +""" +macro fwdthreads(ex) + @assert ex.head === :for "@fwdthreads expects a for loop:\n$ex" + + diffable_ex = quote + if Zygote.isderiving() + $ex + else + Threads.@threads $ex + end + end + + return esc(diffable_ex) +end diff --git a/src/utility/util.jl b/src/utility/util.jl index ff881e79..0b9fd4c4 100644 --- a/src/utility/util.jl +++ b/src/utility/util.jl @@ -2,6 +2,16 @@ _next(i, total) = mod1(i + 1, total) _prev(i, total) = mod1(i - 1, total) +# iterator over each coordinates +""" + eachcoordinate(x, dirs=1:4) + +Enumerate all (dir, row, col) pairs. +""" +function eachcoordinate end + +@non_differentiable eachcoordinate(args...) + # Element-wise multiplication of TensorMaps respecting block structure function _elementwise_mult(a::AbstractTensorMap, b::AbstractTensorMap) dst = similar(a) @@ -166,26 +176,3 @@ macro showtypeofgrad(x) end ) end - -""" - @fwdthreads(ex) - -Apply `Threads.@threads` only in the forward pass of the program. - -It works by wrapping the for-loop expression in an if statement where in the forward pass -the loop in computed in parallel using `Threads.@threads`, whereas in the backwards pass -the `Threads.@threads` is omitted in order to make the expression differentiable. -""" -macro fwdthreads(ex) - @assert ex.head === :for "@fwdthreads expects a for loop:\n$ex" - - diffable_ex = quote - if Zygote.isderiving() - $ex - else - Threads.@threads $ex - end - end - - return esc(diffable_ex) -end diff --git a/test/runtests.jl b/test/runtests.jl index 2b109130..07a4cd2f 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -20,9 +20,6 @@ end @time @safetestset "Unit cell" begin include("ctmrg/unitcell.jl") end - @time @safetestset "SVD wrapper" begin - include("ctmrg/svd_wrapper.jl") - end @time @safetestset ":fixed CTMRG iteration scheme" begin include("ctmrg/fixed_iterscheme.jl") end @@ -32,15 +29,23 @@ end @time @safetestset "CTMRG schemes" begin include("ctmrg/ctmrgschemes.jl") end - @time @safetestset "CTMRG schemes" begin - include("ctmrg/symmetrization.jl") - end end if GROUP == "ALL" || GROUP == "BOUNDARYMPS" @time @safetestset "VUMPS" begin include("boundarymps/vumps.jl") end end + if GROUP == "ALL" || GROUP == "UTILITY" + @time @safetestset "SVD wrapper" begin + include("utility/svd_wrapper.jl") + end + @time @safetestset "Symmetrization" begin + include("utility/symmetrization.jl") + end + @time @safetestset "Differentiable tmap" begin + include("utility/diff_maps.jl") + end + end if GROUP == "ALL" || GROUP == "EXAMPLES" @time @safetestset "Transverse Field Ising model" begin include("tf_ising.jl") diff --git a/test/utility.jl b/test/test_utils.jl similarity index 100% rename from test/utility.jl rename to test/test_utils.jl diff --git a/test/utility/diff_maps.jl b/test/utility/diff_maps.jl new file mode 100644 index 00000000..5606f731 --- /dev/null +++ b/test/utility/diff_maps.jl @@ -0,0 +1,7 @@ +using ChainRulesTestUtils +using PEPSKit: dtmap + +# Can the rrule of dtmap be made inferable? (if check_inferred=true, tests error at the moment) +@testset "Differentiable tmap" begin + test_rrule(dtmap, x -> x^3, randn(5, 5); check_inferred=false) +end diff --git a/test/ctmrg/svd_wrapper.jl b/test/utility/svd_wrapper.jl similarity index 100% rename from test/ctmrg/svd_wrapper.jl rename to test/utility/svd_wrapper.jl diff --git a/test/ctmrg/symmetrization.jl b/test/utility/symmetrization.jl similarity index 100% rename from test/ctmrg/symmetrization.jl rename to test/utility/symmetrization.jl From e78e5fa58f1c0b90339980dc83bb68f7e8c89b8c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 5 Nov 2024 07:57:32 -0500 Subject: [PATCH 168/213] CompatHelper: add new compat entry for OhMyThreads at version 0.7, (keep existing compat) (#87) Co-authored-by: CompatHelper Julia --- Project.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Project.toml b/Project.toml index 3c143ce1..9751d890 100644 --- a/Project.toml +++ b/Project.toml @@ -33,6 +33,7 @@ LinearAlgebra = "1" LoggingExtras = "1" MPSKit = "0.11" MPSKitModels = "0.3" +OhMyThreads = "0.7" OptimKit = "0.3" Printf = "1" Random = "1" From e6f8f060a972e62fa47732eccbaf06d23a76a329 Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Tue, 5 Nov 2024 16:19:02 +0100 Subject: [PATCH 169/213] Make :sequential act column-wise --- .../contractions/ctmrg_contractions.jl | 8 +- src/algorithms/ctmrg/ctmrg.jl | 73 ++++++++----------- test/heisenberg.jl | 6 ++ 3 files changed, 41 insertions(+), 46 deletions(-) diff --git a/src/algorithms/contractions/ctmrg_contractions.jl b/src/algorithms/contractions/ctmrg_contractions.jl index e53bf2ac..cb920d76 100644 --- a/src/algorithms/contractions/ctmrg_contractions.jl +++ b/src/algorithms/contractions/ctmrg_contractions.jl @@ -530,7 +530,7 @@ Apply bottom projector to southwest corner and south edge. function renormalize_bottom_corner((row, col), envs::CTMRGEnv, projectors) C_southwest = envs.corners[SOUTHWEST, row, _prev(col, end)] E_south = envs.edges[SOUTH, row, col] - P_bottom = projectors[1][row, col] + P_bottom = projectors[1][row] return @autoopt @tensor corner[χ_in; χ_out] := E_south[χ_in D1 D2; χ1] * C_southwest[χ1; χ2] * P_bottom[χ2 D1 D2; χ_out] end @@ -549,7 +549,7 @@ Apply top projector to northwest corner and north edge. function renormalize_top_corner((row, col), envs::CTMRGEnv, projectors) C_northwest = envs.corners[NORTHWEST, row, _prev(col, end)] E_north = envs.edges[NORTH, row, col] - P_top = projectors[2][_next(row, end), col] + P_top = projectors[2][_next(row, end)] return @autoopt @tensor corner[χ_in; χ_out] := P_top[χ_in; χ1 D1 D2] * C_northwest[χ1; χ2] * E_north[χ2 D1 D2; χ_out] end @@ -705,8 +705,8 @@ function renormalize_west_edge( # For sequential CTMRG scheme ) return renormalize_west_edge( envs.edges[WEST, row, _prev(col, end)], - projectors[1][row, col], - projectors[2][_next(row, end), col], + projectors[1][row], + projectors[2][_next(row, end)], ket[row, col], bra[row, col], ) diff --git a/src/algorithms/ctmrg/ctmrg.jl b/src/algorithms/ctmrg/ctmrg.jl index 04b8651b..27f8745a 100644 --- a/src/algorithms/ctmrg/ctmrg.jl +++ b/src/algorithms/ctmrg/ctmrg.jl @@ -141,22 +141,23 @@ and also returns the truncation error. """ function ctmrg_iter(state, envs::CTMRGEnv, alg::SequentialCTMRG) ϵ = zero(real(scalartype(state))) - for _ in 1:4 - # left move - enlarged_envs = ctmrg_expand(state, envs, alg) - projectors, info = ctmrg_projectors(enlarged_envs, envs, alg) - envs = ctmrg_renormalize(projectors, state, envs, alg) - - # rotate + for _ in 1:4 # rotate + for col in 1:size(state, 2) # left move column-wise + enlarged_envs = ctmrg_expand( + eachcoordinate(envs, [4, 1])[:, :, col], state, envs + ) + projectors, info = ctmrg_projectors(col, enlarged_envs, envs, alg) + envs = ctmrg_renormalize(col, projectors, state, envs, alg) + ϵ = max(ϵ, info.err) + end state = rotate_north(state, EAST) envs = rotate_north(envs, EAST) - ϵ = max(ϵ, info.err) end return envs, (; err=ϵ) end function ctmrg_iter(state, envs::CTMRGEnv, alg::SimultaneousCTMRG) - enlarged_envs = ctmrg_expand(state, envs, alg) + enlarged_envs = ctmrg_expand(eachcoordinate(state, 1:4), state, envs) projectors, info = ctmrg_projectors(enlarged_envs, envs, alg) envs′ = ctmrg_renormalize(enlarged_envs, projectors, state, envs, alg) return envs′, info @@ -184,14 +185,7 @@ There are two modes of expansion: `M = :sequential` and `M = :simultaneous`. The first mode expands the environment in one direction at a time, for convenience towards the left. The second mode expands the environment in all four directions simultaneously. """ -function ctmrg_expand(state, envs::CTMRGEnv, ::SequentialCTMRG) - return ctmrg_expand([4, 1], state, envs) -end -function ctmrg_expand(state, envs::CTMRGEnv, ::SimultaneousCTMRG) - return ctmrg_expand(1:4, state, envs) -end -function ctmrg_expand(dirs, state, envs::CTMRGEnv) - coordinates = eachcoordinate(state, dirs) +function ctmrg_expand(coordinates, state, envs::CTMRGEnv) return dtmap(idx -> TensorMap(EnlargedCorner(state, envs, idx), idx[1]), coordinates) end @@ -206,17 +200,16 @@ Compute the CTMRG projectors based from enlarged environments. In the `:simultaneous` mode, the environment SVD is run in parallel. """ function ctmrg_projectors( - enlarged_envs, envs::CTMRGEnv{C,E}, alg::SequentialCTMRG + col::Int, enlarged_envs, envs::CTMRGEnv{C,E}, alg::SequentialCTMRG ) where {C,E} projector_alg = alg.projector_alg ϵ = zero(real(scalartype(envs))) - coordinates = eachcoordinate(envs) + # SVD half-infinite environment + coordinates = eachcoordinate(envs)[:, col] projectors = dtmap(coordinates) do (r, c) - # SVD half-infinite environment r′ = _prev(r, size(envs.corners, 2)) - QQ = halfinfinite_environment(enlarged_envs[1, r, c], enlarged_envs[2, r′, c]) - + QQ = halfinfinite_environment(enlarged_envs[1, r], enlarged_envs[2, r′]) trscheme = truncation_scheme(projector_alg, envs.edges[WEST, r′, c]) svd_alg = svd_algorithm(projector_alg, (WEST, r, c)) U, S, V, ϵ_local = PEPSKit.tsvd!(QQ, svd_alg; trunc=trscheme) @@ -231,9 +224,8 @@ function ctmrg_projectors( end # Compute projectors - return build_projectors(U, S, V, enlarged_envs[1, r, c], enlarged_envs[2, r′, c]) + return build_projectors(U, S, V, enlarged_envs[1, r], enlarged_envs[2, r′]) end - return (map(first, projectors), map(last, projectors)), (; err=ϵ) end function ctmrg_projectors( @@ -339,32 +331,29 @@ end Apply projectors to renormalize corners and edges. """ -function ctmrg_renormalize(projectors, state, envs, ::SequentialCTMRG) +function ctmrg_renormalize(col::Int, projectors, state, envs, ::SequentialCTMRG) corners = Zygote.Buffer(envs.corners) edges = Zygote.Buffer(envs.edges) - # copy environments that do not participate - for dir in (NORTHEAST, SOUTHEAST) - for r in axes(envs.corners, 2), c in axes(envs.corners, 3) - corners[dir, r, c] = envs.corners[dir, r, c] - end + for (dir, r, c) in eachcoordinate(state, 1:4) + (c == col && dir in [SOUTHWEST, NORTHWEST]) && continue + corners[dir, r, c] = envs.corners[dir, r, c] end - for dir in (NORTH, EAST, SOUTH) - for r in axes(envs.corners, 2), c in axes(envs.corners, 3) - edges[dir, r, c] = envs.edges[dir, r, c] - end + for (dir, r, c) in eachcoordinate(state, 1:4) + (c == col && dir == WEST) && continue + edges[dir, r, c] = envs.edges[dir, r, c] end - # Apply projectors to renormalize corners and edges - for (r, c) in eachcoordinate(state) - C_southwest = renormalize_bottom_corner((r, c), envs, projectors) - corners[SOUTHWEST, r, c] = C_southwest / norm(C_southwest) + # Apply projectors to renormalize corners and edge + for row in axes(envs.corners, 2) + C_southwest = renormalize_bottom_corner((row, col), envs, projectors) + corners[SOUTHWEST, row, col] = C_southwest / norm(C_southwest) - C_northwest = renormalize_top_corner((r, c), envs, projectors) - corners[NORTHWEST, r, c] = C_northwest / norm(C_northwest) + C_northwest = renormalize_top_corner((row, col), envs, projectors) + corners[NORTHWEST, row, col] = C_northwest / norm(C_northwest) - E_west = renormalize_west_edge((r, c), envs, projectors, state) - edges[WEST, r, c] = E_west / norm(E_west) + E_west = renormalize_west_edge((row, col), envs, projectors, state) + edges[WEST, row, col] = E_west / norm(E_west) end return CTMRGEnv(copy(corners), copy(edges)) diff --git a/test/heisenberg.jl b/test/heisenberg.jl index 1c05b084..c4941c40 100644 --- a/test/heisenberg.jl +++ b/test/heisenberg.jl @@ -12,6 +12,12 @@ ctm_alg = CTMRG() opt_alg = PEPSOptimize(; boundary_alg=ctm_alg, optimizer=LBFGS(4; gradtol=1e-3, verbosity=2) ) +ctm_alg = CTMRG(; ctmrgscheme=:sequential) +opt_alg = PEPSOptimize(; + boundary_alg=ctm_alg, + optimizer=LBFGS(4; gradtol=1e-3, verbosity=2), + gradient_alg=LinSolver(; iterscheme=:diffgauge), +) # initialize states Random.seed!(91283219347) From d9b08bbe3709d0cdbf2e253e94d645af424cd6ec Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Tue, 5 Nov 2024 16:27:32 +0100 Subject: [PATCH 170/213] Update docstrings --- src/algorithms/ctmrg/ctmrg.jl | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/algorithms/ctmrg/ctmrg.jl b/src/algorithms/ctmrg/ctmrg.jl index 27f8745a..8e74436f 100644 --- a/src/algorithms/ctmrg/ctmrg.jl +++ b/src/algorithms/ctmrg/ctmrg.jl @@ -137,7 +137,7 @@ end ctmrg_iter(state, envs::CTMRGEnv, alg::CTMRG) -> envs′, info Perform one iteration of CTMRG that maps the `state` and `envs` to a new environment, -and also returns the truncation error. +and also returns the `info` `NamedTuple`. """ function ctmrg_iter(state, envs::CTMRGEnv, alg::SequentialCTMRG) ϵ = zero(real(scalartype(state))) @@ -178,12 +178,9 @@ ctmrg_logcancel!(log, iter, η, N) = @warnv 1 logcancel!(log, iter, η, N) # ======================================================================================== # """ - ctmrg_expand(state, envs, alg::CTMRG{M}) + ctmrg_expand(coordinates, state, envs) -Expand the environment by absorbing a new PEPS tensor. -There are two modes of expansion: `M = :sequential` and `M = :simultaneous`. -The first mode expands the environment in one direction at a time, for convenience towards -the left. The second mode expands the environment in all four directions simultaneously. +Expand the environment by absorbing a new PEPS tensor on the given coordinates. """ function ctmrg_expand(coordinates, state, envs::CTMRGEnv) return dtmap(idx -> TensorMap(EnlargedCorner(state, envs, idx), idx[1]), coordinates) @@ -194,10 +191,12 @@ end # ======================================================================================== # """ - ctmrg_projectors(enlarged_envs, env, alg::CTMRG{M}) + ctmrg_projectors(col::Int, enlarged_envs, env, alg::CTMRG{:sequential}) + ctmrg_projectors(enlarged_envs, env, alg::CTMRG{:simultaneous}) -Compute the CTMRG projectors based from enlarged environments. -In the `:simultaneous` mode, the environment SVD is run in parallel. +Compute the CTMRG projectors based on enlarged environments. +In the `:sequential` mode the projectors are computed for the column `col`, whereas +in the `:simultaneous` mode, all projectors (and corresponding SVDs) are computed in parallel. """ function ctmrg_projectors( col::Int, enlarged_envs, envs::CTMRGEnv{C,E}, alg::SequentialCTMRG @@ -327,9 +326,12 @@ end # ======================================================================================== # """ - ctmrg_renormalize(enlarged_envs, projectors, state, envs, alg::CTMRG{M}) + ctmrg_renormalize(col::Int, projectors, state, envs, ::CTMRG{:sequential}) + ctmrg_renormalize(enlarged_envs, projectors, state, envs, ::CTMRG{:simultaneous}) Apply projectors to renormalize corners and edges. +The `:sequential` mode renormalizes the environment on the column `col`, where as the +`:simultaneous` mode renormalizes all environment tensors simultaneously. """ function ctmrg_renormalize(col::Int, projectors, state, envs, ::SequentialCTMRG) corners = Zygote.Buffer(envs.corners) From 8d814db32e69c4e44d25096b29b2316b20023e25 Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Tue, 5 Nov 2024 16:42:38 +0100 Subject: [PATCH 171/213] Remove duplicate lines in Heisenberg test --- test/heisenberg.jl | 6 ------ 1 file changed, 6 deletions(-) diff --git a/test/heisenberg.jl b/test/heisenberg.jl index c4941c40..1c05b084 100644 --- a/test/heisenberg.jl +++ b/test/heisenberg.jl @@ -12,12 +12,6 @@ ctm_alg = CTMRG() opt_alg = PEPSOptimize(; boundary_alg=ctm_alg, optimizer=LBFGS(4; gradtol=1e-3, verbosity=2) ) -ctm_alg = CTMRG(; ctmrgscheme=:sequential) -opt_alg = PEPSOptimize(; - boundary_alg=ctm_alg, - optimizer=LBFGS(4; gradtol=1e-3, verbosity=2), - gradient_alg=LinSolver(; iterscheme=:diffgauge), -) # initialize states Random.seed!(91283219347) From 8c03cc220ff25586f9afc20ffaffe5d1438cc9ee Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Wed, 6 Nov 2024 11:53:13 +0100 Subject: [PATCH 172/213] Stabilize large unit cell energy test --- test/ctmrg/ctmrgschemes.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/ctmrg/ctmrgschemes.jl b/test/ctmrg/ctmrgschemes.jl index cf556be2..406d4c1f 100644 --- a/test/ctmrg/ctmrgschemes.jl +++ b/test/ctmrg/ctmrgschemes.jl @@ -48,7 +48,7 @@ unitcells = [(1, 1), (3, 4)] H = heisenberg_XYZ(InfiniteSquare(unitcell...)) E_sequential = costfun(psi, env_sequential, H) E_simultaneous = costfun(psi, env_simultaneous, H) - @test E_sequential ≈ E_simultaneous rtol = 1e-4 + @test E_sequential ≈ E_simultaneous rtol = 1e-3 end # test fixedspace actually fixes space From 08cf1dcd14e964241b6894da96df40cd9398cb20 Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Fri, 8 Nov 2024 12:32:43 +0100 Subject: [PATCH 173/213] Print degenerate S warning only if Zygote.isderiving() (#92) --- src/algorithms/ctmrg/ctmrg.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/algorithms/ctmrg/ctmrg.jl b/src/algorithms/ctmrg/ctmrg.jl index 04b8651b..000e91e2 100644 --- a/src/algorithms/ctmrg/ctmrg.jl +++ b/src/algorithms/ctmrg/ctmrg.jl @@ -223,7 +223,7 @@ function ctmrg_projectors( ϵ = max(ϵ, ϵ_local / norm(S)) # Compute SVD truncation error and check for degenerate singular values - ignore_derivatives() do + Zygote.isderiving() && ignore_derivatives() do if alg.verbosity > 0 && is_degenerate_spectrum(S) svals = TensorKit.SectorDict(c => diag(b) for (c, b) in blocks(S)) @warn("degenerate singular values detected: ", svals) @@ -273,7 +273,7 @@ function ctmrg_projectors( ϵ = max(ϵ, ϵ_local / norm(S_local)) # Compute SVD truncation error and check for degenerate singular values - ignore_derivatives() do + Zygote.isderiving() && ignore_derivatives() do if alg.verbosity > 0 && is_degenerate_spectrum(S_local) svals = TensorKit.SectorDict(c => diag(b) for (c, b) in blocks(S_local)) @warn("degenerate singular values detected: ", svals) From f5086639acb3fe351b32648347f4d1c65a8fdf46 Mon Sep 17 00:00:00 2001 From: Lukas Devos Date: Fri, 15 Nov 2024 03:39:20 -0500 Subject: [PATCH 174/213] Excise recursivevec (#95) --- src/operators/derivatives.jl | 26 ++++++++------------------ 1 file changed, 8 insertions(+), 18 deletions(-) diff --git a/src/operators/derivatives.jl b/src/operators/derivatives.jl index 36c0d149..dd025411 100644 --- a/src/operators/derivatives.jl +++ b/src/operators/derivatives.jl @@ -48,11 +48,9 @@ end (H::PEPS_∂∂C)(x) = MPSKit.∂C(x, H.GL, H.GR) (H::PEPS_∂∂AC)(x) = MPSKit.∂AC(x, (H.top, H.bot), H.GL, H.GR) -function MPSKit.∂AC(x::RecursiveVec, O::Tuple, GL, GR) - return RecursiveVec( - circshift( - map((v, O1, O2, l, r) -> ∂AC(v, (O1, O2), l, r), x.vecs, O[1], O[2], GL, GR), 1 - ), +function MPSKit.∂AC(x::Vector, O::Tuple, GL, GR) + return circshift( + map((v, O1, O2, l, r) -> ∂AC(v, (O1, O2), l, r), x, O[1], O[2], GL, GR), 1 ) end @@ -203,20 +201,12 @@ end (H::PEPO_∂∂C)(x) = MPSKit.∂C(x, H.GL, H.GR) (H::PEPO_∂∂AC)(x) = MPSKit.∂AC(x, (H.top, H.bot, H.mid), H.GL, H.GR) -function MPSKit.∂AC(x::RecursiveVec, O::Tuple{T,T,P}, GL, GR) where {T,P} - return RecursiveVec( - circshift( - map( - (v, O1, O2, O3, l, r) -> ∂AC(v, (O1, O2, O3), l, r), - x.vecs, - O[1], - O[2], - O[3], - GL, - GR, - ), - 1, +function MPSKit.∂AC(x::Vector, O::Tuple{T,T,P}, GL, GR) where {T,P} + return circshift( + map( + (v, O1, O2, O3, l, r) -> ∂AC(v, (O1, O2, O3), l, r), x, O[1], O[2], O[3], GL, GR ), + 1, ) end From 89babcd8ff9175ea5174bf96f875af8d02d1251d Mon Sep 17 00:00:00 2001 From: Lukas Devos Date: Fri, 15 Nov 2024 06:28:33 -0500 Subject: [PATCH 175/213] Bump version v0.3.0 --- Project.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Project.toml b/Project.toml index 9751d890..411543d2 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "PEPSKit" uuid = "52969e89-939e-4361-9b68-9bc7cde4bdeb" -authors = ["Maarten Vandamme", "Paul Brehmer", "Lander Burgelman", "Rui-Zhen Huang", "Daan Maertens", "Lukas Devos "] +version = "0.3.0" [deps] Accessors = "7d9f7c33-5ae7-4f3b-8dc6-eff91059b697" From 90375ca9b34b8c3fc2c7e3550cbdfb033163ccff Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 15 Nov 2024 06:29:01 -0500 Subject: [PATCH 176/213] CompatHelper: bump compat for VectorInterface to 0.5, (keep existing compat) (#96) Co-authored-by: CompatHelper Julia --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 411543d2..cffc9c1a 100644 --- a/Project.toml +++ b/Project.toml @@ -40,7 +40,7 @@ Random = "1" Statistics = "1" TensorKit = "0.12.5" TensorOperations = "4" -VectorInterface = "0.4" +VectorInterface = "0.4, 0.5" Zygote = "0.6" julia = "1.10" From 13827ecc2cb800cb119e4adf4fa38cf04cd8a307 Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Thu, 28 Nov 2024 18:31:31 +0100 Subject: [PATCH 177/213] Add HalfInfiniteProjector and FullInfiniteProjector, add beginnings of implementation --- src/PEPSKit.jl | 4 +- src/algorithms/ctmrg/ctmrg.jl | 102 +++++++++++++++++++++++++--------- 2 files changed, 79 insertions(+), 27 deletions(-) diff --git a/src/PEPSKit.jl b/src/PEPSKit.jl index c8a6b032..388b6efe 100644 --- a/src/PEPSKit.jl +++ b/src/PEPSKit.jl @@ -59,9 +59,10 @@ include("utility/symmetrization.jl") const ctmrgscheme = :simultaneous const reuse_env = true const trscheme = FixedSpaceTruncation() - const fwd_alg = TensorKit.SVD() + const fwd_alg = TensorKit.SDD() const rrule_alg = Arnoldi(; tol=1e-2fpgrad_tol, krylovdim=48, verbosity=-1) const svd_alg = SVDAdjoint(; fwd_alg, rrule_alg) + const projector_alg = HalfInfiniteProjector const optimizer = LBFGS(32; maxiter=100, gradtol=1e-4, verbosity=2) const gradient_linsolver = KrylovKit.BiCGStab(; maxiter=Defaults.fpgrad_maxiter, tol=Defaults.fpgrad_tol @@ -84,6 +85,7 @@ Module containing default values that represent typical algorithm parameters. - `fwd_alg`: SVD algorithm that is used in the forward pass - `rrule_alg`: Reverse-rule for differentiating that SVD - `svd_alg`: Combination of `fwd_alg` and `rrule_alg` +- `projector_alg`: Algorithm to compute CTMRG projectors - `optimizer`: Optimization algorithm for PEPS ground-state optimization - `gradient_linsolver`: Default linear solver for the `LinSolver` gradient algorithm - `iterscheme`: Scheme for differentiating one CTMRG iteration diff --git a/src/algorithms/ctmrg/ctmrg.jl b/src/algorithms/ctmrg/ctmrg.jl index 000e91e2..7ee2bc7c 100644 --- a/src/algorithms/ctmrg/ctmrg.jl +++ b/src/algorithms/ctmrg/ctmrg.jl @@ -8,31 +8,32 @@ have different spaces, this truncation style is different from `TruncationSpace` struct FixedSpaceTruncation <: TensorKit.TruncationScheme end """ - struct ProjectorAlg{S}(; svd_alg=TensorKit.SVD(), trscheme=TensorKit.notrunc(), - fixedspace=false, verbosity=0) - -Algorithm struct collecting all projector related parameters. The truncation scheme has to be -a `TensorKit.TruncationScheme`, and some SVD algorithms might have further restrictions on what -kind of truncation scheme can be used. If `fixedspace` is true, the truncation scheme is set to -`truncspace(V)` where `V` is the environment bond space, adjusted to the corresponding -environment direction/unit cell entry. + struct HalfInfiniteProjector{S,T}(; svd_alg=Defaults.svd_alg, + trscheme=Defaults.trscheme, verbosity=0) + +Projector algorithm implementing projectors from SVDing the half-infinite CTMRG environment. """ -@kwdef struct ProjectorAlg{S<:SVDAdjoint,T} +@kwdef struct HalfInfiniteProjector{S<:SVDAdjoint,T} svd_alg::S = Defaults.svd_alg trscheme::T = Defaults.trscheme verbosity::Int = 0 end -# TODO: add option for different projector styles (half-infinite, full-infinite, etc.) -function truncation_scheme(alg::ProjectorAlg, edge) - if alg.trscheme isa FixedSpaceTruncation - return truncspace(space(edge, 1)) - else - return alg.trscheme - end +""" + struct FullInfiniteProjector{S,T}(; svd_alg=Defaults.svd_alg, + trscheme=Defaults.trscheme, verbosity=0) + +Projector algorithm implementing projectors from SVDing the full 4x4 CTMRG environment. +""" +@kwdef struct FullInfiniteProjector{S<:SVDAdjoint,T} + svd_alg::S = Defaults.svd_alg + trscheme::T = Defaults.trscheme + verbosity::Int = 0 end -function svd_algorithm(alg::ProjectorAlg, (dir, r, c)) +const ProjectorAlgs = Union{HalfInfiniteProjector,FullInfiniteProjector} + +function svd_algorithm(alg::ProjectorAlgs, (dir, r, c)) if alg.svd_alg isa SVDAdjoint{<:FixedSVD} fwd_alg = alg.svd_alg.fwd_alg fix_svd = FixedSVD(fwd_alg.U[dir, r, c], fwd_alg.S[dir, r, c], fwd_alg.V[dir, r, c]) @@ -42,6 +43,14 @@ function svd_algorithm(alg::ProjectorAlg, (dir, r, c)) end end +function truncation_scheme(alg::ProjectorAlgs, edge) + if alg.trscheme isa FixedSpaceTruncation + return truncspace(space(edge, 1)) + else + return alg.trscheme + end +end + """ CTMRG(; tol=Defaults.ctmrg_tol, maxiter=Defaults.ctmrg_maxiter, miniter=Defaults.ctmrg_miniter, verbosity=0, @@ -64,12 +73,12 @@ computed on the western side, and then applied and rotated. Or with `:simultaneo are computed and applied simultaneously on all sides, where in particular the corners get contracted with two projectors at the same time. """ -struct CTMRG{S} +struct CTMRG{S,P<:ProjectorAlgs} tol::Float64 maxiter::Int miniter::Int verbosity::Int - projector_alg::ProjectorAlg + projector_alg::P end function CTMRG(; tol=Defaults.ctmrg_tol, @@ -81,15 +90,19 @@ function CTMRG(; ctmrgscheme::Symbol=Defaults.ctmrgscheme, ) return CTMRG{ctmrgscheme}( - tol, maxiter, miniter, verbosity, ProjectorAlg(; svd_alg, trscheme, verbosity) + tol, + maxiter, + miniter, + verbosity, + Defaults.projector_alg(; svd_alg, trscheme, verbosity), ) end ctmrgscheme(::CTMRG{S}) where {S} = S # aliases for the different CTMRG schemes -const SequentialCTMRG = CTMRG{:sequential} -const SimultaneousCTMRG = CTMRG{:simultaneous} +const SequentialCTMRG{P} = CTMRG{:sequential,P} +const SimultaneousCTMRG{P} = CTMRG{:simultaneous,P} # supply correct constructor for Accessors.@set Accessors.constructorof(::Type{CTMRG{S}}) where {S} = CTMRG{S} @@ -139,7 +152,7 @@ end Perform one iteration of CTMRG that maps the `state` and `envs` to a new environment, and also returns the truncation error. """ -function ctmrg_iter(state, envs::CTMRGEnv, alg::SequentialCTMRG) +function ctmrg_iter(state, envs::CTMRGEnv, alg::SequentialCTMRG{}) ϵ = zero(real(scalartype(state))) for _ in 1:4 # left move @@ -155,7 +168,7 @@ function ctmrg_iter(state, envs::CTMRGEnv, alg::SequentialCTMRG) return envs, (; err=ϵ) end -function ctmrg_iter(state, envs::CTMRGEnv, alg::SimultaneousCTMRG) +function ctmrg_iter(state, envs::CTMRGEnv, alg::SimultaneousCTMRG{}) enlarged_envs = ctmrg_expand(state, envs, alg) projectors, info = ctmrg_projectors(enlarged_envs, envs, alg) envs′ = ctmrg_renormalize(enlarged_envs, projectors, state, envs, alg) @@ -206,7 +219,7 @@ Compute the CTMRG projectors based from enlarged environments. In the `:simultaneous` mode, the environment SVD is run in parallel. """ function ctmrg_projectors( - enlarged_envs, envs::CTMRGEnv{C,E}, alg::SequentialCTMRG + enlarged_envs, envs::CTMRGEnv{C,E}, alg::SequentialCTMRG{<:HalfInfiniteProjector} ) where {C,E} projector_alg = alg.projector_alg ϵ = zero(real(scalartype(envs))) @@ -237,7 +250,44 @@ function ctmrg_projectors( return (map(first, projectors), map(last, projectors)), (; err=ϵ) end function ctmrg_projectors( - enlarged_envs, envs::CTMRGEnv{C,E}, alg::SimultaneousCTMRG + enlarged_envs, envs::CTMRGEnv{C,E}, alg::SequentialCTMRG{<:FullInfiniteProjector} +) where {C,E} + projector_alg = alg.projector_alg + ϵ = zero(real(scalartype(envs))) + + coordinates = eachcoordinate(envs) + projectors = dtmap(coordinates) do (r, c) + # SVD half-infinite environment + r′ = _prev(r, size(envs.corners, 2)) + c′ = _next(c, size(envs.corners, 3)) + QQ = fullinfinite_environment( + enlarged_envs[1, r, c], + enlarged_envs[2, r′, c], + enlarged_envs[3, r′, c′], + enlarged_envs[4, r, c′], # TODO: break up the CTMRG expand/project/renormalize scheme! + ) + + trscheme = truncation_scheme(projector_alg, envs.edges[WEST, r′, c]) + svd_alg = svd_algorithm(projector_alg, (WEST, r, c)) + U, S, V, ϵ_local = PEPSKit.tsvd!(QQ, svd_alg; trunc=trscheme) + ϵ = max(ϵ, ϵ_local / norm(S)) + + # Compute SVD truncation error and check for degenerate singular values + Zygote.isderiving() && ignore_derivatives() do + if alg.verbosity > 0 && is_degenerate_spectrum(S) + svals = TensorKit.SectorDict(c => diag(b) for (c, b) in blocks(S)) + @warn("degenerate singular values detected: ", svals) + end + end + + # Compute projectors + return build_projectors(U, S, V, enlarged_envs[1, r, c], enlarged_envs[2, r′, c]) + end + + return (map(first, projectors), map(last, projectors)), (; err=ϵ) +end +function ctmrg_projectors( + enlarged_envs, envs::CTMRGEnv{C,E}, alg::SimultaneousCTMRG{<:HalfInfiniteProjector} ) where {C,E} projector_alg = alg.projector_alg # pre-allocation From 8d72d7cb04a2cbec0f8a3598a5b13c1b3b2aaebd Mon Sep 17 00:00:00 2001 From: Lukas Devos Date: Mon, 2 Dec 2024 09:36:09 -0500 Subject: [PATCH 178/213] Generated functions expand their macros (#101) --- src/algorithms/contractions/localoperator.jl | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/algorithms/contractions/localoperator.jl b/src/algorithms/contractions/localoperator.jl index fef30e20..324bb028 100644 --- a/src/algorithms/contractions/localoperator.jl +++ b/src/algorithms/contractions/localoperator.jl @@ -237,9 +237,10 @@ end end end - return quote + returnex = quote @tensor opt = $opt_ex $multiplication_ex end + return macroexpand(@__MODULE__, returnex) end """ @@ -445,7 +446,8 @@ end end end - return quote + returnex = quote @tensor opt = $opt_ex $multiplication_ex end + return macroexpand(@__MODULE__, returnex) end From fc5d75478d9c7f9a3ea1534570291287af9f98f4 Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Tue, 3 Dec 2024 17:13:38 +0100 Subject: [PATCH 179/213] Add warning to fixedpoint for real envs --- src/algorithms/peps_opt.jl | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/algorithms/peps_opt.jl b/src/algorithms/peps_opt.jl index b84467da..8ba4cefe 100644 --- a/src/algorithms/peps_opt.jl +++ b/src/algorithms/peps_opt.jl @@ -142,13 +142,13 @@ The function returns a `NamedTuple` which contains the following entries: - `numfg`: total number of calls to the energy function """ function fixedpoint( - ψ₀::InfinitePEPS{T}, + ψ₀::InfinitePEPS{F}, H, alg::PEPSOptimize, - env₀::CTMRGEnv=CTMRGEnv(ψ₀, field(T)^20); + env₀::CTMRGEnv{C,T}=CTMRGEnv(ψ₀, field(F)^20); (finalize!)=OptimKit._finalize!, symmetrization=nothing, -) where {T} +) where {F,C,T} if isnothing(symmetrization) retract = peps_retract else @@ -157,6 +157,13 @@ function fixedpoint( finalize! = (x, f, g, numiter) -> fin!(symm_finalize!(x, f, g, numiter)..., numiter) end + if scalartype(C) <: Real || scalartype(T) <: Real + env₀ = CTMRGEnv(complex.(env₀.corners), complex.(env₀.edges)) + @warn "the provided real environment was converted to a complex environment since\ + :fixed mode generally produces complex gauges; use :diffgauge mode instead to work\ + with purely real environments" + end + (peps, env), E, ∂E, numfg, convhistory = optimize( (ψ₀, env₀), alg.optimizer; retract, inner=real_inner, finalize! ) do (peps, envs) From ccb366f70daeb45a17fcdbe221434c147dfde680 Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Tue, 3 Dec 2024 17:50:55 +0100 Subject: [PATCH 180/213] Add half-infinite contractions methods and diagrams, refactor names slightly --- .../contractions/ctmrg_contractions.jl | 143 ++++++++++++++++-- src/algorithms/ctmrg/ctmrg.jl | 5 +- src/algorithms/ctmrg/sparse_environments.jl | 10 +- 3 files changed, 142 insertions(+), 16 deletions(-) diff --git a/src/algorithms/contractions/ctmrg_contractions.jl b/src/algorithms/contractions/ctmrg_contractions.jl index e53bf2ac..1ef726a6 100644 --- a/src/algorithms/contractions/ctmrg_contractions.jl +++ b/src/algorithms/contractions/ctmrg_contractions.jl @@ -210,12 +210,12 @@ function right_projector(E_1, C, E_2, U, isqS, ket::PEPSTensor, bra::PEPSTensor= end """ - halfinfinite_environment(quadrant1::AbstractTensorMap{S,3,3}, quadrant2::AbstractTensorMap{S,3,3}) - halfinfinite_environment(C_1, C_2, E_1, E_2, E_3, E_4, + half_infinite_environment(quadrant1::AbstractTensorMap{S,3,3}, quadrant2::AbstractTensorMap{S,3,3}) + half_infinite_environment(C_1, C_2, E_1, E_2, E_3, E_4, ket_1::P, ket_2::P, bra_1::P=ket_1, bra_2::P=ket_2) where {P<:PEPSTensor} - halfinfinite_environment(C_1, C_2, E_1, E_2, E_3, E_4, x, + half_infinite_environment(C_1, C_2, E_1, E_2, E_3, E_4, x, ket_1::P, ket_2::P, bra_1::P=ket_1, bra_2::P=ket_2) where {P<:PEPSTensor} - halfinfinite_environment(x, C_1, C_2, E_1, E_2, E_3, E_4, + half_infinite_environment(x, C_1, C_2, E_1, E_2, E_3, E_4, ket_1::P, ket_2::P, bra_1::P=ket_1, bra_2::P=ket_2) where {P<:PEPSTensor} Contract two quadrants (enlarged corners) to form a half-infinite environment. @@ -236,7 +236,7 @@ The environment can also be contracted directly from all its constituent tensors | || || | ``` -Alternatively, contract environment with a vector `x` acting on it +Alternatively, contract the environment with a vector `x` acting on it ``` C_1 -- E_2 -- E_3 -- C_2 @@ -249,14 +249,14 @@ Alternatively, contract environment with a vector `x` acting on it or contract the adjoint environment with `x`, e.g. as needed for iterative solvers. """ -function halfinfinite_environment( +function half_infinite_environment( quadrant1::AbstractTensorMap{S,3,3}, quadrant2::AbstractTensorMap{S,3,3} ) where {S} return @autoopt @tensor env[χ_in D_inabove D_inbelow; χ_out D_outabove D_outbelow] := quadrant1[χ_in D_inabove D_inbelow; χ D1 D2] * quadrant2[χ D1 D2; χ_out D_outabove D_outbelow] end -function halfinfinite_environment( +function half_infinite_environment( C_1, C_2, E_1, E_2, E_3, E_4, ket_1::P, ket_2::P, bra_1::P=ket_1, bra_2::P=ket_2 ) where {P<:PEPSTensor} return @autoopt @tensor env[χ_in D_inabove D_inbelow; χ_out D_outabove D_outbelow] := @@ -271,7 +271,7 @@ function halfinfinite_environment( C_2[χ4; χ5] * E_4[χ5 D7 D8; χ_out] end -function halfinfinite_environment( +function half_infinite_environment( C_1, C_2, E_1, @@ -297,7 +297,7 @@ function halfinfinite_environment( E_4[χ5 D7 D8; χ6] * x[χ6 D11 D12] end -function halfinfinite_environment( +function half_infinite_environment( x::AbstractTensor{S,3}, C_1, C_2, @@ -324,6 +324,131 @@ function halfinfinite_environment( conj(E_4[χ6 D9 D10; χ_in]) end +""" + +Contract four quadrants (enlarged corners) to form a full-infinite environment where the +open bonds can be on any side (diagrams show open bonds in the west). + +``` + |~~~~~~~~~| -- |~~~~~~~~~| + |quadrant1| |quadrant2| + |~~~~~~~~~| == |~~~~~~~~~| + | || || | + || | + | || || | + |~~~~~~~~~| -- |~~~~~~~~~| + |quadrant4| |quadrant3| + |~~~~~~~~~| == |~~~~~~~~~| +``` + +The environment can also be contracted directly from all its constituent tensors. + +``` + C_1 -- E_2 -- E_3 -- C_2 + | || || | + E_1 == ket_bra_1 == ket_bra_2 == E_4 + | || || | + || | + | || || | + E_8 == ket_bra_4 == ket_bra_3 == E_5 + | || || | + C_4 -- E_7 -- E_6 -- C_3 +``` + +Alternatively, contract the environment with a vector `x` acting on it + +``` + C_1 -- E_2 -- E_3 -- C_2 + | || || | + E_1 == ket_bra_1 == ket_bra_2 == E_4 + | || || | + || | + || | + | || || | + [~~~~x~~~~~] || | + | || || | + E_8 == ket_bra_4 == ket_bra_3 == E_5 + | || || | + C_4 -- E_7 -- E_6 -- C_3 + +``` +or contract the adjoint environment with `x`, e.g. as needed for iterative solvers. +""" +function full_infinite_environment( + quadrant1::AbstractTensorMap{S,3,3}, + quadrant2::AbstractTensorMap{S,3,3}, + quadrant3::AbstractTensorMap{S,3,3}, + quadrant4::AbstractTensorMap{S,3,3}, +) where {S} end +function full_infinite_environment( + C_1, + C_2, + C_3, + C_4, + E_1, + E_2, + E_3, + E_4, + E_5, + E_6, + E_7, + E_8, + ket_1::P, + ket_2::P, + ket_3::P, + ket_4::P, + bra_1::P=ket_1, + bra_2::P=ket_2, + bra_3::P=ket_3, + bra_4::P=ket_4, +) where {P<:PEPSTensor} end +function full_infinite_environment( + C_1, + C_2, + C_3, + C_4, + E_1, + E_2, + E_3, + E_4, + E_5, + E_6, + E_7, + E_8, + x::AbstractTensor{S,3}, + ket_1::P, + ket_2::P, + ket_3::P, + ket_4::P, + bra_1::P=ket_1, + bra_2::P=ket_2, + bra_3::P=ket_3, + bra_4::P=ket_4, +) where {S,P<:PEPSTensor} end +function full_infinite_environment( + x::AbstractTensor{S,3}, + C_1, + C_2, + C_3, + C_4, + E_1, + E_2, + E_3, + E_4, + E_5, + E_6, + E_7, + E_8, + ket_1::P, + ket_2::P, + ket_3::P, + ket_4::P, + bra_1::P=ket_1, + bra_2::P=ket_2, + bra_3::P=ket_3, + bra_4::P=ket_4, +) where {S,P<:PEPSTensor} end + # Renormalization contractions # ---------------------------- diff --git a/src/algorithms/ctmrg/ctmrg.jl b/src/algorithms/ctmrg/ctmrg.jl index 7ee2bc7c..ea4a0805 100644 --- a/src/algorithms/ctmrg/ctmrg.jl +++ b/src/algorithms/ctmrg/ctmrg.jl @@ -31,6 +31,7 @@ Projector algorithm implementing projectors from SVDing the full 4x4 CTMRG envir verbosity::Int = 0 end +# TODO: do AbstractProjectorAlg type instead? -> would make it easier for users to implement custom projector alg const ProjectorAlgs = Union{HalfInfiniteProjector,FullInfiniteProjector} function svd_algorithm(alg::ProjectorAlgs, (dir, r, c)) @@ -228,7 +229,7 @@ function ctmrg_projectors( projectors = dtmap(coordinates) do (r, c) # SVD half-infinite environment r′ = _prev(r, size(envs.corners, 2)) - QQ = halfinfinite_environment(enlarged_envs[1, r, c], enlarged_envs[2, r′, c]) + QQ = half_infinite_environment(enlarged_envs[1, r, c], enlarged_envs[2, r′, c]) trscheme = truncation_scheme(projector_alg, envs.edges[WEST, r′, c]) svd_alg = svd_algorithm(projector_alg, (WEST, r, c)) @@ -310,7 +311,7 @@ function ctmrg_projectors( end # SVD half-infinite environment - QQ = halfinfinite_environment( + QQ = half_infinite_environment( enlarged_envs[dir, r, c], enlarged_envs[_next(dir, 4), next_rc...] ) diff --git a/src/algorithms/ctmrg/sparse_environments.jl b/src/algorithms/ctmrg/sparse_environments.jl index 9243067e..b6ca163b 100644 --- a/src/algorithms/ctmrg/sparse_environments.jl +++ b/src/algorithms/ctmrg/sparse_environments.jl @@ -139,7 +139,7 @@ end Instantiate half-infinite environment as `TensorMap` explicitly. """ function TensorKit.TensorMap(env::HalfInfiniteEnv) # Dense operator - return halfinfinite_environment( + return half_infinite_environment( env.C_1, env.C_2, env.E_1, @@ -161,7 +161,7 @@ Contract half-infinite environment with a vector `x`, such that the environment linear map or adjoint linear map on `x` if `Val(true)` or `Val(false)` is passed, respectively. """ function (env::HalfInfiniteEnv)(x, ::Val{false}) # Linear map: env() * x - return halfinfinite_environment( + return half_infinite_environment( env.C_1, env.C_2, env.E_1, @@ -176,7 +176,7 @@ function (env::HalfInfiniteEnv)(x, ::Val{false}) # Linear map: env() * x ) end function (env::HalfInfiniteEnv)(x, ::Val{true}) # Adjoint linear map: env()' * x - return halfinfinite_environment( + return half_infinite_environment( x, env.C_1, env.C_2, @@ -191,8 +191,8 @@ function (env::HalfInfiniteEnv)(x, ::Val{true}) # Adjoint linear map: env()' * ) end -# Wrapper around halfinfinite_environment contraction using EnlargedCorners (used in ctmrg_projectors) -function halfinfinite_environment(ec_1::EnlargedCorner, ec_2::EnlargedCorner) +# Wrapper around half_infinite_environment contraction using EnlargedCorners (used in ctmrg_projectors) +function half_infinite_environment(ec_1::EnlargedCorner, ec_2::EnlargedCorner) return HalfInfiniteEnv( ec_1.C, ec_2.C, From 582e3aeef75eeb92757056671ca3a8d90b36d820 Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Tue, 3 Dec 2024 18:04:54 +0100 Subject: [PATCH 181/213] Add FullInfiniteEnviroment sparse struct --- .../contractions/ctmrg_contractions.jl | 7 +- src/algorithms/ctmrg/sparse_environments.jl | 198 ++++++++++++++++-- 2 files changed, 179 insertions(+), 26 deletions(-) diff --git a/src/algorithms/contractions/ctmrg_contractions.jl b/src/algorithms/contractions/ctmrg_contractions.jl index 1ef726a6..b7deba6b 100644 --- a/src/algorithms/contractions/ctmrg_contractions.jl +++ b/src/algorithms/contractions/ctmrg_contractions.jl @@ -375,11 +375,8 @@ Alternatively, contract the environment with a vector `x` acting on it or contract the adjoint environment with `x`, e.g. as needed for iterative solvers. """ function full_infinite_environment( - quadrant1::AbstractTensorMap{S,3,3}, - quadrant2::AbstractTensorMap{S,3,3}, - quadrant3::AbstractTensorMap{S,3,3}, - quadrant4::AbstractTensorMap{S,3,3}, -) where {S} end + quadrant1::T, quadrant2::T, quadrant3::T, quadrant4::T +) where {T<:AbstractTensorMap{<:ElementarySpace,3,3}} end function full_infinite_environment( C_1, C_2, diff --git a/src/algorithms/ctmrg/sparse_environments.jl b/src/algorithms/ctmrg/sparse_environments.jl index b6ca163b..4926a7f7 100644 --- a/src/algorithms/ctmrg/sparse_environments.jl +++ b/src/algorithms/ctmrg/sparse_environments.jl @@ -97,9 +97,9 @@ function renormalize_southwest_corner(ec::EnlargedCorner, P_left, P_right) ) end -# ------------------ -# Sparse environment -# ------------------ +# -------------------------------- +# Sparse half-infinite environment +# -------------------------------- """ struct HalfInfiniteEnv{C,E,A,A′} @@ -114,8 +114,8 @@ struct HalfInfiniteEnv{C,E,A,A′} # TODO: subtype as AbstractTensorMap once Te E_3::E E_4::E ket_1::A - bra_1::A′ ket_2::A + bra_1::A′ bra_2::A′ end @@ -128,8 +128,10 @@ function HalfInfiniteEnv(quadrant1::EnlargedCorner, quadrant2::EnlargedCorner) quadrant1.E_2, quadrant2.E_1, quadrant2.E_2, - quadrant1.ket_bra, - quadrant2.ket_bra, + quadrant1.ket, + quadrant2.ket, + quadrant1.bra, + quadrant2.bra, ) end @@ -193,24 +195,10 @@ end # Wrapper around half_infinite_environment contraction using EnlargedCorners (used in ctmrg_projectors) function half_infinite_environment(ec_1::EnlargedCorner, ec_2::EnlargedCorner) - return HalfInfiniteEnv( - ec_1.C, - ec_2.C, - ec_1.E_1, - ec_1.E_2, - ec_2.E_1, - ec_2.E_2, - ec_1.ket, - ec_2.ket, - ec_1.bra, - ec_2.bra, - ) + return HalfInfiniteEnv(ec_1, ec_2) end -# ----------------------------------------------------- # AbstractTensorMap subtyping and IterSVD compatibility -# ----------------------------------------------------- - function TensorKit.domain(env::HalfInfiniteEnv) return domain(env.E_4) * domain(env.ket_2)[3] * domain(env.bra_2)[3]' end @@ -222,3 +210,171 @@ end function random_start_vector(env::HalfInfiniteEnv) return Tensor(randn, domain(env)) end + +# -------------------------------- +# Sparse full-infinite environment +# -------------------------------- + +""" + struct FullInfiniteEnv{C,E,A,A′} + +Full-infinite CTMRG environment tensor storage. +""" +struct FullInfiniteEnv{C,E,A,A′} # TODO: subtype as AbstractTensorMap once TensorKit is updated + C_1::C + C_2::C + C_3::C + C_4::C + E_1::E + E_2::E + E_3::E + E_4::E + E_5::E + E_6::E + E_7::E + E_8::E + ket_1::A + ket_2::A + ket_3::A + ket_4::A + bra_1::A′ + bra_2::A′ + bra_3::A′ + bra_4::A′ +end + +# Construct environment from two enlarged corners +function FullInfiniteEnv( + quadrant1::E, quadrant2::E, quadrant3::E, quadrant4::E +) where {E<:EnlargedCorner} + return FullInfiniteEnv( + quadrant1.C, + quadrant2.C, + quadrant3.C, + quadrant4.C, + quadrant1.E_1, + quadrant1.E_2, + quadrant2.E_1, + quadrant2.E_2, + quadrant3.E_1, + quadrant3.E_2, + quadrant4.E_1, + quadrant4.E_2, + quadrant1.ket, + quadrant2.ket, + quadrant3.ket, + quadrant4.ket, + quadrant1.bra, + quadrant2.bra, + quadrant3.bra, + quadrant4.bra, + ) +end + +""" + TensorKit.TensorMap(env::FullInfiniteEnv) + +Instantiate full-infinite environment as `TensorMap` explicitly. +""" +function TensorKit.TensorMap(env::FullInfiniteEnv) # Dense operator + return full_infinite_environment( + env.C_1, + env.C_2, + env.C_3, + env.C_4, + env.E_1, + env.E_2, + env.E_3, + env.E_4, + env.E_2, + env.E_3, + env.E_4, + env.E_5, + env.ket_1, + env.ket_2, + env.ket_3, + env.ket_4, + env.bra_1, + env.bra_2, + env.bra_3, + env.bra_4, + ) +end + +""" + (env::FullInfiniteEnv)(x, ::Val{false}) + (env::FullInfiniteEnv)(x, ::Val{true}) + +Contract full-infinite environment with a vector `x`, such that the environment acts as a +linear map or adjoint linear map on `x` if `Val(true)` or `Val(false)` is passed, respectively. +""" +function (env::FullInfiniteEnv)(x, ::Val{false}) # Linear map: env() * x + return full_infinite_environment( + env.C_1, + env.C_2, + env.C_3, + env.C_4, + env.E_1, + env.E_2, + env.E_3, + env.E_4, + env.E_5, + env.E_6, + env.E_7, + env.E_8, + x, + env.ket_1, + env.ket_2, + env.ket_3, + env.ket_4, + env.bra_1, + env.bra_2, + env.bra_3, + env.bra_4, + ) +end +function (env::FullInfiniteEnv)(x, ::Val{true}) # Adjoint linear map: env()' * x + return full_infinite_environment( + x, + env.C_1, + env.C_2, + env.C_3, + env.C_4, + env.E_1, + env.E_2, + env.E_3, + env.E_4, + env.E_5, + env.E_6, + env.E_7, + env.E_8, + env.ket_1, + env.ket_2, + env.ket_3, + env.ket_4, + env.bra_1, + env.bra_2, + env.bra_3, + env.bra_4, + ) +end + +# Wrapper around full_infinite_environment contraction using EnlargedCorners (used in ctmrg_projectors) +function full_infinite_environment( + ec_1::E, ec_2::E, ec_3::E, ec_4::E +) where {E<:EnlargedCorner} + return FullInfiniteEnv(ec_1, ec_2, ec_3, ec_4) +end + +# AbstractTensorMap subtyping and IterSVD compatibility +function TensorKit.domain(env::FullInfiniteEnv) + return domain(env.E_8) * domain(env.ket_4)[3] * domain(env.bra_4)[3]' +end + +function TensorKit.codomain(env::FullInfiniteEnv) + return codomain(env.E_1)[1] * domain(env.ket_1)[3]' * domain(env.bra_1)[3] +end + +function random_start_vector(env::FullInfiniteEnv) + return Tensor(randn, domain(env)) +end From 1016c6b75a3a26052219dd5558bbdb563fcc0bf6 Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Tue, 3 Dec 2024 18:15:39 +0100 Subject: [PATCH 182/213] Update full-inf docstrings --- .../contractions/ctmrg_contractions.jl | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/algorithms/contractions/ctmrg_contractions.jl b/src/algorithms/contractions/ctmrg_contractions.jl index b7deba6b..17b7b667 100644 --- a/src/algorithms/contractions/ctmrg_contractions.jl +++ b/src/algorithms/contractions/ctmrg_contractions.jl @@ -212,11 +212,11 @@ end """ half_infinite_environment(quadrant1::AbstractTensorMap{S,3,3}, quadrant2::AbstractTensorMap{S,3,3}) half_infinite_environment(C_1, C_2, E_1, E_2, E_3, E_4, - ket_1::P, ket_2::P, bra_1::P=ket_1, bra_2::P=ket_2) where {P<:PEPSTensor} + ket_1::P, ket_2::P, bra_1::P=ket_1, bra_2::P=ket_2) where {P<:PEPSTensor} half_infinite_environment(C_1, C_2, E_1, E_2, E_3, E_4, x, - ket_1::P, ket_2::P, bra_1::P=ket_1, bra_2::P=ket_2) where {P<:PEPSTensor} + ket_1::P, ket_2::P, bra_1::P=ket_1, bra_2::P=ket_2) where {P<:PEPSTensor} half_infinite_environment(x, C_1, C_2, E_1, E_2, E_3, E_4, - ket_1::P, ket_2::P, bra_1::P=ket_1, bra_2::P=ket_2) where {P<:PEPSTensor} + ket_1::P, ket_2::P, bra_1::P=ket_1, bra_2::P=ket_2) where {P<:PEPSTensor} Contract two quadrants (enlarged corners) to form a half-infinite environment. @@ -325,9 +325,19 @@ function half_infinite_environment( end """ + full_infinite_environment( + quadrant1::T, quadrant2::T, quadrant3::T, quadrant4::T + ) where {T<:AbstractTensorMap{<:ElementarySpace,3,3}} + full_infinite_environment(C_1, C_2, C_3, C_4, E_1, E_2, E_3, E_4, E_5, E_6, E_7, E_8, + ket_1::P, ket_2::P, bra_1::P=ket_1, bra_2::P=ket_2) where {P<:PEPSTensor} + full_infinite_environment(C_1, C_2, E_1, E_2, E_3, E_4, x, + ket_1::P, ket_2::P, ket_3::P, ket_4::P, + bra_1::P=ket_1, bra_2::P=ket_2, bra_3::P=ket_3, bra_4::P=ket_4) where {P<:PEPSTensor} + full_infinite_environment(x, C_1, C_2, E_1, E_2, E_3, E_4, + ket_1::P, ket_2::P, ket_3::P, ket_4::P, + bra_1::P=ket_1, bra_2::P=ket_2, bra_3::P=ket_3, bra_4::P=ket_4) where {P<:PEPSTensor} -Contract four quadrants (enlarged corners) to form a full-infinite environment where the -open bonds can be on any side (diagrams show open bonds in the west). +Contract four quadrants (enlarged corners) to form a full-infinite environment. ``` |~~~~~~~~~| -- |~~~~~~~~~| From 3278c4fc3e0d10221ea3922b1c2e736bc71e13f5 Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Tue, 3 Dec 2024 18:21:28 +0100 Subject: [PATCH 183/213] Define complex and real for CTMRGEnv --- src/algorithms/peps_opt.jl | 8 ++++---- src/environments/ctmrg_environments.jl | 3 +++ 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/algorithms/peps_opt.jl b/src/algorithms/peps_opt.jl index 8ba4cefe..39e138a1 100644 --- a/src/algorithms/peps_opt.jl +++ b/src/algorithms/peps_opt.jl @@ -145,10 +145,10 @@ function fixedpoint( ψ₀::InfinitePEPS{F}, H, alg::PEPSOptimize, - env₀::CTMRGEnv{C,T}=CTMRGEnv(ψ₀, field(F)^20); + env₀::CTMRGEnv=CTMRGEnv(ψ₀, field(F)^20); (finalize!)=OptimKit._finalize!, symmetrization=nothing, -) where {F,C,T} +) where {F} if isnothing(symmetrization) retract = peps_retract else @@ -157,8 +157,8 @@ function fixedpoint( finalize! = (x, f, g, numiter) -> fin!(symm_finalize!(x, f, g, numiter)..., numiter) end - if scalartype(C) <: Real || scalartype(T) <: Real - env₀ = CTMRGEnv(complex.(env₀.corners), complex.(env₀.edges)) + if scalartype(env₀) <: Real + env₀ = complex(env₀) @warn "the provided real environment was converted to a complex environment since\ :fixed mode generally produces complex gauges; use :diffgauge mode instead to work\ with purely real environments" diff --git a/src/environments/ctmrg_environments.jl b/src/environments/ctmrg_environments.jl index 460faf5b..c026762b 100644 --- a/src/environments/ctmrg_environments.jl +++ b/src/environments/ctmrg_environments.jl @@ -380,6 +380,9 @@ function eachcoordinate(x::CTMRGEnv, dirs) return collect(Iterators.product(dirs, axes(x, 2), axes(x, 3))) end +Base.real(env::CTMRGEnv) = CTMRGEnv(real.(env.corners), real.(env.edges)) +Base.complex(env::CTMRGEnv) = CTMRGEnv(complex.(env.corners), complex.(env.edges)) + # In-place update of environment function update!(env::CTMRGEnv{C,T}, env´::CTMRGEnv{C,T}) where {C,T} env.corners .= env´.corners From 51f2cc4baefe57b8351e5ab3f6835f37726a5db3 Mon Sep 17 00:00:00 2001 From: Lukas Devos Date: Tue, 3 Dec 2024 14:18:36 -0500 Subject: [PATCH 184/213] excise expansion step --- src/algorithms/ctmrg/ctmrg.jl | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/src/algorithms/ctmrg/ctmrg.jl b/src/algorithms/ctmrg/ctmrg.jl index 700775f8..9fb34dc9 100644 --- a/src/algorithms/ctmrg/ctmrg.jl +++ b/src/algorithms/ctmrg/ctmrg.jl @@ -143,10 +143,7 @@ function ctmrg_iter(state, envs::CTMRGEnv, alg::SequentialCTMRG) ϵ = zero(real(scalartype(state))) for _ in 1:4 # rotate for col in 1:size(state, 2) # left move column-wise - enlarged_envs = ctmrg_expand( - eachcoordinate(envs, [4, 1])[:, :, col], state, envs - ) - projectors, info = ctmrg_projectors(col, enlarged_envs, envs, alg) + projectors, info = ctmrg_projectors(col, state, envs, alg) envs = ctmrg_renormalize(col, projectors, state, envs, alg) ϵ = max(ϵ, info.err) end @@ -182,9 +179,9 @@ ctmrg_logcancel!(log, iter, η, N) = @warnv 1 logcancel!(log, iter, η, N) Expand the environment by absorbing a new PEPS tensor on the given coordinates. """ -function ctmrg_expand(coordinates, state, envs::CTMRGEnv) - return dtmap(idx -> TensorMap(EnlargedCorner(state, envs, idx), idx[1]), coordinates) -end +# function ctmrg_expand(coordinates, state, envs::CTMRGEnv) +# return dtmap(idx -> TensorMap(EnlargedCorner(state, envs, idx), idx[1]), coordinates) +# end # ======================================================================================== # # Projector step @@ -199,7 +196,7 @@ In the `:sequential` mode the projectors are computed for the column `col`, wher in the `:simultaneous` mode, all projectors (and corresponding SVDs) are computed in parallel. """ function ctmrg_projectors( - col::Int, enlarged_envs, envs::CTMRGEnv{C,E}, alg::SequentialCTMRG + col::Int, state::InfinitePEPS, envs::CTMRGEnv{C,E}, alg::SequentialCTMRG ) where {C,E} projector_alg = alg.projector_alg ϵ = zero(real(scalartype(envs))) @@ -208,7 +205,9 @@ function ctmrg_projectors( coordinates = eachcoordinate(envs)[:, col] projectors = dtmap(coordinates) do (r, c) r′ = _prev(r, size(envs.corners, 2)) - QQ = halfinfinite_environment(enlarged_envs[1, r], enlarged_envs[2, r′]) + Q1 = TensorMap(EnlargedCorner(state, envs, (SOUTHWEST, r, c)), SOUTHWEST) + Q2 = TensorMap(EnlargedCorner(state, envs, (NORTHWEST, r′, c)), NORTHWEST) + QQ = halfinfinite_environment(Q1, Q2) trscheme = truncation_scheme(projector_alg, envs.edges[WEST, r′, c]) svd_alg = svd_algorithm(projector_alg, (WEST, r, c)) U, S, V, ϵ_local = PEPSKit.tsvd!(QQ, svd_alg; trunc=trscheme) @@ -223,7 +222,7 @@ function ctmrg_projectors( end # Compute projectors - return build_projectors(U, S, V, enlarged_envs[1, r], enlarged_envs[2, r′]) + return build_projectors(U, S, V, Q1, Q2) end return (map(first, projectors), map(last, projectors)), (; err=ϵ) end From 77fc207184198bdc8a1aa9d7d9b0acf78129d576 Mon Sep 17 00:00:00 2001 From: Lukas Devos Date: Tue, 3 Dec 2024 15:33:15 -0500 Subject: [PATCH 185/213] reenable expansion for simultaneous ctmrg --- src/algorithms/ctmrg/ctmrg.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/algorithms/ctmrg/ctmrg.jl b/src/algorithms/ctmrg/ctmrg.jl index 9fb34dc9..91d86159 100644 --- a/src/algorithms/ctmrg/ctmrg.jl +++ b/src/algorithms/ctmrg/ctmrg.jl @@ -179,9 +179,9 @@ ctmrg_logcancel!(log, iter, η, N) = @warnv 1 logcancel!(log, iter, η, N) Expand the environment by absorbing a new PEPS tensor on the given coordinates. """ -# function ctmrg_expand(coordinates, state, envs::CTMRGEnv) -# return dtmap(idx -> TensorMap(EnlargedCorner(state, envs, idx), idx[1]), coordinates) -# end +function ctmrg_expand(coordinates, state, envs::CTMRGEnv) + return dtmap(idx -> TensorMap(EnlargedCorner(state, envs, idx), idx[1]), coordinates) +end # ======================================================================================== # # Projector step From 8c8eec11322f92a7b25a051e61c32511d3867b83 Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Wed, 4 Dec 2024 10:50:34 +0100 Subject: [PATCH 186/213] Add actual full-infinite contractions --- .../contractions/ctmrg_contractions.jl | 86 +++++++++++++++++-- 1 file changed, 79 insertions(+), 7 deletions(-) diff --git a/src/algorithms/contractions/ctmrg_contractions.jl b/src/algorithms/contractions/ctmrg_contractions.jl index 17b7b667..61185f85 100644 --- a/src/algorithms/contractions/ctmrg_contractions.jl +++ b/src/algorithms/contractions/ctmrg_contractions.jl @@ -244,7 +244,6 @@ Alternatively, contract the environment with a vector `x` acting on it E_1 == ket_bra_1 == ket_bra_2 == E_4 | || || | [~~~~~~x~~~~~~] - || | ``` or contract the adjoint environment with `x`, e.g. as needed for iterative solvers. @@ -310,7 +309,7 @@ function half_infinite_environment( bra_1::P=ket_1, bra_2::P=ket_2, ) where {S,P<:PEPSTensor} - return @autoopt @tensor env_x[χ_in D_inabove D_inbelow] := + return @autoopt @tensor x_env[χ_in D_inabove D_inbelow] := x[χ1 D1 D2] * conj(E_1[χ1 D3 D4; χ2]) * conj(C_1[χ2; χ3]) * @@ -374,7 +373,6 @@ Alternatively, contract the environment with a vector `x` acting on it | || || | || | || | - | || || | [~~~~x~~~~~] || | | || || | E_8 == ket_bra_4 == ket_bra_3 == E_5 @@ -386,7 +384,13 @@ or contract the adjoint environment with `x`, e.g. as needed for iterative solve """ function full_infinite_environment( quadrant1::T, quadrant2::T, quadrant3::T, quadrant4::T -) where {T<:AbstractTensorMap{<:ElementarySpace,3,3}} end +) where {T<:AbstractTensorMap{<:ElementarySpace,3,3}} + return @autoopt @tensor env[χ_in D_inabove D_inbelow; χ_out D_outabove D_outbelow] := + quadrant1[χ_in D_inabove D_inbelow; χ1 D1 D2] * + quadrant2[χ1 D1 D2; χ2 D3 D4] * + quadrant3[χ2 D3 D4; χ3 D5 D6] * + quadrant4[χ3 D5 D6; χ_out D_outabove D_outbelow] +end function full_infinite_environment( C_1, C_2, @@ -408,7 +412,29 @@ function full_infinite_environment( bra_2::P=ket_2, bra_3::P=ket_3, bra_4::P=ket_4, -) where {P<:PEPSTensor} end +) where {P<:PEPSTensor} + return @autoopt @tensor env[χ_in D_inabove D_inbelow; χ_out D_outabove D_outbelow] := + E_1[χ_in D1 D2; χ1] * + C_1[χ1; χ2] * + E_2[χ2 D3 D4; χ3] * + ket_1[d1; D3 D11 D_inabove D1] * + conj(bra_1[d1; D4 D12 D_inbelow D2]) * + ket_2[d2; D5 D7 D9 D11] * + conj(bra_2[d2; D6 D8 D10 D12]) * + E_3[χ3 D5 D6; χ4] * + C_2[χ4; χ5] * + E_4[χ5 D7 D8; χ6] * + E_5[χ6 D13 D14; χ7] * + C_3[χ7; χ8] * + E_6[χ8 D15 D16; χ9] * + ket_3[d3; D9 D13 D15 D17] * + conj(bra_3[d3; D10 D14 D16 D18]) * + ket_4[d4; D_outabove D17 D19 D21] * + conj(bra_4[d4; D_outbelow D18 D20 D22]) * + E_7[χ8 D19 D20; χ9] * + C_4[χ9; χ10] * + E_8[χ10 D21 D22; χ_out] +end function full_infinite_environment( C_1, C_2, @@ -431,7 +457,30 @@ function full_infinite_environment( bra_2::P=ket_2, bra_3::P=ket_3, bra_4::P=ket_4, -) where {S,P<:PEPSTensor} end +) where {S,P<:PEPSTensor} + return @autoopt @tensor env_x[χ_in D_inabove D_inbelow] := + E_1[χ_in D1 D2; χ1] * + C_1[χ1; χ2] * + E_2[χ2 D3 D4; χ3] * + ket_1[d1; D3 D11 D_inabove D1] * + conj(bra_1[d1; D4 D12 D_inbelow D2]) * + ket_2[d2; D5 D7 D9 D11] * + conj(bra_2[d2; D6 D8 D10 D12]) * + E_3[χ3 D5 D6; χ4] * + C_2[χ4; χ5] * + E_4[χ5 D7 D8; χ6] * + E_5[χ6 D13 D14; χ7] * + C_3[χ7; χ8] * + E_6[χ8 D15 D16; χ9] * + ket_3[d3; D9 D13 D15 D17] * + conj(bra_3[d3; D10 D14 D16 D18]) * + ket_4[d4; D_xabove D17 D19 D21] * + conj(bra_4[d4; D_xbelow D18 D20 D22]) * + E_7[χ8 D19 D20; χ9] * + C_4[χ9; χ10] * + E_8[χ10 D21 D22; χ_x] * + x[χ_x D_xabove D_xbelow] +end function full_infinite_environment( x::AbstractTensor{S,3}, C_1, @@ -454,7 +503,30 @@ function full_infinite_environment( bra_2::P=ket_2, bra_3::P=ket_3, bra_4::P=ket_4, -) where {S,P<:PEPSTensor} end +) where {S,P<:PEPSTensor} + return @autoopt @tensor x_env[χ_in D_inabove D_inbelow] := + x[χ_x D_xabove D_xbelow] * + E_1[χ_x D1 D2; χ1] * + C_1[χ1; χ2] * + E_2[χ2 D3 D4; χ3] * + ket_1[d1; D3 D11 D_xabove D1] * + conj(bra_1[d1; D4 D12 D_xbelow D2]) * + ket_2[d2; D5 D7 D9 D11] * + conj(bra_2[d2; D6 D8 D10 D12]) * + E_3[χ3 D5 D6; χ4] * + C_2[χ4; χ5] * + E_4[χ5 D7 D8; χ6] * + E_5[χ6 D13 D14; χ7] * + C_3[χ7; χ8] * + E_6[χ8 D15 D16; χ9] * + ket_3[d3; D9 D13 D15 D17] * + conj(bra_3[d3; D10 D14 D16 D18]) * + ket_4[d4; D_inabove D17 D19 D21] * + conj(bra_4[d4; D_inbelow D18 D20 D22]) * + E_7[χ8 D19 D20; χ9] * + C_4[χ9; χ10] * + E_8[χ10 D21 D22; χ_in] +end # Renormalization contractions # ---------------------------- From 4d0db6e0fcb9bb3db3141761c3c8047c7ceb1bf6 Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Wed, 4 Dec 2024 18:23:20 +0100 Subject: [PATCH 187/213] Add FullInfiniteProjector projector computation and remove redundant previous code --- .../contractions/ctmrg_contractions.jl | 205 -------------- src/algorithms/ctmrg/ctmrg.jl | 258 +++++++++--------- src/algorithms/ctmrg/sparse_environments.jl | 168 ------------ 3 files changed, 133 insertions(+), 498 deletions(-) diff --git a/src/algorithms/contractions/ctmrg_contractions.jl b/src/algorithms/contractions/ctmrg_contractions.jl index 61185f85..2c95af6f 100644 --- a/src/algorithms/contractions/ctmrg_contractions.jl +++ b/src/algorithms/contractions/ctmrg_contractions.jl @@ -323,211 +323,6 @@ function half_infinite_environment( conj(E_4[χ6 D9 D10; χ_in]) end -""" - full_infinite_environment( - quadrant1::T, quadrant2::T, quadrant3::T, quadrant4::T - ) where {T<:AbstractTensorMap{<:ElementarySpace,3,3}} - full_infinite_environment(C_1, C_2, C_3, C_4, E_1, E_2, E_3, E_4, E_5, E_6, E_7, E_8, - ket_1::P, ket_2::P, bra_1::P=ket_1, bra_2::P=ket_2) where {P<:PEPSTensor} - full_infinite_environment(C_1, C_2, E_1, E_2, E_3, E_4, x, - ket_1::P, ket_2::P, ket_3::P, ket_4::P, - bra_1::P=ket_1, bra_2::P=ket_2, bra_3::P=ket_3, bra_4::P=ket_4) where {P<:PEPSTensor} - full_infinite_environment(x, C_1, C_2, E_1, E_2, E_3, E_4, - ket_1::P, ket_2::P, ket_3::P, ket_4::P, - bra_1::P=ket_1, bra_2::P=ket_2, bra_3::P=ket_3, bra_4::P=ket_4) where {P<:PEPSTensor} - -Contract four quadrants (enlarged corners) to form a full-infinite environment. - -``` - |~~~~~~~~~| -- |~~~~~~~~~| - |quadrant1| |quadrant2| - |~~~~~~~~~| == |~~~~~~~~~| - | || || | - || | - | || || | - |~~~~~~~~~| -- |~~~~~~~~~| - |quadrant4| |quadrant3| - |~~~~~~~~~| == |~~~~~~~~~| -``` - -The environment can also be contracted directly from all its constituent tensors. - -``` - C_1 -- E_2 -- E_3 -- C_2 - | || || | - E_1 == ket_bra_1 == ket_bra_2 == E_4 - | || || | - || | - | || || | - E_8 == ket_bra_4 == ket_bra_3 == E_5 - | || || | - C_4 -- E_7 -- E_6 -- C_3 -``` - -Alternatively, contract the environment with a vector `x` acting on it - -``` - C_1 -- E_2 -- E_3 -- C_2 - | || || | - E_1 == ket_bra_1 == ket_bra_2 == E_4 - | || || | - || | - || | - [~~~~x~~~~~] || | - | || || | - E_8 == ket_bra_4 == ket_bra_3 == E_5 - | || || | - C_4 -- E_7 -- E_6 -- C_3 - -``` -or contract the adjoint environment with `x`, e.g. as needed for iterative solvers. -""" -function full_infinite_environment( - quadrant1::T, quadrant2::T, quadrant3::T, quadrant4::T -) where {T<:AbstractTensorMap{<:ElementarySpace,3,3}} - return @autoopt @tensor env[χ_in D_inabove D_inbelow; χ_out D_outabove D_outbelow] := - quadrant1[χ_in D_inabove D_inbelow; χ1 D1 D2] * - quadrant2[χ1 D1 D2; χ2 D3 D4] * - quadrant3[χ2 D3 D4; χ3 D5 D6] * - quadrant4[χ3 D5 D6; χ_out D_outabove D_outbelow] -end -function full_infinite_environment( - C_1, - C_2, - C_3, - C_4, - E_1, - E_2, - E_3, - E_4, - E_5, - E_6, - E_7, - E_8, - ket_1::P, - ket_2::P, - ket_3::P, - ket_4::P, - bra_1::P=ket_1, - bra_2::P=ket_2, - bra_3::P=ket_3, - bra_4::P=ket_4, -) where {P<:PEPSTensor} - return @autoopt @tensor env[χ_in D_inabove D_inbelow; χ_out D_outabove D_outbelow] := - E_1[χ_in D1 D2; χ1] * - C_1[χ1; χ2] * - E_2[χ2 D3 D4; χ3] * - ket_1[d1; D3 D11 D_inabove D1] * - conj(bra_1[d1; D4 D12 D_inbelow D2]) * - ket_2[d2; D5 D7 D9 D11] * - conj(bra_2[d2; D6 D8 D10 D12]) * - E_3[χ3 D5 D6; χ4] * - C_2[χ4; χ5] * - E_4[χ5 D7 D8; χ6] * - E_5[χ6 D13 D14; χ7] * - C_3[χ7; χ8] * - E_6[χ8 D15 D16; χ9] * - ket_3[d3; D9 D13 D15 D17] * - conj(bra_3[d3; D10 D14 D16 D18]) * - ket_4[d4; D_outabove D17 D19 D21] * - conj(bra_4[d4; D_outbelow D18 D20 D22]) * - E_7[χ8 D19 D20; χ9] * - C_4[χ9; χ10] * - E_8[χ10 D21 D22; χ_out] -end -function full_infinite_environment( - C_1, - C_2, - C_3, - C_4, - E_1, - E_2, - E_3, - E_4, - E_5, - E_6, - E_7, - E_8, - x::AbstractTensor{S,3}, - ket_1::P, - ket_2::P, - ket_3::P, - ket_4::P, - bra_1::P=ket_1, - bra_2::P=ket_2, - bra_3::P=ket_3, - bra_4::P=ket_4, -) where {S,P<:PEPSTensor} - return @autoopt @tensor env_x[χ_in D_inabove D_inbelow] := - E_1[χ_in D1 D2; χ1] * - C_1[χ1; χ2] * - E_2[χ2 D3 D4; χ3] * - ket_1[d1; D3 D11 D_inabove D1] * - conj(bra_1[d1; D4 D12 D_inbelow D2]) * - ket_2[d2; D5 D7 D9 D11] * - conj(bra_2[d2; D6 D8 D10 D12]) * - E_3[χ3 D5 D6; χ4] * - C_2[χ4; χ5] * - E_4[χ5 D7 D8; χ6] * - E_5[χ6 D13 D14; χ7] * - C_3[χ7; χ8] * - E_6[χ8 D15 D16; χ9] * - ket_3[d3; D9 D13 D15 D17] * - conj(bra_3[d3; D10 D14 D16 D18]) * - ket_4[d4; D_xabove D17 D19 D21] * - conj(bra_4[d4; D_xbelow D18 D20 D22]) * - E_7[χ8 D19 D20; χ9] * - C_4[χ9; χ10] * - E_8[χ10 D21 D22; χ_x] * - x[χ_x D_xabove D_xbelow] -end -function full_infinite_environment( - x::AbstractTensor{S,3}, - C_1, - C_2, - C_3, - C_4, - E_1, - E_2, - E_3, - E_4, - E_5, - E_6, - E_7, - E_8, - ket_1::P, - ket_2::P, - ket_3::P, - ket_4::P, - bra_1::P=ket_1, - bra_2::P=ket_2, - bra_3::P=ket_3, - bra_4::P=ket_4, -) where {S,P<:PEPSTensor} - return @autoopt @tensor x_env[χ_in D_inabove D_inbelow] := - x[χ_x D_xabove D_xbelow] * - E_1[χ_x D1 D2; χ1] * - C_1[χ1; χ2] * - E_2[χ2 D3 D4; χ3] * - ket_1[d1; D3 D11 D_xabove D1] * - conj(bra_1[d1; D4 D12 D_xbelow D2]) * - ket_2[d2; D5 D7 D9 D11] * - conj(bra_2[d2; D6 D8 D10 D12]) * - E_3[χ3 D5 D6; χ4] * - C_2[χ4; χ5] * - E_4[χ5 D7 D8; χ6] * - E_5[χ6 D13 D14; χ7] * - C_3[χ7; χ8] * - E_6[χ8 D15 D16; χ9] * - ket_3[d3; D9 D13 D15 D17] * - conj(bra_3[d3; D10 D14 D16 D18]) * - ket_4[d4; D_inabove D17 D19 D21] * - conj(bra_4[d4; D_inbelow D18 D20 D22]) * - E_7[χ8 D19 D20; χ9] * - C_4[χ9; χ10] * - E_8[χ10 D21 D22; χ_in] -end - # Renormalization contractions # ---------------------------- diff --git a/src/algorithms/ctmrg/ctmrg.jl b/src/algorithms/ctmrg/ctmrg.jl index ea4a0805..8d6f6642 100644 --- a/src/algorithms/ctmrg/ctmrg.jl +++ b/src/algorithms/ctmrg/ctmrg.jl @@ -44,9 +44,9 @@ function svd_algorithm(alg::ProjectorAlgs, (dir, r, c)) end end -function truncation_scheme(alg::ProjectorAlgs, edge) +function truncation_scheme(alg::ProjectorAlgs, Espace) if alg.trscheme isa FixedSpaceTruncation - return truncspace(space(edge, 1)) + return truncspace(Espace) else return alg.trscheme end @@ -213,139 +213,147 @@ end # Projector step # ======================================================================================== # -""" - ctmrg_projectors(enlarged_envs, env, alg::CTMRG{M}) - -Compute the CTMRG projectors based from enlarged environments. -In the `:simultaneous` mode, the environment SVD is run in parallel. -""" -function ctmrg_projectors( - enlarged_envs, envs::CTMRGEnv{C,E}, alg::SequentialCTMRG{<:HalfInfiniteProjector} -) where {C,E} - projector_alg = alg.projector_alg - ϵ = zero(real(scalartype(envs))) - - coordinates = eachcoordinate(envs) - projectors = dtmap(coordinates) do (r, c) - # SVD half-infinite environment - r′ = _prev(r, size(envs.corners, 2)) - QQ = half_infinite_environment(enlarged_envs[1, r, c], enlarged_envs[2, r′, c]) - - trscheme = truncation_scheme(projector_alg, envs.edges[WEST, r′, c]) - svd_alg = svd_algorithm(projector_alg, (WEST, r, c)) - U, S, V, ϵ_local = PEPSKit.tsvd!(QQ, svd_alg; trunc=trscheme) - ϵ = max(ϵ, ϵ_local / norm(S)) - - # Compute SVD truncation error and check for degenerate singular values - Zygote.isderiving() && ignore_derivatives() do - if alg.verbosity > 0 && is_degenerate_spectrum(S) - svals = TensorKit.SectorDict(c => diag(b) for (c, b) in blocks(S)) - @warn("degenerate singular values detected: ", svals) - end - end - - # Compute projectors - return build_projectors(U, S, V, enlarged_envs[1, r, c], enlarged_envs[2, r′, c]) - end - - return (map(first, projectors), map(last, projectors)), (; err=ϵ) -end -function ctmrg_projectors( - enlarged_envs, envs::CTMRGEnv{C,E}, alg::SequentialCTMRG{<:FullInfiniteProjector} -) where {C,E} - projector_alg = alg.projector_alg - ϵ = zero(real(scalartype(envs))) - - coordinates = eachcoordinate(envs) - projectors = dtmap(coordinates) do (r, c) - # SVD half-infinite environment - r′ = _prev(r, size(envs.corners, 2)) - c′ = _next(c, size(envs.corners, 3)) - QQ = fullinfinite_environment( - enlarged_envs[1, r, c], - enlarged_envs[2, r′, c], - enlarged_envs[3, r′, c′], - enlarged_envs[4, r, c′], # TODO: break up the CTMRG expand/project/renormalize scheme! - ) - - trscheme = truncation_scheme(projector_alg, envs.edges[WEST, r′, c]) - svd_alg = svd_algorithm(projector_alg, (WEST, r, c)) - U, S, V, ϵ_local = PEPSKit.tsvd!(QQ, svd_alg; trunc=trscheme) - ϵ = max(ϵ, ϵ_local / norm(S)) - - # Compute SVD truncation error and check for degenerate singular values - Zygote.isderiving() && ignore_derivatives() do - if alg.verbosity > 0 && is_degenerate_spectrum(S) - svals = TensorKit.SectorDict(c => diag(b) for (c, b) in blocks(S)) - @warn("degenerate singular values detected: ", svals) - end +# TODO: embed this into new functions after refactor +function compute_projector(enlarged_corners, coordinate, alg::HalfInfiniteProjector) + # SVD half-infinite environment + halfinf = half_infinite_environment(enlarged_corners...) + trscheme = truncation_scheme(projector_alg, space(enlarged_corners[2], 1)) + svd_alg = svd_algorithm(projector_alg, coordinate) + U, S, V, err = PEPSKit.tsvd!(halfinf, svd_alg; trunc=trscheme) + + # Compute SVD truncation error and check for degenerate singular values + Zygote.isderiving() && ignore_derivatives() do + if alg.verbosity > 0 && is_degenerate_spectrum(S) + svals = TensorKit.SectorDict(c => diag(b) for (c, b) in blocks(S)) + @warn("degenerate singular values detected: ", svals) end - - # Compute projectors - return build_projectors(U, S, V, enlarged_envs[1, r, c], enlarged_envs[2, r′, c]) end - return (map(first, projectors), map(last, projectors)), (; err=ϵ) + P_left, P_right = build_projectors(U, S, V, enlarged_corners...) + return (P_left, P_right), (; err, U, S, V) end -function ctmrg_projectors( - enlarged_envs, envs::CTMRGEnv{C,E}, alg::SimultaneousCTMRG{<:HalfInfiniteProjector} -) where {C,E} - projector_alg = alg.projector_alg - # pre-allocation - U, V = Zygote.Buffer.(projector_type(envs.edges)) - # Corner type but with real numbers - S = Zygote.Buffer(U.data, tensormaptype(spacetype(C), 1, 1, real(scalartype(E)))) - - ϵ = zero(real(scalartype(envs))) - coordinates = eachcoordinate(envs, 1:4) - projectors = dtmap(coordinates) do (dir, r, c) - # Row-column index of next enlarged corner - next_rc = if dir == 1 - (r, _next(c, size(envs.corners, 3))) - elseif dir == 2 - (_next(r, size(envs.corners, 2)), c) - elseif dir == 3 - (r, _prev(c, size(envs.corners, 3))) - elseif dir == 4 - (_prev(r, size(envs.corners, 2)), c) - end - # SVD half-infinite environment - QQ = half_infinite_environment( - enlarged_envs[dir, r, c], enlarged_envs[_next(dir, 4), next_rc...] - ) - - trscheme = truncation_scheme(projector_alg, envs.edges[dir, next_rc...]) - svd_alg = svd_algorithm(projector_alg, (dir, r, c)) - U_local, S_local, V_local, ϵ_local = PEPSKit.tsvd!(QQ, svd_alg; trunc=trscheme) - U[dir, r, c] = U_local - S[dir, r, c] = S_local - V[dir, r, c] = V_local - ϵ = max(ϵ, ϵ_local / norm(S_local)) - - # Compute SVD truncation error and check for degenerate singular values - Zygote.isderiving() && ignore_derivatives() do - if alg.verbosity > 0 && is_degenerate_spectrum(S_local) - svals = TensorKit.SectorDict(c => diag(b) for (c, b) in blocks(S_local)) - @warn("degenerate singular values detected: ", svals) - end +function compute_projector(enlarged_corners, coordinate, alg::FullInfiniteProjector) + # QR top and bottom half-infinite environments + halfinf_top = half_infinite_environment(enlarged_corners[1], enlarged_corners[2]) + halfinf_bot = half_infinite_environment(enlarged_corners[3], enlarged_corners[4]) + _, R_top = leftorth!(halfinf_top) + _, R_bot = leftorth!(halfinf_bot) + R_fullinf = R_top * R_bot + + # SVD product of R's + trscheme = truncation_scheme(projector_alg, space(enlarged_corners[4], 1)) + svd_alg = svd_algorithm(projector_alg, coordinate) + U, S, V, err = PEPSKit.tsvd!(R_fullinf, svd_alg; trunc=trscheme) + + # Compute SVD truncation error and check for degenerate singular values + Zygote.isderiving() && ignore_derivatives() do + if alg.verbosity > 0 && is_degenerate_spectrum(S) + svals = TensorKit.SectorDict(c => diag(b) for (c, b) in blocks(S)) + @warn("degenerate singular values detected: ", svals) end - - # Compute projectors - return build_projectors( - U_local, - S_local, - V_local, - enlarged_envs[dir, r, c], - enlarged_envs[_next(dir, 4), next_rc...], - ) end - P_left = map(first, projectors) - P_right = map(last, projectors) - return (P_left, P_right), (; err=ϵ, U=copy(U), S=copy(S), V=copy(V)) + P_left, P_right = build_projectors(U, S, V, R_top, R_bot) + return (P_left, P_right), (; err, U, S, V) end +# """ +# ctmrg_projectors(enlarged_envs, env, alg::CTMRG{M}) + +# Compute the CTMRG projectors based from enlarged environments. +# In the `:simultaneous` mode, the environment SVD is run in parallel. +# """ +# function ctmrg_projectors( +# enlarged_envs, envs::CTMRGEnv{C,E}, alg::SequentialCTMRG{<:HalfInfiniteProjector} +# ) where {C,E} +# projector_alg = alg.projector_alg +# ϵ = zero(real(scalartype(envs))) + +# coordinates = eachcoordinate(envs) +# projectors = dtmap(coordinates) do (r, c) +# # SVD half-infinite environment +# r′ = _prev(r, size(envs.corners, 2)) +# QQ = half_infinite_environment(enlarged_envs[1, r, c], enlarged_envs[2, r′, c]) + +# trscheme = truncation_scheme(projector_alg, envs.edges[WEST, r′, c]) +# svd_alg = svd_algorithm(projector_alg, (WEST, r, c)) +# U, S, V, ϵ_local = PEPSKit.tsvd!(QQ, svd_alg; trunc=trscheme) +# ϵ = max(ϵ, ϵ_local / norm(S)) + +# # Compute SVD truncation error and check for degenerate singular values +# Zygote.isderiving() && ignore_derivatives() do +# if alg.verbosity > 0 && is_degenerate_spectrum(S) +# svals = TensorKit.SectorDict(c => diag(b) for (c, b) in blocks(S)) +# @warn("degenerate singular values detected: ", svals) +# end +# end + +# # Compute projectors +# return build_projectors(U, S, V, enlarged_envs[1, r, c], enlarged_envs[2, r′, c]) +# end + +# return (map(first, projectors), map(last, projectors)), (; err=ϵ) +# end +# function ctmrg_projectors( +# enlarged_envs, envs::CTMRGEnv{C,E}, alg::SimultaneousCTMRG{<:HalfInfiniteProjector} +# ) where {C,E} +# projector_alg = alg.projector_alg +# # pre-allocation +# U, V = Zygote.Buffer.(projector_type(envs.edges)) +# # Corner type but with real numbers +# S = Zygote.Buffer(U.data, tensormaptype(spacetype(C), 1, 1, real(scalartype(E)))) + +# ϵ = zero(real(scalartype(envs))) +# coordinates = eachcoordinate(envs, 1:4) +# projectors = dtmap(coordinates) do (dir, r, c) +# # Row-column index of next enlarged corner +# next_rc = if dir == 1 +# (r, _next(c, size(envs.corners, 3))) +# elseif dir == 2 +# (_next(r, size(envs.corners, 2)), c) +# elseif dir == 3 +# (r, _prev(c, size(envs.corners, 3))) +# elseif dir == 4 +# (_prev(r, size(envs.corners, 2)), c) +# end + +# # SVD half-infinite environment +# QQ = half_infinite_environment( +# enlarged_envs[dir, r, c], enlarged_envs[_next(dir, 4), next_rc...] +# ) + +# trscheme = truncation_scheme(projector_alg, envs.edges[dir, next_rc...]) +# svd_alg = svd_algorithm(projector_alg, (dir, r, c)) +# U_local, S_local, V_local, ϵ_local = PEPSKit.tsvd!(QQ, svd_alg; trunc=trscheme) +# U[dir, r, c] = U_local +# S[dir, r, c] = S_local +# V[dir, r, c] = V_local +# ϵ = max(ϵ, ϵ_local / norm(S_local)) + +# # Compute SVD truncation error and check for degenerate singular values +# Zygote.isderiving() && ignore_derivatives() do +# if alg.verbosity > 0 && is_degenerate_spectrum(S_local) +# svals = TensorKit.SectorDict(c => diag(b) for (c, b) in blocks(S_local)) +# @warn("degenerate singular values detected: ", svals) +# end +# end + +# # Compute projectors +# return build_projectors( +# U_local, +# S_local, +# V_local, +# enlarged_envs[dir, r, c], +# enlarged_envs[_next(dir, 4), next_rc...], +# ) +# end + +# P_left = map(first, projectors) +# P_right = map(last, projectors) +# return (P_left, P_right), (; err=ϵ, U=copy(U), S=copy(S), V=copy(V)) +# end + """ build_projectors(U::AbstractTensorMap{E,3,1}, S::AbstractTensorMap{E,1,1}, V::AbstractTensorMap{E,1,3}, Q::AbstractTensorMap{E,3,3}, Q_next::AbstractTensorMap{E,3,3}) where {E<:ElementarySpace} diff --git a/src/algorithms/ctmrg/sparse_environments.jl b/src/algorithms/ctmrg/sparse_environments.jl index 4926a7f7..4f9fbad2 100644 --- a/src/algorithms/ctmrg/sparse_environments.jl +++ b/src/algorithms/ctmrg/sparse_environments.jl @@ -210,171 +210,3 @@ end function random_start_vector(env::HalfInfiniteEnv) return Tensor(randn, domain(env)) end - -# -------------------------------- -# Sparse full-infinite environment -# -------------------------------- - -""" - struct FullInfiniteEnv{C,E,A,A′} - -Full-infinite CTMRG environment tensor storage. -""" -struct FullInfiniteEnv{C,E,A,A′} # TODO: subtype as AbstractTensorMap once TensorKit is updated - C_1::C - C_2::C - C_3::C - C_4::C - E_1::E - E_2::E - E_3::E - E_4::E - E_5::E - E_6::E - E_7::E - E_8::E - ket_1::A - ket_2::A - ket_3::A - ket_4::A - bra_1::A′ - bra_2::A′ - bra_3::A′ - bra_4::A′ -end - -# Construct environment from two enlarged corners -function FullInfiniteEnv( - quadrant1::E, quadrant2::E, quadrant3::E, quadrant4::E -) where {E<:EnlargedCorner} - return FullInfiniteEnv( - quadrant1.C, - quadrant2.C, - quadrant3.C, - quadrant4.C, - quadrant1.E_1, - quadrant1.E_2, - quadrant2.E_1, - quadrant2.E_2, - quadrant3.E_1, - quadrant3.E_2, - quadrant4.E_1, - quadrant4.E_2, - quadrant1.ket, - quadrant2.ket, - quadrant3.ket, - quadrant4.ket, - quadrant1.bra, - quadrant2.bra, - quadrant3.bra, - quadrant4.bra, - ) -end - -""" - TensorKit.TensorMap(env::FullInfiniteEnv) - -Instantiate full-infinite environment as `TensorMap` explicitly. -""" -function TensorKit.TensorMap(env::FullInfiniteEnv) # Dense operator - return full_infinite_environment( - env.C_1, - env.C_2, - env.C_3, - env.C_4, - env.E_1, - env.E_2, - env.E_3, - env.E_4, - env.E_2, - env.E_3, - env.E_4, - env.E_5, - env.ket_1, - env.ket_2, - env.ket_3, - env.ket_4, - env.bra_1, - env.bra_2, - env.bra_3, - env.bra_4, - ) -end - -""" - (env::FullInfiniteEnv)(x, ::Val{false}) - (env::FullInfiniteEnv)(x, ::Val{true}) - -Contract full-infinite environment with a vector `x`, such that the environment acts as a -linear map or adjoint linear map on `x` if `Val(true)` or `Val(false)` is passed, respectively. -""" -function (env::FullInfiniteEnv)(x, ::Val{false}) # Linear map: env() * x - return full_infinite_environment( - env.C_1, - env.C_2, - env.C_3, - env.C_4, - env.E_1, - env.E_2, - env.E_3, - env.E_4, - env.E_5, - env.E_6, - env.E_7, - env.E_8, - x, - env.ket_1, - env.ket_2, - env.ket_3, - env.ket_4, - env.bra_1, - env.bra_2, - env.bra_3, - env.bra_4, - ) -end -function (env::FullInfiniteEnv)(x, ::Val{true}) # Adjoint linear map: env()' * x - return full_infinite_environment( - x, - env.C_1, - env.C_2, - env.C_3, - env.C_4, - env.E_1, - env.E_2, - env.E_3, - env.E_4, - env.E_5, - env.E_6, - env.E_7, - env.E_8, - env.ket_1, - env.ket_2, - env.ket_3, - env.ket_4, - env.bra_1, - env.bra_2, - env.bra_3, - env.bra_4, - ) -end - -# Wrapper around full_infinite_environment contraction using EnlargedCorners (used in ctmrg_projectors) -function full_infinite_environment( - ec_1::E, ec_2::E, ec_3::E, ec_4::E -) where {E<:EnlargedCorner} - return FullInfiniteEnv(ec_1, ec_2, ec_3, ec_4) -end - -# AbstractTensorMap subtyping and IterSVD compatibility -function TensorKit.domain(env::FullInfiniteEnv) - return domain(env.E_8) * domain(env.ket_4)[3] * domain(env.bra_4)[3]' -end - -function TensorKit.codomain(env::FullInfiniteEnv) - return codomain(env.E_1)[1] * domain(env.ket_1)[3]' * domain(env.bra_1)[3] -end - -function random_start_vector(env::FullInfiniteEnv) - return Tensor(randn, domain(env)) -end From 3ea633eb2d829b617beba3fddda3efbb871047f4 Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Thu, 5 Dec 2024 17:40:37 +0100 Subject: [PATCH 188/213] Split up CTMRG file and refactor simultaneous and sequential modes, incorporate new projectors algs --- src/PEPSKit.jl | 6 +- .../contractions/ctmrg_contractions.jl | 25 ++ src/algorithms/ctmrg/ctmrg.jl | 335 ++---------------- src/algorithms/ctmrg/sequential.jl | 85 +++++ src/algorithms/ctmrg/simultaneous.jl | 81 +++++ src/algorithms/peps_opt.jl | 2 +- src/environments/ctmrg_environments.jl | 33 +- src/utility/util.jl | 26 +- test/ctmrg/{ctmrgschemes.jl => flavors.jl} | 12 +- test/ctmrg/gaugefix.jl | 19 +- test/ctmrg/gradients.jl | 3 +- test/ctmrg/gradparts.jl | 5 +- test/runtests.jl | 2 +- 13 files changed, 284 insertions(+), 350 deletions(-) create mode 100644 src/algorithms/ctmrg/sequential.jl create mode 100644 src/algorithms/ctmrg/simultaneous.jl rename test/ctmrg/{ctmrgschemes.jl => flavors.jl} (88%) diff --git a/src/PEPSKit.jl b/src/PEPSKit.jl index 388b6efe..a1875fc1 100644 --- a/src/PEPSKit.jl +++ b/src/PEPSKit.jl @@ -54,9 +54,9 @@ include("utility/symmetrization.jl") const ctmrg_maxiter = 100 const ctmrg_miniter = 4 const ctmrg_tol = 1e-8 + const ctmrg_flavor = :simultaneous const fpgrad_maxiter = 30 const fpgrad_tol = 1e-6 - const ctmrgscheme = :simultaneous const reuse_env = true const trscheme = FixedSpaceTruncation() const fwd_alg = TensorKit.SDD() @@ -77,9 +77,9 @@ Module containing default values that represent typical algorithm parameters. - `ctmrg_maxiter`: Maximal number of CTMRG iterations per run - `ctmrg_miniter`: Minimal number of CTMRG carried out - `ctmrg_tol`: Tolerance checking singular value and norm convergence +- `ctmrg_flavor`: Scheme for growing, projecting and renormalizing CTMRG environments - `fpgrad_maxiter`: Maximal number of iterations for computing the CTMRG fixed-point gradient - `fpgrad_tol`: Convergence tolerance for the fixed-point gradient iteration -- `ctmrgscheme`: Scheme for growing, projecting and renormalizing CTMRG environments - `reuse_env`: If `true`, the current optimization step is initialized on the previous environment - `trscheme`: Truncation scheme for SVDs and other decompositions - `fwd_alg`: SVD algorithm that is used in the forward pass @@ -98,9 +98,9 @@ module Defaults const ctmrg_maxiter = 100 const ctmrg_miniter = 4 const ctmrg_tol = 1e-8 + const ctmrg_flavor = :simultaneous const fpgrad_maxiter = 30 const fpgrad_tol = 1e-6 - const ctmrgscheme = :simultaneous const sparse = false const reuse_env = true const trscheme = FixedSpaceTruncation() diff --git a/src/algorithms/contractions/ctmrg_contractions.jl b/src/algorithms/contractions/ctmrg_contractions.jl index c014265d..0a84ae06 100644 --- a/src/algorithms/contractions/ctmrg_contractions.jl +++ b/src/algorithms/contractions/ctmrg_contractions.jl @@ -163,6 +163,31 @@ end # Projector contractions # ---------------------- +""" + left_and_right_projector(U, S, V, Q::AbstractTensorMap{E,3,3}, Q_next::AbstractTensorMap{E,3,3} + left_and_right_projector(U, S, V, Q::EnlargedCorner, Q_next::EnlargedCorner) + +Compute left and right projectors based on a SVD and quadrant tensors, specified either as +`AbstractTensorMap`s or sparsely as `EnlargedCorner`s such that the quadrants are never +constructed explicitly. +""" +function left_and_right_projector( + U, S, V, Q::AbstractTensorMap{E,3,3}, Q_next::AbstractTensorMap{E,3,3} +) where {E<:ElementarySpace} + isqS = sdiag_inv_sqrt(S) + P_left = Q_next * V' * isqS + P_right = isqS * U' * Q + return P_left, P_right +end +function left_and_right_projector(U, S, V, Q::EnlargedCorner, Q_next::EnlargedCorner) + isqS = sdiag_inv_sqrt(S) + P_left = left_projector(Q.E_1, Q.C, Q.E_2, V, isqS, Q.ket, Q.bra) + P_right = right_projector( + Q_next.E_1, Q_next.C, Q_next.E_2, U, isqS, Q_next.ket, Q_next.bra + ) + return P_left, P_right +end + """ left_projector(E_1, C, E_2, V, isqS, ket::PEPSTensor, bra::PEPSTensor=ket) diff --git a/src/algorithms/ctmrg/ctmrg.jl b/src/algorithms/ctmrg/ctmrg.jl index bc478870..445ee14e 100644 --- a/src/algorithms/ctmrg/ctmrg.jl +++ b/src/algorithms/ctmrg/ctmrg.jl @@ -54,60 +54,54 @@ end """ CTMRG(; tol=Defaults.ctmrg_tol, maxiter=Defaults.ctmrg_maxiter, - miniter=Defaults.ctmrg_miniter, verbosity=0, - svd_alg=SVDAdjoint(), trscheme=FixedSpaceTruncation(), - ctmrgscheme=Defaults.ctmrgscheme) + miniter=Defaults.ctmrg_miniter, flavor=Defaults.ctmrg_flavor, verbosity=0, + svd_alg=SVDAdjoint(), trscheme=FixedSpaceTruncation()) Algorithm struct that represents the CTMRG algorithm for contracting infinite PEPS. Each CTMRG run is converged up to `tol` where the singular value convergence of the corners as well as the norm is checked. The maximal and minimal number of CTMRG iterations -is set with `maxiter` and `miniter`. Different levels of output information are printed -depending on `verbosity`, where `0` suppresses all output, `1` only prints warnings, `2` -gives information at the start and end, and `3` prints information every iteration. +is set with `maxiter` and `miniter`. + +In general, two different flavors of CTMRG can be selected with `flavor` which determine how +CTMRG is implemented. It can either be `:sequential`, where the projectors are succesively +computed on the west side, and then applied and rotated. Or with `:simultaneous` all projectors +are computed and applied simultaneously on all sides, where the corners get contracted with +two projectors at the same time. + +Different levels of output information are printed depending on `verbosity`, where `0` +suppresses all output, `1` only prints warnings, `2` gives information at the start and +end, and `3` prints information every iteration. The projectors are computed from `svd_alg` SVDs where the truncation scheme is set via `trscheme`. - -In general, two different schemes can be selected with `ctmrgscheme` which determine how -CTMRG is implemented. It can either be `:sequential`, where the projectors are succesively -computed on the western side, and then applied and rotated. Or with `:simultaneous` all projectors -are computed and applied simultaneously on all sides, where in particular the corners get -contracted with two projectors at the same time. """ -struct CTMRG{S,P<:ProjectorAlgs} +struct CTMRG tol::Float64 maxiter::Int miniter::Int + flavor::Symbol verbosity::Int - projector_alg::P + projector_alg::ProjectorAlgs end function CTMRG(; tol=Defaults.ctmrg_tol, maxiter=Defaults.ctmrg_maxiter, miniter=Defaults.ctmrg_miniter, + flavor=Defaults.ctmrg_flavor, verbosity=2, svd_alg=Defaults.svd_alg, trscheme=Defaults.trscheme, - ctmrgscheme::Symbol=Defaults.ctmrgscheme, ) - return CTMRG{ctmrgscheme}( + return CTMRG( tol, maxiter, miniter, + flavor, verbosity, Defaults.projector_alg(; svd_alg, trscheme, verbosity), ) end -ctmrgscheme(::CTMRG{S}) where {S} = S - -# aliases for the different CTMRG schemes -const SequentialCTMRG{P} = CTMRG{:sequential,P} -const SimultaneousCTMRG{P} = CTMRG{:simultaneous,P} - -# supply correct constructor for Accessors.@set -Accessors.constructorof(::Type{CTMRG{S}}) where {S} = CTMRG{S} - """ MPSKit.leading_boundary([envinit], state, alg::CTMRG) @@ -126,10 +120,15 @@ function MPSKit.leading_boundary(envinit, state, alg::CTMRG) env = deepcopy(envinit) log = ignore_derivatives(() -> MPSKit.IterLog("CTMRG")) + f = if alg.flavor == :sequential + sequential_ctmrg_iter + elseif alg.flavor == :simultaneous + simultaneous_ctmrg_iter + end return LoggingExtras.withlevel(; alg.verbosity) do ctmrg_loginit!(log, η, N) for iter in 1:(alg.maxiter) - env, = ctmrg_iter(state, env, alg) # Grow and renormalize in all 4 directions + env, = f(state, env, alg) # Grow and renormalize in all 4 directions η, CS, TS = calc_convergence(env, CS, TS) N = norm(state, env) @@ -147,33 +146,7 @@ function MPSKit.leading_boundary(envinit, state, alg::CTMRG) end end -""" - ctmrg_iter(state, envs::CTMRGEnv, alg::CTMRG) -> envs′, info - -Perform one iteration of CTMRG that maps the `state` and `envs` to a new environment, -and also returns the `info` `NamedTuple`. -""" -function ctmrg_iter(state, envs::CTMRGEnv, alg::SequentialCTMRG{}) - ϵ = zero(real(scalartype(state))) - for _ in 1:4 # rotate - for col in 1:size(state, 2) # left move column-wise - projectors, info = ctmrg_projectors(col, state, envs, alg) - envs = ctmrg_renormalize(col, projectors, state, envs, alg) - ϵ = max(ϵ, info.err) - end - state = rotate_north(state, EAST) - envs = rotate_north(envs, EAST) - end - - return envs, (; err=ϵ) -end -function ctmrg_iter(state, envs::CTMRGEnv, alg::SimultaneousCTMRG) - enlarged_envs = ctmrg_expand(eachcoordinate(state, 1:4), state, envs) - projectors, info = ctmrg_projectors(enlarged_envs, envs, alg) - envs′ = ctmrg_renormalize(enlarged_envs, projectors, state, envs, alg) - return envs′, info -end - +# custom CTMRG logging ctmrg_loginit!(log, η, N) = @infov 2 loginit!(log, η, N) ctmrg_logiter!(log, iter, η, N) = @infov 3 logiter!(log, iter, η, N) ctmrg_logfinish!(log, iter, η, N) = @infov 2 logfinish!(log, iter, η, N) @@ -184,24 +157,12 @@ ctmrg_logcancel!(log, iter, η, N) = @warnv 1 logcancel!(log, iter, η, N) @non_differentiable ctmrg_logfinish!(args...) @non_differentiable ctmrg_logcancel!(args...) -# ======================================================================================== # -# Expansion step -# ======================================================================================== # - """ - ctmrg_expand(coordinates, state, envs) + compute_projector(enlarged_corners, coordinate, alg::ProjectorAlgs) -Expand the environment by absorbing a new PEPS tensor on the given coordinates. +Determine left and right projectors at the bond given determined by the enlarged corners +and the given coordinate using the specified `alg`. """ -function ctmrg_expand(coordinates, state, envs::CTMRGEnv) - return dtmap(idx -> TensorMap(EnlargedCorner(state, envs, idx), idx[1]), coordinates) -end - -# ======================================================================================== # -# Projector step -# ======================================================================================== # - -# TODO: embed this into new functions after refactor function compute_projector(enlarged_corners, coordinate, alg::HalfInfiniteProjector) # SVD half-infinite environment halfinf = half_infinite_environment(enlarged_corners...) @@ -217,50 +178,9 @@ function compute_projector(enlarged_corners, coordinate, alg::HalfInfiniteProjec end end - P_left, P_right = build_projectors(U, S, V, enlarged_corners...) + P_left, P_right = left_and_right_projector(U, S, V, enlarged_corners...) return (P_left, P_right), (; err, U, S, V) end - -""" - ctmrg_projectors(col::Int, enlarged_envs, env, alg::CTMRG{:sequential}) - ctmrg_projectors(enlarged_envs, env, alg::CTMRG{:simultaneous}) - -Compute the CTMRG projectors based on enlarged environments. -In the `:sequential` mode the projectors are computed for the column `col`, whereas -in the `:simultaneous` mode, all projectors (and corresponding SVDs) are computed in parallel. -""" -function ctmrg_projectors( - col::Int, state::InfinitePEPS, envs::CTMRGEnv{C,E}, alg::SequentialCTMRG -) where {C,E} - projector_alg = alg.projector_alg - ϵ = zero(real(scalartype(envs))) - - # SVD half-infinite environment - coordinates = eachcoordinate(envs)[:, col] - projectors = dtmap(coordinates) do (r, c) - r′ = _prev(r, size(envs.corners, 2)) - Q1 = TensorMap(EnlargedCorner(state, envs, (SOUTHWEST, r, c)), SOUTHWEST) - Q2 = TensorMap(EnlargedCorner(state, envs, (NORTHWEST, r′, c)), NORTHWEST) - QQ = halfinfinite_environment(Q1, Q2) - trscheme = truncation_scheme(projector_alg, envs.edges[WEST, r′, c]) - svd_alg = svd_algorithm(projector_alg, (WEST, r, c)) - U, S, V, ϵ_local = PEPSKit.tsvd!(QQ, svd_alg; trunc=trscheme) - ϵ = max(ϵ, ϵ_local / norm(S)) - - # Compute SVD truncation error and check for degenerate singular values - Zygote.isderiving() && ignore_derivatives() do - if alg.verbosity > 0 && is_degenerate_spectrum(S) - svals = TensorKit.SectorDict(c => diag(b) for (c, b) in blocks(S)) - @warn("degenerate singular values detected: ", svals) - end - end - - # Compute projectors - return build_projectors(U, S, V, Q1, Q2) - end - return (map(first, projectors), map(last, projectors)), (; err=ϵ) -end - function compute_projector(enlarged_corners, coordinate, alg::FullInfiniteProjector) # QR top and bottom half-infinite environments halfinf_top = half_infinite_environment(enlarged_corners[1], enlarged_corners[2]) @@ -282,199 +202,6 @@ function compute_projector(enlarged_corners, coordinate, alg::FullInfiniteProjec end end - P_left, P_right = build_projectors(U, S, V, R_top, R_bot) + P_left, P_right = left_and_right_projector(U, S, V, R_top, R_bot) return (P_left, P_right), (; err, U, S, V) end - -# """ -# ctmrg_projectors(enlarged_envs, env, alg::CTMRG{M}) - -# Compute the CTMRG projectors based from enlarged environments. -# In the `:simultaneous` mode, the environment SVD is run in parallel. -# """ -# function ctmrg_projectors( -# enlarged_envs, envs::CTMRGEnv{C,E}, alg::SequentialCTMRG{<:HalfInfiniteProjector} -# ) where {C,E} -# projector_alg = alg.projector_alg -# ϵ = zero(real(scalartype(envs))) - -# coordinates = eachcoordinate(envs) -# projectors = dtmap(coordinates) do (r, c) -# # SVD half-infinite environment -# r′ = _prev(r, size(envs.corners, 2)) -# QQ = half_infinite_environment(enlarged_envs[1, r, c], enlarged_envs[2, r′, c]) - -# trscheme = truncation_scheme(projector_alg, envs.edges[WEST, r′, c]) -# svd_alg = svd_algorithm(projector_alg, (WEST, r, c)) -# U, S, V, ϵ_local = PEPSKit.tsvd!(QQ, svd_alg; trunc=trscheme) -# ϵ = max(ϵ, ϵ_local / norm(S)) - -# # Compute SVD truncation error and check for degenerate singular values -# Zygote.isderiving() && ignore_derivatives() do -# if alg.verbosity > 0 && is_degenerate_spectrum(S) -# svals = TensorKit.SectorDict(c => diag(b) for (c, b) in blocks(S)) -# @warn("degenerate singular values detected: ", svals) -# end -# end - -# # Compute projectors -# return build_projectors(U, S, V, enlarged_envs[1, r, c], enlarged_envs[2, r′, c]) -# end - -# return (map(first, projectors), map(last, projectors)), (; err=ϵ) -# end -# function ctmrg_projectors( -# enlarged_envs, envs::CTMRGEnv{C,E}, alg::SimultaneousCTMRG{<:HalfInfiniteProjector} -# ) where {C,E} -# projector_alg = alg.projector_alg -# # pre-allocation -# U, V = Zygote.Buffer.(projector_type(envs.edges)) -# # Corner type but with real numbers -# S = Zygote.Buffer(U.data, tensormaptype(spacetype(C), 1, 1, real(scalartype(E)))) - -# ϵ = zero(real(scalartype(envs))) -# coordinates = eachcoordinate(envs, 1:4) -# projectors = dtmap(coordinates) do (dir, r, c) -# # Row-column index of next enlarged corner -# next_rc = if dir == 1 -# (r, _next(c, size(envs.corners, 3))) -# elseif dir == 2 -# (_next(r, size(envs.corners, 2)), c) -# elseif dir == 3 -# (r, _prev(c, size(envs.corners, 3))) -# elseif dir == 4 -# (_prev(r, size(envs.corners, 2)), c) -# end - -# # SVD half-infinite environment -# QQ = half_infinite_environment( -# enlarged_envs[dir, r, c], enlarged_envs[_next(dir, 4), next_rc...] -# ) - -# trscheme = truncation_scheme(projector_alg, envs.edges[dir, next_rc...]) -# svd_alg = svd_algorithm(projector_alg, (dir, r, c)) -# U_local, S_local, V_local, ϵ_local = PEPSKit.tsvd!(QQ, svd_alg; trunc=trscheme) -# U[dir, r, c] = U_local -# S[dir, r, c] = S_local -# V[dir, r, c] = V_local -# ϵ = max(ϵ, ϵ_local / norm(S_local)) - -# # Compute SVD truncation error and check for degenerate singular values -# Zygote.isderiving() && ignore_derivatives() do -# if alg.verbosity > 0 && is_degenerate_spectrum(S_local) -# svals = TensorKit.SectorDict(c => diag(b) for (c, b) in blocks(S_local)) -# @warn("degenerate singular values detected: ", svals) -# end -# end - -# # Compute projectors -# return build_projectors( -# U_local, -# S_local, -# V_local, -# enlarged_envs[dir, r, c], -# enlarged_envs[_next(dir, 4), next_rc...], -# ) -# end - -# P_left = map(first, projectors) -# P_right = map(last, projectors) -# return (P_left, P_right), (; err=ϵ, U=copy(U), S=copy(S), V=copy(V)) -# end - -""" - build_projectors(U::AbstractTensorMap{E,3,1}, S::AbstractTensorMap{E,1,1}, V::AbstractTensorMap{E,1,3}, - Q::AbstractTensorMap{E,3,3}, Q_next::AbstractTensorMap{E,3,3}) where {E<:ElementarySpace} - build_projectors(U::AbstractTensorMap{E,3,1}, S::AbstractTensorMap{E,1,1}, V::AbstractTensorMap{E,1,3}, - Q::EnlargedCorner, Q_next::EnlargedCorner) where {E<:ElementarySpace} - -Construct left and right projectors where the higher-dimensional is facing left and right, respectively. -""" -function build_projectors( - U::AbstractTensorMap{E,3,1}, - S::AbstractTensorMap{E,1,1}, - V::AbstractTensorMap{E,1,3}, - Q::AbstractTensorMap{E,3,3}, - Q_next::AbstractTensorMap{E,3,3}, -) where {E<:ElementarySpace} - isqS = sdiag_inv_sqrt(S) - P_left = Q_next * V' * isqS - P_right = isqS * U' * Q - return P_left, P_right -end -function build_projectors( - U::AbstractTensorMap{E,3,1}, - S::AbstractTensorMap{E,1,1}, - V::AbstractTensorMap{E,1,3}, - Q::EnlargedCorner, - Q_next::EnlargedCorner, -) where {E<:ElementarySpace} - isqS = sdiag_inv_sqrt(S) - P_left = left_projector(Q.E_1, Q.C, Q.E_2, V, isqS, Q.ket, Q.bra) - P_right = right_projector( - Q_next.E_1, Q_next.C, Q_next.E_2, U, isqS, Q_next.ket, Q_next.bra - ) - return P_left, P_right -end - -# ======================================================================================== # -# Renormalization step -# ======================================================================================== # - -""" - ctmrg_renormalize(col::Int, projectors, state, envs, ::CTMRG{:sequential}) - ctmrg_renormalize(enlarged_envs, projectors, state, envs, ::CTMRG{:simultaneous}) - -Apply projectors to renormalize corners and edges. -The `:sequential` mode renormalizes the environment on the column `col`, where as the -`:simultaneous` mode renormalizes all environment tensors simultaneously. -""" -function ctmrg_renormalize(col::Int, projectors, state, envs, ::SequentialCTMRG) - corners = Zygote.Buffer(envs.corners) - edges = Zygote.Buffer(envs.edges) - - for (dir, r, c) in eachcoordinate(state, 1:4) - (c == col && dir in [SOUTHWEST, NORTHWEST]) && continue - corners[dir, r, c] = envs.corners[dir, r, c] - end - for (dir, r, c) in eachcoordinate(state, 1:4) - (c == col && dir == WEST) && continue - edges[dir, r, c] = envs.edges[dir, r, c] - end - - # Apply projectors to renormalize corners and edge - for row in axes(envs.corners, 2) - C_southwest = renormalize_bottom_corner((row, col), envs, projectors) - corners[SOUTHWEST, row, col] = C_southwest / norm(C_southwest) - - C_northwest = renormalize_top_corner((row, col), envs, projectors) - corners[NORTHWEST, row, col] = C_northwest / norm(C_northwest) - - E_west = renormalize_west_edge((row, col), envs, projectors, state) - edges[WEST, row, col] = E_west / norm(E_west) - end - - return CTMRGEnv(copy(corners), copy(edges)) -end -function ctmrg_renormalize(enlarged_envs, projectors, state, envs, ::SimultaneousCTMRG) - P_left, P_right = projectors - coordinates = eachcoordinate(envs, 1:4) - corners_edges = dtmap(coordinates) do (dir, r, c) - if dir == NORTH - corner = renormalize_northwest_corner((r, c), enlarged_envs, P_left, P_right) - edge = renormalize_north_edge((r, c), envs, P_left, P_right, state) - elseif dir == EAST - corner = renormalize_northeast_corner((r, c), enlarged_envs, P_left, P_right) - edge = renormalize_east_edge((r, c), envs, P_left, P_right, state) - elseif dir == SOUTH - corner = renormalize_southeast_corner((r, c), enlarged_envs, P_left, P_right) - edge = renormalize_south_edge((r, c), envs, P_left, P_right, state) - elseif dir == WEST - corner = renormalize_southwest_corner((r, c), enlarged_envs, P_left, P_right) - edge = renormalize_west_edge((r, c), envs, P_left, P_right, state) - end - return corner / norm(corner), edge / norm(edge) - end - - return CTMRGEnv(map(first, corners_edges), map(last, corners_edges)) -end diff --git a/src/algorithms/ctmrg/sequential.jl b/src/algorithms/ctmrg/sequential.jl new file mode 100644 index 00000000..7631fefe --- /dev/null +++ b/src/algorithms/ctmrg/sequential.jl @@ -0,0 +1,85 @@ +""" + sequential_ctmrg_iter(state, envs::CTMRGEnv, alg::CTMRG) -> envs′, info + +Perform one sequential iteration of CTMRG, where one iteration consists of four expansion, +renormalization and rotation steps that are performed sequentially. +""" +function sequential_ctmrg_iter(state, envs::CTMRGEnv, alg::CTMRG) + ϵ = zero(real(scalartype(state))) + for _ in 1:4 # rotate + for col in 1:size(state, 2) # left move column-wise + projectors, info = sequential_projectors(col, state, envs, alg.projector_alg) + envs = renormalize_sequentially(col, projectors, state, envs) + ϵ = max(ϵ, info.err) + end + state = rotate_north(state, EAST) + envs = rotate_north(envs, EAST) + end + + return envs, (; err=ϵ) +end + +function sequential_projectors( + col::Int, state::InfinitePEPS, envs::CTMRGEnv{C,E}, alg::ProjectorAlgs +) where {C,E} + ϵ = zero(real(scalartype(envs))) + + # SVD half-infinite environment column-wise + coordinates = eachcoordinate(envs)[:, col] + projectors = dtmap(coordinates) do (r, c) + proj, info = simultaneous_projector(state, envs, (WEST, r, c), alg) + ϵ = max(ϵ, info.err / norm(info.S)) + return proj + end + + return (map(first, projectors), map(last, projectors)), (; err=ϵ) +end +function sequential_projector( + state::InfinitePEPS, envs::CTMRGEnv, coordinate, alg::HalfInfiniteProjector +) + _, r, c = coordinate + r′ = _prev(r, size(envs, 2)) + Q1 = TensorMap(EnlargedCorner(state, envs, (SOUTHWEST, r, c)), SOUTHWEST) + Q2 = TensorMap(EnlargedCorner(state, envs, (NORTHWEST, r′, c)), NORTHWEST) + return compute_projector((Q1, Q2), coordinate, alg) +end +function sequential_projector( + state::InfinitePEPS, envs::CTMRGEnv, coordinate, alg::FullInfiniteProjector +) + _, r, c = coordinate + r′ = _next(r, size(envs, 2)) + c′ = _next(c, size(envs, 3)) + Q1 = TensorMap(EnlargedCorner(state, envs, (NORTHWEST, r, c)), NORTHWEST) + Q2 = TensorMap(EnlargedCorner(state, envs, (NORTHEAST, r, c′)), NORTHEAST) + Q3 = TensorMap(EnlargedCorner(state, envs, (SOUTHEAST, r′, c′)), SOUTHEAST) + Q4 = TensorMap(EnlargedCorner(state, envs, (SOUTHWEST, r′, c)), SOUTHWEST) + return compute_projector((Q1, Q2, Q3, Q4), coordinate, alg) +end + +function renormalize_sequentially(col::Int, projectors, state, envs) + corners = Zygote.Buffer(envs.corners) + edges = Zygote.Buffer(envs.edges) + + for (dir, r, c) in eachcoordinate(state, 1:4) + (c == col && dir in [SOUTHWEST, NORTHWEST]) && continue + corners[dir, r, c] = envs.corners[dir, r, c] + end + for (dir, r, c) in eachcoordinate(state, 1:4) + (c == col && dir == WEST) && continue + edges[dir, r, c] = envs.edges[dir, r, c] + end + + # Apply projectors to renormalize corners and edge + for row in axes(envs.corners, 2) + C_southwest = renormalize_bottom_corner((row, col), envs, projectors) + corners[SOUTHWEST, row, col] = C_southwest / norm(C_southwest) + + C_northwest = renormalize_top_corner((row, col), envs, projectors) + corners[NORTHWEST, row, col] = C_northwest / norm(C_northwest) + + E_west = renormalize_west_edge((row, col), envs, projectors, state) + edges[WEST, row, col] = E_west / norm(E_west) + end + + return CTMRGEnv(copy(corners), copy(edges)) +end diff --git a/src/algorithms/ctmrg/simultaneous.jl b/src/algorithms/ctmrg/simultaneous.jl new file mode 100644 index 00000000..43f34fc9 --- /dev/null +++ b/src/algorithms/ctmrg/simultaneous.jl @@ -0,0 +1,81 @@ +""" + simultaneous_ctmrg_iter(state, envs::CTMRGEnv, alg::CTMRG) -> envs′, info + +Perform one simultaneous iteration of CTMRG, in which the environment is expanded and +renormalized in all directions at the same time. +""" +function simultaneous_ctmrg_iter(state, envs::CTMRGEnv, alg::CTMRG) + enlarged_corners = dtmap(eachcoordinate(state, 1:4)) do idx + return TensorMap(EnlargedCorner(state, envs, idx), idx[1]) + end # expand environment + projectors, info = simultaneous_projectors(enlarged_corners, envs, alg.projector_alg) # compute projectors on all coordinates + envs′ = renormalize_simultaneously(enlarged_corners, projectors, state, envs) # renormalize enlarged corners + return envs′, info +end + +function simultaneous_projectors( + enlarged_corners, envs::CTMRGEnv{C,E}, alg::ProjectorAlgs +) where {C,E} + # pre-allocation + U, V = Zygote.Buffer.(projector_type(envs.edges)) + S = Zygote.Buffer(U.data, tensormaptype(spacetype(C), 1, 1, real(scalartype(E)))) # Corner type but with real numbers + ϵ = zero(real(scalartype(envs))) + + projectors = dtmap(eachcoordinate(envs, 1:4)) do coordinate + proj, info = compute_projector(enlarged_corners, coordinate, alg) + U[dir, r, c] = info.U + S[dir, r, c] = info.S + V[dir, r, c] = info.V + ϵ = max(ϵ, info.err / norm(info.S)) + return proj + end + + P_left = map(first, projectors) + P_right = map(last, projectors) + return (P_left, P_right), (; err=ϵ, U=copy(U), S=copy(S), V=copy(V)) +end +function simultaneous_projector( + enlarged_corners::Array{E,3}, coordinate, alg::HalfInfiniteProjector +) where {E} + coordinate′ = _next_projector_coordinate(coordinate, size(enlarged_corners)[2:3]...) + ec = (enlarged_corners[coordinate...], enlarged_corners[coordinate′...]) + return compute_projector(ec, coordinate, alg) +end +function simultaneous_projector( + enlarged_corners::Array{E,3}, coordinate, alg::FullInfiniteProjector +) where {E} + rowsize, colsize = size(enlarged_corners)[2:3] + coordinate2 = _next_projector_coordinate(coordinate, rowsize, colsize) + coordinate3 = _next_projector_coordinate(coordinate2, rowsize, colsize) + coordinate4 = _next_projector_coordinate(coordinate3, rowsize, colsize) + ec = ( + enlarged_corners[coordinate...], + enlarged_corners[coordinate2...], + enlarged_corners[coordinate3...], + enlarged_corners[coordinate4...], + ) + return compute_projector(ec, coordinate, alg) +end + +function renormalize_simultaneously(enlarged_corners, projectors, state, envs) + P_left, P_right = projectors + coordinates = eachcoordinate(envs, 1:4) + corners_edges = dtmap(coordinates) do (dir, r, c) + if dir == NORTH + corner = renormalize_northwest_corner((r, c), enlarged_corners, P_left, P_right) + edge = renormalize_north_edge((r, c), envs, P_left, P_right, state) + elseif dir == EAST + corner = renormalize_northeast_corner((r, c), enlarged_corners, P_left, P_right) + edge = renormalize_east_edge((r, c), envs, P_left, P_right, state) + elseif dir == SOUTH + corner = renormalize_southeast_corner((r, c), enlarged_corners, P_left, P_right) + edge = renormalize_south_edge((r, c), envs, P_left, P_right, state) + elseif dir == WEST + corner = renormalize_southwest_corner((r, c), enlarged_corners, P_left, P_right) + edge = renormalize_west_edge((r, c), envs, P_left, P_right, state) + end + return corner / norm(corner), edge / norm(edge) + end + + return CTMRGEnv(map(first, corners_edges), map(last, corners_edges)) +end diff --git a/src/algorithms/peps_opt.jl b/src/algorithms/peps_opt.jl index 39e138a1..00eac45f 100644 --- a/src/algorithms/peps_opt.jl +++ b/src/algorithms/peps_opt.jl @@ -265,7 +265,7 @@ function _rrule( fwd_alg=FixedSVD(Ufix, info.S, Vfix), rrule_alg=alg.projector_alg.svd_alg.rrule_alg ) alg_fixed = CTMRG(; - svd_alg=svd_alg_fixed, trscheme=notrunc(), ctmrgscheme=:simultaneous + svd_alg=svd_alg_fixed, trscheme=notrunc(), flavor=:simultaneous ) function leading_boundary_fixed_pullback(Δenvs′) diff --git a/src/environments/ctmrg_environments.jl b/src/environments/ctmrg_environments.jl index c026762b..29a823cc 100644 --- a/src/environments/ctmrg_environments.jl +++ b/src/environments/ctmrg_environments.jl @@ -354,23 +354,7 @@ function ChainRulesCore.rrule(::typeof(getproperty), e::CTMRGEnv, name::Symbol) throw(ArgumentError("No rrule for getproperty of $name")) end end - -# Rotate corners & edges counter-clockwise -function Base.rotl90(env::CTMRGEnv{C,T}) where {C,T} - # Initialize rotated corners & edges with rotated sizes - corners′ = Zygote.Buffer( - Array{C,3}(undef, 4, size(env.corners, 3), size(env.corners, 2)) - ) - edges′ = Zygote.Buffer(Array{T,3}(undef, 4, size(env.edges, 3), size(env.edges, 2))) - - for dir in 1:4 - corners′[_prev(dir, 4), :, :] = rotl90(env.corners[dir, :, :]) - edges′[_prev(dir, 4), :, :] = rotl90(env.edges[dir, :, :]) - end - - return CTMRGEnv(copy(corners′), copy(edges′)) -end - +Base.size(env::CTMRG, args...) = size(env.corners, args...) Base.eltype(env::CTMRGEnv) = eltype(env.corners[1]) Base.axes(x::CTMRGEnv, args...) = axes(x.corners, args...) function eachcoordinate(x::CTMRGEnv) @@ -379,7 +363,6 @@ end function eachcoordinate(x::CTMRGEnv, dirs) return collect(Iterators.product(dirs, axes(x, 2), axes(x, 3))) end - Base.real(env::CTMRGEnv) = CTMRGEnv(real.(env.corners), real.(env.edges)) Base.complex(env::CTMRGEnv) = CTMRGEnv(complex.(env.corners), complex.(env.edges)) @@ -390,6 +373,20 @@ function update!(env::CTMRGEnv{C,T}, env´::CTMRGEnv{C,T}) where {C,T} return env end +# Rotate corners & edges counter-clockwise +function Base.rotl90(env::CTMRGEnv{C,T}) where {C,T} + # Initialize rotated corners & edges with rotated sizes + corners′ = Zygote.Buffer(Array{C,3}(undef, 4, size(env, 3), size(env, 2))) + edges′ = Zygote.Buffer(Array{T,3}(undef, 4, size(env, 3), size(env, 2))) + + for dir in 1:4 + corners′[_prev(dir, 4), :, :] = rotl90(env.corners[dir, :, :]) + edges′[_prev(dir, 4), :, :] = rotl90(env.edges[dir, :, :]) + end + + return CTMRGEnv(copy(corners′), copy(edges′)) +end + # Functions used for FP differentiation and by KrylovKit.linsolve function Base.:+(e₁::CTMRGEnv, e₂::CTMRGEnv) return CTMRGEnv(e₁.corners + e₂.corners, e₁.edges + e₂.edges) diff --git a/src/utility/util.jl b/src/utility/util.jl index 0b9fd4c4..93cb9577 100644 --- a/src/utility/util.jl +++ b/src/utility/util.jl @@ -1,7 +1,31 @@ -# Get next and previous directional CTM enviroment index, respecting periodicity +# Get next and previous directional CTMRG environment index, respecting periodicity _next(i, total) = mod1(i + 1, total) _prev(i, total) = mod1(i - 1, total) +# Get next and previous coordinate (direction, row, column), given a direction and going around the environment clockwise +function _next_coordinate((dir, row, col), rowsize, colsize) + if dir == 1 + return (_next(dir, 4), row, _next(col, colsize)) + elseif dir == 2 + return (_next(dir, 4), _next(row, rowsize), col) + elseif dir == 3 + return (_next(dir, 4), row, _prev(col, colsize)) + elseif dir == 4 + return (_next(dir, 4), _prev(row, rowsize), col) + end +end +function _prev_coordinate((dir, row, col), rowsize, colsize) + if dir == 1 + return (_prev(dir, 4), _next(row, rowsize), col) + elseif dir == 2 + return (_prev(dir, 4), row, _prev(col, colsize)) + elseif dir == 3 + return (_prev(dir, 4), _prev(row, rowsize), col) + elseif dir == 4 + return (_prev(dir, 4), row, _next(col, colsize)) + end +end + # iterator over each coordinates """ eachcoordinate(x, dirs=1:4) diff --git a/test/ctmrg/ctmrgschemes.jl b/test/ctmrg/flavors.jl similarity index 88% rename from test/ctmrg/ctmrgschemes.jl rename to test/ctmrg/flavors.jl index 406d4c1f..c1c848a9 100644 --- a/test/ctmrg/ctmrgschemes.jl +++ b/test/ctmrg/flavors.jl @@ -7,8 +7,8 @@ using PEPSKit # initialize parameters χbond = 2 χenv = 16 -ctm_alg_sequential = CTMRG(; ctmrgscheme=:sequential) -ctm_alg_simultaneous = CTMRG(; ctmrgscheme=:simultaneous) +ctm_alg_sequential = CTMRG(; flavor=:sequential) +ctm_alg_simultaneous = CTMRG(; flavor=:simultaneous) unitcells = [(1, 1), (3, 4)] @testset "$(unitcell) unit cell" for unitcell in unitcells @@ -52,13 +52,9 @@ unitcells = [(1, 1), (3, 4)] end # test fixedspace actually fixes space -@testset "Fixedspace truncation ($scheme)" for scheme in [:sequential, :simultaneous] +@testset "Fixedspace truncation ($flavor)" for flavor in [:sequential, :simultaneous] ctm_alg = CTMRG(; - tol=1e-6, - maxiter=1, - verbosity=0, - ctmrgscheme=scheme, - trscheme=FixedSpaceTruncation(), + tol=1e-6, maxiter=1, verbosity=0, flavor, trscheme=FixedSpaceTruncation() ) Ds = fill(2, 3, 3) χs = [16 17 18; 15 20 21; 14 19 22] diff --git a/test/ctmrg/gaugefix.jl b/test/ctmrg/gaugefix.jl index ef36943c..748c5b05 100644 --- a/test/ctmrg/gaugefix.jl +++ b/test/ctmrg/gaugefix.jl @@ -8,7 +8,7 @@ using PEPSKit: ctmrg_iter, gauge_fix, calc_elementwise_convergence scalartypes = [Float64, ComplexF64] unitcells = [(1, 1), (2, 2), (3, 2)] maxiter = 200 -schemes = [:simultaneous, :sequential] +ctmrg_flavors = [:simultaneous, :sequential] χ = 6 atol = 1e-4 @@ -34,10 +34,9 @@ function _semi_random_peps!(psi::InfinitePEPS) return InfinitePEPS(A′) end -@testset "Trivial symmetry ($T) - ($unitcell) - ($ctmrgscheme)" for ( - T, unitcell, ctmrgscheme -) in Iterators.product( - scalartypes, unitcells, schemes +@testset "Trivial symmetry ($T) - ($unitcell) - ($flavor)" for (T, unitcell, flavor) in + Iterators.product( + scalartypes, unitcells, ctmrg_flavors ) physical_space = ComplexSpace(2) peps_space = ComplexSpace(2) @@ -50,7 +49,7 @@ end Random.seed!(987654321) # Seed RNG to make random environment consistent ctm = CTMRGEnv(psi, ctm_space) - alg = CTMRG(; maxiter, ctmrgscheme) + alg = CTMRG(; maxiter, flavor) ctm = leading_boundary(ctm, psi, alg) ctm2, = ctmrg_iter(psi, ctm, alg) @@ -58,9 +57,9 @@ end @test calc_elementwise_convergence(ctm, ctm_fixed) ≈ 0 atol = atol end -@testset "Z2 symmetry ($T) - ($unitcell) - ($ctmrgscheme)" for (T, unitcell, ctmrgscheme) in - Iterators.product( - scalartypes, unitcells, schemes +@testset "Z2 symmetry ($T) - ($unitcell) - ($flavor)" for (T, unitcell, flavor) in + Iterators.product( + scalartypes, unitcells, ctrmg_flavors ) physical_space = Z2Space(0 => 1, 1 => 1) peps_space = Z2Space(0 => 1, 1 => 1) @@ -74,7 +73,7 @@ end psi = InfinitePEPS(physical_space, peps_space; unitcell) ctm = CTMRGEnv(psi, ctm_space) - alg = CTMRG(; maxiter, ctmrgscheme) + alg = CTMRG(; maxiter, flavor) ctm = leading_boundary(ctm, psi, alg) ctm2, = ctmrg_iter(psi, ctm, alg) diff --git a/test/ctmrg/gradients.jl b/test/ctmrg/gradients.jl index 2c18ef90..46677810 100644 --- a/test/ctmrg/gradients.jl +++ b/test/ctmrg/gradients.jl @@ -18,8 +18,7 @@ names = ["Heisenberg", "p-wave superconductor"] gradtol = 1e-4 boundary_algs = [ - CTMRG(; verbosity=0, ctmrgscheme=:simultaneous), - CTMRG(; verbosity=0, ctmrgscheme=:sequential), + CTMRG(; verbosity=0, flavor=:simultaneous), CTMRG(; verbosity=0, flavor=:sequential) ] gradmodes = [ [ diff --git a/test/ctmrg/gradparts.jl b/test/ctmrg/gradparts.jl index 846ea90a..4af4627a 100644 --- a/test/ctmrg/gradparts.jl +++ b/test/ctmrg/gradparts.jl @@ -30,9 +30,10 @@ Vspaces = [ComplexSpace(χbond), Vect[FermionParity](0 => χbond / 2, 1 => χbon Espaces = [ComplexSpace(χenv), Vect[FermionParity](0 => χenv / 2, 1 => χenv / 2)] tol = 1e-10 atol = 1e-6 +verbosity = 0 boundary_algs = [ - CTMRG(; tol, verbosity=0, ctmrgscheme=:simultaneous), - CTMRG(; tol, verbosity=0, ctmrgscheme=:sequential), + CTMRG(; tol, verbosity, flavor=:simultaneous), + CTMRG(; tol, verbosity, flavor=:sequential), ] ## Gauge invariant function of the environment diff --git a/test/runtests.jl b/test/runtests.jl index 07a4cd2f..49d2fc0c 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -27,7 +27,7 @@ end include("ctmrg/unitcell.jl") end @time @safetestset "CTMRG schemes" begin - include("ctmrg/ctmrgschemes.jl") + include("ctmrg/flavors.jl") end end if GROUP == "ALL" || GROUP == "BOUNDARYMPS" From d9d0b05a606dfebe05f01a147a3df2f45a6d9756 Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Fri, 6 Dec 2024 12:11:57 +0100 Subject: [PATCH 189/213] Fix typos, make runnable, replace pullback with rrule_via_ad --- src/PEPSKit.jl | 9 ++- .../contractions/ctmrg_contractions.jl | 56 ++++++++++--------- src/algorithms/ctmrg/ctmrg.jl | 19 ++++--- src/algorithms/ctmrg/sequential.jl | 2 +- src/algorithms/ctmrg/simultaneous.jl | 16 +++--- src/algorithms/ctmrg/sparse_environments.jl | 20 +++++-- src/algorithms/peps_opt.jl | 48 +++++++--------- src/environments/ctmrg_environments.jl | 2 +- test/ctmrg/fixed_iterscheme.jl | 14 ++--- test/ctmrg/gaugefix.jl | 6 +- test/ctmrg/gradparts.jl | 10 ++-- test/ctmrg/unitcell.jl | 4 +- 12 files changed, 112 insertions(+), 94 deletions(-) diff --git a/src/PEPSKit.jl b/src/PEPSKit.jl index a1875fc1..0b2d11d4 100644 --- a/src/PEPSKit.jl +++ b/src/PEPSKit.jl @@ -41,6 +41,8 @@ include("algorithms/contractions/ctmrg_contractions.jl") include("algorithms/ctmrg/sparse_environments.jl") include("algorithms/ctmrg/ctmrg.jl") +include("algorithms/ctmrg/simultaneous.jl") +include("algorithms/ctmrg/sequential.jl") include("algorithms/ctmrg/gaugefix.jl") include("algorithms/toolbox.jl") @@ -94,7 +96,7 @@ Module containing default values that represent typical algorithm parameters. """ module Defaults using TensorKit, KrylovKit, OptimKit, OhMyThreads - using PEPSKit: LinSolver, FixedSpaceTruncation, SVDAdjoint + using PEPSKit: LinSolver, FixedSpaceTruncation, SVDAdjoint, HalfInfiniteProjector const ctmrg_maxiter = 100 const ctmrg_miniter = 4 const ctmrg_tol = 1e-8 @@ -107,6 +109,7 @@ module Defaults const fwd_alg = TensorKit.SDD() const rrule_alg = Arnoldi(; tol=1e-2fpgrad_tol, krylovdim=48, verbosity=-1) const svd_alg = SVDAdjoint(; fwd_alg, rrule_alg) + const projector_alg = HalfInfiniteProjector const optimizer = LBFGS(32; maxiter=100, gradtol=1e-4, verbosity=2) const gradient_linsolver = KrylovKit.BiCGStab(; maxiter=Defaults.fpgrad_maxiter, tol=Defaults.fpgrad_tol @@ -162,9 +165,9 @@ end using .Defaults: set_scheduler! export set_scheduler! export SVDAdjoint, IterSVD, NonTruncSVDAdjoint -export FixedSpaceTruncation, ProjectorAlg, CTMRG, CTMRGEnv, correlation_length +export FixedSpaceTruncation, HalfInfiniteProjector, FullInfiniteProjector, CTMRG, CTMRGEnv export LocalOperator -export expectation_value, costfun, product_peps +export expectation_value, costfun, product_peps, correlation_length export leading_boundary export PEPSOptimize, GeomSum, ManualIter, LinSolver export fixedpoint diff --git a/src/algorithms/contractions/ctmrg_contractions.jl b/src/algorithms/contractions/ctmrg_contractions.jl index 0a84ae06..69921868 100644 --- a/src/algorithms/contractions/ctmrg_contractions.jl +++ b/src/algorithms/contractions/ctmrg_contractions.jl @@ -163,31 +163,6 @@ end # Projector contractions # ---------------------- -""" - left_and_right_projector(U, S, V, Q::AbstractTensorMap{E,3,3}, Q_next::AbstractTensorMap{E,3,3} - left_and_right_projector(U, S, V, Q::EnlargedCorner, Q_next::EnlargedCorner) - -Compute left and right projectors based on a SVD and quadrant tensors, specified either as -`AbstractTensorMap`s or sparsely as `EnlargedCorner`s such that the quadrants are never -constructed explicitly. -""" -function left_and_right_projector( - U, S, V, Q::AbstractTensorMap{E,3,3}, Q_next::AbstractTensorMap{E,3,3} -) where {E<:ElementarySpace} - isqS = sdiag_inv_sqrt(S) - P_left = Q_next * V' * isqS - P_right = isqS * U' * Q - return P_left, P_right -end -function left_and_right_projector(U, S, V, Q::EnlargedCorner, Q_next::EnlargedCorner) - isqS = sdiag_inv_sqrt(S) - P_left = left_projector(Q.E_1, Q.C, Q.E_2, V, isqS, Q.ket, Q.bra) - P_right = right_projector( - Q_next.E_1, Q_next.C, Q_next.E_2, U, isqS, Q_next.ket, Q_next.bra - ) - return P_left, P_right -end - """ left_projector(E_1, C, E_2, V, isqS, ket::PEPSTensor, bra::PEPSTensor=ket) @@ -234,6 +209,37 @@ function right_projector(E_1, C, E_2, U, isqS, ket::PEPSTensor, bra::PEPSTensor= E_1[χ4 D5 D6; χ_out] end +""" + left_and_right_projector(U, S, V, Q::AbstractTensorMap{E,3,3}, Q_next::AbstractTensorMap{E,3,3} + +Compute left and right projectors based on a SVD and quadrant tensors, where the inverse +square root `isqS` of the singular values is computed. + +Left projector: +``` + -- |~~~~~~~~| -- |~~| + | Q_next | |V'| -- isqS -- + == |~~~~~~~~| == |~~| +``` + +Right projector: +``` + |~~| -- |~~~| -- + -- isqS -- |U'| | Q | + |~~| == |~~~| == +``` +""" +function left_and_right_projector( + U, S, V, Q::AbstractTensorMap{E,3,3}, Q_next::AbstractTensorMap{E,3,3} +) where {E<:ElementarySpace} + isqS = sdiag_inv_sqrt(S) + @autoopt @tensor P_left[χ_in D_inabove D_inbelow; χ_out] := + Q_next[χ_in D_inabove D_inbelow; χ1 D1 D2] * conj(V[χ2; χ1 D1 D2]) * isqS[χ2; χ_out] + @tensor P_right[χ_in; χ_out D_outabove D_outbelow] := + isqS[χ_in; χ1] * conj(U[χ2 D1 D2; χ1]) * Q[χ2 D1 D2; χ_out D_outabove D_outbelow] + return P_left, P_right +end + """ half_infinite_environment(quadrant1::AbstractTensorMap{S,3,3}, quadrant2::AbstractTensorMap{S,3,3}) half_infinite_environment(C_1, C_2, E_1, E_2, E_3, E_4, diff --git a/src/algorithms/ctmrg/ctmrg.jl b/src/algorithms/ctmrg/ctmrg.jl index 445ee14e..c9aecc1d 100644 --- a/src/algorithms/ctmrg/ctmrg.jl +++ b/src/algorithms/ctmrg/ctmrg.jl @@ -102,6 +102,14 @@ function CTMRG(; ) end +function ctmrg_iteration(state, env, alg::CTMRG) + if alg.flavor == :simultaneous + return simultaneous_ctmrg_iter(state, env, alg) + elseif alg.flavor == :sequential + return sequential_ctmrg_iter(state, env, alg) + end +end + """ MPSKit.leading_boundary([envinit], state, alg::CTMRG) @@ -120,15 +128,10 @@ function MPSKit.leading_boundary(envinit, state, alg::CTMRG) env = deepcopy(envinit) log = ignore_derivatives(() -> MPSKit.IterLog("CTMRG")) - f = if alg.flavor == :sequential - sequential_ctmrg_iter - elseif alg.flavor == :simultaneous - simultaneous_ctmrg_iter - end return LoggingExtras.withlevel(; alg.verbosity) do ctmrg_loginit!(log, η, N) for iter in 1:(alg.maxiter) - env, = f(state, env, alg) # Grow and renormalize in all 4 directions + env, = ctmrg_iteration(state, env, alg) # Grow and renormalize in all 4 directions η, CS, TS = calc_convergence(env, CS, TS) N = norm(state, env) @@ -166,8 +169,8 @@ and the given coordinate using the specified `alg`. function compute_projector(enlarged_corners, coordinate, alg::HalfInfiniteProjector) # SVD half-infinite environment halfinf = half_infinite_environment(enlarged_corners...) - trscheme = truncation_scheme(projector_alg, space(enlarged_corners[2], 1)) - svd_alg = svd_algorithm(projector_alg, coordinate) + trscheme = truncation_scheme(alg, space(enlarged_corners[2], 1)) + svd_alg = svd_algorithm(alg, coordinate) U, S, V, err = PEPSKit.tsvd!(halfinf, svd_alg; trunc=trscheme) # Compute SVD truncation error and check for degenerate singular values diff --git a/src/algorithms/ctmrg/sequential.jl b/src/algorithms/ctmrg/sequential.jl index 7631fefe..efebc594 100644 --- a/src/algorithms/ctmrg/sequential.jl +++ b/src/algorithms/ctmrg/sequential.jl @@ -27,7 +27,7 @@ function sequential_projectors( # SVD half-infinite environment column-wise coordinates = eachcoordinate(envs)[:, col] projectors = dtmap(coordinates) do (r, c) - proj, info = simultaneous_projector(state, envs, (WEST, r, c), alg) + proj, info = sequential_projector(state, envs, (WEST, r, c), alg) ϵ = max(ϵ, info.err / norm(info.S)) return proj end diff --git a/src/algorithms/ctmrg/simultaneous.jl b/src/algorithms/ctmrg/simultaneous.jl index 43f34fc9..f94bef38 100644 --- a/src/algorithms/ctmrg/simultaneous.jl +++ b/src/algorithms/ctmrg/simultaneous.jl @@ -22,10 +22,10 @@ function simultaneous_projectors( ϵ = zero(real(scalartype(envs))) projectors = dtmap(eachcoordinate(envs, 1:4)) do coordinate - proj, info = compute_projector(enlarged_corners, coordinate, alg) - U[dir, r, c] = info.U - S[dir, r, c] = info.S - V[dir, r, c] = info.V + proj, info = simultaneous_projector(enlarged_corners, coordinate, alg) + U[coordinate...] = info.U + S[coordinate...] = info.S + V[coordinate...] = info.V ϵ = max(ϵ, info.err / norm(info.S)) return proj end @@ -37,7 +37,7 @@ end function simultaneous_projector( enlarged_corners::Array{E,3}, coordinate, alg::HalfInfiniteProjector ) where {E} - coordinate′ = _next_projector_coordinate(coordinate, size(enlarged_corners)[2:3]...) + coordinate′ = _next_coordinate(coordinate, size(enlarged_corners)[2:3]...) ec = (enlarged_corners[coordinate...], enlarged_corners[coordinate′...]) return compute_projector(ec, coordinate, alg) end @@ -45,9 +45,9 @@ function simultaneous_projector( enlarged_corners::Array{E,3}, coordinate, alg::FullInfiniteProjector ) where {E} rowsize, colsize = size(enlarged_corners)[2:3] - coordinate2 = _next_projector_coordinate(coordinate, rowsize, colsize) - coordinate3 = _next_projector_coordinate(coordinate2, rowsize, colsize) - coordinate4 = _next_projector_coordinate(coordinate3, rowsize, colsize) + coordinate2 = _next_coordinate(coordinate, rowsize, colsize) + coordinate3 = _next_coordinate(coordinate2, rowsize, colsize) + coordinate4 = _next_coordinate(coordinate3, rowsize, colsize) ec = ( enlarged_corners[coordinate...], enlarged_corners[coordinate2...], diff --git a/src/algorithms/ctmrg/sparse_environments.jl b/src/algorithms/ctmrg/sparse_environments.jl index 4f9fbad2..7b9edab9 100644 --- a/src/algorithms/ctmrg/sparse_environments.jl +++ b/src/algorithms/ctmrg/sparse_environments.jl @@ -97,6 +97,21 @@ function renormalize_southwest_corner(ec::EnlargedCorner, P_left, P_right) ) end +# Wrapper around half_infinite_environment contraction using EnlargedCorners (used in ctmrg_projectors) +function half_infinite_environment(ec_1::EnlargedCorner, ec_2::EnlargedCorner) + return HalfInfiniteEnv(ec_1, ec_2) +end + +# Compute left and right projectors sparsely without constructing enlarged corners explicitly +function left_and_right_projector(U, S, V, Q::EnlargedCorner, Q_next::EnlargedCorner) + isqS = sdiag_inv_sqrt(S) + P_left = left_projector(Q.E_1, Q.C, Q.E_2, V, isqS, Q.ket, Q.bra) + P_right = right_projector( + Q_next.E_1, Q_next.C, Q_next.E_2, U, isqS, Q_next.ket, Q_next.bra + ) + return P_left, P_right +end + # -------------------------------- # Sparse half-infinite environment # -------------------------------- @@ -193,11 +208,6 @@ function (env::HalfInfiniteEnv)(x, ::Val{true}) # Adjoint linear map: env()' * ) end -# Wrapper around half_infinite_environment contraction using EnlargedCorners (used in ctmrg_projectors) -function half_infinite_environment(ec_1::EnlargedCorner, ec_2::EnlargedCorner) - return HalfInfiniteEnv(ec_1, ec_2) -end - # AbstractTensorMap subtyping and IterSVD compatibility function TensorKit.domain(env::HalfInfiniteEnv) return domain(env.E_4) * domain(env.ket_2)[3] * domain(env.bra_2)[3]' diff --git a/src/algorithms/peps_opt.jl b/src/algorithms/peps_opt.jl index 00eac45f..91e5adc8 100644 --- a/src/algorithms/peps_opt.jl +++ b/src/algorithms/peps_opt.jl @@ -95,13 +95,13 @@ struct PEPSOptimize{G} gradient_alg::G function PEPSOptimize( # Inner constructor to prohibit illegal setting combinations - boundary_alg::CTMRG{S}, + boundary_alg::CTMRG, optimizer, reuse_env, gradient_alg::G, - ) where {S,G} + ) where {G} if gradient_alg isa GradMode - if S === :sequential && iterscheme(gradient_alg) === :fixed + if boundary_alg.flavor === :sequential && iterscheme(gradient_alg) === :fixed throw(ArgumentError(":sequential and :fixed are not compatible")) end end @@ -216,26 +216,24 @@ Evaluating the gradient of the cost function for CTMRG: function _rrule( gradmode::GradMode{:diffgauge}, - ::RuleConfig, + config::RuleConfig, ::typeof(MPSKit.leading_boundary), envinit, state, alg::CTMRG, ) - envs = leading_boundary(envinit, state, alg) #TODO: fixed space for unit cells + envs = leading_boundary(envinit, state, alg) function leading_boundary_diffgauge_pullback(Δenvs′) Δenvs = unthunk(Δenvs′) # find partial gradients of gauge_fixed single CTMRG iteration - # TODO: make this rrule_via_ad so it's zygote-agnostic - _, env_vjp = pullback(state, envs) do A, x - return gauge_fix(x, ctmrg_iter(A, x, alg)[1])[1] - end + f(A, x) = gauge_fix(x, ctmrg_iteration(A, x, alg)[1])[1] + _, env_vjp = rrule_via_ad(config, f, state, envs) # evaluate the geometric sum - ∂f∂A(x)::typeof(state) = env_vjp(x)[1] - ∂f∂x(x)::typeof(envs) = env_vjp(x)[2] + ∂f∂A(x)::typeof(state) = env_vjp(x)[2] + ∂f∂x(x)::typeof(envs) = env_vjp(x)[3] ∂F∂envs = fpgrad(Δenvs, ∂f∂x, ∂f∂A, Δenvs, gradmode) return NoTangent(), ZeroTangent(), ∂F∂envs, NoTangent() @@ -247,44 +245,40 @@ end # Here f is differentiated from an pre-computed SVD with fixed U, S and V function _rrule( gradmode::GradMode{:fixed}, - ::RuleConfig, + config::RuleConfig, ::typeof(MPSKit.leading_boundary), envinit, state, - alg::CTMRG{C}, -) where {C} - @assert C === :simultaneous + alg::CTMRG, +) + @assert alg.flavor === :simultaneous @assert !isnothing(alg.projector_alg.svd_alg.rrule_alg) envs = leading_boundary(envinit, state, alg) - envsconv, info = ctmrg_iter(state, envs, alg) - envsfix, signs = gauge_fix(envs, envsconv) + envsconv, info = ctmrg_iteration(state, envs, alg) + envs_fixed, signs = gauge_fix(envs, envsconv) # Fix SVD Ufix, Vfix = fix_relative_phases(info.U, info.V, signs) svd_alg_fixed = SVDAdjoint(; fwd_alg=FixedSVD(Ufix, info.S, Vfix), rrule_alg=alg.projector_alg.svd_alg.rrule_alg ) - alg_fixed = CTMRG(; - svd_alg=svd_alg_fixed, trscheme=notrunc(), flavor=:simultaneous - ) + alg_fixed = CTMRG(; svd_alg=svd_alg_fixed, trscheme=notrunc(), flavor=:simultaneous) function leading_boundary_fixed_pullback(Δenvs′) Δenvs = unthunk(Δenvs′) - _, env_vjp = pullback(state, envsfix) do A, x - e, = ctmrg_iter(A, x, alg_fixed) - return fix_global_phases(x, e) - end + f(A, x) = fix_global_phases(x, ctmrg_iteration(A, x, alg_fixed)[1]) + _, env_vjp = rrule_via_ad(config, f, state, envs_fixed) # evaluate the geometric sum - ∂f∂A(x)::typeof(state) = env_vjp(x)[1] - ∂f∂x(x)::typeof(envs) = env_vjp(x)[2] + ∂f∂A(x)::typeof(state) = env_vjp(x)[2] + ∂f∂x(x)::typeof(envs) = env_vjp(x)[3] ∂F∂envs = fpgrad(Δenvs, ∂f∂x, ∂f∂A, Δenvs, gradmode) return NoTangent(), ZeroTangent(), ∂F∂envs, NoTangent() end - return envsfix, leading_boundary_fixed_pullback + return envs_fixed, leading_boundary_fixed_pullback end @doc """ diff --git a/src/environments/ctmrg_environments.jl b/src/environments/ctmrg_environments.jl index 29a823cc..6819159e 100644 --- a/src/environments/ctmrg_environments.jl +++ b/src/environments/ctmrg_environments.jl @@ -354,7 +354,7 @@ function ChainRulesCore.rrule(::typeof(getproperty), e::CTMRGEnv, name::Symbol) throw(ArgumentError("No rrule for getproperty of $name")) end end -Base.size(env::CTMRG, args...) = size(env.corners, args...) +Base.size(env::CTMRGEnv, args...) = size(env.corners, args...) Base.eltype(env::CTMRGEnv) = eltype(env.corners[1]) Base.axes(x::CTMRGEnv, args...) = axes(x.corners, args...) function eachcoordinate(x::CTMRGEnv) diff --git a/test/ctmrg/fixed_iterscheme.jl b/test/ctmrg/fixed_iterscheme.jl index 3f005986..b5f9a291 100644 --- a/test/ctmrg/fixed_iterscheme.jl +++ b/test/ctmrg/fixed_iterscheme.jl @@ -5,7 +5,7 @@ using TensorKit, KrylovKit using PEPSKit using PEPSKit: FixedSVD, - ctmrg_iter, + ctmrg_iteration, gauge_fix, fix_relative_phases, fix_global_phases, @@ -30,7 +30,7 @@ unitcells = [(1, 1), (3, 4)] env_conv1 = leading_boundary(CTMRGEnv(psi, ComplexSpace(χenv)), psi, ctm_alg) # do extra iteration to get SVD - env_conv2, info = ctmrg_iter(psi, env_conv1, ctm_alg) + env_conv2, info = ctmrg_iteration(psi, env_conv1, ctm_alg) env_fix, signs = gauge_fix(env_conv1, env_conv2) @test calc_elementwise_convergence(env_conv1, env_fix) ≈ 0 atol = 1e-6 @@ -40,7 +40,7 @@ unitcells = [(1, 1), (3, 4)] ctm_alg_fix = CTMRG(; svd_alg=svd_alg_fix, trscheme=notrunc()) # do iteration with FixedSVD - env_fixedsvd, = ctmrg_iter(psi, env_conv1, ctm_alg_fix) + env_fixedsvd, = ctmrg_iteration(psi, env_conv1, ctm_alg_fix) env_fixedsvd = fix_global_phases(env_conv1, env_fixedsvd) @test calc_elementwise_convergence(env_conv1, env_fixedsvd) ≈ 0 atol = 1e-6 end @@ -59,11 +59,11 @@ end env_conv1 = leading_boundary(env_init, psi, ctm_alg_iter) # do extra iteration to get SVD - env_conv2_iter, info_iter = ctmrg_iter(psi, env_conv1, ctm_alg_iter) + env_conv2_iter, info_iter = ctmrg_iteration(psi, env_conv1, ctm_alg_iter) env_fix_iter, signs_iter = gauge_fix(env_conv1, env_conv2_iter) @test calc_elementwise_convergence(env_conv1, env_fix_iter) ≈ 0 atol = 1e-6 - env_conv2_full, info_full = ctmrg_iter(psi, env_conv1, ctm_alg_full) + env_conv2_full, info_full = ctmrg_iteration(psi, env_conv1, ctm_alg_full) env_fix_full, signs_full = gauge_fix(env_conv1, env_conv2_full) @test calc_elementwise_convergence(env_conv1, env_fix_full) ≈ 0 atol = 1e-6 @@ -77,11 +77,11 @@ end ctm_alg_fix_full = CTMRG(; svd_alg=svd_alg_fix_full, trscheme=notrunc()) # do iteration with FixedSVD - env_fixedsvd_iter, = ctmrg_iter(psi, env_conv1, ctm_alg_fix_iter) + env_fixedsvd_iter, = ctmrg_iteration(psi, env_conv1, ctm_alg_fix_iter) env_fixedsvd_iter = fix_global_phases(env_conv1, env_fixedsvd_iter) @test calc_elementwise_convergence(env_conv1, env_fixedsvd_iter) ≈ 0 atol = 1e-6 # This doesn't work for x₀ = rand(size(b, 1))? - env_fixedsvd_full, = ctmrg_iter(psi, env_conv1, ctm_alg_fix_full) + env_fixedsvd_full, = ctmrg_iteration(psi, env_conv1, ctm_alg_fix_full) env_fixedsvd_full = fix_global_phases(env_conv1, env_fixedsvd_full) @test calc_elementwise_convergence(env_conv1, env_fixedsvd_full) ≈ 0 atol = 1e-6 diff --git a/test/ctmrg/gaugefix.jl b/test/ctmrg/gaugefix.jl index 748c5b05..8757b231 100644 --- a/test/ctmrg/gaugefix.jl +++ b/test/ctmrg/gaugefix.jl @@ -3,7 +3,7 @@ using Random using PEPSKit using TensorKit -using PEPSKit: ctmrg_iter, gauge_fix, calc_elementwise_convergence +using PEPSKit: ctmrg_iteration, gauge_fix, calc_elementwise_convergence scalartypes = [Float64, ComplexF64] unitcells = [(1, 1), (2, 2), (3, 2)] @@ -52,7 +52,7 @@ end alg = CTMRG(; maxiter, flavor) ctm = leading_boundary(ctm, psi, alg) - ctm2, = ctmrg_iter(psi, ctm, alg) + ctm2, = ctmrg_iteration(psi, ctm, alg) ctm_fixed, = gauge_fix(ctm, ctm2) @test calc_elementwise_convergence(ctm, ctm_fixed) ≈ 0 atol = atol end @@ -76,7 +76,7 @@ end alg = CTMRG(; maxiter, flavor) ctm = leading_boundary(ctm, psi, alg) - ctm2, = ctmrg_iter(psi, ctm, alg) + ctm2, = ctmrg_iteration(psi, ctm, alg) ctm_fixed, = gauge_fix(ctm, ctm2) @test calc_elementwise_convergence(ctm, ctm_fixed) ≈ 0 atol = atol end diff --git a/test/ctmrg/gradparts.jl b/test/ctmrg/gradparts.jl index 4af4627a..795d52e0 100644 --- a/test/ctmrg/gradparts.jl +++ b/test/ctmrg/gradparts.jl @@ -13,7 +13,7 @@ using PEPSKit: SOUTHWEST, rotate_north, left_move, - ctmrg_iter + ctmrg_iteration using Zygote using ChainRulesCore @@ -53,15 +53,17 @@ end ## Tests # ------ -@testset "Reverse rules for ctmrg_iter with spacetype $(Vspaces[i])" for i in - eachindex(Pspaces) +@testset "Reverse rules for ctmrg_iteration with spacetype $(Vspaces[i])" for i in + eachindex( + Pspaces +) Random.seed!(42039482030) psi = InfinitePEPS(Pspaces[i], Vspaces[i], Vspaces[i]) env = CTMRGEnv(psi, Espaces[i]) @testset "$alg" for alg in boundary_algs @info "$(typeof(alg)) on $(Vspaces[i])" - f(state, env) = rho(ctmrg_iter(state, env, alg)[1]) + f(state, env) = rho(ctmrg_iteration(state, env, alg)[1]) # use rrule testing functionality but compute rrule via Zygote test_rrule( diff --git a/test/ctmrg/unitcell.jl b/test/ctmrg/unitcell.jl index 0ffbdc09..1ba2ea40 100644 --- a/test/ctmrg/unitcell.jl +++ b/test/ctmrg/unitcell.jl @@ -1,7 +1,7 @@ using Test using Random using PEPSKit -using PEPSKit: _prev, _next, ctmrg_iter +using PEPSKit: _prev, _next, ctmrg_iteration using TensorKit # settings @@ -16,7 +16,7 @@ function test_unitcell( env = CTMRGEnv(randn, stype, peps, chis_north, chis_east, chis_south, chis_west) # apply one CTMRG iteration with fixeds - env′, = ctmrg_iter(peps, env, ctm_alg) + env′, = ctmrg_iteration(peps, env, ctm_alg) # compute random expecation value to test matching bonds random_op = LocalOperator( From 85c2cf88859ae2ee84dbef2d923d0fbe706befda Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Fri, 6 Dec 2024 18:17:33 +0100 Subject: [PATCH 190/213] Make FullInfiniteProjector runnable --- .../contractions/ctmrg_contractions.jl | 18 ++++++++++--- src/algorithms/ctmrg/ctmrg.jl | 27 ++++++++++--------- src/algorithms/ctmrg/simultaneous.jl | 25 ++++++++++++----- src/utility/util.jl | 22 --------------- 4 files changed, 47 insertions(+), 45 deletions(-) diff --git a/src/algorithms/contractions/ctmrg_contractions.jl b/src/algorithms/contractions/ctmrg_contractions.jl index 69921868..8e784dab 100644 --- a/src/algorithms/contractions/ctmrg_contractions.jl +++ b/src/algorithms/contractions/ctmrg_contractions.jl @@ -217,9 +217,9 @@ square root `isqS` of the singular values is computed. Left projector: ``` - -- |~~~~~~~~| -- |~~| - | Q_next | |V'| -- isqS -- - == |~~~~~~~~| == |~~| + -- |~~~~~~| -- |~~| + |Q_next| |V'| -- isqS -- + == |~~~~~~| == |~~| ``` Right projector: @@ -235,10 +235,20 @@ function left_and_right_projector( isqS = sdiag_inv_sqrt(S) @autoopt @tensor P_left[χ_in D_inabove D_inbelow; χ_out] := Q_next[χ_in D_inabove D_inbelow; χ1 D1 D2] * conj(V[χ2; χ1 D1 D2]) * isqS[χ2; χ_out] - @tensor P_right[χ_in; χ_out D_outabove D_outbelow] := + @autoopt @tensor P_right[χ_in; χ_out D_outabove D_outbelow] := isqS[χ_in; χ1] * conj(U[χ2 D1 D2; χ1]) * Q[χ2 D1 D2; χ_out D_outabove D_outbelow] return P_left, P_right end +function left_and_right_projector( + U, S, V, R_left::AbstractTensorMap{E,1,3}, L_right::AbstractTensorMap{E,3,1} +) where {E<:ElementarySpace} + isqS = sdiag_inv_sqrt(S) + @tensor P_left[χ_in D_inabove D_inbelow; χ_out] := + L_right[χ_in D_inabove D_inbelow; full] * conj(V[χ2; full]) * isqS[χ2; χ_out] + @tensor P_right[χ_in; χ_out D_outabove D_outbelow] := + isqS[χ_in; χ1] * conj(U[full; χ1]) * R_left[full; χ_out D_outabove D_outbelow] + return P_left, P_right +end """ half_infinite_environment(quadrant1::AbstractTensorMap{S,3,3}, quadrant2::AbstractTensorMap{S,3,3}) diff --git a/src/algorithms/ctmrg/ctmrg.jl b/src/algorithms/ctmrg/ctmrg.jl index c9aecc1d..ad4648ec 100644 --- a/src/algorithms/ctmrg/ctmrg.jl +++ b/src/algorithms/ctmrg/ctmrg.jl @@ -89,6 +89,7 @@ function CTMRG(; miniter=Defaults.ctmrg_miniter, flavor=Defaults.ctmrg_flavor, verbosity=2, + projector_alg=Defaults.projector_alg, svd_alg=Defaults.svd_alg, trscheme=Defaults.trscheme, ) @@ -98,7 +99,7 @@ function CTMRG(; miniter, flavor, verbosity, - Defaults.projector_alg(; svd_alg, trscheme, verbosity), + projector_alg(; svd_alg, trscheme, verbosity), ) end @@ -185,17 +186,17 @@ function compute_projector(enlarged_corners, coordinate, alg::HalfInfiniteProjec return (P_left, P_right), (; err, U, S, V) end function compute_projector(enlarged_corners, coordinate, alg::FullInfiniteProjector) - # QR top and bottom half-infinite environments - halfinf_top = half_infinite_environment(enlarged_corners[1], enlarged_corners[2]) - halfinf_bot = half_infinite_environment(enlarged_corners[3], enlarged_corners[4]) - _, R_top = leftorth!(halfinf_top) - _, R_bot = leftorth!(halfinf_bot) - R_fullinf = R_top * R_bot - - # SVD product of R's - trscheme = truncation_scheme(projector_alg, space(enlarged_corners[4], 1)) - svd_alg = svd_algorithm(projector_alg, coordinate) - U, S, V, err = PEPSKit.tsvd!(R_fullinf, svd_alg; trunc=trscheme) + # QR left and right half-infinite environments (cut placement is consistent with half-infinite proj!) + halfinf_left = half_infinite_environment(enlarged_corners[4], enlarged_corners[1]) + halfinf_right = half_infinite_environment(enlarged_corners[2], enlarged_corners[3]) + _, R_left = leftorth!(halfinf_left) + L_right, _ = rightorth!(halfinf_right) + + # SVD product of QRs + fullinf = R_left * L_right + trscheme = truncation_scheme(alg, space(enlarged_corners[4], 1)) + svd_alg = svd_algorithm(alg, coordinate) + U, S, V, err = PEPSKit.tsvd!(fullinf, svd_alg; trunc=trscheme) # Compute SVD truncation error and check for degenerate singular values Zygote.isderiving() && ignore_derivatives() do @@ -205,6 +206,6 @@ function compute_projector(enlarged_corners, coordinate, alg::FullInfiniteProjec end end - P_left, P_right = left_and_right_projector(U, S, V, R_top, R_bot) + P_left, P_right = left_and_right_projector(U, S, V, R_left, L_right) return (P_left, P_right), (; err, U, S, V) end diff --git a/src/algorithms/ctmrg/simultaneous.jl b/src/algorithms/ctmrg/simultaneous.jl index f94bef38..e69bc51c 100644 --- a/src/algorithms/ctmrg/simultaneous.jl +++ b/src/algorithms/ctmrg/simultaneous.jl @@ -13,12 +13,25 @@ function simultaneous_ctmrg_iter(state, envs::CTMRGEnv, alg::CTMRG) return envs′, info end -function simultaneous_projectors( - enlarged_corners, envs::CTMRGEnv{C,E}, alg::ProjectorAlgs -) where {C,E} - # pre-allocation - U, V = Zygote.Buffer.(projector_type(envs.edges)) - S = Zygote.Buffer(U.data, tensormaptype(spacetype(C), 1, 1, real(scalartype(E)))) # Corner type but with real numbers +# Pre-allocate U, S, and V tensor as Zygote buffers to make it differentiable +function _prealloc_svd(edges::Array{E,N}, ::HalfInfiniteProjector) where {E,N} + Sc = scalartype(E) + U = Zygote.Buffer(map(e -> TensorMap(zeros, Sc, space(e)), edges)) + V = Zygote.Buffer(map(e -> TensorMap(zeros, Sc, domain(e), codomain(e)), edges)) + S = Zygote.Buffer(U.data, tensormaptype(spacetype(E), 1, 1, real(Sc))) # Corner type but with real numbers + return U, S, V +end +function _prealloc_svd(edges::Array{E,N}, ::FullInfiniteProjector) where {E,N} + Sc = scalartype(E) + Rspace(x) = spacetype(E)(dim(codomain(x))) + U = Zygote.Buffer(map(e -> TensorMap(zeros, Sc, Rspace(e), domain(e)), edges)) + V = Zygote.Buffer(map(e -> TensorMap(zeros, Sc, domain(e), Rspace(e)), edges)) + S = Zygote.Buffer(U.data, tensormaptype(spacetype(E), 1, 1, real(Sc))) # Corner type but with real numbers + return U, S, V +end + +function simultaneous_projectors(enlarged_corners, envs::CTMRGEnv, alg::ProjectorAlgs) + U, S, V = _prealloc_svd(envs.edges, alg) ϵ = zero(real(scalartype(envs))) projectors = dtmap(eachcoordinate(envs, 1:4)) do coordinate diff --git a/src/utility/util.jl b/src/utility/util.jl index 93cb9577..858f8e86 100644 --- a/src/utility/util.jl +++ b/src/utility/util.jl @@ -92,28 +92,6 @@ function is_degenerate_spectrum( return false end -""" - projector_type(T::DataType, size) - projector_type(edges::Array{<:AbstractTensorMap}) - -Create two arrays of specified `size` that contain undefined tensors representing -left and right acting projectors, respectively. The projector types are inferred -from the TensorMap type `T` which avoids having to recompute transpose tensors. -Alternatively, supply an array of edge tensors from which left and right projectors -are intialized explicitly with zeros. -""" -function projector_type(T::DataType, size) - P_left = Array{T,length(size)}(undef, size) - Prtype = tensormaptype(spacetype(T), numin(T), numout(T), storagetype(T)) - P_right = Array{Prtype,length(size)}(undef, size) - return P_left, P_right -end -function projector_type(edges::Array{<:AbstractTensorMap}) - P_left = map(e -> TensorMap(zeros, scalartype(e), space(e)), edges) - P_right = map(e -> TensorMap(zeros, scalartype(e), domain(e), codomain(e)), edges) - return P_left, P_right -end - # There are no rrules for rotl90 and rotr90 in ChainRules.jl function ChainRulesCore.rrule(::typeof(rotl90), a::AbstractMatrix) function rotl90_pullback(x) From 1d75b48adb889accb9531ff7de00415e0c0303d2 Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Fri, 6 Dec 2024 18:35:54 +0100 Subject: [PATCH 191/213] Add docstrings --- src/algorithms/ctmrg/sequential.jl | 26 +++++++++++++++++++------- src/algorithms/ctmrg/simultaneous.jl | 22 ++++++++++++++++++---- 2 files changed, 37 insertions(+), 11 deletions(-) diff --git a/src/algorithms/ctmrg/sequential.jl b/src/algorithms/ctmrg/sequential.jl index efebc594..0632b7ea 100644 --- a/src/algorithms/ctmrg/sequential.jl +++ b/src/algorithms/ctmrg/sequential.jl @@ -19,23 +19,30 @@ function sequential_ctmrg_iter(state, envs::CTMRGEnv, alg::CTMRG) return envs, (; err=ϵ) end +""" + sequential_projectors(col::Int, state::InfinitePEPS, envs::CTMRGEnv, alg::ProjectorAlgs) + sequential_projectors(coordinate, state::InfinitePEPS, envs::CTMRGEnv, alg::ProjectorAlgs) + +Compute CTMRG projectors in the `:sequential` scheme either for an entire column `col` or +for a specific `coordinate` (where `dir=WEST` is already implied in the `:sequential` scheme). +""" function sequential_projectors( - col::Int, state::InfinitePEPS, envs::CTMRGEnv{C,E}, alg::ProjectorAlgs -) where {C,E} + col::Int, state::InfinitePEPS, envs::CTMRGEnv, alg::ProjectorAlgs +) ϵ = zero(real(scalartype(envs))) # SVD half-infinite environment column-wise coordinates = eachcoordinate(envs)[:, col] projectors = dtmap(coordinates) do (r, c) - proj, info = sequential_projector(state, envs, (WEST, r, c), alg) + proj, info = sequential_projectors((WEST, r, c), state, envs, alg) ϵ = max(ϵ, info.err / norm(info.S)) return proj end return (map(first, projectors), map(last, projectors)), (; err=ϵ) end -function sequential_projector( - state::InfinitePEPS, envs::CTMRGEnv, coordinate, alg::HalfInfiniteProjector +function sequential_projectors( + coordinate, state::InfinitePEPS, envs::CTMRGEnv, alg::HalfInfiniteProjector ) _, r, c = coordinate r′ = _prev(r, size(envs, 2)) @@ -43,8 +50,8 @@ function sequential_projector( Q2 = TensorMap(EnlargedCorner(state, envs, (NORTHWEST, r′, c)), NORTHWEST) return compute_projector((Q1, Q2), coordinate, alg) end -function sequential_projector( - state::InfinitePEPS, envs::CTMRGEnv, coordinate, alg::FullInfiniteProjector +function sequential_projectors( + coordinate, state::InfinitePEPS, envs::CTMRGEnv, alg::FullInfiniteProjector ) _, r, c = coordinate r′ = _next(r, size(envs, 2)) @@ -56,6 +63,11 @@ function sequential_projector( return compute_projector((Q1, Q2, Q3, Q4), coordinate, alg) end +""" + renormalize_sequentially(col::Int, projectors, state, envs) + +Renormalize one column of the CTMRG environment. +""" function renormalize_sequentially(col::Int, projectors, state, envs) corners = Zygote.Buffer(envs.corners) edges = Zygote.Buffer(envs.edges) diff --git a/src/algorithms/ctmrg/simultaneous.jl b/src/algorithms/ctmrg/simultaneous.jl index e69bc51c..19828e4f 100644 --- a/src/algorithms/ctmrg/simultaneous.jl +++ b/src/algorithms/ctmrg/simultaneous.jl @@ -30,12 +30,21 @@ function _prealloc_svd(edges::Array{E,N}, ::FullInfiniteProjector) where {E,N} return U, S, V end -function simultaneous_projectors(enlarged_corners, envs::CTMRGEnv, alg::ProjectorAlgs) +""" + simultaneous_projectors(enlarged_corners::Array{E,3}, envs::CTMRGEnv, alg::ProjectorAlgs) + simultaneous_projectors(coordinate, enlarged_corners::Array{E,3}, alg::ProjectorAlgs) + +Compute CTMRG projectors in the `:simultaneous` scheme either for all provided +enlarged corners or on a specific `coordinate`. +""" +function simultaneous_projectors( + enlarged_corners::Array{E,3}, envs::CTMRGEnv, alg::ProjectorAlgs +) where {E} U, S, V = _prealloc_svd(envs.edges, alg) ϵ = zero(real(scalartype(envs))) projectors = dtmap(eachcoordinate(envs, 1:4)) do coordinate - proj, info = simultaneous_projector(enlarged_corners, coordinate, alg) + proj, info = simultaneous_projectors(enlarged_corners, coordinate, alg) U[coordinate...] = info.U S[coordinate...] = info.S V[coordinate...] = info.V @@ -47,14 +56,14 @@ function simultaneous_projectors(enlarged_corners, envs::CTMRGEnv, alg::Projecto P_right = map(last, projectors) return (P_left, P_right), (; err=ϵ, U=copy(U), S=copy(S), V=copy(V)) end -function simultaneous_projector( +function simultaneous_projectors( enlarged_corners::Array{E,3}, coordinate, alg::HalfInfiniteProjector ) where {E} coordinate′ = _next_coordinate(coordinate, size(enlarged_corners)[2:3]...) ec = (enlarged_corners[coordinate...], enlarged_corners[coordinate′...]) return compute_projector(ec, coordinate, alg) end -function simultaneous_projector( +function simultaneous_projectors( enlarged_corners::Array{E,3}, coordinate, alg::FullInfiniteProjector ) where {E} rowsize, colsize = size(enlarged_corners)[2:3] @@ -70,6 +79,11 @@ function simultaneous_projector( return compute_projector(ec, coordinate, alg) end +""" + renormalize_simultaneously(enlarged_corners, projectors, state, envs) + +Renormalize all enlarged corners and edges simultaneously. +""" function renormalize_simultaneously(enlarged_corners, projectors, state, envs) P_left, P_right = projectors coordinates = eachcoordinate(envs, 1:4) From 16a9ee1a829b0d98ae8bc9e09e4f9e2e57b9d28b Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Fri, 6 Dec 2024 20:36:30 +0100 Subject: [PATCH 192/213] Fix FullInfiniteProjector for :sequential, add tests, remove old gradparts test --- src/algorithms/ctmrg/ctmrg.jl | 11 ++-- src/algorithms/ctmrg/sequential.jl | 34 ++++++++---- src/algorithms/ctmrg/simultaneous.jl | 10 ++-- test/ctmrg/fixed_iterscheme.jl | 26 ++++----- test/ctmrg/flavors.jl | 6 ++- test/ctmrg/gaugefix.jl | 43 +++++++-------- test/ctmrg/gradients.jl | 5 +- test/ctmrg/gradparts.jl | 79 ---------------------------- 8 files changed, 81 insertions(+), 133 deletions(-) delete mode 100644 test/ctmrg/gradparts.jl diff --git a/src/algorithms/ctmrg/ctmrg.jl b/src/algorithms/ctmrg/ctmrg.jl index ad4648ec..2485be29 100644 --- a/src/algorithms/ctmrg/ctmrg.jl +++ b/src/algorithms/ctmrg/ctmrg.jl @@ -103,6 +103,11 @@ function CTMRG(; ) end +""" + ctmrg_iteration(state, env, alg::CTMRG) + +Perform a single CTMRG iteration in which all directions are being grown and renormalized. +""" function ctmrg_iteration(state, env, alg::CTMRG) if alg.flavor == :simultaneous return simultaneous_ctmrg_iter(state, env, alg) @@ -186,9 +191,9 @@ function compute_projector(enlarged_corners, coordinate, alg::HalfInfiniteProjec return (P_left, P_right), (; err, U, S, V) end function compute_projector(enlarged_corners, coordinate, alg::FullInfiniteProjector) - # QR left and right half-infinite environments (cut placement is consistent with half-infinite proj!) - halfinf_left = half_infinite_environment(enlarged_corners[4], enlarged_corners[1]) - halfinf_right = half_infinite_environment(enlarged_corners[2], enlarged_corners[3]) + # QR left and right half-infinite environments + halfinf_left = half_infinite_environment(enlarged_corners[1], enlarged_corners[2]) + halfinf_right = half_infinite_environment(enlarged_corners[3], enlarged_corners[4]) _, R_left = leftorth!(halfinf_left) L_right, _ = rightorth!(halfinf_right) diff --git a/src/algorithms/ctmrg/sequential.jl b/src/algorithms/ctmrg/sequential.jl index 0632b7ea..60c27a4d 100644 --- a/src/algorithms/ctmrg/sequential.jl +++ b/src/algorithms/ctmrg/sequential.jl @@ -21,7 +21,7 @@ end """ sequential_projectors(col::Int, state::InfinitePEPS, envs::CTMRGEnv, alg::ProjectorAlgs) - sequential_projectors(coordinate, state::InfinitePEPS, envs::CTMRGEnv, alg::ProjectorAlgs) + sequential_projectors(coordinate::NTuple{3,Int}, state::InfinitePEPS, envs::CTMRGEnv, alg::ProjectorAlgs) Compute CTMRG projectors in the `:sequential` scheme either for an entire column `col` or for a specific `coordinate` (where `dir=WEST` is already implied in the `:sequential` scheme). @@ -42,7 +42,10 @@ function sequential_projectors( return (map(first, projectors), map(last, projectors)), (; err=ϵ) end function sequential_projectors( - coordinate, state::InfinitePEPS, envs::CTMRGEnv, alg::HalfInfiniteProjector + coordinate::NTuple{3,Int}, + state::InfinitePEPS, + envs::CTMRGEnv, + alg::HalfInfiniteProjector, ) _, r, c = coordinate r′ = _prev(r, size(envs, 2)) @@ -51,16 +54,25 @@ function sequential_projectors( return compute_projector((Q1, Q2), coordinate, alg) end function sequential_projectors( - coordinate, state::InfinitePEPS, envs::CTMRGEnv, alg::FullInfiniteProjector + coordinate::NTuple{3,Int}, + state::InfinitePEPS, + envs::CTMRGEnv, + alg::FullInfiniteProjector, ) - _, r, c = coordinate - r′ = _next(r, size(envs, 2)) - c′ = _next(c, size(envs, 3)) - Q1 = TensorMap(EnlargedCorner(state, envs, (NORTHWEST, r, c)), NORTHWEST) - Q2 = TensorMap(EnlargedCorner(state, envs, (NORTHEAST, r, c′)), NORTHEAST) - Q3 = TensorMap(EnlargedCorner(state, envs, (SOUTHEAST, r′, c′)), SOUTHEAST) - Q4 = TensorMap(EnlargedCorner(state, envs, (SOUTHWEST, r′, c)), SOUTHWEST) - return compute_projector((Q1, Q2, Q3, Q4), coordinate, alg) + # _, r, c = coordinate + # r′ = _next(r, size(envs, 2)) + # c′ = _next(c, size(envs, 3)) + rowsize, colsize = size(envs)[2:3] + coordinate_nw = _next_coordinate(coordinate, rowsize, colsize) + coordinate_ne = _next_coordinate(coordinate_nw, rowsize, colsize) + coordinate_se = _next_coordinate(coordinate_ne, rowsize, colsize) + ec = ( + TensorMap(EnlargedCorner(state, envs, coordinate_se), SOUTHEAST), + TensorMap(EnlargedCorner(state, envs, coordinate), SOUTHWEST), + TensorMap(EnlargedCorner(state, envs, coordinate_nw), NORTHWEST), + TensorMap(EnlargedCorner(state, envs, coordinate_ne), NORTHEAST), + ) + return compute_projector(ec, coordinate, alg) end """ diff --git a/src/algorithms/ctmrg/simultaneous.jl b/src/algorithms/ctmrg/simultaneous.jl index 19828e4f..b6992fbb 100644 --- a/src/algorithms/ctmrg/simultaneous.jl +++ b/src/algorithms/ctmrg/simultaneous.jl @@ -23,7 +23,7 @@ function _prealloc_svd(edges::Array{E,N}, ::HalfInfiniteProjector) where {E,N} end function _prealloc_svd(edges::Array{E,N}, ::FullInfiniteProjector) where {E,N} Sc = scalartype(E) - Rspace(x) = spacetype(E)(dim(codomain(x))) + Rspace(x) = fuse(codomain(x)) U = Zygote.Buffer(map(e -> TensorMap(zeros, Sc, Rspace(e), domain(e)), edges)) V = Zygote.Buffer(map(e -> TensorMap(zeros, Sc, domain(e), Rspace(e)), edges)) S = Zygote.Buffer(U.data, tensormaptype(spacetype(E), 1, 1, real(Sc))) # Corner type but with real numbers @@ -44,7 +44,7 @@ function simultaneous_projectors( ϵ = zero(real(scalartype(envs))) projectors = dtmap(eachcoordinate(envs, 1:4)) do coordinate - proj, info = simultaneous_projectors(enlarged_corners, coordinate, alg) + proj, info = simultaneous_projectors(coordinate, enlarged_corners, alg) U[coordinate...] = info.U S[coordinate...] = info.S V[coordinate...] = info.V @@ -57,24 +57,24 @@ function simultaneous_projectors( return (P_left, P_right), (; err=ϵ, U=copy(U), S=copy(S), V=copy(V)) end function simultaneous_projectors( - enlarged_corners::Array{E,3}, coordinate, alg::HalfInfiniteProjector + coordinate, enlarged_corners::Array{E,3}, alg::HalfInfiniteProjector ) where {E} coordinate′ = _next_coordinate(coordinate, size(enlarged_corners)[2:3]...) ec = (enlarged_corners[coordinate...], enlarged_corners[coordinate′...]) return compute_projector(ec, coordinate, alg) end function simultaneous_projectors( - enlarged_corners::Array{E,3}, coordinate, alg::FullInfiniteProjector + coordinate, enlarged_corners::Array{E,3}, alg::FullInfiniteProjector ) where {E} rowsize, colsize = size(enlarged_corners)[2:3] coordinate2 = _next_coordinate(coordinate, rowsize, colsize) coordinate3 = _next_coordinate(coordinate2, rowsize, colsize) coordinate4 = _next_coordinate(coordinate3, rowsize, colsize) ec = ( + enlarged_corners[coordinate4...], enlarged_corners[coordinate...], enlarged_corners[coordinate2...], enlarged_corners[coordinate3...], - enlarged_corners[coordinate4...], ) return compute_projector(ec, coordinate, alg) end diff --git a/test/ctmrg/fixed_iterscheme.jl b/test/ctmrg/fixed_iterscheme.jl index b5f9a291..f13b04a6 100644 --- a/test/ctmrg/fixed_iterscheme.jl +++ b/test/ctmrg/fixed_iterscheme.jl @@ -15,14 +15,17 @@ using PEPSKit: χbond = 2 χenv = 16 svd_algs = [SVDAdjoint(; fwd_alg=TensorKit.SDD()), SVDAdjoint(; fwd_alg=IterSVD())] +projector_algs = [HalfInfiniteProjector] #, FullInfiniteProjector] unitcells = [(1, 1), (3, 4)] +atol = 1e-5 # test for element-wise convergence after application of fixed step -@testset "$unitcell unit cell with $(typeof(svd_alg.fwd_alg))" for (unitcell, svd_alg) in - Iterators.product( - unitcells, svd_algs +@testset "$unitcell unit cell with $(typeof(svd_alg.fwd_alg)) and $projector_alg" for ( + unitcell, svd_alg, projector_alg +) in Iterators.product( + unitcells, svd_algs, projector_algs ) - ctm_alg = CTMRG(; svd_alg) + ctm_alg = CTMRG(; svd_alg, projector_alg) # initialize states Random.seed!(2394823842) @@ -32,17 +35,17 @@ unitcells = [(1, 1), (3, 4)] # do extra iteration to get SVD env_conv2, info = ctmrg_iteration(psi, env_conv1, ctm_alg) env_fix, signs = gauge_fix(env_conv1, env_conv2) - @test calc_elementwise_convergence(env_conv1, env_fix) ≈ 0 atol = 1e-6 + @test calc_elementwise_convergence(env_conv1, env_fix) ≈ 0 atol = atol # fix gauge of SVD U_fix, V_fix = fix_relative_phases(info.U, info.V, signs) svd_alg_fix = SVDAdjoint(; fwd_alg=FixedSVD(U_fix, info.S, V_fix)) - ctm_alg_fix = CTMRG(; svd_alg=svd_alg_fix, trscheme=notrunc()) + ctm_alg_fix = CTMRG(; projector_alg, svd_alg=svd_alg_fix, trscheme=notrunc()) # do iteration with FixedSVD env_fixedsvd, = ctmrg_iteration(psi, env_conv1, ctm_alg_fix) env_fixedsvd = fix_global_phases(env_conv1, env_fixedsvd) - @test calc_elementwise_convergence(env_conv1, env_fixedsvd) ≈ 0 atol = 1e-6 + @test calc_elementwise_convergence(env_conv1, env_fixedsvd) ≈ 0 atol = atol end @testset "Element-wise consistency of TensorKit.SDD and IterSVD" begin @@ -61,11 +64,11 @@ end # do extra iteration to get SVD env_conv2_iter, info_iter = ctmrg_iteration(psi, env_conv1, ctm_alg_iter) env_fix_iter, signs_iter = gauge_fix(env_conv1, env_conv2_iter) - @test calc_elementwise_convergence(env_conv1, env_fix_iter) ≈ 0 atol = 1e-6 + @test calc_elementwise_convergence(env_conv1, env_fix_iter) ≈ 0 atol = atol env_conv2_full, info_full = ctmrg_iteration(psi, env_conv1, ctm_alg_full) env_fix_full, signs_full = gauge_fix(env_conv1, env_conv2_full) - @test calc_elementwise_convergence(env_conv1, env_fix_full) ≈ 0 atol = 1e-6 + @test calc_elementwise_convergence(env_conv1, env_fix_full) ≈ 0 atol = atol # fix gauge of SVD U_fix_iter, V_fix_iter = fix_relative_phases(info_iter.U, info_iter.V, signs_iter) @@ -79,14 +82,13 @@ end # do iteration with FixedSVD env_fixedsvd_iter, = ctmrg_iteration(psi, env_conv1, ctm_alg_fix_iter) env_fixedsvd_iter = fix_global_phases(env_conv1, env_fixedsvd_iter) - @test calc_elementwise_convergence(env_conv1, env_fixedsvd_iter) ≈ 0 atol = 1e-6 # This doesn't work for x₀ = rand(size(b, 1))? + @test calc_elementwise_convergence(env_conv1, env_fixedsvd_iter) ≈ 0 atol = atol # This doesn't work for x₀ = rand(size(b, 1))? env_fixedsvd_full, = ctmrg_iteration(psi, env_conv1, ctm_alg_fix_full) env_fixedsvd_full = fix_global_phases(env_conv1, env_fixedsvd_full) - @test calc_elementwise_convergence(env_conv1, env_fixedsvd_full) ≈ 0 atol = 1e-6 + @test calc_elementwise_convergence(env_conv1, env_fixedsvd_full) ≈ 0 atol = atol # check matching decompositions - atol = 1e-12 decomposition_check = all( zip(info_iter.U, info_iter.S, info_iter.V, info_full.U, info_full.S, info_full.V), ) do (U_iter, S_iter, V_iter, U_full, S_full, V_full) diff --git a/test/ctmrg/flavors.jl b/test/ctmrg/flavors.jl index c1c848a9..9337bc08 100644 --- a/test/ctmrg/flavors.jl +++ b/test/ctmrg/flavors.jl @@ -10,8 +10,12 @@ using PEPSKit ctm_alg_sequential = CTMRG(; flavor=:sequential) ctm_alg_simultaneous = CTMRG(; flavor=:simultaneous) unitcells = [(1, 1), (3, 4)] +projector_algs = [HalfInfiniteProjector, FullInfiniteProjector] -@testset "$(unitcell) unit cell" for unitcell in unitcells +@testset "$(unitcell) unit cell with $projector_alg" for (unitcell, projector_alg) in + Iterators.product( + unitcells, projector_algs +) # compute environments Random.seed!(32350283290358) psi = InfinitePEPS(2, χbond; unitcell) diff --git a/test/ctmrg/gaugefix.jl b/test/ctmrg/gaugefix.jl index 8757b231..040d72ed 100644 --- a/test/ctmrg/gaugefix.jl +++ b/test/ctmrg/gaugefix.jl @@ -7,8 +7,9 @@ using PEPSKit: ctmrg_iteration, gauge_fix, calc_elementwise_convergence scalartypes = [Float64, ComplexF64] unitcells = [(1, 1), (2, 2), (3, 2)] -maxiter = 200 +maxiter = 400 ctmrg_flavors = [:simultaneous, :sequential] +projector_algs = [HalfInfiniteProjector, FullInfiniteProjector] χ = 6 atol = 1e-4 @@ -34,9 +35,10 @@ function _semi_random_peps!(psi::InfinitePEPS) return InfinitePEPS(A′) end -@testset "Trivial symmetry ($T) - ($unitcell) - ($flavor)" for (T, unitcell, flavor) in - Iterators.product( - scalartypes, unitcells, ctmrg_flavors +@testset "Trivial symmetry ($T) - ($unitcell) - ($flavor) - ($projector_alg)" for ( + T, unitcell, flavor, projector_alg +) in Iterators.product( + scalartypes, unitcells, ctmrg_flavors, projector_algs ) physical_space = ComplexSpace(2) peps_space = ComplexSpace(2) @@ -47,19 +49,19 @@ end _make_symmetric!(psi) Random.seed!(987654321) # Seed RNG to make random environment consistent - ctm = CTMRGEnv(psi, ctm_space) + env = CTMRGEnv(psi, ctm_space) + alg = CTMRG(; maxiter, flavor, projector_alg) - alg = CTMRG(; maxiter, flavor) - - ctm = leading_boundary(ctm, psi, alg) - ctm2, = ctmrg_iteration(psi, ctm, alg) - ctm_fixed, = gauge_fix(ctm, ctm2) - @test calc_elementwise_convergence(ctm, ctm_fixed) ≈ 0 atol = atol + env = leading_boundary(env, psi, alg) + env′, = ctmrg_iteration(psi, env, alg) + env_fixed, = gauge_fix(env, env′) + @test calc_elementwise_convergence(env, env_fixed) ≈ 0 atol = atol end -@testset "Z2 symmetry ($T) - ($unitcell) - ($flavor)" for (T, unitcell, flavor) in - Iterators.product( - scalartypes, unitcells, ctrmg_flavors +@testset "Z2 symmetry ($T) - ($unitcell) - ($flavor) - ($projector_alg)" for ( + T, unitcell, flavor, projector_alg +) in Iterators.product( + scalartypes, unitcells, ctmrg_flavors, projector_algs ) physical_space = Z2Space(0 => 1, 1 => 1) peps_space = Z2Space(0 => 1, 1 => 1) @@ -71,12 +73,11 @@ end Random.seed!(987654321) # Seed RNG to make random environment consistent psi = InfinitePEPS(physical_space, peps_space; unitcell) - ctm = CTMRGEnv(psi, ctm_space) - - alg = CTMRG(; maxiter, flavor) + env = CTMRGEnv(psi, ctm_space) + alg = CTMRG(; maxiter, flavor, projector_alg) - ctm = leading_boundary(ctm, psi, alg) - ctm2, = ctmrg_iteration(psi, ctm, alg) - ctm_fixed, = gauge_fix(ctm, ctm2) - @test calc_elementwise_convergence(ctm, ctm_fixed) ≈ 0 atol = atol + env = leading_boundary(env, psi, alg) + env′, = ctmrg_iteration(psi, env, alg) + env_fixed, = gauge_fix(env, env′) + @test calc_elementwise_convergence(env, env_fixed) ≈ 0 atol = atol end diff --git a/test/ctmrg/gradients.jl b/test/ctmrg/gradients.jl index 46677810..a4ceafea 100644 --- a/test/ctmrg/gradients.jl +++ b/test/ctmrg/gradients.jl @@ -18,7 +18,10 @@ names = ["Heisenberg", "p-wave superconductor"] gradtol = 1e-4 boundary_algs = [ - CTMRG(; verbosity=0, flavor=:simultaneous), CTMRG(; verbosity=0, flavor=:sequential) + # CTMRG(; verbosity=0, flavor=:simultaneous, projector_alg=HalfInfiniteProjector), + CTMRG(; verbosity=0, flavor=:simultaneous, projector_alg=FullInfiniteProjector), + # CTMRG(; verbosity=0, flavor=:sequential, projector_alg=HalfInfiniteProjector), + CTMRG(; verbosity=0, flavor=:sequential, projector_alg=FullInfiniteProjector), ] gradmodes = [ [ diff --git a/test/ctmrg/gradparts.jl b/test/ctmrg/gradparts.jl deleted file mode 100644 index 795d52e0..00000000 --- a/test/ctmrg/gradparts.jl +++ /dev/null @@ -1,79 +0,0 @@ -using Test -using Random -using PEPSKit -using TensorKit -using PEPSKit: - NORTH, - SOUTH, - WEST, - EAST, - NORTHWEST, - NORTHEAST, - SOUTHEAST, - SOUTHWEST, - rotate_north, - left_move, - ctmrg_iteration - -using Zygote -using ChainRulesCore -using ChainRulesTestUtils - -include(joinpath("..", "utility.jl")) - -## Test spaces, tested functions and CTMRG algorithm -# -------------------------------------------------- -χbond = 2 -χenv = 4 -Pspaces = [ComplexSpace(2), Vect[FermionParity](0 => 1, 1 => 1)] -Vspaces = [ComplexSpace(χbond), Vect[FermionParity](0 => χbond / 2, 1 => χbond / 2)] -Espaces = [ComplexSpace(χenv), Vect[FermionParity](0 => χenv / 2, 1 => χenv / 2)] -tol = 1e-10 -atol = 1e-6 -verbosity = 0 -boundary_algs = [ - CTMRG(; tol, verbosity, flavor=:simultaneous), - CTMRG(; tol, verbosity, flavor=:sequential), -] - -## Gauge invariant function of the environment -# -------------------------------------------- -function rho(env) - @tensor ρ[-1 -2 -3 -4 -5 -6 -7 -8] := - env.edges[WEST, 1, 1][1 -1 -2; 4] * - env.corners[NORTHWEST, 1, 1][4; 5] * - env.edges[NORTH, 1, 1][5 -3 -4; 8] * - env.corners[NORTHEAST, 1, 1][8; 9] * - env.edges[EAST, 1, 1][9 -5 -6; 12] * - env.corners[SOUTHEAST, 1, 1][12; 13] * - env.edges[SOUTH, 1, 1][13 -7 -8; 16] * - env.corners[SOUTHWEST, 1, 1][16; 1] - return ρ -end - -## Tests -# ------ -@testset "Reverse rules for ctmrg_iteration with spacetype $(Vspaces[i])" for i in - eachindex( - Pspaces -) - Random.seed!(42039482030) - psi = InfinitePEPS(Pspaces[i], Vspaces[i], Vspaces[i]) - env = CTMRGEnv(psi, Espaces[i]) - - @testset "$alg" for alg in boundary_algs - @info "$(typeof(alg)) on $(Vspaces[i])" - f(state, env) = rho(ctmrg_iteration(state, env, alg)[1]) - - # use rrule testing functionality but compute rrule via Zygote - test_rrule( - Zygote.ZygoteRuleConfig(), - f, - psi, - env; - check_inferred=false, - atol, - rrule_f=rrule_via_ad, - ) - end -end From 231cf927c2c49466e15992a0a3a0476d9aa91410 Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Mon, 9 Dec 2024 17:36:30 +0100 Subject: [PATCH 193/213] Apply suggestions, update gaugefix test --- README.md | 2 +- docs/src/index.md | 2 +- examples/boundary_mps.jl | 4 +- examples/heisenberg.jl | 2 +- src/PEPSKit.jl | 30 ++- .../contractions/ctmrg_contractions.jl | 26 +-- src/algorithms/ctmrg/ctmrg.jl | 201 +++++------------- src/algorithms/ctmrg/gaugefix.jl | 34 --- src/algorithms/ctmrg/projectors.jl | 106 +++++++++ src/algorithms/ctmrg/sequential.jl | 47 +++- src/algorithms/ctmrg/simultaneous.jl | 46 +++- src/algorithms/peps_opt.jl | 19 +- test/boundarymps/vumps.jl | 8 +- test/ctmrg/fixed_iterscheme.jl | 12 +- test/ctmrg/flavors.jl | 11 +- test/ctmrg/gaugefix.jl | 46 +--- test/ctmrg/gradients.jl | 8 +- test/ctmrg/unitcell.jl | 2 +- test/heisenberg.jl | 2 +- test/j1j2_model.jl | 2 +- test/pwave.jl | 2 +- test/tf_ising.jl | 2 +- 22 files changed, 305 insertions(+), 309 deletions(-) create mode 100644 src/algorithms/ctmrg/projectors.jl diff --git a/README.md b/README.md index ebb55861..25f9a924 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ H = heisenberg_XYZ(InfiniteSquare(); Jx=-1, Jy=1, Jz=-1) # sublattice rotation t # configuring the parameters D = 2 chi = 20 -ctm_alg = CTMRG(; tol=1e-10, miniter=4, maxiter=100, verbosity=1, trscheme=truncdim(chi)) +ctm_alg = SimultaneousCTMRG(; tol=1e-10, trscheme=truncdim(chi)) opt_alg = PEPSOptimize(; boundary_alg=ctm_alg, optimizer=LBFGS(4; maxiter=100, gradtol=1e-4, verbosity=2), diff --git a/docs/src/index.md b/docs/src/index.md index 465b150f..709f4222 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -26,7 +26,7 @@ H = heisenberg_XYZ(InfiniteSquare(); Jx=-1, Jy=1, Jz=-1) # sublattice rotation t # configuring the parameters D = 2 chi = 20 -ctm_alg = CTMRG(; tol=1e-10, miniter=4, maxiter=100, verbosity=1, trscheme=truncdim(chi)) +ctm_alg = SimultaneousCTMRG(; tol=1e-10, trscheme=truncdim(chi)) opt_alg = PEPSOptimize(; boundary_alg=ctm_alg, optimizer=LBFGS(4; maxiter=100, gradtol=1e-4, verbosity=2), diff --git a/examples/boundary_mps.jl b/examples/boundary_mps.jl index eb699b21..4f00915e 100644 --- a/examples/boundary_mps.jl +++ b/examples/boundary_mps.jl @@ -32,7 +32,7 @@ mps, envs, ϵ = leading_boundary(mps, T, VUMPS()) N = abs(prod(expectation_value(mps, T))) # This can be compared to the result obtained using the CTMRG algorithm -ctm = leading_boundary(peps, CTMRG(; verbosity=1), CTMRGEnv(peps, ComplexSpace(20))) +ctm = leading_boundary(peps, SimultaneousCTMRG(; verbosity=1), CTMRGEnv(peps, ComplexSpace(20))) N´ = abs(norm(peps, ctm)) @show abs(N - N´) / N @@ -53,7 +53,7 @@ mps2 = PEPSKit.initializeMPS(T2, fill(ComplexSpace(20), 2, 2)) mps2, envs2, ϵ = leading_boundary(mps2, T2, VUMPS()) N2 = abs(prod(expectation_value(mps2, T2))) -ctm2 = leading_boundary(peps2, CTMRG(; verbosity=1), CTMRGEnv(peps2, ComplexSpace(20))) +ctm2 = leading_boundary(peps2, SimultaneousCTMRG(; verbosity=1), CTMRGEnv(peps2, ComplexSpace(20))) N2´ = abs(norm(peps2, ctm2)) @show abs(N2 - N2´) / N2 diff --git a/examples/heisenberg.jl b/examples/heisenberg.jl index 25b30c02..34baed18 100644 --- a/examples/heisenberg.jl +++ b/examples/heisenberg.jl @@ -11,7 +11,7 @@ H = heisenberg_XYZ(InfiniteSquare(); Jx=-1, Jy=1, Jz=-1) # Parameters χbond = 2 χenv = 20 -ctm_alg = CTMRG(; tol=1e-10, miniter=4, maxiter=100, verbosity=2) +ctm_alg = SimultaneousCTMRG(; tol=1e-10, verbosity=2) opt_alg = PEPSOptimize(; boundary_alg=ctm_alg, optimizer=LBFGS(4; maxiter=100, gradtol=1e-4, verbosity=2), diff --git a/src/PEPSKit.jl b/src/PEPSKit.jl index 0b2d11d4..334846d4 100644 --- a/src/PEPSKit.jl +++ b/src/PEPSKit.jl @@ -41,6 +41,7 @@ include("algorithms/contractions/ctmrg_contractions.jl") include("algorithms/ctmrg/sparse_environments.jl") include("algorithms/ctmrg/ctmrg.jl") +include("algorithms/ctmrg/projectors.jl") include("algorithms/ctmrg/simultaneous.jl") include("algorithms/ctmrg/sequential.jl") include("algorithms/ctmrg/gaugefix.jl") @@ -56,7 +57,6 @@ include("utility/symmetrization.jl") const ctmrg_maxiter = 100 const ctmrg_miniter = 4 const ctmrg_tol = 1e-8 - const ctmrg_flavor = :simultaneous const fpgrad_maxiter = 30 const fpgrad_tol = 1e-6 const reuse_env = true @@ -64,7 +64,11 @@ include("utility/symmetrization.jl") const fwd_alg = TensorKit.SDD() const rrule_alg = Arnoldi(; tol=1e-2fpgrad_tol, krylovdim=48, verbosity=-1) const svd_alg = SVDAdjoint(; fwd_alg, rrule_alg) - const projector_alg = HalfInfiniteProjector + const projector_alg_type = HalfInfiniteProjector + const projector_alg = projector_alg_type(svd_alg, trscheme, 2) + const ctmrg_alg = SimultaneousCTMRG( + ctmrg_tol, ctmrg_maxiter, ctmrg_miniter, 2, projector_alg + ) const optimizer = LBFGS(32; maxiter=100, gradtol=1e-4, verbosity=2) const gradient_linsolver = KrylovKit.BiCGStab(; maxiter=Defaults.fpgrad_maxiter, tol=Defaults.fpgrad_tol @@ -79,7 +83,6 @@ Module containing default values that represent typical algorithm parameters. - `ctmrg_maxiter`: Maximal number of CTMRG iterations per run - `ctmrg_miniter`: Minimal number of CTMRG carried out - `ctmrg_tol`: Tolerance checking singular value and norm convergence -- `ctmrg_flavor`: Scheme for growing, projecting and renormalizing CTMRG environments - `fpgrad_maxiter`: Maximal number of iterations for computing the CTMRG fixed-point gradient - `fpgrad_tol`: Convergence tolerance for the fixed-point gradient iteration - `reuse_env`: If `true`, the current optimization step is initialized on the previous environment @@ -87,7 +90,9 @@ Module containing default values that represent typical algorithm parameters. - `fwd_alg`: SVD algorithm that is used in the forward pass - `rrule_alg`: Reverse-rule for differentiating that SVD - `svd_alg`: Combination of `fwd_alg` and `rrule_alg` +- `projector_alg_type`: Default type of projector algorithm - `projector_alg`: Algorithm to compute CTMRG projectors +- `ctmrg_alg`: Algorithm for performing CTMRG runs - `optimizer`: Optimization algorithm for PEPS ground-state optimization - `gradient_linsolver`: Default linear solver for the `LinSolver` gradient algorithm - `iterscheme`: Scheme for differentiating one CTMRG iteration @@ -96,11 +101,15 @@ Module containing default values that represent typical algorithm parameters. """ module Defaults using TensorKit, KrylovKit, OptimKit, OhMyThreads - using PEPSKit: LinSolver, FixedSpaceTruncation, SVDAdjoint, HalfInfiniteProjector + using PEPSKit: + LinSolver, + FixedSpaceTruncation, + SVDAdjoint, + HalfInfiniteProjector, + SimultaneousCTMRG + const ctmrg_tol = 1e-8 const ctmrg_maxiter = 100 const ctmrg_miniter = 4 - const ctmrg_tol = 1e-8 - const ctmrg_flavor = :simultaneous const fpgrad_maxiter = 30 const fpgrad_tol = 1e-6 const sparse = false @@ -109,7 +118,11 @@ module Defaults const fwd_alg = TensorKit.SDD() const rrule_alg = Arnoldi(; tol=1e-2fpgrad_tol, krylovdim=48, verbosity=-1) const svd_alg = SVDAdjoint(; fwd_alg, rrule_alg) - const projector_alg = HalfInfiniteProjector + const projector_alg_type = HalfInfiniteProjector + const projector_alg = projector_alg_type(svd_alg, trscheme, 2) + const ctmrg_alg = SimultaneousCTMRG( + ctmrg_tol, ctmrg_maxiter, ctmrg_miniter, 2, projector_alg + ) const optimizer = LBFGS(32; maxiter=100, gradtol=1e-4, verbosity=2) const gradient_linsolver = KrylovKit.BiCGStab(; maxiter=Defaults.fpgrad_maxiter, tol=Defaults.fpgrad_tol @@ -165,7 +178,8 @@ end using .Defaults: set_scheduler! export set_scheduler! export SVDAdjoint, IterSVD, NonTruncSVDAdjoint -export FixedSpaceTruncation, HalfInfiniteProjector, FullInfiniteProjector, CTMRG, CTMRGEnv +export CTMRGEnv, SequentialCTMRG, SimultaneousCTMRG +export FixedSpaceTruncation, HalfInfiniteProjector, FullInfiniteProjector export LocalOperator export expectation_value, costfun, product_peps, correlation_length export leading_boundary diff --git a/src/algorithms/contractions/ctmrg_contractions.jl b/src/algorithms/contractions/ctmrg_contractions.jl index 8e784dab..227e5a7b 100644 --- a/src/algorithms/contractions/ctmrg_contractions.jl +++ b/src/algorithms/contractions/ctmrg_contractions.jl @@ -210,10 +210,10 @@ function right_projector(E_1, C, E_2, U, isqS, ket::PEPSTensor, bra::PEPSTensor= end """ - left_and_right_projector(U, S, V, Q::AbstractTensorMap{E,3,3}, Q_next::AbstractTensorMap{E,3,3} + contract_projectors(U, S, V, Q, Q_next) -Compute left and right projectors based on a SVD and quadrant tensors, where the inverse -square root `isqS` of the singular values is computed. +Compute projectors based on a SVD of `Q * Q_next`, where the inverse square root +`isqS` of the singular values is computed. Left projector: ``` @@ -229,24 +229,10 @@ Right projector: |~~| == |~~~| == ``` """ -function left_and_right_projector( - U, S, V, Q::AbstractTensorMap{E,3,3}, Q_next::AbstractTensorMap{E,3,3} -) where {E<:ElementarySpace} +function contract_projectors(U, S, V, Q, Q_next) isqS = sdiag_inv_sqrt(S) - @autoopt @tensor P_left[χ_in D_inabove D_inbelow; χ_out] := - Q_next[χ_in D_inabove D_inbelow; χ1 D1 D2] * conj(V[χ2; χ1 D1 D2]) * isqS[χ2; χ_out] - @autoopt @tensor P_right[χ_in; χ_out D_outabove D_outbelow] := - isqS[χ_in; χ1] * conj(U[χ2 D1 D2; χ1]) * Q[χ2 D1 D2; χ_out D_outabove D_outbelow] - return P_left, P_right -end -function left_and_right_projector( - U, S, V, R_left::AbstractTensorMap{E,1,3}, L_right::AbstractTensorMap{E,3,1} -) where {E<:ElementarySpace} - isqS = sdiag_inv_sqrt(S) - @tensor P_left[χ_in D_inabove D_inbelow; χ_out] := - L_right[χ_in D_inabove D_inbelow; full] * conj(V[χ2; full]) * isqS[χ2; χ_out] - @tensor P_right[χ_in; χ_out D_outabove D_outbelow] := - isqS[χ_in; χ1] * conj(U[full; χ1]) * R_left[full; χ_out D_outabove D_outbelow] + P_left = Q_next * V' * isqS # use * to respect fermionic case + P_right = isqS * U' * Q return P_left, P_right end diff --git a/src/algorithms/ctmrg/ctmrg.jl b/src/algorithms/ctmrg/ctmrg.jl index 2485be29..bfad86c0 100644 --- a/src/algorithms/ctmrg/ctmrg.jl +++ b/src/algorithms/ctmrg/ctmrg.jl @@ -1,131 +1,37 @@ -""" - FixedSpaceTruncation <: TensorKit.TruncationScheme -CTMRG specific truncation scheme for `tsvd` which keeps the bond space on which the SVD -is performed fixed. Since different environment directions and unit cell entries might -have different spaces, this truncation style is different from `TruncationSpace`. """ -struct FixedSpaceTruncation <: TensorKit.TruncationScheme end + CTMRGAlgorithm +Abstract super type for the corner transfer matrix renormalization group (CTMRG) algorithm +for contracting infinite PEPS. """ - struct HalfInfiniteProjector{S,T}(; svd_alg=Defaults.svd_alg, - trscheme=Defaults.trscheme, verbosity=0) +abstract type CTMRGAlgorithm end -Projector algorithm implementing projectors from SVDing the half-infinite CTMRG environment. """ -@kwdef struct HalfInfiniteProjector{S<:SVDAdjoint,T} - svd_alg::S = Defaults.svd_alg - trscheme::T = Defaults.trscheme - verbosity::Int = 0 -end + ctmrg_iteration(state, env, alg::CTMRG) -> env′, info +Perform a single CTMRG iteration in which all directions are being grown and renormalized. """ - struct FullInfiniteProjector{S,T}(; svd_alg=Defaults.svd_alg, - trscheme=Defaults.trscheme, verbosity=0) +function ctmrg_iteration(state, env, alg::CTMRGAlgorithm) end -Projector algorithm implementing projectors from SVDing the full 4x4 CTMRG environment. """ -@kwdef struct FullInfiniteProjector{S<:SVDAdjoint,T} - svd_alg::S = Defaults.svd_alg - trscheme::T = Defaults.trscheme - verbosity::Int = 0 -end + MPSKit.leading_boundary([envinit], state, alg::CTMRGAlgorithm) -# TODO: do AbstractProjectorAlg type instead? -> would make it easier for users to implement custom projector alg -const ProjectorAlgs = Union{HalfInfiniteProjector,FullInfiniteProjector} +Contract `state` using CTMRG and return the CTM environment. Per default, a random +initial environment is used. -function svd_algorithm(alg::ProjectorAlgs, (dir, r, c)) - if alg.svd_alg isa SVDAdjoint{<:FixedSVD} - fwd_alg = alg.svd_alg.fwd_alg - fix_svd = FixedSVD(fwd_alg.U[dir, r, c], fwd_alg.S[dir, r, c], fwd_alg.V[dir, r, c]) - return SVDAdjoint(; fwd_alg=fix_svd, rrule_alg=alg.svd_alg.rrule_alg) - else - return alg.svd_alg - end -end +Each CTMRG run is converged up to `alg.tol` where the singular value convergence +of the corners and edges is checked. The maximal and minimal number of CTMRG +iterations is set with `alg.maxiter` and `alg.miniter`. -function truncation_scheme(alg::ProjectorAlgs, Espace) - if alg.trscheme isa FixedSpaceTruncation - return truncspace(Espace) - else - return alg.trscheme - end -end - -""" - CTMRG(; tol=Defaults.ctmrg_tol, maxiter=Defaults.ctmrg_maxiter, - miniter=Defaults.ctmrg_miniter, flavor=Defaults.ctmrg_flavor, verbosity=0, - svd_alg=SVDAdjoint(), trscheme=FixedSpaceTruncation()) - -Algorithm struct that represents the CTMRG algorithm for contracting infinite PEPS. -Each CTMRG run is converged up to `tol` where the singular value convergence of the -corners as well as the norm is checked. The maximal and minimal number of CTMRG iterations -is set with `maxiter` and `miniter`. - -In general, two different flavors of CTMRG can be selected with `flavor` which determine how -CTMRG is implemented. It can either be `:sequential`, where the projectors are succesively -computed on the west side, and then applied and rotated. Or with `:simultaneous` all projectors -are computed and applied simultaneously on all sides, where the corners get contracted with -two projectors at the same time. - -Different levels of output information are printed depending on `verbosity`, where `0` +Different levels of output information are printed depending on `alg.verbosity`, where `0` suppresses all output, `1` only prints warnings, `2` gives information at the start and end, and `3` prints information every iteration. - -The projectors are computed from `svd_alg` SVDs where the truncation scheme is set via -`trscheme`. """ -struct CTMRG - tol::Float64 - maxiter::Int - miniter::Int - flavor::Symbol - verbosity::Int - projector_alg::ProjectorAlgs -end -function CTMRG(; - tol=Defaults.ctmrg_tol, - maxiter=Defaults.ctmrg_maxiter, - miniter=Defaults.ctmrg_miniter, - flavor=Defaults.ctmrg_flavor, - verbosity=2, - projector_alg=Defaults.projector_alg, - svd_alg=Defaults.svd_alg, - trscheme=Defaults.trscheme, -) - return CTMRG( - tol, - maxiter, - miniter, - flavor, - verbosity, - projector_alg(; svd_alg, trscheme, verbosity), - ) -end - -""" - ctmrg_iteration(state, env, alg::CTMRG) - -Perform a single CTMRG iteration in which all directions are being grown and renormalized. -""" -function ctmrg_iteration(state, env, alg::CTMRG) - if alg.flavor == :simultaneous - return simultaneous_ctmrg_iter(state, env, alg) - elseif alg.flavor == :sequential - return sequential_ctmrg_iter(state, env, alg) - end -end - -""" - MPSKit.leading_boundary([envinit], state, alg::CTMRG) - -Contract `state` using CTMRG and return the CTM environment. -Per default, a random initial environment is used. -""" -function MPSKit.leading_boundary(state, alg::CTMRG) +function MPSKit.leading_boundary(state, alg::CTMRGAlgorithm) return MPSKit.leading_boundary(CTMRGEnv(state, oneunit(spacetype(state))), state, alg) end -function MPSKit.leading_boundary(envinit, state, alg::CTMRG) +function MPSKit.leading_boundary(envinit, state, alg::CTMRGAlgorithm) CS = map(x -> tsvd(x)[2], envinit.corners) TS = map(x -> tsvd(x)[2], envinit.edges) @@ -166,51 +72,42 @@ ctmrg_logcancel!(log, iter, η, N) = @warnv 1 logcancel!(log, iter, η, N) @non_differentiable ctmrg_logfinish!(args...) @non_differentiable ctmrg_logcancel!(args...) +#= +In order to compute an error measure, we compare the singular values of the current iteration with the previous one. +However, when the virtual spaces change, this comparison is not directly possible. +Instead, we project both tensors into the smaller space and then compare the difference. + +TODO: we might want to consider embedding the smaller tensor into the larger space and then compute the difference +=# +function _singular_value_distance((S₁, S₂)) + V₁ = space(S₁, 1) + V₂ = space(S₂, 1) + if V₁ == V₂ + return norm(S₁ - S₂) + else + V = infimum(V₁, V₂) + e1 = isometry(V₁, V) + e2 = isometry(V₂, V) + return norm(e1' * S₁ * e1 - e2' * S₂ * e2) + end +end + """ - compute_projector(enlarged_corners, coordinate, alg::ProjectorAlgs) + calc_convergence(envs, CSold, TSold) -Determine left and right projectors at the bond given determined by the enlarged corners -and the given coordinate using the specified `alg`. +Given a new environment `envs` and the singular values of previous corners and edges +`CSold` and `TSold`, compute the maximal singular value distance. """ -function compute_projector(enlarged_corners, coordinate, alg::HalfInfiniteProjector) - # SVD half-infinite environment - halfinf = half_infinite_environment(enlarged_corners...) - trscheme = truncation_scheme(alg, space(enlarged_corners[2], 1)) - svd_alg = svd_algorithm(alg, coordinate) - U, S, V, err = PEPSKit.tsvd!(halfinf, svd_alg; trunc=trscheme) - - # Compute SVD truncation error and check for degenerate singular values - Zygote.isderiving() && ignore_derivatives() do - if alg.verbosity > 0 && is_degenerate_spectrum(S) - svals = TensorKit.SectorDict(c => diag(b) for (c, b) in blocks(S)) - @warn("degenerate singular values detected: ", svals) - end - end +function calc_convergence(envs, CSold, TSold) + CSnew = map(x -> tsvd(x)[2], envs.corners) + ΔCS = maximum(_singular_value_distance, zip(CSold, CSnew)) - P_left, P_right = left_and_right_projector(U, S, V, enlarged_corners...) - return (P_left, P_right), (; err, U, S, V) -end -function compute_projector(enlarged_corners, coordinate, alg::FullInfiniteProjector) - # QR left and right half-infinite environments - halfinf_left = half_infinite_environment(enlarged_corners[1], enlarged_corners[2]) - halfinf_right = half_infinite_environment(enlarged_corners[3], enlarged_corners[4]) - _, R_left = leftorth!(halfinf_left) - L_right, _ = rightorth!(halfinf_right) - - # SVD product of QRs - fullinf = R_left * L_right - trscheme = truncation_scheme(alg, space(enlarged_corners[4], 1)) - svd_alg = svd_algorithm(alg, coordinate) - U, S, V, err = PEPSKit.tsvd!(fullinf, svd_alg; trunc=trscheme) - - # Compute SVD truncation error and check for degenerate singular values - Zygote.isderiving() && ignore_derivatives() do - if alg.verbosity > 0 && is_degenerate_spectrum(S) - svals = TensorKit.SectorDict(c => diag(b) for (c, b) in blocks(S)) - @warn("degenerate singular values detected: ", svals) - end - end + TSnew = map(x -> tsvd(x)[2], envs.edges) + ΔTS = maximum(_singular_value_distance, zip(TSold, TSnew)) - P_left, P_right = left_and_right_projector(U, S, V, R_left, L_right) - return (P_left, P_right), (; err, U, S, V) + @debug "maxᵢ|Cⁿ⁺¹ - Cⁿ|ᵢ = $ΔCS maxᵢ|Tⁿ⁺¹ - Tⁿ|ᵢ = $ΔTS" + + return max(ΔCS, ΔTS), CSnew, TSnew end + +@non_differentiable calc_convergence(args...) diff --git a/src/algorithms/ctmrg/gaugefix.jl b/src/algorithms/ctmrg/gaugefix.jl index 5e63d5ef..bedc784d 100644 --- a/src/algorithms/ctmrg/gaugefix.jl +++ b/src/algorithms/ctmrg/gaugefix.jl @@ -141,40 +141,6 @@ function fix_global_phases(envprev::CTMRGEnv, envfix::CTMRGEnv) return CTMRGEnv(cornersgfix, edgesgfix) end -#= -In order to compute an error measure, we compare the singular values of the current iteration with the previous one. -However, when the virtual spaces change, this comparison is not directly possible. -Instead, we project both tensors into the smaller space and then compare the difference. - -TODO: we might want to consider embedding the smaller tensor into the larger space and then compute the difference -=# -function _singular_value_distance((S₁, S₂)) - V₁ = space(S₁, 1) - V₂ = space(S₂, 1) - if V₁ == V₂ - return norm(S₁ - S₂) - else - V = infimum(V₁, V₂) - e1 = isometry(V₁, V) - e2 = isometry(V₂, V) - return norm(e1' * S₁ * e1 - e2' * S₂ * e2) - end -end - -function calc_convergence(envs, CSold, TSold) - CSnew = map(x -> tsvd(x)[2], envs.corners) - ΔCS = maximum(_singular_value_distance, zip(CSold, CSnew)) - - TSnew = map(x -> tsvd(x)[2], envs.edges) - ΔTS = maximum(_singular_value_distance, zip(TSold, TSnew)) - - @debug "maxᵢ|Cⁿ⁺¹ - Cⁿ|ᵢ = $ΔCS maxᵢ|Tⁿ⁺¹ - Tⁿ|ᵢ = $ΔTS" - - return max(ΔCS, ΔTS), CSnew, TSnew -end - -@non_differentiable calc_convergence(args...) - """ calc_elementwise_convergence(envfinal, envfix; atol=1e-6) diff --git a/src/algorithms/ctmrg/projectors.jl b/src/algorithms/ctmrg/projectors.jl new file mode 100644 index 00000000..cf098609 --- /dev/null +++ b/src/algorithms/ctmrg/projectors.jl @@ -0,0 +1,106 @@ +""" + FixedSpaceTruncation <: TensorKit.TruncationScheme + +CTMRG specific truncation scheme for `tsvd` which keeps the bond space on which the SVD +is performed fixed. Since different environment directions and unit cell entries might +have different spaces, this truncation style is different from `TruncationSpace`. +""" +struct FixedSpaceTruncation <: TensorKit.TruncationScheme end + +""" + ProjectorAlgorithm + +Abstract super type for all CTMRG projector algorithms. +""" +abstract type ProjectorAlgorithm end + +function svd_algorithm(alg::ProjectorAlgorithm, (dir, r, c)) + if alg.svd_alg isa SVDAdjoint{<:FixedSVD} + fwd_alg = alg.svd_alg.fwd_alg + fix_svd = FixedSVD(fwd_alg.U[dir, r, c], fwd_alg.S[dir, r, c], fwd_alg.V[dir, r, c]) + return SVDAdjoint(; fwd_alg=fix_svd, rrule_alg=alg.svd_alg.rrule_alg) + else + return alg.svd_alg + end +end + +function truncation_scheme(alg::ProjectorAlgorithm, Espace) + if alg.trscheme isa FixedSpaceTruncation + return truncspace(Espace) + else + return alg.trscheme + end +end + +""" + struct HalfInfiniteProjector{S,T}(; svd_alg=Defaults.svd_alg, + trscheme=Defaults.trscheme, verbosity=0) + +Projector algorithm implementing projectors from SVDing the half-infinite CTMRG environment. +""" +@kwdef struct HalfInfiniteProjector{S<:SVDAdjoint,T} <: ProjectorAlgorithm + svd_alg::S = Defaults.svd_alg + trscheme::T = Defaults.trscheme + verbosity::Int = 0 +end + +""" + struct FullInfiniteProjector{S,T}(; svd_alg=Defaults.svd_alg, + trscheme=Defaults.trscheme, verbosity=0) + +Projector algorithm implementing projectors from SVDing the full 4x4 CTMRG environment. +""" +@kwdef struct FullInfiniteProjector{S<:SVDAdjoint,T} <: ProjectorAlgorithm + svd_alg::S = Defaults.svd_alg + trscheme::T = Defaults.trscheme + verbosity::Int = 0 +end + +""" + compute_projector(enlarged_corners, coordinate, alg::ProjectorAlgorithm) + +Determine left and right projectors at the bond given determined by the enlarged corners +and the given coordinate using the specified `alg`. +""" +function compute_projector(enlarged_corners, coordinate, alg::HalfInfiniteProjector) + # SVD half-infinite environment + halfinf = half_infinite_environment(enlarged_corners...) + trscheme = truncation_scheme(alg, space(enlarged_corners[2], 1)) + svd_alg = svd_algorithm(alg, coordinate) + U, S, V, err = PEPSKit.tsvd!(halfinf, svd_alg; trunc=trscheme) + + # Compute SVD truncation error and check for degenerate singular values + Zygote.isderiving() && ignore_derivatives() do + if alg.verbosity > 0 && is_degenerate_spectrum(S) + svals = TensorKit.SectorDict(c => diag(b) for (c, b) in blocks(S)) + @warn("degenerate singular values detected: ", svals) + end + end + + P_left, P_right = contract_projectors(U, S, V, enlarged_corners...) + return (P_left, P_right), (; err, U, S, V) +end +function compute_projector(enlarged_corners, coordinate, alg::FullInfiniteProjector) + # QR left and right half-infinite environments + halfinf_left = half_infinite_environment(enlarged_corners[1], enlarged_corners[2]) + halfinf_right = half_infinite_environment(enlarged_corners[3], enlarged_corners[4]) + _, R_left = leftorth!(halfinf_left) + L_right, _ = rightorth!(halfinf_right) + + # SVD product of QRs + fullinf = R_left * L_right + trscheme = truncation_scheme(alg, space(enlarged_corners[4], 1)) + svd_alg = svd_algorithm(alg, coordinate) + U, S, V, err = PEPSKit.tsvd!(fullinf, svd_alg; trunc=trscheme) + + # Compute SVD truncation error and check for degenerate singular values + Zygote.isderiving() && ignore_derivatives() do + if alg.verbosity > 0 && is_degenerate_spectrum(S) + svals = TensorKit.SectorDict(c => diag(b) for (c, b) in blocks(S)) + @warn("degenerate singular values detected: ", svals) + end + end + + P_left, P_right = contract_projectors(U, S, V, R_left, L_right) + return (P_left, P_right), (; err, U, S, V) +end \ No newline at end of file diff --git a/src/algorithms/ctmrg/sequential.jl b/src/algorithms/ctmrg/sequential.jl index 60c27a4d..bcb147e6 100644 --- a/src/algorithms/ctmrg/sequential.jl +++ b/src/algorithms/ctmrg/sequential.jl @@ -1,10 +1,36 @@ """ - sequential_ctmrg_iter(state, envs::CTMRGEnv, alg::CTMRG) -> envs′, info + SequentialCTMRG(; tol=Defaults.ctmrg_tol, maxiter=Defaults.ctmrg_maxiter, + miniter=Defaults.ctmrg_miniter, verbosity=0, + projector_alg=typeof(Defaults.projector_alg), + svd_alg=SVDAdjoint(), trscheme=FixedSpaceTruncation()) -Perform one sequential iteration of CTMRG, where one iteration consists of four expansion, -renormalization and rotation steps that are performed sequentially. +CTMRG algorithm where the expansions and renormalization is performed sequentially +column-wise. This is implemented as a growing and projecting step to the left, followed by +a clockwise rotation (performed four times). The projectors are computed using +`projector_alg` from `svd_alg` SVDs where the truncation scheme is set via `trscheme`. """ -function sequential_ctmrg_iter(state, envs::CTMRGEnv, alg::CTMRG) +struct SequentialCTMRG <: CTMRGAlgorithm + tol::Float64 + maxiter::Int + miniter::Int + verbosity::Int + projector_alg::ProjectorAlgorithm +end +function SequentialCTMRG(; + tol=Defaults.ctmrg_tol, + maxiter=Defaults.ctmrg_maxiter, + miniter=Defaults.ctmrg_miniter, + verbosity=2, + projector_alg=Defaults.projector_alg_type, + svd_alg=Defaults.svd_alg, + trscheme=Defaults.trscheme, +) + return SequentialCTMRG( + tol, maxiter, miniter, verbosity, projector_alg(; svd_alg, trscheme, verbosity) + ) +end + +function ctmrg_iteration(state, envs::CTMRGEnv, alg::SequentialCTMRG) ϵ = zero(real(scalartype(state))) for _ in 1:4 # rotate for col in 1:size(state, 2) # left move column-wise @@ -20,26 +46,25 @@ function sequential_ctmrg_iter(state, envs::CTMRGEnv, alg::CTMRG) end """ - sequential_projectors(col::Int, state::InfinitePEPS, envs::CTMRGEnv, alg::ProjectorAlgs) - sequential_projectors(coordinate::NTuple{3,Int}, state::InfinitePEPS, envs::CTMRGEnv, alg::ProjectorAlgs) + sequential_projectors(col::Int, state::InfinitePEPS, envs::CTMRGEnv, alg::ProjectorAlgorithm) + sequential_projectors(coordinate::NTuple{3,Int}, state::InfinitePEPS, envs::CTMRGEnv, alg::ProjectorAlgorithm) Compute CTMRG projectors in the `:sequential` scheme either for an entire column `col` or for a specific `coordinate` (where `dir=WEST` is already implied in the `:sequential` scheme). """ function sequential_projectors( - col::Int, state::InfinitePEPS, envs::CTMRGEnv, alg::ProjectorAlgs + col::Int, state::InfinitePEPS, envs::CTMRGEnv, alg::ProjectorAlgorithm ) - ϵ = zero(real(scalartype(envs))) - # SVD half-infinite environment column-wise + ϵ = Zygote.Buffer(zeros(real(scalartype(envs)), size(envs, 2))) coordinates = eachcoordinate(envs)[:, col] projectors = dtmap(coordinates) do (r, c) proj, info = sequential_projectors((WEST, r, c), state, envs, alg) - ϵ = max(ϵ, info.err / norm(info.S)) + ϵ[c] = max(ϵ, info.err / norm(info.S)) return proj end - return (map(first, projectors), map(last, projectors)), (; err=ϵ) + return (map(first, projectors), map(last, projectors)), (; err=maximum(copy(ϵ))) end function sequential_projectors( coordinate::NTuple{3,Int}, diff --git a/src/algorithms/ctmrg/simultaneous.jl b/src/algorithms/ctmrg/simultaneous.jl index b6992fbb..75d1e72f 100644 --- a/src/algorithms/ctmrg/simultaneous.jl +++ b/src/algorithms/ctmrg/simultaneous.jl @@ -1,10 +1,36 @@ """ - simultaneous_ctmrg_iter(state, envs::CTMRGEnv, alg::CTMRG) -> envs′, info + SimultaneousCTMRG(; tol=Defaults.ctmrg_tol, maxiter=Defaults.ctmrg_maxiter, + miniter=Defaults.ctmrg_miniter, verbosity=0, + projector_alg=Defaults.projector_alg, + svd_alg=SVDAdjoint(), trscheme=FixedSpaceTruncation()) -Perform one simultaneous iteration of CTMRG, in which the environment is expanded and -renormalized in all directions at the same time. +CTMRG algorithm where all sides are grown and renormalized at the same time. In particular, +the projectors are applied to the corners from two sides simultaneously. The projectors are +computed using `projector_alg` from `svd_alg` SVDs where the truncation scheme is set via +`trscheme`. """ -function simultaneous_ctmrg_iter(state, envs::CTMRGEnv, alg::CTMRG) +struct SimultaneousCTMRG <: CTMRGAlgorithm + tol::Float64 + maxiter::Int + miniter::Int + verbosity::Int + projector_alg::ProjectorAlgorithm +end +function SimultaneousCTMRG(; + tol=Defaults.ctmrg_tol, + maxiter=Defaults.ctmrg_maxiter, + miniter=Defaults.ctmrg_miniter, + verbosity=2, + projector_alg=Defaults.projector_alg_type, + svd_alg=Defaults.svd_alg, + trscheme=Defaults.trscheme, +) + return SimultaneousCTMRG( + tol, maxiter, miniter, verbosity, projector_alg(; svd_alg, trscheme, verbosity) + ) +end + +function ctmrg_iteration(state, envs::CTMRGEnv, alg::SimultaneousCTMRG) enlarged_corners = dtmap(eachcoordinate(state, 1:4)) do idx return TensorMap(EnlargedCorner(state, envs, idx), idx[1]) end # expand environment @@ -31,30 +57,30 @@ function _prealloc_svd(edges::Array{E,N}, ::FullInfiniteProjector) where {E,N} end """ - simultaneous_projectors(enlarged_corners::Array{E,3}, envs::CTMRGEnv, alg::ProjectorAlgs) - simultaneous_projectors(coordinate, enlarged_corners::Array{E,3}, alg::ProjectorAlgs) + simultaneous_projectors(enlarged_corners::Array{E,3}, envs::CTMRGEnv, alg::ProjectorAlgorithm) + simultaneous_projectors(coordinate, enlarged_corners::Array{E,3}, alg::ProjectorAlgorithm) Compute CTMRG projectors in the `:simultaneous` scheme either for all provided enlarged corners or on a specific `coordinate`. """ function simultaneous_projectors( - enlarged_corners::Array{E,3}, envs::CTMRGEnv, alg::ProjectorAlgs + enlarged_corners::Array{E,3}, envs::CTMRGEnv, alg::ProjectorAlgorithm ) where {E} U, S, V = _prealloc_svd(envs.edges, alg) - ϵ = zero(real(scalartype(envs))) + ϵ = Zygote.Buffer(zeros(real(scalartype(envs)), size(envs))) projectors = dtmap(eachcoordinate(envs, 1:4)) do coordinate proj, info = simultaneous_projectors(coordinate, enlarged_corners, alg) U[coordinate...] = info.U S[coordinate...] = info.S V[coordinate...] = info.V - ϵ = max(ϵ, info.err / norm(info.S)) + ϵ[coordinate...] = info.err / norm(info.S) return proj end P_left = map(first, projectors) P_right = map(last, projectors) - return (P_left, P_right), (; err=ϵ, U=copy(U), S=copy(S), V=copy(V)) + return (P_left, P_right), (; err=maximum(copy(ϵ)), U=copy(U), S=copy(S), V=copy(V)) end function simultaneous_projectors( coordinate, enlarged_corners::Array{E,3}, alg::HalfInfiniteProjector diff --git a/src/algorithms/peps_opt.jl b/src/algorithms/peps_opt.jl index 91e5adc8..83ffb7ac 100644 --- a/src/algorithms/peps_opt.jl +++ b/src/algorithms/peps_opt.jl @@ -77,8 +77,8 @@ function LinSolver(; end """ - PEPSOptimize{G}(; boundary_alg=CTMRG(), optimizer::OptimKit.OptimizationAlgorithm=Defaults.optimizer - reuse_env::Bool=true, gradient_alg::G=LinSolver()) + PEPSOptimize{G}(; boundary_alg=Defaults.ctmrg_alg, optimizer::OptimKit.OptimizationAlgorithm=Defaults.optimizer + reuse_env::Bool=true, gradient_alg::G=Defaults.gradient_alg) Algorithm struct that represent PEPS ground-state optimization using AD. Set the algorithm to contract the infinite PEPS in `boundary_alg`; @@ -89,19 +89,19 @@ step by setting `reuse_env` to true. Otherwise a random environment is used at e step. The CTMRG gradient itself is computed using the `gradient_alg` algorithm. """ struct PEPSOptimize{G} - boundary_alg::CTMRG + boundary_alg::CTMRGAlgorithm optimizer::OptimKit.OptimizationAlgorithm reuse_env::Bool gradient_alg::G function PEPSOptimize( # Inner constructor to prohibit illegal setting combinations - boundary_alg::CTMRG, + boundary_alg::CTMRGAlgorithm, optimizer, reuse_env, gradient_alg::G, ) where {G} if gradient_alg isa GradMode - if boundary_alg.flavor === :sequential && iterscheme(gradient_alg) === :fixed + if boundary_alg isa SequentialCTMRG && iterscheme(gradient_alg) === :fixed throw(ArgumentError(":sequential and :fixed are not compatible")) end end @@ -109,7 +109,7 @@ struct PEPSOptimize{G} end end function PEPSOptimize(; - boundary_alg=CTMRG(), + boundary_alg=Defaults.ctmrg_alg, optimizer=Defaults.optimizer, reuse_env=Defaults.reuse_env, gradient_alg=Defaults.gradient_alg, @@ -220,7 +220,7 @@ function _rrule( ::typeof(MPSKit.leading_boundary), envinit, state, - alg::CTMRG, + alg::CTMRGAlgorithm, ) envs = leading_boundary(envinit, state, alg) @@ -249,9 +249,8 @@ function _rrule( ::typeof(MPSKit.leading_boundary), envinit, state, - alg::CTMRG, + alg::SimultaneousCTMRG, ) - @assert alg.flavor === :simultaneous @assert !isnothing(alg.projector_alg.svd_alg.rrule_alg) envs = leading_boundary(envinit, state, alg) envsconv, info = ctmrg_iteration(state, envs, alg) @@ -262,7 +261,7 @@ function _rrule( svd_alg_fixed = SVDAdjoint(; fwd_alg=FixedSVD(Ufix, info.S, Vfix), rrule_alg=alg.projector_alg.svd_alg.rrule_alg ) - alg_fixed = CTMRG(; svd_alg=svd_alg_fixed, trscheme=notrunc(), flavor=:simultaneous) + alg_fixed = SimultaneousCTMRG(; svd_alg=svd_alg_fixed, trscheme=notrunc()) function leading_boundary_fixed_pullback(Δenvs′) Δenvs = unthunk(Δenvs′) diff --git a/test/boundarymps/vumps.jl b/test/boundarymps/vumps.jl index a161c073..2ec6265c 100644 --- a/test/boundarymps/vumps.jl +++ b/test/boundarymps/vumps.jl @@ -17,7 +17,9 @@ const vumps_alg = VUMPS(; alg_eigsolve=MPSKit.Defaults.alg_eigsolve(; ishermitia mps, envs, ϵ = leading_boundary(mps, T, vumps_alg) N = abs(sum(expectation_value(mps, T))) - ctm = leading_boundary(CTMRGEnv(psi, ComplexSpace(20)), psi, CTMRG(; verbosity=1)) + ctm = leading_boundary( + CTMRGEnv(psi, ComplexSpace(20)), psi, SimultaneousCTMRG(; verbosity=1) + ) N´ = abs(norm(psi, ctm)) @test N ≈ N´ atol = 1e-3 @@ -31,7 +33,9 @@ end mps, envs, ϵ = leading_boundary(mps, T, vumps_alg) N = abs(prod(expectation_value(mps, T))) - ctm = leading_boundary(CTMRGEnv(psi, ComplexSpace(20)), psi, CTMRG(; verbosity=1)) + ctm = leading_boundary( + CTMRGEnv(psi, ComplexSpace(20)), psi, SimultaneousCTMRG(; verbosity=1) + ) N´ = abs(norm(psi, ctm)) @test N ≈ N´ rtol = 1e-2 diff --git a/test/ctmrg/fixed_iterscheme.jl b/test/ctmrg/fixed_iterscheme.jl index f13b04a6..c0062abf 100644 --- a/test/ctmrg/fixed_iterscheme.jl +++ b/test/ctmrg/fixed_iterscheme.jl @@ -25,7 +25,7 @@ atol = 1e-5 ) in Iterators.product( unitcells, svd_algs, projector_algs ) - ctm_alg = CTMRG(; svd_alg, projector_alg) + ctm_alg = SimultaneousCTMRG(; svd_alg, projector_alg) # initialize states Random.seed!(2394823842) @@ -40,7 +40,7 @@ atol = 1e-5 # fix gauge of SVD U_fix, V_fix = fix_relative_phases(info.U, info.V, signs) svd_alg_fix = SVDAdjoint(; fwd_alg=FixedSVD(U_fix, info.S, V_fix)) - ctm_alg_fix = CTMRG(; projector_alg, svd_alg=svd_alg_fix, trscheme=notrunc()) + ctm_alg_fix = SimultaneousCTMRG(; projector_alg, svd_alg=svd_alg_fix, trscheme=notrunc()) # do iteration with FixedSVD env_fixedsvd, = ctmrg_iteration(psi, env_conv1, ctm_alg_fix) @@ -49,11 +49,11 @@ atol = 1e-5 end @testset "Element-wise consistency of TensorKit.SDD and IterSVD" begin - ctm_alg_iter = CTMRG(; + ctm_alg_iter = SimultaneousCTMRG(; maxiter=200, svd_alg=SVDAdjoint(; fwd_alg=IterSVD(; alg=GKL(; tol=1e-14, krylovdim=χenv + 10))), ) - ctm_alg_full = CTMRG(; svd_alg=SVDAdjoint(; fwd_alg=TensorKit.SDD())) + ctm_alg_full = SimultaneousCTMRG(; svd_alg=SVDAdjoint(; fwd_alg=TensorKit.SDD())) # initialize states Random.seed!(91283219347) @@ -73,11 +73,11 @@ end # fix gauge of SVD U_fix_iter, V_fix_iter = fix_relative_phases(info_iter.U, info_iter.V, signs_iter) svd_alg_fix_iter = SVDAdjoint(; fwd_alg=FixedSVD(U_fix_iter, info_iter.S, V_fix_iter)) - ctm_alg_fix_iter = CTMRG(; svd_alg=svd_alg_fix_iter, trscheme=notrunc()) + ctm_alg_fix_iter = SimultaneousCTMRG(; svd_alg=svd_alg_fix_iter, trscheme=notrunc()) U_fix_full, V_fix_full = fix_relative_phases(info_full.U, info_full.V, signs_full) svd_alg_fix_full = SVDAdjoint(; fwd_alg=FixedSVD(U_fix_full, info_full.S, V_fix_full)) - ctm_alg_fix_full = CTMRG(; svd_alg=svd_alg_fix_full, trscheme=notrunc()) + ctm_alg_fix_full = SimultaneousCTMRG(; svd_alg=svd_alg_fix_full, trscheme=notrunc()) # do iteration with FixedSVD env_fixedsvd_iter, = ctmrg_iteration(psi, env_conv1, ctm_alg_fix_iter) diff --git a/test/ctmrg/flavors.jl b/test/ctmrg/flavors.jl index 9337bc08..3dd26156 100644 --- a/test/ctmrg/flavors.jl +++ b/test/ctmrg/flavors.jl @@ -7,8 +7,8 @@ using PEPSKit # initialize parameters χbond = 2 χenv = 16 -ctm_alg_sequential = CTMRG(; flavor=:sequential) -ctm_alg_simultaneous = CTMRG(; flavor=:simultaneous) +ctm_alg_sequential = SequentialCTMRG() +ctm_alg_simultaneous = SimultaneousCTMRG() unitcells = [(1, 1), (3, 4)] projector_algs = [HalfInfiniteProjector, FullInfiniteProjector] @@ -56,10 +56,9 @@ projector_algs = [HalfInfiniteProjector, FullInfiniteProjector] end # test fixedspace actually fixes space -@testset "Fixedspace truncation ($flavor)" for flavor in [:sequential, :simultaneous] - ctm_alg = CTMRG(; - tol=1e-6, maxiter=1, verbosity=0, flavor, trscheme=FixedSpaceTruncation() - ) +@testset "Fixedspace truncation ($ctmrg_alg)" for ctmrg_alg in + [SequentialCTMRG, SimultanenousCTMRG] + ctm_alg = ctmrg_alg(; tol=1e-6, maxiter=1, verbosity=0, trscheme=FixedSpaceTruncation()) Ds = fill(2, 3, 3) χs = [16 17 18; 15 20 21; 14 19 22] psi = InfinitePEPS(Ds, Ds, Ds) diff --git a/test/ctmrg/gaugefix.jl b/test/ctmrg/gaugefix.jl index 040d72ed..bfb39fef 100644 --- a/test/ctmrg/gaugefix.jl +++ b/test/ctmrg/gaugefix.jl @@ -8,35 +8,13 @@ using PEPSKit: ctmrg_iteration, gauge_fix, calc_elementwise_convergence scalartypes = [Float64, ComplexF64] unitcells = [(1, 1), (2, 2), (3, 2)] maxiter = 400 -ctmrg_flavors = [:simultaneous, :sequential] +ctmrg_flavors = [SequentialCTMRG, SimultaneousCTMRG] projector_algs = [HalfInfiniteProjector, FullInfiniteProjector] χ = 6 atol = 1e-4 -function _make_symmetric!(psi) - if ==(size(psi)...) - return symmetrize!(psi, RotateReflect()) - else - return symmetrize!(symmetrize!(psi, ReflectDepth()), ReflectWidth()) - end -end - -# If I can't make the rng seed behave, I'll just randomly define a peps somehow -function _semi_random_peps!(psi::InfinitePEPS) - i = 0 - A′ = map(psi.A) do a - for (_, b) in blocks(a) - l = length(b) - b .= reshape(collect((1:l) .+ i), size(b)) - i += l - end - return a - end - return InfinitePEPS(A′) -end - -@testset "Trivial symmetry ($T) - ($unitcell) - ($flavor) - ($projector_alg)" for ( - T, unitcell, flavor, projector_alg +@testset "Trivial symmetry ($T) - ($unitcell) - ($ctmrg_alg) - ($projector_alg)" for ( + T, unitcell, ctmrg_alg, projector_alg ) in Iterators.product( scalartypes, unitcells, ctmrg_flavors, projector_algs ) @@ -44,13 +22,11 @@ end peps_space = ComplexSpace(2) ctm_space = ComplexSpace(χ) - psi = InfinitePEPS(undef, T, physical_space, peps_space; unitcell) - _semi_random_peps!(psi) - _make_symmetric!(psi) + psi = InfinitePEPS(randn, T, physical_space, peps_space; unitcell) Random.seed!(987654321) # Seed RNG to make random environment consistent env = CTMRGEnv(psi, ctm_space) - alg = CTMRG(; maxiter, flavor, projector_alg) + alg = ctmrg_alg(; maxiter, projector_alg) env = leading_boundary(env, psi, alg) env′, = ctmrg_iteration(psi, env, alg) @@ -58,23 +34,21 @@ end @test calc_elementwise_convergence(env, env_fixed) ≈ 0 atol = atol end -@testset "Z2 symmetry ($T) - ($unitcell) - ($flavor) - ($projector_alg)" for ( - T, unitcell, flavor, projector_alg +@testset "Z2 symmetry ($T) - ($unitcell) - ($ctmrg_alg) - ($projector_alg)" for ( + T, unitcell, ctmrg_alg, projector_alg ) in Iterators.product( - scalartypes, unitcells, ctmrg_flavors, projector_algs + scalartypes, unitcells, ctmrg_s, projector_algs ) physical_space = Z2Space(0 => 1, 1 => 1) peps_space = Z2Space(0 => 1, 1 => 1) ctm_space = Z2Space(0 => χ ÷ 2, 1 => χ ÷ 2) - psi = InfinitePEPS(undef, T, physical_space, peps_space; unitcell) - _semi_random_peps!(psi) - _make_symmetric!(psi) + psi = InfinitePEPS(randn, T, physical_space, peps_space; unitcell) Random.seed!(987654321) # Seed RNG to make random environment consistent psi = InfinitePEPS(physical_space, peps_space; unitcell) env = CTMRGEnv(psi, ctm_space) - alg = CTMRG(; maxiter, flavor, projector_alg) + alg = ctmrg_alg(; maxiter, projector_alg) env = leading_boundary(env, psi, alg) env′, = ctmrg_iteration(psi, env, alg) diff --git a/test/ctmrg/gradients.jl b/test/ctmrg/gradients.jl index a4ceafea..93c278ce 100644 --- a/test/ctmrg/gradients.jl +++ b/test/ctmrg/gradients.jl @@ -18,10 +18,10 @@ names = ["Heisenberg", "p-wave superconductor"] gradtol = 1e-4 boundary_algs = [ - # CTMRG(; verbosity=0, flavor=:simultaneous, projector_alg=HalfInfiniteProjector), - CTMRG(; verbosity=0, flavor=:simultaneous, projector_alg=FullInfiniteProjector), - # CTMRG(; verbosity=0, flavor=:sequential, projector_alg=HalfInfiniteProjector), - CTMRG(; verbosity=0, flavor=:sequential, projector_alg=FullInfiniteProjector), + SequentialCTMRG(; verbosity=0, projector_alg=HalfInfiniteProjector), + SequentialCTMRG(; verbosity=0, projector_alg=FullInfiniteProjector), + SimultaneousCTMRG(; verbosity=0, projector_alg=HalfInfiniteProjector), + SimultaneousCTMRG(; verbosity=0, projector_alg=FullInfiniteProjector), ] gradmodes = [ [ diff --git a/test/ctmrg/unitcell.jl b/test/ctmrg/unitcell.jl index 1ba2ea40..9601539b 100644 --- a/test/ctmrg/unitcell.jl +++ b/test/ctmrg/unitcell.jl @@ -7,7 +7,7 @@ using TensorKit # settings Random.seed!(91283219347) stype = ComplexF64 -ctm_alg = CTMRG() +ctm_alg = SimultaneousCTMRG() function test_unitcell( unitcell, Pspaces, Nspaces, Espaces, chis_north, chis_east, chis_south, chis_west diff --git a/test/heisenberg.jl b/test/heisenberg.jl index 1c05b084..d985503a 100644 --- a/test/heisenberg.jl +++ b/test/heisenberg.jl @@ -8,7 +8,7 @@ using OptimKit # initialize parameters χbond = 2 χenv = 16 -ctm_alg = CTMRG() +ctm_alg = SimultaneousCTMRG() opt_alg = PEPSOptimize(; boundary_alg=ctm_alg, optimizer=LBFGS(4; gradtol=1e-3, verbosity=2) ) diff --git a/test/j1j2_model.jl b/test/j1j2_model.jl index 45acd5b1..8dd88397 100644 --- a/test/j1j2_model.jl +++ b/test/j1j2_model.jl @@ -8,7 +8,7 @@ using OptimKit # initialize parameters χbond = 2 χenv = 12 -ctm_alg = CTMRG() +ctm_alg = SimultaneousCTMRG() opt_alg = PEPSOptimize(; boundary_alg=ctm_alg, optimizer=LBFGS(4; gradtol=1e-3, verbosity=2), diff --git a/test/pwave.jl b/test/pwave.jl index d663ec0d..dcdecf7e 100644 --- a/test/pwave.jl +++ b/test/pwave.jl @@ -10,7 +10,7 @@ unitcell = (2, 2) H = pwave_superconductor(InfiniteSquare(unitcell...)) χbond = 2 χenv = 16 -ctm_alg = CTMRG(; maxiter=150) +ctm_alg = SimultaneousCTMRG(; maxiter=150) opt_alg = PEPSOptimize(; boundary_alg=ctm_alg, optimizer=LBFGS(4; maxiter=10, gradtol=1e-3, verbosity=2) ) diff --git a/test/tf_ising.jl b/test/tf_ising.jl index 0b4d72f5..8b84696f 100644 --- a/test/tf_ising.jl +++ b/test/tf_ising.jl @@ -19,7 +19,7 @@ mˣ = 0.91 # initialize parameters χbond = 2 χenv = 16 -ctm_alg = CTMRG() +ctm_alg = SimultaneousCTMRG() opt_alg = PEPSOptimize(; boundary_alg=ctm_alg, optimizer=LBFGS(4; gradtol=1e-3, verbosity=2) ) From 48e41c3a03031efb1fa4b746d66db4d1489af125 Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Mon, 9 Dec 2024 19:27:57 +0100 Subject: [PATCH 194/213] Fix FixedSpaceTruncation --- src/PEPSKit.jl | 2 +- src/algorithms/ctmrg/projectors.jl | 10 +++---- src/algorithms/ctmrg/sequential.jl | 10 +++---- src/algorithms/ctmrg/simultaneous.jl | 6 +++- test/ctmrg/flavors.jl | 15 ++++++++-- test/ctmrg/gaugefix.jl | 6 ++-- test/ctmrg/unitcell.jl | 45 +++++++++++++++++++++++----- test/runtests.jl | 2 +- 8 files changed, 68 insertions(+), 28 deletions(-) diff --git a/src/PEPSKit.jl b/src/PEPSKit.jl index 334846d4..4fbf6760 100644 --- a/src/PEPSKit.jl +++ b/src/PEPSKit.jl @@ -3,7 +3,7 @@ module PEPSKit using LinearAlgebra, Statistics, Base.Threads, Base.Iterators, Printf using Base: @kwdef using Compat -using Accessors +using Accessors: @set using VectorInterface using TensorKit, KrylovKit, MPSKit, OptimKit, TensorOperations using ChainRulesCore, Zygote diff --git a/src/algorithms/ctmrg/projectors.jl b/src/algorithms/ctmrg/projectors.jl index cf098609..a02a896c 100644 --- a/src/algorithms/ctmrg/projectors.jl +++ b/src/algorithms/ctmrg/projectors.jl @@ -24,9 +24,9 @@ function svd_algorithm(alg::ProjectorAlgorithm, (dir, r, c)) end end -function truncation_scheme(alg::ProjectorAlgorithm, Espace) +function truncation_scheme(alg::ProjectorAlgorithm, edge) if alg.trscheme isa FixedSpaceTruncation - return truncspace(Espace) + return truncspace(space(edge, 1)) else return alg.trscheme end @@ -65,9 +65,8 @@ and the given coordinate using the specified `alg`. function compute_projector(enlarged_corners, coordinate, alg::HalfInfiniteProjector) # SVD half-infinite environment halfinf = half_infinite_environment(enlarged_corners...) - trscheme = truncation_scheme(alg, space(enlarged_corners[2], 1)) svd_alg = svd_algorithm(alg, coordinate) - U, S, V, err = PEPSKit.tsvd!(halfinf, svd_alg; trunc=trscheme) + U, S, V, err = PEPSKit.tsvd!(halfinf, svd_alg; trunc=alg.trscheme) # Compute SVD truncation error and check for degenerate singular values Zygote.isderiving() && ignore_derivatives() do @@ -89,9 +88,8 @@ function compute_projector(enlarged_corners, coordinate, alg::FullInfiniteProjec # SVD product of QRs fullinf = R_left * L_right - trscheme = truncation_scheme(alg, space(enlarged_corners[4], 1)) svd_alg = svd_algorithm(alg, coordinate) - U, S, V, err = PEPSKit.tsvd!(fullinf, svd_alg; trunc=trscheme) + U, S, V, err = PEPSKit.tsvd!(fullinf, svd_alg; trunc=alg.trscheme) # Compute SVD truncation error and check for degenerate singular values Zygote.isderiving() && ignore_derivatives() do diff --git a/src/algorithms/ctmrg/sequential.jl b/src/algorithms/ctmrg/sequential.jl index bcb147e6..94f4846e 100644 --- a/src/algorithms/ctmrg/sequential.jl +++ b/src/algorithms/ctmrg/sequential.jl @@ -59,8 +59,11 @@ function sequential_projectors( ϵ = Zygote.Buffer(zeros(real(scalartype(envs)), size(envs, 2))) coordinates = eachcoordinate(envs)[:, col] projectors = dtmap(coordinates) do (r, c) - proj, info = sequential_projectors((WEST, r, c), state, envs, alg) - ϵ[c] = max(ϵ, info.err / norm(info.S)) + trscheme = truncation_scheme(alg, envs.edges[WEST, _prev(r, size(envs, 2)), c]) + proj, info = sequential_projectors( + (WEST, r, c), state, envs, @set(alg.trscheme = trscheme) + ) + ϵ[r] = info.err / norm(info.S) return proj end @@ -84,9 +87,6 @@ function sequential_projectors( envs::CTMRGEnv, alg::FullInfiniteProjector, ) - # _, r, c = coordinate - # r′ = _next(r, size(envs, 2)) - # c′ = _next(c, size(envs, 3)) rowsize, colsize = size(envs)[2:3] coordinate_nw = _next_coordinate(coordinate, rowsize, colsize) coordinate_ne = _next_coordinate(coordinate_nw, rowsize, colsize) diff --git a/src/algorithms/ctmrg/simultaneous.jl b/src/algorithms/ctmrg/simultaneous.jl index 75d1e72f..fcae797b 100644 --- a/src/algorithms/ctmrg/simultaneous.jl +++ b/src/algorithms/ctmrg/simultaneous.jl @@ -70,7 +70,11 @@ function simultaneous_projectors( ϵ = Zygote.Buffer(zeros(real(scalartype(envs)), size(envs))) projectors = dtmap(eachcoordinate(envs, 1:4)) do coordinate - proj, info = simultaneous_projectors(coordinate, enlarged_corners, alg) + coordinate′ = _next_coordinate(coordinate, size(envs)[2:3]...) + trscheme = truncation_scheme(alg, envs.edges[coordinate[1], coordinate′[2:3]...]) + proj, info = simultaneous_projectors( + coordinate, enlarged_corners, @set(alg.trscheme = trscheme) + ) U[coordinate...] = info.U S[coordinate...] = info.S V[coordinate...] = info.V diff --git a/test/ctmrg/flavors.jl b/test/ctmrg/flavors.jl index 3dd26156..dc489e9d 100644 --- a/test/ctmrg/flavors.jl +++ b/test/ctmrg/flavors.jl @@ -56,9 +56,18 @@ projector_algs = [HalfInfiniteProjector, FullInfiniteProjector] end # test fixedspace actually fixes space -@testset "Fixedspace truncation ($ctmrg_alg)" for ctmrg_alg in - [SequentialCTMRG, SimultanenousCTMRG] - ctm_alg = ctmrg_alg(; tol=1e-6, maxiter=1, verbosity=0, trscheme=FixedSpaceTruncation()) +@testset "Fixedspace truncation using $ctmrg_alg and $projector_alg" for ( + ctmrg_alg, projector_alg +) in Iterators.product( + [SequentialCTMRG, SimultaneousCTMRG], projector_algs +) + ctm_alg = ctmrg_alg(; + tol=1e-6, + maxiter=1, + verbosity=0, + trscheme=FixedSpaceTruncation(), + projector_alg, + ) Ds = fill(2, 3, 3) χs = [16 17 18; 15 20 21; 14 19 22] psi = InfinitePEPS(Ds, Ds, Ds) diff --git a/test/ctmrg/gaugefix.jl b/test/ctmrg/gaugefix.jl index bfb39fef..8ffed116 100644 --- a/test/ctmrg/gaugefix.jl +++ b/test/ctmrg/gaugefix.jl @@ -8,7 +8,7 @@ using PEPSKit: ctmrg_iteration, gauge_fix, calc_elementwise_convergence scalartypes = [Float64, ComplexF64] unitcells = [(1, 1), (2, 2), (3, 2)] maxiter = 400 -ctmrg_flavors = [SequentialCTMRG, SimultaneousCTMRG] +ctmrg_algs = [SequentialCTMRG, SimultaneousCTMRG] projector_algs = [HalfInfiniteProjector, FullInfiniteProjector] χ = 6 atol = 1e-4 @@ -16,7 +16,7 @@ atol = 1e-4 @testset "Trivial symmetry ($T) - ($unitcell) - ($ctmrg_alg) - ($projector_alg)" for ( T, unitcell, ctmrg_alg, projector_alg ) in Iterators.product( - scalartypes, unitcells, ctmrg_flavors, projector_algs + scalartypes, unitcells, ctmrg_algs, projector_algs ) physical_space = ComplexSpace(2) peps_space = ComplexSpace(2) @@ -37,7 +37,7 @@ end @testset "Z2 symmetry ($T) - ($unitcell) - ($ctmrg_alg) - ($projector_alg)" for ( T, unitcell, ctmrg_alg, projector_alg ) in Iterators.product( - scalartypes, unitcells, ctmrg_s, projector_algs + scalartypes, unitcells, ctmrg_algs, projector_algs ) physical_space = Z2Space(0 => 1, 1 => 1) peps_space = Z2Space(0 => 1, 1 => 1) diff --git a/test/ctmrg/unitcell.jl b/test/ctmrg/unitcell.jl index 9601539b..e2f010fa 100644 --- a/test/ctmrg/unitcell.jl +++ b/test/ctmrg/unitcell.jl @@ -7,10 +7,23 @@ using TensorKit # settings Random.seed!(91283219347) stype = ComplexF64 -ctm_alg = SimultaneousCTMRG() +ctm_algs = [ + SequentialCTMRG(; projector_alg=HalfInfiniteProjector), + SequentialCTMRG(; projector_alg=FullInfiniteProjector), + SimultaneousCTMRG(; projector_alg=HalfInfiniteProjector), + SimultaneousCTMRG(; projector_alg=FullInfiniteProjector), +] function test_unitcell( - unitcell, Pspaces, Nspaces, Espaces, chis_north, chis_east, chis_south, chis_west + ctm_alg, + unitcell, + Pspaces, + Nspaces, + Espaces, + chis_north, + chis_east, + chis_south, + chis_west, ) peps = InfinitePEPS(randn, stype, Pspaces, Nspaces, Espaces) env = CTMRGEnv(randn, stype, peps, chis_north, chis_east, chis_south, chis_west) @@ -40,7 +53,7 @@ function random_dualize!(M::AbstractMatrix{<:ElementarySpace}) return M end -@testset "Integer space specifiers" begin +@testset "Integer space specifiers with $ctm_alg" begin unitcell = (3, 3) Pspaces = rand(2:3, unitcell...) @@ -52,11 +65,19 @@ end chis_west = rand(5:10, unitcell...) test_unitcell( - unitcell, Pspaces, Nspaces, Espaces, chis_north, chis_east, chis_south, chis_west + ctm_alg, + unitcell, + Pspaces, + Nspaces, + Espaces, + chis_north, + chis_east, + chis_south, + chis_west, ) end -@testset "Random Cartesian spaces" begin +@testset "Random Cartesian spaces with $ctm_alg" for ctm_alg in ctm_algs unitcell = (3, 3) Pspaces = random_dualize!(ComplexSpace.(rand(2:3, unitcell...))) @@ -68,11 +89,19 @@ end chis_west = random_dualize!(ComplexSpace.(rand(5:10, unitcell...))) test_unitcell( - unitcell, Pspaces, Nspaces, Espaces, chis_north, chis_east, chis_south, chis_west + ctm_alg, + unitcell, + Pspaces, + Nspaces, + Espaces, + chis_north, + chis_east, + chis_south, + chis_west, ) end -@testset "Specific U1 spaces" begin +@testset "Specific U1 spaces with $ctm_alg" for ctm_alg in ctm_algs unitcell = (2, 2) PA = U1Space(-1 => 1, 0 => 1) @@ -84,5 +113,5 @@ end Nspaces = [Vpeps Vpeps'; Vpeps' Vpeps] chis = [Venv Venv; Venv Venv] - test_unitcell(unitcell, Pspaces, Nspaces, Nspaces, chis, chis, chis, chis) + test_unitcell(ctm_alg, unitcell, Pspaces, Nspaces, Nspaces, chis, chis, chis, chis) end diff --git a/test/runtests.jl b/test/runtests.jl index 49d2fc0c..318a3f41 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -26,7 +26,7 @@ end @time @safetestset "Unit cells" begin include("ctmrg/unitcell.jl") end - @time @safetestset "CTMRG schemes" begin + @time @safetestset "Flavors" begin include("ctmrg/flavors.jl") end end From ea998b1f9b69b1b53cd13738e3e16653b7a9944a Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Mon, 9 Dec 2024 20:11:03 +0100 Subject: [PATCH 195/213] Format, fix gauge test, further steps towards FullInf differentiability --- examples/boundary_mps.jl | 8 ++++++-- src/algorithms/ctmrg/projectors.jl | 2 +- src/algorithms/peps_opt.jl | 3 ++- test/ctmrg/fixed_iterscheme.jl | 4 +++- test/ctmrg/flavors.jl | 6 +----- test/ctmrg/gaugefix.jl | 4 ++-- test/ctmrg/gradients.jl | 29 +++++++++++++++++------------ test/heisenberg.jl | 2 +- 8 files changed, 33 insertions(+), 25 deletions(-) diff --git a/examples/boundary_mps.jl b/examples/boundary_mps.jl index 4f00915e..51af6dea 100644 --- a/examples/boundary_mps.jl +++ b/examples/boundary_mps.jl @@ -32,7 +32,9 @@ mps, envs, ϵ = leading_boundary(mps, T, VUMPS()) N = abs(prod(expectation_value(mps, T))) # This can be compared to the result obtained using the CTMRG algorithm -ctm = leading_boundary(peps, SimultaneousCTMRG(; verbosity=1), CTMRGEnv(peps, ComplexSpace(20))) +ctm = leading_boundary( + peps, SimultaneousCTMRG(; verbosity=1), CTMRGEnv(peps, ComplexSpace(20)) +) N´ = abs(norm(peps, ctm)) @show abs(N - N´) / N @@ -53,7 +55,9 @@ mps2 = PEPSKit.initializeMPS(T2, fill(ComplexSpace(20), 2, 2)) mps2, envs2, ϵ = leading_boundary(mps2, T2, VUMPS()) N2 = abs(prod(expectation_value(mps2, T2))) -ctm2 = leading_boundary(peps2, SimultaneousCTMRG(; verbosity=1), CTMRGEnv(peps2, ComplexSpace(20))) +ctm2 = leading_boundary( + peps2, SimultaneousCTMRG(; verbosity=1), CTMRGEnv(peps2, ComplexSpace(20)) +) N2´ = abs(norm(peps2, ctm2)) @show abs(N2 - N2´) / N2 diff --git a/src/algorithms/ctmrg/projectors.jl b/src/algorithms/ctmrg/projectors.jl index a02a896c..7d7ca4aa 100644 --- a/src/algorithms/ctmrg/projectors.jl +++ b/src/algorithms/ctmrg/projectors.jl @@ -101,4 +101,4 @@ function compute_projector(enlarged_corners, coordinate, alg::FullInfiniteProjec P_left, P_right = contract_projectors(U, S, V, R_left, L_right) return (P_left, P_right), (; err, U, S, V) -end \ No newline at end of file +end diff --git a/src/algorithms/peps_opt.jl b/src/algorithms/peps_opt.jl index 83ffb7ac..69f17338 100644 --- a/src/algorithms/peps_opt.jl +++ b/src/algorithms/peps_opt.jl @@ -261,7 +261,8 @@ function _rrule( svd_alg_fixed = SVDAdjoint(; fwd_alg=FixedSVD(Ufix, info.S, Vfix), rrule_alg=alg.projector_alg.svd_alg.rrule_alg ) - alg_fixed = SimultaneousCTMRG(; svd_alg=svd_alg_fixed, trscheme=notrunc()) + alg_fixed = @set alg.projector_alg.svd_alg = svd_alg_fixed + alg_fixed = @set alg.projector_alg.trscheme = notrunc() function leading_boundary_fixed_pullback(Δenvs′) Δenvs = unthunk(Δenvs′) diff --git a/test/ctmrg/fixed_iterscheme.jl b/test/ctmrg/fixed_iterscheme.jl index c0062abf..208fdece 100644 --- a/test/ctmrg/fixed_iterscheme.jl +++ b/test/ctmrg/fixed_iterscheme.jl @@ -40,7 +40,9 @@ atol = 1e-5 # fix gauge of SVD U_fix, V_fix = fix_relative_phases(info.U, info.V, signs) svd_alg_fix = SVDAdjoint(; fwd_alg=FixedSVD(U_fix, info.S, V_fix)) - ctm_alg_fix = SimultaneousCTMRG(; projector_alg, svd_alg=svd_alg_fix, trscheme=notrunc()) + ctm_alg_fix = SimultaneousCTMRG(; + projector_alg, svd_alg=svd_alg_fix, trscheme=notrunc() + ) # do iteration with FixedSVD env_fixedsvd, = ctmrg_iteration(psi, env_conv1, ctm_alg_fix) diff --git a/test/ctmrg/flavors.jl b/test/ctmrg/flavors.jl index dc489e9d..5d8b440c 100644 --- a/test/ctmrg/flavors.jl +++ b/test/ctmrg/flavors.jl @@ -62,11 +62,7 @@ end [SequentialCTMRG, SimultaneousCTMRG], projector_algs ) ctm_alg = ctmrg_alg(; - tol=1e-6, - maxiter=1, - verbosity=0, - trscheme=FixedSpaceTruncation(), - projector_alg, + tol=1e-6, maxiter=1, verbosity=0, trscheme=FixedSpaceTruncation(), projector_alg ) Ds = fill(2, 3, 3) χs = [16 17 18; 15 20 21; 14 19 22] diff --git a/test/ctmrg/gaugefix.jl b/test/ctmrg/gaugefix.jl index 8ffed116..de798ad4 100644 --- a/test/ctmrg/gaugefix.jl +++ b/test/ctmrg/gaugefix.jl @@ -10,7 +10,7 @@ unitcells = [(1, 1), (2, 2), (3, 2)] maxiter = 400 ctmrg_algs = [SequentialCTMRG, SimultaneousCTMRG] projector_algs = [HalfInfiniteProjector, FullInfiniteProjector] -χ = 6 +χ = 8 atol = 1e-4 @testset "Trivial symmetry ($T) - ($unitcell) - ($ctmrg_alg) - ($projector_alg)" for ( @@ -45,7 +45,7 @@ end psi = InfinitePEPS(randn, T, physical_space, peps_space; unitcell) - Random.seed!(987654321) # Seed RNG to make random environment consistent + Random.seed!(29385293852) # Seed RNG to make random environment consistent psi = InfinitePEPS(physical_space, peps_space; unitcell) env = CTMRGEnv(psi, ctm_space) alg = ctmrg_alg(; maxiter, projector_alg) diff --git a/test/ctmrg/gradients.jl b/test/ctmrg/gradients.jl index 93c278ce..1e805f9b 100644 --- a/test/ctmrg/gradients.jl +++ b/test/ctmrg/gradients.jl @@ -17,11 +17,17 @@ models = [heisenberg_XYZ(InfiniteSquare()), pwave_superconductor(InfiniteSquare( names = ["Heisenberg", "p-wave superconductor"] gradtol = 1e-4 -boundary_algs = [ - SequentialCTMRG(; verbosity=0, projector_alg=HalfInfiniteProjector), - SequentialCTMRG(; verbosity=0, projector_alg=FullInfiniteProjector), - SimultaneousCTMRG(; verbosity=0, projector_alg=HalfInfiniteProjector), - SimultaneousCTMRG(; verbosity=0, projector_alg=FullInfiniteProjector), +ctmrg_algs = [ + [ + SimultaneousCTMRG(; verbosity=0, projector_alg=HalfInfiniteProjector), + SimultaneousCTMRG(; verbosity=0, projector_alg=FullInfiniteProjector), + ], + [ + SimultaneousCTMRG(; verbosity=0, projector_alg=HalfInfiniteProjector), + SimultaneousCTMRG(; verbosity=0, projector_alg=FullInfiniteProjector), + SequentialCTMRG(; verbosity=0, projector_alg=HalfInfiniteProjector), + SequentialCTMRG(; verbosity=0, projector_alg=FullInfiniteProjector), + ], ] gradmodes = [ [ @@ -52,14 +58,15 @@ steps = -0.01:0.005:0.01 Vspace = Pspaces[i] Espace = Espaces[i] gms = gradmodes[i] - boundary_alg = boundary_algs[i] + calgs = ctmrg_algs[i] psi_init = InfinitePEPS(Pspace, Vspace, Vspace) - @testset "$alg_rrule" for alg_rrule in gms - @info "optimtest of $alg_rrule on $(names[i])" + @testset "$ctmrg_alg and $alg_rrule" for (ctmrg_alg, alg_rrule) in + Iterators.product(calgs, gms) + @info "optimtest of $ctmrg_alg and $alg_rrule on $(names[i])" Random.seed!(42039482030) dir = InfinitePEPS(Pspace, Vspace, Vspace) psi = InfinitePEPS(Pspace, Vspace, Vspace) - env = leading_boundary(CTMRGEnv(psi, Espace), psi, boundary_alg) + env = leading_boundary(CTMRGEnv(psi, Espace), psi, ctmrg_alg) alphas, fs, dfs1, dfs2 = OptimKit.optimtest( (psi, env), dir; @@ -68,9 +75,7 @@ steps = -0.01:0.005:0.01 inner=PEPSKit.real_inner, ) do (peps, envs) E, g = Zygote.withgradient(peps) do psi - envs2 = PEPSKit.hook_pullback( - leading_boundary, envs, psi, boundary_alg; alg_rrule - ) + envs2 = PEPSKit.hook_pullback(leading_boundary, envs, psi, ctmrg_alg; alg_rrule) return costfun(psi, envs2, models[i]) end diff --git a/test/heisenberg.jl b/test/heisenberg.jl index d985503a..f72a31a9 100644 --- a/test/heisenberg.jl +++ b/test/heisenberg.jl @@ -8,7 +8,7 @@ using OptimKit # initialize parameters χbond = 2 χenv = 16 -ctm_alg = SimultaneousCTMRG() +ctm_alg = SimultaneousCTMRG(; projector_alg=FullInfiniteProjector) opt_alg = PEPSOptimize(; boundary_alg=ctm_alg, optimizer=LBFGS(4; gradtol=1e-3, verbosity=2) ) From 2082302af840cd37d2c277ed0788f1658370afc1 Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Tue, 10 Dec 2024 13:59:48 +0100 Subject: [PATCH 196/213] Fix differentiability and gradients test --- src/algorithms/peps_opt.jl | 2 +- test/ctmrg/gaugefix.jl | 30 ++++++++++++++++++++++++++++-- test/ctmrg/gradients.jl | 11 ++++------- test/heisenberg.jl | 2 +- 4 files changed, 34 insertions(+), 11 deletions(-) diff --git a/src/algorithms/peps_opt.jl b/src/algorithms/peps_opt.jl index 69f17338..a4a6cc96 100644 --- a/src/algorithms/peps_opt.jl +++ b/src/algorithms/peps_opt.jl @@ -262,7 +262,7 @@ function _rrule( fwd_alg=FixedSVD(Ufix, info.S, Vfix), rrule_alg=alg.projector_alg.svd_alg.rrule_alg ) alg_fixed = @set alg.projector_alg.svd_alg = svd_alg_fixed - alg_fixed = @set alg.projector_alg.trscheme = notrunc() + alg_fixed = @set alg_fixed.projector_alg.trscheme = notrunc() function leading_boundary_fixed_pullback(Δenvs′) Δenvs = unthunk(Δenvs′) diff --git a/test/ctmrg/gaugefix.jl b/test/ctmrg/gaugefix.jl index de798ad4..7c2aae1e 100644 --- a/test/ctmrg/gaugefix.jl +++ b/test/ctmrg/gaugefix.jl @@ -13,6 +13,28 @@ projector_algs = [HalfInfiniteProjector, FullInfiniteProjector] χ = 8 atol = 1e-4 +function _make_symmetric!(psi) + if ==(size(psi)...) + return symmetrize!(psi, RotateReflect()) + else + return symmetrize!(symmetrize!(psi, ReflectDepth()), ReflectWidth()) + end +end + +# If I can't make the rng seed behave, I'll just randomly define a peps somehow +function _semi_random_peps!(psi::InfinitePEPS) + i = 0 + A′ = map(psi.A) do a + for (_, b) in blocks(a) + l = length(b) + b .= reshape(collect((1:l) .+ i), size(b)) + i += l + end + return a + end + return InfinitePEPS(A′) +end + @testset "Trivial symmetry ($T) - ($unitcell) - ($ctmrg_alg) - ($projector_alg)" for ( T, unitcell, ctmrg_alg, projector_alg ) in Iterators.product( @@ -22,7 +44,9 @@ atol = 1e-4 peps_space = ComplexSpace(2) ctm_space = ComplexSpace(χ) - psi = InfinitePEPS(randn, T, physical_space, peps_space; unitcell) + psi = InfinitePEPS(undef, T, physical_space, peps_space; unitcell) + _semi_random_peps!(psi) + _make_symmetric!(psi) Random.seed!(987654321) # Seed RNG to make random environment consistent env = CTMRGEnv(psi, ctm_space) @@ -43,7 +67,9 @@ end peps_space = Z2Space(0 => 1, 1 => 1) ctm_space = Z2Space(0 => χ ÷ 2, 1 => χ ÷ 2) - psi = InfinitePEPS(randn, T, physical_space, peps_space; unitcell) + psi = InfinitePEPS(undef, T, physical_space, peps_space; unitcell) + _semi_random_peps!(psi) + _make_symmetric!(psi) Random.seed!(29385293852) # Seed RNG to make random environment consistent psi = InfinitePEPS(physical_space, peps_space; unitcell) diff --git a/test/ctmrg/gradients.jl b/test/ctmrg/gradients.jl index 1e805f9b..f39ea6cc 100644 --- a/test/ctmrg/gradients.jl +++ b/test/ctmrg/gradients.jl @@ -23,10 +23,7 @@ ctmrg_algs = [ SimultaneousCTMRG(; verbosity=0, projector_alg=FullInfiniteProjector), ], [ - SimultaneousCTMRG(; verbosity=0, projector_alg=HalfInfiniteProjector), - SimultaneousCTMRG(; verbosity=0, projector_alg=FullInfiniteProjector), SequentialCTMRG(; verbosity=0, projector_alg=HalfInfiniteProjector), - SequentialCTMRG(; verbosity=0, projector_alg=FullInfiniteProjector), ], ] gradmodes = [ @@ -50,10 +47,10 @@ steps = -0.01:0.005:0.01 ## Tests # ------ -@testset "AD CTMRG energy gradients for $(names[i]) model" verbose = true for i in - eachindex( - models -) +@testset "AD CTMRG energy gradients for $(names[i]) model" verbose = true for i in [2] +# eachindex( +# models +# ) Pspace = Pspaces[i] Vspace = Pspaces[i] Espace = Espaces[i] diff --git a/test/heisenberg.jl b/test/heisenberg.jl index f72a31a9..d985503a 100644 --- a/test/heisenberg.jl +++ b/test/heisenberg.jl @@ -8,7 +8,7 @@ using OptimKit # initialize parameters χbond = 2 χenv = 16 -ctm_alg = SimultaneousCTMRG(; projector_alg=FullInfiniteProjector) +ctm_alg = SimultaneousCTMRG() opt_alg = PEPSOptimize(; boundary_alg=ctm_alg, optimizer=LBFGS(4; gradtol=1e-3, verbosity=2) ) From 663bf84dad5e94ce926973ab2a81bf4903552e0f Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Tue, 10 Dec 2024 14:00:28 +0100 Subject: [PATCH 197/213] Restore gradients test --- test/ctmrg/gradients.jl | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/test/ctmrg/gradients.jl b/test/ctmrg/gradients.jl index f39ea6cc..648d48bd 100644 --- a/test/ctmrg/gradients.jl +++ b/test/ctmrg/gradients.jl @@ -22,9 +22,7 @@ ctmrg_algs = [ SimultaneousCTMRG(; verbosity=0, projector_alg=HalfInfiniteProjector), SimultaneousCTMRG(; verbosity=0, projector_alg=FullInfiniteProjector), ], - [ - SequentialCTMRG(; verbosity=0, projector_alg=HalfInfiniteProjector), - ], + [SequentialCTMRG(; verbosity=0, projector_alg=HalfInfiniteProjector)], ] gradmodes = [ [ @@ -47,10 +45,10 @@ steps = -0.01:0.005:0.01 ## Tests # ------ -@testset "AD CTMRG energy gradients for $(names[i]) model" verbose = true for i in [2] -# eachindex( -# models -# ) +@testset "AD CTMRG energy gradients for $(names[i]) model" verbose = true for i in + eachindex( + models +) Pspace = Pspaces[i] Vspace = Pspaces[i] Espace = Espaces[i] From 45456f008b9cd4348ea97ecc1c6a742814e31ced Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Tue, 10 Dec 2024 15:13:12 +0100 Subject: [PATCH 198/213] Update gaugefix test again --- test/ctmrg/gaugefix.jl | 86 +++++++++++++----------------------------- 1 file changed, 27 insertions(+), 59 deletions(-) diff --git a/test/ctmrg/gaugefix.jl b/test/ctmrg/gaugefix.jl index 7c2aae1e..048d875d 100644 --- a/test/ctmrg/gaugefix.jl +++ b/test/ctmrg/gaugefix.jl @@ -5,78 +5,46 @@ using TensorKit using PEPSKit: ctmrg_iteration, gauge_fix, calc_elementwise_convergence +spacetypes = [ComplexSpace, Z2Space] scalartypes = [Float64, ComplexF64] unitcells = [(1, 1), (2, 2), (3, 2)] -maxiter = 400 ctmrg_algs = [SequentialCTMRG, SimultaneousCTMRG] projector_algs = [HalfInfiniteProjector, FullInfiniteProjector] -χ = 8 +χ = 6 atol = 1e-4 -function _make_symmetric!(psi) - if ==(size(psi)...) - return symmetrize!(psi, RotateReflect()) - else - return symmetrize!(symmetrize!(psi, ReflectDepth()), ReflectWidth()) - end +function _pre_converge_env( + ::Type{T}, physical_space, peps_space, ctm_space, unitcell; seed=12345 +) where {T} + Random.seed!(seed) # Seed RNG to make random environment consistent + psi = InfinitePEPS(rand, T, physical_space, peps_space; unitcell) + env₀ = CTMRGEnv(psi, ctm_space) + env_conv = leading_boundary(env₀, psi, SequentialCTMRG()) + return env_conv, psi end -# If I can't make the rng seed behave, I'll just randomly define a peps somehow -function _semi_random_peps!(psi::InfinitePEPS) - i = 0 - A′ = map(psi.A) do a - for (_, b) in blocks(a) - l = length(b) - b .= reshape(collect((1:l) .+ i), size(b)) - i += l - end - return a +# pre-converge CTMRG environments with given spacetype, scalartype and unit cell +preconv = Dict() +for (S, T, unitcell) in Iterators.product(spacetypes, scalartypes, unitcells) + if S == ComplexSpace + result = _pre_converge_env(T, S(2), S(2), S(χ), unitcell) + elseif S == Z2Space + result = _pre_converge_env( + T, S(0 => 1, 1 => 1), S(0 => 1, 1 => 1), S(0 => χ ÷ 2, 1 => χ ÷ 2), unitcell + ) end - return InfinitePEPS(A′) -end - -@testset "Trivial symmetry ($T) - ($unitcell) - ($ctmrg_alg) - ($projector_alg)" for ( - T, unitcell, ctmrg_alg, projector_alg -) in Iterators.product( - scalartypes, unitcells, ctmrg_algs, projector_algs -) - physical_space = ComplexSpace(2) - peps_space = ComplexSpace(2) - ctm_space = ComplexSpace(χ) - - psi = InfinitePEPS(undef, T, physical_space, peps_space; unitcell) - _semi_random_peps!(psi) - _make_symmetric!(psi) - - Random.seed!(987654321) # Seed RNG to make random environment consistent - env = CTMRGEnv(psi, ctm_space) - alg = ctmrg_alg(; maxiter, projector_alg) - - env = leading_boundary(env, psi, alg) - env′, = ctmrg_iteration(psi, env, alg) - env_fixed, = gauge_fix(env, env′) - @test calc_elementwise_convergence(env, env_fixed) ≈ 0 atol = atol + push!(preconv, (S, T, unitcell) => result) end -@testset "Z2 symmetry ($T) - ($unitcell) - ($ctmrg_alg) - ($projector_alg)" for ( - T, unitcell, ctmrg_alg, projector_alg +@testset "($S) - ($T) - ($unitcell) - ($ctmrg_alg) - ($projector_alg)" for ( + S, T, unitcell, ctmrg_alg, projector_alg ) in Iterators.product( - scalartypes, unitcells, ctmrg_algs, projector_algs + spacetypes, scalartypes, unitcells, ctmrg_algs, projector_algs ) - physical_space = Z2Space(0 => 1, 1 => 1) - peps_space = Z2Space(0 => 1, 1 => 1) - ctm_space = Z2Space(0 => χ ÷ 2, 1 => χ ÷ 2) - - psi = InfinitePEPS(undef, T, physical_space, peps_space; unitcell) - _semi_random_peps!(psi) - _make_symmetric!(psi) - - Random.seed!(29385293852) # Seed RNG to make random environment consistent - psi = InfinitePEPS(physical_space, peps_space; unitcell) - env = CTMRGEnv(psi, ctm_space) - alg = ctmrg_alg(; maxiter, projector_alg) - - env = leading_boundary(env, psi, alg) + alg = ctmrg_alg(; projector_alg) + env_pre, psi = preconv[(S, T, unitcell)] + env_pre + env = leading_boundary(env_pre, psi, alg) env′, = ctmrg_iteration(psi, env, alg) env_fixed, = gauge_fix(env, env′) @test calc_elementwise_convergence(env, env_fixed) ≈ 0 atol = atol From 528da01d7954715401e3d51d5805ba5877085870 Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Tue, 10 Dec 2024 15:51:11 +0100 Subject: [PATCH 199/213] Fix unitcell test --- test/ctmrg/unitcell.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/ctmrg/unitcell.jl b/test/ctmrg/unitcell.jl index e2f010fa..126126f2 100644 --- a/test/ctmrg/unitcell.jl +++ b/test/ctmrg/unitcell.jl @@ -53,7 +53,7 @@ function random_dualize!(M::AbstractMatrix{<:ElementarySpace}) return M end -@testset "Integer space specifiers with $ctm_alg" begin +@testset "Integer space specifiers with $ctm_alg" for ctm_alg in ctm_algs unitcell = (3, 3) Pspaces = rand(2:3, unitcell...) From 45d00e84f39f8dfaec4ed1876545eec9cc862086 Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Thu, 12 Dec 2024 01:53:57 +0800 Subject: [PATCH 200/213] Add simple update algorithm (#97) * remove CTMRG redundant logging * add simple update algorithm * add full update core algorithm * improve formatting * add TODO for svd initialization of ALS optimization * remove a redundant SVD in full update * prepare for addition of full-infinite env CTMRG * Add `length` for SUWeight * Define `Base.show` for `SUWeight` * add test for simple update * add test for full update * update formatting * Refactor calc_convergence Co-authored-by: Lukas Devos * Update sdiag_pow for latest TensorKit Co-authored-by: Lukas Devos * Rename folder "timeevol" to "time_evolution" * Replace `maxabs` by infinity norm * Focus on simple update * implement `InfiniteWeightPEPS` * Update to the Heisenberg tensors Using MPSKitModels, the functions gen_siteop and gen_bondop are not necessary anymore. The relevant tensors and their tensor product can be defined directly from MPSKitModels * Minor fix after using operators from MPSKitModels * Use Julia's logging system; shorten SU test * Solve deprecation warning for `permute` * Remove buggy in-place rotations and reflections * Minor refactoring * Print more message during simple update * Replace custom rho with existing exp. value calculation * Integrate SU with LocalOperator * remove accidentally added test * Add rotation and reflection of LocalOperator * Improve `get_gateterm` * fix format * fix format again * Introduce `SimpleUpdate` algorithm struct * Export `simpleupdate`; remove abbreviated `SU` * add FixedSpaceTruncation for simple update * Create heisenberg_sufu_onesite This tests whether one-body terms can be accurately handled by SU by rewriting them as one-body terms. This is just an example and should probably not be in the final test set. * format fix * Add spin U(1) symmetry to Heisenberg model SU test * Add SU-AD test for heisenberg * fix formatting * add check that all operators are twosite * update check two-body terms the code now also checks whether all interactions are defined on nearest neighbours * Change PEPSOptimize parameters in `suad` test * Hubbard model added the Hubbard model as in the pull request from lkdvos added test for simple update on the Hubbard model for t = 1, U = 6, half filling * Clean up test on Hubbard model * Add t-J model Hamiltonian * Squashed commit of the following: commit e2545a379f0efb0e70087bb2a526ce08a6986bcc Merge: 36973e7 9532507 Author: Paul Brehmer Date: Thu Dec 5 11:06:04 2024 +0100 Merge pull request #90 from QuantumKitHub/pb-improve-sequential Make `:sequential` act column-wise commit 9532507408401bb09916ff38834c19a085ff1639 Merge: 77fc207 36973e7 Author: Lukas Devos Date: Wed Dec 4 15:32:45 2024 -0500 Merge branch 'master' into pb-improve-sequential commit 77fc207184198bdc8a1aa9d7d9b0acf78129d576 Author: Lukas Devos Date: Tue Dec 3 15:33:15 2024 -0500 reenable expansion for simultaneous ctmrg commit 51f2cc4baefe57b8351e5ab3f6835f37726a5db3 Author: Lukas Devos Date: Tue Dec 3 14:18:36 2024 -0500 excise expansion step commit 3754b67cfe249ffb0aeff33cebd0d22ac322a9c9 Merge: 8a09a82 08cf1dc Author: Paul Brehmer Date: Fri Nov 8 14:57:56 2024 +0100 Merge branch 'master' into pb-improve-sequential * Update `ctmrg_leftmove` with latest upstream * Apply suggestions from code review Co-authored-by: Lukas Devos * Rename for `absorb_weight` * Improve SUWeight construction * Move geometric operations on LocalOperator * Remove `sdiag_inv_sqrt` (replaced with `sdiag_pow`) * Fix rrule for sdiag_pow and formatting * Fix Heisenberg SU test * Improve some docstring * Add back `length` for `SUWeight` * update actions This should allow the tests to run, because the secrets are now explicitly passed on * Refactoring `SUWeight` * Update simple update test * disable multithreading * remove superfluous broadcats * Add function signatures * Merge Heisenberg tests * Fix docstring and error messages * Promote `SUWeight` to a custom `struct` * Fix formatting * Fix pow rrule and make eltype more consistent * Fix `similar` for `InfinitePEPO` after modifying `eltype` * Remove `eltype` of `InfiniteWeightPEPS` * Scrap eltype for CTMRGEnv, add args to similar(::PEPO) * Fix conj in sdiag_pow rrule * Cicrumvent Heisenberg tests errors by using GMRES to differentiate SVD * small improvements * update MPSKitModels compat --------- Co-authored-by: Lukas Devos Co-authored-by: sanderdemeyer <80397440+Sander-De-Meyer@users.noreply.github.com> Co-authored-by: Paul Brehmer --- .github/workflows/{CI.yml => Tests.yml} | 12 +- Project.toml | 2 +- examples/hubbard_su.jl | 62 +++++ src/PEPSKit.jl | 14 +- src/algorithms/ctmrg/ctmrg.jl | 28 ++- src/algorithms/ctmrg/gaugefix.jl | 11 + src/algorithms/peps_opt.jl | 4 +- src/algorithms/time_evolution/gatetools.jl | 52 ++++ src/algorithms/time_evolution/simpleupdate.jl | 218 +++++++++++++++++ src/algorithms/toolbox.jl | 4 +- src/environments/ctmrg_environments.jl | 44 +++- src/environments/transferpeps_environments.jl | 3 +- src/operators/infinitepepo.jl | 7 +- src/operators/lattices/squarelattice.jl | 2 + src/operators/localoperator.jl | 100 ++++++++ src/operators/models.jl | 59 ++++- src/states/infinitepeps.jl | 7 +- src/states/infiniteweightpeps.jl | 223 ++++++++++++++++++ src/utility/mirror.jl | 12 + src/utility/svd.jl | 2 +- src/utility/util.jl | 56 +++-- test/heisenberg.jl | 114 +++++++-- test/runtests.jl | 2 +- 23 files changed, 955 insertions(+), 83 deletions(-) rename .github/workflows/{CI.yml => Tests.yml} (75%) create mode 100644 examples/hubbard_su.jl create mode 100644 src/algorithms/time_evolution/gatetools.jl create mode 100644 src/algorithms/time_evolution/simpleupdate.jl create mode 100644 src/states/infiniteweightpeps.jl create mode 100644 src/utility/mirror.jl diff --git a/.github/workflows/CI.yml b/.github/workflows/Tests.yml similarity index 75% rename from .github/workflows/CI.yml rename to .github/workflows/Tests.yml index 85c75900..1f19b865 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/Tests.yml @@ -1,4 +1,4 @@ -name: CI +name: Tests on: push: branches: @@ -23,8 +23,8 @@ jobs: fail-fast: false matrix: version: - - 'lts' - - '1' + - 'lts' # minimal supported version + - '1' # latest released Julia version group: - ctmrg - boundarymps @@ -34,9 +34,11 @@ jobs: - ubuntu-latest - macOS-latest - windows-latest - uses: "QuantumKitHub/.github/.github/workflows/tests.yml@main" + uses: "QuantumKitHub/QuantumKitHubActions/.github/workflows/Tests.yml@main" with: group: "${{ matrix.group }}" julia-version: "${{ matrix.version }}" os: "${{ matrix.os }}" - secrets: inherit \ No newline at end of file + secrets: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + diff --git a/Project.toml b/Project.toml index cffc9c1a..ba49bc98 100644 --- a/Project.toml +++ b/Project.toml @@ -32,7 +32,7 @@ KrylovKit = "0.8" LinearAlgebra = "1" LoggingExtras = "1" MPSKit = "0.11" -MPSKitModels = "0.3" +MPSKitModels = "0.3.5" OhMyThreads = "0.7" OptimKit = "0.3" Printf = "1" diff --git a/examples/hubbard_su.jl b/examples/hubbard_su.jl new file mode 100644 index 00000000..4bd84846 --- /dev/null +++ b/examples/hubbard_su.jl @@ -0,0 +1,62 @@ +using Test +using Printf +using Random +using PEPSKit +using TensorKit + +# random initialization of 2x2 iPEPS with weights and CTMRGEnv (using real numbers) +Dcut, symm = 8, Trivial +N1, N2 = 2, 2 +Random.seed!(10) +if symm == Trivial + Pspace = Vect[fℤ₂](0 => 2, 1 => 2) + Vspace = Vect[fℤ₂](0 => Dcut / 2, 1 => Dcut / 2) +else + error("Not implemented") +end + +peps = InfiniteWeightPEPS(rand, Float64, Pspace, Vspace; unitcell=(N1, N2)) +# normalize vertex tensors +for ind in CartesianIndices(peps.vertices) + peps.vertices[ind] /= norm(peps.vertices[ind], Inf) +end +# Hubbard model Hamiltonian at half-filling +t, U = 1.0, 6.0 +ham = hubbard_model(Float64, Trivial, Trivial, InfiniteSquare(N1, N2); t=t, U=U, mu=U / 2) + +# simple update +dts = [1e-2, 1e-3, 4e-4, 1e-4] +tols = [1e-6, 1e-8, 1e-8, 1e-8] +maxiter = 10000 +for (n, (dt, tol)) in enumerate(zip(dts, tols)) + trscheme = truncerr(1e-10) & truncdim(Dcut) + alg = SimpleUpdate(dt, tol, maxiter, trscheme) + result = simpleupdate(peps, ham, alg; bipartite=false) + global peps = result[1] +end + +# absort weight into site tensors +peps = InfinitePEPS(peps) +# CTMRG +χenv0, χenv = 6, 20 +Espace = Vect[fℤ₂](0 => χenv0 / 2, 1 => χenv0 / 2) +envs = CTMRGEnv(randn, Float64, peps, Espace) +for χ in [χenv0, χenv] + trscheme = truncerr(1e-9) & truncdim(χ) + ctm_alg = CTMRG(; + maxiter=40, tol=1e-10, verbosity=3, trscheme=trscheme, ctmrgscheme=:sequential + ) + global envs = leading_boundary(envs, peps, ctm_alg) +end + +""" +Benchmark values of the ground state energy from +Qin, M., Shi, H., & Zhang, S. (2016). Benchmark study of the two-dimensional Hubbard model with auxiliary-field quantum Monte Carlo method. Physical Review B, 94(8), 085103. +""" +# measure physical quantities +E = costfun(peps, envs, ham) / (N1 * N2) +Es_exact = Dict(0 => -1.62, 2 => -0.176, 4 => 0.8603, 6 => -0.6567, 8 => -0.5243) +E_exact = Es_exact[U] - U / 2 +@info @sprintf("Energy = %.8f\n", E) +@info @sprintf("Benchmark energy = %.8f\n", E_exact) +@test isapprox(E, E_exact; atol=1e-2) diff --git a/src/PEPSKit.jl b/src/PEPSKit.jl index c8a6b032..a69e5331 100644 --- a/src/PEPSKit.jl +++ b/src/PEPSKit.jl @@ -17,12 +17,14 @@ include("utility/util.jl") include("utility/diffable_threads.jl") include("utility/svd.jl") include("utility/rotations.jl") +include("utility/mirror.jl") include("utility/diffset.jl") include("utility/hook_pullback.jl") include("utility/autoopt.jl") include("states/abstractpeps.jl") include("states/infinitepeps.jl") +include("states/infiniteweightpeps.jl") include("operators/transferpeps.jl") include("operators/infinitepepo.jl") @@ -43,6 +45,9 @@ include("algorithms/ctmrg/sparse_environments.jl") include("algorithms/ctmrg/ctmrg.jl") include("algorithms/ctmrg/gaugefix.jl") +include("algorithms/time_evolution/gatetools.jl") +include("algorithms/time_evolution/simpleupdate.jl") + include("algorithms/toolbox.jl") include("algorithms/peps_opt.jl") @@ -59,7 +64,7 @@ include("utility/symmetrization.jl") const ctmrgscheme = :simultaneous const reuse_env = true const trscheme = FixedSpaceTruncation() - const fwd_alg = TensorKit.SVD() + const fwd_alg = TensorKit.SDD() const rrule_alg = Arnoldi(; tol=1e-2fpgrad_tol, krylovdim=48, verbosity=-1) const svd_alg = SVDAdjoint(; fwd_alg, rrule_alg) const optimizer = LBFGS(32; maxiter=100, gradtol=1e-4, verbosity=2) @@ -167,13 +172,18 @@ export leading_boundary export PEPSOptimize, GeomSum, ManualIter, LinSolver export fixedpoint +export absorb_weight +export su_iter, simpleupdate, SimpleUpdate + export InfinitePEPS, InfiniteTransferPEPS +export SUWeight, InfiniteWeightPEPS export InfinitePEPO, InfiniteTransferPEPO export initializeMPS, initializePEPS export ReflectDepth, ReflectWidth, Rotate, RotateReflect export symmetrize!, symmetrize_retract_and_finalize! export showtypeofgrad export InfiniteSquare, vertices, nearest_neighbours, next_nearest_neighbours -export transverse_field_ising, heisenberg_XYZ, j1_j2, pwave_superconductor +export transverse_field_ising, heisenberg_XYZ, j1_j2 +export pwave_superconductor, hubbard_model, tj_model end # module diff --git a/src/algorithms/ctmrg/ctmrg.jl b/src/algorithms/ctmrg/ctmrg.jl index 91d86159..317a7231 100644 --- a/src/algorithms/ctmrg/ctmrg.jl +++ b/src/algorithms/ctmrg/ctmrg.jl @@ -133,6 +133,26 @@ function MPSKit.leading_boundary(envinit, state, alg::CTMRG) end end +""" + ctmrg_leftmove(col::Int, state, envs::CTMRGEnv, alg::SequentialCTMRG) + +Perform CTMRG left move on the `col`-th column. +""" +function ctmrg_leftmove(col::Int, state, envs::CTMRGEnv, alg::SequentialCTMRG) + #= + ----> left move + C1 ← T1 ← r-1 + ↓ ‖ + T4 = M == r + ↓ ‖ + C4 → T3 → r+1 + c-1 c + =# + projectors, info = ctmrg_projectors(col, state, envs, alg) + envs = ctmrg_renormalize(col, projectors, state, envs, alg) + return envs, info +end + """ ctmrg_iter(state, envs::CTMRGEnv, alg::CTMRG) -> envs′, info @@ -143,14 +163,12 @@ function ctmrg_iter(state, envs::CTMRGEnv, alg::SequentialCTMRG) ϵ = zero(real(scalartype(state))) for _ in 1:4 # rotate for col in 1:size(state, 2) # left move column-wise - projectors, info = ctmrg_projectors(col, state, envs, alg) - envs = ctmrg_renormalize(col, projectors, state, envs, alg) + envs, info = ctmrg_leftmove(col, state, envs, alg) ϵ = max(ϵ, info.err) end state = rotate_north(state, EAST) envs = rotate_north(envs, EAST) end - return envs, (; err=ϵ) end function ctmrg_iter(state, envs::CTMRGEnv, alg::SimultaneousCTMRG) @@ -300,7 +318,7 @@ function build_projectors( Q::AbstractTensorMap{E,3,3}, Q_next::AbstractTensorMap{E,3,3}, ) where {E<:ElementarySpace} - isqS = sdiag_inv_sqrt(S) + isqS = sdiag_pow(S, -0.5) P_left = Q_next * V' * isqS P_right = isqS * U' * Q return P_left, P_right @@ -312,7 +330,7 @@ function build_projectors( Q::EnlargedCorner, Q_next::EnlargedCorner, ) where {E<:ElementarySpace} - isqS = sdiag_inv_sqrt(S) + isqS = sdiag_pow(S, -0.5) P_left = left_projector(Q.E_1, Q.C, Q.E_2, V, isqS, Q.ket, Q.bra) P_right = right_projector( Q_next.E_1, Q_next.C, Q_next.E_2, U, isqS, Q_next.ket, Q_next.bra diff --git a/src/algorithms/ctmrg/gaugefix.jl b/src/algorithms/ctmrg/gaugefix.jl index 5e63d5ef..338395b2 100644 --- a/src/algorithms/ctmrg/gaugefix.jl +++ b/src/algorithms/ctmrg/gaugefix.jl @@ -173,6 +173,17 @@ function calc_convergence(envs, CSold, TSold) return max(ΔCS, ΔTS), CSnew, TSnew end +""" + calc_convergence(envsNew::CTMRGEnv, envsOld::CTMRGEnv) + +Calculate convergence of CTMRG by comparing the singular values of CTM tensors. +""" +function calc_convergence(envsNew::CTMRGEnv, envsOld::CTMRGEnv) + CSOld = map(x -> tsvd(x)[2], envsOld.corners) + TSOld = map(x -> tsvd(x)[2], envsOld.edges) + return calc_convergence(envsNew, CSOld, TSOld) +end + @non_differentiable calc_convergence(args...) """ diff --git a/src/algorithms/peps_opt.jl b/src/algorithms/peps_opt.jl index 39e138a1..8b927f93 100644 --- a/src/algorithms/peps_opt.jl +++ b/src/algorithms/peps_opt.jl @@ -159,8 +159,8 @@ function fixedpoint( if scalartype(env₀) <: Real env₀ = complex(env₀) - @warn "the provided real environment was converted to a complex environment since\ - :fixed mode generally produces complex gauges; use :diffgauge mode instead to work\ + @warn "the provided real environment was converted to a complex environment since \ + :fixed mode generally produces complex gauges; use :diffgauge mode instead to work \ with purely real environments" end diff --git a/src/algorithms/time_evolution/gatetools.jl b/src/algorithms/time_evolution/gatetools.jl new file mode 100644 index 00000000..91ebaa0f --- /dev/null +++ b/src/algorithms/time_evolution/gatetools.jl @@ -0,0 +1,52 @@ +""" + get_gate(dt::Float64, H::LocalOperator) + +Compute `exp(-dt * H)` from the nearest neighbor Hamiltonian `H`. +""" +function get_gate(dt::Float64, H::LocalOperator) + @assert all([ + length(op.dom) == 2 && norm(Tuple(terms[2] - terms[1])) == 1.0 for + (terms, op) in H.terms + ]) "Only nearest-neighbour terms allowed" + return LocalOperator( + H.lattice, Tuple(sites => exp(-dt * op) for (sites, op) in H.terms)... + ) +end + +""" + is_equivalent(bond1::NTuple{2,CartesianIndex{2}}, bond2::NTuple{2,CartesianIndex{2}}, (Nrow, Ncol)::NTuple{2,Int}) + +Check if two 2-site bonds are related by a (periodic) lattice translation. +""" +function is_equivalent( + bond1::NTuple{2,CartesianIndex{2}}, + bond2::NTuple{2,CartesianIndex{2}}, + (Nrow, Ncol)::NTuple{2,Int}, +) + r1 = bond1[1] - bond1[2] + r2 = bond2[1] - bond2[2] + shift_row = bond1[1][1] - bond2[1][1] + shift_col = bond1[1][2] - bond2[1][2] + return r1 == r2 && mod(shift_row, Nrow) == 0 && mod(shift_col, Ncol) == 0 +end + +""" + get_gateterm(gate::LocalOperator, bond::NTuple{2,CartesianIndex{2}}) + +Get the term of a 2-site gate acting on a certain bond. +Input `gate` should only include one term for each nearest neighbor bond. +""" +function get_gateterm(gate::LocalOperator, bond::NTuple{2,CartesianIndex{2}}) + label = findall(p -> is_equivalent(p.first, bond, size(gate.lattice)), gate.terms) + if length(label) == 0 + # try reversed site order + label = findall( + p -> is_equivalent(p.first, reverse(bond), size(gate.lattice)), gate.terms + ) + @assert length(label) == 1 + return permute(gate.terms[label[1]].second, ((2, 1), (4, 3))) + else + @assert length(label) == 1 + return gate.terms[label[1]].second + end +end diff --git a/src/algorithms/time_evolution/simpleupdate.jl b/src/algorithms/time_evolution/simpleupdate.jl new file mode 100644 index 00000000..685d069b --- /dev/null +++ b/src/algorithms/time_evolution/simpleupdate.jl @@ -0,0 +1,218 @@ +""" + struct SimpleUpdate + +Algorithm struct for simple update (SU) of infinite PEPS with bond weights. +Each SU run is converged when the singular value difference becomes smaller than `tol`. +""" +struct SimpleUpdate + dt::Float64 + tol::Float64 + maxiter::Int + trscheme::TensorKit.TruncationScheme +end + +function truncation_scheme(alg::SimpleUpdate, v::ElementarySpace) + if alg.trscheme isa FixedSpaceTruncation + return truncspace(v) + else + return alg.trscheme + end +end + +""" +_su_bondx!(row::Int, col::Int, gate::AbstractTensorMap{S,2,2}, + peps::InfiniteWeightPEPS, alg::SimpleUpdate) where {S<:ElementarySpace} + +Simple update of the x-bond `peps.weights[1,r,c]`. + +``` + [2,r,c] [2,r,c+1] + ↓ ↓ + [1,r,c-1] ← T[r,c] ← [1,r,c] ←- T[r,c+1] ← [1,r,c+1] + ↓ ↓ + [2,r+1,c] [2,r+1,c+1] +``` +""" +function _su_bondx!( + row::Int, + col::Int, + gate::AbstractTensorMap{S,2,2}, + peps::InfiniteWeightPEPS, + alg::SimpleUpdate, +) where {S<:ElementarySpace} + Nr, Nc = size(peps) + @assert 1 <= row <= Nr && 1 <= col <= Nc + row2, col2 = row, _next(col, Nc) + T1, T2 = peps.vertices[row, col], peps.vertices[row2, col2] + # absorb environment weights + for ax in (2, 4, 5) + T1 = absorb_weight(T1, row, col, ax, peps.weights) + end + for ax in (2, 3, 4) + T2 = absorb_weight(T2, row2, col2, ax, peps.weights) + end + # absorb bond weight + T1 = absorb_weight(T1, row, col, 3, peps.weights; sqrtwt=true) + T2 = absorb_weight(T2, row2, col2, 5, peps.weights; sqrtwt=true) + #= QR and LQ decomposition + + 2 1 1 2 + ↓ ↗ ↓ ↗ + 5 ← T ← 3 ====> 3 ← X ← 4 ← 1 ← aR ← 3 + ↓ ↓ + 4 2 + + 2 1 2 2 + ↓ ↗ ↗ ↓ + 5 ← T ← 3 ====> 1 ← bL ← 3 ← 1 ← Y ← 3 + ↓ ↓ + 4 4 + =# + X, aR = leftorth(T1, ((2, 4, 5), (1, 3)); alg=QRpos()) + bL, Y = rightorth(T2, ((5, 1), (2, 3, 4)); alg=LQpos()) + #= apply gate + + -2 -3 + ↑ ↑ + |----gate---| + ↑ ↑ + 1 2 + ↑ ↑ + -1← aR -← 3 -← bL ← -4 + =# + @tensor tmp[-1 -2; -3 -4] := gate[-2, -3, 1, 2] * aR[-1, 1, 3] * bL[3, 2, -4] + # SVD + aR, s, bL, ϵ = tsvd!(tmp; trunc=truncation_scheme(alg, space(T1, 3))) + #= + -2 -1 -1 -2 + | ↗ ↗ | + -5- X ← 1 ← aR - -3 -5 - bL ← 1 ← Y - -3 + | | + -4 -4 + =# + @tensor T1[-1; -2 -3 -4 -5] := X[-2, -4, -5, 1] * aR[1, -1, -3] + @tensor T2[-1; -2 -3 -4 -5] := bL[-5, -1, 1] * Y[1, -2, -3, -4] + # remove environment weights + for ax in (2, 4, 5) + T1 = absorb_weight(T1, row, col, ax, peps.weights; invwt=true) + end + for ax in (2, 3, 4) + T2 = absorb_weight(T2, row2, col2, ax, peps.weights; invwt=true) + end + # update tensor dict and weight on current bond + # (max element of weight is normalized to 1) + peps.vertices[row, col], peps.vertices[row2, col2] = T1, T2 + peps.weights[1, row, col] = s / norm(s, Inf) + return ϵ +end + +""" + su_iter(gate::LocalOperator, peps::InfiniteWeightPEPS, alg::SimpleUpdate; bipartite::Bool=false) + +One round of simple update on `peps` applying the nearest neighbor `gate`. +""" +function su_iter( + gate::LocalOperator, peps::InfiniteWeightPEPS, alg::SimpleUpdate; bipartite::Bool=false +) + @assert size(gate.lattice) == size(peps) + Nr, Nc = size(peps) + if bipartite + @assert Nr == Nc == 2 + end + # TODO: make algorithm independent on the choice of dual in the network + for (r, c) in Iterators.product(1:Nr, 1:Nc) + @assert [isdual(space(peps.vertices[r, c], ax)) for ax in 1:5] == [0, 1, 1, 0, 0] + @assert [isdual(space(peps.weights[1, r, c], ax)) for ax in 1:2] == [0, 1] + @assert [isdual(space(peps.weights[2, r, c], ax)) for ax in 1:2] == [0, 1] + end + peps2 = deepcopy(peps) + gate_mirrored = mirror_antidiag(gate) + for direction in 1:2 + # mirror the y-weights to x-direction + # to update them using code for x-weights + if direction == 2 + peps2 = mirror_antidiag(peps2) + end + if bipartite + for r in 1:2 + rp1 = _next(r, 2) + term = get_gateterm( + direction == 1 ? gate : gate_mirrored, + (CartesianIndex(r, 1), CartesianIndex(r, 2)), + ) + ϵ = _su_bondx!(r, 1, term, peps2, alg) + peps2.vertices[rp1, 2] = deepcopy(peps2.vertices[r, 1]) + peps2.vertices[rp1, 1] = deepcopy(peps2.vertices[r, 2]) + peps2.weights[1, rp1, 2] = deepcopy(peps2.weights[1, r, 1]) + end + else + for site in CartesianIndices(peps2.vertices) + r, c = Tuple(site) + term = get_gateterm( + direction == 1 ? gate : gate_mirrored, + (CartesianIndex(r, c), CartesianIndex(r, c + 1)), + ) + ϵ = _su_bondx!(r, c, term, peps2, alg) + end + end + if direction == 2 + peps2 = mirror_antidiag(peps2) + end + end + return peps2 +end + +""" + simpleupdate(peps::InfiniteWeightPEPS, ham::LocalOperator, alg::SimpleUpdate; + bipartite::Bool=false, check_int::Int=500) + +Perform simple update with nearest neighbor Hamiltonian `ham`, where the evolution +information is printed every `check_int` steps. + +If `bipartite == true` (for square lattice), a unit cell size of `(2, 2)` is assumed, +as well as tensors and x/y weights which are the same across the diagonals, i.e. at +`(row, col)` and `(row+1, col+1)`. +""" +function simpleupdate( + peps::InfiniteWeightPEPS, + ham::LocalOperator, + alg::SimpleUpdate; + bipartite::Bool=false, + check_int::Int=500, +) + time_start = time() + N1, N2 = size(peps) + if bipartite + @assert N1 == N2 == 2 + end + # exponentiating the 2-site Hamiltonian gate + gate = get_gate(alg.dt, ham) + wtdiff = 1.0 + wts0 = deepcopy(peps.weights) + for count in 1:(alg.maxiter) + time0 = time() + peps = su_iter(gate, peps, alg; bipartite=bipartite) + wtdiff = compare_weights(peps.weights, wts0) + converge = (wtdiff < alg.tol) + cancel = (count == alg.maxiter) + wts0 = deepcopy(peps.weights) + time1 = time() + if ((count == 1) || (count % check_int == 0) || converge || cancel) + @info "Space of x-weight at [1, 1] = $(space(peps.weights[1, 1, 1], 1))" + label = (converge ? "conv" : (cancel ? "cancel" : "iter")) + message = @sprintf( + "SU %s %-7d: dt = %.0e, weight diff = %.3e, time = %.3f sec\n", + label, + count, + alg.dt, + wtdiff, + time1 - ((converge || cancel) ? time_start : time0) + ) + cancel ? (@warn message) : (@info message) + end + if converge || cancel + break + end + end + return peps, wtdiff +end diff --git a/src/algorithms/toolbox.jl b/src/algorithms/toolbox.jl index 13e725e3..b89a072a 100644 --- a/src/algorithms/toolbox.jl +++ b/src/algorithms/toolbox.jl @@ -58,11 +58,11 @@ function LinearAlgebra.norm(peps::InfinitePEPS, env::CTMRGEnv) end """ - correlation_length(peps::InfinitePEPS, env::CTMRGEnv; howmany=2) + correlation_length(peps::InfinitePEPS, env::CTMRGEnv; num_vals=2) Compute the PEPS correlation length based on the horizontal and vertical transfer matrices. Additionally the (normalized) eigenvalue spectrum is -returned. Specify the number of computed eigenvalues with `howmany`. +returned. Specify the number of computed eigenvalues with `num_vals`. """ function MPSKit.correlation_length(peps::InfinitePEPS, env::CTMRGEnv; num_vals=2) T = scalartype(peps) diff --git a/src/environments/ctmrg_environments.jl b/src/environments/ctmrg_environments.jl index c026762b..7bca4b90 100644 --- a/src/environments/ctmrg_environments.jl +++ b/src/environments/ctmrg_environments.jl @@ -160,7 +160,7 @@ end """ CTMRGEnv( - [f=randn, ComplexF64], D_north::S, D_south::S, chi_north::S, [chi_east::S], [chi_south::S], [chi_west::S]; unitcell::Tuple{Int,Int}=(1, 1), + [f=randn, ComplexF64], D_north::S, D_east::S, chi_north::S, [chi_east::S], [chi_south::S], [chi_west::S]; unitcell::Tuple{Int,Int}=(1, 1), ) where {S<:Union{Int,ElementarySpace}} Construct a CTMRG environment by specifying the north and east virtual spaces of the @@ -173,7 +173,7 @@ corresponding edge tensor for each direction. """ function CTMRGEnv( D_north::S, - D_south::S, + D_east::S, chi_north::S, chi_east::S=chi_north, chi_south::S=chi_north, @@ -184,7 +184,7 @@ function CTMRGEnv( randn, ComplexF64, fill(D_north, unitcell), - fill(D_south, unitcell), + fill(D_east, unitcell), fill(chi_north, unitcell), fill(chi_east, unitcell), fill(chi_south, unitcell), @@ -195,7 +195,7 @@ function CTMRGEnv( f, T, D_north::S, - D_south::S, + D_east::S, chi_north::S, chi_east::S=chi_north, chi_south::S=chi_north, @@ -206,7 +206,7 @@ function CTMRGEnv( f, T, fill(D_north, unitcell), - fill(D_south, unitcell), + fill(D_east, unitcell), fill(chi_north, unitcell), fill(chi_east, unitcell), fill(chi_south, unitcell), @@ -362,16 +362,44 @@ function Base.rotl90(env::CTMRGEnv{C,T}) where {C,T} Array{C,3}(undef, 4, size(env.corners, 3), size(env.corners, 2)) ) edges′ = Zygote.Buffer(Array{T,3}(undef, 4, size(env.edges, 3), size(env.edges, 2))) + for dir in 1:4 + dir2 = _prev(dir, 4) + corners′[dir2, :, :] = rotl90(env.corners[dir, :, :]) + edges′[dir2, :, :] = rotl90(env.edges[dir, :, :]) + end + return CTMRGEnv(copy(corners′), copy(edges′)) +end +# Rotate corners & edges clockwise +function Base.rotr90(env::CTMRGEnv{C,T}) where {C,T} + # Initialize rotated corners & edges with rotated sizes + corners′ = Zygote.Buffer( + Array{C,3}(undef, 4, size(env.corners, 3), size(env.corners, 2)) + ) + edges′ = Zygote.Buffer(Array{T,3}(undef, 4, size(env.edges, 3), size(env.edges, 2))) for dir in 1:4 - corners′[_prev(dir, 4), :, :] = rotl90(env.corners[dir, :, :]) - edges′[_prev(dir, 4), :, :] = rotl90(env.edges[dir, :, :]) + dir2 = _next(dir, 4) + corners′[dir2, :, :] = rotr90(env.corners[dir, :, :]) + edges′[dir2, :, :] = rotr90(env.edges[dir, :, :]) end + return CTMRGEnv(copy(corners′), copy(edges′)) +end +# Rotate corners & edges by 180 degrees +function Base.rot180(env::CTMRGEnv{C,T}) where {C,T} + # Initialize rotated corners & edges with rotated sizes + corners′ = Zygote.Buffer( + Array{C,3}(undef, 4, size(env.corners, 2), size(env.corners, 3)) + ) + edges′ = Zygote.Buffer(Array{T,3}(undef, 4, size(env.edges, 2), size(env.edges, 3))) + for dir in 1:4 + dir2 = _next(_next(dir, 4), 4) + corners′[dir2, :, :] = rot180(env.corners[dir, :, :]) + edges′[dir2, :, :] = rot180(env.edges[dir, :, :]) + end return CTMRGEnv(copy(corners′), copy(edges′)) end -Base.eltype(env::CTMRGEnv) = eltype(env.corners[1]) Base.axes(x::CTMRGEnv, args...) = axes(x.corners, args...) function eachcoordinate(x::CTMRGEnv) return collect(Iterators.product(axes(x, 2), axes(x, 3))) diff --git a/src/environments/transferpeps_environments.jl b/src/environments/transferpeps_environments.jl index 4332c232..316eaf77 100644 --- a/src/environments/transferpeps_environments.jl +++ b/src/environments/transferpeps_environments.jl @@ -120,8 +120,7 @@ function MPSKit.transfer_spectrum( @assert size(below) == size(O) numrows = size(above, 1) - envtype = eltype(init[1]) - eigenvals = Vector{Vector{scalartype(envtype)}}(undef, numrows) + eigenvals = Vector{Vector{scalartype(init[1])}}(undef, numrows) @threads for cr in 1:numrows L0, = init[cr] diff --git a/src/operators/infinitepepo.jl b/src/operators/infinitepepo.jl index e136aa70..6fad9fd3 100644 --- a/src/operators/infinitepepo.jl +++ b/src/operators/infinitepepo.jl @@ -109,12 +109,13 @@ end Base.size(T::InfinitePEPO) = size(T.A) Base.size(T::InfinitePEPO, i) = size(T.A, i) Base.length(T::InfinitePEPO) = length(T.A) -Base.eltype(T::InfinitePEPO) = eltype(T.A) -VectorInterface.scalartype(T::InfinitePEPO) = scalartype(T.A) +Base.eltype(T::InfinitePEPO) = eltype(typeof(T)) +Base.eltype(::Type{<:InfinitePEPO{T}}) where {T} = T +VectorInterface.scalartype(::Type{T}) where {T<:InfinitePEPO} = scalartype(eltype(T)) ## Copy Base.copy(T::InfinitePEPO) = InfinitePEPO(copy(T.A)) -Base.similar(T::InfinitePEPO) = InfinitePEPO(similar(T.A)) +Base.similar(T::InfinitePEPO, args...) = InfinitePEPO(similar(T.A, args...)) Base.repeat(T::InfinitePEPO, counts...) = InfinitePEPO(repeat(T.A, counts...)) Base.getindex(T::InfinitePEPO, args...) = Base.getindex(T.A, args...) diff --git a/src/operators/lattices/squarelattice.jl b/src/operators/lattices/squarelattice.jl index 097a4f7b..a2f3d9b5 100644 --- a/src/operators/lattices/squarelattice.jl +++ b/src/operators/lattices/squarelattice.jl @@ -12,6 +12,8 @@ struct InfiniteSquare <: AbstractLattice{2} end end +Base.size(lattice::InfiniteSquare) = (lattice.Nrows, lattice.Ncols) + function vertices(lattice::InfiniteSquare) return CartesianIndices((1:(lattice.Nrows), 1:(lattice.Ncols))) end diff --git a/src/operators/localoperator.jl b/src/operators/localoperator.jl index 2698ce8c..90845de0 100644 --- a/src/operators/localoperator.jl +++ b/src/operators/localoperator.jl @@ -108,3 +108,103 @@ end Base.:-(O::LocalOperator) = -1 * O Base.:-(O1::LocalOperator, O2::LocalOperator) = O1 + (-O2) + +# Rotation and mirroring +# ---------------------- + +""" + _mirror_antidiag_site( + site::S, (Nrow, Ncol)::NTuple{2,Int} + ) where {S<:Union{CartesianIndex{2},NTuple{2,Int}}} + +Get the position of `site` after reflection about the anti-diagonal line. +""" +function _mirror_antidiag_site( + site::S, (Nrow, Ncol)::NTuple{2,Int} +) where {S<:Union{CartesianIndex{2},NTuple{2,Int}}} + r, c = site[1], site[2] + return CartesianIndex(1 - c + Ncol, 1 - r + Nrow) +end + +""" + _rotr90_site( + site::S, (Nrow, Ncol)::NTuple{2,Int} + ) where {S<:Union{CartesianIndex{2},NTuple{2,Int}}} + +Get the position of `site` after clockwise (right) rotation by 90 degrees. +""" +function _rotr90_site( + site::S, (Nrow, Ncol)::NTuple{2,Int} +) where {S<:Union{CartesianIndex{2},NTuple{2,Int}}} + r, c = site[1], site[2] + return CartesianIndex(c, 1 + Nrow - r) +end + +""" + _rotl90_site( + site::S, (Nrow, Ncol)::NTuple{2,Int} + ) where {S<:Union{CartesianIndex{2},NTuple{2,Int}}} + +Get the position of `site` after counter-clockwise (left) rotation by 90 degrees. +""" +function _rotl90_site( + site::S, (Nrow, Ncol)::NTuple{2,Int} +) where {S<:Union{CartesianIndex{2},NTuple{2,Int}}} + r, c = site[1], site[2] + return CartesianIndex(1 + Ncol - c, r) +end + +""" + _rot180_site( + site::S, (Nrow, Ncol)::NTuple{2,Int} + ) where {S<:Union{CartesianIndex{2},NTuple{2,Int}}} + +Get the position of `site` after rotation by 180 degrees. +""" +function _rot180_site( + site::S, (Nrow, Ncol)::NTuple{2,Int} +) where {S<:Union{CartesianIndex{2},NTuple{2,Int}}} + r, c = site[1], site[2] + return CartesianIndex(1 + Nrow - r, 1 + Ncol - c) +end + +""" + mirror_antidiag(H::LocalOperator) + +Mirror a `LocalOperator` across the anti-diagonal axis of its lattice. +""" +function mirror_antidiag(H::LocalOperator) + lattice2 = mirror_antidiag(H.lattice) + terms2 = ( + (Tuple(_mirror_antidiag_site(site, size(H.lattice)) for site in sites) => op) for + (sites, op) in H.terms + ) + return LocalOperator(lattice2, terms2...) +end + +function Base.rotr90(H::LocalOperator) + lattice2 = rotr90(H.lattice) + terms2 = ( + (Tuple(_rotr90_site(site, size(H.lattice)) for site in sites) => op) for + (sites, op) in H.terms + ) + return LocalOperator(lattice2, terms2...) +end + +function Base.rotl90(H::LocalOperator) + lattice2 = rotl90(H.lattice) + terms2 = ( + (Tuple(_rotl90_site(site, size(H.lattice)) for site in sites) => op) for + (sites, op) in H.terms + ) + return LocalOperator(lattice2, terms2...) +end + +function Base.rot180(H::LocalOperator) + lattice2 = rot180(H.lattice) + terms2 = ( + (Tuple(_rot180_site(site, size(H.lattice)) for site in sites) => op) for + (sites, op) in H.terms + ) + return LocalOperator(lattice2, terms2...) +end diff --git a/src/operators/models.jl b/src/operators/models.jl index 324397ff..1ca82cd3 100644 --- a/src/operators/models.jl +++ b/src/operators/models.jl @@ -1,5 +1,13 @@ ## Model Hamiltonians # ------------------- +""" + nearest_neighbour_hamiltonian( + lattice::Matrix{S}, h::AbstractTensorMap{S,2,2} + ) where {S} + +Create a nearest neighbor `LocalOperator` by specifying the 2-site interaction term `h` +which acts both in horizontal and vertical direction. +""" function nearest_neighbour_hamiltonian( lattice::Matrix{S}, h::AbstractTensorMap{S,2,2} ) where {S} @@ -54,7 +62,7 @@ end """ j1_j2([elt::Type{T}], [symm::Type{S}], [lattice::InfiniteSquare]; - J1=1.0, J2=1.0, spin=1//2, sublattice=true) + J1=1.0, J2=1.0, spin=1//2, sublattice=true) Square lattice J₁-J₂ model. The `sublattice` kwarg enables a single site unit cell via a sublattice rotation. @@ -122,3 +130,52 @@ function pwave_superconductor( (neighbor => hy for neighbor in y_neighbors)..., ) end + +function MPSKitModels.hubbard_model( + T::Type{<:Number}, + particle_symmetry::Type{<:Sector}, + spin_symmetry::Type{<:Sector}, + lattice::InfiniteSquare; + t=1.0, + U=1.0, + mu=0.0, + n::Integer=0, +) + @assert n == 0 "Currently no support for imposing a fixed particle number" + N = MPSKitModels.e_number(T, particle_symmetry, spin_symmetry) + pspace = space(N, 1) + unit = TensorKit.id(pspace) + hopping = + MPSKitModels.e⁺e⁻(T, particle_symmetry, spin_symmetry) + + MPSKitModels.e⁻e⁺(T, particle_symmetry, spin_symmetry) + interaction_term = MPSKitModels.nꜛnꜜ(T, particle_symmetry, spin_symmetry) + site_term = U * interaction_term - mu * N + h = (-t) * hopping + (1 / 4) * (site_term ⊗ unit + unit ⊗ site_term) + return nearest_neighbour_hamiltonian(fill(pspace, size(lattice)), h) +end + +function MPSKitModels.tj_model( + T::Type{<:Number}, + particle_symmetry::Type{<:Sector}, + spin_symmetry::Type{<:Sector}, + lattice::InfiniteSquare; + t=2.5, + J=1.0, + mu=0.0, + slave_fermion::Bool=false, +) + hopping = + TJOperators.e_plusmin(particle_symmetry, spin_symmetry; slave_fermion) + + TJOperators.e_minplus(particle_symmetry, spin_symmetry; slave_fermion) + num = TJOperators.e_number(particle_symmetry, spin_symmetry; slave_fermion) + heis = + TJOperators.S_exchange(particle_symmetry, spin_symmetry; slave_fermion) - + (1 / 4) * (num ⊗ num) + pspace = space(num, 1) + unit = TensorKit.id(pspace) + h = (-t) * hopping + J * heis - (mu / 4) * (num ⊗ unit + unit ⊗ num) + if T <: Real + h = real(h) + end + return nearest_neighbour_hamiltonian(fill(pspace, size(lattice)), h) +end diff --git a/src/states/infinitepeps.jl b/src/states/infinitepeps.jl index 51b353d4..fea4ab1f 100644 --- a/src/states/infinitepeps.jl +++ b/src/states/infinitepeps.jl @@ -113,12 +113,13 @@ end Base.size(T::InfinitePEPS) = size(T.A) Base.size(T::InfinitePEPS, i) = size(T.A, i) Base.length(T::InfinitePEPS) = length(T.A) -Base.eltype(T::InfinitePEPS) = eltype(T.A) -VectorInterface.scalartype(T::InfinitePEPS) = scalartype(T.A) +Base.eltype(T::InfinitePEPS) = eltype(typeof(T)) +Base.eltype(::Type{<:InfinitePEPS{T}}) where {T} = T +VectorInterface.scalartype(::Type{T}) where {T<:InfinitePEPS} = scalartype(eltype(T)) ## Copy Base.copy(T::InfinitePEPS) = InfinitePEPS(copy(T.A)) -Base.similar(T::InfinitePEPS, args...) = InfinitePEPS(similar.(T.A, args...)) +Base.similar(T::InfinitePEPS, args...) = InfinitePEPS(similar(T.A, args...)) Base.repeat(T::InfinitePEPS, counts...) = InfinitePEPS(repeat(T.A, counts...)) Base.getindex(T::InfinitePEPS, args...) = Base.getindex(T.A, args...) diff --git a/src/states/infiniteweightpeps.jl b/src/states/infiniteweightpeps.jl new file mode 100644 index 00000000..91303bed --- /dev/null +++ b/src/states/infiniteweightpeps.jl @@ -0,0 +1,223 @@ + +""" + const PEPSWeight{S} + +Default type for PEPS bond weights with 2 virtual indices, conventionally ordered as: ``wt : WS ← EN``. +`WS`, `EN` denote the west/south, east/north spaces for x/y-weights on the square lattice, respectively. +""" +const PEPSWeight{S} = AbstractTensorMap{S,1,1} where {S<:ElementarySpace} + +""" + struct SUWeight{E<:PEPSWeight} + +Schmidt bond weights used in simple/cluster update. +Weight elements are always real. +""" +struct SUWeight{E<:PEPSWeight} + data::Array{E,3} + + function SUWeight(data::Array{E,3}) where {E<:PEPSWeight} + @assert eltype(data[1]) <: Real + return new{E}(data) + end +end + +function SUWeight(wts_mats::AbstractMatrix{E}...) where {E<:PEPSWeight} + n_mat = length(wts_mats) + Nr, Nc = size(wts_mats[1]) + @assert all((Nr, Nc) == size(wts_mat) for wts_mat in wts_mats) + weights = collect( + wts_mats[d][r, c] for (d, r, c) in Iterators.product(1:n_mat, 1:Nr, 1:Nc) + ) + return SUWeight(weights) +end + +## Shape and size +Base.size(W::SUWeight) = size(W.data) +Base.size(W::SUWeight, i) = size(W.data, i) +Base.length(W::SUWeight) = length(W.data) +Base.eltype(W::SUWeight) = eltype(typeof(W)) +Base.eltype(::Type{SUWeight{E}}) where {E} = E +VectorInterface.scalartype(::Type{T}) where {T<:SUWeight} = scalartype(eltype(T)) + +Base.getindex(W::SUWeight, args...) = Base.getindex(W.data, args...) +Base.setindex!(W::SUWeight, args...) = (Base.setindex!(W.data, args...); W) +Base.axes(W::SUWeight, args...) = axes(W.data, args...) + +function compare_weights(wts1::SUWeight, wts2::SUWeight) + @assert size(wts1) == size(wts2) + return sum(_singular_value_distance, zip(wts1.data, wts2.data)) / length(wts1) +end + +""" + struct InfiniteWeightPEPS{T<:PEPSTensor,E<:PEPSWeight} <: AbstractPEPS + +Represents an infinite projected entangled-pair state on a 2D square lattice +consisting of vertex tensors and bond weights. +""" +struct InfiniteWeightPEPS{T<:PEPSTensor,E<:PEPSWeight} <: AbstractPEPS + vertices::Matrix{T} + weights::SUWeight{E} + + function InfiniteWeightPEPS( + vertices::Matrix{T}, weights::SUWeight{E} + ) where {T<:PEPSTensor,E<:PEPSWeight} + @assert size(vertices) == size(weights)[2:end] + Nr, Nc = size(vertices) + for (r, c) in Iterators.product(1:Nr, 1:Nc) + space(weights[2, r, c], 1)' == space(vertices[r, c], 2) || throw( + SpaceMismatch("South space of bond weight y$((r, c)) does not match.") + ) + space(weights[2, r, c], 2)' == space(vertices[_prev(r, Nr), c], 4) || throw( + SpaceMismatch("North space of bond weight y$((r, c)) does not match.") + ) + space(weights[1, r, c], 1)' == space(vertices[r, c], 3) || + throw(SpaceMismatch("West space of bond weight x$((r, c)) does not match.")) + space(weights[1, r, c], 2)' == space(vertices[r, _next(c, Nc)], 5) || + throw(SpaceMismatch("East space of bond weight x$((r, c)) does not match.")) + end + return new{T,E}(vertices, weights) + end +end + +""" + InfiniteWeightPEPS( + vertices::Matrix{T}, weight_mats::Matrix{E}... + ) where {T<:PEPSTensor,E<:PEPSWeight} + +Create an InfiniteWeightPEPS from matrices of vertex tensors, +and separate matrices of weights on each type of bond at all locations in the unit cell. +""" +function InfiniteWeightPEPS( + vertices::Matrix{T}, weight_mats::Matrix{E}... +) where {T<:PEPSTensor,E<:PEPSWeight} + return InfiniteWeightPEPS(vertices, SUWeight(weight_mats...)) +end + +""" + InfiniteWeightPEPS( + f, T, Pspace::S, Nspace::S, Espace::S=Nspace; unitcell::Tuple{Int,Int}=(1, 1) + ) where {S<:ElementarySpace} + +Create an InfiniteWeightPEPS by specifying its physical, north and east spaces (as `ElementarySpace`s) and unit cell size. +Use `T` to specify the element type of the vertex tensors. +Bond weights are initialized as identity matrices of element type `Float64`. +""" +function InfiniteWeightPEPS( + f, T, Pspace::S, Nspace::S, Espace::S=Nspace; unitcell::Tuple{Int,Int}=(1, 1) +) where {S<:ElementarySpace} + vertices = InfinitePEPS(f, T, Pspace, Nspace, Espace; unitcell=unitcell).A + Nr, Nc = unitcell + weights = collect( + id(d == 1 ? Espace : Nspace) for (d, r, c) in Iterators.product(1:2, 1:Nr, 1:Nc) + ) + return InfiniteWeightPEPS(vertices, SUWeight(weights)) +end + +""" + absorb_weight(t::T, row::Int, col::Int, ax::Int, weights::SUWeight; + sqrtwt::Bool=false, invwt::Bool=false) where {T<:PEPSTensor} + +Absorb or remove environment weight on axis `ax` of vertex tensor `t` +known to be located at position (`row`, `col`) in the unit cell. +Weights around the tensor at `(row, col)` are +``` + ↓ + [2,r,c] + ↓ + ← [1,r,c-1] ← T[r,c] ← [1,r,c] ← + ↓ + [1,r+1,c] + ↓ +``` + +# Arguments +- `t::T`: The vertex tensor to which the weight will be absorbed. The first axis of `t` should be the physical axis. +- `row::Int`: The row index specifying the position in the tensor network. +- `col::Int`: The column index specifying the position in the tensor network. +- `ax::Int`: The axis along which the weight is absorbed. +- `weights::SUWeight`: The weight object to absorb into the tensor. +- `sqrtwt::Bool=false` (optional): If `true`, the square root of the weight is absorbed. +- `invwt::Bool=false` (optional): If `true`, the inverse of the weight is absorbed. + +# Details +The optional kwargs `sqrtwt` and `invwt` allow taking the square root or the inverse of the weight before absorption. + +# Examples +```julia +# Absorb the weight into the 2nd axis of tensor at position (2, 3) +absorb_weight(t, 2, 3, 2, weights) + +# Absorb the square root of the weight into the tensor +absorb_weight(t, 2, 3, 2, weights; sqrtwt=true) + +# Absorb the inverse of (i.e. remove) the weight into the tensor +absorb_weight(t, 2, 3, 2, weights; invwt=true) +``` +""" +function absorb_weight( + t::T, + row::Int, + col::Int, + ax::Int, + weights::SUWeight; + sqrtwt::Bool=false, + invwt::Bool=false, +) where {T<:PEPSTensor} + Nr, Nc = size(weights)[2:end] + @assert 1 <= row <= Nr && 1 <= col <= Nc + @assert 2 <= ax <= 5 + pow = (sqrtwt ? 1 / 2 : 1) * (invwt ? -1 : 1) + if ax == 2 # north + wt = weights[2, row, col] + elseif ax == 3 # east + wt = weights[1, row, col] + elseif ax == 4 # south + wt = weights[2, _next(row, Nr), col] + else # west + wt = weights[1, row, _prev(col, Nc)] + end + wt2 = sdiag_pow(wt, pow) + indices_t = collect(-1:-1:-5) + indices_t[ax] = 1 + indices_wt = (ax in (2, 3) ? [1, -ax] : [-ax, 1]) + t2 = permute(ncon((t, wt2), (indices_t, indices_wt)), ((1,), Tuple(2:5))) + return t2 +end + +""" + InfinitePEPS(peps::InfiniteWeightPEPS) + +Create `InfinitePEPS` from `InfiniteWeightPEPS` by absorbing bond weights into vertex tensors. +""" +function InfinitePEPS(peps::InfiniteWeightPEPS) + vertices = deepcopy(peps.vertices) + N1, N2 = size(vertices) + for (r, c) in Iterators.product(1:N1, 1:N2) + for ax in 2:5 + vertices[r, c] = absorb_weight( + vertices[r, c], r, c, ax, peps.weights; sqrtwt=true + ) + end + end + return InfinitePEPS(vertices) +end + +function Base.size(peps::InfiniteWeightPEPS) + return size(peps.vertices) +end + +""" + mirror_antidiag(peps::InfiniteWeightPEPS) + +Mirror the unit cell of an iPEPS with weights by its anti-diagonal line. +""" +function mirror_antidiag(peps::InfiniteWeightPEPS) + vertices2 = mirror_antidiag(peps.vertices) + for (i, t) in enumerate(vertices2) + vertices2[i] = permute(t, ((1,), (3, 2, 5, 4))) + end + weights2_x = mirror_antidiag(peps.weights[2, :, :]) + weights2_y = mirror_antidiag(peps.weights[1, :, :]) + return InfiniteWeightPEPS(vertices2, weights2_x, weights2_y) +end diff --git a/src/utility/mirror.jl b/src/utility/mirror.jl new file mode 100644 index 00000000..40626213 --- /dev/null +++ b/src/utility/mirror.jl @@ -0,0 +1,12 @@ +""" + mirror_antidiag(arr::AbstractMatrix) + +Mirror a matrix by its anti-diagonal line (the 45 degree line through the lower-left corner). + +The element originally at [r, c] is moved [Nc-c+1, Nr-r+1], i.e. the element now at [r, c] +was originally at [Nr-c+1, Nc-r+1] +""" +function mirror_antidiag(arr::AbstractMatrix) + Nr, Nc = size(arr) + return collect(arr[Nr - c + 1, Nc - r + 1] for (r, c) in Iterators.product(1:Nc, 1:Nr)) +end diff --git a/src/utility/svd.jl b/src/utility/svd.jl index a590c87a..cfd9a1f9 100644 --- a/src/utility/svd.jl +++ b/src/utility/svd.jl @@ -71,7 +71,7 @@ the iterative SVD didn't converge, the algorithm falls back to a dense SVD. end function random_start_vector(t::Matrix) - return randn(eltype(t), size(t, 1)) + return randn(scalartype(t), size(t, 1)) end # Compute SVD data block-wise using KrylovKit algorithm diff --git a/src/utility/util.jl b/src/utility/util.jl index 0b9fd4c4..45182f1d 100644 --- a/src/utility/util.jl +++ b/src/utility/util.jl @@ -2,7 +2,6 @@ _next(i, total) = mod1(i + 1, total) _prev(i, total) = mod1(i - 1, total) -# iterator over each coordinates """ eachcoordinate(x, dirs=1:4) @@ -21,38 +20,51 @@ function _elementwise_mult(a::AbstractTensorMap, b::AbstractTensorMap) return dst end -# Compute √S⁻¹ for diagonal TensorMaps -_safe_inv(a, tol) = abs(a) < tol ? zero(a) : inv(a) -function sdiag_inv_sqrt(S::AbstractTensorMap; tol::Real=eps(eltype(S))^(3 / 4)) - tol *= norm(S, Inf) # Relative tol w.r.t. largest singular value (use norm(∘, Inf) to make differentiable) - invsq = similar(S) +_safe_pow(a, pow, tol) = (pow < 0 && abs(a) < tol) ? zero(a) : a^pow + +""" + sdiag_pow(S::AbstractTensorMap, pow::Real; tol::Real=eps(scalartype(S))^(3 / 4)) - if sectortype(S) == Trivial +Compute `S^pow` for diagonal matrices `S`. +""" +function sdiag_pow(S::AbstractTensorMap, pow::Real; tol::Real=eps(scalartype(S))^(3 / 4)) + tol *= norm(S, Inf) # Relative tol w.r.t. largest singular value (use norm(∘, Inf) to make differentiable) + Spow = similar(S) + for (k, b) in blocks(S) copyto!( - invsq.data, - LinearAlgebra.diagm(_safe_inv.(LinearAlgebra.diag(S.data), tol) .^ (1 / 2)), + blocks(Spow)[k], + LinearAlgebra.diagm(_safe_pow.(LinearAlgebra.diag(b), pow, tol)), ) - else - for (k, b) in blocks(S) - copyto!( - blocks(invsq)[k], - LinearAlgebra.diagm(_safe_inv.(LinearAlgebra.diag(b), tol) .^ (1 / 2)), - ) - end end + return Spow +end - return invsq +""" + absorb_s(u::AbstractTensorMap, s::AbstractTensorMap, vh::AbstractTensorMap) + +Given `tsvd` result `u`, `s` and `vh`, absorb singular values `s` into `u` and `vh` by: +``` + u -> u * sqrt(s), vh -> sqrt(s) * vh +``` +""" +function absorb_s(u::AbstractTensorMap, s::AbstractTensorMap, vh::AbstractTensorMap) + sqrt_s = sdiag_pow(s, 0.5) + return u * sqrt_s, sqrt_s * vh end function ChainRulesCore.rrule( - ::typeof(sdiag_inv_sqrt), S::AbstractTensorMap; tol::Real=eps(eltype(S))^(3 / 4) + ::typeof(sdiag_pow), + S::AbstractTensorMap, + pow::Real; + tol::Real=eps(scalartype(S))^(3 / 4), ) tol *= norm(S, Inf) - invsq = sdiag_inv_sqrt(S; tol) - function sdiag_inv_sqrt_pullback(c̄) - return (ChainRulesCore.NoTangent(), -1 / 2 * _elementwise_mult(c̄, invsq'^3)) + spow = sdiag_pow(S, pow; tol) + spow_minus1_conj = scale!(sdiag_pow(S', pow - 1; tol), pow) + function sdiag_pow_pullback(c̄) + return (ChainRulesCore.NoTangent(), _elementwise_mult(c̄, spow_minus1_conj)) end - return invsq, sdiag_inv_sqrt_pullback + return spow, sdiag_pow_pullback end # Check whether diagonals contain degenerate values up to absolute or relative tolerance diff --git a/test/heisenberg.jl b/test/heisenberg.jl index 1c05b084..a8bbc426 100644 --- a/test/heisenberg.jl +++ b/test/heisenberg.jl @@ -1,40 +1,104 @@ using Test using Random +using Accessors using PEPSKit using TensorKit using KrylovKit using OptimKit # initialize parameters -χbond = 2 +Dbond = 2 χenv = 16 ctm_alg = CTMRG() opt_alg = PEPSOptimize(; boundary_alg=ctm_alg, optimizer=LBFGS(4; gradtol=1e-3, verbosity=2) ) +# compare against Juraj Hasik's data: +# https://github.com/jurajHasik/j1j2_ipeps_states/blob/main/single-site_pg-C4v-A1/j20.0/state_1s_A1_j20.0_D2_chi_opt48.dat +E_ref = -0.6602310934799577 -# initialize states -Random.seed!(91283219347) -H = heisenberg_XYZ(InfiniteSquare()) -psi_init = InfinitePEPS(2, χbond) -env_init = leading_boundary(CTMRGEnv(psi_init, ComplexSpace(χenv)), psi_init, ctm_alg) - -# find fixedpoint -result = fixedpoint(psi_init, H, opt_alg, env_init) -ξ_h, ξ_v, = correlation_length(result.peps, result.env) - -@test result.E ≈ -0.6694421 atol = 1e-2 -@test all(@. ξ_h > 0 && ξ_v > 0) - -# same test but for 1x2 unit cell -unitcell = (1, 2) -H_1x2 = heisenberg_XYZ(InfiniteSquare(unitcell...)) -psi_init_1x2 = InfinitePEPS(2, χbond; unitcell) -env_init_1x2 = leading_boundary( - CTMRGEnv(psi_init_1x2, ComplexSpace(χenv)), psi_init_1x2, ctm_alg -) -result_1x2 = fixedpoint(psi_init_1x2, H_1x2, opt_alg, env_init_1x2) -ξ_h_1x2, ξ_v_1x2, = correlation_length(result_1x2.peps, result_1x2.env) +@testset "(1, 1) unit cell AD optimization" begin + # initialize states + Random.seed!(123) + H = heisenberg_XYZ(InfiniteSquare()) + psi_init = InfinitePEPS(2, Dbond) + env_init = leading_boundary(CTMRGEnv(psi_init, ComplexSpace(χenv)), psi_init, ctm_alg) + + # optimize energy and compute correlation lengths + result = fixedpoint(psi_init, H, opt_alg, env_init) + ξ_h, ξ_v, = correlation_length(result.peps, result.env) + + @test result.E ≈ E_ref atol = 1e-2 + @test all(@. ξ_h > 0 && ξ_v > 0) +end + +@testset "(1, 2) unit cell AD optimization" begin + # initialize states + Random.seed!(456) + unitcell = (1, 2) + H_1x2 = heisenberg_XYZ(InfiniteSquare(unitcell...)) + psi_init_1x2 = InfinitePEPS(2, Dbond; unitcell) + env_init_1x2 = leading_boundary( + CTMRGEnv(psi_init_1x2, ComplexSpace(χenv)), psi_init_1x2, ctm_alg + ) + + # optimize energy and compute correlation lengths + result_1x2 = fixedpoint(psi_init_1x2, H_1x2, opt_alg, env_init_1x2) + ξ_h_1x2, ξ_v_1x2, = correlation_length(result_1x2.peps, result_1x2.env) + + @test result_1x2.E ≈ 2 * E_ref atol = 1e-2 + @test all(@. ξ_h_1x2 > 0 && ξ_v_1x2 > 0) +end + +@testset "Simple update into AD optimization" begin + # random initialization of 2x2 iPEPS with weights and CTMRGEnv (using real numbers) + Random.seed!(789) + N1, N2 = 2, 2 + Pspace = ℂ^2 + Vspace = ℂ^Dbond + Espace = ℂ^χenv + peps = InfiniteWeightPEPS(rand, Float64, Pspace, Vspace; unitcell=(N1, N2)) + + # normalize vertex tensors + for ind in CartesianIndices(peps.vertices) + peps.vertices[ind] /= norm(peps.vertices[ind], Inf) + end + # Heisenberg model Hamiltonian (already only includes nearest neighbor terms) + ham = heisenberg_XYZ(InfiniteSquare(N1, N2); Jx=1.0, Jy=1.0, Jz=1.0) + # convert to real tensors + ham = LocalOperator(ham.lattice, Tuple(ind => real(op) for (ind, op) in ham.terms)...) + + # simple update + dts = [1e-2, 1e-3, 4e-4, 1e-4] + tols = [1e-7, 1e-8, 1e-8, 1e-8] + maxiter = 5000 + for (n, (dt, tol)) in enumerate(zip(dts, tols)) + Dbond2 = (n == 1) ? Dbond + 2 : Dbond + trscheme = truncerr(1e-10) & truncdim(Dbond2) + alg = SimpleUpdate(dt, tol, maxiter, trscheme) + result = simpleupdate(peps, ham, alg; bipartite=false) + peps = result[1] + end + + # absorb weight into site tensors and CTMRG + peps = InfinitePEPS(peps) + envs = CTMRGEnv(rand, Float64, peps, Espace) + trscheme = truncerr(1e-9) & truncdim(χenv) + envs = leading_boundary(envs, peps, CTMRG(; trscheme, ctmrgscheme=:sequential)) + + # measure physical quantities + e_site = costfun(peps, envs, ham) / (N1 * N2) + @info "Simple update energy = $e_site" + # benchmark data from Phys. Rev. B 94, 035133 (2016) + @test isapprox(e_site, -0.6594; atol=1e-3) -@test result_1x2.E ≈ 2 * result.E atol = 1e-2 -@test all(@. ξ_h_1x2 > 0 && ξ_v_1x2 > 0) + # continue with auto differentiation + svd_alg_gmres = SVDAdjoint(; rrule_alg=GMRES(; tol=1e-8)) + opt_alg_gmres = @set opt_alg.boundary_alg.projector_alg.svd_alg = svd_alg_gmres + result = fixedpoint(peps, ham, opt_alg_gmres, envs) # sensitivity warnings and degeneracies due to SU(2)? + ξ_h, ξ_v, = correlation_length(result.peps, result.env) + e_site2 = result.E / (N1 * N2) + @info "Auto diff energy = $e_site2" + @test e_site2 ≈ E_ref atol = 1e-2 + @test all(@. ξ_h > 0 && ξ_v > 0) +end diff --git a/test/runtests.jl b/test/runtests.jl index 07a4cd2f..b15ad1c6 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -53,7 +53,7 @@ end @time @safetestset "Heisenberg model" begin include("heisenberg.jl") end - @time @safetestset "Heisenberg model" begin + @time @safetestset "J1-J2 model" begin include("j1j2_model.jl") end @time @safetestset "P-wave superconductor" begin From 1c4800c7035ee04590e6013012f7043663b66d1e Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Thu, 12 Dec 2024 15:32:33 +0100 Subject: [PATCH 201/213] Fix formatting --- src/environments/ctmrg_environments.jl | 1 - 1 file changed, 1 deletion(-) diff --git a/src/environments/ctmrg_environments.jl b/src/environments/ctmrg_environments.jl index e40f2c0e..97d890de 100644 --- a/src/environments/ctmrg_environments.jl +++ b/src/environments/ctmrg_environments.jl @@ -418,7 +418,6 @@ function Base.rot180(env::CTMRGEnv{C,T}) where {C,T} return CTMRGEnv(copy(corners′), copy(edges′)) end - # Functions used for FP differentiation and by KrylovKit.linsolve function Base.:+(e₁::CTMRGEnv, e₂::CTMRGEnv) return CTMRGEnv(e₁.corners + e₂.corners, e₁.edges + e₂.edges) From 77a660ecd67467e7515b70148caa1935c040fe7b Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Tue, 17 Dec 2024 18:29:41 +0100 Subject: [PATCH 202/213] Fix OhMyThreads precompilation warning --- src/PEPSKit.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PEPSKit.jl b/src/PEPSKit.jl index 74d68e03..e22c5538 100644 --- a/src/PEPSKit.jl +++ b/src/PEPSKit.jl @@ -11,7 +11,7 @@ using LoggingExtras using MPSKit: loginit!, logiter!, logfinish!, logcancel! using MPSKitModels using FiniteDifferences -using OhMyThreads +using OhMyThreads: tmap include("utility/util.jl") include("utility/diffable_threads.jl") From 60119e819f11d569764ee25b8a0783ab58076350 Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Thu, 19 Dec 2024 13:57:40 +0800 Subject: [PATCH 203/213] Fix FullInfiniteProjector --- src/algorithms/ctmrg/ctmrg.jl | 12 ++++++++++++ src/algorithms/ctmrg/projectors.jl | 27 ++++++++++++++++----------- 2 files changed, 28 insertions(+), 11 deletions(-) diff --git a/src/algorithms/ctmrg/ctmrg.jl b/src/algorithms/ctmrg/ctmrg.jl index ac5444f2..a6c23a5a 100644 --- a/src/algorithms/ctmrg/ctmrg.jl +++ b/src/algorithms/ctmrg/ctmrg.jl @@ -110,4 +110,16 @@ function calc_convergence(envs, CSold, TSold) return max(ΔCS, ΔTS), CSnew, TSnew end +""" + calc_convergence(envsNew::CTMRGEnv, envsOld::CTMRGEnv) + +Calculate convergence of CTMRG by computing +the maximal singular value distance of new and old CTM tensors. +""" +function calc_convergence(envsNew::CTMRGEnv, envsOld::CTMRGEnv) + CSOld = map(x -> tsvd(x)[2], envsOld.corners) + TSOld = map(x -> tsvd(x)[2], envsOld.edges) + return calc_convergence(envsNew, CSOld, TSOld) +end + @non_differentiable calc_convergence(args...) diff --git a/src/algorithms/ctmrg/projectors.jl b/src/algorithms/ctmrg/projectors.jl index 7d7ca4aa..daaa5323 100644 --- a/src/algorithms/ctmrg/projectors.jl +++ b/src/algorithms/ctmrg/projectors.jl @@ -67,7 +67,6 @@ function compute_projector(enlarged_corners, coordinate, alg::HalfInfiniteProjec halfinf = half_infinite_environment(enlarged_corners...) svd_alg = svd_algorithm(alg, coordinate) U, S, V, err = PEPSKit.tsvd!(halfinf, svd_alg; trunc=alg.trscheme) - # Compute SVD truncation error and check for degenerate singular values Zygote.isderiving() && ignore_derivatives() do if alg.verbosity > 0 && is_degenerate_spectrum(S) @@ -75,22 +74,29 @@ function compute_projector(enlarged_corners, coordinate, alg::HalfInfiniteProjec @warn("degenerate singular values detected: ", svals) end end - P_left, P_right = contract_projectors(U, S, V, enlarged_corners...) return (P_left, P_right), (; err, U, S, V) end function compute_projector(enlarged_corners, coordinate, alg::FullInfiniteProjector) - # QR left and right half-infinite environments + # find half-infinite environments halfinf_left = half_infinite_environment(enlarged_corners[1], enlarged_corners[2]) halfinf_right = half_infinite_environment(enlarged_corners[3], enlarged_corners[4]) - _, R_left = leftorth!(halfinf_left) - L_right, _ = rightorth!(halfinf_right) - - # SVD product of QRs - fullinf = R_left * L_right + #= Combine two halves to form the full-infinite projector + + |---|- χL χR -|---| + | |= DL DR =| | + | L | | R | + | |=====D=====| | + |---|-----χ-----|---| + + D_0/D_1 comes from the ket (above) / bra (below) state + =# + @autoopt @tensor fullinf[χL DL0 DL1; χR DR0 DR1] := ( + halfinf_left[χL DL0 DL1; χ D0 D1] * halfinf_right[χ D0 D1; χR DR0 DR1] + ) + # SVD full-infinite environment svd_alg = svd_algorithm(alg, coordinate) U, S, V, err = PEPSKit.tsvd!(fullinf, svd_alg; trunc=alg.trscheme) - # Compute SVD truncation error and check for degenerate singular values Zygote.isderiving() && ignore_derivatives() do if alg.verbosity > 0 && is_degenerate_spectrum(S) @@ -98,7 +104,6 @@ function compute_projector(enlarged_corners, coordinate, alg::FullInfiniteProjec @warn("degenerate singular values detected: ", svals) end end - - P_left, P_right = contract_projectors(U, S, V, R_left, L_right) + P_left, P_right = contract_projectors(U, S, V, halfinf_left, halfinf_right) return (P_left, P_right), (; err, U, S, V) end From 827524fb811fecff75e4275e55a5bc46365fce92 Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Thu, 19 Dec 2024 17:22:32 +0800 Subject: [PATCH 204/213] Fix simultaneous CTMRG pre-allocation --- src/algorithms/ctmrg/simultaneous.jl | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/algorithms/ctmrg/simultaneous.jl b/src/algorithms/ctmrg/simultaneous.jl index fcae797b..16a69ab4 100644 --- a/src/algorithms/ctmrg/simultaneous.jl +++ b/src/algorithms/ctmrg/simultaneous.jl @@ -40,21 +40,13 @@ function ctmrg_iteration(state, envs::CTMRGEnv, alg::SimultaneousCTMRG) end # Pre-allocate U, S, and V tensor as Zygote buffers to make it differentiable -function _prealloc_svd(edges::Array{E,N}, ::HalfInfiniteProjector) where {E,N} +function _prealloc_svd(edges::Array{E,N}, ::ProjectorAlgorithm) where {E,N} Sc = scalartype(E) U = Zygote.Buffer(map(e -> TensorMap(zeros, Sc, space(e)), edges)) V = Zygote.Buffer(map(e -> TensorMap(zeros, Sc, domain(e), codomain(e)), edges)) S = Zygote.Buffer(U.data, tensormaptype(spacetype(E), 1, 1, real(Sc))) # Corner type but with real numbers return U, S, V end -function _prealloc_svd(edges::Array{E,N}, ::FullInfiniteProjector) where {E,N} - Sc = scalartype(E) - Rspace(x) = fuse(codomain(x)) - U = Zygote.Buffer(map(e -> TensorMap(zeros, Sc, Rspace(e), domain(e)), edges)) - V = Zygote.Buffer(map(e -> TensorMap(zeros, Sc, domain(e), Rspace(e)), edges)) - S = Zygote.Buffer(U.data, tensormaptype(spacetype(E), 1, 1, real(Sc))) # Corner type but with real numbers - return U, S, V -end """ simultaneous_projectors(enlarged_corners::Array{E,3}, envs::CTMRGEnv, alg::ProjectorAlgorithm) From 6b32ecc942ee0d3560824a715283e4210a47ab16 Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Thu, 19 Dec 2024 12:28:12 +0100 Subject: [PATCH 205/213] Adjust naming and cosmetics --- .../contractions/ctmrg_contractions.jl | 10 ++++++ src/algorithms/ctmrg/ctmrg.jl | 36 ++++++++----------- src/algorithms/ctmrg/projectors.jl | 17 +-------- src/algorithms/ctmrg/simultaneous.jl | 4 +-- 4 files changed, 28 insertions(+), 39 deletions(-) diff --git a/src/algorithms/contractions/ctmrg_contractions.jl b/src/algorithms/contractions/ctmrg_contractions.jl index 2fc43bdd..7bfa06b6 100644 --- a/src/algorithms/contractions/ctmrg_contractions.jl +++ b/src/algorithms/contractions/ctmrg_contractions.jl @@ -350,6 +350,16 @@ function half_infinite_environment( conj(E_4[χ6 D9 D10; χ_in]) end +function full_infinite_environment( + quadrant1::Q, quadrant2::Q, quadrant3::Q, quadrant4::Q +) where {Q<:AbstractTensorMap{<:ElementarySpace,3,3}} + return @autoopt @tensor env[χ_in D_inabove D_inbelow; χ_out D_outabove D_outbelow] := + quadrant1[χ_in D_inabove D_inbelow; χ1 D1 D2] * + quadrant2[χ1 D1 D2; χ2 D3 D4] * + quadrant3[χ2 D3 D4; χ3 D5 D6] * + quadrant4[χ3 D5 D6; χ_out D_outabove D_outbelow] +end + # Renormalization contractions # ---------------------------- diff --git a/src/algorithms/ctmrg/ctmrg.jl b/src/algorithms/ctmrg/ctmrg.jl index a6c23a5a..e35abeaf 100644 --- a/src/algorithms/ctmrg/ctmrg.jl +++ b/src/algorithms/ctmrg/ctmrg.jl @@ -93,33 +93,27 @@ function _singular_value_distance((S₁, S₂)) end """ - calc_convergence(envs, CSold, TSold) + calc_convergence(envs, CS_old, TS_old) + calc_convergence(envs_new::CTMRGEnv, envs_old::CTMRGEnv) -Given a new environment `envs` and the singular values of previous corners and edges -`CSold` and `TSold`, compute the maximal singular value distance. +Given a new environment `envs`, compute the maximal singular value distance. +This determined either from the previous corner and edge singular values +`CS_old` and `TS_old`, or alternatively, directly from the old environment. """ -function calc_convergence(envs, CSold, TSold) - CSnew = map(x -> tsvd(x)[2], envs.corners) - ΔCS = maximum(_singular_value_distance, zip(CSold, CSnew)) +function calc_convergence(envs, CS_old, TS_old) + CS_new = map(x -> tsvd(x)[2], envs.corners) + ΔCS = maximum(_singular_value_distance, zip(CS_old, CS_new)) - TSnew = map(x -> tsvd(x)[2], envs.edges) - ΔTS = maximum(_singular_value_distance, zip(TSold, TSnew)) + TS_new = map(x -> tsvd(x)[2], envs.edges) + ΔTS = maximum(_singular_value_distance, zip(TS_old, TS_new)) @debug "maxᵢ|Cⁿ⁺¹ - Cⁿ|ᵢ = $ΔCS maxᵢ|Tⁿ⁺¹ - Tⁿ|ᵢ = $ΔTS" - return max(ΔCS, ΔTS), CSnew, TSnew + return max(ΔCS, ΔTS), CS_new, TS_new end - -""" - calc_convergence(envsNew::CTMRGEnv, envsOld::CTMRGEnv) - -Calculate convergence of CTMRG by computing -the maximal singular value distance of new and old CTM tensors. -""" -function calc_convergence(envsNew::CTMRGEnv, envsOld::CTMRGEnv) - CSOld = map(x -> tsvd(x)[2], envsOld.corners) - TSOld = map(x -> tsvd(x)[2], envsOld.edges) - return calc_convergence(envsNew, CSOld, TSOld) +function calc_convergence(envs_new::CTMRGEnv, envs_old::CTMRGEnv) + CS_old = map(x -> tsvd(x)[2], envs_old.corners) + TS_old = map(x -> tsvd(x)[2], envs_old.edges) + return calc_convergence(envs_new, CS_old, TS_old) end - @non_differentiable calc_convergence(args...) diff --git a/src/algorithms/ctmrg/projectors.jl b/src/algorithms/ctmrg/projectors.jl index daaa5323..70f27023 100644 --- a/src/algorithms/ctmrg/projectors.jl +++ b/src/algorithms/ctmrg/projectors.jl @@ -78,23 +78,8 @@ function compute_projector(enlarged_corners, coordinate, alg::HalfInfiniteProjec return (P_left, P_right), (; err, U, S, V) end function compute_projector(enlarged_corners, coordinate, alg::FullInfiniteProjector) - # find half-infinite environments - halfinf_left = half_infinite_environment(enlarged_corners[1], enlarged_corners[2]) - halfinf_right = half_infinite_environment(enlarged_corners[3], enlarged_corners[4]) - #= Combine two halves to form the full-infinite projector - - |---|- χL χR -|---| - | |= DL DR =| | - | L | | R | - | |=====D=====| | - |---|-----χ-----|---| - - D_0/D_1 comes from the ket (above) / bra (below) state - =# - @autoopt @tensor fullinf[χL DL0 DL1; χR DR0 DR1] := ( - halfinf_left[χL DL0 DL1; χ D0 D1] * halfinf_right[χ D0 D1; χR DR0 DR1] - ) # SVD full-infinite environment + fullinf = full_infinite_environment(enlarged_corners...) svd_alg = svd_algorithm(alg, coordinate) U, S, V, err = PEPSKit.tsvd!(fullinf, svd_alg; trunc=alg.trscheme) # Compute SVD truncation error and check for degenerate singular values diff --git a/src/algorithms/ctmrg/simultaneous.jl b/src/algorithms/ctmrg/simultaneous.jl index 16a69ab4..12d72960 100644 --- a/src/algorithms/ctmrg/simultaneous.jl +++ b/src/algorithms/ctmrg/simultaneous.jl @@ -40,7 +40,7 @@ function ctmrg_iteration(state, envs::CTMRGEnv, alg::SimultaneousCTMRG) end # Pre-allocate U, S, and V tensor as Zygote buffers to make it differentiable -function _prealloc_svd(edges::Array{E,N}, ::ProjectorAlgorithm) where {E,N} +function _prealloc_svd(edges::Array{E,N}) where {E,N} Sc = scalartype(E) U = Zygote.Buffer(map(e -> TensorMap(zeros, Sc, space(e)), edges)) V = Zygote.Buffer(map(e -> TensorMap(zeros, Sc, domain(e), codomain(e)), edges)) @@ -58,7 +58,7 @@ enlarged corners or on a specific `coordinate`. function simultaneous_projectors( enlarged_corners::Array{E,3}, envs::CTMRGEnv, alg::ProjectorAlgorithm ) where {E} - U, S, V = _prealloc_svd(envs.edges, alg) + U, S, V = _prealloc_svd(envs.edges) ϵ = Zygote.Buffer(zeros(real(scalartype(envs)), size(envs))) projectors = dtmap(eachcoordinate(envs, 1:4)) do coordinate From d930649f178d79fd49d1630c7f0cbd5fa01ff9f3 Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Thu, 19 Dec 2024 13:28:50 +0100 Subject: [PATCH 206/213] Add full-infinite contractions and sparse struct --- .../contractions/ctmrg_contractions.jl | 222 +++++++++++++++++- src/algorithms/ctmrg/projectors.jl | 5 +- src/algorithms/ctmrg/sparse_environments.jl | 168 +++++++++++++ 3 files changed, 392 insertions(+), 3 deletions(-) diff --git a/src/algorithms/contractions/ctmrg_contractions.jl b/src/algorithms/contractions/ctmrg_contractions.jl index 7bfa06b6..a6ae00ac 100644 --- a/src/algorithms/contractions/ctmrg_contractions.jl +++ b/src/algorithms/contractions/ctmrg_contractions.jl @@ -350,15 +350,233 @@ function half_infinite_environment( conj(E_4[χ6 D9 D10; χ_in]) end +""" + full_infinite_environment( + quadrant1::T, quadrant2::T, quadrant3::T, quadrant4::T + ) where {T<:AbstractTensorMap{<:ElementarySpace,3,3}} + function full_infinite_environment( + half1::T, half2::T + ) where {T<:AbstractTensorMap{<:ElementarySpace,3,3}} + full_infinite_environment(C_1, C_2, C_3, C_4, E_1, E_2, E_3, E_4, E_5, E_6, E_7, E_8, + ket_1::P, ket_2::P, bra_1::P=ket_1, bra_2::P=ket_2) where {P<:PEPSTensor} + full_infinite_environment(C_1, C_2, E_1, E_2, E_3, E_4, x, + ket_1::P, ket_2::P, ket_3::P, ket_4::P, + bra_1::P=ket_1, bra_2::P=ket_2, bra_3::P=ket_3, bra_4::P=ket_4) where {P<:PEPSTensor} + full_infinite_environment(x, C_1, C_2, E_1, E_2, E_3, E_4, + ket_1::P, ket_2::P, ket_3::P, ket_4::P, + bra_1::P=ket_1, bra_2::P=ket_2, bra_3::P=ket_3, bra_4::P=ket_4) where {P<:PEPSTensor} + +Contract four quadrants (enlarged corners) to form a full-infinite environment. + +``` + |~~~~~~~~~| -- |~~~~~~~~~| + |quadrant1| |quadrant2| + |~~~~~~~~~| == |~~~~~~~~~| + | || || | + || | + | || || | + |~~~~~~~~~| -- |~~~~~~~~~| + |quadrant4| |quadrant3| + |~~~~~~~~~| == |~~~~~~~~~| +``` + +In the same manner two halfs can be used to contract the full-infinite environment. + +``` + |~~~~~~~~~~~~~~~~~~~~~~~~| + | half1 | + |~~~~~~~~~~~~~~~~~~~~~~~~| + | || || | + || | + | || || | + |~~~~~~~~~~~~~~~~~~~~~~~~| + | half2 | + |~~~~~~~~~~~~~~~~~~~~~~~~| +``` + +The environment can also be contracted directly from all its constituent tensors. + +``` + C_1 -- E_2 -- E_3 -- C_2 + | || || | + E_1 == ket_bra_1 == ket_bra_2 == E_4 + | || || | + || | + | || || | + E_8 == ket_bra_4 == ket_bra_3 == E_5 + | || || | + C_4 -- E_7 -- E_6 -- C_3 +``` + +Alternatively, contract the environment with a vector `x` acting on it + +``` + C_1 -- E_2 -- E_3 -- C_2 + | || || | + E_1 == ket_bra_1 == ket_bra_2 == E_4 + | || || | + || | + || | + [~~~~x~~~~~] || | + | || || | + E_8 == ket_bra_4 == ket_bra_3 == E_5 + | || || | + C_4 -- E_7 -- E_6 -- C_3 + +``` + +or contract the adjoint environment with `x`, e.g. as needed for iterative solvers. +""" function full_infinite_environment( - quadrant1::Q, quadrant2::Q, quadrant3::Q, quadrant4::Q -) where {Q<:AbstractTensorMap{<:ElementarySpace,3,3}} + quadrant1::T, quadrant2::T, quadrant3::T, quadrant4::T +) where {T<:AbstractTensorMap{<:ElementarySpace,3,3}} return @autoopt @tensor env[χ_in D_inabove D_inbelow; χ_out D_outabove D_outbelow] := quadrant1[χ_in D_inabove D_inbelow; χ1 D1 D2] * quadrant2[χ1 D1 D2; χ2 D3 D4] * quadrant3[χ2 D3 D4; χ3 D5 D6] * quadrant4[χ3 D5 D6; χ_out D_outabove D_outbelow] end +function full_infinite_environment( + half1::T, half2::T +) where {T<:AbstractTensorMap{<:ElementarySpace,3,3}} + return half_infinite_environment(half1, half2) +end +function full_infinite_environment( + C_1, + C_2, + C_3, + C_4, + E_1, + E_2, + E_3, + E_4, + E_5, + E_6, + E_7, + E_8, + ket_1::P, + ket_2::P, + ket_3::P, + ket_4::P, + bra_1::P=ket_1, + bra_2::P=ket_2, + bra_3::P=ket_3, + bra_4::P=ket_4, +) where {P<:PEPSTensor} + return @autoopt @tensor env[χ_in D_inabove D_inbelow; χ_out D_outabove D_outbelow] := + E_1[χ_in D1 D2; χ1] * + C_1[χ1; χ2] * + E_2[χ2 D3 D4; χ3] * + ket_1[d1; D3 D11 D_inabove D1] * + conj(bra_1[d1; D4 D12 D_inbelow D2]) * + ket_2[d2; D5 D7 D9 D11] * + conj(bra_2[d2; D6 D8 D10 D12]) * + E_3[χ3 D5 D6; χ4] * + C_2[χ4; χ5] * + E_4[χ5 D7 D8; χ6] * + E_5[χ6 D13 D14; χ7] * + C_3[χ7; χ8] * + E_6[χ8 D15 D16; χ9] * + ket_3[d3; D9 D13 D15 D17] * + conj(bra_3[d3; D10 D14 D16 D18]) * + ket_4[d4; D_outabove D17 D19 D21] * + conj(bra_4[d4; D_outbelow D18 D20 D22]) * + E_7[χ9 D19 D20; χ10] * + C_4[χ10; χ11] * + E_8[χ11 D21 D22; χ_out] +end +function full_infinite_environment( + C_1, + C_2, + C_3, + C_4, + E_1, + E_2, + E_3, + E_4, + E_5, + E_6, + E_7, + E_8, + x::AbstractTensor{S,3}, + ket_1::P, + ket_2::P, + ket_3::P, + ket_4::P, + bra_1::P=ket_1, + bra_2::P=ket_2, + bra_3::P=ket_3, + bra_4::P=ket_4, +) where {S,P<:PEPSTensor} + return @autoopt @tensor env_x[χ_in D_inabove D_inbelow] := + E_1[χ_in D1 D2; χ1] * + C_1[χ1; χ2] * + E_2[χ2 D3 D4; χ3] * + ket_1[d1; D3 D11 D_inabove D1] * + conj(bra_1[d1; D4 D12 D_inbelow D2]) * + ket_2[d2; D5 D7 D9 D11] * + conj(bra_2[d2; D6 D8 D10 D12]) * + E_3[χ3 D5 D6; χ4] * + C_2[χ4; χ5] * + E_4[χ5 D7 D8; χ6] * + E_5[χ6 D13 D14; χ7] * + C_3[χ7; χ8] * + E_6[χ8 D15 D16; χ9] * + ket_3[d3; D9 D13 D15 D17] * + conj(bra_3[d3; D10 D14 D16 D18]) * + ket_4[d4; D_xabove D17 D19 D21] * + conj(bra_4[d4; D_xbelow D18 D20 D22]) * + E_7[χ9 D19 D20; χ10] * + C_4[χ10; χ11] * + E_8[χ11 D21 D22; χ_x] * + x[χ_x D_xabove D_xbelow] +end +function full_infinite_environment( + x::AbstractTensor{S,3}, + C_1, + C_2, + C_3, + C_4, + E_1, + E_2, + E_3, + E_4, + E_5, + E_6, + E_7, + E_8, + ket_1::P, + ket_2::P, + ket_3::P, + ket_4::P, + bra_1::P=ket_1, + bra_2::P=ket_2, + bra_3::P=ket_3, + bra_4::P=ket_4, +) where {S,P<:PEPSTensor} + return @autoopt @tensor x_env[χ_in D_inabove D_inbelow] := + x[χ_x D_xabove D_xbelow] * + E_1[χ_x D1 D2; χ1] * + C_1[χ1; χ2] * + E_2[χ2 D3 D4; χ3] * + ket_1[d1; D3 D11 D_xabove D1] * + conj(bra_1[d1; D4 D12 D_xbelow D2]) * + ket_2[d2; D5 D7 D9 D11] * + conj(bra_2[d2; D6 D8 D10 D12]) * + E_3[χ3 D5 D6; χ4] * + C_2[χ4; χ5] * + E_4[χ5 D7 D8; χ6] * + E_5[χ6 D13 D14; χ7] * + C_3[χ7; χ8] * + E_6[χ8 D15 D16; χ9] * + ket_3[d3; D9 D13 D15 D17] * + conj(bra_3[d3; D10 D14 D16 D18]) * + ket_4[d4; D_inabove D17 D19 D21] * + conj(bra_4[d4; D_inbelow D18 D20 D22]) * + E_7[χ9 D19 D20; χ10] * + C_4[χ10; χ11] * + E_8[χ11 D21 D22; χ_in] +end # Renormalization contractions # ---------------------------- diff --git a/src/algorithms/ctmrg/projectors.jl b/src/algorithms/ctmrg/projectors.jl index 70f27023..39aa58ee 100644 --- a/src/algorithms/ctmrg/projectors.jl +++ b/src/algorithms/ctmrg/projectors.jl @@ -78,8 +78,11 @@ function compute_projector(enlarged_corners, coordinate, alg::HalfInfiniteProjec return (P_left, P_right), (; err, U, S, V) end function compute_projector(enlarged_corners, coordinate, alg::FullInfiniteProjector) + halfinf_left = half_infinite_environment(enlarged_corners[1], enlarged_corners[2]) + halfinf_right = half_infinite_environment(enlarged_corners[3], enlarged_corners[4]) + # SVD full-infinite environment - fullinf = full_infinite_environment(enlarged_corners...) + fullinf = full_infinite_environment(halfinf_left, halfinf_right) svd_alg = svd_algorithm(alg, coordinate) U, S, V, err = PEPSKit.tsvd!(fullinf, svd_alg; trunc=alg.trscheme) # Compute SVD truncation error and check for degenerate singular values diff --git a/src/algorithms/ctmrg/sparse_environments.jl b/src/algorithms/ctmrg/sparse_environments.jl index 3e8d388e..1cb3241c 100644 --- a/src/algorithms/ctmrg/sparse_environments.jl +++ b/src/algorithms/ctmrg/sparse_environments.jl @@ -220,3 +220,171 @@ end function random_start_vector(env::HalfInfiniteEnv) return Tensor(randn, domain(env)) end + +# -------------------------------- +# Sparse full-infinite environment +# -------------------------------- + +""" + struct FullInfiniteEnv{C,E,A,A′} + +Full-infinite CTMRG environment tensor storage. +""" +struct FullInfiniteEnv{C,E,A,A′} # TODO: subtype as AbstractTensorMap once TensorKit is updated + C_1::C + C_2::C + C_3::C + C_4::C + E_1::E + E_2::E + E_3::E + E_4::E + E_5::E + E_6::E + E_7::E + E_8::E + ket_1::A + ket_2::A + ket_3::A + ket_4::A + bra_1::A′ + bra_2::A′ + bra_3::A′ + bra_4::A′ +end + +# Construct environment from two enlarged corners +function FullInfiniteEnv( + quadrant1::E, quadrant2::E, quadrant3::E, quadrant4::E +) where {E<:EnlargedCorner} + return FullInfiniteEnv( + quadrant1.C, + quadrant2.C, + quadrant3.C, + quadrant4.C, + quadrant1.E_1, + quadrant1.E_2, + quadrant2.E_1, + quadrant2.E_2, + quadrant3.E_1, + quadrant3.E_2, + quadrant4.E_1, + quadrant4.E_2, + quadrant1.ket, + quadrant2.ket, + quadrant3.ket, + quadrant4.ket, + quadrant1.bra, + quadrant2.bra, + quadrant3.bra, + quadrant4.bra, + ) +end + +""" + TensorKit.TensorMap(env::FullInfiniteEnv) + +Instantiate full-infinite environment as `TensorMap` explicitly. +""" +function TensorKit.TensorMap(env::FullInfiniteEnv) # Dense operator + return full_infinite_environment( + env.C_1, + env.C_2, + env.C_3, + env.C_4, + env.E_1, + env.E_2, + env.E_3, + env.E_4, + env.E_2, + env.E_3, + env.E_4, + env.E_5, + env.ket_1, + env.ket_2, + env.ket_3, + env.ket_4, + env.bra_1, + env.bra_2, + env.bra_3, + env.bra_4, + ) +end + +""" + (env::FullInfiniteEnv)(x, ::Val{false}) + (env::FullInfiniteEnv)(x, ::Val{true}) + +Contract full-infinite environment with a vector `x`, such that the environment acts as a +linear map or adjoint linear map on `x` if `Val(true)` or `Val(false)` is passed, respectively. +""" +function (env::FullInfiniteEnv)(x, ::Val{false}) # Linear map: env() * x + return full_infinite_environment( + env.C_1, + env.C_2, + env.C_3, + env.C_4, + env.E_1, + env.E_2, + env.E_3, + env.E_4, + env.E_5, + env.E_6, + env.E_7, + env.E_8, + x, + env.ket_1, + env.ket_2, + env.ket_3, + env.ket_4, + env.bra_1, + env.bra_2, + env.bra_3, + env.bra_4, + ) +end +function (env::FullInfiniteEnv)(x, ::Val{true}) # Adjoint linear map: env()' * x + return full_infinite_environment( + x, + env.C_1, + env.C_2, + env.C_3, + env.C_4, + env.E_1, + env.E_2, + env.E_3, + env.E_4, + env.E_5, + env.E_6, + env.E_7, + env.E_8, + env.ket_1, + env.ket_2, + env.ket_3, + env.ket_4, + env.bra_1, + env.bra_2, + env.bra_3, + env.bra_4, + ) +end + +# Wrapper around full_infinite_environment contraction using EnlargedCorners (used in ctmrg_projectors) +function full_infinite_environment( + ec_1::E, ec_2::E, ec_3::E, ec_4::E +) where {E<:EnlargedCorner} + return FullInfiniteEnv(ec_1, ec_2, ec_3, ec_4) +end + +# AbstractTensorMap subtyping and IterSVD compatibility +function TensorKit.domain(env::FullInfiniteEnv) + return domain(env.E_8) * domain(env.ket_4)[3] * domain(env.bra_4)[3]' +end + +function TensorKit.codomain(env::FullInfiniteEnv) + return codomain(env.E_1)[1] * domain(env.ket_1)[3]' * domain(env.bra_1)[3] +end + +function random_start_vector(env::FullInfiniteEnv) + return Tensor(randn, domain(env)) +end From 3c2c47bba90939fbe14b0846aa4d3f88155a0d2b Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Thu, 19 Dec 2024 14:07:39 +0100 Subject: [PATCH 207/213] Update gaugefix test --- test/ctmrg/gaugefix.jl | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/ctmrg/gaugefix.jl b/test/ctmrg/gaugefix.jl index 048d875d..13d9a15b 100644 --- a/test/ctmrg/gaugefix.jl +++ b/test/ctmrg/gaugefix.jl @@ -10,6 +10,7 @@ scalartypes = [Float64, ComplexF64] unitcells = [(1, 1), (2, 2), (3, 2)] ctmrg_algs = [SequentialCTMRG, SimultaneousCTMRG] projector_algs = [HalfInfiniteProjector, FullInfiniteProjector] +tol = 1e-6 # large tol due to χ=6 χ = 6 atol = 1e-4 @@ -19,7 +20,7 @@ function _pre_converge_env( Random.seed!(seed) # Seed RNG to make random environment consistent psi = InfinitePEPS(rand, T, physical_space, peps_space; unitcell) env₀ = CTMRGEnv(psi, ctm_space) - env_conv = leading_boundary(env₀, psi, SequentialCTMRG()) + env_conv = leading_boundary(env₀, psi, SequentialCTMRG(; tol)) return env_conv, psi end @@ -41,7 +42,7 @@ end ) in Iterators.product( spacetypes, scalartypes, unitcells, ctmrg_algs, projector_algs ) - alg = ctmrg_alg(; projector_alg) + alg = ctmrg_alg(; tol, projector_alg) env_pre, psi = preconv[(S, T, unitcell)] env_pre env = leading_boundary(env_pre, psi, alg) From 9b43dad514b57014cd0607cdc68fd751eebe9a68 Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Sat, 21 Dec 2024 12:19:51 +0100 Subject: [PATCH 208/213] Fix wrong contrations in ctmrg_contractions.jl --- src/algorithms/contractions/ctmrg_contractions.jl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/algorithms/contractions/ctmrg_contractions.jl b/src/algorithms/contractions/ctmrg_contractions.jl index a6ae00ac..1c024f9a 100644 --- a/src/algorithms/contractions/ctmrg_contractions.jl +++ b/src/algorithms/contractions/ctmrg_contractions.jl @@ -202,8 +202,8 @@ function right_projector(E_1, C, E_2, U, isqS, ket::PEPSTensor, bra::PEPSTensor= return @autoopt @tensor P_right[χ_in; χ_out D_outabove D_outbelow] := isqS[χ_in; χ1] * conj(U[χ1; χ2 D1 D2]) * - ket[d; D1 D5 D_outabove D1] * - conj(bra[d; D2 D6 D_outbelow D2]) * + ket[d; D3 D5 D_outabove D1] * + conj(bra[d; D4 D6 D_outbelow D2]) * E_2[χ2 D3 D4; χ3] * C[χ3; χ4] * E_1[χ4 D5 D6; χ_out] @@ -912,8 +912,8 @@ function renormalize_south_edge( ) return @autoopt @tensor edge[χ_E D_Nab D_Nbe; χ_W] := E_south[χ1 D1 D2; χ2] * - bra[d; D_Nab D5 D1 D3] * - conj(ket[d; D_Nbe D6 D2 D4]) * + ket[d; D_Nbe D6 D2 D4] * + conj(bra[d; D_Nab D5 D1 D3]) * P_left[χ2 D3 D4; χ_W] * P_right[χ_E; χ1 D5 D6] end From f80e2b41f1e052131be381c8d1ab4eed83276cfd Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Sat, 21 Dec 2024 12:29:41 +0100 Subject: [PATCH 209/213] Fix docstrings and renormalize_south_edge labeling --- src/algorithms/contractions/ctmrg_contractions.jl | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/algorithms/contractions/ctmrg_contractions.jl b/src/algorithms/contractions/ctmrg_contractions.jl index 1c024f9a..3654f9f4 100644 --- a/src/algorithms/contractions/ctmrg_contractions.jl +++ b/src/algorithms/contractions/ctmrg_contractions.jl @@ -739,7 +739,7 @@ Alternatively, provide the constituent tensors and perform the complete contract ``` | [~~~~~P_right~~~~~] - || | + || || E_west == ket-bra == |~~~~~~| | || |P_left| -- C_southwest -- E_south -- |~~~~~~| @@ -820,7 +820,7 @@ Absorb a bra-ket pair into the north edge using the given projectors and environ |~~~~~~| -- E_north -- |~~~~~~~| -- |P_left| || |P_right| -- |~~~~~~| == ket-bra == |~~~~~~~| - + || ``` """ function renormalize_north_edge( @@ -890,10 +890,10 @@ end Absorb a bra-ket pair into the south edge using the given projectors and environment tensors. ``` + || |~~~~~~~| == ket-bra == |~~~~~~| -- |P_right| || |P_left| -- |~~~~~~~| -- E_south -- |~~~~~~| - ``` """ function renormalize_south_edge( @@ -912,8 +912,8 @@ function renormalize_south_edge( ) return @autoopt @tensor edge[χ_E D_Nab D_Nbe; χ_W] := E_south[χ1 D1 D2; χ2] * - ket[d; D_Nbe D6 D2 D4] * - conj(bra[d; D_Nab D5 D1 D3]) * + ket[d; D_Nab D5 D1 D3] * + conj(bra[d; D_Nbe D6 D2 D4]) * P_left[χ2 D3 D4; χ_W] * P_right[χ_E; χ1 D5 D6] end From 1014306acaf9ef0fcdf96f7cf1d8d250f0a121cc Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Sat, 21 Dec 2024 21:42:10 +0100 Subject: [PATCH 210/213] Correct rotate_north docstring --- src/utility/rotations.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utility/rotations.jl b/src/utility/rotations.jl index 634e6199..d83d4356 100644 --- a/src/utility/rotations.jl +++ b/src/utility/rotations.jl @@ -11,6 +11,6 @@ const SOUTHWEST = 4 """ rotate_north(t, dir) -Rotate north direction of `t` to `dir` by successive applications of `rotl90`. +Rotate the `dir` direction of `t` to face north by successive applications of `rotl90`. """ rotate_north(t, dir) = mod1(dir, 4) == NORTH ? t : rotate_north(rotl90(t), dir - 1) From 4bec98badfce88d6440dcd0bc3d446e65be5e150 Mon Sep 17 00:00:00 2001 From: Paul Brehmer Date: Fri, 10 Jan 2025 15:01:15 +0100 Subject: [PATCH 211/213] Use autoopt in local operator contraction --- src/algorithms/contractions/localoperator.jl | 230 ++++++++++--------- 1 file changed, 127 insertions(+), 103 deletions(-) diff --git a/src/algorithms/contractions/localoperator.jl b/src/algorithms/contractions/localoperator.jl index 324bb028..e38bdca6 100644 --- a/src/algorithms/contractions/localoperator.jl +++ b/src/algorithms/contractions/localoperator.jl @@ -56,29 +56,29 @@ end :(env.corners[ NORTHWEST, mod1($(rmin - 1), size(ket, 1)), mod1($(cmin - 1), size(ket, 2)) ]), - (:C_NW_1,), - (:C_NW_2,), + (:χ_C_NW_1,), + (:χ_C_NW_2,), ) corner_NE = tensorexpr( :(env.corners[ NORTHEAST, mod1($(rmin - 1), size(ket, 1)), mod1($(cmax + 1), size(ket, 2)) ]), - (:C_NE_1,), - (:C_NE_2,), + (:χ_C_NE_1,), + (:χ_C_NE_2,), ) corner_SE = tensorexpr( :(env.corners[ SOUTHEAST, mod1($(rmax + 1), size(ket, 1)), mod1($(cmax + 1), size(ket, 2)) ]), - (:C_SE_1,), - (:C_SE_2,), + (:χ_C_SE_1,), + (:χ_C_SE_2,), ) corner_SW = tensorexpr( :(env.corners[ SOUTHWEST, mod1($(rmax + 1), size(ket, 1)), mod1($(cmin - 1), size(ket, 2)) ]), - (:C_SW_1,), - (:C_SW_2,), + (:χ_C_SW_1,), + (:χ_C_SW_2,), ) edges_N = map(1:gridsize[2]) do i @@ -89,11 +89,11 @@ end mod1($(cmin + i - 1), size(ket, 2)), ]), ( - (i == 1 ? :C_NW_2 : Symbol(:E_N_virtual, i - 1)), - Symbol(:E_N_top, i), - Symbol(:E_N_bot, i), + (i == 1 ? :χ_C_NW_2 : Symbol(:χ_E_N, i - 1)), + Symbol(:D_E_N_top, i), + Symbol(:D_E_N_bot, i), ), - ((i == gridsize[2] ? :C_NE_1 : Symbol(:E_N_virtual, i)),), + ((i == gridsize[2] ? :χ_C_NE_1 : Symbol(:χ_E_N, i)),), ) end @@ -105,11 +105,11 @@ end mod1($(cmax + 1), size(ket, 2)), ]), ( - (i == 1 ? :C_NE_2 : Symbol(:E_E_virtual, i - 1)), - Symbol(:E_E_top, i), - Symbol(:E_E_bot, i), + (i == 1 ? :χ_C_NE_2 : Symbol(:χ_E_E, i - 1)), + Symbol(:D_E_E_top, i), + Symbol(:D_E_E_bot, i), ), - ((i == gridsize[1] ? :C_SE_1 : Symbol(:E_E_virtual, i)),), + ((i == gridsize[1] ? :χ_C_SE_1 : Symbol(:χ_E_E, i)),), ) end @@ -121,11 +121,11 @@ end mod1($(cmin + i - 1), size(ket, 2)), ]), ( - (i == gridsize[2] ? :C_SE_2 : Symbol(:E_S_virtual, i)), - Symbol(:E_S_top, i), - Symbol(:E_S_bot, i), + (i == gridsize[2] ? :χ_C_SE_2 : Symbol(:χ_E_S, i)), + Symbol(:D_E_S_top, i), + Symbol(:D_E_S_bot, i), ), - ((i == 1 ? :C_SW_1 : Symbol(:E_S_virtual, i - 1)),), + ((i == 1 ? :χ_C_SW_1 : Symbol(:χ_E_S, i - 1)),), ) end @@ -137,44 +137,56 @@ end mod1($(cmin - 1), size(ket, 2)), ]), ( - (i == gridsize[1] ? :C_SW_2 : Symbol(:E_W_virtual, i)), - Symbol(:E_W_top, i), - Symbol(:E_W_bot, i), + (i == gridsize[1] ? :χ_C_SW_2 : Symbol(:χ_E_W, i)), + Symbol(:D_E_W_top, i), + Symbol(:D_E_W_bot, i), ), - ((i == 1 ? :C_NW_1 : Symbol(:E_W_virtual, i - 1)),), + ((i == 1 ? :χ_C_NW_1 : Symbol(:χ_E_W, i - 1)),), ) end operator = tensorexpr( - :O, ntuple(i -> Symbol(:O_out_, i), N), ntuple(i -> Symbol(:O_in_, i), N) + :O, ntuple(i -> Symbol(:d_O_out_, i), N), ntuple(i -> Symbol(:d_O_in_, i), N) ) bra = map(Iterators.product(1:gridsize[1], 1:gridsize[2])) do (i, j) inds_id = findfirst(==(CartesianIndex(rmin + i - 1, cmin + j - 1)), cartesian_inds) physical_label = - isnothing(inds_id) ? Symbol(:physical, i, "_", j) : Symbol(:O_out_, inds_id) + isnothing(inds_id) ? Symbol(:d, i, "_", j) : Symbol(:d_O_out_, inds_id) return tensorexpr( :(bra[ mod1($(rmin + i - 1), size(bra, 1)), mod1($(cmin + j - 1), size(bra, 2)) ]), (physical_label,), ( - (i == 1 ? Symbol(:E_N_bot, j) : Symbol(:bra_vertical, i - 1, "_", j)), + ( + if i == 1 + Symbol(:D_E_N_bot, j) + else + Symbol(:D_bra_vertical, i - 1, "_", j) + end + ), ( if j == gridsize[2] - Symbol(:E_E_bot, i) + Symbol(:D_E_E_bot, i) else - Symbol(:bra_horizontal, i, "_", j) + Symbol(:D_bra_horizontal, i, "_", j) end ), ( if i == gridsize[1] - Symbol(:E_S_bot, j) + Symbol(:D_E_S_bot, j) else - Symbol(:bra_vertical, i, "_", j) + Symbol(:D_bra_vertical, i, "_", j) + end + ), + ( + if j == 1 + Symbol(:D_E_W_bot, i) + else + Symbol(:D_bra_horizontal, i, "_", j - 1) end ), - (j == 1 ? Symbol(:E_W_bot, i) : Symbol(:bra_horizontal, i, "_", j - 1)), ), ) end @@ -182,29 +194,41 @@ end ket = map(Iterators.product(1:gridsize[1], 1:gridsize[2])) do (i, j) inds_id = findfirst(==(CartesianIndex(rmin + i - 1, cmin + j - 1)), cartesian_inds) physical_label = - isnothing(inds_id) ? Symbol(:physical, i, "_", j) : Symbol(:O_in_, inds_id) + isnothing(inds_id) ? Symbol(:d, i, "_", j) : Symbol(:d_O_in_, inds_id) return tensorexpr( :(ket[ mod1($(rmin + i - 1), size(ket, 1)), mod1($(cmin + j - 1), size(ket, 2)) ]), (physical_label,), ( - (i == 1 ? Symbol(:E_N_top, j) : Symbol(:ket_vertical, i - 1, "_", j)), + ( + if i == 1 + Symbol(:D_E_N_top, j) + else + Symbol(:D_ket_vertical, i - 1, "_", j) + end + ), ( if j == gridsize[2] - Symbol(:E_E_top, i) + Symbol(:D_E_E_top, i) else - Symbol(:ket_horizontal, i, "_", j) + Symbol(:D_ket_horizontal, i, "_", j) end ), ( if i == gridsize[1] - Symbol(:E_S_top, j) + Symbol(:D_E_S_top, j) else - Symbol(:ket_vertical, i, "_", j) + Symbol(:D_ket_vertical, i, "_", j) + end + ), + ( + if j == 1 + Symbol(:D_E_W_top, i) + else + Symbol(:D_ket_horizontal, i, "_", j - 1) end ), - (j == 1 ? Symbol(:E_W_top, i) : Symbol(:ket_horizontal, i, "_", j - 1)), ), ) end @@ -225,20 +249,8 @@ end operator, ) - opt_ex = Expr(:tuple) - allinds = TensorOperations.getallindices(multiplication_ex) - for label in allinds - if startswith(String(label), "physical") || startswith(String(label), "O") - push!(opt_ex.args, :($label => $PEPS_PHYSICALDIM)) - elseif startswith(String(label), "ket") || startswith(String(label), "bra") - push!(opt_ex.args, :($label => $PEPS_BONDDIM)) - else - push!(opt_ex.args, :($label => $PEPS_ENVBONDDIM)) - end - end - returnex = quote - @tensor opt = $opt_ex $multiplication_ex + @autoopt @tensor opt = $multiplication_ex end return macroexpand(@__MODULE__, returnex) end @@ -276,29 +288,29 @@ end :(env.corners[ NORTHWEST, mod1($(rmin - 1), size(ket, 1)), mod1($(cmin - 1), size(ket, 2)) ]), - (:C_NW_1,), - (:C_NW_2,), + (:χ_C_NW_1,), + (:χ_C_NW_2,), ) corner_NE = tensorexpr( :(env.corners[ NORTHEAST, mod1($(rmin - 1), size(ket, 1)), mod1($(cmax + 1), size(ket, 2)) ]), - (:C_NE_1,), - (:C_NE_2,), + (:χ_C_NE_1,), + (:χ_C_NE_2,), ) corner_SE = tensorexpr( :(env.corners[ SOUTHEAST, mod1($(rmax + 1), size(ket, 1)), mod1($(cmax + 1), size(ket, 2)) ]), - (:C_SE_1,), - (:C_SE_2,), + (:χ_C_SE_1,), + (:χ_C_SE_2,), ) corner_SW = tensorexpr( :(env.corners[ SOUTHWEST, mod1($(rmax + 1), size(ket, 1)), mod1($(cmin - 1), size(ket, 2)) ]), - (:C_SW_1,), - (:C_SW_2,), + (:χ_C_SW_1,), + (:χ_C_SW_2,), ) edges_N = map(1:gridsize[2]) do i @@ -309,11 +321,11 @@ end mod1($(cmin + i - 1), size(ket, 2)), ]), ( - (i == 1 ? :C_NW_2 : Symbol(:E_N_virtual, i - 1)), - Symbol(:E_N_top, i), - Symbol(:E_N_bot, i), + (i == 1 ? :χ_C_NW_2 : Symbol(:χ_E_N, i - 1)), + Symbol(:D_E_N_top, i), + Symbol(:D_E_N_bot, i), ), - ((i == gridsize[2] ? :C_NE_1 : Symbol(:E_N_virtual, i)),), + ((i == gridsize[2] ? :χ_C_NE_1 : Symbol(:χ_E_N, i)),), ) end @@ -325,11 +337,11 @@ end mod1($(cmax + 1), size(ket, 2)), ]), ( - (i == 1 ? :C_NE_2 : Symbol(:E_E_virtual, i - 1)), - Symbol(:E_E_top, i), - Symbol(:E_E_bot, i), + (i == 1 ? :χ_C_NE_2 : Symbol(:χ_E_E, i - 1)), + Symbol(:D_E_E_top, i), + Symbol(:D_E_E_bot, i), ), - ((i == gridsize[1] ? :C_SE_1 : Symbol(:E_E_virtual, i)),), + ((i == gridsize[1] ? :χ_C_SE_1 : Symbol(:χ_E_E, i)),), ) end @@ -341,11 +353,11 @@ end mod1($(cmin + i - 1), size(ket, 2)), ]), ( - (i == gridsize[2] ? :C_SE_2 : Symbol(:E_S_virtual, i)), - Symbol(:E_S_top, i), - Symbol(:E_S_bot, i), + (i == gridsize[2] ? :χ_C_SE_2 : Symbol(:χ_E_S, i)), + Symbol(:D_E_S_top, i), + Symbol(:D_E_S_bot, i), ), - ((i == 1 ? :C_SW_1 : Symbol(:E_S_virtual, i - 1)),), + ((i == 1 ? :χ_C_SW_1 : Symbol(:χ_E_S, i - 1)),), ) end @@ -357,11 +369,11 @@ end mod1($(cmin - 1), size(ket, 2)), ]), ( - (i == gridsize[1] ? :C_SW_2 : Symbol(:E_W_virtual, i)), - Symbol(:E_W_top, i), - Symbol(:E_W_bot, i), + (i == gridsize[1] ? :χ_C_SW_2 : Symbol(:χ_E_W, i)), + Symbol(:D_E_W_top, i), + Symbol(:D_E_W_bot, i), ), - ((i == 1 ? :C_NW_1 : Symbol(:E_W_virtual, i - 1)),), + ((i == 1 ? :χ_C_NW_1 : Symbol(:χ_E_W, i - 1)),), ) end @@ -370,24 +382,36 @@ end :(bra[ mod1($(rmin + i - 1), size(ket, 1)), mod1($(cmin + j - 1), size(ket, 2)) ]), - (Symbol(:physical, i, "_", j),), + (Symbol(:d, i, "_", j),), ( - (i == 1 ? Symbol(:E_N_bot, j) : Symbol(:bra_vertical, i - 1, "_", j)), + ( + if i == 1 + Symbol(:D_E_N_bot, j) + else + Symbol(:D_bra_vertical, i - 1, "_", j) + end + ), ( if j == gridsize[2] - Symbol(:E_E_bot, i) + Symbol(:D_E_E_bot, i) else - Symbol(:bra_horizontal, i, "_", j) + Symbol(:D_bra_horizontal, i, "_", j) end ), ( if i == gridsize[1] - Symbol(:E_S_bot, j) + Symbol(:D_E_S_bot, j) else - Symbol(:bra_vertical, i, "_", j) + Symbol(:D_bra_vertical, i, "_", j) + end + ), + ( + if j == 1 + Symbol(:D_E_W_bot, i) + else + Symbol(:D_bra_horizontal, i, "_", j - 1) end ), - (j == 1 ? Symbol(:E_W_bot, i) : Symbol(:bra_horizontal, i, "_", j - 1)), ), ) end @@ -397,24 +421,36 @@ end :(ket[ mod1($(rmin + i - 1), size(ket, 1)), mod1($(cmin + j - 1), size(ket, 2)) ]), - (Symbol(:physical, i, "_", j),), + (Symbol(:d, i, "_", j),), ( - (i == 1 ? Symbol(:E_N_top, j) : Symbol(:ket_vertical, i - 1, "_", j)), + ( + if i == 1 + Symbol(:D_E_N_top, j) + else + Symbol(:D_ket_vertical, i - 1, "_", j) + end + ), ( if j == gridsize[2] - Symbol(:E_E_top, i) + Symbol(:D_E_E_top, i) else - Symbol(:ket_horizontal, i, "_", j) + Symbol(:D_ket_horizontal, i, "_", j) end ), ( if i == gridsize[1] - Symbol(:E_S_top, j) + Symbol(:D_E_S_top, j) else - Symbol(:ket_vertical, i, "_", j) + Symbol(:D_ket_vertical, i, "_", j) + end + ), + ( + if j == 1 + Symbol(:D_E_W_top, i) + else + Symbol(:D_ket_horizontal, i, "_", j - 1) end ), - (j == 1 ? Symbol(:E_W_top, i) : Symbol(:ket_horizontal, i, "_", j - 1)), ), ) end @@ -434,20 +470,8 @@ end map(x -> Expr(:call, :conj, x), bra)..., ) - opt_ex = Expr(:tuple) - allinds = TensorOperations.getallindices(multiplication_ex) - for label in allinds - if startswith(String(label), "physical") - push!(opt_ex.args, :($label => $PEPS_PHYSICALDIM)) - elseif startswith(String(label), "ket") || startswith(String(label), "bra") - push!(opt_ex.args, :($label => $PEPS_BONDDIM)) - else - push!(opt_ex.args, :($label => $PEPS_ENVBONDDIM)) - end - end - returnex = quote - @tensor opt = $opt_ex $multiplication_ex + @autoopt @tensor opt = $multiplication_ex end return macroexpand(@__MODULE__, returnex) end From ad757523ef303e8dddf235ae90719ca1514ec72b Mon Sep 17 00:00:00 2001 From: Lukas Devos Date: Fri, 10 Jan 2025 11:24:23 -0500 Subject: [PATCH 212/213] Refactor `contract_localoperator` --- src/algorithms/contractions/localoperator.jl | 453 +++++-------------- 1 file changed, 120 insertions(+), 333 deletions(-) diff --git a/src/algorithms/contractions/localoperator.jl b/src/algorithms/contractions/localoperator.jl index e38bdca6..3d6d3dac 100644 --- a/src/algorithms/contractions/localoperator.jl +++ b/src/algorithms/contractions/localoperator.jl @@ -3,10 +3,20 @@ import MPSKit: tensorexpr # currently need this because MPSKit restricts tensor names to symbols -MPSKit.tensorexpr(ex::Expr, inds) = Expr(:ref, ex, inds...) +_totuple(t) = t isa Tuple ? t : tuple(t) +MPSKit.tensorexpr(ex::Expr, inds::Tuple) = Expr(:ref, ex, _totuple(inds)...) function MPSKit.tensorexpr(ex::Expr, indout, indin) - return Expr(:typed_vcat, ex, Expr(:row, indout...), Expr(:row, indin...)) + return Expr( + :typed_vcat, ex, Expr(:row, _totuple(indout)...), Expr(:row, _totuple(indin)...) + ) +end + +function tensorlabel(args...) + return Symbol(ntuple(i -> iseven(i) ? :_ : args[(i + 1) >> 1], 2 * length(args) - 1)...) end +envlabel(args...) = tensorlabel(:χ, args...) +virtuallabel(args...) = tensorlabel(:D, args...) +physicallabel(args...) = tensorlabel(:d, args...) """ contract_localoperator(inds, O, peps, env) @@ -35,203 +45,151 @@ end # This implements the contraction of an operator acting on sites `inds`. # The generated function ensures that we can use @tensor to write dynamic contractions (and maximize performance). -@generated function _contract_localoperator( - inds::NTuple{N,Val}, - O::AbstractTensorMap{S,N,N}, - ket::InfinitePEPS, - bra::InfinitePEPS, - env::CTMRGEnv, -) where {S,N} - cartesian_inds = collect(CartesianIndex{2}, map(x -> x.parameters[1], inds.parameters)) # weird hack to extract information from Val - if !allunique(cartesian_inds) - throw(ArgumentError("Indices should not overlap: $cartesian_inds.")) - end - - rmin, rmax = extrema(getindex.(cartesian_inds, 1)) - cmin, cmax = extrema(getindex.(cartesian_inds, 2)) +function _contract_corner_expr(rowrange, colrange) + rmin, rmax = extrema(rowrange) + cmin, cmax = extrema(colrange) gridsize = (rmax - rmin + 1, cmax - cmin + 1) - corner_NW = tensorexpr( - :(env.corners[ - NORTHWEST, mod1($(rmin - 1), size(ket, 1)), mod1($(cmin - 1), size(ket, 2)) - ]), - (:χ_C_NW_1,), - (:χ_C_NW_2,), - ) - corner_NE = tensorexpr( - :(env.corners[ - NORTHEAST, mod1($(rmin - 1), size(ket, 1)), mod1($(cmax + 1), size(ket, 2)) - ]), - (:χ_C_NE_1,), - (:χ_C_NE_2,), - ) - corner_SE = tensorexpr( - :(env.corners[ - SOUTHEAST, mod1($(rmax + 1), size(ket, 1)), mod1($(cmax + 1), size(ket, 2)) - ]), - (:χ_C_SE_1,), - (:χ_C_SE_2,), - ) - corner_SW = tensorexpr( - :(env.corners[ - SOUTHWEST, mod1($(rmax + 1), size(ket, 1)), mod1($(cmin - 1), size(ket, 2)) - ]), - (:χ_C_SW_1,), - (:χ_C_SW_2,), - ) + C_NW = :(env.corners[NORTHWEST, mod1($(rmin - 1), end), mod1($(cmin - 1), end)]) + corner_NW = tensorexpr(C_NW, envlabel(WEST, 0), envlabel(NORTH, 0)) + + C_NE = :(env.corners[NORTHEAST, mod1($(rmin - 1), end), mod1($(cmax + 1), end)]) + corner_NE = tensorexpr(C_NE, envlabel(NORTH, gridsize[2]), envlabel(EAST, 0)) + + C_SE = :(env.corners[SOUTHEAST, mod1($(rmax + 1), end), mod1($(cmax + 1), end)]) + corner_SE = tensorexpr(C_SE, envlabel(EAST, gridsize[1]), envlabel(SOUTH, gridsize[2])) + + C_SW = :(env.corners[SOUTHWEST, mod1($(rmax + 1), end), mod1($(cmin - 1), end)]) + corner_SW = tensorexpr(C_SW, envlabel(SOUTH, 0), envlabel(WEST, gridsize[1])) + + return corner_NW, corner_NE, corner_SE, corner_SW +end + +function _contract_edge_expr(rowrange, colrange) + rmin, rmax = extrema(rowrange) + cmin, cmax = extrema(colrange) + gridsize = (rmax - rmin + 1, cmax - cmin + 1) edges_N = map(1:gridsize[2]) do i + E_N = :(env.edges[NORTH, mod1($(rmin - 1), end), mod1($(cmin + i - 1), end)]) return tensorexpr( - :(env.edges[ - NORTH, - mod1($(rmin - 1), size(ket, 1)), - mod1($(cmin + i - 1), size(ket, 2)), - ]), + E_N, ( - (i == 1 ? :χ_C_NW_2 : Symbol(:χ_E_N, i - 1)), - Symbol(:D_E_N_top, i), - Symbol(:D_E_N_bot, i), + envlabel(NORTH, i - 1), + virtuallabel(NORTH, :ket, i), + virtuallabel(NORTH, :bra, i), ), - ((i == gridsize[2] ? :χ_C_NE_1 : Symbol(:χ_E_N, i)),), + envlabel(NORTH, i), ) end edges_E = map(1:gridsize[1]) do i + E_E = :(env.edges[EAST, mod1($(rmin + i - 1), end), mod1($(cmax + 1), end)]) return tensorexpr( - :(env.edges[ - EAST, - mod1($(rmin + i - 1), size(ket, 1)), - mod1($(cmax + 1), size(ket, 2)), - ]), + E_E, ( - (i == 1 ? :χ_C_NE_2 : Symbol(:χ_E_E, i - 1)), - Symbol(:D_E_E_top, i), - Symbol(:D_E_E_bot, i), + envlabel(EAST, i - 1), + virtuallabel(EAST, :ket, i), + virtuallabel(EAST, :bra, i), ), - ((i == gridsize[1] ? :χ_C_SE_1 : Symbol(:χ_E_E, i)),), + envlabel(EAST, i), ) end edges_S = map(1:gridsize[2]) do i + E_S = :(env.edges[SOUTH, mod1($(rmax + 1), end), mod1($(cmin + i - 1), end)]) return tensorexpr( - :(env.edges[ - SOUTH, - mod1($(rmax + 1), size(ket, 1)), - mod1($(cmin + i - 1), size(ket, 2)), - ]), + E_S, ( - (i == gridsize[2] ? :χ_C_SE_2 : Symbol(:χ_E_S, i)), - Symbol(:D_E_S_top, i), - Symbol(:D_E_S_bot, i), + envlabel(SOUTH, i), + virtuallabel(SOUTH, :ket, i), + virtuallabel(SOUTH, :bra, i), ), - ((i == 1 ? :χ_C_SW_1 : Symbol(:χ_E_S, i - 1)),), + envlabel(SOUTH, i - 1), ) end edges_W = map(1:gridsize[1]) do i + E_W = :(env.edges[WEST, mod1($(rmin + i - 1), end), mod1($(cmin - 1), end)]) return tensorexpr( - :(env.edges[ - WEST, - mod1($(rmin + i - 1), size(ket, 1)), - mod1($(cmin - 1), size(ket, 2)), - ]), - ( - (i == gridsize[1] ? :χ_C_SW_2 : Symbol(:χ_E_W, i)), - Symbol(:D_E_W_top, i), - Symbol(:D_E_W_bot, i), - ), - ((i == 1 ? :χ_C_NW_1 : Symbol(:χ_E_W, i - 1)),), + E_W, + (envlabel(WEST, i), virtuallabel(WEST, :ket, i), virtuallabel(WEST, :bra, i)), + envlabel(WEST, i - 1), ) end - operator = tensorexpr( - :O, ntuple(i -> Symbol(:d_O_out_, i), N), ntuple(i -> Symbol(:d_O_in_, i), N) - ) + return edges_N, edges_E, edges_S, edges_W +end - bra = map(Iterators.product(1:gridsize[1], 1:gridsize[2])) do (i, j) - inds_id = findfirst(==(CartesianIndex(rmin + i - 1, cmin + j - 1)), cartesian_inds) - physical_label = - isnothing(inds_id) ? Symbol(:d, i, "_", j) : Symbol(:d_O_out_, inds_id) - return tensorexpr( - :(bra[ - mod1($(rmin + i - 1), size(bra, 1)), mod1($(cmin + j - 1), size(bra, 2)) - ]), - (physical_label,), - ( - ( - if i == 1 - Symbol(:D_E_N_bot, j) - else - Symbol(:D_bra_vertical, i - 1, "_", j) - end - ), - ( - if j == gridsize[2] - Symbol(:D_E_E_bot, i) - else - Symbol(:D_bra_horizontal, i, "_", j) - end - ), - ( - if i == gridsize[1] - Symbol(:D_E_S_bot, j) - else - Symbol(:D_bra_vertical, i, "_", j) - end - ), - ( - if j == 1 - Symbol(:D_E_W_bot, i) - else - Symbol(:D_bra_horizontal, i, "_", j - 1) - end - ), - ), - ) - end +function _contract_state_expr(rowrange, colrange, cartesian_inds=nothing) + rmin, rmax = extrema(rowrange) + cmin, cmax = extrema(colrange) + gridsize = (rmax - rmin + 1, cmax - cmin + 1) - ket = map(Iterators.product(1:gridsize[1], 1:gridsize[2])) do (i, j) - inds_id = findfirst(==(CartesianIndex(rmin + i - 1, cmin + j - 1)), cartesian_inds) - physical_label = - isnothing(inds_id) ? Symbol(:d, i, "_", j) : Symbol(:d_O_in_, inds_id) - return tensorexpr( - :(ket[ - mod1($(rmin + i - 1), size(ket, 1)), mod1($(cmin + j - 1), size(ket, 2)) - ]), - (physical_label,), - ( + return map((:bra, :ket)) do side + return map(Iterators.product(1:gridsize[1], 1:gridsize[2])) do (i, j) + inds_id = if isnothing(cartesian_inds) + nothing + else + findfirst(==(CartesianIndex(rmin + i - 1, cmin + j - 1)), cartesian_inds) + end + physical_label = if isnothing(inds_id) + physicallabel(i, j) + else + physicallabel(:O, side, inds_id) + end + return tensorexpr( + :(bra[mod1($(rmin + i - 1), end), mod1($(cmin + j - 1), end)]), + (physical_label,), ( if i == 1 - Symbol(:D_E_N_top, j) + virtuallabel(NORTH, side, j) else - Symbol(:D_ket_vertical, i - 1, "_", j) - end - ), - ( + virtuallabel(:vertical, side, i - 1, j) + end, if j == gridsize[2] - Symbol(:D_E_E_top, i) + virtuallabel(EAST, side, i) else - Symbol(:D_ket_horizontal, i, "_", j) - end - ), - ( + virtuallabel(:horizontal, side, i, j) + end, if i == gridsize[1] - Symbol(:D_E_S_top, j) + virtuallabel(SOUTH, side, j) else - Symbol(:D_ket_vertical, i, "_", j) - end - ), - ( + virtuallabel(:vertical, side, i, j) + end, if j == 1 - Symbol(:D_E_W_top, i) + virtuallabel(WEST, side, i) else - Symbol(:D_ket_horizontal, i, "_", j - 1) - end + virtuallabel(:horizontal, side, i, j - 1) + end, ), - ), - ) + ) + end end +end + +@generated function _contract_localoperator( + inds::NTuple{N,Val}, + O::AbstractTensorMap{S,N,N}, + ket::InfinitePEPS, + bra::InfinitePEPS, + env::CTMRGEnv, +) where {S,N} + cartesian_inds = collect(CartesianIndex{2}, map(x -> x.parameters[1], inds.parameters)) # weird hack to extract information from Val + allunique(cartesian_inds) || + throw(ArgumentError("Indices should not overlap: $cartesian_inds.")) + rowrange = getindex.(cartesian_inds, 1) + colrange = getindex.(cartesian_inds, 2) + + corner_NW, corner_NE, corner_SE, corner_SW = _contract_corner_expr(rowrange, colrange) + edges_N, edges_E, edges_S, edges_W = _contract_edge_expr(rowrange, colrange) + operator = tensorexpr( + :O, + ntuple(i -> physicallabel(:O, :bra, i), N), + ntuple(i -> physicallabel(:O, :ket, i), N), + ) + bra, ket = _contract_state_expr(rowrange, colrange, cartesian_inds) multiplication_ex = Expr( :call, @@ -275,185 +233,14 @@ end inds::NTuple{N,Val}, ket::InfinitePEPS, bra::InfinitePEPS, env::CTMRGEnv ) where {N} cartesian_inds = collect(CartesianIndex{2}, map(x -> x.parameters[1], inds.parameters)) # weird hack to extract information from Val - if !allunique(cartesian_inds) + allunique(cartesian_inds) || throw(ArgumentError("Indices should not overlap: $cartesian_inds.")) - end - - rmin, rmax = extrema(getindex.(cartesian_inds, 1)) - cmin, cmax = extrema(getindex.(cartesian_inds, 2)) + rowrange = getindex.(cartesian_inds, 1) + colrange = getindex.(cartesian_inds, 2) - gridsize = (rmax - rmin + 1, cmax - cmin + 1) - - corner_NW = tensorexpr( - :(env.corners[ - NORTHWEST, mod1($(rmin - 1), size(ket, 1)), mod1($(cmin - 1), size(ket, 2)) - ]), - (:χ_C_NW_1,), - (:χ_C_NW_2,), - ) - corner_NE = tensorexpr( - :(env.corners[ - NORTHEAST, mod1($(rmin - 1), size(ket, 1)), mod1($(cmax + 1), size(ket, 2)) - ]), - (:χ_C_NE_1,), - (:χ_C_NE_2,), - ) - corner_SE = tensorexpr( - :(env.corners[ - SOUTHEAST, mod1($(rmax + 1), size(ket, 1)), mod1($(cmax + 1), size(ket, 2)) - ]), - (:χ_C_SE_1,), - (:χ_C_SE_2,), - ) - corner_SW = tensorexpr( - :(env.corners[ - SOUTHWEST, mod1($(rmax + 1), size(ket, 1)), mod1($(cmin - 1), size(ket, 2)) - ]), - (:χ_C_SW_1,), - (:χ_C_SW_2,), - ) - - edges_N = map(1:gridsize[2]) do i - return tensorexpr( - :(env.edges[ - NORTH, - mod1($(rmin - 1), size(ket, 1)), - mod1($(cmin + i - 1), size(ket, 2)), - ]), - ( - (i == 1 ? :χ_C_NW_2 : Symbol(:χ_E_N, i - 1)), - Symbol(:D_E_N_top, i), - Symbol(:D_E_N_bot, i), - ), - ((i == gridsize[2] ? :χ_C_NE_1 : Symbol(:χ_E_N, i)),), - ) - end - - edges_E = map(1:gridsize[1]) do i - return tensorexpr( - :(env.edges[ - EAST, - mod1($(rmin + i - 1), size(ket, 1)), - mod1($(cmax + 1), size(ket, 2)), - ]), - ( - (i == 1 ? :χ_C_NE_2 : Symbol(:χ_E_E, i - 1)), - Symbol(:D_E_E_top, i), - Symbol(:D_E_E_bot, i), - ), - ((i == gridsize[1] ? :χ_C_SE_1 : Symbol(:χ_E_E, i)),), - ) - end - - edges_S = map(1:gridsize[2]) do i - return tensorexpr( - :(env.edges[ - SOUTH, - mod1($(rmax + 1), size(ket, 1)), - mod1($(cmin + i - 1), size(ket, 2)), - ]), - ( - (i == gridsize[2] ? :χ_C_SE_2 : Symbol(:χ_E_S, i)), - Symbol(:D_E_S_top, i), - Symbol(:D_E_S_bot, i), - ), - ((i == 1 ? :χ_C_SW_1 : Symbol(:χ_E_S, i - 1)),), - ) - end - - edges_W = map(1:gridsize[1]) do i - return tensorexpr( - :(env.edges[ - WEST, - mod1($(rmin + i - 1), size(ket, 1)), - mod1($(cmin - 1), size(ket, 2)), - ]), - ( - (i == gridsize[1] ? :χ_C_SW_2 : Symbol(:χ_E_W, i)), - Symbol(:D_E_W_top, i), - Symbol(:D_E_W_bot, i), - ), - ((i == 1 ? :χ_C_NW_1 : Symbol(:χ_E_W, i - 1)),), - ) - end - - bra = map(Iterators.product(1:gridsize[1], 1:gridsize[2])) do (i, j) - return tensorexpr( - :(bra[ - mod1($(rmin + i - 1), size(ket, 1)), mod1($(cmin + j - 1), size(ket, 2)) - ]), - (Symbol(:d, i, "_", j),), - ( - ( - if i == 1 - Symbol(:D_E_N_bot, j) - else - Symbol(:D_bra_vertical, i - 1, "_", j) - end - ), - ( - if j == gridsize[2] - Symbol(:D_E_E_bot, i) - else - Symbol(:D_bra_horizontal, i, "_", j) - end - ), - ( - if i == gridsize[1] - Symbol(:D_E_S_bot, j) - else - Symbol(:D_bra_vertical, i, "_", j) - end - ), - ( - if j == 1 - Symbol(:D_E_W_bot, i) - else - Symbol(:D_bra_horizontal, i, "_", j - 1) - end - ), - ), - ) - end - - ket = map(Iterators.product(1:gridsize[1], 1:gridsize[2])) do (i, j) - return tensorexpr( - :(ket[ - mod1($(rmin + i - 1), size(ket, 1)), mod1($(cmin + j - 1), size(ket, 2)) - ]), - (Symbol(:d, i, "_", j),), - ( - ( - if i == 1 - Symbol(:D_E_N_top, j) - else - Symbol(:D_ket_vertical, i - 1, "_", j) - end - ), - ( - if j == gridsize[2] - Symbol(:D_E_E_top, i) - else - Symbol(:D_ket_horizontal, i, "_", j) - end - ), - ( - if i == gridsize[1] - Symbol(:D_E_S_top, j) - else - Symbol(:D_ket_vertical, i, "_", j) - end - ), - ( - if j == 1 - Symbol(:D_E_W_top, i) - else - Symbol(:D_ket_horizontal, i, "_", j - 1) - end - ), - ), - ) - end + corner_NW, corner_NE, corner_SE, corner_SW = _contract_corner_expr(rowrange, colrange) + edges_N, edges_E, edges_S, edges_W = _contract_edge_expr(rowrange, colrange) + bra, ket = _contract_state_expr(rowrange, colrange) multiplication_ex = Expr( :call, From 1782bebf4ea4a312c36f1f8b9371de5d187b2039 Mon Sep 17 00:00:00 2001 From: Lander Burgelman <39218680+leburgel@users.noreply.github.com> Date: Mon, 13 Jan 2025 22:19:50 +0100 Subject: [PATCH 213/213] Add CTMRG support for regular 2D partition functions (#111) * Add CTMRG support for regular 2D partition functions * Format * Update for new CTMRG flavor specification * Fix typo * Rename `PartitionFunction` -> `PartitionFunctionTensor` * Add documentation * change convention of PartitionFuntionTensor * bug fix in left_projector * format fix * full_infinite_environment for PartitionFunction * fixes for sequential ctmrg * Update src/algorithms/contractions/ctmrg_contractions.jl Add internal convenience aliases for different flavors of edge tensors Co-authored-by: Lukas Devos * Add internal aliases, merge first set of docstrings and update type annotations * Format * Fix typos and clean up signatures * Split out abstract network definitions * Test different CTMRG and projector flavors to increase test coverage * Use `half_infinite_environment` consistently * Squash more docstrings * Fix typos, switch to shorter aliases internally * Format * Remove overwritten method, use random seed in test * Update src/states/infinitepartitionfunction.jl Co-authored-by: Lukas Devos * Update src/states/infinitepartitionfunction.jl Co-authored-by: Lukas Devos * Remove stray `build_projectors` * Add `InfiniteSquareNetwork` * Fix typos, change temperature in test * Fix more in-place operations * Fix typo * Merge sparse environment structures for different network types * Rename `norm` -> `value` for `InfinitePartitionFunction` * Remove unused abstract network supertypes * Break out network-specific CTMRG obective functions * Make norm computation verbosity-dependent * Fix typos * Remove arrows in tensor docstring diagrams * Add local expectation value for partition functions * Correct signature in docstring * Update test to use partition function expectation value * Fix small docstring typos * Add InfiniteSquareNetwork supertype * Fix division * Move `infinitesquarenetwork.jl` to dedicated folder * Add contract_local_tensor for plaquette tensors and matrices of tensors * Fix partition_function.jl test * Refactor contract_local_tensor * Add operator index safeguard * Add `expectation_value` method for singel index-tensor pair * Add `expectation_value` docstrings * Fix operator contraction order and add matrix & blocked operator test * Add rectangular patch size check for blocked operators * Remove `value` export * Move objective calculation inside logging functions * Revert partition function `expectation_value` to simplest case; match syntax to other methods --------- Co-authored-by: sanderdemeyer Co-authored-by: Lukas Devos Co-authored-by: Paul Brehmer --- .DS_Store | Bin 6148 -> 0 bytes Project.toml | 4 +- src/PEPSKit.jl | 8 +- .../contractions/ctmrg_contractions.jl | 887 ++++++++++++++---- src/algorithms/contractions/localoperator.jl | 59 +- src/algorithms/ctmrg/ctmrg.jl | 29 +- src/algorithms/ctmrg/sequential.jl | 6 +- src/algorithms/ctmrg/sparse_environments.jl | 267 +++--- src/algorithms/toolbox.jl | 80 +- src/environments/ctmrg_environments.jl | 273 ++++-- src/networks/infinitesquarenetwork.jl | 115 +++ .../abstractpeps.jl => networks/tensors.jl} | 88 +- src/operators/infinitepepo.jl | 23 +- src/states/infinitepartitionfunction.jl | 153 +++ src/states/infinitepeps.jl | 75 +- src/states/infiniteweightpeps.jl | 4 +- test/ctmrg/partition_function.jl | 118 +++ test/runtests.jl | 3 + 18 files changed, 1669 insertions(+), 523 deletions(-) delete mode 100644 .DS_Store create mode 100644 src/networks/infinitesquarenetwork.jl rename src/{states/abstractpeps.jl => networks/tensors.jl} (57%) create mode 100644 src/states/infinitepartitionfunction.jl create mode 100644 test/ctmrg/partition_function.jl diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index 900f40f7ff956b21d24f23dbfe96eca4e4dda795..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHKyG|QH6g`7C!U~NGM?xr(_6wZ&4;JKfG(eZi@go?;)>^hfNHqHaDL_I3iH0(D zr2Im@0KOohr=#WEnaO&_YrC`%nk&tm)qU(edv!ha^O5zK_oYK7ZH#>;A1~dQ%VGCNu1Q|& zsxdF__q~~b6g&yUpbG$xm-TW@_b&Ogjb38W`wq$`S$97is4@K$j^v60KDlr)5pg>T-S3tgKX%*T3`AxIv z|6eDCDJT#Wc%2HUsBzHP;kDVlwQ!ZP*Tx)84l&8g4C)e2c01MwvK4P~Xk#pv17cz^ SGe{3Dd x.parameters[1], inds.parameters)) # weird hack to extract information from Val @@ -262,3 +262,38 @@ end end return macroexpand(@__MODULE__, returnex) end + +# Partition function contractions + +""" + contract_local_tensor(inds, O, env) + +Contract a local tensor `O` inserted into a partition function `pf` at position `inds`, +using the environment `env`. +""" +function contract_local_tensor( + inds::Tuple{Int,Int}, + O::AbstractTensorMap{S,2,2}, + env::CTMRGEnv{C,<:CTMRG_PF_EdgeTensor}, +) where {S,C} + r, c = inds + return @autoopt @tensor env.corners[NORTHWEST, _prev(r, end), _prev(c, end)][ + χ_WNW + χ_NNW + ] * + env.edges[NORTH, _prev(r, end), c][χ_NNW D_N; χ_NNE] * + env.corners[NORTHEAST, _prev(r, end), _next(c, end)][χ_NNE; χ_ENE] * + env.edges[EAST, r, _next(c, end)][χ_ENE D_E; χ_ESE] * + env.corners[SOUTHEAST, _next(r, end), _next(c, end)][χ_ESE; χ_SSE] * + env.edges[SOUTH, _next(r, end), c][χ_SSE D_S; χ_SSW] * + env.corners[SOUTHWEST, _next(r, end), _prev(c, end)][χ_SSW; χ_WSW] * + env.edges[WEST, r, _prev(c, end)][χ_WSW D_W; χ_WNW] * + O[D_W D_S; D_N D_E] +end +function contract_local_tensor( + inds::CartesianIndex{2}, + O::AbstractTensorMap{S,2,2}, + env::CTMRGEnv{C,<:CTMRG_PF_EdgeTensor}, +) where {S,C} + return contract_local_tensor(Tuple(inds), O, env) +end diff --git a/src/algorithms/ctmrg/ctmrg.jl b/src/algorithms/ctmrg/ctmrg.jl index e35abeaf..c5225f9d 100644 --- a/src/algorithms/ctmrg/ctmrg.jl +++ b/src/algorithms/ctmrg/ctmrg.jl @@ -1,4 +1,3 @@ - """ CTMRGAlgorithm @@ -36,36 +35,44 @@ function MPSKit.leading_boundary(envinit, state, alg::CTMRGAlgorithm) TS = map(x -> tsvd(x)[2], envinit.edges) η = one(real(scalartype(state))) - N = norm(state, envinit) env = deepcopy(envinit) log = ignore_derivatives(() -> MPSKit.IterLog("CTMRG")) return LoggingExtras.withlevel(; alg.verbosity) do - ctmrg_loginit!(log, η, N) + ctmrg_loginit!(log, η, state, envinit) for iter in 1:(alg.maxiter) env, = ctmrg_iteration(state, env, alg) # Grow and renormalize in all 4 directions η, CS, TS = calc_convergence(env, CS, TS) - N = norm(state, env) if η ≤ alg.tol && iter ≥ alg.miniter - ctmrg_logfinish!(log, iter, η, N) + ctmrg_logfinish!(log, iter, η, state, env) break end if iter == alg.maxiter - ctmrg_logcancel!(log, iter, η, N) + ctmrg_logcancel!(log, iter, η, state, env) else - ctmrg_logiter!(log, iter, η, N) + ctmrg_logiter!(log, iter, η, state, env) end end return env end end +# network-specific objective functions +ctmrg_objective(state::InfinitePEPS, env::CTMRGEnv) = norm(state, env) +ctmrg_objective(state::InfinitePartitionFunction, env::CTMRGEnv) = value(state, env) + # custom CTMRG logging -ctmrg_loginit!(log, η, N) = @infov 2 loginit!(log, η, N) -ctmrg_logiter!(log, iter, η, N) = @infov 3 logiter!(log, iter, η, N) -ctmrg_logfinish!(log, iter, η, N) = @infov 2 logfinish!(log, iter, η, N) -ctmrg_logcancel!(log, iter, η, N) = @warnv 1 logcancel!(log, iter, η, N) +ctmrg_loginit!(log, η, state, env) = @infov 2 loginit!(log, η, ctmrg_objective(state, env)) +function ctmrg_logiter!(log, iter, η, state, env) + @infov 3 logiter!(log, iter, η, ctmrg_objective(state, env)) +end +function ctmrg_logfinish!(log, iter, η, state, env) + @infov 2 logfinish!(log, iter, η, ctmrg_objective(state, env)) +end +function ctmrg_logcancel!(log, iter, η, state, env) + @warnv 1 logcancel!(log, iter, η, ctmrg_objective(state, env)) +end @non_differentiable ctmrg_loginit!(args...) @non_differentiable ctmrg_logiter!(args...) diff --git a/src/algorithms/ctmrg/sequential.jl b/src/algorithms/ctmrg/sequential.jl index 94f4846e..4404a7f5 100644 --- a/src/algorithms/ctmrg/sequential.jl +++ b/src/algorithms/ctmrg/sequential.jl @@ -53,7 +53,7 @@ Compute CTMRG projectors in the `:sequential` scheme either for an entire column for a specific `coordinate` (where `dir=WEST` is already implied in the `:sequential` scheme). """ function sequential_projectors( - col::Int, state::InfinitePEPS, envs::CTMRGEnv, alg::ProjectorAlgorithm + col::Int, state::InfiniteSquareNetwork, envs::CTMRGEnv, alg::ProjectorAlgorithm ) # SVD half-infinite environment column-wise ϵ = Zygote.Buffer(zeros(real(scalartype(envs)), size(envs, 2))) @@ -71,7 +71,7 @@ function sequential_projectors( end function sequential_projectors( coordinate::NTuple{3,Int}, - state::InfinitePEPS, + state::InfiniteSquareNetwork, envs::CTMRGEnv, alg::HalfInfiniteProjector, ) @@ -83,7 +83,7 @@ function sequential_projectors( end function sequential_projectors( coordinate::NTuple{3,Int}, - state::InfinitePEPS, + state::InfiniteSquareNetwork, envs::CTMRGEnv, alg::FullInfiniteProjector, ) diff --git a/src/algorithms/ctmrg/sparse_environments.jl b/src/algorithms/ctmrg/sparse_environments.jl index 1cb3241c..ce243333 100644 --- a/src/algorithms/ctmrg/sparse_environments.jl +++ b/src/algorithms/ctmrg/sparse_environments.jl @@ -3,57 +3,66 @@ # -------------------------------------------------------- """ - struct EnlargedCorner{Ct,E,A,A′} + struct EnlargedCorner{TC,TE,TA} Enlarged CTMRG corner tensor storage. """ -struct EnlargedCorner{Ct,E,A,A′} - C::Ct - E_1::E - E_2::E - ket::A - bra::A′ +struct EnlargedCorner{TC,TE,TA} + C::TC + E_1::TE + E_2::TE + A::TA +end +function EnlargedCorner( + C::CTMRGCornerTensor, + E1::CTMRG_PEPS_EdgeTensor, + E2::CTMRG_PEPS_EdgeTensor, + ket::PEPSTensor, + bra::PEPSTensor=ket, +) + return EnlargedCorner(C, E1, E2, (bra, ket)) +end +function EnlargedCorner( + C::CTMRGCornerTensor, E1::CTMRG_PF_EdgeTensor, E2::CTMRG_PF_EdgeTensor, pf::PFTensor +) + return EnlargedCorner(C, E1, E2, (pf,)) end """ - EnlargedCorner(state, envs, coordinates) + EnlargedCorner(network::InfiniteSquareNetwork, envs, coordinates) Construct an enlarged corner with the correct row and column indices based on the given `coordinates` which are of the form `(dir, row, col)`. """ -function EnlargedCorner(state, envs, coordinates) +function EnlargedCorner(network::InfiniteSquareNetwork, envs, coordinates) dir, r, c = coordinates if dir == NORTHWEST return EnlargedCorner( envs.corners[NORTHWEST, _prev(r, end), _prev(c, end)], envs.edges[WEST, r, _prev(c, end)], envs.edges[NORTH, _prev(r, end), c], - state[r, c], - state[r, c], + network[r, c], ) elseif dir == NORTHEAST return EnlargedCorner( envs.corners[NORTHEAST, _prev(r, end), _next(c, end)], envs.edges[NORTH, _prev(r, end), c], envs.edges[EAST, r, _next(c, end)], - state[r, c], - state[r, c], + network[r, c], ) elseif dir == SOUTHEAST return EnlargedCorner( envs.corners[SOUTHEAST, _next(r, end), _next(c, end)], envs.edges[EAST, r, _next(c, end)], envs.edges[SOUTH, _next(r, end), c], - state[r, c], - state[r, c], + network[r, c], ) elseif dir == SOUTHWEST return EnlargedCorner( envs.corners[SOUTHWEST, _next(r, end), _prev(c, end)], envs.edges[SOUTH, _next(r, end), c], envs.edges[WEST, r, _prev(c, end)], - state[r, c], - state[r, c], + network[r, c], ) end end @@ -66,35 +75,27 @@ direction, i.e. the way the environment and PEPS tensors connect. """ function TensorKit.TensorMap(Q::EnlargedCorner, dir::Int) if dir == NORTHWEST - return enlarge_northwest_corner(Q.E_1, Q.C, Q.E_2, Q.ket, Q.bra) + return enlarge_northwest_corner(Q.E_1, Q.C, Q.E_2, Q.A...) elseif dir == NORTHEAST - return enlarge_northeast_corner(Q.E_1, Q.C, Q.E_2, Q.ket, Q.bra) + return enlarge_northeast_corner(Q.E_1, Q.C, Q.E_2, Q.A...) elseif dir == SOUTHEAST - return enlarge_southeast_corner(Q.E_1, Q.C, Q.E_2, Q.ket, Q.bra) + return enlarge_southeast_corner(Q.E_1, Q.C, Q.E_2, Q.A...) elseif dir == SOUTHWEST - return enlarge_southwest_corner(Q.E_1, Q.C, Q.E_2, Q.ket, Q.bra) + return enlarge_southwest_corner(Q.E_1, Q.C, Q.E_2, Q.A...) end end function renormalize_northwest_corner(ec::EnlargedCorner, P_left, P_right) - return renormalize_northwest_corner( - ec.E_1, ec.C, ec.E_2, P_left, P_right, ec.ket, ec.bra - ) + return renormalize_northwest_corner(ec.E_1, ec.C, ec.E_2, P_left, P_right, ec.A...) end function renormalize_northeast_corner(ec::EnlargedCorner, P_left, P_right) - return renormalize_northeast_corner( - ec.E_1, ec.C, ec.E_2, P_left, P_right, ec.ket, ec.bra - ) + return renormalize_northeast_corner(ec.E_1, ec.C, ec.E_2, P_left, P_right, ec.A...) end function renormalize_southeast_corner(ec::EnlargedCorner, P_left, P_right) - return renormalize_southeast_corner( - ec.E_1, ec.C, ec.E_2, P_left, P_right, ec.ket, ec.bra - ) + return renormalize_southeast_corner(ec.E_1, ec.C, ec.E_2, P_left, P_right, ec.A...) end function renormalize_southwest_corner(ec::EnlargedCorner, P_left, P_right) - return renormalize_southwest_corner( - ec.E_1, ec.C, ec.E_2, P_left, P_right, ec.ket, ec.bra - ) + return renormalize_southwest_corner(ec.E_1, ec.C, ec.E_2, P_left, P_right, ec.A...) end # Wrapper around half_infinite_environment contraction using EnlargedCorners (used in ctmrg_projectors) @@ -121,17 +122,41 @@ end Half-infinite CTMRG environment tensor storage. """ -struct HalfInfiniteEnv{C,E,A,A′} # TODO: subtype as AbstractTensorMap once TensorKit is updated - C_1::C - C_2::C - E_1::E - E_2::E - E_3::E - E_4::E - ket_1::A - ket_2::A - bra_1::A′ - bra_2::A′ +struct HalfInfiniteEnv{TC,TE,TA} # TODO: subtype as AbstractTensorMap once TensorKit is updated + C_1::TC + C_2::TC + E_1::TE + E_2::TE + E_3::TE + E_4::TE + A_1::TA + A_2::TA +end +function HalfInfiniteEnv( + C_1::CTMRGCornerTensor, + C_2::CTMRGCornerTensor, + E_1::CTMRG_PEPS_EdgeTensor, + E_2::CTMRG_PEPS_EdgeTensor, + E_3::CTMRG_PEPS_EdgeTensor, + E_4::CTMRG_PEPS_EdgeTensor, + ket_1::PEPSTensor, + ket_2::PEPSTensor, + bra_1::PEPSTensor=ket_1, + bra_2::PEPSTensor=ket_2, +) + return HalfInfiniteEnv(C_1, C_2, E_1, E_2, E_3, E_4, (bra_1, ket_1), (bra_2, ket_2)) +end +function HalfInfiniteEnv( + C_1::CTMRGCornerTensor, + C_2::CTMRGCornerTensor, + E_1::CTMRG_PF_EdgeTensor, + E_2::CTMRG_PF_EdgeTensor, + E_3::CTMRG_PF_EdgeTensor, + E_4::CTMRG_PF_EdgeTensor, + pf_1::PFTensor, + pf_2::PFTensor, +) + return HalfInfiniteEnv(C_1, C_2, E_1, E_2, E_3, E_4, (pf_1,), (pf_2,)) end # Construct environment from two enlarged corners @@ -143,10 +168,8 @@ function HalfInfiniteEnv(quadrant1::EnlargedCorner, quadrant2::EnlargedCorner) quadrant1.E_2, quadrant2.E_1, quadrant2.E_2, - quadrant1.ket, - quadrant2.ket, - quadrant1.bra, - quadrant2.bra, + quadrant1.A_1, + quadrant2.A_2, ) end @@ -157,16 +180,7 @@ Instantiate half-infinite environment as `TensorMap` explicitly. """ function TensorKit.TensorMap(env::HalfInfiniteEnv) # Dense operator return half_infinite_environment( - env.C_1, - env.C_2, - env.E_1, - env.E_2, - env.E_3, - env.E_4, - env.ket_1, - env.ket_2, - env.bra_1, - env.bra_2, + env.C_1, env.C_2, env.E_1, env.E_2, env.E_3, env.E_4, env.A_1..., env.A_2... ) end @@ -179,42 +193,39 @@ linear map or adjoint linear map on `x` if `Val(true)` or `Val(false)` is passed """ function (env::HalfInfiniteEnv)(x, ::Val{false}) # Linear map: env() * x return half_infinite_environment( - env.C_1, - env.C_2, - env.E_1, - env.E_2, - env.E_3, - env.E_4, - x, - env.ket_1, - env.ket_2, - env.bra_1, - env.bra_2, + env.C_1, env.C_2, env.E_1, env.E_2, env.E_3, env.E_4, x, env.A_1..., env.A_2... ) end function (env::HalfInfiniteEnv)(x, ::Val{true}) # Adjoint linear map: env()' * x return half_infinite_environment( - x, - env.C_1, - env.C_2, - env.E_1, - env.E_2, - env.E_3, - env.E_4, - env.ket_1, - env.ket_2, - env.bra_1, - env.bra_2, + x, env.C_1, env.C_2, env.E_1, env.E_2, env.E_3, env.E_4, env.A_1..., env.A_2... ) end +# ----------------------------------------------------- # AbstractTensorMap subtyping and IterSVD compatibility +# ----------------------------------------------------- + +function _domain_space(A::NTuple{2,<:PEPSTensor}, n::Int) + return domain(A[1])[n] * domain(A[2])[n]' +end +function _domain_space(A::NTuple{1,<:PFTensor}, n::Int) + return domain(A[1])[n] +end + +function _codomain_space(A::NTuple{2,<:PEPSTensor}, n::Int) + return domain(A[1])[n]' * domain(A[2])[n] +end +function _codomain_space(A::NTuple{1,<:PFTensor}, n::Int) + return domain(A[1])[n]' +end + function TensorKit.domain(env::HalfInfiniteEnv) - return domain(env.E_4) * domain(env.ket_2)[3] * domain(env.bra_2)[3]' + return domain(env.E_4) * _domain_space(env.A_2, 3) end function TensorKit.codomain(env::HalfInfiniteEnv) - return codomain(env.E_1)[1] * domain(env.ket_1)[3]' * domain(env.bra_1)[3] + return codomain(env.E_1)[1] * _codomain_space(env.A_1, 3) end function random_start_vector(env::HalfInfiniteEnv) @@ -226,31 +237,27 @@ end # -------------------------------- """ - struct FullInfiniteEnv{C,E,A,A′} + struct FullInfiniteEnv{TC,TE,TA} Full-infinite CTMRG environment tensor storage. """ -struct FullInfiniteEnv{C,E,A,A′} # TODO: subtype as AbstractTensorMap once TensorKit is updated - C_1::C - C_2::C - C_3::C - C_4::C - E_1::E - E_2::E - E_3::E - E_4::E - E_5::E - E_6::E - E_7::E - E_8::E - ket_1::A - ket_2::A - ket_3::A - ket_4::A - bra_1::A′ - bra_2::A′ - bra_3::A′ - bra_4::A′ +struct FullInfiniteEnv{TC,TE,TA} # TODO: subtype as AbstractTensorMap once TensorKit is updated + C_1::TC + C_2::TC + C_3::TC + C_4::TC + E_1::TE + E_2::TE + E_3::TE + E_4::TE + E_5::TE + E_6::TE + E_7::TE + E_8::TE + A_1::TA + A_2::TA + A_3::TA + A_4::TA end # Construct environment from two enlarged corners @@ -270,14 +277,10 @@ function FullInfiniteEnv( quadrant3.E_2, quadrant4.E_1, quadrant4.E_2, - quadrant1.ket, - quadrant2.ket, - quadrant3.ket, - quadrant4.ket, - quadrant1.bra, - quadrant2.bra, - quadrant3.bra, - quadrant4.bra, + quadrant1.A, + quadrant2.A, + quadrant3.A, + quadrant4.A, ) end @@ -300,14 +303,10 @@ function TensorKit.TensorMap(env::FullInfiniteEnv) # Dense operator env.E_3, env.E_4, env.E_5, - env.ket_1, - env.ket_2, - env.ket_3, - env.ket_4, - env.bra_1, - env.bra_2, - env.bra_3, - env.bra_4, + env.A_1..., + env.A_2..., + env.A_3..., + env.A_4..., ) end @@ -333,14 +332,10 @@ function (env::FullInfiniteEnv)(x, ::Val{false}) # Linear map: env() * x env.E_7, env.E_8, x, - env.ket_1, - env.ket_2, - env.ket_3, - env.ket_4, - env.bra_1, - env.bra_2, - env.bra_3, - env.bra_4, + env.A_1..., + env.A_2..., + env.A_3..., + env.A_4..., ) end function (env::FullInfiniteEnv)(x, ::Val{true}) # Adjoint linear map: env()' * x @@ -358,14 +353,10 @@ function (env::FullInfiniteEnv)(x, ::Val{true}) # Adjoint linear map: env()' * env.E_6, env.E_7, env.E_8, - env.ket_1, - env.ket_2, - env.ket_3, - env.ket_4, - env.bra_1, - env.bra_2, - env.bra_3, - env.bra_4, + env.A_1..., + env.A_2..., + env.A_3..., + env.A_4..., ) end @@ -378,11 +369,11 @@ end # AbstractTensorMap subtyping and IterSVD compatibility function TensorKit.domain(env::FullInfiniteEnv) - return domain(env.E_8) * domain(env.ket_4)[3] * domain(env.bra_4)[3]' + return domain(env.E_8) * _domain_space(env.A_4, 3) end function TensorKit.codomain(env::FullInfiniteEnv) - return codomain(env.E_1)[1] * domain(env.ket_1)[3]' * domain(env.bra_1)[3] + return codomain(env.E_1)[1] * _codomain_space(env.A_1, 3) end function random_start_vector(env::FullInfiniteEnv) diff --git a/src/algorithms/toolbox.jl b/src/algorithms/toolbox.jl index b89a072a..d740ab1d 100644 --- a/src/algorithms/toolbox.jl +++ b/src/algorithms/toolbox.jl @@ -1,11 +1,41 @@ +""" + expectation_value(peps::InfinitePEPS, O::LocalOperator, envs::CTMRGEnv) + +Compute the expectation value ⟨peps|O|peps⟩ / ⟨peps|peps⟩ of a [`LocalOperator`](@ref) `O` +for a PEPS `peps` using a given CTMRG environment `envs`. +""" function MPSKit.expectation_value(peps::InfinitePEPS, O::LocalOperator, envs::CTMRGEnv) checklattice(peps, O) term_vals = dtmap([O.terms...]) do (inds, operator) # OhMyThreads can't iterate over O.terms directly - contract_localoperator(inds, operator, peps, peps, envs) / - contract_localnorm(inds, peps, peps, envs) + contract_local_operator(inds, operator, peps, peps, envs) / + contract_local_norm(inds, peps, peps, envs) end return sum(term_vals) end +""" + expectation_value(pf::InfinitePartitionFunction, inds => O, envs::CTMRGEnv) + +Compute the expectation value corresponding to inserting a local tensor(s) `O` at +position `inds` in the partition function `pf` and contracting the chole using a given CTMRG +environment `envs`. + +Here `inds` can be specified as either a `Tuple{Int,Int}` or a `CartesianIndex{2}`, and `O` +should be a rank-4 tensor conforming to the [`PartitionFunctionTensor`](@ref) indexing +convention. +""" +function MPSKit.expectation_value( + pf::InfinitePartitionFunction, + op::Pair{CartesianIndex{2},<:AbstractTensorMap{S,2,2}}, + envs::CTMRGEnv, +) where {S} + return contract_local_tensor(op[1], op[2], envs) / + contract_local_tensor(op[1], pf[op[1]], envs) +end +function MPSKit.expectation_value( + pf::InfinitePartitionFunction, op::Pair{Tuple{Int,Int}}, envs::CTMRGEnv +) + return expectation_value(pf, CartesianIndex(op[1]) => op[2], envs) +end function costfun(peps::InfinitePEPS, envs::CTMRGEnv, O::LocalOperator) E = MPSKit.expectation_value(peps, O, envs) @@ -57,6 +87,52 @@ function LinearAlgebra.norm(peps::InfinitePEPS, env::CTMRGEnv) return total end +""" + value(partfunc::InfinitePartitionFunction, env::CTMRGEnv) + +Return the value (per site) of a given partition function contracted using a given CTMRG +environment. +""" +function value(partfunc::InfinitePartitionFunction, env::CTMRGEnv) + total = one(scalartype(partfunc)) + + for r in 1:size(partfunc, 1), c in 1:size(partfunc, 2) + rprev = _prev(r, size(partfunc, 1)) + rnext = _next(r, size(partfunc, 1)) + cprev = _prev(c, size(partfunc, 2)) + cnext = _next(c, size(partfunc, 2)) + total *= @autoopt @tensor env.edges[WEST, r, cprev][χ1 D1; χ2] * + env.corners[NORTHWEST, rprev, cprev][χ2; χ3] * + env.edges[NORTH, rprev, c][χ3 D3; χ4] * + env.corners[NORTHEAST, rprev, cnext][χ4; χ5] * + env.edges[EAST, r, cnext][χ5 D5; χ6] * + env.corners[SOUTHEAST, rnext, cnext][χ6; χ7] * + env.edges[SOUTH, rnext, c][χ7 D7; χ8] * + env.corners[SOUTHWEST, rnext, cprev][χ8; χ1] * + partfunc[r, c][D1 D7; D3 D5] + total *= tr( + env.corners[NORTHWEST, rprev, cprev] * + env.corners[NORTHEAST, rprev, c] * + env.corners[SOUTHEAST, r, c] * + env.corners[SOUTHWEST, r, cprev], + ) + total /= @autoopt @tensor env.edges[WEST, r, cprev][χ1 D1; χ2] * + env.corners[NORTHWEST, rprev, cprev][χ2; χ3] * + env.corners[NORTHEAST, rprev, c][χ3; χ4] * + env.edges[EAST, r, c][χ4 D1; χ5] * + env.corners[SOUTHEAST, rnext, c][χ5; χ6] * + env.corners[SOUTHWEST, rnext, cprev][χ6; χ1] + total /= @autoopt @tensor env.corners[NORTHWEST, rprev, cprev][χ1; χ2] * + env.edges[NORTH, rprev, c][χ2 D1; χ3] * + env.corners[NORTHEAST, rprev, cnext][χ3; χ4] * + env.corners[SOUTHEAST, r, cnext][χ4; χ5] * + env.edges[SOUTH, r, c][χ5 D1; χ6] * + env.corners[SOUTHWEST, r, cprev][χ6; χ1] + end + + return total +end + """ correlation_length(peps::InfinitePEPS, env::CTMRGEnv; num_vals=2) diff --git a/src/environments/ctmrg_environments.jl b/src/environments/ctmrg_environments.jl index 97d890de..4b73e98c 100644 --- a/src/environments/ctmrg_environments.jl +++ b/src/environments/ctmrg_environments.jl @@ -7,15 +7,18 @@ cell, whereas the first index corresponds to the direction of the corner or edge directions are labeled in clockwise direction, starting from the north-west corner and north edge respectively. -Given arrays of corners `c` and edges `t`, they connect to the PEPS tensors at site `(r, c)` -in the unit cell as: +Given arrays of corners `c` and edges `t`, they connect to the partition function tensors +`P` at site `(r, c)` in the unit cell as: ``` c[1,r-1,c-1]---t[1,r-1,c]----c[2,r-1,c+1] - | || | - t[4,r,c-1]=====AA[r,c]=======t[2,r,c+1] - | || | + | | | + t[4,r,c-1]-----P[r,c]--------t[2,r,c+1] + | | | c[4,r+1,c-1]---t[3,r+1,c]----c[3,r+1,c+1] ``` +Here `P` represents an effective local constituent tensor. This can either be a single +rank-4 tensor, a pair of PEPS tensors, or a stack of PEPS-PEPO-PEPS tensors depending on the +partition function being contracted. # Fields - `corners::Array{C,3}`: Array of corner tensors. @@ -26,42 +29,43 @@ struct CTMRGEnv{C,T} edges::Array{T,3} end +const ElementarySpaceLike = Union{Int,ElementarySpace} +const ProductSpaceLike{N} = Union{NTuple{N,Int},NTuple{N,<:ElementarySpace}} +const SpaceLike = Union{ElementarySpaceLike,ProductSpaceLike} + _spacetype(::Int) = ComplexSpace _spacetype(::S) where {S<:ElementarySpace} = S +_spacetype(s::ProductSpaceLike) = _spacetype(first(s)) _to_space(χ::Int) = ℂ^χ _to_space(χ::ElementarySpace) = χ +_to_space(χ::ProductSpaceLike) = _to_space.(χ) function _corner_tensor( f, ::Type{T}, left_vspace::S, right_vspace::S=left_vspace -) where {T,S<:Union{Int,ElementarySpace}} +) where {T,S<:ElementarySpaceLike} return TensorMap(f, T, _to_space(left_vspace) ← _to_space(right_vspace)) end function _edge_tensor( - f, - ::Type{T}, - left_vspace::S, - top_pspace::S, - bot_pspace::S=top_pspace, - right_vspace::S=left_vspace, -) where {T,S<:Union{Int,ElementarySpace}} + f, ::Type{T}, left_vspace::S, pspaces::P, right_vspace::S=left_vspace +) where {T,S<:ElementarySpaceLike,P<:ProductSpaceLike} return TensorMap( f, T, - _to_space(left_vspace) ⊗ _to_space(top_pspace) ⊗ dual(_to_space(bot_pspace)) ← + prod([_to_space(left_vspace), _to_space.(pspaces)...]), _to_space(right_vspace), ) end """ CTMRGEnv( - [f=randn, ComplexF64], Ds_north, Ds_east::A, chis_north::A, [chis_east::A], [chis_south::A], [chis_west::A] - ) where {A<:AbstractMatrix{<:Union{Int,ElementarySpace}}} + [f=randn, ComplexF64], Ds_north::A, Ds_east::A, chis_north::B, [chis_east::B], [chis_south::B], [chis_west::B] + ) where {A<:AbstractMatrix{<:SpaceLike}, B<:AbstractMatrix{<:ElementarySpaceLike}} Construct a CTMRG environment by specifying matrices of north and east virtual spaces of the -corresponding [`InfinitePEPS`](@ref) and the north, east, south and west virtual spaces of -the environment. Each respective matrix entry corresponds to a site in the unit cell. By +corresponding partition function and the north, east, south and west virtual spaces of the +environment. Each respective matrix entry corresponds to a site in the unit cell. By default, the virtual environment spaces for all directions are taken to be the same. The environment virtual spaces for each site correspond to the north or east virtual space @@ -70,17 +74,32 @@ of the corresponding edge tensor for each direction. Specifically, for a given s `chis_east[r, c]` corresponds to the north space of the east edge tensor, `chis_south[r, c]` corresponds to the east space of the south edge tensor, and `chis_west[r, c]` corresponds to the north space of the west edge tensor. + +Each entry of the `Ds_north` and `Ds_east` matrices corresponds to an effective local space +of the partition function, represented as a tuple of elementary spaces encoding a product +space. This can either contain a single elementary space for the case of a partition +function defined in terms of local rank-4 tensors, or a tuple of elementary spaces +representing a product space for the case of a partition function representing overlaps of +PEPSs and PEPOs. """ function CTMRGEnv( Ds_north::A, Ds_east::A, - chis_north::A, - chis_east::A=chis_north, - chis_south::A=chis_north, - chis_west::A=chis_north, -) where {A<:AbstractMatrix{<:Union{Int,ElementarySpace}}} + chis_north::B, + chis_east::B=chis_north, + chis_south::B=chis_north, + chis_west::B=chis_north, +) where {A<:AbstractMatrix{<:ProductSpaceLike},B<:AbstractMatrix{<:ElementarySpaceLike}} return CTMRGEnv( - randn, ComplexF64, Ds_north, Ds_east, chis_north, chis_east, chis_south, chis_west + randn, + ComplexF64, + N, + Ds_north, + Ds_east, + chis_north, + chis_east, + chis_south, + chis_west, ) end function CTMRGEnv( @@ -88,18 +107,20 @@ function CTMRGEnv( T, Ds_north::A, Ds_east::A, - chis_north::A, - chis_east::A=chis_north, - chis_south::A=chis_north, - chis_west::A=chis_north, -) where {A<:AbstractMatrix{<:Union{Int,ElementarySpace}}} - Ds_south = adjoint.(circshift(Ds_north, (-1, 0))) - Ds_west = adjoint.(circshift(Ds_east, (0, 1))) + chis_north::B, + chis_east::B=chis_north, + chis_south::B=chis_north, + chis_west::B=chis_north, +) where {A<:AbstractMatrix{<:ProductSpaceLike},B<:AbstractMatrix{<:ElementarySpaceLike}} + # no recursive broadcasting? + Ds_south = (x -> adjoint.(x)).(circshift(Ds_north, (-1, 0))) + Ds_west = (x -> adjoint.(x)).(circshift(Ds_east, (0, 1))) # do the whole thing + N = length(first(Ds_north)) st = _spacetype(first(Ds_north)) C_type = tensormaptype(st, 1, 1, T) - T_type = tensormaptype(st, 3, 1, T) + T_type = tensormaptype(st, N + 1, 1, T) # First index is direction corners = Array{C_type}(undef, 4, size(Ds_north)...) @@ -108,36 +129,16 @@ function CTMRGEnv( for I in CartesianIndices(Ds_north) r, c = I.I edges[NORTH, r, c] = _edge_tensor( - f, - T, - chis_north[r, _prev(c, end)], - Ds_north[_next(r, end), c], - Ds_north[_next(r, end), c], - chis_north[r, c], + f, T, chis_north[r, _prev(c, end)], Ds_north[_next(r, end), c], chis_north[r, c] ) edges[EAST, r, c] = _edge_tensor( - f, - T, - chis_east[r, c], - Ds_east[r, _prev(c, end)], - Ds_east[r, _prev(c, end)], - chis_east[_next(r, end), c], + f, T, chis_east[r, c], Ds_east[r, _prev(c, end)], chis_east[_next(r, end), c] ) edges[SOUTH, r, c] = _edge_tensor( - f, - T, - chis_south[r, c], - Ds_south[_prev(r, end), c], - Ds_south[_prev(r, end), c], - chis_south[r, _prev(c, end)], + f, T, chis_south[r, c], Ds_south[_prev(r, end), c], chis_south[r, _prev(c, end)] ) edges[WEST, r, c] = _edge_tensor( - f, - T, - chis_west[_next(r, end), c], - Ds_west[r, _next(c, end)], - Ds_west[r, _next(c, end)], - chis_west[r, c], + f, T, chis_west[_next(r, end), c], Ds_west[r, _next(c, end)], chis_west[r, c] ) corners[NORTHWEST, r, c] = _corner_tensor( @@ -154,14 +155,13 @@ function CTMRGEnv( corners[:, :, :] ./= norm.(corners[:, :, :]) edges[:, :, :] ./= norm.(edges[:, :, :]) - return CTMRGEnv(corners, edges) end """ CTMRGEnv( - [f=randn, ComplexF64], D_north::S, D_east::S, chi_north::S, [chi_east::S], [chi_south::S], [chi_west::S]; unitcell::Tuple{Int,Int}=(1, 1), - ) where {S<:Union{Int,ElementarySpace}} + [f=randn, ComplexF64], D_north::P, D_east::P, chi_north::S, [chi_east::S], [chi_south::S], [chi_west::S]; unitcell::Tuple{Int,Int}=(1, 1), + ) where {P<:ProductSpaceLike,S<:ElementarySpaceLike} Construct a CTMRG environment by specifying the north and east virtual spaces of the corresponding [`InfinitePEPS`](@ref) and the north, east, south and west virtual spaces of @@ -172,14 +172,14 @@ The environment virtual spaces for each site correspond to virtual space of the corresponding edge tensor for each direction. """ function CTMRGEnv( - D_north::S, - D_east::S, + D_north::P, + D_east::P, chi_north::S, chi_east::S=chi_north, chi_south::S=chi_north, chi_west::S=chi_north; unitcell::Tuple{Int,Int}=(1, 1), -) where {S<:Union{Int,ElementarySpace}} +) where {P<:ProductSpaceLike,S<:Union{Int,ElementarySpace}} return CTMRGEnv( randn, ComplexF64, @@ -194,17 +194,18 @@ end function CTMRGEnv( f, T, - D_north::S, - D_east::S, + D_north::P, + D_east::P, chi_north::S, chi_east::S=chi_north, chi_south::S=chi_north, chi_west::S=chi_north; unitcell::Tuple{Int,Int}=(1, 1), -) where {S<:Union{Int,ElementarySpace}} +) where {P<:ProductSpaceLike,S<:Union{Int,ElementarySpace}} return CTMRGEnv( f, T, + N, fill(D_north, unitcell), fill(D_east, unitcell), fill(chi_north, unitcell), @@ -237,12 +238,12 @@ function CTMRGEnv( chis_east::A=chis_north, chis_south::A=chis_north, chis_west::A=chis_north, -) where {A<:AbstractMatrix{<:Union{Int,ElementarySpace}}} +) where {A<:AbstractMatrix{<:ElementarySpaceLike}} Ds_north = map(peps.A) do t - return adjoint(space(t, 2)) + return (adjoint(space(t, 2)), space(t, 2)) end Ds_east = map(peps.A) do t - return adjoint(space(t, 3)) + return (adjoint(space(t, 3)), space(t, 3)) end return CTMRGEnv( randn, @@ -263,12 +264,12 @@ function CTMRGEnv( chis_east::A=chis_north, chis_south::A=chis_north, chis_west::A=chis_north, -) where {A<:AbstractMatrix{<:Union{Int,ElementarySpace}}} +) where {A<:AbstractMatrix{<:ElementarySpaceLike}} Ds_north = map(peps.A) do t - return adjoint(space(t, 2)) + return (adjoint(space(t, 2)), space(t, 2)) end Ds_east = map(peps.A) do t - return adjoint(space(t, 3)) + return (adjoint(space(t, 3)), space(t, 3)) end return CTMRGEnv( f, @@ -282,6 +283,75 @@ function CTMRGEnv( ) end +""" + CTMRGEnv( + [f=randn, T=ComplexF64], partfunc::InfinitePartitionFunction, chis_north::A, [chis_east::A], [chis_south::A], [chis_west::A] + ) where {A<:AbstractMatrix{<:Union{Int,ElementarySpace}}} + +Construct a CTMRG environment by specifying a corresponding +[`InfinitePartitionFunction`](@ref), and the north, east, south and west virtual spaces of +the environment as matrices. Each respective matrix entry corresponds to a site in the unit +cell. By default, the virtual spaces for all directions are taken to be the same. + +The environment virtual spaces for each site correspond to the north or east virtual space +of the corresponding edge tensor for each direction. Specifically, for a given site +`(r, c)`, `chis_north[r, c]` corresponds to the east space of the north edge tensor, +`chis_east[r, c]` corresponds to the north space of the east edge tensor, +`chis_south[r, c]` corresponds to the east space of the south edge tensor, and +`chis_west[r, c]` corresponds to the north space of the west edge tensor. +""" +function CTMRGEnv( + partfunc::InfinitePartitionFunction, + chis_north::A, + chis_east::A=chis_north, + chis_south::A=chis_north, + chis_west::A=chis_north, +) where {A<:AbstractMatrix{<:ElementarySpaceLike}} + Ds_north = map(partfunc.A) do t + return (adjoint(space(t, 3)),) + end + Ds_east = map(partfunc.A) do t + return (adjoint(space(t, 4)),) + end + return CTMRGEnv( + randn, + ComplexF64, + Ds_north, + Ds_east, + _to_space.(chis_north), + _to_space.(chis_east), + _to_space.(chis_south), + _to_space.(chis_west), + ) +end +function CTMRGEnv( + f, + T, + partfunc::InfinitePartitionFunction, + chis_north::A, + chis_east::A=chis_north, + chis_south::A=chis_north, + chis_west::A=chis_north, +) where {A<:AbstractMatrix{<:ElementarySpaceLike}} + Ds_north = map(partfunc.A) do t + return (adjoint(space(t, 3)),) + end + Ds_east = map(partfunc.A) do t + return (adjoint(space(t, 4)),) + end + return CTMRGEnv( + f, + T, + N, + Ds_north, + Ds_east, + _to_space.(chis_north), + _to_space.(chis_east), + _to_space.(chis_south), + _to_space.(chis_west), + ) +end + """ CTMRGEnv( peps::InfinitePEPS, chi_north::S, [chi_east::S], [chi_south::S], [chi_west::S], @@ -300,7 +370,7 @@ function CTMRGEnv( chi_east::S=chi_north, chi_south::S=chi_north, chi_west::S=chi_north, -) where {S<:Union{Int,ElementarySpace}} +) where {S<:ElementarySpaceLike} return CTMRGEnv( peps, fill(chi_north, size(peps)), @@ -317,7 +387,7 @@ function CTMRGEnv( chi_east::S=chi_north, chi_south::S=chi_north, chi_west::S=chi_north, -) where {S<:Union{Int,ElementarySpace}} +) where {S<:ElementarySpaceLike} return CTMRGEnv( f, T, @@ -328,7 +398,56 @@ function CTMRGEnv( fill(chi_west, size(peps)), ) end + +""" + CTMRGEnv( + peps::InfinitePartitionFunction, chi_north::S, [chi_east::S], [chi_south::S], [chi_west::S], + ) where {S<:ElementarySpaceLike + +Construct a CTMRG environment by specifying a corresponding +[`InfinitePartitionFunction`](@ref), and the north, east, south and west virtual spaces of +the environment. By default, the virtual spaces for all directions are taken to be the same. + +The environment virtual spaces for each site correspond to virtual space of the +corresponding edge tensor for each direction. +""" +function CTMRGEnv( + partfunc::InfinitePartitionFunction, + chi_north::S, + chi_east::S=chi_north, + chi_south::S=chi_north, + chi_west::S=chi_north, +) where {S<:ElementarySpaceLike} + return CTMRGEnv( + partfunc, + fill(chi_north, size(partfunc)), + fill(chi_east, size(partfunc)), + fill(chi_south, size(partfunc)), + fill(chi_west, size(partfunc)), + ) +end +function CTMRGEnv( + f, + T, + partfunc::InfinitePartitionFunction, + chi_north::S, + chi_east::S=chi_north, + chi_south::S=chi_north, + chi_west::S=chi_north, +) where {S<:ElementarySpaceLike} + return CTMRGEnv( + f, + T, + partfunc, + fill(chi_north, size(partfunc)), + fill(chi_east, size(partfunc)), + fill(chi_south, size(partfunc)), + fill(chi_west, size(partfunc)), + ) +end + @non_differentiable CTMRGEnv(peps::InfinitePEPS, args...) +@non_differentiable CTMRGEnv(peps::InfinitePartitionFunction, args...) # Custom adjoint for CTMRGEnv constructor, needed for fixed-point differentiation function ChainRulesCore.rrule(::Type{CTMRGEnv}, corners, edges) @@ -437,14 +556,14 @@ function LinearAlgebra.mul!(edst::CTMRGEnv, esrc::CTMRGEnv, α::Number) end function LinearAlgebra.rmul!(e::CTMRGEnv, α::Number) - rmul!(e.corners, α) - rmul!(e.edges, α) + rmul!.(e.corners, α) + rmul!.(e.edges, α) return e end function LinearAlgebra.axpy!(α::Number, e₁::CTMRGEnv, e₂::CTMRGEnv) - e₂.corners .+= α * e₁.corners - e₂.edges .+= α * e₁.edges + axpy!.(α, e₁.corners, e₂.corners) + axpy!.(α, e₁.edges, e₂.edges) return e₂ end diff --git a/src/networks/infinitesquarenetwork.jl b/src/networks/infinitesquarenetwork.jl new file mode 100644 index 00000000..539fdab7 --- /dev/null +++ b/src/networks/infinitesquarenetwork.jl @@ -0,0 +1,115 @@ +""" + InfiniteSquareNetwork{T,N} + +Abstract infinite tensor network consisting of a translationally invariant unit cell +on a square lattice. +""" +abstract type InfiniteSquareNetwork{T,N} end + +## Shape and size +function unitcell(::InfiniteSquareNetwork) end # Return array of constituent tensors +Base.size(A::InfiniteSquareNetwork, args...) = size(unitcell(A), args...) +Base.length(A::InfiniteSquareNetwork) = length(unitcell(A)) +Base.eltype(::Type{<:InfiniteSquareNetwork{T}}) where {T} = T +Base.eltype(A::InfiniteSquareNetwork) = eltype(typeof(A)) + +## Copy +Base.copy(A::NWType) where {NWType<:InfiniteSquareNetwork} = NWType(copy(unitcell(A))) +function Base.similar(A::NWType, args...) where {NWType<:InfiniteSquareNetwork} + return NWType(similar(unitcell(A), args...)) +end +function Base.repeat(A::NWType, counts...) where {NWType<:InfiniteSquareNetwork} + return NWType(repeat(unitcell(A), counts...)) +end + +## Indexing +Base.getindex(A::InfiniteSquareNetwork, args...) = Base.getindex(unitcell(A), args...) +function Base.setindex!(A::InfiniteSquareNetwork, args...) + return (Base.setindex!(unitcell(A), args...); A) +end +Base.axes(A::InfiniteSquareNetwork, args...) = axes(unitcell(A), args...) +function eachcoordinate(A::InfiniteSquareNetwork) + return collect(Iterators.product(axes(A)...)) +end +function eachcoordinate(A::InfiniteSquareNetwork, dirs) + return collect(Iterators.product(dirs, axes(A, 1), axes(A, 2))) +end + +## Vector interface +function VectorInterface.scalartype(::Type{NWType}) where {NWType<:InfiniteSquareNetwork} + return scalartype(eltype(NWType)) +end +function VectorInterface.zerovector(A::NWType) where {NWType<:InfiniteSquareNetwork} + return NWType(zerovector(unitcell(A))) +end + +## Math +function Base.:+(A₁::NWType, A₂::NWType) where {NWType<:InfiniteSquareNetwork} + return NWType(unitcell(A₁) + unitcell(A₂)) +end +function Base.:-(A₁::NWType, A₂::NWType) where {NWType<:InfiniteSquareNetwork} + return NWType(unitcell(A₁) - unitcell(A₂)) +end +function Base.:*(α::Number, A::NWType) where {NWType<:InfiniteSquareNetwork} + return NWType(α * unitcell(A)) +end +function Base.:/(A::NWType, α::Number) where {NWType<:InfiniteSquareNetwork} + return NWType(unitcell(A) / α) +end +function LinearAlgebra.dot(A₁::InfiniteSquareNetwork, A₂::InfiniteSquareNetwork) + return dot(unitcell(A₁), unitcell(A₂)) +end +LinearAlgebra.norm(A::InfiniteSquareNetwork) = norm(unitcell(A)) + +## (Approximate) equality +function Base.:(==)(A₁::InfiniteSquareNetwork, A₂::InfiniteSquareNetwork) + return all(zip(unitcell(A₁), unitcell(A₂))) do (p₁, p₂) + return p₁ == p₂ + end +end +function Base.isapprox(A₁::InfiniteSquareNetwork, A₂::InfiniteSquareNetwork; kwargs...) + return all(zip(unitcell(A₁), unitcell(A₂))) do (p₁, p₂) + return isapprox(p₁, p₂; kwargs...) + end +end + +## Rotations +function Base.rotl90(A::NWType) where {NWType<:InfiniteSquareNetwork{<:Any,2}} # Rotations of matrix unit cells + return NWType(rotl90(rotl90.(unitcell(A)))) +end +function Base.rotr90(A::NWType) where {NWType<:InfiniteSquareNetwork{<:Any,2}} + return NWType(rotr90(rotr90.(unitcell(A)))) +end +function Base.rot180(A::NWType) where {NWType<:InfiniteSquareNetwork{<:Any,2}} + return NWType(rot180(rot180.(unitcell(A)))) +end +function Base.rotl90(A::NWType) where {NWType<:InfiniteSquareNetwork{<:Any,3}} # Rotations of cubic unit cells along z-axis + return NWType(stack(rotl90, eachslice(unitcell(A); dims=3))) +end +function Base.rotr90(A::NWType) where {NWType<:InfiniteSquareNetwork{<:Any,3}} + return NWType(stack(rotr90, eachslice(unitcell(A); dims=3))) +end +function Base.rot180(A::NWType) where {NWType<:InfiniteSquareNetwork{<:Any,3}} + return NWType(stack(rot180, eachslice(unitcell(A); dims=3))) +end + +## OptimKit optimization compatibility +function LinearAlgebra.rmul!(A::InfiniteSquareNetwork, α::Number) # Used in _scale during OptimKit.optimize + rmul!.(unitcell(A), α) + return A +end +function LinearAlgebra.axpy!( + α::Number, A₁::InfiniteSquareNetwork, A₂::InfiniteSquareNetwork +) # Used in _add during OptimKit.optimize + axpy!.(α, unitcell(A₁), unitcell(A₂)) + return A₂ +end + +## FiniteDifferences vectorization +function FiniteDifferences.to_vec(A::NWType) where {NWType<:InfiniteSquareNetwork} + vec, back = FiniteDifferences.to_vec(unitcell(A)) + function state_from_vec(vec) + return NWType(back(vec)) + end + return vec, state_from_vec +end diff --git a/src/states/abstractpeps.jl b/src/networks/tensors.jl similarity index 57% rename from src/states/abstractpeps.jl rename to src/networks/tensors.jl index 93a27142..e96a2d0e 100644 --- a/src/states/abstractpeps.jl +++ b/src/networks/tensors.jl @@ -1,9 +1,52 @@ +# +# Partition function +# + +""" + const PartitionFunctionTensor{S} + +Default type for partition function tensors with 4 virtual indices, conventionally ordered +as: ``T : W ⊗ S ← N ⊗ E``. Here, ``N``, ``E``, ``S`` and ``W`` denote the north, east, south +and west spaces, respectively. + +``` + N + ╱ + ╱ + W---- ----E + ╱ + ╱ + S +``` +""" +const PartitionFunctionTensor{S} = AbstractTensorMap{S,2,2} where {S<:ElementarySpace} +const PFTensor = PartitionFunctionTensor + +Base.rotl90(t::PFTensor) = permute(t, ((3, 1), (4, 2))) +Base.rotr90(t::PFTensor) = permute(t, ((2, 4), (1, 3))) +Base.rot180(t::PFTensor) = permute(t, ((4, 3), (2, 1))) + +# +# PEPS +# + """ const PEPSTensor{S} Default type for PEPS tensors with a single physical index, and 4 virtual indices, -conventionally ordered as: ``T : P ← N ⊗ E ⊗ S ⊗ W``. Here, ``P``, ``N``, ``E``, ``S`` and -``W`` denote the physics, north, east, south and west spaces, respectively. +conventionally ordered as: ``T : P ← N ⊗ E ⊗ S ⊗ W``. Here, ``P`` denotes the physical space +and ``N``, ``E``, ``S`` and ``W`` denote the north, east, south and west virtual spaces, +respectively. + +``` + N + ╱ + ╱ + W---- ----E + ╱| + ╱ | + S P +``` """ const PEPSTensor{S} = AbstractTensorMap{S,1,4} where {S<:ElementarySpace} @@ -40,32 +83,35 @@ function PEPSTensor( return TensorMap(f, T, ℂ^Pspace ← ℂ^Nspace ⊗ ℂ^Espace ⊗ (ℂ^Sspace)' ⊗ (ℂ^Wspace)') end -""" - const PEPOTensor{S} +Base.rotl90(t::PEPSTensor) = permute(t, ((1,), (3, 4, 5, 2))) +Base.rotr90(t::PEPSTensor) = permute(t, ((1,), (5, 2, 3, 4))) +Base.rot180(t::PEPSTensor) = permute(t, ((1,), (4, 5, 2, 3))) -Default type for PEPO tensors with a single incoming and outgoing physical index, and 4 -virtual indices, conventionally ordered as: O : P ⊗ P' ← N ⊗ E ⊗ S ⊗ W. -""" -const PEPOTensor{S} = AbstractTensorMap{S,2,4} where {S<:ElementarySpace} +# +# PEPO +# """ - abstract type AbstractPEPS end + const PEPOTensor{S} -Abstract supertype for a 2D projected entangled-pair state. -""" -abstract type AbstractPEPS end +Default type for PEPO tensors with a single incoming and outgoing physical index, and 4 +virtual indices, conventionally ordered as: ``T : P ⊗ P´ ← N ⊗ E ⊗ S ⊗ W``. Here, ``P´`` and +``P`` denote the incoming and outgoing physical space respectively, encoding the physical +mapping from ``P´'`` to ``P`` where ``P´'`` corresponds to a physical PEPS index. ``N``, +``E``, ``S`` and ``W`` denote the physics, north, east, south and west spaces, respectively. -""" - abstract type AbstractPEPO end -Abstract supertype for a 2D projected entangled-pair operator. +``` + P´ N + | ╱ + |╱ + W---- ----E + ╱| + ╱ | + S P +``` """ -abstract type AbstractPEPO end - -# Rotations -Base.rotl90(t::PEPSTensor) = permute(t, ((1,), (3, 4, 5, 2))) -Base.rotr90(t::PEPSTensor) = permute(t, ((1,), (5, 2, 3, 4))) -Base.rot180(t::PEPSTensor) = permute(t, ((1,), (4, 5, 2, 3))) +const PEPOTensor{S} = AbstractTensorMap{S,2,4} where {S<:ElementarySpace} Base.rotl90(t::PEPOTensor) = permute(t, ((1, 2), (4, 5, 6, 3))) Base.rotr90(t::PEPOTensor) = permute(t, ((1, 2), (6, 3, 4, 5))) diff --git a/src/operators/infinitepepo.jl b/src/operators/infinitepepo.jl index 6fad9fd3..3c4242d7 100644 --- a/src/operators/infinitepepo.jl +++ b/src/operators/infinitepepo.jl @@ -3,7 +3,7 @@ Represents an infinite projected entangled-pair operator (PEPO) on a 3D cubic lattice. """ -struct InfinitePEPO{T<:PEPOTensor} <: AbstractPEPO +struct InfinitePEPO{T<:PEPOTensor} <: InfiniteSquareNetwork{T,3} A::Array{T,3} function InfinitePEPO(A::Array{T,3}) where {T<:PEPOTensor} @@ -105,21 +105,7 @@ function InfinitePEPO( ) end -## Shape and size -Base.size(T::InfinitePEPO) = size(T.A) -Base.size(T::InfinitePEPO, i) = size(T.A, i) -Base.length(T::InfinitePEPO) = length(T.A) -Base.eltype(T::InfinitePEPO) = eltype(typeof(T)) -Base.eltype(::Type{<:InfinitePEPO{T}}) where {T} = T -VectorInterface.scalartype(::Type{T}) where {T<:InfinitePEPO} = scalartype(eltype(T)) - -## Copy -Base.copy(T::InfinitePEPO) = InfinitePEPO(copy(T.A)) -Base.similar(T::InfinitePEPO, args...) = InfinitePEPO(similar(T.A, args...)) -Base.repeat(T::InfinitePEPO, counts...) = InfinitePEPO(repeat(T.A, counts...)) - -Base.getindex(T::InfinitePEPO, args...) = Base.getindex(T.A, args...) -Base.axes(T::InfinitePEPO, args...) = axes(T.A, args...) +unitcell(T::InfinitePEPO) = T.A TensorKit.space(T::InfinitePEPO, i, j) = space(T[i, j, end], 1) function initializePEPS( @@ -134,8 +120,3 @@ function initializePEPS( Espaces = repeat([vspace], size(T, 1), size(T, 2)) return InfinitePEPS(Pspaces, Nspaces, Espaces) end - -# Rotations -Base.rotl90(T::InfinitePEPO) = InfinitePEPO(stack(rotl90, eachslice(T.A; dims=3))) -Base.rotr90(T::InfinitePEPO) = InfinitePEPO(stack(rotr90, eachslice(T.A; dims=3))) -Base.rot180(T::InfinitePEPO) = InfinitePEPO(stack(rot180, eachslice(T.A; dims=3))) diff --git a/src/states/infinitepartitionfunction.jl b/src/states/infinitepartitionfunction.jl new file mode 100644 index 00000000..f6b31fd0 --- /dev/null +++ b/src/states/infinitepartitionfunction.jl @@ -0,0 +1,153 @@ +""" + struct InfinitePartitionFunction{T<:PartitionFunctionTensor} + +Represents an infinite projected entangled-pair state on a 2D square lattice. +""" +struct InfinitePartitionFunction{T<:PartitionFunctionTensor} <: InfiniteSquareNetwork{T,2} + A::Matrix{T} + function InfinitePartitionFunction{T}(A::Matrix{T}) where {T<:PartitionFunctionTensor} + return new{T}(A) + end + function InfinitePartitionFunction(A::Array{T,2}) where {T<:PartitionFunctionTensor} + for (d, w) in Tuple.(CartesianIndices(A)) + space(A[d, w], 1) == space(A[_prev(d, end), w], 4)' || throw( + SpaceMismatch("North virtual space at site $((d, w)) does not match.") + ) + space(A[d, w], 2) == space(A[d, _next(w, end)], 3)' || + throw(SpaceMismatch("East virtual space at site $((d, w)) does not match.")) + dim(space(A[d, w])) > 0 || @warn "no fusion channels at site ($d, $w)" + end + return new{T}(A) + end +end + +const InfinitePF{T} = InfinitePartitionFunction{T} + +## Constructors +""" + InfinitePartitionFunction(A::AbstractMatrix{T}) + +Create an `InfinitePartitionFunction` by specifying a matrix containing the PEPS tensors at each site in +the unit cell. +""" +function InfinitePartitionFunction(A::AbstractMatrix{T}) where {T<:PartitionFunctionTensor} + return InfinitePartitionFunction(Array(deepcopy(A))) # TODO: find better way to copy +end + +""" + InfinitePartitionFunction( + f=randn, T=ComplexF64, Pspaces::A, Nspaces::A, [Espaces::A] + ) where {A<:AbstractMatrix{<:Union{Int,ElementarySpace}}} + +Create an `InfinitePartitionFunction` by specifying the physical, north virtual and east virtual spaces +of the PEPS tensor at each site in the unit cell as a matrix. Each individual space can be +specified as either an `Int` or an `ElementarySpace`. +""" +function InfinitePartitionFunction( + Nspaces::A, Espaces::A +) where {A<:AbstractMatrix{<:Union{Int,ElementarySpace}}} + return InfinitePartitionFunction(randn, ComplexF64, Nspaces, Espaces) +end +function InfinitePartitionFunction( + f, T, Nspaces::M, Espaces::M=Nspaces +) where {M<:AbstractMatrix{<:Union{Int,ElementarySpace}}} + size(Nspaces) == size(Espaces) || + throw(ArgumentError("Input spaces should have equal sizes.")) + + Sspaces = adjoint.(circshift(Nspaces, (-1, 0))) + Wspaces = adjoint.(circshift(Espaces, (0, 1))) + + A = map(Nspaces, Espaces, Sspaces, Wspaces) do P, N, E, S, W + return PartitionFunctionTensor(f, T, N, E, S, W) + end + + return InfinitePartitionFunction(A) +end + +""" + InfinitePartitionFunction(A; unitcell=(1, 1)) + +Create an `InfinitePartitionFunction` by specifying a tensor and unit cell. + +The unit cell is labeled as a matrix which means that any tensor in the unit cell, +regardless if partition function tensor or environment tensor, is obtained by shifting the row +and column index `[r, c]` by one, respectively: +``` + | | | +---C[r-1,c-1]---T[r-1,c]---T[r-1,c+1]--- + | | | +---T[r,c-1]-----AA[r,c]----AA[r,c+1]---- + | | | +---T[r+1,c-1]---AA[r+1,c]--AA[r+1,c+1]-- + | | | +``` +The unit cell has periodic boundary conditions, so `[r, c]` is indexed modulo the +size of the unit cell. +""" +function InfinitePartitionFunction( + A::T; unitcell::Tuple{Int,Int}=(1, 1) +) where {T<:PartitionFunctionTensor} + return InfinitePartitionFunction(fill(A, unitcell)) +end + +""" + InfinitePartitionFunction(f=randn, T=ComplexF64, Pspace, Nspace, [Espace]; unitcell=(1,1)) + +Create an InfinitePartitionFunction by specifying its physical, north and east spaces and unit cell. +Spaces can be specified either via `Int` or via `ElementarySpace`. +""" +function InfinitePartitionFunction( + Nspace::S, Espace::S=Nspace; unitcell::Tuple{Int,Int}=(1, 1) +) where {S<:Union{ElementarySpace,Int}} + return InfinitePartitionFunction( + randn, ComplexF64, fill(Nspace, unitcell), fill(Espace, unitcell) + ) +end +function InfinitePartitionFunction( + f, T, Nspace::S, Espace::S=Nspace; unitcell::Tuple{Int,Int}=(1, 1) +) where {S<:Union{ElementarySpace,Int}} + return InfinitePartitionFunction(f, T, fill(Nspace, unitcell), fill(Espace, unitcell)) +end + +unitcell(t::InfinitePartitionFunction) = t.A +TensorKit.space(t::InfinitePartitionFunction, i, j) = space(t[i, j], 1) + +# Chainrules +function ChainRulesCore.rrule( + ::typeof(Base.getindex), state::InfinitePartitionFunction, row::Int, col::Int +) + PartitionFunctionTensor = state[row, col] + + function getindex_pullback(ΔPartitionFunction) + Δstate = zerovector(state) + Δstate[row, col] = ΔPartitionFunction + return NoTangent(), Δstate, NoTangent(), NoTangent() + end + return PartitionFunctionTensor, getindex_pullback +end + +function ChainRulesCore.rrule( + ::Type{<:InfinitePartitionFunction}, A::Matrix{T} +) where {T<:PartitionFunctionTensor} + peps = InfinitePartitionFunction(A) + function InfinitePartitionFunction_pullback(Δpeps) + return NoTangent(), Δpeps.A + end + return peps, InfinitePartitionFunction_pullback +end + +function ChainRulesCore.rrule(::typeof(rotl90), peps::InfinitePartitionFunction) + peps′ = rotl90(peps) + function rotl90_pullback(Δpeps) + return NoTangent(), rotr90(Δpeps) + end + return peps′, rotl90_pullback +end + +function ChainRulesCore.rrule(::typeof(rotr90), peps::InfinitePartitionFunction) + peps′ = rotr90(peps) + function rotr90_pullback(Δpeps) + return NoTangent(), rotl90(Δpeps) + end + return peps′, rotr90_pullback +end diff --git a/src/states/infinitepeps.jl b/src/states/infinitepeps.jl index fea4ab1f..29409014 100644 --- a/src/states/infinitepeps.jl +++ b/src/states/infinitepeps.jl @@ -3,7 +3,7 @@ Represents an infinite projected entangled-pair state on a 2D square lattice. """ -struct InfinitePEPS{T<:PEPSTensor} <: AbstractPEPS +struct InfinitePEPS{T<:PEPSTensor} <: InfiniteSquareNetwork{T,2} A::Matrix{T} InfinitePEPS{T}(A::Matrix{T}) where {T<:PEPSTensor} = new{T}(A) function InfinitePEPS(A::Array{T,2}) where {T<:PEPSTensor} @@ -109,70 +109,9 @@ function InfinitePEPS( ) end -## Shape and size -Base.size(T::InfinitePEPS) = size(T.A) -Base.size(T::InfinitePEPS, i) = size(T.A, i) -Base.length(T::InfinitePEPS) = length(T.A) -Base.eltype(T::InfinitePEPS) = eltype(typeof(T)) -Base.eltype(::Type{<:InfinitePEPS{T}}) where {T} = T -VectorInterface.scalartype(::Type{T}) where {T<:InfinitePEPS} = scalartype(eltype(T)) - -## Copy -Base.copy(T::InfinitePEPS) = InfinitePEPS(copy(T.A)) -Base.similar(T::InfinitePEPS, args...) = InfinitePEPS(similar(T.A, args...)) -Base.repeat(T::InfinitePEPS, counts...) = InfinitePEPS(repeat(T.A, counts...)) - -Base.getindex(T::InfinitePEPS, args...) = Base.getindex(T.A, args...) -Base.setindex!(T::InfinitePEPS, args...) = (Base.setindex!(T.A, args...); T) -Base.axes(T::InfinitePEPS, args...) = axes(T.A, args...) -function eachcoordinate(x::InfinitePEPS) - return collect(Iterators.product(axes(x)...)) -end -function eachcoordinate(x::InfinitePEPS, dirs) - return collect(Iterators.product(dirs, axes(x, 1), axes(x, 2))) -end +unitcell(t::InfinitePEPS) = t.A TensorKit.space(t::InfinitePEPS, i, j) = space(t[i, j], 1) -## Math -Base.:+(ψ₁::InfinitePEPS, ψ₂::InfinitePEPS) = InfinitePEPS(ψ₁.A + ψ₂.A) -Base.:-(ψ₁::InfinitePEPS, ψ₂::InfinitePEPS) = InfinitePEPS(ψ₁.A - ψ₂.A) -Base.:*(α::Number, ψ::InfinitePEPS) = InfinitePEPS(α * ψ.A) -Base.:/(ψ::InfinitePEPS, α::Number) = InfinitePEPS(ψ.A / α) -LinearAlgebra.dot(ψ₁::InfinitePEPS, ψ₂::InfinitePEPS) = dot(ψ₁.A, ψ₂.A) -LinearAlgebra.norm(ψ::InfinitePEPS) = norm(ψ.A) - -## (Approximate) equality -function Base.:(==)(ψ₁::InfinitePEPS, ψ₂::InfinitePEPS) - return all(zip(ψ₁.A, ψ₂.A)) do (p₁, p₂) - return p₁ == p₂ - end -end -function Base.isapprox(ψ₁::InfinitePEPS, ψ₂::InfinitePEPS; kwargs...) - return all(zip(ψ₁.A, ψ₂.A)) do (p₁, p₂) - return isapprox(p₁, p₂; kwargs...) - end -end - -# Used in _scale during OptimKit.optimize -function LinearAlgebra.rmul!(ψ::InfinitePEPS, α::Number) - rmul!(ψ.A, α) - return ψ -end - -# Used in _add during OptimKit.optimize -function LinearAlgebra.axpy!(α::Number, ψ₁::InfinitePEPS, ψ₂::InfinitePEPS) - ψ₂.A .+= α * ψ₁.A - return ψ₂ -end - -# VectorInterface -VectorInterface.zerovector(x::InfinitePEPS) = InfinitePEPS(zerovector(x.A)) - -# Rotations -Base.rotl90(t::InfinitePEPS) = InfinitePEPS(rotl90(rotl90.(t.A))) -Base.rotr90(t::InfinitePEPS) = InfinitePEPS(rotr90(rotr90.(t.A))) -Base.rot180(t::InfinitePEPS) = InfinitePEPS(rot180(rot180.(t.A))) - # Chainrules function ChainRulesCore.rrule( ::typeof(Base.getindex), state::InfinitePEPS, row::Int, col::Int @@ -210,13 +149,3 @@ function ChainRulesCore.rrule(::typeof(rotr90), peps::InfinitePEPS) end return peps′, rotr90_pullback end - -# FiniteDifferences -# Makes use of tensors already having a to_vec method -function FiniteDifferences.to_vec(state::InfinitePEPS) - vec, back = FiniteDifferences.to_vec(state.A) - function state_from_vec(vec) - return InfinitePEPS(back(vec)) - end - return vec, state_from_vec -end diff --git a/src/states/infiniteweightpeps.jl b/src/states/infiniteweightpeps.jl index 91303bed..24c85633 100644 --- a/src/states/infiniteweightpeps.jl +++ b/src/states/infiniteweightpeps.jl @@ -50,12 +50,12 @@ function compare_weights(wts1::SUWeight, wts2::SUWeight) end """ - struct InfiniteWeightPEPS{T<:PEPSTensor,E<:PEPSWeight} <: AbstractPEPS + struct InfiniteWeightPEPS{T<:PEPSTensor,E<:PEPSWeight} Represents an infinite projected entangled-pair state on a 2D square lattice consisting of vertex tensors and bond weights. """ -struct InfiniteWeightPEPS{T<:PEPSTensor,E<:PEPSWeight} <: AbstractPEPS +struct InfiniteWeightPEPS{T<:PEPSTensor,E<:PEPSWeight} vertices::Matrix{T} weights::SUWeight{E} diff --git a/test/ctmrg/partition_function.jl b/test/ctmrg/partition_function.jl new file mode 100644 index 00000000..8995d6ab --- /dev/null +++ b/test/ctmrg/partition_function.jl @@ -0,0 +1,118 @@ +using Test +using Random +using PEPSKit +using TensorKit +using LinearAlgebra +using QuadGK +using MPSKit + +## Setup + +""" + ising_exact(beta, J) + +[Exact Onsager solution](https://en.wikipedia.org/wiki/Square_lattice_Ising_model#Exact_solution) +for the 2D classical Ising Model with partition function + +```math +\\mathcal{Z}(\\beta) = \\sum_{\\{s\\}} \\exp(-\\beta H(s)) \\text{ with } H(s) = -J \\sum_{\\langle i, j \\rangle} s_i s_j +``` +""" +function classical_ising_exact(; beta=log(1 + sqrt(2)) / 2, J=1.0) + K = beta * J + + k = 1 / sinh(2 * K)^2 + F = quadgk( + theta -> log(cosh(2 * K)^2 + 1 / k * sqrt(1 + k^2 - 2 * k * cos(2 * theta))), 0, pi + )[1] + f = -1 / beta * (log(2) / 2 + 1 / (2 * pi) * F) + + m = 1 - (sinh(2 * K))^(-4) > 0 ? (1 - (sinh(2 * K))^(-4))^(1 / 8) : 0 + + E = quadgk(theta -> 1 / sqrt(1 - (4 * k) * (1 + k)^(-2) * sin(theta)^2), 0, pi / 2)[1] + e = -J * cosh(2 * K) / sinh(2 * K) * (1 + 2 / pi * (2 * tanh(2 * K)^2 - 1) * E) + + return f, m, e +end + +""" + classical_ising(; beta=log(1 + sqrt(2)) / 2) + +Implements the 2D classical Ising model with partition function + +```math +\\mathcal{Z}(\\beta) = \\sum_{\\{s\\}} \\exp(-\\beta H(s)) \\text{ with } H(s) = -J \\sum_{\\langle i, j \\rangle} s_i s_j +``` +""" +function classical_ising(; beta=log(1 + sqrt(2)) / 2, J=1.0) + K = beta * J + # Boltzmann weights + t = ComplexF64[exp(K) exp(-K); exp(-K) exp(K)] + r = eigen(t) + nt = r.vectors * sqrt(LinearAlgebra.Diagonal(r.values)) * r.vectors + + # local partition function tensor + O = zeros(2, 2, 2, 2) + O[1, 1, 1, 1] = 1 + O[2, 2, 2, 2] = 1 + @tensor o[-1 -2; -3 -4] := O[3 4; 2 1] * nt[-3; 3] * nt[-4; 4] * nt[-2; 2] * nt[-1; 1] + + # magnetization tensor + M = copy(O) + M[2, 2, 2, 2] *= -1 + @tensor m[-1 -2; -3 -4] := M[1 2; 3 4] * nt[-1; 1] * nt[-2; 2] * nt[-3; 3] * nt[-4; 4] + + # bond interaction tensor and energy-per-site tensor + e = ComplexF64[-J J; J -J] .* nt + @tensor e_hor[-1 -2; -3 -4] := + O[1 2; 3 4] * nt[-1; 1] * nt[-2; 2] * nt[-3; 3] * e[-4; 4] + @tensor e_vert[-1 -2; -3 -4] := + O[1 2; 3 4] * nt[-1; 1] * nt[-2; 2] * e[-3; 3] * nt[-4; 4] + e = e_hor + e_vert + + # fixed tensor map space for all three + TMS = ℂ^2 ⊗ ℂ^2 ← ℂ^2 ⊗ ℂ^2 + + return TensorMap(o, TMS), TensorMap(m, TMS), TensorMap(e, TMS) +end + +## Test + +# initialize +beta = 0.6 +O, M, E = classical_ising(; beta) +Z = InfinitePartitionFunction(O) +Random.seed!(81812781143) + +# contract +χenv = ℂ^12 +env0 = CTMRGEnv(Z, χenv) + +# cover all different flavors +ctm_styles = [SequentialCTMRG, SimultaneousCTMRG] +projector_algs = [HalfInfiniteProjector, FullInfiniteProjector] + +@testset "Classical Ising partition function using $ctm_style with $projector_alg" for ( + ctm_style, projector_alg +) in Iterators.product( + ctm_styles, projector_algs +) + ctm_alg = ctm_style(; maxiter=150, projector_alg) + env = leading_boundary(env0, Z, ctm_alg) + + # check observables + λ = PEPSKit.value(Z, env) + m = expectation_value(Z, (1, 1) => M, env) + e = expectation_value(Z, (1, 1) => E, env) + f_exact, m_exact, e_exact = classical_ising_exact(; beta) + + # should be real-ish + @test abs(imag(λ)) < 1e-4 + @test abs(imag(m)) < 1e-4 + @test abs(imag(e)) < 1e-4 + + # should match exact solution + @test -log(λ) / beta ≈ f_exact rtol = 1e-4 + @test abs(m) ≈ abs(m_exact) rtol = 1e-4 + @test e ≈ e_exact rtol = 1e-1 # accuracy limited by bond dimension and maxiter +end diff --git a/test/runtests.jl b/test/runtests.jl index c80a4b43..b8e3ac97 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -29,6 +29,9 @@ end @time @safetestset "Flavors" begin include("ctmrg/flavors.jl") end + @time @safetestset "Partition function" begin + include("ctmrg/partition_function.jl") + end end if GROUP == "ALL" || GROUP == "BOUNDARYMPS" @time @safetestset "VUMPS" begin