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 |