Optimisers

Contents

Optimisers#

class autode.opt.optimisers.base.BaseOptimiser#

Base abstract class for an optimiser

abstract property converged: bool#

Has this optimisation converged

property final_coordinates#
abstract property last_energy_change: PotentialEnergy#

The energy change on between the final two optimisation cycles

run(*args: Any, **kwargs: Any) None#
save(filename: str) None#
class autode.opt.optimisers.base.ExternalOptimiser#
abstract property converged: bool#

Has this optimisation has converged

abstract property last_energy_change: PotentialEnergy#

The final energy change in this optimisation

class autode.opt.optimisers.base.NDOptimiser(maxiter: int, gtol: GradientRMS, etol: PotentialEnergy, coords: OptCoordinates | None = None, **kwargs)#

Abstract base class for an optimiser in N-dimensions

__init__(maxiter: int, gtol: GradientRMS, etol: PotentialEnergy, coords: OptCoordinates | None = None, **kwargs)#

Geometry optimiser. Signature follows that in scipy.minimize so species and method are keyword arguments. Converged when both energy and gradient criteria are met.

Parameters:

See also

Optimiser

property converged: bool#

Is this optimisation converged? Must be converged based on both energy and gradient tolerance.

property etol: PotentialEnergy#

Energy tolerance between two consecutive steps of the optimisation

classmethod from_file(filename: str) NDOptimiser#

Create an optimiser from a file i.e. reload a saved state

property gtol: GradientRMS#

Gradient tolerance on |∇E| i.e. the root mean square of each component

classmethod optimise(species: Species, method: Method, n_cores: int | None = None, coords: ~autode.opt.coordinates.base.OptCoordinates | None = None, maxiter: int = 100, gtol: ~typing.Any = RMS(∇E)(0.001 Ha(Å)^-1), etol: ~typing.Any = Energy(0.0001 Ha), **kwargs) None#

Convenience function for constructing and running an optimiser

Parameters:
  • method (autode.methods.Method) –

  • maxiter (int) – Maximum number of iteration to perform

  • gtol (float | autode.values.GradientNorm) – Tolerance on RMS(|∇E|) i.e. the root mean square of the gradient components. If a float then assume units of Ha Å^-1

  • etol (float | autode.values.PotentialEnergy) – Tolerance on |∆E| between two consecutive iterations of the optimiser

  • coords (OptCoordinates | None) – Coordinates to optimise in

  • n_cores (int | None) – Number of cores to run energy/gradient/hessian evaluations. If None then use ade.Config.n_cores

  • kwargs (Any) – Additional keyword arguments to pass to the constructor

Raises:

(ValueError | RuntimeError)

plot_optimisation(filename: str | None = None, plot_energy: bool = True, plot_rms_grad: bool = True) None#

Draw the plot of the energies and/or rms_gradients of the optimisation so far

Parameters:
  • plot_energy (bool) – Whether to plot energy

  • plot_rms_grad (bool) – Whether to plot RMS of gradient

print_geometries(filename: str | None = None) None#

Writes the trajectory of the optimiser in .xyz format

Parameters:

filename (str|None) – Name of the trajectory file (xyz), if not given, generates name from species

save(filename: str) None#

Save the entire state of the optimiser to a file

class autode.opt.optimisers.base.NullOptimiser#

An optimiser that does nothing

property converged: bool#

Has this optimisation converged

property final_coordinates#
property last_energy_change: PotentialEnergy#

The energy change on between the final two optimisation cycles

run(**kwargs: Any) None#
save(filename: str) None#
class autode.opt.optimisers.base.Optimiser(maxiter: int, coords: OptCoordinates | None = None, callback: Callable | None = None, callback_kwargs: dict | None = None)#

Abstract base class for an optimiser

__init__(maxiter: int, coords: OptCoordinates | None = None, callback: Callable | None = None, callback_kwargs: dict | None = None)#

Optimiser

e.g. CartesianCoordinates. If None then will initialise the coordinates from _species

Parameters:
  • callback – Function that will be called after every step. First called after initialisation and before the first step. Takes the current coordinates (which have energy (e), gradient (g) and hessian (h) attributes) as the only positional argument

  • callback_kwargs – Keyword arguments to pass to the callback function

abstract _step() None#

Take a step with this optimiser. Should only act on self._coords using the gradient (self._coords.g) and hessians (self._coords.h)

abstract property converged: bool#

Has this optimisation converged

property final_coordinates: OptCoordinates | None#
property iteration: int#

Iteration of the optimiser, which is equal to the length of the history minus one, for zero indexing.

property last_energy_change: PotentialEnergy#

Last ∆E found in this

abstract classmethod optimise(species: Species, method: Method, n_cores: int | None = None, coords: OptCoordinates | None = None, **kwargs) None#

Optimise a species using a method

>>> import autode as ade
>>> mol = ade.Molecule(smiles='C')
>>> Optimiser.optimise(mol,method=ade.methods.ORCA())
run(species: Species, method: Method, n_cores: int | None = None) None#

Run the optimiser. Updates species.atoms and species.energy

Calculations will use method.keywords.grad for gradient calculations

Parameters:

n_cores – Number of cores to use for calculations. If None then use autode.Config.n_cores

class autode.opt.optimisers.base.OptimiserHistory(initlist=None)#

Sequential history of coordinates

property contains_energy_rise: bool#

Does this history contain a ‘well’ in the energy?:

|
E | —– / <– Does contain a rise in the energy
/
|_________________

Iteration

property final: OptCoordinates#

Last set of coordinates

property minimum: OptCoordinates#

Minimum energy coordinates in the history

property penultimate: OptCoordinates#

Last but one set of coordinates (the penultimate set)

print_geometries(species: Species, filename: str) None#

Print geometries from this history of coordinates as a .xyz trajectory file

Parameters:
  • species – The Species for which this coordinate history is generated

  • filename – Name of file




class autode.opt.optimisers.rfo.RFOptimiser(*args, init_alpha: Distance | float = 0.1, **kwargs)#

Rational function optimisation in delocalised internal coordinates

__init__(*args, init_alpha: Distance | float = 0.1, **kwargs)#

Rational function optimiser (RFO) using a maximum step size of alpha

Parameters:
  • args – Additional arguments for NDOptimiser

  • kwargs – Additional keywords arguments for NDOptimiser

See also

NDOptimiser

_step() None#

RFO step




Partitioned rational function optimisation

class autode.opt.optimisers.prfo.PRFOptimiser(init_alpha: Distance | float = 0.05, recalc_hessian_every: int = 10, *args, **kwargs)#
__init__(init_alpha: Distance | float = 0.05, recalc_hessian_every: int = 10, *args, **kwargs)#

Partitioned rational function optimiser (PRFO) using a maximum step size of alpha trying to maximise along a mode while minimising along all others to locate a transition state (TS)

the most imaginary mode (i.e. most negative eigenvalue)

See also

RFOOptimiser

_step() None#

Partitioned rational function step

property should_calculate_hessian: bool#

Should an explicit Hessian calculation be performed?







class autode.opt.optimisers.bfgs.BFGSOptimiser(maxiter: int, gtol: ~autode.values.GradientRMS, etol: ~autode.values.PotentialEnergy, init_alpha: float = 1.0, line_search_type: ~typing.Type[~autode.opt.optimisers.line_search.LineSearchOptimiser] = <class 'autode.opt.optimisers.line_search.ArmijoLineSearch'>, **kwargs)#
__init__(maxiter: int, gtol: ~autode.values.GradientRMS, etol: ~autode.values.PotentialEnergy, init_alpha: float = 1.0, line_search_type: ~typing.Type[~autode.opt.optimisers.line_search.LineSearchOptimiser] = <class 'autode.opt.optimisers.line_search.ArmijoLineSearch'>, **kwargs)#

Broyden–Fletcher–Goldfarb–Shanno optimiser. Implementation taken from: https://tinyurl.com/526yymsw

See also

NDOptimiser

_step() None#

Perform a BFGS step. Requires an initial guess of the Hessian matrix i.e. (self._coords.h must be defined). Steps follow:

1. Determine the inverse Hessian: h_inv

  1. Determine the search direction with:

\[\boldsymbol{p}_k = - H_k^{-1} \nabla E\]

where H is the Hessian matrix, p is the search direction and \(\nabla E\) is the gradient of the energy with respect to the coordinates. On the first iteration \(H_0\) is either the true or exact Hessian.

  1. Performing a (in)exact line search to obtain a suitable step size

\[\alpha_k = \text{arg min} E(X_k + \alpha \boldsymbol{p}_k)\]

and setting \(s_k = \alpha \boldsymbol{p}_k\), and updating the positions accordingly (\(X_{k+1} = X_{k} + s_k\))




class autode.opt.optimisers.line_search.ArmijoLineSearch(maxiter: int = 10, direction: ndarray | None = None, beta: float = 0.1, tau: float = 0.5, init_alpha: float = 1.0, coords: OptCoordinates | None = None)#
__init__(maxiter: int = 10, direction: ndarray | None = None, beta: float = 0.1, tau: float = 0.5, init_alpha: float = 1.0, coords: OptCoordinates | None = None)#

Backtracking line search by Armijo. Reduces the step size iteratively until the convergence condition is satisfied

[1] L. Armijo. Pacific J. Math. 16, 1966, 1. DOI:10.2140/pjm.1966.16.1.

Keyword Arguments:
  • direction (np.ndarray) – Direction to search along

  • beta (float) – β parameter in the line search

  • tau (float) – τ parameter. Multiplicative factor when reducing the step size.

  • init_alpha (float) – α_0 parameter. Initial value of the step size.

_step() None#

Take a step in the line search

property converged: bool#

Is the line search converged? Defined by the Wolfe condition

\[f(x + \alpha^{(l)} p_k) \le f(x) + \alpha^{(l)} \beta g\cdot p\]

where α is the step size at the current iteration (denoted by l) and β is a variable parameter. The search direction p, gradient are defined for the initial point only. See: https://en.wikipedia.org/wiki/Wolfe_conditions

class autode.opt.optimisers.line_search.LineSearchOptimiser(maxiter: int = 10, direction: ndarray | None = None, coords: OptCoordinates | None = None, init_alpha: float = 1.0)#

Optimiser for a 1D line search in a direction

__init__(maxiter: int = 10, direction: ndarray | None = None, coords: OptCoordinates | None = None, init_alpha: float = 1.0)#

Line search optimiser

Keyword Arguments:
  • direction (np.ndarray | None) – Direction which to move the coordinates. Shape must be broadcastable to the coordinates. If None then will guess a sensible direction

  • coords (autode.opt.coordinates.OptCoordinates | None) – Initial coordinates. If None then they will be initialised from the species at runtime

  • init_alpha (float) – Initial step size

property minimum_e_coords: OptCoordinates | None#

Minimum energy coordinates

classmethod optimise(species: Species, method: Method, n_cores: int | None = None, coords: OptCoordinates | None = None, direction: ndarray | None = None, maxiter: int = 5, **kwargs: Any) None#

Optimise a species along a single direction. If the direction is unspecified then guess the direction as just the steepest decent direction.

class autode.opt.optimisers.line_search.NullLineSearch(maxiter: int = 10, direction: ndarray | None = None, coords: OptCoordinates | None = None, init_alpha: float = 1.0)#

A dummy line search

\[\alpha = \alpha_\text{init}\]
_step() None#

No step required in a null line search

property converged: bool#

A null line search is converged on the first iteration

property minimum_e_coords: OptCoordinates | None#

Minimum energy coordinates are defined to be the true step

class autode.opt.optimisers.line_search.SArmijoLineSearch(maxiter: int = 10, direction: ndarray | None = None, beta: float = 0.1, tau: float = 0.5, init_alpha: float = 1.0, coords: OptCoordinates | None = None)#

Speculative Armijo line search

_step() None#

Take a step in the speculative line search. If the energy is monotonic decreasing in the search then take steps that of the form

\[\alpha = \tau \alpha_\text{init}\]

where \(\tau > 1\). But as soon as the energy rises then switch to \(\tau_{k+1} = 1/\tau_{k}\)

property converged: bool#

Is the line search converged? For the speculative search to be converged requires at least one iteration, there to be a well in the search and that the Wolfe condition is satisfied.




Trust region methods for performing optimisations, in contrast to line search methods the direction within the 1D problem is optimised rather than the distance in a particular direction. The sub-problem is:

\[\min(m_k(p)) = E_k + g_k^T p + \frac{1}{2}p^T H_k p \quad : \quad ||p|| \le \alpha_k\]

where \(p\) is the search direction, \(E_k\) is the energy at an iteration \(k\), \(g_k\) is the gradient and \(H\) is the Hessian and \(alpha_k\) is the trust radius at step k. Notation follows https://optimization.mccormick.northwestern.edu/index.php/Trust-region_methods with \(\Delta \equiv \alpha\)

class autode.opt.optimisers.trust_region.CGSteihaugTROptimiser(*args, epsilon: float = 0.001, **kwargs)#

Conjugate Gradient Steihaung’s Method

__init__(*args, epsilon: float = 0.001, **kwargs)#

CG trust region optimiser

Parameters:

**kwargs – Keyword arguments to pass to TrustRegionOptimiser

_solve_subproblem() None#

Solve the subproblem for a direction

coordinate_type#

alias of CartesianCoordinates

class autode.opt.optimisers.trust_region.CauchyTROptimiser(maxiter: int, gtol: GradientRMS, etol: PotentialEnergy, trust_radius: float, coords: OptCoordinates | None = None, max_trust_radius: float | None = None, eta_1: float = 0.1, eta_2: float = 0.25, eta_3: float = 0.75, t_1: float = 0.25, t_2: float = 2.0, **kwargs)#

Most simple trust-radius optimiser, solving the subproblem with a cauchy point calculation

_solve_subproblem() None#

Solve for the optimum direction by a Cauchy point calculation

\[\begin{split}\tau = \begin{cases} 1 \qquad \text{ if } g^T H g <= 0\\ \min\left( \frac{|g|^3}{\alpha g^T H g}, 1\right) \qquad \text{otherwise} \end{cases}\end{split}\]

and

\[p = -\tau \frac{\alpha}{|g|} g\]
property tau: float#

Calculate τ

class autode.opt.optimisers.trust_region.DoglegTROptimiser(maxiter: int, gtol: GradientRMS, etol: PotentialEnergy, trust_radius: float, coords: OptCoordinates | None = None, max_trust_radius: float | None = None, eta_1: float = 0.1, eta_2: float = 0.25, eta_3: float = 0.75, t_1: float = 0.25, t_2: float = 2.0, **kwargs)#

Dogleg Method for solving the subproblem

_solve_subproblem() None#

Solve the subproblem to generate a dogleg step

class autode.opt.optimisers.trust_region.TrustRegionOptimiser(maxiter: int, gtol: GradientRMS, etol: PotentialEnergy, trust_radius: float, coords: OptCoordinates | None = None, max_trust_radius: float | None = None, eta_1: float = 0.1, eta_2: float = 0.25, eta_3: float = 0.75, t_1: float = 0.25, t_2: float = 2.0, **kwargs)#
__init__(maxiter: int, gtol: GradientRMS, etol: PotentialEnergy, trust_radius: float, coords: OptCoordinates | None = None, max_trust_radius: float | None = None, eta_1: float = 0.1, eta_2: float = 0.25, eta_3: float = 0.75, t_1: float = 0.25, t_2: float = 2.0, **kwargs)#

Trust radius optimiser

abstract _solve_subproblem() None#

Solve the TR ‘subproblem’ for the ideal step to use

classmethod optimise(species: Species, method: Method, n_cores: int | None = None, coords: OptCoordinates | None = None, maxiter: int = 5, gtol: ~autode.values.GradientRMS = RMS(∇E)(0.001 Ha(Å)^-1), etol: ~autode.values.PotentialEnergy = Energy(0.0001 Ha), trust_radius: float = 0.2, **kwargs) None#

Construct and optimiser using a trust region optimiser

property rho: float#

Calculate ρ, the ratio of the actual and predicted reductions for the previous step




class autode.opt.optimisers.hessian_update.BFGSDampedUpdate(min_eigenvalue: float = 1e-05, **kwargs)#

Powell damped BFGS update that ensures reasonable conditioning with the ‘positive definite’ conditions still imposed

property _updated_h: ndarray#

435–459 (10.1007/s12532-016-0101-2)

Type:

Powell damped BFGS from

Type:

Math. Prog. Comp. (2016) 8

class autode.opt.optimisers.hessian_update.BFGSPDUpdate(min_eigenvalue: float = 1e-05, **kwargs)#

BFGS update while ensuring positive definiteness

__init__(min_eigenvalue: float = 1e-05, **kwargs)#

Hessian updater

Keyword Arguments:
  • h_inv (np.ndarray) – Inverse Hessian (\(H^{-1}\)), shape = (N, N)

  • s (np.ndarray) – Coordinate shift. \(s = R_{i+1} - R_i\)

  • y (np.ndarray) – Gradient shift. \(y = \nabla E_{i+1} - \nabla E_i\)

  • subspace_idxs (list(int)) – Indexes of the components of the hessian to update

property conditions_met: bool#

Are all the conditions met to update the Hessian

class autode.opt.optimisers.hessian_update.BFGSSR1Update(**kwargs)#

Interpolates between BFGS and SR1 update in a fashion similar to Bofill updates, but suited for minimisations. Proposed by Farkas and Schlegel in J. Chem. Phys., 111, 1999, 10806

property _updated_h: ndarray#

Hybrid BFGS and SR1 update. The mixing parameter phi is defined as the square root of the (1 - phi_Bofill) used in Bofill update.

Returns:

The updated hessian

Return type:

(np.ndarray)

property _updated_h_inv: ndarray#

For BFGS-SR1 update, only hessian is available

property conditions_met: bool#

No conditions need to be satisfied for BFGS-SR1 update

class autode.opt.optimisers.hessian_update.BFGSUpdate(**kwargs)#
property _updated_h: ndarray#

Update the Hessian with a BFGS like update

\[H_{new} = H + \frac{y y^T}{y^T s} - \frac{H s s^T H} {s^T H s}\]
property _updated_h_inv#

Sherman–Morrison inverse matrix update

\[H_{new}^{-1} = H^{-1} + \frac{(s^Ty + y^T H^{-1} y) s^T s} {s^T y} - \frac{H^{-1} y s^T + s y^T H^{-1}} {s^T y}\]

where \(s = x_{l} - x_{l-1},\; \boldsymbol{y} = \nabla E_l - \nabla E_{l-1}\).

property conditions_met: bool#

BFGS update must meet the secant condition

class autode.opt.optimisers.hessian_update.BofillUpdate(**kwargs)#

Hessian update strategy suggested by Bofill[2] with notation taken from ref. [1].

[1] V. Bakken, T. Helgaker, JCP, 117, 9160, 2002 [2] J. M. Bofill, J. Comput. Chem., 15, 1, 1994

property _updated_h: ndarray#

Bofill Hessian update, interpolating between MS and PBS update strategies. Follows ref. [1] where the notation is

\[ \begin{align}\begin{aligned}h = \boldsymbol{G}_{i-1}\\y = \Delta\boldsymbol{g} = \boldsymbol{g}_i - \boldsymbol{g}_{i-1}\\s = \Delta\boldsymbol{x} = \boldsymbol{x}_i - \boldsymbol{x}_{i-1}\end{aligned}\end{align} \]
property _updated_h_inv: ndarray#

Updated inverse Hessian is available only from the updated H

property conditions_met: bool#

No conditions are need to be satisfied to perform a Bofill update, apart from that on the shapes of the vectors

min_update_tol = 1e-06#
class autode.opt.optimisers.hessian_update.FlowchartUpdate(**kwargs)#

A hybrid update scheme combining BFGS, SR1 and PSB Hessian update formulae. Proposed in A. B. Birkholz and H. B. Schlegel in Theor. Chem. Acc., 135 (84), 2016. This implementation is slightly modified.

property _updated_h: ndarray#

Flowchart (or FlowPSB) Hessian update scheme, that dynamically switches between BFGS and SR1 depending on some criteria.

Alternatively switches to PSB update as a fallback if none of the criteria are satisfied. Notation follows A. B. Birkholz, H. B. Schlegel, Theor. Chem. Acc., 135 (84), 2016.

Returns:

H

Return type:

(np.ndarray)

property _updated_h_inv: ndarray#

Flowchart update is only available for Hessian

property conditions_met: bool#

Flowchart update does not have any conditions, as update scheme is dynamically selected

class autode.opt.optimisers.hessian_update.HessianUpdater(**kwargs)#

Update strategy for the (inverse) Hessian matrix

__init__(**kwargs)#

Hessian updater

Keyword Arguments:
  • h_inv (np.ndarray) – Inverse Hessian (\(H^{-1}\)), shape = (N, N)

  • s (np.ndarray) – Coordinate shift. \(s = R_{i+1} - R_i\)

  • y (np.ndarray) – Gradient shift. \(y = \nabla E_{i+1} - \nabla E_i\)

  • subspace_idxs (list(int)) – Indexes of the components of the hessian to update

abstract property _updated_h: ndarray#

Calculate H

abstract property _updated_h_inv: ndarray#

Calculate H^{-1}

abstract property conditions_met: bool#

Are the conditions met to update the Hessian with this method?

property updated_h: ndarray#

Calculate H from a previous Hessian, coordinate shift and gradient shift

Raises:

(RuntimeError) – If the update fails

property updated_h_inv: ndarray#

Calculate H^{-1} from a previous inverse Hessian, coordinate shift and gradient shift

Raises:

(RuntimeError) – If the update fails

class autode.opt.optimisers.hessian_update.NullUpdate(**kwargs)#
property _updated_h: ndarray#

Updated H is just the input Hessian

property _updated_h_inv: ndarray#

Updated inverse H is just the input inverse Hessian

property conditions_met: bool#

Conditions are always met for a null optimiser

class autode.opt.optimisers.hessian_update.SR1Update(**kwargs)#
property _updated_h: ndarray#

Update H using a symmetric-rank 1 (SR1) update

\[H_{new} = H + \frac{(y- Hs)(y - Hs)^T} {(y- Hs)^T s}\]
property _updated_h_inv: ndarray#

Update H^-1 using a symmetric-rank 1 (SR1) update

\[H_{new}^{-1} = H^{-1} + \frac{(s- H^{-1}y)(s - H^{-1}y)^T} {(s- Hy)^T y}\]
property conditions_met: bool#

Condition for SR1 update. See: https://en.wikipedia.org/wiki/Symmetric_rank-one

\[|s (y - Hs)| \ge r ||s|| \cdot ||y - Hs||\]

where \(r \in (0, 1)\) = 1E-8.




Dimer method for finding transition states given two points on the PES. Notation follows 1. https://aip.scitation.org/doi/10.1063/1.2815812 based on 2. https://aip.scitation.org/doi/10.1063/1.2104507 3. https://aip.scitation.org/doi/10.1063/1.480097

class autode.opt.optimisers.dimer.Dimer(maxiter: int, coords: ~autode.opt.coordinates.dimer.DimerCoordinates, ratio_rot_iters: int = 10, gtol: float | ~autode.values.GradientRMS = RMS(∇E)(0.001 Ha(Å)^-1), trns_tol: ~autode.values.MWDistance = Mass-weighted Distance(0.001 Å amu^1/2), phi_tol: ~autode.values.Angle = Angle(5.0 °), init_alpha: ~autode.values.MWDistance = Mass-weighted Distance(0.3 Å amu^1/2))#

Dimer spanning two points on the PES with a TS at the midpoint

__init__(maxiter: int, coords: ~autode.opt.coordinates.dimer.DimerCoordinates, ratio_rot_iters: int = 10, gtol: float | ~autode.values.GradientRMS = RMS(∇E)(0.001 Ha(Å)^-1), trns_tol: ~autode.values.MWDistance = Mass-weighted Distance(0.001 Å amu^1/2), phi_tol: ~autode.values.Angle = Angle(5.0 °), init_alpha: ~autode.values.MWDistance = Mass-weighted Distance(0.3 Å amu^1/2))#

Dimer optimiser

the interpolated midpoint

Parameters:
  • ratio_rot_iters – Number of rotations per translation in each dimer step

  • gtol – Tolerance on the gradient at the midpoint for convergence

  • trns_tol – Tolerance on the minimum root mean square translation distance, below which convergence is signaled

  • phi_tol – Tolerance on the rotation angle below which rotation is not performed

  • init_alpha – Initial step size to use in mass-weighted cartesian coordinates

_step() None#

Do a single dimer optimisation step, consisting of several rotation and translation steps.

property converged: bool#

Has the dimer converged?

classmethod optimise(species: Species, method: Method, n_cores: int | None = None, coords: DimerCoordinates | None = None, **kwargs) None#

Optimise a dimer pair of coordinates such that the species coordinates are close to a transition state

Parameters:
  • n_cores – Number of cores to use for the optimisation

  • coords – Dimer coordinates