Download this notebook (.ipynb + data files)
7. Hubbard Model with \(t'\)#
Author: Kyle Eskridge
In this tutorial, we will perform AFQMC calculations for the Hubbard model with \(t'\) on a 4x8 lattice. The Hamiltonian is given by,
See Table I of reference [1] The AFQMC energy using a free-electron Trial wavefunction is, for \(U=4\) on a 4x8 square lattice with \(N_{\uparrow} = N_{\downarrow} = 16\), \(−27.271(1)\) versus \(−27.6924\) from DMRG. (Better AFQMC results exist there when using a self-consistent procedure).
References:
https://journals.aps.org/prresearch/abstract/10.1103/PhysRevResearch.3.013065
# simple setup
from tutorial_utils import run_afqmc
from pathlib import Path
scratch_dir = Path("data")
scratch_dir.mkdir(parents=True, exist_ok=True)
7.1. Setup - Make a lattice#
from afqmctools.systems.lattice import get_lattice
from afqmctools.utils.visualize import plot_lattice
lattice_params = {
"L1" : 4,
"L2" : 8,
"boundary1" : "pbc",
"boundary2" : "open"
}
lattice = get_lattice(lattice_params)
plot_lattice(lattice,show_coords=False)
7.2. Setup - Make the Hamiltonian#
…
from afqmctools.hamiltonian.model.director import HamiltonianDirector
from afqmctools.utils.io import write_model_hamiltonian
#TODO: need AFM (staggered) pinning at y=1 and y=Ly with h_pin = 0.25 only for AFQMC!!
hamiltonian_params = {
"hamiltonian" : {
"t" : [1.0,0.3],
"U" : 4
}
}
director = HamiltonianDirector(hamiltonian_params,lattice=lattice)
# we can take more direct control of build steps like this
builder = director.release_builder()
builder.afm_pinning(h_afm_pin=0.25,axis=1,pin_type="same")
# ... and we can resume using the director as usual
director.bind_builder(builder)
hamiltonian = director.build()
write_model_hamiltonian(hamiltonian,fname=scratch_dir/"Hubbard_tprime0.4_U4.0.h5")
7.3. Setup - Trial Wavefunction.#
# First, let's compute the Free-Electron trial wavefunction
from afqmctools.wavefunction.free_electron import free_electron
from afqmctools.wavefunction.common import write_wfn
nelec = (16,16)
input_params = dict(
lattice = lattice_params, # from step 1. above
hamiltonian = hamiltonian_params["hamiltonian"] # from step 2. above
)
# twist from ref 1:
twist = (0.0,0.0) #(0.01,0.01) # this twist was used for 4x16 with t' = 0.3t
wfn,_,results = free_electron(
source=input_params,
nelec=nelec,
twist=twist, # (optional) using the default small twist
return_autohf = True
)
# get lattice dimensions from the Lattice instance
L = lattice.L
write_wfn(scratch_dir/"free_elec_wfn.h5", wfn, walker_type='collinear', norb=L[0]*L[1], nelec=nelec)
import afqmctools.utils.visualize as vis
# convert the 'results' from autohf to a charge density
makeRDMs = results[1]['makeRDMs']
state = results[0]['state']
rdm = makeRDMs(state)
# collinear
rho_up = rdm[0].diagonal().reshape(*L)
rho_down = rdm[1].diagonal().reshape(*L)
rho_charge = rho_up + rho_down
#rho_spin = 0.5*(rho_up - rho_down)
vis.plot_lattice(
lattice,
density=rho_charge.real,
density_label="charge density",
vmin=0.0,
vmax=2.0
)
rho_spin = 0.5*(rho_up - rho_down)
vis.plot_lattice(
lattice,
density=rho_spin.real,
density_label="spin density",
vmin=-0.5,
vmax=0.5
)
7.4. Setup - Write an SAFIRE input file#
# make a json input file
from afqmctools.inputs.from_hdf import write_json
afqmc_params = {
"timestep": 0.01,
"steps": 12000,
"n_walkers_per_mpi_task": 30,
"population_control_interval": 5,
"walker_ortho_interval": 5,
"measure_interval_multiplier": 2,
"seed" : 42, # for reproducibility
"propagator": {
"use_cp_constraint": True,
"use_real_vbias": True
},
"estimator": {
"name":"back_propagation",
"path_restoration": True,
"bp_walker_ortho_interval": 5,
"measure_interval_multiplier": [40, 80, 120, 160],
"equil_multiplier":480,
"onerdm":{
"name":"onerdm"
}
}
}
write_json(
scratch_dir/"afqmc.json",
fwfn0=scratch_dir/"free_elec_wfn.h5",
fham0=scratch_dir/"Hubbard_tprime0.4_U4.0.h5",
exec_opts=afqmc_params
)
7.4.1. Running AFQMC#
Below, we provide a cell that will run AFQMC here; however, for this calculation, you might prefer to submit the job to Slurm.
You can copy the runscript from /mnt/home/beskridge/ceph/software/SAFIRE/local_scripts/run_afqmc_cpu.sh to your scratch
directory for this tutorial and use
sbatch run_afqmc_cpu.sh
to submit the job (perhaps adjusting the run time).
run_afqmc(
run_dir=scratch_dir,
output_file=None, # this will dump the output from AFQMC here
np=16, # set number of MPI tasks
timeout_mins=20
)
7.5. Analysis - compute the AFQMC energy#
we’re looking for −27.271(1) from reference [1]
I’m currently at -26.778445 +/- 0.009127.
Let’s double check the paper.
from stats.scalar_dat import analyze_scalar_data
nequil = 2.0 #t^{-1}
analysis_settings = dict(
fname = scratch_dir/"qmc.s000.scalar.dat",
xaxis = "time", # use units of imaginary time for equilibration
nequil = nequil, # length of equilibration phase (in units of imaginary time)
trace = True, # plots a trace of the scalar data
)
E,dE = analyze_scalar_data(analysis_settings)
print(f"The AFQMC energy is {E:.6f} +/- {dE:.6f} Hartree")
7.6. Analysis - compute the Average 1-rdm#
To check for convergence here, we will compute the average 1-rdm for each “average” saved in the
output HDF5 file, and plot \rho(i,j) vs \(N_{steps}\) for a line cut of the lattice.
We should see the average 1-rdm converge for some value of \(N_{steps}\).
from afqmctools.analysis.rdm import average_afqmc_rdm
naverages = 4
rho_avg, delta_rho = average_afqmc_rdm(rdm_file=scratch_dir/"qmc.s000.stat.h5")
rho_up = rho_avg[:,0,:,:].diagonal().reshape((naverages,4,8))
delta_rho_up = delta_rho[:,0,:,:].diagonal().reshape((naverages,4,8))
rho_down = rho_avg[:,1,:,:].diagonal().reshape((naverages,4,8))
delta_rho_down = delta_rho[:,1,:,:].diagonal().reshape((naverages,4,8))
import matplotlib.pyplot as plt
import numpy as np
ix = 3
iy = np.array(range(8))
rho_spin = 0.5*(rho_up - rho_down)
plt.plot(iy,rho_spin[0,ix,iy])
plt.show()