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
0 commit comments