Tips

Table of Contents

Restarting calculations

Once you have finished a calculation, there will be many situations where you would like to re-issue the obtained results for sub-sequent calculations. Here we will cover the following scenarios

  • Improve and refine calculations from a previous run

  • Restart an ATK run after a system failure

In some situations, for example, you might not know the right choice of parameter settings for a given simulation beforehand. Often, a set of trial-and-error runs are performed until the simulation has been fine tuned to match a given system. This, however, has the disadvantage that you waste a lot of computer resources, since all the information obtained through these test runs is lost. To avoid this, ATK offers the possibility of restoring the result of a previous calculation and use this as the initial condition for a subsequent calculation.

In addition, ATK can also restart an aborted calculation, which typically is needed if your ATK job terminated due to a system crash. Notice that this latter option requires that you have specified a NetCDF checkpoint file.

We will cover both scenarios in the next sub-sections.

Restarting a finished job

Suppose you wanted to calculate the bond length of a hydrogen molecule in its ground state. This could be tackled using the ATK function calculateOptimizedAtomicGeometry().

If, however, the molecular optimization performed by calculateOptimizedAtomicGeometry() for some reason does not converge within a desired accuracy, we can avoid a complete restart of the optimization by specifying that the final state obtained by calculateOptimizedAtomicGeometry() should be saved to a checkpoint file. We do this by specifying the name of the checkpoint file in the runtimeParameters() and assign the returned object to the optional variable runtime_parameters when we call the calculateOptimizedAtomicGeometry() function. Below is an example of how the checkpoint file and verbosity_level (i.e degree of program output to the terminal) is specified in the runtimeParameters():

params = runtimeParameters(
    verbosity_level=10,
    checkpoint_filename='h2.nc'
    )

Here is a complete script for solving the task of optimizing the geometry of the hydrogen molecule

# Import the KohnSham module from ATK
from ATK.KohnSham import *

# Set up elements and positions
elm = [ Hydrogen, Hydrogen ]
pos = [ ( +0.4, 0.0, 0.0)*Angstrom,
        ( -0.4, 0.0, 0.0)*Angstrom ]

# Add them to a configuration
h2 = MoleculeConfiguration(elm,pos)

# Setup Kohn-Sham method
dft_method = KohnShamMethod()

# Set name of check point file
params = runtimeParameters(
    verbosity_level=10,
    checkpoint_filename='h2.nc'
    )

# Calculate the optimized atomic geometry
h2_opt = calculateOptimizedAtomicGeometry(
    atomic_configuration=h2,
    method=dft_method,
    runtime_parameters=params
    )

# Print the coordinates of the individual atoms
print '\nAtom coordinates\n---------------------------'
for coord in h2_opt.cartesianCoordinates():
    for c in coord:
        print '%7.4f' % (c.inUnitsOf(Angstrom)),
    print

h2.py

The key elements in the script are:

  1. Set up a hydrogen molecule with an initial bond length of 0.8 Å.

  2. Define the Kohn-Sham equations using a KohnShamMethod object.

  3. Define the variable params for holding the runtime parameter settings that should be passed on to calculateOptimizedAtomicGeometry(). Here, we have chosen the name h2.nc for the checkpoint file (which is written in the NetCDF file format). In addition, we also choose a high verbosity setting in order to follow the progress of the optimization process.

  4. Do the actual geometry optimization by calling calculateOptimizedAtomicGeometry(). The function calculateOptimizedAtomicGeometry() returns an optimized molecule configuration, which we store in the variable h2_opt.

  5. Print out the optimized coordinates of the hydrogen molecule.

If you run the script, the last few lines of the output will be

Atom coordinates
---------------------------
 0.4000  0.0000  0.0000
-0.4000  0.0000  0.0000

This is not exactly what we had in mind. The initial coordinates of the two hydrogen atoms already satisfy the accuracy of the algorithm used by calculateOptimizedAtomicGeometry(), implying that the atom positions are left untouched by the optimization process. To improve the geometry of the hydrogen molecule, we should have specified a smaller value for the force tolerance criteria used by calculateOptimizedAtomicGeometry(). This is done using the helper function geometricOptimizationParameters() as shown below.

opt = geometricOptimizationParameters(
    force_tolerance=0.00001*electronVolt/Angstrom
    )

Here, we have specified a force tolerance of 10^{-5} eV/Å contrary to the default setting of 5 \cdot 10^{-2} eV/Å. The defined variable opt is then passed as an argument to calculateOptimizedAtomicGeometry() using the optional variable optimization_parameters.

Now, to avoid a complete restart of the calculation, we use the ATK function restoreSelfConsistentCalculation() to load the checkpoint file h2.nc. This is done as

scf = restoreSelfConsistentCalculation('h2.nc')

So, in summary, here is what we should do

  1. Load the checkpoint file using restoreSelfConsistentCalculation().

  2. Set the desired force tolerance by calling the helper function geometricOptimizationParameters().

  3. Resume the calculation by calling calculateOptimizedAtomicGeometry().

The full script implementing the above strategy becomes

# Import the KohnSham module from ATK
from ATK.KohnSham import *

# Read self consistent calculation from NetCDF file
scf = restoreSelfConsistentCalculation('h2.nc')

# Set the force tolerance
opt = geometricOptimizationParameters(
    force_tolerance=0.00001*electronVolt/Angstrom
    )

# Set name of check point file
params = runtimeParameters(
    verbosity_level=10
    )

# Calculate the optimized atomic geometry
h2_opt = calculateOptimizedAtomicGeometry(
    self_consistent_calculation=scf,
    optimization_parameters=opt,
    runtime_parameters=params
    )

# Print the coordinates of the individual atoms
print '\nAtom coordinates\n---------------------------'
for coord in h2_opt.cartesianCoordinates():
    for c in coord:
        print '%7.4f' % (c.inUnitsOf(Angstrom)),
    print

h2-restart.py

Running the script gives rise to the following output:

Atom coordinates
---------------------------
 0.3962 -0.0000 -0.0000
-0.3962  0.0000  0.0000

As you probably have noticed, ATK goes through a lot of iterations before the molecule geometry satisfies the prescribed force tolerance.

It is also possible to restart a complete self-consistent field calculation using the executeSelfConsistentCalculation() method in ATK. Using this approach, an almost converged calculation can be restored and the extra cycles needed for convergence can be applied, thereby salvaging precious computing time.

Restarting an aborted calculation

Sometimes when you execute a script, the execution may terminate due to a system crash, or when running the job through a queue system the job may be terminated because the queue wall clock time is exceeded. In both these situations, if you have specified a NetCDF checkpoint file there are certain built-in features of ATK that may help you to minimize the amount of damage.

During a self-consistent calculation, if the checkpoint file has been specified, ATK will automatically save the system state in the checkpoint file after each completed iteration step.

To see how this works in practice, try to run the following script and simulate a system crash by pressing Ctrl-C before the script finishes:

# Import the KohnSham module from ATK
from ATK.KohnSham import *

# Set up elements and positions
elm = [ Hydrogen, Hydrogen ]
pos = [ ( +0.4, 0.0, 0.0)*Angstrom,
        ( -0.4, 0.0, 0.0)*Angstrom ]

# Add them to a configuration
h2 = MoleculeConfiguration(elm,pos)

# Setup Kohn-Sham method
dft_method = KohnShamMethod()

# Set name of check point file
params = runtimeParameters(
    verbosity_level=10,
    checkpoint_filename='h2.nc'
    )

# Calculate the optimized atomic geometry
h2_opt = calculateOptimizedAtomicGeometry(
    atomic_configuration=h2,
    method=dft_method,
    runtime_parameters=params
    )

h2-kill.py

Now, if you look in your working directory, you will find a checkpoint file

h2.nc

The state of the system after the last successful iteration step is then reloaded using the command

scf = restoreSelfConsistentCalculation('h2.nc')

The variable scf now contains the previously obtained SCF-results and the calculation can be resumed using the NanoLanguage command executeSelfConsistentCalculation().

Overwriting VNLFile samples

In many situations, you often use a VNLFile object for storing your molecules, structures, and calculations. Data is added to the VNLFile using the method addToSample(). A typical scenario could for example be

vnl_file = VNLFile('h2.vnl')
vnl_file.addToSample(h2, 'h2')

If you rerun a script that uses this construction, ATK will overwrite the existing sample in the VNLFile. To avoid this , you may either

  • choose a new name for the sample added in the call to the method addToSample().

  • physically rename or copy the VNLFile on the disk

A more elegant solution for making an automatic backup of a VNLFile could be to define your own class, say MyVNLFile, which inherits the VNLFile class. The check for an existing VNLFile can then be performed in the __init__ constructor of MyVNLFile. Here is a working implementation of MyVNLFile

class MyVNLFile(VNLFile):
    def __init__(self, filename, backup=False):

        # Check if VNL file exists
        if ( path.isfile(filename) and backup ):
            rename(filename,filename+'.bak')

        VNLFile.__init__(self,filename)

Here we have added an additional (optional) variable backup to the constructor of MyVNLFile. The VNLFile is only backed up to when MyVNLFile is constructed like

vnl_file = MyVNLFile('h2.vnl', True)
vnl_file.addToSample(h2, 'h2')

It is important to note that removing the old file is just one possible implementation. Alternatively, one could also have chosen to for example rename the old file. The complete example described above is shown here.

# Import the KohnSham module from ATK
from ATK.KohnSham import *
from os import path, rename

class MyVNLFile(VNLFile):
    def __init__(self, filename, backup=False):

        # Check if VNL file exists
        if ( path.isfile(filename) and backup ):
            rename(filename,filename+'.bak')

        VNLFile.__init__(self,filename)


# Set up elements and positions
elm = [ Hydrogen, Hydrogen ]
pos = [ ( +0.4, 0.0, 0.0)*Angstrom,
        ( -0.4, 0.0, 0.0)*Angstrom ]

# Add them to a configuration
h2 = MoleculeConfiguration(elm,pos)

# Open a VNL file and add the molecule to it
vnl_file = MyVNLFile('h2.vnl', True)
vnl_file.addToSample(h2, 'h2')

MyVNLFile.py

Now a VNLFile named h2.vnl.bak should exist.

Specifying different basis sets for several different atoms

It is possible to specify an individual basis set for each atomic element in a given structure either by using the function basisSetParameters()

basis_set_params_Li = basisSetParameters(
    type = DoubleZetaPolarized,
    element = Lithium
)

basis_set_params_H = basisSetParameters(
    type = SingleZetaPolarized,
    element = Hydrogen
)

or by constructing a basis set variable for each element and using them in a list in the SCF-calculation method in the following way

dft_method = KohnShamMethod(
    [basis_set_params_Li, basis_set_params_H],
    exchange_correlation_type = LDA.PZ
)

The SCF-calculation will now be performed using a SingleZetaPolarized and DoubleZetaPolarized basis set for hydrogen and lithium, respectively. Had we not explicitly specified those basis sets, ATK would have used the DoubleZeta basis set as default for both elements.

Extracting data points from a transmission spectrum

A calculated transmission spectrum can be visualized in VNL, but it is also possible to use other visualization tools.

The data points representing a calculated transmission spectrum can be extracted in the following way:

from ATK.TwoProbe import *
 
old_scf_zero_bias = \
    restoreSelfConsistentCalculation("old_scf_file.nc")

temporary_list = range(-50,50,1)
energy_list = []
for i in temporary_list:
    energy_list.append(i/10.0)

trans_spectrum = calculateTransmissionSpectrum(
       old_scf_zero_bias,
       energies = energy_list*electronVolt
       )

trans_energy = trans_spectrum.energies()
trans_coeff = trans_spectrum.coefficients()
trans_energy = trans_energy.tolist()
trans_energy.reverse()
trans_coeff = trans_coeff.tolist()
trans_coeff.reverse()

while len(trans_energy)>0:
    print trans_energy.pop(),'\t',trans_coeff.pop()

The energy range and file name old_scf_file.nc filename should, of course, be adapted to your specific case. This script will print the calculated transmission coefficients and energies in two columns to the screen. The column based output can then be used as input for your favorite visualization.

The trick is to use the methods energies() and coefficients() of the TransmissionSpectrum object. These allow the extraction of the values contained within the object into a NumPy array. We then turn these NumPy arrays into simple lists using the tolist() method of the NumPy array. The lists are then reversed (using the reverse() method) which allows us to extract values from the lists using the list method pop().

Generating VNL files from parallel runs

If you wish to store data in a VNLFile when executing ATK in parallel, some additional precautions must be taken, since only the master process should be responsible for handling VNLFile related I/O tasks. This, however, can easily be controlled from within ATK by using the Boolean function processIsMaster(), which is included in the ATK module ATK.MPI. processIsMaster() returns True if called by the master process and False otherwise. A very simple in-line fix to handle VNLFile I/O could therefore be

# Open a VNL file (MPI-safe)
from ATK.MPI import *
...
if ( processIsMaster() ):
    vnl_file = ParallelVNLFile('h2.vnl')
    vnl_file.addToSample(h2, 'h2')

A more elegant solution would be to define a new class ParallelVNLFile which inherits the original VNLFile class. Both in the __init__ constructor and in the user-defined implementation of addToSample(), you will have to check whether these methods are called by the master or a slave process. A possible implementation of the class ParallelVNLFile is

class ParallelVNLFile(VNLFile):

    # New constructor with support for removing the VNLFile
    def __init__(self, filename, force=False):

        # Only master process should enter here
        if ( not processIsMaster() ):
            return

        # Check if VNL file exists
        if ( path.isfile(filename) ):

            # and remove it if force = True
            if (force):
                remove(filename)

        VNLFile.__init__(self, filename)

    # MPI-safe addToSample()
    def addToSample(self, sample, sample_name):

        # Only master process should enter here
        if ( not processIsMaster() ):
            return

        VNLFile.addToSample(self, sample, sample_name)

which then can be used in your ATK script as

vnl_file = ParallelVNLFile('h2.vnl', True)
vnl_file.addToSample(h2, 'h2')

Observe that the ParallelVNLFile class also includes support for overwriting a VNLFile. Consult Overwriting VNLFile samples for a discussion of when and why this functionality is relevant.

Importing an XYZ-file

Since NanoLanguage is a Python based scripting language it is easy to create extensions which will make your everyday life easier. A script is provided below which transforms the structural data contained in an XYZ-file into the VNLFile format.

from ATK.KohnSham import *

'''
NanoLanguage XYZ import filters

Description:

3 utility routines for specifying atom positions in XYZ format in NanoLanguage

- xyzBlockToLists: Converts a sequence of lines with the format "element x y z" to two
                   lists [elements,positions]

- importXYZFile: Reads an XYZ file from disk and uses xyzBlockToLists to convert
                 the atom positions to two lists [elements,positions]
                 
- importXYZFile: Same as above, only the XYZ file should not contain the two initial lines
                 (number of atoms and comment). Very useful for copy/pasted atom lists!

For more details, see each individual routine

Notes:

* At the moment, these routines can only read single-configuration xyz files.
* If the xyz file contains more atoms than specified on line 1, the remaining ones will be ignored.
* If the xyz file contains fewer atoms than specified on line 1, an error will be raised.
* Errors will be raised on all format errors.
* No units are attached to the positions; this is up to the user to handle.
'''


def convert(line,types=()):
    '''
    Utility routine used by xyzBlockToLists to parse a single line in the xyz format
    This is the place where we enforce the xyz format
      element x y z
    where element is an arbitrary string, and x y z are floats
    We allow an arbitrary amount of leading and trailing white spaces, 
    plus any number and any kind of white spaces as separators
    '''
    import re
    a = re.split('\s*',line.strip())
    for i in range(len(types)):
        a[i] = apply(types[i],[a[i]])
    return tuple(a)

    
def xyzBlockToLists (xyz_block,number_of_atoms=None):
    '''
    Converts a block (input argument "xyz_block") of the format
    
      element1 x1 y1 z1
      element2 x2 y2 z2
      ...
      elementN xN yN zN
      
    to two lists, one with the elements (in NanoLanguage format, i.e. as objects)
    and one with positions (without unit!), and returns these as
    
      [elements,positions]
    
    If N is given as input (input argument "number_of_atoms), the xyz block must 
    contain at least this number of lines (additional ones will be ignored). 
    If N is not given, all lines are converted.
    
    Strict checking of the xyz format is performed. This means, that only
    initial and/or trailing blank lines are allowed (internal blanks lines are not 
    allowed in the xyz format), and any line not conforming to the xyz format
    will raise an error.
    
    This can easily be modified, to allow for e.g. comments (start line with #),
    as indicated in a commented-out piece of code below.
    '''  
    import re
    # Handle DOS files as well as Unix
    all_lines = re.split('\n',xyz_block.replace('\r',''))
    # Strip off all leading and trailing blank lines
    while all_lines[-1]=='':
        all_lines = all_lines[0:-1]
    while all_lines[0]=='':
        all_lines = all_lines[1:]
    # If number of atoms is not known, attempt to convert all lines
    if number_of_atoms==None:
        number_of_atoms = len(all_lines)
    # Otherwise, convert as many as requested 
    # (first we check that there are these many lines in the block)
    elif len(all_lines)<number_of_atoms:
        raise Exception('Number of atoms is %i, but only %i lines found in the xyz block' % (number_of_atoms,len(all_lines)))
    # Count lines for reference in error messages 
    # and to exit when the number of wanted lines is read
    line_ix = 0
    # Store elements and positions
    elements = []
    positions = []
    for line in all_lines:
        line_ix = line_ix+1
        # Read until we have the desired number of atoms
        if line_ix>number_of_atoms:
            break
        try: 
            element,x,y,z = convert(line,(str,)+(float,)*3)
            positions.append([x,y,z])
        except:
            raise Exception('Invalid format ("element x y z" expected) on line %i (%s)' % (line_ix,line))
        try: 
            elements.append(eval("PeriodicTable.%s" % element))
        except:
            raise Exception('Invalid element on line %i (%s)' % (line_ix,line))
    return (elements,positions)


def importXYZFile (filename):
    '''
    Read an xyz file from disk (file name given as "filename"),
    and convert it to two lists (returned)
    
      [elements,positions]
      
    by using xyzBlockToLists above.
    
    The file must correspond to the xyz format (right now, this can only handle
    single configuration files!), meaning
    
        N (number of atoms)
        comment
        element1 x1 y1 z1
        element1 x2 y2 z2
        ...
        elementN xN yN zN

    The comment is discarded. If the file contains more atoms than N,
    the additional ones will be ignored. No blank lines are allowed.
    '''
    f = open(filename,'r')
    try:
        s = f.readline()
        number_of_atoms = int(s)
    except:
        raise Exception('Invalid format (expected an integer number) on line 1: %s' % s)
    try:
        comment = f.readline()
    except:
        raise Exception('Invalid format on line 2: File ended abruptly')
    try:
        xyz_block = f.read()
    except:
        raise Exception('Invalid format on line 3: File ended abruptly')
    return xyzBlockToLists(xyz_block,number_of_atoms)

    
def importSimplifiedXYZFile (filename):
    '''
    Read a "simplified" xyz file from disk (file name given as "filename"), 
    and convert it to two lists (returned)
    
      [elements,positions]
      
    by using xyzBlockToLists above.
    
    The file must correspond to the xyz format (right now, this can only handle
    single configuration files!), but should NOT contain the initial
    two lines (number of atoms and comment) meaning
    
        element1 x1 y1 z1
        element1 x2 y2 z2
        ...
        elementN xN yN zN

    All lines are read; no blank lines are allowed.
    '''
    f = open(filename,'r')
    xyz_block = f.read()
    if xyz_block=='':
        raise Exception('Invalid format on line 1: File ended abruptly')
    return xyzBlockToLists(xyz_block)

import_xyz.py

The organic alcohol 3-octanol.

Figure 68: The organic alcohol 3-octanol.


Now, suppose you have a file 3-octanol.xyz which contains the chemical structure of 3-octanol in an XYZ-format as shown below:

27
3-octanol
H    8.75072    -2.4733    2.54656
H    11.2604    -4.14431    3.82607
H    11.715    -5.8038    3.84934
H    11.6474    -4.95623    2.27507
C    11.1617    -5.07225    3.25095
H    9.33521    -5.55013    4.12477
H    9.62311    -6.44956    2.6051
O    9.21734    -3.19852    2.99613
C    9.69108    -5.46985    3.09219
H    9.29706    -4.35725    1.28281
C    8.91344    -4.40821    2.30845
H    7.16157    -4.71225    3.38853
H    7.20964    -5.67055    1.87836
H    6.99798    -2.66424    2.05897
C    7.40753    -4.68768    2.32166
H    6.90682    -3.59278    0.52793
C    6.63492    -3.58604    1.58988
H    5.00393    -3.82421    2.85921
H    4.78426    -4.66796    1.29681
H    4.87008    -1.67629    1.69415
C    5.12218    -3.74055    1.7737
H    4.51297    -2.47743    0.130426
C    4.37134    -2.52746    1.21631
H    2.72864    -2.48623    2.65878
H    2.41386    -3.49026    1.21167
C    2.88355    -2.56995    1.5779
H    2.35393    -1.6987    1.17539

This structure can easily be converted into a VNL file format using the following short NanoLanguage script:

from ATK.KohnSham import *

# Import 3-octanol XYZ-file 
from import_xyz import *
(new_vnl_elements,new_vnl_positions) = importXYZFile ('3-octanol.xyz')

# Create NanoLanguage molecule
new_vnl_molecule = MoleculeConfiguration(
    elements=new_vnl_elements,
    cartesian_coordinates=new_vnl_positions*Angstrom
    )

# Save in VNL file    
vnlfile = VNLFile('3-octanol.vnl')
vnlfile.addToSample(new_vnl_molecule,'3-octanol')

import_xyz-test.py

Of course you should have downloaded the import_xyz.py file and have it in the same directory as your other files. This procedure could be extended to convert entire directories filled with structures in an XYZ-format into the VNL file format. This way it is easy to convert all of your old structural files into a format compatible with ATK and VNL.

Timing a calculation

The version of Python supplied with ATK includes the datetime module which can be used for timing a calculation. Here, we will show you how this can be done.

Suppose you want to perform a series of calculations and keep track of how long it takes to perform these calculations. Below is a script which returns an object which can display the time, at which the object was created.

def atkTimestamp():
    '''
    Create a timestamp string without the microseconds
    @return A datetime object which does not display
            microseconds as they have been set to zero.
    '''
    import datetime
    current_time=datetime.datetime.now()
    current_time=current_time.replace(microsecond=0)
    return current_time

timestamp.py

By placing the timestamp.py file in the same directory as our ATK script we can input it and get access to the function atkTimestamp. Say, you would like to time the self-consistent calculation of a Hydrogen using different basis-sets.

from ATK.KohnSham import *
from timestamp import atkTimestamp

basis_sets = [
    SingleZeta,
    SingleZetaPolarized,
    DoubleZeta,
    DoubleZetaPolarized,
    DoubleZetaDoublePolarized
    ]

atoms = [Hydrogen]*2

positions = [(0.0,0.0,0.0),(0.0,0.0,0.802)]*Angstrom

hydrogen_molecule = MoleculeConfiguration(
    elements=atoms,
    cartesian_coordinates=positions
    )

for basisset in basis_sets:
    basis_set_params = basisSetParameters(
        type=basisset,
        element=Hydrogen
        )
    dft_method = KohnShamMethod(
        basis_set_parameters=basis_set_params
        )
    
    start = atkTimestamp() 
    print 'SCF calculation started at ',
    print start
    scf = executeSelfConsistentCalculation(
        method=dft_method,
        atomic_configuration=hydrogen_molecule
        )
    end =  atkTimestamp()
    print 'SCF calculation ended at ',
    print end
    print 'Calculation took: %s' % str(end-start)
    print 50*'-'

time-hydrogen.py

Here, we import our atkTimestamp() function, which will return the time of the system in seconds as a datetime. Datetime objects can be subtracted and added (which we do in the latter part of the for-loop. Running the above script should result in output similar to what is shown below (with different dates and times of course):

SCF calculation started at  2007-04-16 12:37:27
SCF calculation ended at  2007-04-16 12:37:30
Calculation took: 0:00:03
--------------------------------------------------
SCF calculation started at  2007-04-16 12:37:30
SCF calculation ended at  2007-04-16 12:37:34
Calculation took: 0:00:04
--------------------------------------------------
SCF calculation started at  2007-04-16 12:37:34
SCF calculation ended at  2007-04-16 12:37:38
Calculation took: 0:00:04
--------------------------------------------------
SCF calculation started at  2007-04-16 12:37:38
SCF calculation ended at  2007-04-16 12:37:44
Calculation took: 0:00:06
--------------------------------------------------
SCF calculation started at  2007-04-16 12:37:44
SCF calculation ended at  2007-04-16 12:37:51
Calculation took: 0:00:07
--------------------------------------------------

If you have a directory where you like to keep all your 'helper' scripts, you can add the path to this directory using the following construction:

import sys
sys.append('<em class="replaceable"><code>/home/$username/python_library_functions/</code></em>')
      

If you place the above two lines at the top of your ATK script, you can easily import anything in the /home/$username/python_library_functions/.

Convergence of two-probe calculations

The convergence of a calculation for a two-probe system is not a trivial issue. "Simple" systems (such as the two semi-infinite linear chains of lithium atoms with a single hydrogen molecule positioned between the two electrode chains described in Li-H2-Li two-probe system) converge after slightly more than a dozen iterations. However, the distinction between "simple" and "complex" systems is not straightforward and cannot directly be connected with the dimensionality of the system in study. In fact, more "complex" systems exist which would require hundreds of iterations before convergence is achieved, according to the default choice of the tolerance and convergence criterion. In some special case, the convergence to the desired tolerance appears to be so slow that the quantity that is used to measure whether the convergence tolerance has been reached seems rather to oscillate indefinitely around a given value than becoming smaller and smaller.

In this section, we give some general tips on the choice of parameters and procedures, which may facilitate faster convergence of two probe systems that turn out to be challenging or slow to converge with default parameters.

Choosing the appropriate parameters

The convergence behavior of a two-probe calculation can depend to a large extent on the parameters which are set up for the calculation. For several system, a speed up of the convergence can be achieved by increasing the electron temperature. As an example, the increase of the electron temperature from a value of 300 K to 1000-2000 K in the case of the spin polarized system Fe-MgO-Fe presented in Spin-polarized two-probe system improves in a drastic way the convergence behavior of this two-probe system.

Setting up the initial electron density from an equivalent bulk calculation

The initialization of the self-consistent calculation for a two-probe system requires a guess for the initial real-space electron density. This can be taken to be as one of the two following choices:

  • Equivalent bulk: The initial density is derived from a self-consistent calculation for the equivalent bulk system of the two-probe system.

  • Neutral atom density: The initial density is obtained as a superposition of the atomic electron densities of the atoms in the two-probe system.

Since the release of ATK 2.3, the first choice is taken as default for both systems where the left and right electrode parameters have been set-up identically and for two-probe systems with heterogeneous electrodes. This choice has been shown to improve (even drastically) the convergence behavior of a large number of two-probe calculations. However, the memory usage in performing the calculation for the equivalent bulk (including the central region and one unit cell of the left and right electrodes) could be quite large. In order to make feasible calculations for large systems which would require a very large memory usage (the critical size being dependent on the available computer resources), the initialization might be performed using the option neutral atom density.

The following script shows how to explicitly perform a calculation using as initial guess the neutral atom density for a two-probe system with a Li-H2-Li set-up. It requires the VNLFile lih2li.vnl to exist.

from ATK.TwoProbe import *
import ATK
ATK.setVerbosityLevel(2)

# Read the atomic configuration from an VNL file.
vnl_file = VNLFile("lih2li.vnl")
configurations = vnl_file.readAtomicConfigurations()
two_probe_conf = configurations["lih2li"]

# Reduce basis set size for Li
basis_set_params = basisSetParameters(
    type = SingleZeta,
    element = Lithium
    )

# Define electrode method
electrode_bz = brillouinZoneIntegrationParameters( (1,1,100) )
electrode_params = ElectrodeParameters(
    brillouin_zone_integration_parameters = electrode_bz
    )

# Initialize the electron density
two_probe_params = twoProbeAlgorithmParameters(
    initial_density_type = InitialDensityType.NeutralAtom
    )

# Define the two-probe method
twoprobe_method=TwoProbeMethod(
    electrode_parameters = (electrode_params, electrode_params),
    basis_set_parameters = basis_set_params,
    algorithm_parameters = two_probe_params
    )

# Specify checkpoint file
runtime_params = runtimeParameters(
    checkpoint_filename = 'lih2li-scf-neutral-atom-density.nc' 
    )

# Perform the self-consistent two-probe calculation
scf = executeSelfConsistentCalculation(
    atomic_configuration = two_probe_conf,
    method = twoprobe_method,
    runtime_parameters = runtime_params
    )

neutral-atom-density.py

Constrained calculations

Since the release of ATK 2.3, the default for the self-consistent calculation of a two-probe system uses an unconstrained algorithm which does not impose any constraint on the real-space density of the system. This choice differs from the one used in the ATK versions up to ATK 2.2, in which the real-space density in the electrode region is constrained to be identical to the one of the corresponding bulk. Even if the unconstrained algorithm is more theoretically grounded than the previous one, in some very special case the convergence of the two-probe calculation could be affected by some problems like, e.g. a slow convergence behavior or a tendency to loose electronic charge in the central region. If this happens, the following procedure might be used to stabilize the calculation.

  • Step 1 - Constrained run: Perform a self-consistent constrained calculation keeping fixed the real-space density in the electrode regions by setting the parameter electrode_constraint to ElectrodeConstraints.RealSpaceDensity.

  • Step 2 - Unconstrained run: Use the result of the previous calculation (Step 1) for starting an unconstrained calculation.

The following script shows how to apply this procedure to a two-probe system with a Li-H2-Li set-up. It requires the VNLFile lih2li.vnl to exist.

from ATK.TwoProbe import *
import ATK
ATK.setVerbosityLevel(2)

# Restore old two-probe configuration
vnl_file = VNLFile("lih2li.vnl")
configurations = vnl_file.readAtomicConfigurations()
two_probe_conf = configurations["lih2li"]

# Reduce basis set size for Li
basis_set_params = basisSetParameters(
    type = SingleZeta,
    element = Lithium
    )

# Define electrode method
electrode_bz = brillouinZoneIntegrationParameters( (1,1,100) )
electrode_params = ElectrodeParameters(
    brillouin_zone_integration_parameters = electrode_bz
    )

# Define methods for constrained algorithm
constrained_algorithm = twoProbeAlgorithmParameters(
    electrode_constraint = ElectrodeConstraints.RealSpaceDensity
    )
constrained_method = TwoProbeMethod(
    electrode_parameters = (electrode_params,electrode_params),
    basis_set_parameters = basis_set_params,
    algorithm_parameters = constrained_algorithm
    )  

# Execute self-consistent constrained calculation
constrained_scf = constrained_method.apply(two_probe_conf)

# Define methods for unconstrained algorithm (default algorithm)
unconstrained_algorithm = twoProbeAlgorithmParameters(
    electrode_constraint = ElectrodeConstraints.Off
    )
unconstrained_method = TwoProbeMethod(
    electrode_parameters = (electrode_params,electrode_params),
    basis_set_parameters = basis_set_params,
    algorithm_parameters = unconstrained_algorithm
    )

# Specify checkpoint file
runtime_params = runtimeParameters(
    checkpoint_filename = 'lih2li-scf-constrained-unconstrained.nc' 
    )

# Execute self-consistent unconstrained calculation
unconstrained_scf = executeSelfConsistentCalculation(
    atomic_configuration = two_probe_conf,
    method = unconstrained_method,
    runtime_parameters = runtime_params,
    initial_calculation = constrained_scf
    )

constrained_unconstrained.py

Convergence of light-element atoms

Some light-element atom could be difficult to converge using the default parameters set by ATK. In this section, we give some general suggestions for improving the convergence of isolated atoms. These general tips on the choice of parameters and procedures can be used to speed up and enhance the convergence for single atom systems.

  • Choosing the correct electronic temperature

    Atomic and molecular systems have discrete electronic states. In this case, it is physically more correct to use a low electronic temperature, which, at the same time, could improve the convergence behavior.

  • Fully spin-polarized systems

    For fully spin polarized systems, as in the case of the Hydrogen atom, it could be useful for improving the convergence, to set the initial scaled spin to 0.9, instead of using the value corresponding to the full polarized configuration (intial_scaled_spin equals to 1).

  • Choice of the basis set

    When performing calculations on single atoms, the usage of a large basis set is strongly suggested. We recommend the use of basis set not smaller than the default Double Zeta Polarized.

  • Using density matrix mixing instead of Hamiltonian mixing

    The default algorithm for the mixing during the self-consistent iteration is based on mixing the Hamiltonian (IterationMixing.Hamiltonian). However, experience has shown that for light atoms it could be useful to mix the density matrix (IterationMixing.DensityMatrix) instead. Furthermore, increasing the diagonal mixing parameter (diagonal_mixing_parameter) could speed up the convergence.