From c93cbe297944012a3dcf6b7c0cb1325bdb2e5b36 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Thu, 22 Oct 2026 10:29:25 +0200 Subject: [PATCH 1/8] Get started with a C4v specific CTMRG environment --- src/PEPSKit.jl | 3 +- .../contractions/correlator/pepo_1layer.jl | 12 +- .../contractions/correlator/pepo_purified.jl | 8 +- .../contractions/correlator/peps.jl | 12 +- src/algorithms/contractions/ctmrg/gaugefix.jl | 16 +- .../contractions/ctmrg/network_value.jl | 21 +- .../contractions/ctmrg/renormalize_corner.jl | 4 +- .../contractions/ctmrg/renormalize_edge.jl | 10 +- .../local_patch/densitymatrix/ctmrg.jl | 6 +- .../contractions/local_patch/network_expr.jl | 2 +- src/algorithms/ctmrg/c4v.jl | 29 +- src/algorithms/ctmrg/ctmrg.jl | 12 +- src/algorithms/ctmrg/gaugefix.jl | 59 ++- src/algorithms/ctmrg/sparse_environments.jl | 2 +- .../expectation_value/correlator_adapters.jl | 10 +- .../expectation_value/correlators.jl | 10 +- .../expectation_value/expectation_value.jl | 2 +- .../expectation_value/network_value.jl | 12 +- .../optimization/implicit_differentiation.jl | 8 +- .../optimization/preconditioning.jl | 2 +- src/algorithms/select_algorithm.jl | 2 +- src/algorithms/toolbox.jl | 6 +- src/algorithms/transfermatrix.jl | 4 +- src/environments/c4v_ctmrg_environments.jl | 351 ++++++++++++++++++ src/environments/ctmrg_environments.jl | 59 +-- test/testsuite/ctmrg/c4v_sign_branches.jl | 2 +- test/testsuite/ctmrg/fixed_iterscheme.jl | 2 +- test/testsuite/ctmrg/flavors.jl | 2 +- 28 files changed, 540 insertions(+), 128 deletions(-) create mode 100644 src/environments/c4v_ctmrg_environments.jl diff --git a/src/PEPSKit.jl b/src/PEPSKit.jl index 2aeb865b6..4c9e4c775 100644 --- a/src/PEPSKit.jl +++ b/src/PEPSKit.jl @@ -89,6 +89,7 @@ include("operators/lattices/squarelattice.jl") include("operators/models.jl") include("environments/ctmrg_environments.jl") +include("environments/c4v_ctmrg_environments.jl") include("environments/vumps_environments.jl") include("environments/suweight.jl") include("environments/bp_environments.jl") @@ -176,7 +177,7 @@ include("algorithms/select_algorithm.jl") using .Defaults: set_scheduler! export set_scheduler! export EighAdjoint, IterEigh, SVDAdjoint, IterSVD, OrthAdjoint, QRAdjoint -export CTMRGEnv, SequentialCTMRG, SimultaneousCTMRG +export C4vCTMRGEnv, CTMRGEnv, SequentialCTMRG, SimultaneousCTMRG export initialize_ctmrg_environment, RandomInitialization, ProductStateInitialization, ApplicationInitialization, IdentityInitialization export corner, edge, setcorner!, setedge! diff --git a/src/algorithms/contractions/correlator/pepo_1layer.jl b/src/algorithms/contractions/correlator/pepo_1layer.jl index 83924ff35..e569a23a3 100644 --- a/src/algorithms/contractions/correlator/pepo_1layer.jl +++ b/src/algorithms/contractions/correlator/pepo_1layer.jl @@ -2,7 +2,7 @@ function start_correlator_left( i::CartesianIndex{2}, ρ::InfinitePEPO, - O::PFTensor, env::CTMRGEnv + O::PFTensor, env::AbstractCTMRGEnv ) (size(ρ, 3) == 1) || throw(ArgumentError("The input PEPO ρ must have only one layer.")) @@ -29,7 +29,7 @@ end function end_correlator_right_numerator( j::CartesianIndex{2}, V::AbstractTensorMap{T, S, 3, 1}, - ρ::InfinitePEPO, O::PFTensor, env::CTMRGEnv + ρ::InfinitePEPO, O::PFTensor, env::AbstractCTMRGEnv ) where {T, S} (size(ρ, 3) == 1) || throw(ArgumentError("The input PEPO ρ must have only one layer.")) @@ -47,7 +47,7 @@ function end_correlator_right_numerator( end function end_correlator_right_denominator( - j::CartesianIndex{2}, V::AbstractTensorMap{T, S, 2, 1}, env::CTMRGEnv + j::CartesianIndex{2}, V::AbstractTensorMap{T, S, 2, 1}, env::AbstractCTMRGEnv ) where {T, S} r, c = Tuple(j) C_northeast = corner(env, NORTHEAST, r - 1, c + 1) @@ -61,7 +61,7 @@ end function start_correlator_right( i::CartesianIndex{2}, ρ::InfinitePEPO, - O::PFTensor, env::CTMRGEnv + O::PFTensor, env::AbstractCTMRGEnv ) (size(ρ, 3) == 1) || throw(ArgumentError("The input PEPO ρ must have only one layer.")) @@ -87,7 +87,7 @@ end function end_correlator_left_numerator( j::CartesianIndex{2}, V::AbstractTensorMap{T, S, 3, 1}, - ρ::InfinitePEPO, O::PFTensor, env::CTMRGEnv + ρ::InfinitePEPO, O::PFTensor, env::AbstractCTMRGEnv ) where {T, S} (size(ρ, 3) == 1) || throw(ArgumentError("The input PEPO ρ must have only one layer.")) @@ -106,7 +106,7 @@ function end_correlator_left_numerator( end function end_correlator_left_denominator( - j::CartesianIndex{2}, V::AbstractTensorMap{T, S, 2, 1}, env::CTMRGEnv + j::CartesianIndex{2}, V::AbstractTensorMap{T, S, 2, 1}, env::AbstractCTMRGEnv ) where {T, S} r, c = Tuple(j) C_northwest = corner(env, NORTHWEST, r - 1, c - 1) diff --git a/src/algorithms/contractions/correlator/pepo_purified.jl b/src/algorithms/contractions/correlator/pepo_purified.jl index fc2e44b3b..5cbca3442 100644 --- a/src/algorithms/contractions/correlator/pepo_purified.jl +++ b/src/algorithms/contractions/correlator/pepo_purified.jl @@ -5,7 +5,7 @@ function start_correlator_left( below::InfinitePEPO, O::PFTensor, above::InfinitePEPO, - env::CTMRGEnv, + env::AbstractCTMRGEnv, ) r, c = Tuple(i) E_north = edge(env, NORTH, r - 1, c) @@ -44,7 +44,7 @@ function end_correlator_right_numerator( above::InfinitePEPO, O::PFTensor, below::InfinitePEPO, - env::CTMRGEnv, + env::AbstractCTMRGEnv, ) where {T, S} r, c = Tuple(j) E_north = edge(env, NORTH, r - 1, c) @@ -73,7 +73,7 @@ function start_correlator_right( below::InfinitePEPO, O::PFTensor, above::InfinitePEPO, - env::CTMRGEnv, + env::AbstractCTMRGEnv, ) r, c = Tuple(i) E_north = edge(env, NORTH, r - 1, c) @@ -112,7 +112,7 @@ function end_correlator_left_numerator( above::InfinitePEPO, O::PFTensor, below::InfinitePEPO, - env::CTMRGEnv, + env::AbstractCTMRGEnv, ) where {T, S} r, c = Tuple(j) E_north = edge(env, NORTH, r - 1, c) diff --git a/src/algorithms/contractions/correlator/peps.jl b/src/algorithms/contractions/correlator/peps.jl index 39392d709..42d5f3c94 100644 --- a/src/algorithms/contractions/correlator/peps.jl +++ b/src/algorithms/contractions/correlator/peps.jl @@ -5,7 +5,7 @@ function start_correlator_left( below::InfinitePEPS, O::MPOTensor, above::InfinitePEPS, - env::CTMRGEnv, + env::AbstractCTMRGEnv, ) r, c = Tuple(i) E_north = edge(env, NORTH, r - 1, c) @@ -45,7 +45,7 @@ function end_correlator_right_numerator( above::InfinitePEPS, O::MPOTensor, below::InfinitePEPS, - env::CTMRGEnv, + env::AbstractCTMRGEnv, ) where {T, S} r, c = Tuple(j) E_north = edge(env, NORTH, r - 1, c) @@ -68,7 +68,7 @@ end function end_correlator_right_denominator( j::CartesianIndex{2}, V::AbstractTensorMap{T, S, 3, 1}, - env::CTMRGEnv + env::AbstractCTMRGEnv ) where {T, S} r, c = Tuple(j) C_northeast = corner(env, NORTHEAST, r - 1, c + 1) @@ -88,7 +88,7 @@ function start_correlator_right( below::InfinitePEPS, O::MPOTensor, above::InfinitePEPS, - env::CTMRGEnv, + env::AbstractCTMRGEnv, ) r, c = Tuple(i) E_north = edge(env, NORTH, r - 1, c) @@ -126,7 +126,7 @@ function end_correlator_left_numerator( above::InfinitePEPS, O::MPOTensor, below::InfinitePEPS, - env::CTMRGEnv, + env::AbstractCTMRGEnv, ) where {T, S} r, c = Tuple(j) E_north = edge(env, NORTH, r - 1, c) @@ -149,7 +149,7 @@ end function end_correlator_left_denominator( j::CartesianIndex{2}, V::AbstractTensorMap{T, S, 3, 1}, - env::CTMRGEnv + env::AbstractCTMRGEnv ) where {T, S} r, c = Tuple(j) C_northwest = corner(env, NORTHWEST, r - 1, c - 1) diff --git a/src/algorithms/contractions/ctmrg/gaugefix.jl b/src/algorithms/contractions/ctmrg/gaugefix.jl index b4d37acbb..41f9222eb 100644 --- a/src/algorithms/contractions/ctmrg/gaugefix.jl +++ b/src/algorithms/contractions/ctmrg/gaugefix.jl @@ -28,7 +28,7 @@ $(SIGNATURES) Apply `fix_gauge_corner` to the northwest corner with appropriate row and column indices. """ -function fix_gauge_northwest_corner((row, col), env::CTMRGEnv, signs) +function fix_gauge_northwest_corner((row, col), env::AbstractCTMRGEnv, signs) return fix_gauge_corner( corner(env, NORTHWEST, row, col), signs[WEST, row, col], @@ -41,7 +41,7 @@ $(SIGNATURES) Apply `fix_gauge_corner` to the northeast corner with appropriate row and column indices. """ -function fix_gauge_northeast_corner((row, col), env::CTMRGEnv, signs) +function fix_gauge_northeast_corner((row, col), env::AbstractCTMRGEnv, signs) return fix_gauge_corner( corner(env, NORTHEAST, row, col), signs[NORTH, row, col], @@ -54,7 +54,7 @@ $(SIGNATURES) Apply `fix_gauge_corner` to the southeast corner with appropriate row and column indices. """ -function fix_gauge_southeast_corner((row, col), env::CTMRGEnv, signs) +function fix_gauge_southeast_corner((row, col), env::AbstractCTMRGEnv, signs) return fix_gauge_corner( corner(env, SOUTHEAST, row, col), signs[EAST, row, col], @@ -67,7 +67,7 @@ $(SIGNATURES) Apply `fix_gauge_corner` to the southwest corner with appropriate row and column indices. """ -function fix_gauge_southwest_corner((row, col), env::CTMRGEnv, signs) +function fix_gauge_southwest_corner((row, col), env::AbstractCTMRGEnv, signs) return fix_gauge_corner( corner(env, SOUTHWEST, row, col), signs[SOUTH, row, col], @@ -111,7 +111,7 @@ $(SIGNATURES) Apply `fix_gauge_edge` to the north edge with appropriate row and column indices. """ -function fix_gauge_north_edge((row, col), env::CTMRGEnv, signs) +function fix_gauge_north_edge((row, col), env::AbstractCTMRGEnv, signs) return fix_gauge_edge( edge(env, NORTH, row, col), signs[NORTH, row, col], signs[NORTH, row, _next(col, end)], ) @@ -122,7 +122,7 @@ $(SIGNATURES) Apply `fix_gauge_edge` to the east edge with appropriate row and column indices. """ -function fix_gauge_east_edge((row, col), env::CTMRGEnv, signs) +function fix_gauge_east_edge((row, col), env::AbstractCTMRGEnv, signs) return fix_gauge_edge( edge(env, EAST, row, col), signs[EAST, row, col], signs[EAST, _next(row, end), col] ) @@ -133,7 +133,7 @@ $(SIGNATURES) Apply `fix_gauge_edge` to the south edge with appropriate row and column indices. """ -function fix_gauge_south_edge((row, col), env::CTMRGEnv, signs) +function fix_gauge_south_edge((row, col), env::AbstractCTMRGEnv, signs) return fix_gauge_edge( edge(env, SOUTH, row, col), signs[SOUTH, row, col], signs[SOUTH, row, _prev(col, end)], ) @@ -144,7 +144,7 @@ $(SIGNATURES) Apply `fix_gauge_edge` to the west edge with appropriate row and column indices. """ -function fix_gauge_west_edge((row, col), env::CTMRGEnv, signs) +function fix_gauge_west_edge((row, col), env::AbstractCTMRGEnv, signs) return fix_gauge_edge( edge(env, WEST, row, col), signs[WEST, row, col], signs[WEST, _prev(row, end), col] ) diff --git a/src/algorithms/contractions/ctmrg/network_value.jl b/src/algorithms/contractions/ctmrg/network_value.jl index 3961cc061..510084a5d 100644 --- a/src/algorithms/contractions/ctmrg/network_value.jl +++ b/src/algorithms/contractions/ctmrg/network_value.jl @@ -71,7 +71,7 @@ end end """ - _contract_site(ind::Tuple{Int,Int}, network::InfiniteSquareNetwork, env::CTMRGEnv) + _contract_site(ind::Tuple{Int,Int}, network::InfiniteSquareNetwork, env::AbstractCTMRGEnv) Contract around a single site `ind` of a square network using a given CTMRG environment. """ @@ -88,6 +88,19 @@ function _contract_site(ind::Tuple{Int, Int}, network::InfiniteSquareNetwork, en ) end +function _contract_site(ind::Tuple{Int, Int}, network::InfiniteSquareNetwork, env::C4vCTMRGEnv) + r, c = ind + return _contract_site( + corner(env, NORTHWEST, r - 1, c - 1), + corner(env, NORTHEAST, r - 1, c + 1), + corner(env, SOUTHEAST, r + 1, c + 1), + corner(env, SOUTHWEST, r + 1, c - 1), + edge(env, NORTH, r - 1, c), edge(env, EAST, r, c + 1), + edge(env, SOUTH, r + 1, c), edge(env, WEST, r, c - 1), + network[r, c], + ) +end + ## Normalization contractions function _contract_corners( C_northwest::CTMRGCornerTensor, C_northeast::CTMRGCornerTensor, @@ -103,7 +116,7 @@ end Contract all corners around the south-east at position `ind` of the CTMRG environment `env`. """ -function _contract_corners(ind::Tuple{Int, Int}, env::CTMRGEnv) +function _contract_corners(ind::Tuple{Int, Int}, env::AbstractCTMRGEnv) r, c = ind return _contract_corners( corner(env, NORTHWEST, r - 1, c - 1), @@ -145,7 +158,7 @@ end Contract the vertical edges and corners around the east edge at position `ind` of the CTMRG environment `env`. """ -function _contract_vertical_edges(ind::Tuple{Int, Int}, env::CTMRGEnv) +function _contract_vertical_edges(ind::Tuple{Int, Int}, env::AbstractCTMRGEnv) r, c = ind return _contract_vertical_edges( corner(env, NORTHWEST, r - 1, c - 1), @@ -188,7 +201,7 @@ end Contract the horizontal edges and corners around the south edge at position `ind` of the CTMRG environment `env`. """ -function _contract_horizontal_edges(ind::Tuple{Int, Int}, env::CTMRGEnv) +function _contract_horizontal_edges(ind::Tuple{Int, Int}, env::AbstractCTMRGEnv) r, c = ind return _contract_horizontal_edges( corner(env, NORTHWEST, r - 1, c - 1), diff --git a/src/algorithms/contractions/ctmrg/renormalize_corner.jl b/src/algorithms/contractions/ctmrg/renormalize_corner.jl index 51e2e49e0..b551c6283 100644 --- a/src/algorithms/contractions/ctmrg/renormalize_corner.jl +++ b/src/algorithms/contractions/ctmrg/renormalize_corner.jl @@ -406,7 +406,7 @@ Apply left projector to southwest corner and south edge. C -- E --out ``` """ -function renormalize_southwest_corner((row, col), env::CTMRGEnv, projectors) +function renormalize_southwest_corner((row, col), env::AbstractCTMRGEnv, projectors) C_southwest = corner(env, SOUTHWEST, row, col - 1) E_south = edge(env, SOUTH, row, col) P_left = projectors[1][row] @@ -448,7 +448,7 @@ Apply right projector to northwest corner and north edge. out ``` """ -function renormalize_northwest_corner((row, col), env::CTMRGEnv, projectors) +function renormalize_northwest_corner((row, col), env::AbstractCTMRGEnv, projectors) C_northwest = corner(env, NORTHWEST, row, col - 1) E_north = edge(env, NORTH, row, col) P_right = projectors[2][_next(row, end)] diff --git a/src/algorithms/contractions/ctmrg/renormalize_edge.jl b/src/algorithms/contractions/ctmrg/renormalize_edge.jl index a5f8e2a3f..5efd31cac 100644 --- a/src/algorithms/contractions/ctmrg/renormalize_edge.jl +++ b/src/algorithms/contractions/ctmrg/renormalize_edge.jl @@ -16,7 +16,7 @@ environment tensors. ``` """ function renormalize_north_edge( - (row, col), env::CTMRGEnv, P_left, P_right, network::InfiniteSquareNetwork + (row, col), env::AbstractCTMRGEnv, P_left, P_right, network::InfiniteSquareNetwork ) return renormalize_north_edge( edge(env, NORTH, row - 1, col), @@ -87,7 +87,7 @@ environment tensors. ``` """ function renormalize_east_edge( - (row, col), env::CTMRGEnv, P_left, P_right, network::InfiniteSquareNetwork + (row, col), env::AbstractCTMRGEnv, P_left, P_right, network::InfiniteSquareNetwork ) return renormalize_east_edge( edge(env, EAST, row, col + 1), @@ -152,7 +152,7 @@ environment tensors. ``` """ function renormalize_south_edge( - (row, col), env::CTMRGEnv, P_left, P_right, network::InfiniteSquareNetwork + (row, col), env::AbstractCTMRGEnv, P_left, P_right, network::InfiniteSquareNetwork ) return renormalize_south_edge( edge(env, SOUTH, row + 1, col), @@ -222,7 +222,7 @@ environment tensors. ``` """ function renormalize_west_edge( # For simultaneous CTMRG scheme - (row, col), env::CTMRGEnv, P_left, P_right, network::InfiniteSquareNetwork, + (row, col), env::AbstractCTMRGEnv, P_left, P_right, network::InfiniteSquareNetwork, ) return renormalize_west_edge( edge(env, WEST, row, col - 1), @@ -232,7 +232,7 @@ function renormalize_west_edge( # For simultaneous CTMRG scheme ) end function renormalize_west_edge( # For sequential CTMRG scheme - (row, col), env::CTMRGEnv, projectors, network::InfiniteSquareNetwork, + (row, col), env::AbstractCTMRGEnv, projectors, network::InfiniteSquareNetwork, ) return renormalize_west_edge( edge(env, WEST, row, col - 1), diff --git a/src/algorithms/contractions/local_patch/densitymatrix/ctmrg.jl b/src/algorithms/contractions/local_patch/densitymatrix/ctmrg.jl index 823cb579f..882b95097 100644 --- a/src/algorithms/contractions/local_patch/densitymatrix/ctmrg.jl +++ b/src/algorithms/contractions/local_patch/densitymatrix/ctmrg.jl @@ -4,7 +4,7 @@ # Keep contraction order but try to optimize intermediate permutations: # EE_SWA is largest object so keep largest legs to the front there function reduced_densitymatrix1x1( - inds::CartesianIndex{2}, ket::InfinitePEPS, bra::InfinitePEPS, env::CTMRGEnv + inds::CartesianIndex{2}, ket::InfinitePEPS, bra::InfinitePEPS, env::AbstractCTMRGEnv ) row, col = Tuple(inds) @@ -45,7 +45,7 @@ end # Special case 2x1 density matrix: # Keep contraction order but try to optimize intermediate permutations: function reduced_densitymatrix2x1( - ind::CartesianIndex, ket::InfinitePEPS, bra::InfinitePEPS, env::CTMRGEnv + ind::CartesianIndex, ket::InfinitePEPS, bra::InfinitePEPS, env::AbstractCTMRGEnv ) row, col = Tuple(ind) @@ -95,7 +95,7 @@ function reduced_densitymatrix2x1( end function reduced_densitymatrix1x2( - ind::CartesianIndex, ket::InfinitePEPS, bra::InfinitePEPS, env::CTMRGEnv + ind::CartesianIndex, ket::InfinitePEPS, bra::InfinitePEPS, env::AbstractCTMRGEnv ) row, col = Tuple(ind) diff --git a/src/algorithms/contractions/local_patch/network_expr.jl b/src/algorithms/contractions/local_patch/network_expr.jl index 5fb6ab5ba..6e73b7b45 100644 --- a/src/algorithms/contractions/local_patch/network_expr.jl +++ b/src/algorithms/contractions/local_patch/network_expr.jl @@ -39,7 +39,7 @@ boundary_contraction_expr(env::Type, rowrange, colrange) = throw( ) function boundary_contraction_expr( - ::Type{<:CTMRGEnv{C, T}}, rowrange, colrange + ::Type{<:AbstractCTMRGEnv{C, T}}, rowrange, colrange ) where {C, T} # the edges carry one virtual leg per layer of the sandwich, on top of the two environment # indices threading the ring, so the height follows from the edge tensor type diff --git a/src/algorithms/ctmrg/c4v.jl b/src/algorithms/ctmrg/c4v.jl index c79c32e4b..a16d80f01 100644 --- a/src/algorithms/ctmrg/c4v.jl +++ b/src/algorithms/ctmrg/c4v.jl @@ -37,7 +37,7 @@ end CTMRG_SYMBOLS[:C4vCTMRG] = C4vCTMRG function check_input( - ::typeof(leading_boundary), network::InfiniteSquareNetwork, env::CTMRGEnv, alg::C4vCTMRG; atol = 1.0e-10 + ::typeof(leading_boundary), network::InfiniteSquareNetwork, env::C4vCTMRGEnv, alg::C4vCTMRG; atol = 1.0e-10 ) # check unit cell size length(network) == 1 || throw(ArgumentError("C4v CTMRG is only compatible with single-site unit cells.")) @@ -83,23 +83,23 @@ function corner_spectrum(C::AbstractTensorMap, ::C4vCTMRG) end """ - convergence_tensors(env::CTMRGEnv, alg::C4vCTMRG) -> (corners, edges) + convergence_tensors(env::C4vCTMRGEnv, alg::C4vCTMRG) -> (corners, edges) The corners and edges whose singular values determine convergence. For C₄ᵥ-symmetric CTMRG, only one corner and one edge are needed since the corners are identical, and the edges have identical singular values. """ -convergence_tensors(env::CTMRGEnv, ::C4vCTMRG) = +convergence_tensors(env::C4vCTMRGEnv, ::C4vCTMRG) = (view(env.corners, 1:1, :, :), view(env.edges, 1:1, :, :)) -function ctmrg_iteration(network, env::CTMRGEnv, alg::C4vCTMRG) +function ctmrg_iteration(network, env::C4vCTMRGEnv, alg::C4vCTMRG) projector_alg = alg.projector_alg enlarged_corner = c4v_enlarge(network, env, projector_alg) projector, info = c4v_projector!(enlarged_corner, projector_alg) edge′ = c4v_renormalize_edge(network, env, projector) corner′ = c4v_renormalize_corner(edge′, projector, info, projector_alg) - return CTMRGEnv(corner′, edge′), info + return C4vCTMRGEnv(corner′, edge′), info end """Report unsupported projector algorithms when enlarging a C₄ᵥ corner.""" @@ -116,7 +116,7 @@ Renormalize the single C₄ᵥ edge tensor. | ``` """ -function c4v_renormalize_edge(network, env, projector) +function c4v_renormalize_edge(network, env::C4vCTMRGEnv, projector) # TODO: possible missing twists for fermions new_edge = renormalize_north_edge(env.edges[1], projector, projector', network[1, 1]) # additional Hermitian projection step for numerical stability @@ -168,7 +168,7 @@ function c4v_renormalize_corner(new_edge::CTMRGEdgeTensor, projector, info, ::C4 return new_corner / norm(new_corner) end -"""Contract two CTMRG edge tensors into a corner tensor.""" +"""Contract two C4v CTMRG edge tensors into a corner tensor.""" @generated function contract_edges( EL::CTMRGEdgeTensor{T, S, N}, ER::CTMRGEdgeTensor{T, S, N} ) where {T, S, N} @@ -178,14 +178,13 @@ end return macroexpand(@__MODULE__, :(return @tensor $C´_e := $EL_e * $ER_e)) end -# TODO: this should eventually be the constructor for a new C4vCTMRGEnv type -function CTMRGEnv( +function C4vCTMRGEnv( corner::AbstractTensorMap{T, S, 1, 1}, edge::AbstractTensorMap{T′, S, N, 1} ) where {T, T′, S, N} - corners = fill(corner, 4, 1, 1) + corners = fill(corner, 1, 1, 1) edge_SW = physical_flip(edge) edges = reshape([edge, edge, edge_SW, edge_SW], (4, 1, 1)) - return CTMRGEnv(corners, edges) + return C4vCTMRGEnv(corners, edges) end # @@ -217,7 +216,7 @@ end """ initialize_random_c4v_env([f=randn, T=storagetype(state)], state, Venv::ElementarySpace) -Initialize a C₄ᵥ-symmetric `CTMRGEnv` on virtual spaces `Venv` with random entries created +Initialize a C₄ᵥ-symmetric `C4vCTMRGEnv` on virtual spaces `Venv` with random entries created by `f` and scalartype `T`. """ function initialize_random_c4v_env(state, Venv::ElementarySpace) @@ -235,13 +234,13 @@ function initialize_random_c4v_env(f, T, Vstate::VectorSpace, Venv::ElementarySp corner₀ = DiagonalTensorMap(f(similarstoragetype(similarstoragetype(T), real(eltype(T))), Venv ← Venv)) edge₀ = f(T, Venv ⊗ Vstate ← Venv) edge₀ = _project_hermitian(edge₀) - return CTMRGEnv(corner₀, edge₀) + return C4vCTMRGEnv(corner₀, edge₀) end """ initialize_singlet_c4v_env([T=storagetype(state)], state::InfinitePEPS, Venv::ElementarySpace) -Initialize a C₄ᵥ-symmetric `CTMRGEnv` with a singlet corner of dimension `dim(Venv)` and an +Initialize a C₄ᵥ-symmetric `C4vCTMRGEnv` with a singlet corner of dimension `dim(Venv)` and an identity edge from `id(T, Venv ⊗ Vpeps)`. """ function initialize_singlet_c4v_env(state::InfinitePEPS, Venv::ElementarySpace) @@ -258,5 +257,5 @@ function initialize_singlet_c4v_env(T, Vpeps::ElementarySpace, Venv::ElementaryS corner₀ = DiagonalTensorMap(diag, Venv) corner₀.data[2:end] .= zero(real(eltype(T))) edge₀ = permute(id(T, Venv ⊗ Vpeps), ((1, 2, 4), (3,))) - return CTMRGEnv(corner₀, edge₀) + return C4vCTMRGEnv(corner₀, edge₀) end diff --git a/src/algorithms/ctmrg/ctmrg.jl b/src/algorithms/ctmrg/ctmrg.jl index 5852e1448..9263eb89f 100644 --- a/src/algorithms/ctmrg/ctmrg.jl +++ b/src/algorithms/ctmrg/ctmrg.jl @@ -101,12 +101,12 @@ supplied via the keyword arguments or directly as an [`CTMRGAlgorithm`](@ref) st - `info.convergence_error::Real` : The final convergence error at the end of the contraction procedure. - `info.contraction_metrics::NamedTuple` : A `NamedTuple` containing metrics which characterize the contraction. The precise contents depend on `alg`. """ -function leading_boundary(env₀::CTMRGEnv, network::InfiniteSquareNetwork; kwargs...) +function leading_boundary(env₀::AbstractCTMRGEnv, network::InfiniteSquareNetwork; kwargs...) alg = select_algorithm(leading_boundary, env₀; kwargs...) return leading_boundary(env₀, network, alg) end function leading_boundary( - env₀::CTMRGEnv, network::InfiniteSquareNetwork, alg::CTMRGAlgorithm + env₀::AbstractCTMRGEnv, network::InfiniteSquareNetwork, alg::CTMRGAlgorithm ) check_input(leading_boundary, network, env₀, alg) log = ignore_derivatives(() -> MPSKit.IterLog("CTMRG")) @@ -142,7 +142,7 @@ function leading_boundary( return env, info end end -function leading_boundary(env₀::CTMRGEnv, state, args...; kwargs...) +function leading_boundary(env₀::AbstractCTMRGEnv, state, args...; kwargs...) return leading_boundary(env₀, InfiniteSquareNetwork(state), args...; kwargs...) end @@ -206,14 +206,14 @@ The corners and edges whose spectra determine convergence. For generic CTMRG, we need all corners and edges. """ -convergence_tensors(env::CTMRGEnv, alg) = (env.corners, env.edges) +convergence_tensors(env::AbstractCTMRGEnv, alg) = (env.corners, env.edges) """ convergence_spectra(env, alg) Spectra of the tensors that [`convergence_tensors`](@ref) selects. """ -function convergence_spectra(env::CTMRGEnv, alg) +function convergence_spectra(env::AbstractCTMRGEnv, alg) corners, edges = convergence_tensors(env, alg) return corner_spectra(corners, alg), edge_spectra(edges, alg) end @@ -276,7 +276,7 @@ function calc_convergence(env, CS_old, TS_old) return max(ΔCS, ΔTS), CS_new, TS_new end -function calc_convergence(env_new::CTMRGEnv, env_old::CTMRGEnv) +function calc_convergence(env_new::AbstractCTMRGEnv, env_old::AbstractCTMRGEnv) CS_old = map(svd_vals, env_old.corners) TS_old = map(svd_vals, env_old.edges) return calc_convergence(env_new, CS_old, TS_old) diff --git a/src/algorithms/ctmrg/gaugefix.jl b/src/algorithms/ctmrg/gaugefix.jl index 9c5c73d78..152a3a42c 100644 --- a/src/algorithms/ctmrg/gaugefix.jl +++ b/src/algorithms/ctmrg/gaugefix.jl @@ -27,7 +27,7 @@ Given that the CTMRG run is converged, the returned environment will be element-wise converged to `envprev`. """ function gauge_fix( - envfinal::CTMRGEnv{C, T}, envprev::CTMRGEnv{C, T}, alg::G + envfinal::AbstractCTMRGEnv{C, T}, envprev::AbstractCTMRGEnv{C, T}, alg::G ) where {C, T, G <: Union{ScramblingEnvGauge, ScramblingEnvGaugeC4v}} signs, corner_phases, edge_phases = compute_gauge_fix_gauge(envfinal, envprev, alg) return fix_phases(envfinal, signs, corner_phases, edge_phases) @@ -54,14 +54,35 @@ function compute_gauge_fix_gauge( return signs, corner_phases, edge_phases end -function fix_phases(env::CTMRGEnv, signs, corner_phases, edge_phases) +function compute_gauge_fix_gauge( + envfinal::C4vCTMRGEnv{C, T}, envprev::C4vCTMRGEnv{C, T}, alg::G + ) where {C, T, G <: Union{ScramblingEnvGauge, ScramblingEnvGaugeC4v}} + # Check if spaces in envprev and envfinal are the same + same_spaces = map(eachcoordinate(envfinal, 1:4)) do (dir, r, c) + space(envfinal.edges[dir, r, c]) == space(envprev.edges[dir, r, c]) && + space(envfinal.corners[1, r, c]) == space(envprev.corners[1, r, c]) + end + all(same_spaces) || throw(ArgumentError("Spaces of envfinal and envprev are not the same")) + + # find relative phases + signs = compute_relative_phases(envfinal, envprev, alg) + + # find additional global phases + corner_phases, edge_phases = compute_global_phases( + fix_relative_phases(envfinal, signs), envprev + ) + + return signs, corner_phases, edge_phases +end + +function fix_phases(env::AbstractCTMRGEnv, signs, corner_phases, edge_phases) env_fixed_relative = fix_relative_phases(env, signs) env_fixed_global = fix_global_phases(env_fixed_relative, corner_phases, edge_phases) return env_fixed_global end function compute_relative_phases( - envfinal::CTMRGEnv{C, T}, envprev::CTMRGEnv{C, T}, ::ScramblingEnvGauge + envfinal::AbstractCTMRGEnv{C, T}, envprev::AbstractCTMRGEnv{C, T}, ::ScramblingEnvGauge ) where {C, T} signs = map(eachcoordinate(envfinal, 1:4)) do (dir, r, c) @@ -100,7 +121,7 @@ function compute_relative_phases( return signs end -function compute_relative_phases(envfinal::CTMRGEnv{C, T}, envprev::CTMRGEnv{C, T}, ::ScramblingEnvGaugeC4v) where {C, T} +function compute_relative_phases(envfinal::AbstractCTMRGEnv{C, T}, envprev::AbstractCTMRGEnv{C, T}, ::ScramblingEnvGaugeC4v) where {C, T} # "general" algorithm from https://arxiv.org/abs/2311.11894 Tprev = envprev.edges[1, 1, 1] Tfinal = envfinal.edges[1, 1, 1] @@ -194,6 +215,25 @@ function fix_relative_phases(envfinal::CTMRGEnv, signs) return CTMRGEnv(corners_fixed, edges_fixed) end +function fix_relative_phases(envfinal::C4vCTMRGEnv, signs) + # all corners are the same + corner_fixed = map(eachcoordinate(envfinal, 1:1)) do (dir, r, c) + fix_gauge_northwest_corner((r, c), envfinal, signs) + end + edges_fixed = map(eachcoordinate(envfinal, 1:4)) do (dir, r, c) + Ef = if dir == NORTHWEST + fix_gauge_north_edge((r, c), envfinal, signs) + elseif dir == NORTHEAST + fix_gauge_east_edge((r, c), envfinal, signs) + elseif dir == SOUTHEAST + fix_gauge_south_edge((r, c), envfinal, signs) + else # dir == SOUTHWEST + fix_gauge_west_edge((r, c), envfinal, signs) + end + return Ef + end + return C4vCTMRGEnv(corner_fixed, edges_fixed) +end function fix_relative_phases( U::Array{Ut, 3}, V::Array{Vt, 3}, signs ) where {Ut <: AbstractTensorMap, Vt <: AbstractTensorMap} @@ -235,7 +275,7 @@ Fix global multiplicative phase of the environment tensors. To that end, the dot between all corners and all edges are computed to obtain the global phase which is then divided out. """ -function fix_global_phases(envfix::CTMRGEnv, envprev::CTMRGEnv) +function fix_global_phases(envfix::AbstractCTMRGEnv, envprev::AbstractCTMRGEnv) corner_phases, edge_phases = compute_global_phases(envfix, envprev) return fix_global_phases(envfix, corner_phases, edge_phases) end @@ -244,8 +284,13 @@ function fix_global_phases(env::CTMRGEnv, corner_phases, edge_phases) edgesgfix = env.edges .* inv.(edge_phases) return CTMRGEnv(cornersgfix, edgesgfix) end +function fix_global_phases(env::C4vCTMRGEnv, corner_phases, edge_phases) + cornersgfix = env.corners .* inv.(corner_phases) + edgesgfix = env.edges .* inv.(edge_phases) + return C4vCTMRGEnv(cornersgfix, edgesgfix) +end -function compute_global_phases(envfix::CTMRGEnv, envprev::CTMRGEnv) +function compute_global_phases(envfix::AbstractCTMRGEnv, envprev::AbstractCTMRGEnv) corner_phases = dot.(envprev.corners, envfix.corners) edge_phases = dot.(envprev.edges, envfix.edges) return corner_phases, edge_phases @@ -258,7 +303,7 @@ Check if the element-wise difference of the corner and edge tensors of the final CTMRG environments are below `atol` and return the maximal difference. """ function calc_elementwise_convergence( - envfinal::CTMRGEnv{C}, envfix::CTMRGEnv{C′}; atol::Real = 1.0e-6 + envfinal::AbstractCTMRGEnv{C}, envfix::AbstractCTMRGEnv{C′}; atol::Real = 1.0e-6 ) where {C, C′} Cfinal = (C <: DiagonalTensorMap) ? convert.(TensorMap, envfinal.corners) : envfinal.corners Cfix = (C′ <: DiagonalTensorMap) ? convert.(TensorMap, envfix.corners) : envfix.corners diff --git a/src/algorithms/ctmrg/sparse_environments.jl b/src/algorithms/ctmrg/sparse_environments.jl index 1d8218df2..59f7efea1 100644 --- a/src/algorithms/ctmrg/sparse_environments.jl +++ b/src/algorithms/ctmrg/sparse_environments.jl @@ -422,7 +422,7 @@ struct ColumnEnlargedCorner{TC, TE} E::TE dir::Int end -function ColumnEnlargedCorner(env::CTMRGEnv, coordinates) +function ColumnEnlargedCorner(env::AbstractCTMRGEnv, coordinates) dir, row, col = coordinates if dir == NORTHWEST return ColumnEnlargedCorner( diff --git a/src/algorithms/expectation_value/correlator_adapters.jl b/src/algorithms/expectation_value/correlator_adapters.jl index 759d2ad26..39ce2c912 100644 --- a/src/algorithms/expectation_value/correlator_adapters.jl +++ b/src/algorithms/expectation_value/correlator_adapters.jl @@ -1,6 +1,6 @@ # Correlator adapters for InfinitePEPS / InfinitePEPO -struct PEPSCorrelator{B <: InfinitePEPS, K <: InfinitePEPS, E <: CTMRGEnv} +struct PEPSCorrelator{B <: InfinitePEPS, K <: InfinitePEPS, E <: AbstractCTMRGEnv} bra::B ket::K env::E @@ -12,7 +12,7 @@ struct PEPSCorrelator{B <: InfinitePEPS, K <: InfinitePEPS, E <: CTMRGEnv} end end -struct PEPOPurifiedCorrelator{B <: InfinitePEPO, K <: InfinitePEPO, E <: CTMRGEnv} +struct PEPOPurifiedCorrelator{B <: InfinitePEPO, K <: InfinitePEPO, E <: AbstractCTMRGEnv} bra::B ket::K env::E @@ -28,12 +28,12 @@ end const BraketCorrelator = Union{PEPSCorrelator, PEPOPurifiedCorrelator} -_braket_correlator(bra::InfinitePEPS, ket::InfinitePEPS, env::CTMRGEnv) = +_braket_correlator(bra::InfinitePEPS, ket::InfinitePEPS, env::AbstractCTMRGEnv) = PEPSCorrelator(bra, ket, env) -_braket_correlator(bra::InfinitePEPO, ket::InfinitePEPO, env::CTMRGEnv) = +_braket_correlator(bra::InfinitePEPO, ket::InfinitePEPO, env::AbstractCTMRGEnv) = PEPOPurifiedCorrelator(bra, ket, env) -struct PEPOTraceCorrelator{P <: InfinitePEPO, E <: CTMRGEnv} +struct PEPOTraceCorrelator{P <: InfinitePEPO, E <: AbstractCTMRGEnv} ρ::P env::E diff --git a/src/algorithms/expectation_value/correlators.jl b/src/algorithms/expectation_value/correlators.jl index 59e57ab4a..b5f29e085 100644 --- a/src/algorithms/expectation_value/correlators.jl +++ b/src/algorithms/expectation_value/correlators.jl @@ -7,7 +7,7 @@ function MPSKit.correlator( O, i::CartesianIndex{2}, js::CoordCollection{2}, ket::S, - env::CTMRGEnv, + env::AbstractCTMRGEnv, ) where {S <: InfiniteState} return _correlator(_braket_correlator(bra, ket, env), O, i, vec(js)) end @@ -17,13 +17,13 @@ function MPSKit.correlator( O, i::CartesianIndex{2}, j::CartesianIndex{2}, ket::S, - env::CTMRGEnv, + env::AbstractCTMRGEnv, ) where {S <: InfiniteState} return only(correlator(bra, O, i, j:j, ket, env)) end ## reserved for InfinitePEPS -function MPSKit.correlator(state::InfinitePEPS, O, i::CartesianIndex{2}, j, env::CTMRGEnv) +function MPSKit.correlator(state::InfinitePEPS, O, i::CartesianIndex{2}, j, env::AbstractCTMRGEnv) return MPSKit.correlator(state, O, i, j, state, env) end @@ -32,7 +32,7 @@ end function MPSKit.correlator( ρ::InfinitePEPO, O, i::CartesianIndex{2}, js::CoordCollection{2}, - env::CTMRGEnv, + env::AbstractCTMRGEnv, ) return _correlator(PEPOTraceCorrelator(ρ, env), O, i, vec(js)) end @@ -40,7 +40,7 @@ end function MPSKit.correlator( ρ::InfinitePEPO, O, i::CartesianIndex{2}, j::CartesianIndex{2}, - env::CTMRGEnv, + env::AbstractCTMRGEnv, ) return only(correlator(ρ, O, i, j:j, env)) end diff --git a/src/algorithms/expectation_value/expectation_value.jl b/src/algorithms/expectation_value/expectation_value.jl index 7c83aac90..e8d25e3eb 100644 --- a/src/algorithms/expectation_value/expectation_value.jl +++ b/src/algorithms/expectation_value/expectation_value.jl @@ -78,7 +78,7 @@ local_expectation_value(inds, bra, operator::MPOTerm, ket, env::BPEnv) = # ------------------------------------------------------ """ - expectation_value(pf::InfinitePartitionFunction, inds => O, env::CTMRGEnv) + expectation_value(pf::InfinitePartitionFunction, inds => O, env::AbstractCTMRGEnv) Compute the expectation value corresponding to inserting a local tensor(s) `O` at position `inds` in the partition function `pf` and contracting the whole using a given CTMRG diff --git a/src/algorithms/expectation_value/network_value.jl b/src/algorithms/expectation_value/network_value.jl index 0bac1e033..fa0043db5 100644 --- a/src/algorithms/expectation_value/network_value.jl +++ b/src/algorithms/expectation_value/network_value.jl @@ -7,15 +7,15 @@ Return the value (per unit cell) of a given contractible network contracted using a given CTMRG environment. """ -function network_value(network::InfiniteSquareNetwork, env::CTMRGEnv) +function network_value(network::InfiniteSquareNetwork, env::AbstractCTMRGEnv) return prod(Iterators.product(axes(network)...)) do (r, c) return _contract_site((r, c), network, env) * _contract_corners((r, c), env) / _contract_vertical_edges((r, c), env) / _contract_horizontal_edges((r, c), env) end end -network_value(state, env::CTMRGEnv) = network_value(InfiniteSquareNetwork(state), env) +network_value(state, env::AbstractCTMRGEnv) = network_value(InfiniteSquareNetwork(state), env) -function LinearAlgebra.norm(peps::InfinitePEPS, env::CTMRGEnv) +function LinearAlgebra.norm(peps::InfinitePEPS, env::AbstractCTMRGEnv) return network_value(InfiniteSquareNetwork(peps), env) end @@ -29,7 +29,7 @@ Contract a local tensor `O` inserted into a partition function `pf` at position using the environment `env`. """ function contract_local_tensor( - inds::Tuple{Int, Int}, O::PFTensor, env::CTMRGEnv{C, <:CTMRG_PF_EdgeTensor} + inds::Tuple{Int, Int}, O::PFTensor, env::AbstractCTMRGEnv{C, <:CTMRG_PF_EdgeTensor} ) where {C} r, c = inds return _contract_site( @@ -53,7 +53,7 @@ function contract_local_tensor( ind::Tuple{Int, Int, Int}, O::PEPOTensor, network::InfiniteSquareNetwork{<:PEPOSandwich}, - env::CTMRGEnv, + env::AbstractCTMRGEnv, ) r, c, h = ind sandwich´ = Base.setindex(network[r, c], O, h + 2) @@ -68,6 +68,6 @@ function contract_local_tensor( ) end -function contract_local_tensor(inds::CartesianIndex, O::AbstractTensorMap, env::CTMRGEnv) +function contract_local_tensor(inds::CartesianIndex, O::AbstractTensorMap, env::AbstractCTMRGEnv) return contract_local_tensor(Tuple(inds), O, env) end diff --git a/src/algorithms/optimization/implicit_differentiation.jl b/src/algorithms/optimization/implicit_differentiation.jl index 7eeeec356..eedafb971 100644 --- a/src/algorithms/optimization/implicit_differentiation.jl +++ b/src/algorithms/optimization/implicit_differentiation.jl @@ -215,7 +215,7 @@ function _rrule( gradmode::FixedPointGradient, config::RuleConfig, ::typeof(MPSKit.leading_boundary), - envinit::CTMRGEnv, + envinit::AbstractCTMRGEnv, state, alg::CTMRGAlgorithm, ) @@ -471,7 +471,7 @@ function _rrule( gradmode::ImplicitGradient, config::RuleConfig, ::typeof(MPSKit.leading_boundary), - envinit::CTMRGEnv, + envinit::C4vCTMRGEnv, state, alg::C4vCTMRG, ) @@ -499,7 +499,7 @@ function _rrule( u = zeros(storagetype(U), space(UL, numind(UL))' ← space(U, numind(U))') # prepare pullback of C4v CTMRG environment constructor (artefact of reusing asymmetric environment type for C4v symmetric contraction) - _, c4v_env_vjp = rrule_via_ad(config, CTMRGEnv, C, E) + _, c4v_env_vjp = rrule_via_ad(config, C4vCTMRGEnv, C, E) # Two tapes: the linear solve only uses the environment cotangents, so reading the # sandwich through `constant_site` leaves the ket and bra cotangents unforced. The state @@ -620,7 +620,7 @@ function PEPSKit._rrule( gradmode::ImplicitGradient, config::RuleConfig, ::typeof(MPSKit.leading_boundary), - envinit::CTMRGEnv, + envinit::AbstractCTMRGEnv, state, alg::SimultaneousCTMRG{<:HalfInfiniteProjector}, ) diff --git a/src/algorithms/optimization/preconditioning.jl b/src/algorithms/optimization/preconditioning.jl index 26b3d9706..acf3e504f 100644 --- a/src/algorithms/optimization/preconditioning.jl +++ b/src/algorithms/optimization/preconditioning.jl @@ -85,7 +85,7 @@ Apply the regularized local metric ``N / ⟨ψ|ψ⟩ + δ`` to a single PEPS ten ``N`` is obtained by contracting the CTMRG environment `env` around the unit-cell site `(r, c)` and `norm_pref` is the corresponding local norm ``⟨ψ|ψ⟩``. """ -function apply_local_preconditioner(g_rc::PEPSTensor, env::CTMRGEnv, δ, (r, c), norm_pref) +function apply_local_preconditioner(g_rc::PEPSTensor, env::AbstractCTMRGEnv, δ, (r, c), norm_pref) @autoopt @tensor g_rc_prec[d; D_N_below D_E_below D_S_below D_W_below] := g_rc[d; D_N_above D_E_above D_S_above D_W_above] * corner(env, NORTHWEST, r - 1, c - 1)[χ_WNW; χ_NNW] * diff --git a/src/algorithms/select_algorithm.jl b/src/algorithms/select_algorithm.jl index c121fb3fd..2671a8053 100644 --- a/src/algorithms/select_algorithm.jl +++ b/src/algorithms/select_algorithm.jl @@ -134,7 +134,7 @@ end function select_algorithm( ::typeof(leading_boundary), - env₀::CTMRGEnv; + env₀::AbstractCTMRGEnv; alg = Defaults.ctmrg_alg, tol = Defaults.ctmrg_tol, verbosity = Defaults.ctmrg_verbosity, diff --git a/src/algorithms/toolbox.jl b/src/algorithms/toolbox.jl index b825b2f80..cac786ed8 100644 --- a/src/algorithms/toolbox.jl +++ b/src/algorithms/toolbox.jl @@ -31,7 +31,7 @@ end # TODO: decide on appropriate signature and returns for the more generic case """ - correlation_length(state, env::CTMRGEnv; num_vals=2, kwargs...) + correlation_length(state, env::AbstractCTMRGEnv; num_vals=2, kwargs...) Compute the correlation length associated to `state` as contracted using the environment `env`, based on the spectrum of the horizontal and vertical transfer matrices associated to @@ -41,12 +41,12 @@ passed through to `MPSKit.transfer_spectrum` (e.g. allowing to target the correl in a specific symmetry sector). """ -function MPSKit.correlation_length(state, env::CTMRGEnv; num_vals = 2, kwargs...) +function MPSKit.correlation_length(state, env::AbstractCTMRGEnv; num_vals = 2, kwargs...) return _correlation_length(env; num_vals, kwargs...) end function _correlation_length( - env::CTMRGEnv; num_vals = 2, sector = one(sectortype(env)), kwargs... + env::AbstractCTMRGEnv; num_vals = 2, sector = one(sectortype(env)), kwargs... ) _, n_rows, n_cols = size(env) diff --git a/src/algorithms/transfermatrix.jl b/src/algorithms/transfermatrix.jl index cf335c3c0..9f2717b9c 100644 --- a/src/algorithms/transfermatrix.jl +++ b/src/algorithms/transfermatrix.jl @@ -38,7 +38,7 @@ function edge_transfermatrix(a::AbstractVector, b, c::AbstractVector, isflipped end function edge_transfermatrix( - row::Int, col::Int, bra::S, ket::S, env::CTMRGEnv + row::Int, col::Int, bra::S, ket::S, env::AbstractCTMRGEnv ) where {S <: InfiniteState} Etop = edge(env, NORTH, row - 1, col) Ebot = edge(env, SOUTH, row + 1, col) @@ -47,7 +47,7 @@ function edge_transfermatrix( end function edge_transfermatrix( - row::Int, col::Int, ρ::InfinitePEPO, env::CTMRGEnv + row::Int, col::Int, ρ::InfinitePEPO, env::AbstractCTMRGEnv ) Etop = edge(env, NORTH, row - 1, col) Ebot = edge(env, SOUTH, row + 1, col) diff --git a/src/environments/c4v_ctmrg_environments.jl b/src/environments/c4v_ctmrg_environments.jl new file mode 100644 index 000000000..4fec4a6c3 --- /dev/null +++ b/src/environments/c4v_ctmrg_environments.jl @@ -0,0 +1,351 @@ +""" +$(TYPEDEF) + +Corner transfer-matrix environment with C4v symmetry containing unit-cell arrays of corner and edge tensors. +The last two indices of the arrays correspond to the row and column indices of the unit +cell, whereas the first index corresponds to the direction of the corner or edge tensor. +Since the corners are all identical due to the C4v symmetry, the corners array has a first dimension of size 1. +The directions are labeled in clockwise direction, starting from the north edge. + +Given arrays of corners `c` and edges `t`, they connect to the network tensors +`P` at site `(r, c)` in the unit cell as: +``` + c[1,r-1,c-1]---t[1,r-1,c]----c[1,r-1,c+1] + | | | + t[4,r,c-1]-----P[r,c]--------t[2,r,c+1] + | | | + c[1,r+1,c-1]---t[3,r+1,c]----c[1,r+1,c+1] +``` +Here `P` represents an effective local constituent tensor. This can either be a single +rank-4 tensor, a pair of PEPS tensors, or a stack of PEPS-PEPO-PEPS tensors depending on the +network being contracted. + +## Fields + +$(TYPEDFIELDS) +""" +struct C4vCTMRGEnv{C, T} <: AbstractCTMRGEnv{C, T} + "1 x rows x cols array of corner tensors, where the first dimension specifies the spatial direction" + corners::Array{C, 3} + "1 x rows x cols array of edge tensors, where the first dimension specifies the spatial direction" + edges::Array{T, 3} + function C4vCTMRGEnv{C, T}(corners::Array{C, 3}, edges::Array{T, 3}) where {C, T} + return new{C, T}(corners, edges) + end +end +function C4vCTMRGEnv(corners::Array{C, 3}, edges::Array{T, 3}) where {C, T} + foreach(check_environment_virtualspace, edges) + return C4vCTMRGEnv{C, T}(corners, edges) +end + +""" + C4vCTMRGEnv( + [f=randn, T=ComplexF64], Ds_north::A, Ds_east::A, chis::B + ) where {A<:AbstractMatrix{<:VectorSpace}, B<:AbstractMatrix{<:ElementarySpace}} + +Construct a C4v symmetric 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. The virtual +environment spaces for all directions are taken to be the same. + +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 C4vCTMRGEnv( + f, T, Ds::A, chis::B, + ) where { + A <: AbstractMatrix{<:ProductSpace}, B <: AbstractMatrix{<:ElementarySpace}, + } + # check all of the sizes + size(Ds) == size(chis) || throw(ArgumentError("Input spaces should have equal sizes.")) + + # do the whole thing + N = length(first(Ds)) + st = spacetype(first(Ds)) + C_type = tensormaptype(st, 1, 1, T) + T_type = tensormaptype(st, N + 1, 1, T) + + # All corners are identical + corners = Array{C_type}(undef, 1, size(Ds)...) + # Only need one edge tensor + edges = Array{T_type}(undef, 1, size(Ds)...) + + for I in CartesianIndices(Ds) + r, c = I.I + edges[1, r, c] = _edge_tensor( + f, T, chis[r, _prev(c, end)], Ds[_next(r, end), c], chis[r, c] + ) + corners[1, r, c] = _corner_tensor( + f, T, chis[_next(r, end), c], chis[r, c] + ) + end + + corners[1, :, :] ./= norm.(corners[1, :, :]) + edges[1, :, :] ./= norm.(edges[1, :, :]) + return C4vCTMRGEnv(corners, edges) +end +function C4vCTMRGEnv(D::P, args...; kwargs...) where {P <: Union{Matrix{VectorSpace}, VectorSpace}} + return C4vCTMRGEnv(randn, ComplexF64, D, args...; kwargs...) +end + +""" + C4vCTMRGEnv( + [f=randn, T=ComplexF64], D::P, chi::S; + unitcell::Tuple{Int,Int}=(1, 1), + ) where {P<:VectorSpace,S<:ElementarySpace} + +Construct a C4v-symmetric CTMRG environment by specifying the virtual space of the +corresponding [`InfiniteSquareNetwork`](@ref) and the virtual +space of the environment. The network unit cell can be specified by the `unitcell` keyword +argument. By default, the virtual environment spaces for all directions are taken to be the +same. + +The environment virtual spaces for each site correspond to virtual space of the +corresponding edge tensor for each direction. +""" +function C4vCTMRGEnv( + f, T, + D::S, virtual_spaces...; unitcell::Tuple{Int, Int} = (1, 1), + ) where {S <: VectorSpace} + return C4vCTMRGEnv( + f, T, + _fill_edge_physical_spaces(D, D; unitcell)..., + _fill_environment_virtual_spaces(virtual_spaces...; unitcell)..., + ) +end + +""" + C4vCTMRGEnv( + [f=randn, T=ComplexF64], network::InfiniteSquareNetwork, chis::A + ) where {A<:Union{AbstractMatrix{<:ElementarySpace}, ElementarySpace}} + +Construct a C4v-symmetric CTMRG environment by specifying a corresponding +[`InfiniteSquareNetwork`](@ref), and the virtual spaces of the environment. +The virtual spaces can either be specified as matrices of `ElementarySpace`s, +or as individual `ElementarySpace`s which are then filled to match the size of the unit +cell. Each respective matrix entry corresponds to a site in the unit cell. By default, the +virtual spaces for all directions are taken to be the same. + +The environment virtual spaces for each site correspond to the virtual space +of the corresponding edge tensor for each direction. +""" +function C4vCTMRGEnv(f, T, network::InfiniteSquareNetwork, virtual_spaces...) + Ds = _north_edge_physical_spaces(network) + virtual_spaces = _fill_environment_virtual_spaces(virtual_spaces...; unitcell = size(network)) + return C4vCTMRGEnv(f, similarstoragetype(storagetype(network), eltype(T)), Ds, virtual_spaces...) +end +function C4vCTMRGEnv(network::InfiniteSquareNetwork{O}, virtual_spaces...) where {O} + return C4vCTMRGEnv(randn, storagetype(O), network, virtual_spaces...) +end +function C4vCTMRGEnv(network::Union{<:InfinitePartitionFunction, <:InfinitePEPS}, virtual_spaces...) + return C4vCTMRGEnv(randn, storagetype(network), network, virtual_spaces...) +end + +# allow constructing environments for implicitly defined contractible networks +function C4vCTMRGEnv(f, T, state::Union{InfinitePartitionFunction, InfinitePEPS}, args...) + return C4vCTMRGEnv(f, similarstoragetype(eltype(state), eltype(T)), InfiniteSquareNetwork(state), args...) +end + +# copy-like constructor +C4vCTMRGEnv(env::C4vCTMRGEnv) = C4vCTMRGEnv(env.corners, env.edges) + +@non_differentiable C4vCTMRGEnv(state::Union{InfinitePartitionFunction, InfinitePEPS}, args...) + +# Custom adjoint for C4vCTMRGEnv constructor, needed for fixed-point differentiation +function ChainRulesCore.rrule( + ::Type{C4vCTMRGEnv}, corners::Array{C, 3}, edges::Array{T, 3} + ) where {C, T} + c4vctmrgenv_pullback(ē) = NoTangent(), ē.corners, ē.edges + return C4vCTMRGEnv(corners, edges), c4vctmrgenv_pullback +end + +# Custom adjoint for C4vCTMRGEnv getproperty, to avoid creating named tuples in backward pass +function ChainRulesCore.rrule(::typeof(getproperty), e::C4vCTMRGEnv, name::Symbol) + result = getproperty(e, name) + if name === :corners + function corner_pullback(Δcorners_) + Δcorners = unthunk(Δcorners_) + return NoTangent(), C4vCTMRGEnv(Δcorners, zerovector.(e.edges)), NoTangent() + end + return result, corner_pullback + elseif name === :edges + function edge_pullback(Δedges_) + Δedges = unthunk(Δedges_) + return NoTangent(), C4vCTMRGEnv(zerovector.(e.corners), Δedges), NoTangent() + end + return result, edge_pullback + else # this should never happen because already errored in forwards pass + throw(ArgumentError("No rrule for getproperty of $name")) + end +end + +""" + corner(env::C4vCTMRGEnv, dir, i, j) + +Return the corner tensor of `env` at direction `dir` and unit-cell coordinates +`(i, j)`. Due to the C4v symmetry, all the corners are identical. +The coordinates are wrapped periodically (`mod1`); the direction is +passed through unchanged. Mixed `Int`/`CartesianIndex` arguments are supported via +`Base.to_indices`, e.g. `corner(env, NORTHWEST, CartesianIndex(r, c))`. +""" +Base.@propagate_inbounds corner(env::C4vCTMRGEnv, I...) = + periodic_getindex(env, env.corners, I) + +""" + edge(env::C4vCTMRGEnv, dir, i, j) + +Return the edge tensor of `env` at direction `dir` and unit-cell coordinates +`(i, j)`. The coordinates are wrapped periodically (`mod1`); the direction is +passed through unchanged. Mixed `Int`/`CartesianIndex` arguments are supported via +`Base.to_indices`. +""" +Base.@propagate_inbounds edge(env::C4vCTMRGEnv, I...) = + periodic_getindex(env, env.edges, I) + +""" + setcorner!(env::C4vCTMRGEnv, value, dir, i, j) -> value + +Store `value` as the corner tensor of `env` at direction `dir` and unit-cell +coordinates `(i, j)`, with periodic wrapping on `(i, j)`. Returns `value`. +Note that due to the C4v symmetry, all corners are identical, so this modifies +every corner at once! +""" +Base.@propagate_inbounds setcorner!(env::C4vCTMRGEnv, v, I...) = + periodic_setindex!(env, env.corners, v, I) + +""" + setedge!(env::C4vCTMRGEnv, value, dir, i, j) -> value + +Store `value` as the edge tensor of `env` at direction `dir` and unit-cell +coordinates `(i, j)`, with periodic wrapping on `(i, j)`. Returns `value`. +""" +Base.@propagate_inbounds setedge!(env::C4vCTMRGEnv, v, I...) = + periodic_setindex!(env, env.edges, v, I) +function eachcoordinate(x::C4vCTMRGEnv) + return collect(Iterators.product(axes(x, 2), axes(x, 3))) +end +function eachcoordinate(x::C4vCTMRGEnv, dirs) + return collect(Iterators.product(dirs, axes(x, 2), axes(x, 3))) +end +Base.real(env::C4vCTMRGEnv) = C4vCTMRGEnv(real.(env.corners), real.(env.edges)) +Base.complex(env::C4vCTMRGEnv) = C4vCTMRGEnv(complex.(env.corners), complex.(env.edges)) + +cornertype(env::C4vCTMRGEnv) = cornertype(typeof(env)) +cornertype(::Type{C4vCTMRGEnv{C, E}}) where {C, E} = C +edgetype(env::C4vCTMRGEnv) = edgetype(typeof(env)) +edgetype(::Type{C4vCTMRGEnv{C, E}}) where {C, E} = E + +TensorKit.spacetype(::Type{E}) where {E <: C4vCTMRGEnv} = spacetype(cornertype(E)) +function TensorKit.storagetype(::Type{E}) where {E <: C4vCTMRGEnv} + return TensorKit.promote_storagetype(cornertype(E), edgetype(E)) +end + +# In-place update of environment +function update!(env::C4vCTMRGEnv{C, T}, env´::C4vCTMRGEnv{C, T}) where {C, T} + env.corners .= env´.corners + env.edges .= env´.edges + return env +end +# patch for the case where the corners are of a different type, which happens when running +# variational optimization using an implicit gradient +function update!(env::C4vCTMRGEnv{C, T}, env´::C4vCTMRGEnv{C´, T}) where {C, C´, T} + env.corners .= _convert_cornertype.(C, env´.corners) + env.edges .= env´.edges + return env +end + +# Rotate corners & edges counter-clockwise +function Base.rotl90(env::C4vCTMRGEnv{C, T}) where {C, T} + # Initialize rotated corners & edges with rotated sizes + corners′ = Zygote.Buffer( + Array{C, 3}(undef, 1, size(env.corners, 3), size(env.corners, 2)) + ) + edges′ = Zygote.Buffer(Array{T, 3}(undef, 1, size(env.edges, 3), size(env.edges, 2))) + corners′[1, :, :] = env.corners[1, :, :] # all corners are identical! + edges′[1, :, :] = rotl90(env.edges[1, :, :]) + return C4vCTMRGEnv(copy(corners′), copy(edges′)) +end + +# Rotate corners & edges clockwise +function Base.rotr90(env::C4vCTMRGEnv{C, T}) where {C, T} + # Initialize rotated corners & edges with rotated sizes + corners′ = Zygote.Buffer( + Array{C, 3}(undef, 1, size(env.corners, 3), size(env.corners, 2)) + ) + edges′ = Zygote.Buffer(Array{T, 3}(undef, 1, size(env.edges, 3), size(env.edges, 2))) + corners′[1, :, :] = rotr90(env.corners[1, :, :]) # all corners are identical! + edges′[1, :, :] = rotr90(env.edges[1, :, :]) + return C4vCTMRGEnv(copy(corners′), copy(edges′)) +end + +# Rotate corners & edges by 180 degrees +function Base.rot180(env::C4vCTMRGEnv{C, T}) where {C, T} + # Initialize rotated corners & edges with rotated sizes + corners′ = Zygote.Buffer( + Array{C, 3}(undef, 1, size(env.corners, 2), size(env.corners, 3)) + ) + edges′ = Zygote.Buffer(Array{T, 3}(undef, 1, size(env.edges, 2), size(env.edges, 3))) + corners′[1, :, :] = rot180(env.corners[1, :, :]) # all corners are identical! + edges′[1, :, :] = rot180(env.edges[1, :, :]) + return C4vCTMRGEnv(copy(corners′), copy(edges′)) +end + +# Functions used for FP differentiation and by KrylovKit.linsolve +function Base.:+(e₁::C4vCTMRGEnv, e₂::C4vCTMRGEnv) + return C4vCTMRGEnv(e₁.corners + e₂.corners, e₁.edges + e₂.edges) +end + +function Base.:-(e₁::C4vCTMRGEnv, e₂::C4vCTMRGEnv) + return C4vCTMRGEnv(e₁.corners - e₂.corners, e₁.edges - e₂.edges) +end + +Base.:*(α::Number, e::C4vCTMRGEnv) = C4vCTMRGEnv(α * e.corners, α * e.edges) +Base.similar(e::C4vCTMRGEnv) = C4vCTMRGEnv(similar(e.corners), similar(e.edges)) + +# (approximate) equality +function Base.isapprox(env1::C4vCTMRGEnv, env2::C4vCTMRGEnv; kwargs...) + for (c1, c2) in zip(env1.corners, env2.corners) + !isapprox(c1, c2; kwargs...) && return false + end + for (e1, e2) in zip(env1.edges, env2.edges) + !isapprox(e1, e2; kwargs...) && return false + end + return true +end + +# VectorInterface +# --------------- + +# Note: the following methods consider the environment tensors as separate components of one +# big vector. In other words, the associated vector space is not the natural one associated +# to the original (physical) system, and addition, scaling, etc. are performed element-wise. +function VI.scalartype(::Type{C4vCTMRGEnv{C, T}}) where {C, T} + S₁ = scalartype(C) + S₂ = scalartype(T) + return promote_type(S₁, S₂) +end +function VI.zerovector(env::C4vCTMRGEnv, ::Type{S}) where {S <: Number} + _zerovector = Base.Fix2(zerovector, S) + return C4vCTMRGEnv(map(_zerovector, env.corners), map(_zerovector, env.edges)) +end + +function VI.scale(env::C4vCTMRGEnv, α::Number) + _scale = Base.Fix2(scale, α) + return C4vCTMRGEnv(map(_scale, env.corners), map(_scale, env.edges)) +end + +function VI.add(env₁::C4vCTMRGEnv, env₂::C4vCTMRGEnv, α::Number, β::Number) + _add(x, y) = add(x, y, α, β) + return C4vCTMRGEnv( + map(_add, env₁.corners, env₂.corners), map(_add, env₁.edges, env₂.edges) + ) +end +function VI.add!(env₁::C4vCTMRGEnv, env₂::C4vCTMRGEnv, α::Number, β::Number) + _add!(x, y) = add!(x, y, α, β) + foreach(_add!, env₁.corners, env₂.corners) + foreach(_add!, env₁.edges, env₂.edges) + return env₁ +end +VI.add!!(env₁::C4vCTMRGEnv, env₂::C4vCTMRGEnv, α::Number, β::Number) = add!(env₁, env₂, α, β) diff --git a/src/environments/ctmrg_environments.jl b/src/environments/ctmrg_environments.jl index 1b9308244..4682b5bb0 100644 --- a/src/environments/ctmrg_environments.jl +++ b/src/environments/ctmrg_environments.jl @@ -1,6 +1,9 @@ const CTMRGEdgeTensor{T, S, N} = AbstractTensorMap{T, S, N, 1} const CTMRGCornerTensor{T, S} = AbstractTensorMap{T, S, 1, 1} + +abstract type AbstractCTMRGEnv{C, T} end + """ $(TYPEDEF) @@ -27,7 +30,7 @@ network being contracted. $(TYPEDFIELDS) """ -struct CTMRGEnv{C, T} +struct CTMRGEnv{C, T} <: AbstractCTMRGEnv{C, T} "4 x rows x cols array of corner tensors, where the first dimension specifies the spatial direction" corners::Array{C, 3} "4 x rows x cols array of edge tensors, where the first dimension specifies the spatial direction" @@ -238,7 +241,7 @@ CTMRGEnv(env::CTMRGEnv) = CTMRGEnv(env.corners, env.edges) @non_differentiable CTMRGEnv(state::Union{InfinitePartitionFunction, InfinitePEPS}, args...) -function TensorKit.storagetype(::Type{CTMRGEnv{C, E}}) where {C, E} +function TensorKit.storagetype(::Type{AbstractCTMRGEnv{C, E}}) where {C, E} T = promote_type(scalartype(C), scalartype(E)) return TensorKit.promote_storagetype(T, C, E) end @@ -271,8 +274,8 @@ function ChainRulesCore.rrule(::typeof(getproperty), e::CTMRGEnv, name::Symbol) end end -Base.size(env::CTMRGEnv, args...) = size(env.corners, args...) -Base.axes(x::CTMRGEnv, args...) = axes(x.corners, args...) +Base.size(env::AbstractCTMRGEnv, args...) = size(env.corners, args...) +Base.axes(x::AbstractCTMRGEnv, args...) = axes(x.corners, args...) """ corner(env::CTMRGEnv, dir, i, j) @@ -282,7 +285,7 @@ Return the corner tensor of `env` at direction `dir` and unit-cell coordinates passed through unchanged. Mixed `Int`/`CartesianIndex` arguments are supported via `Base.to_indices`, e.g. `corner(env, NORTHWEST, CartesianIndex(r, c))`. """ -Base.@propagate_inbounds corner(env::CTMRGEnv, I...) = +Base.@propagate_inbounds corner(env::AbstractCTMRGEnv, I...) = periodic_getindex(env, env.corners, I) """ @@ -293,7 +296,7 @@ Return the edge tensor of `env` at direction `dir` and unit-cell coordinates passed through unchanged. Mixed `Int`/`CartesianIndex` arguments are supported via `Base.to_indices`. """ -Base.@propagate_inbounds edge(env::CTMRGEnv, I...) = +Base.@propagate_inbounds edge(env::AbstractCTMRGEnv, I...) = periodic_getindex(env, env.edges, I) """ @@ -302,7 +305,7 @@ Base.@propagate_inbounds edge(env::CTMRGEnv, I...) = Store `value` as the corner tensor of `env` at direction `dir` and unit-cell coordinates `(i, j)`, with periodic wrapping on `(i, j)`. Returns `value`. """ -Base.@propagate_inbounds setcorner!(env::CTMRGEnv, v, I...) = +Base.@propagate_inbounds setcorner!(env::AbstractCTMRGEnv, v, I...) = periodic_setindex!(env, env.corners, v, I) """ @@ -311,24 +314,24 @@ Base.@propagate_inbounds setcorner!(env::CTMRGEnv, v, I...) = Store `value` as the edge tensor of `env` at direction `dir` and unit-cell coordinates `(i, j)`, with periodic wrapping on `(i, j)`. Returns `value`. """ -Base.@propagate_inbounds setedge!(env::CTMRGEnv, v, I...) = +Base.@propagate_inbounds setedge!(env::AbstractCTMRGEnv, v, I...) = periodic_setindex!(env, env.edges, v, I) -function eachcoordinate(x::CTMRGEnv) +function eachcoordinate(x::AbstractCTMRGEnv) return collect(Iterators.product(axes(x, 2), axes(x, 3))) end -function eachcoordinate(x::CTMRGEnv, dirs) +function eachcoordinate(x::AbstractCTMRGEnv, dirs) return collect(Iterators.product(dirs, axes(x, 2), axes(x, 3))) end Base.real(env::CTMRGEnv) = CTMRGEnv(real.(env.corners), real.(env.edges)) Base.complex(env::CTMRGEnv) = CTMRGEnv(complex.(env.corners), complex.(env.edges)) -cornertype(env::CTMRGEnv) = cornertype(typeof(env)) -cornertype(::Type{CTMRGEnv{C, E}}) where {C, E} = C -edgetype(env::CTMRGEnv) = edgetype(typeof(env)) -edgetype(::Type{CTMRGEnv{C, E}}) where {C, E} = E +cornertype(env::AbstractCTMRGEnv) = cornertype(typeof(env)) +cornertype(::Type{<:AbstractCTMRGEnv{C, E}}) where {C, E} = C +edgetype(env::AbstractCTMRGEnv) = edgetype(typeof(env)) +edgetype(::Type{<:AbstractCTMRGEnv{C, E}}) where {C, E} = E -TensorKit.spacetype(::Type{E}) where {E <: CTMRGEnv} = spacetype(cornertype(E)) -function TensorKit.storagetype(::Type{E}) where {E <: CTMRGEnv} +TensorKit.spacetype(::Type{E}) where {E <: AbstractCTMRGEnv} = spacetype(cornertype(E)) +function TensorKit.storagetype(::Type{E}) where {E <: AbstractCTMRGEnv} return TensorKit.promote_storagetype(cornertype(E), edgetype(E)) end @@ -425,7 +428,7 @@ end # big vector. In other words, the associated vector space is not the natural one associated # to the original (physical) system, and addition, scaling, etc. are performed element-wise. -function VI.scalartype(::Type{CTMRGEnv{C, T}}) where {C, T} +function VI.scalartype(::Type{<:AbstractCTMRGEnv{C, T}}) where {C, T} S₁ = scalartype(C) S₂ = scalartype(T) return promote_type(S₁, S₂) @@ -435,48 +438,48 @@ function VI.zerovector(env::CTMRGEnv, ::Type{S}) where {S <: Number} _zerovector = Base.Fix2(zerovector, S) return CTMRGEnv(map(_zerovector, env.corners), map(_zerovector, env.edges)) end -function VI.zerovector!(env::CTMRGEnv) +function VI.zerovector!(env::AbstractCTMRGEnv) foreach(zerovector!, env.corners) foreach(zerovector!, env.edges) return env end -VI.zerovector!!(env::CTMRGEnv) = zerovector!(env) +VI.zerovector!!(env::AbstractCTMRGEnv) = zerovector!(env) function VI.scale(env::CTMRGEnv, α::Number) _scale = Base.Fix2(scale, α) return CTMRGEnv(map(_scale, env.corners), map(_scale, env.edges)) end -function VI.scale!(env::CTMRGEnv, α::Number) +function VI.scale!(env::AbstractCTMRGEnv, α::Number) _scale! = Base.Fix2(scale!, α) foreach(_scale!, env.corners) foreach(_scale!, env.edges) return env end -function VI.scale!(env₁::CTMRGEnv, env₂::CTMRGEnv, α::Number) +function VI.scale!(env₁::AbstractCTMRGEnv, env₂::AbstractCTMRGEnv, α::Number) _scale!(x, y) = scale!(x, y, α) foreach(_scale!, env₁.corners, env₂.corners) foreach(_scale!, env₁.edges, env₂.edges) return env₁ end -VI.scale!!(env::CTMRGEnv, α::Number) = scale!(env, α) -VI.scale!!(env₁::CTMRGEnv, env₂::CTMRGEnv, α::Number) = scale!(env₁, env₂, α) +VI.scale!!(env::AbstractCTMRGEnv, α::Number) = scale!(env, α) +VI.scale!!(env₁::AbstractCTMRGEnv, env₂::AbstractCTMRGEnv, α::Number) = scale!(env₁, env₂, α) -function VI.add(env₁::CTMRGEnv, env₂::CTMRGEnv, α::Number, β::Number) +function VI.add(env₁::AbstractCTMRGEnv, env₂::AbstractCTMRGEnv, α::Number, β::Number) _add(x, y) = add(x, y, α, β) return CTMRGEnv( map(_add, env₁.corners, env₂.corners), map(_add, env₁.edges, env₂.edges) ) end -function VI.add!(env₁::CTMRGEnv, env₂::CTMRGEnv, α::Number, β::Number) +function VI.add!(env₁::AbstractCTMRGEnv, env₂::AbstractCTMRGEnv, α::Number, β::Number) _add!(x, y) = add!(x, y, α, β) foreach(_add!, env₁.corners, env₂.corners) foreach(_add!, env₁.edges, env₂.edges) return env₁ end -VI.add!!(env₁::CTMRGEnv, env₂::CTMRGEnv, α::Number, β::Number) = add!(env₁, env₂, α, β) +VI.add!!(env₁::AbstractCTMRGEnv, env₂::AbstractCTMRGEnv, α::Number, β::Number) = add!(env₁, env₂, α, β) # Exploiting the fact that VectorInterface works for tuples: -function VI.inner(env₁::CTMRGEnv, env₂::CTMRGEnv) +function VI.inner(env₁::AbstractCTMRGEnv, env₂::AbstractCTMRGEnv) return inner((env₁.corners, env₁.edges), (env₂.corners, env₂.edges)) end -VI.norm(env::CTMRGEnv) = norm((env.corners, env.edges)) +VI.norm(env::AbstractCTMRGEnv) = norm((env.corners, env.edges)) diff --git a/test/testsuite/ctmrg/c4v_sign_branches.jl b/test/testsuite/ctmrg/c4v_sign_branches.jl index 3f8311d50..3c8e1df1f 100644 --- a/test/testsuite/ctmrg/c4v_sign_branches.jl +++ b/test/testsuite/ctmrg/c4v_sign_branches.jl @@ -37,7 +37,7 @@ function sign_twin(env) E = env.edges[1] g = charge_parity(space(E, 2)) @tensor Ê[χ_out D_ket D_bra; χ_in] := E[χ_out D_ket′ D_bra; χ_in] * g[D_ket; D_ket′] - return CTMRGEnv(env.corners[1], Ê) + return C4vCTMRGEnv(env.corners[1], Ê) end function ctmrg_c4v_sign_branches(AT) diff --git a/test/testsuite/ctmrg/fixed_iterscheme.jl b/test/testsuite/ctmrg/fixed_iterscheme.jl index f0f982283..de8676a96 100644 --- a/test/testsuite/ctmrg/fixed_iterscheme.jl +++ b/test/testsuite/ctmrg/fixed_iterscheme.jl @@ -95,7 +95,7 @@ function ctmrg_fixed_iterscheme_c4v(AT; eigh_alg = :QRIteration) # fix gauge of single iteration signs, corner_phases, edge_phases = compute_gauge_fix_gauge(env_conv2, env_conv1, ScramblingEnvGaugeC4v()) - gauge_fixed_iteration(env::CTMRGEnv) = fix_phases( + gauge_fixed_iteration(env::PEPSKit.AbstractCTMRGEnv) = fix_phases( ctmrg_iteration(n, env, ctm_alg)[1], signs, corner_phases, edge_phases, ) diff --git a/test/testsuite/ctmrg/flavors.jl b/test/testsuite/ctmrg/flavors.jl index a849124a9..234fc2cd0 100644 --- a/test/testsuite/ctmrg/flavors.jl +++ b/test/testsuite/ctmrg/flavors.jl @@ -99,6 +99,6 @@ function ctmrg_flavors_c4v(AT; eigh_alg = :QRIteration, minimal::Bool = false) env₀, peps; alg = :C4vCTMRG, projector_alg, decomposition_alg = (; alg = decomp_alg) ) - @test env isa CTMRGEnv + @test env isa C4vCTMRGEnv end end From 74d994c92f0f604b1aa9cae363409efcd7f6cc31 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Mon, 26 Oct 2026 12:51:43 +0100 Subject: [PATCH 2/8] Comments --- .../contractions/ctmrg/network_value.jl | 1 + src/algorithms/ctmrg/gaugefix.jl | 22 ++++------ .../optimization/implicit_differentiation.jl | 1 - src/algorithms/toolbox.jl | 1 + src/environments/c4v_ctmrg_environments.jl | 40 +++++++++++-------- test/testsuite/ctmrg/c4v_sign_branches.jl | 4 +- test/testsuite/ctmrg/fixed_iterscheme.jl | 7 ++-- test/testsuite/ctmrg/gaugefix.jl | 5 ++- 8 files changed, 41 insertions(+), 40 deletions(-) diff --git a/src/algorithms/contractions/ctmrg/network_value.jl b/src/algorithms/contractions/ctmrg/network_value.jl index 510084a5d..eb757f4d3 100644 --- a/src/algorithms/contractions/ctmrg/network_value.jl +++ b/src/algorithms/contractions/ctmrg/network_value.jl @@ -90,6 +90,7 @@ end function _contract_site(ind::Tuple{Int, Int}, network::InfiniteSquareNetwork, env::C4vCTMRGEnv) r, c = ind + @assert r == c == 1 lazy"only trivial unit cells are supported for C4v-CTMRG, ind ($ind) must be (1, 1)" return _contract_site( corner(env, NORTHWEST, r - 1, c - 1), corner(env, NORTHEAST, r - 1, c + 1), diff --git a/src/algorithms/ctmrg/gaugefix.jl b/src/algorithms/ctmrg/gaugefix.jl index 152a3a42c..f6bf3463f 100644 --- a/src/algorithms/ctmrg/gaugefix.jl +++ b/src/algorithms/ctmrg/gaugefix.jl @@ -10,14 +10,6 @@ asymmetric CTMRG algorithms using multi-site unit cell transfer matrices. """ struct ScramblingEnvGauge end -""" -$(TYPEDEF) - -C4v-symmetric equivalent of the [`ScramblingEnvGauge`](@ref) environment gauge fixing -algorithm. -""" -struct ScramblingEnvGaugeC4v end - """ $(SIGNATURES) @@ -27,15 +19,15 @@ Given that the CTMRG run is converged, the returned environment will be element-wise converged to `envprev`. """ function gauge_fix( - envfinal::AbstractCTMRGEnv{C, T}, envprev::AbstractCTMRGEnv{C, T}, alg::G - ) where {C, T, G <: Union{ScramblingEnvGauge, ScramblingEnvGaugeC4v}} + envfinal::AbstractCTMRGEnv{C, T}, envprev::AbstractCTMRGEnv{C, T}, alg::ScramblingEnvGauge + ) where {C, T} signs, corner_phases, edge_phases = compute_gauge_fix_gauge(envfinal, envprev, alg) return fix_phases(envfinal, signs, corner_phases, edge_phases) end function compute_gauge_fix_gauge( - envfinal::CTMRGEnv{C, T}, envprev::CTMRGEnv{C, T}, alg::G - ) where {C, T, G <: Union{ScramblingEnvGauge, ScramblingEnvGaugeC4v}} + envfinal::CTMRGEnv{C, T}, envprev::CTMRGEnv{C, T}, alg::ScramblingEnvGauge + ) where {C, T} # Check if spaces in envprev and envfinal are the same same_spaces = map(eachcoordinate(envfinal, 1:4)) do (dir, r, c) space(envfinal.edges[dir, r, c]) == space(envprev.edges[dir, r, c]) && @@ -55,8 +47,8 @@ function compute_gauge_fix_gauge( end function compute_gauge_fix_gauge( - envfinal::C4vCTMRGEnv{C, T}, envprev::C4vCTMRGEnv{C, T}, alg::G - ) where {C, T, G <: Union{ScramblingEnvGauge, ScramblingEnvGaugeC4v}} + envfinal::C4vCTMRGEnv{C, T}, envprev::C4vCTMRGEnv{C, T}, alg::ScramblingEnvGauge + ) where {C, T} # Check if spaces in envprev and envfinal are the same same_spaces = map(eachcoordinate(envfinal, 1:4)) do (dir, r, c) space(envfinal.edges[dir, r, c]) == space(envprev.edges[dir, r, c]) && @@ -121,7 +113,7 @@ function compute_relative_phases( return signs end -function compute_relative_phases(envfinal::AbstractCTMRGEnv{C, T}, envprev::AbstractCTMRGEnv{C, T}, ::ScramblingEnvGaugeC4v) where {C, T} +function compute_relative_phases(envfinal::C4vCTMRGEnv{C, T}, envprev::C4vCTMRGEnv{C, T}, ::ScramblingEnvGauge) where {C, T} # "general" algorithm from https://arxiv.org/abs/2311.11894 Tprev = envprev.edges[1, 1, 1] Tfinal = envfinal.edges[1, 1, 1] diff --git a/src/algorithms/optimization/implicit_differentiation.jl b/src/algorithms/optimization/implicit_differentiation.jl index eedafb971..997f944c9 100644 --- a/src/algorithms/optimization/implicit_differentiation.jl +++ b/src/algorithms/optimization/implicit_differentiation.jl @@ -203,7 +203,6 @@ Evaluating the gradient of the cost function for CTMRG: =# _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()) diff --git a/src/algorithms/toolbox.jl b/src/algorithms/toolbox.jl index cac786ed8..85e9e092f 100644 --- a/src/algorithms/toolbox.jl +++ b/src/algorithms/toolbox.jl @@ -30,6 +30,7 @@ end # TODO: decide on appropriate signature and returns for the more generic case +# TODO: add a C4v specific method here """ correlation_length(state, env::AbstractCTMRGEnv; num_vals=2, kwargs...) diff --git a/src/environments/c4v_ctmrg_environments.jl b/src/environments/c4v_ctmrg_environments.jl index 4fec4a6c3..13c335251 100644 --- a/src/environments/c4v_ctmrg_environments.jl +++ b/src/environments/c4v_ctmrg_environments.jl @@ -3,7 +3,7 @@ $(TYPEDEF) Corner transfer-matrix environment with C4v symmetry containing unit-cell arrays of corner and edge tensors. The last two indices of the arrays correspond to the row and column indices of the unit -cell, whereas the first index corresponds to the direction of the corner or edge tensor. +cell, which for C4v CTMRG *must* be 1, whereas the first index corresponds to the direction of the corner or edge tensor. Since the corners are all identical due to the C4v symmetry, the corners array has a first dimension of size 1. The directions are labeled in clockwise direction, starting from the north edge. @@ -25,9 +25,9 @@ network being contracted. $(TYPEDFIELDS) """ struct C4vCTMRGEnv{C, T} <: AbstractCTMRGEnv{C, T} - "1 x rows x cols array of corner tensors, where the first dimension specifies the spatial direction" + "1 x 1 x 1 array of corner tensor, kept 3D for compatibility with generic CTMRG methods" corners::Array{C, 3} - "1 x rows x cols array of edge tensors, where the first dimension specifies the spatial direction" + "4 x 1 x 1 array of edge tensors, where the first dimension specifies the spatial direction" edges::Array{T, 3} function C4vCTMRGEnv{C, T}(corners::Array{C, 3}, edges::Array{T, 3}) where {C, T} return new{C, T}(corners, edges) @@ -60,6 +60,7 @@ function C4vCTMRGEnv( } # check all of the sizes size(Ds) == size(chis) || throw(ArgumentError("Input spaces should have equal sizes.")) + length(Ds) == 1 || throw(ArgumentError("C4v CTMRG only supports trivial unit cells.")) # do the whole thing N = length(first(Ds)) @@ -69,21 +70,28 @@ function C4vCTMRGEnv( # All corners are identical corners = Array{C_type}(undef, 1, size(Ds)...) - # Only need one edge tensor - edges = Array{T_type}(undef, 1, size(Ds)...) - - for I in CartesianIndices(Ds) - r, c = I.I - edges[1, r, c] = _edge_tensor( - f, T, chis[r, _prev(c, end)], Ds[_next(r, end), c], chis[r, c] - ) - corners[1, r, c] = _corner_tensor( - f, T, chis[_next(r, end), c], chis[r, c] - ) - end + # Four edge tensors are related with flipped virtual spaces + edges = Array{T_type}(undef, 4, size(Ds)...) + + r, c = (1, 1) + edges[NORTH, r, c] = _edge_tensor( + f, T, chis[r, _prev(c, end)], Ds[_next(r, end), c], chis[r, c] + ) + edges[EAST, r, c] = _edge_tensor( + f, T, chis[r, c], Ds[r, _prev(c, end)], chis[_next(r, end), c] + ) + edges[SOUTH, r, c] = _edge_tensor( + f, T, chis[r, c], Ds[_prev(r, end), c], chis[r, _prev(c, end)] + ) + edges[WEST, r, c] = _edge_tensor( + f, T, chis[_next(r, end), c], Ds[r, _next(c, end)], chis[r, c] + ) + corners[1, r, c] = _corner_tensor( + f, T, chis[_next(r, end), c], chis[r, c] + ) corners[1, :, :] ./= norm.(corners[1, :, :]) - edges[1, :, :] ./= norm.(edges[1, :, :]) + edges[:, :, :] ./= norm.(edges[:, :, :]) return C4vCTMRGEnv(corners, edges) end function C4vCTMRGEnv(D::P, args...; kwargs...) where {P <: Union{Matrix{VectorSpace}, VectorSpace}} diff --git a/test/testsuite/ctmrg/c4v_sign_branches.jl b/test/testsuite/ctmrg/c4v_sign_branches.jl index 3c8e1df1f..4b2567464 100644 --- a/test/testsuite/ctmrg/c4v_sign_branches.jl +++ b/test/testsuite/ctmrg/c4v_sign_branches.jl @@ -4,7 +4,7 @@ using LinearAlgebra using TensorKit using PEPSKit using Adapt -using PEPSKit: ctmrg_iteration, compute_gauge_fix_gauge, fix_relative_phases, ScramblingEnvGaugeC4v +using PEPSKit: ctmrg_iteration, compute_gauge_fix_gauge, fix_relative_phases, ScramblingEnvGauge # For half-integer U(1) charges, (-1)^{2Q} on the ket bonds negates the sandwich, so fixed # points come in pairs, one of which flips (C, E) to (-C, -E) every iteration. Both should converge. @@ -28,7 +28,7 @@ end # sign by which one gauge-fixed iteration multiplies the edge function iteration_sign(network, env, alg) env′, = ctmrg_iteration(network, env, alg) - signs, = compute_gauge_fix_gauge(env′, env, ScramblingEnvGaugeC4v()) + signs, = compute_gauge_fix_gauge(env′, env, ScramblingEnvGauge()) env′ = fix_relative_phases(env′, signs) return sign(real(dot(env.edges[1], env′.edges[1]))) end diff --git a/test/testsuite/ctmrg/fixed_iterscheme.jl b/test/testsuite/ctmrg/fixed_iterscheme.jl index de8676a96..835b8cfb5 100644 --- a/test/testsuite/ctmrg/fixed_iterscheme.jl +++ b/test/testsuite/ctmrg/fixed_iterscheme.jl @@ -13,8 +13,7 @@ using PEPSKit: fix_relative_phases, calc_elementwise_convergence, peps_normalize, - ScramblingEnvGauge, - ScramblingEnvGaugeC4v + ScramblingEnvGauge using PEPSKit.Defaults: ctmrg_tol # initialize parameters @@ -87,14 +86,14 @@ function ctmrg_fixed_iterscheme_c4v(AT; eigh_alg = :QRIteration) # do extra iteration to check gauge fixing env_conv2, info = @testinferred ctmrg_iteration(n, env_conv1, ctm_alg) # CHECK - env_fixed = gauge_fix(env_conv2, env_conv1, ScramblingEnvGaugeC4v()) + env_fixed = gauge_fix(env_conv2, env_conv1, ScramblingEnvGauge()) env_diff = calc_elementwise_convergence(env_conv1, env_fixed) @info "Diff between iters = $(env_diff)" @test env_diff ≈ 0 atol = atol # fix gauge of single iteration signs, corner_phases, edge_phases = - compute_gauge_fix_gauge(env_conv2, env_conv1, ScramblingEnvGaugeC4v()) + compute_gauge_fix_gauge(env_conv2, env_conv1, ScramblingEnvGauge()) gauge_fixed_iteration(env::PEPSKit.AbstractCTMRGEnv) = fix_phases( ctmrg_iteration(n, env, ctm_alg)[1], signs, corner_phases, edge_phases, diff --git a/test/testsuite/ctmrg/gaugefix.jl b/test/testsuite/ctmrg/gaugefix.jl index 82f6ebe23..f1ee6bde1 100644 --- a/test/testsuite/ctmrg/gaugefix.jl +++ b/test/testsuite/ctmrg/gaugefix.jl @@ -4,7 +4,7 @@ using PEPSKit using TensorKit using Adapt using PEPSKit: ctmrg_iteration, calc_elementwise_convergence -using PEPSKit: ScramblingEnvGauge, ScramblingEnvGaugeC4v +using PEPSKit: ScramblingEnvGauge using PEPSKit: peps_normalize spacetypes = [ComplexSpace, Z2Space] @@ -14,7 +14,7 @@ ctmrg_algs_asymm = [SequentialCTMRG, SimultaneousCTMRG] projector_algs_asymm = [:HalfInfiniteProjector, :FullInfiniteProjector] projector_algs_c4v = [:C4vEighProjector, :C4vQRProjector] gauge_algs_asymm = [ScramblingEnvGauge()] -gauge_algs_c4v = [ScramblingEnvGaugeC4v()] +gauge_algs_c4v = [ScramblingEnvGauge()] # minimal subsets of the combinations above which still cover every option value at least once minimal_combinations_asymm = [ @@ -26,6 +26,7 @@ minimal_combinations_c4v = [ (ComplexSpace, Float64, :C4vEighProjector, ScramblingEnvGaugeC4v()), (Z2Space, ComplexF64, :C4vQRProjector, ScramblingEnvGaugeC4v()), ] + tol = 1.0e-6 # large tol due to χ=6 χ = 6 atol = 1.0e-4 From 5e9480374ee20fa6f3ca51369f5731694692d4be Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Mon, 26 Oct 2026 12:57:19 +0100 Subject: [PATCH 3/8] Formatter --- src/algorithms/toolbox.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/algorithms/toolbox.jl b/src/algorithms/toolbox.jl index 85e9e092f..f88ba8e2b 100644 --- a/src/algorithms/toolbox.jl +++ b/src/algorithms/toolbox.jl @@ -30,7 +30,7 @@ end # TODO: decide on appropriate signature and returns for the more generic case -# TODO: add a C4v specific method here +# TODO: add a C4v specific method here """ correlation_length(state, env::AbstractCTMRGEnv; num_vals=2, kwargs...) From 8d434279444014b64f35488befb075984ba0f5ca Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Tue, 27 Oct 2026 05:12:08 -0400 Subject: [PATCH 4/8] Rebase typo --- test/testsuite/ctmrg/gaugefix.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/testsuite/ctmrg/gaugefix.jl b/test/testsuite/ctmrg/gaugefix.jl index f1ee6bde1..823610616 100644 --- a/test/testsuite/ctmrg/gaugefix.jl +++ b/test/testsuite/ctmrg/gaugefix.jl @@ -23,8 +23,8 @@ minimal_combinations_asymm = [ (ComplexSpace, ComplexF64, (3, 2), SimultaneousCTMRG, :HalfInfiniteProjector, ScramblingEnvGauge()), ] minimal_combinations_c4v = [ - (ComplexSpace, Float64, :C4vEighProjector, ScramblingEnvGaugeC4v()), - (Z2Space, ComplexF64, :C4vQRProjector, ScramblingEnvGaugeC4v()), + (ComplexSpace, Float64, :C4vEighProjector, ScramblingEnvGauge()), + (Z2Space, ComplexF64, :C4vQRProjector, ScramblingEnvGauge()), ] tol = 1.0e-6 # large tol due to χ=6 From b729924c88c113f658e740b8e79811f64db31617 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Tue, 27 Oct 2026 14:11:07 -0400 Subject: [PATCH 5/8] Add an adapt method for C4v envs --- ext/PEPSKitAdaptExt.jl | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/ext/PEPSKitAdaptExt.jl b/ext/PEPSKitAdaptExt.jl index 23df36a83..069a8a598 100644 --- a/ext/PEPSKitAdaptExt.jl +++ b/ext/PEPSKitAdaptExt.jl @@ -29,4 +29,10 @@ function Adapt.adapt_structure(to, x::PEPSKit.CTMRGEnv{C, T}) where {C, T} return CTMRGEnv{eltype(C′), eltype(T′)}(C′, T′) end +function Adapt.adapt_structure(to, x::PEPSKit.C4vCTMRGEnv{C, T}) where {C, T} + C′ = map(c -> adapt(to, c), x.corners) + T′ = map(t -> adapt(to, t), x.edges) + return C4vCTMRGEnv{eltype(C′), eltype(T′)}(C′, T′) +end + end From e1cdf63590bc45f09327b1040a509ae176873f84 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Wed, 28 Oct 2026 05:34:01 -0400 Subject: [PATCH 6/8] More tests and some fixes --- src/environments/c4v_ctmrg_environments.jl | 33 ++-- src/environments/ctmrg_environments.jl | 5 +- test/ctmrg/c4v_contractions.jl | 24 +++ test/testsuite/TestSuite.jl | 7 + test/testsuite/ctmrg/c4v_contractions.jl | 186 +++++++++++++++++++++ test/types/adapt.jl | 14 ++ 6 files changed, 248 insertions(+), 21 deletions(-) create mode 100644 test/ctmrg/c4v_contractions.jl create mode 100644 test/testsuite/ctmrg/c4v_contractions.jl diff --git a/src/environments/c4v_ctmrg_environments.jl b/src/environments/c4v_ctmrg_environments.jl index 13c335251..34de95dda 100644 --- a/src/environments/c4v_ctmrg_environments.jl +++ b/src/environments/c4v_ctmrg_environments.jl @@ -240,16 +240,6 @@ end Base.real(env::C4vCTMRGEnv) = C4vCTMRGEnv(real.(env.corners), real.(env.edges)) Base.complex(env::C4vCTMRGEnv) = C4vCTMRGEnv(complex.(env.corners), complex.(env.edges)) -cornertype(env::C4vCTMRGEnv) = cornertype(typeof(env)) -cornertype(::Type{C4vCTMRGEnv{C, E}}) where {C, E} = C -edgetype(env::C4vCTMRGEnv) = edgetype(typeof(env)) -edgetype(::Type{C4vCTMRGEnv{C, E}}) where {C, E} = E - -TensorKit.spacetype(::Type{E}) where {E <: C4vCTMRGEnv} = spacetype(cornertype(E)) -function TensorKit.storagetype(::Type{E}) where {E <: C4vCTMRGEnv} - return TensorKit.promote_storagetype(cornertype(E), edgetype(E)) -end - # In-place update of environment function update!(env::C4vCTMRGEnv{C, T}, env´::C4vCTMRGEnv{C, T}) where {C, T} env.corners .= env´.corners @@ -270,9 +260,12 @@ function Base.rotl90(env::C4vCTMRGEnv{C, T}) where {C, T} corners′ = Zygote.Buffer( Array{C, 3}(undef, 1, size(env.corners, 3), size(env.corners, 2)) ) - edges′ = Zygote.Buffer(Array{T, 3}(undef, 1, size(env.edges, 3), size(env.edges, 2))) - corners′[1, :, :] = env.corners[1, :, :] # all corners are identical! - edges′[1, :, :] = rotl90(env.edges[1, :, :]) + edges′ = Zygote.Buffer(Array{T, 3}(undef, 4, size(env.edges, 3), size(env.edges, 2))) + corners′[1, :, :] = rotl90(env.corners[1, :, :]) # all corners are identical! + for dir in 1:4 + dir2 = _prev(dir, 4) + edges′[dir2, :, :] = rotl90(env.edges[dir, :, :]) + end return C4vCTMRGEnv(copy(corners′), copy(edges′)) end @@ -282,9 +275,12 @@ function Base.rotr90(env::C4vCTMRGEnv{C, T}) where {C, T} corners′ = Zygote.Buffer( Array{C, 3}(undef, 1, size(env.corners, 3), size(env.corners, 2)) ) - edges′ = Zygote.Buffer(Array{T, 3}(undef, 1, size(env.edges, 3), size(env.edges, 2))) + edges′ = Zygote.Buffer(Array{T, 3}(undef, 4, size(env.edges, 3), size(env.edges, 2))) corners′[1, :, :] = rotr90(env.corners[1, :, :]) # all corners are identical! - edges′[1, :, :] = rotr90(env.edges[1, :, :]) + for dir in 1:4 + dir2 = _next(dir, 4) + edges′[dir2, :, :] = rotr90(env.edges[dir, :, :]) + end return C4vCTMRGEnv(copy(corners′), copy(edges′)) end @@ -294,9 +290,12 @@ function Base.rot180(env::C4vCTMRGEnv{C, T}) where {C, T} corners′ = Zygote.Buffer( Array{C, 3}(undef, 1, size(env.corners, 2), size(env.corners, 3)) ) - edges′ = Zygote.Buffer(Array{T, 3}(undef, 1, size(env.edges, 2), size(env.edges, 3))) + edges′ = Zygote.Buffer(Array{T, 3}(undef, 4, size(env.edges, 2), size(env.edges, 3))) corners′[1, :, :] = rot180(env.corners[1, :, :]) # all corners are identical! - edges′[1, :, :] = rot180(env.edges[1, :, :]) + for dir in 1:4 + dir2 = _next(_next(dir, 4), 4) + edges′[dir2, :, :] = rot180(env.edges[dir, :, :]) + end return C4vCTMRGEnv(copy(corners′), copy(edges′)) end diff --git a/src/environments/ctmrg_environments.jl b/src/environments/ctmrg_environments.jl index 4682b5bb0..50b394b6d 100644 --- a/src/environments/ctmrg_environments.jl +++ b/src/environments/ctmrg_environments.jl @@ -241,7 +241,7 @@ CTMRGEnv(env::CTMRGEnv) = CTMRGEnv(env.corners, env.edges) @non_differentiable CTMRGEnv(state::Union{InfinitePartitionFunction, InfinitePEPS}, args...) -function TensorKit.storagetype(::Type{AbstractCTMRGEnv{C, E}}) where {C, E} +function TensorKit.storagetype(::Type{<:AbstractCTMRGEnv{C, E}}) where {C, E} T = promote_type(scalartype(C), scalartype(E)) return TensorKit.promote_storagetype(T, C, E) end @@ -331,9 +331,6 @@ edgetype(env::AbstractCTMRGEnv) = edgetype(typeof(env)) edgetype(::Type{<:AbstractCTMRGEnv{C, E}}) where {C, E} = E TensorKit.spacetype(::Type{E}) where {E <: AbstractCTMRGEnv} = spacetype(cornertype(E)) -function TensorKit.storagetype(::Type{E}) where {E <: AbstractCTMRGEnv} - return TensorKit.promote_storagetype(cornertype(E), edgetype(E)) -end # In-place update of environment function update!(env::CTMRGEnv{C, T}, env´::CTMRGEnv{C, T}) where {C, T} diff --git a/test/ctmrg/c4v_contractions.jl b/test/ctmrg/c4v_contractions.jl new file mode 100644 index 000000000..83bebb106 --- /dev/null +++ b/test/ctmrg/c4v_contractions.jl @@ -0,0 +1,24 @@ +using PEPSKit, CUDA, AMDGPU + +@isdefined(TestSuite) || include("../testsuite/TestSuite.jl") +using .TestSuite + +is_buildkite = get(ENV, "BUILDKITE", "false") == "true" + +if !is_buildkite + TestSuite.c4v_ctmrg_contractions_cartesian_spaces(Vector) + TestSuite.c4v_ctmrg_contractions_u1_spaces(Vector) + TestSuite.c4v_ctmrg_contractions_fermionic_spaces(Vector) +end + +if CUDA.functional() + TestSuite.c4v_ctmrg_contractions_cartesian_spaces(CuArray) + TestSuite.c4v_ctmrg_contractions_u1_spaces(CuArray) + TestSuite.c4v_ctmrg_contractions_fermionic_spaces(CuArray) +end + +if AMDGPU.functional() + TestSuite.c4v_ctmrg_contractions_cartesian_spaces(ROCArray) + TestSuite.c4v_ctmrg_contractions_u1_spaces(ROCArray) + TestSuite.c4v_ctmrg_contractions_fermionic_spaces(ROCArray) +end diff --git a/test/testsuite/TestSuite.jl b/test/testsuite/TestSuite.jl index a1d493f41..3e576386d 100644 --- a/test/testsuite/TestSuite.jl +++ b/test/testsuite/TestSuite.jl @@ -90,6 +90,13 @@ module CTMRGContractions end using .CTMRGContractions +module CTMRGC4vContractions + include("ctmrg/c4v_contractions.jl") + export c4v_ctmrg_contractions_cartesian_spaces, c4v_ctmrg_contractions_u1_spaces + export c4v_ctmrg_contractions_fermionic_spaces +end +using .CTMRGC4vContractions + module CTMRGCorrelationLength include("ctmrg/correlation_length.jl") export ctmrg_correlation_length diff --git a/test/testsuite/ctmrg/c4v_contractions.jl b/test/testsuite/ctmrg/c4v_contractions.jl new file mode 100644 index 000000000..19927c5e8 --- /dev/null +++ b/test/testsuite/ctmrg/c4v_contractions.jl @@ -0,0 +1,186 @@ +using Test +using Random +using PEPSKit +using TensorKit +using Adapt +using PEPSKit: EnlargedCorner, c4v_enlarge, c4v_projector! +using PEPSKit: _contract_site, _contract_corners, _contract_vertical_edges, + _contract_horizontal_edges +using PEPSKit: NORTH, EAST, SOUTH, WEST, NORTHWEST, NORTHEAST, SOUTHEAST, SOUTHWEST + +# settings +stype = ComplexF64 + +# Unpack a C4v environment into the equivalent generic CTMRGEnv, which serves as a reference +# for all contractions since it stores every corner explicitly +function _expand_c4v_env(env::C4vCTMRGEnv) + C = convert(TensorMap, only(env.corners)) + return CTMRGEnv(fill(C, 4, 1, 1), copy(env.edges)) +end + +function _random_c4v_peps(AT, P, V) + peps = InfinitePEPS(randn, stype, P, V) + symmetrize!(peps, RotateReflect()) + return adapt(AT, InfinitePEPS(map(a -> a / norm(a), peps.A))) +end + +# Random local operator with one-site and nearest-neighbor terms in both directions +function _random_local_operator(P) + O₁ = randn(stype, P ← P) + O₂ = randn(stype, P ⊗ P ← P ⊗ P) + return LocalOperator( + fill(P, 1, 1), + (CartesianIndex(1, 1),) => O₁, + (CartesianIndex(1, 1), CartesianIndex(1, 2)) => O₂, + (CartesianIndex(1, 1), CartesianIndex(2, 1)) => O₂, + ) +end + +function test_c4v_contractions(n::InfiniteSquareNetwork, env::C4vCTMRGEnv) + env_ref = _expand_c4v_env(env) + ind = (1, 1) + + @testset "Network value contractions" begin + @test _contract_site(ind, n, env) ≈ _contract_site(ind, n, env_ref) + @test _contract_corners(ind, env) ≈ _contract_corners(ind, env_ref) + @test _contract_vertical_edges(ind, env) ≈ _contract_vertical_edges(ind, env_ref) + @test _contract_horizontal_edges(ind, env) ≈ _contract_horizontal_edges(ind, env_ref) + @test network_value(n, env) ≈ network_value(n, env_ref) + @test_throws AssertionError _contract_site((1, 2), n, env) + end + + @testset "Enlarged corners" begin + for dir in (NORTHWEST, NORTHEAST, SOUTHEAST, SOUTHWEST) + Q = EnlargedCorner(n, env, (dir, 1, 1)) + Q_ref = EnlargedCorner(n, env_ref, (dir, 1, 1)) + @test TensorMap(Q) ≈ TensorMap(Q_ref) + end + for projector_alg in (C4vEighProjector(), C4vQRProjector()) + @test c4v_enlarge(n, env, projector_alg) ≈ c4v_enlarge(n, env_ref, projector_alg) + end + end + + @testset "Rotations" begin + nv = network_value(n, env) + for rot in (rotl90, rotr90, rot180) + env_rot = rot(env) + env_ref_rot = rot(env_ref) + @test env_rot isa C4vCTMRGEnv + @test size(env_rot.edges) == (4, 1, 1) + @test all(env_rot.edges .≈ env_ref_rot.edges) + @test network_value(rot(n), env_rot) ≈ nv + end + @test rotl90(rotr90(env)) ≈ env + @test rot180(rot180(env)) ≈ env + end + + @testset "VectorInterface" begin + α, β = randn(stype), randn(stype) + @test norm(env) ≈ norm(env_ref) + @test inner(env, env) ≈ inner(env_ref, env_ref) + @test norm(scale(env, α)) ≈ abs(α) * norm(env) + @test scale(env, α) ≈ α * env + @test add(env, env, α, β) ≈ (α + β) * env + @test env + env ≈ 2 * env + @test norm(env - env) ≈ 0 atol = 1.0e-12 + z = zerovector(env) + @test z isa C4vCTMRGEnv + @test norm(z) == 0 + add!(z, env, α, one(stype)) + @test z ≈ α * env + scale!(z, inv(α)) + @test z ≈ env + end + + return nothing +end + +function test_c4v_peps_observables(peps::InfinitePEPS, env::C4vCTMRGEnv) + env_ref = _expand_c4v_env(env) + O = adapt(storagetype(peps), _random_local_operator(physicalspace(peps, 1, 1))) + @test expectation_value(peps, O, env) ≈ expectation_value(peps, O, env_ref) + + ξ_h, ξ_v, λ_h, λ_v = correlation_length(peps, env) + ξ_h_ref, ξ_v_ref, λ_h_ref, λ_v_ref = correlation_length(peps, env_ref) + @test ξ_h ≈ ξ_h_ref + @test ξ_v ≈ ξ_v_ref + @test λ_h ≈ λ_h_ref + @test λ_v ≈ λ_v_ref + return nothing +end + +function test_c4v_ctmrg_contractions(AT, P, V, Venv) + @testset "C4v PEPS contractions" begin + peps = _random_c4v_peps(AT, P, V) + n = InfiniteSquareNetwork(peps) + env = initialize_random_c4v_env(peps, Venv) + + @test storagetype(n) <: AT + test_c4v_contractions(n, env) + test_c4v_peps_observables(peps, env) + end + + @testset "C4v PEPS contractions with singlet initialization" begin + peps = _random_c4v_peps(AT, P, V) + n = InfiniteSquareNetwork(peps) + env = initialize_singlet_c4v_env(peps, Venv) + + test_c4v_contractions(n, env) + end + + @testset "C4v PEPS contractions with generic constructor" begin + peps = _random_c4v_peps(AT, P, V) + n = InfiniteSquareNetwork(peps) + env = C4vCTMRGEnv(randn, stype, peps, Venv) + + test_c4v_contractions(n, env) + end + + @testset "C4v PartitionFunction contractions" begin + pf = adapt(AT, InfinitePartitionFunction(randn, stype, V)) + n = InfiniteSquareNetwork(pf) + env = initialize_random_c4v_env(pf, Venv) + + @test storagetype(n) <: AT + test_c4v_contractions(n, env) + end + + @testset "C4v environment after one CTMRG iteration" begin + peps = _random_c4v_peps(AT, P, V) + n = InfiniteSquareNetwork(peps) + env₀ = initialize_random_c4v_env(peps, Venv) + for projector_alg in (:C4vEighProjector, :C4vQRProjector) + alg = C4vCTMRG(; projector_alg) + env, = PEPSKit.ctmrg_iteration(n, env₀, alg) + @test env isa C4vCTMRGEnv + test_c4v_contractions(n, env) + end + end + + return nothing +end + +function c4v_ctmrg_contractions_cartesian_spaces(AT) + Random.seed!(20261028) + return @testset "C4v contractions with Cartesian spaces ($AT)" begin + test_c4v_ctmrg_contractions(AT, ℂ^2, ℂ^3, ℂ^8) + end +end + +function c4v_ctmrg_contractions_u1_spaces(AT) + Random.seed!(20261028) + return @testset "C4v contractions with U1 spaces ($AT)" begin + P = U1Space(-1 => 1, 1 => 1) + V = U1Space(-1 => 1, 0 => 1, 1 => 1) + Venv = U1Space(-2 => 1, -1 => 2, 0 => 3, 1 => 2, 2 => 1) + test_c4v_ctmrg_contractions(AT, P, V, Venv) + end +end + +function c4v_ctmrg_contractions_fermionic_spaces(AT) + Random.seed!(20261028) + return @testset "C4v contractions with fermionic spaces ($AT)" begin + S = Vect[FermionParity] + test_c4v_ctmrg_contractions(AT, S(0 => 1, 1 => 1), S(0 => 2, 1 => 1), S(0 => 4, 1 => 4)) + end +end diff --git a/test/types/adapt.jl b/test/types/adapt.jl index be76328b9..ccef55ba6 100644 --- a/test/types/adapt.jl +++ b/test/types/adapt.jl @@ -64,5 +64,19 @@ if !is_buildkite @test TensorKit.storagetype(jl_env) <: JLArray end end + @testset "C4vCTMRGEnv" begin + Vphy = Z2Space(0 => 1, 1 => 1) + V = Z2Space(0 => 1, 1 => 2) + Venv = Z2Space(0 => 2, 1 => 2) + peps = InfinitePEPS(randn, ComplexF64, Vphy, V) + symmetrize!(peps, RotateReflect()) + env = initialize_random_c4v_env(peps, Venv) + jl_env = adapt(JLArray, env) + @test jl_env isa C4vCTMRGEnv + @test size(jl_env.corners) == (1, 1, 1) + @test size(jl_env.edges) == (4, 1, 1) + @test TensorKit.storagetype(jl_env) <: JLArray + @test adapt(Array, jl_env) ≈ env + end end end From fce8adc32081c4f5c0c2c9ec824c1dc0b9255bc7 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Wed, 28 Oct 2026 05:44:56 -0400 Subject: [PATCH 7/8] Fix docstring and tests --- src/environments/c4v_ctmrg_environments.jl | 93 ++++++++-------------- test/testsuite/ctmrg/c4v_contractions.jl | 12 ++- 2 files changed, 37 insertions(+), 68 deletions(-) diff --git a/src/environments/c4v_ctmrg_environments.jl b/src/environments/c4v_ctmrg_environments.jl index 34de95dda..f914b73c0 100644 --- a/src/environments/c4v_ctmrg_environments.jl +++ b/src/environments/c4v_ctmrg_environments.jl @@ -40,18 +40,18 @@ end """ C4vCTMRGEnv( - [f=randn, T=ComplexF64], Ds_north::A, Ds_east::A, chis::B - ) where {A<:AbstractMatrix{<:VectorSpace}, B<:AbstractMatrix{<:ElementarySpace}} - -Construct a C4v symmetric 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. The virtual -environment spaces for all directions are taken to be the same. - -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). + [f=randn, T=ComplexF64], Ds::A, chis::B + ) where {A<:AbstractMatrix{<:ProductSpace}, B<:AbstractMatrix{<:ElementarySpace}} + +Construct a C4v symmetric CTMRG environment by specifying 1 x 1 matrices of the north virtual +space of the corresponding partition function and the virtual space of the environment. A +single corner and north edge tensor are generated, from which the remaining edges are +obtained by symmetry. The virtual environment spaces for all directions are the same. + +The entry of `Ds` 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 C4vCTMRGEnv( f, T, Ds::A, chis::B, @@ -62,37 +62,11 @@ function C4vCTMRGEnv( size(Ds) == size(chis) || throw(ArgumentError("Input spaces should have equal sizes.")) length(Ds) == 1 || throw(ArgumentError("C4v CTMRG only supports trivial unit cells.")) - # do the whole thing - N = length(first(Ds)) - st = spacetype(first(Ds)) - C_type = tensormaptype(st, 1, 1, T) - T_type = tensormaptype(st, N + 1, 1, T) - - # All corners are identical - corners = Array{C_type}(undef, 1, size(Ds)...) - # Four edge tensors are related with flipped virtual spaces - edges = Array{T_type}(undef, 4, size(Ds)...) - - r, c = (1, 1) - edges[NORTH, r, c] = _edge_tensor( - f, T, chis[r, _prev(c, end)], Ds[_next(r, end), c], chis[r, c] - ) - edges[EAST, r, c] = _edge_tensor( - f, T, chis[r, c], Ds[r, _prev(c, end)], chis[_next(r, end), c] - ) - edges[SOUTH, r, c] = _edge_tensor( - f, T, chis[r, c], Ds[_prev(r, end), c], chis[r, _prev(c, end)] - ) - edges[WEST, r, c] = _edge_tensor( - f, T, chis[_next(r, end), c], Ds[r, _next(c, end)], chis[r, c] - ) - corners[1, r, c] = _corner_tensor( - f, T, chis[_next(r, end), c], chis[r, c] - ) - - corners[1, :, :] ./= norm.(corners[1, :, :]) - edges[:, :, :] ./= norm.(edges[:, :, :]) - return C4vCTMRGEnv(corners, edges) + # All corners are identical, and the remaining edges follow from the north edge + D, χ = only(Ds), only(chis) + corner = _corner_tensor(f, T, χ, χ) + edge = _edge_tensor(f, T, χ, D, χ) + return C4vCTMRGEnv(corner / norm(corner), edge / norm(edge)) end function C4vCTMRGEnv(D::P, args...; kwargs...) where {P <: Union{Matrix{VectorSpace}, VectorSpace}} return C4vCTMRGEnv(randn, ComplexF64, D, args...; kwargs...) @@ -106,21 +80,20 @@ end Construct a C4v-symmetric CTMRG environment by specifying the virtual space of the corresponding [`InfiniteSquareNetwork`](@ref) and the virtual -space of the environment. The network unit cell can be specified by the `unitcell` keyword -argument. By default, the virtual environment spaces for all directions are taken to be the -same. +space of the environment. The `unitcell` keyword argument must be `(1, 1)`. The virtual +environment spaces for all directions are the same. The environment virtual spaces for each site correspond to virtual space of the corresponding edge tensor for each direction. """ function C4vCTMRGEnv( f, T, - D::S, virtual_spaces...; unitcell::Tuple{Int, Int} = (1, 1), + D::S, chi::ElementarySpace; unitcell::Tuple{Int, Int} = (1, 1), ) where {S <: VectorSpace} return C4vCTMRGEnv( f, T, - _fill_edge_physical_spaces(D, D; unitcell)..., - _fill_environment_virtual_spaces(virtual_spaces...; unitcell)..., + first(_fill_edge_physical_spaces(D; unitcell)), + first(_fill_environment_virtual_spaces(chi; unitcell)), ) end @@ -130,25 +103,23 @@ end ) where {A<:Union{AbstractMatrix{<:ElementarySpace}, ElementarySpace}} Construct a C4v-symmetric CTMRG environment by specifying a corresponding -[`InfiniteSquareNetwork`](@ref), and the virtual spaces of the environment. -The virtual spaces can either be specified as matrices of `ElementarySpace`s, -or as individual `ElementarySpace`s which are then filled to match the size of the unit -cell. Each respective matrix entry corresponds to a site in the unit cell. By default, the -virtual spaces for all directions are taken to be the same. +[`InfiniteSquareNetwork`](@ref), and the virtual space of the environment. +The virtual space can either be specified as a 1 x 1 matrix of `ElementarySpace`s, +or as an individual `ElementarySpace`. The virtual spaces for all directions are the same. The environment virtual spaces for each site correspond to the virtual space of the corresponding edge tensor for each direction. """ -function C4vCTMRGEnv(f, T, network::InfiniteSquareNetwork, virtual_spaces...) +function C4vCTMRGEnv(f, T, network::InfiniteSquareNetwork, chis) Ds = _north_edge_physical_spaces(network) - virtual_spaces = _fill_environment_virtual_spaces(virtual_spaces...; unitcell = size(network)) - return C4vCTMRGEnv(f, similarstoragetype(storagetype(network), eltype(T)), Ds, virtual_spaces...) + chis = first(_fill_environment_virtual_spaces(chis; unitcell = size(network))) + return C4vCTMRGEnv(f, similarstoragetype(storagetype(network), eltype(T)), Ds, chis) end -function C4vCTMRGEnv(network::InfiniteSquareNetwork{O}, virtual_spaces...) where {O} - return C4vCTMRGEnv(randn, storagetype(O), network, virtual_spaces...) +function C4vCTMRGEnv(network::InfiniteSquareNetwork{O}, chis) where {O} + return C4vCTMRGEnv(randn, storagetype(O), network, chis) end -function C4vCTMRGEnv(network::Union{<:InfinitePartitionFunction, <:InfinitePEPS}, virtual_spaces...) - return C4vCTMRGEnv(randn, storagetype(network), network, virtual_spaces...) +function C4vCTMRGEnv(network::Union{<:InfinitePartitionFunction, <:InfinitePEPS}, chis) + return C4vCTMRGEnv(randn, storagetype(network), network, chis) end # allow constructing environments for implicitly defined contractible networks diff --git a/test/testsuite/ctmrg/c4v_contractions.jl b/test/testsuite/ctmrg/c4v_contractions.jl index 19927c5e8..84f328888 100644 --- a/test/testsuite/ctmrg/c4v_contractions.jl +++ b/test/testsuite/ctmrg/c4v_contractions.jl @@ -8,11 +8,9 @@ using PEPSKit: _contract_site, _contract_corners, _contract_vertical_edges, _contract_horizontal_edges using PEPSKit: NORTH, EAST, SOUTH, WEST, NORTHWEST, NORTHEAST, SOUTHEAST, SOUTHWEST -# settings stype = ComplexF64 -# Unpack a C4v environment into the equivalent generic CTMRGEnv, which serves as a reference -# for all contractions since it stores every corner explicitly +# Unpack a C4v environment into the equivalent generic CTMRGEnv to use as a reference function _expand_c4v_env(env::C4vCTMRGEnv) C = convert(TensorMap, only(env.corners)) return CTMRGEnv(fill(C, 4, 1, 1), copy(env.edges)) @@ -20,11 +18,10 @@ end function _random_c4v_peps(AT, P, V) peps = InfinitePEPS(randn, stype, P, V) - symmetrize!(peps, RotateReflect()) + BraidingStyle(sectortype(P)) isa Bosonic && symmetrize!(peps, RotateReflect()) return adapt(AT, InfinitePEPS(map(a -> a / norm(a), peps.A))) end -# Random local operator with one-site and nearest-neighbor terms in both directions function _random_local_operator(P) O₁ = randn(stype, P ← P) O₂ = randn(stype, P ⊗ P ← P ⊗ P) @@ -76,8 +73,9 @@ function test_c4v_contractions(n::InfiniteSquareNetwork, env::C4vCTMRGEnv) @testset "VectorInterface" begin α, β = randn(stype), randn(stype) - @test norm(env) ≈ norm(env_ref) - @test inner(env, env) ≈ inner(env_ref, env_ref) + @test norm(env)^2 ≈ norm(only(env.corners))^2 + sum(e -> norm(e)^2, env.edges) + @test inner(env, env) ≈ norm(env)^2 + @test norm(env)^2 ≈ norm(env_ref)^2 - 3 * norm(only(env.corners))^2 @test norm(scale(env, α)) ≈ abs(α) * norm(env) @test scale(env, α) ≈ α * env @test add(env, env, α, β) ≈ (α + β) * env From 9a9c8a9401d5ecf94be29fd2f36bfb3e7edd53ed Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Wed, 28 Oct 2026 14:32:39 -0400 Subject: [PATCH 8/8] Fix test comparison --- test/testsuite/ctmrg/c4v_contractions.jl | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/test/testsuite/ctmrg/c4v_contractions.jl b/test/testsuite/ctmrg/c4v_contractions.jl index 84f328888..cca67b0d2 100644 --- a/test/testsuite/ctmrg/c4v_contractions.jl +++ b/test/testsuite/ctmrg/c4v_contractions.jl @@ -98,12 +98,17 @@ function test_c4v_peps_observables(peps::InfinitePEPS, env::C4vCTMRGEnv) O = adapt(storagetype(peps), _random_local_operator(physicalspace(peps, 1, 1))) @test expectation_value(peps, O, env) ≈ expectation_value(peps, O, env_ref) - ξ_h, ξ_v, λ_h, λ_v = correlation_length(peps, env) - ξ_h_ref, ξ_v_ref, λ_h_ref, λ_v_ref = correlation_length(peps, env_ref) + num_vals = 2 + ξ_h, ξ_v, λ_h, λ_v = correlation_length(peps, env; num_vals) + ξ_h_ref, ξ_v_ref, λ_h_ref, λ_v_ref = correlation_length(peps, env_ref; num_vals) @test ξ_h ≈ ξ_h_ref @test ξ_v ≈ ξ_v_ref - @test λ_h ≈ λ_h_ref - @test λ_v ≈ λ_v_ref + # eigsolve may return more than `num_vals` eigenvalues depending on the random start + # vector, so only the requested leading eigenvalues are comparable; compare magnitudes + # since the order within a complex-conjugate pair is not fixed + leading(λs) = map(λ -> abs.(λ[1:num_vals]), λs) + @test leading(λ_h) ≈ leading(λ_h_ref) + @test leading(λ_v) ≈ leading(λ_v_ref) return nothing end