Minimisation with scipy.optimize.minimize#
scipy.optimize.minimize() is a powerful tool for numerical optimisation that finds the minimum of any scalar function (a function that returns a single numerical value). Unlike the methods we have implemented so far, scipy.optimize.minimize() uses more sophisticated algorithms that automatically handle many of the challenges we have encountered, such as choosing appropriate step sizes and dealing with difficult potential energy surfaces.
The following code demonstrates how minimize() can be used to find the minimum of a harmonic potential.
from scipy.optimize import minimize
def harmonic_potential(r, k, r0):
"""Calculate the energy in eV for a harmonic potential U(r) = k/2 * (r - r0)^2.
Args:
r (float): Distance parameter to optimise.
k (float): Spring constant in eV/Ų.
r0 (float): Equilibrium distance in Å.
Returns:
float: Potential energy in eV.
"""
return 0.5 * k * (r - r0)**2
# Initial guess and parameters
x0 = 1.0 # Starting guess for r
k = 36.0 # Spring constant
r0 = 0.74 # Equilibrium distance
result = minimize(harmonic_potential, x0=x0, args=(k, r0))
result
message: Optimization terminated successfully.
success: True
status: 0
fun: 1.2580072611001788e-17
x: [ 7.400e-01]
nit: 1
jac: [ 2.983e-07]
hess_inv: [[1]]
nfev: 6
njev: 3
Let us break this down step by step:
First, we import the minimize() function from scipy.optimize.
from scipy.optimize import minimize
Next, we define a function for our harmonic potential:
def harmonic_potential(r, k, r0):
"""Calculate the energy in eV for a harmonic potential U(r) = k/2 * (r - r0)^2.
Args:
r (float): Distance parameter to optimise.
k (float): Spring constant in eV/Ų.
r0 (float): Equilibrium distance in Å.
Returns:
float: Potential energy in eV.
"""
return 0.5 * k * (r - r0)**2
Note that we have ordered the three arguments to this function so that the parameter we want to minimise with respect to, r, is the first parameter. The second and third parameters, k and r0, will be fixed for a given potential energy surface.
Then, we define variables to store our initial guess for the equilibrium bond length and the parameters that define our harmonic potential.
# Initial guess and parameters
x0 = 1.0 # Starting guess for r
k = 36.0 # Spring constant
r0 = 0.74 # Equilibrium distance
Now we call the minimize() function:
result = minimize(harmonic_potential, x0=x0, args=(k, r0))
We pass in three arguments:
The first argument is the name of the function that we want to minimise. In this case our function is called
harmonic_potential.The second argument is our starting guess for the parameter we are minimising with respect to, so in this example, we pass our guess for our equilibrium bond length of 1 Å.
The third argument is a tuple of parameters that will also get passed to the function we are minimising, that are fixed during the minimisation. In this case, we need to define the shape of our harmonic potential energy surface by providing values for
kandr0. Because ourharmonic_potential()function takes two additional parameters, the tuple we pass tominimize()needs to contain two parameters. The order of these parameters also needs to match the order of the corresponding arguments in the function being minimised; so our tuple is structured as(k, r0).
minimize() returns an OptimizeResult object that contains lots of information about the optimisation, such as whether it was deemed to have successfully found a minimum, how many iterations it took, and the estimated minimum value.
We can access the estimated minimum value directly using
result.x
array([0.74])
Here, we are optimising a one-dimensional function (the energy varies with bond-length), and we get back a one-dimensional solution array. If we were minimising a function over more dimensions (e.g., the energy of a linear triatomic molecule, which then depends on two bond lengths) then the solution array would contain the minimisation solution for each dimension.
Exercises#
Exercise 1: Lennard-Jones Potential with scipy#
Use scipy.optimize.minimize() to perform a geometry optimisation for the Lennard-Jones potential:
with \(A\) = 1 × 105 eV Å12 and \(B\) = 40 eV Å6.
Part A:
Write a function lennard_jones(r, A, B) to calculate this potential.
Part B:
Run minimize() with three different starting values: \(r\) = 3.2 Å, \(r\) = 4.4 Å, and \(r\) = 6.0 Å.
Part C:
For each starting point, print:
The starting position
The final optimised position
The number of iterations required
Part D:
Do all starting points converge to the same minimum? Why or why not?
Exercise 2. Multiple Minima in 1,2-Dichloroethane#
The potential energy associated with changes in the Cl-C-C-Cl dihedral angle, \(\theta\), in 1,2-dichloroethane can be modelled as
with \(A_1\) = 55.229 kJ mol\(^{-1}\), \(A_2\) = 3.3472 kJ mol\(^{-1}\), and \(A_3\) = -58.576 kJ mol\(^{-1}\).
This exercise explores how the choice of starting geometry affects which minimum the optimiser finds.
Part A: Define and Visualise the Potential Energy Surface
Write a function
dihedral_potential(theta, A1, A2, A3)that calculates the potential energy for a given dihedral angle \(\theta\) (in radians).Create an array of \(\theta\) values from \(-\pi\) to \(\pi\) using
np.linspace()with at least 200 points.Calculate the potential energy at each point and create a plot showing \(U(\theta)\) vs \(\theta\).
By examining your plot, identify approximately how many minima exist and roughly where they are located.
Part B: Test Individual Starting Points
Use
scipy.optimize.minimize()to find the minimum, starting with \(\theta_0 = 0\) radians as your initial guess. What minimum does the optimiser find?Repeat the optimisation starting from \(\theta_0 = \pi\) radians. Does it find the same minimum?
Try one more starting point of your choice. Based on these three tests, what pattern do you observe?
Part C: Systematic Exploration of Starting Points
Now we will systematically test many different starting points to understand the overall behaviour.
Create an array of 20 equally spaced starting angles between \(-\pi\) and \(\pi\) using
np.linspace(). Store this in a variable calledstart_angles.Create two empty lists called
start_positionsandfinal_positions.Write a
forloop that:Iterates through each angle in
start_anglesPerforms an optimisation starting from that angle
Appends the starting angle to
start_positionsAppends the optimised angle (from
result.x[0]) tofinal_positions
Part D: Visualise the Basins of Attraction
Create a scatter plot with:
Starting angles on the x-axis (
start_positions)Final optimised angles on the y-axis (
final_positions)Appropriate axis labels and title
This plot shows you which starting positions lead to which minima. Each horizontal band of points represents a different minimum (basin of attraction).
Part E: Analysis
How many distinct minima does your scatter plot reveal? (Look for horizontal bands of points.)
Note
You may notice points clustered near both \(\theta \approx +3.14\) (near \(+\pi\)) and \(\theta \approx -3.14\) (near \(-\pi\)). Before counting these as separate minima, remember that dihedral angles are periodic: \(\theta = +\pi\) and \(\theta = -\pi\) represent the same molecular conformation (both are 180°).
Check your potential energy plot from Part A: you should see that \(U(+\pi) = U(-\pi)\). These two sets of points represent convergence to the same minimum approached from different directions, not two different minima.
This is a consequence of how the optimiser works: it finds a nearby minimum by following gradients, and doesn’t “wrap around” the periodic boundary. Starting from \(\theta = +2\) radians naturally leads to the minimum expressed as \(\theta \approx +\pi\), whilst starting from \(\theta = -2\) radians leads to the same minimum expressed as \(\theta \approx -\pi\).
For each minimum, estimate the range of starting angles that lead to that minimum. These ranges are called the “basin of attraction” for each minimum.
Based on your results, if you wanted to find the global minimum (the lowest energy structure), what strategy would you need to use? (Hint: look back at your potential energy plot from Part A to see which minimum has the lowest energy.)