Coverage for ase / optimize / fire2.py: 96.00%
75 statements
« prev ^ index » next coverage.py v7.13.3, created at 2026-02-04 10:20 +0000
« prev ^ index » next coverage.py v7.13.3, created at 2026-02-04 10:20 +0000
1# fmt: off
3# ######################################
4# Implementation of FIRE2.0 and ABC-FIRE
6# The FIRE2.0 algorithm is implemented using the integrator euler semi implicit
7# as described in the paper:
8# J. Guénolé, W.G. Nöhring, A. Vaid, F. Houllé, Z. Xie, A. Prakash,
9# E. Bitzek,
10# Assessment and optimization of the fast inertial relaxation engine (fire)
11# for energy minimization in atomistic simulations and its
12# implementation in lammps,
13# Comput. Mater. Sci. 175 (2020) 109584.
14# https://doi.org/10.1016/j.commatsci.2020.109584.
15# This implementation does not include N(p<0), initialdelay
16#
17# ABC-Fire is implemented as described in the paper:
18# S. Echeverri Restrepo, P. Andric,
19# ABC-FIRE: Accelerated Bias-Corrected Fast Inertial Relaxation Engine,
20# Comput. Mater. Sci. 218 (2023) 111978.
21# https://doi.org/10.1016/j.commatsci.2022.111978.
22#######################################
24from typing import IO, Callable, Optional, Union
26import numpy as np
28from ase import Atoms
29from ase.optimize.optimize import Optimizer
32class FIRE2(Optimizer):
33 def __init__(
34 self,
35 atoms: Atoms,
36 restart: Optional[str] = None,
37 logfile: Union[IO, str] = '-',
38 trajectory: Optional[str] = None,
39 dt: float = 0.1,
40 maxstep: float = 0.2,
41 dtmax: float = 1.0,
42 dtmin: float = 2e-3,
43 Nmin: int = 20,
44 finc: float = 1.1,
45 fdec: float = 0.5,
46 astart: float = 0.25,
47 fa: float = 0.99,
48 position_reset_callback: Optional[Callable] = None,
49 use_abc: Optional[bool] = False,
50 **kwargs,
51 ):
52 """
54 Parameters
55 ----------
56 atoms: :class:`~ase.Atoms`
57 The Atoms object to relax.
59 restart: str
60 JSON file used to store hessian matrix. If set, file with
61 such a name will be searched and hessian matrix stored will
62 be used, if the file exists.
64 logfile: file object or str
65 If *logfile* is a string, a file with that name will be opened.
66 Use '-' for stdout.
68 trajectory: str
69 Trajectory file used to store optimisation path.
71 dt: float
72 Initial time step. Defualt value is 0.1
74 maxstep: float
75 Used to set the maximum distance an atom can move per
76 iteration (default value is 0.2). Note that for ABC-FIRE the
77 check is done independently for each cartesian direction.
79 dtmax: float
80 Maximum time step. Default value is 1.0
82 dtmin: float
83 Minimum time step. Default value is 2e-3
85 Nmin: int
86 Number of steps to wait after the last time the dot product of
87 the velocity and force is negative (P in The FIRE article) before
88 increasing the time step. Default value is 20.
90 finc: float
91 Factor to increase the time step. Default value is 1.1
93 fdec: float
94 Factor to decrease the time step. Default value is 0.5
96 astart: float
97 Initial value of the parameter a. a is the Coefficient for
98 mixing the velocity and the force. Called alpha in the FIRE article.
99 Default value 0.25.
101 fa: float
102 Factor to decrease the parameter alpha. Default value is 0.99
104 position_reset_callback: function(atoms, r, e, e_last)
105 Function that takes current *atoms* object, an array of position
106 *r* that the optimizer will revert to, current energy *e* and
107 energy of last step *e_last*. This is only called if e > e_last.
109 use_abc: bool
110 If True, the Accelerated Bias-Corrected FIRE algorithm is
111 used (ABC-FIRE).
112 Default value is False.
114 kwargs : dict, optional
115 Extra arguments passed to
116 :class:`~ase.optimize.optimize.Optimizer`.
118 """
119 super().__init__(atoms, restart, logfile, trajectory, **kwargs)
121 self.dt = dt
123 self.Nsteps = 0
125 if maxstep is not None:
126 self.maxstep = maxstep
127 else:
128 self.maxstep = self.defaults["maxstep"]
130 self.dtmax = dtmax
131 self.dtmin = dtmin
132 self.Nmin = Nmin
133 self.finc = finc
134 self.fdec = fdec
135 self.astart = astart
136 self.fa = fa
137 self.a = astart
138 self.position_reset_callback = position_reset_callback
139 self.use_abc = use_abc
141 def initialize(self):
142 self.v = None
144 def read(self):
145 self.v, self.dt = self.load()
147 def step(self, f=None):
148 gradient = -self._get_gradient(f)
149 optimizable = self.optimizable
151 if self.v is None:
152 self.v = np.zeros(optimizable.ndofs())
153 else:
154 vf = np.vdot(gradient, self.v)
155 if vf > 0.0:
156 self.Nsteps += 1
157 if self.Nsteps > self.Nmin:
158 self.dt = min(self.dt * self.finc, self.dtmax)
159 self.a *= self.fa
160 else:
161 self.Nsteps = 0
162 self.dt = max(self.dt * self.fdec, self.dtmin)
163 self.a = self.astart
165 dr = - 0.5 * self.dt * self.v
166 r = optimizable.get_x()
167 optimizable.set_x(r + dr)
168 self.v[:] *= 0.0
170 # euler semi implicit
171 gradient = -optimizable.get_gradient()
172 self.v += self.dt * gradient
174 if self.use_abc:
175 self.a = max(self.a, 1e-10)
176 abc_multiplier = 1. / (1. - (1. - self.a)**(self.Nsteps + 1))
177 v_mix = ((1.0 - self.a) * self.v + self.a * gradient / np.sqrt(
178 np.vdot(gradient, gradient)) * np.sqrt(np.vdot(self.v,
179 self.v)))
180 self.v = abc_multiplier * v_mix
182 def clip_velocity(vel):
183 max_velocity = self.maxstep / self.dt
184 return vel.clip(-max_velocity, max_velocity)
186 def old_clip_velocity(v):
187 # Original implementation of clip_velocity(), can we remove?
188 # Let's remove it in 2026 unless assertion crashes etc.
189 v = v.reshape(-1, 3)
190 v_tmp = []
191 for car_dir in range(3):
192 v_i = np.where(
193 np.abs(v[:, car_dir]) * self.dt > self.maxstep,
194 (self.maxstep / self.dt) *
195 (v[:, car_dir] / np.abs(v[:, car_dir])),
196 v[:, car_dir])
197 v_tmp.append(v_i)
198 return np.array(v_tmp).T.ravel()
200 # Verifying if the maximum distance an atom
201 # moved is larger than maxstep, for ABC-FIRE the check
202 # is done independently for each cartesian direction
203 #
204 # Make sure old and new clip_velocity() agree:
205 v1 = clip_velocity(self.v)
206 if np.all(self.v) and len(self.v) % 3 == 0:
207 v2 = old_clip_velocity(self.v)
208 assert abs(v1 - v2).max() < 1e-12
209 self.v = v1
210 else:
211 self.v = ((1.0 - self.a) * self.v + self.a * gradient / np.sqrt(
212 np.vdot(gradient, gradient)) * np.sqrt(np.vdot(self.v,
213 self.v)))
215 dr = self.dt * self.v
217 # Verifying if the maximum distance an atom moved
218 # step is larger than maxstep, for FIRE2.
219 if not self.use_abc:
220 normdr = np.sqrt(np.vdot(dr, dr))
221 if normdr > self.maxstep:
222 dr = self.maxstep * dr / normdr
224 r = optimizable.get_x()
225 optimizable.set_x(r + dr)
227 self.dump((self.v, self.dt))