From c101c796c78f0200db5d758a996eb33a0be6518e Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Mon, 6 Apr 2026 17:36:13 +0800 Subject: [PATCH 01/41] Change ALS tensor axis order --- .../contractions/bondenv/als_solve.jl | 113 +++++++----------- .../contractions/bondenv/gaugefix.jl | 6 +- src/algorithms/time_evolution/apply_mpo.jl | 55 +++++---- src/algorithms/truncation/bond_truncation.jl | 19 +-- .../truncation/fullenv_truncation.jl | 21 +--- test/bondenv/bond_truncate.jl | 6 +- 6 files changed, 91 insertions(+), 129 deletions(-) diff --git a/src/algorithms/contractions/bondenv/als_solve.jl b/src/algorithms/contractions/bondenv/als_solve.jl index c244c7370..af083f618 100644 --- a/src/algorithms/contractions/bondenv/als_solve.jl +++ b/src/algorithms/contractions/bondenv/als_solve.jl @@ -19,11 +19,9 @@ Construct the tensor └-----------------------------------┘ ``` """ -function _tensor_Ra( - benv::BondEnv{T, S}, b::AbstractTensorMap{T, S, 2, 1} - ) where {T <: Number, S <: ElementarySpace} +function _tensor_Ra(benv::BondEnv, b::MPSTensor) return @autoopt @tensor Ra[DX1 Db1; DX0 Db0] := ( - benv[DX1 DY1; DX0 DY0] * b[Db0 DY0; db] * conj(b[Db1 DY1; db]) + benv[DX1 DY1; DX0 DY0] * b[Db0 db; DY0] * conj(b[Db1 db; DY1]) ) end @@ -44,10 +42,10 @@ Construct the tensor ``` """ function _tensor_Sa( - benv::BondEnv{T, S}, b::AbstractTensorMap{T, S, 2, 1}, a2b2::AbstractTensorMap{T, S, 2, 2} + benv::BondEnv, b::MPSTensor, a2b2::AbstractTensorMap{T, S, 2, 2} ) where {T <: Number, S <: ElementarySpace} - return @autoopt @tensor Sa[DX1 Db1; da] := ( - benv[DX1 DY1; DX0 DY0] * conj(b[Db1 DY1; db]) * a2b2[DX0 DY0; da db] + return @autoopt @tensor Sa[DX1 da; Db1] := ( + benv[DX1 DY1; DX0 DY0] * conj(b[Db1 db; DY1]) * a2b2[DX0 DY0; da db] ) end @@ -67,11 +65,9 @@ Construct the tensor └-----------------------------------┘ ``` """ -function _tensor_Rb( - benv::BondEnv{T, S}, a::AbstractTensorMap{T, S, 2, 1} - ) where {T <: Number, S <: ElementarySpace} +function _tensor_Rb(benv::BondEnv, a::MPSTensor) return @autoopt @tensor Rb[Da1 DY1; Da0 DY0] := ( - benv[DX1 DY1; DX0 DY0] * a[DX0 Da0; da] * conj(a[DX1 Da1; da]) + benv[DX1 DY1; DX0 DY0] * a[DX0 da; Da0] * conj(a[DX1 da; Da1]) ) end @@ -92,10 +88,10 @@ Construct the tensor ``` """ function _tensor_Sb( - benv::BondEnv{T, S}, a::AbstractTensorMap{T, S, 2, 1}, a2b2::AbstractTensorMap{T, S, 2, 2} + benv::BondEnv, a::MPSTensor, a2b2::AbstractTensorMap{T, S, 2, 2} ) where {T <: Number, S <: ElementarySpace} - return @autoopt @tensor Sb[Da1 DY1; db] := ( - benv[DX1 DY1; DX0 DY0] * conj(a[DX1 Da1; da]) * a2b2[DX0 DY0; da db] + return @autoopt @tensor Sb[Da1 db; DY1] := ( + benv[DX1 DY1; DX0 DY0] * conj(a[DX1 da; Da1]) * a2b2[DX0 DY0; da db] ) end @@ -116,30 +112,10 @@ Calculate the inner product ``` """ function inner_prod( - benv::BondEnv{T, S}, a1b1::AbstractTensorMap{T, S, 2, 2}, a2b2::AbstractTensorMap{T, S, 2, 2} + benv::BondEnv, a1b1::AbstractTensorMap{T, S, 2, 2}, a2b2::AbstractTensorMap{T, S, 2, 2} ) where {T <: Number, S <: ElementarySpace} return @autoopt @tensor benv[DX1 DY1; DX0 DY0] * - conj(a1b1[DX1 DY1; da db]) * - a2b2[DX0 DY0; da db] -end - -""" -$(SIGNATURES) - -Calculate the fidelity between two evolution steps -``` - |⟨a1,b1|a2,b2⟩|^2 - -------------------------- - ⟨a1,b1|a1,b1⟩⟨a2,b2|a2,b2⟩ -``` -""" -function fidelity( - benv::BondEnv{T, S}, a1b1::AbstractTensorMap{T, S, 2, 2}, a2b2::AbstractTensorMap{T, S, 2, 2} - ) where {T <: Number, S <: ElementarySpace} - b12 = inner_prod(benv, a1b1, a2b2) - b11 = inner_prod(benv, a1b1, a1b1) - b22 = inner_prod(benv, a2b2, a2b2) - return abs2(b12) / abs(b11 * b22) + conj(a1b1[DX1 DY1; da db]) * a2b2[DX0 DY0; da db] end """ @@ -153,14 +129,12 @@ Contract the axis between `a` and `b` tensors ``` """ function _combine_ab( - a::AbstractTensorMap{T, S, 2, 1}, b::AbstractTensorMap{T, S, 1, 2} + a::MPSTensor, b::AbstractTensorMap{T, S, 1, 2} ) where {T <: Number, S <: ElementarySpace} return @tensor ab[DX DY; da db] := a[DX da; D] * b[D; db DY] end -function _combine_ab( - a::AbstractTensorMap{T, S, 2, 1}, b::AbstractTensorMap{T, S, 2, 1} - ) where {T <: Number, S <: ElementarySpace} - return @tensor ab[DX DY; da db] := a[DX D; da] * b[D DY; db] +function _combine_ab(a::MPSTensor, b::MPSTensor) + return @tensor ab[DX DY; da db] := a[DX da; D] * b[D db; DY] end """ @@ -168,41 +142,40 @@ $(SIGNATURES) Calculate the cost function ``` - f(a,b) = ‖ |a1,b1⟩ - |a2,b2⟩ ‖^2 - = ⟨a1,b1|a1,b1⟩ - 2 Re⟨a1,b1|a2,b2⟩ + ⟨a2,b2|a2,b2⟩ + f(a,b) = ‖ |ψ1⟩ - |ψ2⟩ ‖^2 + = ⟨ψ1|benv|ψ1⟩ - 2 Re⟨ψ1|benv|ψ2⟩ + ⟨ψ2|benv|ψ2⟩ +``` +and the fidelity +``` + |⟨ψ1|benv|ψ2⟩|² + ------------------------ + ⟨ψ1|benv|ψ1⟩⟨ψ2|benv|ψ2⟩ ``` """ -function cost_function_als( - benv::BondEnv{T, S}, a1b1::AbstractTensorMap{T, S, 2, 2}, a2b2::AbstractTensorMap{T, S, 2, 2} - ) where {T <: Number, S <: ElementarySpace} - t1 = inner_prod(benv, a1b1, a1b1) - t2 = inner_prod(benv, a2b2, a2b2) - t3 = inner_prod(benv, a1b1, a2b2) - return real(t1) + real(t2) - 2 * real(t3) +function cost_function_als(benv, ψ1, ψ2) + b12 = inner_prod(benv, ψ1, ψ2) + b11 = inner_prod(benv, ψ1, ψ1) + b22 = inner_prod(benv, ψ2, ψ2) + cost = real(b11) + real(b22) - 2 * real(b12) + fid = abs2(b12) / abs(b11 * b22) + return cost, fid end """ $(SIGNATURES) -Solve the equations `Rx x = Sx` (x = a, b) with initial guess `x0` -``` - ┌---------------------------┐ - | ┌----┐ | - └---| |--- 1 -- x -- 2 --┘ - | | ↓ - | Rx | -3 - | | - ┌---| |--- -1 -2 --┐ - | └----┘ | - └---------------------------┘ -``` -""" -function _solve_ab( - Rx::AbstractTensorMap{T, S, 2, 2}, - Sx::AbstractTensorMap{T, S, 2, 1}, - x0::AbstractTensorMap{T, S, 2, 1}, - ) where {T <: Number, S <: ElementarySpace} - f(x) = (@tensor Sx2[-1 -2; -3] := Rx[-1 -2; 1 2] * x[1 2; -3]) - x1, info = linsolve(f, Sx, x0, 0, 1) +Solve the equations `Rx x = Sx` with initial guess `x0`. +""" +function _solve_als( + Rx::AbstractTensorMap{T, S, N, N}, + Sx::GenericMPSTensor{S, N}, + x0::GenericMPSTensor{S, N}; kwargs... + ) where {T, S, N} + @assert N >= 2 + pR = (codomainind(Rx), domainind(Rx)) + pX = ((1, (3:(N + 1))...), (2,)) + pRX = ((1, N + 1, (2:(N - 1))...), (N,)) + f(x) = tensorcontract(Rx, pR, false, x, pX, false, pRX) + x1, info = linsolve(f, Sx, x0, 0, 1; kwargs...) return x1, info end diff --git a/src/algorithms/contractions/bondenv/gaugefix.jl b/src/algorithms/contractions/bondenv/gaugefix.jl index bfd0f7f01..0572d4852 100644 --- a/src/algorithms/contractions/bondenv/gaugefix.jl +++ b/src/algorithms/contractions/bondenv/gaugefix.jl @@ -1,5 +1,5 @@ """ -Replace bond environment `benv` by its positive approximant `Z† Z` +Find positive approximant `Z† Z` of a norm tensor `benv` (returns the "half environment" `Z`) ``` ┌-----------------┐ ┌---------------┐ @@ -11,9 +11,9 @@ Replace bond environment `benv` by its positive approximant `Z† Z` └-----------------┘ └---------------┘ ``` """ -function positive_approx(benv::BondEnv) +function positive_approx(benv::AbstractTensorMap{T, S, N, N}) where {T, S, N} # eigen-decomposition: benv = U D U' - D, U = eigh_full((benv + benv') / 2) + D, U = eigh_full!(project_hermitian(benv)) # determine if `env` is (mostly) positive or negative sgn = sign(sum(D.data)) # When optimizing the truncation of a bond, diff --git a/src/algorithms/time_evolution/apply_mpo.jl b/src/algorithms/time_evolution/apply_mpo.jl index b60818530..b7ae6f42f 100644 --- a/src/algorithms/time_evolution/apply_mpo.jl +++ b/src/algorithms/time_evolution/apply_mpo.jl @@ -112,7 +112,7 @@ Then the fidelity is just ``` =# """ -Perform QR decomposition through a PEPS tensor +Perform QR decomposition through a `GenericMPSTensor` ``` ╱ ╱ -←-R0-←-M-←- => ---Q-←-R1-←- @@ -120,19 +120,22 @@ Perform QR decomposition through a PEPS tensor ``` """ function qr_through( - R0::MPSBondTensor, M::GenericMPSTensor{S, 4}; normalize::Bool = true - ) where {S <: ElementarySpace} + R0::MPSBondTensor, M::GenericMPSTensor{S, N}; normalize::Bool = true + ) where {S, N} @assert !isdual(codomain(R0, 1)) @assert !isdual(domain(M, 1)) && !isdual(codomain(M, 1)) - @tensor A[-1 -2 -3 -4; -5] := R0[-1; 1] * M[1 -2 -3 -4; -5] + pR = (codomainind(R0), domainind(R0)) + pM = ((1,), Tuple(2:(N + 1))) + pRM = (codomainind(M), domainind(M)) + A = tensorcontract(R0, pR, false, M, pM, false, pRM) _, r = left_orth!(A; positive = true) normalize && normalize!(r, Inf) return r end # for `M` at the left end of the MPS function qr_through( - ::Nothing, M::GenericMPSTensor{S, 4}; normalize::Bool = true - ) where {S <: ElementarySpace} + ::Nothing, M::GenericMPSTensor{S, N}; normalize::Bool = true + ) where {S, N} @assert !isdual(domain(M, 1)) _, r = left_orth(M; positive = true) normalize && normalize!(r, Inf) @@ -140,7 +143,7 @@ function qr_through( end """ -Perform LQ decomposition through a tensor +Perform LQ decomposition through a `GenericMPSTensor` ``` ╱ ╱ -←-L0-←-Q-←- <= -←-M-←-L1-←- @@ -148,21 +151,24 @@ Perform LQ decomposition through a tensor ``` """ function lq_through( - M::GenericMPSTensor{S, 4}, L1::MPSBondTensor; normalize::Bool = true - ) where {S <: ElementarySpace} + M::GenericMPSTensor{S, N}, L1::MPSBondTensor; normalize::Bool = true + ) where {S, N} @assert !isdual(domain(L1, 1)) @assert !isdual(codomain(M, 1)) && !isdual(domain(M, 1)) - @tensor A[-1; -2 -3 -4 -5] := M[-1 -2 -3 -4; 1] * L1[1; -5] + pM = (codomainind(M), domainind(M)) + pL = (codomainind(L1), domainind(L1)) + pML = ((1,), Tuple(2:(N + 1))) + A = tensorcontract(M, pM, false, L1, pL, false, pML) l, _ = right_orth!(A; positive = true) normalize && normalize!(l, Inf) return l end # for `M` at the right end of the MPS function lq_through( - M::GenericMPSTensor{S, 4}, ::Nothing; normalize::Bool = true - ) where {S <: ElementarySpace} + M::GenericMPSTensor{S, N}, ::Nothing; normalize::Bool = true + ) where {S, N} @assert !isdual(codomain(M, 1)) - A = permute(M, ((1,), (2, 3, 4, 5))) + A = permute(M, ((1,), Tuple(2:(N + 1))); copy = true) l, _ = right_orth!(A; positive = true) normalize && normalize!(l, Inf) return l @@ -171,7 +177,7 @@ end """ Given a cluster `Ms`, find all `R`, `L` matrices on each internal bond """ -function _get_allRLs(Ms::Vector{T}) where {T <: GenericMPSTensor{<:ElementarySpace, 4}} +function _get_allRLs(Ms::Vector{T}) where {T <: GenericMPSTensor} # M1 -- (R1,L1) -- M2 -- (R2,L2) -- M3 N = length(Ms) # get the first R and the last L @@ -214,11 +220,14 @@ function _proj_from_RL( end """ -Given a cluster `Ms` and the pre-calculated `R`, `L` bond matrices, -find all projectors `Pa`, `Pb` and Schmidt weights `wts` on internal bonds. +Given a cluster `Ms`, find all projectors `Pa`, `Pb` +and Schmidt weights `wts` on internal bonds. """ -function _get_allprojs(Ms, Rs, Ls, truncs::Vector{E}) where {E <: TruncationStrategy} +function _get_allprojs( + Ms::Vector{T}, truncs::Vector{E} + ) where {T <: GenericMPSTensor, E <: TruncationStrategy} N = length(Ms) + Rs, Ls = _get_allRLs(Ms) @assert length(truncs) == N - 1 projs_errs = map(1:(N - 1)) do i trunc = if isa(truncs[i], FixedSpaceTruncation) @@ -258,14 +267,16 @@ Find projectors to truncate internal bonds of the cluster `Ms`. """ function _cluster_truncate!( Ms::Vector{T}, truncs::Vector{E} - ) where {T <: GenericMPSTensor{<:ElementarySpace, 4}, E <: TruncationStrategy} - Rs, Ls = _get_allRLs(Ms) - Pas, Pbs, wts, ϵs = _get_allprojs(Ms, Rs, Ls, truncs) + ) where {T <: GenericMPSTensor, E <: TruncationStrategy} + Pas, Pbs, wts, ϵs = _get_allprojs(Ms, truncs) # apply projectors # M1 -- (Pa1,wt1,Pb1) -- M2 -- (Pa2,wt2,Pb2) -- M3 for (i, (Pa, Pb)) in enumerate(zip(Pas, Pbs)) - @tensor (Ms[i])[-1 -2 -3 -4; -5] := (Ms[i])[-1 -2 -3 -4; 1] * Pa[1; -5] - @tensor (Ms[i + 1])[-1 -2 -3 -4; -5] := Pb[-1; 1] * (Ms[i + 1])[1 -2 -3 -4; -5] + Ms[i] = Ms[i] * twistdual(Pa, 1) + pP = ((1,), (2,)) + pM = ((1,), Tuple(2:numind(Ms[i + 1]))) + pPM = (codomainind(Ms[i + 1]), domainind(Ms[i + 1])) + Ms[i + 1] = tensorcontract(Pb, pP, false, Ms[i + 1], pM, false, pPM) end return wts, ϵs, Pas, Pbs end diff --git a/src/algorithms/truncation/bond_truncation.jl b/src/algorithms/truncation/bond_truncation.jl index d8feb8cb3..57fd68cc7 100644 --- a/src/algorithms/truncation/bond_truncation.jl +++ b/src/algorithms/truncation/bond_truncation.jl @@ -74,17 +74,11 @@ function bond_truncate( perm_ab = ((1, 3), (4, 2)) a, s0, b = svd_trunc(permute(a2b2, perm_ab); trunc = alg.trunc) a, b = absorb_s(a, s0, b) - #= temporarily reorder axes of a and b to - 1 -a/b- 2 - ↓ - 3 - =# - perm = ((1, 3), (2,)) - a, b = permute(a, perm), permute(b, perm) + # put b in MPS axis order + b = permute(b, ((1, 2), (3,))) ab = _combine_ab(a, b) # cost function will be normalized by initial value - cost00 = cost_function_als(benv, ab, a2b2) - fid = fidelity(benv, ab, a2b2) + cost00, fid = cost_function_als(benv, ab, a2b2) cost0, fid0, Δcost, Δfid, Δs = cost00, fid, NaN, NaN, NaN verbose && @info "ALS init" * _als_message(0, cost0, fid, Δcost, Δfid, Δs, 0.0) for iter in 1:(alg.maxiter) @@ -99,15 +93,14 @@ function bond_truncate( =# Ra = _tensor_Ra(benv, b) Sa = _tensor_Sa(benv, b, a2b2) - a, info_a = _solve_ab(Ra, Sa, a) + a, info_a = _solve_als(Ra, Sa, a) # Fixing `a`, solve for `b` from `Rb b = Sb` Rb = _tensor_Rb(benv, a) Sb = _tensor_Sb(benv, a, a2b2) - b, info_b = _solve_ab(Rb, Sb, b) + b, info_b = _solve_als(Rb, Sb, b) @debug "Bond truncation info" info_a info_b ab = _combine_ab(a, b) - cost = cost_function_als(benv, ab, a2b2) - fid = fidelity(benv, ab, a2b2) + cost, fid = cost_function_als(benv, ab, a2b2) # TODO: replace with truncated svdvals (without calculating u, vh) _, s, _ = svd_trunc!(permute(ab, perm_ab); trunc = alg.trunc) # fidelity, cost and normalized bond-s change diff --git a/src/algorithms/truncation/fullenv_truncation.jl b/src/algorithms/truncation/fullenv_truncation.jl index ea339c8d8..fd9c685bd 100644 --- a/src/algorithms/truncation/fullenv_truncation.jl +++ b/src/algorithms/truncation/fullenv_truncation.jl @@ -49,24 +49,7 @@ between two states specified by the bond matrices `b1`, `b2` function inner_prod( benv::BondEnv{T, S}, b1::AbstractTensorMap{T, S, 1, 1}, b2::AbstractTensorMap{T, S, 1, 1} ) where {T <: Number, S <: ElementarySpace} - val = @tensor conj(b1[1; 2]) * benv[1 2; 3 4] * b2[3; 4] - return val -end - -""" -$(SIGNATURES) - -Given the bond environment `benv`, calculate the fidelity -between two states specified by the bond matrices `b1`, `b2` -``` - F(b1, b2) = (⟨b1|b2⟩ ⟨b2|b1⟩) / (⟨b1|b1⟩ ⟨b2|b2⟩) -``` -""" -function fidelity( - benv::BondEnv{T, S}, b1::AbstractTensorMap{T, S, 1, 1}, b2::AbstractTensorMap{T, S, 1, 1} - ) where {T <: Number, S <: ElementarySpace} - return abs2(inner_prod(benv, b1, b2)) / - real(inner_prod(benv, b1, b1) * inner_prod(benv, b2, b2)) + return @tensor conj(b1[1; 2]) * benv[1 2; 3 4] * b2[3; 4] end """ @@ -252,7 +235,7 @@ function fullenv_truncate( l, info_l = linsolve(Base.Fix1(*, B), p, l, 0, 1) @debug "Bond truncation info" info_l info_r @tensor b1[-1; -2] = l[-1 1] * vh[1; -2] - fid = fidelity(benv, b0, b1) + _, fid = cost_function_als(benv, b0, b1) u, s, vh = svd_trunc!(b1; trunc = alg.trunc) # determine convergence s_nrm = norm(s0, Inf) diff --git a/test/bondenv/bond_truncate.jl b/test/bondenv/bond_truncate.jl index 58b0a4158..25f56c200 100644 --- a/test/bondenv/bond_truncate.jl +++ b/test/bondenv/bond_truncate.jl @@ -5,6 +5,7 @@ using TensorKit using PEPSKit using LinearAlgebra using KrylovKit +using PEPSKit: cost_function_als Random.seed!(0) maxiter = 600 @@ -19,6 +20,7 @@ for Vbondl in (Vint, Vint'), Vbondr in (Vint, Vint') # random positive-definite environment Z = randn(Float64, Vext ← Vbond) benv = Z' * Z + normalize!(benv, Inf) # untruncated bond tensor a2b2 = randn(Float64, Vbondl ⊗ Vbondr ← Vphy' ⊗ Vphy') a2, s, b2 = svd_compact(permute(a2b2, perm_ab)) @@ -26,7 +28,7 @@ for Vbondl in (Vint, Vint'), Vbondr in (Vint, Vint') # bond tensor (truncated SVD initialization) a0, s, b0 = svd_trunc(permute(a2b2, perm_ab); trunc = trunc) a0, b0 = PEPSKit.absorb_s(a0, s, b0) - fid0 = PEPSKit.fidelity(benv, PEPSKit._combine_ab(a0, b0), a2b2) + fid0 = cost_function_als(benv, PEPSKit._combine_ab(a0, b0), a2b2)[2] @info "Fidelity of simple SVD truncation = $fid0.\n" ss = Dict{String, DiagonalTensorMap}() for (label, alg) in ( @@ -36,7 +38,7 @@ for Vbondl in (Vint, Vint'), Vbondr in (Vint, Vint') a1, ss[label], b1, info = PEPSKit.bond_truncate(a2, b2, benv, alg) @info "$label improved fidelity = $(info.fid)." # display(ss[label]) - @test info.fid ≈ PEPSKit.fidelity(benv, PEPSKit._combine_ab(a1, b1), a2b2) + @test info.fid ≈ cost_function_als(benv, PEPSKit._combine_ab(a1, b1), a2b2)[2] @test info.fid > fid0 end @test isapprox(ss["ALS"], ss["FET"], atol = 1.0e-3) From 2fdd026f4544b214c1de1c815dd7c9192051d904 Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Mon, 6 Apr 2026 17:59:50 +0800 Subject: [PATCH 02/41] bondenv_ctm for PEPO --- .../contractions/bondenv/benv_ctm.jl | 44 ++++++----- .../contractions/bondenv/benv_tools.jl | 10 +++ test/bondenv/benv_ctm.jl | 73 ++++++++++++------- 3 files changed, 80 insertions(+), 47 deletions(-) diff --git a/src/algorithms/contractions/bondenv/benv_ctm.jl b/src/algorithms/contractions/bondenv/benv_ctm.jl index 9b97c1576..45262cc67 100644 --- a/src/algorithms/contractions/bondenv/benv_ctm.jl +++ b/src/algorithms/contractions/bondenv/benv_ctm.jl @@ -1,14 +1,16 @@ """ Construct the environment (norm) tensor ``` - C1---T1---------T1---C2 r-1 - | ‖ ‖ | - T4===XX== ==YY===T2 r - | ‖ ‖ | - C4---T3---------T3---C3 r+1 - c-1 c c+1 c+2 + -1 C1---E1---------E1---C2 + | ‖ ‖ | + 0 E4===XX== ==YY===E2 + | ‖ ‖ | + 1 C4---E3---------E3---C3 + -1 0 1 2 ``` -where `XX = X' X` and `YY = Y' Y` (stacked together). +with `X` at position `[row, col]`. +`X, Y` are unitary tensors produced when finding the reduced site tensors +with `_qr_bond`; and `XX = X' X` and `YY = Y' Y` (stacked together). Axis order: `[DX1 DY1; DX0 DY0]`, as in ``` @@ -21,7 +23,9 @@ Axis order: `[DX1 DY1; DX0 DY0]`, as in └---------------------┘ ``` """ -function bondenv_ctm(row::Int, col::Int, X::PEPSOrth, Y::PEPSOrth, env::CTMRGEnv) +function bondenv_ctm( + row::Int, col::Int, X::T, Y::T, env::CTMRGEnv + ) where {T} Nr, Nc = size(env.corners)[[2, 3]] cm1 = _prev(col, Nc) cp1 = _next(col, Nc) @@ -32,29 +36,29 @@ function bondenv_ctm(row::Int, col::Int, X::PEPSOrth, Y::PEPSOrth, env::CTMRGEnv c2 = env.corners[2, rm1, cp2] c3 = env.corners[3, rp1, cp2] c4 = env.corners[4, rp1, cm1] - t1X, t1Y = env.edges[1, rm1, col], env.edges[1, rm1, cp1] - t2 = env.edges[2, row, cp2] - t3X, t3Y = env.edges[3, rp1, col], env.edges[3, rp1, cp1] - t4 = env.edges[4, row, cm1] + e1X, e1Y = env.edges[1, rm1, col], env.edges[1, rm1, cp1] + e2 = env.edges[2, row, cp2] + e3X, e3Y = env.edges[3, rp1, col], env.edges[3, rp1, cp1] + e4 = env.edges[4, row, cm1] #= index labels - C1--χ4--T1X---------χ6---------T1Y--χ8---C2 r-1 + C1--χ4--E1X---------χ6---------E1Y--χ8---C2 r-1 | ‖ ‖ | χ2 DNX DNY χ10 | ‖ ‖ | - T4==DWX==XX===DX== ==DY===YY==DEY==T2 r + E4==DWX==XX===DX== ==DY===YY==DEY==E2 r | ‖ ‖ | χ1 DSX DSY χ9 | ‖ ‖ | - C4--χ3--T3X---------χ5---------T3Y--χ7---C3 r+1 + C4--χ3--E3X---------χ5---------E3Y--χ7---C3 r+1 c-1 c c+1 c+2 =# + X, Y = _prepare_site_tensor(X), _prepare_site_tensor(Y) @autoopt @tensor benv[DX1 DY1; DX0 DY0] := - c4[χ3 χ1] * t4[χ1 DWX0 DWX1 χ2] * c1[χ2 χ4] * t3X[χ5 DSX0 DSX1 χ3] * - X[DNX0 DX0 DSX0 DWX0] * conj(X[DNX1 DX1 DSX1 DWX1]) * t1X[χ4 DNX0 DNX1 χ6] * - c3[χ9 χ7] * t2[χ10 DEY0 DEY1 χ9] * c2[χ8 χ10] * t3Y[χ7 DSY0 DSY1 χ5] * - Y[DNY0 DEY0 DSY0 DY0] * conj(Y[DNY1 DEY1 DSY1 DY1]) * t1Y[χ6 DNY0 DNY1 χ8] - + c4[χ3 χ1] * e4[χ1 DWX0 DWX1 χ2] * c1[χ2 χ4] * e3X[χ5 DSX0 DSX1 χ3] * + twistdual(X, 1)[pX DNX0 DX0 DSX0 DWX0] * conj(X[pX DNX1 DX1 DSX1 DWX1]) * e1X[χ4 DNX0 DNX1 χ6] * + c3[χ9 χ7] * e2[χ10 DEY0 DEY1 χ9] * c2[χ8 χ10] * e3Y[χ7 DSY0 DSY1 χ5] * + twistdual(Y, 1)[pY DNY0 DEY0 DSY0 DY0] * conj(Y[pY DNY1 DEY1 DSY1 DY1]) * e1Y[χ6 DNY0 DNY1 χ8] normalize!(benv, Inf) return benv end diff --git a/src/algorithms/contractions/bondenv/benv_tools.jl b/src/algorithms/contractions/bondenv/benv_tools.jl index 91ad5958e..38400b919 100644 --- a/src/algorithms/contractions/bondenv/benv_tools.jl +++ b/src/algorithms/contractions/bondenv/benv_tools.jl @@ -1,3 +1,13 @@ const BondEnv{T, S} = AbstractTensorMap{T, S, 2, 2} where {T <: Number, S <: ElementarySpace} +const BondEnv3site{T, S} = AbstractTensorMap{T, S, 4, 4} where {T <: Number, S <: ElementarySpace} +const Hair{T, S} = AbstractTensor{T, S, 2} where {T <: Number, S <: ElementarySpace} +# Orthogonal tensors obtained PEPSTensor/PEPOTensor +# with one physical leg factored out by `_qr_bond` const PEPSOrth{T, S} = AbstractTensor{T, S, 4} where {T <: Number, S <: ElementarySpace} const PEPOOrth{T, S} = AbstractTensor{T, S, 5} where {T <: Number, S <: ElementarySpace} + +"Convert tensor `t` connected by the bond to be truncated to a `PEPSTensor`." +_prepare_site_tensor(t::PEPSTensor) = t +_prepare_site_tensor(t::PEPOTensor) = first(fuse_physicalspaces(t)) +_prepare_site_tensor(t::PEPSOrth) = permute(insertleftunit(t, 1), ((1,), (2, 3, 4, 5))) +_prepare_site_tensor(t::PEPOOrth) = permute(t, ((1,), (2, 3, 4, 5))) diff --git a/test/bondenv/benv_ctm.jl b/test/bondenv/benv_ctm.jl index c6406ba43..7676c5139 100644 --- a/test/bondenv/benv_ctm.jl +++ b/test/bondenv/benv_ctm.jl @@ -7,8 +7,12 @@ using Random Random.seed!(100) Nr, Nc = 2, 2 +Envspace = Vect[FermionParity ⊠ U1Irrep]( + (0, 0) => 4, (1, 1 // 2) => 1, (1, -1 // 2) => 1, (0, 1) => 1, (0, -1) => 1 +) +ctm_alg = SequentialCTMRG(; tol = 1.0e-10, verbosity = 2, trunc = truncerror(; atol = 1.0e-10) & truncrank(8)) # create Hubbard iPEPS using simple update -function get_hubbard_state(t::Float64 = 1.0, U::Float64 = 8.0) +function get_hubbard_peps(t::Float64 = 1.0, U::Float64 = 8.0) H = hubbard_model(ComplexF64, Trivial, U1Irrep, InfiniteSquare(Nr, Nc); t, U, mu = U / 2) Vphy = Vect[FermionParity ⊠ U1Irrep]((0, 0) => 2, (1, 1 // 2) => 1, (1, -1 // 2) => 1) peps = InfinitePEPS(rand, ComplexF64, Vphy, Vphy; unitcell = (Nr, Nc)) @@ -20,30 +24,45 @@ function get_hubbard_state(t::Float64 = 1.0, U::Float64 = 8.0) return peps end -peps = get_hubbard_state() -# calculate CTMRG environment -Envspace = Vect[FermionParity ⊠ U1Irrep]( - (0, 0) => 4, (1, 1 // 2) => 1, (1, -1 // 2) => 1, (0, 1) => 1, (0, -1) => 1 -) -ctm_alg = SequentialCTMRG(; tol = 1.0e-10, verbosity = 2, trunc = truncerror(; atol = 1.0e-10) & truncrank(8)) -env, = leading_boundary(CTMRGEnv(rand, ComplexF64, peps, Envspace), peps, ctm_alg) -for row in 1:Nr, col in 1:Nc - cp1 = PEPSKit._next(col, Nc) - A, B = peps.A[row, col], peps.A[row, cp1] - X, a, b, Y = PEPSKit._qr_bond(A, B) - benv = PEPSKit.bondenv_ctm(row, col, X, Y, env) - @assert [isdual(space(benv, ax)) for ax in 1:numind(benv)] == [0, 0, 1, 1] - Z = PEPSKit.positive_approx(benv) - # verify that gauge fixing can greatly reduce - # condition number for physical state bond envs - cond1 = cond(Z' * Z) - Z2, a2, b2, (Linv, Rinv) = PEPSKit.fixgauge_benv(Z, a, b) - benv2 = Z2' * Z2 - cond2 = cond(benv2) - @test 1 <= cond2 < cond1 - @info "benv cond number: (gauge-fixed) $(cond2) ≤ $(cond1) (initial)" - # verify gauge fixing is done correctly - @tensor half[:] := Z[-1; 1 3] * a[1; -2 2] * b[2 -3; 3] - @tensor half2[:] := Z2[-1; 1 3] * a2[1; -2 2] * b2[2 -3; 3] - @test half ≈ half2 +function get_hubbard_pepo(t::Float64 = 1.0, U::Float64 = 8.0) + H = hubbard_model(ComplexF64, Trivial, U1Irrep, InfiniteSquare(Nr, Nc); t, U, mu = U / 2) + pepo = PEPSKit.infinite_temperature_density_matrix(H) + wts = SUWeight(pepo) + alg = SimpleUpdate(; + trunc = truncerror(; atol = 1.0e-10) & truncrank(4), bipartite = false + ) + pepo, = time_evolve(pepo, H, 2.0e-3, 500, alg, wts; check_interval = 100) + normalize!.(pepo.A, Inf) + return pepo +end + +function test_benv_ctm(state::Union{InfinitePEPS, InfinitePEPO}) + network = isa(state, InfinitePEPS) ? state : InfinitePEPS(state) + env, = leading_boundary(CTMRGEnv(rand, ComplexF64, network, Envspace), network, ctm_alg) + for row in 1:Nr, col in 1:Nc + cp1 = PEPSKit._next(col, Nc) + A, B = state.A[row, col], state.A[row, cp1] + X, a, b, Y = PEPSKit._qr_bond(A, B) + benv = PEPSKit.bondenv_ctm(row, col, X, Y, env) + Z = PEPSKit.positive_approx(benv) + # verify that gauge fixing can greatly reduce + # condition number for physical state bond envs + cond1 = cond(Z' * Z) + Z2, a2, b2, (Linv, Rinv) = PEPSKit.fixgauge_benv(Z, a, b) + benv2 = Z2' * Z2 + cond2 = cond(benv2) + @test 1 <= cond2 < cond1 + @info "benv cond number: (gauge-fixed) $(cond2) ≤ $(cond1) (initial)" + # verify gauge fixing is done correctly + @tensor half[:] := Z[-1; 1 3] * a[1; -2 2] * b[2 -3; 3] + @tensor half2[:] := Z2[-1; 1 3] * a2[1; -2 2] * b2[2 -3; 3] + @test half ≈ half2 + end + return +end + +peps = get_hubbard_peps() +pepo = get_hubbard_pepo() +for state in (peps, pepo) + test_benv_ctm(state) end From 1a13c39d8beb03713caae94b1a8d2b3441aa83c4 Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Mon, 6 Apr 2026 20:25:37 +0800 Subject: [PATCH 03/41] Rotation of LocalCircuit --- src/PEPSKit.jl | 1 + src/algorithms/time_evolution/trotter_gate.jl | 113 --------------- src/operators/localcircuit.jl | 137 ++++++++++++++++++ test/runtests.jl | 3 + test/types/localcircuit.jl | 21 +++ 5 files changed, 162 insertions(+), 113 deletions(-) create mode 100644 src/operators/localcircuit.jl create mode 100644 test/types/localcircuit.jl diff --git a/src/PEPSKit.jl b/src/PEPSKit.jl index a0317a6b1..f1c537ee6 100644 --- a/src/PEPSKit.jl +++ b/src/PEPSKit.jl @@ -61,6 +61,7 @@ include("utility/symmetrization.jl") include("operators/infinitepepo.jl") include("operators/transfermatrix.jl") include("operators/localoperator.jl") +include("operators/localcircuit.jl") include("operators/lattices/squarelattice.jl") include("operators/models.jl") diff --git a/src/algorithms/time_evolution/trotter_gate.jl b/src/algorithms/time_evolution/trotter_gate.jl index 84a4882b5..24248ad9c 100644 --- a/src/algorithms/time_evolution/trotter_gate.jl +++ b/src/algorithms/time_evolution/trotter_gate.jl @@ -1,116 +1,3 @@ -""" -$(TYPEDEF) - -Circuit consisting of local gates and MPOs. - -## Fields - -$(TYPEDFIELDS) -""" -struct LocalCircuit{O, S} - "lattice of physical spaces on which the gates act" - lattice::Matrix{S} - - "list of `sites => gate` pairs that make up the circuit" - gates::Vector{Pair{Vector{CartesianIndex{2}}, O}} - - LocalCircuit{O, S}(lattice::Matrix{S}) where {O, S} = - new{O, S}(lattice, Vector{Pair{Vector{CartesianIndex{2}}, O}}()) -end - -LocalCircuit{O}(lattice::Matrix{<:ElementarySpace}) where {O} = - LocalCircuit{O, eltype(lattice)}(lattice) -LocalCircuit{O}(lattice, gates::Pair...) where {O} = LocalCircuit{O}(lattice, gates) - -function LocalCircuit{O}(lattice, terms) where {O} - operator = LocalCircuit{O}(lattice) - for (inds, term) in terms - add_factor!(operator, inds, term) - end - return operator -end - -# Default to Any for eltype: needs to be abstract anyways so not that much to gain -LocalCircuit(lattice, terms) = LocalCircuit{Any}(lattice, terms) -LocalCircuit(lattice, terms::Pair...) = LocalCircuit(lattice, terms) - -add_factor!(operator::LocalCircuit, inds::Tuple, term::AbstractTensorMap) = add_factor!(operator, collect(inds), term) -add_factor!(operator::LocalCircuit, inds::Vector, term::AbstractTensorMap) = add_factor!(operator, map(CartesianIndex{2}, inds), term) -function add_factor!(operator::LocalCircuit, inds::Vector{CartesianIndex{2}}, term::AbstractTensorMap) - # input checks - length(inds) == numin(term) == numout(term) || throw(ArgumentError("Incompatible number of indices and tensor legs")) - for (i, ind) in enumerate(inds) - ind_translated = CartesianIndex(mod1.(Tuple(ind), size(operator))) - physicalspace(operator, ind_translated) == domain(term)[i] == codomain(term)[i] || - throw(SpaceMismatch("Incompatible physical spaces at $(ind).")) - end - - # permute input - if !issorted(inds) - I = sortperm(inds) - inds = inds[I] - term = permute(term, (Tuple(I), Tuple(I) .+ numout(term))) - end - - # translate coordinates - I1 = first(inds) - I1_mod = CartesianIndex(mod1.(Tuple(I1), size(operator))) - inds .-= (I1 - I1_mod) - - push!(operator.gates, inds => term) - - return operator -end -# for MPO term -# TODO: consider directly use MPSKit.FiniteMPO -function add_factor!( - operator::LocalCircuit, inds::Vector{CartesianIndex{2}}, term::Vector{M} - ) where {M <: AbstractTensorMap} - # input checks - length(inds) >= 2 || throw(ArgumentError("Gate MPO must act on 2 or more sites.")) - length(inds) == length(term) || throw(ArgumentError("Incompatible number of indices and length of gate MPO.")) - allunique(inds) || throw(ArgumentError("`inds` should not contain repeated coordinates.")) - for (i, (ind, t)) in enumerate(zip(inds, term)) - ind_translated = CartesianIndex(mod1.(Tuple(ind), size(operator))) - out_ax = (i == 1) ? 1 : 2 - in_ax = (i == 1) ? 2 : 3 - physicalspace(operator, ind_translated) == space(t, out_ax) == space(t, in_ax)' || - throw(SpaceMismatch("Incompatible physical spaces at $(ind).")) - if i >= 2 - ind_prev = inds[i - 1] - sum(Tuple(ind - ind_prev) .^ 2) == 1 || throw(ArgumentError("Two consecutive sites in `inds` must be nearest neighbours for MPO terms.")) - end - end - # for MPO term, `inds` should not be sorted - push!(operator.gates, inds => term) - return operator -end - -function checklattice(::Type{Bool}, H1::LocalCircuit, H2::LocalCircuit) - return physicalspace(H1) == physicalspace(H2) -end -function checklattice(::Type{Bool}, peps::InfinitePEPS, O::LocalCircuit) - return physicalspace(peps) == physicalspace(O) -end -function checklattice(::Type{Bool}, H::LocalCircuit, peps::InfinitePEPS) - return checklattice(Bool, peps, H) -end -function checklattice(::Type{Bool}, pepo::InfinitePEPO, O::LocalCircuit) - return size(pepo, 3) == 1 && physicalspace(pepo) == physicalspace(O) -end -function checklattice(::Type{Bool}, O::LocalCircuit, pepo::InfinitePEPO) - return checklattice(Bool, pepo, O) -end - -""" - physicalspace(gates::LocalCircuit) - -Return lattice of physical spaces on which the `LocalCircuit` is defined. -""" -physicalspace(gates::LocalCircuit) = gates.lattice -physicalspace(gates::LocalCircuit, args...) = physicalspace(gates)[args...] -Base.size(gates::LocalCircuit) = size(physicalspace(gates)) - const NNGate{T, S} = AbstractTensorMap{T, S, 2, 2} """ diff --git a/src/operators/localcircuit.jl b/src/operators/localcircuit.jl new file mode 100644 index 000000000..f60a17ad2 --- /dev/null +++ b/src/operators/localcircuit.jl @@ -0,0 +1,137 @@ +""" +$(TYPEDEF) + +Circuit consisting of local gates and MPOs. + +## Fields + +$(TYPEDFIELDS) +""" +struct LocalCircuit{O, S} + "lattice of physical spaces on which the gates act" + lattice::Matrix{S} + + "list of `sites => gate` pairs that make up the circuit" + gates::Vector{Pair{Vector{CartesianIndex{2}}, O}} + + LocalCircuit{O, S}(lattice::Matrix{S}) where {O, S} = + new{O, S}(lattice, Vector{Pair{Vector{CartesianIndex{2}}, O}}()) +end + +LocalCircuit{O}(lattice::Matrix{<:ElementarySpace}) where {O} = + LocalCircuit{O, eltype(lattice)}(lattice) +LocalCircuit{O}(lattice, gates::Pair...) where {O} = LocalCircuit{O}(lattice, gates) + +function LocalCircuit{O}(lattice, terms) where {O} + operator = LocalCircuit{O}(lattice) + for (inds, term) in terms + add_factor!(operator, inds, term) + end + return operator +end + +# Default to Any for eltype: needs to be abstract anyways so not that much to gain +LocalCircuit(lattice, terms) = LocalCircuit{Any}(lattice, terms) +LocalCircuit(lattice, terms::Pair...) = LocalCircuit(lattice, terms) + +add_factor!(operator::LocalCircuit, inds::Tuple, term::AbstractTensorMap) = add_factor!(operator, collect(inds), term) +add_factor!(operator::LocalCircuit, inds::Vector, term::AbstractTensorMap) = add_factor!(operator, map(CartesianIndex{2}, inds), term) +# for AbstractTensorMap term +function add_factor!(operator::LocalCircuit, inds::Vector{CartesianIndex{2}}, term::AbstractTensorMap) + # input checks + length(inds) == numin(term) == numout(term) || throw(ArgumentError("Incompatible number of indices and tensor legs")) + for (i, ind) in enumerate(inds) + ind_translated = CartesianIndex(mod1.(Tuple(ind), size(operator))) + physicalspace(operator, ind_translated) == domain(term)[i] == codomain(term)[i] || + throw(SpaceMismatch("Incompatible physical spaces at $(ind).")) + end + # permute input + if !issorted(inds) + I = sortperm(inds) + inds = inds[I] + term = permute(term, (Tuple(I), Tuple(I) .+ numout(term))) + end + # translate coordinates + I1 = first(inds) + I1_mod = CartesianIndex(mod1.(Tuple(I1), size(operator))) + inds .-= (I1 - I1_mod) + push!(operator.gates, inds => term) + return operator +end +# for MPO term +# TODO: consider directly use MPSKit.FiniteMPO +function add_factor!( + operator::LocalCircuit, inds::Vector{CartesianIndex{2}}, term::Vector{M} + ) where {M <: AbstractTensorMap} + # input checks + length(inds) >= 2 || throw(ArgumentError("Gate MPO must act on 2 or more sites.")) + length(inds) == length(term) || throw(ArgumentError("Incompatible number of indices and length of gate MPO.")) + allunique(inds) || throw(ArgumentError("`inds` should not contain repeated coordinates.")) + for (i, (ind, t)) in enumerate(zip(inds, term)) + ind_translated = CartesianIndex(mod1.(Tuple(ind), size(operator))) + out_ax = (i == 1) ? 1 : 2 + in_ax = (i == 1) ? 2 : 3 + physicalspace(operator, ind_translated) == space(t, out_ax) == space(t, in_ax)' || + throw(SpaceMismatch("Incompatible physical spaces at $(ind).")) + if i >= 2 + ind_prev = inds[i - 1] + sum(Tuple(ind - ind_prev) .^ 2) == 1 || throw(ArgumentError("Two consecutive sites in `inds` must be nearest neighbours for MPO terms.")) + end + end + # for MPO term, `inds` should not be sorted + push!(operator.gates, inds => term) + return operator +end + +function checklattice(::Type{Bool}, H1::LocalCircuit, H2::LocalCircuit) + return physicalspace(H1) == physicalspace(H2) +end +function checklattice(::Type{Bool}, peps::InfinitePEPS, O::LocalCircuit) + return physicalspace(peps) == physicalspace(O) +end +function checklattice(::Type{Bool}, H::LocalCircuit, peps::InfinitePEPS) + return checklattice(Bool, peps, H) +end +function checklattice(::Type{Bool}, pepo::InfinitePEPO, O::LocalCircuit) + return size(pepo, 3) == 1 && physicalspace(pepo) == physicalspace(O) +end +function checklattice(::Type{Bool}, O::LocalCircuit, pepo::InfinitePEPO) + return checklattice(Bool, pepo, O) +end + +""" + physicalspace(gates::LocalCircuit) + +Return lattice of physical spaces on which the `LocalCircuit` is defined. +""" +physicalspace(gates::LocalCircuit) = gates.lattice +physicalspace(gates::LocalCircuit, args...) = physicalspace(gates)[args...] +Base.size(gates::LocalCircuit) = size(physicalspace(gates)) + +# Equality +# ----------- + +Base.:(==)(O₁::LocalCircuit, O₂::LocalCircuit) = + physicalspace(O₁) == physicalspace(O₂) && O₁.gates == O₂.gates + +# Rotation +# ---------------------- + +function Base.rotr90(H::LocalCircuit) + Hsize = size(H) + lattice2 = rotr90(physicalspace(H)) + terms2 = (siterotr90.(inds, Ref(Hsize)) => term for (inds, term) in H.gates) + return LocalCircuit(lattice2, terms2) +end +function Base.rotl90(H::LocalCircuit) + Hsize = size(H) + lattice2 = rotl90(physicalspace(H)) + terms2 = (siterotl90.(inds, Ref(Hsize)) => term for (inds, term) in H.gates) + return LocalCircuit(lattice2, terms2) +end +function Base.rot180(H::LocalCircuit) + Hsize = size(H) + lattice2 = rot180(physicalspace(H)) + terms2 = (siterot180.(inds, Ref(Hsize)) => term for (inds, term) in H.gates) + return LocalCircuit(lattice2, terms2) +end diff --git a/test/runtests.jl b/test/runtests.jl index f59691b93..24bc65bb9 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -23,6 +23,9 @@ end @time @safetestset "LocalOperator" begin include("types/localoperator.jl") end + @time @safetestset "LocalCircuit" begin + include("types/localcircuit.jl") + end end if GROUP == "ALL" || GROUP == "CTMRG" @time @safetestset "Gauge Fixing" begin diff --git a/test/types/localcircuit.jl b/test/types/localcircuit.jl new file mode 100644 index 000000000..16d96597c --- /dev/null +++ b/test/types/localcircuit.jl @@ -0,0 +1,21 @@ +using TensorKit +using PEPSKit +using PEPSKit: LocalCircuit +using Test + +@testset "Rotation" begin + op = LocalCircuit( + [ℂ^1 ℂ^2 ℂ^3; ℂ^4 ℂ^5 ℂ^6], + ( + ((1, 1), (1, 2)) => randn(ℂ^1, ℂ^1) ⊗ randn(ℂ^2, ℂ^2), + ((2, 1), (1, 1)) => randn(ℂ^4, ℂ^4) ⊗ randn(ℂ^1, ℂ^1), + ((1, 2), (2, 3)) => randn(ℂ^2, ℂ^2) ⊗ randn(ℂ^6, ℂ^6), + ((1, 3), (2, 2)) => randn(ℂ^3, ℂ^3) ⊗ randn(ℂ^5, ℂ^5), + )... + ) + @test rot180(rot180(op)) == op + @test rotl90(rotl90(op)) == rot180(op) == rotr90(rotr90(op)) + @test physicalspace(rotl90(op)) == rotl90(physicalspace(op)) + @test physicalspace(rotr90(op)) == rotr90(physicalspace(op)) + @test physicalspace(rot180(op)) == rot180(physicalspace(op)) +end From bfb826aca6d464ac0572441a99063a3beff351f0 Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Tue, 7 Apr 2026 13:33:02 +0800 Subject: [PATCH 04/41] Use vector of sites in `nearest_neighbours` etc --- src/operators/lattices/squarelattice.jl | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/operators/lattices/squarelattice.jl b/src/operators/lattices/squarelattice.jl index 005c0bdad..102546594 100644 --- a/src/operators/lattices/squarelattice.jl +++ b/src/operators/lattices/squarelattice.jl @@ -29,19 +29,19 @@ function vertices(lattice::InfiniteSquare) end function nearest_neighbours(lattice::InfiniteSquare) - neighbors = Tuple{CartesianIndex, CartesianIndex}[] + neighbors = Vector{CartesianIndex{2}}[] for idx in vertices(lattice) - push!(neighbors, (idx, idx + CartesianIndex(0, 1))) - push!(neighbors, (idx, idx + CartesianIndex(1, 0))) + 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}[] + neighbors = Vector{CartesianIndex{2}}[] for idx in vertices(lattice) - push!(neighbors, (idx, idx + CartesianIndex(1, 1))) - push!(neighbors, (idx + CartesianIndex(0, 1), idx + CartesianIndex(1, 0))) + push!(neighbors, [idx, idx + CartesianIndex(1, 1)]) + push!(neighbors, [idx + CartesianIndex(0, 1), idx + CartesianIndex(1, 0)]) end return neighbors end From f6554f386ab695370510d9eadaa6b15d83e0cabb Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Wed, 8 Apr 2026 15:22:03 +0800 Subject: [PATCH 05/41] Neighbourhood tensor update --- src/PEPSKit.jl | 7 +- .../contractions/bondenv/benv_ntu.jl | 239 ++++++++++++++++++ .../contractions/bondenv/benv_tools.jl | 201 +++++++++++++++ .../contractions/bondenv/gaugefix.jl | 31 ++- src/algorithms/time_evolution/ntupdate.jl | 222 ++++++++++++++++ .../time_evolution/ntupdate3site.jl | 89 +++++++ 6 files changed, 778 insertions(+), 11 deletions(-) create mode 100644 src/algorithms/contractions/bondenv/benv_ntu.jl create mode 100644 src/algorithms/time_evolution/ntupdate.jl create mode 100644 src/algorithms/time_evolution/ntupdate3site.jl diff --git a/src/PEPSKit.jl b/src/PEPSKit.jl index f1c537ee6..ce16b89b5 100644 --- a/src/PEPSKit.jl +++ b/src/PEPSKit.jl @@ -89,6 +89,7 @@ include("algorithms/contractions/bp_contractions.jl") include("algorithms/contractions/bondenv/benv_tools.jl") include("algorithms/contractions/bondenv/gaugefix.jl") include("algorithms/contractions/bondenv/als_solve.jl") +include("algorithms/contractions/bondenv/benv_ntu.jl") include("algorithms/contractions/bondenv/benv_ctm.jl") include("algorithms/contractions/correlator/peps.jl") include("algorithms/contractions/correlator/pepo_1layer.jl") @@ -111,6 +112,8 @@ include("algorithms/time_evolution/apply_mpo.jl") include("algorithms/time_evolution/time_evolve.jl") include("algorithms/time_evolution/simpleupdate.jl") include("algorithms/time_evolution/simpleupdate3site.jl") +include("algorithms/time_evolution/ntupdate.jl") +include("algorithms/time_evolution/ntupdate3site.jl") include("algorithms/time_evolution/gaugefix_su.jl") include("algorithms/bp/beliefpropagation.jl") @@ -143,7 +146,8 @@ export fixedpoint export absorb_weight export ALSTruncation, FullEnvTruncation -export SimpleUpdate +export NNEnv, NNpEnv, NNNEnv +export SimpleUpdate, NeighbourUpdate export TimeEvolver, timestep, time_evolve export InfiniteSquareNetwork @@ -151,6 +155,7 @@ export InfinitePartitionFunction export InfinitePEPS, InfiniteTransferPEPS export SUWeight export InfinitePEPO, InfiniteTransferPEPO +export infinite_temperature_density_matrix export BPEnv, BeliefPropagation export BPGauge, SUGauge diff --git a/src/algorithms/contractions/bondenv/benv_ntu.jl b/src/algorithms/contractions/bondenv/benv_ntu.jl new file mode 100644 index 000000000..2c88c1cfe --- /dev/null +++ b/src/algorithms/contractions/bondenv/benv_ntu.jl @@ -0,0 +1,239 @@ +#= +The construction of bond environment for Neighborhood Tensor Update (NTU) +is adapted from YASTN (https://github.com/yastn/yastn). +Copyright 2024 The YASTN Authors. All Rights Reserved. +Licensed under the Apache License, Version 2.0 +=# + +""" +Algorithms to construct bond environment for Neighborhood Tensor Update (NTU). +""" +abstract type NeighbourEnv end + +""" +SVD with `truncrank(1)`. +""" +function _svd_cut!(t::AbstractTensorMap) + t1, s, t2 = svd_trunc!(t; trunc = truncrank(1)) + return absorb_s(t1, s, t2) +end + +""" +Algorithm struct for "NTU-NN" bond environment. +""" +struct NNEnv <: NeighbourEnv end +""" +Calculate the bond environment within "NTU-NN" approximation. +``` + -1 ●=======● + ║ ║ + 0 ●===X== ==Y===● + ║ ║ + 1 ●=======● + -1 0 1 2 +``` +""" +function bondenv_ntu( + row::Int, col::Int, X::T, Y::T, state::S, alg::NNEnv + ) where {T, S <: InfiniteState} + neighbors = [(-1, 0), (0, -1), (1, 0), (1, 1), (0, 2), (-1, 1)] + m = collect_neighbors(state, row, col, neighbors) + X, Y = _prepare_site_tensor(X), _prepare_site_tensor(Y) + # southwest half + @autoopt @tensor benv_sw[Dse1 Dse0 Dw1 Dw0 Dnw1 Dnw0] := + cor_se(m[1, 1])[Dse1 Dse0 Ds1 Ds0] * + cor_sw(m[1, 0])[Dsw1 Dsw0 Ds1 Ds0] * + edge_w(X, hair_w(m[0, -1]))[Dnw1 Dnw0 Dw1 Dw0 Dsw1 Dsw0] + normalize!(benv_sw, Inf) + # northeast half + @autoopt @tensor benv_ne[Dnw1 Dnw0 De1 De0 Dse1 Dse0] := + cor_nw(m[-1, 0])[Dn1 Dn0 Dnw1 Dnw0] * + cor_ne(m[-1, 1])[Dne1 Dne0 Dn1 Dn0] * + edge_e(Y, hair_e(m[0, 2]))[Dne1 Dne0 Dse1 Dse0 De1 De0] + normalize!(benv_ne, Inf) + @tensor benv[Dw1 De1; Dw0 De0] := + benv_sw[Dse1 Dse0 Dw1 Dw0 Dnw1 Dnw0] * benv_ne[Dnw1 Dnw0 De1 De0 Dse1 Dse0] + normalize!(benv, Inf) + return benv +end + +""" +Algorithm struct for "NTU-NN+" bond environment. +""" +struct NNpEnv <: NeighbourEnv end +""" +Calculate the bond environment within "NTU-NN+" approximation. +``` + -2 ●.......● + ║ ║ + -1 ○===●=======●===○ + ║ ║ ║ ║ + 0 ●===●===X== ==Y===●===● + ║ ║ ║ ║ + 1 ○===●=======●===○ + ║ ║ + 2 ●.......● + -2 -1 0 1 2 3 +``` +Dotted lines and ○ are splitted using SVD with `truncrank(1)`. +""" +function bondenv_ntu( + row::Int, col::Int, X::T, Y::T, state::S, alg::NNpEnv + ) where {T, S <: InfiniteState} + neighbors = [ + (-1, -1), (0, -1), (1, -1), (1, 0), (1, 1), (1, 2), (0, 2), (-1, 2), + (-1, 1), (-1, 0), (0, -2), (2, 0), (2, 1), (0, 3), (-2, 1), (-2, 0), + ] + ms = collect_neighbors(state, row, col, neighbors) + X, Y = _prepare_site_tensor(X), _prepare_site_tensor(Y) + + # ---- hairs (size D^2) with a 1D auxiliary leg ---- + + @tensor top[-1 -2; -3 -4] := cor_nw(ms[-2, 0])[1 2 -1 -2] * cor_ne(ms[-2, 1])[-3 -4 1 2] + tl, tr = _svd_cut!(top) + + @tensor bot[-1 -2; -3 -4] := cor_sw(ms[2, 0])[-1 -2 1 2] * cor_se(ms[2, 1])[-3 -4 1 2] + bl, br = _svd_cut!(bot) + + nw = permute(cor_nw(ms[-1, -1]), ((3, 4), (1, 2))) + nw1, nw2 = _svd_cut!(nw) + + ne = permute(cor_ne(ms[-1, 2]), ((3, 4), (1, 2))) + ne1, ne2 = _svd_cut!(ne) + + sw = permute(cor_sw(ms[1, -1]), ((1, 2), (3, 4))) + sw1, sw2 = _svd_cut!(sw) + + se = permute(cor_se(ms[1, 2]), ((3, 4), (1, 2))) + se1, se2 = _svd_cut!(se) + + m = ms[0, -1] + @tensoropt hW[anw DXw1 DXw0 asw] := + hair_w(ms[0, -2])[Dw21 Dw20] * + nw1[Dnw11 Dnw10 anw] * sw1[Dsw11 Dsw10 asw] * + twistdual(m, 1)[p Dnw10 DXw0 Dsw10 Dw20] * + conj(m[p Dnw11 DXw1 Dsw11 Dw21]) + + m = ms[0, 2] + @tensoropt hE[ane DYe1 DYe0 ase] := + hair_e(ms[0, 3])[De21 De20] * + ne2[ane Dne21 Dne20] * se2[ase Dse21 Dse20] * + twistdual(m, 1)[p Dne20 De20 Dse20 DYe0] * + conj(m[p Dne21 De21 Dse21 DYe1]) + + # ---- corners (size D^4) with a 1D auxiliary leg ---- + + m = ms[-1, 0] + @tensoropt NW[at Dn1 Dn0 DXn1 DXn0 anw] := + tl[Dtl1 Dtl0 at] * nw2[anw Dnw21 Dnw20] * + twistdual(m, 1)[p Dtl0 Dn0 DXn0 Dnw20] * + conj(m[p Dtl1 Dn1 DXn1 Dnw21]) + + m = ms[-1, 1] + @tensoropt NE[at Dn1 Dn0 DYn1 DYn0 ane] := + tr[at Dtr1 Dtr0] * ne1[Dne11 Dne10 ane] * + twistdual(m, 1)[p Dtr0 Dne10 DYn0 Dn0] * + conj(m[p Dtr1 Dne11 DYn1 Dn1]) + + m = ms[1, 0] + @tensoropt SW[asw DXs1 DXs0 Ds1 Ds0 ab] := + bl[Dbl1 Dbl0 ab] * sw2[asw Dsw21 Dsw20] * + twistdual(m, 1)[p DXs0 Ds0 Dbl0 Dsw20] * + conj(m[p DXs1 Ds1 Dbl1 Dsw21]) + + m = ms[1, 1] + @tensoropt SE[ase DYs1 DYs0 Ds1 Ds0 ab] := + br[ab Dbr1 Dbr0] * se1[Dse11 Dse10 ase] * + twistdual(m, 1)[p DYs0 Dse10 Dbr0 Ds0] * + conj(m[p DYs1 Dse11 Dbr1 Ds1]) + + # ---- left half ---- + @tensoropt benvL[at Dn1 Dn0 Dw1 Dw0 Ds1 Ds0 ab] := + hW[anw DXw1 DXw0 asw] * + NW[at Dn1 Dn0 DXn1 DXn0 anw] * + SW[asw DXs1 DXs0 Ds1 Ds0 ab] * + twistdual(X, 1)[p DXn0 Dw0 DXs0 DXw0] * + conj(X[p DXn1 Dw1 DXs1 DXw1]) + normalize!(benvL, Inf) + + # ---- right half ---- + + @tensoropt benvR[at Dn1 Dn0 De1 De0 Ds1 Ds0 ab] := + hE[ane DYe1 DYe0 ase] * + NE[at Dn1 Dn0 DYn1 DYn0 ane] * + SE[ase DYs1 DYs0 Ds1 Ds0 ab] * + twistdual(Y, 1)[p DYn0 DYe0 DYs0 De0] * + conj(Y[p DYn1 DYe1 DYs1 De1]) + normalize!(benvR, Inf) + + # ---- the full NN+ environment ---- + + @tensor benv[Dw1, De1; Dw0, De0] := + benvL[at Dn1 Dn0 Dw1 Dw0 Ds1 Ds0 ab] * + benvR[at Dn1 Dn0 De1 De0 Ds1 Ds0 ab] + normalize!(benv, Inf) + return benv +end + +""" +Algorithm struct for "NTU-NNN" bond environment. +""" +struct NNNEnv <: NeighbourEnv end +""" +Calculates the bond environment within "NTU-NNN" approximation. +``` + -1 ●===●=======●===● + ║ ║ ║ ║ + 0 ●===X== ==Y===● + ║ ║ ║ ║ + 1 ●===●=======●===● + -1 0 1 2 +``` +""" +function bondenv_ntu( + row::Int, col::Int, X::T, Y::T, state::S, alg::NNNEnv + ) where {T, S <: InfiniteState} + neighbors = [ + (-1, -1), (0, -1), (1, -1), + (1, 0), (1, 1), (1, 2), (0, 2), + (-1, 2), (-1, 1), (-1, 0), + ] + m = collect_neighbors(state, row, col, neighbors) + X, Y = _prepare_site_tensor(X), _prepare_site_tensor(Y) + #= left half + -1 ●======●=== -1/-2 + ║ ║ + 0 ●======X=== -3/-4 + ║ ║ + ....D1.....D2... + ║ ║ + 1 ●==D3==●=== -5/-6 + -1 0 + =# + vecl = enlarge_corner_nw(cor_nw(m[-1, -1]), edge_n(m[-1, 0]), edge_w(m[0, -1]), X) + @tensor vecl[:] := + cor_sw(m[1, -1])[D11 D10 D31 D30] * + edge_s(m[1, 0])[D21 D20 -5 -6 D31 D30] * + vecl[D11 D10 D21 D20 -1 -2 -3 -4] + normalize!(vecl, Inf) + #= right half + -1 -1/-2===●==D1==● + ║ ║ + ........D2.....D3... + ║ ║ + 0 -3/-4===Y======● + ║ ║ + 1 -5/-6===●======● + 0 1 + =# + vecr = enlarge_corner_se(cor_se(m[1, 2]), edge_s(m[1, 1]), edge_e(m[0, 2]), Y) + @tensor vecr[:] := + edge_n(m[-1, 1])[D11 D10 D21 D20 -1 -2] * + cor_ne(m[-1, 2])[D31 D30 D11 D10] * + vecr[D21 D20 D31 D30 -3 -4 -5 -6] + normalize!(vecr, Inf) + # combine left and right part + @tensor benv[-1 -2; -3 -4] := vecl[1 2 -1 -3 3 4] * vecr[1 2 -2 -4 3 4] + normalize!(benv, Inf) + return benv +end diff --git a/src/algorithms/contractions/bondenv/benv_tools.jl b/src/algorithms/contractions/bondenv/benv_tools.jl index 38400b919..3c8217e06 100644 --- a/src/algorithms/contractions/bondenv/benv_tools.jl +++ b/src/algorithms/contractions/bondenv/benv_tools.jl @@ -1,3 +1,10 @@ +#= +The construction of bond environment for Neighborhood Tensor Update (NTU) +is adapted from YASTN (https://github.com/yastn/yastn). +Copyright 2024 The YASTN Authors. All Rights Reserved. +Licensed under the Apache License, Version 2.0 +=# + const BondEnv{T, S} = AbstractTensorMap{T, S, 2, 2} where {T <: Number, S <: ElementarySpace} const BondEnv3site{T, S} = AbstractTensorMap{T, S, 4, 4} where {T <: Number, S <: ElementarySpace} const Hair{T, S} = AbstractTensor{T, S, 2} where {T <: Number, S <: ElementarySpace} @@ -11,3 +18,197 @@ _prepare_site_tensor(t::PEPSTensor) = t _prepare_site_tensor(t::PEPOTensor) = first(fuse_physicalspaces(t)) _prepare_site_tensor(t::PEPSOrth) = permute(insertleftunit(t, 1), ((1,), (2, 3, 4, 5))) _prepare_site_tensor(t::PEPOOrth) = permute(t, ((1,), (2, 3, 4, 5))) + +""" +Extract tensors in an InfinitePEPS or 1-layer InfinitePEPO +at positions `neighbors` relative to `(row, col)` +""" +function collect_neighbors( + state::InfiniteState, row::Int, col::Int, neighbors::Vector{Tuple{Int, Int}} + ) + Nr, Nc = size(state) + return Dict( + nb => _prepare_site_tensor(state[mod1(row + nb[1], Nr), mod1(col + nb[2], Nc)]) + for nb in neighbors + ) +end + +""" + benv_tensor(ket::PEPSTensor, bra::PEPSTensor, open_axs::Vector{Int}) + benv_tensor(ket::PEPSTensor, bra::PEPSTensor, open_axs::Vector{Int}, hairs::Vector{H}) where {T, H <: Union{Nothing, Hair}} + +Contract the physical axes and the virtual axes of `ket` with `bra` to obtain the tensor on the boundary of the bond environment. +Virtual axes specified by `open_axs` (in ascending order) are not contracted. + +# Examples + +- West "hair" tensor (`open_axs = [EAST]`) + ``` + ╱| + ┌-----ket----- 2 + | ╱ | | + | | | | + | | | ╱ + └---|-bra----- 1 + |╱ + ``` +- Northwest corner tensor (`open_axs = [EAST, SOUTH]`, `hairs = [h, nothing]`) + ``` + ╱| + ┌-----ket----- 2 + | ╱ | h + | 4 | | + | | | + | | ╱ + └-----bra----- 1 + ╱ + 3 + ``` +- West edge tensor (`open_axs = [1, 2, 3]`) + ``` + 2 + ╱ + ┌-----ket----- 4 + | ╱ | + | 6 | 1 + | | ╱ + └-----bra----- 3 + ╱ + 5 + ``` +""" +function benv_tensor( + ket::PEPSTensor, bra::PEPSTensor, open_axs::Vector{Int} + ) + # no hair tensors to be attached to virtual legs + return benv_tensor(ket, bra, open_axs, fill(nothing, 4 - length(open_axs))) +end +function benv_tensor( + ket::PEPSTensor, bra::PEPSTensor, open_axs::Vector{Int}, hairs::Vector{H} + ) where {H <: Union{Nothing, Hair}} + @assert length(hairs) == 4 - length(open_axs) + ket2, nax = copy(ket), numind(ket) + axs, open_axs2 = (2:5), open_axs .+ 1 + # contract with hair tensors + hair_axs = Tuple(ax for ax in axs if ax ∉ open_axs2) + for (h, ax) in zip(hairs, hair_axs) + if h === nothing + twistdual!(ket2, ax) + continue + end + # ensure the hair doesn't change the virtual spaces + @assert space(h, 1) == space(h, 2)' + ket_indices = collect(-1:-1:-nax) + ket_indices[ax] = 1 + ket2 = ncon([h, ket2], [[-ax, 1], ket_indices]) + end + # combine bra and ket + indexlist = [-collect(1:2:(2 * nax)), -collect(2:2:(2 * nax))] + for ax in 1:nax + if ax ∉ open_axs2 + indexlist[1][ax] = indexlist[2][ax] = ax + end + end + return ncon([bra, ket2], indexlist, [true, false]) +end + +#= Free axes of different boundary tensors +(C/E/H mean corner/edge/hair) + + H_n + | + + C_nw - - E_n - - C_ne + | | | + + | | | + H_w - E_w - - ket - - E_e - H_e + | | | + + | | | + C_sw - - E_s - - C_se + + | + H_s +=# +const open_axs_hair = Dict(:n => [SOUTH], :e => [WEST], :s => [NORTH], :w => [EAST]) +const open_axs_cor = Dict( + :nw => [EAST, SOUTH], :ne => [SOUTH, WEST], :se => [NORTH, WEST], :sw => [NORTH, EAST] +) +const open_axs_edge = Dict( + :n => [EAST, SOUTH, WEST], + :e => [NORTH, SOUTH, WEST], + :s => [NORTH, EAST, WEST], + :w => [NORTH, EAST, SOUTH], +) + +# construction of hairs +for (dir, open_axs) in open_axs_hair + fname = Symbol("hair_", dir) + @eval begin + $(fname)(ket) = benv_tensor(ket, ket, $open_axs) + $(fname)(ket, h1, h2, h3) = benv_tensor(ket, ket, $open_axs, [h1, h2, h3]) + end +end + +# construction of corners +for (dir, open_axs) in open_axs_cor + fname = Symbol("cor_", dir) + @eval begin + $(fname)(ket) = benv_tensor(ket, ket, $open_axs) + $(fname)(ket, h1, h2) = benv_tensor(ket, ket, $open_axs, [h1, h2]) + end +end + +# construction of edges +for (dir, open_axs) in open_axs_edge + fname = Symbol("edge_", dir) + @eval begin + $(fname)(ket) = benv_tensor(ket, ket, $open_axs) + $(fname)(ket, h) = benv_tensor(ket, ket, $open_axs, [h]) + end +end + +""" +Enlarge the northwest corner +``` + ctl══ D1 ══ et ══ -5/-6 + ║ ║ + D2 D3 + ║ ║ + el ══ D4 ══ X ═══ -7/-8 + ║ ║ + -1/-2 -3/-4 +``` +""" +function enlarge_corner_nw( + ctl::AbstractTensor{E, S, 4}, + et::AbstractTensor{E, S, 6}, el::AbstractTensor{E, S, 6}, + ket::PEPSTensor, bra::PEPSTensor = ket + ) where {E, S} + return @tensoropt ctl2[:] := ctl[D11 D10 D21 D20] * + et[-5 -6 D31 D30 D11 D10] * el[D21 D20 D41 D40 -1 -2] * + conj(bra[d D31 -7 -3 D41]) * twistdual(ket, 1)[d D30 -8 -4 D40] +end + +""" +Enlarge the southeast corner +``` + -1/-2 -3/-4 + ║ ║ + -5/-6 ═════ Y ══ D1 ═══ er + ║ ║ + D2 D3 + ║ ║ + -7/-8 ═════ eb ═ D4 ══ cbr +``` +""" +function enlarge_corner_se( + cbr::AbstractTensor{E, S, 4}, + eb::AbstractTensor{E, S, 6}, er::AbstractTensor{E, S, 6}, + ket::PEPSTensor, bra::PEPSTensor = ket + ) where {E, S} + return @tensoropt cbr2[:] := cbr[D31 D30 D41 D40] * + eb[D21 D20 D41 D40 -7 -8] * er[-3 -4 D31 D30 D11 D10] * + conj(bra[d -1 D11 D21 -5]) * twistdual(ket, 1)[d -2 D10 D20 -6] +end diff --git a/src/algorithms/contractions/bondenv/gaugefix.jl b/src/algorithms/contractions/bondenv/gaugefix.jl index 0572d4852..9df4c1599 100644 --- a/src/algorithms/contractions/bondenv/gaugefix.jl +++ b/src/algorithms/contractions/bondenv/gaugefix.jl @@ -92,12 +92,13 @@ function fixgauge_benv( end """ -When the (half) bond environment `Z` consists of two `PEPSOrth` tensors `X`, `Y` as +When the (half) bond environment `Z` consists of +two `PEPSOrth` or `PEPOOrth` tensors `X`, `Y` as ``` - ┌---------------┐ ┌-------------------┐ - | | = | | , - └---Z-- --┘ └--Z0---X-- --Y--┘ - ↓ ↓ + ┌-----------------------┐ + | | + └---Z---(X)-- --(Y)---┘ + ↓ ``` apply the gauge transformation `Linv`, `Rinv` for `Z` to `X`, `Y`: ``` @@ -106,15 +107,25 @@ apply the gauge transformation `Linv`, `Rinv` for `Z` to `X`, `Y`: -4 - X - 1 - Rinv - -2 -4 - Linv - 1 - Y - -2 | | -3 -3 + + -2 -2 + | | + -5 - X - 1 - Rinv - -3 -5 - Linv - 1 - Y - -3 + | ╲ | ╲ + -4 -1 -4 -1 ``` """ function _fixgauge_benvXY( - X::PEPSOrth{T, S}, - Y::PEPSOrth{T, S}, - Linv::AbstractTensorMap{T, S, 1, 1}, - Rinv::AbstractTensorMap{T, S, 1, 1}, - ) where {T <: Number, S <: ElementarySpace} + X::PEPSOrth, Y::PEPSOrth, Linv::MPSBondTensor, Rinv::MPSBondTensor, + ) @plansor X[-1 -2 -3 -4] := X[-1 1 -3 -4] * Rinv[1; -2] @plansor Y[-1 -2 -3 -4] := Y[-1 -2 -3 1] * Linv[1; -4] return X, Y end +function _fixgauge_benvXY( + X::PEPOOrth, Y::PEPOOrth, Linv::MPSBondTensor, Rinv::MPSBondTensor, + ) + @plansor X[-1 -2 -3 -4 -5] := X[-1 -2 1 -4 -5] * Rinv[1; -3] + @plansor Y[-1 -2 -3 -4 -5] := Y[-1 -2 -3 -4 1] * Linv[1; -5] + return X, Y +end diff --git a/src/algorithms/time_evolution/ntupdate.jl b/src/algorithms/time_evolution/ntupdate.jl new file mode 100644 index 000000000..f08d9fbfd --- /dev/null +++ b/src/algorithms/time_evolution/ntupdate.jl @@ -0,0 +1,222 @@ +""" +$(TYPEDEF) + +Algorithm struct for neighbourhood tensor update (NTU) of InfinitePEPS or InfinitePEPO. + +## Fields + +$(TYPEDFIELDS) + +Reference: +- Physical Review B 104, 094411 (2021) +- Physical Review B 106, 195105 (2022) +""" +@kwdef struct NeighbourUpdate <: TimeEvolution + "Bond truncation algorithm after applying time evolution gate" + opt_alg::Union{ALSTruncation, FullEnvTruncation} = + ALSTruncation(; trunc = truncerror(; atol = 1.0e-10)) + "When true (or false), the Trotter gate is `exp(-H dt)` (or `exp(-iH dt)`)" + imaginary_time::Bool = true + "Algorithm to construct NTU bond environment." + bondenv_alg::NeighbourEnv = NNEnv() + "When true, fix gauge of bond environment" + fixgauge::Bool = true + "When true, assume bipartite unit cell structure" + bipartite::Bool = false +end + +""" +Internal state of neighbourhood tensor update algorithm +""" +struct NTUState{S <: InfiniteState, N <: Number} + "number of performed iterations" + iter::Int + "evolved time" + t::N + "PEPS/PEPO" + psi::S +end + +""" + TimeEvolver( + psi0::InfiniteState, H::LocalOperator, dt::Number, nstep::Int, + alg::NeighbourUpdate; t0::Number = 0.0, symmetrize_gates::Bool = false + ) + +Initialize a TimeEvolver with Hamiltonian `H` and neighbourhood tensor update `alg`, +starting from the initial state `psi0`. + +- The initial time is specified by `t0`. +- Use `symmetrize_gates = true` for second-order Trotter decomposition. +""" +function TimeEvolver( + psi0::InfiniteState, H::LocalOperator, dt::Number, nstep::Int, + alg::NeighbourUpdate; t0::Number = 0.0, symmetrize_gates::Bool = false + ) + _timeevol_sanity_check(psi0, physicalspace(H), alg) + dt′ = _get_dt(psi0, dt, alg.imaginary_time) + gate = trotterize(H, dt′; symmetrize_gates) + state = NTUState(0, t0, psi0) + return TimeEvolver(alg, dt, nstep, gate, state) +end + +""" +Neighbourhood tensor update optimized for nearest neighbor gates +utilizing reduced bond tensors with the physical leg. +""" +function _ntu_iter( + state::InfiniteState, gate::NNGate, wts::SUWeight, + sites::Vector{CartesianIndex{2}}, alg::NeighbourUpdate + ) + @assert length(sites) == 2 + return _bond_truncate(state, wts, Tuple(sites), alg; gate) +end + +""" +One round of neighbourhood tensor update +""" +function ntu_iter( + state::InfiniteState, circuit::LocalCircuit, alg::NeighbourUpdate + ) + Nr, Nc, = size(state) + state2, wts = deepcopy(state), SUWeight(state) + info = (; fid = 1.0) + for (sites, gate) in circuit.gates + if length(sites) == 1 + # 1-site gate + # TODO: special treatment for bipartite state + site = sites[1] + r, c = mod1(site[1], Nr), mod1(site[2], Nc) + state2[r, c] = _apply_sitegate(state2[r, c], gate) + info′ = (; fid = 1.0) + elseif length(sites) == 2 + (d, r, c), = _nn_bondrev(sites..., (Nr, Nc)) + if alg.bipartite + length(sites) > 2 && error("Multi-site MPO gates are not compatible with bipartite states.") + r > 1 && continue + end + state2, wts, info′ = _ntu_iter(state2, gate, wts, sites, alg) + (!alg.bipartite) && continue + if d == 1 + rp1, cp1 = _next(r, Nr), _next(c, Nc) + state2[rp1, cp1] = deepcopy(state2[r, c]) + state2[rp1, c] = deepcopy(state2[r, cp1]) + wts[1, rp1, cp1] = deepcopy(wts[1, r, c]) + else + rm1, cm1 = _prev(r, Nr), _prev(c, Nc) + state2[rm1, cm1] = deepcopy(state2[r, c]) + state2[r, cm1] = deepcopy(state2[rm1, c]) + wts[2, rm1, cm1] = deepcopy(wts[2, r, c]) + end + else + # N-site MPO gate (N ≥ 2) + alg.bipartite && error("Multi-site MPO gates are not compatible with bipartite states.") + state2, wts, info′ = _ntu_iter(state2, gate, wts, sites, alg) + end + # record the worst fidelity + (info′.fid < info.fid) && (info = info′) + end + return state2, wts, info +end + +function Base.iterate(it::TimeEvolver{<:NeighbourUpdate}, state = it.state) + iter, t = state.iter, state.t + (iter == it.nstep) && return nothing + psi, wts, info = ntu_iter(state.psi, it.gate, it.alg) + iter, t = iter + 1, t + it.dt + # update internal state + it.state = NTUState(iter, t, psi) + info = (; (; t, wts)..., info...) + return (psi, info), it.state +end + +""" + timestep( + it::TimeEvolver{<:NeighbourUpdate}, psi::InfiniteState; + iter::Int = it.state.iter, t::Float64 = it.state.t + ) -> (psi, info) + +Given the TimeEvolver iterator `it`, perform one step of NTU time evolution +on the input state `psi`. + +- Using `iter` and `t` to reset the current iteration number and evolved time + respectively of the TimeEvolver `it`. +""" +function MPSKit.timestep( + it::TimeEvolver{<:NeighbourUpdate}, psi::InfiniteState; + iter::Int = it.state.iter, t::Float64 = it.state.t + ) + _timeevol_sanity_check(psi, physicalspace(it.state.psi), it.alg) + state = NTUState(iter, t, psi) + result = iterate(it, state) + if result === nothing + @warn "TimeEvolver `it` has already reached the end." + return nothing + else + return first(result) + end +end + +""" + time_evolve( + it::TimeEvolver{<:NeighbourUpdate}; check_interval::Int = 500 + ) -> (psi, info) + +Perform time evolution to the end of `TimeEvolver` iterator `it`. + +- `check_interval` sets the number of iterations between outputs of information. +""" +function MPSKit.time_evolve(it::TimeEvolver{<:NeighbourUpdate}; check_interval::Int = 500) + time_start = time0 = time() + @info "--- Time evolution (neighbourhood tensor update), dt = $(it.dt) ---" + info0 = nothing + for (psi, info) in it + iter = it.state.iter + stop = (iter == it.nstep) + showinfo = (iter == 1) || (iter % check_interval == 0) || stop + time1 = time() + if showinfo + Δλ = (info0 === nothing) ? NaN : compare_weights(info.wts, info0.wts) + @info @sprintf( + "NTU iter %d: t = %.2e, |Δλ| = %.3e. Time: %.2f s", + it.state.iter, it.state.t, Δλ, time1 - time0 + ) + end + if stop + time_end = time() + @info @sprintf("Time evolution finished in %.2f s", time_end - time_start) + return psi, info + end + info0, time0 = info, time() + end + return +end + +""" + time_evolve( + psi0::Union{InfinitePEPS, InfinitePEPO}, H::LocalOperator, + dt::Number, nstep::Int, alg::NeighbourUpdate; + t0::Number = 0.0, symmetrize_gates::Bool = false, + check_interval::Int = 10 + ) -> (psi, info) + +Perform time evolution on the initial state `psi0` and initial environment `env0` +with Hamiltonian `H`, using `NeighbourUpdate` algorithm `alg`, time step `dt` for +`nstep` number of steps. + +- Convergence check for ground state search is not supported. +- Set `symmetrize_gates = true` for second-order Trotter decomposition. +- Use `t0` to specify the initial time of `psi0`. +- `check_interval` sets the interval to output information (and check convergence). + Output during the evolution can be turned off by setting `check_interval <= 0`. +- `info` is a NamedTuple containing information of the evolution, + including the time `info.t` evolved since `psi0`. +""" +function MPSKit.time_evolve( + psi0::InfiniteState, H::LocalOperator, dt::Number, nstep::Int, + alg::NeighbourUpdate; symmetrize_gates::Bool = false, + t0::Number = 0.0, check_interval::Int = 10 + ) + it = TimeEvolver(psi0, H, dt, nstep, alg; t0, symmetrize_gates) + return time_evolve(it; check_interval) +end diff --git a/src/algorithms/time_evolution/ntupdate3site.jl b/src/algorithms/time_evolution/ntupdate3site.jl new file mode 100644 index 000000000..00055f54d --- /dev/null +++ b/src/algorithms/time_evolution/ntupdate3site.jl @@ -0,0 +1,89 @@ +""" +Neighbourhood tensor update with N-site MPO `gate` (N ≥ 2). +""" +function _ntu_iter( + state::InfiniteState, gate::Vector{T}, wts::SUWeight, + sites::Vector{CartesianIndex{2}}, alg::NeighbourUpdate + ) where {T <: AbstractTensorMap} + Nr, Nc = size(state) + + # apply gate MPO without truncation + Ms, _, invperms = _get_cluster(state, sites) + flips = [isdual(space(M, 1)) for M in Ms[2:end]] + _flip_virtuals!(Ms, flips) # flip virtual arrows in `Ms` to ← + _apply_gatempo!(Ms, gate) + _flip_virtuals!(Ms, flips) # restore virtual arrows in `Ms` + state2, wts2 = deepcopy(state), deepcopy(wts) + for (M, s, invperm) in zip(Ms, sites, invperms) + s′ = CartesianIndex(mod1(s[1], Nr), mod1(s[2], Nc)) + state2[s′] = permute(M, invperm) + end + + # truncate each bond sequentially along the path + info = (; fid = 1.0) + for bondsites in zip(sites, Iterators.drop(sites, 1)) + state2, wts2, info′ = _bond_truncate(state2, wts2, bondsites, alg) + # record the worst fidelity + (info′.fid < info.fid) && (info = info′) + end + return state2, wts2, info +end + +""" +Truncate a nearest neighbor bond between `site1` and `site2` +after rotating the bond to standard x direction `A ← B`. +""" +function _bond_truncate( + state::InfiniteState, wts::SUWeight, + (site1, site2)::NTuple{2, CartesianIndex{2}}, + alg::NeighbourUpdate; gate::Union{NNGate, Nothing} = nothing + ) + # rotate bond to standard x direction `A ← B` + ucell = size(state)[1:2] + bond, rev = _nn_bondrev(site1, site2, ucell) + state2 = _bond_rotation(state, bond[1], rev; inv = false) + wts2 = _bond_rotation(wts, bond[1], rev; inv = false) + + # rotated bond tensors + siteA = if bond[1] == 1 + rev ? siterot180(site1, ucell) : site1 + else + rev ? siterotl90(site1, ucell) : siterotr90(site1, ucell) + end + row, col = mod1.(Tuple(siteA), size(state2)[1:2]) + cp1 = _next(col, size(state2, 2)) + A, B = state2[row, col], state2[row, cp1] + + # create bond environment + X, a, b, Y = _qr_bond(A, B; trunc = trunctol(; rtol = 1.0e-12)) + benv = bondenv_ntu(row, col, X, Y, state2, alg.bondenv_alg) + @debug "cond(benv) before gauge fix: $(LinearAlgebra.cond(benv))" + if alg.fixgauge + Z = positive_approx(benv) + Z, a, b, (Linv, Rinv) = fixgauge_benv(Z, a, b) + X, Y = _fixgauge_benvXY(X, Y, Linv, Rinv) + benv = Z' * Z + @debug "cond(L) = $(LinearAlgebra.cond(Linv)); cond(R): $(LinearAlgebra.cond(Rinv))" + @debug "cond(benv) after gauge fix: $(LinearAlgebra.cond(benv))" + end + + # (optional) apply the NN gate + if !(gate === nothing) + a, s, b, = _apply_gate(a, b, gate, truncerror(; atol = 1.0e-15)) + else + a = permute(a, ((1, 2), (3,))) + b = permute(b, ((1,), (2, 3))) + end + + a, s, b, info = bond_truncate(a, b, benv, alg.opt_alg) + A, B = _qr_bond_undo(X, a, b, Y) + normalize!(A, Inf) + normalize!(B, Inf) + normalize!(s, Inf) + state2[row, col], state2[row, cp1], wts2[1, row, col] = A, B, s + + # rotate back tensors and bond weight + state2 = _bond_rotation(state2, bond[1], rev; inv = true) + wts2 = _bond_rotation(wts2, bond[1], rev; inv = true) + return state2, wts2, info +end From 2540e48b7716d150a26156fe304b8000b33babc9 Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Wed, 8 Apr 2026 16:07:59 +0800 Subject: [PATCH 06/41] Fix benv_tensor for dual physical space --- src/algorithms/contractions/bondenv/benv_tools.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/src/algorithms/contractions/bondenv/benv_tools.jl b/src/algorithms/contractions/bondenv/benv_tools.jl index 3c8217e06..c3ae13ded 100644 --- a/src/algorithms/contractions/bondenv/benv_tools.jl +++ b/src/algorithms/contractions/bondenv/benv_tools.jl @@ -109,6 +109,7 @@ function benv_tensor( indexlist[1][ax] = indexlist[2][ax] = ax end end + twistdual!(ket2, 1) return ncon([bra, ket2], indexlist, [true, false]) end From 24145b1d3ab3dee85a299bb0f0eeaf1700714865 Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Wed, 8 Apr 2026 16:29:45 +0800 Subject: [PATCH 07/41] Add NTU tests --- test/bondenv/benv_ntu.jl | 72 ++++++++++++++++++++++++ test/runtests.jl | 6 ++ test/timeevol/j1j2_finiteT.jl | 68 +++++++++++----------- test/timeevol/tf_ising_realtime.jl | 90 ++++++++++++++++++++++++++++++ 4 files changed, 201 insertions(+), 35 deletions(-) create mode 100644 test/bondenv/benv_ntu.jl create mode 100644 test/timeevol/tf_ising_realtime.jl diff --git a/test/bondenv/benv_ntu.jl b/test/bondenv/benv_ntu.jl new file mode 100644 index 000000000..d3e2e4ffd --- /dev/null +++ b/test/bondenv/benv_ntu.jl @@ -0,0 +1,72 @@ +using Test +using TensorKit +using PEPSKit +using LinearAlgebra +using KrylovKit +using Random + +Nr, Nc = 2, 2 +Random.seed!(20) + +function test_ntu_env( + state::Union{InfinitePEPS, InfinitePEPO}, env_alg::Alg + ) where {Alg <: PEPSKit.NeighbourEnv} + @info "Testing $(typeof(env_alg))" + for row in 1:Nr, col in 1:Nc + cp1 = PEPSKit._next(col, Nc) + A, B = state.A[row, col], state.A[row, cp1] + X, a, b, Y = PEPSKit._qr_bond(A, B) + @tensor ab[DX DY; da db] := a[DX da D] * b[D db DY] + benv = PEPSKit.bondenv_ntu(row, col, X, Y, state, env_alg) + # this is a result of `_qr_bond` + @assert [isdual(space(benv, ax)) for ax in 1:numind(benv)] == [0, 0, 1, 1] + # NTU bond environments are exact and should be positive definite + @test benv' ≈ benv + benv = project_hermitian(benv) + nrm1 = PEPSKit.inner_prod(benv, ab, ab) + @test nrm1 ≈ real(nrm1) + D, U = eigh_full(benv) + @test minimum(D.data) >= 0 + # gauge fixing + Z = PEPSKit.sdiag_pow(D, 0.5) * U' + @assert benv ≈ Z' * Z + Z2, a2, b2, (Linv, Rinv) = PEPSKit.fixgauge_benv(Z, a, b) + benv2 = Z2' * Z2 + # gauge fixing should reduce condition number + cond = LinearAlgebra.cond(benv) + cond2 = LinearAlgebra.cond(benv2) + @test cond2 <= cond + @info "benv cond number: (gauge-fixed) $(cond2) ≤ $(cond) (initial)" + # verify gauge transformation of X, Y + @tensor a2b2[DX DY; da db] := a2[DX da D] * b2[D db DY] + nrm2 = PEPSKit.inner_prod(benv2, a2b2, a2b2) + X2, Y2 = PEPSKit._fixgauge_benvXY(X, Y, Linv, Rinv) + benv3 = PEPSKit.bondenv_ntu(row, col, X2, Y2, state, env_alg) + benv3 *= norm(benv2, Inf) + nrm3 = PEPSKit.inner_prod(benv3, a2b2, a2b2) + @test benv2 ≈ benv3 + @test nrm1 ≈ nrm2 ≈ nrm3 + end + return +end + +@testset "NTU bond environment ($(sym))" for sym in [U1Irrep, FermionParity] + Pspace = Vect[sym](0 => 1, 1 => 1) + V2 = Vect[sym](0 => 4, 1 => 1) + V3 = Vect[sym](0 => 3, 1 => 2) + V4 = Vect[sym](0 => 2, 1 => 2) + V5 = Vect[sym](0 => 2, 1 => 3) + Pspaces = fill(Pspace, (Nr, Nc)) + Nspaces = [V2 V2; V4 V4] + Espaces = [V3 V5; V5 V3] + + for state in [ + InfinitePEPS(randn, ComplexF64, Pspaces, Nspaces, Espaces), + InfinitePEPO(randn, ComplexF64, Pspaces, Nspaces, Espaces), + ] + normalize!.(state.A, Inf) + for env_alg in (NNEnv(), NNpEnv(), NNNEnv()) + test_ntu_env(state, env_alg) + end + end +end diff --git a/test/runtests.jl b/test/runtests.jl index 24bc65bb9..66f8ac952 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -90,6 +90,9 @@ end @time @safetestset "Gauge fixing" begin include("bondenv/benv_gaugefix.jl") end + @time @safetestset "Exact NTU bond environments" begin + include("bondenv/benv_ntu.jl") + end @time @safetestset "Full update bond environment" begin include("bondenv/benv_ctm.jl") end @@ -110,6 +113,9 @@ end @time @safetestset "J1-J2 model at finite temperature" begin include("timeevol/j1j2_finiteT.jl") end + @time @safetestset "Transverse Field Ising model real-time evolution" begin + include("timeevol/tf_ising_realtime.jl") + end end if GROUP == "ALL" || GROUP == "TOOLBOX" @time @safetestset "Density matrix from double-layer PEPO" begin diff --git a/test/timeevol/j1j2_finiteT.jl b/test/timeevol/j1j2_finiteT.jl index 63576e6a1..1217333ec 100644 --- a/test/timeevol/j1j2_finiteT.jl +++ b/test/timeevol/j1j2_finiteT.jl @@ -5,9 +5,8 @@ import MPSKitModels: σˣ, σᶻ using PEPSKit # Benchmark energy from high-temperature expansion -# at β = 0.3, 0.6 -# Physical Review B 86, 045139 (2012) Fig. 15-16 -bm = [-0.1235, -0.213] +const βs = [0.2, 0.4, 0.6] +const bm = [-0.08624893, -0.15688984, -0.21300888] function converge_env(state, χ::Int) trunc1 = truncrank(χ) & truncerror(; atol = 1.0e-12) @@ -23,37 +22,36 @@ ham = j1_j2_model( ) pepo0 = PEPSKit.infinite_temperature_density_matrix(ham) wts0 = SUWeight(pepo0) -# 7 = 1 (spin-0) + 2 x 3 (spin-1) -trunc_pepo = truncrank(7) & truncerror(; atol = 1.0e-12) -check_interval = 100 -dt, nstep = 1.0e-3, 600 +dt, nstep, check_interval = 5.0e-3, 40, 40 -# PEPO approach -alg = SimpleUpdate(; trunc = trunc_pepo, purified = false) -evolver = TimeEvolver(pepo0, ham, dt, nstep, alg, wts0) -pepo, wts, info = time_evolve(evolver; check_interval) -env = converge_env(InfinitePartitionFunction(pepo), 16) -energy = expectation_value(pepo, ham, env) / (Nr * Nc) -@info "β = $(dt * nstep): tr(ρH) = $(energy)" -@test dt * nstep ≈ info.t -@test energy ≈ bm[2] atol = 5.0e-3 - -# PEPS (purified PEPO) approach -alg = SimpleUpdate(; trunc = trunc_pepo, purified = true) -evolver = TimeEvolver(pepo0, ham, dt, nstep, alg, wts0) -pepo, wts, info = time_evolve(evolver; check_interval) -env = converge_env(InfinitePartitionFunction(pepo), 16) -energy = expectation_value(pepo, ham, env) / (Nr * Nc) -@info "β = $(dt * nstep) / 2: tr(ρH) = $(energy)" -@test energy ≈ bm[1] atol = 5.0e-3 - -# test BP gauge fixing for purified iPEPO -bp_alg = BeliefPropagation(; maxiter = 100, tol = 1.0e-9) -bp_env, = leading_boundary(BPEnv(ones, Float64, pepo), pepo, bp_alg) -pepo, = gauge_fix(pepo, BPGauge(), bp_env) +@testset "Simple update" begin + # 7 = 1 (spin-0) + 2 x 3 (spin-1) + trunc_pepo = truncrank(7) & truncerror(; atol = 1.0e-12) + alg = SimpleUpdate(; trunc = trunc_pepo, purified = true) + pepo, wts = deepcopy(pepo0), deepcopy(wts0) + for (β, bme) in zip(βs, bm) + t0 = β - βs[1] + pepo, wts, info = time_evolve(pepo, ham, dt, nstep, alg, wts; t0, check_interval) + # measure energy + env = converge_env(InfinitePEPS(pepo), 16) + energy = expectation_value(pepo, ham, pepo, env) / (Nr * Nc) + @info "β = $(info.t): ⟨ρ|H|ρ⟩ = $(energy)" + @test energy ≈ bme atol = 5.0e-3 + end +end -env = converge_env(InfinitePEPS(pepo), 16) -energy = expectation_value(pepo, ham, pepo, env) / (Nr * Nc) -@info "β = $(dt * nstep): ⟨ρ|H|ρ⟩ = $(energy)" -@test dt * nstep ≈ info.t -@test energy ≈ bm[2] atol = 5.0e-3 +@testset "Neighbourhood tensor update" begin + trunc_pepo = truncrank(4) & truncerror(; atol = 1.0e-12) + opt_alg = ALSTruncation(; trunc = trunc_pepo, tol = 1.0e-10) + alg = NeighbourUpdate(; opt_alg, bondenv_alg = NNEnv()) + pepo = deepcopy(pepo0) + for (β, bme) in zip(βs, bm) + t0 = β - βs[1] + pepo, info = time_evolve(pepo, ham, dt, nstep, alg; t0, check_interval) + # measure energy + env = converge_env(InfinitePEPS(pepo), 16) + energy = expectation_value(pepo, ham, pepo, env) / (Nr * Nc) + @info "β = $(info.t): ⟨ρ|H|ρ⟩ = $(energy)" + @test energy ≈ bme atol = 2.0e-2 + end +end diff --git a/test/timeevol/tf_ising_realtime.jl b/test/timeevol/tf_ising_realtime.jl new file mode 100644 index 000000000..977d5f283 --- /dev/null +++ b/test/timeevol/tf_ising_realtime.jl @@ -0,0 +1,90 @@ +using Test +using TensorKit +import MPSKitModels: S_zz, σˣ +using PEPSKit +using Printf +using Accessors: @set + +const hc = 3.044382 +const formatter = Printf.Format("t = %.2f, ⟨σˣ⟩ = %.7e + %.7e im.") +# real time evolution of ⟨σx⟩ +# benchmark data from Physical Review B 104, 094411 (2021) +# Figure 6(a) calculated with D = 8 and χ = 32 +const data = [ + # 0.01 9.9920027e-1 + 0.06 9.7274912e-1 + 0.11 9.1973182e-1 + 0.16 8.6230618e-1 + 0.21 8.1894325e-1 + 0.26 8.0003708e-1 + 0.31 8.0081082e-1 + 0.36 8.0979257e-1 + # 0.41 8.1559623e-1 + # 0.46 8.1541661e-1 + # 0.51 8.1274128e-1 +] + +# the fully polarized state +peps0 = InfinitePEPS(zeros, ComplexF64, ℂ^2, ℂ^1; unitcell = (2, 2)) +for t in peps0.A + t[1, 1, 1, 1, 1] = 1.0 + t[2, 1, 1, 1, 1] = 1.0 +end +lattice = collect(space(t, 1) for t in peps0.A) + +# Hamiltonian +op = LocalOperator(lattice, ((1, 1),) => σˣ()) +ham = transverse_field_ising(ComplexF64, Trivial, InfiniteSquare(2, 2); J = 1.0, g = hc) + +# truncation strategy +Dcut, chi = 4, 16 +trunc_peps = truncerror(; atol = 1.0e-10) & truncrank(Dcut) +trunc_env = truncerror(; atol = 1.0e-10) & truncrank(chi) + +ctm_alg = SequentialCTMRG(; + tol = 1.0e-8, maxiter = 50, verbosity = 2, + trunc = trunc_env, projector_alg = :fullinfinite +) + +interval = 5 +ntu_alg = NeighbourUpdate(; + opt_alg = FullEnvTruncation(; trunc = trunc_peps, tol = 1.0e-10), + bondenv_alg = NNEnv(), imaginary_time = false +) + +# do one step of NTU to match benchmark data +peps0, = time_evolve(peps0, ham, 0.01, 6, ntu_alg) +@info "Space of `peps0[1, 1]` = $(space(peps0[1, 1]))." +env0 = CTMRGEnv(ones, ComplexF64, peps0, ℂ^1) +env0, = leading_boundary(env0, peps0, ctm_alg) +# measure magnetization +magx = expectation_value(peps0, op, env0) +@info Printf.format(formatter, 0.06, real(magx), imag(magx)) +@test isapprox(magx, data[1, 2]; atol = 0.005) + +@testset "Neigborhood tensor update" begin + peps, env = deepcopy(peps0), deepcopy(env0) + count = 2 + evolver = TimeEvolver(peps, ham, 0.01, 30, ntu_alg; t0 = 0.06) + spaces0 = collect(space(t) for t in peps.A) + for (peps, info) in evolver + !(evolver.state.iter % interval == 0) && continue + spaces = collect(space(t) for t in peps.A) + if spaces0 == spaces + env, = leading_boundary(env, peps, ctm_alg) + else + env = complex(CTMRGEnv(info.wts)) + env, = leading_boundary(env, peps, ctm_alg) + end + # monitor the growth of env dimension + corner = env.corners[1, 1, 1] + corner_dim = dim.(space(corner, ax) for ax in 1:numind(corner)) + @info "Dimension of env.corner[1, 1, 1] = $(corner_dim)." + # measure magnetization + magx = expectation_value(peps, op, env) + @info Printf.format(formatter, info.t, real(magx), imag(magx)) + @test isapprox(magx, data[count, 2]; atol = 0.005) + count += 1 + spaces0 = spaces + end +end From f9d029ae9ddbde0fa7940893b1624d4b36ad2837 Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Wed, 8 Apr 2026 23:03:45 +0800 Subject: [PATCH 08/41] Remove unneeded bipartite restrictions --- src/algorithms/time_evolution/ntupdate.jl | 5 +---- src/algorithms/time_evolution/simpleupdate.jl | 5 +---- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/src/algorithms/time_evolution/ntupdate.jl b/src/algorithms/time_evolution/ntupdate.jl index f08d9fbfd..b8b90b00f 100644 --- a/src/algorithms/time_evolution/ntupdate.jl +++ b/src/algorithms/time_evolution/ntupdate.jl @@ -91,10 +91,7 @@ function ntu_iter( info′ = (; fid = 1.0) elseif length(sites) == 2 (d, r, c), = _nn_bondrev(sites..., (Nr, Nc)) - if alg.bipartite - length(sites) > 2 && error("Multi-site MPO gates are not compatible with bipartite states.") - r > 1 && continue - end + alg.bipartite && r > 1 && continue state2, wts, info′ = _ntu_iter(state2, gate, wts, sites, alg) (!alg.bipartite) && continue if d == 1 diff --git a/src/algorithms/time_evolution/simpleupdate.jl b/src/algorithms/time_evolution/simpleupdate.jl index 9d6842844..a28f86d8b 100644 --- a/src/algorithms/time_evolution/simpleupdate.jl +++ b/src/algorithms/time_evolution/simpleupdate.jl @@ -142,10 +142,7 @@ function su_iter( state2[r, c] = _apply_sitegate(state2[r, c], gate; alg.purified) elseif length(sites) == 2 (d, r, c), = _nn_bondrev(sites..., (Nr, Nc)) - if alg.bipartite - length(sites) > 2 && error("Multi-site MPO gates are not compatible with bipartite states.") - r > 1 && continue - end + alg.bipartite && r > 1 && continue ϵ′ = _su_iter!(state2, gate, env2, sites, alg) ϵ = max(ϵ, ϵ′) (!alg.bipartite) && continue From c51b54b6e5028404aebe16ffb96abb6597428809 Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Sun, 12 Apr 2026 10:08:05 +0800 Subject: [PATCH 09/41] Test `timestep` for NTU --- test/timeevol/timestep.jl | 36 ++++++++++++++++++++++++++++++------ 1 file changed, 30 insertions(+), 6 deletions(-) diff --git a/test/timeevol/timestep.jl b/test/timeevol/timestep.jl index 39153a898..b9c2ea9fb 100644 --- a/test/timeevol/timestep.jl +++ b/test/timeevol/timestep.jl @@ -3,14 +3,16 @@ using Random using TensorKit using PEPSKit +Nr, Nc = 2, 2 +H = real(heisenberg_XYZ(ComplexF64, Trivial, InfiniteSquare(Nr, Nc); Jx = 1, Jy = 1, Jz = 1)) +Pspace, Vspace = ℂ^2, ℂ^4 +ψ0 = InfinitePEPS(rand, Float64, Pspace, Vspace; unitcell = (Nr, Nc)) +dt, nstep = 0.1, 20 +trunc = truncerror(; atol = 1.0e-10) & truncrank(4) + @testset "SimpleUpdate timestep" begin - Nr, Nc = 2, 2 - H = real(heisenberg_XYZ(ComplexF64, Trivial, InfiniteSquare(Nr, Nc); Jx = 1, Jy = 1, Jz = 1)) - Pspace, Vspace = ℂ^2, ℂ^4 - ψ0 = InfinitePEPS(rand, Float64, Pspace, Vspace; unitcell = (Nr, Nc)) + alg = SimpleUpdate(; trunc) env0 = SUWeight(ψ0) - alg = SimpleUpdate(; trunc = truncerror(; atol = 1.0e-10) & truncrank(4)) - dt, nstep = 1.0e-2, 50 # manual timestep evolver = TimeEvolver(ψ0, H, dt, nstep, alg, env0) ψ1, env1, info1 = deepcopy(ψ0), deepcopy(env0), nothing @@ -31,3 +33,25 @@ using PEPSKit @test env1 == env2 == env3 @test info1 == info2 == info3 end + +@testset "NeighbourUpdate timestep" begin + alg = NeighbourUpdate(; opt_alg = ALSTruncation(; trunc)) + # manual timestep + evolver = TimeEvolver(ψ0, H, dt, nstep, alg) + ψ1, info1 = deepcopy(ψ0), nothing + for iter in 0:(nstep - 1) + ψ1, info1 = timestep(evolver, ψ1) + end + # time_evolve + ψ2, info2 = time_evolve(ψ0, H, dt, nstep, alg) + # for-loop syntax + ## manually reset internal state of evolver + evolver.state = PEPSKit.NTUState(0, 0.0, ψ0) + ψ3, info3 = nothing, nothing, nothing + for state in evolver + ψ3, info3 = state + end + # results should be *exactly* the same + @test ψ1 == ψ2 == ψ3 + @test info1 == info2 == info3 +end From e8f07eb723d0bb2d078c73e5164ba7f44df57328 Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Sun, 12 Apr 2026 11:59:59 +0800 Subject: [PATCH 10/41] Convergence check for NTU --- src/PEPSKit.jl | 2 +- src/algorithms/time_evolution/ntupdate.jl | 104 +++++++++++++++++++--- 2 files changed, 91 insertions(+), 15 deletions(-) diff --git a/src/PEPSKit.jl b/src/PEPSKit.jl index ce16b89b5..4700303a5 100644 --- a/src/PEPSKit.jl +++ b/src/PEPSKit.jl @@ -3,7 +3,7 @@ module PEPSKit using LinearAlgebra, Statistics, Base.Threads, Base.Iterators, Printf using Random using Compat -using Accessors: @set, @reset +using Accessors: @set, @reset, @insert using VectorInterface import VectorInterface as VI diff --git a/src/algorithms/time_evolution/ntupdate.jl b/src/algorithms/time_evolution/ntupdate.jl index b8b90b00f..98fced498 100644 --- a/src/algorithms/time_evolution/ntupdate.jl +++ b/src/algorithms/time_evolution/ntupdate.jl @@ -79,7 +79,7 @@ function ntu_iter( state::InfiniteState, circuit::LocalCircuit, alg::NeighbourUpdate ) Nr, Nc, = size(state) - state2, wts = deepcopy(state), SUWeight(state) + state2, wts = copy(state), SUWeight(state) info = (; fid = 1.0) for (sites, gate) in circuit.gates if length(sites) == 1 @@ -96,14 +96,14 @@ function ntu_iter( (!alg.bipartite) && continue if d == 1 rp1, cp1 = _next(r, Nr), _next(c, Nc) - state2[rp1, cp1] = deepcopy(state2[r, c]) - state2[rp1, c] = deepcopy(state2[r, cp1]) - wts[1, rp1, cp1] = deepcopy(wts[1, r, c]) + state2[rp1, cp1] = copy(state2[r, c]) + state2[rp1, c] = copy(state2[r, cp1]) + wts[1, rp1, cp1] = copy(wts[1, r, c]) else rm1, cm1 = _prev(r, Nr), _prev(c, Nc) - state2[rm1, cm1] = deepcopy(state2[r, c]) - state2[r, cm1] = deepcopy(state2[rm1, c]) - wts[2, rm1, cm1] = deepcopy(wts[2, r, c]) + state2[rm1, cm1] = copy(state2[r, c]) + state2[r, cm1] = copy(state2[rm1, c]) + wts[2, rm1, cm1] = copy(wts[2, r, c]) end else # N-site MPO gate (N ≥ 2) @@ -156,14 +156,91 @@ end """ time_evolve( - it::TimeEvolver{<:NeighbourUpdate}; check_interval::Int = 500 + it::TimeEvolver{<:NeighbourUpdate}, + [H::LocalOperator, env::CTMRGEnv, ctm_alg::CTMRGAlgorithm]; + tol::Float64 = 1.0e-7, check_interval::Int = 10 ) -> (psi, info) -Perform time evolution to the end of `TimeEvolver` iterator `it`. +Perform time evolution to the end of `NeighbourUpdate` TimeEvolver `it`, +or until convergence of energy set by a positive `tol`. -- `check_interval` sets the number of iterations between outputs of information. +To enable convergence check (for imaginary time evolution of InfinitePEPS only), +provide the Hamiltonian `H`, CTMRG environment `env`, CTMRG algorithm `ctm_alg` +and setting `tol > 0`. + +`check_interval` sets the number of iterations between energy checks +(for ground state search) and outputs of information. """ -function MPSKit.time_evolve(it::TimeEvolver{<:NeighbourUpdate}; check_interval::Int = 500) +function MPSKit.time_evolve( + it::TimeEvolver{<:NeighbourUpdate}, + H::LocalOperator, env::CTMRGEnv, ctm_alg::CTMRGAlgorithm; + tol::Float64 = 1.0e-7, check_interval::Int = 10 + ) + @info "--- Time evolution (neighbourhood tensor update), dt = $(it.dt) ---" + time_start = time0 = time() + psi0 = copy(it.state.psi) + @assert (psi0 isa InfinitePEPS) && it.alg.imaginary_time "Only imaginary time evolution of InfinitePEPS allows convergence checking." + # initial energy + env, = leading_boundary(env, psi0, ctm_alg) + energy = expectation_value(psi0, H, env) / prod(size(psi0)) + @info @sprintf("NTU iter 0: E = %.4e", energy) + info0 = (; energy, env) + # start evolving + energy0, ΔE = energy, 0.0 + iter0, t0 = it.state.iter, it.state.t + for (psi, info) in it + iter = it.state.iter + showinfo = (iter == 1) || (iter % check_interval == 0) || (iter == it.nstep) + !showinfo && continue + # bond weight change + Δλ = hasproperty(info0, :wts) ? compare_weights(info.wts, info0.wts) : NaN + # reconverge environment + if all(space(t) == space(t0) for (t, t0) in zip(psi.A, psi0.A)) + # recreate `env` from bond weights if psi virtual space changed + env = CTMRGEnv(info.wts) + end + env, = leading_boundary(env, psi, ctm_alg) + # measure energy + energy = expectation_value(psi, H, env) / prod(size(psi)) + ΔE = energy - energy0 + info = @insert info.energy = energy + info = @insert info.env = env + # show information + time1 = time() + @info @sprintf( + "NTU iter %-6d: E = %.5f, ΔE = %.3e, |Δλ| = %.3e. Time: %.2f s", + it.state.iter, energy, ΔE, Δλ, time1 - time0 + ) + # determine whether to stop evolution + stop = false + if (ΔE <= 0 && abs(ΔE) < tol) + stop = true + @info "NTU: energy has converged." + end + if ΔE > 0 + stop = true + @warn "NTU: energy has increased. Abort evolution and return results from last check." + psi, info, energy = psi0, info0, energy0 + it.state = NTUState(iter0, t0, psi0) + end + if iter == it.nstep + stop = true + @info "NTU: reached maximum iteration." + end + if stop + time_end = time() + @info @sprintf("Time evolution finished in %.2f s", time_end - time_start) + return psi, info + else + iter0, t0 = it.state.iter, it.state.t + psi0, energy0, info0 = psi, energy, info + end + time0 = time() + end + return +end + +function MPSKit.time_evolve(it::TimeEvolver{<:NeighbourUpdate}; check_interval::Int = 50) time_start = time0 = time() @info "--- Time evolution (neighbourhood tensor update), dt = $(it.dt) ---" info0 = nothing @@ -197,11 +274,10 @@ end check_interval::Int = 10 ) -> (psi, info) -Perform time evolution on the initial state `psi0` and initial environment `env0` -with Hamiltonian `H`, using `NeighbourUpdate` algorithm `alg`, time step `dt` for +Perform time evolution on the initial state `psi0` with Hamiltonian `H`, +using `NeighbourUpdate` algorithm `alg`, time step `dt` for `nstep` number of steps. -- Convergence check for ground state search is not supported. - Set `symmetrize_gates = true` for second-order Trotter decomposition. - Use `t0` to specify the initial time of `psi0`. - `check_interval` sets the interval to output information (and check convergence). From a4de5ff54a339d5e4cc612beb77403959fbf3a7a Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Wed, 15 Apr 2026 13:30:47 +0800 Subject: [PATCH 11/41] Streamline convergence checks --- src/algorithms/time_evolution/ntupdate.jl | 110 ++++++++-------------- 1 file changed, 39 insertions(+), 71 deletions(-) diff --git a/src/algorithms/time_evolution/ntupdate.jl b/src/algorithms/time_evolution/ntupdate.jl index 98fced498..cda69b436 100644 --- a/src/algorithms/time_evolution/ntupdate.jl +++ b/src/algorithms/time_evolution/ntupdate.jl @@ -155,34 +155,56 @@ function MPSKit.timestep( end """ - time_evolve( - it::TimeEvolver{<:NeighbourUpdate}, - [H::LocalOperator, env::CTMRGEnv, ctm_alg::CTMRGAlgorithm]; - tol::Float64 = 1.0e-7, check_interval::Int = 10 - ) -> (psi, info) +$(SIGNATURES) -Perform time evolution to the end of `NeighbourUpdate` TimeEvolver `it`, -or until convergence of energy set by a positive `tol`. +Perform time evolution to the end of `NeighbourUpdate` TimeEvolver `it`. +`check_interval` sets the number of iterations between outputs of information. +""" +function MPSKit.time_evolve(it::TimeEvolver{<:NeighbourUpdate}; check_interval::Int = 50) + time_start = time0 = time() + @info "--- Time evolution (neighbourhood tensor update), dt = $(it.dt) ---" + info0 = nothing + for (psi, info) in it + iter = it.state.iter + stop = (iter == it.nstep) + showinfo = (iter == 1) || (iter % check_interval == 0) || stop + time1 = time() + if showinfo + Δλ = (info0 === nothing) ? NaN : compare_weights(info.wts, info0.wts) + @info @sprintf( + "NTU iter %d: t = %.2e, |Δλ| = %.3e. Time: %.2f s", + it.state.iter, it.state.t, Δλ, time1 - time0 + ) + end + if stop + time_end = time() + @info @sprintf("Time evolution finished in %.2f s", time_end - time_start) + return psi, info + end + info0, time0 = info, time() + end + return +end -To enable convergence check (for imaginary time evolution of InfinitePEPS only), -provide the Hamiltonian `H`, CTMRG environment `env`, CTMRG algorithm `ctm_alg` -and setting `tol > 0`. +""" +$(SIGNATURES) -`check_interval` sets the number of iterations between energy checks -(for ground state search) and outputs of information. +Perform time evolution until convergence of `TimeEvolver` iterator `it`. +For `NeighbourUpdate`, convergence is determined by the change of energy +between two checks being smaller than `tol`. """ function MPSKit.time_evolve( - it::TimeEvolver{<:NeighbourUpdate}, + it::TimeEvolver{<:NeighbourUpdate, G, S}, H::LocalOperator, env::CTMRGEnv, ctm_alg::CTMRGAlgorithm; tol::Float64 = 1.0e-7, check_interval::Int = 10 - ) + ) where {G, S <: NTUState{<:InfinitePEPS}} @info "--- Time evolution (neighbourhood tensor update), dt = $(it.dt) ---" time_start = time0 = time() psi0 = copy(it.state.psi) - @assert (psi0 isa InfinitePEPS) && it.alg.imaginary_time "Only imaginary time evolution of InfinitePEPS allows convergence checking." + @assert it.alg.imaginary_time "Only imaginary time evolution of InfinitePEPS allows convergence checking." # initial energy env, = leading_boundary(env, psi0, ctm_alg) - energy = expectation_value(psi0, H, env) / prod(size(psi0)) + energy = real(expectation_value(psi0, H, env)) / prod(size(psi0)) @info @sprintf("NTU iter 0: E = %.4e", energy) info0 = (; energy, env) # start evolving @@ -201,7 +223,7 @@ function MPSKit.time_evolve( end env, = leading_boundary(env, psi, ctm_alg) # measure energy - energy = expectation_value(psi, H, env) / prod(size(psi)) + energy = real(expectation_value(psi, H, env)) / prod(size(psi)) ΔE = energy - energy0 info = @insert info.energy = energy info = @insert info.env = env @@ -239,57 +261,3 @@ function MPSKit.time_evolve( end return end - -function MPSKit.time_evolve(it::TimeEvolver{<:NeighbourUpdate}; check_interval::Int = 50) - time_start = time0 = time() - @info "--- Time evolution (neighbourhood tensor update), dt = $(it.dt) ---" - info0 = nothing - for (psi, info) in it - iter = it.state.iter - stop = (iter == it.nstep) - showinfo = (iter == 1) || (iter % check_interval == 0) || stop - time1 = time() - if showinfo - Δλ = (info0 === nothing) ? NaN : compare_weights(info.wts, info0.wts) - @info @sprintf( - "NTU iter %d: t = %.2e, |Δλ| = %.3e. Time: %.2f s", - it.state.iter, it.state.t, Δλ, time1 - time0 - ) - end - if stop - time_end = time() - @info @sprintf("Time evolution finished in %.2f s", time_end - time_start) - return psi, info - end - info0, time0 = info, time() - end - return -end - -""" - time_evolve( - psi0::Union{InfinitePEPS, InfinitePEPO}, H::LocalOperator, - dt::Number, nstep::Int, alg::NeighbourUpdate; - t0::Number = 0.0, symmetrize_gates::Bool = false, - check_interval::Int = 10 - ) -> (psi, info) - -Perform time evolution on the initial state `psi0` with Hamiltonian `H`, -using `NeighbourUpdate` algorithm `alg`, time step `dt` for -`nstep` number of steps. - -- Set `symmetrize_gates = true` for second-order Trotter decomposition. -- Use `t0` to specify the initial time of `psi0`. -- `check_interval` sets the interval to output information (and check convergence). - Output during the evolution can be turned off by setting `check_interval <= 0`. -- `info` is a NamedTuple containing information of the evolution, - including the time `info.t` evolved since `psi0`. -""" -function MPSKit.time_evolve( - psi0::InfiniteState, H::LocalOperator, dt::Number, nstep::Int, - alg::NeighbourUpdate; symmetrize_gates::Bool = false, - t0::Number = 0.0, check_interval::Int = 10 - ) - it = TimeEvolver(psi0, H, dt, nstep, alg; t0, symmetrize_gates) - return time_evolve(it; check_interval) -end From c14fa161ee54c4c84f82aafda343075b85268c71 Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Fri, 17 Apr 2026 20:09:33 +0800 Subject: [PATCH 12/41] Merge #360 --- src/algorithms/time_evolution/apply_gate.jl | 5 -- src/algorithms/time_evolution/apply_mpo.jl | 10 +-- src/algorithms/time_evolution/simpleupdate.jl | 79 +++++++++++++------ .../time_evolution/simpleupdate3site.jl | 9 ++- test/bondenv/benv_ctm.jl | 2 +- test/ctmrg/suweight.jl | 7 +- test/runtests.jl | 3 + test/timeevol/cluster_projectors.jl | 3 +- test/timeevol/fixedspacetruncation.jl | 32 ++++++++ 9 files changed, 109 insertions(+), 41 deletions(-) create mode 100644 test/timeevol/fixedspacetruncation.jl diff --git a/src/algorithms/time_evolution/apply_gate.jl b/src/algorithms/time_evolution/apply_gate.jl index 363b23f8d..139b68196 100644 --- a/src/algorithms/time_evolution/apply_gate.jl +++ b/src/algorithms/time_evolution/apply_gate.jl @@ -134,11 +134,6 @@ function _apply_gate( else @tensor a2b2[-1 -2; -3 -4] := gate[-2 -3; 1 2] * a[-1 1 3] * b[3 2 -4] end - trunc = if trunc isa FixedSpaceTruncation - need_flip ? truncspace(flip(V)) : truncspace(V) - else - trunc - end a, s, b, ϵ = svd_trunc!(a2b2; trunc, alg = LAPACK_QRIteration()) a, b = absorb_s(a, s, b) if need_flip diff --git a/src/algorithms/time_evolution/apply_mpo.jl b/src/algorithms/time_evolution/apply_mpo.jl index b7ae6f42f..60bfc6db7 100644 --- a/src/algorithms/time_evolution/apply_mpo.jl +++ b/src/algorithms/time_evolution/apply_mpo.jl @@ -229,14 +229,8 @@ function _get_allprojs( N = length(Ms) Rs, Ls = _get_allRLs(Ms) @assert length(truncs) == N - 1 - projs_errs = map(1:(N - 1)) do i - trunc = if isa(truncs[i], FixedSpaceTruncation) - tspace = space(Ms[i + 1], 1) - isdual(tspace) ? truncspace(flip(tspace)) : truncspace(tspace) - else - truncs[i] - end - return _proj_from_RL(Rs[i], Ls[i]; trunc) + projs_errs = map(zip(Rs, Ls, truncs)) do (R, L, trunc) + return _proj_from_RL(R, L; trunc) end Pas = map(Base.Fix2(getindex, 1), projs_errs) wts = map(Base.Fix2(getindex, 2), projs_errs) diff --git a/src/algorithms/time_evolution/simpleupdate.jl b/src/algorithms/time_evolution/simpleupdate.jl index a28f86d8b..3bcfdc256 100644 --- a/src/algorithms/time_evolution/simpleupdate.jl +++ b/src/algorithms/time_evolution/simpleupdate.jl @@ -89,19 +89,23 @@ function _su_iter!( sites::Vector{CartesianIndex{2}}, alg::SimpleUpdate ) Nr, Nc = size(state) - truncs = _get_cluster_trunc(alg.trunc, sites, (Nr, Nc)) - @assert length(sites) == 2 && length(truncs) == 1 + trunc = only(_get_cluster_trunc(alg.trunc, sites, (Nr, Nc))) + @assert length(sites) == 2 Ms, open_vaxs, = _get_cluster(state, sites, env; permute = false) normalize!.(Ms, Inf) # rotate bond, rev = _nn_bondrev(sites..., (Nr, Nc)) A, B = _bond_rotation.(Ms, bond[1], rev; inv = false) + if trunc isa FixedSpaceTruncation + V = west_virtualspace(B) + trunc = truncspace(isdual(V) ? flip(V) : V) + end # apply gate ϵ, s = 0.0, nothing gate_axs = alg.purified ? (1:1) : (1:2) for gate_ax in gate_axs X, a, b, Y = _qr_bond(A, B; gate_ax, positive = true) - a, s, b, ϵ′ = _apply_gate(a, b, gate, truncs[1]) + a, s, b, ϵ′ = _apply_gate(a, b, gate, trunc) ϵ = max(ϵ, ϵ′) A, B = _qr_bond_undo(X, a, b, Y) end @@ -202,10 +206,8 @@ function MPSKit.timestep( end """ - time_evolve( - it::TimeEvolver{<:SimpleUpdate}; - tol::Float64 = 0.0, check_interval::Int = 500 - ) -> (psi, env, info) + time_evolve(it; check_interval = 500) -> (psi, env, info) + time_evolve(it, H; tol = 1.0e-8, check_interval = 500) -> (psi, env, info) Perform time evolution to the end of `TimeEvolver` iterator `it`, or until convergence of `SUWeight` set by a positive `tol`. @@ -215,15 +217,41 @@ or until convergence of `SUWeight` set by a positive `tol`. - `check_interval` sets the number of iterations between outputs of information. """ function MPSKit.time_evolve( - it::TimeEvolver{<:SimpleUpdate}; - tol::Float64 = 0.0, check_interval::Int = 500 + it::TimeEvolver{<:SimpleUpdate}; check_interval::Int = 500 ) time_start = time() - check_convergence = (tol > 0) @info "--- Time evolution (simple update), dt = $(it.dt) ---" - if check_convergence - @assert (it.state.psi isa InfinitePEPS) && it.alg.imaginary_time "Only imaginary time evolution of InfinitePEPS allows convergence checking." + env0, time0 = it.state.env, time() + for (psi, env, info) in it + iter = it.state.iter + diff = compare_weights(env0, env) + stop = (iter == it.nstep) + showinfo = (check_interval > 0) && + ((iter % check_interval == 0) || (iter == 1) || stop) + time1 = time() + if showinfo + @info "Space of x-weight at [1, 1] = $(space(env[1, 1, 1], 1))" + @info @sprintf("SU iter %-7d: |Δλ| = %.3e. Time = %.3f s/it", iter, diff, time1 - time0) + end + if stop + time_end = time() + @info @sprintf("Time evolution finished in %.2f s", time_end - time_start) + return psi, env, info + else + env0 = env + end + time0 = time() end + return +end + +function MPSKit.time_evolve( + it::TimeEvolver{<:SimpleUpdate, G, S}, H::LocalOperator; + tol::Float64 = 1.0e-8, check_interval::Int = 500 + ) where {G, S <: SUState{<:InfinitePEPS}} + time_start = time() + @info "--- Time evolution (simple update), dt = $(it.dt) ---" + @assert it.alg.imaginary_time "Only imaginary time evolution of InfinitePEPS allows convergence checking." env0, time0 = it.state.env, time() for (psi, env, info) in it iter = it.state.iter @@ -233,16 +261,20 @@ function MPSKit.time_evolve( ((iter % check_interval == 0) || (iter == 1) || stop) time1 = time() if showinfo + # TODO: convert to BPEnv instead + ctmenv = CTMRGEnv(env) + energy = real(expectation_value(psi, H, ctmenv)) / prod(size(psi)) @info "Space of x-weight at [1, 1] = $(space(env[1, 1, 1], 1))" - @info @sprintf("SU iter %-7d: |Δλ| = %.3e. Time = %.3f s/it", iter, diff, time1 - time0) + @info @sprintf( + "SU iter %-7d: E ≈ %.5f, |Δλ| = %.3e. Time = %.3f s/it", + iter, energy, diff, time1 - time0 + ) end - if check_convergence - if (iter == it.nstep) && (diff >= tol) - @warn "SU: bond weights have not converged." - end - if diff < tol - @info "SU: bond weights have converged." - end + if (iter == it.nstep) && (diff >= tol) + @warn "SU: bond weights have not converged." + end + if diff < tol + @info "SU: bond weights have converged." end if stop time_end = time() @@ -269,7 +301,6 @@ algorithm `alg`, time step `dt` for `nstep` number of steps. - Set `symmetrize_gates = true` for second-order Trotter decomposition. - Set `tol > 0` to enable convergence check (for imaginary time evolution of iPEPS only). - For other usages it should not be changed. - Use `t0` to specify the initial time of the evolution. - `check_interval` sets the interval to output information. Output during the evolution can be turned off by setting `check_interval <= 0`. - `info` is a NamedTuple containing information of the evolution, @@ -281,5 +312,9 @@ function MPSKit.time_evolve( tol::Float64 = 0.0, t0::Number = 0.0, check_interval::Int = 500 ) it = TimeEvolver(psi0, H, dt, nstep, alg, env0; t0, symmetrize_gates) - return time_evolve(it; tol, check_interval) + return if tol == 0 + time_evolve(it; check_interval) + else + time_evolve(it, H; tol, check_interval) + end end diff --git a/src/algorithms/time_evolution/simpleupdate3site.jl b/src/algorithms/time_evolution/simpleupdate3site.jl index 4b091c830..3b16820f8 100644 --- a/src/algorithms/time_evolution/simpleupdate3site.jl +++ b/src/algorithms/time_evolution/simpleupdate3site.jl @@ -167,13 +167,20 @@ function _su_iter!( sites::Vector{CartesianIndex{2}}, alg::SimpleUpdate ) where {T <: AbstractTensorMap} Nr, Nc = size(state) - truncs = _get_cluster_trunc(alg.trunc, sites, (Nr, Nc)) Ms, open_vaxs, invperms = _get_cluster(state, sites, env) flips = [isdual(space(M, 1)) for M in Ms[2:end]] Vphys = [codomain(M, 2) for M in Ms] normalize!.(Ms, Inf) # flip virtual arrows in `Ms` to ← _flip_virtuals!(Ms, flips) + truncs = _get_cluster_trunc(alg.trunc, sites, (Nr, Nc)) + truncs = map(enumerate(truncs)) do (i, trunc) + return if trunc isa FixedSpaceTruncation + truncspace(space(Ms[i + 1], 1)) + else + trunc + end + end # apply gate MPOs and truncate gate_axs = alg.purified ? (1:1) : (1:2) wts, ϵs = nothing, nothing diff --git a/test/bondenv/benv_ctm.jl b/test/bondenv/benv_ctm.jl index 7676c5139..95ece1658 100644 --- a/test/bondenv/benv_ctm.jl +++ b/test/bondenv/benv_ctm.jl @@ -19,7 +19,7 @@ function get_hubbard_peps(t::Float64 = 1.0, U::Float64 = 8.0) wts = SUWeight(peps) alg = SimpleUpdate(; trunc = truncerror(; atol = 1.0e-10) & truncrank(4)) evolver = TimeEvolver(peps, H, 1.0e-2, 10000, alg, wts) - peps, = time_evolve(evolver; tol = 1.0e-8, check_interval = 2000) + peps, = time_evolve(evolver, H; tol = 1.0e-8, check_interval = 2000) normalize!.(peps.A, Inf) return peps end diff --git a/test/ctmrg/suweight.jl b/test/ctmrg/suweight.jl index 3e90e28cc..12ff64e09 100644 --- a/test/ctmrg/suweight.jl +++ b/test/ctmrg/suweight.jl @@ -2,7 +2,7 @@ using Test using Random using TensorKit using PEPSKit -using PEPSKit: str, twistdual, _prev, _next +using PEPSKit: str, twistdual, _prev, _next, unitcell Vps = Dict( Z2Irrep => Vect[Z2Irrep](0 => 1, 1 => 2), @@ -43,12 +43,13 @@ end normalize!.(wts.data, Inf) end env = CTMRGEnv(wts) - for (r, c) in Tuple.(CartesianIndices(peps.A)) + for idx in CartesianIndices(unitcell(peps)) + r, c = Tuple(idx) ρ1 = su_rdm_1x1(r, c, peps, wts) if init == :trivial @test ρ1 ≈ su_rdm_1x1(r, c, peps, nothing) end - ρ2 = reduced_densitymatrix(((r, c),), peps, env) + ρ2 = reduced_densitymatrix([idx], peps, env) @test ρ1 ≈ ρ2 end end diff --git a/test/runtests.jl b/test/runtests.jl index e6627e258..bdc2f7801 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -110,6 +110,9 @@ end @time @safetestset "Time evolution with site-dependent truncation" begin include("timeevol/sitedep_truncation.jl") end + @time @safetestset "Time evolution with FixedSpaceTruncation" begin + include("timeevol/fixedspacetruncation.jl") + end @time @safetestset "Transverse field Ising model at finite temperature" begin include("timeevol/tf_ising_finiteT.jl") end diff --git a/test/timeevol/cluster_projectors.jl b/test/timeevol/cluster_projectors.jl index 53fad0a68..1276ad2f8 100644 --- a/test/timeevol/cluster_projectors.jl +++ b/test/timeevol/cluster_projectors.jl @@ -35,7 +35,8 @@ Vspaces = [ flips = [isdual(space(M, 1)) for M in Ms1[2:end]] # no truncation Ms2 = _flip_virtuals!(deepcopy(Ms1), flips) - wts2, ϵs, = _cluster_truncate!(Ms2, fill(FixedSpaceTruncation(), N - 1)) + truncs = [truncrank(dim(space(M, 1))) for M in Ms2[2:end]] + wts2, ϵs, = _cluster_truncate!(Ms2, truncs) @test all((ϵ == 0) for ϵ in ϵs) normalize!.(Ms2, Inf) @test fidelity_cluster(Ms1, Ms2) ≈ 1.0 diff --git a/test/timeevol/fixedspacetruncation.jl b/test/timeevol/fixedspacetruncation.jl new file mode 100644 index 000000000..c77703fd7 --- /dev/null +++ b/test/timeevol/fixedspacetruncation.jl @@ -0,0 +1,32 @@ +using Test +using Random +using TensorKit +using PEPSKit + +elt = Float64 +ham = j1_j2_model(elt, U1Irrep, InfiniteSquare(2, 2); J1 = 1.0, J2 = 0.5, sublattice = false) +Vphy = physicalspace(ham) +Vvir = U1Space(0 => 1, -1 / 2 => 1, 1 / 2 => 1) +Vns = [ + U1Space(0 => 1, 1 => 2, -1 => 1) U1Space(0 => 1, 1 => 1, -1 => 2); + U1Space(0 => 1, 1 => 2, -1 => 1) U1Space(0 => 1, 1 => 1, -1 => 2) +] +Ves1 = [ + U1Space(0 => 1, 1 => 1, -1 => 2) U1Space(1 / 2 => 2, -1 / 2 => 1, 3 / 2 => 1); + U1Space(0 => 1, 1 => 1, -1 => 2) U1Space(1 / 2 => 1, -1 / 2 => 2, -3 / 2 => 1) +] +Ves2 = fill(U1Space(0 => 1, 1 => 1, -1 => 2), (2, 2)) +Venv = U1Space(0 => 2, 1 => 1, -1 => 1) +states = [ + InfinitePEPS(randn, elt, Vphy, Vns, Ves1), + InfinitePEPO(randn, elt, Vphy, Vns, Ves2), +] + +@testset "Simple update on $(typeof(state0).name.wrapper)" for state0 in states + alg = SimpleUpdate(; trunc = FixedSpaceTruncation()) + wts0 = SUWeight(state0) + state, wts, = time_evolve(state0, ham, 0.1, 1, alg, wts0) + for (t, t0) in zip(state.A, state0.A) + @test space(t) == space(t0) + end +end From 3a915c41f76770979e814d877c68cdca7e039f77 Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Fri, 17 Apr 2026 20:21:26 +0800 Subject: [PATCH 13/41] FixedSpaceTruncation for NTU --- src/algorithms/time_evolution/ntupdate.jl | 24 +++++++----- .../time_evolution/ntupdate3site.jl | 38 +++++++++++++------ src/algorithms/time_evolution/simpleupdate.jl | 7 ++++ test/timeevol/fixedspacetruncation.jl | 10 +++++ 4 files changed, 57 insertions(+), 22 deletions(-) diff --git a/src/algorithms/time_evolution/ntupdate.jl b/src/algorithms/time_evolution/ntupdate.jl index cda69b436..ace6efee3 100644 --- a/src/algorithms/time_evolution/ntupdate.jl +++ b/src/algorithms/time_evolution/ntupdate.jl @@ -155,10 +155,21 @@ function MPSKit.timestep( end """ -$(SIGNATURES) + time_evolve( + it::TimeEvolver{<:NeighbourUpdate}, + [H::LocalOperator, env::CTMRGEnv, ctm_alg::CTMRGAlgorithm]; + tol::Float64 = 1.0e-7, check_interval::Int = 10 + ) -> (psi, info) + +Perform time evolution to the end of `NeighbourUpdate` TimeEvolver `it`, +or until convergence of energy set by a positive `tol`. + +To enable convergence check (for imaginary time evolution of InfinitePEPS only), +provide the Hamiltonian `H`, CTMRG environment `env`, CTMRG algorithm `ctm_alg` +and setting `tol > 0`. -Perform time evolution to the end of `NeighbourUpdate` TimeEvolver `it`. -`check_interval` sets the number of iterations between outputs of information. +`check_interval` sets the number of iterations between energy checks +(for ground state search) and outputs of information. """ function MPSKit.time_evolve(it::TimeEvolver{<:NeighbourUpdate}; check_interval::Int = 50) time_start = time0 = time() @@ -186,13 +197,6 @@ function MPSKit.time_evolve(it::TimeEvolver{<:NeighbourUpdate}; check_interval:: return end -""" -$(SIGNATURES) - -Perform time evolution until convergence of `TimeEvolver` iterator `it`. -For `NeighbourUpdate`, convergence is determined by the change of energy -between two checks being smaller than `tol`. -""" function MPSKit.time_evolve( it::TimeEvolver{<:NeighbourUpdate, G, S}, H::LocalOperator, env::CTMRGEnv, ctm_alg::CTMRGAlgorithm; diff --git a/src/algorithms/time_evolution/ntupdate3site.jl b/src/algorithms/time_evolution/ntupdate3site.jl index 00055f54d..92cc98081 100644 --- a/src/algorithms/time_evolution/ntupdate3site.jl +++ b/src/algorithms/time_evolution/ntupdate3site.jl @@ -6,27 +6,37 @@ function _ntu_iter( sites::Vector{CartesianIndex{2}}, alg::NeighbourUpdate ) where {T <: AbstractTensorMap} Nr, Nc = size(state) + state, wts = copy(state), deepcopy(wts) - # apply gate MPO without truncation Ms, _, invperms = _get_cluster(state, sites) flips = [isdual(space(M, 1)) for M in Ms[2:end]] _flip_virtuals!(Ms, flips) # flip virtual arrows in `Ms` to ← + truncs = _get_cluster_trunc(alg.opt_alg.trunc, sites, (Nr, Nc)) + truncs = map(enumerate(truncs)) do (i, trunc) + return if trunc isa FixedSpaceTruncation + truncspace(space(Ms[i + 1], 1)) + else + trunc + end + end + + # apply gate MPO without truncation _apply_gatempo!(Ms, gate) _flip_virtuals!(Ms, flips) # restore virtual arrows in `Ms` - state2, wts2 = deepcopy(state), deepcopy(wts) for (M, s, invperm) in zip(Ms, sites, invperms) s′ = CartesianIndex(mod1(s[1], Nr), mod1(s[2], Nc)) - state2[s′] = permute(M, invperm) + state[s′] = permute(M, invperm) end # truncate each bond sequentially along the path info = (; fid = 1.0) - for bondsites in zip(sites, Iterators.drop(sites, 1)) - state2, wts2, info′ = _bond_truncate(state2, wts2, bondsites, alg) + for (bondsites, trunc) in zip(zip(sites, Iterators.drop(sites, 1)), truncs) + alg′ = (@set alg.opt_alg.trunc = trunc) + state, wts, info′ = _bond_truncate(state, wts, bondsites, alg′) # record the worst fidelity (info′.fid < info.fid) && (info = info′) end - return state2, wts2, info + return state, wts, info end """ @@ -45,11 +55,7 @@ function _bond_truncate( wts2 = _bond_rotation(wts, bond[1], rev; inv = false) # rotated bond tensors - siteA = if bond[1] == 1 - rev ? siterot180(site1, ucell) : site1 - else - rev ? siterotl90(site1, ucell) : siterotr90(site1, ucell) - end + siteA = _bond_rotation(site1, bond[1], rev, ucell) row, col = mod1.(Tuple(siteA), size(state2)[1:2]) cp1 = _next(col, size(state2, 2)) A, B = state2[row, col], state2[row, cp1] @@ -68,14 +74,22 @@ function _bond_truncate( end # (optional) apply the NN gate + opt_alg = alg.opt_alg if !(gate === nothing) + trunc = if alg.opt_alg.trunc isa FixedSpaceTruncation + V = space(b, 1) + truncspace(isdual(V) ? flip(V) : V) + else + alg.opt_alg.trunc + end + @reset opt_alg.trunc = trunc a, s, b, = _apply_gate(a, b, gate, truncerror(; atol = 1.0e-15)) else a = permute(a, ((1, 2), (3,))) b = permute(b, ((1,), (2, 3))) end - a, s, b, info = bond_truncate(a, b, benv, alg.opt_alg) + a, s, b, info = bond_truncate(a, b, benv, opt_alg) A, B = _qr_bond_undo(X, a, b, Y) normalize!(A, Inf) normalize!(B, Inf) diff --git a/src/algorithms/time_evolution/simpleupdate.jl b/src/algorithms/time_evolution/simpleupdate.jl index 3bcfdc256..bac4a3fc6 100644 --- a/src/algorithms/time_evolution/simpleupdate.jl +++ b/src/algorithms/time_evolution/simpleupdate.jl @@ -79,6 +79,13 @@ function _bond_rotation(x, bonddir::Int, rev::Bool; inv::Bool = false) error("`bonddir` must be 1 (for x-bonds) or 2 (for y-bonds).") end end +function _bond_rotation(x::CartesianIndex{2}, bonddir::Int, rev::Bool, unitcell::NTuple{2, Int}) + return if bonddir == 1 + rev ? siterot180(x, unitcell) : x + else + rev ? siterotl90(x, unitcell) : siterotr90(x, unitcell) + end +end """ Simple update optimized for nearest neighbor gates diff --git a/test/timeevol/fixedspacetruncation.jl b/test/timeevol/fixedspacetruncation.jl index c77703fd7..1777c0436 100644 --- a/test/timeevol/fixedspacetruncation.jl +++ b/test/timeevol/fixedspacetruncation.jl @@ -30,3 +30,13 @@ states = [ @test space(t) == space(t0) end end + +@testset "Neighborhood tensor update on $(typeof(state0).name.wrapper)" for state0 in states + opt_alg = ALSTruncation(; trunc = FixedSpaceTruncation()) + alg = NeighbourUpdate(; opt_alg, bondenv_alg = NNEnv()) + evolver = TimeEvolver(state0, ham, 0.1, 1, alg) + state, = time_evolve(evolver) + for (t, t0) in zip(state.A, state0.A) + @test space(t) == space(t0) + end +end From 6bfb34f4cf64934e52e49240c5e1674cb9f594e4 Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Fri, 17 Apr 2026 20:21:49 +0800 Subject: [PATCH 14/41] Fix time_evolve interface in tests --- test/timeevol/j1j2_finiteT.jl | 3 ++- test/timeevol/timestep.jl | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/test/timeevol/j1j2_finiteT.jl b/test/timeevol/j1j2_finiteT.jl index 1217333ec..1e3845f39 100644 --- a/test/timeevol/j1j2_finiteT.jl +++ b/test/timeevol/j1j2_finiteT.jl @@ -47,7 +47,8 @@ end pepo = deepcopy(pepo0) for (β, bme) in zip(βs, bm) t0 = β - βs[1] - pepo, info = time_evolve(pepo, ham, dt, nstep, alg; t0, check_interval) + evolver = TimeEvolver(pepo, ham, dt, nstep, alg; t0) + pepo, info = time_evolve(evolver; check_interval) # measure energy env = converge_env(InfinitePEPS(pepo), 16) energy = expectation_value(pepo, ham, pepo, env) / (Nr * Nc) diff --git a/test/timeevol/timestep.jl b/test/timeevol/timestep.jl index b9c2ea9fb..ce86a4569 100644 --- a/test/timeevol/timestep.jl +++ b/test/timeevol/timestep.jl @@ -43,7 +43,8 @@ end ψ1, info1 = timestep(evolver, ψ1) end # time_evolve - ψ2, info2 = time_evolve(ψ0, H, dt, nstep, alg) + evolver = TimeEvolver(ψ0, H, dt, nstep, alg) + ψ2, info2 = time_evolve(evolver) # for-loop syntax ## manually reset internal state of evolver evolver.state = PEPSKit.NTUState(0, 0.0, ψ0) From 26344d209b1ca30435510327d752dcb387326f9a Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Sat, 18 Apr 2026 08:08:05 +0800 Subject: [PATCH 15/41] Fix realtime Ising test --- test/timeevol/tf_ising_realtime.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/timeevol/tf_ising_realtime.jl b/test/timeevol/tf_ising_realtime.jl index 977d5f283..b34318dcf 100644 --- a/test/timeevol/tf_ising_realtime.jl +++ b/test/timeevol/tf_ising_realtime.jl @@ -53,7 +53,7 @@ ntu_alg = NeighbourUpdate(; ) # do one step of NTU to match benchmark data -peps0, = time_evolve(peps0, ham, 0.01, 6, ntu_alg) +peps0, = time_evolve(TimeEvolver(peps0, ham, 0.01, 6, ntu_alg)) @info "Space of `peps0[1, 1]` = $(space(peps0[1, 1]))." env0 = CTMRGEnv(ones, ComplexF64, peps0, ℂ^1) env0, = leading_boundary(env0, peps0, ctm_alg) From 8ed7d13a9a0651a989a65bca11010984dc48370b Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Fri, 24 Apr 2026 15:44:07 +0800 Subject: [PATCH 16/41] Fix FixedSpaceTruncation for simple update --- src/PEPSKit.jl | 2 +- src/algorithms/bp/beliefpropagation.jl | 2 +- src/algorithms/bp/gaugefix.jl | 12 +- src/algorithms/time_evolution/apply_gate.jl | 6 +- src/algorithms/time_evolution/gaugefix_su.jl | 10 +- src/algorithms/time_evolution/simpleupdate.jl | 28 +++-- .../time_evolution/simpleupdate3site.jl | 22 +--- src/algorithms/time_evolution/time_evolve.jl | 36 +++--- src/algorithms/time_evolution/trotter_gate.jl | 2 - .../truncation/truncationschemes.jl | 56 +++++----- src/environments/bp_environments.jl | 9 ++ src/environments/suweight.jl | 9 ++ src/operators/infinitepepo.jl | 8 ++ test/bp/gaugefix.jl | 34 +++++- test/runtests.jl | 2 +- test/timeevol/sitedep_truncation.jl | 103 +++++++----------- 16 files changed, 183 insertions(+), 158 deletions(-) diff --git a/src/PEPSKit.jl b/src/PEPSKit.jl index 4700303a5..44d3d42d8 100644 --- a/src/PEPSKit.jl +++ b/src/PEPSKit.jl @@ -106,9 +106,9 @@ include("algorithms/truncation/truncationschemes.jl") include("algorithms/truncation/fullenv_truncation.jl") include("algorithms/truncation/bond_truncation.jl") -include("algorithms/time_evolution/trotter_gate.jl") include("algorithms/time_evolution/apply_gate.jl") include("algorithms/time_evolution/apply_mpo.jl") +include("algorithms/time_evolution/trotter_gate.jl") include("algorithms/time_evolution/time_evolve.jl") include("algorithms/time_evolution/simpleupdate.jl") include("algorithms/time_evolution/simpleupdate3site.jl") diff --git a/src/algorithms/bp/beliefpropagation.jl b/src/algorithms/bp/beliefpropagation.jl index 0246d7e20..0aa900912 100644 --- a/src/algorithms/bp/beliefpropagation.jl +++ b/src/algorithms/bp/beliefpropagation.jl @@ -59,7 +59,7 @@ function leading_boundary(env₀::BPEnv, network::InfiniteSquareNetwork, alg::Be end function leading_boundary(env₀::BPEnv, state::InfiniteState, alg::BeliefPropagation) if alg.bipartite - @assert _state_bipartite_check(state) + _is_bipartite(state) || error("Input state is not bipartite") end return leading_boundary(env₀, InfiniteSquareNetwork(state), alg) end diff --git a/src/algorithms/bp/gaugefix.jl b/src/algorithms/bp/gaugefix.jl index 561dd2183..5c98dc265 100644 --- a/src/algorithms/bp/gaugefix.jl +++ b/src/algorithms/bp/gaugefix.jl @@ -7,16 +7,6 @@ Algorithm for gauging PEPS with belief propagation fixed point messages. # TODO: add options end -function _bpenv_bipartite_check(env::BPEnv) - for (r, c) in Iterators.product(1:2, 1:2) - r′, c′ = _next(r, 2), _next(c, 2) - if !all(env[:, r, c] .== env[:, r′, c′]) - return false - end - end - return true -end - """ gauge_fix(psi::Union{InfinitePEPS, InfinitePEPO}, alg::BPGauge, env::BPEnv) @@ -25,7 +15,7 @@ an [`InfinitePEPO`](@ref) interpreted as purified state with two physical legs) using fixed point environment `env` of belief propagation. """ function gauge_fix(psi::InfinitePEPS, alg::BPGauge, env::BPEnv) - bipartite = _state_bipartite_check(psi) && _bpenv_bipartite_check(env) + bipartite = _is_bipartite(psi) && _is_bipartite(env) psi′ = copy(psi) XXinv = map(eachcoordinate(psi, 1:2)) do I _, X, Xinv = _bp_gauge_fix!(CartesianIndex(I), psi′, env) diff --git a/src/algorithms/time_evolution/apply_gate.jl b/src/algorithms/time_evolution/apply_gate.jl index 139b68196..5ffb98fee 100644 --- a/src/algorithms/time_evolution/apply_gate.jl +++ b/src/algorithms/time_evolution/apply_gate.jl @@ -1,3 +1,5 @@ +const NNGate{T, S} = AbstractTensorMap{T, S, 2, 2} + """ Apply 1-site `gate` on the PEPS or PEPO tensor `a`. """ @@ -125,8 +127,8 @@ Apply 2-site `gate` on the reduced matrices `a`, `b` """ function _apply_gate( a::AbstractTensorMap, b::AbstractTensorMap, - gate::AbstractTensorMap{T, S, 2, 2}, trunc::TruncationStrategy - ) where {T <: Number, S <: ElementarySpace} + gate::NNGate, trunc::TruncationStrategy + ) V = space(b, 1) need_flip = isdual(V) if isdual(space(a, 2)) diff --git a/src/algorithms/time_evolution/gaugefix_su.jl b/src/algorithms/time_evolution/gaugefix_su.jl index 324a8d604..13a2782d6 100644 --- a/src/algorithms/time_evolution/gaugefix_su.jl +++ b/src/algorithms/time_evolution/gaugefix_su.jl @@ -37,22 +37,24 @@ end Fix the gauge of `psi` using trivial simple update. """ function gauge_fix(psi::InfiniteState, alg::SUGauge) + time0 = time() gates = _trivial_gates(scalartype(psi), physicalspace(psi)) - su_alg = SimpleUpdate(; trunc = FixedSpaceTruncation(), bipartite = _state_bipartite_check(psi)) + trunc = _get_fixedspacetrunc(psi) + su_alg = SimpleUpdate(; trunc, bipartite = _is_bipartite(psi)) wts0 = SUWeight(psi) # use default constructor to avoid calculation of exp(-H * 0) evolver = TimeEvolver(su_alg, 0.0, alg.maxiter, gates, SUState(0, 0.0, psi, wts0)) for (i, (psi′, wts, info)) in enumerate(evolver) ϵ = compare_weights(wts, wts0) if i >= alg.miniter && ϵ < alg.tol - @info "Trivial SU conv $i: |Δλ| = $ϵ." + @info "Trivial SU conv $i: |Δλ| = $ϵ, time = $(time() - time0) s" return psi′, wts, ϵ end if i == alg.maxiter - @warn "Trivial SU cancel $i: |Δλ| = $ϵ." + @warn "Trivial SU cancel $i: |Δλ| = $ϵ, time = $(time() - time0) s" return psi′, wts, ϵ end - wts0 = deepcopy(wts) + wts0 = wts end return end diff --git a/src/algorithms/time_evolution/simpleupdate.jl b/src/algorithms/time_evolution/simpleupdate.jl index bac4a3fc6..ecebda1c5 100644 --- a/src/algorithms/time_evolution/simpleupdate.jl +++ b/src/algorithms/time_evolution/simpleupdate.jl @@ -7,19 +7,19 @@ Algorithm struct for simple update (SU) of InfinitePEPS or InfinitePEPO. $(TYPEDFIELDS) """ -@kwdef struct SimpleUpdate <: TimeEvolution +@kwdef struct SimpleUpdate{T <: TruncationStrategy} <: TimeEvolution "Truncation strategy for bonds updated by Trotter gates" - trunc::TruncationStrategy + trunc::T "When true (or false), the Trotter gate is `exp(-H dt)` (or `exp(-iH dt)`)" imaginary_time::Bool = true "When true, force decomposition of nearest neighbor gates to MPOs." force_mpo::Bool = false "When true, assume bipartite unit cell structure" bipartite::Bool = false - "(Only applicable to InfinitePEPO) + "(Only applicable to InfinitePEPO) When true, the PEPO is regarded as a purified PEPS, and updated as `|ρ(t + dt)⟩ = exp(-H dt/2) |ρ(t)⟩`. - When false, the PEPO is updated as + When false, the PEPO is updated as `ρ(t + dt) = exp(-H dt/2) ρ(t) exp(-H dt/2)`." purified::Bool = true end @@ -62,6 +62,12 @@ function TimeEvolver( # create Trotter gates gate = trotterize(H, dt′; symmetrize_gates, force_mpo = alg.force_mpo) state = SUState(0, t0, psi0, env0) + # convert FixedSpaceTruncation to site-dependent `truncspace`s + if alg.trunc isa FixedSpaceTruncation + trunc = _get_fixedspacetrunc(psi0) + @reset alg.trunc = trunc + end + # TODO: bipartite check for alg.trunc after equality is defined for all kinds of truncation strategies # TODO: check gates for bipartite case return TimeEvolver(alg, dt, nstep, gate, state) end @@ -96,8 +102,8 @@ function _su_iter!( sites::Vector{CartesianIndex{2}}, alg::SimpleUpdate ) Nr, Nc = size(state) - trunc = only(_get_cluster_trunc(alg.trunc, sites, (Nr, Nc))) @assert length(sites) == 2 + trunc = only(_get_cluster_trunc(alg.trunc, sites, (Nr, Nc))) Ms, open_vaxs, = _get_cluster(state, sites, env; permute = false) normalize!.(Ms, Inf) # rotate @@ -159,14 +165,14 @@ function su_iter( (!alg.bipartite) && continue if d == 1 rp1, cp1 = _next(r, Nr), _next(c, Nc) - state2[rp1, cp1] = deepcopy(state2[r, c]) - state2[rp1, c] = deepcopy(state2[r, cp1]) - env2[1, rp1, cp1] = deepcopy(env2[1, r, c]) + state2[rp1, cp1] = copy(state2[r, c]) + state2[rp1, c] = copy(state2[r, cp1]) + env2[1, rp1, cp1] = copy(env2[1, r, c]) else rm1, cm1 = _prev(r, Nr), _prev(c, Nc) - state2[rm1, cm1] = deepcopy(state2[r, c]) - state2[r, cm1] = deepcopy(state2[rm1, c]) - env2[2, rm1, cm1] = deepcopy(env2[2, r, c]) + state2[rm1, cm1] = copy(state2[r, c]) + state2[r, cm1] = copy(state2[rm1, c]) + env2[2, rm1, cm1] = copy(env2[2, r, c]) end else # N-site MPO gate (N ≥ 2) diff --git a/src/algorithms/time_evolution/simpleupdate3site.jl b/src/algorithms/time_evolution/simpleupdate3site.jl index 3b16820f8..df0d767ab 100644 --- a/src/algorithms/time_evolution/simpleupdate3site.jl +++ b/src/algorithms/time_evolution/simpleupdate3site.jl @@ -224,24 +224,14 @@ updated by the Trotter evolution MPO. """ function _get_cluster_trunc( trunc::TruncationStrategy, sites::Vector{CartesianIndex{2}}, - (Nrow, Ncol)::NTuple{2, Int} + unitcell::NTuple{2, Int} ) return map(zip(sites, Iterators.drop(sites, 1))) do (site1, site2) - diff = site2 - site1 - if diff == CartesianIndex(0, 1) - r, c = mod1(site1[1], Nrow), mod1(site1[2], Ncol) - return truncation_strategy(trunc, 1, r, c) - elseif diff == CartesianIndex(0, -1) - r, c = mod1(site2[1], Nrow), mod1(site2[2], Ncol) - return truncation_strategy(trunc, 1, r, c) - elseif diff == CartesianIndex(1, 0) - r, c = mod1(site2[1], Nrow), mod1(site2[2], Ncol) - return truncation_strategy(trunc, 2, r, c) - elseif diff == CartesianIndex(-1, 0) - r, c = mod1(site1[1], Nrow), mod1(site1[2], Ncol) - return truncation_strategy(trunc, 2, r, c) - else - error("The path `sites` contains a long-range bond.") + (d, r, c), rev = _nn_bondrev(site1, site2, unitcell) + t = truncation_strategy(trunc, d, r, c) + if rev && isa(t, TruncationSpace) + t = truncspace(flip(t.space)') end + return t end end diff --git a/src/algorithms/time_evolution/time_evolve.jl b/src/algorithms/time_evolution/time_evolve.jl index ee5f236c1..dd51c2f54 100644 --- a/src/algorithms/time_evolution/time_evolve.jl +++ b/src/algorithms/time_evolution/time_evolve.jl @@ -30,22 +30,6 @@ end Base.iterate(it::TimeEvolver) = iterate(it, it.state) -function _state_bipartite_check(psi::InfiniteState) - if isa(psi, InfinitePEPO) - @assert size(psi, 3) == 1 "Input InfinitePEPO is expected to have only one layer." - end - if !(size(psi, 1) == size(psi, 2) == 2) - return false - end - for (r, c) in Iterators.product(1:2, 1:2) - r′, c′ = _next(r, 2), _next(c, 2) - if psi[r, c] != psi[r′, c′] - return false - end - end - return true -end - """ Process the Trotter time step `dt` according to the intended usage. """ @@ -79,7 +63,7 @@ function _timeevol_sanity_check( @assert ψ₀ isa InfinitePEPO "alg.purified = false is only applicable to PEPO." end if hasfield(typeof(alg), :bipartite) && alg.bipartite - @assert _state_bipartite_check(ψ₀) "Input state is not bipartite with 2 x 2 unit cell." + @assert _is_bipartite(ψ₀) "Input state is not bipartite with 2 x 2 unit cell." end return nothing end @@ -94,3 +78,21 @@ function MPSKit.infinite_temperature_density_matrix(H::LocalOperator) end return InfinitePEPO(cat(A; dims = 3)) end + +""" +Get the `SiteDependentTruncation` used by time evolution +that preserves virtual spaces of `state`. +""" +function _get_fixedspacetrunc(state::InfiniteState) + if state isa InfinitePEPO + size(state, 3) != 1 && error("Input InfinitePEPO is expect to have only one layer.") + end + Nr, Nc = size(state) + return SiteDependentTruncation( + map(Iterators.product(1:2, 1:Nr, 1:Nc)) do (d, r, c) + V = domain(state[r, c], (d == 1) ? EAST : NORTH) + isdual(V) && (V = flip(V)) + return truncspace(V) + end + ) +end diff --git a/src/algorithms/time_evolution/trotter_gate.jl b/src/algorithms/time_evolution/trotter_gate.jl index 24248ad9c..67ae916d3 100644 --- a/src/algorithms/time_evolution/trotter_gate.jl +++ b/src/algorithms/time_evolution/trotter_gate.jl @@ -1,5 +1,3 @@ -const NNGate{T, S} = AbstractTensorMap{T, S, 2, 2} - """ Convert an N-site gate (N ≥ 2) to MPO by SVD, in which the axes are ordered as diff --git a/src/algorithms/truncation/truncationschemes.jl b/src/algorithms/truncation/truncationschemes.jl index 71b5e9dbe..228f9065d 100644 --- a/src/algorithms/truncation/truncationschemes.jl +++ b/src/algorithms/truncation/truncationschemes.jl @@ -1,16 +1,41 @@ """ $(TYPEDEF) -CTMRG specific truncation strategy for `svd_trunc` 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`. +SVD truncation strategy which preserves the `CTMRGEnv` environment virtual spaces, +or `InfinitePEPS`, `InfinitePEPO` virtual spaces. """ struct FixedSpaceTruncation <: TruncationStrategy end +""" +$(TYPEDEF) + +SVD truncation strategy specified for each nearest neighbor bond +in `InfinitePEPS`, `InfinitePEPO`: +- `trunc[1, r, c]` applies to the x-bond between `[r, c]` and `[r, c+1]`. + If it is a `TruncationSpace`, the space refers to the east domain of + `[r, c]` or its `flip`, whichever is non-dual. +- `trunc[2, r, c]` applies to the y-bond between `[r, c]` and `[r-1, c]`. + If it is a `TruncationSpace`, the space refers to the north domain of + `[r, c]` or its `flip`, whichever is non-dual. +""" struct SiteDependentTruncation{T <: TruncationStrategy} <: TruncationStrategy truncs::Array{T, 3} + + function SiteDependentTruncation(truncs::Array{T, 3}) where {T} + # TODO: generalize it to CTMRGEnv + size(truncs, 1) != 2 && throw( + DimensionMismatch( + "The first dimension of `truncs` must have a size of 2. Got $(size(truncs, 1))." + ) + ) + return new{T}(truncs) + end end +Base.getindex(trunc::SiteDependentTruncation, args...) = Base.getindex(trunc.truncs, args...) + +# TODO: _is_bipartite(trunc::SiteDependentTruncation) + const TRUNCATION_STRATEGY_SYMBOLS = IdDict{Symbol, Type{<:TruncationStrategy}}( :notrunc => MatrixAlgebraKit.NoTruncation, :truncerror => MatrixAlgebraKit.TruncationByError, @@ -40,28 +65,5 @@ end function truncation_strategy( trunc::SiteDependentTruncation, direction::Int, row::Int, col::Int ) - return trunc.truncs[direction, row, col] -end - -# TODO: type piracy -Base.rotl90(trunc::TruncationStrategy) = trunc - -function Base.rotl90(trunc::SiteDependentTruncation) - directions, rows, cols = size(trunc.truncs) - truncs_rotated = similar(trunc.truncs, directions, cols, rows) - - if directions == 2 - truncs_rotated[NORTH, :, :] = circshift( - rotl90(trunc.truncs[EAST, :, :]), (0, -1) - ) - truncs_rotated[EAST, :, :] = rotl90(trunc.truncs[NORTH, :, :]) - elseif directions == 4 - for dir in 1:4 - dir′ = _prev(dir, 4) - truncs_rotated[dir′, :, :] = rotl90(trunc.truncs[dir, :, :]) - end - else - throw(ArgumentError("Unsupported number of directions for rotl90: $directions")) - end - return SiteDependentTruncation(truncs_rotated) + return trunc[direction, row, col] end diff --git a/src/environments/bp_environments.jl b/src/environments/bp_environments.jl index 765d31c0c..ea7ae9301 100644 --- a/src/environments/bp_environments.jl +++ b/src/environments/bp_environments.jl @@ -158,6 +158,15 @@ function eachcoordinate(x::BPEnv, dirs) return collect(Iterators.product(dirs, axes(x, 2), axes(x, 3))) end +## Bipartite check +function _is_bipartite(env::BPEnv) + (size(env, 2) == size(env, 3) == 2) || (return false) + for (d, c) in Iterators.product(axes(env, 1), axes(env, 3)) + (env[d, 1, c] == env[d, 2, _next(c, 2)]) || (return false) + end + return true +end + # conversion to CTMRGEnv """ CTMRGEnv(bp_env::BPEnv) diff --git a/src/environments/suweight.jl b/src/environments/suweight.jl index 2966d4155..cd24cb111 100644 --- a/src/environments/suweight.jl +++ b/src/environments/suweight.jl @@ -138,6 +138,15 @@ TensorKit.spacetype(::Type{T}) where {E, T <: SUWeight{E}} = spacetype(E) TensorKit.sectortype(w::SUWeight) = sectortype(typeof(w)) TensorKit.sectortype(::Type{<:SUWeight{T}}) where {T} = sectortype(spacetype(T)) +## Bipartite check +function _is_bipartite(wts::SUWeight) + (size(wts, 2) == size(wts, 3) == 2) || (return false) + for (d, c) in Iterators.product(1:2, 1:2) + (wts[d, 1, c] == wts[d, 2, _next(c, 2)]) || (return false) + end + return true +end + ## (Approximate) equality function Base.:(==)(wts1::SUWeight, wts2::SUWeight) return wts1.data == wts2.data diff --git a/src/operators/infinitepepo.jl b/src/operators/infinitepepo.jl index 172c56b05..0eb44444a 100644 --- a/src/operators/infinitepepo.jl +++ b/src/operators/infinitepepo.jl @@ -202,6 +202,14 @@ function InfinitePEPS(ρ::InfinitePEPO) ) end +## Bipartite check for PEPS/PEPO +function _is_bipartite(psi::InfiniteState) + (size(psi, 1) == size(psi, 2) == 2) || (return false) + for (c, h) in Iterators.product(1:2, 1:size(psi, 3)) + (psi[1, c, h] == psi[2, _next(c, 2), h]) || (return false) + end + return true +end ## Vector interface diff --git a/test/bp/gaugefix.jl b/test/bp/gaugefix.jl index 941c8cbea..350a4f205 100644 --- a/test/bp/gaugefix.jl +++ b/test/bp/gaugefix.jl @@ -3,10 +3,13 @@ using Random using TensorKit using PEPSKit using PEPSKit: compare_weights, random_dual!, twistdual +using PEPSKit: _next, _is_bipartite -@testset "Compare BP and SU ($S, posdef msgs = $h)" for (S, h) in - Iterators.product([U1Irrep, FermionParity], [true, false]) - unitcell = (2, 3) +@testset "BP vs SU ($S, bipartite = $(bipartite), posdef msgs = $h)" for + (S, bipartite, h) in Iterators.product( + [U1Irrep, FermionParity], [true, false], [true, false] + ) + unitcell = bipartite ? (2, 2) : (2, 3) elt = ComplexF64 maxiter, tol = 100, 1.0e-9 Random.seed!(52840679) @@ -32,25 +35,48 @@ using PEPSKit: compare_weights, random_dual!, twistdual end end Nspaces, Espaces = random_dual!(Nspaces), random_dual!(Espaces) + if bipartite + for c in 1:2 + cp1 = _next(c, 2) + Pspaces[2, c] = Pspaces[1, cp1] + Nspaces[2, c] = Nspaces[1, cp1] + Espaces[2, c] = Espaces[1, cp1] + end + end peps0 = InfinitePEPS(randn, elt, Pspaces, Nspaces, Espaces) + if bipartite + for c in 1:2 + peps0[2, c] = copy(peps0[1, _next(c, 2)]) + end + end # start by gauging with SU peps1, wts1 = gauge_fix(peps0, SUGauge(; maxiter, tol)) for (a0, a1) in zip(peps0.A, peps1.A) @test space(a0) == space(a1) end + if bipartite + @test _is_bipartite(peps1) + @test _is_bipartite(wts1) + end normalize!.(wts1.data) # find BP fixed point and SUWeight - bp_alg = BeliefPropagation(; maxiter, tol, project_hermitian = h) + bp_alg = BeliefPropagation(; maxiter, tol, bipartite, project_hermitian = h) env = BPEnv(randn, elt, peps1; posdef = h) env, err = leading_boundary(env, peps1, bp_alg) + if bipartite + @test _is_bipartite(env) + end wts2 = SUWeight(env) normalize!.(wts2.data) @test compare_weights(wts1, wts2) < 1.0e-9 bpg_alg = BPGauge() peps2, XXinv = @constinferred gauge_fix(peps1, bpg_alg, env) + if bipartite + @test _is_bipartite(peps2) + end for (a1, a2) in zip(peps1.A, peps2.A) @test space(a1) == space(a2) end diff --git a/test/runtests.jl b/test/runtests.jl index bdc2f7801..a50cdfe9c 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -107,7 +107,7 @@ end @time @safetestset "Cluster truncation with projectors" begin include("timeevol/cluster_projectors.jl") end - @time @safetestset "Time evolution with site-dependent truncation" begin + @time @safetestset "Fixed-space and site-dependent truncation" begin include("timeevol/sitedep_truncation.jl") end @time @safetestset "Time evolution with FixedSpaceTruncation" begin diff --git a/test/timeevol/sitedep_truncation.jl b/test/timeevol/sitedep_truncation.jl index a36f3de75..6919e71b1 100644 --- a/test/timeevol/sitedep_truncation.jl +++ b/test/timeevol/sitedep_truncation.jl @@ -1,70 +1,51 @@ using Test -using LinearAlgebra using Random using TensorKit using PEPSKit -using PEPSKit: NORTH, EAST, _next +using PEPSKit: _is_bipartite -function get_bonddims(peps::InfinitePEPS) - xdims = collect(dim(domain(t, EAST)) for t in peps.A) - ydims = collect(dim(domain(t, NORTH)) for t in peps.A) - return stack([xdims, ydims]; dims = 1) -end - -function get_bonddims(wts::SUWeight) - xdims = collect(dim(space(wt, 1)) for wt in wts[1, :, :]) - ydims = collect(dim(space(wt, 1)) for wt in wts[2, :, :]) - return stack([xdims, ydims]; dims = 1) -end +elt = Float64 +Nr, Nc = 2, 2 +Vps = fill(U1Space(1 / 2 => 1, -1 / 2 => 1), (Nr, Nc)) +Vns = [ + U1Space(0 => 1, 1 => 2, -1 => 1) U1Space(0 => 1, 1 => 2, -1 => 1)'; + U1Space(0 => 1, 1 => 2, -1 => 1)' U1Space(0 => 1, 1 => 2, -1 => 1) +] +Ves1 = [ + U1Space(1 / 2 => 1, -1 / 2 => 2, -3 / 2 => 1)' U1Space(0 => 1, 1 => 1, -1 => 2); + U1Space(0 => 1, 1 => 1, -1 => 2) U1Space(1 / 2 => 1, -1 / 2 => 2, -3 / 2 => 1)' +] +Ves2 = [ + U1Space(0 => 1, 1 => 2, -1 => 1)' U1Space(0 => 1, 1 => 1, -1 => 2); + U1Space(0 => 1, 1 => 1, -1 => 2) U1Space(0 => 1, 1 => 2, -1 => 1)' +] +Venv = U1Space(0 => 2, 1 => 1, -1 => 1) +states = ( + InfinitePEPS(randn, elt, Vps, Vns, Ves1), + InfinitePEPO(randn, elt, Vps, Vns, Ves2), +) -@testset "Simple update: bipartite 2-site" begin - Nr, Nc = 2, 2 - ham = real(heisenberg_XYZ(InfiniteSquare(Nr, Nc); Jx = 1.0, Jy = 1.0, Jz = 1.0)) - Random.seed!(100) - peps0 = InfinitePEPS(rand, Float64, ℂ^2, ℂ^10; unitcell = (Nr, Nc)) - # make state bipartite - for r in 1:2 - peps0.A[_next(r, 2), 2] = copy(peps0.A[r, 1]) +@testset "Simple update on $(typeof(state0).name.wrapper), bipartite = $(bipartite)" for + (state0, bipartite) in Iterators.product(states, (true, false)) + J2 = 0.5 + if bipartite + state0[2, 1] = copy(state0[1, 2]) + state0[2, 2] = copy(state0[1, 1]) + J2 = 0.0 end - env0 = SUWeight(peps0) - normalize!.(peps0.A, Inf) - # set trunc to be compatible with bipartite structure - bonddims = stack([[6 4; 4 6], [5 7; 7 5]]; dims = 1) - trunc = SiteDependentTruncation(collect(truncrank(d) for d in bonddims)) - alg = SimpleUpdate(; trunc, bipartite = true) - peps, env, = time_evolve(peps0, ham, 1.0e-2, 4, alg, env0) - @test get_bonddims(peps) == bonddims - @test get_bonddims(env) == bonddims - # check bipartite structure is preserved - for col in 1:2 - cp1 = PEPSKit._next(col, 2) - @test ( - peps.A[1, col] == peps.A[2, cp1] && - env[1, 1, col] == env[1, 2, cp1] && - env[2, 1, col] == env[2, 2, cp1] - ) + ham = j1_j2_model(elt, U1Irrep, InfiniteSquare(Nr, Nc); J1 = 1.0, J2, sublattice = false) + # converted internally to SiteDependentTruncation + alg = SimpleUpdate(; trunc = FixedSpaceTruncation(), bipartite) + wts0 = SUWeight(state0) + state, wts, = time_evolve(state0, ham, 0.1, 1, alg, wts0) + for (t, t0) in zip(state.A, state0.A) + @test space(t) == space(t0) + end + for (wt, wt0) in zip(wts.data, wts0.data) + @test space(wt) == space(wt0) + end + if bipartite + @test _is_bipartite(state) + @test _is_bipartite(wts) end -end - -@testset "Simple update: generic 2-site and 3-site" begin - Nr, Nc = 3, 4 - Random.seed!(100) - peps0 = InfinitePEPS(rand, Float64, ℂ^2, ℂ^10; unitcell = (Nr, Nc)) - normalize!.(peps0.A, Inf) - env0 = SUWeight(peps0) - # Site dependent truncation - bonddims = rand(2:8, 2, Nr, Nc) - @show bonddims - trunc = SiteDependentTruncation(collect(truncrank(d) for d in bonddims)) - alg = SimpleUpdate(; trunc) - # 2-site SU - ham = j1_j2_model(Float64, Trivial, InfiniteSquare(Nr, Nc); J2 = 0.0, sublattice = false) - peps, env, = time_evolve(peps0, ham, 1.0e-2, 4, alg, env0) - @test get_bonddims(peps) == bonddims - @test get_bonddims(env) == bonddims - # 3-site SU - ham = j1_j2_model(Float64, Trivial, InfiniteSquare(Nr, Nc); J2 = 0.2, sublattice = false) - peps, env, = time_evolve(peps0, ham, 1.0e-2, 4, alg, env0) - @test get_bonddims(peps) == bonddims - @test get_bonddims(env) == bonddims end From 3498fea2d4501ef21a3bf539ed7ece176e2892e3 Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Fri, 24 Apr 2026 15:44:49 +0800 Subject: [PATCH 17/41] Improve efficiency of bond truncation --- .../contractions/bondenv/als_solve.jl | 230 ++++++++++-------- src/algorithms/truncation/bond_truncation.jl | 75 +++--- .../truncation/fullenv_truncation.jl | 15 +- test/bondenv/bond_truncate.jl | 47 ++-- 4 files changed, 211 insertions(+), 156 deletions(-) diff --git a/src/algorithms/contractions/bondenv/als_solve.jl b/src/algorithms/contractions/bondenv/als_solve.jl index af083f618..745478073 100644 --- a/src/algorithms/contractions/bondenv/als_solve.jl +++ b/src/algorithms/contractions/bondenv/als_solve.jl @@ -2,139 +2,151 @@ In the following, the names `Ra`, `Sa` etc comes from the fast full update article Physical Review B 92, 035142 (2015) =# - """ -$(SIGNATURES) +Contract the virtual legs between +``` + -- DX --a-- D --b-- DY -- + ↓ ↓ + da db +``` +""" +function _combine_ket(a::MPSTensor, b::AbstractTensorMap{T, S, 1, 2}) where {T, S} + return @tensor ket[DX DY; da db] := a[DX da; D] * b[D; db DY] +end +function _combine_ket(a::MPSTensor, b::MPSTensor) + return @tensor ket[DX DY; da db] := a[DX da; D] * b[D db; DY] +end -Construct the tensor -``` - ┌-----------------------------------┐ - | ┌----┐ | - └---| |- DX0 Db0 - b -- DY0 -┘ - | | ↓ - |benv| db - | | ↓ - ┌---| |- DX1 Db1 - b† - DY1 -┐ - | └----┘ | - └-----------------------------------┘ -``` -""" -function _tensor_Ra(benv::BondEnv, b::MPSTensor) - return @autoopt @tensor Ra[DX1 Db1; DX0 Db0] := ( - benv[DX1 DY1; DX0 DY0] * b[Db0 db; DY0] * conj(b[Db1 db; DY1]) - ) +function _combine_ket_for_svd(a::MPSTensor, b::MPSTensor) + return @tensor ket[DX da; db DY] := a[DX da; D] * b[D db; DY] end """ -$(SIGNATURES) - -Construct the tensor +Construct the norm with bra bond tensors removed ``` - ┌-----------------------------------┐ - | ┌----┐ | - └---| |- DX0 -- (a2 b2) -- DY0 --┘ - | | ↓ ↓ - |benv| da db - | | ↓ - ┌---| |- DX1 Db1 -- b† - DY1 --┐ - | └----┘ | - └-----------------------------------┘ + ┌benv-------┐ + ├---a---b---┤ + | ↓ ↓ | + ├-- --┤ + └-----------┘ ``` """ -function _tensor_Sa( - benv::BondEnv, b::MPSTensor, a2b2::AbstractTensorMap{T, S, 2, 2} - ) where {T <: Number, S <: ElementarySpace} - return @autoopt @tensor Sa[DX1 da; Db1] := ( - benv[DX1 DY1; DX0 DY0] * conj(b[Db1 db; DY1]) * a2b2[DX0 DY0; da db] - ) +function _benv_ket(benv::BondEnv, ket::AbstractTensorMap{T, S, 2, 2}) where {T, S} + return benv * twistdual(ket, 1:2) end """ -$(SIGNATURES) + _als_tensor_R(benv::BondEnv, xs::Vector{<:MPSTensor}, i::Int) -Construct the tensor -``` - ┌-----------------------------------┐ - | ┌----┐ | - └---| |- DX0 - a -- Da0 DY0 -┘ - | | ↓ - |benv| da - | | ↓ - ┌---| |- DX1 - a† - Da1 DY1 -┐ - | └----┘ | - └-----------------------------------┘ -``` -""" -function _tensor_Rb(benv::BondEnv, a::MPSTensor) - return @autoopt @tensor Rb[Da1 DY1; Da0 DY0] := ( - benv[DX1 DY1; DX0 DY0] * a[DX0 da; Da0] * conj(a[DX1 da; Da1]) - ) +Construct the bond environment around the `i`th bond tensor +in two-site ALS optimization. +``` + i = 1 i = 2 + ┌benv-------┐ ┌benv-------┐ + ├-- --b---┤ ├---a-- --┤ + | ↓ | | ↓ | + ├-- --b̄---┤ ├---ā-- --┤ + └-----------┘ └-----------┘ +``` +""" +_als_tensor_R(benv, xs, i::Int) = _als_tensor_R(benv, xs, Val(i)) +function _als_tensor_R(benv::BondEnv, xs::Vector{<:MPSTensor}, ::Val{1}) + return @tensor Ra[DX1 D1; DX0 D0] := + benv[DX1 DY1; DX0 DY0] * xs[2][D0 db; DY0] * conj(xs[2][D1 db; DY1]) +end +function _als_tensor_R(benv::BondEnv, xs::Vector{<:MPSTensor}, ::Val{2}) + return @tensor Rb[D1 DY1; D0 DY0] := + benv[DX1 DY1; DX0 DY0] * xs[1][DX0 da; D0] * conj(xs[1][DX1 da; D1]) end """ -$(SIGNATURES) - -Construct the tensor +Calculate the 2-site norm ``` - ┌-----------------------------------┐ - | ┌----┐ | - └---| |- DX0 -- (a2 b2) -- DY0 --┘ - | | ↓ ↓ - |benv| da db - | | ↓ - ┌---| |- DX1 -- a† - Da1 DY1 --┐ - | └----┘ | - └-----------------------------------┘ + ┌benv-------┐ + ├---a---b---┤ + | ↓ ↓ | + ├---ā---b̄---┤ + └-----------┘ ``` +using pre-calcuated partial contraction results. """ -function _tensor_Sb( - benv::BondEnv, a::MPSTensor, a2b2::AbstractTensorMap{T, S, 2, 2} - ) where {T <: Number, S <: ElementarySpace} - return @autoopt @tensor Sb[Da1 db; DY1] := ( - benv[DX1 DY1; DX0 DY0] * conj(a[DX1 da; Da1]) * a2b2[DX0 DY0; da db] - ) +function _als_norm( + ket::AbstractTensorMap{T, S, 2, 2}, benv_ket::AbstractTensorMap{T, S, 2, 2} + ) where {T, S} + return @tensor benv_ket[DX1 DY1; da db] * conj(ket[DX1 DY1; da db]) +end +function _als_norm(a::MPSTensor, Ra::BondEnv) + return @tensor Ra[DX1 D1; DX0 D0] * a[DX0 da; D0] * conj(a[DX1 da; D1]) end """ -$(SIGNATURES) + _als_tensor_S( + benv_ket::AbstractTensorMap{T, S, 2, 2}, + xs::Vector{<:MPSTensor}, i::Int + ) where {T <: Number, S <: ElementarySpace} -Calculate the inner product +Construct the overlap but with one of the bra bond tensor removed. ``` - ┌--------------------------------┐ - | ┌----┐ | - └---| |- DX0 - (a2 b2) - DY0 -┘ - | | ↓ ↓ - |benv| da db - | | ↓ ↓ - ┌---| |- DX1 - (a1 b1)†- DY1 -┐ - | └----┘ | - └--------------------------------┘ + i = 1 i = 2 + ┌benv-------┐ ┌benv-------┐ + ├---a₂==b₂--┤ ├---a₂==b₂--┤ + | ↓ ↓ | | ↓ ↓ | + ├-- --b̄---┤ ├---ā-- --┤ + └-----------┘ └-----------┘ ``` +The ket part is provided by the partial contraction `benv_ket`. """ -function inner_prod( - benv::BondEnv, a1b1::AbstractTensorMap{T, S, 2, 2}, a2b2::AbstractTensorMap{T, S, 2, 2} +_als_tensor_S(benv_ket, xs, i::Int) = _als_tensor_S(benv_ket, xs, Val(i)) +function _als_tensor_S( + benv_ket::AbstractTensorMap{T, S, 2, 2}, + xs::Vector{<:MPSTensor}, ::Val{1} ) where {T <: Number, S <: ElementarySpace} - return @autoopt @tensor benv[DX1 DY1; DX0 DY0] * - conj(a1b1[DX1 DY1; da db]) * a2b2[DX0 DY0; da db] + return @tensor Sa[DX1 da; D1] := + benv_ket[DX1 DY1; da db] * conj(xs[2][D1 db; DY1]) +end +function _als_tensor_S( + benv_ket::AbstractTensorMap{T, S, 2, 2}, + xs::Vector{<:MPSTensor}, ::Val{2} + ) where {T <: Number, S <: ElementarySpace} + return @tensor contractcheck = true Sb[D1 db; DY1] := + benv_ket[DX1 DY1; da db] * conj(xs[1][DX1 da; D1]) end """ -$(SIGNATURES) +Calculate the inner product (overlap) +``` + ┌benv-------┐ + ├---a₂--b₂--┤ + | ↓ ↓ | + ├---ā---b̄---┤ + └-----------┘ +``` +using pre-calculated partial contraction results. +""" +function _als_overlap(a::MPSTensor, Sa::MPSTensor) + # applies to b, Sb as well + # @tensor Sb[D1 db; DY1] * conj(b[D1 db; DY1]) + return @tensor Sa[DX1 da; D1] * conj(a[DX1 da; D1]) +end -Contract the axis between `a` and `b` tensors +""" +Calculate the 2-site ALS inner product ⟨a₁,b₁|a₂,b₂⟩ ``` - -- DX - a - D - b - DY -- - ↓ ↓ - da db + ┌benv-------┐ + ├---a₂--b₂--┤ + | ↓ ↓ | + ├---ā₁--b̄₁--┤ + └-----------┘ ``` +where `|bra⟩ = |a₁,b₁⟩` and `|ket⟩ = |a₂,b₂⟩`, +with virtual leg between a, b contracted. """ -function _combine_ab( - a::MPSTensor, b::AbstractTensorMap{T, S, 1, 2} +function inner_prod( + benv::BondEnv, bra::AbstractTensorMap{T, S, 2, 2}, + ket::AbstractTensorMap{T, S, 2, 2} ) where {T <: Number, S <: ElementarySpace} - return @tensor ab[DX DY; da db] := a[DX da; D] * b[D; db DY] -end -function _combine_ab(a::MPSTensor, b::MPSTensor) - return @tensor ab[DX DY; da db] := a[DX da; D] * b[D db; DY] + return @autoopt @tensor benv[DX1 DY1; DX0 DY0] * + conj(bra[DX1 DY1; da db]) * ket[DX0 DY0; da db] end """ @@ -161,10 +173,30 @@ function cost_function_als(benv, ψ1, ψ2) return cost, fid end +# applies to Rb, Sb, b as well +# b22 is the pre-calculated untruncated norm +function cost_function_als(Ra::BondEnv, Sa::MPSTensor, a::MPSTensor, b22::Real) + b11 = real(_als_norm(a, Ra)) + b12 = _als_overlap(a, Sa) + cost = b11 + b22 - 2 * real(b12) + fid = abs2(b12) / abs(b11 * b22) + return cost, fid +end + """ $(SIGNATURES) Solve the equations `Rx x = Sx` with initial guess `x0`. + +In ALS over `a`, `b`, if we fix `b`, the cost function can +be expressed in the `Ra`, `Sa` tensors as +``` + f(a†,a) = a† Ra a - a† Sa - Sa† a + const +``` +Therefore `f` is minimized when +``` + ∂f/∂ā = Ra a - Sa = 0 +``` """ function _solve_als( Rx::AbstractTensorMap{T, S, N, N}, diff --git a/src/algorithms/truncation/bond_truncation.jl b/src/algorithms/truncation/bond_truncation.jl index 57fd68cc7..40d083e5e 100644 --- a/src/algorithms/truncation/bond_truncation.jl +++ b/src/algorithms/truncation/bond_truncation.jl @@ -18,8 +18,8 @@ The truncation algorithm can be constructed from the following keyword arguments * `tol::Float64=1e-9` : ALS converges when the relative change in bond SVD spectrum between two iterations is smaller than `tol`. * `check_interval::Int=0` : Set number of iterations to print information. Output is suppressed when `check_interval <= 0`. """ -@kwdef struct ALSTruncation - trunc::TruncationStrategy +@kwdef struct ALSTruncation{T <: TruncationStrategy} + trunc::T maxiter::Int = 50 tol::Float64 = 1.0e-9 check_interval::Int = 0 @@ -34,6 +34,20 @@ function _als_message( ) * @sprintf(" cost = %.3e, Δcost/cost0 = %.3e, |Δs| = %.4e.", cost, Δcost, Δs) end +""" +Initialize truncated bond tensors for 2-site ALS +""" +function _als_init_truncate( + ket2::AbstractTensorMap{T, S, 2, 2}, trunc::TruncationStrategy + ) where {T, S} + a, s0, b = svd_trunc!(permute(ket2, ((1, 3), (4, 2)); copy = true); trunc) + a, b = absorb_s(a, s0, b) + # put b in MPS axis order + b = permute(b, ((1, 2), (3,))) + xs = [a, b] + return xs, s0 +end + """ bond_truncate(a::AbstractTensorMap{T,S,2,1}, b::AbstractTensorMap{T,S,1,2}, benv::BondEnv{T,S}, alg) -> U, S, V, info @@ -69,40 +83,39 @@ function bond_truncate( need_flip = isdual(space(b, 1)) time00 = time() verbose = (alg.check_interval > 0) - a2b2 = _combine_ab(a, b) - # initialize truncated a, b - perm_ab = ((1, 3), (4, 2)) - a, s0, b = svd_trunc(permute(a2b2, perm_ab); trunc = alg.trunc) - a, b = absorb_s(a, s0, b) - # put b in MPS axis order - b = permute(b, ((1, 2), (3,))) - ab = _combine_ab(a, b) + + # untruncated things + ket2 = _combine_ket(a, b) + benv_ket2 = _benv_ket(benv, ket2) + b22 = _als_norm(ket2, benv_ket2) + + # initialize truncated bond tensors and bond weight + xs, s0 = _als_init_truncate(ket2, alg.trunc) + + # initialize ALS cache + Rs = [_als_tensor_R(benv, xs, i) for i in 1:2] + Ss = [_als_tensor_S(benv_ket2, xs, i) for i in 1:2] + # cost function will be normalized by initial value - cost00, fid = cost_function_als(benv, ab, a2b2) + cost00, fid = cost_function_als(Rs[1], Ss[1], xs[1], b22) cost0, fid0, Δcost, Δfid, Δs = cost00, fid, NaN, NaN, NaN verbose && @info "ALS init" * _als_message(0, cost0, fid, Δcost, Δfid, Δs, 0.0) + for iter in 1:(alg.maxiter) time0 = time() - #= - Fixing `b`, the cost function can be expressed in the R, S tensors as - ``` - f(a†,a) = a† Ra a - a† Sa - Sa† a + const - ``` - `f` is minimized when - ∂f/∂ā = Ra a - Sa = 0 - =# - Ra = _tensor_Ra(benv, b) - Sa = _tensor_Sa(benv, b, a2b2) - a, info_a = _solve_als(Ra, Sa, a) - # Fixing `a`, solve for `b` from `Rb b = Sb` - Rb = _tensor_Rb(benv, a) - Sb = _tensor_Sb(benv, a, a2b2) - b, info_b = _solve_als(Rb, Sb, b) - @debug "Bond truncation info" info_a info_b - ab = _combine_ab(a, b) - cost, fid = cost_function_als(benv, ab, a2b2) + for (i, (Rx, Sx, x)) in enumerate(zip(Rs, Ss, xs)) + # TODO: option to use pinv + xs[i], info_x = _solve_als(Rx, Sx, x) + @debug "Bond truncation info $(i):" info_x + # update R, S for the next site + i_next = _next(i, 2) + Rs[i_next] = _als_tensor_R(benv, xs, i_next) + Ss[i_next] = _als_tensor_S(benv_ket2, xs, i_next) + end + # cost function and local fidelity + cost, fid = cost_function_als(Rs[1], Ss[1], xs[1], b22) # TODO: replace with truncated svdvals (without calculating u, vh) - _, s, _ = svd_trunc!(permute(ab, perm_ab); trunc = alg.trunc) + _, s, _ = svd_trunc!(_combine_ket_for_svd(xs...); trunc = alg.trunc) # fidelity, cost and normalized bond-s change s_nrm = norm(s0, Inf) Δs = _singular_value_distance(s, s0) / s_nrm @@ -129,7 +142,7 @@ function bond_truncate( end converge && break end - a, s, b = svd_trunc!(permute(_combine_ab(a, b), perm_ab); trunc = alg.trunc) + a, s, b = svd_trunc!(_combine_ket_for_svd(xs...); trunc = alg.trunc) a, b = absorb_s(a, s, b) if need_flip a, s, b = flip(a, numind(a)), _fliptwist_s(s), flip(b, 1) diff --git a/src/algorithms/truncation/fullenv_truncation.jl b/src/algorithms/truncation/fullenv_truncation.jl index fd9c685bd..4ff6bac2b 100644 --- a/src/algorithms/truncation/fullenv_truncation.jl +++ b/src/algorithms/truncation/fullenv_truncation.jl @@ -23,8 +23,8 @@ The truncation algorithm can be constructed from the following keyword arguments * [Glen Evenbly, Phys. Rev. B 98, 085155 (2018)](@cite evenbly_gauge_2018). """ -@kwdef struct FullEnvTruncation - trunc::TruncationStrategy +@kwdef struct FullEnvTruncation{T <: TruncationStrategy} + trunc::T maxiter::Int = 50 tol::Float64 = 1.0e-9 trunc_init::Bool = true @@ -75,13 +75,13 @@ function _fet_message( end """ - fullenv_truncate(benv::BondEnv{T,S}, b0::AbstractTensorMap{T,S,1,1}, alg::FullEnvTruncation) -> U, S, V, info + fullenv_truncate(b0, benv::BondEnv, alg::FullEnvTruncation) -> U, S, V, info Perform full environment truncation algorithm from [Phys. Rev. B 98, 085155 (2018)](@cite evenbly_gauge_2018) on `benv`. -Given a fixed state `|b0⟩` with bond matrix `b0` -and the corresponding positive-definite bond environment `benv`, +Given a fixed state `|b0⟩` with bond matrix `b0` and the +corresponding positive-definite bond environment `benv`, find the state `|b⟩` with truncated bond matrix `b = u s v†` that maximizes the fidelity (not normalized by `⟨b0|b0⟩`) ``` @@ -215,11 +215,12 @@ function fullenv_truncate( b1 = similar(b0) s0 = deepcopy(s) Δfid, Δs, fid, fid0 = NaN, NaN, 0.0, 0.0 + @tensor benv_b0[-1 -2] := benv[-1 -2; 3 4] * b0[3; 4] for iter in 1:(alg.maxiter) time0 = time() # update `← r - = ← s ← v† -` @tensor r[-1 -2] := s[-1; 1] * vh[1; -2] - @tensor p[-1 -2] := conj(u[1; -1]) * benv[1 -2; 3 4] * b0[3; 4] + @tensor p[-1 -2] := conj(u[1; -1]) * benv_b0[1 -2] @tensor B[-1 -2; -3 -4] := conj(u[1; -1]) * benv[1 -2; 3 -4] * u[3; -3] _linearmap_twist!(p) _linearmap_twist!(B) @@ -228,7 +229,7 @@ function fullenv_truncate( u, s, vh = svd_trunc(b1; trunc = alg.trunc) # update `- l ← = - u ← s ←` @tensor l[-1 -2] := u[-1; 1] * s[1; -2] - @tensor p[-1 -2] := conj(vh[-2; 2]) * benv[-1 2; 3 4] * b0[3; 4] + @tensor p[-1 -2] := conj(vh[-2; 2]) * benv_b0[-1 2] @tensor B[-1 -2; -3 -4] := conj(vh[-2; 2]) * benv[-1 2; -3 4] * vh[-4; 4] _linearmap_twist!(p) _linearmap_twist!(B) diff --git a/test/bondenv/bond_truncate.jl b/test/bondenv/bond_truncate.jl index 25f56c200..684cd33c9 100644 --- a/test/bondenv/bond_truncate.jl +++ b/test/bondenv/bond_truncate.jl @@ -5,40 +5,49 @@ using TensorKit using PEPSKit using LinearAlgebra using KrylovKit -using PEPSKit: cost_function_als +using PEPSKit: bond_truncate, cost_function_als +using PEPSKit: _combine_ket, _combine_ket_for_svd Random.seed!(0) maxiter = 600 -check_interval = 20 -trunc = truncerror(; atol = 1.0e-10) & truncrank(8) -Vext = Vect[FermionParity](0 => 100, 1 => 100) -Vint = Vect[FermionParity](0 => 6, 1 => 6) -Vphy = Vect[FermionParity](0 => 1, 1 => 2) -perm_ab = ((1, 3), (4, 2)) -for Vbondl in (Vint, Vint'), Vbondr in (Vint, Vint') - Vbond = Vbondl ⊗ Vbondr +check_interval = 30 +elt = Float64 +# simulating the situation of applying a 2-site gate +# to a bond with virtual dimension D, physical dimension d. +d, D = 2, 4 +trunc = truncerror(; atol = 1.0e-10) & truncrank(D) +Vphy = Vect[FermionParity](0 => div(d, 2), 1 => div(d, 2)) +Vqro = Vect[FermionParity](0 => div(d * D, 2), 1 => div(d * D, 2)) +# virtual dimension of gate MPO is d^2 +Vint = Vect[FermionParity](0 => div(d^2 * D, 2), 1 => div(d^2 * D, 2)) +for Vl in (Vqro, Vqro'), Vr in (Vqro, Vqro') # random positive-definite environment - Z = randn(Float64, Vext ← Vbond) + Vbond = Vl ⊗ Vr + Dext = dim(Vbond) + Vext = Vect[FermionParity](0 => div(Dext, 2) + 1, 1 => div(Dext, 2) + 1) + Z = randn(elt, Vext ← Vbond) + normalize!(Z, Inf) benv = Z' * Z - normalize!(benv, Inf) - # untruncated bond tensor - a2b2 = randn(Float64, Vbondl ⊗ Vbondr ← Vphy' ⊗ Vphy') - a2, s, b2 = svd_compact(permute(a2b2, perm_ab)) - a2, b2 = PEPSKit.absorb_s(a2, s, b2) + @info "Dimension of benv = $(Dext)" + # untruncated bond tensors + a2 = randn(elt, Vl ⊗ Vphy ← Vint) + b2 = randn(elt, Vint ← Vphy' ⊗ Vr') # bond tensor (truncated SVD initialization) - a0, s, b0 = svd_trunc(permute(a2b2, perm_ab); trunc = trunc) + a2b2 = _combine_ket(a2, b2) + a0, s, b0 = svd_trunc(permute(a2b2, ((1, 3), (4, 2))); trunc = trunc) a0, b0 = PEPSKit.absorb_s(a0, s, b0) - fid0 = cost_function_als(benv, PEPSKit._combine_ab(a0, b0), a2b2)[2] + fid0 = cost_function_als(benv, _combine_ket(a0, b0), a2b2)[2] @info "Fidelity of simple SVD truncation = $fid0.\n" ss = Dict{String, DiagonalTensorMap}() + # FET is slower when d is large for (label, alg) in ( ("ALS", ALSTruncation(; trunc, maxiter, check_interval)), ("FET", FullEnvTruncation(; trunc, maxiter, check_interval, trunc_init = false)), ) - a1, ss[label], b1, info = PEPSKit.bond_truncate(a2, b2, benv, alg) + a1, ss[label], b1, info = bond_truncate(a2, b2, benv, alg) @info "$label improved fidelity = $(info.fid)." # display(ss[label]) - @test info.fid ≈ cost_function_als(benv, PEPSKit._combine_ab(a1, b1), a2b2)[2] + @test info.fid ≈ cost_function_als(benv, _combine_ket(a1, b1), a2b2)[2] @test info.fid > fid0 end @test isapprox(ss["ALS"], ss["FET"], atol = 1.0e-3) From ea0a968ba9237a60981b7fb8fe6a4fd83fcb707e Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Fri, 24 Apr 2026 17:48:58 +0800 Subject: [PATCH 18/41] Make j1j2 finiteT test work for all allowed symmetries --- test/timeevol/j1j2_finiteT.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/timeevol/j1j2_finiteT.jl b/test/timeevol/j1j2_finiteT.jl index 1e3845f39..b834b3522 100644 --- a/test/timeevol/j1j2_finiteT.jl +++ b/test/timeevol/j1j2_finiteT.jl @@ -10,7 +10,7 @@ const bm = [-0.08624893, -0.15688984, -0.21300888] function converge_env(state, χ::Int) trunc1 = truncrank(χ) & truncerror(; atol = 1.0e-12) - env0 = CTMRGEnv(ones, Float64, state, Vect[SU2Irrep](0 => 1)) + env0 = CTMRGEnv(ones, Float64, state, oneunit(spacetype(state))) env, = leading_boundary(env0, state; alg = :sequential, trunc = trunc1, tol = 1.0e-10) return env end From 3cc776929dd3af694d1b00634e8fdd01e5b29488 Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Fri, 24 Apr 2026 21:21:25 +0800 Subject: [PATCH 19/41] Reorganize code to get bond tensors --- src/PEPSKit.jl | 1 + src/algorithms/time_evolution/apply_gate.jl | 93 +-------------------- src/algorithms/truncation/bond_tensor.jl | 90 ++++++++++++++++++++ 3 files changed, 92 insertions(+), 92 deletions(-) create mode 100644 src/algorithms/truncation/bond_tensor.jl diff --git a/src/PEPSKit.jl b/src/PEPSKit.jl index 44d3d42d8..cf2e89377 100644 --- a/src/PEPSKit.jl +++ b/src/PEPSKit.jl @@ -104,6 +104,7 @@ include("algorithms/ctmrg/c4v.jl") include("algorithms/truncation/truncationschemes.jl") include("algorithms/truncation/fullenv_truncation.jl") +include("algorithms/truncation/bond_tensor.jl") include("algorithms/truncation/bond_truncation.jl") include("algorithms/time_evolution/apply_gate.jl") diff --git a/src/algorithms/time_evolution/apply_gate.jl b/src/algorithms/time_evolution/apply_gate.jl index 5ffb98fee..7af55701a 100644 --- a/src/algorithms/time_evolution/apply_gate.jl +++ b/src/algorithms/time_evolution/apply_gate.jl @@ -23,98 +23,7 @@ end """ $(SIGNATURES) -Use QR decomposition on two tensors `A`, `B` connected by a bond to get the reduced tensors. -When `A`, `B` are PEPSTensors, -``` - 2 1 1 - | | | - 5 -A/B- 3 ====> 4 - X ← 2 1 ← a - 3 1 - b → 3 4 → Y - 2 - | ↘ | ↘ ↘ | - 4 1 3 2 2 3 -``` -When `A`, `B` are PEPOTensors, -- If `gate_ax = 1` -``` - 2 3 1 2 1 2 - ↘ | ↘ | ↘ | - 6 -A/B- 4 ====> 5 - X ← 3 1 ← a - 3 1 - b → 3 5 → Y - 3 - | ↘ | ↘ ↘ | - 5 1 4 2 2 4 -``` -- If `gate_ax = 2` -``` - 2 3 2 2 2 2 - ↘ | | ↘ ↘ | - 6 -A/B- 4 ====> 5 - X ← 3 1 ← a - 3 1 - b → 3 5 → Y - 3 - | ↘ | ↘ | ↘ - 5 1 4 1 4 1 -``` -""" -function _qr_bond(A::PT, B::PT; gate_ax::Int = 1, kwargs...) where {PT <: Union{PEPSTensor, PEPOTensor}} - @assert 1 <= gate_ax <= numout(A) - permA, permB, permX, permY = if A isa PEPSTensor - ((2, 4, 5), (1, 3)), ((2, 3, 4), (1, 5)), (1, 4, 2, 3), Tuple(1:4) - else - if gate_ax == 1 - ((2, 3, 5, 6), (1, 4)), ((2, 3, 4, 5), (1, 6)), (1, 2, 5, 3, 4), Tuple(1:5) - else - ((1, 3, 5, 6), (2, 4)), ((1, 3, 4, 5), (2, 6)), (1, 2, 5, 3, 4), Tuple(1:5) - end - end - X, a = left_orth!(permute(A, permA; copy = true); kwargs...) - Y, b = left_orth!(permute(B, permB; copy = true); kwargs...) - X, Y = permute(X, permX), permute(Y, permY) - b = permute(b, ((3, 2), (1,))) - return X, a, b, Y -end - -""" -$(SIGNATURES) - -Reconstruct the tensors connected by a bond from their `_qr_bond` results. -For PEPSTensors, -``` - -2 -2 - | | - -5- X - 1 - a - -3 -5 - b - 1 - Y - -3 - | ↘ ↘ | - -4 -1 -1 -4 -``` -For PEPOTensors -``` - -2 -3 -2 -3 - ↘ | ↘ | - -6- X - 1 - a - -4 -6 - b - 1 - Y - -4 - | ↘ ↘ | - -5 -1 -1 -5 - - -3 -2 -2 -3 - | ↘ ↘ | - -6- X - 1 - a - -4 -6 - b - 1 - Y - -4 - | ↘ | ↘ - -5 -1 -5 -1 -``` -""" -function _qr_bond_undo(X::PEPSOrth, a::AbstractTensorMap, b::AbstractTensorMap, Y::PEPSOrth) - @tensor A[-1; -2 -3 -4 -5] := X[-2 1 -4 -5] * a[1 -1 -3] - @tensor B[-1; -2 -3 -4 -5] := b[-5 -1 1] * Y[-2 -3 -4 1] - return A, B -end -function _qr_bond_undo(X::PEPOOrth, a::AbstractTensorMap, b::AbstractTensorMap, Y::PEPOOrth) - if !isdual(space(a, 2)) - @tensor A[-1 -2; -3 -4 -5 -6] := X[-2 -3 1 -5 -6] * a[1 -1 -4] - @tensor B[-1 -2; -3 -4 -5 -6] := b[-6 -1 1] * Y[-2 -3 -4 -5 1] - else - @tensor A[-1 -2; -3 -4 -5 -6] := X[-1 -3 1 -5 -6] * a[1 -2 -4] - @tensor B[-1 -2; -3 -4 -5 -6] := b[-6 -2 1] * Y[-1 -3 -4 -5 1] - end - return A, B -end - -""" -$(SIGNATURES) - -Apply 2-site `gate` on the reduced matrices `a`, `b` +Apply 2-site `gate` on the reduced bond tensors `a`, `b` ``` -1← a --- 3 --- b ← -4 -2 -3 ↓ ↓ ↓ ↓ diff --git a/src/algorithms/truncation/bond_tensor.jl b/src/algorithms/truncation/bond_tensor.jl new file mode 100644 index 000000000..89bfc86ee --- /dev/null +++ b/src/algorithms/truncation/bond_tensor.jl @@ -0,0 +1,90 @@ +""" +$(SIGNATURES) + +Use QR decomposition on two tensors `A`, `B` connected by a bond to get the reduced tensors. +When `A`, `B` are PEPSTensors, +``` + 2 1 1 + | | | + 5 -A/B- 3 ====> 4 - X ← 2 1 ← a - 3 1 - b → 3 4 → Y - 2 + | ↘ | ↘ ↘ | + 4 1 3 2 2 3 +``` +When `A`, `B` are PEPOTensors, +- If `gate_ax = 1` +``` + 2 3 1 2 1 2 + ↘ | ↘ | ↘ | + 6 -A/B- 4 ====> 5 - X ← 3 1 ← a - 3 1 - b → 3 5 → Y - 3 + | ↘ | ↘ ↘ | + 5 1 4 2 2 4 +``` +- If `gate_ax = 2` +``` + 2 3 2 2 2 2 + ↘ | | ↘ ↘ | + 6 -A/B- 4 ====> 5 - X ← 3 1 ← a - 3 1 - b → 3 5 → Y - 3 + | ↘ | ↘ | ↘ + 5 1 4 1 4 1 +``` +""" +function _qr_bond(A::PT, B::PT; gate_ax::Int = 1, kwargs...) where {PT <: Union{PEPSTensor, PEPOTensor}} + @assert 1 <= gate_ax <= numout(A) + permA, permB, permX, permY = if A isa PEPSTensor + ((2, 4, 5), (1, 3)), ((2, 3, 4), (1, 5)), (1, 4, 2, 3), Tuple(1:4) + else + if gate_ax == 1 + ((2, 3, 5, 6), (1, 4)), ((2, 3, 4, 5), (1, 6)), (1, 2, 5, 3, 4), Tuple(1:5) + else + ((1, 3, 5, 6), (2, 4)), ((1, 3, 4, 5), (2, 6)), (1, 2, 5, 3, 4), Tuple(1:5) + end + end + X, a = left_orth!(permute(A, permA; copy = true); kwargs...) + Y, b = left_orth!(permute(B, permB; copy = true); kwargs...) + X, Y = permute(X, permX), permute(Y, permY) + b = permute(b, ((3, 2), (1,))) + return X, a, b, Y +end + +""" +$(SIGNATURES) + +Reconstruct the tensors connected by a bond from their `_qr_bond` results. +For PEPSTensors, +``` + -2 -2 + | | + -5- X - 1 - a - -3 -5 - b - 1 - Y - -3 + | ↘ ↘ | + -4 -1 -1 -4 +``` +For PEPOTensors +``` + -2 -3 -2 -3 + ↘ | ↘ | + -6- X - 1 - a - -4 -6 - b - 1 - Y - -4 + | ↘ ↘ | + -5 -1 -1 -5 + + -3 -2 -2 -3 + | ↘ ↘ | + -6- X - 1 - a - -4 -6 - b - 1 - Y - -4 + | ↘ | ↘ + -5 -1 -5 -1 +``` +""" +function _qr_bond_undo(X::PEPSOrth, a::AbstractTensorMap, b::AbstractTensorMap, Y::PEPSOrth) + @tensor A[-1; -2 -3 -4 -5] := X[-2 1 -4 -5] * a[1 -1 -3] + @tensor B[-1; -2 -3 -4 -5] := b[-5 -1 1] * Y[-2 -3 -4 1] + return A, B +end +function _qr_bond_undo(X::PEPOOrth, a::AbstractTensorMap, b::AbstractTensorMap, Y::PEPOOrth) + if !isdual(space(a, 2)) + @tensor A[-1 -2; -3 -4 -5 -6] := X[-2 -3 1 -5 -6] * a[1 -1 -4] + @tensor B[-1 -2; -3 -4 -5 -6] := b[-6 -1 1] * Y[-2 -3 -4 -5 1] + else + @tensor A[-1 -2; -3 -4 -5 -6] := X[-1 -3 1 -5 -6] * a[1 -2 -4] + @tensor B[-1 -2; -3 -4 -5 -6] := b[-6 -2 1] * Y[-1 -3 -4 -5 1] + end + return A, B +end From 5a901940cbc8cbcbb6b61654472a8b422f5a2f23 Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Fri, 24 Apr 2026 21:45:47 +0800 Subject: [PATCH 20/41] Improve NTU struct type stability --- src/algorithms/time_evolution/ntupdate.jl | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/algorithms/time_evolution/ntupdate.jl b/src/algorithms/time_evolution/ntupdate.jl index ace6efee3..b64e0ca98 100644 --- a/src/algorithms/time_evolution/ntupdate.jl +++ b/src/algorithms/time_evolution/ntupdate.jl @@ -11,14 +11,16 @@ Reference: - Physical Review B 104, 094411 (2021) - Physical Review B 106, 195105 (2022) """ -@kwdef struct NeighbourUpdate <: TimeEvolution +@kwdef struct NeighbourUpdate{ + TR <: Union{ALSTruncation, FullEnvTruncation}, + BE <: NeighbourEnv, + } <: TimeEvolution "Bond truncation algorithm after applying time evolution gate" - opt_alg::Union{ALSTruncation, FullEnvTruncation} = - ALSTruncation(; trunc = truncerror(; atol = 1.0e-10)) + opt_alg::TR = ALSTruncation(; trunc = truncerror(; atol = 1.0e-10)) "When true (or false), the Trotter gate is `exp(-H dt)` (or `exp(-iH dt)`)" imaginary_time::Bool = true "Algorithm to construct NTU bond environment." - bondenv_alg::NeighbourEnv = NNEnv() + bondenv_alg::BE = NNEnv() "When true, fix gauge of bond environment" fixgauge::Bool = true "When true, assume bipartite unit cell structure" From 3d1c3f95bef3033c5de96a5e18b42f99e50a2f4c Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Sat, 25 Apr 2026 11:27:19 +0800 Subject: [PATCH 21/41] Fix FixedSpaceTruncation again --- src/algorithms/time_evolution/ntupdate.jl | 10 ++++- .../time_evolution/ntupdate3site.jl | 21 ++-------- src/algorithms/time_evolution/simpleupdate.jl | 4 -- .../time_evolution/simpleupdate3site.jl | 9 +--- test/runtests.jl | 3 -- test/timeevol/fixedspacetruncation.jl | 42 ------------------- test/timeevol/sitedep_truncation.jl | 22 ++++++++++ 7 files changed, 35 insertions(+), 76 deletions(-) delete mode 100644 test/timeevol/fixedspacetruncation.jl diff --git a/src/algorithms/time_evolution/ntupdate.jl b/src/algorithms/time_evolution/ntupdate.jl index b64e0ca98..3ac1507b5 100644 --- a/src/algorithms/time_evolution/ntupdate.jl +++ b/src/algorithms/time_evolution/ntupdate.jl @@ -58,6 +58,11 @@ function TimeEvolver( _timeevol_sanity_check(psi0, physicalspace(H), alg) dt′ = _get_dt(psi0, dt, alg.imaginary_time) gate = trotterize(H, dt′; symmetrize_gates) + # convert FixedSpaceTruncation to site-dependent `truncspace`s + if alg.opt_alg.trunc isa FixedSpaceTruncation + trunc = _get_fixedspacetrunc(psi0) + @reset alg.opt_alg.trunc = trunc + end state = NTUState(0, t0, psi0) return TimeEvolver(alg, dt, nstep, gate, state) end @@ -71,7 +76,10 @@ function _ntu_iter( sites::Vector{CartesianIndex{2}}, alg::NeighbourUpdate ) @assert length(sites) == 2 - return _bond_truncate(state, wts, Tuple(sites), alg; gate) + Nr, Nc = size(state) + trunc = only(_get_cluster_trunc(alg.opt_alg.trunc, sites, (Nr, Nc))) + alg′ = (@set alg.opt_alg.trunc = trunc) + return _bond_truncate(state, wts, Tuple(sites), alg′; gate) end """ diff --git a/src/algorithms/time_evolution/ntupdate3site.jl b/src/algorithms/time_evolution/ntupdate3site.jl index 92cc98081..ded2447f0 100644 --- a/src/algorithms/time_evolution/ntupdate3site.jl +++ b/src/algorithms/time_evolution/ntupdate3site.jl @@ -6,19 +6,12 @@ function _ntu_iter( sites::Vector{CartesianIndex{2}}, alg::NeighbourUpdate ) where {T <: AbstractTensorMap} Nr, Nc = size(state) + truncs = _get_cluster_trunc(alg.opt_alg.trunc, sites, (Nr, Nc)) state, wts = copy(state), deepcopy(wts) Ms, _, invperms = _get_cluster(state, sites) flips = [isdual(space(M, 1)) for M in Ms[2:end]] _flip_virtuals!(Ms, flips) # flip virtual arrows in `Ms` to ← - truncs = _get_cluster_trunc(alg.opt_alg.trunc, sites, (Nr, Nc)) - truncs = map(enumerate(truncs)) do (i, trunc) - return if trunc isa FixedSpaceTruncation - truncspace(space(Ms[i + 1], 1)) - else - trunc - end - end # apply gate MPO without truncation _apply_gatempo!(Ms, gate) @@ -73,23 +66,15 @@ function _bond_truncate( @debug "cond(benv) after gauge fix: $(LinearAlgebra.cond(benv))" end - # (optional) apply the NN gate - opt_alg = alg.opt_alg + # (optional) apply the NN gate without truncation if !(gate === nothing) - trunc = if alg.opt_alg.trunc isa FixedSpaceTruncation - V = space(b, 1) - truncspace(isdual(V) ? flip(V) : V) - else - alg.opt_alg.trunc - end - @reset opt_alg.trunc = trunc a, s, b, = _apply_gate(a, b, gate, truncerror(; atol = 1.0e-15)) else a = permute(a, ((1, 2), (3,))) b = permute(b, ((1,), (2, 3))) end - a, s, b, info = bond_truncate(a, b, benv, opt_alg) + a, s, b, info = bond_truncate(a, b, benv, alg.opt_alg) A, B = _qr_bond_undo(X, a, b, Y) normalize!(A, Inf) normalize!(B, Inf) diff --git a/src/algorithms/time_evolution/simpleupdate.jl b/src/algorithms/time_evolution/simpleupdate.jl index ecebda1c5..2a79ebfec 100644 --- a/src/algorithms/time_evolution/simpleupdate.jl +++ b/src/algorithms/time_evolution/simpleupdate.jl @@ -109,10 +109,6 @@ function _su_iter!( # rotate bond, rev = _nn_bondrev(sites..., (Nr, Nc)) A, B = _bond_rotation.(Ms, bond[1], rev; inv = false) - if trunc isa FixedSpaceTruncation - V = west_virtualspace(B) - trunc = truncspace(isdual(V) ? flip(V) : V) - end # apply gate ϵ, s = 0.0, nothing gate_axs = alg.purified ? (1:1) : (1:2) diff --git a/src/algorithms/time_evolution/simpleupdate3site.jl b/src/algorithms/time_evolution/simpleupdate3site.jl index df0d767ab..2fc9d096f 100644 --- a/src/algorithms/time_evolution/simpleupdate3site.jl +++ b/src/algorithms/time_evolution/simpleupdate3site.jl @@ -167,20 +167,13 @@ function _su_iter!( sites::Vector{CartesianIndex{2}}, alg::SimpleUpdate ) where {T <: AbstractTensorMap} Nr, Nc = size(state) + truncs = _get_cluster_trunc(alg.trunc, sites, (Nr, Nc)) Ms, open_vaxs, invperms = _get_cluster(state, sites, env) flips = [isdual(space(M, 1)) for M in Ms[2:end]] Vphys = [codomain(M, 2) for M in Ms] normalize!.(Ms, Inf) # flip virtual arrows in `Ms` to ← _flip_virtuals!(Ms, flips) - truncs = _get_cluster_trunc(alg.trunc, sites, (Nr, Nc)) - truncs = map(enumerate(truncs)) do (i, trunc) - return if trunc isa FixedSpaceTruncation - truncspace(space(Ms[i + 1], 1)) - else - trunc - end - end # apply gate MPOs and truncate gate_axs = alg.purified ? (1:1) : (1:2) wts, ϵs = nothing, nothing diff --git a/test/runtests.jl b/test/runtests.jl index a50cdfe9c..fb3848f85 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -110,9 +110,6 @@ end @time @safetestset "Fixed-space and site-dependent truncation" begin include("timeevol/sitedep_truncation.jl") end - @time @safetestset "Time evolution with FixedSpaceTruncation" begin - include("timeevol/fixedspacetruncation.jl") - end @time @safetestset "Transverse field Ising model at finite temperature" begin include("timeevol/tf_ising_finiteT.jl") end diff --git a/test/timeevol/fixedspacetruncation.jl b/test/timeevol/fixedspacetruncation.jl deleted file mode 100644 index 1777c0436..000000000 --- a/test/timeevol/fixedspacetruncation.jl +++ /dev/null @@ -1,42 +0,0 @@ -using Test -using Random -using TensorKit -using PEPSKit - -elt = Float64 -ham = j1_j2_model(elt, U1Irrep, InfiniteSquare(2, 2); J1 = 1.0, J2 = 0.5, sublattice = false) -Vphy = physicalspace(ham) -Vvir = U1Space(0 => 1, -1 / 2 => 1, 1 / 2 => 1) -Vns = [ - U1Space(0 => 1, 1 => 2, -1 => 1) U1Space(0 => 1, 1 => 1, -1 => 2); - U1Space(0 => 1, 1 => 2, -1 => 1) U1Space(0 => 1, 1 => 1, -1 => 2) -] -Ves1 = [ - U1Space(0 => 1, 1 => 1, -1 => 2) U1Space(1 / 2 => 2, -1 / 2 => 1, 3 / 2 => 1); - U1Space(0 => 1, 1 => 1, -1 => 2) U1Space(1 / 2 => 1, -1 / 2 => 2, -3 / 2 => 1) -] -Ves2 = fill(U1Space(0 => 1, 1 => 1, -1 => 2), (2, 2)) -Venv = U1Space(0 => 2, 1 => 1, -1 => 1) -states = [ - InfinitePEPS(randn, elt, Vphy, Vns, Ves1), - InfinitePEPO(randn, elt, Vphy, Vns, Ves2), -] - -@testset "Simple update on $(typeof(state0).name.wrapper)" for state0 in states - alg = SimpleUpdate(; trunc = FixedSpaceTruncation()) - wts0 = SUWeight(state0) - state, wts, = time_evolve(state0, ham, 0.1, 1, alg, wts0) - for (t, t0) in zip(state.A, state0.A) - @test space(t) == space(t0) - end -end - -@testset "Neighborhood tensor update on $(typeof(state0).name.wrapper)" for state0 in states - opt_alg = ALSTruncation(; trunc = FixedSpaceTruncation()) - alg = NeighbourUpdate(; opt_alg, bondenv_alg = NNEnv()) - evolver = TimeEvolver(state0, ham, 0.1, 1, alg) - state, = time_evolve(evolver) - for (t, t0) in zip(state.A, state0.A) - @test space(t) == space(t0) - end -end diff --git a/test/timeevol/sitedep_truncation.jl b/test/timeevol/sitedep_truncation.jl index 6919e71b1..0efde3c5c 100644 --- a/test/timeevol/sitedep_truncation.jl +++ b/test/timeevol/sitedep_truncation.jl @@ -49,3 +49,25 @@ states = ( @test _is_bipartite(wts) end end + +@testset "NTU on $(typeof(state0).name.wrapper), bipartite = $(bipartite)" for + (state0, bipartite) in Iterators.product(states, (true, false)) + J2 = 0.5 + if bipartite + state0[2, 1] = copy(state0[1, 2]) + state0[2, 2] = copy(state0[1, 1]) + J2 = 0.0 + end + ham = j1_j2_model(elt, U1Irrep, InfiniteSquare(Nr, Nc); J1 = 1.0, J2, sublattice = false) + # converted internally to SiteDependentTruncation + opt_alg = ALSTruncation(; trunc = FixedSpaceTruncation()) + alg = NeighbourUpdate(; opt_alg, bondenv_alg = NNEnv(), bipartite) + state, info = time_evolve(TimeEvolver(state0, ham, 0.1, 1, alg)) + for (t, t0) in zip(state.A, state0.A) + @test space(t) == space(t0) + end + if bipartite + @test _is_bipartite(state) + @test _is_bipartite(info.wts) + end +end From 99e3e4865eb9646ea448478b9e18b918db3469f4 Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Sat, 25 Apr 2026 16:43:41 +0800 Subject: [PATCH 22/41] Require bond tensor to be an MPSTensor --- .../contractions/bondenv/als_solve.jl | 3 -- src/algorithms/truncation/bond_truncation.jl | 40 +++++++------------ test/bondenv/bond_truncate.jl | 3 +- 3 files changed, 17 insertions(+), 29 deletions(-) diff --git a/src/algorithms/contractions/bondenv/als_solve.jl b/src/algorithms/contractions/bondenv/als_solve.jl index 745478073..5d049f84c 100644 --- a/src/algorithms/contractions/bondenv/als_solve.jl +++ b/src/algorithms/contractions/bondenv/als_solve.jl @@ -10,9 +10,6 @@ Contract the virtual legs between da db ``` """ -function _combine_ket(a::MPSTensor, b::AbstractTensorMap{T, S, 1, 2}) where {T, S} - return @tensor ket[DX DY; da db] := a[DX da; D] * b[D; db DY] -end function _combine_ket(a::MPSTensor, b::MPSTensor) return @tensor ket[DX DY; da db] := a[DX da; D] * b[D db; DY] end diff --git a/src/algorithms/truncation/bond_truncation.jl b/src/algorithms/truncation/bond_truncation.jl index 40d083e5e..1a95b8d34 100644 --- a/src/algorithms/truncation/bond_truncation.jl +++ b/src/algorithms/truncation/bond_truncation.jl @@ -49,7 +49,7 @@ function _als_init_truncate( end """ - bond_truncate(a::AbstractTensorMap{T,S,2,1}, b::AbstractTensorMap{T,S,1,2}, benv::BondEnv{T,S}, alg) -> U, S, V, info + bond_truncate(a::MPSTensor, b::MPSTensor, benv::BondEnv, alg) -> U, S, V, info After time-evolving the reduced tensors `a` and `b` connected by a bond, truncate the bond dimension using the bond environment tensor `benv`. @@ -63,19 +63,14 @@ truncate the bond dimension using the bond environment tensor `benv`. └-----------------------┘ ``` The truncation algorithm `alg` can be either `FullEnvTruncation` or `ALSTruncation`. -The index order of `a` or `b` is +The index order of `a` or `b` follows `MPSTensor` convention. ``` 1 -a/b- 3 - ↓ a[1 2; 3] - 2 b[1; 2 3] + ↓ [1 2; 3] + 2 ``` """ -function bond_truncate( - a::AbstractTensorMap{T, S, 2, 1}, - b::AbstractTensorMap{T, S, 1, 2}, - benv::BondEnv{T, S}, - alg::ALSTruncation, - ) where {T <: Number, S <: ElementarySpace} +function bond_truncate(a::MPSTensor, b::MPSTensor, benv::BondEnv, alg::ALSTruncation) # dual check of physical index @assert !isdual(space(a, 2)) @assert !isdual(space(b, 2)) @@ -144,18 +139,14 @@ function bond_truncate( end a, s, b = svd_trunc!(_combine_ket_for_svd(xs...); trunc = alg.trunc) a, b = absorb_s(a, s, b) + b = permute(b, ((1, 2), (3,))) if need_flip a, s, b = flip(a, numind(a)), _fliptwist_s(s), flip(b, 1) end return a, s, b, (; fid, Δfid, Δs) end -function bond_truncate( - a::AbstractTensorMap{T, S, 2, 1}, - b::AbstractTensorMap{T, S, 1, 2}, - benv::BondEnv{T, S}, - alg::FullEnvTruncation, - ) where {T <: Number, S <: ElementarySpace} +function bond_truncate(a::MPSTensor, b::MPSTensor, benv::BondEnv, alg::FullEnvTruncation) # dual check of physical index @assert !isdual(space(a, 2)) @assert !isdual(space(b, 2)) @@ -163,14 +154,14 @@ function bond_truncate( need_flip = isdual(space(b, 1)) #= initialize bond matrix using QR as `Ra Lb` - --- a == b --- ==> - Qa ← Ra == Rb ← Qb - + --- a == b --- ==> - Qa ← Ra == Rb → Qb - ↓ ↓ ↓ ↓ =# Qa, Ra = left_orth(a; positive = true) - Rb, Qb = right_orth(b; positive = true) - @assert !isdual(space(Ra, 1)) && !isdual(space(Qb, 1)) - @tensor b0[-1; -2] := Ra[-1 1] * Rb[1 -2] - #= initialize bond environment around `Ra Lb` + b = permute(b, ((3, 2), (1,)); copy = true) + Qb, Rb = left_orth!(b; positive = true) + @tensor b0[-1; -2] := Ra[-1 1] * Rb[-2 1] + #= initialize bond environment around `Ra Rb` ┌--------------------------------------┐ | ┌----┐ | @@ -182,15 +173,14 @@ function bond_truncate( | └----┘ | └--------------------------------------┘ =# - @tensor benv2[-1 -2; -3 -4] := ( - benv[1 2; 3 4] * conj(Qa[1 5 -1]) * conj(Qb[-2 6 2]) * Qa[3 5 -3] * Qb[-4 6 4] - ) + @tensor benv2[-1 -2; -3 -4] := benv[1 2; 3 4] * + conj(Qa[1 5 -1]) * conj(Qb[2 6 -2]) * Qa[3 5 -3] * Qb[4 6 -4] # optimize bond matrix u, s, vh, info = fullenv_truncate(b0, benv2, alg) u, vh = absorb_s(u, s, vh) # truncate a, b tensors with u, s, vh @tensor a[-1 -2; -3] := Qa[-1 -2 3] * u[3 -3] - @tensor b[-1; -2 -3] := vh[-1 1] * Qb[1 -2 -3] + @tensor b[-1 -2; -3] := vh[-1 1] * Qb[-3 -2 1] if need_flip a, s, b = flip(a, numind(a)), _fliptwist_s(s), flip(b, 1) end diff --git a/test/bondenv/bond_truncate.jl b/test/bondenv/bond_truncate.jl index 684cd33c9..415d7c0d4 100644 --- a/test/bondenv/bond_truncate.jl +++ b/test/bondenv/bond_truncate.jl @@ -31,11 +31,12 @@ for Vl in (Vqro, Vqro'), Vr in (Vqro, Vqro') @info "Dimension of benv = $(Dext)" # untruncated bond tensors a2 = randn(elt, Vl ⊗ Vphy ← Vint) - b2 = randn(elt, Vint ← Vphy' ⊗ Vr') + b2 = randn(elt, Vint ⊗ Vphy ← Vr') # bond tensor (truncated SVD initialization) a2b2 = _combine_ket(a2, b2) a0, s, b0 = svd_trunc(permute(a2b2, ((1, 3), (4, 2))); trunc = trunc) a0, b0 = PEPSKit.absorb_s(a0, s, b0) + b0 = permute(b0, ((1, 2), (3,))) fid0 = cost_function_als(benv, _combine_ket(a0, b0), a2b2)[2] @info "Fidelity of simple SVD truncation = $fid0.\n" ss = Dict{String, DiagonalTensorMap}() From b6aeff3724cc14c0fd4dd664e592b721ed3a1302 Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Sat, 25 Apr 2026 17:05:15 +0800 Subject: [PATCH 23/41] Replace `_qr_bond` with `bond_tensor` functions --- .../contractions/bondenv/benv_ctm.jl | 4 +- .../contractions/bondenv/benv_tools.jl | 2 +- .../contractions/bondenv/gaugefix.jl | 6 +- src/algorithms/time_evolution/apply_gate.jl | 6 +- .../time_evolution/ntupdate3site.jl | 6 +- src/algorithms/time_evolution/simpleupdate.jl | 6 +- src/algorithms/truncation/bond_tensor.jl | 158 ++++++++++-------- test/bondenv/benv_ctm.jl | 3 +- test/bondenv/benv_ntu.jl | 5 +- 9 files changed, 111 insertions(+), 85 deletions(-) diff --git a/src/algorithms/contractions/bondenv/benv_ctm.jl b/src/algorithms/contractions/bondenv/benv_ctm.jl index 45262cc67..6910f30ea 100644 --- a/src/algorithms/contractions/bondenv/benv_ctm.jl +++ b/src/algorithms/contractions/bondenv/benv_ctm.jl @@ -9,8 +9,8 @@ Construct the environment (norm) tensor -1 0 1 2 ``` with `X` at position `[row, col]`. -`X, Y` are unitary tensors produced when finding the reduced site tensors -with `_qr_bond`; and `XX = X' X` and `YY = Y' Y` (stacked together). +`X, Y` are unitary tensors produced when finding the reduced site tensors, +and `XX = X' X` and `YY = Y' Y` (stacked together). Axis order: `[DX1 DY1; DX0 DY0]`, as in ``` diff --git a/src/algorithms/contractions/bondenv/benv_tools.jl b/src/algorithms/contractions/bondenv/benv_tools.jl index c3ae13ded..8b2ff362e 100644 --- a/src/algorithms/contractions/bondenv/benv_tools.jl +++ b/src/algorithms/contractions/bondenv/benv_tools.jl @@ -9,7 +9,7 @@ const BondEnv{T, S} = AbstractTensorMap{T, S, 2, 2} where {T <: Number, S <: Ele const BondEnv3site{T, S} = AbstractTensorMap{T, S, 4, 4} where {T <: Number, S <: ElementarySpace} const Hair{T, S} = AbstractTensor{T, S, 2} where {T <: Number, S <: ElementarySpace} # Orthogonal tensors obtained PEPSTensor/PEPOTensor -# with one physical leg factored out by `_qr_bond` +# with one physical leg factored out by `bond_tensor_...` const PEPSOrth{T, S} = AbstractTensor{T, S, 4} where {T <: Number, S <: ElementarySpace} const PEPOOrth{T, S} = AbstractTensor{T, S, 5} where {T <: Number, S <: ElementarySpace} diff --git a/src/algorithms/contractions/bondenv/gaugefix.jl b/src/algorithms/contractions/bondenv/gaugefix.jl index 9df4c1599..a8edafc0a 100644 --- a/src/algorithms/contractions/bondenv/gaugefix.jl +++ b/src/algorithms/contractions/bondenv/gaugefix.jl @@ -44,9 +44,7 @@ Reference: - Physical Review B 92, 035142 (2015) """ function fixgauge_benv( - Z::AbstractTensorMap{T, S, 1, 2}, - a::AbstractTensorMap{T, S, 1, 2}, - b::AbstractTensorMap{T, S, 2, 1}, + Z::AbstractTensorMap{T, S, 1, 2}, a::MPSTensor, b::MPSTensor ) where {T <: Number, S <: ElementarySpace} @assert !isdual(space(Z, 1)) @assert !isdual(space(a, 2)) @@ -83,7 +81,7 @@ function fixgauge_benv( ↓ -1 =# - @plansor a[-1; -2 -3] := R[-1; 1] * a[1; -2 -3] + @plansor a[-1 -2; -3] := R[-1; 1] * a[1 -2; -3] @plansor b[-1 -2; -3] := b[-1 -2; 1] * L[-3; 1] @plansor Z[-1; -2 -3] := Z[-1; 1 2] * Rinv[1; -2] * Linv[2; -3] (isdual(space(R, 1)) == isdual(space(R, 2))) && twist!(a, 1) diff --git a/src/algorithms/time_evolution/apply_gate.jl b/src/algorithms/time_evolution/apply_gate.jl index 7af55701a..67d4c63cf 100644 --- a/src/algorithms/time_evolution/apply_gate.jl +++ b/src/algorithms/time_evolution/apply_gate.jl @@ -34,10 +34,7 @@ Apply 2-site `gate` on the reduced bond tensors `a`, `b` -2 -3 -1← a --- 3 --- b ← -4 ``` """ -function _apply_gate( - a::AbstractTensorMap, b::AbstractTensorMap, - gate::NNGate, trunc::TruncationStrategy - ) +function _apply_gate(a::MPSTensor, b::MPSTensor, gate::NNGate, trunc::TruncationStrategy) V = space(b, 1) need_flip = isdual(V) if isdual(space(a, 2)) @@ -50,5 +47,6 @@ function _apply_gate( if need_flip a, s, b = flip(a, numind(a)), _fliptwist_s(s), flip(b, 1) end + b = permute(b, ((1, 2), (3,))) return a, s, b, ϵ end diff --git a/src/algorithms/time_evolution/ntupdate3site.jl b/src/algorithms/time_evolution/ntupdate3site.jl index ded2447f0..06d573774 100644 --- a/src/algorithms/time_evolution/ntupdate3site.jl +++ b/src/algorithms/time_evolution/ntupdate3site.jl @@ -54,7 +54,8 @@ function _bond_truncate( A, B = state2[row, col], state2[row, cp1] # create bond environment - X, a, b, Y = _qr_bond(A, B; trunc = trunctol(; rtol = 1.0e-12)) + X, a = bond_tensor_first(A; trunc = trunctol(; rtol = 1.0e-12)) + Y, b = bond_tensor_last(B; trunc = trunctol(; rtol = 1.0e-12)) benv = bondenv_ntu(row, col, X, Y, state2, alg.bondenv_alg) @debug "cond(benv) before gauge fix: $(LinearAlgebra.cond(benv))" if alg.fixgauge @@ -75,7 +76,8 @@ function _bond_truncate( end a, s, b, info = bond_truncate(a, b, benv, alg.opt_alg) - A, B = _qr_bond_undo(X, a, b, Y) + A = undo_bond_tensor_first(X, a) + B = undo_bond_tensor_last(Y, b) normalize!(A, Inf) normalize!(B, Inf) normalize!(s, Inf) diff --git a/src/algorithms/time_evolution/simpleupdate.jl b/src/algorithms/time_evolution/simpleupdate.jl index 2a79ebfec..471bafe06 100644 --- a/src/algorithms/time_evolution/simpleupdate.jl +++ b/src/algorithms/time_evolution/simpleupdate.jl @@ -113,10 +113,12 @@ function _su_iter!( ϵ, s = 0.0, nothing gate_axs = alg.purified ? (1:1) : (1:2) for gate_ax in gate_axs - X, a, b, Y = _qr_bond(A, B; gate_ax, positive = true) + X, a = bond_tensor_first(A; gate_ax, positive = true) + Y, b = bond_tensor_last(B; gate_ax, positive = true) a, s, b, ϵ′ = _apply_gate(a, b, gate, trunc) ϵ = max(ϵ, ϵ′) - A, B = _qr_bond_undo(X, a, b, Y) + A = undo_bond_tensor_first(X, a; gate_ax) + B = undo_bond_tensor_last(Y, b; gate_ax) end # rotate back A = _bond_rotation(A, bond[1], rev; inv = true) diff --git a/src/algorithms/truncation/bond_tensor.jl b/src/algorithms/truncation/bond_tensor.jl index 89bfc86ee..a1c02887b 100644 --- a/src/algorithms/truncation/bond_tensor.jl +++ b/src/algorithms/truncation/bond_tensor.jl @@ -1,90 +1,114 @@ """ -$(SIGNATURES) +Given the first tensor `A` in the cluster acted on by a gate, +obtain reduced tensor on its next bond. -Use QR decomposition on two tensors `A`, `B` connected by a bond to get the reduced tensors. -When `A`, `B` are PEPSTensors, +For PEPSTensor, ``` - 2 1 1 - | | | - 5 -A/B- 3 ====> 4 - X ← 2 1 ← a - 3 1 - b → 3 4 → Y - 2 - | ↘ | ↘ ↘ | - 4 1 3 2 2 3 + 1 + | + 4 - X ← 2 1 ← a - 3 + | ↘ + 3 2 ``` -When `A`, `B` are PEPOTensors, -- If `gate_ax = 1` +For PEPOTensor, ``` - 2 3 1 2 1 2 - ↘ | ↘ | ↘ | - 6 -A/B- 4 ====> 5 - X ← 3 1 ← a - 3 1 - b → 3 5 → Y - 3 - | ↘ | ↘ ↘ | - 5 1 4 2 2 4 -``` -- If `gate_ax = 2` -``` - 2 3 2 2 2 2 - ↘ | | ↘ ↘ | - 6 -A/B- 4 ====> 5 - X ← 3 1 ← a - 3 1 - b → 3 5 → Y - 3 - | ↘ | ↘ | ↘ - 5 1 4 1 4 1 + gate_ax = 1 gate_ax = 2 + + 1 2 2 2 + ↘ | | ↘ + 5 - X ← 3 1 ← a - 3 5 - X ← 3 1 ← a - 3 + | ↘ | ↘ + 4 2 4 1 ``` """ -function _qr_bond(A::PT, B::PT; gate_ax::Int = 1, kwargs...) where {PT <: Union{PEPSTensor, PEPOTensor}} - @assert 1 <= gate_ax <= numout(A) - permA, permB, permX, permY = if A isa PEPSTensor - ((2, 4, 5), (1, 3)), ((2, 3, 4), (1, 5)), (1, 4, 2, 3), Tuple(1:4) +function bond_tensor_first(A::PEPSTensor; gate_ax::Integer = 1, kwargs...) + @assert gate_ax == 1 + X, a = left_orth!(permute(A, ((2, 4, 5), (1, 3)); copy = true); kwargs...) + X = permute(X, (1, 4, 2, 3)) + a = permute(a, ((1, 2), (3,))) + return X, a +end +function bond_tensor_first(A::PEPOTensor; gate_ax::Integer = 1, kwargs...) + @assert 1 <= gate_ax <= 2 + X, a = if gate_ax == 1 + left_orth!(permute(A, ((2, 3, 5, 6), (1, 4)); copy = true); kwargs...) else - if gate_ax == 1 - ((2, 3, 5, 6), (1, 4)), ((2, 3, 4, 5), (1, 6)), (1, 2, 5, 3, 4), Tuple(1:5) - else - ((1, 3, 5, 6), (2, 4)), ((1, 3, 4, 5), (2, 6)), (1, 2, 5, 3, 4), Tuple(1:5) - end + left_orth!(permute(A, ((1, 3, 5, 6), (2, 4)); copy = true); kwargs...) end - X, a = left_orth!(permute(A, permA; copy = true); kwargs...) - Y, b = left_orth!(permute(B, permB; copy = true); kwargs...) - X, Y = permute(X, permX), permute(Y, permY) - b = permute(b, ((3, 2), (1,))) - return X, a, b, Y + X = permute(X, (1, 2, 5, 3, 4)) + a = permute(a, ((1, 2), (3,))) + return X, a end """ -$(SIGNATURES) +Undo the decomposition in `bond_tensor_first`. +""" +function undo_bond_tensor_first(X::PEPSOrth, a::MPSTensor; gate_ax::Integer = 1) + @assert gate_ax == 1 + return @tensor A[-1; -2 -3 -4 -5] := X[-2 1 -4 -5] * a[1 -1 -3] +end +function undo_bond_tensor_first(X::PEPOOrth, a::MPSTensor; gate_ax::Integer = 1) + @assert 1 <= gate_ax <= 2 + if gate_ax == 1 + return @tensor A[-1 -2; -3 -4 -5 -6] := X[-2 -3 1 -5 -6] * a[1 -1 -4] + else + return @tensor A[-1 -2; -3 -4 -5 -6] := X[-1 -3 1 -5 -6] * a[1 -2 -4] + end +end -Reconstruct the tensors connected by a bond from their `_qr_bond` results. -For PEPSTensors, +""" +Given the last tensor `A` in the cluster acted on by a gate, +obtain reduced tensor on its previous bond. + +For PEPSTensor, ``` - -2 -2 - | | - -5- X - 1 - a - -3 -5 - b - 1 - Y - -3 - | ↘ ↘ | - -4 -1 -1 -4 + 1 + | + 1 - b → 3 4 → Y - 2 + ↘ | + 2 3 ``` -For PEPOTensors +For PEPOTensor, ``` - -2 -3 -2 -3 - ↘ | ↘ | - -6- X - 1 - a - -4 -6 - b - 1 - Y - -4 - | ↘ ↘ | - -5 -1 -1 -5 + gate_ax = 1 gate_ax = 2 - -3 -2 -2 -3 - | ↘ ↘ | - -6- X - 1 - a - -4 -6 - b - 1 - Y - -4 - | ↘ | ↘ - -5 -1 -5 -1 + 1 2 2 2 + ↘ | ↘ | + 1 - b → 3 5 → Y - 3 1 - b → 3 5 → Y - 3 + ↘ | | ↘ + 2 4 4 1 ``` """ -function _qr_bond_undo(X::PEPSOrth, a::AbstractTensorMap, b::AbstractTensorMap, Y::PEPSOrth) - @tensor A[-1; -2 -3 -4 -5] := X[-2 1 -4 -5] * a[1 -1 -3] - @tensor B[-1; -2 -3 -4 -5] := b[-5 -1 1] * Y[-2 -3 -4 1] - return A, B +function bond_tensor_last(A::PEPSTensor; gate_ax::Integer = 1, kwargs...) + @assert gate_ax == 1 + Y, b = left_orth!(permute(A, ((2, 3, 4), (1, 5)); copy = true); kwargs...) + Y = permute(Y, (1, 2, 3, 4)) + b = permute(b, ((3, 2), (1,))) + return Y, b +end +function bond_tensor_last(A::PEPOTensor; gate_ax::Integer = 1, kwargs...) + @assert 1 <= gate_ax <= 2 + Y, b = if gate_ax == 1 + left_orth!(permute(A, ((2, 3, 4, 5), (1, 6)); copy = true); kwargs...) + else + left_orth!(permute(A, ((1, 3, 4, 5), (2, 6)); copy = true); kwargs...) + end + Y = permute(Y, (1, 2, 3, 4, 5)) + b = permute(b, ((3, 2), (1,))) + return Y, b +end + +""" +Undo the decomposition in `bond_tensor_last`. +""" +function undo_bond_tensor_last(Y::PEPSOrth, b::MPSTensor) + return @tensor A[-1; -2 -3 -4 -5] := b[-5 -1 1] * Y[-2 -3 -4 1] end -function _qr_bond_undo(X::PEPOOrth, a::AbstractTensorMap, b::AbstractTensorMap, Y::PEPOOrth) - if !isdual(space(a, 2)) - @tensor A[-1 -2; -3 -4 -5 -6] := X[-2 -3 1 -5 -6] * a[1 -1 -4] - @tensor B[-1 -2; -3 -4 -5 -6] := b[-6 -1 1] * Y[-2 -3 -4 -5 1] +function undo_bond_tensor_last(Y::PEPOOrth, b::MPSTensor; gate_ax::Integer = 1) + @assert 1 <= gate_ax <= 2 + if gate_ax == 1 + return @tensor A[-1 -2; -3 -4 -5 -6] := b[-6 -1 1] * Y[-2 -3 -4 -5 1] else - @tensor A[-1 -2; -3 -4 -5 -6] := X[-1 -3 1 -5 -6] * a[1 -2 -4] - @tensor B[-1 -2; -3 -4 -5 -6] := b[-6 -2 1] * Y[-1 -3 -4 -5 1] + return @tensor A[-1 -2; -3 -4 -5 -6] := b[-6 -2 1] * Y[-1 -3 -4 -5 1] end - return A, B end diff --git a/test/bondenv/benv_ctm.jl b/test/bondenv/benv_ctm.jl index 95ece1658..baf94e14a 100644 --- a/test/bondenv/benv_ctm.jl +++ b/test/bondenv/benv_ctm.jl @@ -42,7 +42,8 @@ function test_benv_ctm(state::Union{InfinitePEPS, InfinitePEPO}) for row in 1:Nr, col in 1:Nc cp1 = PEPSKit._next(col, Nc) A, B = state.A[row, col], state.A[row, cp1] - X, a, b, Y = PEPSKit._qr_bond(A, B) + X, a = PEPSKit.bond_tensor_first(A) + Y, b = PEPSKit.bond_tensor_last(B) benv = PEPSKit.bondenv_ctm(row, col, X, Y, env) Z = PEPSKit.positive_approx(benv) # verify that gauge fixing can greatly reduce diff --git a/test/bondenv/benv_ntu.jl b/test/bondenv/benv_ntu.jl index d3e2e4ffd..9b439fc85 100644 --- a/test/bondenv/benv_ntu.jl +++ b/test/bondenv/benv_ntu.jl @@ -15,10 +15,11 @@ function test_ntu_env( for row in 1:Nr, col in 1:Nc cp1 = PEPSKit._next(col, Nc) A, B = state.A[row, col], state.A[row, cp1] - X, a, b, Y = PEPSKit._qr_bond(A, B) + X, a = PEPSKit.bond_tensor_first(A) + Y, b = PEPSKit.bond_tensor_last(B) @tensor ab[DX DY; da db] := a[DX da D] * b[D db DY] benv = PEPSKit.bondenv_ntu(row, col, X, Y, state, env_alg) - # this is a result of `_qr_bond` + # this is a result of `bond_tensor_...` @assert [isdual(space(benv, ax)) for ax in 1:numind(benv)] == [0, 0, 1, 1] # NTU bond environments are exact and should be positive definite @test benv' ≈ benv From bbc3bc4d65b6112197ef9c693ec1da5da34a02cb Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Sat, 25 Apr 2026 17:50:55 +0800 Subject: [PATCH 24/41] Fix `undo_bond_tensor_last` --- src/algorithms/truncation/bond_tensor.jl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/algorithms/truncation/bond_tensor.jl b/src/algorithms/truncation/bond_tensor.jl index a1c02887b..3ef7bdc0e 100644 --- a/src/algorithms/truncation/bond_tensor.jl +++ b/src/algorithms/truncation/bond_tensor.jl @@ -101,7 +101,8 @@ end """ Undo the decomposition in `bond_tensor_last`. """ -function undo_bond_tensor_last(Y::PEPSOrth, b::MPSTensor) +function undo_bond_tensor_last(Y::PEPSOrth, b::MPSTensor; gate_ax::Integer = 1) + @assert gate_ax == 1 return @tensor A[-1; -2 -3 -4 -5] := b[-5 -1 1] * Y[-2 -3 -4 1] end function undo_bond_tensor_last(Y::PEPOOrth, b::MPSTensor; gate_ax::Integer = 1) From de5a5d5bd65c658195dc6db521dc47edbe8c35be Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Sat, 25 Apr 2026 20:59:44 +0800 Subject: [PATCH 25/41] Change bond_tensor return order --- .../time_evolution/ntupdate3site.jl | 8 +++---- src/algorithms/time_evolution/simpleupdate.jl | 8 +++---- src/algorithms/truncation/bond_tensor.jl | 24 +++++++++---------- test/bondenv/benv_ctm.jl | 4 ++-- test/bondenv/benv_ntu.jl | 4 ++-- 5 files changed, 24 insertions(+), 24 deletions(-) diff --git a/src/algorithms/time_evolution/ntupdate3site.jl b/src/algorithms/time_evolution/ntupdate3site.jl index 06d573774..713c789fc 100644 --- a/src/algorithms/time_evolution/ntupdate3site.jl +++ b/src/algorithms/time_evolution/ntupdate3site.jl @@ -54,8 +54,8 @@ function _bond_truncate( A, B = state2[row, col], state2[row, cp1] # create bond environment - X, a = bond_tensor_first(A; trunc = trunctol(; rtol = 1.0e-12)) - Y, b = bond_tensor_last(B; trunc = trunctol(; rtol = 1.0e-12)) + a, X = bond_tensor_first(A; trunc = trunctol(; rtol = 1.0e-12)) + b, Y = bond_tensor_last(B; trunc = trunctol(; rtol = 1.0e-12)) benv = bondenv_ntu(row, col, X, Y, state2, alg.bondenv_alg) @debug "cond(benv) before gauge fix: $(LinearAlgebra.cond(benv))" if alg.fixgauge @@ -76,8 +76,8 @@ function _bond_truncate( end a, s, b, info = bond_truncate(a, b, benv, alg.opt_alg) - A = undo_bond_tensor_first(X, a) - B = undo_bond_tensor_last(Y, b) + A = undo_bond_tensor_first(a, X) + B = undo_bond_tensor_last(b, Y) normalize!(A, Inf) normalize!(B, Inf) normalize!(s, Inf) diff --git a/src/algorithms/time_evolution/simpleupdate.jl b/src/algorithms/time_evolution/simpleupdate.jl index 471bafe06..5c3d30a19 100644 --- a/src/algorithms/time_evolution/simpleupdate.jl +++ b/src/algorithms/time_evolution/simpleupdate.jl @@ -113,12 +113,12 @@ function _su_iter!( ϵ, s = 0.0, nothing gate_axs = alg.purified ? (1:1) : (1:2) for gate_ax in gate_axs - X, a = bond_tensor_first(A; gate_ax, positive = true) - Y, b = bond_tensor_last(B; gate_ax, positive = true) + a, X = bond_tensor_first(A; gate_ax, positive = true) + b, Y = bond_tensor_last(B; gate_ax, positive = true) a, s, b, ϵ′ = _apply_gate(a, b, gate, trunc) ϵ = max(ϵ, ϵ′) - A = undo_bond_tensor_first(X, a; gate_ax) - B = undo_bond_tensor_last(Y, b; gate_ax) + A = undo_bond_tensor_first(a, X; gate_ax) + B = undo_bond_tensor_last(b, Y; gate_ax) end # rotate back A = _bond_rotation(A, bond[1], rev; inv = true) diff --git a/src/algorithms/truncation/bond_tensor.jl b/src/algorithms/truncation/bond_tensor.jl index 3ef7bdc0e..33df63d79 100644 --- a/src/algorithms/truncation/bond_tensor.jl +++ b/src/algorithms/truncation/bond_tensor.jl @@ -14,11 +14,11 @@ For PEPOTensor, ``` gate_ax = 1 gate_ax = 2 - 1 2 2 2 - ↘ | | ↘ + 1 2 2 2 + ↘ | | ↘ 5 - X ← 3 1 ← a - 3 5 - X ← 3 1 ← a - 3 - | ↘ | ↘ - 4 2 4 1 + | ↘ | ↘ + 4 2 4 1 ``` """ function bond_tensor_first(A::PEPSTensor; gate_ax::Integer = 1, kwargs...) @@ -26,7 +26,7 @@ function bond_tensor_first(A::PEPSTensor; gate_ax::Integer = 1, kwargs...) X, a = left_orth!(permute(A, ((2, 4, 5), (1, 3)); copy = true); kwargs...) X = permute(X, (1, 4, 2, 3)) a = permute(a, ((1, 2), (3,))) - return X, a + return a, X end function bond_tensor_first(A::PEPOTensor; gate_ax::Integer = 1, kwargs...) @assert 1 <= gate_ax <= 2 @@ -37,17 +37,17 @@ function bond_tensor_first(A::PEPOTensor; gate_ax::Integer = 1, kwargs...) end X = permute(X, (1, 2, 5, 3, 4)) a = permute(a, ((1, 2), (3,))) - return X, a + return a, X end """ Undo the decomposition in `bond_tensor_first`. """ -function undo_bond_tensor_first(X::PEPSOrth, a::MPSTensor; gate_ax::Integer = 1) +function undo_bond_tensor_first(a::MPSTensor, X::PEPSOrth; gate_ax::Integer = 1) @assert gate_ax == 1 return @tensor A[-1; -2 -3 -4 -5] := X[-2 1 -4 -5] * a[1 -1 -3] end -function undo_bond_tensor_first(X::PEPOOrth, a::MPSTensor; gate_ax::Integer = 1) +function undo_bond_tensor_first(a::MPSTensor, X::PEPOOrth; gate_ax::Integer = 1) @assert 1 <= gate_ax <= 2 if gate_ax == 1 return @tensor A[-1 -2; -3 -4 -5 -6] := X[-2 -3 1 -5 -6] * a[1 -1 -4] @@ -84,7 +84,7 @@ function bond_tensor_last(A::PEPSTensor; gate_ax::Integer = 1, kwargs...) Y, b = left_orth!(permute(A, ((2, 3, 4), (1, 5)); copy = true); kwargs...) Y = permute(Y, (1, 2, 3, 4)) b = permute(b, ((3, 2), (1,))) - return Y, b + return b, Y end function bond_tensor_last(A::PEPOTensor; gate_ax::Integer = 1, kwargs...) @assert 1 <= gate_ax <= 2 @@ -95,17 +95,17 @@ function bond_tensor_last(A::PEPOTensor; gate_ax::Integer = 1, kwargs...) end Y = permute(Y, (1, 2, 3, 4, 5)) b = permute(b, ((3, 2), (1,))) - return Y, b + return b, Y end """ Undo the decomposition in `bond_tensor_last`. """ -function undo_bond_tensor_last(Y::PEPSOrth, b::MPSTensor; gate_ax::Integer = 1) +function undo_bond_tensor_last(b::MPSTensor, Y::PEPSOrth; gate_ax::Integer = 1) @assert gate_ax == 1 return @tensor A[-1; -2 -3 -4 -5] := b[-5 -1 1] * Y[-2 -3 -4 1] end -function undo_bond_tensor_last(Y::PEPOOrth, b::MPSTensor; gate_ax::Integer = 1) +function undo_bond_tensor_last(b::MPSTensor, Y::PEPOOrth; gate_ax::Integer = 1) @assert 1 <= gate_ax <= 2 if gate_ax == 1 return @tensor A[-1 -2; -3 -4 -5 -6] := b[-6 -1 1] * Y[-2 -3 -4 -5 1] diff --git a/test/bondenv/benv_ctm.jl b/test/bondenv/benv_ctm.jl index baf94e14a..ee081520f 100644 --- a/test/bondenv/benv_ctm.jl +++ b/test/bondenv/benv_ctm.jl @@ -42,8 +42,8 @@ function test_benv_ctm(state::Union{InfinitePEPS, InfinitePEPO}) for row in 1:Nr, col in 1:Nc cp1 = PEPSKit._next(col, Nc) A, B = state.A[row, col], state.A[row, cp1] - X, a = PEPSKit.bond_tensor_first(A) - Y, b = PEPSKit.bond_tensor_last(B) + a, X = PEPSKit.bond_tensor_first(A) + b, Y = PEPSKit.bond_tensor_last(B) benv = PEPSKit.bondenv_ctm(row, col, X, Y, env) Z = PEPSKit.positive_approx(benv) # verify that gauge fixing can greatly reduce diff --git a/test/bondenv/benv_ntu.jl b/test/bondenv/benv_ntu.jl index 9b439fc85..15c1654c4 100644 --- a/test/bondenv/benv_ntu.jl +++ b/test/bondenv/benv_ntu.jl @@ -15,8 +15,8 @@ function test_ntu_env( for row in 1:Nr, col in 1:Nc cp1 = PEPSKit._next(col, Nc) A, B = state.A[row, col], state.A[row, cp1] - X, a = PEPSKit.bond_tensor_first(A) - Y, b = PEPSKit.bond_tensor_last(B) + a, X = PEPSKit.bond_tensor_first(A) + b, Y = PEPSKit.bond_tensor_last(B) @tensor ab[DX DY; da db] := a[DX da D] * b[D db DY] benv = PEPSKit.bondenv_ntu(row, col, X, Y, state, env_alg) # this is a result of `bond_tensor_...` From 2d90e0497db675970f036dfbdf70af1a9c06468e Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Sun, 26 Apr 2026 10:01:19 +0800 Subject: [PATCH 26/41] Do not move phys leg to bond tensor for middle cluster sites --- .../contractions/bondenv/benv_ctm.jl | 4 +- .../contractions/bondenv/benv_ntu.jl | 12 +-- .../contractions/bondenv/gaugefix.jl | 75 ++++++++------ src/algorithms/time_evolution/ntupdate.jl | 2 +- .../time_evolution/ntupdate3site.jl | 46 +++++++-- src/algorithms/truncation/bond_tensor.jl | 98 +++++++++++++++++++ test/bondenv/benv_gaugefix.jl | 3 +- test/bondenv/benv_ntu.jl | 3 +- 8 files changed, 194 insertions(+), 49 deletions(-) diff --git a/src/algorithms/contractions/bondenv/benv_ctm.jl b/src/algorithms/contractions/bondenv/benv_ctm.jl index 6910f30ea..8c6b1a199 100644 --- a/src/algorithms/contractions/bondenv/benv_ctm.jl +++ b/src/algorithms/contractions/bondenv/benv_ctm.jl @@ -24,8 +24,8 @@ Axis order: `[DX1 DY1; DX0 DY0]`, as in ``` """ function bondenv_ctm( - row::Int, col::Int, X::T, Y::T, env::CTMRGEnv - ) where {T} + row::Int, col::Int, X::TX, Y::TY, env::CTMRGEnv + ) where {TX, TY} Nr, Nc = size(env.corners)[[2, 3]] cm1 = _prev(col, Nc) cp1 = _next(col, Nc) diff --git a/src/algorithms/contractions/bondenv/benv_ntu.jl b/src/algorithms/contractions/bondenv/benv_ntu.jl index 2c88c1cfe..ad4327f7b 100644 --- a/src/algorithms/contractions/bondenv/benv_ntu.jl +++ b/src/algorithms/contractions/bondenv/benv_ntu.jl @@ -34,8 +34,8 @@ Calculate the bond environment within "NTU-NN" approximation. ``` """ function bondenv_ntu( - row::Int, col::Int, X::T, Y::T, state::S, alg::NNEnv - ) where {T, S <: InfiniteState} + row::Int, col::Int, X::TX, Y::TY, state::S, alg::NNEnv + ) where {TX, TY, S <: InfiniteState} neighbors = [(-1, 0), (0, -1), (1, 0), (1, 1), (0, 2), (-1, 1)] m = collect_neighbors(state, row, col, neighbors) X, Y = _prepare_site_tensor(X), _prepare_site_tensor(Y) @@ -78,8 +78,8 @@ Calculate the bond environment within "NTU-NN+" approximation. Dotted lines and ○ are splitted using SVD with `truncrank(1)`. """ function bondenv_ntu( - row::Int, col::Int, X::T, Y::T, state::S, alg::NNpEnv - ) where {T, S <: InfiniteState} + row::Int, col::Int, X::TX, Y::TY, state::S, alg::NNpEnv + ) where {TX, TY, S <: InfiniteState} neighbors = [ (-1, -1), (0, -1), (1, -1), (1, 0), (1, 1), (1, 2), (0, 2), (-1, 2), (-1, 1), (-1, 0), (0, -2), (2, 0), (2, 1), (0, 3), (-2, 1), (-2, 0), @@ -191,8 +191,8 @@ Calculates the bond environment within "NTU-NNN" approximation. ``` """ function bondenv_ntu( - row::Int, col::Int, X::T, Y::T, state::S, alg::NNNEnv - ) where {T, S <: InfiniteState} + row::Int, col::Int, X::TX, Y::TY, state::S, alg::NNNEnv + ) where {TX, TY, S <: InfiniteState} neighbors = [ (-1, -1), (0, -1), (1, -1), (1, 0), (1, 1), (1, 2), (0, 2), diff --git a/src/algorithms/contractions/bondenv/gaugefix.jl b/src/algorithms/contractions/bondenv/gaugefix.jl index a8edafc0a..8d964d395 100644 --- a/src/algorithms/contractions/bondenv/gaugefix.jl +++ b/src/algorithms/contractions/bondenv/gaugefix.jl @@ -90,40 +90,59 @@ function fixgauge_benv( end """ -When the (half) bond environment `Z` consists of -two `PEPSOrth` or `PEPOOrth` tensors `X`, `Y` as +Apply the gauge transformation `Rinv` for `Z` ``` ┌-----------------------┐ - | | - └---Z---(X)-- --(Y)---┘ + └---Z--(X)--Rinv-- ---┘ ↓ ``` -apply the gauge transformation `Linv`, `Rinv` for `Z` to `X`, `Y`: +to `X`. For example, when `X` is a `PEPSTensor`, ``` - -1 -1 - | | - -4 - X - 1 - Rinv - -2 -4 - Linv - 1 - Y - -2 - | | - -3 -3 - - -2 -2 - | | - -5 - X - 1 - Rinv - -3 -5 - Linv - 1 - Y - -3 - | ╲ | ╲ - -4 -1 -4 -1 + -2 + | + -5 - X - 1 - Rinv - -3 + | ╲ + -4 -1 ``` """ -function _fixgauge_benvXY( - X::PEPSOrth, Y::PEPSOrth, Linv::MPSBondTensor, Rinv::MPSBondTensor, - ) - @plansor X[-1 -2 -3 -4] := X[-1 1 -3 -4] * Rinv[1; -2] - @plansor Y[-1 -2 -3 -4] := Y[-1 -2 -3 1] * Linv[1; -4] - return X, Y +function _fixgauge_benvX(X::PEPSOrth, Rinv::MPSBondTensor) + return @plansor X[-1 -2 -3 -4] := X[-1 1 -3 -4] * Rinv[1; -2] end -function _fixgauge_benvXY( - X::PEPOOrth, Y::PEPOOrth, Linv::MPSBondTensor, Rinv::MPSBondTensor, - ) - @plansor X[-1 -2 -3 -4 -5] := X[-1 -2 1 -4 -5] * Rinv[1; -3] - @plansor Y[-1 -2 -3 -4 -5] := Y[-1 -2 -3 -4 1] * Linv[1; -5] - return X, Y +function _fixgauge_benvX(X::PEPSTensor, Rinv::MPSBondTensor) + return @plansor X[-1; -2 -3 -4 -5] := X[-1; -2 1 -4 -5] * Rinv[1; -3] +end +function _fixgauge_benvX(X::PEPOOrth, Rinv::MPSBondTensor) + return @plansor X[-1 -2 -3 -4 -5] := X[-1 -2 1 -4 -5] * Rinv[1; -3] +end +function _fixgauge_benvX(X::PEPOTensor, Rinv::MPSBondTensor) + return @plansor X[-1 -2; -3 -4 -5 -6] := X[-1 -2; -3 1 -5 -6] * Rinv[1; -4] +end + +""" +Apply the gauge transformation `Linv` for `Z` +``` + ┌-----------------------┐ + └---Z--- ---Linv--(Y)--┘ + ↓ +``` +to `Y`. For example, when `Y` is a `PEPSTensor`, +``` + -2 + | + -5 - Linv - 1 - Y - -3 + | ╲ + -4 -1 +``` +""" +function _fixgauge_benvY(Y::PEPSOrth, Linv::MPSBondTensor) + return @plansor Y[-1 -2 -3 -4] := Y[-1 -2 -3 1] * Linv[1; -4] +end +function _fixgauge_benvY(Y::PEPSTensor, Linv::MPSBondTensor) + return @plansor Y[-1; -2 -3 -4 -5] := Y[-1; -2 -3 -4 1] * Linv[1; -5] +end +function _fixgauge_benvY(Y::PEPOOrth, Linv::MPSBondTensor) + return @plansor Y[-1 -2 -3 -4 -5] := Y[-1 -2 -3 -4 1] * Linv[1; -5] +end +function _fixgauge_benvY(Y::PEPOTensor, Linv::MPSBondTensor) + return @plansor Y[-1 -2; -3 -4 -5 -6] := Y[-1 -2; -3 -4 -5 1] * Linv[1; -6] end diff --git a/src/algorithms/time_evolution/ntupdate.jl b/src/algorithms/time_evolution/ntupdate.jl index 3ac1507b5..7c4281762 100644 --- a/src/algorithms/time_evolution/ntupdate.jl +++ b/src/algorithms/time_evolution/ntupdate.jl @@ -79,7 +79,7 @@ function _ntu_iter( Nr, Nc = size(state) trunc = only(_get_cluster_trunc(alg.opt_alg.trunc, sites, (Nr, Nc))) alg′ = (@set alg.opt_alg.trunc = trunc) - return _bond_truncate(state, wts, Tuple(sites), alg′; gate) + return _bond_truncate(state, wts, Tuple(sites), (:first, :last), alg′; gate) end """ diff --git a/src/algorithms/time_evolution/ntupdate3site.jl b/src/algorithms/time_evolution/ntupdate3site.jl index 713c789fc..fa50cdb21 100644 --- a/src/algorithms/time_evolution/ntupdate3site.jl +++ b/src/algorithms/time_evolution/ntupdate3site.jl @@ -23,9 +23,13 @@ function _ntu_iter( # truncate each bond sequentially along the path info = (; fid = 1.0) - for (bondsites, trunc) in zip(zip(sites, Iterators.drop(sites, 1)), truncs) + nbond = length(sites) - 1 + for (i, bondsites) in enumerate(zip(sites, Iterators.drop(sites, 1))) + trunc = truncs[i] alg′ = (@set alg.opt_alg.trunc = trunc) - state, wts, info′ = _bond_truncate(state, wts, bondsites, alg′) + stype1 = (i == 1) ? :first : :middle + stype2 = (i == nbond) ? :last : :middle + state, wts, info′ = _bond_truncate(state, wts, bondsites, (stype1, stype2), alg′) # record the worst fidelity (info′.fid < info.fid) && (info = info′) end @@ -35,10 +39,14 @@ end """ Truncate a nearest neighbor bond between `site1` and `site2` after rotating the bond to standard x direction `A ← B`. + +`bondtype` takes values in (1, 2, 3), meaning that the current bond is +(the first, a middle, the last) bond in the updated cluster. """ function _bond_truncate( state::InfiniteState, wts::SUWeight, (site1, site2)::NTuple{2, CartesianIndex{2}}, + (stype1, stype2)::NTuple{2, Symbol}, alg::NeighbourUpdate; gate::Union{NNGate, Nothing} = nothing ) # rotate bond to standard x direction `A ← B` @@ -54,14 +62,26 @@ function _bond_truncate( A, B = state2[row, col], state2[row, cp1] # create bond environment - a, X = bond_tensor_first(A; trunc = trunctol(; rtol = 1.0e-12)) - b, Y = bond_tensor_last(B; trunc = trunctol(; rtol = 1.0e-12)) + qrtrunc = trunctol(; rtol = 1.0e-12) + a, X = if stype1 == :first + bond_tensor_first(A; trunc = qrtrunc) + else + @assert stype1 == :middle + bond_tensor_midnext(A; trunc = qrtrunc) + end + b, Y = if stype2 == :last + bond_tensor_last(B; trunc = qrtrunc) + else + @assert stype2 == :middle + bond_tensor_midprev(B; trunc = qrtrunc) + end benv = bondenv_ntu(row, col, X, Y, state2, alg.bondenv_alg) @debug "cond(benv) before gauge fix: $(LinearAlgebra.cond(benv))" if alg.fixgauge Z = positive_approx(benv) Z, a, b, (Linv, Rinv) = fixgauge_benv(Z, a, b) - X, Y = _fixgauge_benvXY(X, Y, Linv, Rinv) + X = _fixgauge_benvX(X, Rinv) + Y = _fixgauge_benvY(Y, Linv) benv = Z' * Z @debug "cond(L) = $(LinearAlgebra.cond(Linv)); cond(R): $(LinearAlgebra.cond(Rinv))" @debug "cond(benv) after gauge fix: $(LinearAlgebra.cond(benv))" @@ -70,14 +90,20 @@ function _bond_truncate( # (optional) apply the NN gate without truncation if !(gate === nothing) a, s, b, = _apply_gate(a, b, gate, truncerror(; atol = 1.0e-15)) + end + a, s, b, info = bond_truncate(a, b, benv, alg.opt_alg) + + A = if stype1 == :first + undo_bond_tensor_first(a, X) else - a = permute(a, ((1, 2), (3,))) - b = permute(b, ((1,), (2, 3))) + undo_bond_tensor_midnext(a, X) + end + B = if stype2 == :last + undo_bond_tensor_last(b, Y) + else + undo_bond_tensor_midprev(b, Y) end - a, s, b, info = bond_truncate(a, b, benv, alg.opt_alg) - A = undo_bond_tensor_first(a, X) - B = undo_bond_tensor_last(b, Y) normalize!(A, Inf) normalize!(B, Inf) normalize!(s, Inf) diff --git a/src/algorithms/truncation/bond_tensor.jl b/src/algorithms/truncation/bond_tensor.jl index 33df63d79..6b11fea2b 100644 --- a/src/algorithms/truncation/bond_tensor.jl +++ b/src/algorithms/truncation/bond_tensor.jl @@ -113,3 +113,101 @@ function undo_bond_tensor_last(b::MPSTensor, Y::PEPOOrth; gate_ax::Integer = 1) return @tensor A[-1 -2; -3 -4 -5 -6] := b[-6 -2 1] * Y[-1 -3 -4 -5 1] end end + +""" +Given a middle tensor `A` in the cluster acted on by a gate, +obtain reduced tensor on its next bond. + +For PEPSTensor, +``` + 2 + | + 5 - X ← 3 1 ← a - 3 + | ↘ ↘ + 4 1 (2) +``` +For PEPOTensor, +``` + 2 3 + ↘ | + 6 - X ← 4 1 ← a - 3 + | ↘ ↘ + 5 1 (2) +``` + +Here the physical leg on `a` is an auxiliary trivial leg. +""" +function bond_tensor_midnext(A::PEPSTensor; kwargs...) + X, a = left_orth!(permute(A, ((1, 2, 4, 5), (3,)); copy = true); kwargs...) + X = permute(X, ((1,), (2, 5, 3, 4))) + a = insertrightunit(a, 1) + return a, X +end +function bond_tensor_midnext(A::PEPOTensor; kwargs...) + X, a = left_orth!(permute(A, ((1, 2, 3, 5, 6), (4,)); copy = true); kwargs...) + X = permute(X, ((1, 2), (3, 6, 4, 5))) + a = insertrightunit(a, 1) + return a, X +end + +""" +Undo the decomposition in `bond_tensor_midprev`. +""" +function undo_bond_tensor_midnext(a::MPSTensor, X::PEPSTensor) + a = removeunit(a, 2) + return @tensor A[-1; -2 -3 -4 -5] := X[-1; -2 1 -4 -5] * a[1; -3] +end +function undo_bond_tensor_midnext(a::MPSTensor, X::PEPOTensor) + a = removeunit(a, 2) + return @tensor A[-1 -2; -3 -4 -5 -6] := X[-1 -2; -3 1 -5 -6] * a[1; -4] +end + +""" +Given a middle tensor `A` in the cluster acted on by a gate, +obtain reduced tensor on its previous bond. + +For PEPSTensor, +``` + 2 + | + 1 - b → 3 5 → Y - 3 + ↘ | ↘ + (2) 4 1 +``` +For PEPOTensor, +``` + 2 3 + ↘ | + 1 - b → 3 6 → Y - 4 + ↘ | ↘ + (2) 5 1 +``` + +Here the physical leg on `a` is an auxiliary trivial leg. +""" +function bond_tensor_midprev(A::PEPSTensor; kwargs...) + Y, b = left_orth!(permute(A, ((1, 2, 3, 4), (5,)); copy = true); kwargs...) + Y = permute(Y, ((1,), (2, 3, 4, 5))) + b = permute(b, ((2,), (1,))) + b = insertrightunit(b, 1) + return b, Y +end +function bond_tensor_midprev(A::PEPOTensor; kwargs...) + Y, b = left_orth!(permute(A, ((1, 2, 3, 4, 5), (6,)); copy = true); kwargs...) + Y = permute(Y, ((1, 2), (3, 4, 5, 6))) + b = permute(b, ((2,), (1,))) + b = insertrightunit(b, 1) + return b, Y +end + +""" +Undo the decomposition in `bond_tensor_midprev`. +""" +function undo_bond_tensor_midprev(b::MPSTensor, Y::PEPSTensor) + b = removeunit(b, 2) + return @tensor A[-1; -2 -3 -4 -5] := b[-5; 1] * Y[-1; -2 -3 -4 1] +end +function undo_bond_tensor_midprev(b::MPSTensor, Y::PEPOTensor) + b = removeunit(b, 2) + return @tensor A[-1 -2; -3 -4 -5 -6] := b[-6; 1] * Y[-1 -2; -3 -4 -5 1] +end diff --git a/test/bondenv/benv_gaugefix.jl b/test/bondenv/benv_gaugefix.jl index bdd5cecf7..0c5e2491d 100644 --- a/test/bondenv/benv_gaugefix.jl +++ b/test/bondenv/benv_gaugefix.jl @@ -37,7 +37,8 @@ for V1 in Vs, V2 in Vs, V3 in Vs @tensor half2[:] := Z2[-1; 1 3] * a2[1; -2 2] * b2[2 -3; 3] @test half ≈ half2 # test gauge transformation of X, Y - X2, Y2 = PEPSKit._fixgauge_benvXY(X, Y, Linv, Rinv) + X2 = _fixgauge_benvX(X, Rinv) + Y2 = _fixgauge_benvY(Y, Linv) @tensor Z2_[p; Xe Yw] := Z0[p; Xn Xs Xw Yn Ye Ys] * X2[Xn Xe Xs Xw] * Y2[Yn Ye Ys Yw] @test Z2 ≈ Z2_ end diff --git a/test/bondenv/benv_ntu.jl b/test/bondenv/benv_ntu.jl index 15c1654c4..6b5e11c81 100644 --- a/test/bondenv/benv_ntu.jl +++ b/test/bondenv/benv_ntu.jl @@ -41,7 +41,8 @@ function test_ntu_env( # verify gauge transformation of X, Y @tensor a2b2[DX DY; da db] := a2[DX da D] * b2[D db DY] nrm2 = PEPSKit.inner_prod(benv2, a2b2, a2b2) - X2, Y2 = PEPSKit._fixgauge_benvXY(X, Y, Linv, Rinv) + X2 = _fixgauge_benvX(X, Rinv) + Y2 = _fixgauge_benvY(Y, Linv) benv3 = PEPSKit.bondenv_ntu(row, col, X2, Y2, state, env_alg) benv3 *= norm(benv2, Inf) nrm3 = PEPSKit.inner_prod(benv3, a2b2, a2b2) From 931a575b5615ead021d5754832bcdaf03799f9bf Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Sun, 26 Apr 2026 10:53:54 +0800 Subject: [PATCH 27/41] Fix bondenv tests --- test/bondenv/benv_gaugefix.jl | 10 +++++----- test/bondenv/benv_ntu.jl | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/test/bondenv/benv_gaugefix.jl b/test/bondenv/benv_gaugefix.jl index 0c5e2491d..4be536b0d 100644 --- a/test/bondenv/benv_gaugefix.jl +++ b/test/bondenv/benv_gaugefix.jl @@ -30,15 +30,15 @@ for V1 in Vs, V2 in Vs, V3 in Vs ↓ ↓ ↓ -1 -2 -3 =# - a = randn(ComplexF64, V1 ← Vphy' ⊗ V2) + a = randn(ComplexF64, V1 ⊗ Vphy ← V2) b = randn(ComplexF64, V2 ⊗ Vphy ← V3) - @tensor half[:] := Z[-1; 1 3] * a[1; -2 2] * b[2 -3; 3] + @tensor half[:] := Z[-1; 1 3] * a[1 -2; 2] * b[2 -3; 3] Z2, a2, b2, (Linv, Rinv) = PEPSKit.fixgauge_benv(Z, a, b) - @tensor half2[:] := Z2[-1; 1 3] * a2[1; -2 2] * b2[2 -3; 3] + @tensor half2[:] := Z2[-1; 1 3] * a2[1 -2; 2] * b2[2 -3; 3] @test half ≈ half2 # test gauge transformation of X, Y - X2 = _fixgauge_benvX(X, Rinv) - Y2 = _fixgauge_benvY(Y, Linv) + X2 = PEPSKit._fixgauge_benvX(X, Rinv) + Y2 = PEPSKit._fixgauge_benvY(Y, Linv) @tensor Z2_[p; Xe Yw] := Z0[p; Xn Xs Xw Yn Ye Ys] * X2[Xn Xe Xs Xw] * Y2[Yn Ye Ys Yw] @test Z2 ≈ Z2_ end diff --git a/test/bondenv/benv_ntu.jl b/test/bondenv/benv_ntu.jl index 6b5e11c81..24cb5ab0c 100644 --- a/test/bondenv/benv_ntu.jl +++ b/test/bondenv/benv_ntu.jl @@ -41,8 +41,8 @@ function test_ntu_env( # verify gauge transformation of X, Y @tensor a2b2[DX DY; da db] := a2[DX da D] * b2[D db DY] nrm2 = PEPSKit.inner_prod(benv2, a2b2, a2b2) - X2 = _fixgauge_benvX(X, Rinv) - Y2 = _fixgauge_benvY(Y, Linv) + X2 = PEPSKit._fixgauge_benvX(X, Rinv) + Y2 = PEPSKit._fixgauge_benvY(Y, Linv) benv3 = PEPSKit.bondenv_ntu(row, col, X2, Y2, state, env_alg) benv3 *= norm(benv2, Inf) nrm3 = PEPSKit.inner_prod(benv3, a2b2, a2b2) From 14c2fdeabc73fd0a3fc3c0d87ec856c3dd3a4029 Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Sun, 3 May 2026 14:40:54 +0800 Subject: [PATCH 28/41] Get rid of ncon in benv_tensor --- Project.toml | 6 +- src/PEPSKit.jl | 1 + .../contractions/bondenv/benv_tools.jl | 89 +++++++++---------- src/utility/util.jl | 14 +++ 4 files changed, 62 insertions(+), 48 deletions(-) diff --git a/Project.toml b/Project.toml index 2dec89104..cb5357fc9 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "PEPSKit" uuid = "52969e89-939e-4361-9b68-9bc7cde4bdeb" -authors = ["Paul Brehmer", "Lander Burgelman", "Zhengyuan Yue", "Lukas Devos "] version = "0.7.0" +authors = ["Paul Brehmer", "Lander Burgelman", "Zhengyuan Yue", "Lukas Devos "] [deps] Accessors = "7d9f7c33-5ae7-4f3b-8dc6-eff91059b697" @@ -22,6 +22,7 @@ Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" TensorKit = "07d1fe3e-3e46-537d-9eac-e9e13d0d4cec" TensorOperations = "6aa20fa7-93e2-5fca-9bc0-fbd0db3c71a2" +TupleTools = "9d95972d-f1c8-5527-a6e0-b4b365fa01f6" VectorInterface = "409d34a3-91d5-4945-b6ec-7529ddf182d8" Zygote = "e88e6eb3-aa80-5325-afca-941959d7151f" @@ -29,7 +30,6 @@ Zygote = "e88e6eb3-aa80-5325-afca-941959d7151f" Accessors = "0.1" ChainRulesCore = "1.0" ChainRulesTestUtils = "1.13" -SafeTestsets = "0.1" Compat = "3.46, 4.2" DocStringExtensions = "0.9.3" FiniteDifferences = "0.12" @@ -44,10 +44,12 @@ OptimKit = "0.4" Printf = "1" QuadGK = "2.11.1" Random = "1" +SafeTestsets = "0.1" Statistics = "1" TensorKit = "0.16.2" TensorOperations = "5" TestExtras = "0.3" +TupleTools = "1.6.0" VectorInterface = "0.4, 0.5" Zygote = "0.6, 0.7" julia = "1.10" diff --git a/src/PEPSKit.jl b/src/PEPSKit.jl index cf2e89377..9b81db74c 100644 --- a/src/PEPSKit.jl +++ b/src/PEPSKit.jl @@ -25,6 +25,7 @@ using KrylovKit: Lanczos, BlockLanczos using TensorOperations, OptimKit using ChainRulesCore, Zygote using LoggingExtras +import TupleTools using MPSKit using MPSKit: MPSTensor, MPOTensor, GenericMPSTensor, MPSBondTensor, ProductTransferMatrix diff --git a/src/algorithms/contractions/bondenv/benv_tools.jl b/src/algorithms/contractions/bondenv/benv_tools.jl index 8b2ff362e..3aa932b7c 100644 --- a/src/algorithms/contractions/bondenv/benv_tools.jl +++ b/src/algorithms/contractions/bondenv/benv_tools.jl @@ -1,5 +1,5 @@ -#= -The construction of bond environment for Neighborhood Tensor Update (NTU) +#= +The construction of bond environment for Neighborhood Tensor Update (NTU) is adapted from YASTN (https://github.com/yastn/yastn). Copyright 2024 The YASTN Authors. All Rights Reserved. Licensed under the Apache License, Version 2.0 @@ -34,15 +34,16 @@ function collect_neighbors( end """ - benv_tensor(ket::PEPSTensor, bra::PEPSTensor, open_axs::Vector{Int}) - benv_tensor(ket::PEPSTensor, bra::PEPSTensor, open_axs::Vector{Int}, hairs::Vector{H}) where {T, H <: Union{Nothing, Hair}} + benv_tensor(ket::PEPSTensor, bra::PEPSTensor, open_axs::NTuple{N, Int}) where {N} + benv_tensor(ket::PEPSTensor, bra::PEPSTensor, open_axs::NTuple{N, Int}, hairs::NTuple{Nh, H}) where {N, Nh, H <: Union{Nothing, Hair}} -Contract the physical axes and the virtual axes of `ket` with `bra` to obtain the tensor on the boundary of the bond environment. -Virtual axes specified by `open_axs` (in ascending order) are not contracted. +Contract the physical axes and the virtual axes of `ket` with `bra` to obtain the tensor on the boundary of the bond environment. +Virtual axes specified by `open_axs` (in ascending order) are not contracted. +Hair tensors can be inserted on contracted legs between `ket` and `bra`. # Examples -- West "hair" tensor (`open_axs = [EAST]`) +- West "hair" tensor (`open_axs = (EAST,)`) ``` ╱| ┌-----ket----- 2 @@ -52,7 +53,7 @@ Virtual axes specified by `open_axs` (in ascending order) are not contracted. └---|-bra----- 1 |╱ ``` -- Northwest corner tensor (`open_axs = [EAST, SOUTH]`, `hairs = [h, nothing]`) +- Northwest corner tensor (`open_axs = (EAST, SOUTH)`, `hairs = (h, nothing)`) ``` ╱| ┌-----ket----- 2 @@ -64,7 +65,7 @@ Virtual axes specified by `open_axs` (in ascending order) are not contracted. ╱ 3 ``` -- West edge tensor (`open_axs = [1, 2, 3]`) +- West edge tensor (`open_axs = (NORTH, EAST, SOUTH)`) ``` 2 ╱ @@ -78,39 +79,35 @@ Virtual axes specified by `open_axs` (in ascending order) are not contracted. ``` """ function benv_tensor( - ket::PEPSTensor, bra::PEPSTensor, open_axs::Vector{Int} - ) + ket::PEPSTensor, bra::PEPSTensor, open_axs::NTuple{N, Int} + ) where {N} # no hair tensors to be attached to virtual legs - return benv_tensor(ket, bra, open_axs, fill(nothing, 4 - length(open_axs))) + return benv_tensor(ket, bra, open_axs, ntuple(Returns(nothing), 4 - N)) end function benv_tensor( - ket::PEPSTensor, bra::PEPSTensor, open_axs::Vector{Int}, hairs::Vector{H} - ) where {H <: Union{Nothing, Hair}} - @assert length(hairs) == 4 - length(open_axs) - ket2, nax = copy(ket), numind(ket) - axs, open_axs2 = (2:5), open_axs .+ 1 - # contract with hair tensors - hair_axs = Tuple(ax for ax in axs if ax ∉ open_axs2) + ket::PEPSTensor, bra::PEPSTensor, + open_vaxs::NTuple{N, Int}, hairs::NTuple{Nh, H} + ) where {N, Nh, H <: Union{Nothing, Hair}} + @assert 1 <= N <= 3 && Nh == 4 - N + # axes to be contracted + open_axs = open_vaxs .+ 1 + hair_axs = Tuple(ax for ax in 2:5 if ax ∉ open_axs) + # attach hairs to ket + axes, ket2 = ntuple(identity, Val(5)), twistdual(ket, 1) for (h, ax) in zip(hairs, hair_axs) - if h === nothing - twistdual!(ket2, ax) - continue - end - # ensure the hair doesn't change the virtual spaces - @assert space(h, 1) == space(h, 2)' - ket_indices = collect(-1:-1:-nax) - ket_indices[ax] = 1 - ket2 = ncon([h, ket2], [[-ax, 1], ket_indices]) + twistdual!(ket2, ax) + h === nothing && continue + axes, biperm = _permute_to_first(axes, ax) + ket2 = permute(h, ((1,), (2,))) * permute(ket2, biperm) end + perm_back = invperm(axes) + ket2 = permute(ket2, perm_back) # combine bra and ket - indexlist = [-collect(1:2:(2 * nax)), -collect(2:2:(2 * nax))] - for ax in 1:nax - if ax ∉ open_axs2 - indexlist[1][ax] = indexlist[2][ax] = ax - end - end - twistdual!(ket2, 1) - return ncon([bra, ket2], indexlist, [true, false]) + cont_axs = (1, hair_axs...) + pbra = (open_axs, cont_axs) + pket = (cont_axs, open_axs) + pbraket = (ntuple(j -> isodd(j) ? (j + 1) ÷ 2 : (j ÷ 2) + N, 2N), ()) + return tensorcontract(bra, pbra, true, ket2, pket, false, pbraket) end #= Free axes of different boundary tensors @@ -132,15 +129,15 @@ end | H_s =# -const open_axs_hair = Dict(:n => [SOUTH], :e => [WEST], :s => [NORTH], :w => [EAST]) +const open_axs_hair = Dict(:n => (SOUTH,), :e => (WEST,), :s => (NORTH,), :w => (EAST,)) const open_axs_cor = Dict( - :nw => [EAST, SOUTH], :ne => [SOUTH, WEST], :se => [NORTH, WEST], :sw => [NORTH, EAST] + :nw => (EAST, SOUTH), :ne => (SOUTH, WEST), :se => (NORTH, WEST), :sw => (NORTH, EAST) ) const open_axs_edge = Dict( - :n => [EAST, SOUTH, WEST], - :e => [NORTH, SOUTH, WEST], - :s => [NORTH, EAST, WEST], - :w => [NORTH, EAST, SOUTH], + :n => (EAST, SOUTH, WEST), + :e => (NORTH, SOUTH, WEST), + :s => (NORTH, EAST, WEST), + :w => (NORTH, EAST, SOUTH), ) # construction of hairs @@ -148,7 +145,7 @@ for (dir, open_axs) in open_axs_hair fname = Symbol("hair_", dir) @eval begin $(fname)(ket) = benv_tensor(ket, ket, $open_axs) - $(fname)(ket, h1, h2, h3) = benv_tensor(ket, ket, $open_axs, [h1, h2, h3]) + $(fname)(ket, h1, h2, h3) = benv_tensor(ket, ket, $open_axs, (h1, h2, h3)) end end @@ -157,7 +154,7 @@ for (dir, open_axs) in open_axs_cor fname = Symbol("cor_", dir) @eval begin $(fname)(ket) = benv_tensor(ket, ket, $open_axs) - $(fname)(ket, h1, h2) = benv_tensor(ket, ket, $open_axs, [h1, h2]) + $(fname)(ket, h1, h2) = benv_tensor(ket, ket, $open_axs, (h1, h2)) end end @@ -166,7 +163,7 @@ for (dir, open_axs) in open_axs_edge fname = Symbol("edge_", dir) @eval begin $(fname)(ket) = benv_tensor(ket, ket, $open_axs) - $(fname)(ket, h) = benv_tensor(ket, ket, $open_axs, [h]) + $(fname)(ket, h) = benv_tensor(ket, ket, $open_axs, (h,)) end end @@ -200,7 +197,7 @@ Enlarge the southeast corner -5/-6 ═════ Y ══ D1 ═══ er ║ ║ D2 D3 - ║ ║ + ║ ║ -7/-8 ═════ eb ═ D4 ══ cbr ``` """ diff --git a/src/utility/util.jl b/src/utility/util.jl index 3fa7e0cc1..9a27c6fc6 100644 --- a/src/utility/util.jl +++ b/src/utility/util.jl @@ -215,3 +215,17 @@ function random_dual!(Vs::AbstractMatrix{E}; p = 0.7) where {E <: ElementarySpac end return Vs end + +""" + _permute_to_first(axes::NTuple{N, Int}, ax::Int) where {N} + +Returns `(1, 2, ..., N)` but with `ax` moved to the front, +and the corresponding permutation for `axes` (with `ax` as the only codomain index). +""" +function _permute_to_first(axes::NTuple{N, Int}, ax::Int) where {N} + domain_axes = TupleTools.deleteat(ntuple(identity, N), ax) + q = invperm(axes) + biperm = ((q[ax],), map(i -> q[i], domain_axes)) + new_axes = (ax, ntuple(i -> axes[biperm[2][i]], N - 1)...) + return new_axes, biperm +end From f8aaff08386fa000599acafc02bb6022ef3b8e7a Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Sun, 3 May 2026 15:19:34 +0800 Subject: [PATCH 29/41] More optimizations for benv_tensor --- .../contractions/bondenv/benv_tools.jl | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/algorithms/contractions/bondenv/benv_tools.jl b/src/algorithms/contractions/bondenv/benv_tools.jl index 3aa932b7c..e59596a59 100644 --- a/src/algorithms/contractions/bondenv/benv_tools.jl +++ b/src/algorithms/contractions/bondenv/benv_tools.jl @@ -86,28 +86,31 @@ function benv_tensor( end function benv_tensor( ket::PEPSTensor, bra::PEPSTensor, - open_vaxs::NTuple{N, Int}, hairs::NTuple{Nh, H} - ) where {N, Nh, H <: Union{Nothing, Hair}} + open_vaxs::NTuple{N, Int}, hairs::NTuple{Nh, Union{Nothing, H}} + ) where {N, Nh, H <: Hair} @assert 1 <= N <= 3 && Nh == 4 - N - # axes to be contracted open_axs = open_vaxs .+ 1 + # axes to be contracted hair_axs = Tuple(ax for ax in 2:5 if ax ∉ open_axs) # attach hairs to ket - axes, ket2 = ntuple(identity, Val(5)), twistdual(ket, 1) + ket = twistdual(ket, 1) + axes = ntuple(identity, Val(5)) for (h, ax) in zip(hairs, hair_axs) - twistdual!(ket2, ax) + twistdual!(ket, ax) h === nothing && continue axes, biperm = _permute_to_first(axes, ax) - ket2 = permute(h, ((1,), (2,))) * permute(ket2, biperm) + ket = permute(h, ((1,), (2,))) * permute(ket, biperm) end perm_back = invperm(axes) - ket2 = permute(ket2, perm_back) # combine bra and ket cont_axs = (1, hair_axs...) pbra = (open_axs, cont_axs) - pket = (cont_axs, open_axs) + pket = ( + map(p -> perm_back[p], cont_axs), + map(p -> perm_back[p], open_axs), + ) pbraket = (ntuple(j -> isodd(j) ? (j + 1) ÷ 2 : (j ÷ 2) + N, 2N), ()) - return tensorcontract(bra, pbra, true, ket2, pket, false, pbraket) + return tensorcontract(bra, pbra, true, ket, pket, false, pbraket) end #= Free axes of different boundary tensors From 907931648cfde79fe5a0a6d907d6c6fa5d485fa4 Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Mon, 4 May 2026 10:16:41 +0800 Subject: [PATCH 30/41] Improve simple update performance --- .github/dependabot.yml | 13 +- .github/workflows/CompatHelper.yml | 44 -- .github/workflows/Documentation.yml | 15 +- .github/workflows/Tests.yml | 35 +- Project.toml | 21 +- docs/make.jl | 1 + .../3d_ising_partition_function/index.md | 161 +++---- .../3d_ising_partition_function/main.ipynb | 125 +++--- docs/src/examples/bose_hubbard/index.md | 8 +- docs/src/examples/bose_hubbard/main.ipynb | 144 ++++--- docs/src/examples/fermi_hubbard/index.md | 170 ++++---- docs/src/examples/fermi_hubbard/main.ipynb | 124 +++--- docs/src/examples/j1j2_su/index.md | 398 +++++++++++++----- docs/src/examples/j1j2_su/main.ipynb | 136 +++--- docs/src/examples/xxz/index.md | 176 ++++---- docs/src/examples/xxz/main.ipynb | 104 ++--- docs/src/man/precompilation.md | 5 +- examples/3d_ising_partition_function/main.jl | 3 +- examples/Cache.toml | 8 +- examples/bose_hubbard/main.jl | 10 +- examples/fermi_hubbard/main.jl | 2 +- examples/j1j2_su/main.jl | 12 +- examples/xxz/main.jl | 2 +- src/Defaults.jl | 54 ++- src/PEPSKit.jl | 6 +- src/algorithms/bp/gaugefix.jl | 2 +- src/algorithms/contractions/absorb_weight.jl | 100 +++++ .../contractions/bondenv/als_solve.jl | 43 +- .../contractions/bondenv/benv_tools.jl | 2 +- .../contractions/ctmrg/enlarge_corner.jl | 24 +- src/algorithms/contractions/ctmrg/expr.jl | 159 ++++++- .../contractions/ctmrg/fullinf_env.jl | 236 +++++------ .../contractions/ctmrg/halfinf_env.jl | 150 ++++--- .../contractions/ctmrg/projector.jl | 190 ++++++++- .../contractions/ctmrg/renormalize_corner.jl | 2 +- src/algorithms/ctmrg/c4v.jl | 19 +- src/algorithms/ctmrg/ctmrg.jl | 31 +- src/algorithms/ctmrg/gaugefix.jl | 110 ++--- src/algorithms/ctmrg/projectors.jl | 70 ++- src/algorithms/ctmrg/sequential.jl | 12 +- src/algorithms/ctmrg/simultaneous.jl | 19 +- src/algorithms/ctmrg/sparse_environments.jl | 127 +++++- .../fixed_point_differentiation.jl | 139 +----- .../optimization/peps_optimization.jl | 5 +- src/algorithms/time_evolution/apply_gate.jl | 2 +- src/algorithms/time_evolution/apply_mpo.jl | 36 +- src/algorithms/time_evolution/get_cluster.jl | 171 ++++++++ src/algorithms/time_evolution/ntupdate.jl | 2 +- .../time_evolution/ntupdate3site.jl | 13 +- src/algorithms/time_evolution/simpleupdate.jl | 208 +++++---- .../time_evolution/simpleupdate3site.jl | 189 +-------- src/algorithms/time_evolution/time_evolve.jl | 10 +- src/algorithms/toolbox.jl | 2 +- src/algorithms/truncation/bond_truncation.jl | 12 +- .../truncation/truncationschemes.jl | 63 +++ src/environments/suweight.jl | 82 +--- src/networks/local_sandwich.jl | 8 + src/operators/infinitepepo.jl | 6 +- src/utility/eigh.jl | 187 ++++---- src/utility/qr.jl | 45 +- src/utility/svd.jl | 207 ++++----- src/utility/util.jl | 31 ++ test/Project.toml | 34 ++ test/bondenv/benv_ctm.jl | 15 +- test/bondenv/bond_truncate.jl | 3 +- test/bp/gaugefix.jl | 12 +- test/ctmrg/contractions.jl | 235 +++++++++-- test/ctmrg/fixed_iterscheme.jl | 118 +++--- test/ctmrg/flavors.jl | 6 +- test/ctmrg/gaugefix.jl | 4 +- test/ctmrg/jacobian_real_linear.jl | 27 +- test/ctmrg/pepo.jl | 2 +- test/ctmrg/unitcell.jl | 13 +- test/examples/bose_hubbard.jl | 2 +- test/examples/j1j2_model.jl | 2 +- test/gradients/c4v_ctmrg_gradients.jl | 54 +-- test/gradients/ctmrg_gradients.jl | 27 +- test/runtests.jl | 171 +------- test/timeevol/cluster_projectors.jl | 126 +++++- test/timeevol/cluster_tools.jl | 114 ----- test/timeevol/sitedep_truncation.jl | 14 +- test/utility/eigh_wrapper.jl | 19 +- test/utility/svd_wrapper.jl | 16 +- 83 files changed, 3020 insertions(+), 2485 deletions(-) delete mode 100644 .github/workflows/CompatHelper.yml create mode 100644 src/algorithms/contractions/absorb_weight.jl create mode 100644 src/algorithms/time_evolution/get_cluster.jl create mode 100644 test/Project.toml delete mode 100644 test/timeevol/cluster_tools.jl diff --git a/.github/dependabot.yml b/.github/dependabot.yml index ff6499d68..a49032f8a 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,7 +1,14 @@ -# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates version: 2 updates: - package-ecosystem: "github-actions" - directory: "/" # Location of package manifests + directory: "/" schedule: - interval: "weekly" \ No newline at end of file + interval: "weekly" + - package-ecosystem: "julia" + directory: "/" + schedule: + interval: "weekly" + groups: # uncomment to group all julia package updates into a single PR + all-julia-packages: + patterns: + - "*" diff --git a/.github/workflows/CompatHelper.yml b/.github/workflows/CompatHelper.yml deleted file mode 100644 index 5f4277983..000000000 --- a/.github/workflows/CompatHelper.yml +++ /dev/null @@ -1,44 +0,0 @@ -name: CompatHelper -on: - schedule: - - cron: 0 0 * * * - workflow_dispatch: -permissions: - contents: write - pull-requests: write -jobs: - CompatHelper: - runs-on: ubuntu-latest - steps: - - name: Check if Julia is already available in the PATH - id: julia_in_path - run: which julia - continue-on-error: true - - name: Install Julia, but only if it is not already available in the PATH - uses: julia-actions/setup-julia@v2 - with: - version: '1' - arch: ${{ runner.arch }} - if: steps.julia_in_path.outcome != 'success' - - name: "Add the General registry via Git" - run: | - import Pkg - ENV["JULIA_PKG_SERVER"] = "" - Pkg.Registry.add("General") - shell: julia --color=yes {0} - - name: "Install CompatHelper" - run: | - import Pkg - name = "CompatHelper" - uuid = "aa819f21-2bde-4658-8897-bab36330d9b7" - version = "3" - Pkg.add(; name, uuid, version) - shell: julia --color=yes {0} - - name: "Run CompatHelper" - run: | - import CompatHelper - CompatHelper.main() - shell: julia --color=yes {0} - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - COMPATHELPER_PRIV: ${{ secrets.DOCUMENTER_KEY }} diff --git a/.github/workflows/Documentation.yml b/.github/workflows/Documentation.yml index 126ee01b1..ab2530efd 100644 --- a/.github/workflows/Documentation.yml +++ b/.github/workflows/Documentation.yml @@ -16,24 +16,15 @@ on: jobs: build: - runs-on: ${{ matrix.os }} - strategy: - matrix: - version: - - '1' - os: - - ubuntu-latest - arch: - - x64 + runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - uses: julia-actions/setup-julia@latest with: - version: ${{ matrix.version }} - arch: ${{ matrix.arch }} + version: 1 - name: Install dependencies run: julia --project=docs/ -e 'using Pkg; Pkg.develop(PackageSpec(path=pwd())); Pkg.instantiate()' - name: Build and deploy env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # For authentication with GitHub Actions token - run: julia --project=docs/ docs/make.jl \ No newline at end of file + run: julia --project=docs/ docs/make.jl diff --git a/.github/workflows/Tests.yml b/.github/workflows/Tests.yml index c88bccd70..e03fc30b0 100644 --- a/.github/workflows/Tests.yml +++ b/.github/workflows/Tests.yml @@ -1,51 +1,26 @@ -name: Tests +name: CI + on: push: branches: - - 'master' - 'main' - - 'release-' tags: '*' paths-ignore: - 'docs/**' pull_request: + types: [opened, synchronize, reopened, ready_for_review, converted_to_draft] workflow_dispatch: concurrency: group: ${{ github.workflow }}-${{ github.ref }} - # Cancel intermediate builds: only if it is a pull request build. cancel-in-progress: ${{ startsWith(github.ref, 'refs/pull/') }} jobs: tests: name: "Tests" - strategy: - fail-fast: false - matrix: - version: - - 'lts' # minimal supported version - - '1' - group: - - bp - - types - - ctmrg - - boundarymps - - gradients - - examples - - utility - - bondenv - - timeevol - - toolbox - os: - - ubuntu-latest - - macOS-latest - - windows-latest - uses: "QuantumKitHub/QuantumKitHubActions/.github/workflows/Tests.yml@main" + uses: "QuantumKitHub/QuantumKitHubActions/.github/workflows/TestGroups.yml@main" with: - group: "${{ matrix.group }}" - julia-version: "${{ matrix.version }}" - os: "${{ matrix.os }}" + fast: "${{ github.event.pull_request.draft == true }}" timeout-minutes: 120 secrets: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} - diff --git a/Project.toml b/Project.toml index cb5357fc9..0a29fbc72 100644 --- a/Project.toml +++ b/Project.toml @@ -3,6 +3,9 @@ uuid = "52969e89-939e-4361-9b68-9bc7cde4bdeb" version = "0.7.0" authors = ["Paul Brehmer", "Lander Burgelman", "Zhengyuan Yue", "Lukas Devos "] +[workspace] +projects = ["test", "docs"] + [deps] Accessors = "7d9f7c33-5ae7-4f3b-8dc6-eff91059b697" ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" @@ -29,38 +32,22 @@ Zygote = "e88e6eb3-aa80-5325-afca-941959d7151f" [compat] Accessors = "0.1" ChainRulesCore = "1.0" -ChainRulesTestUtils = "1.13" Compat = "3.46, 4.2" DocStringExtensions = "0.9.3" -FiniteDifferences = "0.12" KrylovKit = "0.9.5, 0.10" LinearAlgebra = "1" LoggingExtras = "1" MPSKit = "0.13.9" MPSKitModels = "0.4" -MatrixAlgebraKit = "0.6" +MatrixAlgebraKit = "0.6.5" OhMyThreads = "0.7, 0.8" OptimKit = "0.4" Printf = "1" -QuadGK = "2.11.1" Random = "1" -SafeTestsets = "0.1" Statistics = "1" TensorKit = "0.16.2" TensorOperations = "5" -TestExtras = "0.3" TupleTools = "1.6.0" VectorInterface = "0.4, 0.5" Zygote = "0.6, 0.7" julia = "1.10" - -[extras] -ChainRulesTestUtils = "cdddcdb0-9152-4a09-a978-84456f9df70a" -FiniteDifferences = "26cc04aa-876d-5657-8c51-4c34ba976000" -QuadGK = "1fd47b50-473d-5c70-9696-f719f8f3bcdc" -SafeTestsets = "1bc83da4-3b8d-516f-aca4-4fe02f6d838f" -Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" -TestExtras = "5ed8adda-3752-4e41-b88a-e8b09835ee3a" - -[targets] -test = ["Test", "TestExtras", "SafeTestsets", "ChainRulesTestUtils", "FiniteDifferences", "QuadGK"] diff --git a/docs/make.jl b/docs/make.jl index 965b10442..636330ec3 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -26,6 +26,7 @@ links = InterLinks( "MPSKitModels" => "https://quantumkithub.github.io/MPSKitModels.jl/dev/", # "Zygote" => "https://fluxml.ai/Zygote.jl/stable/", "ChainRulesCore" => "https://juliadiff.org/ChainRulesCore.jl/stable/", + "MatrixAlgebraKit" => "https://quantumkithub.github.io/MatrixAlgebraKit.jl/stable/", ) # explicitly set math engine diff --git a/docs/src/examples/3d_ising_partition_function/index.md b/docs/src/examples/3d_ising_partition_function/index.md index 21f6f5a20..cec367329 100644 --- a/docs/src/examples/3d_ising_partition_function/index.md +++ b/docs/src/examples/3d_ising_partition_function/index.md @@ -163,7 +163,8 @@ of this cost function. ````julia boundary_alg = SimultaneousCTMRG(; maxiter = 150, tol = 1.0e-8, verbosity = 1) rrule_alg = EigSolver(; - solver_alg = KrylovKit.Arnoldi(; maxiter = 30, tol = 1.0e-6, eager = true), iterscheme = :diffgauge + solver_alg = KrylovKit.Arnoldi(; maxiter = 30, tol = 1.0e-6, eager = true), + iterscheme = :fixed, ) T = InfinitePEPO(O) @@ -293,83 +294,83 @@ optimizer_alg = LBFGS(32; maxiter = 100, gradtol = 1.0e-5, verbosity = 3) ```` [ Info: LBFGS: initializing with f = -5.540733951820e-01, ‖∇f‖ = 7.7844e-01 -┌ Warning: CTMRG cancel 150: obj = +1.702942228759e+01 +1.443123137050e-07im err = 2.4386740933e-05 time = 1.04 sec -└ @ PEPSKit ~/repos/PEPSKit.jl/src/algorithms/ctmrg/ctmrg.jl:153 -[ Info: LBFGS: iter 1, Δt 4.96 s: f = -7.770809303692e-01, ‖∇f‖ = 3.1305e-02, α = 7.10e+02, m = 0, nfg = 7 -[ Info: LBFGS: iter 2, Δt 568.8 ms: f = -7.841115159615e-01, ‖∇f‖ = 2.0103e-02, α = 1.00e+00, m = 1, nfg = 1 -[ Info: LBFGS: iter 3, Δt 175.6 ms: f = -7.927057334845e-01, ‖∇f‖ = 2.3327e-02, α = 1.00e+00, m = 2, nfg = 1 -[ Info: LBFGS: iter 4, Δt 135.5 ms: f = -7.962897324757e-01, ‖∇f‖ = 2.2475e-02, α = 1.00e+00, m = 3, nfg = 1 -[ Info: LBFGS: iter 5, Δt 131.8 ms: f = -7.996749023744e-01, ‖∇f‖ = 7.0288e-03, α = 1.00e+00, m = 4, nfg = 1 -[ Info: LBFGS: iter 6, Δt 159.1 ms: f = -8.000821001207e-01, ‖∇f‖ = 1.2717e-03, α = 1.00e+00, m = 5, nfg = 1 -[ Info: LBFGS: iter 7, Δt 196.0 ms: f = -8.001106031252e-01, ‖∇f‖ = 1.3384e-03, α = 1.00e+00, m = 6, nfg = 1 -[ Info: LBFGS: iter 8, Δt 215.9 ms: f = -8.002622019964e-01, ‖∇f‖ = 2.4945e-03, α = 1.00e+00, m = 7, nfg = 1 -[ Info: LBFGS: iter 9, Δt 243.9 ms: f = -8.004505054484e-01, ‖∇f‖ = 2.9259e-03, α = 1.00e+00, m = 8, nfg = 1 -[ Info: LBFGS: iter 10, Δt 127.7 ms: f = -8.007649170868e-01, ‖∇f‖ = 1.7221e-03, α = 1.00e+00, m = 9, nfg = 1 -[ Info: LBFGS: iter 11, Δt 136.6 ms: f = -8.008760488382e-01, ‖∇f‖ = 2.2475e-03, α = 1.00e+00, m = 10, nfg = 1 -[ Info: LBFGS: iter 12, Δt 126.0 ms: f = -8.011008674672e-01, ‖∇f‖ = 1.5561e-03, α = 1.00e+00, m = 11, nfg = 1 -[ Info: LBFGS: iter 13, Δt 161.5 ms: f = -8.013170488565e-01, ‖∇f‖ = 1.1561e-03, α = 1.00e+00, m = 12, nfg = 1 -[ Info: LBFGS: iter 14, Δt 173.9 ms: f = -8.013730505450e-01, ‖∇f‖ = 7.1300e-04, α = 1.00e+00, m = 13, nfg = 1 -[ Info: LBFGS: iter 15, Δt 169.2 ms: f = -8.013886152636e-01, ‖∇f‖ = 2.8462e-04, α = 1.00e+00, m = 14, nfg = 1 -[ Info: LBFGS: iter 16, Δt 179.4 ms: f = -8.013946333330e-01, ‖∇f‖ = 2.7607e-04, α = 1.00e+00, m = 15, nfg = 1 -[ Info: LBFGS: iter 17, Δt 356.0 ms: f = -8.014080615636e-01, ‖∇f‖ = 3.6096e-04, α = 1.00e+00, m = 16, nfg = 1 -[ Info: LBFGS: iter 18, Δt 134.3 ms: f = -8.015095421688e-01, ‖∇f‖ = 1.9822e-03, α = 1.00e+00, m = 17, nfg = 1 -[ Info: LBFGS: iter 19, Δt 145.7 ms: f = -8.015784052508e-01, ‖∇f‖ = 1.8040e-03, α = 1.00e+00, m = 18, nfg = 1 -[ Info: LBFGS: iter 20, Δt 501.1 ms: f = -8.016945244238e-01, ‖∇f‖ = 2.9356e-03, α = 5.48e-01, m = 19, nfg = 3 -[ Info: LBFGS: iter 21, Δt 361.1 ms: f = -8.017619206832e-01, ‖∇f‖ = 1.1993e-03, α = 3.82e-01, m = 20, nfg = 2 -[ Info: LBFGS: iter 22, Δt 348.7 ms: f = -8.017977854941e-01, ‖∇f‖ = 6.0337e-04, α = 1.00e+00, m = 21, nfg = 1 -[ Info: LBFGS: iter 23, Δt 291.0 ms: f = -8.018087478343e-01, ‖∇f‖ = 3.7053e-04, α = 5.24e-01, m = 22, nfg = 2 -[ Info: LBFGS: iter 24, Δt 161.3 ms: f = -8.018127291733e-01, ‖∇f‖ = 3.0781e-04, α = 1.00e+00, m = 23, nfg = 1 -[ Info: LBFGS: iter 25, Δt 171.3 ms: f = -8.018164452111e-01, ‖∇f‖ = 2.9994e-04, α = 1.00e+00, m = 24, nfg = 1 -[ Info: LBFGS: iter 26, Δt 186.0 ms: f = -8.018247131297e-01, ‖∇f‖ = 3.6496e-04, α = 1.00e+00, m = 25, nfg = 1 -[ Info: LBFGS: iter 27, Δt 197.4 ms: f = -8.018396738228e-01, ‖∇f‖ = 5.4222e-04, α = 1.00e+00, m = 26, nfg = 1 -[ Info: LBFGS: iter 28, Δt 369.0 ms: f = -8.018574789038e-01, ‖∇f‖ = 2.7917e-04, α = 1.00e+00, m = 27, nfg = 1 -[ Info: LBFGS: iter 29, Δt 183.7 ms: f = -8.018645552239e-01, ‖∇f‖ = 1.2319e-04, α = 1.00e+00, m = 28, nfg = 1 -[ Info: LBFGS: iter 30, Δt 165.3 ms: f = -8.018655987357e-01, ‖∇f‖ = 8.6048e-05, α = 1.00e+00, m = 29, nfg = 1 -[ Info: LBFGS: iter 31, Δt 173.6 ms: f = -8.018675717547e-01, ‖∇f‖ = 8.8636e-05, α = 1.00e+00, m = 30, nfg = 1 -[ Info: LBFGS: iter 32, Δt 173.9 ms: f = -8.018703935281e-01, ‖∇f‖ = 2.6554e-04, α = 1.00e+00, m = 31, nfg = 1 -[ Info: LBFGS: iter 33, Δt 177.8 ms: f = -8.018747970386e-01, ‖∇f‖ = 2.7841e-04, α = 1.00e+00, m = 32, nfg = 1 -[ Info: LBFGS: iter 34, Δt 193.3 ms: f = -8.018775666443e-01, ‖∇f‖ = 1.8523e-04, α = 1.00e+00, m = 32, nfg = 1 -[ Info: LBFGS: iter 35, Δt 184.9 ms: f = -8.018785062445e-01, ‖∇f‖ = 2.0638e-04, α = 1.00e+00, m = 32, nfg = 1 -[ Info: LBFGS: iter 36, Δt 389.4 ms: f = -8.018789950966e-01, ‖∇f‖ = 5.6081e-05, α = 1.00e+00, m = 32, nfg = 1 -[ Info: LBFGS: iter 37, Δt 176.7 ms: f = -8.018791535731e-01, ‖∇f‖ = 6.2356e-05, α = 1.00e+00, m = 32, nfg = 1 -[ Info: LBFGS: iter 38, Δt 143.4 ms: f = -8.018793550753e-01, ‖∇f‖ = 6.0528e-05, α = 1.00e+00, m = 32, nfg = 1 -[ Info: LBFGS: iter 39, Δt 160.6 ms: f = -8.018801150998e-01, ‖∇f‖ = 6.2768e-05, α = 1.00e+00, m = 32, nfg = 1 -[ Info: LBFGS: iter 40, Δt 191.0 ms: f = -8.018814750648e-01, ‖∇f‖ = 6.2301e-05, α = 1.00e+00, m = 32, nfg = 1 -[ Info: LBFGS: iter 41, Δt 218.0 ms: f = -8.018822724254e-01, ‖∇f‖ = 9.5267e-05, α = 1.00e+00, m = 32, nfg = 1 -[ Info: LBFGS: iter 42, Δt 210.1 ms: f = -8.018826000327e-01, ‖∇f‖ = 5.1283e-05, α = 1.00e+00, m = 32, nfg = 1 -[ Info: LBFGS: iter 43, Δt 398.2 ms: f = -8.018827118752e-01, ‖∇f‖ = 2.6091e-05, α = 1.00e+00, m = 32, nfg = 1 -[ Info: LBFGS: iter 44, Δt 190.0 ms: f = -8.018828058280e-01, ‖∇f‖ = 2.9316e-05, α = 1.00e+00, m = 32, nfg = 1 -[ Info: LBFGS: iter 45, Δt 164.0 ms: f = -8.018830270596e-01, ‖∇f‖ = 2.7982e-05, α = 1.00e+00, m = 32, nfg = 1 -[ Info: LBFGS: iter 46, Δt 176.0 ms: f = -8.018834021781e-01, ‖∇f‖ = 3.8102e-05, α = 1.00e+00, m = 32, nfg = 1 -[ Info: LBFGS: iter 47, Δt 200.2 ms: f = -8.018837183208e-01, ‖∇f‖ = 5.3658e-05, α = 1.00e+00, m = 32, nfg = 1 -[ Info: LBFGS: iter 48, Δt 196.9 ms: f = -8.018839628864e-01, ‖∇f‖ = 2.8728e-05, α = 1.00e+00, m = 32, nfg = 1 -[ Info: LBFGS: iter 49, Δt 384.9 ms: f = -8.018841580849e-01, ‖∇f‖ = 3.0680e-05, α = 1.00e+00, m = 32, nfg = 1 -[ Info: LBFGS: iter 50, Δt 188.4 ms: f = -8.018843859401e-01, ‖∇f‖ = 4.1973e-05, α = 1.00e+00, m = 32, nfg = 1 -[ Info: LBFGS: iter 51, Δt 198.0 ms: f = -8.018848104588e-01, ‖∇f‖ = 6.8881e-05, α = 1.00e+00, m = 32, nfg = 1 -[ Info: LBFGS: iter 52, Δt 176.0 ms: f = -8.018850110140e-01, ‖∇f‖ = 3.8651e-05, α = 1.00e+00, m = 32, nfg = 1 -[ Info: LBFGS: iter 53, Δt 192.5 ms: f = -8.018851266254e-01, ‖∇f‖ = 1.9013e-05, α = 1.00e+00, m = 32, nfg = 1 -[ Info: LBFGS: iter 54, Δt 207.5 ms: f = -8.018851864896e-01, ‖∇f‖ = 3.2919e-05, α = 1.00e+00, m = 32, nfg = 1 -[ Info: LBFGS: iter 55, Δt 217.6 ms: f = -8.018853097129e-01, ‖∇f‖ = 4.8521e-05, α = 1.00e+00, m = 32, nfg = 1 -[ Info: LBFGS: iter 56, Δt 439.8 ms: f = -8.018854916307e-01, ‖∇f‖ = 1.1478e-04, α = 1.00e+00, m = 32, nfg = 1 -[ Info: LBFGS: iter 57, Δt 237.1 ms: f = -8.018859128567e-01, ‖∇f‖ = 7.7221e-05, α = 1.00e+00, m = 32, nfg = 1 -[ Info: LBFGS: iter 58, Δt 192.7 ms: f = -8.018864519794e-01, ‖∇f‖ = 6.5316e-05, α = 1.00e+00, m = 32, nfg = 1 -[ Info: LBFGS: iter 59, Δt 212.9 ms: f = -8.018866398048e-01, ‖∇f‖ = 5.1566e-05, α = 1.00e+00, m = 32, nfg = 1 -[ Info: LBFGS: iter 60, Δt 435.6 ms: f = -8.018866993724e-01, ‖∇f‖ = 4.5541e-05, α = 3.68e-01, m = 32, nfg = 2 -[ Info: LBFGS: iter 61, Δt 415.0 ms: f = -8.018867239928e-01, ‖∇f‖ = 2.1992e-05, α = 1.00e+00, m = 32, nfg = 1 -[ Info: LBFGS: iter 62, Δt 188.7 ms: f = -8.018867352019e-01, ‖∇f‖ = 1.8064e-05, α = 1.00e+00, m = 32, nfg = 1 -[ Info: LBFGS: iter 63, Δt 173.1 ms: f = -8.018867713955e-01, ‖∇f‖ = 3.8651e-05, α = 1.00e+00, m = 32, nfg = 1 -[ Info: LBFGS: iter 64, Δt 181.4 ms: f = -8.018868019525e-01, ‖∇f‖ = 4.2630e-05, α = 1.00e+00, m = 32, nfg = 1 -[ Info: LBFGS: iter 65, Δt 192.6 ms: f = -8.018868378564e-01, ‖∇f‖ = 3.9318e-05, α = 1.00e+00, m = 32, nfg = 1 -[ Info: LBFGS: iter 66, Δt 209.6 ms: f = -8.018869167860e-01, ‖∇f‖ = 3.8747e-05, α = 1.00e+00, m = 32, nfg = 1 -[ Info: LBFGS: iter 67, Δt 201.4 ms: f = -8.018870300585e-01, ‖∇f‖ = 3.7138e-05, α = 1.00e+00, m = 32, nfg = 1 -[ Info: LBFGS: iter 68, Δt 416.7 ms: f = -8.018871411994e-01, ‖∇f‖ = 5.7019e-05, α = 1.00e+00, m = 32, nfg = 1 -[ Info: LBFGS: iter 69, Δt 417.3 ms: f = -8.018871992080e-01, ‖∇f‖ = 3.0699e-05, α = 5.24e-01, m = 32, nfg = 2 -[ Info: LBFGS: iter 70, Δt 196.3 ms: f = -8.018872466141e-01, ‖∇f‖ = 1.3886e-05, α = 1.00e+00, m = 32, nfg = 1 -[ Info: LBFGS: iter 71, Δt 216.8 ms: f = -8.018872637171e-01, ‖∇f‖ = 1.5769e-05, α = 1.00e+00, m = 32, nfg = 1 -[ Info: LBFGS: iter 72, Δt 223.4 ms: f = -8.018873194654e-01, ‖∇f‖ = 2.1425e-05, α = 1.00e+00, m = 32, nfg = 1 -[ Info: LBFGS: iter 73, Δt 427.1 ms: f = -8.018874061425e-01, ‖∇f‖ = 1.9898e-05, α = 1.00e+00, m = 32, nfg = 1 -[ Info: LBFGS: iter 74, Δt 428.8 ms: f = -8.018874674598e-01, ‖∇f‖ = 1.9802e-05, α = 3.61e-01, m = 32, nfg = 2 -[ Info: LBFGS: converged after 75 iterations and time 11.46 m: f = -8.018875356693e-01, ‖∇f‖ = 9.9333e-06 +┌ Warning: CTMRG cancel 150: obj = +1.702942228759e+01 +1.443123029406e-07im err = 2.4386740984e-05 time = 5.88 sec +└ @ PEPSKit ~/git/PEPSKit.jl/src/algorithms/ctmrg/ctmrg.jl:162 +[ Info: LBFGS: iter 1, Δt 18.49 s: f = -7.770809303692e-01, ‖∇f‖ = 3.1305e-02, α = 7.10e+02, m = 0, nfg = 7 +[ Info: LBFGS: iter 2, Δt 1.48 s: f = -7.841115159615e-01, ‖∇f‖ = 2.0103e-02, α = 1.00e+00, m = 1, nfg = 1 +[ Info: LBFGS: iter 3, Δt 857.1 ms: f = -7.927057334845e-01, ‖∇f‖ = 2.3327e-02, α = 1.00e+00, m = 2, nfg = 1 +[ Info: LBFGS: iter 4, Δt 743.3 ms: f = -7.962897324757e-01, ‖∇f‖ = 2.2475e-02, α = 1.00e+00, m = 3, nfg = 1 +[ Info: LBFGS: iter 5, Δt 1.42 s: f = -7.996749023744e-01, ‖∇f‖ = 7.0288e-03, α = 1.00e+00, m = 4, nfg = 1 +[ Info: LBFGS: iter 6, Δt 695.0 ms: f = -8.000821001207e-01, ‖∇f‖ = 1.2717e-03, α = 1.00e+00, m = 5, nfg = 1 +[ Info: LBFGS: iter 7, Δt 655.4 ms: f = -8.001106031252e-01, ‖∇f‖ = 1.3384e-03, α = 1.00e+00, m = 6, nfg = 1 +[ Info: LBFGS: iter 8, Δt 551.8 ms: f = -8.002622019964e-01, ‖∇f‖ = 2.4945e-03, α = 1.00e+00, m = 7, nfg = 1 +[ Info: LBFGS: iter 9, Δt 560.7 ms: f = -8.004505054484e-01, ‖∇f‖ = 2.9259e-03, α = 1.00e+00, m = 8, nfg = 1 +[ Info: LBFGS: iter 10, Δt 628.4 ms: f = -8.007649170868e-01, ‖∇f‖ = 1.7221e-03, α = 1.00e+00, m = 9, nfg = 1 +[ Info: LBFGS: iter 11, Δt 664.4 ms: f = -8.008760488382e-01, ‖∇f‖ = 2.2475e-03, α = 1.00e+00, m = 10, nfg = 1 +[ Info: LBFGS: iter 12, Δt 595.1 ms: f = -8.011008674672e-01, ‖∇f‖ = 1.5561e-03, α = 1.00e+00, m = 11, nfg = 1 +[ Info: LBFGS: iter 13, Δt 659.7 ms: f = -8.013170488565e-01, ‖∇f‖ = 1.1561e-03, α = 1.00e+00, m = 12, nfg = 1 +[ Info: LBFGS: iter 14, Δt 629.0 ms: f = -8.013730505450e-01, ‖∇f‖ = 7.1300e-04, α = 1.00e+00, m = 13, nfg = 1 +[ Info: LBFGS: iter 15, Δt 624.0 ms: f = -8.013886152636e-01, ‖∇f‖ = 2.8462e-04, α = 1.00e+00, m = 14, nfg = 1 +[ Info: LBFGS: iter 16, Δt 616.8 ms: f = -8.013946333330e-01, ‖∇f‖ = 2.7607e-04, α = 1.00e+00, m = 15, nfg = 1 +[ Info: LBFGS: iter 17, Δt 585.2 ms: f = -8.014080615636e-01, ‖∇f‖ = 3.6096e-04, α = 1.00e+00, m = 16, nfg = 1 +[ Info: LBFGS: iter 18, Δt 710.2 ms: f = -8.015095421688e-01, ‖∇f‖ = 1.9822e-03, α = 1.00e+00, m = 17, nfg = 1 +[ Info: LBFGS: iter 19, Δt 1.53 s: f = -8.015784052508e-01, ‖∇f‖ = 1.8040e-03, α = 1.00e+00, m = 18, nfg = 1 +[ Info: LBFGS: iter 20, Δt 2.53 s: f = -8.016945244238e-01, ‖∇f‖ = 2.9356e-03, α = 5.48e-01, m = 19, nfg = 3 +[ Info: LBFGS: iter 21, Δt 1.71 s: f = -8.017619206832e-01, ‖∇f‖ = 1.1993e-03, α = 3.82e-01, m = 20, nfg = 2 +[ Info: LBFGS: iter 22, Δt 800.7 ms: f = -8.017977854941e-01, ‖∇f‖ = 6.0337e-04, α = 1.00e+00, m = 21, nfg = 1 +[ Info: LBFGS: iter 23, Δt 1.64 s: f = -8.018087478343e-01, ‖∇f‖ = 3.7053e-04, α = 5.24e-01, m = 22, nfg = 2 +[ Info: LBFGS: iter 24, Δt 801.1 ms: f = -8.018127291733e-01, ‖∇f‖ = 3.0781e-04, α = 1.00e+00, m = 23, nfg = 1 +[ Info: LBFGS: iter 25, Δt 947.9 ms: f = -8.018164452111e-01, ‖∇f‖ = 2.9994e-04, α = 1.00e+00, m = 24, nfg = 1 +[ Info: LBFGS: iter 26, Δt 1.50 s: f = -8.018247131297e-01, ‖∇f‖ = 3.6496e-04, α = 1.00e+00, m = 25, nfg = 1 +[ Info: LBFGS: iter 27, Δt 912.3 ms: f = -8.018396738228e-01, ‖∇f‖ = 5.4222e-04, α = 1.00e+00, m = 26, nfg = 1 +[ Info: LBFGS: iter 28, Δt 788.8 ms: f = -8.018574789038e-01, ‖∇f‖ = 2.7917e-04, α = 1.00e+00, m = 27, nfg = 1 +[ Info: LBFGS: iter 29, Δt 888.8 ms: f = -8.018645552239e-01, ‖∇f‖ = 1.2319e-04, α = 1.00e+00, m = 28, nfg = 1 +[ Info: LBFGS: iter 30, Δt 858.3 ms: f = -8.018655987357e-01, ‖∇f‖ = 8.6048e-05, α = 1.00e+00, m = 29, nfg = 1 +[ Info: LBFGS: iter 31, Δt 863.2 ms: f = -8.018675717547e-01, ‖∇f‖ = 8.8636e-05, α = 1.00e+00, m = 30, nfg = 1 +[ Info: LBFGS: iter 32, Δt 889.2 ms: f = -8.018703935281e-01, ‖∇f‖ = 2.6554e-04, α = 1.00e+00, m = 31, nfg = 1 +[ Info: LBFGS: iter 33, Δt 929.2 ms: f = -8.018747970386e-01, ‖∇f‖ = 2.7841e-04, α = 1.00e+00, m = 32, nfg = 1 +[ Info: LBFGS: iter 34, Δt 934.8 ms: f = -8.018775666443e-01, ‖∇f‖ = 1.8523e-04, α = 1.00e+00, m = 32, nfg = 1 +[ Info: LBFGS: iter 35, Δt 883.7 ms: f = -8.018785062445e-01, ‖∇f‖ = 2.0638e-04, α = 1.00e+00, m = 32, nfg = 1 +[ Info: LBFGS: iter 36, Δt 853.0 ms: f = -8.018789950966e-01, ‖∇f‖ = 5.6081e-05, α = 1.00e+00, m = 32, nfg = 1 +[ Info: LBFGS: iter 37, Δt 850.4 ms: f = -8.018791535731e-01, ‖∇f‖ = 6.2356e-05, α = 1.00e+00, m = 32, nfg = 1 +[ Info: LBFGS: iter 38, Δt 1.71 s: f = -8.018793550753e-01, ‖∇f‖ = 6.0528e-05, α = 1.00e+00, m = 32, nfg = 1 +[ Info: LBFGS: iter 39, Δt 944.9 ms: f = -8.018801150998e-01, ‖∇f‖ = 6.2768e-05, α = 1.00e+00, m = 32, nfg = 1 +[ Info: LBFGS: iter 40, Δt 875.9 ms: f = -8.018814750648e-01, ‖∇f‖ = 6.2301e-05, α = 1.00e+00, m = 32, nfg = 1 +[ Info: LBFGS: iter 41, Δt 948.2 ms: f = -8.018822724254e-01, ‖∇f‖ = 9.5267e-05, α = 1.00e+00, m = 32, nfg = 1 +[ Info: LBFGS: iter 42, Δt 886.1 ms: f = -8.018826000327e-01, ‖∇f‖ = 5.1283e-05, α = 1.00e+00, m = 32, nfg = 1 +[ Info: LBFGS: iter 43, Δt 878.1 ms: f = -8.018827118752e-01, ‖∇f‖ = 2.6091e-05, α = 1.00e+00, m = 32, nfg = 1 +[ Info: LBFGS: iter 44, Δt 897.8 ms: f = -8.018828058280e-01, ‖∇f‖ = 2.9316e-05, α = 1.00e+00, m = 32, nfg = 1 +[ Info: LBFGS: iter 45, Δt 856.3 ms: f = -8.018830270596e-01, ‖∇f‖ = 2.7982e-05, α = 1.00e+00, m = 32, nfg = 1 +[ Info: LBFGS: iter 46, Δt 847.5 ms: f = -8.018834021781e-01, ‖∇f‖ = 3.8102e-05, α = 1.00e+00, m = 32, nfg = 1 +[ Info: LBFGS: iter 47, Δt 912.3 ms: f = -8.018837183208e-01, ‖∇f‖ = 5.3658e-05, α = 1.00e+00, m = 32, nfg = 1 +[ Info: LBFGS: iter 48, Δt 1.88 s: f = -8.018839628864e-01, ‖∇f‖ = 2.8728e-05, α = 1.00e+00, m = 32, nfg = 1 +[ Info: LBFGS: iter 49, Δt 1.05 s: f = -8.018841580849e-01, ‖∇f‖ = 3.0680e-05, α = 1.00e+00, m = 32, nfg = 1 +[ Info: LBFGS: iter 50, Δt 933.0 ms: f = -8.018843859400e-01, ‖∇f‖ = 4.1973e-05, α = 1.00e+00, m = 32, nfg = 1 +[ Info: LBFGS: iter 51, Δt 950.3 ms: f = -8.018848104588e-01, ‖∇f‖ = 6.8881e-05, α = 1.00e+00, m = 32, nfg = 1 +[ Info: LBFGS: iter 52, Δt 907.2 ms: f = -8.018850110140e-01, ‖∇f‖ = 3.8651e-05, α = 1.00e+00, m = 32, nfg = 1 +[ Info: LBFGS: iter 53, Δt 909.8 ms: f = -8.018851266254e-01, ‖∇f‖ = 1.9013e-05, α = 1.00e+00, m = 32, nfg = 1 +[ Info: LBFGS: iter 54, Δt 911.4 ms: f = -8.018851864896e-01, ‖∇f‖ = 3.2919e-05, α = 1.00e+00, m = 32, nfg = 1 +[ Info: LBFGS: iter 55, Δt 951.9 ms: f = -8.018853097129e-01, ‖∇f‖ = 4.8521e-05, α = 1.00e+00, m = 32, nfg = 1 +[ Info: LBFGS: iter 56, Δt 991.0 ms: f = -8.018854916306e-01, ‖∇f‖ = 1.1478e-04, α = 1.00e+00, m = 32, nfg = 1 +[ Info: LBFGS: iter 57, Δt 999.6 ms: f = -8.018859128567e-01, ‖∇f‖ = 7.7221e-05, α = 1.00e+00, m = 32, nfg = 1 +[ Info: LBFGS: iter 58, Δt 1.83 s: f = -8.018864519794e-01, ‖∇f‖ = 6.5316e-05, α = 1.00e+00, m = 32, nfg = 1 +[ Info: LBFGS: iter 59, Δt 1.11 s: f = -8.018866398050e-01, ‖∇f‖ = 5.1566e-05, α = 1.00e+00, m = 32, nfg = 1 +[ Info: LBFGS: iter 60, Δt 1.79 s: f = -8.018866993724e-01, ‖∇f‖ = 4.5541e-05, α = 3.68e-01, m = 32, nfg = 2 +[ Info: LBFGS: iter 61, Δt 938.5 ms: f = -8.018867239929e-01, ‖∇f‖ = 2.1992e-05, α = 1.00e+00, m = 32, nfg = 1 +[ Info: LBFGS: iter 62, Δt 833.4 ms: f = -8.018867352019e-01, ‖∇f‖ = 1.8064e-05, α = 1.00e+00, m = 32, nfg = 1 +[ Info: LBFGS: iter 63, Δt 903.7 ms: f = -8.018867713956e-01, ‖∇f‖ = 3.8651e-05, α = 1.00e+00, m = 32, nfg = 1 +[ Info: LBFGS: iter 64, Δt 859.6 ms: f = -8.018868019526e-01, ‖∇f‖ = 4.2630e-05, α = 1.00e+00, m = 32, nfg = 1 +[ Info: LBFGS: iter 65, Δt 860.1 ms: f = -8.018868378565e-01, ‖∇f‖ = 3.9318e-05, α = 1.00e+00, m = 32, nfg = 1 +[ Info: LBFGS: iter 66, Δt 861.3 ms: f = -8.018869167865e-01, ‖∇f‖ = 3.8747e-05, α = 1.00e+00, m = 32, nfg = 1 +[ Info: LBFGS: iter 67, Δt 912.3 ms: f = -8.018870300592e-01, ‖∇f‖ = 3.7138e-05, α = 1.00e+00, m = 32, nfg = 1 +[ Info: LBFGS: iter 68, Δt 944.6 ms: f = -8.018871411997e-01, ‖∇f‖ = 5.7019e-05, α = 1.00e+00, m = 32, nfg = 1 +[ Info: LBFGS: iter 69, Δt 3.06 s: f = -8.018871992087e-01, ‖∇f‖ = 3.0698e-05, α = 5.24e-01, m = 32, nfg = 2 +[ Info: LBFGS: iter 70, Δt 861.7 ms: f = -8.018872466144e-01, ‖∇f‖ = 1.3886e-05, α = 1.00e+00, m = 32, nfg = 1 +[ Info: LBFGS: iter 71, Δt 881.0 ms: f = -8.018872637174e-01, ‖∇f‖ = 1.5769e-05, α = 1.00e+00, m = 32, nfg = 1 +[ Info: LBFGS: iter 72, Δt 918.2 ms: f = -8.018873194656e-01, ‖∇f‖ = 2.1426e-05, α = 1.00e+00, m = 32, nfg = 1 +[ Info: LBFGS: iter 73, Δt 953.3 ms: f = -8.018874061424e-01, ‖∇f‖ = 1.9898e-05, α = 1.00e+00, m = 32, nfg = 1 +[ Info: LBFGS: iter 74, Δt 1.89 s: f = -8.018874674599e-01, ‖∇f‖ = 1.9802e-05, α = 3.61e-01, m = 32, nfg = 2 +[ Info: LBFGS: converged after 75 iterations and time 18.21 m: f = -8.018875356692e-01, ‖∇f‖ = 9.9332e-06 ```` @@ -384,7 +385,7 @@ the final value of the cost function we have just optimized. ```` ```` --0.8018875356693276 +-0.8018875356692025 ```` As another check, we can compute the magnetization per site and compare it to a [reference @@ -402,7 +403,7 @@ m_ref = 0.667162 ```` ```` -0.00011314613831048259 +0.00011314483261026798 ```` --- diff --git a/docs/src/examples/3d_ising_partition_function/main.ipynb b/docs/src/examples/3d_ising_partition_function/main.ipynb index b35edd5c0..edcc2b4f6 100644 --- a/docs/src/examples/3d_ising_partition_function/main.ipynb +++ b/docs/src/examples/3d_ising_partition_function/main.ipynb @@ -1,16 +1,17 @@ { "cells": [ { - "outputs": [], "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "using Markdown #hide" - ], - "metadata": {}, - "execution_count": null + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "# The 3D classical Ising model\n", "\n", @@ -36,12 +37,13 @@ "spirit as the boundary MPS methods demonstrated in another example.\n", "\n", "Let's start by making the example deterministic and importing the required packages:" - ], - "metadata": {} + ] }, { - "outputs": [], "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "using Random\n", "using LinearAlgebra\n", @@ -49,12 +51,11 @@ "using KrylovKit, OptimKit, Zygote\n", "\n", "Random.seed!(81812781144);" - ], - "metadata": {}, - "execution_count": null + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "## Defining the partition function\n", "\n", @@ -65,12 +66,13 @@ "constituent rank-6 `PEPSKit.PEPOTensor` `O` located at each site of the cubic\n", "lattice. To verify our example we will check the magnetization and energy, so we also define\n", "the corresponding rank-6 tensors `M` and `E` while we're at it." - ], - "metadata": {} + ] }, { - "outputs": [], "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "function three_dimensional_classical_ising(; beta, J = 1.0)\n", " K = beta * J\n", @@ -108,31 +110,30 @@ "\n", " return TensorMap(o, TMS), TensorMap(m, TMS), TensorMap(e, TMS)\n", "end;" - ], - "metadata": {}, - "execution_count": null + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "Let's initialize these tensors at inverse temperature $\\beta=0.2391$, which corresponds to\n", "a slightly lower temperature than the critical value $\\beta_c=0.2216544…$" - ], - "metadata": {} + ] }, { - "outputs": [], "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "beta = 0.2391\n", "O, M, E = three_dimensional_classical_ising(; beta)\n", "O isa PEPSKit.PEPOTensor" - ], - "metadata": {}, - "execution_count": null + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "## Contracting the partition function\n", "\n", @@ -190,16 +191,18 @@ "contraction algorithm we can use to compute the values of these two networks. In addition,\n", "we'll specify the specific reverse rule algorithm that will be used to compute the gradient\n", "of this cost function." - ], - "metadata": {} + ] }, { - "outputs": [], "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "boundary_alg = SimultaneousCTMRG(; maxiter = 150, tol = 1.0e-8, verbosity = 1)\n", "rrule_alg = EigSolver(;\n", - " solver_alg = KrylovKit.Arnoldi(; maxiter = 30, tol = 1.0e-6, eager = true), iterscheme = :diffgauge\n", + " solver_alg = KrylovKit.Arnoldi(; maxiter = 30, tol = 1.0e-6, eager = true),\n", + " iterscheme = :fixed,\n", ")\n", "T = InfinitePEPO(O)\n", "\n", @@ -240,12 +243,11 @@ " g = only(gs)\n", " return E, g\n", "end;" - ], - "metadata": {}, - "execution_count": null + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "There are a few things to note about this cost function definition. Since we will pass it to\n", "the `OptimKit.optimize`, we require it to return both our cost function and the\n", @@ -286,12 +288,13 @@ "them where the only difference is that we have to pass along an extra environment since our\n", "cost function requires two distinct contractions as opposed to the setting of Hamiltonian\n", "PEPS optimization which only requires a double-layer contraction." - ], - "metadata": {} + ] }, { - "outputs": [], "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "function pepo_retract((peps, env_double_layer, env_triple_layer), η, α)\n", " (peps´, env_double_layer´), ξ = PEPSKit.peps_retract((peps, env_double_layer), η, α)\n", @@ -309,24 +312,24 @@ " ξ, (peps, env_double_layer), η, α, (peps´, env_double_layer´)\n", " )\n", "end;" - ], - "metadata": {}, - "execution_count": null + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "### Finding the fixed point\n", "\n", "All that is left then is to specify the virtual spaces of the PEPS and the two environments,\n", "initialize them in the appropriate way, choose an optimization algortithm and call the\n", "`optimize` function from OptimKit.jl to get our desired PEPS fixed point." - ], - "metadata": {} + ] }, { - "outputs": [], "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "Vpeps = ℂ^2\n", "Venv = ℂ^12\n", @@ -345,41 +348,41 @@ " retract = pepo_retract,\n", " (transport!) = (pepo_transport!),\n", ");" - ], - "metadata": {}, - "execution_count": null + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "### Verifying the result\n", "\n", "Having found the fixed point, we have essentially contracted the entire partition function\n", "and we can start computing observables. The free energy per site for example is just given by\n", "the final value of the cost function we have just optimized." - ], - "metadata": {} + ] }, { - "outputs": [], "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "@show f" - ], - "metadata": {}, - "execution_count": null + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "As another check, we can compute the magnetization per site and compare it to a [reference\n", "value obtaind through Monte-Carlo simulations](@cite hasenbusch_monte_2001)." - ], - "metadata": {} + ] }, { - "outputs": [], "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "n3_final = InfiniteSquareNetwork(psi_final, T)\n", "num = PEPSKit.contract_local_tensor((1, 1, 1), M, n3_final, env3_final)\n", @@ -389,33 +392,31 @@ "m_ref = 0.667162\n", "\n", "@show abs(m - m_ref)" - ], - "metadata": {}, - "execution_count": null + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "---\n", "\n", "*This notebook was generated using [Literate.jl](https://github.com/fredrikekre/Literate.jl).*" - ], - "metadata": {} + ] } ], - "nbformat_minor": 3, "metadata": { + "kernelspec": { + "display_name": "Julia 1.12.5", + "language": "julia", + "name": "julia-1.12" + }, "language_info": { "file_extension": ".jl", "mimetype": "application/julia", "name": "julia", "version": "1.12.5" - }, - "kernelspec": { - "name": "julia-1.12", - "display_name": "Julia 1.12.5", - "language": "julia" } }, - "nbformat": 4 + "nbformat": 4, + "nbformat_minor": 3 } \ No newline at end of file diff --git a/docs/src/examples/bose_hubbard/index.md b/docs/src/examples/bose_hubbard/index.md index fb732804e..b5a3e13c6 100644 --- a/docs/src/examples/bose_hubbard/index.md +++ b/docs/src/examples/bose_hubbard/index.md @@ -111,11 +111,9 @@ optimizer_alg = (; tol = 1.0e-4, alg = :lbfgs, maxiter = 150, ls_maxiter = 2, ls general-purpose set of settings which will always work, so instead one has to adjust the simulation settings for each specific application. For example, it might help to switch between the CTMRG flavors `alg=:simultaneous` and `alg=:sequential` to - improve convergence. The evaluation of the CTMRG gradient can be instable, so there it - is advised to try the different `iterscheme=:diffgauge` and `iterscheme=:fixed` schemes - as well as different `alg` keywords. Of course the tolerances of the algorithms and - their subalgorithms also have to be compatible. For more details on the available - options, see the [`fixedpoint`](@ref) docstring. + improve convergence. Of course the tolerances of the algorithms and their subalgorithms + also have to be compatible. For more details on the available options, see the + [`fixedpoint`](@ref) docstring. Keep in mind that the PEPS is constructed from a unit cell of spaces, so we have to make a matrix of `V_peps` spaces: diff --git a/docs/src/examples/bose_hubbard/main.ipynb b/docs/src/examples/bose_hubbard/main.ipynb index f33b4031f..ceb2057e8 100644 --- a/docs/src/examples/bose_hubbard/main.ipynb +++ b/docs/src/examples/bose_hubbard/main.ipynb @@ -1,16 +1,17 @@ { "cells": [ { - "outputs": [], "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "using Markdown #hide" - ], - "metadata": {}, - "execution_count": null + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "# Optimizing the $U(1)$-symmetric Bose-Hubbard model\n", "\n", @@ -21,23 +22,23 @@ "environment - made possible through TensorKit.\n", "\n", "But first let's seed the RNG and import the required modules:" - ], - "metadata": {} + ] }, { - "outputs": [], "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "using Random\n", "using TensorKit, PEPSKit\n", "using MPSKit: add_physical_charge\n", "Random.seed!(2928528935);" - ], - "metadata": {}, - "execution_count": null + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "## Defining the model\n", "\n", @@ -48,62 +49,62 @@ "ground state to be well approximated by a PEPS with a manifest global $U(1)$ symmetry.\n", "Furthermore, we'll impose a cutoff at 2 bosons per site, set the chemical potential to zero\n", "and use a simple $1 \\times 1$ unit cell:" - ], - "metadata": {} + ] }, { - "outputs": [], "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "t = 1.0\n", "U = 30.0\n", "cutoff = 2\n", "mu = 0.0\n", "lattice = InfiniteSquare(1, 1);" - ], - "metadata": {}, - "execution_count": null + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "Next, we impose an explicit global $U(1)$ symmetry as well as a fixed particle number\n", "density in our simulations. We can do this by setting the `symmetry` argument of the\n", "Hamiltonian constructor to `U1Irrep` and passing one as the particle number density\n", "keyword argument `n`:" - ], - "metadata": {} + ] }, { - "outputs": [], "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "symmetry = U1Irrep\n", "n = 1\n", "H = bose_hubbard_model(ComplexF64, symmetry, lattice; cutoff, t, U, n);" - ], - "metadata": {}, - "execution_count": null + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "Before we continue, it might be interesting to inspect the corresponding lattice physical\n", "spaces (which is here just a $1 \\times 1$ matrix due to the single-site unit cell):" - ], - "metadata": {} + ] }, { - "outputs": [], "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "physical_spaces = physicalspace(H)" - ], - "metadata": {}, - "execution_count": null + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "Note that the physical space contains $U(1)$ charges -1, 0 and +1. Indeed, imposing a\n", "particle number density of +1 corresponds to shifting the physical charges by -1 to\n", @@ -126,43 +127,43 @@ "with a model at unit filling our physical space only contains integer $U(1)$ irreps.\n", "Therefore, we'll build our PEPS and environment spaces using integer $U(1)$ irreps centered\n", "around the zero charge:" - ], - "metadata": {} + ] }, { - "outputs": [], "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "V_peps = U1Space(0 => 2, 1 => 1, -1 => 1)\n", "V_env = U1Space(0 => 6, 1 => 4, -1 => 4, 2 => 2, -2 => 2);" - ], - "metadata": {}, - "execution_count": null + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "## Finding the ground state\n", "\n", "Having defined our Hamiltonian and spaces, it is just a matter of plugging this into the\n", "optimization framework in the usual way to find the ground state. So, we first specify all\n", "algorithms and their tolerances:" - ], - "metadata": {} + ] }, { - "outputs": [], "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "boundary_alg = (; tol = 1.0e-8, alg = :simultaneous, trunc = (; alg = :fixedspace))\n", "gradient_alg = (; tol = 1.0e-6, maxiter = 10, alg = :linsolver, iterscheme = :fixed)\n", "optimizer_alg = (; tol = 1.0e-4, alg = :lbfgs, maxiter = 150, ls_maxiter = 2, ls_maxfg = 2);" - ], - "metadata": {}, - "execution_count": null + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "!!! note\n", "\tTaking CTMRG gradients and optimizing symmetric tensors tends to be more problematic\n", @@ -171,89 +172,86 @@ " general-purpose set of settings which will always work, so instead one has to adjust\n", " the simulation settings for each specific application. For example, it might help to\n", " switch between the CTMRG flavors `alg=:simultaneous` and `alg=:sequential` to\n", - " improve convergence. The evaluation of the CTMRG gradient can be instable, so there it\n", - " is advised to try the different `iterscheme=:diffgauge` and `iterscheme=:fixed` schemes\n", - " as well as different `alg` keywords. Of course the tolerances of the algorithms and\n", - " their subalgorithms also have to be compatible. For more details on the available\n", - " options, see the `fixedpoint` docstring.\n", + " improve convergence. Of course the tolerances of the algorithms and their subalgorithms\n", + " also have to be compatible. For more details on the available options, see the\n", + " [`fixedpoint`](@ref) docstring.\n", "\n", "Keep in mind that the PEPS is constructed from a unit cell of spaces, so we have to make a\n", "matrix of `V_peps` spaces:" - ], - "metadata": {} + ] }, { - "outputs": [], "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "virtual_spaces = fill(V_peps, size(lattice)...)\n", "peps₀ = InfinitePEPS(randn, ComplexF64, physical_spaces, virtual_spaces)\n", "env₀, = leading_boundary(CTMRGEnv(peps₀, V_env), peps₀; boundary_alg...);" - ], - "metadata": {}, - "execution_count": null + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "And at last, we optimize (which might take a bit):" - ], - "metadata": {} + ] }, { - "outputs": [], "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "peps, env, E, info = fixedpoint(\n", " H, peps₀, env₀; boundary_alg, gradient_alg, optimizer_alg, verbosity = 3\n", ")\n", "@show E;" - ], - "metadata": {}, - "execution_count": null + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "We can compare our PEPS result to the energy obtained using a cylinder-MPS calculation\n", "using a cylinder circumference of $L_y = 7$ and a bond dimension of 446, which yields\n", "$E = -0.273284888$:" - ], - "metadata": {} + ] }, { - "outputs": [], "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "E_ref = -0.273284888\n", "@show (E - E_ref) / E_ref;" - ], - "metadata": {}, - "execution_count": null + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "---\n", "\n", "*This notebook was generated using [Literate.jl](https://github.com/fredrikekre/Literate.jl).*" - ], - "metadata": {} + ] } ], - "nbformat_minor": 3, "metadata": { + "kernelspec": { + "display_name": "Julia 1.12.5", + "language": "julia", + "name": "julia-1.12" + }, "language_info": { "file_extension": ".jl", "mimetype": "application/julia", "name": "julia", "version": "1.12.5" - }, - "kernelspec": { - "name": "julia-1.12", - "display_name": "Julia 1.12.5", - "language": "julia" } }, - "nbformat": 4 -} \ No newline at end of file + "nbformat": 4, + "nbformat_minor": 3 +} diff --git a/docs/src/examples/fermi_hubbard/index.md b/docs/src/examples/fermi_hubbard/index.md index 8480950ed..71c284fe5 100644 --- a/docs/src/examples/fermi_hubbard/index.md +++ b/docs/src/examples/fermi_hubbard/index.md @@ -82,7 +82,7 @@ define all algorithmic parameters: ````julia boundary_alg = (; tol = 1.0e-8, alg = :simultaneous, trunc = (; alg = :fixedspace)) -gradient_alg = (; tol = 1.0e-6, alg = :eigsolver, maxiter = 10, iterscheme = :diffgauge) +gradient_alg = (; tol = 1.0e-6, alg = :eigsolver, maxiter = 10, iterscheme = :fixed) optimizer_alg = (; tol = 1.0e-4, alg = :lbfgs, maxiter = 80, ls_maxiter = 3, ls_maxfg = 3) ```` @@ -101,8 +101,8 @@ env₀, = leading_boundary(CTMRGEnv(peps₀, V_env), peps₀; boundary_alg...); ```` ```` -[ Info: CTMRG init: obj = +5.484842275411e+04 +4.469243203539e+04im err = 1.0000e+00 -[ Info: CTMRG conv 26: obj = +8.371681846538e+04 -3.790073606069e-07im err = 7.4963854907e-09 time = 7.98 sec +[ Info: CTMRG init: obj = +5.484842275412e+04 +4.469243203539e+04im err = 1.0000e+00 +[ Info: CTMRG conv 26: obj = +8.371681846538e+04 -3.790119080804e-07im err = 7.4963849914e-09 time = 36.38 sec ```` @@ -120,91 +120,91 @@ peps, env, E, info = fixedpoint( ┌ Warning: Linesearch not converged after 1 iterations and 4 function evaluations: │ α = 2.50e+01, dϕ = -1.49e-01, ϕ - ϕ₀ = -2.88e+00 └ @ OptimKit ~/.julia/packages/OptimKit/OEwMx/src/linesearches.jl:151 -[ Info: LBFGS: iter 1, Δt 44.79 s: f = 3.801336895996e+00, ‖∇f‖ = 2.3457e+01, α = 2.50e+01, m = 0, nfg = 4 +[ Info: LBFGS: iter 1, Δt 38.64 s: f = 3.801336895927e+00, ‖∇f‖ = 2.3457e+01, α = 2.50e+01, m = 0, nfg = 4 ┌ Warning: Linesearch not converged after 1 iterations and 4 function evaluations: │ α = 2.50e+01, dϕ = -5.73e-03, ϕ - ϕ₀ = -3.81e+00 └ @ OptimKit ~/.julia/packages/OptimKit/OEwMx/src/linesearches.jl:151 -[ Info: LBFGS: iter 2, Δt 38.26 s: f = -9.717026892628e-03, ‖∇f‖ = 3.2049e+00, α = 2.50e+01, m = 0, nfg = 4 -[ Info: LBFGS: iter 3, Δt 9.11 s: f = -1.151937221231e-01, ‖∇f‖ = 2.7846e+00, α = 1.00e+00, m = 1, nfg = 1 -[ Info: LBFGS: iter 4, Δt 7.93 s: f = -6.164097148624e-01, ‖∇f‖ = 2.3680e+00, α = 1.00e+00, m = 2, nfg = 1 -[ Info: LBFGS: iter 5, Δt 7.97 s: f = -8.177983956552e-01, ‖∇f‖ = 1.9112e+00, α = 1.00e+00, m = 3, nfg = 1 -[ Info: LBFGS: iter 6, Δt 7.27 s: f = -9.902797531380e-01, ‖∇f‖ = 2.3790e+00, α = 1.00e+00, m = 4, nfg = 1 -[ Info: LBFGS: iter 7, Δt 6.91 s: f = -1.142781180434e+00, ‖∇f‖ = 1.5680e+00, α = 1.00e+00, m = 5, nfg = 1 -[ Info: LBFGS: iter 8, Δt 6.04 s: f = -1.238252367608e+00, ‖∇f‖ = 3.5020e+00, α = 1.00e+00, m = 6, nfg = 1 -[ Info: LBFGS: iter 9, Δt 6.35 s: f = -1.438152718476e+00, ‖∇f‖ = 1.3366e+00, α = 1.00e+00, m = 7, nfg = 1 -[ Info: LBFGS: iter 10, Δt 5.96 s: f = -1.523106534555e+00, ‖∇f‖ = 1.3495e+00, α = 1.00e+00, m = 8, nfg = 1 -[ Info: LBFGS: iter 11, Δt 13.16 s: f = -1.619309099210e+00, ‖∇f‖ = 1.1948e+00, α = 1.72e-01, m = 9, nfg = 2 -[ Info: LBFGS: iter 12, Δt 12.73 s: f = -1.681436569538e+00, ‖∇f‖ = 9.4842e-01, α = 2.37e-01, m = 10, nfg = 2 -[ Info: LBFGS: iter 13, Δt 6.23 s: f = -1.720664405828e+00, ‖∇f‖ = 1.4227e+00, α = 1.00e+00, m = 11, nfg = 1 -[ Info: LBFGS: iter 14, Δt 6.20 s: f = -1.770786332451e+00, ‖∇f‖ = 6.2727e-01, α = 1.00e+00, m = 12, nfg = 1 -[ Info: LBFGS: iter 15, Δt 6.49 s: f = -1.807472184382e+00, ‖∇f‖ = 5.1285e-01, α = 1.00e+00, m = 13, nfg = 1 -[ Info: LBFGS: iter 16, Δt 6.20 s: f = -1.859749157697e+00, ‖∇f‖ = 7.1361e-01, α = 1.00e+00, m = 14, nfg = 1 -[ Info: LBFGS: iter 17, Δt 6.22 s: f = -1.893132038649e+00, ‖∇f‖ = 6.7317e-01, α = 1.00e+00, m = 15, nfg = 1 -[ Info: LBFGS: iter 18, Δt 6.53 s: f = -1.923092864927e+00, ‖∇f‖ = 5.5354e-01, α = 1.00e+00, m = 16, nfg = 1 -[ Info: LBFGS: iter 19, Δt 6.17 s: f = -1.948135786436e+00, ‖∇f‖ = 4.7674e-01, α = 1.00e+00, m = 17, nfg = 1 -[ Info: LBFGS: iter 20, Δt 6.51 s: f = -1.969521622377e+00, ‖∇f‖ = 4.1602e-01, α = 1.00e+00, m = 18, nfg = 1 -[ Info: LBFGS: iter 21, Δt 6.24 s: f = -1.982569431031e+00, ‖∇f‖ = 4.5188e-01, α = 1.00e+00, m = 19, nfg = 1 -[ Info: LBFGS: iter 22, Δt 6.17 s: f = -1.994023093772e+00, ‖∇f‖ = 3.1544e-01, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 23, Δt 6.62 s: f = -2.002841836933e+00, ‖∇f‖ = 3.0502e-01, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 24, Δt 6.18 s: f = -2.014066310812e+00, ‖∇f‖ = 3.3498e-01, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 25, Δt 6.29 s: f = -2.022003031089e+00, ‖∇f‖ = 4.3896e-01, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 26, Δt 6.60 s: f = -2.030108712400e+00, ‖∇f‖ = 2.0527e-01, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 27, Δt 6.22 s: f = -2.035064140788e+00, ‖∇f‖ = 1.6295e-01, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 28, Δt 7.21 s: f = -2.038644453084e+00, ‖∇f‖ = 1.6908e-01, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 29, Δt 6.63 s: f = -2.041287656776e+00, ‖∇f‖ = 2.4233e-01, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 30, Δt 6.27 s: f = -2.044963003064e+00, ‖∇f‖ = 1.2134e-01, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 31, Δt 6.26 s: f = -2.046709201566e+00, ‖∇f‖ = 9.5293e-02, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 32, Δt 6.58 s: f = -2.048704698396e+00, ‖∇f‖ = 1.0554e-01, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 33, Δt 6.27 s: f = -2.049753774431e+00, ‖∇f‖ = 1.7672e-01, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 34, Δt 6.62 s: f = -2.051012655381e+00, ‖∇f‖ = 6.4429e-02, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 35, Δt 6.24 s: f = -2.051487362644e+00, ‖∇f‖ = 4.8991e-02, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 36, Δt 6.25 s: f = -2.051906992546e+00, ‖∇f‖ = 6.2050e-02, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 37, Δt 6.57 s: f = -2.052351423104e+00, ‖∇f‖ = 9.2729e-02, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 38, Δt 6.17 s: f = -2.052848307081e+00, ‖∇f‖ = 4.8571e-02, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 39, Δt 6.58 s: f = -2.053135861431e+00, ‖∇f‖ = 3.5616e-02, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 40, Δt 6.13 s: f = -2.053405790904e+00, ‖∇f‖ = 4.2303e-02, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 41, Δt 6.30 s: f = -2.053600750553e+00, ‖∇f‖ = 5.7966e-02, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 42, Δt 6.60 s: f = -2.053812274065e+00, ‖∇f‖ = 3.2230e-02, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 43, Δt 6.26 s: f = -2.054009903020e+00, ‖∇f‖ = 3.1640e-02, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 44, Δt 6.29 s: f = -2.054189826272e+00, ‖∇f‖ = 4.1575e-02, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 45, Δt 6.60 s: f = -2.054332724188e+00, ‖∇f‖ = 6.9194e-02, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 46, Δt 6.29 s: f = -2.054519394728e+00, ‖∇f‖ = 2.9113e-02, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 47, Δt 6.30 s: f = -2.054613025514e+00, ‖∇f‖ = 2.5330e-02, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 48, Δt 6.64 s: f = -2.054720907548e+00, ‖∇f‖ = 3.1755e-02, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 49, Δt 6.31 s: f = -2.054879186805e+00, ‖∇f‖ = 3.4648e-02, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 50, Δt 6.28 s: f = -2.054968291030e+00, ‖∇f‖ = 8.4868e-02, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 51, Δt 6.59 s: f = -2.055240598515e+00, ‖∇f‖ = 3.1534e-02, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 52, Δt 6.30 s: f = -2.055381144002e+00, ‖∇f‖ = 2.5669e-02, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 53, Δt 6.68 s: f = -2.055572825440e+00, ‖∇f‖ = 3.8027e-02, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 54, Δt 6.30 s: f = -2.055872604944e+00, ‖∇f‖ = 4.6489e-02, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 55, Δt 6.28 s: f = -2.056396522667e+00, ‖∇f‖ = 8.8080e-02, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 56, Δt 6.66 s: f = -2.056856239722e+00, ‖∇f‖ = 8.3565e-02, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 57, Δt 6.31 s: f = -2.057479315508e+00, ‖∇f‖ = 4.4471e-02, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 58, Δt 6.68 s: f = -2.057912243806e+00, ‖∇f‖ = 5.9337e-02, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 59, Δt 6.32 s: f = -2.058287160865e+00, ‖∇f‖ = 6.0136e-02, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 60, Δt 6.23 s: f = -2.058998799983e+00, ‖∇f‖ = 6.2226e-02, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 61, Δt 12.83 s: f = -2.059475804662e+00, ‖∇f‖ = 1.0086e-01, α = 4.83e-01, m = 20, nfg = 2 -[ Info: LBFGS: iter 62, Δt 6.34 s: f = -2.060083017277e+00, ‖∇f‖ = 6.8345e-02, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 63, Δt 6.76 s: f = -2.060482561109e+00, ‖∇f‖ = 7.3320e-02, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 64, Δt 6.31 s: f = -2.060741769883e+00, ‖∇f‖ = 9.5623e-02, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 65, Δt 6.41 s: f = -2.061312309048e+00, ‖∇f‖ = 7.1633e-02, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 66, Δt 6.65 s: f = -2.061708108692e+00, ‖∇f‖ = 5.4922e-02, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 67, Δt 6.30 s: f = -2.062077117667e+00, ‖∇f‖ = 5.4604e-02, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 68, Δt 6.58 s: f = -2.062376818127e+00, ‖∇f‖ = 7.1800e-02, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 69, Δt 12.31 s: f = -2.062695947352e+00, ‖∇f‖ = 9.6476e-02, α = 5.00e-01, m = 20, nfg = 2 -[ Info: LBFGS: iter 70, Δt 6.39 s: f = -2.063157063540e+00, ‖∇f‖ = 7.1450e-02, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 71, Δt 6.15 s: f = -2.063918977538e+00, ‖∇f‖ = 9.1357e-02, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 72, Δt 6.09 s: f = -2.064221211695e+00, ‖∇f‖ = 7.8535e-02, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 73, Δt 6.42 s: f = -2.064680585193e+00, ‖∇f‖ = 7.3845e-02, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 74, Δt 6.05 s: f = -2.065193848145e+00, ‖∇f‖ = 1.1291e-01, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 75, Δt 6.38 s: f = -2.066080890415e+00, ‖∇f‖ = 9.7562e-02, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 76, Δt 6.06 s: f = -2.067019814101e+00, ‖∇f‖ = 1.6919e-01, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 77, Δt 7.04 s: f = -2.067204715883e+00, ‖∇f‖ = 1.6814e-01, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 78, Δt 6.41 s: f = -2.068147829832e+00, ‖∇f‖ = 1.8170e-01, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 79, Δt 13.08 s: f = -2.068989082597e+00, ‖∇f‖ = 2.0607e-01, α = 3.00e-01, m = 20, nfg = 2 -┌ Warning: LBFGS: not converged to requested tol after 80 iterations and time 19.47 m: f = -2.070356853340e+00, ‖∇f‖ = 2.9208e-01 +[ Info: LBFGS: iter 2, Δt 33.51 s: f = -9.717027903025e-03, ‖∇f‖ = 3.2049e+00, α = 2.50e+01, m = 0, nfg = 4 +[ Info: LBFGS: iter 3, Δt 6.56 s: f = -1.151937230814e-01, ‖∇f‖ = 2.7846e+00, α = 1.00e+00, m = 1, nfg = 1 +[ Info: LBFGS: iter 4, Δt 7.11 s: f = -6.164097151643e-01, ‖∇f‖ = 2.3680e+00, α = 1.00e+00, m = 2, nfg = 1 +[ Info: LBFGS: iter 5, Δt 5.99 s: f = -8.177983968326e-01, ‖∇f‖ = 1.9112e+00, α = 1.00e+00, m = 3, nfg = 1 +[ Info: LBFGS: iter 6, Δt 5.39 s: f = -9.902797555067e-01, ‖∇f‖ = 2.3790e+00, α = 1.00e+00, m = 4, nfg = 1 +[ Info: LBFGS: iter 7, Δt 6.47 s: f = -1.142781183014e+00, ‖∇f‖ = 1.5680e+00, α = 1.00e+00, m = 5, nfg = 1 +[ Info: LBFGS: iter 8, Δt 4.97 s: f = -1.238252399424e+00, ‖∇f‖ = 3.5020e+00, α = 1.00e+00, m = 6, nfg = 1 +[ Info: LBFGS: iter 9, Δt 5.92 s: f = -1.438152723955e+00, ‖∇f‖ = 1.3366e+00, α = 1.00e+00, m = 7, nfg = 1 +[ Info: LBFGS: iter 10, Δt 4.83 s: f = -1.523106553003e+00, ‖∇f‖ = 1.3495e+00, α = 1.00e+00, m = 8, nfg = 1 +[ Info: LBFGS: iter 11, Δt 11.09 s: f = -1.619309113033e+00, ‖∇f‖ = 1.1948e+00, α = 1.72e-01, m = 9, nfg = 2 +[ Info: LBFGS: iter 12, Δt 10.75 s: f = -1.681436580003e+00, ‖∇f‖ = 9.4842e-01, α = 2.37e-01, m = 10, nfg = 2 +[ Info: LBFGS: iter 13, Δt 4.81 s: f = -1.720664442091e+00, ‖∇f‖ = 1.4227e+00, α = 1.00e+00, m = 11, nfg = 1 +[ Info: LBFGS: iter 14, Δt 5.91 s: f = -1.770786352947e+00, ‖∇f‖ = 6.2727e-01, α = 1.00e+00, m = 12, nfg = 1 +[ Info: LBFGS: iter 15, Δt 4.69 s: f = -1.807472232437e+00, ‖∇f‖ = 5.1285e-01, α = 1.00e+00, m = 13, nfg = 1 +[ Info: LBFGS: iter 16, Δt 6.02 s: f = -1.859749167278e+00, ‖∇f‖ = 7.1361e-01, α = 1.00e+00, m = 14, nfg = 1 +[ Info: LBFGS: iter 17, Δt 4.65 s: f = -1.893132058603e+00, ‖∇f‖ = 6.7317e-01, α = 1.00e+00, m = 15, nfg = 1 +[ Info: LBFGS: iter 18, Δt 4.84 s: f = -1.923092871787e+00, ‖∇f‖ = 5.5354e-01, α = 1.00e+00, m = 16, nfg = 1 +[ Info: LBFGS: iter 19, Δt 5.89 s: f = -1.948135797818e+00, ‖∇f‖ = 4.7674e-01, α = 1.00e+00, m = 17, nfg = 1 +[ Info: LBFGS: iter 20, Δt 4.83 s: f = -1.969521620203e+00, ‖∇f‖ = 4.1602e-01, α = 1.00e+00, m = 18, nfg = 1 +[ Info: LBFGS: iter 21, Δt 6.12 s: f = -1.982569429583e+00, ‖∇f‖ = 4.5188e-01, α = 1.00e+00, m = 19, nfg = 1 +[ Info: LBFGS: iter 22, Δt 4.79 s: f = -1.994023088009e+00, ‖∇f‖ = 3.1544e-01, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 23, Δt 6.09 s: f = -2.002841835268e+00, ‖∇f‖ = 3.0502e-01, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 24, Δt 4.93 s: f = -2.014066311538e+00, ‖∇f‖ = 3.3498e-01, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 25, Δt 5.04 s: f = -2.022003034919e+00, ‖∇f‖ = 4.3896e-01, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 26, Δt 5.98 s: f = -2.030108714405e+00, ‖∇f‖ = 2.0527e-01, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 27, Δt 4.94 s: f = -2.035064142878e+00, ‖∇f‖ = 1.6295e-01, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 28, Δt 6.60 s: f = -2.038644459020e+00, ‖∇f‖ = 1.6908e-01, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 29, Δt 4.92 s: f = -2.041287669506e+00, ‖∇f‖ = 2.4233e-01, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 30, Δt 5.95 s: f = -2.044963015191e+00, ‖∇f‖ = 1.2134e-01, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 31, Δt 4.83 s: f = -2.046709214218e+00, ‖∇f‖ = 9.5293e-02, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 32, Δt 6.05 s: f = -2.048704711627e+00, ‖∇f‖ = 1.0554e-01, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 33, Δt 4.78 s: f = -2.049753785745e+00, ‖∇f‖ = 1.7672e-01, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 34, Δt 5.96 s: f = -2.051012657058e+00, ‖∇f‖ = 6.4429e-02, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 35, Δt 4.80 s: f = -2.051487365494e+00, ‖∇f‖ = 4.8991e-02, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 36, Δt 4.82 s: f = -2.051906995005e+00, ‖∇f‖ = 6.2050e-02, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 37, Δt 6.04 s: f = -2.052351424092e+00, ‖∇f‖ = 9.2730e-02, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 38, Δt 4.80 s: f = -2.052848308926e+00, ‖∇f‖ = 4.8571e-02, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 39, Δt 5.93 s: f = -2.053135861688e+00, ‖∇f‖ = 3.5616e-02, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 40, Δt 4.80 s: f = -2.053405790277e+00, ‖∇f‖ = 4.2303e-02, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 41, Δt 4.88 s: f = -2.053600751472e+00, ‖∇f‖ = 5.7965e-02, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 42, Δt 5.95 s: f = -2.053812276343e+00, ‖∇f‖ = 3.2230e-02, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 43, Δt 4.68 s: f = -2.054009904322e+00, ‖∇f‖ = 3.1640e-02, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 44, Δt 4.74 s: f = -2.054189829937e+00, ‖∇f‖ = 4.1575e-02, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 45, Δt 5.93 s: f = -2.054332727237e+00, ‖∇f‖ = 6.9194e-02, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 46, Δt 4.71 s: f = -2.054519396790e+00, ‖∇f‖ = 2.9113e-02, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 47, Δt 5.92 s: f = -2.054613028290e+00, ‖∇f‖ = 2.5330e-02, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 48, Δt 4.69 s: f = -2.054720909675e+00, ‖∇f‖ = 3.1755e-02, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 49, Δt 5.72 s: f = -2.054879189481e+00, ‖∇f‖ = 3.4648e-02, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 50, Δt 4.90 s: f = -2.054968277255e+00, ‖∇f‖ = 8.4871e-02, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 51, Δt 4.71 s: f = -2.055240591253e+00, ‖∇f‖ = 3.1534e-02, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 52, Δt 5.92 s: f = -2.055381130477e+00, ‖∇f‖ = 2.5669e-02, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 53, Δt 4.75 s: f = -2.055572809496e+00, ‖∇f‖ = 3.8027e-02, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 54, Δt 4.88 s: f = -2.055872578414e+00, ‖∇f‖ = 4.6489e-02, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 55, Δt 6.02 s: f = -2.056396546910e+00, ‖∇f‖ = 8.8069e-02, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 56, Δt 4.94 s: f = -2.056856099700e+00, ‖∇f‖ = 8.3587e-02, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 57, Δt 6.07 s: f = -2.057479296853e+00, ‖∇f‖ = 4.4471e-02, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 58, Δt 4.81 s: f = -2.057912211557e+00, ‖∇f‖ = 5.9322e-02, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 59, Δt 6.08 s: f = -2.058287106374e+00, ‖∇f‖ = 6.0138e-02, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 60, Δt 4.78 s: f = -2.058998688168e+00, ‖∇f‖ = 6.2214e-02, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 61, Δt 10.81 s: f = -2.059475430386e+00, ‖∇f‖ = 1.0083e-01, α = 4.82e-01, m = 20, nfg = 2 +[ Info: LBFGS: iter 62, Δt 4.85 s: f = -2.060082712314e+00, ‖∇f‖ = 6.8338e-02, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 63, Δt 6.18 s: f = -2.060482611701e+00, ‖∇f‖ = 7.3298e-02, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 64, Δt 4.92 s: f = -2.060741140972e+00, ‖∇f‖ = 9.5443e-02, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 65, Δt 4.88 s: f = -2.061312696274e+00, ‖∇f‖ = 7.1658e-02, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 66, Δt 5.97 s: f = -2.061709741165e+00, ‖∇f‖ = 5.4940e-02, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 67, Δt 4.87 s: f = -2.062078223556e+00, ‖∇f‖ = 5.4620e-02, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 68, Δt 6.00 s: f = -2.062377140676e+00, ‖∇f‖ = 7.1416e-02, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 69, Δt 10.93 s: f = -2.062698062544e+00, ‖∇f‖ = 9.6845e-02, α = 5.00e-01, m = 20, nfg = 2 +[ Info: LBFGS: iter 70, Δt 5.04 s: f = -2.063163727897e+00, ‖∇f‖ = 7.1580e-02, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 71, Δt 6.13 s: f = -2.063925627174e+00, ‖∇f‖ = 9.0915e-02, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 72, Δt 5.02 s: f = -2.064219513055e+00, ‖∇f‖ = 8.0698e-02, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 73, Δt 4.91 s: f = -2.064673250535e+00, ‖∇f‖ = 7.5326e-02, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 74, Δt 6.12 s: f = -2.065226882556e+00, ‖∇f‖ = 1.0638e-01, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 75, Δt 4.85 s: f = -2.066059171824e+00, ‖∇f‖ = 9.5076e-02, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 76, Δt 4.99 s: f = -2.067195111916e+00, ‖∇f‖ = 1.5006e-01, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 77, Δt 5.94 s: f = -2.068289278812e+00, ‖∇f‖ = 2.3385e-01, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 78, Δt 16.76 s: f = -2.068482519585e+00, ‖∇f‖ = 6.6865e-01, α = 1.56e-01, m = 20, nfg = 3 +[ Info: LBFGS: iter 79, Δt 12.04 s: f = -2.068666326649e+00, ‖∇f‖ = 3.3508e-01, α = 2.43e-01, m = 20, nfg = 2 +┌ Warning: LBFGS: not converged to requested tol after 80 iterations and time 21.90 m: f = -2.069886404460e+00, ‖∇f‖ = 2.6573e-01 └ @ OptimKit ~/.julia/packages/OptimKit/OEwMx/src/lbfgs.jl:199 -E = -2.07035685333967 +E = -2.069886404460094 ```` @@ -219,7 +219,7 @@ E_ref = -2.09765625 ```` ```` -(E - E_ref) / E_ref = -0.013014237514049358 +(E - E_ref) / E_ref = -0.013238511095374089 ```` diff --git a/docs/src/examples/fermi_hubbard/main.ipynb b/docs/src/examples/fermi_hubbard/main.ipynb index c6d055d4f..260320382 100644 --- a/docs/src/examples/fermi_hubbard/main.ipynb +++ b/docs/src/examples/fermi_hubbard/main.ipynb @@ -1,16 +1,17 @@ { "cells": [ { - "outputs": [], "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "using Markdown #hide" - ], - "metadata": {}, - "execution_count": null + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "# Fermi-Hubbard model with $f\\mathbb{Z}_2 \\boxtimes U(1)$ symmetry, at large $U$ and half-filling\n", "\n", @@ -28,44 +29,44 @@ "workflow remains the same.\n", "\n", "First though, we make the example deterministic by seeding the RNG, and we make our imports:" - ], - "metadata": {} + ] }, { - "outputs": [], "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "using Random\n", "using TensorKit, PEPSKit\n", "using MPSKit: add_physical_charge\n", "Random.seed!(2928528937);" - ], - "metadata": {}, - "execution_count": null + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "## Defining the fermionic Hamiltonian\n", "\n", "Let us start by fixing the parameters of the Hubbard model. We're going to use a hopping of\n", "$t=1$ and a large $U=8$ on a $2 \\times 2$ unit cell:" - ], - "metadata": {} + ] }, { - "outputs": [], "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "t = 1.0\n", "U = 8.0\n", "lattice = InfiniteSquare(2, 2);" - ], - "metadata": {}, - "execution_count": null + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "In order to create fermionic tensors, one needs to define symmetry sectors using TensorKit's\n", "`FermionParity`. Not only do we want use fermion parity but we also want our\n", @@ -73,33 +74,34 @@ "using the [Deligne product](https://jutho.github.io/TensorKit.jl/stable/lib/sectors/#TensorKitSectors.deligneproduct-Tuple{Sector,%20Sector}),\n", "called through `⊠` which is obtained by typing `\\boxtimes+TAB`. We will not impose any extra\n", "spin symmetry, so we have:" - ], - "metadata": {} + ] }, { - "outputs": [], "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "fermion = fℤ₂\n", "particle_symmetry = U1Irrep\n", "spin_symmetry = Trivial\n", "S = fermion ⊠ particle_symmetry" - ], - "metadata": {}, - "execution_count": null + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "The next step is defining graded virtual PEPS and environment spaces using `S`. Here we also\n", "use the symmetry sector to impose half-filling. That is all we need to define the Hubbard\n", "Hamiltonian:" - ], - "metadata": {} + ] }, { - "outputs": [], "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "D, χ = 1, 1\n", "V_peps = Vect[S]((0, 0) => 2 * D, (1, 1) => D, (1, -1) => D)\n", @@ -109,113 +111,111 @@ "S_aux = S((1, 1))\n", "H₀ = hubbard_model(ComplexF64, particle_symmetry, spin_symmetry, lattice; t, U)\n", "H = add_physical_charge(H₀, fill(S_aux, size(H₀.lattice)...));" - ], - "metadata": {}, - "execution_count": null + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "## Finding the ground state\n", "\n", "Again, the procedure of ground state optimization is very similar to before. First, we\n", "define all algorithmic parameters:" - ], - "metadata": {} + ] }, { - "outputs": [], "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "boundary_alg = (; tol = 1.0e-8, alg = :simultaneous, trunc = (; alg = :fixedspace))\n", - "gradient_alg = (; tol = 1.0e-6, alg = :eigsolver, maxiter = 10, iterscheme = :diffgauge)\n", + "gradient_alg = (; tol = 1.0e-6, alg = :eigsolver, maxiter = 10, iterscheme = :fixed)\n", "optimizer_alg = (; tol = 1.0e-4, alg = :lbfgs, maxiter = 80, ls_maxiter = 3, ls_maxfg = 3)" - ], - "metadata": {}, - "execution_count": null + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "Second, we initialize a PEPS state and environment (which we converge) constructed from\n", "symmetric physical and virtual spaces:" - ], - "metadata": {} + ] }, { - "outputs": [], "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "physical_spaces = physicalspace(H)\n", "virtual_spaces = fill(V_peps, size(lattice)...)\n", "peps₀ = InfinitePEPS(randn, ComplexF64, physical_spaces, virtual_spaces)\n", "env₀, = leading_boundary(CTMRGEnv(peps₀, V_env), peps₀; boundary_alg...);" - ], - "metadata": {}, - "execution_count": null + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "And third, we start the ground state search (this does take quite long):" - ], - "metadata": {} + ] }, { - "outputs": [], "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "peps, env, E, info = fixedpoint(\n", " H, peps₀, env₀; boundary_alg, gradient_alg, optimizer_alg, verbosity = 3\n", ")\n", "@show E;" - ], - "metadata": {}, - "execution_count": null + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "Finally, let's compare the obtained energy against a reference energy from a QMC study by\n", "[Qin et al.](@cite qin_benchmark_2016). With the parameters specified above, they obtain an\n", "energy of $E_\\text{ref} \\approx 4 \\times -0.5244140625 = -2.09765625$ (the factor 4 comes\n", "from the $2 \\times 2$ unit cell that we use here). Thus, we find:" - ], - "metadata": {} + ] }, { - "outputs": [], "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "E_ref = -2.09765625\n", "@show (E - E_ref) / E_ref;" - ], - "metadata": {}, - "execution_count": null + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "---\n", "\n", "*This notebook was generated using [Literate.jl](https://github.com/fredrikekre/Literate.jl).*" - ], - "metadata": {} + ] } ], - "nbformat_minor": 3, "metadata": { + "kernelspec": { + "display_name": "Julia 1.12.5", + "language": "julia", + "name": "julia-1.12" + }, "language_info": { "file_extension": ".jl", "mimetype": "application/julia", "name": "julia", "version": "1.12.5" - }, - "kernelspec": { - "name": "julia-1.12", - "display_name": "Julia 1.12.5", - "language": "julia" } }, - "nbformat": 4 + "nbformat": 4, + "nbformat_minor": 3 } \ No newline at end of file diff --git a/docs/src/examples/j1j2_su/index.md b/docs/src/examples/j1j2_su/index.md index baf3d1fbb..5edd3051e 100644 --- a/docs/src/examples/j1j2_su/index.md +++ b/docs/src/examples/j1j2_su/index.md @@ -27,7 +27,7 @@ We first import all required modules and seed the RNG: ````julia using Random using TensorKit, PEPSKit -Random.seed!(29385293); +Random.seed!(29385294); ```` ## Simple updating a challenging phase @@ -69,36 +69,41 @@ end ```` ```` +[ Info: --- Time evolution (simple update), dt = 0.01 --- [ Info: Space of x-weight at [1, 1] = Rep[U₁](0 => 2, 1/2 => 1, -1/2 => 1) -[ Info: SU iter 1 : dt = 0.01, |Δλ| = 1.189e+00. Time = 0.034 s/it +[ Info: SU iter 1 : |Δλ| = 1.190e+00. Time = 118.568 s/it [ Info: Space of x-weight at [1, 1] = Rep[U₁](0 => 2, 1 => 1, -1 => 1) -[ Info: SU iter 1833 : dt = 0.01, |Δλ| = 9.859e-09. Time = 0.037 s/it +[ Info: SU iter 1373 : |Δλ| = 9.898e-09. Time = 0.069 s/it [ Info: SU: bond weights have converged. -[ Info: Simple update finished. Total time elapsed: 70.90 s +[ Info: Time evolution finished in 225.44 s +[ Info: --- Time evolution (simple update), dt = 0.01 --- [ Info: Space of x-weight at [1, 1] = Rep[U₁](0 => 2, 1 => 1, -1 => 1) -[ Info: SU iter 1 : dt = 0.01, |Δλ| = 3.401e-04. Time = 0.037 s/it +[ Info: SU iter 1 : |Δλ| = 2.985e-04. Time = 0.089 s/it [ Info: Space of x-weight at [1, 1] = Rep[U₁](0 => 2, 1 => 1, -1 => 1) -[ Info: SU iter 523 : dt = 0.01, |Δλ| = 9.965e-09. Time = 0.037 s/it +[ Info: SU iter 523 : |Δλ| = 9.955e-09. Time = 0.070 s/it [ Info: SU: bond weights have converged. -[ Info: Simple update finished. Total time elapsed: 21.18 s +[ Info: Time evolution finished in 38.09 s +[ Info: --- Time evolution (simple update), dt = 0.01 --- [ Info: Space of x-weight at [1, 1] = Rep[U₁](0 => 2, 1 => 1, -1 => 1) -[ Info: SU iter 1 : dt = 0.01, |Δλ| = 3.526e-04. Time = 0.038 s/it +[ Info: SU iter 1 : |Δλ| = 3.001e-04. Time = 0.085 s/it [ Info: Space of x-weight at [1, 1] = Rep[U₁](0 => 2, 1 => 1, -1 => 1) -[ Info: SU iter 611 : dt = 0.01, |Δλ| = 9.848e-09. Time = 0.037 s/it +[ Info: SU iter 610 : |Δλ| = 9.971e-09. Time = 0.070 s/it [ Info: SU: bond weights have converged. -[ Info: Simple update finished. Total time elapsed: 24.83 s +[ Info: Time evolution finished in 44.48 s +[ Info: --- Time evolution (simple update), dt = 0.01 --- [ Info: Space of x-weight at [1, 1] = Rep[U₁](0 => 2, 1 => 1, -1 => 1) -[ Info: SU iter 1 : dt = 0.01, |Δλ| = 3.664e-04. Time = 0.037 s/it +[ Info: SU iter 1 : |Δλ| = 3.021e-04. Time = 0.084 s/it [ Info: Space of x-weight at [1, 1] = Rep[U₁](0 => 2, 1 => 1, -1 => 1) -[ Info: SU iter 735 : dt = 0.01, |Δλ| = 9.963e-09. Time = 0.092 s/it +[ Info: SU iter 740 : |Δλ| = 9.953e-09. Time = 0.070 s/it [ Info: SU: bond weights have converged. -[ Info: Simple update finished. Total time elapsed: 29.87 s +[ Info: Time evolution finished in 53.86 s +[ Info: --- Time evolution (simple update), dt = 0.01 --- [ Info: Space of x-weight at [1, 1] = Rep[U₁](0 => 2, 1 => 1, -1 => 1) -[ Info: SU iter 1 : dt = 0.01, |Δλ| = 3.828e-04. Time = 0.037 s/it +[ Info: SU iter 1 : |Δλ| = 3.089e-04. Time = 0.069 s/it [ Info: Space of x-weight at [1, 1] = Rep[U₁](0 => 2, 1 => 1, -1 => 1) -[ Info: SU iter 901 : dt = 0.01, |Δλ| = 9.995e-09. Time = 0.037 s/it +[ Info: SU iter 1140 : |Δλ| = 1.000e-08. Time = 0.070 s/it [ Info: SU: bond weights have converged. -[ Info: Simple update finished. Total time elapsed: 36.57 s +[ Info: Time evolution finished in 83.30 s ```` @@ -116,32 +121,185 @@ end ```` ```` +[ Info: --- Time evolution (simple update), dt = 0.001 --- [ Info: Space of x-weight at [1, 1] = Rep[U₁](0 => 2, 1 => 1, -1 => 1) -[ Info: SU iter 1 : dt = 0.001, |Δλ| = 4.477e-04. Time = 0.037 s/it +[ Info: SU iter 1 : |Δλ| = 7.604e-04. Time = 0.138 s/it [ Info: Space of x-weight at [1, 1] = Rep[U₁](0 => 2, 1 => 1, -1 => 1) -[ Info: SU iter 500 : dt = 0.001, |Δλ| = 2.767e-08. Time = 0.037 s/it +[ Info: SU iter 500 : |Δλ| = 1.692e-06. Time = 0.085 s/it [ Info: Space of x-weight at [1, 1] = Rep[U₁](0 => 2, 1 => 1, -1 => 1) -[ Info: SU iter 1000 : dt = 0.001, |Δλ| = 9.954e-09. Time = 0.037 s/it +[ Info: SU iter 1000 : |Δλ| = 1.002e-06. Time = 0.069 s/it [ Info: Space of x-weight at [1, 1] = Rep[U₁](0 => 2, 1 => 1, -1 => 1) -[ Info: SU iter 1500 : dt = 0.001, |Δλ| = 5.019e-09. Time = 0.038 s/it +[ Info: SU iter 1500 : |Δλ| = 6.304e-07. Time = 0.070 s/it [ Info: Space of x-weight at [1, 1] = Rep[U₁](0 => 2, 1 => 1, -1 => 1) -[ Info: SU iter 2000 : dt = 0.001, |Δλ| = 3.015e-09. Time = 0.039 s/it +[ Info: SU iter 2000 : |Δλ| = 4.078e-07. Time = 0.069 s/it [ Info: Space of x-weight at [1, 1] = Rep[U₁](0 => 2, 1 => 1, -1 => 1) -[ Info: SU iter 2500 : dt = 0.001, |Δλ| = 1.935e-09. Time = 0.090 s/it +[ Info: SU iter 2500 : |Δλ| = 2.688e-07. Time = 0.070 s/it [ Info: Space of x-weight at [1, 1] = Rep[U₁](0 => 2, 1 => 1, -1 => 1) -[ Info: SU iter 3000 : dt = 0.001, |Δλ| = 1.273e-09. Time = 0.037 s/it +[ Info: SU iter 3000 : |Δλ| = 1.797e-07. Time = 0.070 s/it [ Info: Space of x-weight at [1, 1] = Rep[U₁](0 => 2, 1 => 1, -1 => 1) -[ Info: SU iter 3295 : dt = 0.001, |Δλ| = 9.994e-10. Time = 0.036 s/it -[ Info: SU: bond weights have converged. -[ Info: Simple update finished. Total time elapsed: 132.66 s +[ Info: SU iter 3500 : |Δλ| = 1.217e-07. Time = 0.069 s/it +[ Info: Space of x-weight at [1, 1] = Rep[U₁](0 => 2, 1 => 1, -1 => 1) +[ Info: SU iter 4000 : |Δλ| = 8.352e-08. Time = 0.084 s/it +[ Info: Space of x-weight at [1, 1] = Rep[U₁](0 => 2, 1 => 1, -1 => 1) +[ Info: SU iter 4500 : |Δλ| = 5.817e-08. Time = 0.085 s/it +[ Info: Space of x-weight at [1, 1] = Rep[U₁](0 => 2, 1 => 1, -1 => 1) +[ Info: SU iter 5000 : |Δλ| = 4.132e-08. Time = 0.069 s/it +[ Info: Space of x-weight at [1, 1] = Rep[U₁](0 => 2, 1 => 1, -1 => 1) +[ Info: SU iter 5500 : |Δλ| = 3.002e-08. Time = 0.069 s/it +[ Info: Space of x-weight at [1, 1] = Rep[U₁](0 => 2, 1 => 1, -1 => 1) +[ Info: SU iter 6000 : |Δλ| = 2.216e-08. Time = 0.069 s/it +[ Info: Space of x-weight at [1, 1] = Rep[U₁](0 => 2, 1 => 1, -1 => 1) +[ Info: SU iter 6500 : |Δλ| = 1.651e-08. Time = 0.069 s/it +[ Info: Space of x-weight at [1, 1] = Rep[U₁](0 => 2, 1 => 1, -1 => 1) +[ Info: SU iter 7000 : |Δλ| = 1.235e-08. Time = 0.084 s/it +[ Info: Space of x-weight at [1, 1] = Rep[U₁](0 => 2, 1 => 1, -1 => 1) +[ Info: SU iter 7500 : |Δλ| = 9.271e-09. Time = 0.085 s/it +[ Info: Space of x-weight at [1, 1] = Rep[U₁](0 => 2, 1 => 1, -1 => 1) +[ Info: SU iter 8000 : |Δλ| = 6.980e-09. Time = 0.086 s/it [ Info: Space of x-weight at [1, 1] = Rep[U₁](0 => 2, 1 => 1, -1 => 1) -[ Info: SU iter 1 : dt = 0.0001, |Δλ| = 4.467e-05. Time = 0.036 s/it +[ Info: SU iter 8500 : |Δλ| = 5.278e-09. Time = 0.069 s/it [ Info: Space of x-weight at [1, 1] = Rep[U₁](0 => 2, 1 => 1, -1 => 1) -[ Info: SU iter 500 : dt = 0.0001, |Δλ| = 1.150e-09. Time = 0.035 s/it +[ Info: SU iter 9000 : |Δλ| = 4.021e-09. Time = 0.069 s/it [ Info: Space of x-weight at [1, 1] = Rep[U₁](0 => 2, 1 => 1, -1 => 1) -[ Info: SU iter 873 : dt = 0.0001, |Δλ| = 9.998e-10. Time = 0.037 s/it +[ Info: SU iter 9500 : |Δλ| = 3.090e-09. Time = 0.070 s/it +[ Info: Space of x-weight at [1, 1] = Rep[U₁](0 => 2, 1 => 1, -1 => 1) +[ Info: SU iter 10000 : |Δλ| = 2.394e-09. Time = 0.085 s/it +[ Info: Space of x-weight at [1, 1] = Rep[U₁](0 => 2, 1 => 1, -1 => 1) +[ Info: SU iter 10500 : |Δλ| = 1.878e-09. Time = 0.070 s/it +[ Info: Space of x-weight at [1, 1] = Rep[U₁](0 => 2, 1 => 1, -1 => 1) +[ Info: SU iter 11000 : |Δλ| = 1.498e-09. Time = 0.070 s/it +[ Info: Space of x-weight at [1, 1] = Rep[U₁](0 => 2, 1 => 1, -1 => 1) +[ Info: SU iter 11500 : |Δλ| = 1.209e-09. Time = 0.071 s/it +[ Info: Space of x-weight at [1, 1] = Rep[U₁](0 => 2, 1 => 1, -1 => 1) +[ Info: SU iter 11962 : |Δλ| = 9.999e-10. Time = 0.070 s/it [ Info: SU: bond weights have converged. -[ Info: Simple update finished. Total time elapsed: 34.62 s +[ Info: Time evolution finished in 870.98 s +[ Info: --- Time evolution (simple update), dt = 0.0001 --- +[ Info: Space of x-weight at [1, 1] = Rep[U₁](0 => 2, 1 => 1, -1 => 1) +[ Info: SU iter 1 : |Δλ| = 7.683e-05. Time = 0.070 s/it +[ Info: Space of x-weight at [1, 1] = Rep[U₁](0 => 2, 1 => 1, -1 => 1) +[ Info: SU iter 500 : |Δλ| = 3.277e-08. Time = 0.069 s/it +[ Info: Space of x-weight at [1, 1] = Rep[U₁](0 => 2, 1 => 1, -1 => 1) +[ Info: SU iter 1000 : |Δλ| = 2.995e-08. Time = 0.085 s/it +[ Info: Space of x-weight at [1, 1] = Rep[U₁](0 => 2, 1 => 1, -1 => 1) +[ Info: SU iter 1500 : |Δλ| = 2.752e-08. Time = 0.070 s/it +[ Info: Space of x-weight at [1, 1] = Rep[U₁](0 => 2, 1 => 1, -1 => 1) +[ Info: SU iter 2000 : |Δλ| = 2.540e-08. Time = 0.069 s/it +[ Info: Space of x-weight at [1, 1] = Rep[U₁](0 => 2, 1 => 1, -1 => 1) +[ Info: SU iter 2500 : |Δλ| = 2.355e-08. Time = 0.069 s/it +[ Info: Space of x-weight at [1, 1] = Rep[U₁](0 => 2, 1 => 1, -1 => 1) +[ Info: SU iter 3000 : |Δλ| = 2.193e-08. Time = 0.071 s/it +[ Info: Space of x-weight at [1, 1] = Rep[U₁](0 => 2, 1 => 1, -1 => 1) +[ Info: SU iter 3500 : |Δλ| = 2.049e-08. Time = 0.069 s/it +[ Info: Space of x-weight at [1, 1] = Rep[U₁](0 => 2, 1 => 1, -1 => 1) +[ Info: SU iter 4000 : |Δλ| = 1.920e-08. Time = 0.069 s/it +[ Info: Space of x-weight at [1, 1] = Rep[U₁](0 => 2, 1 => 1, -1 => 1) +[ Info: SU iter 4500 : |Δλ| = 1.805e-08. Time = 0.069 s/it +[ Info: Space of x-weight at [1, 1] = Rep[U₁](0 => 2, 1 => 1, -1 => 1) +[ Info: SU iter 5000 : |Δλ| = 1.700e-08. Time = 0.071 s/it +[ Info: Space of x-weight at [1, 1] = Rep[U₁](0 => 2, 1 => 1, -1 => 1) +[ Info: SU iter 5500 : |Δλ| = 1.605e-08. Time = 0.087 s/it +[ Info: Space of x-weight at [1, 1] = Rep[U₁](0 => 2, 1 => 1, -1 => 1) +[ Info: SU iter 6000 : |Δλ| = 1.518e-08. Time = 0.069 s/it +[ Info: Space of x-weight at [1, 1] = Rep[U₁](0 => 2, 1 => 1, -1 => 1) +[ Info: SU iter 6500 : |Δλ| = 1.438e-08. Time = 0.070 s/it +[ Info: Space of x-weight at [1, 1] = Rep[U₁](0 => 2, 1 => 1, -1 => 1) +[ Info: SU iter 7000 : |Δλ| = 1.363e-08. Time = 0.069 s/it +[ Info: Space of x-weight at [1, 1] = Rep[U₁](0 => 2, 1 => 1, -1 => 1) +[ Info: SU iter 7500 : |Δλ| = 1.294e-08. Time = 0.069 s/it +[ Info: Space of x-weight at [1, 1] = Rep[U₁](0 => 2, 1 => 1, -1 => 1) +[ Info: SU iter 8000 : |Δλ| = 1.230e-08. Time = 0.073 s/it +[ Info: Space of x-weight at [1, 1] = Rep[U₁](0 => 2, 1 => 1, -1 => 1) +[ Info: SU iter 8500 : |Δλ| = 1.170e-08. Time = 0.069 s/it +[ Info: Space of x-weight at [1, 1] = Rep[U₁](0 => 2, 1 => 1, -1 => 1) +[ Info: SU iter 9000 : |Δλ| = 1.114e-08. Time = 0.084 s/it +[ Info: Space of x-weight at [1, 1] = Rep[U₁](0 => 2, 1 => 1, -1 => 1) +[ Info: SU iter 9500 : |Δλ| = 1.060e-08. Time = 0.070 s/it +[ Info: Space of x-weight at [1, 1] = Rep[U₁](0 => 2, 1 => 1, -1 => 1) +[ Info: SU iter 10000 : |Δλ| = 1.011e-08. Time = 0.070 s/it +[ Info: Space of x-weight at [1, 1] = Rep[U₁](0 => 2, 1 => 1, -1 => 1) +[ Info: SU iter 10500 : |Δλ| = 9.635e-09. Time = 0.069 s/it +[ Info: Space of x-weight at [1, 1] = Rep[U₁](0 => 2, 1 => 1, -1 => 1) +[ Info: SU iter 11000 : |Δλ| = 9.190e-09. Time = 0.070 s/it +[ Info: Space of x-weight at [1, 1] = Rep[U₁](0 => 2, 1 => 1, -1 => 1) +[ Info: SU iter 11500 : |Δλ| = 8.770e-09. Time = 0.070 s/it +[ Info: Space of x-weight at [1, 1] = Rep[U₁](0 => 2, 1 => 1, -1 => 1) +[ Info: SU iter 12000 : |Δλ| = 8.372e-09. Time = 0.085 s/it +[ Info: Space of x-weight at [1, 1] = Rep[U₁](0 => 2, 1 => 1, -1 => 1) +[ Info: SU iter 12500 : |Δλ| = 7.995e-09. Time = 0.070 s/it +[ Info: Space of x-weight at [1, 1] = Rep[U₁](0 => 2, 1 => 1, -1 => 1) +[ Info: SU iter 13000 : |Δλ| = 7.637e-09. Time = 0.069 s/it +[ Info: Space of x-weight at [1, 1] = Rep[U₁](0 => 2, 1 => 1, -1 => 1) +[ Info: SU iter 13500 : |Δλ| = 7.298e-09. Time = 0.093 s/it +[ Info: Space of x-weight at [1, 1] = Rep[U₁](0 => 2, 1 => 1, -1 => 1) +[ Info: SU iter 14000 : |Δλ| = 6.976e-09. Time = 0.069 s/it +[ Info: Space of x-weight at [1, 1] = Rep[U₁](0 => 2, 1 => 1, -1 => 1) +[ Info: SU iter 14500 : |Δλ| = 6.670e-09. Time = 0.070 s/it +[ Info: Space of x-weight at [1, 1] = Rep[U₁](0 => 2, 1 => 1, -1 => 1) +[ Info: SU iter 15000 : |Δλ| = 6.379e-09. Time = 0.070 s/it +[ Info: Space of x-weight at [1, 1] = Rep[U₁](0 => 2, 1 => 1, -1 => 1) +[ Info: SU iter 15500 : |Δλ| = 6.102e-09. Time = 0.085 s/it +[ Info: Space of x-weight at [1, 1] = Rep[U₁](0 => 2, 1 => 1, -1 => 1) +[ Info: SU iter 16000 : |Δλ| = 5.839e-09. Time = 0.069 s/it +[ Info: Space of x-weight at [1, 1] = Rep[U₁](0 => 2, 1 => 1, -1 => 1) +[ Info: SU iter 16500 : |Δλ| = 5.588e-09. Time = 0.071 s/it +[ Info: Space of x-weight at [1, 1] = Rep[U₁](0 => 2, 1 => 1, -1 => 1) +[ Info: SU iter 17000 : |Δλ| = 5.349e-09. Time = 0.085 s/it +[ Info: Space of x-weight at [1, 1] = Rep[U₁](0 => 2, 1 => 1, -1 => 1) +[ Info: SU iter 17500 : |Δλ| = 5.122e-09. Time = 0.070 s/it +[ Info: Space of x-weight at [1, 1] = Rep[U₁](0 => 2, 1 => 1, -1 => 1) +[ Info: SU iter 18000 : |Δλ| = 4.905e-09. Time = 0.069 s/it +[ Info: Space of x-weight at [1, 1] = Rep[U₁](0 => 2, 1 => 1, -1 => 1) +[ Info: SU iter 18500 : |Δλ| = 4.699e-09. Time = 0.070 s/it +[ Info: Space of x-weight at [1, 1] = Rep[U₁](0 => 2, 1 => 1, -1 => 1) +[ Info: SU iter 19000 : |Δλ| = 4.502e-09. Time = 0.070 s/it +[ Info: Space of x-weight at [1, 1] = Rep[U₁](0 => 2, 1 => 1, -1 => 1) +[ Info: SU iter 19500 : |Δλ| = 4.314e-09. Time = 0.086 s/it +[ Info: Space of x-weight at [1, 1] = Rep[U₁](0 => 2, 1 => 1, -1 => 1) +[ Info: SU iter 20000 : |Δλ| = 4.134e-09. Time = 0.069 s/it +[ Info: Space of x-weight at [1, 1] = Rep[U₁](0 => 2, 1 => 1, -1 => 1) +[ Info: SU iter 20500 : |Δλ| = 3.963e-09. Time = 0.069 s/it +[ Info: Space of x-weight at [1, 1] = Rep[U₁](0 => 2, 1 => 1, -1 => 1) +[ Info: SU iter 21000 : |Δλ| = 3.800e-09. Time = 0.070 s/it +[ Info: Space of x-weight at [1, 1] = Rep[U₁](0 => 2, 1 => 1, -1 => 1) +[ Info: SU iter 21500 : |Δλ| = 3.644e-09. Time = 0.069 s/it +[ Info: Space of x-weight at [1, 1] = Rep[U₁](0 => 2, 1 => 1, -1 => 1) +[ Info: SU iter 22000 : |Δλ| = 3.494e-09. Time = 0.069 s/it +[ Info: Space of x-weight at [1, 1] = Rep[U₁](0 => 2, 1 => 1, -1 => 1) +[ Info: SU iter 22500 : |Δλ| = 3.352e-09. Time = 0.085 s/it +[ Info: Space of x-weight at [1, 1] = Rep[U₁](0 => 2, 1 => 1, -1 => 1) +[ Info: SU iter 23000 : |Δλ| = 3.216e-09. Time = 0.069 s/it +[ Info: Space of x-weight at [1, 1] = Rep[U₁](0 => 2, 1 => 1, -1 => 1) +[ Info: SU iter 23500 : |Δλ| = 3.085e-09. Time = 0.070 s/it +[ Info: Space of x-weight at [1, 1] = Rep[U₁](0 => 2, 1 => 1, -1 => 1) +[ Info: SU iter 24000 : |Δλ| = 2.961e-09. Time = 0.086 s/it +[ Info: Space of x-weight at [1, 1] = Rep[U₁](0 => 2, 1 => 1, -1 => 1) +[ Info: SU iter 24500 : |Δλ| = 2.842e-09. Time = 0.070 s/it +[ Info: Space of x-weight at [1, 1] = Rep[U₁](0 => 2, 1 => 1, -1 => 1) +[ Info: SU iter 25000 : |Δλ| = 2.728e-09. Time = 0.085 s/it +[ Info: Space of x-weight at [1, 1] = Rep[U₁](0 => 2, 1 => 1, -1 => 1) +[ Info: SU iter 25500 : |Δλ| = 2.619e-09. Time = 0.069 s/it +[ Info: Space of x-weight at [1, 1] = Rep[U₁](0 => 2, 1 => 1, -1 => 1) +[ Info: SU iter 26000 : |Δλ| = 2.515e-09. Time = 0.085 s/it +[ Info: Space of x-weight at [1, 1] = Rep[U₁](0 => 2, 1 => 1, -1 => 1) +[ Info: SU iter 26500 : |Δλ| = 2.415e-09. Time = 0.069 s/it +[ Info: Space of x-weight at [1, 1] = Rep[U₁](0 => 2, 1 => 1, -1 => 1) +[ Info: SU iter 27000 : |Δλ| = 2.319e-09. Time = 0.069 s/it +[ Info: Space of x-weight at [1, 1] = Rep[U₁](0 => 2, 1 => 1, -1 => 1) +[ Info: SU iter 27500 : |Δλ| = 2.228e-09. Time = 0.084 s/it +[ Info: Space of x-weight at [1, 1] = Rep[U₁](0 => 2, 1 => 1, -1 => 1) +[ Info: SU iter 28000 : |Δλ| = 2.140e-09. Time = 0.069 s/it +[ Info: Space of x-weight at [1, 1] = Rep[U₁](0 => 2, 1 => 1, -1 => 1) +[ Info: SU iter 28500 : |Δλ| = 2.056e-09. Time = 0.069 s/it +[ Info: Space of x-weight at [1, 1] = Rep[U₁](0 => 2, 1 => 1, -1 => 1) +[ Info: SU iter 29000 : |Δλ| = 1.976e-09. Time = 0.069 s/it +[ Info: Space of x-weight at [1, 1] = Rep[U₁](0 => 2, 1 => 1, -1 => 1) +[ Info: SU iter 29500 : |Δλ| = 1.899e-09. Time = 0.069 s/it +[ Info: Space of x-weight at [1, 1] = Rep[U₁](0 => 2, 1 => 1, -1 => 1) +[ Info: SU iter 30000 : |Δλ| = 1.825e-09. Time = 0.069 s/it +┌ Warning: SU: bond weights have not converged. +└ @ PEPSKit ~/git/PEPSKit.jl/src/algorithms/time_evolution/simpleupdate.jl:241 +[ Info: Time evolution finished in 2182.89 s ```` @@ -161,7 +319,7 @@ E = expectation_value(peps, H, env) / (Nr * Nc) ```` ```` --0.4908483447932438 +-0.4908450911219574 ```` Let us compare that estimate with benchmark data obtained from the @@ -174,7 +332,7 @@ E_ref = -0.49425 ```` ```` -(E - E_ref) / abs(E_ref) = 0.006882458688429391 +(E - E_ref) / abs(E_ref) = 0.006889041736049819 ```` @@ -189,99 +347,105 @@ In order to break some of the $C_{4v}$ symmetry of the PEPS, we will add a bit o This is conviently done using MPSKit's `randomize!` function. (Breaking some of the spatial symmetry can be advantageous for obtaining lower energies.) +In our optimization, we will use a fixed-point differentiation scheme which requires a +gauge fixing of the contraction environment +(specified by the `gradient_alg = (; iterscheme = :fixed)` setting). +Since this gauge fixing involves potentially complex phases, we have to convert our +real-valued contraction environment to complex numbers before the optimization. + ````julia using MPSKit: randomize! noise_peps = InfinitePEPS(randomize!.(deepcopy(peps.A))) peps₀ = peps + 1.0e-1noise_peps peps_opt, env_opt, E_opt, = fixedpoint( - H, peps₀, env; - optimizer_alg = (; tol = 1.0e-4, maxiter = 80), gradient_alg = (; iterscheme = :diffgauge) + H, peps₀, complex(env); + optimizer_alg = (; tol = 1.0e-4, maxiter = 80), gradient_alg = (; iterscheme = :fixed) ); ```` ```` -[ Info: LBFGS: initializing with f = -1.917915769323e+00, ‖∇f‖ = 4.2598e-01 -[ Info: LBFGS: iter 1, Δt 16.45 s: f = -1.921875846208e+00, ‖∇f‖ = 3.6931e-01, α = 1.00e+00, m = 0, nfg = 1 -[ Info: LBFGS: iter 2, Δt 12.76 s: f = -1.942022618990e+00, ‖∇f‖ = 2.0804e-01, α = 1.00e+00, m = 1, nfg = 1 -[ Info: LBFGS: iter 3, Δt 12.69 s: f = -1.948412125614e+00, ‖∇f‖ = 1.4224e-01, α = 1.00e+00, m = 2, nfg = 1 -[ Info: LBFGS: iter 4, Δt 11.25 s: f = -1.954734282994e+00, ‖∇f‖ = 1.6428e-01, α = 1.00e+00, m = 3, nfg = 1 -[ Info: LBFGS: iter 5, Δt 12.79 s: f = -1.957501635760e+00, ‖∇f‖ = 1.8493e-01, α = 1.00e+00, m = 4, nfg = 1 -[ Info: LBFGS: iter 6, Δt 12.20 s: f = -1.959162742352e+00, ‖∇f‖ = 1.0888e-01, α = 1.00e+00, m = 5, nfg = 1 -[ Info: LBFGS: iter 7, Δt 12.06 s: f = -1.961758227820e+00, ‖∇f‖ = 9.0418e-02, α = 1.00e+00, m = 6, nfg = 1 -[ Info: LBFGS: iter 8, Δt 12.57 s: f = -1.963102797068e+00, ‖∇f‖ = 7.7890e-02, α = 1.00e+00, m = 7, nfg = 1 -[ Info: LBFGS: iter 9, Δt 12.02 s: f = -1.965635307562e+00, ‖∇f‖ = 5.7454e-02, α = 1.00e+00, m = 8, nfg = 1 -[ Info: LBFGS: iter 10, Δt 12.22 s: f = -1.967012786653e+00, ‖∇f‖ = 1.0695e-01, α = 1.00e+00, m = 9, nfg = 1 -[ Info: LBFGS: iter 11, Δt 12.75 s: f = -1.968331989207e+00, ‖∇f‖ = 4.7357e-02, α = 1.00e+00, m = 10, nfg = 1 -[ Info: LBFGS: iter 12, Δt 12.52 s: f = -1.968984356192e+00, ‖∇f‖ = 3.6819e-02, α = 1.00e+00, m = 11, nfg = 1 -[ Info: LBFGS: iter 13, Δt 13.08 s: f = -1.969738509271e+00, ‖∇f‖ = 3.8320e-02, α = 1.00e+00, m = 12, nfg = 1 -[ Info: LBFGS: iter 14, Δt 13.97 s: f = -1.970765612340e+00, ‖∇f‖ = 4.1807e-02, α = 1.00e+00, m = 13, nfg = 1 -[ Info: LBFGS: iter 15, Δt 13.32 s: f = -1.971316317211e+00, ‖∇f‖ = 4.5580e-02, α = 1.00e+00, m = 14, nfg = 1 -[ Info: LBFGS: iter 16, Δt 13.46 s: f = -1.971822370984e+00, ‖∇f‖ = 2.4262e-02, α = 1.00e+00, m = 15, nfg = 1 -[ Info: LBFGS: iter 17, Δt 12.99 s: f = -1.972203923570e+00, ‖∇f‖ = 2.4564e-02, α = 1.00e+00, m = 16, nfg = 1 -[ Info: LBFGS: iter 18, Δt 12.95 s: f = -1.972802900923e+00, ‖∇f‖ = 2.8491e-02, α = 1.00e+00, m = 17, nfg = 1 -[ Info: LBFGS: iter 19, Δt 13.74 s: f = -1.973589666789e+00, ‖∇f‖ = 3.2039e-02, α = 1.00e+00, m = 18, nfg = 1 -[ Info: LBFGS: iter 20, Δt 13.18 s: f = -1.973913379566e+00, ‖∇f‖ = 5.1316e-02, α = 1.00e+00, m = 19, nfg = 1 -[ Info: LBFGS: iter 21, Δt 13.55 s: f = -1.974416985201e+00, ‖∇f‖ = 1.8951e-02, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 22, Δt 13.47 s: f = -1.974639779350e+00, ‖∇f‖ = 1.8591e-02, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 23, Δt 13.23 s: f = -1.974980106926e+00, ‖∇f‖ = 1.9583e-02, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 24, Δt 14.00 s: f = -1.975202056049e+00, ‖∇f‖ = 3.9045e-02, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 25, Δt 13.33 s: f = -1.975442229571e+00, ‖∇f‖ = 1.8554e-02, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 26, Δt 13.95 s: f = -1.975560352122e+00, ‖∇f‖ = 1.5857e-02, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 27, Δt 13.25 s: f = -1.975643058810e+00, ‖∇f‖ = 1.2993e-02, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 28, Δt 13.13 s: f = -1.975704724372e+00, ‖∇f‖ = 1.9944e-02, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 29, Δt 14.12 s: f = -1.975779000149e+00, ‖∇f‖ = 1.1828e-02, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 30, Δt 13.48 s: f = -1.975862495962e+00, ‖∇f‖ = 1.0766e-02, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 31, Δt 13.36 s: f = -1.975947783240e+00, ‖∇f‖ = 9.5066e-03, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 32, Δt 13.70 s: f = -1.976052804517e+00, ‖∇f‖ = 1.5333e-02, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 33, Δt 13.19 s: f = -1.976106986012e+00, ‖∇f‖ = 2.1883e-02, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 34, Δt 13.41 s: f = -1.976164433529e+00, ‖∇f‖ = 7.9599e-03, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 35, Δt 14.17 s: f = -1.976190058936e+00, ‖∇f‖ = 6.7778e-03, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 36, Δt 13.45 s: f = -1.976224201954e+00, ‖∇f‖ = 7.6168e-03, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 37, Δt 13.73 s: f = -1.976267854501e+00, ‖∇f‖ = 1.7393e-02, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 38, Δt 13.84 s: f = -1.976315102216e+00, ‖∇f‖ = 8.0912e-03, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 39, Δt 13.22 s: f = -1.976342354040e+00, ‖∇f‖ = 6.1351e-03, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 40, Δt 13.44 s: f = -1.976371404915e+00, ‖∇f‖ = 6.6216e-03, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 41, Δt 13.71 s: f = -1.976421353914e+00, ‖∇f‖ = 8.2468e-03, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 42, Δt 13.47 s: f = -1.976466860391e+00, ‖∇f‖ = 9.9248e-03, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 43, Δt 13.64 s: f = -1.976507540729e+00, ‖∇f‖ = 6.9877e-03, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 44, Δt 14.04 s: f = -1.976535673591e+00, ‖∇f‖ = 5.6235e-03, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 45, Δt 13.18 s: f = -1.976573549708e+00, ‖∇f‖ = 7.4445e-03, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 46, Δt 13.51 s: f = -1.976590425955e+00, ‖∇f‖ = 1.5662e-02, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 47, Δt 13.88 s: f = -1.976621753066e+00, ‖∇f‖ = 6.2307e-03, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 48, Δt 13.08 s: f = -1.976639047822e+00, ‖∇f‖ = 5.1405e-03, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 49, Δt 13.33 s: f = -1.976657938685e+00, ‖∇f‖ = 1.1028e-02, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 50, Δt 13.74 s: f = -1.976676808863e+00, ‖∇f‖ = 9.7274e-03, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 51, Δt 13.29 s: f = -1.976691906282e+00, ‖∇f‖ = 5.8050e-03, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 52, Δt 13.20 s: f = -1.976710655182e+00, ‖∇f‖ = 5.7333e-03, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 53, Δt 13.66 s: f = -1.976732623549e+00, ‖∇f‖ = 5.2887e-03, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 54, Δt 13.26 s: f = -1.976766233304e+00, ‖∇f‖ = 8.5088e-03, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 55, Δt 13.39 s: f = -1.976781456137e+00, ‖∇f‖ = 1.1582e-02, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 56, Δt 24.51 s: f = -1.976799996356e+00, ‖∇f‖ = 5.1443e-03, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 57, Δt 13.08 s: f = -1.976812132182e+00, ‖∇f‖ = 3.9200e-03, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 58, Δt 13.75 s: f = -1.976828056507e+00, ‖∇f‖ = 6.6193e-03, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 59, Δt 13.05 s: f = -1.976851081866e+00, ‖∇f‖ = 6.9020e-03, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 60, Δt 13.15 s: f = -1.976872354754e+00, ‖∇f‖ = 5.8100e-03, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 61, Δt 13.76 s: f = -1.976887353263e+00, ‖∇f‖ = 1.5507e-02, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 62, Δt 13.33 s: f = -1.976913441456e+00, ‖∇f‖ = 5.4576e-03, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 63, Δt 13.19 s: f = -1.976925022985e+00, ‖∇f‖ = 4.5942e-03, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 64, Δt 13.90 s: f = -1.976945457629e+00, ‖∇f‖ = 6.1499e-03, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 65, Δt 13.54 s: f = -1.976948121976e+00, ‖∇f‖ = 1.2404e-02, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 66, Δt 13.37 s: f = -1.976990622832e+00, ‖∇f‖ = 1.0997e-02, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 67, Δt 13.89 s: f = -1.977019727681e+00, ‖∇f‖ = 5.3064e-03, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 68, Δt 13.40 s: f = -1.977038041468e+00, ‖∇f‖ = 3.9154e-03, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 69, Δt 13.22 s: f = -1.977061135927e+00, ‖∇f‖ = 5.5351e-03, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 70, Δt 25.93 s: f = -1.977069437407e+00, ‖∇f‖ = 9.1542e-03, α = 2.16e-01, m = 20, nfg = 2 -[ Info: LBFGS: iter 71, Δt 11.98 s: f = -1.977082632066e+00, ‖∇f‖ = 7.0584e-03, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 72, Δt 12.86 s: f = -1.977109455547e+00, ‖∇f‖ = 6.5585e-03, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 73, Δt 12.26 s: f = -1.977124023586e+00, ‖∇f‖ = 1.0470e-02, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 74, Δt 12.29 s: f = -1.977136222448e+00, ‖∇f‖ = 9.2794e-03, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 75, Δt 12.79 s: f = -1.977174289195e+00, ‖∇f‖ = 6.4735e-03, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 76, Δt 12.52 s: f = -1.977220054869e+00, ‖∇f‖ = 6.4042e-03, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 77, Δt 12.26 s: f = -1.977245223602e+00, ‖∇f‖ = 6.9440e-03, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 78, Δt 12.87 s: f = -1.977264616541e+00, ‖∇f‖ = 1.5115e-02, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 79, Δt 12.17 s: f = -1.977292459833e+00, ‖∇f‖ = 6.6226e-03, α = 1.00e+00, m = 20, nfg = 1 -┌ Warning: LBFGS: not converged to requested tol after 80 iterations and time 25.05 m: f = -1.977304651029e+00, ‖∇f‖ = 4.5558e-03 +[ Info: LBFGS: initializing with f = -1.887578587634e+00, ‖∇f‖ = 7.8780e-01 +[ Info: LBFGS: iter 1, Δt 2.11 m: f = -1.926388092775e+00, ‖∇f‖ = 4.7092e-01, α = 1.19e+01, m = 0, nfg = 4 +[ Info: LBFGS: iter 2, Δt 30.68 s: f = -1.936546032899e+00, ‖∇f‖ = 2.1792e-01, α = 1.00e+00, m = 1, nfg = 1 +[ Info: LBFGS: iter 3, Δt 29.68 s: f = -1.942713411326e+00, ‖∇f‖ = 2.0267e-01, α = 1.00e+00, m = 2, nfg = 1 +[ Info: LBFGS: iter 4, Δt 29.00 s: f = -1.952873152491e+00, ‖∇f‖ = 1.6713e-01, α = 1.00e+00, m = 3, nfg = 1 +[ Info: LBFGS: iter 5, Δt 26.25 s: f = -1.958619448466e+00, ‖∇f‖ = 1.5871e-01, α = 1.00e+00, m = 4, nfg = 1 +[ Info: LBFGS: iter 6, Δt 27.27 s: f = -1.961555293087e+00, ‖∇f‖ = 9.2861e-02, α = 1.00e+00, m = 5, nfg = 1 +[ Info: LBFGS: iter 7, Δt 25.52 s: f = -1.962811912498e+00, ‖∇f‖ = 6.7900e-02, α = 1.00e+00, m = 6, nfg = 1 +[ Info: LBFGS: iter 8, Δt 27.71 s: f = -1.965000830793e+00, ‖∇f‖ = 5.5994e-02, α = 1.00e+00, m = 7, nfg = 1 +[ Info: LBFGS: iter 9, Δt 26.97 s: f = -1.966386885688e+00, ‖∇f‖ = 5.8303e-02, α = 1.00e+00, m = 8, nfg = 1 +[ Info: LBFGS: iter 10, Δt 27.50 s: f = -1.967582741430e+00, ‖∇f‖ = 1.1340e-01, α = 1.00e+00, m = 9, nfg = 1 +[ Info: LBFGS: iter 11, Δt 24.91 s: f = -1.968967360870e+00, ‖∇f‖ = 4.1612e-02, α = 1.00e+00, m = 10, nfg = 1 +[ Info: LBFGS: iter 12, Δt 24.17 s: f = -1.969387768033e+00, ‖∇f‖ = 3.6625e-02, α = 1.00e+00, m = 11, nfg = 1 +[ Info: LBFGS: iter 13, Δt 22.91 s: f = -1.970335449656e+00, ‖∇f‖ = 3.8759e-02, α = 1.00e+00, m = 12, nfg = 1 +[ Info: LBFGS: iter 14, Δt 27.81 s: f = -1.971409090676e+00, ‖∇f‖ = 4.5864e-02, α = 1.00e+00, m = 13, nfg = 1 +[ Info: LBFGS: iter 15, Δt 28.11 s: f = -1.972264675328e+00, ‖∇f‖ = 3.3125e-02, α = 1.00e+00, m = 14, nfg = 1 +[ Info: LBFGS: iter 16, Δt 30.00 s: f = -1.972963700423e+00, ‖∇f‖ = 3.8311e-02, α = 1.00e+00, m = 15, nfg = 1 +[ Info: LBFGS: iter 17, Δt 27.60 s: f = -1.973393808483e+00, ‖∇f‖ = 3.4025e-02, α = 1.00e+00, m = 16, nfg = 1 +[ Info: LBFGS: iter 18, Δt 26.40 s: f = -1.973719445599e+00, ‖∇f‖ = 2.4697e-02, α = 1.00e+00, m = 17, nfg = 1 +[ Info: LBFGS: iter 19, Δt 28.38 s: f = -1.974117003995e+00, ‖∇f‖ = 2.3402e-02, α = 1.00e+00, m = 18, nfg = 1 +[ Info: LBFGS: iter 20, Δt 27.75 s: f = -1.974537198653e+00, ‖∇f‖ = 2.3338e-02, α = 1.00e+00, m = 19, nfg = 1 +[ Info: LBFGS: iter 21, Δt 27.06 s: f = -1.975099516920e+00, ‖∇f‖ = 2.6565e-02, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 22, Δt 26.82 s: f = -1.975286936081e+00, ‖∇f‖ = 4.4455e-02, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 23, Δt 25.91 s: f = -1.975701217338e+00, ‖∇f‖ = 1.5220e-02, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 24, Δt 28.52 s: f = -1.975821697001e+00, ‖∇f‖ = 1.8130e-02, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 25, Δt 26.42 s: f = -1.975915354964e+00, ‖∇f‖ = 2.1138e-02, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 26, Δt 24.35 s: f = -1.975998069768e+00, ‖∇f‖ = 1.3443e-02, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 27, Δt 24.80 s: f = -1.976153039607e+00, ‖∇f‖ = 1.4680e-02, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 28, Δt 23.66 s: f = -1.976260274804e+00, ‖∇f‖ = 1.7700e-02, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 29, Δt 24.93 s: f = -1.976336675890e+00, ‖∇f‖ = 3.6212e-02, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 30, Δt 22.98 s: f = -1.976481030702e+00, ‖∇f‖ = 1.2667e-02, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 31, Δt 23.03 s: f = -1.976539819784e+00, ‖∇f‖ = 1.2939e-02, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 32, Δt 23.95 s: f = -1.976636245246e+00, ‖∇f‖ = 1.3135e-02, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 33, Δt 48.36 s: f = -1.976684971058e+00, ‖∇f‖ = 1.9116e-02, α = 4.62e-01, m = 20, nfg = 2 +[ Info: LBFGS: iter 34, Δt 23.65 s: f = -1.976743262496e+00, ‖∇f‖ = 1.1658e-02, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 35, Δt 23.80 s: f = -1.976804909822e+00, ‖∇f‖ = 9.8461e-03, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 36, Δt 23.74 s: f = -1.976867380046e+00, ‖∇f‖ = 1.1271e-02, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 37, Δt 24.66 s: f = -1.976958913613e+00, ‖∇f‖ = 1.1353e-02, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 38, Δt 24.14 s: f = -1.977004149888e+00, ‖∇f‖ = 3.0196e-02, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 39, Δt 24.29 s: f = -1.977132482812e+00, ‖∇f‖ = 8.3199e-03, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 40, Δt 23.70 s: f = -1.977168854066e+00, ‖∇f‖ = 6.5281e-03, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 41, Δt 24.26 s: f = -1.977230989059e+00, ‖∇f‖ = 9.2541e-03, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 42, Δt 24.18 s: f = -1.977285220461e+00, ‖∇f‖ = 1.3935e-02, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 43, Δt 23.53 s: f = -1.977362590244e+00, ‖∇f‖ = 1.0743e-02, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 44, Δt 23.86 s: f = -1.977429195025e+00, ‖∇f‖ = 8.6810e-03, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 45, Δt 25.41 s: f = -1.977458751818e+00, ‖∇f‖ = 2.5717e-02, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 46, Δt 22.06 s: f = -1.977509209358e+00, ‖∇f‖ = 1.3339e-02, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 47, Δt 22.96 s: f = -1.977531778536e+00, ‖∇f‖ = 1.0403e-02, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 48, Δt 22.65 s: f = -1.977584620950e+00, ‖∇f‖ = 1.0581e-02, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 49, Δt 23.16 s: f = -1.977638977445e+00, ‖∇f‖ = 1.1478e-02, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 50, Δt 24.26 s: f = -1.977695226616e+00, ‖∇f‖ = 1.1637e-02, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 51, Δt 22.61 s: f = -1.977738682348e+00, ‖∇f‖ = 1.8362e-02, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 52, Δt 21.76 s: f = -1.977786348227e+00, ‖∇f‖ = 8.4144e-03, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 53, Δt 23.05 s: f = -1.977834226107e+00, ‖∇f‖ = 9.5424e-03, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 54, Δt 24.76 s: f = -1.977872709228e+00, ‖∇f‖ = 1.0069e-02, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 55, Δt 23.20 s: f = -1.977896046364e+00, ‖∇f‖ = 1.1177e-02, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 56, Δt 22.34 s: f = -1.977948855869e+00, ‖∇f‖ = 1.5958e-02, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 57, Δt 22.56 s: f = -1.977994579851e+00, ‖∇f‖ = 8.9051e-03, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 58, Δt 22.35 s: f = -1.978026072493e+00, ‖∇f‖ = 6.8546e-03, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 59, Δt 24.53 s: f = -1.978061626606e+00, ‖∇f‖ = 9.8392e-03, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 60, Δt 24.11 s: f = -1.978102554569e+00, ‖∇f‖ = 9.4732e-03, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 61, Δt 22.77 s: f = -1.978139870452e+00, ‖∇f‖ = 9.1003e-03, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 62, Δt 23.50 s: f = -1.978176133536e+00, ‖∇f‖ = 6.4191e-03, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 63, Δt 23.05 s: f = -1.978213215021e+00, ‖∇f‖ = 6.9446e-03, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 64, Δt 24.10 s: f = -1.978234971062e+00, ‖∇f‖ = 1.5980e-02, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 65, Δt 25.47 s: f = -1.978268225326e+00, ‖∇f‖ = 8.5546e-03, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 66, Δt 22.02 s: f = -1.978286888113e+00, ‖∇f‖ = 6.4777e-03, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 67, Δt 23.79 s: f = -1.978300997146e+00, ‖∇f‖ = 8.0692e-03, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 68, Δt 22.57 s: f = -1.978300528984e+00, ‖∇f‖ = 5.5147e-03, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 69, Δt 23.63 s: f = -1.978328958743e+00, ‖∇f‖ = 6.3994e-03, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 70, Δt 24.58 s: f = -1.978331153308e+00, ‖∇f‖ = 6.5489e-03, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 71, Δt 24.48 s: f = -1.978363716954e+00, ‖∇f‖ = 5.9050e-03, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 72, Δt 24.34 s: f = -1.978377069104e+00, ‖∇f‖ = 1.1874e-02, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 73, Δt 23.47 s: f = -1.978401095295e+00, ‖∇f‖ = 5.3880e-03, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 74, Δt 23.53 s: f = -1.978411402203e+00, ‖∇f‖ = 3.9472e-03, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 75, Δt 23.03 s: f = -1.978423812150e+00, ‖∇f‖ = 5.1734e-03, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 76, Δt 23.97 s: f = -1.978437405148e+00, ‖∇f‖ = 7.4640e-03, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 77, Δt 23.49 s: f = -1.978449959375e+00, ‖∇f‖ = 4.6023e-03, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 78, Δt 23.47 s: f = -1.978465209487e+00, ‖∇f‖ = 4.0286e-03, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 79, Δt 23.17 s: f = -1.978478118790e+00, ‖∇f‖ = 6.5526e-03, α = 1.00e+00, m = 20, nfg = 1 +┌ Warning: LBFGS: not converged to requested tol after 80 iterations and time 52.05 m: f = -1.978491610275e+00, ‖∇f‖ = 7.4246e-03 └ @ OptimKit ~/.julia/packages/OptimKit/OEwMx/src/lbfgs.jl:199 ```` @@ -297,8 +461,8 @@ E_opt /= (Nr * Nc) ```` ```` -E_opt = -0.49432616275726 -(E_opt - E_ref) / abs(E_ref) = -0.0001540976373494541 +E_opt = -0.4946229025687655 +(E_opt - E_ref) / abs(E_ref) = -0.0007544816768143398 ```` diff --git a/docs/src/examples/j1j2_su/main.ipynb b/docs/src/examples/j1j2_su/main.ipynb index 737098291..67086a16f 100644 --- a/docs/src/examples/j1j2_su/main.ipynb +++ b/docs/src/examples/j1j2_su/main.ipynb @@ -1,16 +1,17 @@ { "cells": [ { - "outputs": [], "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "using Markdown #hide" - ], - "metadata": {}, - "execution_count": null + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "# Three-site simple update for the $J_1$-$J_2$ model\n", "\n", @@ -28,22 +29,22 @@ "optimization.\n", "\n", "We first import all required modules and seed the RNG:" - ], - "metadata": {} + ] }, { - "outputs": [], "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "using Random\n", "using TensorKit, PEPSKit\n", - "Random.seed!(29385293);" - ], - "metadata": {}, - "execution_count": null + "Random.seed!(29385294);" + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "## Simple updating a challenging phase\n", "\n", @@ -52,12 +53,13 @@ "The `SUWeight` used by simple update will be initialized to identity matrices.\n", "We use the minimal unit cell size ($2 \\times 2$) required by the simple update algorithm\n", "for Hamiltonians with next-nearest-neighbour interactions:" - ], - "metadata": {} + ] }, { - "outputs": [], "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "Dbond, symm = 4, U1Irrep\n", "Nr, Nc, J1 = 2, 2, 1.0\n", @@ -67,23 +69,23 @@ "Vspace = Vect[U1Irrep](0 => 2, 1 // 2 => 1, -1 // 2 => 1)\n", "peps = InfinitePEPS(rand, Float64, Pspace, Vspace; unitcell = (Nr, Nc));\n", "wts = SUWeight(peps);" - ], - "metadata": {}, - "execution_count": null + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "The value $J_2 / J_1 = 0.5$ corresponds to a [possible spin liquid phase](@cite liu_gapless_2022),\n", "which is challenging for SU to produce a relatively good state from random initialization.\n", "Therefore, we shall gradually increase $J_2 / J_1$ from 0.1 to 0.5, each time initializing\n", "on the previously evolved PEPS:" - ], - "metadata": {} + ] }, { - "outputs": [], "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "dt, tol, nstep = 1.0e-2, 1.0e-8, 30000\n", "check_interval = 4000\n", @@ -96,21 +98,21 @@ " )\n", " global peps, wts, = time_evolve(peps, H, dt, nstep, alg, wts; tol, check_interval)\n", "end" - ], - "metadata": {}, - "execution_count": null + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "After we reach $J_2 / J_1 = 0.5$, we gradually decrease the evolution time step to obtain\n", "a more accurately evolved PEPS:" - ], - "metadata": {} + ] }, { - "outputs": [], "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "dts = [1.0e-3, 1.0e-4]\n", "tols = [1.0e-9, 1.0e-9]\n", @@ -119,23 +121,23 @@ "for (dt, tol) in zip(dts, tols)\n", " global peps, wts, = time_evolve(peps, H, dt, nstep, alg, wts; tol)\n", "end" - ], - "metadata": {}, - "execution_count": null + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "## Computing the simple update energy estimate\n", "\n", "Finally, we measure the ground-state energy by converging a CTMRG environment and computing\n", "the expectation value, where we first normalize tensors in the PEPS:" - ], - "metadata": {} + ] }, { - "outputs": [], "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "normalize!.(peps.A, Inf) ## normalize each PEPS tensor by largest element\n", "χenv = 32\n", @@ -144,31 +146,30 @@ "env₀ = CTMRGEnv(rand, Float64, peps, Espace)\n", "env, = leading_boundary(env₀, peps; tol = 1.0e-10, alg = :sequential, trunc = trunc_env);\n", "E = expectation_value(peps, H, env) / (Nr * Nc)" - ], - "metadata": {}, - "execution_count": null + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "Let us compare that estimate with benchmark data obtained from the\n", "[YASTN/peps-torch package](https://github.com/jurajHasik/j1j2_ipeps_states/blob/ea4140fbd7da0fc1b75fac2871f75bda125189a8/single-site_pg-C4v-A1_internal-U1/j20.5/state_1s_A1_U1B_j20.5_D4_chi_opt96.dat).\n", "which utilizes AD-based PEPS optimization to find $E_\\text{ref}=-0.49425$:" - ], - "metadata": {} + ] }, { - "outputs": [], "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "E_ref = -0.49425\n", "@show (E - E_ref) / abs(E_ref);" - ], - "metadata": {}, - "execution_count": null + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "## Variational PEPS optimization using AD\n", "\n", @@ -179,69 +180,74 @@ "the already evolved `peps`, thus giving us a physical initial guess for the optimization.\n", "In order to break some of the $C_{4v}$ symmetry of the PEPS, we will add a bit of noise to it.\n", "This is conviently done using MPSKit's `randomize!` function.\n", - "(Breaking some of the spatial symmetry can be advantageous for obtaining lower energies.)" - ], - "metadata": {} + "(Breaking some of the spatial symmetry can be advantageous for obtaining lower energies.)\n", + "\n", + "In our optimization, we will use a fixed-point differentiation scheme which requires a\n", + "gauge fixing of the contraction environment\n", + "(specified by the `gradient_alg = (; iterscheme = :fixed)` setting).\n", + "Since this gauge fixing involves potentially complex phases, we have to convert our\n", + "real-valued contraction environment to complex numbers before the optimization." + ] }, { - "outputs": [], "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "using MPSKit: randomize!\n", "\n", "noise_peps = InfinitePEPS(randomize!.(deepcopy(peps.A)))\n", "peps₀ = peps + 1.0e-1noise_peps\n", "peps_opt, env_opt, E_opt, = fixedpoint(\n", - " H, peps₀, env;\n", - " optimizer_alg = (; tol = 1.0e-4, maxiter = 80), gradient_alg = (; iterscheme = :diffgauge)\n", + " H, peps₀, complex(env);\n", + " optimizer_alg = (; tol = 1.0e-4, maxiter = 80), gradient_alg = (; iterscheme = :fixed)\n", ");" - ], - "metadata": {}, - "execution_count": null + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "Finally, we compare the variationally optimized energy against the reference energy. Indeed,\n", "we find that the additional AD-based optimization improves the SU-evolved PEPS and leads to\n", "a more accurate energy estimate." - ], - "metadata": {} + ] }, { - "outputs": [], "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "E_opt /= (Nr * Nc)\n", "@show E_opt\n", "@show (E_opt - E_ref) / abs(E_ref);" - ], - "metadata": {}, - "execution_count": null + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "---\n", "\n", "*This notebook was generated using [Literate.jl](https://github.com/fredrikekre/Literate.jl).*" - ], - "metadata": {} + ] } ], - "nbformat_minor": 3, "metadata": { + "kernelspec": { + "display_name": "Julia 1.12.5", + "language": "julia", + "name": "julia-1.12" + }, "language_info": { "file_extension": ".jl", "mimetype": "application/julia", "name": "julia", "version": "1.12.5" - }, - "kernelspec": { - "name": "julia-1.12", - "display_name": "Julia 1.12.5", - "language": "julia" } }, - "nbformat": 4 + "nbformat": 4, + "nbformat_minor": 3 } \ No newline at end of file diff --git a/docs/src/examples/xxz/index.md b/docs/src/examples/xxz/index.md index 2a4762152..bc2f10dc0 100644 --- a/docs/src/examples/xxz/index.md +++ b/docs/src/examples/xxz/index.md @@ -85,7 +85,7 @@ From this point onwards it's business as usual: Create an initial PEPS and envir ````julia boundary_alg = (; tol = 1.0e-8, alg = :simultaneous, trunc = (; alg = :fixedspace)) -gradient_alg = (; tol = 1.0e-6, alg = :eigsolver, maxiter = 10, iterscheme = :diffgauge) +gradient_alg = (; tol = 1.0e-6, alg = :eigsolver, maxiter = 10, iterscheme = :fixed) optimizer_alg = (; tol = 1.0e-4, alg = :lbfgs, maxiter = 85, ls_maxiter = 3, ls_maxfg = 3) peps₀ = InfinitePEPS(randn, ComplexF64, physical_spaces, virtual_spaces) @@ -94,7 +94,7 @@ env₀, = leading_boundary(CTMRGEnv(peps₀, V_env), peps₀; boundary_alg...); ```` [ Info: CTMRG init: obj = -2.356413456811e+03 +3.307968169629e+02im err = 1.0000e+00 -[ Info: CTMRG conv 30: obj = +6.245129734283e+03 -4.009962140117e-08im err = 5.3638613065e-09 time = 1.07 sec +[ Info: CTMRG conv 30: obj = +6.245129734283e+03 -4.008506948594e-08im err = 5.3638614844e-09 time = 14.24 sec ```` @@ -116,96 +116,96 @@ peps, env, E, info = fixedpoint( ┌ Warning: Linesearch not converged after 1 iterations and 4 function evaluations: │ α = 2.50e+01, dϕ = -2.44e-02, ϕ - ϕ₀ = -4.56e-01 └ @ OptimKit ~/.julia/packages/OptimKit/OEwMx/src/linesearches.jl:151 -[ Info: LBFGS: iter 1, Δt 49.56 s: f = -5.947088553802e-01, ‖∇f‖ = 3.7329e+00, α = 2.50e+01, m = 0, nfg = 4 +[ Info: LBFGS: iter 1, Δt 45.37 s: f = -5.947088553357e-01, ‖∇f‖ = 3.7329e+00, α = 2.50e+01, m = 0, nfg = 4 ┌ Warning: Linesearch not converged after 1 iterations and 4 function evaluations: │ α = 2.50e+01, dϕ = -7.72e-03, ϕ - ϕ₀ = -1.52e+00 └ @ OptimKit ~/.julia/packages/OptimKit/OEwMx/src/linesearches.jl:151 -[ Info: LBFGS: iter 2, Δt 45.67 s: f = -2.114273976232e+00, ‖∇f‖ = 2.9121e+00, α = 2.50e+01, m = 0, nfg = 4 -[ Info: LBFGS: iter 3, Δt 8.71 s: f = -2.218657556737e+00, ‖∇f‖ = 1.4788e+00, α = 1.00e+00, m = 1, nfg = 1 -[ Info: LBFGS: iter 4, Δt 27.86 s: f = -2.473597362493e+00, ‖∇f‖ = 1.2506e+00, α = 3.17e+00, m = 2, nfg = 3 -[ Info: LBFGS: iter 5, Δt 9.06 s: f = -2.546159337642e+00, ‖∇f‖ = 1.4463e+00, α = 1.00e+00, m = 3, nfg = 1 -[ Info: LBFGS: iter 6, Δt 8.67 s: f = -2.614645566780e+00, ‖∇f‖ = 4.0554e-01, α = 1.00e+00, m = 4, nfg = 1 -[ Info: LBFGS: iter 7, Δt 9.03 s: f = -2.622673933972e+00, ‖∇f‖ = 1.8054e-01, α = 1.00e+00, m = 5, nfg = 1 -[ Info: LBFGS: iter 8, Δt 8.40 s: f = -2.626310260618e+00, ‖∇f‖ = 1.7749e-01, α = 1.00e+00, m = 6, nfg = 1 -[ Info: LBFGS: iter 9, Δt 7.93 s: f = -2.632769136711e+00, ‖∇f‖ = 1.8586e-01, α = 1.00e+00, m = 7, nfg = 1 -[ Info: LBFGS: iter 10, Δt 7.61 s: f = -2.639694621229e+00, ‖∇f‖ = 2.2500e-01, α = 1.00e+00, m = 8, nfg = 1 -[ Info: LBFGS: iter 11, Δt 7.16 s: f = -2.644827933828e+00, ‖∇f‖ = 1.2801e-01, α = 1.00e+00, m = 9, nfg = 1 -[ Info: LBFGS: iter 12, Δt 7.50 s: f = -2.646459705942e+00, ‖∇f‖ = 6.7575e-02, α = 1.00e+00, m = 10, nfg = 1 -[ Info: LBFGS: iter 13, Δt 6.99 s: f = -2.647499600848e+00, ‖∇f‖ = 6.0731e-02, α = 1.00e+00, m = 11, nfg = 1 -[ Info: LBFGS: iter 14, Δt 7.50 s: f = -2.648703045941e+00, ‖∇f‖ = 7.1313e-02, α = 1.00e+00, m = 12, nfg = 1 -[ Info: LBFGS: iter 15, Δt 7.11 s: f = -2.650602127531e+00, ‖∇f‖ = 9.3675e-02, α = 1.00e+00, m = 13, nfg = 1 -[ Info: LBFGS: iter 16, Δt 6.94 s: f = -2.652309117887e+00, ‖∇f‖ = 8.3679e-02, α = 1.00e+00, m = 14, nfg = 1 -[ Info: LBFGS: iter 17, Δt 7.16 s: f = -2.654182949559e+00, ‖∇f‖ = 9.5661e-02, α = 1.00e+00, m = 15, nfg = 1 -[ Info: LBFGS: iter 18, Δt 7.49 s: f = -2.655830713827e+00, ‖∇f‖ = 1.4282e-01, α = 1.00e+00, m = 16, nfg = 1 -[ Info: LBFGS: iter 19, Δt 7.23 s: f = -2.658506509688e+00, ‖∇f‖ = 8.6259e-02, α = 1.00e+00, m = 17, nfg = 1 -[ Info: LBFGS: iter 20, Δt 7.64 s: f = -2.660101929784e+00, ‖∇f‖ = 5.5569e-02, α = 1.00e+00, m = 18, nfg = 1 -[ Info: LBFGS: iter 21, Δt 6.90 s: f = -2.660655804151e+00, ‖∇f‖ = 5.0089e-02, α = 1.00e+00, m = 19, nfg = 1 -[ Info: LBFGS: iter 22, Δt 6.77 s: f = -2.661713763966e+00, ‖∇f‖ = 6.6021e-02, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 23, Δt 7.52 s: f = -2.663782980193e+00, ‖∇f‖ = 1.4168e-01, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 24, Δt 7.16 s: f = -2.664843902331e+00, ‖∇f‖ = 1.3559e-01, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 25, Δt 7.53 s: f = -2.666211884109e+00, ‖∇f‖ = 6.7533e-02, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 26, Δt 7.02 s: f = -2.666722962130e+00, ‖∇f‖ = 5.1877e-02, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 27, Δt 7.53 s: f = -2.667030602502e+00, ‖∇f‖ = 4.7362e-02, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 28, Δt 7.18 s: f = -2.668170280191e+00, ‖∇f‖ = 5.6312e-02, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 29, Δt 7.54 s: f = -2.668423712729e+00, ‖∇f‖ = 1.1943e-01, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 30, Δt 7.17 s: f = -2.669339626497e+00, ‖∇f‖ = 4.0858e-02, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 31, Δt 7.49 s: f = -2.669607082478e+00, ‖∇f‖ = 3.0582e-02, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 32, Δt 6.99 s: f = -2.669888660598e+00, ‖∇f‖ = 3.6474e-02, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 33, Δt 7.62 s: f = -2.670409252201e+00, ‖∇f‖ = 5.7241e-02, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 34, Δt 7.24 s: f = -2.670955657881e+00, ‖∇f‖ = 6.0849e-02, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 35, Δt 7.57 s: f = -2.671400731193e+00, ‖∇f‖ = 4.4891e-02, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 36, Δt 7.01 s: f = -2.671654809276e+00, ‖∇f‖ = 2.3662e-02, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 37, Δt 7.38 s: f = -2.671805678430e+00, ‖∇f‖ = 2.3809e-02, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 38, Δt 6.97 s: f = -2.672069404251e+00, ‖∇f‖ = 3.7660e-02, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 39, Δt 7.29 s: f = -2.672392002437e+00, ‖∇f‖ = 4.6077e-02, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 40, Δt 6.99 s: f = -2.672631813806e+00, ‖∇f‖ = 2.8973e-02, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 41, Δt 7.42 s: f = -2.672757659661e+00, ‖∇f‖ = 2.0266e-02, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 42, Δt 6.29 s: f = -2.672874991777e+00, ‖∇f‖ = 2.3891e-02, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 43, Δt 6.72 s: f = -2.673085962228e+00, ‖∇f‖ = 3.1468e-02, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 44, Δt 6.38 s: f = -2.673264939913e+00, ‖∇f‖ = 5.1073e-02, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 45, Δt 6.77 s: f = -2.673441648495e+00, ‖∇f‖ = 2.2047e-02, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 46, Δt 6.31 s: f = -2.673518600682e+00, ‖∇f‖ = 1.6760e-02, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 47, Δt 6.72 s: f = -2.673610627656e+00, ‖∇f‖ = 2.1357e-02, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 48, Δt 6.49 s: f = -2.673749851382e+00, ‖∇f‖ = 3.0805e-02, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 49, Δt 6.83 s: f = -2.673964407099e+00, ‖∇f‖ = 2.8044e-02, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 50, Δt 6.54 s: f = -2.674085306498e+00, ‖∇f‖ = 3.7187e-02, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 51, Δt 6.70 s: f = -2.674190416395e+00, ‖∇f‖ = 1.7204e-02, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 52, Δt 6.90 s: f = -2.674244147958e+00, ‖∇f‖ = 1.4388e-02, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 53, Δt 6.69 s: f = -2.674308492367e+00, ‖∇f‖ = 1.8135e-02, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 54, Δt 6.99 s: f = -2.674434142909e+00, ‖∇f‖ = 2.0460e-02, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 55, Δt 13.65 s: f = -2.674482027661e+00, ‖∇f‖ = 2.0923e-02, α = 3.35e-01, m = 20, nfg = 2 -[ Info: LBFGS: iter 56, Δt 6.68 s: f = -2.674544061262e+00, ‖∇f‖ = 1.1687e-02, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 57, Δt 7.07 s: f = -2.674594606079e+00, ‖∇f‖ = 1.2128e-02, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 58, Δt 6.64 s: f = -2.674646184030e+00, ‖∇f‖ = 1.7080e-02, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 59, Δt 7.11 s: f = -2.674708616316e+00, ‖∇f‖ = 1.4174e-02, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 60, Δt 6.76 s: f = -2.674768771588e+00, ‖∇f‖ = 1.4598e-02, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 61, Δt 7.12 s: f = -2.674820230487e+00, ‖∇f‖ = 1.9700e-02, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 62, Δt 6.68 s: f = -2.674864015912e+00, ‖∇f‖ = 1.5491e-02, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 63, Δt 6.87 s: f = -2.674936674188e+00, ‖∇f‖ = 1.4360e-02, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 64, Δt 6.57 s: f = -2.674957688553e+00, ‖∇f‖ = 2.0196e-02, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 65, Δt 6.80 s: f = -2.674990714506e+00, ‖∇f‖ = 1.0037e-02, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 66, Δt 6.30 s: f = -2.675007817715e+00, ‖∇f‖ = 9.4268e-03, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 67, Δt 6.97 s: f = -2.675032496794e+00, ‖∇f‖ = 1.1461e-02, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 68, Δt 6.68 s: f = -2.675089463603e+00, ‖∇f‖ = 1.4806e-02, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 69, Δt 7.01 s: f = -2.675108881011e+00, ‖∇f‖ = 2.8465e-02, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 70, Δt 6.60 s: f = -2.675166443058e+00, ‖∇f‖ = 1.1529e-02, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 71, Δt 7.03 s: f = -2.675186479546e+00, ‖∇f‖ = 6.7512e-03, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 72, Δt 6.69 s: f = -2.675204431516e+00, ‖∇f‖ = 8.4356e-03, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 73, Δt 6.98 s: f = -2.675227128219e+00, ‖∇f‖ = 1.1948e-02, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 74, Δt 6.74 s: f = -2.675257941898e+00, ‖∇f‖ = 1.3696e-02, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 75, Δt 7.10 s: f = -2.675283294818e+00, ‖∇f‖ = 9.3807e-03, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 76, Δt 6.73 s: f = -2.675300545094e+00, ‖∇f‖ = 6.3181e-03, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 77, Δt 7.08 s: f = -2.675312515675e+00, ‖∇f‖ = 8.9126e-03, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 78, Δt 6.76 s: f = -2.675328270454e+00, ‖∇f‖ = 7.2766e-03, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 79, Δt 7.09 s: f = -2.675354289574e+00, ‖∇f‖ = 7.6916e-03, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 80, Δt 13.87 s: f = -2.675364316717e+00, ‖∇f‖ = 9.2305e-03, α = 4.61e-01, m = 20, nfg = 2 -[ Info: LBFGS: iter 81, Δt 6.72 s: f = -2.675376292963e+00, ‖∇f‖ = 6.5369e-03, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 82, Δt 7.11 s: f = -2.675389682288e+00, ‖∇f‖ = 7.1072e-03, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 83, Δt 6.63 s: f = -2.675405538777e+00, ‖∇f‖ = 9.7469e-03, α = 1.00e+00, m = 20, nfg = 1 -[ Info: LBFGS: iter 84, Δt 7.06 s: f = -2.675421118041e+00, ‖∇f‖ = 7.2757e-03, α = 1.00e+00, m = 20, nfg = 1 -┌ Warning: LBFGS: not converged to requested tol after 85 iterations and time 12.42 m: f = -2.675438005792e+00, ‖∇f‖ = 6.4678e-03 +[ Info: LBFGS: iter 2, Δt 45.67 s: f = -2.114273976569e+00, ‖∇f‖ = 2.9121e+00, α = 2.50e+01, m = 0, nfg = 4 +[ Info: LBFGS: iter 3, Δt 9.49 s: f = -2.218657558447e+00, ‖∇f‖ = 1.4788e+00, α = 1.00e+00, m = 1, nfg = 1 +[ Info: LBFGS: iter 4, Δt 28.19 s: f = -2.473597365661e+00, ‖∇f‖ = 1.2506e+00, α = 3.17e+00, m = 2, nfg = 3 +[ Info: LBFGS: iter 5, Δt 9.28 s: f = -2.546159342811e+00, ‖∇f‖ = 1.4463e+00, α = 1.00e+00, m = 3, nfg = 1 +[ Info: LBFGS: iter 6, Δt 7.74 s: f = -2.614645567632e+00, ‖∇f‖ = 4.0554e-01, α = 1.00e+00, m = 4, nfg = 1 +[ Info: LBFGS: iter 7, Δt 8.85 s: f = -2.622673934023e+00, ‖∇f‖ = 1.8054e-01, α = 1.00e+00, m = 5, nfg = 1 +[ Info: LBFGS: iter 8, Δt 7.07 s: f = -2.626310260611e+00, ‖∇f‖ = 1.7749e-01, α = 1.00e+00, m = 6, nfg = 1 +[ Info: LBFGS: iter 9, Δt 8.59 s: f = -2.632769137184e+00, ‖∇f‖ = 1.8586e-01, α = 1.00e+00, m = 7, nfg = 1 +[ Info: LBFGS: iter 10, Δt 6.53 s: f = -2.639694621494e+00, ‖∇f‖ = 2.2500e-01, α = 1.00e+00, m = 8, nfg = 1 +[ Info: LBFGS: iter 11, Δt 8.00 s: f = -2.644827934020e+00, ‖∇f‖ = 1.2801e-01, α = 1.00e+00, m = 9, nfg = 1 +[ Info: LBFGS: iter 12, Δt 5.91 s: f = -2.646459705819e+00, ‖∇f‖ = 6.7575e-02, α = 1.00e+00, m = 10, nfg = 1 +[ Info: LBFGS: iter 13, Δt 6.22 s: f = -2.647499600831e+00, ‖∇f‖ = 6.0731e-02, α = 1.00e+00, m = 11, nfg = 1 +[ Info: LBFGS: iter 14, Δt 7.75 s: f = -2.648703045894e+00, ‖∇f‖ = 7.1313e-02, α = 1.00e+00, m = 12, nfg = 1 +[ Info: LBFGS: iter 15, Δt 6.29 s: f = -2.650602127388e+00, ‖∇f‖ = 9.3675e-02, α = 1.00e+00, m = 13, nfg = 1 +[ Info: LBFGS: iter 16, Δt 7.53 s: f = -2.652309117542e+00, ‖∇f‖ = 8.3679e-02, α = 1.00e+00, m = 14, nfg = 1 +[ Info: LBFGS: iter 17, Δt 5.86 s: f = -2.654182949224e+00, ‖∇f‖ = 9.5661e-02, α = 1.00e+00, m = 15, nfg = 1 +[ Info: LBFGS: iter 18, Δt 7.49 s: f = -2.655830713358e+00, ‖∇f‖ = 1.4282e-01, α = 1.00e+00, m = 16, nfg = 1 +[ Info: LBFGS: iter 19, Δt 5.62 s: f = -2.658506508894e+00, ‖∇f‖ = 8.6260e-02, α = 1.00e+00, m = 17, nfg = 1 +[ Info: LBFGS: iter 20, Δt 5.98 s: f = -2.660101929403e+00, ‖∇f‖ = 5.5569e-02, α = 1.00e+00, m = 18, nfg = 1 +[ Info: LBFGS: iter 21, Δt 7.27 s: f = -2.660655802769e+00, ‖∇f‖ = 5.0089e-02, α = 1.00e+00, m = 19, nfg = 1 +[ Info: LBFGS: iter 22, Δt 5.69 s: f = -2.661713752636e+00, ‖∇f‖ = 6.6020e-02, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 23, Δt 6.23 s: f = -2.663782967628e+00, ‖∇f‖ = 1.4168e-01, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 24, Δt 7.11 s: f = -2.664843906404e+00, ‖∇f‖ = 1.3559e-01, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 25, Δt 6.05 s: f = -2.666211885495e+00, ‖∇f‖ = 6.7533e-02, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 26, Δt 7.18 s: f = -2.666722965867e+00, ‖∇f‖ = 5.1877e-02, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 27, Δt 6.02 s: f = -2.667030607084e+00, ‖∇f‖ = 4.7362e-02, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 28, Δt 7.70 s: f = -2.668170313888e+00, ‖∇f‖ = 5.6311e-02, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 29, Δt 6.04 s: f = -2.668423708832e+00, ‖∇f‖ = 1.1943e-01, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 30, Δt 7.68 s: f = -2.669339639442e+00, ‖∇f‖ = 4.0859e-02, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 31, Δt 5.65 s: f = -2.669607092068e+00, ‖∇f‖ = 3.0582e-02, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 32, Δt 6.03 s: f = -2.669888674448e+00, ‖∇f‖ = 3.6474e-02, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 33, Δt 7.56 s: f = -2.670409260559e+00, ‖∇f‖ = 5.7241e-02, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 34, Δt 6.35 s: f = -2.670955670621e+00, ‖∇f‖ = 6.0848e-02, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 35, Δt 7.45 s: f = -2.671400740936e+00, ‖∇f‖ = 4.4890e-02, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 36, Δt 6.16 s: f = -2.671654819742e+00, ‖∇f‖ = 2.3662e-02, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 37, Δt 7.47 s: f = -2.671805688410e+00, ‖∇f‖ = 2.3809e-02, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 38, Δt 6.15 s: f = -2.672069418094e+00, ‖∇f‖ = 3.7660e-02, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 39, Δt 7.50 s: f = -2.672391998692e+00, ‖∇f‖ = 4.6082e-02, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 40, Δt 6.15 s: f = -2.672631813229e+00, ‖∇f‖ = 2.8972e-02, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 41, Δt 7.60 s: f = -2.672757646725e+00, ‖∇f‖ = 2.0266e-02, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 42, Δt 5.76 s: f = -2.672874968531e+00, ‖∇f‖ = 2.3891e-02, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 43, Δt 6.09 s: f = -2.673085935987e+00, ‖∇f‖ = 3.1467e-02, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 44, Δt 7.17 s: f = -2.673264955042e+00, ‖∇f‖ = 5.1067e-02, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 45, Δt 6.11 s: f = -2.673441653001e+00, ‖∇f‖ = 2.2050e-02, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 46, Δt 7.64 s: f = -2.673518614777e+00, ‖∇f‖ = 1.6760e-02, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 47, Δt 6.03 s: f = -2.673610642660e+00, ‖∇f‖ = 2.1356e-02, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 48, Δt 7.60 s: f = -2.673749855167e+00, ‖∇f‖ = 3.0804e-02, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 49, Δt 6.08 s: f = -2.673964481832e+00, ‖∇f‖ = 2.8038e-02, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 50, Δt 6.24 s: f = -2.674085336827e+00, ‖∇f‖ = 3.7211e-02, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 51, Δt 7.20 s: f = -2.674190542900e+00, ‖∇f‖ = 1.7211e-02, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 52, Δt 6.09 s: f = -2.674244307002e+00, ‖∇f‖ = 1.4385e-02, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 53, Δt 7.45 s: f = -2.674308652203e+00, ‖∇f‖ = 1.8132e-02, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 54, Δt 6.28 s: f = -2.674434242130e+00, ‖∇f‖ = 2.0442e-02, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 55, Δt 13.84 s: f = -2.674482156825e+00, ‖∇f‖ = 2.0921e-02, α = 3.35e-01, m = 20, nfg = 2 +[ Info: LBFGS: iter 56, Δt 7.94 s: f = -2.674544199355e+00, ‖∇f‖ = 1.1684e-02, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 57, Δt 5.86 s: f = -2.674594731099e+00, ‖∇f‖ = 1.2135e-02, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 58, Δt 6.35 s: f = -2.674646300923e+00, ‖∇f‖ = 1.7065e-02, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 59, Δt 7.54 s: f = -2.674708781785e+00, ‖∇f‖ = 1.4159e-02, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 60, Δt 6.33 s: f = -2.674769125064e+00, ‖∇f‖ = 1.4517e-02, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 61, Δt 7.79 s: f = -2.674820428656e+00, ‖∇f‖ = 1.9999e-02, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 62, Δt 6.10 s: f = -2.674864524466e+00, ‖∇f‖ = 1.5512e-02, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 63, Δt 8.24 s: f = -2.674936594389e+00, ‖∇f‖ = 1.4904e-02, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 64, Δt 6.26 s: f = -2.674955282643e+00, ‖∇f‖ = 1.9377e-02, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 65, Δt 7.47 s: f = -2.674989475402e+00, ‖∇f‖ = 1.0749e-02, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 66, Δt 5.94 s: f = -2.675008560457e+00, ‖∇f‖ = 9.3873e-03, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 67, Δt 7.63 s: f = -2.675035121673e+00, ‖∇f‖ = 1.0893e-02, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 68, Δt 5.88 s: f = -2.675093424988e+00, ‖∇f‖ = 1.5948e-02, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 69, Δt 13.66 s: f = -2.675123092585e+00, ‖∇f‖ = 1.8297e-02, α = 5.07e-01, m = 20, nfg = 2 +[ Info: LBFGS: iter 70, Δt 6.11 s: f = -2.675158145862e+00, ‖∇f‖ = 1.0411e-02, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 71, Δt 8.91 s: f = -2.675184397098e+00, ‖∇f‖ = 7.5551e-03, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 72, Δt 6.70 s: f = -2.675202127278e+00, ‖∇f‖ = 1.0326e-02, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 73, Δt 9.29 s: f = -2.675232074331e+00, ‖∇f‖ = 1.0276e-02, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 74, Δt 15.73 s: f = -2.675249906405e+00, ‖∇f‖ = 1.7745e-02, α = 3.56e-01, m = 20, nfg = 2 +[ Info: LBFGS: iter 75, Δt 6.61 s: f = -2.675277747740e+00, ‖∇f‖ = 8.6877e-03, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 76, Δt 6.90 s: f = -2.675295012134e+00, ‖∇f‖ = 5.9675e-03, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 77, Δt 9.00 s: f = -2.675309457155e+00, ‖∇f‖ = 8.1916e-03, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 78, Δt 6.50 s: f = -2.675327390804e+00, ‖∇f‖ = 1.1679e-02, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 79, Δt 8.11 s: f = -2.675346033915e+00, ‖∇f‖ = 7.3995e-03, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 80, Δt 6.22 s: f = -2.675361840744e+00, ‖∇f‖ = 6.2470e-03, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 81, Δt 7.71 s: f = -2.675372633878e+00, ‖∇f‖ = 1.0378e-02, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 82, Δt 6.16 s: f = -2.675385864468e+00, ‖∇f‖ = 7.9203e-03, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 83, Δt 8.23 s: f = -2.675408682962e+00, ‖∇f‖ = 5.6544e-03, α = 1.00e+00, m = 20, nfg = 1 +[ Info: LBFGS: iter 84, Δt 6.10 s: f = -2.675422272234e+00, ‖∇f‖ = 7.8184e-03, α = 1.00e+00, m = 20, nfg = 1 +┌ Warning: LBFGS: not converged to requested tol after 85 iterations and time 18.43 m: f = -2.675439882383e+00, ‖∇f‖ = 7.8348e-03 └ @ OptimKit ~/.julia/packages/OptimKit/OEwMx/src/lbfgs.jl:199 -E / prod(size(lattice)) = -0.6688595014480129 +E / prod(size(lattice)) = -0.6688599705957462 ```` diff --git a/docs/src/examples/xxz/main.ipynb b/docs/src/examples/xxz/main.ipynb index f015a4dbe..319cc86e2 100644 --- a/docs/src/examples/xxz/main.ipynb +++ b/docs/src/examples/xxz/main.ipynb @@ -1,16 +1,17 @@ { "cells": [ { - "outputs": [], "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "using Markdown #hide" - ], - "metadata": {}, - "execution_count": null + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "# Néel order in the $U(1)$-symmetric XXZ model\n", "\n", @@ -27,34 +28,35 @@ "PEPS and CTMRG environments. For simplicity, we will consider spin-$1/2$ operators.\n", "\n", "But first, let's make this example deterministic and import the required packages:" - ], - "metadata": {} + ] }, { - "outputs": [], "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "using Random\n", "using TensorKit, PEPSKit\n", "using MPSKit: add_physical_charge\n", "Random.seed!(2928528935);" - ], - "metadata": {}, - "execution_count": null + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "## Constructing the model\n", "\n", "Let us define the $U(1)$-symmetric XXZ Hamiltonian on a $2 \\times 2$ unit cell with the\n", "parameters:" - ], - "metadata": {} + ] }, { - "outputs": [], "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "J = 1.0\n", "Delta = 1.0\n", @@ -62,34 +64,33 @@ "symmetry = U1Irrep\n", "lattice = InfiniteSquare(2, 2)\n", "H₀ = heisenberg_XXZ(ComplexF64, symmetry, lattice; J, Delta, spin);" - ], - "metadata": {}, - "execution_count": null + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "This ensures that our PEPS ansatz can support the bipartite Néel order. As discussed above,\n", "we encode the Néel order directly in the ansatz by adding staggered auxiliary physical\n", "charges:" - ], - "metadata": {} + ] }, { - "outputs": [], "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "S_aux = [\n", " U1Irrep(-1 // 2) U1Irrep(1 // 2)\n", " U1Irrep(1 // 2) U1Irrep(-1 // 2)\n", "]\n", "H = add_physical_charge(H₀, S_aux);" - ], - "metadata": {}, - "execution_count": null + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "## Specifying the symmetric virtual spaces\n", "\n", @@ -99,70 +100,70 @@ "symmetry sector. From the virtual spaces, we will need to construct a unit cell (a matrix)\n", "of spaces which will be supplied to the PEPS constructor. The same is true for the physical\n", "spaces, which can be extracted directly from the Hamiltonian `LocalOperator`:" - ], - "metadata": {} + ] }, { - "outputs": [], "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "V_peps = U1Space(0 => 2, 1 => 1, -1 => 1)\n", "V_env = U1Space(0 => 6, 1 => 4, -1 => 4, 2 => 2, -2 => 2)\n", "virtual_spaces = fill(V_peps, size(lattice)...)\n", "physical_spaces = physicalspace(H)" - ], - "metadata": {}, - "execution_count": null + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "## Ground state search\n", "\n", "From this point onwards it's business as usual: Create an initial PEPS and environment\n", "(using the symmetric spaces), specify the algorithmic parameters and optimize:" - ], - "metadata": {} + ] }, { - "outputs": [], "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "boundary_alg = (; tol = 1.0e-8, alg = :simultaneous, trunc = (; alg = :fixedspace))\n", - "gradient_alg = (; tol = 1.0e-6, alg = :eigsolver, maxiter = 10, iterscheme = :diffgauge)\n", + "gradient_alg = (; tol = 1.0e-6, alg = :eigsolver, maxiter = 10, iterscheme = :fixed)\n", "optimizer_alg = (; tol = 1.0e-4, alg = :lbfgs, maxiter = 85, ls_maxiter = 3, ls_maxfg = 3)\n", "\n", "peps₀ = InfinitePEPS(randn, ComplexF64, physical_spaces, virtual_spaces)\n", "env₀, = leading_boundary(CTMRGEnv(peps₀, V_env), peps₀; boundary_alg...);" - ], - "metadata": {}, - "execution_count": null + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "Finally, we can optimize the PEPS with respect to the XXZ Hamiltonian and check the\n", "resulting ground state energy per site using our $(2 \\times 2)$ unit cell. Note that the\n", "optimization might take a while since precompilation of symmetric AD code takes longer and\n", "because symmetric tensors do create a bit of overhead (which does pay off at larger bond and\n", "environment dimensions):" - ], - "metadata": {} + ] }, { - "outputs": [], "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "peps, env, E, info = fixedpoint(\n", " H, peps₀, env₀; boundary_alg, gradient_alg, optimizer_alg, verbosity = 3\n", ")\n", "@show E / prod(size(lattice));" - ], - "metadata": {}, - "execution_count": null + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "Note that for the specified parameters $J = \\Delta = 1$, we simulated the same Hamiltonian\n", "as in the Heisenberg example. In that example, with a\n", @@ -170,32 +171,31 @@ "$E_\\text{D=2} = -0.6625\\dots$. Again comparing against [Sandvik's](@cite\n", "sandvik_computational_2011) accurate QMC estimate $E_{\\text{ref}}=−0.6694421$, we see that\n", "we already got closer to the reference energy." - ], - "metadata": {} + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "---\n", "\n", "*This notebook was generated using [Literate.jl](https://github.com/fredrikekre/Literate.jl).*" - ], - "metadata": {} + ] } ], - "nbformat_minor": 3, "metadata": { + "kernelspec": { + "display_name": "Julia 1.12.5", + "language": "julia", + "name": "julia-1.12" + }, "language_info": { "file_extension": ".jl", "mimetype": "application/julia", "name": "julia", "version": "1.12.5" - }, - "kernelspec": { - "name": "julia-1.12", - "display_name": "Julia 1.12.5", - "language": "julia" } }, - "nbformat": 4 + "nbformat": 4, + "nbformat_minor": 3 } \ No newline at end of file diff --git a/docs/src/man/precompilation.md b/docs/src/man/precompilation.md index 7ce7cc62f..b1977ec64 100644 --- a/docs/src/man/precompilation.md +++ b/docs/src/man/precompilation.md @@ -62,9 +62,8 @@ using PrecompileTools SequentialCTMRG(; maxiter, projector_alg=:halfinfinite, verbosity), ] gradient_algs = [ - LinSolver(; solver_alg=BiCGStab(; tol=gradtol), iterscheme=:fixed), - LinSolver(; solver_alg=BiCGStab(; tol=gradtol), iterscheme=:diffgauge), - EigSolver(; solver_alg=Arnoldi(; tol=gradtol, eager=true), iterscheme=:fixed), + LinSolver(; solver_alg=BiCGStab(; tol=gradtol)), + EigSolver(; solver_alg=Arnoldi(; tol=gradtol, eager=true)), ] # Initialize OhMyThreads scheduler (precompilation occurs before __init__ call) diff --git a/examples/3d_ising_partition_function/main.jl b/examples/3d_ising_partition_function/main.jl index 222989330..51747f7ec 100644 --- a/examples/3d_ising_partition_function/main.jl +++ b/examples/3d_ising_partition_function/main.jl @@ -152,7 +152,8 @@ of this cost function. boundary_alg = SimultaneousCTMRG(; maxiter = 150, tol = 1.0e-8, verbosity = 1) rrule_alg = EigSolver(; - solver_alg = KrylovKit.Arnoldi(; maxiter = 30, tol = 1.0e-6, eager = true), iterscheme = :diffgauge + solver_alg = KrylovKit.Arnoldi(; maxiter = 30, tol = 1.0e-6, eager = true), + iterscheme = :fixed, ) T = InfinitePEPO(O) diff --git a/examples/Cache.toml b/examples/Cache.toml index 0efc83e68..ee31b89a4 100644 --- a/examples/Cache.toml +++ b/examples/Cache.toml @@ -2,10 +2,10 @@ boundary_mps = "e935558f16247ba5532ce1e2fa5577574d75d9818ab9863775ff6b97a920affb heisenberg_su = "20949c9f88410a30de2e79b15c1af47dfa87be4b0203b99f703b757220d9497b" bose_hubbard = "a006cc5ed863ce0a31b47ccfe861d4830157ddf0de6bacab03fcb5ba5ea348aa" c4v_ctmrg = "75669dae8280d608fa83612bb44b2b28a28ef3297ff16d69fba2a216a1ca9697" -j1j2_su = "9fb021d1cc62fc2ca7447d53e277f784f9fb17d285063f52bcfd8d74e0101b9c" +j1j2_su = "185d7e0475c2020fe86814537d648115c53770991a18d8ba941fb154308e8a70" hubbard_su = "8060c867a1b50753f8482c5fc217c9ec12f6af4b9710fc6aefbd9d812edb218f" heisenberg = "80bb9cc57ed85297b1b789a6c5f09494dac81b23631f48c6600ede9424c5d248" 2d_ising_partition_function = "043e1b0b97197ed611559f4a4683cb8f166c01af82a97f71364f2f5421abe3d2" -3d_ising_partition_function = "baf05623f2b0c496393892be1dfe5c7f72af94ac8c1158db9af5c1aae816c264" -xxz = "0231f0c1af2013e8edd9e5c192b108e1ab19a7dca22251c3bde525e3eee2a6aa" -fermi_hubbard = "4997680c826e555557c661e605e1d7d363e0cee15522d0895fa0cfd53b1de001" +3d_ising_partition_function = "933663904d0652218951111d9c6c128ee03c1e8e3dd7c8c97177c15de9d74aef" +xxz = "b48824c6f56be5c6d113e25097ebc515c982907982d32a123f294e62c38b9e19" +fermi_hubbard = "9651a02a27d1a88dd96c3f1892a56ec7d5b5ec83edfb8feab65ab94c623de2bb" diff --git a/examples/bose_hubbard/main.jl b/examples/bose_hubbard/main.jl index b4aec5213..4d1dd420e 100644 --- a/examples/bose_hubbard/main.jl +++ b/examples/bose_hubbard/main.jl @@ -88,7 +88,7 @@ algorithms and their tolerances: """ boundary_alg = (; tol = 1.0e-8, alg = :simultaneous, trunc = (; alg = :fixedspace)) -gradient_alg = (; tol = 1.0e-6, maxiter = 10, alg = :linsolver, iterscheme = :fixed) +gradient_alg = (; tol = 1.0e-6, maxiter = 10, alg = :linsolver) optimizer_alg = (; tol = 1.0e-4, alg = :lbfgs, maxiter = 150, ls_maxiter = 2, ls_maxfg = 2); md""" @@ -99,11 +99,9 @@ md""" general-purpose set of settings which will always work, so instead one has to adjust the simulation settings for each specific application. For example, it might help to switch between the CTMRG flavors `alg=:simultaneous` and `alg=:sequential` to - improve convergence. The evaluation of the CTMRG gradient can be instable, so there it - is advised to try the different `iterscheme=:diffgauge` and `iterscheme=:fixed` schemes - as well as different `alg` keywords. Of course the tolerances of the algorithms and - their subalgorithms also have to be compatible. For more details on the available - options, see the [`fixedpoint`](@ref) docstring. + improve convergence. Of course the tolerances of the algorithms and their subalgorithms + also have to be compatible. For more details on the available options, see the + [`fixedpoint`](@ref) docstring. Keep in mind that the PEPS is constructed from a unit cell of spaces, so we have to make a matrix of `V_peps` spaces: diff --git a/examples/fermi_hubbard/main.jl b/examples/fermi_hubbard/main.jl index 247e288d6..0517bc7c3 100644 --- a/examples/fermi_hubbard/main.jl +++ b/examples/fermi_hubbard/main.jl @@ -71,7 +71,7 @@ define all algorithmic parameters: """ boundary_alg = (; tol = 1.0e-8, alg = :simultaneous, trunc = (; alg = :fixedspace)) -gradient_alg = (; tol = 1.0e-6, alg = :eigsolver, maxiter = 10, iterscheme = :diffgauge) +gradient_alg = (; tol = 1.0e-6, alg = :eigsolver, maxiter = 10, iterscheme = :fixed) optimizer_alg = (; tol = 1.0e-4, alg = :lbfgs, maxiter = 80, ls_maxiter = 3, ls_maxfg = 3) md""" diff --git a/examples/j1j2_su/main.jl b/examples/j1j2_su/main.jl index 4e4be49c0..acb201084 100644 --- a/examples/j1j2_su/main.jl +++ b/examples/j1j2_su/main.jl @@ -20,7 +20,7 @@ We first import all required modules and seed the RNG: using Random using TensorKit, PEPSKit -Random.seed!(29385293); +Random.seed!(29385294); md""" ## Simple updating a challenging phase @@ -108,6 +108,12 @@ the already evolved `peps`, thus giving us a physical initial guess for the opti In order to break some of the $C_{4v}$ symmetry of the PEPS, we will add a bit of noise to it. This is conviently done using MPSKit's `randomize!` function. (Breaking some of the spatial symmetry can be advantageous for obtaining lower energies.) + +In our optimization, we will use a fixed-point differentiation scheme which requires a +gauge fixing of the contraction environment +(specified by the `gradient_alg = (; iterscheme = :fixed)` setting). +Since this gauge fixing involves potentially complex phases, we have to convert our +real-valued contraction environment to complex numbers before the optimization. """ using MPSKit: randomize! @@ -115,8 +121,8 @@ using MPSKit: randomize! noise_peps = InfinitePEPS(randomize!.(deepcopy(peps.A))) peps₀ = peps + 1.0e-1noise_peps peps_opt, env_opt, E_opt, = fixedpoint( - H, peps₀, env; - optimizer_alg = (; tol = 1.0e-4, maxiter = 80), gradient_alg = (; iterscheme = :diffgauge) + H, peps₀, complex(env); + optimizer_alg = (; tol = 1.0e-4, maxiter = 80), gradient_alg = (; iterscheme = :fixed) ); md""" diff --git a/examples/xxz/main.jl b/examples/xxz/main.jl index 4980cefa5..234b0479f 100644 --- a/examples/xxz/main.jl +++ b/examples/xxz/main.jl @@ -72,7 +72,7 @@ From this point onwards it's business as usual: Create an initial PEPS and envir """ boundary_alg = (; tol = 1.0e-8, alg = :simultaneous, trunc = (; alg = :fixedspace)) -gradient_alg = (; tol = 1.0e-6, alg = :eigsolver, maxiter = 10, iterscheme = :diffgauge) +gradient_alg = (; tol = 1.0e-6, alg = :eigsolver, maxiter = 10, iterscheme = :fixed) optimizer_alg = (; tol = 1.0e-4, alg = :lbfgs, maxiter = 85, ls_maxiter = 3, ls_maxfg = 3) peps₀ = InfinitePEPS(randn, ComplexF64, physical_spaces, virtual_spaces) diff --git a/src/Defaults.jl b/src/Defaults.jl index a0b2e9731..c7db81158 100644 --- a/src/Defaults.jl +++ b/src/Defaults.jl @@ -22,30 +22,40 @@ Module containing default algorithm parameter values and arguments. - `:truncrank` : Additionally supply truncation dimension `η`; truncate such that the 2-norm of the truncated values is smaller than `η` - `:truncspace` : Additionally supply truncation space `η`; truncate according to the supplied vector space - `:trunctol` : Additionally supply singular value cutoff `η`; truncate such that every retained singular value is larger than `η` -* `rrule_degeneracy_atol=$(Defaults.rrule_degeneracy_atol)` : Broadening amplitude which smoothens the divergent term in the retained contributions of an SVD or eigh pullback, in case of (pseudo) degenerate singular values +* `rrule_degeneracy_atol=$(Defaults.rrule_degeneracy_atol)` : Broadening amplitude which + smoothens the divergent term in the retained contributions of an SVD or eigh pullback, in + case of (pseudo) degenerate singular values * `svd_fwd_alg=:$(Defaults.svd_fwd_alg)` : SVD algorithm that is used in the forward pass. - - `:sdd` : MatrixAlgebraKit's `LAPACK_DivideAndConquer` - - `:svd` : MatrixAlgebraKit's `LAPACK_QRIteration` - - `:iterative` : Iterative SVD only computing the specifed number of singular values and vectors, see [`IterSVD`](@ref PEPSKit.IterSVD) + - `:DefaultAlgorithm` : MatrixAlgebraKit's default SVD algorithm for a given matrix type. + - `:DivideAndConquer` : MatrixAlgebraKit's [`DivideAndConquer`](@extref MatrixAlgebraKit.DivideAndConquer) + - `:QRIteration` : MatrixAlgebraKit's [`QRIteration`](@extref MatrixAlgebraKit.QRIteration) + - `:Bisection` : MatrixAlgebraKit's [`Bisection`](@extref MatrixAlgebraKit.Bisection) + - `:Jacobi` : MatrixAlgebraKit's [`Jacobi`](@extref MatrixAlgebraKit.Jacobi) + - `:SVDViaPolar` : MatrixAlgebraKit's [`SVDViaPolar`](@extref MatrixAlgebraKit.SVDViaPolar) + - `:SafeDivideAndConquer` : MatrixAlgebraKit's [`SafeDivideAndConquer`](@extref MatrixAlgebraKit.SafeDivideAndConquer) + - `:iterative` : Iterative Krylov-based SVD only computing the specifed number of + singular values and vectors, see [`IterSVD`](@ref PEPSKit.IterSVD) for details. * `svd_rrule_tol=$(Defaults.svd_rrule_tol)` : Accuracy of SVD reverse-rule. * `svd_rrule_min_krylovdim=$(Defaults.svd_rrule_min_krylovdim)` : Minimal Krylov dimension of the reverse-rule algorithm (if it is a Krylov algorithm). * `svd_rrule_verbosity=$(Defaults.svd_rrule_verbosity)` : SVD gradient output verbosity. * `svd_rrule_alg=:$(Defaults.svd_rrule_alg)` : Reverse-rule algorithm for the SVD gradient. - - `:full` : Uses a modified version of MatrixAlgebraKit's reverse-rule for `svd_compact` which doesn't solve any linear problem and instead requires access to the full SVD, see [`PEPSKit.FullSVDPullback`](@ref). - - `:trunc` : MatrixAlgebraKit's `svd_trunc_pullback!` solving a Sylvester equation on the truncated subspace and therefore only requires access to the truncated SVD. - - `:gmres` : GMRES iterative linear solver, see the [KrylovKit docs](https://jutho.github.io/KrylovKit.jl/stable/man/algorithms/#KrylovKit.GMRES) for details - - `:bicgstab` : BiCGStab iterative linear solver, see the [KrylovKit docs](https://jutho.github.io/KrylovKit.jl/stable/man/algorithms/#KrylovKit.BiCGStab) for details - - `:arnoldi` : Arnoldi Krylov algorithm, see the [KrylovKit docs](https://jutho.github.io/KrylovKit.jl/stable/man/algorithms/#KrylovKit.Arnoldi) for details + - `:full` : MatrixAlgebraKit's [`svd_pullback!`](@extref MatrixAlgebraKit.svd_pullback!) that requires access to the full spectrum + - `:trunc` : MatrixAlgebraKit's [`svd_trunc_pullback!`](@extref MatrixAlgebraKit.svd_trunc_pullback!) solving a Sylvester equation on the truncated subspace + - `:gmres` : GMRES iterative linear solver, see [`KrylovKit.GMRES`](@extref) + - `:bicgstab` : BiCGStab iterative linear solver, see [`KrylovKit.BiCGStab`](@extref) + - `:arnoldi` : Arnoldi Krylov algorithm, see the [`KrylovKit.Arnoldi`](@extref) ## `eigh` forward & reverse * `eigh_fwd_alg=:$(Defaults.eigh_fwd_alg)` : `eigh` algorithm that is used in the forward pass. - - `:qriteration` : MatrixAlgebraKit's `LAPACK_QRIteration`. - - `:bisection` : MatrixAlgebraKit's `LAPACK_Bisection`. - - `:divideandconquer` : MatrixAlgebraKit's `LAPACK_DivideAndConquer`. - - `:multiple` : MatrixAlgebraKit's `LAPACK_MultipleRelativelyRobustRepresentations`. - - `:lanczos` : Lanczos algorithm, see [`KrylovKit.Lanczos`](@extref) for details. - - `:blocklanczos` : Block Lanczos algorithm, see [`KrylovKit.BlockLanczos`](@extref) for details. + - `:DefaultAlgorithm` : MatrixAlgebraKit's default Eigh algorithm for a given matrix type. + - `:DivideAndConquer` : MatrixAlgebraKit's [`DivideAndConquer`](@extref MatrixAlgebraKit.DivideAndConquer) + - `:QRIteration` : MatrixAlgebraKit's [`QRIteration`](@extref MatrixAlgebraKit.QRIteration) + - `:Bisection` : MatrixAlgebraKit's [`Bisection`](@extref MatrixAlgebraKit.Bisection) + - `:Jacobi` : MatrixAlgebraKit's [`Jacobi`](@extref MatrixAlgebraKit.Jacobi) + - `:RobustRepresentations` : MatrixAlgebraKit's [`RobustRepresentations`](@extref MatrixAlgebraKit.RobustRepresentations) + - `:Lanczos` : Lanczos algorithm for symmetric/Hermitian matrices, see [`KrylovKit.Lanczos`](@extref) + - `:BlockLanczos` : Block version of `:Lanczos` for repeated extremal eigenvalues, see [`KrylovKit.BlockLanczos`](@extref) * `eigh_rrule_alg=:$(Defaults.eigh_rrule_alg)` : Reverse-rule algorithm for the `eigh` gradient. - `:full` : Full pullback algorithm for eigendecompositions, see [`PEPSKit.FullEighPullback`](@ref). - `:trunc` : Truncated reverse-mode algorithm for eigendecompositions, see [`PEPSKit.TruncEighPullback`](@ref). @@ -57,6 +67,9 @@ Module containing default algorithm parameter values and arguments. - `:halfinfinite` : Projection via SVDs of half-infinite (two enlarged corners) CTMRG environments. - `:fullinfinite` : Projection via SVDs of full-infinite (all four enlarged corners) CTMRG environments. * `projector_verbosity=$(Defaults.projector_verbosity)` : Projector output information verbosity. +* `projector_alg_c4v=:$(Defaults.projector_alg_c4v)` : Default variant of the C4v CTMRG projector algorithm. + - `:c4v_eigh` : Projection via truncated Eigh of an enlarged corner. + - `:c4v_qr` : Projection via QR decomposition of a column-enlarged corner. ## Fixed-point gradient @@ -71,7 +84,6 @@ Module containing default algorithm parameter values and arguments. * `gradient_eigsolver_eager=$(Defaults.gradient_eigsolver_eager)` : Enables `EigSolver` algorithm to finish before the full Krylov dimension is reached. * `gradient_iterscheme=:$(Defaults.gradient_iterscheme)` : Scheme for differentiating one CTMRG iteration. - `:fixed` : the differentiated CTMRG iteration uses a pre-computed SVD with a fixed set of gauges - - `:diffgauge` : the differentiated iteration consists of a CTMRG iteration and a subsequent gauge-fixing step such that the gauge-fixing procedure is differentiated as well * `gradient_alg=:$(Defaults.gradient_alg)` : Algorithm variant for computing the gradient fixed-point. ## Optimization @@ -109,7 +121,7 @@ const sparse = false # TODO: implement sparse CTMRG # SVD forward & reverse const trunc = :fixedspace # ∈ {:fixedspace, :notrunc, :truncerror, :truncspace, :trunctol} const rrule_degeneracy_atol = 1.0e-13 -const svd_fwd_alg = :sdd # ∈ {:sdd, :svd, :bisection, :jacobi, :iterative} +const svd_fwd_alg = :DefaultAlgorithm # ∈ {:, :iterative} const svd_rrule_tol = ctmrg_tol const svd_rrule_min_krylovdim = 48 const svd_rrule_verbosity = -1 @@ -117,12 +129,12 @@ const svd_rrule_alg = :full # ∈ {:full, :trunc, :gmres, :bicgstab, :arnoldi} const krylovdim_factor = 1.4 # eigh forward & reverse -const eigh_fwd_alg = :qriteration # ∈ {:qriteration, :bisection, :divideandconquer, :multiple, :lanczos, :blocklanczos} +const eigh_fwd_alg = :DefaultAlgorithm # ∈ {:, :Lanczos, :BlockLanczos} const eigh_rrule_alg = :full # ∈ {:full, :trunc} const eigh_rrule_verbosity = 0 # QR forward & reverse -const qr_fwd_alg = :qr +const qr_fwd_alg = :Householder const qr_fwd_positive = true const qr_rrule_alg = :qr const qr_rrule_verbosity = 0 @@ -130,7 +142,7 @@ const qr_rrule_verbosity = 0 # Projectors const projector_alg = :halfinfinite # ∈ {:halfinfinite, :fullinfinite} const projector_verbosity = 0 -const projector_alg_c4v = :c4v_eigh # ∈ {:c4v_eigh, :c4v_qr (TODO)} +const projector_alg_c4v = :c4v_eigh # ∈ {:c4v_eigh, :c4v_qr} # Fixed-point gradient const gradient_tol = 1.0e-6 @@ -139,7 +151,7 @@ const gradient_verbosity = -1 const gradient_linsolver = :bicgstab # ∈ {:gmres, :bicgstab} const gradient_eigsolver = :arnoldi const gradient_eigsolver_eager = true -const gradient_iterscheme = :fixed # ∈ {:fixed, :diffgauge} +const gradient_iterscheme = :fixed const gradient_alg = :eigsolver # ∈ {:geomsum, :manualiter, :linsolver, :eigsolver} # Optimization diff --git a/src/PEPSKit.jl b/src/PEPSKit.jl index 9b81db74c..65ae8d7d2 100644 --- a/src/PEPSKit.jl +++ b/src/PEPSKit.jl @@ -8,10 +8,10 @@ using VectorInterface import VectorInterface as VI using MatrixAlgebraKit -using MatrixAlgebraKit: LAPACK_DivideAndConquer, LAPACK_QRIteration using MatrixAlgebraKit: TruncationStrategy, NoTruncation, truncate, findtruncated, truncation_error, diagview -using MatrixAlgebraKit: LAPACK_EighAlgorithm, eigh_pullback!, eigh_trunc_pullback! +using MatrixAlgebraKit: TruncatedAlgorithm +using MatrixAlgebraKit: eigh_pullback!, eigh_trunc_pullback! using MatrixAlgebraKit: svd_pullback!, svd_trunc_pullback! using TensorKit @@ -83,6 +83,7 @@ include("algorithms/contractions/ctmrg/renormalize_edge.jl") include("algorithms/contractions/ctmrg/contract_site.jl") include("algorithms/contractions/ctmrg/gaugefix.jl") +include("algorithms/contractions/absorb_weight.jl") include("algorithms/contractions/transfer.jl") include("algorithms/contractions/localoperator.jl") include("algorithms/contractions/vumps_contractions.jl") @@ -110,6 +111,7 @@ include("algorithms/truncation/bond_truncation.jl") include("algorithms/time_evolution/apply_gate.jl") include("algorithms/time_evolution/apply_mpo.jl") +include("algorithms/time_evolution/get_cluster.jl") include("algorithms/time_evolution/trotter_gate.jl") include("algorithms/time_evolution/time_evolve.jl") include("algorithms/time_evolution/simpleupdate.jl") diff --git a/src/algorithms/bp/gaugefix.jl b/src/algorithms/bp/gaugefix.jl index 5c98dc265..56e5e9f30 100644 --- a/src/algorithms/bp/gaugefix.jl +++ b/src/algorithms/bp/gaugefix.jl @@ -42,7 +42,7 @@ function gauge_fix(psi::InfinitePEPO, alg::BPGauge, env::BPEnv) Fs = map(Base.Fix2(getindex, 2), psi_Fs) psi′, XXinv = gauge_fix(InfinitePEPS(psi′), alg, env) # convert back to iPEPO - psi′ = map(zip(psi′.A, Fs)) do (t, F) + psi′ = map(psi′.A, Fs) do t, F return F' * t end psi′ = reshape(psi′, (Nr, Nc, 1)) diff --git a/src/algorithms/contractions/absorb_weight.jl b/src/algorithms/contractions/absorb_weight.jl new file mode 100644 index 000000000..155034c3e --- /dev/null +++ b/src/algorithms/contractions/absorb_weight.jl @@ -0,0 +1,100 @@ +""" + absorb_weight(t::Union{PEPSTensor, PEPOTensor}, weights::SUWeight, rowcol::CartesianIndex{2}, virt_axes::NTuple{N, Int}; inv::Bool = false) + absorb_weight(t::Union{PEPSTensor, PEPOTensor}, weights::SUWeight, row::Int, col::Int, virt_axes::NTuple{N, Int}; inv::Bool = false) + +Absorb or remove (in a twist-free way) the square root of environment weight +on an axis of the PEPS/PEPO tensor `t` known to be at position (`row`, `col`) +in the unit cell of an InfinitePEPS/InfinitePEPO. The involved weights are +``` + | + [2,r,c] + | + - [1,r,c-1] - T[r,c] - [1,r,c] - + | + [2,r+1,c] + | +``` + +## Arguments + +- `t::Union{PEPSTensor, PEPOTensor}` : PEPSTensor or PEPOTensor to which the weight will be absorbed. +- `weights::SUWeight` : All simple update weights. +- `row::Int` : The row index specifying the position in the tensor network. +- `col::Int` : The column index specifying the position in the tensor network. +- `virt_axes::Int` : The axis into which the weight is absorbed, taking values from 1 to 4, standing for north, east, south, west respectively. + +## Keyword arguments + +- `inv::Bool=false` : If `true`, the inverse square root of the weight is absorbed. + +## Examples + +```julia +# Absorb the weight into the north axis of tensor at position (2, 3) +absorb_weight(t, weights, 2, 3, (1,)) + +# Absorb the inverse of (i.e. remove) the weight into the east axis +absorb_weight(t, weights, 2, 3, (2,); inv=true) +``` +""" +function absorb_weight( + t::Union{PEPSTensor, PEPOTensor}, weights::SUWeight, + rowcol::CartesianIndex{2}, virt_axes::NTuple{N, Int}; inv::Bool = false + ) where {N} + return absorb_weight(t, weights, rowcol[1], rowcol[2], virt_axes; inv) +end + +function absorb_weight( + t::Union{PEPSTensor, PEPOTensor}, weights::SUWeight, + row::Int, col::Int, virt_axes::NTuple{N, Int}; inv::Bool = false + ) where {N} + Np = numout(t) + vax = first(virt_axes) + wt = weight_to_absorb(weights, row, col, vax; inv) + axes, t2 = absorb_first_weight(t, wt, vax) + for vax in Base.tail(virt_axes) + axes, biperm = _permute_to_last(axes, vax + Np) + wt = weight_to_absorb(weights, row, col, vax; inv) + # use `*` to make absorption/removal twist-free + t2 = permute(t2, biperm) * wt + end + perm_back = invperm(axes) + return permute(t2, (perm_back[begin:numout(t)], perm_back[(numout(t) + 1):end])) +end + +""" +Pick out the weight to be absorbed to the `ax`th domain +of the tensor at position `[row, col]`, and take its +square root (or inverse square root if `inv = true`). +""" +function weight_to_absorb( + weights::SUWeight, row::Int, col::Int, ax::Int; inv::Bool = false + ) + _, Nr, Nc = size(weights) + r, c = mod1(row, Nr), mod1(col, Nc) + pow = inv ? -1 / 2 : 1 / 2 + wt = sdiag_pow( + if ax == NORTH + weights[2, r, c] + elseif ax == EAST + weights[1, r, c] + elseif ax == SOUTH + weights[2, _next(r, Nr), c] + else # WEST + weights[1, r, _prev(c, Nc)] + end, + pow, + ) + (ax == SOUTH || ax == WEST) && return transpose(wt) + return wt +end + +function absorb_first_weight( + t::Union{PEPSTensor, PEPOTensor}, wt::DiagonalTensorMap, vax::Int + ) + Np = numout(t) + axes, biperm = _permute_to_last(ntuple(identity, numind(t)), vax + Np) + # use `*` to make absorption/removal twist-free + t2 = permute(t, biperm) * wt + return axes, t2 +end diff --git a/src/algorithms/contractions/bondenv/als_solve.jl b/src/algorithms/contractions/bondenv/als_solve.jl index 5d049f84c..9b65f18aa 100644 --- a/src/algorithms/contractions/bondenv/als_solve.jl +++ b/src/algorithms/contractions/bondenv/als_solve.jl @@ -46,14 +46,22 @@ in two-site ALS optimization. └-----------┘ └-----------┘ ``` """ -_als_tensor_R(benv, xs, i::Int) = _als_tensor_R(benv, xs, Val(i)) -function _als_tensor_R(benv::BondEnv, xs::Vector{<:MPSTensor}, ::Val{1}) +function _als_tensor_R(benv::BondEnv, xs::Vector{<:MPSTensor}, i::Int) + @assert 1 <= i <= 2 + return if i == 1 + _als_tensor_Ra(benv, xs[2]) + else + _als_tensor_Rb(benv, xs[1]) + end +end + +function _als_tensor_Ra(benv::BondEnv, b::MPSTensor) return @tensor Ra[DX1 D1; DX0 D0] := - benv[DX1 DY1; DX0 DY0] * xs[2][D0 db; DY0] * conj(xs[2][D1 db; DY1]) + benv[DX1 DY1; DX0 DY0] * b[D0 db; DY0] * conj(b[D1 db; DY1]) end -function _als_tensor_R(benv::BondEnv, xs::Vector{<:MPSTensor}, ::Val{2}) +function _als_tensor_Rb(benv::BondEnv, a::MPSTensor) return @tensor Rb[D1 DY1; D0 DY0] := - benv[DX1 DY1; DX0 DY0] * xs[1][DX0 da; D0] * conj(xs[1][DX1 da; D1]) + benv[DX1 DY1; DX0 DY0] * a[DX0 da; D0] * conj(a[DX1 da; D1]) end """ @@ -93,20 +101,29 @@ Construct the overlap but with one of the bra bond tensor removed. ``` The ket part is provided by the partial contraction `benv_ket`. """ -_als_tensor_S(benv_ket, xs, i::Int) = _als_tensor_S(benv_ket, xs, Val(i)) function _als_tensor_S( benv_ket::AbstractTensorMap{T, S, 2, 2}, - xs::Vector{<:MPSTensor}, ::Val{1} + xs::Vector{<:MPSTensor}, i::Int + ) where {T <: Number, S <: ElementarySpace} + @assert 1 <= i <= 2 + return if i == 1 + _als_tensor_Sa(benv_ket, xs[2]) + else + _als_tensor_Sb(benv_ket, xs[1]) + end +end + +function _als_tensor_Sa( + benv_ket::AbstractTensorMap{T, S, 2, 2}, b::MPSTensor ) where {T <: Number, S <: ElementarySpace} return @tensor Sa[DX1 da; D1] := - benv_ket[DX1 DY1; da db] * conj(xs[2][D1 db; DY1]) + benv_ket[DX1 DY1; da db] * conj(b[D1 db; DY1]) end -function _als_tensor_S( - benv_ket::AbstractTensorMap{T, S, 2, 2}, - xs::Vector{<:MPSTensor}, ::Val{2} +function _als_tensor_Sb( + benv_ket::AbstractTensorMap{T, S, 2, 2}, a::MPSTensor ) where {T <: Number, S <: ElementarySpace} - return @tensor contractcheck = true Sb[D1 db; DY1] := - benv_ket[DX1 DY1; da db] * conj(xs[1][DX1 da; D1]) + return @tensor Sb[D1 db; DY1] := + benv_ket[DX1 DY1; da db] * conj(a[DX1 da; D1]) end """ diff --git a/src/algorithms/contractions/bondenv/benv_tools.jl b/src/algorithms/contractions/bondenv/benv_tools.jl index e59596a59..bcb159c45 100644 --- a/src/algorithms/contractions/bondenv/benv_tools.jl +++ b/src/algorithms/contractions/bondenv/benv_tools.jl @@ -9,7 +9,7 @@ const BondEnv{T, S} = AbstractTensorMap{T, S, 2, 2} where {T <: Number, S <: Ele const BondEnv3site{T, S} = AbstractTensorMap{T, S, 4, 4} where {T <: Number, S <: ElementarySpace} const Hair{T, S} = AbstractTensor{T, S, 2} where {T <: Number, S <: ElementarySpace} # Orthogonal tensors obtained PEPSTensor/PEPOTensor -# with one physical leg factored out by `bond_tensor_...` +# from `bond_tensor_...` functions const PEPSOrth{T, S} = AbstractTensor{T, S, 4} where {T <: Number, S <: ElementarySpace} const PEPOOrth{T, S} = AbstractTensor{T, S, 5} where {T <: Number, S <: ElementarySpace} diff --git a/src/algorithms/contractions/ctmrg/enlarge_corner.jl b/src/algorithms/contractions/ctmrg/enlarge_corner.jl index 0dcfd32b7..ec92e6181 100644 --- a/src/algorithms/contractions/ctmrg/enlarge_corner.jl +++ b/src/algorithms/contractions/ctmrg/enlarge_corner.jl @@ -36,7 +36,7 @@ function enlarge_northwest_corner( E_west::CTMRG_PEPS_EdgeTensor, C_northwest::CTMRGCornerTensor, E_north::CTMRG_PEPS_EdgeTensor, A::PEPSSandwich, ) - return @tensor begin + @tensor begin EC[χS DWt DWb; χ2] := E_west[χS DWt DWb; χ1] * C_northwest[χ1; χ2] # already putting χE in front here to make next permute cheaper ECE[χS χE DWb DNb; DWt DNt] := EC[χS DWt DWb; χ2] * E_north[χ2 DNt DNb; χE] @@ -45,16 +45,18 @@ function enlarge_northwest_corner( corner[χS DSt DSb; χE DEt DEb] := ECEket[χS χE DEt DSt; DWb DNb d] * conj(bra(A)[d; DNb DEb DSb DWb]) end + return corner end function enlarge_northwest_corner( E_west::CTMRG_PF_EdgeTensor, C_northwest::CTMRGCornerTensor, E_north::CTMRG_PF_EdgeTensor, A::PFTensor, ) - return @tensor begin + @tensor begin EC[χ_S DW; χ2] := E_west[χ_S DW; χ1] * C_northwest[χ1; χ2] ECE[χ_S χ_E; DW DN] := EC[χ_S DW; χ2] * E_north[χ2 DN; χ_E] corner[χ_S D_S; χ_E D_E] := ECE[χ_S χ_E; DW DN] * A[DW D_S; DN D_E] end + return corner end @generated function enlarge_northwest_corner( @@ -103,7 +105,7 @@ function enlarge_northeast_corner( E_north::CTMRG_PEPS_EdgeTensor, C_northeast::CTMRGCornerTensor, E_east::CTMRG_PEPS_EdgeTensor, A::PEPSSandwich, ) - return @tensor begin + @tensor begin EC[χW DNt DNb; χ2] := E_north[χW DNt DNb; χ1] * C_northeast[χ1; χ2] # already putting χE in front here to make next permute cheaper ECE[χW χS DNb DEb; DNt DEt] := EC[χW DNt DNb; χ2] * E_east[χ2 DEt DEb; χS] @@ -112,16 +114,18 @@ function enlarge_northeast_corner( corner[χW DWt DWb; χS DSt DSb] := ECEket[χW χS DSt DWt; DNb DEb d] * conj(bra(A)[d; DNb DEb DSb DWb]) end + return corner end function enlarge_northeast_corner( E_north::CTMRG_PF_EdgeTensor, C_northeast::CTMRGCornerTensor, E_east::CTMRG_PF_EdgeTensor, A::PFTensor, ) - return @tensor begin + @tensor begin EC[DN χ_W; χ2] := E_north[χ_W DN; χ1] * C_northeast[χ1; χ2] ECE[DN DE; χ_S χ_W] := EC[DN χ_W; χ2] * E_east[χ2 DE; χ_S] corner[χ_W D_W; χ_S D_S] := A[D_W D_S; DN DE] * ECE[DN DE; χ_S χ_W] end + return corner end @generated function enlarge_northeast_corner( @@ -170,7 +174,7 @@ function enlarge_southeast_corner( E_east::CTMRG_PEPS_EdgeTensor, C_southeast::CTMRGCornerTensor, E_south::CTMRG_PEPS_EdgeTensor, A::PEPSSandwich, ) - return @tensor begin + @tensor begin EC[χN DEt DEb; χ2] := E_east[χN DEt DEb; χ1] * C_southeast[χ1; χ2] # already putting χE in front here to make next permute cheaper ECE[χN χW DEb DSb; DEt DSt] := EC[χN DEt DEb; χ2] * E_south[χ2 DSt DSb; χW] @@ -179,16 +183,18 @@ function enlarge_southeast_corner( corner[χN DNt DNb; χW DWt DWb] := ECEket[χN χW DNt DWt; DEb DSb d] * conj(bra(A)[d; DNb DEb DSb DWb]) end + return corner end function enlarge_southeast_corner( E_east::CTMRG_PF_EdgeTensor, C_southeast::CTMRGCornerTensor, E_south::CTMRG_PF_EdgeTensor, A::PFTensor, ) - return @tensor begin + @tensor begin EC[χ_N D1; χ2] := E_east[χ_N D1; χ1] * C_southeast[χ1; χ2] ECE[χ_N χ_W; D1 D2] := EC[χ_N D1; χ2] * E_south[χ2 D2; χ_W] corner[χ_N D_N; χ_W D_W] := ECE[χ_N χ_W; D1 D2] * A[D_W D2; D_N D1] end + return corner end @generated function enlarge_southeast_corner( @@ -237,7 +243,7 @@ function enlarge_southwest_corner( E_south::CTMRG_PEPS_EdgeTensor, C_southwest::CTMRGCornerTensor, E_west::CTMRG_PEPS_EdgeTensor, A::PEPSSandwich, ) - return @tensor begin + @tensor begin EC[χE DSt DSb; χ2] := E_south[χE DSt DSb; χ1] * C_southwest[χ1; χ2] # already putting χE in front here to make next permute cheaper ECE[χE χN DSb DWb; DSt DWt] := EC[χE DSt DSb; χ2] * E_west[χ2 DWt DWb; χN] @@ -246,16 +252,18 @@ function enlarge_southwest_corner( corner[χE DEt DEb; χN DNt DNb] := ECEket[χE χN DNt DEt; DSb DWb d] * conj(bra(A)[d; DNb DEb DSb DWb]) end + return corner end function enlarge_southwest_corner( E_south::CTMRG_PF_EdgeTensor, C_southwest::CTMRGCornerTensor, E_west::CTMRG_PF_EdgeTensor, A::PFTensor, ) - return @tensor begin + @tensor begin EC[χ_E D1; χ2] := E_south[χ_E D1; χ1] * C_southwest[χ1; χ2] ECE[χ_E χ_N; D2 D1] := EC[χ_E D1; χ2] * E_west[χ2 D2; χ_N] corner[χ_E D_E; χ_N D_N] := ECE[χ_E χ_N; D2 D1] * A[D2 D1; D_N D_E] end + return corner end @generated function enlarge_southwest_corner( diff --git a/src/algorithms/contractions/ctmrg/expr.jl b/src/algorithms/contractions/ctmrg/expr.jl index 3f5e8b192..6a10ecb0a 100644 --- a/src/algorithms/contractions/ctmrg/expr.jl +++ b/src/algorithms/contractions/ctmrg/expr.jl @@ -73,12 +73,12 @@ function _pepo_edge_expr(edgename, codom_label, dom_label, dir, H::Int, args...) return tensorexpr( edgename, ( - envlabel(codom_label, args...), + envlabel(codom_label), virtuallabel(dir, :top, args...), ntuple(i -> virtuallabel(dir, :mid, i, args...), H)..., virtuallabel(dir, :bot, args...), ), - (envlabel(dom_label, args...),), + (envlabel(dom_label),), ) end @@ -146,9 +146,9 @@ function _pepo_codomain_projector_expr( ) return tensorexpr( projname, - (envlabel(codom_label, args...),), + (envlabel(codom_label),), ( - envlabel(dom_label, args...), + envlabel(dom_label), virtuallabel(dom_dir, :top, args...), ntuple(i -> virtuallabel(dom_dir, :mid, i, args...), H)..., virtuallabel(dom_dir, :bot, args...), @@ -162,11 +162,158 @@ function _pepo_domain_projector_expr( return tensorexpr( projname, ( - envlabel(codom_label, args...), + envlabel(codom_label), virtuallabel(codom_dir, :top, args...), ntuple(i -> virtuallabel(codom_dir, :mid, i, args...), H)..., virtuallabel(codom_dir, :bot, args...), ), - (envlabel(dom_label, args...),), + (envlabel(dom_label),), + ) +end + +## HalfInfiniteEnv expressions + +function _half_infinite_environment_expr_parts(H) + # site 1 (codomain) + C1_e = _corner_expr(:C_1, :WNW, :NNW) + E1_e = _pepo_edge_expr(:E_1, :SW, :WNW, :W, H, 1) + E2_e = _pepo_edge_expr(:E_2, :NNW, :NC, :N, H, 1) + ket1_e, bra1_e, pepo1_es = _pepo_sandwich_expr(:A_1, H, 1; contract_east = :NC) + + # site 2 (domain) + C2_e = _corner_expr(:C_2, :NNE, :ENE) + E3_e = _pepo_edge_expr(:E_3, :NC, :NNE, :N, H, 2) + E4_e = _pepo_edge_expr(:E_4, :ENE, :SE, :E, H, 2) + ket2_e, bra2_e, pepo2_es = _pepo_sandwich_expr(:A_2, H, 2; contract_west = :NC) + + return C1_e, E1_e, E2_e, ket1_e, bra1_e, pepo1_es, C2_e, E3_e, E4_e, ket2_e, bra2_e, pepo2_es +end + +function _half_infinite_environment_expr(H) + + C1_e, E1_e, E2_e, ket1_e, bra1_e, pepo1_es, C2_e, E3_e, E4_e, ket2_e, bra2_e, pepo2_es = + _half_infinite_environment_expr_parts(H) + + partial_expr = Expr( + :call, :*, + E1_e, C1_e, E2_e, + ket1_e, Expr(:call, :conj, bra1_e), pepo1_es..., + E3_e, C2_e, E4_e, + ket2_e, Expr(:call, :conj, bra2_e), pepo2_es..., + ) + + return partial_expr +end + +function _half_infinite_environment_conj_expr(H) + + C1_e, E1_e, E2_e, ket1_e, bra1_e, pepo1_es, C2_e, E3_e, E4_e, ket2_e, bra2_e, pepo2_es = + _half_infinite_environment_expr_parts(H) + + partial_expr = Expr( + :call, :*, + Expr(:call, :conj, E1_e), Expr(:call, :conj, C1_e), Expr(:call, :conj, E2_e), + Expr(:call, :conj, ket1_e), bra1_e, map(x -> Expr(:call, :conj, x), pepo1_es)..., + Expr(:call, :conj, E3_e), Expr(:call, :conj, C2_e), Expr(:call, :conj, E4_e), + Expr(:call, :conj, ket2_e), bra2_e, map(x -> Expr(:call, :conj, x), pepo2_es)..., + ) + + return partial_expr +end + +## FullInfiniteEnv expressions + +function _full_infinite_environment_expr_parts(H) + # site 1 (codomain) + C1_e = _corner_expr(:C_1, :WNW, :NNW) + E1_e = _pepo_edge_expr(:E_1, :SW, :WNW, :W, H, 1) + E2_e = _pepo_edge_expr(:E_2, :NNW, :NC, :N, H, 1) + ket1_e, bra1_e, pepo1_es = _pepo_sandwich_expr(:A_1, H, 1; contract_east = :NC) + + # site 2 + C2_e = _corner_expr(:C_2, :NNE, :ENE) + E3_e = _pepo_edge_expr(:E_3, :NC, :NNE, :N, H, 2) + E4_e = _pepo_edge_expr(:E_4, :ENE, :EC, :E, H, 2) + ket2_e, bra2_e, pepo2_es = _pepo_sandwich_expr( + :A_2, H, 2; contract_west = :NC, contract_south = :EC + ) + + # site 3 + C3_e = _corner_expr(:C_3, :ESE, :SSE) + E5_e = _pepo_edge_expr(:E_5, :EC, :ESE, :E, H, 3) + E6_e = _pepo_edge_expr(:E_6, :SSE, :SC, :S, H, 3) + ket3_e, bra3_e, pepo3_es = _pepo_sandwich_expr( + :A_3, H, 3; contract_north = :EC, contract_west = :SC + ) + + # site 4 (domain) + C4_e = _corner_expr(:C_4, :SSW, :WSW) + E7_e = _pepo_edge_expr(:E_7, :SC, :SSW, :S, H, 4) + E8_e = _pepo_edge_expr(:E_8, :WSW, :NW, :W, H, 4) + ket4_e, bra4_e, pepo4_es = _pepo_sandwich_expr(:A_4, H, 4; contract_east = :SC) + + return ( + E1_e, C1_e, E2_e, + ket1_e, bra1_e, pepo1_es, + E3_e, C2_e, E4_e, + ket2_e, bra2_e, pepo2_es, + E5_e, C3_e, E6_e, + ket3_e, bra3_e, pepo3_es, + E7_e, C4_e, E8_e, + ket4_e, bra4_e, pepo4_es, ) end + +function _full_infinite_environment_expr(H) + ( + E1_e, C1_e, E2_e, + ket1_e, bra1_e, pepo1_es, + E3_e, C2_e, E4_e, + ket2_e, bra2_e, pepo2_es, + E5_e, C3_e, E6_e, + ket3_e, bra3_e, pepo3_es, + E7_e, C4_e, E8_e, + ket4_e, bra4_e, pepo4_es, + ) = _full_infinite_environment_expr_parts(H) + + partial_expr = Expr( + :call, :*, + E1_e, C1_e, E2_e, + ket1_e, Expr(:call, :conj, bra1_e), pepo1_es..., + E3_e, C2_e, E4_e, + ket2_e, Expr(:call, :conj, bra2_e), pepo2_es..., + E5_e, C3_e, E6_e, + ket3_e, Expr(:call, :conj, bra3_e), pepo3_es..., + E7_e, C4_e, E8_e, + ket4_e, Expr(:call, :conj, bra4_e), pepo4_es..., + ) + + return partial_expr +end + +function _full_infinite_environment_conj_expr(H) + ( + E1_e, C1_e, E2_e, + ket1_e, bra1_e, pepo1_es, + E3_e, C2_e, E4_e, + ket2_e, bra2_e, pepo2_es, + E5_e, C3_e, E6_e, + ket3_e, bra3_e, pepo3_es, + E7_e, C4_e, E8_e, + ket4_e, bra4_e, pepo4_es, + ) = _full_infinite_environment_expr_parts(H) + + partial_expr = Expr( + :call, :*, + Expr(:call, :conj, E1_e), Expr(:call, :conj, C1_e), Expr(:call, :conj, E2_e), + Expr(:call, :conj, ket1_e), bra1_e, map(x -> Expr(:call, :conj, x), pepo1_es)..., + Expr(:call, :conj, E3_e), Expr(:call, :conj, C2_e), Expr(:call, :conj, E4_e), + Expr(:call, :conj, ket2_e), bra2_e, map(x -> Expr(:call, :conj, x), pepo2_es)..., + Expr(:call, :conj, E5_e), Expr(:call, :conj, C3_e), Expr(:call, :conj, E6_e), + Expr(:call, :conj, ket3_e), bra3_e, map(x -> Expr(:call, :conj, x), pepo3_es)..., + Expr(:call, :conj, E7_e), Expr(:call, :conj, C4_e), Expr(:call, :conj, E8_e), + Expr(:call, :conj, ket4_e), bra4_e, map(x -> Expr(:call, :conj, x), pepo4_es)..., + ) + + return partial_expr +end diff --git a/src/algorithms/contractions/ctmrg/fullinf_env.jl b/src/algorithms/contractions/ctmrg/fullinf_env.jl index fc9a229d5..6d11c0e23 100644 --- a/src/algorithms/contractions/ctmrg/fullinf_env.jl +++ b/src/algorithms/contractions/ctmrg/fullinf_env.jl @@ -67,7 +67,6 @@ Alternatively, contract the environment with a vector `x` acting on it E_8 -- A_4 -- A_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. @@ -129,40 +128,6 @@ function full_infinite_environment( ket(A_4)[d4; D_inabove D17 D19 D21] * conj(bra(A_4)[d4; D_inbelow D18 D20 D22]) * E_7[χ9 D19 D20; χ10] * C_4[χ10; χ11] * E_8[χ11 D21 D22; χ_in] 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{T, S, 3}, - A_1::P, A_2::P, A_3::P, A_4::P, - ) where {T, S, P <: PEPSSandwich} - return @autoopt @tensor env_x[χ_out D_outabove D_outbelow] := - E_1[χ_out D1 D2; χ1] * C_1[χ1; χ2] * E_2[χ2 D3 D4; χ3] * - ket(A_1)[d1; D3 D11 D_outabove D1] * conj(bra(A_1)[d1; D4 D12 D_outbelow D2]) * - ket(A_2)[d2; D5 D7 D9 D11] * conj(bra(A_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(A_3)[d3; D9 D13 D15 D17] * conj(bra_3[d3; D10 D14 D16 D18]) * - ket(A_4)[d4; D_xabove D17 D19 D21] * conj(bra(A_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{T, 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, - A_1::P, A_2::P, A_3::P, A_4::P, - ) where {T, S, P <: PEPSSandwich} - 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(A_1)[d1; D3 D11 D_xabove D1] * conj(bra(A_1)[d1; D4 D12 D_xbelow D2]) * - ket(A_2)[d2; D5 D7 D9 D11] * conj(bra(A_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(A_3)[d3; D9 D13 D15 D17] * conj(bra(A_3)[d3; D10 D14 D16 D18]) * - ket(A_4)[d4; D_inabove D17 D19 D21] * conj(bra(A_4)[d4; D_inbelow D18 D20 D22]) * - E_7[χ9 D19 D20; χ10] * C_4[χ10; χ11] * E_8[χ11 D21 D22; χ_in] -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, @@ -178,7 +143,55 @@ function full_infinite_environment( A_4[D21 D19; D_in D17] * E_7[χ9 D19; χ10] * C_4[χ10; χ11] * E_8[χ11 D21; χ_in] end +@generated 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, + A_1::PEPOSandwich{H}, A_2::PEPOSandwich{H}, A_3::PEPOSandwich{H}, A_4::PEPOSandwich{H}, + ) where {H} + # return projector expression + env_e = _pepo_env_expr(:env, :SW, :NW, :S, :N, 1, 4, H) + + # reuse partial multiplication expression + proj_expr = _full_infinite_environment_expr(H) + + return macroexpand(@__MODULE__, :(return @autoopt @tensor $env_e := $proj_expr)) +end + +# right linear map action: env * x +function full_infinite_environment( + env::AbstractTensorMap{T, S, N, N}, x::AbstractTensor{T, S, N} + ) where {T, S, N} + return half_infinite_environment(env, x) +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{T, S, N}, + A_1, A_2, A_3, A_4, + ) where {T, S, N} + xt = twistdual(x, 1:N) + return _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, xt, A_1, A_2, A_3, A_4 + ) +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{T, S, 3}, + A_1::P, A_2::P, A_3::P, A_4::P, + ) where {T, S, P <: PEPSSandwich} + return @autoopt @tensor env_x[χ_out D_outabove D_outbelow] := + E_1[χ_out D1 D2; χ1] * C_1[χ1; χ2] * E_2[χ2 D3 D4; χ3] * + ket(A_1)[d1; D3 D11 D_outabove D1] * conj(bra(A_1)[d1; D4 D12 D_outbelow D2]) * + ket(A_2)[d2; D5 D7 D9 D11] * conj(bra(A_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(A_3)[d3; D9 D13 D15 D17] * conj(bra(A_3)[d3; D10 D14 D16 D18]) * + ket(A_4)[d4; D_xabove D17 D19 D21] * conj(bra(A_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( 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{T, S, 2}, @@ -195,109 +208,78 @@ function full_infinite_environment( E_7[χ9 D19; χ10] * C_4[χ10; χ11] * E_8[χ11 D21; χ_x] * x[χ_x D_x] end -function full_infinite_environment( - x::AbstractTensor{T, S, 2}, +@generated 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, - A_1::P, A_2::P, A_3::P, A_4::P, - ) where {T, S, P <: PFTensor} - return @autoopt @tensor x_env[χ_in D_in] := - x[χ_x D_x] * - E_1[χ_x D1; χ1] * C_1[χ1; χ2] * E_2[χ2 D3; χ3] * - A_1[D1 D_x; D3 D11] * - A_2[D11 D9; D5 D7] * - E_3[χ3 D5; χ4] * C_2[χ4; χ5] * E_4[χ5 D7; χ6] * - E_5[χ6 D13; χ7] * C_3[χ7; χ8] * E_6[χ8 D15; χ9] * - A_3[D17 D15; D9 D13] * - A_4[D21 D19; D_in D17] * - E_7[χ9 D19; χ10] * C_4[χ10; χ11] * E_8[χ11 D21; χ_in] -end - - -## FullInfiniteEnvironment contractions + x::AbstractTensor{T, S, N}, + A_1::PEPOSandwich{H}, A_2::PEPOSandwich{H}, A_3::PEPOSandwich{H}, A_4::PEPOSandwich{H}, + ) where {T, S, N, H} + @assert N == H + 3 -# reuse partial multiplication expression; TODO: return quadrants separately? -function _full_infinite_environment_expr(H) - # site 1 (codomain) - C1_e = _corner_expr(:C_1, :WNW, :NNW) - E1_e = _pepo_edge_expr(:E_1, :SW, :WNW, :W, H, 1) - E2_e = _pepo_edge_expr(:E_2, :NNW, :NC, :N, H, 1) - ket1_e, bra1_e, pepo1_es = _pepo_sandwich_expr(:A_1, H, 1; contract_east = :NC) + # codomain vector (output) + env_x_e = _pepo_env_arg_expr(:env_x, :SW, :S, 1, H) - # site 2 - C2_e = _corner_expr(:C_2, :NNE, :ENE) - E3_e = _pepo_edge_expr(:E_3, :NC, :NNE, :N, H, 2) - E4_e = _pepo_edge_expr(:E_4, :ENE, :EC, :E, H, 2) - ket2_e, bra2_e, pepo2_es = _pepo_sandwich_expr( - :A_2, H, 2; contract_west = :NC, contract_south = :EC - ) + # reuse partial multiplication expression + proj_expr = _full_infinite_environment_expr(H) - # site 3 - C3_e = _corner_expr(:C_3, :WSW, :SSW) - E5_e = _pepo_edge_expr(:E_5, :EC, :WSW, :E, H, 3) - E6_e = _pepo_edge_expr(:E_6, :SSW, :SC, :S, H, 3) - ket3_e, bra3_e, pepo3_es = _pepo_sandwich_expr( - :A_3, H, 3; contract_north = :EC, contract_west = :SC - ) + # domain vector (input) + x_e = _pepo_env_arg_expr(:x, :NW, :N, 4, H) - # site 4 (domain) - C4_e = _corner_expr(:C_4, :SSW, :WSW) - E7_e = _pepo_edge_expr(:E_7, :SC, :SSW, :S, H, 4) - E8_e = _pepo_edge_expr(:E_8, :WSW, :NW, :W, H, 4) - ket4_e, bra4_e, pepo4_es = _pepo_sandwich_expr(:A_4, H, 4; contract_east = :SC) + return macroexpand(@__MODULE__, :(return @autoopt @tensor $env_x_e := $proj_expr * $x_e)) +end - partial_expr = Expr( - :call, :*, - E1_e, C1_e, E2_e, - ket1_e, Expr(:call, :conj, bra1_e), - pepo1_es..., - E3_e, C2_e, E4_e, - ket2_e, Expr(:call, :conj, bra2_e), - pepo2_es..., - E5_e, C3_e, E6_e, - ket3_e, Expr(:call, :conj, bra3_e), - pepo3_es..., - E7_e, C4_e, E8_e, - ket4_e, Expr(:call, :conj, bra4_e), - pepo4_es..., +# left linear map action via adjoint: env' * x +function full_infinite_environment( + x::AbstractTensor{T, S, N}, env::AbstractTensorMap{T, S, N, N}, + ) where {T, S, N} + return half_infinite_environment(x, env) +end +function full_infinite_environment( + x::AbstractTensor{T, S, N}, + C_1, C_2, C_3, C_4, + E_1, E_2, E_3, E_4, E_5, E_6, E_7, E_8, + A_1, A_2, A_3, A_4, + ) where {T, S, N} + xt = twistdual(x, 1:N) + return _full_infinite_environment( + xt, C_1, C_2, C_3, C_4, E_1, E_2, E_3, E_4, E_5, E_6, E_7, E_8, A_1, A_2, A_3, A_4 ) - - return partial_expr end - -@generated function full_infinite_environment( +function _full_infinite_environment( + x::AbstractTensor{T, 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, - A_1::PEPOSandwich{H}, A_2::PEPOSandwich{H}, A_3::PEPOSandwich{H}, A_4::PEPOSandwich{H}, - ) where {H} - # return projector expression - env_e = _pepo_env_expr(:env, :SW, :NW, :S, :N, 1, 4, N - 1) - - # reuse partial multiplication expression - proj_expr = _full_infinite_environment_expr(H) - - return macroexpand(@__MODULE__, :(return @autoopt @tensor $env_e := $proj_expr)) + A_1::P, A_2::P, A_3::P, A_4::P, + ) where {T, S, P <: PEPSSandwich} + return @autoopt @tensor x_env[χ_in D_inabove D_inbelow] := + x[χ_x D_xabove D_xbelow] * + conj(E_1[χ_x D1 D2; χ1]) * conj(C_1[χ1; χ2]) * conj(E_2[χ2 D3 D4; χ3]) * + conj(ket(A_1)[d1; D3 D11 D_xabove D1]) * bra(A_1)[d1; D4 D12 D_xbelow D2] * + conj(ket(A_2)[d2; D5 D7 D9 D11]) * bra(A_2)[d2; D6 D8 D10 D12] * + conj(E_3[χ3 D5 D6; χ4]) * conj(C_2[χ4; χ5]) * conj(E_4[χ5 D7 D8; χ6]) * + conj(E_5[χ6 D13 D14; χ7]) * conj(C_3[χ7; χ8]) * conj(E_6[χ8 D15 D16; χ9]) * + conj(ket(A_3)[d3; D9 D13 D15 D17]) * bra(A_3)[d3; D10 D14 D16 D18] * + conj(ket(A_4)[d4; D_inabove D17 D19 D21]) * bra(A_4)[d4; D_inbelow D18 D20 D22] * + conj(E_7[χ9 D19 D20; χ10]) * conj(C_4[χ10; χ11]) * conj(E_8[χ11 D21 D22; χ_in]) end -@generated function full_infinite_environment( +function _full_infinite_environment( + x::AbstractTensor{T, S, 2}, 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{T, S, N}, - A_1::PEPOSandwich{H}, A_2::PEPOSandwich{H}, A_3::PEPOSandwich{H}, A_4::PEPOSandwich{H}, - ) where {T, S, N, H} - @assert N == H + 3 - - # codomain vecor (output) - env_x_e = _pepo_env_arg_expr(:env, :SW, :S, 1, N - 1) - - # reuse partial multiplication expression - proj_expr = _full_infinite_environment_expr(H) - - # domain vector (input) - x_e = _pepo_env_arg_expr(:env, :NW, :N, 4, N - 1) - - return macroexpand(@__MODULE__, :(return @autoopt @tensor $env_x_e := $proj_expr * x_e)) + A_1::P, A_2::P, A_3::P, A_4::P, + ) where {T, S, P <: PFTensor} + return @autoopt @tensor x_env[χ_in D_in] := + x[χ_x D_x] * + conj(E_1[χ_x D1; χ1]) * conj(C_1[χ1; χ2]) * conj(E_2[χ2 D3; χ3]) * + conj(A_1[D1 D_x; D3 D11]) * + conj(A_2[D11 D9; D5 D7]) * + conj(E_3[χ3 D5; χ4]) * conj(C_2[χ4; χ5]) * conj(E_4[χ5 D7; χ6]) * + conj(E_5[χ6 D13; χ7]) * conj(C_3[χ7; χ8]) * conj(E_6[χ8 D15; χ9]) * + conj(A_3[D17 D15; D9 D13]) * + conj(A_4[D21 D19; D_in D17]) * + conj(E_7[χ9 D19; χ10]) * conj(C_4[χ10; χ11]) * conj(E_8[χ11 D21; χ_in]) end -@generated function full_infinite_environment( +@generated function _full_infinite_environment( x::AbstractTensor{T, S, N}, C_1, C_2, C_3, C_4, E_1, E_2, E_3, E_4, E_5, E_6, E_7, E_8, @@ -305,14 +287,14 @@ end ) where {T, S, N, H} @assert N == H + 3 - # codomain vecor (input) - x_e = _pepo_env_arg_expr(:env, :SW, :S, 1, N - 1) + # codomain vector (input) + x_e = _pepo_env_arg_expr(:x, :SW, :S, 1, H) # reuse partial multiplication expression - proj_expr = _full_infinite_environment_expr(H) + proj_expr = _full_infinite_environment_conj_expr(H) # domain vector (output) - x_env_e = _pepo_env_arg_expr(:env, :NW, :N, 4, N - 1) + x_env_e = _pepo_env_arg_expr(:x_env, :NW, :N, 4, H) return macroexpand( @__MODULE__, :(return @autoopt @tensor $x_env_e := $x_e * $proj_expr) diff --git a/src/algorithms/contractions/ctmrg/halfinf_env.jl b/src/algorithms/contractions/ctmrg/halfinf_env.jl index e09fc9ae0..01e05baa3 100644 --- a/src/algorithms/contractions/ctmrg/halfinf_env.jl +++ b/src/algorithms/contractions/ctmrg/halfinf_env.jl @@ -49,27 +49,7 @@ function half_infinite_environment( E_1[χ_out D1 D2; χ1] * C_1[χ1; χ2] * E_2[χ2 D3 D4; χ3] * ket(A_1)[d1; D3 D9 D_outabove D1] * conj(bra(A_1)[d1; D4 D10 D_outbelow D2]) * ket(A_2)[d2; D5 D7 D_inabove D9] * conj(bra(A_2)[d2; D6 D8 D_inbelow D10]) * - E_3[χ3 D5 D6; χ4] * C_2[χ4; χ5] * E_4[χ5 D7 D8; χ_out] -end -function half_infinite_environment( - C_1, C_2, E_1, E_2, E_3, E_4, x::AbstractTensor{T, S, 3}, A_1::P, A_2::P - ) where {T, S, P <: PEPSSandwich} - return @autoopt @tensor env_x[χ_out D_outabove D_outbelow] := - E_1[χ_out D1 D2; χ1] * C_1[χ1; χ2] * E_2[χ2 D3 D4; χ3] * - ket(A_1)[d1; D3 D9 D_outabove D1] * conj(bra(A_1)[d1; D4 D10 D_outbelow D2]) * - ket(A_2)[d2; D5 D7 D11 D9] * conj(bra(A_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] -end -function half_infinite_environment( - x::AbstractTensor{T, S, 3}, C_1, C_2, E_1, E_2, E_3, E_4, A_1::P, A_2::P - ) where {T, S, P <: PEPSSandwich} - 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]) * conj(E_2[χ3 D5 D6; χ4]) * - conj(ket(A_1)[d1; D5 D11 D1 D3]) * bra(A_1)[d1; D6 D12 D2 D4] * - conj(ket(A_2)[d2; D7 D9 D_inabove D11]) * bra(A_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]) + E_3[χ3 D5 D6; χ4] * C_2[χ4; χ5] * E_4[χ5 D7 D8; χ_in] end function half_infinite_environment( C_1, C_2, E_1, E_2, E_3, E_4, A_1::P, A_2::P @@ -80,56 +60,6 @@ function half_infinite_environment( A_2[D9 D_in; D5 D7] * E_3[χ3 D5; χ4] * C_2[χ4; χ5] * E_4[χ5 D7; χ_in] end -function half_infinite_environment( - C_1, C_2, E_1, E_2, E_3, E_4, x::AbstractTensor{T, S, 2}, A_1::P, A::P - ) where {T, S, P <: PFTensor} - return @autoopt @tensor env_x[χ_out D_out] := - E_1[χ_out D1; χ1] * C_1[χ1; χ2] * E_2[χ2 D3; χ3] * - A_1[D1 D_out; D3 D9] * - A_2[D9 D11; D5 D7] * - E_3[χ3 D5; χ4] * C_2[χ4; χ5] * E_4[χ5 D7; χ6] * - x[χ6 D11] -end -function half_infinite_environment( - x::AbstractTensor{T, S, 2}, C_1, C_2, E_1, E_2, E_3, E_4, A_1::P, A_2::P - ) where {T, S, P <: PFTensor} - return @autoopt @tensor env_x[χ_in D_in] := - x[χ1 D1 D2] * - conj(E_1[χ1 D3; χ2]) * conj(C_1[χ2; χ3]) * conj(E_2[χ3 D5; χ4]) * - conj(A_1[D3 D1; D5 D11]) * - conj(A_2[D11 D_in; D7 D9]) * - conj(E_3[χ4 D7; χ5]) * conj(C_2[χ5; χ6]) * conj(E_4[χ6 D9; χ_in]) -end - -## HalfInfiniteEnvironment contractions - -# reuse partial multiplication expression; TODO: return quadrants separately? -function _half_infinite_environnment_expr(H) - # site 1 (codomain) - C1_e = _corner_expr(:C_1, :WNW, :NNW) - E1_e = _pepo_edge_expr(:E_1, :SW, :WNW, :W, H, 1) - E2_e = _pepo_edge_expr(:E_2, :NNW, :NC, :N, H, 1) - ket1_e, bra1_e, pepo1_es = _pepo_sandwich_expr(:A_1, H, 1; contract_east = :NC) - - # site 2 (domain) - C2_e = _corner_expr(:C_2, :NNE, :ENE) - E3_e = _pepo_edge_expr(:E_3, :NC, :NNE, :N, H, 2) - E4_e = _pepo_edge_expr(:E_4, :ENE, :SE, :E, H, 2) - ket2_e, bra2_e, pepo2_es = _pepo_sandwich_expr(:A_2, H, 2; contract_west = :NC) - - partial_expr = Expr( - :call, :*, - E1_e, C1_e, E2_e, - ket1_e, Expr(:call, :conj, bra1_e), - pepo1_es..., - E3_e, C2_e, E4_e, - ket2_e, Expr(:call, :conj, bra2_e), - pepo2_es..., - ) - - return partial_expr -end - @generated function half_infinite_environment( C_1, C_2, E_1, E_2, E_3, E_4, A_1::PEPOSandwich{H}, A_2::PEPOSandwich{H} ) where {H} @@ -137,11 +67,44 @@ end env_e = _pepo_env_expr(:env, :SW, :SE, :S, :S, 1, 2, H) # reuse partial multiplication expression - proj_expr = _half_infinite_environnment_expr(H) + proj_expr = _half_infinite_environment_expr(H) - return macroexpand(@__MODULE__, :(return @autoopt @tensor $env_e := $rhs)) + return macroexpand(@__MODULE__, :(return @autoopt @tensor $env_e := $proj_expr)) end -@generated function half_infinite_environment( + +# right linear map action: env * x +function half_infinite_environment( + env::AbstractTensorMap{T, S, N, N}, x::AbstractTensor{T, S, N} + ) where {T, S, N} + return env * x +end +function half_infinite_environment( + C_1, C_2, E_1, E_2, E_3, E_4, x::AbstractTensor{T, S, N}, A_1, A_2 + ) where {T, S, N} + xt = twistdual(x, 1:N) + return _half_infinite_environment(C_1, C_2, E_1, E_2, E_3, E_4, xt, A_1, A_2) +end +function _half_infinite_environment( + C_1, C_2, E_1, E_2, E_3, E_4, x::AbstractTensor{T, S, 3}, A_1::P, A_2::P + ) where {T, S, P <: PEPSSandwich} + return @autoopt @tensor env_x[χ_out D_outabove D_outbelow] := + E_1[χ_out D1 D2; χ1] * C_1[χ1; χ2] * E_2[χ2 D3 D4; χ3] * + ket(A_1)[d1; D3 D9 D_outabove D1] * conj(bra(A_1)[d1; D4 D10 D_outbelow D2]) * + ket(A_2)[d2; D5 D7 D11 D9] * conj(bra(A_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] +end +function _half_infinite_environment( + C_1, C_2, E_1, E_2, E_3, E_4, x::AbstractTensor{T, S, 2}, A_1::P, A_2::P + ) where {T, S, P <: PFTensor} + return @autoopt @tensor env_x[χ_out D_out] := + E_1[χ_out D1; χ1] * C_1[χ1; χ2] * E_2[χ2 D3; χ3] * + A_1[D1 D_out; D3 D9] * + A_2[D9 D11; D5 D7] * + E_3[χ3 D5; χ4] * C_2[χ4; χ5] * E_4[χ5 D7; χ6] * + x[χ6 D11] +end +@generated function _half_infinite_environment( C_1, C_2, E_1, E_2, E_3, E_4, x::AbstractTensor{T, S, N}, @@ -153,7 +116,7 @@ end env_x_e = _pepo_env_arg_expr(:env_x, :SW, :S, 1, H) # reuse partial multiplication expression - proj_expr = _half_infinite_environnment_expr(H) + proj_expr = _half_infinite_environment_expr(H) # domain vector (input) x_e = _pepo_env_arg_expr(:x, :SE, :S, 2, H) @@ -162,7 +125,40 @@ end @__MODULE__, :(return @autoopt @tensor $env_x_e := $proj_expr * $x_e) ) end -@generated function half_infinite_environment( + +# left linear map action via adjoint: env' * x +function half_infinite_environment( + x::AbstractTensor{T, S, N}, env::AbstractTensorMap{T, S, N, N}, + ) where {T, S, N} + return env' * x +end +function half_infinite_environment( + x::AbstractTensor{T, S, N}, C_1, C_2, E_1, E_2, E_3, E_4, A_1, A_2 + ) where {T, S, N} + xt = twistdual(x, 1:N) + return _half_infinite_environment(xt, C_1, C_2, E_1, E_2, E_3, E_4, A_1, A_2) +end +function _half_infinite_environment( + x::AbstractTensor{T, S, 3}, C_1, C_2, E_1, E_2, E_3, E_4, A_1::P, A_2::P + ) where {T, S, P <: PEPSSandwich} + 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]) * conj(E_2[χ3 D5 D6; χ4]) * + conj(ket(A_1)[d1; D5 D11 D1 D3]) * bra(A_1)[d1; D6 D12 D2 D4] * + conj(ket(A_2)[d2; D7 D9 D_inabove D11]) * bra(A_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 +function _half_infinite_environment( + x::AbstractTensor{T, S, 2}, C_1, C_2, E_1, E_2, E_3, E_4, A_1::P, A_2::P + ) where {T, S, P <: PFTensor} + return @autoopt @tensor env_x[χ_in D_in] := + x[χ_SW D_S1] * + conj(E_1[χ_SW D_W1; χ_WNW]) * conj(C_1[χ_WNW; χ_NNW]) * conj(E_2[χ_NNW D_N1; χ_N]) * + conj(A_1[D_W1 D_S1; D_N1 D_C]) * + conj(A_2[D_C D_in; D_N2 D_E2]) * + conj(E_3[χ_N D_N2; χ_NNE]) * conj(C_2[χ_NNE; χ_ENE]) * conj(E_4[χ_ENE D_E2; χ_in]) +end +@generated function _half_infinite_environment( x::AbstractTensor{T, S, N}, C_1, C_2, E_1, E_2, E_3, E_4, @@ -173,7 +169,7 @@ end x_e = _pepo_env_arg_expr(:x, :SW, :S, 1, H) # reuse partial multiplication expression - proj_expr = _half_infinite_environnment_expr(H) + proj_expr = _half_infinite_environment_conj_expr(H) # domain vector (output) x_env_e = _pepo_env_arg_expr(:env_x, :SE, :S, 2, H) diff --git a/src/algorithms/contractions/ctmrg/projector.jl b/src/algorithms/contractions/ctmrg/projector.jl index d5069df87..f8791d77f 100644 --- a/src/algorithms/contractions/ctmrg/projector.jl +++ b/src/algorithms/contractions/ctmrg/projector.jl @@ -14,16 +14,99 @@ Contract the CTMRG left projector with the higher-dimensional subspace facing to out ``` """ -function left_projector(E_1, C, E_2, V, isqS, A::PEPSSandwich) +function left_projector(E_1, C, E_2, V, isqS, A) + Vdt_isqS = twistdual(V', 1:(numind(V) - 1)) * isqS + return _left_projector(E_1, C, E_2, Vdt_isqS, A) +end +function _left_projector(E_1, C, E_2, Vd, A::PEPSSandwich) return @autoopt @tensor P_left[χ_out D_outabove D_outbelow; χ_in] := E_1[χ_out D1 D2; χ1] * C[χ1; χ2] * E_2[χ2 D3 D4; χ3] * ket(A)[d; D3 D5 D_outabove D1] * conj(bra(A)[d; D4 D6 D_outbelow D2]) * - conj(V[χ4; χ3 D5 D6]) * isqS[χ4; χ_in] + Vd[χ3 D5 D6; χ_in] end -function left_projector(E_1, C, E_2, V, isqS, A::PFTensor) +function _left_projector(E_1, C, E_2, Vd, A::PFTensor) return @autoopt @tensor P_left[χ_out D_out; χ_in] := E_1[χ_out D1; χ1] * C[χ1; χ2] * E_2[χ2 D2; χ3] * - A[D1 D_out; D2 D3] * conj(V[χ4; χ3 D3]) * isqS[χ4; χ_in] + A[D1 D_out; D2 D3] * Vd[χ3 D3; χ_in] +end +@generated function _left_projector( + E_1, C, E_2, Vd, A::PEPOSandwich{H} + ) where {H} + + E_west_e = _pepo_edge_expr(:E_1, :out, :WNW, :W, H) + E_north_e = _pepo_edge_expr(:E_2, :NNW, :NE, :N, H) + C_northwest_e = _corner_expr(:C, :WNW, :NNW) + ket_e, bra_e, pepo_es = _pepo_sandwich_expr(:A, H) + + Vd_e = _pepo_domain_projector_expr(:Vd, :NE, :E, :in, H) + + P_left_e = _pepo_domain_projector_expr(:P_left, :out, :S, :in, H) + + rhs = Expr( + :call, :*, + E_west_e, C_northwest_e, E_north_e, + ket_e, Expr(:call, :conj, bra_e), + pepo_es..., + Vd_e + ) + + return macroexpand( + @__MODULE__, :(return @autoopt @tensor $P_left_e := $rhs) + ) +end + +""" +$(SIGNATURES) + +Contract the CTMRG left projector with the higher-dimensional subspace facing to the left. + +``` + C_1 -- E_2 -- E_3 -- C_2 + | | | | + E_1 -- A_1 -- A_2 -- E_4 + | | | | + out [~~~V'~] + | + isqS + | + in +``` +""" +function left_projector(E_1, C_1, E_2, E_3, C_2, E_4, V, isqS, A_1, A_2) + Vdt_isqS = twistdual(V', 1:(numind(V) - 1)) * isqS + return _left_projector(E_1, C_1, E_2, E_3, C_2, E_4, Vdt_isqS, A_1, A_2) +end +function _left_projector( + E_1, C_1, E_2, E_3, C_2, E_4, Vd, A_1::PEPSSandwich, A_2::PEPSSandwich + ) + return @autoopt @tensor P_left[χ_out D_outa D_outb; χ_in] := + E_1[χ_out D_W1a D_W1b; χ_WNW] * C_1[χ_WNW; χ_NNW] * E_2[χ_NNW D_N1a D_N1b; χ_N] * + ket(A_1)[d1; D_N1a D_Ca D_outa D_W1a] * conj(bra(A_1)[d1; D_N1b D_Cb D_outb D_W1b]) * + E_3[χ_N D_N2a D_N2b; χ_NNE] * C_2[χ_NNE; χ_ENE] * E_4[χ_ENE D_E2a D_E2b; χ_SE] * + ket(A_2)[d2; D_N2a D_E2a D_S2a D_Ca] * conj(bra(A_2)[d2; D_N2b D_E2b D_S2b D_Cb]) * + Vd[χ_SE D_S2a D_S2b; χ_in] +end +function _left_projector( + E_1, C_1, E_2, E_3, C_2, E_4, Vd, A_1::PFTensor, A_2::PFTensor + ) + return @autoopt @tensor P_left[χ_out D_out; χ_in] := + E_1[χ_out D_W1; χ_WNW] * C_1[χ_WNW; χ_NNW] * E_2[χ_NNW D_N1; χ_N] * + A_1[D_W1 D_out; D_N1 D_C] * + E_3[χ_N D_N2; χ_NNE] * C_2[χ_NNE; χ_ENE] * E_4[χ_ENE D_E2; χ_SE] * + A_2[D_C D_S2; D_N2 D_E2] * + Vd[χ_SE D_S2; χ_in] +end +@generated function _left_projector( + E_1, C_1, E_2, E_3, C_2, E_4, Vd, A_1::PEPOSandwich{H}, A_2::PEPOSandwich{H} + ) where {H} + + Vd_e = _pepo_domain_projector_expr(:Vd, :SE, :S, :in, H, 2) + proj_expr = _half_infinite_environment_expr(H) + P_left_e = _pepo_domain_projector_expr(:P_left, :SW, :S, :in, H, 1) + + return macroexpand( + @__MODULE__, :(return @autoopt @tensor $P_left_e := $proj_expr * $Vd_e) + ) end """ @@ -32,24 +115,107 @@ $(SIGNATURES) Contract the CTMRG right projector with the higher-dimensional subspace facing to the right. ``` - |~~| -- E_2 -- C + |~~| -- E_1 -- C out-- isqS -- |U'| | | - |~~| -- A -- E_1 + |~~| -- A -- E_2 | | in ``` """ -function right_projector(E_1, C, E_2, U, isqS, A::PEPSSandwich) +function right_projector(E_1, C, E_2, U, isqS, A) + isqS_Udt = isqS * twistnondual(U', 2:numind(U)) + return _right_projector(E_1, C, E_2, isqS_Udt, A) +end +function _right_projector(E_1, C, E_2, Ud, A::PEPSSandwich) return @autoopt @tensor P_right[χ_out; χ_in D_inabove D_inbelow] := - isqS[χ_out; χ1] * conj(U[χ1; χ2 D1 D2]) * + Ud[χ_out; χ2 D1 D2] * ket(A)[d; D3 D5 D_inabove D1] * conj(bra(A)[d; D4 D6 D_inbelow D2]) * - E_2[χ2 D3 D4; χ3] * C[χ3; χ4] * E_1[χ4 D5 D6; χ_in] + E_1[χ2 D3 D4; χ3] * C[χ3; χ4] * E_2[χ4 D5 D6; χ_in] end -function right_projector(E_1, C, E_2, U, isqS, A::PFTensor) +function _right_projector(E_1, C, E_2, Ud, A::PFTensor) return @autoopt @tensor P_right[χ_out; χ_in D_in] := - isqS[χ_out; χ1] * conj(U[χ1; χ2 D1]) * + Ud[χ_out; χ2 D1] * A[D1 D_in; D2 D3] * - E_2[χ2 D2; χ3] * C[χ3; χ4] * E_1[χ4 D3; χ_in] + E_1[χ2 D2; χ3] * C[χ3; χ4] * E_2[χ4 D3; χ_in] +end +@generated function _right_projector( + E_1, C, E_2, Ud, A::PEPOSandwich{H} + ) where {H} + + E_north_e = _pepo_edge_expr(:E_1, :NW, :NNE, :N, H) + E_east_e = _pepo_edge_expr(:E_2, :ENE, :in, :E, H) + C_northeast_e = _corner_expr(:C, :NNE, :ENE) + ket_e, bra_e, pepo_es = _pepo_sandwich_expr(:A, H) + + Ud_e = _pepo_codomain_projector_expr(:Ud, :out, :NW, :W, H) + + P_right_e = _pepo_codomain_projector_expr(:P_right, :out, :in, :S, H) + + rhs = Expr( + :call, :*, + E_north_e, C_northeast_e, E_east_e, + ket_e, Expr(:call, :conj, bra_e), + pepo_es..., + Ud_e + ) + + return macroexpand( + @__MODULE__, :(return @autoopt @tensor $P_right_e := $rhs) + ) +end + +""" +$(SIGNATURES) + +Contract the CTMRG left projector with the higher-dimensional subspace facing to the left. + +``` + C_1 -- E_2 -- E_3 -- C_2 + | | | | + E_1 -- A_1 -- A_2 -- E_4 + | | | | + [~~~U'~] in + | + isqS + | + out +``` +""" +function right_projector(E_1, C_1, E_2, E_3, C_2, E_4, U, isqS, A_1, A_2) + isqS_Udt = isqS * twistnondual(U', 2:numind(U)) + return _right_projector(E_1, C_1, E_2, E_3, C_2, E_4, isqS_Udt, A_1, A_2) +end +function _right_projector( + E_1, C_1, E_2, E_3, C_2, E_4, Ud, A_1::PEPSSandwich, A_2::PEPSSandwich + ) + return @autoopt @tensor P_right[χ_out; χ_in D_ina D_inb] := + E_1[χ_SW D_W1a D_W1b; χ_WNW] * C_1[χ_WNW; χ_NNW] * E_2[χ_NNW D_N1a D_N1b; χ_N] * + ket(A_1)[d1; D_N1a D_Ca D_S1a D_W1a] * conj(bra(A_1)[d1; D_N1b D_Cb D_S1b D_W1b]) * + E_3[χ_N D_N2a D_N2b; χ_NNE] * C_2[χ_NNE; χ_ENE] * E_4[χ_ENE D_E2a D_E2b; χ_in] * + ket(A_2)[d2; D_N2a D_E2a D_ina D_Ca] * conj(bra(A_2)[d2; D_N2b D_E2b D_inb D_Cb]) * + Ud[χ_out; χ_SW D_S1a D_S1b] +end +function _right_projector( + E_1, C_1, E_2, E_3, C_2, E_4, Ud, A_1::PFTensor, A_2::PFTensor + ) + return @autoopt @tensor P_right[χ_out; χ_in D_in] := + E_1[χ_SW D_W1; χ_WNW] * C_1[χ_WNW; χ_NNW] * E_2[χ_NNW D_N1; χ_N] * + A_1[D_W1 D_S1; D_N1 D_C] * + E_3[χ_N D_N2; χ_NNE] * C_2[χ_NNE; χ_ENE] * E_4[χ_ENE D_E2; χ_in] * + A_2[D_C D_in; D_N2 D_E2] * + Ud[χ_out; χ_SW D_S1] +end +@generated function _right_projector( + E_1, C_1, E_2, E_3, C_2, E_4, Ud, A_1::PEPOSandwich{H}, A_2::PEPOSandwich{H} + ) where {H} + + Ud_e = _pepo_codomain_projector_expr(:Ud, :out, :SW, :S, H, 1) + proj_expr = _half_infinite_environment_expr(H) + P_right_e = _pepo_codomain_projector_expr(:P_right, :out, :SE, :S, H, 2) + + return macroexpand( + @__MODULE__, :(return @autoopt @tensor $P_right_e := $Ud_e * $proj_expr) + ) end """ diff --git a/src/algorithms/contractions/ctmrg/renormalize_corner.jl b/src/algorithms/contractions/ctmrg/renormalize_corner.jl index bfe817571..5522b2849 100644 --- a/src/algorithms/contractions/ctmrg/renormalize_corner.jl +++ b/src/algorithms/contractions/ctmrg/renormalize_corner.jl @@ -285,7 +285,7 @@ end C_out_e = _corner_expr(:corner, :out, :in) P_right_e = _pepo_codomain_projector_expr(:P_right, :out, :N, :N, H) - E_east_e = _pepo_edge_expr(:E_east, :N, :EWE, :E, H) + E_east_e = _pepo_edge_expr(:E_east, :N, :ESE, :E, H) C_southeast_e = _corner_expr(:C_southeast, :ESE, :SSE) E_south_e = _pepo_edge_expr(:E_south, :SSE, :W, :S, H) ket_e, bra_e, pepo_es = _pepo_sandwich_expr(:A, H) diff --git a/src/algorithms/ctmrg/c4v.jl b/src/algorithms/ctmrg/c4v.jl index fa0b7650f..777bd4b27 100644 --- a/src/algorithms/ctmrg/c4v.jl +++ b/src/algorithms/ctmrg/c4v.jl @@ -21,7 +21,7 @@ For a full description, see [`leading_boundary`](@ref). The supported keywords a * `miniter::Int=$(Defaults.ctmrg_miniter)` * `verbosity::Int=$(Defaults.ctmrg_verbosity)` * `trunc::Union{TruncationStrategy,NamedTuple}=(; alg::Symbol=:$(Defaults.trunc))` -* `decomposition_alg::Union{<:EighAdjoint,NamedTuple}` +* `decomposition_alg::Union{NamedTuple,<:EighAdjoint,<:QRAdjoint}=(;)` * `projector_alg::Symbol=:$(Defaults.projector_alg_c4v)` """ struct C4vCTMRG{P <: ProjectorAlgorithm} <: CTMRGAlgorithm @@ -99,8 +99,11 @@ function C4vQRProjector(; kwargs...) end PROJECTOR_SYMBOLS[:c4v_qr] = C4vQRProjector +decomposition_algorithm(alg::C4vQRProjector) = alg.decomposition_alg + # no truncation _set_truncation(alg::C4vQRProjector, ::TruncationStrategy) = alg +_set_decomposition_truncation(alg::C4vQRProjector, ::TruncationStrategy) = alg function check_input( ::typeof(leading_boundary), network::InfiniteSquareNetwork, env::CTMRGEnv, alg::C4vCTMRG; atol = 1.0e-10 @@ -144,8 +147,8 @@ function ctmrg_iteration( corner′, projector, info = c4v_projector!(enlarged_corner, alg.projector_alg) edge′ = c4v_renormalize_edge(network, env, projector) info = (; - contraction_metrics = (; info.truncation_error, info.condition_number), - info.D, info.V, info.D_full, info.V_full, info.truncation_indices, + contraction_metrics = (; info.truncation_error), + info.D, info.V, ) return CTMRGEnv(corner′, edge′), info end @@ -189,8 +192,10 @@ Compute the C₄ᵥ projector from `eigh` decomposing the Hermitian `enlarged_co Also return the normalized eigenvalues as the new corner tensor. """ function c4v_projector!(enlarged_corner, alg::C4vEighProjector) - trunc = truncation_strategy(alg, enlarged_corner) - D, V, info = eigh_trunc!(enlarged_corner, decomposition_algorithm(alg); trunc) + alg = _set_decomposition_truncation(alg, truncation_strategy(alg, enlarged_corner)) + eigh_alg = decomposition_algorithm(alg) + + D, V, truncation_error = eigh_trunc!(enlarged_corner, eigh_alg) # Check for degenerate eigenvalues Zygote.isderiving() && ignore_derivatives() do @@ -200,7 +205,7 @@ function c4v_projector!(enlarged_corner, alg::C4vEighProjector) end end - return D / norm(D), V, (; D, V, info...) + return D / norm(D), V, (; D, V, truncation_error) end """ c4v_projector!(enlarged_corner, alg::C4vQRProjector) @@ -216,7 +221,7 @@ Compute the C₄ᵥ projector by decomposing the column-enlarged corner with `le function c4v_projector!(enlarged_corner, alg::C4vQRProjector) Q, R = left_orth!(enlarged_corner, decomposition_algorithm(alg)) # TODO: what's a meaningful way to compute a truncation error/condition number in this scheme? - return Q, (; Q, R, truncation_error = zero(scalartype(Q)), condition_number = 0) + return Q, (; Q, R, truncation_error = zero(scalartype(Q))) end """ diff --git a/src/algorithms/ctmrg/ctmrg.jl b/src/algorithms/ctmrg/ctmrg.jl index 839e32a43..fb6155143 100644 --- a/src/algorithms/ctmrg/ctmrg.jl +++ b/src/algorithms/ctmrg/ctmrg.jl @@ -8,6 +8,7 @@ abstract type CTMRGAlgorithm end const CTMRG_SYMBOLS = IdDict{Symbol, Type{<:CTMRGAlgorithm}}() + """ CTMRGAlgorithm(; kwargs...) @@ -30,6 +31,10 @@ function CTMRGAlgorithm(; if alg == :c4v && projector_alg == Defaults.projector_alg projector_alg = Defaults.projector_alg_c4v end + # check for full decomposition algorithm specification, otherwise interpret as forward alg + if decomposition_alg isa NamedTuple + decomposition_alg = (; fwd_alg = decomposition_alg) + end projector_algorithm = ProjectorAlgorithm(; alg = projector_alg, decomposition_alg, trunc, verbosity ) @@ -79,19 +84,24 @@ supplied via the keyword arguments or directly as an [`CTMRGAlgorithm`](@ref) st - `:truncrank` : Additionally supply truncation dimension `η`; truncate such that the 2-norm of the truncated values is smaller than `η` - `:truncspace` : Additionally supply truncation space `η`; truncate according to the supplied vector space - `:trunctol` : Additionally supply singular value cutoff `η`; truncate such that every retained singular value is larger than `η` -* `decomposition_alg` : Tensor decomposition algorithm for computing projectors. See e.g. [`SVDAdjoint`](@ref). * `projector_alg::Symbol=:$(Defaults.projector_alg)` : Variant of the projector algorithm. See also [`ProjectorAlgorithm`](@ref). - `:halfinfinite` : Projection via SVDs of half-infinite (two enlarged corners) CTMRG environments. - `:fullinfinite` : Projection via SVDs of full-infinite (all four enlarged corners) CTMRG environments. - `:c4v_eigh` : Projection via `eigh` of the Hermitian enlarged corner, works only for [`C4vCTMRG`](@ref). - `:c4v_qr` : Projection via QR decomposition of the lower-rank column-enlarged corner, works only for [`C4vCTMRG`](@ref). +* `decomposition_alg::Union{NamedTuple,<:SVDAdjoint,<:EighAdjoint,<:QRAdjoint}` : Tensor + decomposition algorithm used for computing projectors. When specified as a `NamedTuple`, + the settings are passed a the forward algorithm to the appropriate decomposition + for the given projector algorithm. For information on which forward algorithms are + available, and how to specify them, see [`SVDAdjoint`](@ref), [`EighAdjoint`](@ref) and [`QRAdjoint`](@ref). ## Return values -The `leading_boundary` routine returns the final environment as well as an information `NamedTuple` -that generally contains a `contraction_metrics` `NamedTuple` storing different contents depending -on the chosen `alg`. Depending on the contraction method, the information tuple may also contain -the final tensor decomposition (used in the projectors) including its truncation indices. +* `env` : The final environment. +* `info` : A `NamedTuple` containing information about the `leading_boundary` convergence, which has the following fields: + - `info.converged::Bool` : Convergence flag indicating whether the contraction converged within `maxiter` and `tol`. + - `info.convergence_error::Real` : The final convergence error at the end of the contraction procedure. + - `info.contraction_metrics::NamedTuple` : A `NamedTuple` containing metrics which characterize the contraction. The precise contents depend on `alg`. """ function leading_boundary(env₀::CTMRGEnv, network::InfiniteSquareNetwork; kwargs...) alg = select_algorithm(leading_boundary, env₀; kwargs...) @@ -109,13 +119,15 @@ function leading_boundary( end η = one(real(scalartype(network))) ctmrg_loginit!(log, η, network, env₀) - local info + local info_iter + converged = false for iter in 1:(alg.maxiter) - env, info = ctmrg_iteration(network, env, alg) + env, info_iter = ctmrg_iteration(network, env, alg) η, CS, TS = calc_convergence(env, CS, TS) if η ≤ alg.tol && iter ≥ alg.miniter ctmrg_logfinish!(log, iter, η, network, env) + converged = true break end if iter == alg.maxiter @@ -124,6 +136,11 @@ function leading_boundary( ctmrg_logiter!(log, iter, η, network, env) end end + info = (; + converged, + convergence_error = η, + contraction_metrics = info_iter.contraction_metrics, + ) return env, info end end diff --git a/src/algorithms/ctmrg/gaugefix.jl b/src/algorithms/ctmrg/gaugefix.jl index afab216c8..9d238cafc 100644 --- a/src/algorithms/ctmrg/gaugefix.jl +++ b/src/algorithms/ctmrg/gaugefix.jl @@ -1,20 +1,3 @@ -""" - gauge_fix(alg::CTMRGAlgorithm, signs, info) - gauge_fix(alg::ProjectorAlgorithm, signs, info) - -Fix the free gauges of the tensor decompositions associated with `alg`. -""" -function gauge_fix(alg::CTMRGAlgorithm, signs, info) - alg_fixed = @set alg.projector_alg = gauge_fix(alg.projector_alg, signs, info) - return alg_fixed -end -function gauge_fix(alg::ProjectorAlgorithm, signs, info) - decomposition_alg_fixed = gauge_fix(decomposition_algorithm(alg), signs, info) - alg_fixed = @set alg.decomposition_alg = decomposition_alg_fixed # every ProjectorAlgorithm needs an `decomposition_alg` field? - alg_fixed = _set_truncation(alg_fixed, notrunc()) # potentially set no truncation - return alg_fixed -end - # TODO: add eigensolver algorithm, verbosity to gauge algorithm structs """ $(TYPEDEF) @@ -43,13 +26,43 @@ 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(envfinal::CTMRGEnv{C, T}, envprev::CTMRGEnv{C, T}, ::ScramblingEnvGauge) where {C, T} +function gauge_fix( + envfinal::CTMRGEnv{C, T}, envprev::CTMRGEnv{C, T}, alg::G + ) where {C, T, G <: Union{ScramblingEnvGauge, ScramblingEnvGaugeC4v}} + signs, corner_phases, edge_phases = compute_gauge_fix_gauge(envfinal, envprev, alg) + return fix_phases(envfinal, signs, corner_phases, edge_phases) +end + +function compute_gauge_fix_gauge( + envfinal::CTMRGEnv{C, T}, envprev::CTMRGEnv{C, T}, alg::G + ) where {C, T, G <: Union{ScramblingEnvGauge, ScramblingEnvGaugeC4v}} # Check if spaces in envprev and envfinal are the same 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" + all(same_spaces) || throw(ArgumentError("Spaces of envfinal and envprev are not the same")) + + # find relative phases + signs = compute_relative_phases(envfinal, envprev, alg) + + # find additional global phases + corner_phases, edge_phases = compute_global_phases( + fix_relative_phases(envfinal, signs), envprev + ) + + return signs, corner_phases, edge_phases +end + +function fix_phases(env::CTMRGEnv, signs, corner_phases, edge_phases) + env_fixed_relative = fix_relative_phases(env, signs) + env_fixed_global = fix_global_phases(env_fixed_relative, corner_phases, edge_phases) + return env_fixed_global +end + +function compute_relative_phases( + envfinal::CTMRGEnv{C, T}, envprev::CTMRGEnv{C, T}, ::ScramblingEnvGauge + ) where {C, T} signs = map(eachcoordinate(envfinal, 1:4)) do (dir, r, c) # Gather edge tensors and pretend they're InfiniteMPSs @@ -84,21 +97,10 @@ function gauge_fix(envfinal::CTMRGEnv{C, T}, envprev::CTMRGEnv{C, T}, ::Scrambli return Qprev * Qfinal' end - env_fixed_relative = fix_relative_phases(envfinal, signs) - env_fixed_full = fix_global_phases(env_fixed_relative, envprev) - - return env_fixed_full, signs + return signs end -# C4v specialized gauge fixing routine with Hermitian transfer matrix -function gauge_fix(envfinal::CTMRGEnv{C, T}, envprev::CTMRGEnv{C, T}, ::ScramblingEnvGaugeC4v) where {C, T} - # Check if spaces in envprev and envfinal are the same - 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" - +function compute_relative_phases(envfinal::CTMRGEnv{C, T}, envprev::CTMRGEnv{C, T}, ::ScramblingEnvGaugeC4v) where {C, T} # "general" algorithm from https://arxiv.org/abs/2311.11894 Tprev = envprev.edges[1, 1, 1] Tfinal = envfinal.edges[1, 1, 1] @@ -118,11 +120,9 @@ function gauge_fix(envfinal::CTMRGEnv{C, T}, envprev::CTMRGEnv{C, T}, ::Scrambli σ = Qprev * Qfinal' - cornerfix = σ * envfinal.corners[1] * σ' - @tensor edgefix[χ_in D_in_above D_in_below; χ_out] := - σ[χ_in; χ1] * envfinal.edges[1][χ1 D_in_above D_in_below; χ2] * conj(σ[χ_out; χ2]) + signs = fill(σ, (4, 1, 1)) - return CTMRGEnv(cornerfix, edgefix), fill(σ, (4, 1, 1)) + return signs end # this is a bit of a hack to get the fixed point of the mixed transfer matrix @@ -167,27 +167,29 @@ end # Explicit fixing of relative phases (doing this compactly in a loop is annoying) function fix_relative_phases(envfinal::CTMRGEnv, signs) corners_fixed = map(eachcoordinate(envfinal, 1:4)) do (dir, r, c) - if dir == NORTHWEST + Cf = 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 + else # dir == SOUTHWEST fix_gauge_southwest_corner((r, c), envfinal, signs) end + return Cf end edges_fixed = map(eachcoordinate(envfinal, 1:4)) do (dir, r, c) - if dir == NORTHWEST + Ef = 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 + else # dir == SOUTHWEST fix_gauge_west_edge((r, c), envfinal, signs) end + return Ef end return CTMRGEnv(corners_fixed, edges_fixed) @@ -197,28 +199,30 @@ function fix_relative_phases( ) where {Ut <: AbstractTensorMap, Vt <: AbstractTensorMap} U_fixed = map(eachindex(IndexCartesian(), U)) do I dir, r, c = Tuple(I) - if dir == NORTHWEST + Uf = 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 + else # dir == SOUTHWEST fix_gauge_west_left_vecs((r, c), U, signs) end + return Uf end V_fixed = map(eachindex(IndexCartesian(), V)) do I dir, r, c = Tuple(I) - if dir == NORTHWEST + Vf = 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 + else # dir == SOUTHWEST fix_gauge_west_right_vecs((r, c), V, signs) end + return Vf end return U_fixed, V_fixed @@ -232,15 +236,21 @@ between all corners and all edges are computed to obtain the global phase which divided out. """ function fix_global_phases(envfix::CTMRGEnv, envprev::CTMRGEnv) - cornersgfix = map(zip(envprev.corners, envfix.corners)) do (Cprev, Cfix) - return Cfix * inv(dot(Cprev, Cfix)) - end - edgesgfix = map(zip(envprev.edges, envfix.edges)) do (Tprev, Tfix) - return Tfix * inv(dot(Tprev, Tfix)) - end + corner_phases, edge_phases = compute_global_phases(envfix, envprev) + return fix_global_phases(envfix, corner_phases, edge_phases) +end +function fix_global_phases(env::CTMRGEnv, corner_phases, edge_phases) + cornersgfix = env.corners .* inv.(corner_phases) + edgesgfix = env.edges .* inv.(edge_phases) return CTMRGEnv(cornersgfix, edgesgfix) end +function compute_global_phases(envfix::CTMRGEnv, envprev::CTMRGEnv) + corner_phases = dot.(envprev.corners, envfix.corners) + edge_phases = dot.(envprev.edges, envfix.edges) + return corner_phases, edge_phases +end + """ calc_elementwise_convergence(envfinal, envfix; atol=1e-6) diff --git a/src/algorithms/ctmrg/projectors.jl b/src/algorithms/ctmrg/projectors.jl index f90f9d0c4..b49b542d4 100644 --- a/src/algorithms/ctmrg/projectors.jl +++ b/src/algorithms/ctmrg/projectors.jl @@ -52,34 +52,10 @@ end """ decomposition_algorithm(alg::ProjectorAlgorithm) - decomposition_algorithm(alg::ProjectorAlgorithm, (dir, r, c)) Return the tensor decomposition algorithm of the `alg` projector algorithm. -Additionally, the multi-index `(dir, r, c)` can be supplied which will return the -decomposition performed at that index, e.g. when using [`FixedEig`](@ref) or [`FixedSVD`](@ref). """ decomposition_algorithm(alg::ProjectorAlgorithm) = alg.decomposition_alg -function decomposition_algorithm(alg::ProjectorAlgorithm, (dir, r, c)) - decomposition_alg = decomposition_algorithm(alg) - if decomposition_alg isa SVDAdjoint{<:FixedSVD} - fwd_alg = decomposition_alg.fwd_alg - fix_svd = if isfullsvd(decomposition_alg.fwd_alg) - FixedSVD( - fwd_alg.U[dir, r, c], fwd_alg.S[dir, r, c], fwd_alg.V[dir, r, c], - fwd_alg.U_full[dir, r, c], fwd_alg.S_full[dir, r, c], fwd_alg.V_full[dir, r, c], - fwd_alg.truncation_indices[dir, r, c], - ) - else - FixedSVD( - fwd_alg.U[dir, r, c], fwd_alg.S[dir, r, c], fwd_alg.V[dir, r, c], - nothing, nothing, nothing, nothing, - ) - end - return SVDAdjoint(; fwd_alg = fix_svd, rrule_alg = decomposition_alg.rrule_alg) - else - return decomposition_alg - end -end function truncation_strategy(alg::ProjectorAlgorithm, edge) if alg.trunc isa FixedSpaceTruncation @@ -97,8 +73,22 @@ Update the truncation strategy of a given projector algorithm, keeping all other the same. """ function _set_truncation(alg::ProjectorAlgorithm, trunc::TruncationStrategy) - alg´ = @set alg.trunc = trunc - return alg´ + alg = @set alg.trunc = trunc + return alg +end + +""" + _set_decomposition_truncation(alg::ProjectorAlgorithm, trunc::TruncationStrategy) + +Set the truncation strategy of the decomposition algorithm within a given projector +algorithm, keeping all other settings the same. +""" +function _set_decomposition_truncation(alg::ProjectorAlgorithm, trunc::TruncationStrategy) + decomp_alg = decomposition_algorithm(alg) + truncated_fwd_alg = TruncatedAlgorithm(decomp_alg.fwd_alg, trunc) + decomp_alg = @set decomp_alg.fwd_alg = truncated_fwd_alg + alg = @set alg.decomposition_alg = decomp_alg + return alg end """ @@ -178,16 +168,19 @@ end PROJECTOR_SYMBOLS[:fullinfinite] = FullInfiniteProjector """ - compute_projector(enlarged_corners, coordinate, alg::ProjectorAlgorithm) + compute_projector(enlarged_corners, alg::ProjectorAlgorithm) Determine left and right projectors at the bond given determined by the enlarged corners -and the given coordinate using the specified `alg`. +using the specified `alg`. """ -function compute_projector(enlarged_corners, coordinate, alg::HalfInfiniteProjector) +function compute_projector(enlarged_corners, alg::HalfInfiniteProjector) # SVD half-infinite environment halfinf = half_infinite_environment(enlarged_corners...) - svd_alg = decomposition_algorithm(alg, coordinate) - U, S, V, info = svd_trunc!(halfinf / norm(halfinf), svd_alg; trunc = alg.trunc) + svd_alg = decomposition_algorithm(alg) + U, S, V, truncation_error = svd_trunc!(halfinf / norm(halfinf), svd_alg) + + # get some decomposition info + truncation_error = truncation_error / norm(S) # normalize truncation error # Check for degenerate singular values Zygote.isderiving() && ignore_derivatives() do @@ -197,18 +190,20 @@ function compute_projector(enlarged_corners, coordinate, alg::HalfInfiniteProjec end end - @reset info.truncation_error = info.truncation_error / norm(S) # normalize truncation error P_left, P_right = contract_projectors(U, S, V, enlarged_corners...) - return (P_left, P_right), (; U, S, V, info...) + return (P_left, P_right), (; U, S, V, truncation_error) end -function compute_projector(enlarged_corners, coordinate, alg::FullInfiniteProjector) +function compute_projector(enlarged_corners, 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(halfinf_left, halfinf_right) - svd_alg = decomposition_algorithm(alg, coordinate) - U, S, V, info = svd_trunc!(fullinf / norm(fullinf), svd_alg; trunc = alg.trunc) + svd_alg = decomposition_algorithm(alg) + U, S, V, truncation_error = svd_trunc!(fullinf / norm(fullinf), svd_alg) + + # get some decomposition info + truncation_error = truncation_error / norm(S) # normalize truncation error # Check for degenerate singular values Zygote.isderiving() && ignore_derivatives() do @@ -218,7 +213,6 @@ function compute_projector(enlarged_corners, coordinate, alg::FullInfiniteProjec end end - @reset info.truncation_error = info.truncation_error / norm(S) # normalize truncation error P_left, P_right = contract_projectors(U, S, V, halfinf_left, halfinf_right) - return (P_left, P_right), (; U, S, V, info...) + return (P_left, P_right), (; U, S, V, truncation_error) end diff --git a/src/algorithms/ctmrg/sequential.jl b/src/algorithms/ctmrg/sequential.jl index 9c858d36d..69d45a34a 100644 --- a/src/algorithms/ctmrg/sequential.jl +++ b/src/algorithms/ctmrg/sequential.jl @@ -59,17 +59,15 @@ end function ctmrg_iteration(network, env::CTMRGEnv, alg::SequentialCTMRG) truncation_error = zero(real(scalartype(network))) - condition_number = zero(real(scalartype(network))) for _ in 1:4 # rotate for col in 1:size(network, 2) # left move column-wise env, info = ctmrg_leftmove(col, network, env, alg) truncation_error = max(truncation_error, info.truncation_error) - condition_number = max(condition_number, info.condition_number) end network = rotate_north(network, EAST) env = rotate_north(env, EAST) end - return env, (; contraction_metrics = (; truncation_error, condition_number)) + return env, (; contraction_metrics = (; truncation_error)) end """ @@ -97,10 +95,10 @@ function sequential_projectors( _, r, c = coordinate r′ = _prev(r, size(env, 2)) trunc = truncation_strategy(alg, env.edges[WEST, r′, c]) - alg′ = @set alg.trunc = trunc + alg´ = _set_decomposition_truncation(alg, trunc) Q1 = TensorMap(EnlargedCorner(network, env, (SOUTHWEST, r, c))) Q2 = TensorMap(EnlargedCorner(network, env, (NORTHWEST, r′, c))) - return compute_projector((Q1, Q2), coordinate, alg′) + return compute_projector((Q1, Q2), alg´) end function sequential_projectors( coordinate::NTuple{3, Int}, network, env::CTMRGEnv, alg::FullInfiniteProjector @@ -110,14 +108,14 @@ function sequential_projectors( coordinate_ne = _next_coordinate(coordinate_nw, rowsize, colsize) coordinate_se = _next_coordinate(coordinate_ne, rowsize, colsize) trunc = truncation_strategy(alg, env.edges[WEST, coordinate_nw[2:3]...]) - alg′ = @set alg.trunc = trunc + alg´ = _set_decomposition_truncation(alg, trunc) ec = ( TensorMap(EnlargedCorner(network, env, coordinate_se)), TensorMap(EnlargedCorner(network, env, coordinate)), TensorMap(EnlargedCorner(network, env, coordinate_nw)), TensorMap(EnlargedCorner(network, env, coordinate_ne)), ) - return compute_projector(ec, coordinate, alg′) + return compute_projector(ec, alg´) end """ diff --git a/src/algorithms/ctmrg/simultaneous.jl b/src/algorithms/ctmrg/simultaneous.jl index 7e1fafb00..cab9e6dc3 100644 --- a/src/algorithms/ctmrg/simultaneous.jl +++ b/src/algorithms/ctmrg/simultaneous.jl @@ -49,8 +49,8 @@ function ctmrg_iteration(network, env::CTMRGEnv, alg::SimultaneousCTMRG) projectors, info = simultaneous_projectors(enlarged_corners, env, alg.projector_alg) # compute projectors on all coordinates env′ = renormalize_simultaneously(enlarged_corners, projectors, network, env) # renormalize enlarged corners info = (; - contraction_metrics = (; info.truncation_error, info.condition_number), - info.U, info.S, info.V, info.U_full, info.S_full, info.V_full, info.truncation_indices, + contraction_metrics = (; info.truncation_error), + info.U, info.S, info.V, ) return env′, info end @@ -61,15 +61,10 @@ function _split_proj_and_info(proj_and_info) P_left = map(x -> x[1][1], proj_and_info) P_right = map(x -> x[1][2], proj_and_info) truncation_error = maximum(x -> x[2].truncation_error, proj_and_info) - condition_number = maximum(x -> x[2].condition_number, proj_and_info) U = map(x -> x[2].U, proj_and_info) S = map(x -> x[2].S, proj_and_info) V = map(x -> x[2].V, proj_and_info) - U_full = map(x -> x[2].U_full, proj_and_info) - S_full = map(x -> x[2].S_full, proj_and_info) - V_full = map(x -> x[2].V_full, proj_and_info) - truncation_indices = map(x -> x[2].truncation_indices, proj_and_info) - info = (; truncation_error, condition_number, U, S, V, U_full, S_full, V_full, truncation_indices) + info = (; truncation_error, U, S, V) return (P_left, P_right), info end @@ -100,9 +95,9 @@ function simultaneous_projectors( ) where {E} coordinate′ = _next_coordinate(coordinate, size(env)[2:3]...) trunc = truncation_strategy(alg, env.edges[coordinate[1], coordinate′[2:3]...]) - alg′ = @set alg.trunc = trunc + alg′ = _set_decomposition_truncation(alg, trunc) ec = (enlarged_corners[coordinate...], enlarged_corners[coordinate′...]) - return compute_projector(ec, coordinate, alg′) + return compute_projector(ec, alg′) end function simultaneous_projectors( coordinate, enlarged_corners::Array{E, 3}, env, alg::FullInfiniteProjector @@ -112,14 +107,14 @@ function simultaneous_projectors( coordinate3 = _next_coordinate(coordinate2, rowsize, colsize) coordinate4 = _next_coordinate(coordinate3, rowsize, colsize) trunc = truncation_strategy(alg, env.edges[coordinate[1], coordinate2[2:3]...]) - alg′ = @set alg.trunc = trunc + alg′ = _set_decomposition_truncation(alg, trunc) ec = ( enlarged_corners[coordinate4...], enlarged_corners[coordinate...], enlarged_corners[coordinate2...], enlarged_corners[coordinate3...], ) - return compute_projector(ec, coordinate, alg′) + return compute_projector(ec, alg′) end """ diff --git a/src/algorithms/ctmrg/sparse_environments.jl b/src/algorithms/ctmrg/sparse_environments.jl index 01a9c98da..5d5526fd9 100644 --- a/src/algorithms/ctmrg/sparse_environments.jl +++ b/src/algorithms/ctmrg/sparse_environments.jl @@ -99,10 +99,12 @@ function half_infinite_environment(ec_1::EnlargedCorner, ec_2::EnlargedCorner) 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) +function contract_projectors(U, S, V, Q::EnlargedCorner, Q_next::EnlargedCorner) + Ar = _rotate_north_localsandwich(Q.A, _prev(Q.dir, 4)) + Ar_next = _rotate_north_localsandwich(Q_next.A, Q_next.dir) isqS = sdiag_pow(S, -0.5) - P_left = left_projector(Q.E_1, Q.C, Q.E_2, V, isqS, Q.A) - P_right = right_projector(Q_next.E_1, Q_next.C, Q_next.E_2, U, isqS, Q_next.A) + P_left = left_projector(Q_next.E_1, Q_next.C, Q_next.E_2, V, isqS, Ar_next) + P_right = right_projector(Q.E_1, Q.C, Q.E_2, U, isqS, Ar) return P_left, P_right end @@ -134,12 +136,23 @@ struct HalfInfiniteEnv{TC, TE, TA} # TODO: subtype as AbstractTensorMap once Te E_4::TE A_1::TA A_2::TA + A_1r::TA # prerotate + A_2r::TA + dir::Int + function HalfInfiniteEnv( + C_1::TC, C_2::TC, E_1::TE, E_2::TE, E_3::TE, E_4::TE, A_1::TA, A_2::TA, dir::Int + ) where {TC, TE, TA} + A_1r = _rotate_north_localsandwich(A_1, dir) + A_2r = _rotate_north_localsandwich(A_2, dir) + return new{TC, TE, TA}(C_1, C_2, E_1, E_2, E_3, E_4, A_1, A_2, A_1r, A_2r, dir) + end end function HalfInfiniteEnv(quadrant1::EnlargedCorner, quadrant2::EnlargedCorner) return HalfInfiniteEnv( quadrant1.C, quadrant2.C, quadrant1.E_1, quadrant1.E_2, quadrant2.E_1, quadrant2.E_2, - quadrant1.A_1, quadrant2.A_2, + quadrant1.A, quadrant2.A, + quadrant1.dir, ) end @@ -150,7 +163,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.A_1, env.A_2 + env.C_1, env.C_2, env.E_1, env.E_2, env.E_3, env.E_4, env.A_1r, env.A_2r ) end @@ -163,29 +176,58 @@ 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.A_1, env.A_2 + env.C_1, env.C_2, env.E_1, env.E_2, env.E_3, env.E_4, x, env.A_1r, env.A_2r ) 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.A_1, env.A_2 + x, env.C_1, env.C_2, env.E_1, env.E_2, env.E_3, env.E_4, env.A_1r, env.A_2r + ) +end + +function contract_projectors(U, S, V, env::HalfInfiniteEnv) + Q = EnlargedCorner(env.C_1, env.E_1, env.E_2, env.A_1, env.dir) + Q_next = EnlargedCorner(env.C_2, env.E_3, env.E_4, env.A_2, _next(env.dir, 4)) + return contract_projectors(U, S, V, Q, Q_next) +end + +function contract_projectors(U, S, V, henv::HalfInfiniteEnv, henv_next::HalfInfiniteEnv) + isqS = sdiag_pow(S, -0.5) + P_left = left_projector( + henv_next.E_1, henv_next.C_1, henv_next.E_2, henv_next.E_3, henv_next.C_2, henv_next.E_4, + V, isqS, + henv_next.A_1r, henv_next.A_2r ) + P_right = right_projector( + henv.E_1, henv.C_1, henv.E_2, henv.E_3, henv.C_2, henv.E_4, + U, isqS, + henv.A_1r, henv.A_2r + ) + return P_left, P_right end # ----------------------------------------------------- # AbstractTensorMap subtyping and IterSVD compatibility # ----------------------------------------------------- +function TensorKit.storagetype(::Type{HalfInfiniteEnv{TC, TE, TA}}) where {TC, TE, TA} + return TensorKit.promote_storagetype(TC, TE, storagetype(TA)) +end + +function TensorKit.spacetype(::Type{HalfInfiniteEnv{TC, TE, TA}}) where {TC, TE, TA} + return spacetype(TC) +end + function TensorKit.domain(env::HalfInfiniteEnv) - return domain(env.E_4) * _elementwise_dual(south_virtualspace(env.A_2)) + return domain(env.E_4) * _elementwise_dual(south_virtualspace(env.A_2r)) end function TensorKit.codomain(env::HalfInfiniteEnv) - return first(codomain(env.E_1)) * south_virtualspace(env.A_1) + return first(codomain(env.E_1)) * south_virtualspace(env.A_1r) end function random_start_vector(env::HalfInfiniteEnv) - return randn(domain(env)) + return randn(storagetype(env), domain(env)) end # -------------------------------- @@ -225,6 +267,28 @@ struct FullInfiniteEnv{TC, TE, TA} # TODO: subtype as AbstractTensorMap once Te A_2::TA A_3::TA A_4::TA + A_1r::TA + A_2r::TA + A_3r::TA + A_4r::TA + dir::Int + function FullInfiniteEnv( + 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, dir::Int + ) where {TC, TE, TA} + A_1r = _rotate_north_localsandwich(A_1, dir) + A_2r = _rotate_north_localsandwich(A_2, dir) + A_3r = _rotate_north_localsandwich(A_3, dir) + A_4r = _rotate_north_localsandwich(A_4, dir) + return new{TC, TE, TA}( + C_1, C_2, C_3, C_4, + E_1, E_2, E_3, E_4, E_5, E_6, E_7, E_8, + A_1, A_2, A_3, A_4, + A_1r, A_2r, A_3r, A_4r, + dir, + ) + end end function FullInfiniteEnv( quadrant1::E, quadrant2::E, quadrant3::E, quadrant4::E @@ -234,6 +298,7 @@ function FullInfiniteEnv( 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.A, quadrant2.A, quadrant3.A, quadrant4.A, + quadrant1.dir, ) end @@ -245,8 +310,8 @@ 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.A_1, env.A_2, env.A_3, env.A_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.A_1r, env.A_2r, env.A_3r, env.A_4r, ) end @@ -262,7 +327,7 @@ function (env::FullInfiniteEnv)(x, ::Val{false}) # Linear map: env() * 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, x, - env.A_1, env.A_2, env.A_3, env.A_4, + env.A_1r, env.A_2r, env.A_3r, env.A_4r, ) end function (env::FullInfiniteEnv)(x, ::Val{true}) # Adjoint linear map: env()' * x @@ -270,7 +335,7 @@ function (env::FullInfiniteEnv)(x, ::Val{true}) # Adjoint linear map: env()' * 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.A_1, env.A_2, env.A_3, env.A_4, + env.A_1r, env.A_2r, env.A_3r, env.A_4r, ) end @@ -281,17 +346,45 @@ function full_infinite_environment( return FullInfiniteEnv(ec_1, ec_2, ec_3, ec_4) end +function contract_projectors(U, S, V, env::FullInfiniteEnv) + ndir = _next(env.dir, 4) + nndir = _next(ndir, 4) + henv = HalfInfiniteEnv( + env.C_1, env.C_2, + env.E_1, env.E_2, env.E_3, env.E_4, + env.A_1, env.A_2, env.dir, + ) + henv_next = HalfInfiniteEnv( + env.C_3, env.C_4, + env.E_5, env.E_6, env.E_7, env.E_8, + env.A_3, env.A_4, nndir, + ) + return contract_projectors(U, S, V, henv, henv_next) +end + + +# ----------------------------------------------------- # AbstractTensorMap subtyping and IterSVD compatibility +# ----------------------------------------------------- + +function TensorKit.storagetype(::Type{FullInfiniteEnv{TC, TE, TA}}) where {TC, TE, TA} + return TensorKit.promote_storagetype(TC, TE, storagetype(TA)) +end + +function TensorKit.spacetype(::Type{FullInfiniteEnv{TC, TE, TA}}) where {TC, TE, TA} + return spacetype(TC) +end + function TensorKit.domain(env::FullInfiniteEnv) - return domain(env.E_8) * _elementwise_dual(north_virtualspace(env.A_4)) + return domain(env.E_8) * _elementwise_dual(north_virtualspace(env.A_4r)) end function TensorKit.codomain(env::FullInfiniteEnv) - return first(codomain(env.E_1)) * south_virtualspace(env.A_1) + return first(codomain(env.E_1)) * south_virtualspace(env.A_1r) end function random_start_vector(env::FullInfiniteEnv) - return randn(domain(env)) + return randn(storagetype(env), domain(env)) end # ----------------------------- diff --git a/src/algorithms/optimization/fixed_point_differentiation.jl b/src/algorithms/optimization/fixed_point_differentiation.jl index b5c47f979..70fe8fdc9 100644 --- a/src/algorithms/optimization/fixed_point_differentiation.jl +++ b/src/algorithms/optimization/fixed_point_differentiation.jl @@ -90,7 +90,6 @@ Construct the `GeomSum` algorithm struct based on the following keyword argument 2. Information at each gradient iteration * `iterscheme::Symbol=:$(Defaults.gradient_iterscheme)` : Style of CTMRG iteration which is being differentiated, which can be: - `:fixed` : the differentiated CTMRG iteration uses a pre-computed SVD with a fixed set of gauges - - `:diffgauge` : the differentiated iteration consists of a CTMRG iteration and a subsequent gauge-fixing step such that the gauge-fixing procedure is differentiated as well """ struct GeomSum{F} <: GradMode{F} tol::Real @@ -124,7 +123,6 @@ Construct the `ManualIter` algorithm struct based on the following keyword argum 2. Information at each gradient iteration * `iterscheme::Symbol=:$(Defaults.gradient_iterscheme)` : Style of CTMRG iteration which is being differentiated, which can be: - `:fixed` : the differentiated CTMRG iteration uses a pre-computed SVD with a fixed set of gauges - - `:diffgauge` : the differentiated iteration consists of a CTMRG iteration and a subsequent gauge-fixing step such that the gauge-fixing procedure is differentiated as well """ struct ManualIter{F} <: GradMode{F} tol::Real @@ -156,7 +154,6 @@ Construct the `LinSolver` algorithm struct based on the following keyword argume * `verbosity::Int=$(Defaults.gradient_verbosity)` : Output information verbosity of the linear solver. * `iterscheme::Symbol=:$(Defaults.gradient_iterscheme)` : Style of CTMRG iteration which is being differentiated, which can be: - `:fixed` : the differentiated CTMRG iteration uses a pre-computed SVD with a fixed set of gauges - - `:diffgauge` : the differentiated iteration consists of a CTMRG iteration and a subsequent gauge-fixing step such that the gauge-fixing procedure is differentiated as well * `solver_alg::Union{KrylovKit.LinearSolver,NamedTuple}=(; alg::Symbol=:$(Defaults.gradient_linsolver)` : Linear solver algorithm which, if supplied directly as a `KrylovKit.LinearSolver` overrides the above specified `tol`, `maxiter` and `verbosity`. Alternatively, it can be supplied via a `NamedTuple` where `alg` can be one of the following: - `:gmres` : GMRES iterative linear solver, see [`KrylovKit.GMRES`](@extref) for details - `:bicgstab` : BiCGStab iterative linear solver, see [`KrylovKit.BiCGStab`](@extref) for details @@ -189,7 +186,6 @@ Construct the `EigSolver` algorithm struct based on the following keyword argume * `verbosity::Int=$(Defaults.gradient_verbosity)` : Output information verbosity of the linear solver. * `iterscheme::Symbol=:$(Defaults.gradient_iterscheme)` : Style of CTMRG iteration which is being differentiated, which can be: - `:fixed` : the differentiated CTMRG iteration uses a pre-computed SVD with a fixed set of gauges - - `:diffgauge` : the differentiated iteration consists of a CTMRG iteration and a subsequent gauge-fixing step such that the gauge-fixing procedure is differentiated as well * `solver_alg::Union{KrylovKit.KrylovAlgorithm,NamedTuple}=(; alg=:$(Defaults.gradient_eigsolver)` : Eigen solver algorithm which, if supplied directly as a `KrylovKit.KrylovAlgorithm` overrides the above specified `tol`, `maxiter` and `verbosity`. Alternatively, it can be supplied via a `NamedTuple` where `alg` can be one of the following: - `:arnoldi` : Arnoldi Krylov algorithm, see [`KrylovKit.Arnoldi`](@extref) for details """ @@ -220,12 +216,6 @@ function _check_algorithm_combination(::SequentialCTMRG, ::GradMode{:fixed}) gauges; select SimultaneousCTMRG instead to use :fixed mode" throw(ArgumentError(msg)) end -function _check_algorithm_combination(::C4vCTMRG{<:C4vEighProjector}, ::GradMode{:diffgauge}) - msg = "`:diffgauge` mode is currently not compatible with eigh-based C4v CTMRG; \ - either switch to a different projector algorithm (e.g. `c4v_qr`), or use :fixed \ - mode for differentiation instead." - throw(ArgumentError(msg)) -end function _check_algorithm_combination(::C4vCTMRG, symm::Union{Nothing, <:SymmetrizationStyle}) if !(symm isa RotateReflect) msg = "C4vCTMRG optimization is compatible only with RotateReflect symmetrization. \ @@ -250,42 +240,6 @@ function _set_fixed_truncation(alg::CTMRGAlgorithm) return alg_fixed end -function _rrule( - gradmode::GradMode{:diffgauge}, - config::RuleConfig, - ::typeof(leading_boundary), - envinit, - state, - alg::CTMRGAlgorithm, - ) - _check_algorithm_combination(alg, gradmode) - - env, info = leading_boundary(envinit, state, alg) - alg_fixed = _set_fixed_truncation(alg) # fix spaces during differentiation - alg_gauge = _scrambling_env_gauge(alg) # TODO: make this a field in GradMode? - - # prepare iterating function corresponding to a single gauge-fixed CTMRG iteration - function f(A, x) - return gauge_fix(ctmrg_iteration(InfiniteSquareNetwork(A), x, alg_fixed)[1], x, alg_gauge)[1] - end - # compute its pullback - _, env_vjp = rrule_via_ad(config, f, state, env) - # split off state and environment parts - ∂f∂A(x)::typeof(state) = env_vjp(x)[2] - ∂f∂x(x)::typeof(env) = env_vjp(x)[3] - - function leading_boundary_diffgauge_pullback((Δenv′, Δinfo)) - Δenv = unthunk(Δenv′) - - # evaluate the geometric sum - ∂F∂env = fpgrad(Δenv, ∂f∂x, ∂f∂A, Δenv, gradmode) - - return NoTangent(), ZeroTangent(), ∂F∂env, NoTangent() - end - - return (env, info), leading_boundary_diffgauge_pullback -end - # Here f is differentiated from an pre-computed SVD with fixed U, S and V function _rrule( gradmode::GradMode{:fixed}, @@ -301,20 +255,18 @@ function _rrule( alg_fixed = _set_fixed_truncation(alg) # fix spaces during differentiation alg_gauge = _scrambling_env_gauge(alg) # TODO: make this a field in GradMode? env_conv, info = ctmrg_iteration(InfiniteSquareNetwork(state), env, alg_fixed) - _, signs = gauge_fix(env_conv, env, alg_gauge) - - # fix decomposition - alg_fixed = gauge_fix(alg, signs, info) + signs, corner_phases, edge_phases = compute_gauge_fix_gauge(env_conv, env, alg_gauge) # prepare iterating function corresponding to a single CTMRG iteration with a # gauge-fixed projector - function f(A, x) - return fix_global_phases( - ctmrg_iteration(InfiniteSquareNetwork(A), x, alg_fixed)[1], x, + function gauge_fixed_iteration(A, x) + return fix_phases( + ctmrg_iteration(InfiniteSquareNetwork(A), x, alg_fixed)[1], + signs, corner_phases, edge_phases, ) end # prepare its pullback - _, env_vjp = rrule_via_ad(config, f, state, env) + _, env_vjp = rrule_via_ad(config, gauge_fixed_iteration, state, env) # split off state and environment parts ∂f∂A(x)::typeof(state) = env_vjp(x)[2] ∂f∂x(x)::typeof(env) = env_vjp(x)[3] @@ -331,85 +283,6 @@ function _rrule( return (env, info), leading_boundary_fixed_pullback end -function gauge_fix(alg::SVDAdjoint, signs, info) - # embed gauge signs in larger space to fix gauge of full U and V on truncated subspace - rowsize, colsize = size(signs, 2), size(signs, 3) - inds = info.truncation_indices - signs_full = map(Iterators.product(1:4, 1:rowsize, 1:colsize)) do (dir, row, col) - σ = signs[dir, row, col] - row_sign, col_sign = if dir == NORTH # take unit cell interdependency of signs into account - row, _prev(col, colsize) - elseif dir == EAST - _prev(row, rowsize), col - elseif dir == SOUTH - row, _next(col, colsize) - elseif dir == WEST - _next(row, rowsize), col - end - - ind = inds[dir, row_sign, col_sign] - extended_σ = id(scalartype(σ), domain(info.S_full[dir, row_sign, col_sign])) - for (c, b) in blocks(σ) - I = get(ind, c, nothing) - @assert !isnothing(I) - block(extended_σ, c)[I, I] = b - end - return extended_σ - end - - # fix kept and full U and V - U_fixed, V_fixed = fix_relative_phases(info.U, info.V, signs) - U_full_fixed, V_full_fixed = fix_relative_phases(info.U_full, info.V_full, signs_full) - return SVDAdjoint(; - fwd_alg = FixedSVD(U_fixed, info.S, V_fixed, U_full_fixed, info.S_full, V_full_fixed, inds), - rrule_alg = alg.rrule_alg, - ) -end -function gauge_fix(alg::SVDAdjoint{F}, signs, info) where {F <: IterSVD} - # fix kept U and V only since iterative SVD doesn't have access to full spectrum - U_fixed, V_fixed = fix_relative_phases(info.U, info.V, signs) - return SVDAdjoint(; - fwd_alg = FixedSVD(U_fixed, info.S, V_fixed, nothing, nothing, nothing, nothing), - rrule_alg = alg.rrule_alg, - ) -end -function gauge_fix(alg::EighAdjoint, signs, info) - σ = signs[1] - inds = info.truncation_indices - - # embed gauge signs in larger space to fix gauge of full V on truncated subspace - extended_σ = id(scalartype(σ), domain(info.D_full)) - for (c, b) in blocks(σ) - I = get(inds, c, nothing) - @assert !isnothing(I) - block(extended_σ, c)[I, I] = b - end - - # fix kept and full V - V_fixed = info.V * σ' - V_full_fixed = info.V_full * extended_σ' - return EighAdjoint(; - fwd_alg = FixedEig(info.D, V_fixed, info.D_full, V_full_fixed, inds), - rrule_alg = alg.rrule_alg, - ) -end -function gauge_fix(alg::EighAdjoint{F}, signs, info) where {F <: IterEigh} - # fix kept V only since iterative decomposition doesn't have access to full spectrum - V_fixed = info.V * signs[1]' - return EighAdjoint(; - fwd_alg = FixedEig(info.D, V_fixed, nothing, nothing, nothing), - rrule_alg = alg.rrule_alg, - ) -end -function gauge_fix(alg::QRAdjoint, signs, info) - Q_fixed = info.Q * signs[1]' - R_fixed = signs[1] * info.R - return QRAdjoint(; - fwd_alg = FixedQR(Q_fixed, R_fixed), - rrule_alg = alg.rrule_alg, - ) -end - @doc """ fpgrad(∂F∂x, ∂f∂x, ∂f∂A, y0, alg) diff --git a/src/algorithms/optimization/peps_optimization.jl b/src/algorithms/optimization/peps_optimization.jl index db9acb72a..acca99c57 100644 --- a/src/algorithms/optimization/peps_optimization.jl +++ b/src/algorithms/optimization/peps_optimization.jl @@ -136,7 +136,6 @@ keyword arguments are: * `verbosity::Int` : Gradient output verbosity, ≤0 by default to disable too verbose printing. Should only be >0 for debug purposes. * `iterscheme::Symbol=:$(Defaults.gradient_iterscheme)` : CTMRG iteration scheme determining mode of differentiation. This can be: - `:fixed` : the differentiated CTMRG iteration uses a pre-computed SVD with a fixed set of gauges - - `:diffgauge` : the differentiated iteration consists of a CTMRG iteration and a subsequent gauge-fixing step such that the gauge-fixing procedure is differentiated as well ### Optimizer settings @@ -243,9 +242,7 @@ function check_input(::typeof(fixedpoint), peps₀, env₀, alg::PEPSOptimize) e function check_input(::typeof(fixedpoint), peps₀, env₀, alg::PEPSOptimize{<:SimultaneousCTMRG, <:GradMode{:fixed}}) if scalartype(env₀) <: Real # :fixed mode gauge fixing is incompatible with real environments msg = "the provided real environment is incompatible with :fixed mode \ - since :fixed mode generally produces complex gauges; use :diffgauge mode \ - instead by passing gradient_alg=(; iterscheme=:diffgauge) to the fixedpoint \ - keyword arguments to work with purely real environments and asymmetric CTMRG" + since :fixed mode generally produces complex gauges" throw(ArgumentError(msg)) end return nothing diff --git a/src/algorithms/time_evolution/apply_gate.jl b/src/algorithms/time_evolution/apply_gate.jl index 67d4c63cf..63f6e50b7 100644 --- a/src/algorithms/time_evolution/apply_gate.jl +++ b/src/algorithms/time_evolution/apply_gate.jl @@ -42,7 +42,7 @@ function _apply_gate(a::MPSTensor, b::MPSTensor, gate::NNGate, trunc::Truncation else @tensor a2b2[-1 -2; -3 -4] := gate[-2 -3; 1 2] * a[-1 1 3] * b[3 2 -4] end - a, s, b, ϵ = svd_trunc!(a2b2; trunc, alg = LAPACK_QRIteration()) + a, s, b, ϵ = svd_trunc!(a2b2; trunc) a, b = absorb_s(a, s, b) if need_flip a, s, b = flip(a, numind(a)), _fliptwist_s(s), flip(b, 1) diff --git a/src/algorithms/time_evolution/apply_mpo.jl b/src/algorithms/time_evolution/apply_mpo.jl index 60bfc6db7..25fd47974 100644 --- a/src/algorithms/time_evolution/apply_mpo.jl +++ b/src/algorithms/time_evolution/apply_mpo.jl @@ -1,4 +1,4 @@ -#= +#= # Mixed canonical form of an open boundary MPS ``` |ψ⟩ = M[1]-←-...-←-M[N] @@ -54,7 +54,7 @@ Note that Then `M̃[n]` (n = 1, ..., N - 1) satisfies the (generalized) left-orthogonal condition ``` ┌---←--M̃[n]--←- ┌-←- 2 - | | | + | | | s[n-1] ↓ = s[n] (s[0] = 1) | | | └---→--M̃†[n]-→- └-→- 1 @@ -71,16 +71,16 @@ Similarly, we can express M̃ using Qb Then `M̃[n]` (n = 2, ..., N) satisfies the (generalized) right-orthogonal condition ``` -←-M̃[n]-←┐ 1 -←-┐ - ↓ | | + ↓ | | * s[n] = s[n-1] (s[N] = 1) ↓ | | -→M̃†[n]-→┘ 2 -→-┘ ``` -Here `-*-` is the twist on the physical axis. +Here `-*-` is the twist on the physical axis. # Truncation of a bond on OBC-MPS -Suppose we want to truncate the bond between +Suppose we want to truncate the bond between the n-th and the (n+1)-th sites such that the truncated state ``` |ψ̃⟩ = M[1]-←-...-←-M̃[n]-←-M̃[n+1]-←-...-←-M[N] @@ -157,7 +157,7 @@ function lq_through( @assert !isdual(codomain(M, 1)) && !isdual(domain(M, 1)) pM = (codomainind(M), domainind(M)) pL = (codomainind(L1), domainind(L1)) - pML = ((1,), Tuple(2:(N + 1))) + pML = ((1,), ntuple(i -> i + 1, N)) A = tensorcontract(M, pM, false, L1, pL, false, pML) l, _ = right_orth!(A; positive = true) normalize && normalize!(l, Inf) @@ -168,7 +168,7 @@ function lq_through( M::GenericMPSTensor{S, N}, ::Nothing; normalize::Bool = true ) where {S, N} @assert !isdual(codomain(M, 1)) - A = permute(M, ((1,), Tuple(2:(N + 1))); copy = true) + A = permute(M, ((1,), ntuple(i -> i + 1, N)); copy = true) l, _ = right_orth!(A; positive = true) normalize && normalize!(l, Inf) return l @@ -181,8 +181,8 @@ function _get_allRLs(Ms::Vector{T}) where {T <: GenericMPSTensor} # M1 -- (R1,L1) -- M2 -- (R2,L2) -- M3 N = length(Ms) # get the first R and the last L - R_first = qr_through(nothing, Ms[1]; normalize = true) - L_last = lq_through(Ms[N], nothing; normalize = true) + R_first = qr_through(nothing, first(Ms); normalize = true) + L_last = lq_through(last(Ms), nothing; normalize = true) Rs = Vector{typeof(R_first)}(undef, N - 1) Ls = Vector{typeof(L_last)}(undef, N - 1) Rs[1], Ls[end] = R_first, L_last @@ -196,7 +196,7 @@ function _get_allRLs(Ms::Vector{T}) where {T <: GenericMPSTensor} end """ -Given the tensors `R`, `L` on a bond, construct +Given the tensors `R`, `L` on a bond, construct the projectors `Pa`, `Pb` and the new bond weight `s` such that the contraction of `Pa`, `s`, `Pb` is identity when `trunc = notrunc`, @@ -229,7 +229,7 @@ function _get_allprojs( N = length(Ms) Rs, Ls = _get_allRLs(Ms) @assert length(truncs) == N - 1 - projs_errs = map(zip(Rs, Ls, truncs)) do (R, L, trunc) + projs_errs = map(Rs, Ls, truncs) do R, L, trunc return _proj_from_RL(R, L; trunc) end Pas = map(Base.Fix2(getindex, 1), projs_errs) @@ -268,7 +268,7 @@ function _cluster_truncate!( for (i, (Pa, Pb)) in enumerate(zip(Pas, Pbs)) Ms[i] = Ms[i] * twistdual(Pa, 1) pP = ((1,), (2,)) - pM = ((1,), Tuple(2:numind(Ms[i + 1]))) + pM = ((1,), ntuple(i -> i + 1, numind(eltype(Ms)) - 1)) pPM = (codomainind(Ms[i + 1]), domainind(Ms[i + 1])) Ms[i + 1] = tensorcontract(Pb, pP, false, Ms[i + 1], pM, false, pPM) end @@ -294,12 +294,11 @@ function _apply_gatempo!( ) where {T1 <: GenericMPSTensor{<:ElementarySpace, 4}, T2 <: AbstractTensorMap} @assert length(Ms) == length(gs) @assert gate_ax == 1 - @assert all(!isdual(space(g, 1)) for g in gs[2:end]) - @assert all(!isdual(space(M, 1)) for M in Ms[2:end]) # fusers to merge axes on bonds in the gate-cluster product # M1 == f1† -- f1 == M2 == f2† -- f2 == M3 - fusers = map(@view(Ms[2:end]), @view(gs[2:end])) do M, g + fusers = map(Iterators.drop(Ms, 1), Iterators.drop(gs, 1)) do M, g V1, V2 = space(M, 1), space(g, 1) + @assert !isdual(V1) && !isdual(V2) return isomorphism(fuse(V1, V2) ← V1 ⊗ V2) end #= gate on codomain of PEPS @@ -332,12 +331,11 @@ function _apply_gatempo!( ) where {T1 <: GenericMPSTensor{<:ElementarySpace, 5}, T2 <: AbstractTensorMap} @assert length(Ms) == length(gs) @assert gate_ax == 1 || gate_ax == 2 - @assert all(!isdual(space(g, 1)) for g in gs[2:end]) - @assert all(!isdual(space(M, 1)) for M in Ms[2:end]) # fusers to merge axes on bonds in the gate-cluster product # M1 == f1† -- f1 == M2 == f2† -- f2 == M3 - fusers = map(@view(Ms[2:end]), @view(gs[2:end])) do M, g + fusers = map(Iterators.drop(Ms, 1), Iterators.drop(gs, 1)) do M, g V1, V2 = space(M, 1), space(g, 1) + @assert !isdual(V1) && !isdual(V2) return isomorphism(fuse(V1, V2) ← V1 ⊗ V2) end #= gate on codomain of PEPO (gate_ax = 1) @@ -361,7 +359,7 @@ function _apply_gatempo!( -5 -2 -5 -2 -5 -2 =# for (i, (g, M)) in enumerate(zip(gs, Ms)) - @assert !isdual(space(M, 2)) + @assert !isdual(space(M, 2)) && isdual(space(M, 3)) if i == 1 fr = fusers[i] if gate_ax == 1 diff --git a/src/algorithms/time_evolution/get_cluster.jl b/src/algorithms/time_evolution/get_cluster.jl new file mode 100644 index 000000000..1a0de434d --- /dev/null +++ b/src/algorithms/time_evolution/get_cluster.jl @@ -0,0 +1,171 @@ +# code to extract and permute tensors (the cluster) updated by a gate + +""" +Find the permutation to permute `out_ax`, `in_ax` legs to +the first and the last position of a tensor with `N` legs, +then assign the last leg to domain, and the others to codomain +to follow the `GenericMPSTensor` convention. +""" +function _mpo_perm(out_ax::Integer, in_ax::Integer, ::Val{N}) where {N} + perm = TupleTools.deleteat(ntuple(identity, N), (out_ax, in_ax)) + return (out_ax, perm...), (in_ax,) +end + +""" +Given the permutation `perm` that converts a PEPSTensor (N = 1) +or PEPOTensor (N = 2) to MPS axis order, find the inverse permutation. +""" +function _inv_mpo_perm(perm::Tuple{Tuple, Tuple}, ::Val{N}) where {N} + return _inv_mpo_perm((first(perm)..., last(perm)...), Val(N)) +end +function _inv_mpo_perm(perm::Tuple, ::Val{N}) where {N} + p = invperm(perm) + return (p[begin:N], p[(N + 1):end]) +end + +""" +Convert nearest neighbor vector `nn_vec` to direction labels. +``` + NORTH + (-1,0) + ↑ + WEST (0,-1)-←-∘-→-(0,+1) EAST + ↓ + (+1,0) + SOUTH +``` +""" +function _nn_vec_direction(nn_vec::CartesianIndex{2}) + if nn_vec == CartesianIndex(-1, 0) + return NORTH + elseif nn_vec == CartesianIndex(0, 1) + return EAST + elseif nn_vec == CartesianIndex(1, 0) + return SOUTH + elseif nn_vec == CartesianIndex(0, -1) + return WEST + else + error("Input is not a nearest neighbor vector") + end +end + +""" +Given `site1`, `site2` connected by a nearest neighbor bond, +return the bond index and whether it is reversed from the +standard orientation (`site1` on the west/south of `site2`). +""" +function _nn_bondrev(site1::CartesianIndex{2}, site2::CartesianIndex{2}, (Nrow, Ncol)::NTuple{2, Int}) + diff = site1 - site2 + if diff == CartesianIndex(0, -1) + r, c = mod1(site1[1], Nrow), mod1(site1[2], Ncol) + return (1, r, c), false + elseif diff == CartesianIndex(0, 1) + r, c = mod1(site2[1], Nrow), mod1(site2[2], Ncol) + return (1, r, c), true + elseif diff == CartesianIndex(1, 0) + r, c = mod1(site1[1], Nrow), mod1(site1[2], Ncol) + return (2, r, c), false + elseif diff == CartesianIndex(-1, 0) + r, c = mod1(site2[1], Nrow), mod1(site2[2], Ncol) + return (2, r, c), true + else + error("`site1` and `site2` are not nearest neighbors.") + end +end + +function _bond_rotation(x, bonddir::Int, rev::Bool; inv::Bool = false) + return if bonddir == 1 # x-bond + rev ? rot180(x) : x + elseif bonddir == 2 # y-bond + if rev + inv ? rotr90(x) : rotl90(x) + else + inv ? rotl90(x) : rotr90(x) + end + else + error("`bonddir` must be 1 (for x-bonds) or 2 (for y-bonds).") + end +end +function _bond_rotation(x::CartesianIndex{2}, bonddir::Int, rev::Bool, unitcell::NTuple{2, Int}) + return if bonddir == 1 + rev ? siterot180(x, unitcell) : x + else + rev ? siterotl90(x, unitcell) : siterotr90(x, unitcell) + end +end + +""" +Obtain the cluster `Ms` along the (open) path `sites` in `state`. + +## Returns + +- `Ms`: Tensors in the cluster (not permuted to MPS axis order). +- `open_vaxs`: Open virtual axes (1 to 4) of each cluster tensor before permutation. +- `perms`: Permutations to change each tensor to MPS axis order. +""" +function _get_cluster( + state::InfiniteState, sites::Vector{CartesianIndex{2}} + ) + Nr, Nc = size(state) + # number of sites + Ns = length(sites) + # number of physical axes + Np = isa(state, InfinitePEPS) ? 1 : 2 + # number of axes of each state tensor + Nax = Val(4 + Np) + out_axs = map(2:Ns) do i + return _nn_vec_direction(sites[i - 1] - sites[i]) + end + in_axs = map(1:(Ns - 1)) do i + return _nn_vec_direction(sites[i + 1] - sites[i]) + end + open_vaxs = map(1:Ns) do i + return if i == 1 + TupleTools.deleteat((1, 2, 3, 4), in_axs[i]) + elseif i == Ns + TupleTools.deleteat((1, 2, 3, 4), out_axs[i - 1]) + else + TupleTools.deleteat((1, 2, 3, 4), (out_axs[i - 1], in_axs[i])) + end + end + perms = map(1:Ns) do i + out_ax, in_ax = if i == 1 + # use direction opposite to `in` as `out` + mod1(2 + in_axs[i], 4), in_axs[i] + elseif i == Ns + # use direction opposite to `out` as `in` + out_axs[i - 1], mod1(2 + out_axs[i - 1], 4) + else + out_axs[i - 1], in_axs[i] + end + return _mpo_perm(out_ax + Np, in_ax + Np, Nax) + end + Ms = map(sites) do site + return state[CartesianIndex(mod1(site[1], Nr), mod1(site[2], Nc))] + end + return Ms, open_vaxs, perms +end + +""" +Absorb weights into the open virtual legs of +the cluster `Ms` of PEPS/PEPO tensors. +""" +function _absorb_weight!( + Ms::Vector{T}, sites::Vector{CartesianIndex{2}}, + open_vaxs::Vector{<:Tuple}, env::SUWeight; inv::Bool = false + ) where {T} + for (idx, (M, site, vaxs)) in enumerate(zip(Ms, sites, open_vaxs)) + Ms[idx] = absorb_weight(M, env, site, vaxs; inv) + end + return Ms +end + +""" +Given a vector `Ms` of `AbstractTensorMap`s, +apply permutations `perms` to each tensor. +""" +function _permute_cluster(Ms::Vector{<:AbstractTensorMap}, perms::Vector{<:Tuple}) + return map(Ms, perms) do M, perm + return permute(M, perm) + end +end diff --git a/src/algorithms/time_evolution/ntupdate.jl b/src/algorithms/time_evolution/ntupdate.jl index 7c4281762..a154bb7e6 100644 --- a/src/algorithms/time_evolution/ntupdate.jl +++ b/src/algorithms/time_evolution/ntupdate.jl @@ -129,7 +129,7 @@ end function Base.iterate(it::TimeEvolver{<:NeighbourUpdate}, state = it.state) iter, t = state.iter, state.t (iter == it.nstep) && return nothing - psi, wts, info = ntu_iter(state.psi, it.gate, it.alg) + psi, wts, info = ntu_iter(state.psi, it.circuit, it.alg) iter, t = iter + 1, t + it.dt # update internal state it.state = NTUState(iter, t, psi) diff --git a/src/algorithms/time_evolution/ntupdate3site.jl b/src/algorithms/time_evolution/ntupdate3site.jl index fa50cdb21..885c61136 100644 --- a/src/algorithms/time_evolution/ntupdate3site.jl +++ b/src/algorithms/time_evolution/ntupdate3site.jl @@ -1,3 +1,12 @@ +function _get_cluster_permute( + state::InfiniteState, sites::Vector{CartesianIndex{2}} +) + Ms, _, perms = _get_cluster(state, sites) + Np = (state isa InfinitePEPS) ? Val(1) : Val(2) + invperms = map(p -> _inv_mpo_perm(p, Np), perms) + return _permute_cluster(Ms, perms), invperms +end + """ Neighbourhood tensor update with N-site MPO `gate` (N ≥ 2). """ @@ -9,8 +18,8 @@ function _ntu_iter( truncs = _get_cluster_trunc(alg.opt_alg.trunc, sites, (Nr, Nc)) state, wts = copy(state), deepcopy(wts) - Ms, _, invperms = _get_cluster(state, sites) - flips = [isdual(space(M, 1)) for M in Ms[2:end]] + Ms, invperms = _get_cluster_permute(state, sites) + flips = [isdual(space(M, 1)) for M in Iterators.drop(Ms, 1)] _flip_virtuals!(Ms, flips) # flip virtual arrows in `Ms` to ← # apply gate MPO without truncation diff --git a/src/algorithms/time_evolution/simpleupdate.jl b/src/algorithms/time_evolution/simpleupdate.jl index 5c3d30a19..9fab38443 100644 --- a/src/algorithms/time_evolution/simpleupdate.jl +++ b/src/algorithms/time_evolution/simpleupdate.jl @@ -45,7 +45,7 @@ end symmetrize_gates::Bool = false ) -Initialize a `TimeEvolver` with Hamiltonian `H` and simple update `alg`, +Initialize a `TimeEvolver` with Hamiltonian `H` and simple update `alg`, starting from the initial state `psi0` and `SUWeight` environment `env0`. - The initial time is specified by `t0`. @@ -60,7 +60,7 @@ function TimeEvolver( _timeevol_sanity_check(psi0, physicalspace(H), alg) dt′ = _get_dt(psi0, dt, alg.imaginary_time) # create Trotter gates - gate = trotterize(H, dt′; symmetrize_gates, force_mpo = alg.force_mpo) + circuit = trotterize(H, dt′; symmetrize_gates, force_mpo = alg.force_mpo) state = SUState(0, t0, psi0, env0) # convert FixedSpaceTruncation to site-dependent `truncspace`s if alg.trunc isa FixedSpaceTruncation @@ -69,29 +69,9 @@ function TimeEvolver( end # TODO: bipartite check for alg.trunc after equality is defined for all kinds of truncation strategies # TODO: check gates for bipartite case - return TimeEvolver(alg, dt, nstep, gate, state) + return TimeEvolver(alg, dt, nstep, circuit, state) end -function _bond_rotation(x, bonddir::Int, rev::Bool; inv::Bool = false) - return if bonddir == 1 # x-bond - rev ? rot180(x) : x - elseif bonddir == 2 # y-bond - if rev - inv ? rotr90(x) : rotl90(x) - else - inv ? rotl90(x) : rotr90(x) - end - else - error("`bonddir` must be 1 (for x-bonds) or 2 (for y-bonds).") - end -end -function _bond_rotation(x::CartesianIndex{2}, bonddir::Int, rev::Bool, unitcell::NTuple{2, Int}) - return if bonddir == 1 - rev ? siterot180(x, unitcell) : x - else - rev ? siterotl90(x, unitcell) : siterotr90(x, unitcell) - end -end """ Simple update optimized for nearest neighbor gates @@ -104,25 +84,27 @@ function _su_iter!( Nr, Nc = size(state) @assert length(sites) == 2 trunc = only(_get_cluster_trunc(alg.trunc, sites, (Nr, Nc))) - Ms, open_vaxs, = _get_cluster(state, sites, env; permute = false) - normalize!.(Ms, Inf) + Ms, open_vaxs, = _get_cluster(state, sites) + _absorb_weight!(Ms, sites, open_vaxs, env) # rotate bond, rev = _nn_bondrev(sites..., (Nr, Nc)) - A, B = _bond_rotation.(Ms, bond[1], rev; inv = false) + dir = first(bond) + A, B = _bond_rotation.(Ms, dir, rev; inv = false) # apply gate - ϵ, s = 0.0, nothing - gate_axs = alg.purified ? (1:1) : (1:2) - for gate_ax in gate_axs + ϵ = 0.0 + local s + for gate_ax in 1:2 a, X = bond_tensor_first(A; gate_ax, positive = true) b, Y = bond_tensor_last(B; gate_ax, positive = true) a, s, b, ϵ′ = _apply_gate(a, b, gate, trunc) ϵ = max(ϵ, ϵ′) A = undo_bond_tensor_first(a, X; gate_ax) B = undo_bond_tensor_last(b, Y; gate_ax) + alg.purified && break # only apply gate to 1st physical leg end # rotate back - A = _bond_rotation(A, bond[1], rev; inv = true) - B = _bond_rotation(B, bond[1], rev; inv = true) + A = _bond_rotation(A, dir, rev; inv = true) + B = _bond_rotation(B, dir, rev; inv = true) rev && (s = transpose(s)) # remove environment weights siteA, siteB = map(sites) do site @@ -131,11 +113,9 @@ function _su_iter!( A = absorb_weight(A, env, siteA[1], siteA[2], open_vaxs[1]; inv = true) B = absorb_weight(B, env, siteB[1], siteB[2], open_vaxs[2]; inv = true) # update tensor dict and weight on current bond - normalize!(A, Inf) - normalize!(B, Inf) - normalize!(s, Inf) - state[siteA], state[siteB] = A, B - env[bond...] = s + state[siteA] = normalize!(A, Inf) + state[siteB] = normalize!(B, Inf) + env[bond...] = normalize!(s, Inf) return ϵ end @@ -185,7 +165,7 @@ end function Base.iterate(it::TimeEvolver{<:SimpleUpdate}, state = it.state) iter, t = state.iter, state.t (iter == it.nstep) && return nothing - psi, env, ϵ = su_iter(state.psi, it.gate, it.alg, state.env) + psi, env, ϵ = su_iter(state.psi, it.circuit, it.alg, state.env) # update internal state iter += 1 t += it.dt @@ -217,115 +197,127 @@ function MPSKit.timestep( end """ - time_evolve(it; check_interval = 500) -> (psi, env, info) - time_evolve(it, H; tol = 1.0e-8, check_interval = 500) -> (psi, env, info) + time_evolve(it; verbosity = 2, check_interval = 500) -> (psi, env, info) + time_evolve(it, H; tol = 1.0e-8, verbosity = 2, check_interval = 500) -> (psi, env, info) Perform time evolution to the end of `TimeEvolver` iterator `it`, or until convergence of `SUWeight` set by a positive `tol`. - Setting `tol > 0` enables convergence check (for imaginary time evolution of InfinitePEPS only). For other usages it should not be changed. -- `check_interval` sets the number of iterations between outputs of information. +- `verbosity` sets the verbosity level to output information. + - 0: output no information except warnings. + - 1: indicate the start and the end of the evolution. + - 2: (default) output detailed progress of the evolution. +- `check_interval` sets the number of iterations to output evolution progress. """ function MPSKit.time_evolve( - it::TimeEvolver{<:SimpleUpdate}; check_interval::Int = 500 + it::TimeEvolver{<:SimpleUpdate}; + verbosity::Int = 2, check_interval::Int = 500 ) - time_start = time() - @info "--- Time evolution (simple update), dt = $(it.dt) ---" - env0, time0 = it.state.env, time() - for (psi, env, info) in it - iter = it.state.iter - diff = compare_weights(env0, env) - stop = (iter == it.nstep) - showinfo = (check_interval > 0) && - ((iter % check_interval == 0) || (iter == 1) || stop) - time1 = time() - if showinfo - @info "Space of x-weight at [1, 1] = $(space(env[1, 1, 1], 1))" - @info @sprintf("SU iter %-7d: |Δλ| = %.3e. Time = %.3f s/it", iter, diff, time1 - time0) - end - if stop - time_end = time() - @info @sprintf("Time evolution finished in %.2f s", time_end - time_start) - return psi, env, info - else - env0 = env + @assert check_interval >= 0 + return LoggingExtras.withlevel(; verbosity) do + time_start = time() + @infov 1 "--- Time evolution (simple update), dt = $(it.dt) ---" + env0, time0 = it.state.env, time() + for (psi, env, info) in it + iter = it.state.iter + diff = compare_weights(env0, env) + stop = (iter == it.nstep) + showinfo = (check_interval > 0) && + ((iter % check_interval == 0) || (iter == 1) || stop) + time1 = time() + if showinfo + @infov 2 "Space of x-weight at [1, 1] = $(space(env[1, 1, 1], 1))" + @infov 2 @sprintf("SU iter %-7d: |Δλ| = %.3e. Time = %.3f s/it", iter, diff, time1 - time0) + end + if stop + time_end = time() + @infov 1 @sprintf("Time evolution finished in %.2f s", time_end - time_start) + return psi, env, info + else + env0 = env + end + time0 = time() end - time0 = time() end - return end function MPSKit.time_evolve( it::TimeEvolver{<:SimpleUpdate, G, S}, H::LocalOperator; - tol::Float64 = 1.0e-8, check_interval::Int = 500 + tol::Float64 = 1.0e-8, verbosity::Int = 2, check_interval::Int = 500 ) where {G, S <: SUState{<:InfinitePEPS}} - time_start = time() - @info "--- Time evolution (simple update), dt = $(it.dt) ---" - @assert it.alg.imaginary_time "Only imaginary time evolution of InfinitePEPS allows convergence checking." - env0, time0 = it.state.env, time() - for (psi, env, info) in it - iter = it.state.iter - diff = compare_weights(env0, env) - stop = (iter == it.nstep) || (diff < tol) - showinfo = (check_interval > 0) && - ((iter % check_interval == 0) || (iter == 1) || stop) - time1 = time() - if showinfo - # TODO: convert to BPEnv instead - ctmenv = CTMRGEnv(env) - energy = real(expectation_value(psi, H, ctmenv)) / prod(size(psi)) - @info "Space of x-weight at [1, 1] = $(space(env[1, 1, 1], 1))" - @info @sprintf( - "SU iter %-7d: E ≈ %.5f, |Δλ| = %.3e. Time = %.3f s/it", - iter, energy, diff, time1 - time0 - ) - end - if (iter == it.nstep) && (diff >= tol) - @warn "SU: bond weights have not converged." - end - if diff < tol - @info "SU: bond weights have converged." - end - if stop - time_end = time() - @info @sprintf("Time evolution finished in %.2f s", time_end - time_start) - return psi, env, info - else - env0 = env + @assert check_interval >= 0 + return LoggingExtras.withlevel(; verbosity) do + time_start = time() + @infov 1 "--- Time evolution (simple update), dt = $(it.dt) ---" + @assert it.alg.imaginary_time "Only imaginary time evolution of InfinitePEPS allows convergence checking." + env0, time0 = it.state.env, time() + for (psi, env, info) in it + iter = it.state.iter + diff = compare_weights(env0, env) + stop = (iter == it.nstep) || (diff < tol) + showinfo = (check_interval > 0) && + ((iter % check_interval == 0) || (iter == 1) || stop) + time1 = time() + if showinfo + # TODO: convert to BPEnv instead + ctmenv = CTMRGEnv(env) + energy = real(expectation_value(psi, H, ctmenv)) / prod(size(psi)) + @infov 2 "Space of x-weight at [1, 1] = $(space(env[1, 1, 1], 1))" + @infov 2 @sprintf( + "SU iter %-7d: E ≈ %.5f, |Δλ| = %.3e. Time = %.3f s/it", + iter, energy, diff, time1 - time0 + ) + end + if (iter == it.nstep) && (diff >= tol) + @warn "SU: bond weights have not converged." + end + if diff < tol + @infov 2 "SU: bond weights have converged." + end + if stop + time_end = time() + @infov 1 @sprintf("Time evolution finished in %.2f s", time_end - time_start) + return psi, env, info + else + env0 = env + end + time0 = time() end - time0 = time() end - return end """ time_evolve( - psi0::Union{InfinitePEPS, InfinitePEPO}, H::LocalOperator, dt::Number, nstep::Int, - alg::SimpleUpdate, env0::SUWeight; symmetrize_gates::Bool = false, - tol::Float64 = 0.0, t0::Number = 0.0, check_interval::Int = 500 + psi0::Union{InfinitePEPS, InfinitePEPO}, H::LocalOperator, + dt::Number, nstep::Int, alg::SimpleUpdate, env0::SUWeight; + symmetrize_gates::Bool = false, tol::Float64 = 0.0, + t0::Number = 0.0, verbosity::Int = 2, check_interval::Int = 500, ) -> (psi, env, info) Perform time evolution on the initial iPEPS or iPEPO `psi0` and initial environment `env0` with Hamiltonian `H`, using `SimpleUpdate` -algorithm `alg`, time step `dt` for `nstep` number of steps. +algorithm `alg`, time step `dt` for `nstep` number of steps. - Set `symmetrize_gates = true` for second-order Trotter decomposition. - Set `tol > 0` to enable convergence check (for imaginary time evolution of iPEPS only). - Use `t0` to specify the initial time of the evolution. -- `check_interval` sets the interval to output information. Output during the evolution can be turned off by setting `check_interval <= 0`. -- `info` is a NamedTuple containing information of the evolution, +- `verbosity` sets the verbosity level to output information. +- `check_interval` sets the interval to output evolution progress. +- `info` is a NamedTuple containing information of the evolution, including the time `info.t` evolved since `psi0`. """ function MPSKit.time_evolve( psi0::InfiniteState, H::LocalOperator, dt::Number, nstep::Int, alg::SimpleUpdate, env0::SUWeight; symmetrize_gates::Bool = false, - tol::Float64 = 0.0, t0::Number = 0.0, check_interval::Int = 500 + tol::Float64 = 0.0, t0::Number = 0.0, + verbosity::Int = 2, check_interval::Int = 500, ) it = TimeEvolver(psi0, H, dt, nstep, alg, env0; t0, symmetrize_gates) return if tol == 0 - time_evolve(it; check_interval) + time_evolve(it; verbosity, check_interval) else - time_evolve(it, H; tol, check_interval) + time_evolve(it, H; tol, verbosity, check_interval) end end diff --git a/src/algorithms/time_evolution/simpleupdate3site.jl b/src/algorithms/time_evolution/simpleupdate3site.jl index 2fc9d096f..3fe31c98f 100644 --- a/src/algorithms/time_evolution/simpleupdate3site.jl +++ b/src/algorithms/time_evolution/simpleupdate3site.jl @@ -13,150 +13,14 @@ function _unfuse_physicalspace( return O_unfused, F end -""" -Convert nearest neighbor vector `nn_vec` to direction labels. -``` - NORTH - (-1,0) - ↑ - WEST (0,-1)-←-∘-→-(0,+1) EAST - ↓ - (+1,0) - SOUTH -``` -""" -function _nn_vec_direction(nn_vec::CartesianIndex{2}) - if nn_vec == CartesianIndex(-1, 0) - return NORTH - elseif nn_vec == CartesianIndex(0, 1) - return EAST - elseif nn_vec == CartesianIndex(1, 0) - return SOUTH - elseif nn_vec == CartesianIndex(0, -1) - return WEST - else - error("Input is not a nearest neighbor vector") - end -end - -""" -Given `site1`, `site2` connected by a nearest neighbor bond, -return the bond index and whether it is reversed from the -standard orientation (`site1` on the west/south of `site2`). -""" -function _nn_bondrev(site1::CartesianIndex{2}, site2::CartesianIndex{2}, (Nrow, Ncol)::NTuple{2, Int}) - diff = site1 - site2 - if diff == CartesianIndex(0, -1) - r, c = mod1(site1[1], Nrow), mod1(site1[2], Ncol) - return (1, r, c), false - elseif diff == CartesianIndex(0, 1) - r, c = mod1(site2[1], Nrow), mod1(site2[2], Ncol) - return (1, r, c), true - elseif diff == CartesianIndex(1, 0) - r, c = mod1(site1[1], Nrow), mod1(site1[2], Ncol) - return (2, r, c), false - elseif diff == CartesianIndex(-1, 0) - r, c = mod1(site2[1], Nrow), mod1(site2[2], Ncol) - return (2, r, c), true - else - error("`site1` and `site2` are not nearest neighbors.") - end -end - -""" -Find the permutation to permute `out_ax`, `in_ax` legs to -the first and the last position of a tensor with `Nax` legs, -then assign the last leg to domain, and the others to codomain. -""" -function _get_mpo_perm(out_ax::Int, in_ax::Int, Nax::Int) - perm = collect(1:Nax) - filter!(x -> x != out_ax && x != in_ax, perm) - pushfirst!(perm, out_ax) - push!(perm, in_ax) - return (Tuple(perm[1:(end - 1)]), (perm[end],)) -end - -""" -Obtain the cluster `Ms` along the (open) path `sites` in `state`. - -When the `SUWeight` environment `env` is provided, -it will be absorbed into tensors of `Ms`. - -When `permute = true`, permute tensors in `Ms` to MPS axis order -``` - PEPS: PEPO: - 3 3 4 - ╱ | ╱ - o -- M -- i o -- M -- i - ╱ | ╱ | - 4 2 5 2 - M[o 2 3 4; i] M[o 2 3 4 5; i] -``` -where `o` (`i`) connects to the previous (next) tensor. -Otherwise, axes order of each tensor in `Ms` are preserved. - -## Returns - -- `Ms`: Tensors in the cluster. -- `open_vaxs`: Open virtual axes (1 to 4) of each cluster tensor before permutation. -- `invperms`: Permutations to restore the axes order of each cluster tensor. -""" -function _get_cluster(state, sites; permute::Bool = true) - return _get_cluster(state, sites, nothing; permute) -end -function _get_cluster( - state::InfiniteState, sites::Vector{CartesianIndex{2}}, - env::Union{SUWeight, Nothing}; permute::Bool = true +function _get_cluster_with_weights( + state::InfiniteState, sites::Vector{CartesianIndex{2}}, env::SUWeight ) - Nr, Nc = size(state) - # number of sites - Ns = length(sites) - # number of physical axes - Np = isa(state, InfinitePEPS) ? 1 : 2 - # number of axes of each state tensor - Nax = 4 + Np - out_axs = map(2:Ns) do i - return _nn_vec_direction(sites[i - 1] - sites[i]) - end - in_axs = map(1:(Ns - 1)) do i - return _nn_vec_direction(sites[i + 1] - sites[i]) - end - all_vaxs = Tuple(1:4) - open_vaxs = map(1:Ns) do i - return if i == 1 - filter(x -> x != in_axs[i], all_vaxs) - elseif i == Ns - filter(x -> x != out_axs[i - 1], all_vaxs) - else - filter(x -> x != out_axs[i - 1] && x != in_axs[i], all_vaxs) - end - end - perms = map(1:Ns) do i - out_ax, in_ax = if i == 1 - # use direction opposite to `in` as `out` - mod1(2 + in_axs[i], 4), in_axs[i] - elseif i == Ns - # use direction opposite to `out` as `in` - out_axs[i - 1], mod1(2 + out_axs[i - 1], 4) - else - out_axs[i - 1], in_axs[i] - end - return _get_mpo_perm(out_ax + Np, in_ax + Np, Nax) - end - invperms = map(perms) do (p1, p2) - p = invperm((p1..., p2...)) - return (p[1:Np], p[(Np + 1):end]) - end - Ms = map(zip(sites, open_vaxs, perms)) do (site, vaxs, perm) - s = CartesianIndex(mod1(site[1], Nr), mod1(site[2], Nc)) - M = if env === nothing - state[s] - else - absorb_weight(state[s], env, s[1], s[2], vaxs) - end - return permute ? TensorKit.permute(M, perm) : M - end - return Ms, open_vaxs, invperms + Ms, open_vaxs, perms = _get_cluster(state, sites) + _absorb_weight!(Ms, sites, open_vaxs, env; inv = false) + Np = (state isa InfinitePEPS) ? Val(1) : Val(2) + invperms = map(p -> _inv_mpo_perm(p, Np), perms) + return _permute_cluster(Ms, perms), open_vaxs, invperms end """ @@ -168,47 +32,38 @@ function _su_iter!( ) where {T <: AbstractTensorMap} Nr, Nc = size(state) truncs = _get_cluster_trunc(alg.trunc, sites, (Nr, Nc)) - Ms, open_vaxs, invperms = _get_cluster(state, sites, env) - flips = [isdual(space(M, 1)) for M in Ms[2:end]] - Vphys = [codomain(M, 2) for M in Ms] - normalize!.(Ms, Inf) + Ms, open_vaxs, invperms = _get_cluster_with_weights(state, sites, env) # flip virtual arrows in `Ms` to ← + flips = [isdual(space(M, 1)) for M in Iterators.drop(Ms, 1)] _flip_virtuals!(Ms, flips) # apply gate MPOs and truncate - gate_axs = alg.purified ? (1:1) : (1:2) - wts, ϵs = nothing, nothing - for gate_ax in gate_axs + ϵ = 0.0 + local wts + for gate_ax in 1:2 _apply_gatempo!(Ms, gate; gate_ax) - if isa(state, InfinitePEPO) - Ms = [first(_fuse_physicalspaces(M)) for M in Ms] - end wts, ϵs, = _cluster_truncate!(Ms, truncs) - if isa(state, InfinitePEPO) - Ms = [first(_unfuse_physicalspace(M, Vphy)) for (M, Vphy) in zip(Ms, Vphys)] - end + ϵ = max(ϵ, maximum(ϵs)) + alg.purified && break end # restore virtual arrows in `Ms` _flip_virtuals!(Ms, flips) # update env weights - bond_revs = map(zip(sites, Iterators.drop(sites, 1))) do (site1, site2) + bond_revs = map(sites, Iterators.drop(sites, 1)) do site1, site2 _nn_bondrev(site1, site2, (Nr, Nc)) end for (wt, (bond, rev), flip) in zip(wts, bond_revs, flips) wt_new = flip ? _fliptwist_s(wt) : wt wt_new = rev ? transpose(wt_new) : wt_new - @assert all(wt_new.data .>= 0) - env[CartesianIndex(bond)] = normalize(wt_new, Inf) + env[CartesianIndex(bond)] = normalize!(wt_new, Inf) end + # update state tensors for (M, s, invperm, vaxs) in zip(Ms, sites, invperms, open_vaxs) s′ = CartesianIndex(mod1(s[1], Nr), mod1(s[2], Nc)) - # restore original axes order - M = permute(M, invperm) - # remove weights on open axes of the cluster - M = absorb_weight(M, env, s′[1], s′[2], vaxs; inv = true) - # update state tensors - state[s′] = normalize(M, Inf) + # restore original axes order and remove weights on open axes of the cluster + M′ = absorb_weight(permute(M, invperm), env, s′, vaxs; inv = true) + state[s′] = normalize!(M′, Inf) end - return maximum(ϵs) + return ϵ end """ @@ -219,7 +74,7 @@ function _get_cluster_trunc( trunc::TruncationStrategy, sites::Vector{CartesianIndex{2}}, unitcell::NTuple{2, Int} ) - return map(zip(sites, Iterators.drop(sites, 1))) do (site1, site2) + return map(sites, Iterators.drop(sites, 1)) do site1, site2 (d, r, c), rev = _nn_bondrev(site1, site2, unitcell) t = truncation_strategy(trunc, d, r, c) if rev && isa(t, TruncationSpace) diff --git a/src/algorithms/time_evolution/time_evolve.jl b/src/algorithms/time_evolution/time_evolve.jl index dd51c2f54..c1dc32233 100644 --- a/src/algorithms/time_evolution/time_evolve.jl +++ b/src/algorithms/time_evolution/time_evolve.jl @@ -6,7 +6,7 @@ Abstract super type for time evolution algorithms of InfinitePEPS or InfinitePEP abstract type TimeEvolution end """ - mutable struct TimeEvolver{TE <: TimeEvolution, G, S, N <: Number} + mutable struct TimeEvolver{TE <: TimeEvolution, C, S, N <: Number} Iterator for Trotter-based time evolution of InfinitePEPS or InfinitePEPO. @@ -14,15 +14,15 @@ Iterator for Trotter-based time evolution of InfinitePEPS or InfinitePEPO. $(TYPEDFIELDS) """ -mutable struct TimeEvolver{TE <: TimeEvolution, G, S, N <: Number} +mutable struct TimeEvolver{TE <: TimeEvolution, C, S, N <: Number} "Time evolution algorithm (currently supported: `SimpleUpdate`)" alg::TE "Trotter time step" dt::N "The number of iteration steps" nstep::Int - "Trotter gates" - gate::G + "LocalCircuit representing trotterized gates" + circuit::C "Internal state of the iterator, including the number of already performed iterations, evolved time, PEPS/PEPO and its environment" state::S @@ -85,7 +85,7 @@ that preserves virtual spaces of `state`. """ function _get_fixedspacetrunc(state::InfiniteState) if state isa InfinitePEPO - size(state, 3) != 1 && error("Input InfinitePEPO is expect to have only one layer.") + size(state, 3) != 1 && error("Input InfinitePEPO is expected to have only one layer.") end Nr, Nc = size(state) return SiteDependentTruncation( diff --git a/src/algorithms/toolbox.jl b/src/algorithms/toolbox.jl index 647ad1221..d9b7802a3 100644 --- a/src/algorithms/toolbox.jl +++ b/src/algorithms/toolbox.jl @@ -357,7 +357,7 @@ function product_peps(peps_args...; unitcell = (1, 1), noise_amp = 1.0e-2, state 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) + prod_tensors = map(noise_peps.A, state_vector) do t, v pt = zero(t) pt[][:, 1, 1, 1, 1] .= v return pt diff --git a/src/algorithms/truncation/bond_truncation.jl b/src/algorithms/truncation/bond_truncation.jl index 1a95b8d34..62c4a3283 100644 --- a/src/algorithms/truncation/bond_truncation.jl +++ b/src/algorithms/truncation/bond_truncation.jl @@ -82,7 +82,7 @@ function bond_truncate(a::MPSTensor, b::MPSTensor, benv::BondEnv, alg::ALSTrunca # untruncated things ket2 = _combine_ket(a, b) benv_ket2 = _benv_ket(benv, ket2) - b22 = _als_norm(ket2, benv_ket2) + b22 = real(_als_norm(ket2, benv_ket2)) # initialize truncated bond tensors and bond weight xs, s0 = _als_init_truncate(ket2, alg.trunc) @@ -158,9 +158,9 @@ function bond_truncate(a::MPSTensor, b::MPSTensor, benv::BondEnv, alg::FullEnvTr ↓ ↓ ↓ ↓ =# Qa, Ra = left_orth(a; positive = true) - b = permute(b, ((3, 2), (1,)); copy = true) - Qb, Rb = left_orth!(b; positive = true) - @tensor b0[-1; -2] := Ra[-1 1] * Rb[-2 1] + b = permute(b, ((1,), (2, 3)); copy = true) + Rb, Qb = right_orth!(b; positive = true) + @tensor b0[-1; -2] := Ra[-1 1] * Rb[1 -2] #= initialize bond environment around `Ra Rb` ┌--------------------------------------┐ @@ -174,13 +174,13 @@ function bond_truncate(a::MPSTensor, b::MPSTensor, benv::BondEnv, alg::FullEnvTr └--------------------------------------┘ =# @tensor benv2[-1 -2; -3 -4] := benv[1 2; 3 4] * - conj(Qa[1 5 -1]) * conj(Qb[2 6 -2]) * Qa[3 5 -3] * Qb[4 6 -4] + conj(Qa[1 5 -1]) * conj(Qb[-2 6 2]) * Qa[3 5 -3] * Qb[-4 6 4] # optimize bond matrix u, s, vh, info = fullenv_truncate(b0, benv2, alg) u, vh = absorb_s(u, s, vh) # truncate a, b tensors with u, s, vh @tensor a[-1 -2; -3] := Qa[-1 -2 3] * u[3 -3] - @tensor b[-1 -2; -3] := vh[-1 1] * Qb[-3 -2 1] + @tensor b[-1 -2; -3] := vh[-1 1] * Qb[1 -2 -3] if need_flip a, s, b = flip(a, numind(a)), _fliptwist_s(s), flip(b, 1) end diff --git a/src/algorithms/truncation/truncationschemes.jl b/src/algorithms/truncation/truncationschemes.jl index 228f9065d..7cad1dfd6 100644 --- a/src/algorithms/truncation/truncationschemes.jl +++ b/src/algorithms/truncation/truncationschemes.jl @@ -67,3 +67,66 @@ function truncation_strategy( ) return trunc[direction, row, col] end + +# rotation of SiteDependentTruncation +# (similar to rotation of SUWeight) +function _rotl90_trunc_x(trunc_x::AbstractMatrix{<:TruncationStrategy}) + trunc_y = rotl90(trunc_x) + return trunc_y +end +function _rotr90_trunc_x(trunc_x::AbstractMatrix{<:TruncationStrategy}) + trunc_y = circshift(rotr90(trunc_x), (1, 0)) + for (i, t) in enumerate(trunc_y) + if t isa TruncationSpace + trunc_y[i] = truncspace(flip(t.space)') + end + end + return trunc_y +end +function _rot180_trunc_x(trunc_x::AbstractMatrix{<:TruncationStrategy}) + trunc_x_ = circshift(rot180(trunc_x), (0, -1)) + for (i, t) in enumerate(trunc_x_) + trunc_x_[i] = truncspace(flip(t.space)') + end + return trunc_x_ +end + +function _rotl90_trunc_y(trunc_y::AbstractMatrix{<:TruncationStrategy}) + trunc_x = circshift(rotl90(trunc_y), (0, -1)) + for (i, t) in enumerate(trunc_x) + if t isa TruncationSpace + trunc_x[i] = truncspace(flip(t.space)') + end + end + return trunc_x +end +function _rotr90_trunc_y(trunc_y::AbstractMatrix{<:TruncationStrategy}) + trunc_x = rotr90(trunc_y) + return trunc_x +end +function _rot180_trunc_y(trunc_y::AbstractMatrix{<:TruncationStrategy}) + trunc_y_ = circshift(rot180(trunc_y), (1, 0)) + for (i, t) in enumerate(trunc_y_) + trunc_y_[i] = truncspace(flip(t.space)') + end + return trunc_y_ +end + +function Base.rotl90(trunc::SiteDependentTruncation) + trunc_y = _rotl90_trunc_x(trunc[1, :, :]) + trunc_x = _rotl90_trunc_y(trunc[2, :, :]) + trunc = stack((trunc_x, trunc_y); dims = 1) + return SiteDependentTruncation(trunc) +end +function Base.rotr90(trunc::SiteDependentTruncation) + trunc_y = _rotr90_trunc_x(trunc[1, :, :]) + trunc_x = _rotr90_trunc_y(trunc[2, :, :]) + trunc = stack((trunc_x, trunc_y); dims = 1) + return SiteDependentTruncation(trunc) +end +function Base.rot180(trunc::SiteDependentTruncation) + trunc_x = _rot180_trunc_x(trunc[1, :, :]) + trunc_y = _rot180_trunc_y(trunc[2, :, :]) + trunc = stack((trunc_x, trunc_y); dims = 1) + return SiteDependentTruncation(trunc) +end diff --git a/src/environments/suweight.jl b/src/environments/suweight.jl index cd24cb111..e052d08d3 100644 --- a/src/environments/suweight.jl +++ b/src/environments/suweight.jl @@ -77,7 +77,7 @@ end """ SUWeight(Nspace::S, Espace::S=Nspace; unitcell::Tuple{Int,Int}=(1, 1)) where {S<:ElementarySpace} -Create a trivial `SUWeight` by specifying its vertical (north) and horizontal (east) +Create a trivial `SUWeight` by specifying its vertical (north) and horizontal (east) as `ElementarySpace`s) and unit cell size. """ function SUWeight( @@ -175,84 +175,6 @@ function Base.show(io::IO, ::MIME"text/plain", wts::SUWeight) return nothing end -""" - absorb_weight(t::Union{PEPSTensor, PEPOTensor}, weights::SUWeight, row::Int, col::Int, ax::Int; inv::Bool = false) - absorb_weight(t::Union{PEPSTensor, PEPOTensor}, weights::SUWeight, row::Int, col::Int, ax::NTuple{N, Int}; inv::Bool = false) - -Absorb or remove (in a twist-free way) the square root of environment weight -on an axis of the PEPS/PEPO tensor `t` known to be at position (`row`, `col`) -in the unit cell of an InfinitePEPS/InfinitePEPO. The involved weights are -``` - | - [2,r,c] - | - - [1,r,c-1] - T[r,c] - [1,r,c] - - | - [1,r+1,c] - | -``` - -## Arguments - -- `t::Union{PEPSTensor, PEPOTensor}` : PEPSTensor or PEPOTensor to which the weight will be absorbed. -- `weights::SUWeight` : All simple update weights. -- `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 into which the weight is absorbed, taking values from 1 to 4, standing for north, east, south, west respectively. - -## Keyword arguments - -- `inv::Bool=false` : If `true`, the inverse square root of the weight is absorbed. - -## Examples - -```julia -# Absorb the weight into the north axis of tensor at position (2, 3) -absorb_weight(t, weights, 2, 3, 1) - -# Absorb the inverse of (i.e. remove) the weight into the east axis -absorb_weight(t, weights, 2, 3, 2; inv=true) -``` -""" -function absorb_weight( - t::Union{PEPSTensor, PEPOTensor}, weights::SUWeight, - row::Int, col::Int, ax::Int; inv::Bool = false - ) - Nr, Nc = size(weights)[2:end] - nin, nout, ntol = numin(t), numout(t), numind(t) - @assert 1 <= row <= Nr && 1 <= col <= Nc - @assert 1 <= ax <= nin - pow = inv ? -1 / 2 : 1 / 2 - wt = sdiag_pow( - if ax == NORTH - weights[2, row, col] - elseif ax == EAST - weights[1, row, col] - elseif ax == SOUTH - weights[2, _next(row, Nr), col] - else # WEST - weights[1, row, _prev(col, Nc)] - end, - pow, - ) - t_idx = [(n - nout == ax) ? 1 : -n for n in 1:ntol] - ax′ = ax + nout - wt_idx = (ax == NORTH || ax == EAST) ? [1, -ax′] : [-ax′, 1] - # make absorption/removal twist-free - twistdual!(wt, 1) - return permute(ncon((t, wt), (t_idx, wt_idx)), (Tuple(1:nout), Tuple((nout + 1):ntol))) -end -function absorb_weight( - t::Union{PEPSTensor, PEPOTensor}, weights::SUWeight, - row::Int, col::Int, ax::NTuple{N, Int}; inv::Bool = false - ) where {N} - t2 = copy(t) - for a in ax - t2 = absorb_weight(t2, weights, row, col, a; inv) - end - return t2 -end - #= Rotation of SUWeight. Example: 3 x 3 network - Original @@ -398,7 +320,7 @@ end """ CTMRGEnv(wts::SUWeight) -Construct a CTMRG environment with a trivial environment space +Construct a CTMRG environment with a trivial environment space (bond dimension χ = 1) from SUWeight `wts`, which has the same real scalartype as ``wts`. """ diff --git a/src/networks/local_sandwich.jl b/src/networks/local_sandwich.jl index 0d5e01f61..4c46cd9fe 100644 --- a/src/networks/local_sandwich.jl +++ b/src/networks/local_sandwich.jl @@ -26,6 +26,10 @@ _rotl90_localsandwich(O) = rotl90.(O) _rotr90_localsandwich(O) = rotr90.(O) _rot180_localsandwich(O) = rot180.(O) +function _rotate_north_localsandwich(t, dir) + return mod1(dir, 4) == NORTH ? t : _rotate_north_localsandwich(_rotl90_localsandwich(t), dir - 1) +end + ## Math (for Zygote accumulation) # generic local interface @@ -67,6 +71,7 @@ flip_physicalspace(O::PEPSSandwich) = flip_physicalspace.(O) herm_depth(O::PEPSSandwich) = herm_depth.(O) TensorKit.spacetype(::Type{P}) where {P <: PEPSSandwich} = spacetype(eltype(P)) +TensorKit.storagetype(::Type{P}) where {P <: PEPSSandwich} = storagetype(eltype(P)) # not overloading MPOTensor because that defines AbstractTensorMap{<:Any,S,2,2}(::PEPSTensor, ::PEPSTensor) # ie type piracy @@ -129,3 +134,6 @@ flip_physicalspace(O::PEPOSandwich) = flip_physicalspace.(O) herm_depth(O::PEPOSandwich) = herm_depth.(O) TensorKit.spacetype(::Type{P}) where {P <: PEPOSandwich} = spacetype(eltype(P)) +function TensorKit.storagetype(::Type{PEPOSandwich{N, T, P}}) where {N, T, P} + return TensorKit.promote_storagetype(T, P) +end diff --git a/src/operators/infinitepepo.jl b/src/operators/infinitepepo.jl index 0eb44444a..85270c636 100644 --- a/src/operators/infinitepepo.jl +++ b/src/operators/infinitepepo.jl @@ -56,9 +56,9 @@ function InfinitePEPO( size(Pspaces) == size(Nspaces) == size(Espaces) || throw(ArgumentError("Input spaces should have equal sizes.")) - Sspaces = adjoint.(circshift(Nspaces, (1, 0, 0))) - Wspaces = adjoint.(circshift(Espaces, (0, -1, 0))) - Ppspaces = adjoint.(circshift(Pspaces, (0, 0, -1))) + Sspaces = adjoint.(circshift(Nspaces, (-1, 0, 0))) + Wspaces = adjoint.(circshift(Espaces, (0, 1, 0))) + Ppspaces = adjoint.(circshift(Pspaces, (0, 0, 1))) P = map(Pspaces, Ppspaces, Nspaces, Espaces, Sspaces, Wspaces) do P, Pp, N, E, S, W return f(T, P * Pp ← N * E * S * W) diff --git a/src/utility/eigh.jl b/src/utility/eigh.jl index 20f0773ad..eec53ad42 100644 --- a/src/utility/eigh.jl +++ b/src/utility/eigh.jl @@ -46,7 +46,6 @@ end $(TYPEDEF) Wrapper for a eigenvalue decomposition algorithm `fwd_alg` with a defined reverse rule `rrule_alg`. -If `isnothing(rrule_alg)`, Zygote differentiates the forward call automatically. ## Fields @@ -58,34 +57,55 @@ $(TYPEDFIELDS) Construct a `EighAdjoint` algorithm struct based on the following keyword arguments: -* `fwd_alg::Union{Algorithm,NamedTuple}=(; alg::Symbol=$(Defaults.eigh_fwd_alg))`: Eig algorithm of the forward pass which can either be passed as an `Algorithm` instance or a `NamedTuple` where `alg` is one of the following: - - `:qriteration` : MatrixAlgebraKit's `LAPACK_QRIteration` - - `:bisection` : MatrixAlgebraKit's `LAPACK_Bisection` - - `:divideandconquer` : MatrixAlgebraKit's `LAPACK_DivideAndConquer` - - `:multiple` : MatrixAlgebraKit's `LAPACK_MultipleRelativelyRobustRepresentations` - - `:lanczos` : Lanczos algorithm for symmetric/Hermitian matrices, see [KrylovKit docs](https://jutho.github.io/KrylovKit.jl/stable/man/algorithms/#KrylovKit.Lanczos) - - `:blocklanczos` : Block version of `:lanczos` for repeated extremal eigenvalues, see [KrylovKit docs](https://jutho.github.io/KrylovKit.jl/stable/man/algorithms/#KrylovKit.BlockLanczos) -* `rrule_alg::Union{Algorithm,NamedTuple}=(; alg::Symbol=$(Defaults.eigh_rrule_alg))`: Reverse-rule algorithm for differentiating the eigenvalue decomposition. Can be supplied by an `Algorithm` instance directly or as a `NamedTuple` where `alg` is one of the following: - - `:full` : MatrixAlgebraKit's `eigh_pullback!` that requires access to the full spectrum - - `:trunc` : MatrixAlgebraKit's `eigh_trunc_pullback!` solving a Sylvester equation on the truncated subspace +* `fwd_alg::Union{Algorithm,NamedTuple}=(; alg::Symbol=$(Defaults.eigh_fwd_alg))`: Eigh + algorithm of the forward pass which can either be passed as an `Algorithm` instance or a + `NamedTuple` where the algorithm is specified by the `alg` keyword. + The available Eigh algorithms can be divided into two categories: + - "Dense" Eigh algorithms which compute a truncated Eigh through the truncation of a full + [`MatrixAlgebraKit.eigh_full!`](@extref) decomposition. + Available algorithms are: + - `:DefaultAlgorithm` : MatrixAlgebraKit's [default Eigh algorithm](@extref MatrixAlgebraKit.DefaultAlgorithm) for a given matrix type. + - `:DivideAndConquer` : MatrixAlgebraKit's [`DivideAndConquer`](@extref MatrixAlgebraKit.DivideAndConquer) + - `:QRIteration` : MatrixAlgebraKit's [`QRIteration`](@extref MatrixAlgebraKit.QRIteration) + - `:Bisection` : MatrixAlgebraKit's [`Bisection`](@extref MatrixAlgebraKit.Bisection) + - `:Jacobi` : MatrixAlgebraKit's [`Jacobi`](@extref MatrixAlgebraKit.Jacobi) + - `:RobustRepresentations` : MatrixAlgebraKit's [`RobustRepresentations`](@extref MatrixAlgebraKit.RobustRepresentations) + - "Sparse" Eigh algorithms which directly compute a truncated Eigh without access to the + full decomposition. Available algorithms are: + - `:Lanczos` : Lanczos algorithm for symmetric/Hermitian matrices, see [`KrylovKit.Lanczos`](@extref) + - `:BlockLanczos` : Block version of `:Lanczos` for repeated extremal eigenvalues, see [`KrylovKit.BlockLanczos`](@extref) +* `rrule_alg::Union{Algorithm,NamedTuple}=(; alg::Symbol=$(Defaults.eigh_rrule_alg))`: + Reverse-rule algorithm for differentiating the eigenvalue decomposition. Can be supplied + by an `Algorithm` instance directly or as a `NamedTuple` where `alg` is one of the + following: + - `:full` : MatrixAlgebraKit's [`eigh_pullback!`](@extref MatrixAlgebraKit.eigh_pullback!) that requires access to the full spectrum + - `:trunc` : MatrixAlgebraKit's [`eigh_trunc_pullback!`](@extref MatrixAlgebraKit.eigh_trunc_pullback!) solving a Sylvester equation on the truncated subspace + +!!! note + Manually specifying a `rrule_alg` is considered expert-mode usage, and should only be done when full control over the implementation is desired. + For all regular use cases, the default reverse rule algorithms, automatically chosen based on the forward algorithm, should be sufficient. """ struct EighAdjoint{F, R} fwd_alg::F rrule_alg::R -end # Keep truncation algorithm separate to be able to specify CTMRG dependent information +end const EIGH_FWD_SYMBOLS = IdDict{Symbol, Any}( - :qriteration => LAPACK_QRIteration, - :bisection => LAPACK_Bisection, - :divideandconquer => LAPACK_DivideAndConquer, - :multiple => LAPACK_MultipleRelativelyRobustRepresentations, - :lanczos => (; tol = 1.0e-14, krylovdim = 30, kwargs...) -> IterEigh(; alg = Lanczos(; tol, krylovdim), kwargs...), - :blocklanczos => (; tol = 1.0e-14, krylovdim = 30, kwargs...) -> IterEigh(; alg = BlockLanczos(; tol, krylovdim), kwargs...), + :DefaultAlgorithm => DefaultAlgorithm, + :QRIteration => QRIteration, + :Bisection => Bisection, + :DivideAndConquer => DivideAndConquer, + :Jacobi => Jacobi, + :RobustRepresentations => RobustRepresentations, + :Lanczos => (; tol = 1.0e-14, krylovdim = 30, kwargs...) -> IterEigh(; alg = Lanczos(; tol, krylovdim), kwargs...), + :BlockLanczos => (; tol = 1.0e-14, krylovdim = 30, kwargs...) -> IterEigh(; alg = BlockLanczos(; tol, krylovdim), kwargs...), ) const EIGH_RRULE_SYMBOLS = IdDict{Symbol, Type{<:Any}}( :full => FullEighPullback, :trunc => TruncEighPullback, ) +_default_eigh_rrule_alg(::MatrixAlgebraKit.Algorithm) = :full + function EighAdjoint(; fwd_alg = (;), rrule_alg = (;)) # parse forward algorithm fwd_algorithm = if fwd_alg isa NamedTuple @@ -102,7 +122,7 @@ function EighAdjoint(; fwd_alg = (;), rrule_alg = (;)) # parse reverse-rule algorithm rrule_algorithm = if rrule_alg isa NamedTuple rrule_kwargs = (; - alg = Defaults.eigh_rrule_alg, + alg = _default_eigh_rrule_alg(fwd_algorithm), degeneracy_atol = Defaults.rrule_degeneracy_atol, verbosity = Defaults.eigh_rrule_verbosity, rrule_alg..., @@ -124,86 +144,25 @@ function EighAdjoint(; fwd_alg = (;), rrule_alg = (;)) end """ - eigh_trunc(t, alg::EighAdjoint; trunc=notrunc()) - eigh_trunc!(t, alg::EighAdjoint; trunc=notrunc()) + eigh_trunc(t, alg::EighAdjoint) + eigh_trunc!(t, alg::EighAdjoint) Wrapper around `eigh_trunc(!)` which dispatches on the `EighAdjoint` algorithm. This is needed since a custom adjoint may be defined, depending on the `alg`. """ -MatrixAlgebraKit.eigh_trunc(t, alg::EighAdjoint; kwargs...) = eigh_trunc!(copy(t), alg; kwargs...) -function MatrixAlgebraKit.eigh_trunc!(t, alg::EighAdjoint; trunc = notrunc()) - return _eigh_trunc!(t, alg.fwd_alg, trunc) +MatrixAlgebraKit.eigh_trunc(t, alg::EighAdjoint) = eigh_trunc!(copy(t), alg) +function MatrixAlgebraKit.eigh_trunc!(t, alg::EighAdjoint) + return eigh_trunc!(t, alg.fwd_alg) end -function MatrixAlgebraKit.eigh_trunc!( - t::AdjointTensorMap, alg::EighAdjoint; trunc = notrunc() - ) - D, V, info = eigh_trunc!(adjoint(t), alg; trunc) - return adjoint(D), adjoint(V), info +function MatrixAlgebraKit.eigh_trunc!(t::AdjointTensorMap, alg::EighAdjoint) + D, V, ϵ = eigh_trunc!(adjoint(t), alg) + return adjoint(D), adjoint(V), ϵ end # ## Forward algorithms # -# Truncated eigh but also return full D and V to make it compatible with :fixed mode -function _eigh_trunc!( - t::TensorMap, - alg::LAPACK_EighAlgorithm, - trunc::TruncationStrategy, - ) - D, V = eigh_full!(t; alg) - (D̃, Ṽ), ind = truncate(eigh_trunc!, (D, V), trunc) - truncerror = truncation_error(diagview(D), ind) - - # construct info NamedTuple - condnum = cond(D) - info = (; - truncation_error = truncerror, - condition_number = condnum, - D_full = D, - V_full = V, - truncation_indices = ind, - ) - return D̃, Ṽ, info -end - -""" -$(TYPEDEF) - -Eigenvalue decomposition struct containing a pre-computed decomposition or even multiple -ones. Additionally, it can contain the full untruncated decomposition and the corresponding -truncation indices as well. The call to `eigh_trunc`/`eig_trunc` just returns the -pre-computed D and V. In the reverse pass, the adjoint is computed with these exact D and V -and, potentially, the full decompositions if the adjoints require access to them. - -## Fields - -$(TYPEDFIELDS) -""" -struct FixedEig{Dt, Vt, Dtf, Vtf, It} - D::Dt - V::Vt - D_full::Dtf - V_full::Vtf - truncation_indices::It -end - -# check whether the full D and V are supplied -isfulleig(alg::FixedEig) = !isnothing(alg.D_full) && !isnothing(alg.V_full) && !isnothing(alg.truncation_indices) - -# Return pre-computed decomposition -function _eigh_trunc!(_, alg::FixedEig, ::TruncationStrategy) - info = (; - truncation_error = zero(real(scalartype(alg.D))), - condition_number = cond(alg.D), - D_full = alg.D_full, - V_full = alg.V_full, - truncation_indices = alg.truncation_indices, - ) - return alg.D, alg.V, info -end - - """ $(TYPEDEF) @@ -225,35 +184,31 @@ Construct an `IterEigh` algorithm struct based on the following keyword argument * `alg=KrylovKit.Lanczos(; tol=1e-14, krylovdim=25)` : KrylovKit algorithm struct for iterative eigenvalue decomposition. * `fallback_threshold::Float64=Inf` : Threshold for `howmany / minimum(size(block))` above which (if the block is too small) the algorithm falls back to a dense decomposition. -* `start_vector=random_start_vector` : Function providing the initial vector for the iterative algorithm. +* `start_vector=deterministic_start_vector` : Function providing the initial vector for the iterative algorithm. """ @kwdef struct IterEigh alg = KrylovKit.Lanczos(; tol = 1.0e-14, krylovdim = 25) fallback_threshold::Float64 = Inf - start_vector = random_start_vector + start_vector = deterministic_start_vector end +_default_eigh_rrule_alg(::IterEigh) = :trunc # Compute eigh data block-wise using KrylovKit algorithm -function _eigh_trunc!(f, alg::IterEigh, trunc::TruncationStrategy) +function MatrixAlgebraKit.eigh_trunc!(f, alg::TruncatedAlgorithm{<:IterEigh}) D, V = if isempty(blocksectors(f)) # early return truncation_error = zero(real(scalartype(f))) - MatrixAlgebraKit.initialize_output(eigh_full!, f, LAPACK_QRIteration()) # specified algorithm doesn't matter here + MatrixAlgebraKit.initialize_output(eigh_full!, f, QRIteration()) # specified algorithm doesn't matter here else - eighdata, dims = _compute_eighdata!(f, alg, trunc) + eighdata, dims = _compute_eighdata!(f, alg.alg, alg.trunc) _create_eightensors(f, eighdata, dims) end # construct info NamedTuple truncation_error = - trunc isa NoTruncation ? abs(zero(scalartype(f))) : norm(V * D * V' - f) - condition_number = cond(D) - info = (; - truncation_error, condition_number, D_full = nothing, V_full = nothing, - truncation_indices = nothing, - ) + alg.trunc isa NoTruncation ? abs(zero(scalartype(f))) : norm(V * D * V' - f) - return D, V, info + return D, V, truncation_error end # Obtain sparse decomposition from block-wise eigsolve calls @@ -272,7 +227,7 @@ function _compute_eighdata!( howmany = trunc isa NoTruncation ? minimum(size(b)) : blockdim(trunc.space, c) if howmany / minimum(size(b)) > alg.fallback_threshold # Use dense decomposition for small blocks - D, V = eigh_full!(b, LAPACK_QRIteration()) + D, V = eigh_full!(b) lm_ordering = sortperm(abs.(D.diag); rev = true) # order values and vectors consistently with eigsolve D = D.diag[lm_ordering] # extracts diagonal as Vector instead of Diagonal to make compatible with D of svdsolve V = stack(eachcol(V)[lm_ordering])[:, 1:howmany] @@ -285,7 +240,7 @@ function _compute_eighdata!( D, lvecs, info = eigsolve(b, x₀, howmany, :LM, eig_alg) if info.converged < howmany # Fall back to dense SVD if not properly converged @warn "Iterative eigendecomposition did not converge for block $c, falling back to eigh_full" - D, V = eigh_full!(b, LAPACK_QRIteration()) + D, V = eigh_full!(b) lm_ordering = sortperm(abs.(D.diag); rev = true) D = D.diag[lm_ordering] V = stack(eachcol(V)[lm_ordering])[:, 1:howmany] @@ -294,6 +249,9 @@ function _compute_eighdata!( end end + # make it deterministic-ish + MatrixAlgebraKit.gaugefix!(eigh_full!, V) + resize!(D, howmany) dims[c] = length(D) return c => (D, V) @@ -338,14 +296,17 @@ function _get_pullback_gauge_tol(verbosity::Int) end # eigh_trunc! rrule wrapping MatrixAlgebraKit's eigh_pullback! +# https://github.com/QuantumKitHub/MatrixAlgebraKit.jl/blob/b76c7bb60014ecfead6925d0df6cb4b8d7c2668a/src/pullbacks/eigh.jl#L34 function ChainRulesCore.rrule( ::typeof(eigh_trunc!), t::AbstractTensorMap, - alg::EighAdjoint{F, R}; - trunc::TruncationStrategy = notrunc(), - ) where {F <: Union{<:LAPACK_EighAlgorithm, <:FixedEig}, R <: FullEighPullback} - D̃, Ṽ, info = eigh_trunc(t, alg; trunc) - D, V, inds = info.D_full, info.V_full, info.truncation_indices # untruncated decomposition + alg::EighAdjoint{<:TruncatedAlgorithm{<:MatrixAlgebraKit.Algorithm}, <:FullEighPullback} + ) + + D, V = eigh_full!(t; alg.fwd_alg.alg) + (D̃, Ṽ), inds = truncate(eigh_trunc!, (D, V), alg.fwd_alg.trunc) + truncerror = truncation_error(diagview(D), inds) + gtol = _get_pullback_gauge_tol(alg.rrule_alg.verbosity) function eigh_trunc!_full_pullback(ΔDV) @@ -359,17 +320,17 @@ function ChainRulesCore.rrule( return NoTangent(), ZeroTangent(), NoTangent() end - return (D̃, Ṽ, info), eigh_trunc!_full_pullback + return (D̃, Ṽ, truncerror), eigh_trunc!_full_pullback end # eigh_trunc! rrule wrapping MatrixAlgebraKit's eigh_trunc_pullback! (also works for IterEigh) +# https://github.com/QuantumKitHub/MatrixAlgebraKit.jl/blob/b76c7bb60014ecfead6925d0df6cb4b8d7c2668a/src/pullbacks/eigh.jl#L113 function ChainRulesCore.rrule( ::typeof(eigh_trunc!), t, - alg::EighAdjoint{F, R}; - trunc::TruncationStrategy = notrunc(), - ) where {F <: Union{<:LAPACK_EighAlgorithm, <:FixedEig, IterEigh}, R <: TruncEighPullback} - D, V, info = eigh_trunc(t, alg; trunc) + alg::EighAdjoint{F, R} + ) where {F, R <: TruncEighPullback} + D, V, truncerror = eigh_trunc(t, alg) gtol = _get_pullback_gauge_tol(alg.rrule_alg.verbosity) function eigh_trunc!_trunc_pullback(ΔDV) @@ -383,5 +344,5 @@ function ChainRulesCore.rrule( return NoTangent(), ZeroTangent(), NoTangent() end - return (D, V, info), eigh_trunc!_trunc_pullback + return (D, V, truncerror), eigh_trunc!_trunc_pullback end diff --git a/src/utility/qr.jl b/src/utility/qr.jl index 426a2a643..9c26dd074 100644 --- a/src/utility/qr.jl +++ b/src/utility/qr.jl @@ -11,7 +11,6 @@ end $(TYPEDEF) Wrapper for a QR decomposition algorithm `fwd_alg` with a defined reverse rule `rrule_alg`. -If `isnothing(rrule_alg)`, Zygote differentiates the forward call automatically. ## Fields @@ -23,19 +22,27 @@ $(TYPEDFIELDS) Construct a `QRAdjoint` algorithm struct based on the following keyword arguments: -* `fwd_alg::Union{Algorithm,NamedTuple}=(; alg::Symbol=$(Defaults.qr_fwd_alg))`: Eig algorithm of the forward pass which can either be passed as an `Algorithm` instance or a `NamedTuple` where `alg` is one of the following: - - `:qr` : MatrixAlgebraKit's `LAPACK_HouseholderQR` - +* `fwd_alg::Union{Algorithm,NamedTuple}=(; alg::Symbol=$(Defaults.qr_fwd_alg))`: QR + algorithm of the forward pass which can either be passed as an `Algorithm` instance or a + `NamedTuple` where the algorithm is specified by the `alg` keyword. + The available algorithms are provided through MatrixAlgebraKit and include: + - `:DefaultAlgorithm` : MatrixAlgebraKit's [default QR algorithm](@extref MatrixAlgebraKit.DefaultAlgorithm) for a given matrix type. + - `:Householder` : MatrixAlgebraKit's [`Householder`](@extref MatrixAlgebraKit.Householder) * `rrule_alg::Union{Algorithm,NamedTuple}=(; alg::Symbol=$(Defaults.qr_rrule_alg))`: Reverse-rule algorithm for differentiating the eigenvalue decomposition. Can be supplied by an `Algorithm` instance directly or as a `NamedTuple` where `alg` is one of the following: - - `:qr` : MatrixAlgebraKit's `qr_pullback` + - `:qr` : MatrixAlgebraKit's [`qr_pullback!`](@extref MatrixAlgebraKit.qr_pullback!) + +!!! note + Manually specifying a `rrule_alg` is considered expert-mode usage, and should only be done when full control over the implementation is desired. + For all regular use cases, the default reverse rule algorithms, automatically chosen based on the forward algorithm, should be sufficient. """ struct QRAdjoint{F, R} fwd_alg::F rrule_alg::R -end # Keep truncation algorithm separate to be able to specify CTMRG dependent information +end const QR_FWD_SYMBOLS = IdDict{Symbol, Any}( - :qr => LAPACK_HouseholderQR + # :DefaultAlgorithm => DefaultAlgorithm, # TODO: broken, needs to be fixed + :Householder => Householder, ) const QR_RRULE_SYMBOLS = IdDict{Symbol, Type{<:Any}}( :qr => QRPullback @@ -85,33 +92,15 @@ Wrapper around `left_orth(!)` which dispatches on the `QRAdjoint` algorithm. This is needed since a custom adjoint may be defined, depending on the `alg`. """ MatrixAlgebraKit.left_orth(t, alg::QRAdjoint) = left_orth!(copy(t), alg) -MatrixAlgebraKit.left_orth!(t, alg::QRAdjoint) = _left_orth!(t, alg.fwd_alg) -_left_orth!(t, alg::LAPACK_HouseholderQR) = left_orth!(t; alg) - -""" -$(TYPEDEF) - -QR decomposition struct containing a pre-computed decomposition. Th call to `left_orth(!)` -just returns the precomputed `Q` and `R`. In the reverse pass, the adjoint is computed with -these exact `D` and `R`. - -## Fields - -$(TYPEDFIELDS) -""" -struct FixedQR{Qt, Rt} - Q::Qt - R::Rt -end - -_left_orth!(_, alg::FixedQR) = alg.Q, alg.R +MatrixAlgebraKit.left_orth!(t, alg::QRAdjoint) = left_orth!(t, alg.fwd_alg) # left_orth! rrule wrapping MatrixAlgebraKit's qr_pullback! +# https://github.com/QuantumKitHub/MatrixAlgebraKit.jl/blob/b76c7bb60014ecfead6925d0df6cb4b8d7c2668a/src/pullbacks/qr.jl#L49 function ChainRulesCore.rrule( ::typeof(left_orth!), t::AbstractTensorMap, alg::QRAdjoint{F, R}, - ) where {F <: Union{LAPACK_HouseholderQR, FixedQR}, R <: QRPullback} + ) where {F <: MatrixAlgebraKit.Algorithm, R <: QRPullback} QR = left_orth(t, alg) gtol = _get_pullback_gauge_tol(alg.rrule_alg.verbosity) diff --git a/src/utility/svd.jl b/src/utility/svd.jl index 8d834296b..a7887091e 100644 --- a/src/utility/svd.jl +++ b/src/utility/svd.jl @@ -50,7 +50,6 @@ end $(TYPEDEF) Wrapper for a SVD algorithm `fwd_alg` with a defined reverse rule `rrule_alg`. -If `isnothing(rrule_alg)`, Zygote differentiates the forward call automatically. ## Fields @@ -62,29 +61,50 @@ $(TYPEDFIELDS) Construct a `SVDAdjoint` algorithm struct based on the following keyword arguments: -* `fwd_alg::Union{Algorithm,NamedTuple}=(; alg::Symbol=$(Defaults.svd_fwd_alg))`: SVD algorithm of the forward pass which can either be passed as an `Algorithm` instance or a `NamedTuple` where `alg` is one of the following: - - `:sdd` : MatrixAlgebraKit's `LAPACK_DivideAndConquer` - - `:svd` : MatrixAlgebraKit's `LAPACK_QRIteration` - - `:bisection` : MatrixAlgebraKit's `LAPACK_Bisection` - - `:jacobi` : MatrixAlgebraKit's `LAPACK_Jacobi` - - `:iterative` : Iterative SVD only computing the specifed number of singular values and vectors, see [`IterSVD`](@ref) -* `rrule_alg::Union{Algorithm,NamedTuple}=(; alg::Symbol=$(Defaults.svd_rrule_alg))`: Reverse-rule algorithm for differentiating the SVD. Can be supplied by an `Algorithm` instance directly or as a `NamedTuple` where `alg` is one of the following: - - `:full` : MatrixAlgebraKit's `svd_pullback!` that requires access to the full spectrum - - `:trunc` : MatrixAlgebraKit's `svd_trunc_pullback!` solving a Sylvester equation on the truncated subspace - - `:gmres` : GMRES iterative linear solver, see the [KrylovKit docs](https://jutho.github.io/KrylovKit.jl/stable/man/algorithms/#KrylovKit.GMRES) for details - - `:bicgstab` : BiCGStab iterative linear solver, see the [KrylovKit docs](https://jutho.github.io/KrylovKit.jl/stable/man/algorithms/#KrylovKit.BiCGStab) for details - - `:arnoldi` : Arnoldi Krylov algorithm, see the [KrylovKit docs](https://jutho.github.io/KrylovKit.jl/stable/man/algorithms/#KrylovKit.Arnoldi) for details +* `fwd_alg::Union{Algorithm,NamedTuple}=(; alg::Symbol=$(Defaults.svd_fwd_alg))`: SVD + algorithm of the forward pass which can either be passed as an `Algorithm` instance or a + `NamedTuple` where the algorithm is specified by the `alg` keyword. + The available SVD algorithms can be divided into two categories: + - "Dense" SVD algorithms which compute a truncated SVD through the truncation of a full + [`MatrixAlgebraKit.svd_compact!`](@extref) decomposition. + Available algorithms are: + - `:DefaultAlgorithm` : MatrixAlgebraKit's [default SVD algorithm](@extref MatrixAlgebraKit.DefaultAlgorithm) for a given matrix type. + - `:DivideAndConquer` : MatrixAlgebraKit's [`DivideAndConquer`](@extref MatrixAlgebraKit.DivideAndConquer) + - `:QRIteration` : MatrixAlgebraKit's [`QRIteration`](@extref MatrixAlgebraKit.QRIteration) + - `:Bisection` : MatrixAlgebraKit's [`Bisection`](@extref MatrixAlgebraKit.Bisection) + - `:Jacobi` : MatrixAlgebraKit's [`Jacobi`](@extref MatrixAlgebraKit.Jacobi) + - `:SVDViaPolar` : MatrixAlgebraKit's [`SVDViaPolar`](@extref MatrixAlgebraKit.SVDViaPolar) + - `:SafeDivideAndConquer` : MatrixAlgebraKit's [`SafeDivideAndConquer`](@extref MatrixAlgebraKit.SafeDivideAndConquer) + - "Sparse" SVD algorithms which directly compute a truncated SVD without access to the + full decomposition. Available algorithms are: + - `:iterative` : Iterative Krylov-based SVD only computing the specifed number of + singular values and vectors, see [`IterSVD`](@ref) +* `rrule_alg::Union{Algorithm,NamedTuple}=(; alg::Symbol=:$(Defaults.svd_rrule_alg))`: + Reverse-rule algorithm for differentiating the SVD. Can be supplied by an `Algorithm` + instance directly or as a `NamedTuple` where `alg` is one of the following: + - `:full` : MatrixAlgebraKit's [`svd_pullback!`](@extref MatrixAlgebraKit.svd_pullback!) that requires access to the full spectrum + - `:trunc` : MatrixAlgebraKit's [`svd_trunc_pullback!`](@extref MatrixAlgebraKit.svd_trunc_pullback!) solving a Sylvester equation on the truncated subspace + - `:gmres` : GMRES iterative linear solver, see [`KrylovKit.GMRES`](@extref) + - `:bicgstab` : BiCGStab iterative linear solver, see [`KrylovKit.BiCGStab`](@extref) + - `:arnoldi` : Arnoldi Krylov algorithm, see the [`KrylovKit.Arnoldi`](@extref KrylovKit.Arnoldi) + +!!! note + Manually specifying a `rrule_alg` is considered expert-mode usage, and should only be done when full control over the implementation is desired. + For all regular use cases, the default reverse rule algorithms, automatically chosen based on the forward algorithm, should be sufficient. """ struct SVDAdjoint{F, R} fwd_alg::F rrule_alg::R -end # Keep truncation algorithm separate to be able to specify CTMRG dependent information +end const SVD_FWD_SYMBOLS = IdDict{Symbol, Any}( - :sdd => LAPACK_DivideAndConquer, - :svd => LAPACK_QRIteration, - :bisection => LAPACK_Bisection, - :jacobi => LAPACK_Jacobi, + :DefaultAlgorithm => DefaultAlgorithm, + :DivideAndConquer => DivideAndConquer, + :QRIteration => QRIteration, + :Bisection => Bisection, + :Jacobi => Jacobi, + :SVDViaPolar => SVDViaPolar, + :SafeDivideAndConquer => SafeDivideAndConquer, :iterative => (; tol = 1.0e-14, krylovdim = 25, kwargs...) -> IterSVD(; alg = GKL(; tol, krylovdim), kwargs...), ) const SVD_RRULE_SYMBOLS = IdDict{Symbol, Type{<:Any}}( @@ -92,6 +112,8 @@ const SVD_RRULE_SYMBOLS = IdDict{Symbol, Type{<:Any}}( :gmres => GMRES, :bicgstab => BiCGStab, :arnoldi => Arnoldi ) +_default_svd_rrule_alg(::MatrixAlgebraKit.Algorithm) = :full + function SVDAdjoint(; fwd_alg = (;), rrule_alg = (;)) # parse forward SVD algorithm fwd_algorithm = if fwd_alg isa NamedTuple @@ -108,7 +130,7 @@ function SVDAdjoint(; fwd_alg = (;), rrule_alg = (;)) # parse reverse-rule SVD algorithm rrule_algorithm = if rrule_alg isa NamedTuple rrule_kwargs = (; - alg = Defaults.svd_rrule_alg, + alg = _default_svd_rrule_alg(fwd_algorithm), # default rrule depends on forward algorithm tol = Defaults.svd_rrule_tol, krylovdim = Defaults.svd_rrule_min_krylovdim, degeneracy_atol = Defaults.rrule_degeneracy_atol, @@ -141,92 +163,26 @@ function SVDAdjoint(; fwd_alg = (;), rrule_alg = (;)) end """ - svd_trunc(t, alg::SVDAdjoint; trunc=notrunc()) - svd_trunc!(t, alg::SVDAdjoint; trunc=notrunc()) + svd_trunc(t, alg::SVDAdjoint) + svd_trunc!(t, alg::SVDAdjoint) Wrapper around `svd_trunc(!)` which dispatches on the `SVDAdjoint` algorithm. This is needed since a custom adjoint may be defined, depending on the `alg`. E.g., for `IterSVD` the adjoint for a truncated SVD from `KrylovKit.svdsolve` is used. """ -MatrixAlgebraKit.svd_trunc(t, alg::SVDAdjoint; kwargs...) = svd_trunc!(copy(t), alg; kwargs...) -function MatrixAlgebraKit.svd_trunc!(t, alg::SVDAdjoint; trunc = notrunc()) - return _svd_trunc!(t, alg.fwd_alg, trunc) +MatrixAlgebraKit.svd_trunc(t, alg::SVDAdjoint) = svd_trunc!(copy(t), alg) +function MatrixAlgebraKit.svd_trunc!(t, alg::SVDAdjoint) + return svd_trunc!(t, alg.fwd_alg) end -function MatrixAlgebraKit.svd_trunc!( - t::AdjointTensorMap, alg::SVDAdjoint; trunc = notrunc() - ) - u, s, vt, info = svd_trunc!(adjoint(t), alg; trunc) - return adjoint(vt), adjoint(s), adjoint(u), info +function MatrixAlgebraKit.svd_trunc!(t::AdjointTensorMap, alg::SVDAdjoint) + u, s, vt, ϵ = svd_trunc!(adjoint(t), alg) + return adjoint(vt), adjoint(s), adjoint(u), ϵ end # ## Forward algorithms # -# Truncated SVD but also return full U, S and V to make it compatible with :fixed mode -function _svd_trunc!( - t::TensorMap, - alg::Union{LAPACK_DivideAndConquer, LAPACK_QRIteration}, - trunc::TruncationStrategy, - ) - U, S, V⁺ = svd_compact!(t; alg) - (Ũ, S̃, Ṽ⁺), ind = truncate(svd_trunc!, (U, S, V⁺), trunc) - truncerror = truncation_error(diagview(S), ind) - - # construct info NamedTuple - condnum = cond(S) - info = (; - truncation_error = truncerror, condition_number = condnum, - U_full = U, S_full = S, V_full = V⁺, - truncation_indices = ind, - ) - return Ũ, S̃, Ṽ⁺, info -end - -""" -$(TYPEDEF) - -SVD struct containing a pre-computed decomposition or even multiple ones. Additionally, it -can contain the untruncated full decomposition as well. The call to `svd_trunc` 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 and, potentially, the full decompositions if the adjoints needs access to them. - -## Fields - -$(TYPEDFIELDS) -""" -struct FixedSVD{Ut, St, Vt, Utf, Stf, Vtf, It} - U::Ut - S::St - V::Vt - U_full::Utf - S_full::Stf - V_full::Vtf - truncation_indices::It -end - -# check whether the full U, S and V are supplied -function isfullsvd(alg::FixedSVD) - if isnothing(alg.U_full) || isnothing(alg.S_full) || isnothing(alg.V_full) || isnothing(alg.truncation_indices) - return false - else - return true - end -end - -# Return pre-computed SVD -function _svd_trunc!(_, alg::FixedSVD, ::TruncationStrategy) - info = (; - truncation_error = zero(real(scalartype(alg.S))), - condition_number = cond(alg.S), - U_full = alg.U_full, - S_full = alg.S_full, - V_full = alg.V_full, - truncation_indices = alg.truncation_indices, - ) - return alg.U, alg.S, alg.V, info -end - """ $(TYPEDEF) @@ -248,40 +204,36 @@ Construct an `IterSVD` algorithm struct based on the following keyword arguments * `alg::KrylovKit.GKL=KrylovKit.GKL(; tol=1e-14, krylovdim=25)` : GKL algorithm struct for block-wise iterative SVD. * `fallback_threshold::Float64=Inf` : Threshold for `howmany / minimum(size(block))` above which (if the block is too small) the algorithm falls back to TensorKit's dense SVD. -* `start_vector=random_start_vector` : Function providing the initial vector for the iterative SVD algorithm. +* `start_vector=deterministic_start_vector` : Function providing the initial vector for the iterative SVD algorithm. """ @kwdef struct IterSVD alg::KrylovKit.GKL = KrylovKit.GKL(; tol = 1.0e-14, krylovdim = 25) fallback_threshold::Float64 = Inf - start_vector = random_start_vector + start_vector = deterministic_start_vector end +_default_svd_rrule_alg(::IterSVD) = :trunc -function random_start_vector(t::AbstractMatrix) - return randn(scalartype(t), size(t, 1)) -end +random_start_vector(t::AbstractMatrix) = randn(scalartype(t), size(t, 1)) +deterministic_start_vector(t::AbstractMatrix) = ones(scalartype(t), size(t, 1)) # Compute SVD data block-wise using KrylovKit algorithm # TODO: redefine _empty_svdtensors, _create_svdtensors -function _svd_trunc!(f, alg::IterSVD, trunc::TruncationStrategy) +function MatrixAlgebraKit.svd_trunc!(f, alg::TruncatedAlgorithm{<:IterSVD}) + fwd_alg = alg.alg + trunc = alg.trunc U, S, V = if isempty(blocksectors(f)) # early return truncation_error = zero(real(scalartype(f))) - MatrixAlgebraKit.initialize_output(svd_compact!, f, LAPACK_QRIteration()) # specified algorithm doesn't matter here + MatrixAlgebraKit.initialize_output(svd_compact!, f, DefaultAlgorithm()) # specified algorithm doesn't matter here else - SVDdata, dims = _compute_svddata!(f, alg, trunc) + SVDdata, dims = _compute_svddata!(f, fwd_alg, trunc) _create_svdtensors(f, SVDdata, dims) end - # construct info NamedTuple truncation_error = trunc isa NoTruncation ? abs(zero(scalartype(f))) : norm(U * S * V - f) - condition_number = cond(S) - info = (; - truncation_error, condition_number, U_full = nothing, S_full = nothing, V_full = nothing, - truncation_indices = nothing, - ) - return U, S, V, info + return U, S, V, truncation_error end # Copy from TensorKit v0.14 internal functions @@ -319,7 +271,7 @@ function _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 = svd_compact!(b, LAPACK_DivideAndConquer()) + U, S, V = svd_compact!(b; alg = Defaults.svd_fwd_alg) S = S.diag # extracts diagonal as Vector instead of Diagonal to make compatible with S of svdsolve U = U[:, 1:howmany] V = V[1:howmany, :] @@ -332,7 +284,7 @@ function _compute_svddata!( S, lvecs, rvecs, info = svdsolve(b, x₀, howmany, :LR, svd_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 = svd_compact!(b, LAPACK_DivideAndConquer()) + U, S, V = svd_compact!(b; alg = Defaults.svd_fwd_alg) S = S.diag U = U[:, 1:howmany] V = V[1:howmany, :] @@ -342,6 +294,9 @@ function _compute_svddata!( end end + # make it deterministic-ish + MatrixAlgebraKit.gaugefix!(svd_trunc!, U, V) + resize!(S, howmany) dims[c] = length(S) return c => (U, S, V) @@ -356,16 +311,19 @@ end # # svd_trunc! rrule wrapping MatrixAlgebraKit's svd_pullback! +# https://github.com/QuantumKitHub/MatrixAlgebraKit.jl/blob/b76c7bb60014ecfead6925d0df6cb4b8d7c2668a/src/pullbacks/svd.jl#L33 function ChainRulesCore.rrule( ::typeof(svd_trunc!), t::AbstractTensorMap, - alg::SVDAdjoint{F, R}; - trunc::TruncationStrategy = notrunc(), - ) where {F, R <: FullSVDPullback} - @assert !(alg.fwd_alg isa IterSVD) "IterSVD is not compatible with FullSVDPullback" + alg::SVDAdjoint{F, R} + ) where {F <: TruncatedAlgorithm{<:MatrixAlgebraKit.Algorithm}, R <: FullSVDPullback} + # TODO: filter out any decomposition algorithm that doesn't give access to the full spectrum + + # requires access to the full decomposition + U, S, V⁺ = svd_compact!(t, alg.fwd_alg.alg) + (Ũ, S̃, Ṽ⁺), inds = truncate(svd_trunc!, (U, S, V⁺), alg.fwd_alg.trunc) + truncerror = truncation_error(diagview(S), inds) - Ũ, S̃, Ṽ⁺, info = svd_trunc(t, alg; trunc) - U, S, V⁺, inds = info.U_full, info.S_full, info.V_full, info.truncation_indices # untruncated decomposition gtol = _get_pullback_gauge_tol(alg.rrule_alg.verbosity) function svd_trunc!_full_pullback(ΔUSV′) @@ -380,17 +338,17 @@ function ChainRulesCore.rrule( return NoTangent(), ZeroTangent(), NoTangent() end - return (Ũ, S̃, Ṽ⁺, info), svd_trunc!_full_pullback + return (Ũ, S̃, Ṽ⁺, truncerror), svd_trunc!_full_pullback end # svd_trunc! rrule wrapping MatrixAlgebraKit's svd_trunc_pullback! (also works for IterSVD) +# https://github.com/QuantumKitHub/MatrixAlgebraKit.jl/blob/b76c7bb60014ecfead6925d0df6cb4b8d7c2668a/src/pullbacks/svd.jl#L143 function ChainRulesCore.rrule( ::typeof(svd_trunc!), t, - alg::SVDAdjoint{F, R}; - trunc::TruncationStrategy = notrunc(), + alg::SVDAdjoint{F, R}, ) where {F, R <: TruncSVDPullback} - U, S, V⁺, info = svd_trunc(t, alg; trunc) + U, S, V⁺, ϵ = svd_trunc(t, alg) gtol = _get_pullback_gauge_tol(alg.rrule_alg.verbosity) function svd_trunc!_trunc_pullback(ΔUSV′) @@ -405,17 +363,16 @@ function ChainRulesCore.rrule( return NoTangent(), ZeroTangent(), NoTangent() end - return (U, S, V⁺, info), svd_trunc!_trunc_pullback + return (U, S, V⁺, ϵ), svd_trunc!_trunc_pullback end # KrylovKit rrule compatible with TensorMaps & function handles function ChainRulesCore.rrule( ::typeof(svd_trunc!), f, - alg::SVDAdjoint{F, R}; - trunc::TruncationStrategy = notrunc(), + alg::SVDAdjoint{F, R} ) where {F, R <: Union{GMRES, BiCGStab, Arnoldi}} - U, S, V, info = svd_trunc(f, alg; trunc) + U, S, V, ϵ = svd_trunc(f, alg) # update rrule_alg tolerance to be compatible with smallest singular value rrule_alg = alg.rrule_alg @@ -475,5 +432,5 @@ function ChainRulesCore.rrule( return NoTangent(), ZeroTangent(), NoTangent() end - return (U, S, V, info), svd_trunc!_itersvd_pullback + return (U, S, V, ϵ), svd_trunc!_itersvd_pullback end diff --git a/src/utility/util.jl b/src/utility/util.jl index 9a27c6fc6..36b953474 100644 --- a/src/utility/util.jl +++ b/src/utility/util.jl @@ -118,6 +118,23 @@ function twistdual!(t::AbstractTensorMap, is) end twistdual(t::AbstractTensorMap, is) = twistdual!(copy(t), is) +# lifted from #311, to be removed once that's merged +""" + twistnondual(t::AbstractTensorMap, i) + twistnondual!(t::AbstractTensorMap, i) + +Twist the i-th leg of a tensor `t` if it represents a non-dual space. +""" +function twistnondual!(t::AbstractTensorMap, i::Int) + !isdual(space(t, i)) || return t + return twist!(t, i) +end +function twistnondual!(t::AbstractTensorMap, is) + is′ = filter(i -> !isdual(space(t, i)), is) + return twist!(t, is′) +end +twistnondual(t::AbstractTensorMap, is) = twistnondual!(copy(t), is) + """ str(t) @@ -216,6 +233,20 @@ function random_dual!(Vs::AbstractMatrix{E}; p = 0.7) where {E <: ElementarySpac return Vs end +""" + _permute_to_last(axes::NTuple{N, Int}, ax::Int) where {N} + +Returns `(1, 2, ..., N)` but with `ax` moved to the end, +and the corresponding permutation for `axes` (with `ax` as the only domain index). +""" +function _permute_to_last(axes::NTuple{N, Int}, ax::Int) where {N} + codomain_axes = TupleTools.deleteat(ntuple(identity, N), ax) + q = invperm(axes) + biperm = (map(i -> q[i], codomain_axes), (q[ax],)) + new_axes = (ntuple(i -> axes[biperm[1][i]], N - 1)..., ax) + return new_axes, biperm +end + """ _permute_to_first(axes::NTuple{N, Int}, ax::Int) where {N} diff --git a/test/Project.toml b/test/Project.toml new file mode 100644 index 000000000..d23ab3142 --- /dev/null +++ b/test/Project.toml @@ -0,0 +1,34 @@ +name = "PEPSKitTests" + +[deps] +Accessors = "7d9f7c33-5ae7-4f3b-8dc6-eff91059b697" +ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" +ChainRulesTestUtils = "cdddcdb0-9152-4a09-a978-84456f9df70a" +FiniteDifferences = "26cc04aa-876d-5657-8c51-4c34ba976000" +KrylovKit = "0b1a1467-8014-51b9-945f-bf0ae24f4b77" +LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" +MPSKit = "bb1c41ca-d63c-52ed-829e-0820dda26502" +MPSKitModels = "ca635005-6f8c-4cd1-b51d-8491250ef2ab" +MatrixAlgebraKit = "6c742aac-3347-4629-af66-fc926824e5e4" +OptimKit = "77e91f04-9b3b-57a6-a776-40b61faaebe0" +PEPSKit = "52969e89-939e-4361-9b68-9bc7cde4bdeb" +ParallelTestRunner = "d3525ed8-44d0-4b2c-a655-542cee43accc" +QuadGK = "1fd47b50-473d-5c70-9696-f719f8f3bcdc" +Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" +SafeTestsets = "1bc83da4-3b8d-516f-aca4-4fe02f6d838f" +TensorKit = "07d1fe3e-3e46-537d-9eac-e9e13d0d4cec" +Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" +TestExtras = "5ed8adda-3752-4e41-b88a-e8b09835ee3a" +VectorInterface = "409d34a3-91d5-4945-b6ec-7529ddf182d8" +Zygote = "e88e6eb3-aa80-5325-afca-941959d7151f" + +[sources] +PEPSKit = {path = ".."} + +[compat] +ChainRulesTestUtils = "1.13" +FiniteDifferences = "0.12" +ParallelTestRunner = "2.6.0" +QuadGK = "2.11.1" +Test = "1" +TestExtras = "0.3" diff --git a/test/bondenv/benv_ctm.jl b/test/bondenv/benv_ctm.jl index ee081520f..ad83f2028 100644 --- a/test/bondenv/benv_ctm.jl +++ b/test/bondenv/benv_ctm.jl @@ -10,6 +10,7 @@ Nr, Nc = 2, 2 Envspace = Vect[FermionParity ⊠ U1Irrep]( (0, 0) => 4, (1, 1 // 2) => 1, (1, -1 // 2) => 1, (0, 1) => 1, (0, -1) => 1 ) +trunc_state = truncerror(; atol = 1.0e-10) & truncrank(4) ctm_alg = SequentialCTMRG(; tol = 1.0e-10, verbosity = 2, trunc = truncerror(; atol = 1.0e-10) & truncrank(8)) # create Hubbard iPEPS using simple update function get_hubbard_peps(t::Float64 = 1.0, U::Float64 = 8.0) @@ -17,9 +18,9 @@ function get_hubbard_peps(t::Float64 = 1.0, U::Float64 = 8.0) Vphy = Vect[FermionParity ⊠ U1Irrep]((0, 0) => 2, (1, 1 // 2) => 1, (1, -1 // 2) => 1) peps = InfinitePEPS(rand, ComplexF64, Vphy, Vphy; unitcell = (Nr, Nc)) wts = SUWeight(peps) - alg = SimpleUpdate(; trunc = truncerror(; atol = 1.0e-10) & truncrank(4)) + alg = SimpleUpdate(; trunc = trunc_state) evolver = TimeEvolver(peps, H, 1.0e-2, 10000, alg, wts) - peps, = time_evolve(evolver, H; tol = 1.0e-8, check_interval = 2000) + peps, = time_evolve(evolver, H; tol = 1.0e-8, verbosity = 1, check_interval = 2000) normalize!.(peps.A, Inf) return peps end @@ -28,10 +29,8 @@ function get_hubbard_pepo(t::Float64 = 1.0, U::Float64 = 8.0) H = hubbard_model(ComplexF64, Trivial, U1Irrep, InfiniteSquare(Nr, Nc); t, U, mu = U / 2) pepo = PEPSKit.infinite_temperature_density_matrix(H) wts = SUWeight(pepo) - alg = SimpleUpdate(; - trunc = truncerror(; atol = 1.0e-10) & truncrank(4), bipartite = false - ) - pepo, = time_evolve(pepo, H, 2.0e-3, 500, alg, wts; check_interval = 100) + alg = SimpleUpdate(; trunc = trunc_state, bipartite = false) + pepo, = time_evolve(pepo, H, 2.0e-3, 500, alg, wts; verbosity = 1, check_interval = 100) normalize!.(pepo.A, Inf) return pepo end @@ -55,8 +54,8 @@ function test_benv_ctm(state::Union{InfinitePEPS, InfinitePEPO}) @test 1 <= cond2 < cond1 @info "benv cond number: (gauge-fixed) $(cond2) ≤ $(cond1) (initial)" # verify gauge fixing is done correctly - @tensor half[:] := Z[-1; 1 3] * a[1; -2 2] * b[2 -3; 3] - @tensor half2[:] := Z2[-1; 1 3] * a2[1; -2 2] * b2[2 -3; 3] + @tensor half[:] := Z[-1; 1 3] * a[1 -2; 2] * b[2 -3; 3] + @tensor half2[:] := Z2[-1; 1 3] * a2[1 -2; 2] * b2[2 -3; 3] @test half ≈ half2 end return diff --git a/test/bondenv/bond_truncate.jl b/test/bondenv/bond_truncate.jl index 415d7c0d4..ebf58c7f3 100644 --- a/test/bondenv/bond_truncate.jl +++ b/test/bondenv/bond_truncate.jl @@ -1,5 +1,4 @@ using Random -using Printf using Test using TensorKit using PEPSKit @@ -11,7 +10,7 @@ using PEPSKit: _combine_ket, _combine_ket_for_svd Random.seed!(0) maxiter = 600 check_interval = 30 -elt = Float64 +elt = ComplexF64 # simulating the situation of applying a 2-site gate # to a bond with virtual dimension D, physical dimension d. d, D = 2, 4 diff --git a/test/bp/gaugefix.jl b/test/bp/gaugefix.jl index 350a4f205..76d8fdc4e 100644 --- a/test/bp/gaugefix.jl +++ b/test/bp/gaugefix.jl @@ -14,23 +14,23 @@ using PEPSKit: _next, _is_bipartite maxiter, tol = 100, 1.0e-9 Random.seed!(52840679) Pspaces, Nspaces, Espaces = if S == U1Irrep - map(zip(rand(1:2, unitcell), rand(1:2, unitcell), rand(1:2, unitcell))) do (d0, d1, d2) + map(rand(1:2, unitcell), rand(1:2, unitcell), rand(1:2, unitcell)) do d0, d1, d2 Vect[S](0 => d0, 1 => d1, -1 => d2) end, - map(zip(rand(2:4, unitcell), rand(2:4, unitcell), rand(2:4, unitcell))) do (d0, d1, d2) + map(rand(2:4, unitcell), rand(2:4, unitcell), rand(2:4, unitcell)) do d0, d1, d2 Vect[S](0 => d0, 1 => d1, -1 => d2) end, - map(zip(rand(2:4, unitcell), rand(2:4, unitcell), rand(2:4, unitcell))) do (d0, d1, d2) + map(rand(2:4, unitcell), rand(2:4, unitcell), rand(2:4, unitcell)) do d0, d1, d2 Vect[S](0 => d0, 1 => d1, -1 => d2) end else - map(zip(rand(2:3, unitcell), rand(2:3, unitcell))) do (d0, d1) + map(rand(2:3, unitcell), rand(2:3, unitcell)) do d0, d1 Vect[S](0 => d0, 1 => d1) end, - map(zip(rand(2:4, unitcell), rand(2:4, unitcell))) do (d0, d1) + map(rand(2:4, unitcell), rand(2:4, unitcell)) do d0, d1 Vect[S](0 => d0, 1 => d1) end, - map(zip(rand(2:4, unitcell), rand(2:4, unitcell))) do (d0, d1) + map(rand(2:4, unitcell), rand(2:4, unitcell)) do d0, d1 Vect[S](0 => d0, 1 => d1) end end diff --git a/test/ctmrg/contractions.jl b/test/ctmrg/contractions.jl index a4aae7280..7a91de3c2 100644 --- a/test/ctmrg/contractions.jl +++ b/test/ctmrg/contractions.jl @@ -3,23 +3,57 @@ using Random using PEPSKit using TensorKit -using PEPSKit: eachcoordinate -using PEPSKit: EnlargedCorner, simultaneous_projectors -using PEPSKit: renormalize_northwest_corner, renormalize_northeast_corner, renormalize_southeast_corner, renormalize_southwest_corner +using PEPSKit: eachcoordinate, _next_coordinate +using PEPSKit: EnlargedCorner, HalfInfiniteEnv, FullInfiniteEnv +using PEPSKit: half_infinite_environment, full_infinite_environment +using PEPSKit: simultaneous_projectors, contract_projectors +using PEPSKit: renormalize_northwest_corner, renormalize_northeast_corner, + renormalize_southeast_corner, renormalize_southwest_corner +using PEPSKit: random_start_vector # settings -Random.seed!(91283219347) +Random.seed!(91283219348) stype = ComplexF64 -ctm_alg = SimultaneousCTMRG(; projector_alg = :halfinfinite) -function test_contractions( +renormalize_corner_fns = ( + renormalize_northwest_corner, renormalize_northeast_corner, + renormalize_southeast_corner, renormalize_southwest_corner, +) + +function test_ctmrg_contractions( 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) - n = InfiniteSquareNetwork(peps) - coordinates = eachcoordinate(n) + @testset "CTMRG PEPS contractions" begin + peps = InfinitePEPS(randn, stype, Pspaces, Nspaces, Espaces) + env = CTMRGEnv(randn, stype, peps, chis_north, chis_east, chis_south, chis_west) + + n = InfiniteSquareNetwork(peps) + + test_contractions(n, env) + end + + @testset "CTMRG PartitionFunction contractions" begin + pf = InfinitePartitionFunction(randn, stype, Nspaces, Espaces) + env = CTMRGEnv(randn, stype, pf, chis_north, chis_east, chis_south, chis_west) + n = InfiniteSquareNetwork(pf) + + test_contractions(n, env) + end + + @testset "CTMRG PEPO contractions" begin + pepo = InfinitePEPO(randn, stype, Pspaces, Pspaces, Pspaces) + peps = InfinitePEPS(randn, stype, Pspaces, Nspaces, Espaces) + n = InfiniteSquareNetwork(peps, pepo) + env = CTMRGEnv(randn, stype, n, chis_north, chis_east, chis_south, chis_west) + + test_contractions(n, env) + end + + return nothing +end + +function test_contractions(n::InfiniteSquareNetwork, env::CTMRGEnv) dirs_and_coordinates = eachcoordinate(n, 1:4) # initialize dense and sparse enlarged corners @@ -28,24 +62,143 @@ function test_contractions( end dense_enlarged_corners = map(TensorMap, sparse_enlarged_corners) - # compute projectors (doesn't matter how) - (P_left, P_right), info = simultaneous_projectors( - dense_enlarged_corners, env, ctm_alg.projector_alg + # initialize sparse and dense half-inifite environments + sparse_halfinf_envs = map(dirs_and_coordinates) do co + co´ = _next_coordinate(co, size(env)[2:3]...) + return HalfInfiniteEnv( + sparse_enlarged_corners[co...], sparse_enlarged_corners[co´...] + ) + end + dense_halfinf_envs = map(TensorMap, sparse_halfinf_envs) + # also compute directly from dense enlarged corners, for consistency with current implementation + dense_halfinf_envs_bis = map(dirs_and_coordinates) do co + co´ = _next_coordinate(co, size(env)[2:3]...) + return half_infinite_environment( + dense_enlarged_corners[co...], dense_enlarged_corners[co´...] + ) + end + + # initialize sparse and dense full-inifite environments + sparse_fullinf_envs = map(dirs_and_coordinates) do co + rowsize, colsize = size(env)[2:3] + co2 = _next_coordinate(co, rowsize, colsize) + co3 = _next_coordinate(co2, rowsize, colsize) + co4 = _next_coordinate(co3, rowsize, colsize) + return FullInfiniteEnv( + sparse_enlarged_corners[co4...], + sparse_enlarged_corners[co...], + sparse_enlarged_corners[co2...], + sparse_enlarged_corners[co3...], + ) + end + dense_fullinf_envs = map(TensorMap, sparse_fullinf_envs) + # also compute directly from dense enlarged corners, for consistency with current implementation + dense_fullinf_envs_bis = map(dirs_and_coordinates) do co + rowsize, colsize = size(env)[2:3] + co2 = _next_coordinate(co, rowsize, colsize) + co3 = _next_coordinate(co2, rowsize, colsize) + co4 = _next_coordinate(co3, rowsize, colsize) + return full_infinite_environment( + dense_enlarged_corners[co4...], + dense_enlarged_corners[co...], + dense_enlarged_corners[co2...], + dense_enlarged_corners[co3...], + ) + end + + # SVD half and full infinite environments + (P_left_half, P_right_half), info_half = simultaneous_projectors( + dense_enlarged_corners, env, HalfInfiniteProjector() + ) + U_half, S_half, V_half = info_half.U, info_half.S, info_half.V + (P_left_full, P_right_full), info_full = simultaneous_projectors( + dense_enlarged_corners, env, FullInfiniteProjector() ) + U_full, S_full, V_full = info_full.U, info_full.S, info_full.V - # test corner renormalization - return foreach(coordinates) do (r, c) - for renormalize_f in ( - renormalize_northwest_corner, renormalize_northeast_corner, - renormalize_southeast_corner, renormalize_southwest_corner, - ) - C_sparse = renormalize_f((r, c), sparse_enlarged_corners, P_left, P_right) - C_dense = renormalize_f((r, c), dense_enlarged_corners, P_left, P_right) - @test C_sparse ≈ C_dense - end + # check projector computation for both types of environments, + # comparing dense and sparse implementations + foreach(dirs_and_coordinates) do co + dir, r, c = co + + co2 = _next_coordinate(co, size(env)[2:3]...) + co3 = _next_coordinate(co2, size(env)[2:3]...) + co4 = _next_coordinate(co3, size(env)[2:3]...) + + ## HalfInfiniteEnv + + shenv = sparse_halfinf_envs[dir, r, c] + dhenv = dense_halfinf_envs[dir, r, c] + dhenv_bis = dense_halfinf_envs_bis[dir, r, c] + @test dhenv ≈ dhenv_bis + + # application + xr = random_start_vector(shenv) + xl = randn(storagetype(shenv), codomain(shenv)) + @test shenv(xr, Val(false)) ≈ dhenv * xr + @test shenv(xl, Val(true)) ≈ dhenv' * xl + + # projector computation + P_left_sparse, P_right_sparse = contract_projectors( + U_half[dir, r, c], S_half[dir, r, c], V_half[dir, r, c], shenv + ) + P_left_dense, P_right_dense = contract_projectors( + U_half[dir, r, c], S_half[dir, r, c], V_half[dir, r, c], + dense_enlarged_corners[co...], dense_enlarged_corners[co2...], + ) + @test P_left_sparse ≈ P_left_dense + @test P_right_sparse ≈ P_right_dense + @test P_left_sparse ≈ P_left_half[dir, r, c] + @test P_right_sparse ≈ P_right_half[dir, r, c] + + + ## FullInfiniteEnv + + sfenv = sparse_fullinf_envs[dir, r, c] + dfenv = dense_fullinf_envs[dir, r, c] + dfenv_bis = dense_fullinf_envs_bis[dir, r, c] + @test dfenv ≈ dfenv_bis + + # application + xl = randn(storagetype(sfenv), codomain(sfenv)) + xr = random_start_vector(sfenv) + @test sfenv(xr, Val(false)) ≈ dfenv * xr + @test sfenv(xl, Val(true)) ≈ dfenv' * xl + + # projector computation + P_left_sparse, P_right_sparse = contract_projectors( + U_full[dir, r, c], S_full[dir, r, c], V_full[dir, r, c], sfenv + ) + P_left_dense, P_right_dense = contract_projectors( + U_full[dir, r, c], S_full[dir, r, c], V_full[dir, r, c], + half_infinite_environment( + dense_enlarged_corners[co4...], dense_enlarged_corners[co...] + ), + half_infinite_environment( + dense_enlarged_corners[co2...], dense_enlarged_corners[co3...] + ), + ) + @test P_left_sparse ≈ P_left_dense + @test P_right_sparse ≈ P_right_dense + @test P_left_sparse ≈ P_left_full[dir, r, c] + @test P_right_sparse ≈ P_right_full[dir, r, c] + end + + foreach(dirs_and_coordinates) do co + dir, r, c = co + + ## Corner renormalization + + C_sparse = renormalize_corner_fns[dir]( + (r, c), sparse_enlarged_corners, P_left_half, P_right_half + ) + C_dense = renormalize_corner_fns[dir]( + (r, c), dense_enlarged_corners, P_left_half, P_right_half + ) + @test C_sparse ≈ C_dense end - # TODO: test all other uncovered contracitions + return nothing end @testset "Random Cartesian spaces" begin @@ -59,7 +212,7 @@ end chis_south = ComplexSpace.(rand(5:10, unitcell...)) chis_west = ComplexSpace.(rand(5:10, unitcell...)) - test_contractions( + test_ctmrg_contractions( Pspaces, Nspaces, Espaces, chis_north, chis_east, chis_south, chis_west, ) end @@ -76,7 +229,7 @@ end Nspaces = [Vpeps Vpeps'; Vpeps' Vpeps] chis = [Venv Venv; Venv Venv] - test_contractions(Pspaces, Nspaces, Nspaces, chis, chis, chis, chis) + test_ctmrg_contractions(Pspaces, Nspaces, Nspaces, chis, chis, chis, chis) # 4x4 unit cell with all 32 inequivalent bonds # @@ -119,5 +272,33 @@ end Pspaces = fill(phys_space, (4, 4)) chis = fill(corner_space, (4, 4)) - test_contractions(Pspaces, Nspaces, Nspaces, chis, chis, chis, chis) + test_ctmrg_contractions(Pspaces, Nspaces, Nspaces, chis, chis, chis, chis) +end + +@testset "Random fermionic spaces" begin + unitcell = (3, 3) + + S = Vect[FermionParity] + pdims = rand(1:2, unitcell..., 2) + vdims = rand(2:4, unitcell..., 2) + edims = rand(3:6, unitcell..., 2) + + function _construct_space(ds::Array{<:Int, 3}) + V = map(Iterators.product(axes(ds)[1:2]...)) do (r, c) + return S(0 => ds[r, c, 1], 1 => ds[r, c, 2]) + end + return V + end + + Pspaces = _construct_space(pdims) + Nspaces = _construct_space(vdims) + Espaces = _construct_space(vdims) + chis_north = _construct_space(edims) + chis_east = _construct_space(edims) + chis_south = _construct_space(edims) + chis_west = _construct_space(edims) + + test_ctmrg_contractions( + Pspaces, Nspaces, Espaces, chis_north, chis_east, chis_south, chis_west, + ) end diff --git a/test/ctmrg/fixed_iterscheme.jl b/test/ctmrg/fixed_iterscheme.jl index 2952a485c..62c4251e4 100644 --- a/test/ctmrg/fixed_iterscheme.jl +++ b/test/ctmrg/fixed_iterscheme.jl @@ -6,10 +6,10 @@ using LinearAlgebra using TensorKit, KrylovKit using PEPSKit using PEPSKit: - FixedSVD, ctmrg_iteration, + compute_gauge_fix_gauge, + fix_phases, fix_relative_phases, - fix_global_phases, calc_elementwise_convergence, peps_normalize, ScramblingEnvGauge, @@ -19,13 +19,13 @@ using PEPSKit.Defaults: ctmrg_tol # initialize parameters D = 2 χ = 16 -svd_algs = [SVDAdjoint(; fwd_alg = (; alg = :sdd)), SVDAdjoint(; fwd_alg = (; alg = :iterative))] +svd_algs = [(; alg = :DivideAndConquer), (; alg = :iterative)] projector_algs_asymm = [:halfinfinite] #, :fullinfinite] unitcells = [(1, 1), (3, 4)] atol = 1.0e-5 # test for element-wise convergence after application of fixed step -@testset "$unitcell unit cell with $(typeof(decomposition_alg.fwd_alg)) and $projector_alg" for ( +@testset "$unitcell unit cell with $(decomposition_alg.alg) and $projector_alg" for ( unitcell, decomposition_alg, projector_alg, ) in Iterators.product( unitcells, svd_algs, projector_algs_asymm @@ -39,27 +39,31 @@ atol = 1.0e-5 env_conv1, = leading_boundary(CTMRGEnv(psi, ComplexSpace(χ)), psi, ctm_alg) - # do extra iteration to get SVD - env_conv2, info = @constinferred ctmrg_iteration(n, env_conv1, ctm_alg) - env_fix, signs = gauge_fix(env_conv2, env_conv1, ScramblingEnvGauge()) - @test calc_elementwise_convergence(env_conv1, env_fix) ≈ 0 atol = atol - - # fix gauge of SVD - ctm_alg_fix = gauge_fix(ctm_alg, signs, info) + # do extra iteration and gauge fix + env_conv2, = @constinferred ctmrg_iteration(n, env_conv1, ctm_alg) + env_fixed = gauge_fix(env_conv2, env_conv1, ScramblingEnvGauge()) + @test calc_elementwise_convergence(env_conv1, env_fixed) ≈ 0 atol = atol + + # fix gauge of single iteration + signs, corner_phases, edge_phases = + compute_gauge_fix_gauge(env_conv2, env_conv1, ScramblingEnvGauge()) + gauge_fixed_iteration(env::CTMRGEnv) = fix_phases( + ctmrg_iteration(n, env, ctm_alg)[1], + signs, corner_phases, edge_phases, + ) - # do iteration with FixedSVD - env_fixedsvd, = @constinferred ctmrg_iteration(n, env_conv1, ctm_alg_fix) - env_fixedsvd = fix_global_phases(env_fixedsvd, env_conv1) - @test calc_elementwise_convergence(env_conv1, env_fixedsvd) ≈ 0 atol = atol + # do gauge-fixed iteration + env_fixed2 = @constinferred gauge_fixed_iteration(env_conv1) + @test calc_elementwise_convergence(env_conv1, env_fixed2) ≈ 0 atol = atol end # test same thing for C4v CTMRG c4v_algs = [ - (:c4v_qr, QRAdjoint(; fwd_alg = (; alg = :qr))), - (:c4v_eigh, EighAdjoint(; fwd_alg = (; alg = :qriteration))), - (:c4v_eigh, EighAdjoint(; fwd_alg = (; alg = :lanczos))), + (:c4v_qr, (; alg = :Householder)), + (:c4v_eigh, (; alg = :QRIteration)), + (:c4v_eigh, (; alg = :Lanczos)), ] -@testset "$(typeof(decomposition_alg.fwd_alg)) and $projector_alg" for +@testset "$(decomposition_alg.alg) and $projector_alg" for (projector_alg, decomposition_alg) in c4v_algs # initialize states Random.seed!(2394823842) @@ -74,31 +78,35 @@ c4v_algs = [ n = InfiniteSquareNetwork(psi) env₀ = initialize_random_c4v_env(psi, ComplexSpace(χ)) - env_conv1, = leading_boundary(env₀, psi, ctm_alg) + env_conv1, info = leading_boundary(env₀, psi, ctm_alg) # do extra iteration to check gauge fixing env_conv2, info = @constinferred ctmrg_iteration(n, env_conv1, ctm_alg) # CHECK - env_fix, signs = gauge_fix(env_conv2, env_conv1, ScramblingEnvGaugeC4v()) - env_diff = calc_elementwise_convergence(env_conv1, env_fix) + + env_fixed = gauge_fix(env_conv2, env_conv1, ScramblingEnvGaugeC4v()) + env_diff = calc_elementwise_convergence(env_conv1, env_fixed) @info "Diff between iters = $(env_diff)" @test env_diff ≈ 0 atol = atol - if projector_alg == :c4v_eigh # TODO: enable this for :c4v_qr projector - # fix gauge of decomposition - ctm_alg_fix = gauge_fix(ctm_alg, signs, info) - # do iteration with decomposition - env_fixedsvd, = @constinferred ctmrg_iteration(n, env_conv1, ctm_alg_fix) - env_fixedsvd = fix_global_phases(env_fixedsvd, env_conv1) - @test calc_elementwise_convergence(env_conv1, env_fixedsvd) ≈ 0 atol = atol - end + # fix gauge of single iteration + signs, corner_phases, edge_phases = + compute_gauge_fix_gauge(env_conv2, env_conv1, ScramblingEnvGaugeC4v()) + gauge_fixed_iteration(env::CTMRGEnv) = fix_phases( + ctmrg_iteration(n, env, ctm_alg)[1], + signs, corner_phases, edge_phases, + ) + + # do gauge-fixed iteration + env_fixed2 = @constinferred gauge_fixed_iteration(env_conv1) + @test calc_elementwise_convergence(env_conv1, env_fixed2) ≈ 0 atol = atol end -@testset "Element-wise consistency of :sdd and :iterative" begin +@testset "Element-wise consistency of :DivideAndConquer and :iterative" begin ctm_alg_iter = SimultaneousCTMRG(; maxiter = 200, - decomposition_alg = SVDAdjoint(; fwd_alg = (; alg = :iterative, krylovdim = χ + 10)), + decomposition_alg = (; alg = :iterative, krylovdim = χ + 10), ) - ctm_alg_full = SimultaneousCTMRG(; decomposition_alg = SVDAdjoint(; fwd_alg = (; alg = :sdd))) + ctm_alg_full = SimultaneousCTMRG(; decomposition_alg = (; alg = :DivideAndConquer)) # initialize states Random.seed!(91283219347) @@ -107,26 +115,34 @@ end env₀ = CTMRGEnv(psi, ComplexSpace(χ)) env_conv1, = leading_boundary(env₀, psi, ctm_alg_iter) - # do extra iteration to get SVD - env_conv2_iter, info_iter = ctmrg_iteration(n, env_conv1, ctm_alg_iter) - env_fix_iter, signs_iter = gauge_fix(env_conv2_iter, env_conv1, ScramblingEnvGauge()) + # do extra iteration to get gauge fixing + env_conv2_iter, info_iter = @constinferred ctmrg_iteration(n, env_conv1, ctm_alg_iter) + env_fix_iter = gauge_fix(env_conv2_iter, env_conv1, ScramblingEnvGauge()) @test calc_elementwise_convergence(env_conv1, env_fix_iter) ≈ 0 atol = atol - env_conv2_full, info_full = ctmrg_iteration(n, env_conv1, ctm_alg_full) - env_fix_full, signs_full = gauge_fix(env_conv2_full, env_conv1, ScramblingEnvGauge()) + env_conv2_full, info_full = @constinferred ctmrg_iteration(n, env_conv1, ctm_alg_full) + env_fix_full = gauge_fix(env_conv2_full, env_conv1, ScramblingEnvGauge()) @test calc_elementwise_convergence(env_conv1, env_fix_full) ≈ 0 atol = atol - # fix gauge of SVD - ctm_alg_fix_iter = gauge_fix(ctm_alg_iter, signs_iter, info_iter) - ctm_alg_fix_full = gauge_fix(ctm_alg_full, signs_full, info_full) + # fix gauge of single iteration + signs_iter, corner_phases_iter, edge_phases_iter = + compute_gauge_fix_gauge(env_conv2_iter, env_conv1, ScramblingEnvGauge()) + gauge_fixed_iteration_iter(env::CTMRGEnv) = fix_phases( + ctmrg_iteration(n, env, ctm_alg_iter)[1], + signs_iter, corner_phases_iter, edge_phases_iter, + ) + signs_full, corner_phases_full, edge_phases_full = + compute_gauge_fix_gauge(env_conv2_full, env_conv1, ScramblingEnvGauge()) + gauge_fixed_iteration_full(env::CTMRGEnv) = fix_phases( + ctmrg_iteration(n, env, ctm_alg_full)[1], + signs_full, corner_phases_full, edge_phases_full, + ) - # do iteration with FixedSVD - env_fixedsvd_iter, = ctmrg_iteration(n, env_conv1, ctm_alg_fix_iter) - env_fixedsvd_iter = fix_global_phases(env_fixedsvd_iter, env_conv1) + # do gauge-fixed iteration + env_fixedsvd_iter = @constinferred gauge_fixed_iteration_iter(env_conv1) @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(n, env_conv1, ctm_alg_fix_full) - env_fixedsvd_full = fix_global_phases(env_fixedsvd_full, env_conv1) + env_fixedsvd_full = @constinferred gauge_fixed_iteration_full(env_conv1) @test calc_elementwise_convergence(env_conv1, env_fixedsvd_full) ≈ 0 atol = atol # check matching decompositions @@ -145,11 +161,13 @@ end end @test svalues_check + # gauge-fix the isometries using computed relative signs + U_iter_fix, V_iter_fix = fix_relative_phases(info_iter.U, info_iter.V, signs_iter) + U_full_fix, V_full_fix = fix_relative_phases(info_full.U, info_full.V, signs_full) + # check normalization of U's and V's - salg_fix_iter = ctm_alg_fix_iter.projector_alg.decomposition_alg.fwd_alg - salg_fix_full = ctm_alg_fix_full.projector_alg.decomposition_alg.fwd_alg - Us = [info_iter.U, salg_fix_iter.U, info_full.U, salg_fix_full.U] - Vs = [info_iter.V, salg_fix_iter.V, info_full.V, salg_fix_full.V] + Us = [info_iter.U, U_iter_fix, info_full.U, U_full_fix] + Vs = [info_iter.V, V_iter_fix, info_full.V, V_full_fix] for (U, V) in zip(Us, Vs) U_check = all(U) do u uu = u' * u diff --git a/test/ctmrg/flavors.jl b/test/ctmrg/flavors.jl index fe4588dca..37614ab02 100644 --- a/test/ctmrg/flavors.jl +++ b/test/ctmrg/flavors.jl @@ -12,8 +12,8 @@ D = 2 unitcells = [(1, 1), (3, 4)] projector_algs_asymm = [:halfinfinite, :fullinfinite] projector_algs_c4v = [ - (:c4v_qr, :qr), - (:c4v_eigh, :qriteration), (:c4v_eigh, :lanczos), + (:c4v_qr, :Householder), + (:c4v_eigh, :QRIteration), (:c4v_eigh, :Lanczos), ] Ts = [Float64, ComplexF64] @@ -81,7 +81,7 @@ end env₀ = initialize_random_c4v_env(peps, Venv) env, = leading_boundary( env₀, peps; alg = :c4v, projector_alg, - decomposition_alg = (; fwd_alg = (; alg = decomp_alg)) + decomposition_alg = (; alg = decomp_alg) ) @test env isa CTMRGEnv end diff --git a/test/ctmrg/gaugefix.jl b/test/ctmrg/gaugefix.jl index 6199d7ac2..fe668a4df 100644 --- a/test/ctmrg/gaugefix.jl +++ b/test/ctmrg/gaugefix.jl @@ -71,7 +71,7 @@ end n = InfiniteSquareNetwork(psi) env, = leading_boundary(env_pre, psi, alg) env′, = ctmrg_iteration(n, env, alg) - env_fixed, = gauge_fix(env′, env, gauge_alg) + env_fixed = gauge_fix(env′, env, gauge_alg) env_diff = calc_elementwise_convergence(env, env_fixed) @info "Diff between iters = $(env_diff)" @test env_diff ≈ 0 atol = atol @@ -88,7 +88,7 @@ end n = InfiniteSquareNetwork(psi) env, = leading_boundary(env_pre, psi, alg) env′, = ctmrg_iteration(n, env, alg) - env_fixed, = gauge_fix(env′, env, gauge_alg) + env_fixed = gauge_fix(env′, env, gauge_alg) env_diff = calc_elementwise_convergence(env, env_fixed) @info "Diff between iters = $(env_diff)" @test env_diff ≈ 0 atol = atol diff --git a/test/ctmrg/jacobian_real_linear.jl b/test/ctmrg/jacobian_real_linear.jl index 62d1f6062..0674658bc 100644 --- a/test/ctmrg/jacobian_real_linear.jl +++ b/test/ctmrg/jacobian_real_linear.jl @@ -4,19 +4,15 @@ using Accessors using Zygote using TensorKit, KrylovKit, PEPSKit using PEPSKit: - ctmrg_iteration, fix_relative_phases, fix_global_phases, ScramblingEnvGauge + ctmrg_iteration, compute_gauge_fix_gauge, fix_phases, ScramblingEnvGauge algs = [ (:fixed, SimultaneousCTMRG(; projector_alg = :halfinfinite)), - (:diffgauge, SequentialCTMRG(; projector_alg = :halfinfinite)), - (:diffgauge, SimultaneousCTMRG(; projector_alg = :halfinfinite)), - # TODO: FullInfiniteProjector errors since even real_err_∂A, real_err_∂x are finite? - # (:fixed, SimultaneousCTMRG(; projector_alg=FullInfiniteProjector)), - # (:diffgauge, SequentialCTMRG(; projector_alg=FullInfiniteProjector)), - # (:diffgauge, SimultaneousCTMRG(; projector_alg=FullInfiniteProjector)), + (:fixed, SimultaneousCTMRG(; projector_alg = :fullinfinite)), # TODO: why are the errors quite a bit larger for :fullinfinite? ] Dbond, χenv = 2, 16 alg_gauge = ScramblingEnvGauge() +errtol = 1.0e-3 @testset "$iterscheme and $ctm_alg" for (iterscheme, ctm_alg) in algs Random.seed!(123521938519) @@ -26,16 +22,11 @@ alg_gauge = ScramblingEnvGauge() # follow code of _rrule if iterscheme == :fixed env_conv, info = ctmrg_iteration(InfiniteSquareNetwork(state), env, ctm_alg) - env_fixed, signs = gauge_fix(env_conv, env, alg_gauge) - alg_fixed = gauge_fix(ctm_alg, signs, info) + signs, corner_phases, edge_phases = compute_gauge_fix_gauge(env_conv, env, alg_gauge) - _, env_vjp = pullback(state, env_fixed) do A, x - e, = PEPSKit.ctmrg_iteration(InfiniteSquareNetwork(A), x, alg_fixed) - return PEPSKit.fix_global_phases(e, x) - end - elseif iterscheme == :diffgauge - _, env_vjp = pullback(state, env) do A, x - return gauge_fix(ctmrg_iteration(InfiniteSquareNetwork(A), x, ctm_alg)[1], x, alg_gauge)[1] + _, env_vjp = pullback(state, env_conv) do A, x + e, = ctmrg_iteration(InfiniteSquareNetwork(A), x, ctm_alg) + return fix_phases(e, signs, corner_phases, edge_phases) end end @@ -53,8 +44,8 @@ alg_gauge = ScramblingEnvGauge() complex_err_∂A = norm(scale(∂f∂A(env_in), α_complex) - ∂f∂A(scale(env_in, α_complex))) complex_err_∂x = norm(scale(∂f∂x(env_in), α_complex) - ∂f∂x(scale(env_in, α_complex))) - @test real_err_∂A < 1.0e-9 - @test real_err_∂x < 1.0e-9 + @test real_err_∂A < errtol + @test real_err_∂x < errtol @test complex_err_∂A > 1.0e-3 @test complex_err_∂x > 1.0e-3 end diff --git a/test/ctmrg/pepo.jl b/test/ctmrg/pepo.jl index 5b52bd367..4bd22a60a 100644 --- a/test/ctmrg/pepo.jl +++ b/test/ctmrg/pepo.jl @@ -87,7 +87,7 @@ end ctm_alg = SimultaneousCTMRG(; maxiter = 150, tol = 1.0e-8, verbosity = 2) alg_rrule = EigSolver(; solver_alg = KrylovKit.Arnoldi(; maxiter = 30, tol = 1.0e-6, eager = true), - iterscheme = :diffgauge, + iterscheme = :fixed, ) opt_alg = LBFGS(32; maxiter = 50, gradtol = 1.0e-5, verbosity = 3) function pepo_retract(x, η, α) diff --git a/test/ctmrg/unitcell.jl b/test/ctmrg/unitcell.jl index 89aa76280..1b83de2ef 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_iteration, ScramblingEnvGauge +using PEPSKit: _prev, _next, ctmrg_iteration, compute_gauge_fix_gauge, ScramblingEnvGauge using TensorKit # settings @@ -39,14 +39,11 @@ function test_unitcell( @test expectation_value(peps, random_op, env′) isa Number # test if gauge fixing routines run through - _, signs = gauge_fix(env″, env′, ScramblingEnvGauge()) + signs, corner_phases, edge_phases = compute_gauge_fix_gauge(env″, env′, ScramblingEnvGauge()) @test signs isa Array - return if ctm_alg isa SimultaneousCTMRG # also test :fixed mode gauge fixing for simultaneous CTMRG - svd_alg_fixed_full = gauge_fix(SVDAdjoint(; fwd_alg = (; alg = :sdd)), signs, info) - svd_alg_fixed_iter = gauge_fix(SVDAdjoint(; fwd_alg = (; alg = :iterative)), signs, info) - @test svd_alg_fixed_full isa SVDAdjoint - @test svd_alg_fixed_iter isa SVDAdjoint - end + @test corner_phases isa Array + @test edge_phases isa Array + return nothing end @testset "Random Cartesian spaces with $ctm_alg" for ctm_alg in ctm_algs diff --git a/test/examples/bose_hubbard.jl b/test/examples/bose_hubbard.jl index 3b3a16ec6..3d7161bf6 100644 --- a/test/examples/bose_hubbard.jl +++ b/test/examples/bose_hubbard.jl @@ -27,7 +27,7 @@ Venv = U1Space(0 => 6, 1 => 4, -1 => 4, 2 => 2, -2 => 2) # algorithms boundary_alg = (; tol = 1.0e-8, alg = :simultaneous, verbosity = 2, trunc = (; alg = :fixedspace)) -gradient_alg = (; tol = 1.0e-6, maxiter = 10, alg = :eigsolver, iterscheme = :diffgauge) +gradient_alg = (; tol = 1.0e-6, maxiter = 10, alg = :eigsolver, iterscheme = :fixed) optimizer_alg = (; tol = 1.0e-4, alg = :lbfgs, verbosity = 3, maxiter = 25, ls_maxiter = 2, ls_maxfg = 2) reuse_env = true diff --git a/test/examples/j1j2_model.jl b/test/examples/j1j2_model.jl index b155d5264..3ab11d6cf 100644 --- a/test/examples/j1j2_model.jl +++ b/test/examples/j1j2_model.jl @@ -19,7 +19,7 @@ env₀, = leading_boundary(CTMRGEnv(peps₀, ComplexSpace(χenv)), peps₀) # find fixedpoint peps, env, E, = fixedpoint( H, peps₀, env₀; - tol = 1.0e-3, gradient_alg = (; iterscheme = :diffgauge), symmetrization = RotateReflect(), + tol = 1.0e-3, gradient_alg = (; iterscheme = :fixed), symmetrization = RotateReflect(), ) ξ_h, ξ_v, = correlation_length(peps, env) diff --git a/test/gradients/c4v_ctmrg_gradients.jl b/test/gradients/c4v_ctmrg_gradients.jl index 6b0b2f9ad..a2fded178 100644 --- a/test/gradients/c4v_ctmrg_gradients.jl +++ b/test/gradients/c4v_ctmrg_gradients.jl @@ -24,8 +24,7 @@ ctmrg_verbosity = 1 ctmrg_algs = [[:c4v]] projector_algs = [[:c4v_eigh, :c4v_qr]] decomposition_rrule_algs = [[:full, :trunc, :qr]] -gradient_algs = [[nothing, :geomsum, :manualiter, :linsolver, :eigsolver]] -gradient_iterschemes = [[:fixed, :diffgauge]] +gradient_algs = [[nothing, :geomsum, :manualiter, :linsolver, :eigsolver]] # they all use :fixed mode by default (except for nothing) steps = -0.01:0.005:0.01 # record which rrule alg is compatible with which projector alg @@ -35,22 +34,9 @@ allowed_rrule_algs = Dict( ) # be selective on which configurations to test the naive gradient for -naive_gradient_combinations = [ - (:c4v, :c4v_eigh, :full, :fixed), - (:c4v, :c4v_eigh, :full, :diffgauge), - (:c4v, :c4v_qr, :qr, :fixed), - (:c4v, :c4v_qr, :qr, :diffgauge), -] +naive_gradient_combinations = [(:c4v, :c4v_eigh, :full), (:c4v, :c4v_qr, :qr)] naive_gradient_done = Set() -# :diffgauge iterscheme is incompatible with :c4v_eigh projector algorithm -function _check_disallowed_combination( - ctmrg_alg, projector_alg, decomposition_rrule_alg, gradient_alg, gradient_iterscheme - ) - projector_alg == :c4v_eigh && gradient_iterscheme == :diffgauge && return true - return false -end - ## Tests # ------ @testset "AD C4v CTMRG energy gradients for $(names[i]) model" verbose = true for i in @@ -64,28 +50,15 @@ end palgs = projector_algs[i] dalgs = decomposition_rrule_algs[i] galgs = gradient_algs[i] - gischemes = gradient_iterschemes[i] - @testset "ctmrg_alg=:$ctmrg_alg, projector_alg=:$projector_alg, decomposition_rrule_alg=:$decomposition_rrule_alg, gradient_alg=:$gradient_alg, gradient_iterscheme=:$gradient_iterscheme" for ( - ctmrg_alg, projector_alg, decomposition_rrule_alg, gradient_alg, gradient_iterscheme, + @testset "ctmrg_alg=:$ctmrg_alg, projector_alg=:$projector_alg, decomposition_rrule_alg=:$decomposition_rrule_alg and gradient_alg=:$gradient_alg" for ( + ctmrg_alg, projector_alg, decomposition_rrule_alg, gradient_alg, ) in Iterators.product( - calgs, palgs, dalgs, galgs, gischemes + calgs, palgs, dalgs, galgs ) - # filter disallowed algorithm combinations - if _check_disallowed_combination( - ctmrg_alg, projector_alg, decomposition_rrule_alg, gradient_alg, gradient_iterscheme - ) - # but verify that its use would throw an error - @test_throws ArgumentError PEPSOptimize(; - boundary_alg = (; alg = ctmrg_alg, projector_alg, decomposition_alg = (; rrule_alg = (; alg = decomposition_rrule_alg))), - gradient_alg = (; alg = gradient_alg, iterscheme = gradient_iterscheme, tol = gradtol), - ) - continue - end - # check for allowed algorithm combinations when testing naive gradient if isnothing(gradient_alg) - combo = (ctmrg_alg, projector_alg, decomposition_rrule_alg, gradient_iterscheme) + combo = (ctmrg_alg, projector_alg, decomposition_rrule_alg) combo in naive_gradient_combinations || continue combo in naive_gradient_done && continue push!(naive_gradient_done, combo) @@ -94,7 +67,16 @@ end # check for allowed combinations of projector alg and decomposition rrule alg decomposition_rrule_alg in allowed_rrule_algs[projector_alg] || continue - @info "optimtest of ctmrg_alg=:$ctmrg_alg, projector_alg=:$projector_alg, decomposition_rrule_alg=:$decomposition_rrule_alg, gradient_alg=:$gradient_alg and gradient_iterscheme=:$gradient_iterscheme on $(names[i])" + # construct appropriate decomposition struct to pass custom rrule alg + decomposition_alg = if projector_alg == :c4v_eigh + EighAdjoint(; rrule_alg = (; alg = decomposition_rrule_alg)) + elseif projector_alg == :c4v_qr + QRAdjoint(; rrule_alg = (; alg = decomposition_rrule_alg)) + else + error("unknown projector alg: $projector_alg") + end + + @info "optimtest of ctmrg_alg=:$ctmrg_alg, projector_alg=:$projector_alg, decomposition_rrule_alg=:$decomposition_rrule_alg and gradient_alg=:$gradient_alg on $(names[i])" Random.seed!(sd) dir = InfinitePEPS(Pspace, Vspace) psi = InfinitePEPS(Pspace, Vspace) @@ -105,13 +87,13 @@ end alg = ctmrg_alg, verbosity = ctmrg_verbosity, projector_alg = projector_alg, - decomposition_alg = (; rrule_alg = (; alg = decomposition_rrule_alg)), + decomposition_alg, ) # instantiate because hook_pullback doesn't go through the keyword selector... concrete_gradient_alg = if isnothing(gradient_alg) nothing # TODO: add this to the PEPSKit.GradMode selector? else - PEPSKit.GradMode(; alg = gradient_alg, tol = gradtol, iterscheme = gradient_iterscheme) + PEPSKit.GradMode(; alg = gradient_alg, tol = gradtol) end env0 = PEPSKit.initialize_random_c4v_env(psi, Espace) env, = leading_boundary(env0, psi, contrete_ctmrg_alg) diff --git a/test/gradients/ctmrg_gradients.jl b/test/gradients/ctmrg_gradients.jl index ab4b0915e..df847aea7 100644 --- a/test/gradients/ctmrg_gradients.jl +++ b/test/gradients/ctmrg_gradients.jl @@ -20,14 +20,14 @@ gradtol = 1.0e-4 ctmrg_verbosity = 0 ctmrg_algs = [[:sequential, :simultaneous], [:sequential, :simultaneous]] projector_algs = [[:halfinfinite, :fullinfinite], [:halfinfinite, :fullinfinite]] -svd_rrule_algs = [[:full, :arnoldi], [:full, :arnoldi]] +svd_rrule_algs = [[:full, :trunc, :arnoldi], [:full, :arnoldi]] gradient_algs = [ [nothing, :geomsum, :manualiter, :linsolver, :eigsolver], [:geomsum, :manualiter, :linsolver, :eigsolver], ] -gradient_iterschemes = [[:fixed, :diffgauge], [:fixed, :diffgauge]] steps = -0.01:0.005:0.01 +# don't check naive AD gradients for all algorithm combinations, since it's slow naive_gradient_combinations = [ (:simultaneous, :halfinfinite, :full), (:simultaneous, :fullinfinite, :full), @@ -37,9 +37,9 @@ naive_gradient_done = Set() # :fixed iterscheme is incompatible with sequential CTMRG function _check_disallowed_combination( - ctmrg_alg, projector_alg, decomposition_rrule_alg, gradient_alg, gradient_iterscheme + ctmrg_alg, projector_alg, decomposition_rrule_alg, gradient_alg ) - ctmrg_alg == :sequential && gradient_iterscheme == :fixed && return true + ctmrg_alg == :sequential && !isnothing(gradient_alg) && return true return false end @@ -57,21 +57,20 @@ end palgs = projector_algs[i] salgs = svd_rrule_algs[i] galgs = gradient_algs[i] - gischemes = gradient_iterschemes[i] - @testset "ctmrg_alg=:$ctmrg_alg, projector_alg=:$projector_alg, svd_rrule_alg=:$svd_rrule_alg, gradient_alg=:$gradient_alg, gradient_iterscheme=:$gradient_iterscheme" for ( - ctmrg_alg, projector_alg, svd_rrule_alg, gradient_alg, gradient_iterscheme, + @testset "ctmrg_alg=:$ctmrg_alg, projector_alg=:$projector_alg, svd_rrule_alg=:$svd_rrule_alg and gradient_alg=:$gradient_alg" for ( + ctmrg_alg, projector_alg, svd_rrule_alg, gradient_alg, ) in Iterators.product( - calgs, palgs, salgs, galgs, gischemes + calgs, palgs, salgs, galgs ) # filter disallowed algorithm combinations if _check_disallowed_combination( - ctmrg_alg, projector_alg, svd_rrule_alg, gradient_alg, gradient_iterscheme + ctmrg_alg, projector_alg, svd_rrule_alg, gradient_alg ) # but verify that its use would throw an error @test_throws ArgumentError PEPSOptimize(; boundary_alg = (; alg = ctmrg_alg, projector_alg, decomposition_alg = (; rrule_alg = (; alg = svd_rrule_alg))), - gradient_alg = (; alg = gradient_alg, iterscheme = gradient_iterscheme, tol = gradtol), + gradient_alg = (; alg = gradient_alg, tol = gradtol, iterscheme = :fixed), ) continue end @@ -84,7 +83,7 @@ end push!(naive_gradient_done, combo) end - @info "optimtest of ctmrg_alg=:$ctmrg_alg, projector_alg=:$projector_alg, svd_rrule_alg=:$svd_rrule_alg, gradient_alg=:$gradient_alg and gradient_iterscheme=:$gradient_iterscheme on $(names[i])" + @info "optimtest of ctmrg_alg=:$ctmrg_alg, projector_alg=:$projector_alg, svd_rrule_alg=:$svd_rrule_alg and gradient_alg=:$gradient_alg on $(names[i])" Random.seed!(42039482030) dir = InfinitePEPS(Pspace, Vspace) psi = InfinitePEPS(Pspace, Vspace) @@ -93,13 +92,13 @@ end alg = ctmrg_alg, verbosity = ctmrg_verbosity, projector_alg = projector_alg, - decomposition_alg = (; rrule_alg = (; alg = svd_rrule_alg)), + decomposition_alg = SVDAdjoint(; rrule_alg = (; alg = svd_rrule_alg)), ) # instantiate because hook_pullback doesn't go through the keyword selector... concrete_gradient_alg = if isnothing(gradient_alg) nothing # TODO: add this to the PEPSKit.GradMode selector? else - PEPSKit.GradMode(; alg = gradient_alg, tol = gradtol, iterscheme = gradient_iterscheme) + PEPSKit.GradMode(; alg = gradient_alg, tol = gradtol) end env, = leading_boundary(CTMRGEnv(psi, Espace), psi, contrete_ctmrg_alg) alphas, fs, dfs1, dfs2 = OptimKit.optimtest( @@ -131,7 +130,7 @@ end Random.seed!(1234) boundary_alg = PEPSKit.CTMRGAlgorithm(; tol = 1.0e-10) - gradient_alg = PEPSKit.GradMode(; alg = :linsolver, tol = 5.0e-8, iterscheme = :fixed) + gradient_alg = PEPSKit.GradMode(; alg = :linsolver, tol = 5.0e-8) function fg((peps, env)) E, g = Zygote.withgradient(peps) do ψ diff --git a/test/runtests.jl b/test/runtests.jl index fb3848f85..2283d9c89 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,165 +1,12 @@ -using SafeTestsets +using ParallelTestRunner +using PEPSKit -# check if user supplied args -pat = r"(?:--group=)(\w+)" -arg_id = findfirst(contains(pat), ARGS) -const GROUP = if isnothing(arg_id) - uppercase(get(ENV, "GROUP", "ALL")) -else - uppercase(only(match(pat, ARGS[arg_id]).captures)) -end +# --fast to indicate a smaller set of tests +args = parse_args(ARGS; custom = ["fast"]) +fast = !isnothing(args.custom["fast"]) -@time begin - if GROUP == "ALL" || GROUP == "TYPES" - @time @safetestset "InfiniteSquareNetwork" begin - include("types/infinitesquarenetwork.jl") - end - @time @safetestset "InfinitePartitionFunction" begin - include("types/infinitepartitionfunction.jl") - end - @time @safetestset "SUWeight" begin - include("types/suweight.jl") - end - @time @safetestset "LocalOperator" begin - include("types/localoperator.jl") - end - @time @safetestset "LocalCircuit" begin - include("types/localcircuit.jl") - end - end - if GROUP == "ALL" || GROUP == "CTMRG" - @time @safetestset "Gauge Fixing" begin - include("ctmrg/gaugefix.jl") - end - @time @safetestset "Unit cell" begin - include("ctmrg/unitcell.jl") - end - @time @safetestset ":fixed CTMRG iteration scheme" begin - include("ctmrg/fixed_iterscheme.jl") - end - @time @safetestset "SUWeight conversion" begin - include("ctmrg/suweight.jl") - end - @time @safetestset "Flavors" begin - include("ctmrg/flavors.jl") - end - @time @safetestset "Jacobian real linearity" begin - include("ctmrg/jacobian_real_linear.jl") - end - @time @safetestset "Partition function" begin - include("ctmrg/partition_function.jl") - end - @time @safetestset "PEPO" begin - include("ctmrg/pepo.jl") - end - @time @safetestset "correlation length" begin - include("ctmrg/correlation_length.jl") - end - @time @safetestset "Contractions" begin - include("ctmrg/contractions.jl") - end - end - if GROUP == "ALL" || GROUP == "BP" - @time @safetestset "Unit cell bond matching" begin - include("bp/unitcell.jl") - end - @time @safetestset "Expectation values" begin - include("bp/expvals.jl") - end - @time @safetestset "Rotation of BPEnv" begin - include("bp/rotation.jl") - end - @time @safetestset "Gauge-fixing iPEPS" begin - include("bp/gaugefix.jl") - end - end - if GROUP == "ALL" || GROUP == "GRADIENTS" - @time @safetestset "CTMRG gradients" begin - include("gradients/ctmrg_gradients.jl") - end - @time @safetestset "C4v CTMRG gradients" begin - include("gradients/c4v_ctmrg_gradients.jl") - end - end - if GROUP == "ALL" || GROUP == "BOUNDARYMPS" - @time @safetestset "VUMPS" begin - include("boundarymps/vumps.jl") - end - end - if GROUP == "ALL" || GROUP == "BONDENV" - @time @safetestset "Iterative optimization after truncation" begin - include("bondenv/bond_truncate.jl") - end - @time @safetestset "Gauge fixing" begin - include("bondenv/benv_gaugefix.jl") - end - @time @safetestset "Exact NTU bond environments" begin - include("bondenv/benv_ntu.jl") - end - @time @safetestset "Full update bond environment" begin - include("bondenv/benv_ctm.jl") - end - end - if GROUP == "ALL" || GROUP == "TIMEEVOL" - @time @safetestset "`timestep` function" begin - include("timeevol/timestep.jl") - end - @time @safetestset "Cluster truncation with projectors" begin - include("timeevol/cluster_projectors.jl") - end - @time @safetestset "Fixed-space and site-dependent truncation" begin - include("timeevol/sitedep_truncation.jl") - end - @time @safetestset "Transverse field Ising model at finite temperature" begin - include("timeevol/tf_ising_finiteT.jl") - end - @time @safetestset "J1-J2 model at finite temperature" begin - include("timeevol/j1j2_finiteT.jl") - end - @time @safetestset "Transverse Field Ising model real-time evolution" begin - include("timeevol/tf_ising_realtime.jl") - end - end - if GROUP == "ALL" || GROUP == "TOOLBOX" - @time @safetestset "Density matrix from double-layer PEPO" begin - include("toolbox/densitymatrices.jl") - end - end - if GROUP == "ALL" || GROUP == "UTILITY" - @time @safetestset "Eigh wrapper" begin - include("utility/eigh_wrapper.jl") - end - @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 - @time @safetestset "Norm-preserving retractions" begin - include("utility/retractions.jl") - end - @time @safetestset "Correlators" begin - include("utility/correlator.jl") - end - end - if GROUP == "ALL" || GROUP == "EXAMPLES" - @time @safetestset "Transverse field Ising model" begin - include("examples/tf_ising.jl") - end - @time @safetestset "Heisenberg model" begin - include("examples/heisenberg.jl") - end - @time @safetestset "J1-J2 model" begin - include("examples/j1j2_model.jl") - end - @time @safetestset "P-wave superconductor" begin - include("examples/pwave.jl") - end - @time @safetestset "U1-symmetric Bose-Hubbard model" begin - include("examples/bose_hubbard.jl") - end - end +const init_code = quote + const fast_tests = $fast end + +ParallelTestRunner.runtests(PEPSKit, args; init_code) diff --git a/test/timeevol/cluster_projectors.jl b/test/timeevol/cluster_projectors.jl index 1276ad2f8..6b86f6ee6 100644 --- a/test/timeevol/cluster_projectors.jl +++ b/test/timeevol/cluster_projectors.jl @@ -6,7 +6,123 @@ using Random import MPSKitModels: hubbard_space using PEPSKit: sdiag_pow, _cluster_truncate!, _flip_virtuals!, _next using MPSKit: GenericMPSTensor, MPSBondTensor -include("cluster_tools.jl") + +# Utility setup +# ------------- +function _contract_left( + M::GenericMPSTensor{S, 4}, sl::DiagonalTensorMap{T, S} + ) where {T <: Number, S <: ElementarySpace} + @assert !isdual(codomain(M, 1)) && !isdual(domain(M, 1)) + M0 = twist(M, filter(ax -> isdual(space(M, ax)), 1:4)) + @tensor sl1[e1; e0] := conj(M[w1; p n s e1]) * sl[w1; w0] * M0[w0; p n s e0] + return sl1 +end +function _contract_left( + M::GenericMPSTensor{S, 4}, ::Nothing + ) where {S <: ElementarySpace} + @assert !isdual(domain(M, 1)) + M0 = twist(M, filter(ax -> isdual(space(M, ax)), 1:4)) + @tensor sl1[e1; e0] := conj(M[w; p n s e1]) * M0[w; p n s e0] + return sl1 +end + +function _contract_right( + M::GenericMPSTensor{S, 4}, sr::DiagonalTensorMap{T, S} + ) where {T <: Number, S <: ElementarySpace} + @assert !isdual(codomain(M, 1)) && !isdual(domain(M, 1)) + M0 = twist(M, filter(ax -> !isdual(space(M, ax)), 2:5)) + @tensor sr1[w0; w1] := M0[w0; p n s e0] * sr[e0; e1] * conj(M[w1; p n s e1]) + return sr1 +end +function _contract_right( + M::GenericMPSTensor{S, 4}, ::Nothing + ) where {S <: ElementarySpace} + @assert !isdual(codomain(M, 1)) + M0 = twist(M, filter(ax -> !isdual(space(M, ax)), 2:5)) + @tensor sr1[w0; w1] := M0[w0; p n s e] * conj(M[w1; p n s e]) + return sr1 +end + +""" +Verify the generalized left/right orthogonal condition +""" +function verify_cluster_orth( + Ms::Vector{T1}, wts::Vector{T2} + ) where {T1 <: GenericMPSTensor{<:ElementarySpace, 4}, T2 <: DiagonalTensorMap} + N = length(Ms) + @assert length(wts) == N - 1 + lorths = fill(false, N - 1) + rorths = fill(false, N - 1) + # left orthogonal + for i in 1:(N - 1) + M, sl0 = Ms[i], wts[i] + sl1 = _contract_left(M, i == 1 ? nothing : wts[i - 1]) + lorths[i] = (normalize(TensorMap(sl0)) ≈ normalize(sl1)) # sl0 is DiagonalTensorMap while sl1 is not + end + # right orthogonal + for i in 2:N + M, sr0 = Ms[i], wts[i - 1] + sr1 = _contract_right(M, i == N ? nothing : wts[i]) + rorths[i - 1] = (normalize(TensorMap(sr0)) ≈ normalize(sr1)) + end + return lorths, rorths +end + +function inner_prod_cluster( + Ms1::Vector{T1}, Ms2::Vector{T2} + ) where { + T1 <: GenericMPSTensor{<:ElementarySpace, 4}, + T2 <: GenericMPSTensor{<:ElementarySpace, 4}, + } + N = length(Ms1) + @assert length(Ms2) == N + # physical spaces are assumed to be non-dual + @assert all(!isdual(space(t, 2)) for t in Ms1) + @assert all(!isdual(space(t, 2)) for t in Ms2) + # not the most efficient implementation + M1, M2 = Ms1[1], deepcopy(Ms2[1]) + for ax in 1:4 + isdual(space(M2, ax)) && twist!(M2, ax) + end + @tensor res[-1 -2] := conj(M1[1 2 3 4; -1]) * M2[1 2 3 4; -2] + for i in 2:(N - 1) + M1, M2 = Ms1[i], deepcopy(Ms2[i]) + for ax in 2:4 + isdual(space(M2, ax)) && twist!(M2, ax) + end + @tensor M[-1 -2; -3 -4] := conj(M1[-1 1 2 3; -3]) * M2[-2 1 2 3; -4] + @tensor res[-1 -2] := res[1 2] * M[1 2; -1 -2] + end + M1, M2 = Ms1[N], deepcopy(Ms2[N]) + for ax in 2:5 + isdual(space(M2, ax)) && twist!(M2, ax) + end + @tensor M[-1 -2] := conj(M1[-1 1 2 3; 4]) * M2[-2 1 2 3; 4] + return @tensor res[1 2] * M[1 2] +end + +function fidelity_cluster( + Ms1::Vector{T1}, Ms2::Vector{T2} + ) where { + T1 <: GenericMPSTensor{<:ElementarySpace, 4}, + T2 <: GenericMPSTensor{<:ElementarySpace, 4}, + } + return abs2(inner_prod_cluster(Ms1, Ms2)) / + (inner_prod_cluster(Ms1, Ms1) * inner_prod_cluster(Ms2, Ms2)) +end + +function mpo_to_gate3(gs::Vector{T}) where {T <: AbstractTensorMap} + #= + -4 -5 -6 + ↓ ↓ ↓ + g1 ←- 1 ←- g2 ←- 2 ←- g3 + ↓ ↓ ↓ + -1 -2 -3 + =# + @assert length(gs) == 3 + @tensor gate[-1 -2 -3; -4 -5 -6] := gs[1][-1 -4 1] * gs[2][1 -2 -5 2] * gs[3][2 -3 -6] + return gate +end Vspaces = [ ( @@ -32,10 +148,10 @@ Vspaces = [ return rand(Vw ⊗ Vphy ⊗ Vns' ⊗ Vns ← Ve) end normalize!.(Ms1, Inf) - flips = [isdual(space(M, 1)) for M in Ms1[2:end]] + flips = [isdual(space(M, 1)) for M in Iterators.drop(Ms1, 1)] # no truncation Ms2 = _flip_virtuals!(deepcopy(Ms1), flips) - truncs = [truncrank(dim(space(M, 1))) for M in Ms2[2:end]] + truncs = [truncrank(dim(space(M, 1))) for M in Iterators.drop(Ms2, 1)] wts2, ϵs, = _cluster_truncate!(Ms2, truncs) @test all((ϵ == 0) for ϵ in ϵs) normalize!.(Ms2, Inf) @@ -68,7 +184,7 @@ end Vw, Ve = Vvirs[i], Vvirs[i + 1] return normalize(rand(Vw ⊗ Vphy ⊗ Vns' ⊗ Vns ← Ve), Inf) end - flips = [isdual(space(M, 1)) for M in Ms1[2:end]] + flips = [isdual(space(M, 1)) for M in Iterators.drop(Ms1, 1)] unit = id(Vphy) gate = reduce(⊗, fill(unit, 3)) gs = PEPSKit.gate_to_mpo(gate) @@ -85,7 +201,7 @@ end Vw, Ve = Vvirs[i], Vvirs[i + 1] return normalize(rand(Vw ⊗ Vphy ⊗ Vphy' ⊗ Vns' ⊗ Vns ← Ve), Inf) end - flips = [isdual(space(M, 1)) for M in Ms1[2:end]] + flips = [isdual(space(M, 1)) for M in Iterators.drop(Ms1, 1)] unit = id(Vphy) gate = reduce(⊗, fill(unit, 3)) gs = PEPSKit.gate_to_mpo(gate) diff --git a/test/timeevol/cluster_tools.jl b/test/timeevol/cluster_tools.jl deleted file mode 100644 index efcdfc770..000000000 --- a/test/timeevol/cluster_tools.jl +++ /dev/null @@ -1,114 +0,0 @@ -function _contract_left( - M::GenericMPSTensor{S, 4}, sl::DiagonalTensorMap{T, S} - ) where {T <: Number, S <: ElementarySpace} - @assert !isdual(codomain(M, 1)) && !isdual(domain(M, 1)) - M0 = twist(M, filter(ax -> isdual(space(M, ax)), 1:4)) - @tensor sl1[e1; e0] := conj(M[w1; p n s e1]) * sl[w1; w0] * M0[w0; p n s e0] - return sl1 -end -function _contract_left( - M::GenericMPSTensor{S, 4}, ::Nothing - ) where {S <: ElementarySpace} - @assert !isdual(domain(M, 1)) - M0 = twist(M, filter(ax -> isdual(space(M, ax)), 1:4)) - @tensor sl1[e1; e0] := conj(M[w; p n s e1]) * M0[w; p n s e0] - return sl1 -end - -function _contract_right( - M::GenericMPSTensor{S, 4}, sr::DiagonalTensorMap{T, S} - ) where {T <: Number, S <: ElementarySpace} - @assert !isdual(codomain(M, 1)) && !isdual(domain(M, 1)) - M0 = twist(M, filter(ax -> !isdual(space(M, ax)), 2:5)) - @tensor sr1[w0; w1] := M0[w0; p n s e0] * sr[e0; e1] * conj(M[w1; p n s e1]) - return sr1 -end -function _contract_right( - M::GenericMPSTensor{S, 4}, ::Nothing - ) where {S <: ElementarySpace} - @assert !isdual(codomain(M, 1)) - M0 = twist(M, filter(ax -> !isdual(space(M, ax)), 2:5)) - @tensor sr1[w0; w1] := M0[w0; p n s e] * conj(M[w1; p n s e]) - return sr1 -end - -""" -Verify the generalized left/right orthogonal condition -""" -function verify_cluster_orth( - Ms::Vector{T1}, wts::Vector{T2} - ) where {T1 <: GenericMPSTensor{<:ElementarySpace, 4}, T2 <: DiagonalTensorMap} - N = length(Ms) - @assert length(wts) == N - 1 - lorths = fill(false, N - 1) - rorths = fill(false, N - 1) - # left orthogonal - for i in 1:(N - 1) - M, sl0 = Ms[i], wts[i] - sl1 = _contract_left(M, i == 1 ? nothing : wts[i - 1]) - lorths[i] = (normalize(TensorMap(sl0)) ≈ normalize(sl1)) # sl0 is DiagonalTensorMap while sl1 is not - end - # right orthogonal - for i in 2:N - M, sr0 = Ms[i], wts[i - 1] - sr1 = _contract_right(M, i == N ? nothing : wts[i]) - rorths[i - 1] = (normalize(TensorMap(sr0)) ≈ normalize(sr1)) - end - return lorths, rorths -end - -function inner_prod_cluster( - Ms1::Vector{T1}, Ms2::Vector{T2} - ) where { - T1 <: GenericMPSTensor{<:ElementarySpace, 4}, - T2 <: GenericMPSTensor{<:ElementarySpace, 4}, - } - N = length(Ms1) - @assert length(Ms2) == N - # physical spaces are assumed to be non-dual - @assert all(!isdual(space(t, 2)) for t in Ms1) - @assert all(!isdual(space(t, 2)) for t in Ms2) - # not the most efficient implementation - M1, M2 = Ms1[1], deepcopy(Ms2[1]) - for ax in 1:4 - isdual(space(M2, ax)) && twist!(M2, ax) - end - @tensor res[-1 -2] := conj(M1[1 2 3 4; -1]) * M2[1 2 3 4; -2] - for i in 2:(N - 1) - M1, M2 = Ms1[i], deepcopy(Ms2[i]) - for ax in 2:4 - isdual(space(M2, ax)) && twist!(M2, ax) - end - @tensor M[-1 -2; -3 -4] := conj(M1[-1 1 2 3; -3]) * M2[-2 1 2 3; -4] - @tensor res[-1 -2] := res[1 2] * M[1 2; -1 -2] - end - M1, M2 = Ms1[N], deepcopy(Ms2[N]) - for ax in 2:5 - isdual(space(M2, ax)) && twist!(M2, ax) - end - @tensor M[-1 -2] := conj(M1[-1 1 2 3; 4]) * M2[-2 1 2 3; 4] - return @tensor res[1 2] * M[1 2] -end - -function fidelity_cluster( - Ms1::Vector{T1}, Ms2::Vector{T2} - ) where { - T1 <: GenericMPSTensor{<:ElementarySpace, 4}, - T2 <: GenericMPSTensor{<:ElementarySpace, 4}, - } - return abs2(inner_prod_cluster(Ms1, Ms2)) / - (inner_prod_cluster(Ms1, Ms1) * inner_prod_cluster(Ms2, Ms2)) -end - -function mpo_to_gate3(gs::Vector{T}) where {T <: AbstractTensorMap} - #= - -4 -5 -6 - ↓ ↓ ↓ - g1 ←- 1 ←- g2 ←- 2 ←- g3 - ↓ ↓ ↓ - -1 -2 -3 - =# - @assert length(gs) == 3 - @tensor gate[-1 -2 -3; -4 -5 -6] := gs[1][-1 -4 1] * gs[2][1 -2 -5 2] * gs[3][2 -3 -6] - return gate -end diff --git a/test/timeevol/sitedep_truncation.jl b/test/timeevol/sitedep_truncation.jl index 0efde3c5c..b61b50d95 100644 --- a/test/timeevol/sitedep_truncation.jl +++ b/test/timeevol/sitedep_truncation.jl @@ -2,7 +2,7 @@ using Test using Random using TensorKit using PEPSKit -using PEPSKit: _is_bipartite +using PEPSKit: _is_bipartite, _get_fixedspacetrunc elt = Float64 Nr, Nc = 2, 2 @@ -20,11 +20,23 @@ Ves2 = [ U1Space(0 => 1, 1 => 1, -1 => 2) U1Space(0 => 1, 1 => 2, -1 => 1)' ] Venv = U1Space(0 => 2, 1 => 1, -1 => 1) +Random.seed!(48736) states = ( InfinitePEPS(randn, elt, Vps, Vns, Ves1), InfinitePEPO(randn, elt, Vps, Vns, Ves2), ) +@testset "Rotation of SiteDependentTruncation" begin + state = states[1] + for f in (rotl90, rotr90, rot180) + trunc1 = f(_get_fixedspacetrunc(state)) + trunc2 = _get_fixedspacetrunc(f(state)) + @test all( + t1.space == t2.space for (t1, t2) in zip(trunc1.truncs, trunc2.truncs) + ) + end +end + @testset "Simple update on $(typeof(state0).name.wrapper), bipartite = $(bipartite)" for (state0, bipartite) in Iterators.product(states, (true, false)) J2 = 0.5 diff --git a/test/utility/eigh_wrapper.jl b/test/utility/eigh_wrapper.jl index 4533897b2..d7ff4ed5f 100644 --- a/test/utility/eigh_wrapper.jl +++ b/test/utility/eigh_wrapper.jl @@ -6,9 +6,12 @@ using ChainRulesCore, Zygote using Accessors using PEPSKit +using MatrixAlgebraKit: TruncatedAlgorithm, diagview + # Gauge-invariant loss function function lossfun(A, alg, R = randn(space(A)), trunc = notrunc()) - D, V, = eigh_trunc(A, alg; trunc) + alg = @set alg.fwd_alg = TruncatedAlgorithm(alg.fwd_alg, trunc) + D, V, = eigh_trunc(A, alg) return real(dot(R, V * V')) + dot(D, D) # Overlap with random tensor R is gauge-invariant and differentiable end @@ -23,9 +26,9 @@ r = 0.5 * (r + r') # make r Hermitian R = randn(space(r)) R = 0.5 * (R + R') -full_alg = EighAdjoint(; fwd_alg = (; alg = :qriteration), rrule_alg = (; alg = :full)) -trunc_alg = EighAdjoint(; fwd_alg = (; alg = :qriteration), rrule_alg = (; alg = :trunc)) -iter_alg = EighAdjoint(; fwd_alg = (; alg = :lanczos), rrule_alg = (; alg = :trunc)) +full_alg = EighAdjoint(; fwd_alg = (; alg = :QRIteration), rrule_alg = (; alg = :full)) +trunc_alg = EighAdjoint(; fwd_alg = (; alg = :QRIteration), rrule_alg = (; alg = :trunc)) +iter_alg = EighAdjoint(; fwd_alg = (; alg = :Lanczos), rrule_alg = (; alg = :trunc)) @testset "Non-truncated eigh" begin l_full, g_full = withgradient(A -> lossfun(A, full_alg, R), r) @@ -114,7 +117,13 @@ end @testset "Truncated symmetric eigh broadening for $(alg.rrule_alg)" for alg in [full_alg, trunc_alg] d, v = eigh_full(symm_r) - d.data[1:2:symm_m] .= d.data[2:2:symm_m] # make every eigenvalue two-fold degenerate + # make every singular value in the 0-sector three-fold degenerate + b0 = diagview(block(d, Z2Irrep(0))) + b0[1:3:symm_m] .= b0[3:3:symm_m] + b0[2:3:symm_m] .= b0[3:3:symm_m] + # make every singular value in the 1-sector two-fold degenerate + b1 = diagview(block(d, Z2Irrep(1))) + b1[1:2:symm_n] .= b1[2:2:symm_n] symm_r_degen = v * d * v' no_broadening_no_cutoff_alg = @set alg.rrule_alg.degeneracy_atol = 1.0e-30 diff --git a/test/utility/svd_wrapper.jl b/test/utility/svd_wrapper.jl index 411b05d9a..95c0208df 100644 --- a/test/utility/svd_wrapper.jl +++ b/test/utility/svd_wrapper.jl @@ -5,11 +5,13 @@ using TensorKit using ChainRulesCore, Zygote using Accessors using PEPSKit -# using PEPSKit: HalfInfiniteEnv + +using MatrixAlgebraKit: TruncatedAlgorithm, diagview # Gauge-invariant loss function function lossfun(A, alg, R = randn(space(A)), trunc = notrunc()) - U, S, V, = svd_trunc(A, alg; trunc) + alg = @set alg.fwd_alg = TruncatedAlgorithm(alg.fwd_alg, trunc) + U, S, V, = svd_trunc(A, alg) return real(dot(R, U * V)) + dot(S, S) # Overlap with random tensor R is gauge-invariant and differentiable, also for m≠n end @@ -18,7 +20,7 @@ m, n = 20, 30 χ = 12 trunc = truncspace(ℂ^χ) rtol = 1.0e-9 -Random.seed!(123456789) +Random.seed!(12345678) r = randn(dtype, ℂ^m, ℂ^n) R = randn(space(r)) @@ -111,7 +113,13 @@ end @testset "Truncated symmetric SVD broadening for $(alg.rrule_alg)" for alg in [full_alg, trunc_alg] u, s, v, = svd_compact(symm_r) - s.data[1:2:m] .= s.data[2:2:m] # make every singular value two-fold degenerate + # make every singular value in the 0-sector three-fold degenerate + b0 = diagview(block(s, Z2Irrep(0))) + b0[1:3:symm_m] .= b0[3:3:symm_m] + b0[2:3:symm_m] .= b0[3:3:symm_m] + # make every singular value in the 1-sector two-fold degenerate + b1 = diagview(block(s, Z2Irrep(1))) + b1[1:2:symm_n] .= b1[2:2:symm_n] symm_r_degen = u * s * v no_broadening_no_cutoff_alg = @set alg.rrule_alg.degeneracy_atol = 1.0e-30 From 511a6541c34b1affc3132497641990411bd7eb8d Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Mon, 4 May 2026 10:19:07 +0800 Subject: [PATCH 31/41] Fix formatting --- src/algorithms/time_evolution/ntupdate3site.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/algorithms/time_evolution/ntupdate3site.jl b/src/algorithms/time_evolution/ntupdate3site.jl index 885c61136..75a8ff95e 100644 --- a/src/algorithms/time_evolution/ntupdate3site.jl +++ b/src/algorithms/time_evolution/ntupdate3site.jl @@ -1,6 +1,6 @@ function _get_cluster_permute( - state::InfiniteState, sites::Vector{CartesianIndex{2}} -) + state::InfiniteState, sites::Vector{CartesianIndex{2}} + ) Ms, _, perms = _get_cluster(state, sites) Np = (state isa InfinitePEPS) ? Val(1) : Val(2) invperms = map(p -> _inv_mpo_perm(p, Np), perms) From c60ab31f1240309e420d323680b456bafd6e66ef Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Mon, 4 May 2026 12:40:42 +0800 Subject: [PATCH 32/41] Add Printf to test environment --- test/Project.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/test/Project.toml b/test/Project.toml index d23ab3142..7067f27f3 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -13,6 +13,7 @@ MatrixAlgebraKit = "6c742aac-3347-4629-af66-fc926824e5e4" OptimKit = "77e91f04-9b3b-57a6-a776-40b61faaebe0" PEPSKit = "52969e89-939e-4361-9b68-9bc7cde4bdeb" ParallelTestRunner = "d3525ed8-44d0-4b2c-a655-542cee43accc" +Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7" QuadGK = "1fd47b50-473d-5c70-9696-f719f8f3bcdc" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" SafeTestsets = "1bc83da4-3b8d-516f-aca4-4fe02f6d838f" From dbb4a6b488a51631d58b137f9834a56acd01baae Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Tue, 5 May 2026 09:40:16 +0800 Subject: [PATCH 33/41] Remove unnecessary type specification --- src/algorithms/contractions/bondenv/benv_ctm.jl | 4 +--- src/algorithms/contractions/bondenv/benv_ntu.jl | 14 +++++++------- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/src/algorithms/contractions/bondenv/benv_ctm.jl b/src/algorithms/contractions/bondenv/benv_ctm.jl index 8c6b1a199..143dce59a 100644 --- a/src/algorithms/contractions/bondenv/benv_ctm.jl +++ b/src/algorithms/contractions/bondenv/benv_ctm.jl @@ -23,9 +23,7 @@ Axis order: `[DX1 DY1; DX0 DY0]`, as in └---------------------┘ ``` """ -function bondenv_ctm( - row::Int, col::Int, X::TX, Y::TY, env::CTMRGEnv - ) where {TX, TY} +function bondenv_ctm(row::Int, col::Int, X, Y, env::CTMRGEnv) Nr, Nc = size(env.corners)[[2, 3]] cm1 = _prev(col, Nc) cp1 = _next(col, Nc) diff --git a/src/algorithms/contractions/bondenv/benv_ntu.jl b/src/algorithms/contractions/bondenv/benv_ntu.jl index ad4327f7b..7c7431d37 100644 --- a/src/algorithms/contractions/bondenv/benv_ntu.jl +++ b/src/algorithms/contractions/bondenv/benv_ntu.jl @@ -34,8 +34,8 @@ Calculate the bond environment within "NTU-NN" approximation. ``` """ function bondenv_ntu( - row::Int, col::Int, X::TX, Y::TY, state::S, alg::NNEnv - ) where {TX, TY, S <: InfiniteState} + row::Int, col::Int, X, Y, state::S, alg::NNEnv + ) where {S <: InfiniteState} neighbors = [(-1, 0), (0, -1), (1, 0), (1, 1), (0, 2), (-1, 1)] m = collect_neighbors(state, row, col, neighbors) X, Y = _prepare_site_tensor(X), _prepare_site_tensor(Y) @@ -78,8 +78,8 @@ Calculate the bond environment within "NTU-NN+" approximation. Dotted lines and ○ are splitted using SVD with `truncrank(1)`. """ function bondenv_ntu( - row::Int, col::Int, X::TX, Y::TY, state::S, alg::NNpEnv - ) where {TX, TY, S <: InfiniteState} + row::Int, col::Int, X, Y, state::S, alg::NNpEnv + ) where {S <: InfiniteState} neighbors = [ (-1, -1), (0, -1), (1, -1), (1, 0), (1, 1), (1, 2), (0, 2), (-1, 2), (-1, 1), (-1, 0), (0, -2), (2, 0), (2, 1), (0, 3), (-2, 1), (-2, 0), @@ -191,8 +191,8 @@ Calculates the bond environment within "NTU-NNN" approximation. ``` """ function bondenv_ntu( - row::Int, col::Int, X::TX, Y::TY, state::S, alg::NNNEnv - ) where {TX, TY, S <: InfiniteState} + row::Int, col::Int, X, Y, state::S, alg::NNNEnv + ) where {S <: InfiniteState} neighbors = [ (-1, -1), (0, -1), (1, -1), (1, 0), (1, 1), (1, 2), (0, 2), @@ -224,7 +224,7 @@ function bondenv_ntu( 0 -3/-4===Y======● ║ ║ 1 -5/-6===●======● - 0 1 + 1 2 =# vecr = enlarge_corner_se(cor_se(m[1, 2]), edge_s(m[1, 1]), edge_e(m[0, 2]), Y) @tensor vecr[:] := From 523412652e77217cb9c14b31ce6a963bbd3f9a0c Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Tue, 5 May 2026 19:34:54 +0800 Subject: [PATCH 34/41] Revert a comment --- src/algorithms/truncation/bond_truncation.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/algorithms/truncation/bond_truncation.jl b/src/algorithms/truncation/bond_truncation.jl index 62c4a3283..8fbb9d1ca 100644 --- a/src/algorithms/truncation/bond_truncation.jl +++ b/src/algorithms/truncation/bond_truncation.jl @@ -154,7 +154,7 @@ function bond_truncate(a::MPSTensor, b::MPSTensor, benv::BondEnv, alg::FullEnvTr need_flip = isdual(space(b, 1)) #= initialize bond matrix using QR as `Ra Lb` - --- a == b --- ==> - Qa ← Ra == Rb → Qb - + --- a == b --- ==> - Qa ← Ra == Rb ← Qb - ↓ ↓ ↓ ↓ =# Qa, Ra = left_orth(a; positive = true) From 6034a67b6e18be81d7d9605676c7075d872fee8f Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Thu, 7 May 2026 10:12:35 +0800 Subject: [PATCH 35/41] Change default opt_alg for NTU --- src/algorithms/time_evolution/ntupdate.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/algorithms/time_evolution/ntupdate.jl b/src/algorithms/time_evolution/ntupdate.jl index a154bb7e6..047851689 100644 --- a/src/algorithms/time_evolution/ntupdate.jl +++ b/src/algorithms/time_evolution/ntupdate.jl @@ -16,7 +16,7 @@ Reference: BE <: NeighbourEnv, } <: TimeEvolution "Bond truncation algorithm after applying time evolution gate" - opt_alg::TR = ALSTruncation(; trunc = truncerror(; atol = 1.0e-10)) + opt_alg::TR = ALSTruncation(; trunc = FixedSpaceTruncation()) "When true (or false), the Trotter gate is `exp(-H dt)` (or `exp(-iH dt)`)" imaginary_time::Bool = true "Algorithm to construct NTU bond environment." From 8e4d44a71e81e8e76d63cfb2235c19b285d3e8e1 Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Thu, 7 May 2026 10:25:56 +0800 Subject: [PATCH 36/41] Add verbosity setting for NTU --- src/algorithms/time_evolution/ntupdate.jl | 178 +++++++++--------- src/algorithms/time_evolution/simpleupdate.jl | 11 +- 2 files changed, 98 insertions(+), 91 deletions(-) diff --git a/src/algorithms/time_evolution/ntupdate.jl b/src/algorithms/time_evolution/ntupdate.jl index 047851689..0d3428c96 100644 --- a/src/algorithms/time_evolution/ntupdate.jl +++ b/src/algorithms/time_evolution/ntupdate.jl @@ -165,11 +165,8 @@ function MPSKit.timestep( end """ - time_evolve( - it::TimeEvolver{<:NeighbourUpdate}, - [H::LocalOperator, env::CTMRGEnv, ctm_alg::CTMRGAlgorithm]; - tol::Float64 = 1.0e-7, check_interval::Int = 10 - ) -> (psi, info) + time_evolve(it; verbosity = 2, check_interval = 10) -> (psi, info) + time_evolve(it, H, env, ctm_alg; tol = 1.0e-7, verbosity = 2, check_interval = 10) -> (psi, info) Perform time evolution to the end of `NeighbourUpdate` TimeEvolver `it`, or until convergence of energy set by a positive `tol`. @@ -181,97 +178,102 @@ and setting `tol > 0`. `check_interval` sets the number of iterations between energy checks (for ground state search) and outputs of information. """ -function MPSKit.time_evolve(it::TimeEvolver{<:NeighbourUpdate}; check_interval::Int = 50) - time_start = time0 = time() - @info "--- Time evolution (neighbourhood tensor update), dt = $(it.dt) ---" - info0 = nothing - for (psi, info) in it - iter = it.state.iter - stop = (iter == it.nstep) - showinfo = (iter == 1) || (iter % check_interval == 0) || stop - time1 = time() - if showinfo - Δλ = (info0 === nothing) ? NaN : compare_weights(info.wts, info0.wts) - @info @sprintf( - "NTU iter %d: t = %.2e, |Δλ| = %.3e. Time: %.2f s", - it.state.iter, it.state.t, Δλ, time1 - time0 - ) - end - if stop - time_end = time() - @info @sprintf("Time evolution finished in %.2f s", time_end - time_start) - return psi, info +function MPSKit.time_evolve( + it::TimeEvolver{<:NeighbourUpdate}; + verbosity::Int = 2, check_interval::Int = 10 + ) + return LoggingExtras.withlevel(; verbosity) do + time_start = time0 = time() + @infov 1 "--- Time evolution (neighbourhood tensor update), dt = $(it.dt) ---" + info0 = nothing + for (psi, info) in it + iter = it.state.iter + stop = (iter == it.nstep) + showinfo = (iter == 1) || (iter % check_interval == 0) || stop + time1 = time() + if showinfo + Δλ = (info0 === nothing) ? NaN : compare_weights(info.wts, info0.wts) + @infov 2 @sprintf( + "NTU iter %d: t = %.2e, |Δλ| = %.3e. Time: %.2f s", + it.state.iter, it.state.t, Δλ, time1 - time0 + ) + end + if stop + time_end = time() + @infov 1 @sprintf("Time evolution finished in %.2f s", time_end - time_start) + return psi, info + end + info0, time0 = info, time() end - info0, time0 = info, time() end - return end function MPSKit.time_evolve( it::TimeEvolver{<:NeighbourUpdate, G, S}, H::LocalOperator, env::CTMRGEnv, ctm_alg::CTMRGAlgorithm; - tol::Float64 = 1.0e-7, check_interval::Int = 10 + tol::Float64 = 1.0e-7, verbosity::Int = 2, check_interval::Int = 10 ) where {G, S <: NTUState{<:InfinitePEPS}} - @info "--- Time evolution (neighbourhood tensor update), dt = $(it.dt) ---" - time_start = time0 = time() - psi0 = copy(it.state.psi) - @assert it.alg.imaginary_time "Only imaginary time evolution of InfinitePEPS allows convergence checking." - # initial energy - env, = leading_boundary(env, psi0, ctm_alg) - energy = real(expectation_value(psi0, H, env)) / prod(size(psi0)) - @info @sprintf("NTU iter 0: E = %.4e", energy) - info0 = (; energy, env) - # start evolving - energy0, ΔE = energy, 0.0 - iter0, t0 = it.state.iter, it.state.t - for (psi, info) in it - iter = it.state.iter - showinfo = (iter == 1) || (iter % check_interval == 0) || (iter == it.nstep) - !showinfo && continue - # bond weight change - Δλ = hasproperty(info0, :wts) ? compare_weights(info.wts, info0.wts) : NaN - # reconverge environment - if all(space(t) == space(t0) for (t, t0) in zip(psi.A, psi0.A)) - # recreate `env` from bond weights if psi virtual space changed - env = CTMRGEnv(info.wts) - end - env, = leading_boundary(env, psi, ctm_alg) - # measure energy - energy = real(expectation_value(psi, H, env)) / prod(size(psi)) - ΔE = energy - energy0 - info = @insert info.energy = energy - info = @insert info.env = env - # show information - time1 = time() - @info @sprintf( - "NTU iter %-6d: E = %.5f, ΔE = %.3e, |Δλ| = %.3e. Time: %.2f s", - it.state.iter, energy, ΔE, Δλ, time1 - time0 - ) - # determine whether to stop evolution - stop = false - if (ΔE <= 0 && abs(ΔE) < tol) - stop = true - @info "NTU: energy has converged." - end - if ΔE > 0 - stop = true - @warn "NTU: energy has increased. Abort evolution and return results from last check." - psi, info, energy = psi0, info0, energy0 - it.state = NTUState(iter0, t0, psi0) - end - if iter == it.nstep - stop = true - @info "NTU: reached maximum iteration." - end - if stop - time_end = time() - @info @sprintf("Time evolution finished in %.2f s", time_end - time_start) - return psi, info - else - iter0, t0 = it.state.iter, it.state.t - psi0, energy0, info0 = psi, energy, info + return LoggingExtras.withlevel(; verbosity) do + @infov 1 "--- Time evolution (neighbourhood tensor update), dt = $(it.dt) ---" + time_start = time0 = time() + psi0 = copy(it.state.psi) + @assert it.alg.imaginary_time "Only imaginary time evolution of InfinitePEPS allows convergence checking." + # initial energy + env, = leading_boundary(env, psi0, ctm_alg) + energy = real(expectation_value(psi0, H, env)) / prod(size(psi0)) + @infov 2 @sprintf("NTU iter 0: E = %.4e", energy) + info0 = (; energy, env) + # start evolving + energy0, ΔE = energy, 0.0 + iter0, t0 = it.state.iter, it.state.t + for (psi, info) in it + iter = it.state.iter + showinfo = (iter == 1) || (iter % check_interval == 0) || (iter == it.nstep) + !showinfo && continue + # bond weight change + Δλ = hasproperty(info0, :wts) ? compare_weights(info.wts, info0.wts) : NaN + # reconverge environment + if all(space(t) == space(t0) for (t, t0) in zip(psi.A, psi0.A)) + # recreate `env` from bond weights if psi virtual space changed + env = CTMRGEnv(info.wts) + end + env, = leading_boundary(env, psi, ctm_alg) + # measure energy + energy = real(expectation_value(psi, H, env)) / prod(size(psi)) + ΔE = energy - energy0 + info = @insert info.energy = energy + info = @insert info.env = env + # show information + time1 = time() + @infov 2 @sprintf( + "NTU iter %-6d: E = %.5f, ΔE = %.3e, |Δλ| = %.3e. Time: %.2f s", + it.state.iter, energy, ΔE, Δλ, time1 - time0 + ) + # determine whether to stop evolution + stop = false + if (ΔE <= 0 && abs(ΔE) < tol) + stop = true + @infov 2 "NTU: energy has converged." + end + if ΔE > 0 + stop = true + @warn "NTU: energy has increased. Abort evolution and return results from last check." + psi, info, energy = psi0, info0, energy0 + it.state = NTUState(iter0, t0, psi0) + end + if iter == it.nstep + stop = true + @warn "NTU: reached maximum iteration." + end + if stop + time_end = time() + @infov 1 @sprintf("Time evolution finished in %.2f s", time_end - time_start) + return psi, info + else + iter0, t0 = it.state.iter, it.state.t + psi0, energy0, info0 = psi, energy, info + end + time0 = time() end - time0 = time() end - return end diff --git a/src/algorithms/time_evolution/simpleupdate.jl b/src/algorithms/time_evolution/simpleupdate.jl index 9fab38443..51a58d742 100644 --- a/src/algorithms/time_evolution/simpleupdate.jl +++ b/src/algorithms/time_evolution/simpleupdate.jl @@ -176,17 +176,22 @@ end """ timestep( - it::TimeEvolver{<:SimpleUpdate}, psi::InfiniteState, env::SUWeight + it::TimeEvolver{<:SimpleUpdate}, psi::InfiniteState, env::SUWeight; + iter::Int = it.state.iter, t::Float64 = it.state.t ) -> (psi, env, info) Given the `TimeEvolver` iterator `it`, perform one step of time evolution on the input state `psi` and its environment `env`. + +- Using `iter` and `t` to reset the current iteration number and evolved time + respectively of the TimeEvolver `it`. """ function MPSKit.timestep( - it::TimeEvolver{<:SimpleUpdate}, psi::InfiniteState, env::SUWeight + it::TimeEvolver{<:SimpleUpdate}, psi::InfiniteState, env::SUWeight; + iter::Int = it.state.iter, t::Float64 = it.state.t ) _timeevol_sanity_check(psi, physicalspace(it.state.psi), it.alg) - state = SUState(it.state.iter, it.state.t, psi, env) + state = SUState(iter, t, psi, env) result = iterate(it, state) if result === nothing @warn "TimeEvolver `it` has already reached the end." From fa109bc5fac6b61b867324279454b27517ac8f99 Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Thu, 7 May 2026 10:56:04 +0800 Subject: [PATCH 37/41] Test coverage on NTU for ground state --- test/timeevol/heis_groundstate.jl | 40 +++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 test/timeevol/heis_groundstate.jl diff --git a/test/timeevol/heis_groundstate.jl b/test/timeevol/heis_groundstate.jl new file mode 100644 index 000000000..2b4fb44ec --- /dev/null +++ b/test/timeevol/heis_groundstate.jl @@ -0,0 +1,40 @@ +using Test +using Random +using TensorKit +using PEPSKit + +function converge_env(state, ctm_alg) + env0 = CTMRGEnv(ones, scalartype(state), state, oneunit(spacetype(state))) + env, = leading_boundary(env0, state, ctm_alg) + return env +end + +Random.seed!(1457860) +Nr, Nc = 2, 2 +H = j1_j2_model( + Float64, Trivial, InfiniteSquare(Nr, Nc); + J1 = 1.0, J2 = 0.0, sublattice = false +) +Pspace, Vspace = ℂ^2, ℂ^4 +ψ0 = InfinitePEPS(rand, Float64, Pspace, Vspace; unitcell = (Nr, Nc)) +trunc = truncerror(; atol = 1.0e-10) & truncrank(4) +ctm_alg = SequentialCTMRG(; tol = 1.0e-10, verbosity = 2, trunc = truncerror(; atol = 1.0e-10) & truncrank(16)) + +# prepare simple update state +su_alg = SimpleUpdate(; trunc) +evolver = TimeEvolver(ψ0, H, 0.01, 5000, su_alg, SUWeight(ψ0)) +ψ0, = time_evolve(evolver, H; tol = 1.0e-8, check_interval = 200) +env0 = converge_env(ψ0, ctm_alg) +e0 = expectation_value(ψ0, H, env0) / (Nr * Nc) +@info "Simple update energy = $(e0)." + +# continue with NTU +@testset "NTU for ground state" begin + ntu_alg = NeighbourUpdate() + evolver = TimeEvolver(ψ0, H, 0.01, 5000, ntu_alg) + ψ, info = time_evolve(evolver, H, env0, ctm_alg) + env = info.env + e = expectation_value(ψ, H, env) / (Nr * Nc) + @info "NTU energy = $(e)" + @test e < e0 +end From e16c935b0cbd812849da62cab015bf5209dc6551 Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Thu, 7 May 2026 12:00:48 +0800 Subject: [PATCH 38/41] Unexport infinite_temperature_density_matrix --- src/PEPSKit.jl | 1 - 1 file changed, 1 deletion(-) diff --git a/src/PEPSKit.jl b/src/PEPSKit.jl index 65ae8d7d2..574ccf8ef 100644 --- a/src/PEPSKit.jl +++ b/src/PEPSKit.jl @@ -159,7 +159,6 @@ export InfinitePartitionFunction export InfinitePEPS, InfiniteTransferPEPS export SUWeight export InfinitePEPO, InfiniteTransferPEPO -export infinite_temperature_density_matrix export BPEnv, BeliefPropagation export BPGauge, SUGauge From 1fdc81c5170896d68a648ae5b4349df20b89a8f0 Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Fri, 8 May 2026 20:58:57 +0800 Subject: [PATCH 39/41] Use periodic indexing --- .../contractions/bondenv/benv_tools.jl | 3 +-- src/algorithms/time_evolution/ntupdate.jl | 4 +-- .../time_evolution/ntupdate3site.jl | 27 +++++++++---------- src/algorithms/time_evolution/simpleupdate.jl | 2 +- test/bondenv/benv_ntu.jl | 3 +-- test/timeevol/j1j2_finiteT.jl | 4 +-- test/timeevol/tf_ising_finiteT.jl | 2 +- test/timeevol/tf_ising_realtime.jl | 4 +-- 8 files changed, 22 insertions(+), 27 deletions(-) diff --git a/src/algorithms/contractions/bondenv/benv_tools.jl b/src/algorithms/contractions/bondenv/benv_tools.jl index bcb159c45..3850cefc9 100644 --- a/src/algorithms/contractions/bondenv/benv_tools.jl +++ b/src/algorithms/contractions/bondenv/benv_tools.jl @@ -26,9 +26,8 @@ at positions `neighbors` relative to `(row, col)` function collect_neighbors( state::InfiniteState, row::Int, col::Int, neighbors::Vector{Tuple{Int, Int}} ) - Nr, Nc = size(state) return Dict( - nb => _prepare_site_tensor(state[mod1(row + nb[1], Nr), mod1(col + nb[2], Nc)]) + nb => _prepare_site_tensor(state[row + nb[1], col + nb[2]]) for nb in neighbors ) end diff --git a/src/algorithms/time_evolution/ntupdate.jl b/src/algorithms/time_evolution/ntupdate.jl index bb754892b..5b170d271 100644 --- a/src/algorithms/time_evolution/ntupdate.jl +++ b/src/algorithms/time_evolution/ntupdate.jl @@ -93,8 +93,8 @@ function ntu_iter( if length(sites) == 1 # 1-site gate # TODO: special treatment for bipartite state - site = sites[1] - state2[r, c] = _apply_sitegate(state2[r, c], gate) + site = only(sites) + state2[site] = _apply_sitegate(state2[site], gate) info′ = (; fid = 1.0) elseif length(sites) == 2 (d, r, c), = _nn_bondrev(sites...) diff --git a/src/algorithms/time_evolution/ntupdate3site.jl b/src/algorithms/time_evolution/ntupdate3site.jl index f8861a652..ba03cd21a 100644 --- a/src/algorithms/time_evolution/ntupdate3site.jl +++ b/src/algorithms/time_evolution/ntupdate3site.jl @@ -14,7 +14,6 @@ function _ntu_iter( state::InfiniteState, gate::Vector{T}, wts::SUWeight, sites::Vector{CartesianIndex{2}}, alg::NeighbourUpdate ) where {T <: AbstractTensorMap} - Nr, Nc = size(state) truncs = _get_cluster_trunc(alg.opt_alg.trunc, sites) state, wts = copy(state), deepcopy(wts) @@ -26,8 +25,7 @@ function _ntu_iter( _apply_gatempo!(Ms, gate) _flip_virtuals!(Ms, flips) # restore virtual arrows in `Ms` for (M, s, invperm) in zip(Ms, sites, invperms) - s′ = CartesianIndex(mod1(s[1], Nr), mod1(s[2], Nc)) - state[s′] = permute(M, invperm) + state[s] = permute(M, invperm) end # truncate each bond sequentially along the path @@ -61,14 +59,14 @@ function _bond_truncate( # rotate bond to standard x direction `A ← B` ucell = size(state)[1:2] bond, rev = _nn_bondrev(site1, site2) - state2 = _bond_rotation(state, bond[1], rev; inv = false) - wts2 = _bond_rotation(wts, bond[1], rev; inv = false) + dir = first(bond) + state2 = _bond_rotation(state, dir, rev; inv = false) + wts2 = _bond_rotation(wts, dir, rev; inv = false) # rotated bond tensors - siteA = _bond_rotation(site1, bond[1], rev, ucell) - row, col = mod1.(Tuple(siteA), size(state2)[1:2]) - cp1 = _next(col, size(state2, 2)) - A, B = state2[row, col], state2[row, cp1] + siteA = _bond_rotation(site1, dir, rev, ucell) + row, col = siteA[1], siteA[2] + A, B = state2[row, col], state2[row, col + 1] # create bond environment qrtrunc = trunctol(; rtol = 1.0e-12) @@ -113,13 +111,12 @@ function _bond_truncate( undo_bond_tensor_midprev(b, Y) end - normalize!(A, Inf) - normalize!(B, Inf) - normalize!(s, Inf) - state2[row, col], state2[row, cp1], wts2[1, row, col] = A, B, s + state2[row, col] = normalize!(A, Inf) + state2[row, col + 1] = normalize!(B, Inf) + wts2[1, row, col] = normalize!(s, Inf) # rotate back tensors and bond weight - state2 = _bond_rotation(state2, bond[1], rev; inv = true) - wts2 = _bond_rotation(wts2, bond[1], rev; inv = true) + state2 = _bond_rotation(state2, dir, rev; inv = true) + wts2 = _bond_rotation(wts2, dir, rev; inv = true) return state2, wts2, info end diff --git a/src/algorithms/time_evolution/simpleupdate.jl b/src/algorithms/time_evolution/simpleupdate.jl index 3f6ecb24b..99af9cb1e 100644 --- a/src/algorithms/time_evolution/simpleupdate.jl +++ b/src/algorithms/time_evolution/simpleupdate.jl @@ -128,7 +128,7 @@ function su_iter( if length(sites) == 1 # 1-site gate # TODO: special treatment for bipartite state - site = sites[1] + site = only(sites) state2[site] = _apply_sitegate(state2[site], gate; alg.purified) elseif length(sites) == 2 (d, r, c), = _nn_bondrev(sites...) diff --git a/test/bondenv/benv_ntu.jl b/test/bondenv/benv_ntu.jl index 24cb5ab0c..c9757d6cf 100644 --- a/test/bondenv/benv_ntu.jl +++ b/test/bondenv/benv_ntu.jl @@ -13,8 +13,7 @@ function test_ntu_env( ) where {Alg <: PEPSKit.NeighbourEnv} @info "Testing $(typeof(env_alg))" for row in 1:Nr, col in 1:Nc - cp1 = PEPSKit._next(col, Nc) - A, B = state.A[row, col], state.A[row, cp1] + A, B = state[row, col], state[row, col + 1] a, X = PEPSKit.bond_tensor_first(A) b, Y = PEPSKit.bond_tensor_last(B) @tensor ab[DX DY; da db] := a[DX da D] * b[D db DY] diff --git a/test/timeevol/j1j2_finiteT.jl b/test/timeevol/j1j2_finiteT.jl index b834b3522..226dc3d49 100644 --- a/test/timeevol/j1j2_finiteT.jl +++ b/test/timeevol/j1j2_finiteT.jl @@ -10,8 +10,8 @@ const bm = [-0.08624893, -0.15688984, -0.21300888] function converge_env(state, χ::Int) trunc1 = truncrank(χ) & truncerror(; atol = 1.0e-12) - env0 = CTMRGEnv(ones, Float64, state, oneunit(spacetype(state))) - env, = leading_boundary(env0, state; alg = :sequential, trunc = trunc1, tol = 1.0e-10) + env0 = CTMRGEnv(ones, scalartype(state), state, oneunit(spacetype(state))) + env, = leading_boundary(env0, state; alg = :SequentialCTMRG, trunc = trunc1, tol = 1.0e-10) return env end diff --git a/test/timeevol/tf_ising_finiteT.jl b/test/timeevol/tf_ising_finiteT.jl index 62cc7edc7..60b70c8de 100644 --- a/test/timeevol/tf_ising_finiteT.jl +++ b/test/timeevol/tf_ising_finiteT.jl @@ -11,7 +11,7 @@ bm_2β = [0.5297, 0.8265] function converge_env(state, χ::Int) trunc1 = truncrank(4) & truncerror(; atol = 1.0e-12) - env0 = CTMRGEnv(rand, Float64, state, ℂ^2) + env0 = CTMRGEnv(rand, scalartype(state), state, ℂ^2) env, = leading_boundary(env0, state; alg = :SequentialCTMRG, trunc = trunc1, tol = 1.0e-10) trunc2 = truncrank(χ) & truncerror(; atol = 1.0e-12) env, = leading_boundary(env, state; alg = :SequentialCTMRG, trunc = trunc2, tol = 1.0e-10) diff --git a/test/timeevol/tf_ising_realtime.jl b/test/timeevol/tf_ising_realtime.jl index b34318dcf..b7a2a3369 100644 --- a/test/timeevol/tf_ising_realtime.jl +++ b/test/timeevol/tf_ising_realtime.jl @@ -33,7 +33,7 @@ end lattice = collect(space(t, 1) for t in peps0.A) # Hamiltonian -op = LocalOperator(lattice, ((1, 1),) => σˣ()) +op = LocalOperator(lattice, [(1, 1)] => σˣ()) ham = transverse_field_ising(ComplexF64, Trivial, InfiniteSquare(2, 2); J = 1.0, g = hc) # truncation strategy @@ -43,7 +43,7 @@ trunc_env = truncerror(; atol = 1.0e-10) & truncrank(chi) ctm_alg = SequentialCTMRG(; tol = 1.0e-8, maxiter = 50, verbosity = 2, - trunc = trunc_env, projector_alg = :fullinfinite + trunc = trunc_env, projector_alg = :FullInfiniteProjector ) interval = 5 From 1655e48323caf902dac167dd98395568bde76d51 Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Fri, 8 May 2026 21:02:12 +0800 Subject: [PATCH 40/41] Slightly reduce output --- test/timeevol/heis_groundstate.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/timeevol/heis_groundstate.jl b/test/timeevol/heis_groundstate.jl index 2b4fb44ec..328765e56 100644 --- a/test/timeevol/heis_groundstate.jl +++ b/test/timeevol/heis_groundstate.jl @@ -23,7 +23,7 @@ ctm_alg = SequentialCTMRG(; tol = 1.0e-10, verbosity = 2, trunc = truncerror(; a # prepare simple update state su_alg = SimpleUpdate(; trunc) evolver = TimeEvolver(ψ0, H, 0.01, 5000, su_alg, SUWeight(ψ0)) -ψ0, = time_evolve(evolver, H; tol = 1.0e-8, check_interval = 200) +ψ0, = time_evolve(evolver, H; tol = 1.0e-8, check_interval = 500) env0 = converge_env(ψ0, ctm_alg) e0 = expectation_value(ψ0, H, env0) / (Nr * Nc) @info "Simple update energy = $(e0)." From 03d2c8f9ad95092c1ec676717b3b0209c16c820d Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Fri, 15 May 2026 11:38:50 +0800 Subject: [PATCH 41/41] Update tests --- src/algorithms/time_evolution/ntupdate3site.jl | 2 +- test/timeevol/j1j2_finiteT.jl | 4 ++-- test/timeevol/sitedep_truncation.jl | 7 ++++++- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/algorithms/time_evolution/ntupdate3site.jl b/src/algorithms/time_evolution/ntupdate3site.jl index ba03cd21a..a04c05701 100644 --- a/src/algorithms/time_evolution/ntupdate3site.jl +++ b/src/algorithms/time_evolution/ntupdate3site.jl @@ -96,7 +96,7 @@ function _bond_truncate( # (optional) apply the NN gate without truncation if !(gate === nothing) - a, s, b, = _apply_gate(a, b, gate, truncerror(; atol = 1.0e-15)) + a, _, b, = _apply_gate(a, b, gate, truncerror(; atol = 1.0e-15)) end a, s, b, info = bond_truncate(a, b, benv, alg.opt_alg) diff --git a/test/timeevol/j1j2_finiteT.jl b/test/timeevol/j1j2_finiteT.jl index 226dc3d49..bcb6dab6d 100644 --- a/test/timeevol/j1j2_finiteT.jl +++ b/test/timeevol/j1j2_finiteT.jl @@ -41,7 +41,7 @@ dt, nstep, check_interval = 5.0e-3, 40, 40 end @testset "Neighbourhood tensor update" begin - trunc_pepo = truncrank(4) & truncerror(; atol = 1.0e-12) + trunc_pepo = truncrank(7) & truncerror(; atol = 1.0e-12) opt_alg = ALSTruncation(; trunc = trunc_pepo, tol = 1.0e-10) alg = NeighbourUpdate(; opt_alg, bondenv_alg = NNEnv()) pepo = deepcopy(pepo0) @@ -53,6 +53,6 @@ end env = converge_env(InfinitePEPS(pepo), 16) energy = expectation_value(pepo, ham, pepo, env) / (Nr * Nc) @info "β = $(info.t): ⟨ρ|H|ρ⟩ = $(energy)" - @test energy ≈ bme atol = 2.0e-2 + @test energy ≈ bme atol = 5.0e-3 end end diff --git a/test/timeevol/sitedep_truncation.jl b/test/timeevol/sitedep_truncation.jl index b61b50d95..67a47f4c5 100644 --- a/test/timeevol/sitedep_truncation.jl +++ b/test/timeevol/sitedep_truncation.jl @@ -3,6 +3,7 @@ using Random using TensorKit using PEPSKit using PEPSKit: _is_bipartite, _get_fixedspacetrunc +using PEPSKit: NORTH, EAST elt = Float64 Nr, Nc = 2, 2 @@ -63,7 +64,7 @@ end end @testset "NTU on $(typeof(state0).name.wrapper), bipartite = $(bipartite)" for - (state0, bipartite) in Iterators.product(states, (true, false)) + (state0, bipartite) in Iterators.product(states, (false, true)) J2 = 0.5 if bipartite state0[2, 1] = copy(state0[1, 2]) @@ -78,6 +79,10 @@ end for (t, t0) in zip(state.A, state0.A) @test space(t) == space(t0) end + for (r, c) in Iterators.product(1:Nr, 1:Nc) + @test space(info.wts[1, r, c], 1) == domain(state[r, c], EAST) + @test space(info.wts[2, r, c], 1) == domain(state[r, c], NORTH) + end if bipartite @test _is_bipartite(state) @test _is_bipartite(info.wts)