From 0960301247377852cafe03b6262489dabf85055b Mon Sep 17 00:00:00 2001 From: sanderdemeyer Date: Fri, 23 Jan 2026 17:20:07 +0100 Subject: [PATCH 01/41] add Triangular CTMRG This code is based on its (asymetric) implementation in TNRKit.jl, which was based on the paper https://arxiv.org/pdf/2510.04907 --- src/PEPSKit.jl | 11 +- src/algorithms/select_algorithm.jl | 7 + src/algorithms/triangular_ctmrg/ctmrg.jl | 50 +++ .../triangular_ctmrg/simultaneous.jl | 319 ++++++++++++++++++ src/algorithms/triangular_ctmrg/utility.jl | 56 +++ src/environments/ctmrg_tria_environments.jl | 77 +++++ src/networks/infinitetriangularnetwork.jl | 174 ++++++++++ src/networks/local_triangular_sandwich.jl | 43 +++ src/networks/tensors_tria.jl | 101 ++++++ test/ctmrg/triangular.jl | 95 ++++++ 10 files changed, 932 insertions(+), 1 deletion(-) create mode 100644 src/algorithms/triangular_ctmrg/ctmrg.jl create mode 100644 src/algorithms/triangular_ctmrg/simultaneous.jl create mode 100644 src/algorithms/triangular_ctmrg/utility.jl create mode 100644 src/environments/ctmrg_tria_environments.jl create mode 100644 src/networks/infinitetriangularnetwork.jl create mode 100644 src/networks/local_triangular_sandwich.jl create mode 100644 src/networks/tensors_tria.jl create mode 100644 test/ctmrg/triangular.jl diff --git a/src/PEPSKit.jl b/src/PEPSKit.jl index 2aeb865b6..e5a23f596 100644 --- a/src/PEPSKit.jl +++ b/src/PEPSKit.jl @@ -72,6 +72,9 @@ include("utility/initialization.jl") include("networks/tensors.jl") include("networks/local_sandwich.jl") include("networks/infinitesquarenetwork.jl") +include("networks/tensors_tria.jl") +include("networks/local_triangular_sandwich.jl") +include("networks/infinitetriangularnetwork.jl") include("states/infinitepeps.jl") include("states/infinitepartitionfunction.jl") @@ -89,6 +92,7 @@ include("operators/lattices/squarelattice.jl") include("operators/models.jl") include("environments/ctmrg_environments.jl") +include("environments/ctmrg_tria_environments.jl") include("environments/vumps_environments.jl") include("environments/suweight.jl") include("environments/bp_environments.jl") @@ -139,6 +143,10 @@ include("algorithms/ctmrg/gaugefix.jl") include("algorithms/ctmrg/c4v.jl") include("algorithms/ctmrg/initialization.jl") +include("algorithms/triangular_ctmrg/ctmrg.jl") +include("algorithms/triangular_ctmrg/utility.jl") +include("algorithms/triangular_ctmrg/simultaneous.jl") + include("algorithms/truncation/truncationschemes.jl") include("algorithms/truncation/fullenv_truncation.jl") include("algorithms/truncation/bond_tensor.jl") @@ -177,6 +185,7 @@ using .Defaults: set_scheduler! export set_scheduler! export EighAdjoint, IterEigh, SVDAdjoint, IterSVD, OrthAdjoint, QRAdjoint export CTMRGEnv, SequentialCTMRG, SimultaneousCTMRG +export CTMRGTriaEnv, SimultaneousCTMRGTria export initialize_ctmrg_environment, RandomInitialization, ProductStateInitialization, ApplicationInitialization, IdentityInitialization export corner, edge, setcorner!, setedge! @@ -200,7 +209,7 @@ export ALSTruncation, FullEnvTruncation export SimpleUpdate export TimeEvolver, timestep, time_evolve -export InfiniteSquareNetwork +export InfiniteSquareNetwork, InfiniteTriangularNetwork export InfinitePartitionFunction export InfinitePEPS, InfiniteTransferPEPS export SUWeight diff --git a/src/algorithms/select_algorithm.jl b/src/algorithms/select_algorithm.jl index c121fb3fd..5eb29c4b2 100644 --- a/src/algorithms/select_algorithm.jl +++ b/src/algorithms/select_algorithm.jl @@ -158,3 +158,10 @@ function select_algorithm( return CTMRGAlgorithm(; alg, tol, verbosity, decomposition_alg, kwargs...) end + +function select_algorithm( + ::typeof(leading_boundary), + env₀::CTMRGTriaEnv + ) + return SimultaneousCTMRGTria() +end diff --git a/src/algorithms/triangular_ctmrg/ctmrg.jl b/src/algorithms/triangular_ctmrg/ctmrg.jl new file mode 100644 index 000000000..9e06e2ad4 --- /dev/null +++ b/src/algorithms/triangular_ctmrg/ctmrg.jl @@ -0,0 +1,50 @@ +abstract type CTMRGTriaAlgorithm end + +function leading_boundary(env₀::CTMRGTriaEnv, network::InfiniteTriangularNetwork; kwargs...) + alg = select_algorithm(leading_boundary, env₀; kwargs...) + return leading_boundary(env₀, network, alg) +end +function leading_boundary( + env₀::CTMRGTriaEnv, network::InfiniteTriangularNetwork, alg::CTMRGTriaAlgorithm + ) + log = ignore_derivatives(() -> MPSKit.IterLog("CTMRG")) + return LoggingExtras.withlevel(; alg.verbosity) do + env = deepcopy(env₀) + S_old = DiagonalTensorMap.(id.(domain.(env₀.C))) + η = one(real(scalartype(network))) + ctmrg_loginit!(log, η, network, env₀) + local info + for iter in 1:(alg.maxiter) + env, S = ctmrg_iteration(network, env, alg) # Grow and renormalize in all 4 directions + η = calc_convergence(S, S_old) + S_old = copy(S) + + if η ≤ alg.tol && iter ≥ alg.miniter + ctmrg_logfinish!(log, iter, η, network, env) + break + end + if iter == alg.maxiter + ctmrg_logcancel!(log, iter, η, network, env) + else + ctmrg_logiter!(log, iter, η, network, env) + end + info = (; convergence_metric = η) + end + return env, info + end +end +function leading_boundary(env₀::CTMRGTriaEnv, state, args...; kwargs...) + return leading_boundary(env₀, InfiniteTriangularNetwork(state), args...; kwargs...) +end + +function calc_convergence(S_new::Array{T, 3}, S_old::Array{T, 3}) where {E, S, T <: DiagonalTensorMap{E, S}} + ε = Inf + function dist(S1, S2) + if space(S1) == space(S2) + return norm(S1^4 - S2^4) + else + return Inf + end + end + return maximum(dist.(S_new, S_old)) +end diff --git a/src/algorithms/triangular_ctmrg/simultaneous.jl b/src/algorithms/triangular_ctmrg/simultaneous.jl new file mode 100644 index 000000000..4c3610bcd --- /dev/null +++ b/src/algorithms/triangular_ctmrg/simultaneous.jl @@ -0,0 +1,319 @@ +struct SimultaneousCTMRGTria <: CTMRGTriaAlgorithm + tol::Float64 + maxiter::Int + miniter::Int + verbosity::Int + conditioning::Bool + projector_alg::Symbol + trunctype::Symbol +end +function SimultaneousCTMRGTria(; + tol = Defaults.ctmrg_tol, + maxiter = Defaults.ctmrg_maxiter, + miniter = Defaults.ctmrg_miniter, + verbosity = Defaults.ctmrg_verbosity, + conditioning = true, + projector_alg = :twothirds, + trunctype = :truncrank + ) + return SimultaneousCTMRGTria(tol, maxiter, miniter, verbosity, conditioning, projector_alg, trunctype) +end + +# Based on +# https://arxiv.org/pdf/2510.04907 + +function ctmrg_iteration(network::InfiniteTriangularNetwork, env::CTMRGTriaEnv, alg::SimultaneousCTMRGTria) + trunc_type = getfield(@__MODULE__, alg.trunctype) + trunc = trunc_type(dim(domain(env.C[1, 1, 1])[1])) + Pas, Pbs, S = calculate_projectors(network, env, trunc, alg.projector_alg) + + renormalize_corners!(network, env, Pas, Pbs) + normalize_corners!(env) + + Ẽas, Ẽbs, Ẽastr, Ẽbstr = semi_renormalize(network, env, Pas, Pbs, trunc) + Qas, Qbs = build_matrix_second_projectors(network, env, Ẽas, Ẽbs, Ẽastr, Ẽbstr, trunc; alg.conditioning) + + env = renormalize_edges(env, Ẽas, Ẽbs, Qas, Qbs) + normalize_edges!(env) + return env, S +end + +function calculate_projectors(network, env, trunc, projector_alg) + if projector_alg == :full + return calculate_full_projectors(network, env, trunc) + elseif projector_alg == :twothirds + return calculate_twothirds_projectors(network, env, trunc) + else + @error "projector_alg = $projector_alg not defined" + end +end + +function calculate_twothirds_projectors(network::InfiniteTriangularNetwork{P}, env, trunc) where {P} + coordinates = eachcoordinate(network, 1:6) + E = scalartype(env.C[1, 1, 1]) + S = spacetype(env.C[1, 1, 1]) + T_proj = Tuple{AbstractTensorMap{E, S, 1, 2 + (network[1, 1] isa Tuple)}, AbstractTensorMap{E, S, 2 + (network[1, 1] isa Tuple), 1}, AbstractTensorMap{E, S, 1, 1}} + projectors′ = similar(coordinates, T_proj) + projectors = dtmap!!(projectors′, coordinates) do (dir, r, c) + ρL = build_double_corner_matrix_triangular(network, env, mod1(dir - 1, 6), r, c) + ρR = build_double_corner_matrix_triangular(network, env, mod1(dir + 1, 6), r, c) + ρρ = ρL * ρR + ρρ /= norm(ρρ) + + U, S, V = svd_trunc(ρρ; trunc = trunc & trunctol(; atol = 1.0e-20)) + + Pb = ρR * V' * sdiag_pow(S, -1 / 2) + Pa = sdiag_pow(S, -1 / 2) * U' * ρL + return Pa, Pb, S + end + return getindex.(projectors, 1), getindex.(projectors, 2), getindex.(projectors, 3) +end + +function calculate_full_projectors(network::InfiniteTriangularNetwork{P}, env, trunc) where {P} + coordinates = eachcoordinate(network, 1:6) + E = scalartype(env.C[1, 1, 1]) + S = spacetype(env.C[1, 1, 1]) + T_proj = Tuple{AbstractTensorMap{E, S, 1, 2 + (network[1, 1] isa Tuple)}, AbstractTensorMap{E, S, 2 + (network[1, 1] isa Tuple), 1}, AbstractTensorMap{E, S, 1, 1}} + projectors′ = similar(coordinates, T_proj) + projectors = dtmap!!(projectors′, coordinates) do (dir, r, c) + ρL = build_double_corner_matrix_triangular(network, env, mod1(dir - 1, 6), r, c) + ρR = build_double_corner_matrix_triangular(network, env, mod1(dir + 1, 6), r, c) + ρ̄ = build_double_corner_matrix_triangular(network, env, mod1(dir + 3, 6), r, c) + ρ̄ /= norm(ρ̄) + Ū, S̄, V̄ᴴ = svd_trunc(ρ̄; trunc = trunctol(; atol = 1.0e-20)) + ρ̄ᴿ = Ū * sqrt(S̄) + ρ̄ᴸ = sqrt(S̄) * V̄ᴴ + ρρ = ρ̄ᴸ * ρL * ρR * ρ̄ᴿ + ρρ /= norm(ρρ) + + U, S, Vᴴ = svd_trunc(ρρ; trunc = trunctol(; atol = 1.0e-20)) + U, S, Vᴴ = svd_trunc(ρρ; trunc) + U, S, Vᴴ = svd_trunc(ρρ; trunc = trunc & trunctol(; atol = 1.0e-20)) + + Pb = ρR * ρ̄ᴿ * Vᴴ' * sdiag_pow(S, -1 / 2) + Pa = sdiag_pow(S, -1 / 2) * U' * ρ̄ᴸ * ρL + + return Pa, Pb, S + end + return getindex.(projectors, 1), getindex.(projectors, 2), getindex.(projectors, 3) +end + +function renormalize_corners!(network::InfiniteTriangularNetwork{P}, env, Pas, Pbs) where {P <: PEPSTriaSandwich} + coordinates = eachcoordinate(network, 1:6) + E = scalartype(env.C[1, 1, 1]) + S = spacetype(env.C[1, 1, 1]) + T_proj = AbstractTensorMap{E, S, 2 + (network[1, 1] isa Tuple), 1} + new_corners′ = similar(coordinates, T_proj) + new_corners = dtmap!!(new_corners′, coordinates) do (dir, r, c) + @tensor opt = true env_C_new[-1 -2 -3; -4] := env.C[_coordinates(dir, 0, r, c, size(network))...][1 3 10; 6] * env.Ea[_coordinates(dir - 1, 0, r, c, size(network))...][4 2 11; 1] * env.Eb[_coordinates(dir, 1, r, c, size(network))...][6 7 12; 8] * + rotl60(ket(network[r, c]), mod(dir - 1, 6))[15; 3 7 9 -2 5 2] * conj(rotl60(bra(network[r, c]), mod(dir - 1, 6))[15; 10 12 14 -3 13 11]) * + Pas[mod1(dir - 1, 6), r, c][-1; 4 5 13] * Pbs[dir, r, c][8 9 14; -4] + return env_C_new + end + return new_corners +end + +function renormalize_corners!(network::InfiniteTriangularNetwork{P}, env, Pas, Pbs) where {P <: PFTriaTensor} + coordinates = eachcoordinate(network, 1:6) + E = scalartype(env.C[1, 1, 1]) + S = spacetype(env.C[1, 1, 1]) + T_proj = AbstractTensorMap{E, S, 2, 1} + new_corners′ = similar(coordinates, T_proj) + new_corners = dtmap!!(new_corners′, coordinates) do (dir, r, c) + @tensor opt = true env_C_new[-1 -2; -3] := env.C[_coordinates(dir, 0, r, c, size(network))...][1 3; 6] * env.Ea[_coordinates(dir - 1, 0, r, c, size(network))...][4 2; 1] * env.Eb[_coordinates(dir, 1, r, c, size(network))...][6 7; 8] * + rotl60(network[r, c], mod(dir - 1, 6))[2 5 -2; 3 7 9] * Pas[mod1(dir - 1, 6), r, c][-1; 4 5] * Pbs[dir, r, c][8 9; -3] + return env_C_new + end + return new_corners +end + +function build_double_corner_matrix_triangular(network::InfiniteTriangularNetwork{P}, env::CTMRGTriaEnv, dir::Int, r::Int, c::Int) where {P <: PFTriaTensor} + @tensor opt = true mat[-1 -2; -3 -4] := env.C[_coordinates(dir, 0, r, c, size(network))...][6 5; 1] * env.C[_coordinates(dir + 1, 0, r, c, size(network))...][1 3; 2] * + env.Ea[_coordinates(dir - 1, 0, r, c, size(network))...][-1 7; 6] * env.Eb[_coordinates(dir + 1, 0, r, c, size(network))...][2 4; -3] * rotl60(network[r, c], mod(dir - 1, 6))[7 -2 -4; 5 3 4] + return mat +end + +function build_double_corner_matrix_triangular(network::InfiniteTriangularNetwork{P}, env::CTMRGTriaEnv, dir::Int, r::Int, c::Int) where {P <: PEPSTriaSandwich} + @tensor opt = true mat[-1 -2 -3; -4 -5 -6] := env.C[_coordinates(dir, 0, r, c, size(network))...][6 5 8; 1] * env.C[_coordinates(dir + 1, 0, r, c, size(network))...][1 3 9; 2] * + env.Ea[_coordinates(dir - 1, 0, r, c, size(network))...][-1 7 10; 6] * env.Eb[_coordinates(dir + 1, 0, r, c, size(network))...][2 4 11; -4] * + rotl60(ket(network[r, c]), mod(dir - 1, 6))[12; 5 3 4 -5 -2 7] * conj(rotl60(bra(network[r, c]), mod(dir - 1, 6))[12; 8 9 11 -6 -3 10]) + return mat +end + +function semi_renormalize_edge(network::InfiniteTriangularNetwork{P}, env::CTMRGTriaEnv, Pas, Pbs, dir, r, c) where {P <: PEPSTriaSandwich} + @tensor opt = true mat[-1 -2 -3; -4 -5 -6] := Pas[dir, r, c][-1; 1 2 8] * Pbs[dir, r, c][6 7 9; -4] * + env.Ea[_coordinates(dir, 0, r, c, size(network))...][1 3 10; 4] * env.Eb[_coordinates(dir, 1, r, c, size(network))...][4 5 11; 6] * + rotl60(ket(network[r, c]), mod(dir - 1, 6))[12; 3 5 7 -5 -2 2] * conj(rotl60(bra(network[r, c]), mod(dir - 1, 6))[12; 10 11 9 -6 -3 8]) + return mat / norm(mat) +end + +function semi_renormalize_edge(network::InfiniteTriangularNetwork{P}, env::CTMRGTriaEnv, Pas, Pbs, dir, r, c) where {P <: PFTriaTensor} + @tensor opt = true mat[-1 -2; -3 -4] := Pas[dir, r, c][-1; 1 2] * Pbs[dir, r, c][6 7; -3] * + env.Ea[_coordinates(dir, 0, r, c, size(network))...][1 3; 4] * env.Eb[_coordinates(dir, 1, r, c, size(network))...][4 5; 6] * rotl60(network[r, c], mod(dir - 1, 6))[2 -2 -4; 3 5 7] + return mat / norm(mat) +end + +function _permute_edge(t::Union{T1, T2}) where {E, S, T1 <: AbstractTensorMap{E, S, 2, 1}, T2 <: AbstractTensorMap{E, S, 1, 2}} + return permute(t, ((1, 3), (2,))) +end + +function _permute_edge(t::Union{T1, T2}) where {E, S, T1 <: AbstractTensorMap{E, S, 3, 1}, T2 <: AbstractTensorMap{E, S, 1, 3}} + return permute(t, ((1, 3, 4), (2,))) +end + +function semi_renormalize(network::InfiniteTriangularNetwork, env::CTMRGTriaEnv, Pas, Pbs, trunc) + coordinates = eachcoordinate(network, 1:6) + E = scalartype(env.C[1, 1, 1]) + S = spacetype(env.C[1, 1, 1]) + T_proj = Tuple{AbstractTensorMap{E, S, 2 + (network[1, 1] isa Tuple), 1}, AbstractTensorMap{E, S, 2 + (network[1, 1] isa Tuple), 1}, AbstractTensorMap{E, S, 2 + (network[1, 1] isa Tuple), 1}, AbstractTensorMap{E, S, 2 + (network[1, 1] isa Tuple), 1}} + projectors′ = similar(coordinates, T_proj) + projectors = dtmap!!(projectors′, coordinates) do (dir, r, c) + mat = semi_renormalize_edge(network, env, Pas, Pbs, dir, r, c) + + U, S, V = svd_trunc(mat; trunc = trunctol(; atol = 1.0e-20)) + + Ẽb = U * sqrt(S) + Ẽa = _permute_edge(sqrt(S) * V) + + Utr, Str, Vtr = svd_trunc(mat; trunc = trunc & trunctol(; atol = 1.0e-20)) + Ẽbtr = Utr * sqrt(Str) + Ẽatr = _permute_edge(sqrt(Str) * Vtr) + + return Ẽa, Ẽb, Ẽatr, Ẽbtr + end + return getindex.(projectors, 1), getindex.(projectors, 2), getindex.(projectors, 3), getindex.(projectors, 4) +end + +function build_halfinfinite_projectors(network::InfiniteTriangularNetwork{P}, env::CTMRGTriaEnv, Ẽas, Ẽbs, Ẽastr, Ẽbstr, dir, r, c) where {P <: PEPSTriaSandwich} + @tensor opt = true σL[-1 -2 -3; -4] := env.C[_coordinates(dir, 0, r, c, size(network))...][1 2 10; 8] * env.C[_coordinates(dir - 1, 0, r, c, size(network))...][4 3 11; 1] * env.C[_coordinates(dir - 2, 0, r, c, size(network))...][6 5 12; 4] * + Ẽbs[_coordinates(dir, 1, r, c, size(network))...][8 9 13; -4] * Ẽastr[_coordinates(dir - 3, 0, r, c, size(network))...][-1 7 14; 6] * + rotl60(ket(network[r, c]), mod(dir - 1, 6))[15 2 9 -2 7 5 3] * conj(rotl60(bra(network[r, c]), mod(dir - 1, 6))[15; 10 13 -3 14 12 11]) + @tensor opt = true σR[-1; -2 -3 -4] := env.C[_coordinates(dir + 1, 0, r, c, size(network))...][8 2 10; 1] * env.C[_coordinates(dir + 2, 0, r, c, size(network))...][1 3 11; 4] * env.C[_coordinates(dir + 3, 0, r, c, size(network))...][4 5 12; 6] * + Ẽbstr[_coordinates(dir + 3, -1, r, c, size(network))...][6 7 13; -2] * Ẽas[_coordinates(dir, 0, r, c, size(network))...][-1 9 14; 8] * + rotl60(ket(network[_coordinates(dir + 2, 0, r, c, size(network))[2:3]...]), mod(dir - 1, 6))[15; 9 2 3 5 7 -3] * conj(rotl60(bra(network[_coordinates(dir + 2, 0, r, c, size(network))[2:3]...]), mod(dir - 1, 6))[15; 14 10 11 12 13 -4]) + return σL, σR +end + +function build_halfinfinite_projectors(network::InfiniteTriangularNetwork{P}, env::CTMRGTriaEnv, Ẽas, Ẽbs, Ẽastr, Ẽbstr, dir, r, c) where {P <: PFTriaTensor} + @tensor opt = true σL[-1 -2; -3] := env.C[_coordinates(dir, 0, r, c, size(network))...][1 2; 8] * env.C[_coordinates(dir - 1, 0, r, c, size(network))...][4 3; 1] * env.C[_coordinates(dir - 2, 0, r, c, size(network))...][6 5; 4] * + Ẽbs[_coordinates(dir, 1, r, c, size(network))...][8 9; -3] * Ẽastr[_coordinates(dir - 3, 0, r, c, size(network))...][-1 7; 6] * + rotl60(network[r, c], mod(dir - 1, 6))[3 5 7; 2 9 -2] + @tensor opt = true σR[-1; -2 -3] := env.C[_coordinates(dir + 1, 0, r, c, size(network))...][8 2; 1] * env.C[_coordinates(dir + 2, 0, r, c, size(network))...][1 3; 4] * env.C[_coordinates(dir + 3, 0, r, c, size(network))...][4 5; 6] * + Ẽbstr[_coordinates(dir + 3, -1, r, c, size(network))...][6 7; -2] * Ẽas[_coordinates(dir, 0, r, c, size(network))...][-1 9; 8] * + rotl60(network[_coordinates(dir + 2, 0, r, c, size(network))[2:3]...], mod(dir - 1, 6))[-3 7 5; 9 2 3] + return σL, σR +end + +function build_matrix_second_projectors(network::InfiniteTriangularNetwork, env::CTMRGTriaEnv, Ẽas, Ẽbs, Ẽastr, Ẽbstr, trunc; conditioning = true) + coordinates = eachcoordinate(network, 1:6) + E = scalartype(env.C[1, 1, 1]) + S = spacetype(env.C[1, 1, 1]) + T_proj = Tuple{AbstractTensorMap{E, S, 1, 1}, AbstractTensorMap{E, S, 1, 1}} + projectors′ = similar(coordinates, T_proj) + projectors = dtmap!!(projectors′, coordinates) do (dir, r, c) + σL, σR = build_halfinfinite_projectors(network, env, Ẽas, Ẽbs, Ẽastr, Ẽbstr, dir, r, c) + if conditioning + σL /= norm(σL) + σR /= norm(σR) + UL, SL, VLᴴ = svd_trunc(σL; trunc = trunctol(; atol = 1.0e-20)) + UR, SR, VRᴴ = svd_trunc(σR; trunc = trunctol(; atol = 1.0e-20)) + + FLU = sqrt(SL) * VLᴴ + FRU = UR * sqrt(SR) + + mat = FLU * FRU + mat /= norm(mat) + WU, SU, QUᴴ = svd_trunc(mat; trunc = trunc & trunctol(; atol = 1.0e-20)) + + Qa = sdiag_pow(SU, -1 / 2) * WU' * FLU + Qb = FRU * QUᴴ' * sdiag_pow(SU, -1 / 2) + else + mat = σL * σR + mat /= norm(mat) + U, S, V = svd_trunc(mat; trunc = trunc & trunctol(; atol = 1.0e-20)) + Qa = sdiag_pow(S, -1 / 2) * U' * σL + Qb = σR * V' * sdiag_pow(S, -1 / 2) + end + return Qa, Qb + end + return getindex.(projectors, 1), getindex.(projectors, 2) +end + +function renormalize_edges(env::CTMRGTriaEnv, Ẽas::Array{T1, 3}, Ẽbs::Array{T1, 3}, Qas::Array{T2, 3}, Qbs::Array{T2, 3}) where {E, S, T1 <: AbstractTensorMap{E, S, 3, 1}, T2 <: AbstractTensorMap{E, S, 1, 1}} + coordinates = collect(Iterators.product(axes(env.Ea)...)) + T_proj = Tuple{AbstractTensorMap{E, S, 3, 1}, AbstractTensorMap{E, S, 3, 1}} + new_edges′ = similar(env.Ea, T_proj) + new_edges = dtmap!!(new_edges′, coordinates) do (dir, r, c) + @tensor Eb_new[-1 -2 -3; -4] := Ẽbs[dir, r, c][-1 -2 -3; 1] * Qbs[dir, r, c][1; -4] + @tensor Ea_new[-1 -2 -3; -4] := Qas[dir, r, c][-1; 1] * Ẽas[dir, r, c][1 -2 -3; -4] + return Ea_new, Eb_new + end + return CTMRGTriaEnv(env.C, getindex.(new_edges, 1), getindex.(new_edges, 2)) +end + +function renormalize_edges(env::CTMRGTriaEnv, Ẽas::Array{T1, 3}, Ẽbs::Array{T1, 3}, Qas::Array{T2, 3}, Qbs::Array{T2, 3}) where {E, S, T1 <: AbstractTensorMap{E, S, 2, 1}, T2 <: AbstractTensorMap{E, S, 1, 1}} + coordinates = collect(Iterators.product(axes(env.Ea)...)) + T_proj = Tuple{AbstractTensorMap{E, S, 2, 1}, AbstractTensorMap{E, S, 2, 1}} + new_edges′ = similar(env.Ea, T_proj) + new_edges = dtmap!!(new_edges′, coordinates) do (dir, r, c) + @tensor Eb_new[-1 -2; -3] := Ẽbs[dir, r, c][-1 -2; 1] * Qbs[dir, r, c][1; -3] + @tensor Ea_new[-1 -2; -3] := Qas[dir, r, c][-1; 1] * Ẽas[dir, r, c][1 -2; -3] + return Ea_new, Eb_new + end + return CTMRGTriaEnv(env.C, getindex.(new_edges, 1), getindex.(new_edges, 2)) +end + +function normalize_corners!(env) + coordinates = collect(Iterators.product(axes(env.Ea)...)) + new_env′ = similar(env.C, typeof(env.C[1, 1, 1])) + new_env = dtmap!!(new_env′, coordinates) do (dir, r, c) + env_C_new = env.C[dir, r, c] / norm(env.C[dir, r, c]) + return env_C_new + end + return new_env +end + +function normalize_edges!(env) + for dir in 1:6 + env.Ea[dir] /= norm(env.Ea[dir]) + env.Eb[dir] /= norm(env.Eb[dir]) + end + return env +end + +function calculate_error(Ss, Ss_prev) + ε = Inf + for (S, S_prev) in zip(Ss, Ss_prev) + if space(S) == space(S_prev) + ε = norm(S^4 - S_prev^4) + else + return Inf + end + end + return ε +end + +# Expectation_values + +function network_value(network::InfiniteTriangularNetwork, env::CTMRGTriaEnv) + return zero(scalartype(network)) +end + +function energy(network::InfiniteTriangularNetwork{P}, env::CTMRGTriaEnv, H) where {P <: PEPSTriaSandwich} + (r, c) = (1, 1) + numerator = @tensor opt = true ket(network[r, c])[dLt; DLt120 DLt60 DLt0 DLt300 DLt240 DLt180] * ket(network[r, c])[dRt; DRt120 DRt60 DRt0 DRt300 DRt240 DLt0] * + conj(bra(network[r, c])[dLb DLb120 DLb60 DLb0 DLb300 DLb240 DLb180]) * conj(bra(network[r, c])[dRb DRb120 DRb60 DRb0 DRb300 DRb240 DLb0]) * + env.C[1][χNW DLt120 DLb120; χNa] * env.C[2][χNb DRt60 DRb60; χNE] * env.C[3][χNE DRt0 DRb0; χSE] * + env.C[4][χSE DRt300 DRb300; χSa] * env.C[5][χSb DLt240 DLb240; χSW] * env.C[6][χSW DLt180 DLb180; χNW] * + env.Eb[1][χNa DLt60 DLb60; χNC] * env.Ea[1][χNC DRt120 DRb120; χNb] * + env.Eb[4][χSa DRt240 DRb240; χSC] * env.Ea[4][χSC DLt300 DLb300; χSb] * + H[dLb dRb; dLt dRt] + + denumerator = @tensor opt = true ket(network[r, c])[dL; DLt120 DLt60 DLt0 DLt300 DLt240 DLt180] * ket(network[r, c])[dR; DRt120 DRt60 DRt0 DRt300 DRt240 DLt0] * + conj(bra(network[r, c])[dL; DLb120 DLb60 DLb0 DLb300 DLb240 DLb180]) * conj(bra(network[r, c])[dR; DRb120 DRb60 DRb0 DRb300 DRb240 DLb0]) * + env.C[1][χNW DLt120 DLb120; χNa] * env.C[2][χNb DRt60 DRb60; χNE] * env.C[3][χNE DRt0 DRb0; χSE] * + env.C[4][χSE DRt300 DRb300; χSa] * env.C[5][χSb DLt240 DLb240; χSW] * env.C[6][χSW DLt180 DLb180; χNW] * + env.Eb[1][χNa DLt60 DLb60; χNC] * env.Ea[1][χNC DRt120 DRb120; χNb] * + env.Eb[4][χSa DRt240 DRb240; χSC] * env.Ea[4][χSC DLt300 DLb300; χSb] + return numerator / denumerator +end diff --git a/src/algorithms/triangular_ctmrg/utility.jl b/src/algorithms/triangular_ctmrg/utility.jl new file mode 100644 index 000000000..0230e0944 --- /dev/null +++ b/src/algorithms/triangular_ctmrg/utility.jl @@ -0,0 +1,56 @@ +function _coordinates(dir::Int, rot::Int, r::Int, c::Int, unitcell::Tuple{Int, Int}) + rows, cols = unitcell + if mod1(dir + rot, 6) == 1 + return (mod1(dir, 6), mod1(r - 1, rows), mod1(c, cols)) + elseif mod1(dir + rot, 6) == 2 + return (mod1(dir, 6), mod1(r - 1, rows), mod1(c + 1, cols)) + elseif mod1(dir + rot, 6) == 3 + return (mod1(dir, 6), mod1(r, rows), mod1(c + 1, cols)) + elseif mod1(dir + rot, 6) == 4 + return (mod1(dir, 6), mod1(r + 1, rows), mod1(c, cols)) + elseif mod1(dir + rot, 6) == 5 + return (mod1(dir, 6), mod1(r + 1, rows), mod1(c - 1, cols)) + elseif mod1(dir + rot, 6) == 6 + return (mod1(dir, 6), mod1(r, rows), mod1(c - 1, cols)) + end +end + +function _truncway(trunctype::Symbol, D::E) where {E <: ElementarySpace} + if trunctype == :truncdim + return truncdim(dim(D)) + else + return notrunc() + end +end + +function rotr60(A::T, dir::Int) where {E, S, T <: AbstractTensorMap{E, S, 1, 6}} + if dir == 0 + return A + else + return rotr60(permute(A, ((1,), (7, 2, 3, 4, 5, 6))), dir - 1) + end +end + +function rotr60(A::T, dir::Int) where {E, S, T <: AbstractTensorMap{E, S, 3, 3}} + if dir == 0 + return A + else + return rotr60(permute(A, ((2, 3, 6), (1, 4, 5))), dir - 1) + end +end + +function rotl60(A::T, dir::Int) where {E, S, T <: AbstractTensorMap{E, S, 1, 6}} + if dir == 0 + return A + else + return rotl60(permute(A, ((1,), (3, 4, 5, 6, 7, 2))), dir - 1) + end +end + +function rotl60(A::T, dir::Int) where {E, S, T <: AbstractTensorMap{E, S, 3, 3}} + if dir == 0 + return A + else + return rotl60(permute(A, ((4, 1, 2), (5, 6, 3))), dir - 1) + end +end diff --git a/src/environments/ctmrg_tria_environments.jl b/src/environments/ctmrg_tria_environments.jl new file mode 100644 index 000000000..6d961100a --- /dev/null +++ b/src/environments/ctmrg_tria_environments.jl @@ -0,0 +1,77 @@ +#TODO: Add docs and figure +struct CTMRGTriaEnv{T} + "6 x rows x cols array of corner C tensors, where the first dimension specifies the spatial direction" + C::Array{T, 3} + "6 x rows x cols array of edge Ta tensors, where the first dimension specifies the spatial direction" + Ea::Array{T, 3} + "6 x rows x cols array of edge Ta tensors, where the first dimension specifies the spatial direction" + Eb::Array{T, 3} +end +# function CTMRGTriaEnv(corners::Array{C, 3}, edges::Array{T, 3}) where {C, T} +# foreach(check_environment_virtualspace, edges) +# return CTMRGTriaEnv{C, T}(corners, edges) +# end + +# """ +# CTMRGEnv( +# [f=randn, T=ComplexF64], Ds_north::A, Ds_east::A, chis_north::B, [chis_east::B], [chis_south::B], [chis_west::B] +# ) where {A<:AbstractMatrix{<:VectorSpace}, B<:AbstractMatrix{<:ElementarySpace}} + +# Construct a CTMRG environment by specifying matrices of north and east virtual spaces of the +# corresponding partition function and the north, east, south and west virtual spaces of the +# environment. Each respective matrix entry corresponds to a site in the unit cell. By +# default, the virtual environment spaces for all directions are taken to be the same. + +# The environment virtual spaces for each site correspond to the north or east virtual space +# of the corresponding edge tensor for each direction. Specifically, for a given site +# `(r, c)`, `chis_north[r, c]` corresponds to the east space of the north edge tensor, +# `chis_east[r, c]` corresponds to the north space of the east edge tensor, +# `chis_south[r, c]` corresponds to the east space of the south edge tensor, and +# `chis_west[r, c]` corresponds to the north space of the west edge tensor. + +# Each entry of the `Ds_north` and `Ds_east` matrices corresponds to an effective local space +# of the partition function, and can be represented as an `ElementarySpace` (e.g. for the case +# of a partition function defined in terms of local rank-4 tensors) or a `ProductSpace` (e.g. +# for the case of a network representing overlaps of PEPSs and PEPOs). +# """ +function get_Ds(D::A) where {A <: ProductSpace} + return [dir > 3 ? reverse(D') : D for dir in 1:6] +end + +function get_Ds(D::A) where {A <: ElementarySpace} + return [dir > 3 ? D' : D for dir in 1:6] +end + +function CTMRGTriaEnv( + f, T, D::Union{A, B}, chis::B; unitcell::Tuple{Int, Int} = (1, 1) + ) where { + A <: ProductSpace, B <: ElementarySpace, + } + if typeof(D) <: ElementarySpace + N = 1 + else + N = length(D) + end + st = spacetype(D) + T_type = tensormaptype(st, N + 1, 1, T) + + Cs = Array{T_type}(undef, 6, unitcell...) + Eas = Array{T_type}(undef, 6, unitcell...) + Ebs = Array{T_type}(undef, 6, unitcell...) + + Ds = get_Ds(D) + for dir in 1:6, r in 1:unitcell[1], c in 1:unitcell[2] + C = _edge_tensor(f, T, chis, Ds[dir], chis) + Ea = _edge_tensor(f, T, chis, Ds[dir], chis) + Eb = _edge_tensor(f, T, chis, Ds[mod1(dir + 1, 6)], chis) + + C /= norm(C) + Ea /= norm(Ea) + Eb /= norm(Eb) + + Cs[dir, r, c] = C + Eas[dir, r, c] = Ea + Ebs[dir, r, c] = Eb + end + return CTMRGTriaEnv(Cs, Eas, Ebs) +end diff --git a/src/networks/infinitetriangularnetwork.jl b/src/networks/infinitetriangularnetwork.jl new file mode 100644 index 000000000..2ffa61ab0 --- /dev/null +++ b/src/networks/infinitetriangularnetwork.jl @@ -0,0 +1,174 @@ +const PEPSTriaTensor{S <: ElementarySpace} = AbstractTensorMap{<:Any, S, 1, 6} + +""" +$(TYPEDEF) + +Contractible square network. Wraps a matrix of 'rank-6-tensor-like' objects. + +## Fields + +$(TYPEDFIELDS) +""" +struct InfiniteTriangularNetwork{O} + A::Matrix{O} + InfiniteTriangularNetwork{O}(A::Matrix{O}) where {O} = new{O}(A) + function InfiniteTriangularNetwork(A::Matrix) + # for I in eachindex(IndexCartesian(), A) + # d, w = Tuple(I) + # northwest_virtualspace(A[d, w]) == + # _elementwise_dual(southeast_virtualspace(A[_prev(d, end), w])) || throw( + # SpaceMismatch("North virtual space at site $((d, w)) does not match.") + # ) + # northeast_virtualspace(A[d, w]) == + # _elementwise_dual(southwest_virtualspace(A[d, _next(w, end)])) || + # throw(SpaceMismatch("East virtual space at site $((d, w)) does not match.")) + # east_virtualspace(A[d, w]) == + # _elementwise_dual(west_virtualspace(A[d, _next(w, end)])) || + # throw(SpaceMismatch("East virtual space at site $((d, w)) does not match.")) + # end + return InfiniteTriangularNetwork{eltype(A)}(A) + end +end +InfiniteTriangularNetwork(n::InfiniteTriangularNetwork) = n + +## Unit cell interface + +unitcell(n::InfiniteTriangularNetwork) = n.A +Base.size(n::InfiniteTriangularNetwork, args...) = size(unitcell(n), args...) +Base.length(n::InfiniteTriangularNetwork) = length(unitcell(n)) +Base.eltype(n::InfiniteTriangularNetwork) = eltype(typeof(n)) +Base.eltype(::Type{InfiniteTriangularNetwork{O}}) where {O} = O + +Base.copy(n::InfiniteTriangularNetwork) = InfiniteTriangularNetwork(copy(unitcell(n))) +function Base.similar(n::InfiniteTriangularNetwork, T::Type{TorA} = scalartype(n)) where {TorA} + return InfiniteTriangularNetwork(map(t -> similar(t, T), unitcell(n))) +end +function Base.repeat(n::InfiniteTriangularNetwork, counts...) + return InfiniteTriangularNetwork(repeat(unitcell(n), counts...)) +end + +## Indexing +Base.getindex(n::InfiniteTriangularNetwork, args...) = Base.getindex(unitcell(n), args...) +function Base.setindex!(n::InfiniteTriangularNetwork, args...) + return (Base.setindex!(unitcell(n), args...); n) +end +Base.axes(n::InfiniteTriangularNetwork, args...) = axes(unitcell(n), args...) +eachcoordinate(n::InfiniteTriangularNetwork) = collect(Iterators.product(axes(n)...)) +function eachcoordinate(n::InfiniteTriangularNetwork, dirs) + return collect(Iterators.product(dirs, axes(n, 1), axes(n, 2))) +end + +## Spaces + +TensorKit.spacetype(::Type{T}) where {T <: InfiniteTriangularNetwork} = spacetype(eltype(T)) +function virtualspace(n::InfiniteTriangularNetwork, r::Int, c::Int, dir) + Nr, Nc = size(n) + return virtualspace(n[mod1(r, Nr), mod1(c, Nc)], dir) +end + +## Vector interface + +function VectorInterface.scalartype(::Type{T}) where {T <: InfiniteTriangularNetwork} + return scalartype(eltype(T)) +end +function VectorInterface.zerovector(A::InfiniteTriangularNetwork) + return InfiniteTriangularNetwork(zerovector(unitcell(A))) +end + +## Math (for Zygote accumulation) + +function Base.:+(A₁::InfiniteTriangularNetwork, A₂::InfiniteTriangularNetwork) + return InfiniteTriangularNetwork(_add_localsandwich.(unitcell(A₁), unitcell(A₂))) +end +function Base.:-(A₁::InfiniteTriangularNetwork, A₂::InfiniteTriangularNetwork) + return InfiniteTriangularNetwork(_subtract_localsandwich.(unitcell(A₁), unitcell(A₂))) +end +function Base.:*(α::Number, A::InfiniteTriangularNetwork) + return InfiniteTriangularNetwork(_mul_localsandwich.(α, unitcell(A))) +end +Base.:*(A::InfiniteTriangularNetwork, α::Number) = α * A +function Base.:/(A::InfiniteTriangularNetwork, α::Number) + return A * inv(α) +end +function LinearAlgebra.dot(A₁::InfiniteTriangularNetwork, A₂::InfiniteTriangularNetwork) + return dot(unitcell(A₁), unitcell(A₂)) +end +LinearAlgebra.norm(A::InfiniteTriangularNetwork) = norm(unitcell(A)) + +## (Approximate) equality + +function Base.:(==)(A₁::InfiniteTriangularNetwork, A₂::InfiniteTriangularNetwork) + return all(zip(unitcell(A₁), unitcell(A₂))) do (p₁, p₂) + return p₁ == p₂ + end +end +function Base.isapprox(A₁::InfiniteTriangularNetwork, A₂::InfiniteTriangularNetwork; kwargs...) + return all(zip(unitcell(A₁), unitcell(A₂))) do (p₁, p₂) + return _isapprox_localsandwich(p₁, p₂; kwargs...) + end +end + +## Rotations + +function rotl60(n::InfiniteTriangularNetwork) + return InfiniteTriangularNetwork(rotl60(_rotl60_localsandwich.(unitcell(n)))) +end +function rotr60(n::InfiniteTriangularNetwork) + return InfiniteTriangularNetwork(rotr60(_rotr60_localsandwich.(unitcell(n)))) +end +function rot180(n::InfiniteTriangularNetwork) + return InfiniteTriangularNetwork(rot180(_rot180_localsandwich.(unitcell(n)))) +end + +## Chainrules + +# generic implementation +function ChainRulesCore.rrule( + ::typeof(Base.getindex), network::InfiniteTriangularNetwork, r::Int, c::Int + ) + O = network[r, c] + + function getindex_pullback(ΔO_) + ΔO = map(unthunk, ΔO_) + if ΔO isa Tangent + ΔO = ChainRulesCore.construct(typeof(O), ChainRulesCore.backing(ΔO)) + end + Δnetwork = zerovector(network) + Δnetwork[r, c] = ΔO + return NoTangent(), Δnetwork, NoTangent(), NoTangent() + end + return O, getindex_pullback +end + +# specialized PFTensor implementation +function ChainRulesCore.rrule( + ::typeof(Base.getindex), network::InfiniteTriangularNetwork{<:PFTensor}, r::Int, c::Int + ) + O = network[r, c] + + function getindex_pullback(ΔO_) + ΔO = unthunk(ΔO_) + Δnetwork = zerovector(network) + Δnetwork[r, c] = ΔO + return NoTangent(), Δnetwork, NoTangent(), NoTangent() + end + return O, getindex_pullback +end + +# function ChainRulesCore.rrule(::typeof(rotl90), network::InfiniteTriangularNetwork) +# network´ = rotl90(network) +# function rotl90_pullback(Δnetwork_) +# Δnetwork = unthunk(Δnetwork_) +# return NoTangent(), rotr90(Δnetwork) +# end +# return network´, rotl90_pullback +# end + +# function ChainRulesCore.rrule(::typeof(rotr90), network::InfiniteTriangularNetwork) +# network´ = rotr90(network) +# function rotr90_pullback(Δnetwork) +# Δnetwork = unthunk(Δnetwork) +# return NoTangent(), rotl90(Δnetwork) +# end +# return network´, rotr90_pullback +# end diff --git a/src/networks/local_triangular_sandwich.jl b/src/networks/local_triangular_sandwich.jl new file mode 100644 index 000000000..380fa18e9 --- /dev/null +++ b/src/networks/local_triangular_sandwich.jl @@ -0,0 +1,43 @@ +# +# Interface for local effective-rank-6 tensor sandwiches +# + +# route all virtualspace getters through a single method for convenience +northwest_virtualspace(O::AbstractTensorMap{E, S, 1, 6}, args...) where {E, S} = domain(O)[1] +northeast_virtualspace(O::AbstractTensorMap{E, S, 1, 6}, args...) where {E, S} = domain(O)[2] +east_virtualspace(O::AbstractTensorMap{E, S, 1, 6}, args...) where {E, S} = domain(O)[3] +southeast_virtualspace(O::AbstractTensorMap{E, S, 1, 6}, args...) where {E, S} = domain(O)[4] +southwest_virtualspace(O::AbstractTensorMap{E, S, 1, 6}, args...) where {E, S} = domain(O)[5] +west_virtualspace(O::AbstractTensorMap{E, S, 1, 6}, args...) where {E, S} = domain(O)[6] + +## Rotations + +# generic local interface +_rotl60_localsandwich(O) = rotl60.(O) +_rotr60_localsandwich(O) = rotr60.(O) + +## PartitionFunction + +# specialized local rotation interface +_rotl60_localsandwich(O::PFTriaTensor) = rotl60(O) +_rotr60_localsandwich(O::PFTriaTensor) = rotr60(O) +_rot180_localsandwich(O::PFTriaTensor) = rot180(O) + +# specialized local math interface +_add_localsandwich(O1::PFTriaTensor, O2::PFTriaTensor) = O1 + O2 +_subtract_localsandwich(O1::PFTriaTensor, O2::PFTriaTensor) = O1 - O2 +_mul_localsandwich(α::Number, O::PFTriaTensor) = α * O +_isapprox_localsandwich(O1::PFTriaTensor, O2::PFTriaTensor; kwargs...) = isapprox(O1, O2; kwargs...) + +## PEPS + +const PEPSTriaSandwich{T <: PEPSTriaTensor} = Tuple{T, T} + +ket(O::PEPSTriaSandwich) = O[1] +bra(O::PEPSTriaSandwich) = O[2] + +function virtualspace(O::PEPSTriaSandwich, dir) + return virtualspace(ket(O), dir) ⊗ virtualspace(bra(O), dir)' +end + +TensorKit.spacetype(::Type{P}) where {P <: PEPSTriaSandwich} = spacetype(eltype(P)) diff --git a/src/networks/tensors_tria.jl b/src/networks/tensors_tria.jl new file mode 100644 index 000000000..cca27ff1f --- /dev/null +++ b/src/networks/tensors_tria.jl @@ -0,0 +1,101 @@ +# +# Partition function +# + +""" + const PartitionFunctionTriaTensor{S} + +Default type for partition function tensors with 6 virtual indices, conventionally ordered +as: ``T : W ⊗ SW ⊗ SE ← NW ⊗ NE ⊗ E``. Here ``NW``, ``NE``, ``E``, ``SE``, ``SW`` and ``W`` denote the +north-west (120°), north-east (60°), east (0°), south-east (300°), south-west (240°) and west (180°) spaces, respectively, +where the angles denote the directions of the legs with respect to the positive horizontal axis. + +``` + NW NE + ╲ ╱ + ╲ ╱ + ╲ ╱ + W ----- T ----- E + ╱ ╲ + ╱ ╲ + ╱ ╲ + SW P SE +``` +""" +const PartitionFunctionTriaTensor{S <: ElementarySpace} = AbstractTensorMap{<:Any, S, 3, 3} +const PFTriaTensor = PartitionFunctionTriaTensor + +""" + PartitionFunctionTriaTensor(f, ::Type{T}, NWspace::S, + [NEspace::S], [Espace::S], [SEspace::S], + [SWspace::S], [Wspace::S]) where {T,S<:Union{Int,ElementarySpace}} + +Construct a PartitionFunctionTriaTensor tensor based on the +north-west, north-east, east, south-east, south-west and west spaces +The tensor elements are generated based on `f` and the element type is specified in `T`. +""" +function PartitionFunctionTriaTensor( + f, ::Type{T}, + NWspace::S, NEspace::S = NWspace, Espace::S = NWspace, SEspace::S = NWspace, + SWspace::S = NWspace, Wspace::S = NWspace, + ) where {T, S <: ElementarySpace} + return f(T, Wspace ⊗ SWspace ⊗ SEspace ← NWspace ⊗ NEspace ⊗ Espace) +end + +function virtualspace(t::PFTriaTensor, dir) + invp = (4, 5, 6, 3, 2, 1) # internally, virtual directions are ordered as NW, NE, E, SE, SW... + return space(t, invp[dir]) +end + +# +# PEPS +# + +""" + const PEPSTriaTensor{S} + +Default type for PEPS tensors with a single physical index, and 6 virtual indices, +conventionally ordered as: ``T : P ← NW ⊗ NE ⊗ E ⊗ SE ⊗ SW ⊗ W``. Here, ``P`` denotes the physical space +and ``NW``, ``NE``, ``E``, ``SE``, ``SW`` and ``W`` denote the north-west (120°), north-east (60°), +east (0°), south-east (300°), south-west (240°) and west (180°) spaces, respectively, +where the angles denote the directions of the legs with respect to the positive horizontal axis. + +``` +``` + NW NE + ╲ ╱ + ╲ ╱ + ╲ ╱ + W ----- T ----- E + ╱|╲ + ╱ | ╲ + ╱ | ╲ + SW P SE +``` +``` +""" +const PEPSTriaTensor{S <: ElementarySpace} = AbstractTensorMap{<:Any, S, 1, 6} + +""" + PEPSTensor(f, ::Type{T}, Pspace::S, NWspace::S, + [NEspace::S], [Espace::S], [SEspace::S], + [SWspace::S], [Wspace::S]) where {T,S<:Union{Int,ElementarySpace}} + +Construct a PEPS tensor based on the physical, north-west, north-east, east, south-east, south-west and west spaces. +The tensor elements are generated based on `f` and the element type is specified in `T`. +""" +function PEPSTriaTensor( + f, ::Type{T}, + Pspace::S, + NWspace::S, NEspace::S = NWspace, Espace::S = NWspace, SEspace::S = NWspace, + SWspace::S = NWspace, Wspace::S = NWspace, + ) where {T, S <: ElementarySpace} + return f(T, Pspace ← NWspace ⊗ NEspace ⊗ Espace ⊗ SEspace ⊗ SWspace ⊗ Wspace) +end + +rotl60(t::PEPSTriaTensor) = permute(t, ((1,), (3, 4, 5, 6, 7, 2))) +rotr60(t::PEPSTriaTensor) = permute(t, ((1,), (7, 2, 3, 4, 5, 6))) +Base.rot180(t::PEPSTriaTensor) = permute(t, ((1,), (5, 6, 7, 2, 3, 4))) + +physicalspace(t::PEPSTriaTensor) = space(t, 1) +virtualspace(t::PEPSTriaTensor, dir) = space(t, dir + 1) diff --git a/test/ctmrg/triangular.jl b/test/ctmrg/triangular.jl new file mode 100644 index 000000000..f3973a4ad --- /dev/null +++ b/test/ctmrg/triangular.jl @@ -0,0 +1,95 @@ +using Test +using Random +using MatrixAlgebraKit +using TensorKit +using MPSKit +using PEPSKit +using OptimKit +using Zygote + +@testset "CTM_triangular - Random tensor" begin + for conditioning in [true false] + for projector_alg in [:twothirds :full] + alg = SimultaneousCTMRGTria(; conditioning, projector_alg) + T = Float64 + χ = 7 + pspace = ℂ^2 + vspace = ℂ^3 + envspace = ℂ^χ + + ket = randn(T, pspace, vspace ⊗ vspace ⊗ vspace ⊗ vspace' ⊗ vspace' ⊗ vspace') + bra = copy(ket) + pf = randn(T, vspace ⊗ vspace ⊗ vspace, vspace ⊗ vspace ⊗ vspace) + sandwiches = [pf, (ket, bra)] + unitcell = (1, 1) + + for (sandwich, vspace) in zip(sandwiches, [vspace, vspace ⊗ vspace']) + network = InfiniteTriangularNetwork(fill(sandwich, unitcell)) + env₀ = CTMRGTriaEnv(randn, T, vspace, envspace; unitcell) + env, info = leading_boundary(env₀, network, alg) + @test info.convergence_metric < 1.0 + end + end + end +end + +@testset "CTM_triangular - Differentiability" begin + for conditioning in [true false] + for projector_alg in [:twothirds :full] + alg = SimultaneousCTMRGTria(; conditioning, projector_alg) + T = Float64 + χ = 7 + pspace = ℂ^2 + vspace = ℂ^3 + envspace = ℂ^χ + ket = randn(T, pspace, vspace ⊗ vspace ⊗ vspace ⊗ vspace' ⊗ vspace' ⊗ vspace') + bra = copy(ket) + + unitcell = (1, 1) + network = InfiniteTriangularNetwork(fill((ket, bra), unitcell)) + env₀ = CTMRGTriaEnv(randn, T, vspace ⊗ vspace', envspace; unitcell) + + optimizer_alg = LBFGS(4; maxiter = 10) + real_inner(_, η₁, η₂) = real(dot(η₁, η₂)) + reuse_env = true + peps = copy(ket) + + function peps_retract(x, η, α) + peps = x[1] + env = deepcopy(x[2]) + + retractions = norm_preserving_retract.(unitcell(peps), unitcell(η), α) + peps´ = InfinitePEPS(map(first, retractions)) + ξ = InfinitePEPS(map(last, retractions)) + + return (peps´, env), ξ + end + retract = peps_retract + + + # optimize operator cost function + (peps_final, env_final), cost_final, ∂cost, numfg, convergence_history = optimize( + (peps₀, env₀), optimizer_alg; + retract, inner = real_inner, finalize!, (transport!) = (peps_transport!), + ) do (peps, env) + start_time = time_ns() + E, gs = withgradient(peps) do ψ + env′, info = hook_pullback( + leading_boundary, env, ψ, alg.boundary_alg; + alg_rrule = alg.gradient_alg, + ) + # ignore_derivatives() do + # reuse_env && update!(env, env′) + # push!(truncation_errors, info.truncation_error) + # push!(condition_numbers, info.condition_number) + # end + return energy(ψ, env′, operator) + end + g = only(gs) # `withgradient` returns tuple of gradients `gs` + push!(gradnorms_unitcell, norm.(g.A)) + push!(times, (time_ns() - start_time) * 1.0e-9) + return E, g + end + end + end +end From e6414ee6d77b1388a2a29aae4c39bff8579357de Mon Sep 17 00:00:00 2001 From: sanderdemeyer Date: Fri, 23 Jan 2026 17:27:41 +0100 Subject: [PATCH 02/41] small fixes --- test/ctmrg/triangular.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/ctmrg/triangular.jl b/test/ctmrg/triangular.jl index f3973a4ad..e18ac28d0 100644 --- a/test/ctmrg/triangular.jl +++ b/test/ctmrg/triangular.jl @@ -52,7 +52,7 @@ end optimizer_alg = LBFGS(4; maxiter = 10) real_inner(_, η₁, η₂) = real(dot(η₁, η₂)) reuse_env = true - peps = copy(ket) + peps₀ = copy(ket) function peps_retract(x, η, α) peps = x[1] @@ -70,7 +70,7 @@ end # optimize operator cost function (peps_final, env_final), cost_final, ∂cost, numfg, convergence_history = optimize( (peps₀, env₀), optimizer_alg; - retract, inner = real_inner, finalize!, (transport!) = (peps_transport!), + retract, inner = real_inner, ) do (peps, env) start_time = time_ns() E, gs = withgradient(peps) do ψ From c36b3a0efdeda578c7ce3a4454d53dfed9ab5572 Mon Sep 17 00:00:00 2001 From: sanderdemeyer Date: Mon, 23 Feb 2026 20:25:49 +0100 Subject: [PATCH 03/41] updates on Triangular CTMRG Name changes to make them more consistent and verbose Use index convention in contractions Add some stuff necessary for differentiability --- src/PEPSKit.jl | 16 +- .../ctmrg_contractions_triangular.jl | 58 +++ .../ctmrg.jl | 8 +- .../ctmrg_triangular/simultaneous.jl | 240 +++++++++ .../utility.jl | 4 + .../fixed_point_differentiation.jl | 464 ++++++++++++++++++ src/algorithms/select_algorithm.jl | 2 +- src/algorithms/toolbox_triangular.jl | 126 +++++ .../triangular_ctmrg/simultaneous.jl | 319 ------------ ...ts.jl => ctmrg_environments_triangular.jl} | 10 +- src/networks/infinitetriangularnetwork.jl | 2 +- src/networks/local_triangular_sandwich.jl | 24 +- ...{tensors_tria.jl => tensors_triangular.jl} | 30 +- src/states/infinitepepstriangular.jl | 268 ++++++++++ test/ctmrg/triangular.jl | 176 ++++--- 15 files changed, 1328 insertions(+), 419 deletions(-) create mode 100644 src/algorithms/contractions/ctmrg_contractions_triangular.jl rename src/algorithms/{triangular_ctmrg => ctmrg_triangular}/ctmrg.jl (82%) create mode 100644 src/algorithms/ctmrg_triangular/simultaneous.jl rename src/algorithms/{triangular_ctmrg => ctmrg_triangular}/utility.jl (92%) create mode 100644 src/algorithms/optimization/fixed_point_differentiation.jl create mode 100644 src/algorithms/toolbox_triangular.jl delete mode 100644 src/algorithms/triangular_ctmrg/simultaneous.jl rename src/environments/{ctmrg_tria_environments.jl => ctmrg_environments_triangular.jl} (92%) rename src/networks/{tensors_tria.jl => tensors_triangular.jl} (75%) create mode 100644 src/states/infinitepepstriangular.jl diff --git a/src/PEPSKit.jl b/src/PEPSKit.jl index e5a23f596..0e222aec7 100644 --- a/src/PEPSKit.jl +++ b/src/PEPSKit.jl @@ -72,11 +72,12 @@ include("utility/initialization.jl") include("networks/tensors.jl") include("networks/local_sandwich.jl") include("networks/infinitesquarenetwork.jl") -include("networks/tensors_tria.jl") +include("networks/tensors_triangular.jl") include("networks/local_triangular_sandwich.jl") include("networks/infinitetriangularnetwork.jl") include("states/infinitepeps.jl") +include("states/infinitepepstriangular.jl") include("states/infinitepartitionfunction.jl") include("utility/symmetrization.jl") @@ -92,7 +93,7 @@ include("operators/lattices/squarelattice.jl") include("operators/models.jl") include("environments/ctmrg_environments.jl") -include("environments/ctmrg_tria_environments.jl") +include("environments/ctmrg_environments_triangular.jl") include("environments/vumps_environments.jl") include("environments/suweight.jl") include("environments/bp_environments.jl") @@ -143,9 +144,10 @@ include("algorithms/ctmrg/gaugefix.jl") include("algorithms/ctmrg/c4v.jl") include("algorithms/ctmrg/initialization.jl") -include("algorithms/triangular_ctmrg/ctmrg.jl") -include("algorithms/triangular_ctmrg/utility.jl") -include("algorithms/triangular_ctmrg/simultaneous.jl") +include("algorithms/contractions/ctmrg_contractions_triangular.jl") +include("algorithms/ctmrg_triangular/ctmrg.jl") +include("algorithms/ctmrg_triangular/utility.jl") +include("algorithms/ctmrg_triangular/simultaneous.jl") include("algorithms/truncation/truncationschemes.jl") include("algorithms/truncation/fullenv_truncation.jl") @@ -174,6 +176,8 @@ include("algorithms/expectation_value/network_value.jl") include("algorithms/expectation_value/correlator_adapters.jl") include("algorithms/expectation_value/correlators.jl") include("algorithms/toolbox.jl") +include("algorithms/toolbox_triangular.jl") +include("algorithms/correlators.jl") include("algorithms/optimization/implicit_differentiation.jl") include("algorithms/optimization/preconditioning.jl") @@ -189,6 +193,7 @@ export CTMRGTriaEnv, SimultaneousCTMRGTria export initialize_ctmrg_environment, RandomInitialization, ProductStateInitialization, ApplicationInitialization, IdentityInitialization export corner, edge, setcorner!, setedge! +export CTMRGEnvTriangular, SimultaneousCTMRGTriangular # CTMRGTriaEnv, SimultaneousCTMRGTria export FixedSpaceTruncation, SiteDependentTruncation export HalfInfiniteProjector, FullInfiniteProjector export C4vCTMRG, C4vEighProjector, C4vQRProjector @@ -212,6 +217,7 @@ export TimeEvolver, timestep, time_evolve export InfiniteSquareNetwork, InfiniteTriangularNetwork export InfinitePartitionFunction export InfinitePEPS, InfiniteTransferPEPS +export InfinitePEPSTriangular export SUWeight export InfinitePEPO, InfiniteTransferPEPO diff --git a/src/algorithms/contractions/ctmrg_contractions_triangular.jl b/src/algorithms/contractions/ctmrg_contractions_triangular.jl new file mode 100644 index 000000000..d00515d6b --- /dev/null +++ b/src/algorithms/contractions/ctmrg_contractions_triangular.jl @@ -0,0 +1,58 @@ +function build_double_corner_matrix_triangular(network::InfiniteTriangularNetwork{P}, env::CTMRGEnvTriangular, dir::Int, r::Int, c::Int) where {P <: PFTensorTriangular} + @tensor opt = true mat[-1 -2; -3 -4] := env.C[_coordinates(dir, 0, r, c, size(network))...][6 5; 1] * env.C[_coordinates(dir + 1, 0, r, c, size(network))...][1 3; 2] * + env.Ea[_coordinates(dir - 1, 0, r, c, size(network))...][-1 7; 6] * env.Eb[_coordinates(dir + 1, 0, r, c, size(network))...][2 4; -3] * rotl60(network[r, c], mod(dir - 1, 6))[7 -2 -4; 5 3 4] + return mat +end + +function build_double_corner_matrix_triangular(network::InfiniteTriangularNetwork{P}, env::CTMRGEnvTriangular, dir::Int, r::Int, c::Int) where {P <: PEPSSandwichTriangular} + @tensor opt = true mat[-1 -2 -3; -4 -5 -6] := env.C[_coordinates(dir, 0, r, c, size(network))...][6 5 8; 1] * env.C[_coordinates(dir + 1, 0, r, c, size(network))...][1 3 9; 2] * + env.Ea[_coordinates(dir - 1, 0, r, c, size(network))...][-1 7 10; 6] * env.Eb[_coordinates(dir + 1, 0, r, c, size(network))...][2 4 11; -4] * + rotl60(ket(network[r, c]), mod(dir - 1, 6))[12; 5 3 4 -5 -2 7] * conj(rotl60(bra(network[r, c]), mod(dir - 1, 6))[12; 8 9 11 -6 -3 10]) + return mat +end + +function semi_renormalize_edge(network::InfiniteTriangularNetwork{P}, env::CTMRGEnvTriangular, Pas, Pbs, dir, r, c) where {P <: PEPSSandwichTriangular} + @tensor opt = true mat[-1 -2 -3; -4 -5 -6] := Pas[dir, r, c][-1; 1 2 8] * Pbs[dir, r, c][6 7 9; -4] * + env.Ea[_coordinates(dir, 0, r, c, size(network))...][1 3 10; 4] * env.Eb[_coordinates(dir, 1, r, c, size(network))...][4 5 11; 6] * + rotl60(ket(network[r, c]), mod(dir - 1, 6))[12; 3 5 7 -5 -2 2] * conj(rotl60(bra(network[r, c]), mod(dir - 1, 6))[12; 10 11 9 -6 -3 8]) + return mat / norm(mat) +end + +function semi_renormalize_edge(network::InfiniteTriangularNetwork{P}, env::CTMRGEnvTriangular, Pas, Pbs, dir, r, c) where {P <: PFTensorTriangular} + @tensor opt = true mat[-1 -2; -3 -4] := Pas[dir, r, c][-1; 1 2] * Pbs[dir, r, c][6 7; -3] * + env.Ea[_coordinates(dir, 0, r, c, size(network))...][1 3; 4] * env.Eb[_coordinates(dir, 1, r, c, size(network))...][4 5; 6] * rotl60(network[r, c], mod(dir - 1, 6))[2 -2 -4; 3 5 7] + return mat / norm(mat) +end + +function build_halfinfinite_projectors(network::InfiniteTriangularNetwork{P}, env::CTMRGEnvTriangular, Ẽas, Ẽbs, Ẽastr, Ẽbstr, dir, r, c) where {P <: PEPSSandwichTriangular} + @tensor opt = true σL[-1 -2 -3; -4] := env.C[_coordinates(dir, 0, r, c, size(network))...][1 2 10; 8] * env.C[_coordinates(dir - 1, 0, r, c, size(network))...][4 3 11; 1] * env.C[_coordinates(dir - 2, 0, r, c, size(network))...][6 5 12; 4] * + Ẽbs[_coordinates(dir, 1, r, c, size(network))...][8 9 13; -4] * Ẽastr[_coordinates(dir - 3, 0, r, c, size(network))...][-1 7 14; 6] * + rotl60(ket(network[r, c]), mod(dir - 1, 6))[15 2 9 -2 7 5 3] * conj(rotl60(bra(network[r, c]), mod(dir - 1, 6))[15; 10 13 -3 14 12 11]) + @tensor opt = true σR[-1; -2 -3 -4] := env.C[_coordinates(dir + 1, 0, r, c, size(network))...][8 2 10; 1] * env.C[_coordinates(dir + 2, 0, r, c, size(network))...][1 3 11; 4] * env.C[_coordinates(dir + 3, 0, r, c, size(network))...][4 5 12; 6] * + Ẽbstr[_coordinates(dir + 3, -1, r, c, size(network))...][6 7 13; -2] * Ẽas[_coordinates(dir, 0, r, c, size(network))...][-1 9 14; 8] * + rotl60(ket(network[_coordinates(dir + 2, 0, r, c, size(network))[2:3]...]), mod(dir - 1, 6))[15; 9 2 3 5 7 -3] * conj(rotl60(bra(network[_coordinates(dir + 2, 0, r, c, size(network))[2:3]...]), mod(dir - 1, 6))[15; 14 10 11 12 13 -4]) + return σL, σR +end + +function build_halfinfinite_projectors(network::InfiniteTriangularNetwork{P}, env::CTMRGEnvTriangular, Ẽas, Ẽbs, Ẽastr, Ẽbstr, dir, r, c) where {P <: PFTensorTriangular} + @tensor opt = true σL[-1 -2; -3] := env.C[_coordinates(dir, 0, r, c, size(network))...][1 2; 8] * env.C[_coordinates(dir - 1, 0, r, c, size(network))...][4 3; 1] * env.C[_coordinates(dir - 2, 0, r, c, size(network))...][6 5; 4] * + Ẽbs[_coordinates(dir, 1, r, c, size(network))...][8 9; -3] * Ẽastr[_coordinates(dir - 3, 0, r, c, size(network))...][-1 7; 6] * + rotl60(network[r, c], mod(dir - 1, 6))[3 5 7; 2 9 -2] + @tensor opt = true σR[-1; -2 -3] := env.C[_coordinates(dir + 1, 0, r, c, size(network))...][8 2; 1] * env.C[_coordinates(dir + 2, 0, r, c, size(network))...][1 3; 4] * env.C[_coordinates(dir + 3, 0, r, c, size(network))...][4 5; 6] * + Ẽbstr[_coordinates(dir + 3, -1, r, c, size(network))...][6 7; -2] * Ẽas[_coordinates(dir, 0, r, c, size(network))...][-1 9; 8] * + rotl60(network[_coordinates(dir + 2, 0, r, c, size(network))[2:3]...], mod(dir - 1, 6))[-3 7 5; 9 2 3] + return σL, σR +end + +function renormalize_corner_triangular((dir, r, c), network::InfiniteTriangularNetwork{P}, env, Pas, Pbs) where {P <: PFTensorTriangular} + @tensor opt = true env_C_new[-1 -2; -3] := env.C[_coordinates(dir, 0, r, c, size(network))...][1 3; 6] * env.Ea[_coordinates(dir - 1, 0, r, c, size(network))...][4 2; 1] * env.Eb[_coordinates(dir, 1, r, c, size(network))...][6 7; 8] * + rotl60(network[r, c], mod(dir - 1, 6))[2 5 -2; 3 7 9] * Pas[mod1(dir - 1, 6), r, c][-1; 4 5] * Pbs[dir, r, c][8 9; -3] + return env_C_new +end + +function renormalize_corner_triangular((dir, r, c), network::InfiniteTriangularNetwork{P}, env, Pas, Pbs) where {P <: PEPSSandwichTriangular} + @tensor opt = true env_C_new[-1 -2 -3; -4] := env.C[_coordinates(dir, 0, r, c, size(network))...][1 3 10; 6] * env.Ea[_coordinates(dir - 1, 0, r, c, size(network))...][4 2 11; 1] * env.Eb[_coordinates(dir, 1, r, c, size(network))...][6 7 12; 8] * + rotl60(ket(network[r, c]), mod(dir - 1, 6))[15; 3 7 9 -2 5 2] * conj(rotl60(bra(network[r, c]), mod(dir - 1, 6))[15; 10 12 14 -3 13 11]) * + Pas[mod1(dir - 1, 6), r, c][-1; 4 5 13] * Pbs[dir, r, c][8 9 14; -4] + return env_C_new +end \ No newline at end of file diff --git a/src/algorithms/triangular_ctmrg/ctmrg.jl b/src/algorithms/ctmrg_triangular/ctmrg.jl similarity index 82% rename from src/algorithms/triangular_ctmrg/ctmrg.jl rename to src/algorithms/ctmrg_triangular/ctmrg.jl index 9e06e2ad4..f981de1e9 100644 --- a/src/algorithms/triangular_ctmrg/ctmrg.jl +++ b/src/algorithms/ctmrg_triangular/ctmrg.jl @@ -1,11 +1,11 @@ -abstract type CTMRGTriaAlgorithm end +abstract type CTMRGAlgorithmTriangular end -function leading_boundary(env₀::CTMRGTriaEnv, network::InfiniteTriangularNetwork; kwargs...) +function leading_boundary(env₀::CTMRGEnvTriangular, network::InfiniteTriangularNetwork; kwargs...) alg = select_algorithm(leading_boundary, env₀; kwargs...) return leading_boundary(env₀, network, alg) end function leading_boundary( - env₀::CTMRGTriaEnv, network::InfiniteTriangularNetwork, alg::CTMRGTriaAlgorithm + env₀::CTMRGEnvTriangular, network::InfiniteTriangularNetwork, alg::CTMRGAlgorithmTriangular ) log = ignore_derivatives(() -> MPSKit.IterLog("CTMRG")) return LoggingExtras.withlevel(; alg.verbosity) do @@ -33,7 +33,7 @@ function leading_boundary( return env, info end end -function leading_boundary(env₀::CTMRGTriaEnv, state, args...; kwargs...) +function leading_boundary(env₀::CTMRGEnvTriangular, state, args...; kwargs...) return leading_boundary(env₀, InfiniteTriangularNetwork(state), args...; kwargs...) end diff --git a/src/algorithms/ctmrg_triangular/simultaneous.jl b/src/algorithms/ctmrg_triangular/simultaneous.jl new file mode 100644 index 000000000..cddda61a2 --- /dev/null +++ b/src/algorithms/ctmrg_triangular/simultaneous.jl @@ -0,0 +1,240 @@ +struct SimultaneousCTMRGTriangular <: CTMRGAlgorithmTriangular + tol::Float64 + maxiter::Int + miniter::Int + verbosity::Int + conditioning::Bool + projector_alg::Symbol + trunctype::Symbol +end +function SimultaneousCTMRGTriangular(; + tol = Defaults.ctmrg_tol, + maxiter = Defaults.ctmrg_maxiter, + miniter = Defaults.ctmrg_miniter, + verbosity = Defaults.ctmrg_verbosity, + conditioning = true, + projector_alg = :twothirds, + trunctype = :truncrank + ) + return SimultaneousCTMRGTriangular(tol, maxiter, miniter, verbosity, conditioning, projector_alg, trunctype) +end + +# Based on +# https://arxiv.org/pdf/2510.04907 + +function ctmrg_iteration(network::InfiniteTriangularNetwork, env::CTMRGEnvTriangular, alg::SimultaneousCTMRGTriangular) + trunc_type = getfield(@__MODULE__, alg.trunctype) + if trunc_type == FixedSpaceTruncation + trunc = FixedSpaceTruncation() + else + trunc = trunc_type(dim(domain(env.C[1, 1, 1])[1])) + end + Pas, Pbs, S = calculate_projectors(network, env, trunc, alg.projector_alg) + + env = renormalize_corners!(network, env, Pas, Pbs) + env = normalize_corners!(env) + + Ẽas, Ẽbs, Ẽastr, Ẽbstr = semi_renormalize(network, env, Pas, Pbs, trunc) + Qas, Qbs = build_matrix_second_projectors(network, env, Ẽas, Ẽbs, Ẽastr, Ẽbstr, trunc; alg.conditioning) + + env = renormalize_edges(env, Ẽas, Ẽbs, Qas, Qbs) + normalize_edges!(env) + return env, S +end + +function calculate_projectors(network, env, trunc, projector_alg) + if projector_alg == :full + return calculate_full_projectors(network, env, trunc) + elseif projector_alg == :twothirds + return calculate_twothirds_projectors(network, env, trunc) + else + @error "projector_alg = $projector_alg not defined" + end +end + +function calculate_twothirds_projectors(network::InfiniteTriangularNetwork{P}, env, trunc) where {P} + coordinates = eachcoordinate(network, 1:6) + E = scalartype(env.C[1, 1, 1]) + S = spacetype(env.C[1, 1, 1]) + T_proj = Tuple{AbstractTensorMap{E, S, 1, 2 + (network[1, 1] isa Tuple)}, AbstractTensorMap{E, S, 2 + (network[1, 1] isa Tuple), 1}, DiagonalTensorMap{real(E), S}} + projectors′ = similar(coordinates, T_proj) + projectors = dtmap!!(projectors′, coordinates) do (dir, r, c) + ρL = build_double_corner_matrix_triangular(network, env, mod1(dir - 1, 6), r, c) + ρR = build_double_corner_matrix_triangular(network, env, mod1(dir + 1, 6), r, c) + ρρ = ρL * ρR + ρρ /= norm(ρρ) + + U, S, V = svd_trunc(ρρ; trunc) + + Pb = ρR * V' * sdiag_pow(S, -1 / 2) + Pa = sdiag_pow(S, -1 / 2) * U' * ρL + return Pa, Pb, S + end + return getindex.(projectors, 1), getindex.(projectors, 2), getindex.(projectors, 3) +end + +function calculate_full_projectors(network::InfiniteTriangularNetwork{P}, env, trunc) where {P} + coordinates = eachcoordinate(network, 1:6) + E = scalartype(env.C[1, 1, 1]) + S = spacetype(env.C[1, 1, 1]) + T_proj = Tuple{AbstractTensorMap{E, S, 1, 2 + (network[1, 1] isa Tuple)}, AbstractTensorMap{E, S, 2 + (network[1, 1] isa Tuple), 1}, DiagonalTensorMap{real(E), S}} + projectors′ = similar(coordinates, T_proj) + projectors = dtmap!!(projectors′, coordinates) do (dir, r, c) + ρL = build_double_corner_matrix_triangular(network, env, mod1(dir - 1, 6), r, c) + ρR = build_double_corner_matrix_triangular(network, env, mod1(dir + 1, 6), r, c) + ρ̄ = build_double_corner_matrix_triangular(network, env, mod1(dir + 3, 6), r, c) + ρ̄ /= norm(ρ̄) + Ū, S̄, V̄ᴴ = svd_full(ρ̄) + ρ̄ᴿ = Ū * sqrt(S̄) + ρ̄ᴸ = sqrt(S̄) * V̄ᴴ + ρρ = ρ̄ᴸ * ρL * ρR * ρ̄ᴿ + ρρ /= norm(ρρ) + + U, S, Vᴴ = svd_trunc(ρρ; trunc) + + Pb = ρR * ρ̄ᴿ * Vᴴ' * sdiag_pow(S, -1 / 2) + Pa = sdiag_pow(S, -1 / 2) * U' * ρ̄ᴸ * ρL + + return Pa, Pb, S + end + return getindex.(projectors, 1), getindex.(projectors, 2), getindex.(projectors, 3) +end + +function renormalize_corners!(network::InfiniteTriangularNetwork{P}, env, Pas, Pbs) where {P <: PEPSSandwichTriangular} + coordinates = eachcoordinate(network, 1:6) + new_corners′ = similar(env.C) + new_corners = dtmap!!(new_corners′, coordinates) do (dir, r, c) + return renormalize_corner_triangular((dir, r, c), network, env, Pas, Pbs) + end + return CTMRGEnvTriangular(new_corners, env.Ea, env.Eb) +end + +function renormalize_corners!(network::InfiniteTriangularNetwork{P}, env, Pas, Pbs) where {P <: PFTensorTriangular} + coordinates = eachcoordinate(network, 1:6) + new_corners′ = similar(env.C) + new_corners = dtmap!!(new_corners′, coordinates) do (dir, r, c) + return renormalize_corner_triangular((dir, r, c), network, env, Pas, Pbs) + end + return CTMRGEnvTriangular(new_corners, env.Ea, env.Eb) +end + +function _permute_edge(t::Union{T1, T2}) where {E, S, T1 <: AbstractTensorMap{E, S, 2, 1}, T2 <: AbstractTensorMap{E, S, 1, 2}} + return permute(t, ((1, 3), (2,))) +end + +function _permute_edge(t::Union{T1, T2}) where {E, S, T1 <: AbstractTensorMap{E, S, 3, 1}, T2 <: AbstractTensorMap{E, S, 1, 3}} + return permute(t, ((1, 3, 4), (2,))) +end + +function semi_renormalize(network::InfiniteTriangularNetwork, env::CTMRGEnvTriangular, Pas, Pbs, trunc) + coordinates = eachcoordinate(network, 1:6) + E = scalartype(env.C[1, 1, 1]) + S = spacetype(env.C[1, 1, 1]) + T_proj = Tuple{AbstractTensorMap{E, S, 2 + (network[1, 1] isa Tuple), 1}, AbstractTensorMap{E, S, 2 + (network[1, 1] isa Tuple), 1}, AbstractTensorMap{E, S, 2 + (network[1, 1] isa Tuple), 1}, AbstractTensorMap{E, S, 2 + (network[1, 1] isa Tuple), 1}} + projectors′ = similar(coordinates, T_proj) + projectors = dtmap!!(projectors′, coordinates) do (dir, r, c) + mat = semi_renormalize_edge(network, env, Pas, Pbs, dir, r, c) + + U, S, V = svd_full(mat) + + Ẽb = U * sqrt(S) + Ẽa = _permute_edge(sqrt(S) * V) + + Utr, Str, Vtr = svd_trunc(mat; trunc) + Ẽbtr = Utr * sqrt(Str) + Ẽatr = _permute_edge(sqrt(Str) * Vtr) + + return Ẽa, Ẽb, Ẽatr, Ẽbtr + end + return getindex.(projectors, 1), getindex.(projectors, 2), getindex.(projectors, 3), getindex.(projectors, 4) +end + +function build_matrix_second_projectors(network::InfiniteTriangularNetwork, env::CTMRGEnvTriangular, Ẽas, Ẽbs, Ẽastr, Ẽbstr, trunc; conditioning = true) + coordinates = eachcoordinate(network, 1:6) + E = scalartype(env.C[1, 1, 1]) + S = spacetype(env.C[1, 1, 1]) + T_proj = Tuple{AbstractTensorMap{E, S, 1, 1}, AbstractTensorMap{E, S, 1, 1}} + projectors′ = similar(coordinates, T_proj) + projectors = dtmap!!(projectors′, coordinates) do (dir, r, c) + σL, σR = build_halfinfinite_projectors(network, env, Ẽas, Ẽbs, Ẽastr, Ẽbstr, dir, r, c) + if conditioning + σL /= norm(σL) + σR /= norm(σR) + UL, SL, VLᴴ = svd_full(σL) + UR, SR, VRᴴ = svd_full(σR) + + FLU = sqrt(SL) * VLᴴ + FRU = UR * sqrt(SR) + + mat = FLU * FRU + mat /= norm(mat) + WU, SU, QUᴴ = svd_trunc(mat; trunc) + + Qa = sdiag_pow(SU, -1 / 2) * WU' * FLU + Qb = FRU * QUᴴ' * sdiag_pow(SU, -1 / 2) + else + mat = σL * σR + mat /= norm(mat) + U, S, V = svd_trunc(mat; trunc) + Qa = sdiag_pow(S, -1 / 2) * U' * σL + Qb = σR * V' * sdiag_pow(S, -1 / 2) + end + return Qa, Qb + end + return getindex.(projectors, 1), getindex.(projectors, 2) +end + +function renormalize_edges(env::CTMRGEnvTriangular, Ẽas::Array{T1, 3}, Ẽbs::Array{T1, 3}, Qas::Array{T2, 3}, Qbs::Array{T2, 3}) where {E, S, T1 <: AbstractTensorMap{E, S, 3, 1}, T2 <: AbstractTensorMap{E, S, 1, 1}} + coordinates = collect(Iterators.product(axes(env.Ea)...)) + T_proj = Tuple{AbstractTensorMap{E, S, 3, 1}, AbstractTensorMap{E, S, 3, 1}} + new_edges′ = similar(env.Ea, T_proj) + new_edges = dtmap!!(new_edges′, coordinates) do (dir, r, c) + Eb_new = Ẽbs[dir, r, c] * Qbs[dir, r, c] + Ea_new = permute(Qas[dir, r, c] * permute(Ẽas[dir, r, c], ((1,),(2,3,4))), ((1,2,3),(4,))) + return Ea_new, Eb_new + end + return CTMRGEnvTriangular(env.C, getindex.(new_edges, 1), getindex.(new_edges, 2)) +end + +function renormalize_edges(env::CTMRGEnvTriangular, Ẽas::Array{T1, 3}, Ẽbs::Array{T1, 3}, Qas::Array{T2, 3}, Qbs::Array{T2, 3}) where {E, S, T1 <: AbstractTensorMap{E, S, 2, 1}, T2 <: AbstractTensorMap{E, S, 1, 1}} + coordinates = collect(Iterators.product(axes(env.Ea)...)) + T_proj = Tuple{AbstractTensorMap{E, S, 2, 1}, AbstractTensorMap{E, S, 2, 1}} + new_edges′ = similar(env.Ea, T_proj) + new_edges = dtmap!!(new_edges′, coordinates) do (dir, r, c) + Eb_new = Ẽbs[dir, r, c] * Qbs[dir, r, c] + Ea_new = permute(Qas[dir, r, c] * permute(Ẽas[dir, r, c], ((1,),(2,3))), ((1,2),(3,))) + return Ea_new, Eb_new + end + return CTMRGEnvTriangular(env.C, getindex.(new_edges, 1), getindex.(new_edges, 2)) +end + +function normalize_corners!(env) + coordinates = collect(Iterators.product(axes(env.Ea)...)) + new_corners′ = similar(env.C, typeof(env.C[1, 1, 1])) + new_corners = dtmap!!(new_corners′, coordinates) do (dir, r, c) + env_C_new = env.C[dir, r, c] / norm(env.C[dir, r, c]) + return env_C_new + end + return CTMRGEnvTriangular(new_corners, env.Ea, env.Eb) +end + +function normalize_edges!(env) + (r, c) = (1,1) + for dir in 1:6 + env.Ea[dir,r,c] /= norm(env.Ea[dir,r,c]) + env.Eb[dir,r,c] /= norm(env.Eb[dir,r,c]) + end + return env +end + +function calculate_error(Ss, Ss_prev) + ε = Inf + for (S, S_prev) in zip(Ss, Ss_prev) + if space(S) == space(S_prev) + ε = norm(S^4 - S_prev^4) + else + return Inf + end + end + return ε +end diff --git a/src/algorithms/triangular_ctmrg/utility.jl b/src/algorithms/ctmrg_triangular/utility.jl similarity index 92% rename from src/algorithms/triangular_ctmrg/utility.jl rename to src/algorithms/ctmrg_triangular/utility.jl index 0230e0944..faf1e7066 100644 --- a/src/algorithms/triangular_ctmrg/utility.jl +++ b/src/algorithms/ctmrg_triangular/utility.jl @@ -15,6 +15,10 @@ function _coordinates(dir::Int, rot::Int, r::Int, c::Int, unitcell::Tuple{Int, I end end +function _coordinates(dir::Int, r::Int, c::Int, unitcell::Tuple{Int, Int}) + return _coordinates(dir, 0, r, c; unitcell)[2:3] +end + function _truncway(trunctype::Symbol, D::E) where {E <: ElementarySpace} if trunctype == :truncdim return truncdim(dim(D)) diff --git a/src/algorithms/optimization/fixed_point_differentiation.jl b/src/algorithms/optimization/fixed_point_differentiation.jl new file mode 100644 index 000000000..4ac24cb2c --- /dev/null +++ b/src/algorithms/optimization/fixed_point_differentiation.jl @@ -0,0 +1,464 @@ +abstract type GradMode{F} end + +const GRADIENT_MODE_SYMBOLS = IdDict{Symbol, Type{<:GradMode}}() +const LINSOLVER_SOLVER_SYMBOLS = IdDict{Symbol, Type{<:KrylovKit.LinearSolver}}( + :gmres => GMRES, :bicgstab => BiCGStab +) +const EIGSOLVER_SOLVER_SYMBOLS = IdDict{Symbol, Type{<:KrylovKit.KrylovAlgorithm}}( + :arnoldi => Arnoldi +) + +""" + GradMode(; kwargs...) + +Keyword argument parser returning the appropriate `GradMode` algorithm struct. +""" +function GradMode(; + alg = Defaults.gradient_alg, + tol = Defaults.gradient_tol, + maxiter = Defaults.gradient_maxiter, + verbosity = Defaults.gradient_verbosity, + iterscheme = Defaults.gradient_iterscheme, + solver_alg = (;), + ) + # replace symbol with GradMode alg type + haskey(GRADIENT_MODE_SYMBOLS, alg) || + throw(ArgumentError("unknown GradMode algorithm: $alg")) + alg_type = GRADIENT_MODE_SYMBOLS[alg] + + # parse GradMode algorithm + gradient_algorithm = if alg_type <: Union{GeomSum, ManualIter} + alg_type{iterscheme}(tol, maxiter, verbosity) + elseif alg_type <: Union{<:LinSolver, <:EigSolver} + solver = if solver_alg isa NamedTuple # determine linear/eigen solver algorithm + solver_kwargs = (; tol, maxiter, verbosity, solver_alg...) + + solver_type = if alg_type <: LinSolver # replace symbol with solver alg type + solver_kwargs = (; alg = Defaults.gradient_linsolver, solver_kwargs...) + haskey(LINSOLVER_SOLVER_SYMBOLS, solver_kwargs.alg) || throw( + ArgumentError("unknown LinSolver solver: $(solver_kwargs.alg)"), + ) + LINSOLVER_SOLVER_SYMBOLS[solver_kwargs.alg] + elseif alg_type <: EigSolver + solver_kwargs = (; + alg = Defaults.gradient_eigsolver, + eager = Defaults.gradient_eigsolver_eager, + solver_kwargs..., + ) + haskey(EIGSOLVER_SOLVER_SYMBOLS, solver_kwargs.alg) || throw( + ArgumentError("unknown EigSolver solver: $(solver_kwargs.alg)"), + ) + EIGSOLVER_SOLVER_SYMBOLS[solver_kwargs.alg] + end + + solver_kwargs = Base.structdiff(solver_kwargs, (; alg = nothing)) # remove `alg` keyword argument + solver_type(; solver_kwargs...) + else + solver_alg + end + + alg_type{iterscheme}(solver) + else + throw(ArgumentError("unknown gradient algorithm: $alg")) + end + + return gradient_algorithm +end + +iterscheme(::GradMode{F}) where {F} = F + +""" +$(TYPEDEF) + +Gradient mode for CTMRG using explicit evaluation of the geometric sum. + +## Fields + +$(TYPEDFIELDS) + +## Constructors + + GeomSum(; kwargs...) + +Construct the `GeomSum` algorithm struct based on the following keyword arguments: + +* `tol::Real=$(Defaults.gradient_tol)` : Convergence tolerance for the difference of norms of two consecutive summands in the geometric sum. +* `maxiter::Int=$(Defaults.gradient_maxiter)` : Maximal number of gradient iterations. +* `verbosity::Int=$(Defaults.gradient_verbosity)` : Output information verbosity that can be one of the following: + 0. Suppress output information + 1. Print convergence warnings + 2. Information at each gradient iteration +* `iterscheme::Symbol=:$(Defaults.gradient_iterscheme)` : Style of CTMRG iteration which is being differentiated, which can be: + - `:fixed` : the differentiated CTMRG iteration uses a pre-computed SVD with a fixed set of gauges + - `:diffgauge` : the differentiated iteration consists of a CTMRG iteration and a subsequent gauge-fixing step such that the gauge-fixing procedure is differentiated as well +""" +struct GeomSum{F} <: GradMode{F} + tol::Real + maxiter::Int + verbosity::Int +end +GeomSum(; kwargs...) = GradMode(; alg = :geomsum, kwargs...) + +GRADIENT_MODE_SYMBOLS[:geomsum] = GeomSum + +""" +$(TYPEDEF) + +Gradient mode for CTMRG using manual iteration to solve the linear problem. + +## Fields + +$(TYPEDFIELDS) + +## Constructors + + ManualIter(; kwargs...) + +Construct the `ManualIter` algorithm struct based on the following keyword arguments: + +* `tol::Real=$(Defaults.gradient_tol)` : Convergence tolerance for the norm difference of two consecutive `dx` contributions. +* `maxiter::Int=$(Defaults.gradient_maxiter)` : Maximal number of gradient iterations. +* `verbosity::Int=$(Defaults.gradient_verbosity)` : Output information verbosity that can be one of the following: + 0. Suppress output information + 1. Print convergence warnings + 2. Information at each gradient iteration +* `iterscheme::Symbol=:$(Defaults.gradient_iterscheme)` : Style of CTMRG iteration which is being differentiated, which can be: + - `:fixed` : the differentiated CTMRG iteration uses a pre-computed SVD with a fixed set of gauges + - `:diffgauge` : the differentiated iteration consists of a CTMRG iteration and a subsequent gauge-fixing step such that the gauge-fixing procedure is differentiated as well +""" +struct ManualIter{F} <: GradMode{F} + tol::Real + maxiter::Int + verbosity::Int +end +ManualIter(; kwargs...) = GradMode(; alg = :manualiter, kwargs...) + +GRADIENT_MODE_SYMBOLS[:manualiter] = ManualIter + +""" +$(TYPEDEF) + +Gradient mode wrapper around `KrylovKit.LinearSolver` for solving the gradient linear +problem using iterative solvers. + +## Fields + +$(TYPEDFIELDS) + +## Constructors + + LinSolver(; kwargs...) + +Construct the `LinSolver` algorithm struct based on the following keyword arguments: + +* `tol::Real=$(Defaults.gradient_tol)` : Convergence tolerance of the linear solver. +* `maxiter::Int=$(Defaults.gradient_maxiter)` : Maximal number of solver iterations. +* `verbosity::Int=$(Defaults.gradient_verbosity)` : Output information verbosity of the linear solver. +* `iterscheme::Symbol=:$(Defaults.gradient_iterscheme)` : Style of CTMRG iteration which is being differentiated, which can be: + - `:fixed` : the differentiated CTMRG iteration uses a pre-computed SVD with a fixed set of gauges + - `:diffgauge` : the differentiated iteration consists of a CTMRG iteration and a subsequent gauge-fixing step such that the gauge-fixing procedure is differentiated as well +* `solver_alg::Union{KrylovKit.LinearSolver,NamedTuple}=(; alg::Symbol=:$(Defaults.gradient_linsolver)` : Linear solver algorithm which, if supplied directly as a `KrylovKit.LinearSolver` overrides the above specified `tol`, `maxiter` and `verbosity`. Alternatively, it can be supplied via a `NamedTuple` where `alg` can be one of the following: + - `:gmres` : GMRES iterative linear solver, see [`KrylovKit.GMRES`](@extref) for details + - `:bicgstab` : BiCGStab iterative linear solver, see [`KrylovKit.BiCGStab`](@extref) for details +""" +struct LinSolver{F} <: GradMode{F} + solver_alg::KrylovKit.LinearSolver +end +LinSolver(; kwargs...) = GradMode(; alg = :linsolver, kwargs...) + +GRADIENT_MODE_SYMBOLS[:linsolver] = LinSolver + +""" +$(TYPEDEF) + +Gradient mode wrapper around `KrylovKit.KrylovAlgorithm` for solving the gradient linear +problem as an eigenvalue problem. + +## Fields + +$(TYPEDFIELDS) + +## Constructors + + EigSolver(; kwargs...) + +Construct the `EigSolver` algorithm struct based on the following keyword arguments: + +* `tol::Real=$(Defaults.gradient_tol)` : Convergence tolerance of the eigen solver. +* `maxiter::Int=$(Defaults.gradient_maxiter)` : Maximal number of solver iterations. +* `verbosity::Int=$(Defaults.gradient_verbosity)` : Output information verbosity of the linear solver. +* `iterscheme::Symbol=:$(Defaults.gradient_iterscheme)` : Style of CTMRG iteration which is being differentiated, which can be: + - `:fixed` : the differentiated CTMRG iteration uses a pre-computed SVD with a fixed set of gauges + - `:diffgauge` : the differentiated iteration consists of a CTMRG iteration and a subsequent gauge-fixing step such that the gauge-fixing procedure is differentiated as well +* `solver_alg::Union{KrylovKit.KrylovAlgorithm,NamedTuple}=(; alg=:$(Defaults.gradient_eigsolver)` : Eigen solver algorithm which, if supplied directly as a `KrylovKit.KrylovAlgorithm` overrides the above specified `tol`, `maxiter` and `verbosity`. Alternatively, it can be supplied via a `NamedTuple` where `alg` can be one of the following: + - `:arnoldi` : Arnoldi Krylov algorithm, see [`KrylovKit.Arnoldi`](@extref) for details +""" +struct EigSolver{F} <: GradMode{F} + solver_alg::KrylovKit.KrylovAlgorithm +end +EigSolver(; kwargs...) = GradMode(; alg = :eigsolver, kwargs...) + +GRADIENT_MODE_SYMBOLS[:eigsolver] = EigSolver + +#= +Evaluating the gradient of the cost function for CTMRG: +- The gradient of the cost function for CTMRG can be computed using automatic differentiation (AD) or explicit evaluation of the geometric sum. +- With AD, the gradient is computed by differentiating the cost function with respect to the PEPS tensors, including computing the environment tensors. +- With explicit evaluation of the geometric sum, the gradient is computed by differentiating the cost function with the environment kept fixed, and then manually adding the gradient contributions from the environments. +=# + +function _rrule( + gradmode::GradMode{:diffgauge}, + config::RuleConfig, + ::typeof(leading_boundary), + envinit, + state, + alg::Union{CTMRGAlgorithm,CTMRGAlgorithmTriangular}, + ) + env, info = leading_boundary(envinit, state, alg) + alg_fixed = @set alg.projector_alg.trunc = FixedSpaceTruncation() # fix spaces during differentiation + + function leading_boundary_diffgauge_pullback((Δenv′, Δinfo)) + Δenv = unthunk(Δenv′) + + # find partial gradients of gauge-fixed single CTMRG iteration + function f(A, x) + return gauge_fix(x, ctmrg_iteration(InfiniteSquareNetwork(A), x, alg_fixed)[1])[1] + end + _, env_vjp = rrule_via_ad(config, f, state, env) + + # evaluate the geometric sum + ∂f∂A(x)::typeof(state) = env_vjp(x)[2] + ∂f∂x(x)::typeof(env) = env_vjp(x)[3] + ∂F∂env = fpgrad(Δenv, ∂f∂x, ∂f∂A, Δenv, gradmode) + + return NoTangent(), ZeroTangent(), ∂F∂env, NoTangent() + end + + return (env, info), leading_boundary_diffgauge_pullback +end + +# Here f is differentiated from an pre-computed SVD with fixed U, S and V +function _rrule( + gradmode::GradMode{:fixed}, + config::RuleConfig, + ::typeof(MPSKit.leading_boundary), + envinit, + state, + alg::SimultaneousCTMRG, + ) + env, = leading_boundary(envinit, state, alg) + alg_fixed = @set alg.projector_alg.trunc = FixedSpaceTruncation() # fix spaces during differentiation + env_conv, info = ctmrg_iteration(InfiniteSquareNetwork(state), env, alg_fixed) + env_fixed, signs = gauge_fix(env, env_conv) + + # Fix SVD + svd_alg_fixed = _fix_svd_algorithm(alg.projector_alg.svd_alg, signs, info) + alg_fixed = @set alg.projector_alg.svd_alg = svd_alg_fixed + alg_fixed = @set alg_fixed.projector_alg.trunc = notrunc() + + function leading_boundary_fixed_pullback((Δenv′, Δinfo)) + Δenv = unthunk(Δenv′) + + function f(A, x) + return fix_global_phases( + x, ctmrg_iteration(InfiniteSquareNetwork(A), x, alg_fixed)[1] + ) + end + _, env_vjp = rrule_via_ad(config, f, state, env_fixed) + + # evaluate the geometric sum + ∂f∂A(x)::typeof(state) = env_vjp(x)[2] + ∂f∂x(x)::typeof(env) = env_vjp(x)[3] + ∂F∂env = fpgrad(Δenv, ∂f∂x, ∂f∂A, Δenv, gradmode) + + return NoTangent(), ZeroTangent(), ∂F∂env, NoTangent() + end + + return (env_fixed, info), leading_boundary_fixed_pullback +end + +function _rrule( + gradmode::GradMode{:fixed}, + config::RuleConfig, + ::typeof(MPSKit.leading_boundary), + envinit, + state, + alg::SimultaneousCTMRGTriangular, + ) + env, = leading_boundary(envinit, state, alg) + alg_fixed = @set alg.trunctype = :FixedSpaceTruncation # fix spaces during differentiation + env_conv, info = ctmrg_iteration(InfiniteTriangularNetwork(state), env, alg_fixed) + env_fixed, signs = gauge_fix(env, env_conv) + + # Fix SVD + svd_alg_fixed = _fix_svd_algorithm(alg.projector_alg.svd_alg, signs, info) + alg_fixed = @set alg.projector_alg.svd_alg = svd_alg_fixed + alg_fixed = @set alg_fixed.projector_alg.trunc = notrunc() + + function leading_boundary_fixed_pullback((Δenv′, Δinfo)) + Δenv = unthunk(Δenv′) + + function f(A, x) + return fix_global_phases( + x, ctmrg_iteration(InfiniteTriangularNetwork(A), x, alg_fixed)[1] + ) + end + _, env_vjp = rrule_via_ad(config, f, state, env_fixed) + + # evaluate the geometric sum + ∂f∂A(x)::typeof(state) = env_vjp(x)[2] + ∂f∂x(x)::typeof(env) = env_vjp(x)[3] + ∂F∂env = fpgrad(Δenv, ∂f∂x, ∂f∂A, Δenv, gradmode) + + return NoTangent(), ZeroTangent(), ∂F∂env, NoTangent() + end + + return (env_fixed, info), leading_boundary_fixed_pullback +end + +function _fix_svd_algorithm(alg::SVDAdjoint, signs, info) + # embed gauge signs in larger space to fix gauge of full U and V on truncated subspace + rowsize, colsize = size(signs, 2), size(signs, 3) + signs_full = map(Iterators.product(1:4, 1:rowsize, 1:colsize)) do (dir, r, c) + σ = signs[dir, r, c] + r_sign, c_sign = if dir == NORTH # take unit cell interdependency of signs into account + r, _prev(c, colsize) + elseif dir == EAST + _prev(r, rowsize), c + elseif dir == SOUTH + r, _next(c, colsize) + elseif dir == WEST + _next(r, rowsize), c + end + extended_space = domain(info.U_full[dir, r_sign, c_sign]) ← codomain(info.V_full[dir, r_sign, c_sign]) + extended_σ = zeros(scalartype(σ), extended_space) + for (c, b) in blocks(extended_σ) + σc = block(σ, c) + kept_dim = size(σc, 1) + b[diagind(b)] .= one(scalartype(σ)) # put ones on the diagonal + b[1:kept_dim, 1:kept_dim] .= σc # set to σ on kept subspace + end + return extended_σ + end + + # fix kept and full U and V + U_fixed, V_fixed = fix_relative_phases(info.U, info.V, signs) + U_full_fixed, V_full_fixed = fix_relative_phases(info.U_full, info.V_full, signs_full) + return SVDAdjoint(; + fwd_alg = FixedSVD(U_fixed, info.S, V_fixed, U_full_fixed, info.S_full, V_full_fixed), + rrule_alg = alg.rrule_alg, + ) +end +function _fix_svd_algorithm(alg::SVDAdjoint{F}, signs, info) where {F <: IterSVD} + # fix kept U and V only since iterative SVD doesn't have access to full spectrum + U_fixed, V_fixed = fix_relative_phases(info.U, info.V, signs) + return SVDAdjoint(; + fwd_alg = FixedSVD(U_fixed, info.S, V_fixed, nothing, nothing, nothing), + rrule_alg = alg.rrule_alg, + ) +end + +@doc """ + fpgrad(∂F∂x, ∂f∂x, ∂f∂A, y0, alg) + +Compute the gradient of the CTMRG fixed point by solving the following equation: + +dx = ∑ₙ (∂f∂x)ⁿ ∂f∂A dA = (1 - ∂f∂x)⁻¹ ∂f∂A dA + +where `∂F∂x` is the gradient of the cost function with respect to the PEPS tensors, `∂f∂x` +is the partial gradient of the CTMRG iteration with respect to the environment tensors, +`∂f∂A` is the partial gradient of the CTMRG iteration with respect to the PEPS tensors, and +`y0` is the initial guess for the fixed-point iteration. The function returns the gradient +`dx` of the fixed-point iteration. +""" +fpgrad + +# TODO: can we construct an implementation that does not need to evaluate the vjp +# twice if both ∂f∂A and ∂f∂x are needed? +function fpgrad(∂F∂x, ∂f∂x, ∂f∂A, _, alg::GeomSum) + g = ∂F∂x + dx = ∂f∂A(g) # n = 0 term: ∂F∂x ∂f∂A + ϵ = 2 * alg.tol + for i in 1:(alg.maxiter) + g = ∂f∂x(g) + Σₙ = ∂f∂A(g) + dx += Σₙ + ϵnew = norm(Σₙ) # TODO: normalize this error? + Δϵ = ϵ - ϵnew + alg.verbosity > 1 && + @printf("Gradient iter: %3d ‖Σₙ‖: %.2e Δ‖Σₙ‖: %.2e\n", i, ϵnew, Δϵ) + ϵ = ϵnew + + ϵ < alg.tol && break + if alg.verbosity > 0 && i == alg.maxiter + @warn "gradient fixed-point iteration reached maximal number of iterations at ‖Σₙ‖ = $ϵ" + end + end + return dx +end + +function fpgrad(∂F∂x, ∂f∂x, ∂f∂A, y₀, alg::ManualIter) + y = deepcopy(y₀) # Do not mutate y₀ + dx = ∂f∂A(y) + ϵ = 1.0 + for i in 1:(alg.maxiter) + y′ = ∂F∂x + ∂f∂x(y) + + dxnew = ∂f∂A(y′) + ϵnew = norm(dxnew - dx) + Δϵ = ϵ - ϵnew + alg.verbosity > 1 && @printf( + "Gradient iter: %3d ‖Cᵢ₊₁-Cᵢ‖/N: %.2e Δ‖Cᵢ₊₁-Cᵢ‖/N: %.2e\n", i, ϵnew, Δϵ + ) + y = y′ + dx = dxnew + ϵ = ϵnew + + ϵ < alg.tol && break + if alg.verbosity > 0 && i == alg.maxiter + @warn "gradient fixed-point iteration reached maximal number of iterations at ‖Cᵢ₊₁-Cᵢ‖ = $ϵ" + end + end + return dx +end + +function fpgrad(∂F∂x, ∂f∂x, ∂f∂A, y₀, alg::LinSolver) + y, info = reallinsolve(∂f∂x, ∂F∂x, y₀, alg.solver_alg, 1, -1) + if alg.solver_alg.verbosity > 0 && info.converged != 1 + @warn("gradient fixed-point iteration reached maximal number of iterations:", info) + end + + return ∂f∂A(y) +end + +function fpgrad(∂F∂x, ∂f∂x, ∂f∂A, x₀, alg::EigSolver) + function f(X) + y = ∂f∂x(X[1]) + return (y + X[2] * ∂F∂x, X[2]) + end + X₀ = (x₀, one(scalartype(x₀))) + _, vecs, info = realeigsolve(f, X₀, 1, :LM, alg.solver_alg) + if alg.solver_alg.verbosity > 0 && info.converged < 1 + @warn("gradient fixed-point iteration reached maximal number of iterations:", info) + end + if norm(vecs[1][2]) < 1.0e-2 * alg.solver_alg.tol + @warn "Fixed-point gradient computation using Arnoldi failed:\n\tauxiliary component should be finite but was $(vecs[1][2])\n\tpossibly the Jacobian does not have a unique eigenvalue 1" + @info "Falling back to linear solver for fixed-point gradient computation." + backup_ls_alg = _alg_or_nt( + GradMode, + (; + alg = :linsolver, + tol = alg.solver_alg.tol, + maxiter = alg.solver_alg.maxiter * alg.solver_alg.krylovdim, + verbosity = alg.solver_alg.verbosity, + iterscheme = iterscheme(alg), + ) + ) + return fpgrad(∂F∂x, ∂f∂x, ∂f∂A, x₀, backup_ls_alg) + else + y = scale(vecs[1][1], 1 / vecs[1][2]) + end + + return ∂f∂A(y) +end diff --git a/src/algorithms/select_algorithm.jl b/src/algorithms/select_algorithm.jl index 5eb29c4b2..7faf9564f 100644 --- a/src/algorithms/select_algorithm.jl +++ b/src/algorithms/select_algorithm.jl @@ -161,7 +161,7 @@ end function select_algorithm( ::typeof(leading_boundary), - env₀::CTMRGTriaEnv + env₀::CTMRGEnvTriangular ) return SimultaneousCTMRGTria() end diff --git a/src/algorithms/toolbox_triangular.jl b/src/algorithms/toolbox_triangular.jl new file mode 100644 index 000000000..cdf2f663d --- /dev/null +++ b/src/algorithms/toolbox_triangular.jl @@ -0,0 +1,126 @@ +function network_value(network::InfiniteTriangularNetwork, env::CTMRGEnvTriangular) + return prod(Iterators.product(axes(network)...)) do (r, c) + nw_corners = complex(_contract_corners((r,c), network, env)) + nw_full = complex(_contract_site_large((r,c), network, env)) + nw_0 = complex(_contract_edges_0((r,c), network, env)) + nw_60 = complex(_contract_edges_60((r,c), network, env)) + nw_120 = complex(_contract_edges_120((r,c), network, env)) + return (nw_full * nw_corners^2 / (nw_0 * nw_60 * nw_120))^(1 / 3) + end +end + +function _contract_edges_0((r,c)::Tuple{Int,Int}, network::InfiniteTriangularNetwork{P}, env::CTMRGEnvTriangular) where {P <: PFTensorTriangular} + return @tensor opt = true network[r,c][DL180 DL240 DL300; DL120 DL60 DL0] * network[r,_next(c,end)][DL0 DR240 DR300; DR120 DR60 DR0] * + env.C[1,_prev(r,end),c][χNW DL120; χNa] * env.C[2,_prev(r,end),_next(c+1,end)][χNb DR60; χNE] * env.C[3,r,_next(c+1,end)][χNE DR0; χSE] * + env.C[4,_next(r,end),_next(c,end)][χSE DR300; χSa] * env.C[5,_next(r,end),_prev(c,end)][χSb DL240; χSW] * env.C[6,r,_prev(c,end)][χSW DL180; χNW] * + env.Eb[1,_prev(r,end),_next(c,end)][χNa DL60; χNC] * env.Ea[1,_prev(r,end),_next(c,end)][χNC DR120; χNb] * + env.Eb[4,_next(r,end),c][χSa DR240; χSC] * env.Ea[4,_next(r,end),c][χSC DL300; χSb] +end + +function _contract_edges_60((r,c)::Tuple{Int,Int}, network::InfiniteTriangularNetwork{P}, env::CTMRGEnvTriangular) where {P <: PFTensorTriangular} + return @tensor opt = true network[r,c][DTR180 DBL60 DTR300; DTR120 DTR60 DTR0] * network[_next(r,end),_prev(c,end)][DBL180 DBL240 DBL300; DBL120 DBL60 DBL0] * + env.C[1,_prev(r,end),c][χNWb DTR120; χN] * env.C[2,_prev(r,end),_next(c,end)][χN DTR60; χNE] * env.C[3,r,_next(c,end)][χNE DTR0; χSEa] * + env.C[4,_next(r+1,end),_prev(c,end)][χSEb DBL300; χS] * env.C[5,_next(r+1,end),_prev(c-1,end)][χS DBL240; χSW] * env.C[6,_next(r,end),_prev(c-1,end)][χSW DBL180; χNWa] * + env.Eb[3,_next(r,end),c][χSEa DTR300; χSEC] * env.Ea[3,_next(r,end),c][χSEC DBL0; χSEb] * + env.Eb[6,r,_prev(c,end)][χNWa DBL120; χNWC] * env.Ea[6,r,_prev(c,end)][χNWC DTR180; χNWb] +end + +function _contract_edges_120((r,c)::Tuple{Int,Int}, network::InfiniteTriangularNetwork{P}, env::CTMRGEnvTriangular) where {P <: PFTensorTriangular} + return @tensor opt = true network[r,c][DTL180 DTL240 DTL300; DTL120 DTL60 DTL0] * network[_next(r,end),c][DBR180 DBR240 DBR300; DTL300 DBR60 DBR0] * + env.C[1,_prev(r,end),c][χNW DTL120; χN] * env.C[2,_prev(r,end),_next(c,end)][χN DTL60; χNEa] * env.C[3,_next(r,end),_next(c,end)][χNEb DBR0; χSE] * + env.C[4,_next(r+1,end),c][χSE DBR300; χS] * env.C[5,_next(r+1,end),_prev(c,end)][χS DBR240; χSWa] * env.C[6,r,_prev(c,end)][χSWb DTL180; χNW] * + env.Eb[2,r,_next(c,end)][χNEa DTL0; χNEC] * env.Ea[2,r,_next(c,end)][χNEC DBR60; χNEb] * + env.Eb[5,_next(r,end),_prev(c,end)][χSWa DBR180; χSWC] * env.Ea[5,_next(r,end),_prev(c,end)][χSWC DTL240; χSWb] +end + +function _contract_site_large((r,c)::Tuple{Int,Int}, network::InfiniteTriangularNetwork{P}, env::CTMRGEnvTriangular) where {P <: PFTensorTriangular} + return @tensor opt = true network[_prev(r,end),c][DNW180 DW60 DNW300; DNW120 DNW60 DNW0] * network[_prev(r,end),_next(c,end)][DNW0 DNE240 DNE300; DNE120 DNE60 DNE0] * + network[r,_next(c,end)][DE180 DE240 DE300; DNE300 DE60 DE0] * network[_next(r,end),c][DSE180 DSE240 DSE300; DSE120 DE240 DSE0] * network[_next(r,end),_prev(c,end)][DSW180 DSW240 DSW300; DSW120 DSW60 DSE180] * + network[r,_prev(c,end)][DW180 DW240 DSW120; DW120 DW60 DW0] * network[r,c][DW0 DSW60 DSE120; DNW300 DNE240 DE180] * + env.C[1,_prev(r-1,end),c][χNWa DNW120; χNb] * env.Eb[1,_prev(r-1,end),_next(c,end)][χNb DNW60; χNC] * env.Ea[1,_prev(r-1,end),_next(c,end)][χNC DNE120; χNa] * + env.C[2,_prev(r-1,end),_next(c+1,end)][χNa DNE60; χNEb] * env.Eb[2,_prev(r,end),_next(c+1,end)][χNEb DNE0; χNEC] * env.Ea[2,_prev(r,end),_next(c+1,end)][χNEC DE60; χNEa] * + env.C[3,r,_next(c+1,end)][χNEa DE0; χSEb] * env.Eb[3,_next(r,end),_next(c,end)][χSEb DE300; χSEC] * env.Ea[3,_next(r,end),_next(c,end)][χSEC DSE0; χSEa] * + env.C[4,_next(r+1,end),c][χSEa DSE300; χSb] * env.Eb[4,_next(r+1,end),_prev(c,end)][χSb DSE240; χSC] * env.Ea[4,_next(r+1,end),_prev(c,end)][χSC DSW300; χSa] * + env.C[5,_next(r+1,end),_prev(c-1,end)][χSa DSW240; χSWb] * env.Eb[5,_next(r,end),_prev(c-1,end)][χSWb DSW180; χSWC] * env.Ea[5,_next(r,end),_prev(c-1,end)][χSWC DW240; χSWa] * + env.C[6,r,_prev(c-1,end)][χSWa DW180; χNWb] * env.Eb[6,_prev(r,end),_prev(c,end)][χNWb DW120; χNWC] * env.Ea[6,_prev(r,end),_prev(c,end)][χNWC DNW180; χNWa] +end + +function _contract_corners((r,c)::Tuple{Int,Int}, network::InfiniteTriangularNetwork{P}, env::CTMRGEnvTriangular) where {P <: PFTensorTriangular} + return @tensor opt = true env.C[1,_prev(r,end),c][χNW D120; χN] * env.C[2,_prev(r,end),_next(c,end)][χN D60; χNE] * env.C[3,r,_next(c,end)][χNE D0; χSE] * + env.C[4,_next(r,end),c][χSE D300; χS] * env.C[5,_next(r,end),_prev(c,end)][χS D240; χSW] * env.C[6,r,_prev(c,end)][χSW D180; χNW] * + network[r,c][D180 D240 D300; D120 D60 D0] +end + +### For + +function _contract_edges_0((r,c)::Tuple{Int,Int}, network::InfiniteTriangularNetwork{P}, env::CTMRGEnvTriangular) where {P <: PEPSSandwichTriangular} + return @tensor opt = true ket(network[r,c])[dL; DLt120 DLt60 DLt0 DLt300 DLt240 DLt180] * ket(network[r,_next(c,end)])[dR; DRt120 DRt60 DRt0 DRt300 DRt240 DLt0] * + conj(bra(network[r,c])[dL; DLb120 DLb60 DLb0 DLb300 DLb240 DLb180]) * conj(bra(network[r,_next(c,end)])[dR; DRb120 DRb60 DRb0 DRb300 DRb240 DLb0]) * + env.C[1,_prev(r,end),c][χNW DLt120 DLb120; χNa] * env.C[2,_prev(r,end),_next(c+1,end)][χNb DRt60 DRb60; χNE] * env.C[3,r,_next(c+1,end)][χNE DRt0 DRb0; χSE] * + env.C[4,_next(r,end),_next(c,end)][χSE DRt300 DRb300; χSa] * env.C[5,_next(r,end),_prev(c,end)][χSb DLt240 DLb240; χSW] * env.C[6,r,_prev(c,end)][χSW DLt180 DLb180; χNW] * + env.Eb[1,_prev(r,end),_next(c,end)][χNa DLt60 DLb60; χNC] * env.Ea[1,_prev(r,end),_next(c,end)][χNC DRt120 DRb120; χNb] * + env.Eb[4,_next(r,end),c][χSa DRt240 DRb240; χSC] * env.Ea[4,_next(r,end),c][χSC DLt300 DLb300; χSb] +end + +function _contract_edges_0((r,c)::Tuple{Int,Int}, network::InfiniteTriangularNetwork{P}, env::CTMRGEnvTriangular, op::AbstractTensorMap{E,S,2,2}) where {P <: PEPSSandwichTriangular, E, S} + return @tensor opt = true ket(network[r,c])[dLt; DLt120 DLt60 DLt0 DLt300 DLt240 DLt180] * ket(network[r,_next(c,end)])[dRt; DRt120 DRt60 DRt0 DRt300 DRt240 DLt0] * + conj(bra(network[r,c])[dLb; DLb120 DLb60 DLb0 DLb300 DLb240 DLb180]) * conj(bra(network[r,_next(c,end)])[dRb; DRb120 DRb60 DRb0 DRb300 DRb240 DLb0]) * + env.C[1,_prev(r,end),c][χNW DLt120 DLb120; χNa] * env.C[2,_prev(r,end),_next(c+1,end)][χNb DRt60 DRb60; χNE] * env.C[3,r,_next(c+1,end)][χNE DRt0 DRb0; χSE] * + env.C[4,_next(r,end),_next(c,end)][χSE DRt300 DRb300; χSa] * env.C[5,_next(r,end),_prev(c,end)][χSb DLt240 DLb240; χSW] * env.C[6,r,_prev(c,end)][χSW DLt180 DLb180; χNW] * + env.Eb[1,_prev(r,end),_next(c,end)][χNa DLt60 DLb60; χNC] * env.Ea[1,_prev(r,end),_next(c,end)][χNC DRt120 DRb120; χNb] * + env.Eb[4,_next(r,end),c][χSa DRt240 DRb240; χSC] * env.Ea[4,_next(r,end),c][χSC DLt300 DLb300; χSb] * + op[dLb dRb; dLt dRt] +end + +function _contract_edges_60((r,c)::Tuple{Int,Int}, network::InfiniteTriangularNetwork{P}, env::CTMRGEnvTriangular) where {P <: PEPSSandwichTriangular} + return @tensor opt = true ket(network[r,c])[dTR; DTRt120 DTRt60 DTRt0 DTRt300 DBLt60 DTRt180] * ket(network[_next(r,end),_prev(c,end)])[dBL; DBLt120 DBLt60 DBLt0 DBLt300 DBLt240 DBLt180] * + conj(bra(network[r,c])[dTR; DTRb120 DTRb60 DTRb0 DTRb300 DBLb60 DTRb180]) * conj(bra(network[_next(r,end),_prev(c,end)])[dBL; DBLb120 DBLb60 DBLb0 DBLb300 DBLb240 DBLb180]) * + env.C[1,_prev(r,end),c][χNWb DTRt120 DTRb120; χN] * env.C[2,_prev(r,end),_next(c,end)][χN DTRt60 DTRb60; χNE] * env.C[3,r,_next(c,end)][χNE DTRt0 DTRb0; χSEa] * + env.C[4,_next(r+1,end),_prev(c,end)][χSEb DBLt300 DBLb300; χS] * env.C[5,_next(r+1,end),_prev(c-1,end)][χS DBLt240 DBLb240; χSW] * env.C[6,_next(r,end),_prev(c-1,end)][χSW DBLt180 DBLb180; χNWa] * + env.Eb[3,_next(r,end),c][χSEa DTRt300 DTRb300; χSEC] * env.Ea[3,_next(r,end),c][χSEC DBLt0 DBLb0; χSEb] * + env.Eb[6,r,_prev(c,end)][χNWa DBLt120 DBLb120; χNWC] * env.Ea[6,r,_prev(c,end)][χNWC DTRt180 DTRb180; χNWb] +end + +function _contract_edges_120((r,c)::Tuple{Int,Int}, network::InfiniteTriangularNetwork{P}, env::CTMRGEnvTriangular) where {P <: PEPSSandwichTriangular} + return @tensor opt = true ket(network[r,c])[dTL; DTLt120 DTLt60 DTLt0 DTLt300 DTLt240 DTLt180] * ket(network[_next(r,end),c])[dBR; DTLt300 DBRt60 DBRt0 DBRt300 DBRt240 DBRt180] * + conj(bra(network[r,c])[dTL; DTLb120 DTLb60 DTLb0 DTLb300 DTLb240 DTLb180]) * conj(bra(network[_next(r,end),c])[dBR; DTLb300 DBRb60 DBRb0 DBRb300 DBRb240 DBRb180]) * + env.C[1,_prev(r,end),c][χNW DTLt120 DTLb120; χN] * env.C[2,_prev(r,end),_next(c,end)][χN DTLt60 DTLb60; χNEa] * env.C[3,_next(r,end),_next(c,end)][χNEb DBRt0 DBRb0; χSE] * + env.C[4,_next(r+1,end),c][χSE DBRt300 DBRb300; χS] * env.C[5,_next(r+1,end),_prev(c,end)][χS DBRt240 DBRb240; χSWa] * env.C[6,r,_prev(c,end)][χSWb DTLt180 DTLb180; χNW] * + env.Eb[2,r,_next(c,end)][χNEa DTLt0 DTLb0; χNEC] * env.Ea[2,r,_next(c,end)][χNEC DBRt60 DBRb60; χNEb] * + env.Eb[5,_next(r,end),_prev(c,end)][χSWa DBRt180 DBRb180; χSWC] * env.Ea[5,_next(r,end),_prev(c,end)][χSWC DTLt240 DTLb240; χSWb] +end + +function _contract_site_large((r,c)::Tuple{Int,Int}, network::InfiniteTriangularNetwork{P}, env::CTMRGEnvTriangular) where {P <: PEPSSandwichTriangular} + return @tensor opt = true ket(network[_prev(r,end),c])[dNW; DNWt120 DNWt60 DNWt0 DNWt300 DWt60 DNWt180] * ket(network[_prev(r,end),_next(c,end)])[dNE; DNEt120 DNEt60 DNEt0 DNEt300 DNEt240 DNWt0] * + ket(network[r,_next(c,end)])[dE; DNEt300 DEt60 DEt0 DEt300 DEt240 DEt180] * ket(network[_next(r,end),c])[dSE; DSEt120 DEt240 DSEt0 DSEt300 DSEt240 DSEt180] * ket(network[_next(r,end),_prev(c,end)])[dSW; DSWt120 DSWt60 DSEt180 DSWt300 DSWt240 DSWt180] * + ket(network[r,_prev(c,end)])[dW; DWt120 DWt60 DWt0 DSWt120 DWt240 DWt180] * ket(network[r,c])[dCenter; DNWt300 DNEt240 DEt180 DSEt120 DSWt60 DWt0] * + conj(bra(network[_prev(r,end),c])[dNW; DNWb120 DNWb60 DNWb0 DNWb300 DWb60 DNWb180]) * conj(bra(network[_prev(r,end),_next(c,end)])[dNE; DNEb120 DNEb60 DNEb0 DNEb300 DNEb240 DNWb0]) * + conj(bra(network[r,_next(c,end)])[dE; DNEb300 DEb60 DEb0 DEb300 DEb240 DEb180]) * conj(bra(network[_next(r,end),c])[dSE; DSEb120 DEb240 DSEb0 DSEb300 DSEb240 DSEb180]) * conj(bra(network[_next(r,end),_prev(c,end)])[dSW; DSWb120 DSWb60 DSEb180 DSWb300 DSWb240 DSWb180]) * + conj(bra(network[r,_prev(c,end)])[dW; DWb120 DWb60 DWb0 DSWb120 DWb240 DWb180]) * conj(bra(network[r,c])[dCenter; DNWb300 DNEb240 DEb180 DSEb120 DSWb60 DWb0]) * + env.C[1,_prev(r-1,end),c][χNWa DNWt120 DNWb120; χNb] * env.Eb[1,_prev(r-1,end),_next(c,end)][χNb DNWt60 DNWb60; χNC] * env.Ea[1,_prev(r-1,end),_next(c,end)][χNC DNEt120 DNEb120; χNa] * + env.C[2,_prev(r-1,end),_next(c+1,end)][χNa DNEt60 DNEb60; χNEb] * env.Eb[2,_prev(r,end),_next(c+1,end)][χNEb DNEt0 DNEb0; χNEC] * env.Ea[2,_prev(r,end),_next(c+1,end)][χNEC DEt60 DEb60; χNEa] * + env.C[3,r,_next(c+1,end)][χNEa DEt0 DEb0; χSEb] * env.Eb[3,_next(r,end),_next(c,end)][χSEb DEt300 DEb300; χSEC] * env.Ea[3,_next(r,end),_next(c,end)][χSEC DSEt0 DSEb0; χSEa] * + env.C[4,_next(r+1,end),c][χSEa DSEt300 DSEb300; χSb] * env.Eb[4,_next(r+1,end),_prev(c,end)][χSb DSEt240 DSEb240; χSC] * env.Ea[4,_next(r+1,end),_prev(c,end)][χSC DSWt300 DSWb300; χSa] * + env.C[5,_next(r+1,end),_prev(c-1,end)][χSa DSWt240 DSWb240; χSWb] * env.Eb[5,_next(r,end),_prev(c-1,end)][χSWb DSWt180 DSWb180; χSWC] * env.Ea[5,_next(r,end),_prev(c-1,end)][χSWC DWt240 DWb240; χSWa] * + env.C[6,r,_prev(c-1,end)][χSWa DWt180 DWb180; χNWb] * env.Eb[6,_prev(r,end),_prev(c,end)][χNWb DWt120 DWb120; χNWC] * env.Ea[6,_prev(r,end),_prev(c,end)][χNWC DNWt180 DNWb180; χNWa] +end + +function _contract_corners((r,c)::Tuple{Int,Int}, network::InfiniteTriangularNetwork{P}, env::CTMRGEnvTriangular) where {P <: PEPSSandwichTriangular} + return @tensor opt = true env.C[1,_prev(r,end),c][χNW Dt120 Db120; χN] * env.C[2,_prev(r,end),_next(c,end)][χN Dt60 Db60; χNE] * env.C[3,r,_next(c,end)][χNE Dt0 Db0; χSE] * + env.C[4,_next(r,end),c][χSE Dt300 Db300; χS] * env.C[5,_next(r,end),_prev(c,end)][χS Dt240 Db240; χSW] * env.C[6,r,_prev(c,end)][χSW Dt180 Db180; χNW] * + ket(network[r,c])[d; Dt120 Dt60 Dt0 Dt300 Dt240 Dt180] * conj(bra(network[r,c])[d; Db120 Db60 Db0 Db300 Db240 Db180]) +end + +function _contract_corners((r,c)::Tuple{Int,Int}, network::InfiniteTriangularNetwork{P}, env::CTMRGEnvTriangular, op::AbstractTensorMap{E,S,1,1}) where {P <: PEPSSandwichTriangular, E, S} + return @tensor opt = true env.C[1,_prev(r,end),c][χNW Dt120 Db120; χN] * env.C[2,_prev(r,end),_next(c,end)][χN Dt60 Db60; χNE] * env.C[3,r,_next(c,end)][χNE Dt0 Db0; χSE] * + env.C[4,_next(r,end),c][χSE Dt300 Db300; χS] * env.C[5,_next(r,end),_prev(c,end)][χS Dt240 Db240; χSW] * env.C[6,r,_prev(c,end)][χSW Dt180 Db180; χNW] * + ket(network[r,c])[dt; Dt120 Dt60 Dt0 Dt300 Dt240 Dt180] * conj(bra(network[r,c])[db; Db120 Db60 Db0 Db300 Db240 Db180]) * op[db; dt] +end + +function energy(network::InfiniteTriangularNetwork{P}, env::CTMRGEnvTriangular, onesite_op, twosite_op) where {P <: PEPSSandwichTriangular} + return sum(Iterators.product(axes(network)...)) do (r, c) + expval_onesite = _contract_corners((r,c), network, env, onesite_op) / _contract_corners((r,c), network, env) + expval_twosite = _contract_edges_0((r,c), network, env, twosite_op) / _contract_edges_0((r,c), network, env) + return expval_onesite + expval_twosite + end +end diff --git a/src/algorithms/triangular_ctmrg/simultaneous.jl b/src/algorithms/triangular_ctmrg/simultaneous.jl deleted file mode 100644 index 4c3610bcd..000000000 --- a/src/algorithms/triangular_ctmrg/simultaneous.jl +++ /dev/null @@ -1,319 +0,0 @@ -struct SimultaneousCTMRGTria <: CTMRGTriaAlgorithm - tol::Float64 - maxiter::Int - miniter::Int - verbosity::Int - conditioning::Bool - projector_alg::Symbol - trunctype::Symbol -end -function SimultaneousCTMRGTria(; - tol = Defaults.ctmrg_tol, - maxiter = Defaults.ctmrg_maxiter, - miniter = Defaults.ctmrg_miniter, - verbosity = Defaults.ctmrg_verbosity, - conditioning = true, - projector_alg = :twothirds, - trunctype = :truncrank - ) - return SimultaneousCTMRGTria(tol, maxiter, miniter, verbosity, conditioning, projector_alg, trunctype) -end - -# Based on -# https://arxiv.org/pdf/2510.04907 - -function ctmrg_iteration(network::InfiniteTriangularNetwork, env::CTMRGTriaEnv, alg::SimultaneousCTMRGTria) - trunc_type = getfield(@__MODULE__, alg.trunctype) - trunc = trunc_type(dim(domain(env.C[1, 1, 1])[1])) - Pas, Pbs, S = calculate_projectors(network, env, trunc, alg.projector_alg) - - renormalize_corners!(network, env, Pas, Pbs) - normalize_corners!(env) - - Ẽas, Ẽbs, Ẽastr, Ẽbstr = semi_renormalize(network, env, Pas, Pbs, trunc) - Qas, Qbs = build_matrix_second_projectors(network, env, Ẽas, Ẽbs, Ẽastr, Ẽbstr, trunc; alg.conditioning) - - env = renormalize_edges(env, Ẽas, Ẽbs, Qas, Qbs) - normalize_edges!(env) - return env, S -end - -function calculate_projectors(network, env, trunc, projector_alg) - if projector_alg == :full - return calculate_full_projectors(network, env, trunc) - elseif projector_alg == :twothirds - return calculate_twothirds_projectors(network, env, trunc) - else - @error "projector_alg = $projector_alg not defined" - end -end - -function calculate_twothirds_projectors(network::InfiniteTriangularNetwork{P}, env, trunc) where {P} - coordinates = eachcoordinate(network, 1:6) - E = scalartype(env.C[1, 1, 1]) - S = spacetype(env.C[1, 1, 1]) - T_proj = Tuple{AbstractTensorMap{E, S, 1, 2 + (network[1, 1] isa Tuple)}, AbstractTensorMap{E, S, 2 + (network[1, 1] isa Tuple), 1}, AbstractTensorMap{E, S, 1, 1}} - projectors′ = similar(coordinates, T_proj) - projectors = dtmap!!(projectors′, coordinates) do (dir, r, c) - ρL = build_double_corner_matrix_triangular(network, env, mod1(dir - 1, 6), r, c) - ρR = build_double_corner_matrix_triangular(network, env, mod1(dir + 1, 6), r, c) - ρρ = ρL * ρR - ρρ /= norm(ρρ) - - U, S, V = svd_trunc(ρρ; trunc = trunc & trunctol(; atol = 1.0e-20)) - - Pb = ρR * V' * sdiag_pow(S, -1 / 2) - Pa = sdiag_pow(S, -1 / 2) * U' * ρL - return Pa, Pb, S - end - return getindex.(projectors, 1), getindex.(projectors, 2), getindex.(projectors, 3) -end - -function calculate_full_projectors(network::InfiniteTriangularNetwork{P}, env, trunc) where {P} - coordinates = eachcoordinate(network, 1:6) - E = scalartype(env.C[1, 1, 1]) - S = spacetype(env.C[1, 1, 1]) - T_proj = Tuple{AbstractTensorMap{E, S, 1, 2 + (network[1, 1] isa Tuple)}, AbstractTensorMap{E, S, 2 + (network[1, 1] isa Tuple), 1}, AbstractTensorMap{E, S, 1, 1}} - projectors′ = similar(coordinates, T_proj) - projectors = dtmap!!(projectors′, coordinates) do (dir, r, c) - ρL = build_double_corner_matrix_triangular(network, env, mod1(dir - 1, 6), r, c) - ρR = build_double_corner_matrix_triangular(network, env, mod1(dir + 1, 6), r, c) - ρ̄ = build_double_corner_matrix_triangular(network, env, mod1(dir + 3, 6), r, c) - ρ̄ /= norm(ρ̄) - Ū, S̄, V̄ᴴ = svd_trunc(ρ̄; trunc = trunctol(; atol = 1.0e-20)) - ρ̄ᴿ = Ū * sqrt(S̄) - ρ̄ᴸ = sqrt(S̄) * V̄ᴴ - ρρ = ρ̄ᴸ * ρL * ρR * ρ̄ᴿ - ρρ /= norm(ρρ) - - U, S, Vᴴ = svd_trunc(ρρ; trunc = trunctol(; atol = 1.0e-20)) - U, S, Vᴴ = svd_trunc(ρρ; trunc) - U, S, Vᴴ = svd_trunc(ρρ; trunc = trunc & trunctol(; atol = 1.0e-20)) - - Pb = ρR * ρ̄ᴿ * Vᴴ' * sdiag_pow(S, -1 / 2) - Pa = sdiag_pow(S, -1 / 2) * U' * ρ̄ᴸ * ρL - - return Pa, Pb, S - end - return getindex.(projectors, 1), getindex.(projectors, 2), getindex.(projectors, 3) -end - -function renormalize_corners!(network::InfiniteTriangularNetwork{P}, env, Pas, Pbs) where {P <: PEPSTriaSandwich} - coordinates = eachcoordinate(network, 1:6) - E = scalartype(env.C[1, 1, 1]) - S = spacetype(env.C[1, 1, 1]) - T_proj = AbstractTensorMap{E, S, 2 + (network[1, 1] isa Tuple), 1} - new_corners′ = similar(coordinates, T_proj) - new_corners = dtmap!!(new_corners′, coordinates) do (dir, r, c) - @tensor opt = true env_C_new[-1 -2 -3; -4] := env.C[_coordinates(dir, 0, r, c, size(network))...][1 3 10; 6] * env.Ea[_coordinates(dir - 1, 0, r, c, size(network))...][4 2 11; 1] * env.Eb[_coordinates(dir, 1, r, c, size(network))...][6 7 12; 8] * - rotl60(ket(network[r, c]), mod(dir - 1, 6))[15; 3 7 9 -2 5 2] * conj(rotl60(bra(network[r, c]), mod(dir - 1, 6))[15; 10 12 14 -3 13 11]) * - Pas[mod1(dir - 1, 6), r, c][-1; 4 5 13] * Pbs[dir, r, c][8 9 14; -4] - return env_C_new - end - return new_corners -end - -function renormalize_corners!(network::InfiniteTriangularNetwork{P}, env, Pas, Pbs) where {P <: PFTriaTensor} - coordinates = eachcoordinate(network, 1:6) - E = scalartype(env.C[1, 1, 1]) - S = spacetype(env.C[1, 1, 1]) - T_proj = AbstractTensorMap{E, S, 2, 1} - new_corners′ = similar(coordinates, T_proj) - new_corners = dtmap!!(new_corners′, coordinates) do (dir, r, c) - @tensor opt = true env_C_new[-1 -2; -3] := env.C[_coordinates(dir, 0, r, c, size(network))...][1 3; 6] * env.Ea[_coordinates(dir - 1, 0, r, c, size(network))...][4 2; 1] * env.Eb[_coordinates(dir, 1, r, c, size(network))...][6 7; 8] * - rotl60(network[r, c], mod(dir - 1, 6))[2 5 -2; 3 7 9] * Pas[mod1(dir - 1, 6), r, c][-1; 4 5] * Pbs[dir, r, c][8 9; -3] - return env_C_new - end - return new_corners -end - -function build_double_corner_matrix_triangular(network::InfiniteTriangularNetwork{P}, env::CTMRGTriaEnv, dir::Int, r::Int, c::Int) where {P <: PFTriaTensor} - @tensor opt = true mat[-1 -2; -3 -4] := env.C[_coordinates(dir, 0, r, c, size(network))...][6 5; 1] * env.C[_coordinates(dir + 1, 0, r, c, size(network))...][1 3; 2] * - env.Ea[_coordinates(dir - 1, 0, r, c, size(network))...][-1 7; 6] * env.Eb[_coordinates(dir + 1, 0, r, c, size(network))...][2 4; -3] * rotl60(network[r, c], mod(dir - 1, 6))[7 -2 -4; 5 3 4] - return mat -end - -function build_double_corner_matrix_triangular(network::InfiniteTriangularNetwork{P}, env::CTMRGTriaEnv, dir::Int, r::Int, c::Int) where {P <: PEPSTriaSandwich} - @tensor opt = true mat[-1 -2 -3; -4 -5 -6] := env.C[_coordinates(dir, 0, r, c, size(network))...][6 5 8; 1] * env.C[_coordinates(dir + 1, 0, r, c, size(network))...][1 3 9; 2] * - env.Ea[_coordinates(dir - 1, 0, r, c, size(network))...][-1 7 10; 6] * env.Eb[_coordinates(dir + 1, 0, r, c, size(network))...][2 4 11; -4] * - rotl60(ket(network[r, c]), mod(dir - 1, 6))[12; 5 3 4 -5 -2 7] * conj(rotl60(bra(network[r, c]), mod(dir - 1, 6))[12; 8 9 11 -6 -3 10]) - return mat -end - -function semi_renormalize_edge(network::InfiniteTriangularNetwork{P}, env::CTMRGTriaEnv, Pas, Pbs, dir, r, c) where {P <: PEPSTriaSandwich} - @tensor opt = true mat[-1 -2 -3; -4 -5 -6] := Pas[dir, r, c][-1; 1 2 8] * Pbs[dir, r, c][6 7 9; -4] * - env.Ea[_coordinates(dir, 0, r, c, size(network))...][1 3 10; 4] * env.Eb[_coordinates(dir, 1, r, c, size(network))...][4 5 11; 6] * - rotl60(ket(network[r, c]), mod(dir - 1, 6))[12; 3 5 7 -5 -2 2] * conj(rotl60(bra(network[r, c]), mod(dir - 1, 6))[12; 10 11 9 -6 -3 8]) - return mat / norm(mat) -end - -function semi_renormalize_edge(network::InfiniteTriangularNetwork{P}, env::CTMRGTriaEnv, Pas, Pbs, dir, r, c) where {P <: PFTriaTensor} - @tensor opt = true mat[-1 -2; -3 -4] := Pas[dir, r, c][-1; 1 2] * Pbs[dir, r, c][6 7; -3] * - env.Ea[_coordinates(dir, 0, r, c, size(network))...][1 3; 4] * env.Eb[_coordinates(dir, 1, r, c, size(network))...][4 5; 6] * rotl60(network[r, c], mod(dir - 1, 6))[2 -2 -4; 3 5 7] - return mat / norm(mat) -end - -function _permute_edge(t::Union{T1, T2}) where {E, S, T1 <: AbstractTensorMap{E, S, 2, 1}, T2 <: AbstractTensorMap{E, S, 1, 2}} - return permute(t, ((1, 3), (2,))) -end - -function _permute_edge(t::Union{T1, T2}) where {E, S, T1 <: AbstractTensorMap{E, S, 3, 1}, T2 <: AbstractTensorMap{E, S, 1, 3}} - return permute(t, ((1, 3, 4), (2,))) -end - -function semi_renormalize(network::InfiniteTriangularNetwork, env::CTMRGTriaEnv, Pas, Pbs, trunc) - coordinates = eachcoordinate(network, 1:6) - E = scalartype(env.C[1, 1, 1]) - S = spacetype(env.C[1, 1, 1]) - T_proj = Tuple{AbstractTensorMap{E, S, 2 + (network[1, 1] isa Tuple), 1}, AbstractTensorMap{E, S, 2 + (network[1, 1] isa Tuple), 1}, AbstractTensorMap{E, S, 2 + (network[1, 1] isa Tuple), 1}, AbstractTensorMap{E, S, 2 + (network[1, 1] isa Tuple), 1}} - projectors′ = similar(coordinates, T_proj) - projectors = dtmap!!(projectors′, coordinates) do (dir, r, c) - mat = semi_renormalize_edge(network, env, Pas, Pbs, dir, r, c) - - U, S, V = svd_trunc(mat; trunc = trunctol(; atol = 1.0e-20)) - - Ẽb = U * sqrt(S) - Ẽa = _permute_edge(sqrt(S) * V) - - Utr, Str, Vtr = svd_trunc(mat; trunc = trunc & trunctol(; atol = 1.0e-20)) - Ẽbtr = Utr * sqrt(Str) - Ẽatr = _permute_edge(sqrt(Str) * Vtr) - - return Ẽa, Ẽb, Ẽatr, Ẽbtr - end - return getindex.(projectors, 1), getindex.(projectors, 2), getindex.(projectors, 3), getindex.(projectors, 4) -end - -function build_halfinfinite_projectors(network::InfiniteTriangularNetwork{P}, env::CTMRGTriaEnv, Ẽas, Ẽbs, Ẽastr, Ẽbstr, dir, r, c) where {P <: PEPSTriaSandwich} - @tensor opt = true σL[-1 -2 -3; -4] := env.C[_coordinates(dir, 0, r, c, size(network))...][1 2 10; 8] * env.C[_coordinates(dir - 1, 0, r, c, size(network))...][4 3 11; 1] * env.C[_coordinates(dir - 2, 0, r, c, size(network))...][6 5 12; 4] * - Ẽbs[_coordinates(dir, 1, r, c, size(network))...][8 9 13; -4] * Ẽastr[_coordinates(dir - 3, 0, r, c, size(network))...][-1 7 14; 6] * - rotl60(ket(network[r, c]), mod(dir - 1, 6))[15 2 9 -2 7 5 3] * conj(rotl60(bra(network[r, c]), mod(dir - 1, 6))[15; 10 13 -3 14 12 11]) - @tensor opt = true σR[-1; -2 -3 -4] := env.C[_coordinates(dir + 1, 0, r, c, size(network))...][8 2 10; 1] * env.C[_coordinates(dir + 2, 0, r, c, size(network))...][1 3 11; 4] * env.C[_coordinates(dir + 3, 0, r, c, size(network))...][4 5 12; 6] * - Ẽbstr[_coordinates(dir + 3, -1, r, c, size(network))...][6 7 13; -2] * Ẽas[_coordinates(dir, 0, r, c, size(network))...][-1 9 14; 8] * - rotl60(ket(network[_coordinates(dir + 2, 0, r, c, size(network))[2:3]...]), mod(dir - 1, 6))[15; 9 2 3 5 7 -3] * conj(rotl60(bra(network[_coordinates(dir + 2, 0, r, c, size(network))[2:3]...]), mod(dir - 1, 6))[15; 14 10 11 12 13 -4]) - return σL, σR -end - -function build_halfinfinite_projectors(network::InfiniteTriangularNetwork{P}, env::CTMRGTriaEnv, Ẽas, Ẽbs, Ẽastr, Ẽbstr, dir, r, c) where {P <: PFTriaTensor} - @tensor opt = true σL[-1 -2; -3] := env.C[_coordinates(dir, 0, r, c, size(network))...][1 2; 8] * env.C[_coordinates(dir - 1, 0, r, c, size(network))...][4 3; 1] * env.C[_coordinates(dir - 2, 0, r, c, size(network))...][6 5; 4] * - Ẽbs[_coordinates(dir, 1, r, c, size(network))...][8 9; -3] * Ẽastr[_coordinates(dir - 3, 0, r, c, size(network))...][-1 7; 6] * - rotl60(network[r, c], mod(dir - 1, 6))[3 5 7; 2 9 -2] - @tensor opt = true σR[-1; -2 -3] := env.C[_coordinates(dir + 1, 0, r, c, size(network))...][8 2; 1] * env.C[_coordinates(dir + 2, 0, r, c, size(network))...][1 3; 4] * env.C[_coordinates(dir + 3, 0, r, c, size(network))...][4 5; 6] * - Ẽbstr[_coordinates(dir + 3, -1, r, c, size(network))...][6 7; -2] * Ẽas[_coordinates(dir, 0, r, c, size(network))...][-1 9; 8] * - rotl60(network[_coordinates(dir + 2, 0, r, c, size(network))[2:3]...], mod(dir - 1, 6))[-3 7 5; 9 2 3] - return σL, σR -end - -function build_matrix_second_projectors(network::InfiniteTriangularNetwork, env::CTMRGTriaEnv, Ẽas, Ẽbs, Ẽastr, Ẽbstr, trunc; conditioning = true) - coordinates = eachcoordinate(network, 1:6) - E = scalartype(env.C[1, 1, 1]) - S = spacetype(env.C[1, 1, 1]) - T_proj = Tuple{AbstractTensorMap{E, S, 1, 1}, AbstractTensorMap{E, S, 1, 1}} - projectors′ = similar(coordinates, T_proj) - projectors = dtmap!!(projectors′, coordinates) do (dir, r, c) - σL, σR = build_halfinfinite_projectors(network, env, Ẽas, Ẽbs, Ẽastr, Ẽbstr, dir, r, c) - if conditioning - σL /= norm(σL) - σR /= norm(σR) - UL, SL, VLᴴ = svd_trunc(σL; trunc = trunctol(; atol = 1.0e-20)) - UR, SR, VRᴴ = svd_trunc(σR; trunc = trunctol(; atol = 1.0e-20)) - - FLU = sqrt(SL) * VLᴴ - FRU = UR * sqrt(SR) - - mat = FLU * FRU - mat /= norm(mat) - WU, SU, QUᴴ = svd_trunc(mat; trunc = trunc & trunctol(; atol = 1.0e-20)) - - Qa = sdiag_pow(SU, -1 / 2) * WU' * FLU - Qb = FRU * QUᴴ' * sdiag_pow(SU, -1 / 2) - else - mat = σL * σR - mat /= norm(mat) - U, S, V = svd_trunc(mat; trunc = trunc & trunctol(; atol = 1.0e-20)) - Qa = sdiag_pow(S, -1 / 2) * U' * σL - Qb = σR * V' * sdiag_pow(S, -1 / 2) - end - return Qa, Qb - end - return getindex.(projectors, 1), getindex.(projectors, 2) -end - -function renormalize_edges(env::CTMRGTriaEnv, Ẽas::Array{T1, 3}, Ẽbs::Array{T1, 3}, Qas::Array{T2, 3}, Qbs::Array{T2, 3}) where {E, S, T1 <: AbstractTensorMap{E, S, 3, 1}, T2 <: AbstractTensorMap{E, S, 1, 1}} - coordinates = collect(Iterators.product(axes(env.Ea)...)) - T_proj = Tuple{AbstractTensorMap{E, S, 3, 1}, AbstractTensorMap{E, S, 3, 1}} - new_edges′ = similar(env.Ea, T_proj) - new_edges = dtmap!!(new_edges′, coordinates) do (dir, r, c) - @tensor Eb_new[-1 -2 -3; -4] := Ẽbs[dir, r, c][-1 -2 -3; 1] * Qbs[dir, r, c][1; -4] - @tensor Ea_new[-1 -2 -3; -4] := Qas[dir, r, c][-1; 1] * Ẽas[dir, r, c][1 -2 -3; -4] - return Ea_new, Eb_new - end - return CTMRGTriaEnv(env.C, getindex.(new_edges, 1), getindex.(new_edges, 2)) -end - -function renormalize_edges(env::CTMRGTriaEnv, Ẽas::Array{T1, 3}, Ẽbs::Array{T1, 3}, Qas::Array{T2, 3}, Qbs::Array{T2, 3}) where {E, S, T1 <: AbstractTensorMap{E, S, 2, 1}, T2 <: AbstractTensorMap{E, S, 1, 1}} - coordinates = collect(Iterators.product(axes(env.Ea)...)) - T_proj = Tuple{AbstractTensorMap{E, S, 2, 1}, AbstractTensorMap{E, S, 2, 1}} - new_edges′ = similar(env.Ea, T_proj) - new_edges = dtmap!!(new_edges′, coordinates) do (dir, r, c) - @tensor Eb_new[-1 -2; -3] := Ẽbs[dir, r, c][-1 -2; 1] * Qbs[dir, r, c][1; -3] - @tensor Ea_new[-1 -2; -3] := Qas[dir, r, c][-1; 1] * Ẽas[dir, r, c][1 -2; -3] - return Ea_new, Eb_new - end - return CTMRGTriaEnv(env.C, getindex.(new_edges, 1), getindex.(new_edges, 2)) -end - -function normalize_corners!(env) - coordinates = collect(Iterators.product(axes(env.Ea)...)) - new_env′ = similar(env.C, typeof(env.C[1, 1, 1])) - new_env = dtmap!!(new_env′, coordinates) do (dir, r, c) - env_C_new = env.C[dir, r, c] / norm(env.C[dir, r, c]) - return env_C_new - end - return new_env -end - -function normalize_edges!(env) - for dir in 1:6 - env.Ea[dir] /= norm(env.Ea[dir]) - env.Eb[dir] /= norm(env.Eb[dir]) - end - return env -end - -function calculate_error(Ss, Ss_prev) - ε = Inf - for (S, S_prev) in zip(Ss, Ss_prev) - if space(S) == space(S_prev) - ε = norm(S^4 - S_prev^4) - else - return Inf - end - end - return ε -end - -# Expectation_values - -function network_value(network::InfiniteTriangularNetwork, env::CTMRGTriaEnv) - return zero(scalartype(network)) -end - -function energy(network::InfiniteTriangularNetwork{P}, env::CTMRGTriaEnv, H) where {P <: PEPSTriaSandwich} - (r, c) = (1, 1) - numerator = @tensor opt = true ket(network[r, c])[dLt; DLt120 DLt60 DLt0 DLt300 DLt240 DLt180] * ket(network[r, c])[dRt; DRt120 DRt60 DRt0 DRt300 DRt240 DLt0] * - conj(bra(network[r, c])[dLb DLb120 DLb60 DLb0 DLb300 DLb240 DLb180]) * conj(bra(network[r, c])[dRb DRb120 DRb60 DRb0 DRb300 DRb240 DLb0]) * - env.C[1][χNW DLt120 DLb120; χNa] * env.C[2][χNb DRt60 DRb60; χNE] * env.C[3][χNE DRt0 DRb0; χSE] * - env.C[4][χSE DRt300 DRb300; χSa] * env.C[5][χSb DLt240 DLb240; χSW] * env.C[6][χSW DLt180 DLb180; χNW] * - env.Eb[1][χNa DLt60 DLb60; χNC] * env.Ea[1][χNC DRt120 DRb120; χNb] * - env.Eb[4][χSa DRt240 DRb240; χSC] * env.Ea[4][χSC DLt300 DLb300; χSb] * - H[dLb dRb; dLt dRt] - - denumerator = @tensor opt = true ket(network[r, c])[dL; DLt120 DLt60 DLt0 DLt300 DLt240 DLt180] * ket(network[r, c])[dR; DRt120 DRt60 DRt0 DRt300 DRt240 DLt0] * - conj(bra(network[r, c])[dL; DLb120 DLb60 DLb0 DLb300 DLb240 DLb180]) * conj(bra(network[r, c])[dR; DRb120 DRb60 DRb0 DRb300 DRb240 DLb0]) * - env.C[1][χNW DLt120 DLb120; χNa] * env.C[2][χNb DRt60 DRb60; χNE] * env.C[3][χNE DRt0 DRb0; χSE] * - env.C[4][χSE DRt300 DRb300; χSa] * env.C[5][χSb DLt240 DLb240; χSW] * env.C[6][χSW DLt180 DLb180; χNW] * - env.Eb[1][χNa DLt60 DLb60; χNC] * env.Ea[1][χNC DRt120 DRb120; χNb] * - env.Eb[4][χSa DRt240 DRb240; χSC] * env.Ea[4][χSC DLt300 DLb300; χSb] - return numerator / denumerator -end diff --git a/src/environments/ctmrg_tria_environments.jl b/src/environments/ctmrg_environments_triangular.jl similarity index 92% rename from src/environments/ctmrg_tria_environments.jl rename to src/environments/ctmrg_environments_triangular.jl index 6d961100a..433cc7b47 100644 --- a/src/environments/ctmrg_tria_environments.jl +++ b/src/environments/ctmrg_environments_triangular.jl @@ -1,5 +1,5 @@ #TODO: Add docs and figure -struct CTMRGTriaEnv{T} +struct CTMRGEnvTriangular{T} "6 x rows x cols array of corner C tensors, where the first dimension specifies the spatial direction" C::Array{T, 3} "6 x rows x cols array of edge Ta tensors, where the first dimension specifies the spatial direction" @@ -7,9 +7,9 @@ struct CTMRGTriaEnv{T} "6 x rows x cols array of edge Ta tensors, where the first dimension specifies the spatial direction" Eb::Array{T, 3} end -# function CTMRGTriaEnv(corners::Array{C, 3}, edges::Array{T, 3}) where {C, T} +# function CTMRGEnvTriangular(corners::Array{C, 3}, edges::Array{T, 3}) where {C, T} # foreach(check_environment_virtualspace, edges) -# return CTMRGTriaEnv{C, T}(corners, edges) +# return CTMRGEnvTriangular{C, T}(corners, edges) # end # """ @@ -42,7 +42,7 @@ function get_Ds(D::A) where {A <: ElementarySpace} return [dir > 3 ? D' : D for dir in 1:6] end -function CTMRGTriaEnv( +function CTMRGEnvTriangular( f, T, D::Union{A, B}, chis::B; unitcell::Tuple{Int, Int} = (1, 1) ) where { A <: ProductSpace, B <: ElementarySpace, @@ -73,5 +73,5 @@ function CTMRGTriaEnv( Eas[dir, r, c] = Ea Ebs[dir, r, c] = Eb end - return CTMRGTriaEnv(Cs, Eas, Ebs) + return CTMRGEnvTriangular(Cs, Eas, Ebs) end diff --git a/src/networks/infinitetriangularnetwork.jl b/src/networks/infinitetriangularnetwork.jl index 2ffa61ab0..953f595dd 100644 --- a/src/networks/infinitetriangularnetwork.jl +++ b/src/networks/infinitetriangularnetwork.jl @@ -1,4 +1,4 @@ -const PEPSTriaTensor{S <: ElementarySpace} = AbstractTensorMap{<:Any, S, 1, 6} +const PEPSTensorTriangular{S <: ElementarySpace} = AbstractTensorMap{<:Any, S, 1, 6} """ $(TYPEDEF) diff --git a/src/networks/local_triangular_sandwich.jl b/src/networks/local_triangular_sandwich.jl index 380fa18e9..a5fe0ab99 100644 --- a/src/networks/local_triangular_sandwich.jl +++ b/src/networks/local_triangular_sandwich.jl @@ -19,25 +19,25 @@ _rotr60_localsandwich(O) = rotr60.(O) ## PartitionFunction # specialized local rotation interface -_rotl60_localsandwich(O::PFTriaTensor) = rotl60(O) -_rotr60_localsandwich(O::PFTriaTensor) = rotr60(O) -_rot180_localsandwich(O::PFTriaTensor) = rot180(O) +_rotl60_localsandwich(O::PFTensorTriangular) = rotl60(O) +_rotr60_localsandwich(O::PFTensorTriangular) = rotr60(O) +_rot180_localsandwich(O::PFTensorTriangular) = rot180(O) # specialized local math interface -_add_localsandwich(O1::PFTriaTensor, O2::PFTriaTensor) = O1 + O2 -_subtract_localsandwich(O1::PFTriaTensor, O2::PFTriaTensor) = O1 - O2 -_mul_localsandwich(α::Number, O::PFTriaTensor) = α * O -_isapprox_localsandwich(O1::PFTriaTensor, O2::PFTriaTensor; kwargs...) = isapprox(O1, O2; kwargs...) +_add_localsandwich(O1::PFTensorTriangular, O2::PFTensorTriangular) = O1 + O2 +_subtract_localsandwich(O1::PFTensorTriangular, O2::PFTensorTriangular) = O1 - O2 +_mul_localsandwich(α::Number, O::PFTensorTriangular) = α * O +_isapprox_localsandwich(O1::PFTensorTriangular, O2::PFTensorTriangular; kwargs...) = isapprox(O1, O2; kwargs...) ## PEPS -const PEPSTriaSandwich{T <: PEPSTriaTensor} = Tuple{T, T} +const PEPSSandwichTriangular{T <: PEPSTensorTriangular} = Tuple{T, T} -ket(O::PEPSTriaSandwich) = O[1] -bra(O::PEPSTriaSandwich) = O[2] +ket(O::PEPSSandwichTriangular) = O[1] +bra(O::PEPSSandwichTriangular) = O[2] -function virtualspace(O::PEPSTriaSandwich, dir) +function virtualspace(O::PEPSSandwichTriangular, dir) return virtualspace(ket(O), dir) ⊗ virtualspace(bra(O), dir)' end -TensorKit.spacetype(::Type{P}) where {P <: PEPSTriaSandwich} = spacetype(eltype(P)) +TensorKit.spacetype(::Type{P}) where {P <: PEPSSandwichTriangular} = spacetype(eltype(P)) diff --git a/src/networks/tensors_tria.jl b/src/networks/tensors_triangular.jl similarity index 75% rename from src/networks/tensors_tria.jl rename to src/networks/tensors_triangular.jl index cca27ff1f..00c60de23 100644 --- a/src/networks/tensors_tria.jl +++ b/src/networks/tensors_triangular.jl @@ -3,7 +3,7 @@ # """ - const PartitionFunctionTriaTensor{S} + const PartitionFunctionTensorTriangular{S} Default type for partition function tensors with 6 virtual indices, conventionally ordered as: ``T : W ⊗ SW ⊗ SE ← NW ⊗ NE ⊗ E``. Here ``NW``, ``NE``, ``E``, ``SE``, ``SW`` and ``W`` denote the @@ -22,19 +22,19 @@ where the angles denote the directions of the legs with respect to the positive SW P SE ``` """ -const PartitionFunctionTriaTensor{S <: ElementarySpace} = AbstractTensorMap{<:Any, S, 3, 3} -const PFTriaTensor = PartitionFunctionTriaTensor +const PartitionFunctionTensorTriangular{S <: ElementarySpace} = AbstractTensorMap{<:Any, S, 3, 3} +const PFTensorTriangular = PartitionFunctionTensorTriangular """ - PartitionFunctionTriaTensor(f, ::Type{T}, NWspace::S, + PartitionFunctionTensorTriangular(f, ::Type{T}, NWspace::S, [NEspace::S], [Espace::S], [SEspace::S], [SWspace::S], [Wspace::S]) where {T,S<:Union{Int,ElementarySpace}} -Construct a PartitionFunctionTriaTensor tensor based on the +Construct a PartitionFunctionTensorTriangular tensor based on the north-west, north-east, east, south-east, south-west and west spaces The tensor elements are generated based on `f` and the element type is specified in `T`. """ -function PartitionFunctionTriaTensor( +function PartitionFunctionTensorTriangular( f, ::Type{T}, NWspace::S, NEspace::S = NWspace, Espace::S = NWspace, SEspace::S = NWspace, SWspace::S = NWspace, Wspace::S = NWspace, @@ -42,7 +42,7 @@ function PartitionFunctionTriaTensor( return f(T, Wspace ⊗ SWspace ⊗ SEspace ← NWspace ⊗ NEspace ⊗ Espace) end -function virtualspace(t::PFTriaTensor, dir) +function virtualspace(t::PFTensorTriangular, dir) invp = (4, 5, 6, 3, 2, 1) # internally, virtual directions are ordered as NW, NE, E, SE, SW... return space(t, invp[dir]) end @@ -52,7 +52,7 @@ end # """ - const PEPSTriaTensor{S} + const PEPSTensorTriangular{S} Default type for PEPS tensors with a single physical index, and 6 virtual indices, conventionally ordered as: ``T : P ← NW ⊗ NE ⊗ E ⊗ SE ⊗ SW ⊗ W``. Here, ``P`` denotes the physical space @@ -74,7 +74,7 @@ where the angles denote the directions of the legs with respect to the positive ``` ``` """ -const PEPSTriaTensor{S <: ElementarySpace} = AbstractTensorMap{<:Any, S, 1, 6} +const PEPSTensorTriangular{S <: ElementarySpace} = AbstractTensorMap{<:Any, S, 1, 6} """ PEPSTensor(f, ::Type{T}, Pspace::S, NWspace::S, @@ -84,7 +84,7 @@ const PEPSTriaTensor{S <: ElementarySpace} = AbstractTensorMap{<:Any, S, 1, 6} Construct a PEPS tensor based on the physical, north-west, north-east, east, south-east, south-west and west spaces. The tensor elements are generated based on `f` and the element type is specified in `T`. """ -function PEPSTriaTensor( +function PEPSTensorTriangular( f, ::Type{T}, Pspace::S, NWspace::S, NEspace::S = NWspace, Espace::S = NWspace, SEspace::S = NWspace, @@ -93,9 +93,9 @@ function PEPSTriaTensor( return f(T, Pspace ← NWspace ⊗ NEspace ⊗ Espace ⊗ SEspace ⊗ SWspace ⊗ Wspace) end -rotl60(t::PEPSTriaTensor) = permute(t, ((1,), (3, 4, 5, 6, 7, 2))) -rotr60(t::PEPSTriaTensor) = permute(t, ((1,), (7, 2, 3, 4, 5, 6))) -Base.rot180(t::PEPSTriaTensor) = permute(t, ((1,), (5, 6, 7, 2, 3, 4))) +rotl60(t::PEPSTensorTriangular) = permute(t, ((1,), (3, 4, 5, 6, 7, 2))) +rotr60(t::PEPSTensorTriangular) = permute(t, ((1,), (7, 2, 3, 4, 5, 6))) +Base.rot180(t::PEPSTensorTriangular) = permute(t, ((1,), (5, 6, 7, 2, 3, 4))) -physicalspace(t::PEPSTriaTensor) = space(t, 1) -virtualspace(t::PEPSTriaTensor, dir) = space(t, dir + 1) +physicalspace(t::PEPSTensorTriangular) = space(t, 1) +virtualspace(t::PEPSTensorTriangular, dir) = space(t, dir + 1) diff --git a/src/states/infinitepepstriangular.jl b/src/states/infinitepepstriangular.jl new file mode 100644 index 000000000..07ab09a08 --- /dev/null +++ b/src/states/infinitepepstriangular.jl @@ -0,0 +1,268 @@ +""" + struct InfinitePEPSTriangular{T<:PEPSTensorTriangularTriangular} + +Represents an infinite projected entangled-pair state on a 2D triangular lattice. + +## Fields + +$(TYPEDFIELDS) +""" +struct InfinitePEPSTriangular{T <: PEPSTensorTriangular} + A::Matrix{T} + InfinitePEPSTriangular{T}(A::Matrix{T}) where {T <: PEPSTensorTriangular} = new{T}(A) + function InfinitePEPSTriangular(A::Array{T, 2}) where {T <: PEPSTensorTriangular} + bosonic_braiding = BraidingStyle(sectortype(T)) === Bosonic() + # for (d, w) in Tuple.(CartesianIndices(A)) + # (bosonic_braiding || !isdual(physicalspace(A[d, w]))) || + # throw(ArgumentError("Dual physical spaces for symmetry sectors with non-trivial twists are not allowed (for now).")) + # north_virtualspace(A[d, w]) == south_virtualspace(A[_prev(d, end), w])' || + # throw( + # SpaceMismatch("North virtual space at site $((d, w)) does not match.") + # ) + # east_virtualspace(A[d, w]) == west_virtualspace(A[d, _next(w, end)])' || + # throw(SpaceMismatch("East virtual space at site $((d, w)) does not match.")) + # dim(space(A[d, w])) > 0 || @warn "no fusion channels at site ($d, $w)" + # end + return new{T}(A) + end +end + +## Constructors + +""" + InfinitePEPSTriangular(A::AbstractMatrix{T}) + +Create an `InfinitePEPSTriangular` by specifying a matrix containing the PEPS tensors at each site in +the unit cell. +""" +function InfinitePEPSTriangular(A::AbstractMatrix{<:PEPSTensorTriangular}) + return InfinitePEPSTriangular(Array(deepcopy(A))) # TODO: find better way to copy +end + +""" + InfinitePEPSTriangular([f=randn, T=ComplexF64,] Pspaces::A, Nspaces::A, [Espaces::A]) where {A<:AbstractMatrix{ElementarySpace}} + +Create an `InfinitePEPSTriangular` by specifying the physical, north virtual and east virtual spaces +of the PEPS tensor at each site in the unit cell as a matrix. +""" +function InfinitePEPSTriangular( + f, T::Type{<:Number}, Pspaces::M, NWspaces::M, NEspaces::M = NWspaces, Espaces::M = NWspaces + ) where {M <: AbstractMatrix{<:ElementarySpace}} + size(Pspaces) == size(NEspaces) == size(NEspaces) == size(Espaces) || + throw(ArgumentError("Input spaces should have equal sizes.")) + + SEspaces = adjoint.(circshift(NWspaces, (-1, 0))) + SWspaces = adjoint.(NEspaces) + Wspaces = adjoint.(circshift(Espaces, (0, 1))) + + A = map(Pspaces, NWspaces, NEspaces, Espaces, SEspaces, SWspaces, Wspaces) do NW, NE, E, SE, SW, W + return PEPSTensorTriangular(f, T, P, NW, NE, E, SE, SW, W) + end + + return InfinitePEPSTriangular(A) +end +function InfinitePEPSTriangular( + Pspaces::A, virtual_spaces...; kwargs... + ) where {A <: Union{AbstractMatrix{<:ElementarySpace}, ElementarySpace}} + return InfinitePEPSTriangular(randn, ComplexF64, Pspaces, virtual_spaces...; kwargs...) +end + +""" + InfinitePEPSTriangular(A::PEPSTensorTriangular; unitcell=(1, 1)) + +Create an `InfinitePEPSTriangular` by specifying a tensor and unit cell. + +The unit cell is labeled as a matrix which means that any tensor in the unit cell, +regardless if PEPS tensor or environment tensor, is obtained by shifting the row +and column index `[r, c]` by one, respectively: +``` + | | | +---C[r-1,c-1]---T[r-1,c]---T[r-1,c+1]--- + | || || +---T[r,c-1]=====AA[r,c]====AA[r,c+1]==== + | || || +---T[r+1,c-1]===AA[r+1,c]==AA[r+1,c+1]== + | || || +``` +The unit cell has periodic boundary conditions, so `[r, c]` is indexed modulo the +size of the unit cell. +""" +function InfinitePEPSTriangular(A::T; unitcell::Tuple{Int, Int} = (1, 1)) where {T <: PEPSTensorTriangular} + return InfinitePEPSTriangular(fill(A, unitcell)) +end + +# expand PEPS spaces to unit cell size +function _fill_state_virtual_spaces_triangular( + NWspace::S, NEspace::S = NWspace, Espace::S = NWspace; unitcell::Tuple{Int, Int} = (1, 1) + ) where {S <: ElementarySpace} + return fill(NWspace, unitcell), fill(NEspace, unitcell), fill(Espace, unitcell) +end + +""" + InfinitePEPSTriangular([f=randn, T=ComplexF64,] Pspace, Nspace, [Espace]; unitcell=(1,1)) + +Create an InfinitePEPSTriangular by specifying its physical, north and east spaces and unit cell. +""" +function InfinitePEPSTriangular( + f, T::Type{<:Number}, Pspace::S, vspaces...; unitcell::Tuple{Int, Int} = (1, 1) + ) where {S <: ElementarySpace} + return InfinitePEPSTriangular( + f, T, + _fill_state_physical_spaces(Pspace; unitcell), + _fill_state_virtual_spaces_triangular(vspaces...; unitcell)..., + ) +end + +## Unit cell interface + +unitcell(t::InfinitePEPSTriangular) = t.A +Base.size(A::InfinitePEPSTriangular, args...) = size(unitcell(A), args...) +Base.length(A::InfinitePEPSTriangular) = length(unitcell(A)) +Base.eltype(::Type{InfinitePEPSTriangular{T}}) where {T} = T +Base.eltype(A::InfinitePEPSTriangular) = eltype(typeof(A)) + +Base.copy(A::InfinitePEPSTriangular) = InfinitePEPSTriangular(copy(unitcell(A))) +function Base.similar(A::InfinitePEPSTriangular, T::Type{TorA} = scalartype(A)) where {TorA} + return InfinitePEPSTriangular(map(t -> similar(t, T), unitcell(A))) +end +Base.repeat(A::InfinitePEPSTriangular, counts...) = InfinitePEPSTriangular(repeat(unitcell(A), counts...)) + +Base.getindex(A::InfinitePEPSTriangular, args...) = Base.getindex(unitcell(A), args...) +Base.setindex!(A::InfinitePEPSTriangular, args...) = (Base.setindex!(unitcell(A), args...); A) +Base.axes(A::InfinitePEPSTriangular, args...) = axes(unitcell(A), args...) +eachcoordinate(A::InfinitePEPSTriangular) = collect(Iterators.product(axes(A)...)) +function eachcoordinate(A::InfinitePEPSTriangular, dirs) + return collect(Iterators.product(dirs, axes(A, 1), axes(A, 2))) +end + +## Spaces + +TensorKit.spacetype(::Type{T}) where {T <: InfinitePEPSTriangular} = spacetype(eltype(T)) +virtualspace(n::InfinitePEPSTriangular, dir) = virtualspace.(unitcell(n), dir) +function virtualspace(n::InfinitePEPSTriangular, r::Int, c::Int, dir) + Nr, Nc = size(n) + return virtualspace(n[mod1(r, Nr), mod1(c, Nc)], dir) +end +physicalspace(n::InfinitePEPSTriangular) = physicalspace.(unitcell(n)) +function physicalspace(n::InfinitePEPSTriangular, r::Int, c::Int) + Nr, Nc = size(n) + return physicalspace(n[mod1(r, Nr), mod1(c, Nc)]) +end + +## InfiniteTriangularNetwork interface + +function InfiniteTriangularNetwork(top::InfinitePEPSTriangular, bot::InfinitePEPSTriangular = top) + size(top) == size(bot) || throw( + ArgumentError("Top PEPS, bottom PEPS and PEPO rows should have the same length") + ) + return InfiniteTriangularNetwork(map(tuple, unitcell(top), unitcell(bot))) +end + +## Vector interface + +VI.scalartype(::Type{NT}) where {NT <: InfinitePEPSTriangular} = scalartype(eltype(NT)) +VI.zerovector(A::InfinitePEPSTriangular) = InfinitePEPSTriangular(zerovector(unitcell(A))) + +function VI.scale(ψ::InfinitePEPSTriangular, α::Number) + _scale = Base.Fix2(scale, α) + return InfinitePEPSTriangular(map(_scale, unitcell(ψ))) +end +function VI.scale!(ψ::InfinitePEPSTriangular, α::Number) + _scale! = Base.Fix2(scale!, α) + foreach(_scale!, unitcell(ψ)) + return ψ +end +function VI.scale!(ψ₁::InfinitePEPSTriangular, ψ₂::InfinitePEPSTriangular, α::Number) + _scale!(x, y) = scale!(x, y, α) + foreach(_scale!, unitcell(ψ₁), unitcell(ψ₂)) + return ψ₁ +end +VI.scale!!(ψ::InfinitePEPSTriangular, α::Number) = scale!(ψ, α) +VI.scale!!(ψ₁::InfinitePEPSTriangular, ψ₂::InfinitePEPSTriangular, α::Number) = scale!(ψ₁, ψ₂, α) + +function VI.add(ψ₁::InfinitePEPSTriangular, ψ₂::InfinitePEPSTriangular, α::Number, β::Number) + _add(x, y) = add(x, y, α, β) + return InfinitePEPSTriangular(map(_add, unitcell(ψ₁), unitcell(ψ₂))) +end +function VI.add!(ψ₁::InfinitePEPSTriangular, ψ₂::InfinitePEPSTriangular, α::Number, β::Number) + _add!(x, y) = add!(x, y, α, β) + foreach(_add!, unitcell(ψ₁), unitcell(ψ₂)) + return ψ₁ +end +VI.add!!(ψ₁::InfinitePEPSTriangular, ψ₂::InfinitePEPSTriangular, α::Number, β::Number) = add!(ψ₁, ψ₂, α, β) + +## Math + +function Base.:+(A₁::InfinitePEPSTriangular, A₂::InfinitePEPSTriangular) + return InfinitePEPSTriangular(unitcell(A₁) + unitcell(A₂)) +end +function Base.:-(A₁::InfinitePEPSTriangular, A₂::InfinitePEPSTriangular) + return InfinitePEPSTriangular(unitcell(A₁) - unitcell(A₂)) +end +Base.:*(α::Number, A::InfinitePEPSTriangular) = InfinitePEPSTriangular(α * unitcell(A)) +Base.:*(A::InfinitePEPSTriangular, α::Number) = α * A +Base.:/(A::InfinitePEPSTriangular, α::Number) = InfinitePEPSTriangular(unitcell(A) / α) +LinearAlgebra.dot(A₁::InfinitePEPSTriangular, A₂::InfinitePEPSTriangular) = dot(unitcell(A₁), unitcell(A₂)) +LinearAlgebra.norm(A::InfinitePEPSTriangular) = norm(unitcell(A)) + +## (Approximate) equality +function Base.:(==)(A₁::InfinitePEPSTriangular, A₂::InfinitePEPSTriangular) + return all(zip(unitcell(A₁), unitcell(A₂))) do (p₁, p₂) + return p₁ == p₂ + end +end +function Base.isapprox(A₁::InfinitePEPSTriangular, A₂::InfinitePEPSTriangular; kwargs...) + return all(zip(unitcell(A₁), unitcell(A₂))) do (p₁, p₂) + return isapprox(p₁, p₂; kwargs...) + end +end + +## Rotations + +# rotl60(A::InfinitePEPSTriangular) = InfinitePEPSTriangular(rotl60(_rotl60_localsandwich.(unitcell(A)))) +# rotr60(A::InfinitePEPSTriangular) = InfinitePEPSTriangular(rotr60(_rotl60_localsandwich.(unitcell(A)))) +# Base.rot180(A::InfinitePEPSTriangular) = InfinitePEPSTriangular(rot180(rot180.(unitcell(A)))) + +## FiniteDifferences vectorization + +""" + to_vec(A::InfinitePEPSTriangular) -> vec, state_from_vec + +Vectorize an `InfinitePEPSTriangular` into a vector of real numbers. A vectorized infinite PEPS can +retrieved again as an `InfinitePEPSTriangular` by application of the `state_from_vec` map. +""" +function FiniteDifferences.to_vec(A::InfinitePEPSTriangular) + vec, back = FiniteDifferences.to_vec(unitcell(A)) + function state_from_vec(vec) + return NWType(back(vec)) + end + return vec, state_from_vec +end + +## Chainrules + +function ChainRulesCore.rrule(::typeof(Base.getindex), network::InfinitePEPSTriangular, args...) + tensor = network[args...] + + function getindex_pullback(Δtensor_) + Δtensor = unthunk(Δtensor_) + Δnetwork = zerovector(network) + Δnetwork[args...] = Δtensor + return NoTangent(), Δnetwork, NoTangent(), NoTangent() + end + return tensor, getindex_pullback +end + +function ChainRulesCore.rrule( + ::Type{InfiniteTriangularNetwork}, top::InfinitePEPSTriangular, bot::InfinitePEPSTriangular + ) + network = InfiniteTriangularNetwork(top, bot) + + function InfiniteTriangularNetwork_pullback(Δnetwork_) + Δnetwork = unthunk(Δnetwork_) + Δtop = InfinitePEPSTriangular(map(ket, unitcell(Δnetwork))) + Δbot = InfinitePEPSTriangular(map(bra, unitcell(Δnetwork))) + return NoTangent(), Δtop, Δbot + end + return network, InfiniteTriangularNetwork_pullback +end diff --git a/test/ctmrg/triangular.jl b/test/ctmrg/triangular.jl index e18ac28d0..c71647d42 100644 --- a/test/ctmrg/triangular.jl +++ b/test/ctmrg/triangular.jl @@ -6,26 +6,51 @@ using MPSKit using PEPSKit using OptimKit using Zygote +using LinearAlgebra +using KrylovKit + +const ising_βc_triangular = BigFloat(BigFloat(asinh(BigFloat(sqrt(BigFloat(1.0) / BigFloat(3.0))))) / BigFloat(2.0)) +const f_onsager_triangular::BigFloat = -3.20253248660790791834355252025862951439 + +function classical_ising_triangular(β) + t = Float64[exp(β) exp(-β); exp(-β) exp(β)] + + r = eigen(t) + nt = r.vectors * sqrt(LinearAlgebra.Diagonal(r.values)) * r.vectors + + O = zeros(2, 2, 2, 2, 2, 2) + O[1, 1, 1, 1, 1, 1] = 1 + O[2, 2, 2, 2, 2, 2] = 1 + + H = [1 1; 1 -1] / sqrt(2) + + @tensor o[-1 -2 -3; -4 -5 -6] := O[1 2 3; 4 5 6] * nt[-1; 1] * nt[-2; 2] * nt[-3; 3] * nt[-4; 4] * nt[-5; 5] * nt[-6; 6] + @tensor o2[-1 -2 -3; -4 -5 -6] := o[1 2 3; 4 5 6] * H[-1; 1] * H[-2; 2] * H[-3; 3] * H[-4; 4] * H[-5; 5] * H[-6; 6] + return TensorMap(o2, ℂ^2 * ℂ^2 * ℂ^2, ℂ^2 * ℂ^2 * ℂ^2) +end @testset "CTM_triangular - Random tensor" begin for conditioning in [true false] for projector_alg in [:twothirds :full] - alg = SimultaneousCTMRGTria(; conditioning, projector_alg) - T = Float64 + Random.seed!(79413165445) + alg = SimultaneousCTMRGTriangular(; maxiter = 300, conditioning, projector_alg) + eltype = ComplexF64 χ = 7 pspace = ℂ^2 vspace = ℂ^3 envspace = ℂ^χ - ket = randn(T, pspace, vspace ⊗ vspace ⊗ vspace ⊗ vspace' ⊗ vspace' ⊗ vspace') + ket = randn(eltype, pspace, vspace ⊗ vspace ⊗ vspace ⊗ vspace' ⊗ vspace' ⊗ vspace') bra = copy(ket) - pf = randn(T, vspace ⊗ vspace ⊗ vspace, vspace ⊗ vspace ⊗ vspace) + pf = randn(eltype, vspace ⊗ vspace ⊗ vspace, vspace ⊗ vspace ⊗ vspace) sandwiches = [pf, (ket, bra)] - unitcell = (1, 1) + # sandwiches = [(ket, bra), pf] + unitcell = (2, 2) for (sandwich, vspace) in zip(sandwiches, [vspace, vspace ⊗ vspace']) + # for (sandwich, vspace) in zip(sandwiches, [vspace ⊗ vspace', vspace]) network = InfiniteTriangularNetwork(fill(sandwich, unitcell)) - env₀ = CTMRGTriaEnv(randn, T, vspace, envspace; unitcell) + env₀ = CTMRGEnvTriangular(randn, eltype, vspace, envspace; unitcell) env, info = leading_boundary(env₀, network, alg) @test info.convergence_metric < 1.0 end @@ -33,63 +58,100 @@ using Zygote end end -@testset "CTM_triangular - Differentiability" begin +@testset "CTM_triangular - Classical Ising" begin for conditioning in [true false] for projector_alg in [:twothirds :full] - alg = SimultaneousCTMRGTria(; conditioning, projector_alg) - T = Float64 - χ = 7 - pspace = ℂ^2 - vspace = ℂ^3 - envspace = ℂ^χ - ket = randn(T, pspace, vspace ⊗ vspace ⊗ vspace ⊗ vspace' ⊗ vspace' ⊗ vspace') - bra = copy(ket) + Random.seed!(156484561351) + alg = SimultaneousCTMRGTriangular(; maxiter = 300, conditioning, projector_alg) unitcell = (1, 1) - network = InfiniteTriangularNetwork(fill((ket, bra), unitcell)) - env₀ = CTMRGTriaEnv(randn, T, vspace ⊗ vspace', envspace; unitcell) - - optimizer_alg = LBFGS(4; maxiter = 10) - real_inner(_, η₁, η₂) = real(dot(η₁, η₂)) - reuse_env = true - peps₀ = copy(ket) + T = classical_ising_triangular(ising_βc_triangular) + pf = InfiniteTriangularNetwork(fill(T, unitcell)) - function peps_retract(x, η, α) - peps = x[1] - env = deepcopy(x[2]) - - retractions = norm_preserving_retract.(unitcell(peps), unitcell(η), α) - peps´ = InfinitePEPS(map(first, retractions)) - ξ = InfinitePEPS(map(last, retractions)) + χ = 20 + vspace = codomain(T)[1] + envspace = ℂ^χ + eltype = Float64 + env₀ = CTMRGEnvTriangular(randn, eltype, vspace, envspace; unitcell) + env, info = leading_boundary(env₀, pf, alg) - return (peps´, env), ξ - end - retract = peps_retract - - - # optimize operator cost function - (peps_final, env_final), cost_final, ∂cost, numfg, convergence_history = optimize( - (peps₀, env₀), optimizer_alg; - retract, inner = real_inner, - ) do (peps, env) - start_time = time_ns() - E, gs = withgradient(peps) do ψ - env′, info = hook_pullback( - leading_boundary, env, ψ, alg.boundary_alg; - alg_rrule = alg.gradient_alg, - ) - # ignore_derivatives() do - # reuse_env && update!(env, env′) - # push!(truncation_errors, info.truncation_error) - # push!(condition_numbers, info.condition_number) - # end - return energy(ψ, env′, operator) - end - g = only(gs) # `withgradient` returns tuple of gradients `gs` - push!(gradnorms_unitcell, norm.(g.A)) - push!(times, (time_ns() - start_time) * 1.0e-9) - return E, g - end + nw_value = network_value(pf, env) + lz = real(log(nw_value)) + fs = lz * -1 / ising_βc_triangular + @test fs ≈ f_onsager_triangular rtol = 1.0e-4 end end end + +# @testset "CTM_triangular - Differentiability" begin +# for conditioning in [true false] +# for projector_alg in [:twothirds :full] +# ctm_alg = SimultaneousCTMRGTriangular(; conditioning, projector_alg) +# T = ComplexF64 +# S = Trivial +# χ = 7 +# pspace = ℂ^2 + +# J = 1.0 +# g = 2.5 + +# ZZ = rmul!(PEPSKit.σᶻᶻ(T, S), -J) +# X = rmul!(PEPSKit.σˣ(T, S), g * -J) + +# vspace = ℂ^3 +# envspace = ℂ^χ +# ket = randn(T, pspace, vspace ⊗ vspace ⊗ vspace ⊗ vspace' ⊗ vspace' ⊗ vspace') +# bra = copy(ket) + +# unitcell = (1, 1) +# network = InfiniteTriangularNetwork(fill((ket, bra), unitcell)) +# env₀ = CTMRGEnvTriangular(randn, T, vspace ⊗ vspace', envspace; unitcell) + +# env, info = leading_boundary(env₀, network, ctm_alg) +# E₀ = PEPSKit.energy(network, env, X, ZZ) +# optimizer_alg = LBFGS(4; maxiter = 10) +# real_inner(_, η₁, η₂) = real(dot(η₁, η₂)) +# reuse_env = true +# peps₀ = InfinitePEPSTriangular(copy(ket)) + +# function peps_retract(x, η, α) +# peps = x[1] +# env = deepcopy(x[2]) + +# retractions = norm_preserving_retract.(unitcell(peps), unitcell(η), α) +# peps´ = InfinitePEPS(map(first, retractions)) +# ξ = InfinitePEPS(map(last, retractions)) + +# return (peps´, env), ξ +# end +# retract = peps_retract + +# # alg = PEPSOptimize() +# gradtol = 1e-3 +# gradient_alg = EigSolver(; solver_alg=Arnoldi(; tol=gradtol, eager=true), iterscheme=:fixed) +# # optimize operator cost function +# (peps_final, env_final), cost_final, ∂cost, numfg, convergence_history = optimize( +# (peps₀, env₀), optimizer_alg; +# retract, inner = real_inner, +# ) do (peps, env) +# start_time = time_ns() +# E, gs = withgradient(peps) do ψ +# env′, info = PEPSKit.hook_pullback( +# leading_boundary, env, ψ, ctm_alg; +# alg_rrule = gradient_alg, +# ) +# # ignore_derivatives() do +# # reuse_env && update!(env, env′) +# # push!(truncation_errors, info.truncation_error) +# # push!(condition_numbers, info.condition_number) +# # end +# return energy(ψ, env′, operator_onesite, operator_twosite) +# end +# g = only(gs) # `withgradient` returns tuple of gradients `gs` +# push!(gradnorms_unitcell, norm.(g.A)) +# push!(times, (time_ns() - start_time) * 1.0e-9) +# return E, g +# end +# end +# end +# end From 9c582f4aaed6cd1563c55ea198169f7b12759e61 Mon Sep 17 00:00:00 2001 From: sanderdemeyer Date: Tue, 24 Feb 2026 13:46:40 +0100 Subject: [PATCH 04/41] fix import of Base.rot180 --- src/networks/infinitetriangularnetwork.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/networks/infinitetriangularnetwork.jl b/src/networks/infinitetriangularnetwork.jl index 953f595dd..1383c83fe 100644 --- a/src/networks/infinitetriangularnetwork.jl +++ b/src/networks/infinitetriangularnetwork.jl @@ -116,7 +116,7 @@ end function rotr60(n::InfiniteTriangularNetwork) return InfiniteTriangularNetwork(rotr60(_rotr60_localsandwich.(unitcell(n)))) end -function rot180(n::InfiniteTriangularNetwork) +function Base.rot180(n::InfiniteTriangularNetwork) return InfiniteTriangularNetwork(rot180(_rot180_localsandwich.(unitcell(n)))) end From f7df34c18136b3847acdfd122e877dd55e9df7d9 Mon Sep 17 00:00:00 2001 From: sanderdemeyer Date: Tue, 24 Feb 2026 13:47:36 +0100 Subject: [PATCH 05/41] Fix formatting --- .../ctmrg_contractions_triangular.jl | 8 +- .../ctmrg_triangular/simultaneous.jl | 10 +- .../fixed_point_differentiation.jl | 14 +- src/algorithms/toolbox_triangular.jl | 178 +++++++++--------- test/ctmrg/triangular.jl | 4 +- 5 files changed, 107 insertions(+), 107 deletions(-) diff --git a/src/algorithms/contractions/ctmrg_contractions_triangular.jl b/src/algorithms/contractions/ctmrg_contractions_triangular.jl index d00515d6b..1ef921c8b 100644 --- a/src/algorithms/contractions/ctmrg_contractions_triangular.jl +++ b/src/algorithms/contractions/ctmrg_contractions_triangular.jl @@ -46,13 +46,13 @@ end function renormalize_corner_triangular((dir, r, c), network::InfiniteTriangularNetwork{P}, env, Pas, Pbs) where {P <: PFTensorTriangular} @tensor opt = true env_C_new[-1 -2; -3] := env.C[_coordinates(dir, 0, r, c, size(network))...][1 3; 6] * env.Ea[_coordinates(dir - 1, 0, r, c, size(network))...][4 2; 1] * env.Eb[_coordinates(dir, 1, r, c, size(network))...][6 7; 8] * - rotl60(network[r, c], mod(dir - 1, 6))[2 5 -2; 3 7 9] * Pas[mod1(dir - 1, 6), r, c][-1; 4 5] * Pbs[dir, r, c][8 9; -3] + rotl60(network[r, c], mod(dir - 1, 6))[2 5 -2; 3 7 9] * Pas[mod1(dir - 1, 6), r, c][-1; 4 5] * Pbs[dir, r, c][8 9; -3] return env_C_new end function renormalize_corner_triangular((dir, r, c), network::InfiniteTriangularNetwork{P}, env, Pas, Pbs) where {P <: PEPSSandwichTriangular} @tensor opt = true env_C_new[-1 -2 -3; -4] := env.C[_coordinates(dir, 0, r, c, size(network))...][1 3 10; 6] * env.Ea[_coordinates(dir - 1, 0, r, c, size(network))...][4 2 11; 1] * env.Eb[_coordinates(dir, 1, r, c, size(network))...][6 7 12; 8] * - rotl60(ket(network[r, c]), mod(dir - 1, 6))[15; 3 7 9 -2 5 2] * conj(rotl60(bra(network[r, c]), mod(dir - 1, 6))[15; 10 12 14 -3 13 11]) * - Pas[mod1(dir - 1, 6), r, c][-1; 4 5 13] * Pbs[dir, r, c][8 9 14; -4] + rotl60(ket(network[r, c]), mod(dir - 1, 6))[15; 3 7 9 -2 5 2] * conj(rotl60(bra(network[r, c]), mod(dir - 1, 6))[15; 10 12 14 -3 13 11]) * + Pas[mod1(dir - 1, 6), r, c][-1; 4 5 13] * Pbs[dir, r, c][8 9 14; -4] return env_C_new -end \ No newline at end of file +end diff --git a/src/algorithms/ctmrg_triangular/simultaneous.jl b/src/algorithms/ctmrg_triangular/simultaneous.jl index cddda61a2..81b4f980c 100644 --- a/src/algorithms/ctmrg_triangular/simultaneous.jl +++ b/src/algorithms/ctmrg_triangular/simultaneous.jl @@ -190,7 +190,7 @@ function renormalize_edges(env::CTMRGEnvTriangular, Ẽas::Array{T1, 3}, Ẽbs:: new_edges′ = similar(env.Ea, T_proj) new_edges = dtmap!!(new_edges′, coordinates) do (dir, r, c) Eb_new = Ẽbs[dir, r, c] * Qbs[dir, r, c] - Ea_new = permute(Qas[dir, r, c] * permute(Ẽas[dir, r, c], ((1,),(2,3,4))), ((1,2,3),(4,))) + Ea_new = permute(Qas[dir, r, c] * permute(Ẽas[dir, r, c], ((1,), (2, 3, 4))), ((1, 2, 3), (4,))) return Ea_new, Eb_new end return CTMRGEnvTriangular(env.C, getindex.(new_edges, 1), getindex.(new_edges, 2)) @@ -202,7 +202,7 @@ function renormalize_edges(env::CTMRGEnvTriangular, Ẽas::Array{T1, 3}, Ẽbs:: new_edges′ = similar(env.Ea, T_proj) new_edges = dtmap!!(new_edges′, coordinates) do (dir, r, c) Eb_new = Ẽbs[dir, r, c] * Qbs[dir, r, c] - Ea_new = permute(Qas[dir, r, c] * permute(Ẽas[dir, r, c], ((1,),(2,3))), ((1,2),(3,))) + Ea_new = permute(Qas[dir, r, c] * permute(Ẽas[dir, r, c], ((1,), (2, 3))), ((1, 2), (3,))) return Ea_new, Eb_new end return CTMRGEnvTriangular(env.C, getindex.(new_edges, 1), getindex.(new_edges, 2)) @@ -219,10 +219,10 @@ function normalize_corners!(env) end function normalize_edges!(env) - (r, c) = (1,1) + (r, c) = (1, 1) for dir in 1:6 - env.Ea[dir,r,c] /= norm(env.Ea[dir,r,c]) - env.Eb[dir,r,c] /= norm(env.Eb[dir,r,c]) + env.Ea[dir, r, c] /= norm(env.Ea[dir, r, c]) + env.Eb[dir, r, c] /= norm(env.Eb[dir, r, c]) end return env end diff --git a/src/algorithms/optimization/fixed_point_differentiation.jl b/src/algorithms/optimization/fixed_point_differentiation.jl index 4ac24cb2c..3e55bad12 100644 --- a/src/algorithms/optimization/fixed_point_differentiation.jl +++ b/src/algorithms/optimization/fixed_point_differentiation.jl @@ -213,7 +213,7 @@ function _rrule( ::typeof(leading_boundary), envinit, state, - alg::Union{CTMRGAlgorithm,CTMRGAlgorithmTriangular}, + alg::Union{CTMRGAlgorithm, CTMRGAlgorithmTriangular}, ) env, info = leading_boundary(envinit, state, alg) alg_fixed = @set alg.projector_alg.trunc = FixedSpaceTruncation() # fix spaces during differentiation @@ -279,12 +279,12 @@ function _rrule( end function _rrule( - gradmode::GradMode{:fixed}, - config::RuleConfig, - ::typeof(MPSKit.leading_boundary), - envinit, - state, - alg::SimultaneousCTMRGTriangular, + gradmode::GradMode{:fixed}, + config::RuleConfig, + ::typeof(MPSKit.leading_boundary), + envinit, + state, + alg::SimultaneousCTMRGTriangular, ) env, = leading_boundary(envinit, state, alg) alg_fixed = @set alg.trunctype = :FixedSpaceTruncation # fix spaces during differentiation diff --git a/src/algorithms/toolbox_triangular.jl b/src/algorithms/toolbox_triangular.jl index cdf2f663d..ab67be0ef 100644 --- a/src/algorithms/toolbox_triangular.jl +++ b/src/algorithms/toolbox_triangular.jl @@ -1,126 +1,126 @@ function network_value(network::InfiniteTriangularNetwork, env::CTMRGEnvTriangular) return prod(Iterators.product(axes(network)...)) do (r, c) - nw_corners = complex(_contract_corners((r,c), network, env)) - nw_full = complex(_contract_site_large((r,c), network, env)) - nw_0 = complex(_contract_edges_0((r,c), network, env)) - nw_60 = complex(_contract_edges_60((r,c), network, env)) - nw_120 = complex(_contract_edges_120((r,c), network, env)) + nw_corners = complex(_contract_corners((r, c), network, env)) + nw_full = complex(_contract_site_large((r, c), network, env)) + nw_0 = complex(_contract_edges_0((r, c), network, env)) + nw_60 = complex(_contract_edges_60((r, c), network, env)) + nw_120 = complex(_contract_edges_120((r, c), network, env)) return (nw_full * nw_corners^2 / (nw_0 * nw_60 * nw_120))^(1 / 3) end end -function _contract_edges_0((r,c)::Tuple{Int,Int}, network::InfiniteTriangularNetwork{P}, env::CTMRGEnvTriangular) where {P <: PFTensorTriangular} - return @tensor opt = true network[r,c][DL180 DL240 DL300; DL120 DL60 DL0] * network[r,_next(c,end)][DL0 DR240 DR300; DR120 DR60 DR0] * - env.C[1,_prev(r,end),c][χNW DL120; χNa] * env.C[2,_prev(r,end),_next(c+1,end)][χNb DR60; χNE] * env.C[3,r,_next(c+1,end)][χNE DR0; χSE] * - env.C[4,_next(r,end),_next(c,end)][χSE DR300; χSa] * env.C[5,_next(r,end),_prev(c,end)][χSb DL240; χSW] * env.C[6,r,_prev(c,end)][χSW DL180; χNW] * - env.Eb[1,_prev(r,end),_next(c,end)][χNa DL60; χNC] * env.Ea[1,_prev(r,end),_next(c,end)][χNC DR120; χNb] * - env.Eb[4,_next(r,end),c][χSa DR240; χSC] * env.Ea[4,_next(r,end),c][χSC DL300; χSb] +function _contract_edges_0((r, c)::Tuple{Int, Int}, network::InfiniteTriangularNetwork{P}, env::CTMRGEnvTriangular) where {P <: PFTensorTriangular} + return @tensor opt = true network[r, c][DL180 DL240 DL300; DL120 DL60 DL0] * network[r, _next(c, end)][DL0 DR240 DR300; DR120 DR60 DR0] * + env.C[1, _prev(r, end), c][χNW DL120; χNa] * env.C[2, _prev(r, end), _next(c + 1, end)][χNb DR60; χNE] * env.C[3, r, _next(c + 1, end)][χNE DR0; χSE] * + env.C[4, _next(r, end), _next(c, end)][χSE DR300; χSa] * env.C[5, _next(r, end), _prev(c, end)][χSb DL240; χSW] * env.C[6, r, _prev(c, end)][χSW DL180; χNW] * + env.Eb[1, _prev(r, end), _next(c, end)][χNa DL60; χNC] * env.Ea[1, _prev(r, end), _next(c, end)][χNC DR120; χNb] * + env.Eb[4, _next(r, end), c][χSa DR240; χSC] * env.Ea[4, _next(r, end), c][χSC DL300; χSb] end -function _contract_edges_60((r,c)::Tuple{Int,Int}, network::InfiniteTriangularNetwork{P}, env::CTMRGEnvTriangular) where {P <: PFTensorTriangular} - return @tensor opt = true network[r,c][DTR180 DBL60 DTR300; DTR120 DTR60 DTR0] * network[_next(r,end),_prev(c,end)][DBL180 DBL240 DBL300; DBL120 DBL60 DBL0] * - env.C[1,_prev(r,end),c][χNWb DTR120; χN] * env.C[2,_prev(r,end),_next(c,end)][χN DTR60; χNE] * env.C[3,r,_next(c,end)][χNE DTR0; χSEa] * - env.C[4,_next(r+1,end),_prev(c,end)][χSEb DBL300; χS] * env.C[5,_next(r+1,end),_prev(c-1,end)][χS DBL240; χSW] * env.C[6,_next(r,end),_prev(c-1,end)][χSW DBL180; χNWa] * - env.Eb[3,_next(r,end),c][χSEa DTR300; χSEC] * env.Ea[3,_next(r,end),c][χSEC DBL0; χSEb] * - env.Eb[6,r,_prev(c,end)][χNWa DBL120; χNWC] * env.Ea[6,r,_prev(c,end)][χNWC DTR180; χNWb] +function _contract_edges_60((r, c)::Tuple{Int, Int}, network::InfiniteTriangularNetwork{P}, env::CTMRGEnvTriangular) where {P <: PFTensorTriangular} + return @tensor opt = true network[r, c][DTR180 DBL60 DTR300; DTR120 DTR60 DTR0] * network[_next(r, end), _prev(c, end)][DBL180 DBL240 DBL300; DBL120 DBL60 DBL0] * + env.C[1, _prev(r, end), c][χNWb DTR120; χN] * env.C[2, _prev(r, end), _next(c, end)][χN DTR60; χNE] * env.C[3, r, _next(c, end)][χNE DTR0; χSEa] * + env.C[4, _next(r + 1, end), _prev(c, end)][χSEb DBL300; χS] * env.C[5, _next(r + 1, end), _prev(c - 1, end)][χS DBL240; χSW] * env.C[6, _next(r, end), _prev(c - 1, end)][χSW DBL180; χNWa] * + env.Eb[3, _next(r, end), c][χSEa DTR300; χSEC] * env.Ea[3, _next(r, end), c][χSEC DBL0; χSEb] * + env.Eb[6, r, _prev(c, end)][χNWa DBL120; χNWC] * env.Ea[6, r, _prev(c, end)][χNWC DTR180; χNWb] end -function _contract_edges_120((r,c)::Tuple{Int,Int}, network::InfiniteTriangularNetwork{P}, env::CTMRGEnvTriangular) where {P <: PFTensorTriangular} - return @tensor opt = true network[r,c][DTL180 DTL240 DTL300; DTL120 DTL60 DTL0] * network[_next(r,end),c][DBR180 DBR240 DBR300; DTL300 DBR60 DBR0] * - env.C[1,_prev(r,end),c][χNW DTL120; χN] * env.C[2,_prev(r,end),_next(c,end)][χN DTL60; χNEa] * env.C[3,_next(r,end),_next(c,end)][χNEb DBR0; χSE] * - env.C[4,_next(r+1,end),c][χSE DBR300; χS] * env.C[5,_next(r+1,end),_prev(c,end)][χS DBR240; χSWa] * env.C[6,r,_prev(c,end)][χSWb DTL180; χNW] * - env.Eb[2,r,_next(c,end)][χNEa DTL0; χNEC] * env.Ea[2,r,_next(c,end)][χNEC DBR60; χNEb] * - env.Eb[5,_next(r,end),_prev(c,end)][χSWa DBR180; χSWC] * env.Ea[5,_next(r,end),_prev(c,end)][χSWC DTL240; χSWb] +function _contract_edges_120((r, c)::Tuple{Int, Int}, network::InfiniteTriangularNetwork{P}, env::CTMRGEnvTriangular) where {P <: PFTensorTriangular} + return @tensor opt = true network[r, c][DTL180 DTL240 DTL300; DTL120 DTL60 DTL0] * network[_next(r, end), c][DBR180 DBR240 DBR300; DTL300 DBR60 DBR0] * + env.C[1, _prev(r, end), c][χNW DTL120; χN] * env.C[2, _prev(r, end), _next(c, end)][χN DTL60; χNEa] * env.C[3, _next(r, end), _next(c, end)][χNEb DBR0; χSE] * + env.C[4, _next(r + 1, end), c][χSE DBR300; χS] * env.C[5, _next(r + 1, end), _prev(c, end)][χS DBR240; χSWa] * env.C[6, r, _prev(c, end)][χSWb DTL180; χNW] * + env.Eb[2, r, _next(c, end)][χNEa DTL0; χNEC] * env.Ea[2, r, _next(c, end)][χNEC DBR60; χNEb] * + env.Eb[5, _next(r, end), _prev(c, end)][χSWa DBR180; χSWC] * env.Ea[5, _next(r, end), _prev(c, end)][χSWC DTL240; χSWb] end -function _contract_site_large((r,c)::Tuple{Int,Int}, network::InfiniteTriangularNetwork{P}, env::CTMRGEnvTriangular) where {P <: PFTensorTriangular} - return @tensor opt = true network[_prev(r,end),c][DNW180 DW60 DNW300; DNW120 DNW60 DNW0] * network[_prev(r,end),_next(c,end)][DNW0 DNE240 DNE300; DNE120 DNE60 DNE0] * - network[r,_next(c,end)][DE180 DE240 DE300; DNE300 DE60 DE0] * network[_next(r,end),c][DSE180 DSE240 DSE300; DSE120 DE240 DSE0] * network[_next(r,end),_prev(c,end)][DSW180 DSW240 DSW300; DSW120 DSW60 DSE180] * - network[r,_prev(c,end)][DW180 DW240 DSW120; DW120 DW60 DW0] * network[r,c][DW0 DSW60 DSE120; DNW300 DNE240 DE180] * - env.C[1,_prev(r-1,end),c][χNWa DNW120; χNb] * env.Eb[1,_prev(r-1,end),_next(c,end)][χNb DNW60; χNC] * env.Ea[1,_prev(r-1,end),_next(c,end)][χNC DNE120; χNa] * - env.C[2,_prev(r-1,end),_next(c+1,end)][χNa DNE60; χNEb] * env.Eb[2,_prev(r,end),_next(c+1,end)][χNEb DNE0; χNEC] * env.Ea[2,_prev(r,end),_next(c+1,end)][χNEC DE60; χNEa] * - env.C[3,r,_next(c+1,end)][χNEa DE0; χSEb] * env.Eb[3,_next(r,end),_next(c,end)][χSEb DE300; χSEC] * env.Ea[3,_next(r,end),_next(c,end)][χSEC DSE0; χSEa] * - env.C[4,_next(r+1,end),c][χSEa DSE300; χSb] * env.Eb[4,_next(r+1,end),_prev(c,end)][χSb DSE240; χSC] * env.Ea[4,_next(r+1,end),_prev(c,end)][χSC DSW300; χSa] * - env.C[5,_next(r+1,end),_prev(c-1,end)][χSa DSW240; χSWb] * env.Eb[5,_next(r,end),_prev(c-1,end)][χSWb DSW180; χSWC] * env.Ea[5,_next(r,end),_prev(c-1,end)][χSWC DW240; χSWa] * - env.C[6,r,_prev(c-1,end)][χSWa DW180; χNWb] * env.Eb[6,_prev(r,end),_prev(c,end)][χNWb DW120; χNWC] * env.Ea[6,_prev(r,end),_prev(c,end)][χNWC DNW180; χNWa] +function _contract_site_large((r, c)::Tuple{Int, Int}, network::InfiniteTriangularNetwork{P}, env::CTMRGEnvTriangular) where {P <: PFTensorTriangular} + return @tensor opt = true network[_prev(r, end), c][DNW180 DW60 DNW300; DNW120 DNW60 DNW0] * network[_prev(r, end), _next(c, end)][DNW0 DNE240 DNE300; DNE120 DNE60 DNE0] * + network[r, _next(c, end)][DE180 DE240 DE300; DNE300 DE60 DE0] * network[_next(r, end), c][DSE180 DSE240 DSE300; DSE120 DE240 DSE0] * network[_next(r, end), _prev(c, end)][DSW180 DSW240 DSW300; DSW120 DSW60 DSE180] * + network[r, _prev(c, end)][DW180 DW240 DSW120; DW120 DW60 DW0] * network[r, c][DW0 DSW60 DSE120; DNW300 DNE240 DE180] * + env.C[1, _prev(r - 1, end), c][χNWa DNW120; χNb] * env.Eb[1, _prev(r - 1, end), _next(c, end)][χNb DNW60; χNC] * env.Ea[1, _prev(r - 1, end), _next(c, end)][χNC DNE120; χNa] * + env.C[2, _prev(r - 1, end), _next(c + 1, end)][χNa DNE60; χNEb] * env.Eb[2, _prev(r, end), _next(c + 1, end)][χNEb DNE0; χNEC] * env.Ea[2, _prev(r, end), _next(c + 1, end)][χNEC DE60; χNEa] * + env.C[3, r, _next(c + 1, end)][χNEa DE0; χSEb] * env.Eb[3, _next(r, end), _next(c, end)][χSEb DE300; χSEC] * env.Ea[3, _next(r, end), _next(c, end)][χSEC DSE0; χSEa] * + env.C[4, _next(r + 1, end), c][χSEa DSE300; χSb] * env.Eb[4, _next(r + 1, end), _prev(c, end)][χSb DSE240; χSC] * env.Ea[4, _next(r + 1, end), _prev(c, end)][χSC DSW300; χSa] * + env.C[5, _next(r + 1, end), _prev(c - 1, end)][χSa DSW240; χSWb] * env.Eb[5, _next(r, end), _prev(c - 1, end)][χSWb DSW180; χSWC] * env.Ea[5, _next(r, end), _prev(c - 1, end)][χSWC DW240; χSWa] * + env.C[6, r, _prev(c - 1, end)][χSWa DW180; χNWb] * env.Eb[6, _prev(r, end), _prev(c, end)][χNWb DW120; χNWC] * env.Ea[6, _prev(r, end), _prev(c, end)][χNWC DNW180; χNWa] end -function _contract_corners((r,c)::Tuple{Int,Int}, network::InfiniteTriangularNetwork{P}, env::CTMRGEnvTriangular) where {P <: PFTensorTriangular} - return @tensor opt = true env.C[1,_prev(r,end),c][χNW D120; χN] * env.C[2,_prev(r,end),_next(c,end)][χN D60; χNE] * env.C[3,r,_next(c,end)][χNE D0; χSE] * - env.C[4,_next(r,end),c][χSE D300; χS] * env.C[5,_next(r,end),_prev(c,end)][χS D240; χSW] * env.C[6,r,_prev(c,end)][χSW D180; χNW] * - network[r,c][D180 D240 D300; D120 D60 D0] +function _contract_corners((r, c)::Tuple{Int, Int}, network::InfiniteTriangularNetwork{P}, env::CTMRGEnvTriangular) where {P <: PFTensorTriangular} + return @tensor opt = true env.C[1, _prev(r, end), c][χNW D120; χN] * env.C[2, _prev(r, end), _next(c, end)][χN D60; χNE] * env.C[3, r, _next(c, end)][χNE D0; χSE] * + env.C[4, _next(r, end), c][χSE D300; χS] * env.C[5, _next(r, end), _prev(c, end)][χS D240; χSW] * env.C[6, r, _prev(c, end)][χSW D180; χNW] * + network[r, c][D180 D240 D300; D120 D60 D0] end -### For +### For -function _contract_edges_0((r,c)::Tuple{Int,Int}, network::InfiniteTriangularNetwork{P}, env::CTMRGEnvTriangular) where {P <: PEPSSandwichTriangular} - return @tensor opt = true ket(network[r,c])[dL; DLt120 DLt60 DLt0 DLt300 DLt240 DLt180] * ket(network[r,_next(c,end)])[dR; DRt120 DRt60 DRt0 DRt300 DRt240 DLt0] * - conj(bra(network[r,c])[dL; DLb120 DLb60 DLb0 DLb300 DLb240 DLb180]) * conj(bra(network[r,_next(c,end)])[dR; DRb120 DRb60 DRb0 DRb300 DRb240 DLb0]) * - env.C[1,_prev(r,end),c][χNW DLt120 DLb120; χNa] * env.C[2,_prev(r,end),_next(c+1,end)][χNb DRt60 DRb60; χNE] * env.C[3,r,_next(c+1,end)][χNE DRt0 DRb0; χSE] * - env.C[4,_next(r,end),_next(c,end)][χSE DRt300 DRb300; χSa] * env.C[5,_next(r,end),_prev(c,end)][χSb DLt240 DLb240; χSW] * env.C[6,r,_prev(c,end)][χSW DLt180 DLb180; χNW] * - env.Eb[1,_prev(r,end),_next(c,end)][χNa DLt60 DLb60; χNC] * env.Ea[1,_prev(r,end),_next(c,end)][χNC DRt120 DRb120; χNb] * - env.Eb[4,_next(r,end),c][χSa DRt240 DRb240; χSC] * env.Ea[4,_next(r,end),c][χSC DLt300 DLb300; χSb] +function _contract_edges_0((r, c)::Tuple{Int, Int}, network::InfiniteTriangularNetwork{P}, env::CTMRGEnvTriangular) where {P <: PEPSSandwichTriangular} + return @tensor opt = true ket(network[r, c])[dL; DLt120 DLt60 DLt0 DLt300 DLt240 DLt180] * ket(network[r, _next(c, end)])[dR; DRt120 DRt60 DRt0 DRt300 DRt240 DLt0] * + conj(bra(network[r, c])[dL; DLb120 DLb60 DLb0 DLb300 DLb240 DLb180]) * conj(bra(network[r, _next(c, end)])[dR; DRb120 DRb60 DRb0 DRb300 DRb240 DLb0]) * + env.C[1, _prev(r, end), c][χNW DLt120 DLb120; χNa] * env.C[2, _prev(r, end), _next(c + 1, end)][χNb DRt60 DRb60; χNE] * env.C[3, r, _next(c + 1, end)][χNE DRt0 DRb0; χSE] * + env.C[4, _next(r, end), _next(c, end)][χSE DRt300 DRb300; χSa] * env.C[5, _next(r, end), _prev(c, end)][χSb DLt240 DLb240; χSW] * env.C[6, r, _prev(c, end)][χSW DLt180 DLb180; χNW] * + env.Eb[1, _prev(r, end), _next(c, end)][χNa DLt60 DLb60; χNC] * env.Ea[1, _prev(r, end), _next(c, end)][χNC DRt120 DRb120; χNb] * + env.Eb[4, _next(r, end), c][χSa DRt240 DRb240; χSC] * env.Ea[4, _next(r, end), c][χSC DLt300 DLb300; χSb] end -function _contract_edges_0((r,c)::Tuple{Int,Int}, network::InfiniteTriangularNetwork{P}, env::CTMRGEnvTriangular, op::AbstractTensorMap{E,S,2,2}) where {P <: PEPSSandwichTriangular, E, S} - return @tensor opt = true ket(network[r,c])[dLt; DLt120 DLt60 DLt0 DLt300 DLt240 DLt180] * ket(network[r,_next(c,end)])[dRt; DRt120 DRt60 DRt0 DRt300 DRt240 DLt0] * - conj(bra(network[r,c])[dLb; DLb120 DLb60 DLb0 DLb300 DLb240 DLb180]) * conj(bra(network[r,_next(c,end)])[dRb; DRb120 DRb60 DRb0 DRb300 DRb240 DLb0]) * - env.C[1,_prev(r,end),c][χNW DLt120 DLb120; χNa] * env.C[2,_prev(r,end),_next(c+1,end)][χNb DRt60 DRb60; χNE] * env.C[3,r,_next(c+1,end)][χNE DRt0 DRb0; χSE] * - env.C[4,_next(r,end),_next(c,end)][χSE DRt300 DRb300; χSa] * env.C[5,_next(r,end),_prev(c,end)][χSb DLt240 DLb240; χSW] * env.C[6,r,_prev(c,end)][χSW DLt180 DLb180; χNW] * - env.Eb[1,_prev(r,end),_next(c,end)][χNa DLt60 DLb60; χNC] * env.Ea[1,_prev(r,end),_next(c,end)][χNC DRt120 DRb120; χNb] * - env.Eb[4,_next(r,end),c][χSa DRt240 DRb240; χSC] * env.Ea[4,_next(r,end),c][χSC DLt300 DLb300; χSb] * +function _contract_edges_0((r, c)::Tuple{Int, Int}, network::InfiniteTriangularNetwork{P}, env::CTMRGEnvTriangular, op::AbstractTensorMap{E, S, 2, 2}) where {P <: PEPSSandwichTriangular, E, S} + return @tensor opt = true ket(network[r, c])[dLt; DLt120 DLt60 DLt0 DLt300 DLt240 DLt180] * ket(network[r, _next(c, end)])[dRt; DRt120 DRt60 DRt0 DRt300 DRt240 DLt0] * + conj(bra(network[r, c])[dLb; DLb120 DLb60 DLb0 DLb300 DLb240 DLb180]) * conj(bra(network[r, _next(c, end)])[dRb; DRb120 DRb60 DRb0 DRb300 DRb240 DLb0]) * + env.C[1, _prev(r, end), c][χNW DLt120 DLb120; χNa] * env.C[2, _prev(r, end), _next(c + 1, end)][χNb DRt60 DRb60; χNE] * env.C[3, r, _next(c + 1, end)][χNE DRt0 DRb0; χSE] * + env.C[4, _next(r, end), _next(c, end)][χSE DRt300 DRb300; χSa] * env.C[5, _next(r, end), _prev(c, end)][χSb DLt240 DLb240; χSW] * env.C[6, r, _prev(c, end)][χSW DLt180 DLb180; χNW] * + env.Eb[1, _prev(r, end), _next(c, end)][χNa DLt60 DLb60; χNC] * env.Ea[1, _prev(r, end), _next(c, end)][χNC DRt120 DRb120; χNb] * + env.Eb[4, _next(r, end), c][χSa DRt240 DRb240; χSC] * env.Ea[4, _next(r, end), c][χSC DLt300 DLb300; χSb] * op[dLb dRb; dLt dRt] end -function _contract_edges_60((r,c)::Tuple{Int,Int}, network::InfiniteTriangularNetwork{P}, env::CTMRGEnvTriangular) where {P <: PEPSSandwichTriangular} - return @tensor opt = true ket(network[r,c])[dTR; DTRt120 DTRt60 DTRt0 DTRt300 DBLt60 DTRt180] * ket(network[_next(r,end),_prev(c,end)])[dBL; DBLt120 DBLt60 DBLt0 DBLt300 DBLt240 DBLt180] * - conj(bra(network[r,c])[dTR; DTRb120 DTRb60 DTRb0 DTRb300 DBLb60 DTRb180]) * conj(bra(network[_next(r,end),_prev(c,end)])[dBL; DBLb120 DBLb60 DBLb0 DBLb300 DBLb240 DBLb180]) * - env.C[1,_prev(r,end),c][χNWb DTRt120 DTRb120; χN] * env.C[2,_prev(r,end),_next(c,end)][χN DTRt60 DTRb60; χNE] * env.C[3,r,_next(c,end)][χNE DTRt0 DTRb0; χSEa] * - env.C[4,_next(r+1,end),_prev(c,end)][χSEb DBLt300 DBLb300; χS] * env.C[5,_next(r+1,end),_prev(c-1,end)][χS DBLt240 DBLb240; χSW] * env.C[6,_next(r,end),_prev(c-1,end)][χSW DBLt180 DBLb180; χNWa] * - env.Eb[3,_next(r,end),c][χSEa DTRt300 DTRb300; χSEC] * env.Ea[3,_next(r,end),c][χSEC DBLt0 DBLb0; χSEb] * - env.Eb[6,r,_prev(c,end)][χNWa DBLt120 DBLb120; χNWC] * env.Ea[6,r,_prev(c,end)][χNWC DTRt180 DTRb180; χNWb] +function _contract_edges_60((r, c)::Tuple{Int, Int}, network::InfiniteTriangularNetwork{P}, env::CTMRGEnvTriangular) where {P <: PEPSSandwichTriangular} + return @tensor opt = true ket(network[r, c])[dTR; DTRt120 DTRt60 DTRt0 DTRt300 DBLt60 DTRt180] * ket(network[_next(r, end), _prev(c, end)])[dBL; DBLt120 DBLt60 DBLt0 DBLt300 DBLt240 DBLt180] * + conj(bra(network[r, c])[dTR; DTRb120 DTRb60 DTRb0 DTRb300 DBLb60 DTRb180]) * conj(bra(network[_next(r, end), _prev(c, end)])[dBL; DBLb120 DBLb60 DBLb0 DBLb300 DBLb240 DBLb180]) * + env.C[1, _prev(r, end), c][χNWb DTRt120 DTRb120; χN] * env.C[2, _prev(r, end), _next(c, end)][χN DTRt60 DTRb60; χNE] * env.C[3, r, _next(c, end)][χNE DTRt0 DTRb0; χSEa] * + env.C[4, _next(r + 1, end), _prev(c, end)][χSEb DBLt300 DBLb300; χS] * env.C[5, _next(r + 1, end), _prev(c - 1, end)][χS DBLt240 DBLb240; χSW] * env.C[6, _next(r, end), _prev(c - 1, end)][χSW DBLt180 DBLb180; χNWa] * + env.Eb[3, _next(r, end), c][χSEa DTRt300 DTRb300; χSEC] * env.Ea[3, _next(r, end), c][χSEC DBLt0 DBLb0; χSEb] * + env.Eb[6, r, _prev(c, end)][χNWa DBLt120 DBLb120; χNWC] * env.Ea[6, r, _prev(c, end)][χNWC DTRt180 DTRb180; χNWb] end -function _contract_edges_120((r,c)::Tuple{Int,Int}, network::InfiniteTriangularNetwork{P}, env::CTMRGEnvTriangular) where {P <: PEPSSandwichTriangular} - return @tensor opt = true ket(network[r,c])[dTL; DTLt120 DTLt60 DTLt0 DTLt300 DTLt240 DTLt180] * ket(network[_next(r,end),c])[dBR; DTLt300 DBRt60 DBRt0 DBRt300 DBRt240 DBRt180] * - conj(bra(network[r,c])[dTL; DTLb120 DTLb60 DTLb0 DTLb300 DTLb240 DTLb180]) * conj(bra(network[_next(r,end),c])[dBR; DTLb300 DBRb60 DBRb0 DBRb300 DBRb240 DBRb180]) * - env.C[1,_prev(r,end),c][χNW DTLt120 DTLb120; χN] * env.C[2,_prev(r,end),_next(c,end)][χN DTLt60 DTLb60; χNEa] * env.C[3,_next(r,end),_next(c,end)][χNEb DBRt0 DBRb0; χSE] * - env.C[4,_next(r+1,end),c][χSE DBRt300 DBRb300; χS] * env.C[5,_next(r+1,end),_prev(c,end)][χS DBRt240 DBRb240; χSWa] * env.C[6,r,_prev(c,end)][χSWb DTLt180 DTLb180; χNW] * - env.Eb[2,r,_next(c,end)][χNEa DTLt0 DTLb0; χNEC] * env.Ea[2,r,_next(c,end)][χNEC DBRt60 DBRb60; χNEb] * - env.Eb[5,_next(r,end),_prev(c,end)][χSWa DBRt180 DBRb180; χSWC] * env.Ea[5,_next(r,end),_prev(c,end)][χSWC DTLt240 DTLb240; χSWb] +function _contract_edges_120((r, c)::Tuple{Int, Int}, network::InfiniteTriangularNetwork{P}, env::CTMRGEnvTriangular) where {P <: PEPSSandwichTriangular} + return @tensor opt = true ket(network[r, c])[dTL; DTLt120 DTLt60 DTLt0 DTLt300 DTLt240 DTLt180] * ket(network[_next(r, end), c])[dBR; DTLt300 DBRt60 DBRt0 DBRt300 DBRt240 DBRt180] * + conj(bra(network[r, c])[dTL; DTLb120 DTLb60 DTLb0 DTLb300 DTLb240 DTLb180]) * conj(bra(network[_next(r, end), c])[dBR; DTLb300 DBRb60 DBRb0 DBRb300 DBRb240 DBRb180]) * + env.C[1, _prev(r, end), c][χNW DTLt120 DTLb120; χN] * env.C[2, _prev(r, end), _next(c, end)][χN DTLt60 DTLb60; χNEa] * env.C[3, _next(r, end), _next(c, end)][χNEb DBRt0 DBRb0; χSE] * + env.C[4, _next(r + 1, end), c][χSE DBRt300 DBRb300; χS] * env.C[5, _next(r + 1, end), _prev(c, end)][χS DBRt240 DBRb240; χSWa] * env.C[6, r, _prev(c, end)][χSWb DTLt180 DTLb180; χNW] * + env.Eb[2, r, _next(c, end)][χNEa DTLt0 DTLb0; χNEC] * env.Ea[2, r, _next(c, end)][χNEC DBRt60 DBRb60; χNEb] * + env.Eb[5, _next(r, end), _prev(c, end)][χSWa DBRt180 DBRb180; χSWC] * env.Ea[5, _next(r, end), _prev(c, end)][χSWC DTLt240 DTLb240; χSWb] end -function _contract_site_large((r,c)::Tuple{Int,Int}, network::InfiniteTriangularNetwork{P}, env::CTMRGEnvTriangular) where {P <: PEPSSandwichTriangular} - return @tensor opt = true ket(network[_prev(r,end),c])[dNW; DNWt120 DNWt60 DNWt0 DNWt300 DWt60 DNWt180] * ket(network[_prev(r,end),_next(c,end)])[dNE; DNEt120 DNEt60 DNEt0 DNEt300 DNEt240 DNWt0] * - ket(network[r,_next(c,end)])[dE; DNEt300 DEt60 DEt0 DEt300 DEt240 DEt180] * ket(network[_next(r,end),c])[dSE; DSEt120 DEt240 DSEt0 DSEt300 DSEt240 DSEt180] * ket(network[_next(r,end),_prev(c,end)])[dSW; DSWt120 DSWt60 DSEt180 DSWt300 DSWt240 DSWt180] * - ket(network[r,_prev(c,end)])[dW; DWt120 DWt60 DWt0 DSWt120 DWt240 DWt180] * ket(network[r,c])[dCenter; DNWt300 DNEt240 DEt180 DSEt120 DSWt60 DWt0] * - conj(bra(network[_prev(r,end),c])[dNW; DNWb120 DNWb60 DNWb0 DNWb300 DWb60 DNWb180]) * conj(bra(network[_prev(r,end),_next(c,end)])[dNE; DNEb120 DNEb60 DNEb0 DNEb300 DNEb240 DNWb0]) * - conj(bra(network[r,_next(c,end)])[dE; DNEb300 DEb60 DEb0 DEb300 DEb240 DEb180]) * conj(bra(network[_next(r,end),c])[dSE; DSEb120 DEb240 DSEb0 DSEb300 DSEb240 DSEb180]) * conj(bra(network[_next(r,end),_prev(c,end)])[dSW; DSWb120 DSWb60 DSEb180 DSWb300 DSWb240 DSWb180]) * - conj(bra(network[r,_prev(c,end)])[dW; DWb120 DWb60 DWb0 DSWb120 DWb240 DWb180]) * conj(bra(network[r,c])[dCenter; DNWb300 DNEb240 DEb180 DSEb120 DSWb60 DWb0]) * - env.C[1,_prev(r-1,end),c][χNWa DNWt120 DNWb120; χNb] * env.Eb[1,_prev(r-1,end),_next(c,end)][χNb DNWt60 DNWb60; χNC] * env.Ea[1,_prev(r-1,end),_next(c,end)][χNC DNEt120 DNEb120; χNa] * - env.C[2,_prev(r-1,end),_next(c+1,end)][χNa DNEt60 DNEb60; χNEb] * env.Eb[2,_prev(r,end),_next(c+1,end)][χNEb DNEt0 DNEb0; χNEC] * env.Ea[2,_prev(r,end),_next(c+1,end)][χNEC DEt60 DEb60; χNEa] * - env.C[3,r,_next(c+1,end)][χNEa DEt0 DEb0; χSEb] * env.Eb[3,_next(r,end),_next(c,end)][χSEb DEt300 DEb300; χSEC] * env.Ea[3,_next(r,end),_next(c,end)][χSEC DSEt0 DSEb0; χSEa] * - env.C[4,_next(r+1,end),c][χSEa DSEt300 DSEb300; χSb] * env.Eb[4,_next(r+1,end),_prev(c,end)][χSb DSEt240 DSEb240; χSC] * env.Ea[4,_next(r+1,end),_prev(c,end)][χSC DSWt300 DSWb300; χSa] * - env.C[5,_next(r+1,end),_prev(c-1,end)][χSa DSWt240 DSWb240; χSWb] * env.Eb[5,_next(r,end),_prev(c-1,end)][χSWb DSWt180 DSWb180; χSWC] * env.Ea[5,_next(r,end),_prev(c-1,end)][χSWC DWt240 DWb240; χSWa] * - env.C[6,r,_prev(c-1,end)][χSWa DWt180 DWb180; χNWb] * env.Eb[6,_prev(r,end),_prev(c,end)][χNWb DWt120 DWb120; χNWC] * env.Ea[6,_prev(r,end),_prev(c,end)][χNWC DNWt180 DNWb180; χNWa] +function _contract_site_large((r, c)::Tuple{Int, Int}, network::InfiniteTriangularNetwork{P}, env::CTMRGEnvTriangular) where {P <: PEPSSandwichTriangular} + return @tensor opt = true ket(network[_prev(r, end), c])[dNW; DNWt120 DNWt60 DNWt0 DNWt300 DWt60 DNWt180] * ket(network[_prev(r, end), _next(c, end)])[dNE; DNEt120 DNEt60 DNEt0 DNEt300 DNEt240 DNWt0] * + ket(network[r, _next(c, end)])[dE; DNEt300 DEt60 DEt0 DEt300 DEt240 DEt180] * ket(network[_next(r, end), c])[dSE; DSEt120 DEt240 DSEt0 DSEt300 DSEt240 DSEt180] * ket(network[_next(r, end), _prev(c, end)])[dSW; DSWt120 DSWt60 DSEt180 DSWt300 DSWt240 DSWt180] * + ket(network[r, _prev(c, end)])[dW; DWt120 DWt60 DWt0 DSWt120 DWt240 DWt180] * ket(network[r, c])[dCenter; DNWt300 DNEt240 DEt180 DSEt120 DSWt60 DWt0] * + conj(bra(network[_prev(r, end), c])[dNW; DNWb120 DNWb60 DNWb0 DNWb300 DWb60 DNWb180]) * conj(bra(network[_prev(r, end), _next(c, end)])[dNE; DNEb120 DNEb60 DNEb0 DNEb300 DNEb240 DNWb0]) * + conj(bra(network[r, _next(c, end)])[dE; DNEb300 DEb60 DEb0 DEb300 DEb240 DEb180]) * conj(bra(network[_next(r, end), c])[dSE; DSEb120 DEb240 DSEb0 DSEb300 DSEb240 DSEb180]) * conj(bra(network[_next(r, end), _prev(c, end)])[dSW; DSWb120 DSWb60 DSEb180 DSWb300 DSWb240 DSWb180]) * + conj(bra(network[r, _prev(c, end)])[dW; DWb120 DWb60 DWb0 DSWb120 DWb240 DWb180]) * conj(bra(network[r, c])[dCenter; DNWb300 DNEb240 DEb180 DSEb120 DSWb60 DWb0]) * + env.C[1, _prev(r - 1, end), c][χNWa DNWt120 DNWb120; χNb] * env.Eb[1, _prev(r - 1, end), _next(c, end)][χNb DNWt60 DNWb60; χNC] * env.Ea[1, _prev(r - 1, end), _next(c, end)][χNC DNEt120 DNEb120; χNa] * + env.C[2, _prev(r - 1, end), _next(c + 1, end)][χNa DNEt60 DNEb60; χNEb] * env.Eb[2, _prev(r, end), _next(c + 1, end)][χNEb DNEt0 DNEb0; χNEC] * env.Ea[2, _prev(r, end), _next(c + 1, end)][χNEC DEt60 DEb60; χNEa] * + env.C[3, r, _next(c + 1, end)][χNEa DEt0 DEb0; χSEb] * env.Eb[3, _next(r, end), _next(c, end)][χSEb DEt300 DEb300; χSEC] * env.Ea[3, _next(r, end), _next(c, end)][χSEC DSEt0 DSEb0; χSEa] * + env.C[4, _next(r + 1, end), c][χSEa DSEt300 DSEb300; χSb] * env.Eb[4, _next(r + 1, end), _prev(c, end)][χSb DSEt240 DSEb240; χSC] * env.Ea[4, _next(r + 1, end), _prev(c, end)][χSC DSWt300 DSWb300; χSa] * + env.C[5, _next(r + 1, end), _prev(c - 1, end)][χSa DSWt240 DSWb240; χSWb] * env.Eb[5, _next(r, end), _prev(c - 1, end)][χSWb DSWt180 DSWb180; χSWC] * env.Ea[5, _next(r, end), _prev(c - 1, end)][χSWC DWt240 DWb240; χSWa] * + env.C[6, r, _prev(c - 1, end)][χSWa DWt180 DWb180; χNWb] * env.Eb[6, _prev(r, end), _prev(c, end)][χNWb DWt120 DWb120; χNWC] * env.Ea[6, _prev(r, end), _prev(c, end)][χNWC DNWt180 DNWb180; χNWa] end -function _contract_corners((r,c)::Tuple{Int,Int}, network::InfiniteTriangularNetwork{P}, env::CTMRGEnvTriangular) where {P <: PEPSSandwichTriangular} - return @tensor opt = true env.C[1,_prev(r,end),c][χNW Dt120 Db120; χN] * env.C[2,_prev(r,end),_next(c,end)][χN Dt60 Db60; χNE] * env.C[3,r,_next(c,end)][χNE Dt0 Db0; χSE] * - env.C[4,_next(r,end),c][χSE Dt300 Db300; χS] * env.C[5,_next(r,end),_prev(c,end)][χS Dt240 Db240; χSW] * env.C[6,r,_prev(c,end)][χSW Dt180 Db180; χNW] * - ket(network[r,c])[d; Dt120 Dt60 Dt0 Dt300 Dt240 Dt180] * conj(bra(network[r,c])[d; Db120 Db60 Db0 Db300 Db240 Db180]) +function _contract_corners((r, c)::Tuple{Int, Int}, network::InfiniteTriangularNetwork{P}, env::CTMRGEnvTriangular) where {P <: PEPSSandwichTriangular} + return @tensor opt = true env.C[1, _prev(r, end), c][χNW Dt120 Db120; χN] * env.C[2, _prev(r, end), _next(c, end)][χN Dt60 Db60; χNE] * env.C[3, r, _next(c, end)][χNE Dt0 Db0; χSE] * + env.C[4, _next(r, end), c][χSE Dt300 Db300; χS] * env.C[5, _next(r, end), _prev(c, end)][χS Dt240 Db240; χSW] * env.C[6, r, _prev(c, end)][χSW Dt180 Db180; χNW] * + ket(network[r, c])[d; Dt120 Dt60 Dt0 Dt300 Dt240 Dt180] * conj(bra(network[r, c])[d; Db120 Db60 Db0 Db300 Db240 Db180]) end -function _contract_corners((r,c)::Tuple{Int,Int}, network::InfiniteTriangularNetwork{P}, env::CTMRGEnvTriangular, op::AbstractTensorMap{E,S,1,1}) where {P <: PEPSSandwichTriangular, E, S} - return @tensor opt = true env.C[1,_prev(r,end),c][χNW Dt120 Db120; χN] * env.C[2,_prev(r,end),_next(c,end)][χN Dt60 Db60; χNE] * env.C[3,r,_next(c,end)][χNE Dt0 Db0; χSE] * - env.C[4,_next(r,end),c][χSE Dt300 Db300; χS] * env.C[5,_next(r,end),_prev(c,end)][χS Dt240 Db240; χSW] * env.C[6,r,_prev(c,end)][χSW Dt180 Db180; χNW] * - ket(network[r,c])[dt; Dt120 Dt60 Dt0 Dt300 Dt240 Dt180] * conj(bra(network[r,c])[db; Db120 Db60 Db0 Db300 Db240 Db180]) * op[db; dt] +function _contract_corners((r, c)::Tuple{Int, Int}, network::InfiniteTriangularNetwork{P}, env::CTMRGEnvTriangular, op::AbstractTensorMap{E, S, 1, 1}) where {P <: PEPSSandwichTriangular, E, S} + return @tensor opt = true env.C[1, _prev(r, end), c][χNW Dt120 Db120; χN] * env.C[2, _prev(r, end), _next(c, end)][χN Dt60 Db60; χNE] * env.C[3, r, _next(c, end)][χNE Dt0 Db0; χSE] * + env.C[4, _next(r, end), c][χSE Dt300 Db300; χS] * env.C[5, _next(r, end), _prev(c, end)][χS Dt240 Db240; χSW] * env.C[6, r, _prev(c, end)][χSW Dt180 Db180; χNW] * + ket(network[r, c])[dt; Dt120 Dt60 Dt0 Dt300 Dt240 Dt180] * conj(bra(network[r, c])[db; Db120 Db60 Db0 Db300 Db240 Db180]) * op[db; dt] end function energy(network::InfiniteTriangularNetwork{P}, env::CTMRGEnvTriangular, onesite_op, twosite_op) where {P <: PEPSSandwichTriangular} return sum(Iterators.product(axes(network)...)) do (r, c) - expval_onesite = _contract_corners((r,c), network, env, onesite_op) / _contract_corners((r,c), network, env) - expval_twosite = _contract_edges_0((r,c), network, env, twosite_op) / _contract_edges_0((r,c), network, env) + expval_onesite = _contract_corners((r, c), network, env, onesite_op) / _contract_corners((r, c), network, env) + expval_twosite = _contract_edges_0((r, c), network, env, twosite_op) / _contract_edges_0((r, c), network, env) return expval_onesite + expval_twosite end end diff --git a/test/ctmrg/triangular.jl b/test/ctmrg/triangular.jl index c71647d42..d55c3ce48 100644 --- a/test/ctmrg/triangular.jl +++ b/test/ctmrg/triangular.jl @@ -48,7 +48,7 @@ end unitcell = (2, 2) for (sandwich, vspace) in zip(sandwiches, [vspace, vspace ⊗ vspace']) - # for (sandwich, vspace) in zip(sandwiches, [vspace ⊗ vspace', vspace]) + # for (sandwich, vspace) in zip(sandwiches, [vspace ⊗ vspace', vspace]) network = InfiniteTriangularNetwork(fill(sandwich, unitcell)) env₀ = CTMRGEnvTriangular(randn, eltype, vspace, envspace; unitcell) env, info = leading_boundary(env₀, network, alg) @@ -97,7 +97,7 @@ end # ZZ = rmul!(PEPSKit.σᶻᶻ(T, S), -J) # X = rmul!(PEPSKit.σˣ(T, S), g * -J) - + # vspace = ℂ^3 # envspace = ℂ^χ # ket = randn(T, pspace, vspace ⊗ vspace ⊗ vspace ⊗ vspace' ⊗ vspace' ⊗ vspace') From 21899c2152db92a38f01840313566c796d44ab6c Mon Sep 17 00:00:00 2001 From: sanderdemeyer Date: Tue, 24 Feb 2026 14:04:21 +0100 Subject: [PATCH 06/41] add triangular test --- test/ctmrg/triangular.jl | 226 +++++++++++++++++++-------------------- 1 file changed, 113 insertions(+), 113 deletions(-) diff --git a/test/ctmrg/triangular.jl b/test/ctmrg/triangular.jl index d55c3ce48..2e1f2ffcd 100644 --- a/test/ctmrg/triangular.jl +++ b/test/ctmrg/triangular.jl @@ -29,129 +29,129 @@ function classical_ising_triangular(β) return TensorMap(o2, ℂ^2 * ℂ^2 * ℂ^2, ℂ^2 * ℂ^2 * ℂ^2) end -@testset "CTM_triangular - Random tensor" begin - for conditioning in [true false] - for projector_alg in [:twothirds :full] - Random.seed!(79413165445) - alg = SimultaneousCTMRGTriangular(; maxiter = 300, conditioning, projector_alg) - eltype = ComplexF64 - χ = 7 - pspace = ℂ^2 - vspace = ℂ^3 - envspace = ℂ^χ - - ket = randn(eltype, pspace, vspace ⊗ vspace ⊗ vspace ⊗ vspace' ⊗ vspace' ⊗ vspace') - bra = copy(ket) - pf = randn(eltype, vspace ⊗ vspace ⊗ vspace, vspace ⊗ vspace ⊗ vspace) - sandwiches = [pf, (ket, bra)] - # sandwiches = [(ket, bra), pf] - unitcell = (2, 2) - - for (sandwich, vspace) in zip(sandwiches, [vspace, vspace ⊗ vspace']) - # for (sandwich, vspace) in zip(sandwiches, [vspace ⊗ vspace', vspace]) - network = InfiniteTriangularNetwork(fill(sandwich, unitcell)) - env₀ = CTMRGEnvTriangular(randn, eltype, vspace, envspace; unitcell) - env, info = leading_boundary(env₀, network, alg) - @test info.convergence_metric < 1.0 - end - end - end -end - -@testset "CTM_triangular - Classical Ising" begin - for conditioning in [true false] - for projector_alg in [:twothirds :full] - Random.seed!(156484561351) - - alg = SimultaneousCTMRGTriangular(; maxiter = 300, conditioning, projector_alg) - unitcell = (1, 1) - T = classical_ising_triangular(ising_βc_triangular) - pf = InfiniteTriangularNetwork(fill(T, unitcell)) - - χ = 20 - vspace = codomain(T)[1] - envspace = ℂ^χ - eltype = Float64 - env₀ = CTMRGEnvTriangular(randn, eltype, vspace, envspace; unitcell) - env, info = leading_boundary(env₀, pf, alg) - - nw_value = network_value(pf, env) - lz = real(log(nw_value)) - fs = lz * -1 / ising_βc_triangular - @test fs ≈ f_onsager_triangular rtol = 1.0e-4 - end - end -end - -# @testset "CTM_triangular - Differentiability" begin +# @testset "CTM_triangular - Random tensor" begin # for conditioning in [true false] # for projector_alg in [:twothirds :full] -# ctm_alg = SimultaneousCTMRGTriangular(; conditioning, projector_alg) -# T = ComplexF64 -# S = Trivial +# Random.seed!(79413165445) +# alg = SimultaneousCTMRGTriangular(; maxiter = 300, conditioning, projector_alg) +# eltype = ComplexF64 # χ = 7 # pspace = ℂ^2 - -# J = 1.0 -# g = 2.5 - -# ZZ = rmul!(PEPSKit.σᶻᶻ(T, S), -J) -# X = rmul!(PEPSKit.σˣ(T, S), g * -J) - # vspace = ℂ^3 # envspace = ℂ^χ -# ket = randn(T, pspace, vspace ⊗ vspace ⊗ vspace ⊗ vspace' ⊗ vspace' ⊗ vspace') -# bra = copy(ket) -# unitcell = (1, 1) -# network = InfiniteTriangularNetwork(fill((ket, bra), unitcell)) -# env₀ = CTMRGEnvTriangular(randn, T, vspace ⊗ vspace', envspace; unitcell) - -# env, info = leading_boundary(env₀, network, ctm_alg) -# E₀ = PEPSKit.energy(network, env, X, ZZ) -# optimizer_alg = LBFGS(4; maxiter = 10) -# real_inner(_, η₁, η₂) = real(dot(η₁, η₂)) -# reuse_env = true -# peps₀ = InfinitePEPSTriangular(copy(ket)) +# ket = randn(eltype, pspace, vspace ⊗ vspace ⊗ vspace ⊗ vspace' ⊗ vspace' ⊗ vspace') +# bra = copy(ket) +# pf = randn(eltype, vspace ⊗ vspace ⊗ vspace, vspace ⊗ vspace ⊗ vspace) +# sandwiches = [pf, (ket, bra)] +# # sandwiches = [(ket, bra), pf] +# unitcell = (2, 2) + +# for (sandwich, vspace) in zip(sandwiches, [vspace, vspace ⊗ vspace']) +# # for (sandwich, vspace) in zip(sandwiches, [vspace ⊗ vspace', vspace]) +# network = InfiniteTriangularNetwork(fill(sandwich, unitcell)) +# env₀ = CTMRGEnvTriangular(randn, eltype, vspace, envspace; unitcell) +# env, info = leading_boundary(env₀, network, alg) +# @test info.convergence_metric < 1.0 +# end +# end +# end +# end -# function peps_retract(x, η, α) -# peps = x[1] -# env = deepcopy(x[2]) +# @testset "CTM_triangular - Classical Ising" begin +# for conditioning in [true false] +# for projector_alg in [:twothirds :full] +# Random.seed!(156484561351) -# retractions = norm_preserving_retract.(unitcell(peps), unitcell(η), α) -# peps´ = InfinitePEPS(map(first, retractions)) -# ξ = InfinitePEPS(map(last, retractions)) +# alg = SimultaneousCTMRGTriangular(; maxiter = 300, conditioning, projector_alg) +# unitcell = (1, 1) +# T = classical_ising_triangular(ising_βc_triangular) +# pf = InfiniteTriangularNetwork(fill(T, unitcell)) -# return (peps´, env), ξ -# end -# retract = peps_retract - -# # alg = PEPSOptimize() -# gradtol = 1e-3 -# gradient_alg = EigSolver(; solver_alg=Arnoldi(; tol=gradtol, eager=true), iterscheme=:fixed) -# # optimize operator cost function -# (peps_final, env_final), cost_final, ∂cost, numfg, convergence_history = optimize( -# (peps₀, env₀), optimizer_alg; -# retract, inner = real_inner, -# ) do (peps, env) -# start_time = time_ns() -# E, gs = withgradient(peps) do ψ -# env′, info = PEPSKit.hook_pullback( -# leading_boundary, env, ψ, ctm_alg; -# alg_rrule = gradient_alg, -# ) -# # ignore_derivatives() do -# # reuse_env && update!(env, env′) -# # push!(truncation_errors, info.truncation_error) -# # push!(condition_numbers, info.condition_number) -# # end -# return energy(ψ, env′, operator_onesite, operator_twosite) -# end -# g = only(gs) # `withgradient` returns tuple of gradients `gs` -# push!(gradnorms_unitcell, norm.(g.A)) -# push!(times, (time_ns() - start_time) * 1.0e-9) -# return E, g -# end +# χ = 20 +# vspace = codomain(T)[1] +# envspace = ℂ^χ +# eltype = Float64 +# env₀ = CTMRGEnvTriangular(randn, eltype, vspace, envspace; unitcell) +# env, info = leading_boundary(env₀, pf, alg) + +# nw_value = network_value(pf, env) +# lz = real(log(nw_value)) +# fs = lz * -1 / ising_βc_triangular +# @test fs ≈ f_onsager_triangular rtol = 1.0e-4 # end # end # end + +@testset "CTM_triangular - Differentiability" begin + for conditioning in [true false] + for projector_alg in [:twothirds :full] + ctm_alg = SimultaneousCTMRGTriangular(; conditioning, projector_alg) + T = ComplexF64 + S = Trivial + χ = 7 + pspace = ℂ^2 + + J = 1.0 + g = 2.5 + + ZZ = rmul!(PEPSKit.σᶻᶻ(T, S), -J) + X = rmul!(PEPSKit.σˣ(T, S), g * -J) + + vspace = ℂ^3 + envspace = ℂ^χ + ket = randn(T, pspace, vspace ⊗ vspace ⊗ vspace ⊗ vspace' ⊗ vspace' ⊗ vspace') + bra = copy(ket) + + unitcell = (1, 1) + network = InfiniteTriangularNetwork(fill((ket, bra), unitcell)) + env₀ = CTMRGEnvTriangular(randn, T, vspace ⊗ vspace', envspace; unitcell) + + env, info = leading_boundary(env₀, network, ctm_alg) + E₀ = PEPSKit.energy(network, env, X, ZZ) + optimizer_alg = LBFGS(4; maxiter = 10) + real_inner(_, η₁, η₂) = real(dot(η₁, η₂)) + reuse_env = true + peps₀ = InfinitePEPSTriangular(copy(ket)) + + function peps_retract(x, η, α) + peps = x[1] + env = deepcopy(x[2]) + + retractions = norm_preserving_retract.(unitcell(peps), unitcell(η), α) + peps´ = InfinitePEPS(map(first, retractions)) + ξ = InfinitePEPS(map(last, retractions)) + + return (peps´, env), ξ + end + retract = peps_retract + + # alg = PEPSOptimize() + gradtol = 1.0e-3 + gradient_alg = EigSolver(; solver_alg = Arnoldi(; tol = gradtol, eager = true), iterscheme = :fixed) + # optimize operator cost function + (peps_final, env_final), cost_final, ∂cost, numfg, convergence_history = optimize( + (peps₀, env₀), optimizer_alg; + retract, inner = real_inner, + ) do (peps, env) + start_time = time_ns() + E, gs = withgradient(peps) do ψ + env′, info = PEPSKit.hook_pullback( + leading_boundary, env, ψ, ctm_alg; + alg_rrule = gradient_alg, + ) + # ignore_derivatives() do + # reuse_env && update!(env, env′) + # push!(truncation_errors, info.truncation_error) + # push!(condition_numbers, info.condition_number) + # end + return energy(ψ, env′, operator_onesite, operator_twosite) + end + g = only(gs) # `withgradient` returns tuple of gradients `gs` + push!(gradnorms_unitcell, norm.(g.A)) + push!(times, (time_ns() - start_time) * 1.0e-9) + return E, g + end + end + end +end From 71b333652c68293bb6fdcdca6dbca977dec31412 Mon Sep 17 00:00:00 2001 From: leburgel Date: Mon, 2 Mar 2026 13:50:56 +0100 Subject: [PATCH 07/41] Patches for differentiability --- src/PEPSKit.jl | 4 + .../ctmrg_contractions_triangular.jl | 94 ++++-- .../ctmrg_triangular/simultaneous.jl | 139 +++++---- .../fixed_point_differentiation.jl | 38 +-- src/algorithms/select_algorithm.jl | 7 - src/algorithms/toolbox_triangular.jl | 61 ++-- .../ctmrg_environments_triangular.jl | 128 ++++++--- src/networks/infinitetriangularnetwork.jl | 18 -- src/states/infinitepepstriangular.jl | 43 +-- test/ctmrg/triangular.jl | 269 ++++++++++-------- 10 files changed, 436 insertions(+), 365 deletions(-) diff --git a/src/PEPSKit.jl b/src/PEPSKit.jl index 0e222aec7..0bdf99147 100644 --- a/src/PEPSKit.jl +++ b/src/PEPSKit.jl @@ -144,6 +144,10 @@ include("algorithms/ctmrg/gaugefix.jl") include("algorithms/ctmrg/c4v.jl") include("algorithms/ctmrg/initialization.jl") +include("algorithms/truncation/truncationschemes.jl") +include("algorithms/truncation/fullenv_truncation.jl") +include("algorithms/truncation/bond_truncation.jl") + include("algorithms/contractions/ctmrg_contractions_triangular.jl") include("algorithms/ctmrg_triangular/ctmrg.jl") include("algorithms/ctmrg_triangular/utility.jl") diff --git a/src/algorithms/contractions/ctmrg_contractions_triangular.jl b/src/algorithms/contractions/ctmrg_contractions_triangular.jl index 1ef921c8b..42b777923 100644 --- a/src/algorithms/contractions/ctmrg_contractions_triangular.jl +++ b/src/algorithms/contractions/ctmrg_contractions_triangular.jl @@ -1,58 +1,104 @@ function build_double_corner_matrix_triangular(network::InfiniteTriangularNetwork{P}, env::CTMRGEnvTriangular, dir::Int, r::Int, c::Int) where {P <: PFTensorTriangular} - @tensor opt = true mat[-1 -2; -3 -4] := env.C[_coordinates(dir, 0, r, c, size(network))...][6 5; 1] * env.C[_coordinates(dir + 1, 0, r, c, size(network))...][1 3; 2] * - env.Ea[_coordinates(dir - 1, 0, r, c, size(network))...][-1 7; 6] * env.Eb[_coordinates(dir + 1, 0, r, c, size(network))...][2 4; -3] * rotl60(network[r, c], mod(dir - 1, 6))[7 -2 -4; 5 3 4] + @tensor opt = true mat[-1 -2; -3 -4] := + env.C[_coordinates(dir, 0, r, c, size(network))...][6 5; 1] * + env.C[_coordinates(dir + 1, 0, r, c, size(network))...][1 3; 2] * + env.Ea[_coordinates(dir - 1, 0, r, c, size(network))...][-1 7; 6] * + env.Eb[_coordinates(dir + 1, 0, r, c, size(network))...][2 4; -3] * + rotl60(network[r, c], mod(dir - 1, 6))[7 -2 -4; 5 3 4] return mat end function build_double_corner_matrix_triangular(network::InfiniteTriangularNetwork{P}, env::CTMRGEnvTriangular, dir::Int, r::Int, c::Int) where {P <: PEPSSandwichTriangular} - @tensor opt = true mat[-1 -2 -3; -4 -5 -6] := env.C[_coordinates(dir, 0, r, c, size(network))...][6 5 8; 1] * env.C[_coordinates(dir + 1, 0, r, c, size(network))...][1 3 9; 2] * - env.Ea[_coordinates(dir - 1, 0, r, c, size(network))...][-1 7 10; 6] * env.Eb[_coordinates(dir + 1, 0, r, c, size(network))...][2 4 11; -4] * - rotl60(ket(network[r, c]), mod(dir - 1, 6))[12; 5 3 4 -5 -2 7] * conj(rotl60(bra(network[r, c]), mod(dir - 1, 6))[12; 8 9 11 -6 -3 10]) + O_r_c = network[r, c] + @tensor opt = true mat[-1 -2 -3; -4 -5 -6] := + env.C[_coordinates(dir, 0, r, c, size(network))...][6 5 8; 1] * + env.C[_coordinates(dir + 1, 0, r, c, size(network))...][1 3 9; 2] * + env.Ea[_coordinates(dir - 1, 0, r, c, size(network))...][-1 7 10; 6] * + env.Eb[_coordinates(dir + 1, 0, r, c, size(network))...][2 4 11; -4] * + rotl60(ket(O_r_c), mod(dir - 1, 6))[12; 5 3 4 -5 -2 7] * + conj(rotl60(bra(O_r_c), mod(dir - 1, 6))[12; 8 9 11 -6 -3 10]) return mat end function semi_renormalize_edge(network::InfiniteTriangularNetwork{P}, env::CTMRGEnvTriangular, Pas, Pbs, dir, r, c) where {P <: PEPSSandwichTriangular} - @tensor opt = true mat[-1 -2 -3; -4 -5 -6] := Pas[dir, r, c][-1; 1 2 8] * Pbs[dir, r, c][6 7 9; -4] * - env.Ea[_coordinates(dir, 0, r, c, size(network))...][1 3 10; 4] * env.Eb[_coordinates(dir, 1, r, c, size(network))...][4 5 11; 6] * - rotl60(ket(network[r, c]), mod(dir - 1, 6))[12; 3 5 7 -5 -2 2] * conj(rotl60(bra(network[r, c]), mod(dir - 1, 6))[12; 10 11 9 -6 -3 8]) + O_r_c = network[r, c] + @tensor opt = true mat[-1 -2 -3; -4 -5 -6] := + Pas[dir, r, c][-1; 1 2 8] * Pbs[dir, r, c][6 7 9; -4] * + env.Ea[_coordinates(dir, 0, r, c, size(network))...][1 3 10; 4] * + env.Eb[_coordinates(dir, 1, r, c, size(network))...][4 5 11; 6] * + rotl60(ket(O_r_c), mod(dir - 1, 6))[12; 3 5 7 -5 -2 2] * + conj(rotl60(bra(O_r_c), mod(dir - 1, 6))[12; 10 11 9 -6 -3 8]) return mat / norm(mat) end function semi_renormalize_edge(network::InfiniteTriangularNetwork{P}, env::CTMRGEnvTriangular, Pas, Pbs, dir, r, c) where {P <: PFTensorTriangular} - @tensor opt = true mat[-1 -2; -3 -4] := Pas[dir, r, c][-1; 1 2] * Pbs[dir, r, c][6 7; -3] * - env.Ea[_coordinates(dir, 0, r, c, size(network))...][1 3; 4] * env.Eb[_coordinates(dir, 1, r, c, size(network))...][4 5; 6] * rotl60(network[r, c], mod(dir - 1, 6))[2 -2 -4; 3 5 7] + @tensor opt = true mat[-1 -2; -3 -4] := + Pas[dir, r, c][-1; 1 2] * + Pbs[dir, r, c][6 7; -3] * + env.Ea[_coordinates(dir, 0, r, c, size(network))...][1 3; 4] * + env.Eb[_coordinates(dir, 1, r, c, size(network))...][4 5; 6] * + rotl60(network[r, c], mod(dir - 1, 6))[2 -2 -4; 3 5 7] return mat / norm(mat) end function build_halfinfinite_projectors(network::InfiniteTriangularNetwork{P}, env::CTMRGEnvTriangular, Ẽas, Ẽbs, Ẽastr, Ẽbstr, dir, r, c) where {P <: PEPSSandwichTriangular} - @tensor opt = true σL[-1 -2 -3; -4] := env.C[_coordinates(dir, 0, r, c, size(network))...][1 2 10; 8] * env.C[_coordinates(dir - 1, 0, r, c, size(network))...][4 3 11; 1] * env.C[_coordinates(dir - 2, 0, r, c, size(network))...][6 5 12; 4] * - Ẽbs[_coordinates(dir, 1, r, c, size(network))...][8 9 13; -4] * Ẽastr[_coordinates(dir - 3, 0, r, c, size(network))...][-1 7 14; 6] * - rotl60(ket(network[r, c]), mod(dir - 1, 6))[15 2 9 -2 7 5 3] * conj(rotl60(bra(network[r, c]), mod(dir - 1, 6))[15; 10 13 -3 14 12 11]) - @tensor opt = true σR[-1; -2 -3 -4] := env.C[_coordinates(dir + 1, 0, r, c, size(network))...][8 2 10; 1] * env.C[_coordinates(dir + 2, 0, r, c, size(network))...][1 3 11; 4] * env.C[_coordinates(dir + 3, 0, r, c, size(network))...][4 5 12; 6] * - Ẽbstr[_coordinates(dir + 3, -1, r, c, size(network))...][6 7 13; -2] * Ẽas[_coordinates(dir, 0, r, c, size(network))...][-1 9 14; 8] * - rotl60(ket(network[_coordinates(dir + 2, 0, r, c, size(network))[2:3]...]), mod(dir - 1, 6))[15; 9 2 3 5 7 -3] * conj(rotl60(bra(network[_coordinates(dir + 2, 0, r, c, size(network))[2:3]...]), mod(dir - 1, 6))[15; 14 10 11 12 13 -4]) + O_r_c = network[r, c] + O_rp2_c = network[_coordinates(dir + 2, 0, r, c, size(network))[2:3]...] + @tensor opt = true σL[-1 -2 -3; -4] := + env.C[_coordinates(dir, 0, r, c, size(network))...][1 2 10; 8] * + env.C[_coordinates(dir - 1, 0, r, c, size(network))...][4 3 11; 1] * + env.C[_coordinates(dir - 2, 0, r, c, size(network))...][6 5 12; 4] * + Ẽbs[_coordinates(dir, 1, r, c, size(network))...][8 9 13; -4] * + Ẽastr[_coordinates(dir - 3, 0, r, c, size(network))...][-1 7 14; 6] * + rotl60(ket(O_r_c), mod(dir - 1, 6))[15 2 9 -2 7 5 3] * + conj(rotl60(bra(O_r_c), mod(dir - 1, 6))[15; 10 13 -3 14 12 11]) + @tensor opt = true σR[-1; -2 -3 -4] := + env.C[_coordinates(dir + 1, 0, r, c, size(network))...][8 2 10; 1] * + env.C[_coordinates(dir + 2, 0, r, c, size(network))...][1 3 11; 4] * + env.C[_coordinates(dir + 3, 0, r, c, size(network))...][4 5 12; 6] * + Ẽbstr[_coordinates(dir + 3, -1, r, c, size(network))...][6 7 13; -2] * + Ẽas[_coordinates(dir, 0, r, c, size(network))...][-1 9 14; 8] * + rotl60(ket(O_rp2_c), mod(dir - 1, 6))[15; 9 2 3 5 7 -3] * + conj(rotl60(bra(O_rp2_c), mod(dir - 1, 6))[15; 14 10 11 12 13 -4]) return σL, σR end function build_halfinfinite_projectors(network::InfiniteTriangularNetwork{P}, env::CTMRGEnvTriangular, Ẽas, Ẽbs, Ẽastr, Ẽbstr, dir, r, c) where {P <: PFTensorTriangular} - @tensor opt = true σL[-1 -2; -3] := env.C[_coordinates(dir, 0, r, c, size(network))...][1 2; 8] * env.C[_coordinates(dir - 1, 0, r, c, size(network))...][4 3; 1] * env.C[_coordinates(dir - 2, 0, r, c, size(network))...][6 5; 4] * - Ẽbs[_coordinates(dir, 1, r, c, size(network))...][8 9; -3] * Ẽastr[_coordinates(dir - 3, 0, r, c, size(network))...][-1 7; 6] * + @tensor opt = true σL[-1 -2; -3] := + env.C[_coordinates(dir, 0, r, c, size(network))...][1 2; 8] * + env.C[_coordinates(dir - 1, 0, r, c, size(network))...][4 3; 1] * + env.C[_coordinates(dir - 2, 0, r, c, size(network))...][6 5; 4] * + Ẽbs[_coordinates(dir, 1, r, c, size(network))...][8 9; -3] * + Ẽastr[_coordinates(dir - 3, 0, r, c, size(network))...][-1 7; 6] * rotl60(network[r, c], mod(dir - 1, 6))[3 5 7; 2 9 -2] - @tensor opt = true σR[-1; -2 -3] := env.C[_coordinates(dir + 1, 0, r, c, size(network))...][8 2; 1] * env.C[_coordinates(dir + 2, 0, r, c, size(network))...][1 3; 4] * env.C[_coordinates(dir + 3, 0, r, c, size(network))...][4 5; 6] * - Ẽbstr[_coordinates(dir + 3, -1, r, c, size(network))...][6 7; -2] * Ẽas[_coordinates(dir, 0, r, c, size(network))...][-1 9; 8] * + @tensor opt = true σR[-1; -2 -3] := + env.C[_coordinates(dir + 1, 0, r, c, size(network))...][8 2; 1] * + env.C[_coordinates(dir + 2, 0, r, c, size(network))...][1 3; 4] * + env.C[_coordinates(dir + 3, 0, r, c, size(network))...][4 5; 6] * + Ẽbstr[_coordinates(dir + 3, -1, r, c, size(network))...][6 7; -2] * + Ẽas[_coordinates(dir, 0, r, c, size(network))...][-1 9; 8] * rotl60(network[_coordinates(dir + 2, 0, r, c, size(network))[2:3]...], mod(dir - 1, 6))[-3 7 5; 9 2 3] return σL, σR end function renormalize_corner_triangular((dir, r, c), network::InfiniteTriangularNetwork{P}, env, Pas, Pbs) where {P <: PFTensorTriangular} - @tensor opt = true env_C_new[-1 -2; -3] := env.C[_coordinates(dir, 0, r, c, size(network))...][1 3; 6] * env.Ea[_coordinates(dir - 1, 0, r, c, size(network))...][4 2; 1] * env.Eb[_coordinates(dir, 1, r, c, size(network))...][6 7; 8] * - rotl60(network[r, c], mod(dir - 1, 6))[2 5 -2; 3 7 9] * Pas[mod1(dir - 1, 6), r, c][-1; 4 5] * Pbs[dir, r, c][8 9; -3] + @tensor opt = true env_C_new[-1 -2; -3] := + env.C[_coordinates(dir, 0, r, c, size(network))...][1 3; 6] * + env.Ea[_coordinates(dir - 1, 0, r, c, size(network))...][4 2; 1] * + env.Eb[_coordinates(dir, 1, r, c, size(network))...][6 7; 8] * + rotl60(network[r, c], mod(dir - 1, 6))[2 5 -2; 3 7 9] * + Pas[mod1(dir - 1, 6), r, c][-1; 4 5] * Pbs[dir, r, c][8 9; -3] return env_C_new end function renormalize_corner_triangular((dir, r, c), network::InfiniteTriangularNetwork{P}, env, Pas, Pbs) where {P <: PEPSSandwichTriangular} - @tensor opt = true env_C_new[-1 -2 -3; -4] := env.C[_coordinates(dir, 0, r, c, size(network))...][1 3 10; 6] * env.Ea[_coordinates(dir - 1, 0, r, c, size(network))...][4 2 11; 1] * env.Eb[_coordinates(dir, 1, r, c, size(network))...][6 7 12; 8] * - rotl60(ket(network[r, c]), mod(dir - 1, 6))[15; 3 7 9 -2 5 2] * conj(rotl60(bra(network[r, c]), mod(dir - 1, 6))[15; 10 12 14 -3 13 11]) * + O_r_c = network[r, c] + @tensor opt = true env_C_new[-1 -2 -3; -4] := + env.C[_coordinates(dir, 0, r, c, size(network))...][1 3 10; 6] * + env.Ea[_coordinates(dir - 1, 0, r, c, size(network))...][4 2 11; 1] * + env.Eb[_coordinates(dir, 1, r, c, size(network))...][6 7 12; 8] * + rotl60(ket(O_r_c), mod(dir - 1, 6))[15; 3 7 9 -2 5 2] * + conj(rotl60(bra(O_r_c), mod(dir - 1, 6))[15; 10 12 14 -3 13 11]) * Pas[mod1(dir - 1, 6), r, c][-1; 4 5 13] * Pbs[dir, r, c][8 9 14; -4] return env_C_new end diff --git a/src/algorithms/ctmrg_triangular/simultaneous.jl b/src/algorithms/ctmrg_triangular/simultaneous.jl index 81b4f980c..90e1321bb 100644 --- a/src/algorithms/ctmrg_triangular/simultaneous.jl +++ b/src/algorithms/ctmrg_triangular/simultaneous.jl @@ -32,13 +32,13 @@ function ctmrg_iteration(network::InfiniteTriangularNetwork, env::CTMRGEnvTriang Pas, Pbs, S = calculate_projectors(network, env, trunc, alg.projector_alg) env = renormalize_corners!(network, env, Pas, Pbs) - env = normalize_corners!(env) + env = normalize_corners(env) Ẽas, Ẽbs, Ẽastr, Ẽbstr = semi_renormalize(network, env, Pas, Pbs, trunc) Qas, Qbs = build_matrix_second_projectors(network, env, Ẽas, Ẽbs, Ẽastr, Ẽbstr, trunc; alg.conditioning) env = renormalize_edges(env, Ẽas, Ẽbs, Qas, Qbs) - normalize_edges!(env) + env = normalize_edges(env) return env, S end @@ -52,48 +52,49 @@ function calculate_projectors(network, env, trunc, projector_alg) end end +# TODO: remove once this once everything is plumbed through a proper projector algorithm +_truncation_strategy(trunc::TruncationStrategy, _) = trunc +function _truncation_strategy(::FixedSpaceTruncation, edge) + tspace = space(edge, 1) + return isdual(tspace) ? truncspace(flip(tspace)) : truncspace(tspace) +end + function calculate_twothirds_projectors(network::InfiniteTriangularNetwork{P}, env, trunc) where {P} - coordinates = eachcoordinate(network, 1:6) - E = scalartype(env.C[1, 1, 1]) - S = spacetype(env.C[1, 1, 1]) - T_proj = Tuple{AbstractTensorMap{E, S, 1, 2 + (network[1, 1] isa Tuple)}, AbstractTensorMap{E, S, 2 + (network[1, 1] isa Tuple), 1}, DiagonalTensorMap{real(E), S}} - projectors′ = similar(coordinates, T_proj) - projectors = dtmap!!(projectors′, coordinates) do (dir, r, c) - ρL = build_double_corner_matrix_triangular(network, env, mod1(dir - 1, 6), r, c) - ρR = build_double_corner_matrix_triangular(network, env, mod1(dir + 1, 6), r, c) + projectors = dtmap(eachcoordinate(network, 1:6)) do (dir, r, c) + ρL = build_double_corner_matrix_triangular(network, env, _prev(dir, 6), r, c) + ρR = build_double_corner_matrix_triangular(network, env, _next(dir, 6), r, c) ρρ = ρL * ρR ρρ /= norm(ρρ) + trunc = _truncation_strategy(trunc, env.Ea[dir, r, c]) U, S, V = svd_trunc(ρρ; trunc) + iqsrtS = sdiag_pow(S, -0.5) - Pb = ρR * V' * sdiag_pow(S, -1 / 2) - Pa = sdiag_pow(S, -1 / 2) * U' * ρL + Pb = ρR * V' * iqsrtS + Pa = iqsrtS * U' * ρL return Pa, Pb, S end return getindex.(projectors, 1), getindex.(projectors, 2), getindex.(projectors, 3) end function calculate_full_projectors(network::InfiniteTriangularNetwork{P}, env, trunc) where {P} - coordinates = eachcoordinate(network, 1:6) - E = scalartype(env.C[1, 1, 1]) - S = spacetype(env.C[1, 1, 1]) - T_proj = Tuple{AbstractTensorMap{E, S, 1, 2 + (network[1, 1] isa Tuple)}, AbstractTensorMap{E, S, 2 + (network[1, 1] isa Tuple), 1}, DiagonalTensorMap{real(E), S}} - projectors′ = similar(coordinates, T_proj) - projectors = dtmap!!(projectors′, coordinates) do (dir, r, c) + projectors = dtmap(eachcoordinate(network, 1:6)) do (dir, r, c) ρL = build_double_corner_matrix_triangular(network, env, mod1(dir - 1, 6), r, c) ρR = build_double_corner_matrix_triangular(network, env, mod1(dir + 1, 6), r, c) ρ̄ = build_double_corner_matrix_triangular(network, env, mod1(dir + 3, 6), r, c) ρ̄ /= norm(ρ̄) Ū, S̄, V̄ᴴ = svd_full(ρ̄) - ρ̄ᴿ = Ū * sqrt(S̄) - ρ̄ᴸ = sqrt(S̄) * V̄ᴴ + sqrtS̄ = sdiag_pow(S̄, 0.5) + ρ̄ᴿ = Ū * sqrtS̄ + ρ̄ᴸ = sqrtS̄ * V̄ᴴ ρρ = ρ̄ᴸ * ρL * ρR * ρ̄ᴿ ρρ /= norm(ρρ) + trunc = _truncation_strategy(trunc, env.Ea[dir, r, c]) U, S, Vᴴ = svd_trunc(ρρ; trunc) - - Pb = ρR * ρ̄ᴿ * Vᴴ' * sdiag_pow(S, -1 / 2) - Pa = sdiag_pow(S, -1 / 2) * U' * ρ̄ᴸ * ρL + isqrtS = sdiag_pow(S, -0.5) + Pb = ρR * ρ̄ᴿ * Vᴴ' * isqrtS + Pa = isqrtS * U' * ρ̄ᴸ * ρL return Pa, Pb, S end @@ -127,22 +128,21 @@ function _permute_edge(t::Union{T1, T2}) where {E, S, T1 <: AbstractTensorMap{E, end function semi_renormalize(network::InfiniteTriangularNetwork, env::CTMRGEnvTriangular, Pas, Pbs, trunc) - coordinates = eachcoordinate(network, 1:6) - E = scalartype(env.C[1, 1, 1]) - S = spacetype(env.C[1, 1, 1]) - T_proj = Tuple{AbstractTensorMap{E, S, 2 + (network[1, 1] isa Tuple), 1}, AbstractTensorMap{E, S, 2 + (network[1, 1] isa Tuple), 1}, AbstractTensorMap{E, S, 2 + (network[1, 1] isa Tuple), 1}, AbstractTensorMap{E, S, 2 + (network[1, 1] isa Tuple), 1}} - projectors′ = similar(coordinates, T_proj) - projectors = dtmap!!(projectors′, coordinates) do (dir, r, c) + projectors = dtmap(eachcoordinate(network, 1:6)) do (dir, r, c) mat = semi_renormalize_edge(network, env, Pas, Pbs, dir, r, c) U, S, V = svd_full(mat) - Ẽb = U * sqrt(S) - Ẽa = _permute_edge(sqrt(S) * V) + sqrtS = sdiag_pow(S, 0.5) + Ẽb = U * sqrtS + Ẽa = _permute_edge(sqrtS * V) + + trunc = _truncation_strategy(trunc, env.Ea[dir, r, c]) Utr, Str, Vtr = svd_trunc(mat; trunc) - Ẽbtr = Utr * sqrt(Str) - Ẽatr = _permute_edge(sqrt(Str) * Vtr) + sqrt_Str = sdiag_pow(Str, 0.5) + Ẽbtr = Utr * sqrt_Str + Ẽatr = _permute_edge(sqrt_Str * Vtr) return Ẽa, Ẽb, Ẽatr, Ẽbtr end @@ -150,57 +150,56 @@ function semi_renormalize(network::InfiniteTriangularNetwork, env::CTMRGEnvTrian end function build_matrix_second_projectors(network::InfiniteTriangularNetwork, env::CTMRGEnvTriangular, Ẽas, Ẽbs, Ẽastr, Ẽbstr, trunc; conditioning = true) - coordinates = eachcoordinate(network, 1:6) - E = scalartype(env.C[1, 1, 1]) - S = spacetype(env.C[1, 1, 1]) - T_proj = Tuple{AbstractTensorMap{E, S, 1, 1}, AbstractTensorMap{E, S, 1, 1}} - projectors′ = similar(coordinates, T_proj) - projectors = dtmap!!(projectors′, coordinates) do (dir, r, c) + projectors = dtmap(eachcoordinate(network, 1:6)) do (dir, r, c) + trunc = _truncation_strategy(trunc, env.Ea[dir, r, c]) σL, σR = build_halfinfinite_projectors(network, env, Ẽas, Ẽbs, Ẽastr, Ẽbstr, dir, r, c) if conditioning σL /= norm(σL) σR /= norm(σR) - UL, SL, VLᴴ = svd_full(σL) - UR, SR, VRᴴ = svd_full(σR) + _, SL, VLᴴ = svd_full(σL) + UR, SR, _ = svd_full(σR) - FLU = sqrt(SL) * VLᴴ - FRU = UR * sqrt(SR) + sqrtSL = sdiag_pow(SL, 0.5) + sqrtSR = sdiag_pow(SR, 0.5) + + FLU = sqrtSL * VLᴴ + FRU = UR * sqrtSR mat = FLU * FRU mat /= norm(mat) WU, SU, QUᴴ = svd_trunc(mat; trunc) + isqrtSU = sdiag_pow(SU, -0.5) - Qa = sdiag_pow(SU, -1 / 2) * WU' * FLU - Qb = FRU * QUᴴ' * sdiag_pow(SU, -1 / 2) + Qa = isqrtSU * WU' * FLU + Qb = FRU * QUᴴ' * isqrtSU else mat = σL * σR mat /= norm(mat) U, S, V = svd_trunc(mat; trunc) - Qa = sdiag_pow(S, -1 / 2) * U' * σL - Qb = σR * V' * sdiag_pow(S, -1 / 2) + isqrtS = sdiag_pow(S, -0.5) + Qa = isqrtS * U' * σL + Qb = σR * V' * isqrtS end return Qa, Qb end - return getindex.(projectors, 1), getindex.(projectors, 2) + PL = map(x -> x[1], projectors) + PR = map(x -> x[2], projectors) + return PL, PR end function renormalize_edges(env::CTMRGEnvTriangular, Ẽas::Array{T1, 3}, Ẽbs::Array{T1, 3}, Qas::Array{T2, 3}, Qbs::Array{T2, 3}) where {E, S, T1 <: AbstractTensorMap{E, S, 3, 1}, T2 <: AbstractTensorMap{E, S, 1, 1}} - coordinates = collect(Iterators.product(axes(env.Ea)...)) - T_proj = Tuple{AbstractTensorMap{E, S, 3, 1}, AbstractTensorMap{E, S, 3, 1}} - new_edges′ = similar(env.Ea, T_proj) - new_edges = dtmap!!(new_edges′, coordinates) do (dir, r, c) + new_edges = dtmap(eachcoordinate(env, 1:6)) do (dir, r, c) Eb_new = Ẽbs[dir, r, c] * Qbs[dir, r, c] Ea_new = permute(Qas[dir, r, c] * permute(Ẽas[dir, r, c], ((1,), (2, 3, 4))), ((1, 2, 3), (4,))) return Ea_new, Eb_new end - return CTMRGEnvTriangular(env.C, getindex.(new_edges, 1), getindex.(new_edges, 2)) + Ea = map(x -> x[1], new_edges) + Eb = map(x -> x[2], new_edges) + return CTMRGEnvTriangular(env.C, Ea, Eb) end -function renormalize_edges(env::CTMRGEnvTriangular, Ẽas::Array{T1, 3}, Ẽbs::Array{T1, 3}, Qas::Array{T2, 3}, Qbs::Array{T2, 3}) where {E, S, T1 <: AbstractTensorMap{E, S, 2, 1}, T2 <: AbstractTensorMap{E, S, 1, 1}} - coordinates = collect(Iterators.product(axes(env.Ea)...)) - T_proj = Tuple{AbstractTensorMap{E, S, 2, 1}, AbstractTensorMap{E, S, 2, 1}} - new_edges′ = similar(env.Ea, T_proj) - new_edges = dtmap!!(new_edges′, coordinates) do (dir, r, c) +function renormalize_edges(env::CTMRGEnvTriangular, Ẽas::Array{T1, 3}, Ẽbs::Array{T1, 3}, Qas::Array{T2, 3}, Qbs::Array{T2, 3}) where {T1 <: CTMRGEdgeTensor, T2 <: CTMRGCornerTensor} + new_edges = dtmap(eachcoordinate(env, 1:6)) do (dir, r, c) Eb_new = Ẽbs[dir, r, c] * Qbs[dir, r, c] Ea_new = permute(Qas[dir, r, c] * permute(Ẽas[dir, r, c], ((1,), (2, 3))), ((1, 2), (3,))) return Ea_new, Eb_new @@ -208,23 +207,21 @@ function renormalize_edges(env::CTMRGEnvTriangular, Ẽas::Array{T1, 3}, Ẽbs:: return CTMRGEnvTriangular(env.C, getindex.(new_edges, 1), getindex.(new_edges, 2)) end -function normalize_corners!(env) - coordinates = collect(Iterators.product(axes(env.Ea)...)) - new_corners′ = similar(env.C, typeof(env.C[1, 1, 1])) - new_corners = dtmap!!(new_corners′, coordinates) do (dir, r, c) - env_C_new = env.C[dir, r, c] / norm(env.C[dir, r, c]) - return env_C_new +function normalize_corners(env::CTMRGEnvTriangular) + C_normalized = map(env.C) do C + return C / norm(C) end - return CTMRGEnvTriangular(new_corners, env.Ea, env.Eb) + return CTMRGEnvTriangular(C_normalized, env.Ea, env.Eb) end -function normalize_edges!(env) - (r, c) = (1, 1) - for dir in 1:6 - env.Ea[dir, r, c] /= norm(env.Ea[dir, r, c]) - env.Eb[dir, r, c] /= norm(env.Eb[dir, r, c]) +function normalize_edges(env::CTMRGEnvTriangular) + Ea_normalized = map(env.Ea) do e + return e / norm(e) + end + Eb_normalized = map(env.Eb) do e + return e / norm(e) end - return env + return CTMRGEnvTriangular(env.C, Ea_normalized, Eb_normalized) end function calculate_error(Ss, Ss_prev) diff --git a/src/algorithms/optimization/fixed_point_differentiation.jl b/src/algorithms/optimization/fixed_point_differentiation.jl index 3e55bad12..cb2436d9e 100644 --- a/src/algorithms/optimization/fixed_point_differentiation.jl +++ b/src/algorithms/optimization/fixed_point_differentiation.jl @@ -213,7 +213,7 @@ function _rrule( ::typeof(leading_boundary), envinit, state, - alg::Union{CTMRGAlgorithm, CTMRGAlgorithmTriangular}, + alg::CTMRGAlgorithm, ) env, info = leading_boundary(envinit, state, alg) alg_fixed = @set alg.projector_alg.trunc = FixedSpaceTruncation() # fix spaces during differentiation @@ -279,42 +279,16 @@ function _rrule( end function _rrule( - gradmode::GradMode{:fixed}, + gradmode::GradMode, config::RuleConfig, ::typeof(MPSKit.leading_boundary), envinit, state, - alg::SimultaneousCTMRGTriangular, + alg::CTMRGAlgorithmTriangular, ) - env, = leading_boundary(envinit, state, alg) - alg_fixed = @set alg.trunctype = :FixedSpaceTruncation # fix spaces during differentiation - env_conv, info = ctmrg_iteration(InfiniteTriangularNetwork(state), env, alg_fixed) - env_fixed, signs = gauge_fix(env, env_conv) - - # Fix SVD - svd_alg_fixed = _fix_svd_algorithm(alg.projector_alg.svd_alg, signs, info) - alg_fixed = @set alg.projector_alg.svd_alg = svd_alg_fixed - alg_fixed = @set alg_fixed.projector_alg.trunc = notrunc() - - function leading_boundary_fixed_pullback((Δenv′, Δinfo)) - Δenv = unthunk(Δenv′) - - function f(A, x) - return fix_global_phases( - x, ctmrg_iteration(InfiniteTriangularNetwork(A), x, alg_fixed)[1] - ) - end - _, env_vjp = rrule_via_ad(config, f, state, env_fixed) - - # evaluate the geometric sum - ∂f∂A(x)::typeof(state) = env_vjp(x)[2] - ∂f∂x(x)::typeof(env) = env_vjp(x)[3] - ∂F∂env = fpgrad(Δenv, ∂f∂x, ∂f∂A, Δenv, gradmode) - - return NoTangent(), ZeroTangent(), ∂F∂env, NoTangent() - end - - return (env_fixed, info), leading_boundary_fixed_pullback + msg = "Fixed-point differentiation is not available for `CTMRGAlgorithmTriangular`, \ + since gauge fixing for triangular CTMRG is currently not implemented." + throw(ArgumentError(msg)) end function _fix_svd_algorithm(alg::SVDAdjoint, signs, info) diff --git a/src/algorithms/select_algorithm.jl b/src/algorithms/select_algorithm.jl index 7faf9564f..c121fb3fd 100644 --- a/src/algorithms/select_algorithm.jl +++ b/src/algorithms/select_algorithm.jl @@ -158,10 +158,3 @@ function select_algorithm( return CTMRGAlgorithm(; alg, tol, verbosity, decomposition_alg, kwargs...) end - -function select_algorithm( - ::typeof(leading_boundary), - env₀::CTMRGEnvTriangular - ) - return SimultaneousCTMRGTria() -end diff --git a/src/algorithms/toolbox_triangular.jl b/src/algorithms/toolbox_triangular.jl index ab67be0ef..99da822fc 100644 --- a/src/algorithms/toolbox_triangular.jl +++ b/src/algorithms/toolbox_triangular.jl @@ -54,8 +54,10 @@ end ### For function _contract_edges_0((r, c)::Tuple{Int, Int}, network::InfiniteTriangularNetwork{P}, env::CTMRGEnvTriangular) where {P <: PEPSSandwichTriangular} - return @tensor opt = true ket(network[r, c])[dL; DLt120 DLt60 DLt0 DLt300 DLt240 DLt180] * ket(network[r, _next(c, end)])[dR; DRt120 DRt60 DRt0 DRt300 DRt240 DLt0] * - conj(bra(network[r, c])[dL; DLb120 DLb60 DLb0 DLb300 DLb240 DLb180]) * conj(bra(network[r, _next(c, end)])[dR; DRb120 DRb60 DRb0 DRb300 DRb240 DLb0]) * + O_r_c = network[r, c] + O_r_cn = network[r, _next(c, end)] + return @tensor opt = true ket(O_r_c)[dL; DLt120 DLt60 DLt0 DLt300 DLt240 DLt180] * ket(O_r_cn)[dR; DRt120 DRt60 DRt0 DRt300 DRt240 DLt0] * + conj(bra(O_r_c)[dL; DLb120 DLb60 DLb0 DLb300 DLb240 DLb180]) * conj(bra(O_r_cn)[dR; DRb120 DRb60 DRb0 DRb300 DRb240 DLb0]) * env.C[1, _prev(r, end), c][χNW DLt120 DLb120; χNa] * env.C[2, _prev(r, end), _next(c + 1, end)][χNb DRt60 DRb60; χNE] * env.C[3, r, _next(c + 1, end)][χNE DRt0 DRb0; χSE] * env.C[4, _next(r, end), _next(c, end)][χSE DRt300 DRb300; χSa] * env.C[5, _next(r, end), _prev(c, end)][χSb DLt240 DLb240; χSW] * env.C[6, r, _prev(c, end)][χSW DLt180 DLb180; χNW] * env.Eb[1, _prev(r, end), _next(c, end)][χNa DLt60 DLb60; χNC] * env.Ea[1, _prev(r, end), _next(c, end)][χNC DRt120 DRb120; χNb] * @@ -63,8 +65,10 @@ function _contract_edges_0((r, c)::Tuple{Int, Int}, network::InfiniteTriangularN end function _contract_edges_0((r, c)::Tuple{Int, Int}, network::InfiniteTriangularNetwork{P}, env::CTMRGEnvTriangular, op::AbstractTensorMap{E, S, 2, 2}) where {P <: PEPSSandwichTriangular, E, S} - return @tensor opt = true ket(network[r, c])[dLt; DLt120 DLt60 DLt0 DLt300 DLt240 DLt180] * ket(network[r, _next(c, end)])[dRt; DRt120 DRt60 DRt0 DRt300 DRt240 DLt0] * - conj(bra(network[r, c])[dLb; DLb120 DLb60 DLb0 DLb300 DLb240 DLb180]) * conj(bra(network[r, _next(c, end)])[dRb; DRb120 DRb60 DRb0 DRb300 DRb240 DLb0]) * + O_r_c = network[r, c] + O_r_cn = network[r, _next(c, end)] + return @tensor opt = true ket(O_r_c)[dLt; DLt120 DLt60 DLt0 DLt300 DLt240 DLt180] * ket(O_r_cn)[dRt; DRt120 DRt60 DRt0 DRt300 DRt240 DLt0] * + conj(bra(O_r_c)[dLb; DLb120 DLb60 DLb0 DLb300 DLb240 DLb180]) * conj(bra(O_r_cn)[dRb; DRb120 DRb60 DRb0 DRb300 DRb240 DLb0]) * env.C[1, _prev(r, end), c][χNW DLt120 DLb120; χNa] * env.C[2, _prev(r, end), _next(c + 1, end)][χNb DRt60 DRb60; χNE] * env.C[3, r, _next(c + 1, end)][χNE DRt0 DRb0; χSE] * env.C[4, _next(r, end), _next(c, end)][χSE DRt300 DRb300; χSa] * env.C[5, _next(r, end), _prev(c, end)][χSb DLt240 DLb240; χSW] * env.C[6, r, _prev(c, end)][χSW DLt180 DLb180; χNW] * env.Eb[1, _prev(r, end), _next(c, end)][χNa DLt60 DLb60; χNC] * env.Ea[1, _prev(r, end), _next(c, end)][χNC DRt120 DRb120; χNb] * @@ -73,8 +77,10 @@ function _contract_edges_0((r, c)::Tuple{Int, Int}, network::InfiniteTriangularN end function _contract_edges_60((r, c)::Tuple{Int, Int}, network::InfiniteTriangularNetwork{P}, env::CTMRGEnvTriangular) where {P <: PEPSSandwichTriangular} - return @tensor opt = true ket(network[r, c])[dTR; DTRt120 DTRt60 DTRt0 DTRt300 DBLt60 DTRt180] * ket(network[_next(r, end), _prev(c, end)])[dBL; DBLt120 DBLt60 DBLt0 DBLt300 DBLt240 DBLt180] * - conj(bra(network[r, c])[dTR; DTRb120 DTRb60 DTRb0 DTRb300 DBLb60 DTRb180]) * conj(bra(network[_next(r, end), _prev(c, end)])[dBL; DBLb120 DBLb60 DBLb0 DBLb300 DBLb240 DBLb180]) * + O_r_c = network[r, c] + O_rn_cn = network[_next(r, end), _next(c, end)] + return @tensor opt = true ket(O_r_c)[dTR; DTRt120 DTRt60 DTRt0 DTRt300 DBLt60 DTRt180] * ket(O_rn_cn)[dBL; DBLt120 DBLt60 DBLt0 DBLt300 DBLt240 DBLt180] * + conj(bra(O_r_c)[dTR; DTRb120 DTRb60 DTRb0 DTRb300 DBLb60 DTRb180]) * conj(bra(O_rn_cn)[dBL; DBLb120 DBLb60 DBLb0 DBLb300 DBLb240 DBLb180]) * env.C[1, _prev(r, end), c][χNWb DTRt120 DTRb120; χN] * env.C[2, _prev(r, end), _next(c, end)][χN DTRt60 DTRb60; χNE] * env.C[3, r, _next(c, end)][χNE DTRt0 DTRb0; χSEa] * env.C[4, _next(r + 1, end), _prev(c, end)][χSEb DBLt300 DBLb300; χS] * env.C[5, _next(r + 1, end), _prev(c - 1, end)][χS DBLt240 DBLb240; χSW] * env.C[6, _next(r, end), _prev(c - 1, end)][χSW DBLt180 DBLb180; χNWa] * env.Eb[3, _next(r, end), c][χSEa DTRt300 DTRb300; χSEC] * env.Ea[3, _next(r, end), c][χSEC DBLt0 DBLb0; χSEb] * @@ -82,8 +88,10 @@ function _contract_edges_60((r, c)::Tuple{Int, Int}, network::InfiniteTriangular end function _contract_edges_120((r, c)::Tuple{Int, Int}, network::InfiniteTriangularNetwork{P}, env::CTMRGEnvTriangular) where {P <: PEPSSandwichTriangular} - return @tensor opt = true ket(network[r, c])[dTL; DTLt120 DTLt60 DTLt0 DTLt300 DTLt240 DTLt180] * ket(network[_next(r, end), c])[dBR; DTLt300 DBRt60 DBRt0 DBRt300 DBRt240 DBRt180] * - conj(bra(network[r, c])[dTL; DTLb120 DTLb60 DTLb0 DTLb300 DTLb240 DTLb180]) * conj(bra(network[_next(r, end), c])[dBR; DTLb300 DBRb60 DBRb0 DBRb300 DBRb240 DBRb180]) * + O_r_c = network[r, c] + O_rn_c = network[_next(r, end), c] + return @tensor opt = true ket(O_r_c)[dTL; DTLt120 DTLt60 DTLt0 DTLt300 DTLt240 DTLt180] * ket(O_rn_c)[dBR; DTLt300 DBRt60 DBRt0 DBRt300 DBRt240 DBRt180] * + conj(bra(O_r_c)[dTL; DTLb120 DTLb60 DTLb0 DTLb300 DTLb240 DTLb180]) * conj(bra(O_rn_c)[dBR; DTLb300 DBRb60 DBRb0 DBRb300 DBRb240 DBRb180]) * env.C[1, _prev(r, end), c][χNW DTLt120 DTLb120; χN] * env.C[2, _prev(r, end), _next(c, end)][χN DTLt60 DTLb60; χNEa] * env.C[3, _next(r, end), _next(c, end)][χNEb DBRt0 DBRb0; χSE] * env.C[4, _next(r + 1, end), c][χSE DBRt300 DBRb300; χS] * env.C[5, _next(r + 1, end), _prev(c, end)][χS DBRt240 DBRb240; χSWa] * env.C[6, r, _prev(c, end)][χSWb DTLt180 DTLb180; χNW] * env.Eb[2, r, _next(c, end)][χNEa DTLt0 DTLb0; χNEC] * env.Ea[2, r, _next(c, end)][χNEC DBRt60 DBRb60; χNEb] * @@ -91,12 +99,19 @@ function _contract_edges_120((r, c)::Tuple{Int, Int}, network::InfiniteTriangula end function _contract_site_large((r, c)::Tuple{Int, Int}, network::InfiniteTriangularNetwork{P}, env::CTMRGEnvTriangular) where {P <: PEPSSandwichTriangular} - return @tensor opt = true ket(network[_prev(r, end), c])[dNW; DNWt120 DNWt60 DNWt0 DNWt300 DWt60 DNWt180] * ket(network[_prev(r, end), _next(c, end)])[dNE; DNEt120 DNEt60 DNEt0 DNEt300 DNEt240 DNWt0] * - ket(network[r, _next(c, end)])[dE; DNEt300 DEt60 DEt0 DEt300 DEt240 DEt180] * ket(network[_next(r, end), c])[dSE; DSEt120 DEt240 DSEt0 DSEt300 DSEt240 DSEt180] * ket(network[_next(r, end), _prev(c, end)])[dSW; DSWt120 DSWt60 DSEt180 DSWt300 DSWt240 DSWt180] * - ket(network[r, _prev(c, end)])[dW; DWt120 DWt60 DWt0 DSWt120 DWt240 DWt180] * ket(network[r, c])[dCenter; DNWt300 DNEt240 DEt180 DSEt120 DSWt60 DWt0] * - conj(bra(network[_prev(r, end), c])[dNW; DNWb120 DNWb60 DNWb0 DNWb300 DWb60 DNWb180]) * conj(bra(network[_prev(r, end), _next(c, end)])[dNE; DNEb120 DNEb60 DNEb0 DNEb300 DNEb240 DNWb0]) * - conj(bra(network[r, _next(c, end)])[dE; DNEb300 DEb60 DEb0 DEb300 DEb240 DEb180]) * conj(bra(network[_next(r, end), c])[dSE; DSEb120 DEb240 DSEb0 DSEb300 DSEb240 DSEb180]) * conj(bra(network[_next(r, end), _prev(c, end)])[dSW; DSWb120 DSWb60 DSEb180 DSWb300 DSWb240 DSWb180]) * - conj(bra(network[r, _prev(c, end)])[dW; DWb120 DWb60 DWb0 DSWb120 DWb240 DWb180]) * conj(bra(network[r, c])[dCenter; DNWb300 DNEb240 DEb180 DSEb120 DSWb60 DWb0]) * + O_r_c = network[r, c] + O_rp_c = network[_prev(r, end), c] + O_rp_cn = network[_prev(r, end), _next(c, end)] + O_rn_cp = network[_next(r, end), _prev(c, end)] + O_rn_c = network[_next(r, end), c] + O_r_cn = network[r, _next(c, end)] + O_r_cp = network[r, _prev(c, end)] + return @tensor opt = true ket(O_rp_c)[dNW; DNWt120 DNWt60 DNWt0 DNWt300 DWt60 DNWt180] * ket(O_rp_cn)[dNE; DNEt120 DNEt60 DNEt0 DNEt300 DNEt240 DNWt0] * + ket(O_r_cn)[dE; DNEt300 DEt60 DEt0 DEt300 DEt240 DEt180] * ket(O_rn_c)[dSE; DSEt120 DEt240 DSEt0 DSEt300 DSEt240 DSEt180] * ket(O_rn_cp)[dSW; DSWt120 DSWt60 DSEt180 DSWt300 DSWt240 DSWt180] * + ket(O_r_cp)[dW; DWt120 DWt60 DWt0 DSWt120 DWt240 DWt180] * ket(O_r_c)[dCenter; DNWt300 DNEt240 DEt180 DSEt120 DSWt60 DWt0] * + conj(bra(O_rp_c)[dNW; DNWb120 DNWb60 DNWb0 DNWb300 DWb60 DNWb180]) * conj(bra(O_rp_cn)[dNE; DNEb120 DNEb60 DNEb0 DNEb300 DNEb240 DNWb0]) * + conj(bra(O_r_cn)[dE; DNEb300 DEb60 DEb0 DEb300 DEb240 DEb180]) * conj(bra(O_rn_c)[dSE; DSEb120 DEb240 DSEb0 DSEb300 DSEb240 DSEb180]) * conj(bra(O_rn_cp)[dSW; DSWb120 DSWb60 DSEb180 DSWb300 DSWb240 DSWb180]) * + conj(bra(O_r_cp)[dW; DWb120 DWb60 DWb0 DSWb120 DWb240 DWb180]) * conj(bra(O_r_c)[dCenter; DNWb300 DNEb240 DEb180 DSEb120 DSWb60 DWb0]) * env.C[1, _prev(r - 1, end), c][χNWa DNWt120 DNWb120; χNb] * env.Eb[1, _prev(r - 1, end), _next(c, end)][χNb DNWt60 DNWb60; χNC] * env.Ea[1, _prev(r - 1, end), _next(c, end)][χNC DNEt120 DNEb120; χNa] * env.C[2, _prev(r - 1, end), _next(c + 1, end)][χNa DNEt60 DNEb60; χNEb] * env.Eb[2, _prev(r, end), _next(c + 1, end)][χNEb DNEt0 DNEb0; χNEC] * env.Ea[2, _prev(r, end), _next(c + 1, end)][χNEC DEt60 DEb60; χNEa] * env.C[3, r, _next(c + 1, end)][χNEa DEt0 DEb0; χSEb] * env.Eb[3, _next(r, end), _next(c, end)][χSEb DEt300 DEb300; χSEC] * env.Ea[3, _next(r, end), _next(c, end)][χSEC DSEt0 DSEb0; χSEa] * @@ -106,21 +121,33 @@ function _contract_site_large((r, c)::Tuple{Int, Int}, network::InfiniteTriangul end function _contract_corners((r, c)::Tuple{Int, Int}, network::InfiniteTriangularNetwork{P}, env::CTMRGEnvTriangular) where {P <: PEPSSandwichTriangular} + O_r_c = network[r, c] return @tensor opt = true env.C[1, _prev(r, end), c][χNW Dt120 Db120; χN] * env.C[2, _prev(r, end), _next(c, end)][χN Dt60 Db60; χNE] * env.C[3, r, _next(c, end)][χNE Dt0 Db0; χSE] * env.C[4, _next(r, end), c][χSE Dt300 Db300; χS] * env.C[5, _next(r, end), _prev(c, end)][χS Dt240 Db240; χSW] * env.C[6, r, _prev(c, end)][χSW Dt180 Db180; χNW] * - ket(network[r, c])[d; Dt120 Dt60 Dt0 Dt300 Dt240 Dt180] * conj(bra(network[r, c])[d; Db120 Db60 Db0 Db300 Db240 Db180]) + ket(O_r_c)[d; Dt120 Dt60 Dt0 Dt300 Dt240 Dt180] * conj(bra(O_r_c)[d; Db120 Db60 Db0 Db300 Db240 Db180]) end function _contract_corners((r, c)::Tuple{Int, Int}, network::InfiniteTriangularNetwork{P}, env::CTMRGEnvTriangular, op::AbstractTensorMap{E, S, 1, 1}) where {P <: PEPSSandwichTriangular, E, S} + O_r_c = network[r, c] return @tensor opt = true env.C[1, _prev(r, end), c][χNW Dt120 Db120; χN] * env.C[2, _prev(r, end), _next(c, end)][χN Dt60 Db60; χNE] * env.C[3, r, _next(c, end)][χNE Dt0 Db0; χSE] * env.C[4, _next(r, end), c][χSE Dt300 Db300; χS] * env.C[5, _next(r, end), _prev(c, end)][χS Dt240 Db240; χSW] * env.C[6, r, _prev(c, end)][χSW Dt180 Db180; χNW] * - ket(network[r, c])[dt; Dt120 Dt60 Dt0 Dt300 Dt240 Dt180] * conj(bra(network[r, c])[db; Db120 Db60 Db0 Db300 Db240 Db180]) * op[db; dt] + ket(O_r_c)[dt; Dt120 Dt60 Dt0 Dt300 Dt240 Dt180] * conj(bra(O_r_c)[db; Db120 Db60 Db0 Db300 Db240 Db180]) * op[db; dt] end function energy(network::InfiniteTriangularNetwork{P}, env::CTMRGEnvTriangular, onesite_op, twosite_op) where {P <: PEPSSandwichTriangular} - return sum(Iterators.product(axes(network)...)) do (r, c) + E = sum(Iterators.product(axes(network)...)) do (r, c) expval_onesite = _contract_corners((r, c), network, env, onesite_op) / _contract_corners((r, c), network, env) expval_twosite = _contract_edges_0((r, c), network, env, twosite_op) / _contract_edges_0((r, c), network, env) return expval_onesite + expval_twosite end + ignore_derivatives() do + isapprox(imag(E), 0; atol = sqrt(eps(real(E)))) || + @warn "Expectation value is not real: $E." + end + return real(E) +end +function energy(state::InfinitePEPSTriangular, env::CTMRGEnvTriangular, args...) + return energy(InfiniteTriangularNetwork(state), env, args...) end + +# TODO: write a proper cost_function that works on an InfinitePEPSTriangular and a LocalOperator, and use it in the optimization loop. For now, we just compute the energy using the network contraction functions above. diff --git a/src/environments/ctmrg_environments_triangular.jl b/src/environments/ctmrg_environments_triangular.jl index 433cc7b47..c2fea0a7c 100644 --- a/src/environments/ctmrg_environments_triangular.jl +++ b/src/environments/ctmrg_environments_triangular.jl @@ -1,65 +1,34 @@ #TODO: Add docs and figure -struct CTMRGEnvTriangular{T} +struct CTMRGEnvTriangular{TC, TA, TB} "6 x rows x cols array of corner C tensors, where the first dimension specifies the spatial direction" - C::Array{T, 3} + C::Array{TC, 3} "6 x rows x cols array of edge Ta tensors, where the first dimension specifies the spatial direction" - Ea::Array{T, 3} - "6 x rows x cols array of edge Ta tensors, where the first dimension specifies the spatial direction" - Eb::Array{T, 3} + Ea::Array{TA, 3} + "6 x rows x cols array of edge Tb tensors, where the first dimension specifies the spatial direction" + Eb::Array{TB, 3} end -# function CTMRGEnvTriangular(corners::Array{C, 3}, edges::Array{T, 3}) where {C, T} -# foreach(check_environment_virtualspace, edges) -# return CTMRGEnvTriangular{C, T}(corners, edges) -# end - -# """ -# CTMRGEnv( -# [f=randn, T=ComplexF64], Ds_north::A, Ds_east::A, chis_north::B, [chis_east::B], [chis_south::B], [chis_west::B] -# ) where {A<:AbstractMatrix{<:VectorSpace}, B<:AbstractMatrix{<:ElementarySpace}} - -# Construct a CTMRG environment by specifying matrices of north and east virtual spaces of the -# corresponding partition function and the north, east, south and west virtual spaces of the -# environment. Each respective matrix entry corresponds to a site in the unit cell. By -# default, the virtual environment spaces for all directions are taken to be the same. - -# The environment virtual spaces for each site correspond to the north or east virtual space -# of the corresponding edge tensor for each direction. Specifically, for a given site -# `(r, c)`, `chis_north[r, c]` corresponds to the east space of the north edge tensor, -# `chis_east[r, c]` corresponds to the north space of the east edge tensor, -# `chis_south[r, c]` corresponds to the east space of the south edge tensor, and -# `chis_west[r, c]` corresponds to the north space of the west edge tensor. -# Each entry of the `Ds_north` and `Ds_east` matrices corresponds to an effective local space -# of the partition function, and can be represented as an `ElementarySpace` (e.g. for the case -# of a partition function defined in terms of local rank-4 tensors) or a `ProductSpace` (e.g. -# for the case of a network representing overlaps of PEPSs and PEPOs). -# """ function get_Ds(D::A) where {A <: ProductSpace} - return [dir > 3 ? reverse(D') : D for dir in 1:6] + return [dir > 3 ? _elementwise_dual(D) : D for dir in 1:6] end function get_Ds(D::A) where {A <: ElementarySpace} - return [dir > 3 ? D' : D for dir in 1:6] + return [dir > 3 ? ProductSpace(D') : ProductSpace(D) for dir in 1:6] end function CTMRGEnvTriangular( f, T, D::Union{A, B}, chis::B; unitcell::Tuple{Int, Int} = (1, 1) - ) where { - A <: ProductSpace, B <: ElementarySpace, - } - if typeof(D) <: ElementarySpace - N = 1 - else - N = length(D) - end - st = spacetype(D) + ) where {A <: ProductSpace, B <: ElementarySpace} + Ds = get_Ds(D) + N = length(Ds[1]) + st = spacetype(Ds[1]) + T_type = tensormaptype(st, N + 1, 1, T) Cs = Array{T_type}(undef, 6, unitcell...) Eas = Array{T_type}(undef, 6, unitcell...) Ebs = Array{T_type}(undef, 6, unitcell...) - Ds = get_Ds(D) for dir in 1:6, r in 1:unitcell[1], c in 1:unitcell[2] C = _edge_tensor(f, T, chis, Ds[dir], chis) Ea = _edge_tensor(f, T, chis, Ds[dir], chis) @@ -75,3 +44,76 @@ function CTMRGEnvTriangular( end return CTMRGEnvTriangular(Cs, Eas, Ebs) end + +Base.size(env::CTMRGEnvTriangular, args...) = size(env.C, args...) +Base.axes(x::CTMRGEnvTriangular, args...) = axes(x.C, args...) +function eachcoordinate(x::CTMRGEnvTriangular) + return collect(Iterators.product(axes(x, 2), axes(x, 3))) +end +function eachcoordinate(x::CTMRGEnvTriangular, dirs) + return collect(Iterators.product(dirs, axes(x, 2), axes(x, 3))) +end +Base.real(env::CTMRGEnvTriangular) = CTMRGEnvTriangular(real.(env.C), real.(env.Ea), real.(env.Eb)) +Base.complex(env::CTMRGEnvTriangular) = CTMRGEnvTriangular(complex.(env.C), complex.(env.Ea), complex.(env.Eb)) + +cornertype(env::CTMRGEnvTriangular) = cornertype(typeof(env)) +cornertype(::Type{CTMRGEnvTriangular{T}}) where {T} = T +edgetype(env::CTMRGEnvTriangular) = edgetype(typeof(env)) +edgetype(::Type{CTMRGEnvTriangular{T}}) where {T} = T + +TensorKit.spacetype(::Type{E}) where {E <: CTMRGEnvTriangular} = spacetype(cornertype(E)) + +# In-place update of environment +function update!(env::CTMRGEnvTriangular{T}, env´::CTMRGEnvTriangular{T}) where {T} + env.C .= env´.C + env.Ea .= env´.Ea + env.Eb .= env´.Eb + return env +end + +# Custom adjoint for CTMRGEnvTriangular constructor, needed for fixed-point differentiation +function ChainRulesCore.rrule( + ::Type{CTMRGEnvTriangular}, C::Array{T, 3}, Ea::Array{T, 3}, Eb::Array{T, 3} + ) where {T} + function triangularenv_pullback(Δenv_) + Δenv = unthunk(Δenv_) + return NoTangent(), Δenv.C, Δenv.Ea, Δenv.Eb + end + return CTMRGEnvTriangular(C, Ea, Eb), triangularenv_pullback +end + +# math basics used for tangent accumulation in backwards pass +function Base.:+(e₁::CTMRGEnvTriangular, e₂::CTMRGEnvTriangular) + return CTMRGEnvTriangular(e₁.C + e₂.C, e₁.Ea + e₂.Ea, e₁.Eb + e₂.Eb) +end +function Base.:-(e₁::CTMRGEnvTriangular, e₂::CTMRGEnvTriangular) + return CTMRGEnvTriangular(e₁.C - e₂.C, e₁.Ea - e₂.Ea, e₁.Eb - e₂.Eb) +end +Base.:*(α::Number, e::CTMRGEnvTriangular) = CTMRGEnvTriangular(α * e.C, α * e.Ea, α * e.Eb) + +# Custom adjoint for CTMRGEnvTriangular getproperty, to avoid creating named tuples in backward pass +function ChainRulesCore.rrule(::typeof(getproperty), e::CTMRGEnvTriangular, name::Symbol) + result = getproperty(e, name) + if name === :C + function C_pullback(ΔC_) + ΔC = unthunk(ΔC_) + return NoTangent(), CTMRGEnvTriangular(ΔC, zerovector(e.Ea), zerovector(e.Eb)) + end + return result, C_pullback + elseif name === :Ea + function Ea_pullback(ΔEa_) + ΔEa = unthunk(ΔEa_) + return NoTangent(), CTMRGEnvTriangular(zerovector(e.C), ΔEa, zerovector(e.Eb)) + end + return result, Ea_pullback + elseif name === :Eb + function Eb_pullback(ΔEb_) + ΔEb = unthunk(ΔEb_) + return NoTangent(), CTMRGEnvTriangular(zerovector(e.C), zerovector(e.Ea), ΔEb) + end + return result, Eb_pullback + else + # this should never happen because already errored in forwards pass + throw(ArgumentError("No rrule for getproperty of $name")) + end +end diff --git a/src/networks/infinitetriangularnetwork.jl b/src/networks/infinitetriangularnetwork.jl index 1383c83fe..d8eebdd34 100644 --- a/src/networks/infinitetriangularnetwork.jl +++ b/src/networks/infinitetriangularnetwork.jl @@ -154,21 +154,3 @@ function ChainRulesCore.rrule( end return O, getindex_pullback end - -# function ChainRulesCore.rrule(::typeof(rotl90), network::InfiniteTriangularNetwork) -# network´ = rotl90(network) -# function rotl90_pullback(Δnetwork_) -# Δnetwork = unthunk(Δnetwork_) -# return NoTangent(), rotr90(Δnetwork) -# end -# return network´, rotl90_pullback -# end - -# function ChainRulesCore.rrule(::typeof(rotr90), network::InfiniteTriangularNetwork) -# network´ = rotr90(network) -# function rotr90_pullback(Δnetwork) -# Δnetwork = unthunk(Δnetwork) -# return NoTangent(), rotl90(Δnetwork) -# end -# return network´, rotr90_pullback -# end diff --git a/src/states/infinitepepstriangular.jl b/src/states/infinitepepstriangular.jl index 07ab09a08..a8660e982 100644 --- a/src/states/infinitepepstriangular.jl +++ b/src/states/infinitepepstriangular.jl @@ -12,17 +12,7 @@ struct InfinitePEPSTriangular{T <: PEPSTensorTriangular} InfinitePEPSTriangular{T}(A::Matrix{T}) where {T <: PEPSTensorTriangular} = new{T}(A) function InfinitePEPSTriangular(A::Array{T, 2}) where {T <: PEPSTensorTriangular} bosonic_braiding = BraidingStyle(sectortype(T)) === Bosonic() - # for (d, w) in Tuple.(CartesianIndices(A)) - # (bosonic_braiding || !isdual(physicalspace(A[d, w]))) || - # throw(ArgumentError("Dual physical spaces for symmetry sectors with non-trivial twists are not allowed (for now).")) - # north_virtualspace(A[d, w]) == south_virtualspace(A[_prev(d, end), w])' || - # throw( - # SpaceMismatch("North virtual space at site $((d, w)) does not match.") - # ) - # east_virtualspace(A[d, w]) == west_virtualspace(A[d, _next(w, end)])' || - # throw(SpaceMismatch("East virtual space at site $((d, w)) does not match.")) - # dim(space(A[d, w])) > 0 || @warn "no fusion channels at site ($d, $w)" - # end + # TODO: check braiding and spaces return new{T}(A) end end @@ -55,7 +45,7 @@ function InfinitePEPSTriangular( SWspaces = adjoint.(NEspaces) Wspaces = adjoint.(circshift(Espaces, (0, 1))) - A = map(Pspaces, NWspaces, NEspaces, Espaces, SEspaces, SWspaces, Wspaces) do NW, NE, E, SE, SW, W + A = map(Pspaces, NWspaces, NEspaces, Espaces, SEspaces, SWspaces, Wspaces) do P, NW, NE, E, SE, SW, W return PEPSTensorTriangular(f, T, P, NW, NE, E, SE, SW, W) end @@ -76,13 +66,7 @@ The unit cell is labeled as a matrix which means that any tensor in the unit cel regardless if PEPS tensor or environment tensor, is obtained by shifting the row and column index `[r, c]` by one, respectively: ``` - | | | ----C[r-1,c-1]---T[r-1,c]---T[r-1,c+1]--- - | || || ----T[r,c-1]=====AA[r,c]====AA[r,c+1]==== - | || || ----T[r+1,c-1]===AA[r+1,c]==AA[r+1,c+1]== - | || || +TODO: diagram ``` The unit cell has periodic boundary conditions, so `[r, c]` is indexed modulo the size of the unit cell. @@ -217,27 +201,6 @@ function Base.isapprox(A₁::InfinitePEPSTriangular, A₂::InfinitePEPSTriangula end end -## Rotations - -# rotl60(A::InfinitePEPSTriangular) = InfinitePEPSTriangular(rotl60(_rotl60_localsandwich.(unitcell(A)))) -# rotr60(A::InfinitePEPSTriangular) = InfinitePEPSTriangular(rotr60(_rotl60_localsandwich.(unitcell(A)))) -# Base.rot180(A::InfinitePEPSTriangular) = InfinitePEPSTriangular(rot180(rot180.(unitcell(A)))) - -## FiniteDifferences vectorization - -""" - to_vec(A::InfinitePEPSTriangular) -> vec, state_from_vec - -Vectorize an `InfinitePEPSTriangular` into a vector of real numbers. A vectorized infinite PEPS can -retrieved again as an `InfinitePEPSTriangular` by application of the `state_from_vec` map. -""" -function FiniteDifferences.to_vec(A::InfinitePEPSTriangular) - vec, back = FiniteDifferences.to_vec(unitcell(A)) - function state_from_vec(vec) - return NWType(back(vec)) - end - return vec, state_from_vec -end ## Chainrules diff --git a/test/ctmrg/triangular.jl b/test/ctmrg/triangular.jl index 2e1f2ffcd..45741c4e5 100644 --- a/test/ctmrg/triangular.jl +++ b/test/ctmrg/triangular.jl @@ -1,6 +1,5 @@ using Test using Random -using MatrixAlgebraKit using TensorKit using MPSKit using PEPSKit @@ -9,6 +8,17 @@ using Zygote using LinearAlgebra using KrylovKit +sd = 123456 +Random.seed!(sd) + +D = 2 +χ = 7 +T = ComplexF64 + +ctmrg_tol = 1.0e-8 +ctmrg_maxiter = 300 +ctmrg_verbosity = 2 + const ising_βc_triangular = BigFloat(BigFloat(asinh(BigFloat(sqrt(BigFloat(1.0) / BigFloat(3.0))))) / BigFloat(2.0)) const f_onsager_triangular::BigFloat = -3.20253248660790791834355252025862951439 @@ -29,129 +39,162 @@ function classical_ising_triangular(β) return TensorMap(o2, ℂ^2 * ℂ^2 * ℂ^2, ℂ^2 * ℂ^2 * ℂ^2) end -# @testset "CTM_triangular - Random tensor" begin -# for conditioning in [true false] -# for projector_alg in [:twothirds :full] -# Random.seed!(79413165445) -# alg = SimultaneousCTMRGTriangular(; maxiter = 300, conditioning, projector_alg) -# eltype = ComplexF64 -# χ = 7 -# pspace = ℂ^2 -# vspace = ℂ^3 -# envspace = ℂ^χ - -# ket = randn(eltype, pspace, vspace ⊗ vspace ⊗ vspace ⊗ vspace' ⊗ vspace' ⊗ vspace') -# bra = copy(ket) -# pf = randn(eltype, vspace ⊗ vspace ⊗ vspace, vspace ⊗ vspace ⊗ vspace) -# sandwiches = [pf, (ket, bra)] -# # sandwiches = [(ket, bra), pf] -# unitcell = (2, 2) - -# for (sandwich, vspace) in zip(sandwiches, [vspace, vspace ⊗ vspace']) -# # for (sandwich, vspace) in zip(sandwiches, [vspace ⊗ vspace', vspace]) -# network = InfiniteTriangularNetwork(fill(sandwich, unitcell)) -# env₀ = CTMRGEnvTriangular(randn, eltype, vspace, envspace; unitcell) -# env, info = leading_boundary(env₀, network, alg) -# @test info.convergence_metric < 1.0 -# end -# end -# end -# end - -# @testset "CTM_triangular - Classical Ising" begin -# for conditioning in [true false] -# for projector_alg in [:twothirds :full] -# Random.seed!(156484561351) - -# alg = SimultaneousCTMRGTriangular(; maxiter = 300, conditioning, projector_alg) -# unitcell = (1, 1) -# T = classical_ising_triangular(ising_βc_triangular) -# pf = InfiniteTriangularNetwork(fill(T, unitcell)) - -# χ = 20 -# vspace = codomain(T)[1] -# envspace = ℂ^χ -# eltype = Float64 -# env₀ = CTMRGEnvTriangular(randn, eltype, vspace, envspace; unitcell) -# env, info = leading_boundary(env₀, pf, alg) - -# nw_value = network_value(pf, env) -# lz = real(log(nw_value)) -# fs = lz * -1 / ising_βc_triangular -# @test fs ≈ f_onsager_triangular rtol = 1.0e-4 -# end -# end -# end - -@testset "CTM_triangular - Differentiability" begin +@testset "CTM_triangular - Random tensor" begin for conditioning in [true false] for projector_alg in [:twothirds :full] - ctm_alg = SimultaneousCTMRGTriangular(; conditioning, projector_alg) - T = ComplexF64 - S = Trivial - χ = 7 - pspace = ℂ^2 - - J = 1.0 - g = 2.5 - - ZZ = rmul!(PEPSKit.σᶻᶻ(T, S), -J) - X = rmul!(PEPSKit.σˣ(T, S), g * -J) + Random.seed!(79413165445) + alg = SimultaneousCTMRGTriangular(; + tol = ctmrg_tol, maxiter = ctmrg_maxiter, verbosity = ctmrg_verbosity, + conditioning, projector_alg + ) + pspace = ComplexSpace(2) + vspace = ComplexSpace(D) + envspace = ComplexSpace(χ) - vspace = ℂ^3 - envspace = ℂ^χ ket = randn(T, pspace, vspace ⊗ vspace ⊗ vspace ⊗ vspace' ⊗ vspace' ⊗ vspace') bra = copy(ket) + pf = randn(T, vspace ⊗ vspace ⊗ vspace, vspace ⊗ vspace ⊗ vspace) + sandwiches = [pf, (ket, bra)] + # sandwiches = [(ket, bra), pf] + unitcell = (2, 2) + + for (sandwich, V) in zip(sandwiches, [vspace, vspace ⊗ vspace']) + # for (sandwich, vspace) in zip(sandwiches, [vspace ⊗ vspace', vspace]) + network = InfiniteTriangularNetwork(fill(sandwich, unitcell)) + env₀ = CTMRGEnvTriangular(randn, T, V, envspace; unitcell) + env, info = leading_boundary(env₀, network, alg) + @test info.convergence_metric < 1.0 # this is not much of a test + end + end + end +end - unitcell = (1, 1) - network = InfiniteTriangularNetwork(fill((ket, bra), unitcell)) - env₀ = CTMRGEnvTriangular(randn, T, vspace ⊗ vspace', envspace; unitcell) +@testset "CTM_triangular - Classical Ising" begin + χ_local = 20 + T_local = Float64 - env, info = leading_boundary(env₀, network, ctm_alg) - E₀ = PEPSKit.energy(network, env, X, ZZ) - optimizer_alg = LBFGS(4; maxiter = 10) - real_inner(_, η₁, η₂) = real(dot(η₁, η₂)) - reuse_env = true - peps₀ = InfinitePEPSTriangular(copy(ket)) + for conditioning in [true false] + for projector_alg in [:twothirds :full] + Random.seed!(156484561351) + + alg = SimultaneousCTMRGTriangular(; + tol = ctmrg_tol, maxiter = ctmrg_maxiter, verbosity = ctmrg_verbosity, + conditioning, projector_alg + ) + sz = (1, 1) + T = classical_ising_triangular(ising_βc_triangular) + pf = InfiniteTriangularNetwork(fill(T, sz)) + + vspace = codomain(T)[1] + envspace = ComplexSpace(χ_local) + env₀ = CTMRGEnvTriangular(randn, T_local, vspace, envspace; unitcell = sz) + env, info = leading_boundary(env₀, pf, alg) + + nw_value = network_value(pf, env) + lz = real(log(nw_value)) + fs = lz * -1 / ising_βc_triangular + @test fs ≈ f_onsager_triangular rtol = 1.0e-4 + end + end +end - function peps_retract(x, η, α) - peps = x[1] - env = deepcopy(x[2]) - retractions = norm_preserving_retract.(unitcell(peps), unitcell(η), α) - peps´ = InfinitePEPS(map(first, retractions)) - ξ = InfinitePEPS(map(last, retractions)) +## Gradient tests for triangular CTMRG +# ------------------------------------ + +Pspaces = [ComplexSpace(2)] +Vspaces = [ComplexSpace(χbond)] +Espaces = [ComplexSpace(χenv)] +# TODO: actually add triangular lattice models... +models = [transverse_field_ising(InfiniteSquare(); J = 1.0, g = 1.0)] +names = ["Heisenberg"] + +gradtol = 1.0e-4 +ctmrg_verbosity = 0 +ctmrg_algs = [[:triangular]] +projector_algs = [[:twothirds, :full]] +conditionings = [[true, false]] +# svd_rrule_algs = nothing # TODO: allow this +gradient_algs = [[nothing]] +# gradient_iterschemes = [[:fixed, :diffgauge]] # TODO: support this +steps = -0.01:0.005:0.01 + +# have to use a custom retraction, since the state type of hardcoded... +function peps_triangular_retract(x, η, α) + peps = x[1] + env = deepcopy(x[2]) + + retractions = norm_preserving_retract.(unitcell(peps), unitcell(η), α) + peps´ = typeof(peps)(map(first, retractions)) + ξ = typeof(peps)(map(last, retractions)) + + return (peps´, env), ξ +end - return (peps´, env), ξ - end - retract = peps_retract - - # alg = PEPSOptimize() - gradtol = 1.0e-3 - gradient_alg = EigSolver(; solver_alg = Arnoldi(; tol = gradtol, eager = true), iterscheme = :fixed) - # optimize operator cost function - (peps_final, env_final), cost_final, ∂cost, numfg, convergence_history = optimize( - (peps₀, env₀), optimizer_alg; - retract, inner = real_inner, - ) do (peps, env) - start_time = time_ns() - E, gs = withgradient(peps) do ψ - env′, info = PEPSKit.hook_pullback( - leading_boundary, env, ψ, ctm_alg; - alg_rrule = gradient_alg, - ) - # ignore_derivatives() do - # reuse_env && update!(env, env′) - # push!(truncation_errors, info.truncation_error) - # push!(condition_numbers, info.condition_number) - # end - return energy(ψ, env′, operator_onesite, operator_twosite) - end - g = only(gs) # `withgradient` returns tuple of gradients `gs` - push!(gradnorms_unitcell, norm.(g.A)) - push!(times, (time_ns() - start_time) * 1.0e-9) - return E, g + +@testset "Triangular CTMRG energy gradient test for $(names[i]) model" verbose = true for i in + eachindex( + models + ) + Pspace = Pspaces[i] + Vspace = Vspaces[i] + Espace = Espaces[i] + # calgs = ctmrg_algs[i] + palgs = projector_algs[i] + conds = conditionings[i] + # salgs = svd_rrule_algs[i] + galgs = gradient_algs[i] + # gischemes = gradient_iterschemes[i] + + # TODO: allow passing actual LocalOperator + H = models[i] + O1 = last(first(filter(t -> length(first(t)) == 1, H.terms))) + O2 = last(first(filter(t -> length(first(t)) == 2, H.terms))) + + @testset "ctmrg_alg=:simultaneous_triangular, projector_alg=:$projector_alg, conditioning=$conditioning, gradient_alg=:$gradient_alg" for ( + projector_alg, conditioning, gradient_alg, + ) in Iterators.product(palgs, conds, galgs) + + @info "optimtest of ctmrg_alg=:simultaneous_triangular, projector_alg=:$projector_alg, conditioning=$conditioning, gradient_alg=:$gradient_alg on $(names[i])" + Random.seed!(42039482030) + dir = InfinitePEPSTriangular(Pspace, Vspace) + psi = InfinitePEPSTriangular(Pspace, Vspace) + + # TODO: actually go through CTMRGAlgorithm selection + contrete_ctmrg_alg = SimultaneousCTMRGTriangular(; + projector_alg, conditioning, verbosity = ctmrg_verbosity, + trunctype = :FixedSpaceTruncation + ) + # instantiate because hook_pullback doesn't go through the keyword selector... + concrete_gradient_alg = if isnothing(gradient_alg) + nothing # TODO: add this to the PEPSKit.GradMode selector? + else + PEPSKit.GradMode(; alg = gradient_alg, tol = gradtol, iterscheme = gradient_iterscheme) + end + env, = leading_boundary( + CTMRGEnvTriangular(randn, ComplexF64, Vspace ⊗ Vspace', Espace), + psi, contrete_ctmrg_alg + ) + alphas, fs, dfs1, dfs2 = OptimKit.optimtest( + (psi, env), + dir; + alpha = steps, + retract = peps_triangular_retract, + inner = PEPSKit.real_inner, + ) do (peps, env) + E, g = Zygote.withgradient(peps) do psi + env2, = PEPSKit.hook_pullback( + leading_boundary, + env, + psi, + contrete_ctmrg_alg; + alg_rrule = concrete_gradient_alg, + ) + return PEPSKit.energy(psi, env2, O1, O2) end + + return E, only(g) end + @test dfs1 ≈ dfs2 atol = 1.0e-2 end end From ecaf5e8dd74fe546246bff891de30a8c4d278c2a Mon Sep 17 00:00:00 2001 From: leburgel Date: Mon, 2 Mar 2026 15:28:44 +0100 Subject: [PATCH 08/41] Fix renaming typo --- test/ctmrg/triangular.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/ctmrg/triangular.jl b/test/ctmrg/triangular.jl index 45741c4e5..b451dbb8f 100644 --- a/test/ctmrg/triangular.jl +++ b/test/ctmrg/triangular.jl @@ -103,8 +103,8 @@ end # ------------------------------------ Pspaces = [ComplexSpace(2)] -Vspaces = [ComplexSpace(χbond)] -Espaces = [ComplexSpace(χenv)] +Vspaces = [ComplexSpace(D)] +Espaces = [ComplexSpace(χ)] # TODO: actually add triangular lattice models... models = [transverse_field_ising(InfiniteSquare(); J = 1.0, g = 1.0)] names = ["Heisenberg"] From 7e59999124d3daaf9112e1e1312eb800832e2d6a Mon Sep 17 00:00:00 2001 From: leburgel Date: Tue, 3 Mar 2026 07:38:12 +0100 Subject: [PATCH 09/41] Add missing imports --- test/ctmrg/triangular.jl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/ctmrg/triangular.jl b/test/ctmrg/triangular.jl index b451dbb8f..554a4a793 100644 --- a/test/ctmrg/triangular.jl +++ b/test/ctmrg/triangular.jl @@ -8,6 +8,8 @@ using Zygote using LinearAlgebra using KrylovKit +using PEPSKit: unitcell, norm_preserving_retract + sd = 123456 Random.seed!(sd) From ad54df8d8da1cf5df934870b7b89f992e4f1daad Mon Sep 17 00:00:00 2001 From: sanderdemeyer Date: Tue, 27 Oct 2026 16:30:15 +0100 Subject: [PATCH 10/41] fix merge conflict fixedpoint --- .../fixed_point_differentiation.jl | 209 +++++++++++++----- 1 file changed, 149 insertions(+), 60 deletions(-) diff --git a/src/algorithms/optimization/fixed_point_differentiation.jl b/src/algorithms/optimization/fixed_point_differentiation.jl index cb2436d9e..5f9dcb2d1 100644 --- a/src/algorithms/optimization/fixed_point_differentiation.jl +++ b/src/algorithms/optimization/fixed_point_differentiation.jl @@ -200,6 +200,41 @@ EigSolver(; kwargs...) = GradMode(; alg = :eigsolver, kwargs...) GRADIENT_MODE_SYMBOLS[:eigsolver] = EigSolver +""" + _check_algorithm_combination(boundary_alg, gradient_alg_or_symmetrization) + _check_algorithm_combination(boundary_alg, gradient_alg, symmetrization) + +Check for allowed combinations of gradient algorithm and boundary algorithm to be used for +computing the gradient of a `leading_boundary` call. Throws an error containing a +recommended fix if the combination is not allowed or broken. +""" +function _check_algorithm_combination(boundary_alg, gradient_alg_or_symmetrization) end +function _check_algorithm_combination(boundary_alg, gradient_alg, symmetrization) + _check_algorithm_combination(boundary_alg, gradient_alg) + _check_algorithm_combination(boundary_alg, symmetrization) + return nothing +end +function _check_algorithm_combination(::SequentialCTMRG, ::GradMode{:fixed}) + msg = "`:fixed` mode is not compatible with `SequentialCTMRG` since the sequential \ + application of SVDs does not allow to differentiate through a fixed set of \ + gauges; select SimultaneousCTMRG instead to use :fixed mode" + throw(ArgumentError(msg)) +end +function _check_algorithm_combination(::C4vCTMRG{<:C4vEighProjector}, ::GradMode{:diffgauge}) + msg = "`:diffgauge` mode is currently not compatible with eigh-based C4v CTMRG; \ + either switch to a different projector algorithm (e.g. `c4v_qr`), or use :fixed \ + mode for differentiation instead." + throw(ArgumentError(msg)) +end +function _check_algorithm_combination(::C4vCTMRG, symm::Union{Nothing, <:SymmetrizationStyle}) + if !(symm isa RotateReflect) + msg = "C4vCTMRG optimization is compatible only with RotateReflect symmetrization. \ + Make sure to set `symmetrization = RotateReflect()`." + throw(ArgumentError(msg)) + end + return nothing +end + #= Evaluating the gradient of the cost function for CTMRG: - The gradient of the cost function for CTMRG can be computed using automatic differentiation (AD) or explicit evaluation of the geometric sum. @@ -207,6 +242,14 @@ Evaluating the gradient of the cost function for CTMRG: - With explicit evaluation of the geometric sum, the gradient is computed by differentiating the cost function with the environment kept fixed, and then manually adding the gradient contributions from the environments. =# +_scrambling_env_gauge(::CTMRGAlgorithm) = ScramblingEnvGauge() +_scrambling_env_gauge(::C4vCTMRG) = ScramblingEnvGaugeC4v() + +function _set_fixed_truncation(alg::CTMRGAlgorithm) + alg_fixed = @set alg.projector_alg = _set_truncation(alg.projector_alg, FixedSpaceTruncation()) + return alg_fixed +end + function _rrule( gradmode::GradMode{:diffgauge}, config::RuleConfig, @@ -215,21 +258,26 @@ function _rrule( state, alg::CTMRGAlgorithm, ) + _check_algorithm_combination(alg, gradmode) + env, info = leading_boundary(envinit, state, alg) - alg_fixed = @set alg.projector_alg.trunc = FixedSpaceTruncation() # fix spaces during differentiation + alg_fixed = _set_fixed_truncation(alg) # fix spaces during differentiation + alg_gauge = _scrambling_env_gauge(alg) # TODO: make this a field in GradMode? + + # prepare iterating function corresponding to a single gauge-fixed CTMRG iteration + function f(A, x) + return gauge_fix(ctmrg_iteration(InfiniteSquareNetwork(A), x, alg_fixed)[1], x, alg_gauge)[1] + end + # compute its pullback + _, env_vjp = rrule_via_ad(config, f, state, env) + # split off state and environment parts + ∂f∂A(x)::typeof(state) = env_vjp(x)[2] + ∂f∂x(x)::typeof(env) = env_vjp(x)[3] function leading_boundary_diffgauge_pullback((Δenv′, Δinfo)) Δenv = unthunk(Δenv′) - # find partial gradients of gauge-fixed single CTMRG iteration - function f(A, x) - return gauge_fix(x, ctmrg_iteration(InfiniteSquareNetwork(A), x, alg_fixed)[1])[1] - end - _, env_vjp = rrule_via_ad(config, f, state, env) - # evaluate the geometric sum - ∂f∂A(x)::typeof(state) = env_vjp(x)[2] - ∂f∂x(x)::typeof(env) = env_vjp(x)[3] ∂F∂env = fpgrad(Δenv, ∂f∂x, ∂f∂A, Δenv, gradmode) return NoTangent(), ZeroTangent(), ∂F∂env, NoTangent() @@ -238,6 +286,18 @@ function _rrule( return (env, info), leading_boundary_diffgauge_pullback end +function _rrule( + gradmode::GradMode, + config::RuleConfig, + ::typeof(MPSKit.leading_boundary), + envinit, + state, + alg::CTMRGAlgorithmTriangular, +) + msg = "Fixed-point differentiation is not available for `CTMRGAlgorithmTriangular`, \ + since gauge fixing for triangular CTMRG is currently not implemented." + throw(ArgumentError(msg)) +end # Here f is differentiated from an pre-computed SVD with fixed U, S and V function _rrule( gradmode::GradMode{:fixed}, @@ -245,73 +305,66 @@ function _rrule( ::typeof(MPSKit.leading_boundary), envinit, state, - alg::SimultaneousCTMRG, + alg::CTMRGAlgorithm, ) + _check_algorithm_combination(alg, gradmode) + env, = leading_boundary(envinit, state, alg) - alg_fixed = @set alg.projector_alg.trunc = FixedSpaceTruncation() # fix spaces during differentiation + alg_fixed = _set_fixed_truncation(alg) # fix spaces during differentiation + alg_gauge = _scrambling_env_gauge(alg) # TODO: make this a field in GradMode? env_conv, info = ctmrg_iteration(InfiniteSquareNetwork(state), env, alg_fixed) - env_fixed, signs = gauge_fix(env, env_conv) + _, signs = gauge_fix(env_conv, env, alg_gauge) - # Fix SVD - svd_alg_fixed = _fix_svd_algorithm(alg.projector_alg.svd_alg, signs, info) - alg_fixed = @set alg.projector_alg.svd_alg = svd_alg_fixed - alg_fixed = @set alg_fixed.projector_alg.trunc = notrunc() + # fix decomposition + alg_fixed = gauge_fix(alg, signs, info) + + # prepare iterating function corresponding to a single CTMRG iteration with a + # gauge-fixed projector + function f(A, x) + return fix_global_phases( + ctmrg_iteration(InfiniteSquareNetwork(A), x, alg_fixed)[1], x, + ) + end + # prepare its pullback + _, env_vjp = rrule_via_ad(config, f, state, env) + # split off state and environment parts + ∂f∂A(x)::typeof(state) = env_vjp(x)[2] + ∂f∂x(x)::typeof(env) = env_vjp(x)[3] function leading_boundary_fixed_pullback((Δenv′, Δinfo)) Δenv = unthunk(Δenv′) - function f(A, x) - return fix_global_phases( - x, ctmrg_iteration(InfiniteSquareNetwork(A), x, alg_fixed)[1] - ) - end - _, env_vjp = rrule_via_ad(config, f, state, env_fixed) - # evaluate the geometric sum - ∂f∂A(x)::typeof(state) = env_vjp(x)[2] - ∂f∂x(x)::typeof(env) = env_vjp(x)[3] ∂F∂env = fpgrad(Δenv, ∂f∂x, ∂f∂A, Δenv, gradmode) return NoTangent(), ZeroTangent(), ∂F∂env, NoTangent() end - return (env_fixed, info), leading_boundary_fixed_pullback -end - -function _rrule( - gradmode::GradMode, - config::RuleConfig, - ::typeof(MPSKit.leading_boundary), - envinit, - state, - alg::CTMRGAlgorithmTriangular, - ) - msg = "Fixed-point differentiation is not available for `CTMRGAlgorithmTriangular`, \ - since gauge fixing for triangular CTMRG is currently not implemented." - throw(ArgumentError(msg)) + return (env, info), leading_boundary_fixed_pullback end -function _fix_svd_algorithm(alg::SVDAdjoint, signs, info) +function gauge_fix(alg::SVDAdjoint, signs, info) # embed gauge signs in larger space to fix gauge of full U and V on truncated subspace rowsize, colsize = size(signs, 2), size(signs, 3) - signs_full = map(Iterators.product(1:4, 1:rowsize, 1:colsize)) do (dir, r, c) - σ = signs[dir, r, c] - r_sign, c_sign = if dir == NORTH # take unit cell interdependency of signs into account - r, _prev(c, colsize) + inds = info.truncation_indices + signs_full = map(Iterators.product(1:4, 1:rowsize, 1:colsize)) do (dir, row, col) + σ = signs[dir, row, col] + row_sign, col_sign = if dir == NORTH # take unit cell interdependency of signs into account + row, _prev(col, colsize) elseif dir == EAST - _prev(r, rowsize), c + _prev(row, rowsize), col elseif dir == SOUTH - r, _next(c, colsize) + row, _next(col, colsize) elseif dir == WEST - _next(r, rowsize), c + _next(row, rowsize), col end - extended_space = domain(info.U_full[dir, r_sign, c_sign]) ← codomain(info.V_full[dir, r_sign, c_sign]) - extended_σ = zeros(scalartype(σ), extended_space) - for (c, b) in blocks(extended_σ) - σc = block(σ, c) - kept_dim = size(σc, 1) - b[diagind(b)] .= one(scalartype(σ)) # put ones on the diagonal - b[1:kept_dim, 1:kept_dim] .= σc # set to σ on kept subspace + + ind = inds[dir, row_sign, col_sign] + extended_σ = id(scalartype(σ), domain(info.S_full[dir, row_sign, col_sign])) + for (c, b) in blocks(σ) + I = get(ind, c, nothing) + @assert !isnothing(I) + block(extended_σ, c)[I, I] = b end return extended_σ end @@ -320,15 +373,51 @@ function _fix_svd_algorithm(alg::SVDAdjoint, signs, info) U_fixed, V_fixed = fix_relative_phases(info.U, info.V, signs) U_full_fixed, V_full_fixed = fix_relative_phases(info.U_full, info.V_full, signs_full) return SVDAdjoint(; - fwd_alg = FixedSVD(U_fixed, info.S, V_fixed, U_full_fixed, info.S_full, V_full_fixed), + fwd_alg = FixedSVD(U_fixed, info.S, V_fixed, U_full_fixed, info.S_full, V_full_fixed, inds), rrule_alg = alg.rrule_alg, ) end -function _fix_svd_algorithm(alg::SVDAdjoint{F}, signs, info) where {F <: IterSVD} +function gauge_fix(alg::SVDAdjoint{F}, signs, info) where {F <: IterSVD} # fix kept U and V only since iterative SVD doesn't have access to full spectrum U_fixed, V_fixed = fix_relative_phases(info.U, info.V, signs) return SVDAdjoint(; - fwd_alg = FixedSVD(U_fixed, info.S, V_fixed, nothing, nothing, nothing), + fwd_alg = FixedSVD(U_fixed, info.S, V_fixed, nothing, nothing, nothing, nothing), + rrule_alg = alg.rrule_alg, + ) +end +function gauge_fix(alg::EighAdjoint, signs, info) + σ = signs[1] + inds = info.truncation_indices + + # embed gauge signs in larger space to fix gauge of full V on truncated subspace + extended_σ = id(scalartype(σ), domain(info.D_full)) + for (c, b) in blocks(σ) + I = get(inds, c, nothing) + @assert !isnothing(I) + block(extended_σ, c)[I, I] = b + end + + # fix kept and full V + V_fixed = info.V * σ' + V_full_fixed = info.V_full * extended_σ' + return EighAdjoint(; + fwd_alg = FixedEig(info.D, V_fixed, info.D_full, V_full_fixed, inds), + rrule_alg = alg.rrule_alg, + ) +end +function gauge_fix(alg::EighAdjoint{F}, signs, info) where {F <: IterEigh} + # fix kept V only since iterative decomposition doesn't have access to full spectrum + V_fixed = info.V * signs[1]' + return EighAdjoint(; + fwd_alg = FixedEig(info.D, V_fixed, nothing, nothing, nothing), + rrule_alg = alg.rrule_alg, + ) +end +function gauge_fix(alg::QRAdjoint, signs, info) + Q_fixed = info.Q * signs[1]' + R_fixed = signs[1] * info.R + return QRAdjoint(; + fwd_alg = FixedQR(Q_fixed, R_fixed), rrule_alg = alg.rrule_alg, ) end @@ -409,7 +498,7 @@ end function fpgrad(∂F∂x, ∂f∂x, ∂f∂A, x₀, alg::EigSolver) function f(X) y = ∂f∂x(X[1]) - return (y + X[2] * ∂F∂x, X[2]) + return (VI.add!!(y, ∂F∂x, X[2]), X[2]) end X₀ = (x₀, one(scalartype(x₀))) _, vecs, info = realeigsolve(f, X₀, 1, :LM, alg.solver_alg) @@ -431,8 +520,8 @@ function fpgrad(∂F∂x, ∂f∂x, ∂f∂A, x₀, alg::EigSolver) ) return fpgrad(∂F∂x, ∂f∂x, ∂f∂A, x₀, backup_ls_alg) else - y = scale(vecs[1][1], 1 / vecs[1][2]) + y = VI.scale!!(vecs[1][1], inv(vecs[1][2])) end return ∂f∂A(y) -end +end \ No newline at end of file From 41d017bdad390b133f5d2d90cd5cdfb7486b41d2 Mon Sep 17 00:00:00 2001 From: sanderdemeyer Date: Tue, 27 Oct 2026 16:34:05 +0100 Subject: [PATCH 11/41] Update PEPSKit.jl --- src/PEPSKit.jl | 62 ++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 57 insertions(+), 5 deletions(-) diff --git a/src/PEPSKit.jl b/src/PEPSKit.jl index 0bdf99147..b43b18cf1 100644 --- a/src/PEPSKit.jl +++ b/src/PEPSKit.jl @@ -23,7 +23,6 @@ using KrylovKit using KrylovKit: Lanczos, BlockLanczos using TensorOperations, OptimKit -import OptimKit: fixedpoint using ChainRulesCore, Zygote import TupleTools @@ -35,9 +34,20 @@ using MPSKit.DynamicTols: adapt_solver using MPSKit: @log_initialization, @log_iteration, @log_convergence, @log_nonconvergence using MPSKit.IterativeLoggers: with_verbosity import MPSKit.DynamicTols: _updatetol +using MPSKit: DynamicTol +using MPSKit.DynamicTols: adapt_solver +using MPSKit: @log_initialization, @log_iteration, @log_convergence, @log_nonconvergence +using MPSKit.IterativeLoggers: with_verbosity +import MPSKit.DynamicTols: _updatetol import MPSKit: tensorexpr, leading_boundary, loginit!, logiter!, logfinish!, logcancel!, physicalspace import MPSKit: infinite_temperature_density_matrix +using TensorKitTensors: fuse_charge +import TensorKitTensors.SpinOperators as SO +import TensorKitTensors.FermionOperators as FO +import TensorKitTensors.HubbardOperators as HO +import TensorKitTensors.TJOperators as TJ +import TensorKitTensors.BosonOperators as BO using TensorKitTensors: fuse_charge import TensorKitTensors.SpinOperators as SO import TensorKitTensors.FermionOperators as FO @@ -45,6 +55,9 @@ import TensorKitTensors.HubbardOperators as HO import TensorKitTensors.TJOperators as TJ import TensorKitTensors.BosonOperators as BO using MPSKitModels +import MPSKitModels: + transverse_field_ising, heisenberg_XYZ, heisenberg_XXZ, + hubbard_model, bose_hubbard_model, tj_model import MPSKitModels: transverse_field_ising, heisenberg_XYZ, heisenberg_XXZ, hubbard_model, bose_hubbard_model, tj_model @@ -57,17 +70,22 @@ include("Defaults.jl") # Include first to allow for docstring interpolation wit include("utility/util.jl") include("utility/contraction_labels.jl") include("utility/tensor_traces.jl") +include("utility/contraction_labels.jl") +include("utility/tensor_traces.jl") include("utility/indexing.jl") include("utility/diffable_threads.jl") include("utility/twistdual.jl") +include("utility/twistdual.jl") include("utility/eigh.jl") include("utility/svd.jl") include("utility/orth.jl") +include("utility/orth.jl") include("utility/rotations.jl") include("utility/hook_pullback.jl") include("utility/autoopt.jl") include("utility/retractions.jl") include("utility/initialization.jl") +include("utility/initialization.jl") include("networks/tensors.jl") include("networks/local_sandwich.jl") @@ -88,6 +106,9 @@ include("operators/localoperator.jl") include("operators/localoperator/dense.jl") include("operators/localoperator/tensorproduct.jl") include("operators/localoperator/mpo.jl") +include("operators/localoperator/dense.jl") +include("operators/localoperator/tensorproduct.jl") +include("operators/localoperator/mpo.jl") include("operators/localcircuit.jl") include("operators/lattices/squarelattice.jl") include("operators/models.jl") @@ -98,6 +119,7 @@ include("environments/vumps_environments.jl") include("environments/suweight.jl") include("environments/bp_environments.jl") include("environments/product_state_environments.jl") +include("environments/product_state_environments.jl") include("algorithms/contractions/ctmrg/types.jl") include("algorithms/contractions/ctmrg/expr.jl") @@ -109,9 +131,12 @@ include("algorithms/contractions/ctmrg/fullinf_env.jl") include("algorithms/contractions/ctmrg/renormalize_corner.jl") include("algorithms/contractions/ctmrg/renormalize_edge.jl") include("algorithms/contractions/ctmrg/network_value.jl") +include("algorithms/contractions/ctmrg/network_value.jl") include("algorithms/contractions/ctmrg/gaugefix.jl") include("algorithms/contractions/ctmrg/characteristic_equations.jl") +include("algorithms/contractions/ctmrg/characteristic_equations.jl") +include("algorithms/contractions/absorb.jl") include("algorithms/contractions/absorb.jl") include("algorithms/contractions/absorb_weight.jl") include("algorithms/contractions/transfer.jl") @@ -123,12 +148,20 @@ include("algorithms/contractions/local_patch/patch_contractions.jl") include("algorithms/contractions/local_patch/densitymatrix/generic.jl") include("algorithms/contractions/local_patch/densitymatrix/ctmrg.jl") include("algorithms/contractions/local_patch/densitymatrix/bp.jl") +include("algorithms/contractions/bp_messages.jl") +include("algorithms/contractions/local_patch/expr_utils.jl") +include("algorithms/contractions/local_patch/network_expr.jl") +include("algorithms/contractions/local_patch/patch_contractions.jl") +include("algorithms/contractions/local_patch/densitymatrix/generic.jl") +include("algorithms/contractions/local_patch/densitymatrix/ctmrg.jl") +include("algorithms/contractions/local_patch/densitymatrix/bp.jl") include("algorithms/contractions/bondenv/benv_tools.jl") include("algorithms/contractions/bondenv/gaugefix.jl") include("algorithms/contractions/bondenv/als_solve.jl") include("algorithms/contractions/bondenv/benv_ctm.jl") include("algorithms/contractions/correlator/peps.jl") include("algorithms/contractions/correlator/pepo_purified.jl") +include("algorithms/contractions/correlator/pepo_purified.jl") include("algorithms/contractions/correlator/pepo_1layer.jl") include("algorithms/ctmrg/sparse_environments.jl") @@ -138,16 +171,24 @@ include("algorithms/ctmrg/projectors/halfinfinite.jl") include("algorithms/ctmrg/projectors/fullinfinite.jl") include("algorithms/ctmrg/projectors/c4v_eigh.jl") include("algorithms/ctmrg/projectors/c4v_qr.jl") +include("algorithms/ctmrg/projectors/projectors.jl") +include("algorithms/ctmrg/projectors/halfinfinite.jl") +include("algorithms/ctmrg/projectors/fullinfinite.jl") +include("algorithms/ctmrg/projectors/c4v_eigh.jl") +include("algorithms/ctmrg/projectors/c4v_qr.jl") include("algorithms/ctmrg/simultaneous.jl") include("algorithms/ctmrg/sequential.jl") include("algorithms/ctmrg/gaugefix.jl") include("algorithms/ctmrg/c4v.jl") include("algorithms/ctmrg/initialization.jl") +include("algorithms/ctmrg/initialization.jl") include("algorithms/truncation/truncationschemes.jl") include("algorithms/truncation/fullenv_truncation.jl") include("algorithms/truncation/bond_truncation.jl") +include("algorithms/compress/local.jl") + include("algorithms/contractions/ctmrg_contractions_triangular.jl") include("algorithms/ctmrg_triangular/ctmrg.jl") include("algorithms/ctmrg_triangular/utility.jl") @@ -179,10 +220,17 @@ include("algorithms/expectation_value/patch_contractions.jl") include("algorithms/expectation_value/network_value.jl") include("algorithms/expectation_value/correlator_adapters.jl") include("algorithms/expectation_value/correlators.jl") +include("algorithms/expectation_value/expectation_value.jl") +include("algorithms/expectation_value/reduced_densitymatrix.jl") +include("algorithms/expectation_value/patch_contractions.jl") +include("algorithms/expectation_value/network_value.jl") +include("algorithms/expectation_value/correlator_adapters.jl") +include("algorithms/expectation_value/correlators.jl") include("algorithms/toolbox.jl") include("algorithms/toolbox_triangular.jl") -include("algorithms/correlators.jl") +include("algorithms/optimization/implicit_differentiation.jl") +include("algorithms/optimization/preconditioning.jl") include("algorithms/optimization/implicit_differentiation.jl") include("algorithms/optimization/preconditioning.jl") include("algorithms/optimization/peps_optimization.jl") @@ -192,12 +240,12 @@ include("algorithms/select_algorithm.jl") using .Defaults: set_scheduler! export set_scheduler! export EighAdjoint, IterEigh, SVDAdjoint, IterSVD, OrthAdjoint, QRAdjoint +export EighAdjoint, IterEigh, SVDAdjoint, IterSVD, OrthAdjoint, QRAdjoint export CTMRGEnv, SequentialCTMRG, SimultaneousCTMRG -export CTMRGTriaEnv, SimultaneousCTMRGTria +export CTMRGEnvTriangular, SimultaneousCTMRGTriangular # CTMRGTriaEnv, SimultaneousCTMRGTria export initialize_ctmrg_environment, RandomInitialization, ProductStateInitialization, ApplicationInitialization, IdentityInitialization export corner, edge, setcorner!, setedge! -export CTMRGEnvTriangular, SimultaneousCTMRGTriangular # CTMRGTriaEnv, SimultaneousCTMRGTria export FixedSpaceTruncation, SiteDependentTruncation export HalfInfiniteProjector, FullInfiniteProjector export C4vCTMRG, C4vEighProjector, C4vQRProjector @@ -208,11 +256,15 @@ export reduced_densitymatrix, expectation_value, network_value, cost_function export correlator, correlation_length export leading_boundary export PEPSOptimize, FixedPointGradient, GeomSum, ManualIter, ImplicitGradient +export PEPSOptimize, FixedPointGradient, GeomSum, ManualIter, ImplicitGradient export fixedpoint export LocalTruncation export compress +export LocalTruncation +export compress + export absorb_weight export ALSTruncation, FullEnvTruncation export SimpleUpdate @@ -238,4 +290,4 @@ export transverse_field_ising, heisenberg_XYZ, heisenberg_XXZ, j1_j2_model, bose_hubbard_model export pwave_superconductor, hubbard_model, tj_model -end # module +end # module \ No newline at end of file From 8a22365be7211dc2d87abaee3768245787337eeb Mon Sep 17 00:00:00 2001 From: sanderdemeyer Date: Tue, 27 Oct 2026 16:56:11 +0100 Subject: [PATCH 12/41] fix formatting --- src/PEPSKit.jl | 2 +- .../optimization/fixed_point_differentiation.jl | 16 ++++++++-------- src/operators/transfermatrix.jl | 8 ++++---- test/testsuite/ctmrg/unitcell.jl | 10 +++++----- 4 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/PEPSKit.jl b/src/PEPSKit.jl index b43b18cf1..3301b634e 100644 --- a/src/PEPSKit.jl +++ b/src/PEPSKit.jl @@ -290,4 +290,4 @@ export transverse_field_ising, heisenberg_XYZ, heisenberg_XXZ, j1_j2_model, bose_hubbard_model export pwave_superconductor, hubbard_model, tj_model -end # module \ No newline at end of file +end # module diff --git a/src/algorithms/optimization/fixed_point_differentiation.jl b/src/algorithms/optimization/fixed_point_differentiation.jl index 5f9dcb2d1..c8005f7fd 100644 --- a/src/algorithms/optimization/fixed_point_differentiation.jl +++ b/src/algorithms/optimization/fixed_point_differentiation.jl @@ -287,13 +287,13 @@ function _rrule( end function _rrule( - gradmode::GradMode, - config::RuleConfig, - ::typeof(MPSKit.leading_boundary), - envinit, - state, - alg::CTMRGAlgorithmTriangular, -) + gradmode::GradMode, + config::RuleConfig, + ::typeof(MPSKit.leading_boundary), + envinit, + state, + alg::CTMRGAlgorithmTriangular, + ) msg = "Fixed-point differentiation is not available for `CTMRGAlgorithmTriangular`, \ since gauge fixing for triangular CTMRG is currently not implemented." throw(ArgumentError(msg)) @@ -524,4 +524,4 @@ function fpgrad(∂F∂x, ∂f∂x, ∂f∂A, x₀, alg::EigSolver) end return ∂f∂A(y) -end \ No newline at end of file +end diff --git a/src/operators/transfermatrix.jl b/src/operators/transfermatrix.jl index fd94825e1..b1162fb6a 100644 --- a/src/operators/transfermatrix.jl +++ b/src/operators/transfermatrix.jl @@ -153,10 +153,10 @@ function initialize_mps( return InfiniteMPS( [ f( - T, - virtualspaces[_prev(i, end)] * _elementwise_dual(north_virtualspace(O, i)), - virtualspaces[mod1(i, end)], - ) for i in 1:length(O) + T, + virtualspaces[_prev(i, end)] * _elementwise_dual(north_virtualspace(O, i)), + virtualspaces[mod1(i, end)], + ) for i in 1:length(O) ]; kwargs... ) end diff --git a/test/testsuite/ctmrg/unitcell.jl b/test/testsuite/ctmrg/unitcell.jl index fb7b24e70..abcd74ebe 100644 --- a/test/testsuite/ctmrg/unitcell.jl +++ b/test/testsuite/ctmrg/unitcell.jl @@ -31,11 +31,11 @@ function test_unitcell( Pspaces, [ (c,) => adapt( - AT, randn( - scalartype(peps), - Pspaces[c], Pspaces[c], - ) - ) for c in CartesianIndices(unitcell) + AT, randn( + scalartype(peps), + Pspaces[c], Pspaces[c], + ) + ) for c in CartesianIndices(unitcell) ]..., ) @test expectation_value(peps, random_op, env) isa Number From ef0b544a903dbf708ee3d70e903681b2607d1d40 Mon Sep 17 00:00:00 2001 From: sanderdemeyer Date: Tue, 27 Oct 2026 22:43:11 +0100 Subject: [PATCH 13/41] small fixed related to merge conflicts --- src/PEPSKit.jl | 16 +--------------- src/algorithms/ctmrg_triangular/ctmrg.jl | 2 +- 2 files changed, 2 insertions(+), 16 deletions(-) diff --git a/src/PEPSKit.jl b/src/PEPSKit.jl index 3301b634e..5bda89392 100644 --- a/src/PEPSKit.jl +++ b/src/PEPSKit.jl @@ -23,6 +23,7 @@ using KrylovKit using KrylovKit: Lanczos, BlockLanczos using TensorOperations, OptimKit +import OptimKit: fixedpoint using ChainRulesCore, Zygote import TupleTools @@ -34,20 +35,9 @@ using MPSKit.DynamicTols: adapt_solver using MPSKit: @log_initialization, @log_iteration, @log_convergence, @log_nonconvergence using MPSKit.IterativeLoggers: with_verbosity import MPSKit.DynamicTols: _updatetol -using MPSKit: DynamicTol -using MPSKit.DynamicTols: adapt_solver -using MPSKit: @log_initialization, @log_iteration, @log_convergence, @log_nonconvergence -using MPSKit.IterativeLoggers: with_verbosity -import MPSKit.DynamicTols: _updatetol import MPSKit: tensorexpr, leading_boundary, loginit!, logiter!, logfinish!, logcancel!, physicalspace import MPSKit: infinite_temperature_density_matrix -using TensorKitTensors: fuse_charge -import TensorKitTensors.SpinOperators as SO -import TensorKitTensors.FermionOperators as FO -import TensorKitTensors.HubbardOperators as HO -import TensorKitTensors.TJOperators as TJ -import TensorKitTensors.BosonOperators as BO using TensorKitTensors: fuse_charge import TensorKitTensors.SpinOperators as SO import TensorKitTensors.FermionOperators as FO @@ -55,9 +45,6 @@ import TensorKitTensors.HubbardOperators as HO import TensorKitTensors.TJOperators as TJ import TensorKitTensors.BosonOperators as BO using MPSKitModels -import MPSKitModels: - transverse_field_ising, heisenberg_XYZ, heisenberg_XXZ, - hubbard_model, bose_hubbard_model, tj_model import MPSKitModels: transverse_field_ising, heisenberg_XYZ, heisenberg_XXZ, hubbard_model, bose_hubbard_model, tj_model @@ -256,7 +243,6 @@ export reduced_densitymatrix, expectation_value, network_value, cost_function export correlator, correlation_length export leading_boundary export PEPSOptimize, FixedPointGradient, GeomSum, ManualIter, ImplicitGradient -export PEPSOptimize, FixedPointGradient, GeomSum, ManualIter, ImplicitGradient export fixedpoint export LocalTruncation diff --git a/src/algorithms/ctmrg_triangular/ctmrg.jl b/src/algorithms/ctmrg_triangular/ctmrg.jl index f981de1e9..3e1eff763 100644 --- a/src/algorithms/ctmrg_triangular/ctmrg.jl +++ b/src/algorithms/ctmrg_triangular/ctmrg.jl @@ -8,7 +8,7 @@ function leading_boundary( env₀::CTMRGEnvTriangular, network::InfiniteTriangularNetwork, alg::CTMRGAlgorithmTriangular ) log = ignore_derivatives(() -> MPSKit.IterLog("CTMRG")) - return LoggingExtras.withlevel(; alg.verbosity) do + return with_verbosity(; alg.verbosity) do env = deepcopy(env₀) S_old = DiagonalTensorMap.(id.(domain.(env₀.C))) η = one(real(scalartype(network))) From 5ed5a096f0733ecb821719496eb01b1a44aa147a Mon Sep 17 00:00:00 2001 From: Philipp Schmoll Date: Tue, 27 Oct 2026 17:19:42 +0100 Subject: [PATCH 14/41] Fix integration issues in rebased triangular CTMRG branch --- src/PEPSKit.jl | 46 +- .../fixed_point_differentiation.jl | 527 ------------------ src/utility/util.jl | 1 + test/ctmrg/triangular.jl | 4 +- test/testsuite/utility/diff_maps.jl | 2 + 5 files changed, 6 insertions(+), 574 deletions(-) delete mode 100644 src/algorithms/optimization/fixed_point_differentiation.jl diff --git a/src/PEPSKit.jl b/src/PEPSKit.jl index 5bda89392..4c4b6caca 100644 --- a/src/PEPSKit.jl +++ b/src/PEPSKit.jl @@ -57,22 +57,17 @@ include("Defaults.jl") # Include first to allow for docstring interpolation wit include("utility/util.jl") include("utility/contraction_labels.jl") include("utility/tensor_traces.jl") -include("utility/contraction_labels.jl") -include("utility/tensor_traces.jl") include("utility/indexing.jl") include("utility/diffable_threads.jl") include("utility/twistdual.jl") -include("utility/twistdual.jl") include("utility/eigh.jl") include("utility/svd.jl") include("utility/orth.jl") -include("utility/orth.jl") include("utility/rotations.jl") include("utility/hook_pullback.jl") include("utility/autoopt.jl") include("utility/retractions.jl") include("utility/initialization.jl") -include("utility/initialization.jl") include("networks/tensors.jl") include("networks/local_sandwich.jl") @@ -93,9 +88,6 @@ include("operators/localoperator.jl") include("operators/localoperator/dense.jl") include("operators/localoperator/tensorproduct.jl") include("operators/localoperator/mpo.jl") -include("operators/localoperator/dense.jl") -include("operators/localoperator/tensorproduct.jl") -include("operators/localoperator/mpo.jl") include("operators/localcircuit.jl") include("operators/lattices/squarelattice.jl") include("operators/models.jl") @@ -106,7 +98,6 @@ include("environments/vumps_environments.jl") include("environments/suweight.jl") include("environments/bp_environments.jl") include("environments/product_state_environments.jl") -include("environments/product_state_environments.jl") include("algorithms/contractions/ctmrg/types.jl") include("algorithms/contractions/ctmrg/expr.jl") @@ -118,12 +109,9 @@ include("algorithms/contractions/ctmrg/fullinf_env.jl") include("algorithms/contractions/ctmrg/renormalize_corner.jl") include("algorithms/contractions/ctmrg/renormalize_edge.jl") include("algorithms/contractions/ctmrg/network_value.jl") -include("algorithms/contractions/ctmrg/network_value.jl") include("algorithms/contractions/ctmrg/gaugefix.jl") include("algorithms/contractions/ctmrg/characteristic_equations.jl") -include("algorithms/contractions/ctmrg/characteristic_equations.jl") -include("algorithms/contractions/absorb.jl") include("algorithms/contractions/absorb.jl") include("algorithms/contractions/absorb_weight.jl") include("algorithms/contractions/transfer.jl") @@ -135,20 +123,12 @@ include("algorithms/contractions/local_patch/patch_contractions.jl") include("algorithms/contractions/local_patch/densitymatrix/generic.jl") include("algorithms/contractions/local_patch/densitymatrix/ctmrg.jl") include("algorithms/contractions/local_patch/densitymatrix/bp.jl") -include("algorithms/contractions/bp_messages.jl") -include("algorithms/contractions/local_patch/expr_utils.jl") -include("algorithms/contractions/local_patch/network_expr.jl") -include("algorithms/contractions/local_patch/patch_contractions.jl") -include("algorithms/contractions/local_patch/densitymatrix/generic.jl") -include("algorithms/contractions/local_patch/densitymatrix/ctmrg.jl") -include("algorithms/contractions/local_patch/densitymatrix/bp.jl") include("algorithms/contractions/bondenv/benv_tools.jl") include("algorithms/contractions/bondenv/gaugefix.jl") include("algorithms/contractions/bondenv/als_solve.jl") include("algorithms/contractions/bondenv/benv_ctm.jl") include("algorithms/contractions/correlator/peps.jl") include("algorithms/contractions/correlator/pepo_purified.jl") -include("algorithms/contractions/correlator/pepo_purified.jl") include("algorithms/contractions/correlator/pepo_1layer.jl") include("algorithms/ctmrg/sparse_environments.jl") @@ -158,34 +138,22 @@ include("algorithms/ctmrg/projectors/halfinfinite.jl") include("algorithms/ctmrg/projectors/fullinfinite.jl") include("algorithms/ctmrg/projectors/c4v_eigh.jl") include("algorithms/ctmrg/projectors/c4v_qr.jl") -include("algorithms/ctmrg/projectors/projectors.jl") -include("algorithms/ctmrg/projectors/halfinfinite.jl") -include("algorithms/ctmrg/projectors/fullinfinite.jl") -include("algorithms/ctmrg/projectors/c4v_eigh.jl") -include("algorithms/ctmrg/projectors/c4v_qr.jl") include("algorithms/ctmrg/simultaneous.jl") include("algorithms/ctmrg/sequential.jl") include("algorithms/ctmrg/gaugefix.jl") include("algorithms/ctmrg/c4v.jl") include("algorithms/ctmrg/initialization.jl") -include("algorithms/ctmrg/initialization.jl") include("algorithms/truncation/truncationschemes.jl") include("algorithms/truncation/fullenv_truncation.jl") +include("algorithms/truncation/bond_tensor.jl") include("algorithms/truncation/bond_truncation.jl") -include("algorithms/compress/local.jl") - include("algorithms/contractions/ctmrg_contractions_triangular.jl") include("algorithms/ctmrg_triangular/ctmrg.jl") include("algorithms/ctmrg_triangular/utility.jl") include("algorithms/ctmrg_triangular/simultaneous.jl") -include("algorithms/truncation/truncationschemes.jl") -include("algorithms/truncation/fullenv_truncation.jl") -include("algorithms/truncation/bond_tensor.jl") -include("algorithms/truncation/bond_truncation.jl") - include("algorithms/compress/local.jl") include("algorithms/time_evolution/apply_gate.jl") @@ -207,17 +175,9 @@ include("algorithms/expectation_value/patch_contractions.jl") include("algorithms/expectation_value/network_value.jl") include("algorithms/expectation_value/correlator_adapters.jl") include("algorithms/expectation_value/correlators.jl") -include("algorithms/expectation_value/expectation_value.jl") -include("algorithms/expectation_value/reduced_densitymatrix.jl") -include("algorithms/expectation_value/patch_contractions.jl") -include("algorithms/expectation_value/network_value.jl") -include("algorithms/expectation_value/correlator_adapters.jl") -include("algorithms/expectation_value/correlators.jl") include("algorithms/toolbox.jl") include("algorithms/toolbox_triangular.jl") -include("algorithms/optimization/implicit_differentiation.jl") -include("algorithms/optimization/preconditioning.jl") include("algorithms/optimization/implicit_differentiation.jl") include("algorithms/optimization/preconditioning.jl") include("algorithms/optimization/peps_optimization.jl") @@ -227,7 +187,6 @@ include("algorithms/select_algorithm.jl") using .Defaults: set_scheduler! export set_scheduler! export EighAdjoint, IterEigh, SVDAdjoint, IterSVD, OrthAdjoint, QRAdjoint -export EighAdjoint, IterEigh, SVDAdjoint, IterSVD, OrthAdjoint, QRAdjoint export CTMRGEnv, SequentialCTMRG, SimultaneousCTMRG export CTMRGEnvTriangular, SimultaneousCTMRGTriangular # CTMRGTriaEnv, SimultaneousCTMRGTria export initialize_ctmrg_environment, @@ -248,9 +207,6 @@ export fixedpoint export LocalTruncation export compress -export LocalTruncation -export compress - export absorb_weight export ALSTruncation, FullEnvTruncation export SimpleUpdate diff --git a/src/algorithms/optimization/fixed_point_differentiation.jl b/src/algorithms/optimization/fixed_point_differentiation.jl deleted file mode 100644 index c8005f7fd..000000000 --- a/src/algorithms/optimization/fixed_point_differentiation.jl +++ /dev/null @@ -1,527 +0,0 @@ -abstract type GradMode{F} end - -const GRADIENT_MODE_SYMBOLS = IdDict{Symbol, Type{<:GradMode}}() -const LINSOLVER_SOLVER_SYMBOLS = IdDict{Symbol, Type{<:KrylovKit.LinearSolver}}( - :gmres => GMRES, :bicgstab => BiCGStab -) -const EIGSOLVER_SOLVER_SYMBOLS = IdDict{Symbol, Type{<:KrylovKit.KrylovAlgorithm}}( - :arnoldi => Arnoldi -) - -""" - GradMode(; kwargs...) - -Keyword argument parser returning the appropriate `GradMode` algorithm struct. -""" -function GradMode(; - alg = Defaults.gradient_alg, - tol = Defaults.gradient_tol, - maxiter = Defaults.gradient_maxiter, - verbosity = Defaults.gradient_verbosity, - iterscheme = Defaults.gradient_iterscheme, - solver_alg = (;), - ) - # replace symbol with GradMode alg type - haskey(GRADIENT_MODE_SYMBOLS, alg) || - throw(ArgumentError("unknown GradMode algorithm: $alg")) - alg_type = GRADIENT_MODE_SYMBOLS[alg] - - # parse GradMode algorithm - gradient_algorithm = if alg_type <: Union{GeomSum, ManualIter} - alg_type{iterscheme}(tol, maxiter, verbosity) - elseif alg_type <: Union{<:LinSolver, <:EigSolver} - solver = if solver_alg isa NamedTuple # determine linear/eigen solver algorithm - solver_kwargs = (; tol, maxiter, verbosity, solver_alg...) - - solver_type = if alg_type <: LinSolver # replace symbol with solver alg type - solver_kwargs = (; alg = Defaults.gradient_linsolver, solver_kwargs...) - haskey(LINSOLVER_SOLVER_SYMBOLS, solver_kwargs.alg) || throw( - ArgumentError("unknown LinSolver solver: $(solver_kwargs.alg)"), - ) - LINSOLVER_SOLVER_SYMBOLS[solver_kwargs.alg] - elseif alg_type <: EigSolver - solver_kwargs = (; - alg = Defaults.gradient_eigsolver, - eager = Defaults.gradient_eigsolver_eager, - solver_kwargs..., - ) - haskey(EIGSOLVER_SOLVER_SYMBOLS, solver_kwargs.alg) || throw( - ArgumentError("unknown EigSolver solver: $(solver_kwargs.alg)"), - ) - EIGSOLVER_SOLVER_SYMBOLS[solver_kwargs.alg] - end - - solver_kwargs = Base.structdiff(solver_kwargs, (; alg = nothing)) # remove `alg` keyword argument - solver_type(; solver_kwargs...) - else - solver_alg - end - - alg_type{iterscheme}(solver) - else - throw(ArgumentError("unknown gradient algorithm: $alg")) - end - - return gradient_algorithm -end - -iterscheme(::GradMode{F}) where {F} = F - -""" -$(TYPEDEF) - -Gradient mode for CTMRG using explicit evaluation of the geometric sum. - -## Fields - -$(TYPEDFIELDS) - -## Constructors - - GeomSum(; kwargs...) - -Construct the `GeomSum` algorithm struct based on the following keyword arguments: - -* `tol::Real=$(Defaults.gradient_tol)` : Convergence tolerance for the difference of norms of two consecutive summands in the geometric sum. -* `maxiter::Int=$(Defaults.gradient_maxiter)` : Maximal number of gradient iterations. -* `verbosity::Int=$(Defaults.gradient_verbosity)` : Output information verbosity that can be one of the following: - 0. Suppress output information - 1. Print convergence warnings - 2. Information at each gradient iteration -* `iterscheme::Symbol=:$(Defaults.gradient_iterscheme)` : Style of CTMRG iteration which is being differentiated, which can be: - - `:fixed` : the differentiated CTMRG iteration uses a pre-computed SVD with a fixed set of gauges - - `:diffgauge` : the differentiated iteration consists of a CTMRG iteration and a subsequent gauge-fixing step such that the gauge-fixing procedure is differentiated as well -""" -struct GeomSum{F} <: GradMode{F} - tol::Real - maxiter::Int - verbosity::Int -end -GeomSum(; kwargs...) = GradMode(; alg = :geomsum, kwargs...) - -GRADIENT_MODE_SYMBOLS[:geomsum] = GeomSum - -""" -$(TYPEDEF) - -Gradient mode for CTMRG using manual iteration to solve the linear problem. - -## Fields - -$(TYPEDFIELDS) - -## Constructors - - ManualIter(; kwargs...) - -Construct the `ManualIter` algorithm struct based on the following keyword arguments: - -* `tol::Real=$(Defaults.gradient_tol)` : Convergence tolerance for the norm difference of two consecutive `dx` contributions. -* `maxiter::Int=$(Defaults.gradient_maxiter)` : Maximal number of gradient iterations. -* `verbosity::Int=$(Defaults.gradient_verbosity)` : Output information verbosity that can be one of the following: - 0. Suppress output information - 1. Print convergence warnings - 2. Information at each gradient iteration -* `iterscheme::Symbol=:$(Defaults.gradient_iterscheme)` : Style of CTMRG iteration which is being differentiated, which can be: - - `:fixed` : the differentiated CTMRG iteration uses a pre-computed SVD with a fixed set of gauges - - `:diffgauge` : the differentiated iteration consists of a CTMRG iteration and a subsequent gauge-fixing step such that the gauge-fixing procedure is differentiated as well -""" -struct ManualIter{F} <: GradMode{F} - tol::Real - maxiter::Int - verbosity::Int -end -ManualIter(; kwargs...) = GradMode(; alg = :manualiter, kwargs...) - -GRADIENT_MODE_SYMBOLS[:manualiter] = ManualIter - -""" -$(TYPEDEF) - -Gradient mode wrapper around `KrylovKit.LinearSolver` for solving the gradient linear -problem using iterative solvers. - -## Fields - -$(TYPEDFIELDS) - -## Constructors - - LinSolver(; kwargs...) - -Construct the `LinSolver` algorithm struct based on the following keyword arguments: - -* `tol::Real=$(Defaults.gradient_tol)` : Convergence tolerance of the linear solver. -* `maxiter::Int=$(Defaults.gradient_maxiter)` : Maximal number of solver iterations. -* `verbosity::Int=$(Defaults.gradient_verbosity)` : Output information verbosity of the linear solver. -* `iterscheme::Symbol=:$(Defaults.gradient_iterscheme)` : Style of CTMRG iteration which is being differentiated, which can be: - - `:fixed` : the differentiated CTMRG iteration uses a pre-computed SVD with a fixed set of gauges - - `:diffgauge` : the differentiated iteration consists of a CTMRG iteration and a subsequent gauge-fixing step such that the gauge-fixing procedure is differentiated as well -* `solver_alg::Union{KrylovKit.LinearSolver,NamedTuple}=(; alg::Symbol=:$(Defaults.gradient_linsolver)` : Linear solver algorithm which, if supplied directly as a `KrylovKit.LinearSolver` overrides the above specified `tol`, `maxiter` and `verbosity`. Alternatively, it can be supplied via a `NamedTuple` where `alg` can be one of the following: - - `:gmres` : GMRES iterative linear solver, see [`KrylovKit.GMRES`](@extref) for details - - `:bicgstab` : BiCGStab iterative linear solver, see [`KrylovKit.BiCGStab`](@extref) for details -""" -struct LinSolver{F} <: GradMode{F} - solver_alg::KrylovKit.LinearSolver -end -LinSolver(; kwargs...) = GradMode(; alg = :linsolver, kwargs...) - -GRADIENT_MODE_SYMBOLS[:linsolver] = LinSolver - -""" -$(TYPEDEF) - -Gradient mode wrapper around `KrylovKit.KrylovAlgorithm` for solving the gradient linear -problem as an eigenvalue problem. - -## Fields - -$(TYPEDFIELDS) - -## Constructors - - EigSolver(; kwargs...) - -Construct the `EigSolver` algorithm struct based on the following keyword arguments: - -* `tol::Real=$(Defaults.gradient_tol)` : Convergence tolerance of the eigen solver. -* `maxiter::Int=$(Defaults.gradient_maxiter)` : Maximal number of solver iterations. -* `verbosity::Int=$(Defaults.gradient_verbosity)` : Output information verbosity of the linear solver. -* `iterscheme::Symbol=:$(Defaults.gradient_iterscheme)` : Style of CTMRG iteration which is being differentiated, which can be: - - `:fixed` : the differentiated CTMRG iteration uses a pre-computed SVD with a fixed set of gauges - - `:diffgauge` : the differentiated iteration consists of a CTMRG iteration and a subsequent gauge-fixing step such that the gauge-fixing procedure is differentiated as well -* `solver_alg::Union{KrylovKit.KrylovAlgorithm,NamedTuple}=(; alg=:$(Defaults.gradient_eigsolver)` : Eigen solver algorithm which, if supplied directly as a `KrylovKit.KrylovAlgorithm` overrides the above specified `tol`, `maxiter` and `verbosity`. Alternatively, it can be supplied via a `NamedTuple` where `alg` can be one of the following: - - `:arnoldi` : Arnoldi Krylov algorithm, see [`KrylovKit.Arnoldi`](@extref) for details -""" -struct EigSolver{F} <: GradMode{F} - solver_alg::KrylovKit.KrylovAlgorithm -end -EigSolver(; kwargs...) = GradMode(; alg = :eigsolver, kwargs...) - -GRADIENT_MODE_SYMBOLS[:eigsolver] = EigSolver - -""" - _check_algorithm_combination(boundary_alg, gradient_alg_or_symmetrization) - _check_algorithm_combination(boundary_alg, gradient_alg, symmetrization) - -Check for allowed combinations of gradient algorithm and boundary algorithm to be used for -computing the gradient of a `leading_boundary` call. Throws an error containing a -recommended fix if the combination is not allowed or broken. -""" -function _check_algorithm_combination(boundary_alg, gradient_alg_or_symmetrization) end -function _check_algorithm_combination(boundary_alg, gradient_alg, symmetrization) - _check_algorithm_combination(boundary_alg, gradient_alg) - _check_algorithm_combination(boundary_alg, symmetrization) - return nothing -end -function _check_algorithm_combination(::SequentialCTMRG, ::GradMode{:fixed}) - msg = "`:fixed` mode is not compatible with `SequentialCTMRG` since the sequential \ - application of SVDs does not allow to differentiate through a fixed set of \ - gauges; select SimultaneousCTMRG instead to use :fixed mode" - throw(ArgumentError(msg)) -end -function _check_algorithm_combination(::C4vCTMRG{<:C4vEighProjector}, ::GradMode{:diffgauge}) - msg = "`:diffgauge` mode is currently not compatible with eigh-based C4v CTMRG; \ - either switch to a different projector algorithm (e.g. `c4v_qr`), or use :fixed \ - mode for differentiation instead." - throw(ArgumentError(msg)) -end -function _check_algorithm_combination(::C4vCTMRG, symm::Union{Nothing, <:SymmetrizationStyle}) - if !(symm isa RotateReflect) - msg = "C4vCTMRG optimization is compatible only with RotateReflect symmetrization. \ - Make sure to set `symmetrization = RotateReflect()`." - throw(ArgumentError(msg)) - end - return nothing -end - -#= -Evaluating the gradient of the cost function for CTMRG: -- The gradient of the cost function for CTMRG can be computed using automatic differentiation (AD) or explicit evaluation of the geometric sum. -- With AD, the gradient is computed by differentiating the cost function with respect to the PEPS tensors, including computing the environment tensors. -- With explicit evaluation of the geometric sum, the gradient is computed by differentiating the cost function with the environment kept fixed, and then manually adding the gradient contributions from the environments. -=# - -_scrambling_env_gauge(::CTMRGAlgorithm) = ScramblingEnvGauge() -_scrambling_env_gauge(::C4vCTMRG) = ScramblingEnvGaugeC4v() - -function _set_fixed_truncation(alg::CTMRGAlgorithm) - alg_fixed = @set alg.projector_alg = _set_truncation(alg.projector_alg, FixedSpaceTruncation()) - return alg_fixed -end - -function _rrule( - gradmode::GradMode{:diffgauge}, - config::RuleConfig, - ::typeof(leading_boundary), - envinit, - state, - alg::CTMRGAlgorithm, - ) - _check_algorithm_combination(alg, gradmode) - - env, info = leading_boundary(envinit, state, alg) - alg_fixed = _set_fixed_truncation(alg) # fix spaces during differentiation - alg_gauge = _scrambling_env_gauge(alg) # TODO: make this a field in GradMode? - - # prepare iterating function corresponding to a single gauge-fixed CTMRG iteration - function f(A, x) - return gauge_fix(ctmrg_iteration(InfiniteSquareNetwork(A), x, alg_fixed)[1], x, alg_gauge)[1] - end - # compute its pullback - _, env_vjp = rrule_via_ad(config, f, state, env) - # split off state and environment parts - ∂f∂A(x)::typeof(state) = env_vjp(x)[2] - ∂f∂x(x)::typeof(env) = env_vjp(x)[3] - - function leading_boundary_diffgauge_pullback((Δenv′, Δinfo)) - Δenv = unthunk(Δenv′) - - # evaluate the geometric sum - ∂F∂env = fpgrad(Δenv, ∂f∂x, ∂f∂A, Δenv, gradmode) - - return NoTangent(), ZeroTangent(), ∂F∂env, NoTangent() - end - - return (env, info), leading_boundary_diffgauge_pullback -end - -function _rrule( - gradmode::GradMode, - config::RuleConfig, - ::typeof(MPSKit.leading_boundary), - envinit, - state, - alg::CTMRGAlgorithmTriangular, - ) - msg = "Fixed-point differentiation is not available for `CTMRGAlgorithmTriangular`, \ - since gauge fixing for triangular CTMRG is currently not implemented." - throw(ArgumentError(msg)) -end -# Here f is differentiated from an pre-computed SVD with fixed U, S and V -function _rrule( - gradmode::GradMode{:fixed}, - config::RuleConfig, - ::typeof(MPSKit.leading_boundary), - envinit, - state, - alg::CTMRGAlgorithm, - ) - _check_algorithm_combination(alg, gradmode) - - env, = leading_boundary(envinit, state, alg) - alg_fixed = _set_fixed_truncation(alg) # fix spaces during differentiation - alg_gauge = _scrambling_env_gauge(alg) # TODO: make this a field in GradMode? - env_conv, info = ctmrg_iteration(InfiniteSquareNetwork(state), env, alg_fixed) - _, signs = gauge_fix(env_conv, env, alg_gauge) - - # fix decomposition - alg_fixed = gauge_fix(alg, signs, info) - - # prepare iterating function corresponding to a single CTMRG iteration with a - # gauge-fixed projector - function f(A, x) - return fix_global_phases( - ctmrg_iteration(InfiniteSquareNetwork(A), x, alg_fixed)[1], x, - ) - end - # prepare its pullback - _, env_vjp = rrule_via_ad(config, f, state, env) - # split off state and environment parts - ∂f∂A(x)::typeof(state) = env_vjp(x)[2] - ∂f∂x(x)::typeof(env) = env_vjp(x)[3] - - function leading_boundary_fixed_pullback((Δenv′, Δinfo)) - Δenv = unthunk(Δenv′) - - # evaluate the geometric sum - ∂F∂env = fpgrad(Δenv, ∂f∂x, ∂f∂A, Δenv, gradmode) - - return NoTangent(), ZeroTangent(), ∂F∂env, NoTangent() - end - - return (env, info), leading_boundary_fixed_pullback -end - -function gauge_fix(alg::SVDAdjoint, signs, info) - # embed gauge signs in larger space to fix gauge of full U and V on truncated subspace - rowsize, colsize = size(signs, 2), size(signs, 3) - inds = info.truncation_indices - signs_full = map(Iterators.product(1:4, 1:rowsize, 1:colsize)) do (dir, row, col) - σ = signs[dir, row, col] - row_sign, col_sign = if dir == NORTH # take unit cell interdependency of signs into account - row, _prev(col, colsize) - elseif dir == EAST - _prev(row, rowsize), col - elseif dir == SOUTH - row, _next(col, colsize) - elseif dir == WEST - _next(row, rowsize), col - end - - ind = inds[dir, row_sign, col_sign] - extended_σ = id(scalartype(σ), domain(info.S_full[dir, row_sign, col_sign])) - for (c, b) in blocks(σ) - I = get(ind, c, nothing) - @assert !isnothing(I) - block(extended_σ, c)[I, I] = b - end - return extended_σ - end - - # fix kept and full U and V - U_fixed, V_fixed = fix_relative_phases(info.U, info.V, signs) - U_full_fixed, V_full_fixed = fix_relative_phases(info.U_full, info.V_full, signs_full) - return SVDAdjoint(; - fwd_alg = FixedSVD(U_fixed, info.S, V_fixed, U_full_fixed, info.S_full, V_full_fixed, inds), - rrule_alg = alg.rrule_alg, - ) -end -function gauge_fix(alg::SVDAdjoint{F}, signs, info) where {F <: IterSVD} - # fix kept U and V only since iterative SVD doesn't have access to full spectrum - U_fixed, V_fixed = fix_relative_phases(info.U, info.V, signs) - return SVDAdjoint(; - fwd_alg = FixedSVD(U_fixed, info.S, V_fixed, nothing, nothing, nothing, nothing), - rrule_alg = alg.rrule_alg, - ) -end -function gauge_fix(alg::EighAdjoint, signs, info) - σ = signs[1] - inds = info.truncation_indices - - # embed gauge signs in larger space to fix gauge of full V on truncated subspace - extended_σ = id(scalartype(σ), domain(info.D_full)) - for (c, b) in blocks(σ) - I = get(inds, c, nothing) - @assert !isnothing(I) - block(extended_σ, c)[I, I] = b - end - - # fix kept and full V - V_fixed = info.V * σ' - V_full_fixed = info.V_full * extended_σ' - return EighAdjoint(; - fwd_alg = FixedEig(info.D, V_fixed, info.D_full, V_full_fixed, inds), - rrule_alg = alg.rrule_alg, - ) -end -function gauge_fix(alg::EighAdjoint{F}, signs, info) where {F <: IterEigh} - # fix kept V only since iterative decomposition doesn't have access to full spectrum - V_fixed = info.V * signs[1]' - return EighAdjoint(; - fwd_alg = FixedEig(info.D, V_fixed, nothing, nothing, nothing), - rrule_alg = alg.rrule_alg, - ) -end -function gauge_fix(alg::QRAdjoint, signs, info) - Q_fixed = info.Q * signs[1]' - R_fixed = signs[1] * info.R - return QRAdjoint(; - fwd_alg = FixedQR(Q_fixed, R_fixed), - rrule_alg = alg.rrule_alg, - ) -end - -@doc """ - fpgrad(∂F∂x, ∂f∂x, ∂f∂A, y0, alg) - -Compute the gradient of the CTMRG fixed point by solving the following equation: - -dx = ∑ₙ (∂f∂x)ⁿ ∂f∂A dA = (1 - ∂f∂x)⁻¹ ∂f∂A dA - -where `∂F∂x` is the gradient of the cost function with respect to the PEPS tensors, `∂f∂x` -is the partial gradient of the CTMRG iteration with respect to the environment tensors, -`∂f∂A` is the partial gradient of the CTMRG iteration with respect to the PEPS tensors, and -`y0` is the initial guess for the fixed-point iteration. The function returns the gradient -`dx` of the fixed-point iteration. -""" -fpgrad - -# TODO: can we construct an implementation that does not need to evaluate the vjp -# twice if both ∂f∂A and ∂f∂x are needed? -function fpgrad(∂F∂x, ∂f∂x, ∂f∂A, _, alg::GeomSum) - g = ∂F∂x - dx = ∂f∂A(g) # n = 0 term: ∂F∂x ∂f∂A - ϵ = 2 * alg.tol - for i in 1:(alg.maxiter) - g = ∂f∂x(g) - Σₙ = ∂f∂A(g) - dx += Σₙ - ϵnew = norm(Σₙ) # TODO: normalize this error? - Δϵ = ϵ - ϵnew - alg.verbosity > 1 && - @printf("Gradient iter: %3d ‖Σₙ‖: %.2e Δ‖Σₙ‖: %.2e\n", i, ϵnew, Δϵ) - ϵ = ϵnew - - ϵ < alg.tol && break - if alg.verbosity > 0 && i == alg.maxiter - @warn "gradient fixed-point iteration reached maximal number of iterations at ‖Σₙ‖ = $ϵ" - end - end - return dx -end - -function fpgrad(∂F∂x, ∂f∂x, ∂f∂A, y₀, alg::ManualIter) - y = deepcopy(y₀) # Do not mutate y₀ - dx = ∂f∂A(y) - ϵ = 1.0 - for i in 1:(alg.maxiter) - y′ = ∂F∂x + ∂f∂x(y) - - dxnew = ∂f∂A(y′) - ϵnew = norm(dxnew - dx) - Δϵ = ϵ - ϵnew - alg.verbosity > 1 && @printf( - "Gradient iter: %3d ‖Cᵢ₊₁-Cᵢ‖/N: %.2e Δ‖Cᵢ₊₁-Cᵢ‖/N: %.2e\n", i, ϵnew, Δϵ - ) - y = y′ - dx = dxnew - ϵ = ϵnew - - ϵ < alg.tol && break - if alg.verbosity > 0 && i == alg.maxiter - @warn "gradient fixed-point iteration reached maximal number of iterations at ‖Cᵢ₊₁-Cᵢ‖ = $ϵ" - end - end - return dx -end - -function fpgrad(∂F∂x, ∂f∂x, ∂f∂A, y₀, alg::LinSolver) - y, info = reallinsolve(∂f∂x, ∂F∂x, y₀, alg.solver_alg, 1, -1) - if alg.solver_alg.verbosity > 0 && info.converged != 1 - @warn("gradient fixed-point iteration reached maximal number of iterations:", info) - end - - return ∂f∂A(y) -end - -function fpgrad(∂F∂x, ∂f∂x, ∂f∂A, x₀, alg::EigSolver) - function f(X) - y = ∂f∂x(X[1]) - return (VI.add!!(y, ∂F∂x, X[2]), X[2]) - end - X₀ = (x₀, one(scalartype(x₀))) - _, vecs, info = realeigsolve(f, X₀, 1, :LM, alg.solver_alg) - if alg.solver_alg.verbosity > 0 && info.converged < 1 - @warn("gradient fixed-point iteration reached maximal number of iterations:", info) - end - if norm(vecs[1][2]) < 1.0e-2 * alg.solver_alg.tol - @warn "Fixed-point gradient computation using Arnoldi failed:\n\tauxiliary component should be finite but was $(vecs[1][2])\n\tpossibly the Jacobian does not have a unique eigenvalue 1" - @info "Falling back to linear solver for fixed-point gradient computation." - backup_ls_alg = _alg_or_nt( - GradMode, - (; - alg = :linsolver, - tol = alg.solver_alg.tol, - maxiter = alg.solver_alg.maxiter * alg.solver_alg.krylovdim, - verbosity = alg.solver_alg.verbosity, - iterscheme = iterscheme(alg), - ) - ) - return fpgrad(∂F∂x, ∂f∂x, ∂f∂A, x₀, backup_ls_alg) - else - y = VI.scale!!(vecs[1][1], inv(vecs[1][2])) - end - - return ∂f∂A(y) -end diff --git a/src/utility/util.jl b/src/utility/util.jl index 4c450ddb4..b1073c8e9 100644 --- a/src/utility/util.jl +++ b/src/utility/util.jl @@ -23,6 +23,7 @@ function _maxabs(data::AbstractArray) return norm(data, Inf) end _maxabs(s::AbstractTensorMap) = _maxabs(s.data) +_maxabs(s::AdjointTensorMap) = _maxabs(adjoint(s)) """ sdiag_pow(s, pow::Real; tol::Real=eps(real(scalartype(s)))^(3 / 4)) diff --git a/test/ctmrg/triangular.jl b/test/ctmrg/triangular.jl index 554a4a793..04ae6b2af 100644 --- a/test/ctmrg/triangular.jl +++ b/test/ctmrg/triangular.jl @@ -169,9 +169,9 @@ end ) # instantiate because hook_pullback doesn't go through the keyword selector... concrete_gradient_alg = if isnothing(gradient_alg) - nothing # TODO: add this to the PEPSKit.GradMode selector? + nothing else - PEPSKit.GradMode(; alg = gradient_alg, tol = gradtol, iterscheme = gradient_iterscheme) + PEPSKit.GradientAlgorithm(; alg = gradient_alg, tol = gradtol) end env, = leading_boundary( CTMRGEnvTriangular(randn, ComplexF64, Vspace ⊗ Vspace', Espace), diff --git a/test/testsuite/utility/diff_maps.jl b/test/testsuite/utility/diff_maps.jl index a1a904f87..d986327c3 100644 --- a/test/testsuite/utility/diff_maps.jl +++ b/test/testsuite/utility/diff_maps.jl @@ -17,6 +17,8 @@ function utility_diff_maps(AT) gradient = only(Zygote.gradient(X -> real(sum(sdiag_pow(X, power).data)), s)) @test storagetype(gradient) === storagetype(s) @test gradient.data ≈ power .* s.data .^ (power - 1) + dense_gradient = only(Zygote.gradient(X -> real(sum(sdiag_pow(X, power).data)), TensorMap(s))) + @test dense_gradient ≈ TensorMap(gradient) end end end From 456be56e4f8d05e03117af85b3e92252da3e6ae7 Mon Sep 17 00:00:00 2001 From: Philipp Schmoll Date: Tue, 27 Oct 2026 17:26:47 +0100 Subject: [PATCH 15/41] Reuse the preliminary edge SVD in triangular CTMRG --- src/algorithms/ctmrg_triangular/simultaneous.jl | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/algorithms/ctmrg_triangular/simultaneous.jl b/src/algorithms/ctmrg_triangular/simultaneous.jl index 90e1321bb..c77101610 100644 --- a/src/algorithms/ctmrg_triangular/simultaneous.jl +++ b/src/algorithms/ctmrg_triangular/simultaneous.jl @@ -136,15 +136,19 @@ function semi_renormalize(network::InfiniteTriangularNetwork, env::CTMRGEnvTrian sqrtS = sdiag_pow(S, 0.5) Ẽb = U * sqrtS - Ẽa = _permute_edge(sqrtS * V) + Ẽa = sqrtS * V trunc = _truncation_strategy(trunc, env.Ea[dir, r, c]) - Utr, Str, Vtr = svd_trunc(mat; trunc) - sqrt_Str = sdiag_pow(Str, 0.5) - Ẽbtr = Utr * sqrt_Str - Ẽatr = _permute_edge(sqrt_Str * Vtr) - return Ẽa, Ẽb, Ẽatr, Ẽbtr + P = ignore_derivatives() do + I = id(storagetype(S), space(S, 1)) + (P, _, _), _ = truncate(svd_trunc!, (I, DiagonalTensorMap(S), I), trunc) + return P + end + Ẽbtr = Ẽb * P + Ẽatr = _permute_edge(P' * Ẽa) + + return _permute_edge(Ẽa), Ẽb, Ẽatr, Ẽbtr end return getindex.(projectors, 1), getindex.(projectors, 2), getindex.(projectors, 3), getindex.(projectors, 4) end From fd1b01f8a8e8d59c1124bf769bca0e123c039016 Mon Sep 17 00:00:00 2001 From: Philipp Schmoll Date: Tue, 27 Oct 2026 17:40:22 +0100 Subject: [PATCH 16/41] Replace triangular Q-projectors with simultaneous Ta/Tb updates --- .../ctmrg_contractions_triangular.jl | 72 +++++------ .../ctmrg_triangular/simultaneous.jl | 119 ++++++------------ test/ctmrg/triangular.jl | 98 +++++++++++++++ 3 files changed, 172 insertions(+), 117 deletions(-) diff --git a/src/algorithms/contractions/ctmrg_contractions_triangular.jl b/src/algorithms/contractions/ctmrg_contractions_triangular.jl index 42b777923..c40b6beff 100644 --- a/src/algorithms/contractions/ctmrg_contractions_triangular.jl +++ b/src/algorithms/contractions/ctmrg_contractions_triangular.jl @@ -41,44 +41,44 @@ function semi_renormalize_edge(network::InfiniteTriangularNetwork{P}, env::CTMRG return mat / norm(mat) end -function build_halfinfinite_projectors(network::InfiniteTriangularNetwork{P}, env::CTMRGEnvTriangular, Ẽas, Ẽbs, Ẽastr, Ẽbstr, dir, r, c) where {P <: PEPSSandwichTriangular} - O_r_c = network[r, c] - O_rp2_c = network[_coordinates(dir + 2, 0, r, c, size(network))[2:3]...] - @tensor opt = true σL[-1 -2 -3; -4] := - env.C[_coordinates(dir, 0, r, c, size(network))...][1 2 10; 8] * - env.C[_coordinates(dir - 1, 0, r, c, size(network))...][4 3 11; 1] * - env.C[_coordinates(dir - 2, 0, r, c, size(network))...][6 5 12; 4] * - Ẽbs[_coordinates(dir, 1, r, c, size(network))...][8 9 13; -4] * - Ẽastr[_coordinates(dir - 3, 0, r, c, size(network))...][-1 7 14; 6] * - rotl60(ket(O_r_c), mod(dir - 1, 6))[15 2 9 -2 7 5 3] * - conj(rotl60(bra(O_r_c), mod(dir - 1, 6))[15; 10 13 -3 14 12 11]) - @tensor opt = true σR[-1; -2 -3 -4] := - env.C[_coordinates(dir + 1, 0, r, c, size(network))...][8 2 10; 1] * - env.C[_coordinates(dir + 2, 0, r, c, size(network))...][1 3 11; 4] * - env.C[_coordinates(dir + 3, 0, r, c, size(network))...][4 5 12; 6] * - Ẽbstr[_coordinates(dir + 3, -1, r, c, size(network))...][6 7 13; -2] * - Ẽas[_coordinates(dir, 0, r, c, size(network))...][-1 9 14; 8] * - rotl60(ket(O_rp2_c), mod(dir - 1, 6))[15; 9 2 3 5 7 -3] * - conj(rotl60(bra(O_rp2_c), mod(dir - 1, 6))[15; 14 10 11 12 13 -4]) - return σL, σR +function build_halfinfinite_environments(network::InfiniteTriangularNetwork{P}, env::CTMRGEnvTriangular, dir, r, c) where {P <: PEPSSandwichTriangular} + rL, cL = _coordinates(dir + 4, 0, r, c, size(network))[2:3] + rR, cR = _coordinates(dir + 3, 0, r, c, size(network))[2:3] + O_L = network[rL, cL] + O_R = network[rR, cR] + @tensor opt = true ρL[-1 -2 -3; -4 -5 -6] := + env.C[_coordinates(dir, 0, rL, cL, size(network))...][1 2 10; -4] * + env.C[_coordinates(dir - 1, 0, rL, cL, size(network))...][4 3 11; 1] * + env.C[_coordinates(dir - 2, 0, rL, cL, size(network))...][6 5 12; 4] * + env.Ea[_coordinates(dir + 3, 0, rL, cL, size(network))...][-1 7 14; 6] * + rotl60(ket(O_L), mod(dir - 1, 6))[15; 2 -5 -2 7 5 3] * + conj(rotl60(bra(O_L), mod(dir - 1, 6))[15; 10 -6 -3 14 12 11]) + @tensor opt = true ρR[-1 -2 -3; -4 -5 -6] := + env.C[_coordinates(dir + 1, 0, rR, cR, size(network))...][-1 2 10; 1] * + env.C[_coordinates(dir + 2, 0, rR, cR, size(network))...][1 3 11; 4] * + env.C[_coordinates(dir + 3, 0, rR, cR, size(network))...][4 5 12; 6] * + env.Eb[_coordinates(dir + 3, 1, rR, cR, size(network))...][6 7 13; -4] * + rotl60(ket(O_R), mod(dir - 1, 6))[15; -2 2 3 5 7 -5] * + conj(rotl60(bra(O_R), mod(dir - 1, 6))[15; -3 10 11 12 13 -6]) + return ρL, ρR end -function build_halfinfinite_projectors(network::InfiniteTriangularNetwork{P}, env::CTMRGEnvTriangular, Ẽas, Ẽbs, Ẽastr, Ẽbstr, dir, r, c) where {P <: PFTensorTriangular} - @tensor opt = true σL[-1 -2; -3] := - env.C[_coordinates(dir, 0, r, c, size(network))...][1 2; 8] * - env.C[_coordinates(dir - 1, 0, r, c, size(network))...][4 3; 1] * - env.C[_coordinates(dir - 2, 0, r, c, size(network))...][6 5; 4] * - Ẽbs[_coordinates(dir, 1, r, c, size(network))...][8 9; -3] * - Ẽastr[_coordinates(dir - 3, 0, r, c, size(network))...][-1 7; 6] * - rotl60(network[r, c], mod(dir - 1, 6))[3 5 7; 2 9 -2] - @tensor opt = true σR[-1; -2 -3] := - env.C[_coordinates(dir + 1, 0, r, c, size(network))...][8 2; 1] * - env.C[_coordinates(dir + 2, 0, r, c, size(network))...][1 3; 4] * - env.C[_coordinates(dir + 3, 0, r, c, size(network))...][4 5; 6] * - Ẽbstr[_coordinates(dir + 3, -1, r, c, size(network))...][6 7; -2] * - Ẽas[_coordinates(dir, 0, r, c, size(network))...][-1 9; 8] * - rotl60(network[_coordinates(dir + 2, 0, r, c, size(network))[2:3]...], mod(dir - 1, 6))[-3 7 5; 9 2 3] - return σL, σR +function build_halfinfinite_environments(network::InfiniteTriangularNetwork{P}, env::CTMRGEnvTriangular, dir, r, c) where {P <: PFTensorTriangular} + rL, cL = _coordinates(dir + 4, 0, r, c, size(network))[2:3] + rR, cR = _coordinates(dir + 3, 0, r, c, size(network))[2:3] + @tensor opt = true ρL[-1 -2; -3 -4] := + env.C[_coordinates(dir, 0, rL, cL, size(network))...][1 2; -3] * + env.C[_coordinates(dir - 1, 0, rL, cL, size(network))...][4 3; 1] * + env.C[_coordinates(dir - 2, 0, rL, cL, size(network))...][6 5; 4] * + env.Ea[_coordinates(dir + 3, 0, rL, cL, size(network))...][-1 7; 6] * + rotl60(network[rL, cL], mod(dir - 1, 6))[3 5 7; 2 -4 -2] + @tensor opt = true ρR[-1 -2; -3 -4] := + env.C[_coordinates(dir + 1, 0, rR, cR, size(network))...][-1 2; 1] * + env.C[_coordinates(dir + 2, 0, rR, cR, size(network))...][1 3; 4] * + env.C[_coordinates(dir + 3, 0, rR, cR, size(network))...][4 5; 6] * + env.Eb[_coordinates(dir + 3, 1, rR, cR, size(network))...][6 7; -3] * + rotl60(network[rR, cR], mod(dir - 1, 6))[-4 7 5; -2 2 3] + return ρL, ρR end function renormalize_corner_triangular((dir, r, c), network::InfiniteTriangularNetwork{P}, env, Pas, Pbs) where {P <: PFTensorTriangular} diff --git a/src/algorithms/ctmrg_triangular/simultaneous.jl b/src/algorithms/ctmrg_triangular/simultaneous.jl index c77101610..84cb57c93 100644 --- a/src/algorithms/ctmrg_triangular/simultaneous.jl +++ b/src/algorithms/ctmrg_triangular/simultaneous.jl @@ -34,11 +34,8 @@ function ctmrg_iteration(network::InfiniteTriangularNetwork, env::CTMRGEnvTriang env = renormalize_corners!(network, env, Pas, Pbs) env = normalize_corners(env) - Ẽas, Ẽbs, Ẽastr, Ẽbstr = semi_renormalize(network, env, Pas, Pbs, trunc) - Qas, Qbs = build_matrix_second_projectors(network, env, Ẽas, Ẽbs, Ẽastr, Ẽbstr, trunc; alg.conditioning) - - env = renormalize_edges(env, Ẽas, Ẽbs, Qas, Qbs) - env = normalize_edges(env) + mats, env = semi_renormalize(network, env, Pas, Pbs, trunc) + env = renormalize_edges(network, env, mats, trunc; alg.conditioning) return env, S end @@ -66,8 +63,8 @@ function calculate_twothirds_projectors(network::InfiniteTriangularNetwork{P}, e ρρ = ρL * ρR ρρ /= norm(ρρ) - trunc = _truncation_strategy(trunc, env.Ea[dir, r, c]) - U, S, V = svd_trunc(ρρ; trunc) + trunc′ = _truncation_strategy(trunc, env.Ea[dir, r, c]) + U, S, V = svd_trunc(ρρ; trunc = trunc′) iqsrtS = sdiag_pow(S, -0.5) Pb = ρR * V' * iqsrtS @@ -90,8 +87,8 @@ function calculate_full_projectors(network::InfiniteTriangularNetwork{P}, env, t ρρ = ρ̄ᴸ * ρL * ρR * ρ̄ᴿ ρρ /= norm(ρρ) - trunc = _truncation_strategy(trunc, env.Ea[dir, r, c]) - U, S, Vᴴ = svd_trunc(ρρ; trunc) + trunc′ = _truncation_strategy(trunc, env.Ea[dir, r, c]) + U, S, Vᴴ = svd_trunc(ρρ; trunc = trunc′) isqrtS = sdiag_pow(S, -0.5) Pb = ρR * ρ̄ᴿ * Vᴴ' * isqrtS Pa = isqrtS * U' * ρ̄ᴸ * ρL @@ -128,87 +125,47 @@ function _permute_edge(t::Union{T1, T2}) where {E, S, T1 <: AbstractTensorMap{E, end function semi_renormalize(network::InfiniteTriangularNetwork, env::CTMRGEnvTriangular, Pas, Pbs, trunc) - projectors = dtmap(eachcoordinate(network, 1:6)) do (dir, r, c) + edges = dtmap(eachcoordinate(network, 1:6)) do (dir, r, c) mat = semi_renormalize_edge(network, env, Pas, Pbs, dir, r, c) - - U, S, V = svd_full(mat) - + trunc′ = _truncation_strategy(trunc, env.Ea[dir, r, c]) + U, S, V = svd_trunc(mat; trunc = trunc′) sqrtS = sdiag_pow(S, 0.5) - Ẽb = U * sqrtS - Ẽa = sqrtS * V - - trunc = _truncation_strategy(trunc, env.Ea[dir, r, c]) - - P = ignore_derivatives() do - I = id(storagetype(S), space(S, 1)) - (P, _, _), _ = truncate(svd_trunc!, (I, DiagonalTensorMap(S), I), trunc) - return P - end - Ẽbtr = Ẽb * P - Ẽatr = _permute_edge(P' * Ẽa) - - return _permute_edge(Ẽa), Ẽb, Ẽatr, Ẽbtr + Ẽa = _permute_edge(sqrtS * V) + return mat, Ẽa, Ẽb end - return getindex.(projectors, 1), getindex.(projectors, 2), getindex.(projectors, 3), getindex.(projectors, 4) -end - -function build_matrix_second_projectors(network::InfiniteTriangularNetwork, env::CTMRGEnvTriangular, Ẽas, Ẽbs, Ẽastr, Ẽbstr, trunc; conditioning = true) - projectors = dtmap(eachcoordinate(network, 1:6)) do (dir, r, c) - trunc = _truncation_strategy(trunc, env.Ea[dir, r, c]) - σL, σR = build_halfinfinite_projectors(network, env, Ẽas, Ẽbs, Ẽastr, Ẽbstr, dir, r, c) - if conditioning - σL /= norm(σL) - σR /= norm(σR) - _, SL, VLᴴ = svd_full(σL) - UR, SR, _ = svd_full(σR) - - sqrtSL = sdiag_pow(SL, 0.5) - sqrtSR = sdiag_pow(SR, 0.5) - - FLU = sqrtSL * VLᴴ - FRU = UR * sqrtSR - - mat = FLU * FRU - mat /= norm(mat) - WU, SU, QUᴴ = svd_trunc(mat; trunc) - isqrtSU = sdiag_pow(SU, -0.5) - - Qa = isqrtSU * WU' * FLU - Qb = FRU * QUᴴ' * isqrtSU - else - mat = σL * σR - mat /= norm(mat) - U, S, V = svd_trunc(mat; trunc) - isqrtS = sdiag_pow(S, -0.5) - Qa = isqrtS * U' * σL - Qb = σR * V' * isqrtS - end - return Qa, Qb + return getindex.(edges, 1), CTMRGEnvTriangular(env.C, getindex.(edges, 2), getindex.(edges, 3)) +end + +function _truncate_edge(mat, ρL, ρR, trunc; conditioning = true) + ρL /= norm(ρL) + ρR /= norm(ρR) + if conditioning + _, SL, VLᴴ = svd_full(ρL) + UR, SR, _ = svd_full(ρR) + sqrtSL = sdiag_pow(SL, 0.5) + sqrtSR = sdiag_pow(SR, 0.5) + ρL = sqrtSL * VLᴴ + ρR = UR * sqrtSR end - PL = map(x -> x[1], projectors) - PR = map(x -> x[2], projectors) - return PL, PR -end -function renormalize_edges(env::CTMRGEnvTriangular, Ẽas::Array{T1, 3}, Ẽbs::Array{T1, 3}, Qas::Array{T2, 3}, Qbs::Array{T2, 3}) where {E, S, T1 <: AbstractTensorMap{E, S, 3, 1}, T2 <: AbstractTensorMap{E, S, 1, 1}} - new_edges = dtmap(eachcoordinate(env, 1:6)) do (dir, r, c) - Eb_new = Ẽbs[dir, r, c] * Qbs[dir, r, c] - Ea_new = permute(Qas[dir, r, c] * permute(Ẽas[dir, r, c], ((1,), (2, 3, 4))), ((1, 2, 3), (4,))) - return Ea_new, Eb_new - end - Ea = map(x -> x[1], new_edges) - Eb = map(x -> x[2], new_edges) - return CTMRGEnvTriangular(env.C, Ea, Eb) + ρ = ρL * mat * ρR + + ρ /= norm(ρ) + U, S, Vᴴ = svd_trunc(ρ; trunc) + isqrtS = sdiag_pow(S, -0.5) + Ea = _permute_edge(isqrtS * U' * ρL * mat) + Eb = mat * ρR * Vᴴ' * isqrtS + return Ea, Eb end -function renormalize_edges(env::CTMRGEnvTriangular, Ẽas::Array{T1, 3}, Ẽbs::Array{T1, 3}, Qas::Array{T2, 3}, Qbs::Array{T2, 3}) where {T1 <: CTMRGEdgeTensor, T2 <: CTMRGCornerTensor} - new_edges = dtmap(eachcoordinate(env, 1:6)) do (dir, r, c) - Eb_new = Ẽbs[dir, r, c] * Qbs[dir, r, c] - Ea_new = permute(Qas[dir, r, c] * permute(Ẽas[dir, r, c], ((1,), (2, 3))), ((1, 2), (3,))) - return Ea_new, Eb_new +function renormalize_edges(network::InfiniteTriangularNetwork, env::CTMRGEnvTriangular, mats, trunc; conditioning = true) + edges = dtmap(eachcoordinate(network, 1:6)) do (dir, r, c) + ρL, ρR = build_halfinfinite_environments(network, env, dir, r, c) + trunc′ = _truncation_strategy(trunc, env.Ea[dir, r, c]) + return _truncate_edge(mats[dir, r, c], ρL, ρR, trunc′; conditioning) end - return CTMRGEnvTriangular(env.C, getindex.(new_edges, 1), getindex.(new_edges, 2)) + return normalize_edges(CTMRGEnvTriangular(env.C, getindex.(edges, 1), getindex.(edges, 2))) end function normalize_corners(env::CTMRGEnvTriangular) diff --git a/test/ctmrg/triangular.jl b/test/ctmrg/triangular.jl index 04ae6b2af..2a5a8b574 100644 --- a/test/ctmrg/triangular.jl +++ b/test/ctmrg/triangular.jl @@ -41,6 +41,104 @@ function classical_ising_triangular(β) return TensorMap(o2, ℂ^2 * ℂ^2 * ℂ^2, ℂ^2 * ℂ^2 * ℂ^2) end +@testset "CTM_triangular - Direct edge split" begin + Random.seed!(19461) + vspace = ComplexSpace(2) + envspace = ComplexSpace(3) + for (V, inds) in ((envspace ⊗ vspace, (3, 2)), (envspace ⊗ vspace ⊗ vspace', (4, 2, 3))), conditioning in (false, true), ill_conditioned in (false, true) + mat, ρL, ρR = (randn(ComplexF64, V, V) + 5 * id(V) for _ in 1:3) + if ill_conditioned + block(ρL, Trivial()) .= Diagonal([ones(dim(V) - 1); 1.0e-14]) + block(ρR, Trivial()) .= Diagonal([ones(dim(V) - 1); 1.0e-14]) + end + L, R = ρL / norm(ρL), ρR / norm(ρR) + if conditioning + _, SL, VL = svd_full(L) + UR, SR, _ = svd_full(R) + L = PEPSKit.sdiag_pow(SL, 0.5) * VL + R = UR * PEPSKit.sdiag_pow(SR, 0.5) + end + ρ = L * mat * R + for trunc in (notrunc(), truncrank(3), truncspace(envspace)) + Ea, Eb = PEPSKit._truncate_edge(mat, ρL, ρR, trunc; conditioning) + reconstructed = Eb * permute(Ea, ((1,), inds)) + U, S, Vᴴ = svd_trunc(ρ / norm(ρ); trunc) + @test space(Ea, 1) == space(S, 1) + @test L * reconstructed * R ≈ norm(ρ)^2 * U * S * Vᴴ + if trunc == notrunc() && !ill_conditioned + @test reconstructed / norm(reconstructed) ≈ mat / norm(mat) + end + end + end +end + +@testset "CTM_triangular - Direct edge coordinates" begin + Random.seed!(68191) + sz = (3, 4) + vspace = ComplexSpace(1) + offsets = ((-1, 0), (-1, 1), (0, 1), (1, 0), (1, -1), (0, -1)) + for sandwich in (false, true) + tensors = map(Iterators.product(1:sz[1], 1:sz[2])) do _ + if sandwich + V = vspace^3 ⊗ (vspace')^3 + return randn(ComplexF64, vspace, V), randn(ComplexF64, vspace, V) + end + return randn(ComplexF64, vspace^3, vspace^3) + end + network = InfiniteTriangularNetwork(tensors) + env = CTMRGEnvTriangular(randn, ComplexF64, sandwich ? vspace ⊗ vspace' : vspace, vspace; unitcell = sz) + at(A, d, r, c) = only(A[mod1(d, 6), mod1(r, sz[1]), mod1(c, sz[2])].data) + function site(r, c) + t = tensors[mod1(r, sz[1]), mod1(c, sz[2])] + return sandwich ? only(t[1].data) * conj(only(t[2].data)) : only(t.data) + end + for dir in 1:6, r in 1:sz[1], c in 1:sz[2] + δL, δR = offsets[mod1(dir + 4, 6)], offsets[mod1(dir + 3, 6)] + rL, cL = r + δL[1], c + δL[2] + rR, cR = r + δR[1], c + δR[2] + left = site(rL, cL) * at(env.Ea, dir + 3, rL + δR[1], cL + δR[2]) + right = site(rR, cR) * at(env.Eb, dir + 3, rR + δL[1], cR + δL[2]) + for i in 0:2 + δ = offsets[mod1(dir - i, 6)] + left *= at(env.C, dir - i, rL + δ[1], cL + δ[2]) + δ = offsets[mod1(dir + i + 1, 6)] + right *= at(env.C, dir + i + 1, rR + δ[1], cR + δ[2]) + end + ρL, ρR = PEPSKit.build_halfinfinite_environments(network, env, dir, r, c) + @test only(ρL.data) ≈ left + @test only(ρR.data) ≈ right + end + end +end + +@testset "CTM_triangular - Simultaneous edge update" begin + Random.seed!(38471) + vspace = ComplexSpace(2) + ket = randn(ComplexF64, ComplexSpace(2), vspace^3 ⊗ (vspace')^3) + pf = randn(ComplexF64, vspace^3, vspace^3) + for (sandwich, V) in ((pf, vspace), ((ket, ket), vspace ⊗ vspace')) + network = InfiniteTriangularNetwork(fill(sandwich, (1, 1))) + env = CTMRGEnvTriangular(randn, ComplexF64, V, ComplexSpace(2)) + trunc = PEPSKit.FixedSpaceTruncation() + Pas, Pbs, = PEPSKit.calculate_projectors(network, env, trunc, :twothirds) + mats, preliminary = PEPSKit.semi_renormalize(network, env, Pas, Pbs, trunc) + for conditioning in (false, true) + previous = deepcopy(preliminary) + edges = map(PEPSKit.eachcoordinate(network, 1:6)) do (dir, r, c) + ρL, ρR = PEPSKit.build_halfinfinite_environments(network, preliminary, dir, r, c) + trunc′ = PEPSKit._truncation_strategy(trunc, preliminary.Ea[dir, r, c]) + return PEPSKit._truncate_edge(mats[dir, r, c], ρL, ρR, trunc′; conditioning) + end + expected = PEPSKit.normalize_edges(CTMRGEnvTriangular(preliminary.C, getindex.(edges, 1), getindex.(edges, 2))) + updated = PEPSKit.renormalize_edges(network, preliminary, mats, trunc; conditioning) + @test all(isapprox.(updated.Ea, expected.Ea)) + @test all(isapprox.(updated.Eb, expected.Eb)) + @test preliminary.Ea == previous.Ea + @test preliminary.Eb == previous.Eb + end + end +end + @testset "CTM_triangular - Random tensor" begin for conditioning in [true false] for projector_alg in [:twothirds :full] From 7ed746470db16823eeca9d4eb78f29844276fd78 Mon Sep 17 00:00:00 2001 From: Philipp Schmoll Date: Tue, 27 Oct 2026 17:44:54 +0100 Subject: [PATCH 17/41] =?UTF-8?q?Organize=20triangular=20contractions=20an?= =?UTF-8?q?d=20adopt=20d/D/=CF=87=20index=20labels?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/PEPSKit.jl | 7 +- .../ctmrg_contractions_triangular.jl | 104 --------------- .../triangular_ctmrg/double_corner.jl | 23 ++++ .../triangular_ctmrg/expectation_value.jl | 18 +++ .../triangular_ctmrg/halfinf_env.jl | 41 ++++++ .../triangular_ctmrg/network_value.jl | 103 +++++++++++++++ .../triangular_ctmrg/renormalize_corner.jl | 23 ++++ .../triangular_ctmrg/renormalize_edge.jl | 22 +++ src/algorithms/toolbox_triangular.jl | 125 ------------------ 9 files changed, 236 insertions(+), 230 deletions(-) delete mode 100644 src/algorithms/contractions/ctmrg_contractions_triangular.jl create mode 100644 src/algorithms/contractions/triangular_ctmrg/double_corner.jl create mode 100644 src/algorithms/contractions/triangular_ctmrg/expectation_value.jl create mode 100644 src/algorithms/contractions/triangular_ctmrg/halfinf_env.jl create mode 100644 src/algorithms/contractions/triangular_ctmrg/network_value.jl create mode 100644 src/algorithms/contractions/triangular_ctmrg/renormalize_corner.jl create mode 100644 src/algorithms/contractions/triangular_ctmrg/renormalize_edge.jl diff --git a/src/PEPSKit.jl b/src/PEPSKit.jl index 4c4b6caca..131088971 100644 --- a/src/PEPSKit.jl +++ b/src/PEPSKit.jl @@ -149,7 +149,12 @@ include("algorithms/truncation/fullenv_truncation.jl") include("algorithms/truncation/bond_tensor.jl") include("algorithms/truncation/bond_truncation.jl") -include("algorithms/contractions/ctmrg_contractions_triangular.jl") +include("algorithms/contractions/triangular_ctmrg/double_corner.jl") +include("algorithms/contractions/triangular_ctmrg/renormalize_edge.jl") +include("algorithms/contractions/triangular_ctmrg/halfinf_env.jl") +include("algorithms/contractions/triangular_ctmrg/renormalize_corner.jl") +include("algorithms/contractions/triangular_ctmrg/network_value.jl") +include("algorithms/contractions/triangular_ctmrg/expectation_value.jl") include("algorithms/ctmrg_triangular/ctmrg.jl") include("algorithms/ctmrg_triangular/utility.jl") include("algorithms/ctmrg_triangular/simultaneous.jl") diff --git a/src/algorithms/contractions/ctmrg_contractions_triangular.jl b/src/algorithms/contractions/ctmrg_contractions_triangular.jl deleted file mode 100644 index c40b6beff..000000000 --- a/src/algorithms/contractions/ctmrg_contractions_triangular.jl +++ /dev/null @@ -1,104 +0,0 @@ -function build_double_corner_matrix_triangular(network::InfiniteTriangularNetwork{P}, env::CTMRGEnvTriangular, dir::Int, r::Int, c::Int) where {P <: PFTensorTriangular} - @tensor opt = true mat[-1 -2; -3 -4] := - env.C[_coordinates(dir, 0, r, c, size(network))...][6 5; 1] * - env.C[_coordinates(dir + 1, 0, r, c, size(network))...][1 3; 2] * - env.Ea[_coordinates(dir - 1, 0, r, c, size(network))...][-1 7; 6] * - env.Eb[_coordinates(dir + 1, 0, r, c, size(network))...][2 4; -3] * - rotl60(network[r, c], mod(dir - 1, 6))[7 -2 -4; 5 3 4] - return mat -end - -function build_double_corner_matrix_triangular(network::InfiniteTriangularNetwork{P}, env::CTMRGEnvTriangular, dir::Int, r::Int, c::Int) where {P <: PEPSSandwichTriangular} - O_r_c = network[r, c] - @tensor opt = true mat[-1 -2 -3; -4 -5 -6] := - env.C[_coordinates(dir, 0, r, c, size(network))...][6 5 8; 1] * - env.C[_coordinates(dir + 1, 0, r, c, size(network))...][1 3 9; 2] * - env.Ea[_coordinates(dir - 1, 0, r, c, size(network))...][-1 7 10; 6] * - env.Eb[_coordinates(dir + 1, 0, r, c, size(network))...][2 4 11; -4] * - rotl60(ket(O_r_c), mod(dir - 1, 6))[12; 5 3 4 -5 -2 7] * - conj(rotl60(bra(O_r_c), mod(dir - 1, 6))[12; 8 9 11 -6 -3 10]) - return mat -end - -function semi_renormalize_edge(network::InfiniteTriangularNetwork{P}, env::CTMRGEnvTriangular, Pas, Pbs, dir, r, c) where {P <: PEPSSandwichTriangular} - O_r_c = network[r, c] - @tensor opt = true mat[-1 -2 -3; -4 -5 -6] := - Pas[dir, r, c][-1; 1 2 8] * Pbs[dir, r, c][6 7 9; -4] * - env.Ea[_coordinates(dir, 0, r, c, size(network))...][1 3 10; 4] * - env.Eb[_coordinates(dir, 1, r, c, size(network))...][4 5 11; 6] * - rotl60(ket(O_r_c), mod(dir - 1, 6))[12; 3 5 7 -5 -2 2] * - conj(rotl60(bra(O_r_c), mod(dir - 1, 6))[12; 10 11 9 -6 -3 8]) - return mat / norm(mat) -end - -function semi_renormalize_edge(network::InfiniteTriangularNetwork{P}, env::CTMRGEnvTriangular, Pas, Pbs, dir, r, c) where {P <: PFTensorTriangular} - @tensor opt = true mat[-1 -2; -3 -4] := - Pas[dir, r, c][-1; 1 2] * - Pbs[dir, r, c][6 7; -3] * - env.Ea[_coordinates(dir, 0, r, c, size(network))...][1 3; 4] * - env.Eb[_coordinates(dir, 1, r, c, size(network))...][4 5; 6] * - rotl60(network[r, c], mod(dir - 1, 6))[2 -2 -4; 3 5 7] - return mat / norm(mat) -end - -function build_halfinfinite_environments(network::InfiniteTriangularNetwork{P}, env::CTMRGEnvTriangular, dir, r, c) where {P <: PEPSSandwichTriangular} - rL, cL = _coordinates(dir + 4, 0, r, c, size(network))[2:3] - rR, cR = _coordinates(dir + 3, 0, r, c, size(network))[2:3] - O_L = network[rL, cL] - O_R = network[rR, cR] - @tensor opt = true ρL[-1 -2 -3; -4 -5 -6] := - env.C[_coordinates(dir, 0, rL, cL, size(network))...][1 2 10; -4] * - env.C[_coordinates(dir - 1, 0, rL, cL, size(network))...][4 3 11; 1] * - env.C[_coordinates(dir - 2, 0, rL, cL, size(network))...][6 5 12; 4] * - env.Ea[_coordinates(dir + 3, 0, rL, cL, size(network))...][-1 7 14; 6] * - rotl60(ket(O_L), mod(dir - 1, 6))[15; 2 -5 -2 7 5 3] * - conj(rotl60(bra(O_L), mod(dir - 1, 6))[15; 10 -6 -3 14 12 11]) - @tensor opt = true ρR[-1 -2 -3; -4 -5 -6] := - env.C[_coordinates(dir + 1, 0, rR, cR, size(network))...][-1 2 10; 1] * - env.C[_coordinates(dir + 2, 0, rR, cR, size(network))...][1 3 11; 4] * - env.C[_coordinates(dir + 3, 0, rR, cR, size(network))...][4 5 12; 6] * - env.Eb[_coordinates(dir + 3, 1, rR, cR, size(network))...][6 7 13; -4] * - rotl60(ket(O_R), mod(dir - 1, 6))[15; -2 2 3 5 7 -5] * - conj(rotl60(bra(O_R), mod(dir - 1, 6))[15; -3 10 11 12 13 -6]) - return ρL, ρR -end - -function build_halfinfinite_environments(network::InfiniteTriangularNetwork{P}, env::CTMRGEnvTriangular, dir, r, c) where {P <: PFTensorTriangular} - rL, cL = _coordinates(dir + 4, 0, r, c, size(network))[2:3] - rR, cR = _coordinates(dir + 3, 0, r, c, size(network))[2:3] - @tensor opt = true ρL[-1 -2; -3 -4] := - env.C[_coordinates(dir, 0, rL, cL, size(network))...][1 2; -3] * - env.C[_coordinates(dir - 1, 0, rL, cL, size(network))...][4 3; 1] * - env.C[_coordinates(dir - 2, 0, rL, cL, size(network))...][6 5; 4] * - env.Ea[_coordinates(dir + 3, 0, rL, cL, size(network))...][-1 7; 6] * - rotl60(network[rL, cL], mod(dir - 1, 6))[3 5 7; 2 -4 -2] - @tensor opt = true ρR[-1 -2; -3 -4] := - env.C[_coordinates(dir + 1, 0, rR, cR, size(network))...][-1 2; 1] * - env.C[_coordinates(dir + 2, 0, rR, cR, size(network))...][1 3; 4] * - env.C[_coordinates(dir + 3, 0, rR, cR, size(network))...][4 5; 6] * - env.Eb[_coordinates(dir + 3, 1, rR, cR, size(network))...][6 7; -3] * - rotl60(network[rR, cR], mod(dir - 1, 6))[-4 7 5; -2 2 3] - return ρL, ρR -end - -function renormalize_corner_triangular((dir, r, c), network::InfiniteTriangularNetwork{P}, env, Pas, Pbs) where {P <: PFTensorTriangular} - @tensor opt = true env_C_new[-1 -2; -3] := - env.C[_coordinates(dir, 0, r, c, size(network))...][1 3; 6] * - env.Ea[_coordinates(dir - 1, 0, r, c, size(network))...][4 2; 1] * - env.Eb[_coordinates(dir, 1, r, c, size(network))...][6 7; 8] * - rotl60(network[r, c], mod(dir - 1, 6))[2 5 -2; 3 7 9] * - Pas[mod1(dir - 1, 6), r, c][-1; 4 5] * Pbs[dir, r, c][8 9; -3] - return env_C_new -end - -function renormalize_corner_triangular((dir, r, c), network::InfiniteTriangularNetwork{P}, env, Pas, Pbs) where {P <: PEPSSandwichTriangular} - O_r_c = network[r, c] - @tensor opt = true env_C_new[-1 -2 -3; -4] := - env.C[_coordinates(dir, 0, r, c, size(network))...][1 3 10; 6] * - env.Ea[_coordinates(dir - 1, 0, r, c, size(network))...][4 2 11; 1] * - env.Eb[_coordinates(dir, 1, r, c, size(network))...][6 7 12; 8] * - rotl60(ket(O_r_c), mod(dir - 1, 6))[15; 3 7 9 -2 5 2] * - conj(rotl60(bra(O_r_c), mod(dir - 1, 6))[15; 10 12 14 -3 13 11]) * - Pas[mod1(dir - 1, 6), r, c][-1; 4 5 13] * Pbs[dir, r, c][8 9 14; -4] - return env_C_new -end diff --git a/src/algorithms/contractions/triangular_ctmrg/double_corner.jl b/src/algorithms/contractions/triangular_ctmrg/double_corner.jl new file mode 100644 index 000000000..7f3e85b0d --- /dev/null +++ b/src/algorithms/contractions/triangular_ctmrg/double_corner.jl @@ -0,0 +1,23 @@ +# Directions refer to the local frame after rotl60; t/b denote ket/bra layers. + +function build_double_corner_matrix_triangular(network::InfiniteTriangularNetwork{P}, env::CTMRGEnvTriangular, dir::Int, r::Int, c::Int) where {P <: PFTensorTriangular} + @tensor opt = true mat[χW D240; χE D300] := + env.C[_coordinates(dir, 0, r, c, size(network))...][χNW D120; χN] * + env.C[_coordinates(dir + 1, 0, r, c, size(network))...][χN D60; χNE] * + env.Ea[_coordinates(dir - 1, 0, r, c, size(network))...][χW D180; χNW] * + env.Eb[_coordinates(dir + 1, 0, r, c, size(network))...][χNE D0; χE] * + rotl60(network[r, c], mod(dir - 1, 6))[D180 D240 D300; D120 D60 D0] + return mat +end + +function build_double_corner_matrix_triangular(network::InfiniteTriangularNetwork{P}, env::CTMRGEnvTriangular, dir::Int, r::Int, c::Int) where {P <: PEPSSandwichTriangular} + O_r_c = network[r, c] + @tensor opt = true mat[χW Dt240 Db240; χE Dt300 Db300] := + env.C[_coordinates(dir, 0, r, c, size(network))...][χNW Dt120 Db120; χN] * + env.C[_coordinates(dir + 1, 0, r, c, size(network))...][χN Dt60 Db60; χNE] * + env.Ea[_coordinates(dir - 1, 0, r, c, size(network))...][χW Dt180 Db180; χNW] * + env.Eb[_coordinates(dir + 1, 0, r, c, size(network))...][χNE Dt0 Db0; χE] * + rotl60(ket(O_r_c), mod(dir - 1, 6))[d; Dt120 Dt60 Dt0 Dt300 Dt240 Dt180] * + conj(rotl60(bra(O_r_c), mod(dir - 1, 6))[d; Db120 Db60 Db0 Db300 Db240 Db180]) + return mat +end diff --git a/src/algorithms/contractions/triangular_ctmrg/expectation_value.jl b/src/algorithms/contractions/triangular_ctmrg/expectation_value.jl new file mode 100644 index 000000000..e591936b1 --- /dev/null +++ b/src/algorithms/contractions/triangular_ctmrg/expectation_value.jl @@ -0,0 +1,18 @@ +function _contract_edges_0((r, c)::Tuple{Int, Int}, network::InfiniteTriangularNetwork{P}, env::CTMRGEnvTriangular, op::AbstractTensorMap{E, S, 2, 2}) where {P <: PEPSSandwichTriangular, E, S} + O_r_c = network[r, c] + O_r_cn = network[r, _next(c, end)] + return @tensor opt = true ket(O_r_c)[dtL; DLt120 DLt60 DLt0 DLt300 DLt240 DLt180] * ket(O_r_cn)[dtR; DRt120 DRt60 DRt0 DRt300 DRt240 DLt0] * + conj(bra(O_r_c)[dbL; DLb120 DLb60 DLb0 DLb300 DLb240 DLb180]) * conj(bra(O_r_cn)[dbR; DRb120 DRb60 DRb0 DRb300 DRb240 DLb0]) * + env.C[1, _prev(r, end), c][χNW DLt120 DLb120; χaN] * env.C[2, _prev(r, end), _next(c + 1, end)][χbN DRt60 DRb60; χNE] * env.C[3, r, _next(c + 1, end)][χNE DRt0 DRb0; χSE] * + env.C[4, _next(r, end), _next(c, end)][χSE DRt300 DRb300; χaS] * env.C[5, _next(r, end), _prev(c, end)][χbS DLt240 DLb240; χSW] * env.C[6, r, _prev(c, end)][χSW DLt180 DLb180; χNW] * + env.Eb[1, _prev(r, end), _next(c, end)][χaN DLt60 DLb60; χCN] * env.Ea[1, _prev(r, end), _next(c, end)][χCN DRt120 DRb120; χbN] * + env.Eb[4, _next(r, end), c][χaS DRt240 DRb240; χCS] * env.Ea[4, _next(r, end), c][χCS DLt300 DLb300; χbS] * + op[dbL dbR; dtL dtR] +end + +function _contract_corners((r, c)::Tuple{Int, Int}, network::InfiniteTriangularNetwork{P}, env::CTMRGEnvTriangular, op::AbstractTensorMap{E, S, 1, 1}) where {P <: PEPSSandwichTriangular, E, S} + O_r_c = network[r, c] + return @tensor opt = true env.C[1, _prev(r, end), c][χNW Dt120 Db120; χN] * env.C[2, _prev(r, end), _next(c, end)][χN Dt60 Db60; χNE] * env.C[3, r, _next(c, end)][χNE Dt0 Db0; χSE] * + env.C[4, _next(r, end), c][χSE Dt300 Db300; χS] * env.C[5, _next(r, end), _prev(c, end)][χS Dt240 Db240; χSW] * env.C[6, r, _prev(c, end)][χSW Dt180 Db180; χNW] * + ket(O_r_c)[dt; Dt120 Dt60 Dt0 Dt300 Dt240 Dt180] * conj(bra(O_r_c)[db; Db120 Db60 Db0 Db300 Db240 Db180]) * op[db; dt] +end diff --git a/src/algorithms/contractions/triangular_ctmrg/halfinf_env.jl b/src/algorithms/contractions/triangular_ctmrg/halfinf_env.jl new file mode 100644 index 000000000..f93e2baaa --- /dev/null +++ b/src/algorithms/contractions/triangular_ctmrg/halfinf_env.jl @@ -0,0 +1,41 @@ +# Directions refer to the local frame after rotl60; t/b denote ket/bra layers. + +function build_halfinfinite_environments(network::InfiniteTriangularNetwork{P}, env::CTMRGEnvTriangular, dir, r, c) where {P <: PEPSSandwichTriangular} + rL, cL = _coordinates(dir + 4, 0, r, c, size(network))[2:3] + rR, cR = _coordinates(dir + 3, 0, r, c, size(network))[2:3] + O_L = network[rL, cL] + O_R = network[rR, cR] + @tensor opt = true ρL[χSE Dt0 Db0; χN Dt60 Db60] := + env.C[_coordinates(dir, 0, rL, cL, size(network))...][χNW Dt120 Db120; χN] * + env.C[_coordinates(dir - 1, 0, rL, cL, size(network))...][χSW Dt180 Db180; χNW] * + env.C[_coordinates(dir - 2, 0, rL, cL, size(network))...][χS Dt240 Db240; χSW] * + env.Ea[_coordinates(dir + 3, 0, rL, cL, size(network))...][χSE Dt300 Db300; χS] * + rotl60(ket(O_L), mod(dir - 1, 6))[d; Dt120 Dt60 Dt0 Dt300 Dt240 Dt180] * + conj(rotl60(bra(O_L), mod(dir - 1, 6))[d; Db120 Db60 Db0 Db300 Db240 Db180]) + @tensor opt = true ρR[χN Dt120 Db120; χSW Dt180 Db180] := + env.C[_coordinates(dir + 1, 0, rR, cR, size(network))...][χN Dt60 Db60; χNE] * + env.C[_coordinates(dir + 2, 0, rR, cR, size(network))...][χNE Dt0 Db0; χSE] * + env.C[_coordinates(dir + 3, 0, rR, cR, size(network))...][χSE Dt300 Db300; χS] * + env.Eb[_coordinates(dir + 3, 1, rR, cR, size(network))...][χS Dt240 Db240; χSW] * + rotl60(ket(O_R), mod(dir - 1, 6))[d; Dt120 Dt60 Dt0 Dt300 Dt240 Dt180] * + conj(rotl60(bra(O_R), mod(dir - 1, 6))[d; Db120 Db60 Db0 Db300 Db240 Db180]) + return ρL, ρR +end + +function build_halfinfinite_environments(network::InfiniteTriangularNetwork{P}, env::CTMRGEnvTriangular, dir, r, c) where {P <: PFTensorTriangular} + rL, cL = _coordinates(dir + 4, 0, r, c, size(network))[2:3] + rR, cR = _coordinates(dir + 3, 0, r, c, size(network))[2:3] + @tensor opt = true ρL[χSE D0; χN D60] := + env.C[_coordinates(dir, 0, rL, cL, size(network))...][χNW D120; χN] * + env.C[_coordinates(dir - 1, 0, rL, cL, size(network))...][χSW D180; χNW] * + env.C[_coordinates(dir - 2, 0, rL, cL, size(network))...][χS D240; χSW] * + env.Ea[_coordinates(dir + 3, 0, rL, cL, size(network))...][χSE D300; χS] * + rotl60(network[rL, cL], mod(dir - 1, 6))[D180 D240 D300; D120 D60 D0] + @tensor opt = true ρR[χN D120; χSW D180] := + env.C[_coordinates(dir + 1, 0, rR, cR, size(network))...][χN D60; χNE] * + env.C[_coordinates(dir + 2, 0, rR, cR, size(network))...][χNE D0; χSE] * + env.C[_coordinates(dir + 3, 0, rR, cR, size(network))...][χSE D300; χS] * + env.Eb[_coordinates(dir + 3, 1, rR, cR, size(network))...][χS D240; χSW] * + rotl60(network[rR, cR], mod(dir - 1, 6))[D180 D240 D300; D120 D60 D0] + return ρL, ρR +end diff --git a/src/algorithms/contractions/triangular_ctmrg/network_value.jl b/src/algorithms/contractions/triangular_ctmrg/network_value.jl new file mode 100644 index 000000000..749c8584f --- /dev/null +++ b/src/algorithms/contractions/triangular_ctmrg/network_value.jl @@ -0,0 +1,103 @@ +function _contract_edges_0((r, c)::Tuple{Int, Int}, network::InfiniteTriangularNetwork{P}, env::CTMRGEnvTriangular) where {P <: PFTensorTriangular} + return @tensor opt = true network[r, c][DL180 DL240 DL300; DL120 DL60 DL0] * network[r, _next(c, end)][DL0 DR240 DR300; DR120 DR60 DR0] * + env.C[1, _prev(r, end), c][χNW DL120; χaN] * env.C[2, _prev(r, end), _next(c + 1, end)][χbN DR60; χNE] * env.C[3, r, _next(c + 1, end)][χNE DR0; χSE] * + env.C[4, _next(r, end), _next(c, end)][χSE DR300; χaS] * env.C[5, _next(r, end), _prev(c, end)][χbS DL240; χSW] * env.C[6, r, _prev(c, end)][χSW DL180; χNW] * + env.Eb[1, _prev(r, end), _next(c, end)][χaN DL60; χCN] * env.Ea[1, _prev(r, end), _next(c, end)][χCN DR120; χbN] * + env.Eb[4, _next(r, end), c][χaS DR240; χCS] * env.Ea[4, _next(r, end), c][χCS DL300; χbS] +end + +function _contract_edges_60((r, c)::Tuple{Int, Int}, network::InfiniteTriangularNetwork{P}, env::CTMRGEnvTriangular) where {P <: PFTensorTriangular} + return @tensor opt = true network[r, c][DTR180 DBL60 DTR300; DTR120 DTR60 DTR0] * network[_next(r, end), _prev(c, end)][DBL180 DBL240 DBL300; DBL120 DBL60 DBL0] * + env.C[1, _prev(r, end), c][χbNW DTR120; χN] * env.C[2, _prev(r, end), _next(c, end)][χN DTR60; χNE] * env.C[3, r, _next(c, end)][χNE DTR0; χaSE] * + env.C[4, _next(r + 1, end), _prev(c, end)][χbSE DBL300; χS] * env.C[5, _next(r + 1, end), _prev(c - 1, end)][χS DBL240; χSW] * env.C[6, _next(r, end), _prev(c - 1, end)][χSW DBL180; χaNW] * + env.Eb[3, _next(r, end), c][χaSE DTR300; χCSE] * env.Ea[3, _next(r, end), c][χCSE DBL0; χbSE] * + env.Eb[6, r, _prev(c, end)][χaNW DBL120; χCNW] * env.Ea[6, r, _prev(c, end)][χCNW DTR180; χbNW] +end + +function _contract_edges_120((r, c)::Tuple{Int, Int}, network::InfiniteTriangularNetwork{P}, env::CTMRGEnvTriangular) where {P <: PFTensorTriangular} + return @tensor opt = true network[r, c][DTL180 DTL240 DTL300; DTL120 DTL60 DTL0] * network[_next(r, end), c][DBR180 DBR240 DBR300; DTL300 DBR60 DBR0] * + env.C[1, _prev(r, end), c][χNW DTL120; χN] * env.C[2, _prev(r, end), _next(c, end)][χN DTL60; χaNE] * env.C[3, _next(r, end), _next(c, end)][χbNE DBR0; χSE] * + env.C[4, _next(r + 1, end), c][χSE DBR300; χS] * env.C[5, _next(r + 1, end), _prev(c, end)][χS DBR240; χaSW] * env.C[6, r, _prev(c, end)][χbSW DTL180; χNW] * + env.Eb[2, r, _next(c, end)][χaNE DTL0; χCNE] * env.Ea[2, r, _next(c, end)][χCNE DBR60; χbNE] * + env.Eb[5, _next(r, end), _prev(c, end)][χaSW DBR180; χCSW] * env.Ea[5, _next(r, end), _prev(c, end)][χCSW DTL240; χbSW] +end + +function _contract_site_large((r, c)::Tuple{Int, Int}, network::InfiniteTriangularNetwork{P}, env::CTMRGEnvTriangular) where {P <: PFTensorTriangular} + return @tensor opt = true network[_prev(r, end), c][DNW180 DW60 DNW300; DNW120 DNW60 DNW0] * network[_prev(r, end), _next(c, end)][DNW0 DNE240 DNE300; DNE120 DNE60 DNE0] * + network[r, _next(c, end)][DE180 DE240 DE300; DNE300 DE60 DE0] * network[_next(r, end), c][DSE180 DSE240 DSE300; DSE120 DE240 DSE0] * network[_next(r, end), _prev(c, end)][DSW180 DSW240 DSW300; DSW120 DSW60 DSE180] * + network[r, _prev(c, end)][DW180 DW240 DSW120; DW120 DW60 DW0] * network[r, c][DW0 DSW60 DSE120; DNW300 DNE240 DE180] * + env.C[1, _prev(r - 1, end), c][χaNW DNW120; χbN] * env.Eb[1, _prev(r - 1, end), _next(c, end)][χbN DNW60; χCN] * env.Ea[1, _prev(r - 1, end), _next(c, end)][χCN DNE120; χaN] * + env.C[2, _prev(r - 1, end), _next(c + 1, end)][χaN DNE60; χbNE] * env.Eb[2, _prev(r, end), _next(c + 1, end)][χbNE DNE0; χCNE] * env.Ea[2, _prev(r, end), _next(c + 1, end)][χCNE DE60; χaNE] * + env.C[3, r, _next(c + 1, end)][χaNE DE0; χbSE] * env.Eb[3, _next(r, end), _next(c, end)][χbSE DE300; χCSE] * env.Ea[3, _next(r, end), _next(c, end)][χCSE DSE0; χaSE] * + env.C[4, _next(r + 1, end), c][χaSE DSE300; χbS] * env.Eb[4, _next(r + 1, end), _prev(c, end)][χbS DSE240; χCS] * env.Ea[4, _next(r + 1, end), _prev(c, end)][χCS DSW300; χaS] * + env.C[5, _next(r + 1, end), _prev(c - 1, end)][χaS DSW240; χbSW] * env.Eb[5, _next(r, end), _prev(c - 1, end)][χbSW DSW180; χCSW] * env.Ea[5, _next(r, end), _prev(c - 1, end)][χCSW DW240; χaSW] * + env.C[6, r, _prev(c - 1, end)][χaSW DW180; χbNW] * env.Eb[6, _prev(r, end), _prev(c, end)][χbNW DW120; χCNW] * env.Ea[6, _prev(r, end), _prev(c, end)][χCNW DNW180; χaNW] +end + +function _contract_corners((r, c)::Tuple{Int, Int}, network::InfiniteTriangularNetwork{P}, env::CTMRGEnvTriangular) where {P <: PFTensorTriangular} + return @tensor opt = true env.C[1, _prev(r, end), c][χNW D120; χN] * env.C[2, _prev(r, end), _next(c, end)][χN D60; χNE] * env.C[3, r, _next(c, end)][χNE D0; χSE] * + env.C[4, _next(r, end), c][χSE D300; χS] * env.C[5, _next(r, end), _prev(c, end)][χS D240; χSW] * env.C[6, r, _prev(c, end)][χSW D180; χNW] * + network[r, c][D180 D240 D300; D120 D60 D0] +end + +function _contract_edges_0((r, c)::Tuple{Int, Int}, network::InfiniteTriangularNetwork{P}, env::CTMRGEnvTriangular) where {P <: PEPSSandwichTriangular} + O_r_c = network[r, c] + O_r_cn = network[r, _next(c, end)] + return @tensor opt = true ket(O_r_c)[dL; DLt120 DLt60 DLt0 DLt300 DLt240 DLt180] * ket(O_r_cn)[dR; DRt120 DRt60 DRt0 DRt300 DRt240 DLt0] * + conj(bra(O_r_c)[dL; DLb120 DLb60 DLb0 DLb300 DLb240 DLb180]) * conj(bra(O_r_cn)[dR; DRb120 DRb60 DRb0 DRb300 DRb240 DLb0]) * + env.C[1, _prev(r, end), c][χNW DLt120 DLb120; χaN] * env.C[2, _prev(r, end), _next(c + 1, end)][χbN DRt60 DRb60; χNE] * env.C[3, r, _next(c + 1, end)][χNE DRt0 DRb0; χSE] * + env.C[4, _next(r, end), _next(c, end)][χSE DRt300 DRb300; χaS] * env.C[5, _next(r, end), _prev(c, end)][χbS DLt240 DLb240; χSW] * env.C[6, r, _prev(c, end)][χSW DLt180 DLb180; χNW] * + env.Eb[1, _prev(r, end), _next(c, end)][χaN DLt60 DLb60; χCN] * env.Ea[1, _prev(r, end), _next(c, end)][χCN DRt120 DRb120; χbN] * + env.Eb[4, _next(r, end), c][χaS DRt240 DRb240; χCS] * env.Ea[4, _next(r, end), c][χCS DLt300 DLb300; χbS] +end + +function _contract_edges_60((r, c)::Tuple{Int, Int}, network::InfiniteTriangularNetwork{P}, env::CTMRGEnvTriangular) where {P <: PEPSSandwichTriangular} + O_r_c = network[r, c] + O_rn_cn = network[_next(r, end), _next(c, end)] + return @tensor opt = true ket(O_r_c)[dTR; DTRt120 DTRt60 DTRt0 DTRt300 DBLt60 DTRt180] * ket(O_rn_cn)[dBL; DBLt120 DBLt60 DBLt0 DBLt300 DBLt240 DBLt180] * + conj(bra(O_r_c)[dTR; DTRb120 DTRb60 DTRb0 DTRb300 DBLb60 DTRb180]) * conj(bra(O_rn_cn)[dBL; DBLb120 DBLb60 DBLb0 DBLb300 DBLb240 DBLb180]) * + env.C[1, _prev(r, end), c][χbNW DTRt120 DTRb120; χN] * env.C[2, _prev(r, end), _next(c, end)][χN DTRt60 DTRb60; χNE] * env.C[3, r, _next(c, end)][χNE DTRt0 DTRb0; χaSE] * + env.C[4, _next(r + 1, end), _prev(c, end)][χbSE DBLt300 DBLb300; χS] * env.C[5, _next(r + 1, end), _prev(c - 1, end)][χS DBLt240 DBLb240; χSW] * env.C[6, _next(r, end), _prev(c - 1, end)][χSW DBLt180 DBLb180; χaNW] * + env.Eb[3, _next(r, end), c][χaSE DTRt300 DTRb300; χCSE] * env.Ea[3, _next(r, end), c][χCSE DBLt0 DBLb0; χbSE] * + env.Eb[6, r, _prev(c, end)][χaNW DBLt120 DBLb120; χCNW] * env.Ea[6, r, _prev(c, end)][χCNW DTRt180 DTRb180; χbNW] +end + +function _contract_edges_120((r, c)::Tuple{Int, Int}, network::InfiniteTriangularNetwork{P}, env::CTMRGEnvTriangular) where {P <: PEPSSandwichTriangular} + O_r_c = network[r, c] + O_rn_c = network[_next(r, end), c] + return @tensor opt = true ket(O_r_c)[dTL; DTLt120 DTLt60 DTLt0 DTLt300 DTLt240 DTLt180] * ket(O_rn_c)[dBR; DTLt300 DBRt60 DBRt0 DBRt300 DBRt240 DBRt180] * + conj(bra(O_r_c)[dTL; DTLb120 DTLb60 DTLb0 DTLb300 DTLb240 DTLb180]) * conj(bra(O_rn_c)[dBR; DTLb300 DBRb60 DBRb0 DBRb300 DBRb240 DBRb180]) * + env.C[1, _prev(r, end), c][χNW DTLt120 DTLb120; χN] * env.C[2, _prev(r, end), _next(c, end)][χN DTLt60 DTLb60; χaNE] * env.C[3, _next(r, end), _next(c, end)][χbNE DBRt0 DBRb0; χSE] * + env.C[4, _next(r + 1, end), c][χSE DBRt300 DBRb300; χS] * env.C[5, _next(r + 1, end), _prev(c, end)][χS DBRt240 DBRb240; χaSW] * env.C[6, r, _prev(c, end)][χbSW DTLt180 DTLb180; χNW] * + env.Eb[2, r, _next(c, end)][χaNE DTLt0 DTLb0; χCNE] * env.Ea[2, r, _next(c, end)][χCNE DBRt60 DBRb60; χbNE] * + env.Eb[5, _next(r, end), _prev(c, end)][χaSW DBRt180 DBRb180; χCSW] * env.Ea[5, _next(r, end), _prev(c, end)][χCSW DTLt240 DTLb240; χbSW] +end + +function _contract_site_large((r, c)::Tuple{Int, Int}, network::InfiniteTriangularNetwork{P}, env::CTMRGEnvTriangular) where {P <: PEPSSandwichTriangular} + O_r_c = network[r, c] + O_rp_c = network[_prev(r, end), c] + O_rp_cn = network[_prev(r, end), _next(c, end)] + O_rn_cp = network[_next(r, end), _prev(c, end)] + O_rn_c = network[_next(r, end), c] + O_r_cn = network[r, _next(c, end)] + O_r_cp = network[r, _prev(c, end)] + return @tensor opt = true ket(O_rp_c)[dNW; DNWt120 DNWt60 DNWt0 DNWt300 DWt60 DNWt180] * ket(O_rp_cn)[dNE; DNEt120 DNEt60 DNEt0 DNEt300 DNEt240 DNWt0] * + ket(O_r_cn)[dE; DNEt300 DEt60 DEt0 DEt300 DEt240 DEt180] * ket(O_rn_c)[dSE; DSEt120 DEt240 DSEt0 DSEt300 DSEt240 DSEt180] * ket(O_rn_cp)[dSW; DSWt120 DSWt60 DSEt180 DSWt300 DSWt240 DSWt180] * + ket(O_r_cp)[dW; DWt120 DWt60 DWt0 DSWt120 DWt240 DWt180] * ket(O_r_c)[dCenter; DNWt300 DNEt240 DEt180 DSEt120 DSWt60 DWt0] * + conj(bra(O_rp_c)[dNW; DNWb120 DNWb60 DNWb0 DNWb300 DWb60 DNWb180]) * conj(bra(O_rp_cn)[dNE; DNEb120 DNEb60 DNEb0 DNEb300 DNEb240 DNWb0]) * + conj(bra(O_r_cn)[dE; DNEb300 DEb60 DEb0 DEb300 DEb240 DEb180]) * conj(bra(O_rn_c)[dSE; DSEb120 DEb240 DSEb0 DSEb300 DSEb240 DSEb180]) * conj(bra(O_rn_cp)[dSW; DSWb120 DSWb60 DSEb180 DSWb300 DSWb240 DSWb180]) * + conj(bra(O_r_cp)[dW; DWb120 DWb60 DWb0 DSWb120 DWb240 DWb180]) * conj(bra(O_r_c)[dCenter; DNWb300 DNEb240 DEb180 DSEb120 DSWb60 DWb0]) * + env.C[1, _prev(r - 1, end), c][χaNW DNWt120 DNWb120; χbN] * env.Eb[1, _prev(r - 1, end), _next(c, end)][χbN DNWt60 DNWb60; χCN] * env.Ea[1, _prev(r - 1, end), _next(c, end)][χCN DNEt120 DNEb120; χaN] * + env.C[2, _prev(r - 1, end), _next(c + 1, end)][χaN DNEt60 DNEb60; χbNE] * env.Eb[2, _prev(r, end), _next(c + 1, end)][χbNE DNEt0 DNEb0; χCNE] * env.Ea[2, _prev(r, end), _next(c + 1, end)][χCNE DEt60 DEb60; χaNE] * + env.C[3, r, _next(c + 1, end)][χaNE DEt0 DEb0; χbSE] * env.Eb[3, _next(r, end), _next(c, end)][χbSE DEt300 DEb300; χCSE] * env.Ea[3, _next(r, end), _next(c, end)][χCSE DSEt0 DSEb0; χaSE] * + env.C[4, _next(r + 1, end), c][χaSE DSEt300 DSEb300; χbS] * env.Eb[4, _next(r + 1, end), _prev(c, end)][χbS DSEt240 DSEb240; χCS] * env.Ea[4, _next(r + 1, end), _prev(c, end)][χCS DSWt300 DSWb300; χaS] * + env.C[5, _next(r + 1, end), _prev(c - 1, end)][χaS DSWt240 DSWb240; χbSW] * env.Eb[5, _next(r, end), _prev(c - 1, end)][χbSW DSWt180 DSWb180; χCSW] * env.Ea[5, _next(r, end), _prev(c - 1, end)][χCSW DWt240 DWb240; χaSW] * + env.C[6, r, _prev(c - 1, end)][χaSW DWt180 DWb180; χbNW] * env.Eb[6, _prev(r, end), _prev(c, end)][χbNW DWt120 DWb120; χCNW] * env.Ea[6, _prev(r, end), _prev(c, end)][χCNW DNWt180 DNWb180; χaNW] +end + +function _contract_corners((r, c)::Tuple{Int, Int}, network::InfiniteTriangularNetwork{P}, env::CTMRGEnvTriangular) where {P <: PEPSSandwichTriangular} + O_r_c = network[r, c] + return @tensor opt = true env.C[1, _prev(r, end), c][χNW Dt120 Db120; χN] * env.C[2, _prev(r, end), _next(c, end)][χN Dt60 Db60; χNE] * env.C[3, r, _next(c, end)][χNE Dt0 Db0; χSE] * + env.C[4, _next(r, end), c][χSE Dt300 Db300; χS] * env.C[5, _next(r, end), _prev(c, end)][χS Dt240 Db240; χSW] * env.C[6, r, _prev(c, end)][χSW Dt180 Db180; χNW] * + ket(O_r_c)[d; Dt120 Dt60 Dt0 Dt300 Dt240 Dt180] * conj(bra(O_r_c)[d; Db120 Db60 Db0 Db300 Db240 Db180]) +end diff --git a/src/algorithms/contractions/triangular_ctmrg/renormalize_corner.jl b/src/algorithms/contractions/triangular_ctmrg/renormalize_corner.jl new file mode 100644 index 000000000..00968750b --- /dev/null +++ b/src/algorithms/contractions/triangular_ctmrg/renormalize_corner.jl @@ -0,0 +1,23 @@ +# Directions refer to the local frame after rotl60; t/b denote ket/bra layers. + +function renormalize_corner_triangular((dir, r, c), network::InfiniteTriangularNetwork{P}, env, Pas, Pbs) where {P <: PFTensorTriangular} + @tensor opt = true env_C_new[χSW D300; χSE] := + env.C[_coordinates(dir, 0, r, c, size(network))...][χNW D120; χN] * + env.Ea[_coordinates(dir - 1, 0, r, c, size(network))...][χW D180; χNW] * + env.Eb[_coordinates(dir, 1, r, c, size(network))...][χN D60; χNE] * + rotl60(network[r, c], mod(dir - 1, 6))[D180 D240 D300; D120 D60 D0] * + Pas[mod1(dir - 1, 6), r, c][χSW; χW D240] * Pbs[dir, r, c][χNE D0; χSE] + return env_C_new +end + +function renormalize_corner_triangular((dir, r, c), network::InfiniteTriangularNetwork{P}, env, Pas, Pbs) where {P <: PEPSSandwichTriangular} + O_r_c = network[r, c] + @tensor opt = true env_C_new[χSW Dt300 Db300; χSE] := + env.C[_coordinates(dir, 0, r, c, size(network))...][χNW Dt120 Db120; χN] * + env.Ea[_coordinates(dir - 1, 0, r, c, size(network))...][χW Dt180 Db180; χNW] * + env.Eb[_coordinates(dir, 1, r, c, size(network))...][χN Dt60 Db60; χNE] * + rotl60(ket(O_r_c), mod(dir - 1, 6))[d; Dt120 Dt60 Dt0 Dt300 Dt240 Dt180] * + conj(rotl60(bra(O_r_c), mod(dir - 1, 6))[d; Db120 Db60 Db0 Db300 Db240 Db180]) * + Pas[mod1(dir - 1, 6), r, c][χSW; χW Dt240 Db240] * Pbs[dir, r, c][χNE Dt0 Db0; χSE] + return env_C_new +end diff --git a/src/algorithms/contractions/triangular_ctmrg/renormalize_edge.jl b/src/algorithms/contractions/triangular_ctmrg/renormalize_edge.jl new file mode 100644 index 000000000..a3dacc3bf --- /dev/null +++ b/src/algorithms/contractions/triangular_ctmrg/renormalize_edge.jl @@ -0,0 +1,22 @@ +# Directions refer to the local frame after rotl60; t/b denote ket/bra layers. + +function semi_renormalize_edge(network::InfiniteTriangularNetwork{P}, env::CTMRGEnvTriangular, Pas, Pbs, dir, r, c) where {P <: PEPSSandwichTriangular} + O_r_c = network[r, c] + @tensor opt = true mat[χW Dt240 Db240; χE Dt300 Db300] := + Pas[dir, r, c][χW; χNW Dt180 Db180] * Pbs[dir, r, c][χNE Dt0 Db0; χE] * + env.Ea[_coordinates(dir, 0, r, c, size(network))...][χNW Dt120 Db120; χN] * + env.Eb[_coordinates(dir, 1, r, c, size(network))...][χN Dt60 Db60; χNE] * + rotl60(ket(O_r_c), mod(dir - 1, 6))[d; Dt120 Dt60 Dt0 Dt300 Dt240 Dt180] * + conj(rotl60(bra(O_r_c), mod(dir - 1, 6))[d; Db120 Db60 Db0 Db300 Db240 Db180]) + return mat / norm(mat) +end + +function semi_renormalize_edge(network::InfiniteTriangularNetwork{P}, env::CTMRGEnvTriangular, Pas, Pbs, dir, r, c) where {P <: PFTensorTriangular} + @tensor opt = true mat[χW D240; χE D300] := + Pas[dir, r, c][χW; χNW D180] * + Pbs[dir, r, c][χNE D0; χE] * + env.Ea[_coordinates(dir, 0, r, c, size(network))...][χNW D120; χN] * + env.Eb[_coordinates(dir, 1, r, c, size(network))...][χN D60; χNE] * + rotl60(network[r, c], mod(dir - 1, 6))[D180 D240 D300; D120 D60 D0] + return mat / norm(mat) +end diff --git a/src/algorithms/toolbox_triangular.jl b/src/algorithms/toolbox_triangular.jl index 99da822fc..32ef6346e 100644 --- a/src/algorithms/toolbox_triangular.jl +++ b/src/algorithms/toolbox_triangular.jl @@ -9,131 +9,6 @@ function network_value(network::InfiniteTriangularNetwork, env::CTMRGEnvTriangul end end -function _contract_edges_0((r, c)::Tuple{Int, Int}, network::InfiniteTriangularNetwork{P}, env::CTMRGEnvTriangular) where {P <: PFTensorTriangular} - return @tensor opt = true network[r, c][DL180 DL240 DL300; DL120 DL60 DL0] * network[r, _next(c, end)][DL0 DR240 DR300; DR120 DR60 DR0] * - env.C[1, _prev(r, end), c][χNW DL120; χNa] * env.C[2, _prev(r, end), _next(c + 1, end)][χNb DR60; χNE] * env.C[3, r, _next(c + 1, end)][χNE DR0; χSE] * - env.C[4, _next(r, end), _next(c, end)][χSE DR300; χSa] * env.C[5, _next(r, end), _prev(c, end)][χSb DL240; χSW] * env.C[6, r, _prev(c, end)][χSW DL180; χNW] * - env.Eb[1, _prev(r, end), _next(c, end)][χNa DL60; χNC] * env.Ea[1, _prev(r, end), _next(c, end)][χNC DR120; χNb] * - env.Eb[4, _next(r, end), c][χSa DR240; χSC] * env.Ea[4, _next(r, end), c][χSC DL300; χSb] -end - -function _contract_edges_60((r, c)::Tuple{Int, Int}, network::InfiniteTriangularNetwork{P}, env::CTMRGEnvTriangular) where {P <: PFTensorTriangular} - return @tensor opt = true network[r, c][DTR180 DBL60 DTR300; DTR120 DTR60 DTR0] * network[_next(r, end), _prev(c, end)][DBL180 DBL240 DBL300; DBL120 DBL60 DBL0] * - env.C[1, _prev(r, end), c][χNWb DTR120; χN] * env.C[2, _prev(r, end), _next(c, end)][χN DTR60; χNE] * env.C[3, r, _next(c, end)][χNE DTR0; χSEa] * - env.C[4, _next(r + 1, end), _prev(c, end)][χSEb DBL300; χS] * env.C[5, _next(r + 1, end), _prev(c - 1, end)][χS DBL240; χSW] * env.C[6, _next(r, end), _prev(c - 1, end)][χSW DBL180; χNWa] * - env.Eb[3, _next(r, end), c][χSEa DTR300; χSEC] * env.Ea[3, _next(r, end), c][χSEC DBL0; χSEb] * - env.Eb[6, r, _prev(c, end)][χNWa DBL120; χNWC] * env.Ea[6, r, _prev(c, end)][χNWC DTR180; χNWb] -end - -function _contract_edges_120((r, c)::Tuple{Int, Int}, network::InfiniteTriangularNetwork{P}, env::CTMRGEnvTriangular) where {P <: PFTensorTriangular} - return @tensor opt = true network[r, c][DTL180 DTL240 DTL300; DTL120 DTL60 DTL0] * network[_next(r, end), c][DBR180 DBR240 DBR300; DTL300 DBR60 DBR0] * - env.C[1, _prev(r, end), c][χNW DTL120; χN] * env.C[2, _prev(r, end), _next(c, end)][χN DTL60; χNEa] * env.C[3, _next(r, end), _next(c, end)][χNEb DBR0; χSE] * - env.C[4, _next(r + 1, end), c][χSE DBR300; χS] * env.C[5, _next(r + 1, end), _prev(c, end)][χS DBR240; χSWa] * env.C[6, r, _prev(c, end)][χSWb DTL180; χNW] * - env.Eb[2, r, _next(c, end)][χNEa DTL0; χNEC] * env.Ea[2, r, _next(c, end)][χNEC DBR60; χNEb] * - env.Eb[5, _next(r, end), _prev(c, end)][χSWa DBR180; χSWC] * env.Ea[5, _next(r, end), _prev(c, end)][χSWC DTL240; χSWb] -end - -function _contract_site_large((r, c)::Tuple{Int, Int}, network::InfiniteTriangularNetwork{P}, env::CTMRGEnvTriangular) where {P <: PFTensorTriangular} - return @tensor opt = true network[_prev(r, end), c][DNW180 DW60 DNW300; DNW120 DNW60 DNW0] * network[_prev(r, end), _next(c, end)][DNW0 DNE240 DNE300; DNE120 DNE60 DNE0] * - network[r, _next(c, end)][DE180 DE240 DE300; DNE300 DE60 DE0] * network[_next(r, end), c][DSE180 DSE240 DSE300; DSE120 DE240 DSE0] * network[_next(r, end), _prev(c, end)][DSW180 DSW240 DSW300; DSW120 DSW60 DSE180] * - network[r, _prev(c, end)][DW180 DW240 DSW120; DW120 DW60 DW0] * network[r, c][DW0 DSW60 DSE120; DNW300 DNE240 DE180] * - env.C[1, _prev(r - 1, end), c][χNWa DNW120; χNb] * env.Eb[1, _prev(r - 1, end), _next(c, end)][χNb DNW60; χNC] * env.Ea[1, _prev(r - 1, end), _next(c, end)][χNC DNE120; χNa] * - env.C[2, _prev(r - 1, end), _next(c + 1, end)][χNa DNE60; χNEb] * env.Eb[2, _prev(r, end), _next(c + 1, end)][χNEb DNE0; χNEC] * env.Ea[2, _prev(r, end), _next(c + 1, end)][χNEC DE60; χNEa] * - env.C[3, r, _next(c + 1, end)][χNEa DE0; χSEb] * env.Eb[3, _next(r, end), _next(c, end)][χSEb DE300; χSEC] * env.Ea[3, _next(r, end), _next(c, end)][χSEC DSE0; χSEa] * - env.C[4, _next(r + 1, end), c][χSEa DSE300; χSb] * env.Eb[4, _next(r + 1, end), _prev(c, end)][χSb DSE240; χSC] * env.Ea[4, _next(r + 1, end), _prev(c, end)][χSC DSW300; χSa] * - env.C[5, _next(r + 1, end), _prev(c - 1, end)][χSa DSW240; χSWb] * env.Eb[5, _next(r, end), _prev(c - 1, end)][χSWb DSW180; χSWC] * env.Ea[5, _next(r, end), _prev(c - 1, end)][χSWC DW240; χSWa] * - env.C[6, r, _prev(c - 1, end)][χSWa DW180; χNWb] * env.Eb[6, _prev(r, end), _prev(c, end)][χNWb DW120; χNWC] * env.Ea[6, _prev(r, end), _prev(c, end)][χNWC DNW180; χNWa] -end - -function _contract_corners((r, c)::Tuple{Int, Int}, network::InfiniteTriangularNetwork{P}, env::CTMRGEnvTriangular) where {P <: PFTensorTriangular} - return @tensor opt = true env.C[1, _prev(r, end), c][χNW D120; χN] * env.C[2, _prev(r, end), _next(c, end)][χN D60; χNE] * env.C[3, r, _next(c, end)][χNE D0; χSE] * - env.C[4, _next(r, end), c][χSE D300; χS] * env.C[5, _next(r, end), _prev(c, end)][χS D240; χSW] * env.C[6, r, _prev(c, end)][χSW D180; χNW] * - network[r, c][D180 D240 D300; D120 D60 D0] -end - -### For - -function _contract_edges_0((r, c)::Tuple{Int, Int}, network::InfiniteTriangularNetwork{P}, env::CTMRGEnvTriangular) where {P <: PEPSSandwichTriangular} - O_r_c = network[r, c] - O_r_cn = network[r, _next(c, end)] - return @tensor opt = true ket(O_r_c)[dL; DLt120 DLt60 DLt0 DLt300 DLt240 DLt180] * ket(O_r_cn)[dR; DRt120 DRt60 DRt0 DRt300 DRt240 DLt0] * - conj(bra(O_r_c)[dL; DLb120 DLb60 DLb0 DLb300 DLb240 DLb180]) * conj(bra(O_r_cn)[dR; DRb120 DRb60 DRb0 DRb300 DRb240 DLb0]) * - env.C[1, _prev(r, end), c][χNW DLt120 DLb120; χNa] * env.C[2, _prev(r, end), _next(c + 1, end)][χNb DRt60 DRb60; χNE] * env.C[3, r, _next(c + 1, end)][χNE DRt0 DRb0; χSE] * - env.C[4, _next(r, end), _next(c, end)][χSE DRt300 DRb300; χSa] * env.C[5, _next(r, end), _prev(c, end)][χSb DLt240 DLb240; χSW] * env.C[6, r, _prev(c, end)][χSW DLt180 DLb180; χNW] * - env.Eb[1, _prev(r, end), _next(c, end)][χNa DLt60 DLb60; χNC] * env.Ea[1, _prev(r, end), _next(c, end)][χNC DRt120 DRb120; χNb] * - env.Eb[4, _next(r, end), c][χSa DRt240 DRb240; χSC] * env.Ea[4, _next(r, end), c][χSC DLt300 DLb300; χSb] -end - -function _contract_edges_0((r, c)::Tuple{Int, Int}, network::InfiniteTriangularNetwork{P}, env::CTMRGEnvTriangular, op::AbstractTensorMap{E, S, 2, 2}) where {P <: PEPSSandwichTriangular, E, S} - O_r_c = network[r, c] - O_r_cn = network[r, _next(c, end)] - return @tensor opt = true ket(O_r_c)[dLt; DLt120 DLt60 DLt0 DLt300 DLt240 DLt180] * ket(O_r_cn)[dRt; DRt120 DRt60 DRt0 DRt300 DRt240 DLt0] * - conj(bra(O_r_c)[dLb; DLb120 DLb60 DLb0 DLb300 DLb240 DLb180]) * conj(bra(O_r_cn)[dRb; DRb120 DRb60 DRb0 DRb300 DRb240 DLb0]) * - env.C[1, _prev(r, end), c][χNW DLt120 DLb120; χNa] * env.C[2, _prev(r, end), _next(c + 1, end)][χNb DRt60 DRb60; χNE] * env.C[3, r, _next(c + 1, end)][χNE DRt0 DRb0; χSE] * - env.C[4, _next(r, end), _next(c, end)][χSE DRt300 DRb300; χSa] * env.C[5, _next(r, end), _prev(c, end)][χSb DLt240 DLb240; χSW] * env.C[6, r, _prev(c, end)][χSW DLt180 DLb180; χNW] * - env.Eb[1, _prev(r, end), _next(c, end)][χNa DLt60 DLb60; χNC] * env.Ea[1, _prev(r, end), _next(c, end)][χNC DRt120 DRb120; χNb] * - env.Eb[4, _next(r, end), c][χSa DRt240 DRb240; χSC] * env.Ea[4, _next(r, end), c][χSC DLt300 DLb300; χSb] * - op[dLb dRb; dLt dRt] -end - -function _contract_edges_60((r, c)::Tuple{Int, Int}, network::InfiniteTriangularNetwork{P}, env::CTMRGEnvTriangular) where {P <: PEPSSandwichTriangular} - O_r_c = network[r, c] - O_rn_cn = network[_next(r, end), _next(c, end)] - return @tensor opt = true ket(O_r_c)[dTR; DTRt120 DTRt60 DTRt0 DTRt300 DBLt60 DTRt180] * ket(O_rn_cn)[dBL; DBLt120 DBLt60 DBLt0 DBLt300 DBLt240 DBLt180] * - conj(bra(O_r_c)[dTR; DTRb120 DTRb60 DTRb0 DTRb300 DBLb60 DTRb180]) * conj(bra(O_rn_cn)[dBL; DBLb120 DBLb60 DBLb0 DBLb300 DBLb240 DBLb180]) * - env.C[1, _prev(r, end), c][χNWb DTRt120 DTRb120; χN] * env.C[2, _prev(r, end), _next(c, end)][χN DTRt60 DTRb60; χNE] * env.C[3, r, _next(c, end)][χNE DTRt0 DTRb0; χSEa] * - env.C[4, _next(r + 1, end), _prev(c, end)][χSEb DBLt300 DBLb300; χS] * env.C[5, _next(r + 1, end), _prev(c - 1, end)][χS DBLt240 DBLb240; χSW] * env.C[6, _next(r, end), _prev(c - 1, end)][χSW DBLt180 DBLb180; χNWa] * - env.Eb[3, _next(r, end), c][χSEa DTRt300 DTRb300; χSEC] * env.Ea[3, _next(r, end), c][χSEC DBLt0 DBLb0; χSEb] * - env.Eb[6, r, _prev(c, end)][χNWa DBLt120 DBLb120; χNWC] * env.Ea[6, r, _prev(c, end)][χNWC DTRt180 DTRb180; χNWb] -end - -function _contract_edges_120((r, c)::Tuple{Int, Int}, network::InfiniteTriangularNetwork{P}, env::CTMRGEnvTriangular) where {P <: PEPSSandwichTriangular} - O_r_c = network[r, c] - O_rn_c = network[_next(r, end), c] - return @tensor opt = true ket(O_r_c)[dTL; DTLt120 DTLt60 DTLt0 DTLt300 DTLt240 DTLt180] * ket(O_rn_c)[dBR; DTLt300 DBRt60 DBRt0 DBRt300 DBRt240 DBRt180] * - conj(bra(O_r_c)[dTL; DTLb120 DTLb60 DTLb0 DTLb300 DTLb240 DTLb180]) * conj(bra(O_rn_c)[dBR; DTLb300 DBRb60 DBRb0 DBRb300 DBRb240 DBRb180]) * - env.C[1, _prev(r, end), c][χNW DTLt120 DTLb120; χN] * env.C[2, _prev(r, end), _next(c, end)][χN DTLt60 DTLb60; χNEa] * env.C[3, _next(r, end), _next(c, end)][χNEb DBRt0 DBRb0; χSE] * - env.C[4, _next(r + 1, end), c][χSE DBRt300 DBRb300; χS] * env.C[5, _next(r + 1, end), _prev(c, end)][χS DBRt240 DBRb240; χSWa] * env.C[6, r, _prev(c, end)][χSWb DTLt180 DTLb180; χNW] * - env.Eb[2, r, _next(c, end)][χNEa DTLt0 DTLb0; χNEC] * env.Ea[2, r, _next(c, end)][χNEC DBRt60 DBRb60; χNEb] * - env.Eb[5, _next(r, end), _prev(c, end)][χSWa DBRt180 DBRb180; χSWC] * env.Ea[5, _next(r, end), _prev(c, end)][χSWC DTLt240 DTLb240; χSWb] -end - -function _contract_site_large((r, c)::Tuple{Int, Int}, network::InfiniteTriangularNetwork{P}, env::CTMRGEnvTriangular) where {P <: PEPSSandwichTriangular} - O_r_c = network[r, c] - O_rp_c = network[_prev(r, end), c] - O_rp_cn = network[_prev(r, end), _next(c, end)] - O_rn_cp = network[_next(r, end), _prev(c, end)] - O_rn_c = network[_next(r, end), c] - O_r_cn = network[r, _next(c, end)] - O_r_cp = network[r, _prev(c, end)] - return @tensor opt = true ket(O_rp_c)[dNW; DNWt120 DNWt60 DNWt0 DNWt300 DWt60 DNWt180] * ket(O_rp_cn)[dNE; DNEt120 DNEt60 DNEt0 DNEt300 DNEt240 DNWt0] * - ket(O_r_cn)[dE; DNEt300 DEt60 DEt0 DEt300 DEt240 DEt180] * ket(O_rn_c)[dSE; DSEt120 DEt240 DSEt0 DSEt300 DSEt240 DSEt180] * ket(O_rn_cp)[dSW; DSWt120 DSWt60 DSEt180 DSWt300 DSWt240 DSWt180] * - ket(O_r_cp)[dW; DWt120 DWt60 DWt0 DSWt120 DWt240 DWt180] * ket(O_r_c)[dCenter; DNWt300 DNEt240 DEt180 DSEt120 DSWt60 DWt0] * - conj(bra(O_rp_c)[dNW; DNWb120 DNWb60 DNWb0 DNWb300 DWb60 DNWb180]) * conj(bra(O_rp_cn)[dNE; DNEb120 DNEb60 DNEb0 DNEb300 DNEb240 DNWb0]) * - conj(bra(O_r_cn)[dE; DNEb300 DEb60 DEb0 DEb300 DEb240 DEb180]) * conj(bra(O_rn_c)[dSE; DSEb120 DEb240 DSEb0 DSEb300 DSEb240 DSEb180]) * conj(bra(O_rn_cp)[dSW; DSWb120 DSWb60 DSEb180 DSWb300 DSWb240 DSWb180]) * - conj(bra(O_r_cp)[dW; DWb120 DWb60 DWb0 DSWb120 DWb240 DWb180]) * conj(bra(O_r_c)[dCenter; DNWb300 DNEb240 DEb180 DSEb120 DSWb60 DWb0]) * - env.C[1, _prev(r - 1, end), c][χNWa DNWt120 DNWb120; χNb] * env.Eb[1, _prev(r - 1, end), _next(c, end)][χNb DNWt60 DNWb60; χNC] * env.Ea[1, _prev(r - 1, end), _next(c, end)][χNC DNEt120 DNEb120; χNa] * - env.C[2, _prev(r - 1, end), _next(c + 1, end)][χNa DNEt60 DNEb60; χNEb] * env.Eb[2, _prev(r, end), _next(c + 1, end)][χNEb DNEt0 DNEb0; χNEC] * env.Ea[2, _prev(r, end), _next(c + 1, end)][χNEC DEt60 DEb60; χNEa] * - env.C[3, r, _next(c + 1, end)][χNEa DEt0 DEb0; χSEb] * env.Eb[3, _next(r, end), _next(c, end)][χSEb DEt300 DEb300; χSEC] * env.Ea[3, _next(r, end), _next(c, end)][χSEC DSEt0 DSEb0; χSEa] * - env.C[4, _next(r + 1, end), c][χSEa DSEt300 DSEb300; χSb] * env.Eb[4, _next(r + 1, end), _prev(c, end)][χSb DSEt240 DSEb240; χSC] * env.Ea[4, _next(r + 1, end), _prev(c, end)][χSC DSWt300 DSWb300; χSa] * - env.C[5, _next(r + 1, end), _prev(c - 1, end)][χSa DSWt240 DSWb240; χSWb] * env.Eb[5, _next(r, end), _prev(c - 1, end)][χSWb DSWt180 DSWb180; χSWC] * env.Ea[5, _next(r, end), _prev(c - 1, end)][χSWC DWt240 DWb240; χSWa] * - env.C[6, r, _prev(c - 1, end)][χSWa DWt180 DWb180; χNWb] * env.Eb[6, _prev(r, end), _prev(c, end)][χNWb DWt120 DWb120; χNWC] * env.Ea[6, _prev(r, end), _prev(c, end)][χNWC DNWt180 DNWb180; χNWa] -end - -function _contract_corners((r, c)::Tuple{Int, Int}, network::InfiniteTriangularNetwork{P}, env::CTMRGEnvTriangular) where {P <: PEPSSandwichTriangular} - O_r_c = network[r, c] - return @tensor opt = true env.C[1, _prev(r, end), c][χNW Dt120 Db120; χN] * env.C[2, _prev(r, end), _next(c, end)][χN Dt60 Db60; χNE] * env.C[3, r, _next(c, end)][χNE Dt0 Db0; χSE] * - env.C[4, _next(r, end), c][χSE Dt300 Db300; χS] * env.C[5, _next(r, end), _prev(c, end)][χS Dt240 Db240; χSW] * env.C[6, r, _prev(c, end)][χSW Dt180 Db180; χNW] * - ket(O_r_c)[d; Dt120 Dt60 Dt0 Dt300 Dt240 Dt180] * conj(bra(O_r_c)[d; Db120 Db60 Db0 Db300 Db240 Db180]) -end - -function _contract_corners((r, c)::Tuple{Int, Int}, network::InfiniteTriangularNetwork{P}, env::CTMRGEnvTriangular, op::AbstractTensorMap{E, S, 1, 1}) where {P <: PEPSSandwichTriangular, E, S} - O_r_c = network[r, c] - return @tensor opt = true env.C[1, _prev(r, end), c][χNW Dt120 Db120; χN] * env.C[2, _prev(r, end), _next(c, end)][χN Dt60 Db60; χNE] * env.C[3, r, _next(c, end)][χNE Dt0 Db0; χSE] * - env.C[4, _next(r, end), c][χSE Dt300 Db300; χS] * env.C[5, _next(r, end), _prev(c, end)][χS Dt240 Db240; χSW] * env.C[6, r, _prev(c, end)][χSW Dt180 Db180; χNW] * - ket(O_r_c)[dt; Dt120 Dt60 Dt0 Dt300 Dt240 Dt180] * conj(bra(O_r_c)[db; Db120 Db60 Db0 Db300 Db240 Db180]) * op[db; dt] -end - function energy(network::InfiniteTriangularNetwork{P}, env::CTMRGEnvTriangular, onesite_op, twosite_op) where {P <: PEPSSandwichTriangular} E = sum(Iterators.product(axes(network)...)) do (r, c) expval_onesite = _contract_corners((r, c), network, env, onesite_op) / _contract_corners((r, c), network, env) From c41e3059ce5fab0c92f7a2d4fb6f3ed060a301d1 Mon Sep 17 00:00:00 2001 From: Philipp Schmoll Date: Tue, 27 Oct 2026 17:51:23 +0100 Subject: [PATCH 18/41] Add triangular CTMRG truncation diagnostics --- src/algorithms/ctmrg_triangular/ctmrg.jl | 29 +++++++-- .../ctmrg_triangular/simultaneous.jl | 44 +++++++++---- test/ctmrg/triangular.jl | 64 ++++++++++++++++++- 3 files changed, 119 insertions(+), 18 deletions(-) diff --git a/src/algorithms/ctmrg_triangular/ctmrg.jl b/src/algorithms/ctmrg_triangular/ctmrg.jl index 3e1eff763..f6c6de892 100644 --- a/src/algorithms/ctmrg_triangular/ctmrg.jl +++ b/src/algorithms/ctmrg_triangular/ctmrg.jl @@ -1,5 +1,19 @@ abstract type CTMRGAlgorithmTriangular end +""" + leading_boundary(env₀::CTMRGEnvTriangular, network, alg::CTMRGAlgorithmTriangular) + +Return the contracted environment and convergence information. `info.converged` and +`info.convergence_error` describe convergence; `info.convergence_metric` is retained as +an alias for the latter. `info.contraction_metrics` contains `corner_truncation_error` +and `edge_truncation_error`, maximized over all sites and directions of the final +iteration, and their maximum `truncation_error`. + +Errors are discarded norms divided by the retained SVD spectrum norm, following +PEPSKit's corner-projector convention. They include singular values suppressed by +the inverse-square-root cutoff, but exclude preliminary seed and conditioning SVDs. +These are local truncation diagnostics, not errors on the final observables. +""" function leading_boundary(env₀::CTMRGEnvTriangular, network::InfiniteTriangularNetwork; kwargs...) alg = select_algorithm(leading_boundary, env₀; kwargs...) return leading_boundary(env₀, network, alg) @@ -13,14 +27,16 @@ function leading_boundary( S_old = DiagonalTensorMap.(id.(domain.(env₀.C))) η = one(real(scalartype(network))) ctmrg_loginit!(log, η, network, env₀) - local info + local info_iter + converged = false for iter in 1:(alg.maxiter) - env, S = ctmrg_iteration(network, env, alg) # Grow and renormalize in all 4 directions - η = calc_convergence(S, S_old) - S_old = copy(S) + env, info_iter = ctmrg_iteration(network, env, alg) + η = calc_convergence(info_iter.S, S_old) + S_old = copy(info_iter.S) if η ≤ alg.tol && iter ≥ alg.miniter ctmrg_logfinish!(log, iter, η, network, env) + converged = true break end if iter == alg.maxiter @@ -28,8 +44,11 @@ function leading_boundary( else ctmrg_logiter!(log, iter, η, network, env) end - info = (; convergence_metric = η) end + info = (; + converged, convergence_error = η, convergence_metric = η, + contraction_metrics = info_iter.contraction_metrics, + ) return env, info end end diff --git a/src/algorithms/ctmrg_triangular/simultaneous.jl b/src/algorithms/ctmrg_triangular/simultaneous.jl index 84cb57c93..adb82a0e6 100644 --- a/src/algorithms/ctmrg_triangular/simultaneous.jl +++ b/src/algorithms/ctmrg_triangular/simultaneous.jl @@ -29,14 +29,18 @@ function ctmrg_iteration(network::InfiniteTriangularNetwork, env::CTMRGEnvTriang else trunc = trunc_type(dim(domain(env.C[1, 1, 1])[1])) end - Pas, Pbs, S = calculate_projectors(network, env, trunc, alg.projector_alg) + Pas, Pbs, S, corner_truncation_error = calculate_projectors(network, env, trunc, alg.projector_alg) env = renormalize_corners!(network, env, Pas, Pbs) env = normalize_corners(env) mats, env = semi_renormalize(network, env, Pas, Pbs, trunc) - env = renormalize_edges(network, env, mats, trunc; alg.conditioning) - return env, S + env, edge_truncation_error = renormalize_edges(network, env, mats, trunc; alg.conditioning) + truncation_error = max(corner_truncation_error, edge_truncation_error) + info = (; + contraction_metrics = (; truncation_error, corner_truncation_error, edge_truncation_error), S, + ) + return env, info end function calculate_projectors(network, env, trunc, projector_alg) @@ -56,6 +60,18 @@ function _truncation_strategy(::FixedSpaceTruncation, edge) return isdual(tspace) ? truncspace(flip(tspace)) : truncspace(tspace) end +# Include retained values suppressed by the inverse square root, without subtracting +# nearly equal norms. Normalize by norm(S), as in the square CTMRG projectors. +function _relative_truncation_error(S, isqrtS, truncation_error) + return ignore_derivatives() do + discarded_S = DiagonalTensorMap(S.data .* iszero.(isqrtS.data), space(S, 1)) + discarded_norm = hypot(truncation_error, norm(discarded_S)) + retained_norm = norm(S) + iszero(retained_norm) && return iszero(discarded_norm) ? zero(discarded_norm) : oftype(discarded_norm, Inf) + return discarded_norm / retained_norm + end +end + function calculate_twothirds_projectors(network::InfiniteTriangularNetwork{P}, env, trunc) where {P} projectors = dtmap(eachcoordinate(network, 1:6)) do (dir, r, c) ρL = build_double_corner_matrix_triangular(network, env, _prev(dir, 6), r, c) @@ -64,14 +80,15 @@ function calculate_twothirds_projectors(network::InfiniteTriangularNetwork{P}, e ρρ /= norm(ρρ) trunc′ = _truncation_strategy(trunc, env.Ea[dir, r, c]) - U, S, V = svd_trunc(ρρ; trunc = trunc′) + U, S, V, truncation_error = svd_trunc(ρρ; trunc = trunc′) iqsrtS = sdiag_pow(S, -0.5) + truncation_error = _relative_truncation_error(S, iqsrtS, truncation_error) Pb = ρR * V' * iqsrtS Pa = iqsrtS * U' * ρL - return Pa, Pb, S + return Pa, Pb, S, truncation_error end - return getindex.(projectors, 1), getindex.(projectors, 2), getindex.(projectors, 3) + return getindex.(projectors, 1), getindex.(projectors, 2), getindex.(projectors, 3), maximum(x -> x[4], projectors) end function calculate_full_projectors(network::InfiniteTriangularNetwork{P}, env, trunc) where {P} @@ -88,14 +105,15 @@ function calculate_full_projectors(network::InfiniteTriangularNetwork{P}, env, t ρρ /= norm(ρρ) trunc′ = _truncation_strategy(trunc, env.Ea[dir, r, c]) - U, S, Vᴴ = svd_trunc(ρρ; trunc = trunc′) + U, S, Vᴴ, truncation_error = svd_trunc(ρρ; trunc = trunc′) isqrtS = sdiag_pow(S, -0.5) + truncation_error = _relative_truncation_error(S, isqrtS, truncation_error) Pb = ρR * ρ̄ᴿ * Vᴴ' * isqrtS Pa = isqrtS * U' * ρ̄ᴸ * ρL - return Pa, Pb, S + return Pa, Pb, S, truncation_error end - return getindex.(projectors, 1), getindex.(projectors, 2), getindex.(projectors, 3) + return getindex.(projectors, 1), getindex.(projectors, 2), getindex.(projectors, 3), maximum(x -> x[4], projectors) end function renormalize_corners!(network::InfiniteTriangularNetwork{P}, env, Pas, Pbs) where {P <: PEPSSandwichTriangular} @@ -152,11 +170,12 @@ function _truncate_edge(mat, ρL, ρR, trunc; conditioning = true) ρ = ρL * mat * ρR ρ /= norm(ρ) - U, S, Vᴴ = svd_trunc(ρ; trunc) + U, S, Vᴴ, truncation_error = svd_trunc(ρ; trunc) isqrtS = sdiag_pow(S, -0.5) + truncation_error = _relative_truncation_error(S, isqrtS, truncation_error) Ea = _permute_edge(isqrtS * U' * ρL * mat) Eb = mat * ρR * Vᴴ' * isqrtS - return Ea, Eb + return Ea, Eb, truncation_error end function renormalize_edges(network::InfiniteTriangularNetwork, env::CTMRGEnvTriangular, mats, trunc; conditioning = true) @@ -165,7 +184,8 @@ function renormalize_edges(network::InfiniteTriangularNetwork, env::CTMRGEnvTria trunc′ = _truncation_strategy(trunc, env.Ea[dir, r, c]) return _truncate_edge(mats[dir, r, c], ρL, ρR, trunc′; conditioning) end - return normalize_edges(CTMRGEnvTriangular(env.C, getindex.(edges, 1), getindex.(edges, 2))) + env′ = normalize_edges(CTMRGEnvTriangular(env.C, getindex.(edges, 1), getindex.(edges, 2))) + return env′, maximum(x -> x[3], edges) end function normalize_corners(env::CTMRGEnvTriangular) diff --git a/test/ctmrg/triangular.jl b/test/ctmrg/triangular.jl index 2a5a8b574..3d828add1 100644 --- a/test/ctmrg/triangular.jl +++ b/test/ctmrg/triangular.jl @@ -130,7 +130,7 @@ end return PEPSKit._truncate_edge(mats[dir, r, c], ρL, ρR, trunc′; conditioning) end expected = PEPSKit.normalize_edges(CTMRGEnvTriangular(preliminary.C, getindex.(edges, 1), getindex.(edges, 2))) - updated = PEPSKit.renormalize_edges(network, preliminary, mats, trunc; conditioning) + updated, = PEPSKit.renormalize_edges(network, preliminary, mats, trunc; conditioning) @test all(isapprox.(updated.Ea, expected.Ea)) @test all(isapprox.(updated.Eb, expected.Eb)) @test preliminary.Ea == previous.Ea @@ -298,3 +298,65 @@ end @test dfs1 ≈ dfs2 atol = 1.0e-2 end end + +@testset "CTM_triangular - Truncation diagnostics" begin + V = ComplexSpace(2) ⊗ ComplexSpace(2) + for T in (Float64, ComplexF64), conditioning in (false, true), scale in (1.0e-30, 1.0, 1.0e30) + for values in ([4.0, 3.0, 2.0, 1.0], [4.0, 3.0, 1.0e-9, 2.0e-10], [4.0, 3.0, 1.0e-14, 2.0e-15], [4.0, 3.0, 0.0, 0.0]) + mat = TensorMap(Matrix(Diagonal(T.(scale .* values))), V, V) + for rank in 1:5 + _, _, error = PEPSKit._truncate_edge(mat, id(V), id(V), truncrank(rank); conditioning) + kept = values[1:min(rank, end)] + discarded = [values[(min(rank, end) + 1):end]; filter(<(eps(Float64)^(3 / 4) * maximum(kept)), kept)] + @test error ≈ norm(discarded) / norm(kept) rtol = 1.0e-12 + end + _, _, error = PEPSKit._truncate_edge(mat, id(V), id(V), notrunc(); conditioning) + discarded = filter(<(eps(Float64)^(3 / 4) * maximum(values)), values) + @test error ≈ norm(discarded) / norm(values) rtol = 1.0e-12 + end + end + + # Non-Abelian sector dimensions must weight both the cutoff loss and norm(S). + S = DiagonalTensorMap([4.0, 3.0, 1.0e-14], SU2Space(0 => 1, 1 // 2 => 2)) + isqrtS = PEPSKit.sdiag_pow(S, -0.5) + error = PEPSKit._relative_truncation_error(S, isqrtS, 2.0e-15) + @test error ≈ sqrt((2.0e-15)^2 + 2 * (1.0e-14)^2) / sqrt(4.0^2 + 2 * 3.0^2) + for S in (DiagonalTensorMap(zeros(2), ComplexSpace(2)), DiagonalTensorMap(Float64[], ComplexSpace(0))) + @test iszero(PEPSKit._relative_truncation_error(S, S, 0.0)) + @test isinf(PEPSKit._relative_truncation_error(S, S, 1.0)) + end +end + +@testset "CTM_triangular - Iteration diagnostics" begin + Random.seed!(88193) + vspace = ComplexSpace(2) + sz = (1, 2) + for sandwich in (false, true) + tensors = map(Iterators.product(1:sz[1], 1:sz[2])) do _ + if sandwich + ket = randn(ComplexF64, vspace, vspace^3 ⊗ (vspace')^3) + return ket, copy(ket) + end + return randn(ComplexF64, vspace^3, vspace^3) + end + network = InfiniteTriangularNetwork(tensors) + env₀ = CTMRGEnvTriangular(randn, ComplexF64, sandwich ? vspace ⊗ vspace' : vspace, vspace; unitcell = sz) + for projector_alg in (:twothirds, :full), conditioning in (false, true), trunctype in (:truncrank, :FixedSpaceTruncation) + alg = SimultaneousCTMRGTriangular(; projector_alg, conditioning, trunctype, verbosity = 0) + env₁, info₁ = PEPSKit.ctmrg_iteration(network, env₀, alg) + _, info₂ = PEPSKit.ctmrg_iteration(network, env₁, alg) + for (maxiter, miniter, tol, expected) in ((1, 1, Inf, info₁), (2, 2, Inf, info₂), (1, 1, 0.0, info₁)) + alg = SimultaneousCTMRGTriangular(; projector_alg, conditioning, trunctype, verbosity = 0, maxiter, miniter, tol) + _, info = leading_boundary(env₀, network, alg) + metrics = info.contraction_metrics + @test metrics == expected.contraction_metrics + @test all(isfinite, metrics) + @test metrics.corner_truncation_error > 0 + @test metrics.edge_truncation_error > 0 + @test metrics.truncation_error == max(metrics.corner_truncation_error, metrics.edge_truncation_error) + @test info.converged == isinf(tol) + @test info.convergence_error == info.convergence_metric + end + end + end +end From 3d8f3f56547157554febdecbeccae36057796e23 Mon Sep 17 00:00:00 2001 From: Philipp Schmoll Date: Tue, 27 Oct 2026 17:58:16 +0100 Subject: [PATCH 19/41] Add triangular bond observables and fix diagonal normalization --- .../triangular_ctmrg/expectation_value.jl | 32 ++++++++++-- .../triangular_ctmrg/network_value.jl | 6 +-- src/algorithms/toolbox_triangular.jl | 24 ++++++++- test/ctmrg/triangular.jl | 50 +++++++++++++++++++ 4 files changed, 104 insertions(+), 8 deletions(-) diff --git a/src/algorithms/contractions/triangular_ctmrg/expectation_value.jl b/src/algorithms/contractions/triangular_ctmrg/expectation_value.jl index e591936b1..7a5ff04a2 100644 --- a/src/algorithms/contractions/triangular_ctmrg/expectation_value.jl +++ b/src/algorithms/contractions/triangular_ctmrg/expectation_value.jl @@ -1,3 +1,10 @@ +function _contract_corners((r, c)::Tuple{Int, Int}, network::InfiniteTriangularNetwork{P}, env::CTMRGEnvTriangular, op::AbstractTensorMap{E, S, 1, 1}) where {P <: PEPSSandwichTriangular, E, S} + O_r_c = network[r, c] + return @tensor opt = true env.C[1, _prev(r, end), c][χNW Dt120 Db120; χN] * env.C[2, _prev(r, end), _next(c, end)][χN Dt60 Db60; χNE] * env.C[3, r, _next(c, end)][χNE Dt0 Db0; χSE] * + env.C[4, _next(r, end), c][χSE Dt300 Db300; χS] * env.C[5, _next(r, end), _prev(c, end)][χS Dt240 Db240; χSW] * env.C[6, r, _prev(c, end)][χSW Dt180 Db180; χNW] * + ket(O_r_c)[dt; Dt120 Dt60 Dt0 Dt300 Dt240 Dt180] * conj(bra(O_r_c)[db; Db120 Db60 Db0 Db300 Db240 Db180]) * op[db; dt] +end + function _contract_edges_0((r, c)::Tuple{Int, Int}, network::InfiniteTriangularNetwork{P}, env::CTMRGEnvTriangular, op::AbstractTensorMap{E, S, 2, 2}) where {P <: PEPSSandwichTriangular, E, S} O_r_c = network[r, c] O_r_cn = network[r, _next(c, end)] @@ -10,9 +17,26 @@ function _contract_edges_0((r, c)::Tuple{Int, Int}, network::InfiniteTriangularN op[dbL dbR; dtL dtR] end -function _contract_corners((r, c)::Tuple{Int, Int}, network::InfiniteTriangularNetwork{P}, env::CTMRGEnvTriangular, op::AbstractTensorMap{E, S, 1, 1}) where {P <: PEPSSandwichTriangular, E, S} +function _contract_edges_60((r, c)::Tuple{Int, Int}, network::InfiniteTriangularNetwork{P}, env::CTMRGEnvTriangular, op::AbstractTensorMap{E, S, 2, 2}) where {P <: PEPSSandwichTriangular, E, S} O_r_c = network[r, c] - return @tensor opt = true env.C[1, _prev(r, end), c][χNW Dt120 Db120; χN] * env.C[2, _prev(r, end), _next(c, end)][χN Dt60 Db60; χNE] * env.C[3, r, _next(c, end)][χNE Dt0 Db0; χSE] * - env.C[4, _next(r, end), c][χSE Dt300 Db300; χS] * env.C[5, _next(r, end), _prev(c, end)][χS Dt240 Db240; χSW] * env.C[6, r, _prev(c, end)][χSW Dt180 Db180; χNW] * - ket(O_r_c)[dt; Dt120 Dt60 Dt0 Dt300 Dt240 Dt180] * conj(bra(O_r_c)[db; Db120 Db60 Db0 Db300 Db240 Db180]) * op[db; dt] + O_rn_cp = network[_next(r, end), _prev(c, end)] + return @tensor opt = true ket(O_r_c)[dtTR; DTRt120 DTRt60 DTRt0 DTRt300 DBLt60 DTRt180] * ket(O_rn_cp)[dtBL; DBLt120 DBLt60 DBLt0 DBLt300 DBLt240 DBLt180] * + conj(bra(O_r_c)[dbTR; DTRb120 DTRb60 DTRb0 DTRb300 DBLb60 DTRb180]) * conj(bra(O_rn_cp)[dbBL; DBLb120 DBLb60 DBLb0 DBLb300 DBLb240 DBLb180]) * + env.C[1, _prev(r, end), c][χbNW DTRt120 DTRb120; χN] * env.C[2, _prev(r, end), _next(c, end)][χN DTRt60 DTRb60; χNE] * env.C[3, r, _next(c, end)][χNE DTRt0 DTRb0; χaSE] * + env.C[4, _next(r + 1, end), _prev(c, end)][χbSE DBLt300 DBLb300; χS] * env.C[5, _next(r + 1, end), _prev(c - 1, end)][χS DBLt240 DBLb240; χSW] * env.C[6, _next(r, end), _prev(c - 1, end)][χSW DBLt180 DBLb180; χaNW] * + env.Eb[3, _next(r, end), c][χaSE DTRt300 DTRb300; χCSE] * env.Ea[3, _next(r, end), c][χCSE DBLt0 DBLb0; χbSE] * + env.Eb[6, r, _prev(c, end)][χaNW DBLt120 DBLb120; χCNW] * env.Ea[6, r, _prev(c, end)][χCNW DTRt180 DTRb180; χbNW] * + op[dbTR dbBL; dtTR dtBL] +end + +function _contract_edges_120((r, c)::Tuple{Int, Int}, network::InfiniteTriangularNetwork{P}, env::CTMRGEnvTriangular, op::AbstractTensorMap{E, S, 2, 2}) where {P <: PEPSSandwichTriangular, E, S} + O_r_c = network[r, c] + O_rn_c = network[_next(r, end), c] + return @tensor opt = true ket(O_r_c)[dtTL; DTLt120 DTLt60 DTLt0 DTLt300 DTLt240 DTLt180] * ket(O_rn_c)[dtBR; DTLt300 DBRt60 DBRt0 DBRt300 DBRt240 DBRt180] * + conj(bra(O_r_c)[dbTL; DTLb120 DTLb60 DTLb0 DTLb300 DTLb240 DTLb180]) * conj(bra(O_rn_c)[dbBR; DTLb300 DBRb60 DBRb0 DBRb300 DBRb240 DBRb180]) * + env.C[1, _prev(r, end), c][χNW DTLt120 DTLb120; χN] * env.C[2, _prev(r, end), _next(c, end)][χN DTLt60 DTLb60; χaNE] * env.C[3, _next(r, end), _next(c, end)][χbNE DBRt0 DBRb0; χSE] * + env.C[4, _next(r + 1, end), c][χSE DBRt300 DBRb300; χS] * env.C[5, _next(r + 1, end), _prev(c, end)][χS DBRt240 DBRb240; χaSW] * env.C[6, r, _prev(c, end)][χbSW DTLt180 DTLb180; χNW] * + env.Eb[2, r, _next(c, end)][χaNE DTLt0 DTLb0; χCNE] * env.Ea[2, r, _next(c, end)][χCNE DBRt60 DBRb60; χbNE] * + env.Eb[5, _next(r, end), _prev(c, end)][χaSW DBRt180 DBRb180; χCSW] * env.Ea[5, _next(r, end), _prev(c, end)][χCSW DTLt240 DTLb240; χbSW] * + op[dbTL dbBR; dtTL dtBR] end diff --git a/src/algorithms/contractions/triangular_ctmrg/network_value.jl b/src/algorithms/contractions/triangular_ctmrg/network_value.jl index 749c8584f..b0c537145 100644 --- a/src/algorithms/contractions/triangular_ctmrg/network_value.jl +++ b/src/algorithms/contractions/triangular_ctmrg/network_value.jl @@ -53,9 +53,9 @@ end function _contract_edges_60((r, c)::Tuple{Int, Int}, network::InfiniteTriangularNetwork{P}, env::CTMRGEnvTriangular) where {P <: PEPSSandwichTriangular} O_r_c = network[r, c] - O_rn_cn = network[_next(r, end), _next(c, end)] - return @tensor opt = true ket(O_r_c)[dTR; DTRt120 DTRt60 DTRt0 DTRt300 DBLt60 DTRt180] * ket(O_rn_cn)[dBL; DBLt120 DBLt60 DBLt0 DBLt300 DBLt240 DBLt180] * - conj(bra(O_r_c)[dTR; DTRb120 DTRb60 DTRb0 DTRb300 DBLb60 DTRb180]) * conj(bra(O_rn_cn)[dBL; DBLb120 DBLb60 DBLb0 DBLb300 DBLb240 DBLb180]) * + O_rn_cp = network[_next(r, end), _prev(c, end)] + return @tensor opt = true ket(O_r_c)[dTR; DTRt120 DTRt60 DTRt0 DTRt300 DBLt60 DTRt180] * ket(O_rn_cp)[dBL; DBLt120 DBLt60 DBLt0 DBLt300 DBLt240 DBLt180] * + conj(bra(O_r_c)[dTR; DTRb120 DTRb60 DTRb0 DTRb300 DBLb60 DTRb180]) * conj(bra(O_rn_cp)[dBL; DBLb120 DBLb60 DBLb0 DBLb300 DBLb240 DBLb180]) * env.C[1, _prev(r, end), c][χbNW DTRt120 DTRb120; χN] * env.C[2, _prev(r, end), _next(c, end)][χN DTRt60 DTRb60; χNE] * env.C[3, r, _next(c, end)][χNE DTRt0 DTRb0; χaSE] * env.C[4, _next(r + 1, end), _prev(c, end)][χbSE DBLt300 DBLb300; χS] * env.C[5, _next(r + 1, end), _prev(c - 1, end)][χS DBLt240 DBLb240; χSW] * env.C[6, _next(r, end), _prev(c - 1, end)][χSW DBLt180 DBLb180; χaNW] * env.Eb[3, _next(r, end), c][χaSE DTRt300 DTRb300; χCSE] * env.Ea[3, _next(r, end), c][χCSE DBLt0 DBLb0; χbSE] * diff --git a/src/algorithms/toolbox_triangular.jl b/src/algorithms/toolbox_triangular.jl index 32ef6346e..8e7c98783 100644 --- a/src/algorithms/toolbox_triangular.jl +++ b/src/algorithms/toolbox_triangular.jl @@ -25,4 +25,26 @@ function energy(state::InfinitePEPSTriangular, env::CTMRGEnvTriangular, args...) return energy(InfiniteTriangularNetwork(state), env, args...) end -# TODO: write a proper cost_function that works on an InfinitePEPSTriangular and a LocalOperator, and use it in the optimization loop. For now, we just compute the energy using the network contraction functions above. +""" + energy(network::InfiniteTriangularNetwork, env, onesite_op, twosite_ops::NTuple{3}) + +Compute the energy per unit cell with a one-site term and nearest-neighbor terms +in the 0°, 60°, and 120° bond families. The oriented bonds connect `(r, c)` to +`(r, c + 1)`, `(r + 1, c - 1)`, and `(r + 1, c)`, respectively. +Divide by `length(network)` for the energy per site. +""" +function energy(network::InfiniteTriangularNetwork{P}, env::CTMRGEnvTriangular, onesite_op, twosite_ops::NTuple{3, AbstractTensorMap}) where {P <: PEPSSandwichTriangular} + contractions = (_contract_edges_0, _contract_edges_60, _contract_edges_120) + E = sum(Iterators.product(axes(network)...)) do site + expval_onesite = _contract_corners(site, network, env, onesite_op) / _contract_corners(site, network, env) + expval_twosite = sum(zip(contractions, twosite_ops)) do (contract, op) + return contract(site, network, env, op) / contract(site, network, env) + end + return expval_onesite + expval_twosite + end + ignore_derivatives() do + isapprox(imag(E), 0; atol = sqrt(eps(real(E)))) || + @warn "Expectation value is not real: $E." + end + return real(E) +end diff --git a/test/ctmrg/triangular.jl b/test/ctmrg/triangular.jl index 3d828add1..ed588a633 100644 --- a/test/ctmrg/triangular.jl +++ b/test/ctmrg/triangular.jl @@ -360,3 +360,53 @@ end end end end + +@testset "Triangular nearest-neighbor energy" begin + Random.seed!(28514) + pspace, vspace = ComplexSpace(2), ComplexSpace(1) + vspaces = vspace^3 ⊗ (vspace')^3 + Sx = TensorMap(ComplexF64[0 1; 1 0] / 2, pspace, pspace) + Sy = TensorMap(ComplexF64[0 -im; im 0] / 2, pspace, pspace) + Sz = TensorMap(ComplexF64[1 0; 0 -1] / 2, pspace, pspace) + spins = (Sx, Sy, Sz) + # Unequal site norms expose neighbor-index errors even in identity contractions. + vectors = [randn(ComplexF64, 2) for r in 1:3, c in 1:4] + tensors = map(v -> TensorMap(reshape(v, 2, 1, 1, 1, 1, 1, 1), pspace, vspaces), vectors) + state = InfinitePEPSTriangular(tensors) + network = InfiniteTriangularNetwork(state) + env = CTMRGEnvTriangular(ones, ComplexF64, vspace ⊗ vspace', vspace; unitcell = size(state)) + contractions = (PEPSKit._contract_edges_0, PEPSKit._contract_edges_60, PEPSKit._contract_edges_120) + offsets = ((0, 1), (1, -1), (1, 0)) + pairs = ((Sx, Sz), (Sy, Sx), (Sz, Sy)) + ops = map(p -> p[1] ⊗ p[2], pairs) + expectation(v, op) = dot(v, reshape(convert(Array, op), 2, 2) * v) / dot(v, v) + expected = 0.0 + expected_horizontal = 0.0 + for r in 1:3, c in 1:4 + v = vectors[r, c] + onsite = real(expectation(v, Sz)) + expected += onsite + expected_horizontal += onsite + for (i, (contract, (δr, δc))) in enumerate(zip(contractions, offsets)) + w = vectors[mod1(r + δr, 3), mod1(c + δc, 4)] + n = dot(v, v) * dot(w, w) + bond = expectation(v, pairs[i][1]) * expectation(w, pairs[i][2]) + @test contract((r, c), network, env) ≈ n + @test contract((r, c), network, env, id(pspace ⊗ pspace)) ≈ n + @test contract((r, c), network, env, ops[i]) / n ≈ bond + expected += real(bond) + i == 1 && (expected_horizontal += real(bond)) + end + end + @test PEPSKit.energy(state, env, Sz, ops) ≈ expected + @test PEPSKit.energy(state, env, Sz, ops[1]) ≈ expected_horizontal + + # Spin-1/2 120° product state: each of the three bonds contributes -1/8 per site. + rotation(θ) = TensorMap([cos(θ / 2) -sin(θ / 2); sin(θ / 2) cos(θ / 2)], pspace, pspace) + A = TensorMap(reshape([1.0, 0.0], 2, 1, 1, 1, 1, 1, 1), pspace, vspaces) + B, C = rotation(2π / 3) * A, rotation(4π / 3) * A + state = InfinitePEPSTriangular([A B C; C A B; B C A]) + env = CTMRGEnvTriangular(ones, Float64, vspace ⊗ vspace', vspace; unitcell = (3, 3)) + h = sum(S ⊗ S for S in spins) + @test PEPSKit.energy(state, env, zero(Sz), (h, h, h)) / length(state) ≈ -3 / 8 +end From 39abcaf2fa01c5e0b2d49675b99d667d231854e9 Mon Sep 17 00:00:00 2001 From: Philipp Schmoll Date: Tue, 27 Oct 2026 18:45:19 +0100 Subject: [PATCH 20/41] Normalize triangular corner factors separately --- .../ctmrg_triangular/simultaneous.jl | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/algorithms/ctmrg_triangular/simultaneous.jl b/src/algorithms/ctmrg_triangular/simultaneous.jl index adb82a0e6..cf228dd5c 100644 --- a/src/algorithms/ctmrg_triangular/simultaneous.jl +++ b/src/algorithms/ctmrg_triangular/simultaneous.jl @@ -72,12 +72,15 @@ function _relative_truncation_error(S, isqrtS, truncation_error) end end +_normalize_corner_environment(ρ) = ρ / norm(ρ) + function calculate_twothirds_projectors(network::InfiniteTriangularNetwork{P}, env, trunc) where {P} projectors = dtmap(eachcoordinate(network, 1:6)) do (dir, r, c) ρL = build_double_corner_matrix_triangular(network, env, _prev(dir, 6), r, c) ρR = build_double_corner_matrix_triangular(network, env, _next(dir, 6), r, c) - ρρ = ρL * ρR - ρρ /= norm(ρρ) + ρL = _normalize_corner_environment(ρL) + ρR = _normalize_corner_environment(ρR) + ρρ = _normalize_corner_environment(ρL * ρR) trunc′ = _truncation_strategy(trunc, env.Ea[dir, r, c]) U, S, V, truncation_error = svd_trunc(ρρ; trunc = trunc′) @@ -96,20 +99,21 @@ function calculate_full_projectors(network::InfiniteTriangularNetwork{P}, env, t ρL = build_double_corner_matrix_triangular(network, env, mod1(dir - 1, 6), r, c) ρR = build_double_corner_matrix_triangular(network, env, mod1(dir + 1, 6), r, c) ρ̄ = build_double_corner_matrix_triangular(network, env, mod1(dir + 3, 6), r, c) - ρ̄ /= norm(ρ̄) + ρ̄ = _normalize_corner_environment(ρ̄) Ū, S̄, V̄ᴴ = svd_full(ρ̄) sqrtS̄ = sdiag_pow(S̄, 0.5) ρ̄ᴿ = Ū * sqrtS̄ ρ̄ᴸ = sqrtS̄ * V̄ᴴ - ρρ = ρ̄ᴸ * ρL * ρR * ρ̄ᴿ - ρρ /= norm(ρρ) + ρL = _normalize_corner_environment(ρ̄ᴸ * ρL) + ρR = _normalize_corner_environment(ρR * ρ̄ᴿ) + ρρ = _normalize_corner_environment(ρL * ρR) trunc′ = _truncation_strategy(trunc, env.Ea[dir, r, c]) U, S, Vᴴ, truncation_error = svd_trunc(ρρ; trunc = trunc′) isqrtS = sdiag_pow(S, -0.5) truncation_error = _relative_truncation_error(S, isqrtS, truncation_error) - Pb = ρR * ρ̄ᴿ * Vᴴ' * isqrtS - Pa = isqrtS * U' * ρ̄ᴸ * ρL + Pb = ρR * Vᴴ' * isqrtS + Pa = isqrtS * U' * ρL return Pa, Pb, S, truncation_error end From 299ba4fda6638637176618a65e6da13af4ca789a Mon Sep 17 00:00:00 2001 From: Philipp Schmoll Date: Tue, 27 Oct 2026 19:34:18 +0100 Subject: [PATCH 21/41] Use corner and edge spectra for triangular CTMRG convergence --- src/algorithms/ctmrg_triangular/ctmrg.jl | 27 ++++++++++++------------ test/ctmrg/triangular.jl | 24 ++++++++++++++++++++- 2 files changed, 36 insertions(+), 15 deletions(-) diff --git a/src/algorithms/ctmrg_triangular/ctmrg.jl b/src/algorithms/ctmrg_triangular/ctmrg.jl index f6c6de892..4bbb044fc 100644 --- a/src/algorithms/ctmrg_triangular/ctmrg.jl +++ b/src/algorithms/ctmrg_triangular/ctmrg.jl @@ -5,7 +5,10 @@ abstract type CTMRGAlgorithmTriangular end Return the contracted environment and convergence information. `info.converged` and `info.convergence_error` describe convergence; `info.convergence_metric` is retained as -an alias for the latter. `info.contraction_metrics` contains `corner_truncation_error` +an alias for the latter. Convergence is the maximum singular-value distance of all +six corners and twelve edge halves at every site, following PEPSKit's native tensor +bipartitions and sector-aware distance. Spectral convergence does not imply an +elementwise fixed point. `info.contraction_metrics` contains `corner_truncation_error` and `edge_truncation_error`, maximized over all sites and directions of the final iteration, and their maximum `truncation_error`. @@ -24,15 +27,16 @@ function leading_boundary( log = ignore_derivatives(() -> MPSKit.IterLog("CTMRG")) return with_verbosity(; alg.verbosity) do env = deepcopy(env₀) - S_old = DiagonalTensorMap.(id.(domain.(env₀.C))) + CS, TS = ignore_derivatives() do + return convergence_spectra(env₀, alg) + end η = one(real(scalartype(network))) ctmrg_loginit!(log, η, network, env₀) local info_iter converged = false for iter in 1:(alg.maxiter) env, info_iter = ctmrg_iteration(network, env, alg) - η = calc_convergence(info_iter.S, S_old) - S_old = copy(info_iter.S) + η, CS, TS = calc_convergence(env, CS, TS, alg) if η ≤ alg.tol && iter ≥ alg.miniter ctmrg_logfinish!(log, iter, η, network, env) @@ -56,14 +60,9 @@ function leading_boundary(env₀::CTMRGEnvTriangular, state, args...; kwargs...) return leading_boundary(env₀, InfiniteTriangularNetwork(state), args...; kwargs...) end -function calc_convergence(S_new::Array{T, 3}, S_old::Array{T, 3}) where {E, S, T <: DiagonalTensorMap{E, S}} - ε = Inf - function dist(S1, S2) - if space(S1) == space(S2) - return norm(S1^4 - S2^4) - else - return Inf - end - end - return maximum(dist.(S_new, S_old)) +convergence_tensors(env::CTMRGEnvTriangular, alg) = (env.C, [env.Ea; env.Eb]) + +function convergence_spectra(env::CTMRGEnvTriangular, alg) + corners, edges = convergence_tensors(env, alg) + return corner_spectra(corners, alg), edge_spectra(edges, alg) end diff --git a/test/ctmrg/triangular.jl b/test/ctmrg/triangular.jl index ed588a633..dd6e4d492 100644 --- a/test/ctmrg/triangular.jl +++ b/test/ctmrg/triangular.jl @@ -327,6 +327,26 @@ end end end +@testset "CTM_triangular - Convergence spectra" begin + Random.seed!(71834) + vspace = ComplexSpace(2) + alg = SimultaneousCTMRGTriangular(; verbosity = 0) + for V in (vspace, vspace ⊗ vspace') + env = CTMRGEnvTriangular(randn, ComplexF64, V, vspace; unitcell = (1, 2)) + CS, TS = PEPSKit.convergence_spectra(env, alg) + @test size(CS) == (6, 1, 2) + @test size(TS) == (12, 1, 2) + @test first(PEPSKit.calc_convergence(env, CS, TS, alg)) == 0 + for field in (:C, :Ea, :Eb) + changed = deepcopy(env) + tensors = getproperty(changed, field) + expected = norm(tensors[6, 1, 2]) + tensors[6, 1, 2] *= 2 + @test first(PEPSKit.calc_convergence(changed, CS, TS, alg)) ≈ expected + end + end +end + @testset "CTM_triangular - Iteration diagnostics" begin Random.seed!(88193) vspace = ComplexSpace(2) @@ -347,7 +367,9 @@ end _, info₂ = PEPSKit.ctmrg_iteration(network, env₁, alg) for (maxiter, miniter, tol, expected) in ((1, 1, Inf, info₁), (2, 2, Inf, info₂), (1, 1, 0.0, info₁)) alg = SimultaneousCTMRGTriangular(; projector_alg, conditioning, trunctype, verbosity = 0, maxiter, miniter, tol) - _, info = leading_boundary(env₀, network, alg) + env, info = leading_boundary(env₀, network, alg) + CS, TS = PEPSKit.convergence_spectra(maxiter == 1 ? env₀ : env₁, alg) + @test info.convergence_error ≈ first(PEPSKit.calc_convergence(env, CS, TS, alg)) metrics = info.contraction_metrics @test metrics == expected.contraction_metrics @test all(isfinite, metrics) From c444c86ff334c05ce731fb68c475a993e7811715 Mon Sep 17 00:00:00 2001 From: Philipp Schmoll Date: Tue, 27 Oct 2026 19:49:03 +0100 Subject: [PATCH 22/41] Add triangular algorithm selection --- src/algorithms/ctmrg_triangular/ctmrg.jl | 4 ++++ src/algorithms/select_algorithm.jl | 8 ++++++++ test/ctmrg/triangular.jl | 16 ++++++++++++++++ 3 files changed, 28 insertions(+) diff --git a/src/algorithms/ctmrg_triangular/ctmrg.jl b/src/algorithms/ctmrg_triangular/ctmrg.jl index 4bbb044fc..3b50f46f6 100644 --- a/src/algorithms/ctmrg_triangular/ctmrg.jl +++ b/src/algorithms/ctmrg_triangular/ctmrg.jl @@ -1,8 +1,12 @@ abstract type CTMRGAlgorithmTriangular end """ + leading_boundary(env₀::CTMRGEnvTriangular, network; alg=:SimultaneousCTMRGTriangular, kwargs...) leading_boundary(env₀::CTMRGEnvTriangular, network, alg::CTMRGAlgorithmTriangular) +Keyword arguments are forwarded to `SimultaneousCTMRGTriangular`: `tol`, `maxiter`, +`miniter`, `verbosity`, `conditioning`, `projector_alg`, and `trunctype`. + Return the contracted environment and convergence information. `info.converged` and `info.convergence_error` describe convergence; `info.convergence_metric` is retained as an alias for the latter. Convergence is the maximum singular-value distance of all diff --git a/src/algorithms/select_algorithm.jl b/src/algorithms/select_algorithm.jl index c121fb3fd..c302ddc87 100644 --- a/src/algorithms/select_algorithm.jl +++ b/src/algorithms/select_algorithm.jl @@ -158,3 +158,11 @@ function select_algorithm( return CTMRGAlgorithm(; alg, tol, verbosity, decomposition_alg, kwargs...) end + +function select_algorithm( + ::typeof(leading_boundary), env₀::CTMRGEnvTriangular; + alg = :SimultaneousCTMRGTriangular, kwargs..., + ) + alg == :SimultaneousCTMRGTriangular || throw(ArgumentError("unknown triangular CTMRG algorithm: $alg")) + return SimultaneousCTMRGTriangular(; kwargs...) +end diff --git a/test/ctmrg/triangular.jl b/test/ctmrg/triangular.jl index dd6e4d492..af5447e1f 100644 --- a/test/ctmrg/triangular.jl +++ b/test/ctmrg/triangular.jl @@ -383,6 +383,22 @@ end end end +@testset "Triangular algorithm selection" begin + V = ComplexSpace(1) + state = InfinitePEPSTriangular(ones(ComplexF64, ComplexSpace(2), V^3 ⊗ (V')^3)) + env₀ = CTMRGEnvTriangular(ones, ComplexF64, V ⊗ V', V) + kwargs = (; tol = 1.0e-9, maxiter = 2, miniter = 2, verbosity = 0, conditioning = false, projector_alg = :full, trunctype = :FixedSpaceTruncation) + alg = SimultaneousCTMRGTriangular(; kwargs...) + @test PEPSKit.select_algorithm(leading_boundary, env₀; kwargs...) == alg + env, info = leading_boundary(env₀, state, alg) + for network in (state, InfiniteTriangularNetwork(state)) + env′, info′ = leading_boundary(env₀, network; alg = :SimultaneousCTMRGTriangular, kwargs...) + @test (env′.C, env′.Ea, env′.Eb) == (env.C, env.Ea, env.Eb) + @test info′ == info + end + @test_throws ArgumentError leading_boundary(env₀, state; alg = :unknown) +end + @testset "Triangular nearest-neighbor energy" begin Random.seed!(28514) pspace, vspace = ComplexSpace(2), ComplexSpace(1) From c890a8fdcc74a4164950b33b0adcf28bf9eb88b4 Mon Sep 17 00:00:00 2001 From: Philipp Schmoll Date: Tue, 27 Oct 2026 19:49:54 +0100 Subject: [PATCH 23/41] Add expectation_value and cost_function for LocalOperator --- .../expectation_value/expectation_value.jl | 8 ++++-- .../optimization/peps_optimization.jl | 4 +-- src/algorithms/toolbox_triangular.jl | 26 +++++++++++++++++++ src/operators/localoperator.jl | 4 +-- test/ctmrg/triangular.jl | 19 ++++++++++++++ 5 files changed, 55 insertions(+), 6 deletions(-) diff --git a/src/algorithms/expectation_value/expectation_value.jl b/src/algorithms/expectation_value/expectation_value.jl index 7c83aac90..d3da35c26 100644 --- a/src/algorithms/expectation_value/expectation_value.jl +++ b/src/algorithms/expectation_value/expectation_value.jl @@ -9,17 +9,21 @@ Compute the expectation value ⟨bra|O|ket⟩ / ⟨bra|ket⟩ or tr(O * state) / This can be done either for a PEPS, or alternatively for a density matrix PEPO. In the latter case the first signature corresponds to a single layer PEPO contraction, while the second signature yields a bilayer contraction instead. + +For an `InfinitePEPSTriangular`, dense one-site and nearest-neighbor terms are supported. +The three bond families have offsets `(0, 1)`, `(1, -1)`, and `(1, 0)`. """ function MPSKit.expectation_value( bra::S, O::LocalOperator, ket::S, env - ) where {S <: InfiniteState} + ) where {S <: Union{InfiniteState, InfinitePEPSTriangular}} checklattice(bra, O, ket) + isempty(O.terms) && return zero(scalartype(ket)) term_vals = dtmap(collect(O.terms)) do (inds, operator) # OhMyThreads can't iterate over O.terms directly return local_expectation_value(inds, bra, operator, ket, env) end return sum(term_vals) end -MPSKit.expectation_value(peps::InfinitePEPS, O::LocalOperator, env) = expectation_value(peps, O, peps, env) +MPSKit.expectation_value(peps::Union{InfinitePEPS, InfinitePEPSTriangular}, O::LocalOperator, env) = expectation_value(peps, O, peps, env) function MPSKit.expectation_value(state::InfinitePEPO, O::LocalOperator, env) checklattice(state, O) term_vals = dtmap(collect(O.terms)) do (inds, operator) # OhMyThreads can't iterate over O.terms directly diff --git a/src/algorithms/optimization/peps_optimization.jl b/src/algorithms/optimization/peps_optimization.jl index 808ed04f5..9dd6fc194 100644 --- a/src/algorithms/optimization/peps_optimization.jl +++ b/src/algorithms/optimization/peps_optimization.jl @@ -1,10 +1,10 @@ """ - cost_function(peps::InfinitePEPS, env, O::LocalOperator) + cost_function(peps::Union{InfinitePEPS, InfinitePEPSTriangular}, env, O::LocalOperator) Real part of expectation value of `O`, used as the cost function in variational PEPS optimization. Prints a warning if the expectation value yields a finite imaginary part (up to a tolerance). """ -function cost_function(peps::InfinitePEPS, env, O::LocalOperator) +function cost_function(peps::Union{InfinitePEPS, InfinitePEPSTriangular}, env, O::LocalOperator) E = MPSKit.expectation_value(peps, O, env) ignore_derivatives() do isapprox(imag(E), 0; atol = sqrt(eps(real(E)))) || diff --git a/src/algorithms/toolbox_triangular.jl b/src/algorithms/toolbox_triangular.jl index 8e7c98783..283bf028b 100644 --- a/src/algorithms/toolbox_triangular.jl +++ b/src/algorithms/toolbox_triangular.jl @@ -9,6 +9,32 @@ function network_value(network::InfiniteTriangularNetwork, env::CTMRGEnvTriangul end end +function local_expectation_value( + inds, bra::InfinitePEPSTriangular, operator::AbstractTensorMap, + ket::InfinitePEPSTriangular, env::CTMRGEnvTriangular, + ) + site = first(inds) + if length(inds) == 1 + contract = _contract_corners + elseif length(inds) == 2 + offsets = (CartesianIndex(0, 1), CartesianIndex(1, -1), CartesianIndex(1, 0)) + offset = inds[2] - inds[1] + dir = findfirst(==(offset), offsets) + if isnothing(dir) + dir = findfirst(==(-offset), offsets) + isnothing(dir) && throw(ArgumentError("only nearest-neighbor triangular terms are supported")) + site = inds[2] + operator = permute(operator, ((2, 1), (4, 3))) + end + contract = (_contract_edges_0, _contract_edges_60, _contract_edges_120)[dir] + else + throw(ArgumentError("only one-site and two-site triangular terms are supported")) + end + network = InfiniteTriangularNetwork(ket, bra) + site = mod1.(Tuple(site), size(network)) + return contract(site, network, env, operator) / contract(site, network, env) +end + function energy(network::InfiniteTriangularNetwork{P}, env::CTMRGEnvTriangular, onesite_op, twosite_op) where {P <: PEPSSandwichTriangular} E = sum(Iterators.product(axes(network)...)) do (r, c) expval_onesite = _contract_corners((r, c), network, env, onesite_op) / _contract_corners((r, c), network, env) diff --git a/src/operators/localoperator.jl b/src/operators/localoperator.jl index 47391f4d2..b7be57391 100644 --- a/src/operators/localoperator.jl +++ b/src/operators/localoperator.jl @@ -89,10 +89,10 @@ end function checklattice(::Type{Bool}, H1::LocalOperator, H2::LocalOperator) return physicalspace(H1) == physicalspace(H2) end -function checklattice(::Type{Bool}, peps::InfinitePEPS, O::LocalOperator) +function checklattice(::Type{Bool}, peps::Union{InfinitePEPS, InfinitePEPSTriangular}, O::LocalOperator) return physicalspace(peps) == physicalspace(O) end -function checklattice(::Type{Bool}, H::LocalOperator, peps::InfinitePEPS) +function checklattice(::Type{Bool}, H::LocalOperator, peps::Union{InfinitePEPS, InfinitePEPSTriangular}) return checklattice(Bool, peps, H) end function checklattice(::Type{Bool}, pepo::InfinitePEPO, O::LocalOperator) diff --git a/test/ctmrg/triangular.jl b/test/ctmrg/triangular.jl index af5447e1f..5f09a727f 100644 --- a/test/ctmrg/triangular.jl +++ b/test/ctmrg/triangular.jl @@ -420,11 +420,13 @@ end expectation(v, op) = dot(v, reshape(convert(Array, op), 2, 2) * v) / dot(v, v) expected = 0.0 expected_horizontal = 0.0 + terms = Pair[] for r in 1:3, c in 1:4 v = vectors[r, c] onsite = real(expectation(v, Sz)) expected += onsite expected_horizontal += onsite + push!(terms, ((r, c),) => Sz) for (i, (contract, (δr, δc))) in enumerate(zip(contractions, offsets)) w = vectors[mod1(r + δr, 3), mod1(c + δc, 4)] n = dot(v, v) * dot(w, w) @@ -434,11 +436,28 @@ end @test contract((r, c), network, env, ops[i]) / n ≈ bond expected += real(bond) i == 1 && (expected_horizontal += real(bond)) + push!(terms, ((r, c), (r + δr, c + δc)) => ops[i]) end end @test PEPSKit.energy(state, env, Sz, ops) ≈ expected @test PEPSKit.energy(state, env, Sz, ops[1]) ≈ expected_horizontal + H = LocalOperator(physicalspace(state), terms) + @test expectation_value(state, H, env) ≈ expected + @test expectation_value(state, im * H, env) ≈ im * expected + @test cost_function(state, env, H) ≈ expected + @test iszero(expectation_value(state, LocalOperator(physicalspace(state)), env)) + @test iszero(expectation_value(InfinitePEPS(pspace, vspace), LocalOperator(fill(pspace, 1, 1)), nothing)) + + # Distinct bra/ket and an asymmetric diagonal bond check operator ordering. + bra = InfinitePEPSTriangular(map(t -> 2Sx * t, tensors)) + bond = LocalOperator(physicalspace(state), ((1, 1), (2, 0)) => ops[2]) + transition(v, op) = dot(reverse(v), reshape(convert(Array, op), 2, 2) * v) / dot(reverse(v), v) + @test expectation_value(bra, bond, state, env) ≈ transition(vectors[1, 1], Sy) * transition(vectors[2, 4], Sx) + @test_throws ArgumentError expectation_value(state, LocalOperator(fill(pspace, 1, 1), ((1, 1),) => Sz), env) + @test_throws ArgumentError expectation_value(state, LocalOperator(physicalspace(state), ((1, 1), (1, 3)) => ops[1]), env) + @test_throws ArgumentError expectation_value(state, LocalOperator(physicalspace(state), ((1, 1), (1, 2), (2, 1)) => Sz ⊗ Sz ⊗ Sz), env) + # Spin-1/2 120° product state: each of the three bonds contributes -1/8 per site. rotation(θ) = TensorMap([cos(θ / 2) -sin(θ / 2); sin(θ / 2) cos(θ / 2)], pspace, pspace) A = TensorMap(reshape([1.0, 0.0], 2, 1, 1, 1, 1, 1, 1), pspace, vspaces) From 1ed73d9a083610e9878caa922054026e458aa665 Mon Sep 17 00:00:00 2001 From: Philipp Schmoll Date: Tue, 27 Oct 2026 19:57:26 +0100 Subject: [PATCH 24/41] Add triangular fixedpoint interface --- .../optimization/peps_optimization.jl | 28 +++++++++++++++---- test/ctmrg/triangular.jl | 9 ++++++ 2 files changed, 32 insertions(+), 5 deletions(-) diff --git a/src/algorithms/optimization/peps_optimization.jl b/src/algorithms/optimization/peps_optimization.jl index 9dd6fc194..eac8aa755 100644 --- a/src/algorithms/optimization/peps_optimization.jl +++ b/src/algorithms/optimization/peps_optimization.jl @@ -108,9 +108,9 @@ function _OptimizationAlgorithm(; end """ - fixedpoint(operator, peps₀::InfinitePEPS, env₀; kwargs...) -> peps_final, env_final, cost_final, info + fixedpoint(operator, peps₀::Union{InfinitePEPS, InfinitePEPSTriangular}, env₀; kwargs...) -> peps_final, env_final, cost_final, info # expert version: - fixedpoint(operator, peps₀::InfinitePEPS, env₀, alg::PEPSOptimize; finalize!=OptimKit._finalize!) + fixedpoint(operator, peps₀::Union{InfinitePEPS, InfinitePEPSTriangular}, env₀, alg::PEPSOptimize; finalize!=OptimKit._finalize!) Find the fixed point of `operator` (i.e. the ground state) starting from `peps₀` according to the supplied optimization parameters. The initial environment `env₀` serves as an @@ -118,7 +118,11 @@ initial guess for the first boundary contraction run. By default, a random initi Each local tensor is normalized initially, and optimization preserves its Euclidean norm by projecting gradients and preconditioned directions onto its real tangent space. The optimization parameters can be supplied via the keyword arguments or directly as a -`PEPSOptimize` struct. The following keyword arguments are supported: +`PEPSOptimize` struct. Triangular PEPS currently require `gradient_alg = nothing` +(differentiate the CTMRG iterations) and `precondition_alg = nothing`. Symmetrization +and fixed-point differentiation are not yet supported for triangular PEPS. + +The following keyword arguments are supported: ## Keyword arguments @@ -223,13 +227,13 @@ information `NamedTuple` which contains the following entries: * `gradnorms_unitcell` : History of gradient norms for each respective unit cell entry. * `times` : History of optimization step execution times. """ -function fixedpoint(operator, peps₀::InfinitePEPS, env₀; kwargs...) +function fixedpoint(operator, peps₀::Union{InfinitePEPS, InfinitePEPSTriangular}, env₀; kwargs...) extra_kwarg_keys = (:finalize!, :hasconverged, :shouldstop) # these will not be passed to `select_algorithm`, only to the 2nd `fixedpoint` call alg = select_algorithm(fixedpoint, env₀; filter(kw -> !(first(kw) in extra_kwarg_keys), kwargs)...) return fixedpoint(operator, peps₀, env₀, alg; filter(kw -> first(kw) in extra_kwarg_keys, kwargs)...) end function fixedpoint( - operator, peps₀::InfinitePEPS, env₀, alg::PEPSOptimize; + operator, peps₀::Union{InfinitePEPS, InfinitePEPSTriangular}, env₀, alg::PEPSOptimize; (finalize!) = OptimKit._finalize!, hasconverged = OptimKit.DefaultHasConverged(alg.optimizer_alg.gradtol), shouldstop = OptimKit.DefaultShouldStop(alg.optimizer_alg.maxiter), @@ -347,6 +351,20 @@ function check_input(::typeof(fixedpoint), peps₀, env₀, alg::PEPSOptimize) return nothing end +function check_input(::typeof(fixedpoint), peps₀::InfinitePEPSTriangular, env₀, alg::PEPSOptimize) + env₀ isa CTMRGEnvTriangular || + throw(ArgumentError("triangular PEPS optimization requires a CTMRGEnvTriangular environment")) + parent_alg(alg.boundary_alg) isa CTMRGAlgorithmTriangular || + throw(ArgumentError("triangular PEPS optimization requires a triangular CTMRG algorithm")) + isnothing(alg.gradient_alg) || + throw(ArgumentError("triangular fixed-point differentiation is not supported; set gradient_alg = nothing to differentiate the CTMRG iterations")) + isnothing(alg.precondition_alg) || + throw(ArgumentError("triangular preconditioning is not supported; set precondition_alg = nothing")) + isnothing(alg.symmetrization) || + throw(ArgumentError("triangular symmetrization is not supported; set symmetrization = nothing")) + return nothing +end + """ peps_normalize(A::InfinitePEPS) diff --git a/test/ctmrg/triangular.jl b/test/ctmrg/triangular.jl index 5f09a727f..e5b413026 100644 --- a/test/ctmrg/triangular.jl +++ b/test/ctmrg/triangular.jl @@ -399,6 +399,15 @@ end @test_throws ArgumentError leading_boundary(env₀, state; alg = :unknown) end +@testset "Triangular fixedpoint interface" begin + P, V = ComplexSpace(2), ComplexSpace(1) + state = InfinitePEPSTriangular(ones(Float64, P, V^3 ⊗ (V')^3)) + env = CTMRGEnvTriangular(ones, Float64, V ⊗ V', V) + alg = PEPSOptimize(SimultaneousCTMRGTriangular(), nothing, OptimKit.LBFGS(), nothing, true, nothing) + @test isnothing(PEPSKit.check_input(fixedpoint, state, env, alg)) + @test_throws ArgumentError fixedpoint(LocalOperator(physicalspace(state)), state, nothing, alg) +end + @testset "Triangular nearest-neighbor energy" begin Random.seed!(28514) pspace, vspace = ComplexSpace(2), ComplexSpace(1) From 8c5cdd2da4c6d926bd49ed0c57d92daa39e486ac Mon Sep 17 00:00:00 2001 From: Philipp Schmoll Date: Tue, 27 Oct 2026 19:59:15 +0100 Subject: [PATCH 25/41] Support triangular PEPS in the native optimizer --- src/algorithms/ctmrg_triangular/ctmrg.jl | 5 +++ .../optimization/peps_optimization.jl | 24 ++++++---- src/algorithms/select_algorithm.jl | 3 +- .../ctmrg_environments_triangular.jl | 4 ++ src/states/infinitepepstriangular.jl | 3 +- test/ctmrg/triangular.jl | 45 +++++++++++++++++++ 6 files changed, 72 insertions(+), 12 deletions(-) diff --git a/src/algorithms/ctmrg_triangular/ctmrg.jl b/src/algorithms/ctmrg_triangular/ctmrg.jl index 3b50f46f6..6f78e4b24 100644 --- a/src/algorithms/ctmrg_triangular/ctmrg.jl +++ b/src/algorithms/ctmrg_triangular/ctmrg.jl @@ -1,5 +1,10 @@ abstract type CTMRGAlgorithmTriangular end +function CTMRGAlgorithmTriangular(; alg = :SimultaneousCTMRGTriangular, kwargs...) + alg == :SimultaneousCTMRGTriangular || throw(ArgumentError("unknown triangular CTMRG algorithm: $alg")) + return SimultaneousCTMRGTriangular(; kwargs...) +end + """ leading_boundary(env₀::CTMRGEnvTriangular, network; alg=:SimultaneousCTMRGTriangular, kwargs...) leading_boundary(env₀::CTMRGEnvTriangular, network, alg::CTMRGAlgorithmTriangular) diff --git a/src/algorithms/optimization/peps_optimization.jl b/src/algorithms/optimization/peps_optimization.jl index eac8aa755..e4a8464d1 100644 --- a/src/algorithms/optimization/peps_optimization.jl +++ b/src/algorithms/optimization/peps_optimization.jl @@ -62,7 +62,13 @@ function PEPSOptimize(; boundary_alg = (;), gradient_alg = (;), optimizer_alg = (;), precondition_alg = (;), reuse_env = Defaults.reuse_env, symmetrization = nothing, ) - boundary_algorithm = _alg_or_nt(CTMRGAlgorithm, boundary_alg) + boundary_type = if parent_alg(boundary_alg) isa CTMRGAlgorithmTriangular || + (boundary_alg isa NamedTuple && get(boundary_alg, :alg, nothing) == :SimultaneousCTMRGTriangular) + CTMRGAlgorithmTriangular + else + CTMRGAlgorithm + end + boundary_algorithm = _alg_or_nt(boundary_type, boundary_alg) gradient_algorithm = _alg_or_nt(GradientAlgorithm, gradient_alg) optimizer_algorithm = _alg_or_nt(OptimKit.OptimizationAlgorithm, optimizer_alg) precondition_algorithm = _alg_or_nt(PreconditionAlgorithm, precondition_alg) @@ -366,14 +372,14 @@ function check_input(::typeof(fixedpoint), peps₀::InfinitePEPSTriangular, env end """ - peps_normalize(A::InfinitePEPS) + peps_normalize(A::Union{InfinitePEPS, InfinitePEPSTriangular}) -Normalize the individual tensors in the unit cell of an `InfinitePEPS` such that they each +Normalize the individual tensors in the PEPS unit cell such that they each have unit Euclidean norm. """ -function peps_normalize(A::InfinitePEPS) +function peps_normalize(A::Union{InfinitePEPS, InfinitePEPSTriangular}) normalized_tensors = normalize.(unitcell(A)) - return InfinitePEPS(normalized_tensors) + return typeof(A)(normalized_tensors) end """Real part of the Hermitian inner product.""" @@ -386,11 +392,11 @@ $(SIGNATURES) Project each direction onto the tangent space of its fixed-norm PEPS tensor using [`project_tangent`](@ref) with the real part of the Hermitian inner product. Complex tensors are treated as a real vector space, retaining the tangent phase direction ``i A``. """ -function peps_project_tangent(η::InfinitePEPS, peps::InfinitePEPS) +function peps_project_tangent(η::Union{InfinitePEPS, InfinitePEPSTriangular}, peps::Union{InfinitePEPS, InfinitePEPSTriangular}) tangents = map(unitcell(η), unitcell(peps)) do direction, A return project_tangent(direction, A; inner = real_inner) end - return InfinitePEPS(tangents) + return typeof(peps)(tangents) end """ @@ -414,8 +420,8 @@ function peps_retract(x, η, α) env = deepcopy(x[2]) retractions = norm_preserving_retract.(unitcell(peps), unitcell(η), α; inner = real_inner) - peps′ = InfinitePEPS(map(first, retractions)) - ξ = InfinitePEPS(map(last, retractions)) + peps′ = typeof(peps)(map(first, retractions)) + ξ = typeof(peps)(map(last, retractions)) return (peps′, env), ξ end diff --git a/src/algorithms/select_algorithm.jl b/src/algorithms/select_algorithm.jl index c302ddc87..60415a13d 100644 --- a/src/algorithms/select_algorithm.jl +++ b/src/algorithms/select_algorithm.jl @@ -163,6 +163,5 @@ function select_algorithm( ::typeof(leading_boundary), env₀::CTMRGEnvTriangular; alg = :SimultaneousCTMRGTriangular, kwargs..., ) - alg == :SimultaneousCTMRGTriangular || throw(ArgumentError("unknown triangular CTMRG algorithm: $alg")) - return SimultaneousCTMRGTriangular(; kwargs...) + return CTMRGAlgorithmTriangular(; alg, kwargs...) end diff --git a/src/environments/ctmrg_environments_triangular.jl b/src/environments/ctmrg_environments_triangular.jl index c2fea0a7c..e457dbb27 100644 --- a/src/environments/ctmrg_environments_triangular.jl +++ b/src/environments/ctmrg_environments_triangular.jl @@ -63,6 +63,10 @@ edgetype(::Type{CTMRGEnvTriangular{T}}) where {T} = T TensorKit.spacetype(::Type{E}) where {E <: CTMRGEnvTriangular} = spacetype(cornertype(E)) +function VI.scalartype(::Type{CTMRGEnvTriangular{C, A, B}}) where {C, A, B} + return promote_type(scalartype(C), scalartype(A), scalartype(B)) +end + # In-place update of environment function update!(env::CTMRGEnvTriangular{T}, env´::CTMRGEnvTriangular{T}) where {T} env.C .= env´.C diff --git a/src/states/infinitepepstriangular.jl b/src/states/infinitepepstriangular.jl index a8660e982..14bc7d025 100644 --- a/src/states/infinitepepstriangular.jl +++ b/src/states/infinitepepstriangular.jl @@ -174,6 +174,7 @@ function VI.add!(ψ₁::InfinitePEPSTriangular, ψ₂::InfinitePEPSTriangular, return ψ₁ end VI.add!!(ψ₁::InfinitePEPSTriangular, ψ₂::InfinitePEPSTriangular, α::Number, β::Number) = add!(ψ₁, ψ₂, α, β) +VI.inner(ψ₁::InfinitePEPSTriangular, ψ₂::InfinitePEPSTriangular) = inner(unitcell(ψ₁), unitcell(ψ₂)) ## Math @@ -186,7 +187,7 @@ end Base.:*(α::Number, A::InfinitePEPSTriangular) = InfinitePEPSTriangular(α * unitcell(A)) Base.:*(A::InfinitePEPSTriangular, α::Number) = α * A Base.:/(A::InfinitePEPSTriangular, α::Number) = InfinitePEPSTriangular(unitcell(A) / α) -LinearAlgebra.dot(A₁::InfinitePEPSTriangular, A₂::InfinitePEPSTriangular) = dot(unitcell(A₁), unitcell(A₂)) +LinearAlgebra.dot(A₁::InfinitePEPSTriangular, A₂::InfinitePEPSTriangular) = inner(A₁, A₂) LinearAlgebra.norm(A::InfinitePEPSTriangular) = norm(unitcell(A)) ## (Approximate) equality diff --git a/test/ctmrg/triangular.jl b/test/ctmrg/triangular.jl index e5b413026..e9a6d9c16 100644 --- a/test/ctmrg/triangular.jl +++ b/test/ctmrg/triangular.jl @@ -408,6 +408,51 @@ end @test_throws ArgumentError fixedpoint(LocalOperator(physicalspace(state)), state, nothing, alg) end +@testset "Triangular native optimization" begin + P, V = ComplexSpace(2), ComplexSpace(1) + Sz = TensorMap([1.0 0; 0 -1] / 2, P, P) + for T in (Float64, ComplexF64) + tensors = [TensorMap(reshape(T[1, a], 2, 1, 1, 1, 1, 1, 1), P, V^3 ⊗ (V')^3) for a in [0.6 1.3]] + state = InfinitePEPSTriangular(tensors) + original = deepcopy(state) + env = CTMRGEnvTriangular(ones, T, V ⊗ V', V; unitcell = size(state)) + H = LocalOperator(physicalspace(state), [((1, c),) => Sz for c in 1:2]) + boundary_alg = (; miniter = 1, maxiter = 1) + kwargs = (; boundary_alg, gradient_alg = nothing, precondition_alg = nothing, optimizer_alg = (; maxiter = 2, tol = 0), verbosity = 0) + alg = PEPSKit.select_algorithm(fixedpoint, env; kwargs...) + @test PEPSKit.parent_alg(alg.boundary_alg) isa SimultaneousCTMRGTriangular + direct_alg = PEPSOptimize(; boundary_alg = (; alg = :SimultaneousCTMRGTriangular, miniter = 1, maxiter = 1), gradient_alg = nothing, precondition_alg = nothing) + @test direct_alg.boundary_alg isa SimultaneousCTMRGTriangular + @test direct_alg.boundary_alg.maxiter == 1 + @test PEPSKit.adapt_solver(alg.boundary_alg; iter = 2, g_global = 0.1) isa SimultaneousCTMRGTriangular + iterations = Int[] + finalize! = (x, f, g, iter) -> (push!(iterations, iter); (x, f, g)) + initial_cost = cost_function(state, env, H) + ψ, env′, E, info = if T === Float64 + fixedpoint(H, state, env, alg; finalize!) + else + fixedpoint(H, state, env; kwargs..., finalize!) + end + @test ψ isa typeof(state) + @test env′ isa typeof(env) + @test E < initial_cost + @test E ≈ cost_function(ψ, env′, H) + @test all(isapprox.(norm.(unitcell(ψ)), 1)) + @test state == original + @test iterations == [1, 2] + @test info.costs[end] ≈ E + @test info.last_gradient isa typeof(state) + @test all(isfinite, info.gradnorms) + @test length(info.contraction_metrics) == length(info.gradnorms_unitcell) == length(info.times) == 2 + + @test_throws ArgumentError fixedpoint(H, state, nothing, alg) + @test_throws ArgumentError fixedpoint(H, state, env; boundary_alg) + @test_throws ArgumentError fixedpoint(H, state, env; boundary_alg, gradient_alg = nothing) + @test_throws ArgumentError fixedpoint(H, state, env; kwargs..., symmetrization = Rotate()) + @test_throws ArgumentError fixedpoint(H, state, env; kwargs..., boundary_alg = SimultaneousCTMRG()) + end +end + @testset "Triangular nearest-neighbor energy" begin Random.seed!(28514) pspace, vspace = ComplexSpace(2), ComplexSpace(1) From 9026cf7a1c94dd64ee4dd02798c418e2eb1ff8b9 Mon Sep 17 00:00:00 2001 From: Philipp Schmoll Date: Tue, 27 Oct 2026 20:13:08 +0100 Subject: [PATCH 26/41] Add triangular gauge fixing and fixed-point differentiation --- src/PEPSKit.jl | 1 + src/algorithms/ctmrg_triangular/gaugefix.jl | 77 ++++++++++++++++ .../optimization/implicit_differentiation.jl | 28 +++++- .../optimization/peps_optimization.jl | 11 ++- .../ctmrg_environments_triangular.jl | 60 +++++++++++- test/ctmrg/triangular.jl | 91 +++++++++++++++++++ 6 files changed, 255 insertions(+), 13 deletions(-) create mode 100644 src/algorithms/ctmrg_triangular/gaugefix.jl diff --git a/src/PEPSKit.jl b/src/PEPSKit.jl index 131088971..584b5ee1c 100644 --- a/src/PEPSKit.jl +++ b/src/PEPSKit.jl @@ -158,6 +158,7 @@ include("algorithms/contractions/triangular_ctmrg/expectation_value.jl") include("algorithms/ctmrg_triangular/ctmrg.jl") include("algorithms/ctmrg_triangular/utility.jl") include("algorithms/ctmrg_triangular/simultaneous.jl") +include("algorithms/ctmrg_triangular/gaugefix.jl") include("algorithms/compress/local.jl") diff --git a/src/algorithms/ctmrg_triangular/gaugefix.jl b/src/algorithms/ctmrg_triangular/gaugefix.jl new file mode 100644 index 000000000..e7e250b74 --- /dev/null +++ b/src/algorithms/ctmrg_triangular/gaugefix.jl @@ -0,0 +1,77 @@ +function gauge_fix(envfinal::CTMRGEnvTriangular, envprev::CTMRGEnvTriangular, alg::ScramblingEnvGauge) + signs, corner_phases, edge_phases = compute_gauge_fix_gauge(envfinal, envprev, alg) + return fix_phases(envfinal, signs, corner_phases, edge_phases) +end + +function compute_gauge_fix_gauge(envfinal::CTMRGEnvTriangular, envprev::CTMRGEnvTriangular, alg::ScramblingEnvGauge) + same_spaces = all(zip((envfinal.C, envfinal.Ea, envfinal.Eb), (envprev.C, envprev.Ea, envprev.Eb))) do (final, prev) + return size(final) == size(prev) && all(space.(final) .== space.(prev)) + end + same_spaces || throw(ArgumentError("Spaces of envfinal and envprev are not the same")) + signs = compute_relative_phases(envfinal, envprev, alg) + corner_phases, edge_phases = compute_global_phases(fix_relative_phases(envfinal, signs), envprev) + return signs, corner_phases, edge_phases +end + +# Follow the boundary tangent, alternating the two edge halves at each site. +function _triangular_edge_chain(env::CTMRGEnvTriangular, dir, r, c) + tensors = [env.Eb[dir, r, c], env.Ea[dir, r, c]] + unitcell = size(env)[2:3] + _, r′, c′ = _coordinates(dir, 2, r, c, unitcell) + while (r′, c′) != (r, c) + push!(tensors, env.Eb[dir, r′, c′], env.Ea[dir, r′, c′]) + _, r′, c′ = _coordinates(dir, 2, r′, c′, unitcell) + end + return tensors +end + +function compute_relative_phases(envfinal::CTMRGEnvTriangular, envprev::CTMRGEnvTriangular, ::ScramblingEnvGauge) + signs = map((1, 0)) do shift + return map(eachcoordinate(envfinal, 1:6)) do (dir, r, c) + Tsprev = circshift(_triangular_edge_chain(envprev, dir, r, c), -shift) + Tsfinal = circshift(_triangular_edge_chain(envfinal, dir, r, c), -shift) + M = map(t -> randn(storagetype(t), space(t)), Tsfinal) + ρprev = right_transfermatrix_fixedpoint(Tsprev, M, Arnoldi()) + ρfinal = right_transfermatrix_fixedpoint(Tsfinal, M, Arnoldi()) + Qprev, = left_orth!(ρprev; positive = true) + Qfinal, = left_orth!(ρfinal; positive = true) + return Qprev * Qfinal' + end + end + return (; Ea = signs[1], Eb = signs[2]) +end + +function fix_relative_phases(env::CTMRGEnvTriangular, signs) + unitcell = size(env)[2:3] + # Triangular corners have the same tensor layout as the edge halves. + C = map(eachcoordinate(env, 1:6)) do (dir, r, c) + next = _coordinates(dir, 2, r, c, unitcell) + return fix_gauge_edge(env.C[dir, r, c], signs.Eb[mod1(dir - 1, 6), r, c], signs.Eb[next...]) + end + Ea = map(eachcoordinate(env, 1:6)) do (dir, r, c) + next = _coordinates(dir, 2, r, c, unitcell) + return fix_gauge_edge(env.Ea[dir, r, c], signs.Ea[dir, r, c], signs.Eb[next...]) + end + Eb = map(eachcoordinate(env, 1:6)) do (dir, r, c) + return fix_gauge_edge(env.Eb[dir, r, c], signs.Eb[dir, r, c], signs.Ea[dir, r, c]) + end + return CTMRGEnvTriangular(C, Ea, Eb) +end + +function compute_global_phases(envfix::CTMRGEnvTriangular, envprev::CTMRGEnvTriangular) + corner_phases = dot.(envprev.C, envfix.C) + edge_phases = (; Ea = dot.(envprev.Ea, envfix.Ea), Eb = dot.(envprev.Eb, envfix.Eb)) + return corner_phases, edge_phases +end + +function fix_global_phases(env::CTMRGEnvTriangular, corner_phases, edge_phases) + return CTMRGEnvTriangular(env.C .* inv.(corner_phases), env.Ea .* inv.(edge_phases.Ea), env.Eb .* inv.(edge_phases.Eb)) +end + +function fix_phases(env::CTMRGEnvTriangular, signs, corner_phases, edge_phases) + return fix_global_phases(fix_relative_phases(env, signs), corner_phases, edge_phases) +end + +function calc_elementwise_convergence(envfinal::CTMRGEnvTriangular, envfix::CTMRGEnvTriangular; atol::Real = 1.0e-6) + return max(norm(envfinal.C .- envfix.C, Inf), norm(envfinal.Ea .- envfix.Ea, Inf), norm(envfinal.Eb .- envfix.Eb, Inf)) +end diff --git a/src/algorithms/optimization/implicit_differentiation.jl b/src/algorithms/optimization/implicit_differentiation.jl index 7eeeec356..0c78cc665 100644 --- a/src/algorithms/optimization/implicit_differentiation.jl +++ b/src/algorithms/optimization/implicit_differentiation.jl @@ -202,7 +202,7 @@ Evaluating the gradient of the cost function for CTMRG: - With explicit evaluation of the geometric sum, the gradient is computed by differentiating the cost function with the environment kept fixed, and then manually adding the gradient contributions from the environments. =# -_scrambling_env_gauge(::CTMRGAlgorithm) = ScramblingEnvGauge() +_scrambling_env_gauge(::Union{CTMRGAlgorithm, CTMRGAlgorithmTriangular}) = ScramblingEnvGauge() _scrambling_env_gauge(::C4vCTMRG) = ScramblingEnvGaugeC4v() function _set_fixed_truncation(alg::CTMRGAlgorithm) @@ -210,14 +210,21 @@ function _set_fixed_truncation(alg::CTMRGAlgorithm) return alg_fixed end +function _set_fixed_truncation(alg::SimultaneousCTMRGTriangular) + return @set alg.trunctype = :FixedSpaceTruncation +end + +_ctmrg_network(state, ::CTMRGAlgorithm) = InfiniteSquareNetwork(state) +_ctmrg_network(state, ::CTMRGAlgorithmTriangular) = InfiniteTriangularNetwork(state) + # compute the CTMRG gradient through fixed-point differentiation function _rrule( gradmode::FixedPointGradient, config::RuleConfig, ::typeof(MPSKit.leading_boundary), - envinit::CTMRGEnv, + envinit::Union{CTMRGEnv, CTMRGEnvTriangular}, state, - alg::CTMRGAlgorithm, + alg::Union{CTMRGAlgorithm, CTMRGAlgorithmTriangular}, ) _check_algorithm_combination(alg, gradmode) @@ -226,11 +233,11 @@ function _rrule( # prepare iterating function corresponding to a single gauge-fixed CTMRG iteration alg_fixed = _set_fixed_truncation(alg) # fix spaces during differentiation alg_gauge = _scrambling_env_gauge(alg) # select appropriate gauge-fixing algorithm - env_conv, info = ctmrg_iteration(InfiniteSquareNetwork(state), env, alg_fixed) + env_conv, info = ctmrg_iteration(_ctmrg_network(state, alg), env, alg_fixed) signs, corner_phases, edge_phases = compute_gauge_fix_gauge(env_conv, env, alg_gauge) function gauge_fixed_iteration(A, x) return fix_phases( - ctmrg_iteration(InfiniteSquareNetwork(A), x, alg_fixed)[1], + ctmrg_iteration(_ctmrg_network(A, alg), x, alg_fixed)[1], signs, corner_phases, edge_phases, ) end @@ -413,6 +420,17 @@ function _gauge_fix_c4v_projector(::C4vCTMRG{<:C4vQRProjector}, signs, info) return info.Q * signs[1]' end +function _check_algorithm_combination(::CTMRGAlgorithmTriangular, ::ImplicitGradient) + throw(ArgumentError("triangular CTMRG supports FixedPointGradient; characteristic equations for ImplicitGradient are not implemented")) +end + +function _rrule( + gradmode::ImplicitGradient, config::RuleConfig, + ::typeof(MPSKit.leading_boundary), envinit, state, alg::CTMRGAlgorithmTriangular, + ) + return _check_algorithm_combination(alg, gradmode) +end + function _check_algorithm_combination(::SequentialCTMRG, ::ImplicitGradient) msg = "The `:ImplicitGradient` algorithm is not yet compatible with the `SequentialCTMRG` algorithm; select \ SimultaneousCTMRG instead to use :ImplicitGradient" diff --git a/src/algorithms/optimization/peps_optimization.jl b/src/algorithms/optimization/peps_optimization.jl index e4a8464d1..04a9eadb9 100644 --- a/src/algorithms/optimization/peps_optimization.jl +++ b/src/algorithms/optimization/peps_optimization.jl @@ -124,9 +124,9 @@ initial guess for the first boundary contraction run. By default, a random initi Each local tensor is normalized initially, and optimization preserves its Euclidean norm by projecting gradients and preconditioned directions onto its real tangent space. The optimization parameters can be supplied via the keyword arguments or directly as a -`PEPSOptimize` struct. Triangular PEPS currently require `gradient_alg = nothing` -(differentiate the CTMRG iterations) and `precondition_alg = nothing`. Symmetrization -and fixed-point differentiation are not yet supported for triangular PEPS. +`PEPSOptimize` struct. Triangular PEPS support `FixedPointGradient` with a complex +environment, or `gradient_alg = nothing` to differentiate the CTMRG iterations. +They require `precondition_alg = nothing`; symmetrization is not yet supported. The following keyword arguments are supported: @@ -362,8 +362,9 @@ function check_input(::typeof(fixedpoint), peps₀::InfinitePEPSTriangular, env throw(ArgumentError("triangular PEPS optimization requires a CTMRGEnvTriangular environment")) parent_alg(alg.boundary_alg) isa CTMRGAlgorithmTriangular || throw(ArgumentError("triangular PEPS optimization requires a triangular CTMRG algorithm")) - isnothing(alg.gradient_alg) || - throw(ArgumentError("triangular fixed-point differentiation is not supported; set gradient_alg = nothing to differentiate the CTMRG iterations")) + if parent_alg(alg.gradient_alg) isa FixedPointGradient && scalartype(env₀) <: Real + throw(ArgumentError("triangular fixed-point differentiation requires a complex environment because gauge fixing generally produces complex gauges")) + end isnothing(alg.precondition_alg) || throw(ArgumentError("triangular preconditioning is not supported; set precondition_alg = nothing")) isnothing(alg.symmetrization) || diff --git a/src/environments/ctmrg_environments_triangular.jl b/src/environments/ctmrg_environments_triangular.jl index e457dbb27..7689ec801 100644 --- a/src/environments/ctmrg_environments_triangular.jl +++ b/src/environments/ctmrg_environments_triangular.jl @@ -101,19 +101,19 @@ function ChainRulesCore.rrule(::typeof(getproperty), e::CTMRGEnvTriangular, name if name === :C function C_pullback(ΔC_) ΔC = unthunk(ΔC_) - return NoTangent(), CTMRGEnvTriangular(ΔC, zerovector(e.Ea), zerovector(e.Eb)) + return NoTangent(), CTMRGEnvTriangular(ΔC, zerovector(e.Ea), zerovector(e.Eb)), NoTangent() end return result, C_pullback elseif name === :Ea function Ea_pullback(ΔEa_) ΔEa = unthunk(ΔEa_) - return NoTangent(), CTMRGEnvTriangular(zerovector(e.C), ΔEa, zerovector(e.Eb)) + return NoTangent(), CTMRGEnvTriangular(zerovector(e.C), ΔEa, zerovector(e.Eb)), NoTangent() end return result, Ea_pullback elseif name === :Eb function Eb_pullback(ΔEb_) ΔEb = unthunk(ΔEb_) - return NoTangent(), CTMRGEnvTriangular(zerovector(e.C), zerovector(e.Ea), ΔEb) + return NoTangent(), CTMRGEnvTriangular(zerovector(e.C), zerovector(e.Ea), ΔEb), NoTangent() end return result, Eb_pullback else @@ -121,3 +121,57 @@ function ChainRulesCore.rrule(::typeof(getproperty), e::CTMRGEnvTriangular, name throw(ArgumentError("No rrule for getproperty of $name")) end end + +function VI.zerovector(env::CTMRGEnvTriangular, ::Type{S}) where {S <: Number} + _zerovector = Base.Fix2(zerovector, S) + return CTMRGEnvTriangular(map(_zerovector, env.C), map(_zerovector, env.Ea), map(_zerovector, env.Eb)) +end +function VI.zerovector!(env::CTMRGEnvTriangular) + foreach(zerovector!, env.C) + foreach(zerovector!, env.Ea) + foreach(zerovector!, env.Eb) + return env +end +VI.zerovector!!(env::CTMRGEnvTriangular) = zerovector!(env) + +function VI.scale(env::CTMRGEnvTriangular, α::Number) + _scale = Base.Fix2(scale, α) + return CTMRGEnvTriangular(map(_scale, env.C), map(_scale, env.Ea), map(_scale, env.Eb)) +end +function VI.scale!(env::CTMRGEnvTriangular, α::Number) + _scale! = Base.Fix2(scale!, α) + foreach(_scale!, env.C) + foreach(_scale!, env.Ea) + foreach(_scale!, env.Eb) + return env +end +function VI.scale!(env₁::CTMRGEnvTriangular, env₂::CTMRGEnvTriangular, α::Number) + _scale!(x, y) = scale!(x, y, α) + foreach(_scale!, env₁.C, env₂.C) + foreach(_scale!, env₁.Ea, env₂.Ea) + foreach(_scale!, env₁.Eb, env₂.Eb) + return env₁ +end +VI.scale!!(env::CTMRGEnvTriangular, α::Number) = scale!(env, α) +VI.scale!!(env₁::CTMRGEnvTriangular, env₂::CTMRGEnvTriangular, α::Number) = scale!(env₁, env₂, α) + +function VI.add(env₁::CTMRGEnvTriangular, env₂::CTMRGEnvTriangular, α::Number, β::Number) + _add(x, y) = add(x, y, α, β) + return CTMRGEnvTriangular( + map(_add, env₁.C, env₂.C), map(_add, env₁.Ea, env₂.Ea), map(_add, env₁.Eb, env₂.Eb) + ) +end +function VI.add!(env₁::CTMRGEnvTriangular, env₂::CTMRGEnvTriangular, α::Number, β::Number) + _add!(x, y) = add!(x, y, α, β) + foreach(_add!, env₁.C, env₂.C) + foreach(_add!, env₁.Ea, env₂.Ea) + foreach(_add!, env₁.Eb, env₂.Eb) + return env₁ +end +VI.add!!(env₁::CTMRGEnvTriangular, env₂::CTMRGEnvTriangular, α::Number, β::Number) = add!(env₁, env₂, α, β) + +# Exploiting the fact that VectorInterface works for tuples: +function VI.inner(env₁::CTMRGEnvTriangular, env₂::CTMRGEnvTriangular) + return inner((env₁.C, env₁.Ea, env₁.Eb), (env₂.C, env₂.Ea, env₂.Eb)) +end +VI.norm(env::CTMRGEnvTriangular) = norm((env.C, env.Ea, env.Eb)) diff --git a/test/ctmrg/triangular.jl b/test/ctmrg/triangular.jl index e9a6d9c16..51d4ca4d7 100644 --- a/test/ctmrg/triangular.jl +++ b/test/ctmrg/triangular.jl @@ -445,6 +445,19 @@ end @test all(isfinite, info.gradnorms) @test length(info.contraction_metrics) == length(info.gradnorms_unitcell) == length(info.times) == 2 + if T === ComplexF64 + ψfixed, envfixed, Efixed, infofixed = fixedpoint( + H, state, env; kwargs..., gradient_alg = (; tol = 1.0e-8), + boundary_alg = (; miniter = 2, maxiter = 10, tol = 1.0e-10), + ) + @test Efixed ≈ E + @test infofixed.costs ≈ info.costs + @test infofixed.gradnorms ≈ info.gradnorms + @test Efixed ≈ cost_function(ψfixed, envfixed, H) + else + @test_throws ArgumentError fixedpoint(H, state, env; kwargs..., gradient_alg = (;)) + end + @test_throws ArgumentError fixedpoint(H, state, env; kwargs..., gradient_alg = (; alg = :ImplicitGradient)) @test_throws ArgumentError fixedpoint(H, state, nothing, alg) @test_throws ArgumentError fixedpoint(H, state, env; boundary_alg) @test_throws ArgumentError fixedpoint(H, state, env; boundary_alg, gradient_alg = nothing) @@ -453,6 +466,84 @@ end end end +@testset "Triangular gauge fixing" begin + Random.seed!(63412) + for (V, χ, sandwich) in ( + (ComplexSpace(2), ComplexSpace(2), false), + (ComplexSpace(2), ComplexSpace(2), true), + (Z2Space(0 => 1, 1 => 1), Z2Space(0 => 1, 1 => 1), true), + ) + sz = (2, 3) # diagonal boundaries traverse both unit-cell axes + env = CTMRGEnvTriangular(randn, ComplexF64, sandwich ? V ⊗ V' : V, χ; unitcell = sz) + signs = map((:Ea, :Eb)) do _ + map(env.C) do t + first(left_orth(randn(ComplexF64, space(t, 1), space(t, 1)); positive = true)) + end + end + rotated = PEPSKit.fix_relative_phases(env, (; Ea = signs[1], Eb = signs[2])) + original = deepcopy(env) + # Check that the gauges cancel in the physical boundary contractions. + network = if sandwich + InfiniteTriangularNetwork(InfinitePEPSTriangular([randn(ComplexF64, V, V^3 ⊗ (V')^3) for _ in 1:sz[1], _ in 1:sz[2]])) + else + InfiniteTriangularNetwork([randn(ComplexF64, V^3, V^3) for _ in 1:sz[1], _ in 1:sz[2]]) + end + for contract in (PEPSKit._contract_corners, PEPSKit._contract_edges_0, PEPSKit._contract_edges_60, PEPSKit._contract_edges_120) + @test contract((1, 2), network, rotated) ≈ contract((1, 2), network, env) + end + rotated = CTMRGEnvTriangular(im .* rotated.C, -rotated.Ea, -im .* rotated.Eb) + fixed = PEPSKit.gauge_fix(rotated, env, PEPSKit.ScramblingEnvGauge()) + @test PEPSKit.calc_elementwise_convergence(env, fixed) < 1.0e-10 + @test (env.C, env.Ea, env.Eb) == (original.C, original.Ea, original.Eb) + different = CTMRGEnvTriangular(ones, ComplexF64, sandwich ? V ⊗ V' : V, χ; unitcell = (1, 1)) + @test_throws ArgumentError PEPSKit.gauge_fix(different, env, PEPSKit.ScramblingEnvGauge()) + end +end + +@testset "Triangular fixed-point differentiation" begin + Random.seed!(48531) + V = ComplexSpace(2) + fields = [0.02 -0.03] + β = 0.1 + W = sqrt(Symmetric([exp(β / 2) exp(-β / 2); exp(-β / 2) exp(β / 2)])) + state = InfinitePEPSTriangular( + map(fields) do h + data = [exp(h * (3 - 2s) / 2 + im * h * s) * prod(W[s, i] for i in Tuple(I)) for s in 1:2, I in CartesianIndices((2, 2, 2, 2, 2, 2))] + TensorMap(data, V, V^3 ⊗ (V')^3) + end + ) + Sz = TensorMap(ComplexF64[1 0; 0 -1] / 2, V, V) + H = LocalOperator(physicalspace(state), [((1, c),) => Sz for c in 1:2]) + direction = InfinitePEPSTriangular(map(t -> Sz * t, unitcell(state))) + env₀ = CTMRGEnvTriangular(ones, ComplexF64, V ⊗ V', V; unitcell = size(state)) + gradalg = PEPSKit.FixedPointGradient(; solver_alg = (; alg = :GMRES, krylovdim = 30), tol = 1.0e-8, maxiter = 10, verbosity = 0) + for (projector_alg, conditioning) in ((:twothirds, false), (:full, true)) + alg = SimultaneousCTMRGTriangular(; projector_alg, conditioning, maxiter = 200, miniter = 4, tol = 1.0e-10, verbosity = 0) + env, info = leading_boundary(env₀, state, alg) + @test info.converged + env′, = PEPSKit.ctmrg_iteration(InfiniteTriangularNetwork(state), env, PEPSKit._set_fixed_truncation(alg)) + fixed = PEPSKit.gauge_fix(env′, env, PEPSKit.ScramblingEnvGauge()) + @test PEPSKit.calc_elementwise_convergence(env, fixed) < 1.0e-7 + E, gs = withgradient(state) do A + env′, = PEPSKit.hook_pullback(leading_boundary, env, A, alg; alg_rrule = gradalg) + cost_function(A, env′, H) + end + g = only(gs) + @test g isa typeof(state) + # The custom rule reconverges from env before evaluating the energy. + env_ref, = leading_boundary(env, state, alg) + @test E ≈ cost_function(state, env_ref, H) + ε = 1.0e-4 + energies = map((-ε, ε)) do step + A = state + step * direction + env′, info′ = leading_boundary(env, A, alg) + @test info′.converged + cost_function(A, env′, H) + end + @test real(dot(g, direction)) ≈ (energies[2] - energies[1]) / (2ε) rtol = 1.0e-5 + end +end + @testset "Triangular nearest-neighbor energy" begin Random.seed!(28514) pspace, vspace = ComplexSpace(2), ComplexSpace(1) From 004cfd0e3600f4f68f2d0d3442b44700d69d2056 Mon Sep 17 00:00:00 2001 From: Philipp Schmoll Date: Tue, 27 Oct 2026 20:18:55 +0100 Subject: [PATCH 27/41] Fix triangular constructors and validate bond spaces --- src/networks/infinitetriangularnetwork.jl | 25 +++++---- src/states/infinitepepstriangular.jl | 16 ++++-- test/types/infinitetriangularnetwork.jl | 64 +++++++++++++++++++++++ 3 files changed, 89 insertions(+), 16 deletions(-) create mode 100644 test/types/infinitetriangularnetwork.jl diff --git a/src/networks/infinitetriangularnetwork.jl b/src/networks/infinitetriangularnetwork.jl index d8eebdd34..0711e8419 100644 --- a/src/networks/infinitetriangularnetwork.jl +++ b/src/networks/infinitetriangularnetwork.jl @@ -13,19 +13,18 @@ struct InfiniteTriangularNetwork{O} A::Matrix{O} InfiniteTriangularNetwork{O}(A::Matrix{O}) where {O} = new{O}(A) function InfiniteTriangularNetwork(A::Matrix) - # for I in eachindex(IndexCartesian(), A) - # d, w = Tuple(I) - # northwest_virtualspace(A[d, w]) == - # _elementwise_dual(southeast_virtualspace(A[_prev(d, end), w])) || throw( - # SpaceMismatch("North virtual space at site $((d, w)) does not match.") - # ) - # northeast_virtualspace(A[d, w]) == - # _elementwise_dual(southwest_virtualspace(A[d, _next(w, end)])) || - # throw(SpaceMismatch("East virtual space at site $((d, w)) does not match.")) - # east_virtualspace(A[d, w]) == - # _elementwise_dual(west_virtualspace(A[d, _next(w, end)])) || - # throw(SpaceMismatch("East virtual space at site $((d, w)) does not match.")) - # end + for I in eachindex(IndexCartesian(), A) + r, c = Tuple(I) + virtualspace(A[r, c], 1) == + _elementwise_dual(virtualspace(A[_prev(r, end), c], 4)) || + throw(SpaceMismatch("Northwest virtual space at site $((r, c)) does not match.")) + virtualspace(A[r, c], 2) == + _elementwise_dual(virtualspace(A[_prev(r, end), _next(c, end)], 5)) || + throw(SpaceMismatch("Northeast virtual space at site $((r, c)) does not match.")) + virtualspace(A[r, c], 3) == + _elementwise_dual(virtualspace(A[r, _next(c, end)], 6)) || + throw(SpaceMismatch("East virtual space at site $((r, c)) does not match.")) + end return InfiniteTriangularNetwork{eltype(A)}(A) end end diff --git a/src/states/infinitepepstriangular.jl b/src/states/infinitepepstriangular.jl index 14bc7d025..f72039612 100644 --- a/src/states/infinitepepstriangular.jl +++ b/src/states/infinitepepstriangular.jl @@ -12,7 +12,17 @@ struct InfinitePEPSTriangular{T <: PEPSTensorTriangular} InfinitePEPSTriangular{T}(A::Matrix{T}) where {T <: PEPSTensorTriangular} = new{T}(A) function InfinitePEPSTriangular(A::Array{T, 2}) where {T <: PEPSTensorTriangular} bosonic_braiding = BraidingStyle(sectortype(T)) === Bosonic() - # TODO: check braiding and spaces + for (r, c) in Tuple.(CartesianIndices(A)) + (bosonic_braiding || !isdual(physicalspace(A[r, c]))) || + throw(ArgumentError("Dual physical spaces for symmetry sectors with non-trivial twists are not allowed (for now).")) + northwest_virtualspace(A[r, c]) == southeast_virtualspace(A[_prev(r, end), c])' || + throw(SpaceMismatch("Northwest virtual space at site $((r, c)) does not match.")) + northeast_virtualspace(A[r, c]) == southwest_virtualspace(A[_prev(r, end), _next(c, end)])' || + throw(SpaceMismatch("Northeast virtual space at site $((r, c)) does not match.")) + east_virtualspace(A[r, c]) == west_virtualspace(A[r, _next(c, end)])' || + throw(SpaceMismatch("East virtual space at site $((r, c)) does not match.")) + dim(space(A[r, c])) > 0 || @warn "no fusion channels at site ($r, $c)" + end return new{T}(A) end end @@ -38,11 +48,11 @@ of the PEPS tensor at each site in the unit cell as a matrix. function InfinitePEPSTriangular( f, T::Type{<:Number}, Pspaces::M, NWspaces::M, NEspaces::M = NWspaces, Espaces::M = NWspaces ) where {M <: AbstractMatrix{<:ElementarySpace}} - size(Pspaces) == size(NEspaces) == size(NEspaces) == size(Espaces) || + size(Pspaces) == size(NWspaces) == size(NEspaces) == size(Espaces) || throw(ArgumentError("Input spaces should have equal sizes.")) SEspaces = adjoint.(circshift(NWspaces, (-1, 0))) - SWspaces = adjoint.(NEspaces) + SWspaces = adjoint.(circshift(NEspaces, (-1, 1))) Wspaces = adjoint.(circshift(Espaces, (0, 1))) A = map(Pspaces, NWspaces, NEspaces, Espaces, SEspaces, SWspaces, Wspaces) do P, NW, NE, E, SE, SW, W diff --git a/test/types/infinitetriangularnetwork.jl b/test/types/infinitetriangularnetwork.jl new file mode 100644 index 000000000..ab2e9f501 --- /dev/null +++ b/test/types/infinitetriangularnetwork.jl @@ -0,0 +1,64 @@ +using Test +using TensorKit +using PEPSKit +using Zygote +using LinearAlgebra +using PEPSKit: unitcell, virtualspace + +@testset "Triangular constructors" begin + for T in (Float64, ComplexF64), (V, W) in ( + (ComplexSpace(1), ComplexSpace(2)), + (U1Space(0 => 1), U1Space(0 => 1, 1 => 1)), + ) + NW = [V W V; W V W] + NE = [W V V; V W W] + E = [V V W; W V V] + P = fill(W, 2, 3) + state = InfinitePEPSTriangular(ones, T, P, NW, NE, E) + A = unitcell(state) + for (dir, δr, δc) in ((1, -1, 0), (2, -1, 1), (3, 0, 1)), r in 1:2, c in 1:3 + @test virtualspace(state, r, c, dir) == virtualspace(state, r + δr, c + δc, dir + 3)' + end + @test InfinitePEPSTriangular(copy(A)) == state + @test unitcell(InfiniteTriangularNetwork(state)) == map(tuple, A, A) + @test_throws ArgumentError InfinitePEPSTriangular(ones, T, P, NW[:, 1:2], NE, E) + + # Same six virtual legs in the classical tensor layout W SW SE ← NW NE E. + pf(t) = ones(T, domain(t)[6]' ⊗ domain(t)[5]' ⊗ domain(t)[4]', domain(t)[1] ⊗ domain(t)[2] ⊗ domain(t)[3]) + tensors = map(pf, A) + @test unitcell(InfiniteTriangularNetwork(tensors)) == tensors + + # Break each leg at a periodic boundary without changing its dimension. + for dir in 1:6 + invalid = copy(A) + spaces = collect(domain(A[1, 3])) + spaces[dir] = spaces[dir]' + invalid[1, 3] = ones(T, codomain(A[1, 3]), ProductSpace(spaces...)) + @test_throws SpaceMismatch InfinitePEPSTriangular(invalid) + @test_throws SpaceMismatch InfiniteTriangularNetwork(map(tuple, invalid, A)) + @test_throws SpaceMismatch InfiniteTriangularNetwork(map(tuple, A, invalid)) + @test_throws SpaceMismatch InfiniteTriangularNetwork(map(pf, invalid)) + end + + uniform = InfinitePEPSTriangular(ones, T, W, V; unitcell = (1, 1)) + @test size(InfinitePEPSTriangular(uniform[1, 1]; unitcell = (2, 3))) == (2, 3) + end + + P = Vect[FermionParity](0 => 1, 1 => 1) + V = Vect[FermionParity](0 => 1) + @test size(InfinitePEPSTriangular(ones, Float64, P, V)) == (1, 1) + @test_throws ArgumentError InfinitePEPSTriangular(ones(Float64, P', V^3 ⊗ (V')^3)) +end + +@testset "Triangular constructor differentiation" begin + V = ComplexSpace(1) + A = unitcell(InfinitePEPSTriangular(ones, Float64, V, V; unitcell = (1, 2))) + for construct in (InfinitePEPSTriangular, a -> InfiniteTriangularNetwork(map(tuple, a, a))) + f(a) = sum(unitcell(construct(a))) do t + x = t isa Tuple ? first(t) : t + return real(dot(x, x)) + end + g = only(Zygote.gradient(f, A)) + @test all(isapprox.(g, 2 .* A)) + end +end From f7bf6419d65fcb94222e35ae380f32ae290442b0 Mon Sep 17 00:00:00 2001 From: Philipp Schmoll Date: Tue, 27 Oct 2026 20:22:56 +0100 Subject: [PATCH 28/41] Fix triangular cornertype/edgetype dispatch --- .../ctmrg_environments_triangular.jl | 4 ++-- test/types/infinitetriangularnetwork.jl | 21 +++++++++++++++++++ 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/environments/ctmrg_environments_triangular.jl b/src/environments/ctmrg_environments_triangular.jl index 7689ec801..4e66346af 100644 --- a/src/environments/ctmrg_environments_triangular.jl +++ b/src/environments/ctmrg_environments_triangular.jl @@ -57,9 +57,9 @@ Base.real(env::CTMRGEnvTriangular) = CTMRGEnvTriangular(real.(env.C), real.(env. Base.complex(env::CTMRGEnvTriangular) = CTMRGEnvTriangular(complex.(env.C), complex.(env.Ea), complex.(env.Eb)) cornertype(env::CTMRGEnvTriangular) = cornertype(typeof(env)) -cornertype(::Type{CTMRGEnvTriangular{T}}) where {T} = T +cornertype(::Type{CTMRGEnvTriangular{C, A, B}}) where {C, A, B} = C edgetype(env::CTMRGEnvTriangular) = edgetype(typeof(env)) -edgetype(::Type{CTMRGEnvTriangular{T}}) where {T} = T +edgetype(::Type{CTMRGEnvTriangular{C, A, B}}) where {C, A, B} = promote_type(A, B) TensorKit.spacetype(::Type{E}) where {E <: CTMRGEnvTriangular} = spacetype(cornertype(E)) diff --git a/test/types/infinitetriangularnetwork.jl b/test/types/infinitetriangularnetwork.jl index ab2e9f501..bf2797673 100644 --- a/test/types/infinitetriangularnetwork.jl +++ b/test/types/infinitetriangularnetwork.jl @@ -62,3 +62,24 @@ end @test all(isapprox.(g, 2 .* A)) end end + +@testset "Triangular environment types" begin + V = ComplexSpace(1) + for sandwich in (false, true) + env = CTMRGEnvTriangular(ones, Float64, sandwich ? V ⊗ V' : V, V) + complex_edges = complex.(env.Ea) + for (Ea, Eb, E) in ( + (env.Ea, env.Eb, eltype(env.Ea)), + (complex_edges, env.Eb, eltype(complex_edges)), + (env.Ea, complex_edges, eltype(complex_edges)), + ) + env′ = CTMRGEnvTriangular(env.C, Ea, Eb) + @test PEPSKit.cornertype(env′) === eltype(env.C) + @test PEPSKit.cornertype(typeof(env′)) === eltype(env.C) + @test PEPSKit.edgetype(env′) === E + @test PEPSKit.edgetype(typeof(env′)) === E + @test spacetype(env′) === typeof(V) + @test spacetype(typeof(env′)) === typeof(V) + end + end +end From 0962834902022bad2e1f85d1613c67b29a6758fd Mon Sep 17 00:00:00 2001 From: Philipp Schmoll Date: Tue, 27 Oct 2026 20:46:23 +0100 Subject: [PATCH 29/41] Document triangular CTMRG --- .../ctmrg_triangular/simultaneous.jl | 21 +++++++++++++++ .../ctmrg_environments_triangular.jl | 22 ++++++++++++++- src/states/infinitepepstriangular.jl | 27 +++++++++---------- 3 files changed, 55 insertions(+), 15 deletions(-) diff --git a/src/algorithms/ctmrg_triangular/simultaneous.jl b/src/algorithms/ctmrg_triangular/simultaneous.jl index cf228dd5c..628de4220 100644 --- a/src/algorithms/ctmrg_triangular/simultaneous.jl +++ b/src/algorithms/ctmrg_triangular/simultaneous.jl @@ -1,3 +1,24 @@ +""" +$(TYPEDEF) + +Simultaneous CTMRG for triangular PEPS and classical networks, with direct Ta/Tb +updates of all six edge directions. See [`leading_boundary`](@ref) for convergence and truncation diagnostics. + +## Constructors + + SimultaneousCTMRGTriangular(; kwargs...) + +* `tol::Real=$(Defaults.ctmrg_tol)` : Convergence tolerance for corner and edge spectra. +* `maxiter::Int=$(Defaults.ctmrg_maxiter)` : Maximum number of CTMRG iterations. +* `miniter::Int=$(Defaults.ctmrg_miniter)` : Minimum number of CTMRG iterations. +* `verbosity::Int=$(Defaults.ctmrg_verbosity)` : Output verbosity, as in [`leading_boundary`](@ref). +* `projector_alg::Symbol=:twothirds` : Corner-projector construction, either `:twothirds` or `:full`. +* `conditioning::Bool=true` : Square-root SVD conditioning of the Ta/Tb edge split. + This does not change the corner projectors; `:full` always splits its opposite + corner factor with a square-root SVD, while `:twothirds` uses its factors directly. +* `trunctype::Symbol=:truncrank` : Keep the initial environment dimension. + Use `:FixedSpaceTruncation` to preserve each environment bond space, including its sectors. +""" struct SimultaneousCTMRGTriangular <: CTMRGAlgorithmTriangular tol::Float64 maxiter::Int diff --git a/src/environments/ctmrg_environments_triangular.jl b/src/environments/ctmrg_environments_triangular.jl index 4e66346af..454bdd9d9 100644 --- a/src/environments/ctmrg_environments_triangular.jl +++ b/src/environments/ctmrg_environments_triangular.jl @@ -1,4 +1,24 @@ -#TODO: Add docs and figure +""" +$(TYPEDEF) + +Triangular corner transfer-matrix environment, with one corner and two edge halves +(`Ea` and `Eb`, also called Ta and Tb) for each of six directions at every site. The arrays +are indexed as `[direction, row, column]`, with directions ordered `NW, NE, E, SE, SW, W`. + +## Fields + +$(TYPEDFIELDS) + +## Constructors + + CTMRGEnvTriangular(f, T, D, χspace; unitcell=(1, 1)) + +Initialize normalized tensors using `f` and scalar type `T`, with uniform virtual +space `D` and environment space `χspace`. For a PEPS with virtual space `V`, use +`D = V ⊗ V'`; for a classical network, use its single-layer virtual space. +The unit-cell size must match the state or network. Use a complex scalar type for +optimization with [`FixedPointGradient`](@ref). +""" struct CTMRGEnvTriangular{TC, TA, TB} "6 x rows x cols array of corner C tensors, where the first dimension specifies the spatial direction" C::Array{TC, 3} diff --git a/src/states/infinitepepstriangular.jl b/src/states/infinitepepstriangular.jl index f72039612..1ace2ae81 100644 --- a/src/states/infinitepepstriangular.jl +++ b/src/states/infinitepepstriangular.jl @@ -1,5 +1,5 @@ """ - struct InfinitePEPSTriangular{T<:PEPSTensorTriangularTriangular} + struct InfinitePEPSTriangular{T<:PEPSTensorTriangular} Represents an infinite projected entangled-pair state on a 2D triangular lattice. @@ -40,10 +40,11 @@ function InfinitePEPSTriangular(A::AbstractMatrix{<:PEPSTensorTriangular}) end """ - InfinitePEPSTriangular([f=randn, T=ComplexF64,] Pspaces::A, Nspaces::A, [Espaces::A]) where {A<:AbstractMatrix{ElementarySpace}} + InfinitePEPSTriangular([f=randn, T=ComplexF64,] Pspaces::A, NWspaces::A, [NEspaces::A], [Espaces::A]) where {A<:AbstractMatrix{ElementarySpace}} -Create an `InfinitePEPSTriangular` by specifying the physical, north virtual and east virtual spaces -of the PEPS tensor at each site in the unit cell as a matrix. +Create an `InfinitePEPSTriangular` by specifying matrices of physical, northwest, northeast +and east virtual spaces. Omitted virtual spaces default to `NWspaces`; the opposite +spaces are the duals of the corresponding neighboring spaces, with periodic boundaries. """ function InfinitePEPSTriangular( f, T::Type{<:Number}, Pspaces::M, NWspaces::M, NEspaces::M = NWspaces, Espaces::M = NWspaces @@ -72,14 +73,11 @@ end Create an `InfinitePEPSTriangular` by specifying a tensor and unit cell. -The unit cell is labeled as a matrix which means that any tensor in the unit cell, -regardless if PEPS tensor or environment tensor, is obtained by shifting the row -and column index `[r, c]` by one, respectively: -``` -TODO: diagram -``` -The unit cell has periodic boundary conditions, so `[r, c]` is indexed modulo the -size of the unit cell. +The unit cell uses one-based matrix coordinates `[r, c]`. The virtual legs are ordered +`NW, NE, E, SE, SW, W`, connecting to neighbors at offsets +`(-1, 0), (-1, 1), (0, 1), (1, 0), (1, -1), (0, -1)`, respectively. +Contractions wrap these coordinates periodically; direct `peps[r, c]` indexing follows +ordinary matrix bounds. A three-sublattice cell is `[A B C; C A B; B C A]`. """ function InfinitePEPSTriangular(A::T; unitcell::Tuple{Int, Int} = (1, 1)) where {T <: PEPSTensorTriangular} return InfinitePEPSTriangular(fill(A, unitcell)) @@ -93,9 +91,10 @@ function _fill_state_virtual_spaces_triangular( end """ - InfinitePEPSTriangular([f=randn, T=ComplexF64,] Pspace, Nspace, [Espace]; unitcell=(1,1)) + InfinitePEPSTriangular([f=randn, T=ComplexF64,] Pspace, NWspace, [NEspace], [Espace]; unitcell=(1,1)) -Create an InfinitePEPSTriangular by specifying its physical, north and east spaces and unit cell. +Create an `InfinitePEPSTriangular` with uniform physical, northwest, northeast and east +virtual spaces and the given unit-cell size. Omitted virtual spaces default to `NWspace`. """ function InfinitePEPSTriangular( f, T::Type{<:Number}, Pspace::S, vspaces...; unitcell::Tuple{Int, Int} = (1, 1) From ba21bea28b781604a5c6fe608233002f9a451c27 Mon Sep 17 00:00:00 2001 From: Philipp Schmoll Date: Tue, 27 Oct 2026 20:47:20 +0100 Subject: [PATCH 30/41] Add three-sublattice AF Heisenberg optimization example --- docs/make.jl | 2 +- docs/src/examples/triangular_ctmrg/index.md | 165 ++++++++++++ docs/src/examples/triangular_ctmrg/main.ipynb | 246 ++++++++++++++++++ examples/triangular_ctmrg/main.jl | 146 +++++++++++ 4 files changed, 558 insertions(+), 1 deletion(-) create mode 100644 docs/src/examples/triangular_ctmrg/index.md create mode 100644 docs/src/examples/triangular_ctmrg/main.ipynb create mode 100644 examples/triangular_ctmrg/main.jl diff --git a/docs/make.jl b/docs/make.jl index 3e7b253eb..63ea39962 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -46,7 +46,7 @@ mathengine = MathJax3( # examples pages examples_optimization = joinpath.( - ["heisenberg", "bose_hubbard", "xxz", "fermi_hubbard", "c4v_ctmrg"], Ref("index.md") + ["heisenberg", "bose_hubbard", "xxz", "fermi_hubbard", "c4v_ctmrg", "triangular_ctmrg"], Ref("index.md") ) examples_time_evolution = joinpath.( ["heisenberg_su", "hubbard_su", "j1j2_su"], Ref("index.md") diff --git a/docs/src/examples/triangular_ctmrg/index.md b/docs/src/examples/triangular_ctmrg/index.md new file mode 100644 index 000000000..01da33391 --- /dev/null +++ b/docs/src/examples/triangular_ctmrg/index.md @@ -0,0 +1,165 @@ +```@meta +EditURL = "../../../../examples/triangular_ctmrg/main.jl" +``` + +[![](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/QuantumKitHub/PEPSKit.jl/gh-pages?filepath=dev/examples/triangular_ctmrg/main.ipynb) +[![](https://img.shields.io/badge/show-nbviewer-579ACA.svg)](https://nbviewer.jupyter.org/github/QuantumKitHub/PEPSKit.jl/blob/gh-pages/dev/examples/triangular_ctmrg/main.ipynb) +[![](https://img.shields.io/badge/download-project-orange)](https://minhaskamal.github.io/DownGit/#/home?url=https://github.com/QuantumKitHub/PEPSKit.jl/examples/tree/gh-pages/dev/examples/triangular_ctmrg) + +````julia +using Markdown #hide +```` + +# [Triangular CTMRG and optimization](@id examples_triangular_ctmrg) + +This example contracts and optimizes an antiferromagnetic spin-1/2 Heisenberg PEPS +on the triangular lattice using [`InfinitePEPSTriangular`](@ref), +[`CTMRGEnvTriangular`](@ref), and the native [`fixedpoint`](@ref) interface. +A single-site tensor and fixed 120° sublattice rotations represent a three-sublattice +state. We start from a seeded random real tensor with `D = 2` and a complex +environment with `χ = 40`. Check convergence in `χ` before drawing quantitative +conclusions from the energy. + +From the repository root, prepare the example environment once with +`julia --project=examples -e 'using Pkg; Pkg.develop(path="."); Pkg.instantiate()'`, +then run `julia --project=examples --threads=2 examples/triangular_ctmrg/main.jl`. + +````julia +using Random +using TensorKit, PEPSKit +using TensorKitTensors.SpinOperators +Random.seed!(123456789); +nothing #hide +```` + +## Unit cell and nearest neighbors + +PEPSKit indexes the unit cell as a Julia matrix: `[r, c]` means row `r`, column `c`, +starting at 1. For `[A B C; D E F; G H I]`, `[2, 1]` is `D` and `[1, 2]` is `B`. +The six virtual legs are ordered `NW, NE, E, SE, SW, W`, with offsets +`(-1, 0), (-1, 1), (0, 1), (1, 0), (1, -1), (0, -1)` respectively. + +To count every nearest-neighbor bond once, we use the following three offsets: + +| Bond family | Offset `(Δr, Δc)` | Virtual leg | Relative rotation | +|:------------|:------------------|:------------|:------------------| +| 0° | `(0, 1)` | E | `2π/3` | +| 60° | `(1, -1)` | SW | `-4π/3` | +| 120° | `(1, 0)` | SE | `-2π/3` | + +The bond-family angles are unoriented; the relative rotations refer to the directed +offsets in the table. With `R(θ) = exp(-im * θ * S_y)`, the physical tensor at +`[r, c]` is `R((r - 1)q[1] + (c - 1)q[2]) * A`, where `q = (-2π/3, 2π/3)`. +This gives `[A B C; C A B; B C A]`, with `B = R(2π/3) * A` and +`C = R(4π/3) * A`, up to irrelevant spin-1/2 tensor phases. It is the PEPSKit +version of the variPEPS pattern `[A B C; B C A; C A B]` with `q = (2π/3, 2π/3)`. + +We optimize `A` in a single-site unit cell. All three sublattices share its +variational parameters, and the wavevector stays fixed during optimization. + +````julia +D = 2 +χ = 32 +P, V = ℂ^2, ℂ^D +peps₀ = InfinitePEPSTriangular(randn, Float64, P, V; unitcell = (1, 1)); +nothing #hide +```` + +## Hamiltonian and CTMRG + +The Heisenberg interaction is invariant under a common rotation of both spins, so +only their relative rotation enters each bond. We absorb it into the gate on the +second site: `hδ = (id(P) ⊗ R(q⋅δ))' * h * (id(P) ⊗ R(q⋅δ))`. The diagonal +angle `-4π/3` gives the same gate as `2π/3`. This lets the existing +[`LocalOperator`](@ref) and `expectation_value` interfaces evaluate the physical +three-sublattice energy using one tensor and three bond gates. + +We use antiferromagnetic coupling `J = 1`. Keep the neighbor coordinates unwrapped, +including those outside the unit cell: the contractions apply periodicity while +preserving which bond is intended. Triangular expectation values support onsite and nearest-neighbor +terms in all three bond families. + +````julia +h = S_x_S_x() + S_y_S_y() + S_z_S_z() +offsets = (CartesianIndex(0, 1), CartesianIndex(1, -1), CartesianIndex(1, 0)) +q = (-2π / 3, 2π / 3) +rotation(θ) = TensorMap([cos(θ / 2) -sin(θ / 2); sin(θ / 2) cos(θ / 2)], P, P) +gates = map(offsets) do δ + U = id(P) ⊗ rotation(q[1] * δ[1] + q[2] * δ[2]) + return U' * h * U +end +H = LocalOperator( + physicalspace(peps₀), + [(I, I + δ) => gate for I in CartesianIndices(size(peps₀)) for (δ, gate) in zip(offsets, gates)], +); +nothing #hide +```` + +The environment constructor takes the doubled virtual space `V ⊗ V'` of the PEPS +norm network and an environment space `ℂ^χ`. We initialize it with `ones`. +The PEPS tensor and the rotated Heisenberg gates are real, but the current gauge +fixing for fixed-point differentiation requires a complex environment. + +[`leading_boundary`](@ref) selects triangular CTMRG from the environment; here we +also name it explicitly, so the same settings can be passed to the optimizer. +We choose `projector_alg = :full` for the corner construction; the default +`:twothirds` uses two thirds of the environment. `conditioning = true` (the default) conditions the direct +Ta/Tb edge split. It does not toggle corner conditioning. + +````julia +env_initial = CTMRGEnvTriangular(ones, ComplexF64, V ⊗ V', ℂ^χ; unitcell = size(peps₀)) +boundary_alg = (; + alg = :SimultaneousCTMRGTriangular, tol = 1.0e-7, maxiter = 100, + projector_alg = :full, trunctype = :FixedSpaceTruncation, +) +env₀, info_ctmrg = leading_boundary(env_initial, peps₀; boundary_alg...); +@show info_ctmrg.converged info_ctmrg.convergence_error info_ctmrg.contraction_metrics; +nothing #hide +```` + +Convergence compares the corner and edge singular-value spectra. The truncation +metrics describe the local SVD errors, not the error in the energy. Check CTMRG +convergence and increase `χ` when assessing the accuracy of an entangled calculation. + +`expectation_value` sums all terms in `H`, so this Hamiltonian gives the energy per +unit cell. Divide by the number of sites to obtain the energy per site: + +````julia +E₀ = real(expectation_value(peps₀, H, env₀)) +@show E₀ / length(peps₀); +nothing #hide +```` + +## Native optimization + +We use L-BFGS with [`FixedPointGradient`](@ref) and a GMRES linear solver to +differentiate the CTMRG fixed point. Triangular optimization currently requires +`precondition_alg = nothing` +and does not support symmetrization. Alternatively, `gradient_alg = nothing` +differentiates through the CTMRG iterations. + +PEPSKit adapts the CTMRG and gradient tolerances during optimization, using looser +solves when the gradient is large. Check the final gradient norm and increase the +optimization budget if necessary. +Convergence of CTMRG at a fixed `χ` does not establish convergence in `χ`; repeat +the calculation at larger environment dimensions to assess the energy. + +````julia +peps, env, E, info_opt = fixedpoint( + H, peps₀, env₀; + boundary_alg, + gradient_alg = (; alg = :FixedPointGradient, solver_alg = (; alg = :GMRES)), + precondition_alg = nothing, + optimizer_alg = (; alg = :LBFGS, tol = 1.0e-6, maxiter = 100), verbosity = 3, +); +@show E / length(peps) info_opt.gradnorms[end]; +nothing #hide +```` + +The `D = 2` ansatz allows entanglement beyond the 120° product state, whose +energy is `-3/8` per site. The returned `E` is the energy per unit cell, which here +contains a single site. Increase both `D` and `χ` to study the quantum ground state. + +--- + +*This page was generated using [Literate.jl](https://github.com/fredrikekre/Literate.jl).* diff --git a/docs/src/examples/triangular_ctmrg/main.ipynb b/docs/src/examples/triangular_ctmrg/main.ipynb new file mode 100644 index 000000000..87f120ee8 --- /dev/null +++ b/docs/src/examples/triangular_ctmrg/main.ipynb @@ -0,0 +1,246 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "using Markdown #hide" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Triangular CTMRG and optimization\n", + "\n", + "This example contracts and optimizes an antiferromagnetic spin-1/2 Heisenberg PEPS\n", + "on the triangular lattice using `InfinitePEPSTriangular`,\n", + "`CTMRGEnvTriangular`, and the native `fixedpoint` interface.\n", + "A single-site tensor and fixed 120° sublattice rotations represent a three-sublattice\n", + "state. We start from a seeded random real tensor with `D = 2` and a complex\n", + "environment with `χ = 40`. Check convergence in `χ` before drawing quantitative\n", + "conclusions from the energy.\n", + "\n", + "From the repository root, prepare the example environment once with\n", + "`julia --project=examples -e 'using Pkg; Pkg.develop(path=\".\"); Pkg.instantiate()'`,\n", + "then run `julia --project=examples --threads=2 examples/triangular_ctmrg/main.jl`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "using Random\n", + "using TensorKit, PEPSKit\n", + "using TensorKitTensors.SpinOperators\n", + "Random.seed!(123456789);" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Unit cell and nearest neighbors\n", + "\n", + "PEPSKit indexes the unit cell as a Julia matrix: `[r, c]` means row `r`, column `c`,\n", + "starting at 1. For `[A B C; D E F; G H I]`, `[2, 1]` is `D` and `[1, 2]` is `B`.\n", + "The six virtual legs are ordered `NW, NE, E, SE, SW, W`, with offsets\n", + "`(-1, 0), (-1, 1), (0, 1), (1, 0), (1, -1), (0, -1)` respectively.\n", + "\n", + "To count every nearest-neighbor bond once, we use the following three offsets:\n", + "\n", + "| Bond family | Offset `(Δr, Δc)` | Virtual leg | Relative rotation |\n", + "|:------------|:------------------|:------------|:------------------|\n", + "| 0° | `(0, 1)` | E | `2π/3` |\n", + "| 60° | `(1, -1)` | SW | `-4π/3` |\n", + "| 120° | `(1, 0)` | SE | `-2π/3` |\n", + "\n", + "The bond-family angles are unoriented; the relative rotations refer to the directed\n", + "offsets in the table. With `R(θ) = exp(-im * θ * S_y)`, the physical tensor at\n", + "`[r, c]` is `R((r - 1)q[1] + (c - 1)q[2]) * A`, where `q = (-2π/3, 2π/3)`.\n", + "This gives `[A B C; C A B; B C A]`, with `B = R(2π/3) * A` and\n", + "`C = R(4π/3) * A`, up to irrelevant spin-1/2 tensor phases. It is the PEPSKit\n", + "version of the variPEPS pattern `[A B C; B C A; C A B]` with `q = (2π/3, 2π/3)`.\n", + "\n", + "We optimize `A` in a single-site unit cell. All three sublattices share its\n", + "variational parameters, and the wavevector stays fixed during optimization." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "D = 2\n", + "χ = 32\n", + "P, V = ℂ^2, ℂ^D\n", + "peps₀ = InfinitePEPSTriangular(randn, Float64, P, V; unitcell = (1, 1));" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Hamiltonian and CTMRG\n", + "\n", + "The Heisenberg interaction is invariant under a common rotation of both spins, so\n", + "only their relative rotation enters each bond. We absorb it into the gate on the\n", + "second site: `hδ = (id(P) ⊗ R(q⋅δ))' * h * (id(P) ⊗ R(q⋅δ))`. The diagonal\n", + "angle `-4π/3` gives the same gate as `2π/3`. This lets the existing\n", + "`LocalOperator` and `expectation_value` interfaces evaluate the physical\n", + "three-sublattice energy using one tensor and three bond gates.\n", + "\n", + "We use antiferromagnetic coupling `J = 1`. Keep the neighbor coordinates unwrapped,\n", + "including those outside the unit cell: the contractions apply periodicity while\n", + "preserving which bond is intended. Triangular expectation values support onsite and nearest-neighbor\n", + "terms in all three bond families." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "h = S_x_S_x() + S_y_S_y() + S_z_S_z()\n", + "offsets = (CartesianIndex(0, 1), CartesianIndex(1, -1), CartesianIndex(1, 0))\n", + "q = (-2π / 3, 2π / 3)\n", + "rotation(θ) = TensorMap([cos(θ / 2) -sin(θ / 2); sin(θ / 2) cos(θ / 2)], P, P)\n", + "gates = map(offsets) do δ\n", + " U = id(P) ⊗ rotation(q[1] * δ[1] + q[2] * δ[2])\n", + " return U' * h * U\n", + "end\n", + "H = LocalOperator(\n", + " physicalspace(peps₀),\n", + " [(I, I + δ) => gate for I in CartesianIndices(size(peps₀)) for (δ, gate) in zip(offsets, gates)],\n", + ");" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The environment constructor takes the doubled virtual space `V ⊗ V'` of the PEPS\n", + "norm network and an environment space `ℂ^χ`. We initialize it with `ones`.\n", + "The PEPS tensor and the rotated Heisenberg gates are real, but the current gauge\n", + "fixing for fixed-point differentiation requires a complex environment.\n", + "\n", + "`leading_boundary` selects triangular CTMRG from the environment; here we\n", + "also name it explicitly, so the same settings can be passed to the optimizer.\n", + "We choose `projector_alg = :full` for the corner construction; the default\n", + "`:twothirds` uses two thirds of the environment. `conditioning = true` (the default) conditions the direct\n", + "Ta/Tb edge split. It does not toggle corner conditioning." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "env_initial = CTMRGEnvTriangular(ones, ComplexF64, V ⊗ V', ℂ^χ; unitcell = size(peps₀))\n", + "boundary_alg = (;\n", + " alg = :SimultaneousCTMRGTriangular, tol = 1.0e-7, maxiter = 100,\n", + " projector_alg = :full, trunctype = :FixedSpaceTruncation,\n", + ")\n", + "env₀, info_ctmrg = leading_boundary(env_initial, peps₀; boundary_alg...);\n", + "@show info_ctmrg.converged info_ctmrg.convergence_error info_ctmrg.contraction_metrics;" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Convergence compares the corner and edge singular-value spectra. The truncation\n", + "metrics describe the local SVD errors, not the error in the energy. Check CTMRG\n", + "convergence and increase `χ` when assessing the accuracy of an entangled calculation.\n", + "\n", + "`expectation_value` sums all terms in `H`, so this Hamiltonian gives the energy per\n", + "unit cell. Divide by the number of sites to obtain the energy per site:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "E₀ = real(expectation_value(peps₀, H, env₀))\n", + "@show E₀ / length(peps₀);" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Native optimization\n", + "\n", + "We use L-BFGS with `FixedPointGradient` and a GMRES linear solver to\n", + "differentiate the CTMRG fixed point. Triangular optimization currently requires\n", + "`precondition_alg = nothing`\n", + "and does not support symmetrization. Alternatively, `gradient_alg = nothing`\n", + "differentiates through the CTMRG iterations.\n", + "\n", + "PEPSKit adapts the CTMRG and gradient tolerances during optimization, using looser\n", + "solves when the gradient is large. Check the final gradient norm and increase the\n", + "optimization budget if necessary.\n", + "Convergence of CTMRG at a fixed `χ` does not establish convergence in `χ`; repeat\n", + "the calculation at larger environment dimensions to assess the energy." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "peps, env, E, info_opt = fixedpoint(\n", + " H, peps₀, env₀;\n", + " boundary_alg,\n", + " gradient_alg = (; alg = :FixedPointGradient, solver_alg = (; alg = :GMRES)),\n", + " precondition_alg = nothing,\n", + " optimizer_alg = (; alg = :LBFGS, tol = 1.0e-6, maxiter = 100), verbosity = 3,\n", + ");\n", + "@show E / length(peps) info_opt.gradnorms[end];" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The `D = 2` ansatz allows entanglement beyond the 120° product state, whose\n", + "energy is `-3/8` per site. The returned `E` is the energy per unit cell, which here\n", + "contains a single site. Increase both `D` and `χ` to study the quantum ground state." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "---\n", + "\n", + "*This notebook was generated using [Literate.jl](https://github.com/fredrikekre/Literate.jl).*" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Julia 1.11.6", + "language": "julia", + "name": "julia-1.11" + }, + "language_info": { + "file_extension": ".jl", + "mimetype": "application/julia", + "name": "julia", + "version": "1.11.6" + } + }, + "nbformat": 4, + "nbformat_minor": 3 +} diff --git a/examples/triangular_ctmrg/main.jl b/examples/triangular_ctmrg/main.jl new file mode 100644 index 000000000..d18b9269d --- /dev/null +++ b/examples/triangular_ctmrg/main.jl @@ -0,0 +1,146 @@ +using Markdown #hide +md""" +# [Triangular CTMRG and optimization](@id examples_triangular_ctmrg) + +This example contracts and optimizes an antiferromagnetic spin-1/2 Heisenberg PEPS +on the triangular lattice using [`InfinitePEPSTriangular`](@ref), +[`CTMRGEnvTriangular`](@ref), and the native [`fixedpoint`](@ref) interface. +A single-site tensor and fixed 120° sublattice rotations represent a three-sublattice +state. We start from a seeded random real tensor with `D = 2` and a complex +environment with `χ = 40`. Check convergence in `χ` before drawing quantitative +conclusions from the energy. + +From the repository root, prepare the example environment once with +`julia --project=examples -e 'using Pkg; Pkg.develop(path="."); Pkg.instantiate()'`, +then run `julia --project=examples --threads=2 examples/triangular_ctmrg/main.jl`. +""" + +using Random +using TensorKit, PEPSKit +using TensorKitTensors.SpinOperators +Random.seed!(123456789); + +md""" +## Unit cell and nearest neighbors + +PEPSKit indexes the unit cell as a Julia matrix: `[r, c]` means row `r`, column `c`, +starting at 1. For `[A B C; D E F; G H I]`, `[2, 1]` is `D` and `[1, 2]` is `B`. +The six virtual legs are ordered `NW, NE, E, SE, SW, W`, with offsets +`(-1, 0), (-1, 1), (0, 1), (1, 0), (1, -1), (0, -1)` respectively. + +To count every nearest-neighbor bond once, we use the following three offsets: + +| Bond family | Offset `(Δr, Δc)` | Virtual leg | Relative rotation | +|:------------|:------------------|:------------|:------------------| +| 0° | `(0, 1)` | E | `2π/3` | +| 60° | `(1, -1)` | SW | `-4π/3` | +| 120° | `(1, 0)` | SE | `-2π/3` | + +The bond-family angles are unoriented; the relative rotations refer to the directed +offsets in the table. With `R(θ) = exp(-im * θ * S_y)`, the physical tensor at +`[r, c]` is `R((r - 1)q[1] + (c - 1)q[2]) * A`, where `q = (-2π/3, 2π/3)`. +This gives `[A B C; C A B; B C A]`, with `B = R(2π/3) * A` and +`C = R(4π/3) * A`, up to irrelevant spin-1/2 tensor phases. It is the PEPSKit +version of the variPEPS pattern `[A B C; B C A; C A B]` with `q = (2π/3, 2π/3)`. + +We optimize `A` in a single-site unit cell. All three sublattices share its +variational parameters, and the wavevector stays fixed during optimization. +""" + +D = 2 +χ = 32 +P, V = ℂ^2, ℂ^D +peps₀ = InfinitePEPSTriangular(randn, Float64, P, V; unitcell = (1, 1)); + +md""" +## Hamiltonian and CTMRG + +The Heisenberg interaction is invariant under a common rotation of both spins, so +only their relative rotation enters each bond. We absorb it into the gate on the +second site: `hδ = (id(P) ⊗ R(q⋅δ))' * h * (id(P) ⊗ R(q⋅δ))`. The diagonal +angle `-4π/3` gives the same gate as `2π/3`. This lets the existing +[`LocalOperator`](@ref) and `expectation_value` interfaces evaluate the physical +three-sublattice energy using one tensor and three bond gates. + +We use antiferromagnetic coupling `J = 1`. Keep the neighbor coordinates unwrapped, +including those outside the unit cell: the contractions apply periodicity while +preserving which bond is intended. Triangular expectation values support onsite and nearest-neighbor +terms in all three bond families. +""" + +h = S_x_S_x() + S_y_S_y() + S_z_S_z() +offsets = (CartesianIndex(0, 1), CartesianIndex(1, -1), CartesianIndex(1, 0)) +q = (-2π / 3, 2π / 3) +rotation(θ) = TensorMap([cos(θ / 2) -sin(θ / 2); sin(θ / 2) cos(θ / 2)], P, P) +gates = map(offsets) do δ + U = id(P) ⊗ rotation(q[1] * δ[1] + q[2] * δ[2]) + return U' * h * U +end +H = LocalOperator( + physicalspace(peps₀), + [(I, I + δ) => gate for I in CartesianIndices(size(peps₀)) for (δ, gate) in zip(offsets, gates)], +); + +md""" +The environment constructor takes the doubled virtual space `V ⊗ V'` of the PEPS +norm network and an environment space `ℂ^χ`. We initialize it with `ones`. +The PEPS tensor and the rotated Heisenberg gates are real, but the current gauge +fixing for fixed-point differentiation requires a complex environment. + +[`leading_boundary`](@ref) selects triangular CTMRG from the environment; here we +also name it explicitly, so the same settings can be passed to the optimizer. +We choose `projector_alg = :full` for the corner construction; the default +`:twothirds` uses two thirds of the environment. `conditioning = true` (the default) conditions the direct +Ta/Tb edge split. It does not toggle corner conditioning. +""" + +env_initial = CTMRGEnvTriangular(ones, ComplexF64, V ⊗ V', ℂ^χ; unitcell = size(peps₀)) +boundary_alg = (; + alg = :SimultaneousCTMRGTriangular, tol = 1.0e-7, maxiter = 100, + projector_alg = :full, trunctype = :FixedSpaceTruncation, +) +env₀, info_ctmrg = leading_boundary(env_initial, peps₀; boundary_alg...); +@show info_ctmrg.converged info_ctmrg.convergence_error info_ctmrg.contraction_metrics; + +md""" +Convergence compares the corner and edge singular-value spectra. The truncation +metrics describe the local SVD errors, not the error in the energy. Check CTMRG +convergence and increase `χ` when assessing the accuracy of an entangled calculation. + +`expectation_value` sums all terms in `H`, so this Hamiltonian gives the energy per +unit cell. Divide by the number of sites to obtain the energy per site: +""" + +E₀ = real(expectation_value(peps₀, H, env₀)) +@show E₀ / length(peps₀); + +md""" +## Native optimization + +We use L-BFGS with [`FixedPointGradient`](@ref) and a GMRES linear solver to +differentiate the CTMRG fixed point. Triangular optimization currently requires +`precondition_alg = nothing` +and does not support symmetrization. Alternatively, `gradient_alg = nothing` +differentiates through the CTMRG iterations. + +PEPSKit adapts the CTMRG and gradient tolerances during optimization, using looser +solves when the gradient is large. Check the final gradient norm and increase the +optimization budget if necessary. +Convergence of CTMRG at a fixed `χ` does not establish convergence in `χ`; repeat +the calculation at larger environment dimensions to assess the energy. +""" + +peps, env, E, info_opt = fixedpoint( + H, peps₀, env₀; + boundary_alg, + gradient_alg = (; alg = :FixedPointGradient, solver_alg = (; alg = :GMRES)), + precondition_alg = nothing, + optimizer_alg = (; alg = :LBFGS, tol = 1.0e-6, maxiter = 100), verbosity = 3, +); +@show E / length(peps) info_opt.gradnorms[end]; + +md""" +The `D = 2` ansatz allows entanglement beyond the 120° product state, whose +energy is `-3/8` per site. The returned `E` is the energy per unit cell, which here +contains a single site. Increase both `D` and `χ` to study the quantum ground state. +""" From 2f7590ef971624996c2929b9edad1f738c3fa7bc Mon Sep 17 00:00:00 2001 From: Philipp Schmoll Date: Wed, 28 Oct 2026 08:38:12 +0100 Subject: [PATCH 31/41] Use SVDAdjoint for triangular PEPS --- src/algorithms/ctmrg_triangular/ctmrg.jl | 2 +- .../ctmrg_triangular/simultaneous.jl | 56 +++++++++++-------- src/utility/svd.jl | 43 ++++++++++++++ test/ctmrg/triangular.jl | 37 ++++++++++++ test/testsuite/utility/svd_wrapper.jl | 47 ++++++++++++++++ 5 files changed, 162 insertions(+), 23 deletions(-) diff --git a/src/algorithms/ctmrg_triangular/ctmrg.jl b/src/algorithms/ctmrg_triangular/ctmrg.jl index 6f78e4b24..235ef8834 100644 --- a/src/algorithms/ctmrg_triangular/ctmrg.jl +++ b/src/algorithms/ctmrg_triangular/ctmrg.jl @@ -10,7 +10,7 @@ end leading_boundary(env₀::CTMRGEnvTriangular, network, alg::CTMRGAlgorithmTriangular) Keyword arguments are forwarded to `SimultaneousCTMRGTriangular`: `tol`, `maxiter`, -`miniter`, `verbosity`, `conditioning`, `projector_alg`, and `trunctype`. +`miniter`, `verbosity`, `decomposition_alg`, `conditioning`, `projector_alg`, and `trunctype`. Return the contracted environment and convergence information. `info.converged` and `info.convergence_error` describe convergence; `info.convergence_metric` is retained as diff --git a/src/algorithms/ctmrg_triangular/simultaneous.jl b/src/algorithms/ctmrg_triangular/simultaneous.jl index 628de4220..0151c174a 100644 --- a/src/algorithms/ctmrg_triangular/simultaneous.jl +++ b/src/algorithms/ctmrg_triangular/simultaneous.jl @@ -12,6 +12,9 @@ updates of all six edge directions. See [`leading_boundary`](@ref) for convergen * `maxiter::Int=$(Defaults.ctmrg_maxiter)` : Maximum number of CTMRG iterations. * `miniter::Int=$(Defaults.ctmrg_miniter)` : Minimum number of CTMRG iterations. * `verbosity::Int=$(Defaults.ctmrg_verbosity)` : Output verbosity, as in [`leading_boundary`](@ref). +* `decomposition_alg::Union{SVDAdjoint,NamedTuple}=(;)` : SVD algorithm and reverse rule, + as in square CTMRG. A `NamedTuple` specifies the forward algorithm; use [`SVDAdjoint`](@ref) + to configure its pullback. Full corner and edge conditioning require a dense SVD. * `projector_alg::Symbol=:twothirds` : Corner-projector construction, either `:twothirds` or `:full`. * `conditioning::Bool=true` : Square-root SVD conditioning of the Ta/Tb edge split. This does not change the corner projectors; `:full` always splits its opposite @@ -19,7 +22,7 @@ updates of all six edge directions. See [`leading_boundary`](@ref) for convergen * `trunctype::Symbol=:truncrank` : Keep the initial environment dimension. Use `:FixedSpaceTruncation` to preserve each environment bond space, including its sectors. """ -struct SimultaneousCTMRGTriangular <: CTMRGAlgorithmTriangular +struct SimultaneousCTMRGTriangular{S <: SVDAdjoint} <: CTMRGAlgorithmTriangular tol::Float64 maxiter::Int miniter::Int @@ -27,6 +30,7 @@ struct SimultaneousCTMRGTriangular <: CTMRGAlgorithmTriangular conditioning::Bool projector_alg::Symbol trunctype::Symbol + decomposition_alg::S end function SimultaneousCTMRGTriangular(; tol = Defaults.ctmrg_tol, @@ -35,9 +39,13 @@ function SimultaneousCTMRGTriangular(; verbosity = Defaults.ctmrg_verbosity, conditioning = true, projector_alg = :twothirds, - trunctype = :truncrank + trunctype = :truncrank, + decomposition_alg = (;) ) - return SimultaneousCTMRGTriangular(tol, maxiter, miniter, verbosity, conditioning, projector_alg, trunctype) + if decomposition_alg isa NamedTuple + decomposition_alg = SVDAdjoint(; fwd_alg = decomposition_alg) + end + return SimultaneousCTMRGTriangular(tol, maxiter, miniter, verbosity, conditioning, projector_alg, trunctype, decomposition_alg) end # Based on @@ -50,13 +58,13 @@ function ctmrg_iteration(network::InfiniteTriangularNetwork, env::CTMRGEnvTriang else trunc = trunc_type(dim(domain(env.C[1, 1, 1])[1])) end - Pas, Pbs, S, corner_truncation_error = calculate_projectors(network, env, trunc, alg.projector_alg) + Pas, Pbs, S, corner_truncation_error = calculate_projectors(network, env, trunc, alg.projector_alg, alg.decomposition_alg) env = renormalize_corners!(network, env, Pas, Pbs) env = normalize_corners(env) - mats, env = semi_renormalize(network, env, Pas, Pbs, trunc) - env, edge_truncation_error = renormalize_edges(network, env, mats, trunc; alg.conditioning) + mats, env = semi_renormalize(network, env, Pas, Pbs, trunc, alg.decomposition_alg) + env, edge_truncation_error = renormalize_edges(network, env, mats, trunc, alg.decomposition_alg; alg.conditioning) truncation_error = max(corner_truncation_error, edge_truncation_error) info = (; contraction_metrics = (; truncation_error, corner_truncation_error, edge_truncation_error), S, @@ -64,11 +72,11 @@ function ctmrg_iteration(network::InfiniteTriangularNetwork, env::CTMRGEnvTriang return env, info end -function calculate_projectors(network, env, trunc, projector_alg) +function calculate_projectors(network, env, trunc, projector_alg, decomposition_alg = SVDAdjoint()) if projector_alg == :full - return calculate_full_projectors(network, env, trunc) + return calculate_full_projectors(network, env, trunc, decomposition_alg) elseif projector_alg == :twothirds - return calculate_twothirds_projectors(network, env, trunc) + return calculate_twothirds_projectors(network, env, trunc, decomposition_alg) else @error "projector_alg = $projector_alg not defined" end @@ -95,7 +103,7 @@ end _normalize_corner_environment(ρ) = ρ / norm(ρ) -function calculate_twothirds_projectors(network::InfiniteTriangularNetwork{P}, env, trunc) where {P} +function calculate_twothirds_projectors(network::InfiniteTriangularNetwork{P}, env, trunc, decomposition_alg = SVDAdjoint()) where {P} projectors = dtmap(eachcoordinate(network, 1:6)) do (dir, r, c) ρL = build_double_corner_matrix_triangular(network, env, _prev(dir, 6), r, c) ρR = build_double_corner_matrix_triangular(network, env, _next(dir, 6), r, c) @@ -104,7 +112,8 @@ function calculate_twothirds_projectors(network::InfiniteTriangularNetwork{P}, e ρρ = _normalize_corner_environment(ρL * ρR) trunc′ = _truncation_strategy(trunc, env.Ea[dir, r, c]) - U, S, V, truncation_error = svd_trunc(ρρ; trunc = trunc′) + svd_alg = _set_decomposition_truncation(decomposition_alg, trunc′) + U, S, V, truncation_error = svd_trunc(ρρ, svd_alg) iqsrtS = sdiag_pow(S, -0.5) truncation_error = _relative_truncation_error(S, iqsrtS, truncation_error) @@ -115,13 +124,13 @@ function calculate_twothirds_projectors(network::InfiniteTriangularNetwork{P}, e return getindex.(projectors, 1), getindex.(projectors, 2), getindex.(projectors, 3), maximum(x -> x[4], projectors) end -function calculate_full_projectors(network::InfiniteTriangularNetwork{P}, env, trunc) where {P} +function calculate_full_projectors(network::InfiniteTriangularNetwork{P}, env, trunc, decomposition_alg = SVDAdjoint()) where {P} projectors = dtmap(eachcoordinate(network, 1:6)) do (dir, r, c) ρL = build_double_corner_matrix_triangular(network, env, mod1(dir - 1, 6), r, c) ρR = build_double_corner_matrix_triangular(network, env, mod1(dir + 1, 6), r, c) ρ̄ = build_double_corner_matrix_triangular(network, env, mod1(dir + 3, 6), r, c) ρ̄ = _normalize_corner_environment(ρ̄) - Ū, S̄, V̄ᴴ = svd_full(ρ̄) + Ū, S̄, V̄ᴴ = svd_full(ρ̄, decomposition_alg) sqrtS̄ = sdiag_pow(S̄, 0.5) ρ̄ᴿ = Ū * sqrtS̄ ρ̄ᴸ = sqrtS̄ * V̄ᴴ @@ -130,7 +139,8 @@ function calculate_full_projectors(network::InfiniteTriangularNetwork{P}, env, t ρρ = _normalize_corner_environment(ρL * ρR) trunc′ = _truncation_strategy(trunc, env.Ea[dir, r, c]) - U, S, Vᴴ, truncation_error = svd_trunc(ρρ; trunc = trunc′) + svd_alg = _set_decomposition_truncation(decomposition_alg, trunc′) + U, S, Vᴴ, truncation_error = svd_trunc(ρρ, svd_alg) isqrtS = sdiag_pow(S, -0.5) truncation_error = _relative_truncation_error(S, isqrtS, truncation_error) Pb = ρR * Vᴴ' * isqrtS @@ -167,11 +177,12 @@ function _permute_edge(t::Union{T1, T2}) where {E, S, T1 <: AbstractTensorMap{E, return permute(t, ((1, 3, 4), (2,))) end -function semi_renormalize(network::InfiniteTriangularNetwork, env::CTMRGEnvTriangular, Pas, Pbs, trunc) +function semi_renormalize(network::InfiniteTriangularNetwork, env::CTMRGEnvTriangular, Pas, Pbs, trunc, decomposition_alg = SVDAdjoint()) edges = dtmap(eachcoordinate(network, 1:6)) do (dir, r, c) mat = semi_renormalize_edge(network, env, Pas, Pbs, dir, r, c) trunc′ = _truncation_strategy(trunc, env.Ea[dir, r, c]) - U, S, V = svd_trunc(mat; trunc = trunc′) + svd_alg = _set_decomposition_truncation(decomposition_alg, trunc′) + U, S, V = svd_trunc(mat, svd_alg) sqrtS = sdiag_pow(S, 0.5) Ẽb = U * sqrtS Ẽa = _permute_edge(sqrtS * V) @@ -180,12 +191,12 @@ function semi_renormalize(network::InfiniteTriangularNetwork, env::CTMRGEnvTrian return getindex.(edges, 1), CTMRGEnvTriangular(env.C, getindex.(edges, 2), getindex.(edges, 3)) end -function _truncate_edge(mat, ρL, ρR, trunc; conditioning = true) +function _truncate_edge(mat, ρL, ρR, trunc, decomposition_alg = SVDAdjoint(); conditioning = true) ρL /= norm(ρL) ρR /= norm(ρR) if conditioning - _, SL, VLᴴ = svd_full(ρL) - UR, SR, _ = svd_full(ρR) + _, SL, VLᴴ = svd_full(ρL, decomposition_alg) + UR, SR, _ = svd_full(ρR, decomposition_alg) sqrtSL = sdiag_pow(SL, 0.5) sqrtSR = sdiag_pow(SR, 0.5) ρL = sqrtSL * VLᴴ @@ -195,7 +206,8 @@ function _truncate_edge(mat, ρL, ρR, trunc; conditioning = true) ρ = ρL * mat * ρR ρ /= norm(ρ) - U, S, Vᴴ, truncation_error = svd_trunc(ρ; trunc) + svd_alg = _set_decomposition_truncation(decomposition_alg, trunc) + U, S, Vᴴ, truncation_error = svd_trunc(ρ, svd_alg) isqrtS = sdiag_pow(S, -0.5) truncation_error = _relative_truncation_error(S, isqrtS, truncation_error) Ea = _permute_edge(isqrtS * U' * ρL * mat) @@ -203,11 +215,11 @@ function _truncate_edge(mat, ρL, ρR, trunc; conditioning = true) return Ea, Eb, truncation_error end -function renormalize_edges(network::InfiniteTriangularNetwork, env::CTMRGEnvTriangular, mats, trunc; conditioning = true) +function renormalize_edges(network::InfiniteTriangularNetwork, env::CTMRGEnvTriangular, mats, trunc, decomposition_alg = SVDAdjoint(); conditioning = true) edges = dtmap(eachcoordinate(network, 1:6)) do (dir, r, c) ρL, ρR = build_halfinfinite_environments(network, env, dir, r, c) trunc′ = _truncation_strategy(trunc, env.Ea[dir, r, c]) - return _truncate_edge(mats[dir, r, c], ρL, ρR, trunc′; conditioning) + return _truncate_edge(mats[dir, r, c], ρL, ρR, trunc′, decomposition_alg; conditioning) end env′ = normalize_edges(CTMRGEnvTriangular(env.C, getindex.(edges, 1), getindex.(edges, 2))) return env′, maximum(x -> x[3], edges) diff --git a/src/utility/svd.jl b/src/utility/svd.jl index 8a39ebf1d..8d42d9d34 100644 --- a/src/utility/svd.jl +++ b/src/utility/svd.jl @@ -51,6 +51,10 @@ struct SVDAdjoint{F, R} rrule_alg::R end +function _set_decomposition_truncation(alg::SVDAdjoint, trunc::TruncationStrategy) + return @set alg.fwd_alg = TruncatedAlgorithm(alg.fwd_alg, trunc) +end + const SVD_FWD_SYMBOLS = IdDict{Symbol, Any}( :DefaultAlgorithm => DefaultAlgorithm, :DivideAndConquer => DivideAndConquer, @@ -116,6 +120,25 @@ function SVDAdjoint(; fwd_alg = (;), rrule_alg = (;)) return SVDAdjoint(fwd_algorithm, rrule_algorithm) end +""" + svd_full(t, alg::SVDAdjoint) + svd_full!(t, alg::SVDAdjoint) + +Full SVD with a dense forward algorithm and the gauge verbosity and degeneracy tolerance +of `alg.rrule_alg`. Both `FullPullback` and `TruncPullback` use the full pullback here, +since no singular values are discarded. +""" +MatrixAlgebraKit.svd_full(t, alg::SVDAdjoint) = svd_full!(copy(t), alg) +function MatrixAlgebraKit.svd_full!(t, alg::SVDAdjoint) + alg.fwd_alg isa MatrixAlgebraKit.Algorithm && alg.rrule_alg isa Union{FullPullback, TruncPullback} || + throw(ArgumentError("full SVD requires a dense forward algorithm and FullPullback or TruncPullback")) + return svd_full!(t, alg.fwd_alg) +end +function MatrixAlgebraKit.svd_full!(t::AdjointTensorMap, alg::SVDAdjoint) + u, s, vt = svd_full!(adjoint(t), alg) + return adjoint(vt), adjoint(s), adjoint(u) +end + """ svd_trunc(t, alg::SVDAdjoint) svd_trunc!(t, alg::SVDAdjoint) @@ -285,6 +308,26 @@ end ## Reverse-rule algorithms # +# svd_full! rrule wrapping MatrixAlgebraKit's svd_pullback! +function ChainRulesCore.rrule( + ::typeof(svd_full!), t::AbstractTensorMap, alg::SVDAdjoint{F, R} + ) where {F <: MatrixAlgebraKit.Algorithm, R <: Union{FullPullback, TruncPullback}} + USV = svd_full!(t, alg.fwd_alg) + gtol = _get_pullback_gauge_tol(alg.rrule_alg.verbosity) + function svd_full!_pullback(ΔUSV′) + ΔUSV = unthunk.(ΔUSV′) + Δt = svd_pullback!( + zeros(storagetype(t), space(t)), t, USV, ΔUSV; + gauge_atol = gtol(ΔUSV), degeneracy_atol = alg.rrule_alg.degeneracy_atol, + ) + return NoTangent(), Δt, NoTangent() + end + function svd_full!_pullback(::Tuple{ZeroTangent, ZeroTangent, ZeroTangent}) + return NoTangent(), ZeroTangent(), NoTangent() + end + return USV, svd_full!_pullback +end + # svd_trunc! rrule wrapping MatrixAlgebraKit's svd_pullback! # https://github.com/QuantumKitHub/MatrixAlgebraKit.jl/blob/b76c7bb60014ecfead6925d0df6cb4b8d7c2668a/src/pullbacks/svd.jl#L33 function ChainRulesCore.rrule( diff --git a/test/ctmrg/triangular.jl b/test/ctmrg/triangular.jl index 51d4ca4d7..fa06e793f 100644 --- a/test/ctmrg/triangular.jl +++ b/test/ctmrg/triangular.jl @@ -7,6 +7,7 @@ using OptimKit using Zygote using LinearAlgebra using KrylovKit +using Logging using PEPSKit: unitcell, norm_preserving_retract @@ -72,6 +73,29 @@ end end end +@testset "CTM_triangular - SVD pullback settings" begin + Random.seed!(21735) + V = ℂ^2 ⊗ ℂ^2 + for T in (Float64, ComplexF64), pullback in (:FullPullback, :TruncPullback) + tensors = ntuple(_ -> randn(T, V, V) + 5 * id(V), 3) + directions = map(t -> normalize(randn(T, space(t))), tensors) + target = randn(T, V, V) + decomposition_alg = SVDAdjoint(; fwd_alg = (; alg = :QRIteration), rrule_alg = (; alg = pullback, verbosity = 0)) + loss = function (mat, L, R) + Ea, Eb, = PEPSKit._truncate_edge(mat, L, R, truncrank(2), decomposition_alg) + return real(dot(target, Eb * permute(Ea, ((1,), (3, 2))))) + end + result = @test_logs min_level = Logging.Warn withgradient(loss, tensors...) + ε = 1.0e-5 + for i in 1:3 + plus = Base.setindex(tensors, tensors[i] + ε * directions[i], i) + minus = Base.setindex(tensors, tensors[i] - ε * directions[i], i) + @test real(dot(result.grad[i], directions[i])) ≈ (loss(plus...) - loss(minus...)) / (2ε) rtol = 1.0e-5 atol = 1.0e-7 + end + @test decomposition_alg.fwd_alg isa PEPSKit.MatrixAlgebraKit.QRIteration + end +end + @testset "CTM_triangular - Direct edge coordinates" begin Random.seed!(68191) sz = (3, 4) @@ -397,6 +421,19 @@ end @test info′ == info end @test_throws ArgumentError leading_boundary(env₀, state; alg = :unknown) + + decomposition_alg = SVDAdjoint(; fwd_alg = (; alg = :QRIteration), rrule_alg = (; verbosity = 1)) + configured = PEPSKit.select_algorithm(leading_boundary, env₀; kwargs..., decomposition_alg) + @test configured.decomposition_alg === decomposition_alg + @test SimultaneousCTMRGTriangular(; decomposition_alg = (; alg = :QRIteration)).decomposition_alg.fwd_alg isa PEPSKit.MatrixAlgebraKit.QRIteration + @test PEPSKit.PEPSOptimize(; boundary_alg = (; alg = :SimultaneousCTMRGTriangular, decomposition_alg), precondition_alg = nothing).boundary_alg.decomposition_alg === decomposition_alg + env′, info′ = leading_boundary(env₀, state, configured) + @test all(isapprox.(env′.C, env.C)) + @test all(isapprox.(env′.Ea, env.Ea)) + @test all(isapprox.(env′.Eb, env.Eb)) + @test info′.converged == info.converged + @test SimultaneousCTMRGTriangular().projector_alg === :twothirds + @test PEPSKit._set_fixed_truncation(configured).decomposition_alg === decomposition_alg end @testset "Triangular fixedpoint interface" begin diff --git a/test/testsuite/utility/svd_wrapper.jl b/test/testsuite/utility/svd_wrapper.jl index 7259b347a..c3a773a29 100644 --- a/test/testsuite/utility/svd_wrapper.jl +++ b/test/testsuite/utility/svd_wrapper.jl @@ -6,10 +6,57 @@ using ChainRulesCore, Zygote using Accessors using PEPSKit using Adapt +using Logging using MatrixAlgebraKit: TruncatedAlgorithm, diagview, svd_trunc_no_error function utility_svd_wrapper(AT) return @testset "svd_wrapper ($AT)" begin + @testset "Full SVD" begin + Random.seed!(41279) + for T in (Float64, ComplexF64), (V, W) in ( + (ℂ^2, ℂ^3), (ℂ^3, ℂ^2), + (Z2Space(0 => 2, 1 => 1), Z2Space(0 => 1, 1 => 2)), + ), pullback in (:FullPullback, :TruncPullback) + A = adapt(AT, randn(T, V, W)) + R = adapt(AT, randn(T, V, W)) + direction = normalize(adapt(AT, randn(T, V, W))) + alg = SVDAdjoint(; rrule_alg = (; alg = pullback)) + loss = function (A) + U, S, V = svd_full(A, alg) + return real(dot(R, U * (S * S' * S) * V)) + end + original = copy(A) + U, S, V = svd_full(A, alg) + @test U * S * V ≈ A + @test space.((U, S, V)) == space.(svd_full(A)) + @test A == original + @test prod(svd_full(A', alg)) ≈ A' + g = only(gradient(loss, A)) + g′ = only( + gradient(A) do A + U, S, V = svd_full(A', alg) + real(dot(R', U * (S * S' * S) * V)) + end + ) + @test g′ ≈ g + ε = 1.0e-5 + @test real(dot(g, direction)) ≈ (loss(A + ε * direction) - loss(A - ε * direction)) / (2ε) rtol = 1.0e-6 atol = 1.0e-8 + end + + A = adapt(AT, randn(ComplexF64, ℂ^2, ℂ^3)) + @test_throws ArgumentError svd_full(A, SVDAdjoint(; fwd_alg = (; alg = :GKL))) + for verbosity in (0, 1) + alg = SVDAdjoint(; rrule_alg = (; verbosity)) + (U, S, V), back = rrule(svd_full!, copy(A), alg) + if verbosity == 0 + @test_logs min_level = Logging.Warn back((im * U, zero(S), zero(V))) + else + @test_logs (:warn, r"`svd` cotangents sensitive to gauge choice") back((im * U, zero(S), zero(V))) + end + @test back((ZeroTangent(), ZeroTangent(), ZeroTangent()))[2] isa ZeroTangent + end + end + # Gauge-invariant loss function function lossfun(svd_trunc_f, A, alg, R = randn(space(A)), trunc = notrunc()) alg = @set alg.fwd_alg = TruncatedAlgorithm(alg.fwd_alg, trunc) From 22e524c640049828a5f4c3d10f85c19ade1d4cab Mon Sep 17 00:00:00 2001 From: Philipp Schmoll Date: Wed, 28 Oct 2026 08:48:40 +0100 Subject: [PATCH 32/41] Fix triangular classical-network pullbacks and rotations --- src/networks/infinitetriangularnetwork.jl | 4 ++-- src/networks/tensors_triangular.jl | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/networks/infinitetriangularnetwork.jl b/src/networks/infinitetriangularnetwork.jl index 0711e8419..1b5a8c3ae 100644 --- a/src/networks/infinitetriangularnetwork.jl +++ b/src/networks/infinitetriangularnetwork.jl @@ -139,9 +139,9 @@ function ChainRulesCore.rrule( return O, getindex_pullback end -# specialized PFTensor implementation +# specialized PFTensorTriangular implementation function ChainRulesCore.rrule( - ::typeof(Base.getindex), network::InfiniteTriangularNetwork{<:PFTensor}, r::Int, c::Int + ::typeof(Base.getindex), network::InfiniteTriangularNetwork{<:PFTensorTriangular}, r::Int, c::Int ) O = network[r, c] diff --git a/src/networks/tensors_triangular.jl b/src/networks/tensors_triangular.jl index 00c60de23..637621d9a 100644 --- a/src/networks/tensors_triangular.jl +++ b/src/networks/tensors_triangular.jl @@ -42,6 +42,8 @@ function PartitionFunctionTensorTriangular( return f(T, Wspace ⊗ SWspace ⊗ SEspace ← NWspace ⊗ NEspace ⊗ Espace) end +Base.rot180(t::PFTensorTriangular) = permute(t, ((6, 5, 4), (3, 2, 1))) + function virtualspace(t::PFTensorTriangular, dir) invp = (4, 5, 6, 3, 2, 1) # internally, virtual directions are ordered as NW, NE, E, SE, SW... return space(t, invp[dir]) From d6b287ed559546e1079ca0c92aa198bc343e6564 Mon Sep 17 00:00:00 2001 From: Philipp Schmoll Date: Wed, 28 Oct 2026 08:54:07 +0100 Subject: [PATCH 33/41] Fix triangular coordinate overload --- src/algorithms/ctmrg_triangular/utility.jl | 2 +- test/ctmrg/triangular.jl | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/algorithms/ctmrg_triangular/utility.jl b/src/algorithms/ctmrg_triangular/utility.jl index faf1e7066..fc294266c 100644 --- a/src/algorithms/ctmrg_triangular/utility.jl +++ b/src/algorithms/ctmrg_triangular/utility.jl @@ -16,7 +16,7 @@ function _coordinates(dir::Int, rot::Int, r::Int, c::Int, unitcell::Tuple{Int, I end function _coordinates(dir::Int, r::Int, c::Int, unitcell::Tuple{Int, Int}) - return _coordinates(dir, 0, r, c; unitcell)[2:3] + return _coordinates(dir, 0, r, c, unitcell)[2:3] end function _truncway(trunctype::Symbol, D::E) where {E <: ElementarySpace} diff --git a/test/ctmrg/triangular.jl b/test/ctmrg/triangular.jl index fa06e793f..a50989f56 100644 --- a/test/ctmrg/triangular.jl +++ b/test/ctmrg/triangular.jl @@ -42,6 +42,13 @@ function classical_ising_triangular(β) return TensorMap(o2, ℂ^2 * ℂ^2 * ℂ^2, ℂ^2 * ℂ^2 * ℂ^2) end +@testset "CTM_triangular - Coordinates" begin + neighbors = ((3, 1), (3, 2), (1, 2), (2, 1), (2, 4), (1, 4)) + for dir in 1:6 + @test PEPSKit._coordinates(dir, 1, 1, (3, 4)) == neighbors[dir] + end +end + @testset "CTM_triangular - Direct edge split" begin Random.seed!(19461) vspace = ComplexSpace(2) From ec928fd95605ce341419fdbbac7f89cbb3921b7d Mon Sep 17 00:00:00 2001 From: Philipp Schmoll Date: Wed, 28 Oct 2026 08:58:32 +0100 Subject: [PATCH 34/41] Fix phase cancellation in triangular network_value --- src/algorithms/toolbox_triangular.jl | 14 +++++++++-- test/ctmrg/triangular.jl | 37 ++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 2 deletions(-) diff --git a/src/algorithms/toolbox_triangular.jl b/src/algorithms/toolbox_triangular.jl index 283bf028b..f767fb0c5 100644 --- a/src/algorithms/toolbox_triangular.jl +++ b/src/algorithms/toolbox_triangular.jl @@ -1,12 +1,22 @@ +""" + network_value(network::InfiniteTriangularNetwork, env::CTMRGEnvTriangular) + +Return the value per unit cell using the principal cube root of the contraction-ratio +product. Boundary phases cancel before taking the cube root. For general complex +networks, this estimator determines the phase only modulo `2π/3`. +""" function network_value(network::InfiniteTriangularNetwork, env::CTMRGEnvTriangular) - return prod(Iterators.product(axes(network)...)) do (r, c) + value = prod(Iterators.product(axes(network)...)) do (r, c) nw_corners = complex(_contract_corners((r, c), network, env)) nw_full = complex(_contract_site_large((r, c), network, env)) nw_0 = complex(_contract_edges_0((r, c), network, env)) nw_60 = complex(_contract_edges_60((r, c), network, env)) nw_120 = complex(_contract_edges_120((r, c), network, env)) - return (nw_full * nw_corners^2 / (nw_0 * nw_60 * nw_120))^(1 / 3) + ratio = nw_full * nw_corners^2 / (nw_0 * nw_60 * nw_120) + # Keep the full phase until the unit-cell product, without cubing its magnitude. + return cbrt(abs(ratio)) * sign(ratio) end + return abs(value) * cis(angle(value) / 3) end function local_expectation_value( diff --git a/test/ctmrg/triangular.jl b/test/ctmrg/triangular.jl index a50989f56..249721e32 100644 --- a/test/ctmrg/triangular.jl +++ b/test/ctmrg/triangular.jl @@ -49,6 +49,43 @@ end end end +@testset "CTM_triangular - Network value phases" begin + V = ComplexSpace(1) + for sandwich in (false, true) + network = if sandwich + A = normalize(ones(Float64, ComplexSpace(2), V^3 ⊗ (V')^3)) + InfiniteTriangularNetwork(InfinitePEPSTriangular(A; unitcell = (2, 2))) + else + InfiniteTriangularNetwork(fill(ones(Float64, V^3, V^3), 2, 2)) + end + env = CTMRGEnvTriangular(ones, ComplexF64, sandwich ? V ⊗ V' : V, V; unitcell = size(network)) + @test network_value(network, env) ≈ 1 + # Arbitrary boundary phases must cancel across the unit cell. + for field in (:C, :Ea, :Eb), phase in (-1, cis(2π / 3)) + env′ = deepcopy(env) + getproperty(env′, field)[3, 1, 1] *= phase + @test network_value(network, env′) ≈ 1 + end + end + + # Two negative scalar tensors give a positive value per unit cell. + network = InfiniteTriangularNetwork(fill(-ones(Float64, V^3, V^3), 1, 2)) + env = CTMRGEnvTriangular(ones, ComplexF64, V, V; unitcell = size(network)) + @test network_value(network, env) ≈ 1 + + for α in (1.1, 1.1 + 0.1im) + g = only(Zygote.gradient(n -> real(network_value(n, env)), α * network)) + @test all(t -> t ≈ -conj(α) * ones(Float64, V^3, V^3), unitcell(g)) + end + + # Taking the cube root must not require cubing the full unit-cell magnitude. + for scale in (1.0e-30, 1.0e30) + network = InfiniteTriangularNetwork(fill(scale * ones(Float64, V^3, V^3), 2, 2)) + env = CTMRGEnvTriangular(ones, ComplexF64, V, V; unitcell = size(network)) + @test network_value(network, env) ≈ scale^length(network) + end +end + @testset "CTM_triangular - Direct edge split" begin Random.seed!(19461) vspace = ComplexSpace(2) From 36d37039573b6cfc5912622ad3e81fc0c851265f Mon Sep 17 00:00:00 2001 From: Philipp Schmoll Date: Wed, 28 Oct 2026 09:07:07 +0100 Subject: [PATCH 35/41] Test triangular nearest-neighbor fixed-point optimization --- test/ctmrg/triangular.jl | 46 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/test/ctmrg/triangular.jl b/test/ctmrg/triangular.jl index 249721e32..f24350f03 100644 --- a/test/ctmrg/triangular.jl +++ b/test/ctmrg/triangular.jl @@ -547,6 +547,52 @@ end end end +@testset "Triangular AF Heisenberg optimization" begin + Random.seed!(48631) + V = ComplexSpace(2) + state = InfinitePEPSTriangular(randn, ComplexF64, V, V) + Sx = TensorMap(ComplexF64[0 1; 1 0] / 2, V, V) + Sy = TensorMap(ComplexF64[0 -im; im 0] / 2, V, V) + Sz = TensorMap(ComplexF64[1 0; 0 -1] / 2, V, V) + # Ferromagnetic nearest-neighbor Heisenberg energy per site. + h = -(Sx ⊗ Sx + Sy ⊗ Sy + Sz ⊗ Sz) + offsets = (CartesianIndex(0, 1), CartesianIndex(1, -1), CartesianIndex(1, 0)) + H = LocalOperator(physicalspace(state), [(CartesianIndex(1, 1), CartesianIndex(1, 1) + δ) => h for δ in offsets]) + env₀ = CTMRGEnvTriangular(ones, ComplexF64, V ⊗ V', ComplexSpace(4)) + boundary_alg = SimultaneousCTMRGTriangular(; projector_alg = :full, tol = 1.0e-10, maxiter = 100, miniter = 4, verbosity = 0) + gradient_alg = PEPSKit.FixedPointGradient(; solver_alg = (; alg = :GMRES, krylovdim = 30), tol = 1.0e-8, maxiter = 10, verbosity = 0) + env₀, info₀ = leading_boundary(env₀, state, boundary_alg) + @test info₀.converged + # A mixed one-site density matrix rules out a padded product state. + m = [real(expectation_value(state, LocalOperator(physicalspace(state), ((1, 1),) => S), env₀)) for S in (Sx, Sy, Sz)] + @test 0.5 ≤ 0.5 + 2sum(abs2, m) < 1 - 1.0e-4 + E₀ = cost_function(state, env₀, H) + ψ, env, E, info = fixedpoint( + H, state, env₀; boundary_alg, gradient_alg, precondition_alg = nothing, + optimizer_alg = (; maxiter = 1, tol = 0), verbosity = 0, + ) + @test E < E₀ + @test info.costs[end] ≈ E + @test length(info.contraction_metrics) == 1 + @test all(isfinite, info.gradnorms) + @test info.last_gradient isa typeof(ψ) + @test isfinite(norm(info.last_gradient)) && norm(info.last_gradient) > 0 + @test all(isapprox.(norm.(unitcell(ψ)), 1)) + env′, info′ = leading_boundary(env, ψ, boundary_alg) + @test info′.converged + @test E ≈ cost_function(ψ, env′, H) atol = 1.0e-8 + # Check the returned nearest-neighbor gradient along one normalized direction. + direction = info.last_gradient / norm(info.last_gradient) + ε = 1.0e-4 + energies = map((-ε, ε)) do step + ψ′ = ψ + step * direction + env′, info′ = leading_boundary(env, ψ′, boundary_alg) + @test info′.converged + cost_function(ψ′, env′, H) + end + @test real(dot(info.last_gradient, direction)) ≈ (energies[2] - energies[1]) / (2ε) rtol = 1.0e-4 atol = 1.0e-6 +end + @testset "Triangular gauge fixing" begin Random.seed!(63412) for (V, χ, sandwich) in ( From 229ef632aa35dea75aae262a38f0ef2f314d6077 Mon Sep 17 00:00:00 2001 From: Philipp Schmoll Date: Wed, 28 Oct 2026 09:14:57 +0100 Subject: [PATCH 36/41] Document triangular CTMRG support and clean up docstrings --- docs/src/changelog.md | 1 + src/networks/infinitetriangularnetwork.jl | 2 +- src/networks/tensors_triangular.jl | 24 +++++++++++------------ 3 files changed, 13 insertions(+), 14 deletions(-) diff --git a/docs/src/changelog.md b/docs/src/changelog.md index 7196cfa2a..5433cdec7 100644 --- a/docs/src/changelog.md +++ b/docs/src/changelog.md @@ -22,6 +22,7 @@ When releasing a new version, move the "Unreleased" changes to a new version sec ### Added +- Triangular CTMRG for PEPS and classical partition functions, with onsite and nearest-neighbor PEPS observables and native optimization using `FixedPointGradient` - `TensorKit.storagetype` for `InfinitePEPS`, `InfinitePEPO`, `InfinitePartitionFunction`, `InfiniteSquareNetwork` and `CTMRGEnv` (#429) ### Changed diff --git a/src/networks/infinitetriangularnetwork.jl b/src/networks/infinitetriangularnetwork.jl index 1b5a8c3ae..ac3071c9f 100644 --- a/src/networks/infinitetriangularnetwork.jl +++ b/src/networks/infinitetriangularnetwork.jl @@ -3,7 +3,7 @@ const PEPSTensorTriangular{S <: ElementarySpace} = AbstractTensorMap{<:Any, S, 1 """ $(TYPEDEF) -Contractible square network. Wraps a matrix of 'rank-6-tensor-like' objects. +Contractible triangular network. Wraps a matrix of 'rank-6-tensor-like' objects. ## Fields diff --git a/src/networks/tensors_triangular.jl b/src/networks/tensors_triangular.jl index 637621d9a..9366feb7c 100644 --- a/src/networks/tensors_triangular.jl +++ b/src/networks/tensors_triangular.jl @@ -19,7 +19,7 @@ where the angles denote the directions of the legs with respect to the positive ╱ ╲ ╱ ╲ ╱ ╲ - SW P SE + SW SE ``` """ const PartitionFunctionTensorTriangular{S <: ElementarySpace} = AbstractTensorMap{<:Any, S, 3, 3} @@ -27,11 +27,11 @@ const PFTensorTriangular = PartitionFunctionTensorTriangular """ PartitionFunctionTensorTriangular(f, ::Type{T}, NWspace::S, - [NEspace::S], [Espace::S], [SEspace::S], - [SWspace::S], [Wspace::S]) where {T,S<:Union{Int,ElementarySpace}} - -Construct a PartitionFunctionTensorTriangular tensor based on the -north-west, north-east, east, south-east, south-west and west spaces + [NEspace::S], [Espace::S], [SEspace::S], + [SWspace::S], [Wspace::S]) where {T,S<:ElementarySpace} + +Construct a `PartitionFunctionTensorTriangular` tensor based on the +north-west, north-east, east, south-east, south-west and west spaces. The tensor elements are generated based on `f` and the element type is specified in `T`. """ function PartitionFunctionTensorTriangular( @@ -58,11 +58,10 @@ end Default type for PEPS tensors with a single physical index, and 6 virtual indices, conventionally ordered as: ``T : P ← NW ⊗ NE ⊗ E ⊗ SE ⊗ SW ⊗ W``. Here, ``P`` denotes the physical space -and ``NW``, ``NE``, ``E``, ``SE``, ``SW`` and ``W`` denote the north-west (120°), north-east (60°), +and ``NW``, ``NE``, ``E``, ``SE``, ``SW`` and ``W`` denote the north-west (120°), north-east (60°), east (0°), south-east (300°), south-west (240°) and west (180°) spaces, respectively, where the angles denote the directions of the legs with respect to the positive horizontal axis. -``` ``` NW NE ╲ ╱ @@ -74,15 +73,14 @@ where the angles denote the directions of the legs with respect to the positive ╱ | ╲ SW P SE ``` -``` """ const PEPSTensorTriangular{S <: ElementarySpace} = AbstractTensorMap{<:Any, S, 1, 6} """ - PEPSTensor(f, ::Type{T}, Pspace::S, NWspace::S, - [NEspace::S], [Espace::S], [SEspace::S], - [SWspace::S], [Wspace::S]) where {T,S<:Union{Int,ElementarySpace}} - + PEPSTensorTriangular(f, ::Type{T}, Pspace::S, NWspace::S, + [NEspace::S], [Espace::S], [SEspace::S], + [SWspace::S], [Wspace::S]) where {T,S<:ElementarySpace} + Construct a PEPS tensor based on the physical, north-west, north-east, east, south-east, south-west and west spaces. The tensor elements are generated based on `f` and the element type is specified in `T`. """ From 55ecb51256a9d8ef1b7a07b3a7aed1825bcaa739 Mon Sep 17 00:00:00 2001 From: Philipp Schmoll Date: Wed, 28 Oct 2026 09:21:52 +0100 Subject: [PATCH 37/41] Set default projector and update example --- docs/src/examples/triangular_ctmrg/index.md | 6 +++--- docs/src/examples/triangular_ctmrg/main.ipynb | 6 +++--- examples/triangular_ctmrg/main.jl | 6 +++--- src/algorithms/ctmrg_triangular/simultaneous.jl | 4 ++-- test/ctmrg/triangular.jl | 5 +++-- 5 files changed, 14 insertions(+), 13 deletions(-) diff --git a/docs/src/examples/triangular_ctmrg/index.md b/docs/src/examples/triangular_ctmrg/index.md index 01da33391..e05f36ff9 100644 --- a/docs/src/examples/triangular_ctmrg/index.md +++ b/docs/src/examples/triangular_ctmrg/index.md @@ -17,7 +17,7 @@ on the triangular lattice using [`InfinitePEPSTriangular`](@ref), [`CTMRGEnvTriangular`](@ref), and the native [`fixedpoint`](@ref) interface. A single-site tensor and fixed 120° sublattice rotations represent a three-sublattice state. We start from a seeded random real tensor with `D = 2` and a complex -environment with `χ = 40`. Check convergence in `χ` before drawing quantitative +environment with `χ = 32`. Check convergence in `χ` before drawing quantitative conclusions from the energy. From the repository root, prepare the example environment once with @@ -102,8 +102,8 @@ fixing for fixed-point differentiation requires a complex environment. [`leading_boundary`](@ref) selects triangular CTMRG from the environment; here we also name it explicitly, so the same settings can be passed to the optimizer. -We choose `projector_alg = :full` for the corner construction; the default -`:twothirds` uses two thirds of the environment. `conditioning = true` (the default) conditions the direct +We use the default `projector_alg = :full` for the corner construction. +The alternative `:twothirds` uses two thirds of the environment. `conditioning = true` (the default) conditions the direct Ta/Tb edge split. It does not toggle corner conditioning. ````julia diff --git a/docs/src/examples/triangular_ctmrg/main.ipynb b/docs/src/examples/triangular_ctmrg/main.ipynb index 87f120ee8..6d2c3df97 100644 --- a/docs/src/examples/triangular_ctmrg/main.ipynb +++ b/docs/src/examples/triangular_ctmrg/main.ipynb @@ -20,7 +20,7 @@ "`CTMRGEnvTriangular`, and the native `fixedpoint` interface.\n", "A single-site tensor and fixed 120° sublattice rotations represent a three-sublattice\n", "state. We start from a seeded random real tensor with `D = 2` and a complex\n", - "environment with `χ = 40`. Check convergence in `χ` before drawing quantitative\n", + "environment with `χ = 32`. Check convergence in `χ` before drawing quantitative\n", "conclusions from the energy.\n", "\n", "From the repository root, prepare the example environment once with\n", @@ -132,8 +132,8 @@ "\n", "`leading_boundary` selects triangular CTMRG from the environment; here we\n", "also name it explicitly, so the same settings can be passed to the optimizer.\n", - "We choose `projector_alg = :full` for the corner construction; the default\n", - "`:twothirds` uses two thirds of the environment. `conditioning = true` (the default) conditions the direct\n", + "We use the default `projector_alg = :full` for the corner construction.\n", + "The alternative `:twothirds` uses two thirds of the environment. `conditioning = true` (the default) conditions the direct\n", "Ta/Tb edge split. It does not toggle corner conditioning." ] }, diff --git a/examples/triangular_ctmrg/main.jl b/examples/triangular_ctmrg/main.jl index d18b9269d..6f416b570 100644 --- a/examples/triangular_ctmrg/main.jl +++ b/examples/triangular_ctmrg/main.jl @@ -7,7 +7,7 @@ on the triangular lattice using [`InfinitePEPSTriangular`](@ref), [`CTMRGEnvTriangular`](@ref), and the native [`fixedpoint`](@ref) interface. A single-site tensor and fixed 120° sublattice rotations represent a three-sublattice state. We start from a seeded random real tensor with `D = 2` and a complex -environment with `χ = 40`. Check convergence in `χ` before drawing quantitative +environment with `χ = 32`. Check convergence in `χ` before drawing quantitative conclusions from the energy. From the repository root, prepare the example environment once with @@ -89,8 +89,8 @@ fixing for fixed-point differentiation requires a complex environment. [`leading_boundary`](@ref) selects triangular CTMRG from the environment; here we also name it explicitly, so the same settings can be passed to the optimizer. -We choose `projector_alg = :full` for the corner construction; the default -`:twothirds` uses two thirds of the environment. `conditioning = true` (the default) conditions the direct +We use the default `projector_alg = :full` for the corner construction. +The alternative `:twothirds` uses two thirds of the environment. `conditioning = true` (the default) conditions the direct Ta/Tb edge split. It does not toggle corner conditioning. """ diff --git a/src/algorithms/ctmrg_triangular/simultaneous.jl b/src/algorithms/ctmrg_triangular/simultaneous.jl index 0151c174a..dc9796857 100644 --- a/src/algorithms/ctmrg_triangular/simultaneous.jl +++ b/src/algorithms/ctmrg_triangular/simultaneous.jl @@ -15,7 +15,7 @@ updates of all six edge directions. See [`leading_boundary`](@ref) for convergen * `decomposition_alg::Union{SVDAdjoint,NamedTuple}=(;)` : SVD algorithm and reverse rule, as in square CTMRG. A `NamedTuple` specifies the forward algorithm; use [`SVDAdjoint`](@ref) to configure its pullback. Full corner and edge conditioning require a dense SVD. -* `projector_alg::Symbol=:twothirds` : Corner-projector construction, either `:twothirds` or `:full`. +* `projector_alg::Symbol=:full` : Corner-projector construction, either `:twothirds` or `:full`. * `conditioning::Bool=true` : Square-root SVD conditioning of the Ta/Tb edge split. This does not change the corner projectors; `:full` always splits its opposite corner factor with a square-root SVD, while `:twothirds` uses its factors directly. @@ -38,7 +38,7 @@ function SimultaneousCTMRGTriangular(; miniter = Defaults.ctmrg_miniter, verbosity = Defaults.ctmrg_verbosity, conditioning = true, - projector_alg = :twothirds, + projector_alg = :full, trunctype = :truncrank, decomposition_alg = (;) ) diff --git a/test/ctmrg/triangular.jl b/test/ctmrg/triangular.jl index f24350f03..7fd5e25ac 100644 --- a/test/ctmrg/triangular.jl +++ b/test/ctmrg/triangular.jl @@ -455,7 +455,7 @@ end V = ComplexSpace(1) state = InfinitePEPSTriangular(ones(ComplexF64, ComplexSpace(2), V^3 ⊗ (V')^3)) env₀ = CTMRGEnvTriangular(ones, ComplexF64, V ⊗ V', V) - kwargs = (; tol = 1.0e-9, maxiter = 2, miniter = 2, verbosity = 0, conditioning = false, projector_alg = :full, trunctype = :FixedSpaceTruncation) + kwargs = (; tol = 1.0e-9, maxiter = 2, miniter = 2, verbosity = 0, conditioning = false, trunctype = :FixedSpaceTruncation) alg = SimultaneousCTMRGTriangular(; kwargs...) @test PEPSKit.select_algorithm(leading_boundary, env₀; kwargs...) == alg env, info = leading_boundary(env₀, state, alg) @@ -476,7 +476,8 @@ end @test all(isapprox.(env′.Ea, env.Ea)) @test all(isapprox.(env′.Eb, env.Eb)) @test info′.converged == info.converged - @test SimultaneousCTMRGTriangular().projector_alg === :twothirds + @test SimultaneousCTMRGTriangular().projector_alg === :full + @test SimultaneousCTMRGTriangular(; projector_alg = :twothirds).projector_alg === :twothirds @test PEPSKit._set_fixed_truncation(configured).decomposition_alg === decomposition_alg end From 3362487e4de96f76d0042761890e2b976e1fb8a5 Mon Sep 17 00:00:00 2001 From: Philipp Schmoll Date: Wed, 4 Nov 2026 14:33:07 +0100 Subject: [PATCH 38/41] Fix Logging dependency in tests --- test/ctmrg/triangular.jl | 3 +-- test/testsuite/utility/svd_wrapper.jl | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/test/ctmrg/triangular.jl b/test/ctmrg/triangular.jl index 7fd5e25ac..1b4b66b20 100644 --- a/test/ctmrg/triangular.jl +++ b/test/ctmrg/triangular.jl @@ -7,7 +7,6 @@ using OptimKit using Zygote using LinearAlgebra using KrylovKit -using Logging using PEPSKit: unitcell, norm_preserving_retract @@ -129,7 +128,7 @@ end Ea, Eb, = PEPSKit._truncate_edge(mat, L, R, truncrank(2), decomposition_alg) return real(dot(target, Eb * permute(Ea, ((1,), (3, 2))))) end - result = @test_logs min_level = Logging.Warn withgradient(loss, tensors...) + result = @test_logs min_level = Base.CoreLogging.Warn withgradient(loss, tensors...) ε = 1.0e-5 for i in 1:3 plus = Base.setindex(tensors, tensors[i] + ε * directions[i], i) diff --git a/test/testsuite/utility/svd_wrapper.jl b/test/testsuite/utility/svd_wrapper.jl index c3a773a29..6c98e5a0a 100644 --- a/test/testsuite/utility/svd_wrapper.jl +++ b/test/testsuite/utility/svd_wrapper.jl @@ -6,7 +6,6 @@ using ChainRulesCore, Zygote using Accessors using PEPSKit using Adapt -using Logging using MatrixAlgebraKit: TruncatedAlgorithm, diagview, svd_trunc_no_error function utility_svd_wrapper(AT) @@ -49,7 +48,7 @@ function utility_svd_wrapper(AT) alg = SVDAdjoint(; rrule_alg = (; verbosity)) (U, S, V), back = rrule(svd_full!, copy(A), alg) if verbosity == 0 - @test_logs min_level = Logging.Warn back((im * U, zero(S), zero(V))) + @test_logs min_level = Base.CoreLogging.Warn back((im * U, zero(S), zero(V))) else @test_logs (:warn, r"`svd` cotangents sensitive to gauge choice") back((im * U, zero(S), zero(V))) end From e6a7591e2e9d5774557ba14807c121764cf4e5f3 Mon Sep 17 00:00:00 2001 From: Philipp Schmoll Date: Wed, 4 Nov 2026 15:52:47 +0100 Subject: [PATCH 39/41] Remove _notrunc_ind import --- src/PEPSKit.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PEPSKit.jl b/src/PEPSKit.jl index 584b5ee1c..4269acfdd 100644 --- a/src/PEPSKit.jl +++ b/src/PEPSKit.jl @@ -17,7 +17,7 @@ using MatrixAlgebraKit: svd_pullback!, svd_trunc_pullback! using TensorKit using TensorKit: AdjointTensorMap, SectorDict using TensorKit: throw_invalid_innerproduct, similarstoragetype -using TensorKit.Factorizations: TruncationSpace, _notrunc_ind +using TensorKit.Factorizations: TruncationSpace using KrylovKit using KrylovKit: Lanczos, BlockLanczos From 90c94e55799ba3793bdb7e5019239f378d45ab4d Mon Sep 17 00:00:00 2001 From: Philipp Schmoll Date: Thu, 5 Nov 2026 14:50:07 +0100 Subject: [PATCH 40/41] Use StableRNG in triangular optimization test --- test/ctmrg/triangular.jl | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/ctmrg/triangular.jl b/test/ctmrg/triangular.jl index 1b4b66b20..3fa413a6b 100644 --- a/test/ctmrg/triangular.jl +++ b/test/ctmrg/triangular.jl @@ -1,5 +1,6 @@ using Test using Random +using StableRNGs using TensorKit using MPSKit using PEPSKit @@ -548,9 +549,9 @@ end end @testset "Triangular AF Heisenberg optimization" begin - Random.seed!(48631) + rng = StableRNG(48631) V = ComplexSpace(2) - state = InfinitePEPSTriangular(randn, ComplexF64, V, V) + state = InfinitePEPSTriangular((args...) -> randn(rng, args...), ComplexF64, V, V) Sx = TensorMap(ComplexF64[0 1; 1 0] / 2, V, V) Sy = TensorMap(ComplexF64[0 -im; im 0] / 2, V, V) Sz = TensorMap(ComplexF64[1 0; 0 -1] / 2, V, V) From 89e390792c1e29f6dce08b6eec2106b4ea99b42d Mon Sep 17 00:00:00 2001 From: sanderdemeyer Date: Fri, 6 Nov 2026 15:28:54 +0100 Subject: [PATCH 41/41] small bug fixes --- .../contractions/triangular_ctmrg/double_corner.jl | 4 ++-- .../contractions/triangular_ctmrg/renormalize_corner.jl | 4 ++-- .../contractions/triangular_ctmrg/renormalize_edge.jl | 4 ++-- src/algorithms/ctmrg_triangular/simultaneous.jl | 9 ++++++--- 4 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/algorithms/contractions/triangular_ctmrg/double_corner.jl b/src/algorithms/contractions/triangular_ctmrg/double_corner.jl index 7f3e85b0d..649d4b5ab 100644 --- a/src/algorithms/contractions/triangular_ctmrg/double_corner.jl +++ b/src/algorithms/contractions/triangular_ctmrg/double_corner.jl @@ -5,7 +5,7 @@ function build_double_corner_matrix_triangular(network::InfiniteTriangularNetwor env.C[_coordinates(dir, 0, r, c, size(network))...][χNW D120; χN] * env.C[_coordinates(dir + 1, 0, r, c, size(network))...][χN D60; χNE] * env.Ea[_coordinates(dir - 1, 0, r, c, size(network))...][χW D180; χNW] * - env.Eb[_coordinates(dir + 1, 0, r, c, size(network))...][χNE D0; χE] * + env.Eb[_coordinates(dir + 1, 1, r, c, size(network))...][χNE D0; χE] * rotl60(network[r, c], mod(dir - 1, 6))[D180 D240 D300; D120 D60 D0] return mat end @@ -16,7 +16,7 @@ function build_double_corner_matrix_triangular(network::InfiniteTriangularNetwor env.C[_coordinates(dir, 0, r, c, size(network))...][χNW Dt120 Db120; χN] * env.C[_coordinates(dir + 1, 0, r, c, size(network))...][χN Dt60 Db60; χNE] * env.Ea[_coordinates(dir - 1, 0, r, c, size(network))...][χW Dt180 Db180; χNW] * - env.Eb[_coordinates(dir + 1, 0, r, c, size(network))...][χNE Dt0 Db0; χE] * + env.Eb[_coordinates(dir + 1, 1, r, c, size(network))...][χNE Dt0 Db0; χE] * rotl60(ket(O_r_c), mod(dir - 1, 6))[d; Dt120 Dt60 Dt0 Dt300 Dt240 Dt180] * conj(rotl60(bra(O_r_c), mod(dir - 1, 6))[d; Db120 Db60 Db0 Db300 Db240 Db180]) return mat diff --git a/src/algorithms/contractions/triangular_ctmrg/renormalize_corner.jl b/src/algorithms/contractions/triangular_ctmrg/renormalize_corner.jl index 00968750b..e662e4091 100644 --- a/src/algorithms/contractions/triangular_ctmrg/renormalize_corner.jl +++ b/src/algorithms/contractions/triangular_ctmrg/renormalize_corner.jl @@ -6,7 +6,7 @@ function renormalize_corner_triangular((dir, r, c), network::InfiniteTriangularN env.Ea[_coordinates(dir - 1, 0, r, c, size(network))...][χW D180; χNW] * env.Eb[_coordinates(dir, 1, r, c, size(network))...][χN D60; χNE] * rotl60(network[r, c], mod(dir - 1, 6))[D180 D240 D300; D120 D60 D0] * - Pas[mod1(dir - 1, 6), r, c][χSW; χW D240] * Pbs[dir, r, c][χNE D0; χSE] + Pas[_coordinates(dir - 1, 5, r, c, size(network))...][χSW; χW D240] * Pbs[dir, r, c][χNE D0; χSE] return env_C_new end @@ -18,6 +18,6 @@ function renormalize_corner_triangular((dir, r, c), network::InfiniteTriangularN env.Eb[_coordinates(dir, 1, r, c, size(network))...][χN Dt60 Db60; χNE] * rotl60(ket(O_r_c), mod(dir - 1, 6))[d; Dt120 Dt60 Dt0 Dt300 Dt240 Dt180] * conj(rotl60(bra(O_r_c), mod(dir - 1, 6))[d; Db120 Db60 Db0 Db300 Db240 Db180]) * - Pas[mod1(dir - 1, 6), r, c][χSW; χW Dt240 Db240] * Pbs[dir, r, c][χNE Dt0 Db0; χSE] + Pas[_coordinates(dir - 1, 5, r, c, size(network))...][χSW; χW Dt240 Db240] * Pbs[dir, r, c][χNE Dt0 Db0; χSE] return env_C_new end diff --git a/src/algorithms/contractions/triangular_ctmrg/renormalize_edge.jl b/src/algorithms/contractions/triangular_ctmrg/renormalize_edge.jl index a3dacc3bf..6b579f0e2 100644 --- a/src/algorithms/contractions/triangular_ctmrg/renormalize_edge.jl +++ b/src/algorithms/contractions/triangular_ctmrg/renormalize_edge.jl @@ -3,7 +3,7 @@ function semi_renormalize_edge(network::InfiniteTriangularNetwork{P}, env::CTMRGEnvTriangular, Pas, Pbs, dir, r, c) where {P <: PEPSSandwichTriangular} O_r_c = network[r, c] @tensor opt = true mat[χW Dt240 Db240; χE Dt300 Db300] := - Pas[dir, r, c][χW; χNW Dt180 Db180] * Pbs[dir, r, c][χNE Dt0 Db0; χE] * + Pas[_coordinates(dir, 5, r, c, size(network))...][χW; χNW Dt180 Db180] * Pbs[dir, r, c][χNE Dt0 Db0; χE] * env.Ea[_coordinates(dir, 0, r, c, size(network))...][χNW Dt120 Db120; χN] * env.Eb[_coordinates(dir, 1, r, c, size(network))...][χN Dt60 Db60; χNE] * rotl60(ket(O_r_c), mod(dir - 1, 6))[d; Dt120 Dt60 Dt0 Dt300 Dt240 Dt180] * @@ -13,7 +13,7 @@ end function semi_renormalize_edge(network::InfiniteTriangularNetwork{P}, env::CTMRGEnvTriangular, Pas, Pbs, dir, r, c) where {P <: PFTensorTriangular} @tensor opt = true mat[χW D240; χE D300] := - Pas[dir, r, c][χW; χNW D180] * + Pas[_coordinates(dir, 5, r, c, size(network))...][χW; χNW D180] * Pbs[dir, r, c][χNE D0; χE] * env.Ea[_coordinates(dir, 0, r, c, size(network))...][χNW D120; χN] * env.Eb[_coordinates(dir, 1, r, c, size(network))...][χN D60; χNE] * diff --git a/src/algorithms/ctmrg_triangular/simultaneous.jl b/src/algorithms/ctmrg_triangular/simultaneous.jl index dc9796857..816e44b5c 100644 --- a/src/algorithms/ctmrg_triangular/simultaneous.jl +++ b/src/algorithms/ctmrg_triangular/simultaneous.jl @@ -103,10 +103,13 @@ end _normalize_corner_environment(ρ) = ρ / norm(ρ) +# The double corners around the elementary triangle of projector `[dir, r, c]` sit at +# `(r, c)`, its neighbor in direction `dir + 2` and its neighbor in direction `dir + 3`. +# `Pb` acts on the leg of `(r, c)`, `Pa` on the leg of its neighbor in direction `dir + 2`. function calculate_twothirds_projectors(network::InfiniteTriangularNetwork{P}, env, trunc, decomposition_alg = SVDAdjoint()) where {P} projectors = dtmap(eachcoordinate(network, 1:6)) do (dir, r, c) ρL = build_double_corner_matrix_triangular(network, env, _prev(dir, 6), r, c) - ρR = build_double_corner_matrix_triangular(network, env, _next(dir, 6), r, c) + ρR = build_double_corner_matrix_triangular(network, env, _next(dir, 6), _coordinates(dir, 2, r, c, size(network))[2:3]...) ρL = _normalize_corner_environment(ρL) ρR = _normalize_corner_environment(ρR) ρρ = _normalize_corner_environment(ρL * ρR) @@ -127,8 +130,8 @@ end function calculate_full_projectors(network::InfiniteTriangularNetwork{P}, env, trunc, decomposition_alg = SVDAdjoint()) where {P} projectors = dtmap(eachcoordinate(network, 1:6)) do (dir, r, c) ρL = build_double_corner_matrix_triangular(network, env, mod1(dir - 1, 6), r, c) - ρR = build_double_corner_matrix_triangular(network, env, mod1(dir + 1, 6), r, c) - ρ̄ = build_double_corner_matrix_triangular(network, env, mod1(dir + 3, 6), r, c) + ρR = build_double_corner_matrix_triangular(network, env, mod1(dir + 1, 6), _coordinates(dir, 2, r, c, size(network))[2:3]...) + ρ̄ = build_double_corner_matrix_triangular(network, env, mod1(dir + 3, 6), _coordinates(dir, 3, r, c, size(network))[2:3]...) ρ̄ = _normalize_corner_environment(ρ̄) Ū, S̄, V̄ᴴ = svd_full(ρ̄, decomposition_alg) sqrtS̄ = sdiag_pow(S̄, 0.5)