EditURL = "../../../../examples/boundary_mps/main.jl"
Instead of using CTMRG to contract the network encoding the norm of an infinite PEPS, one can also use so-called [boundary MPS methods](@cite haegeman_diagonalizing_2017) to contract this network. In this example, we will demonstrate how to use [the VUMPS algorithm](@cite vanderstraeten_tangentspace_2019) to do so.
Before we start, we'll fix the random seed for reproducability:
using Random
Random.seed!(29384293742893);Besides TensorKit and PEPSKit, here we also need to load the
MPSKit.jl package which implements a
host of tools for working with 1D matrix product states (MPS), including the VUMPS
algorithm:
using TensorKit, PEPSKit, MPSKitWe start by initializing a random infinite PEPS. Let us use normally distributed complex
entries using randn:
ψ = InfinitePEPS(randn, ComplexF64, ComplexSpace(2), ComplexSpace(2))InfinitePEPS{TensorKit.TensorMap{ComplexF64, TensorKit.ComplexSpace, 1, 4, Vector{ComplexF64}}}(TensorKit.TensorMap{ComplexF64, TensorKit.ComplexSpace, 1, 4, Vector{ComplexF64}}[TensorMap{ComplexF64, TensorKit.ComplexSpace, 1, 4, Vector{ComplexF64}}(ComplexF64[-0.5524390176345264 - 0.07357188568178248im, -0.5455245317233405 + 0.8946618856309984im, 0.34014501646081047 - 0.7552574870030472im, 1.249282911658007 + 0.45352274131986825im, 0.33621043661988675 + 0.4400876608299719im, -0.0077250067072679235 + 1.7380910495900947im, -0.9866664087107284 - 0.28688827761325675im, -0.19071062901939098 - 1.1367500834118434im, -0.09149850722392933 + 0.3560942836258964im, -0.19309251474097275 - 0.32363899914302613im, 1.6255618447281441 - 0.5689426732891244im, -0.025356816648697236 + 0.5632279168368712im, 0.07675114584269166 - 0.011479824536308164im, -1.0116302866282385 - 0.9253070687198848im, -0.17779977372973318 + 1.1379201927122535im, 1.1649047337212566 + 0.9936369101208083im, 0.2510676919806213 - 0.182052326055189im, 0.04501645227038913 - 0.8140971172854408im, -0.5792402993550532 - 0.4309109406268341im, -0.5608346802110794 + 0.21262550530307248im, 1.5061767210554262 + 0.17190948125245623im, -0.8176938467062373 - 0.40919675695722396im, -0.8001234458239143 + 0.6764943808639017im, -0.6692181340575689 + 0.6923370271564298im, -0.16556382071485704 + 0.2540132491548349im, -0.29883021417599165 - 0.07229462525164528im, 0.05546115732751907 + 0.3723175507964387im, -1.200173153698329 - 0.45509299328832953im, 0.289873563752043 + 0.44718981087960125im, 0.5128282969211142 - 0.2865462937979091im, 0.018357838612906643 + 0.9634127683557584im, -0.44278618042821827 + 0.2612084385439659im], ℂ^2 ← (ℂ^2 ⊗ ℂ^2 ⊗ (ℂ^2)' ⊗ (ℂ^2)'));;])
To compute its norm, we have to contract a double-layer network which encodes the bra-ket
PEPS overlap \langle ψ | ψ \rangle:
<center>
<img src="../../assets/figures/peps_norm_network.svg" alt="peps norm network" class="color-invertible" style="zoom: 180%"/>
</center>
In PEPSKit.jl, this structure is represented as an InfiniteSquareNetwork object,
whose effective local rank-4 constituent tensor is given by the contraction of a pair of bra
and ket PEPSKit.PEPSTensors across their physical legs. Until now, we have always
contracted such a network using the CTMRG algorithm. Here however, we will use another
approach.
If we take out a single row of this infinite norm network, we can interpret it as a 1D
row-to-row transfer operator \mathbb{T},
<center>
<img src="../../assets/figures/peps_transfer_operator.svg" alt="peps transfer operator" class="color-invertible" style="zoom: 180%"/>
</center>
This transfer operator can be seen as an infinite chain of the effective local rank-4
tensors that make up the PEPS norm network. Since the network we want to contract can be
interpreted as the infinite power of \mathbb{T}, we can contract it by finding its
leading eigenvector as a 1D MPS | \psi_{\text{MPS}} \rangle, which we call the boundary
MPS. This boundary MPS should satisfy the eigenvalue equation
\mathbb{T} | \psi_{\text{MPS}} \rangle \approx \Lambda | \psi_{\text{MPS}} \rangle, or
diagrammatically:
<center>
<img src="../../assets/figures/peps_transfer_fixedpoint_equation.svg" alt="peps transfer fixedpoint equation" class="color-invertible" style="zoom: 180%"/>
</center>
Note that if \mathbb{T} is Hermitian, we can formulate this eigenvalue equation in terms of a
variational problem for the free energy,
where \lambda = \Lambda^{1/N} is the 'eigenvalue per site' of \mathbb{T}, giving
f the meaning of a free energy density.
Since the contraction of a PEPS norm network is in essence exactly the same problem as the
contraction of a 2D classical partition function, we can directly use boundary MPS
algorithms designed for 2D statistical mechanics models in this context. In particular,
we'll use the [the VUMPS algorithm](@cite vanderstraeten_tangentspace_2019) to perform the
boundary MPS contraction, and we'll call it through the leading_boundary method
from MPSKit.jl. This method precisely finds the MPS fixed point of a 1D transfer operator.
To use leading_boundary, we first need to contruct the transfer operator
\mathbb{T} as an MPSKit.InfiniteMPO object. In PEPSKit.jl, we can directly
construct the transfer operator corresponding to a PEPS norm network from a given infinite
PEPS as an InfiniteTransferPEPS object, which is a specific kind of
MPSKit.InfiniteMPO.
To construct a 1D transfer operator from a 2D PEPS state, we need to specify which direction
should be facing north (dir=1 corresponding to north, counting clockwise) and which row of
the network is selected from the north - but since we have a trivial unit cell there is only
one row here:
dir = 1 ## does not rotate the partition function
row = 1
T = InfiniteTransferPEPS(ψ, dir, row)1-site InfiniteMPO(ComplexF64, TensorKit.ComplexSpace) with maximal dimension 4:
| ⋮
| (ℂ^2 ⊗ (ℂ^2)')
┼─[1]─ (ℂ^2 ⊗ (ℂ^2)')
│ (ℂ^2 ⊗ (ℂ^2)')
| ⋮
Since we'll find the leading eigenvector of \mathbb{T} as a boundary MPS, we first need
to construct an initial guess to supply to our algorithm. We can do this using the
initialize_mps function, which constructs a random MPS with a specific virtual
space for a given transfer operator. Here, we'll build an initial guess for the boundary MPS
with a bond dimension of 20:
mps₀ = initialize_mps(T, [ComplexSpace(20)])1-site InfiniteMPS(ComplexF64, TensorKit.ComplexSpace) with maximal dimension 20:
| ⋮
| ℂ^20
├─[1]─ (ℂ^2 ⊗ (ℂ^2)')
│ ℂ^20
| ⋮
Note that this will just construct a MPS with random Gaussian entries based on the physical
spaces of the supplied transfer operator. Of course, one might come up with a better initial
guess (leading to better convergence) depending on the application. To find the leading
boundary MPS fixed point, we call leading_boundary using the
MPSKit.VUMPS algorithm:
mps, env, ϵ = leading_boundary(mps₀, T, VUMPS(; tol = 1.0e-6, verbosity = 2));[ Info: VUMPS init: obj = +1.674563752306e+00 +3.035692829590e+00im err = 7.5576e-01
[ Info: VUMPS conv 120: obj = +6.831610878219e+00 -9.723041888467e-09im err = 9.5233523833e-07 time = 6.10 sec
The norm of the state per unit cell is then given by the expectation value
norm_vumps = abs(prod(expectation_value(mps, T)))6.83161087821908
This can be compared to the result obtained using CTMRG, where we see that the results match:
env_ctmrg, = leading_boundary(CTMRGEnv(ψ, ComplexSpace(20)), ψ; tol = 1.0e-6, verbosity = 2)
norm_ctmrg = abs(norm(ψ, env_ctmrg))
@show abs(norm_vumps - norm_ctmrg) / norm_vumps;[ Info: CTMRG init: obj = -1.495741317009e+01 +3.091851579630e-01im err = 1.0000e+00
[ Info: CTMRG conv 30: obj = +6.831603585666e+00 err = 6.2262595115e-07 time = 0.20 sec
abs(norm_vumps - norm_ctmrg) / norm_vumps = 1.0674719949350553e-6
For PEPS with non-trivial unit cells, the principle is exactly the same. The only difference
is that now the transfer operator of the PEPS norm partition function has multiple rows or
'lines', each of which can be represented by an InfiniteTransferPEPS object. Such
a multi-line transfer operator is represented by a PEPSKit.MultilineTransferPEPS
object. In this case, the boundary MPS is an MultilineMPS object, which should
be initialized by specifying a virtual space for each site in the partition function unit
cell.
First, we construct a PEPS with a unitcell keyword
argument and then define the corresponding transfer operator, where we again specify the
direction which will be facing north:
ψ_2x2 = InfinitePEPS(rand, ComplexF64, ComplexSpace(2), ComplexSpace(2); unitcell = (2, 2))
T_2x2 = PEPSKit.MultilineTransferPEPS(ψ_2x2, dir);Now, the procedure is the same as before: We compute the norm once using VUMPS, once using CTMRG and then compare.
mps₀_2x2 = initialize_mps(T_2x2, fill(ComplexSpace(20), 2, 2))
mps_2x2, = leading_boundary(mps₀_2x2, T_2x2, VUMPS(; tol = 1.0e-6, verbosity = 2))
norm_2x2_vumps = abs(prod(expectation_value(mps_2x2, T_2x2)))
env_ctmrg_2x2, = leading_boundary(
CTMRGEnv(ψ_2x2, ComplexSpace(20)), ψ_2x2; tol = 1.0e-6, verbosity = 2
)
norm_2x2_ctmrg = abs(norm(ψ_2x2, env_ctmrg_2x2))
@show abs(norm_2x2_vumps - norm_2x2_ctmrg) / norm_2x2_vumps;[ Info: VUMPS init: obj = +8.149302834396e+02 -8.860408249120e+01im err = 8.6172e-01
┌ Warning: VUMPS cancel 200: obj = +1.046766793317e+05 -1.493448261103e+02im err = 3.7375841028e-02 time = 11.59 sec
└ @ MPSKit ~/.julia/packages/MPSKit/9ITGf/src/algorithms/groundstate/vumps.jl:76
[ Info: CTMRG init: obj = -1.240261729401e+02 -1.672150510262e+01im err = 1.0000e+00
[ Info: CTMRG conv 47: obj = +1.046633714846e+05 err = 1.6993512829e-07 time = 0.76 sec
abs(norm_2x2_vumps - norm_2x2_ctmrg) / norm_2x2_vumps = 0.00012815051487696121
Again, the results are compatible. Note that for larger unit cells and non-Hermitian PEPS [the VUMPS algorithm may become unstable](@cite vanderstraeten_variational_2022), in which case the CTMRG algorithm is recommended.
Using exactly the same machinery, we can contract 2D networks which encode the expectation
value of a PEPO for a given PEPS state. As an example, we can consider the overlap of the
PEPO correponding to the partition function of [3D classical Ising model](@ref e_3d_ising)
with our random PEPS from before and evaluate the overlap
The classical Ising PEPO is defined as follows:
function ising_pepo(β; unitcell = (1, 1, 1))
t = ComplexF64[exp(β) exp(-β); exp(-β) exp(β)]
q = sqrt(t)
O = zeros(2, 2, 2, 2, 2, 2)
O[1, 1, 1, 1, 1, 1] = 1
O[2, 2, 2, 2, 2, 2] = 1
@tensor o[-1 -2; -3 -4 -5 -6] :=
O[1 2; 3 4 5 6] * q[-1; 1] * q[-2; 2] * q[-3; 3] * q[-4; 4] * q[-5; 5] * q[-6; 6]
O = TensorMap(o, ℂ^2 ⊗ (ℂ^2)' ← ℂ^2 ⊗ ℂ^2 ⊗ (ℂ^2)' ⊗ (ℂ^2)')
return InfinitePEPO(O; unitcell)
end;To evaluate the overlap, we instantiate the PEPO and the corresponding InfiniteTransferPEPO
in the right direction, on the right row of the partition function (trivial here):
T = ising_pepo(1)
transfer_pepo = InfiniteTransferPEPO(ψ, T, 1, 1)1-site InfiniteMPO(ComplexF64, TensorKit.ComplexSpace) with maximal dimension 8:
| ⋮
| (ℂ^2 ⊗ ℂ^2 ⊗ (ℂ^2)')
┼─[1]─ (ℂ^2 ⊗ ℂ^2 ⊗ (ℂ^2)')
│ (ℂ^2 ⊗ ℂ^2 ⊗ (ℂ^2)')
| ⋮
As before, we converge the boundary MPS using VUMPS and then compute the expectation value:
mps₀_pepo = initialize_mps(transfer_pepo, [ComplexSpace(20)])
mps_pepo, = leading_boundary(mps₀_pepo, transfer_pepo, VUMPS(; tol = 1.0e-6, verbosity = 2))
norm_pepo = abs(prod(expectation_value(mps_pepo, transfer_pepo)));
@show norm_pepo;[ Info: VUMPS init: obj = +2.655321432467e+01 +3.760603778362e-01im err = 8.9759e-01
┌ Warning: VUMPS cancel 200: obj = +9.515362005489e+01 -2.503860489548e+00im err = 2.3598739398e-01 time = 33.35 sec
└ @ MPSKit ~/.julia/packages/MPSKit/9ITGf/src/algorithms/groundstate/vumps.jl:76
norm_pepo = 95.18655749054498
These objects and routines can be used to optimize PEPS fixed points of 3D partition functions, see for example [Vanderstraeten et al.](@cite vanderstraeten_residual_2018)
This page was generated using Literate.jl.