root / ase / atoms.py
Historique | Voir | Annoter | Télécharger (44,24 ko)
1 | 1 | tkerber | # Copyright 2008, 2009 CAMd
|
---|---|---|---|
2 | 1 | tkerber | # (see accompanying license files for details).
|
3 | 1 | tkerber | |
4 | 1 | tkerber | """Definition of the Atoms class.
|
5 | 1 | tkerber |
|
6 | 1 | tkerber | This module defines the central object in the ASE package: the Atoms
|
7 | 1 | tkerber | object.
|
8 | 1 | tkerber | """
|
9 | 1 | tkerber | |
10 | 1 | tkerber | from math import cos, sin |
11 | 1 | tkerber | |
12 | 1 | tkerber | import numpy as np |
13 | 1 | tkerber | |
14 | 1 | tkerber | from ase.atom import Atom |
15 | 1 | tkerber | from ase.data import atomic_numbers, chemical_symbols, atomic_masses |
16 | 1 | tkerber | import ase.units as units |
17 | 1 | tkerber | |
18 | 1 | tkerber | class Atoms(object): |
19 | 1 | tkerber | """Atoms object.
|
20 | 1 | tkerber |
|
21 | 1 | tkerber | The Atoms object can represent an isolated molecule, or a
|
22 | 1 | tkerber | periodically repeated structure. It has a unit cell and
|
23 | 1 | tkerber | there may be periodic boundary conditions along any of the three
|
24 | 1 | tkerber | unit cell axes.
|
25 | 1 | tkerber |
|
26 | 1 | tkerber | Information about the atoms (atomic numbers and position) is
|
27 | 1 | tkerber | stored in ndarrays. Optionally, there can be information about
|
28 | 1 | tkerber | tags, momenta, masses, magnetic moments and charges.
|
29 | 1 | tkerber |
|
30 | 1 | tkerber | In order to calculate energies, forces and stresses, a calculator
|
31 | 1 | tkerber | object has to attached to the atoms object.
|
32 | 1 | tkerber |
|
33 | 1 | tkerber | Parameters:
|
34 | 1 | tkerber |
|
35 | 1 | tkerber | symbols: str (formula) or list of str
|
36 | 1 | tkerber | Can be a string formula, a list of symbols or a list of
|
37 | 1 | tkerber | Atom objects. Examples: 'H2O', 'COPt12', ['H', 'H', 'O'],
|
38 | 1 | tkerber | [Atom('Ne', (x, y, z)), ...].
|
39 | 1 | tkerber | positions: list of xyz-positions
|
40 | 1 | tkerber | Atomic positions. Anything that can be converted to an
|
41 | 1 | tkerber | ndarray of shape (n, 3) will do: [(x1,y1,z1), (x2,y2,z2),
|
42 | 1 | tkerber | ...].
|
43 | 1 | tkerber | scaled_positions: list of scaled-positions
|
44 | 1 | tkerber | Like positions, but given in units of the unit cell.
|
45 | 1 | tkerber | Can not be set at the same time as positions.
|
46 | 1 | tkerber | numbers: list of int
|
47 | 1 | tkerber | Atomic numbers (use only one of symbols/numbers).
|
48 | 1 | tkerber | tags: list of int
|
49 | 1 | tkerber | Special purpose tags.
|
50 | 1 | tkerber | momenta: list of xyz-momenta
|
51 | 1 | tkerber | Momenta for all atoms.
|
52 | 1 | tkerber | masses: list of float
|
53 | 1 | tkerber | Atomic masses in atomic units.
|
54 | 1 | tkerber | magmoms: list of float
|
55 | 1 | tkerber | Magnetic moments.
|
56 | 1 | tkerber | charges: list of float
|
57 | 1 | tkerber | Atomic charges.
|
58 | 1 | tkerber | cell: 3x3 matrix
|
59 | 1 | tkerber | Unit cell vectors. Can also be given as just three
|
60 | 1 | tkerber | numbers for orthorhombic cells. Default value: [1, 1, 1].
|
61 | 1 | tkerber | pbc: one or three bool
|
62 | 1 | tkerber | Periodic boundary conditions flags. Examples: True,
|
63 | 1 | tkerber | False, 0, 1, (1, 1, 0), (True, False, False). Default
|
64 | 1 | tkerber | value: False.
|
65 | 1 | tkerber | constraint: constraint object(s)
|
66 | 1 | tkerber | Used for applying one or more constraints during structure
|
67 | 1 | tkerber | optimization.
|
68 | 1 | tkerber | calculator: calculator object
|
69 | 1 | tkerber | Used to attach a calculator for calulating energies and atomic
|
70 | 1 | tkerber | forces.
|
71 | 1 | tkerber |
|
72 | 1 | tkerber | Examples:
|
73 | 1 | tkerber |
|
74 | 1 | tkerber | These three are equivalent:
|
75 | 1 | tkerber |
|
76 | 1 | tkerber | >>> d = 1.104 # N2 bondlength
|
77 | 1 | tkerber | >>> a = Atoms('N2', [(0, 0, 0), (0, 0, d)])
|
78 | 1 | tkerber | >>> a = Atoms(numbers=[7, 7], positions=[(0, 0, 0), (0, 0, d)])
|
79 | 1 | tkerber | >>> a = Atoms([Atom('N', (0, 0, 0)), Atom('N', (0, 0, d)])
|
80 | 1 | tkerber |
|
81 | 1 | tkerber | FCC gold:
|
82 | 1 | tkerber |
|
83 | 1 | tkerber | >>> a = 4.05 # Gold lattice constant
|
84 | 1 | tkerber | >>> b = a / 2
|
85 | 1 | tkerber | >>> fcc = Atoms('Au',
|
86 | 1 | tkerber | ... cell=[(0, b, b), (b, 0, b), (b, b, 0)],
|
87 | 1 | tkerber | ... pbc=True)
|
88 | 1 | tkerber |
|
89 | 1 | tkerber | Hydrogen wire:
|
90 | 1 | tkerber |
|
91 | 1 | tkerber | >>> d = 0.9 # H-H distance
|
92 | 1 | tkerber | >>> L = 7.0
|
93 | 1 | tkerber | >>> h = Atoms('H', positions=[(0, L / 2, L / 2)],
|
94 | 1 | tkerber | ... cell=(d, L, L),
|
95 | 1 | tkerber | ... pbc=(1, 0, 0))
|
96 | 1 | tkerber | """
|
97 | 1 | tkerber | |
98 | 1 | tkerber | def __init__(self, symbols=None, |
99 | 1 | tkerber | positions=None, numbers=None, |
100 | 1 | tkerber | tags=None, momenta=None, masses=None, |
101 | 1 | tkerber | magmoms=None, charges=None, |
102 | 1 | tkerber | scaled_positions=None,
|
103 | 1 | tkerber | cell=None, pbc=None, |
104 | 1 | tkerber | constraint=None,
|
105 | 1 | tkerber | calculator=None):
|
106 | 1 | tkerber | |
107 | 1 | tkerber | atoms = None
|
108 | 1 | tkerber | |
109 | 1 | tkerber | if hasattr(symbols, 'GetUnitCell'): |
110 | 1 | tkerber | from ase.old import OldASEListOfAtomsWrapper |
111 | 1 | tkerber | atoms = OldASEListOfAtomsWrapper(symbols) |
112 | 1 | tkerber | symbols = None
|
113 | 1 | tkerber | elif hasattr(symbols, "get_positions"): |
114 | 1 | tkerber | atoms = symbols |
115 | 1 | tkerber | symbols = None
|
116 | 1 | tkerber | elif (isinstance(symbols, (list, tuple)) and |
117 | 1 | tkerber | len(symbols) > 0 and isinstance(symbols[0], Atom)): |
118 | 1 | tkerber | # Get data from a list or tuple of Atom objects:
|
119 | 1 | tkerber | data = zip(*[atom.get_data() for atom in symbols]) |
120 | 1 | tkerber | atoms = self.__class__(None, *data) |
121 | 1 | tkerber | symbols = None
|
122 | 1 | tkerber | |
123 | 1 | tkerber | if atoms is not None: |
124 | 1 | tkerber | # Get data from another Atoms object:
|
125 | 1 | tkerber | if scaled_positions is not None: |
126 | 1 | tkerber | raise NotImplementedError |
127 | 1 | tkerber | if symbols is None and numbers is None: |
128 | 1 | tkerber | numbers = atoms.get_atomic_numbers() |
129 | 1 | tkerber | if positions is None: |
130 | 1 | tkerber | positions = atoms.get_positions() |
131 | 1 | tkerber | if tags is None and atoms.has('tags'): |
132 | 1 | tkerber | tags = atoms.get_tags() |
133 | 1 | tkerber | if momenta is None and atoms.has('momenta'): |
134 | 1 | tkerber | momenta = atoms.get_momenta() |
135 | 1 | tkerber | if magmoms is None and atoms.has('magmoms'): |
136 | 1 | tkerber | magmoms = atoms.get_initial_magnetic_moments() |
137 | 1 | tkerber | if masses is None and atoms.has('masses'): |
138 | 1 | tkerber | masses = atoms.get_masses() |
139 | 1 | tkerber | if charges is None and atoms.has('charges'): |
140 | 1 | tkerber | charges = atoms.get_charges() |
141 | 1 | tkerber | if cell is None: |
142 | 1 | tkerber | cell = atoms.get_cell() |
143 | 1 | tkerber | if pbc is None: |
144 | 1 | tkerber | pbc = atoms.get_pbc() |
145 | 1 | tkerber | if constraint is None: |
146 | 1 | tkerber | constraint = [c.copy() for c in atoms.constraints] |
147 | 1 | tkerber | if calculator is None: |
148 | 1 | tkerber | calculator = atoms.get_calculator() |
149 | 1 | tkerber | |
150 | 1 | tkerber | self.arrays = {}
|
151 | 1 | tkerber | |
152 | 1 | tkerber | if symbols is None: |
153 | 1 | tkerber | if numbers is None: |
154 | 1 | tkerber | if positions is not None: |
155 | 1 | tkerber | natoms = len(positions)
|
156 | 1 | tkerber | elif scaled_positions is not None: |
157 | 1 | tkerber | natoms = len(scaled_positions)
|
158 | 1 | tkerber | else:
|
159 | 1 | tkerber | natoms = 0
|
160 | 1 | tkerber | numbers = np.zeros(natoms, int)
|
161 | 1 | tkerber | self.new_array('numbers', numbers, int) |
162 | 1 | tkerber | else:
|
163 | 1 | tkerber | if numbers is not None: |
164 | 1 | tkerber | raise ValueError( |
165 | 1 | tkerber | 'Use only one of "symbols" and "numbers".')
|
166 | 1 | tkerber | else:
|
167 | 1 | tkerber | self.new_array('numbers', symbols2numbers(symbols), int) |
168 | 1 | tkerber | |
169 | 1 | tkerber | if cell is None: |
170 | 1 | tkerber | cell = np.eye(3)
|
171 | 1 | tkerber | self.set_cell(cell)
|
172 | 1 | tkerber | |
173 | 1 | tkerber | if positions is None: |
174 | 1 | tkerber | if scaled_positions is None: |
175 | 1 | tkerber | positions = np.zeros((len(self.arrays['numbers']), 3)) |
176 | 1 | tkerber | else:
|
177 | 1 | tkerber | positions = np.dot(scaled_positions, self._cell)
|
178 | 1 | tkerber | else:
|
179 | 1 | tkerber | if scaled_positions is not None: |
180 | 1 | tkerber | raise RuntimeError, 'Both scaled and cartesian positions set!' |
181 | 1 | tkerber | self.new_array('positions', positions, float, (3,)) |
182 | 1 | tkerber | |
183 | 1 | tkerber | self.set_constraint(constraint)
|
184 | 1 | tkerber | self.set_tags(default(tags, 0)) |
185 | 1 | tkerber | self.set_momenta(default(momenta, (0.0, 0.0, 0.0))) |
186 | 1 | tkerber | self.set_masses(default(masses, None)) |
187 | 1 | tkerber | self.set_initial_magnetic_moments(default(magmoms, 0.0)) |
188 | 1 | tkerber | self.set_charges(default(charges, 0.0)) |
189 | 1 | tkerber | if pbc is None: |
190 | 1 | tkerber | pbc = False
|
191 | 1 | tkerber | self.set_pbc(pbc)
|
192 | 1 | tkerber | |
193 | 1 | tkerber | self.adsorbate_info = {}
|
194 | 1 | tkerber | |
195 | 1 | tkerber | self.set_calculator(calculator)
|
196 | 1 | tkerber | |
197 | 1 | tkerber | def set_calculator(self, calc=None): |
198 | 1 | tkerber | """Attach calculator object."""
|
199 | 1 | tkerber | if hasattr(calc, '_SetListOfAtoms'): |
200 | 1 | tkerber | from ase.old import OldASECalculatorWrapper |
201 | 1 | tkerber | calc = OldASECalculatorWrapper(calc, self)
|
202 | 1 | tkerber | if hasattr(calc, 'set_atoms'): |
203 | 1 | tkerber | calc.set_atoms(self)
|
204 | 1 | tkerber | self.calc = calc
|
205 | 1 | tkerber | |
206 | 1 | tkerber | def get_calculator(self): |
207 | 1 | tkerber | """Get currently attached calculator object."""
|
208 | 1 | tkerber | return self.calc |
209 | 1 | tkerber | |
210 | 1 | tkerber | def set_constraint(self, constraint=None): |
211 | 1 | tkerber | """Apply one or more constrains.
|
212 | 1 | tkerber |
|
213 | 1 | tkerber | The *constraint* argument must be one constraint object or a
|
214 | 1 | tkerber | list of constraint objects."""
|
215 | 1 | tkerber | if constraint is None: |
216 | 1 | tkerber | self._constraints = []
|
217 | 1 | tkerber | else:
|
218 | 1 | tkerber | if isinstance(constraint, (list, tuple)): |
219 | 1 | tkerber | self._constraints = constraint
|
220 | 1 | tkerber | else:
|
221 | 1 | tkerber | self._constraints = [constraint]
|
222 | 1 | tkerber | |
223 | 1 | tkerber | def _get_constraints(self): |
224 | 1 | tkerber | return self._constraints |
225 | 1 | tkerber | |
226 | 1 | tkerber | def _del_constraints(self): |
227 | 1 | tkerber | self._constraints = []
|
228 | 1 | tkerber | |
229 | 1 | tkerber | constraints = property(_get_constraints, set_constraint, _del_constraints,
|
230 | 1 | tkerber | "Constraints of the atoms.")
|
231 | 1 | tkerber | |
232 | 1 | tkerber | def set_cell(self, cell, scale_atoms=False, fix=None): |
233 | 1 | tkerber | """Set unit cell vectors.
|
234 | 1 | tkerber |
|
235 | 1 | tkerber | Parameters:
|
236 | 1 | tkerber |
|
237 | 1 | tkerber | cell :
|
238 | 1 | tkerber | Unit cell. A 3x3 matrix (the three unit cell vectors) or
|
239 | 1 | tkerber | just three numbers for an orthorhombic cell.
|
240 | 1 | tkerber | scale_atoms : bool
|
241 | 1 | tkerber | Fix atomic positions or move atoms with the unit cell?
|
242 | 1 | tkerber | Default behavior is to *not* move the atoms (scale_atoms=False).
|
243 | 1 | tkerber |
|
244 | 1 | tkerber | Examples:
|
245 | 1 | tkerber |
|
246 | 1 | tkerber | Two equivalent ways to define an orthorhombic cell:
|
247 | 1 | tkerber |
|
248 | 1 | tkerber | >>> a.set_cell([a, b, c])
|
249 | 1 | tkerber | >>> a.set_cell([(a, 0, 0), (0, b, 0), (0, 0, c)])
|
250 | 1 | tkerber |
|
251 | 1 | tkerber | FCC unit cell:
|
252 | 1 | tkerber |
|
253 | 1 | tkerber | >>> a.set_cell([(0, b, b), (b, 0, b), (b, b, 0)])
|
254 | 1 | tkerber | """
|
255 | 1 | tkerber | |
256 | 1 | tkerber | if fix is not None: |
257 | 1 | tkerber | raise TypeError('Please use scale_atoms=%s' % (not fix)) |
258 | 1 | tkerber | |
259 | 1 | tkerber | cell = np.array(cell, float)
|
260 | 1 | tkerber | if cell.shape == (3,): |
261 | 1 | tkerber | cell = np.diag(cell) |
262 | 1 | tkerber | elif cell.shape != (3, 3): |
263 | 1 | tkerber | raise ValueError('Cell must be length 3 sequence or ' |
264 | 1 | tkerber | '3x3 matrix!')
|
265 | 1 | tkerber | if scale_atoms:
|
266 | 1 | tkerber | M = np.linalg.solve(self._cell, cell)
|
267 | 1 | tkerber | self.arrays['positions'][:] = np.dot(self.arrays['positions'], M) |
268 | 1 | tkerber | self._cell = cell
|
269 | 1 | tkerber | |
270 | 1 | tkerber | def get_cell(self): |
271 | 1 | tkerber | """Get the three unit cell vectors as a 3x3 ndarray."""
|
272 | 1 | tkerber | return self._cell.copy() |
273 | 1 | tkerber | |
274 | 1 | tkerber | def set_pbc(self, pbc): |
275 | 1 | tkerber | """Set periodic boundary condition flags."""
|
276 | 1 | tkerber | if isinstance(pbc, int): |
277 | 1 | tkerber | pbc = (pbc,) * 3
|
278 | 1 | tkerber | self._pbc = np.array(pbc, bool) |
279 | 1 | tkerber | |
280 | 1 | tkerber | def get_pbc(self): |
281 | 1 | tkerber | """Get periodic boundary condition flags."""
|
282 | 1 | tkerber | return self._pbc.copy() |
283 | 1 | tkerber | |
284 | 1 | tkerber | def new_array(self, name, a, dtype=None, shape=None): |
285 | 1 | tkerber | """Add new array.
|
286 | 1 | tkerber |
|
287 | 1 | tkerber | If *shape* is not *None*, the shape of *a* will be checked."""
|
288 | 1 | tkerber | if dtype is not None: |
289 | 1 | tkerber | a = np.array(a, dtype) |
290 | 1 | tkerber | else:
|
291 | 1 | tkerber | a = a.copy() |
292 | 1 | tkerber | |
293 | 1 | tkerber | if name in self.arrays: |
294 | 1 | tkerber | raise RuntimeError |
295 | 1 | tkerber | |
296 | 1 | tkerber | for b in self.arrays.values(): |
297 | 1 | tkerber | if len(a) != len(b): |
298 | 1 | tkerber | raise ValueError('Array has wrong length: %d != %d.' % |
299 | 1 | tkerber | (len(a), len(b))) |
300 | 1 | tkerber | break
|
301 | 1 | tkerber | |
302 | 1 | tkerber | if shape is not None and a.shape[1:] != shape: |
303 | 1 | tkerber | raise ValueError('Array has wrong shape %s != %s.' % |
304 | 1 | tkerber | (a.shape, (a.shape[0:1] + shape))) |
305 | 1 | tkerber | |
306 | 1 | tkerber | self.arrays[name] = a
|
307 | 1 | tkerber | |
308 | 1 | tkerber | def get_array(self, name, copy=True): |
309 | 1 | tkerber | """Get an array.
|
310 | 1 | tkerber |
|
311 | 1 | tkerber | Returns a copy unless the optional argument copy is false.
|
312 | 1 | tkerber | """
|
313 | 2 | tkerber | |
314 | 1 | tkerber | if copy:
|
315 | 1 | tkerber | return self.arrays[name].copy() |
316 | 1 | tkerber | else:
|
317 | 1 | tkerber | return self.arrays[name] |
318 | 1 | tkerber | |
319 | 1 | tkerber | def set_array(self, name, a, dtype=None, shape=None): |
320 | 1 | tkerber | """Update array.
|
321 | 1 | tkerber |
|
322 | 1 | tkerber | If *shape* is not *None*, the shape of *a* will be checked.
|
323 | 1 | tkerber | If *a* is *None*, then the array is deleted."""
|
324 | 1 | tkerber | |
325 | 1 | tkerber | b = self.arrays.get(name)
|
326 | 1 | tkerber | if b is None: |
327 | 1 | tkerber | if a is not None: |
328 | 1 | tkerber | self.new_array(name, a, dtype, shape)
|
329 | 1 | tkerber | else:
|
330 | 1 | tkerber | if a is None: |
331 | 1 | tkerber | del self.arrays[name] |
332 | 1 | tkerber | else:
|
333 | 1 | tkerber | a = np.asarray(a) |
334 | 1 | tkerber | if a.shape != b.shape:
|
335 | 1 | tkerber | raise ValueError('Array has wrong shape %s != %s.' % |
336 | 1 | tkerber | (a.shape, b.shape)) |
337 | 1 | tkerber | b[:] = a |
338 | 1 | tkerber | |
339 | 1 | tkerber | def has(self, name): |
340 | 1 | tkerber | """Check for existance of array.
|
341 | 1 | tkerber |
|
342 | 1 | tkerber | name must be one of: 'tags', 'momenta', 'masses', 'magmoms',
|
343 | 1 | tkerber | 'charges'."""
|
344 | 1 | tkerber | return name in self.arrays |
345 | 1 | tkerber | |
346 | 1 | tkerber | def set_atomic_numbers(self, numbers): |
347 | 1 | tkerber | """Set atomic numbers."""
|
348 | 1 | tkerber | self.set_array('numbers', numbers, int, ()) |
349 | 1 | tkerber | |
350 | 1 | tkerber | def get_atomic_numbers(self): |
351 | 1 | tkerber | """Get integer array of atomic numbers."""
|
352 | 1 | tkerber | return self.arrays['numbers'] |
353 | 1 | tkerber | |
354 | 1 | tkerber | def set_chemical_symbols(self, symbols): |
355 | 1 | tkerber | """Set chemical symbols."""
|
356 | 1 | tkerber | self.set_array('numbers', symbols2numbers(symbols), int, ()) |
357 | 1 | tkerber | |
358 | 1 | tkerber | def get_chemical_symbols(self, reduce=False): |
359 | 1 | tkerber | """Get list of chemical symbol strings.
|
360 | 1 | tkerber |
|
361 | 1 | tkerber | If reduce is True, a single string is returned, where repeated
|
362 | 1 | tkerber | elements have been contracted to a single symbol and a number.
|
363 | 1 | tkerber | E.g. instead of ['C', 'O', 'O', 'H'], the string 'CO2H' is returned.
|
364 | 1 | tkerber | """
|
365 | 1 | tkerber | if not reduce: |
366 | 1 | tkerber | return [chemical_symbols[Z] for Z in self.arrays['numbers']] |
367 | 1 | tkerber | else:
|
368 | 1 | tkerber | num = self.get_atomic_numbers()
|
369 | 1 | tkerber | N = len(num)
|
370 | 1 | tkerber | dis = np.concatenate(([0], np.arange(1, N)[num[1:] != num[:-1]])) |
371 | 1 | tkerber | repeat = np.append(dis[1:], N) - dis
|
372 | 1 | tkerber | symbols = ''.join([chemical_symbols[num[d]] + str(r) * (r != 1) |
373 | 1 | tkerber | for r, d in zip(repeat, dis)]) |
374 | 1 | tkerber | return symbols
|
375 | 1 | tkerber | |
376 | 1 | tkerber | def set_tags(self, tags): |
377 | 1 | tkerber | """Set tags for all atoms."""
|
378 | 1 | tkerber | self.set_array('tags', tags, int, ()) |
379 | 1 | tkerber | |
380 | 1 | tkerber | def get_tags(self): |
381 | 1 | tkerber | """Get integer array of tags."""
|
382 | 1 | tkerber | if 'tags' in self.arrays: |
383 | 1 | tkerber | return self.arrays['tags'].copy() |
384 | 1 | tkerber | else:
|
385 | 1 | tkerber | return np.zeros(len(self), int) |
386 | 1 | tkerber | |
387 | 1 | tkerber | def set_momenta(self, momenta): |
388 | 1 | tkerber | """Set momenta."""
|
389 | 1 | tkerber | if len(self.constraints) > 0 and momenta is not None: |
390 | 1 | tkerber | momenta = np.array(momenta) # modify a copy
|
391 | 1 | tkerber | for constraint in self.constraints: |
392 | 1 | tkerber | constraint.adjust_forces(self.arrays['positions'], momenta) |
393 | 1 | tkerber | self.set_array('momenta', momenta, float, (3,)) |
394 | 1 | tkerber | |
395 | 1 | tkerber | def set_velocities(self, velocities): |
396 | 1 | tkerber | """Set the momenta by specifying the velocities."""
|
397 | 1 | tkerber | self.set_momenta(self.get_masses()[:,np.newaxis] * velocities) |
398 | 1 | tkerber | |
399 | 1 | tkerber | def get_momenta(self): |
400 | 1 | tkerber | """Get array of momenta."""
|
401 | 1 | tkerber | if 'momenta' in self.arrays: |
402 | 1 | tkerber | return self.arrays['momenta'].copy() |
403 | 1 | tkerber | else:
|
404 | 1 | tkerber | return np.zeros((len(self), 3)) |
405 | 1 | tkerber | |
406 | 1 | tkerber | def set_masses(self, masses='defaults'): |
407 | 1 | tkerber | """Set atomic masses.
|
408 | 1 | tkerber |
|
409 | 1 | tkerber | The array masses should contain the a list masses. In case
|
410 | 1 | tkerber | the masses argument is not given or for those elements of the
|
411 | 1 | tkerber | masses list that are None, standard values are set."""
|
412 | 1 | tkerber | |
413 | 1 | tkerber | if masses == 'defaults': |
414 | 1 | tkerber | masses = atomic_masses[self.arrays['numbers']] |
415 | 1 | tkerber | elif isinstance(masses, (list, tuple)): |
416 | 1 | tkerber | newmasses = [] |
417 | 1 | tkerber | for m, Z in zip(masses, self.arrays['numbers']): |
418 | 1 | tkerber | if m is None: |
419 | 1 | tkerber | newmasses.append(atomic_masses[Z]) |
420 | 1 | tkerber | else:
|
421 | 1 | tkerber | newmasses.append(m) |
422 | 1 | tkerber | masses = newmasses |
423 | 1 | tkerber | self.set_array('masses', masses, float, ()) |
424 | 1 | tkerber | |
425 | 1 | tkerber | def get_masses(self): |
426 | 1 | tkerber | """Get array of masses."""
|
427 | 1 | tkerber | if 'masses' in self.arrays: |
428 | 1 | tkerber | return self.arrays['masses'].copy() |
429 | 1 | tkerber | else:
|
430 | 1 | tkerber | return atomic_masses[self.arrays['numbers']] |
431 | 1 | tkerber | |
432 | 1 | tkerber | def set_initial_magnetic_moments(self, magmoms): |
433 | 1 | tkerber | """Set the initial magnetic moments."""
|
434 | 1 | tkerber | self.set_array('magmoms', magmoms, float, ()) |
435 | 1 | tkerber | |
436 | 1 | tkerber | def set_magnetic_moments(self, magmoms): |
437 | 1 | tkerber | print 'Please use set_initial_magnetic_moments() instead!' |
438 | 1 | tkerber | self.set_initial_magnetic_moments(magmoms)
|
439 | 1 | tkerber | |
440 | 1 | tkerber | def get_initial_magnetic_moments(self): |
441 | 1 | tkerber | """Get array of initial magnetic moments."""
|
442 | 1 | tkerber | if 'magmoms' in self.arrays: |
443 | 1 | tkerber | return self.arrays['magmoms'].copy() |
444 | 1 | tkerber | else:
|
445 | 1 | tkerber | return np.zeros(len(self)) |
446 | 1 | tkerber | |
447 | 1 | tkerber | def get_magnetic_moments(self): |
448 | 1 | tkerber | """Get calculated local magnetic moments."""
|
449 | 1 | tkerber | if self.calc is None: |
450 | 1 | tkerber | raise RuntimeError('Atoms object has no calculator.') |
451 | 1 | tkerber | if self.calc.get_spin_polarized(): |
452 | 1 | tkerber | return self.calc.get_magnetic_moments(self) |
453 | 1 | tkerber | else:
|
454 | 1 | tkerber | return np.zeros(len(self)) |
455 | 1 | tkerber | |
456 | 1 | tkerber | def get_magnetic_moment(self): |
457 | 1 | tkerber | """Get calculated total magnetic moment."""
|
458 | 1 | tkerber | if self.calc is None: |
459 | 1 | tkerber | raise RuntimeError('Atoms object has no calculator.') |
460 | 1 | tkerber | if self.calc.get_spin_polarized(): |
461 | 1 | tkerber | return self.calc.get_magnetic_moment(self) |
462 | 1 | tkerber | else:
|
463 | 1 | tkerber | return 0.0 |
464 | 1 | tkerber | |
465 | 1 | tkerber | def set_charges(self, charges): |
466 | 1 | tkerber | """Set charges."""
|
467 | 1 | tkerber | self.set_array('charges', charges, float, ()) |
468 | 1 | tkerber | |
469 | 1 | tkerber | def get_charges(self): |
470 | 1 | tkerber | """Get array of charges."""
|
471 | 1 | tkerber | if 'charges' in self.arrays: |
472 | 1 | tkerber | return self.arrays['charges'].copy() |
473 | 1 | tkerber | else:
|
474 | 1 | tkerber | return np.zeros(len(self)) |
475 | 1 | tkerber | |
476 | 1 | tkerber | def set_positions(self, newpositions): |
477 | 1 | tkerber | """Set positions."""
|
478 | 1 | tkerber | positions = self.arrays['positions'] |
479 | 1 | tkerber | if self.constraints: |
480 | 1 | tkerber | newpositions = np.asarray(newpositions, float)
|
481 | 1 | tkerber | for constraint in self.constraints: |
482 | 1 | tkerber | constraint.adjust_positions(positions, newpositions) |
483 | 1 | tkerber | |
484 | 1 | tkerber | self.set_array('positions', newpositions, shape=(3,)) |
485 | 1 | tkerber | |
486 | 1 | tkerber | def get_positions(self): |
487 | 1 | tkerber | """Get array of positions."""
|
488 | 1 | tkerber | return self.arrays['positions'].copy() |
489 | 1 | tkerber | |
490 | 1 | tkerber | def get_potential_energy(self): |
491 | 1 | tkerber | """Calculate potential energy."""
|
492 | 1 | tkerber | if self.calc is None: |
493 | 1 | tkerber | raise RuntimeError('Atoms object has no calculator.') |
494 | 1 | tkerber | return self.calc.get_potential_energy(self) |
495 | 1 | tkerber | |
496 | 1 | tkerber | def get_potential_energies(self): |
497 | 1 | tkerber | """Calculate the potential energies of all the atoms.
|
498 | 1 | tkerber |
|
499 | 1 | tkerber | Only available with calculators supporting per-atom energies
|
500 | 1 | tkerber | (e.g. classical potentials).
|
501 | 1 | tkerber | """
|
502 | 1 | tkerber | if self.calc is None: |
503 | 1 | tkerber | raise RuntimeError('Atoms object has no calculator.') |
504 | 1 | tkerber | return self.calc.get_potential_energies(self) |
505 | 1 | tkerber | |
506 | 1 | tkerber | def get_kinetic_energy(self): |
507 | 1 | tkerber | """Get the kinetic energy."""
|
508 | 1 | tkerber | momenta = self.arrays.get('momenta') |
509 | 1 | tkerber | if momenta is None: |
510 | 1 | tkerber | return 0.0 |
511 | 1 | tkerber | return 0.5 * np.vdot(momenta, self.get_velocities()) |
512 | 1 | tkerber | |
513 | 1 | tkerber | def get_velocities(self): |
514 | 1 | tkerber | """Get array of velocities."""
|
515 | 1 | tkerber | momenta = self.arrays.get('momenta') |
516 | 1 | tkerber | if momenta is None: |
517 | 1 | tkerber | return None |
518 | 1 | tkerber | m = self.arrays.get('masses') |
519 | 1 | tkerber | if m is None: |
520 | 1 | tkerber | m = atomic_masses[self.arrays['numbers']] |
521 | 1 | tkerber | return momenta / m.reshape(-1, 1) |
522 | 1 | tkerber | |
523 | 1 | tkerber | def get_total_energy(self): |
524 | 1 | tkerber | """Get the total energy - potential plus kinetic energy."""
|
525 | 1 | tkerber | return self.get_potential_energy() + self.get_kinetic_energy() |
526 | 1 | tkerber | |
527 | 1 | tkerber | def get_forces(self, apply_constraint=True): |
528 | 1 | tkerber | """Calculate atomic forces.
|
529 | 1 | tkerber |
|
530 | 1 | tkerber | Ask the attached calculator to calculate the forces and apply
|
531 | 1 | tkerber | constraints. Use *apply_constraint=False* to get the raw
|
532 | 1 | tkerber | forces."""
|
533 | 1 | tkerber | |
534 | 1 | tkerber | if self.calc is None: |
535 | 1 | tkerber | raise RuntimeError('Atoms object has no calculator.') |
536 | 1 | tkerber | forces = self.calc.get_forces(self) |
537 | 1 | tkerber | if apply_constraint:
|
538 | 1 | tkerber | for constraint in self.constraints: |
539 | 1 | tkerber | constraint.adjust_forces(self.arrays['positions'], forces) |
540 | 1 | tkerber | return forces
|
541 | 1 | tkerber | |
542 | 1 | tkerber | def get_stress(self): |
543 | 1 | tkerber | """Calculate stress tensor.
|
544 | 1 | tkerber |
|
545 | 1 | tkerber | Returns an array of the six independent components of the
|
546 | 1 | tkerber | symmetric stress tensor, in the traditional order
|
547 | 1 | tkerber | (s_xx, s_yy, s_zz, s_yz, s_xz, s_xy).
|
548 | 1 | tkerber | """
|
549 | 1 | tkerber | if self.calc is None: |
550 | 1 | tkerber | raise RuntimeError('Atoms object has no calculator.') |
551 | 1 | tkerber | stress = self.calc.get_stress(self) |
552 | 1 | tkerber | shape = getattr(stress, "shape", None) |
553 | 1 | tkerber | if shape == (3,3): |
554 | 1 | tkerber | return np.array([stress[0,0], stress[1,1], stress[2,2], |
555 | 1 | tkerber | stress[1,2], stress[0,2], stress[0,1]]) |
556 | 1 | tkerber | else:
|
557 | 1 | tkerber | # Hopefully a 6-vector, but don't check in case some weird
|
558 | 1 | tkerber | # calculator does something else.
|
559 | 1 | tkerber | return stress
|
560 | 1 | tkerber | |
561 | 1 | tkerber | def get_stresses(self): |
562 | 1 | tkerber | """Calculate the stress-tensor of all the atoms.
|
563 | 1 | tkerber |
|
564 | 1 | tkerber | Only available with calculators supporting per-atom energies and
|
565 | 1 | tkerber | stresses (e.g. classical potentials). Even for such calculators
|
566 | 1 | tkerber | there is a certain arbitrariness in defining per-atom stresses.
|
567 | 1 | tkerber | """
|
568 | 1 | tkerber | if self.calc is None: |
569 | 1 | tkerber | raise RuntimeError('Atoms object has no calculator.') |
570 | 1 | tkerber | return self.calc.get_stresses(self) |
571 | 1 | tkerber | |
572 | 1 | tkerber | def get_dipole_moment(self): |
573 | 1 | tkerber | """Calculate the electric dipole moment for the atoms object.
|
574 | 1 | tkerber |
|
575 | 1 | tkerber | Only available for calculators which has a get_dipole_moment() method."""
|
576 | 1 | tkerber | if self.calc is None: |
577 | 1 | tkerber | raise RuntimeError('Atoms object has no calculator.') |
578 | 1 | tkerber | try:
|
579 | 1 | tkerber | dipole = self.calc.get_dipole_moment(self) |
580 | 1 | tkerber | except AttributeError: |
581 | 1 | tkerber | raise AttributeError('Calculator object has no get_dipole_moment method.') |
582 | 1 | tkerber | return dipole
|
583 | 1 | tkerber | |
584 | 1 | tkerber | def copy(self): |
585 | 1 | tkerber | """Return a copy."""
|
586 | 1 | tkerber | import copy |
587 | 3 | tkerber | atoms = Atoms(cell=self._cell, pbc=self._pbc) |
588 | 1 | tkerber | |
589 | 1 | tkerber | atoms.arrays = {} |
590 | 1 | tkerber | for name, a in self.arrays.items(): |
591 | 1 | tkerber | atoms.arrays[name] = a.copy() |
592 | 1 | tkerber | atoms.constraints = copy.deepcopy(self.constraints)
|
593 | 1 | tkerber | atoms.adsorbate_info = copy.deepcopy(self.adsorbate_info)
|
594 | 1 | tkerber | return atoms
|
595 | 1 | tkerber | |
596 | 1 | tkerber | def __len__(self): |
597 | 1 | tkerber | return len(self.arrays['positions']) |
598 | 1 | tkerber | |
599 | 1 | tkerber | def get_number_of_atoms(self): |
600 | 1 | tkerber | """Returns the number of atoms.
|
601 | 1 | tkerber |
|
602 | 1 | tkerber | Equivalent to len(atoms) in the standard ASE Atoms class.
|
603 | 1 | tkerber | """
|
604 | 1 | tkerber | return len(self) |
605 | 1 | tkerber | |
606 | 1 | tkerber | def __repr__(self): |
607 | 1 | tkerber | num = self.get_atomic_numbers()
|
608 | 1 | tkerber | N = len(num)
|
609 | 1 | tkerber | if N == 0: |
610 | 1 | tkerber | symbols = ''
|
611 | 1 | tkerber | elif N <= 60: |
612 | 1 | tkerber | symbols = self.get_chemical_symbols(reduce=True) |
613 | 1 | tkerber | else:
|
614 | 1 | tkerber | symbols = ''.join([chemical_symbols[Z] for Z in num[:15]]) + '...' |
615 | 1 | tkerber | s = "%s(symbols='%s', " %(self.__class__.__name__, symbols) |
616 | 1 | tkerber | for name in self.arrays: |
617 | 1 | tkerber | if name == 'numbers': |
618 | 1 | tkerber | continue
|
619 | 1 | tkerber | s += '%s=..., ' % name
|
620 | 1 | tkerber | if (self._cell - np.diag(self._cell.diagonal())).any(): |
621 | 1 | tkerber | s += 'cell=%s, ' % self._cell.tolist() |
622 | 1 | tkerber | else:
|
623 | 1 | tkerber | s += 'cell=%s, ' % self._cell.diagonal().tolist() |
624 | 1 | tkerber | s += 'pbc=%s, ' % self._pbc.tolist() |
625 | 1 | tkerber | if len(self.constraints) == 1: |
626 | 1 | tkerber | s += 'constraint=%s, ' % repr(self.constraints[0]) |
627 | 1 | tkerber | if len(self.constraints) > 1: |
628 | 1 | tkerber | s += 'constraint=%s, ' % repr(self.constraints) |
629 | 1 | tkerber | if self.calc is not None: |
630 | 1 | tkerber | s += 'calculator=%s(...), ' % self.calc.__class__.__name__ |
631 | 1 | tkerber | return s[:-2] + ')' |
632 | 1 | tkerber | |
633 | 1 | tkerber | def __add__(self, other): |
634 | 1 | tkerber | atoms = self.copy()
|
635 | 1 | tkerber | atoms += other |
636 | 1 | tkerber | return atoms
|
637 | 1 | tkerber | |
638 | 1 | tkerber | def extend(self, other): |
639 | 1 | tkerber | """Extend atoms object by appending atoms from *other*."""
|
640 | 1 | tkerber | if isinstance(other, Atom): |
641 | 1 | tkerber | other = self.__class__([other])
|
642 | 1 | tkerber | |
643 | 1 | tkerber | n1 = len(self) |
644 | 1 | tkerber | n2 = len(other)
|
645 | 1 | tkerber | |
646 | 1 | tkerber | for name, a1 in self.arrays.items(): |
647 | 1 | tkerber | a = np.zeros((n1 + n2,) + a1.shape[1:], a1.dtype)
|
648 | 1 | tkerber | a[:n1] = a1 |
649 | 1 | tkerber | a2 = other.arrays.get(name) |
650 | 1 | tkerber | if a2 is not None: |
651 | 1 | tkerber | a[n1:] = a2 |
652 | 1 | tkerber | self.arrays[name] = a
|
653 | 1 | tkerber | |
654 | 1 | tkerber | for name, a2 in other.arrays.items(): |
655 | 1 | tkerber | if name in self.arrays: |
656 | 1 | tkerber | continue
|
657 | 1 | tkerber | a = np.zeros((n1 + n2,) + a2.shape[1:], a2.dtype)
|
658 | 1 | tkerber | a[n1:] = a2 |
659 | 1 | tkerber | self.set_array(name, a)
|
660 | 1 | tkerber | |
661 | 1 | tkerber | return self |
662 | 1 | tkerber | |
663 | 1 | tkerber | __iadd__ = extend |
664 | 1 | tkerber | |
665 | 1 | tkerber | def append(self, atom): |
666 | 1 | tkerber | """Append atom to end."""
|
667 | 1 | tkerber | self.extend(self.__class__([atom])) |
668 | 1 | tkerber | |
669 | 1 | tkerber | def __getitem__(self, i): |
670 | 1 | tkerber | """Return a subset of the atoms.
|
671 | 1 | tkerber |
|
672 | 1 | tkerber | i -- scalar integer, list of integers, or slice object
|
673 | 1 | tkerber | describing which atoms to return.
|
674 | 1 | tkerber |
|
675 | 1 | tkerber | If i is a scalar, return an Atom object. If i is a list or a
|
676 | 1 | tkerber | slice, return an Atoms object with the same cell, pbc, and
|
677 | 1 | tkerber | other associated info as the original Atoms object. The
|
678 | 1 | tkerber | indices of the constraints will be shuffled so that they match
|
679 | 1 | tkerber | the indexing in the subset returned.
|
680 | 1 | tkerber |
|
681 | 1 | tkerber | """
|
682 | 1 | tkerber | if isinstance(i, int): |
683 | 1 | tkerber | natoms = len(self) |
684 | 1 | tkerber | if i < -natoms or i >= natoms: |
685 | 1 | tkerber | raise IndexError('Index out of range.') |
686 | 1 | tkerber | |
687 | 1 | tkerber | return Atom(atoms=self, index=i) |
688 | 1 | tkerber | |
689 | 1 | tkerber | import copy |
690 | 1 | tkerber | from ase.constraints import FixConstraint |
691 | 1 | tkerber | |
692 | 1 | tkerber | atoms = self.__class__(cell=self._cell, pbc=self._pbc) |
693 | 1 | tkerber | # TODO: Do we need to shuffle indices in adsorbate_info too?
|
694 | 1 | tkerber | atoms.adsorbate_info = self.adsorbate_info
|
695 | 1 | tkerber | |
696 | 1 | tkerber | atoms.arrays = {} |
697 | 1 | tkerber | for name, a in self.arrays.items(): |
698 | 1 | tkerber | atoms.arrays[name] = a[i].copy() |
699 | 1 | tkerber | |
700 | 1 | tkerber | # Constraints need to be deepcopied, since we need to shuffle
|
701 | 1 | tkerber | # the indices
|
702 | 1 | tkerber | atoms.constraints = copy.deepcopy(self.constraints)
|
703 | 1 | tkerber | condel = [] |
704 | 1 | tkerber | for con in atoms.constraints: |
705 | 1 | tkerber | if isinstance(con, FixConstraint): |
706 | 1 | tkerber | try:
|
707 | 1 | tkerber | con.index_shuffle(i) |
708 | 1 | tkerber | except IndexError: |
709 | 1 | tkerber | condel.append(con) |
710 | 1 | tkerber | for con in condel: |
711 | 1 | tkerber | atoms.constraints.remove(con) |
712 | 1 | tkerber | return atoms
|
713 | 1 | tkerber | |
714 | 1 | tkerber | def __delitem__(self, i): |
715 | 1 | tkerber | if len(self._constraints) > 0: |
716 | 1 | tkerber | raise RuntimeError('Remove constraint using set_constraint() ' + |
717 | 1 | tkerber | 'before deleting atoms.')
|
718 | 1 | tkerber | mask = np.ones(len(self), bool) |
719 | 1 | tkerber | mask[i] = False
|
720 | 1 | tkerber | for name, a in self.arrays.items(): |
721 | 1 | tkerber | self.arrays[name] = a[mask]
|
722 | 1 | tkerber | |
723 | 1 | tkerber | def pop(self, i=-1): |
724 | 1 | tkerber | """Remove and return atom at index *i* (default last)."""
|
725 | 1 | tkerber | atom = self[i]
|
726 | 1 | tkerber | atom.cut_reference_to_atoms() |
727 | 1 | tkerber | del self[i] |
728 | 1 | tkerber | return atom
|
729 | 1 | tkerber | |
730 | 1 | tkerber | def __imul__(self, m): |
731 | 1 | tkerber | """In-place repeat of atoms."""
|
732 | 1 | tkerber | if isinstance(m, int): |
733 | 1 | tkerber | m = (m, m, m) |
734 | 1 | tkerber | |
735 | 1 | tkerber | M = np.product(m) |
736 | 1 | tkerber | n = len(self) |
737 | 1 | tkerber | |
738 | 1 | tkerber | for name, a in self.arrays.items(): |
739 | 1 | tkerber | self.arrays[name] = np.tile(a, (M,) + (1,) * (len(a.shape) - 1)) |
740 | 1 | tkerber | |
741 | 1 | tkerber | positions = self.arrays['positions'] |
742 | 1 | tkerber | i0 = 0
|
743 | 1 | tkerber | for m2 in range(m[2]): |
744 | 1 | tkerber | for m1 in range(m[1]): |
745 | 1 | tkerber | for m0 in range(m[0]): |
746 | 1 | tkerber | i1 = i0 + n |
747 | 1 | tkerber | positions[i0:i1] += np.dot((m0, m1, m2), self._cell)
|
748 | 1 | tkerber | i0 = i1 |
749 | 1 | tkerber | |
750 | 1 | tkerber | if self.constraints is not None: |
751 | 1 | tkerber | self.constraints = [c.repeat(m, n) for c in self.constraints] |
752 | 1 | tkerber | |
753 | 1 | tkerber | self._cell = np.array([m[c] * self._cell[c] for c in range(3)]) |
754 | 1 | tkerber | |
755 | 1 | tkerber | return self |
756 | 1 | tkerber | |
757 | 1 | tkerber | def repeat(self, rep): |
758 | 1 | tkerber | """Create new repeated atoms object.
|
759 | 1 | tkerber |
|
760 | 1 | tkerber | The *rep* argument should be a sequence of three positive
|
761 | 1 | tkerber | integers like *(2,3,1)* or a single integer (*r*) equivalent
|
762 | 1 | tkerber | to *(r,r,r)*."""
|
763 | 1 | tkerber | |
764 | 1 | tkerber | atoms = self.copy()
|
765 | 1 | tkerber | atoms *= rep |
766 | 1 | tkerber | return atoms
|
767 | 1 | tkerber | |
768 | 1 | tkerber | __mul__ = repeat |
769 | 1 | tkerber | |
770 | 1 | tkerber | def translate(self, displacement): |
771 | 1 | tkerber | """Translate atomic positions.
|
772 | 1 | tkerber |
|
773 | 1 | tkerber | The displacement argument can be a float an xyz vector or an
|
774 | 1 | tkerber | nx3 array (where n is the number of atoms)."""
|
775 | 1 | tkerber | |
776 | 1 | tkerber | self.arrays['positions'] += np.array(displacement) |
777 | 1 | tkerber | |
778 | 1 | tkerber | def center(self, vacuum=None, axis=None): |
779 | 1 | tkerber | """Center atoms in unit cell.
|
780 | 1 | tkerber |
|
781 | 1 | tkerber | Centers the atoms in the unit cell, so there is the same
|
782 | 1 | tkerber | amount of vacuum on all sides.
|
783 | 1 | tkerber |
|
784 | 1 | tkerber | Parameters:
|
785 | 1 | tkerber |
|
786 | 1 | tkerber | vacuum (default: None): If specified adjust the amount of
|
787 | 1 | tkerber | vacuum when centering. If vacuum=10.0 there will thus be 10
|
788 | 1 | tkerber | Angstrom of vacuum on each side.
|
789 | 1 | tkerber |
|
790 | 1 | tkerber | axis (default: None): If specified, only act on the specified
|
791 | 1 | tkerber | axis. Default: Act on all axes.
|
792 | 1 | tkerber | """
|
793 | 1 | tkerber | # Find the orientations of the faces of the unit cell
|
794 | 1 | tkerber | c = self.get_cell()
|
795 | 1 | tkerber | dirs = np.zeros_like(c) |
796 | 1 | tkerber | for i in range(3): |
797 | 1 | tkerber | dirs[i] = np.cross(c[i-1], c[i-2]) |
798 | 1 | tkerber | dirs[i] /= np.sqrt(np.dot(dirs[i], dirs[i])) #Normalize
|
799 | 1 | tkerber | if np.dot(dirs[i], c[i]) < 0.0: |
800 | 1 | tkerber | dirs[i] *= -1
|
801 | 1 | tkerber | |
802 | 1 | tkerber | # Now, decide how much each basis vector should be made longer
|
803 | 1 | tkerber | if axis is None: |
804 | 1 | tkerber | axes = (0,1,2) |
805 | 1 | tkerber | else:
|
806 | 1 | tkerber | axes = (axis,) |
807 | 1 | tkerber | p = self.arrays['positions'] |
808 | 1 | tkerber | longer = np.zeros(3)
|
809 | 1 | tkerber | shift = np.zeros(3)
|
810 | 1 | tkerber | for i in axes: |
811 | 1 | tkerber | p0 = np.dot(p, dirs[i]).min() |
812 | 1 | tkerber | p1 = np.dot(p, dirs[i]).max() |
813 | 1 | tkerber | height = np.dot(c[i], dirs[i]) |
814 | 1 | tkerber | if vacuum is not None: |
815 | 1 | tkerber | lng = (p1 - p0 + 2*vacuum) - height
|
816 | 1 | tkerber | else:
|
817 | 1 | tkerber | lng = 0.0 # Do not change unit cell size! |
818 | 1 | tkerber | top = lng + height - p1 |
819 | 1 | tkerber | shf = 0.5 * (top - p0)
|
820 | 1 | tkerber | cosphi = np.dot(c[i], dirs[i]) / np.sqrt(np.dot(c[i], c[i])) |
821 | 1 | tkerber | longer[i] = lng / cosphi |
822 | 1 | tkerber | shift[i] = shf / cosphi |
823 | 1 | tkerber | |
824 | 1 | tkerber | # Now, do it!
|
825 | 1 | tkerber | translation = np.zeros(3)
|
826 | 1 | tkerber | for i in axes: |
827 | 1 | tkerber | nowlen = np.sqrt(np.dot(c[i], c[i])) |
828 | 1 | tkerber | self._cell[i] *= 1 + longer[i] / nowlen |
829 | 1 | tkerber | translation += shift[i] * c[i] / nowlen |
830 | 1 | tkerber | self.arrays['positions'] += translation |
831 | 1 | tkerber | |
832 | 1 | tkerber | def get_center_of_mass(self, scaled = False): |
833 | 1 | tkerber | """Get the center of mass.
|
834 | 1 | tkerber |
|
835 | 1 | tkerber | If scaled=True the center of mass in scaled coordinates
|
836 | 1 | tkerber | is returned."""
|
837 | 1 | tkerber | m = self.arrays.get('masses') |
838 | 1 | tkerber | if m is None: |
839 | 1 | tkerber | m = atomic_masses[self.arrays['numbers']] |
840 | 1 | tkerber | com = np.dot(m, self.arrays['positions']) / m.sum() |
841 | 1 | tkerber | if scaled:
|
842 | 1 | tkerber | return np.dot(np.linalg.inv(self._cell), com) |
843 | 1 | tkerber | else:
|
844 | 1 | tkerber | return com
|
845 | 1 | tkerber | |
846 | 1 | tkerber | def get_moments_of_inertia(self): |
847 | 1 | tkerber | '''Get the moments of inertia
|
848 | 1 | tkerber |
|
849 | 1 | tkerber | The three principal moments of inertia are computed from the
|
850 | 1 | tkerber | eigenvalues of the inertial tensor. periodic boundary
|
851 | 1 | tkerber | conditions are ignored. Units of the moments of inertia are
|
852 | 1 | tkerber | amu*angstrom**2.
|
853 | 1 | tkerber | '''
|
854 | 1 | tkerber | |
855 | 1 | tkerber | com = self.get_center_of_mass()
|
856 | 1 | tkerber | positions = self.get_positions()
|
857 | 1 | tkerber | positions -= com #translate center of mass to origin
|
858 | 1 | tkerber | masses = self.get_masses()
|
859 | 1 | tkerber | |
860 | 1 | tkerber | #initialize elements of the inertial tensor
|
861 | 1 | tkerber | I11 = I22 = I33 = I12 = I13 = I23 = 0.0
|
862 | 1 | tkerber | for i in range(len(self)): |
863 | 1 | tkerber | x,y,z = positions[i] |
864 | 1 | tkerber | m = masses[i] |
865 | 1 | tkerber | |
866 | 1 | tkerber | I11 += m*(y**2 + z**2) |
867 | 1 | tkerber | I22 += m*(x**2 + z**2) |
868 | 1 | tkerber | I33 += m*(x**2 + y**2) |
869 | 1 | tkerber | I12 += -m*x*y |
870 | 1 | tkerber | I13 += -m*x*z |
871 | 1 | tkerber | I23 += -m*y*z |
872 | 1 | tkerber | |
873 | 1 | tkerber | I = np.array([[I11, I12, I13], |
874 | 1 | tkerber | [I12, I22, I23], |
875 | 1 | tkerber | [I13, I23, I33]]) |
876 | 1 | tkerber | |
877 | 1 | tkerber | evals, evecs = np.linalg.eig(I) |
878 | 1 | tkerber | return evals
|
879 | 1 | tkerber | |
880 | 1 | tkerber | def rotate(self, v, a=None, center=(0, 0, 0), rotate_cell=False): |
881 | 1 | tkerber | """Rotate atoms.
|
882 | 1 | tkerber |
|
883 | 1 | tkerber | Rotate the angle *a* around the vector *v*. If *a* is not
|
884 | 1 | tkerber | given, the length of *v* is used as the angle. If *a* is a
|
885 | 1 | tkerber | vector, then *v* is rotated into *a*. The point at *center*
|
886 | 1 | tkerber | is fixed. Use *center='COM'* to fix the center of mass.
|
887 | 1 | tkerber | Vectors can also be strings: 'x', '-x', 'y', ... .
|
888 | 1 | tkerber | If both *v* and *a* are vectors: rotate *v* into *a*.
|
889 | 1 | tkerber |
|
890 | 1 | tkerber | Examples:
|
891 | 1 | tkerber |
|
892 | 1 | tkerber | Rotate 90 degrees around the z-axis, so that the x-axis is
|
893 | 1 | tkerber | rotated into the y-axis:
|
894 | 1 | tkerber |
|
895 | 1 | tkerber | >>> a = pi / 2
|
896 | 1 | tkerber | >>> atoms.rotate('z', a)
|
897 | 1 | tkerber | >>> atoms.rotate((0, 0, 1), a)
|
898 | 1 | tkerber | >>> atoms.rotate('-z', -a)
|
899 | 1 | tkerber | >>> atoms.rotate((0, 0, a))
|
900 | 1 | tkerber | >>> atoms.rotate('x', 'y')
|
901 | 1 | tkerber | """
|
902 | 1 | tkerber | |
903 | 1 | tkerber | norm = np.linalg.norm |
904 | 1 | tkerber | v = string2vector(v) |
905 | 1 | tkerber | if a is None: |
906 | 1 | tkerber | a = norm(v) |
907 | 1 | tkerber | if isinstance(a, (float, int)): |
908 | 1 | tkerber | v /= norm(v) |
909 | 1 | tkerber | c = cos(a) |
910 | 1 | tkerber | s = sin(a) |
911 | 1 | tkerber | else:
|
912 | 1 | tkerber | v2 = string2vector(a) |
913 | 1 | tkerber | v /= norm(v) |
914 | 1 | tkerber | v2 /= norm(v2) |
915 | 1 | tkerber | c = np.dot(v, v2) |
916 | 1 | tkerber | v = np.cross(v, v2) |
917 | 1 | tkerber | s = norm(v) |
918 | 1 | tkerber | # In case *v* and *a* are parallel, np.cross(v, v2) vanish
|
919 | 1 | tkerber | # and can't be used as a rotation axis. However, in this
|
920 | 1 | tkerber | # case any rotation axis perpendicular to v2 will do.
|
921 | 1 | tkerber | eps = 1e-7
|
922 | 1 | tkerber | if s < eps:
|
923 | 1 | tkerber | v = np.cross((0, 0, 1), v2) |
924 | 1 | tkerber | if norm(v) < eps:
|
925 | 1 | tkerber | v = np.cross((1, 0, 0), v2) |
926 | 1 | tkerber | assert norm(v) >= eps
|
927 | 1 | tkerber | if s > 0: v /= s |
928 | 1 | tkerber | |
929 | 1 | tkerber | if isinstance(center, str) and center.lower() == 'com': |
930 | 1 | tkerber | center = self.get_center_of_mass()
|
931 | 1 | tkerber | |
932 | 1 | tkerber | p = self.arrays['positions'] - center |
933 | 1 | tkerber | self.arrays['positions'][:] = (c * p - |
934 | 1 | tkerber | np.cross(p, s * v) + |
935 | 1 | tkerber | np.outer(np.dot(p, v), (1.0 - c) * v)+
|
936 | 1 | tkerber | center) |
937 | 1 | tkerber | if rotate_cell:
|
938 | 1 | tkerber | rotcell = self.get_cell()
|
939 | 1 | tkerber | rotcell[:] = (c * rotcell - |
940 | 1 | tkerber | np.cross(rotcell, s * v) + |
941 | 1 | tkerber | np.outer(np.dot(rotcell, v), (1.0 - c) * v))
|
942 | 1 | tkerber | self.set_cell(rotcell)
|
943 | 1 | tkerber | |
944 | 1 | tkerber | def rotate_euler(self, center=(0, 0, 0), phi=0.0, theta=0.0, psi=0.0): |
945 | 1 | tkerber | """Rotate atoms via Euler angles.
|
946 | 1 | tkerber |
|
947 | 1 | tkerber | See e.g http://mathworld.wolfram.com/EulerAngles.html for explanation.
|
948 | 1 | tkerber |
|
949 | 1 | tkerber | Parameters:
|
950 | 1 | tkerber |
|
951 | 1 | tkerber | center :
|
952 | 1 | tkerber | The point to rotate about. A sequence of length 3 with the
|
953 | 1 | tkerber | coordinates, or 'COM' to select the center of mass.
|
954 | 1 | tkerber | phi :
|
955 | 1 | tkerber | The 1st rotation angle around the z axis.
|
956 | 1 | tkerber | theta :
|
957 | 1 | tkerber | Rotation around the x axis.
|
958 | 1 | tkerber | psi :
|
959 | 1 | tkerber | 2nd rotation around the z axis.
|
960 | 1 | tkerber |
|
961 | 1 | tkerber | """
|
962 | 1 | tkerber | if isinstance(center, str) and center.lower() == 'com': |
963 | 1 | tkerber | center = self.get_center_of_mass()
|
964 | 1 | tkerber | else:
|
965 | 1 | tkerber | center = np.array(center) |
966 | 1 | tkerber | # First move the molecule to the origin In contrast to MATLAB,
|
967 | 1 | tkerber | # numpy broadcasts the smaller array to the larger row-wise,
|
968 | 1 | tkerber | # so there is no need to play with the Kronecker product.
|
969 | 1 | tkerber | rcoords = self.positions - center
|
970 | 1 | tkerber | # First Euler rotation about z in matrix form
|
971 | 1 | tkerber | D = np.array(((cos(phi), sin(phi), 0.),
|
972 | 1 | tkerber | (-sin(phi), cos(phi), 0.),
|
973 | 1 | tkerber | (0., 0., 1.))) |
974 | 1 | tkerber | # Second Euler rotation about x:
|
975 | 1 | tkerber | C = np.array(((1., 0., 0.), |
976 | 1 | tkerber | (0., cos(theta), sin(theta)),
|
977 | 1 | tkerber | (0., -sin(theta), cos(theta))))
|
978 | 1 | tkerber | # Third Euler rotation, 2nd rotation about z:
|
979 | 1 | tkerber | B = np.array(((cos(psi), sin(psi), 0.),
|
980 | 1 | tkerber | (-sin(psi), cos(psi), 0.),
|
981 | 1 | tkerber | (0., 0., 1.))) |
982 | 1 | tkerber | # Total Euler rotation
|
983 | 1 | tkerber | A = np.dot(B, np.dot(C, D)) |
984 | 1 | tkerber | # Do the rotation
|
985 | 1 | tkerber | rcoords = np.dot(A, np.transpose(rcoords)) |
986 | 1 | tkerber | # Move back to the rotation point
|
987 | 1 | tkerber | self.positions = np.transpose(rcoords) + center
|
988 | 1 | tkerber | |
989 | 1 | tkerber | def get_dihedral(self,list): |
990 | 1 | tkerber | """
|
991 | 1 | tkerber | calculate dihedral angle between the vectors list[0]->list[1] and list[2]->list[3],
|
992 | 1 | tkerber | where list contains the atomic indexes in question.
|
993 | 1 | tkerber | """
|
994 | 1 | tkerber | # vector 0->1, 1->2, 2->3 and their normalized cross products:
|
995 | 1 | tkerber | a = self.positions[list[1]]-self.positions[list[0]] |
996 | 1 | tkerber | b = self.positions[list[2]]-self.positions[list[1]] |
997 | 1 | tkerber | c = self.positions[list[3]]-self.positions[list[2]] |
998 | 1 | tkerber | bxa = np.cross(b,a) |
999 | 1 | tkerber | bxa /= np.sqrt(np.vdot(bxa,bxa)) |
1000 | 1 | tkerber | cxb = np.cross(c,b) |
1001 | 1 | tkerber | cxb /= np.sqrt(np.vdot(cxb,cxb)) |
1002 | 1 | tkerber | angle = np.vdot(bxa,cxb) |
1003 | 1 | tkerber | # check for numerical trouble due to finite precision:
|
1004 | 1 | tkerber | if angle < -1: angle = -1 |
1005 | 1 | tkerber | if angle > 1: angle = 1 |
1006 | 1 | tkerber | angle = np.arccos(angle) |
1007 | 1 | tkerber | if (np.vdot(bxa,c)) > 0: angle = 2*np.pi-angle |
1008 | 1 | tkerber | return angle
|
1009 | 1 | tkerber | |
1010 | 1 | tkerber | def set_dihedral(self,list,angle,mask=None): |
1011 | 1 | tkerber | """
|
1012 | 1 | tkerber | set the dihedral angle between vectors list[0]->list[1] and
|
1013 | 1 | tkerber | list[2]->list[3] by changing the atom indexed by list[3]
|
1014 | 1 | tkerber | if mask is not None, all the atoms described in mask
|
1015 | 1 | tkerber | (read: the entire subgroup) are moved
|
1016 | 1 | tkerber |
|
1017 | 1 | tkerber | example: the following defines a very crude
|
1018 | 1 | tkerber | ethane-like molecule and twists one half of it by 30 degrees.
|
1019 | 1 | tkerber |
|
1020 | 1 | tkerber | >>> atoms = Atoms('HHCCHH',[[-1,1,0],[-1,-1,0],[0,0,0],[1,0,0],[2,1,0],[2,-1,0]])
|
1021 | 1 | tkerber | >>> atoms.set_dihedral([1,2,3,4],7*pi/6,mask=[0,0,0,1,1,1])
|
1022 | 1 | tkerber | """
|
1023 | 1 | tkerber | # if not provided, set mask to the last atom in the dihedral description
|
1024 | 1 | tkerber | if mask is None: |
1025 | 1 | tkerber | mask = np.zeros(len(self)) |
1026 | 1 | tkerber | mask[list[3]] = 1 |
1027 | 1 | tkerber | # compute necessary in dihedral change, from current value
|
1028 | 1 | tkerber | current =self.get_dihedral(list) |
1029 | 1 | tkerber | diff = angle - current |
1030 | 1 | tkerber | # do rotation of subgroup by copying it to temporary atoms object and then rotating that
|
1031 | 1 | tkerber | axis = self.positions[list[2]]-self.positions[list[1]] |
1032 | 1 | tkerber | center = self.positions[list[2]] |
1033 | 1 | tkerber | # recursive object definition might not be the most elegant thing, more generally useful might be a rotation function with a mask?
|
1034 | 1 | tkerber | group = self.__class__()
|
1035 | 1 | tkerber | for i in range(len(self)): |
1036 | 1 | tkerber | if mask[i]:
|
1037 | 1 | tkerber | group += self[i]
|
1038 | 1 | tkerber | group.translate(-center) |
1039 | 1 | tkerber | group.rotate(axis,diff) |
1040 | 1 | tkerber | group.translate(center) |
1041 | 1 | tkerber | # set positions in original atoms object
|
1042 | 1 | tkerber | j = 0
|
1043 | 1 | tkerber | for i in range(len(self)): |
1044 | 1 | tkerber | if mask[i]:
|
1045 | 1 | tkerber | self.positions[i] = group[j].get_position()
|
1046 | 1 | tkerber | j += 1
|
1047 | 1 | tkerber | |
1048 | 1 | tkerber | def rotate_dihedral(self,list,angle,mask=None): |
1049 | 1 | tkerber | """ complementing the two routines above: rotate a group by a predefined dihedral angle,
|
1050 | 1 | tkerber | starting from its current configuration
|
1051 | 1 | tkerber | """
|
1052 | 1 | tkerber | start = self.get_dihedral(list) |
1053 | 1 | tkerber | self.set_dihedral(list,angle+start,mask) |
1054 | 1 | tkerber | |
1055 | 1 | tkerber | def rattle(self, stdev=0.001, seed=42): |
1056 | 1 | tkerber | """Randomly displace atoms.
|
1057 | 1 | tkerber |
|
1058 | 1 | tkerber | This method adds random displacements to the atomic positions,
|
1059 | 1 | tkerber | taking a possible constraint into account. The random numbers are
|
1060 | 1 | tkerber | drawn from a normal distribution of standard deviation stdev.
|
1061 | 1 | tkerber |
|
1062 | 1 | tkerber | For a parallel calculation, it is important to use the same
|
1063 | 1 | tkerber | seed on all processors! """
|
1064 | 1 | tkerber | |
1065 | 1 | tkerber | rs = np.random.RandomState(seed) |
1066 | 1 | tkerber | positions = self.arrays['positions'] |
1067 | 1 | tkerber | self.set_positions(positions +
|
1068 | 1 | tkerber | rs.normal(scale=stdev, size=positions.shape)) |
1069 | 1 | tkerber | |
1070 | 1 | tkerber | def get_distance(self, a0, a1, mic=False): |
1071 | 1 | tkerber | """Return distance between two atoms.
|
1072 | 1 | tkerber |
|
1073 | 1 | tkerber | Use mic=True to use the Minimum Image Convention.
|
1074 | 1 | tkerber | """
|
1075 | 1 | tkerber | |
1076 | 1 | tkerber | R = self.arrays['positions'] |
1077 | 1 | tkerber | D = R[a1] - R[a0] |
1078 | 1 | tkerber | if mic:
|
1079 | 1 | tkerber | raise NotImplemented # XXX |
1080 | 1 | tkerber | return np.linalg.norm(D)
|
1081 | 1 | tkerber | |
1082 | 1 | tkerber | def set_distance(self, a0, a1, distance, fix=0.5): |
1083 | 1 | tkerber | """Set the distance between two atoms.
|
1084 | 1 | tkerber |
|
1085 | 1 | tkerber | Set the distance between atoms *a0* and *a1* to *distance*.
|
1086 | 1 | tkerber | By default, the center of the two atoms will be fixed. Use
|
1087 | 1 | tkerber | *fix=0* to fix the first atom, *fix=1* to fix the second
|
1088 | 1 | tkerber | atom and *fix=0.5* (default) to fix the center of the bond."""
|
1089 | 1 | tkerber | |
1090 | 1 | tkerber | R = self.arrays['positions'] |
1091 | 1 | tkerber | D = R[a1] - R[a0] |
1092 | 1 | tkerber | x = 1.0 - distance / np.linalg.norm(D)
|
1093 | 1 | tkerber | R[a0] += (x * fix) * D |
1094 | 1 | tkerber | R[a1] -= (x * (1.0 - fix)) * D
|
1095 | 1 | tkerber | |
1096 | 1 | tkerber | def get_scaled_positions(self): |
1097 | 1 | tkerber | """Get positions relative to unit cell.
|
1098 | 1 | tkerber |
|
1099 | 1 | tkerber | Atoms outside the unit cell will be wrapped into the cell in
|
1100 | 1 | tkerber | those directions with periodic boundary conditions so that the
|
1101 | 1 | tkerber | scaled coordinates are beween zero and one."""
|
1102 | 1 | tkerber | |
1103 | 1 | tkerber | scaled = np.linalg.solve(self._cell.T, self.arrays['positions'].T).T |
1104 | 1 | tkerber | for i in range(3): |
1105 | 1 | tkerber | if self._pbc[i]: |
1106 | 1 | tkerber | # Yes, we need to do it twice.
|
1107 | 1 | tkerber | # See the scaled_positions.py test
|
1108 | 1 | tkerber | scaled[:, i] %= 1.0
|
1109 | 1 | tkerber | scaled[:, i] %= 1.0
|
1110 | 1 | tkerber | return scaled
|
1111 | 1 | tkerber | |
1112 | 1 | tkerber | def set_scaled_positions(self, scaled): |
1113 | 1 | tkerber | """Set positions relative to unit cell."""
|
1114 | 1 | tkerber | self.arrays['positions'][:] = np.dot(scaled, self._cell) |
1115 | 1 | tkerber | |
1116 | 1 | tkerber | def get_temperature(self): |
1117 | 1 | tkerber | """Get the temperature. in Kelvin"""
|
1118 | 1 | tkerber | ekin = self.get_kinetic_energy() / len(self) |
1119 | 1 | tkerber | return ekin /(1.5*units.kB) |
1120 | 1 | tkerber | |
1121 | 1 | tkerber | def get_isotropic_pressure(self, stress): |
1122 | 1 | tkerber | """ get the current calculated pressure, assume isotropic medium.
|
1123 | 1 | tkerber | in Bar
|
1124 | 1 | tkerber | """
|
1125 | 1 | tkerber | if type(stress) == type(1.0) or type(stress) == type(1): |
1126 | 1 | tkerber | return -stress * 1e-5 / units.Pascal |
1127 | 1 | tkerber | elif stress.shape == (3,3): |
1128 | 1 | tkerber | return (-(stress[0, 0] + stress[1, 1] + stress[2, 2]) / 3.0) * \ |
1129 | 1 | tkerber | 1e-5 / units.Pascal
|
1130 | 1 | tkerber | elif stress.shape == (6,): |
1131 | 1 | tkerber | return (-(stress[0] + stress[1] + stress[2]) / 3.0) * \ |
1132 | 1 | tkerber | 1e-5 / units.Pascal
|
1133 | 1 | tkerber | else:
|
1134 | 1 | tkerber | raise ValueError, "The external stress has the wrong shape." |
1135 | 1 | tkerber | |
1136 | 1 | tkerber | |
1137 | 1 | tkerber | |
1138 | 1 | tkerber | def __eq__(self, other): |
1139 | 1 | tkerber | """Check for identity of two atoms objects.
|
1140 | 1 | tkerber |
|
1141 | 1 | tkerber | Identity means: same positions, atomic numbers, unit cell and
|
1142 | 1 | tkerber | periodic boundary conditions."""
|
1143 | 1 | tkerber | try:
|
1144 | 1 | tkerber | a = self.arrays
|
1145 | 1 | tkerber | b = other.arrays |
1146 | 1 | tkerber | return (len(self) == len(other) and |
1147 | 1 | tkerber | (a['positions'] == b['positions']).all() and |
1148 | 1 | tkerber | (a['numbers'] == b['numbers']).all() and |
1149 | 1 | tkerber | (self._cell == other.cell).all() and |
1150 | 1 | tkerber | (self._pbc == other.pbc).all())
|
1151 | 1 | tkerber | except AttributeError: |
1152 | 1 | tkerber | return NotImplemented |
1153 | 1 | tkerber | |
1154 | 1 | tkerber | def __ne__(self, other): |
1155 | 1 | tkerber | eq = self.__eq__(other)
|
1156 | 1 | tkerber | if eq is NotImplemented: |
1157 | 1 | tkerber | return eq
|
1158 | 1 | tkerber | else:
|
1159 | 1 | tkerber | return not eq |
1160 | 1 | tkerber | |
1161 | 1 | tkerber | __hash__ = None
|
1162 | 1 | tkerber | |
1163 | 1 | tkerber | def get_volume(self): |
1164 | 1 | tkerber | """Get volume of unit cell."""
|
1165 | 1 | tkerber | return abs(np.linalg.det(self._cell)) |
1166 | 1 | tkerber | |
1167 | 1 | tkerber | def _get_positions(self): |
1168 | 1 | tkerber | """Return reference to positions-array for inplace manipulations."""
|
1169 | 1 | tkerber | return self.arrays['positions'] |
1170 | 1 | tkerber | |
1171 | 1 | tkerber | def _set_positions(self, pos): |
1172 | 1 | tkerber | """Set positions directly, bypassing constraints."""
|
1173 | 1 | tkerber | self.arrays['positions'][:] = pos |
1174 | 1 | tkerber | |
1175 | 1 | tkerber | positions = property(_get_positions, _set_positions,
|
1176 | 1 | tkerber | doc='Attribute for direct ' +
|
1177 | 1 | tkerber | 'manipulation of the positions.')
|
1178 | 1 | tkerber | |
1179 | 1 | tkerber | def _get_atomic_numbers(self): |
1180 | 1 | tkerber | """Return reference to atomic numbers for inplace manipulations."""
|
1181 | 1 | tkerber | return self.arrays['numbers'] |
1182 | 1 | tkerber | |
1183 | 1 | tkerber | numbers = property(_get_atomic_numbers, set_atomic_numbers,
|
1184 | 1 | tkerber | doc='Attribute for direct ' +
|
1185 | 1 | tkerber | 'manipulation of the atomic numbers.')
|
1186 | 1 | tkerber | |
1187 | 1 | tkerber | def _get_cell(self): |
1188 | 1 | tkerber | """Return reference to unit cell for inplace manipulations."""
|
1189 | 1 | tkerber | return self._cell |
1190 | 1 | tkerber | |
1191 | 1 | tkerber | cell = property(_get_cell, set_cell, doc='Attribute for direct ' + |
1192 | 1 | tkerber | 'manipulation of the unit cell.')
|
1193 | 1 | tkerber | |
1194 | 1 | tkerber | def _get_pbc(self): |
1195 | 1 | tkerber | """Return reference to pbc-flags for inplace manipulations."""
|
1196 | 1 | tkerber | return self._pbc |
1197 | 1 | tkerber | |
1198 | 1 | tkerber | pbc = property(_get_pbc, set_pbc,
|
1199 | 1 | tkerber | doc='Attribute for direct manipulation ' +
|
1200 | 1 | tkerber | 'of the periodic boundary condition flags.')
|
1201 | 1 | tkerber | |
1202 | 1 | tkerber | def get_name(self): |
1203 | 1 | tkerber | """Return a name extracted from the elements."""
|
1204 | 1 | tkerber | elements = {} |
1205 | 1 | tkerber | for a in self: |
1206 | 1 | tkerber | try:
|
1207 | 1 | tkerber | elements[a.symbol] += 1
|
1208 | 1 | tkerber | except:
|
1209 | 1 | tkerber | elements[a.symbol] = 1
|
1210 | 1 | tkerber | name = ''
|
1211 | 1 | tkerber | for element in elements: |
1212 | 1 | tkerber | name += element |
1213 | 1 | tkerber | if elements[element] > 1: |
1214 | 1 | tkerber | name += str(elements[element])
|
1215 | 1 | tkerber | return name
|
1216 | 1 | tkerber | |
1217 | 1 | tkerber | def write(self, filename, format=None): |
1218 | 1 | tkerber | """Write yourself to a file."""
|
1219 | 1 | tkerber | from ase.io import write |
1220 | 1 | tkerber | write(filename, self, format)
|
1221 | 1 | tkerber | |
1222 | 1 | tkerber | def string2symbols(s): |
1223 | 1 | tkerber | """Convert string to list of chemical symbols."""
|
1224 | 1 | tkerber | n = len(s)
|
1225 | 1 | tkerber | |
1226 | 1 | tkerber | if n == 0: |
1227 | 1 | tkerber | return []
|
1228 | 1 | tkerber | |
1229 | 1 | tkerber | c = s[0]
|
1230 | 1 | tkerber | |
1231 | 1 | tkerber | if c.isdigit():
|
1232 | 1 | tkerber | i = 1
|
1233 | 1 | tkerber | while i < n and s[i].isdigit(): |
1234 | 1 | tkerber | i += 1
|
1235 | 1 | tkerber | return int(s[:i]) * string2symbols(s[i:]) |
1236 | 1 | tkerber | |
1237 | 1 | tkerber | if c == '(': |
1238 | 1 | tkerber | p = 0
|
1239 | 1 | tkerber | for i, c in enumerate(s): |
1240 | 1 | tkerber | if c == '(': |
1241 | 1 | tkerber | p += 1
|
1242 | 1 | tkerber | elif c == ')': |
1243 | 1 | tkerber | p -= 1
|
1244 | 1 | tkerber | if p == 0: |
1245 | 1 | tkerber | break
|
1246 | 1 | tkerber | j = i + 1
|
1247 | 1 | tkerber | while j < n and s[j].isdigit(): |
1248 | 1 | tkerber | j += 1
|
1249 | 1 | tkerber | if j > i + 1: |
1250 | 1 | tkerber | m = int(s[i + 1:j]) |
1251 | 1 | tkerber | else:
|
1252 | 1 | tkerber | m = 1
|
1253 | 1 | tkerber | return m * string2symbols(s[1:i]) + string2symbols(s[j:]) |
1254 | 1 | tkerber | |
1255 | 1 | tkerber | if c.isupper():
|
1256 | 1 | tkerber | i = 1
|
1257 | 1 | tkerber | if 1 < n and s[1].islower(): |
1258 | 1 | tkerber | i += 1
|
1259 | 1 | tkerber | j = i |
1260 | 1 | tkerber | while j < n and s[j].isdigit(): |
1261 | 1 | tkerber | j += 1
|
1262 | 1 | tkerber | if j > i:
|
1263 | 1 | tkerber | m = int(s[i:j])
|
1264 | 1 | tkerber | else:
|
1265 | 1 | tkerber | m = 1
|
1266 | 1 | tkerber | return m * [s[:i]] + string2symbols(s[j:])
|
1267 | 1 | tkerber | else:
|
1268 | 1 | tkerber | raise ValueError |
1269 | 1 | tkerber | |
1270 | 1 | tkerber | def symbols2numbers(symbols): |
1271 | 1 | tkerber | if isinstance(symbols, str): |
1272 | 1 | tkerber | symbols = string2symbols(symbols) |
1273 | 1 | tkerber | numbers = [] |
1274 | 1 | tkerber | for s in symbols: |
1275 | 1 | tkerber | if isinstance(s, str): |
1276 | 1 | tkerber | numbers.append(atomic_numbers[s]) |
1277 | 1 | tkerber | else:
|
1278 | 1 | tkerber | numbers.append(s) |
1279 | 1 | tkerber | return numbers
|
1280 | 1 | tkerber | |
1281 | 1 | tkerber | def string2vector(v): |
1282 | 1 | tkerber | if isinstance(v, str): |
1283 | 1 | tkerber | if v[0] == '-': |
1284 | 1 | tkerber | return -string2vector(v[1:]) |
1285 | 1 | tkerber | w = np.zeros(3)
|
1286 | 1 | tkerber | w['xyz'.index(v)] = 1.0 |
1287 | 1 | tkerber | return w
|
1288 | 1 | tkerber | return np.array(v, float) |
1289 | 1 | tkerber | |
1290 | 1 | tkerber | def default(data, dflt): |
1291 | 1 | tkerber | """Helper function for setting default values."""
|
1292 | 1 | tkerber | if data is None: |
1293 | 1 | tkerber | return None |
1294 | 1 | tkerber | elif isinstance(data, (list, tuple)): |
1295 | 1 | tkerber | newdata = [] |
1296 | 1 | tkerber | allnone = True
|
1297 | 1 | tkerber | for x in data: |
1298 | 1 | tkerber | if x is None: |
1299 | 1 | tkerber | newdata.append(dflt) |
1300 | 1 | tkerber | else:
|
1301 | 1 | tkerber | newdata.append(x) |
1302 | 1 | tkerber | allnone = False
|
1303 | 1 | tkerber | if allnone:
|
1304 | 1 | tkerber | return None |
1305 | 1 | tkerber | return newdata
|
1306 | 1 | tkerber | else:
|
1307 | 1 | tkerber | return data
|