Three-site simple update for the $J_1$-$J_2$ model

In this example, we will use SimpleUpdate imaginary time evolution to treat the two-dimensional $J_1$-$J_2$ model, which contains next-nearest-neighbour interactions:

\[H = J_1 \sum_{\langle i,j \rangle} \mathbf{S}_i \cdot \mathbf{S}_j + J_2 \sum_{\langle \langle i,j \rangle \rangle} \mathbf{S}_i \cdot \mathbf{S}_j\]

Here we will exploit the $U(1)$ spin rotation symmetry in the $J_1$-$J_2$ model. The goal will be to calculate the energy at $J_1 = 1$ and $J_2 = 1/2$, first using the simple update algorithm and then, to refine the energy estimate, using AD-based variational PEPS optimization.

We first import all required modules and seed the RNG:

using Random
using TensorKit, PEPSKit
Random.seed!(29385293);

Simple updating a challenging phase

Let's start by initializing an InfinitePEPS for which we set the required parameters as well as physical and virtual vector spaces. The SUWeight used by simple update will be initialized to identity matrices. We use the minimal unit cell size ($2 \times 2$) required by the simple update algorithm for Hamiltonians with next-nearest-neighbour interactions:

Dbond, symm = 4, U1Irrep
Nr, Nc, J1 = 2, 2, 1.0

# random initialization of 2x2 iPEPS (using real numbers) and SUWeight
Pspace = Vect[U1Irrep](1 // 2 => 1, -1 // 2 => 1)
Vspace = Vect[U1Irrep](0 => 2, 1 // 2 => 1, -1 // 2 => 1)
peps = InfinitePEPS(rand, Float64, Pspace, Vspace; unitcell = (Nr, Nc));
wts = SUWeight(peps);

The value $J_2 / J_1 = 0.5$ corresponds to a possible spin liquid phase, which is challenging for SU to produce a relatively good state from random initialization. Therefore, we shall gradually increase $J_2 / J_1$ from 0.1 to 0.5, each time initializing on the previously evolved PEPS:

dt, tol, maxiter = 1.0e-2, 1.0e-8, 30000
check_interval = 4000
trscheme_peps = truncerr(1.0e-10) & truncdim(Dbond)
alg = SimpleUpdate(dt, tol, maxiter, trscheme_peps)
for J2 in 0.1:0.1:0.5
    H = real( ## convert Hamiltonian `LocalOperator` to real floats
        j1_j2_model(ComplexF64, symm, InfiniteSquare(Nr, Nc); J1, J2, sublattice = false),
    )
    global peps, wts, = simpleupdate(peps, H, alg, wts; check_interval)
end
[ Info: Space of x-weight at [1, 1] = Rep[TensorKitSectors.U₁](0=>2, 1/2=>1, -1/2=>1)
[ Info: SU iter 1      :  dt = 1e-02,  weight diff = 1.188e+00,  time = 28.698 sec
[ Info: Space of x-weight at [1, 1] = Rep[TensorKitSectors.U₁](0=>2, 1=>1, -1=>1)
[ Info: SU conv 1832   :  dt = 1e-02,  weight diff = 9.942e-09,  time = 104.269 sec
[ Info: Space of x-weight at [1, 1] = Rep[TensorKitSectors.U₁](0=>2, 1=>1, -1=>1)
[ Info: SU iter 1      :  dt = 1e-02,  weight diff = 3.400e-04,  time = 0.084 sec
[ Info: Space of x-weight at [1, 1] = Rep[TensorKitSectors.U₁](0=>2, 1=>1, -1=>1)
[ Info: SU conv 523    :  dt = 1e-02,  weight diff = 9.964e-09,  time = 19.444 sec
[ Info: Space of x-weight at [1, 1] = Rep[TensorKitSectors.U₁](0=>2, 1=>1, -1=>1)
[ Info: SU iter 1      :  dt = 1e-02,  weight diff = 3.524e-04,  time = 0.065 sec
[ Info: Space of x-weight at [1, 1] = Rep[TensorKitSectors.U₁](0=>2, 1=>1, -1=>1)
[ Info: SU conv 611    :  dt = 1e-02,  weight diff = 9.848e-09,  time = 22.684 sec
[ Info: Space of x-weight at [1, 1] = Rep[TensorKitSectors.U₁](0=>2, 1=>1, -1=>1)
[ Info: SU iter 1      :  dt = 1e-02,  weight diff = 3.661e-04,  time = 0.036 sec
[ Info: Space of x-weight at [1, 1] = Rep[TensorKitSectors.U₁](0=>2, 1=>1, -1=>1)
[ Info: SU conv 735    :  dt = 1e-02,  weight diff = 9.962e-09,  time = 27.379 sec
[ Info: Space of x-weight at [1, 1] = Rep[TensorKitSectors.U₁](0=>2, 1=>1, -1=>1)
[ Info: SU iter 1      :  dt = 1e-02,  weight diff = 3.823e-04,  time = 0.036 sec
[ Info: Space of x-weight at [1, 1] = Rep[TensorKitSectors.U₁](0=>2, 1=>1, -1=>1)
[ Info: SU conv 901    :  dt = 1e-02,  weight diff = 9.994e-09,  time = 33.440 sec

After we reach $J_2 / J_1 = 0.5$, we gradually decrease the evolution time step to obtain a more accurately evolved PEPS:

dts = [1.0e-3, 1.0e-4]
tols = [1.0e-9, 1.0e-9]
J2 = 0.5
H = real(j1_j2_model(ComplexF64, symm, InfiniteSquare(Nr, Nc); J1, J2, sublattice = false))
for (dt, tol) in zip(dts, tols)
    alg′ = SimpleUpdate(dt, tol, maxiter, trscheme_peps)
    global peps, wts, = simpleupdate(peps, H, alg′, wts; check_interval)
end
[ Info: Space of x-weight at [1, 1] = Rep[TensorKitSectors.U₁](0=>2, 1=>1, -1=>1)
[ Info: SU iter 1      :  dt = 1e-03,  weight diff = 4.447e-04,  time = 0.036 sec
[ Info: Space of x-weight at [1, 1] = Rep[TensorKitSectors.U₁](0=>2, 1=>1, -1=>1)
[ Info: SU conv 3236   :  dt = 1e-03,  weight diff = 9.998e-10,  time = 118.922 sec
[ Info: Space of x-weight at [1, 1] = Rep[TensorKitSectors.U₁](0=>2, 1=>1, -1=>1)
[ Info: SU iter 1      :  dt = 1e-04,  weight diff = 4.436e-05,  time = 0.036 sec
[ Info: Space of x-weight at [1, 1] = Rep[TensorKitSectors.U₁](0=>2, 1=>1, -1=>1)
[ Info: SU conv 796    :  dt = 1e-04,  weight diff = 9.999e-10,  time = 29.313 sec

Computing the simple update energy estimate

Finally, we measure the ground-state energy by converging a CTMRG environment and computing the expectation value, where we first normalize tensors in the PEPS:

normalize!.(peps.A, Inf) ## normalize each PEPS tensor by largest element
χenv = 32
trscheme_env = truncerr(1.0e-10) & truncdim(χenv)
Espace = Vect[U1Irrep](0 => χenv ÷ 2, 1 // 2 => χenv ÷ 4, -1 // 2 => χenv ÷ 4)
env₀ = CTMRGEnv(rand, Float64, peps, Espace)
env, = leading_boundary(env₀, peps; tol = 1.0e-10, alg = :sequential, trscheme = trscheme_env);
E = expectation_value(peps, H, env) / (Nr * Nc)
-0.4908482949689091

Let us compare that estimate with benchmark data obtained from the YASTN/peps-torch package. which utilizes AD-based PEPS optimization to find $E_\text{ref}=-0.49425$:

E_ref = -0.49425
@show (E - E_ref) / abs(E_ref);
(E - E_ref) / abs(E_ref) = 0.00688255949639037

Variational PEPS optimization using AD

As a last step, we will use the SU-evolved PEPS as a starting point for a fixedpoint PEPS optimization. Note that we could have also used a sublattice-rotated version of H to fit the Hamiltonian onto a single-site unit cell which would require us to optimize fewer parameters and hence lead to a faster optimization. But here we instead take advantage of the already evolved peps, thus giving us a physical initial guess for the optimization. In order to break some of the $C_{4v}$ symmetry of the PEPS, we will add a bit of noise to it

  • this is conviently done using MPSKit's randomize! function. (Breaking some of the spatial

symmetry can be advantageous for obtaining lower energies.)

using MPSKit: randomize!

noise_peps = InfinitePEPS(randomize!.(deepcopy(peps.A)))
peps₀ = peps + 1.0e-1noise_peps
peps_opt, env_opt, E_opt, = fixedpoint(
    H, peps₀, env; optimizer_alg = (; tol = 1.0e-4, maxiter = 80)
);
┌ Warning: the provided real environment was converted to a complex environment since :fixed mode generally produces complex gauges; use :diffgauge mode instead by passing gradient_alg=(; iterscheme=:diffgauge) to the fixedpoint keyword arguments to work with purely real environments
└ @ PEPSKit ~/PEPSKit.jl/src/algorithms/optimization/peps_optimization.jl:204
[ Info: LBFGS: initializing with f = -1.906213849448, ‖∇f‖ = 7.1073e-01
[ Info: LBFGS: iter    1, time  652.92 s: f = -1.912844704754, ‖∇f‖ = 6.1629e-01, α = 1.00e+00, m = 0, nfg = 1
[ Info: LBFGS: iter    2, time  664.36 s: f = -1.937956278406, ‖∇f‖ = 2.9827e-01, α = 1.00e+00, m = 1, nfg = 1
[ Info: LBFGS: iter    3, time  674.98 s: f = -1.943097558049, ‖∇f‖ = 2.0511e-01, α = 1.00e+00, m = 2, nfg = 1
[ Info: LBFGS: iter    4, time  686.77 s: f = -1.951025956511, ‖∇f‖ = 1.4073e-01, α = 1.00e+00, m = 3, nfg = 1
[ Info: LBFGS: iter    5, time  697.58 s: f = -1.955168827774, ‖∇f‖ = 1.2051e-01, α = 1.00e+00, m = 4, nfg = 1
[ Info: LBFGS: iter    6, time  708.82 s: f = -1.960117970278, ‖∇f‖ = 1.1611e-01, α = 1.00e+00, m = 5, nfg = 1
[ Info: LBFGS: iter    7, time  720.26 s: f = -1.961216550036, ‖∇f‖ = 1.6042e-01, α = 1.00e+00, m = 6, nfg = 1
[ Info: LBFGS: iter    8, time  731.38 s: f = -1.963394812594, ‖∇f‖ = 7.0500e-02, α = 1.00e+00, m = 7, nfg = 1
[ Info: LBFGS: iter    9, time  742.56 s: f = -1.964678496250, ‖∇f‖ = 4.8767e-02, α = 1.00e+00, m = 8, nfg = 1
[ Info: LBFGS: iter   10, time  755.02 s: f = -1.965924354920, ‖∇f‖ = 6.0609e-02, α = 1.00e+00, m = 9, nfg = 1
[ Info: LBFGS: iter   11, time  767.32 s: f = -1.968120587109, ‖∇f‖ = 7.1520e-02, α = 1.00e+00, m = 10, nfg = 1
[ Info: LBFGS: iter   12, time  780.79 s: f = -1.969562403653, ‖∇f‖ = 9.5065e-02, α = 1.00e+00, m = 11, nfg = 1
[ Info: LBFGS: iter   13, time  793.16 s: f = -1.970582005143, ‖∇f‖ = 6.1728e-02, α = 1.00e+00, m = 12, nfg = 1
[ Info: LBFGS: iter   14, time  805.31 s: f = -1.971121191726, ‖∇f‖ = 3.2137e-02, α = 1.00e+00, m = 13, nfg = 1
[ Info: LBFGS: iter   15, time  818.38 s: f = -1.971554453753, ‖∇f‖ = 2.5404e-02, α = 1.00e+00, m = 14, nfg = 1
[ Info: LBFGS: iter   16, time  831.96 s: f = -1.972190061011, ‖∇f‖ = 3.2651e-02, α = 1.00e+00, m = 15, nfg = 1
[ Info: LBFGS: iter   17, time  844.26 s: f = -1.972856212899, ‖∇f‖ = 3.0425e-02, α = 1.00e+00, m = 16, nfg = 1
[ Info: LBFGS: iter   18, time  857.72 s: f = -1.973765750218, ‖∇f‖ = 3.2201e-02, α = 1.00e+00, m = 17, nfg = 1
[ Info: LBFGS: iter   19, time  870.23 s: f = -1.974161602591, ‖∇f‖ = 3.5568e-02, α = 1.00e+00, m = 18, nfg = 1
[ Info: LBFGS: iter   20, time  883.37 s: f = -1.974478336877, ‖∇f‖ = 1.8682e-02, α = 1.00e+00, m = 19, nfg = 1
[ Info: LBFGS: iter   21, time  895.17 s: f = -1.974710833057, ‖∇f‖ = 1.5936e-02, α = 1.00e+00, m = 20, nfg = 1
[ Info: LBFGS: iter   22, time  908.28 s: f = -1.974975741077, ‖∇f‖ = 2.3555e-02, α = 1.00e+00, m = 20, nfg = 1
[ Info: LBFGS: iter   23, time  920.27 s: f = -1.975198522454, ‖∇f‖ = 2.7962e-02, α = 1.00e+00, m = 20, nfg = 1
[ Info: LBFGS: iter   24, time  932.03 s: f = -1.975394248410, ‖∇f‖ = 2.5314e-02, α = 1.00e+00, m = 20, nfg = 1
[ Info: LBFGS: iter   25, time  944.70 s: f = -1.975512455006, ‖∇f‖ = 2.9518e-02, α = 1.00e+00, m = 20, nfg = 1
[ Info: LBFGS: iter   26, time  956.37 s: f = -1.975616686676, ‖∇f‖ = 1.6802e-02, α = 1.00e+00, m = 20, nfg = 1
[ Info: LBFGS: iter   27, time  967.76 s: f = -1.975710241181, ‖∇f‖ = 1.5024e-02, α = 1.00e+00, m = 20, nfg = 1
[ Info: LBFGS: iter   28, time  978.98 s: f = -1.975795271168, ‖∇f‖ = 1.2833e-02, α = 1.00e+00, m = 20, nfg = 1
[ Info: LBFGS: iter   29, time  989.49 s: f = -1.975865143335, ‖∇f‖ = 2.3418e-02, α = 1.00e+00, m = 20, nfg = 1
[ Info: LBFGS: iter   30, time 1002.34 s: f = -1.975957427509, ‖∇f‖ = 1.1381e-02, α = 1.00e+00, m = 20, nfg = 1
[ Info: LBFGS: iter   31, time 1013.66 s: f = -1.976015850153, ‖∇f‖ = 1.0083e-02, α = 1.00e+00, m = 20, nfg = 1
[ Info: LBFGS: iter   32, time 1024.96 s: f = -1.976080164930, ‖∇f‖ = 1.3119e-02, α = 1.00e+00, m = 20, nfg = 1
[ Info: LBFGS: iter   33, time 1037.67 s: f = -1.976152913537, ‖∇f‖ = 1.4611e-02, α = 1.00e+00, m = 20, nfg = 1
[ Info: LBFGS: iter   34, time 1048.69 s: f = -1.976198207069, ‖∇f‖ = 9.6971e-03, α = 1.00e+00, m = 20, nfg = 1
[ Info: LBFGS: iter   35, time 1061.48 s: f = -1.976250601632, ‖∇f‖ = 9.3909e-03, α = 1.00e+00, m = 20, nfg = 1
[ Info: LBFGS: iter   36, time 1073.15 s: f = -1.976291935078, ‖∇f‖ = 1.4028e-02, α = 1.00e+00, m = 20, nfg = 1
[ Info: LBFGS: iter   37, time 1084.60 s: f = -1.976340224013, ‖∇f‖ = 1.0472e-02, α = 1.00e+00, m = 20, nfg = 1
[ Info: LBFGS: iter   38, time 1097.26 s: f = -1.976377344626, ‖∇f‖ = 6.9713e-03, α = 1.00e+00, m = 20, nfg = 1
[ Info: LBFGS: iter   39, time 1109.15 s: f = -1.976411251633, ‖∇f‖ = 6.5882e-03, α = 1.00e+00, m = 20, nfg = 1
[ Info: LBFGS: iter   40, time 1122.25 s: f = -1.976451966987, ‖∇f‖ = 7.7535e-03, α = 1.00e+00, m = 20, nfg = 1
[ Info: LBFGS: iter   41, time 1133.98 s: f = -1.976491819091, ‖∇f‖ = 1.1597e-02, α = 1.00e+00, m = 20, nfg = 1
[ Info: LBFGS: iter   42, time 1145.62 s: f = -1.976529574561, ‖∇f‖ = 8.6643e-03, α = 1.00e+00, m = 20, nfg = 1
[ Info: LBFGS: iter   43, time 1158.08 s: f = -1.976564683972, ‖∇f‖ = 7.6509e-03, α = 1.00e+00, m = 20, nfg = 1
[ Info: LBFGS: iter   44, time 1168.84 s: f = -1.976599313000, ‖∇f‖ = 7.3612e-03, α = 1.00e+00, m = 20, nfg = 1
[ Info: LBFGS: iter   45, time 1181.74 s: f = -1.976636515361, ‖∇f‖ = 1.8259e-02, α = 1.00e+00, m = 20, nfg = 1
[ Info: LBFGS: iter   46, time 1193.53 s: f = -1.976680292934, ‖∇f‖ = 9.8005e-03, α = 1.00e+00, m = 20, nfg = 1
[ Info: LBFGS: iter   47, time 1205.13 s: f = -1.976709176062, ‖∇f‖ = 8.4490e-03, α = 1.00e+00, m = 20, nfg = 1
[ Info: LBFGS: iter   48, time 1216.87 s: f = -1.976735409874, ‖∇f‖ = 1.5557e-02, α = 1.00e+00, m = 20, nfg = 1
[ Info: LBFGS: iter   49, time 1228.56 s: f = -1.976776133103, ‖∇f‖ = 1.3263e-02, α = 1.00e+00, m = 20, nfg = 1
[ Info: LBFGS: iter   50, time 1241.43 s: f = -1.976811137668, ‖∇f‖ = 1.1320e-02, α = 1.00e+00, m = 20, nfg = 1
[ Info: LBFGS: iter   51, time 1265.73 s: f = -1.976843854259, ‖∇f‖ = 1.4911e-02, α = 5.24e-01, m = 20, nfg = 2
[ Info: LBFGS: iter   52, time 1278.46 s: f = -1.976923448868, ‖∇f‖ = 9.7919e-03, α = 1.00e+00, m = 20, nfg = 1
[ Info: LBFGS: iter   53, time 1289.71 s: f = -1.976965559297, ‖∇f‖ = 7.7472e-03, α = 1.00e+00, m = 20, nfg = 1
[ Info: LBFGS: iter   54, time 1301.37 s: f = -1.977044614238, ‖∇f‖ = 1.3785e-02, α = 1.00e+00, m = 20, nfg = 1
[ Info: LBFGS: iter   55, time 1313.26 s: f = -1.977075753938, ‖∇f‖ = 2.0809e-02, α = 1.00e+00, m = 20, nfg = 1
[ Info: LBFGS: iter   56, time 1325.74 s: f = -1.977139445975, ‖∇f‖ = 9.3821e-03, α = 1.00e+00, m = 20, nfg = 1
[ Info: LBFGS: iter   57, time 1336.75 s: f = -1.977165276657, ‖∇f‖ = 7.5268e-03, α = 1.00e+00, m = 20, nfg = 1
[ Info: LBFGS: iter   58, time 1348.24 s: f = -1.977219450466, ‖∇f‖ = 1.1473e-02, α = 1.00e+00, m = 20, nfg = 1
[ Info: LBFGS: iter   59, time 1359.70 s: f = -1.977275314249, ‖∇f‖ = 1.9030e-02, α = 1.00e+00, m = 20, nfg = 1
[ Info: LBFGS: iter   60, time 1372.04 s: f = -1.977335522183, ‖∇f‖ = 1.1000e-02, α = 1.00e+00, m = 20, nfg = 1
[ Info: LBFGS: iter   61, time 1383.33 s: f = -1.977385273141, ‖∇f‖ = 9.0696e-03, α = 1.00e+00, m = 20, nfg = 1
[ Info: LBFGS: iter   62, time 1394.84 s: f = -1.977430128301, ‖∇f‖ = 1.1553e-02, α = 1.00e+00, m = 20, nfg = 1
[ Info: LBFGS: iter   63, time 1406.33 s: f = -1.977469892558, ‖∇f‖ = 8.2877e-03, α = 1.00e+00, m = 20, nfg = 1
[ Info: LBFGS: iter   64, time 1418.79 s: f = -1.977495288994, ‖∇f‖ = 7.9121e-03, α = 1.00e+00, m = 20, nfg = 1
[ Info: LBFGS: iter   65, time 1430.48 s: f = -1.977553209211, ‖∇f‖ = 8.8664e-03, α = 1.00e+00, m = 20, nfg = 1
[ Info: LBFGS: iter   66, time 1443.25 s: f = -1.977597458477, ‖∇f‖ = 1.9052e-02, α = 1.00e+00, m = 20, nfg = 1
[ Info: LBFGS: iter   67, time 1453.49 s: f = -1.977660134707, ‖∇f‖ = 1.1063e-02, α = 1.00e+00, m = 20, nfg = 1
[ Info: LBFGS: iter   68, time 1465.98 s: f = -1.977685405419, ‖∇f‖ = 2.0774e-02, α = 1.00e+00, m = 20, nfg = 1
[ Info: LBFGS: iter   69, time 1476.58 s: f = -1.977717638754, ‖∇f‖ = 8.7390e-03, α = 1.00e+00, m = 20, nfg = 1
[ Info: LBFGS: iter   70, time 1488.80 s: f = -1.977730883236, ‖∇f‖ = 7.0000e-03, α = 1.00e+00, m = 20, nfg = 1
[ Info: LBFGS: iter   71, time 1500.40 s: f = -1.977768669053, ‖∇f‖ = 8.1853e-03, α = 1.00e+00, m = 20, nfg = 1
[ Info: LBFGS: iter   72, time 1511.94 s: f = -1.977810913385, ‖∇f‖ = 1.0740e-02, α = 1.00e+00, m = 20, nfg = 1
[ Info: LBFGS: iter   73, time 1523.64 s: f = -1.977817034893, ‖∇f‖ = 1.7372e-02, α = 1.00e+00, m = 20, nfg = 1
[ Info: LBFGS: iter   74, time 1535.46 s: f = -1.977859790789, ‖∇f‖ = 6.3128e-03, α = 1.00e+00, m = 20, nfg = 1
[ Info: LBFGS: iter   75, time 1547.05 s: f = -1.977877604524, ‖∇f‖ = 5.9647e-03, α = 1.00e+00, m = 20, nfg = 1
[ Info: LBFGS: iter   76, time 1559.50 s: f = -1.977904920943, ‖∇f‖ = 8.3968e-03, α = 1.00e+00, m = 20, nfg = 1
[ Info: LBFGS: iter   77, time 1571.15 s: f = -1.977943608891, ‖∇f‖ = 1.1579e-02, α = 1.00e+00, m = 20, nfg = 1
[ Info: LBFGS: iter   78, time 1582.92 s: f = -1.977960278758, ‖∇f‖ = 1.3388e-02, α = 1.00e+00, m = 20, nfg = 1
[ Info: LBFGS: iter   79, time 1595.75 s: f = -1.977963135797, ‖∇f‖ = 1.0697e-02, α = 1.00e+00, m = 20, nfg = 1
┌ Warning: LBFGS: not converged to requested tol after 80 iterations and time 1606.72 s: f = -1.977988611344, ‖∇f‖ = 1.6698e-02
└ @ OptimKit ~/.julia/packages/OptimKit/G6i79/src/lbfgs.jl:197

Finally, we compare the variationally optimized energy against the reference energy. Indeed, we find that the additional AD-based optimization improves the SU-evolved PEPS and leads to a more accurate energy estimate.

E_opt /= (Nr * Nc)
@show E_opt
@show (E_opt - E_ref) / abs(E_ref);
E_opt = -0.4944971528359841
(E_opt - E_ref) / abs(E_ref) = -0.0005000563196440829

This page was generated using Literate.jl.