sisl icon indicating copy to clipboard operation
sisl copied to clipboard

Berry flux over BZ

Open bfocassio opened this issue 4 years ago • 13 comments

This is not much code related but it would serve as a nice example.

I'm trying to compute the Chern number for the Haldane model using the new feature that computes the berry flux and berry phase.

I'm setting up the model as:

graphene = sisl.geom.graphene(0.5774)
H = Hamiltonian(graphene,dtype=np.complex128)

delta=-0.2
t=-1.0
t2 =0.15*np.exp((1.j)*np.pi/2.)
t2c=t2.conjugate()

r = (0.1,  0.6)
t = (-delta , t )
H.construct([r, t])

H[1,1] = delta

H[0,0,(1,0)] = t2
H[1,1,(1,-1)] = t2
H[1,1,(0,1)] = t2
H[1,1,(1,0)] = t2c
H[0,0,(1,-1)] = t2c
H[0,0,(0,1)] = t2c

The bandstructure seems ok:

band = BandStructure(H, [[0.,0.],[2./3.,1./3.],[.5,0.5],[1./3.,2./3.], [0.,0.]], 400, [r'$\Gamma $',r'$K$', r'$M$', r'$K^\prime$', r'$\Gamma $'])

bs = band.asarray().eigh()

lk, kt, kl = band.lineark(True)
plt.xticks(kt, kl)
plt.xlim(0, lk[-1])
plt.ylim([-2.5, 2.5])
plt.ylabel(r'$E-\epsilon_F$ (eV)')
for bk in bs.T:
    plt.plot(lk, bk)

Now, for the berry flux, I'm doing:

nk = 24
kpts = np.linspace(-0.5,.5,nk,endpoint=False)

flux_lower_xy = np.zeros((len(kpts),len(kpts)))
for ki in range(len(kpts)):
    for kj in range(len(kpts)):
        origo = [kpts[ki], kpts[kj], 0.]
        flux_lower_xy[ki,kj] = H.eigenstate(k=origo).berry_flux()[0][0,1]

were the first index selects the lower band, and the last two the xy element.

And integrating it like:

simps([simps(flux_lower_xy[ki,:],kpts) for ki in range(len(kpts))],kpts)/(2*np.pi)

which should result in an integer. I'm obtaining something around 0.02 .

The model setup is ok ? And the berry flux part ?

Hoppings and berry flux/berry phase may be found here

Also, is there some way to open the boundary conditions (BC) of a periodic model (similar to the cut_piece of pythTB)?

Thanks in advance, any help is appreciated.

Version details 3.7.4 (default, Aug 13 2019, 20:35:49) [GCC 7.3.0] 0.9.8 13a327bd8e27d689f119bafdf38519bab7f6e0f6

bfocassio avatar Apr 01 '20 21:04 bfocassio

I think this is because my naming scheme for the functions are pretty bad... I am no expert in this, but I have tried to provide the necessary functionality.

These functions clearly need a revision to clarify their intent, thanks for bringing this to my attention.

zerothi avatar Apr 02 '20 09:04 zerothi

I have just implemented the Haldane model as Pythtb does it. There is however the difference that PythTB does not work in units the way sisl, does. So the two models are not exactly the same.

This may be checked by doing (see in the script about H.Hk(...)). One may add the same in PythTB (print(my_model._gen_ham([0.2, 0.2]))) but they are not the same, telling me that units are important here.

Note, that I haven't implemented the berry_flux and the name of the function was clearly wrong... :( Sorry about that.

If you have any ideas, please let me know.

import numpy as np
import sisl as si

import matplotlib.pyplot as plt

sc = si.SuperCell([[1, 0, 0], [0.5, 3 ** .5 / 2, 0], [0, 0, 10]], nsc=[3, 3, 1])
gr = si.Geometry(np.dot([[1/3, 1/3, 0], [2/3, 2/3, 0]], sc.cell), si.Atom(6, R=0.58), sc)

# System parameters
delta = 0.
t = -1.
t2 = 0.15 * np.exp(1j * np.pi / 2)
t2c = t2.conjugate()

H = si.Hamiltonian(gr, dtype=np.complex128)
H.construct([[0.1, .6], [0, t]])

# Create displaced on-site terms
H[0, 0] = -delta
H[1, 1] = delta

# Assing 2nd nearest neighbour couplings
# Contrary to PythTB sisl does not automatically impose
# symmetry. So you have to define all mirror couplings
# as well.
H[0, 0, (1, 0)] = t2
H[0, 0, (-1, 0)] = t2c
H[1, 1, (1,-1)] = t2
H[1, 1, (-1,1)] = t2c
H[1, 1, (0, 1)] = t2
H[1, 1, (0, -1)] = t2c
H[1, 1, (1, 0)] = t2c
H[1, 1, (-1, 0)] = t2
H[0, 0, (1, -1)] = t2c
H[0, 0, (-1, 1)] = t2
H[0, 0, (0, 1)] = t2c
H[0, 0, (0, -1)] = t2

# Pretty print-hoppings
#for r, c in H:
#    print("{r:2d} {c:2d} [ {i[0]:2d}, {i[1]:2d}]  == {v:.4f}".format(r=r, c=c % H.no, i=H.o2isc(c), v=H[r, c]))

# Compare the Hamiltonian with PythTB
#print(H.Hk([-0.2, -0.2, 0], format='array'))

special = [
    [0, 0, 0],
    [2/3, 1/3, 0],
    [1/2, 1/2, 0],
    [1/3, 2/3, 0],
    [0, 0, 0]]
label = [r'$\Gamma $',r'$K$', r'$M$', r'$K^\prime$', r'$\Gamma $']

band = si.BandStructure(H, special, 101, label)
lk, kt, kl = band.lineark(True)
plt.xticks(kt, kl)
plt.xlim(0, lk[-1])
plt.ylabel('$E-E_F$ [eV]')
plt.plot(lk, band.eigh())


# Now calculate the berry-phase
# Create a BZ model starting @ [-1/2, -1/2, 0]
Nx, Ny = 30, 30
mpx = si.MonkhorstPack(H, [Nx, 1, 1], trs=False)
mpy = si.MonkhorstPack(H, [1, Ny, 1], trs=False)
mpx.k[:, 0] = np.linspace(-0.5, 0.5, Nx, endpoint=False)
mpy.k[:, 1] = np.linspace(-0.5, 0.5, Ny, endpoint=False)

phi_a_1 = np.empty(Ny)
phi_b_1 = np.empty(Ny)
phi_c_1 = np.empty(Ny)

berry_phase = si.physics.electron.berry_phase
for ik, ky in enumerate(mpy):
    mpx.k[:, 1] = ky[1]
    # Calculate berry phase along line
    phi_a_1[ik] = berry_phase(mpx, sub=[0])
    phi_b_1[ik] = berry_phase(mpx, sub=[1])
    phi_c_1[ik] = berry_phase(mpx)

# I am not fully sure why this is needed?
phi_a_1 -= phi_a_1[0]
phi_b_1 -= phi_b_1[0]
phi_c_1 -= phi_c_1[0]

fig, ax = plt.subplots()
ky = mpy.k[:, 1]
ax.plot(ky, phi_a_1, 'ro')
ax.plot(ky, phi_b_1, 'go')
ax.plot(ky, phi_c_1, 'bo')
ax.set_title("Berry phase for lower (red), top (green), both bands (blue)")
ax.set_xlabel(r"$k_y$")
ax.set_ylabel(r"Berry phase along $k_x$")
ax.set_xlim(-0.5, 0.5)
ax.set_ylim(-7.,7.)
ax.yaxis.set_ticks([-2.*np.pi,-np.pi,0.,np.pi,2.*np.pi])
ax.set_yticklabels((r'$-2\pi$',r'$-\pi$',r'$0$',r'$\pi$', r'$2\pi$'))

plt.show()

zerothi avatar Apr 02 '20 12:04 zerothi

This is awesome.

At the model setup, it is very important to note that we should impose the symmetry.

After setting up the model, if this is going to serve as an example, one could add a nice edge state visualization, such as:

pbcH = H.copy().tile(30,0)
obcH = pbcH.copy()
obcH.set_nsc(a=1)

ribbon_special = [[0.,0.,0.],[.0,.5,0.],[0.,0.,0.]]
ribbon_labels = [r'$\Gamma $', r'$Y$', r'$\Gamma $']

ribbon_band = si.BandStructure(pbcH, ribbon_special, 101, ribbon_labels)
lk, kt, kl = ribbon_band.lineark(True)

fig,ax = plt.subplots(1,2,sharex='all',sharey='all',figsize=(12,4))

ax[0].set_xticks(kt)
ax[0].set_xticklabels(kl)
ax[0].set_xlim(0, lk[-1])
ax[0].set_ylim([-2,2])
ax[0].set_ylabel('$E-E_F$ [eV]')

ax[0].plot(lk, ribbon_band.eigh())

ribbon_band.set_parent(obcH)
ax[1].plot(lk, ribbon_band.eigh())

ax[0].set_title('PBC')
ax[1].set_title('partial OBC')

plt.show()

At the berry phase part,

I would change it a bit to:

Nx, Ny = 31, 31
mpx = si.MonkhorstPack(H, [Nx, 1, 1], trs=False)
mpy = si.MonkhorstPack(H, [1, Ny, 1], trs=False)

After subtracting the first element (Note that the subtraction of the first element is needed to avoid the ambiguity of the berry phase definition).

If I'm not confusing things, the Chern number is simply given by the berry phase of occupied bands around the BZ contour, thus,

phi_a_1[-1]/(2*np.pi)

Is the berry phase routine working for SOC Hamiltonians ?

bfocassio avatar Apr 02 '20 21:04 bfocassio

At the model setup, it is very important to note that we should impose the symmetry.

Yes, I mention this one place in the documentation, but I should probably emphasize this.

After setting up the model, if this is going to serve as an example, one could add a nice edge state visualization, such as:

Thanks!!

pbcH = H.copy().tile(30,0)

you don't need to copy here, tile returns a new instance.

obcH = pbcH.copy()
obcH.set_nsc(a=1)

ribbon_special = [[0.,0.,0.],[.0,.5,0.],[0.,0.,0.]]
ribbon_labels = [r'$\Gamma $', r'$Y$', r'$\Gamma $']

ribbon_band = si.BandStructure(pbcH, ribbon_special, 101, ribbon_labels)
lk, kt, kl = ribbon_band.lineark(True)

fig,ax = plt.subplots(1,2,sharex='all',sharey='all',figsize=(12,4))

ax[0].set_xticks(kt)
ax[0].set_xticklabels(kl)
ax[0].set_xlim(0, lk[-1])
ax[0].set_ylim([-2,2])
ax[0].set_ylabel('$E-E_F$ [eV]')

ax[0].plot(lk, ribbon_band.eigh())

ribbon_band.set_parent(obcH)
ax[1].plot(lk, ribbon_band.eigh())

ax[0].set_title('PBC')
ax[1].set_title('partial OBC')

plt.show()

At the berry phase part,

I would change it a bit to:

Nx, Ny = 31, 31
mpx = si.MonkhorstPack(H, [Nx, 1, 1], trs=False)
mpy = si.MonkhorstPack(H, [1, Ny, 1], trs=False)

It does not matter, a MP grid never uses the same point twice, so for Nx = 4, you would get [-1/4, 0, 1/4, 1/2]

After subtracting the first element (Note that the subtraction of the first element is needed to avoid the ambiguity of the berry phase definition).

If I'm not confusing things, the Chern number is simply given by the berry phase of occupied bands around the BZ contour, thus,

phi_a_1[-1]/(2*np.pi)

No this won't work (I guess), you would have to use the first and last point to get the accumulated Berry-phase over the path (this is how I currently understand it, but I could be wrong!)

Is the berry phase routine working for SOC Hamiltonians ?

I think so, the Berry phase only uses the eigenstates. However, the berry_curvature will not work since it requires the velocity matrix.

zerothi avatar Apr 03 '20 10:04 zerothi

Thank you for your patience and help.

No this won't work (I guess), you would have to use the first and last point to get the accumulated Berry-phase over the path (this is how I currently understand it, but I could be wrong!)

You're right, the best way would be (phi_a_1[0] - phi_a_1[-1])/(2*np.pi)

Now I'm going to explore the Kane-Mele model

bfocassio avatar Apr 03 '20 13:04 bfocassio

Hmm, shouldn't it be phi_a_1[-1] - phi_a_1[0] since you want the accumulated phase? if the last is 2pi above the first, it would give you Chern == 1

zerothi avatar Apr 03 '20 14:04 zerothi

Yes, that's what I meant, thanks again

bfocassio avatar Apr 03 '20 14:04 bfocassio

I set up the Kane and Mele model (as in here) and I'm trying to calculate the Berry phase for the two occupied bands, together and separately.

The model is:

import numpy as np
import sisl as si
import matplotlib.pyplot as plt

# ### Setting up the model

sc = si.SuperCell([[1., 0, 0], [0.5, 3 ** .5 / 2, 0], [0, 0, 30]], nsc=[3, 3, 1])
gr = si.Geometry(np.dot([[1/3, 1/3, 0], [2/3, 2/3, 0]], sc.cell), si.Atom(6, R=0.58), sc)

# ### Useful definitions
#[Re(upup) Re(dndn) Re(updn) Im(updn) Im(upup) Im(dndn) Re(dnup) Im(dnup)]

eye = np.array([1.,1.,0.,0.,0.,0.,0.,0.])
sigma_z = np.array([1,-1,0,0,0,0,0,0])
sigma_x = np.array([0,0,1,0,0,0,1,0])
sigma_y = np.array([0,0,0,-1,0,0,0,1])

imag_sigma_z = np.array([0,0,0,0,1.,-1.,0,0])
imag_sigma_x = np.array([0,0,0,1,0,0,0,1])
imag_sigma_y = np.array([0,0,1,0,0,0,-1,0])

# System parameters

t = 1.
esite = .1*t
spin_orb = .06*t
rashba = .05*t

H = si.Hamiltonian(gr,dim=8)

# spin-independent first-neighbor hoppings

H.construct([[0.1, .6], [0, t*eye]])

# set on-site energies

H[0, 0] = esite*eye
H[1, 1] = -1*esite*eye

# Assing 2nd nearest neighbour couplings
 
# Define all mirror couplings as well.

# second-neighbour spin-orbit hoppings (s_z)

H[0, 0, (0,1)] = -1*spin_orb*imag_sigma_z
H[0, 0, (0,-1)] = 1*spin_orb*imag_sigma_z

H[0, 0, (1,0)] = 1*spin_orb*imag_sigma_z
H[0, 0, (-1,0)] = -1*spin_orb*imag_sigma_z

H[0, 0, (1,-1)] = -1*spin_orb*imag_sigma_z
H[0, 0, (-1,1)] = 1*spin_orb*imag_sigma_z

H[1, 1, (0,1)] = 1*spin_orb*imag_sigma_z
H[1, 1, (0,-1)] = -1*spin_orb*imag_sigma_z

H[1, 1, (1,0)] = -1*spin_orb*imag_sigma_z
H[1, 1, (-1,0)] = 1*spin_orb*imag_sigma_z

H[1, 1, (1,-1)] = 1*spin_orb*imag_sigma_z
H[1, 1, (-1,1)] = -1*spin_orb*imag_sigma_z

# Rashba first-neighbor hoppings: (s_x)(dy)-(s_y)(d_x)

r3h =np.sqrt(3.0)/2.0

H[0,1, (0,0)] +=  1*rashba*(0.5*imag_sigma_x-r3h*imag_sigma_y)
H[1,0, (0,0)] += -1*rashba*(0.5*imag_sigma_x-r3h*imag_sigma_y)

H[0,1, (0,-1)] += 1*rashba*(-1.0*imag_sigma_x)
H[0,1, (0,1)] += -1*rashba*(-1.0*imag_sigma_x)

H[0,1, (-1,0)] += 1.*rashba*( 0.5*imag_sigma_x+r3h*imag_sigma_y)
H[0,1, (1,0)] += -1.*rashba*( 0.5*imag_sigma_x+r3h*imag_sigma_y)

# print model at gamma

print(H.Hk([0., 0., 0], format='array'))

# bulk band structure

special = [
    [0, 0, 0],
    [2/3, 1/3, 0],
    [1/3, 2/3, 0],
    [0, 0, 0]]
label = [r'$\Gamma $',r'$K$', r'$K^\prime$', r'$\Gamma $']

band = si.BandStructure(H, special, 301, label)
lk, kt, kl = band.lineark(True)
plt.xticks(kt, kl)
plt.xlim(0, lk[-1])
plt.ylabel('$E-E_F$ [eV]')
plt.plot(lk, band.eigh())
plt.show()

# Ribbon band structure for partially finite geometry

pbcH = H.tile(30,0)

obcH = pbcH.copy()
obcH.set_nsc(a=1)

ribbon_special = [[0.,0.,0.],[.0,.5,0.],[0.,0.,0.]]
ribbon_labels = [r'$\Gamma $', r'$Y$', r'$\Gamma $']

ribbon_band = si.BandStructure(pbcH, ribbon_special, 501, ribbon_labels)
lk, kt, kl = ribbon_band.lineark(True)

fig,ax = plt.subplots(1,2,sharex='all',sharey='all',figsize=(12,4))

ax[0].set_xticks(kt)
ax[0].set_xticklabels(kl)
ax[0].set_xlim(0, lk[-1])
ax[0].set_ylim([-1.75,1.75])
ax[0].set_ylabel('$E-E_F$ [eV]')

ax[0].plot(lk, ribbon_band.eigh())

ribbon_band.set_parent(obcH)
ax[1].plot(lk, ribbon_band.eigh())

ax[0].set_title('PBC')
ax[1].set_title('partial OBC')

plt.show()

For the Berry phase I do:

# Now calculate the berry-phase

# Create a BZ model starting @ [-1/2, -1/2, 0]

Nx, Ny = 30,30
mpx = si.MonkhorstPack(H, [Nx, 1, 1], trs=False)
mpy = si.MonkhorstPack(H, [1, Ny, 1], trs=False)
mpx.k[:, 0] = np.linspace(-0.5, 0.5, Nx, endpoint=False)
mpy.k[:, 1] = np.linspace(-0.5, 0.5, Ny, endpoint=False)

phi_occ_0 = np.empty(Ny)
phi_occ_1 = np.empty(Ny)
phi_occ = np.empty(Ny)

# Calculate berry phase along line

berry_phase = si.physics.electron.berry_phase
for ik, ky in enumerate(mpy):
    mpx.k[:, 1] = ky[1]
   
    phi_occ_0[ik] = berry_phase(mpx, sub=[0])
    phi_occ_1[ik] = berry_phase(mpx, sub=[1])
    phi_occ[ik] = berry_phase(mpx, sub=[0,1])

This returns me the following error:

---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-39-4fd182f2dfe7> in <module>
      3     mpx.k[:, 1] = ky[1]
      4     # Calculate berry phase along line
----> 5     phi_occ_0[ik] = berry_phase(mpx, sub=[0])
      6     phi_occ_1[ik] = berry_phase(mpx, sub=[1])
      7     phi_occ[ik] = berry_phase(mpx, sub=[0,1])

~/anaconda3/lib/python3.7/site-packages/sisl/physics/electron.py in berry_phase(contour, sub, eigvals, closed, method)
   1188 
   1189     # Do the actual calculation of the final matrix
-> 1190     d = _berry(contour.asyield().eigenstate())
   1191 
   1192     # Correct return values

~/anaconda3/lib/python3.7/site-packages/sisl/physics/electron.py in _berry(eigenstates)
   1169         def _berry(eigenstates):
   1170             first = next(eigenstates).sub(sub)
-> 1171             first.change_gauge('r')
   1172             prev = first
   1173             prd = 1

~/anaconda3/lib/python3.7/site-packages/sisl/physics/electron.py in change_gauge(self, gauge)
   1796 
   1797         if gauge == 'r':
-> 1798             self.state *= np.exp(1j * phase).reshape(1, -1)
   1799         elif gauge == 'R':
   1800             self.state *= np.exp(-1j * phase).reshape(1, -1)

ValueError: operands could not be broadcast together with shapes (1,4) (1,2) (1,4) 

Any ideas of what I'm doing wrong ?

Thanks in advance

bfocassio avatar Apr 07 '20 12:04 bfocassio

Thanks for discovering this... It is definitely a bug!

zerothi avatar Apr 07 '20 13:04 zerothi

@bfocassio could you try the latest commit of sisl?

zerothi avatar Apr 07 '20 13:04 zerothi

Yes, It's working

Thank you

bfocassio avatar Apr 07 '20 14:04 bfocassio

@bfocassio I am going to re-open.

Then I will remember to make a small tutorial about (if you don't mind me using your supplied codes?)

:)

zerothi avatar Apr 07 '20 16:04 zerothi

I don't mind at all. And thank you again

bfocassio avatar Apr 07 '20 17:04 bfocassio