Skip to content

Commit f5695c8

Browse files
committed
enh: improvements to (twisted) bilayer geometry construction
1 parent 5b84510 commit f5695c8

File tree

2 files changed

+80
-54
lines changed

2 files changed

+80
-54
lines changed

sisl/geom/bilayer.py

Lines changed: 73 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -5,62 +5,87 @@
55
__all__ = ['bilayer']
66

77

8-
def bilayer(bond, atom=None, twist=(5, 6), separation=3.35, return_angle=False, layer='both'):
9-
""" Commensurate unit cell of a twisted (hexagonal) bilayer structure.
8+
def bilayer(bond=1.42, bottom_atom=None, top_atom=None, stacking='AB', twist=(0, 0), separation=3.35, ret_angle=False, layer='both'):
9+
""" Commensurate unit cell of a hexagonal bilayer structure, possibly with a twist angle.
1010
1111
This routine follows the prescription of twisted bilayer graphene found in
1212
Laissardiere et al., Nano Lett. 10, 804-808 (2010).
1313
1414
1515
Parameters
1616
----------
17-
bond : float
18-
length between atoms in nano-tube
19-
atom : Atom(6)
20-
bilayer atoms
21-
twist : (int, int)
22-
coordinates (m, n) defining the twist angle (theta)
17+
bond : 1.42
18+
bond length (in Angstrom) between atoms in the honeycomb lattice
19+
bottom_atom : Atom(6)
20+
atom (or atoms) in the bottom layer
21+
top_atom : Atom(6)
22+
atom (or atoms) in the top layer
23+
stacking : {'AB', 'AA'}
24+
stacking sequence of the bilayer
25+
twist : (0, 0)
26+
integer coordinates (m, n) defining a commensurate twist angle
2327
separation : float
24-
distance between the two layers
25-
return_angle : bool
26-
computed twist angle is returned in addition to the geometry instance
28+
distance between the two layers (in Angstrom)
29+
ret_angle : bool
30+
return the twist angle (in degrees) in addition to the geometry instance
2731
layer : {'both', 'bottom', 'top'}
2832
control which layer(s) to include in geometry
2933
"""
30-
if atom is None:
31-
atom = Atom(Z=6, R=bond * 1.01)
34+
if bottom_atom is None:
35+
bottom_atom = Atom(Z=6, R=bond * 1.01)
36+
if top_atom is None:
37+
top_atom = Atom(Z=6, R=bond * 1.01)
38+
39+
# Construct two layers
40+
bottom = geom.honeycomb(bond=bond, atom=bottom_atom)
41+
top = geom.honeycomb(bond=bond, atom=top_atom)
42+
honeycomb = bottom.copy()
43+
44+
if stacking.lower() == 'aa':
45+
top = top.move([0, 0, separation])
46+
elif stacking.lower() == 'ab':
47+
top = top.move([bond, 0, separation])
3248

3349
# Compute twist angle
3450
m, n = twist
51+
m, n = abs(m), abs(n)
3552
if m > n:
53+
# Set m as the smaller integer
3654
m, n = n, m
3755

3856
if not (isinstance(n, int) and isinstance(m, int)):
3957
raise RuntimeError('twist coordinates need to be integers!')
4058

41-
cos_theta = (n ** 2 + 4 * n * m + m ** 2) / (2 * (n ** 2 + n * m + m ** 2) )
42-
theta = np.arccos(cos_theta) * 360 / (2 * np.pi)
43-
44-
# Build structure
45-
graphene = geom.graphene(bond=bond, atom=atom)
46-
47-
# Choose a repetition that should assure both layers extend over the unit cell
48-
rep = 4 * (n + m)
49-
50-
# Construct bottom layer
51-
bottom = graphene.tile(rep, axis=0).tile(rep, axis=1)
52-
bottom = bottom.move(-bottom.center())
53-
bottom.cell[0] = n * graphene.cell[0] + m * graphene.cell[1]
54-
bottom.cell[1] = -m * graphene.cell[0] + (n + m) * graphene.cell[1]
55-
56-
# Construct top layer
57-
top = bottom.move([0, 0, separation]).rotate(theta, [0, 0, 1])
58-
top.cell[:] = bottom.cell[:]
59-
60-
# Compute number of atoms per layer
61-
natoms = 2 * (n ** 2 + n * m + m ** 2)
62-
63-
# Which layers to include
59+
if m == n:
60+
# No twisting
61+
theta = 0
62+
rep = 1
63+
natoms = 2
64+
else:
65+
# Twisting
66+
cos_theta = (n ** 2 + 4 * n * m + m ** 2) / (2 * (n ** 2 + n * m + m ** 2) )
67+
theta = np.arccos(cos_theta) * 360 / (2 * np.pi)
68+
rep = 4 * (n + m)
69+
natoms = 2 * (n ** 2 + n * m + m ** 2)
70+
71+
if rep > 1:
72+
# Set origo through an A atom near the middle of the geometry
73+
bottom = bottom.tile(rep, axis=0).tile(rep, axis=1)
74+
top = top.tile(rep, axis=0).tile(rep, axis=1)
75+
tvec = rep * (honeycomb.cell[0] + honeycomb.cell[1]) / 2
76+
bottom = bottom.move(-tvec)
77+
top = top.move(-tvec)
78+
79+
# Set new lattice vectors
80+
bottom.cell[0] = n * honeycomb.cell[0] + m * honeycomb.cell[1]
81+
bottom.cell[1] = -m * honeycomb.cell[0] + (n + m) * honeycomb.cell[1]
82+
83+
# Rotate top layer around A atom in bottom layer
84+
top = top.rotate(theta, [0, 0, 1])
85+
86+
top.cell[:] = bottom.cell[:]
87+
88+
# Which layers to be returned
6489
if layer.lower() == 'bottom':
6590
bilayer = bottom
6691
elif layer.lower() == 'top':
@@ -69,27 +94,23 @@ def bilayer(bond, atom=None, twist=(5, 6), separation=3.35, return_angle=False,
6994
bilayer = bottom.add(top)
7095
natoms *= 2
7196

72-
# Remove atoms outside cell
73-
cell_box = Cuboid(bilayer.cell[:], center=bilayer.sc.center())
74-
outside_idx = []
75-
for i in bilayer:
76-
if not cell_box.within(bilayer[i]):
77-
outside_idx.append(i)
78-
bilayer = bilayer.remove(outside_idx)
79-
80-
# Center geometry around first atom
81-
bilayer = bilayer.move(-bilayer.xyz[0])
97+
if rep > 1:
98+
# Remove atoms outside cell
99+
center = 1e-3 * np.array([1, 1, 1])
100+
cell_box = Cuboid(1.0 * bilayer.cell[:], center=center)
101+
inside_idx = cell_box.within_index(bilayer.xyz)
102+
bilayer = bilayer.sub(inside_idx)
82103

83-
# Rotate whole cell
84-
vec = bilayer.cell[0] + bilayer.cell[1]
85-
vec_costh = np.dot([1, 0, 0], vec) / np.dot(vec, vec) ** .5
86-
vec_th = np.arccos(vec_costh) * 360 / (2 * np.pi)
87-
bilayer = bilayer.rotate(vec_th, [0, 0, 1])
104+
# Rotate whole cell
105+
vec = bilayer.cell[0] + bilayer.cell[1]
106+
vec_costh = np.dot([1, 0, 0], vec) / np.dot(vec, vec) ** .5
107+
vec_th = np.arccos(vec_costh) * 360 / (2 * np.pi)
108+
bilayer = bilayer.rotate(vec_th, [0, 0, 1])
88109

89110
# Sanity check
90111
assert len(bilayer) == natoms
91112

92-
if return_angle:
113+
if ret_angle:
93114
return bilayer, theta
94115
else:
95116
return bilayer

sisl/geom/tests/test_geom.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,12 @@ def test_diamond(self):
3232
a = diamond()
3333

3434
def test_bilayer(self):
35-
a = bilayer(1.42, twist=(6, 7))
35+
a = bilayer(1.42)
36+
a = bilayer(1.42, stacking='AA')
37+
for m in range(7):
38+
a = bilayer(1.42, twist=(m, m + 1))
3639
a = bilayer(1.42, twist=(6, 7), layer='bottom')
3740
a = bilayer(1.42, twist=(6, 7), layer='TOP')
38-
a, th = bilayer(1.42, twist=(6, 7), return_angle=True)
41+
a = bilayer(1.42, bottom_atom=(Atom['B'], Atom['N']), twist=(6, 7))
42+
a = bilayer(1.42, top_atom=(Atom(5), Atom(7)), twist=(6, 7))
43+
a, th = bilayer(1.42, twist=(6, 7), ret_angle=True)

0 commit comments

Comments
 (0)