Density of States

DOS representation in Euphonic

Density of states in Euphonic is represented by a generic Spectrum1D object. If there are multiple DOS with the same energy bins (e.g. per-element partial DOS and total DOS) they can be contained in a Spectrum1DCollection object. PDOS can be labelled with the metadata attributes, e.g. for a Spectrum1D object:

>>> si_pdos.metadata
{'label': 'Si', 'species': 'Si'}

Or, for a Spectrum1DCollection object:

>>> all_dos.metadata
{'line_data': [{'label': 'Total'}, {'label': 'O', 'species': 'O'}, {'label': 'Si', 'species': 'Si'}]}

See the Spectrum1D or Spectrum1DCollection documentation for further information on processing and plotting.

Reading DOS

DOS can be read from a CASTEP .phonon_dos file using Spectrum1D.from_castep_phonon_dos or Spectrum1DCollection.from_castep_phonon_dos. The Spectrum1D version will return either the total DOS or a specific PDOS from the file, which can be specified with the element argument. The Spectrum1DCollection version will read both the total DOS and per-element PDOS. Each DOS is labelled by the Spectrum1DCollection.metadata attribute. An example is shown below.

from euphonic import Spectrum1D, Spectrum1DCollection

# Read total DOS
dos_total = Spectrum1D.from_castep_phonon_dos('quartz-151512.phonon_dos')

# Read Silicon PDOS
dos_si = Spectrum1D.from_castep_phonon_dos('quartz-151512.phonon_dos', element='Si')

# Read all DOS and PDOS
dos_all = Spectrum1DCollection.from_castep_phonon_dos('quartz-151512.phonon_dos')
# View DOS labels
print(dos_all.metadata)
{'line_data': [{'label': 'Total'}, {'species': 'O', 'label': 'O'}, {'species': 'Si', 'label': 'Si'}]}

Calculating DOS

Density of states can be calculated for any Euphonic object containing frequencies using its calculate_dos method. This requires an array of energy bin edges, with the units specified by wrapping it as a pint.Quantity (see Units for details). This function returns a generic Spectrum1D object. For example, using QpointFrequencies.calculate_dos.

from euphonic import ureg, QpointFrequencies
import numpy as np

phonons = QpointFrequencies.from_castep('quartz.phonon')

# Create an array of energy bins 0 - 100 in meV
energy_bins = np.arange(0, 101, 1)*ureg('meV')

# Calculate dos
dos = phonons.calculate_dos(energy_bins)

Adaptive Broadening

Adaptive broadening can also be enabled to get a more accurate DOS than with standard fixed width broadening. For adaptive broadening each mode at each q-point is broadened individually with a specific width. There are two adaptive broadening methods available, the ‘reference’ and ‘fast’ methods. The ‘reference’ scheme explicitly calculates a gaussian for each mode width. These mode widths are derived from the mode gradients, and the mode gradients can be calculated at the same time as the phonon frequencies and eigenvectors, by passing return_mode_gradients=True to ForceConstants.calculate_qpoint_phonon_modes or ForceConstants.calculate_qpoint_frequencies. The mode widths can be estimated from the mode gradients using euphonic.util.mode_gradients_to_widths. These widths can then be passed to calculate_dos through the mode_widths keyword argument. An example is shown below.

from euphonic import ureg, ForceConstants
from euphonic.util import mp_grid, mode_gradients_to_widths
import numpy as np

fc = ForceConstants.from_castep('quartz.castep_bin')
phonons, mode_grads = fc.calculate_qpoint_frequencies(
    mp_grid([5, 5, 4]),
    return_mode_gradients=True)
mode_widths = mode_gradients_to_widths(mode_grads, fc.crystal.cell_vectors)

energy_bins = np.arange(0, 166, 0.1)*ureg('meV')
adaptive_dos = phonons.calculate_dos(energy_bins, mode_widths=mode_widths)

The ‘fast’ approximate adaptive brodening method reduces computation time by reducing the number of Gaussian functions that have to be evaluated. Rather than individually broadening each mode at each q-point with a Gaussian of specific width, broadening kernels need only be computed for regularly spaced values across range of mode widths. The kernels at intermediate mode width values can then be approximated using interpolation. Interpolation weights can then be used to scale the input spectrum, before scaled spectra are then convolved by each of the broadening functions associated with the mode width sample values and then summed.

For fast adaptive broadening the adaptive_method keyword argument must be set to ‘fast’ when passed to calculate_dos. Optionally, an acceptable error level for the interpolated kernels can be specified, using the adaptive_error keyword argument. The error is defined as the absolute difference between the areas of the true and approximate Gaussians. Changing the adaptive_error value will change the number of mode width samples; more samples will make the gaussian approximations more accurate but will also increase computation time. Following on from the above example, fast adaptive broadening can be performed as follows:

fast_adaptive_dos = phonons.calculate_dos(energy_bins,
                                          mode_widths=mode_widths,
                                          adaptive_method='fast')

Calculating Partial and Neutron-weighted DOS

See Calculating PDOS