Kitaev’s honeycomb Hamiltonian in vortex sectors given by the plaquette operators¶
This example shows how one can employ QuSpin’s user_basis class to construct the Kitaev honeycomb model Hamiltonian in the spectral sectors generated by the Wilson loop operators W. The transformation generated by W cannot be defined using QuSpin’s basis_general classes.
Consider the Kitaev honeycomb model (see reference for details of the definition):
where \(\sigma\) are the Pauli matrices, and we consider 4 unit cells (8 sites) with periodic boundary conditions.
It can be shown that this Hamiltonian commutes with all Wilson loop plauqette operators, e.g., \(W=\sigma^x_0\sigma^y_1\sigma^z_2\sigma^x_3\sigma^y_4\sigma^z_5\), etc. Hence, the \(W\)-operators generate a transformation according to which \(H_\mathrm{Kitaev}\) can be block diagonalized; the corresponding sectors can be labelled by the eigenvalues of \(W\) which take the values \(\pm 1\), and determine the vortex configuration of the states in a given sector.
Here, we want to construct the Hamiltonian \(H_\mathrm{Kitaev}\) in a given vortex configuration sector using QuSpin.
It turns out, this cannot be achieved using the basis_general classes, because the operation induced by \(W\) on the computational basis states cannot be represented by a combination of lattice sites permutations and spin inversion operations. Indeed, the operators \(\sigma^z_2, \sigma^z_5\) multiply the resulting state by a state-dependent sign, while the operators \(\sigma^y_1, \sigma^y_4\) multiply the resulting state by a state-dependent factor \(\pm i\).
- Notes:
the procedure described below is generic, and can be applied to any symmetry, provided that (in the basis chosen) the symmetry is represented by a real-valued operator.
the code below works only for 8 lattice sites with periodic boundary conditions; however, it should be clear how to generalize it to other system sizes.
this code shows an interesting example of using the sign pointer variable, inherent to the symmetry map function template of the user_basis, for spin systems [typically, sign is used to accomodate fermionic signs in fermionic models].
there are two more Wilson loops that wrap around the two directions of the torus; we leave it to the reader to code them up.
Please consult this tutorial post – A tutorial on QuSpin’s user_basis – for more detailed explanations on using the user_basis class.
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 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 | from __future__ import print_function, division
#
import sys,os
os.environ['KMP_DUPLICATE_LIB_OK']='True' # uncomment this line if omp error occurs on OSX for python 3
os.environ['OMP_NUM_THREADS']='4' # set number of OpenMP threads to run in parallel
os.environ['MKL_NUM_THREADS']='4' # set number of MKL threads to run in parallel
#
quspin_path = os.path.join(os.getcwd(),"../../")
sys.path.insert(0,quspin_path)
#########################################################################
# example 28 #
# In this script we demonstrate how to use QuSpin's user_basis #
# to define symmetries, which are not supported by the basis_general #
# classes. We take an 8-site honeycomb lattice with PBC and define #
# the Kitaev model in the spectral sectors given by the Wilson loop / #
# plaquette operators W. #
#########################################################################
from quspin.operators import hamiltonian # Hamiltonians and operators
from quspin.basis.user import user_basis # Hilbert space user basis
from quspin.basis.user import next_state_sig_32,op_sig_32,map_sig_32,count_particles_sig_32 # user basis data types signatures
from numba import carray,cfunc,jit # numba helper functions
from numba import uint32,int32 # numba data types
import numpy as np
#
N=8 # lattice sites
assert(N==8) # code below works only for N=8!
#
############ create spin-1/2 user basis object #############
#
###### function to call when applying operators
@cfunc(op_sig_32,
locals=dict(s=int32,n=int32,b=uint32), )
def op(op_struct_ptr,op_str,site_ind,N,args):
# using struct pointer to pass op_struct_ptr back to C++ see numba Records
op_struct = carray(op_struct_ptr,1)[0]
err = 0
#
site_ind = N - site_ind - 1 # convention for QuSpin for mapping from bits to sites.
n = (op_struct.state>>site_ind)&1 # either 0 or 1
s = (((op_struct.state>>site_ind)&1)<<1)-1 # either -1 or 1
b = (1<<site_ind)
#
if op_str==120: # "x" is integer value 120 = ord("x")
op_struct.state ^= b
elif op_str==121: # "y" is integer value 120 = ord("y")
op_struct.state ^= b
op_struct.matrix_ele *= 1.0j*s
elif op_str==43: # "+" is integer value 43 = ord("+")
if n: op_struct.matrix_ele = 0
else: op_struct.state ^= b # create spin
elif op_str==45: # "-" is integer value 45 = ord("-")
if n: op_struct.state ^= b # destroy spin
else: op_struct.matrix_ele = 0
elif op_str==122: # "z" is integer value 120 = ord("z")
op_struct.matrix_ele *= s
elif op_str==110: # "n" is integer value 110 = ord("n")
op_struct.matrix_ele *= n
elif op_str==73: # "I" is integer value 73 = ord("I")
pass
else:
op_struct.matrix_ele = 0
err = -1
#
return err
op_args=np.array([],dtype=np.uint32)
#
###### define symmetry maps
#
@jit(uint32(uint32,uint32,int32),)
def _compute_occupation(state,site_ind,N):
# auxiliary function to check occupation of state at site_ind
# CAUTION: 32-bit integers code only!
site_ind = N - site_ind - 1 # compute lattice site in reversed bit configuration (cf QuSpin convention for mapping from bits to sites)
return (state>>site_ind)&1 # occupation: either 0 or 1
#
@jit(uint32(uint32,uint32,int32),locals=dict(b=uint32,))
def _flip_occupation(state,site_ind,N):
# auxiliary function to flip occupation of state at site_ind
# CAUTION: 32-bit integers code only!
site_ind = N - site_ind - 1 # compute lattice site in reversed bit configuration (cf QuSpin convention for mapping from bits to sites)
b = 1; b <<= site_ind # compute a "mask" integer b which is 1 on site site_ind and zero elsewhere
return state^b # flip occupation on site site_ind
#
@cfunc(map_sig_32,
locals=dict(out=uint32,) )
def W_symm(state,N,sign_ptr,args):
"""
applies plaquette operator W = "xyzxyz" on sites defined in args.
# CAUTION: 32-bit integers code only!
- the two 'z'-operators in W are easiest to apply, since just return the occupation number in the spin basis.
- the two 'x'-operators in W are alo easy -- they only flip the occupation
- the two 'y'-operators in W do both: they flip the occupation and mutiply the state by +/-i (each)
"""
assert(N==8) # works only for N=8!
out = 0
#
# compute sign by applying operators from W on state
sign_ptr[0] *= -1 + 2*_compute_occupation(state,args[1],N) # 'y'; factor i taken into account below
sign_ptr[0] *= -1 + 2*_compute_occupation(state,args[2],N) # 'z'
sign_ptr[0] *= -1 + 2*_compute_occupation(state,args[4],N) # 'y'; factor i taken into account below
sign_ptr[0] *= -1 + 2*_compute_occupation(state,args[5],N) # 'z'
sign_ptr[0] *= -1 # -1=i**2, yy
#
# flip occupation of state on sites 0,1, 3,4
out = _flip_occupation(state,args[0],N) # 'x'
out = _flip_occupation(out, args[1],N) # 'y'
out = _flip_occupation(out, args[3],N) # 'x'
out = _flip_occupation(out, args[4],N) # 'y'
#
return out
# the argument list stores the sites for the four W operators, defined clockwise
W_args_1=np.array([0,1,2,3,4,5],dtype=np.uint32) # plaquette 1
W_args_2=np.array([4,3,6,1,0,7],dtype=np.uint32) # plaquette 2
W_args_3=np.array([6,7,0,5,2,1],dtype=np.uint32) # plaquette 3
W_args_4=np.array([2,5,4,7,6,3],dtype=np.uint32) # plaquette 4
#
###### construct user_basis
#
# define maps dict
q_nums=[0,0,0,0] # not all sectors contain a finite-number of states
maps = dict( W1_block=(W_symm,2,q_nums[0],W_args_1),
W2_block=(W_symm,2,q_nums[1],W_args_2),
W3_block=(W_symm,2,q_nums[2],W_args_3),
W4_block=(W_symm,2,q_nums[3],W_args_4),
)
op_dict = dict(op=op,op_args=op_args)
# create user basis
basis = user_basis(np.uint32,N,op_dict,allowed_ops=set("+-xyznI"),sps=2,**maps)
#print(basis)
#
##### define Kitaev Hamiltonian
#
### coupling strengths
Jx,Jy,Jz=1.0,1.0,1.0
#
### site-coupling lists
zz_int = [[Jz,0,1], [Jz,4,3], [Jz,2,5], [Jz,6,7] ]
yy_int = [[Jy,2,3], [Jy,0,5], [Jy,6,1], [Jy,4,7] ]
xx_int = [[Jx,2,1], [Jx,4,5], [Jx,6,3], [Jx,0,7] ]
#
### construct Hamitonian
no_checks=dict(check_pcon=False, check_herm=False, check_symm=False)
#
Hzz = hamiltonian([['zz',zz_int],], [], basis=basis, dtype=np.complex128, **no_checks)
Hyy = hamiltonian([['yy',yy_int],], [], basis=basis, dtype=np.complex128, **no_checks)
Hxx = hamiltonian([['xx',xx_int],], [], basis=basis, dtype=np.complex128, **no_checks)
#
H_Kitaev = Hzz + Hyy + Hxx
#
### diagonalize H_Kitaev
#
#E, V = H_Kitaev.eigsh(k=4, which='SA')
E, V = H_Kitaev.eigh()
print('energy spectrum:', E[:4])
#
### construct plaquette operator and check its e'state expectation
#
W_int = [[1.0, *list(W_args_1) ]]
W = hamiltonian([['xyzxyz',W_int],], [], basis=basis, dtype=np.float64, **no_checks)
print('check expectation of W-operator:', W.expt_value(V[:,:4],).real)
|