"""Phonon calculations."""
from __future__ import annotations
from collections.abc import Sequence
from typing import Any, get_args
from ase import Atoms
from numpy import ndarray
import phonopy
from phonopy.file_IO import write_force_constants_to_hdf5
from phonopy.phonon.band_structure import (
get_band_qpoints_and_path_connections,
get_band_qpoints_by_seekpath,
)
from phonopy.structure.atoms import PhonopyAtoms
from yaml import safe_load
from janus_core.calculations.base import BaseCalculation
from janus_core.calculations.geom_opt import GeomOpt
from janus_core.helpers.janus_types import (
Architectures,
ASEReadArgs,
Devices,
MaybeList,
MaybeSequence,
PathLike,
PhononCalcs,
)
from janus_core.helpers.utils import none_to_dict, track_progress
[docs]
class Phonons(BaseCalculation):
"""
Configure, perform phonon calculations and write out results.
Parameters
----------
struct
ASE Atoms structure to calculate phonons for. Required if `struct_path` is
None. Default is None.
struct_path
Path of structure to calculate phonons for. Required if `struct` is None.
Default is None.
arch
MLIP architecture to use for calculations. Default is "mace_mp".
device
Device to run MLIP model on. Default is "cpu".
model_path
Path to MLIP model. Default is `None`.
read_kwargs
Keyword arguments to pass to ase.io.read. By default,
read_kwargs["index"] is -1.
calc_kwargs
Keyword arguments to pass to the selected calculator. Default is {}.
set_calc
Whether to set (new) calculators for structures. Default is None.
attach_logger
Whether to attach a logger. Default is True if "filename" is passed in
log_kwargs, else False.
log_kwargs
Keyword arguments to pass to `config_logger`. Default is {}.
track_carbon
Whether to track carbon emissions of calculation. Requires attach_logger.
Default is True if attach_logger is True, else False.
tracker_kwargs
Keyword arguments to pass to `config_tracker`. Default is {}.
calcs
Phonon calculations to run. Default calculates force constants only.
supercell
The size of a supercell for calculation, or the supercell itself.
If a single number is provided, it is interpreted as the size, so a
diagonal supercell of that size in all dimensions is constructed.
If three values are provided, they are interpreted as the diagonal
values of a diagonal supercell. If nine values are provided, they
are assumed to be the full supercell matrix in the style of Phonopy,
so the first three values will be used as the first row, the second
three as the second row, etc. Default is 2.
displacement
Displacement for force constants calculation, in A. Default is 0.01.
displacement_kwargs
Keyword arguments to pass to generate_displacements. Default is {}.
mesh
Mesh for sampling. Default is (10, 10, 10).
symmetrize
Whether to symmetrize structure and force constants after calculation.
Default is False.
minimize
Whether to perform geometry optimisation before calculating phonons.
Default is False.
minimize_kwargs
Keyword arguments to pass to geometry optimizer. Default is {}.
n_qpoints
Number of q-points to sample along generated path, including end points.
Unused if `qpoint_file` is specified. Default is 51.
qpoint_file
Path to yaml file with info to generate a path of q-points for band structure.
Default is None.
dos_kwargs
Keyword arguments to pass to run_total_dos. Default is {}.
pdos_kwargs
Keyword arguments to pass to run_projected_dos. Default is {}.
temp_min
Start temperature for thermal properties calculations, in K. Default is 0.0.
temp_max
End temperature for thermal properties calculations, in K. Default is 1000.0.
temp_step
Temperature step for thermal properties calculations, in K. Default is 50.0.
force_consts_to_hdf5
Whether to write force constants in hdf format or not. Default is True.
plot_to_file
Whether to plot various graphs as band stuctures, dos/pdos in svg.
Default is False.
write_results
Default for whether to write out results to file. Default is True.
write_full
Whether to maximize information written in various output files.
Default is True.
file_prefix
Prefix for output filenames. Default is inferred from chemical formula of the
structure.
enable_progress_bar
Whether to show a progress bar during phonon calculations. Default is False.
Attributes
----------
calc : ase.calculators.calculator.Calculator
ASE Calculator attached to structure.
results : dict
Results of phonon calculations.
"""
[docs]
def __init__(
self,
struct: Atoms | None = None,
struct_path: PathLike | None = None,
arch: Architectures = "mace_mp",
device: Devices = "cpu",
model_path: PathLike | None = None,
read_kwargs: ASEReadArgs | None = None,
calc_kwargs: dict[str, Any] | None = None,
set_calc: bool | None = None,
attach_logger: bool | None = None,
log_kwargs: dict[str, Any] | None = None,
track_carbon: bool | None = None,
tracker_kwargs: dict[str, Any] | None = None,
calcs: MaybeSequence[PhononCalcs] = (),
supercell: MaybeList[int] = 2,
displacement: float = 0.01,
displacement_kwargs: dict[str, Any] | None = None,
mesh: tuple[int, int, int] = (10, 10, 10),
symmetrize: bool = False,
minimize: bool = False,
minimize_kwargs: dict[str, Any] | None = None,
n_qpoints: int = 51,
qpoint_file: PathLike | None = None,
dos_kwargs: dict[str, Any] | None = None,
pdos_kwargs: dict[str, Any] | None = None,
temp_min: float = 0.0,
temp_max: float = 1000.0,
temp_step: float = 50.0,
force_consts_to_hdf5: bool = True,
plot_to_file: bool = False,
write_results: bool = True,
write_full: bool = True,
file_prefix: PathLike | None = None,
enable_progress_bar: bool = False,
) -> None:
"""
Initialise Phonons class.
Parameters
----------
struct
ASE Atoms structure to calculate phonons for. Required if `struct_path` is
None. Default is None.
struct_path
Path of structure to calculate phonons for. Required if `struct` is None.
Default is None.
arch
MLIP architecture to use for calculations. Default is "mace_mp".
device
Device to run MLIP model on. Default is "cpu".
model_path
Path to MLIP model. Default is `None`.
read_kwargs
Keyword arguments to pass to ase.io.read. By default,
read_kwargs["index"] is -1.
calc_kwargs
Keyword arguments to pass to the selected calculator. Default is {}.
set_calc
Whether to set (new) calculators for structures. Default is None.
attach_logger
Whether to attach a logger. Default is True if "filename" is passed in
log_kwargs, else False.
log_kwargs
Keyword arguments to pass to `config_logger`. Default is {}.
track_carbon
Whether to track carbon emissions of calculation. Requires attach_logger.
Default is True if attach_logger is True, else False.
tracker_kwargs
Keyword arguments to pass to `config_tracker`. Default is {}.
calcs
Phonon calculations to run. Default calculates force constants only.
supercell
The size of a supercell for calculation, or the supercell itself.
If a single number is provided, it is interpreted as the size, so a
diagonal supercell of that size in all dimensions is constructed.
If three values are provided, they are interpreted as the diagonal
values of a diagonal supercell. If nine values are provided, they
are assumed to be the full supercell matrix in the style of Phonopy,
so the first three values will be used as the first row, the second
three as the second row, etc. Default is 2.
displacement
Displacement for force constants calculation, in A. Default is 0.01.
displacement_kwargs
Keyword arguments to pass to generate_displacements. Default is {}.
mesh
Mesh for sampling. Default is (10, 10, 10).
symmetrize
Whether to symmetrize structure and force constants after calculation.
Default is False.
minimize
Whether to perform geometry optimisation before calculating phonons.
Default is False.
minimize_kwargs
Keyword arguments to pass to geometry optimizer. Default is {}.
n_qpoints
Number of q-points to sample along generated path, including end points.
Unused if `qpoint_file` is specified. Default is 51.
qpoint_file
Path to yaml file with info to generate a path of q-points for band
structure. Default is None.
dos_kwargs
Keyword arguments to pass to run_total_dos. Default is {}.
pdos_kwargs
Keyword arguments to pass to run_projected_dos. Default is {}.
temp_min
Start temperature for thermal calculations, in K. Default is 0.0.
temp_max
End temperature for thermal calculations, in K. Default is 1000.0.
temp_step
Temperature step for thermal calculations, in K. Default is 50.0.
force_consts_to_hdf5
Whether to write force constants in hdf format or not. Default is True.
plot_to_file
Whether to plot various graphs as band stuctures, dos/pdos in svg.
Default is False.
write_results
Default for whether to write out results to file. Default is True.
write_full
Whether to maximize information written in various output files.
Default is True.
file_prefix
Prefix for output filenames. Default is inferred from structure name, or
chemical formula of the structure.
enable_progress_bar
Whether to show a progress bar during phonon calculations. Default is False.
"""
read_kwargs, displacement_kwargs, minimize_kwargs, dos_kwargs, pdos_kwargs = (
none_to_dict(
read_kwargs,
displacement_kwargs,
minimize_kwargs,
dos_kwargs,
pdos_kwargs,
)
)
self.calcs = calcs
self.displacement = displacement
self.displacement_kwargs = displacement_kwargs
self.mesh = mesh
self.symmetrize = symmetrize
self.minimize = minimize
self.minimize_kwargs = minimize_kwargs
self.n_qpoints = n_qpoints
self.qpoint_file = qpoint_file
self.dos_kwargs = dos_kwargs
self.pdos_kwargs = pdos_kwargs
self.temp_min = temp_min
self.temp_max = temp_max
self.temp_step = temp_step
self.force_consts_to_hdf5 = force_consts_to_hdf5
self.plot_to_file = plot_to_file
self.write_results = write_results
self.write_full = write_full
self.enable_progress_bar = enable_progress_bar
# Ensure supercell is a valid list
self.supercell = [supercell] * 3 if isinstance(supercell, int) else supercell
if len(self.supercell) not in [3, 9]:
raise ValueError(
"`supercell` must be an integer, or list of length 3 or 9. A list of "
"length 3 must specify the values of a diagonal supercell, and a list "
"of length 9 must specify all values of a full supercell matrix, where "
"the first three values are the first row, the second three are the "
"second row, etc."
)
# Read last image by default
read_kwargs.setdefault("index", -1)
# Initialise structures and logging
super().__init__(
calc_name=__name__,
struct=struct,
struct_path=struct_path,
arch=arch,
device=device,
model_path=model_path,
read_kwargs=read_kwargs,
sequence_allowed=False,
calc_kwargs=calc_kwargs,
set_calc=set_calc,
attach_logger=attach_logger,
log_kwargs=log_kwargs,
track_carbon=track_carbon,
tracker_kwargs=tracker_kwargs,
file_prefix=file_prefix,
)
if not self.struct.calc:
raise ValueError("Please attach a calculator to `struct`.")
if self.minimize:
if self.logger:
self.minimize_kwargs["log_kwargs"] = {
"filename": self.log_kwargs["filename"],
"name": self.logger.name,
"filemode": "a",
}
self.minimize_kwargs["track_carbon"] = self.track_carbon
# Write out file by default
self.minimize_kwargs.setdefault("write_results", True)
# If not specified otherwise, save optimized structure consistently with
# other phonon output files
opt_file = self._build_filename("opt.extxyz")
if "write_kwargs" in self.minimize_kwargs:
# Use _build_filename even if given filename to ensure directory exists
self.minimize_kwargs["write_kwargs"].setdefault("filename", None)
self.minimize_kwargs["write_kwargs"]["filename"] = self._build_filename(
"", filename=self.minimize_kwargs["write_kwargs"]["filename"]
).absolute()
else:
self.minimize_kwargs["write_kwargs"] = {"filename": opt_file}
if self.symmetrize:
self.minimize_kwargs.setdefault("symmetrize", True)
self.calc = self.struct.calc
self.results = {}
@property
def calcs(self) -> Sequence[PhononCalcs]:
"""
Phonon calculations to be run.
Returns
-------
Sequence[PhononCalcs]
Phonon calculations.
"""
return self._calcs
@calcs.setter
def calcs(self, value: MaybeSequence[PhononCalcs]) -> None:
"""
Setter for `calcs`.
Parameters
----------
value
Phonon calculations to be run.
"""
if isinstance(value, str):
value = (value,)
for calc in value:
if calc not in get_args(PhononCalcs):
raise NotImplementedError(
f"Calculations '{calc}' cannot currently be performed."
)
# If none specified, only force constants will be calculated
if not value:
value = ()
self._calcs = value
[docs]
def calc_force_constants(
self, write_force_consts: bool | None = None, **kwargs
) -> None:
"""
Calculate force constants and optionally write results.
Parameters
----------
write_force_consts
Whether to write out results to file. Default is self.write_results.
**kwargs
Additional keyword arguments to pass to `write_force_constants`.
"""
if write_force_consts is None:
write_force_consts = self.write_results
if self.minimize:
optimizer = GeomOpt(self.struct, **self.minimize_kwargs)
optimizer.run()
if self.logger:
self.logger.info("Starting phonons calculation")
if self.tracker:
self.tracker.start_task("Phonon calculation")
self._set_info_units()
cell = self._ASE_to_PhonopyAtoms(self.struct)
if len(self.supercell) == 3:
supercell_matrix = (
(self.supercell[0], 0, 0),
(0, self.supercell[1], 0),
(0, 0, self.supercell[2]),
)
else:
supercell_matrix = (
tuple(self.supercell[:3]),
tuple(self.supercell[3:6]),
tuple(self.supercell[6:]),
)
phonon = phonopy.Phonopy(cell, supercell_matrix)
phonon.generate_displacements(
distance=self.displacement,
**self.displacement_kwargs,
)
disp_supercells = phonon.supercells_with_displacements
if self.enable_progress_bar:
disp_supercells = track_progress(
disp_supercells, "Computing displacements..."
)
phonon.forces = [
self._calc_forces(supercell)
for supercell in disp_supercells
if supercell is not None
]
phonon.produce_force_constants()
self.results["phonon"] = phonon
if self.symmetrize:
self.results["phonon"].symmetrize_force_constants(level=1)
if self.logger:
self.logger.info("Phonons calculation complete")
if self.tracker:
emissions = self.tracker.stop_task().emissions
self.struct.info["emissions"] = emissions
self.tracker.flush()
if write_force_consts:
self.write_force_constants(**kwargs)
[docs]
def write_force_constants(
self,
*,
phonopy_file: PathLike | None = None,
force_consts_to_hdf5: bool | None = None,
force_consts_file: PathLike | None = None,
) -> None:
"""
Write results of force constants calculations.
Parameters
----------
phonopy_file
Name of yaml file to save params of phonopy and optionally force constants.
Default is inferred from `file_prefix`.
force_consts_to_hdf5
Whether to save the force constants separately to an hdf5 file. Default is
self.force_consts_to_hdf5.
force_consts_file
Name of hdf5 file to save force constants. Unused if `force_consts_to_hdf5`
is False. Default is inferred from `file_prefix`.
"""
if "phonon" not in self.results:
raise ValueError(
"Force constants have not been calculated yet. "
"Please run `calc_force_constants` first"
)
if force_consts_to_hdf5 is None:
force_consts_to_hdf5 = self.force_consts_to_hdf5
phonopy_file = self._build_filename("phonopy.yml", filename=phonopy_file)
force_consts_file = self._build_filename(
"force_constants.hdf5", filename=force_consts_file
)
phonon = self.results["phonon"]
save_force_consts = not force_consts_to_hdf5
phonon.save(phonopy_file, settings={"force_constants": save_force_consts})
if force_consts_to_hdf5:
write_force_constants_to_hdf5(
phonon.force_constants, filename=force_consts_file
)
[docs]
def calc_bands(self, write_bands: bool | None = None, **kwargs) -> None:
"""
Calculate band structure and optionally write and plot results.
Parameters
----------
write_bands
Whether to write out results to file. Default is self.write_results.
**kwargs
Additional keyword arguments to pass to `write_bands`.
"""
if write_bands is None:
write_bands = self.write_results
# Calculate phonons if not already in results
if "phonon" not in self.results:
# Use general (self.write_results) setting for writing force constants
self.calc_force_constants(write_force_consts=self.write_results)
if write_bands:
self.write_bands(**kwargs)
[docs]
def write_bands(
self,
*,
bands_file: PathLike | None = None,
save_plots: bool | None = None,
plot_file: PathLike | None = None,
) -> None:
"""
Write results of band structure calculations.
Parameters
----------
bands_file
Name of yaml file to save band structure. Default is inferred from
`file_prefix`.
save_plots
Whether to save plot to file. Default is self.plot_to_file.
plot_file
Name of svg file if saving band structure plot. Default is inferred from
`file_prefix`.
"""
if "phonon" not in self.results:
raise ValueError(
"Force constants have not been calculated yet. "
"Please run `calc_force_constants` first"
)
if save_plots is None:
save_plots = self.plot_to_file
if self.qpoint_file:
bands_file = self._build_filename("bands.yml.xz", filename=bands_file)
with open(self.qpoint_file, encoding="utf8") as file:
paths_info = safe_load(file)
labels = paths_info["labels"]
num_q_points = sum(len(q) for q in paths_info["paths"])
num_labels = len(labels)
assert num_q_points == num_labels, (
"Number of labels is different to number of q-points specified"
)
q_points, connections = get_band_qpoints_and_path_connections(
band_paths=paths_info["paths"], npoints=paths_info["npoints"]
)
else:
bands_file = self._build_filename("auto_bands.yml.xz", filename=bands_file)
q_points, labels, connections = get_band_qpoints_by_seekpath(
self.results["phonon"].primitive, self.n_qpoints
)
self.results["phonon"].run_band_structure(
paths=q_points,
path_connections=connections,
labels=labels,
with_eigenvectors=self.write_full,
with_group_velocities=self.write_full,
)
self.results["phonon"].write_yaml_band_structure(
filename=bands_file,
compression="lzma",
)
bplt = self.results["phonon"].plot_band_structure()
if save_plots:
plot_file = self._build_filename("bands.svg", filename=plot_file)
bplt.savefig(plot_file)
[docs]
def calc_thermal_props(
self,
mesh: tuple[int, int, int] | None = None,
write_thermal: bool | None = None,
**kwargs,
) -> None:
"""
Calculate thermal properties and optionally write results.
Parameters
----------
mesh
Mesh for sampling. Default is self.mesh.
write_thermal
Whether to write out thermal properties to file. Default is
self.write_results.
**kwargs
Additional keyword arguments to pass to `write_thermal_props`.
"""
if write_thermal is None:
write_thermal = self.write_results
if mesh is None:
mesh = self.mesh
# Calculate phonons if not already in results
if "phonon" not in self.results:
# Use general (self.write_results) setting for writing force constants
self.calc_force_constants(write_force_consts=self.write_results)
if self.logger:
self.logger.info("Starting thermal properties calculation")
if self.tracker:
self.tracker.start_task("Thermal calculation")
self.results["phonon"].run_mesh(mesh)
self.results["phonon"].run_thermal_properties(
t_step=self.temp_step, t_max=self.temp_max, t_min=self.temp_min
)
self.results["thermal_properties"] = self.results[
"phonon"
].get_thermal_properties_dict()
if self.logger:
self.logger.info("Thermal properties calculation complete")
if self.tracker:
emissions = self.tracker.stop_task().emissions
self.struct.info["emissions"] = emissions
self.tracker.flush()
if write_thermal:
self.write_thermal_props(**kwargs)
[docs]
def write_thermal_props(self, thermal_file: PathLike | None = None) -> None:
"""
Write results of thermal properties calculations.
Parameters
----------
thermal_file
Name of data file to save thermal properties. Default is inferred from
`file_prefix`.
"""
thermal_file = self._build_filename("thermal.yml", filename=thermal_file)
self.results["phonon"].write_yaml_thermal_properties(filename=thermal_file)
[docs]
def calc_dos(
self,
*,
mesh: tuple[int, int, int] | None = None,
write_dos: bool | None = None,
**kwargs,
) -> None:
"""
Calculate density of states and optionally write results.
Parameters
----------
mesh
Mesh for sampling. Default is self.mesh.
write_dos
Whether to write out results to file. Default is True.
**kwargs
Additional keyword arguments to pass to `write_dos`.
"""
if write_dos is None:
write_dos = self.write_results
if mesh is None:
mesh = self.mesh
# Calculate phonons if not already in results
if "phonon" not in self.results:
# Use general (self.write_results) setting for writing force constants
self.calc_force_constants(write_force_consts=self.write_results)
if self.logger:
self.logger.info("Starting DOS calculation")
if self.tracker:
self.tracker.start_task("DOS calculation")
self.results["phonon"].run_mesh(mesh)
self.results["phonon"].run_total_dos(**self.dos_kwargs)
if self.logger:
self.logger.info("DOS calculation complete")
if self.tracker:
emissions = self.tracker.stop_task().emissions
self.struct.info["emissions"] = emissions
self.tracker.flush()
if write_dos:
self.write_dos(**kwargs)
[docs]
def write_dos(
self,
*,
dos_file: PathLike | None = None,
plot_to_file: bool | None = None,
plot_file: PathLike | None = None,
plot_bands: bool = False,
plot_bands_file: PathLike | None = None,
) -> None:
"""
Write results of DOS calculation.
Parameters
----------
dos_file
Name of data file to save the calculated DOS. Default is inferred from
`file_prefix`.
plot_to_file
Whether to save plot to file. Default is self.plot_to_file.
plot_file
Name of svg file if saving plot of the DOS. Default is inferred from
`file_prefix`.
plot_bands
Whether to plot the band structure and DOS together. Default is True.
plot_bands_file
Name of svg file if saving plot of the band structure and DOS.
Default is inferred from `file_prefix`.
"""
# Calculate phonons if not already in results
if "phonon" not in self.results or self.results["phonon"].total_dos is None:
raise ValueError(
"The DOS has not been calculated yet. Please run `calc_dos` first"
)
if plot_bands and self.results["phonon"].band_structure is None:
raise ValueError(
"The band structure has not been calculated yet. "
"Please run `calc_bands` first, or set `plot_bands = False`"
)
if plot_to_file is None:
plot_to_file = self.plot_to_file
dos_file = self._build_filename("dos.dat", filename=dos_file)
self.results["phonon"].total_dos.write(dos_file)
bplt = self.results["phonon"].plot_total_dos()
if plot_to_file:
plot_file = self._build_filename("dos.svg", filename=plot_file)
bplt.savefig(plot_file)
if plot_bands:
bplt = self.results["phonon"].plot_band_structure_and_dos()
if plot_to_file:
plot_bands_file = self._build_filename(
"bs-dos.svg", filename=plot_bands_file
)
bplt.savefig(plot_bands_file)
[docs]
def calc_pdos(
self,
*,
mesh: tuple[int, int, int] | None = None,
write_pdos: bool | None = None,
**kwargs,
) -> None:
"""
Calculate projected density of states and optionally write results.
Parameters
----------
mesh
Mesh for sampling. Default is self.mesh.
write_pdos
Whether to write out results to file. Default is self.write_results.
**kwargs
Additional keyword arguments to pass to `write_pdos`.
"""
if write_pdos is None:
write_pdos = self.write_results
if mesh is None:
mesh = self.mesh
# Calculate phonons if not already in results
if "phonon" not in self.results:
# Use general (self.write_results) setting for writing force constants
self.calc_force_constants(write_force_consts=self.write_results)
if self.logger:
self.logger.info("Starting PDOS calculation")
if self.tracker:
self.tracker.start_task("PDOS calculation")
self.results["phonon"].run_mesh(
mesh, with_eigenvectors=True, is_mesh_symmetry=False
)
self.results["phonon"].run_projected_dos(**self.pdos_kwargs)
if self.logger:
self.logger.info("PDOS calculation complete")
if self.tracker:
emissions = self.tracker.stop_task().emissions
self.struct.info["emissions"] = emissions
self.tracker.flush()
if write_pdos:
self.write_pdos(**kwargs)
[docs]
def write_pdos(
self,
*,
pdos_file: PathLike | None = None,
plot_to_file: bool | None = None,
plot_file: PathLike | None = None,
) -> None:
"""
Write results of PDOS calculation.
Parameters
----------
pdos_file
Name of file to save the calculated PDOS. Default is inferred from
`file_prefix`.
plot_to_file
Whether to save plot to file. Default is self.plot_to_file.
plot_file
Name of svg file if saving plot of the calculated PDOS. Default is inferred
from `file_prefix`.
"""
# Calculate phonons if not already in results
if "phonon" not in self.results or self.results["phonon"].projected_dos is None:
raise ValueError(
"The PSDOS has not been calculated yet. Please run `calc_pdos` first"
)
if plot_to_file is None:
plot_to_file = self.plot_to_file
pdos_file = self._build_filename("pdos.dat", filename=pdos_file)
self.results["phonon"].projected_dos.write(pdos_file)
bplt = self.results["phonon"].plot_projected_dos()
if plot_to_file:
plot_file = self._build_filename("pdos.svg", filename=plot_file)
bplt.savefig(plot_file)
# No magnetic moments considered
# Disable invalid-function-name
[docs]
def _Phonopy_to_ASEAtoms(self, struct: PhonopyAtoms) -> Atoms: # noqa: N802
"""
Convert Phonopy Atoms structure to ASE Atoms structure.
Parameters
----------
struct
PhonopyAtoms structure to be converted.
Returns
-------
Atoms
Converted ASE Atoms structure.
"""
return Atoms(
symbols=struct.symbols,
scaled_positions=struct.scaled_positions,
cell=struct.cell,
masses=struct.masses,
pbc=True,
calculator=self.calc,
)
# Disable invalid-function-name
[docs]
def _ASE_to_PhonopyAtoms(self, struct: Atoms) -> PhonopyAtoms: # noqa: N802
"""
Convert ASE Atoms structure to Phonopy Atoms structure.
Parameters
----------
struct
ASE Atoms structure to be converted.
Returns
-------
PhonopyAtoms
Converted PhonopyAtoms structure.
"""
return PhonopyAtoms(
symbols=struct.get_chemical_symbols(),
cell=struct.cell.array,
scaled_positions=struct.get_scaled_positions(),
masses=struct.get_masses(),
)
[docs]
def _calc_forces(self, struct: PhonopyAtoms) -> ndarray:
"""
Calculate forces on PhonopyAtoms structure.
Parameters
----------
struct
Structure to calculate forces on.
Returns
-------
ndarray
Forces on the structure.
"""
atoms = self._Phonopy_to_ASEAtoms(struct)
return atoms.get_forces()
[docs]
def run(self) -> None:
"""Run phonon calculations."""
# Calculate force constants
self.calc_force_constants()
# Calculate band structure
if "bands" in self.calcs:
self.calc_bands()
# Calculate thermal properties if specified
if "thermal" in self.calcs:
self.calc_thermal_props()
# Calculate DOS and PDOS if specified
if "dos" in self.calcs:
self.calc_dos(plot_bands="bands" in self.calcs)
if "pdos" in self.calcs:
self.calc_pdos()