In this mini-tutorial we will present a work flow that enables the computation of an I-V curve - the current as a function of the bias - for a two-probe system.´The I-V curve is in many cases the main results of a study in ATK, although there are several steps towards it, and there are many supporting quantities that provide valuable information about the transport mechanisms. One of these quantities, which has a strong physical relevance, is the voltage drop, computed as the difference in the electrostatic potential between the finite-bias calculation and the corresponding zero-bias calculation. This essentially tells you where the resistance is in the device, for a given apvoltagesplied bias.
The calculations will be performed in two steps. First, we will loop over the bias voltages and perform all self-consistent calculations. Then, in a subsequent step, we will compute all the transmission spetra and electrostatic potentials. This work flow is to some extent already presented in the tutorial on basic transport calculations, but we will here extend the analysis a bit and the work flow will be more readily adaptable for any given two-probe system.
This tutorial is somewhat more advanced than others, and it will be assumed that you already have some basic experience of setting up and running calculations with ATK. The basic transport tutorial mentioned just above is an excellent starting point for that.
Setting up the calculation
- We will not concern ourselves here with how to define the geometry of the two-probe system. Instead, we provide a ready script (details on how to build it are available elsewhere) that you can just download and save. Drop the script on the Viewer
to make sure the geometry looks correct.

- Next, drop the downloaded script on the Scripter
and insert a "New Calculator". We will use all default parameters, meaning we will run the calculation with DFT, as opposed to the basic transport tutorial which used the extended Hückel method.
- Define a suitable name and location for the resulting NetCDF file under "Default output file".

- Save the script.
Making a loop over bias voltages (self-consistent part)
- Transfer the script to the Editor, using the "Send to" button
in the lower right-hand corner of the Script Generator.
- Scroll down to the end of the script. The 4 last lines are the ones which actually carry out the calculation, and these are the ones that need to be repeated for each bias.
- First, to define the bias voltages, insert a new line in the script, before the last 4 lines (above device_configuration.setCalculator(calculator)):
for bias in [0., 0.1, 0.2, 0.3]*Volt: Don't forget the trailing colon! (See the appendix below for other way to define the voltage more generally.)
- Then, mark the four last lines in the script and press the TAB button on the keyboard. This will indent the lines, such that they will be executed for each step in the bias loop.

- The first indented line attaches the calculator to the configuration. This needs to be modified to take into account the bias. In addition, to speed the calculation up, the converged state of the previous bias will be used as the initial guess for the next one. Therefore, change the line
device_configuration.setCalculator(calculator) to (remember the retain the indentation)
device_configuration.setCalculator(
calculator(electrode_voltages=(0.5*bias,-0.5*bias)),
initial_state=device_configuration
)
- Note how the device_configuration is used as initial state for the calculation (for the first step, ATK will discover that no previous calculation was performed, and it will just ignore this specification).
- The bias is applied in such a way that we obtain a positive current, meaning a current flow from left to right (i.e. electron flow from right to left!); this is the case when the specified voltage for the left electrode is higher than for the right one.
- The following lines can be left as they are, although you may want to move the line which uses "nlprint" to print out the whole device configuration before the "for" loop over voltages, so that the lists of atomic coordinates are printed once only, and not for each bias. In that case, the line must be unindented again.
The script is now ready to be run. Save it as lih2li_iv.py, and send it to the Job Manager.
You can download the complete finished script to compare with your own. Also, if you prefer not to run all the self-consistent calculations yourself, you can save time by downloading the resulting NetCDF file, and use that for the following step: the calculation of the current and voltage drop.
NOTE: If you apply the methodology in this tutorial to your own device configuration, and if the calculations are rather large, it is a good idea to save the configurations in separate files: nlsave("lih2li_iv_scf_%g.nc" % bias.inUnitsOf(Volt), device_configuration)
In this case you need to modify some of the analysis below, to read the configurations back from the individual files; see the Notes below for more information.
Calculating the I-V curve and voltage drop
We now have a converged calculation for each bias, all stored in the NetCDF file. Next, we need to compute the transmission and voltage drop for each case. For this purpose, we will use the script below; download it and save it in the same folder as your NetCDF file.
# Define input and output NetCDF files here
scf_filename = "lih2li_iv_scf.nc"
analysis_filename = "lih2li_iv_analysis.nc"
# Read all configurations from NetCDF file
configurations = nlread(scf_filename, DeviceConfiguration)
biases = [float(conf.calculator().electrodeVoltages()[0]-conf.calculator().electrodeVoltages()[1]) for conf in configurations]
configurations = [configurations[i] for i in numpy.argsort(biases)]
# First compute the zero-bias potential
for configuration in configurations:
calculator = configuration.calculator()
bias = calculator.electrodeVoltages()[0]-calculator.electrodeVoltages()[1]
if float(bias)==0.:
zero_bias_potential = ElectrostaticDifferencePotential(configuration)
break
for configuration in configurations:
# For each one, extract the bias,
calculator = configuration.calculator()
bias = calculator.electrodeVoltages()[0]-calculator.electrodeVoltages()[1]
# ... calculate and save the transmission spectrum,
transmission_spectrum = TransmissionSpectrum(
configuration=configuration,
energies=numpy.linspace(-2,8,100)*eV,
kpoints=MonkhorstPackGrid(1,1,1)
)
nlsave(analysis_filename, transmission_spectrum, object_id="Transmission %s" % bias)
# Uncomment the line below if you want all transmission spectra in the log file
#nlprint(transmission_spectrum)
potential = ElectrostaticDifferencePotential(configuration)
# Uncomment the line below if you want to save all potentials, and not just the voltage drops
#nlsave(analysis_filename, potential, object_id="Potential %s" % bias)
# Calculate and save the voltage drop (except for zero bias)
if float(bias)!=0.:
voltage_drop = potential - zero_bias_potential
nlsave(analysis_filename, voltage_drop, object_id="Voltage drop %s" % bias)
# Copy geometry to analysis file, for plotting
geometry = nlread(scf_filename, DeviceConfiguration, read_state = False)[0]
nlsave(analysis_filename, geometry)
The script should hopefully be more or less self-explanatory (click above to expand the source code).
Notes
- All the results are stored in a new NetCDF file, and we also copy the device geometry to this file, for plotting convenience.
- Note how we use the bias as a label for each quantity we store in the NetCDF file, via the object_id keyword.
- There is no need to compute the current in the script, this will be done later, see below.
- If you intend to use this script for your own device configuration, make sure to adjust the energy range of the transmission spectrum, as well as the k-point sampling!
- As mentioned above, for larger calculations it's a good idea to save the different bias points in separate files. In that situation, replace the first 6 lines in the analysis script with these (i.e. keep the original lines starting from line 7, "biases = ..."):
# Define input and output NetCDF files here
scf_filename = "lih2li_iv_scf_%g.nc"
analysis_filename = "lih2li_iv_analysis.nc"
biases = [0, 0.1, 0.2, 0.3]
# Read configurations from NetCDF files
configurations = []
for bias in biases:
configurations.append(nlread(scf_filename % bias, DeviceConfiguration)[0])
Also, the second last line in the script should be modified to read
geometry = nlread(scf_filename % 0, DeviceConfiguration, read_state = False)[0]
Note how in case it is also necessary to manually copy the list of biases into the analysis script.
Important: Before running the script, you should open it in an editor and check that the file names on the first two lines reflect your existing NetCDF file with the self-consistent calculations, and the new result file. If you want to run from the Job Manager, both should be specified with full absolute paths (on Windows, make sure to use "/" instead of "\" in file names).
Run the script, either by dropping it on the Job Manager or from the command line. It should take less than a minute to complete.
Plotting the results
- In the VNL main window, click the Analyzer icon
and choose from the menu "Analyzers > IV Curve".
- Drop the analysis NetCDF file on the drop-zone labeled "Drop file here".

- As we see, VNL will plot the I-V curve, and also print out the values of the current in the log window at the bottom. Also the transmission spectra for each bias are shown, shifted vertically by essentially the bias.
- Finally, to visualize the voltage drop, select one of the quantities labeled "Voltage drop" in the NetCDF file, and click "Show" next to "Contour".

- In addition, drag and drop the device configuration in the NetCDF file to the Viewer window that opens to visualize the geometry overlaid on the data.
Finally, let us average the voltage drop over the X/Y plane, to get the profile as function of Z. For this, we will again use the Analyzer tool, but this time with a custom-made script.
Download the analyzer script, and drop it on the Analyzer icon in the main VNL window (you can inspect the source code just below, if you are interested in learning more about writing your own plug-ins to VNL).
-
# Voltage drop analyzer
def analyzer(filename, data_index, plot0):
if filename == None
return None
# Get list of data in file
file_data = nlread(filename,ElectrostaticDifferencePotential)
if len(file_data) == 0:
print "No voltage drop data in file"
return None
elif data_index>=len(file_data):
print "There are only",len(file_data),"voltage drop data objects in the file\n(the first one is data index=0)"
return None
voltage_drop_3d = file_data[data_index]
z_axis_length = voltage_drop_3d.unitCell()[2][2].inUnitsOf(Angstrom)
# Number of grid points in the Z direction
Nz = voltage_drop_3d.shape()[2]
z = numpy.array(range(Nz))*z_axis_length/float(Nz)
dV = numpy.array([numpy.average(voltage_drop_3d[:,:,i]) for i in range(Nz)])
# Convert Ry to eV
dV = dV*(1.*Hartree).inUnitsOf(eV)
plot0 = Plot2D()
plot0.setXLabel("Z (Angstrom)")
plot0.setYLabel("dV (eV)")
plot0.addData([z,dV])
for i in range(Nz):
print z[i],"\t",dV[i]
return plot0
# Initialize the Analyzer
builder = Builder()
builder.title('Voltage drop')
# Set the Analysis function
builder.setAnalyzerGenerator(analyzer)
# Set up a Analysis widget interface
builder.filename('filename', label='Voltage drop file', text='Drop NetCDF file here')
builder.integer('data_index', 0, 'Data index' , min=0)
builder.plot('plot0', label='voltagedrop')
- Drop the NetCDF file with the analysis results on the drop-zone labeled "Voltage drop NetCDF file"

- If the file contains more than one voltage drop, you can choose which one to plot by changing the "Data index". The data for the curve is printed in the log panel, from where it can be copied and pasted into another plotting program.
Notes
- In principle all the calculations above could be carried out in a single script. Our approach will be a bit more transparent, and it is easier adapt the scripts produced by the Script Generator by hand for this work flow.
- We apply the electrode voltages symmetrically, but it doesn't actually matter, since only the difference in the voltages is used for the calculation, at least as long as we do not introduce additional gate electrodes in the system.
- Using the converged state of a lower bias to boot-strap the calculation for a higher bias, is generally the best way to achieve stable convergence for finite-bias calculations.
- One should not put too much emphasis on the results of this calculation. For example, the voltage drop does not level off towards the electrodes, but this is purely an effect of the extremely poor screening in this system. By using a larger X/Y unit cell, and by also increasing the total number of Li atoms in the central region, the effect can be made smaller, but it is hard to avoid completely in this rather artificial system.
Appendix
There are many different ways to set up the voltages. Above we used a simple list, but we could also do a more advanced construction like
for voltage in numpy.linspace(0.,1.,10)*Volt
to get 10 equally spaced points between 0 and 1.
|