root / ase / neb.py @ 20
Historique | Voir | Annoter | Télécharger (9,43 ko)
1 |
from math import sqrt |
---|---|
2 |
|
3 |
import numpy as np |
4 |
|
5 |
from ase.parallel import world, rank, size |
6 |
from ase.calculators.singlepoint import SinglePointCalculator |
7 |
from ase.io import read |
8 |
|
9 |
class NEB: |
10 |
def __init__(self, images, k=0.1, climb=False, parallel=False): |
11 |
self.images = images
|
12 |
self.k = k
|
13 |
self.climb = climb
|
14 |
self.parallel = parallel
|
15 |
self.natoms = len(images[0]) |
16 |
self.nimages = len(images) |
17 |
self.emax = np.nan
|
18 |
|
19 |
def interpolate(self): |
20 |
pos1 = self.images[0].get_positions() |
21 |
pos2 = self.images[-1].get_positions() |
22 |
d = (pos2 - pos1) / (self.nimages - 1.0) |
23 |
for i in range(1, self.nimages - 1): |
24 |
self.images[i].set_positions(pos1 + i * d)
|
25 |
|
26 |
def get_positions(self): |
27 |
positions = np.empty(((self.nimages - 2) * self.natoms, 3)) |
28 |
n1 = 0
|
29 |
for image in self.images[1:-1]: |
30 |
n2 = n1 + self.natoms
|
31 |
positions[n1:n2] = image.get_positions() |
32 |
n1 = n2 |
33 |
return positions
|
34 |
|
35 |
def set_positions(self, positions): |
36 |
n1 = 0
|
37 |
for image in self.images[1:-1]: |
38 |
n2 = n1 + self.natoms
|
39 |
image.set_positions(positions[n1:n2]) |
40 |
n1 = n2 |
41 |
|
42 |
def get_forces(self): |
43 |
"""Evaluate and return the forces."""
|
44 |
images = self.images
|
45 |
forces = np.empty(((self.nimages - 2), self.natoms, 3)) |
46 |
energies = np.empty(self.nimages - 2) |
47 |
|
48 |
if not self.parallel: |
49 |
# Do all images - one at a time:
|
50 |
for i in range(1, self.nimages - 1): |
51 |
energies[i - 1] = images[i].get_potential_energy()
|
52 |
forces[i - 1] = images[i].get_forces()
|
53 |
else:
|
54 |
# Parallelize over images:
|
55 |
i = rank * (self.nimages - 2) // size + 1 |
56 |
try:
|
57 |
energies[i - 1] = images[i].get_potential_energy()
|
58 |
forces[i - 1] = images[i].get_forces()
|
59 |
except:
|
60 |
# Make sure other images also fail:
|
61 |
error = world.sum(1.0)
|
62 |
raise
|
63 |
else:
|
64 |
error = world.sum(0.0)
|
65 |
if error:
|
66 |
raise RuntimeError('Parallel NEB failed!') |
67 |
|
68 |
for i in range(1, self.nimages - 1): |
69 |
root = (i - 1) * size // (self.nimages - 2) |
70 |
world.broadcast(energies[i - 1:i], root)
|
71 |
world.broadcast(forces[i - 1], root)
|
72 |
|
73 |
imax = 1 + np.argsort(energies)[-1] |
74 |
self.emax = energies[imax - 1] |
75 |
|
76 |
tangent1 = images[1].get_positions() - images[0].get_positions() |
77 |
for i in range(1, self.nimages - 1): |
78 |
tangent2 = (images[i + 1].get_positions() -
|
79 |
images[i].get_positions()) |
80 |
if i < imax:
|
81 |
tangent = tangent2 |
82 |
elif i > imax:
|
83 |
tangent = tangent1 |
84 |
else:
|
85 |
tangent = tangent1 + tangent2 |
86 |
|
87 |
tt = np.vdot(tangent, tangent) |
88 |
f = forces[i - 1]
|
89 |
ft = np.vdot(f, tangent) |
90 |
if i == imax and self.climb: |
91 |
f -= 2 * ft / tt * tangent
|
92 |
else:
|
93 |
f -= ft / tt * tangent |
94 |
f -= (np.vdot(tangent1 - tangent2, tangent) * |
95 |
self.k / tt * tangent)
|
96 |
|
97 |
tangent1 = tangent2 |
98 |
|
99 |
return forces.reshape((-1, 3)) |
100 |
|
101 |
def get_potential_energy(self): |
102 |
return self.emax |
103 |
|
104 |
def __len__(self): |
105 |
return (self.nimages - 2) * self.natoms |
106 |
|
107 |
class SingleCalculatorNEB(NEB): |
108 |
def __init__(self, images, k=0.1, climb=False): |
109 |
if isinstance(images, str): |
110 |
# this is a filename
|
111 |
traj = read(images, '0:')
|
112 |
images = [] |
113 |
for atoms in traj: |
114 |
images.append(atoms) |
115 |
|
116 |
NEB.__init__(self, images, k, climb, False) |
117 |
self.calculators = [None] * self.nimages |
118 |
self.energies_ok = False |
119 |
|
120 |
def interpolate(self, initial=0, final=-1): |
121 |
"""Interpolate linearly between initial and final images."""
|
122 |
if final < 0: |
123 |
final = self.nimages + final
|
124 |
n = final - initial |
125 |
pos1 = self.images[initial].get_positions()
|
126 |
pos2 = self.images[final].get_positions()
|
127 |
d = (pos2 - pos1) / n |
128 |
for i in range(1, n): |
129 |
self.images[initial + i].set_positions(pos1 + i * d)
|
130 |
|
131 |
def refine(self, steps=1, begin=0, end=-1): |
132 |
"""Refine the NEB trajectory."""
|
133 |
if end < 0: |
134 |
end = self.nimages + end
|
135 |
j = begin |
136 |
n = end - begin |
137 |
for i in range(n): |
138 |
for k in range(steps): |
139 |
self.images.insert(j + 1, self.images[j].copy()) |
140 |
self.calculators.insert(j + 1, None) |
141 |
self.nimages = len(self.images) |
142 |
self.interpolate(j, j + steps + 1) |
143 |
j += steps + 1
|
144 |
|
145 |
def set_positions(self, positions): |
146 |
# new positions -> new forces
|
147 |
if self.energies_ok: |
148 |
# restore calculators
|
149 |
self.set_calculators(self.calculators[1:-1]) |
150 |
NEB.set_positions(self, positions)
|
151 |
|
152 |
def get_calculators(self): |
153 |
"""Return the original calculators."""
|
154 |
calculators = [] |
155 |
for i, image in enumerate(self.images): |
156 |
if self.calculators[i] is None: |
157 |
calculators.append(image.get_calculator()) |
158 |
else:
|
159 |
calculators.append(self.calculators[i])
|
160 |
return calculators
|
161 |
|
162 |
def set_calculators(self, calculators): |
163 |
"""Set new calculators to the images."""
|
164 |
self.energies_ok = False |
165 |
|
166 |
if not isinstance(calculators, list): |
167 |
calculators = [calculators] * self.nimages
|
168 |
|
169 |
n = len(calculators)
|
170 |
if n == self.nimages: |
171 |
for i in range(self.nimages): |
172 |
self.images[i].set_calculator(calculators[i])
|
173 |
elif n == self.nimages - 2: |
174 |
for i in range(1, self.nimages -1): |
175 |
self.images[i].set_calculator(calculators[i-1]) |
176 |
else:
|
177 |
raise RuntimeError( |
178 |
'len(calculators)=%d does not fit to len(images)=%d'
|
179 |
% (n, self.nimages))
|
180 |
|
181 |
def get_energies_and_forces(self, all=False): |
182 |
"""Evaluate energies and forces and hide the calculators"""
|
183 |
if self.energies_ok: |
184 |
return
|
185 |
|
186 |
images = self.images
|
187 |
forces = np.zeros(((self.nimages - 2), self.natoms, 3)) |
188 |
energies = np.zeros(self.nimages - 2) |
189 |
self.emax = -1.e32 |
190 |
|
191 |
def calculate_and_hide(i): |
192 |
image = self.images[i]
|
193 |
calc = image.get_calculator() |
194 |
if self.calculators[i] is None: |
195 |
self.calculators[i] = calc
|
196 |
if calc is not None: |
197 |
if not isinstance(calc, SinglePointCalculator): |
198 |
self.images[i].set_calculator(
|
199 |
SinglePointCalculator(image.get_potential_energy(), |
200 |
image.get_forces(), |
201 |
None,
|
202 |
None,
|
203 |
image)) |
204 |
self.emax = min(self.emax, image.get_potential_energy()) |
205 |
|
206 |
if all and self.calculators[0] is None: |
207 |
calculate_and_hide(0)
|
208 |
|
209 |
# Do all images - one at a time:
|
210 |
for i in range(1, self.nimages - 1): |
211 |
calculate_and_hide(i) |
212 |
|
213 |
if all and self.calculators[-1] is None: |
214 |
calculate_and_hide(-1)
|
215 |
|
216 |
self.energies_ok = True |
217 |
|
218 |
def get_forces(self): |
219 |
self.get_energies_and_forces()
|
220 |
return NEB.get_forces(self) |
221 |
|
222 |
def n(self): |
223 |
return self.nimages |
224 |
|
225 |
def write(self, filename): |
226 |
from ase.io.trajectory import PickleTrajectory |
227 |
traj = PickleTrajectory(filename, 'w', self) |
228 |
traj.write() |
229 |
traj.close() |
230 |
|
231 |
def __add__(self, other): |
232 |
for image in other: |
233 |
self.images.append(image)
|
234 |
return self |
235 |
|
236 |
def fit(images): |
237 |
E = [i.get_potential_energy() for i in images] |
238 |
F = [i.get_forces() for i in images] |
239 |
R = [i.get_positions() for i in images] |
240 |
return fit0(E, F, R)
|
241 |
|
242 |
def fit0(E, F, R): |
243 |
E = np.array(E) - E[0]
|
244 |
n = len(E)
|
245 |
Efit = np.empty((n - 1) * 20 + 1) |
246 |
Sfit = np.empty((n - 1) * 20 + 1) |
247 |
|
248 |
s = [0]
|
249 |
for i in range(n - 1): |
250 |
s.append(s[-1] + sqrt(((R[i + 1] - R[i])**2).sum())) |
251 |
|
252 |
lines = [] |
253 |
for i in range(n): |
254 |
if i == 0: |
255 |
d = R[1] - R[0] |
256 |
ds = 0.5 * s[1] |
257 |
elif i == n - 1: |
258 |
d = R[-1] - R[-2] |
259 |
ds = 0.5 * (s[-1] - s[-2]) |
260 |
else:
|
261 |
d = R[i + 1] - R[i - 1] |
262 |
ds = 0.25 * (s[i + 1] - s[i - 1]) |
263 |
|
264 |
d = d / sqrt((d**2).sum())
|
265 |
dEds = -(F[i] * d).sum() |
266 |
x = np.linspace(s[i] - ds, s[i] + ds, 3)
|
267 |
y = E[i] + dEds * (x - s[i]) |
268 |
lines.append((x, y)) |
269 |
|
270 |
if i > 0: |
271 |
s0 = s[i - 1]
|
272 |
s1 = s[i] |
273 |
x = np.linspace(s0, s1, 20, endpoint=False) |
274 |
c = np.linalg.solve(np.array([(1, s0, s0**2, s0**3), |
275 |
(1, s1, s1**2, s1**3), |
276 |
(0, 1, 2 * s0, 3 * s0**2), |
277 |
(0, 1, 2 * s1, 3 * s1**2)]), |
278 |
np.array([E[i - 1], E[i], dEds0, dEds]))
|
279 |
y = c[0] + x * (c[1] + x * (c[2] + x * c[3])) |
280 |
Sfit[(i - 1) * 20:i * 20] = x |
281 |
Efit[(i - 1) * 20:i * 20] = y |
282 |
|
283 |
dEds0 = dEds |
284 |
|
285 |
Sfit[-1] = s[-1] |
286 |
Efit[-1] = E[-1] |
287 |
return s, E, Sfit, Efit, lines
|