Coverage for /builds/ase/ase/ase/optimize/fire2.py: 95.52%
67 statements
« prev ^ index » next coverage.py v7.5.3, created at 2025-08-02 00:12 +0000
« prev ^ index » next coverage.py v7.5.3, created at 2025-08-02 00:12 +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 Optimizer.__init__(self, 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 optimizable = self.optimizable
150 if f is None:
151 f = optimizable.get_gradient().reshape(-1, 3)
153 if self.v is None:
154 self.v = np.zeros(optimizable.ndofs()).reshape(-1, 3)
155 else:
157 vf = np.vdot(f, self.v)
158 if vf > 0.0:
160 self.Nsteps += 1
161 if self.Nsteps > self.Nmin:
162 self.dt = min(self.dt * self.finc, self.dtmax)
163 self.a *= self.fa
164 else:
165 self.Nsteps = 0
166 self.dt = max(self.dt * self.fdec, self.dtmin)
167 self.a = self.astart
169 dr = - 0.5 * self.dt * self.v
170 r = optimizable.get_x().reshape(-1, 3)
171 optimizable.set_x((r + dr).ravel())
172 self.v[:] *= 0.0
174 # euler semi implicit
175 f = optimizable.get_gradient().reshape(-1, 3)
176 self.v += self.dt * f
178 if self.use_abc:
179 self.a = max(self.a, 1e-10)
180 abc_multiplier = 1. / (1. - (1. - self.a)**(self.Nsteps + 1))
181 v_mix = ((1.0 - self.a) * self.v + self.a * f / np.sqrt(
182 np.vdot(f, f)) * np.sqrt(np.vdot(self.v, self.v)))
183 self.v = abc_multiplier * v_mix
185 # Verifying if the maximum distance an atom
186 # moved is larger than maxstep, for ABC-FIRE the check
187 # is done independently for each cartesian direction
188 if np.all(self.v):
189 v_tmp = []
190 for car_dir in range(3):
191 v_i = np.where(np.abs(self.v[:, car_dir]) *
192 self.dt > self.maxstep,
193 (self.maxstep / self.dt) *
194 (self.v[:, car_dir] /
195 np.abs(self.v[:, car_dir])),
196 self.v[:, car_dir])
197 v_tmp.append(v_i)
198 self.v = np.array(v_tmp).T
200 else:
201 self.v = ((1.0 - self.a) * self.v + self.a * f / np.sqrt(
202 np.vdot(f, f)) * np.sqrt(np.vdot(self.v, self.v)))
204 dr = self.dt * self.v
206 # Verifying if the maximum distance an atom moved
207 # step is larger than maxstep, for FIRE2.
208 if not self.use_abc:
209 normdr = np.sqrt(np.vdot(dr, dr))
210 if normdr > self.maxstep:
211 dr = self.maxstep * dr / normdr
213 r = optimizable.get_x().reshape(-1, 3)
214 optimizable.set_x((r + dr).ravel())
216 self.dump((self.v, self.dt))