RunMinimisation.py - Writing a Local Minimisation Function for NISP¶
In this article, we will look at how to write the local optimisation method for NISP.
What is the Minimisation_Function¶
The Minimisation_Function
is a definition that perform local optimisations during NISP. This is used by NISP as a def
(i.e. as a function). This means that, rather than a variable being passed into NISP, a function is passed into the algorithm.
The implementation of the local minimisation process into NISP has been designed to be as free as possible, so that the user can use whatever local optimisation algorithm or program they want to use. In general, this algorithm will import a cluster in an ASE format from NISP. The user can locally optimise it before sending it back to NISP again in the ASE format.
Because of this flexibility, it is possible to use any type of calculator from ASE, ASAP, GWAP, LAMMPS, etc. It is even possible for the user to design this to use with non-python user-interface based local optimisers. See How to write the Minimisation_Function for non-ASE Implemented Calculator for information on how to write a Minimisation_Function
def to do this.
If you want to use VASP to perform local optimisation calculations, see How to perform NISP with VASP calculations. If you want to use another long running programming like Quantum Espresso, you will need to enter in cluster energies from the program you use into NISP manually. See How to manually enter energy results into NISP for more information.
In the following documentation we will describe how the Minimisation_Function
method is designed in a RunMinimisation.py
file, and how you can make your own. Examples of RunMinimisation.py
files used in NISP runs can be found in github.com/GardenGroupUO/NISP in the directory path Examples
(this should be found in github.com/GardenGroupUO/NISP/tree/main/Examples).
Where to write the Minimisation_Function¶
The Minimisation_Function
can be written into the Run.py file. However, as a personal preference and also to make the code cleaner to read, write and use, I put it into another python file. This file I have called RunMinimisation.py
. This does not need to be the name of this file. For example, I have named this file RunMinimisation_AuPd.py
when I wanted to keep a record that this minimisation python file contained the Gupta parameters and code for locally minimising a cluster using the Gupta potential for a cluster containing Au and Pd atoms.
Furthermore, the def Minimisation_Function
does not even need to be called Minimisation_Function
. It could be called TheGuptaFunction
, the_local_minimisation_function
, or The_Electric_Eel_Function
. Again, I have just always called it Minimisation_Function
for simplicity and for ease when using different Interpolation_Script.py
files with different Minimisation_Function
codes.
However, it is important that this code is referenced somehow in your Interpolation_Script.py
script if you want to locally optimise clusters during the NISP program. The algorithm is imported into Interpolation_Script.py
as follows (You can also see this in Interpolation_Script.py - How to run NISP):
# The RunMinimisation.py algorithm is one set by the user. It contain the def Minimisation_Function
# That is used for local optimisations. This can be written in whatever way the user wants to perform
# the local optimisations. This is meant to be as free as possible.
from RunMinimisation import Minimisation_Function
where, in the above code, RunMinimisation
is the name of the file the local minimisation code is found in (This file is called RunMinimisation.py
), and Minimisation_Function
is the name of the function that is found in the RunMinimisation.py
. If you do it like this, make sure that your RunMinimisation.py
file is in the same folder as your Run.py file.
How to write the Minimisation_Function¶
The Minimisation_Function
must be written with the following requirements:
cluster (ase.Atoms): This is the unoptimised version of the cluster.
NOTE: The collection and cluster_name variables do not need to be used in your RunMinimisation.py
script if you are using an ASE or ASE implemented calculator and local optimisator. This information may be useful if you want the cluster to be locally optimised using an external program that can not be easily used with python (for example with VASP, see How to write the Minimisation_Function for non-ASE Implemented Calculator).
returns:
cluster (ase.Atoms) - This is the optimised version of the cluster.
An example of a RunMinimisation.py file for a Gupta potential involving only Au atoms is given below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | '''
RunMinimisation.py, GRW, 8/6/17
This script is designed to locally optimise clusters.
'''
from asap3.Internal.BuiltinPotentials import Gupta
from ase.optimize import FIRE
from ase.io import write
def Minimisation_Function(cluster):
cluster.pbc = False
# Perform the local optimisation method on the cluster.
# Parameter sequence: [p, q, a, xi, r0]
#Au_parameters = {'Au': [10.229, 4.0360, 0.2061, 1.7900, 2.884]}
r0 = 4.07/(2.0 ** 0.5)
Au_parameters = {'Au': [10.53, 4.30, 0.2197, 1.855, r0]} # Baletto
Gupta_parameters = Au_parameters
cutoff = 1000
calculator = Gupta(Gupta_parameters, cutoff=cutoff, debug=False)
cluster.set_calculator(calculator)
original_cluster = cluster.copy()
dyn = FIRE(cluster,logfile=None)
converged = False
try:
dyn.run(fmax=0.01,steps=5000)
converged = dyn.converged()
if not converged:
cluster_name = 'issue_cluster.xyz'
errorMessage = 'The optimisation of cluster ' + str(original_cluster) + ' did not optimise completely.\n'
errorMessage += 'The cluster of issue before optimisation has been saved as: '+str(cluster_name)
write(cluster_name,original_cluster)
raise Exception(errorMessage)
except Exception as exception_message:
cluster_name = 'issue_cluster.xyz'
errorMessage = 'The optimisation of cluster ' + str(original_cluster) + ' did not optimise completely.\n'
errorMessage += 'The cluster of issue before optimisation has been saved as: '+str(cluster_name)+'\n'
errorMessage += exception_message
write(cluster_name,original_cluster)
raise Exception(errorMessage)
return cluster
|
We will explain the components of this example below:
Importing external code¶
To begin, you will need to import all the external files that you will need so that you have the descriptor of the potential you want to use, and the local optimiser that you would like to use. In this example, the Gupta potential is used as the descriptor for the potential, while FIRE
is the local optimiser that will be used to locally optimise the cluster.
6 7 8 | from asap3.Internal.BuiltinPotentials import Gupta
from ase.optimize import FIRE
from ase.io import write
|
Preparing the cluster¶
First, it is usually a good idea to tell ase if you want the calculator to calculate the cluster with periodic boundary conditions pbc
or not. In the case of the Gupta potential, we will include the line cluster.pbc = False
to make sure that there are no boundary conditions on upon the cluster, since we do not want this and the Gupta potential does not need this turned on. For your potential, you may want to include this, or not.
11 | cluster.pbc = False
|
Preparing the Potential, and setting up the local optimiser.¶
We would like to set up the parameters needed for the descriptor of the potential, attach the descriptor as a calculator to the cluster
, and set up the local optimiser. In this example, Gupta is called a calculator. It contains a description of the Gupta potential that can be used to calculate the energy of cluster
. We do this in the line cluster.set_calculator(Gupta(Gupta_parameters, cutoff=1000, debug=True))
. For more information on how this works, see Tutorial on Using Calculators in ASE.
The last line, dyn = FIRE(cluster)
, sets up the local optimiser, FIRE
, to be used to locally minimise the cluster cluster
(see Tutorial on Structure Optimization in ASE).
See below for a example:
12 13 14 15 16 17 18 19 20 21 22 | # Perform the local optimisation method on the cluster.
# Parameter sequence: [p, q, a, xi, r0]
#Au_parameters = {'Au': [10.229, 4.0360, 0.2061, 1.7900, 2.884]}
r0 = 4.07/(2.0 ** 0.5)
Au_parameters = {'Au': [10.53, 4.30, 0.2197, 1.855, r0]} # Baletto
Gupta_parameters = Au_parameters
cutoff = 1000
calculator = Gupta(Gupta_parameters, cutoff=cutoff, debug=False)
cluster.set_calculator(calculator)
original_cluster = cluster.copy()
dyn = FIRE(cluster,logfile=None)
|
Executing the local optimiser¶
We would like to now get the definition to run a local optimisation. This is done by performing dyn.run(fmax=0.01,steps=5000)
. However I have found that if something breaks for some reason during the optimisation, this can completely stop the genetic algorithm in its tracks, and cause it to finish with a fatal error. You may want this to happen so that you can address issues when they arise, but sometimes it is hard to continue to work when it keeps happening. If you would like, you can make sure the genetic algorithm does not fail entirely by adding a Error Handling block, as shown in the example below:
23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | converged = False
try:
dyn.run(fmax=0.01,steps=5000)
converged = dyn.converged()
if not converged:
cluster_name = 'issue_cluster.xyz'
errorMessage = 'The optimisation of cluster ' + str(original_cluster) + ' did not optimise completely.\n'
errorMessage += 'The cluster of issue before optimisation has been saved as: '+str(cluster_name)
write(cluster_name,original_cluster)
raise Exception(errorMessage)
except Exception as exception_message:
cluster_name = 'issue_cluster.xyz'
errorMessage = 'The optimisation of cluster ' + str(original_cluster) + ' did not optimise completely.\n'
errorMessage += 'The cluster of issue before optimisation has been saved as: '+str(cluster_name)+'\n'
errorMessage += exception_message
write(cluster_name,original_cluster)
raise Exception(errorMessage)
|
You can also see that I have placed an if statement to determine if the local optimsation actually converged. I have found that it is useful to include a way of noting if the optimisation was able to converge or not. See more about How to perform a local optimisation in ASE here, or refer to the manual of the local optimiser you are using for more information on how to do this.
Return the Optimised Cluster and Info¶
Remember to return the cluster
to the genetic algorithm so that it can use this information, as well as the optimiser cluster, to proceed to explore the potential energy surface of the cluster you wish to explore.
40 | return cluster
|
How to write the Minimisation_Function for a ASE Implemented Calculator¶
If the descriptor for the potential you would like to use is implemented in ASE, it is very easy to implement this into your Minimisation_Function definition. You can use the example of RunMinimisation.py above, where the only component you need to change is the set_calculator function used by Opt_cluster. This is the bit of the code above that looks like this:
Gupta_parameters = {'Cu': [10.960, 2.2780, 0.0855, 1.224, 2.556]}
cluster.set_calculator(Gupta(Gupta_parameters, cutoff=1000, debug=True))
Instead of this, you can include all the parameters that you need for your potential before the set_calculator line. For example:
Potential_Parameters = ...
cluster.set_calculator(Potential(Potential_Parameters))
Where Potential
is the potential you would like to use, and Potential_Parameters
are all the parameters that Potential
needs to work. Please consult the manual of the potential you would like to use to learn how to use that potential.
How to write the Minimisation_Function for non-ASE Implemented Calculator¶
In the previous section of this page we have been performing a local optimisation using ASE implemented calculators. However, you may want to use a calculator to locally optimise your cluster. This may only be possible by allowing the program you wish to use to itself completely locally optimise the cluster. This is no issue for us! We just need to be careful to implement the local optimisation using your own program, and make sure that the RunMinimisation.py file is constructed as follows:
Input into Minimisation_Function
:
cluster (ASE.Atoms): This is the unoptimised version of the cluster.
returns:
cluster (ASE.Atoms) - This is now the optimised version of the cluster.
A general script for locally optimising however you want to is given below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | '''
RunMinimisation.py, GRW, 8/6/17
This script is designed to locally optimise clusters.
'''
import time
from ase.io import write
from subprocess import Popen
def Minimisation_Function(cluster):
cluster.pbc = What_you_want # make sure that the periodic boundry conditions are set off
# -----------------------------------------------------
# Perform any pre-optimisation work here
# -----------------------------------------------------
startTime = time.time(); converged = False
try:
Popen(['run','external','program'])
except Exception as exception_message:
cluster_name = 'issue_cluster.xyz'
errorMessage = 'The optimisation of cluster ' + str(original_cluster) + ' did not optimise completely.\n'
errorMessage += 'The cluster of issue before optimisation has been saved as: '+str(cluster_name)+'\n'
errorMessage += exception_message
write(cluster_name,original_cluster)
raise Exception(errorMessage)
endTime = time.time()
# -----------------------------------------------------
# Perform any post-optimisation work here
# -----------------------------------------------------
return cluster
|
If you want to locally optimise your clusters with computational heavy program, like VASP and QuantumEspresso, it may be better to run these programs manually and then enter the results of this into an input file. See How to manually enter energy results into NISP to obtain more information about how to manually enter energy results into NISP.