Minimisation Libraries#
Part of the power of the Python programming language comes from the vast ecosystem of pre-existing libraries that are available to use.
Previously, you have written your own minimisation algorithms in Python, using loops and functions.
Numerical function minimisation is a very common scientific problem, and, perhaps unsurprisingly, there is a library that we can use to do this.
The library is called scipy
(pronounced Sigh-Pie), and includes an enormous number of common scientific computing utilities. In this section, we will focus on the minimize
method (note the American spelling).
The minimize
method is located within the optimize
module of the scipy
library, so to access this, we must import it.
from scipy.optimize import minimize
Let us first investigate the documentation of the minimize function, this is available online or it can be printed in the Jupyter Notebook using
print(minimize.__doc__)
or
minimize?
Because this documentation is long we will not reproduce it fully here, but it is useful to know where to look when figuring out how a new function or method behaves.
From the documentation, we can see that the minimize
function takes two required arguments:
fun
: this is the function that we will minimize.x0
: this is our initial guess of the parameter used in the minimisation.
The only optional argument we will look at from the documentation is args
, which are the invariant (unchanging) parameters in our model that should be passed to our function. For example, in the case of our Lennard-Jones potential, the precise shape of the function depends on the invariant parameters \(A\) and \(B\), so if we want to ue minimize
to find the minimum of our function, we need to specify the relevant values of \(A\) and \(B\).
To minimize the potential energy from the Lennard-Jones potential, we write a function for this, this is the fun
discussed above. The parameter we are minimising with respect to (in this case, the interatomic separation, \(r\)) needs to be the first argument in our function, followed by any additional parameters that we are not varying as part of our minimisation (here \(A\) and \(B\)).
def lennard_jones(r, A, B):
"""
Lennard-Jones potential energy.
Args:
r (float): the distances to find the energy for (Å).
A (float): the repulsive prefactor (eV/Å^12).
B (float): the attractive prefactor (eV/Å^6).
Returns:
(float): the potential energy at each r.
"""
return A / r**12 - B / r**6
Note in the documentation, it says that the minimize
function should be passed a function with the form
fun(x, *args) -> float
Above, this is mapped out, were r
is our x
and A, B
are the *args
(note the *
means this can be any number of args
).
The args
are not varied during the minimisation process.
The minimization
function works as follows.
A = 1e5
B = 40
result = minimize(lennard_jones, [3.5], args=(A, B))
print(result)
message: Optimization terminated successfully.
success: True
status: 0
fun: -0.003999999054071188
x: [ 4.135e+00]
nit: 8
jac: [-5.648e-06]
hess_inv: [[ 5.471e+01]]
nfev: 18
njev: 9
The object that is returned by the minimize
function is a special object which gives a lot of information about the minimisation.
The value we are interested in is the minimum energy difference, which is the x
attribute of this object.
print(result.x)
[4.13485048]
This agrees well with the value that we found when writing our own minimisation methods.
Exercise#
Write a function to model the following potential energy function,
this function describes the potential energy of the Cl-C-C-Cl dihedral angle in a molecule of 1, 2-dichloroethane, where \(A_1 = 55.229\) kJ/mol, \(A_2 = 3.3472\) kJ/mol, \(A_3 = -58.576\) kJ/mol and \(\phi\) is the dihedral angle.
Plot this function between \(-\pi\) and \(\pi\), remember to label your axes.
Using
minimize
test different initial values ofx0
for \(\phi\). Consider why the result is so dependent on the starting position for this function (it may help to plot your result on the function).
Tip#
It is advised to use the NumPy trigonmetric functions in this exercise.