phonon - Methodology and code

Python imports

[1]:
# Standard library imports
from pathlib import Path
import os
import sys
import uuid
import shutil
import datetime
from collections import OrderedDict
from math import floor
from copy import deepcopy
from typing import Optional, Tuple

# http://www.numpy.org/
import numpy as np

# https://ipython.org/
from IPython.display import display, Markdown, Image

# https://matplotlib.org/
import matplotlib.pyplot as plt

# https://atztogo.github.io/spglib/python-spglib.html
import spglib

# https://atztogo.github.io/phonopy/phonopy-module.html
import phonopy

# https://github.com/usnistgov/DataModelDict
from DataModelDict import DataModelDict as DM

# https://github.com/usnistgov/atomman
import atomman as am
import atomman.lammps as lmp
import atomman.unitconvert as uc
from atomman.tools import filltemplate

# https://github.com/usnistgov/iprPy
import iprPy
from iprPy.tools import read_calc_file

print('Notebook last executed on', datetime.date.today(), 'using iprPy version', iprPy.__version__)
Notebook last executed on 2023-07-31 using iprPy version 0.11.6

1. Load calculation and view description

1.1. Load the calculation

[2]:
# Load the calculation being demoed
calculation = iprPy.load_calculation('phonon')

1.2. Display calculation description and theory

[3]:
# Display main docs and theory
display(Markdown(calculation.maindoc))
display(Markdown(calculation.theorydoc))

phonon calculation style

Lucas M. Hale, lucas.hale@nist.gov, Materials Science and Engineering Division, NIST.

Introduction

The phonon calculation style applies small atomic displacements to a unit cell system and evaluates the forces on the atoms to evaluate phonon-based properties.

Version notes
  • 2020-12-21: Script extended to include quasiharmonic calculations.

  • 2022-03-11: Notebook updated to reflect version 0.11.

Additional dependencies
Disclaimers

Method and Theory

Starting with an initial system, spglib is used to identify the associated primitive unit cell. The primitive cell is passed to phonopy, which constructs super cell systems with small atomic displacements. A LAMMPS calculation is performed on the displaced systems to evaluate the atomic forces on each atom without relaxing. The measured atomic forces are then passed back to phonopy, which computes force constants for the system. Plots are then created for the band structure, density of states, and other thermal properties.

See phonopy documentation for more details about the package and the associated theory.

2. Define calculation functions and generate files

This section defines the calculation functions and associated resource files exactly as they exist inside the iprPy package. This allows for the code used to be directly visible and modifiable by anyone looking to see how it works.

2.1. phonon_quasiharmonic()

This is the primary function for the calculation. The version of this function built in iprPy can be accessed by calling the calc() method of an object of the associated calculation class.

[4]:
def phonon_quasiharmonic(lammps_command: str,
                         ucell: am.System,
                         potential: lmp.Potential,
                         mpi_command: Optional[str] = None,
                         a_mult: int = 2,
                         b_mult: int = 2,
                         c_mult: int = 2,
                         distance: float = 0.01,
                         symprec: float = 1e-5,
                         strainrange: float = 0.01,
                         numstrains: int = 5) -> dict:
    """
    Function that performs phonon and quasiharmonic approximation calculations
    using phonopy and LAMMPS.

    Parameters
    ----------
    lammps_command :str
        Command for running LAMMPS.
    ucell : atomman.System
        The unit cell system to perform the calculation on.
    potential : atomman.lammps.Potential
        The LAMMPS implemented potential to use.
    mpi_command : str, optional
        The MPI command for running LAMMPS in parallel.  If not given, LAMMPS
        will run serially.
    a_mult : int, optional
        The a size multiplier to use on ucell before running the phonon
        calculation.  Must be an int and not a tuple.  Default value is 2.
    b_mult : int, optional
        The b size multiplier to use on ucell before running the phonon
        calculation.  Must be an int and not a tuple.  Default value is 2.
    c_mult : int, optional
        The c size multiplier to use on ucell before running the phonon
        calculation.  Must be an int and not a tuple.  Default value is 2.
    distance : float, optional
        The atomic displacement distance used for computing the phonons.
        Default value is 0.01.
    symprec : float, optional
        Absolute length tolerance to use in identifying symmetry of atomic
        sites and system boundaries. Default value is 1e-5.
    strainrange : float, optional
        The range of strains to apply to the unit cell to use with the
        quasiharmonic calculations.  Default value is 0.01.
    numstrains : int, optional
        The number of strains to use for the quasiharmonic calculations.
        Must be an odd integer.  If 1, then the quasiharmonic calculations
        will not be performed.  Default value is 5.
    """
    # Get lammps units
    lammps_units = lmp.style.unit(potential.units)

    # Get lammps version date
    lammps_date = lmp.checkversion(lammps_command)['date']

    # Convert ucell to a primitive cell
    ucell = ucell.dump('primitive_cell', symprec=symprec)

    # Get unstrained box vectors
    vects = ucell.box.vects

    # Generate the range of strains
    if numstrains == 1:
        zerostrain = phononcalc(lammps_command, ucell, potential,
                                mpi_command=mpi_command,
                                a_mult=a_mult, b_mult=b_mult, c_mult=c_mult,
                                distance=distance, symprec=symprec,
                                lammps_date=lammps_date)
        phonons = [zerostrain['phonon']]
        qha = None

    elif numstrains % 2 == 0 or numstrains < 5:
        raise ValueError('Invalid number of strains: must be odd and 1 or >= 5')
    else:
        strains = np.linspace(-strainrange, strainrange, numstrains)
        istrains = np.linspace(-(numstrains-1)/2, (numstrains-1)/2, numstrains, dtype=int)

        volumes = []
        energies = []
        phonons = []
        temperatures = None
        free_energy = None
        heat_capacity = None
        entropy = None

        # Loop over all strains
        for i in range(numstrains):
            strain = strains[i]
            if numstrains != 1:
                istrain = f'_{istrains[i]}'
            else:
                istrain = ''

            # Identify the zero strain run
            if istrains[i] == 0:
                zerostrainrun = True
            else:
                zerostrainrun = False

            # Generate system at the strain
            newvects = vects * (1 + strain)
            ucell.box_set(vects=newvects, scale=True)
            volumes.append(ucell.box.volume)
            system = ucell.supersize(a_mult, b_mult, c_mult)

            # Define lammps variables
            lammps_variables = {}
            system_info = system.dump('atom_data', f='disp.dat',
                                    potential=potential)
            lammps_variables['atomman_system_pair_info'] = system_info

            # Set dump_modify_format based on lammps_date
            if lammps_date < datetime.date(2016, 8, 3):
                lammps_variables['dump_modify_format'] = '"%d %d %.13e %.13e %.13e %.13e %.13e %.13e"'
            else:
                lammps_variables['dump_modify_format'] = 'float %.13e'

            # Write lammps input script
            lammps_script = 'phonon.in'
            template = read_calc_file('iprPy.calculation.phonon', 'phonon.template')
            with open(lammps_script, 'w') as f:
                f.write(filltemplate(template, lammps_variables, '<', '>'))

            # Run LAMMPS
            output = lmp.run(lammps_command, script_name='phonon.in',
                             mpi_command=mpi_command)

            # Extract system energy
            thermo = output.simulations[0]['thermo']
            energy = uc.set_in_units(thermo.PotEng.values[-1], lammps_units['energy'])

            # Scale energy by sizemults and append to list
            energies.append(energy / (a_mult * b_mult * c_mult))

            # Compute phonon info for ucell
            phononinfo = phononcalc(lammps_command, ucell, potential, mpi_command=mpi_command,
                                    a_mult=a_mult, b_mult=b_mult, c_mult=c_mult,
                                    distance=distance, symprec=symprec, istrain=istrain,
                                    plot=zerostrainrun, lammps_date=lammps_date)
            phonons.append(phononinfo['phonon'])

            # Extract temperature values from the first run
            if temperatures is None:
                temperatures = phononinfo['thermal_properties']['temperatures']

                # Initialize QHA input arrays
                free_energy = np.empty((len(temperatures), len(strains)))
                heat_capacity = np.empty((len(temperatures), len(strains)))
                entropy = np.empty((len(temperatures), len(strains)))

            # Get values for zerostrainrun
            if zerostrainrun is True:
                zerostrain = phononinfo

            # Copy values to qha input arrays
            free_energy[:, i] = phononinfo['thermal_properties']['free_energy']
            entropy[:, i] = phononinfo['thermal_properties']['entropy']
            heat_capacity[:, i] = phononinfo['thermal_properties']['heat_capacity']

        # Try to compute qha
        try:
            eos = 'vinet'
            qha = phonopy.PhonopyQHA(volumes=volumes,
                        electronic_energies=energies,
                        temperatures=temperatures,
                        free_energy=free_energy,
                        cv=heat_capacity, eos=eos,
                        entropy=entropy)
        except:
            try:
                eos = 'birch_murnaghan'
                qha = phonopy.PhonopyQHA(volumes=volumes,
                        electronic_energies=energies,
                        temperatures=temperatures,
                        free_energy=free_energy,
                        cv=heat_capacity, eos=eos,
                        entropy=entropy)
            except:
                qha = None

    results = {}

    # Add phonopy objects
    results['phonon_objects'] = phonons
    results['qha_object'] = qha

    # Extract zerostrain properties
    results['band_structure'] = zerostrain['band_structure']
    results['density_of_states'] = zerostrain['dos']

    # Convert units on thermal properties
    results['thermal_properties'] = zerostrain['thermal_properties']
    results['thermal_properties']['temperature'] = results['thermal_properties'].pop('temperatures')
    results['thermal_properties']['Helmholtz'] = uc.set_in_units(results['thermal_properties'].pop('free_energy'), 'kJ/mol')
    results['thermal_properties']['entropy'] = uc.set_in_units(results['thermal_properties'].pop('entropy'), 'J/K/mol')
    results['thermal_properties']['heat_capacity_v'] = uc.set_in_units(results['thermal_properties'].pop('heat_capacity'), 'J/K/mol')

    if qha is not None:

        results['qha_eos'] = eos

        # Create QHA plots
        qha.plot_bulk_modulus()
        plt.xlabel('Volume ($Å^3$)', size='large')
        plt.ylabel('Energy ($eV$)', size='large')
        plt.savefig('bulk_modulus.png', dpi=400, bbox_inches='tight')
        plt.close()

        qha.plot_helmholtz_volume()
        plt.savefig('helmholtz_volume.png', dpi=400)
        plt.close()

        # Package volume vs energy scans
        results['volume_scan'] = {}
        results['volume_scan']['volume'] = np.array(volumes)
        results['volume_scan']['strain'] = strains
        results['volume_scan']['energy'] = np.array(energies)

        # Compute and add QHA properties
        properties = qha.get_bulk_modulus_parameters()
        results['E0'] = uc.set_in_units(properties[0], 'eV')
        results['B0'] = uc.set_in_units(properties[1], 'eV/angstrom^3')
        results['B0prime'] = uc.set_in_units(properties[2], 'eV/angstrom^3')
        results['V0'] = uc.set_in_units(properties[3], 'angstrom^3')

        results['thermal_properties']['volume'] = uc.set_in_units(np.hstack([qha.volume_temperature, np.nan]), 'angstrom^3')
        results['thermal_properties']['thermal_expansion'] = np.hstack([qha.thermal_expansion, np.nan])
        results['thermal_properties']['Gibbs'] = uc.set_in_units(np.hstack([qha.gibbs_temperature, np.nan]), 'eV')
        results['thermal_properties']['bulk_modulus'] = uc.set_in_units(np.hstack([qha.bulk_modulus_temperature, np.nan]), 'GPa')
        results['thermal_properties']['heat_capacity_p_numerical'] = uc.set_in_units(np.hstack([qha.heat_capacity_P_numerical, np.nan]), 'J/K/mol')
        results['thermal_properties']['heat_capacity_p_polyfit'] = uc.set_in_units(np.hstack([qha.heat_capacity_P_polyfit, np.nan]), 'J/K/mol')
        results['thermal_properties']['gruneisen'] = np.hstack([qha.gruneisen_temperature, np.nan])

    return results

2.2. phononcalc()

[5]:
def phononcalc(lammps_command: str,
               ucell: am.System,
               potential: lmp.Potential,
               mpi_command: Optional[str] = None,
               a_mult: int = 2,
               b_mult: int = 2,
               c_mult: int = 2,
               distance: float = 0.01,
               symprec: float = 1e-5,
               istrain: str = '',
               plot: bool = True,
               lammps_date: Optional[datetime.date] = None) -> dict:
    """
    Uses phonopy to compute the phonons for a unit cell structure using a
    LAMMPS interatomic potential.

    Parameters
    ----------
    lammps_command :str
        Command for running LAMMPS.
    ucell : atomman.System
        The unit cell system to perform the calculation on.
    potential : atomman.lammps.Potential
        The LAMMPS implemented potential to use.
    mpi_command : str, optional
        The MPI command for running LAMMPS in parallel.  If not given, LAMMPS
        will run serially.
    a_mult : int, optional
        The a size multiplier to use on ucell before running the phonon
        calculation.  Must be an int and not a tuple.  Default value is 2.
    b_mult : int, optional
        The b size multiplier to use on ucell before running the phonon
        calculation.  Must be an int and not a tuple.  Default value is 2.
    c_mult : int, optional
        The c size multiplier to use on ucell before running the phonon
        calculation.  Must be an int and not a tuple.  Default value is 2.
    distance : float, optional
        The atomic displacement distance used for computing the phonons.
        Default value is 0.01.
    symprec : float, optional
        Absolute length tolerance to use in identifying symmetry of atomic
        sites and system boundaries. Default value is 1e-5.
    istrain: str, optional
        A string to add to saved yaml files to ensure their uniqueness for the
        different strains explored by QHA.  Default value is '', which assumes
        only one calculation is being performed.
    plot : bool, optional
        Flag indicating if band structure and DOS figures are to be generated.
        Default value is True.
    lammps_date : datetime.date, optional
        The version date associated with lammps_command.  If not given, the
        version will be identified.
    """
    # Get lammps units
    lammps_units = lmp.style.unit(potential.units)

    # Get lammps version date
    if lammps_date is None:
        lammps_date = lmp.checkversion(lammps_command)['date']

   # Convert ucell to a primitive cell
    ucell = ucell.dump('primitive_cell', symprec=symprec)

    # Initialize Phonopy object
    phonon = phonopy.Phonopy(ucell.dump('phonopy_Atoms', symbols=potential.elements(ucell.symbols)),
                             [[a_mult, 0, 0], [0, b_mult, 0], [0, 0, c_mult]],
                             factor=phonopy.units.VaspToTHz)
    phonon.generate_displacements(distance=distance)

    # Loop over displaced supercells to compute forces
    forcearrays = []
    for supercell in phonon.supercells_with_displacements:

        # Save to LAMMPS data file
        system = am.load('phonopy_Atoms', supercell, symbols=ucell.symbols)
        system_info = system.dump('atom_data', f='disp.dat',
                                  potential=potential)

        # Define lammps variables
        lammps_variables = {}
        lammps_variables['atomman_system_pair_info'] = system_info

        # Set dump_modify_format based on lammps_date
        if lammps_date < datetime.date(2016, 8, 3):
            lammps_variables['dump_modify_format'] = '"%d %d %.13e %.13e %.13e %.13e %.13e %.13e"'
        else:
            lammps_variables['dump_modify_format'] = 'float %.13e'

        # Write lammps input script
        lammps_script = 'phonon.in'
        template = read_calc_file('iprPy.calculation.phonon', 'phonon.template')
        with open(lammps_script, 'w') as f:
            f.write(filltemplate(template, lammps_variables, '<', '>'))

        # Run LAMMPS
        lmp.run(lammps_command, script_name=lammps_script, mpi_command=mpi_command)

        # Extract forces from dump file
        forcestructure = am.load('atom_dump', 'forces.dump')
        forces = uc.set_in_units(forcestructure.atoms.force, lammps_units['force'])
        forcearrays.append(forces)

    results = {}

    # Set computed forces
    phonon.forces = forcearrays

    # Save to yaml file
    phonon.save(f'phonopy_params{istrain}.yaml')

    # Compute band structure
    phonon.produce_force_constants()
    phonon.auto_band_structure(plot=plot)
    results['band_structure'] = phonon.get_band_structure_dict()
    if plot:
        plt.ylabel('Frequency (THz)')
        plt.savefig(Path('.', 'band.png'), dpi=400)
        plt.close()

    # Compute density of states
    phonon.auto_total_dos(plot=False)
    phonon.auto_projected_dos(plot=False)
    dos = phonon.get_total_dos_dict()
    dos['frequency'] = uc.set_in_units(dos.pop('frequency_points'), 'THz')
    dos['projected_dos'] = phonon.get_projected_dos_dict()['projected_dos']
    results['dos'] = dos

    # Compute thermal properties
    phonon.run_thermal_properties()
    results['thermal_properties'] = phonon.get_thermal_properties_dict()
    phonon.write_yaml_thermal_properties(filename=f"thermal_properties{istrain}.yaml")

    results['phonon'] = phonon
    return results

2.3. phonon.template file

[6]:
with open('phonon.template', 'w') as f:
    f.write("""# LAMMPS input script that evaluates atomic forces without relaxing

box tilt large

<atomman_system_pair_info>

thermo_style custom step pe
thermo_modify format float %.13e

dump dumpy all custom 1 forces.dump id type x y z fx fy fz
dump_modify dumpy format <dump_modify_format>

run 0""")

3. Specify input parameters

3.1. System-specific paths

  • lammps_command is the LAMMPS command to use (required).

  • mpi_command MPI command for running LAMMPS in parallel. A value of None will run simulations serially.

[7]:
lammps_command = 'lmp'
mpi_command = None

# Optional: check that LAMMPS works and show its version
print(f'LAMMPS version = {am.lammps.checkversion(lammps_command)["version"]}')
LAMMPS version = 15 Sep 2022

3.2. Interatomic potential

  • potential_name gives the name of the potential_LAMMPS reference record in the iprPy library to use for the calculation.

  • potential is an atomman.lammps.Potential object (required).

[8]:
potential_name = '1999--Mishin-Y--Ni--LAMMPS--ipr1'

# Retrieve potential and parameter file(s)
potential = am.load_lammps_potential(id=potential_name, getfiles=True)

3.3. Initial unit cell system

  • ucell is an atomman.System representing a fundamental unit cell of the system (required). Here, this is generated using the load parameters and symbols.

[9]:
# Create ucell by loading prototype record
ucell = am.load('crystal', potential=potential, family='A1--Cu--fcc')

print(ucell)
avect =  [ 3.520,  0.000,  0.000]
bvect =  [ 0.000,  3.520,  0.000]
cvect =  [ 0.000,  0.000,  3.520]
origin = [ 0.000,  0.000,  0.000]
natoms = 4
natypes = 1
symbols = ('Ni',)
pbc = [ True  True  True]
per-atom properties = ['atype', 'pos']
     id   atype  pos[0]  pos[1]  pos[2]
      0       1   0.000   0.000   0.000
      1       1   0.000   1.760   1.760
      2       1   1.760   0.000   1.760
      3       1   1.760   1.760   0.000

3.4. System modifications

  • sizemults list of three integers specifying how many times the ucell vectors of \(a\), \(b\) and \(c\) are replicated in creating system.

  • system is an atomman.System to perform the scan on (required).

[10]:
sizemults = [2, 2, 2]

# Generate system by supersizing ucell
system = ucell.supersize(*sizemults)
print('# of atoms in system =', system.natoms)
# of atoms in system = 32

3.5. Calculation-specific parameters

  • displacementdistance is the displacement distance scalar to use when applying random displacements to the atoms to compute the force constants.

  • symmetryprecision is the absolute precision tolerance to use in determining crystal symmetry elements for identifying the primitive unit cell.

  • strainrange is the size of the largest strain used for the quasiharmonic calculations.

  • numstrains is the number of strain states to use for the quasiharmonic calculation. Needs to be odd and either 1 or greater than or equal to 5. If set to 1, then the quasiharmonic calculations will not be performed.

[11]:
displacementdistance = uc.set_in_units(0.01, 'angstrom')
symmetryprecision = 1e-5
strainrange = 0.01
numstrains = 5

3. Define calculation function(s) and generate template LAMMPS script(s)

4. Run calculation function(s)

[12]:
results_dict = phonon_quasiharmonic(lammps_command, ucell, potential,
                                    mpi_command = mpi_command,
                                    a_mult = sizemults[0],
                                    b_mult = sizemults[1],
                                    c_mult = sizemults[2],
                                    distance = displacementdistance,
                                    symprec = symmetryprecision,
                                    strainrange = strainrange,
                                    numstrains = numstrains)
[13]:
results_dict.keys()
[13]:
dict_keys(['phonon_objects', 'qha_object', 'band_structure', 'density_of_states', 'thermal_properties', 'qha_eos', 'volume_scan', 'E0', 'B0', 'B0prime', 'V0'])

5. Report results

The calculation both returns results_dict containing phonon information and generates files.

5.1. Phonon properties

In results_dict:

  • phonon_objects is a list containing all of the Phonopy objects at all of the strains evaluated.

  • band_structure contains the evaluated band structure points for the zero strain structure.

  • density_of_states contains the evaluated density of states (both total and projected) for the zero strain structure.

  • thermal_properties contains the temperature-dependent Helmholtz free energy, entropy, and constant volume heat capacity.

Generated files:

  • band.png plots the band structure for the zero strain structure.

  • phonopy_params.yaml is the YAML-formatted Phonopy data that can be read back in to generate a Phonopy object later.

5.2. Quasiharmonic properties

These are only generated if numstrains is greater than 1.

In results_dict:

  • qha_object is the PhonopyQHA object.

  • volume_scan contains the energy versus volume evaluations taken from the different strain states.

  • E0, B0, B0prime, V0 are the energy, Bulk modulus, B’ modulus, and volume associated with the interpolated equilibrium state from the volume scan.

  • thermal_properties is expanded to also include the temperature-dependent volume, thermal expansion, Gibbs free energy, bulk modulus, constant pressure heat capacity, and Grüneisen parameter.

Generated files:

  • bulk_modulus.png plots the volume scan data as it is used to estimate the bulk modulus. This plot provides a quick visual means of telling if the quasiharmonic analysis is likely good for the strains explored.

  • helmholtz.png plots the helmholtz free energy versus volume.

  • phonopy_params_*.yaml are the YAML-formatted Phonopy data for the non-zero strains that can be read back in to generate Phonopy objects later.

Band structure plot and values

[14]:
print('keys of "band_structure":', results_dict['band_structure'].keys())
Image("band.png", width=600)
keys of "band_structure": dict_keys(['qpoints', 'distances', 'frequencies', 'eigenvectors', 'group_velocities'])
[14]:
../_images/notebook_phonon_31_1.png

Volume versus energy scan plot and values

[15]:
print('keys of "volume_scan":',results_dict['volume_scan'].keys())
print('E0 =', uc.get_in_units(results_dict['E0'], 'eV'), 'eV')
print('B0 =', uc.get_in_units(results_dict['B0'], 'GPa'), 'GPa')
print('B0`=', uc.get_in_units(results_dict['B0prime'], 'GPa'), 'GPa')
print('V0 =', uc.get_in_units(results_dict['V0'], 'Å^3'), 'Å^3')

Image("bulk_modulus.png", width=600)
keys of "volume_scan": dict_keys(['volume', 'strain', 'energy'])
E0 = -4.450015536334154 eV
B0 = 187.29046746993 GPa
B0`= 137.80052380494985 GPa
V0 = 10.90348147775658 Å^3
[15]:
../_images/notebook_phonon_33_1.png

Helmholtz free energy versus volume plot

[16]:
Image("helmholtz_volume.png", width=600)
[16]:
../_images/notebook_phonon_35_0.png

Density of states

[17]:
dos = results_dict['density_of_states']

fig = plt.figure(figsize=(9,6))

frequency = uc.get_in_units(dos['frequency'], 'THz')
plt.plot(frequency, dos['total_dos'], '-', lw=3, label='total')
for projected in dos['projected_dos']:
    plt.plot(frequency, projected, 'r:', lw=3, label='projected')
plt.xlabel('Frequency (THz)', size='xx-large')
plt.ylabel('Density of states', size='xx-large')
plt.legend(loc=2, fontsize='xx-large')
plt.ylim(0.0, None)
plt.show()
../_images/notebook_phonon_37_0.png

Thermal properties - pull out temperature

[18]:
results_dict['thermal_properties'].keys()

thermal = results_dict['thermal_properties']
temperature = thermal['temperature']

Helmholtz and Gibbs free energies versus temperature

[19]:
energy_unit = 'eV'
#energy_unit = 'kJ/mol'

fig = plt.figure(figsize=(8,5))
helmholtz = uc.get_in_units(thermal['Helmholtz'], energy_unit)
plt.plot(temperature, helmholtz, '-', lw=3)
plt.xlabel('Temperature ($K$)', size='xx-large')
plt.xlim(0, 1000)
plt.ylabel(f'Helmholtz free energy (${energy_unit}$)', size='xx-large')
plt.show()

fig = plt.figure(figsize=(8,5))
gibbs = uc.get_in_units(thermal['Gibbs'], energy_unit)
plt.plot(temperature, gibbs, '-', lw=3)
plt.xlabel('Temperature ($K$)', size='xx-large')
plt.xlim(0, 1000)
plt.ylabel(f'Gibbs free energy (${energy_unit}$)', size='xx-large')
plt.show()
../_images/notebook_phonon_41_0.png
../_images/notebook_phonon_41_1.png

Entropy and heat capacity versus temperature

[20]:
#entropy_unit = 'eV/K'
entropy_unit = 'J/K/mol'

fig = plt.figure(figsize=(8,5))
entropy = uc.get_in_units(thermal['entropy'], entropy_unit)
plt.plot(temperature, entropy, '-', lw=3)
plt.xlabel('Temperature ($K$)', size='xx-large')
plt.xlim(0, 1000)
plt.ylabel(f'Entropy (${entropy_unit}$)', size='xx-large')
plt.show()

fig = plt.figure(figsize=(8,5))
heat_capacity_v = uc.get_in_units(thermal['heat_capacity_v'], entropy_unit)
heat_capacity_p_numerical = uc.get_in_units(thermal['heat_capacity_p_numerical'], entropy_unit)
heat_capacity_p_polyfit = uc.get_in_units(thermal['heat_capacity_p_polyfit'], entropy_unit)

plt.plot(temperature, heat_capacity_v, '-', lw=3, label='Cv')
plt.plot(temperature, heat_capacity_p_numerical, '-', lw=3, label='Cp (numerical)')
plt.plot(temperature, heat_capacity_p_polyfit, '-', lw=3, label='Cp (polyfit)')
plt.xlabel('Temperature ($K$)', size='xx-large')
plt.xlim(0, 1000)
plt.ylabel(f'Heat capacity (${entropy_unit}$)', size='xx-large')
plt.legend(loc=4, fontsize='xx-large')
plt.show()
../_images/notebook_phonon_43_0.png
../_images/notebook_phonon_43_1.png

Volume versus temperature

[21]:
volume_unit = 'Å^3'
#volume_unit = 'nm^3'

fig = plt.figure(figsize=(8,5))
volume = uc.get_in_units(thermal['volume'], volume_unit)
plt.plot(temperature, volume, '-', lw=3)
plt.xlabel('Temperature ($K$)', size='xx-large')
plt.xlim(0, 1000)
plt.ylabel(f'Volume (${volume_unit}$)', size='xx-large')
plt.show()
../_images/notebook_phonon_45_0.png

Thermal expansion versus temperature

[22]:
fig = plt.figure(figsize=(8,5))
expansion = thermal['thermal_expansion']
plt.plot(temperature, expansion, '-', lw=3)
plt.xlabel('Temperature ($K$)', size='xx-large')
plt.xlim(0, 1000)
plt.ylabel(f'Thermal expansion', size='xx-large')
plt.show()
../_images/notebook_phonon_47_0.png

Grüneisen parameter versus temperature

[23]:
fig = plt.figure(figsize=(8,5))
gruneisen = thermal['gruneisen']
plt.plot(temperature, gruneisen, '-', lw=3)
plt.xlabel('Temperature ($K$)', size='xx-large')
plt.xlim(0, 1000)
plt.ylabel(f'Grüneisen parameter', size='xx-large')
plt.show()
../_images/notebook_phonon_49_0.png