From 49eb37fe3678c42fe62cdb3944f682fbb43d201d Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Sun, 8 Jun 2025 21:01:49 +0800 Subject: [PATCH 01/62] Add option to use pseudo-inverse to solve linear equations in `ALSTruncation` --- src/algorithms/contractions/bondenv/als_solve.jl | 10 ++++++++++ src/algorithms/truncation/bond_truncation.jl | 15 +++++++++++++-- src/utility/svd.jl | 6 ++++++ test/bondenv/bond_truncate.jl | 3 ++- 4 files changed, 31 insertions(+), 3 deletions(-) diff --git a/src/algorithms/contractions/bondenv/als_solve.jl b/src/algorithms/contractions/bondenv/als_solve.jl index 1bc06b9d9..62c0bbace 100644 --- a/src/algorithms/contractions/bondenv/als_solve.jl +++ b/src/algorithms/contractions/bondenv/als_solve.jl @@ -206,3 +206,13 @@ function _solve_ab( x1, info = linsolve(f, Sx, x0, 0, 1) return x1, info end + +function _solve_ab_pinv!( + Rx::AbstractTensorMap{T,S,2,2}, Sx::AbstractTensorMap{T,S,2,1}; kwargs... +) where {T<:Number,S<:ElementarySpace} + Rx_inv, ϵ = _pinv!(copy(Rx); kwargs...) + is = filter(i -> isdual(codomain(Rx_inv, i)), 1:numout(Rx_inv)) + x = Rx_inv * Sx + twist!(x, is) + return x, ϵ +end diff --git a/src/algorithms/truncation/bond_truncation.jl b/src/algorithms/truncation/bond_truncation.jl index 40f413074..84a1e281f 100644 --- a/src/algorithms/truncation/bond_truncation.jl +++ b/src/algorithms/truncation/bond_truncation.jl @@ -16,12 +16,14 @@ The truncation algorithm can be constructed from the following keyword arguments * `trscheme::TensorKit.TruncationScheme`: SVD truncation scheme when initilizing the truncated tensors connected by the bond. * `maxiter::Int=50` : Maximal number of ALS iterations. * `tol::Float64=1e-15` : ALS converges when fidelity change between two FET iterations is smaller than `tol`. +* `use_pinv::Bool=true`: Use pseudo-inverse (instead of `KrylovKit.linsolve`) to solve linear equations in ALS itertions. * `check_interval::Int=0` : Set number of iterations to print information. Output is suppressed when `check_interval <= 0`. """ @kwdef struct ALSTruncation trscheme::TensorKit.TruncationScheme maxiter::Int = 50 tol::Float64 = 1e-15 + use_pinv::Bool = true check_interval::Int = 0 end @@ -103,11 +105,20 @@ 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 = if alg.use_pinv + _solve_ab_pinv!(Ra, Sa; trunc=truncerr(1e-10)) + else + _solve_ab(Ra, Sa, a) + end # 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 = if alg.use_pinv + _solve_ab_pinv!(Rb, Sb; trunc=truncerr(1e-10)) + else + _solve_ab(Rb, Sb, b) + end + @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) diff --git a/src/utility/svd.jl b/src/utility/svd.jl index 25033c9fb..fe36990e0 100644 --- a/src/utility/svd.jl +++ b/src/utility/svd.jl @@ -609,3 +609,9 @@ function svd_pullback!( end return ΔA end + +# Calculate the pseudo-inverse using SVD +function _pinv!(a::AbstractTensorMap; kwargs...) + u, s, vh, ϵ = tsvd!(a; kwargs...) + return vh' * sdiag_pow(s, -1) * u', ϵ +end diff --git a/test/bondenv/bond_truncate.jl b/test/bondenv/bond_truncate.jl index 33225a635..3bc08bde9 100644 --- a/test/bondenv/bond_truncate.jl +++ b/test/bondenv/bond_truncate.jl @@ -30,7 +30,8 @@ for Vbondl in (Vint, Vint'), Vbondr in (Vint, Vint') @info "Fidelity of simple SVD truncation = $fid0.\n" ss = Dict{String,DiagonalTensorMap}() for (label, alg) in ( - ("ALS", ALSTruncation(; trscheme, maxiter, check_interval)), + ("ALS", ALSTruncation(; trscheme, maxiter, check_interval, use_pinv=false)), + ("ALS (pinv)", ALSTruncation(; trscheme, maxiter, check_interval, use_pinv=true)), ("FET", FullEnvTruncation(; trscheme, maxiter, check_interval, trunc_init=false)), ) a1, ss[label], b1, info = PEPSKit.bond_truncate(a2, b2, benv, alg) From 431be7a40a5246d8e48fc7e782040a8ff2b0e3c3 Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Fri, 13 Jun 2025 11:58:16 +0800 Subject: [PATCH 02/62] Fix 3-site SU `dt` and reduce artificial C4v breaking --- src/algorithms/time_evolution/evoltools.jl | 19 ++++++++++--------- .../time_evolution/simpleupdate3site.jl | 16 +++++++++++----- 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/src/algorithms/time_evolution/evoltools.jl b/src/algorithms/time_evolution/evoltools.jl index 588fd9c14..299266855 100644 --- a/src/algorithms/time_evolution/evoltools.jl +++ b/src/algorithms/time_evolution/evoltools.jl @@ -181,19 +181,20 @@ Obtain the 3-site gate MPO on the southeast cluster at position `[row, col]` c c+1 ``` """ -function _get_gatempo_se(gate::LocalOperator, row::Int, col::Int) - Nr, Nc = size(gate.lattice) +function _get_gatempo_se(ham::LocalOperator, dt::Number, row::Int, col::Int) + Nr, Nc = size(ham.lattice) @assert 1 <= row <= Nr && 1 <= col <= Nc - unit = id(space(gate.terms[1].second, 1)) + unit = id(space(ham.terms[1].second, 1)) sites = ( CartesianIndex(row, col), CartesianIndex(row, col + 1), CartesianIndex(row - 1, col + 1), ) - nb1x = get_gateterm(gate, (sites[1], sites[2])) - nb1y = get_gateterm(gate, (sites[2], sites[3])) - nb2 = get_gateterm(gate, (sites[1], sites[3])) + nb1x = get_gateterm(ham, (sites[1], sites[2])) + nb1y = get_gateterm(ham, (sites[2], sites[3])) + nb2 = get_gateterm(ham, (sites[1], sites[3])) op = (1 / 2) * (nb1x ⊗ unit + unit ⊗ nb1y) + permute(nb2 ⊗ unit, ((1, 3, 2), (4, 6, 5))) + op = exp(-dt * op) return gate_to_mpo3(op) end @@ -201,7 +202,7 @@ end Construct the 3-site gate MPOs on the southeast cluster for 3-site simple update on square lattice. """ -function _get_gatempos_se(gate::LocalOperator) - Nr, Nc = size(gate.lattice) - return collect(_get_gatempo_se(gate, r, c) for r in 1:Nr, c in 1:Nc) +function _get_gatempos_se(ham::LocalOperator, dt::Number) + Nr, Nc = size(ham.lattice) + return collect(_get_gatempo_se(ham, dt, r, c) for r in 1:Nr, c in 1:Nc) end diff --git a/src/algorithms/time_evolution/simpleupdate3site.jl b/src/algorithms/time_evolution/simpleupdate3site.jl index 678ac69a0..f459c2abe 100644 --- a/src/algorithms/time_evolution/simpleupdate3site.jl +++ b/src/algorithms/time_evolution/simpleupdate3site.jl @@ -414,13 +414,13 @@ function su3site_iter( ), ) peps2 = deepcopy(peps) - for i in 1:2 + for i in 1:4 for site in CartesianIndices(peps2.vertices) r, c = site[1], site[2] gs = gatempos[i][r, c] _su3site_se!(r, c, gs, peps2, alg) end - peps2 = (i == 1) ? rotl90(peps2) : rotr90(peps2) + peps2 = rotl90(peps2) end return peps2 end @@ -432,9 +432,15 @@ function _simpleupdate3site( peps::InfiniteWeightPEPS, ham::LocalOperator, alg::SimpleUpdate; check_interval::Int=500 ) time_start = time() - gate = get_expham(alg.dt, ham) - # convert gates to 3-site MPOs - gatempos = [_get_gatempos_se(gate), _get_gatempos_se(rotl90(gate))] + # Convert Hamiltonian to 3-site exponentiated gate MPOs. + # Since each bond is updated 4 times, + # `dt` for each MPO should be divided by 4 + gatempos = [ + _get_gatempos_se(ham, alg.dt / 4), + _get_gatempos_se(rotl90(ham), alg.dt / 4), + _get_gatempos_se(rot180(ham), alg.dt / 4), + _get_gatempos_se(rotr90(ham), alg.dt / 4), + ] wtdiff = 1.0 wts0 = deepcopy(peps.weights) for count in 1:(alg.maxiter) From 11e8633d8233aa468f4d29553be31bbfcfc979c4 Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Fri, 13 Jun 2025 12:39:56 +0800 Subject: [PATCH 03/62] Fix formatting --- src/algorithms/time_evolution/simpleupdate3site.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/algorithms/time_evolution/simpleupdate3site.jl b/src/algorithms/time_evolution/simpleupdate3site.jl index f459c2abe..f34d989b5 100644 --- a/src/algorithms/time_evolution/simpleupdate3site.jl +++ b/src/algorithms/time_evolution/simpleupdate3site.jl @@ -436,7 +436,7 @@ function _simpleupdate3site( # Since each bond is updated 4 times, # `dt` for each MPO should be divided by 4 gatempos = [ - _get_gatempos_se(ham, alg.dt / 4), + _get_gatempos_se(ham, alg.dt / 4), _get_gatempos_se(rotl90(ham), alg.dt / 4), _get_gatempos_se(rot180(ham), alg.dt / 4), _get_gatempos_se(rotr90(ham), alg.dt / 4), From 28d0a4c74674a8ff2f477fb2b496f0292d4a17ad Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Fri, 13 Jun 2025 22:30:18 +0800 Subject: [PATCH 04/62] Add full update and example test with TF Ising --- src/PEPSKit.jl | 2 + src/algorithms/ctmrg/sequential.jl | 29 +++- src/algorithms/time_evolution/evoltools.jl | 12 +- src/algorithms/time_evolution/fullupdate.jl | 155 ++++++++++++++++++++ test/runtests.jl | 8 + test/timeevol/tf_ising_fu.jl | 98 +++++++++++++ 6 files changed, 293 insertions(+), 11 deletions(-) create mode 100644 src/algorithms/time_evolution/fullupdate.jl create mode 100644 test/timeevol/tf_ising_fu.jl diff --git a/src/PEPSKit.jl b/src/PEPSKit.jl index 02253a719..afe5aca80 100644 --- a/src/PEPSKit.jl +++ b/src/PEPSKit.jl @@ -63,6 +63,7 @@ include("algorithms/truncation/bond_truncation.jl") include("algorithms/time_evolution/evoltools.jl") include("algorithms/time_evolution/simpleupdate.jl") include("algorithms/time_evolution/simpleupdate3site.jl") +include("algorithms/time_evolution/fullupdate.jl") include("algorithms/toolbox.jl") @@ -87,6 +88,7 @@ export fixedpoint export absorb_weight export ALSTruncation, FullEnvTruncation export su_iter, su3site_iter, simpleupdate, SimpleUpdate +export fu_iter, fu_iter2, FullUpdate export InfiniteSquareNetwork export InfinitePartitionFunction diff --git a/src/algorithms/ctmrg/sequential.jl b/src/algorithms/ctmrg/sequential.jl index 6ec8e7581..a14b52812 100644 --- a/src/algorithms/ctmrg/sequential.jl +++ b/src/algorithms/ctmrg/sequential.jl @@ -38,11 +38,11 @@ end CTMRG_SYMBOLS[:sequential] = SequentialCTMRG """ - ctmrg_leftmove(col::Int, network, env::CTMRGEnv, alg::SequentialCTMRG) + ctmrg_leftmove(col::Int, network, env::CTMRGEnv, alg::ProjectorAlgorithm) Perform sequential CTMRG left move on the `col`-th column. """ -function ctmrg_leftmove(col::Int, network, env::CTMRGEnv, alg::SequentialCTMRG) +function ctmrg_leftmove(col::Int, network, env::CTMRGEnv, alg::ProjectorAlgorithm) #= ----> left move C1 ← T1 ← r-1 @@ -52,17 +52,38 @@ function ctmrg_leftmove(col::Int, network, env::CTMRGEnv, alg::SequentialCTMRG) C4 → T3 → r+1 c-1 c =# - projectors, info = sequential_projectors(col, network, env, alg.projector_alg) + projectors, info = sequential_projectors(col, network, env, alg) env = renormalize_sequentially(col, projectors, network, env) return env, info end +""" + ctmrg_rightmove(col::Int, network, env::CTMRGEnv, alg::ProjectorAlgorithm) + +Perform sequential CTMRG right move on the `col`-th column. +""" +function ctmrg_rightmove(col::Int, network, env::CTMRGEnv, alg::ProjectorAlgorithm) + #= + right move <--- + ←-- T1 ← C2 r-1 + ‖ ↑ + === M' = T2 r + ‖ ↑ + --→ T3 → C3 r+1 + c c+1 + =# + Nc = size(network)[2] + @assert 1 <= col <= Nc + env, info = ctmrg_leftmove(Nc + 1 - col, rot180(network), rot180(env), alg) + return rot180(env), info +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) + env, info = ctmrg_leftmove(col, network, env, alg.projector_alg) truncation_error = max(truncation_error, info.truncation_error) condition_number = max(condition_number, info.condition_number) end diff --git a/src/algorithms/time_evolution/evoltools.jl b/src/algorithms/time_evolution/evoltools.jl index 588fd9c14..a84e6a985 100644 --- a/src/algorithms/time_evolution/evoltools.jl +++ b/src/algorithms/time_evolution/evoltools.jl @@ -77,20 +77,18 @@ to get the reduced tensors ``` 2 1 | | - 5 - A ← 3 ====> 4 - X ← 2 1 ← a ← 3 + 5 - A - 3 ====> 4 - X ← 2 1 ← a - 3 | ↘ | ↘ 4 1 3 2 2 1 | | - 5 ← B - 3 ====> 1 ← b → 3 4 → Y - 2 + 5 - B - 3 ====> 1 - b → 3 4 → Y - 2 | ↘ ↘ | 4 1 2 3 ``` """ function _qr_bond(A::PEPSTensor, B::PEPSTensor) - # TODO: relax dual requirement on the bonds - @assert isdual(space(A, 3)) # currently only allow A ← B X, a = leftorth(A, ((2, 4, 5), (1, 3))) Y, b = leftorth(B, ((2, 3, 4), (1, 5))) @assert !isdual(space(a, 1)) @@ -125,7 +123,7 @@ $(SIGNATURES) Apply 2-site `gate` on the reduced matrices `a`, `b` ``` - -1← a -← 3 -← b ← -4 + -1← a -- 3 -- b ← -4 ↓ ↓ 1 2 ↓ ↓ @@ -135,8 +133,8 @@ Apply 2-site `gate` on the reduced matrices `a`, `b` ``` """ function _apply_gate( - a::AbstractTensorMap{T,S}, - b::AbstractTensorMap{T,S}, + a::AbstractTensorMap, + b::AbstractTensorMap, gate::AbstractTensorMap{T,S,2,2}, trscheme::TruncationScheme, ) where {T<:Number,S<:ElementarySpace} diff --git a/src/algorithms/time_evolution/fullupdate.jl b/src/algorithms/time_evolution/fullupdate.jl new file mode 100644 index 000000000..85049a202 --- /dev/null +++ b/src/algorithms/time_evolution/fullupdate.jl @@ -0,0 +1,155 @@ +""" +$(TYPEDEF) + +Algorithm struct for full update (FU) of infinite PEPS. + +## Fields + +$(TYPEDFIELDS) +""" +@kwdef struct FullUpdate + "Time evolution step, such that the Trotter gate is exp(-dt * Hᵢⱼ). + Use imaginary `dt` for real time evolution." + dt::Number + "Number of evolution steps without fully reconverging the environment." + niter::Int + "Fix gauge of bond environment." + fixgauge::Bool = true + "Bond truncation algorithm after applying time evolution gate." + opt_alg::Union{ALSTruncation,FullEnvTruncation} = ALSTruncation(; + trscheme=truncerr(1e-10) + ) + "CTMRG algorithm to reconverge environment. + Its `projector_alg` is also used for the fast update + of the environment after each FU iteration." + ctm_alg::CTMRGAlgorithm = SequentialCTMRG(; + tol=1e-9, + maxiter=20, + verbosity=1, + trscheme=truncerr(1e-10), + projector_alg=:fullinfinite, + ) +end + +""" +Full update for the bond between `[row, col]` and `[row, col+1]`. +""" +function _fu_xbond!( + row::Int, + col::Int, + gate::AbstractTensorMap{T,S,2,2}, + peps::InfinitePEPS, + env::CTMRGEnv, + alg::FullUpdate, +) where {T<:Number,S<:ElementarySpace} + cp1 = _next(col, size(peps, 2)) + A, B = peps[row, col], peps[row, cp1] + X, a, b, Y = _qr_bond(A, B) + # positive/negative-definite approximant: benv = ± Z Z† + benv = bondenv_fu(row, col, X, Y, env) + Z = positive_approx(benv) + @debug "cond(benv) before gauge fix: $(LinearAlgebra.cond(Z' * Z))" + # fix gauge + if alg.fixgauge + Z, a, b, (Linv, Rinv) = fixgauge_benv(Z, a, b) + X, Y = _fixgauge_benvXY(X, Y, Linv, Rinv) + @debug "cond(L) = $(LinearAlgebra.cond(Linv)); cond(R): $(LinearAlgebra.cond(Rinv))" + @debug "cond(benv) after gauge fix: $(LinearAlgebra.cond(Z' * Z))" + end + benv = Z' * Z + # apply gate + need_flip = isdual(space(b, 1)) + a, s, b, = _apply_gate(a, b, gate, truncerr(1e-15)) + a, b = absorb_s(a, s, b) + # optimize a, b + a, s, b, info = bond_truncate(a, b, benv, alg.opt_alg) + a, b = absorb_s(a, s, b) + # bond truncation is done with arrow `a ← b`. + # now revert back to `a → b` when needed. + if need_flip + a, b = flip(a, 3), flip(b, 1) + end + a /= norm(a, Inf) + b /= norm(b, Inf) + A, B = _qr_bond_undo(X, a, b, Y) + peps.A[row, col] = A / norm(A, Inf) + peps.A[row, cp1] = B / norm(B, Inf) + return s, info +end + +""" +Update all horizontal bonds in the c-th column +(i.e. `(r,c) (r,c+1)` for all `r = 1, ..., Nr`). +To update rows, rotate the network clockwise by 90 degrees. +The iPEPS `peps` is modified in place. +""" +function _fu_column!( + col::Int, gate::LocalOperator, peps::InfinitePEPS, env::CTMRGEnv, alg::FullUpdate +) + Nr, Nc = size(peps) + @assert 1 <= col <= Nc + fid = 1.0 + wts_col = Vector{PEPSWeight}(undef, Nr) + for row in 1:Nr + term = get_gateterm(gate, (CartesianIndex(row, col), CartesianIndex(row, col + 1))) + wts_col[row], info = _fu_xbond!(row, col, term, peps, env, alg) + fid = min(fid, info.fid) + end + # update CTMRGEnv + network = InfiniteSquareNetwork(peps) + env2, info = ctmrg_leftmove(col, network, env, alg.ctm_alg.projector_alg) + env2, info = ctmrg_rightmove(_next(col, Nc), network, env2, alg.ctm_alg.projector_alg) + for c in [col, _next(col, Nc)] + env.corners[:, :, c] = env2.corners[:, :, c] + env.edges[:, :, c] = env2.edges[:, :, c] + end + return wts_col, fid +end + +""" +One round of full update on the input InfinitePEPS `peps` and its CTMRGEnv `env`. + +Reference: Physical Review B 92, 035142 (2015) +""" +function fu_iter(gate::LocalOperator, peps::InfinitePEPS, env::CTMRGEnv, alg::FullUpdate) + Nr, Nc = size(peps) + fidmin = 1.0 + peps2, env2 = deepcopy(peps), deepcopy(env) + wts = Array{PEPSWeight}(undef, 2, Nr, Nc) + for i in 1:4 + N = size(peps2, 2) + for col in 1:N + wts_col, fid_col = _fu_column!(col, gate, peps2, env2, alg) + fid = min(fidmin, fid_col) + # assign the weights to the un-rotated `wts` + if i == 1 + wts[1, :, col] = wts_col + elseif i == 2 + wts[2, _next(col, N), :] = reverse(wts_col) + elseif i == 3 + wts[1, :, mod1(N - col, N)] = reverse(wts_col) + else + wts[2, N + 1 - col, :] = wts_col + end + end + gate, peps2, env2 = rotl90(gate), rotl90(peps2), rotl90(env2) + end + return peps2, env2, SUWeight(collect(wt for wt in wts)), fidmin +end + +""" +Full update an infinite PEPS with nearest neighbor Hamiltonian. +""" +function fu_iter2(ham::LocalOperator, peps::InfinitePEPS, env::CTMRGEnv, alg::FullUpdate) + # Each NN bond is updated twice in _fu_iter, + # thus `dt` is divided by 2 when exponentiating `ham`. + gate = get_expham(alg.dt / 2, ham) + wts, fidmin = nothing, 1.0 + for it in 1:(alg.niter) + peps, env, wts, fid = fu_iter(gate, peps, env, alg) + fidmin = min(fidmin, fid) + end + # reconverge environment + env, = leading_boundary(env, peps, alg.ctm_alg) + return peps, env, wts, fidmin +end diff --git a/test/runtests.jl b/test/runtests.jl index 29f8fc0e3..fd34ed263 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -62,6 +62,14 @@ end include("timeevol/cluster_projectors.jl") end end + if GROUP == "ALL" || GROUP == "TIMEEVOL" + @time @safetestset "Cluster truncation with projectors" begin + include("timeevol/cluster_projectors.jl") + end + @time @safetestset "Transverse Field Ising model: real-time full update " begin + include("timeevol/tf_ising_fu.jl") + end + end if GROUP == "ALL" || GROUP == "UTILITY" @time @safetestset "SVD wrapper" begin include("utility/svd_wrapper.jl") diff --git a/test/timeevol/tf_ising_fu.jl b/test/timeevol/tf_ising_fu.jl new file mode 100644 index 000000000..a945eb97b --- /dev/null +++ b/test/timeevol/tf_ising_fu.jl @@ -0,0 +1,98 @@ +using Test +using TensorKit +import MPSKitModels: S_zz, σˣ +using PEPSKit +using Printf +using Random +Random.seed!(0) + +const hc = 3.044382 +const formatter = Printf.Format("t = %.2f, ⟨σˣ⟩ = %.7e + %.7e im. Time = %.3f s") +# real time evolution of ⟨σx⟩ +# benchmark data from Physical Review B 104, 094411 (2021) Figure 6(a) +# calculated with D = 8 and χ = 4 D = 32 +const data = [ + 0.01 9.9920027e-01 + 0.06 9.7274912e-01 + 0.11 9.1973182e-01 + 0.16 8.6230618e-01 + 0.21 8.1894325e-01 + 0.26 8.0003708e-01 + 0.31 8.0081082e-01 + 0.36 8.0979257e-01 + 0.41 8.1559623e-01 + 0.46 8.1541661e-01 + 0.51 8.1274128e-01 +] + +# redefine tfising Hamiltonian with only 2-site gate +function tfising( + T::Type{<:Number}, + S::Union{Type{Trivial},Type{Z2Irrep}}, + lattice::InfiniteSquare; + J=1.0, + g=1.0, +) + ZZ = rmul!(4 * S_zz(T, S), -J) + X = rmul!(σˣ(T, S), g * -J) + unit = id(space(X, 1)) + gate = ZZ + (1 / 4) * (unit ⊗ X + X ⊗ unit) + spaces = fill(domain(X)[1], (lattice.Nrows, lattice.Ncols)) + return LocalOperator( + spaces, (neighbor => gate for neighbor in PEPSKit.nearest_neighbours(lattice))... + ) +end + +function tfising_fu(g::Float64, maxiter::Int, Dcut::Int, chi::Int; als=true, use_pinv=false) + # the fully polarized state + peps = InfinitePEPS(randn, ComplexF64, 2, 1; unitcell=(2, 2)) + for t in peps.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 peps.A) + op = LocalOperator(lattice, ((1, 1),) => σˣ()) + ham = tfising(ComplexF64, Trivial, InfiniteSquare(2, 2); J=1.0, g=g) + + trscheme_peps = truncerr(1e-10) & truncdim(Dcut) + trscheme_env = truncerr(1e-10) & truncdim(chi) + env = CTMRGEnv(rand, ComplexF64, peps, chi) + env, = leading_boundary(env, peps; tol=1e-10, verbosity=2, trscheme=trscheme_env) + + ctm_alg = SequentialCTMRG(; + tol=1e-9, + maxiter=50, + verbosity=2, + trscheme=trscheme_env, + projector_alg=:fullinfinite, + ) + opt_alg = ALSTruncation(; trscheme=trscheme_peps, tol=1e-10, use_pinv=true) + # opt_alg = FullEnvTruncation(; trscheme=trscheme_peps, tol=1e-10) + + # do one extra step at the beginning to match benchmark data + t = 0.01 + fu_alg = FullUpdate(; dt=0.01im, niter=1, opt_alg, ctm_alg) + time0 = time() + peps, env, = fu_iter2(ham, peps, env, fu_alg) + magx = expectation_value(peps, op, env) + time1 = time() + @info Printf.format(formatter, t, real(magx), imag(magx), time1 - time0) + @test isapprox(magx, data[1, 2]; atol=0.005) + + fu_alg = FullUpdate(; dt=0.01im, niter=5, opt_alg, ctm_alg) + for count in 1:maxiter + time0 = time() + peps, env, = fu_iter2(ham, peps, env, fu_alg) + normalize!.(peps.A, Inf) + magx = expectation_value(peps, op, env) + time1 = time() + t += 0.05 + @info Printf.format(formatter, t, real(magx), imag(magx), time1 - time0) + @test isapprox(magx, data[count + 1, 2]; atol=0.005) + flush(stdout) + flush(stderr) + end + return nothing +end + +tfising_fu(hc, 10, 6, 24; als=false, use_pinv=false) From f22f5d032beddf4511185d2fd3f7c716f681aa76 Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Sat, 14 Jun 2025 10:43:56 +0800 Subject: [PATCH 05/62] Fix size of `dt`again in 3-site SU --- src/algorithms/time_evolution/evoltools.jl | 22 ++++++++++++++----- src/algorithms/time_evolution/simpleupdate.jl | 2 +- .../time_evolution/simpleupdate3site.jl | 12 +++++----- src/operators/localoperator.jl | 4 ++++ 4 files changed, 26 insertions(+), 14 deletions(-) diff --git a/src/algorithms/time_evolution/evoltools.jl b/src/algorithms/time_evolution/evoltools.jl index 299266855..93de27691 100644 --- a/src/algorithms/time_evolution/evoltools.jl +++ b/src/algorithms/time_evolution/evoltools.jl @@ -182,19 +182,29 @@ Obtain the 3-site gate MPO on the southeast cluster at position `[row, col]` ``` """ function _get_gatempo_se(ham::LocalOperator, dt::Number, row::Int, col::Int) - Nr, Nc = size(ham.lattice) + Nr, Nc = size(ham) @assert 1 <= row <= Nr && 1 <= col <= Nc - unit = id(space(ham.terms[1].second, 1)) - sites = ( + sites = [ CartesianIndex(row, col), CartesianIndex(row, col + 1), CartesianIndex(row - 1, col + 1), - ) + ] nb1x = get_gateterm(ham, (sites[1], sites[2])) nb1y = get_gateterm(ham, (sites[2], sites[3])) nb2 = get_gateterm(ham, (sites[1], sites[3])) - op = (1 / 2) * (nb1x ⊗ unit + unit ⊗ nb1y) + permute(nb2 ⊗ unit, ((1, 3, 2), (4, 6, 5))) - op = exp(-dt * op) + # identity operator at each site + units = map(sites) do site + site_ = CartesianIndex(mod1(site[1], Nr), mod1(site[2], Nc)) + return id(physicalspace(ham)[site_]) + end + # when iterating through ┘, └, ┌, ┐ clusters in the unit cell, + # NN / NNN bonds are counted 4 / 2 times, respectively. + @tensor Odt[i' j' k'; i j k] := + -dt * ( + (nb1x[i' j'; i j] * units[3][k' k] + units[1][i'; i] * nb1y[j' k'; j k]) / 4 + + (nb2[i' k'; i k] * units[2][j'; j]) / 2 + ) + op = exp(Odt) return gate_to_mpo3(op) end diff --git a/src/algorithms/time_evolution/simpleupdate.jl b/src/algorithms/time_evolution/simpleupdate.jl index de75c323d..a84e40a77 100644 --- a/src/algorithms/time_evolution/simpleupdate.jl +++ b/src/algorithms/time_evolution/simpleupdate.jl @@ -9,7 +9,7 @@ Each SU run is converged when the singular value difference becomes smaller than $(TYPEDFIELDS) """ struct SimpleUpdate - dt::Float64 + dt::Number tol::Float64 maxiter::Int trscheme::TensorKit.TruncationScheme diff --git a/src/algorithms/time_evolution/simpleupdate3site.jl b/src/algorithms/time_evolution/simpleupdate3site.jl index f34d989b5..1ce887b5b 100644 --- a/src/algorithms/time_evolution/simpleupdate3site.jl +++ b/src/algorithms/time_evolution/simpleupdate3site.jl @@ -432,14 +432,12 @@ function _simpleupdate3site( peps::InfiniteWeightPEPS, ham::LocalOperator, alg::SimpleUpdate; check_interval::Int=500 ) time_start = time() - # Convert Hamiltonian to 3-site exponentiated gate MPOs. - # Since each bond is updated 4 times, - # `dt` for each MPO should be divided by 4 + # convert Hamiltonian to 3-site exponentiated gate MPOs gatempos = [ - _get_gatempos_se(ham, alg.dt / 4), - _get_gatempos_se(rotl90(ham), alg.dt / 4), - _get_gatempos_se(rot180(ham), alg.dt / 4), - _get_gatempos_se(rotr90(ham), alg.dt / 4), + _get_gatempos_se(ham, alg.dt), + _get_gatempos_se(rotl90(ham), alg.dt), + _get_gatempos_se(rot180(ham), alg.dt), + _get_gatempos_se(rotr90(ham), alg.dt), ] wtdiff = 1.0 wts0 = deepcopy(peps.weights) diff --git a/src/operators/localoperator.jl b/src/operators/localoperator.jl index 0bafec669..be35ec608 100644 --- a/src/operators/localoperator.jl +++ b/src/operators/localoperator.jl @@ -105,6 +105,10 @@ function physicalspace(O::LocalOperator) return O.lattice end +function Base.size(O::LocalOperator) + return size(O.lattice) +end + # Real and imaginary part # ----------------------- function Base.real(O::LocalOperator) From 3519e8284db73d402e9e32942745948e971d63fe Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Sat, 14 Jun 2025 15:56:47 +0800 Subject: [PATCH 06/62] Minor fix --- src/algorithms/time_evolution/fullupdate.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/algorithms/time_evolution/fullupdate.jl b/src/algorithms/time_evolution/fullupdate.jl index 85049a202..d0fec9e55 100644 --- a/src/algorithms/time_evolution/fullupdate.jl +++ b/src/algorithms/time_evolution/fullupdate.jl @@ -120,7 +120,7 @@ function fu_iter(gate::LocalOperator, peps::InfinitePEPS, env::CTMRGEnv, alg::Fu N = size(peps2, 2) for col in 1:N wts_col, fid_col = _fu_column!(col, gate, peps2, env2, alg) - fid = min(fidmin, fid_col) + fidmin = min(fidmin, fid_col) # assign the weights to the un-rotated `wts` if i == 1 wts[1, :, col] = wts_col From d8cffce5d09fb345f0b095e14cedaa66b4702f32 Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Sat, 14 Jun 2025 21:01:29 +0800 Subject: [PATCH 07/62] Update localoperator.jl Co-authored-by: Lukas Devos --- src/operators/localoperator.jl | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/operators/localoperator.jl b/src/operators/localoperator.jl index be35ec608..1d925792e 100644 --- a/src/operators/localoperator.jl +++ b/src/operators/localoperator.jl @@ -105,9 +105,7 @@ function physicalspace(O::LocalOperator) return O.lattice end -function Base.size(O::LocalOperator) - return size(O.lattice) -end +Base.size(O::LocalOperator) = size(physicalspace(O)) # Real and imaginary part # ----------------------- From 97bd5ae1c2234bcc0baf233cabbb9086f64a2756 Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Mon, 16 Jun 2025 10:28:08 +0800 Subject: [PATCH 08/62] Change argument order in `get_expham` --- src/algorithms/time_evolution/evoltools.jl | 4 ++-- src/algorithms/time_evolution/simpleupdate.jl | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/algorithms/time_evolution/evoltools.jl b/src/algorithms/time_evolution/evoltools.jl index 93de27691..4dfab91da 100644 --- a/src/algorithms/time_evolution/evoltools.jl +++ b/src/algorithms/time_evolution/evoltools.jl @@ -1,11 +1,11 @@ """ - get_expham(dt::Number, H::LocalOperator) + get_expham(H::LocalOperator, dt::Number) Compute `exp(-dt * op)` for each term `op` in `H`, and combine them into a new LocalOperator. Each `op` in `H` must be a single `TensorMap`. """ -function get_expham(dt::Number, H::LocalOperator) +function get_expham(H::LocalOperator, dt::Number) return LocalOperator( physicalspace(H), (sites => exp(-dt * op) for (sites, op) in H.terms)... ) diff --git a/src/algorithms/time_evolution/simpleupdate.jl b/src/algorithms/time_evolution/simpleupdate.jl index a84e40a77..23fc25ce4 100644 --- a/src/algorithms/time_evolution/simpleupdate.jl +++ b/src/algorithms/time_evolution/simpleupdate.jl @@ -128,7 +128,7 @@ function _simpleupdate2site( ) time_start = time() # exponentiating the 2-site Hamiltonian gate - gate = get_expham(alg.dt, ham) + gate = get_expham(ham, alg.dt) wtdiff = 1.0 wts0 = deepcopy(peps.weights) for count in 1:(alg.maxiter) From ee1cdebb86b93b6c357f108e4f7300ef1f9ed873 Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Mon, 16 Jun 2025 10:29:40 +0800 Subject: [PATCH 09/62] Change argument order in `get_expham` --- src/algorithms/time_evolution/fullupdate.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/algorithms/time_evolution/fullupdate.jl b/src/algorithms/time_evolution/fullupdate.jl index d0fec9e55..6afc8e62c 100644 --- a/src/algorithms/time_evolution/fullupdate.jl +++ b/src/algorithms/time_evolution/fullupdate.jl @@ -143,7 +143,7 @@ Full update an infinite PEPS with nearest neighbor Hamiltonian. function fu_iter2(ham::LocalOperator, peps::InfinitePEPS, env::CTMRGEnv, alg::FullUpdate) # Each NN bond is updated twice in _fu_iter, # thus `dt` is divided by 2 when exponentiating `ham`. - gate = get_expham(alg.dt / 2, ham) + gate = get_expham(ham, alg.dt / 2) wts, fidmin = nothing, 1.0 for it in 1:(alg.niter) peps, env, wts, fid = fu_iter(gate, peps, env, alg) From 06a929e7fc8359e1e8ac5fb9f71784e1d4dc1b4d Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Wed, 18 Jun 2025 11:42:28 +0800 Subject: [PATCH 10/62] Revert unintended changes --- src/algorithms/time_evolution/evoltools.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/algorithms/time_evolution/evoltools.jl b/src/algorithms/time_evolution/evoltools.jl index a312471d2..3db3e5032 100644 --- a/src/algorithms/time_evolution/evoltools.jl +++ b/src/algorithms/time_evolution/evoltools.jl @@ -133,8 +133,8 @@ Apply 2-site `gate` on the reduced matrices `a`, `b` ``` """ function _apply_gate( - a::AbstractTensorMap, - b::AbstractTensorMap, + a::AbstractTensorMap{T,S}, + b::AbstractTensorMap{T,S}, gate::AbstractTensorMap{T,S,2,2}, trscheme::TruncationScheme, ) where {T<:Number,S<:ElementarySpace} From 82aa58486cf65267d0669cd66fabec9833e48d90 Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Tue, 23 Sep 2025 17:01:02 +0800 Subject: [PATCH 11/62] Fix formatting and change function signature --- .../contractions/bondenv/als_solve.jl | 4 +- src/algorithms/time_evolution/fullupdate.jl | 60 +++++++--------- src/algorithms/truncation/bond_truncation.jl | 14 +++- test/timeevol/tf_ising_fu.jl | 72 +++++++++---------- 4 files changed, 77 insertions(+), 73 deletions(-) diff --git a/src/algorithms/contractions/bondenv/als_solve.jl b/src/algorithms/contractions/bondenv/als_solve.jl index d9bc91921..f86bf3112 100644 --- a/src/algorithms/contractions/bondenv/als_solve.jl +++ b/src/algorithms/contractions/bondenv/als_solve.jl @@ -208,8 +208,8 @@ function _solve_ab( end function _solve_ab_pinv!( - Rx::AbstractTensorMap{T,S,2,2}, Sx::AbstractTensorMap{T,S,2,1}; kwargs... -) where {T<:Number,S<:ElementarySpace} + Rx::AbstractTensorMap{T, S, 2, 2}, Sx::AbstractTensorMap{T, S, 2, 1}; kwargs... + ) where {T <: Number, S <: ElementarySpace} Rx_inv, ϵ = _pinv!(copy(Rx); kwargs...) is = filter(i -> isdual(codomain(Rx_inv, i)), 1:numout(Rx_inv)) x = Rx_inv * Sx diff --git a/src/algorithms/time_evolution/fullupdate.jl b/src/algorithms/time_evolution/fullupdate.jl index 6afc8e62c..63692c199 100644 --- a/src/algorithms/time_evolution/fullupdate.jl +++ b/src/algorithms/time_evolution/fullupdate.jl @@ -16,18 +16,18 @@ $(TYPEDFIELDS) "Fix gauge of bond environment." fixgauge::Bool = true "Bond truncation algorithm after applying time evolution gate." - opt_alg::Union{ALSTruncation,FullEnvTruncation} = ALSTruncation(; - trscheme=truncerr(1e-10) + opt_alg::Union{ALSTruncation, FullEnvTruncation} = ALSTruncation(; + trscheme = truncerr(1.0e-10) ) "CTMRG algorithm to reconverge environment. Its `projector_alg` is also used for the fast update of the environment after each FU iteration." ctm_alg::CTMRGAlgorithm = SequentialCTMRG(; - tol=1e-9, - maxiter=20, - verbosity=1, - trscheme=truncerr(1e-10), - projector_alg=:fullinfinite, + tol = 1.0e-9, + maxiter = 20, + verbosity = 1, + trscheme = truncerr(1.0e-10), + projector_alg = :fullinfinite, ) end @@ -35,13 +35,13 @@ end Full update for the bond between `[row, col]` and `[row, col+1]`. """ function _fu_xbond!( - row::Int, - col::Int, - gate::AbstractTensorMap{T,S,2,2}, - peps::InfinitePEPS, - env::CTMRGEnv, - alg::FullUpdate, -) where {T<:Number,S<:ElementarySpace} + row::Int, + col::Int, + gate::AbstractTensorMap{T, S, 2, 2}, + peps::InfinitePEPS, + env::CTMRGEnv, + alg::FullUpdate, + ) where {T <: Number, S <: ElementarySpace} cp1 = _next(col, size(peps, 2)) A, B = peps[row, col], peps[row, cp1] X, a, b, Y = _qr_bond(A, B) @@ -58,22 +58,16 @@ function _fu_xbond!( end benv = Z' * Z # apply gate - need_flip = isdual(space(b, 1)) - a, s, b, = _apply_gate(a, b, gate, truncerr(1e-15)) - a, b = absorb_s(a, s, b) + a, s, b, = _apply_gate(a, b, gate, truncerr(1.0e-15)) # optimize a, b a, s, b, info = bond_truncate(a, b, benv, alg.opt_alg) - a, b = absorb_s(a, s, b) - # bond truncation is done with arrow `a ← b`. - # now revert back to `a → b` when needed. - if need_flip - a, b = flip(a, 3), flip(b, 1) - end - a /= norm(a, Inf) - b /= norm(b, Inf) + normalize!(a, Inf) + normalize!(b, Inf) A, B = _qr_bond_undo(X, a, b, Y) - peps.A[row, col] = A / norm(A, Inf) - peps.A[row, cp1] = B / norm(B, Inf) + normalize!(A, Inf) + normalize!(B, Inf) + normalize!(s, Inf) + peps.A[row, col], peps.A[row, cp1] = A, B return s, info end @@ -84,8 +78,8 @@ To update rows, rotate the network clockwise by 90 degrees. The iPEPS `peps` is modified in place. """ function _fu_column!( - col::Int, gate::LocalOperator, peps::InfinitePEPS, env::CTMRGEnv, alg::FullUpdate -) + col::Int, gate::LocalOperator, peps::InfinitePEPS, env::CTMRGEnv, alg::FullUpdate + ) Nr, Nc = size(peps) @assert 1 <= col <= Nc fid = 1.0 @@ -111,7 +105,7 @@ One round of full update on the input InfinitePEPS `peps` and its CTMRGEnv `env` Reference: Physical Review B 92, 035142 (2015) """ -function fu_iter(gate::LocalOperator, peps::InfinitePEPS, env::CTMRGEnv, alg::FullUpdate) +function fu_iter(peps::InfinitePEPS, gate::LocalOperator, alg::FullUpdate, env::CTMRGEnv) Nr, Nc = size(peps) fidmin = 1.0 peps2, env2 = deepcopy(peps), deepcopy(env) @@ -140,13 +134,13 @@ end """ Full update an infinite PEPS with nearest neighbor Hamiltonian. """ -function fu_iter2(ham::LocalOperator, peps::InfinitePEPS, env::CTMRGEnv, alg::FullUpdate) - # Each NN bond is updated twice in _fu_iter, +function fu_iter2(peps::InfinitePEPS, ham::LocalOperator, alg::FullUpdate, env::CTMRGEnv) + # Each NN bond is updated twice in _fu_iter, # thus `dt` is divided by 2 when exponentiating `ham`. gate = get_expham(ham, alg.dt / 2) wts, fidmin = nothing, 1.0 for it in 1:(alg.niter) - peps, env, wts, fid = fu_iter(gate, peps, env, alg) + peps, env, wts, fid = fu_iter(peps, gate, alg, env) fidmin = min(fidmin, fid) end # reconverge environment diff --git a/src/algorithms/truncation/bond_truncation.jl b/src/algorithms/truncation/bond_truncation.jl index 5ec2ef1dc..dd72efaf9 100644 --- a/src/algorithms/truncation/bond_truncation.jl +++ b/src/algorithms/truncation/bond_truncation.jl @@ -67,6 +67,7 @@ function bond_truncate( @assert !isdual(space(a, 2)) @assert !isdual(space(b, 2)) @assert codomain(benv) == domain(benv) + need_flip = isdual(space(b, 1)) time00 = time() verbose = (alg.check_interval > 0) a2b2 = _combine_ab(a, b) @@ -101,7 +102,7 @@ function bond_truncate( Ra = _tensor_Ra(benv, b) Sa = _tensor_Sa(benv, b, a2b2) a, info_a = if alg.use_pinv - _solve_ab_pinv!(Ra, Sa; trunc=truncerr(1e-10)) + _solve_ab_pinv!(Ra, Sa; trunc = truncerr(1.0e-10)) else _solve_ab(Ra, Sa, a) end @@ -109,7 +110,7 @@ function bond_truncate( Rb = _tensor_Rb(benv, a) Sb = _tensor_Sb(benv, a, a2b2) b, info_b = if alg.use_pinv - _solve_ab_pinv!(Rb, Sb; trunc=truncerr(1e-10)) + _solve_ab_pinv!(Rb, Sb; trunc = truncerr(1.0e-10)) else _solve_ab(Rb, Sb, b) end @@ -142,6 +143,10 @@ function bond_truncate( a, s, b = tsvd!(permute(_combine_ab(a, b), perm_ab); trunc = alg.trscheme) # normalize singular value spectrum s /= norm(s, Inf) + a, b = absorb_s(a, s, b) + if need_flip + a, s, b = flip_svd(a, s, b) + end return a, s, b, (; fid, Δfid) end @@ -155,6 +160,7 @@ function bond_truncate( @assert !isdual(space(a, 2)) @assert !isdual(space(b, 2)) @assert codomain(benv) == domain(benv) + need_flip = isdual(space(b, 1)) #= initialize bond matrix using QR as `Ra Lb` --- a == b --- ==> - Qa - Ra == Rb - Qb - @@ -185,8 +191,12 @@ function bond_truncate( ) # 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] + if need_flip + a, s, b = flip_svd(a, s, vh) + end return a, s, b, info end diff --git a/test/timeevol/tf_ising_fu.jl b/test/timeevol/tf_ising_fu.jl index a945eb97b..855151c54 100644 --- a/test/timeevol/tf_ising_fu.jl +++ b/test/timeevol/tf_ising_fu.jl @@ -12,27 +12,27 @@ const formatter = Printf.Format("t = %.2f, ⟨σˣ⟩ = %.7e + %.7e im. Time = % # benchmark data from Physical Review B 104, 094411 (2021) Figure 6(a) # calculated with D = 8 and χ = 4 D = 32 const data = [ - 0.01 9.9920027e-01 - 0.06 9.7274912e-01 - 0.11 9.1973182e-01 - 0.16 8.6230618e-01 - 0.21 8.1894325e-01 - 0.26 8.0003708e-01 - 0.31 8.0081082e-01 - 0.36 8.0979257e-01 - 0.41 8.1559623e-01 - 0.46 8.1541661e-01 - 0.51 8.1274128e-01 + 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 ] # redefine tfising Hamiltonian with only 2-site gate function tfising( - T::Type{<:Number}, - S::Union{Type{Trivial},Type{Z2Irrep}}, - lattice::InfiniteSquare; - J=1.0, - g=1.0, -) + T::Type{<:Number}, + S::Union{Type{Trivial}, Type{Z2Irrep}}, + lattice::InfiniteSquare; + J = 1.0, + g = 1.0, + ) ZZ = rmul!(4 * S_zz(T, S), -J) X = rmul!(σˣ(T, S), g * -J) unit = id(space(X, 1)) @@ -43,56 +43,56 @@ function tfising( ) end -function tfising_fu(g::Float64, maxiter::Int, Dcut::Int, chi::Int; als=true, use_pinv=false) +function tfising_fu(g::Float64, maxiter::Int, Dcut::Int, chi::Int; als = true, use_pinv = false) # the fully polarized state - peps = InfinitePEPS(randn, ComplexF64, 2, 1; unitcell=(2, 2)) + peps = InfinitePEPS(randn, ComplexF64, 2, 1; unitcell = (2, 2)) for t in peps.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 peps.A) op = LocalOperator(lattice, ((1, 1),) => σˣ()) - ham = tfising(ComplexF64, Trivial, InfiniteSquare(2, 2); J=1.0, g=g) + ham = tfising(ComplexF64, Trivial, InfiniteSquare(2, 2); J = 1.0, g = g) - trscheme_peps = truncerr(1e-10) & truncdim(Dcut) - trscheme_env = truncerr(1e-10) & truncdim(chi) + trscheme_peps = truncerr(1.0e-10) & truncdim(Dcut) + trscheme_env = truncerr(1.0e-10) & truncdim(chi) env = CTMRGEnv(rand, ComplexF64, peps, chi) - env, = leading_boundary(env, peps; tol=1e-10, verbosity=2, trscheme=trscheme_env) + env, = leading_boundary(env, peps; tol = 1.0e-10, verbosity = 2, trscheme = trscheme_env) ctm_alg = SequentialCTMRG(; - tol=1e-9, - maxiter=50, - verbosity=2, - trscheme=trscheme_env, - projector_alg=:fullinfinite, + tol = 1.0e-9, + maxiter = 50, + verbosity = 2, + trscheme = trscheme_env, + projector_alg = :fullinfinite, ) - opt_alg = ALSTruncation(; trscheme=trscheme_peps, tol=1e-10, use_pinv=true) + opt_alg = ALSTruncation(; trscheme = trscheme_peps, tol = 1.0e-10, use_pinv = true) # opt_alg = FullEnvTruncation(; trscheme=trscheme_peps, tol=1e-10) # do one extra step at the beginning to match benchmark data t = 0.01 - fu_alg = FullUpdate(; dt=0.01im, niter=1, opt_alg, ctm_alg) + fu_alg = FullUpdate(; dt = 0.01im, niter = 1, opt_alg, ctm_alg) time0 = time() - peps, env, = fu_iter2(ham, peps, env, fu_alg) + peps, env, = fu_iter2(peps, ham, fu_alg, env) magx = expectation_value(peps, op, env) time1 = time() @info Printf.format(formatter, t, real(magx), imag(magx), time1 - time0) - @test isapprox(magx, data[1, 2]; atol=0.005) + @test isapprox(magx, data[1, 2]; atol = 0.005) - fu_alg = FullUpdate(; dt=0.01im, niter=5, opt_alg, ctm_alg) + fu_alg = FullUpdate(; dt = 0.01im, niter = 5, opt_alg, ctm_alg) for count in 1:maxiter time0 = time() - peps, env, = fu_iter2(ham, peps, env, fu_alg) + peps, env, = fu_iter2(peps, ham, fu_alg, env) normalize!.(peps.A, Inf) magx = expectation_value(peps, op, env) time1 = time() t += 0.05 @info Printf.format(formatter, t, real(magx), imag(magx), time1 - time0) - @test isapprox(magx, data[count + 1, 2]; atol=0.005) + @test isapprox(magx, data[count + 1, 2]; atol = 0.005) flush(stdout) flush(stderr) end return nothing end -tfising_fu(hc, 10, 6, 24; als=false, use_pinv=false) +tfising_fu(hc, 10, 6, 24; als = false, use_pinv = false) From 3f916ca799dbf98bb9c641151ddad29a8ac3a600 Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Tue, 23 Sep 2025 21:00:33 +0800 Subject: [PATCH 12/62] Fix bond_truncate test --- test/bondenv/bond_truncate.jl | 2 +- test/runtests.jl | 2 +- test/timeevol/{tf_ising_fu.jl => tf_ising_realtime.jl} | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) rename test/timeevol/{tf_ising_fu.jl => tf_ising_realtime.jl} (98%) diff --git a/test/bondenv/bond_truncate.jl b/test/bondenv/bond_truncate.jl index acad3c088..0f8927628 100644 --- a/test/bondenv/bond_truncate.jl +++ b/test/bondenv/bond_truncate.jl @@ -37,9 +37,9 @@ 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]) - a1, b1 = PEPSKit.absorb_s(a1, ss[label], b1) @test info.fid ≈ PEPSKit.fidelity(benv, PEPSKit._combine_ab(a1, b1), a2b2) @test info.fid > fid0 end + @test isapprox(ss["ALS"], ss["ALS (pinv)"], atol = 1.0e-3) @test isapprox(ss["ALS"], ss["FET"], atol = 1.0e-3) end diff --git a/test/runtests.jl b/test/runtests.jl index e47b095d2..9a92e8750 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -65,7 +65,7 @@ end include("timeevol/sitedep_truncation.jl") end @time @safetestset "Transverse Field Ising model: real-time full update " begin - include("timeevol/tf_ising_fu.jl") + include("timeevol/tf_ising_realtime.jl") end end if GROUP == "ALL" || GROUP == "TOOLBOX" diff --git a/test/timeevol/tf_ising_fu.jl b/test/timeevol/tf_ising_realtime.jl similarity index 98% rename from test/timeevol/tf_ising_fu.jl rename to test/timeevol/tf_ising_realtime.jl index 855151c54..0791c173b 100644 --- a/test/timeevol/tf_ising_fu.jl +++ b/test/timeevol/tf_ising_realtime.jl @@ -10,7 +10,7 @@ const hc = 3.044382 const formatter = Printf.Format("t = %.2f, ⟨σˣ⟩ = %.7e + %.7e im. Time = %.3f s") # real time evolution of ⟨σx⟩ # benchmark data from Physical Review B 104, 094411 (2021) Figure 6(a) -# calculated with D = 8 and χ = 4 D = 32 +# calculated with D = 8 and χ = 4D = 32 const data = [ 0.01 9.9920027e-1 0.06 9.7274912e-1 From 0c387ce0f33fc3ed25a12a0fe3573f49e9ef869b Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Fri, 26 Sep 2025 10:12:27 +0800 Subject: [PATCH 13/62] Add full update for PEPO (experimental) --- src/PEPSKit.jl | 2 +- .../contractions/bondenv/benv_ctm.jl | 30 ++++++-- .../contractions/bondenv/gaugefix.jl | 31 +++++--- src/algorithms/time_evolution/evoltools.jl | 3 +- src/algorithms/time_evolution/fullupdate.jl | 75 ++++++++++++------- src/algorithms/truncation/bond_truncation.jl | 2 +- test/bondenv/benv_fu.jl | 71 +++++++++++------- test/timeevol/tf_ising_realtime.jl | 18 ++--- 8 files changed, 146 insertions(+), 86 deletions(-) diff --git a/src/PEPSKit.jl b/src/PEPSKit.jl index 31b3a80ec..ca1ee8919 100644 --- a/src/PEPSKit.jl +++ b/src/PEPSKit.jl @@ -101,7 +101,7 @@ export fixedpoint export absorb_weight export ALSTruncation, FullEnvTruncation export su_iter, su3site_iter, simpleupdate, SimpleUpdate -export fu_iter, fu_iter2, FullUpdate +export fu_iter, fullupdate, FullUpdate export InfiniteSquareNetwork export InfinitePartitionFunction diff --git a/src/algorithms/contractions/bondenv/benv_ctm.jl b/src/algorithms/contractions/bondenv/benv_ctm.jl index a936a74f5..979aa762e 100644 --- a/src/algorithms/contractions/bondenv/benv_ctm.jl +++ b/src/algorithms/contractions/bondenv/benv_ctm.jl @@ -8,7 +8,8 @@ Construct the environment (norm) tensor C4---T3---------T3---C3 r+1 c-1 c c+1 c+2 ``` -where `XX = X' X` and `YY = Y' Y` (stacked together). +where `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 +22,9 @@ Axis order: `[DX1 DY1; DX0 DY0]`, as in └---------------------┘ ``` """ -function bondenv_fu(row::Int, col::Int, X::PEPSOrth, Y::PEPSOrth, env::CTMRGEnv) +function bondenv_fu( + row::Int, col::Int, X::PO, Y::PO, env::CTMRGEnv + ) where {PO <: Union{PEPSOrth, PEPOOrth}} Nr, Nc = size(env.corners)[[2, 3]] cm1 = _prev(col, Nc) cp1 = _next(col, Nc) @@ -49,12 +52,23 @@ function bondenv_fu(row::Int, col::Int, X::PEPSOrth, Y::PEPSOrth, env::CTMRGEnv) C4--χ3--T3X---------χ5---------T3Y--χ7---C3 r+1 c-1 c c+1 c+2 =# - @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] - + benv = nothing + if PO <: PEPSOrth + @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] + else + # eliminate fermion sign when contracting the remaining physical axis in X, Y + X2 = isdual(space(X, 1)) ? twist(X, 1) : X + Y2 = isdual(space(Y, 1)) ? twist(Y, 1) : Y + @autoopt @tensor benv[DX1 DY1; DX0 DY0] := + c4[χ3 χ1] * t4[χ1 DWX0 DWX1 χ2] * c1[χ2 χ4] * t3X[χ5 DSX0 DSX1 χ3] * + X2[pX DNX0 DX0 DSX0 DWX0] * conj(X[pX 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] * + Y2[pY DNY0 DEY0 DSY0 DY0] * conj(Y[pY DNY1 DEY1 DSY1 DY1]) * t1Y[χ6 DNY0 DNY1 χ8] + end normalize!(benv, Inf) return benv end diff --git a/src/algorithms/contractions/bondenv/gaugefix.jl b/src/algorithms/contractions/bondenv/gaugefix.jl index c6b7af059..ac793eaae 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/evoltools.jl b/src/algorithms/time_evolution/evoltools.jl index b153a488e..82ce89d9c 100644 --- a/src/algorithms/time_evolution/evoltools.jl +++ b/src/algorithms/time_evolution/evoltools.jl @@ -143,7 +143,7 @@ For PEPSTensors, | ↘ ↘ | -4 -1 -1 -4 ``` -For PEPOTensors +For PEPOTensors, ``` -2 -3 -2 -3 ↘ | ↘ | @@ -157,6 +157,7 @@ For PEPOTensors | ↘ | ↘ -5 -1 -5 -1 ``` +It is assumed that the physical domain and codomain spaces are not dualed. """ 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] diff --git a/src/algorithms/time_evolution/fullupdate.jl b/src/algorithms/time_evolution/fullupdate.jl index 63692c199..a02e418bc 100644 --- a/src/algorithms/time_evolution/fullupdate.jl +++ b/src/algorithms/time_evolution/fullupdate.jl @@ -1,7 +1,7 @@ """ $(TYPEDEF) -Algorithm struct for full update (FU) of infinite PEPS. +Algorithm struct for full update (FU) of InfinitePEPS or InfinitePEPO. ## Fields @@ -35,15 +35,11 @@ end Full update for the bond between `[row, col]` and `[row, col+1]`. """ function _fu_xbond!( - row::Int, - col::Int, - gate::AbstractTensorMap{T, S, 2, 2}, - peps::InfinitePEPS, - env::CTMRGEnv, - alg::FullUpdate, + state::InfiniteState, gate::AbstractTensorMap{T, S, 2, 2}, env::CTMRGEnv, + row::Int, col::Int, alg::FullUpdate ) where {T <: Number, S <: ElementarySpace} - cp1 = _next(col, size(peps, 2)) - A, B = peps[row, col], peps[row, cp1] + cp1 = _next(col, size(state, 2)) + A, B = state[row, col], state[row, cp1] X, a, b, Y = _qr_bond(A, B) # positive/negative-definite approximant: benv = ± Z Z† benv = bondenv_fu(row, col, X, Y, env) @@ -67,7 +63,7 @@ function _fu_xbond!( normalize!(A, Inf) normalize!(B, Inf) normalize!(s, Inf) - peps.A[row, col], peps.A[row, cp1] = A, B + state.A[row, col], state.A[row, cp1] = A, B return s, info end @@ -75,22 +71,27 @@ end Update all horizontal bonds in the c-th column (i.e. `(r,c) (r,c+1)` for all `r = 1, ..., Nr`). To update rows, rotate the network clockwise by 90 degrees. -The iPEPS `peps` is modified in place. +The iPEPS/iPEPO `state` is modified in place. """ function _fu_column!( - col::Int, gate::LocalOperator, peps::InfinitePEPS, env::CTMRGEnv, alg::FullUpdate + state::InfiniteState, gate::LocalOperator, + alg::FullUpdate, env::CTMRGEnv, col::Int ) - Nr, Nc = size(peps) + Nr, Nc = size(state) @assert 1 <= col <= Nc fid = 1.0 wts_col = Vector{PEPSWeight}(undef, Nr) for row in 1:Nr term = get_gateterm(gate, (CartesianIndex(row, col), CartesianIndex(row, col + 1))) - wts_col[row], info = _fu_xbond!(row, col, term, peps, env, alg) + wts_col[row], info = _fu_xbond!(state, term, env, row, col, alg) fid = min(fid, info.fid) end - # update CTMRGEnv - network = InfiniteSquareNetwork(peps) + # update 2-layer CTMRGEnv + network = if isa(state, InfinitePEPS) + InfiniteSquareNetwork(state) + else + InfiniteSquareNetwork(InfinitePEPS(state)) + end env2, info = ctmrg_leftmove(col, network, env, alg.ctm_alg.projector_alg) env2, info = ctmrg_rightmove(_next(col, Nc), network, env2, alg.ctm_alg.projector_alg) for c in [col, _next(col, Nc)] @@ -100,20 +101,29 @@ function _fu_column!( return wts_col, fid end +const _fu_pepo_warning_shown = Ref(false) + """ -One round of full update on the input InfinitePEPS `peps` and its CTMRGEnv `env`. +One round of fast full update on the input InfinitePEPS or InfinitePEPO `state` +and its 2-layer CTMRGEnv `env`, without fully reconverging `env`. Reference: Physical Review B 92, 035142 (2015) """ -function fu_iter(peps::InfinitePEPS, gate::LocalOperator, alg::FullUpdate, env::CTMRGEnv) - Nr, Nc = size(peps) +function fu_iter( + state::InfiniteState, gate::LocalOperator, alg::FullUpdate, env::CTMRGEnv + ) + if state isa InfinitePEPO && !_fu_pepo_warning_shown[] + @warn "Full update of InfinitePEPO is experimental." + _fu_pepo_warning_shown[] = true + end + Nr, Nc = size(state)[1:2] fidmin = 1.0 - peps2, env2 = deepcopy(peps), deepcopy(env) + state2, env2 = deepcopy(state), deepcopy(env) wts = Array{PEPSWeight}(undef, 2, Nr, Nc) for i in 1:4 - N = size(peps2, 2) + N = size(state2, 2) for col in 1:N - wts_col, fid_col = _fu_column!(col, gate, peps2, env2, alg) + wts_col, fid_col = _fu_column!(state2, gate, alg, env2, col) fidmin = min(fidmin, fid_col) # assign the weights to the un-rotated `wts` if i == 1 @@ -126,24 +136,31 @@ function fu_iter(peps::InfinitePEPS, gate::LocalOperator, alg::FullUpdate, env:: wts[2, N + 1 - col, :] = wts_col end end - gate, peps2, env2 = rotl90(gate), rotl90(peps2), rotl90(env2) + gate, state2, env2 = rotl90(gate), rotl90(state2), rotl90(env2) end - return peps2, env2, SUWeight(collect(wt for wt in wts)), fidmin + return state2, env2, SUWeight(collect(wt for wt in wts)), fidmin end """ Full update an infinite PEPS with nearest neighbor Hamiltonian. """ -function fu_iter2(peps::InfinitePEPS, ham::LocalOperator, alg::FullUpdate, env::CTMRGEnv) - # Each NN bond is updated twice in _fu_iter, +function fullupdate( + state::InfiniteState, ham::LocalOperator, alg::FullUpdate, env::CTMRGEnv + ) + if state isa InfinitePEPO && !_fu_pepo_warning_shown[] + @warn "Full update of InfinitePEPO is an experimental feature." + _fu_pepo_warning_shown[] = true + end + # Each NN bond is updated twice in fu_iter, # thus `dt` is divided by 2 when exponentiating `ham`. gate = get_expham(ham, alg.dt / 2) wts, fidmin = nothing, 1.0 for it in 1:(alg.niter) - peps, env, wts, fid = fu_iter(peps, gate, alg, env) + state, env, wts, fid = fu_iter(state, gate, alg, env) fidmin = min(fidmin, fid) end # reconverge environment - env, = leading_boundary(env, peps, alg.ctm_alg) - return peps, env, wts, fidmin + network = isa(state, InfinitePEPS) ? state : InfinitePEPS(state) + env, = leading_boundary(env, network, alg.ctm_alg) + return state, env, wts, fidmin end diff --git a/src/algorithms/truncation/bond_truncation.jl b/src/algorithms/truncation/bond_truncation.jl index dd72efaf9..e5a759215 100644 --- a/src/algorithms/truncation/bond_truncation.jl +++ b/src/algorithms/truncation/bond_truncation.jl @@ -196,7 +196,7 @@ function bond_truncate( @tensor a[-1 -2; -3] := Qa[-1 -2 3] * u[3 -3] @tensor b[-1; -2 -3] := vh[-1 1] * Qb[1 -2 -3] if need_flip - a, s, b = flip_svd(a, s, vh) + a, s, b = flip_svd(a, s, b) end return a, s, b, info end diff --git a/test/bondenv/benv_fu.jl b/test/bondenv/benv_fu.jl index d6a374a8b..1d2e4eacc 100644 --- a/test/bondenv/benv_fu.jl +++ b/test/bondenv/benv_fu.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, trscheme = truncerr(1.0e-10) & truncdim(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)) @@ -19,30 +23,43 @@ 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, trscheme = truncerr(1.0e-10) & truncdim(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_fu(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(2e-3, 0.0, 500, truncerr(1.0e-10) & truncdim(4)) + pepo, = simpleupdate(pepo, H, alg, wts; bipartite = false, check_interval = 100) + normalize!.(pepo.A, Inf) + return pepo +end + +function test_benv_fu(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_fu(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 + end +end + +peps = get_hubbard_peps() +pepo = get_hubbard_pepo() +for state in (peps, pepo) + test_benv_fu(state) end diff --git a/test/timeevol/tf_ising_realtime.jl b/test/timeevol/tf_ising_realtime.jl index 0791c173b..eabef2ace 100644 --- a/test/timeevol/tf_ising_realtime.jl +++ b/test/timeevol/tf_ising_realtime.jl @@ -43,7 +43,7 @@ function tfising( ) end -function tfising_fu(g::Float64, maxiter::Int, Dcut::Int, chi::Int; als = true, use_pinv = false) +function tfising_fu(g::Float64, maxiter::Int, Dcut::Int, chi::Int; als = true, use_pinv = true) # the fully polarized state peps = InfinitePEPS(randn, ComplexF64, 2, 1; unitcell = (2, 2)) for t in peps.A @@ -66,14 +66,17 @@ function tfising_fu(g::Float64, maxiter::Int, Dcut::Int, chi::Int; als = true, u trscheme = trscheme_env, projector_alg = :fullinfinite, ) - opt_alg = ALSTruncation(; trscheme = trscheme_peps, tol = 1.0e-10, use_pinv = true) - # opt_alg = FullEnvTruncation(; trscheme=trscheme_peps, tol=1e-10) + opt_alg = if als + ALSTruncation(; trscheme = trscheme_peps, tol = 1.0e-10, use_pinv) + else + FullEnvTruncation(; trscheme = trscheme_peps, tol = 1.0e-10) + end # do one extra step at the beginning to match benchmark data t = 0.01 fu_alg = FullUpdate(; dt = 0.01im, niter = 1, opt_alg, ctm_alg) time0 = time() - peps, env, = fu_iter2(peps, ham, fu_alg, env) + peps, env, = fullupdate(peps, ham, fu_alg, env) magx = expectation_value(peps, op, env) time1 = time() @info Printf.format(formatter, t, real(magx), imag(magx), time1 - time0) @@ -82,17 +85,14 @@ function tfising_fu(g::Float64, maxiter::Int, Dcut::Int, chi::Int; als = true, u fu_alg = FullUpdate(; dt = 0.01im, niter = 5, opt_alg, ctm_alg) for count in 1:maxiter time0 = time() - peps, env, = fu_iter2(peps, ham, fu_alg, env) - normalize!.(peps.A, Inf) + peps, env, = fullupdate(peps, ham, fu_alg, env) magx = expectation_value(peps, op, env) time1 = time() t += 0.05 @info Printf.format(formatter, t, real(magx), imag(magx), time1 - time0) @test isapprox(magx, data[count + 1, 2]; atol = 0.005) - flush(stdout) - flush(stderr) end return nothing end -tfising_fu(hc, 10, 6, 24; als = false, use_pinv = false) +tfising_fu(hc, 10, 6, 24; als = false, use_pinv = true) From 33d12230d2f5514eb19b04df11c6f25ad8d55d59 Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Fri, 26 Sep 2025 10:30:38 +0800 Subject: [PATCH 14/62] Fix formatting --- test/bondenv/benv_fu.jl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/bondenv/benv_fu.jl b/test/bondenv/benv_fu.jl index 1d2e4eacc..1ce423430 100644 --- a/test/bondenv/benv_fu.jl +++ b/test/bondenv/benv_fu.jl @@ -27,7 +27,7 @@ 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(2e-3, 0.0, 500, truncerr(1.0e-10) & truncdim(4)) + alg = SimpleUpdate(2.0e-3, 0.0, 500, truncerr(1.0e-10) & truncdim(4)) pepo, = simpleupdate(pepo, H, alg, wts; bipartite = false, check_interval = 100) normalize!.(pepo.A, Inf) return pepo @@ -56,6 +56,7 @@ function test_benv_fu(state::Union{InfinitePEPS, InfinitePEPO}) @tensor half2[:] := Z2[-1; 1 3] * a2[1; -2 2] * b2[2 -3; 3] @test half ≈ half2 end + return end peps = get_hubbard_peps() From 9fcf0e2d8b090efd37d403f464c8552df7e7ab9f Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Fri, 3 Oct 2025 15:21:56 +0800 Subject: [PATCH 15/62] Restore signature of `ctmrg_leftmove` --- src/algorithms/ctmrg/sequential.jl | 12 ++++++------ src/algorithms/time_evolution/fullupdate.jl | 15 +++++++++++++-- test/runtests.jl | 2 +- 3 files changed, 20 insertions(+), 9 deletions(-) diff --git a/src/algorithms/ctmrg/sequential.jl b/src/algorithms/ctmrg/sequential.jl index 4d1380786..65acbffb8 100644 --- a/src/algorithms/ctmrg/sequential.jl +++ b/src/algorithms/ctmrg/sequential.jl @@ -38,11 +38,11 @@ end CTMRG_SYMBOLS[:sequential] = SequentialCTMRG """ - ctmrg_leftmove(col::Int, network, env::CTMRGEnv, alg::ProjectorAlgorithm) + ctmrg_leftmove(col::Int, network, env::CTMRGEnv, alg::SequentialCTMRG) Perform sequential CTMRG left move on the `col`-th column. """ -function ctmrg_leftmove(col::Int, network, env::CTMRGEnv, alg::ProjectorAlgorithm) +function ctmrg_leftmove(col::Int, network, env::CTMRGEnv, alg::SequentialCTMRG) #= ----> left move C1 ← T1 ← r-1 @@ -52,17 +52,17 @@ function ctmrg_leftmove(col::Int, network, env::CTMRGEnv, alg::ProjectorAlgorith C4 → T3 → r+1 c-1 c =# - projectors, info = sequential_projectors(col, network, env, alg) + projectors, info = sequential_projectors(col, network, env, alg.projector_alg) env = renormalize_sequentially(col, projectors, network, env) return env, info end """ - ctmrg_rightmove(col::Int, network, env::CTMRGEnv, alg::ProjectorAlgorithm) + ctmrg_rightmove(col::Int, network, env::CTMRGEnv, alg::SequentialCTMRG) Perform sequential CTMRG right move on the `col`-th column. """ -function ctmrg_rightmove(col::Int, network, env::CTMRGEnv, alg::ProjectorAlgorithm) +function ctmrg_rightmove(col::Int, network, env::CTMRGEnv, alg::SequentialCTMRG) #= right move <--- ←-- T1 ← C2 r-1 @@ -83,7 +83,7 @@ function ctmrg_iteration(network, env::CTMRGEnv, alg::SequentialCTMRG) 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.projector_alg) + 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 diff --git a/src/algorithms/time_evolution/fullupdate.jl b/src/algorithms/time_evolution/fullupdate.jl index a02e418bc..55fd0b929 100644 --- a/src/algorithms/time_evolution/fullupdate.jl +++ b/src/algorithms/time_evolution/fullupdate.jl @@ -92,8 +92,19 @@ function _fu_column!( else InfiniteSquareNetwork(InfinitePEPS(state)) end - env2, info = ctmrg_leftmove(col, network, env, alg.ctm_alg.projector_alg) - env2, info = ctmrg_rightmove(_next(col, Nc), network, env2, alg.ctm_alg.projector_alg) + colmove_alg = if alg.ctm_alg isa SequentialCTMRG + alg.ctm_alg + else + # extract projector_alg to construct an auxiliary SequentialCTMRG struct + projector_alg = if alg.ctm_alg.projector_alg isa FullInfiniteProjector + :fullinfinite + else + :halfinfinite + end + SequentialCTMRG(; projector_alg) + end + env2, info = ctmrg_leftmove(col, network, env, colmove_alg) + env2, info = ctmrg_rightmove(_next(col, Nc), network, env2, colmove_alg) for c in [col, _next(col, Nc)] env.corners[:, :, c] = env2.corners[:, :, c] env.edges[:, :, c] = env2.edges[:, :, c] diff --git a/test/runtests.jl b/test/runtests.jl index 6f4aa51da..a6fb980af 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -64,7 +64,7 @@ end @time @safetestset "Time evolution with site-dependent truncation" begin include("timeevol/sitedep_truncation.jl") end - @time @safetestset "Transverse Field Ising model: real-time full update " begin + @time @safetestset "Transverse Field Ising model: real-time full update" begin include("timeevol/tf_ising_realtime.jl") end end From 5682e1c1b5e3ac440c16983be37abb9f599f8457 Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Fri, 3 Oct 2025 15:40:06 +0800 Subject: [PATCH 16/62] Update construction of colmove_alg --- src/algorithms/time_evolution/fullupdate.jl | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/algorithms/time_evolution/fullupdate.jl b/src/algorithms/time_evolution/fullupdate.jl index 55fd0b929..cd10ad26f 100644 --- a/src/algorithms/time_evolution/fullupdate.jl +++ b/src/algorithms/time_evolution/fullupdate.jl @@ -95,13 +95,9 @@ function _fu_column!( colmove_alg = if alg.ctm_alg isa SequentialCTMRG alg.ctm_alg else - # extract projector_alg to construct an auxiliary SequentialCTMRG struct - projector_alg = if alg.ctm_alg.projector_alg isa FullInfiniteProjector - :fullinfinite - else - :halfinfinite - end - SequentialCTMRG(; projector_alg) + c = alg.ctm_alg + # an auxiliary struct to pass `projector_alg` to left/right move + SequentialCTMRG(c.tol, c.maxiter, c.miniter, c.verbosity, c.projector_alg) end env2, info = ctmrg_leftmove(col, network, env, colmove_alg) env2, info = ctmrg_rightmove(_next(col, Nc), network, env2, colmove_alg) From 40aca5d18a9cc4d2263541c64be780f4838f28b2 Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Fri, 3 Oct 2025 18:48:28 +0800 Subject: [PATCH 17/62] Fix FU test on tfising --- test/timeevol/tf_ising_realtime.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/timeevol/tf_ising_realtime.jl b/test/timeevol/tf_ising_realtime.jl index eabef2ace..121088198 100644 --- a/test/timeevol/tf_ising_realtime.jl +++ b/test/timeevol/tf_ising_realtime.jl @@ -45,7 +45,7 @@ end function tfising_fu(g::Float64, maxiter::Int, Dcut::Int, chi::Int; als = true, use_pinv = true) # the fully polarized state - peps = InfinitePEPS(randn, ComplexF64, 2, 1; unitcell = (2, 2)) + peps = InfinitePEPS(randn, ComplexF64, ℂ^2, ℂ^1; unitcell = (2, 2)) for t in peps.A t[1, 1, 1, 1, 1] = 1.0 t[2, 1, 1, 1, 1] = 1.0 @@ -56,7 +56,7 @@ function tfising_fu(g::Float64, maxiter::Int, Dcut::Int, chi::Int; als = true, u trscheme_peps = truncerr(1.0e-10) & truncdim(Dcut) trscheme_env = truncerr(1.0e-10) & truncdim(chi) - env = CTMRGEnv(rand, ComplexF64, peps, chi) + env = CTMRGEnv(rand, ComplexF64, peps, ℂ^chi) env, = leading_boundary(env, peps; tol = 1.0e-10, verbosity = 2, trscheme = trscheme_env) ctm_alg = SequentialCTMRG(; From c3b06d3090542e9b1e9a9b938f5e1c3e98fec207 Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Sun, 12 Oct 2025 14:11:47 +0800 Subject: [PATCH 18/62] Simplify construction of colmove_alg --- src/algorithms/time_evolution/fullupdate.jl | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/algorithms/time_evolution/fullupdate.jl b/src/algorithms/time_evolution/fullupdate.jl index cd10ad26f..beda9aae7 100644 --- a/src/algorithms/time_evolution/fullupdate.jl +++ b/src/algorithms/time_evolution/fullupdate.jl @@ -92,13 +92,8 @@ function _fu_column!( else InfiniteSquareNetwork(InfinitePEPS(state)) end - colmove_alg = if alg.ctm_alg isa SequentialCTMRG - alg.ctm_alg - else - c = alg.ctm_alg - # an auxiliary struct to pass `projector_alg` to left/right move - SequentialCTMRG(c.tol, c.maxiter, c.miniter, c.verbosity, c.projector_alg) - end + colmove_alg = SequentialCTMRG() + @reset colmove_alg.projector_alg = alg.ctm_alg.projector_alg env2, info = ctmrg_leftmove(col, network, env, colmove_alg) env2, info = ctmrg_rightmove(_next(col, Nc), network, env2, colmove_alg) for c in [col, _next(col, Nc)] From da6174e733b42bd5e0e179e74be74e09172adcbd Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Thu, 23 Oct 2025 22:53:43 +0800 Subject: [PATCH 19/62] Improve time evolution interface --- examples/heisenberg_su/main.jl | 6 +- src/PEPSKit.jl | 4 +- src/algorithms/time_evolution/evoltools.jl | 19 +++ src/algorithms/time_evolution/simpleupdate.jl | 142 +++++++----------- .../time_evolution/simpleupdate3site.jl | 66 ++++---- src/algorithms/time_evolution/time_evolve.jl | 25 +++ test/bondenv/benv_fu.jl | 4 +- test/examples/heisenberg.jl | 6 +- test/examples/j1j2_finiteT.jl | 17 +-- test/examples/tf_ising_finiteT.jl | 20 +-- test/timeevol/cluster_projectors.jl | 12 +- test/timeevol/sitedep_truncation.jl | 14 +- 12 files changed, 169 insertions(+), 166 deletions(-) create mode 100644 src/algorithms/time_evolution/time_evolve.jl diff --git a/examples/heisenberg_su/main.jl b/examples/heisenberg_su/main.jl index 18d266bbb..ad30075be 100644 --- a/examples/heisenberg_su/main.jl +++ b/examples/heisenberg_su/main.jl @@ -70,12 +70,12 @@ fix a truncation error (if that can be reached by remaining below `Dbond`): dts = [1.0e-2, 1.0e-3, 4.0e-4] tols = [1.0e-6, 1.0e-8, 1.0e-8] -maxiter = 10000 +nstep = 10000 trscheme_peps = truncerr(1.0e-10) & truncdim(Dbond) for (dt, tol) in zip(dts, tols) - alg = SimpleUpdate(dt, tol, maxiter, trscheme_peps) - global peps, wts, = simpleupdate(peps, H, alg, wts; bipartite = true) + alg = SimpleUpdate(; tol, trscheme = trscheme_peps, bipartite = true, check_interval = 500) + global peps, wts, = time_evolve(peps, H, dt, nstep, alg, wts) end md""" diff --git a/src/PEPSKit.jl b/src/PEPSKit.jl index 9e1c24b74..25216ebc2 100644 --- a/src/PEPSKit.jl +++ b/src/PEPSKit.jl @@ -70,6 +70,7 @@ include("algorithms/truncation/fullenv_truncation.jl") include("algorithms/truncation/bond_truncation.jl") include("algorithms/time_evolution/evoltools.jl") +include("algorithms/time_evolution/time_evolve.jl") include("algorithms/time_evolution/simpleupdate.jl") include("algorithms/time_evolution/simpleupdate3site.jl") @@ -99,7 +100,8 @@ export fixedpoint export absorb_weight export ALSTruncation, FullEnvTruncation -export su_iter, su3site_iter, simpleupdate, SimpleUpdate +export SimpleUpdate +export time_evolve export InfiniteSquareNetwork export InfinitePartitionFunction diff --git a/src/algorithms/time_evolution/evoltools.jl b/src/algorithms/time_evolution/evoltools.jl index b153a488e..ba36750e1 100644 --- a/src/algorithms/time_evolution/evoltools.jl +++ b/src/algorithms/time_evolution/evoltools.jl @@ -1,5 +1,24 @@ const InfiniteState = Union{InfinitePEPS, InfinitePEPO} +function _process_timeevol_args( + state::InfiniteState, dt::Float64, imaginary_time::Bool, tol::Float64 + ) + dt′ = (state isa InfinitePEPS) ? dt : (dt / 2) + if (state isa InfinitePEPO) + @assert size(state)[3] == 1 + end + if !imaginary_time + @assert (state isa InfinitePEPS) "Real time evolution of InfinitePEPO (Heisenberg picture) is not implemented." + dt′ = 1.0im * dt′ + end + @assert tol >= 0 + check_convergence = (tol > 0) + if check_convergence + @assert ((state isa InfinitePEPS) && imaginary_time) "Convergence check can only be enabled for ground state search." + end + return dt′, check_convergence +end + function MPSKit.infinite_temperature_density_matrix(H::LocalOperator) T = scalartype(H) A = map(physicalspace(H)) do Vp diff --git a/src/algorithms/time_evolution/simpleupdate.jl b/src/algorithms/time_evolution/simpleupdate.jl index 78c5cd7b7..c69ef2cbe 100644 --- a/src/algorithms/time_evolution/simpleupdate.jl +++ b/src/algorithms/time_evolution/simpleupdate.jl @@ -1,24 +1,23 @@ """ $(TYPEDEF) -Algorithm struct for simple update (SU) of infinite PEPS with bond weights. -Each SU run is converged when the singular value difference becomes smaller than `tol`. +Algorithm struct for simple update (SU) of InfinitePEPS or InfinitePEPO. ## Fields $(TYPEDFIELDS) """ -struct SimpleUpdate - dt::Number - tol::Float64 - maxiter::Int +@kwdef struct SimpleUpdate trscheme::TruncationScheme + check_interval::Int = 500 + # only applicable to ground state search + bipartite::Bool = false + tol::Float64 = 0.0 + # only applicable to PEPO evolution + gate_bothsides::Bool = false # when false, purified approach is assumed end -# TODO: add kwarg constructor and SU Defaults - -""" -$(SIGNATURES) +#= Simple update of the x-bond between `[r,c]` and `[r,c+1]`. ``` @@ -26,7 +25,7 @@ Simple update of the x-bond between `[r,c]` and `[r,c+1]`. -- T[r,c] -- T[r,c+1] -- | | ``` -""" +=# function _su_xbond!( state::InfiniteState, gate::AbstractTensorMap{T, S, 2, 2}, env::SUWeight, row::Int, col::Int, trscheme::TruncationScheme; gate_ax::Int = 1 @@ -56,7 +55,7 @@ function _su_xbond!( return ϵ end -""" +#= Simple update of the y-bond between `[r,c]` and `[r-1,c]`. ``` | @@ -65,7 +64,7 @@ Simple update of the y-bond between `[r,c]` and `[r-1,c]`. -- T[r,c] --- | ``` -""" +=# function _su_ybond!( state::InfiniteState, gate::AbstractTensorMap{T, S, 2, 2}, env::SUWeight, row::Int, col::Int, trscheme::TruncationScheme; gate_ax::Int = 1 @@ -95,37 +94,24 @@ function _su_ybond!( return ϵ end -""" - su_iter(state::InfinitePEPS, gate::LocalOperator, alg::SimpleUpdate, env::SUWeight; bipartite::Bool = false) - su_iter(densitymatrix::InfinitePEPO, gate::LocalOperator, alg::SimpleUpdate, env::SUWeight; gate_bothsides::Bool = true) - -One round of simple update, which applies the nearest neighbor `gate` on an InfinitePEPS `state` or InfinitePEPO `densitymatrix`. -When the input `state` has a unit cell size of (2, 2), one can set `bipartite = true` to enforce the bipartite structure. -""" function su_iter( - state::InfiniteState, gate::LocalOperator, alg::SimpleUpdate, env::SUWeight; - bipartite::Bool = false, gate_bothsides::Bool = true + state::InfiniteState, gate::LocalOperator, alg::SimpleUpdate, env::SUWeight ) @assert size(gate.lattice) == size(state)[1:2] - if state isa InfinitePEPS - gate_bothsides = false - else - @assert size(state, 3) == 1 - end Nr, Nc = size(state)[1:2] - bipartite && (@assert Nr == Nc == 2) + alg.bipartite && (@assert Nr == Nc == 2) (Nr >= 2 && Nc >= 2) || throw( ArgumentError("`state` unit cell size for simple update should be no smaller than (2, 2)."), ) state2, env2 = deepcopy(state), deepcopy(env) - gate_axs = gate_bothsides ? (1:2) : (1:1) + gate_axs = alg.gate_bothsides ? (1:2) : (1:1) for r in 1:Nr, c in 1:Nc term = get_gateterm(gate, (CartesianIndex(r, c), CartesianIndex(r, c + 1))) trscheme = truncation_scheme(alg.trscheme, 1, r, c) for gate_ax in gate_axs _su_xbond!(state2, term, env2, r, c, trscheme; gate_ax) end - if bipartite + if alg.bipartite rp1, cp1 = _next(r, Nr), _next(c, Nc) state2.A[rp1, cp1] = deepcopy(state2.A[r, c]) state2.A[rp1, c] = deepcopy(state2.A[r, cp1]) @@ -136,92 +122,70 @@ function su_iter( for gate_ax in gate_axs _su_ybond!(state2, term, env2, r, c, trscheme; gate_ax) end - if bipartite + if alg.bipartite rm1, cm1 = _prev(r, Nr), _prev(c, Nc) state2.A[rm1, cm1] = deepcopy(state2.A[r, c]) state2.A[r, cm1] = deepcopy(state2.A[rm1, c]) env2.data[2, rm1, cm1] = deepcopy(env2.data[2, r, c]) end end - return state2, env2 + wtdiff = compare_weights(env2, env) + return state2, env2, (; wtdiff) end """ Perform simple update with Hamiltonian `ham` containing up to nearest neighbor interaction terms. """ function _simpleupdate2site( - state::InfiniteState, ham::LocalOperator, alg::SimpleUpdate, env::SUWeight; - bipartite::Bool = false, check_interval::Int = 500, gate_bothsides::Bool = true + state::InfiniteState, H::LocalOperator, + dt::Float64, nstep::Int, alg::SimpleUpdate, env::SUWeight; + imaginary_time::Bool ) time_start = time() - # exponentiating the 2-site Hamiltonian gate - if state isa InfinitePEPS - gate_bothsides = false - end - dt = gate_bothsides ? (alg.dt / 2) : alg.dt - gate = get_expham(ham, dt) - wtdiff = 1.0 - env0 = deepcopy(env) - for count in 1:(alg.maxiter) + dt′, check_convergence = _process_timeevol_args(state, dt, imaginary_time, alg.tol) + gate = get_expham(H, dt′) + info = nothing + for step in 1:nstep time0 = time() - state, env = su_iter(state, gate, alg, env; bipartite, gate_bothsides) - wtdiff = compare_weights(env, env0) - converge = (wtdiff < alg.tol) - cancel = (count == alg.maxiter) - env0 = deepcopy(env) + state, env, info = su_iter(state, gate, alg, env) + converge = (info.wtdiff < alg.tol) time1 = time() - if ((count == 1) || (count % check_interval == 0) || converge || cancel) + if (step % alg.check_interval == 0) || (step == nstep) || converge @info "Space of x-weight at [1, 1] = $(space(env[1, 1, 1], 1))" - label = (converge ? "conv" : (cancel ? "cancel" : "iter")) - message = @sprintf( - "SU %s %-7d: dt = %.0e, weight diff = %.3e, time = %.3f sec\n", - label, count, alg.dt, wtdiff, time1 - ((converge || cancel) ? time_start : time0) + @info @sprintf( + "SU iter %-7d: dt = %.0e, |Δλ| = %.3e. Time = %.3f s/it", + step, dt, info.wtdiff, time1 - time0 ) - cancel ? (@warn message) : (@info message) end - converge && break + if check_convergence + converge && break + if step == nstep + @warn "SU ground state search has not converged." + end + end end - return state, env, wtdiff + time_end = time() + @info @sprintf("Simple update finished. Time elasped: %.2f s", time_end - time_start) + return state, env, info end -""" - simpleupdate( - state::InfinitePEPS, ham::LocalOperator, alg::SimpleUpdate, env::SUWeight; - bipartite::Bool = false, force_3site::Bool = false, check_interval::Int = 500 - ) - simpleupdate( - densitymatrix::InfinitePEPO, ham::LocalOperator, alg::SimpleUpdate, env::SUWeight; - gate_bothsides::Bool = true, force_3site::Bool = false, check_interval::Int = 500 - ) - -Perform a simple update on an InfinitePEPS `state` or an InfinitePEPO `densitymatrix` -using the Hamiltonian `ham`, which can contain up to next-nearest-neighbor interaction terms. - -## Keyword Arguments - -- `bipartite::Bool=false`: If `true`, enforces the bipartite structure of the PEPS. -- `gate_bothsides::Bool=true`: (Effective only for PEPO evolution) If true, apply the Trotter gate to both side of the PEPO. Otherwise, the gate is only applied to the physical codomain legs. -- `force_3site::Bool=false`: Forces the use of the 3-site simple update algorithm, even if the Hamiltonian contains only nearest-neighbor terms. -- `check_interval::Int=500`: Specifies the number of evolution steps between printing progress information. - -## Notes - -- Setting `bipartite = true` is allowed only for PEPS evolution with up to next-nearest neighbor terms, and requires the input `peps` to have a unit cell size of (2, 2). -""" -function simpleupdate( - state::InfiniteState, ham::LocalOperator, alg::SimpleUpdate, env::SUWeight; - bipartite::Bool = false, gate_bothsides::Bool = true, - force_3site::Bool = false, check_interval::Int = 500 +function MPSKit.time_evolve( + state::InfiniteState, H::LocalOperator, + dt::Float64, nstep::Int, alg::SimpleUpdate, env::SUWeight; + imaginary_time::Bool = true, force_3site::Bool = false ) # determine if Hamiltonian contains nearest neighbor terms only - nnonly = is_nearest_neighbour(ham) + nnonly = is_nearest_neighbour(H) use_3site = force_3site || !nnonly - @assert !(state isa InfinitePEPO && bipartite) "Evolution of PEPO with bipartite structure is not implemented." - @assert !(bipartite && use_3site) "3-site simple update is incompatible with bipartite lattice." + @assert !(alg.bipartite && state isa InfinitePEPO) "Evolution of PEPO with bipartite structure is not implemented." + @assert !(alg.bipartite && use_3site) "3-site simple update is incompatible with bipartite lattice." + if state isa InfinitePEPS + @assert !(alg.gate_bothsides) "alg.gate_bothsides = true is incompatible with PEPS evolution." + end # TODO: check SiteDependentTruncation is compatible with bipartite structure if use_3site - return _simpleupdate3site(state, ham, alg, env; gate_bothsides, check_interval) + return _simpleupdate3site(state, H, dt, nstep, alg, env; imaginary_time) else - return _simpleupdate2site(state, ham, alg, env; bipartite, gate_bothsides, check_interval) + return _simpleupdate2site(state, H, dt, nstep, alg, env; imaginary_time) end end diff --git a/src/algorithms/time_evolution/simpleupdate3site.jl b/src/algorithms/time_evolution/simpleupdate3site.jl index 84cab37a8..34c7062f7 100644 --- a/src/algorithms/time_evolution/simpleupdate3site.jl +++ b/src/algorithms/time_evolution/simpleupdate3site.jl @@ -497,12 +497,9 @@ One round of 3-site simple update, which applies the Trotter gate MPOs `gatempos on an InfinitePEPS `state` or InfinitePEPO `densitymatrix`. """ function su3site_iter( - state::InfiniteState, gatempos::Vector{G}, alg::SimpleUpdate, env::SUWeight; - gate_bothsides::Bool = true + state::InfiniteState, gatempos::Vector{G}, alg::SimpleUpdate, env::SUWeight ) where {G <: AbstractMatrix} - if state isa InfinitePEPS - gate_bothsides = false - else + if state isa InfinitePEPO @assert size(state, 3) == 1 end Nr, Nc = size(state)[1:2] @@ -521,53 +518,52 @@ function su3site_iter( truncation_scheme(trscheme, 1, r, c) truncation_scheme(trscheme, 2, r, _next(c, Nc)) ] - _su3site_se!(state2, gs, env2, r, c, trschemes; gate_bothsides) + _su3site_se!(state2, gs, env2, r, c, trschemes; alg.gate_bothsides) end state2, env2 = rotl90(state2), rotl90(env2) trscheme = rotl90(trscheme) end - return state2, env2 + wtdiff = compare_weights(env2, env) + return state2, env2, (; wtdiff) end """ -Perform 3-site simple update for Hamiltonian `ham`. +Perform 3-site simple update for Hamiltonian `H`. """ function _simpleupdate3site( - state::InfiniteState, ham::LocalOperator, alg::SimpleUpdate, env::SUWeight; - check_interval::Int = 500, gate_bothsides::Bool = true + state::InfiniteState, H::LocalOperator, + dt::Float64, nstep::Int, alg::SimpleUpdate, env::SUWeight; + imaginary_time::Bool ) time_start = time() - # convert Hamiltonian to 3-site exponentiated gate MPOs - if state isa InfinitePEPS - gate_bothsides = false - end - dt = gate_bothsides ? (alg.dt / 2) : alg.dt + dt′, check_convergence = _process_timeevol_args(state, dt, imaginary_time, alg.tol) gatempos = [ - _get_gatempos_se(ham, dt), - _get_gatempos_se(rotl90(ham), dt), - _get_gatempos_se(rot180(ham), dt), - _get_gatempos_se(rotr90(ham), dt), + _get_gatempos_se(H, dt′), + _get_gatempos_se(rotl90(H), dt′), + _get_gatempos_se(rot180(H), dt′), + _get_gatempos_se(rotr90(H), dt′), ] - wtdiff = 1.0 - env0 = deepcopy(env) - for count in 1:(alg.maxiter) + info = nothing + for step in 1:nstep time0 = time() - state, env = su3site_iter(state, gatempos, alg, env; gate_bothsides) - wtdiff = compare_weights(env, env0) - converge = (wtdiff < alg.tol) - cancel = (count == alg.maxiter) - env0 = deepcopy(env) + state, env, info = su3site_iter(state, gatempos, alg, env) + converge = (info.wtdiff < alg.tol) time1 = time() - if ((count == 1) || (count % check_interval == 0) || converge || cancel) + if (step % alg.check_interval == 0) || (step == nstep) || converge @info "Space of x-weight at [1, 1] = $(space(env[1, 1, 1], 1))" - label = (converge ? "conv" : (cancel ? "cancel" : "iter")) - message = @sprintf( - "SU %s %-7d: dt = %.0e, weight diff = %.3e, time = %.3f sec\n", - label, count, alg.dt, wtdiff, time1 - ((converge || cancel) ? time_start : time0) + @info @sprintf( + "SU iter %-7d: dt = %.0e, |Δλ| = %.3e. Time = %.3f s/it", + step, dt, info.wtdiff, time1 - time0 ) - cancel ? (@warn message) : (@info message) end - converge && break + if check_convergence + converge && break + if step == nstep + @warn "SU ground state search has not converged." + end + end end - return state, env, wtdiff + time_end = time() + @info @sprintf("Simple update finished. Time elasped: %.2f s", time_end - time_start) + return state, env, info end diff --git a/src/algorithms/time_evolution/time_evolve.jl b/src/algorithms/time_evolution/time_evolve.jl new file mode 100644 index 000000000..62becbfcf --- /dev/null +++ b/src/algorithms/time_evolution/time_evolve.jl @@ -0,0 +1,25 @@ +@doc """ + time_evolve(ψ₀, H, dt, nstep, alg, envs₀; kwargs...) -> (ψ, envs, info) + +Time-evolve the initial state `ψ₀` with Hamiltonian `H` over +the time span `dt * nstep` based on Trotter decomposition. + +## Arguments + +- `ψ₀::Union{InfinitePEPS, InfinitePEPO}`: Initial state. +- `H::LocalOperator`: Hamiltonian operator (time-independent). +- `dt::Float64`: Trotter time evolution step. +- `nstep::Int`: Number of Trotter steps to be taken, such that the evolved time span is `dt * nstep`. +- `alg`: Time evolution algorithm. +- `envs₀`: Environment of the initial state. + +## Keyword Arguments + +- `imaginary_time::Bool=false`: if true, the time evolution is done with an imaginary time step + instead, (i.e. ``\\exp(-Hdt)`` instead of ``\\exp(-iHdt)``). This can be useful for using this + function to compute the ground state of a Hamiltonian, or to compute finite-temperature + properties of a system. +- `tol::Float64 = 0.0`: Tolerance of the change in SUWeight (for simple update; default 1.0e-9) + or energy (for full update; default 1.0e-8) to determine if the ground state search has converged. +""" +time_evolve diff --git a/test/bondenv/benv_fu.jl b/test/bondenv/benv_fu.jl index d6a374a8b..52bfca5ce 100644 --- a/test/bondenv/benv_fu.jl +++ b/test/bondenv/benv_fu.jl @@ -13,8 +13,8 @@ function get_hubbard_state(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(1.0e-2, 1.0e-8, 10000, truncerr(1.0e-10) & truncdim(4)) - peps, = simpleupdate(peps, H, alg, wts; bipartite = false, check_interval = 2000) + alg = SimpleUpdate(; tol = 1.0e-8, trscheme = truncerr(1.0e-10) & truncdim(4), check_interval = 2000) + peps, = time_evolve(peps, H, 1.0e-2, 10000, alg, wts) normalize!.(peps.A, Inf) return peps end diff --git a/test/examples/heisenberg.jl b/test/examples/heisenberg.jl index 9a390d914..ed8e1b11e 100644 --- a/test/examples/heisenberg.jl +++ b/test/examples/heisenberg.jl @@ -66,12 +66,12 @@ end # simple update dts = [1.0e-2, 1.0e-3, 1.0e-3, 1.0e-4] tols = [1.0e-7, 1.0e-8, 1.0e-8, 1.0e-8] - maxiter = 5000 + nstep = 5000 for (n, (dt, tol)) in enumerate(zip(dts, tols)) Dbond2 = (n == 2) ? Dbond + 2 : Dbond trscheme = truncerr(1.0e-10) & truncdim(Dbond2) - alg = SimpleUpdate(dt, tol, maxiter, trscheme) - peps, wts, = simpleupdate(peps, ham, alg, wts; bipartite = false) + alg = SimpleUpdate(; trscheme, bipartite = false) + peps, wts, = time_evolve(peps, ham, dt, nstep, alg, wts) end # measure physical quantities with CTMRG diff --git a/test/examples/j1j2_finiteT.jl b/test/examples/j1j2_finiteT.jl index b28ef7deb..8b827a692 100644 --- a/test/examples/j1j2_finiteT.jl +++ b/test/examples/j1j2_finiteT.jl @@ -31,24 +31,23 @@ trscheme_pepo = truncdim(7) & truncerr(1.0e-12) check_interval = 100 # PEPO approach -dt, maxiter = 1.0e-3, 600 -alg = SimpleUpdate(dt, 0.0, maxiter, trscheme_pepo) -pepo, wts, = simpleupdate(pepo0, ham, alg, wts0; check_interval, gate_bothsides = true) +dt, nstep = 1.0e-3, 600 +alg = SimpleUpdate(; trscheme = trscheme_pepo, gate_bothsides = true, check_interval) +pepo, wts, = time_evolve(pepo0, ham, dt, nstep, alg, wts0) env = converge_env(InfinitePartitionFunction(pepo), 16) energy = expectation_value(pepo, ham, env) / (Nr * Nc) -@info "β = $(dt * maxiter): tr(ρH) = $(energy)" +@info "β = $(dt * nstep): tr(ρH) = $(energy)" @test energy ≈ bm[2] atol = 5.0e-3 # PEPS (purified PEPO) approach -dt, maxiter = 1.0e-3, 300 -alg = SimpleUpdate(dt, 0.0, maxiter, trscheme_pepo) -pepo, wts, = simpleupdate(pepo0, ham, alg, wts0; check_interval, gate_bothsides = false) +alg = SimpleUpdate(; trscheme = trscheme_pepo, gate_bothsides = false, check_interval) +pepo, wts, = time_evolve(pepo0, ham, dt, nstep, alg, wts0) env = converge_env(InfinitePartitionFunction(pepo), 16) energy = expectation_value(pepo, ham, env) / (Nr * Nc) -@info "β = $(dt * maxiter): tr(ρH) = $(energy)" +@info "β = $(dt * nstep) / 2: tr(ρH) = $(energy)" @test energy ≈ bm[1] atol = 5.0e-3 env = converge_env(InfinitePEPS(pepo), 16) energy = expectation_value(pepo, ham, pepo, env) / (Nr * Nc) -@info "β = 2 × $(dt * maxiter): ⟨ρ|H|ρ⟩ = $(energy)" +@info "β = $(dt * nstep): ⟨ρ|H|ρ⟩ = $(energy)" @test energy ≈ bm[2] atol = 5.0e-3 diff --git a/test/examples/tf_ising_finiteT.jl b/test/examples/tf_ising_finiteT.jl index d14f10ae5..37b2484d8 100644 --- a/test/examples/tf_ising_finiteT.jl +++ b/test/examples/tf_ising_finiteT.jl @@ -54,29 +54,29 @@ wts0 = SUWeight(pepo0) trscheme_pepo = truncdim(8) & truncerr(1.0e-12) -dt, maxiter = 1.0e-3, 400 -β = dt * maxiter -alg = SimpleUpdate(dt, 0.0, maxiter, trscheme_pepo) +dt, nstep = 1.0e-3, 400 +β = dt * nstep # when g = 2, β = 0.4 and 2β = 0.8 belong to two phases (without and with nonzero σᶻ) -# PEPO approach -## results at β, or T = 2.5 -pepo, wts, = simpleupdate(pepo0, ham, alg, wts0; gate_bothsides = true) +# PEPO approach: results at β, or T = 2.5 +alg = SimpleUpdate(; trscheme = trscheme_pepo, gate_bothsides = true) +pepo, wts, = time_evolve(pepo0, ham, dt, nstep, alg, wts0) env = converge_env(InfinitePartitionFunction(pepo), 16) result_β = measure_mag(pepo, env) @info "Magnetization at T = $(1 / β)" result_β @test isapprox(abs.(result_β), bm_β, rtol = 1.0e-2) -## results at 2β, or T = 1.25 -pepo, wts, = simpleupdate(pepo, ham, alg, wts; gate_bothsides = true) +# continue to get results at 2β, or T = 1.25 +pepo, wts, = time_evolve(pepo, ham, dt, nstep, alg, wts) env = converge_env(InfinitePartitionFunction(pepo), 16) result_2β = measure_mag(pepo, env) @info "Magnetization at T = $(1 / (2β))" result_2β @test isapprox(abs.(result_2β), bm_2β, rtol = 1.0e-4) -# purification approach (should match 2β result) -pepo, = simpleupdate(pepo0, ham, alg, wts0; gate_bothsides = false) +# Purification approach: results at 2β, or T = 1.25 +alg = SimpleUpdate(; trscheme = trscheme_pepo, gate_bothsides = false) +pepo, = time_evolve(pepo0, ham, dt, 2 * nstep, alg, wts0) env = converge_env(InfinitePEPS(pepo), 8) result_2β′ = measure_mag(pepo, env; purified = true) @info "Magnetization at T = $(1 / (2β)) (purification approach)" result_2β′ diff --git a/test/timeevol/cluster_projectors.jl b/test/timeevol/cluster_projectors.jl index 2d3dc867a..432d65c5e 100644 --- a/test/timeevol/cluster_projectors.jl +++ b/test/timeevol/cluster_projectors.jl @@ -116,11 +116,11 @@ end # usual 2-site simple update, and measure energy dts = [1.0e-2, 1.0e-2, 5.0e-3] tols = [1.0e-8, 1.0e-8, 1.0e-8] - maxiter = 5000 + nstep = 5000 for (n, (dt, tol)) in enumerate(zip(dts, tols)) trscheme = truncerr(1.0e-10) & truncdim(n == 1 ? 4 : 2) - alg = SimpleUpdate(dt, tol, maxiter, trscheme) - peps, wts, = simpleupdate(peps, ham, alg, wts; bipartite = true, check_interval = 1000) + alg = SimpleUpdate(; tol, trscheme, bipartite = true, check_interval = 1000) + peps, wts, = time_evolve(peps, ham, dt, nstep, alg, wts) end normalize!.(peps.A, Inf) env = CTMRGEnv(rand, Float64, peps, Espace) @@ -132,10 +132,8 @@ end tols = [1.0e-8, 1.0e-8] trscheme = truncerr(1.0e-10) & truncdim(2) for (n, (dt, tol)) in enumerate(zip(dts, tols)) - alg = SimpleUpdate(dt, tol, maxiter, trscheme) - peps, wts, = simpleupdate( - peps, ham, alg, wts; check_interval = 1000, force_3site = true - ) + alg = SimpleUpdate(; tol, trscheme, check_interval = 1000) + peps, wts, = time_evolve(peps, ham, dt, nstep, alg, wts; force_3site = true) end normalize!.(peps.A, Inf) env, = leading_boundary(env, peps; tol = ctmrg_tol, trscheme = trscheme_env) diff --git a/test/timeevol/sitedep_truncation.jl b/test/timeevol/sitedep_truncation.jl index 13f3e1c12..3ee588d6c 100644 --- a/test/timeevol/sitedep_truncation.jl +++ b/test/timeevol/sitedep_truncation.jl @@ -27,8 +27,8 @@ end # set trscheme to be compatible with bipartite structure bonddims = stack([[6 4; 4 6], [5 7; 7 5]]; dims = 1) trscheme = SiteDependentTruncation(collect(truncdim(d) for d in bonddims)) - alg = SimpleUpdate(1.0e-2, 1.0e-14, 4, trscheme) - peps, env, = simpleupdate(peps0, ham, alg, env0; bipartite = true) + alg = SimpleUpdate(; trscheme, 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 @@ -44,22 +44,22 @@ end @testset "Simple update: generic 2-site and 3-site" begin Nr, Nc = 3, 4 - 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)) normalize!.(peps0.A, Inf) env0 = SUWeight(peps0) # Site dependent truncation bonddims = rand(2:8, 2, Nr, Nc) - @show bonddims trscheme = SiteDependentTruncation(collect(truncdim(d) for d in bonddims)) - alg = SimpleUpdate(1.0e-2, 1.0e-14, 2, trscheme) + alg = SimpleUpdate(; trscheme) # 2-site SU - peps, env, = simpleupdate(peps0, ham, alg, env0; bipartite = false) + ham = real(heisenberg_XYZ(InfiniteSquare(Nr, Nc); Jx = 1.0, Jy = 1.0, Jz = 1.0)) + 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 - peps, env, = simpleupdate(peps0, ham, alg, env0; bipartite = false, force_3site = true) + ham = real(j1_j2_model(InfiniteSquare(Nr, Nc); J1 = 1.0, J2 = 0.5, 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 88ced8924e28685735eba05d75c8588cd7f7dd52 Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Thu, 23 Oct 2025 22:57:14 +0800 Subject: [PATCH 20/62] Update docstring --- src/algorithms/time_evolution/time_evolve.jl | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/algorithms/time_evolution/time_evolve.jl b/src/algorithms/time_evolution/time_evolve.jl index 62becbfcf..a88d87c52 100644 --- a/src/algorithms/time_evolution/time_evolve.jl +++ b/src/algorithms/time_evolution/time_evolve.jl @@ -19,7 +19,5 @@ the time span `dt * nstep` based on Trotter decomposition. instead, (i.e. ``\\exp(-Hdt)`` instead of ``\\exp(-iHdt)``). This can be useful for using this function to compute the ground state of a Hamiltonian, or to compute finite-temperature properties of a system. -- `tol::Float64 = 0.0`: Tolerance of the change in SUWeight (for simple update; default 1.0e-9) - or energy (for full update; default 1.0e-8) to determine if the ground state search has converged. """ time_evolve From dd3d602b475efdfe704a2e8fb7eac546f09fcfeb Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Thu, 23 Oct 2025 23:33:58 +0800 Subject: [PATCH 21/62] Move `force_3site` into `SimpleUpdate` --- src/algorithms/time_evolution/simpleupdate.jl | 5 +++-- test/timeevol/cluster_projectors.jl | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/algorithms/time_evolution/simpleupdate.jl b/src/algorithms/time_evolution/simpleupdate.jl index c69ef2cbe..9c143719d 100644 --- a/src/algorithms/time_evolution/simpleupdate.jl +++ b/src/algorithms/time_evolution/simpleupdate.jl @@ -10,6 +10,7 @@ $(TYPEDFIELDS) @kwdef struct SimpleUpdate trscheme::TruncationScheme check_interval::Int = 500 + force_3site::Bool = false # only applicable to ground state search bipartite::Bool = false tol::Float64 = 0.0 @@ -172,11 +173,11 @@ end function MPSKit.time_evolve( state::InfiniteState, H::LocalOperator, dt::Float64, nstep::Int, alg::SimpleUpdate, env::SUWeight; - imaginary_time::Bool = true, force_3site::Bool = false + imaginary_time::Bool = true ) # determine if Hamiltonian contains nearest neighbor terms only nnonly = is_nearest_neighbour(H) - use_3site = force_3site || !nnonly + use_3site = alg.force_3site || !nnonly @assert !(alg.bipartite && state isa InfinitePEPO) "Evolution of PEPO with bipartite structure is not implemented." @assert !(alg.bipartite && use_3site) "3-site simple update is incompatible with bipartite lattice." if state isa InfinitePEPS diff --git a/test/timeevol/cluster_projectors.jl b/test/timeevol/cluster_projectors.jl index 432d65c5e..90220aa81 100644 --- a/test/timeevol/cluster_projectors.jl +++ b/test/timeevol/cluster_projectors.jl @@ -132,8 +132,8 @@ end tols = [1.0e-8, 1.0e-8] trscheme = truncerr(1.0e-10) & truncdim(2) for (n, (dt, tol)) in enumerate(zip(dts, tols)) - alg = SimpleUpdate(; tol, trscheme, check_interval = 1000) - peps, wts, = time_evolve(peps, ham, dt, nstep, alg, wts; force_3site = true) + alg = SimpleUpdate(; tol, trscheme, check_interval = 1000, force_3site = true) + peps, wts, = time_evolve(peps, ham, dt, nstep, alg, wts) end normalize!.(peps.A, Inf) env, = leading_boundary(env, peps; tol = ctmrg_tol, trscheme = trscheme_env) From 6e71ed928c518803be0552e31e01ce89939487d4 Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Tue, 28 Oct 2025 21:06:17 +0800 Subject: [PATCH 22/62] Define `Base.iterate` for SimpleUpdate --- src/algorithms/time_evolution/evoltools.jl | 9 +- src/algorithms/time_evolution/simpleupdate.jl | 157 ++++++++++-------- .../time_evolution/simpleupdate3site.jl | 124 +++++--------- src/algorithms/time_evolution/time_evolve.jl | 35 ++-- 4 files changed, 138 insertions(+), 187 deletions(-) diff --git a/src/algorithms/time_evolution/evoltools.jl b/src/algorithms/time_evolution/evoltools.jl index ba36750e1..02c6f56a9 100644 --- a/src/algorithms/time_evolution/evoltools.jl +++ b/src/algorithms/time_evolution/evoltools.jl @@ -1,7 +1,7 @@ const InfiniteState = Union{InfinitePEPS, InfinitePEPO} function _process_timeevol_args( - state::InfiniteState, dt::Float64, imaginary_time::Bool, tol::Float64 + state::InfiniteState, dt::Float64, imaginary_time::Bool ) dt′ = (state isa InfinitePEPS) ? dt : (dt / 2) if (state isa InfinitePEPO) @@ -11,12 +11,7 @@ function _process_timeevol_args( @assert (state isa InfinitePEPS) "Real time evolution of InfinitePEPO (Heisenberg picture) is not implemented." dt′ = 1.0im * dt′ end - @assert tol >= 0 - check_convergence = (tol > 0) - if check_convergence - @assert ((state isa InfinitePEPS) && imaginary_time) "Convergence check can only be enabled for ground state search." - end - return dt′, check_convergence + return dt′ end function MPSKit.infinite_temperature_density_matrix(H::LocalOperator) diff --git a/src/algorithms/time_evolution/simpleupdate.jl b/src/algorithms/time_evolution/simpleupdate.jl index 9c143719d..cf19e616b 100644 --- a/src/algorithms/time_evolution/simpleupdate.jl +++ b/src/algorithms/time_evolution/simpleupdate.jl @@ -7,14 +7,31 @@ Algorithm struct for simple update (SU) of InfinitePEPS or InfinitePEPO. $(TYPEDFIELDS) """ -@kwdef struct SimpleUpdate +@kwdef struct SimpleUpdate <: TimeEvolution + # Initial state (InfinitePEPS or InfinitePEPO) + ψ0::InfiniteState + # Hamiltonian + H::LocalOperator + # Initial Bond weights + env0::SUWeight + # Trotter time step + dt::Float64 + # number of iteration steps + nstep::Int + # Truncation scheme after applying Trotter gates trscheme::TruncationScheme + # Switch for imaginary or real time + imaginary_time::Bool = true + # controls interval to print information check_interval::Int = 500 + # force usage of 3-site simple update force_3site::Bool = false - # only applicable to ground state search + # ---- only applicable to ground state search ---- + # assume bipartite unit cell structure bipartite::Bool = false + # converged change of weight between two steps tol::Float64 = 0.0 - # only applicable to PEPO evolution + # ---- only applicable to PEPO evolution ---- gate_bothsides::Bool = false # when false, purified approach is assumed end @@ -28,14 +45,14 @@ Simple update of the x-bond between `[r,c]` and `[r,c+1]`. ``` =# function _su_xbond!( - state::InfiniteState, gate::AbstractTensorMap{T, S, 2, 2}, env::SUWeight, + ψ::InfiniteState, gate::AbstractTensorMap{T, S, 2, 2}, env::SUWeight, row::Int, col::Int, trscheme::TruncationScheme; gate_ax::Int = 1 ) where {T <: Number, S <: ElementarySpace} - Nr, Nc = size(state)[1:2] + Nr, Nc = size(ψ)[1:2] @assert 1 <= row <= Nr && 1 <= col <= Nc cp1 = _next(col, Nc) # absorb environment weights - A, B = state.A[row, col], state.A[row, cp1] + A, B = ψ.A[row, col], ψ.A[row, cp1] A = absorb_weight(A, env, row, col, (NORTH, SOUTH, WEST); inv = false) B = absorb_weight(B, env, row, cp1, (NORTH, SOUTH, EAST); inv = false) normalize!(A, Inf) @@ -51,7 +68,7 @@ function _su_xbond!( normalize!(B, Inf) normalize!(s, Inf) # update tensor dict and weight on current bond - state.A[row, col], state.A[row, cp1] = A, B + ψ.A[row, col], ψ.A[row, cp1] = A, B env.data[1, row, col] = s return ϵ end @@ -67,14 +84,14 @@ Simple update of the y-bond between `[r,c]` and `[r-1,c]`. ``` =# function _su_ybond!( - state::InfiniteState, gate::AbstractTensorMap{T, S, 2, 2}, env::SUWeight, + ψ::InfiniteState, gate::AbstractTensorMap{T, S, 2, 2}, env::SUWeight, row::Int, col::Int, trscheme::TruncationScheme; gate_ax::Int = 1 ) where {T <: Number, S <: ElementarySpace} - Nr, Nc = size(state)[1:2] + Nr, Nc = size(ψ)[1:2] @assert 1 <= row <= Nr && 1 <= col <= Nc rm1 = _prev(row, Nr) # absorb environment weights - A, B = state.A[row, col], state.A[rm1, col] + A, B = ψ.A[row, col], ψ.A[rm1, col] A = absorb_weight(A, env, row, col, (EAST, SOUTH, WEST); inv = false) B = absorb_weight(B, env, rm1, col, (NORTH, EAST, WEST); inv = false) normalize!(A, Inf) @@ -90,103 +107,101 @@ function _su_ybond!( normalize!(A, Inf) normalize!(B, Inf) normalize!(s, Inf) - state.A[row, col], state.A[rm1, col] = A, B + ψ.A[row, col], ψ.A[rm1, col] = A, B env.data[2, row, col] = s return ϵ end function su_iter( - state::InfiniteState, gate::LocalOperator, alg::SimpleUpdate, env::SUWeight + ψ::InfiniteState, gate::LocalOperator, alg::SimpleUpdate, env::SUWeight ) - @assert size(gate.lattice) == size(state)[1:2] - Nr, Nc = size(state)[1:2] + @assert size(gate.lattice) == size(ψ)[1:2] + Nr, Nc = size(ψ)[1:2] alg.bipartite && (@assert Nr == Nc == 2) (Nr >= 2 && Nc >= 2) || throw( ArgumentError("`state` unit cell size for simple update should be no smaller than (2, 2)."), ) - state2, env2 = deepcopy(state), deepcopy(env) + ψ2, env2 = deepcopy(ψ), deepcopy(env) gate_axs = alg.gate_bothsides ? (1:2) : (1:1) for r in 1:Nr, c in 1:Nc term = get_gateterm(gate, (CartesianIndex(r, c), CartesianIndex(r, c + 1))) trscheme = truncation_scheme(alg.trscheme, 1, r, c) for gate_ax in gate_axs - _su_xbond!(state2, term, env2, r, c, trscheme; gate_ax) + _su_xbond!(ψ2, term, env2, r, c, trscheme; gate_ax) end if alg.bipartite rp1, cp1 = _next(r, Nr), _next(c, Nc) - state2.A[rp1, cp1] = deepcopy(state2.A[r, c]) - state2.A[rp1, c] = deepcopy(state2.A[r, cp1]) + ψ2.A[rp1, cp1] = deepcopy(ψ2.A[r, c]) + ψ2.A[rp1, c] = deepcopy(ψ2.A[r, cp1]) env2.data[1, rp1, cp1] = deepcopy(env2.data[1, r, c]) end term = get_gateterm(gate, (CartesianIndex(r, c), CartesianIndex(r - 1, c))) trscheme = truncation_scheme(alg.trscheme, 2, r, c) for gate_ax in gate_axs - _su_ybond!(state2, term, env2, r, c, trscheme; gate_ax) + _su_ybond!(ψ2, term, env2, r, c, trscheme; gate_ax) end if alg.bipartite rm1, cm1 = _prev(r, Nr), _prev(c, Nc) - state2.A[rm1, cm1] = deepcopy(state2.A[r, c]) - state2.A[r, cm1] = deepcopy(state2.A[rm1, c]) + ψ2.A[rm1, cm1] = deepcopy(ψ2.A[r, c]) + ψ2.A[r, cm1] = deepcopy(ψ2.A[rm1, c]) env2.data[2, rm1, cm1] = deepcopy(env2.data[2, r, c]) end end wtdiff = compare_weights(env2, env) - return state2, env2, (; wtdiff) + return ψ2, env2, (; wtdiff) end -""" -Perform simple update with Hamiltonian `ham` containing up to nearest neighbor interaction terms. -""" -function _simpleupdate2site( - state::InfiniteState, H::LocalOperator, - dt::Float64, nstep::Int, alg::SimpleUpdate, env::SUWeight; - imaginary_time::Bool - ) - time_start = time() - dt′, check_convergence = _process_timeevol_args(state, dt, imaginary_time, alg.tol) - gate = get_expham(H, dt′) - info = nothing - for step in 1:nstep - time0 = time() - state, env, info = su_iter(state, gate, alg, env) - converge = (info.wtdiff < alg.tol) - time1 = time() - if (step % alg.check_interval == 0) || (step == nstep) || converge - @info "Space of x-weight at [1, 1] = $(space(env[1, 1, 1], 1))" - @info @sprintf( - "SU iter %-7d: dt = %.0e, |Δλ| = %.3e. Time = %.3f s/it", - step, dt, info.wtdiff, time1 - time0 - ) - end - if check_convergence - converge && break - if step == nstep - @warn "SU ground state search has not converged." - end - end - end - time_end = time() - @info @sprintf("Simple update finished. Time elasped: %.2f s", time_end - time_start) - return state, env, info -end - -function MPSKit.time_evolve( - state::InfiniteState, H::LocalOperator, - dt::Float64, nstep::Int, alg::SimpleUpdate, env::SUWeight; - imaginary_time::Bool = true - ) - # determine if Hamiltonian contains nearest neighbor terms only - nnonly = is_nearest_neighbour(H) +# Initial iteration +function Base.iterate(alg::SimpleUpdate) + # sanity checks + nnonly = is_nearest_neighbour(alg.H) use_3site = alg.force_3site || !nnonly - @assert !(alg.bipartite && state isa InfinitePEPO) "Evolution of PEPO with bipartite structure is not implemented." + @assert alg.tol >= 0 + @assert !(alg.bipartite && alg.ψ0 isa InfinitePEPO) "Evolution of PEPO with bipartite structure is not implemented." @assert !(alg.bipartite && use_3site) "3-site simple update is incompatible with bipartite lattice." - if state isa InfinitePEPS + if alg.ψ0 isa InfinitePEPS @assert !(alg.gate_bothsides) "alg.gate_bothsides = true is incompatible with PEPS evolution." end - # TODO: check SiteDependentTruncation is compatible with bipartite structure - if use_3site - return _simpleupdate3site(state, H, dt, nstep, alg, env; imaginary_time) + # construct Trotter 2-site gates or 3-site gate-MPOs + dt′ = _process_timeevol_args(alg.ψ0, alg.dt, alg.imaginary_time) + gate = if use_3site + [ + _get_gatempos_se(alg.H, dt′), + _get_gatempos_se(rotl90(alg.H), dt′), + _get_gatempos_se(rot180(alg.H), dt′), + _get_gatempos_se(rotr90(alg.H), dt′), + ] else - return _simpleupdate2site(state, H, dt, nstep, alg, env; imaginary_time) + get_expham(alg.H, dt′) + end + time0 = time() + ψ, env, info = su_iter(alg.ψ0, gate, alg, alg.env0) + elapsed_time = time() - time0 + converged = false + return (ψ, env, info), (ψ, env, info, gate, converged, 1, elapsed_time) +end + +# subsequent iterations +function Base.iterate(alg::SimpleUpdate, state) + ψ0, env0, info, gate, converged, it, elapsed_time = state + check_convergence = (alg.tol > 0) + if (it % alg.check_interval == 0) || (it == 1) || (it == alg.nstep) || converged + @info "Space of x-weight at [1, 1] = $(space(env0[1, 1, 1], 1))" + @info @sprintf( + "SU iter %-7d: dt = %.0e, |Δλ| = %.3e. Time = %.3f s/it", + it, alg.dt, info.wtdiff, elapsed_time + ) + if (it == alg.nstep) + check_convergence && (@warn "SU: bond weights have not converged.") + return nothing + elseif converged + @info "SU: bond weights have converged." + return nothing + end end + time0 = time() + ψ, env, info = su_iter(ψ0, gate, alg, env0) + elapsed_time = time() - time0 + converged = check_convergence ? (info.wtdiff < alg.tol) : false + return (ψ, env, info), (ψ, env, info, gate, converged, it + 1, elapsed_time) end diff --git a/src/algorithms/time_evolution/simpleupdate3site.jl b/src/algorithms/time_evolution/simpleupdate3site.jl index 34c7062f7..abd37e4b2 100644 --- a/src/algorithms/time_evolution/simpleupdate3site.jl +++ b/src/algorithms/time_evolution/simpleupdate3site.jl @@ -95,7 +95,7 @@ Here `-*-` is the twist on the physical axis. # Truncation of a bond on OBC-MPS Suppose we want to truncate the bond between -the n-th and the (n+1)-th sites such that the truncated state +the n-th and the (n+1)-th sites such that the truncated ψ ``` |ψ̃⟩ = M[1]---...---M̃[n]---M̃[n+1]---...---M[N] ↓ ↓ ↓ ↓ @@ -125,14 +125,14 @@ Then the fidelity is just F(ψ̃) = (norm(s̃[n], 2) / norm(s[n], 2))^2 ``` =# -""" +#= Perform QR decomposition through a PEPS tensor ``` ╱ ╱ --R0----M--- → ---Q--*-R1-- ╱ | ╱ | ``` -""" +=# function qr_through( R0::MPSBondTensor, M::GenericMPSTensor{S, 4}; normalize::Bool = true ) where {S <: ElementarySpace} @@ -151,14 +151,14 @@ function qr_through( return q, r end -""" +#= Perform LQ decomposition through a tensor ``` ╱ ╱ --L0-*--Q--- ← ---M--*-L1-- ╱ | ╱ | ``` -""" +=# function lq_through( M::GenericMPSTensor{S, 4}, L1::MPSBondTensor; normalize::Bool = true ) where {S <: ElementarySpace} @@ -177,9 +177,9 @@ function lq_through( return l, q 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}} # M1 -- (R1,L1) -- M2 -- (R2,L2) -- M3 N = length(Ms) @@ -198,7 +198,7 @@ function _get_allRLs(Ms::Vector{T}) where {T <: GenericMPSTensor{<:ElementarySpa return Rs, Ls end -""" +#= 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`, @@ -211,7 +211,7 @@ The arrows between `Pa`, `s`, `Pb` are rev = true: - Pa --→-- Pb - 2 → s → 1 ``` -""" +=# function _proj_from_RL( r::MPSBondTensor, l::MPSBondTensor; trunc::TruncationScheme = notrunc(), rev::Bool = false, @@ -227,10 +227,10 @@ function _proj_from_RL( return Pa, s, Pb, ϵ 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. -""" +=# function _get_allprojs( Ms, Rs, Ls, trschemes::Vector{E}, revs::Vector{Bool} ) where {E <: TruncationScheme} @@ -252,9 +252,9 @@ function _get_allprojs( return Pas, Pbs, wts, ϵs end -""" +#= Find projectors to truncate internal bonds of the cluster `Ms`. -""" +=# function _cluster_truncate!( Ms::Vector{T}, trschemes::Vector{E}, revs::Vector{Bool} ) where {T <: GenericMPSTensor{<:ElementarySpace, 4}, E <: TruncationScheme} @@ -269,7 +269,7 @@ function _cluster_truncate!( return wts, ϵs, Pas, Pbs end -""" +#= Apply the gate MPO `gs` on the cluster `Ms`. When `gate_ax` is 1 or 2, the gate acts from the physical codomain or domain side. @@ -293,7 +293,7 @@ In the cluster, the axes of each tensor use the MPS order 4 2 5 2 M[1 2 3 4; 5] M[1 2 3 4 5; 6] ``` -""" +=# function _apply_gatempo!( Ms::Vector{T1}, gs::Vector{T2}; gate_ax::Int = 1 ) where {T1 <: GenericMPSTensor{<:ElementarySpace, 4}, T2 <: AbstractTensorMap} @@ -423,7 +423,7 @@ const perms_se_pepo = map(invperms_se_pepo) do (p1, p2) p = invperm((p1..., p2...)) return (p[1:(end - 1)], (p[end],)) end -""" +#= Obtain the 3-site cluster in the "southeast corner" of a square plaquette. ``` r-1 M3 @@ -432,29 +432,29 @@ Obtain the 3-site cluster in the "southeast corner" of a square plaquette. r M1 -←- M2 c c+1 ``` -""" -function get_3site_se(state::InfiniteState, env::SUWeight, row::Int, col::Int) - Nr, Nc = size(state) +=# +function get_3site_se(ψ::InfiniteState, env::SUWeight, row::Int, col::Int) + Nr, Nc = size(ψ) rm1, cp1 = _prev(row, Nr), _next(col, Nc) coords_se = [(row, col), (row, cp1), (rm1, cp1)] - perms_se = isa(state, InfinitePEPS) ? perms_se_peps : perms_se_pepo + perms_se = isa(ψ, InfinitePEPS) ? perms_se_peps : perms_se_pepo Ms = map(zip(coords_se, perms_se, openaxs_se)) do (coord, perm, openaxs) - M = absorb_weight(state.A[CartesianIndex(coord)], env, coord[1], coord[2], openaxs) + M = absorb_weight(ψ.A[CartesianIndex(coord)], env, coord[1], coord[2], openaxs) return permute(M, perm) end return Ms end function _su3site_se!( - state::InfiniteState, gs::Vector{T}, env::SUWeight, + ψ::InfiniteState, gs::Vector{T}, env::SUWeight, row::Int, col::Int, trschemes::Vector{E}; gate_bothsides::Bool = true ) where {T <: AbstractTensorMap, E <: TruncationScheme} - Nr, Nc = size(state) + Nr, Nc = size(ψ) @assert 1 <= row <= Nr && 1 <= col <= Nc rm1, cp1 = _prev(row, Nr), _next(col, Nc) # southwest 3-site cluster and arrow direction within it - Ms = get_3site_se(state, env, row, col) + Ms = get_3site_se(ψ, env, row, col) revs = [isdual(space(M, 1)) for M in Ms[2:end]] Vphys = [codomain(M, 2) for M in Ms] normalize!.(Ms, Inf) @@ -467,103 +467,55 @@ function _su3site_se!( ϵs = nothing for gate_ax in gate_axs _apply_gatempo!(Ms, gs; gate_ax) - if isa(state, InfinitePEPO) + if isa(ψ, InfinitePEPO) Ms = [first(_fuse_physicalspaces(M)) for M in Ms] end wts, ϵs, = _cluster_truncate!(Ms, trschemes, revs) - if isa(state, InfinitePEPO) + if isa(ψ, InfinitePEPO) Ms = [first(_unfuse_physicalspace(M, Vphy)) for (M, Vphy) in zip(Ms, Vphys)] end for (wt, wt_idx) in zip(wts, wt_idxs) env[CartesianIndex(wt_idx)] = normalize(wt, Inf) end end - invperms_se = isa(state, InfinitePEPS) ? invperms_se_peps : invperms_se_pepo + invperms_se = isa(ψ, InfinitePEPS) ? invperms_se_peps : invperms_se_pepo for (M, coord, invperm, openaxs, Vphy) in zip(Ms, coords, invperms_se, openaxs_se, Vphys) # restore original axes order M = permute(M, invperm) # remove weights on open axes of the cluster M = absorb_weight(M, env, coord[1], coord[2], openaxs; inv = true) - state.A[CartesianIndex(coord)] = normalize(M, Inf) + ψ.A[CartesianIndex(coord)] = normalize(M, Inf) end return ϵs end -""" - su3site_iter(state::InfinitePEPS, gatempos, alg::SimpleUpdate, env::SUWeight) - su3site_iter(densitymatrix::InfinitePEPO, gatempos, alg::SimpleUpdate, env::SUWeight; gate_bothsides::Bool = true) - -One round of 3-site simple update, which applies the Trotter gate MPOs `gatempos` -on an InfinitePEPS `state` or InfinitePEPO `densitymatrix`. -""" -function su3site_iter( - state::InfiniteState, gatempos::Vector{G}, alg::SimpleUpdate, env::SUWeight +function su_iter( + ψ::InfiniteState, gatempos::Vector{G}, alg::SimpleUpdate, env::SUWeight ) where {G <: AbstractMatrix} - if state isa InfinitePEPO - @assert size(state, 3) == 1 + if ψ isa InfinitePEPO + @assert size(ψ, 3) == 1 end - Nr, Nc = size(state)[1:2] + Nr, Nc = size(ψ)[1:2] (Nr >= 2 && Nc >= 2) || throw( ArgumentError( "iPEPS unit cell size for simple update should be no smaller than (2, 2)." ), ) - state2, env2 = deepcopy(state), deepcopy(env) + ψ2, env2 = deepcopy(ψ), deepcopy(env) trscheme = alg.trscheme for i in 1:4 - Nr, Nc = size(state2)[1:2] + Nr, Nc = size(ψ2)[1:2] for r in 1:Nr, c in 1:Nc gs = gatempos[i][r, c] trschemes = [ truncation_scheme(trscheme, 1, r, c) truncation_scheme(trscheme, 2, r, _next(c, Nc)) ] - _su3site_se!(state2, gs, env2, r, c, trschemes; alg.gate_bothsides) + _su3site_se!(ψ2, gs, env2, r, c, trschemes; alg.gate_bothsides) end - state2, env2 = rotl90(state2), rotl90(env2) + ψ2, env2 = rotl90(ψ2), rotl90(env2) trscheme = rotl90(trscheme) end wtdiff = compare_weights(env2, env) - return state2, env2, (; wtdiff) -end - -""" -Perform 3-site simple update for Hamiltonian `H`. -""" -function _simpleupdate3site( - state::InfiniteState, H::LocalOperator, - dt::Float64, nstep::Int, alg::SimpleUpdate, env::SUWeight; - imaginary_time::Bool - ) - time_start = time() - dt′, check_convergence = _process_timeevol_args(state, dt, imaginary_time, alg.tol) - gatempos = [ - _get_gatempos_se(H, dt′), - _get_gatempos_se(rotl90(H), dt′), - _get_gatempos_se(rot180(H), dt′), - _get_gatempos_se(rotr90(H), dt′), - ] - info = nothing - for step in 1:nstep - time0 = time() - state, env, info = su3site_iter(state, gatempos, alg, env) - converge = (info.wtdiff < alg.tol) - time1 = time() - if (step % alg.check_interval == 0) || (step == nstep) || converge - @info "Space of x-weight at [1, 1] = $(space(env[1, 1, 1], 1))" - @info @sprintf( - "SU iter %-7d: dt = %.0e, |Δλ| = %.3e. Time = %.3f s/it", - step, dt, info.wtdiff, time1 - time0 - ) - end - if check_convergence - converge && break - if step == nstep - @warn "SU ground state search has not converged." - end - end - end - time_end = time() - @info @sprintf("Simple update finished. Time elasped: %.2f s", time_end - time_start) - return state, env, info + return ψ2, env2, (; wtdiff) end diff --git a/src/algorithms/time_evolution/time_evolve.jl b/src/algorithms/time_evolution/time_evolve.jl index a88d87c52..7809e7fdd 100644 --- a/src/algorithms/time_evolution/time_evolve.jl +++ b/src/algorithms/time_evolution/time_evolve.jl @@ -1,23 +1,12 @@ -@doc """ - time_evolve(ψ₀, H, dt, nstep, alg, envs₀; kwargs...) -> (ψ, envs, info) - -Time-evolve the initial state `ψ₀` with Hamiltonian `H` over -the time span `dt * nstep` based on Trotter decomposition. - -## Arguments - -- `ψ₀::Union{InfinitePEPS, InfinitePEPO}`: Initial state. -- `H::LocalOperator`: Hamiltonian operator (time-independent). -- `dt::Float64`: Trotter time evolution step. -- `nstep::Int`: Number of Trotter steps to be taken, such that the evolved time span is `dt * nstep`. -- `alg`: Time evolution algorithm. -- `envs₀`: Environment of the initial state. - -## Keyword Arguments - -- `imaginary_time::Bool=false`: if true, the time evolution is done with an imaginary time step - instead, (i.e. ``\\exp(-Hdt)`` instead of ``\\exp(-iHdt)``). This can be useful for using this - function to compute the ground state of a Hamiltonian, or to compute finite-temperature - properties of a system. -""" -time_evolve +abstract type TimeEvolution end + +function MPSKit.time_evolve(alg::Alg) where {Alg <: TimeEvolution} + time_start = time() + result = nothing + for state in alg + result = state + end + time_end = time() + @info @sprintf("Simple update finished. Total time elasped: %.2f s", time_end - time_start) + return result +end From fe4cb7a7edf2407d7b86d5a64164084525f8c073 Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Tue, 28 Oct 2025 21:28:48 +0800 Subject: [PATCH 23/62] Update tests and some examples --- examples/heisenberg_su/main.jl | 7 +++- examples/j1j2_su/main.jl | 16 +++++--- test/examples/heisenberg.jl | 16 ++++---- test/examples/j1j2_finiteT.jl | 34 +++++++++------- test/examples/tf_ising_finiteT.jl | 60 +++++++++++++++++------------ test/timeevol/cluster_projectors.jl | 26 ++++++++----- test/timeevol/sitedep_truncation.jl | 17 ++++---- 7 files changed, 103 insertions(+), 73 deletions(-) diff --git a/examples/heisenberg_su/main.jl b/examples/heisenberg_su/main.jl index ad30075be..2a917f0c5 100644 --- a/examples/heisenberg_su/main.jl +++ b/examples/heisenberg_su/main.jl @@ -74,8 +74,11 @@ nstep = 10000 trscheme_peps = truncerr(1.0e-10) & truncdim(Dbond) for (dt, tol) in zip(dts, tols) - alg = SimpleUpdate(; tol, trscheme = trscheme_peps, bipartite = true, check_interval = 500) - global peps, wts, = time_evolve(peps, H, dt, nstep, alg, wts) + alg = SimpleUpdate(; + ψ0 = peps, env0 = wts, H, dt, tol, nstep, + trscheme = trscheme_peps, bipartite = true, check_interval = 500 + ) + global peps, wts, = time_evolve(alg) end md""" diff --git a/examples/j1j2_su/main.jl b/examples/j1j2_su/main.jl index 8d1b00307..697d3aacd 100644 --- a/examples/j1j2_su/main.jl +++ b/examples/j1j2_su/main.jl @@ -48,15 +48,17 @@ Therefore, we shall gradually increase $J_2 / J_1$ from 0.1 to 0.5, each time in on the previously evolved PEPS: """ -dt, tol, maxiter = 1.0e-2, 1.0e-8, 30000 +dt, tol, nstep = 1.0e-2, 1.0e-8, 30000 check_interval = 4000 trscheme_peps = truncerr(1.0e-10) & truncdim(Dbond) -alg = SimpleUpdate(dt, tol, maxiter, trscheme_peps) for J2 in 0.1:0.1:0.5 H = real( ## convert Hamiltonian `LocalOperator` to real floats j1_j2_model(ComplexF64, symm, InfiniteSquare(Nr, Nc); J1, J2, sublattice = false), ) - global peps, wts, = simpleupdate(peps, H, alg, wts; check_interval) + alg = SimpleUpdate(; + ψ0 = peps, env0 = wts, H, dt, tol, nstep, trscheme = trscheme_peps, check_interval + ) + global peps, wts, = time_evolve(alg) end md""" @@ -69,8 +71,10 @@ tols = [1.0e-9, 1.0e-9] J2 = 0.5 H = real(j1_j2_model(ComplexF64, symm, InfiniteSquare(Nr, Nc); J1, J2, sublattice = false)) for (dt, tol) in zip(dts, tols) - alg′ = SimpleUpdate(dt, tol, maxiter, trscheme_peps) - global peps, wts, = simpleupdate(peps, H, alg′, wts; check_interval) + alg = SimpleUpdate(; + ψ0 = peps, env0 = wts, H, dt, tol, nstep, trscheme = trscheme_peps, check_interval + ) + global peps, wts, = time_evolve(alg) end md""" @@ -115,7 +119,7 @@ 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) + H, peps₀, env; optimizer_alg = (; tol = 1.0e-4, nstep = 80) ); md""" diff --git a/test/examples/heisenberg.jl b/test/examples/heisenberg.jl index 2dcec90dc..374860538 100644 --- a/test/examples/heisenberg.jl +++ b/test/examples/heisenberg.jl @@ -59,10 +59,10 @@ end normalize!.(peps.A, Inf) # Heisenberg model Hamiltonian - ham = heisenberg_XYZ(InfiniteSquare(N1, N2); Jx = 1.0, Jy = 1.0, Jz = 1.0) + H = heisenberg_XYZ(InfiniteSquare(N1, N2); Jx = 1.0, Jy = 1.0, Jz = 1.0) # assert imaginary part is zero - @assert length(imag(ham).terms) == 0 - ham = real(ham) + @assert length(imag(H).terms) == 0 + H = real(H) # simple update dts = [1.0e-2, 1.0e-3, 1.0e-3, 1.0e-4] @@ -71,23 +71,21 @@ end for (n, (dt, tol)) in enumerate(zip(dts, tols)) Dbond2 = (n == 2) ? Dbond + 2 : Dbond trscheme = truncerr(1.0e-10) & truncdim(Dbond2) - alg = SimpleUpdate(; trscheme, bipartite = false) - peps, wts, = time_evolve(peps, ham, dt, nstep, alg, wts) + alg = SimpleUpdate(; ψ0 = peps, env0 = wts, H, dt, nstep, trscheme, bipartite = false) + peps, wts, = time_evolve(alg) end # measure physical quantities with CTMRG normalize!.(peps.A, Inf) env, = leading_boundary(CTMRGEnv(rand, Float64, peps, Espace), peps; tol = ctmrg_tol, maxiter = ctmrg_maxiter) - e_site = cost_function(peps, env, ham) / (N1 * N2) + e_site = cost_function(peps, env, H) / (N1 * N2) @info "Simple update energy = $e_site" # benchmark data from Phys. Rev. B 94, 035133 (2016) @test isapprox(e_site, -0.6594; atol = 1.0e-3) # continue with auto differentiation peps_final, env_final, E_final, = fixedpoint( - ham, - peps, - env; + H, peps, env; optimizer_alg = (; tol = gradtol, maxiter = 25), boundary_alg = (; maxiter = ctmrg_maxiter), gradient_alg = (; alg = :linsolver, solver_alg = (; alg = :gmres)), diff --git a/test/examples/j1j2_finiteT.jl b/test/examples/j1j2_finiteT.jl index 8b827a692..a23b3c561 100644 --- a/test/examples/j1j2_finiteT.jl +++ b/test/examples/j1j2_finiteT.jl @@ -20,34 +20,40 @@ function converge_env(state, χ::Int) end Nr, Nc = 2, 2 -ham = j1_j2_model( +H = j1_j2_model( Float64, SU2Irrep, InfiniteSquare(Nr, Nc); J1 = 1.0, J2 = 0.5, sublattice = false ) -pepo0 = PEPSKit.infinite_temperature_density_matrix(ham) -wts0 = SUWeight(pepo0) +ψ0 = PEPSKit.infinite_temperature_density_matrix(H) +wts0 = SUWeight(ψ0) # 7 = 1 (spin-0) + 2 x 3 (spin-1) -trscheme_pepo = truncdim(7) & truncerr(1.0e-12) +trscheme_ψ = truncdim(7) & truncerr(1.0e-12) check_interval = 100 # PEPO approach dt, nstep = 1.0e-3, 600 -alg = SimpleUpdate(; trscheme = trscheme_pepo, gate_bothsides = true, check_interval) -pepo, wts, = time_evolve(pepo0, ham, dt, nstep, alg, wts0) -env = converge_env(InfinitePartitionFunction(pepo), 16) -energy = expectation_value(pepo, ham, env) / (Nr * Nc) +alg = SimpleUpdate(; + ψ0, env0 = wts0, H, dt, nstep, trscheme = trscheme_ψ, + gate_bothsides = true, check_interval +) +ψ, wts, = time_evolve(alg) +env = converge_env(InfinitePartitionFunction(ψ), 16) +energy = expectation_value(ψ, H, env) / (Nr * Nc) @info "β = $(dt * nstep): tr(ρH) = $(energy)" @test energy ≈ bm[2] atol = 5.0e-3 # PEPS (purified PEPO) approach -alg = SimpleUpdate(; trscheme = trscheme_pepo, gate_bothsides = false, check_interval) -pepo, wts, = time_evolve(pepo0, ham, dt, nstep, alg, wts0) -env = converge_env(InfinitePartitionFunction(pepo), 16) -energy = expectation_value(pepo, ham, env) / (Nr * Nc) +alg = SimpleUpdate(; + ψ0, env0 = wts0, H, dt, nstep, trscheme = trscheme_ψ, + gate_bothsides = false, check_interval +) +ψ, wts, = time_evolve(alg) +env = converge_env(InfinitePartitionFunction(ψ), 16) +energy = expectation_value(ψ, H, env) / (Nr * Nc) @info "β = $(dt * nstep) / 2: tr(ρH) = $(energy)" @test energy ≈ bm[1] atol = 5.0e-3 -env = converge_env(InfinitePEPS(pepo), 16) -energy = expectation_value(pepo, ham, pepo, env) / (Nr * Nc) +env = converge_env(InfinitePEPS(ψ), 16) +energy = expectation_value(ψ, H, ψ, env) / (Nr * Nc) @info "β = $(dt * nstep): ⟨ρ|H|ρ⟩ = $(energy)" @test energy ≈ bm[2] atol = 5.0e-3 diff --git a/test/examples/tf_ising_finiteT.jl b/test/examples/tf_ising_finiteT.jl index 37b2484d8..b85c3835c 100644 --- a/test/examples/tf_ising_finiteT.jl +++ b/test/examples/tf_ising_finiteT.jl @@ -23,36 +23,36 @@ function tfising_model(T::Type{<:Number}, lattice::InfiniteSquare; J = 1.0, g = return PEPSKit.nearest_neighbour_hamiltonian(spaces, gate) end -function converge_env(state, χ::Int) +function converge_env(ψ, χ::Int) trscheme1 = truncdim(4) & truncerr(1.0e-12) - env0 = CTMRGEnv(randn, Float64, state, ℂ^4) - env, = leading_boundary(env0, state; alg = :sequential, trscheme = trscheme1, tol = 1.0e-10) + env0 = CTMRGEnv(randn, Float64, ψ, ℂ^4) + env, = leading_boundary(env0, ψ; alg = :sequential, trscheme = trscheme1, tol = 1.0e-10) trscheme2 = truncdim(χ) & truncerr(1.0e-12) - env, = leading_boundary(env, state; alg = :sequential, trscheme = trscheme2, tol = 1.0e-10) + env, = leading_boundary(env, ψ; alg = :sequential, trscheme = trscheme2, tol = 1.0e-10) return env end -function measure_mag(pepo::InfinitePEPO, env::CTMRGEnv; purified::Bool = false) +function measure_mag(ψ::InfinitePEPO, env::CTMRGEnv; purified::Bool = false) r, c = 1, 1 - lattice = physicalspace(pepo) + lattice = physicalspace(ψ) Mx = LocalOperator(lattice, ((r, c),) => σˣ(Float64, Trivial)) Mz = LocalOperator(lattice, ((r, c),) => σᶻ(Float64, Trivial)) if purified - magx = expectation_value(pepo, Mx, pepo, env) - magz = expectation_value(pepo, Mz, pepo, env) + magx = expectation_value(ψ, Mx, ψ, env) + magz = expectation_value(ψ, Mz, ψ, env) else - magx = expectation_value(pepo, Mx, env) - magz = expectation_value(pepo, Mz, env) + magx = expectation_value(ψ, Mx, env) + magz = expectation_value(ψ, Mz, env) end return [magx, magz] end Nr, Nc = 2, 2 -ham = tfising_model(Float64, InfiniteSquare(Nr, Nc); J = 1.0, g = 2.0) -pepo0 = PEPSKit.infinite_temperature_density_matrix(ham) -wts0 = SUWeight(pepo0) +H = tfising_model(Float64, InfiniteSquare(Nr, Nc); J = 1.0, g = 2.0) +ψ0 = PEPSKit.infinite_temperature_density_matrix(H) +wts0 = SUWeight(ψ0) -trscheme_pepo = truncdim(8) & truncerr(1.0e-12) +trscheme_ψ = truncdim(8) & truncerr(1.0e-12) dt, nstep = 1.0e-3, 400 β = dt * nstep @@ -60,24 +60,34 @@ dt, nstep = 1.0e-3, 400 # when g = 2, β = 0.4 and 2β = 0.8 belong to two phases (without and with nonzero σᶻ) # PEPO approach: results at β, or T = 2.5 -alg = SimpleUpdate(; trscheme = trscheme_pepo, gate_bothsides = true) -pepo, wts, = time_evolve(pepo0, ham, dt, nstep, alg, wts0) -env = converge_env(InfinitePartitionFunction(pepo), 16) -result_β = measure_mag(pepo, env) +alg = SimpleUpdate(; + ψ0, env0 = wts0, H, dt, nstep, + trscheme = trscheme_ψ, gate_bothsides = true +) +ψ, wts, = time_evolve(alg) +env = converge_env(InfinitePartitionFunction(ψ), 16) +result_β = measure_mag(ψ, env) @info "Magnetization at T = $(1 / β)" result_β @test isapprox(abs.(result_β), bm_β, rtol = 1.0e-2) # continue to get results at 2β, or T = 1.25 -pepo, wts, = time_evolve(pepo, ham, dt, nstep, alg, wts) -env = converge_env(InfinitePartitionFunction(pepo), 16) -result_2β = measure_mag(pepo, env) +alg = SimpleUpdate(; + ψ0 = ψ, env0 = wts, H, dt, nstep, + trscheme = trscheme_ψ, gate_bothsides = true +) +ψ, wts, = time_evolve(alg) +env = converge_env(InfinitePartitionFunction(ψ), 16) +result_2β = measure_mag(ψ, env) @info "Magnetization at T = $(1 / (2β))" result_2β @test isapprox(abs.(result_2β), bm_2β, rtol = 1.0e-4) # Purification approach: results at 2β, or T = 1.25 -alg = SimpleUpdate(; trscheme = trscheme_pepo, gate_bothsides = false) -pepo, = time_evolve(pepo0, ham, dt, 2 * nstep, alg, wts0) -env = converge_env(InfinitePEPS(pepo), 8) -result_2β′ = measure_mag(pepo, env; purified = true) +alg = SimpleUpdate(; + ψ0, env0 = wts0, H, dt, nstep = 2 * nstep, + trscheme = trscheme_ψ, gate_bothsides = false +) +ψ, = time_evolve(alg) +env = converge_env(InfinitePEPS(ψ), 8) +result_2β′ = measure_mag(ψ, env; purified = true) @info "Magnetization at T = $(1 / (2β)) (purification approach)" result_2β′ @test isapprox(abs.(result_2β′), bm_2β, rtol = 1.0e-2) diff --git a/test/timeevol/cluster_projectors.jl b/test/timeevol/cluster_projectors.jl index 90220aa81..65a8b8930 100644 --- a/test/timeevol/cluster_projectors.jl +++ b/test/timeevol/cluster_projectors.jl @@ -108,7 +108,7 @@ end trscheme_env = truncerr(1.0e-12) & truncdim(16) peps = InfinitePEPS(rand, Float64, Pspace, Vspace; unitcell = (Nr, Nc)) wts = SUWeight(peps) - ham = real( + H = real( hubbard_model( ComplexF64, Trivial, U1Irrep, InfiniteSquare(Nr, Nc); t = 1.0, U = 8.0, mu = 0.0 ), @@ -119,25 +119,33 @@ end nstep = 5000 for (n, (dt, tol)) in enumerate(zip(dts, tols)) trscheme = truncerr(1.0e-10) & truncdim(n == 1 ? 4 : 2) - alg = SimpleUpdate(; tol, trscheme, bipartite = true, check_interval = 1000) - peps, wts, = time_evolve(peps, ham, dt, nstep, alg, wts) + alg = SimpleUpdate(; + ψ0 = peps, env0 = wts, H, dt, nstep, tol, trscheme, + bipartite = true, check_interval = 1000 + ) + peps, wts, = time_evolve(alg) end normalize!.(peps.A, Inf) - env = CTMRGEnv(rand, Float64, peps, Espace) - env, = leading_boundary(env, peps; tol = ctmrg_tol, trscheme = trscheme_env) - e_site = cost_function(peps, env, ham) / (Nr * Nc) + env, = leading_boundary( + CTMRGEnv(wts, peps), peps; + tol = ctmrg_tol, trscheme = trscheme_env + ) + e_site = cost_function(peps, env, H) / (Nr * Nc) @info "2-site simple update energy = $e_site" # continue with 3-site simple update; energy should not change much dts = [1.0e-2, 5.0e-3] tols = [1.0e-8, 1.0e-8] trscheme = truncerr(1.0e-10) & truncdim(2) for (n, (dt, tol)) in enumerate(zip(dts, tols)) - alg = SimpleUpdate(; tol, trscheme, check_interval = 1000, force_3site = true) - peps, wts, = time_evolve(peps, ham, dt, nstep, alg, wts) + alg = SimpleUpdate(; + ψ0 = peps, env0 = wts, H, dt, nstep, tol, trscheme, + check_interval = 1000, force_3site = true + ) + peps, wts, = time_evolve(alg) end normalize!.(peps.A, Inf) env, = leading_boundary(env, peps; tol = ctmrg_tol, trscheme = trscheme_env) - e_site2 = cost_function(peps, env, ham) / (Nr * Nc) + e_site2 = cost_function(peps, env, H) / (Nr * Nc) @info "3-site simple update energy = $e_site2" @test e_site ≈ e_site2 atol = 5.0e-4 end diff --git a/test/timeevol/sitedep_truncation.jl b/test/timeevol/sitedep_truncation.jl index 3ee588d6c..cb038f1a5 100644 --- a/test/timeevol/sitedep_truncation.jl +++ b/test/timeevol/sitedep_truncation.jl @@ -19,7 +19,7 @@ end @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)) + H = 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)) env0 = SUWeight(peps0) @@ -27,8 +27,8 @@ end # set trscheme to be compatible with bipartite structure bonddims = stack([[6 4; 4 6], [5 7; 7 5]]; dims = 1) trscheme = SiteDependentTruncation(collect(truncdim(d) for d in bonddims)) - alg = SimpleUpdate(; trscheme, bipartite = true) - peps, env, = time_evolve(peps0, ham, 1.0e-2, 4, alg, env0) + alg = SimpleUpdate(; ψ0 = peps0, env0, H, dt = 1.0e-2, nstep = 4, trscheme, bipartite = true) + peps, env, = time_evolve(alg) @test get_bonddims(peps) == bonddims @test get_bonddims(env) == bonddims # check bipartite structure is preserved @@ -51,15 +51,16 @@ end # Site dependent truncation bonddims = rand(2:8, 2, Nr, Nc) trscheme = SiteDependentTruncation(collect(truncdim(d) for d in bonddims)) - alg = SimpleUpdate(; trscheme) # 2-site SU - ham = real(heisenberg_XYZ(InfiniteSquare(Nr, Nc); Jx = 1.0, Jy = 1.0, Jz = 1.0)) - peps, env, = time_evolve(peps0, ham, 1.0e-2, 4, alg, env0) + H = real(heisenberg_XYZ(InfiniteSquare(Nr, Nc); Jx = 1.0, Jy = 1.0, Jz = 1.0)) + alg = SimpleUpdate(; ψ0 = peps0, env0, H, dt = 1.0e-2, nstep = 4, trscheme) + peps, env, = time_evolve(alg) @test get_bonddims(peps) == bonddims @test get_bonddims(env) == bonddims # 3-site SU - ham = real(j1_j2_model(InfiniteSquare(Nr, Nc); J1 = 1.0, J2 = 0.5, sublattice = false)) - peps, env, = time_evolve(peps0, ham, 1.0e-2, 4, alg, env0) + H = real(j1_j2_model(InfiniteSquare(Nr, Nc); J1 = 1.0, J2 = 0.5, sublattice = false)) + alg = SimpleUpdate(; ψ0 = peps0, env0, H, dt = 1.0e-2, nstep = 4, trscheme) + peps, env, = time_evolve(alg) @test get_bonddims(peps) == bonddims @test get_bonddims(env) == bonddims end From b5468eed58a76e93a6fe82b70f364124208506dc Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Tue, 28 Oct 2025 22:59:17 +0800 Subject: [PATCH 24/62] Fix test --- test/bondenv/benv_fu.jl | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/test/bondenv/benv_fu.jl b/test/bondenv/benv_fu.jl index 52bfca5ce..d3e61530a 100644 --- a/test/bondenv/benv_fu.jl +++ b/test/bondenv/benv_fu.jl @@ -13,8 +13,11 @@ function get_hubbard_state(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(; tol = 1.0e-8, trscheme = truncerr(1.0e-10) & truncdim(4), check_interval = 2000) - peps, = time_evolve(peps, H, 1.0e-2, 10000, alg, wts) + alg = SimpleUpdate(; + ψ0 = peps, env0 = wts, H, dt = 1.0e-2, nstep = 10000, tol = 1.0e-8, + trscheme = truncerr(1.0e-10) & truncdim(4), check_interval = 2000 + ) + peps, = time_evolve(alg) normalize!.(peps.A, Inf) return peps end From f4b98a7ab145224009f66b5291a05c911c0ddcea Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Wed, 29 Oct 2025 16:59:54 +0800 Subject: [PATCH 25/62] Minor fixes [skip ci] --- src/algorithms/time_evolution/simpleupdate3site.jl | 2 +- src/algorithms/time_evolution/time_evolve.jl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/algorithms/time_evolution/simpleupdate3site.jl b/src/algorithms/time_evolution/simpleupdate3site.jl index abd37e4b2..cba5d51f7 100644 --- a/src/algorithms/time_evolution/simpleupdate3site.jl +++ b/src/algorithms/time_evolution/simpleupdate3site.jl @@ -95,7 +95,7 @@ Here `-*-` is the twist on the physical axis. # Truncation of a bond on OBC-MPS Suppose we want to truncate the bond between -the n-th and the (n+1)-th sites such that the truncated ψ +the n-th and the (n+1)-th sites such that the truncated state ``` |ψ̃⟩ = M[1]---...---M̃[n]---M̃[n+1]---...---M[N] ↓ ↓ ↓ ↓ diff --git a/src/algorithms/time_evolution/time_evolve.jl b/src/algorithms/time_evolution/time_evolve.jl index 7809e7fdd..6ef533399 100644 --- a/src/algorithms/time_evolution/time_evolve.jl +++ b/src/algorithms/time_evolution/time_evolve.jl @@ -7,6 +7,6 @@ function MPSKit.time_evolve(alg::Alg) where {Alg <: TimeEvolution} result = state end time_end = time() - @info @sprintf("Simple update finished. Total time elasped: %.2f s", time_end - time_start) + @info @sprintf("Time evolution finished. Total time elasped: %.2f s", time_end - time_start) return result end From 8f7dff4594d25dd15564511b56db84a26f9ec6e9 Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Tue, 4 Nov 2025 10:02:35 +0800 Subject: [PATCH 26/62] Fix merge regressions --- examples/j1j2_su/main.jl | 2 +- .../time_evolution/simpleupdate3site.jl | 2 +- test/timeevol/cluster_projectors.jl | 154 +++++++++--------- test/timeevol/sitedep_truncation.jl | 4 +- 4 files changed, 81 insertions(+), 81 deletions(-) diff --git a/examples/j1j2_su/main.jl b/examples/j1j2_su/main.jl index d2e34814d..d50e256e5 100644 --- a/examples/j1j2_su/main.jl +++ b/examples/j1j2_su/main.jl @@ -119,7 +119,7 @@ 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, nstep = 80) + H, peps₀, env; optimizer_alg = (; tol = 1.0e-4, maxiter = 80) ); md""" diff --git a/src/algorithms/time_evolution/simpleupdate3site.jl b/src/algorithms/time_evolution/simpleupdate3site.jl index 6e3ede02e..67e0f38c4 100644 --- a/src/algorithms/time_evolution/simpleupdate3site.jl +++ b/src/algorithms/time_evolution/simpleupdate3site.jl @@ -463,7 +463,7 @@ function _su3site_se!( ψ::InfiniteState, gs::Vector{T}, env::SUWeight, row::Int, col::Int, truncs::Vector{E}; gate_bothsides::Bool = true - ) where {T <: AbstractTensorMap, E <: TruncationScheme} + ) where {T <: AbstractTensorMap, E <: TruncationStrategy} Nr, Nc = size(ψ) @assert 1 <= row <= Nr && 1 <= col <= Nc rm1, cp1 = _prev(row, Nr), _next(col, Nc) diff --git a/test/timeevol/cluster_projectors.jl b/test/timeevol/cluster_projectors.jl index 1f2d0f6d5..f710c4c25 100644 --- a/test/timeevol/cluster_projectors.jl +++ b/test/timeevol/cluster_projectors.jl @@ -21,82 +21,82 @@ Vspaces = [ ), ] -@testset "Cluster bond truncation with projectors" begin - Random.seed!(0) - N, n = 5, 2 - for (Vphy, Vns, V) in Vspaces - Vvirs = fill(Vns, N + 1) - Vvirs[n + 1] = V - Ms1 = map(1:N) do i - Vw, Ve = Vvirs[i], Vvirs[i + 1] - return rand(Vw ⊗ Vphy ⊗ Vns' ⊗ Vns ← Ve) - end - normalize!.(Ms1, Inf) - revs = [isdual(space(M, 1)) for M in Ms1[2:end]] - # no truncation - Ms2 = deepcopy(Ms1) - wts2, ϵs, = _cluster_truncate!(Ms2, fill(FixedSpaceTruncation(), N - 1), revs) - @test all((ϵ == 0) for ϵ in ϵs) - normalize!.(Ms2, Inf) - @test fidelity_cluster(Ms1, Ms2) ≈ 1.0 - lorths, rorths = verify_cluster_orth(Ms2, wts2) - @test all(lorths) && all(rorths) - # truncation on one bond - Ms3 = deepcopy(Ms1) - tspace = isdual(Vns) ? flip(Vns) : Vns - wts3, ϵs, = _cluster_truncate!(Ms3, fill(truncspace(tspace), N - 1), revs) - @test all((i == n) || (ϵ == 0) for (i, ϵ) in enumerate(ϵs)) - normalize!.(Ms3, Inf) - ϵ = ϵs[n] - wt2, wt3 = wts2[n], wts3[n] - fid3, fid3_ = fidelity_cluster(Ms1, Ms3), fidelity_cluster(Ms2, Ms3) - @info "Fidelity of truncated cluster = $(fid3)" - @test fid3 ≈ fid3_ - @test fid3 ≈ (norm(wt3) / norm(wt2))^2 - @test fid3 ≈ 1.0 - (ϵ / norm(wt2))^2 - end -end +# @testset "Cluster bond truncation with projectors" begin +# Random.seed!(0) +# N, n = 5, 2 +# for (Vphy, Vns, V) in Vspaces +# Vvirs = fill(Vns, N + 1) +# Vvirs[n + 1] = V +# Ms1 = map(1:N) do i +# Vw, Ve = Vvirs[i], Vvirs[i + 1] +# return rand(Vw ⊗ Vphy ⊗ Vns' ⊗ Vns ← Ve) +# end +# normalize!.(Ms1, Inf) +# revs = [isdual(space(M, 1)) for M in Ms1[2:end]] +# # no truncation +# Ms2 = deepcopy(Ms1) +# wts2, ϵs, = _cluster_truncate!(Ms2, fill(FixedSpaceTruncation(), N - 1), revs) +# @test all((ϵ == 0) for ϵ in ϵs) +# normalize!.(Ms2, Inf) +# @test fidelity_cluster(Ms1, Ms2) ≈ 1.0 +# lorths, rorths = verify_cluster_orth(Ms2, wts2) +# @test all(lorths) && all(rorths) +# # truncation on one bond +# Ms3 = deepcopy(Ms1) +# tspace = isdual(Vns) ? flip(Vns) : Vns +# wts3, ϵs, = _cluster_truncate!(Ms3, fill(truncspace(tspace), N - 1), revs) +# @test all((i == n) || (ϵ == 0) for (i, ϵ) in enumerate(ϵs)) +# normalize!.(Ms3, Inf) +# ϵ = ϵs[n] +# wt2, wt3 = wts2[n], wts3[n] +# fid3, fid3_ = fidelity_cluster(Ms1, Ms3), fidelity_cluster(Ms2, Ms3) +# @info "Fidelity of truncated cluster = $(fid3)" +# @test fid3 ≈ fid3_ +# @test fid3 ≈ (norm(wt3) / norm(wt2))^2 +# @test fid3 ≈ 1.0 - (ϵ / norm(wt2))^2 +# end +# end -@testset "Identity gate on 3-site cluster" begin - N, n = 3, 1 - for (Vphy, Vns, V) in Vspaces - Vvirs = fill(Vns, N + 1) - Vvirs[n + 1] = V - Ms1 = map(1:N) do i - Vw, Ve = Vvirs[i], Vvirs[i + 1] - return normalize(rand(Vw ⊗ Vphy ⊗ Vns' ⊗ Vns ← Ve), Inf) - end - unit = id(Vphy) - gate = reduce(⊗, fill(unit, 3)) - gs = PEPSKit.gate_to_mpo3(gate) - @test mpo_to_gate3(gs) ≈ gate - Ms2 = deepcopy(Ms1) - PEPSKit._apply_gatempo!(Ms2, gs) - fid = fidelity_cluster(Ms1, Ms2) - @test fid ≈ 1.0 - end - for (Vphy, Vns, V) in Vspaces - Vvirs = fill(Vns, N + 1) - Vvirs[n + 1] = V - Ms1 = map(1:N) do i - Vw, Ve = Vvirs[i], Vvirs[i + 1] - return normalize(rand(Vw ⊗ Vphy ⊗ Vphy' ⊗ Vns' ⊗ Vns ← Ve), Inf) - end - unit = id(Vphy) - gate = reduce(⊗, fill(unit, 3)) - gs = PEPSKit.gate_to_mpo3(gate) - @test mpo_to_gate3(gs) ≈ gate - for gate_ax in 1:2 - Ms2 = deepcopy(Ms1) - PEPSKit._apply_gatempo!(Ms2, gs) - fid = fidelity_cluster( - [first(PEPSKit._fuse_physicalspaces(M)) for M in Ms1], - [first(PEPSKit._fuse_physicalspaces(M)) for M in Ms2] - ) - @test fid ≈ 1.0 - end - end -end +# @testset "Identity gate on 3-site cluster" begin +# N, n = 3, 1 +# for (Vphy, Vns, V) in Vspaces +# Vvirs = fill(Vns, N + 1) +# Vvirs[n + 1] = V +# Ms1 = map(1:N) do i +# Vw, Ve = Vvirs[i], Vvirs[i + 1] +# return normalize(rand(Vw ⊗ Vphy ⊗ Vns' ⊗ Vns ← Ve), Inf) +# end +# unit = id(Vphy) +# gate = reduce(⊗, fill(unit, 3)) +# gs = PEPSKit.gate_to_mpo3(gate) +# @test mpo_to_gate3(gs) ≈ gate +# Ms2 = deepcopy(Ms1) +# PEPSKit._apply_gatempo!(Ms2, gs) +# fid = fidelity_cluster(Ms1, Ms2) +# @test fid ≈ 1.0 +# end +# for (Vphy, Vns, V) in Vspaces +# Vvirs = fill(Vns, N + 1) +# Vvirs[n + 1] = V +# Ms1 = map(1:N) do i +# Vw, Ve = Vvirs[i], Vvirs[i + 1] +# return normalize(rand(Vw ⊗ Vphy ⊗ Vphy' ⊗ Vns' ⊗ Vns ← Ve), Inf) +# end +# unit = id(Vphy) +# gate = reduce(⊗, fill(unit, 3)) +# gs = PEPSKit.gate_to_mpo3(gate) +# @test mpo_to_gate3(gs) ≈ gate +# for gate_ax in 1:2 +# Ms2 = deepcopy(Ms1) +# PEPSKit._apply_gatempo!(Ms2, gs) +# fid = fidelity_cluster( +# [first(PEPSKit._fuse_physicalspaces(M)) for M in Ms1], +# [first(PEPSKit._fuse_physicalspaces(M)) for M in Ms2] +# ) +# @test fid ≈ 1.0 +# end +# end +# end @testset "Hubbard model with 2-site and 3-site SU" begin Nr, Nc = 2, 2 @@ -129,7 +129,7 @@ end normalize!.(peps.A, Inf) env, = leading_boundary( CTMRGEnv(wts, peps), peps; - tol = ctmrg_tol, trscheme = trscheme_env + tol = ctmrg_tol, trunc = trunc_env ) e_site = cost_function(peps, env, H) / (Nr * Nc) @info "2-site simple update energy = $e_site" @@ -145,7 +145,7 @@ end peps, wts, = time_evolve(alg) end normalize!.(peps.A, Inf) - env, = leading_boundary(env, peps; tol = ctmrg_tol, trscheme = trscheme_env) + env, = leading_boundary(env, peps; tol = ctmrg_tol, trunc = trunc_env) e_site2 = cost_function(peps, env, H) / (Nr * Nc) @info "3-site simple update energy = $e_site2" @test e_site ≈ e_site2 atol = 5.0e-4 diff --git a/test/timeevol/sitedep_truncation.jl b/test/timeevol/sitedep_truncation.jl index fe9f97d16..3f7a48ad0 100644 --- a/test/timeevol/sitedep_truncation.jl +++ b/test/timeevol/sitedep_truncation.jl @@ -54,13 +54,13 @@ end trunc = SiteDependentTruncation(collect(truncrank(d) for d in bonddims)) # 2-site SU H = real(heisenberg_XYZ(InfiniteSquare(Nr, Nc); Jx = 1.0, Jy = 1.0, Jz = 1.0)) - alg = SimpleUpdate(; ψ0 = peps0, env0, H, dt = 1.0e-2, nstep = 4, trscheme) + alg = SimpleUpdate(; ψ0 = peps0, env0, H, dt = 1.0e-2, nstep = 4, trunc) peps, env, = time_evolve(alg) @test get_bonddims(peps) == bonddims @test get_bonddims(env) == bonddims # 3-site SU H = real(j1_j2_model(InfiniteSquare(Nr, Nc); J1 = 1.0, J2 = 0.5, sublattice = false)) - alg = SimpleUpdate(; ψ0 = peps0, env0, H, dt = 1.0e-2, nstep = 4, trscheme) + alg = SimpleUpdate(; ψ0 = peps0, env0, H, dt = 1.0e-2, nstep = 4, trunc) peps, env, = time_evolve(alg) @test get_bonddims(peps) == bonddims @test get_bonddims(env) == bonddims From 542b58efeef54fc871486861633f9c57dd94a707 Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Tue, 4 Nov 2025 10:29:23 +0800 Subject: [PATCH 27/62] Restore accidentally commented tests --- test/timeevol/cluster_projectors.jl | 150 ++++++++++++++-------------- 1 file changed, 75 insertions(+), 75 deletions(-) diff --git a/test/timeevol/cluster_projectors.jl b/test/timeevol/cluster_projectors.jl index f710c4c25..9a091cca7 100644 --- a/test/timeevol/cluster_projectors.jl +++ b/test/timeevol/cluster_projectors.jl @@ -21,82 +21,82 @@ Vspaces = [ ), ] -# @testset "Cluster bond truncation with projectors" begin -# Random.seed!(0) -# N, n = 5, 2 -# for (Vphy, Vns, V) in Vspaces -# Vvirs = fill(Vns, N + 1) -# Vvirs[n + 1] = V -# Ms1 = map(1:N) do i -# Vw, Ve = Vvirs[i], Vvirs[i + 1] -# return rand(Vw ⊗ Vphy ⊗ Vns' ⊗ Vns ← Ve) -# end -# normalize!.(Ms1, Inf) -# revs = [isdual(space(M, 1)) for M in Ms1[2:end]] -# # no truncation -# Ms2 = deepcopy(Ms1) -# wts2, ϵs, = _cluster_truncate!(Ms2, fill(FixedSpaceTruncation(), N - 1), revs) -# @test all((ϵ == 0) for ϵ in ϵs) -# normalize!.(Ms2, Inf) -# @test fidelity_cluster(Ms1, Ms2) ≈ 1.0 -# lorths, rorths = verify_cluster_orth(Ms2, wts2) -# @test all(lorths) && all(rorths) -# # truncation on one bond -# Ms3 = deepcopy(Ms1) -# tspace = isdual(Vns) ? flip(Vns) : Vns -# wts3, ϵs, = _cluster_truncate!(Ms3, fill(truncspace(tspace), N - 1), revs) -# @test all((i == n) || (ϵ == 0) for (i, ϵ) in enumerate(ϵs)) -# normalize!.(Ms3, Inf) -# ϵ = ϵs[n] -# wt2, wt3 = wts2[n], wts3[n] -# fid3, fid3_ = fidelity_cluster(Ms1, Ms3), fidelity_cluster(Ms2, Ms3) -# @info "Fidelity of truncated cluster = $(fid3)" -# @test fid3 ≈ fid3_ -# @test fid3 ≈ (norm(wt3) / norm(wt2))^2 -# @test fid3 ≈ 1.0 - (ϵ / norm(wt2))^2 -# end -# end +@testset "Cluster bond truncation with projectors" begin + Random.seed!(0) + N, n = 5, 2 + for (Vphy, Vns, V) in Vspaces + Vvirs = fill(Vns, N + 1) + Vvirs[n + 1] = V + Ms1 = map(1:N) do i + Vw, Ve = Vvirs[i], Vvirs[i + 1] + return rand(Vw ⊗ Vphy ⊗ Vns' ⊗ Vns ← Ve) + end + normalize!.(Ms1, Inf) + revs = [isdual(space(M, 1)) for M in Ms1[2:end]] + # no truncation + Ms2 = deepcopy(Ms1) + wts2, ϵs, = _cluster_truncate!(Ms2, fill(FixedSpaceTruncation(), N - 1), revs) + @test all((ϵ == 0) for ϵ in ϵs) + normalize!.(Ms2, Inf) + @test fidelity_cluster(Ms1, Ms2) ≈ 1.0 + lorths, rorths = verify_cluster_orth(Ms2, wts2) + @test all(lorths) && all(rorths) + # truncation on one bond + Ms3 = deepcopy(Ms1) + tspace = isdual(Vns) ? flip(Vns) : Vns + wts3, ϵs, = _cluster_truncate!(Ms3, fill(truncspace(tspace), N - 1), revs) + @test all((i == n) || (ϵ == 0) for (i, ϵ) in enumerate(ϵs)) + normalize!.(Ms3, Inf) + ϵ = ϵs[n] + wt2, wt3 = wts2[n], wts3[n] + fid3, fid3_ = fidelity_cluster(Ms1, Ms3), fidelity_cluster(Ms2, Ms3) + @info "Fidelity of truncated cluster = $(fid3)" + @test fid3 ≈ fid3_ + @test fid3 ≈ (norm(wt3) / norm(wt2))^2 + @test fid3 ≈ 1.0 - (ϵ / norm(wt2))^2 + end +end -# @testset "Identity gate on 3-site cluster" begin -# N, n = 3, 1 -# for (Vphy, Vns, V) in Vspaces -# Vvirs = fill(Vns, N + 1) -# Vvirs[n + 1] = V -# Ms1 = map(1:N) do i -# Vw, Ve = Vvirs[i], Vvirs[i + 1] -# return normalize(rand(Vw ⊗ Vphy ⊗ Vns' ⊗ Vns ← Ve), Inf) -# end -# unit = id(Vphy) -# gate = reduce(⊗, fill(unit, 3)) -# gs = PEPSKit.gate_to_mpo3(gate) -# @test mpo_to_gate3(gs) ≈ gate -# Ms2 = deepcopy(Ms1) -# PEPSKit._apply_gatempo!(Ms2, gs) -# fid = fidelity_cluster(Ms1, Ms2) -# @test fid ≈ 1.0 -# end -# for (Vphy, Vns, V) in Vspaces -# Vvirs = fill(Vns, N + 1) -# Vvirs[n + 1] = V -# Ms1 = map(1:N) do i -# Vw, Ve = Vvirs[i], Vvirs[i + 1] -# return normalize(rand(Vw ⊗ Vphy ⊗ Vphy' ⊗ Vns' ⊗ Vns ← Ve), Inf) -# end -# unit = id(Vphy) -# gate = reduce(⊗, fill(unit, 3)) -# gs = PEPSKit.gate_to_mpo3(gate) -# @test mpo_to_gate3(gs) ≈ gate -# for gate_ax in 1:2 -# Ms2 = deepcopy(Ms1) -# PEPSKit._apply_gatempo!(Ms2, gs) -# fid = fidelity_cluster( -# [first(PEPSKit._fuse_physicalspaces(M)) for M in Ms1], -# [first(PEPSKit._fuse_physicalspaces(M)) for M in Ms2] -# ) -# @test fid ≈ 1.0 -# end -# end -# end +@testset "Identity gate on 3-site cluster" begin + N, n = 3, 1 + for (Vphy, Vns, V) in Vspaces + Vvirs = fill(Vns, N + 1) + Vvirs[n + 1] = V + Ms1 = map(1:N) do i + Vw, Ve = Vvirs[i], Vvirs[i + 1] + return normalize(rand(Vw ⊗ Vphy ⊗ Vns' ⊗ Vns ← Ve), Inf) + end + unit = id(Vphy) + gate = reduce(⊗, fill(unit, 3)) + gs = PEPSKit.gate_to_mpo3(gate) + @test mpo_to_gate3(gs) ≈ gate + Ms2 = deepcopy(Ms1) + PEPSKit._apply_gatempo!(Ms2, gs) + fid = fidelity_cluster(Ms1, Ms2) + @test fid ≈ 1.0 + end + for (Vphy, Vns, V) in Vspaces + Vvirs = fill(Vns, N + 1) + Vvirs[n + 1] = V + Ms1 = map(1:N) do i + Vw, Ve = Vvirs[i], Vvirs[i + 1] + return normalize(rand(Vw ⊗ Vphy ⊗ Vphy' ⊗ Vns' ⊗ Vns ← Ve), Inf) + end + unit = id(Vphy) + gate = reduce(⊗, fill(unit, 3)) + gs = PEPSKit.gate_to_mpo3(gate) + @test mpo_to_gate3(gs) ≈ gate + for gate_ax in 1:2 + Ms2 = deepcopy(Ms1) + PEPSKit._apply_gatempo!(Ms2, gs) + fid = fidelity_cluster( + [first(PEPSKit._fuse_physicalspaces(M)) for M in Ms1], + [first(PEPSKit._fuse_physicalspaces(M)) for M in Ms2] + ) + @test fid ≈ 1.0 + end + end +end @testset "Hubbard model with 2-site and 3-site SU" begin Nr, Nc = 2, 2 From 6e65759696921eb0aa9b7fd5a1f0c24653ae1547 Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Tue, 4 Nov 2025 15:13:00 +0800 Subject: [PATCH 28/62] Undo unnecessary renames --- src/algorithms/time_evolution/simpleupdate.jl | 38 +++++++------- .../time_evolution/simpleupdate3site.jl | 40 +++++++-------- test/examples/heisenberg.jl | 14 +++--- test/examples/j1j2_finiteT.jl | 28 +++++------ test/examples/tf_ising_finiteT.jl | 50 +++++++++---------- test/timeevol/cluster_projectors.jl | 16 +++--- test/timeevol/sitedep_truncation.jl | 11 ++-- 7 files changed, 98 insertions(+), 99 deletions(-) diff --git a/src/algorithms/time_evolution/simpleupdate.jl b/src/algorithms/time_evolution/simpleupdate.jl index 0461d40f3..be56d442f 100644 --- a/src/algorithms/time_evolution/simpleupdate.jl +++ b/src/algorithms/time_evolution/simpleupdate.jl @@ -45,14 +45,14 @@ Simple update of the x-bond between `[r,c]` and `[r,c+1]`. ``` =# function _su_xbond!( - ψ::InfiniteState, gate::AbstractTensorMap{T, S, 2, 2}, env::SUWeight, + state::InfiniteState, gate::AbstractTensorMap{T, S, 2, 2}, env::SUWeight, row::Int, col::Int, trunc::TruncationStrategy; gate_ax::Int = 1 ) where {T <: Number, S <: ElementarySpace} - Nr, Nc = size(ψ)[1:2] + Nr, Nc = size(state)[1:2] @assert 1 <= row <= Nr && 1 <= col <= Nc cp1 = _next(col, Nc) # absorb environment weights - A, B = ψ.A[row, col], ψ.A[row, cp1] + A, B = state.A[row, col], state.A[row, cp1] A = absorb_weight(A, env, row, col, (NORTH, SOUTH, WEST); inv = false) B = absorb_weight(B, env, row, cp1, (NORTH, SOUTH, EAST); inv = false) normalize!(A, Inf) @@ -68,7 +68,7 @@ function _su_xbond!( normalize!(B, Inf) normalize!(s, Inf) # update tensor dict and weight on current bond - ψ.A[row, col], ψ.A[row, cp1] = A, B + state.A[row, col], state.A[row, cp1] = A, B env.data[1, row, col] = s return ϵ end @@ -84,14 +84,14 @@ Simple update of the y-bond between `[r,c]` and `[r-1,c]`. ``` =# function _su_ybond!( - ψ::InfiniteState, gate::AbstractTensorMap{T, S, 2, 2}, env::SUWeight, + state::InfiniteState, gate::AbstractTensorMap{T, S, 2, 2}, env::SUWeight, row::Int, col::Int, trunc::TruncationStrategy; gate_ax::Int = 1 ) where {T <: Number, S <: ElementarySpace} - Nr, Nc = size(ψ)[1:2] + Nr, Nc = size(state)[1:2] @assert 1 <= row <= Nr && 1 <= col <= Nc rm1 = _prev(row, Nr) # absorb environment weights - A, B = ψ.A[row, col], ψ.A[rm1, col] + A, B = state.A[row, col], state.A[rm1, col] A = absorb_weight(A, env, row, col, (EAST, SOUTH, WEST); inv = false) B = absorb_weight(B, env, rm1, col, (NORTH, EAST, WEST); inv = false) normalize!(A, Inf) @@ -107,48 +107,48 @@ function _su_ybond!( normalize!(A, Inf) normalize!(B, Inf) normalize!(s, Inf) - ψ.A[row, col], ψ.A[rm1, col] = A, B + state.A[row, col], state.A[rm1, col] = A, B env.data[2, row, col] = s return ϵ end function su_iter( - ψ::InfiniteState, gate::LocalOperator, alg::SimpleUpdate, env::SUWeight + state::InfiniteState, gate::LocalOperator, alg::SimpleUpdate, env::SUWeight ) - @assert size(gate.lattice) == size(ψ)[1:2] - Nr, Nc = size(ψ)[1:2] + @assert size(gate.lattice) == size(state)[1:2] + Nr, Nc = size(state)[1:2] alg.bipartite && (@assert Nr == Nc == 2) (Nr >= 2 && Nc >= 2) || throw( ArgumentError("`state` unit cell size for simple update should be no smaller than (2, 2)."), ) - ψ2, env2 = deepcopy(ψ), deepcopy(env) + state2, env2 = deepcopy(state), deepcopy(env) gate_axs = alg.gate_bothsides ? (1:2) : (1:1) for r in 1:Nr, c in 1:Nc term = get_gateterm(gate, (CartesianIndex(r, c), CartesianIndex(r, c + 1))) trunc = truncation_strategy(alg.trunc, 1, r, c) for gate_ax in gate_axs - _su_xbond!(ψ2, term, env2, r, c, trunc; gate_ax) + _su_xbond!(state2, term, env2, r, c, trunc; gate_ax) end if alg.bipartite rp1, cp1 = _next(r, Nr), _next(c, Nc) - ψ2.A[rp1, cp1] = deepcopy(ψ2.A[r, c]) - ψ2.A[rp1, c] = deepcopy(ψ2.A[r, cp1]) + state2.A[rp1, cp1] = deepcopy(state2.A[r, c]) + state2.A[rp1, c] = deepcopy(state2.A[r, cp1]) env2.data[1, rp1, cp1] = deepcopy(env2.data[1, r, c]) end term = get_gateterm(gate, (CartesianIndex(r, c), CartesianIndex(r - 1, c))) trunc = truncation_strategy(alg.trunc, 2, r, c) for gate_ax in gate_axs - _su_ybond!(ψ2, term, env2, r, c, trunc; gate_ax) + _su_ybond!(state2, term, env2, r, c, trunc; gate_ax) end if alg.bipartite rm1, cm1 = _prev(r, Nr), _prev(c, Nc) - ψ2.A[rm1, cm1] = deepcopy(ψ2.A[r, c]) - ψ2.A[r, cm1] = deepcopy(ψ2.A[rm1, c]) + state2.A[rm1, cm1] = deepcopy(state2.A[r, c]) + state2.A[r, cm1] = deepcopy(state2.A[rm1, c]) env2.data[2, rm1, cm1] = deepcopy(env2.data[2, r, c]) end end wtdiff = compare_weights(env2, env) - return ψ2, env2, (; wtdiff) + return state2, env2, (; wtdiff) end # Initial iteration diff --git a/src/algorithms/time_evolution/simpleupdate3site.jl b/src/algorithms/time_evolution/simpleupdate3site.jl index 67e0f38c4..7dcbdd08d 100644 --- a/src/algorithms/time_evolution/simpleupdate3site.jl +++ b/src/algorithms/time_evolution/simpleupdate3site.jl @@ -447,28 +447,28 @@ Obtain the 3-site cluster in the "southeast corner" of a square plaquette. c c+1 ``` =# -function get_3site_se(ψ::InfiniteState, env::SUWeight, row::Int, col::Int) - Nr, Nc = size(ψ) +function get_3site_se(state::InfiniteState, env::SUWeight, row::Int, col::Int) + Nr, Nc = size(state) rm1, cp1 = _prev(row, Nr), _next(col, Nc) coords_se = [(row, col), (row, cp1), (rm1, cp1)] - perms_se = isa(ψ, InfinitePEPS) ? perms_se_peps : perms_se_pepo + perms_se = isa(state, InfinitePEPS) ? perms_se_peps : perms_se_pepo Ms = map(zip(coords_se, perms_se, openaxs_se)) do (coord, perm, openaxs) - M = absorb_weight(ψ.A[CartesianIndex(coord)], env, coord[1], coord[2], openaxs) + M = absorb_weight(state.A[CartesianIndex(coord)], env, coord[1], coord[2], openaxs) return permute(M, perm) end return Ms end function _su3site_se!( - ψ::InfiniteState, gs::Vector{T}, env::SUWeight, + state::InfiniteState, gs::Vector{T}, env::SUWeight, row::Int, col::Int, truncs::Vector{E}; gate_bothsides::Bool = true ) where {T <: AbstractTensorMap, E <: TruncationStrategy} - Nr, Nc = size(ψ) + Nr, Nc = size(state) @assert 1 <= row <= Nr && 1 <= col <= Nc rm1, cp1 = _prev(row, Nr), _next(col, Nc) # southwest 3-site cluster and arrow direction within it - Ms = get_3site_se(ψ, env, row, col) + Ms = get_3site_se(state, env, row, col) revs = [isdual(space(M, 1)) for M in Ms[2:end]] Vphys = [codomain(M, 2) for M in Ms] normalize!.(Ms, Inf) @@ -481,55 +481,55 @@ function _su3site_se!( ϵs = nothing for gate_ax in gate_axs _apply_gatempo!(Ms, gs; gate_ax) - if isa(ψ, InfinitePEPO) + if isa(state, InfinitePEPO) Ms = [first(_fuse_physicalspaces(M)) for M in Ms] end wts, ϵs, = _cluster_truncate!(Ms, truncs, revs) - if isa(ψ, InfinitePEPO) + if isa(state, InfinitePEPO) Ms = [first(_unfuse_physicalspace(M, Vphy)) for (M, Vphy) in zip(Ms, Vphys)] end for (wt, wt_idx) in zip(wts, wt_idxs) env[CartesianIndex(wt_idx)] = normalize(wt, Inf) end end - invperms_se = isa(ψ, InfinitePEPS) ? invperms_se_peps : invperms_se_pepo + invperms_se = isa(state, InfinitePEPS) ? invperms_se_peps : invperms_se_pepo for (M, coord, invperm, openaxs, Vphy) in zip(Ms, coords, invperms_se, openaxs_se, Vphys) # restore original axes order M = permute(M, invperm) # remove weights on open axes of the cluster M = absorb_weight(M, env, coord[1], coord[2], openaxs; inv = true) - ψ.A[CartesianIndex(coord)] = normalize(M, Inf) + state.A[CartesianIndex(coord)] = normalize(M, Inf) end return ϵs end function su_iter( - ψ::InfiniteState, gatempos::Vector{G}, alg::SimpleUpdate, env::SUWeight + state::InfiniteState, gatempos::Vector{G}, alg::SimpleUpdate, env::SUWeight ) where {G <: AbstractMatrix} - if ψ isa InfinitePEPO - @assert size(ψ, 3) == 1 + if state isa InfinitePEPO + @assert size(state, 3) == 1 end - Nr, Nc = size(ψ)[1:2] + Nr, Nc = size(state)[1:2] (Nr >= 2 && Nc >= 2) || throw( ArgumentError( "iPEPS unit cell size for simple update should be no smaller than (2, 2)." ), ) - ψ2, env2 = deepcopy(ψ), deepcopy(env) + state2, env2 = deepcopy(state), deepcopy(env) trunc = alg.trunc for i in 1:4 - Nr, Nc = size(ψ2)[1:2] + Nr, Nc = size(state2)[1:2] for r in 1:Nr, c in 1:Nc gs = gatempos[i][r, c] truncs = [ truncation_strategy(trunc, 1, r, c) truncation_strategy(trunc, 2, r, _next(c, Nc)) ] - _su3site_se!(ψ2, gs, env2, r, c, truncs; alg.gate_bothsides) + _su3site_se!(state2, gs, env2, r, c, truncs; alg.gate_bothsides) end - ψ2, env2 = rotl90(ψ2), rotl90(env2) + state2, env2 = rotl90(state2), rotl90(env2) trunc = rotl90(trunc) end wtdiff = compare_weights(env2, env) - return ψ2, env2, (; wtdiff) + return state2, env2, (; wtdiff) end diff --git a/test/examples/heisenberg.jl b/test/examples/heisenberg.jl index 3762c0e8e..d4224af9d 100644 --- a/test/examples/heisenberg.jl +++ b/test/examples/heisenberg.jl @@ -59,10 +59,10 @@ end normalize!.(peps.A, Inf) # Heisenberg model Hamiltonian - H = heisenberg_XYZ(InfiniteSquare(N1, N2); Jx = 1.0, Jy = 1.0, Jz = 1.0) + ham = heisenberg_XYZ(InfiniteSquare(N1, N2); Jx = 1.0, Jy = 1.0, Jz = 1.0) # assert imaginary part is zero - @assert length(imag(H).terms) == 0 - H = real(H) + @assert length(imag(ham).terms) == 0 + ham = real(ham) # simple update dts = [1.0e-2, 1.0e-3, 1.0e-3, 1.0e-4] @@ -71,21 +71,23 @@ end for (n, (dt, tol)) in enumerate(zip(dts, tols)) Dbond2 = (n == 2) ? Dbond + 2 : Dbond trunc = truncerror(; atol = 1.0e-10) & truncrank(Dbond2) - alg = SimpleUpdate(; ψ0 = peps, env0 = wts, H, dt, nstep, trunc, bipartite = false) + alg = SimpleUpdate(; ψ0 = peps, env0 = wts, H = ham, dt, nstep, trunc, bipartite = false) peps, wts, = time_evolve(alg) end # measure physical quantities with CTMRG normalize!.(peps.A, Inf) env, = leading_boundary(CTMRGEnv(rand, Float64, peps, Espace), peps; tol = ctmrg_tol, maxiter = ctmrg_maxiter) - e_site = cost_function(peps, env, H) / (N1 * N2) + e_site = cost_function(peps, env, ham) / (N1 * N2) @info "Simple update energy = $e_site" # benchmark data from Phys. Rev. B 94, 035133 (2016) @test isapprox(e_site, -0.6594; atol = 1.0e-3) # continue with auto differentiation peps_final, env_final, E_final, = fixedpoint( - H, peps, env; + ham, + peps, + env; optimizer_alg = (; tol = gradtol, maxiter = 25), boundary_alg = (; maxiter = ctmrg_maxiter), gradient_alg = (; alg = :linsolver, solver_alg = (; alg = :gmres)), diff --git a/test/examples/j1j2_finiteT.jl b/test/examples/j1j2_finiteT.jl index c12273e14..9a3828a8c 100644 --- a/test/examples/j1j2_finiteT.jl +++ b/test/examples/j1j2_finiteT.jl @@ -20,12 +20,12 @@ function converge_env(state, χ::Int) end Nr, Nc = 2, 2 -H = j1_j2_model( +ham = j1_j2_model( Float64, SU2Irrep, InfiniteSquare(Nr, Nc); J1 = 1.0, J2 = 0.5, sublattice = false ) -ψ0 = PEPSKit.infinite_temperature_density_matrix(H) -wts0 = SUWeight(ψ0) +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 @@ -33,27 +33,27 @@ check_interval = 100 # PEPO approach dt, nstep = 1.0e-3, 600 alg = SimpleUpdate(; - ψ0, env0 = wts0, H, dt, nstep, trunc = trunc_pepo, + ψ0 = pepo0, env0 = wts0, H = ham, dt, nstep, trunc = trunc_pepo, gate_bothsides = true, check_interval ) -ψ, wts, = time_evolve(alg) -env = converge_env(InfinitePartitionFunction(ψ), 16) -energy = expectation_value(ψ, H, env) / (Nr * Nc) +pepo, wts, = time_evolve(alg) +env = converge_env(InfinitePartitionFunction(pepo), 16) +energy = expectation_value(pepo, ham, env) / (Nr * Nc) @info "β = $(dt * nstep): tr(ρH) = $(energy)" @test energy ≈ bm[2] atol = 5.0e-3 # PEPS (purified PEPO) approach alg = SimpleUpdate(; - ψ0, env0 = wts0, H, dt, nstep, trunc = trunc_pepo, + ψ0 = pepo0, env0 = wts0, H = ham, dt, nstep, trunc = trunc_pepo, gate_bothsides = false, check_interval ) -ψ, wts, = time_evolve(alg) -env = converge_env(InfinitePartitionFunction(ψ), 16) -energy = expectation_value(ψ, H, env) / (Nr * Nc) +pepo, wts, = time_evolve(alg) +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 -env = converge_env(InfinitePEPS(ψ), 16) -energy = expectation_value(ψ, H, ψ, env) / (Nr * Nc) -@info "β = $(dt * nstep): ⟨ρ|H|ρ⟩ = $(energy)" +env = converge_env(InfinitePEPS(pepo), 16) +energy = expectation_value(pepo, ham, pepo, env) / (Nr * Nc) +@info "β = $(dt * nstep): ⟨ρ|ham|ρ⟩ = $(energy)" @test energy ≈ bm[2] atol = 5.0e-3 diff --git a/test/examples/tf_ising_finiteT.jl b/test/examples/tf_ising_finiteT.jl index 6b5d3c503..f3217b908 100644 --- a/test/examples/tf_ising_finiteT.jl +++ b/test/examples/tf_ising_finiteT.jl @@ -23,34 +23,34 @@ function tfising_model(T::Type{<:Number}, lattice::InfiniteSquare; J = 1.0, g = return PEPSKit.nearest_neighbour_hamiltonian(spaces, gate) end -function converge_env(ψ, χ::Int) +function converge_env(state, χ::Int) trunc1 = truncrank(4) & truncerror(; atol = 1.0e-12) - env0 = CTMRGEnv(randn, Float64, ψ, ℂ^4) - env, = leading_boundary(env0, ψ; alg = :sequential, trunc = trunc1, tol = 1.0e-10) + env0 = CTMRGEnv(randn, Float64, state, ℂ^4) + env, = leading_boundary(env0, state; alg = :sequential, trunc = trunc1, tol = 1.0e-10) trunc2 = truncrank(χ) & truncerror(; atol = 1.0e-12) - env, = leading_boundary(env, ψ; alg = :sequential, trunc = trunc2, tol = 1.0e-10) + env, = leading_boundary(env, state; alg = :sequential, trunc = trunc2, tol = 1.0e-10) return env end -function measure_mag(ψ::InfinitePEPO, env::CTMRGEnv; purified::Bool = false) +function measure_mag(pepo::InfinitePEPO, env::CTMRGEnv; purified::Bool = false) r, c = 1, 1 - lattice = physicalspace(ψ) + lattice = physicalspace(pepo) Mx = LocalOperator(lattice, ((r, c),) => σˣ(Float64, Trivial)) Mz = LocalOperator(lattice, ((r, c),) => σᶻ(Float64, Trivial)) if purified - magx = expectation_value(ψ, Mx, ψ, env) - magz = expectation_value(ψ, Mz, ψ, env) + magx = expectation_value(pepo, Mx, pepo, env) + magz = expectation_value(pepo, Mz, pepo, env) else - magx = expectation_value(ψ, Mx, env) - magz = expectation_value(ψ, Mz, env) + magx = expectation_value(pepo, Mx, env) + magz = expectation_value(pepo, Mz, env) end return [magx, magz] end Nr, Nc = 2, 2 -H = tfising_model(Float64, InfiniteSquare(Nr, Nc); J = 1.0, g = 2.0) -ψ0 = PEPSKit.infinite_temperature_density_matrix(H) -wts0 = SUWeight(ψ0) +ham = tfising_model(Float64, InfiniteSquare(Nr, Nc); J = 1.0, g = 2.0) +pepo0 = PEPSKit.infinite_temperature_density_matrix(ham) +wts0 = SUWeight(pepo0) trunc_pepo = truncrank(8) & truncerror(; atol = 1.0e-12) @@ -61,33 +61,33 @@ dt, nstep = 1.0e-3, 400 # PEPO approach: results at β, or T = 2.5 alg = SimpleUpdate(; - ψ0, env0 = wts0, H, dt, nstep, + ψ0 = pepo0, env0 = wts0, H = ham, dt, nstep, trunc = trunc_pepo, gate_bothsides = true ) -ψ, wts, = time_evolve(alg) -env = converge_env(InfinitePartitionFunction(ψ), 16) -result_β = measure_mag(ψ, env) +pepo, wts, = time_evolve(alg) +env = converge_env(InfinitePartitionFunction(pepo), 16) +result_β = measure_mag(pepo, env) @info "Magnetization at T = $(1 / β)" result_β @test isapprox(abs.(result_β), bm_β, rtol = 1.0e-2) # continue to get results at 2β, or T = 1.25 alg = SimpleUpdate(; - ψ0 = ψ, env0 = wts, H, dt, nstep, + ψ0 = pepo, env0 = wts, H = ham, dt, nstep, trunc = trunc_pepo, gate_bothsides = true ) -ψ, wts, = time_evolve(alg) -env = converge_env(InfinitePartitionFunction(ψ), 16) -result_2β = measure_mag(ψ, env) +pepo, wts, = time_evolve(alg) +env = converge_env(InfinitePartitionFunction(pepo), 16) +result_2β = measure_mag(pepo, env) @info "Magnetization at T = $(1 / (2β))" result_2β @test isapprox(abs.(result_2β), bm_2β, rtol = 1.0e-4) # Purification approach: results at 2β, or T = 1.25 alg = SimpleUpdate(; - ψ0, env0 = wts0, H, dt, nstep = 2 * nstep, + ψ0 = pepo0, env0 = wts0, H = ham, dt, nstep = 2 * nstep, trunc = trunc_pepo, gate_bothsides = false ) -ψ, = time_evolve(alg) -env = converge_env(InfinitePEPS(ψ), 8) -result_2β′ = measure_mag(ψ, env; purified = true) +pepo, = time_evolve(alg) +env = converge_env(InfinitePEPS(pepo), 8) +result_2β′ = measure_mag(pepo, env; purified = true) @info "Magnetization at T = $(1 / (2β)) (purification approach)" result_2β′ @test isapprox(abs.(result_2β′), bm_2β, rtol = 1.0e-2) diff --git a/test/timeevol/cluster_projectors.jl b/test/timeevol/cluster_projectors.jl index 9a091cca7..501e63344 100644 --- a/test/timeevol/cluster_projectors.jl +++ b/test/timeevol/cluster_projectors.jl @@ -109,7 +109,7 @@ end trunc_env = truncerror(; atol = 1.0e-12) & truncrank(16) peps = InfinitePEPS(rand, Float64, Pspace, Vspace; unitcell = (Nr, Nc)) wts = SUWeight(peps) - H = real( + ham = real( hubbard_model( ComplexF64, Trivial, U1Irrep, InfiniteSquare(Nr, Nc); t = 1.0, U = 8.0, mu = 0.0 ), @@ -121,17 +121,15 @@ end for (n, (dt, tol)) in enumerate(zip(dts, tols)) trunc = truncerror(; atol = 1.0e-10) & truncrank(n == 1 ? 4 : 2) alg = SimpleUpdate(; - ψ0 = peps, env0 = wts, H, dt, nstep, tol, trunc, + ψ0 = peps, env0 = wts, H = ham, dt, nstep, tol, trunc, bipartite = true, check_interval = 1000 ) peps, wts, = time_evolve(alg) end normalize!.(peps.A, Inf) - env, = leading_boundary( - CTMRGEnv(wts, peps), peps; - tol = ctmrg_tol, trunc = trunc_env - ) - e_site = cost_function(peps, env, H) / (Nr * Nc) + env = CTMRGEnv(wts, peps) + env, = leading_boundary(env, peps; tol = ctmrg_tol, trunc = trunc_env) + e_site = cost_function(peps, env, ham) / (Nr * Nc) @info "2-site simple update energy = $e_site" # continue with 3-site simple update; energy should not change much dts = [1.0e-2, 5.0e-3] @@ -139,14 +137,14 @@ end trunc = truncerror(; atol = 1.0e-10) & truncrank(2) for (n, (dt, tol)) in enumerate(zip(dts, tols)) alg = SimpleUpdate(; - ψ0 = peps, env0 = wts, H, dt, nstep, tol, trunc, + ψ0 = peps, env0 = wts, H = ham, dt, nstep, tol, trunc, check_interval = 1000, force_3site = true ) peps, wts, = time_evolve(alg) end normalize!.(peps.A, Inf) env, = leading_boundary(env, peps; tol = ctmrg_tol, trunc = trunc_env) - e_site2 = cost_function(peps, env, H) / (Nr * Nc) + e_site2 = cost_function(peps, env, ham) / (Nr * Nc) @info "3-site simple update energy = $e_site2" @test e_site ≈ e_site2 atol = 5.0e-4 end diff --git a/test/timeevol/sitedep_truncation.jl b/test/timeevol/sitedep_truncation.jl index 3f7a48ad0..b872b0a03 100644 --- a/test/timeevol/sitedep_truncation.jl +++ b/test/timeevol/sitedep_truncation.jl @@ -19,7 +19,7 @@ end @testset "Simple update: bipartite 2-site" begin Nr, Nc = 2, 2 - H = real(heisenberg_XYZ(InfiniteSquare(Nr, Nc); Jx = 1.0, Jy = 1.0, Jz = 1.0)) + 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)) env0 = SUWeight(peps0) @@ -27,7 +27,7 @@ end # 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(; ψ0 = peps0, env0, H, dt = 1.0e-2, nstep = 4, trunc, bipartite = true) + alg = SimpleUpdate(; ψ0 = peps0, env0, H = ham, dt = 1.0e-2, nstep = 4, trunc, bipartite = true) peps, env, = time_evolve(alg) @test get_bonddims(peps) == bonddims @test get_bonddims(env) == bonddims @@ -44,6 +44,7 @@ end @testset "Simple update: generic 2-site and 3-site" begin Nr, Nc = 3, 4 + 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)) normalize!.(peps0.A, Inf) @@ -53,14 +54,12 @@ end @show bonddims trunc = SiteDependentTruncation(collect(truncrank(d) for d in bonddims)) # 2-site SU - H = real(heisenberg_XYZ(InfiniteSquare(Nr, Nc); Jx = 1.0, Jy = 1.0, Jz = 1.0)) - alg = SimpleUpdate(; ψ0 = peps0, env0, H, dt = 1.0e-2, nstep = 4, trunc) + alg = SimpleUpdate(; ψ0 = peps0, env0, H = ham, dt = 1.0e-2, nstep = 4, trunc) peps, env, = time_evolve(alg) @test get_bonddims(peps) == bonddims @test get_bonddims(env) == bonddims # 3-site SU - H = real(j1_j2_model(InfiniteSquare(Nr, Nc); J1 = 1.0, J2 = 0.5, sublattice = false)) - alg = SimpleUpdate(; ψ0 = peps0, env0, H, dt = 1.0e-2, nstep = 4, trunc) + alg = SimpleUpdate(; ψ0 = peps0, env0, H = ham, dt = 1.0e-2, nstep = 4, trunc, force_3site = true) peps, env, = time_evolve(alg) @test get_bonddims(peps) == bonddims @test get_bonddims(env) == bonddims From 08c7dc8c17c9c2f091f9b7d6648af4cc809a4708 Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Thu, 6 Nov 2025 10:14:11 +0800 Subject: [PATCH 29/62] Separate SU alg, state, iterator --- examples/heisenberg_su/main.jl | 8 +- src/PEPSKit.jl | 2 +- src/algorithms/time_evolution/evoltools.jl | 2 +- src/algorithms/time_evolution/simpleupdate.jl | 195 +++++++++++------- .../time_evolution/simpleupdate3site.jl | 9 +- src/algorithms/time_evolution/time_evolve.jl | 45 +++- test/bondenv/benv_fu.jl | 4 +- test/examples/j1j2_finiteT.jl | 20 +- 8 files changed, 179 insertions(+), 106 deletions(-) diff --git a/examples/heisenberg_su/main.jl b/examples/heisenberg_su/main.jl index b8cb0eb23..1fcee9e45 100644 --- a/examples/heisenberg_su/main.jl +++ b/examples/heisenberg_su/main.jl @@ -72,13 +72,9 @@ dts = [1.0e-2, 1.0e-3, 4.0e-4] tols = [1.0e-6, 1.0e-8, 1.0e-8] nstep = 10000 trunc_peps = truncerror(; atol = 1.0e-10) & truncrank(Dbond) - +alg = SimpleUpdate(; trunc = trunc_peps, bipartite = true, check_interval = 500) for (dt, tol) in zip(dts, tols) - alg = SimpleUpdate(; - ψ0 = peps, env0 = wts, H, dt, tol, nstep, - trunc = trunc_peps, bipartite = true, check_interval = 500 - ) - global peps, wts, = time_evolve(alg) + global peps, wts, = time_evolve(peps, H, dt, nstep, alg, wts; tol) end md""" diff --git a/src/PEPSKit.jl b/src/PEPSKit.jl index eaa08ef5f..2fb5131be 100644 --- a/src/PEPSKit.jl +++ b/src/PEPSKit.jl @@ -102,7 +102,7 @@ export fixedpoint export absorb_weight export ALSTruncation, FullEnvTruncation export SimpleUpdate -export time_evolve +export TimeEvolver, timestep, time_evolve export InfiniteSquareNetwork export InfinitePartitionFunction diff --git a/src/algorithms/time_evolution/evoltools.jl b/src/algorithms/time_evolution/evoltools.jl index d00555a09..d1f66bb53 100644 --- a/src/algorithms/time_evolution/evoltools.jl +++ b/src/algorithms/time_evolution/evoltools.jl @@ -1,6 +1,6 @@ const InfiniteState = Union{InfinitePEPS, InfinitePEPO} -function _process_timeevol_args( +function _get_dt( state::InfiniteState, dt::Float64, imaginary_time::Bool ) dt′ = (state isa InfinitePEPS) ? dt : (dt / 2) diff --git a/src/algorithms/time_evolution/simpleupdate.jl b/src/algorithms/time_evolution/simpleupdate.jl index be56d442f..51487de15 100644 --- a/src/algorithms/time_evolution/simpleupdate.jl +++ b/src/algorithms/time_evolution/simpleupdate.jl @@ -8,16 +8,6 @@ Algorithm struct for simple update (SU) of InfinitePEPS or InfinitePEPO. $(TYPEDFIELDS) """ @kwdef struct SimpleUpdate <: TimeEvolution - # Initial state (InfinitePEPS or InfinitePEPO) - ψ0::InfiniteState - # Hamiltonian - H::LocalOperator - # Initial Bond weights - env0::SUWeight - # Trotter time step - dt::Float64 - # number of iteration steps - nstep::Int # Truncation scheme after applying Trotter gates trunc::TruncationStrategy # Switch for imaginary or real time @@ -29,12 +19,64 @@ $(TYPEDFIELDS) # ---- only applicable to ground state search ---- # assume bipartite unit cell structure bipartite::Bool = false - # converged change of weight between two steps - tol::Float64 = 0.0 # ---- only applicable to PEPO evolution ---- gate_bothsides::Bool = false # when false, purified approach is assumed end +# internal state of simple update algorithm +struct SUState{S} + # number of performed iterations + iter::Int + # evolved time + t::Float64 + # measure of difference (of SUWeight, energy, etc.) from last iteration + diff::Float64 + # PEPS/PEPO + ψ::S + # SUWeight environment + env::SUWeight +end + +""" + TimeEvolver( + ψ₀::InfiniteState, H::LocalOperator, dt::Float64, nstep::Int, + alg::SimpleUpdate, env₀::SUWeight; + tol::Float64 = 0.0, t₀::Float64 = 0.0 + ) + +Initialize a TimeEvolver with Hamiltonian `H` and simple update `alg`, +starting from the initial state `ψ₀` and SUWeight environment `env₀`. +The initial time is specified by `t₀`. +Convergence check is enabled by setting `tol > 0`; otherwise it is disabled. +""" +function TimeEvolver( + ψ₀::InfiniteState, H::LocalOperator, dt::Float64, nstep::Int, + alg::SimpleUpdate, env₀::SUWeight; + tol::Float64 = 0.0, t₀::Float64 = 0.0 + ) + # sanity checks + _timeevol_sanity_check(ψ₀, H, tol, alg) + # create Trotter gates + nnonly = is_nearest_neighbour(H) + use_3site = alg.force_3site || !nnonly + if alg.bipartite + @assert !use_3site "3-site simple update is incompatible with bipartite lattice." + end + dt′ = _get_dt(ψ₀, dt, alg.imaginary_time) + gate = if use_3site + [ + _get_gatempos_se(H, dt′), + _get_gatempos_se(rotl90(H), dt′), + _get_gatempos_se(rot180(H), dt′), + _get_gatempos_se(rotr90(H), dt′), + ] + else + get_expham(H, dt′) + end + state = SUState(0, t₀, NaN, ψ₀, env₀) + return TimeEvolver(alg, dt, nstep, H, gate, tol, state) +end + #= Simple update of the x-bond between `[r,c]` and `[r,c+1]`. @@ -48,8 +90,7 @@ function _su_xbond!( state::InfiniteState, gate::AbstractTensorMap{T, S, 2, 2}, env::SUWeight, row::Int, col::Int, trunc::TruncationStrategy; gate_ax::Int = 1 ) where {T <: Number, S <: ElementarySpace} - Nr, Nc = size(state)[1:2] - @assert 1 <= row <= Nr && 1 <= col <= Nc + Nr, Nc, = size(state) cp1 = _next(col, Nc) # absorb environment weights A, B = state.A[row, col], state.A[row, cp1] @@ -87,8 +128,7 @@ function _su_ybond!( state::InfiniteState, gate::AbstractTensorMap{T, S, 2, 2}, env::SUWeight, row::Int, col::Int, trunc::TruncationStrategy; gate_ax::Int = 1 ) where {T <: Number, S <: ElementarySpace} - Nr, Nc = size(state)[1:2] - @assert 1 <= row <= Nr && 1 <= col <= Nc + Nr, Nc, = size(state) rm1 = _prev(row, Nr) # absorb environment weights A, B = state.A[row, col], state.A[rm1, col] @@ -115,19 +155,15 @@ end function su_iter( state::InfiniteState, gate::LocalOperator, alg::SimpleUpdate, env::SUWeight ) - @assert size(gate.lattice) == size(state)[1:2] - Nr, Nc = size(state)[1:2] - alg.bipartite && (@assert Nr == Nc == 2) - (Nr >= 2 && Nc >= 2) || throw( - ArgumentError("`state` unit cell size for simple update should be no smaller than (2, 2)."), - ) - state2, env2 = deepcopy(state), deepcopy(env) + Nr, Nc, = size(state) + state2, env2, ϵ = deepcopy(state), deepcopy(env), 0.0 gate_axs = alg.gate_bothsides ? (1:2) : (1:1) for r in 1:Nr, c in 1:Nc term = get_gateterm(gate, (CartesianIndex(r, c), CartesianIndex(r, c + 1))) trunc = truncation_strategy(alg.trunc, 1, r, c) for gate_ax in gate_axs - _su_xbond!(state2, term, env2, r, c, trunc; gate_ax) + ϵ′ = _su_xbond!(state2, term, env2, r, c, trunc; gate_ax) + ϵ = maximum(ϵ, ϵ′) end if alg.bipartite rp1, cp1 = _next(r, Nr), _next(c, Nc) @@ -138,7 +174,8 @@ function su_iter( term = get_gateterm(gate, (CartesianIndex(r, c), CartesianIndex(r - 1, c))) trunc = truncation_strategy(alg.trunc, 2, r, c) for gate_ax in gate_axs - _su_ybond!(state2, term, env2, r, c, trunc; gate_ax) + ϵ′ = _su_ybond!(state2, term, env2, r, c, trunc; gate_ax) + ϵ = maximum(ϵ, ϵ′) end if alg.bipartite rm1, cm1 = _prev(r, Nr), _prev(c, Nc) @@ -147,61 +184,75 @@ function su_iter( env2.data[2, rm1, cm1] = deepcopy(env2.data[2, r, c]) end end - wtdiff = compare_weights(env2, env) - return state2, env2, (; wtdiff) + return state2, env2, ϵ end -# Initial iteration -function Base.iterate(alg::SimpleUpdate) - # sanity checks - nnonly = is_nearest_neighbour(alg.H) - use_3site = alg.force_3site || !nnonly - @assert alg.tol >= 0 - @assert !(alg.bipartite && alg.ψ0 isa InfinitePEPO) "Evolution of PEPO with bipartite structure is not implemented." - @assert !(alg.bipartite && use_3site) "3-site simple update is incompatible with bipartite lattice." - if alg.ψ0 isa InfinitePEPS - @assert !(alg.gate_bothsides) "alg.gate_bothsides = true is incompatible with PEPS evolution." - end - # construct Trotter 2-site gates or 3-site gate-MPOs - dt′ = _process_timeevol_args(alg.ψ0, alg.dt, alg.imaginary_time) - gate = if use_3site - [ - _get_gatempos_se(alg.H, dt′), - _get_gatempos_se(rotl90(alg.H), dt′), - _get_gatempos_se(rot180(alg.H), dt′), - _get_gatempos_se(rotr90(alg.H), dt′), - ] +function Base.iterate(it::TimeEvolver{<:SimpleUpdate}, state = it.state) + alg = it.alg + iter, t, diff = state.iter, state.t, state.diff + check_convergence = (it.tol > 0) + if check_convergence + if diff < it.tol + @info "SU: bond weights have converged." + return nothing + elseif iter == it.nstep + @warn "SU: bond weights have not converged." + return nothing + end else - get_expham(alg.H, dt′) + (iter == it.nstep) && return nothing end + # perform one iteration and record time time0 = time() - ψ, env, info = su_iter(alg.ψ0, gate, alg, alg.env0) + ψ, env, ϵ = su_iter(state.ψ, it.gate, it.alg, state.env) + diff = compare_weights(env, state.env) elapsed_time = time() - time0 - converged = false - return (ψ, env, info), (ψ, env, info, gate, converged, 1, elapsed_time) -end - -# subsequent iterations -function Base.iterate(alg::SimpleUpdate, state) - ψ0, env0, info, gate, converged, it, elapsed_time = state - check_convergence = (alg.tol > 0) - if (it % alg.check_interval == 0) || (it == 1) || (it == alg.nstep) || converged - @info "Space of x-weight at [1, 1] = $(space(env0[1, 1, 1], 1))" + # update internal state + iter += 1 + t += it.dt + it.state = SUState(iter, t, diff, ψ, env) + # output information + converged = check_convergence ? (diff < it.tol) : false + showinfo = (iter % alg.check_interval == 0) || (iter == 1) || (iter == it.nstep) || converged + if showinfo + @info "Space of x-weight at [1, 1] = $(space(env[1, 1, 1], 1))" @info @sprintf( "SU iter %-7d: dt = %.0e, |Δλ| = %.3e. Time = %.3f s/it", - it, alg.dt, info.wtdiff, elapsed_time + iter, it.dt, diff, elapsed_time ) - if (it == alg.nstep) - check_convergence && (@warn "SU: bond weights have not converged.") - return nothing - elseif converged - @info "SU: bond weights have converged." - return nothing - end end - time0 = time() - ψ, env, info = su_iter(ψ0, gate, alg, env0) - elapsed_time = time() - time0 - converged = check_convergence ? (info.wtdiff < alg.tol) : false - return (ψ, env, info), (ψ, env, info, gate, converged, it + 1, elapsed_time) + info = (; t, ϵ) + return (ψ, env, info), it.state +end + +# evolve one step from given state +function MPSKit.timestep( + it::TimeEvolver{<:SimpleUpdate}, ψ::InfiniteState, env::SUWeight; + iter::Int = it.state.iter, t::Float64 = it.state.t + ) + _timeevol_sanity_check(ψ, it.H, it.tol, it.alg) + state = SUState(iter, t, NaN, ψ, env) + return first(iterate(it, state)) +end + +# evolve till the end +function MPSKit.time_evolve(it::TimeEvolver{<:SimpleUpdate}) + time_start = time() + result = nothing + for state in it + result = state + end + time_end = time() + @info @sprintf("Simple update finished. Total time elasped: %.2f s", time_end - time_start) + return result +end + +# ordinary mode +function MPSKit.time_evolve( + ψ₀::InfiniteState, H::LocalOperator, dt::Float64, nstep::Int, + alg::SimpleUpdate, env₀::SUWeight; + tol::Float64 = 0.0, t₀::Float64 = 0.0 + ) + it = TimeEvolver(ψ₀, H, dt, nstep, alg, env₀; tol, t₀) + return time_evolve(it) end diff --git a/src/algorithms/time_evolution/simpleupdate3site.jl b/src/algorithms/time_evolution/simpleupdate3site.jl index 7dcbdd08d..c75663b9f 100644 --- a/src/algorithms/time_evolution/simpleupdate3site.jl +++ b/src/algorithms/time_evolution/simpleupdate3site.jl @@ -500,7 +500,7 @@ function _su3site_se!( M = absorb_weight(M, env, coord[1], coord[2], openaxs; inv = true) state.A[CartesianIndex(coord)] = normalize(M, Inf) end - return ϵs + return maximum(ϵs) end function su_iter( @@ -515,7 +515,7 @@ function su_iter( "iPEPS unit cell size for simple update should be no smaller than (2, 2)." ), ) - state2, env2 = deepcopy(state), deepcopy(env) + state2, env2, ϵ = deepcopy(state), deepcopy(env), 0.0 trunc = alg.trunc for i in 1:4 Nr, Nc = size(state2)[1:2] @@ -525,11 +525,10 @@ function su_iter( truncation_strategy(trunc, 1, r, c) truncation_strategy(trunc, 2, r, _next(c, Nc)) ] - _su3site_se!(state2, gs, env2, r, c, truncs; alg.gate_bothsides) + ϵ = _su3site_se!(state2, gs, env2, r, c, truncs; alg.gate_bothsides) end state2, env2 = rotl90(state2), rotl90(env2) trunc = rotl90(trunc) end - wtdiff = compare_weights(env2, env) - return state2, env2, (; wtdiff) + return state2, env2, ϵ end diff --git a/src/algorithms/time_evolution/time_evolve.jl b/src/algorithms/time_evolution/time_evolve.jl index 6ef533399..e5bd0ed9e 100644 --- a/src/algorithms/time_evolution/time_evolve.jl +++ b/src/algorithms/time_evolution/time_evolve.jl @@ -1,12 +1,41 @@ abstract type TimeEvolution end -function MPSKit.time_evolve(alg::Alg) where {Alg <: TimeEvolution} - time_start = time() - result = nothing - for state in alg - result = state +mutable struct TimeEvolver{TE <: TimeEvolution, G, S} + # Time evolution algorithm + alg::TE + # Trotter time step + dt::Float64 + # Maximal iteration steps + nstep::Int + # Hamiltonian + H::LocalOperator + # Trotter gates + gate::G + # Convergence tolerance (change of weight or energy from last iteration) + tol::Float64 + # PEPS/PEPO (and environment) + state::S +end + +Base.iterate(it::TimeEvolver) = iterate(it, it.state) + +function _timeevol_sanity_check( + ψ₀::InfiniteState, H::LocalOperator, tol::Float64, alg::A + ) where {A <: TimeEvolution} + Nr, Nc, = size(ψ₀) + @assert (Nr >= 2 && Nc >= 2) "Unit cell size for simple update should be no smaller than (2, 2)." + @assert physicalspace(H) == physicalspace(ψ₀) "Physical space mismatch between `ψ₀` and `H`." + @assert tol >= 0 + if tol > 0 + @assert alg.imaginary_time "`tol` should be 0 for real time evolution." + @assert ψ₀ isa InfinitePEPS "`tol` should be 0 for time evolution of InfinitePEPO." + end + if hasfield(typeof(alg), :gate_bothsides) && alg.gate_bothsides + @assert ψ₀ isa InfinitePEPO "alg.gate_bothsides = true is only compatible with PEPO." + end + if hasfield(typeof(alg), :bipartite) && alg.bipartite + @assert Nr == Nc == 2 "`bipartite = true` requires 2 x 2 unit cell size." + @assert ψ₀ isa InfinitePEPS "Evolution of PEPO with bipartite structure is not implemented." end - time_end = time() - @info @sprintf("Time evolution finished. Total time elasped: %.2f s", time_end - time_start) - return result + return nothing end diff --git a/test/bondenv/benv_fu.jl b/test/bondenv/benv_fu.jl index 181616b1f..8d74f96a1 100644 --- a/test/bondenv/benv_fu.jl +++ b/test/bondenv/benv_fu.jl @@ -14,10 +14,10 @@ function get_hubbard_state(t::Float64 = 1.0, U::Float64 = 8.0) peps = InfinitePEPS(rand, ComplexF64, Vphy, Vphy; unitcell = (Nr, Nc)) wts = SUWeight(peps) alg = SimpleUpdate(; - ψ0 = peps, env0 = wts, H, dt = 1.0e-2, nstep = 10000, tol = 1.0e-8, trunc = truncerror(; atol = 1.0e-10) & truncrank(4), check_interval = 2000 ) - peps, = time_evolve(alg) + evolver = TimeEvolver(peps, H, 1.0e-2, 10000, alg, wts; tol = 1.0e-8) + peps, = time_evolve(evolver) normalize!.(peps.A, Inf) return peps end diff --git a/test/examples/j1j2_finiteT.jl b/test/examples/j1j2_finiteT.jl index 9a3828a8c..8ffa38369 100644 --- a/test/examples/j1j2_finiteT.jl +++ b/test/examples/j1j2_finiteT.jl @@ -32,22 +32,19 @@ check_interval = 100 # PEPO approach dt, nstep = 1.0e-3, 600 -alg = SimpleUpdate(; - ψ0 = pepo0, env0 = wts0, H = ham, dt, nstep, trunc = trunc_pepo, - gate_bothsides = true, check_interval -) -pepo, wts, = time_evolve(alg) +alg = SimpleUpdate(; trunc = trunc_pepo, gate_bothsides = true, check_interval) +evolver = TimeEvolver(pepo0, ham, dt, nstep, alg, wts0) +pepo, wts, info = time_evolve(evolver) 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(; - ψ0 = pepo0, env0 = wts0, H = ham, dt, nstep, trunc = trunc_pepo, - gate_bothsides = false, check_interval -) -pepo, wts, = time_evolve(alg) +alg = SimpleUpdate(; trunc = trunc_pepo, gate_bothsides = false, check_interval) +evolver = TimeEvolver(pepo0, ham, dt, nstep, alg, wts0) +pepo, wts, = time_evolve(evolver) env = converge_env(InfinitePartitionFunction(pepo), 16) energy = expectation_value(pepo, ham, env) / (Nr * Nc) @info "β = $(dt * nstep) / 2: tr(ρH) = $(energy)" @@ -55,5 +52,6 @@ energy = expectation_value(pepo, ham, env) / (Nr * Nc) env = converge_env(InfinitePEPS(pepo), 16) energy = expectation_value(pepo, ham, pepo, env) / (Nr * Nc) -@info "β = $(dt * nstep): ⟨ρ|ham|ρ⟩ = $(energy)" +@info "β = $(dt * nstep): ⟨ρ|H|ρ⟩ = $(energy)" +@test dt * nstep ≈ info.t @test energy ≈ bm[2] atol = 5.0e-3 From d8c2653908c9b465987234045e4c8661db2d9966 Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Thu, 6 Nov 2025 11:10:43 +0800 Subject: [PATCH 30/62] Remove `H` from TimeEvolver --- src/algorithms/time_evolution/simpleupdate.jl | 11 ++++++----- src/algorithms/time_evolution/time_evolve.jl | 8 +++----- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/algorithms/time_evolution/simpleupdate.jl b/src/algorithms/time_evolution/simpleupdate.jl index 51487de15..5cebb080a 100644 --- a/src/algorithms/time_evolution/simpleupdate.jl +++ b/src/algorithms/time_evolution/simpleupdate.jl @@ -55,7 +55,7 @@ function TimeEvolver( tol::Float64 = 0.0, t₀::Float64 = 0.0 ) # sanity checks - _timeevol_sanity_check(ψ₀, H, tol, alg) + _timeevol_sanity_check(ψ₀, physicalspace(H), tol, alg) # create Trotter gates nnonly = is_nearest_neighbour(H) use_3site = alg.force_3site || !nnonly @@ -74,7 +74,7 @@ function TimeEvolver( get_expham(H, dt′) end state = SUState(0, t₀, NaN, ψ₀, env₀) - return TimeEvolver(alg, dt, nstep, H, gate, tol, state) + return TimeEvolver(alg, dt, nstep, gate, tol, state) end #= @@ -163,7 +163,7 @@ function su_iter( trunc = truncation_strategy(alg.trunc, 1, r, c) for gate_ax in gate_axs ϵ′ = _su_xbond!(state2, term, env2, r, c, trunc; gate_ax) - ϵ = maximum(ϵ, ϵ′) + ϵ = max(ϵ, ϵ′) end if alg.bipartite rp1, cp1 = _next(r, Nr), _next(c, Nc) @@ -175,7 +175,7 @@ function su_iter( trunc = truncation_strategy(alg.trunc, 2, r, c) for gate_ax in gate_axs ϵ′ = _su_ybond!(state2, term, env2, r, c, trunc; gate_ax) - ϵ = maximum(ϵ, ϵ′) + ϵ = max(ϵ, ϵ′) end if alg.bipartite rm1, cm1 = _prev(r, Nr), _prev(c, Nc) @@ -230,7 +230,8 @@ function MPSKit.timestep( it::TimeEvolver{<:SimpleUpdate}, ψ::InfiniteState, env::SUWeight; iter::Int = it.state.iter, t::Float64 = it.state.t ) - _timeevol_sanity_check(ψ, it.H, it.tol, it.alg) + Pspaces = physicalspace(it.state.ψ) + _timeevol_sanity_check(ψ, Pspaces, it.tol, it.alg) state = SUState(iter, t, NaN, ψ, env) return first(iterate(it, state)) end diff --git a/src/algorithms/time_evolution/time_evolve.jl b/src/algorithms/time_evolution/time_evolve.jl index e5bd0ed9e..af51a714f 100644 --- a/src/algorithms/time_evolution/time_evolve.jl +++ b/src/algorithms/time_evolution/time_evolve.jl @@ -7,8 +7,6 @@ mutable struct TimeEvolver{TE <: TimeEvolution, G, S} dt::Float64 # Maximal iteration steps nstep::Int - # Hamiltonian - H::LocalOperator # Trotter gates gate::G # Convergence tolerance (change of weight or energy from last iteration) @@ -20,11 +18,11 @@ end Base.iterate(it::TimeEvolver) = iterate(it, it.state) function _timeevol_sanity_check( - ψ₀::InfiniteState, H::LocalOperator, tol::Float64, alg::A - ) where {A <: TimeEvolution} + ψ₀::InfiniteState, Pspaces::M, tol::Float64, alg::A + ) where {A <: TimeEvolution, M <: AbstractMatrix{<:ElementarySpace}} Nr, Nc, = size(ψ₀) @assert (Nr >= 2 && Nc >= 2) "Unit cell size for simple update should be no smaller than (2, 2)." - @assert physicalspace(H) == physicalspace(ψ₀) "Physical space mismatch between `ψ₀` and `H`." + @assert Pspaces == physicalspace(ψ₀) "Physical spaces of `ψ₀` do not match `Pspaces`." @assert tol >= 0 if tol > 0 @assert alg.imaginary_time "`tol` should be 0 for real time evolution." From f589d9d4fb47992db55534b19d704ce9b53c09f5 Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Thu, 6 Nov 2025 11:44:28 +0800 Subject: [PATCH 31/62] Add test for `timestep` --- src/algorithms/time_evolution/simpleupdate.jl | 8 ++++- test/runtests.jl | 3 ++ test/timeevol/timestep.jl | 33 +++++++++++++++++++ 3 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 test/timeevol/timestep.jl diff --git a/src/algorithms/time_evolution/simpleupdate.jl b/src/algorithms/time_evolution/simpleupdate.jl index 5cebb080a..cd6010894 100644 --- a/src/algorithms/time_evolution/simpleupdate.jl +++ b/src/algorithms/time_evolution/simpleupdate.jl @@ -233,7 +233,13 @@ function MPSKit.timestep( Pspaces = physicalspace(it.state.ψ) _timeevol_sanity_check(ψ, Pspaces, it.tol, it.alg) state = SUState(iter, t, NaN, ψ, env) - return first(iterate(it, state)) + result = iterate(it, state) + if result === nothing + @warn "TimeEvolver `it` has already reached the end." + return ψ, env, (; t, ϵ = NaN) + else + return first(result) + end end # evolve till the end diff --git a/test/runtests.jl b/test/runtests.jl index f18007942..38d4fdd4f 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -61,6 +61,9 @@ end 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 diff --git a/test/timeevol/timestep.jl b/test/timeevol/timestep.jl new file mode 100644 index 000000000..ede88db61 --- /dev/null +++ b/test/timeevol/timestep.jl @@ -0,0 +1,33 @@ +using Test +using Random +using TensorKit +using PEPSKit + +function test_timestep(ψ0, env0, H) + trunc = truncerror(; atol = 1.0e-10) & truncrank(4) + alg = SimpleUpdate(; trunc) + dt, nstep = 1.0e-2, 50 + # manual timestep + evolver = TimeEvolver(ψ0, H, dt, nstep, alg, env0) + ψ1, env1, info1 = deepcopy(ψ0), deepcopy(env0), nothing + for iter in 0:(nstep - 1) + ψ1, env1, info1 = timestep(evolver, ψ1, env1; iter) + end + @info info1 + # time_evolve + ψ2, env2, info2 = time_evolve(ψ0, H, dt, nstep, alg, env0) + @info info2 + # results should be *exactly* the same + @test ψ1 == ψ2 + @test env1 == env2 + @test info1 == info2 + return nothing +end + +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)) +env0 = SUWeight(ψ0) + +test_timestep(ψ0, env0, H) From 2b8fa1fdd26d028849deefbad77de94cd9f63eba Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Thu, 6 Nov 2025 12:04:18 +0800 Subject: [PATCH 32/62] Add docstrings --- src/algorithms/time_evolution/simpleupdate.jl | 44 ++++++++++++++++--- src/algorithms/time_evolution/time_evolve.jl | 14 ++++++ 2 files changed, 53 insertions(+), 5 deletions(-) diff --git a/src/algorithms/time_evolution/simpleupdate.jl b/src/algorithms/time_evolution/simpleupdate.jl index cd6010894..8877ce855 100644 --- a/src/algorithms/time_evolution/simpleupdate.jl +++ b/src/algorithms/time_evolution/simpleupdate.jl @@ -46,8 +46,10 @@ end Initialize a TimeEvolver with Hamiltonian `H` and simple update `alg`, starting from the initial state `ψ₀` and SUWeight environment `env₀`. -The initial time is specified by `t₀`. -Convergence check is enabled by setting `tol > 0`; otherwise it is disabled. + +- The initial (real or imaginary) time is specified by `t₀`. +- Setting `tol > 0` enables convergence check (for imaginary time evolution of InfinitePEPS only). + For other usages it should not be changed. """ function TimeEvolver( ψ₀::InfiniteState, H::LocalOperator, dt::Float64, nstep::Int, @@ -225,7 +227,18 @@ function Base.iterate(it::TimeEvolver{<:SimpleUpdate}, state = it.state) return (ψ, env, info), it.state end -# evolve one step from given state +""" + timestep( + it::TimeEvolver{<:SimpleUpdate}, ψ::InfiniteState, env::SUWeight; + iter::Int = it.state.iter, t::Float64 = it.state.t + ) -> (ψ, env, info) + +Given the TimeEvolver iterator `it`, perform one step of time evolution +on the input state `ψ` 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}, ψ::InfiniteState, env::SUWeight; iter::Int = it.state.iter, t::Float64 = it.state.t @@ -242,7 +255,12 @@ function MPSKit.timestep( end end -# evolve till the end +""" + time_evolve(it::TimeEvolver{<:SimpleUpdate}) + +Perform time evolution until the set number of iterations or convergence +directly using the specified TimeEvolver iterator. +""" function MPSKit.time_evolve(it::TimeEvolver{<:SimpleUpdate}) time_start = time() result = nothing @@ -254,7 +272,23 @@ function MPSKit.time_evolve(it::TimeEvolver{<:SimpleUpdate}) return result end -# ordinary mode +""" + time_evolve( + ψ₀::Union{InfinitePEPS, InfinitePEPO}, H::LocalOperator, + dt::Float64, nstep::Int, alg::SimpleUpdate, env₀::SUWeight; + tol::Float64 = 0.0, t₀::Float64 = 0.0 + ) -> (ψ, env, info) + +Perform time evolution on the initial state `ψ₀` and initial environment `env₀` +with Hamiltonian `H`, using SimpleUpdate algorithm `alg`, time step `dt` for +`nstep` number of steps. + +- Setting `tol > 0` enables convergence check (for imaginary time evolution of InfinitePEPS only). + For other usages it should not be changed. +- Using `t₀` to specify the initial (real or imaginary) time of `ψ₀`. +- `info` is a NamedTuple containing information of the evolution, + including the time evolved since `ψ₀`. +""" function MPSKit.time_evolve( ψ₀::InfiniteState, H::LocalOperator, dt::Float64, nstep::Int, alg::SimpleUpdate, env₀::SUWeight; diff --git a/src/algorithms/time_evolution/time_evolve.jl b/src/algorithms/time_evolution/time_evolve.jl index af51a714f..cb4ae3ce7 100644 --- a/src/algorithms/time_evolution/time_evolve.jl +++ b/src/algorithms/time_evolution/time_evolve.jl @@ -1,5 +1,19 @@ +""" +$(TYPEDEF) + +Abstract super type for time evolution algorithms of InfinitePEPS or InfinitePEPO. +""" abstract type TimeEvolution end +""" + mutable struct TimeEvolver{TE <: TimeEvolution, G, S} + +Iterator for Trotter-based time evolution of InfinitePEPS or InfinitePEPO. + +## Fields + +$(TYPEDFIELDS) +""" mutable struct TimeEvolver{TE <: TimeEvolution, G, S} # Time evolution algorithm alg::TE From 0844b6081bb98ecf393ad881c8186bcb4938ded9 Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Thu, 6 Nov 2025 12:40:48 +0800 Subject: [PATCH 33/62] Update SU examples --- examples/hubbard_su/main.jl | 16 +++++++--------- examples/j1j2_su/main.jl | 14 +++++--------- src/algorithms/time_evolution/simpleupdate.jl | 4 ++-- 3 files changed, 14 insertions(+), 20 deletions(-) diff --git a/examples/hubbard_su/main.jl b/examples/hubbard_su/main.jl index 68c94cc2f..bfb5350a7 100644 --- a/examples/hubbard_su/main.jl +++ b/examples/hubbard_su/main.jl @@ -66,26 +66,24 @@ maxiter = 20000 for (dt, tol, Dbond) in zip(dts, tols, Ds) trunc = truncerror(; atol = 1.0e-10) & truncrank(Dbond) - alg = SimpleUpdate(dt, tol, maxiter, trunc) - global peps, wts, = simpleupdate( - peps, H, alg, wts; bipartite = false, check_interval = 2000 - ) + alg = SimpleUpdate(; trunc, bipartite = false, check_interval = 2000) + global peps, wts, = time_evolve(peps, H, dt, maxiter, alg, wts; tol) end md""" ## Computing the ground-state energy In order to compute the energy expectation value with evolved PEPS, we need to converge a -CTMRG environment on it. We first converge an environment with a small enviroment dimension -and then use that to initialize another run with bigger environment dimension. We'll use -`trunc=truncrank(χ)` for that such that the dimension is increased during the second CTMRG -run: +CTMRG environment on it. We first converge an environment with a small enviroment dimension, +which is initialized using the simple update bond weights. Next we use it to initialize +another run with bigger environment dimension. The dynamic adjustion of environment dimension +is achieved by using `trunc=truncrank(χ)` in the CTMRG runs: """ χenv₀, χenv = 6, 16 env_space = Vect[fℤ₂](0 => χenv₀ / 2, 1 => χenv₀ / 2) normalize!.(peps.A, Inf) -env = CTMRGEnv(rand, Float64, peps, env_space) +env = CTMRGEnv(wts, peps) for χ in [χenv₀, χenv] global env, = leading_boundary( env, peps; alg = :sequential, tol = 1.0e-8, maxiter = 50, trunc = truncrank(χ) diff --git a/examples/j1j2_su/main.jl b/examples/j1j2_su/main.jl index d50e256e5..6b3ec5753 100644 --- a/examples/j1j2_su/main.jl +++ b/examples/j1j2_su/main.jl @@ -51,14 +51,13 @@ on the previously evolved PEPS: dt, tol, nstep = 1.0e-2, 1.0e-8, 30000 check_interval = 4000 trunc_peps = truncerror(; atol = 1.0e-10) & truncrank(Dbond) +alg = SimpleUpdate(; trunc = trunc_peps, check_interval) for J2 in 0.1:0.1:0.5 - H = real( ## convert Hamiltonian `LocalOperator` to real floats + ## convert Hamiltonian `LocalOperator` to real floats + H = real( j1_j2_model(ComplexF64, symm, InfiniteSquare(Nr, Nc); J1, J2, sublattice = false), ) - alg = SimpleUpdate(; - ψ0 = peps, env0 = wts, H, dt, tol, nstep, trunc = trunc_peps, check_interval - ) - global peps, wts, = time_evolve(alg) + global peps, wts, = time_evolve(peps, H, dt, nstep, alg, wts; tol) end md""" @@ -71,10 +70,7 @@ tols = [1.0e-9, 1.0e-9] J2 = 0.5 H = real(j1_j2_model(ComplexF64, symm, InfiniteSquare(Nr, Nc); J1, J2, sublattice = false)) for (dt, tol) in zip(dts, tols) - alg = SimpleUpdate(; - ψ0 = peps, env0 = wts, H, dt, tol, nstep, trunc = trunc_peps, check_interval - ) - global peps, wts, = time_evolve(alg) + global peps, wts, = time_evolve(peps, H, dt, nstep, alg, wts; tol) end md""" diff --git a/src/algorithms/time_evolution/simpleupdate.jl b/src/algorithms/time_evolution/simpleupdate.jl index 8877ce855..da0b352a7 100644 --- a/src/algorithms/time_evolution/simpleupdate.jl +++ b/src/algorithms/time_evolution/simpleupdate.jl @@ -258,8 +258,8 @@ end """ time_evolve(it::TimeEvolver{<:SimpleUpdate}) -Perform time evolution until the set number of iterations or convergence -directly using the specified TimeEvolver iterator. +Perform time evolution until convergence or the set number of iterations +using the specified TimeEvolver iterator `it` directly. """ function MPSKit.time_evolve(it::TimeEvolver{<:SimpleUpdate}) time_start = time() From 9e1f8252a6b5de59908bbc2af00c2665ec43fc60 Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Thu, 6 Nov 2025 14:56:22 +0800 Subject: [PATCH 34/62] Update tests --- test/examples/heisenberg.jl | 4 ++-- test/examples/tf_ising_finiteT.jl | 23 ++++++++--------------- test/timeevol/cluster_projectors.jl | 14 ++++---------- test/timeevol/sitedep_truncation.jl | 12 ++++++------ 4 files changed, 20 insertions(+), 33 deletions(-) diff --git a/test/examples/heisenberg.jl b/test/examples/heisenberg.jl index d4224af9d..df526e9f4 100644 --- a/test/examples/heisenberg.jl +++ b/test/examples/heisenberg.jl @@ -71,8 +71,8 @@ end for (n, (dt, tol)) in enumerate(zip(dts, tols)) Dbond2 = (n == 2) ? Dbond + 2 : Dbond trunc = truncerror(; atol = 1.0e-10) & truncrank(Dbond2) - alg = SimpleUpdate(; ψ0 = peps, env0 = wts, H = ham, dt, nstep, trunc, bipartite = false) - peps, wts, = time_evolve(alg) + alg = SimpleUpdate(; trunc, bipartite = false) + peps, wts, = time_evolve(peps, ham, dt, nstep, alg, wts; tol) end # measure physical quantities with CTMRG diff --git a/test/examples/tf_ising_finiteT.jl b/test/examples/tf_ising_finiteT.jl index f3217b908..625a209e8 100644 --- a/test/examples/tf_ising_finiteT.jl +++ b/test/examples/tf_ising_finiteT.jl @@ -60,34 +60,27 @@ dt, nstep = 1.0e-3, 400 # when g = 2, β = 0.4 and 2β = 0.8 belong to two phases (without and with nonzero σᶻ) # PEPO approach: results at β, or T = 2.5 -alg = SimpleUpdate(; - ψ0 = pepo0, env0 = wts0, H = ham, dt, nstep, - trunc = trunc_pepo, gate_bothsides = true -) -pepo, wts, = time_evolve(alg) +alg = SimpleUpdate(; trunc = trunc_pepo, gate_bothsides = true) +pepo, wts, info = time_evolve(pepo0, ham, dt, nstep, alg, wts0) env = converge_env(InfinitePartitionFunction(pepo), 16) result_β = measure_mag(pepo, env) @info "Magnetization at T = $(1 / β)" result_β +@test β ≈ info.t @test isapprox(abs.(result_β), bm_β, rtol = 1.0e-2) # continue to get results at 2β, or T = 1.25 -alg = SimpleUpdate(; - ψ0 = pepo, env0 = wts, H = ham, dt, nstep, - trunc = trunc_pepo, gate_bothsides = true -) -pepo, wts, = time_evolve(alg) +pepo, wts, info = time_evolve(pepo, ham, dt, nstep, alg, wts; t₀ = β) env = converge_env(InfinitePartitionFunction(pepo), 16) result_2β = measure_mag(pepo, env) @info "Magnetization at T = $(1 / (2β))" result_2β +@test 2 * β ≈ info.t @test isapprox(abs.(result_2β), bm_2β, rtol = 1.0e-4) # Purification approach: results at 2β, or T = 1.25 -alg = SimpleUpdate(; - ψ0 = pepo0, env0 = wts0, H = ham, dt, nstep = 2 * nstep, - trunc = trunc_pepo, gate_bothsides = false -) -pepo, = time_evolve(alg) +alg = SimpleUpdate(; trunc = trunc_pepo, gate_bothsides = false) +pepo, wts, info = time_evolve(pepo0, ham, dt, 2 * nstep, alg, wts0) env = converge_env(InfinitePEPS(pepo), 8) result_2β′ = measure_mag(pepo, env; purified = true) @info "Magnetization at T = $(1 / (2β)) (purification approach)" result_2β′ +@test 2 * β ≈ info.t @test isapprox(abs.(result_2β′), bm_2β, rtol = 1.0e-2) diff --git a/test/timeevol/cluster_projectors.jl b/test/timeevol/cluster_projectors.jl index 501e63344..71f4163a2 100644 --- a/test/timeevol/cluster_projectors.jl +++ b/test/timeevol/cluster_projectors.jl @@ -120,11 +120,8 @@ end nstep = 5000 for (n, (dt, tol)) in enumerate(zip(dts, tols)) trunc = truncerror(; atol = 1.0e-10) & truncrank(n == 1 ? 4 : 2) - alg = SimpleUpdate(; - ψ0 = peps, env0 = wts, H = ham, dt, nstep, tol, trunc, - bipartite = true, check_interval = 1000 - ) - peps, wts, = time_evolve(alg) + alg = SimpleUpdate(; trunc, bipartite = true, check_interval = 1000) + peps, wts, = time_evolve(peps, ham, dt, nstep, alg, wts; tol) end normalize!.(peps.A, Inf) env = CTMRGEnv(wts, peps) @@ -135,12 +132,9 @@ end dts = [1.0e-2, 5.0e-3] tols = [1.0e-8, 1.0e-8] trunc = truncerror(; atol = 1.0e-10) & truncrank(2) + alg = SimpleUpdate(; trunc, check_interval = 1000, force_3site = true) for (n, (dt, tol)) in enumerate(zip(dts, tols)) - alg = SimpleUpdate(; - ψ0 = peps, env0 = wts, H = ham, dt, nstep, tol, trunc, - check_interval = 1000, force_3site = true - ) - peps, wts, = time_evolve(alg) + peps, wts, = time_evolve(peps, ham, dt, nstep, alg, wts; tol) end normalize!.(peps.A, Inf) env, = leading_boundary(env, peps; tol = ctmrg_tol, trunc = trunc_env) diff --git a/test/timeevol/sitedep_truncation.jl b/test/timeevol/sitedep_truncation.jl index b872b0a03..fbc86fb86 100644 --- a/test/timeevol/sitedep_truncation.jl +++ b/test/timeevol/sitedep_truncation.jl @@ -27,8 +27,8 @@ end # 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(; ψ0 = peps0, env0, H = ham, dt = 1.0e-2, nstep = 4, trunc, bipartite = true) - peps, env, = time_evolve(alg) + 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 @@ -54,13 +54,13 @@ end @show bonddims trunc = SiteDependentTruncation(collect(truncrank(d) for d in bonddims)) # 2-site SU - alg = SimpleUpdate(; ψ0 = peps0, env0, H = ham, dt = 1.0e-2, nstep = 4, trunc) - peps, env, = time_evolve(alg) + alg = SimpleUpdate(; trunc) + 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 - alg = SimpleUpdate(; ψ0 = peps0, env0, H = ham, dt = 1.0e-2, nstep = 4, trunc, force_3site = true) - peps, env, = time_evolve(alg) + alg = SimpleUpdate(; trunc, force_3site = true) + peps, env, = time_evolve(peps0, ham, 1.0e-2, 4, alg, env0) @test get_bonddims(peps) == bonddims @test get_bonddims(env) == bonddims end From 3abd2ceac73abb5de078ffe63fef89f9ea878f84 Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Thu, 6 Nov 2025 14:59:21 +0800 Subject: [PATCH 35/62] Minor changes --- test/examples/tf_ising_finiteT.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/examples/tf_ising_finiteT.jl b/test/examples/tf_ising_finiteT.jl index 625a209e8..766fb632d 100644 --- a/test/examples/tf_ising_finiteT.jl +++ b/test/examples/tf_ising_finiteT.jl @@ -64,7 +64,7 @@ alg = SimpleUpdate(; trunc = trunc_pepo, gate_bothsides = true) pepo, wts, info = time_evolve(pepo0, ham, dt, nstep, alg, wts0) env = converge_env(InfinitePartitionFunction(pepo), 16) result_β = measure_mag(pepo, env) -@info "Magnetization at T = $(1 / β)" result_β +@info "tr(σ(x,z)ρ) at T = $(1 / β)" result_β @test β ≈ info.t @test isapprox(abs.(result_β), bm_β, rtol = 1.0e-2) @@ -72,7 +72,7 @@ result_β = measure_mag(pepo, env) pepo, wts, info = time_evolve(pepo, ham, dt, nstep, alg, wts; t₀ = β) env = converge_env(InfinitePartitionFunction(pepo), 16) result_2β = measure_mag(pepo, env) -@info "Magnetization at T = $(1 / (2β))" result_2β +@info "tr(σ(x,z)ρ) at T = $(1 / (2β))" result_2β @test 2 * β ≈ info.t @test isapprox(abs.(result_2β), bm_2β, rtol = 1.0e-4) @@ -81,6 +81,6 @@ alg = SimpleUpdate(; trunc = trunc_pepo, gate_bothsides = false) pepo, wts, info = time_evolve(pepo0, ham, dt, 2 * nstep, alg, wts0) env = converge_env(InfinitePEPS(pepo), 8) result_2β′ = measure_mag(pepo, env; purified = true) -@info "Magnetization at T = $(1 / (2β)) (purification approach)" result_2β′ +@info "⟨ρ|σ(x,z)|ρ⟩ at T = $(1 / (2β)) (purification approach)" result_2β′ @test 2 * β ≈ info.t @test isapprox(abs.(result_2β′), bm_2β, rtol = 1.0e-2) From b5e39da85b146394f220df95e29960024dd2ddf6 Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Thu, 6 Nov 2025 18:08:52 +0800 Subject: [PATCH 36/62] Add verbosity for TimeEvolver --- src/algorithms/time_evolution/simpleupdate.jl | 80 ++++++++++--------- src/algorithms/time_evolution/time_evolve.jl | 2 + test/timeevol/sitedep_truncation.jl | 6 +- 3 files changed, 46 insertions(+), 42 deletions(-) diff --git a/src/algorithms/time_evolution/simpleupdate.jl b/src/algorithms/time_evolution/simpleupdate.jl index da0b352a7..cdf73fb6a 100644 --- a/src/algorithms/time_evolution/simpleupdate.jl +++ b/src/algorithms/time_evolution/simpleupdate.jl @@ -41,7 +41,7 @@ end TimeEvolver( ψ₀::InfiniteState, H::LocalOperator, dt::Float64, nstep::Int, alg::SimpleUpdate, env₀::SUWeight; - tol::Float64 = 0.0, t₀::Float64 = 0.0 + tol::Float64 = 0.0, t₀::Float64 = 0.0, verbosity = 1 ) Initialize a TimeEvolver with Hamiltonian `H` and simple update `alg`, @@ -54,7 +54,7 @@ starting from the initial state `ψ₀` and SUWeight environment `env₀`. function TimeEvolver( ψ₀::InfiniteState, H::LocalOperator, dt::Float64, nstep::Int, alg::SimpleUpdate, env₀::SUWeight; - tol::Float64 = 0.0, t₀::Float64 = 0.0 + tol::Float64 = 0.0, t₀::Float64 = 0.0, verbosity::Int = 1 ) # sanity checks _timeevol_sanity_check(ψ₀, physicalspace(H), tol, alg) @@ -76,7 +76,7 @@ function TimeEvolver( get_expham(H, dt′) end state = SUState(0, t₀, NaN, ψ₀, env₀) - return TimeEvolver(alg, dt, nstep, gate, tol, state) + return TimeEvolver(alg, dt, nstep, gate, tol, verbosity, state) end #= @@ -190,41 +190,43 @@ function su_iter( end function Base.iterate(it::TimeEvolver{<:SimpleUpdate}, state = it.state) - alg = it.alg - iter, t, diff = state.iter, state.t, state.diff - check_convergence = (it.tol > 0) - if check_convergence - if diff < it.tol - @info "SU: bond weights have converged." - return nothing - elseif iter == it.nstep - @warn "SU: bond weights have not converged." - return nothing + return LoggingExtras.withlevel(; it.verbosity) do + alg = it.alg + iter, t, diff = state.iter, state.t, state.diff + check_convergence = (it.tol > 0) + if check_convergence + if diff < it.tol + @infov 1 "SU: bond weights have converged." + return nothing + elseif iter == it.nstep + @warn "SU: bond weights have not converged." + return nothing + end + else + (iter == it.nstep) && return nothing end - else - (iter == it.nstep) && return nothing - end - # perform one iteration and record time - time0 = time() - ψ, env, ϵ = su_iter(state.ψ, it.gate, it.alg, state.env) - diff = compare_weights(env, state.env) - elapsed_time = time() - time0 - # update internal state - iter += 1 - t += it.dt - it.state = SUState(iter, t, diff, ψ, env) - # output information - converged = check_convergence ? (diff < it.tol) : false - showinfo = (iter % alg.check_interval == 0) || (iter == 1) || (iter == it.nstep) || converged - if showinfo - @info "Space of x-weight at [1, 1] = $(space(env[1, 1, 1], 1))" - @info @sprintf( - "SU iter %-7d: dt = %.0e, |Δλ| = %.3e. Time = %.3f s/it", - iter, it.dt, diff, elapsed_time - ) + # perform one iteration and record time + time0 = time() + ψ, env, ϵ = su_iter(state.ψ, it.gate, it.alg, state.env) + diff = compare_weights(env, state.env) + elapsed_time = time() - time0 + # update internal state + iter += 1 + t += it.dt + it.state = SUState(iter, t, diff, ψ, env) + # output information + converged = check_convergence ? (diff < it.tol) : false + showinfo = (iter % alg.check_interval == 0) || (iter == 1) || (iter == it.nstep) || converged + if showinfo + @infov 1 "Space of x-weight at [1, 1] = $(space(env[1, 1, 1], 1))" + @infov 1 @sprintf( + "SU iter %-7d: dt = %.0e, |Δλ| = %.3e. Time = %.3f s/it", + iter, it.dt, diff, elapsed_time + ) + end + info = (; t, ϵ) + return (ψ, env, info), it.state end - info = (; t, ϵ) - return (ψ, env, info), it.state end """ @@ -276,7 +278,7 @@ end time_evolve( ψ₀::Union{InfinitePEPS, InfinitePEPO}, H::LocalOperator, dt::Float64, nstep::Int, alg::SimpleUpdate, env₀::SUWeight; - tol::Float64 = 0.0, t₀::Float64 = 0.0 + tol::Float64 = 0.0, t₀::Float64 = 0.0, verbosity::Int = 1 ) -> (ψ, env, info) Perform time evolution on the initial state `ψ₀` and initial environment `env₀` @@ -292,8 +294,8 @@ with Hamiltonian `H`, using SimpleUpdate algorithm `alg`, time step `dt` for function MPSKit.time_evolve( ψ₀::InfiniteState, H::LocalOperator, dt::Float64, nstep::Int, alg::SimpleUpdate, env₀::SUWeight; - tol::Float64 = 0.0, t₀::Float64 = 0.0 + tol::Float64 = 0.0, t₀::Float64 = 0.0, verbosity::Int = 1 ) - it = TimeEvolver(ψ₀, H, dt, nstep, alg, env₀; tol, t₀) + it = TimeEvolver(ψ₀, H, dt, nstep, alg, env₀; tol, t₀, verbosity) return time_evolve(it) end diff --git a/src/algorithms/time_evolution/time_evolve.jl b/src/algorithms/time_evolution/time_evolve.jl index cb4ae3ce7..5e75bdf23 100644 --- a/src/algorithms/time_evolution/time_evolve.jl +++ b/src/algorithms/time_evolution/time_evolve.jl @@ -25,6 +25,8 @@ mutable struct TimeEvolver{TE <: TimeEvolution, G, S} gate::G # Convergence tolerance (change of weight or energy from last iteration) tol::Float64 + # verbosity level for showing information during evolution + verbosity::Int # PEPS/PEPO (and environment) state::S end diff --git a/test/timeevol/sitedep_truncation.jl b/test/timeevol/sitedep_truncation.jl index fbc86fb86..3207ac643 100644 --- a/test/timeevol/sitedep_truncation.jl +++ b/test/timeevol/sitedep_truncation.jl @@ -28,7 +28,7 @@ end 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) + peps, env, = time_evolve(peps0, ham, 1.0e-2, 4, alg, env0; verbosity = 0) @test get_bonddims(peps) == bonddims @test get_bonddims(env) == bonddims # check bipartite structure is preserved @@ -55,12 +55,12 @@ end trunc = SiteDependentTruncation(collect(truncrank(d) for d in bonddims)) # 2-site SU alg = SimpleUpdate(; trunc) - peps, env, = time_evolve(peps0, ham, 1.0e-2, 4, alg, env0) + peps, env, = time_evolve(peps0, ham, 1.0e-2, 4, alg, env0; verbosity = 0) @test get_bonddims(peps) == bonddims @test get_bonddims(env) == bonddims # 3-site SU alg = SimpleUpdate(; trunc, force_3site = true) - peps, env, = time_evolve(peps0, ham, 1.0e-2, 4, alg, env0) + peps, env, = time_evolve(peps0, ham, 1.0e-2, 4, alg, env0; verbosity = 0) @test get_bonddims(peps) == bonddims @test get_bonddims(env) == bonddims end From b125ed090d6fe373985d51d37f9e6ab7d5a5f1e7 Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Mon, 10 Nov 2025 09:33:04 +0800 Subject: [PATCH 37/62] Store Hamiltonian in TimeEvolver --- src/algorithms/time_evolution/simpleupdate.jl | 9 ++++----- src/algorithms/time_evolution/time_evolve.jl | 12 +++++++----- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/algorithms/time_evolution/simpleupdate.jl b/src/algorithms/time_evolution/simpleupdate.jl index cdf73fb6a..219718b57 100644 --- a/src/algorithms/time_evolution/simpleupdate.jl +++ b/src/algorithms/time_evolution/simpleupdate.jl @@ -29,7 +29,7 @@ struct SUState{S} iter::Int # evolved time t::Float64 - # measure of difference (of SUWeight, energy, etc.) from last iteration + # SUWeight difference from last iteration diff::Float64 # PEPS/PEPO ψ::S @@ -57,7 +57,7 @@ function TimeEvolver( tol::Float64 = 0.0, t₀::Float64 = 0.0, verbosity::Int = 1 ) # sanity checks - _timeevol_sanity_check(ψ₀, physicalspace(H), tol, alg) + _timeevol_sanity_check(ψ₀, H, tol, alg) # create Trotter gates nnonly = is_nearest_neighbour(H) use_3site = alg.force_3site || !nnonly @@ -76,7 +76,7 @@ function TimeEvolver( get_expham(H, dt′) end state = SUState(0, t₀, NaN, ψ₀, env₀) - return TimeEvolver(alg, dt, nstep, gate, tol, verbosity, state) + return TimeEvolver(alg, dt, nstep, H, gate, tol, verbosity, state) end #= @@ -245,8 +245,7 @@ function MPSKit.timestep( it::TimeEvolver{<:SimpleUpdate}, ψ::InfiniteState, env::SUWeight; iter::Int = it.state.iter, t::Float64 = it.state.t ) - Pspaces = physicalspace(it.state.ψ) - _timeevol_sanity_check(ψ, Pspaces, it.tol, it.alg) + _timeevol_sanity_check(ψ, it.ham, it.tol, it.alg) state = SUState(iter, t, NaN, ψ, env) result = iterate(it, state) if result === nothing diff --git a/src/algorithms/time_evolution/time_evolve.jl b/src/algorithms/time_evolution/time_evolve.jl index 5e75bdf23..b53b3bd5b 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} + mutable struct TimeEvolver{TE <: TimeEvolution, H, G, S} Iterator for Trotter-based time evolution of InfinitePEPS or InfinitePEPO. @@ -14,13 +14,15 @@ Iterator for Trotter-based time evolution of InfinitePEPS or InfinitePEPO. $(TYPEDFIELDS) """ -mutable struct TimeEvolver{TE <: TimeEvolution, G, S} +mutable struct TimeEvolver{TE <: TimeEvolution, H, G, S} # Time evolution algorithm alg::TE # Trotter time step dt::Float64 # Maximal iteration steps nstep::Int + # Hamiltonian + ham::H # Trotter gates gate::G # Convergence tolerance (change of weight or energy from last iteration) @@ -34,11 +36,11 @@ end Base.iterate(it::TimeEvolver) = iterate(it, it.state) function _timeevol_sanity_check( - ψ₀::InfiniteState, Pspaces::M, tol::Float64, alg::A - ) where {A <: TimeEvolution, M <: AbstractMatrix{<:ElementarySpace}} + ψ₀::InfiniteState, ham::LocalOperator, tol::Float64, alg::A + ) where {A <: TimeEvolution} Nr, Nc, = size(ψ₀) @assert (Nr >= 2 && Nc >= 2) "Unit cell size for simple update should be no smaller than (2, 2)." - @assert Pspaces == physicalspace(ψ₀) "Physical spaces of `ψ₀` do not match `Pspaces`." + @assert physicalspace(ham) == physicalspace(ψ₀) "Physical spaces of `ψ₀` do not match `Pspaces`." @assert tol >= 0 if tol > 0 @assert alg.imaginary_time "`tol` should be 0 for real time evolution." From 71b23f355c279245c9ec16f03d7cc12a522b811e Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Mon, 10 Nov 2025 22:05:03 +0800 Subject: [PATCH 38/62] Adapt _pinv to new TensorKit --- src/algorithms/truncation/bond_truncation.jl | 4 ++-- src/utility/svd.jl | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/algorithms/truncation/bond_truncation.jl b/src/algorithms/truncation/bond_truncation.jl index e50e55bf6..67215fcdd 100644 --- a/src/algorithms/truncation/bond_truncation.jl +++ b/src/algorithms/truncation/bond_truncation.jl @@ -102,7 +102,7 @@ function bond_truncate( Ra = _tensor_Ra(benv, b) Sa = _tensor_Sa(benv, b, a2b2) a, info_a = if alg.use_pinv - _solve_ab_pinv!(Ra, Sa; trunc = truncerror(; atol = 1.0e-12)) + _solve_ab_pinv!(Ra, Sa; atol = 1.0e-10) else _solve_ab(Ra, Sa, a) end @@ -110,7 +110,7 @@ function bond_truncate( Rb = _tensor_Rb(benv, a) Sb = _tensor_Sb(benv, a, a2b2) b, info_b = if alg.use_pinv - _solve_ab_pinv!(Rb, Sb; trunc = truncerror(; atol = 1.0e-12)) + _solve_ab_pinv!(Rb, Sb; atol = 1.0e-10) else _solve_ab(Rb, Sb, b) end diff --git a/src/utility/svd.jl b/src/utility/svd.jl index d96cae03a..f4299e886 100644 --- a/src/utility/svd.jl +++ b/src/utility/svd.jl @@ -630,9 +630,9 @@ function svd_pullback!( end # Calculate the pseudo-inverse using SVD -function _pinv!(a::AbstractTensorMap; trunc::TruncationStrategy) +function _pinv!(a::AbstractTensorMap; kwargs...) # TODO: replace this with actual truncation error once TensorKit is updated u, s, vh = svd_compact!(a; alg = LAPACK_QRIteration()) - u, s, vh, ϵ = _truncate_compact((u, s, vh), trunc) + u, s, vh, ϵ = _truncate_compact((u, s, vh), truncerror(; kwargs...)) return vh' * sdiag_pow(s, -1) * u', ϵ end From 3e99aac6557d5b9247b71cc18aa39c579cea04dc Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Mon, 10 Nov 2025 22:44:52 +0800 Subject: [PATCH 39/62] Update real time FU test --- test/timeevol/tf_ising_realtime.jl | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/test/timeevol/tf_ising_realtime.jl b/test/timeevol/tf_ising_realtime.jl index bd6cd30ff..a805b041c 100644 --- a/test/timeevol/tf_ising_realtime.jl +++ b/test/timeevol/tf_ising_realtime.jl @@ -4,6 +4,7 @@ import MPSKitModels: S_zz, σˣ using PEPSKit using Printf using Random +using PEPSKit: fullupdate Random.seed!(0) const hc = 3.044382 @@ -54,22 +55,22 @@ function tfising_fu(g::Float64, maxiter::Int, Dcut::Int, chi::Int; als = true, u op = LocalOperator(lattice, ((1, 1),) => σˣ()) ham = tfising(ComplexF64, Trivial, InfiniteSquare(2, 2); J = 1.0, g = g) - trscheme_peps = truncerror(; atol = 1.0e-10) & truncdim(Dcut) - trscheme_env = truncerror(; atol = 1.0e-10) & truncdim(chi) + trunc_peps = truncerror(; atol = 1.0e-10) & truncrank(Dcut) + trunc_env = truncerror(; atol = 1.0e-10) & truncrank(chi) env = CTMRGEnv(rand, ComplexF64, peps, ℂ^chi) - env, = leading_boundary(env, peps; tol = 1.0e-10, verbosity = 2, trscheme = trscheme_env) + env, = leading_boundary(env, peps; tol = 1.0e-10, verbosity = 2, trunc = trunc_env) ctm_alg = SequentialCTMRG(; tol = 1.0e-9, maxiter = 50, verbosity = 2, - trscheme = trscheme_env, + trunc = trunc_env, projector_alg = :fullinfinite, ) opt_alg = if als - ALSTruncation(; trscheme = trscheme_peps, tol = 1.0e-10, use_pinv) + ALSTruncation(; trunc = trunc_peps, tol = 1.0e-10, use_pinv) else - FullEnvTruncation(; trscheme = trscheme_peps, tol = 1.0e-10) + FullEnvTruncation(; trunc = trunc_peps, tol = 1.0e-10) end # do one extra step at the beginning to match benchmark data From 87893ae98dba0a1585b5ddf07ec07398f5934754 Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Tue, 11 Nov 2025 17:28:40 +0800 Subject: [PATCH 40/62] Attempt to fix real time FU test --- src/algorithms/time_evolution/fullupdate.jl | 2 ++ test/timeevol/tf_ising_realtime.jl | 7 +++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/algorithms/time_evolution/fullupdate.jl b/src/algorithms/time_evolution/fullupdate.jl index edcd5ac01..448f00eec 100644 --- a/src/algorithms/time_evolution/fullupdate.jl +++ b/src/algorithms/time_evolution/fullupdate.jl @@ -164,5 +164,7 @@ function fullupdate( # reconverge environment network = isa(state, InfinitePEPS) ? state : InfinitePEPS(state) env, = leading_boundary(env, network, alg.ctm_alg) + espace = codomain(env.corners[1, 1, 1], 1) + @info "Space of env.corner[1, 1, 1] = $(espace)." return state, env, wts, fidmin end diff --git a/test/timeevol/tf_ising_realtime.jl b/test/timeevol/tf_ising_realtime.jl index a805b041c..dffe76da9 100644 --- a/test/timeevol/tf_ising_realtime.jl +++ b/test/timeevol/tf_ising_realtime.jl @@ -57,8 +57,11 @@ function tfising_fu(g::Float64, maxiter::Int, Dcut::Int, chi::Int; als = true, u trunc_peps = truncerror(; atol = 1.0e-10) & truncrank(Dcut) trunc_env = truncerror(; atol = 1.0e-10) & truncrank(chi) - env = CTMRGEnv(rand, ComplexF64, peps, ℂ^chi) - env, = leading_boundary(env, peps; tol = 1.0e-10, verbosity = 2, trunc = trunc_env) + env = CTMRGEnv(ones, ComplexF64, peps, ℂ^1) + env, = leading_boundary( + env, peps; alg = :sequential, + tol = 1.0e-10, verbosity = 2, trunc = trunc_env + ) ctm_alg = SequentialCTMRG(; tol = 1.0e-9, From c6baaed06a8afe5f3c79fe3afde78014e4e839ae Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Tue, 11 Nov 2025 20:22:40 +0800 Subject: [PATCH 41/62] Move convergence check out of TimeEvolver --- examples/heisenberg_su/main.jl | 4 +- examples/hubbard_su/main.jl | 4 +- examples/j1j2_su/main.jl | 4 +- src/algorithms/time_evolution/simpleupdate.jl | 142 +++++++++--------- src/algorithms/time_evolution/time_evolve.jl | 15 +- test/examples/j1j2_finiteT.jl | 8 +- test/examples/tf_ising_finiteT.jl | 2 +- 7 files changed, 85 insertions(+), 94 deletions(-) diff --git a/examples/heisenberg_su/main.jl b/examples/heisenberg_su/main.jl index 1fcee9e45..d028c459c 100644 --- a/examples/heisenberg_su/main.jl +++ b/examples/heisenberg_su/main.jl @@ -72,9 +72,9 @@ dts = [1.0e-2, 1.0e-3, 4.0e-4] tols = [1.0e-6, 1.0e-8, 1.0e-8] nstep = 10000 trunc_peps = truncerror(; atol = 1.0e-10) & truncrank(Dbond) -alg = SimpleUpdate(; trunc = trunc_peps, bipartite = true, check_interval = 500) +alg = SimpleUpdate(; trunc = trunc_peps, bipartite = true) for (dt, tol) in zip(dts, tols) - global peps, wts, = time_evolve(peps, H, dt, nstep, alg, wts; tol) + global peps, wts, = time_evolve(peps, H, dt, nstep, alg, wts; tol, check_interval = 500) end md""" diff --git a/examples/hubbard_su/main.jl b/examples/hubbard_su/main.jl index bfb5350a7..77f13338f 100644 --- a/examples/hubbard_su/main.jl +++ b/examples/hubbard_su/main.jl @@ -66,8 +66,8 @@ maxiter = 20000 for (dt, tol, Dbond) in zip(dts, tols, Ds) trunc = truncerror(; atol = 1.0e-10) & truncrank(Dbond) - alg = SimpleUpdate(; trunc, bipartite = false, check_interval = 2000) - global peps, wts, = time_evolve(peps, H, dt, maxiter, alg, wts; tol) + alg = SimpleUpdate(; trunc, bipartite = false) + global peps, wts, = time_evolve(peps, H, dt, maxiter, alg, wts; tol, check_interval = 2000) end md""" diff --git a/examples/j1j2_su/main.jl b/examples/j1j2_su/main.jl index 6b3ec5753..e027f5bcc 100644 --- a/examples/j1j2_su/main.jl +++ b/examples/j1j2_su/main.jl @@ -51,13 +51,13 @@ on the previously evolved PEPS: dt, tol, nstep = 1.0e-2, 1.0e-8, 30000 check_interval = 4000 trunc_peps = truncerror(; atol = 1.0e-10) & truncrank(Dbond) -alg = SimpleUpdate(; trunc = trunc_peps, check_interval) +alg = SimpleUpdate(; trunc = trunc_peps) for J2 in 0.1:0.1:0.5 ## convert Hamiltonian `LocalOperator` to real floats H = real( j1_j2_model(ComplexF64, symm, InfiniteSquare(Nr, Nc); J1, J2, sublattice = false), ) - global peps, wts, = time_evolve(peps, H, dt, nstep, alg, wts; tol) + global peps, wts, = time_evolve(peps, H, dt, nstep, alg, wts; tol, check_interval) end md""" diff --git a/src/algorithms/time_evolution/simpleupdate.jl b/src/algorithms/time_evolution/simpleupdate.jl index 219718b57..5befa8bf1 100644 --- a/src/algorithms/time_evolution/simpleupdate.jl +++ b/src/algorithms/time_evolution/simpleupdate.jl @@ -12,8 +12,6 @@ $(TYPEDFIELDS) trunc::TruncationStrategy # Switch for imaginary or real time imaginary_time::Bool = true - # controls interval to print information - check_interval::Int = 500 # force usage of 3-site simple update force_3site::Bool = false # ---- only applicable to ground state search ---- @@ -29,8 +27,6 @@ struct SUState{S} iter::Int # evolved time t::Float64 - # SUWeight difference from last iteration - diff::Float64 # PEPS/PEPO ψ::S # SUWeight environment @@ -40,24 +36,20 @@ end """ TimeEvolver( ψ₀::InfiniteState, H::LocalOperator, dt::Float64, nstep::Int, - alg::SimpleUpdate, env₀::SUWeight; - tol::Float64 = 0.0, t₀::Float64 = 0.0, verbosity = 1 + alg::SimpleUpdate, env₀::SUWeight; t₀::Float64 = 0.0 ) Initialize a TimeEvolver with Hamiltonian `H` and simple update `alg`, starting from the initial state `ψ₀` and SUWeight environment `env₀`. - The initial (real or imaginary) time is specified by `t₀`. -- Setting `tol > 0` enables convergence check (for imaginary time evolution of InfinitePEPS only). - For other usages it should not be changed. """ function TimeEvolver( ψ₀::InfiniteState, H::LocalOperator, dt::Float64, nstep::Int, - alg::SimpleUpdate, env₀::SUWeight; - tol::Float64 = 0.0, t₀::Float64 = 0.0, verbosity::Int = 1 + alg::SimpleUpdate, env₀::SUWeight; t₀::Float64 = 0.0 ) # sanity checks - _timeevol_sanity_check(ψ₀, H, tol, alg) + _timeevol_sanity_check(ψ₀, H, alg) # create Trotter gates nnonly = is_nearest_neighbour(H) use_3site = alg.force_3site || !nnonly @@ -75,11 +67,11 @@ function TimeEvolver( else get_expham(H, dt′) end - state = SUState(0, t₀, NaN, ψ₀, env₀) - return TimeEvolver(alg, dt, nstep, H, gate, tol, verbosity, state) + state = SUState(0, t₀, ψ₀, env₀) + return TimeEvolver(alg, dt, nstep, gate, state) end -#= +""" Simple update of the x-bond between `[r,c]` and `[r,c+1]`. ``` @@ -87,7 +79,7 @@ Simple update of the x-bond between `[r,c]` and `[r,c+1]`. -- T[r,c] -- T[r,c+1] -- | | ``` -=# +""" function _su_xbond!( state::InfiniteState, gate::AbstractTensorMap{T, S, 2, 2}, env::SUWeight, row::Int, col::Int, trunc::TruncationStrategy; gate_ax::Int = 1 @@ -116,7 +108,7 @@ function _su_xbond!( return ϵ end -#= +""" Simple update of the y-bond between `[r,c]` and `[r-1,c]`. ``` | @@ -125,7 +117,7 @@ Simple update of the y-bond between `[r,c]` and `[r-1,c]`. -- T[r,c] --- | ``` -=# +""" function _su_ybond!( state::InfiniteState, gate::AbstractTensorMap{T, S, 2, 2}, env::SUWeight, row::Int, col::Int, trunc::TruncationStrategy; gate_ax::Int = 1 @@ -154,6 +146,9 @@ function _su_ybond!( return ϵ end +""" +One iteration of simple update +""" function su_iter( state::InfiniteState, gate::LocalOperator, alg::SimpleUpdate, env::SUWeight ) @@ -190,43 +185,15 @@ function su_iter( end function Base.iterate(it::TimeEvolver{<:SimpleUpdate}, state = it.state) - return LoggingExtras.withlevel(; it.verbosity) do - alg = it.alg - iter, t, diff = state.iter, state.t, state.diff - check_convergence = (it.tol > 0) - if check_convergence - if diff < it.tol - @infov 1 "SU: bond weights have converged." - return nothing - elseif iter == it.nstep - @warn "SU: bond weights have not converged." - return nothing - end - else - (iter == it.nstep) && return nothing - end - # perform one iteration and record time - time0 = time() - ψ, env, ϵ = su_iter(state.ψ, it.gate, it.alg, state.env) - diff = compare_weights(env, state.env) - elapsed_time = time() - time0 - # update internal state - iter += 1 - t += it.dt - it.state = SUState(iter, t, diff, ψ, env) - # output information - converged = check_convergence ? (diff < it.tol) : false - showinfo = (iter % alg.check_interval == 0) || (iter == 1) || (iter == it.nstep) || converged - if showinfo - @infov 1 "Space of x-weight at [1, 1] = $(space(env[1, 1, 1], 1))" - @infov 1 @sprintf( - "SU iter %-7d: dt = %.0e, |Δλ| = %.3e. Time = %.3f s/it", - iter, it.dt, diff, elapsed_time - ) - end - info = (; t, ϵ) - return (ψ, env, info), it.state - end + iter, t = state.iter, state.t + (iter == it.nstep) && return nothing + ψ, env, ϵ = su_iter(state.ψ, it.gate, it.alg, state.env) + # update internal state + iter += 1 + t += it.dt + it.state = SUState(iter, t, ψ, env) + info = (; t, ϵ) + return (ψ, env, info), it.state end """ @@ -245,8 +212,8 @@ function MPSKit.timestep( it::TimeEvolver{<:SimpleUpdate}, ψ::InfiniteState, env::SUWeight; iter::Int = it.state.iter, t::Float64 = it.state.t ) - _timeevol_sanity_check(ψ, it.ham, it.tol, it.alg) - state = SUState(iter, t, NaN, ψ, env) + _timeevol_sanity_check(ψ, it.ham, it.alg) + state = SUState(iter, t, ψ, env) result = iterate(it, state) if result === nothing @warn "TimeEvolver `it` has already reached the end." @@ -257,27 +224,62 @@ function MPSKit.timestep( end """ - time_evolve(it::TimeEvolver{<:SimpleUpdate}) + time_evolve( + it::TimeEvolver{<:SimpleUpdate}; + tol::Float64 = 0.0, check_interval::Int = 500 + ) -Perform time evolution until convergence or the set number of iterations -using the specified TimeEvolver iterator `it` directly. +Perform time evolution to the end of TimeEvolver iterator `it`, +or until convergence of SUWeight set by a positive `tol`. """ -function MPSKit.time_evolve(it::TimeEvolver{<:SimpleUpdate}) +function MPSKit.time_evolve( + it::TimeEvolver{<:SimpleUpdate}; + tol::Float64 = 0.0, check_interval::Int = 500 + ) time_start = time() - result = nothing - for state in it - result = state + check_convergence = (tol > 0) + if check_convergence + @assert (it.state.ψ isa InfinitePEPS) && it.alg.imaginary_time + end + env0, time0 = it.state.env, time() + for (ψ, env, info) in it + iter = it.state.iter + diff = compare_weights(env0, env) + stop = (iter == it.nstep) || (diff < tol) + showinfo = (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: dt = %.0e, |Δλ| = %.3e. Time = %.3f s/it", + iter, it.dt, 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 + end + if stop + time_end = time() + @info @sprintf("Simple update finished. Total time elasped: %.2f s", time_end - time_start) + return ψ, env, info + else + env0 = env + end + time0 = time() end - time_end = time() - @info @sprintf("Simple update finished. Total time elasped: %.2f s", time_end - time_start) - return result + return end """ time_evolve( ψ₀::Union{InfinitePEPS, InfinitePEPO}, H::LocalOperator, dt::Float64, nstep::Int, alg::SimpleUpdate, env₀::SUWeight; - tol::Float64 = 0.0, t₀::Float64 = 0.0, verbosity::Int = 1 + tol::Float64 = 0.0, t₀::Float64 = 0.0, check_interval::Int = 500 ) -> (ψ, env, info) Perform time evolution on the initial state `ψ₀` and initial environment `env₀` @@ -293,8 +295,8 @@ with Hamiltonian `H`, using SimpleUpdate algorithm `alg`, time step `dt` for function MPSKit.time_evolve( ψ₀::InfiniteState, H::LocalOperator, dt::Float64, nstep::Int, alg::SimpleUpdate, env₀::SUWeight; - tol::Float64 = 0.0, t₀::Float64 = 0.0, verbosity::Int = 1 + tol::Float64 = 0.0, t₀::Float64 = 0.0, check_interval::Int = 500 ) - it = TimeEvolver(ψ₀, H, dt, nstep, alg, env₀; tol, t₀, verbosity) - return time_evolve(it) + it = TimeEvolver(ψ₀, H, dt, nstep, alg, env₀; t₀) + return time_evolve(it; tol, check_interval) end diff --git a/src/algorithms/time_evolution/time_evolve.jl b/src/algorithms/time_evolution/time_evolve.jl index b53b3bd5b..a14582fb3 100644 --- a/src/algorithms/time_evolution/time_evolve.jl +++ b/src/algorithms/time_evolution/time_evolve.jl @@ -14,21 +14,15 @@ Iterator for Trotter-based time evolution of InfinitePEPS or InfinitePEPO. $(TYPEDFIELDS) """ -mutable struct TimeEvolver{TE <: TimeEvolution, H, G, S} +mutable struct TimeEvolver{TE <: TimeEvolution, G, S} # Time evolution algorithm alg::TE # Trotter time step dt::Float64 # Maximal iteration steps nstep::Int - # Hamiltonian - ham::H # Trotter gates gate::G - # Convergence tolerance (change of weight or energy from last iteration) - tol::Float64 - # verbosity level for showing information during evolution - verbosity::Int # PEPS/PEPO (and environment) state::S end @@ -36,16 +30,11 @@ end Base.iterate(it::TimeEvolver) = iterate(it, it.state) function _timeevol_sanity_check( - ψ₀::InfiniteState, ham::LocalOperator, tol::Float64, alg::A + ψ₀::InfiniteState, ham::LocalOperator, alg::A ) where {A <: TimeEvolution} Nr, Nc, = size(ψ₀) @assert (Nr >= 2 && Nc >= 2) "Unit cell size for simple update should be no smaller than (2, 2)." @assert physicalspace(ham) == physicalspace(ψ₀) "Physical spaces of `ψ₀` do not match `Pspaces`." - @assert tol >= 0 - if tol > 0 - @assert alg.imaginary_time "`tol` should be 0 for real time evolution." - @assert ψ₀ isa InfinitePEPS "`tol` should be 0 for time evolution of InfinitePEPO." - end if hasfield(typeof(alg), :gate_bothsides) && alg.gate_bothsides @assert ψ₀ isa InfinitePEPO "alg.gate_bothsides = true is only compatible with PEPO." end diff --git a/test/examples/j1j2_finiteT.jl b/test/examples/j1j2_finiteT.jl index 8ffa38369..1ec19bfb9 100644 --- a/test/examples/j1j2_finiteT.jl +++ b/test/examples/j1j2_finiteT.jl @@ -32,9 +32,9 @@ check_interval = 100 # PEPO approach dt, nstep = 1.0e-3, 600 -alg = SimpleUpdate(; trunc = trunc_pepo, gate_bothsides = true, check_interval) +alg = SimpleUpdate(; trunc = trunc_pepo, gate_bothsides = true) evolver = TimeEvolver(pepo0, ham, dt, nstep, alg, wts0) -pepo, wts, info = time_evolve(evolver) +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)" @@ -42,9 +42,9 @@ energy = expectation_value(pepo, ham, env) / (Nr * Nc) @test energy ≈ bm[2] atol = 5.0e-3 # PEPS (purified PEPO) approach -alg = SimpleUpdate(; trunc = trunc_pepo, gate_bothsides = false, check_interval) +alg = SimpleUpdate(; trunc = trunc_pepo, gate_bothsides = false) evolver = TimeEvolver(pepo0, ham, dt, nstep, alg, wts0) -pepo, wts, = time_evolve(evolver) +pepo, wts, = 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)" diff --git a/test/examples/tf_ising_finiteT.jl b/test/examples/tf_ising_finiteT.jl index 766fb632d..109b41097 100644 --- a/test/examples/tf_ising_finiteT.jl +++ b/test/examples/tf_ising_finiteT.jl @@ -25,7 +25,7 @@ end function converge_env(state, χ::Int) trunc1 = truncrank(4) & truncerror(; atol = 1.0e-12) - env0 = CTMRGEnv(randn, Float64, state, ℂ^4) + env0 = CTMRGEnv(ones, Float64, state, ℂ^1) env, = leading_boundary(env0, state; alg = :sequential, trunc = trunc1, tol = 1.0e-10) trunc2 = truncrank(χ) & truncerror(; atol = 1.0e-12) env, = leading_boundary(env, state; alg = :sequential, trunc = trunc2, tol = 1.0e-10) From e841bf147d4f54a07ad016a5a314a427c752efa9 Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Tue, 11 Nov 2025 20:27:06 +0800 Subject: [PATCH 42/62] Restore docstrings --- .../time_evolution/simpleupdate3site.jl | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/algorithms/time_evolution/simpleupdate3site.jl b/src/algorithms/time_evolution/simpleupdate3site.jl index c75663b9f..9be45a7fe 100644 --- a/src/algorithms/time_evolution/simpleupdate3site.jl +++ b/src/algorithms/time_evolution/simpleupdate3site.jl @@ -124,14 +124,14 @@ Then the fidelity is just F(ψ̃) = (norm(s̃[n], 2) / norm(s[n], 2))^2 ``` =# -#= +""" Perform QR decomposition through a PEPS tensor ``` ╱ ╱ --R0----M--- → ---Q--*-R1-- ╱ | ╱ | ``` -=# +""" function qr_through( R0::MPSBondTensor, M::GenericMPSTensor{S, 4}; normalize::Bool = true ) where {S <: ElementarySpace} @@ -154,14 +154,14 @@ function qr_through( return r end -#= +""" Perform LQ decomposition through a tensor ``` ╱ ╱ --L0-*--Q--- ← ---M--*-L1-- ╱ | ╱ | ``` -=# +""" function lq_through( M::GenericMPSTensor{S, 4}, L1::MPSBondTensor; normalize::Bool = true ) where {S <: ElementarySpace} @@ -186,9 +186,9 @@ function lq_through( return l 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}} # M1 -- (R1,L1) -- M2 -- (R2,L2) -- M3 N = length(Ms) @@ -207,7 +207,7 @@ function _get_allRLs(Ms::Vector{T}) where {T <: GenericMPSTensor{<:ElementarySpa return Rs, Ls end -#= +""" 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`, @@ -220,7 +220,7 @@ The arrows between `Pa`, `s`, `Pb` are rev = true: - Pa --→-- Pb - 2 → s → 1 ``` -=# +""" function _proj_from_RL( r::MPSBondTensor, l::MPSBondTensor; trunc::TruncationStrategy = notrunc(), rev::Bool = false, @@ -240,10 +240,10 @@ function _proj_from_RL( return Pa, s, Pb, ϵ 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. -=# +""" function _get_allprojs( Ms, Rs, Ls, truncs::Vector{E}, revs::Vector{Bool} ) where {E <: TruncationStrategy} @@ -266,9 +266,9 @@ function _get_allprojs( return Pas, Pbs, wts, ϵs end -#= +""" Find projectors to truncate internal bonds of the cluster `Ms`. -=# +""" function _cluster_truncate!( Ms::Vector{T}, truncs::Vector{E}, revs::Vector{Bool} ) where {T <: GenericMPSTensor{<:ElementarySpace, 4}, E <: TruncationStrategy} @@ -283,7 +283,7 @@ function _cluster_truncate!( return wts, ϵs, Pas, Pbs end -#= +""" Apply the gate MPO `gs` on the cluster `Ms`. When `gate_ax` is 1 or 2, the gate acts from the physical codomain or domain side. @@ -307,7 +307,7 @@ In the cluster, the axes of each tensor use the MPS order 4 2 5 2 M[1 2 3 4; 5] M[1 2 3 4 5; 6] ``` -=# +""" function _apply_gatempo!( Ms::Vector{T1}, gs::Vector{T2}; gate_ax::Int = 1 ) where {T1 <: GenericMPSTensor{<:ElementarySpace, 4}, T2 <: AbstractTensorMap} @@ -437,7 +437,7 @@ const perms_se_pepo = map(invperms_se_pepo) do (p1, p2) p = invperm((p1..., p2...)) return (p[1:(end - 1)], (p[end],)) end -#= +""" Obtain the 3-site cluster in the "southeast corner" of a square plaquette. ``` r-1 M3 @@ -446,7 +446,7 @@ Obtain the 3-site cluster in the "southeast corner" of a square plaquette. r M1 -←- M2 c c+1 ``` -=# +""" function get_3site_se(state::InfiniteState, env::SUWeight, row::Int, col::Int) Nr, Nc = size(state) rm1, cp1 = _prev(row, Nr), _next(col, Nc) From ef5f353f9d1b031fc363eceb528241b116648fa5 Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Tue, 11 Nov 2025 20:43:28 +0800 Subject: [PATCH 43/62] Fix `timestep` --- src/algorithms/time_evolution/simpleupdate.jl | 4 ++-- src/algorithms/time_evolution/time_evolve.jl | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/algorithms/time_evolution/simpleupdate.jl b/src/algorithms/time_evolution/simpleupdate.jl index 5befa8bf1..4046b8ee0 100644 --- a/src/algorithms/time_evolution/simpleupdate.jl +++ b/src/algorithms/time_evolution/simpleupdate.jl @@ -49,7 +49,7 @@ function TimeEvolver( alg::SimpleUpdate, env₀::SUWeight; t₀::Float64 = 0.0 ) # sanity checks - _timeevol_sanity_check(ψ₀, H, alg) + _timeevol_sanity_check(ψ₀, physicalspace(H), alg) # create Trotter gates nnonly = is_nearest_neighbour(H) use_3site = alg.force_3site || !nnonly @@ -212,7 +212,7 @@ function MPSKit.timestep( it::TimeEvolver{<:SimpleUpdate}, ψ::InfiniteState, env::SUWeight; iter::Int = it.state.iter, t::Float64 = it.state.t ) - _timeevol_sanity_check(ψ, it.ham, it.alg) + _timeevol_sanity_check(ψ, physicalspace(it.state.ψ), it.alg) state = SUState(iter, t, ψ, env) result = iterate(it, state) if result === nothing diff --git a/src/algorithms/time_evolution/time_evolve.jl b/src/algorithms/time_evolution/time_evolve.jl index a14582fb3..d1fb7eb0d 100644 --- a/src/algorithms/time_evolution/time_evolve.jl +++ b/src/algorithms/time_evolution/time_evolve.jl @@ -30,11 +30,11 @@ end Base.iterate(it::TimeEvolver) = iterate(it, it.state) function _timeevol_sanity_check( - ψ₀::InfiniteState, ham::LocalOperator, alg::A - ) where {A <: TimeEvolution} + ψ₀::InfiniteState, Pspaces::M, alg::A + ) where {A <: TimeEvolution, M <: AbstractMatrix{<:ElementarySpace}} Nr, Nc, = size(ψ₀) @assert (Nr >= 2 && Nc >= 2) "Unit cell size for simple update should be no smaller than (2, 2)." - @assert physicalspace(ham) == physicalspace(ψ₀) "Physical spaces of `ψ₀` do not match `Pspaces`." + @assert Pspaces == physicalspace(ψ₀) "Physical spaces of `ψ₀` do not match `Pspaces`." if hasfield(typeof(alg), :gate_bothsides) && alg.gate_bothsides @assert ψ₀ isa InfinitePEPO "alg.gate_bothsides = true is only compatible with PEPO." end From 5ec704ec1e417a40be5288357fd503c42580a49b Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Tue, 11 Nov 2025 20:58:27 +0800 Subject: [PATCH 44/62] Fix tests --- test/bondenv/benv_fu.jl | 6 ++---- test/timeevol/cluster_projectors.jl | 8 ++++---- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/test/bondenv/benv_fu.jl b/test/bondenv/benv_fu.jl index 8d74f96a1..9212f3b15 100644 --- a/test/bondenv/benv_fu.jl +++ b/test/bondenv/benv_fu.jl @@ -13,10 +13,8 @@ function get_hubbard_state(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), check_interval = 2000 - ) - evolver = TimeEvolver(peps, H, 1.0e-2, 10000, alg, wts; tol = 1.0e-8) + alg = SimpleUpdate(; trunc = truncerror(; atol = 1.0e-10) & truncrank(4)) + evolver = TimeEvolver(peps, H, 1.0e-2, 10000, alg, wts; tol = 1.0e-8, check_interval = 2000) peps, = time_evolve(evolver) normalize!.(peps.A, Inf) return peps diff --git a/test/timeevol/cluster_projectors.jl b/test/timeevol/cluster_projectors.jl index 71f4163a2..279907c16 100644 --- a/test/timeevol/cluster_projectors.jl +++ b/test/timeevol/cluster_projectors.jl @@ -120,8 +120,8 @@ end nstep = 5000 for (n, (dt, tol)) in enumerate(zip(dts, tols)) trunc = truncerror(; atol = 1.0e-10) & truncrank(n == 1 ? 4 : 2) - alg = SimpleUpdate(; trunc, bipartite = true, check_interval = 1000) - peps, wts, = time_evolve(peps, ham, dt, nstep, alg, wts; tol) + alg = SimpleUpdate(; trunc, bipartite = true) + peps, wts, = time_evolve(peps, ham, dt, nstep, alg, wts; tol, check_interval = 1000) end normalize!.(peps.A, Inf) env = CTMRGEnv(wts, peps) @@ -132,9 +132,9 @@ end dts = [1.0e-2, 5.0e-3] tols = [1.0e-8, 1.0e-8] trunc = truncerror(; atol = 1.0e-10) & truncrank(2) - alg = SimpleUpdate(; trunc, check_interval = 1000, force_3site = true) + alg = SimpleUpdate(; trunc, force_3site = true) for (n, (dt, tol)) in enumerate(zip(dts, tols)) - peps, wts, = time_evolve(peps, ham, dt, nstep, alg, wts; tol) + peps, wts, = time_evolve(peps, ham, dt, nstep, alg, wts; tol, check_interval = 1000) end normalize!.(peps.A, Inf) env, = leading_boundary(env, peps; tol = ctmrg_tol, trunc = trunc_env) From 97dd4208c25fdee4dd3404357a01b975ce3de768 Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Tue, 11 Nov 2025 21:52:35 +0800 Subject: [PATCH 45/62] Fix more tests --- test/bondenv/benv_fu.jl | 4 ++-- test/timeevol/sitedep_truncation.jl | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/test/bondenv/benv_fu.jl b/test/bondenv/benv_fu.jl index 9212f3b15..1a531032a 100644 --- a/test/bondenv/benv_fu.jl +++ b/test/bondenv/benv_fu.jl @@ -14,8 +14,8 @@ function get_hubbard_state(t::Float64 = 1.0, U::Float64 = 8.0) peps = InfinitePEPS(rand, ComplexF64, Vphy, Vphy; unitcell = (Nr, Nc)) wts = SUWeight(peps) alg = SimpleUpdate(; trunc = truncerror(; atol = 1.0e-10) & truncrank(4)) - evolver = TimeEvolver(peps, H, 1.0e-2, 10000, alg, wts; tol = 1.0e-8, check_interval = 2000) - peps, = time_evolve(evolver) + evolver = TimeEvolver(peps, H, 1.0e-2, 10000, alg, wts) + peps, = time_evolve(evolver; tol = 1.0e-8, check_interval = 2000) normalize!.(peps.A, Inf) return peps end diff --git a/test/timeevol/sitedep_truncation.jl b/test/timeevol/sitedep_truncation.jl index 3207ac643..fbc86fb86 100644 --- a/test/timeevol/sitedep_truncation.jl +++ b/test/timeevol/sitedep_truncation.jl @@ -28,7 +28,7 @@ end 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; verbosity = 0) + 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 @@ -55,12 +55,12 @@ end trunc = SiteDependentTruncation(collect(truncrank(d) for d in bonddims)) # 2-site SU alg = SimpleUpdate(; trunc) - peps, env, = time_evolve(peps0, ham, 1.0e-2, 4, alg, env0; verbosity = 0) + 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 alg = SimpleUpdate(; trunc, force_3site = true) - peps, env, = time_evolve(peps0, ham, 1.0e-2, 4, alg, env0; verbosity = 0) + peps, env, = time_evolve(peps0, ham, 1.0e-2, 4, alg, env0) @test get_bonddims(peps) == bonddims @test get_bonddims(env) == bonddims end From a131181f439dbd15883756c5cc35655f806cb894 Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Wed, 12 Nov 2025 09:34:21 +0800 Subject: [PATCH 46/62] Simplify FullUpdate algorithm struct --- src/algorithms/time_evolution/fullupdate.jl | 33 +++++++-------------- test/timeevol/tf_ising_realtime.jl | 7 ++--- 2 files changed, 14 insertions(+), 26 deletions(-) diff --git a/src/algorithms/time_evolution/fullupdate.jl b/src/algorithms/time_evolution/fullupdate.jl index 448f00eec..1adaf37a7 100644 --- a/src/algorithms/time_evolution/fullupdate.jl +++ b/src/algorithms/time_evolution/fullupdate.jl @@ -8,24 +8,17 @@ Algorithm struct for full update (FU) of InfinitePEPS or InfinitePEPO. $(TYPEDFIELDS) """ @kwdef struct FullUpdate - "Time evolution step, such that the Trotter gate is exp(-dt * Hᵢⱼ). - Use imaginary `dt` for real time evolution." - dt::Number - "Number of evolution steps without fully reconverging the environment." - niter::Int - "Fix gauge of bond environment." + # Fix gauge of bond environment fixgauge::Bool = true - "Bond truncation algorithm after applying time evolution gate." + # Switch for imaginary or real time + imaginary_time::Bool = true + # Bond truncation algorithm after applying time evolution gate opt_alg::Union{ALSTruncation, FullEnvTruncation} = ALSTruncation(; trunc = truncerror(; atol = 1.0e-10) ) - "CTMRG algorithm to reconverge environment. - Its `projector_alg` is also used for the fast update - of the environment after each FU iteration." + # CTMRG algorithm to reconverge environment. Its `projector_alg` is also used for the fast update of the environment after each FU iteration ctm_alg::CTMRGAlgorithm = SequentialCTMRG(; - tol = 1.0e-9, - maxiter = 20, - verbosity = 1, + tol = 1.0e-9, maxiter = 20, verbosity = 1, trunc = truncerror(; atol = 1.0e-10), projector_alg = :fullinfinite, ) @@ -103,8 +96,6 @@ function _fu_column!( return wts_col, fid end -const _fu_pepo_warning_shown = Ref(false) - """ One round of fast full update on the input InfinitePEPS or InfinitePEPO `state` and its 2-layer CTMRGEnv `env`, without fully reconverging `env`. @@ -147,17 +138,15 @@ end Full update an infinite PEPS with nearest neighbor Hamiltonian. """ function fullupdate( - state::InfiniteState, ham::LocalOperator, alg::FullUpdate, env::CTMRGEnv + state::InfiniteState, ham::LocalOperator, dt::Float64, + alg::FullUpdate, env::CTMRGEnv; reconv_interval::Int = 5 ) - if state isa InfinitePEPO && !_fu_pepo_warning_shown[] - @warn "Full update of InfinitePEPO is an experimental feature." - _fu_pepo_warning_shown[] = true - end # Each NN bond is updated twice in fu_iter, # thus `dt` is divided by 2 when exponentiating `ham`. - gate = get_expham(ham, alg.dt / 2) + dt′ = _get_dt(state, dt, alg.imaginary_time) + gate = get_expham(ham, dt′ / 2) wts, fidmin = nothing, 1.0 - for it in 1:(alg.niter) + for it in 1:reconv_interval state, env, wts, fid = fu_iter(state, gate, alg, env) fidmin = min(fidmin, fid) end diff --git a/test/timeevol/tf_ising_realtime.jl b/test/timeevol/tf_ising_realtime.jl index dffe76da9..b157dc81c 100644 --- a/test/timeevol/tf_ising_realtime.jl +++ b/test/timeevol/tf_ising_realtime.jl @@ -78,18 +78,17 @@ function tfising_fu(g::Float64, maxiter::Int, Dcut::Int, chi::Int; als = true, u # do one extra step at the beginning to match benchmark data t = 0.01 - fu_alg = FullUpdate(; dt = 0.01im, niter = 1, opt_alg, ctm_alg) + fu_alg = FullUpdate(; opt_alg, ctm_alg, imaginary_time = false) time0 = time() - peps, env, = fullupdate(peps, ham, fu_alg, env) + peps, env, = fullupdate(peps, ham, 0.01, fu_alg, env; reconv_interval = 1) magx = expectation_value(peps, op, env) time1 = time() @info Printf.format(formatter, t, real(magx), imag(magx), time1 - time0) @test isapprox(magx, data[1, 2]; atol = 0.005) - fu_alg = FullUpdate(; dt = 0.01im, niter = 5, opt_alg, ctm_alg) for count in 1:maxiter time0 = time() - peps, env, = fullupdate(peps, ham, fu_alg, env) + peps, env, = fullupdate(peps, ham, 0.01, fu_alg, env; reconv_interval = 5) magx = expectation_value(peps, op, env) time1 = time() t += 0.05 From 8207b73f16ddfba9edeac5ffc634bd964ba541ed Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Wed, 12 Nov 2025 09:37:01 +0800 Subject: [PATCH 47/62] Finer parameters for SUState --- src/algorithms/time_evolution/simpleupdate.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/algorithms/time_evolution/simpleupdate.jl b/src/algorithms/time_evolution/simpleupdate.jl index 4046b8ee0..a184d4e82 100644 --- a/src/algorithms/time_evolution/simpleupdate.jl +++ b/src/algorithms/time_evolution/simpleupdate.jl @@ -22,7 +22,7 @@ $(TYPEDFIELDS) end # internal state of simple update algorithm -struct SUState{S} +struct SUState{S <: InfiniteState, E <: SUWeight} # number of performed iterations iter::Int # evolved time @@ -30,7 +30,7 @@ struct SUState{S} # PEPS/PEPO ψ::S # SUWeight environment - env::SUWeight + env::E end """ From 59ca2406574b1c4eb4669407f50c0638f3dd233f Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Wed, 12 Nov 2025 12:10:04 +0800 Subject: [PATCH 48/62] Adapt full update to TimeEvolver interface --- src/algorithms/time_evolution/fullupdate.jl | 202 +++++++++++++++++--- 1 file changed, 174 insertions(+), 28 deletions(-) diff --git a/src/algorithms/time_evolution/fullupdate.jl b/src/algorithms/time_evolution/fullupdate.jl index 1adaf37a7..7abd39fca 100644 --- a/src/algorithms/time_evolution/fullupdate.jl +++ b/src/algorithms/time_evolution/fullupdate.jl @@ -6,12 +6,16 @@ Algorithm struct for full update (FU) of InfinitePEPS or InfinitePEPO. ## Fields $(TYPEDFIELDS) + +Reference: Physical Review B 92, 035142 (2015) """ -@kwdef struct FullUpdate +@kwdef struct FullUpdate <: TimeEvolution # Fix gauge of bond environment fixgauge::Bool = true # Switch for imaginary or real time imaginary_time::Bool = true + # Number of iterations without fully reconverging the environment + reconverge_interval::Int = 5 # Bond truncation algorithm after applying time evolution gate opt_alg::Union{ALSTruncation, FullEnvTruncation} = ALSTruncation(; trunc = truncerror(; atol = 1.0e-10) @@ -24,6 +28,42 @@ $(TYPEDFIELDS) ) end +# internal state of full update algorithm +struct FUState{S <: InfiniteState, E <: CTMRGEnv} + # number of performed iterations + iter::Int + # evolved time + t::Float64 + # PEPS/PEPO + ψ::S + # CTMRG environment + env::E + # whether the current environment is reconverged + reconverged::Bool +end + +""" + TimeEvolver( + ψ₀::InfiniteState, H::LocalOperator, dt::Float64, nstep::Int, + alg::FullUpdate, env₀::CTMRGEnv; t₀::Float64 = 0.0 + ) + +Initialize a TimeEvolver with Hamiltonian `H` and full update `alg`, +starting from the initial state `ψ₀` and CTMRG environment `env₀`. + +- The initial (real or imaginary) time is specified by `t₀`. +""" +function TimeEvolver( + ψ₀::InfiniteState, H::LocalOperator, dt::Float64, nstep::Int, + alg::FullUpdate, env₀::CTMRGEnv; t₀::Float64 = 0.0 + ) + _timeevol_sanity_check(ψ₀, physicalspace(H), alg) + dt′ = _get_dt(ψ₀, dt, alg.imaginary_time) + gate = get_expham(H, dt′ / 2) + state = FUState(0, t₀, ψ₀, env₀, true) + return TimeEvolver(alg, dt, nstep, gate, state) +end + """ Full update for the bond between `[row, col]` and `[row, col+1]`. """ @@ -70,8 +110,7 @@ function _fu_column!( state::InfiniteState, gate::LocalOperator, alg::FullUpdate, env::CTMRGEnv, col::Int ) - Nr, Nc = size(state) - @assert 1 <= col <= Nc + Nr, Nc, = size(state) fid = 1.0 wts_col = Vector{PEPSWeight}(undef, Nr) for row in 1:Nr @@ -99,17 +138,11 @@ end """ One round of fast full update on the input InfinitePEPS or InfinitePEPO `state` and its 2-layer CTMRGEnv `env`, without fully reconverging `env`. - -Reference: Physical Review B 92, 035142 (2015) """ function fu_iter( state::InfiniteState, gate::LocalOperator, alg::FullUpdate, env::CTMRGEnv ) - if state isa InfinitePEPO && !_fu_pepo_warning_shown[] - @warn "Full update of InfinitePEPO is experimental." - _fu_pepo_warning_shown[] = true - end - Nr, Nc = size(state)[1:2] + Nr, Nc, = size(state) fidmin = 1.0 state2, env2 = deepcopy(state), deepcopy(env) wts = Array{PEPSWeight}(undef, 2, Nr, Nc) @@ -134,26 +167,139 @@ function fu_iter( return state2, env2, SUWeight(collect(wt for wt in wts)), fidmin end +function Base.iterate(it::TimeEvolver{<:FullUpdate}, state = it.state) + iter, t = state.iter, state.t + (iter == it.nstep) && return nothing + ψ, env, wts, fid = fu_iter(state.ψ, it.gate, it.alg, state.env) + iter, t = iter + 1, t + it.dt + # reconverge environment for the last step and every `reconverge_interval` steps + reconverged = (iter % it.alg.reconverge_interval == 0) || (iter == it.nstep) + if reconverged + network = isa(ψ, InfinitePEPS) ? ψ : InfinitePEPS(ψ) + env, = leading_boundary(env, network, it.alg.ctm_alg) + end + # update internal state + it.state = FUState(iter, t, ψ, env, reconverged) + info = (; t, wts, fid) + return (ψ, env, info), it.state +end + """ -Full update an infinite PEPS with nearest neighbor Hamiltonian. + timestep( + it::TimeEvolver{<:FullUpdate}, ψ::InfiniteState, env::CTMRGEnv; + iter::Int = it.state.iter, t::Float64 = it.state.t + ) -> (ψ, env, info) + +Given the TimeEvolver iterator `it`, perform one step of time evolution +on the input state `ψ` and its environment `env`. + +- Using `iter` and `t` to reset the current iteration number and evolved time + respectively of the TimeEvolver `it`. +- Use `reconverge_env` to force reconverging the obtained environment. """ -function fullupdate( - state::InfiniteState, ham::LocalOperator, dt::Float64, - alg::FullUpdate, env::CTMRGEnv; reconv_interval::Int = 5 +function MPSKit.timestep( + it::TimeEvolver{<:FullUpdate}, ψ::InfiniteState, env::CTMRGEnv; + iter::Int = it.state.iter, t::Float64 = it.state.t, reconverge_env::Bool = false ) - # Each NN bond is updated twice in fu_iter, - # thus `dt` is divided by 2 when exponentiating `ham`. - dt′ = _get_dt(state, dt, alg.imaginary_time) - gate = get_expham(ham, dt′ / 2) - wts, fidmin = nothing, 1.0 - for it in 1:reconv_interval - state, env, wts, fid = fu_iter(state, gate, alg, env) - fidmin = min(fidmin, fid) + _timeevol_sanity_check(ψ, physicalspace(it.state.ψ), it.alg) + state = FUState(iter, t, ψ, env, true) + result = iterate(it, state) + if result === nothing + @warn "TimeEvolver `it` has already reached the end." + return nothing + else + ψ, env, info = first(result) + if reconverge_env && !(it.state.reconverged) + network = isa(ψ, InfinitePEPS) ? ψ : InfinitePEPS(ψ) + env, = leading_boundary(env, network, it.alg.ctm_alg) + end + return ψ, env, info + end +end + +function MPSKit.time_evolve( + it::TimeEvolver{<:FullUpdate}; + tol::Float64 = 0.0, H::Union{Nothing, LocalOperator} = nothing + ) + time_start = time() + check_convergence = (tol > 0) + if check_convergence + @assert (it.state.ψ isa InfinitePEPS) && it.alg.imaginary_time "Only imaginary time evolution of InfinitePEPS allows convergence checking." + @assert H isa LocalOperator "Hamiltonian should be provided for convergence checking in full update." + end + time0 = time() + iter0, t0 = it.state.iter, it.state.t + ψ0, env0, info0 = it.state.ψ, it.state.env, nothing + energy0 = check_convergence ? + expectation_value(ψ0, H, ψ0, env0) / prod(size(ψ0)) : NaN + if check_convergence + @info "FU: initial state energy = $(energy0)." end - # reconverge environment - network = isa(state, InfinitePEPS) ? state : InfinitePEPS(state) - env, = leading_boundary(env, network, alg.ctm_alg) - espace = codomain(env.corners[1, 1, 1], 1) - @info "Space of env.corner[1, 1, 1] = $(espace)." - return state, env, wts, fidmin + for (ψ, env, info) in it + !(it.state.reconverged) && continue + # do the following only when env has been reconverged + iter = it.state.iter + energy = check_convergence ? + expectation_value(ψ, H, ψ, env) / prod(size(ψ)) : NaN + diff = energy - energy0 + stop = (iter == it.nstep) || (diff < 0 && abs(diff) < tol) || (diff > 0) + showinfo = (iter == 1) || it.state.reconverged || stop + time1 = time() + if showinfo + 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)." + Δλ = (info0 === nothing) ? NaN : compare_weights(info.wts, info0.wts) + if check_convergence + @info @sprintf( + "FU iter %-6d: E = %.5f, ΔE = %.3e, |Δλ| = %.3e. Time: %.2f s", + it.state.iter, energy, diff, Δλ, time1 - time0 + ) + else + @info @sprintf( + "FU iter %d: t = %.2e, |Δλ| = %.3e. Time: %.2f s", + it.state.iter, it.state.t, Δλ, time1 - time0 + ) + end + end + if check_convergence + if (diff < 0 && abs(diff) < tol) + @info "FU: energy has converged." + return ψ, env, info + end + if diff > 0 + @warn "FU: energy has increased from last check. Abort evolution and return results from last check." + # also reset internal state of `it` to last check + it.state = FUState(iter0, t0, ψ0, env0, true) + return ψ0, env0, info0 + end + if iter == it.nstep + @info "FU: energy has not converged." + return ψ, env, info + end + end + if stop + time_end = time() + @info @sprintf("Full update finished. Total time elasped: %.2f s", time_end - time_start) + return ψ, env, info + else + # update backup variables + iter0, t0 = it.state.iter, it.state.t + ψ0, env0, info0, energy0 = ψ, env, info, energy + end + time0 = time() + end + return +end + +""" +Full update an infinite PEPS with nearest neighbor Hamiltonian. +""" +function MPSKit.time_evolve( + ψ₀::InfiniteState, H::LocalOperator, dt::Float64, nstep::Int, + alg::FullUpdate, env₀::CTMRGEnv; + tol::Float64 = 0.0, t₀::Float64 = 0.0 + ) + it = TimeEvolver(ψ₀, H, dt, nstep, alg, env₀; t₀) + return time_evolve(it; tol, H) end From f83accfadeeca56a325c072e902c4c36ba5b8a4e Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Wed, 12 Nov 2025 16:39:37 +0800 Subject: [PATCH 49/62] Update real time FU test --- src/algorithms/time_evolution/fullupdate.jl | 3 + src/environments/ctmrg_environments.jl | 5 ++ test/timeevol/tf_ising_realtime.jl | 42 +++++++------ test/timeevol/timestep.jl | 66 ++++++++++++++++----- 4 files changed, 83 insertions(+), 33 deletions(-) diff --git a/src/algorithms/time_evolution/fullupdate.jl b/src/algorithms/time_evolution/fullupdate.jl index 7abd39fca..4c0f982aa 100644 --- a/src/algorithms/time_evolution/fullupdate.jl +++ b/src/algorithms/time_evolution/fullupdate.jl @@ -212,6 +212,9 @@ function MPSKit.timestep( if reconverge_env && !(it.state.reconverged) network = isa(ψ, InfinitePEPS) ? ψ : InfinitePEPS(ψ) env, = leading_boundary(env, network, it.alg.ctm_alg) + # update internal state of `it` + state0 = it.state + it.state = (@set state0.env = env) end return ψ, env, info end diff --git a/src/environments/ctmrg_environments.jl b/src/environments/ctmrg_environments.jl index 08fa91e62..014dd4082 100644 --- a/src/environments/ctmrg_environments.jl +++ b/src/environments/ctmrg_environments.jl @@ -277,6 +277,11 @@ edgetype(::Type{CTMRGEnv{C, E}}) where {C, E} = E TensorKit.spacetype(::Type{E}) where {E <: CTMRGEnv} = spacetype(cornertype(E)) +## (Approximate) equality +function Base.:(==)(env1::CTMRGEnv, env2::CTMRGEnv) + return env1.corners == env2.corners && env1.edges == env2.edges +end + # In-place update of environment function update!(env::CTMRGEnv{C, T}, env´::CTMRGEnv{C, T}) where {C, T} env.corners .= env´.corners diff --git a/test/timeevol/tf_ising_realtime.jl b/test/timeevol/tf_ising_realtime.jl index b157dc81c..6c9aafffd 100644 --- a/test/timeevol/tf_ising_realtime.jl +++ b/test/timeevol/tf_ising_realtime.jl @@ -4,11 +4,11 @@ import MPSKitModels: S_zz, σˣ using PEPSKit using Printf using Random -using PEPSKit: fullupdate +using Accessors: @set Random.seed!(0) const hc = 3.044382 -const formatter = Printf.Format("t = %.2f, ⟨σˣ⟩ = %.7e + %.7e im. Time = %.3f s") +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 χ = 4D = 32 @@ -44,7 +44,7 @@ function tfising( ) end -function tfising_fu(g::Float64, maxiter::Int, Dcut::Int, chi::Int; als = true, use_pinv = true) +function tfising_fu(g::Float64, Dcut::Int, chi::Int; als = true, use_pinv = true) # the fully polarized state peps = InfinitePEPS(randn, ComplexF64, ℂ^2, ℂ^1; unitcell = (2, 2)) for t in peps.A @@ -77,25 +77,33 @@ function tfising_fu(g::Float64, maxiter::Int, Dcut::Int, chi::Int; als = true, u end # do one extra step at the beginning to match benchmark data - t = 0.01 - fu_alg = FullUpdate(; opt_alg, ctm_alg, imaginary_time = false) - time0 = time() - peps, env, = fullupdate(peps, ham, 0.01, fu_alg, env; reconv_interval = 1) + fu_alg = FullUpdate(; opt_alg, ctm_alg, imaginary_time = false, reconverge_interval = 5) + evolver = TimeEvolver(peps, ham, 0.01, 50, fu_alg, env) + peps, env, info = timestep(evolver, peps, env; reconverge_env = true) + # ensure the recoverged environment is updated to the internal state of `evolver` + @test env == evolver.state.env magx = expectation_value(peps, op, env) - time1 = time() - @info Printf.format(formatter, t, real(magx), imag(magx), time1 - time0) + @info Printf.format(formatter, info.t, real(magx), imag(magx)) @test isapprox(magx, data[1, 2]; atol = 0.005) - for count in 1:maxiter - time0 = time() - peps, env, = fullupdate(peps, ham, 0.01, fu_alg, env; reconv_interval = 5) + # reset the number of performed iterations + state0 = evolver.state + evolver.state = (@set state0.iter = 0) + # continue the remaining evolution + count = 2 + for (peps, env, info) in evolver + !evolver.state.reconverged && continue + # 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)." + magx = expectation_value(peps, op, env) - time1 = time() - t += 0.05 - @info Printf.format(formatter, t, real(magx), imag(magx), time1 - time0) - @test isapprox(magx, data[count + 1, 2]; atol = 0.005) + @info Printf.format(formatter, info.t, real(magx), imag(magx)) + @test isapprox(magx, data[count, 2]; atol = 0.005) + count += 1 end return nothing end -tfising_fu(hc, 10, 6, 24; als = false, use_pinv = true) +tfising_fu(hc, 6, 24; als = false, use_pinv = true) diff --git a/test/timeevol/timestep.jl b/test/timeevol/timestep.jl index ede88db61..12ddbef2c 100644 --- a/test/timeevol/timestep.jl +++ b/test/timeevol/timestep.jl @@ -3,9 +3,13 @@ using Random using TensorKit using PEPSKit -function test_timestep(ψ0, env0, H) - trunc = truncerror(; atol = 1.0e-10) & truncrank(4) - alg = SimpleUpdate(; trunc) +@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)) + 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) @@ -13,21 +17,51 @@ function test_timestep(ψ0, env0, H) for iter in 0:(nstep - 1) ψ1, env1, info1 = timestep(evolver, ψ1, env1; iter) end - @info info1 # time_evolve ψ2, env2, info2 = time_evolve(ψ0, H, dt, nstep, alg, env0) - @info info2 + # for-loop syntax + ## manually reset internal state of evolver + evolver.state = PEPSKit.SUState(0, 0.0, ψ0, env0) + ψ3, env3, info3 = nothing, nothing, nothing + for state in evolver + ψ3, env3, info3 = state + end # results should be *exactly* the same - @test ψ1 == ψ2 - @test env1 == env2 - @test info1 == info2 - return nothing + @test ψ1 == ψ2 == ψ3 + @test env1 == env2 == env3 + @test info1 == info2 == info3 end -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)) -env0 = SUWeight(ψ0) - -test_timestep(ψ0, env0, H) +@testset "FullUpdate timestep" begin + Nr, Nc = 2, 2 + H = real(heisenberg_XYZ(ComplexF64, Trivial, InfiniteSquare(Nr, Nc); Jx = 1, Jy = 1, Jz = 1)) + ψ0 = PEPSKit.infinite_temperature_density_matrix(H) + env0 = CTMRGEnv(ones, Float64, InfinitePEPS(ψ0), ℂ^1) + opt_alg = FullEnvTruncation(; trunc = truncerror(; atol = 1.0e-10) & truncrank(4)) + ctm_alg = SequentialCTMRG(; + tol = 1.0e-9, maxiter = 20, verbosity = 2, + trunc = truncerror(; atol = 1.0e-10) & truncrank(8), + projector_alg = :fullinfinite, + ) + alg = FullUpdate(; opt_alg, ctm_alg) + dt, nstep = 1.0e-2, 20 + # manual timestep + evolver = TimeEvolver(ψ0, H, dt, nstep, alg, env0) + ψ1, env1, info1 = deepcopy(ψ0), deepcopy(env0), nothing + for iter in 0:(nstep - 1) + ψ1, env1, info1 = timestep(evolver, ψ1, env1; iter) + end + # time_evolve + ψ2, env2, info2 = time_evolve(ψ0, H, dt, nstep, alg, env0) + # for-loop syntax + ## manually reset internal state of evolver + evolver.state = PEPSKit.FUState(0, 0.0, ψ0, env0, true) + ψ3, env3, info3 = nothing, nothing, nothing + for state in evolver + ψ3, env3, info3 = state + end + # results should be *exactly* the same + @test ψ1 == ψ2 == ψ3 + @test env1 == env2 == env3 + @test info1.wts == info2.wts == info3.wts +end From 8c769a4dce6dbffeb115117570a9fbccc6f00530 Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Wed, 12 Nov 2025 17:18:42 +0800 Subject: [PATCH 50/62] Fix test --- test/bondenv/benv_fu.jl | 4 ++-- test/bondenv/bond_truncate.jl | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/test/bondenv/benv_fu.jl b/test/bondenv/benv_fu.jl index 2bf3b2e38..5fc6c90bf 100644 --- a/test/bondenv/benv_fu.jl +++ b/test/bondenv/benv_fu.jl @@ -29,9 +29,9 @@ function get_hubbard_pepo(t::Float64 = 1.0, U::Float64 = 8.0) pepo = PEPSKit.infinite_temperature_density_matrix(H) wts = SUWeight(pepo) alg = SimpleUpdate(; - trunc = truncerror(; atol = 1.0e-10) & truncrank(4), bipartite = false, check_interval = 100 + trunc = truncerror(; atol = 1.0e-10) & truncrank(4), bipartite = false ) - pepo, = time_evolve(pepo, H, 2.0e-3, 500, alg, wts) + pepo, = time_evolve(pepo, H, 2.0e-3, 500, alg, wts; check_interval = 100) normalize!.(pepo.A, Inf) return pepo end diff --git a/test/bondenv/bond_truncate.jl b/test/bondenv/bond_truncate.jl index 9e8ef41a2..5132b8cba 100644 --- a/test/bondenv/bond_truncate.jl +++ b/test/bondenv/bond_truncate.jl @@ -7,7 +7,7 @@ using LinearAlgebra using KrylovKit Random.seed!(0) -maxiter = 500 +maxiter = 600 check_interval = 20 trunc = truncerror(; atol = 1.0e-10) & truncrank(8) Vext = Vect[FermionParity](0 => 100, 1 => 100) From 1d3e962a5d89bc3dfa5e7bd380cda6b3ca053fc1 Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Wed, 12 Nov 2025 17:32:12 +0800 Subject: [PATCH 51/62] Make timestep test look better --- src/algorithms/time_evolution/simpleupdate.jl | 2 +- test/timeevol/timestep.jl | 34 +++++++++---------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/algorithms/time_evolution/simpleupdate.jl b/src/algorithms/time_evolution/simpleupdate.jl index a184d4e82..ac922fa5d 100644 --- a/src/algorithms/time_evolution/simpleupdate.jl +++ b/src/algorithms/time_evolution/simpleupdate.jl @@ -239,7 +239,7 @@ function MPSKit.time_evolve( time_start = time() check_convergence = (tol > 0) if check_convergence - @assert (it.state.ψ isa InfinitePEPS) && it.alg.imaginary_time + @assert (it.state.ψ isa InfinitePEPS) && it.alg.imaginary_time "Only imaginary time evolution of InfinitePEPS allows convergence checking." end env0, time0 = it.state.env, time() for (ψ, env, info) in it diff --git a/test/timeevol/timestep.jl b/test/timeevol/timestep.jl index ede88db61..c1513bd99 100644 --- a/test/timeevol/timestep.jl +++ b/test/timeevol/timestep.jl @@ -3,9 +3,13 @@ using Random using TensorKit using PEPSKit -function test_timestep(ψ0, env0, H) - trunc = truncerror(; atol = 1.0e-10) & truncrank(4) - alg = SimpleUpdate(; trunc) +@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)) + 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) @@ -13,21 +17,17 @@ function test_timestep(ψ0, env0, H) for iter in 0:(nstep - 1) ψ1, env1, info1 = timestep(evolver, ψ1, env1; iter) end - @info info1 # time_evolve ψ2, env2, info2 = time_evolve(ψ0, H, dt, nstep, alg, env0) - @info info2 + # for-loop syntax + ## manually reset internal state of evolver + evolver.state = PEPSKit.SUState(0, 0.0, ψ0, env0) + ψ3, env3, info3 = nothing, nothing, nothing + for state in evolver + ψ3, env3, info3 = state + end # results should be *exactly* the same - @test ψ1 == ψ2 - @test env1 == env2 - @test info1 == info2 - return nothing + @test ψ1 == ψ2 == ψ3 + @test env1 == env2 == env3 + @test info1 == info2 == info3 end - -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)) -env0 = SUWeight(ψ0) - -test_timestep(ψ0, env0, H) From c323c798eaaa2faae168e97a6a56abe052559167 Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Wed, 12 Nov 2025 23:05:50 +0800 Subject: [PATCH 52/62] Allow complex Trotter time step --- src/algorithms/time_evolution/evoltools.jl | 2 +- src/algorithms/time_evolution/simpleupdate.jl | 29 ++++++++++--------- src/algorithms/time_evolution/time_evolve.jl | 6 ++-- 3 files changed, 19 insertions(+), 18 deletions(-) diff --git a/src/algorithms/time_evolution/evoltools.jl b/src/algorithms/time_evolution/evoltools.jl index d1f66bb53..9659bc95b 100644 --- a/src/algorithms/time_evolution/evoltools.jl +++ b/src/algorithms/time_evolution/evoltools.jl @@ -1,7 +1,7 @@ const InfiniteState = Union{InfinitePEPS, InfinitePEPO} function _get_dt( - state::InfiniteState, dt::Float64, imaginary_time::Bool + state::InfiniteState, dt::Number, imaginary_time::Bool ) dt′ = (state isa InfinitePEPS) ? dt : (dt / 2) if (state isa InfinitePEPO) diff --git a/src/algorithms/time_evolution/simpleupdate.jl b/src/algorithms/time_evolution/simpleupdate.jl index ac922fa5d..00eafb30f 100644 --- a/src/algorithms/time_evolution/simpleupdate.jl +++ b/src/algorithms/time_evolution/simpleupdate.jl @@ -10,7 +10,7 @@ $(TYPEDFIELDS) @kwdef struct SimpleUpdate <: TimeEvolution # Truncation scheme after applying Trotter gates trunc::TruncationStrategy - # Switch for imaginary or real time + # Switch for imaginary time (exp(-H dt)) or real time (exp(-iH dt)) evolution imaginary_time::Bool = true # force usage of 3-site simple update force_3site::Bool = false @@ -22,11 +22,11 @@ $(TYPEDFIELDS) end # internal state of simple update algorithm -struct SUState{S <: InfiniteState, E <: SUWeight} +struct SUState{N <: Number, S <: InfiniteState, E <: SUWeight} # number of performed iterations iter::Int # evolved time - t::Float64 + t::N # PEPS/PEPO ψ::S # SUWeight environment @@ -35,8 +35,8 @@ end """ TimeEvolver( - ψ₀::InfiniteState, H::LocalOperator, dt::Float64, nstep::Int, - alg::SimpleUpdate, env₀::SUWeight; t₀::Float64 = 0.0 + ψ₀::InfiniteState, H::LocalOperator, dt::Number, nstep::Int, + alg::SimpleUpdate, env₀::SUWeight; t₀::Number = 0.0 ) Initialize a TimeEvolver with Hamiltonian `H` and simple update `alg`, @@ -45,8 +45,8 @@ starting from the initial state `ψ₀` and SUWeight environment `env₀`. - The initial (real or imaginary) time is specified by `t₀`. """ function TimeEvolver( - ψ₀::InfiniteState, H::LocalOperator, dt::Float64, nstep::Int, - alg::SimpleUpdate, env₀::SUWeight; t₀::Float64 = 0.0 + ψ₀::InfiniteState, H::LocalOperator, dt::Number, nstep::Int, + alg::SimpleUpdate, env₀::SUWeight; t₀::Number = 0.0 ) # sanity checks _timeevol_sanity_check(ψ₀, physicalspace(H), alg) @@ -217,7 +217,7 @@ function MPSKit.timestep( result = iterate(it, state) if result === nothing @warn "TimeEvolver `it` has already reached the end." - return ψ, env, (; t, ϵ = NaN) + return nothing else return first(result) end @@ -251,8 +251,9 @@ function MPSKit.time_evolve( if showinfo @info "Space of x-weight at [1, 1] = $(space(env[1, 1, 1], 1))" @info @sprintf( - "SU iter %-7d: dt = %.0e, |Δλ| = %.3e. Time = %.3f s/it", - iter, it.dt, diff, time1 - time0 + "SU iter %-7d: dt = %s, |Δλ| = %.3e. Time = %.3f s/it", + # using `string` since `dt` can be complex + iter, string(it.dt), diff, time1 - time0 ) end if check_convergence @@ -278,8 +279,8 @@ end """ time_evolve( ψ₀::Union{InfinitePEPS, InfinitePEPO}, H::LocalOperator, - dt::Float64, nstep::Int, alg::SimpleUpdate, env₀::SUWeight; - tol::Float64 = 0.0, t₀::Float64 = 0.0, check_interval::Int = 500 + dt::Number, nstep::Int, alg::SimpleUpdate, env₀::SUWeight; + tol::Float64 = 0.0, t₀::Number = 0.0, check_interval::Int = 500 ) -> (ψ, env, info) Perform time evolution on the initial state `ψ₀` and initial environment `env₀` @@ -293,9 +294,9 @@ with Hamiltonian `H`, using SimpleUpdate algorithm `alg`, time step `dt` for including the time evolved since `ψ₀`. """ function MPSKit.time_evolve( - ψ₀::InfiniteState, H::LocalOperator, dt::Float64, nstep::Int, + ψ₀::InfiniteState, H::LocalOperator, dt::Number, nstep::Int, alg::SimpleUpdate, env₀::SUWeight; - tol::Float64 = 0.0, t₀::Float64 = 0.0, check_interval::Int = 500 + tol::Float64 = 0.0, t₀::Number = 0.0, check_interval::Int = 500 ) it = TimeEvolver(ψ₀, H, dt, nstep, alg, env₀; t₀) return time_evolve(it; tol, check_interval) diff --git a/src/algorithms/time_evolution/time_evolve.jl b/src/algorithms/time_evolution/time_evolve.jl index d1fb7eb0d..6b778a203 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, H, G, S} + mutable struct TimeEvolver{TE <: TimeEvolution, N <: Number, G, S} Iterator for Trotter-based time evolution of InfinitePEPS or InfinitePEPO. @@ -14,11 +14,11 @@ Iterator for Trotter-based time evolution of InfinitePEPS or InfinitePEPO. $(TYPEDFIELDS) """ -mutable struct TimeEvolver{TE <: TimeEvolution, G, S} +mutable struct TimeEvolver{TE <: TimeEvolution, N <: Number, G, S} # Time evolution algorithm alg::TE # Trotter time step - dt::Float64 + dt::N # Maximal iteration steps nstep::Int # Trotter gates From f78268d45e61a8bfb800f9753324a0dbcbb23101 Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Wed, 12 Nov 2025 23:10:27 +0800 Subject: [PATCH 53/62] Reorder parameters of SUState --- src/algorithms/time_evolution/simpleupdate.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/algorithms/time_evolution/simpleupdate.jl b/src/algorithms/time_evolution/simpleupdate.jl index 00eafb30f..aa72b23bf 100644 --- a/src/algorithms/time_evolution/simpleupdate.jl +++ b/src/algorithms/time_evolution/simpleupdate.jl @@ -22,7 +22,7 @@ $(TYPEDFIELDS) end # internal state of simple update algorithm -struct SUState{N <: Number, S <: InfiniteState, E <: SUWeight} +struct SUState{S <: InfiniteState, E <: SUWeight, N <: Number} # number of performed iterations iter::Int # evolved time From c6dce5c4349a398ee50f9f842b9233b80db9103a Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Thu, 13 Nov 2025 08:47:48 +0800 Subject: [PATCH 54/62] Docstring updates --- src/algorithms/time_evolution/simpleupdate.jl | 46 +++++++++++-------- src/algorithms/time_evolution/time_evolve.jl | 14 +++--- src/algorithms/truncation/bond_truncation.jl | 2 +- 3 files changed, 36 insertions(+), 26 deletions(-) diff --git a/src/algorithms/time_evolution/simpleupdate.jl b/src/algorithms/time_evolution/simpleupdate.jl index aa72b23bf..abb54ea1b 100644 --- a/src/algorithms/time_evolution/simpleupdate.jl +++ b/src/algorithms/time_evolution/simpleupdate.jl @@ -8,28 +8,29 @@ Algorithm struct for simple update (SU) of InfinitePEPS or InfinitePEPO. $(TYPEDFIELDS) """ @kwdef struct SimpleUpdate <: TimeEvolution - # Truncation scheme after applying Trotter gates + "Truncation strategy for bonds updated by Trotter gates" trunc::TruncationStrategy - # Switch for imaginary time (exp(-H dt)) or real time (exp(-iH dt)) evolution + "When true (or false), the Trotter gate is `exp(-H dt)` (or `exp(-iH dt)`)" imaginary_time::Bool = true - # force usage of 3-site simple update + "When true, force the usage of 3-site simple update" force_3site::Bool = false - # ---- only applicable to ground state search ---- - # assume bipartite unit cell structure + "(Only applicable to InfinitePEPS) When true, assume bipartite unit cell structure" bipartite::Bool = false - # ---- only applicable to PEPO evolution ---- - gate_bothsides::Bool = false # when false, purified approach is assumed + "(Only applicable to InfinitePEPO) When true (or false), the PEPO is updated as `exp(-H dt/2) * ρ * exp(-H dt/2)` (or `exp(-H dt) ρ`) in each Trotter step" + gate_bothsides::Bool = false end -# internal state of simple update algorithm +""" +Internal state of simple update algorithm +""" struct SUState{S <: InfiniteState, E <: SUWeight, N <: Number} - # number of performed iterations + "number of performed iterations" iter::Int - # evolved time + "evolved time" t::N - # PEPS/PEPO + "PEPS/PEPO" ψ::S - # SUWeight environment + "SUWeight environment" env::E end @@ -42,7 +43,7 @@ end Initialize a TimeEvolver with Hamiltonian `H` and simple update `alg`, starting from the initial state `ψ₀` and SUWeight environment `env₀`. -- The initial (real or imaginary) time is specified by `t₀`. +- The initial time is specified by `t₀`. """ function TimeEvolver( ψ₀::InfiniteState, H::LocalOperator, dt::Number, nstep::Int, @@ -73,12 +74,13 @@ end """ Simple update of the x-bond between `[r,c]` and `[r,c+1]`. - ``` | | -- T[r,c] -- T[r,c+1] -- | | ``` +When `gate_ax = 1` (or `2`), the gate will be applied to +the codomain (or domain) physicsl legs of `state`. """ function _su_xbond!( state::InfiniteState, gate::AbstractTensorMap{T, S, 2, 2}, env::SUWeight, @@ -117,6 +119,8 @@ Simple update of the y-bond between `[r,c]` and `[r-1,c]`. -- T[r,c] --- | ``` +When `gate_ax = 1` (or `2`), the gate will be applied to +the codomain (or domain) physicsl legs of `state`. """ function _su_ybond!( state::InfiniteState, gate::AbstractTensorMap{T, S, 2, 2}, env::SUWeight, @@ -227,10 +231,14 @@ end time_evolve( it::TimeEvolver{<:SimpleUpdate}; tol::Float64 = 0.0, check_interval::Int = 500 - ) + ) -> (ψ, 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. """ function MPSKit.time_evolve( it::TimeEvolver{<:SimpleUpdate}; @@ -246,7 +254,8 @@ function MPSKit.time_evolve( iter = it.state.iter diff = compare_weights(env0, env) stop = (iter == it.nstep) || (diff < tol) - showinfo = (iter % check_interval == 0) || (iter == 1) || stop + 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))" @@ -289,9 +298,10 @@ with Hamiltonian `H`, using SimpleUpdate algorithm `alg`, time step `dt` for - Setting `tol > 0` enables convergence check (for imaginary time evolution of InfinitePEPS only). For other usages it should not be changed. -- Using `t₀` to specify the initial (real or imaginary) time of `ψ₀`. +- Use `t₀` to specify the initial time of `ψ₀`. +- `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, - including the time evolved since `ψ₀`. + including the time `info.t` evolved since `ψ₀`. """ function MPSKit.time_evolve( ψ₀::InfiniteState, H::LocalOperator, dt::Number, nstep::Int, diff --git a/src/algorithms/time_evolution/time_evolve.jl b/src/algorithms/time_evolution/time_evolve.jl index 6b778a203..5071bc1ae 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, N <: Number, G, S} + mutable struct TimeEvolver{TE <: TimeEvolution, G, S, N <: Number} Iterator for Trotter-based time evolution of InfinitePEPS or InfinitePEPO. @@ -14,16 +14,16 @@ Iterator for Trotter-based time evolution of InfinitePEPS or InfinitePEPO. $(TYPEDFIELDS) """ -mutable struct TimeEvolver{TE <: TimeEvolution, N <: Number, G, S} - # Time evolution algorithm +mutable struct TimeEvolver{TE <: TimeEvolution, G, S, N <: Number} + "Time evolution algorithm (currently supported: `SimpleUpdate`)" alg::TE - # Trotter time step + "Trotter time step" dt::N - # Maximal iteration steps + "The number of iteration steps" nstep::Int - # Trotter gates + "Trotter gates" gate::G - # PEPS/PEPO (and environment) + "PEPS/PEPO and its environment" state::S end diff --git a/src/algorithms/truncation/bond_truncation.jl b/src/algorithms/truncation/bond_truncation.jl index 25fcc190f..b2f54ac1c 100644 --- a/src/algorithms/truncation/bond_truncation.jl +++ b/src/algorithms/truncation/bond_truncation.jl @@ -15,7 +15,7 @@ The truncation algorithm can be constructed from the following keyword arguments * `trunc::TruncationStrategy`: SVD truncation strategy when initilizing the truncated tensors connected by the bond. * `maxiter::Int=50` : Maximal number of ALS iterations. -* `tol::Float64=1e-15` : ALS converges when fidelity change between two FET iterations is smaller than `tol`. +* `tol::Float64=1e-15` : ALS converges when fidelity change 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 From 0545de0e81e444284ea8190857ca68e7ff9b8275 Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Thu, 13 Nov 2025 15:42:59 +0800 Subject: [PATCH 55/62] Change `gate_bothsides` to `purified` --- src/algorithms/time_evolution/evoltools.jl | 6 ++++++ src/algorithms/time_evolution/simpleupdate.jl | 10 +++++++--- src/algorithms/time_evolution/simpleupdate3site.jl | 6 +++--- src/algorithms/time_evolution/time_evolve.jl | 4 ++-- test/examples/j1j2_finiteT.jl | 6 +++--- test/examples/tf_ising_finiteT.jl | 4 ++-- 6 files changed, 23 insertions(+), 13 deletions(-) diff --git a/src/algorithms/time_evolution/evoltools.jl b/src/algorithms/time_evolution/evoltools.jl index 9659bc95b..e2bd0088c 100644 --- a/src/algorithms/time_evolution/evoltools.jl +++ b/src/algorithms/time_evolution/evoltools.jl @@ -1,8 +1,14 @@ const InfiniteState = Union{InfinitePEPS, InfinitePEPO} +""" +Process the Trotter time step `dt` according to the intended usage. +""" function _get_dt( state::InfiniteState, dt::Number, imaginary_time::Bool ) + # PEPS update: exp(-H dt)|ψ⟩ + # PEPO update (purified): exp(-H dt/2)|ρ⟩ + # PEPO update (not purified): exp(-H dt/2) ρ exp(-H dt/2) dt′ = (state isa InfinitePEPS) ? dt : (dt / 2) if (state isa InfinitePEPO) @assert size(state)[3] == 1 diff --git a/src/algorithms/time_evolution/simpleupdate.jl b/src/algorithms/time_evolution/simpleupdate.jl index abb54ea1b..26869bf0e 100644 --- a/src/algorithms/time_evolution/simpleupdate.jl +++ b/src/algorithms/time_evolution/simpleupdate.jl @@ -16,8 +16,12 @@ $(TYPEDFIELDS) force_3site::Bool = false "(Only applicable to InfinitePEPS) When true, assume bipartite unit cell structure" bipartite::Bool = false - "(Only applicable to InfinitePEPO) When true (or false), the PEPO is updated as `exp(-H dt/2) * ρ * exp(-H dt/2)` (or `exp(-H dt) ρ`) in each Trotter step" - gate_bothsides::Bool = false + "(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 + `ρ(t + dt) = exp(-H dt/2) ρ(t) exp(-H dt/2)`." + purified::Bool = true end """ @@ -158,7 +162,7 @@ function su_iter( ) Nr, Nc, = size(state) state2, env2, ϵ = deepcopy(state), deepcopy(env), 0.0 - gate_axs = alg.gate_bothsides ? (1:2) : (1:1) + gate_axs = alg.purified ? (1:1) : (1:2) for r in 1:Nr, c in 1:Nc term = get_gateterm(gate, (CartesianIndex(r, c), CartesianIndex(r, c + 1))) trunc = truncation_strategy(alg.trunc, 1, r, c) diff --git a/src/algorithms/time_evolution/simpleupdate3site.jl b/src/algorithms/time_evolution/simpleupdate3site.jl index 9be45a7fe..7b80acde3 100644 --- a/src/algorithms/time_evolution/simpleupdate3site.jl +++ b/src/algorithms/time_evolution/simpleupdate3site.jl @@ -462,7 +462,7 @@ end function _su3site_se!( state::InfiniteState, gs::Vector{T}, env::SUWeight, row::Int, col::Int, truncs::Vector{E}; - gate_bothsides::Bool = true + purified::Bool = true ) where {T <: AbstractTensorMap, E <: TruncationStrategy} Nr, Nc = size(state) @assert 1 <= row <= Nr && 1 <= col <= Nc @@ -477,7 +477,7 @@ function _su3site_se!( # weights in the cluster wt_idxs = ((1, row, col), (2, row, cp1)) # apply gate MPOs - gate_axs = gate_bothsides ? (1:2) : (1:1) + gate_axs = purified ? (1:1) : (1:2) ϵs = nothing for gate_ax in gate_axs _apply_gatempo!(Ms, gs; gate_ax) @@ -525,7 +525,7 @@ function su_iter( truncation_strategy(trunc, 1, r, c) truncation_strategy(trunc, 2, r, _next(c, Nc)) ] - ϵ = _su3site_se!(state2, gs, env2, r, c, truncs; alg.gate_bothsides) + ϵ = _su3site_se!(state2, gs, env2, r, c, truncs; alg.purified) end state2, env2 = rotl90(state2), rotl90(env2) trunc = rotl90(trunc) diff --git a/src/algorithms/time_evolution/time_evolve.jl b/src/algorithms/time_evolution/time_evolve.jl index 5071bc1ae..9ec72947d 100644 --- a/src/algorithms/time_evolution/time_evolve.jl +++ b/src/algorithms/time_evolution/time_evolve.jl @@ -35,8 +35,8 @@ function _timeevol_sanity_check( Nr, Nc, = size(ψ₀) @assert (Nr >= 2 && Nc >= 2) "Unit cell size for simple update should be no smaller than (2, 2)." @assert Pspaces == physicalspace(ψ₀) "Physical spaces of `ψ₀` do not match `Pspaces`." - if hasfield(typeof(alg), :gate_bothsides) && alg.gate_bothsides - @assert ψ₀ isa InfinitePEPO "alg.gate_bothsides = true is only compatible with PEPO." + if hasfield(typeof(alg), :purified) && !alg.purified + @assert ψ₀ isa InfinitePEPO "alg.purified = false is only applicable to PEPO." end if hasfield(typeof(alg), :bipartite) && alg.bipartite @assert Nr == Nc == 2 "`bipartite = true` requires 2 x 2 unit cell size." diff --git a/test/examples/j1j2_finiteT.jl b/test/examples/j1j2_finiteT.jl index 1ec19bfb9..707d3605b 100644 --- a/test/examples/j1j2_finiteT.jl +++ b/test/examples/j1j2_finiteT.jl @@ -32,7 +32,7 @@ check_interval = 100 # PEPO approach dt, nstep = 1.0e-3, 600 -alg = SimpleUpdate(; trunc = trunc_pepo, gate_bothsides = true) +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) @@ -42,9 +42,9 @@ energy = expectation_value(pepo, ham, env) / (Nr * Nc) @test energy ≈ bm[2] atol = 5.0e-3 # PEPS (purified PEPO) approach -alg = SimpleUpdate(; trunc = trunc_pepo, gate_bothsides = false) +alg = SimpleUpdate(; trunc = trunc_pepo, purified = true) evolver = TimeEvolver(pepo0, ham, dt, nstep, alg, wts0) -pepo, wts, = time_evolve(evolver; check_interval) +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)" diff --git a/test/examples/tf_ising_finiteT.jl b/test/examples/tf_ising_finiteT.jl index 109b41097..bb2185c56 100644 --- a/test/examples/tf_ising_finiteT.jl +++ b/test/examples/tf_ising_finiteT.jl @@ -60,7 +60,7 @@ dt, nstep = 1.0e-3, 400 # when g = 2, β = 0.4 and 2β = 0.8 belong to two phases (without and with nonzero σᶻ) # PEPO approach: results at β, or T = 2.5 -alg = SimpleUpdate(; trunc = trunc_pepo, gate_bothsides = true) +alg = SimpleUpdate(; trunc = trunc_pepo, purified = false) pepo, wts, info = time_evolve(pepo0, ham, dt, nstep, alg, wts0) env = converge_env(InfinitePartitionFunction(pepo), 16) result_β = measure_mag(pepo, env) @@ -77,7 +77,7 @@ result_2β = measure_mag(pepo, env) @test isapprox(abs.(result_2β), bm_2β, rtol = 1.0e-4) # Purification approach: results at 2β, or T = 1.25 -alg = SimpleUpdate(; trunc = trunc_pepo, gate_bothsides = false) +alg = SimpleUpdate(; trunc = trunc_pepo, purified = true) pepo, wts, info = time_evolve(pepo0, ham, dt, 2 * nstep, alg, wts0) env = converge_env(InfinitePEPS(pepo), 8) result_2β′ = measure_mag(pepo, env; purified = true) From c3de149261aa7eb34bb8c15304a9d2856e28c72c Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Thu, 13 Nov 2025 15:44:35 +0800 Subject: [PATCH 56/62] Remove `rand` from finite-T SU tests --- test/examples/j1j2_finiteT.jl | 5 +---- test/examples/tf_ising_finiteT.jl | 3 --- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/test/examples/j1j2_finiteT.jl b/test/examples/j1j2_finiteT.jl index 707d3605b..991521c35 100644 --- a/test/examples/j1j2_finiteT.jl +++ b/test/examples/j1j2_finiteT.jl @@ -1,12 +1,9 @@ using Test -using Random using LinearAlgebra using TensorKit import MPSKitModels: σˣ, σᶻ using PEPSKit -Random.seed!(10235876) - # Benchmark energy from high-temperature expansion # at β = 0.3, 0.6 # Physical Review B 86, 045139 (2012) Fig. 15-16 @@ -14,7 +11,7 @@ bm = [-0.1235, -0.213] function converge_env(state, χ::Int) trunc1 = truncrank(χ) & truncerror(; atol = 1.0e-12) - env0 = CTMRGEnv(randn, Float64, state, Vect[SU2Irrep](0 => 1)) + env0 = CTMRGEnv(ones, Float64, state, Vect[SU2Irrep](0 => 1)) env, = leading_boundary(env0, state; alg = :sequential, trunc = trunc1, tol = 1.0e-10) return env end diff --git a/test/examples/tf_ising_finiteT.jl b/test/examples/tf_ising_finiteT.jl index bb2185c56..37176cc51 100644 --- a/test/examples/tf_ising_finiteT.jl +++ b/test/examples/tf_ising_finiteT.jl @@ -1,12 +1,9 @@ using Test -using Random using LinearAlgebra using TensorKit import MPSKitModels: σˣ, σᶻ using PEPSKit -Random.seed!(10235876) - # Benchmark data of [σx, σz] from HOTRG # Physical Review B 86, 045139 (2012) Fig. 15-16 bm_β = [0.5632, 0.0] From a4c4ec88b512495de8f24d878a20adcd44b9f4a8 Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Fri, 14 Nov 2025 09:39:15 +0800 Subject: [PATCH 57/62] Remove `rand` from real time FU test --- test/timeevol/tf_ising_realtime.jl | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/test/timeevol/tf_ising_realtime.jl b/test/timeevol/tf_ising_realtime.jl index 6c9aafffd..4fb05d1a5 100644 --- a/test/timeevol/tf_ising_realtime.jl +++ b/test/timeevol/tf_ising_realtime.jl @@ -3,9 +3,7 @@ using TensorKit import MPSKitModels: S_zz, σˣ using PEPSKit using Printf -using Random using Accessors: @set -Random.seed!(0) const hc = 3.044382 const formatter = Printf.Format("t = %.2f, ⟨σˣ⟩ = %.7e + %.7e im.") @@ -46,7 +44,7 @@ end function tfising_fu(g::Float64, Dcut::Int, chi::Int; als = true, use_pinv = true) # the fully polarized state - peps = InfinitePEPS(randn, ComplexF64, ℂ^2, ℂ^1; unitcell = (2, 2)) + peps = InfinitePEPS(zeros, ComplexF64, ℂ^2, ℂ^1; unitcell = (2, 2)) for t in peps.A t[1, 1, 1, 1, 1] = 1.0 t[2, 1, 1, 1, 1] = 1.0 From 79ae4ff7fc04b1bcafdbdf059b8255eb51192b25 Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Fri, 14 Nov 2025 10:06:15 +0800 Subject: [PATCH 58/62] Separate convergence checking enabled FU, add finer_env_update --- src/algorithms/time_evolution/fullupdate.jl | 251 +++++++++++++------- test/timeevol/tf_ising_realtime.jl | 23 +- 2 files changed, 176 insertions(+), 98 deletions(-) diff --git a/src/algorithms/time_evolution/fullupdate.jl b/src/algorithms/time_evolution/fullupdate.jl index 4c0f982aa..5797073a5 100644 --- a/src/algorithms/time_evolution/fullupdate.jl +++ b/src/algorithms/time_evolution/fullupdate.jl @@ -10,52 +10,58 @@ $(TYPEDFIELDS) Reference: Physical Review B 92, 035142 (2015) """ @kwdef struct FullUpdate <: TimeEvolution - # Fix gauge of bond environment + "When true, fix gauge of bond environment" fixgauge::Bool = true - # Switch for imaginary or real time + "When true (or false), the Trotter gate is `exp(-H dt)` (or `exp(-iH dt)`)" imaginary_time::Bool = true - # Number of iterations without fully reconverging the environment + "Number of iterations without fully reconverging the environment" reconverge_interval::Int = 5 - # Bond truncation algorithm after applying time evolution gate - opt_alg::Union{ALSTruncation, FullEnvTruncation} = ALSTruncation(; - trunc = truncerror(; atol = 1.0e-10) - ) - # CTMRG algorithm to reconverge environment. Its `projector_alg` is also used for the fast update of the environment after each FU iteration - ctm_alg::CTMRGAlgorithm = SequentialCTMRG(; + "Do column and row moves after updating every single bond, + instaed of waiting until an entire row/column of bonds are updated" + finer_env_update::Bool = false + "Bond truncation algorithm after applying time evolution gate" + opt_alg::Union{ALSTruncation, FullEnvTruncation} = + FullEnvTruncation(; trunc = truncerror(; atol = 1.0e-10)) + "CTMRG algorithm to reconverge environment. + Its `projector_alg` is also used for the fast update of + the environment after each FU iteration" + ctm_alg::SequentialCTMRG = SequentialCTMRG(; tol = 1.0e-9, maxiter = 20, verbosity = 1, trunc = truncerror(; atol = 1.0e-10), projector_alg = :fullinfinite, ) end -# internal state of full update algorithm -struct FUState{S <: InfiniteState, E <: CTMRGEnv} - # number of performed iterations +""" +Internal state of full update algorithm +""" +struct FUState{S <: InfiniteState, E <: CTMRGEnv, N <: Number} + "number of performed iterations" iter::Int - # evolved time - t::Float64 - # PEPS/PEPO + "evolved time" + t::N + "PEPS/PEPO" ψ::S - # CTMRG environment + "CTMRG environment" env::E - # whether the current environment is reconverged + "whether the current environment is reconverged" reconverged::Bool end """ TimeEvolver( - ψ₀::InfiniteState, H::LocalOperator, dt::Float64, nstep::Int, - alg::FullUpdate, env₀::CTMRGEnv; t₀::Float64 = 0.0 + ψ₀::InfiniteState, H::LocalOperator, dt::Number, nstep::Int, + alg::FullUpdate, env₀::CTMRGEnv; t₀::Number = 0.0 ) Initialize a TimeEvolver with Hamiltonian `H` and full update `alg`, starting from the initial state `ψ₀` and CTMRG environment `env₀`. -- The initial (real or imaginary) time is specified by `t₀`. +- The initial time is specified by `t₀`. """ function TimeEvolver( - ψ₀::InfiniteState, H::LocalOperator, dt::Float64, nstep::Int, - alg::FullUpdate, env₀::CTMRGEnv; t₀::Float64 = 0.0 + ψ₀::InfiniteState, H::LocalOperator, dt::Number, nstep::Int, + alg::FullUpdate, env₀::CTMRGEnv; t₀::Number = 0.0 ) _timeevol_sanity_check(ψ₀, physicalspace(H), alg) dt′ = _get_dt(ψ₀, dt, alg.imaginary_time) @@ -78,24 +84,23 @@ function _fu_xbond!( benv = bondenv_fu(row, col, X, Y, env) Z = positive_approx(benv) @debug "cond(benv) before gauge fix: $(LinearAlgebra.cond(Z' * Z))" - # fix gauge + # fix gauge of bond environment if alg.fixgauge 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(Z' * Z))" + @debug "cond(benv) after gauge fix: $(LinearAlgebra.cond(benv))" + else + benv = Z' * Z end - benv = Z' * Z # apply gate a, s, b, = _apply_gate(a, b, gate, truncerror(; atol = 1.0e-15)) - # optimize a, b + # optimize a, b; s is already normalized a, s, b, info = bond_truncate(a, b, benv, alg.opt_alg) - normalize!(a, Inf) - normalize!(b, Inf) A, B = _qr_bond_undo(X, a, b, Y) normalize!(A, Inf) normalize!(B, Inf) - normalize!(s, Inf) state.A[row, col], state.A[row, cp1] = A, B return s, info end @@ -113,21 +118,32 @@ function _fu_column!( Nr, Nc, = size(state) fid = 1.0 wts_col = Vector{PEPSWeight}(undef, Nr) - for row in 1:Nr + colmove_alg = SequentialCTMRG() + @reset colmove_alg.projector_alg = alg.ctm_alg.projector_alg + env2 = deepcopy(env) + for row′ in 1:Nr + # update row order: 1, Nr, 2, Nr-1, 3, Nr-2, ... + row = isodd(row′) ? ((row′ + 1) ÷ 2) : (Nr - (row′ ÷ 2) + 1) term = get_gateterm(gate, (CartesianIndex(row, col), CartesianIndex(row, col + 1))) wts_col[row], info = _fu_xbond!(state, term, env, row, col, alg) fid = min(fid, info.fid) + if alg.finer_env_update + network = isa(state, InfinitePEPS) ? InfiniteSquareNetwork(state) : + InfiniteSquareNetwork(InfinitePEPS(state)) + # match bonds between updated state tensors and the environment + env2, info = ctmrg_leftmove(col, network, env2, colmove_alg) + env2, info = ctmrg_rightmove(_next(col, Nc), network, env2, colmove_alg) + # update environment around the bond to be updated next + env2, info = ctmrg_upmove(row, network, env2, colmove_alg) + env2, info = ctmrg_downmove(row, network, env2, colmove_alg) + end end - # update 2-layer CTMRGEnv - network = if isa(state, InfinitePEPS) - InfiniteSquareNetwork(state) - else - InfiniteSquareNetwork(InfinitePEPS(state)) + if !alg.finer_env_update + network = isa(state, InfinitePEPS) ? InfiniteSquareNetwork(state) : + InfiniteSquareNetwork(InfinitePEPS(state)) + env2, info = ctmrg_leftmove(col, network, env, colmove_alg) + env2, info = ctmrg_rightmove(_next(col, Nc), network, env2, colmove_alg) end - colmove_alg = SequentialCTMRG() - @reset colmove_alg.projector_alg = alg.ctm_alg.projector_alg - env2, info = ctmrg_leftmove(col, network, env, colmove_alg) - env2, info = ctmrg_rightmove(_next(col, Nc), network, env2, colmove_alg) for c in [col, _next(col, Nc)] env.corners[:, :, c] = env2.corners[:, :, c] env.edges[:, :, c] = env2.edges[:, :, c] @@ -220,30 +236,33 @@ function MPSKit.timestep( end end -function MPSKit.time_evolve( - it::TimeEvolver{<:FullUpdate}; - tol::Float64 = 0.0, H::Union{Nothing, LocalOperator} = nothing +""" +Imaginary time evolution of InfinitePEPS with convergence checking +""" +function _time_evolve_gs( + it::TimeEvolver{<:FullUpdate}, tol::Float64, H::LocalOperator ) time_start = time() - check_convergence = (tol > 0) - if check_convergence - @assert (it.state.ψ isa InfinitePEPS) && it.alg.imaginary_time "Only imaginary time evolution of InfinitePEPS allows convergence checking." - @assert H isa LocalOperator "Hamiltonian should be provided for convergence checking in full update." - end + @assert (it.state.ψ isa InfinitePEPS) && it.alg.imaginary_time "Only imaginary time evolution of InfinitePEPS allows convergence checking." time0 = time() + # backup variables iter0, t0 = it.state.iter, it.state.t ψ0, env0, info0 = it.state.ψ, it.state.env, nothing - energy0 = check_convergence ? - expectation_value(ψ0, H, ψ0, env0) / prod(size(ψ0)) : NaN - if check_convergence - @info "FU: initial state energy = $(energy0)." - end + energy0 = expectation_value(ψ0, H, ψ0, env0) / prod(size(ψ0)) + @info "FU: initial state energy = $(energy0)." for (ψ, env, info) in it + iter = it.state.iter + if iter == 1 + # reconverge for the 1st step + network = isa(ψ, InfinitePEPS) ? ψ : InfinitePEPS(ψ) + env, = leading_boundary(env, network, it.alg.ctm_alg) + # update internal state of `it` + # TODO: more elegant to use `Accessors.@set` + it.state = FUState(iter, it.state.t, it.state.ψ, env, true) + end !(it.state.reconverged) && continue # do the following only when env has been reconverged - iter = it.state.iter - energy = check_convergence ? - expectation_value(ψ, H, ψ, env) / prod(size(ψ)) : NaN + energy = expectation_value(ψ, H, ψ, env) / prod(size(ψ)) diff = energy - energy0 stop = (iter == it.nstep) || (diff < 0 && abs(diff) < tol) || (diff > 0) showinfo = (iter == 1) || it.state.reconverged || stop @@ -253,35 +272,25 @@ function MPSKit.time_evolve( corner_dim = dim.(space(corner, ax) for ax in 1:numind(corner)) @info "Dimension of env.corner[1, 1, 1] = $(corner_dim)." Δλ = (info0 === nothing) ? NaN : compare_weights(info.wts, info0.wts) - if check_convergence - @info @sprintf( - "FU iter %-6d: E = %.5f, ΔE = %.3e, |Δλ| = %.3e. Time: %.2f s", - it.state.iter, energy, diff, Δλ, time1 - time0 - ) - else - @info @sprintf( - "FU iter %d: t = %.2e, |Δλ| = %.3e. Time: %.2f s", - it.state.iter, it.state.t, Δλ, time1 - time0 - ) - end + @info @sprintf( + "FU iter %-6d: E = %.5f, ΔE = %.3e, |Δλ| = %.3e. Time: %.2f s", + it.state.iter, energy, diff, Δλ, time1 - time0 + ) end - if check_convergence - if (diff < 0 && abs(diff) < tol) - @info "FU: energy has converged." - return ψ, env, info - end - if diff > 0 - @warn "FU: energy has increased from last check. Abort evolution and return results from last check." - # also reset internal state of `it` to last check - it.state = FUState(iter0, t0, ψ0, env0, true) - return ψ0, env0, info0 - end - if iter == it.nstep - @info "FU: energy has not converged." - return ψ, env, info - end + if (diff < 0 && abs(diff) < tol) + @info "FU: energy has converged." + end + if diff > 0 + @warn "FU: energy has increased from last check. Abort evolution and return results from last check." + ψ, env, info, energy = ψ0, env0, info0, energy0 + # also reset internal state of `it` to last check + it.state = FUState(iter0, t0, ψ0, env0, true) + end + if iter == it.nstep + @info "FU: energy has not converged." end if stop + @assert it.state.reconverged time_end = time() @info @sprintf("Full update finished. Total time elasped: %.2f s", time_end - time_start) return ψ, env, info @@ -296,12 +305,86 @@ function MPSKit.time_evolve( end """ -Full update an infinite PEPS with nearest neighbor Hamiltonian. +Time evolution without convergence checking +""" +function _time_evolve(it::TimeEvolver{<:FullUpdate}) + time_start = time0 = time() + info0 = nothing + for (ψ, env, info) in it + iter = it.state.iter + !(it.state.reconverged) && continue + # do the following only when env has been reconverged + stop = (iter == it.nstep) + showinfo = (iter == 1) || it.state.reconverged || stop + time1 = time() + if showinfo + 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)." + Δλ = (info0 === nothing) ? NaN : compare_weights(info.wts, info0.wts) + @info @sprintf( + "FU iter %d: t = %.2e, |Δλ| = %.3e. Time: %.2f s", + it.state.iter, it.state.t, Δλ, time1 - time0 + ) + end + if stop + @assert it.state.reconverged + time_end = time() + @info @sprintf("Full update finished. Total time elasped: %.2f s", time_end - time_start) + return ψ, env, info + else + info0 = info + end + time0 = time() + end + return +end + +""" + time_evolve( + it::TimeEvolver{<:FullUpdate}; + tol::Float64 = 0.0, H::Union{Nothing, LocalOperator} = nothing + ) + +Perform time evolution to the end of FullUpdate TimeEvolver `it`, +or until convergence of energy set by a positive `tol`. + +- Setting `tol > 0` enables convergence check (for imaginary time evolution of InfinitePEPS only). The Hamiltonian `H` should also be provided to measure the energy. + For other usages they should not be changed. +""" +function MPSKit.time_evolve( + it::TimeEvolver{<:FullUpdate}; + tol::Float64 = 0.0, H::Union{Nothing, LocalOperator} = nothing + ) + if tol > 0 + return _time_evolve_gs(it, tol, H) + else + @assert tol == 0 + return _time_evolve(it) + end +end + +""" + MPSKit.time_evolve( + ψ₀::InfiniteState, H::LocalOperator, dt::Number, nstep::Int, + alg::FullUpdate, env₀::CTMRGEnv; + tol::Float64 = 0.0, t₀::Number = 0.0 + ) -> (ψ, env, info) + +Perform time evolution on the initial state `ψ₀` and initial environment `env₀` +with Hamiltonian `H`, using FullUpdate algorithm `alg`, time step `dt` for +`nstep` number of steps. + +- Setting `tol > 0` enables convergence check (for imaginary time evolution of InfinitePEPS only). + For other usages it should not be changed. +- Use `t₀` to specify the initial time of `ψ₀`. +- `info` is a NamedTuple containing information of the evolution, + including the time `info.t` evolved since `ψ₀`. """ function MPSKit.time_evolve( - ψ₀::InfiniteState, H::LocalOperator, dt::Float64, nstep::Int, + ψ₀::InfiniteState, H::LocalOperator, dt::Number, nstep::Int, alg::FullUpdate, env₀::CTMRGEnv; - tol::Float64 = 0.0, t₀::Float64 = 0.0 + tol::Float64 = 0.0, t₀::Number = 0.0 ) it = TimeEvolver(ψ₀, H, dt, nstep, alg, env₀; t₀) return time_evolve(it; tol, H) diff --git a/test/timeevol/tf_ising_realtime.jl b/test/timeevol/tf_ising_realtime.jl index 4fb05d1a5..d1cec22db 100644 --- a/test/timeevol/tf_ising_realtime.jl +++ b/test/timeevol/tf_ising_realtime.jl @@ -54,26 +54,21 @@ function tfising_fu(g::Float64, Dcut::Int, chi::Int; als = true, use_pinv = true ham = tfising(ComplexF64, Trivial, InfiniteSquare(2, 2); J = 1.0, g = g) trunc_peps = truncerror(; atol = 1.0e-10) & truncrank(Dcut) - trunc_env = truncerror(; atol = 1.0e-10) & truncrank(chi) - env = CTMRGEnv(ones, ComplexF64, peps, ℂ^1) - env, = leading_boundary( - env, peps; alg = :sequential, - tol = 1.0e-10, verbosity = 2, trunc = trunc_env - ) - - ctm_alg = SequentialCTMRG(; - tol = 1.0e-9, - maxiter = 50, - verbosity = 2, - trunc = trunc_env, - projector_alg = :fullinfinite, - ) opt_alg = if als ALSTruncation(; trunc = trunc_peps, tol = 1.0e-10, use_pinv) else FullEnvTruncation(; trunc = trunc_peps, tol = 1.0e-10) end + 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 + ) + + env = CTMRGEnv(ones, ComplexF64, peps, ℂ^1) + env, = leading_boundary(env, peps, ctm_alg) + # do one extra step at the beginning to match benchmark data fu_alg = FullUpdate(; opt_alg, ctm_alg, imaginary_time = false, reconverge_interval = 5) evolver = TimeEvolver(peps, ham, 0.01, 50, fu_alg, env) From 9649c47ad74a7d1a9b04e603cee0907fde98e9bf Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Mon, 17 Nov 2025 22:08:57 +0800 Subject: [PATCH 59/62] Reduce unicode usage --- src/algorithms/time_evolution/fullupdate.jl | 98 ++++++++++----------- 1 file changed, 49 insertions(+), 49 deletions(-) diff --git a/src/algorithms/time_evolution/fullupdate.jl b/src/algorithms/time_evolution/fullupdate.jl index 5797073a5..fe959e4a0 100644 --- a/src/algorithms/time_evolution/fullupdate.jl +++ b/src/algorithms/time_evolution/fullupdate.jl @@ -41,7 +41,7 @@ struct FUState{S <: InfiniteState, E <: CTMRGEnv, N <: Number} "evolved time" t::N "PEPS/PEPO" - ψ::S + psi::S "CTMRG environment" env::E "whether the current environment is reconverged" @@ -50,23 +50,23 @@ end """ TimeEvolver( - ψ₀::InfiniteState, H::LocalOperator, dt::Number, nstep::Int, - alg::FullUpdate, env₀::CTMRGEnv; t₀::Number = 0.0 + psi0::InfiniteState, H::LocalOperator, dt::Number, nstep::Int, + alg::FullUpdate, env0::CTMRGEnv; t0::Number = 0.0 ) Initialize a TimeEvolver with Hamiltonian `H` and full update `alg`, -starting from the initial state `ψ₀` and CTMRG environment `env₀`. +starting from the initial state `psi0` and CTMRG environment `env0`. -- The initial time is specified by `t₀`. +- The initial time is specified by `t0`. """ function TimeEvolver( - ψ₀::InfiniteState, H::LocalOperator, dt::Number, nstep::Int, - alg::FullUpdate, env₀::CTMRGEnv; t₀::Number = 0.0 + psi0::InfiniteState, H::LocalOperator, dt::Number, nstep::Int, + alg::FullUpdate, env0::CTMRGEnv; t0::Number = 0.0 ) - _timeevol_sanity_check(ψ₀, physicalspace(H), alg) - dt′ = _get_dt(ψ₀, dt, alg.imaginary_time) + _timeevol_sanity_check(psi0, physicalspace(H), alg) + dt′ = _get_dt(psi0, dt, alg.imaginary_time) gate = get_expham(H, dt′ / 2) - state = FUState(0, t₀, ψ₀, env₀, true) + state = FUState(0, t0, psi0, env0, true) return TimeEvolver(alg, dt, nstep, gate, state) end @@ -186,83 +186,83 @@ end function Base.iterate(it::TimeEvolver{<:FullUpdate}, state = it.state) iter, t = state.iter, state.t (iter == it.nstep) && return nothing - ψ, env, wts, fid = fu_iter(state.ψ, it.gate, it.alg, state.env) + psi, env, wts, fid = fu_iter(state.psi, it.gate, it.alg, state.env) iter, t = iter + 1, t + it.dt # reconverge environment for the last step and every `reconverge_interval` steps reconverged = (iter % it.alg.reconverge_interval == 0) || (iter == it.nstep) if reconverged - network = isa(ψ, InfinitePEPS) ? ψ : InfinitePEPS(ψ) + network = isa(psi, InfinitePEPS) ? psi : InfinitePEPS(psi) env, = leading_boundary(env, network, it.alg.ctm_alg) end # update internal state - it.state = FUState(iter, t, ψ, env, reconverged) + it.state = FUState(iter, t, psi, env, reconverged) info = (; t, wts, fid) - return (ψ, env, info), it.state + return (psi, env, info), it.state end """ timestep( - it::TimeEvolver{<:FullUpdate}, ψ::InfiniteState, env::CTMRGEnv; + it::TimeEvolver{<:FullUpdate}, psi::InfiniteState, env::CTMRGEnv; iter::Int = it.state.iter, t::Float64 = it.state.t - ) -> (ψ, env, info) + ) -> (psi, env, info) Given the TimeEvolver iterator `it`, perform one step of time evolution -on the input state `ψ` and its environment `env`. +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`. - Use `reconverge_env` to force reconverging the obtained environment. """ function MPSKit.timestep( - it::TimeEvolver{<:FullUpdate}, ψ::InfiniteState, env::CTMRGEnv; + it::TimeEvolver{<:FullUpdate}, psi::InfiniteState, env::CTMRGEnv; iter::Int = it.state.iter, t::Float64 = it.state.t, reconverge_env::Bool = false ) - _timeevol_sanity_check(ψ, physicalspace(it.state.ψ), it.alg) - state = FUState(iter, t, ψ, env, true) + _timeevol_sanity_check(psi, physicalspace(it.state.psi), it.alg) + state = FUState(iter, t, psi, env, true) result = iterate(it, state) if result === nothing @warn "TimeEvolver `it` has already reached the end." return nothing else - ψ, env, info = first(result) + psi, env, info = first(result) if reconverge_env && !(it.state.reconverged) - network = isa(ψ, InfinitePEPS) ? ψ : InfinitePEPS(ψ) + network = isa(psi, InfinitePEPS) ? psi : InfinitePEPS(psi) env, = leading_boundary(env, network, it.alg.ctm_alg) # update internal state of `it` state0 = it.state it.state = (@set state0.env = env) end - return ψ, env, info + return psi, env, info end end """ -Imaginary time evolution of InfinitePEPS with convergence checking +Imaginary time full update of InfinitePEPS with convergence checking """ function _time_evolve_gs( it::TimeEvolver{<:FullUpdate}, tol::Float64, H::LocalOperator ) time_start = time() - @assert (it.state.ψ isa InfinitePEPS) && it.alg.imaginary_time "Only imaginary time evolution of InfinitePEPS allows convergence checking." + @assert (it.state.psi isa InfinitePEPS) && it.alg.imaginary_time "Only imaginary time evolution of InfinitePEPS allows convergence checking." time0 = time() # backup variables iter0, t0 = it.state.iter, it.state.t - ψ0, env0, info0 = it.state.ψ, it.state.env, nothing - energy0 = expectation_value(ψ0, H, ψ0, env0) / prod(size(ψ0)) + psi0, env0, info0 = it.state.psi, it.state.env, nothing + energy0 = expectation_value(psi0, H, psi0, env0) / prod(size(psi0)) @info "FU: initial state energy = $(energy0)." - for (ψ, env, info) in it + for (psi, env, info) in it iter = it.state.iter if iter == 1 # reconverge for the 1st step - network = isa(ψ, InfinitePEPS) ? ψ : InfinitePEPS(ψ) + network = isa(psi, InfinitePEPS) ? psi : InfinitePEPS(psi) env, = leading_boundary(env, network, it.alg.ctm_alg) # update internal state of `it` # TODO: more elegant to use `Accessors.@set` - it.state = FUState(iter, it.state.t, it.state.ψ, env, true) + it.state = FUState(iter, it.state.t, it.state.psi, env, true) end !(it.state.reconverged) && continue # do the following only when env has been reconverged - energy = expectation_value(ψ, H, ψ, env) / prod(size(ψ)) + energy = expectation_value(psi, H, psi, env) / prod(size(psi)) diff = energy - energy0 stop = (iter == it.nstep) || (diff < 0 && abs(diff) < tol) || (diff > 0) showinfo = (iter == 1) || it.state.reconverged || stop @@ -282,9 +282,9 @@ function _time_evolve_gs( end if diff > 0 @warn "FU: energy has increased from last check. Abort evolution and return results from last check." - ψ, env, info, energy = ψ0, env0, info0, energy0 + psi, env, info, energy = psi0, env0, info0, energy0 # also reset internal state of `it` to last check - it.state = FUState(iter0, t0, ψ0, env0, true) + it.state = FUState(iter0, t0, psi0, env0, true) end if iter == it.nstep @info "FU: energy has not converged." @@ -293,11 +293,11 @@ function _time_evolve_gs( @assert it.state.reconverged time_end = time() @info @sprintf("Full update finished. Total time elasped: %.2f s", time_end - time_start) - return ψ, env, info + return psi, env, info else # update backup variables iter0, t0 = it.state.iter, it.state.t - ψ0, env0, info0, energy0 = ψ, env, info, energy + psi0, env0, info0, energy0 = psi, env, info, energy end time0 = time() end @@ -305,12 +305,12 @@ function _time_evolve_gs( end """ -Time evolution without convergence checking +Full update without convergence checking """ function _time_evolve(it::TimeEvolver{<:FullUpdate}) time_start = time0 = time() info0 = nothing - for (ψ, env, info) in it + for (psi, env, info) in it iter = it.state.iter !(it.state.reconverged) && continue # do the following only when env has been reconverged @@ -331,7 +331,7 @@ function _time_evolve(it::TimeEvolver{<:FullUpdate}) @assert it.state.reconverged time_end = time() @info @sprintf("Full update finished. Total time elasped: %.2f s", time_end - time_start) - return ψ, env, info + return psi, env, info else info0 = info end @@ -366,26 +366,26 @@ end """ MPSKit.time_evolve( - ψ₀::InfiniteState, H::LocalOperator, dt::Number, nstep::Int, - alg::FullUpdate, env₀::CTMRGEnv; - tol::Float64 = 0.0, t₀::Number = 0.0 - ) -> (ψ, env, info) + psi0::InfiniteState, H::LocalOperator, dt::Number, nstep::Int, + alg::FullUpdate, env0::CTMRGEnv; + tol::Float64 = 0.0, t0::Number = 0.0 + ) -> (psi, env, info) -Perform time evolution on the initial state `ψ₀` and initial environment `env₀` +Perform time evolution on the initial state `psi0` and initial environment `env0` with Hamiltonian `H`, using FullUpdate algorithm `alg`, time step `dt` for `nstep` number of steps. - Setting `tol > 0` enables convergence check (for imaginary time evolution of InfinitePEPS only). For other usages it should not be changed. -- Use `t₀` to specify the initial time of `ψ₀`. +- Use `t0` to specify the initial time of `psi0`. - `info` is a NamedTuple containing information of the evolution, - including the time `info.t` evolved since `ψ₀`. + including the time `info.t` evolved since `psi0`. """ function MPSKit.time_evolve( - ψ₀::InfiniteState, H::LocalOperator, dt::Number, nstep::Int, - alg::FullUpdate, env₀::CTMRGEnv; - tol::Float64 = 0.0, t₀::Number = 0.0 + psi0::InfiniteState, H::LocalOperator, dt::Number, nstep::Int, + alg::FullUpdate, env0::CTMRGEnv; + tol::Float64 = 0.0, t0::Number = 0.0 ) - it = TimeEvolver(ψ₀, H, dt, nstep, alg, env₀; t₀) + it = TimeEvolver(psi0, H, dt, nstep, alg, env0; t0) return time_evolve(it; tol, H) end From 9fbcdaee891f696d3e5f0145b6245f1a4b229a5f Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Mon, 17 Nov 2025 22:18:26 +0800 Subject: [PATCH 60/62] Fix merge regressions --- src/algorithms/ctmrg/sequential.jl | 38 ++++++++++++++++++++++ src/algorithms/time_evolution/evoltools.jl | 23 +------------ test/timeevol/tf_ising_realtime.jl | 10 +++--- 3 files changed, 44 insertions(+), 27 deletions(-) diff --git a/src/algorithms/ctmrg/sequential.jl b/src/algorithms/ctmrg/sequential.jl index 7c4cbf10f..7a36e0b29 100644 --- a/src/algorithms/ctmrg/sequential.jl +++ b/src/algorithms/ctmrg/sequential.jl @@ -78,6 +78,44 @@ function ctmrg_rightmove(col::Int, network, env::CTMRGEnv, alg::SequentialCTMRG) return rot180(env), info end +""" + ctmrg_upmove(col::Int, network, env::CTMRGEnv, alg::SequentialCTMRG) + +Perform sequential CTMRG up move on the `row`-th row. +""" +function ctmrg_upmove(row::Int, network, env::CTMRGEnv, alg::SequentialCTMRG) + #= + c-1 c c+1 + r-1 C1 ← T1 ← C2 + ↓ ‖ ↑ | up + r T4 = M == T2 ↓ move + ↓ ‖ ↑ + =# + Nr = size(network)[1] + @assert 1 <= row <= Nr + env, info = ctmrg_leftmove(Nr + 1 - row, rotl90(network), rotl90(env), alg) + return rotr90(env), info +end + +""" + ctmrg_downmove(col::Int, network, env::CTMRGEnv, alg::SequentialCTMRG) + +Perform sequential CTMRG down move on the `row`-th row. +""" +function ctmrg_downmove(row::Int, network, env::CTMRGEnv, alg::SequentialCTMRG) + #= + ↓ ‖ ↑ + r T4 = M == T2 ↑ down + ↓ ‖ ↑ | move + r+1 C4 → T3 → C3 + c-1 c c+1 + =# + Nr = size(network)[1] + @assert 1 <= row <= Nr + env, info = ctmrg_leftmove(row, rotr90(network), rotr90(env), alg) + return rotl90(env), info +end + function ctmrg_iteration(network, env::CTMRGEnv, alg::SequentialCTMRG) truncation_error = zero(real(scalartype(network))) condition_number = zero(real(scalartype(network))) diff --git a/src/algorithms/time_evolution/evoltools.jl b/src/algorithms/time_evolution/evoltools.jl index 4c4d60c7c..627e38bf1 100644 --- a/src/algorithms/time_evolution/evoltools.jl +++ b/src/algorithms/time_evolution/evoltools.jl @@ -18,26 +18,6 @@ function _get_dt( return dt′ end -""" -Process the Trotter time step `dt` according to the intended usage. -""" -function _get_dt( - state::InfiniteState, dt::Number, imaginary_time::Bool - ) - # PEPS update: exp(-H dt)|ψ⟩ - # PEPO update (purified): exp(-H dt/2)|ρ⟩ - # PEPO update (not purified): exp(-H dt/2) ρ exp(-H dt/2) - dt′ = (state isa InfinitePEPS) ? dt : (dt / 2) - if (state isa InfinitePEPO) - @assert size(state)[3] == 1 - end - if !imaginary_time - @assert (state isa InfinitePEPS) "Real time evolution of InfinitePEPO (Heisenberg picture) is not implemented." - dt′ = 1.0im * dt′ - end - return dt′ -end - function MPSKit.infinite_temperature_density_matrix(H::LocalOperator) T = scalartype(H) A = map(physicalspace(H)) do Vp @@ -182,7 +162,7 @@ For PEPSTensors, | ↘ ↘ | -4 -1 -1 -4 ``` -For PEPOTensors, +For PEPOTensors ``` -2 -3 -2 -3 ↘ | ↘ | @@ -196,7 +176,6 @@ For PEPOTensors, | ↘ | ↘ -5 -1 -5 -1 ``` -It is assumed that the physical domain and codomain spaces are not dualed. """ 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] diff --git a/test/timeevol/tf_ising_realtime.jl b/test/timeevol/tf_ising_realtime.jl index d1cec22db..6e2f99e32 100644 --- a/test/timeevol/tf_ising_realtime.jl +++ b/test/timeevol/tf_ising_realtime.jl @@ -18,10 +18,10 @@ const data = [ 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 + # 0.36 8.0979257e-1 + # 0.41 8.1559623e-1 + # 0.46 8.1541661e-1 + # 0.51 8.1274128e-1 ] # redefine tfising Hamiltonian with only 2-site gate @@ -62,7 +62,7 @@ function tfising_fu(g::Float64, Dcut::Int, chi::Int; als = true, use_pinv = true trunc_env = truncerror(; atol = 1.0e-10) & truncrank(chi) ctm_alg = SequentialCTMRG(; - tol = 1.0e-8, maxiter = 50, verbosity = 2, + tol = 1.0e-8, maxiter = 30, verbosity = 2, trunc = trunc_env, projector_alg = :fullinfinite ) From d16301a0c769a448937def1a98aeee2f86dd1f70 Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Mon, 17 Nov 2025 22:19:20 +0800 Subject: [PATCH 61/62] Change definition of tol in ALS/FET --- src/algorithms/truncation/bond_truncation.jl | 34 +++++++++++-------- .../truncation/fullenv_truncation.jl | 11 +++--- 2 files changed, 26 insertions(+), 19 deletions(-) diff --git a/src/algorithms/truncation/bond_truncation.jl b/src/algorithms/truncation/bond_truncation.jl index 67215fcdd..f4e3099fe 100644 --- a/src/algorithms/truncation/bond_truncation.jl +++ b/src/algorithms/truncation/bond_truncation.jl @@ -1,7 +1,7 @@ """ $(TYPEDEF) -Algorithm struct for the alternating least square (ALS) optimization of a bond. +Algorithm struct for the alternating least square (ALS) optimization of a bond between two tensors. ## Fields @@ -15,24 +15,25 @@ The truncation algorithm can be constructed from the following keyword arguments * `trunc::TruncationStrategy`: SVD truncation strategy when initilizing the truncated tensors connected by the bond. * `maxiter::Int=50` : Maximal number of ALS iterations. -* `tol::Float64=1e-15` : ALS converges when fidelity change between two FET iterations is smaller than `tol`. +* `tol::Float64=1e-9` : ALS converges when bond SVD spectrum change between two iterations is smaller than `tol`. * `use_pinv::Bool=true`: Use pseudo-inverse (instead of `KrylovKit.linsolve`) to solve linear equations in ALS itertions. * `check_interval::Int=0` : Set number of iterations to print information. Output is suppressed when `check_interval <= 0`. """ @kwdef struct ALSTruncation trunc::TruncationStrategy maxiter::Int = 50 - tol::Float64 = 1.0e-15 + tol::Float64 = 1.0e-9 use_pinv::Bool = true check_interval::Int = 0 end function _als_message( - iter::Int, cost::Float64, fid::Float64, Δcost::Float64, Δfid::Float64, time_elapsed::Float64, + iter::Int, cost::Float64, fid::Float64, Δcost::Float64, + Δfid::Float64, Δs::Float64, time_elapsed::Float64, ) return @sprintf( "%5d, fid = %.8e, Δfid = %.8e, time = %.4f s\n", iter, fid, Δfid, time_elapsed - ) * @sprintf(" cost = %.3e, Δcost/cost0 = %.3e", cost, Δcost) + ) * @sprintf(" cost = %.3e, Δcost/cost0 = %.3e, |Δs| = %.4e.", cost, Δcost, Δs) end """ @@ -73,9 +74,9 @@ function bond_truncate( a2b2 = _combine_ab(a, b) # initialize truncated a, b perm_ab = ((1, 3), (4, 2)) - a, s, b = svd_trunc(permute(a2b2, perm_ab); trunc = alg.trunc) - s /= norm(s, Inf) - a, b = absorb_s(a, s, b) + a, s0, b = svd_trunc(permute(a2b2, perm_ab); trunc = alg.trunc) + normalize!(s0, Inf) + a, b = absorb_s(a, s0, b) #= temporarily reorder axes of a and b to 1 -a/b- 2 ↓ @@ -87,8 +88,8 @@ function bond_truncate( # cost function will be normalized by initial value cost00 = cost_function_als(benv, ab, a2b2) fid = fidelity(benv, ab, a2b2) - cost0, fid0, Δfid = cost00, fid, 0.0 - verbose && @info "ALS init" * _als_message(0, cost0, fid, NaN, NaN, 0.0) + 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() #= @@ -118,17 +119,22 @@ function bond_truncate( ab = _combine_ab(a, b) cost = cost_function_als(benv, ab, a2b2) fid = fidelity(benv, ab, a2b2) + _, s, _ = svd_trunc!(permute(ab, perm_ab); trunc = alg.trunc) + normalize!(s, Inf) + # fidelity, cost and bond-s change + Δs = (space(s) == space(s0)) ? _singular_value_distance((s, s0)) : NaN Δcost = abs(cost - cost0) / cost00 Δfid = abs(fid - fid0) - cost0, fid0 = cost, fid + cost0, fid0, s0 = cost, fid, s time1 = time() - converge = (Δfid < alg.tol) + converge = (Δs < alg.tol) cancel = (iter == alg.maxiter) showinfo = cancel || (verbose && (converge || iter == 1 || iter % alg.check_interval == 0)) if showinfo message = _als_message( - iter, cost, fid, Δcost, Δfid, time1 - ((cancel || converge) ? time00 : time0), + iter, cost, fid, Δcost, Δfid, Δs, + time1 - ((cancel || converge) ? time00 : time0), ) if converge @info "ALS conv" * message @@ -147,7 +153,7 @@ function bond_truncate( if need_flip a, s, b = flip_svd(a, s, b) end - return a, s, b, (; fid, Δfid) + return a, s, b, (; fid, Δfid, Δs) end function bond_truncate( diff --git a/src/algorithms/truncation/fullenv_truncation.jl b/src/algorithms/truncation/fullenv_truncation.jl index 6590787de..763d31ec7 100644 --- a/src/algorithms/truncation/fullenv_truncation.jl +++ b/src/algorithms/truncation/fullenv_truncation.jl @@ -15,7 +15,7 @@ The truncation algorithm can be constructed from the following keyword arguments * `trunc::TruncationStrategy` : SVD truncation strategy when optimizing the new bond matrix. * `maxiter::Int=50` : Maximal number of FET iterations. -* `tol::Float64=1e-15` : FET converges when fidelity change between two FET iterations is smaller than `tol`. +* `tol::Float64=1e-9` : FET converges when bond SVD spectrum change between two FET iterations is smaller than `tol`. * `trunc_init::Bool=true` : Controls whether the initialization of the new bond matrix is obtained from truncated SVD of the old bond matrix. * `check_interval::Int=0` : Set number of iterations to print information. Output is suppressed when `check_interval <= 0`. @@ -26,7 +26,7 @@ The truncation algorithm can be constructed from the following keyword arguments @kwdef struct FullEnvTruncation trunc::TruncationStrategy maxiter::Int = 50 - tol::Float64 = 1.0e-15 + tol::Float64 = 1.0e-9 trunc_init::Bool = true check_interval::Int = 0 end @@ -242,7 +242,7 @@ function fullenv_truncate( @tensor B[-1 -2; -3 -4] := conj(u[1; -1]) * benv[1 -2; 3 -4] * u[3; -3] _linearmap_twist!(p) _linearmap_twist!(B) - r, info_r = linsolve(Base.Fix1(*, B), p, r, 0, 1) + r, info_r = linsolve(Base.Fix1(*, B), p, r) @tensor b1[-1; -2] = u[-1; 1] * r[1 -2] u, s, vh = svd_trunc(b1; trunc = alg.trunc) s /= norm(s, Inf) @@ -252,7 +252,8 @@ function fullenv_truncate( @tensor B[-1 -2; -3 -4] := conj(vh[-2; 2]) * benv[-1 2; -3 4] * vh[-4; 4] _linearmap_twist!(p) _linearmap_twist!(B) - l, info_l = linsolve(Base.Fix1(*, B), p, l, 0, 1) + l, info_l = linsolve(Base.Fix1(*, B), p, l) + @debug "Bond truncation info" info_l info_r @tensor b1[-1; -2] = l[-1 1] * vh[1; -2] fid = fidelity(benv, b0, b1) u, s, vh = svd_trunc!(b1; trunc = alg.trunc) @@ -263,7 +264,7 @@ function fullenv_truncate( s0 = deepcopy(s) fid0 = fid time1 = time() - converge = (Δfid < alg.tol) + converge = (Δs < alg.tol) cancel = (iter == alg.maxiter) showinfo = cancel || (verbose && (converge || iter == 1 || iter % alg.check_interval == 0)) From 6fe76e2c9afbc573f48555e5f33158f3c7a735a1 Mon Sep 17 00:00:00 2001 From: Yue Zhengyuan Date: Mon, 17 Nov 2025 22:24:36 +0800 Subject: [PATCH 62/62] Reduce runtime of real time FU test --- test/timeevol/tf_ising_realtime.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/timeevol/tf_ising_realtime.jl b/test/timeevol/tf_ising_realtime.jl index 6e2f99e32..d59d57f06 100644 --- a/test/timeevol/tf_ising_realtime.jl +++ b/test/timeevol/tf_ising_realtime.jl @@ -62,7 +62,7 @@ function tfising_fu(g::Float64, Dcut::Int, chi::Int; als = true, use_pinv = true trunc_env = truncerror(; atol = 1.0e-10) & truncrank(chi) ctm_alg = SequentialCTMRG(; - tol = 1.0e-8, maxiter = 30, verbosity = 2, + tol = 1.0e-8, maxiter = 50, verbosity = 2, trunc = trunc_env, projector_alg = :fullinfinite ) @@ -71,7 +71,7 @@ function tfising_fu(g::Float64, Dcut::Int, chi::Int; als = true, use_pinv = true # do one extra step at the beginning to match benchmark data fu_alg = FullUpdate(; opt_alg, ctm_alg, imaginary_time = false, reconverge_interval = 5) - evolver = TimeEvolver(peps, ham, 0.01, 50, fu_alg, env) + evolver = TimeEvolver(peps, ham, 0.01, 30, fu_alg, env) peps, env, info = timestep(evolver, peps, env; reconverge_env = true) # ensure the recoverged environment is updated to the internal state of `evolver` @test env == evolver.state.env