Statistiques
| Révision :

root / ase / neb.py @ 11

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