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