Repository navigation
C4v CTMRG can report convergence while the environment is still changing (frequent with C4vQRProjector) #440
Description
C4vCTMRG stops when the corner eigenvalues and edge singular values stop changing. These are invariant under rotating the edge relative to the corner eigenbasis, so they miss part of the environment. With C4vQRProjector, the slowest mode of the iteration lies in that blind part, and leading_boundary often stops while the gauge-fixed environment still changes by orders of magnitude more than tol. The fixed-point and implicit gradients then start from an environment that is not a fixed point.
1. QR stops too early. With tol = 1e-12, QR stops at η = 7.4e-13 while one more iteration still changes the environment by 3.1e-8. That change decays by ~0.72 per iteration, so CTMRG had not converged. Eigh stops at 1.7e-12.
2. The measure is blind to edge rotations. Rotating only the edge of a converged environment changes ⟨σz⟩ by 5e-4 but the convergence measure by 3e-16.
Reproducer (main, ccc2d4a)
using Random, LinearAlgebra, Printf
using TensorKit, PEPSKit
using PEPSKit: ctmrg_iteration, gauge_fix, ScramblingEnvGaugeC4v, calc_elementwise_convergence,
convergence_spectra, calc_convergence
Random.seed!(7)
P = Z2Space(0 => 1, 1 => 1)
ψ = symmetrize!(InfinitePEPS(randn, Float64, P, Z2Space(0 => 1, 1 => 1)), RotateReflect())
ψ = InfinitePEPS(map(a -> a / norm(a), ψ.A))
env₀ = initialize_random_c4v_env(ψ, Z2Space(0 => 8, 1 => 8))
network = InfiniteSquareNetwork(ψ)
# elementwise change of one CTMRG iteration, after gauge fixing
function step_change(env, alg)
env′, = ctmrg_iteration(network, env, alg)
return calc_elementwise_convergence(env, gauge_fix(env′, env, ScramblingEnvGaugeC4v())), env′
end
# 1. QR stops while the environment is still converging
for projector_alg in (:C4vQRProjector, :C4vEighProjector)
alg = C4vCTMRG(; projector_alg, tol = 1.0e-12, maxiter = 1000, verbosity = 0)
env, info = leading_boundary(env₀, ψ, alg)
@printf("%-17s spectral η at stop %.1e\n", projector_alg, info.convergence_error)
for k in 0:30
Δ, env′ = step_change(env, alg)
k in (0, 10, 20, 30) && @printf(" %2d more iterations: elementwise change per iteration %.1e\n", k, Δ)
env = env′
end
end
# 2. the convergence measure is blind to rotating the edge against the corner
alg = C4vCTMRG(; projector_alg = :C4vQRProjector, tol = 1.0e-12, maxiter = 1000, verbosity = 0)
env, = leading_boundary(env₀, ψ, alg)
C, E = env.corners[1, 1, 1], env.edges[1, 1, 1]
X = randn(Float64, space(C)); U = exp(1.0e-2 * (X - X'))
@tensor E_rot[a s t; b] := U'[a; a′] * E[a′ s t; b′] * U[b′; b] # rotate E only, keep C
env_rot = PEPSKit.CTMRGEnv(C, E_rot)
CS, TS = convergence_spectra(env, alg)
σz = zeros(Float64, P ← P); block(σz, Z2Irrep(0)) .= 1; block(σz, Z2Irrep(1)) .= -1
O = LocalOperator(physicalspace(ψ), ((1, 1),) => σz)
@printf("spectral distance %.1e; ⟨σz⟩ %.10f vs %.10f\n", first(calc_convergence(env_rot, CS, TS, alg)),
real(expectation_value(ψ, O, env_rot)), real(expectation_value(ψ, O, env)))Output:
C4vQRProjector spectral η at stop 7.4e-13
0 more iterations: elementwise change per iteration 3.1e-08
10 more iterations: elementwise change per iteration 1.2e-09
20 more iterations: elementwise change per iteration 6.9e-11
30 more iterations: elementwise change per iteration 2.2e-12
C4vEighProjector spectral η at stop 5.8e-13
0 more iterations: elementwise change per iteration 1.7e-12
10 more iterations: elementwise change per iteration 7.8e-13
20 more iterations: elementwise change per iteration 3.2e-12
30 more iterations: elementwise change per iteration 4.5e-14
spectral distance 2.7e-16; ⟨σz⟩ -0.9397300392 vs -0.9402416884
This is common. On 48 random C4v PEPS (D = 2, χ = 12–16, real and complex, with and without Z2), checked with an exact gauge fix, QR stopped with an elementwise change above 1e-9 on 21 of the 46 states where it reached tol. Eigh did so on 1 of 48.
Activity
leburgel commentedon Oct 10, 2026
One possible fix could be to also compare the gauge-fixed new environment with the previous one once the spectral check passes, and to stop
leading_boundaryonly if that change is ≤tolas well. The gauge fix then runs only in the final iterations. The scrambling gauge fix limits this check to ~1e-11 on gapped states and ~1e-8 at criticality, so if the change stalls there, stop with a warning.