Simple update for the Fermi-Hubbard model at half-filling

Once again, we consider the Hubbard model but this time we obtain the ground-state PEPS by imaginary time evolution. In particular, we'll use the SimpleUpdate algorithm. As a reminder, we define the Hubbard model as

\[H = -t \sum_{\langle i,j \rangle} \sum_{\sigma} \left( c_{i,\sigma}^+ c_{j,\sigma}^- - c_{i,\sigma}^- c_{j,\sigma}^+ \right) + U \sum_i n_{i,\uparrow}n_{i,\downarrow} - \mu \sum_i n_i\]

with $\sigma \in \{\uparrow,\downarrow\}$ and $n_{i,\sigma} = c_{i,\sigma}^+ c_{i,\sigma}^-$.

Let's get started by seeding the RNG and importing the required modules:

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

Defining the Hamiltonian

First, we define the Hubbard model at $t=1$ hopping and $U=6$ using Trivial sectors for the particle and spin symmetries, and set $\mu = U/2$ for half-filling. The model will be constructed on a $2 \times 2$ unit cell, so we have:

t = 1
U = 6
Nr, Nc = 2, 2
H = hubbard_model(Float64, Trivial, Trivial, InfiniteSquare(Nr, Nc); t, U, mu = U / 2);
physical_space = Vect[fℤ₂](0 => 2, 1 => 2);

Running the simple update algorithm

Suppose the goal is to use imaginary-time simple update to optimize a PEPS with bond dimension D = 8, and $2 \times 2$ unit cells. For a challenging model like the Hubbard model, a naive evolution starting from a random PEPS at D = 8 will almost always produce a sub-optimal state. In this example, we shall demonstrate some common practices to improve SU result.

First, we shall use a small D for the random PEPS initialization, which is chosen as 4 here. For convenience, here we work with real tensors with Float64 entries. The bond weights are still initialized as identity matrices.

virtual_space = Vect[fℤ₂](0 => 2, 1 => 2)
peps = InfinitePEPS(rand, Float64, physical_space, virtual_space; unitcell = (Nr, Nc));
wts = SUWeight(peps);

Starting from the random state, we first use a relatively large evolution time step dt = 1e-2. After convergence at D = 4, to avoid stucking at some bad local minimum, we first increase D to 12, and drop it back to D = 8 after a while. Afterwards, we keep D = 8 and gradually decrease dt to 1e-4 to improve convergence.

dts = [1.0e-2, 1.0e-2, 1.0e-3, 4.0e-4, 1.0e-4]
tols = [1.0e-7, 1.0e-7, 1.0e-8, 1.0e-8, 1.0e-8]
Ds = [4, 12, 8, 8, 8]
maxiter = 20000

for (dt, tol, Dbond) in zip(dts, tols, Ds)
    trscheme = truncerr(1.0e-10) & truncdim(Dbond)
    alg = SimpleUpdate(dt, tol, maxiter, trscheme)
    global peps, wts, = simpleupdate(
        peps, H, alg, wts; bipartite = false, check_interval = 2000
    )
end
[ Info: Space of x-weight at [1, 1] = Vect[FermionParity](0=>2, 1=>2)
[ Info: SU iter 1      :  dt = 1e-02,  weight diff = 1.316e+00,  time = 27.486 sec
[ Info: Space of x-weight at [1, 1] = Vect[FermionParity](0=>2, 1=>2)
[ Info: SU conv 1045   :  dt = 1e-02,  weight diff = 9.843e-08,  time = 34.956 sec
[ Info: Space of x-weight at [1, 1] = Vect[FermionParity](0=>6, 1=>6)
[ Info: SU iter 1      :  dt = 1e-02,  weight diff = 6.459e-06,  time = 0.077 sec
[ Info: Space of x-weight at [1, 1] = Vect[FermionParity](0=>6, 1=>6)
[ Info: SU conv 584    :  dt = 1e-02,  weight diff = 9.946e-08,  time = 42.322 sec
[ Info: Space of x-weight at [1, 1] = Vect[FermionParity](0=>3, 1=>5)
[ Info: SU iter 1      :  dt = 1e-03,  weight diff = 5.245e-03,  time = 0.235 sec
[ Info: Space of x-weight at [1, 1] = Vect[FermionParity](0=>3, 1=>5)
[ Info: SU iter 2000   :  dt = 1e-03,  weight diff = 1.418e-07,  time = 0.018 sec
[ Info: Space of x-weight at [1, 1] = Vect[FermionParity](0=>3, 1=>5)
[ Info: SU conv 3791   :  dt = 1e-03,  weight diff = 9.990e-09,  time = 78.783 sec
[ Info: Space of x-weight at [1, 1] = Vect[FermionParity](0=>3, 1=>5)
[ Info: SU iter 1      :  dt = 4e-04,  weight diff = 3.256e-04,  time = 0.018 sec
[ Info: Space of x-weight at [1, 1] = Vect[FermionParity](0=>3, 1=>5)
[ Info: SU iter 2000   :  dt = 4e-04,  weight diff = 1.888e-08,  time = 0.024 sec
[ Info: Space of x-weight at [1, 1] = Vect[FermionParity](0=>3, 1=>5)
[ Info: SU conv 3034   :  dt = 4e-04,  weight diff = 9.997e-09,  time = 62.113 sec
[ Info: Space of x-weight at [1, 1] = Vect[FermionParity](0=>3, 1=>5)
[ Info: SU iter 1      :  dt = 1e-04,  weight diff = 1.627e-04,  time = 0.024 sec
[ Info: Space of x-weight at [1, 1] = Vect[FermionParity](0=>3, 1=>5)
[ Info: SU iter 2000   :  dt = 1e-04,  weight diff = 1.532e-08,  time = 0.018 sec
[ Info: Space of x-weight at [1, 1] = Vect[FermionParity](0=>3, 1=>5)
[ Info: SU conv 2916   :  dt = 1e-04,  weight diff = 9.997e-09,  time = 59.560 sec

Computing the ground-state energy

In order to compute the energy expectation value with evolved PEPS, we need to converge a CTMRG environment on it. We first converge an environment with a small enviroment dimension and then use that to initialize another run with bigger environment dimension. We'll use trscheme=truncdim(χ) for that such that the dimension is increased during the second CTMRG run:

χenv₀, χenv = 6, 16
env_space = Vect[fℤ₂](0 => χenv₀ / 2, 1 => χenv₀ / 2)
normalize!.(peps.A, Inf)
env = CTMRGEnv(rand, Float64, peps, env_space)
for χ in [χenv₀, χenv]
    global env, = leading_boundary(
        env, peps; alg = :sequential, tol = 1.0e-8, maxiter = 50, trscheme = truncdim(χ)
    )
end
[ Info: CTMRG init:	obj = +4.034556135739e-13	err = 1.0000e+00
┌ Warning: CTMRG cancel 50:	obj = +1.777694990783e+00	err = 2.1447151954e-06	time = 18.75 sec
└ @ PEPSKit ~/PEPSKit.jl/src/algorithms/ctmrg/ctmrg.jl:152
[ Info: CTMRG init:	obj = +1.777694990783e+00	err = 1.0000e+00
[ Info: CTMRG conv 7:	obj = +1.781063096355e+00	err = 3.5793745596e-10	time = 21.32 sec

We measure the energy by computing the H expectation value, where we have to make sure to normalize with respect to the unit cell to obtain the energy per site:

E = expectation_value(peps, H, env) / (Nr * Nc)
@show E;
E = -3.652497562261351

Finally, we can compare the obtained ground-state energy against the literature, namely the QMC estimates from Qin et al.. We find that the results generally agree:

Es_exact = Dict(0 => -1.62, 2 => -0.176, 4 => 0.8603, 6 => -0.6567, 8 => -0.5243)
E_exact = Es_exact[U] - U / 2
@show (E - E_exact) / abs(E_exact);
(E - E_exact) / abs(E_exact) = 0.001149243235334783

This page was generated using Literate.jl.