# Finding symmetries of the Kekule-Y continuum model¶

The complete source code of this example can be found in kekule.py. A Jupyter notebook can be found in kekule.ipynb.

## Kekule-Y¶

Find symmetries of Kekule-Y effective model, starting from the full hexagonal symmetry group with time reversal and particle-hole symmetry.

ham_kek_y = """
vs * kron((k_x * sigma_x + k_y * sigma_y), eye(2))
+ vt * kron(eye(2), (k_x * sigma_x + k_y * sigma_y))
"""
display(qsymm.sympify(ham_kek_y))

$\begin{split}\displaystyle \left[\begin{matrix}0 & vt \left(k_{x} - i k_{y}\right) & vs \left(k_{x} - i k_{y}\right) & 0\\vt \left(k_{x} + i k_{y}\right) & 0 & 0 & vs \left(k_{x} - i k_{y}\right)\\vs \left(k_{x} + i k_{y}\right) & 0 & 0 & vt \left(k_{x} - i k_{y}\right)\\0 & vs \left(k_{x} + i k_{y}\right) & vt \left(k_{x} + i k_{y}\right) & 0\end{matrix}\right]\end{split}$
H_kek_y= qsymm.Model(ham_kek_y, momenta=['k_x', 'k_y'])
candidates = qsymm.groups.hexagonal()
sgy, cg = qsymm.symmetries(H_kek_y, candidates)


Enumerate subgroups that protect double Dirac cone:

all_subgroups = qsymm.groups.generate_subgroups(sgy)

# Terms proportional to the identity matrix don't open a gap.
identity_terms = [qsymm.Model({qsymm.sympify(1): np.eye(4)})]

dd_sg = []
for subgroup, gens in all_subgroups.items():
family = qsymm.continuum_hamiltonian(gens, 2, 0)
family = qsymm.hamiltonian_generator.subtract_family(family, identity_terms)
if family == []:
dd_sg.append(subgroup)

dd_sg.sort(key=len)

dd_sg

frozenset({1, R(π) C})


Twofold rotation combined with sublattice symmetry is enough. However 2-fold rotation is not a physical symmetry of the lattice model. Let’s get rid of 2-fold rotations:

rdd_sg = []
for subgroup, gens in all_subgroups.items():
invs = {
qsymm.inversion(2) * g
for g in [
qsymm.identity(2),
qsymm.time_reversal(2),
qsymm.particle_hole(2),
qsymm.chiral(2),
]
}
# Skip subgroups that have inversion
if subgroup & invs:
continue
family = qsymm.continuum_hamiltonian(gens, 2, 0)
family = qsymm.hamiltonian_generator.subtract_family(family, identity_terms)
if family == []:
rdd_sg.append(subgroup)

rdd_sg.sort(key=len)

rdd_sg

frozenset({1, R(2π/3), R(-2π/3), C, R(2π/3) C, R(-2π/3) C})


Sublattice symmetry with 3-fold rotations is the minimal symmetry group required. Let’s test if any of the solutions has no sublattice symmetry:

any(g.antisymmetry for sg in rdd_sg for g in sg)

True


Sublattice symmetry is required for the protection of the double Dirac cone.

## Continuous rotation symmetries of continuum models¶

### Kekule-Y¶

_, cg = qsymm.symmetries(H_kek_y, candidates=[], continuous_rotations=True, prettify=True)
cg

$\begin{split}e^{-i\phi L}H(\mathbf{k})e^{i\phi L} = H(R_{\phi}\mathbf{k}) \\R_{{\phi}} = R\left(\phi\right)\\L = \begin{bmatrix}1.+0.j&0.+0.j&0.+0.j&0.+0.j\\0.+0.j&0.+0.j&0.+0.j&0.+0.j\\0.+0.j&0.+0.j&0.+0.j&0.+0.j\\ 0.+0.j& 0.+0.j& 0.+0.j&-1.+0.j\\\end{bmatrix} \\\end{split}$

### Kekule-O¶

ham_kek_o= """
vs * (
k_x * kron(eye(2), sigma_x)
+ k_y * kron(sigma_z, sigma_y)
)
+ Delta * kron(sigma_y, sigma_y)
"""
display(qsymm.sympify(ham_kek_o))

$\begin{split}\displaystyle \left[\begin{matrix}0 & vs \left(k_{x} - i k_{y}\right) & 0 & - \Delta\\vs \left(k_{x} + i k_{y}\right) & 0 & \Delta & 0\\0 & \Delta & 0 & vs \left(k_{x} + i k_{y}\right)\\- \Delta & 0 & vs \left(k_{x} - i k_{y}\right) & 0\end{matrix}\right]\end{split}$
H_kek_o= qsymm.Model(ham_kek_o, momenta=['k_x', 'k_y'])
_, cg = qsymm.symmetries(H_kek_o, candidates=[], continuous_rotations=True, prettify=True)
cg

$\begin{split}\left[ H(\mathbf{{k}}), L \right] = 0 \\L = \begin{bmatrix} 0. +0.j&-0. +0.j&-0. +0.j& 0.5+0.j\\-0. -0.j& 0. +0.j& 0.5-0.j&-0. -0.j\\-0. -0.j& 0.5+0.j& 0. +0.j&-0. +0.j\\ 0.5-0.j&-0. +0.j&-0. -0.j& 0. +0.j\\\end{bmatrix} \\\\e^{-i\phi L}H(\mathbf{k})e^{i\phi L} = H(R_{\phi}\mathbf{k}) \\R_{{\phi}} = R\left(\phi\right)\\L = \begin{bmatrix} 0.5+0.j&-0. -0.j&-0. -0.j&-0. +0.j\\-0. +0.j&-0.5-0.j& 0. -0.j&-0. +0.j\\-0. +0.j& 0. +0.j&-0.5+0.j&-0. +0.j\\-0. -0.j&-0. -0.j&-0. -0.j& 0.5+0.j\\\end{bmatrix} \\\end{split}$

The rotation generators are different in the two cases

## Lattice models¶

### Generate Kekule-O model¶

6 sites (3 A and 3 B) per unit cell, spinless orbitals. Time-reversal, sublattice and 6-fold rotation and mirror symmetry.

sites = ['A1', 'B1', 'A2', 'B2', 'A3', 'B3']
norbs = [(site, 1) for site in sites]

# Time reversal
TR = qsymm.PointGroupElement(sympy.eye(2), True, False, np.eye(6))

# Chiral symmetry
C = qsymm.PointGroupElement(sympy.eye(2), False, True, np.kron(np.eye(3), ([[1, 0], [0, -1]])))

# Atom A rotates into B, B into A.
sphi = 2*sympy.pi/6
RC6 = sympy.Matrix([[sympy.cos(sphi), -sympy.sin(sphi)],
[sympy.sin(sphi), sympy.cos(sphi)]])
permC6 = {'A1': 'B1', 'B1': 'A2', 'A2': 'B2', 'B2': 'A3', 'A3': 'B3', 'B3': 'A1'}
C6 = qsymm.groups.symmetry_from_permutation(RC6, permC6, norbs)

RMx = sympy.Matrix([[-1, 0], [0, 1]])
permMx = {'A1': 'A3', 'B1': 'B2', 'A2': 'A2', 'B2': 'B1', 'A3': 'A1', 'B3': 'B3'}
Mx = qsymm.groups.symmetry_from_permutation(RMx, permMx, norbs)

symmetries = [C, TR, C6, Mx]

# Only need the unique hoppings, one weak and one strong
hopping_vectors = [
('A1', 'B1', np.array([0, -1])), # around ring
('A2', 'B3', np.array([0, -1])), # next UC
]

# construct Hamiltonian terms to symmetry-allowed terms
family = qsymm.bloch_family(hopping_vectors, symmetries, norbs=norbs)
qsymm.display_family(family)

$\begin{split}\displaystyle \left[\begin{matrix}0 & e^{- i k_{y}} & 0 & 0 & 0 & e^{\frac{i k_{y}}{2}} e^{- \frac{\sqrt{3} i k_{x}}{2}}\\e^{i k_{y}} & 0 & e^{- \frac{i k_{y}}{2}} e^{- \frac{\sqrt{3} i k_{x}}{2}} & 0 & 0 & 0\\0 & e^{\frac{i k_{y}}{2}} e^{\frac{\sqrt{3} i k_{x}}{2}} & 0 & e^{\frac{i k_{y}}{2}} e^{- \frac{\sqrt{3} i k_{x}}{2}} & 0 & 0\\0 & 0 & e^{- \frac{i k_{y}}{2}} e^{\frac{\sqrt{3} i k_{x}}{2}} & 0 & e^{i k_{y}} & 0\\0 & 0 & 0 & e^{- i k_{y}} & 0 & e^{\frac{i k_{y}}{2}} e^{\frac{\sqrt{3} i k_{x}}{2}}\\e^{- \frac{i k_{y}}{2}} e^{\frac{\sqrt{3} i k_{x}}{2}} & 0 & 0 & 0 & e^{- \frac{i k_{y}}{2}} e^{- \frac{\sqrt{3} i k_{x}}{2}} & 0\end{matrix}\right]\end{split}$
$\begin{split}\displaystyle \left[\begin{matrix}0 & 0 & 0 & e^{\frac{i k_{y}}{2}} e^{\frac{\sqrt{3} i k_{x}}{2}} & 0 & 0\\0 & 0 & 0 & 0 & e^{- \frac{i k_{y}}{2}} e^{\frac{\sqrt{3} i k_{x}}{2}} & 0\\0 & 0 & 0 & 0 & 0 & e^{- i k_{y}}\\e^{- \frac{i k_{y}}{2}} e^{- \frac{\sqrt{3} i k_{x}}{2}} & 0 & 0 & 0 & 0 & 0\\0 & e^{\frac{i k_{y}}{2}} e^{- \frac{\sqrt{3} i k_{x}}{2}} & 0 & 0 & 0 & 0\\0 & 0 & e^{i k_{y}} & 0 & 0 & 0\end{matrix}\right]\end{split}$

Make terms at k=0 and check degeneracy for some linear combination.

Gamma_terms = [term.subs(dict(k_x=0, k_y=0)) for term in family]
evals, evecs = np.linalg.eigh(
np.sum([
list(term.values()) * coeff
for term, coeff in zip(Gamma_terms, [1, 2])
],
axis=0)
)

evals

array([-4., -1., -1.,  1.,  1.,  4.])


Find symmetry representation in the low energy subspace

# projector to low energy subspace
proj = evecs[:, 1:5]
UC3 = (C6 * C6).U

# projected symmetry operator
pC3 = proj.T.conj() @ UC3 @ proj

assert np.allclose(pC3 @ pC3.T.conj(), np.eye(4))

evals2, evecs2 = np.linalg.eig(pC3)

# Check angles of the eigenvalues
np.angle(evals2) / (2 * np.pi)

array([ 0.33333333, -0.33333333,  0.33333333, -0.33333333])


### Generate Kekule-Y model¶

6 sites (3 A and 3 B) per unit cell, spinless orbitals. Time-reversal, sublattice and 3-fold rotation and mirror symmetry.

sites = ['A1', 'B1', 'A2', 'B2', 'A3', 'B3']
norbs = [(site, 1) for site in sites]

# Time reversal
TR = qsymm.time_reversal(2, U=np.eye(6))

# Chiral symmetry
C = qsymm.chiral(2, U=np.kron(np.eye(3), ([[1, 0], [0, -1]])))

# Atom A rotates into B, B into A.
sphi = 2*sympy.pi/3
RC3 = sympy.Matrix([
[sympy.cos(sphi), -sympy.sin(sphi)],
[sympy.sin(sphi), sympy.cos(sphi)],
])
permC3 = {'A1': 'A1', 'B1': 'B3', 'A2': 'A2', 'B2': 'B1', 'A3': 'A3', 'B3': 'B2'}
C3 = qsymm.groups.symmetry_from_permutation(RC3, permC3, norbs)

RMx = sympy.Matrix([[-1, 0], [0, 1]])
permMx = {'A1': 'A1', 'B1': 'B1', 'A2': 'A3', 'B2': 'B3', 'A3': 'A2', 'B3': 'B2'}
Mx = qsymm.groups.symmetry_from_permutation(RMx, permMx, norbs)

symmetries = [C, TR, C3, Mx]

# Only need the unique hoppings, one weak and one strong
hopping_vectors = [
('A1', 'B1', np.array([0, -1])), # Y
('A2', 'B3', np.array([0, -1])), # other
]

# construct Hamiltonian terms to symmetry-allowed terms
family = qsymm.bloch_family(hopping_vectors, symmetries, norbs=norbs)
qsymm.display_family(family)

$\begin{split}\displaystyle \left[\begin{matrix}0 & e^{- i k_{y}} & 0 & e^{\frac{i k_{y}}{2}} e^{\frac{\sqrt{3} i k_{x}}{2}} & 0 & e^{\frac{i k_{y}}{2}} e^{- \frac{\sqrt{3} i k_{x}}{2}}\\e^{i k_{y}} & 0 & 0 & 0 & 0 & 0\\0 & 0 & 0 & 0 & 0 & 0\\e^{- \frac{i k_{y}}{2}} e^{- \frac{\sqrt{3} i k_{x}}{2}} & 0 & 0 & 0 & 0 & 0\\0 & 0 & 0 & 0 & 0 & 0\\e^{- \frac{i k_{y}}{2}} e^{\frac{\sqrt{3} i k_{x}}{2}} & 0 & 0 & 0 & 0 & 0\end{matrix}\right]\end{split}$
$\begin{split}\displaystyle \left[\begin{matrix}0 & 0 & 0 & 0 & 0 & 0\\0 & 0 & e^{- \frac{i k_{y}}{2}} e^{- \frac{\sqrt{3} i k_{x}}{2}} & 0 & e^{- \frac{i k_{y}}{2}} e^{\frac{\sqrt{3} i k_{x}}{2}} & 0\\0 & e^{\frac{i k_{y}}{2}} e^{\frac{\sqrt{3} i k_{x}}{2}} & 0 & e^{\frac{i k_{y}}{2}} e^{- \frac{\sqrt{3} i k_{x}}{2}} & 0 & e^{- i k_{y}}\\0 & 0 & e^{- \frac{i k_{y}}{2}} e^{\frac{\sqrt{3} i k_{x}}{2}} & 0 & e^{i k_{y}} & 0\\0 & e^{\frac{i k_{y}}{2}} e^{- \frac{\sqrt{3} i k_{x}}{2}} & 0 & e^{- i k_{y}} & 0 & e^{\frac{i k_{y}}{2}} e^{\frac{\sqrt{3} i k_{x}}{2}}\\0 & 0 & e^{i k_{y}} & 0 & e^{- \frac{i k_{y}}{2}} e^{- \frac{\sqrt{3} i k_{x}}{2}} & 0\end{matrix}\right]\end{split}$

Make terms at k=0 and check degeneracy for some linear combination.

Gamma_terms = [term.subs(dict(k_x=0, k_y=0)) for term in family]
evals, evecs = np.linalg.eigh(
np.sum([
list(term.values()) * coeff
for term, coeff in zip(Gamma_terms, [1, 2])
],
axis=0)
)

evals

array([-5.19615242e+00, -8.69969719e-18,  0.00000000e+00,  0.00000000e+00,
0.00000000e+00,  5.19615242e+00])


Find symmetry representation of 3-fold rotations in the low energy subspace

# projector to low energy subspace
proj = evecs[:, 1:5]
UC3 = C3.U

# projected symmetry operator
pC3 = proj.T.conj() @ UC3 @ proj

assert np.allclose(pC3 @ pC3.T.conj(), np.eye(4))

evals2, evecs2 = np.linalg.eig(pC3)

# Check angles of the eigenvalues
np.angle(evals2) / (2 * np.pi)

array([ 0.        ,  0.        ,  0.33333333, -0.33333333])


The symmetry representation in the low energy subspace near the Gamma point is different in the two cases.