Coverage for /builds/debichem-team/python-ase/ase/optimize/fire2.py: 95.52%
67 statements
« prev ^ index » next coverage.py v7.5.3, created at 2025-03-06 04:00 +0000
« prev ^ index » next coverage.py v7.5.3, created at 2025-03-06 04:00 +0000
1# ######################################
2# Implementation of FIRE2.0 and ABC-FIRE
4# The FIRE2.0 algorithm is implemented using the integrator euler semi implicit
5# as described in the paper:
6# J. Guénolé, W.G. Nöhring, A. Vaid, F. Houllé, Z. Xie, A. Prakash,
7# E. Bitzek,
8# Assessment and optimization of the fast inertial relaxation engine (fire)
9# for energy minimization in atomistic simulations and its
10# implementation in lammps,
11# Comput. Mater. Sci. 175 (2020) 109584.
12# https://doi.org/10.1016/j.commatsci.2020.109584.
13# This implementation does not include N(p<0), initialdelay
14#
15# ABC-Fire is implemented as described in the paper:
16# S. Echeverri Restrepo, P. Andric,
17# ABC-FIRE: Accelerated Bias-Corrected Fast Inertial Relaxation Engine,
18# Comput. Mater. Sci. 218 (2023) 111978.
19# https://doi.org/10.1016/j.commatsci.2022.111978.
20#######################################
22from typing import IO, Callable, Optional, Union
24import numpy as np
26from ase import Atoms
27from ase.optimize.optimize import Optimizer
30class FIRE2(Optimizer):
31 def __init__(
32 self,
33 atoms: Atoms,
34 restart: Optional[str] = None,
35 logfile: Union[IO, str] = '-',
36 trajectory: Optional[str] = None,
37 dt: float = 0.1,
38 maxstep: float = 0.2,
39 dtmax: float = 1.0,
40 dtmin: float = 2e-3,
41 Nmin: int = 20,
42 finc: float = 1.1,
43 fdec: float = 0.5,
44 astart: float = 0.25,
45 fa: float = 0.99,
46 position_reset_callback: Optional[Callable] = None,
47 use_abc: Optional[bool] = False,
48 **kwargs,
49 ):
50 """
52 Parameters
53 ----------
54 atoms: :class:`~ase.Atoms`
55 The Atoms object to relax.
57 restart: str
58 JSON file used to store hessian matrix. If set, file with
59 such a name will be searched and hessian matrix stored will
60 be used, if the file exists.
62 logfile: file object or str
63 If *logfile* is a string, a file with that name will be opened.
64 Use '-' for stdout.
66 trajectory: str
67 Trajectory file used to store optimisation path.
69 dt: float
70 Initial time step. Defualt value is 0.1
72 maxstep: float
73 Used to set the maximum distance an atom can move per
74 iteration (default value is 0.2). Note that for ABC-FIRE the
75 check is done independently for each cartesian direction.
77 dtmax: float
78 Maximum time step. Default value is 1.0
80 dtmin: float
81 Minimum time step. Default value is 2e-3
83 Nmin: int
84 Number of steps to wait after the last time the dot product of
85 the velocity and force is negative (P in The FIRE article) before
86 increasing the time step. Default value is 20.
88 finc: float
89 Factor to increase the time step. Default value is 1.1
91 fdec: float
92 Factor to decrease the time step. Default value is 0.5
94 astart: float
95 Initial value of the parameter a. a is the Coefficient for
96 mixing the velocity and the force. Called alpha in the FIRE article.
97 Default value 0.25.
99 fa: float
100 Factor to decrease the parameter alpha. Default value is 0.99
102 position_reset_callback: function(atoms, r, e, e_last)
103 Function that takes current *atoms* object, an array of position
104 *r* that the optimizer will revert to, current energy *e* and
105 energy of last step *e_last*. This is only called if e > e_last.
107 use_abc: bool
108 If True, the Accelerated Bias-Corrected FIRE algorithm is
109 used (ABC-FIRE).
110 Default value is False.
112 kwargs : dict, optional
113 Extra arguments passed to
114 :class:`~ase.optimize.optimize.Optimizer`.
116 """
117 Optimizer.__init__(self, atoms, restart, logfile, trajectory, **kwargs)
119 self.dt = dt
121 self.Nsteps = 0
123 if maxstep is not None:
124 self.maxstep = maxstep
125 else:
126 self.maxstep = self.defaults["maxstep"]
128 self.dtmax = dtmax
129 self.dtmin = dtmin
130 self.Nmin = Nmin
131 self.finc = finc
132 self.fdec = fdec
133 self.astart = astart
134 self.fa = fa
135 self.a = astart
136 self.position_reset_callback = position_reset_callback
137 self.use_abc = use_abc
139 def initialize(self):
140 self.v = None
142 def read(self):
143 self.v, self.dt = self.load()
145 def step(self, f=None):
146 optimizable = self.optimizable
148 if f is None:
149 f = optimizable.get_forces()
151 if self.v is None:
152 self.v = np.zeros((len(optimizable), 3))
153 else:
155 vf = np.vdot(f, self.v)
156 if vf > 0.0:
158 self.Nsteps += 1
159 if self.Nsteps > self.Nmin:
160 self.dt = min(self.dt * self.finc, self.dtmax)
161 self.a *= self.fa
162 else:
163 self.Nsteps = 0
164 self.dt = max(self.dt * self.fdec, self.dtmin)
165 self.a = self.astart
167 dr = - 0.5 * self.dt * self.v
168 r = optimizable.get_positions()
169 optimizable.set_positions(r + dr)
170 self.v[:] *= 0.0
172 # euler semi implicit
173 f = optimizable.get_forces()
174 self.v += self.dt * f
176 if self.use_abc:
177 self.a = max(self.a, 1e-10)
178 abc_multiplier = 1. / (1. - (1. - self.a)**(self.Nsteps + 1))
179 v_mix = ((1.0 - self.a) * self.v + self.a * f / np.sqrt(
180 np.vdot(f, f)) * np.sqrt(np.vdot(self.v, self.v)))
181 self.v = abc_multiplier * v_mix
183 # Verifying if the maximum distance an atom
184 # moved is larger than maxstep, for ABC-FIRE the check
185 # is done independently for each cartesian direction
186 if np.all(self.v):
187 v_tmp = []
188 for car_dir in range(3):
189 v_i = np.where(np.abs(self.v[:, car_dir]) *
190 self.dt > self.maxstep,
191 (self.maxstep / self.dt) *
192 (self.v[:, car_dir] /
193 np.abs(self.v[:, car_dir])),
194 self.v[:, car_dir])
195 v_tmp.append(v_i)
196 self.v = np.array(v_tmp).T
198 else:
199 self.v = ((1.0 - self.a) * self.v + self.a * f / np.sqrt(
200 np.vdot(f, f)) * np.sqrt(np.vdot(self.v, self.v)))
202 dr = self.dt * self.v
204 # Verifying if the maximum distance an atom moved
205 # step is larger than maxstep, for FIRE2.
206 if not self.use_abc:
207 normdr = np.sqrt(np.vdot(dr, dr))
208 if normdr > self.maxstep:
209 dr = self.maxstep * dr / normdr
211 r = optimizable.get_positions()
212 optimizable.set_positions(r + dr)
214 self.dump((self.v, self.dt))