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);

Running the simple update algorithm

Next, we'll specify the virtual PEPS bond dimension and define the fermionic physical and virtual spaces. The simple update algorithm evolves an infinite PEPS with weights on the virtual bonds, so we here need to intialize an InfiniteWeightPEPS. By default, the bond weights will be identity. Unlike in the other examples, we here use tensors with real Float64 entries:

Dbond = 8
physical_space = Vect[fℤ₂](0 => 2, 1 => 2)
virtual_space = Vect[fℤ₂](0 => Dbond / 2, 1 => Dbond / 2)
wpeps = InfiniteWeightPEPS(rand, Float64, physical_space, virtual_space; unitcell=(Nr, Nc));

Let's set the algorithm parameters: The plan is to successively decrease the time interval of the Trotter-Suzuki as well as the convergence tolerance such that we obtain a more accurate result at each iteration. To run the simple update, we call simpleupdate where we use the keyword bipartite=false - meaning that we use the full $2 \times 2$ unit cell without assuming a bipartite structure. Thus, we can start evolving:

dts = [1e-2, 1e-3, 4e-4, 1e-4]
tols = [1e-6, 1e-8, 1e-8, 1e-8]
maxiter = 20000

for (n, (dt, tol)) in enumerate(zip(dts, tols))
    trscheme = truncerr(1e-10) & truncdim(Dbond)
    alg = SimpleUpdate(dt, tol, maxiter, trscheme)
    global wpeps, = simpleupdate(wpeps, H, alg; bipartite=false)
end
[ Info: Space of x-weight at [1, 1] = Vect[FermionParity](0=>4, 1=>4)
[ Info: SU iter 1      :  dt = 1e-02,  weight diff = 2.355e+00,  time = 22.185 sec
[ Info: Space of x-weight at [1, 1] = Vect[FermionParity](0=>4, 1=>4)
[ Info: SU iter 500    :  dt = 1e-02,  weight diff = 3.984e-04,  time = 0.015 sec
[ Info: Space of x-weight at [1, 1] = Vect[FermionParity](0=>4, 1=>4)
[ Info: SU iter 1000   :  dt = 1e-02,  weight diff = 2.866e-06,  time = 0.014 sec
[ Info: Space of x-weight at [1, 1] = Vect[FermionParity](0=>4, 1=>4)
[ Info: SU conv 1061   :  dt = 1e-02,  weight diff = 9.956e-07,  time = 41.388 sec
[ Info: Space of x-weight at [1, 1] = Vect[FermionParity](0=>4, 1=>4)
[ Info: SU iter 1      :  dt = 1e-03,  weight diff = 6.070e-03,  time = 0.014 sec
[ Info: Space of x-weight at [1, 1] = Vect[FermionParity](0=>4, 1=>4)
[ Info: SU iter 500    :  dt = 1e-03,  weight diff = 1.874e-06,  time = 0.024 sec
[ Info: Space of x-weight at [1, 1] = Vect[FermionParity](0=>4, 1=>4)
[ Info: SU iter 1000   :  dt = 1e-03,  weight diff = 6.437e-07,  time = 0.021 sec
[ Info: Space of x-weight at [1, 1] = Vect[FermionParity](0=>4, 1=>4)
[ Info: SU iter 1500   :  dt = 1e-03,  weight diff = 2.591e-07,  time = 0.014 sec
[ Info: Space of x-weight at [1, 1] = Vect[FermionParity](0=>4, 1=>4)
[ Info: SU iter 2000   :  dt = 1e-03,  weight diff = 1.053e-07,  time = 0.014 sec
[ Info: Space of x-weight at [1, 1] = Vect[FermionParity](0=>4, 1=>4)
[ Info: SU iter 2500   :  dt = 1e-03,  weight diff = 4.280e-08,  time = 0.014 sec
[ Info: Space of x-weight at [1, 1] = Vect[FermionParity](0=>4, 1=>4)
[ Info: SU iter 3000   :  dt = 1e-03,  weight diff = 1.741e-08,  time = 0.021 sec
[ Info: Space of x-weight at [1, 1] = Vect[FermionParity](0=>4, 1=>4)
[ Info: SU conv 3309   :  dt = 1e-03,  weight diff = 9.983e-09,  time = 58.103 sec
[ Info: Space of x-weight at [1, 1] = Vect[FermionParity](0=>4, 1=>4)
[ Info: SU iter 1      :  dt = 4e-04,  weight diff = 4.030e-04,  time = 0.014 sec
[ Info: Space of x-weight at [1, 1] = Vect[FermionParity](0=>4, 1=>4)
[ Info: SU iter 500    :  dt = 4e-04,  weight diff = 1.776e-07,  time = 0.016 sec
[ Info: Space of x-weight at [1, 1] = Vect[FermionParity](0=>4, 1=>4)
[ Info: SU iter 1000   :  dt = 4e-04,  weight diff = 7.091e-08,  time = 0.016 sec
[ Info: Space of x-weight at [1, 1] = Vect[FermionParity](0=>4, 1=>4)
[ Info: SU iter 1500   :  dt = 4e-04,  weight diff = 3.997e-08,  time = 0.021 sec
[ Info: Space of x-weight at [1, 1] = Vect[FermionParity](0=>4, 1=>4)
[ Info: SU iter 2000   :  dt = 4e-04,  weight diff = 2.622e-08,  time = 0.016 sec
[ Info: Space of x-weight at [1, 1] = Vect[FermionParity](0=>4, 1=>4)
[ Info: SU iter 2500   :  dt = 4e-04,  weight diff = 1.796e-08,  time = 0.021 sec
[ Info: Space of x-weight at [1, 1] = Vect[FermionParity](0=>4, 1=>4)
[ Info: SU iter 3000   :  dt = 4e-04,  weight diff = 1.245e-08,  time = 0.014 sec
[ Info: Space of x-weight at [1, 1] = Vect[FermionParity](0=>4, 1=>4)
[ Info: SU conv 3303   :  dt = 4e-04,  weight diff = 9.997e-09,  time = 57.388 sec
[ Info: Space of x-weight at [1, 1] = Vect[FermionParity](0=>4, 1=>4)
[ Info: SU iter 1      :  dt = 1e-04,  weight diff = 2.014e-04,  time = 0.014 sec
[ Info: Space of x-weight at [1, 1] = Vect[FermionParity](0=>4, 1=>4)
[ Info: SU iter 500    :  dt = 1e-04,  weight diff = 5.664e-08,  time = 0.016 sec
[ Info: Space of x-weight at [1, 1] = Vect[FermionParity](0=>4, 1=>4)
[ Info: SU iter 1000   :  dt = 1e-04,  weight diff = 4.106e-08,  time = 0.014 sec
[ Info: Space of x-weight at [1, 1] = Vect[FermionParity](0=>4, 1=>4)
[ Info: SU iter 1500   :  dt = 1e-04,  weight diff = 3.033e-08,  time = 0.021 sec
[ Info: Space of x-weight at [1, 1] = Vect[FermionParity](0=>4, 1=>4)
[ Info: SU iter 2000   :  dt = 1e-04,  weight diff = 2.290e-08,  time = 0.021 sec
[ Info: Space of x-weight at [1, 1] = Vect[FermionParity](0=>4, 1=>4)
[ Info: SU iter 2500   :  dt = 1e-04,  weight diff = 1.773e-08,  time = 0.014 sec
[ Info: Space of x-weight at [1, 1] = Vect[FermionParity](0=>4, 1=>4)
[ Info: SU iter 3000   :  dt = 1e-04,  weight diff = 1.410e-08,  time = 0.015 sec
[ Info: Space of x-weight at [1, 1] = Vect[FermionParity](0=>4, 1=>4)
[ Info: SU iter 3500   :  dt = 1e-04,  weight diff = 1.152e-08,  time = 0.023 sec
[ Info: Space of x-weight at [1, 1] = Vect[FermionParity](0=>4, 1=>4)
[ Info: SU conv 3893   :  dt = 1e-04,  weight diff = 9.997e-09,  time = 69.269 sec

To obtain the evolved InfiniteWeightPEPS as an actual PEPS without weights on the bonds, we can just call the following constructor:

peps = InfinitePEPS(wpeps);

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)

env = CTMRGEnv(rand, Float64, peps, env_space)
for χ in [χenv₀, χenv]
    global env, = leading_boundary(
        env, peps; alg=:sequential, tol=1e-5, trscheme=truncdim(χ)
    )
end
[ Info: CTMRG init:	obj = -1.542952315399e-10	err = 1.0000e+00
┌ Warning: CTMRG cancel 100:	obj = +6.651673256987e-01	err = 4.3704037477e-01	time = 34.96 sec
└ @ PEPSKit ~/repos/PEPSKit.jl/src/algorithms/ctmrg/ctmrg.jl:155
[ Info: CTMRG init:	obj = +6.651673256987e-01	err = 1.0000e+00
[ Info: CTMRG conv 38:	obj = +5.888235917397e-01	err = 5.1440892086e-06	time = 1.54 min

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.6333025742768026

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) / E_exact;
(E - E_exact) / E_exact = -0.006398508415565196

This page was generated using Literate.jl.