@@ -34,6 +34,7 @@ def __init__(
3434 self .delta = delta0
3535 self .logfile = logfile
3636 self .trajfile = trajfile
37+ self ._restart = False
3738 if not self .indices :
3839 self ._get_moving_atoms ()
3940 self .hessian = 100 * np .eye (3 * len (self .indices ))
@@ -118,7 +119,18 @@ def _initialize_atoms(self: None) -> tuple[Atoms, np.ndarray, np.ndarray]:
118119 B_init = self .hessian .copy ()
119120 return atoms , idx , B_init
120121
121- def _initialize_run (self : None , atoms : Atoms , idx : list ) -> np .ndarray :
122+ def _initialize_atoms_restart (self : None ) -> tuple [Atoms ,
123+ np .ndarray ,
124+ np .ndarray ]:
125+ atoms = self ._restart_trajectory .copy ()
126+ constraints = self ._restart_trajectory .constraints .copy ()
127+ atoms .set_constraint (constraints )
128+ atoms .calc = copy .deepcopy (self .calculator )
129+ idx = self .indices .copy ()
130+ B_init = self ._restart_trajectory .info ['saddleclimb_hessian' ].copy ()
131+ return atoms , idx , B_init
132+
133+ def _initialize_run (self : None , atoms : Atoms , idx : list ):
122134 traj = Trajectory (self .trajfile , 'w' )
123135 g_init = - self ._get_F (atoms )[idx , :].reshape (- 1 )
124136 E_init = atoms .calc .results ['energy' ]
@@ -128,15 +140,22 @@ def _initialize_run(self: None, atoms: Atoms, idx: list) -> np.ndarray:
128140 self ._log (log_string )
129141 return traj , g_init , E_init
130142
143+ def _initialize_run_restart (self : None , idx : list ):
144+ traj = Trajectory (self .trajfile , 'a' )
145+ g_tot = - self ._restart_trajectory .calc .results ['forces' ]
146+ g = g_tot [idx , :].reshape (- 1 ).copy ()
147+ E = self ._restart_trajectory .calc .results ['energy' ] + 0
148+ Fmax = np .max (np .abs (g ))
149+ return traj , g , E , Fmax
150+
131151 def _get_initial_step (
132152 self : None , idx : list
133153 ) -> tuple [np .ndarray , np .ndarray ]:
134-
135154 self ._pos_f_1D = self .atoms_final .positions [idx , :].reshape (- 1 )
136155 self ._pos_i_1D = self .atoms_initial .positions [idx , :].reshape (- 1 )
137156 dx_1D = self .delta * self .normalize (self ._pos_f_1D - self ._pos_i_1D )
138- dx_init = dx_1D .reshape (- 1 , 3 )
139- return dx_init
157+ dx = dx_1D .reshape (- 1 , 3 )
158+ return dx , dx_1D
140159
141160 def _get_log_string (self , n , E , Fmax ):
142161 n_str = str (n ).ljust (20 )
@@ -154,9 +173,12 @@ def _initialize_logging(self: None):
154173 n_str = 'Iteration' .ljust (20 )
155174 E_str = 'Energy (eV)' .ljust (20 )
156175 F_str = 'Fmax (eV/A)' .ljust (20 )
157- log_string = n_str + E_str + F_str
176+ if self ._restart :
177+ log_string = '\n Restarting:\n ' + n_str + E_str + F_str
178+ else :
179+ log_string = n_str + E_str + F_str
158180 climb = Path (self .logfile )
159- if climb .exists ():
181+ if climb .exists () and not self . _restart :
160182 os .remove (self .logfile )
161183 self ._log (log_string )
162184
@@ -168,13 +190,23 @@ def _get_F(self, atoms):
168190 raise Exception ('forces not able to be computed' )
169191 return f
170192
171- def run (self : None ) -> None :
172- atoms , idx , B = self ._initialize_atoms ()
193+ def climb (self : None , maxsteps = None ) -> None :
173194 self ._initialize_logging ()
174- traj , g , E = self ._initialize_run (atoms , idx )
175- dx = self ._get_initial_step (idx )
176- dx_1D = dx .reshape (- 1 )
177- Fmax , dxi , n = 1 , 0 , 0
195+ if self ._restart :
196+ atoms , idx , B = self ._initialize_atoms_restart ()
197+ traj , g , E , Fmax = self ._initialize_run_restart (idx )
198+ self ._pos_f_1D = self .atoms_final .positions [idx , :].reshape (- 1 )
199+ self ._pos_i_1D = self .atoms_initial .positions [idx , :].reshape (- 1 )
200+ dx_1D = self ._get_step (B , g , atoms .positions [idx , :].reshape (- 1 ))
201+ dx = dx_1D .reshape (- 1 , 3 )
202+ pos_1D = atoms .positions [idx , :].reshape (- 1 )
203+ dxi = LA .norm (self ._pos_i_1D - pos_1D )
204+ n = self ._restart_trajectory .info ['saddleclimb_iterations' ]
205+ else :
206+ atoms , idx , B = self ._initialize_atoms ()
207+ traj , g , E = self ._initialize_run (atoms , idx )
208+ dx , dx_1D = self ._get_initial_step (idx )
209+ Fmax , dxi , n = 1 , 0 , 0
178210 while Fmax > self .fmax or dxi < 0.5 :
179211 atoms .positions [idx , :] += dx
180212 pos_1D = atoms .positions [idx , :].reshape (- 1 )
@@ -190,7 +222,20 @@ def run(self: None) -> None:
190222 n += 1
191223 log_string = self ._get_log_string (n , E , Fmax )
192224 self ._log (log_string )
225+ atoms .info ['saddleclimb_hessian' ] = B .copy ()
226+ atoms .info ['saddleclimb_iterations' ] = n + 0
193227 traj .write (atoms )
228+ if maxsteps and n >= maxsteps :
229+ self ._log ('maxsteps reached, terminating!' )
230+ break
231+ if Fmax < self .fmax and dxi > 0.5 :
232+ self ._log ('Optimization complete!' )
233+
234+ def restart_climb (self , restart_trajectory : Atoms ):
235+ assert 'saddleclimb_hessian' in restart_trajectory .info
236+ self ._restart = True
237+ self ._restart_trajectory = copy .deepcopy (restart_trajectory )
238+ self .climb ()
194239
195240 def normalize (self : None , v : np .ndarray ) -> np .ndarray :
196241 norm = LA .norm (v )
0 commit comments