Many-Body Localization in the Fermi-Hubbard Model

This example shows how to code up the disordered Fermi-Hubbard chain:

\[H = -J\sum_{i=0,\sigma}^{L-2} \left(c^\dagger_{i\sigma}c_{i+1,\sigma} - c_{i\sigma}c^\dagger_{i+1,\sigma}\right) +U\sum_{i=0}^{L-1} n_{i\uparrow }n_{i\downarrow } + \sum_{i=0,\sigma}^{L-1} V_i n_{i\sigma}.\]

Details about the code below can be found in SciPost Phys. 7, 020 (2019).

Script

download script

  1from quspin.operators import hamiltonian, exp_op, quantum_operator  # operators
  2from quspin.basis import spinful_fermion_basis_1d  # Hilbert space basis
  3from quspin.tools.measurements import obs_vs_time  # calculating dynamics
  4import numpy as np  # general math functions
  5from numpy.random import uniform, choice  # tools for doing random sampling
  6from time import time  # tool for calculating computation time
  7import matplotlib.pyplot as plt  # plotting library
  8
  9#####################################################################
 10#                            example 6                              #
 11#   In this script we demonstrate how to use QuSpin's to create	    #
 12#   a disordered Fermi-Hubbard model with a parameter-dependent     #
 13#   Hamiltonian, and measure the imbalance on different lattice     #
 14#   sites (see arXiv:1501.05661). We also show how to prepare       #
 15#   fermion Fock states, and do disorder averaging.                 #
 16#####################################################################
 17##### setting parameters for simulation
 18# simulation parameters
 19n_real = 100  # number of realizations
 20n_boot = 100  # number of bootstrap samples to calculate error
 21# physical parameters
 22L = 8  # system size
 23N = L // 2  # number of particles
 24N_up = N // 2 + N % 2  # number of fermions with spin up
 25N_down = N // 2  # number of fermions with spin down
 26w_list = [1.0, 4.0, 10.0]  # disorder strength
 27J = 1.0  # hopping strength
 28U = 5.0  # interaction strength
 29# range in time to evolve system
 30start, stop, num = 0.0, 35.0, 101
 31t = np.linspace(start, stop, num=num, endpoint=True)
 32#
 33###### create the basis
 34# build spinful fermions basis
 35basis = spinful_fermion_basis_1d(L, Nf=(N_up, N_down))
 36#
 37##### create model
 38# define site-coupling lists
 39hop_right = [[J, i, i + 1] for i in range(L - 1)]  # hopping to the right OBC
 40hop_left = [[+J, i, i + 1] for i in range(L - 1)]  # hopping to the left OBC
 41int_list = [[U, i, i] for i in range(L)]  # onsite interaction
 42# site-coupling list to create the sublattice imbalance observable
 43sublat_list = [[(-1.0) ** i / N, i] for i in range(0, L)]
 44# create static lists
 45operator_list_0 = [
 46    ["+-|", hop_left],  # up hop left
 47    ["-+|", hop_right],  # up hop right
 48    ["|+-", hop_left],  # down hop left
 49    ["|-+", hop_right],  # down hop right
 50    ["n|n", int_list],  # onsite interaction
 51]
 52imbalance_list = [["n|", sublat_list], ["|n", sublat_list]]
 53# create operator dictionary for quantum_operator class
 54# add key for Hubbard hamiltonian
 55operator_dict = dict(H0=operator_list_0)
 56# add keys for local potential in each site
 57for i in range(L):
 58    # add to dictioanry keys h0,h1,h2,...,hL with local potential operator
 59    operator_dict["n" + str(i)] = [["n|", [[1.0, i]]], ["|n", [[1.0, i]]]]
 60#
 61###### setting up operators
 62# set up hamiltonian dictionary and observable (imbalance I)
 63no_checks = dict(check_pcon=False, check_symm=False, check_herm=False)
 64H_dict = quantum_operator(operator_dict, basis=basis, **no_checks)
 65I = hamiltonian(imbalance_list, [], basis=basis, **no_checks)
 66# strings which represent the initial state
 67s_up = "".join("1000" for i in range(N_up))
 68s_down = "".join("0010" for i in range(N_down))
 69# basis.index accepts strings and returns the index
 70# which corresponds to that state in the basis list
 71i_0 = basis.index(s_up, s_down)  # find index of product state
 72psi_0 = np.zeros(basis.Ns)  # allocate space for state
 73psi_0[i_0] = 1.0  # set MB state to be the given product state
 74print(
 75    "H-space size: {:d}, initial state: |{:s}>(x)|{:s}>".format(basis.Ns, s_up, s_down)
 76)
 77
 78
 79#
 80# define function to do dynamics for different disorder realizations.
 81def real(H_dict, I, psi_0, w, t, i):
 82    # body of function goes below
 83    ti = time()  # start timing function for duration of reach realisation
 84    # create a parameter list which specifies the onsite potential with disorder
 85    params_dict = dict(H0=1.0)
 86    for j in range(L):
 87        params_dict["n" + str(j)] = uniform(-w, w)
 88    # using the parameters dictionary construct a hamiltonian object with those
 89    # parameters defined in the list
 90    H = H_dict.tohamiltonian(params_dict)
 91    # use exp_op to get the evolution operator
 92    U = exp_op(H, a=-1j, start=t.min(), stop=t.max(), num=len(t), iterate=True)
 93    psi_t = U.dot(psi_0)  # get generator psi_t for time evolved state
 94    # use obs_vs_time to evaluate the dynamics
 95    t = U.grid  # extract time grid stored in U, and defined in exp_op
 96    obs_t = obs_vs_time(psi_t, t, dict(I=I))
 97    # print reporting the computation time for realization
 98    print("realization {}/{} completed in {:.2f} s".format(i + 1, n_real, time() - ti))
 99    # return observable values
100    return obs_t["I"].real
101
102
103#
104###### looping over different disorder strengths
105for w in w_list:
106    I_data = np.vstack([real(H_dict, I, psi_0, w, t, i) for i in range(n_real)])
107    ##### averaging and error estimation
108    I_avg = I_data.mean(axis=0)  # get mean value of I for all time points
109    # generate bootstrap samples
110    bootstrap_gen = (
111        I_data[choice(n_real, size=n_real)].mean(axis=0) for i in range(n_boot)
112    )
113    # generate the fluctuations about the mean of I
114    sq_fluc_gen = ((bootstrap - I_avg) ** 2 for bootstrap in bootstrap_gen)
115    I_error = np.sqrt(sum(sq_fluc_gen) / n_boot)
116    ##### plotting results
117    plt.errorbar(t, I_avg, I_error, marker=".", label="w={:.2f}".format(w))
118# configuring plots
119plt.xlabel("$Jt$", fontsize=18)
120plt.ylabel("$\\mathcal{I}$", fontsize=18)
121plt.grid(True)
122plt.tick_params(labelsize=16)
123plt.legend(loc=0)
124plt.tight_layout()
125plt.savefig("fermion_MBL.pdf", bbox_inches="tight")
126# plt.show()
127plt.close()