The Fu-Kane Parity Criterion: Z2 from Inversion Eigenvalues#

L. Fu and C. Kane showed in 2007 that for a crystal with inversion symmetry the \(\mathbb{Z}_2\) invariant needs no Wilson loop at all: it is fixed by the parities \(\xi = \pm1\) of the occupied Kramers pairs at the four time-reversal-invariant momenta \(\Gamma_i\),

\[(-1)^\nu = \prod_{i=1}^{4}\delta_i\, ,\qquad \delta_i = \prod_{m}\xi_{2m}(\Gamma_i)\, .\]

A band inversion at one \(\Gamma_i\) flips \(\delta_i\) and makes the insulator topological. The criterion led directly to the prediction of the 3D topological insulators (Bi1-xSbx, then Bi2Se3).

Here it is applied to the Bernevig-Hughes-Zhang (BHZ) model of HgTe quantum wells: s-like and p-like orbitals of opposite parity on a square lattice, with the mass term \(d_z(\mathbf{k}) = m - 2(2 - \cos k_x - \cos k_y)\). The parity product is compared, across the phase diagram, with the invariant from the Wannier-centre flow (z2_invariant()), which knows nothing about inversion: they agree everywhere.

import numpy as np
import matplotlib.pyplot as plt

from tbkit.lattice import Lattice
from tbkit.kspace import KSpace, PAULI
from tbkit.plot import Plot
from tbkit.system import System


# orbitals: (s, up), (s, down), (p, up), (p, down) -> two "sites" with spin
lat = Lattice(unit_cell=[{'tag': 's', 'r0': (0., 0.)}, {'tag': 'p', 'r0': (0., 0.)}],
                     prim_vec=[(1., 0.), (0., 1.)])
SZ = PAULI['z']
INVERSION = np.kron(np.diag([1., -1.]), np.eye(2))  # s even, p odd
TIME_REVERSAL = np.kron(np.eye(2), 1j * PAULI['y'])


def bhz(m, a=1.):
    r'''
    H(k) = sin kx tau_x s_z + sin ky tau_y + d_z(k) tau_z  (tau: orbital, s: spin),
    i.e. two time-reversed Chern insulators, one per spin.
    '''
    model = KSpace(lat, spin=True)
    model.set_onsite({'s': (m - 4.) * PAULI['0'], 'p': -(m - 4.) * PAULI['0']})
    for R in [(1, 0), (0, 1)]:
        model.set_hopping([{'i': 0, 'j': 0, 'R': R, 't': PAULI['0']},
                                    {'i': 1, 'j': 1, 'R': R, 't': -PAULI['0']}])
    # tau_x s_z sin kx + tau_y sin ky between s and p
    model.set_hopping([{'i': 0, 'j': 1, 'R': (1, 0), 't': -0.5j * a * SZ},
                                {'i': 0, 'j': 1, 'R': (-1, 0), 't': 0.5j * a * SZ},
                                {'i': 0, 'j': 1, 'R': (0, 1), 't': -0.5 * a * PAULI['0']},
                                {'i': 0, 'j': 1, 'R': (0, -1), 't': 0.5 * a * PAULI['0']}])
    return model

The lattice#

The square lattice with its nearest-neighbour bonds. Each site holds an s and a p orbital, each with spin, and every bond carries a \(4\times4\) hopping matrix that couples them (not drawn).

patch = Lattice(unit_cell=[{'tag': 'a', 'r0': (0., 0.)}], prim_vec=lat.prim_vec)
patch.get_lattice(n1=6, n2=6)
vis = System(patch)
vis.set_hopping([{'n': 1, 't': 1.}])
fig_lat = Plot(vis).lattice(plt_hop=True, ms=10, figsize=(4, 4))
plot fu kane parity

Both symmetries hold#

model = bhz(1.)
assert model.symmetry_error(INVERSION) < 1e-12
assert model.symmetry_error(TIME_REVERSAL, antiunitary=True) < 1e-12
print('Inversion and time reversal (T^2 = -1): class', model.tenfold_class(time_reversal=TIME_REVERSAL))
Inversion and time reversal (T^2 = -1): class AII

Parities at the TRIMs, and the phase diagram#

The occupied pair has the parity of the lower of the two orbitals, set by the sign of \(d_z\) at each TRIM: \(m\) at \(\Gamma\), \(m - 4\) at X and Y, \(m - 8\) at M. So \(\nu = 1\) for \(0 < m < 4\) and \(4 < m < 8\), and 0 otherwise.

masses = np.array([-1., 1., 3., 5., 7., 9.])
nu_parity = [bhz(m).parity_z2(INVERSION, [0, 1]) for m in masses]
nu_flow = [bhz(m).z2_invariant([0, 1], nk=40, nk_perp=31) for m in masses]
expected = [int(0 < m < 4 or 4 < m < 8) for m in masses]
print('m:', masses, '\n  Fu-Kane parity:', nu_parity, '\n  Wannier flow:  ', nu_flow)
assert nu_parity == expected and nu_flow == expected

fig, ax = plt.subplots(figsize=(6, 3.4))
ax.plot(masses, nu_parity, 'ob', ms=9, label='Fu-Kane parity')
ax.plot(masses, nu_flow, 'xr', ms=9, mew=2, label='Wannier-centre flow')
ax.set_xlabel('$m$')
ax.set_ylabel(r'$\nu$')
ax.set_yticks([0, 1])
ax.legend()
ax.set_title('BHZ model: two routes to the same Z2 invariant')
fig.set_layout_engine('tight')
BHZ model: two routes to the same Z2 invariant
m: [-1.  1.  3.  5.  7.  9.]
  Fu-Kane parity: [0, 1, 1, 1, 1, 0]
  Wannier flow:   [0, 1, 1, 1, 1, 0]

Total running time of the script: (0 minutes 0.097 seconds)

Gallery generated by Sphinx-Gallery