Li-H2-Li two-probe system

Table of Contents

Introduction

In this tutorial, you will learn how to set up a calculation of a two-probe system. In two-probe systems, two semi-infinite electrodes are coupled by a so-called scattering region (also know as the central region). Typical electrodes include cleaved crystal surfaces of gold, nanotubes, and atomic chains. The scattering region may for example consist of a small molecule, a crystal interface, or a piece of a nanotube (perhaps with an adsorbed molecule).

Modeling the electron density for two-probe systems is generally not a simple task. The periodic boundary conditions are removed in the transport direction, and charge is allowed to flow through the system. This allows us to apply a finite bias across the electrodes, and calculate the current-voltage characteristics of the system. In this tutorial, we will demonstrate how to perform such calculations with ATK. Specifically, you will learn how to

  • construct the geometries of the electrodes and the scattering region, and combine these into a two-probe system

  • configure a self-consistent calculation for a two-probe system and see how various input parameters affect computation time

  • calculate and analyze selected properties, such as the transmission spectrum as well as the associated conductance and current

The specific system we will look at is two semi-infinite linear chains of lithium atoms, with a single hydrogen molecule positioned between the two electrode chains; the system is illustrated in Figure 24. Our motivation for considering this structure is an experimental set-up, where the conductance of a single hydrogen molecule was measured [11]. Even though the hydrogen molecule was positioned between two platinum wires, a general understanding of the system can still be obtained by using lithium chains instead; in addition, this also reduces the computational requirements of the calculation substantially. We will investigate why this system has a nearly perfect conductance despite the huge energy gap between the highest occupied molecular orbital (HOMO) and the lowest unoccupied molecular orbital (LUMO) of the hydrogen molecule.

[Note] Note

If you wish to reproduce the results for the Li-H2-Li two-probe system obtained with ATK 2.0, please consult the Changes since ATK 2.0 section. Please, see Comparing with old versions section for more details regarding differences between different ATK versions.

Prerequisites

Before you start this tutorial, we recommend that you have a basic understanding for running various types of calculations using NanoLanguage and ATK. In particular, experience with basic Python elements such as lists and for-loops, as well as the usage of NanoLanguage functions to define system geometries and the numerical parameters for the calculations will be important. These subjects are all covered in the first couple of tutorials, such as Geometry of a water molecule, Calculating molecular properties, and The band structure of bulk silicon.

Basic concepts of a two-probe setup

In ATK, a two-probe system is created in three steps

  1. Constructing the electrodes.

  2. Setting up the scattering region.

  3. Combining the electrodes and scattering region into a single system.

During the calculations, the two electrodes will be treated as bulk systems and must therefore be constructed as such. The scattering region contains the molecules (or a similar interface) located between the two electrodes. In this example, we will construct a two-probe system consisting of two lithium electrodes positioned on each side of a hydrogen molecule. The system is shown in Figure 24.

If you want to read more on the geometric considerations when constructing a two-probe system, you should consult the tutorial Understanding two-probe geometry which gives a more in-depth explanation of how to create a two-probe system.

A preview of the two-probe system studied in this tutorial: Purple/dark corresponds to lithium atoms and white/light corresponds to hydrogen. The green/dark boxes indicate the unit cell of the electrodes. In the calculations, the electrodes are treated as bulk systems whereas the scattering region in the middle is treated as an open system.

Figure 24: A preview of the two-probe system studied in this tutorial: Purple/dark corresponds to lithium atoms and white/light corresponds to hydrogen. The green/dark boxes indicate the unit cell of the electrodes. In the calculations, the electrodes are treated as bulk systems whereas the scattering region in the middle is treated as an open system.


Constructing the two-probe system

The ATK tools we need for constructing the two-probe system are located in the module ATK.TwoProbe. You gain access to these by including the following statement at the top of the ATK input file

from ATK.TwoProbe import *

instructing ATK to load all objects and functions from the ATK.TwoProbe module.

Constructing the lithium electrodes

First, we need to construct the unit cell of the electrodes. The unit cell is spanned by three linearly independent vectors. We shall refer to these as A, B, and C, whereas there respective axis are referred to as the A, B, and C direction. In ATK, the C direction should always coincide with the Cartesian Z-axis. In addition, both of the vectors A and B must be perpendicular to the C. These criteria must be fulfilled since the numerical models used for two-probe systems in ATK require that periodic boundary conditions in the AB-plane (perpendicular to the transport direction).

We want our electrodes to consist of one-dimensional chains of lithium atoms. Due to the periodic boundary conditions just mentioned, these wires will be repeated periodically in the AB plane. We must therefore ensure that the electrode unit cell is sufficiently large in these directions, to avoid interactions between the parallel lithium chains. Fortunately, the localized basis sets used in ATK make it possible to keep the interaction range relatively small.

Note, that the interaction range ultimately depends on the parameters for the basis set; most often, however, the basis set interaction range is smaller than the range of the electrostatic interactions. Therefore, the required separation of the chains should be determined in a systematic way, for example by looking at the total energy of two lithium atoms as a function of the lithium-lithium distance. This is beyond the scope of this example, so for the time being we will settle with an estimate, and use a separation three times the distance between the Li atoms in the chain.

ATK is a first-principles code. This means, that no experimental input parameters are required in the calculations (there are, of course, certain numerical parameters determining e.g. the accuracy). However, we obviously need to specify the geometry of the system that we wish to study. In some cases, you use atomic positions and lattice parameters from experiments, but in most situations the best way to ensure consistent results from a numerical simulation is to optimize or relax the geometry using the same method that will be used for the final calculation of the desired physical quantity; in our case, the transmission spectrum and current.

In the tutorial Optimizing a lithium chain, we determine the optimized lattice constant of a one-dimensional chain of Li atoms to be 2.90 Å. Furthermore, using a similar procedure as described at the end of the tutorial Spin-polarized oxygen, you can use the function calculateOptimizedAtomicGeometry() to estimate the distance between the Li and H atoms in the central region. The resulting values are used in the script below to define the geometry of the system.

Having defined the geometrical parameters of the system, we now construct the electrode unit cell as follows:

# Li chain lattice constant
a = 2.90
# Construct the electrode unit cell
unit_cell = [ [3*a, 0.0, 0.0 ],
              [0.0, 3*a, 0.0 ],
              [0.0, 0.0, 4*a ] ] * Angstrom

Technically, the unit cell is a list containing nine objects of type PhysicalQuantity stored in three lists. A PhysicalQuantity is how a number associated with a unit is represented in ATK. The 3 lists represent the unit cell vectors A, B and C; by setting all off-diagonal elements to zero, we create a rectangular unit cell (a simple tetragonal unit cell).

Observe how we use the variable a to relate everything to a single length-scale. This way, we can easily change the chain lattice constant by modifying the script in one single place only. It is generally a good idea to create structures in this way. It might be more cumbersome typing everything the first time, but it will save time, should you want to modify the geometry of the system later on. In fact, the setup of the two-probe system presented here is on purpose rather simplistic, in order to highlight the important elements of NanoLanguage rather than to present fancy Python programming. For a more sophisticated version, please have a look at the script at the end of this tutorial.

When defining the electrode in NanoLanguage, we have to specify both the elements as well as the absolute or relative position of each atom in the electrode chain. We do this using the PeriodicAtomConfiguration method:

# Define the electrode
electrode_Li = PeriodicAtomConfiguration(
    super_cell_vectors=unit_cell,
    elements=4*[Lithium],
    fractional_coordinates=[(0.5, 0.5, float(i)/4.0) for i in range(0,4)]
    )

We use the predefined unit_cell as the electrode unit cell and specify the constituents (elements) of the electrodes as four lithium atoms positioned in the middle of the cell along the A and B-axes and distributed evenly along the C-axis. Since the unit cell is 4 × 2.9 Å wide and there are 4 atoms, the distance between the atoms becomes 2.9 Å, as desired. To avoid changing the positions if the size of the cell changes, which would be necessary if the positions were specified as absolute values, fractional (relative) coordinates is generally preferred when dealing with atoms in unit cells.

The electrodes of the two-probe system visualized in Virtual NanoLab.

Figure 25: The electrodes of the two-probe system visualized in Virtual NanoLab.


Constructing the central scattering region

The geometry of the electrodes is now well-defined, and we turn to the definition of the central region, located between the two lithium probes. In our case, this consist of a hydrogen molecule located between three lithium atoms on both sides. These lithium atoms should be considered as an extension of the electrodes providing screening, such that the presence of the scattering hydrogen atoms is not felt inside the actual electrodes. This is a fundamental assumption in the two-probe algorithm used in ATK, namely that the electronic structure of the electrodes is bulk-like. The electron density around the lithium atoms in the central region may be perturbed by the hydrogen atoms. This perturbation, however, should decay towards the bulk density as it approaches the electrodes to both sides. For this reason, it is often necessary to include several "electrode" atoms (or layers, in the case of crystalline electrodes) in the central region.

The exact number of necessary screening layers can be hard to estimate up front. Often, a systematic study is needed, where the number of screening layers are increased until the results no longer change. In this example, we will simply assume that three layers are sufficient (it is not, as we will later see in the section Voltage drop in the next tutorial, Further analysis of two-probe systems).

As noted above, the interatomic distances between the atoms have can be determined through a geometry optimization. Below, we define these distances as variables, and together with the chain lattice constant, we can define the positions of all the atoms in the central region in a simple way:

[Tip] Tip

NanoLanguage scripts become more maintainable if you specify your atomic coordinates using variables or functions instead of explicit coordinates. This prevents the situation of having to retype hundreds of coordinates because you decide to increase e.g. the lithium-lithium bond distance. It also reduces the risk of typing errors!

# Setup the two-probe scattering region

# Distances between Li-H and H-H (found from relaxing a Li4H2 cluster)
dist_HH = 0.804
dist_LiH = 2.366

# The atoms in the central region
elements = 3*[Lithium] + 2*[Hydrogen] + 3*[Lithium]

positions = [
    (0.0, 0.0, 0*a),
    (0.0, 0.0, 1*a),
    (0.0, 0.0, 2*a),
    (0.0, 0.0, 2*a + dist_LiH),
    (0.0, 0.0, 2*a + dist_LiH + dist_HH),
    (0.0, 0.0, 2*a + dist_LiH + dist_HH + dist_LiH + 0*a),
    (0.0, 0.0, 2*a + dist_LiH + dist_HH + dist_LiH + 1*a),
    (0.0, 0.0, 2*a + dist_LiH + dist_HH + dist_LiH + 2*a)
    ] * Angstrom
The scattering region visualized in Virtual NanoLab. The hydrogen molecule (white/light) is positioned between the two chains of lithium atoms (purple/dark).

Figure 26: The scattering region visualized in Virtual NanoLab. The hydrogen molecule (white/light) is positioned between the two chains of lithium atoms (purple/dark).


The X or Y coordinates of the atoms in the scattering region are kept at 0.0, because ATK automatically aligns the first and last atom in the scattering region with the last and first atom in the electrodes, respectively. In this way, the scattering region attains the same unit cell as the electrodes. Furthermore, in the final system, the central region atoms are positioned in the middle of the unit cell in the AB plane.

Again, this way of constructing the geometry is not the most elegant method. It is chosen because of readability, but it is not maintainable if you want to include more screening layers. A more elegant script approach is presented below.

Combining electrodes and scattering region

We are now ready to combine the three constituents needed to construct the two-probe system: The two electrodes and the scattering region. We do this using the ATK class TwoProbeConfiguration. Note that we use the same object (electrode_Li) to represent both electrodes; ATK automatically positions the right electrode relative to the central region.

# Combine electrode and scattering region
# into a two-probe system
two_probe = TwoProbeConfiguration(
    electrodes = (electrode_Li,electrode_Li),
    scattering_region_elements              = elements,
    scattering_region_cartesian_coordinates = positions
    )

The constructed TwoProbeConfiguration object stored in the variable two_probe can now be used to perform a self-consistent field (SCF) calculation on the system.

Exporting the structure to a VNLFile

The two-probe system can be visualized in VNL by exporting the object generated by TwoProbeConfiguration to a VNLFile and then importing this into VNL. By exporting the configuration to a VNLFile, we are also able to reuse the configuration in a later calculation by importing and reusing the configuration in a different ATK script. The steps are simple and shown below:

# Export the two-probe system to VNL file.
vnl_file = VNLFile("lih2li.vnl")
vnl_file.addToSample(two_probe, "lih2li")

We first create the file object with a syntax very similar to the open() command in Python. After this, the configuration is added to the lih2li.vnl file by calling the addToSample() method of the VNLFile object. Notice the use of the keyword tag lih2li; this identifier can be used later on, to reuse or load the configuration into another ATK script.

The input file

We have now discussed the details of the different steps that are needed for setting up the two-probe system. Combining all the code snippets discussed above, we end up with the following script for completing the task

from ATK.TwoProbe import *

# Li chain lattice constant
a = 2.90
# Construct the electrode unit cell
unit_cell = [ [3*a, 0.0, 0.0 ],
              [0.0, 3*a, 0.0 ],
              [0.0, 0.0, 4*a ] ] * Angstrom

# Define the electrode
electrode_Li = PeriodicAtomConfiguration(
    super_cell_vectors=unit_cell,
    elements=4*[Lithium],
    fractional_coordinates=[(0.5, 0.5, float(i)/4.0) for i in range(0,4)]
    )

# Setup the two-probe scattering region

# Distances between Li-H and H-H (found from relaxing a Li4H2 cluster)
dist_HH = 0.804
dist_LiH = 2.366

# The atoms in the central region
elements = 3*[Lithium] + 2*[Hydrogen] + 3*[Lithium]

positions = [
    (0.0, 0.0, 0*a),
    (0.0, 0.0, 1*a),
    (0.0, 0.0, 2*a),
    (0.0, 0.0, 2*a + dist_LiH),
    (0.0, 0.0, 2*a + dist_LiH + dist_HH),
    (0.0, 0.0, 2*a + dist_LiH + dist_HH + dist_LiH + 0*a),
    (0.0, 0.0, 2*a + dist_LiH + dist_HH + dist_LiH + 1*a),
    (0.0, 0.0, 2*a + dist_LiH + dist_HH + dist_LiH + 2*a)
    ] * Angstrom
    
# Combine electrode and scattering region
# into a two-probe system
two_probe = TwoProbeConfiguration(
    electrodes = (electrode_Li,electrode_Li),
    scattering_region_elements              = elements,
    scattering_region_cartesian_coordinates = positions
    )

# Export the two-probe system to VNL file.
vnl_file = VNLFile("lih2li.vnl")
vnl_file.addToSample(two_probe, "lih2li")

lih2li-setup.py

Download the script and process it with ATK by issuing the command

atk lih2li-setup.py

This will create the file lih2li.vnl which contains the atomic configuration of our two-probe system.

Performing a self-consistent calculation for the two-probe system

In the following section, we will present the details involved when performing a self-consistent calculation for the two-probe system. In this case, there are also several sub-steps involved in setting up the calculation; we start by going through the details of these step-by-step.

Importing the structure from a VNL file

Once you have processed the script lih2li-setup.py with ATK, a file lih2li.vnl should be located on your disk. You may use this for visualizing the two-probe system in VNL.

We can reload the system setup from lih2li.vnl and use this as our starting point for the calculation. Here is how to do that in ATK

from ATK.TwoProbe import *

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

First, we open the VNLFile, read in its stored atomic configurations using the method readAtomicConfigurations(), and finally pull out the one associated with the tag lih2li from the list returned by readAtomicConfigurations(). The variable two_probe_conf now contains the two-probe configuration as described in the previous section.

Configuring the calculation

The two-probe configuration has now been loaded from the VNLFile. The next step is to set up the parameter values that determine the specifics of the calculation. We will leave most parameters at their default settings, in order not to remove focus from the essentials of two-probe systems. However, quantities such as the mesh cut-off can of course also be adjusted for two-probe systems, as described in the tutorial Calculating molecular properties.

To demonstrate the principle, we will show how to use a smaller basis set for the lithium atoms. This is achieved by calling the function basisSetParameters() like this

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

This construction allows us to specify individual basis sets for each element present in a particular system. Obviously, our choice will yield a cruder description of electrons in the vicinity of the lithium atoms; for this short tutorial, however, the computational gain is worth it. The hydrogen atoms are not affected by the statement above, and will still be modeled the default basis set, which is DoubleZetaPolarized basis set.

The degree of accuracy is also influenced by the sampling resolution used in the reciprocal space when converging the electrodes in the calculation; the more points we choose, the better the resolution. In NanoLanguage, we specify the number of k-points along the lattice vectors by using the brillouinZoneIntegrationParameters() function. For our particular problem, we invoke this as

# Set k-points for electrodes
bz_int_param = brillouinZoneIntegrationParameters( (1,1,100) )

Because of the large separation between the lithium wires discussed above, we have specified a single k-point along the A- and B-axis, since there is no dispersion in the band structure along these axes. Be careful to use several k-points along the C axis to obtain an accurate integral of the density over the Brillouin zone. This is important in order to obtain an accurate value of the Fermi energy, which is needed for the two-probe calculation. Clearly, the choice of k-points affects both the accuracy of the calculation and the required computation time. We also underline that the choice of k-points can influence the convergence of the self-consistent field. In case of poor or slow convergence it's highly suggested to increase the number of k-points. For systems with a three dimensional periodicity, it could be necessary to increase the number of k-points even along the A-and B-axis.

The chosen k-point mesh is added to the electrode description using the class ElectrodeParameters

# Create parameters for electrodes
electrode_params = ElectrodeParameters(
    brillouin_zone_integration_parameters = bz_int_param
    )

ATK needs information about when it should regard a calculation as being converged. You define this threshold by using the iterationControlParameters() function

# Tolerance for convergence (default)
iteration_control_params = iterationControlParameters(
    tolerance = 1e-5
    )

Here, we have specified the default value, so the above setting is superfluous. But this is an important parameter to set, should you want to either lower or increase the accuracy of your calculation. A lower tolerance (corresponding to a higher degree of accuracy) requires more iterations in order for the SCF loop to converge.

Note, that the statements above which define the basis set, the tolerance, as well as other parameters have no effect by themselves; they merely store the parameters in a number of variables. Only when we use these variables in the construction of a TwoProbeMethod, will the parameter settings come into effect:

# Collect parameters into a two-probe calculation method
method = TwoProbeMethod(
    (electrode_params,electrode_params),
    basis_set_parameters = basis_set_params,
    iteration_control_parameters = iteration_control_params
    )
[Note] Note

Notice how we use the same variable, electrode_params, for both electrodes. Had we not done so, the probes would have been regarded as different by ATK and the calculation would take substantially longer. Sometimes, however, using two different electrodes might be exactly what you want to do.

[Note] Note

The parameters apply to the entire system. This is a safe-guard against e.g. setting a different mesh cut-off for the electrode and the central region my mistake, which would be inconsistent and compromise the quality of the results.

Now we are almost done! Having specified our desired degree of accuracy for the calculation, the last step we need to take care of is configuring the output settings for the calculation. This is defined via the function runtimeParameters(), which enables us to set both the output file for the calculation and the verbosity (degree of textual output) used during the calculation:

# Specify verbosity and checkpoint file
runtime_params = runtimeParameters(
    verbosity_level = 10,
    checkpoint_filename = 'lih2li-scf.nc' 
    )

The parameter verbosity_level controls the verbosity, which determines the amount of informative output displayed on your screen during the calculation; here we have chosen the value 10, which corresponds to the highest possible degree of verbosity. A completely mute calculation is the default in ATK (corresponding to verbosity_level equal to 0). The second parameter, checkpoint_filename, sets the filename of the file used for storing checkpoints generated during the calculation; you may then later use this file to either restart or resume your calculation from a different script.

The runtime parameters are effectuated by including them in the call to executeSelfConsistentCalculation(), which executes the self-consistent calculation of our two-probe system:

# Perform SCF calculation with chosen parameters
scf = executeSelfConsistentCalculation(
    atomic_configuration = two_probe_conf,
    method = method,
    runtime_parameters = runtime_params
    )

The input file

The script performing all the steps listed above is shown below

from ATK.TwoProbe import *

# 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
    )

# Set k-points for electrodes
bz_int_param = brillouinZoneIntegrationParameters( (1,1,100) )

# Create parameters for electrodes
electrode_params = ElectrodeParameters(
    brillouin_zone_integration_parameters = bz_int_param
    )

# Tolerance for convergence (default)
iteration_control_params = iterationControlParameters(
    tolerance = 1e-5
    )

# Collect parameters into a two-probe calculation method
method = TwoProbeMethod(
    (electrode_params,electrode_params),
    basis_set_parameters = basis_set_params,
    iteration_control_parameters = iteration_control_params
    )

# Specify verbosity and checkpoint file
runtime_params = runtimeParameters(
    verbosity_level = 10,
    checkpoint_filename = 'lih2li-scf.nc' 
    )

# Perform SCF calculation with chosen parameters
scf = executeSelfConsistentCalculation(
    atomic_configuration = two_probe_conf,
    method = method,
    runtime_parameters = runtime_params
    )

lih2li-scf.py

Please note, that the script above depends on the existence of a file called lih2li.vnl containing a two-probe configuration stored under the name lih2li. This should be created by first running the script described in the previous section Constructing the two-probe system.

Running the calculation

Finally, it is time to run the actual calculation:

atk lih2li-scf.py

Depending on your CPU, it might take some time for this run to finish, so please be patient, go get yourself a cup of coffee or read on while your machine crunches numbers.

During the execution of the self-consistent calculation, you can observe the progress in the terminal window. Compared to the previous tutorials on molecules and bulk systems, you will find that ATK performs no less than three self-consistent iteration cycles for this two-probe system.

  1. First, there is the self-consistent calculation for the electrodes. Since the two electrodes are identical, there is only need to do this once.

  2. Second, ATK will attempt to converge the two-probe system as a periodic bulk system. This is done to generate a good starting guess for the final run, with open boundary conditions. This step in only performed if both electrodes are identical, or more specifically if the two objects in the tuple which defines the electrodes when constructing the TwoProbeConfiguration are the same.

  3. Finally, the real two-probe calculations is performed with open boundary conditions.

For more information about the information printed during the calculation, see the reference page for the function setVerbosityLevel().

After the run has terminated, the file lih2li-scf.nc contains the actual results of the calculation stored in the NetCDF data format. This is the important file to hang on to if you at some point would want to reuse your obtained results.

In the next section, we will show you how to extract actual results from the simulation of the lithium-hydrogen two-probe system.

Analyzing the two-probe system

After the two-probe calculation has finished, it is time to analyze some of the physics that can be extracted from the results of the calculation. Here we first show how to import the resulting data from the calculation performed in the previous section; after that, we demonstrate how various important physical properties can be calculated from these.

Loading a previous calculation

After the calculation has finished, we may load or retrieve the results of the calculation using the restoreSelfConsistentCalculation() function.

from ATK.TwoProbe import *

# Restore initial density from old calculation
zero_bias = restoreSelfConsistentCalculation("lih2li-scf.nc")

This will enable us to analyze our two-probe system without having to redo the time-consuming self-consistent calculation again.

Calculating the transmission spectrum

In order to calculate a transmission spectrum, we need to specify a range of energies over which the spectrum should be calculated. We do this using the convenient NumPy function arange,

# Create a list of energies from -2 to 5 eV, with 0.1 eV spacing
import numpy 
energy_list = numpy.arange(-2.0, 5.0, 0.1)*electronVolt

We now pass these energies to the function calculateTransmissionSpectrum() to perform the calculation. The resulting transmission spectrum is stored in the variable trans_spectrum ; in order to study it in VNL, we save it in a VNLFile:

# Set k-points for transmission
bz_int_param = brillouinZoneIntegrationParameters( (1,1) )

# Calculate transmission spectrum
trans_spectrum = calculateTransmissionSpectrum(
    self_consistent_calculation = zero_bias,
    energies = energy_list,
    brillouin_zone_integration_parameters = bz_int_param
    )

vnlfile = VNLFile("lih2li_trans.vnl")
vnlfile.addToSample(trans_spectrum,'lih2li')

Since our system effectively is one-dimensional, we only require one k-point in the transverse direction (cf. above). This is actually the default value for the k-point sampling, but it is instructive to include it explicitly.

The spectrum can now be studied and plotted by importing the file lih2li.vnl into VNL. The resulting plot is illustrated in Figure 27. Alternatively, the data points could be printed to the screen or a file and visualized using your favorite visualization tool; but we leave that as an example discussed in Extracting data points from a transmission spectrum in the manual section Tips. The complete script for generating a VNL file containing the transmission spectrum can be found at the end of the next section.

The calculated transmission spectrum of the lithium-hydrogen two-probe visualized using VNL.

Figure 27: The calculated transmission spectrum of the lithium-hydrogen two-probe visualized using VNL.


The SingleZeta basis set used for the lithium atoms gives a relatively trivial band structure for the electrodes as reflected in the transmission spectrum. We also see that there is a very high tunneling probability through the hydrogen atoms for essentially all energies within the band. For more further details, see Projected eigenstates.

Conductance

One of the most important concepts of electronic transport in mesoscopic devices is conductance. Having said this, conductance easily becomes a non-trivial and complex issue, since great care has to be taken when defining both the concept itself and a strategy for its calculation. In addition, these steps are typically highly problem-dependent and must therefore be defined specifically for each individual system. We will not go into deeper details with these challenges here, but refer to the rich literature. A good introduction is for example given in [17].

First of all, the simplest definition of the conductance G is

\displaystyle
G = \frac{I}{V}.

Classically, the conductance is just the inverse of the resistance, but in nanoscale devices, there is no reason to assume a constant current-voltage relationship; as a consequence of this, the conductance G will generally be a (complicated) function of the bias voltage V.

The above relationship obviously fails for zero bias; still, the conductance is well-defined in the limit where V goes to zero. We may then use the expression

\displaystyle
G = T(E_\mathrm{F})\, G_0,

where T(E_\mathrm{F}) is the transmission coefficient at the Fermi energy E_\mathrm{F} whereas G_0 is the conductance quantum (7.748091733\cdot10^{-5}\ \mathrm{S}). This expression is exact in the limit where the temperature goes to zero.

Using NanoLanguage, we can define our own function for calculating the conductance. Below is shown how the conductance for a zero bias two-probe sample can be calculated from the transmission coefficient at Fermi energy. By reusing the electron density from our previously performed calculation, the conductance can be calculated in the following way:

# Define conductance quantum
conductance_quantum = 7.748091733e-5*Siemens

# Calculate transmission spectrum at E_Fermi
fermi_trans = calculateTransmissionSpectrum(
    self_consistent_calculation = zero_bias,
    energies = [0.0]*electronVolt
    )
  
conductance = fermi_trans.coefficients()[0] * conductance_quantum
print 'Zero bias conductance: %.2e Siemens' % (conductance.inUnitsOf(Siemens))

The above code sample produces the following output when executed using the complete script below

Zero bias conductance: 4.08e-05 Siemens

Here is the complete input script for calculating the conductance and producing a VNL file with the transmission spectrum

from ATK.TwoProbe import *

# Restore initial density from old calculation
zero_bias = restoreSelfConsistentCalculation("lih2li-scf.nc")

# Create a list of energies from -2 to 5 eV, with 0.1 eV spacing
import numpy 
energy_list = numpy.arange(-2.0, 5.0, 0.1)*electronVolt

# Set k-points for transmission
bz_int_param = brillouinZoneIntegrationParameters( (1,1) )

# Calculate transmission spectrum
trans_spectrum = calculateTransmissionSpectrum(
    self_consistent_calculation = zero_bias,
    energies = energy_list,
    brillouin_zone_integration_parameters = bz_int_param
    )

vnlfile = VNLFile("lih2li_trans.vnl")
vnlfile.addToSample(trans_spectrum,'lih2li')
    
# Define conductance quantum
conductance_quantum = 7.748091733e-5*Siemens

# Calculate transmission spectrum at E_Fermi
fermi_trans = calculateTransmissionSpectrum(
    self_consistent_calculation = zero_bias,
    energies = [0.0]*electronVolt
    )
  
conductance = fermi_trans.coefficients()[0] * conductance_quantum
print 'Zero bias conductance: %.2e Siemens' % (conductance.inUnitsOf(Siemens))

lih2li-trans.py

The current as a function of bias

The transmission spectrum and conductance calculated above, was determined at zero bias. We now want to calculate the variation of the current through the two-probe system as a function of a steadily increasing bias.

When performing calculations for finite bias, it is strongly recommended always to use a converged self-consistent calculation (for a different bias) as initial guess. Typically this saves computation time, but is often also the only way to achieve convergence of the SCF loop. We will demonstrate how to do this in the script below. For the first bias (which actually will be zero), we therefore load and use the result of the previous calculation:

# Restore initial density from old calculation
scf = restoreSelfConsistentCalculation("lih2li-scf.nc")

To produce the current-voltage data, we will loop over the bias from 0 to 1 Volt, and use the NumPy function arange to generate the voltages. Since we want the 1.0 Volt calculation to be included, we set the stopping criterion to 1.01.

For each voltage, we need to create a new TwoProbeMethod. The left electrode is assigned a positive voltage equal to half the desired bias, while the right electrode is assigned the corresponding negative voltage. The bias over the two-probe system is the difference between these assigned voltages.

# Run bias from 0.0 and 1.0 in steps of 0.1
for voltage in numpy.arange(0.0, 1.01, 0.1):

    dft_method = TwoProbeMethod(
        electrode_parameters=(electrode_params,electrode_params),
        basis_set_parameters = basis_set_params,
        iteration_control_parameters = iteration_control_params,
        electrode_voltages = (voltage/2.0, -voltage/2.0)*Volt    
        )

The other variables used in the definition of the TwoProbeMethod are the same as those used in the zero bias calculation above. These are all part of the complete script (see below).

Furthermore, we store each calculation in a separate NetCDF file, which are distinguished by the bias included in their filename:

    ATK.setCheckpointFilename ('lih2li-bias-%.1f.nc' % voltage)

For each voltage, we can now perform the self-consistent calculation. As mentioned above, we should make sure to use the result from the previous value of the bias as starting guess. This is achieved by using the parameter initial_calculation as shown below. Note, that the output from the calculation is then stored in the same variable scf:

    scf = executeSelfConsistentCalculation(
        atomic_configuration=lih2li,
        method = dft_method,
        initial_calculation = scf
        )

Finally, we calculate and print the current by using the function calculateCurrent():

    current = calculateCurrent(scf)

    print "%.1f\t\t%.2e" %(voltage, current.inUnitsOf(Ampere))

The input file

The steps that we have discussed above for extracting both the current and the conductance for a particular system bias are summarized in the script below. This script requires a working lih2li.vnl containing the atomic configuration as well as a converged electron density stored in a file named lih2li-scf.nc; see the previous sections for instructions on how to generate these files.

from ATK.TwoProbe import *
import numpy 
import ATK

# Restore two-probe geometry
vnlfile = VNLFile('lih2li.vnl')
lih2li = vnlfile.readAtomicConfigurations()['lih2li']

# Use the same parameters for final bias as for zero bias
bz_int_param = brillouinZoneIntegrationParameters(
    monkhorst_pack_parameters=(1,1,100)
    )
electrode_params = ElectrodeParameters(
    brillouin_zone_integration_parameters = bz_int_param
    )
basis_set_params = basisSetParameters(
    type = SingleZeta,
    element = Lithium
    )
iteration_control_params = iterationControlParameters(
    tolerance = 1e-5
    )

ATK.setVerbosityLevel(0)

# Restore initial density from old calculation
scf = restoreSelfConsistentCalculation("lih2li-scf.nc")

print '# Bias (Volt)\tCurrent (Ampere)'

# Run bias from 0.0 and 1.0 in steps of 0.1
for voltage in numpy.arange(0.0, 1.01, 0.1):

    dft_method = TwoProbeMethod(
        electrode_parameters=(electrode_params,electrode_params),
        basis_set_parameters = basis_set_params,
        iteration_control_parameters = iteration_control_params,
        electrode_voltages = (voltage/2.0, -voltage/2.0)*Volt    
        )

    # Store each calculation in a separate NetCDF file    
    ATK.setCheckpointFilename ('lih2li-bias-%.1f.nc' % voltage)
    
    scf = executeSelfConsistentCalculation(
        atomic_configuration=lih2li,
        method = dft_method,
        initial_calculation = scf
        )

    current = calculateCurrent(scf)

    print "%.1f\t\t%.2e" %(voltage, current.inUnitsOf(Ampere))

lih2li-bias.py

Current-voltage curve

After saving it to your disk, the script can be processed through ATK by issuing the command

atk lih2li-bias.py
Current-voltage characteristics for the lithium-hydrogen two-probe system. The drop in current at the highest voltage is due to the small basis set used for lithium, which is not able to accurately describe the band structure.

Figure 28: Current-voltage characteristics for the lithium-hydrogen two-probe system. The drop in current at the highest voltage is due to the small basis set used for lithium, which is not able to accurately describe the band structure.


The code sample above will, when run using the complete script below, give rise to the output shown below. If you plot the output from the lih2li-bias.py script, you should get a plot similar to the one shown in Figure 28.

# Bias (Volt)   Current (Ampere)
0.0             0.00e+00
0.1             3.95e-06
0.2             7.68e-06
0.3             1.10e-05
0.4             1.37e-05
0.5             1.57e-05
0.6             1.69e-05
0.7             1.76e-05
0.8             1.78e-05
0.9             1.71e-05
1.0             1.46e-05

The sign of the current has significance. The convention in ATK is that a positive current means flow of charge from the left electrode to the right, i.e. the electrons are flowing from the right to the left. This direction of the current is obtained when the bias applied to the left electrode is higher than the bias applied to the right electrode (as in the example discussed here). This is consistent with the picture that the current in a circuit flows from the high-voltage (bias) terminal to the low-voltage one, whereas the electrons traveling in the opposite direction.

Advanced geometry setup

The script below implements a slightly more advanced way of setting up the geometry of the Li-H2-Li two-probe system. Initially in the script, we define all geometrical parameters as variables. For this reason, we do not have to change anything in the rest of the script if we want to change for example the number of Li atoms in the electrode or the unit cell size in the AB direction.

from ATK.TwoProbe import *

# ---------------------------------------------------------------------
# Define all system parameters
# All distances in Angstrom
#
# Li chain lattice constant
a = 2.90
# Number of Li atoms in the electrode
N1 = 4
# Number of Li atoms in the screening layers
N2 = 3
# AB unit cell side length
AB = 3*a
# Distances between Li-H and H-H (found from relaxing a Li4H2 cluster)
dist_HH = 0.804
dist_LiH = 2.366
#
# Nothing below here needs to be changed
# to add atoms or change inter-atomic distances
# or the unit cell size
# ---------------------------------------------------------------------

# Construct the electrode unit cell
unit_cell = [ [AB,  0.0, 0.0  ],
              [0.0, AB,  0.0  ],
              [0.0, 0.0, N1*a ] ]

# Define the electrode
electrode_Li = PeriodicAtomConfiguration(
    super_cell_vectors=unit_cell*Angstrom,
    elements=N1*[Lithium],
    fractional_coordinates=[(0.5,0.5,float(i)/N1) for i in range(0,N1)]
    )

# Setup the two-probe scattering region

# The atoms in the central region
elements = N2*[Lithium] + 2*[Hydrogen] + N2*[Lithium]

# List that defines the distances in the C direction between the atoms 
# in the scattering region
C_distances = (N2-1)*[a] + [dist_LiH,dist_HH,dist_LiH] + N2*[a]

# Convert distances to positions
positions = []
z = 0.0
for i in C_distances:
    positions.append( (0.0, 0.0, z) )
    z += i
    
# Combine electrode and scattering region
# into a two-probe system
two_probe = TwoProbeConfiguration(
    electrodes = (electrode_Li,electrode_Li),
    scattering_region_elements              = elements,
    scattering_region_cartesian_coordinates = positions*Angstrom
    )

# Export the two-probe system to VNL file.
vnl_file = VNLFile("lih2li_2.vnl")
vnl_file.addToSample(two_probe, "lih2li")

lih2li-setup-advanced.py

This script can easily be converted into a function, which takes the six parameters that define the system as arguments, and then returns the two-probe geometry as an object. An example of how this works is described in the tutorial Finding an energetic minimum, where a function is used to generate a water molecule based on the bond angle and length.

Summing up

In this example, we have demonstrated how a two-probe system is constructed, initialized, and how its physical properties are simulated and extracted using ATK. Specifically, we have discussed the following key issues

  • How to construct electrodes and scattering regions.

  • Combining electrodes and scattering regions into a two-probe configuration.

  • Performing a self-consistent calculation for a two-probe system.

  • Using ATK tools for analyzing the data obtained from the two-probe experiment, such as the I-V and conductance characteristics, as well as the transmission spectrum.

  • How to export and reuse both calculations, atomic configurations, and two-probe setups.

  • Save computation time by reusing previously converged calculations in new calculations and scripts.

You should now be ready to set up your own two-probe systems and extract results from your calculations using ATK.