Back to the main Index

This notebook explains how to use AbiPy and the DDB file produced by Abinit to analyze:

- Phonon band structures including the LO-TO splitting in heteropolar semiconductors
- Phonon fatbands, phonon DOS and projected DOS
- Born effectives charges $Z^*_{\kappa,\alpha\beta}$ and the dielectric tensors $\epsilon^{\infty}_{\alpha\beta}$, $\epsilon^{0}_{\alpha\beta}$
- Thermodynamic properties in the harmonic approximation

In the last part, we discuss how to use the `DdbRobot`

to analyze multiple DDB
files and perform typical convergence studies.

- How to create a Ddbfile object
- Invoking Anaddb from the DdbFile object
- Plotting Phonon Bands and DOS
- Fatbands and projected DOS
- Visualizing atomic displacements
- Analyzing the breaking of the acoustic sum rule
- Computing DOS with different q-meshes
- Thermodynamic properties in the harmonic approximation
- Macroscopic dielectric tensor and Born effective charges
- Using DdbRobot to perform convergence studies

- Dynamical matrices, Born effective charges, dielectric permittivity tensors, and interatomic force constants from density-functional perturbation theory
- Phonons and related crystal properties from density-functional perturbation theory

AbiPy, pymatgen and fireworks have been used by Petretto et al to compute the vibrational properties of more than 1500 compounds with Abinit. The results are available on the materials project website. The results for the rocksalt phase of MgO are available at https://materialsproject.org/materials/mp-1009129/

To fetch the DDB file from the materials project database and build a `DdbFile`

object, use:

```
ddb = abilab.DdbFile.from_mpid("mp-1009129")
```

Remember to set the `PMG_MAPI_KEY`

in your ~/.pmgrc.yaml as described
here.

Let us start by importing the basic AbiPy modules we have already used in the other examples:

In [1]:

```
from __future__ import division, print_function, unicode_literals
import warnings
warnings.filterwarnings("ignore") # Ignore warnings
from abipy import abilab
abilab.enable_notebook() # This line tells AbiPy we are running inside a notebook
import abipy.data as abidata
import os
# This line configures matplotlib to show figures embedded in the notebook.
# Replace `inline` with `notebook` in classic notebook
%matplotlib inline
# Option available in jupyterlab. See https://github.com/matplotlib/jupyter-matplotlib
#%matplotlib widget
```

To open a DDB file, use the high-level interface provided by abiopen:

In [2]:

```
ddb_filepath = abidata.ref_file("mp-1009129-9x9x10q_ebecs_DDB")
ddb = abilab.abiopen(ddb_filepath)
```

A DdbFile has a structure object:

In [3]:

```
print(ddb.structure) # Lengths in Angstrom.
```

and a list of q-points associated to the dynamical matrix $D(q)$:

In [4]:

```
ddb.qpoints
```

Out[4]:

At this point, it is worth mentioning that Abinit takes advantage of symmetries to reduce the number of q-points as well as the number of perturbations that must be computed explicitly within DFPT.

The set of q-points in the DDB file (usually) does not form a homogeneous sampling of the Brillouin zone (BZ). Actually they correspond to the sampling of the irreducible wedge (IBZ), and this sampling is obtained from an initial q-mesh specified in terms of divisions along the three reduced directions (ngqpt).

In [5]:

```
ddb.qpoints.plot();
```

Note that the DDB file does not contain any information about the value of ngqpt because one can merge an arbitrary list of q-points in the same DDB. The algorithms implemented in anaddb, however, need to know the divisions to compute integrals in the full BZ (this is indeed one of the variables that must be provided by the user in the anaddb input file).

AbiPy uses a heuristic method to guess the q-mesh from this scattered list of q-points so that you do not need to specify this parameter when calling anaddb:

In [6]:

```
ddb.guessed_ngqpt
```

Out[6]:

If the guess is wrong, you will need to manually set this attribute to the correct value of `ngqpt` before
invoking anaddb from python.
This could happen if you have merged DDB files computed with $q$-points that do not belong to same grid.

To test whether the DDB file contains the entries associated to $\epsilon^{\infty}_{\alpha\beta}$ and $Z^*_{\kappa,\alpha\beta}$, use:

In [7]:

```
print("Contains macroscopic dielectric tensor:", ddb.has_emacro_terms())
print("Contains Born effective charges:", ddb.has_bec_terms())
```

Metadata are stored in the header of the DDB file. AbiPy parses this initial section and stores the values in a dict-like object. Let us print a sorted list with all the keys in the header:

In [8]:

```
from pprint import pprint
pprint(sorted(list(ddb.header.keys())))
```

and use the standard syntax for dictionaries to access the keys:

In [9]:

```
print("This DDB has been generated with ecut", ddb.header["ecut"], "Ha and nsym", ddb.header["nsym"])
```

We can also print the DDB object to get a summary of the most important parameters and dimensions:

In [10]:

```
print(ddb)
# If more info is needed use:
# print(ddb.to_string(verbose=1)
```

If you are a terminal aficionado, remember that one can use the
abiopen.py script
to open the DDB file directly from the shell and generate a jupyter notebook with the `-nb`

option.
For a quick visualization script use abiview.py.

The `DdbFile`

object provides specialized methods to invoke anaddb and
compute important physical properties such as the phonon band structure, the phonon density of states, etc.
All these methods have a name that begins with the `ana*`

prefix followed by a verb (`anaget`

, `anacompare`

).
These specialized methods

- build the anaddb input file
- run anaddb
- parse the netcdf files produced by the Fortran code
- build and return AbiPy objects that can be used to plot/analyze the data.

Note that in order to run anaddb from AbiPy, you need a manager.yml with configuration options. For further details, please consult the TaskManager documentation.

The python API is flexible and exposes several anaddb input variables. The majority of the arguments have default values covering the most common cases so that you will need to specify these arguments explicitly only if the default behavior does not suit your needs. The most important parameters to remember are:

**ndivsm**: Number of divisions used for the smallest segment of the high-symmetry q-path.**nqsmall**: Defines the q-mesh for the phonon DOS in terms of the number of divisions used to sample the smallest reciprocal lattice vector. 0 to disable DOS computation.**lo_to_splitting**: Activate the computation of the frequencies in the $q\rightarrow 0$ limit with the inclusion of the non-analytical term (requires**dipdip**1 and DDB with $Z^*_{\kappa,\alpha\beta}$ and $\epsilon^{\infty}_{\alpha\beta}$).

The high-symmetry q-path is automatically selected assuming the structure fulfills the conventions introduced by Setyawan and Curtarolo but you can also specify your own q-path if needed.

In [11]:

```
# Call anaddb to compute phonon bands and DOS. Return PHBST and PHDOS netcdf files.
phbstnc, phdosnc = ddb.anaget_phbst_and_phdos_files(
ndivsm=20, nqsmall=20, lo_to_splitting=True, asr=2, chneut=1, dipdip=1, dos_method="tetra")
# Extract phbands and phdos from the netcdf object.
phbands = phbstnc.phbands
phdos = phdosnc.phdos
```

Let us have a look at the high symmetry q-path automatically selected by AbiPy with:

In [12]:

```
phbands.qpoints.plot();
```

and plot the phonon bands along this path with:

In [13]:

```
phbands.plot();
```

Note the discontinuity of the optical modes when we cross the $\Gamma$ point.
In heteropolar semiconductors, indeed, the dynamical matrix is non-analytical for $q \rightarrow 0$.
Since `lo_to_splitting`

was set to True, AbiPy has activated the calculation of the phonon frequencies
for all the $q \rightarrow \Gamma$ directions present in the path.

There are several band crossings and anti-crossings hence it's not easy
to understand how the branches should be connected.
Fortunately, there is a heuristic method to **estimate** band connection from
the overlap of the eigenvectors at adjacent q-points.
To connect the modes and plot the phonon branches with different colors, use:

In [14]:

```
phbands.plot_colored_matched();
```

This heuristic method may fail so the results should be analyzed critically (especially
when there are non-analytic branches crossing $\Gamma$).
Besides, the algorithm is sensitive to the k-path resolution
thus it is recommended to check the results by increasing the number of points per segment.

To plot the DOS, $g(\omega)$, and the integrated $IDOS(\omega) = \int^{\omega}_{-\infty} g(\omega')\,d\omega'$, use:

In [15]:

```
phdos.plot();
```

Note how the phonon DOS integrates to $3 * N_{atom} = 6$

To plot the phonon bands and the DOS on the same figure use:

In [16]:

```
phbands.plot_with_phdos(phdos, units="meV");
```

The `phbands`

object stores the phonon displacements, $\vec{d}_{q\nu}$ and
the eigenvectors, $\vec{\epsilon}_{q\nu}$
obtained by diagonalizing the dynamical matrix $D(q)$.

We can therefore use the eigenvectors (or the displacements) to associate a width to the different bands (a.k.a. fatbands). This width gives us a qualitative understanding of the vibrational mode: what are the atomic types involved in the vibrations at a given energy, their direction of oscillation and the amplitude (related to the displacement).

In [17]:

```
# NB: LO-TO is not available in fatbands
phbands.plot_fatbands(use_eigvec=True, units="Thz");
```

To plot the fatbands with the type-projected DOS stored in `phdocsnc`

use:

In [18]:

```
phbands.plot_fatbands(phdos_file=phdosnc, colormap="rainbow", alpha=0.4, units="cm-1");
```

We can also plot the PJDOS summed over directions and atomic types without fatbands with the command:

In [19]:

```
phdosnc.plot_pjdos_type();
```

The netcdf file contains the individual contributions to the total DOS for each atomic site and the three cartesian directions. So there are several quantities we can plot to understand the vibrational spectrum of our systems. For example, we can decide to sum over all atoms of the same type while keeping the dependence on the Cartesian direction:

In [20]:

```
phdosnc.plot_pjdos_cartdirs_type(units="Thz");
```

This analysis tells us that the peak at ~16 Thz mainly consists of oxygen vibrations along z. We could now extend this analysis by looking at the contributions arising from the different sites with:

In [21]:

```
#phdosnc.plot_pjdos_cartdirs_site(view="inequivalent", units="eV", stacked=True);
```

but we prefer to stop here and discuss other tools that can be used to analyze individual phonon modes.

In you need to visualize the lattice vibrations in 3D to gain a better insight about the nature of the phonon modes you may want to use the phononwebsite. One can either convert the out_PHBST.nc produced by anaddb into json format and upload it to the phononwebsite server or, alternatively, open the terminal and execute the AbiPy script:

```
abiview.py ddb out_DDB --phononwebsite
```

to automate the entire process (replace out_DDB with the name of your DDB file).

Note that there are other AbiPy methods that are quite handy if we need to investigate the nature of the phonon modes at a particular q-point without a 3D visualization tool. For example, it is possible to analyze the contribution given by the different types of atoms to the phonon displacements at a given q-point with:

In [22]:

```
phbands.plot_phdispl(qpoint=(0, 0, 0), tight_layout=True);
```

As expected the first three (acoustic) modes have zero frequency and the two atoms oscillate with the same amplitude. These modes indeed correspond to a rigid translation of the crystal hence the amplitude (and the direction) does not depend on the atomic site. The other three optical modes are (almost) degenerate, while LO-TO splitting should be present, but this is due to the fact that we are using the frequencies at the $\Gamma$ point without the inclusion of the non-analytical term.

In [23]:

```
#phbands.plot_phdispl((0, 0, 0.1), is_non_analytical_direction=True, tight_layout=True);
```

To project the phonon displacements along the three cartesian directions, use:

In [24]:

```
phbands.plot_phdispl_cartdirs(qpoint=(0, 0, 0.0), tight_layout=True);
```

This plot confirms that the first three modes correspond to a rigid translation along x, y and z, respectively.

Due to the invariance of the system under an *infinitesimal* rigid translation, the frequency of the lowest
three modes at $\Gamma$ should be zero.
Unfortunately, all the terms that are evaluated on the real-space FFT mesh
(e.g. $V_{xc}$, non-linear core-correction) break this kind of translational invariance.
The error depends on several factors: the density of the FFT mesh,
pseudopotentials with hard model core charges, XC functional, etc.)
Note that it is not always possible to reduce the error to zero by just increasing the convergence parameters
but fortunately it is possible to restore the acoustic sum rule via the `asr`

input variable.

One can easily compare the phonons bands obtained with different values of `asr`

with:

In [25]:

```
asr_plotter = ddb.anacompare_asr()
```

This method invokes anaddb with different values of `asr`

and returns a plotter object
we can call to compare the phonon band structures:

In [26]:

```
asr_plotter.combiplot();
```

Now we can perform a similar test for the treatment of the non-analytical term in the $q \rightarrow 0$ limit. We compute the phonon band dispersion for dipdip in [0, 1] and compare the results on the same figure with the commands:

In [27]:

```
dipdip_plotter = ddb.anacompare_dipdip(nqsmall=0)
```

In [28]:

```
dipdip_plotter.combiplot();
```

The figure above shows that the (Fourier interpolated) bands obtained with dipdip = 0 have unphysical oscillations around the $\Gamma$ point. These oscillation are due to the long-range behavior in real space of the interatomic force constants in heteropolar semiconductors. The correct description of this long-range term without dipdip = 1 would require using an extremely dense q-point mesh in the DFPT calculation.

With dipdip = 1, on the other hand, we can model this long-range behavior in terms of a dipole-dipole interaction involving the Born effective charges and the macroscopic dielectric tensor. This allows us to decompose the full dynamical matrix into:

\begin{equation} D(q) = D^{sr}(q) + D^{dip-dip}(q) \end{equation}The analytical part of the dynamical matrix, $D^{sr}(q)$, is short-ranged and can be Fourier-interpolated with a relatively coarse q-mesh. Then the model for the non-analytical term, $D^{dip-dip}(q)$, is added back to the interpolated matrix to get the full dynamical matrix. This procedure solves the problem with the unphysical oscillations and is required to describe correctly the non-analytical behavior of the optical modes for $q \rightarrow 0$.

Phonon DOS and derived quantites (e.g. thermodynamic properties) are sensitive to the BZ sampling and dense meshes may be required to converge the final results.

The method `anacompare_phdos`

provides a simple interface to
compare phonon DOS computed with different $q$-meshes.
We only need to provide a list of integers (`nqsmalls`

). Each integer defines the number of divisions used to
sample the smallest reciprocal lattice vector while the other two vectors are sampled such
that proportions are preserved.

To calculate three phonon DOS with increasing number of q-points use:

In [29]:

```
res = ddb.anacompare_phdos(nqsmalls=[8, 12, 24])
```

The return value is a named tuple with the phonon DOSes in res.phdoses while res.plotter is PhononDosPlotter. We can easily compare our results with:

In [30]:

```
res.plotter.combiplot();
```

The thermodynamic properties of an ensemble of non-interacting phonons can be expressed in terms of integrals of the phonon DOS $g(\omega)$ using:

\begin{equation} %\label{eq:helmholtz} \Delta F = 3nNk_BT\int_{0}^{\omega_L}\text{ln}\left(2\text{sinh}\frac{\hbar\omega}{2k_BT}\right)g(\omega)d\omega \end{equation}\begin{equation} %\label{eq:free_en} \Delta E = 3nN\frac{\hbar}{2}\int_{0}^{\omega_L}\omega\text{coth}\left(\frac{\hbar\omega}{2k_BT}\right)g(\omega)d\omega \end{equation}\begin{equation} %\label{eq:c_v} C_v = 3nNk_B\int_{0}^{\omega_L}\left(\frac{\hbar\omega}{2k_BT}\right)^2\text{csch}^2\left(\frac{\hbar\omega}{2k_BT}\right)g(\omega)d\omega \end{equation}\begin{equation} %\label{eq:entropy} S = 3nNk_B\int_{0}^{\omega_L}\left(\frac{\hbar\omega}{2k_BT}\text{coth}\left(\frac{\hbar\omega}{2k_BT}\right) - \text{ln}\left(2\text{sinh}\frac{\hbar\omega}{2k_BT}\right)\right)g(\omega)d\omega, \end{equation}where $k_B$ is the Boltzmann constant. This should represent a reasonable approximation especially in the low-temperature regime in which anharmonic effects can be neglected.

Let's plot the vibrational contributions to the thermodynamic properties as function of temperature $T$:

In [49]:

```
phdos.plot_harmonic_thermo();
```

and the zero-point energy $\dfrac{1}{2 N_q} \sum_{q\nu} \omega_{q\nu}$

In [32]:

```
zpe = phdos.zero_point_energy
print("Zero-point energy", zpe, zpe.to("Ha"))
```

To get the free energy for a range of temperatures in Kelvin degrees, use:

In [33]:

```
f = phdos.get_free_energy(tstart=10, tstop=100)
#f.plot();
```

Let us call anaddb to compute the electronic contribution to the macroscopic dielectric tensor, $\epsilon^{\infty}_{\alpha\beta}$, and the Born effective charges $Z^*_{\kappa,\alpha\beta}$:

In [34]:

```
emacro, becs = ddb.anaget_emacro_and_becs(chneut=1)
```

Note the use of `chneut`

to enforce charge neutrality.

In [35]:

```
emacro
```

Out[35]:

In [36]:

```
becs
```

Out[36]:

In the computation of the low-frequency (infrared) dielectric tensor, $\epsilon^{0}_{\alpha\beta}$, one has to include the response of the ions, whose motion will be triggered by the electric field. One can show that:

\begin{equation} %\label{eq:dielectric} \epsilon^{0}_{\alpha\beta}(\omega) = \epsilon^{\infty}_{\alpha\beta} + 4\pi\sum_m\frac{S_{m,\alpha\beta}}{(\omega^{\Gamma}_m - \omega)^2}. \end{equation}where $\omega^{\Gamma}_m$ are the phonon frequencies at the center of the BZ and $S_{m,\alpha\beta}$ is the so-called mode-oscillator strengh tensor that depends on the phonon displacement and the Born effective charges.

In [37]:

```
dtgen = ddb.anaget_dielectric_tensor_generator()
```

In [38]:

```
e0 = dtgen.tensor_at_frequency(0)
print(e0)
```

In [39]:

```
#dtgen.plot_vs_w(w_max=None, component='diag', units='eV');
```

`DdbRobot`

to perform convergence studies¶A `DdbRobot`

receives a list of DDB files and provides methods
to construct pandas dataframes
and analyze the results of multiple calculations.
DdbRobots, in particular, are extremely useful to study the convergence of the phonon frequencies with respect to some computational parameters e.g. the number of k-points and the electronic smearing in metallic systems.

In this example, we are interested in the effect of the k-point sampling and the smearing parameter
on the vibrational properties of magnesium diboride.
$MgB_2$ is a metallic system with a critical temperature of 39 K that is the highest among conventional
(phonon-mediated) superconductors.
We use precomputed DDB files obtained by running GS+DFPT calculations with different values
of `nkpt`

and `tsmear`

.

Let's build our `DdbRobot`

object with:

In [40]:

```
import os
paths = [
#"mgb2_444k_0.01tsmear_DDB",
#"mgb2_444k_0.02tsmear_DDB",
#"mgb2_444k_0.04tsmear_DDB",
"mgb2_888k_0.01tsmear_DDB",
"mgb2_888k_0.02tsmear_DDB",
"mgb2_888k_0.04tsmear_DDB",
"mgb2_121212k_0.01tsmear_DDB",
"mgb2_121212k_0.02tsmear_DDB",
"mgb2_121212k_0.04tsmear_DDB",
]
paths = [os.path.join(abidata.dirpath, "refs", "mgb2_phonons_nkpt_tsmear", f) for f in paths]
robot = abilab.DdbRobot.from_files(paths)
robot
```

Out[40]:

The abicomp.py script provides a command line interface to build robots from a list of files/directories given as arguments

The DDB files are now stored in the robot with a label constructed from the file path.
These labels, however, are not very informative. In principle we would like to have a label
that reflects the value of `(nkpt, tsmear)`

also because these labels
will be used to generate the labels in our plots.

Let's fix it with a function that recomputes the labels from the metadata available in ddb.header:

In [41]:

```
function = lambda ddb: "nkpt: %s, tsmear: %.2f" % (ddb.header["nkpt"], ddb.header["tsmear"])
robot.remap_labels(function);
robot
```

Out[41]:

We are usually interested in the convergence behavior with respect to one or two parameters of the calculations. Let's build a pandas dataframe with the most important parameters extracted from the DDB header:

In [42]:

```
robot.get_params_dataframe()
```

Out[42]:

Now we tell the robot to invoke anaddb to compute the phonon bands for all DDB files.
Since we are not interested in the phonon DOS, `nqsmall`

is set to 0

In [43]:

```
r = robot.anaget_phonon_plotters(nqsmall=0)
```

Now we can plot all the phonon band structures on the same figure with:

In [44]:

```
r.phbands_plotter.combiplot();
```

The plot is a bit crowded. Still, it is clear that there are portions of the vibration spectrum that are quite sensitive to the values of (nkpt, tsmear).

In metals, it's common to analyze the convergence of the physical properties by plotting the results as function of the k-point sampling for fixed value of tsmear. Let's do something similar for the phonon band structures with the command:

In [45]:

```
r.phbands_plotter.gridplot_with_hue("tsmear", units="Thz");
```