root / ase / lattice / surface.py @ 16
Historique | Voir | Annoter | Télécharger (10,52 ko)
1 |
"""Helper functions for creating the most common surfaces and related tasks.
|
---|---|
2 |
|
3 |
The helper functions can create the most common low-index surfaces,
|
4 |
add vacuum layers and add adsorbates.
|
5 |
|
6 |
"""
|
7 |
|
8 |
from math import sqrt |
9 |
|
10 |
import numpy as np |
11 |
|
12 |
from ase.atom import Atom |
13 |
from ase.atoms import Atoms |
14 |
from ase.data import reference_states, atomic_numbers |
15 |
|
16 |
|
17 |
def fcc100(symbol, size, a=None, vacuum=None): |
18 |
"""FCC(100) surface.
|
19 |
|
20 |
Supported special adsorption sites: 'ontop', 'bridge', 'hollow'."""
|
21 |
return surface(symbol, 'fcc', '100', size, a, None, vacuum) |
22 |
|
23 |
def fcc110(symbol, size, a=None, vacuum=None): |
24 |
"""FCC(110) surface.
|
25 |
|
26 |
Supported special adsorption sites: 'ontop', 'longbridge',
|
27 |
'shortbridge','hollow'."""
|
28 |
return surface(symbol, 'fcc', '110', size, a, None, vacuum) |
29 |
|
30 |
def bcc100(symbol, size, a=None, vacuum=None): |
31 |
"""BCC(100) surface.
|
32 |
|
33 |
Supported special adsorption sites: 'ontop', 'bridge', 'hollow'."""
|
34 |
return surface(symbol, 'bcc', '100', size, a, None, vacuum) |
35 |
|
36 |
def bcc110(symbol, size, a=None, vacuum=None, orthogonal=False): |
37 |
"""BCC(110) surface.
|
38 |
|
39 |
Supported special adsorption sites: 'ontop', 'longbridge',
|
40 |
'shortbridge', 'hollow'.
|
41 |
|
42 |
Use *orthogonal=True* to get an orthogonal unit cell - works only
|
43 |
for size=(i,j,k) with j even."""
|
44 |
return surface(symbol, 'bcc', '110', size, a, None, vacuum, orthogonal) |
45 |
|
46 |
def bcc111(symbol, size, a=None, vacuum=None, orthogonal=False): |
47 |
"""BCC(111) surface.
|
48 |
|
49 |
Supported special adsorption sites: 'ontop'.
|
50 |
|
51 |
Use *orthogonal=True* to get an orthogonal unit cell - works only
|
52 |
for size=(i,j,k) with j even."""
|
53 |
return surface(symbol, 'bcc', '111', size, a, None, vacuum, orthogonal) |
54 |
|
55 |
def fcc111(symbol, size, a=None, vacuum=None, orthogonal=False): |
56 |
"""FCC(111) surface.
|
57 |
|
58 |
Supported special adsorption sites: 'ontop', 'bridge', 'fcc' and 'hcp'.
|
59 |
|
60 |
Use *orthogonal=True* to get an orthogonal unit cell - works only
|
61 |
for size=(i,j,k) with j even."""
|
62 |
return surface(symbol, 'fcc', '111', size, a, None, vacuum, orthogonal) |
63 |
|
64 |
def hcp0001(symbol, size, a=None, c=None, vacuum=None, orthogonal=False): |
65 |
"""HCP(0001) surface.
|
66 |
|
67 |
Supported special adsorption sites: 'ontop', 'bridge', 'fcc' and 'hcp'.
|
68 |
|
69 |
Use *orthogonal=True* to get an orthogonal unit cell - works only
|
70 |
for size=(i,j,k) with j even."""
|
71 |
return surface(symbol, 'hcp', '0001', size, a, c, vacuum, orthogonal) |
72 |
|
73 |
|
74 |
def add_adsorbate(slab, adsorbate, height, position=(0, 0), offset=None, |
75 |
mol_index=0):
|
76 |
"""Add an adsorbate to a surface.
|
77 |
|
78 |
This function adds an adsorbate to a slab. If the slab is
|
79 |
produced by one of the utility functions in ase.lattice.surface, it
|
80 |
is possible to specify the position of the adsorbate by a keyword
|
81 |
(the supported keywords depend on which function was used to
|
82 |
create the slab).
|
83 |
|
84 |
If the adsorbate is a molecule, the atom indexed by the mol_index
|
85 |
optional argument is positioned on top of the adsorption position
|
86 |
on the surface, and it is the responsibility of the user to orient
|
87 |
the adsorbate in a sensible way.
|
88 |
|
89 |
This function can be called multiple times to add more than one
|
90 |
adsorbate.
|
91 |
|
92 |
Parameters:
|
93 |
|
94 |
slab: The surface onto which the adsorbate should be added.
|
95 |
|
96 |
adsorbate: The adsorbate. Must be one of the following three types:
|
97 |
A string containing the chemical symbol for a single atom.
|
98 |
An atom object.
|
99 |
An atoms object (for a molecular adsorbate).
|
100 |
|
101 |
height: Height above the surface.
|
102 |
|
103 |
position: The x-y position of the adsorbate, either as a tuple of
|
104 |
two numbers or as a keyword (if the surface is produced by one
|
105 |
of the functions in ase.lattice.surfaces).
|
106 |
|
107 |
offset (default: None): Offsets the adsorbate by a number of unit
|
108 |
cells. Mostly useful when adding more than one adsorbate.
|
109 |
|
110 |
mol_index (default: 0): If the adsorbate is a molecule, index of
|
111 |
the atom to be positioned above the location specified by the
|
112 |
position argument.
|
113 |
|
114 |
Note *position* is given in absolute xy coordinates (or as
|
115 |
a keyword), whereas offset is specified in unit cells. This
|
116 |
can be used to give the positions in units of the unit cell by
|
117 |
using *offset* instead.
|
118 |
|
119 |
"""
|
120 |
info = slab.adsorbate_info |
121 |
if 'cell' not in info: |
122 |
info['cell'] = slab.get_cell()[:2,:2] |
123 |
|
124 |
|
125 |
pos = np.array([0.0, 0.0]) # (x, y) part |
126 |
spos = np.array([0.0, 0.0]) # part relative to unit cell |
127 |
if offset is not None: |
128 |
spos += np.asarray(offset, float)
|
129 |
|
130 |
if isinstance(position, str): |
131 |
# A site-name:
|
132 |
if 'sites' not in info: |
133 |
raise TypeError('If the atoms are not made by an ' + |
134 |
'ase.lattice.surface function, ' +
|
135 |
'position cannot be a name.')
|
136 |
if position not in info['sites']: |
137 |
raise TypeError('Adsorption site %s not supported.' % position) |
138 |
spos += info['sites'][position]
|
139 |
else:
|
140 |
pos += position |
141 |
|
142 |
pos += np.dot(spos, info['cell'])
|
143 |
|
144 |
# Convert the adsorbate to an Atoms object
|
145 |
if isinstance(adsorbate, Atoms): |
146 |
ads = adsorbate |
147 |
elif isinstance(adsorbate, Atom): |
148 |
ads = Atoms([adsorbate]) |
149 |
else:
|
150 |
# Hope it is a useful string or something like that
|
151 |
ads = Atoms(adsorbate) |
152 |
|
153 |
# Get the z-coordinate:
|
154 |
try:
|
155 |
a = info['top layer atom index']
|
156 |
except KeyError: |
157 |
a = slab.positions[:, 2].argmax()
|
158 |
info['top layer atom index']= a
|
159 |
z = slab.positions[a, 2] + height
|
160 |
|
161 |
# Move adsorbate into position
|
162 |
ads.translate([pos[0], pos[1], z] - ads.positions[mol_index]) |
163 |
|
164 |
# Attach the adsorbate
|
165 |
slab.extend(ads) |
166 |
|
167 |
|
168 |
def surface(symbol, structure, face, size, a, c, vacuum, orthogonal=True): |
169 |
"""Function to build often used surfaces.
|
170 |
|
171 |
Don't call this function directly - use fcc100, fcc110, bcc111, ..."""
|
172 |
|
173 |
Z = atomic_numbers[symbol] |
174 |
|
175 |
if a is None: |
176 |
sym = reference_states[Z]['symmetry'].lower()
|
177 |
if sym != structure:
|
178 |
raise ValueError("Can't guess lattice constant for %s-%s!" % |
179 |
(structure, symbol)) |
180 |
a = reference_states[Z]['a']
|
181 |
|
182 |
if structure == 'hcp' and c is None: |
183 |
if reference_states[Z]['symmetry'].lower() == 'hcp': |
184 |
c = reference_states[Z]['c/a'] * a
|
185 |
else:
|
186 |
c = sqrt(8 / 3.0) * a |
187 |
|
188 |
positions = np.empty((size[2], size[1], size[0], 3)) |
189 |
positions[..., 0] = np.arange(size[0]).reshape((1, 1, -1)) |
190 |
positions[..., 1] = np.arange(size[1]).reshape((1, -1, 1)) |
191 |
positions[..., 2] = np.arange(size[2]).reshape((-1, 1, 1)) |
192 |
|
193 |
numbers = np.ones(size[0] * size[1] * size[2], int) * Z |
194 |
|
195 |
tags = np.empty((size[2], size[1], size[0]), int) |
196 |
tags[:] = np.arange(size[2], 0, -1).reshape((-1, 1, 1)) |
197 |
|
198 |
slab = Atoms(numbers, |
199 |
tags=tags.ravel(), |
200 |
pbc=(True, True, False), |
201 |
cell=size) |
202 |
|
203 |
surface_cell = None
|
204 |
sites = {'ontop': (0, 0)} |
205 |
surf = structure + face |
206 |
if surf == 'fcc100': |
207 |
cell = (sqrt(0.5), sqrt(0.5), 0.5) |
208 |
positions[-2::-2, ..., :2] += 0.5 |
209 |
sites.update({'hollow': (0.5, 0.5), 'bridge': (0.5, 0)}) |
210 |
elif surf == 'fcc110': |
211 |
cell = (1.0, sqrt(0.5), sqrt(0.125)) |
212 |
positions[-2::-2, ..., :2] += 0.5 |
213 |
sites.update({'hollow': (0.5, 0.5), 'longbridge': (0.5, 0), |
214 |
'shortbridge': (0, 0.5)}) |
215 |
elif surf == 'bcc100': |
216 |
cell = (1.0, 1.0, 0.5) |
217 |
positions[-2::-2, ..., :2] += 0.5 |
218 |
sites.update({'hollow': (0.5, 0.5), 'bridge': (0.5, 0)}) |
219 |
else:
|
220 |
if orthogonal and size[1] % 2 == 1: |
221 |
raise ValueError(("Can't make orthorhombic cell with size=%r. " % |
222 |
(tuple(size),)) +
|
223 |
'Second number in size must be even.')
|
224 |
if surf == 'fcc111': |
225 |
cell = (sqrt(0.5), sqrt(0.375), 1 / sqrt(3)) |
226 |
if orthogonal:
|
227 |
positions[-1::-3, 1::2, :, 0] += 0.5 |
228 |
positions[-2::-3, 1::2, :, 0] += 0.5 |
229 |
positions[-3::-3, 1::2, :, 0] -= 0.5 |
230 |
positions[-2::-3, ..., :2] += (0.0, 2.0 / 3) |
231 |
positions[-3::-3, ..., :2] += (0.5, 1.0 / 3) |
232 |
else:
|
233 |
positions[-2::-3, ..., :2] += (-1.0 / 3, 2.0 / 3) |
234 |
positions[-3::-3, ..., :2] += (1.0 / 3, 1.0 / 3) |
235 |
sites.update({'bridge': (0.5, 0), 'fcc': (1.0 / 3, 1.0 / 3), |
236 |
'hcp': (2.0 / 3, 2.0 / 3)}) |
237 |
elif surf == 'hcp0001': |
238 |
cell = (1.0, sqrt(0.75), 0.5 * c / a) |
239 |
if orthogonal:
|
240 |
positions[:, 1::2, :, 0] += 0.5 |
241 |
positions[-2::-2, ..., :2] += (0.0, 2.0 / 3) |
242 |
else:
|
243 |
positions[-2::-2, ..., :2] += (-1.0 / 3, 2.0 / 3) |
244 |
sites.update({'bridge': (0.5, 0), 'fcc': (1.0 / 3, 1.0 / 3), |
245 |
'hcp': (2.0 / 3, 2.0 / 3)}) |
246 |
elif surf == 'bcc110': |
247 |
cell = (1.0, sqrt(0.5), sqrt(0.5)) |
248 |
if orthogonal:
|
249 |
positions[:, 1::2, :, 0] += 0.5 |
250 |
positions[-2::-2, ..., :2] += (0.0, 1.0) |
251 |
else:
|
252 |
positions[-2::-2, ..., :2] += (-0.5, 1.0) |
253 |
sites.update({'shortbridge': (0, 0.5), |
254 |
'longbridge': (0.5, 0), |
255 |
'hollow': (0.375, 0.25)}) |
256 |
elif surf == 'bcc111': |
257 |
cell = (sqrt(2), sqrt(1.5), sqrt(3) / 6) |
258 |
if orthogonal:
|
259 |
positions[-1::-3, 1::2, :, 0] += 0.5 |
260 |
positions[-2::-3, 1::2, :, 0] += 0.5 |
261 |
positions[-3::-3, 1::2, :, 0] -= 0.5 |
262 |
positions[-2::-3, ..., :2] += (0.0, 2.0 / 3) |
263 |
positions[-3::-3, ..., :2] += (0.5, 1.0 / 3) |
264 |
else:
|
265 |
positions[-2::-3, ..., :2] += (-1.0 / 3, 2.0 / 3) |
266 |
positions[-3::-3, ..., :2] += (1.0 / 3, 1.0 / 3) |
267 |
sites.update({'hollow': (1.0 / 3, 1.0 / 3)}) |
268 |
|
269 |
surface_cell = a * np.array([(cell[0], 0), |
270 |
(cell[0] / 2, cell[1])]) |
271 |
if not orthogonal: |
272 |
cell = np.array([(cell[0], 0, 0), |
273 |
(cell[0] / 2, cell[1], 0), |
274 |
(0, 0, cell[2])]) |
275 |
|
276 |
if surface_cell is None: |
277 |
surface_cell = a * np.diag(cell[:2])
|
278 |
|
279 |
if isinstance(cell, tuple): |
280 |
cell = np.diag(cell) |
281 |
|
282 |
slab.set_positions(positions.reshape((-1, 3))) |
283 |
|
284 |
slab.set_cell([a * v * n for v, n in zip(cell, size)], scale_atoms=True) |
285 |
|
286 |
if vacuum is not None: |
287 |
slab.center(vacuum=vacuum, axis=2)
|
288 |
|
289 |
slab.adsorbate_info['cell'] = surface_cell
|
290 |
slab.adsorbate_info['sites'] = sites
|
291 |
|
292 |
return slab
|
293 |
|
294 |
|
295 |
|