oILAB
Loading...
Searching...
No Matches
bilayers.py
Go to the documentation of this file.
1from dataclasses import dataclass, field
2import numpy as np
3import pyoilab as gb
4
5from layers import (
6 Layer2D,
7 make_graphene_layer_from_basis,
8 make_mos2_layer_from_basis,
9)
10
11
12@dataclass(frozen=True)
14 """
15 Bilayer consisting of two 2D multilattices.
16
17 Parameters
18 ----------
19 name : str
20 Name of the bilayer.
21 top_layer : Layer2D
22 Top layer.
23 bottom_layer : Layer2D
24 Bottom layer.
25 interlayer_spacing : float
26 Distance between the Bravais lattice planes of the two layers.
27 """
28 name: str
29 top_layer: Layer2D
30 bottom_layer: Layer2D
31 interlayer_spacing: float
32
33 # Constructed once when the bilayer is created
34 bicrystal: gb.BiCrystal2D = field(init=False, repr=False)
35
36 def __post_init__(self):
37 object.__setattr__(
38 self,
39 "bicrystal",
40 gb.BiCrystal2D(
41 self.bottom_layer.lattice,
42 self.top_layer.lattice,
43 True,
44 ),
45 )
46
47 @property
48 def atom_style(self) -> str:
49 if self.top_layer.atom_style != self.bottom_layer.atom_style:
50 raise ValueError("Top and bottom layers must use the same atom_style.")
51 return self.top_layer.atom_style
52
53 def box(self, ell1, ell2):
54 """
55 Build Cartesian atomic positions using CSL lattice vectors.
56
57 Parameters
58 ----------
59 ell1, ell2 : gb.LatticeVector2D
60 CSL lattice vectors belonging to self.bicrystal.csl.
61 These may be primitive vectors or any integer combination of them.
62
63 Returns
64 -------
65 positions : list[(x, y, z)]
66 atom_types : list[int]
67 charges : list[float]
68 labels : list[str]
69 molecule_ids : list[int]
70 """
71 # Convert CSL vectors into each layer's lattice vectors
72 ell1_bottom = self.bicrystal.getLatticeVectorInA(ell1)
73 ell2_bottom = self.bicrystal.getLatticeVectorInA(ell2)
74
75 ell1_top = self.bicrystal.getLatticeVectorInB(ell1)
76 ell2_top = self.bicrystal.getLatticeVectorInB(ell2)
77
78 # Build each layer independently
79 pos_bottom, types_bottom, charges_bottom, labels_bottom = \
80 self.bottom_layer.box([ell1_bottom, ell2_bottom])
81
82 pos_top, types_top, charges_top, labels_top = \
83 self.top_layer.box([ell1_top, ell2_top])
84
85 # Shift top layer by interlayer spacing
86 pos_top = [
88 for (x, y, z) in pos_top
89 ]
90
91 # Offset atom types in top layer
92 type_offset = max(types_bottom)
93 types_top = [t + type_offset for t in types_top]
94
95 # Molecule IDs: bottom = 1, top = 2
96 molecule_ids_bottom = [1] * len(pos_bottom)
97 molecule_ids_top = [2] * len(pos_top)
98
99 # Concatenate
100 positions = pos_bottom + pos_top
101 atom_types = types_bottom + types_top
102 charges = charges_bottom + charges_top
103 labels = labels_bottom + labels_top
104 molecule_ids = molecule_ids_bottom + molecule_ids_top
105
106 return positions, atom_types, charges, labels, molecule_ids
107
108
109def heterodeform_bilayer(
110 bilayer: Bilayer2D,
111 F_bottom=None,
112 F_top=None,
113 new_name=None,
114):
115 """
116 Apply in-plane deformation gradients to the two layers of a bilayer.
117
118 Default behavior: no deformation unless provided explicitly.
119
120 Parameters
121 ----------
122 bilayer : Bilayer2D
123 Input bilayer.
124 F_bottom : np.ndarray or None
125 Deformation gradient for the bottom layer.
126 F_top : np.ndarray or None
127 Deformation gradient for the top layer.
128 new_name : str or None
129 Optional name for the deformed bilayer.
130
131 Returns
132 -------
133 Bilayer2D
134 New bilayer with deformed layers. Its bicrystal is constructed once
135 during Bilayer2D initialization.
136 """
137 if F_bottom is None:
138 F_bottom = np.eye(2)
139 if F_top is None:
140 F_top = np.eye(2)
141
142 bottom_layer = bilayer.bottom_layer.deform(F_bottom)
143 top_layer = bilayer.top_layer.deform(F_top)
144
145 return Bilayer2D(
146 name=bilayer.name if new_name is None else new_name,
147 top_layer=top_layer,
148 bottom_layer=bottom_layer,
149 interlayer_spacing=bilayer.interlayer_spacing,
150 )
151
152def make_ab_graphene_bilayer(interlayer_spacing) -> Bilayer2D:
153 """
154 AB-stacked bilayer graphene reference configuration.
155
156 Top layer basis:
157 s1 = (0, 0)
158 s2 = (1/3, 2/3)
159
160 Bottom layer basis:
161 t1 = (0, 0)
162 t2 = (2/3, 1/3)
163 """
164 top_layer = make_graphene_layer_from_basis(
165 name="graphene_top",
166 basis_frac_xy=[
167 (0.0, 0.0),
168 (1.0 / 3.0, 2.0 / 3.0),
169 ],
170 )
171
172 bottom_layer = make_graphene_layer_from_basis(
173 name="graphene_bottom",
174 basis_frac_xy=[
175 (0.0, 0.0),
176 (2.0 / 3.0, 1.0 / 3.0),
177 ],
178 )
179
180 return Bilayer2D(
181 name="graphene_ab",
182 top_layer=top_layer,
183 bottom_layer=bottom_layer,
184 interlayer_spacing=interlayer_spacing,
185 )
186
187
188def make_aa_prime_mos2_bilayer(interlayer_spacing) -> Bilayer2D:
189 """
190 AA'-stacked bilayer MoS2 reference configuration.
191
192 Top layer basis:
193 s1 = (0, 0, 0)
194 s2 = (0, 0, 1)
195 s3 = (1/3, 2/3, 1/2)
196
197 Bottom layer basis:
198 t1 = (1/3, 2/3, 0)
199 t2 = (1/3, 2/3, 1)
200 t3 = (0, 0, 1/2)
201 """
202 t = 3.1902
203
204 top_layer = make_mos2_layer_from_basis(
205 name="mos2_top",
206 basis_data=[
207 ("S", (0.0, 0.0), 0.0 * t, 1, -0.42),
208 ("S", (0.0, 0.0), 1.0 * t, 2, -0.42),
209 ("Mo", (1.0 / 3.0, 2.0 / 3.0), 0.5 * t, 3, 0.84),
210 ],
211 )
212
213 bottom_layer = make_mos2_layer_from_basis(
214 name="mos2_bottom",
215 basis_data=[
216 ("S", (1.0 / 3.0, 2.0 / 3.0), 0.0 * t, 1, -0.42),
217 ("S", (1.0 / 3.0, 2.0 / 3.0), 1.0 * t, 2, -0.42),
218 ("Mo", (0.0, 0.0), 0.5 * t, 3, 0.84),
219 ],
220 )
221
222 return Bilayer2D(
223 name="mos2_aa_prime",
224 top_layer=top_layer,
225 bottom_layer=bottom_layer,
226 interlayer_spacing=interlayer_spacing,
227 )
Layer2D bottom_layer
Definition bilayers.py:30
box(self, ell1, ell2)
Definition bilayers.py:53
Layer2D top_layer
Definition bilayers.py:29
float interlayer_spacing
Definition bilayers.py:31
str atom_style(self)
Definition bilayers.py:48