# coding: utf-8
# Distributed under the terms of the MIT License.
""" This module implements the :class:`CastepPhononWorkflow`
class, which performs phonon calculations with CASTEP in
multiple steps (only when necessary):
1. Try to pre-relax structure (skipped if check file
is already present).
2. Calculate dynamical matrix.
3. If ``phonon_fine_kpoint_mp_spacing`` keyword is found,
interpolate dynamical matrix to form phonon DOS.
4. If ``phonon_fine_kpoint_path_spacing`` key word is found,
interpolate dynamical matrix to form phonon dispersion
on the path given by seekpath.
5. If ``task=thermodynamics``, perform a CASTEP thermodynamics
calculation for the temperature-dependence of the free energy.
"""
import os
import copy
import logging
from matador.workflows.workflows import Workflow
LOG = logging.getLogger("run3")
[docs]def castep_full_phonon(computer, calc_doc, seed, **kwargs):
"""Perform a "full" phonon calculation on a system, i.e.
first perform a relaxation in a standardised unit cell,
then compute the dynamical matrix, then finally interpolate
that dynamical matrix into dispersion curves and DOS. This function
is a wrapper for the :class:`CastepPhononWorkflow` class.
Parameters:
computer (:obj:`matador.compute.ComputeTask`): the object that will be calling CASTEP.
calc_doc (dict): dictionary of structure and calculation
parameters.
seed (str): root seed for the calculation.
Raises:
RuntimeError: if any part of the calculation fails.
Returns:
bool: True if Workflow completed successfully, or False otherwise.
"""
workflow = CastepPhononWorkflow(computer, calc_doc, seed, **kwargs)
return workflow.success
[docs]class CastepPhononWorkflow(Workflow):
"""Perform a "full" phonon calculation on a system, i.e.
first perform a relaxation in a standardised unit cell,
then compute the dynamical matrix, then finally interpolate
that dynamical matrix into dispersion curves and DOS.
Attributes:
computer (:obj:`ComputeTask`): the object that calls CASTEP.
calc_doc (dict): the interim dictionary of structural and
calculation parameters.
seed (str): the root seed for the calculation.
success (bool): the status of the Workflow: only set to True after
post-processing method completes.
"""
[docs] def preprocess(self):
"""Decide which parts of the Workflow need to be performed,
and set the appropriate CASTEP parameters.
"""
# default todo
todo = {
"relax": True,
"dynmat": True,
"vdos": False,
"dispersion": False,
"thermodynamics": False,
}
# definition of steps and names
steps = {
"relax": castep_phonon_prerelax,
"dynmat": castep_phonon_dynmat,
"vdos": castep_phonon_dos,
"dispersion": castep_phonon_dispersion,
"thermodynamics": castep_phonon_thermodynamics,
}
exts = {
"relax": {
"input": [".cell", ".param"],
"output": [".castep", "-out.cell", ".*err"],
},
"dynmat": {"input": [".cell", ".param"], "output": [".castep", ".*err"]},
"vdos": {
"input": [".cell", ".param"],
"output": [".castep", ".phonon", ".phonon_dos", ".*err"],
},
"dispersion": {
"input": [".cell", ".param"],
"output": [".castep", ".phonon", ".*err"],
},
"thermodynamics": {
"input": [".cell", ".param"],
"output": [".castep", ".*err"],
},
}
if self.calc_doc.get("task").lower() in [
"phonon",
"thermodynamics",
"phonon+efield",
]:
if (
"phonon_fine_kpoint_path" in self.calc_doc
or "phonon_fine_kpoint_list" in self.calc_doc
or "phonon_fine_kpoint_path_spacing" in self.calc_doc
):
todo["dispersion"] = True
if "phonon_fine_kpoint_mp_spacing" in self.calc_doc:
todo["vdos"] = True
if self.calc_doc["task"].lower() == "thermodynamics":
todo["thermodynamics"] = True
# prepare to do pre-relax if there's no check file
if os.path.isfile(self.seed + ".check"):
todo["relax"] = False
LOG.info(
"Restarting from {}.check, so not performing re-relaxation".format(
self.seed
)
)
for key in todo:
if todo[key]:
self.add_step(
steps[key],
key,
input_exts=exts[key].get("input"),
output_exts=exts[key].get("output"),
)
# always standardise the cell so that any phonon calculation can have
# post-processing performed after the fact, unless a path has been provided
if (
"phonon_fine_kpoint_list" not in self.calc_doc
and "phonon_fine_kpoint_path" not in self.calc_doc
):
from matador.utils.cell_utils import cart2abc
prim_doc, kpt_path = self.computer.get_seekpath_compliant_input(
self.calc_doc,
self.calc_doc.get("phonon_fine_kpoint_path_spacing", 0.02),
)
self.calc_doc.update(prim_doc)
self.calc_doc["lattice_abc"] = cart2abc(self.calc_doc["lattice_cart"])
if todo["dispersion"]:
self.calc_doc["phonon_fine_kpoint_list"] = kpt_path
elif todo["dispersion"] and "phonon_fine_kpoint_path" in self.calc_doc:
self._user_defined_kpt_path = True
LOG.warning("Using user-defined k-point path for all structures.")
self.calc_doc["phonon_fine_kpoint_spacing"] = self.calc_doc.get(
"phonon_fine_kpoint_path_spacing", 0.05
)
# always shift phonon grid to include Gamma
if "phonon_kpoint_mp_spacing" in self.calc_doc:
from matador.utils.cell_utils import calc_mp_grid, shift_to_include_gamma
grid = calc_mp_grid(
self.calc_doc["lattice_cart"], self.calc_doc["phonon_kpoint_mp_spacing"]
)
offset = shift_to_include_gamma(grid)
if offset != [0, 0, 0]:
self.calc_doc["phonon_kpoint_mp_offset"] = offset
LOG.debug("Set phonon MP grid offset to {}".format(offset))
LOG.info("Preprocessing completed: run3 phonon options {}".format(todo))
[docs]def castep_phonon_prerelax(computer, calc_doc, seed):
"""Run a singleshot geometry optimisation before an SCF-style calculation.
This is typically used to ensure phonon calculations start successfully.
The phonon calculation will then be restarted from the .check file produced here.
Parameters:
computer (:obj:`ComputeTask`): the object that will be calling CASTEP.
calc_doc (dict): the structure to run on.
seed (str): root filename of structure.
"""
from matador.workflows.castep.common import castep_prerelax
LOG.info("Performing CASTEP phonon pre-relax...")
required = ["write_checkpoint"]
forbidden = [
"phonon_fine_kpoint_list",
"phonon_fine_kpoint_path",
"phonon_fine_kpoint_mp_spacing",
"phonon_fine_kpoint_path_spacing",
]
return castep_prerelax(
computer, calc_doc, seed, required_keys=required, forbidden_keys=forbidden
)
[docs]def castep_phonon_dynmat(computer, calc_doc, seed):
"""Runs a singleshot phonon dynmat calculation, with no "fine_method" interpolation.
Parameters:
computer (:obj:`ComputeTask`): the object that will be calling CASTEP.
calc_doc (dict): the structure to run on.
seed (str): root filename of structure.
"""
LOG.info("Performing CASTEP dynmat calculation...")
dynmat_doc = copy.deepcopy(calc_doc)
dynmat_doc["write_checkpoint"] = "ALL"
if calc_doc["task"].lower() == "phonon+efield":
dynmat_doc["task"] = "phonon+efield"
else:
dynmat_doc["task"] = "phonon"
dynmat_doc["continuation"] = "default"
required = ["continuation", "write_checkpoint"]
forbidden = [
"phonon_fine_kpoint_list",
"phonon_fine_kpoint_path",
"phonon_fine_kpoint_mp_spacing",
"phonon_fine_kpoint_path_spacing",
]
computer.validate_calc_doc(dynmat_doc, required, forbidden)
return computer.run_castep_singleshot(
dynmat_doc, seed, keep=True, intermediate=True
)
[docs]def castep_phonon_dos(computer, calc_doc, seed):
"""Runs a DOS interpolation on top of a completed
phonon calculation.
Parameters:
computer (:obj:`ComputeTask`): the object that will be calling CASTEP.
calc_doc (dict): the structure to run on.
seed (str): root filename of structure.
"""
LOG.info("Performing CASTEP phonon DOS calculation...")
dos_doc = copy.deepcopy(calc_doc)
dos_doc["task"] = "phonon"
dos_doc["phonon_calculate_dos"] = True
dos_doc["continuation"] = "default"
required = ["phonon_fine_kpoint_mp_spacing"]
forbidden = [
"phonon_fine_kpoint_list",
"phonon_fine_kpoint_path",
"phonon_fine_kpoint_path_spacing",
]
computer.validate_calc_doc(dos_doc, required, forbidden)
return computer.run_castep_singleshot(dos_doc, seed, keep=True, intermediate=True)
[docs]def castep_phonon_dispersion(computer, calc_doc, seed):
"""Runs a dispersion interpolation on top of a completed
phonon calculation.
Parameters:
computer (:obj:`ComputeTask`): the object that will be calling CASTEP.
calc_doc (dict): the structure to run on.
seed (str): root filename of structure.
"""
LOG.info("Performing CASTEP phonon dispersion calculation...")
disp_doc = copy.deepcopy(calc_doc)
disp_doc["task"] = "phonon"
disp_doc["phonon_calculate_dos"] = False
disp_doc["continuation"] = "default"
required = []
forbidden = ["phonon_fine_kpoint_mp_spacing"]
computer.validate_calc_doc(disp_doc, required, forbidden)
return computer.run_castep_singleshot(disp_doc, seed, keep=True, intermediate=True)
[docs]def castep_phonon_thermodynamics(computer, calc_doc, seed):
"""Runs a "thermodynamics" interpolation on top of a completed
phonon calculation, using the phonon_fine_kpoint_mp_grid.
Parameters:
computer (:obj:`ComputeTask`): the object that will be calling CASTEP.
calc_doc (dict): the structure to run on.
seed (str): root filename of structure.
"""
LOG.info("Performing CASTEP thermodynamics calculation...")
thermo_doc = copy.deepcopy(calc_doc)
thermo_doc["continuation"] = "default"
thermo_doc["task"] = "thermodynamics"
thermo_doc["phonon_calculate_dos"] = False
required = ["phonon_fine_kpoint_mp_spacing"]
forbidden = [
"phonon_fine_kpoint_list",
"phonon_fine_kpoint_path",
"phonon_fine_kpoint_path_spacing",
]
computer.validate_calc_doc(thermo_doc, required, forbidden)
return computer.run_castep_singleshot(
thermo_doc, seed, keep=True, intermediate=True
)