Skip to content

Commit 59aad10

Browse files
committed
enh: finalized Thomas fixes
We now retain Thomas alignment but shift all atoms to the center. Signed-off-by: Nick Papior <[email protected]>
1 parent 0e60337 commit 59aad10

File tree

2 files changed

+50
-23
lines changed

2 files changed

+50
-23
lines changed

sisl/geom/bilayer.py

Lines changed: 48 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import numpy as np
22

3+
from math import acos, pi
34
from sisl import geom, Atom, Cuboid
45

56
__all__ = ['bilayer']
@@ -28,7 +29,7 @@ def bilayer(bond=1.42, bottom_atom=None, top_atom=None, stacking='AB',
2829
twist : tuple of int, optional
2930
integer coordinates (m, n) defining a commensurate twist angle
3031
separation : float, optional
31-
distance between the two layers (in Angstrom)
32+
distance between the two layers
3233
ret_angle : bool, optional
3334
return the twist angle (in degrees) in addition to the geometry instance
3435
layer : {'both', 'bottom', 'top'}
@@ -50,12 +51,15 @@ def bilayer(bond=1.42, bottom_atom=None, top_atom=None, stacking='AB',
5051
top = geom.honeycomb(bond, top_atom)
5152
ref_cell = bottom.cell.copy()
5253

53-
if stacking.lower() == 'aa':
54+
stacking = stacking.lower()
55+
if stacking == 'aa':
5456
top = top.move([0, 0, separation])
55-
elif stacking.lower() == 'ab':
57+
elif stacking == 'ab':
5658
top = top.move([-bond, 0, separation])
57-
elif stacking.lower() == 'ba':
59+
elif stacking == 'ba':
5860
top = top.move([bond, 0, separation])
61+
else:
62+
raise ValueError("bilayer: stacking must be one of {AA, AB, BA}")
5963

6064
# Compute twist angle
6165
m, n = twist
@@ -75,48 +79,69 @@ def bilayer(bond=1.42, bottom_atom=None, top_atom=None, stacking='AB',
7579
else:
7680
# Twisting
7781
cos_theta = (n ** 2 + 4 * n * m + m ** 2) / (2 * (n ** 2 + n * m + m ** 2))
78-
theta = np.arccos(cos_theta) * 180 / np.pi
82+
theta = acos(cos_theta) * 180 / pi
7983
rep = 4 * (n + m)
8084
natoms = 2 * (n ** 2 + n * m + m ** 2)
8185

8286
if rep > 1:
8387
# Set origo through an A atom near the middle of the geometry
84-
bottom = bottom.tile(rep, axis=0).tile(rep, axis=1)
85-
top = top.tile(rep, axis=0).tile(rep, axis=1)
86-
tvec = rep * (ref_cell[0] + ref_cell[1]) / 2
87-
bottom = bottom.move(-tvec)
88-
top = top.move(-tvec)
88+
align_vec = - rep * (ref_cell[0] + ref_cell[1]) / 2
89+
90+
bottom = (bottom
91+
.tile(rep, axis=0)
92+
.tile(rep, axis=1)
93+
.move(align_vec))
8994

9095
# Set new lattice vectors
9196
bottom.cell[0] = n * ref_cell[0] + m * ref_cell[1]
9297
bottom.cell[1] = -m * ref_cell[0] + (n + m) * ref_cell[1]
9398

99+
# Remove atoms outside cell
100+
cell_box = Cuboid(bottom.cell, center=[- bond * 1e-4] * 3)
101+
102+
# Reduce atoms in bottom
103+
inside_idx = cell_box.within_index(bottom.xyz)
104+
bottom = bottom.sub(inside_idx)
105+
94106
# Rotate top layer around A atom in bottom layer
95-
top = top.rotate(theta, [0, 0, 1])
107+
top = (top
108+
.tile(rep, axis=0)
109+
.tile(rep, axis=1)
110+
.move(align_vec)
111+
.rotate(theta, [0, 0, 1]))
96112

113+
inside_idx = cell_box.within_index(top.xyz)
114+
top = top.sub(inside_idx)
115+
116+
# Ensure the cells are commensurate
97117
top.cell[:] = bottom.cell[:]
98118

99119
# Which layers to be returned
100-
if layer.lower() == 'bottom':
120+
layer = layer.lower()
121+
if layer == 'bottom':
101122
bilayer = bottom
102-
elif layer.lower() == 'top':
123+
elif layer == 'top':
103124
bilayer = top
104-
else:
125+
elif layer == 'both':
105126
bilayer = bottom.add(top)
106127
natoms *= 2
128+
else:
129+
raise ValueError("bilayer: layer must be one of {both, bottom, top}")
107130

108131
if rep > 1:
109-
# Remove atoms outside cell
110-
cell_box = Cuboid(bilayer.cell)
111-
cell_box.set_center([-0.0001] * 3)
112-
inside_idx = cell_box.within_index(bilayer.xyz)
113-
bilayer = bilayer.sub(inside_idx)
114-
115-
# Rotate whole cell
132+
# Rotate and shift unit cell back
133+
fxyz_min = bilayer.fxyz.min(axis=0)
134+
fxyz_min[2] = 0.
135+
# This is a small hack since rotate is not numerically
136+
# precise.
137+
# TODO We could consider using mpfmath in Quaternion for increased
138+
# precision...
139+
fxyz_min[np.fabs(fxyz_min) > 1.e-7] *= 0.49
140+
offset = fxyz_min.dot(bilayer.cell)
116141
vec = bilayer.cell[0] + bilayer.cell[1]
117142
vec_costh = vec[0] / vec.dot(vec) ** 0.5
118-
vec_th = np.arccos(vec_costh) * 180 / np.pi
119-
bilayer = bilayer.rotate(vec_th, [0, 0, 1])
143+
vec_th = acos(vec_costh) * 180 / pi
144+
bilayer = bilayer.move(-offset).rotate(vec_th, [0, 0, 1])
120145

121146
# Sanity check
122147
assert len(bilayer) == natoms

sisl/geom/tests/test_geom.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ def test_diamond():
3636
def test_bilayer():
3737
a = bilayer(1.42)
3838
a = bilayer(1.42, stacking='AA')
39+
a = bilayer(1.42, stacking='BA')
40+
a = bilayer(1.42, stacking='AB')
3941
for m in range(7):
4042
a = bilayer(1.42, twist=(m, m + 1))
4143
a = bilayer(1.42, twist=(6, 7), layer='bottom')

0 commit comments

Comments
 (0)