Custom Hamiltonian
In this tutorial, we provide an example python script for performing DMRG using custom Hamiltonians, where the operators and states at local Hilbert space at every site can be redefined. It is also possible to use different local Hilbert space for different sites. New letters can be introduced for representing new operators.
In the following example, we implement a custom Hamiltonian for the Hubbard model.
In the standard implementation, the on-site term was represented as cdCD
.
Here we instead introduce a single letter N
for the cdCD
term.
The matrix representation of N
is given in the init_site_ops
method.
from pyblock2.driver.core import DMRGDriver, SymmetryTypes
import numpy as np
from itertools import accumulate
L = 8
U = 2
driver = DMRGDriver(scratch="./tmp", symm_type=SymmetryTypes.SZ, n_threads=4)
driver.initialize_system(n_sites=L, n_elec=L, spin=0, orb_sym=None)
GH = driver.bw.bs.GeneralHamiltonian
class HubbardHamiltonian(GH):
def __init__(self, vacuum, n_sites, orb_sym):
GH.__init__(self)
self.opf = driver.bw.bs.OperatorFunctions(driver.bw.brs.CG())
self.vacuum = vacuum
self.n_sites = n_sites
self.orb_sym = orb_sym
self.basis = driver.bw.brs.VectorStateInfo([
self.get_site_basis(m) for m in range(self.n_sites)
])
self.site_op_infos = driver.bw.brs.VectorVectorPLMatInfo([
driver.bw.brs.VectorPLMatInfo() for _ in range(self.n_sites)
])
self.site_norm_ops = driver.bw.bs.VectorMapStrSpMat([
driver.bw.bs.MapStrSpMat() for _ in range(self.n_sites)
])
self.init_site_ops()
def get_site_basis(self, m):
"""Single site states."""
bz = driver.bw.brs.StateInfo()
bz.allocate(4)
bz.quanta[0] = driver.bw.SX(0, 0, 0)
bz.quanta[1] = driver.bw.SX(1, 1, self.orb_sym[m])
bz.quanta[2] = driver.bw.SX(1, -1, self.orb_sym[m])
bz.quanta[3] = driver.bw.SX(2, 0, 0)
bz.n_states[0] = bz.n_states[1] = bz.n_states[2] = bz.n_states[3] = 1
bz.sort_states()
return bz
def init_site_ops(self):
"""Initialize operator quantum numbers at each site (site_op_infos)
and primitive (single character) site operators (site_norm_ops)."""
i_alloc = driver.bw.b.IntVectorAllocator()
d_alloc = driver.bw.b.DoubleVectorAllocator()
# site op infos
max_n, max_s = 10, 10
max_n_odd, max_s_odd = max_n | 1, max_s | 1
max_n_even, max_s_even = max_n_odd ^ 1, max_s_odd ^ 1
for m in range(self.n_sites):
qs = {self.vacuum}
for n in range(-max_n_odd, max_n_odd + 1, 2):
for s in range(-max_s_odd, max_s_odd + 1, 2):
qs.add(driver.bw.SX(n, s, self.orb_sym[m]))
for n in range(-max_n_even, max_n_even + 1, 2):
for s in range(-max_s_even, max_s_even + 1, 2):
qs.add(driver.bw.SX(n, s, 0))
for q in sorted(qs):
mat = driver.bw.brs.SparseMatrixInfo(i_alloc)
mat.initialize(self.basis[m], self.basis[m], q, q.is_fermion)
self.site_op_infos[m].append((q, mat))
# prim ops
for m in range(self.n_sites):
# ident
mat = driver.bw.bs.SparseMatrix(d_alloc)
info = self.find_site_op_info(m, driver.bw.SX(0, 0, 0))
mat.allocate(info)
mat[info.find_state(driver.bw.SX(0, 0, 0))] = np.array([1.0])
mat[info.find_state(driver.bw.SX(1, 1, self.orb_sym[m]))] = np.array([1.0])
mat[info.find_state(driver.bw.SX(1, -1, self.orb_sym[m]))] = np.array([1.0])
mat[info.find_state(driver.bw.SX(2, 0, 0))] = np.array([1.0])
self.site_norm_ops[m][""] = mat
# C alpha
mat = driver.bw.bs.SparseMatrix(d_alloc)
info = self.find_site_op_info(m, driver.bw.SX(1, 1, self.orb_sym[m]))
mat.allocate(info)
mat[info.find_state(driver.bw.SX(0, 0, 0))] = np.array([1.0])
mat[info.find_state(driver.bw.SX(1, -1, self.orb_sym[m]))] = np.array([1.0])
self.site_norm_ops[m]["c"] = mat
# D alpha
mat = driver.bw.bs.SparseMatrix(d_alloc)
info = self.find_site_op_info(m, driver.bw.SX(-1, -1, self.orb_sym[m]))
mat.allocate(info)
mat[info.find_state(driver.bw.SX(1, 1, self.orb_sym[m]))] = np.array([1.0])
mat[info.find_state(driver.bw.SX(2, 0, 0))] = np.array([1.0])
self.site_norm_ops[m]["d"] = mat
# C beta
mat = driver.bw.bs.SparseMatrix(d_alloc)
info = self.find_site_op_info(m, driver.bw.SX(1, -1, self.orb_sym[m]))
mat.allocate(info)
mat[info.find_state(driver.bw.SX(0, 0, 0))] = np.array([1.0])
mat[info.find_state(driver.bw.SX(1, 1, self.orb_sym[m]))] = np.array([-1.0])
self.site_norm_ops[m]["C"] = mat
# D beta
mat = driver.bw.bs.SparseMatrix(d_alloc)
info = self.find_site_op_info(m, driver.bw.SX(-1, 1, self.orb_sym[m]))
mat.allocate(info)
mat[info.find_state(driver.bw.SX(1, -1, self.orb_sym[m]))] = np.array([1.0])
mat[info.find_state(driver.bw.SX(2, 0, 0))] = np.array([-1.0])
self.site_norm_ops[m]["D"] = mat
# Nup * Ndn
mat = driver.bw.bs.SparseMatrix(d_alloc)
info = self.find_site_op_info(m, driver.bw.SX(0, 0, 0))
mat.allocate(info)
mat[info.find_state(driver.bw.SX(2, 0, 0))] = np.array([1.0])
self.site_norm_ops[m]["N"] = mat
def get_site_string_ops(self, m, ops):
"""Construct longer site operators from primitive ones."""
d_alloc = driver.bw.b.DoubleVectorAllocator()
for k in ops:
if k in self.site_norm_ops[m]:
ops[k] = self.site_norm_ops[m][k]
else:
xx = self.site_norm_ops[m][k[0]]
for p in k[1:]:
xp = self.site_norm_ops[m][p]
q = xx.info.delta_quantum + xp.info.delta_quantum
mat = driver.bw.bs.SparseMatrix(d_alloc)
mat.allocate(self.find_site_op_info(m, q))
self.opf.product(0, xx, xp, mat)
xx = mat
ops[k] = self.site_norm_ops[m][k] = xx
return ops
def init_string_quanta(self, exprs, term_l, left_vacuum):
"""Quantum number for string operators (orbital independent part)."""
qs = {
'N': driver.bw.SX(0, 0, 0),
'c': driver.bw.SX(1, 1, 0),
'C': driver.bw.SX(1, -1, 0),
'd': driver.bw.SX(-1, -1, 0),
'D': driver.bw.SX(-1, 1, 0),
}
return driver.bw.VectorVectorSX([driver.bw.VectorSX(list(accumulate(
[qs['N']] + [qs[x] for x in expr], lambda x, y: x + y)))
for expr in exprs
])
def get_string_quanta(self, ref, expr, idxs, k):
"""Quantum number for string operators (orbital dependent part)."""
l, r = ref[k], ref[-1] - ref[k]
for j, (ex, ix) in enumerate(zip(expr, idxs)):
ipg = self.orb_sym[ix]
if ex == "N":
pass
elif j < k:
l.pg = l.pg ^ ipg
else:
r.pg = r.pg ^ ipg
return l, r
def get_string_quantum(self, expr, idxs):
"""Total quantum number for a string operator."""
qs = lambda ix: {
'N': driver.bw.SX(0, 0, 0),
'c': driver.bw.SX(1, 1, self.orb_sym[ix]),
'C': driver.bw.SX(1, -1, self.orb_sym[ix]),
'd': driver.bw.SX(-1, -1, self.orb_sym[ix]),
'D': driver.bw.SX(-1, 1, self.orb_sym[ix]),
}
return sum([qs(0)['N']] + [qs(ix)[ex] for ex, ix in zip(expr, idxs)])
def deallocate(self):
"""Release memory."""
for ops in self.site_norm_ops:
for p in ops.values():
p.deallocate()
for infos in self.site_op_infos:
for _, p in infos:
p.deallocate()
for bz in self.basis:
bz.deallocate()
driver.ghamil = HubbardHamiltonian(driver.vacuum, driver.n_sites, driver.orb_sym)
b = driver.expr_builder()
# make sure the indices in every term are non-descending
for t, ops in zip([-1, 1, -1, 1], ["cd", "dc", "CD", "DC"]):
b.add_term(ops, np.array([[i, i + 1] for i in range(L - 1)]).ravel(),
[t] * (L - 1))
b.add_term("N", np.array([[i, ] for i in range(L)]).ravel(), [U] * L)
mpo = driver.get_mpo(b.finalize(adjust_order=False), iprint=2)
def run_dmrg(driver, mpo):
ket = driver.get_random_mps(tag="GS", bond_dim=250, nroots=1)
bond_dims = [250] * 4 + [500] * 4
noises = [1e-4] * 4 + [1e-5] * 4 + [0]
thrds = [1e-10] * 8
return driver.dmrg(
mpo,
ket,
n_sweeps=20,
bond_dims=bond_dims,
noises=noises,
thrds=thrds,
iprint=1,
)
energies = run_dmrg(driver, mpo)
print('DMRG energy = %20.15f' % energies)