Coverage for /builds/ase/ase/ase/optimize/bfgs.py : 77.22%

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 self.maxstep = maxstep
49 if self.maxstep is None:
50 self.maxstep = self.defaults['maxstep']
52 if self.maxstep > 1.0:
53 warnings.warn('You are using a *very* large value for '
54 'the maximum step size: %.1f Å' % maxstep)
56 self.alpha = alpha
57 if self.alpha is None:
58 self.alpha = self.defaults['alpha']
60 Optimizer.__init__(self, atoms, restart, logfile, trajectory, master)
62 def initialize(self):
63 # initial hessian
64 self.H0 = np.eye(3 * len(self.atoms)) * self.alpha
66 self.H = None
67 self.pos0 = None
68 self.forces0 = None
70 def read(self):
71 self.H, self.pos0, self.forces0, self.maxstep = self.load()
73 def step(self, forces=None):
74 atoms = self.atoms
76 if forces is None:
77 forces = atoms.get_forces()
79 pos = atoms.get_positions()
80 dpos, steplengths = self.prepare_step(pos, forces)
81 dpos = self.determine_step(dpos, steplengths)
82 atoms.set_positions(pos + dpos)
83 self.dump((self.H, self.pos0, self.forces0, self.maxstep))
85 def prepare_step(self, pos, forces):
86 forces = forces.reshape(-1)
87 self.update(pos.flat, forces, self.pos0, self.forces0)
88 omega, V = eigh(self.H)
90 # FUTURE: Log this properly
91 # # check for negative eigenvalues of the hessian
92 # if any(omega < 0):
93 # n_negative = len(omega[omega < 0])
94 # msg = '\n** BFGS Hessian has {} negative eigenvalues.'.format(
95 # n_negative
96 # )
97 # print(msg, flush=True)
98 # if self.logfile is not None:
99 # self.logfile.write(msg)
100 # self.logfile.flush()
102 dpos = np.dot(V, np.dot(forces, V) / np.fabs(omega)).reshape((-1, 3))
103 steplengths = (dpos**2).sum(1)**0.5
104 self.pos0 = pos.flat.copy()
105 self.forces0 = forces.copy()
106 return dpos, steplengths
108 def determine_step(self, dpos, steplengths):
109 """Determine step to take according to maxstep
111 Normalize all steps as the largest step. This way
112 we still move along the eigendirection.
113 """
114 maxsteplength = np.max(steplengths)
115 if maxsteplength >= self.maxstep:
116 scale = self.maxstep / maxsteplength
117 # FUTURE: Log this properly
118 # msg = '\n** scale step by {:.3f} to be shorter than {}'.format(
119 # scale, self.maxstep
120 # )
121 # print(msg, flush=True)
123 dpos *= scale
124 return dpos
126 def update(self, pos, forces, pos0, forces0):
127 if self.H is None:
128 self.H = self.H0
129 return
130 dpos = pos - pos0
132 if np.abs(dpos).max() < 1e-7:
133 # Same configuration again (maybe a restart):
134 return
136 dforces = forces - forces0
137 a = np.dot(dpos, dforces)
138 dg = np.dot(self.H, dpos)
139 b = np.dot(dpos, dg)
140 self.H -= np.outer(dforces, dforces) / a + np.outer(dg, dg) / b
142 def replay_trajectory(self, traj):
143 """Initialize hessian from old trajectory."""
144 if isinstance(traj, str):
145 from ase.io.trajectory import Trajectory
146 traj = Trajectory(traj, 'r')
147 self.H = None
148 atoms = traj[0]
149 pos0 = atoms.get_positions().ravel()
150 forces0 = atoms.get_forces().ravel()
151 for atoms in traj:
152 pos = atoms.get_positions().ravel()
153 forces = atoms.get_forces().ravel()
154 self.update(pos, forces, pos0, forces0)
155 pos0 = pos
156 forces0 = forces
158 self.pos0 = pos0
159 self.forces0 = forces0
162class oldBFGS(BFGS):
163 def determine_step(self, dpos, steplengths):
164 """Old BFGS behaviour for scaling step lengths
166 This keeps the behaviour of truncating individual steps. Some might
167 depend of this as some absurd kind of stimulated annealing to find the
168 global minimum.
169 """
170 dpos /= np.maximum(steplengths / self.maxstep, 1.0).reshape(-1, 1)
171 return dpos