#!/usr/bin/env python
# coding: utf-8
# Back to the main [Index](../index.ipynb)
# # Phonons and Born effective charges with Abinit and AbiPy
# This lesson discusses how to compute phonon band structures, DOS
# and Born effective charges with Abinit and AbiPy.
# The discussion closely follows the [second lesson](https://docs.abinit.org/tutorial/rf2/index.html)
# on DFPT available on the Abinit web site.
# More specifically, we will discuss how to
#
# * Perform a convergence study for the phonon frequencies at $\Gamma$ as function of `ecut`
# * Compute the full phonon band structure of `AlAs` with the inclusion of LO-TO splitting
# * Obtain thermodynamic properties within the harmonic approximation
#
# We assume that you have read the references mentioned in the [first Abinit lesson](https://docs.abinit.org/tutorial/rf1/index.html)
# on DFPT.
# You might find additional material, related to the present section, in the following references:
#
# * [Dynamical matrices, Born effective charges, dielectric permittivity tensors, and interatomic force constants from density-functional perturbation theory](https://journals.aps.org/prb/abstract/10.1103/PhysRevB.55.10355)
# * [Phonons and related crystal properties from density-functional perturbation theory](https://journals.aps.org/rmp/abstract/10.1103/RevModPhys.73.515)
#
# If you are already familiar with python and AbiPy-Abinit are already installed and configured,
# you may want to use directly the command line interface.
# See the README.md file in the directory of this lesson explaining how to analyze the data from the shell
# using ipython and matplotlib.
#
# ## Table of Contents
# [[back to top](#top)]
#
# - [Phonon frequencies at $\Gamma$ as function of ecut](#Phonon-frequencies-at-$\Gamma$-as-function-of-ecut)
# - [Convergence study at $\Gamma$](#Convergence-study-at-$\Gamma$)
# - [Phonon band structure of AlAs](#Phonon-band-structure-of-AlAs)
# - [Post-processing the results](#Post-processing-the-results)
# - [Macroscopic dielectric tensor and Born effective-charges](#Macroscopic-dielectric-tensor-and-Born-effective-charges)
# - [Thermodynamic properties within the harmonic approximation](#Thermodynamic-properties-within-the-harmonic-approximation)
# - [Exercises](#Exercises)
# ## Phonon frequencies at $\Gamma$ as function of ecut
# [[back to top](#top)]
# Before starting, we need to import the python modules and the functions we will need in the notebook:
# In[1]:
# Use this at the beginning of your script so that your code will be compatible with python3
from __future__ import print_function, division, unicode_literals
import numpy as np
import warnings
warnings.filterwarnings("ignore") # to get rid of deprecation warnings
from abipy import abilab
abilab.enable_notebook() # This line tells AbiPy we are running inside a notebook
import abipy.flowtk as flowtk
# This line configures matplotlib to show figures embedded in the notebook.
# Replace `inline` with `notebook` in classic notebook
get_ipython().run_line_magic('matplotlib', 'inline')
# Option available in jupyterlab. See https://github.com/matplotlib/jupyter-matplotlib
#%matplotlib widget
# and an useful function from the `lesson_dfpt` module that will be used to generate our DFPT flows:
# In[2]:
from lesson_dfpt import make_scf_input
abilab.print_source(make_scf_input)
# The function makes some assumptions for important parameters such as
# the crystalline structure and the pseudos.
# This is done on purpose to keep the code as simple as possible.
# It should not be so difficult to generalize the implementation to take into account other cases.
# Let's start to play with our new function:
# In[3]:
scf_input = make_scf_input()
scf_input
# In[4]:
print(scf_input.structure)
# In[5]:
scf_input.structure.plot();
# We are using the same pseudopotentials than the official tutorial.
# Note that the xc functional is LDA in both pseudos but with a different
# parametrization. This is the reason why we are using `ixc` in the input file.
# In[6]:
for pseudo in scf_input.pseudos:
print(pseudo, "\n")
# As you can see, we essentialy have a standard input to perform a GS calculation. This object will represent
# the **building block** for our DFPT calculation with AbiPy.
#
# It this is not your first time you use the DFPT part of Abinit, you already know that phonon calculations
# require an initial GS run to produce the `WFK` file
# followed by a DFPT run that reads the `WFK` file and solves the Sternheimer equations for $N_{\text{irred}}(q)$
# atomic perturbations where $N_{\text{irred}}$ is the number of independent atomic displacements (assuming $q$ belongs to the k-mesh).
#
# If you try to do a convergence study wrt `ecut` **without multi-datasets**, you will likely start from an initial GS input file with a given value of `ecut`, use it as a template to generate the DFPT input files, create symbolic
# links to the `WFK` file produced in the first GS step and then instruct Abinit to read this file with `irdwfk`.
# Once you have a set of input files that work for a particular `ecut`, one can simply replicate the set of
# directories and files and use a script to change the value of `ecut` in the input files.
# Then, of course, one has to run the calculations manually, collect the results and produce nice plots to understand
# what is happening.
#
# This approach is obviously boring and error-prone if you are a human being but it is easy to implement in an algorithm
# and machines do not complain if they have a lot of repetive work to do!
# There are also several **technical advantages** in using this **task-based approach vs multi-datasets** but we discuss this point in more details afterwards.
#
# If the machine could speak, it will tell you: give me an object that represents an input for GS calculations,
# give me the list of q-points you want to compute as well as the parameters that must be changed in the initial input
# and I will generate a `Flow` for DFPT calculations.
# This logic appears so frequenty that we decided to encapsulate it in the `flowtk.phonon_conv_flow` factory function:
# In[7]:
from lesson_dfpt import build_flow_alas_ecut_conv
abilab.print_source(build_flow_alas_ecut_conv)
# Let's call the function to build our flow:
# In[8]:
flow = build_flow_alas_ecut_conv(options=None)
flow.show_info()
# and call the `get_graphviz` method to visualize the connection among the `Tasks`:
# In[9]:
flow.get_graphviz()
# In[10]:
# matplotlib version based on networkx
#flow.plot_networkx(with_edge_labels=True);
# The flow contains three independent groups of tasks, one group per each value of `ecut` specified in `params`.
# In[11]:
for work in flow:
for task in work:
print(task.pos_str, "uses ecut:", task.input["ecut"])
# In[12]:
flow.get_vars_dataframe("ecut")
# Each group represents a `Workflow` and consists of one `ScfTask`(red circle) that solves the `KS` equations self-consistently producing a `WFK` file that will be used by the two children (`PhononTasks` - blue circles)
# to compute the first-order change of the wavefunctions due to one of the *irreducible* atomic pertubations.
#
# Note that `phonon_conv_flow` invokes Abinit under the hood to get the list of irreducible perturbations
# and uses this information to build the flow.
# This explains why we have two `PhononTasks` per $q$-point instead of the total number of phonon modes that
# equals $3*N_{atom}=6$.
#
# Perhaps a table with the values of the input variables associated to the DFPT perturbation will help.
# `None` means that the variable is not defined in that particular input.
# In[13]:
flow.get_vars_dataframe("rfphon", "rfatpol", "rfdir", "qpt", "kptopt")
# If the meaning of these variables is not clear, you can consult the [Abinit documentation](https://docs.abinit.org)
# e.g. [the documentation of the rfatpol input variable](https://docs.abinit.org/variables/dfpt/#rfatpol)
# or access the documentation directly from python with:
# In[14]:
abilab.docvar("rfatpol")
# Now we can generate the `flow_alas_ecut` directory with the input files by executing
# the `lesson_dfpt.py` script.
# Then use the `abirun.py` script to launch the entire calculation with:
#
# abirun.py flow_alas_ecut_conv scheduler
#
# You will see that all `PhononTasks` will be executed in parallel on your machine.
#
#
# Please make sure that AbiPy is properly configured by running abicheck --with flow
#
#
# If you prefer to skip this part, you may want to jump to the next section, that presents the post-processing of the results.
# Note that the output files are already available in the repository so it is also possible to try
# the AbiPy post-processing tools without having to run the flow.
# ## Convergence study at $\Gamma$
# [[back to top](#top)]
#
# There are several output files located inside the `outdata` directories:
# In[15]:
get_ipython().system('find flow_alas_ecut_conv/ -name "*_DDB"')
# Remember that our goal is to analyze the convergence of the phonon frequencies at $\Gamma$
# as function of `ecut`.
# So we are mainly interested in the DDB files located in the `outdata` directories
# of the `PhononWorks` (`w0/outdata`, `w1/outdata`, `w2/outdata`).
# These are indeed the DDB files with all the information needed to reconstruct the
# dynamical matrix at $\Gamma$ and to compute the phonon frequencies (AbiPy calls `mrgddb`
# to merge the DDB files when all the perturbations in the `PhononWork` have been computed).
#
# The code below tells our robot that we would like to analyze all the DDB files
# located in the output directories of the works:
# In[16]:
robot = abilab.DdbRobot.from_dir_glob("./flow_alas_ecut_conv/w*/outdata/")
robot
# For more examples on the use of DDB and robots, see the
# [DDB notebook](https://nbviewer.jupyter.org/github/abinit/abitutorials/blob/master/abitutorials/ddb.ipynb).
# Now we ask the robot to call `anaddb` to compute the phonon frequencies at $\Gamma$ for all DDBs
# and return a pandas `DataFrame`:
# In[17]:
data_gamma = robot.get_dataframe_at_qpoint((0, 0, 0))
# The `DataFrame` is a dict-like object whose keys are the name of the colums in the table
# In[18]:
print(data_gamma.keys())
# where `mode-i` is the frequency in eV of the i-th phonon mode.
#
# We are mainly interested in the convergence of the phonon frequencies versus `ecut` so we filter these columns with:
# In[19]:
data_gamma = data_gamma[["ecut"] + [k for k in data_gamma if k.startswith("mode")]]
data_gamma
# and we get some statistics about our data with:
# In[20]:
data_gamma.describe()
# Pandas tables are extremly powerful and the `describe` method already gives some useful info
# about the convergence of the phonon modes.
# Sometimes, however, we would like to visualize the data to have a better understanding of what's happening:
# In[21]:
data_gamma.plot(x="ecut", y="mode3", style="-o");
# Let's plot all the modes in different subplots with:
# In[22]:
data_gamma.plot(x="ecut", y=[k for k in data_gamma if k.startswith("mode")], subplots=True, style="-o");
# This convergence study at $\Gamma$ thus reveals that our pseudos require
# an `ecut` >= 6 Ha to get reasonably converged phonon frequencies at $\Gamma$.
# In what follows, we assume that also the modes at the other $q$-points present a similar
# convergence behaviour and we use `ecut` = 6 Ha to keep the computational cost low.
# For a quick introduction to Pandas, see:
#
# * [Pandas cookbook](https://github.com/jvns/pandas-cookbook)
# * [Pandas cookbook Chapter 1 Reading data from a csv file](http://nbviewer.jupyter.org/github/jvns/pandas-cookbook/blob/master/cookbook/Chapter%201%20-%20Reading%20from%20a%20CSV.ipynb)
# ## Phonon band structure of AlAs
# [[back to top](#top)]
#
# Now we are finally ready for the calculation of the vibrational spectrum of $AlAs$.
# We already managed to run DFPT calculations at $\Gamma$ with different values of `ecut` and the
# steps required to get a full band structure are not that different, provided that
# the following differences are taken into account:
#
# - we need the dynamical matrix $D(q)$ on a homogeneous mesh so that it is possible to calculate $D(R)$
# in anaddb via Fourier transform and then phonon frequencies for arbitrary q-points via Fourier interpolation
#
# - $AlAs$ is a polar semiconductor so we need to include the LO-TO splitting for $q \rightarrow 0$ that, in turns,
# requires the DFPT computation of the Born effective charges and of the dielectric constant.
#
#
# In AbiPy, these concepts are translated in an easy-to-use API in which you pass an initial `AbinitInput` object,
# you specify the q-mesh for phonons in terms of `ph_nqpt` and activate the computation of the
# Born effective charges with the boolean flag `with_becs`.
#
# Let's have a look at the code (as usual there are more comments than lines of code):
# In[23]:
from lesson_dfpt import build_flow_alas_phonons
abilab.print_source(build_flow_alas_phonons)
# We can finally construct the flow with:
# In[24]:
flow_phbands = build_flow_alas_phonons(options=None)
# and visualize the connections with:
# In[25]:
flow_phbands.get_graphviz()
#flow_phbands.plot_networkx();
# Note that there are a lot of things happening under the hood here.
#
# First of all, AbiPy generates `PhononTasks` only for the $q$-points in the
# irreducible wedge of the Brillouin zone corresponding to `ph_ngqpt`.
# Moreover, for a given $q$-point, only the irreducible atomic perturbations are explicitly computed
# since the other atomic perturbations can be reconstructed by symmetry.
# Fortunately you do not have to care about all these technical details as AbiPy and Abinit
# will automate the whole procedure.
#
# Remember that the $q$-point mesh cannot be chosen arbitrarily
# since all $q$ wavevectors should connect two $k$ points of the grid used for the electrons.
#
# It is also worth stressing that the computational cost of the DFPT run depends on the q-point
# since only those symmetries that preserve the q-point as well as the direction of the perturbation
# can be employed (calculations at $\Gamma$ are therefore much faster than other q-points).
# In[26]:
flow_phbands.get_vars_dataframe("rfphon", "rfatpol", "rfdir", "qpt", "kptopt")
# Now we can generate the directories and the input files of the `Flow`.
# Change lesson_dfpt.py so that the build_flow_alas_phonons function is called in main
# instead of build_flow_alas_ecut_conv.
# Run the script to generate the directory with the flow.
# Finally, use
#
# abirun.py flow_alas_phonons scheduler
#
# to launch the entire calculation.
# ## Post-processing the results
# [[back to top](#top)]
#
# Our flow is completed and we have the final DDB file with all the $q$-points and all the independent atomic perturbations.
# Let's open this DDB file with:
# In[27]:
ddb = abilab.abiopen("flow_alas_phonons/outdata/out_DDB")
print(ddb)
# The `DdbFile` object provides an easy-to-use interface that invokes `anaddb` to post-process
# the data stored in the DDB file.
#
# `anacompare_phdos`, for example, computes the phonon DOS with different $q$-meshes.
# Each mesh is defined by a single integer, `nqsmall`, that gives the number of
# divisions used to sample the smallest reciprocal lattice vector.
# The number of divisions along the other directions are chosen so that proportions are preserved:
# In[28]:
c = ddb.anacompare_phdos(nqsmalls=[8, 10, 12, 14, 16])
# In[29]:
c.plotter.combiplot();
# A 16x16x16 $q$-mesh with the tethraedron method gives a well converged phonon DOS.
# To function `anaget_phbst_and_phdos_files` allows one to compute the phonon band structure on an automatically defined $q$-path as well as the the phonon DOS:
# In[30]:
phbst_file, phdos_file = ddb.anaget_phbst_and_phdos_files(ndivsm=10, nqsmall=16, lo_to_splitting=False)
# Extract the phonon bands and the phonon DOS from phbst_file and phdos_file
phbands = phbst_file.phbands
phdos = phdos_file.phdos
# Let's plot the bands with matplotlib:
# In[31]:
phbands.plot();
# and the high-symmetry q-path:
# In[32]:
phbands.qpoints.plot();
# Do you see the two strange dips for the highest phonon band, at the $\Gamma$ point?
# They are due to the lack of LO-TO splitting for the ANADDB treatment of the first list of vector.
# See also the discussion in the [second DFPT lesson](https://docs.abinit.org/tutorial/rf2/index.html).
# For years, Abinit users had to patch manually the output frequencies to include the LO-TO splitting.
# These days are finally gone and we can plot the LO-TO splitting with AbiPy by just setting
# lo_to_splitting=True`:
# In[33]:
phbst_file, phdos_file = ddb.anaget_phbst_and_phdos_files(ndivsm=10, nqsmall=16, lo_to_splitting=True)
# Extract the phonon bands and the phonon DOS from phbst_file and phdos_file
phbands = phbst_file.phbands
phdos = phdos_file.phdos
# In[34]:
#phbst_file.phbands.qpoints.plot();
# The band structure plot now correctly shows the non-analytical behaviour around $\Gamma$:
# In[35]:
phbands.plot();
#
# `lo_to_splitting=True` works only when the DDB contains the Born effective charges and the dielectric constant,
# that must be computed in the Abinit run.
#
# To plot bands and DOS on the same figure:
# In[36]:
phbands.plot_with_phdos(phdos);
# The `PhdosFile` contains the phonon frequencies, the displacement vectors
# as well as the decomposition of the total DOS in terms of the contributions due to
# the different types of atom in the unit cell.
# This means that one can plot the type-projected phonon DOS with:
# In[37]:
phdos_file.plot_pjdos_type(title="AlAs type-projected phonon DOS");
# and it is even possible to plot fatbands and type-projected DOSes on the same figure with:
# In[38]:
phbands.plot_fatbands(phdos_file=phdos_file);
# The highest frequency modes have a strong Al-character while the low frequency modes originate from As.
# This behaviour is somehow expected. Could you explain it in terms of a simple physical model?
# ## Macroscopic dielectric tensor and Born effective charges
# [[back to top](#top)]
#
# Our calculations includes the response of the system to an external electric field.
# The code below extracts the macroscopic dielectric tensor (`emacro`)
# and the Born effective charges (`becs`) from the DDB file:
# In[39]:
emacro, becs = ddb.anaget_emacro_and_becs()
# In[40]:
emacro
# In[41]:
becs
# As explained in the references, the Born effective charges must fulfill
# the charge neutrality sum-rule.
# This rule is usually broken due to the discretization introduced by the FFT mesh, and `anaddb` will enforce it if `chneut` is set to 1 (default behaviour). Let's check it out!
# In[42]:
print(becs)
# Let's repeat the same calculation but without enforcing the sum-rule:
# In[43]:
emacro, becs_chneut0 = ddb.anaget_emacro_and_becs(chneut=0)
print(becs_chneut0)
# ## Thermodynamic properties within the harmonic approximation
# [[back to top](#top)]
#
# The thermodynamic properties of an ensemble of non-interacting phonons can be
# expresses in terms of integrals of the DOS.
#
# \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 thermodynamic properties as function of $T$:
# In[44]:
phdos.plot_harmonic_thermo();
# ## Exercises
# [[back to top](#top)]
#
# * Our first phonon band structure has been computed with a (4, 4, 4) $k$-mesh
# for the electrons and a (2, 2, 2) $q$-mesh for phonons.
# You may try to increase the density of $k$-points/$q$-points
# to see if this change affects the final results.
#
# * Why do you get an error from AbiPy if you try `ngkpt` = (4, 4, 4,) and `ngqpt` = (3, 3, 3)?
# Back to the main [Index](../index.ipynb)
# In[ ]: