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:
gtol (autode.values.GradientRMS) – Tolerance on RMS(|∇E|)
etol (autode.values.PotentialEnergy) – Tolerance on |E_i+1 - E_i|
See also
- 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)
- 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
- _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
- _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
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.
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\))
Line search optimisers used to solve the 1D optimisation problem by taking steps \(X_{i+1} = X_i + \alpha p\) , where α is a step size and p is a search direction. See e.g. https://www.numerical.rl.ac.uk/people/nimg/oumsc/lectures/uepart2.2.pdf
- 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:
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