Tutorials
Here, we provide tutorials for Perturbo postprocessing. For each tutorial, we first must perform a Perturbo calculation, described here. Perturbo offers several calculation modes, which compute different properties of a material. The results are outputted to a YAML file, which is used as an input to Perturbopy.
Below, we give general information on how to use Perturbopy. We also provide tutorials for each calculation mode:
General information
All Perturbo calculations output a YAML file (default name ‘<prefix>_<calc_mode>.yml.’). This YAML file structure is described on the Perturbo website. It contains the following information:
inputs to the Perturbo calculation (the input parameters field in the YAML file)
information about the material (the basic data field in the YAML file), such as the lattice parameter and lattice vectors.
outputs from the Perturbo calculation (fields that are specific to each
calc_mode
, described in individual tutorials)
We will use this YAML file to export the data to Python with Perturbopy.
Exporting data
The first step is to create a object containing the data. All of the data from the YAML file is stored in Perturbopy objects, which have a datatype specific to their calc_mode
. For example, let’s say the calc_mode
= 'bands'
. To postprocess the results, we should create a Bands
object called si_bands
, using the YAML file as an input. The si_bands
object contains all the data stored in the YAML file, ‘si_bands.yml’.
import perturbopy.postproc as ppy
si_bands = ppy.Bands.from_yaml('si_bands.yml')
Note
Some Perturbo calculation modes output results that are too large to be stored in a YAML file. These results are stored instead in an HDF5 file. If a particular calculation mode requires an HDF5 file, it will be stated in the respective tutorial.
The basic data field of the YAML file stores information about the material from the ‘prefix’_epr.h5 file generated in the qe2pert
step. Each entry of the basic data is stored in separate attributes in the Perturbopy object. For example, here we obtain the lattice parameter (alat) and lattice vectors (lat) used for silicon:
si_bands.alat
>> 10.264
si_bands.lat
>> array([[-0.5, 0. , -0.5],
[ 0. , 0.5, 0.5],
[ 0.5, 0.5, 0. ]])
For full details on all the basic data entries stored as attributes in si_bands
, please refer to the parameters of CalcMode
.
Perturbo outputs are stored in attributes that are specific to their calc_mode
, Please refer to the individual tutorials for more details. Two important databases are used to store k-points and q-points, and to store other physical quantities such as band energies, temperatures, etc.
Remaining data from the YAML file are stored in the CalcMode._pert_dict
attribute. For example, calculation inputs are stored in the inputs field of the CalcMode._pert_dict
dictionary in every Perturbopy object.
si_bands._pert_dict['input parameters']['after conversion']['sampling']
>> 'uniform'
Perturbo outputs that are not physically relevant (such as timing information and parallelization information) are also stored in CalcMode._pert_dict
.
Handling k-points and q-points
In several calculation modes, we work with sets of points in reciprocal space, such as k-points and q-points. These are stored in objects of type RecipPtDB
. For example, let’s look at the kpt
attribute of the Bands
, which stores the k-points used in the bands
calculation.
The N coordinates are stored as a 3xN array in the RecipPtDB.points
attribute of kpt
. For example, to access the first k-point:
si_bands.kpt.points[:, 0]
>> array([0.5, 0.5, 0.5])
The units of RecipPtDB.points
can either be:
crystal: coordinates are in relative coordinates of the reciprocal lattice vectors
cartesian: coordinates are in units of \(\frac{2\pi}{a}\)
To see the units,
si_bands.kpt.units
>> 'crystal'
To change the units,
si_bands.kpt.convert_units("cartesian") # returns the converted units
si_bands.kpt.units
>> 'cartesian'
The kpt
attribute also stores the k-path coordinates, which are the one-dimensional coordinates assigned to each k-point. These would be the x-coordinates on a plot of the band structure.
si_bands.kpt.path
>> array([0., 0.0169809, 0.0339618, ... 3.7386444, 3.7594417, 3.780239])
It is also possible to rescale the k-path, which has arbitrary units.
# Rescale the k-path to a range between 0 and 10
si_bands.kpt.scale_path(0, 10)
si_bands.kpt.path
>> array([0., 0.04492018, 0.08984035, ... 9.88996833, 9.94498417, 10.])
The :py:class`.RecipPtDB` dataype also provides methods to:
search an array of k-points for a particular k-point, and return the indices of the matches (
RecipPtDB.find()
)find the k-path coordinate corresponding to a k-point coordinate (
RecipPtDB.point2path()
)find the k-point coordinate corresponding to a k-path coordinate (
RecipPtDB.path2point()
)
# Finds the index or indices of the k-point [0.5, 0.25, 0.75]
si_bands.kpt.find([0.5, 0.25, 0.75])
>> array([123], dtype=int64)
# Check that this index is correct
si_bands.kpt.points[:, 123]
>> array([0.5 , 0.25, 0.75])
# Find the k-path coordinate corresponding to k-point [0.5, 0.25, 0.75]
si_bands.kpt.point2path([0.5, 0.25, 0.75])
>> array([6.25893072])
# Check that this k-path coordinate is correct
si_bands.kpt.path[123]
>> 6.258930718401667
# Do the reverse: convert from k-path coordinate to k-point
si_bands.kpt.path2point(6.26)
>> array([0.5 , 0.25, 0.75])
Note that, in the case of repeated k-points, both indices will be returned:
# Find the index of the gamma point, which is in the k-points twice
si_bands.kpt.find([0,0,0])
>> array([ 51, 195], dtype=int64)
# Check this result
si_bands.kpt.points[:, 51]
si_bands.kpt.points[:, 195]
>> array([0., 0., 0.])
array([0., 0., 0.])
Note that all three of these functions take two additional inputs: max_dist and nearest. The max_dist (default 0.025) specifies the maximum distance between two k-points to consider them a match. For example,
# Find the index of [0.01, 0.01, 0.01], which is not one of the k-points stored in kpt.points.
# However, its distance from [0,0,0] is 0.017 < 0.025, so the results for [0, 0, 0] are returned.
si_bands.kpt.find([0.01, 0.01, 0.01])
>> array([ 51, 195], dtype=int64)
# Check this result
si_bands.kpt.points[:, 51]
si_bands.kpt.points[:, 195]
>> array([0., 0., 0.])
>> array([0., 0., 0.])
If nearest (default True) is True, only the k-point(s) that are closest to a requested k-point is considered a match, even if other k-points are within the max_dist range. For example, if max_dist = 0.05, then both [0.01, 0.01, 0.01] and [0.02, 0.02, 0.02] lie within that distance from [0, 0, 0]. If nearest = True, only [0.01, 0.01, 0.01] is considered a match. If nearest is False, both are considered matches.
We can also add labels to the k-points. For example, the FCC Brillouin zone identifies [0.5, 0.5, 0.5] as the L point, and [0.5, 0.0, 0.5] as the X point. To add these labels,
si_bands.kpt.add_labels({"L": [0.5, 0.5, 0.5], "X": [0.5, 0.0, 0.5]})
si_bands.kpt.labels
>> {'L': [0.5, 0.5, 0.5], 'X': [0.5, 0.0, 0.5]}
Note these labels can be removed with kpt.remove_labels.
si_bands.kpt.remove_labels(["L"])
si_bands.kpt.labels
>> {'X': [0.5, 0.0, 0.5]}
A dictionary of labels for the FCC lattice can be found in ppy.lattice.points_fcc
.
si_bands.kpt.add_labels(ppy.lattice.points_fcc)
si_bands.kpt.labels
>> {'L': [0.5, 0.5, 0.5],
>> 'X': [0.5, 0.0, 0.5],
>> 'W': [0.5, 0.25, 0.75],
>> 'K': [0.375, 0.375, 0.75],
>> '$\\Gamma$': [0, 0, 0]}
Physical quantities
Physical quantities such as energies, temperatures, mobilities, conductivities, etc are stored in UnitsDict
objects. These objects inherit from and function as Python dictionaries. They have an additional attribute to store the units.
For example in si_bands
, the band energies are stored in the Bands.bands
attribute. This is a UnitsDict
, with keys corresponding to band index and values corresponding to the energies of that band along the k-point path. It functions as a Python dictionary:
si_bands.bands.keys()
>> dict_keys([1, 2, 3, 4, 5, 6, 7, 8])
si_bands.bands[8]
>> array([13.69848506, 13.70154719, ..., 9.47676028, 9.46081004])
We can also access the energy units.
si_bands.bands.units
>> 'eV'