Statistiques
| Révision :

root / ase / optimize / oldqn.py @ 1

Historique | Voir | Annoter | Télécharger (14,44 ko)

1
# Copyright (C) 2003  CAMP
2
# Please see the accompanying LICENSE file for further information.
3

    
4
"""
5
Quasi-Newton algorithm
6
"""
7

    
8
__docformat__ = 'reStructuredText'
9

    
10
import numpy as np
11
import weakref,time,sys
12

    
13

    
14
def f(lamda,Gbar,b,radius): 
15
        b1 = b - lamda
16
        g = radius**2 - np.dot(Gbar/b1, Gbar/b1)
17
        return g
18

    
19

    
20

    
21
def scale_radius_energy(f,r):
22
        scale = 1.0
23
#       if(r<=0.01):
24
#               return scale
25
        
26
        if f<0.01: scale*=1.4
27
        if f<0.05: scale*=1.4
28
        if f<0.10: scale*=1.4
29
        if f<0.40: scale*=1.4
30

    
31
        if f>0.5: scale *= 1./1.4               
32
        if f>0.7: scale *= 1./1.4               
33
        if f>1.0: scale *= 1./1.4
34

    
35
        return scale
36

    
37
def scale_radius_force(f,r):
38
        scale = 1.0
39
#       if(r<=0.01):
40
#               return scale
41
        g = abs(f -1)
42
        if g<0.01: scale*=1.4
43
        if g<0.05: scale*=1.4
44
        if g<0.10: scale*=1.4
45
        if g<0.40: scale*=1.4
46

    
47
        if g>0.5: scale *= 1./1.4               
48
        if g>0.7: scale *= 1./1.4               
49
        if g>1.0: scale *= 1./1.4
50

    
51
        return scale
52

    
53
def find_lamda(upperlimit,Gbar,b,radius):
54
        lowerlimit = upperlimit
55
        eps = 1e-12
56
        step = 0.1
57
        while  f(lowerlimit,Gbar,b,radius) < 0:
58
                lowerlimit -= step
59
                
60
        converged = False
61

    
62
        while not converged: 
63

    
64
                midt = (upperlimit+lowerlimit)/2.
65
                lamda = midt
66
                fmidt = f(midt,Gbar,b,radius)
67
                fupper = f(upperlimit,Gbar,b,radius)
68
                flower = f(lowerlimit,Gbar,b,radius)
69
        
70
                if fupper*fmidt<0: 
71
                        lowerlimit = midt 
72
                else: 
73
                        upperlimit = midt
74

    
75
                if abs(upperlimit-lowerlimit)<1e-6: 
76
                        converged = True
77

    
78
        return lamda
79

    
80
def get_hessian_inertia(eigenvalues):
81
        # return number of negative modes
82
        n = 0
83
        print 'eigenvalues ',eigenvalues[0],eigenvalues[1],eigenvalues[2]
84
        while eigenvalues[n]<0:
85
                n+=1
86
        return n 
87

    
88

    
89
from numpy.linalg import eigh, solve
90

    
91
from ase.optimize.optimize import Optimizer
92

    
93

    
94

    
95
class GoodOldQuasiNewton(Optimizer):
96

    
97
    def __init__(self, atoms, restart=None, logfile='-', trajectory=None,
98
                 fmax=None, converged=None,
99
                hessianupdate='BFGS',hessian=None,forcemin=True,
100
                verbosity=None,maxradius=None,
101
                diagonal=20.,radius=None,
102
                transitionstate = False):
103
            
104
        Optimizer.__init__(self, atoms, restart, logfile, trajectory)
105

    
106
        self.eps = 1e-12
107
        self.hessianupdate = hessianupdate
108
        self.forcemin = forcemin
109
        self.verbosity = verbosity
110
        self.diagonal = diagonal
111

    
112
        self.atoms = atoms
113

    
114
        n = len(self.atoms) * 3
115
        if radius is None: 
116
                self.radius = 0.05*np.sqrt(n)/10.0
117
        else:
118
                self.radius = radius
119

    
120
        if maxradius is None: 
121
                self.maxradius = 0.5*np.sqrt(n)
122
        else:
123
                self.maxradius = maxradius
124
                
125
        # 0.01 < radius < maxradius
126
        self.radius = max(min( self.radius, self.maxradius ), 0.0001)
127

    
128
        self.transitionstate = transitionstate
129

    
130
        # check if this is a nudged elastic band calculation
131
        if hasattr(atoms,'springconstant'): 
132
                self.forcemin=False
133

    
134
        self.t0 = time.time() 
135

    
136
    def initialize(self):pass
137

    
138
    def write_log(self,text):
139
        if self.logfile is not None:
140
            self.logfile.write(text + '\n')
141
            self.logfile.flush()
142

    
143
    def set_max_radius(self, maxradius):
144
                self.maxradius = maxradius
145
                self.radius = min(self.maxradius, self.radius)
146
                
147
    def set_hessian(self,hessian):
148
        self.hessian = hessian
149

    
150
    def get_hessian(self):
151
        if not hasattr(self,'hessian'): 
152
                self.set_default_hessian() 
153
        return self.hessian
154

    
155
    def set_default_hessian(self): 
156
        # set unit matrix
157
        n = len(self.atoms) * 3
158
        hessian = np.zeros((n,n)) 
159
        for i in range(n): 
160
                        hessian[i][i] = self.diagonal
161
        self.set_hessian(hessian) 
162

    
163
    def read_hessian(self,filename): 
164
        import cPickle
165
        f = open(filename,'r')
166
        self.set_hessian(cPickle.load(f))
167
        f.close()
168

    
169
    def write_hessian(self,filename): 
170
        import cPickle
171
        f = paropen(filename,'w')
172
        cPickle.dump(self.get_hessian(),f)
173
        f.close()
174

    
175
    def write_to_restartfile(self):
176
        import cPickle
177
        f = paropen(self.restartfile,'w')
178
        cPickle.dump((self.oldpos,
179
                      self.oldG,
180
                      self.oldenergy,
181
                      self.radius,
182
                      self.hessian,
183
                      self.energy_estimate),f)
184
        f.close()
185
        
186

    
187

    
188
    def update_hessian(self,pos,G):
189
        import copy
190
        if hasattr(self,'oldG'): 
191
                if self.hessianupdate=='BFGS': 
192
                        self.update_hessian_bfgs(pos,G) 
193
                elif self.hessianupdate== 'Powell': 
194
                        self.update_hessian_powell(pos,G) 
195
                else:           
196
                        self.update_hessian_bofill(pos,G) 
197
        else: 
198
                if not hasattr(self,'hessian'): 
199
                        self.set_default_hessian()
200

    
201
        self.oldpos = copy.copy(pos)
202
        self.oldG = copy.copy(G)
203

    
204
        if self.verbosity: 
205
                print 'hessian ',self.hessian
206

    
207

    
208
        
209
    def update_hessian_bfgs(self,pos,G): 
210
        n = len(self.hessian)
211
        dgrad = G - self.oldG
212
        dpos  = pos - self.oldpos
213
        absdpos = np.sqrt(np.dot(dpos, dpos))
214
        dotg  = np.dot(dgrad,dpos) 
215
        tvec  = np.dot(dpos,self.hessian)
216
        dott  = np.dot(dpos,tvec)
217
        if (abs(dott)>self.eps) and (abs(dotg)>self.eps): 
218
                for i in range(n): 
219
                        for j in range(n): 
220
                                h = dgrad[i]*dgrad[j]/dotg - tvec[i]*tvec[j]/dott
221
                                self.hessian[i][j] += h
222

    
223

    
224

    
225
    def update_hessian_powell(self,pos,G):          
226
        n = len(self.hessian)
227
        dgrad = G - self.oldG
228
        dpos  = pos - self.oldpos
229
        absdpos = np.dot(dpos, dpos)
230
        if absdpos<self.eps: 
231
                return
232

    
233
        dotg  = np.dot(dgrad,dpos) 
234
        tvec  = dgrad-np.dot(dpos,self.hessian)
235
        tvecdot = np.dot(tvec,tvec)
236
        tvecdpos = np.dot(tvec,dpos) 
237
        ddot = tvecdpos/absdpos
238

    
239
        dott  = np.dot(dpos,tvec)
240
        if (abs(dott)>self.eps) and (abs(dotg)>self.eps): 
241
                for i in range(n): 
242
                        for j in range(n): 
243
                                h = tvec[i]*dpos[j] + dpos[i]*tvec[j]-ddot*dpos[i]*dpos[j]
244
                                h *= 1./absdpos
245
                                self.hessian[i][j] += h
246

    
247

    
248
    def update_hessian_bofill(self,pos,G):                                                                     
249
        print 'update Bofill'
250
        n = len(self.hessian)                                                                               
251
        dgrad = G - self.oldG                                                                               
252
        dpos  = pos - self.oldpos                                                                           
253
        absdpos = np.dot(dpos, dpos)                                                                          
254
        if absdpos<self.eps: 
255
                return
256
        dotg  = np.dot(dgrad,dpos)                                                                         
257
        tvec  = dgrad-np.dot(dpos,self.hessian)                                                 
258
        tvecdot = np.dot(tvec,tvec)                                                                        
259
        tvecdpos = np.dot(tvec,dpos)                                                                       
260
        ddot = tvecdpos/absdpos                                                                             
261

    
262
        coef1 = 1. - tvecdpos*tvecdpos/(absdpos*tvecdot)
263
        coef2 = (1. - coef1)*absdpos/tvecdpos
264
        coef3 = coef1*tvecdpos/absdpos
265

    
266
        dott  = np.dot(dpos,tvec)                                                                          
267
        if (abs(dott)>self.eps) and (abs(dotg)>self.eps):                                                   
268
                for i in range(n):                                                                          
269
                        for j in range(n):                                                                  
270
                                h = coef1*(tvec[i]*dpos[j] + dpos[i]*tvec[j])-dpos[i]*dpos[j]*coef3 + coef2*tvec[i]*tvec[j]
271
                                h *= 1./absdpos
272
                                self.hessian[i][j] += h                                                     
273

    
274

    
275

    
276
    def step(self, f):
277
        """ Do one QN step
278
        """
279

    
280
        pos = self.atoms.get_positions().ravel()
281
        G = -self.atoms.get_forces().ravel()
282
        energy = self.atoms.get_potential_energy()
283

    
284

    
285
        self.write_iteration(energy,G)
286

    
287
        if hasattr(self,'oldenergy'):
288

    
289
                self.write_log('energies ' + str(energy) + ' ' + str(self.oldenergy))
290

    
291
                if self.forcemin:
292
                        de = 1e-4
293
                else:
294
                        de = 1e-2
295

    
296
                if self.transitionstate:
297
                        de = 0.2
298

    
299
                if (energy-self.oldenergy)>de:
300
                        self.write_log('reject step')
301
                        self.atoms.set_positions(self.oldpos.reshape((-1, 3)))
302
                        G = self.oldG
303
                        energy = self.oldenergy
304
                        self.radius *= 0.5
305
                else: 
306
                        self.update_hessian(pos,G)
307
                        de = energy - self.oldenergy
308
                        f = 1.0
309
                        if self.forcemin: 
310
                                self.write_log("energy change; actual: %f estimated: %f "%(de,self.energy_estimate))
311
                                if abs(self.energy_estimate)>self.eps: 
312
                                        f = abs((de/self.energy_estimate)-1)
313
                                        self.write_log('Energy prediction factor ' + str(f))
314
                                        # fg = self.get_force_prediction(G)
315
                                        self.radius *= scale_radius_energy(f,self.radius) 
316

    
317
                        else:
318
                                self.write_log("energy change; actual: %f "%(de))
319
                                self.radius*=1.5
320

    
321
                        fg = self.get_force_prediction(G)
322
                        self.write_log("Scale factors %f %f "%(scale_radius_energy(f,self.radius),
323
                                                                scale_radius_force(fg,self.radius)))
324
                        
325
                                   
326
                self.radius = max(min(self.radius,self.maxradius), 0.0001)
327
        else: 
328
                self.update_hessian(pos,G)
329

    
330
        self.write_log("new radius %f "%(self.radius))          
331
        self.oldenergy = energy
332

    
333
        b,V = eigh(self.hessian)
334
        V=V.T.copy()
335
        self.V = V
336

    
337
        # calculate projection of G onto eigenvectors V
338
        Gbar = np.dot(G,np.transpose(V))
339
        
340
        lamdas = self.get_lambdas(b,Gbar)
341

    
342
        D = -Gbar/(b-lamdas) 
343
        n = len(D)
344
        step = np.zeros((n))
345
        for i in range(n): 
346
                step += D[i]*V[i]
347

    
348
        pos = self.atoms.get_positions().ravel()
349
        pos += step
350

    
351
        energy_estimate = self.get_energy_estimate(D,Gbar,b) 
352
        self.energy_estimate = energy_estimate
353
        self.gbar_estimate = self.get_gbar_estimate(D,Gbar,b)
354
        self.old_gbar = Gbar
355

    
356
        self.atoms.set_positions(pos.reshape((-1, 3)))
357

    
358

    
359

    
360

    
361
    def get_energy_estimate(self,D,Gbar,b): 
362

    
363
        de = 0.0
364
        for n in range(len(D)): 
365
                de += D[n]*Gbar[n] + 0.5*D[n]*b[n]*D[n]
366
        return de
367

    
368
    def get_gbar_estimate(self,D,Gbar,b):
369
        gbar_est = (D*b) + Gbar
370
        self.write_log('Abs Gbar estimate ' + str(np.dot(gbar_est,gbar_est)))
371
        return gbar_est
372

    
373
    def get_lambdas(self,b,Gbar):
374
        lamdas = np.zeros((len(b)))
375

    
376
        D = -Gbar/b
377
        #absD = np.sqrt(np.sum(D**2))
378
        absD = np.sqrt(np.dot(D, D))
379

    
380
        eps = 1e-12
381
        nminus = self.get_hessian_inertia(b)
382

    
383
        if absD < self.radius:
384
                if not self.transitionstate:
385
                        self.write_log('Newton step') 
386
                        return lamdas
387
                else:
388
                        if nminus==1:
389
                                self.write_log('Newton step')
390
                                return lamdas
391
                        else:
392
                                self.write_log("Wrong inertia of Hessian matrix: %2.2f %2.2f "%(b[0],b[1]))
393

    
394
        else:
395
                self.write_log("Corrected Newton step: abs(D) = %2.2f "%(absD))
396

    
397
        if not self.transitionstate: 
398
                # upper limit
399
                upperlimit = min(0,b[0])-eps
400
                lowerlimit = upperlimit
401
                lamda = find_lamda(upperlimit,Gbar,b,self.radius)
402
                lamdas += lamda
403
        else:
404
                # upperlimit
405
                upperlimit = min(-b[0],b[1],0)-eps
406
                lamda = find_lamda(upperlimit,Gbar,b,self.radius)
407
                lamdas += lamda
408
                lamdas[0] -= 2*lamda
409
                
410
        return lamdas
411

    
412

    
413

    
414
    def print_hessian(self): 
415
        hessian = self.get_hessian()
416
        n = len(hessian)
417
        for i in range(n): 
418
            for j in range(n): 
419
                print "%2.4f " %(hessian[i][j]),
420
            print " "
421

    
422

    
423
    
424

    
425
    def get_hessian_inertia(self,eigenvalues):
426
        # return number of negative modes
427
        self.write_log("eigenvalues %2.2f %2.2f %2.2f "%(eigenvalues[0],
428
                                                        eigenvalues[1],
429
                                                        eigenvalues[2]))
430
        n = 0
431
        while eigenvalues[n]<0:
432
                n+=1
433
        return n
434

    
435
    def get_force_prediction(self,G):
436
        # return measure of how well the forces are predicted
437
        Gbar = np.dot(G,np.transpose(self.V))
438
        dGbar_actual = Gbar-self.old_gbar
439
        dGbar_predicted = Gbar-self.gbar_estimate
440

    
441
        f = np.dot(dGbar_actual,dGbar_predicted)/np.dot(dGbar_actual,dGbar_actual)
442
        self.write_log('Force prediction factor ' + str(f))
443
        return f
444

    
445
    def write_iteration(self,energy,G):pass