Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1import warnings
3import numpy as np
4from numpy.linalg import eigh
6from ase.optimize.optimize import Optimizer
9class BFGS(Optimizer):
10 # default parameters
11 defaults = {**Optimizer.defaults, 'alpha': 70.0}
13 def __init__(self, atoms, restart=None, logfile='-', trajectory=None,
14 maxstep=None, master=None, alpha=None):
15 """BFGS optimizer.
17 Parameters:
19 atoms: Atoms object
20 The Atoms object to relax.
22 restart: string
23 Pickle file used to store hessian matrix. If set, file with
24 such a name will be searched and hessian matrix stored will
25 be used, if the file exists.
27 trajectory: string
28 Pickle file used to store trajectory of atomic movement.
30 logfile: file object or str
31 If *logfile* is a string, a file with that name will be opened.
32 Use '-' for stdout.
34 maxstep: float
35 Used to set the maximum distance an atom can move per
36 iteration (default value is 0.2 Å).
38 master: boolean
39 Defaults to None, which causes only rank 0 to save files. If
40 set to true, this rank will save files.
42 alpha: float
43 Initial guess for the Hessian (curvature of energy surface). A
44 conservative value of 70.0 is the default, but number of needed
45 steps to converge might be less if a lower value is used. However,
46 a lower value also means risk of instability.
47 """
48 if maxstep is None:
49 self.maxstep = self.defaults['maxstep']
50 else:
51 self.maxstep = maxstep
53 if self.maxstep > 1.0:
54 warnings.warn('You are using a *very* large value for '
55 'the maximum step size: %.1f Å' % maxstep)
57 if alpha is None:
58 self.alpha = self.defaults['alpha']
59 else:
60 self.alpha = alpha
62 Optimizer.__init__(self, atoms, restart, logfile, trajectory, master)
64 def todict(self):
65 d = Optimizer.todict(self)
66 if hasattr(self, 'maxstep'):
67 d.update(maxstep=self.maxstep)
68 return d
70 def initialize(self):
71 # initial hessian
72 self.H0 = np.eye(3 * len(self.atoms)) * self.alpha
74 self.H = None
75 self.r0 = None
76 self.f0 = None
78 def read(self):
79 self.H, self.r0, self.f0, self.maxstep = self.load()
81 def step(self, f=None):
82 atoms = self.atoms
84 if f is None:
85 f = atoms.get_forces()
87 r = atoms.get_positions()
88 f = f.reshape(-1)
89 self.update(r.flat, f, self.r0, self.f0)
90 omega, V = eigh(self.H)
92 # FUTURE: Log this properly
93 # # check for negative eigenvalues of the hessian
94 # if any(omega < 0):
95 # n_negative = len(omega[omega < 0])
96 # msg = '\n** BFGS Hessian has {} negative eigenvalues.'.format(
97 # n_negative
98 # )
99 # print(msg, flush=True)
100 # if self.logfile is not None:
101 # self.logfile.write(msg)
102 # self.logfile.flush()
104 dr = np.dot(V, np.dot(f, V) / np.fabs(omega)).reshape((-1, 3))
105 steplengths = (dr**2).sum(1)**0.5
106 dr = self.determine_step(dr, steplengths)
107 atoms.set_positions(r + dr)
108 self.r0 = r.flat.copy()
109 self.f0 = f.copy()
110 self.dump((self.H, self.r0, self.f0, self.maxstep))
112 def determine_step(self, dr, steplengths):
113 """Determine step to take according to maxstep
115 Normalize all steps as the largest step. This way
116 we still move along the eigendirection.
117 """
118 maxsteplength = np.max(steplengths)
119 if maxsteplength >= self.maxstep:
120 scale = self.maxstep / maxsteplength
121 # FUTURE: Log this properly
122 # msg = '\n** scale step by {:.3f} to be shorter than {}'.format(
123 # scale, self.maxstep
124 # )
125 # print(msg, flush=True)
127 dr *= scale
129 return dr
131 def update(self, r, f, r0, f0):
132 if self.H is None:
133 self.H = self.H0
134 return
135 dr = r - r0
137 if np.abs(dr).max() < 1e-7:
138 # Same configuration again (maybe a restart):
139 return
141 df = f - f0
142 a = np.dot(dr, df)
143 dg = np.dot(self.H, dr)
144 b = np.dot(dr, dg)
145 self.H -= np.outer(df, df) / a + np.outer(dg, dg) / b
147 def replay_trajectory(self, traj):
148 """Initialize hessian from old trajectory."""
149 if isinstance(traj, str):
150 from ase.io.trajectory import Trajectory
151 traj = Trajectory(traj, 'r')
152 self.H = None
153 atoms = traj[0]
154 r0 = atoms.get_positions().ravel()
155 f0 = atoms.get_forces().ravel()
156 for atoms in traj:
157 r = atoms.get_positions().ravel()
158 f = atoms.get_forces().ravel()
159 self.update(r, f, r0, f0)
160 r0 = r
161 f0 = f
163 self.r0 = r0
164 self.f0 = f0
167class oldBFGS(BFGS):
168 def determine_step(self, dr, steplengths):
169 """Old BFGS behaviour for scaling step lengths
171 This keeps the behaviour of truncating individual steps. Some might
172 depend of this as some absurd kind of stimulated annealing to find the
173 global minimum.
174 """
175 dr /= np.maximum(steplengths / self.maxstep, 1.0).reshape(-1, 1)
176 return dr