Skip to content

Commit 22aa50d

Browse files
committed
Merge branch 'bilayer'
2 parents b0dc082 + dcf4bfe commit 22aa50d

File tree

4 files changed

+167
-24
lines changed

4 files changed

+167
-24
lines changed

sisl/geom/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
honeycomb
2323
nanotube
2424
diamond
25+
bilayer
2526
2627
2728
Basic
@@ -44,6 +45,8 @@
4445
:noindex:
4546
.. autofunction:: graphene
4647
:noindex:
48+
.. autofunction:: bilayer
49+
:noindex:
4750
4851
4952
Nanotube
@@ -64,5 +67,6 @@
6467
from .flat import *
6568
from .nanotube import *
6669
from .special import *
70+
from .bilayer import *
6771

6872
__all__ = [s for s in dir() if not s.startswith('_')]

sisl/geom/bilayer.py

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
import numpy as np
2+
3+
from sisl import geom, Atom, Cuboid
4+
5+
__all__ = ['bilayer']
6+
7+
8+
def bilayer(bond=1.42, bottom_atom=None, top_atom=None, stacking='AB',
9+
twist=(0, 0), separation=3.35, ret_angle=False, layer='both'):
10+
r""" Commensurate unit cell of a hexagonal bilayer structure, possibly with a twist angle.
11+
12+
This routine follows the prescription of twisted bilayer graphene found in [1]_.
13+
14+
Notes
15+
-----
16+
This routine may change in the future to force some of the arguments.
17+
18+
Parameters
19+
----------
20+
bond : float, optional
21+
bond length between atoms in the honeycomb lattice
22+
bottom_atom : Atom, optional
23+
atom (or atoms) in the bottom layer. Defaults to ``Atom(6)``
24+
top_atom : Atom, optional
25+
atom (or atoms) in the top layer, defaults to `bottom_atom`
26+
stacking : {'AB', 'AA'}
27+
stacking sequence of the bilayer
28+
twist : tuple of int, optional
29+
integer coordinates (m, n) defining a commensurate twist angle
30+
separation : float, optional
31+
distance between the two layers (in Angstrom)
32+
ret_angle : bool, optional
33+
return the twist angle (in degrees) in addition to the geometry instance
34+
layer : {'both', 'bottom', 'top'}
35+
control which layer(s) to return
36+
37+
References
38+
----------
39+
.. [1] G. Trambly de Laissardiere, D. Mayou, L. Magaud, "Localization of Dirac Electrons in Rotated Graphene Bilayers", Nano Letts. 10, 804-808 (2010)
40+
"""
41+
if bottom_atom is None:
42+
bottom_atom = Atom(Z=6, R=bond * 1.01)
43+
if top_atom is None:
44+
top_atom = bottom_atom
45+
46+
# Construct two layers
47+
bottom = geom.honeycomb(bond, bottom_atom)
48+
top = geom.honeycomb(bond, top_atom)
49+
ref_cell = bottom.cell.copy()
50+
51+
if stacking.lower() == 'aa':
52+
top = top.move([0, 0, separation])
53+
elif stacking.lower() == 'ab':
54+
top = top.move([bond, 0, separation])
55+
56+
# Compute twist angle
57+
m, n = twist
58+
m, n = abs(m), abs(n)
59+
if m > n:
60+
# Set m as the smaller integer
61+
m, n = n, m
62+
63+
if not (isinstance(n, int) and isinstance(m, int)):
64+
raise ValueError("bilayer: twist coordinates need to be integers!")
65+
66+
if m == n:
67+
# No twisting
68+
theta = 0
69+
rep = 1
70+
natoms = 2
71+
else:
72+
# Twisting
73+
cos_theta = (n ** 2 + 4 * n * m + m ** 2) / (2 * (n ** 2 + n * m + m ** 2))
74+
theta = np.arccos(cos_theta) * 180 / np.pi
75+
rep = 4 * (n + m)
76+
natoms = 2 * (n ** 2 + n * m + m ** 2)
77+
78+
if rep > 1:
79+
# Set origo through an A atom near the middle of the geometry
80+
bottom = bottom.tile(rep, axis=0).tile(rep, axis=1)
81+
top = top.tile(rep, axis=0).tile(rep, axis=1)
82+
tvec = rep * (ref_cell[0] + ref_cell[1]) / 2
83+
bottom = bottom.move(-tvec)
84+
top = top.move(-tvec)
85+
86+
# Set new lattice vectors
87+
bottom.cell[0] = n * ref_cell[0] + m * ref_cell[1]
88+
bottom.cell[1] = -m * ref_cell[0] + (n + m) * ref_cell[1]
89+
90+
# Rotate top layer around A atom in bottom layer
91+
top = top.rotate(theta, [0, 0, 1])
92+
93+
top.cell[:] = bottom.cell[:]
94+
95+
# Which layers to be returned
96+
if layer.lower() == 'bottom':
97+
bilayer = bottom
98+
elif layer.lower() == 'top':
99+
bilayer = top
100+
else:
101+
bilayer = bottom.add(top)
102+
natoms *= 2
103+
104+
if rep > 1:
105+
# Remove atoms outside cell
106+
cell_box = Cuboid(bilayer.cell)
107+
cell_box.set_origo([-0.0001] * 3)
108+
inside_idx = cell_box.within_index(bilayer.xyz)
109+
bilayer = bilayer.sub(inside_idx)
110+
111+
# Rotate whole cell
112+
vec = bilayer.cell[0] + bilayer.cell[1]
113+
vec_costh = vec[0] / vec.dot(vec) ** 0.5
114+
vec_th = np.arccos(vec_costh) * 180 / np.pi
115+
bilayer = bilayer.rotate(vec_th, [0, 0, 1])
116+
else:
117+
# Shift back
118+
bilayer = bilayer.move(tvec)
119+
120+
# Sanity check
121+
assert len(bilayer) == natoms
122+
123+
if ret_angle:
124+
return bilayer, theta
125+
else:
126+
return bilayer

sisl/geom/nanotube.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ def nanotube(bond, atom=None, chirality=(1, 1)):
2020
chirality of nanotube (n, m)
2121
"""
2222
if atom is None:
23-
atom = Atom[6]
23+
atom = Atom(Z=6, R=bond * 1.01)
2424

2525
# Correct the input...
2626
n, m = chirality

sisl/geom/tests/test_geom.py

Lines changed: 36 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -7,26 +7,39 @@
77
import numpy as np
88

99

10-
class Test:
11-
12-
def test_basis(self):
13-
a = sc(2.52, Atom['Fe'])
14-
a = bcc(2.52, Atom['Fe'])
15-
a = bcc(2.52, Atom['Fe'], orthogonal=True)
16-
a = fcc(2.52, Atom['Au'])
17-
a = fcc(2.52, Atom['Au'], orthogonal=True)
18-
a = hcp(2.52, Atom['Au'])
19-
a = hcp(2.52, Atom['Au'], orthogonal=True)
20-
21-
def test_flat(self):
22-
a = graphene()
23-
a = graphene(atom='C')
24-
a = graphene(orthogonal=True)
25-
26-
def test_nanotube(self):
27-
a = nanotube(1.42)
28-
a = nanotube(1.42, chirality=(3, 5))
29-
a = nanotube(1.42, chirality=(6, -3))
30-
31-
def test_diamond(self):
32-
a = diamond()
10+
def test_basis():
11+
a = sc(2.52, Atom['Fe'])
12+
a = bcc(2.52, Atom['Fe'])
13+
a = bcc(2.52, Atom['Fe'], orthogonal=True)
14+
a = fcc(2.52, Atom['Au'])
15+
a = fcc(2.52, Atom['Au'], orthogonal=True)
16+
a = hcp(2.52, Atom['Au'])
17+
a = hcp(2.52, Atom['Au'], orthogonal=True)
18+
19+
20+
def test_flat():
21+
a = graphene()
22+
a = graphene(atom='C')
23+
a = graphene(orthogonal=True)
24+
25+
26+
def test_nanotube():
27+
a = nanotube(1.42)
28+
a = nanotube(1.42, chirality=(3, 5))
29+
a = nanotube(1.42, chirality=(6, -3))
30+
31+
32+
def test_diamond():
33+
a = diamond()
34+
35+
36+
def test_bilayer():
37+
a = bilayer(1.42)
38+
a = bilayer(1.42, stacking='AA')
39+
for m in range(7):
40+
a = bilayer(1.42, twist=(m, m + 1))
41+
a = bilayer(1.42, twist=(6, 7), layer='bottom')
42+
a = bilayer(1.42, twist=(6, 7), layer='TOP')
43+
a = bilayer(1.42, bottom_atom=(Atom['B'], Atom['N']), twist=(6, 7))
44+
a = bilayer(1.42, top_atom=(Atom(5), Atom(7)), twist=(6, 7))
45+
a, th = bilayer(1.42, twist=(6, 7), ret_angle=True)

0 commit comments

Comments
 (0)