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

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