Managing Conquest with ASE
Below we give an introduction how to setup the ASE environment with respect to CONQUEST repository along with a few examples of ASE/Conquest capabilities. We assume that a python script or jupyter-notebook is used.
Setup
Environment variables
The script will need to set environmental variables specifying the
locations of the CONQUEST executable Conquest
, and if required, the basis
set generation executable MakeIonFiles
and pseudopotential database.
These variables are:
ASE_CONQUEST_COMMAND
: the Conquest executable command including MPI/openMPI prefix.CQ_PP_PATH
: the PAO path directory to where are located the the.ion
files.(optional)
CQ_GEN_BASIS_CMD
: the PAO generation executableMakeIonFiles
.
Given the Conquest root directory CQ_ROOT
, initialisation might look to something like
import os
CQ_ROOT = 'PATH_TO_CONQUEST_DIRECTORY'
os.environ['ASE_CONQUEST_COMMAND'] = 'mpirun -np 4 '+CQ_ROOT+'/bin/Conquest'
os.environ["CQ_GEN_BASIS_CMD"] = CQ_ROOT+'/bin/MakeIonFiles"
os.environ['CQ_PP_PATH'] = CQ_ROOT+'/pseudo-and-pao/'
Go to top.
Pseudopotential/PAO files
Conquest atomic pseudotential and basis functions are store in the .ion
files which will ne referred as to PAO files. Provided the pseudopotential files .pot
available in CQ_PP_PATH
,
automatic generation of numerical PAOs is possible using the program MakeIonFiles
available from the Conquest package.
Provided the PAO files, the basis set is specified through a python dictionary, for example:
basis = {'O' : {'file': 'O_SZP.ion'},
'H' : {'file': 'H_SZP.ion'},
'C' : {'file': 'C_SZP.ion'}}
In this case they are all assumed to be obtained from Hamann pseudopotentials,
which are the default. Knowing the the exchange and correlation functional <XC>
from the Conquest input (vide infra) and the chemical symbol <X>, the Calculator
will search the .ion
file in different places:
CQ_PP_PATH
CQ_PP_PATH/lib/
CQ_PP_PATH/<XC>/<X>/
including the current directory and the ASE working directory (vide infra). If your PAO file is located in a different place you can include the path in the basis dictionary:
basis = {'O' : {'file': 'O_SZP.ion',
'directory': '<PATH_TO_FILE>'},
'H' : {'file' : 'H_SZP.ion'},
'C' : {'file' : 'C_SZP.ion'}}
For generating the PAO files, the keyword gen_basis
should be set to True
(default is False
) and the size be provided (default is medium
).
For instance:
basis = {'O' : {'gen_basis' : True,
'basis_size': 'small'},
'C' : {'gen_basis' : True,
'basis_size': 'medium'},
'H' : {'gen_basis' : True,
'basis_size': 'large'}}
will create the O.ion
, C.ion
and H.ion
files where small
, medium
and large
are default size basis set. You are allowed to choose the functional and
to add options for basis set generation:
basis = {'H' : {'gen_basis' : True,
'basis_size': 'small',
'xc' : 'LDA',
'Atom.Perturbative_Polarised': False}}
Note
Only Hamann pseudopotentials for LDA, PBE and PBEsol are available within the CONQUEST distribution. For using other functionals see Generating new pseudopotentials.
Warning
Generating polarised PAOs for some atoms can be problematic (mainly group I
and II). Please review carefuly the MakeIonFiles
input files
named Conquest_ion_input
which are collected in CQ_PP_PATH/<XC>/<X>/
if you are not sure about what you are doing, and check your PAOs.
Go to top.
CONQUEST Calculator
The CONQUEST Calculator class can be invoked from the ase Calculator set as described in the example below:
from ase.calculators.conquest import Conquest
A minimal example is given below for setting the CONQUEST Calculator (named calc
)
of the ASE Atoms object
named struct:
from ase.calculators.conquest import Conquest
from ase.build import bulk
struct = bulk('NaCl', crystalstructure='rocksalt', a=5.71, cubic=True)
basis = {'Cl' : {'file' : 'Cl.ion'}, 'Na' : {'file' : 'Na.ion'}}
calc = Conquest(basis=basis,atoms=struct)
or, equivalently,
from ase.calculators.conquest import Conquest
from ase.build import bulk
struct = bulk('NaCl', crystalstructure='rocksalt', a=5.71, cubic=True)
basis = {'Cl' : {'file' : 'Cl.ion'}, 'Na' : {'file' : 'Na.ion'}}
struct.calc = Conquest(basis=basis)
In basic calculate mode (compute energy
), the Calculator comes with 3 methods:
write_input()
:this function will setup the input files. For CONQUEST, the PAO basis will be generated/copied with respect to the dictionary key/value pairs, and
Conquest_input
file including the calculation parameters will be written, a long with thecoordinate
file, containing the lattice vectors (in Bohr Unit) and atomic positions (in fractional coordinates).
execute()
:this function execute the calculation. For CONQUEST, it will launch the
ASE_CONQUEST_COMMAND
setup in the environment varaibles.
read_results()
:this function post-process the the output file. For CONQUEST, the
energy
,forces
,stress
andeigenvalues
will be extracted from theConquest_out_ase
output file.
Note
The funtion read_results()
operate on the Conquest_out_ase
file.
This output file is not created by default by CONQUEST. If you want to post-process
a calculation with an input generated by hand you must add IO.WriteOutToASEFile True
in conquest_input
.
The indirect way for managing CONQUEST calculation with ASE is:
struct.calc = Conquest(basis=basis)
struct.calc.write_input(struct)
struct.calc.execute()
struct.calc.read_results(struct)
where struct.calc.execute()
can be ignored when, for instance, the calculation
is performed on a supercomputer and the output file is then copied back to the current
directory for post-processing.
The direct way is simply:
struct.calc = Conquest(basis=basis)
struct.calc.calculate(struct)
or, equivalently,
struct.calc = Conquest(basis=basis)
struct.get_potential_energy()
Go to top.
Keywords for generating the Conquest_input file
In principle all the Conquest input parameters
can be added to Conquest_out_ase
using key/value pairs in a dictionary. There are 3 class of parameters:
mandatory : they are parsed to the Calculator and have no defaults ; there are mandatory.
important : they are parsed to the Calculator they can be freely modified. Some of them are pure ASE keywords.
defaults : they are set as defaults ; some of them must not be modified. They are read by the Calculator through a dictionay
conquest_flags
.
Mandatory keywords
keyword |
type |
default value |
description |
---|---|---|---|
|
|
None |
an atoms object constructed either via ASE or read from an input |
|
|
None |
a dictionary specifying the pseudopotential/basis files |
Important keywords
keyword |
CONQUEST equivalence |
type |
default value |
description |
---|---|---|---|---|
|
None |
|
None |
directory used for storing input/output and calculation files |
|
None |
|
None |
basename for working files (only used by ASE, eg. NEB) |
|
None |
|
None |
k-points grid ; converted to CONQUEST Monkhorst-Pack grid |
|
|
|
100 |
integration grid in Ha |
|
|
|
‘PBE’ |
exchange and correlation functional |
|
|
|
True |
choose either SCF or non-SCF |
|
|
|
1e-6 |
Self-consistent-field convergence tolerance in Ha |
|
|
|
1 |
spin polarisation: 1 for unpolarized or 2 for polarised |
|
None |
|
None |
other CONQUET keyword arguments |
Defaults keywords
keyword |
type |
default value |
description |
---|---|---|---|
|
|
True |
write ASE output file ; must always be True when using ASE for post-processing |
|
|
1 |
verbose for the output ; must always be 1 when using ASE for post-processing |
|
|
‘diagon’ |
‘diagon’ stands for diagonalisation other is ‘ordern’ (base on density matrix) |
|
|
‘Hamann’ |
kind of pseudopotential other type are ‘siesta’ and ‘abinit’ |
|
|
50 |
maximum number SCF cycles |
|
|
‘static’ |
‘static’ stands for single (non)SCF other are ‘md’ or optimisation algorithms. |
|
|
1 |
1 for Methfessel-Paxton ; 0 for Fermi-Dirac |
|
|
0.001 |
smearing temperature in Ha |
Some examples
An example of more advanced Calculator setup is given below for a SCF calculation on BCC-Na where for a PBE calculation using a k-point grid of \(6\times 6\times6\) using the Fermi-Dirac distribution for the occupation with a smearing of 0.005 Ha:
struct = bulk('Na', crystalstructure='bcc', a=4.17, cubic=True)
basis = {'Na' : {'file' : 'NaCQ.ion'}}
conquest_flags = {'Diag.SmearingType': 0,
'Diag.kT' : 0.005}
struct.calc = Conquest(directory = 'Na_bcc_example',
grid_cutoff = 90.0,
self_consistent= True,
xc = 'PBE',
basis = basis,
kpts = [6,6,6],
nspin = 1,
**conquest_flags)
struct.get_potential_energy()
Finally, defaults and other input flags can be defined in a new dictionary, and passed as an expanded set of keyword arguments.
conquest_flags = {'DM.SolutionMethod' : 'ordern',
'DM.L_range' : 12.0,
'minE.LTolerance' : 1.0e-6}
Here is an example, combining the above. We set up a cubic diamond cell containing 8 atoms, and perform a single point energy calculation using the order(N) method (the default is diagonalisation, so we must specify all of the order(N) flags). We don’t define a basis set, instead providing keywords that specify that a minimal basis set should be constructed using the MakeIonFiles basis generation tool.
import os
from ase.build import bulk
from ase.calculators.conquest import Conquest
CQ_ROOT = 'PATH_TO_CONQUEST_DIRECTORY'
os.environ['ASE_CONQUEST_COMMAND'] = 'mpirun -np 4 '+CQ_ROOT+'/bin/Conquest'
os.environ["CQ_GEN_BASIS_CMD"] = CQ_ROOT+'/bin/MakeIonFiles"
os.environ['CQ_PP_PATH'] = CQ_ROOT+'/pseudo-and-pao/'
diamond = bulk('C', 'diamond', a=3.6, cubic=True) # The atoms object
conquest_flags = {'DM.SolutionMethod' : 'ordern', # Conquest keywords
'DM.L_range' : 12.0,
'minE.LTolerance' : 1.0e-6}
basis = {'C': {'basis_size' : 'minimal', # Generate a minimal basis
'gen_basis' : True}
calc = Conquest(grid_cutoff = 80, # Set the calculator keywords
xc = 'PBE',
self_consistent=True,
basis = basis,
nspin = 1,
**conquest_flags)
diamond.set_calculator(calc) # attach the calculator to the atoms object
energy = diamond.get_potential_energy() # calculate the potential energy
Go to top.
Multisite support functions
Multisite support functions require a few additional keywords in the atomic species block, which can be specified as follows:
basis = {'C': {"basis_size": 'medium',
"gen_basis": True,
"pseudopotential_type": "hamann",
"Atom.NumberofSupports": 4,
"Atom.MultisiteRange": 7.0,
"Atom.LFDRange": 7.0}}
Note that we are constructing a DZP basis set (size medium) with 13 primitive
support functions using MakeIonFiles
, and contracting it to multisite basis
of 4 support functions. The calculation requires a few more input flags, which
are specified in the other_keywords
dictionary:
other_keywords = {"Basis.MultisiteSF": True,
"Multisite.LFD": True,
"Multisite.LFD.Min.ThreshE": 1.0e-7,
"Multisite.LFD.Min.ThreshD": 1.0e-7,
"Multisite.LFD.Min.MaxIteration": 150,
}
Go to top.
Loading the K/L matrix
Most calculation that involve incrementally moving atoms (molecular dynamics, geometry optimisation, equations of state, nudged elastic band etc.) can be made faster by using the K or L matrix from a previous calculation as the initial guess for a subsequent calculation in which that atoms have been moved slightly. This can be achieved by first performing a single point calculation to generate the first K/L matrix, then adding the following keywords to the calculator:
other_keywords = {"General.LoadL": True,
"SC.MakeInitialChargeFromK": True}
These keywords respectively cause the K or L matrix to be loaded from file(s)
Kmatrix.i**.p*****
, and the initial charge density to be constructed from
this matrix. In all subsequent calculations, the K or L matrix will be written
at the end of the calculation and used as the initial guess for the subsequent
ionic step.
Go to top.
Equation of state
The following code computes the equation of state of diamond by doing single
point calculations on a uniform grid of the a
lattice parameter. It then
interpolates the equation of state and uses matplotlib
to generate a plot.
import scipy as sp
from ase.build import bulk
from ase.io.trajectory import Trajectory
from ase.calculators.conquest import Conquest
# Construct a unit cell
diamond = bulk('C', 'diamond', a=3.6, cubic=True)
basis = {'C': {"basis_size": 'minimal',
"gen_basis": True}}
calc = Conquest(grid_cutoff = 50,
xc = "PBE",
basis = basis,
kpts = [4,4,4])
diamond.set_calculator(calc)
cell = diamond.get_cell()
traj = Trajectory('diamond.traj', 'w') # save all results to trajectory
for x in sp.linspace(0.95, 1.05, 5): # grid for equation of state
diamond.set_cell(cell*x, scale_atoms=True)
diamond.get_potential_energy()
traj.write(diamond)
from ase.io import read
from ase.eos import EquationOfState
configs = read('diamond.traj@0:5')
volumes = [diamond.get_volume() for diamond in configs]
energies = [diamond.get_potential_energy() for diamond in configs]
eos = EquationOfState(volumes, energies)
v0, e0, B = eos.fit()
import matplotlib
eos.plot('diamond-eos.pdf') # Plot the equation of state
Go to top.