Tuto 4: the Bose-Hubbard system
Dr. Saad Yalouz - Laboratoire de Chimie Quantique de Strasbourg, France - July 2022
Introduction : the Bose-Hubbard system
The Bose-Hubbard Hamiltonian is defined in the site basis as follows
where \(h_{ij}\) and \(U_{iiii}\) are the two- and four-indices integrals associated respectively to the single-body and the two-body part of the full Hamiltonian.
In an extended basis (i.e. in a basis of non-local modes), the same Hamiltonian takes the most general form
where the single-body integrals have been transformed (thanks to a transfer matrix \(C\)) into an extended basis such that
and similarily for the two-body integrals
In this more general context, the energy of a given reference many-body state \(| \Psi \rangle\) is defined as follows
where \(\gamma\) represents the so-called 1-RDM whose elements are defined like
and \(\Gamma\) the 2-RDM with the elements
We show below how to build all these objects with QuantNBody.
Importing the required libraries
import quantnbody as qnb
import scipy
import numpy as np
Defining the basic properties of the system
n_mode = 5 # Number of modes
n_boson = 5 # Number of bosons
# Building the one-body tensor in a general extended basis
h_tensor = np.zeros(( n_mode, n_mode ))
for site in range(n_mode):
for site_ in range(n_mode):
if (site != site_):
h_tensor[site,site_] = h_tensor[site_,site] = -1 # <== a lattice fully connected with a same hopping term
# Building the two-body tensor in a general extended basis
U_tensor = np.zeros(( n_mode, n_mode, n_mode, n_mode ))
for site in range(n_mode):
U_tensor[ site, site, site, site ] = - 10.1 # <=== Local on-site attraction of the bosons
# # Uncomment below in case we want to switch to an extended basis
# eig_h, C = scipy.linalg.eigh( h_tensor ) # <== Extended basis simply given by the eigenmode of h_tensor
# h_tensor, U_tensor = qnb.bosonic.tools.transform_1_2_body_tensors_in_new_basis(h_tensor, U_tensor, C)
Building the essential tools for the QuantNBody package
# Building the many-body basis
nbodybasis = qnb.bosonic.tools.build_nbody_basis( n_mode, n_boson )
# Building the a†a operators
a_dagger_a = qnb.bosonic.tools.build_operator_a_dagger_a( nbodybasis )
All-in-one function
We define below an “all-in-one” function that returns :
Bose-Hubbard Hamiltonian
Groundstate FCI energy
Groundstate wavefunction
Groundstate 1- and 2-RDMs.
def Bose_hubbard_all_in_one( h_tensor, U_tensor, nbodybasis, a_dagger_a ):
# Building the matrix representation of the Hamiltonian operators
Hamiltonian = qnb.bosonic.tools.build_hamiltonian_bose_hubbard( h_tensor,
U_tensor,
nbodybasis,
a_dagger_a )
eig_en, eig_vec = scipy.linalg.eigh( Hamiltonian.A )
GS_WFT = eig_vec[:,0]
GS_energy = eig_en[0]
GS_one_rdm = qnb.bosonic.tools.build_1rdm( GS_WFT, a_dagger_a )
GS_two_rdm = qnb.bosonic.tools.build_2rdm( GS_WFT, a_dagger_a )
return Hamiltonian, GS_energy, GS_WFT, GS_one_rdm, GS_two_rdm
Applying the function to get information from the system
Hamiltonian, GS_energy, GS_WFT, GS_one_rdm, GS_two_rdm = Bose_hubbard_all_in_one( h_tensor,
U_tensor,
nbodybasis,
a_dagger_a )
Visualizing the resulting wavefunction in the many-body basis
qnb.bosonic.tools.visualize_wft( GS_WFT, nbodybasis )
print()
-----------
Coeff. N-body state
------- -------------
+0.44648 |0,0,5,0,0⟩
+0.44648 |0,0,0,0,5⟩
+0.44648 |0,0,0,5,0⟩
+0.44648 |0,5,0,0,0⟩
+0.44648 |5,0,0,0,0⟩
+0.01283 |0,0,4,0,1⟩
+0.01283 |0,0,4,1,0⟩
+0.01283 |0,1,4,0,0⟩
Checking the implementation : comparing different ways to estimate the groundstate energy
In order to check if everything is correct, we can compare the resulting GS energy. First, let us evaluate it via the left/right projections on the Hamiltonian \(\langle \Psi | H |\Psi\rangle\) as shown below
E_projection = GS_WFT.T @ Hamiltonian @ GS_WFT # <== Very simple and intuitive
Then using our knowledge of the groundstate RDMs (as shown at the begining of the notebook), this can be done like this
E_with_RDMs = ( np.einsum( 'pq,pq->', h_tensor, GS_one_rdm, optimize=True) # <== A bit more elaborated
+ np.einsum( 'pqrs,pqrs->', U_tensor, GS_two_rdm, optimize=True) )
And we can finally compare all these results to the one provided by the “all in one function” :
print("GS energy estimations ======================== ")
print( "With the all in one function", GS_energy )
print( "With the projection method ", E_projection )
print( "With the RDMs method ", E_with_RDMs )
GS energy estimations ========================
With the all in one function -202.25704161029097
With the projection method -202.25704161029097
With the RDMs method -202.257041610291
we should obtain exactly the same thing !