# 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'
```