Coverage for /builds/ase/ase/ase/optimize/climbfixinternals.py : 98.44%

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
1from numpy.linalg import norm
2from ase.optimize.bfgs import BFGS
3from ase.constraints import FixInternals
6class BFGSClimbFixInternals(BFGS):
7 """Class for transition state search and optimization
9 Climbs the 1D reaction coordinate defined as constrained internal coordinate
10 via the :class:`~ase.constraints.FixInternals` class while minimizing all
11 remaining degrees of freedom.
13 Details: Two optimizers, 'A' and 'B', are applied orthogonal to each other.
14 Optimizer 'A' climbs the constrained coordinate while optimizer 'B'
15 optimizes the remaining degrees of freedom after each climbing step.
16 Optimizer 'A' uses the BFGS algorithm to climb along the projected force of
17 the selected constraint. Optimizer 'B' can be any ASE optimizer
18 (default: BFGS).
20 In combination with other constraints, the order of constraints matters.
21 Generally, the FixInternals constraint should come first in the list of
22 constraints.
23 This has been tested with the :class:`~ase.constraints.FixAtoms` constraint.
25 Inspired by concepts described by P. N. Plessow. [1]_
27 .. [1] Plessow, P. N., Efficient Transition State Optimization of Periodic
28 Structures through Automated Relaxed Potential Energy Surface Scans.
29 J. Chem. Theory Comput. 2018, 14 (2), 981–990.
30 https://doi.org/10.1021/acs.jctc.7b01070.
32 .. note::
33 Convergence is based on 'fmax' of the total forces which is the sum of
34 the projected forces and the forces of the remaining degrees of freedom.
35 This value is logged in the 'logfile'. Optimizer 'B' logs 'fmax' of the
36 remaining degrees of freedom without the projected forces. The projected
37 forces can be inspected using the :meth:`get_projected_forces` method:
39 >>> for _ in dyn.irun():
40 ... projected_forces = dyn.get_projected_forces()
42 Example
43 -------
44 .. literalinclude:: ../../ase/test/optimize/test_climb_fix_internals.py
45 :end-before: # end example for documentation
46 """
47 def __init__(self, atoms, restart=None, logfile='-', trajectory=None,
48 maxstep=None, master=None, alpha=None,
49 climb_coordinate=None,
50 optB=BFGS, optB_kwargs=None, optB_fmax=0.05,
51 optB_fmax_scaling=0.0):
52 """Allowed parameters are similar to the parent class
53 :class:`~ase.optimize.bfgs.BFGS` with the following additions:
55 Parameters
56 ----------
57 climb_coordinate: list
58 Specifies which subconstraint of the
59 :class:`~ase.constraints.FixInternals` constraint is to be climbed.
60 Provide the corresponding nested list of indices
61 (including coefficients in the case of Combo constraints).
62 See the example above.
64 optB: any ASE optimizer, optional
65 Optimizer 'B' for optimization of the remaining degrees of freedom
66 after each climbing step.
68 optB_kwargs: dict, optional
69 Specifies keyword arguments to be passed to optimizer 'B' at its
70 initialization. By default, optimizer 'B' writes a logfile and
71 trajectory (optB_{...}.log, optB_{...}.traj) where {...} is the
72 current value of the ``climb_coordinate``. Set ``logfile`` to '-'
73 for console output. Set ``trajectory`` to 'None' to suppress
74 writing of the trajectory file.
76 optB_fmax: float, optional
77 Specifies the convergence criterion 'fmax' of optimizer 'B'.
79 optB_fmax_scaling: float, optional
80 Scaling factor to dynamically tighten 'fmax' of optimizer 'B' to
81 the value of ``optB_fmax`` when close to convergence.
82 Can speed up the climbing process. The scaling formula is
84 'fmax' = ``optB_fmax`` + ``optB_fmax_scaling``
85 :math:`\\cdot` norm_of_projected_forces
87 The final optimization with optimizer 'B' is
88 performed with ``optB_fmax`` independent of ``optB_fmax_scaling``.
89 """
90 self.targetvalue = None # may be assigned during restart in self.read()
91 super().__init__(atoms, restart=restart, logfile=logfile,
92 trajectory=trajectory, maxstep=maxstep, master=master,
93 alpha=alpha)
95 self.constr2climb = self.get_constr2climb(self.atoms, climb_coordinate)
96 self.targetvalue = self.targetvalue or self.constr2climb.targetvalue
98 self.optB = optB
99 self.optB_kwargs = optB_kwargs or {}
100 self.optB_fmax = optB_fmax
101 self.scaling = optB_fmax_scaling
102 # log optimizer 'B' in logfiles named after current value of constraint
103 self.autolog = 'logfile' not in self.optB_kwargs
104 self.autotraj = 'trajectory' not in self.optB_kwargs
106 def get_constr2climb(self, atoms, climb_coordinate):
107 """Get pointer to the subconstraint that is to be climbed.
108 Identification by its definition via indices (and coefficients)."""
109 constr = self.get_fixinternals(atoms)
110 return constr.get_subconstraint(atoms, climb_coordinate)
112 def get_fixinternals(self, atoms):
113 """Get pointer to the FixInternals constraint on the atoms object."""
114 all_constr_types = list(map(type, atoms.constraints))
115 index = all_constr_types.index(FixInternals) # locate constraint
116 return atoms.constraints[index]
118 def read(self):
119 (self.H, self.pos0, self.forces0, self.maxstep,
120 self.targetvalue) = self.load()
122 def step(self):
123 self.relax_remaining_dof() # optimization with optimizer 'B'
125 pos, dpos = self.pretend2climb() # with optimizer 'A'
126 self.update_positions_and_targetvalue(pos, dpos) # obey other constr.
128 self.dump((self.H, self.pos0, self.forces0, self.maxstep,
129 self.targetvalue))
131 def pretend2climb(self):
132 """Get directions for climbing and climb with optimizer 'A'."""
133 proj_forces = self.get_projected_forces()
134 pos = self.atoms.get_positions()
135 dpos, steplengths = self.prepare_step(pos, proj_forces)
136 dpos = self.determine_step(dpos, steplengths)
137 return pos, dpos
139 def update_positions_and_targetvalue(self, pos, dpos):
140 """Adjust constrained targetvalue of constraint and update positions."""
141 self.constr2climb.adjust_positions(pos, pos + dpos) # update sigma
142 self.targetvalue += self.constr2climb.sigma # climb constraint
143 self.constr2climb.targetvalue = self.targetvalue # adjust positions
144 self.atoms.set_positions(self.atoms.get_positions()) # to targetvalue
146 def relax_remaining_dof(self):
147 """Optimize remaining degrees of freedom with optimizer 'B'."""
148 if self.autolog:
149 self.optB_kwargs['logfile'] = f'optB_{self.targetvalue}.log'
150 if self.autotraj:
151 self.optB_kwargs['trajectory'] = f'optB_{self.targetvalue}.traj'
152 fmax = self.get_scaled_fmax()
153 with self.optB(self.atoms, **self.optB_kwargs) as opt:
154 opt.run(fmax) # optimize with scaled fmax
155 if self.converged() and fmax > self.optB_fmax:
156 # (final) optimization with desired fmax
157 opt.run(self.optB_fmax)
159 def get_scaled_fmax(self):
160 """Return the adaptive 'fmax' based on the estimated distance to the
161 transition state."""
162 return (self.optB_fmax +
163 self.scaling * norm(self.constr2climb.projected_forces))
165 def get_projected_forces(self):
166 """Return the projected forces along the constrained coordinate in
167 uphill direction (negative sign)."""
168 forces = self.constr2climb.projected_forces
169 forces = -forces.reshape(self.atoms.positions.shape)
170 return forces
172 def get_total_forces(self):
173 """Return forces obeying all constraints plus projected forces."""
174 return self.atoms.get_forces() + self.get_projected_forces()
176 def converged(self, forces=None):
177 """Did the optimization converge based on the total forces?"""
178 forces = forces or self.get_total_forces()
179 return super().converged(forces=forces)
181 def log(self, forces=None):
182 forces = forces or self.get_total_forces()
183 super().log(forces=forces)