root / ase / constraints.py @ 18
Historique | Voir | Annoter | Télécharger (21,74 ko)
1 |
from math import sqrt |
---|---|
2 |
|
3 |
import numpy as np |
4 |
|
5 |
__all__ = ['FixCartesian', 'FixBondLength', 'FixedMode', 'FixConstraintSingle', |
6 |
'FixAtoms', 'UnitCellFilter', 'FixScaled', 'StrainFilter', |
7 |
'FixedPlane', 'Filter', 'FixConstraint', 'FixedLine', |
8 |
'FixBondLengths']
|
9 |
|
10 |
|
11 |
def slice2enlist(s): |
12 |
"""Convert a slice object into a list of (new, old) tuples."""
|
13 |
if isinstance(s, (list, tuple)): |
14 |
return enumerate(s) |
15 |
if s.step == None: |
16 |
step = 1
|
17 |
else:
|
18 |
step = s.step |
19 |
if s.start == None: |
20 |
start = 0
|
21 |
else:
|
22 |
start = s.start |
23 |
return enumerate(range(start, s.stop, step)) |
24 |
|
25 |
class FixConstraint: |
26 |
"""Base class for classes that fix one or more atoms in some way."""
|
27 |
|
28 |
def index_shuffle(self, ind): |
29 |
"""Change the indices.
|
30 |
|
31 |
When the ordering of the atoms in the Atoms object changes,
|
32 |
this method can be called to shuffle the indices of the
|
33 |
constraints.
|
34 |
|
35 |
ind -- List or tuple of indices.
|
36 |
|
37 |
"""
|
38 |
raise NotImplementedError |
39 |
|
40 |
def repeat(self, m, n): |
41 |
""" basic method to multiply by m, needs to know the length
|
42 |
of the underlying atoms object for the assignment of
|
43 |
multiplied constraints to work.
|
44 |
"""
|
45 |
raise NotImplementedError |
46 |
|
47 |
class FixConstraintSingle(FixConstraint): |
48 |
"Base class for classes that fix a single atom."
|
49 |
|
50 |
def index_shuffle(self, ind): |
51 |
"The atom index must be stored as self.a."
|
52 |
newa = -1 # Signal error |
53 |
for new, old in slice2enlist(ind): |
54 |
if old == self.a: |
55 |
newa = new |
56 |
break
|
57 |
if newa == -1: |
58 |
raise IndexError('Constraint not part of slice') |
59 |
self.a = newa
|
60 |
|
61 |
class FixAtoms(FixConstraint): |
62 |
"""Constraint object for fixing some chosen atoms."""
|
63 |
def __init__(self, indices=None, mask=None): |
64 |
"""Constrain chosen atoms.
|
65 |
|
66 |
Parameters
|
67 |
----------
|
68 |
indices : list of int
|
69 |
Indices for those atoms that should be constrained.
|
70 |
mask : list of bool
|
71 |
One boolean per atom indicating if the atom should be
|
72 |
constrained or not.
|
73 |
|
74 |
Examples
|
75 |
--------
|
76 |
Fix all Copper atoms:
|
77 |
|
78 |
>>> c = FixAtoms(mask=[s == 'Cu' for s in atoms.get_chemical_symbols()])
|
79 |
>>> atoms.set_constraint(c)
|
80 |
|
81 |
Fix all atoms with z-coordinate less than 1.0 Angstrom:
|
82 |
|
83 |
>>> c = FixAtoms(mask=atoms.positions[:, 2] < 1.0)
|
84 |
>>> atoms.set_constraint(c)
|
85 |
"""
|
86 |
|
87 |
if indices is None and mask is None: |
88 |
raise ValueError('Use "indices" or "mask".') |
89 |
if indices is not None and mask is not None: |
90 |
raise ValueError('Use only one of "indices" and "mask".') |
91 |
|
92 |
if mask is not None: |
93 |
self.index = np.asarray(mask, bool) |
94 |
else:
|
95 |
# Check for duplicates
|
96 |
srt = np.sort(indices) |
97 |
for i in range(len(indices)-1): |
98 |
if srt[i] == srt[i+1]: |
99 |
raise ValueError("FixAtoms: The indices array contained duplicates. Perhaps you wanted to specify a mask instead, but forgot the mask= keyword.") |
100 |
self.index = np.asarray(indices, int) |
101 |
|
102 |
if self.index.ndim != 1: |
103 |
raise ValueError('Wrong argument to FixAtoms class!') |
104 |
|
105 |
def adjust_positions(self, old, new): |
106 |
new[self.index] = old[self.index] |
107 |
|
108 |
def adjust_forces(self, positions, forces): |
109 |
forces[self.index] = 0.0 |
110 |
|
111 |
def index_shuffle(self, ind): |
112 |
# See docstring of superclass
|
113 |
if self.index.dtype == bool: |
114 |
self.index = self.index[ind] |
115 |
else:
|
116 |
index = [] |
117 |
for new, old in slice2enlist(ind): |
118 |
if old in self.index: |
119 |
index.append(new) |
120 |
if len(index) == 0: |
121 |
raise IndexError('All indices in FixAtoms not part of slice') |
122 |
self.index = np.asarray(index, int) |
123 |
|
124 |
def copy(self): |
125 |
if self.index.dtype == bool: |
126 |
return FixAtoms(mask=self.index.copy()) |
127 |
else:
|
128 |
return FixAtoms(indices=self.index.copy()) |
129 |
|
130 |
def __repr__(self): |
131 |
if self.index.dtype == bool: |
132 |
return 'FixAtoms(mask=%s)' % ints2string(self.index.astype(int)) |
133 |
return 'FixAtoms(indices=%s)' % ints2string(self.index) |
134 |
|
135 |
def repeat(self, m, n): |
136 |
i0 = 0
|
137 |
l = len(self.index) |
138 |
natoms = 0
|
139 |
if isinstance(m, int): |
140 |
m = (m, m, m) |
141 |
index_new = [] |
142 |
for m2 in range(m[2]): |
143 |
for m1 in range(m[1]): |
144 |
for m0 in range(m[0]): |
145 |
i1 = i0 + n |
146 |
if self.index.dtype == bool: |
147 |
index_new += self.index
|
148 |
else:
|
149 |
index_new += [i+natoms for i in self.index] |
150 |
i0 = i1 |
151 |
natoms += n |
152 |
if self.index.dtype == bool: |
153 |
self.index = np.asarray(index_new, bool) |
154 |
else:
|
155 |
self.index = np.asarray(index_new, int) |
156 |
return self |
157 |
|
158 |
def ints2string(x, threshold=10): |
159 |
"""Convert ndarray of ints to string."""
|
160 |
if len(x) <= threshold: |
161 |
return str(x.tolist()) |
162 |
return str(x[:threshold].tolist())[:-1] + ', ...]' |
163 |
|
164 |
class FixBondLengths(FixConstraint): |
165 |
def __init__(self, pairs, iterations=10): |
166 |
self.constraints = [FixBondLength(a1, a2)
|
167 |
for a1, a2 in pairs] |
168 |
self.iterations = iterations
|
169 |
|
170 |
def adjust_positions(self, old, new): |
171 |
for i in range(self.iterations): |
172 |
for constraint in self.constraints: |
173 |
constraint.adjust_positions(old, new) |
174 |
|
175 |
def adjust_forces(self, positions, forces): |
176 |
for i in range(self.iterations): |
177 |
for constraint in self.constraints: |
178 |
constraint.adjust_forces(positions, forces) |
179 |
|
180 |
def copy(self): |
181 |
return FixBondLengths([constraint.indices for constraint in self.constraints]) |
182 |
|
183 |
class FixBondLength(FixConstraint): |
184 |
"""Constraint object for fixing a bond length."""
|
185 |
def __init__(self, a1, a2): |
186 |
"""Fix distance between atoms with indices a1 and a2."""
|
187 |
self.indices = [a1, a2]
|
188 |
|
189 |
def adjust_positions(self, old, new): |
190 |
p1, p2 = old[self.indices]
|
191 |
d = p2 - p1 |
192 |
p = sqrt(np.dot(d, d)) |
193 |
q1, q2 = new[self.indices]
|
194 |
d = q2 - q1 |
195 |
q = sqrt(np.dot(d, d)) |
196 |
d *= 0.5 * (p - q) / q
|
197 |
new[self.indices] = (q1 - d, q2 + d)
|
198 |
|
199 |
def adjust_forces(self, positions, forces): |
200 |
d = np.subtract.reduce(positions[self.indices])
|
201 |
d2 = np.dot(d, d) |
202 |
d *= 0.5 * np.dot(np.subtract.reduce(forces[self.indices]), d) / d2 |
203 |
forces[self.indices] += (-d, d)
|
204 |
|
205 |
def index_shuffle(self, ind): |
206 |
'Shuffle the indices of the two atoms in this constraint'
|
207 |
newa = [-1, -1] # Signal error |
208 |
for new, old in slice2enlist(ind): |
209 |
for i, a in enumerate(self.indices): |
210 |
if old == a:
|
211 |
newa[i] = new |
212 |
if newa[0] == -1 or newa[1] == -1: |
213 |
raise IndexError('Constraint not part of slice') |
214 |
self.indices = newa
|
215 |
|
216 |
def copy(self): |
217 |
return FixBondLength(*self.indices) |
218 |
|
219 |
def __repr__(self): |
220 |
return 'FixBondLength(%d, %d)' % tuple(self.indices) |
221 |
|
222 |
class FixedMode(FixConstraint): |
223 |
"""Constrain atoms to move along directions orthogonal to
|
224 |
a given mode only."""
|
225 |
|
226 |
def __init__(self,indices,mode): |
227 |
if indices is None: |
228 |
raise ValueError('Use "indices".') |
229 |
if indices is not None: |
230 |
self.index = np.asarray(indices, int) |
231 |
self.mode = (np.asarray(mode) / np.sqrt((mode **2).sum())).reshape(-1) |
232 |
|
233 |
def adjust_positions(self, oldpositions, newpositions): |
234 |
newpositions = newpositions.ravel() |
235 |
oldpositions = oldpositions.ravel() |
236 |
step = newpositions - oldpositions |
237 |
newpositions -= self.mode * np.dot(step, self.mode) |
238 |
newpositions = newpositions.reshape(-1,3) |
239 |
oldpositions = oldpositions.reshape(-1,3) |
240 |
|
241 |
def adjust_forces(self, positions, forces): |
242 |
forces = forces.ravel() |
243 |
forces -= self.mode * np.dot(forces, self.mode) |
244 |
forces = forces.reshape(-1 ,3) |
245 |
|
246 |
def copy(self): |
247 |
return FixedMode(self.index.copy(), self.mode) |
248 |
|
249 |
def __repr__(self): |
250 |
return 'FixedMode(%d, %s)' % (ints2string(self.index), self.mode.tolist()) |
251 |
|
252 |
class FixedPlane(FixConstraintSingle): |
253 |
"""Constrain an atom *a* to move in a given plane only.
|
254 |
|
255 |
The plane is defined by its normal: *direction*."""
|
256 |
|
257 |
def __init__(self, a, direction): |
258 |
self.a = a
|
259 |
self.dir = np.asarray(direction) / sqrt(np.dot(direction, direction))
|
260 |
|
261 |
def adjust_positions(self, oldpositions, newpositions): |
262 |
step = newpositions[self.a] - oldpositions[self.a] |
263 |
newpositions[self.a] -= self.dir * np.dot(step, self.dir) |
264 |
|
265 |
def adjust_forces(self, positions, forces): |
266 |
forces[self.a] -= self.dir * np.dot(forces[self.a], self.dir) |
267 |
|
268 |
def copy(self): |
269 |
return FixedPlane(self.a, self.dir) |
270 |
|
271 |
def __repr__(self): |
272 |
return 'FixedPlane(%d, %s)' % (self.a, self.dir.tolist()) |
273 |
|
274 |
|
275 |
class FixedLine(FixConstraintSingle): |
276 |
"""Constrain an atom *a* to move on a given line only.
|
277 |
|
278 |
The line is defined by its *direction*."""
|
279 |
|
280 |
def __init__(self, a, direction): |
281 |
self.a = a
|
282 |
self.dir = np.asarray(direction) / sqrt(np.dot(direction, direction))
|
283 |
|
284 |
def adjust_positions(self, oldpositions, newpositions): |
285 |
step = newpositions[self.a] - oldpositions[self.a] |
286 |
x = np.dot(step, self.dir)
|
287 |
newpositions[self.a] = oldpositions[self.a] + x * self.dir |
288 |
|
289 |
def adjust_forces(self, positions, forces): |
290 |
forces[self.a] = self.dir * np.dot(forces[self.a], self.dir) |
291 |
|
292 |
def copy(self): |
293 |
return FixedLine(self.a, self.dir) |
294 |
|
295 |
def __repr__(self): |
296 |
return 'FixedLine(%d, %s)' % (self.a, self.dir.tolist()) |
297 |
|
298 |
class FixCartesian(FixConstraintSingle): |
299 |
"Fix an atom in the directions of the cartesian coordinates."
|
300 |
def __init__(self, a, mask=[1,1,1]): |
301 |
self.a=a
|
302 |
self.mask = -(np.array(mask)-1) |
303 |
|
304 |
def adjust_positions(self, old, new): |
305 |
step = new - old |
306 |
step[self.a] *= self.mask |
307 |
new = old + step |
308 |
|
309 |
def adjust_forces(self, positions, forces): |
310 |
forces[self.a] *= self.mask |
311 |
|
312 |
def copy(self): |
313 |
return FixCartesian(self.a, self.mask) |
314 |
|
315 |
def __repr__(self): |
316 |
return 'FixCartesian(indice=%s mask=%s)' % (self.a, self.mask) |
317 |
|
318 |
class fix_cartesian(FixCartesian): |
319 |
"Backwards compatibility for FixCartesian."
|
320 |
def __init__(self, a, mask=[1,1,1]): |
321 |
import warnings |
322 |
super(fix_cartesian, self).__init__(a, mask) |
323 |
warnings.warn('fix_cartesian is deprecated. Please use FixCartesian' \
|
324 |
' instead.', DeprecationWarning, stacklevel=2) |
325 |
|
326 |
class FixScaled(FixConstraintSingle): |
327 |
"Fix an atom in the directions of the unit vectors."
|
328 |
def __init__(self, cell, a, mask=[1,1,1]): |
329 |
self.cell = cell
|
330 |
self.a = a
|
331 |
self.mask = np.array(mask)
|
332 |
|
333 |
def adjust_positions(self, old, new): |
334 |
scaled_old = np.linalg.solve(self.cell.T, old.T).T
|
335 |
scaled_new = np.linalg.solve(self.cell.T, new.T).T
|
336 |
for n in range(3): |
337 |
if self.mask[n]: |
338 |
scaled_new[self.a, n] = scaled_old[self.a, n] |
339 |
new[self.a] = np.dot(scaled_new, self.cell)[self.a] |
340 |
|
341 |
def adjust_forces(self, positions, forces): |
342 |
scaled_forces = np.linalg.solve(self.cell.T, forces.T).T
|
343 |
scaled_forces[self.a] *= -(self.mask-1) |
344 |
forces[self.a] = np.dot(scaled_forces, self.cell)[self.a] |
345 |
|
346 |
def copy(self): |
347 |
return FixScaled(self.cell ,self.a, self.mask) |
348 |
|
349 |
def __repr__(self): |
350 |
return 'FixScaled(indice=%s mask=%s)' % (self.a, self.mask) |
351 |
|
352 |
class fix_scaled(FixScaled): |
353 |
"Backwards compatibility for FixScaled."
|
354 |
def __init__(self, cell, a, mask=[1,1,1]): |
355 |
import warnings |
356 |
super(fix_scaled, self).__init__(cell, a, mask) |
357 |
warnings.warn('fix_scaled is deprecated. Please use FixScaled ' \
|
358 |
'instead.', DeprecationWarning, stacklevel=2) |
359 |
|
360 |
class Filter: |
361 |
"""Subset filter class."""
|
362 |
def __init__(self, atoms, indices=None, mask=None): |
363 |
"""Filter atoms.
|
364 |
|
365 |
This filter can be used to hide degrees of freedom in an Atoms
|
366 |
object.
|
367 |
|
368 |
Parameters
|
369 |
----------
|
370 |
indices : list of int
|
371 |
Indices for those atoms that should remain visible.
|
372 |
mask : list of bool
|
373 |
One boolean per atom indicating if the atom should remain
|
374 |
visible or not.
|
375 |
"""
|
376 |
|
377 |
self.atoms = atoms
|
378 |
self.constraints = []
|
379 |
|
380 |
if indices is None and mask is None: |
381 |
raise ValuError('Use "indices" or "mask".') |
382 |
if indices is not None and mask is not None: |
383 |
raise ValuError('Use only one of "indices" and "mask".') |
384 |
|
385 |
if mask is not None: |
386 |
self.index = np.asarray(mask, bool) |
387 |
self.n = self.index.sum() |
388 |
else:
|
389 |
self.index = np.asarray(indices, int) |
390 |
self.n = len(self.index) |
391 |
|
392 |
def get_cell(self): |
393 |
"""Returns the computational cell.
|
394 |
|
395 |
The computational cell is the same as for the original system.
|
396 |
"""
|
397 |
return self.atoms.get_cell() |
398 |
|
399 |
def get_pbc(self): |
400 |
"""Returns the periodic boundary conditions.
|
401 |
|
402 |
The boundary conditions are the same as for the original system.
|
403 |
"""
|
404 |
return self.atoms.get_pbc() |
405 |
|
406 |
def get_positions(self): |
407 |
"Return the positions of the visible atoms."
|
408 |
return self.atoms.get_positions()[self.index] |
409 |
|
410 |
def set_positions(self, positions): |
411 |
"Set the positions of the visible atoms."
|
412 |
pos = self.atoms.get_positions()
|
413 |
pos[self.index] = positions
|
414 |
self.atoms.set_positions(pos)
|
415 |
|
416 |
def get_momenta(self): |
417 |
"Return the momenta of the visible atoms."
|
418 |
return self.atoms.get_momenta()[self.index] |
419 |
|
420 |
def set_momenta(self, momenta): |
421 |
"Set the momenta of the visible atoms."
|
422 |
mom = self.atoms.get_momenta()
|
423 |
mom[self.index] = momenta
|
424 |
self.atoms.set_momenta(mom)
|
425 |
|
426 |
def get_atomic_numbers(self): |
427 |
"Return the atomic numbers of the visible atoms."
|
428 |
return self.atoms.get_atomic_numbers()[self.index] |
429 |
|
430 |
def set_atomic_numbers(self, atomic_numbers): |
431 |
"Set the atomic numbers of the visible atoms."
|
432 |
z = self.atoms.get_atomic_numbers()
|
433 |
z[self.index] = atomic_numbers
|
434 |
self.atoms.set_atomic_numbers(z)
|
435 |
|
436 |
def get_tags(self): |
437 |
"Return the tags of the visible atoms."
|
438 |
return self.atoms.get_tags()[self.index] |
439 |
|
440 |
def set_tags(self, tags): |
441 |
"Set the tags of the visible atoms."
|
442 |
tg = self.atoms.get_tags()
|
443 |
tg[self.index] = tags
|
444 |
self.atoms.set_tags(tg)
|
445 |
|
446 |
def get_forces(self, *args, **kwargs): |
447 |
return self.atoms.get_forces(*args, **kwargs)[self.index] |
448 |
|
449 |
def get_stress(self): |
450 |
return self.atoms.get_stress() |
451 |
|
452 |
def get_stresses(self): |
453 |
return self.atoms.get_stresses()[self.index] |
454 |
|
455 |
def get_masses(self): |
456 |
return self.atoms.get_masses()[self.index] |
457 |
|
458 |
def get_potential_energy(self): |
459 |
"""Calculate potential energy.
|
460 |
|
461 |
Returns the potential energy of the full system.
|
462 |
"""
|
463 |
return self.atoms.get_potential_energy() |
464 |
|
465 |
def get_calculator(self): |
466 |
"""Returns the calculator.
|
467 |
|
468 |
WARNING: The calculator is unaware of this filter, and sees a
|
469 |
different number of atoms.
|
470 |
"""
|
471 |
return self.atoms.get_calculator() |
472 |
|
473 |
def has(self, name): |
474 |
"""Check for existance of array."""
|
475 |
return self.atoms.has(name) |
476 |
|
477 |
def __len__(self): |
478 |
"Return the number of movable atoms."
|
479 |
return self.n |
480 |
|
481 |
def __getitem__(self, i): |
482 |
"Return an atom."
|
483 |
return self.atoms[self.index[i]] |
484 |
|
485 |
|
486 |
class StrainFilter: |
487 |
"""Modify the supercell while keeping the scaled positions fixed.
|
488 |
|
489 |
Presents the strain of the supercell as the generalized positions,
|
490 |
and the global stress tensor (times the volume) as the generalized
|
491 |
force.
|
492 |
|
493 |
This filter can be used to relax the unit cell until the stress is
|
494 |
zero. If MDMin is used for this, the timestep (dt) to be used
|
495 |
depends on the system size. 0.01/x where x is a typical dimension
|
496 |
seems like a good choice.
|
497 |
|
498 |
The stress and strain are presented as 6-vectors, the order of the
|
499 |
components follow the standard engingeering practice: xx, yy, zz,
|
500 |
yz, xz, xy.
|
501 |
|
502 |
"""
|
503 |
def __init__(self, atoms, mask=None): |
504 |
"""Create a filter applying a homogeneous strain to a list of atoms.
|
505 |
|
506 |
The first argument, atoms, is the atoms object.
|
507 |
|
508 |
The optional second argument, mask, is a list of six booleans,
|
509 |
indicating which of the six independent components of the
|
510 |
strain that are allowed to become non-zero. It defaults to
|
511 |
[1,1,1,1,1,1].
|
512 |
|
513 |
"""
|
514 |
|
515 |
self.atoms = atoms
|
516 |
self.strain = np.zeros(6) |
517 |
|
518 |
if mask is None: |
519 |
self.mask = np.ones(6) |
520 |
else:
|
521 |
self.mask = np.array(mask)
|
522 |
|
523 |
self.origcell = atoms.get_cell()
|
524 |
|
525 |
def get_positions(self): |
526 |
return self.strain.reshape((2, 3)) |
527 |
|
528 |
def set_positions(self, new): |
529 |
new = new.ravel() * self.mask
|
530 |
eps = np.array([[1.0 + new[0], 0.5 * new[5], 0.5 * new[4]], |
531 |
[0.5 * new[5], 1.0 + new[1], 0.5 * new[3]], |
532 |
[0.5 * new[4], 0.5 * new[3], 1.0 + new[2]]]) |
533 |
|
534 |
self.atoms.set_cell(np.dot(self.origcell, eps), scale_atoms=True) |
535 |
self.strain[:] = new
|
536 |
|
537 |
def get_forces(self): |
538 |
stress = self.atoms.get_stress()
|
539 |
return -self.atoms.get_volume() * (stress * self.mask).reshape((2, 3)) |
540 |
|
541 |
def get_potential_energy(self): |
542 |
return self.atoms.get_potential_energy() |
543 |
|
544 |
def has(self, x): |
545 |
return self.atoms.has(x) |
546 |
|
547 |
def __len__(self): |
548 |
return 2 |
549 |
|
550 |
class UnitCellFilter: |
551 |
"""Modify the supercell and the atom positions. """
|
552 |
def __init__(self, atoms, mask=None): |
553 |
"""Create a filter that returns the atomic forces and unit
|
554 |
cell stresses together, so they can simultaneously be
|
555 |
minimized.
|
556 |
|
557 |
The first argument, atoms, is the atoms object.
|
558 |
|
559 |
The optional second argument, mask, is a list of booleans,
|
560 |
indicating which of the six independent
|
561 |
components of the strain are relaxed.
|
562 |
1, True = relax to zero
|
563 |
0, False = fixed, ignore this component
|
564 |
|
565 |
use atom Constraints, e.g. FixAtoms, to control relaxation of
|
566 |
the atoms.
|
567 |
|
568 |
#this should be equivalent to the StrainFilter
|
569 |
>>> atoms = Atoms(...)
|
570 |
>>> atoms.set_constraint(FixAtoms(mask=[True for atom in atoms]))
|
571 |
>>> ucf = UCFilter(atoms)
|
572 |
|
573 |
You should not attach this UCFilter object to a
|
574 |
trajectory. Instead, create a trajectory for the atoms, and
|
575 |
attach it to an optimizer like this:
|
576 |
|
577 |
>>> atoms = Atoms(...)
|
578 |
>>> ucf = UCFilter(atoms)
|
579 |
>>> qn = QuasiNewton(ucf)
|
580 |
>>> traj = PickleTrajectory('TiO2.traj','w',atoms)
|
581 |
>>> qn.attach(traj)
|
582 |
>>> qn.run(fmax=0.05)
|
583 |
|
584 |
Helpful conversion table
|
585 |
========================
|
586 |
0.05 eV/A^3 = 8 GPA
|
587 |
0.003 eV/A^3 = 0.48 GPa
|
588 |
0.0006 eV/A^3 = 0.096 GPa
|
589 |
0.0003 eV/A^3 = 0.048 GPa
|
590 |
0.0001 eV/A^3 = 0.02 GPa
|
591 |
"""
|
592 |
|
593 |
self.atoms = atoms
|
594 |
self.strain = np.zeros(6) |
595 |
|
596 |
if mask is None: |
597 |
self.mask = np.ones(6) |
598 |
else:
|
599 |
self.mask = np.array(mask)
|
600 |
|
601 |
self.origcell = atoms.get_cell()
|
602 |
|
603 |
def get_positions(self): |
604 |
'''
|
605 |
this returns an array with shape (natoms+2,3).
|
606 |
|
607 |
the first natoms rows are the positions of the atoms, the last
|
608 |
two rows are the strains associated with the unit cell
|
609 |
'''
|
610 |
|
611 |
atom_positions = self.atoms.get_positions()
|
612 |
strains = self.strain.reshape((2, 3)) |
613 |
|
614 |
natoms = len(self.atoms) |
615 |
all_pos = np.zeros((natoms+2,3),np.float) |
616 |
all_pos[0:natoms,:] = atom_positions
|
617 |
all_pos[natoms:,:] = strains |
618 |
|
619 |
return all_pos
|
620 |
|
621 |
def set_positions(self, new): |
622 |
'''
|
623 |
new is an array with shape (natoms+2,3).
|
624 |
|
625 |
the first natoms rows are the positions of the atoms, the last
|
626 |
two rows are the strains used to change the cell shape.
|
627 |
|
628 |
The atom positions are set first, then the unit cell is
|
629 |
changed keeping the atoms in their scaled positions.
|
630 |
'''
|
631 |
|
632 |
natoms = len(self.atoms) |
633 |
|
634 |
atom_positions = new[0:natoms,:]
|
635 |
self.atoms.set_positions(atom_positions)
|
636 |
|
637 |
new = new[natoms:,:] #this is only the strains
|
638 |
new = new.ravel() * self.mask
|
639 |
eps = np.array([[1.0 + new[0], 0.5 * new[5], 0.5 * new[4]], |
640 |
[0.5 * new[5], 1.0 + new[1], 0.5 * new[3]], |
641 |
[0.5 * new[4], 0.5 * new[3], 1.0 + new[2]]]) |
642 |
|
643 |
self.atoms.set_cell(np.dot(self.origcell, eps), scale_atoms=True) |
644 |
self.strain[:] = new
|
645 |
|
646 |
def get_forces(self,apply_constraint=False): |
647 |
'''
|
648 |
returns an array with shape (natoms+2,3) of the atomic forces
|
649 |
and unit cell stresses.
|
650 |
|
651 |
the first natoms rows are the forces on the atoms, the last
|
652 |
two rows are the stresses on the unit cell, which have been
|
653 |
reshaped to look like "atomic forces". i.e.,
|
654 |
|
655 |
f[-2] = -vol*[sxx,syy,szz]*mask[0:3]
|
656 |
f[-1] = -vol*[syz, sxz, sxy]*mask[3:]
|
657 |
|
658 |
apply_constraint is an argument expected by ase
|
659 |
'''
|
660 |
|
661 |
stress = self.atoms.get_stress()
|
662 |
atom_forces = self.atoms.get_forces()
|
663 |
|
664 |
natoms = len(self.atoms) |
665 |
all_forces = np.zeros((natoms+2,3),np.float) |
666 |
all_forces[0:natoms,:] = atom_forces
|
667 |
|
668 |
vol = self.atoms.get_volume()
|
669 |
stress_forces = -vol * (stress * self.mask).reshape((2, 3)) |
670 |
all_forces[natoms:,:] = stress_forces |
671 |
return all_forces
|
672 |
|
673 |
def get_potential_energy(self): |
674 |
return self.atoms.get_potential_energy() |
675 |
|
676 |
def has(self, x): |
677 |
return self.atoms.has(x) |
678 |
|
679 |
def __len__(self): |
680 |
return (2 + len(self.atoms)) |