diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml
index cafdc811bb..4affb98327 100644
--- a/.github/workflows/testing.yml
+++ b/.github/workflows/testing.yml
@@ -67,8 +67,20 @@ jobs:
mkdir -p ~/.abinit/pseudos
cp -r tests/test_data/abinit/pseudos/ONCVPSP-PBE-SR-PDv0.4 ~/.abinit/pseudos
uv pip install .[strict,strict-forcefields,tests,abinit]
+ uv pip install -i https://test.pypi.org/simple/ pheasy==0.0.1 # Install pheasy
uv pip install torch-runstats
uv pip install --no-deps nequip==0.5.6
+
+
+ - name: Install ALA-Mode
+ run: |
+ micromamba activate a2
+ micromamba install -n a2 -c conda-forge numpy scipy h5py compilers “libblas=*=*mkl” spglib boost eigen cmake ipython mkl-include openmpi --yes
+ git clone https://github.com/ttadano/ALM.git # do I need to modify this?
+ cd ALM # do I need to modify this?
+ cd python
+ python setup.py build # do I need to modify this?
+ uv pip install -e . # do I need to modify this?
- name: Install pymatgen from master if triggered by pymatgen repo dispatch
if: github.event_name == 'repository_dispatch' && github.event.action == 'pymatgen-ci-trigger'
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index b9bb05469e..eef4f0c3b5 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -3,7 +3,7 @@ default_language_version:
exclude: ^(.github/|tests/test_data/abinit/)
repos:
- repo: https://github.com/charliermarsh/ruff-pre-commit
- rev: v0.7.3
+ rev: v0.8.0
hooks:
- id: ruff
args: [--fix]
@@ -45,7 +45,7 @@ repos:
args: [--ignore-words-list, 'titel,statics,ba,nd,te,atomate']
types_or: [python, rst, markdown]
- repo: https://github.com/kynan/nbstripout
- rev: 0.8.0
+ rev: 0.8.1
hooks:
- id: nbstripout
args:
diff --git a/docs/user/codes/index.md b/docs/user/codes/index.md
index fcb3b5f085..38bde51068 100644
--- a/docs/user/codes/index.md
+++ b/docs/user/codes/index.md
@@ -6,4 +6,5 @@ The section gives the instructions for codes supported by atomate2.
```{toctree}
vasp
+openmm
```
diff --git a/docs/user/codes/vasp.md b/docs/user/codes/vasp.md
index e0b63ab51e..d9788e1a98 100644
--- a/docs/user/codes/vasp.md
+++ b/docs/user/codes/vasp.md
@@ -1,4 +1,5 @@
(codes.vasp)=
+
# VASP
At present, most workflows in atomate2 use the Vienna *ab initio* simulation package
@@ -260,20 +261,48 @@ With the help of phonopy, these forces are then converted into a dynamical matri
The dynamical matrices of three structures are then used as an input to the phonopy Grueneisen api
to compute mode-dependent Grueneisen parameters.
-
### Quasi-harmonic Workflow
+
Uses the quasi-harmonic approximation with the help of Phonopy to compute thermodynamic properties.
First, a tight relaxation is performed. Subsequently, several optimizations at different constant
volumes are performed. At each of the volumes, an additional phonon run is performed as well.
Afterwards, equation of state fits are performed with phonopy.
-
-
### Equation of State Workflow
-An equation of state workflow is implemented. First, a tight relaxation is performed. Subsequently, several optimizations at different constant
+
+An equation of state (EOS) workflow is implemented. First, a tight relaxation is performed. Subsequently, several optimizations at different constant
volumes are performed. Additional static calculations might be performed afterwards to arrive at more
-accurate energies. Then, an equation of state fit is performed with pymatgen.
+accurate energies. Then, an EOS fit is performed with pymatgen.
+
+The output of the workflow is, by default, a dictionary containing the energy and volume data generated with DFT, in addition to fitted equation of state parameters for all models currently available in pymatgen (Murnaghan, Birch-Murnaghan, Poirier-Tarantola, and Vinet/UBER).
+
+#### Materials Project-compliant workflows
+
+If the user wishes to reproduce the EOS data currently in the Materials Project, they should use the atomate 1-compatible `MPLegacy`-prefixed flows (and jobs and input sets). For performing updated PBE-GGA EOS flows with Materials Project-compliant parameters, the user should use the `MPGGA`-prefixed classes. Lastly, the `MPMetaGGA`-prefixed classes allow the user to perform Materials Project-compliant r2SCAN EOS workflows.
+
+**Summary:** For Materials Project-compliant equation of state (EOS) workflows, the user should use:
+* `MPGGAEosMaker` for faster, lower-accuracy calculation with the PBE-GGA
+* `MPMetaGGAEosMaker` for higher-accuracy but slower calculations with the r2SCAN meta-GGA
+* `MPLegacyEosMaker` for consistency with the PBE-GGA data currently distributed by the Materials Project
+
+#### Implementation details
+
+The Materials Project-compliant EOS flows, jobs, and sets currently use three prefixes to indicate their usage.
+* `MPGGA`: MP-compatible PBE-GGA (current)
+* `MPMetaGGA`: MP-compatible r2SCAN meta-GGA (current)
+* `MPLegacy`: a reproduction of the atomate 1 implementation, described in
+ K. Latimer, S. Dwaraknath, K. Mathew, D. Winston, and K.A. Persson, npj Comput. Materials **vol. 4**, p. 40 (2018), DOI: 10.1038/s41524-018-0091-x
+
+ For reference, the original atomate workflows can be found here:
+ * [`atomate.vasp.workflows.base.wf_bulk_modulus`](https://github.com/hackingmaterials/atomate/blob/main/atomate/vasp/workflows/presets/core.py#L564)
+ * [`atomate.vasp.workflows.base.bulk_modulus.get_wf_bulk_modulus`](https://github.com/hackingmaterials/atomate/blob/main/atomate/vasp/workflows/base/bulk_modulus.py#L21)
+
+In the original atomate 1 workflow and the atomate2 `MPLegacyEosMaker`, the k-point density is **extremely** high. This is despite the convergence tests in the supplementary information
+of Latimer *et al.* not showing strong sensitivity when the "number of ***k***-points per reciprocal atom" (KPPRA) is at least 3,000.
+
+To make the `MPGGAEosMaker` and `MPMetaGGAEosMaker` more tractable for high-throughput jobs, their input sets (`MPGGAEos{Relax,Static}SetGenerator` and `MPMetaGGAEos{Relax,Static}SetGenerator` respectively) still use the highest ***k***-point density in standard Materials Project jobs, `KSPACING = 0.22` Å-1, which is comparable to KPPRA = 3,000.
+This choice is justified by Fig. S12 of the supplemantary information of Latimer *et al.*, which shows that all fitted EOS parameters (equilibrium energy $E_0$, equilibrium volume $V_0$, bulk modulus $B_0$, and bulk modulus pressure derivative $B_1$) do not deviate by more than 1.5%, and typically by less than 0.1%, from well-converged values when KPPRA = 3,000.
### LOBSTER
diff --git a/pyproject.toml b/pyproject.toml
index d2384e1689..8243d73542 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -43,6 +43,7 @@ amset = ["amset>=0.4.15", "pydash"]
cclib = ["cclib"]
mp = ["mp-api>=0.37.5"]
phonons = ["phonopy>=1.10.8", "seekpath>=2.0.0"]
+pheasy = ["hiphive==1.3.1"]
lobster = ["ijson>=3.2.2", "lobsterpy>=0.4.0"]
defects = [
"dscribe>=1.2.0",
@@ -51,7 +52,7 @@ defects = [
]
forcefields = [
"ase>=3.23.0",
- "calorine<=2.2.1",
+ "calorine>=3.0",
"chgnet>=0.2.2",
"mace-torch>=0.3.3",
"matgl>=1.1.3",
@@ -74,7 +75,7 @@ docs = [
"FireWorks==2.0.3",
"autodoc_pydantic==2.2.0",
"furo==2024.8.6",
- "ipython==8.29.0",
+ "ipython==8.30.0",
"jsonschema[format]",
"myst_parser==4.0.0",
"numpydoc==1.8.0",
@@ -89,7 +90,7 @@ tests = [
"pytest-cov==6.0.0",
"pytest-mock==3.14.0",
"pytest-split==0.10.0",
- "pytest==8.3.3",
+ "pytest==8.3.4",
]
strict = [
"PyYAML==6.0.2",
@@ -98,17 +99,17 @@ strict = [
"click==8.1.7",
"custodian==2024.10.16",
"dscribe==2.1.1",
- "emmet-core==0.84.3rc3",
+ "emmet-core==0.84.3rc6",
"ijson==3.3.0",
- "jobflow==0.1.18",
+ "jobflow==0.1.19",
"lobsterpy==0.4.9",
"mdanalysis==2.7.0",
"monty==2024.10.21",
- "mp-api==0.42.2",
+ "mp-api==0.43.0",
"numpy",
"openmm-mdanalysis-reporter==0.1.0",
"openmm==8.1.1",
- "phonopy==2.27.0",
+ "phonopy==2.30.1",
"pydantic-settings==2.6.1",
"pydantic==2.9.2",
"pymatgen-analysis-defects==2024.10.22",
@@ -117,6 +118,8 @@ strict = [
"seekpath==2.1.0",
"tblite==0.3.0; python_version < '3.12'",
"typing-extensions==4.12.2",
+ "hiphive==1.3.1",
+ "f90nml==1.4.4",
]
strict-forcefields = [
"calorine==3.0",
@@ -124,7 +127,7 @@ strict-forcefields = [
"mace-torch>=0.3.6",
"matgl==1.1.3",
"quippy-ase==0.9.14; python_version < '3.12'",
- "sevenn==0.10.1",
+ "sevenn==0.10.2",
"torch==2.5.1",
"torchdata==0.7.1", # TODO: remove when issue fixed
]
@@ -207,7 +210,6 @@ ignore = [
"PLR0913", # too many arguments
"PLR0915", # too many local statements
"PLR2004",
- "PT004", # pytest-missing-fixture-name-underscore
"PT006", # pytest-parametrize-names-wrong-type
"PT013", # pytest-incorrect-pytest-import
"PTH", # prefer Pathlib to os.path
diff --git a/src/atomate2/ase/schemas.py b/src/atomate2/ase/schemas.py
index 3bce7e443d..b9a426135a 100644
--- a/src/atomate2/ase/schemas.py
+++ b/src/atomate2/ase/schemas.py
@@ -99,7 +99,7 @@ class AseBaseModel(BaseModel):
)
molecule: Optional[Molecule] = Field(None, description="The molecule at this step.")
- def model_post_init(self, __context: Any) -> None:
+ def model_post_init(self, _context: Any) -> None:
"""Establish alias to structure and molecule fields."""
if self.structure is None and isinstance(self.mol_or_struct, Structure):
self.structure = self.mol_or_struct
diff --git a/src/atomate2/common/flows/anharmonicity.py b/src/atomate2/common/flows/anharmonicity.py
index 5d9067bc4f..e2d7534592 100644
--- a/src/atomate2/common/flows/anharmonicity.py
+++ b/src/atomate2/common/flows/anharmonicity.py
@@ -81,7 +81,7 @@ def make(
A previous calculation directory to use for copying outputs.
Default is None.
born: Optional[list[Matrix3D]]
- Instead of recomputing born charges and epsilon, these values can also be
+ Instead of recomputing Born charges and epsilon, these values can also be
provided manually. If born and epsilon_static are provided, the born run
will be skipped it can be provided in the VASP convention with information
for every atom in unit cell. Please be careful when converting structures
diff --git a/src/atomate2/common/flows/gruneisen.py b/src/atomate2/common/flows/gruneisen.py
index 700aa0b793..0977f9ebc6 100644
--- a/src/atomate2/common/flows/gruneisen.py
+++ b/src/atomate2/common/flows/gruneisen.py
@@ -40,7 +40,7 @@ class BaseGruneisenMaker(Maker, ABC):
generated for all the three structures (ground state, expanded and shrunk volume)
and accurate forces are computed for these structures. With the help of phonopy,
these forces are then converted into a dynamical matrix. This dynamical matrix of
- three structures is then used as an input for the phonopy Grueneisen api
+ three structures is then used as an input for the phonopy Grueneisen API
to compute Grueneisen parameters.
diff --git a/src/atomate2/common/flows/pheasy.py b/src/atomate2/common/flows/pheasy.py
new file mode 100644
index 0000000000..4ec933b022
--- /dev/null
+++ b/src/atomate2/common/flows/pheasy.py
@@ -0,0 +1,442 @@
+"""Flow for calculating (an)harmonic FCs and phonon energy renorma. with pheasy."""
+
+from __future__ import annotations
+
+from abc import ABC, abstractmethod
+from dataclasses import dataclass, field
+from typing import TYPE_CHECKING
+
+from jobflow import Flow, Maker
+
+from atomate2.common.jobs.pheasy import (
+ generate_frequencies_eigenvectors,
+ generate_phonon_displacements,
+ run_phonon_displacements,
+)
+from atomate2.common.jobs.phonons import get_supercell_size, get_total_energy_per_cell
+from atomate2.common.jobs.utils import structure_to_conventional, structure_to_primitive
+
+if TYPE_CHECKING:
+ from pathlib import Path
+
+ from emmet.core.math import Matrix3D
+ from pymatgen.core.structure import Structure
+
+ from atomate2.aims.jobs.base import BaseAimsMaker
+ from atomate2.forcefields.jobs import ForceFieldRelaxMaker, ForceFieldStaticMaker
+ from atomate2.vasp.jobs.base import BaseVaspMaker
+
+SUPPORTED_CODES = frozenset(("vasp", "aims", "forcefields"))
+
+
+@dataclass
+class BasePhononMaker(Maker, ABC):
+ """Maker to calculate harmonic phonons with LASSO-based ML code Pheasy.
+
+ Calculate the zero-K harmonic phonons of a material and higher-order FCs.
+ Initially, a tight structural relaxation is performed to obtain a structure
+ without forces on the atoms. Subsequently, supercells with all atoms displaced
+ by a small amplitude (generally using 0.01 A) are generated and accurate forces
+ are computed for these structures for the second order force constants. With the
+ help of pheasy (LASSO technique), these forces are then converted into a dynamical
+ matrix. In this Workflow, we separate the harmonic phonon calculations and
+ anharmonic force constants calculations. To correct for polarization effects, a
+ correction of the dynamical matrix based on BORN charges can be performed. Finally,
+ phonon densities of states, phonon band structures and thermodynamic properties
+ are computed. For the anharmonic force constants, the supercells with all atoms
+ displaced by a larger amplitude (generally using 0.08 A) are generated and accurate
+ forces are computed for these structures. With the help of pheasy (LASSO technique),
+ the third- and fourth-order force constants are extracted at once.
+
+ .. Note::
+ It is heavily recommended to symmetrize the structure before passing it to
+ this flow. Otherwise, a different space group might be detected and too many
+ displacement calculations will be required for pheasy phonon calculation. It
+ is recommended to check the convergence parameters here and adjust them if
+ necessary. The default might not be strict enough for your specific case.
+ Additionally, for high-throughoput calculations, it is recommended to calculate
+ the residual forces on the atoms in the supercell after the relaxation. Then the
+ forces on displaced supercells can deduct the residual forces to reduce the
+ error in the dynamical matrix.
+
+ Parameters
+ ----------
+ name : str
+ Name of the flow produced by this maker.
+ sym_reduce : bool
+ Whether to reduce the number of deformations using symmetry.
+ symprec : float
+ Symmetry precision to use in the
+ reduction of symmetry to find the primitive/conventional cell
+ (use_primitive_standard_structure, use_conventional_standard_structure)
+ and to handle all symmetry-related tasks in pheasy, we recommend to
+ use the value of 1e-3.
+ displacement: float
+ displacement distance for phonons, for most cases 0.01 A is a good choice,
+ but it can be increased to 0.02 A for heavier elements.
+ num_displaced_supercells: int
+ number of displacements to be generated using a random-displacement approach
+ for harmonic phonon calculations. The default value is 0 and the number of
+ displacements is automatically determined by the number of atoms in the
+ supercell and its space group.
+ cal_anhar_fcs: bool
+ if set to True, anharmonic force constants(FCs) up to fourth-order FCs will
+ be calculated. The default value is False, and only harmonic phonons will
+ be calculated.
+ displacement_anhar: float
+ displacement distance for anharmonic force constants(FCs) up to fourth-order
+ FCs, for most cases 0.08 A is a good choice, but it can be increased to 0.1 A.
+ num_disp_anhar: int
+ number of displacements to be generated using a random-displacement approach
+ for anharmonic phonon calculations. The default value is 0 and the number of
+ displacements is automatically determined by the number of atoms in the
+ supercell, cutoff distance for anharmonic FCs its space group. generally,
+ 50 large-distance displacements are enough for most cases.
+ fcs_cutoff_radius: list
+ cutoff distance for anharmonic force constants(FCs) up to fourth-order FCs.
+ The default value is [-1, 12, 10], which means that the cutoff distance for
+ second-order FCs is the Wigner-Seitz cell boundary and the cutoff distance
+ for third-order FCs is 12 Borh, and the cutoff distance for fourth-order FCs
+ is 10 Bohr. Generally, the default value is good enough.
+ min_length: float
+ minimum length of lattice constants will be used to create the supercell,
+ the default value is 14.0 A. In most cases, the default value is good
+ enough, but it can be increased for larger supercells.
+ prefer_90_degrees: bool
+ if set to True, supercell algorithm will first try to find a supercell
+ with 3 90 degree angles.
+ get_supercell_size_kwargs: dict
+ kwargs that will be passed to get_supercell_size to determine supercell size
+ use_symmetrized_structure: str
+ allowed strings: "primitive", "conventional", None
+
+ - "primitive" will enforce to start the phonon computation
+ from the primitive standard structure
+ according to Setyawan, W., & Curtarolo, S. (2010).
+ High-throughput electronic band structure calculations:
+ Challenges and tools. Computational Materials Science,
+ 49(2), 299-312. doi:10.1016/j.commatsci.2010.05.010.
+ This makes it possible to use certain k-path definitions
+ with this workflow. Otherwise, we must rely on seekpath
+ - "conventional" will enforce to start the phonon computation
+ from the conventional standard structure
+ according to Setyawan, W., & Curtarolo, S. (2010).
+ High-throughput electronic band structure calculations:
+ Challenges and tools. Computational Materials Science,
+ 49(2), 299-312. doi:10.1016/j.commatsci.2010.05.010.
+ We will however use seekpath and primitive structures
+ as determined by from phonopy to compute the phonon band structure
+ bulk_relax_maker: .ForceFieldRelaxMaker, .BaseAimsMaker, .BaseVaspMaker, or None
+ A maker to perform a tight relaxation on the bulk.
+ Set to ``None`` to skip the
+ bulk relaxation
+ static_energy_maker: .ForceFieldRelaxMaker, .BaseAimsMaker, .BaseVaspMaker, or None
+ A maker to perform the computation of the DFT energy on the bulk.
+ Set to ``None`` to skip the
+ static energy computation
+ born_maker: .ForceFieldStaticMaker, .BaseAsimsMaker, .BaseVaspMaker, or None
+ Maker to compute the BORN charges.
+ phonon_displacement_maker: .ForceFieldStaticMaker, .BaseAimsMaker, .BaseVaspMaker
+ Maker used to compute the forces for a supercell.
+ generate_frequencies_eigenvectors_kwargs : dict
+ Keyword arguments passed to :obj:`generate_frequencies_eigenvectors`.
+ create_thermal_displacements: bool
+ Bool that determines if thermal_displacement_matrices are computed
+ kpath_scheme: str
+ scheme to generate kpoints. Please be aware that
+ you can only use seekpath with any kind of cell
+ Otherwise, please use the standard primitive structure
+ Available schemes are:
+ "seekpath", "hinuma", "setyawan_curtarolo", "latimer_munro".
+ "seekpath" and "hinuma" are the same definition but
+ seekpath can be used with any kind of unit cell as
+ it relies on phonopy to handle the relationship
+ to the primitive cell and not pymatgen
+ code: str
+ determines the dft or force field code.
+ mp_id: str
+ The mp_id of the material in the Materials Project database.
+ store_force_constants: bool
+ if True, force constants will be stored
+ socket: bool
+ If True, use the socket for the calculation
+
+ """
+
+ name: str = "phonon"
+ sym_reduce: bool = True
+ symprec: float = 1e-3
+ displacement: float = 0.01
+ num_displaced_supercells: int = 0
+ cal_anhar_fcs: bool = False
+ displacement_anhar: float = 0.08
+ num_disp_anhar: int = 0
+ fcs_cutoff_radius: list = field(
+ default_factory=lambda: [-1, 12, 10]
+ ) # units in Bohr
+ renorm_phonon: bool = False
+ renorm_temp: list = field(default_factory=lambda: [100, 700, 100])
+ cal_ther_cond: bool = False
+ ther_cond_mesh: list = field(default_factory=lambda: [20, 20, 20])
+ ther_cond_temp: list = field(default_factory=lambda: [100, 700, 100])
+ min_length: float | None = 12.0
+ max_length: float | None = 16.0
+ prefer_90_degrees: bool = True
+ get_supercell_size_kwargs: dict = field(default_factory=dict)
+ use_symmetrized_structure: str | None = None
+ bulk_relax_maker: ForceFieldRelaxMaker | BaseVaspMaker | BaseAimsMaker | None = None
+ static_energy_maker: ForceFieldRelaxMaker | BaseVaspMaker | BaseAimsMaker | None = (
+ None
+ )
+ born_maker: ForceFieldStaticMaker | BaseVaspMaker | None = None
+ phonon_displacement_maker: ForceFieldStaticMaker | BaseVaspMaker | BaseAimsMaker = (
+ None
+ )
+ create_thermal_displacements: bool = False
+ generate_frequencies_eigenvectors_kwargs: dict = field(default_factory=dict)
+ kpath_scheme: str = "seekpath"
+ code: str = None
+ mp_id: str = None
+ store_force_constants: bool = True
+ socket: bool = False
+
+ def make(
+ self,
+ structure: Structure,
+ prev_dir: str | Path | None = None,
+ born: list[Matrix3D] | None = None,
+ epsilon_static: Matrix3D | None = None,
+ total_dft_energy_per_formula_unit: float | None = None,
+ supercell_matrix: Matrix3D | None = None,
+ ) -> Flow:
+ """Make flow to calculate the phonon properties.
+
+ Parameters
+ ----------
+ structure : Structure
+ A pymatgen structure object. Please start with a structure
+ that is nearly fully optimized as the internal optimizers
+ have very strict settings!
+ prev_dir : str or Path or None
+ A previous calculation directory to use for copying outputs.
+ born: Matrix3D
+ Instead of recomputing born charges and epsilon, these values can also be
+ provided manually. If born and epsilon_static are provided, the born run
+ will be skipped it can be provided in the VASP convention with information
+ for every atom in unit cell. Please be careful when converting structures
+ within in this workflow as this could lead to errors
+ epsilon_static: Matrix3D
+ The high-frequency dielectric constant to use instead of recomputing born
+ charges and epsilon. If born, epsilon_static are provided, the born run
+ will be skipped
+ total_dft_energy_per_formula_unit: float
+ It has to be given per formula unit (as a result in corresponding Doc).
+ Instead of recomputing the energy of the bulk structure every time, this
+ value can also be provided in eV. If it is provided, the static run will be
+ skipped. This energy is the typical output dft energy of the dft workflow.
+ No conversion needed.
+ supercell_matrix: list
+ Instead of min_length, also a supercell_matrix can be given, e.g.
+ [[1.0,0.0,0.0],[0.0,1.0,0.0],[0.0,0.0,1.0]
+ """
+ use_symmetrized_structure = self.use_symmetrized_structure
+ kpath_scheme = self.kpath_scheme
+ valid_structs = (None, "primitive", "conventional")
+ if use_symmetrized_structure not in valid_structs:
+ raise ValueError(
+ f"Invalid {use_symmetrized_structure=}, use one of {valid_structs}"
+ )
+
+ if use_symmetrized_structure != "primitive" and kpath_scheme != "seekpath":
+ raise ValueError(
+ f"You can't use {kpath_scheme=} with the primitive standard "
+ "structure, please use seekpath"
+ )
+
+ valid_schemes = ("seekpath", "hinuma", "setyawan_curtarolo", "latimer_munro")
+ if kpath_scheme not in valid_schemes:
+ raise ValueError(
+ f"{kpath_scheme=} is not implemented, use one of {valid_schemes}"
+ )
+
+ if self.code is None or self.code not in SUPPORTED_CODES:
+ raise ValueError(
+ "The code variable must be passed and it must be a supported code."
+ f" Supported codes are: {SUPPORTED_CODES}"
+ )
+
+ jobs = []
+
+ # TODO: should this be after or before structural optimization as the
+ # optimization could change the symmetry we could add a tutorial and point out
+ # that the structure should be nearly optimized before the phonon workflow
+ if self.use_symmetrized_structure == "primitive":
+ # These structures are compatible with many
+ # of the kpath algorithms that are used for Materials Project
+ prim_job = structure_to_primitive(structure, self.symprec)
+ jobs.append(prim_job)
+ structure = prim_job.output
+ elif self.use_symmetrized_structure == "conventional":
+ # it could be beneficial to use conventional standard structures to arrive
+ # faster at supercells with right angles
+ conv_job = structure_to_conventional(structure, self.symprec)
+ jobs.append(conv_job)
+ structure = conv_job.output
+
+ optimization_run_job_dir = None
+ optimization_run_uuid = None
+
+ if self.bulk_relax_maker is not None:
+ # optionally relax the structure
+ bulk_kwargs = {}
+ if self.prev_calc_dir_argname is not None:
+ bulk_kwargs[self.prev_calc_dir_argname] = prev_dir
+ bulk = self.bulk_relax_maker.make(structure, **bulk_kwargs)
+ jobs.append(bulk)
+ structure = bulk.output.structure
+ prev_dir = bulk.output.dir_name
+ optimization_run_job_dir = bulk.output.dir_name
+ optimization_run_uuid = bulk.output.uuid
+
+ # if supercell_matrix is None, supercell size will be determined after relax
+ # maker to ensure that cell lengths are really larger than threshold.
+ # Note that If one wants to calculate the lattice thermal conductivity,
+ # the supercell dimensions should be forced to be diagonal, e.g.
+ # supercell_matrix = [[2, 0, 0], [0, 2, 0], [0, 0, 2]]
+ if supercell_matrix is None:
+ supercell_job = get_supercell_size(
+ structure,
+ self.min_length,
+ self.max_length,
+ self.prefer_90_degrees,
+ **self.get_supercell_size_kwargs,
+ )
+ jobs.append(supercell_job)
+ supercell_matrix = supercell_job.output
+
+ # Computation of static energy
+ total_dft_energy = None
+ static_run_job_dir = None
+ static_run_uuid = None
+ if (self.static_energy_maker is not None) and (
+ total_dft_energy_per_formula_unit is None
+ ):
+ static_job_kwargs = {}
+ if self.prev_calc_dir_argname is not None:
+ static_job_kwargs[self.prev_calc_dir_argname] = prev_dir
+ static_job = self.static_energy_maker.make(
+ structure=structure, **static_job_kwargs
+ )
+ jobs.append(static_job)
+ total_dft_energy = static_job.output.output.energy
+ static_run_job_dir = static_job.output.dir_name
+ static_run_uuid = static_job.output.uuid
+ prev_dir = static_job.output.dir_name
+ elif total_dft_energy_per_formula_unit is not None:
+ # to make sure that one can reuse results from Doc
+ compute_total_energy_job = get_total_energy_per_cell(
+ total_dft_energy_per_formula_unit, structure
+ )
+ jobs.append(compute_total_energy_job)
+ total_dft_energy = compute_total_energy_job.output
+
+ # get a phonon object from pheasy code using the random-displacement approach
+ displacements = generate_phonon_displacements(
+ structure=structure,
+ supercell_matrix=supercell_matrix,
+ displacement=self.displacement,
+ num_displaced_supercells=self.num_displaced_supercells,
+ cal_anhar_fcs=self.cal_anhar_fcs,
+ displacement_anhar=self.displacement_anhar,
+ num_disp_anhar=self.num_disp_anhar,
+ fcs_cutoff_radius=self.fcs_cutoff_radius,
+ sym_reduce=self.sym_reduce,
+ symprec=self.symprec,
+ use_symmetrized_structure=self.use_symmetrized_structure,
+ kpath_scheme=self.kpath_scheme,
+ code=self.code,
+ )
+ jobs.append(displacements)
+
+ # perform the phonon displacement calculations
+ displacement_calcs = run_phonon_displacements(
+ displacements=displacements.output,
+ structure=structure,
+ supercell_matrix=supercell_matrix,
+ phonon_maker=self.phonon_displacement_maker,
+ socket=self.socket,
+ prev_dir_argname=self.prev_calc_dir_argname,
+ prev_dir=prev_dir,
+ )
+ jobs.append(displacement_calcs)
+
+ # Computation of BORN charges
+ born_run_job_dir = None
+ born_run_uuid = None
+ if self.born_maker is not None and (born is None or epsilon_static is None):
+ born_kwargs = {}
+ if self.prev_calc_dir_argname is not None:
+ born_kwargs[self.prev_calc_dir_argname] = prev_dir
+ born_job = self.born_maker.make(structure, **born_kwargs)
+ jobs.append(born_job)
+
+ # I am not happy how we currently access "born" charges
+ # This is very vasp specific code aims and forcefields
+ # do not support this at the moment, if this changes we have
+ # to update this section
+ epsilon_static = born_job.output.calcs_reversed[0].output.epsilon_static
+ born = born_job.output.calcs_reversed[0].output.outcar["born"]
+ born_run_job_dir = born_job.output.dir_name
+ born_run_uuid = born_job.output.uuid
+
+ phonon_collect = generate_frequencies_eigenvectors(
+ supercell_matrix=supercell_matrix,
+ displacement=self.displacement,
+ num_displaced_supercells=self.num_displaced_supercells,
+ cal_anhar_fcs=self.cal_anhar_fcs,
+ displacement_anhar=self.displacement_anhar,
+ num_disp_anhar=self.num_disp_anhar,
+ fcs_cutoff_radius=self.fcs_cutoff_radius,
+ renorm_phonon=self.renorm_phonon,
+ renorm_temp=self.renorm_temp,
+ cal_ther_cond=self.cal_ther_cond,
+ ther_cond_mesh=self.ther_cond_mesh,
+ ther_cond_temp=self.ther_cond_temp,
+ sym_reduce=self.sym_reduce,
+ symprec=self.symprec,
+ use_symmetrized_structure=self.use_symmetrized_structure,
+ kpath_scheme=self.kpath_scheme,
+ code=self.code,
+ mp_id=self.mp_id,
+ structure=structure,
+ displacement_data=displacement_calcs.output,
+ epsilon_static=epsilon_static,
+ born=born,
+ total_dft_energy=total_dft_energy,
+ static_run_job_dir=static_run_job_dir,
+ static_run_uuid=static_run_uuid,
+ born_run_job_dir=born_run_job_dir,
+ born_run_uuid=born_run_uuid,
+ optimization_run_job_dir=optimization_run_job_dir,
+ optimization_run_uuid=optimization_run_uuid,
+ create_thermal_displacements=self.create_thermal_displacements,
+ store_force_constants=self.store_force_constants,
+ **self.generate_frequencies_eigenvectors_kwargs,
+ )
+
+ jobs.append(phonon_collect)
+
+ # create a flow including all jobs for a phonon computation
+ return Flow(jobs, phonon_collect.output)
+
+ @property
+ @abstractmethod
+ def prev_calc_dir_argname(self) -> str | None:
+ """Name of argument informing static maker of previous calculation directory.
+
+ As this differs between different DFT codes (e.g., VASP, CP2K), it
+ has been left as a property to be implemented by the inheriting class.
+
+ Note: this is only applicable if a relax_maker is specified; i.e., two
+ calculations are performed for each ordering (relax -> static)
+ """
diff --git a/src/atomate2/common/flows/phonons.py b/src/atomate2/common/flows/phonons.py
index 2c3e57778c..b32b3674c6 100644
--- a/src/atomate2/common/flows/phonons.py
+++ b/src/atomate2/common/flows/phonons.py
@@ -69,6 +69,8 @@ class BasePhononMaker(Maker, ABC):
displacement distance for phonons
min_length: float
min length of the supercell that will be built
+ max_length: float
+ max length of the supercell that will be built
prefer_90_degrees: bool
if set to True, supercell algorithm will first try to find a supercell
with 3 90 degree angles
@@ -162,6 +164,7 @@ class BasePhononMaker(Maker, ABC):
kpath_scheme: str = "seekpath"
code: str = None
+ mp_id: str = None
store_force_constants: bool = True
socket: bool = False
@@ -185,7 +188,7 @@ def make(
prev_dir : str or Path or None
A previous calculation directory to use for copying outputs.
born: Matrix3D
- Instead of recomputing born charges and epsilon, these values can also be
+ Instead of recomputing Born charges and epsilon, these values can also be
provided manually. If born and epsilon_static are provided, the born run
will be skipped it can be provided in the VASP convention with information
for every atom in unit cell. Please be careful when converting structures
diff --git a/src/atomate2/common/flows/qha.py b/src/atomate2/common/flows/qha.py
index 512404d0bd..74c33209c3 100644
--- a/src/atomate2/common/flows/qha.py
+++ b/src/atomate2/common/flows/qha.py
@@ -10,11 +10,16 @@
from jobflow import Flow, Maker
from atomate2.common.flows.eos import CommonEosMaker
-from atomate2.common.jobs.qha import analyze_free_energy, get_phonon_jobs
+from atomate2.common.jobs.qha import (
+ analyze_free_energy,
+ get_phonon_jobs,
+ get_supercell_size,
+)
if TYPE_CHECKING:
from pathlib import Path
+ from emmet.core.math import Matrix3D
from pymatgen.core import Structure
from atomate2.common.flows.phonons import BasePhononMaker
@@ -26,23 +31,18 @@
@dataclass
class CommonQhaMaker(Maker, ABC):
- """
- Use the quasi-harmonic approximation.
+ """Use the quasi-harmonic approximation.
- First relax a structure.
- Then we scale the relaxed structure, and
- then compute harmonic phonons for each scaled
- structure with Phonopy.
- Finally, we compute the Gibb's free energy and
- other thermodynamic properties available from
- the quasi-harmonic approximation.
+ First relax a structure. Then we scale the relaxed structure, and then compute
+ harmonic phonons for each scaled structure with Phonopy. Finally, we compute the
+ Gibbs free energy and other thermodynamic properties available from the
+ quasi-harmonic approximation.
Note: We do not consider electronic free energies so far.
This might be problematic for metals (see e.g.,
Wolverton and Zunger, Phys. Rev. B, 52, 8813 (1994).)
- Note: Magnetic Materials have never been computed with
- this workflow.
+ Note: Magnetic Materials have never been computed with this workflow.
Parameters
----------
@@ -71,6 +71,15 @@ class CommonQhaMaker(Maker, ABC):
will be ignored
eos_type: str
Equation of State type used for the fitting. Defaults to vinet.
+ min_length: float
+ min length of the supercell that will be built
+ max_length: float
+ max length of the supercell that will be built
+ prefer_90_degrees: bool
+ if set to True, supercell algorithm will first try to find a supercell
+ with 3 90 degree angles
+ get_supercell_size_kwargs: dict
+ kwargs that will be passed to get_supercell_size to determine supercell size
"""
name: str = "QHA Maker"
@@ -85,16 +94,27 @@ class CommonQhaMaker(Maker, ABC):
skip_analysis: bool = False
eos_type: Literal["vinet", "birch_murnaghan", "murnaghan"] = "vinet"
analyze_free_energy_kwargs: dict = field(default_factory=dict)
- # TODO: implement advanced handling of
- # imaginary modes in phonon runs (i.e., fitting procedures)
-
- def make(self, structure: Structure, prev_dir: str | Path = None) -> Flow:
+ min_length: float | None = 20.0
+ max_length: float | None = None
+ prefer_90_degrees: bool = True
+ allow_orthorhombic: bool = False
+ get_supercell_size_kwargs: dict = field(default_factory=dict)
+
+ def make(
+ self,
+ structure: Structure,
+ supercell_matrix: Matrix3D | None = None,
+ prev_dir: str | Path = None,
+ ) -> Flow:
"""Run an EOS flow.
Parameters
----------
structure : Structure
A pymatgen structure object.
+ supercell_matrix: list
+ Instead of min_length, also a supercell_matrix can be given, e.g.
+ [[1.0,0.0,0.0],[0.0,1.0,0.0],[0.0,0.0,1.0]
prev_dir : str or Path or None
A previous calculation directory to copy output files from.
@@ -103,10 +123,7 @@ def make(self, structure: Structure, prev_dir: str | Path = None) -> Flow:
.Flow, a QHA flow
"""
if self.eos_type not in supported_eos:
- raise ValueError(
- "EOS not supported.",
- "Please choose 'vinet', 'birch_murnaghan', 'murnaghan'",
- )
+ raise ValueError(f"EOS not supported. Choose one of {set(supported_eos)}")
qha_jobs = []
@@ -116,14 +133,31 @@ def make(self, structure: Structure, prev_dir: str | Path = None) -> Flow:
eos_relax_maker=self.eos_relax_maker,
static_maker=None,
postprocessor=None,
+ linear_strain=self.linear_strain,
number_of_frames=self.number_of_frames,
)
eos_job = self.eos.make(structure)
qha_jobs.append(eos_job)
+ # implement a supercell job to get matrix for just the equilibrium structure
+ if supercell_matrix is None:
+ supercell = get_supercell_size(
+ eos_output=eos_job.output,
+ min_length=self.min_length,
+ max_length=self.max_length,
+ prefer_90_degrees=self.prefer_90_degrees,
+ allow_orthorhombic=self.allow_orthorhombic,
+ **self.get_supercell_size_kwargs,
+ )
+ qha_jobs.append(supercell)
+ supercell_matrix = supercell.output
+
+ # pass the matrix to the phonon_jobs, allow to set a consistent matrix instead
phonon_jobs = get_phonon_jobs(
- phonon_maker=self.phonon_maker, eos_output=eos_job.output
+ phonon_maker=self.phonon_maker,
+ eos_output=eos_job.output,
+ supercell_matrix=supercell_matrix,
)
qha_jobs.append(phonon_jobs)
if not self.skip_analysis:
diff --git a/src/atomate2/common/jobs/anharmonicity.py b/src/atomate2/common/jobs/anharmonicity.py
index e44aa7f863..5ebace0e9e 100644
--- a/src/atomate2/common/jobs/anharmonicity.py
+++ b/src/atomate2/common/jobs/anharmonicity.py
@@ -44,15 +44,16 @@ class ImaginaryModeError(Exception):
def __init__(self, largest_mode: float) -> None:
self.largest_mode = largest_mode
- self.message = f"""Structure has imaginary modes: the largest optical
- eigenmode {largest_mode} < 0.0001"""
+ self.message = (
+ f"Structure has imaginary modes: the largest optical eigenmode "
+ f"{largest_mode} < 0.0001"
+ )
super().__init__(self.message)
@job
def get_phonon_supercell(
- structure: Structure,
- supercell_matrix: np.ndarray,
+ structure: Structure, supercell_matrix: np.ndarray
) -> Structure:
"""Get the phonon supercell of a structure.
@@ -68,12 +69,9 @@ def get_phonon_supercell(
Structure
The phonopy structure
"""
- cell = get_phonopy_structure(structure)
- phonon = Phonopy(
- cell,
- supercell_matrix,
- )
- return get_pmg_structure(phonon.supercell)
+ unit_cell = get_phonopy_structure(structure)
+ phonopy = Phonopy(unit_cell, supercell_matrix)
+ return get_pmg_structure(phonopy.supercell)
def get_sigma_per_site(
diff --git a/src/atomate2/common/jobs/electrode.py b/src/atomate2/common/jobs/electrode.py
index 407632ae7a..3e697e9615 100644
--- a/src/atomate2/common/jobs/electrode.py
+++ b/src/atomate2/common/jobs/electrode.py
@@ -250,17 +250,17 @@ def get_relaxed_job_summaries(
"""
relax_jobs = []
outputs = []
- for ii, structure in enumerate(structures):
+ for idx, structure in enumerate(structures):
job_ = relax_maker.make(structure=structure)
relax_jobs.append(job_)
- job_.append_name(f" {append_name} ({ii})")
- d_ = {
- "structure": job_.output.structure,
- "entry": job_.output.entry,
- "dir_name": job_.output.dir_name,
- "uuid": job_.output.uuid,
- }
- outputs.append(RelaxJobSummary(**d_))
+ job_.append_name(f" {append_name} ({idx})")
+ job_summary = RelaxJobSummary(
+ structure=job_.output.structure,
+ entry=job_.output.entry,
+ dir_name=job_.output.dir_name,
+ uuid=job_.output.uuid,
+ )
+ outputs.append(job_summary)
replace_flow = Flow(relax_jobs, output=outputs)
return Response(replace=replace_flow, output=outputs)
diff --git a/src/atomate2/common/jobs/gruneisen.py b/src/atomate2/common/jobs/gruneisen.py
index 8e1fa14c2e..d78fceb636 100644
--- a/src/atomate2/common/jobs/gruneisen.py
+++ b/src/atomate2/common/jobs/gruneisen.py
@@ -17,6 +17,8 @@
from atomate2.common.schemas.phonons import PhononBSDOSDoc
if TYPE_CHECKING:
+ from pathlib import Path
+
from pymatgen.core.structure import Structure
from atomate2.common.flows.phonons import BasePhononMaker
@@ -128,11 +130,11 @@ def run_phonon_jobs(
)
def compute_gruneisen_param(
code: str,
- phonopy_yaml_paths_dict: dict,
- phonon_imaginary_modes_info: dict,
+ phonopy_yaml_paths_dict: dict[str, Path],
+ phonon_imaginary_modes_info: dict[str, bool],
kpath_scheme: str,
symprec: float,
- mesh: tuple | float = (20, 20, 20),
+ mesh: tuple[int, int, int] | float = (20, 20, 20),
structure: Structure = None,
**compute_gruneisen_param_kwargs,
) -> GruneisenParameterDocument:
diff --git a/src/atomate2/common/jobs/pheasy.py b/src/atomate2/common/jobs/pheasy.py
new file mode 100644
index 0000000000..3ccbbfc3b9
--- /dev/null
+++ b/src/atomate2/common/jobs/pheasy.py
@@ -0,0 +1,431 @@
+"""Jobs for running phonon calculations."""
+
+from __future__ import annotations
+
+import contextlib
+import logging
+import warnings
+from typing import TYPE_CHECKING
+
+import numpy as np
+from jobflow import Flow, Response, job
+from phonopy import Phonopy
+from pymatgen.core import Structure
+from pymatgen.io.phonopy import get_phonopy_structure, get_pmg_structure
+from pymatgen.phonon.bandstructure import PhononBandStructureSymmLine
+from pymatgen.phonon.dos import PhononDos
+
+from atomate2.common.schemas.phonons import get_factor
+
+if TYPE_CHECKING:
+ from pathlib import Path
+
+ from emmet.core.math import Matrix3D
+
+ from atomate2.aims.jobs.base import BaseAimsMaker
+ from atomate2.forcefields.jobs import ForceFieldStaticMaker
+ from atomate2.vasp.jobs.base import BaseVaspMaker
+
+# move to here to avoid circular import
+from atomate2.common.schemas.pheasy import Forceconstants, PhononBSDOSDoc
+
+logger = logging.getLogger(__name__)
+
+
+@job(data=[Structure])
+def generate_phonon_displacements(
+ structure: Structure,
+ supercell_matrix: np.array,
+ displacement: float,
+ num_displaced_supercells: int,
+ cal_anhar_fcs: bool,
+ displacement_anhar: float,
+ # num_disp_anhar: int,
+ # fcs_cutoff_radius: list[int],
+ sym_reduce: bool,
+ symprec: float,
+ use_symmetrized_structure: str | None,
+ kpath_scheme: str,
+ code: str,
+) -> list[Structure]:
+ """
+
+ Generate small-distance perturbed structures with phonopy based on two ways.
+
+ (we will directly use the pheasy to generate the supercell in the near future)
+ 1. finite-displacment method (one displaced atom) when the displacement number
+ is less than 3. 2. random-displacement method (all-displaced atoms) when the
+ displacement number is more than 3.
+
+ Parameters
+ ----------
+ structure: Structure object
+ Fully optimized input structure for phonon run
+ supercell_matrix: np.array
+ array to describe supercell matrix
+ displacement: float
+ displacement in Angstrom (default: 0.01)
+ num_displaced_supercells: int
+ number of displaced supercells defined by users
+ sym_reduce: bool
+ if True, symmetry will be used to generate displacements
+ symprec: float
+ precision to determine symmetry
+ use_symmetrized_structure: str or None
+ primitive, conventional or None
+ kpath_scheme: str
+ scheme to generate kpath
+ code:
+ code to perform the computations
+
+ """
+ warnings.warn(
+ "Initial magnetic moments will not be considered for the determination "
+ "of the symmetry of the structure and thus will be removed now.",
+ stacklevel=1,
+ )
+ cell = get_phonopy_structure(
+ structure.remove_site_property(property_name="magmom")
+ if "magmom" in structure.site_properties
+ else structure
+ )
+ factor = get_factor(code)
+
+ # a bit of code repetition here as I currently
+ # do not see how to pass the phonopy object?
+ if use_symmetrized_structure == "primitive" and kpath_scheme == "seekpath":
+ primitive_matrix: np.ndarray | str = np.eye(3)
+ else:
+ primitive_matrix = "auto"
+
+ # TARP: THIS IS BAD! Including for discussions sake
+ if cell.magnetic_moments is not None and primitive_matrix == "auto":
+ if np.any(cell.magnetic_moments != 0.0):
+ raise ValueError(
+ "For materials with magnetic moments, "
+ "use_symmetrized_structure must be 'primitive'"
+ )
+ cell.magnetic_moments = None
+ # create the phonopy object to get some information
+ # for the displacement generation in ALM code.
+ phonon = Phonopy(
+ cell,
+ supercell_matrix,
+ primitive_matrix=primitive_matrix,
+ factor=factor,
+ symprec=symprec,
+ is_symmetry=sym_reduce,
+ )
+
+ # 1. the ALM module is used to determine how many free parameters
+ # (irreducible force constants) of second order force constants (FCs)
+ # within the supercell.
+ # 2. Based on the number of free parameters, we can determine how many
+ # displaced supercells we need to use to extract the second order force
+ # constants. Generally, the number of free parameters should be less than
+ # 3 * natom(supercell) * num_displaced_supercells. However, the full rank
+ # of matrix can not always guarantee the accurate result sometimes, you
+ # may need to displace more random configurations. At least use one or
+ # two more configurations based on the suggested number of displacements.
+
+ try:
+ from alm import ALM
+ except ImportError:
+ logging.exception(
+ "Error importing ALM. Please ensure the 'alm'" "library is installed."
+ )
+
+ supercell_ph = phonon.supercell
+ lattice = supercell_ph.cell
+ positions = supercell_ph.scaled_positions
+ numbers = supercell_ph.numbers
+ natom = len(numbers)
+
+ # get the number of free parameters of 2ND FCs from ALM, labeled as n_fp
+ with ALM(lattice, positions, numbers) as alm:
+ alm.define(1)
+ alm.suggest()
+ n_fp = alm._get_number_of_irred_fc_elements(1) # noqa: SLF001
+
+ # get the number of displaced supercells based on the number of free parameters
+ num = int(np.ceil(n_fp / (3.0 * natom)))
+
+ # get the number of displaced supercells from phonopy to compared with the number
+ # of 3, if the number of displaced supercells is less than 3, we will use the finite
+ # displacement method to generate the supercells. Otherwise, we will use the random
+ # displacement method to generate the supercells.
+ phonon.generate_displacements(distance=displacement)
+ num_disp_f = len(phonon.displacements)
+ if num_disp_f > 3:
+ num_d = int(np.ceil(num * 1.8)) + 1
+ else:
+ pass
+
+ logger.info(
+ "The number of free parameters of Second Order Force Constants is %s", n_fp
+ )
+ logger.info("")
+
+ logger.info(
+ "The Number of Equations Used to Obtain the 2ND FCs is %s", 3 * natom * num
+ )
+ logger.info("")
+
+ logger.warning(
+ "Be Careful!!! Full Rank of Matrix cannot always guarantee the correct result\
+ sometimes.\n"
+ "If the total atoms in the supercell are less than 100 and\n"
+ "lattice constants are less than 10 angstroms,\n"
+ "I highly suggest displacing more random configurations.\n"
+ "At least use one or two more configurations based on the suggested\
+ number of displacements."
+ )
+ logger.info("")
+
+ if num_disp_f > 3:
+ if num_displaced_supercells != 0:
+ phonon.generate_displacements(
+ distance=displacement,
+ number_of_snapshots=num_displaced_supercells,
+ random_seed=103,
+ )
+ else:
+ phonon.generate_displacements(
+ distance=displacement,
+ number_of_snapshots=num_d,
+ random_seed=103,
+ )
+ else:
+ pass
+
+ supercells = phonon.supercells_with_displacements
+ displacements = [get_pmg_structure(cell) for cell in supercells]
+
+ # Here, the ALM module is used to determine how many free parameters of third and
+ # fourth order force constants (FCs) within the specific supercell.
+ if cal_anhar_fcs:
+ # # Due to the cutoff radius of the force constants use the unit of Borh in ALM,
+ # # we need to convert the cutoff radius from Angstrom to Bohr.
+ # with ALM(lattice * 1.89, positions, numbers) as alm:
+ # # Define the force constants up to fourth order with a list of
+ # # cutoff radius
+ # alm.define(3, fcs_cutoff_radius)
+ # # Perform symmetry analysis and suggest irreducible force constants.
+ # alm.suggest()
+ # # Get the number of irreducible elements for both 3RD- and 4TH-order
+ # # force constants
+ # n_rd_anh = alm._get_number_of_irred_fc_elements(
+ # 2
+ # ) + alm._get_number_of_irred_fc_elements(3)
+ # # we can determine how many displaced supercells we need to use to extract
+ # # the 3rd and 4th order force constants, and we can add a scaling factor
+ # # to reduce the number of displaced supercells due to we use the lasso
+ # # technique.
+ # num_d_anh = int(np.ceil(n_rd_anh / (3.0 * natom)))
+ # num_dis_cells_anhar = num_disp_anhar if num_disp_anhar != 0 else num_d_anh
+
+ num_dis_cells_anhar = 20
+ # generate the supercells for anharmonic force constants
+ phonon.generate_displacements(
+ distance=displacement_anhar,
+ number_of_snapshots=num_dis_cells_anhar,
+ random_seed=103,
+ )
+ supercells = phonon.supercells_with_displacements
+ displacements += [get_pmg_structure(cell) for cell in supercells]
+ else:
+ pass
+
+ # add the equilibrium structure to the list for calculating
+ # the residual forces.
+ displacements.append(get_pmg_structure(phonon.supercell))
+ return displacements
+
+
+@job(
+ output_schema=PhononBSDOSDoc,
+ data=[PhononDos, PhononBandStructureSymmLine, Forceconstants],
+)
+def generate_frequencies_eigenvectors(
+ structure: Structure,
+ supercell_matrix: np.array,
+ displacement: float,
+ displacement_anhar: float,
+ num_displaced_supercells: int,
+ num_disp_anhar: int,
+ cal_anhar_fcs: bool,
+ fcs_cutoff_radius: list[int],
+ renorm_phonon: bool,
+ renorm_temp: list[int],
+ cal_ther_cond: bool,
+ ther_cond_mesh: list[int],
+ ther_cond_temp: list[int],
+ sym_reduce: bool,
+ symprec: float,
+ use_symmetrized_structure: str | None,
+ kpath_scheme: str,
+ code: str,
+ mp_id: str,
+ displacement_data: dict[str, list],
+ total_dft_energy: float,
+ epsilon_static: Matrix3D = None,
+ born: Matrix3D = None,
+ **kwargs,
+) -> PhononBSDOSDoc:
+ """
+ Analyze the phonon runs and summarize the results.
+
+ Parameters
+ ----------
+ structure: Structure object
+ Fully optimized structure used for phonon runs
+ supercell_matrix: np.array
+ array to describe supercell
+ displacement: float
+ displacement in Angstrom used for supercell computation
+ sym_reduce: bool
+ if True, symmetry will be used in phonopy
+ symprec: float
+ precision to determine symmetry
+ use_symmetrized_structure: str
+ primitive, conventional, None are allowed
+ kpath_scheme: str
+ kpath scheme for phonon band structure computation
+ code: str
+ code to run computations
+ displacement_data: dict
+ outputs from displacements
+ total_dft_energy: float
+ total DFT energy in eV per cell
+ epsilon_static: Matrix3D
+ The high-frequency dielectric constant
+ born: Matrix3D
+ Born charges
+ kwargs: dict
+ Additional parameters that are passed to PhononBSDOSDoc.from_forces_born
+ """
+ return PhononBSDOSDoc.from_forces_born(
+ structure=structure.remove_site_property(property_name="magmom")
+ if "magmom" in structure.site_properties
+ else structure,
+ supercell_matrix=supercell_matrix,
+ displacement=displacement,
+ num_displaced_supercells=num_displaced_supercells,
+ cal_anhar_fcs=cal_anhar_fcs,
+ displacement_anhar=displacement_anhar,
+ num_disp_anhar=num_disp_anhar,
+ fcs_cutoff_radius=fcs_cutoff_radius,
+ renorm_phonon=renorm_phonon,
+ renorm_temp=renorm_temp,
+ cal_ther_cond=cal_ther_cond,
+ ther_cond_mesh=ther_cond_mesh,
+ ther_cond_temp=ther_cond_temp,
+ sym_reduce=sym_reduce,
+ symprec=symprec,
+ use_symmetrized_structure=use_symmetrized_structure,
+ kpath_scheme=kpath_scheme,
+ code=code,
+ mp_id=mp_id,
+ displacement_data=displacement_data,
+ total_dft_energy=total_dft_energy,
+ epsilon_static=epsilon_static,
+ born=born,
+ **kwargs,
+ )
+
+
+# I did not directly import this job from the phonon module
+# because I modified the job to pass the displaced structures
+# to the output.
+@job(data=["forces", "displaced_structures"])
+def run_phonon_displacements(
+ displacements: list[Structure],
+ structure: Structure,
+ supercell_matrix: Matrix3D,
+ phonon_maker: BaseVaspMaker | ForceFieldStaticMaker | BaseAimsMaker = None,
+ prev_dir: str | Path = None,
+ prev_dir_argname: str = None,
+ socket: bool = False,
+) -> Flow:
+ """
+ Run phonon displacements.
+
+ Note, this job will replace itself with N displacement calculations,
+ or a single socket calculation for all displacements.
+
+ Parameters
+ ----------
+ displacements: Sequence
+ All displacements to calculate
+ structure: Structure object
+ Fully optimized structure used for phonon computations.
+ supercell_matrix: Matrix3D
+ supercell matrix for meta data
+ phonon_maker : .BaseVaspMaker or .ForceFieldStaticMaker or .BaseAimsMaker
+ A maker to use to generate dispacement calculations
+ prev_dir: str or Path
+ The previous working directory
+ prev_dir_argname: str
+ argument name for the prev_dir variable
+ socket: bool
+ If True use the socket-io interface to increase performance
+ """
+ phonon_jobs = []
+ outputs: dict[str, list] = {
+ "displacement_number": [],
+ "forces": [],
+ "uuids": [],
+ "dirs": [],
+ "displaced_structures": [],
+ }
+ phonon_job_kwargs = {}
+ if prev_dir is not None and prev_dir_argname is not None:
+ phonon_job_kwargs[prev_dir_argname] = prev_dir
+
+ if socket:
+ phonon_job = phonon_maker.make(displacements, **phonon_job_kwargs)
+ info = {
+ "original_structure": structure,
+ "supercell_matrix": supercell_matrix,
+ "displaced_structures": displacements,
+ }
+ phonon_job.update_maker_kwargs(
+ {"_set": {"write_additional_data->phonon_info:json": info}}, dict_mod=True
+ )
+ phonon_jobs.append(phonon_job)
+ outputs["displacement_number"] = list(range(len(displacements)))
+ outputs["uuids"] = [phonon_job.output.uuid] * len(displacements)
+ outputs["dirs"] = [phonon_job.output.dir_name] * len(displacements)
+ outputs["forces"] = phonon_job.output.output.all_forces
+ # add the displaced structures, still need to be careful with the order,
+ # experimental feature
+ outputs["displaced_structures"] = displacements
+ else:
+ for idx, displacement in enumerate(displacements):
+ if prev_dir is not None:
+ phonon_job = phonon_maker.make(displacement, prev_dir=prev_dir)
+ else:
+ phonon_job = phonon_maker.make(displacement)
+ phonon_job.append_name(f" {idx + 1}/{len(displacements)}")
+
+ # we will add some meta data
+ info = {
+ "displacement_number": idx,
+ "original_structure": structure,
+ "supercell_matrix": supercell_matrix,
+ "displaced_structure": displacement,
+ }
+ with contextlib.suppress(Exception):
+ phonon_job.update_maker_kwargs(
+ {"_set": {"write_additional_data->phonon_info:json": info}},
+ dict_mod=True,
+ )
+ phonon_jobs.append(phonon_job)
+ outputs["displacement_number"].append(idx)
+ outputs["uuids"].append(phonon_job.output.uuid)
+ outputs["dirs"].append(phonon_job.output.dir_name)
+ outputs["forces"].append(phonon_job.output.output.forces)
+ outputs["displaced_structures"].append(displacement)
+
+ displacement_flow = Flow(phonon_jobs, outputs)
+ return Response(replace=displacement_flow)
diff --git a/src/atomate2/common/jobs/phonons.py b/src/atomate2/common/jobs/phonons.py
index 39457c8c9f..41b7eafb10 100644
--- a/src/atomate2/common/jobs/phonons.py
+++ b/src/atomate2/common/jobs/phonons.py
@@ -14,11 +14,9 @@
from pymatgen.io.phonopy import get_phonopy_structure, get_pmg_structure
from pymatgen.phonon.bandstructure import PhononBandStructureSymmLine
from pymatgen.phonon.dos import PhononDos
-from pymatgen.transformations.advanced_transformations import (
- CubicSupercellTransformation,
-)
from atomate2.common.schemas.phonons import ForceConstants, PhononBSDOSDoc, get_factor
+from atomate2.common.utils import get_supercell_matrix
if TYPE_CHECKING:
from pathlib import Path
@@ -82,45 +80,15 @@ def get_supercell_size(
**kwargs:
Additional parameters that can be set.
"""
- kwargs.setdefault("force_diagonal", False)
- common_kwds = dict(
- min_length=min_length,
+ return get_supercell_matrix(
+ allow_orthorhombic=allow_orthorhombic,
max_length=max_length,
- min_atoms=kwargs.get("min_atoms"),
- max_atoms=kwargs.get("max_atoms"),
- step_size=kwargs.get("step_size", 0.1),
- force_diagonal=kwargs["force_diagonal"],
+ min_length=min_length,
+ prefer_90_degrees=prefer_90_degrees,
+ structure=structure,
+ **kwargs,
)
- if not prefer_90_degrees:
- transformation = CubicSupercellTransformation(
- **common_kwds,
- force_90_degrees=False,
- allow_orthorhombic=allow_orthorhombic,
- )
- transformation.apply_transformation(structure=structure)
- else:
- try:
- common_kwds.update({"max_atoms": kwargs.get("max_atoms", 1200)})
- transformation = CubicSupercellTransformation(
- **common_kwds,
- force_90_degrees=True,
- angle_tolerance=kwargs.get("angle_tolerance", 1e-2),
- allow_orthorhombic=allow_orthorhombic,
- )
- transformation.apply_transformation(structure=structure)
-
- except AttributeError:
- transformation = CubicSupercellTransformation(
- **common_kwds,
- force_90_degrees=False,
- allow_orthorhombic=allow_orthorhombic,
- )
- transformation.apply_transformation(structure=structure)
-
- # matrix from pymatgen has to be transposed
- return transformation.transformation_matrix.transpose().tolist()
-
@job(data=[Structure])
def generate_phonon_displacements(
@@ -154,17 +122,23 @@ def generate_phonon_displacements(
scheme to generate kpath
code:
code to perform the computations
+
+ Returns
+ -------
+ List[Structure]
+ Displaced structures
"""
warnings.warn(
"Initial magnetic moments will not be considered for the determination "
"of the symmetry of the structure and thus will be removed now.",
- stacklevel=1,
- )
- cell = get_phonopy_structure(
- structure.remove_site_property(property_name="magmom")
- if "magmom" in structure.site_properties
- else structure
+ stacklevel=2,
)
+ if "magmom" in structure.site_properties:
+ # remove_site_property is in-place so make a structure copy first
+ no_mag_struct = structure.copy().remove_site_property(property_name="magmom")
+ else:
+ no_mag_struct = structure
+ cell = get_phonopy_structure(no_mag_struct)
factor = get_factor(code)
# a bit of code repetition here as I currently
diff --git a/src/atomate2/common/jobs/qha.py b/src/atomate2/common/jobs/qha.py
index 517654040f..51f657cd7c 100644
--- a/src/atomate2/common/jobs/qha.py
+++ b/src/atomate2/common/jobs/qha.py
@@ -9,6 +9,7 @@
from atomate2.common.schemas.phonons import PhononBSDOSDoc
from atomate2.common.schemas.qha import PhononQHADoc
+from atomate2.common.utils import get_supercell_matrix
if TYPE_CHECKING:
from pymatgen.core.structure import Structure
@@ -18,10 +19,47 @@
logger = logging.getLogger(__name__)
-@job(
- data=[PhononBSDOSDoc],
-)
-def get_phonon_jobs(phonon_maker: BasePhononMaker, eos_output: dict) -> Flow:
+@job
+def get_supercell_size(
+ eos_output: dict,
+ min_length: float,
+ max_length: float,
+ prefer_90_degrees: bool,
+ allow_orthorhombic: bool = False,
+ **kwargs,
+) -> list[list[float]]:
+ """
+ Job to get the supercell size from an eos output.
+
+ Parameters
+ ----------
+ eos_output: dict
+ output from eos state job
+ min_length: float
+ minimum length of cell in Angstrom
+ max_length: float
+ maximum length of cell in Angstrom
+ prefer_90_degrees: bool
+ if True, the algorithm will try to find a cell with 90 degree angles first
+ allow_orthorhombic: bool
+ if True, orthorhombic supercells are allowed
+ **kwargs:
+ Additional parameters that can be set.
+ """
+ return get_supercell_matrix(
+ eos_output["relax"]["structure"][0],
+ min_length,
+ max_length,
+ prefer_90_degrees,
+ allow_orthorhombic,
+ **kwargs,
+ )
+
+
+@job(data=[PhononBSDOSDoc])
+def get_phonon_jobs(
+ phonon_maker: BasePhononMaker, eos_output: dict, supercell_matrix: list[list[float]]
+) -> Flow:
"""
Start all relevant phonon jobs.
@@ -31,17 +69,20 @@ def get_phonon_jobs(phonon_maker: BasePhononMaker, eos_output: dict) -> Flow:
Maker to start harmonic phonon runs.
eos_output: dict
Output from EOSMaker
-
+ supercell_matrix:
+ Supercell matrix to be passed into the phonon runs.
"""
phonon_jobs = []
outputs = []
for istructure, structure in enumerate(eos_output["relax"]["structure"]):
if eos_output["relax"]["dir_name"][istructure] is not None:
phonon_job = phonon_maker.make(
- structure, prev_dir=eos_output["relax"]["dir_name"][istructure]
+ structure,
+ prev_dir=eos_output["relax"]["dir_name"][istructure],
+ supercell_matrix=supercell_matrix,
)
else:
- phonon_job = phonon_maker.make(structure)
+ phonon_job = phonon_maker.make(structure, supercell_matrix=supercell_matrix)
phonon_job.append_name(f" eos deformation {istructure + 1}")
phonon_jobs.append(phonon_job)
outputs.append(phonon_job.output)
@@ -92,6 +133,7 @@ def analyze_free_energy(
output.volume_per_formula_unit * output.formula_units
for output in phonon_outputs
]
+ supercell_matrix: list[list[float]] = phonon_outputs[0].supercell_matrix
for itemp, temp in enumerate(phonon_outputs[0].temperatures):
temperatures.append(float(temp))
@@ -129,5 +171,6 @@ def analyze_free_energy(
pressure=pressure,
formula_units=next(iter(set(formula_units))),
eos_type=eos_type,
+ supercell_matrix=supercell_matrix,
**kwargs,
)
diff --git a/src/atomate2/common/schemas/gruneisen.py b/src/atomate2/common/schemas/gruneisen.py
index b12f9c78a9..379dc173c2 100644
--- a/src/atomate2/common/schemas/gruneisen.py
+++ b/src/atomate2/common/schemas/gruneisen.py
@@ -4,12 +4,8 @@
from pathlib import Path
from typing import Optional, Union
-import matplotlib.pyplot as plt
-import numpy as np
import phonopy
from emmet.core.structure import StructureMetadata
-from matplotlib import colors
-from matplotlib.colors import LinearSegmentedColormap
from phonopy.api_gruneisen import PhonopyGruneisen
from phonopy.phonon.band_structure import get_band_qpoints_and_path_connections
from pydantic import BaseModel, Field
@@ -24,12 +20,7 @@
GruneisenParameter,
GruneisenPhononBandStructureSymmLine,
)
-from pymatgen.phonon.plotter import (
- GruneisenPhononBSPlotter,
- GruneisenPlotter,
- freq_units,
-)
-from pymatgen.util.plotting import pretty_plot
+from pymatgen.phonon.plotter import GruneisenPhononBSPlotter, GruneisenPlotter
from typing_extensions import Self
from atomate2.common.schemas.phonons import PhononBSDOSDoc
@@ -164,7 +155,7 @@ def from_phonon_yamls(
mesh=mesh,
shift=compute_gruneisen_param_kwargs.get("shift"),
is_gamma_center=compute_gruneisen_param_kwargs.get(
- "is_gamma_center", True
+ "is_gamma_center", False
),
is_time_reversal=compute_gruneisen_param_kwargs.get(
"is_time_reversal", True
@@ -184,7 +175,7 @@ def from_phonon_yamls(
mesh=kpoint.kpts[0],
shift=compute_gruneisen_param_kwargs.get("shift"),
is_gamma_center=compute_gruneisen_param_kwargs.get(
- "is_gamma_center", True
+ "is_gamma_center", False
),
is_time_reversal=compute_gruneisen_param_kwargs.get(
"is_time_reversal", True
@@ -228,9 +219,14 @@ def from_phonon_yamls(
labels_dict=kpath_dict,
)
gp_bs_plot = GruneisenPhononBSPlotter(bs=gruneisen_band_structure)
- GruneisenParameterDocument.get_gruneisen_weighted_bandstructure(
- gruneisen_band_symline_plotter=gp_bs_plot,
- save_fig=True,
+
+ gruneisen_bs_plot = compute_gruneisen_param_kwargs.get(
+ "gruneisen_bs", "gruneisen_band.pdf"
+ )
+ gp_bs_plot.save_plot_gs(
+ filename=gruneisen_bs_plot,
+ plot_ph_bs_with_gruneisen=True,
+ img_format=compute_gruneisen_param_kwargs.get("img_format", "pdf"),
**compute_gruneisen_param_kwargs,
)
gruneisen_parameter_inputs = {
@@ -261,82 +257,3 @@ def from_phonon_yamls(
gruneisen_band_structure=gruneisen_band_structure,
derived_properties=derived_properties,
)
-
- @staticmethod
- def get_gruneisen_weighted_bandstructure(
- gruneisen_band_symline_plotter: GruneisenPhononBSPlotter,
- save_fig: bool = True,
- **kwargs,
- ) -> None:
- """Save a phonon band structure weighted with Grueneisen parameters.
-
- Parameters
- ----------
- gruneisen_band_symline_plotter: GruneisenPhononBSPlotter
- pymatgen GruneisenPhononBSPlotter obj
- save_fig: bool
- bool to save plots
- kwargs: dict
- keyword arguments to adjust plotter
-
- Returns
- -------
- None
- """
- u = freq_units(kwargs.get("units", "THz"))
- ax = pretty_plot(12, 8)
- gruneisen_band_symline_plotter._make_ticks(ax) # noqa: SLF001
-
- # plot y=0 line
- ax.axhline(0, linewidth=1, color="black")
-
- # Create custom colormap (default is red to blue)
- cmap = LinearSegmentedColormap.from_list(
- "mycmap", kwargs.get("mycmap", ["red", "blue"])
- )
-
- data = gruneisen_band_symline_plotter.bs_plot_data()
-
- # extract min and max Grüneisen parameter values
- max_gruneisen = np.array(data["gruneisen"]).max()
- min_gruneisen = np.array(data["gruneisen"]).min()
-
- # LogNormalize colormap based on the min and max Grüneisen parameter values
- norm = colors.SymLogNorm(
- vmin=min_gruneisen,
- vmax=max_gruneisen,
- linthresh=1e-2,
- linscale=1,
- )
-
- for (dists_inx, dists), (_, freqs) in zip(
- enumerate(data["distances"]), enumerate(data["frequency"]), strict=True
- ):
- for band_idx in range(gruneisen_band_symline_plotter.n_bands):
- ys = [freqs[band_idx][j] * u.factor for j in range(len(dists))]
- ys_gru = [
- data["gruneisen"][dists_inx][band_idx][idx]
- for idx in range(len(data["distances"][dists_inx]))
- ]
- sc = ax.scatter(
- dists, ys, c=ys_gru, cmap=cmap, norm=norm, marker="o", s=1
- )
-
- # Main X and Y Labels
- ax.set_xlabel(r"$\mathrm{Wave\ Vector}$", fontsize=30)
- units = kwargs.get("units", "THz")
- ax.set_ylabel(f"Frequencies ({units})", fontsize=30)
- # X range (K)
- # last distance point
- x_max = data["distances"][-1][-1]
- ax.set_xlim(0, x_max)
-
- cbar = plt.colorbar(sc, ax=ax)
- cbar.set_label(r"$\gamma \ \mathrm{(logarithmized)}$", fontsize=30)
- plt.tight_layout()
- gruneisen_band_plot = kwargs.get("gruneisen_bs", "gruneisen_band.pdf")
- if save_fig:
- plt.savefig(fname=gruneisen_band_plot)
- plt.close()
- else:
- plt.close()
diff --git a/src/atomate2/common/schemas/pheasy.py b/src/atomate2/common/schemas/pheasy.py
new file mode 100644
index 0000000000..b2ae75fcb3
--- /dev/null
+++ b/src/atomate2/common/schemas/pheasy.py
@@ -0,0 +1,1006 @@
+"""Schemas for phonon documents."""
+
+import copy
+import logging
+import pickle
+import shlex
+import subprocess
+from pathlib import Path
+from typing import Optional, Union
+
+import numpy as np
+
+# import lib by jiongzhi zheng
+from ase.io import read
+from emmet.core.math import Matrix3D
+from emmet.core.structure import StructureMetadata
+from hiphive import (
+ ClusterSpace,
+ ForceConstantPotential,
+ ForceConstants,
+ enforce_rotational_sum_rules,
+)
+from hiphive.cutoffs import estimate_maximum_cutoff
+from hiphive.utilities import extract_parameters
+from monty.json import MSONable
+from phonopy import Phonopy
+from phonopy.file_IO import parse_FORCE_CONSTANTS, write_force_constants_to_hdf5
+from phonopy.interface.vasp import write_vasp
+from phonopy.phonon.band_structure import get_band_qpoints_and_path_connections
+from phonopy.structure.symmetry import symmetrize_borns_and_epsilon
+from pydantic import Field
+from pymatgen.core import Structure
+from pymatgen.io.phonopy import (
+ get_ph_bs_symm_line,
+ get_ph_dos,
+ get_phonopy_structure,
+ get_pmg_structure,
+)
+from pymatgen.io.vasp import Kpoints
+from pymatgen.phonon.bandstructure import PhononBandStructureSymmLine
+from pymatgen.phonon.dos import PhononDos
+from pymatgen.phonon.plotter import PhononBSPlotter, PhononDosPlotter
+from pymatgen.symmetry.bandstructure import HighSymmKpath
+from pymatgen.symmetry.kpath import KPathSeek
+from typing_extensions import Self
+
+# import some classmethod directly from phonons
+from atomate2.common.schemas.phonons import (
+ PhononComputationalSettings,
+ PhononJobDirs,
+ PhononUUIDs,
+ ThermalDisplacementData,
+ get_factor,
+)
+
+logger = logging.getLogger(__name__)
+
+
+class Forceconstants(MSONable):
+ """A force constants class."""
+
+ def __init__(self, force_constants: list[list[Matrix3D]]) -> None:
+ self.force_constants = force_constants
+
+
+class PhononBSDOSDoc(StructureMetadata, extra="allow"): # type: ignore[call-arg]
+ """Collection of all data produced by the phonon workflow."""
+
+ structure: Optional[Structure] = Field(
+ None, description="Structure of Materials Project."
+ )
+
+ phonon_bandstructure: Optional[PhononBandStructureSymmLine] = Field(
+ None,
+ description="Phonon band structure object.",
+ )
+
+ phonon_dos: Optional[PhononDos] = Field(
+ None,
+ description="Phonon density of states object.",
+ )
+
+ free_energies: Optional[list[float]] = Field(
+ None,
+ description="vibrational part of the free energies in J/mol per "
+ "formula unit for temperatures in temperature_list",
+ )
+
+ heat_capacities: Optional[list[float]] = Field(
+ None,
+ description="heat capacities in J/K/mol per "
+ "formula unit for temperatures in temperature_list",
+ )
+
+ internal_energies: Optional[list[float]] = Field(
+ None,
+ description="internal energies in J/mol per "
+ "formula unit for temperatures in temperature_list",
+ )
+ entropies: Optional[list[float]] = Field(
+ None,
+ description="entropies in J/(K*mol) per formula unit"
+ "for temperatures in temperature_list ",
+ )
+
+ temperatures: Optional[list[int]] = Field(
+ None,
+ description="temperatures at which the vibrational"
+ " part of the free energies"
+ " and other properties have been computed",
+ )
+
+ total_dft_energy: Optional[float] = Field("total DFT energy per formula unit in eV")
+
+ has_imaginary_modes: Optional[bool] = Field(
+ None, description="if true, structure has imaginary modes"
+ )
+
+ # needed, e.g. to compute Grueneisen parameter etc
+ force_constants: Optional[Forceconstants] = Field(
+ None, description="Force constants between every pair of atoms in the structure"
+ )
+
+ born: Optional[list[Matrix3D]] = Field(
+ None,
+ description="born charges as computed from phonopy. Only for symmetrically "
+ "different atoms",
+ )
+
+ epsilon_static: Optional[Matrix3D] = Field(
+ None, description="The high-frequency dielectric constant"
+ )
+
+ supercell_matrix: Matrix3D = Field("matrix describing the supercell")
+ primitive_matrix: Matrix3D = Field(
+ "matrix describing relationship to primitive cell"
+ )
+
+ code: str = Field("String describing the code for the computation")
+
+ phonopy_settings: PhononComputationalSettings = Field(
+ "Field including settings for Phonopy"
+ )
+
+ thermal_displacement_data: Optional[ThermalDisplacementData] = Field(
+ "Includes all data of the computation of the thermal displacements"
+ )
+
+ jobdirs: Optional[PhononJobDirs] = Field(
+ "Field including all relevant job directories"
+ )
+
+ uuids: Optional[PhononUUIDs] = Field("Field including all relevant uuids")
+
+ @classmethod
+ def from_forces_born(
+ cls,
+ structure: Structure,
+ supercell_matrix: np.array,
+ displacement: float,
+ num_displaced_supercells: int,
+ cal_anhar_fcs: bool,
+ displacement_anhar: float,
+ num_disp_anhar: int,
+ fcs_cutoff_radius: list[int],
+ renorm_phonon: bool,
+ cal_ther_cond: bool,
+ ther_cond_mesh: list[int],
+ ther_cond_temp: list[int],
+ sym_reduce: bool,
+ symprec: float,
+ use_symmetrized_structure: Union[str, None],
+ kpath_scheme: str,
+ code: str,
+ mp_id: str,
+ displacement_data: dict[str, list],
+ total_dft_energy: float,
+ epsilon_static: Matrix3D = None,
+ born: Matrix3D = None,
+ **kwargs,
+ ) -> Self:
+ """Generate collection of phonon data.
+
+ Parameters
+ ----------
+ structure: Structure object
+ supercell_matrix: numpy array describing the supercell
+ displacement: float
+ size of displacement in angstrom
+ num_displaced_supercells: int
+ number of displaced supercells
+ cal_anhar_fcs: bool
+ if True, anharmonic force constants will be computed
+ displacement_anhar: float
+ size of displacement in angstrom for anharmonic force constants
+ num_disp_anhar: int
+ number of displaced supercells for anharmonic force constants
+ fcs_cutoff_radius: list
+ cutoff radius for force constants
+ sym_reduce: bool
+ if True, phonopy will use symmetry
+ symprec: float
+ precision to determine kpaths,
+ primitive cells and symmetry in phonopy and pymatgen
+ use_symmetrized_structure: str
+ primitive, conventional or None
+ kpath_scheme: str
+ kpath scheme to generate phonon band structure
+ code: str
+ which code was used for computation
+ displacement_data:
+ output of the displacement data
+ total_dft_energy: float
+ total energy in eV per cell
+ epsilon_static: Matrix3D
+ The high-frequency dielectric constant
+ born: Matrix3D
+ born charges
+ **kwargs:
+ additional arguments
+ """
+ factor = get_factor(code)
+ # This opens the opportunity to add support for other codes
+ # that are supported by phonopy
+
+ cell = get_phonopy_structure(structure)
+
+ if use_symmetrized_structure == "primitive":
+ primitive_matrix: Union[np.ndarray, str] = np.eye(3)
+ else:
+ primitive_matrix = "auto"
+
+ # TARP: THIS IS BAD! Including for discussions sake
+ if cell.magnetic_moments is not None and primitive_matrix == "auto":
+ if np.any(cell.magnetic_moments != 0.0):
+ raise ValueError(
+ "For materials with magnetic moments, "
+ "use_symmetrized_structure must be 'primitive'"
+ )
+ cell.magnetic_moments = None
+
+ # Create the phonon object using the phonopy API to write the POSCAR and
+ # SPOSCAR files for the input of pheasy code.
+ phonon = Phonopy(
+ cell,
+ supercell_matrix,
+ primitive_matrix=primitive_matrix,
+ factor=factor,
+ symprec=symprec,
+ is_symmetry=sym_reduce,
+ )
+
+ # Write the POSCAR and SPOSCAR files for the input of pheasy code
+ supercell = phonon.get_supercell()
+ write_vasp("POSCAR", cell)
+ write_vasp("SPOSCAR", supercell)
+
+ # get the force-displacement dataset from previous calculations
+ dataset_forces = [np.array(forces) for forces in displacement_data["forces"]]
+ dataset_forces_array = np.array(dataset_forces)
+ # save to csv file
+ import csv
+
+ with open("dataset_forces.csv", "w") as file:
+ writer = csv.writer(file)
+ writer.writerows(dataset_forces_array)
+
+ # To deduct the residual forces on an equilibrium structure to eliminate the
+ # fitting error
+ dataset_forces_array_rr = dataset_forces_array - dataset_forces_array[-1, :, :]
+
+ # force matrix on the displaced structures
+ dataset_forces_array_disp = dataset_forces_array_rr[:-1, :, :]
+ # dataset_disps = [
+ # np.array(disps.cart_coords)
+ # for disps in displacement_data["displaced_structures"]
+ # ]
+
+ # get the displacement dataset
+ # dataset_disps_array_rr = np.round(
+ # (dataset_disps - supercell.get_positions()),
+ # decimals=16
+ # ).astype('double')
+ # dataset_disps_array_use = dataset_disps_array_rr[:-1, :, :]
+
+ # To handle the large dispalced distance in the dataset
+ dataset_disps = [
+ np.array(disps.frac_coords)
+ for disps in displacement_data["displaced_structures"]
+ ]
+ logger.info(f"dataset_disps = {dataset_disps}")
+ # save to csv file
+ import csv
+
+ with open("dataset_disps.csv", "w") as file:
+ writer = csv.writer(file)
+ writer.writerows(dataset_disps)
+ dataset_disps_array_rr = np.round(
+ (dataset_disps - supercell.get_scaled_positions()), decimals=16
+ ).astype("double")
+ # save to csv file
+ with open("dataset_disps_array_rr.csv", "w") as file:
+ writer = csv.writer(file)
+ writer.writerows(dataset_disps_array_rr)
+ dataset_disps_array_rr = np.where(
+ dataset_disps_array_rr > 0.5,
+ dataset_disps_array_rr - 1.0,
+ dataset_disps_array_rr,
+ )
+ dataset_disps_array_rr = np.where(
+ dataset_disps_array_rr < -0.5,
+ dataset_disps_array_rr + 1.0,
+ dataset_disps_array_rr,
+ )
+
+ # Transpose the displacement array on the
+ # last two axes (atoms and coordinates)
+ dataset_disps_array_rr_transposed = np.transpose(
+ dataset_disps_array_rr, (0, 2, 1)
+ )
+
+ # Perform matrix multiplication with the transposed supercell.cell
+ # 'ij' for supercell.cell.T and
+ # 'nkj' for the transposed dataset_disps_array_rr
+ dataset_disps_array_rr_cartesian = np.einsum(
+ "ij,njk->nik", supercell.cell.T, dataset_disps_array_rr_transposed
+ )
+ # Transpose back to the original format
+ dataset_disps_array_rr_cartesian = np.transpose(
+ dataset_disps_array_rr_cartesian, (0, 2, 1)
+ )
+
+ dataset_disps_array_use = dataset_disps_array_rr_cartesian[:-1, :, :]
+
+ # separate the dataset into harmonic and anharmonic parts
+ if cal_anhar_fcs:
+ try:
+ from alm import ALM
+ except ImportError:
+ logging.exception(
+ "Error importing ALM. Please ensure the 'alm'"
+ "library is installed."
+ )
+
+ supercell_ph = phonon.supercell
+ lattice = supercell_ph.cell
+ positions = supercell_ph.scaled_positions
+ numbers = supercell_ph.numbers
+ natom = len(numbers)
+
+ # get the number of free parameters of 2ND FCs from ALM, labeled as n_fp
+ with ALM(lattice, positions, numbers) as alm:
+ alm.define(1)
+ alm.suggest()
+ n_fp = alm._get_number_of_irred_fc_elements(1) # noqa: SLF001
+
+ # get the number of displaced supercells based on the
+ # number of free parameters
+ num = int(np.ceil(n_fp / (3.0 * natom)))
+
+ # get the number of displaced supercells from phonopy to compared
+ # with the number of 3, if the number of displaced supercells is
+ # less than 3, we will use the finite displacement method to generate
+ # the supercells. Otherwise, we will use the random displacement
+ # method to generate the supercells.
+ phonon.generate_displacements(distance=displacement)
+ num_disp_f = len(phonon.displacements)
+ if num_disp_f > 3:
+ num_d = int(np.ceil(num * 1.8))
+ num_har = num_d
+ else:
+ num_har = num_disp_f
+ else:
+ num_har = dataset_disps_array_use.shape[0]
+
+ if cal_anhar_fcs:
+ dataset_disps_array_use_har = dataset_disps_array_use[:num_har, :, :]
+ dataset_forces_array_disp_har = dataset_forces_array_disp[:num_har, :, :]
+ with open("disp_matrix.pkl", "wb") as file:
+ pickle.dump(dataset_disps_array_use_har, file)
+ with open("force_matrix.pkl", "wb") as file:
+ pickle.dump(dataset_forces_array_disp_har, file)
+
+ else:
+ with open("disp_matrix.pkl", "wb") as file:
+ pickle.dump(dataset_disps_array_use, file)
+ with open("force_matrix.pkl", "wb") as file:
+ pickle.dump(dataset_forces_array_disp, file)
+
+ # get the born charges and dielectric constant
+ if born is not None and epsilon_static is not None:
+ if len(structure) == len(born):
+ borns, epsilon = symmetrize_borns_and_epsilon(
+ ucell=phonon.unitcell,
+ borns=np.array(born),
+ epsilon=np.array(epsilon_static),
+ symprec=symprec,
+ primitive_matrix=phonon.primitive_matrix,
+ supercell_matrix=phonon.supercell_matrix,
+ is_symmetry=kwargs.get("symmetrize_born", True),
+ )
+ else:
+ raise ValueError(
+ "Number of born charges does not agree with number of atoms"
+ )
+ if code == "vasp" and not np.all(np.isclose(borns, 0.0)):
+ phonon.nac_params = {
+ "born": borns,
+ "dielectric": epsilon,
+ "factor": 14.399652,
+ }
+ # Other codes could be added here
+
+ else:
+ borns = None
+ epsilon = None
+
+ prim = read("POSCAR")
+ supercell = read("SPOSCAR")
+
+ # Create the clusters and orbitals for second order force constants
+ # For the variables: --w, --nbody, they are used to specify the order of the
+ # force constants. in the near future, we will add the option to specify the
+ # order of the force constants. And these two variables can be defined by the
+ # users.
+ pheasy_cmd_1 = (
+ f"pheasy --dim {int(supercell_matrix[0][0])} "
+ f"{int(supercell_matrix[1][1])} "
+ f"{int(supercell_matrix[2][2])} "
+ f"-s -w 2 --symprec {float(symprec)} --nbody 2"
+ )
+
+ # Create the null space to further reduce the free parameters for
+ # specific force constants and make them physically correct.
+ pheasy_cmd_2 = (
+ f"pheasy --dim {int(supercell_matrix[0][0])} "
+ f"{int(supercell_matrix[1][1])} "
+ f"{int(supercell_matrix[2][2])} -c --symprec "
+ f"{float(symprec)} -w 2"
+ )
+
+ # Generate the Compressive Sensing matrix,i.e., displacement matrix
+ # for the input of machine leaning method.i.e., LASSO,
+ pheasy_cmd_3 = (
+ f"pheasy --dim {int(supercell_matrix[0][0])} "
+ f"{int(supercell_matrix[1][1])} "
+ f"{int(supercell_matrix[2][2])} -w 2 -d "
+ f"--symprec {float(symprec)} "
+ f"--ndata {int(num_har)} --disp_file"
+ )
+
+ # Here we set a criteria to determine which method to use to generate the
+ # force constants. If the number of displacements is larger than 3, we
+ # will use the LASSO method to generate the force constants. Otherwise,
+ # we will use the least-squred method to generate the force constants.
+ phonon.generate_displacements(distance=displacement)
+ disps = phonon.displacements
+ num_judge = len(disps)
+
+ if num_judge > 3:
+ # Calculate the force constants using the LASSO method due to the
+ # random-displacement method Obviously, the rotaional invariance
+ # constraint, i.e., tag: --rasr BHH, is enforced during the
+ # fitting process.
+ pheasy_cmd_4 = (
+ f"pheasy --dim {int(supercell_matrix[0][0])} "
+ f"{int(supercell_matrix[1][1])} "
+ f"{int(supercell_matrix[2][2])} -f --full_ifc "
+ f"-w 2 --symprec {float(symprec)} "
+ f"-l LASSO --std --rasr BHH --ndata {int(num_har)}"
+ )
+
+ else:
+ # Calculate the force constants using the least-squred method
+ pheasy_cmd_4 = (
+ f"pheasy --dim {int(supercell_matrix[0][0])} "
+ f"{int(supercell_matrix[1][1])} "
+ f"{int(supercell_matrix[2][2])} -f --full_ifc "
+ f"-w 2 --symprec {float(symprec)} "
+ f"--rasr BHH --ndata {int(num_har)}"
+ )
+
+ logger.info("Start running pheasy in cluster")
+
+ subprocess.call(shlex.split(pheasy_cmd_1))
+ logger.info(f"all files in cwd after cmd_1 are {list(Path.cwd().iterdir())}")
+ subprocess.call(shlex.split(pheasy_cmd_2))
+ logger.info(f"all files in cwd after cmd_2 are {list(Path.cwd().iterdir())}")
+ subprocess.call(shlex.split(pheasy_cmd_3))
+ logger.info(f"all files in cwd after cmd_3 are {list(Path.cwd().iterdir())}")
+ # print the cwd
+ logger.info(f"path before running cmd_4 is {Path.cwd()}")
+ subprocess.call(shlex.split(pheasy_cmd_4))
+ logger.info(f"path after running cmd_4 is {Path.cwd()}")
+ # print all the files in the current directory
+ logger.info(f"all files in cwd after cmd_4 are {list(Path.cwd().iterdir())}")
+
+ # When this code is run on Github tests, it is failing because it is
+ # not able to find the FORCE_CONSTANTS file. This is because the file is
+ # somehow getting generated in some temp directory. Can you fix the bug?
+ cwd = Path.cwd()
+ fc_file = cwd / "FORCE_CONSTANT"
+
+ if cal_anhar_fcs:
+ # subprocess.call("rm -f disp_matrix.pkl force_matrix.pkl", shell=True)
+ subprocess.run(
+ ["/bin/rm", "-f", "disp_matrix.pkl", "force_matrix.pkl"], check=True
+ )
+ dataset_disps_array_use_anahr = dataset_disps_array_use[num_har:, :, :]
+ dataset_forces_array_disp_anhar = dataset_forces_array_disp[num_har:, :, :]
+ with open("disp_matrix.pkl", "wb") as file:
+ pickle.dump(dataset_disps_array_use_anahr, file)
+ with open("force_matrix.pkl", "wb") as file:
+ pickle.dump(dataset_forces_array_disp_anhar, file)
+ num_anhar = dataset_disps_array_use_anahr.shape[0]
+ else:
+ pass
+
+ # We next begin to generate the anharmonic force constants up to fourth
+ # order using the LASSO method
+
+ if cal_anhar_fcs:
+ pheasy_cmd_5 = (
+ f"pheasy --dim {int(supercell_matrix[0][0])} "
+ f"{int(supercell_matrix[1][1])} "
+ f"{int(supercell_matrix[2][2])} -s -w 4 --symprec "
+ f"{float(symprec)} "
+ f"--nbody 2 3 3 --c3 {float(fcs_cutoff_radius[1]/1.89)} "
+ f"--c4 {float(fcs_cutoff_radius[2]/1.89)}"
+ )
+ logger.info("pheasy_cmd_5 = %s", pheasy_cmd_5)
+
+ pheasy_cmd_6 = (
+ f"pheasy --dim {int(supercell_matrix[0][0])} "
+ f"{int(supercell_matrix[1][1])} "
+ f"{int(supercell_matrix[2][2])} -c --symprec "
+ f"{float(symprec)} -w 4"
+ )
+ logger.info("pheasy_cmd_6 = %s", pheasy_cmd_6)
+ pheasy_cmd_7 = (
+ f"pheasy --dim {int(supercell_matrix[0][0])} "
+ f"{int(supercell_matrix[1][1])} "
+ f"{int(supercell_matrix[2][2])} -w 4 -d --symprec "
+ f"{float(symprec)} "
+ f"--ndata {int(num_anhar)} --disp_file"
+ )
+ logger.info("pheasy_cmd_7 = %s", pheasy_cmd_7)
+ pheasy_cmd_8 = (
+ f"pheasy --dim {int(supercell_matrix[0][0])} "
+ f"{int(supercell_matrix[1][1])} "
+ f"{int(supercell_matrix[2][2])} -f -w 4 --fix_fc2 "
+ f"--symprec {float(symprec)} "
+ f"--ndata {int(num_anhar)} "
+ )
+ logger.info("pheasy_cmd_8 = %s", pheasy_cmd_8)
+ logger.info("Start running pheasy in cluster")
+
+ subprocess.call(shlex.split(pheasy_cmd_5))
+ subprocess.call(shlex.split(pheasy_cmd_6))
+ subprocess.call(shlex.split(pheasy_cmd_7))
+ subprocess.call(shlex.split(pheasy_cmd_8))
+ else:
+ pass
+
+ # begin to renormzlize the phonon energies
+ if renorm_phonon:
+ pheasy_cmd_9 = (
+ f"pheasy --dim {int(supercell_matrix[0][0])} "
+ f"{int(supercell_matrix[1][1])} "
+ f"{int(supercell_matrix[2][2])} -f -w 4 --fix_fc2 "
+ f"--hdf5 --symprec {float(symprec)} "
+ f"--ndata {int(num_anhar)}"
+ )
+
+ logger.info("Start running pheasy in cluster")
+ subprocess.call(shlex.split(pheasy_cmd_9))
+
+ # write the born charges and dielectric constant to the pheasy format
+
+ else:
+ pass
+
+ # begin to convert the force constants to the phonopy and phono3py format
+ # for the further lattice thermal conductivity calculations
+ if cal_ther_cond:
+ # convert the 2ND order force constants to the phonopy format
+ fc_phonopy_text = parse_FORCE_CONSTANTS(filename="FORCE_CONSTANTS")
+ write_force_constants_to_hdf5(fc_phonopy_text, filename="fc2.hdf5")
+
+ # convert the 3RD order force constants to the phonopy format
+
+ prim_hiphive = read("POSCAR")
+ supercell_hiphive = read("SPOSCAR")
+ fcs = ForceConstants.read_shengBTE(
+ supercell_hiphive, "FORCE_CONSTANTS_3RD", prim_hiphive
+ )
+ fcs.write_to_phono3py("fc3.hdf5")
+
+ phono3py_cmd = (
+ f"phono3py --dim {int(supercell_matrix[0][0])} "
+ f"{int(supercell_matrix[1][1])} {int(supercell_matrix[2][2])} "
+ f"--fc2 --fc3 --br --isotope --wigner "
+ f"--mesh {ther_cond_mesh[0]} {ther_cond_mesh[1]} {ther_cond_mesh[2]} "
+ f"--tmin {ther_cond_temp[0]} --tmax {ther_cond_temp[1]} "
+ f"--tstep {ther_cond_temp[2]}"
+ )
+
+ subprocess.call(shlex.split(phono3py_cmd))
+ else:
+ pass
+
+ # Read the force constants from the output file of pheasy code
+ force_constants = parse_FORCE_CONSTANTS(filename=fc_file)
+ phonon.force_constants = force_constants
+ # symmetrize the force constants to make them physically correct based on
+ # the space group symmetry of the crystal structure.
+ phonon.symmetrize_force_constants()
+
+ # with phonopy.load("phonopy.yaml") the phonopy API can be used
+ phonon.save("phonopy.yaml")
+
+ # get phonon band structure
+ kpath_dict, kpath_concrete = PhononBSDOSDoc.get_kpath(
+ structure=get_pmg_structure(phonon.primitive),
+ kpath_scheme=kpath_scheme,
+ symprec=symprec,
+ )
+
+ npoints_band = kwargs.get("npoints_band", 101)
+ qpoints, connections = get_band_qpoints_and_path_connections(
+ kpath_concrete, npoints=kwargs.get("npoints_band", 101)
+ )
+
+ # phonon band structures will always be computed
+ filename_band_yaml = "phonon_band_structure.yaml"
+
+ # TODO: potentially add kwargs to avoid computation of eigenvectors
+ phonon.run_band_structure(
+ qpoints,
+ path_connections=connections,
+ with_eigenvectors=kwargs.get("band_structure_eigenvectors", False),
+ is_band_connection=kwargs.get("band_structure_eigenvectors", False),
+ )
+ phonon.write_yaml_band_structure(filename=filename_band_yaml)
+ bs_symm_line = get_ph_bs_symm_line(
+ filename_band_yaml, labels_dict=kpath_dict, has_nac=born is not None
+ )
+ new_plotter = PhononBSPlotter(bs=bs_symm_line)
+ new_plotter.save_plot(
+ filename=kwargs.get("filename_bs", "phonon_band_structure.pdf"),
+ units=kwargs.get("units", "THz"),
+ )
+
+ # will determine if imaginary modes are present in the structure
+ imaginary_modes = bs_symm_line.has_imaginary_freq(
+ tol=kwargs.get("tol_imaginary_modes", 1e-5)
+ )
+
+ # If imaginary modes are present, we first use the hiphive code to enforce
+ # some symmetry constraints to eliminate the imaginary modes (generally work
+ # for small imaginary modes near Gamma point). If the imaginary modes are
+ # still present, we will use the pheasy code to generate the force constants
+ # using a shorter cutoff (10 A) to eliminate the imaginary modes, also we
+ # just want to remove the imaginary modes near Gamma point. In the future,
+ # we will only use the pheasy code to do the job.
+
+ if imaginary_modes:
+ # Define a cluster space using the largest cutoff you can
+ max_cutoff = estimate_maximum_cutoff(supercell) - 0.01
+ cutoffs = [max_cutoff] # only second order needed
+ cs = ClusterSpace(prim, cutoffs)
+
+ # import the phonopy force constants using the correct supercell also
+ # provided by phonopy
+ fcs = ForceConstants.read_phonopy(supercell, "FORCE_CONSTANTS")
+
+ # Find the parameters that best fits the force constants given you
+ # cluster space
+ parameters = extract_parameters(fcs, cs)
+
+ # Enforce the rotational sum rules
+ parameters_rot = enforce_rotational_sum_rules(
+ cs, parameters, ["Huang", "Born-Huang"], alpha=1e-6
+ )
+
+ # use the new parameters to make a fcp and then create the force
+ # constants and write to a phonopy file
+ fcp = ForceConstantPotential(cs, parameters_rot)
+ fcs = fcp.get_force_constants(supercell)
+ fcs.write_to_phonopy("FORCE_CONSTANTS_new", format="text")
+
+ force_constants = parse_FORCE_CONSTANTS(filename="FORCE_CONSTANTS_new")
+ phonon.force_constants = force_constants
+ phonon.symmetrize_force_constants()
+
+ phonon.run_band_structure(
+ qpoints, path_connections=connections, with_eigenvectors=True
+ )
+ phonon.write_yaml_band_structure(filename=filename_band_yaml)
+ bs_symm_line = get_ph_bs_symm_line(
+ filename_band_yaml, labels_dict=kpath_dict, has_nac=born is not None
+ )
+
+ new_plotter = PhononBSPlotter(bs=bs_symm_line)
+
+ new_plotter.save_plot(
+ filename=kwargs.get("filename_bs", "phonon_band_structure.pdf"),
+ units=kwargs.get("units", "THz"),
+ )
+
+ # new_plotter.save_plot("phonon_band_structure.eps",
+ # img_format=kwargs.get("img_format", "eps"),
+ # units=kwargs.get("units", "THz"),)
+
+ imaginary_modes_hiphive = bs_symm_line.has_imaginary_freq(
+ tol=kwargs.get("tol_imaginary_modes", 1e-5)
+ )
+
+ else:
+ imaginary_modes_hiphive = False
+ imaginary_modes = False
+
+ # Using a shorter cutoff (10 A) to generate the force constants to
+ # eliminate the imaginary modes near Gamma point in phesay code
+ if imaginary_modes_hiphive:
+ pheasy_cmd_11 = (
+ f"pheasy --dim {int(supercell_matrix[0][0])} "
+ f"{int(supercell_matrix[1][1])} "
+ f"{int(supercell_matrix[2][2])} -s -w 2 --c2 "
+ f"10.0 --symprec {float(symprec)} "
+ f"--nbody 2"
+ )
+
+ pheasy_cmd_12 = (
+ f"pheasy --dim {int(supercell_matrix[0][0])} "
+ f"{int(supercell_matrix[1][1])} "
+ f"{int(supercell_matrix[2][2])} -c --symprec "
+ f"{float(symprec)} --c2 10.0 -w 2"
+ )
+
+ pheasy_cmd_13 = (
+ f"pheasy --dim {int(supercell_matrix[0][0])} "
+ f"{int(supercell_matrix[1][1])} "
+ f"{int(supercell_matrix[2][2])} -w 2 -d --symprec "
+ f"{float(symprec)} --c2 10.0 "
+ f"--ndata {int(num_har)} --disp_file"
+ )
+
+ phonon.generate_displacements(distance=displacement)
+ disps = phonon.displacements
+ num_judge = len(disps)
+
+ if num_judge > 3:
+ pheasy_cmd_14 = (
+ f"pheasy --dim {int(supercell_matrix[0][0])} "
+ f"{int(supercell_matrix[1][1])} "
+ f"{int(supercell_matrix[2][2])} -f --c2 10.0 "
+ f"--full_ifc -w 2 --symprec {float(symprec)} "
+ f"-l LASSO --std --rasr BHH --ndata {int(num_har)}"
+ )
+
+ else:
+ pheasy_cmd_14 = (
+ f"pheasy --dim {int(supercell_matrix[0][0])} "
+ f"{int(supercell_matrix[1][1])} "
+ f"{int(supercell_matrix[2][2])} -f --full_ifc "
+ f"--c2 10.0 -w 2 --symprec {float(symprec)} "
+ f"--rasr BHH --ndata {int(num_har)}"
+ )
+
+ logger.info("Start running pheasy in cluster")
+
+ subprocess.call(shlex.split(pheasy_cmd_11))
+ subprocess.call(shlex.split(pheasy_cmd_12))
+ subprocess.call(shlex.split(pheasy_cmd_13))
+ subprocess.call(shlex.split(pheasy_cmd_14))
+
+ force_constants = parse_FORCE_CONSTANTS(filename="FORCE_CONSTANTS")
+ phonon.force_constants = force_constants
+ phonon.symmetrize_force_constants()
+
+ # with phonon.load("phonopy.yaml") the phonopy API can be used
+ phonon.save("phonopy.yaml")
+
+ # get phonon band structure
+ kpath_dict, kpath_concrete = cls.get_kpath(
+ structure=get_pmg_structure(phonon.primitive),
+ kpath_scheme=kpath_scheme,
+ symprec=symprec,
+ )
+
+ npoints_band = kwargs.get("npoints_band", 101)
+ qpoints, connections = get_band_qpoints_and_path_connections(
+ kpath_concrete, npoints=kwargs.get("npoints_band", 101)
+ )
+
+ # phonon band structures will always be cmouted
+ filename_band_yaml = "phonon_band_structure.yaml"
+ phonon.run_band_structure(
+ qpoints, path_connections=connections, with_eigenvectors=True
+ )
+ phonon.write_yaml_band_structure(filename=filename_band_yaml)
+ bs_symm_line = get_ph_bs_symm_line(
+ filename_band_yaml, labels_dict=kpath_dict, has_nac=born is not None
+ )
+ new_plotter = PhononBSPlotter(bs=bs_symm_line)
+
+ new_plotter.save_plot(
+ filename=kwargs.get("filename_bs", "phonon_band_structure.pdf"),
+ units=kwargs.get("units", "THz"),
+ )
+
+ imaginary_modes_cutoff = bs_symm_line.has_imaginary_freq(
+ tol=kwargs.get("tol_imaginary_modes", 1e-5)
+ )
+ imaginary_modes = imaginary_modes_cutoff
+ # new_plotter.save_plot(
+ # "phonon_band_structure.eps",
+ # img_format=kwargs.get("img_format", "eps"),
+ # units=kwargs.get("units", "THz"),
+ # )
+ else:
+ pass
+
+ # gets data for visualization on website - yaml is also enough
+ if kwargs.get("band_structure_eigenvectors"):
+ bs_symm_line.write_phononwebsite("phonon_website.json")
+
+ # get phonon density of states
+ filename_dos_yaml = "phonon_dos.yaml"
+
+ kpoint_density_dos = kwargs.get("kpoint_density_dos", 7_000)
+ kpoint = Kpoints.automatic_density(
+ structure=get_pmg_structure(phonon.primitive),
+ kppa=kpoint_density_dos,
+ force_gamma=True,
+ )
+ phonon.run_mesh(kpoint.kpts[0])
+ phonon.run_total_dos()
+ phonon.write_total_dos(filename=filename_dos_yaml)
+ dos = get_ph_dos(filename_dos_yaml)
+ new_plotter_dos = PhononDosPlotter()
+ new_plotter_dos.add_dos(label="total", dos=dos)
+ new_plotter_dos.save_plot(
+ filename=kwargs.get("filename_dos", "phonon_dos.pdf"),
+ units=kwargs.get("units", "THz"),
+ )
+
+ # compute vibrational part of free energies per formula unit
+ temperature_range = np.arange(
+ kwargs.get("tmin", 0), kwargs.get("tmax", 500), kwargs.get("tstep", 10)
+ )
+
+ free_energies = [
+ dos.helmholtz_free_energy(
+ temp=temp, structure=get_pmg_structure(phonon.primitive)
+ )
+ for temp in temperature_range
+ ]
+
+ entropies = [
+ dos.entropy(temp=temp, structure=get_pmg_structure(phonon.primitive))
+ for temp in temperature_range
+ ]
+
+ internal_energies = [
+ dos.internal_energy(
+ temp=temp, structure=get_pmg_structure(phonon.primitive)
+ )
+ for temp in temperature_range
+ ]
+
+ heat_capacities = [
+ dos.cv(temp=temp, structure=get_pmg_structure(phonon.primitive))
+ for temp in temperature_range
+ ]
+
+ # will compute thermal displacement matrices
+ # for the primitive cell (phonon.primitive!)
+ # only this is available in phonopy
+ if kwargs.get("create_thermal_displacements"):
+ phonon.run_mesh(
+ kpoint.kpts[0], with_eigenvectors=True, is_mesh_symmetry=False
+ )
+ freq_min_thermal_displacements = kwargs.get(
+ "freq_min_thermal_displacements", 0.0
+ )
+ phonon.run_thermal_displacement_matrices(
+ t_min=kwargs.get("tmin_thermal_displacements", 0),
+ t_max=kwargs.get("tmax_thermal_displacements", 500),
+ t_step=kwargs.get("tstep_thermal_displacements", 100),
+ freq_min=freq_min_thermal_displacements,
+ )
+
+ temperature_range_thermal_displacements = np.arange(
+ kwargs.get("tmin_thermal_displacements", 0),
+ kwargs.get("tmax_thermal_displacements", 500),
+ kwargs.get("tstep_thermal_displacements", 100),
+ )
+ for idx, temp in enumerate(temperature_range_thermal_displacements):
+ phonon.thermal_displacement_matrices.write_cif(
+ phonon.primitive, idx, filename=f"tdispmat_{temp}K.cif"
+ )
+ _disp_mat = phonon._thermal_displacement_matrices # noqa: SLF001
+ tdisp_mat = _disp_mat.thermal_displacement_matrices.tolist()
+
+ tdisp_mat_cif = _disp_mat.thermal_displacement_matrices_cif.tolist()
+
+ else:
+ tdisp_mat = None
+ tdisp_mat_cif = None
+
+ formula_units = (
+ structure.composition.num_atoms
+ / structure.composition.reduced_composition.num_atoms
+ )
+
+ total_dft_energy_per_formula_unit = (
+ total_dft_energy / formula_units if total_dft_energy is not None else None
+ )
+
+ return cls.from_structure(
+ structure=structure,
+ meta_structure=structure,
+ num_displaced_supercells=num_displaced_supercells,
+ displacement_anhar=displacement_anhar,
+ num_disp_anhar=num_disp_anhar,
+ phonon_bandstructure=bs_symm_line,
+ phonon_dos=dos,
+ free_energies=free_energies,
+ internal_energies=internal_energies,
+ heat_capacities=heat_capacities,
+ entropies=entropies,
+ temperatures=temperature_range.tolist(),
+ total_dft_energy=total_dft_energy_per_formula_unit,
+ has_imaginary_modes=imaginary_modes,
+ force_constants={"force_constants": phonon.force_constants.tolist()}
+ if kwargs["store_force_constants"]
+ else None,
+ born=borns.tolist() if borns is not None else None,
+ epsilon_static=epsilon.tolist() if epsilon is not None else None,
+ supercell_matrix=phonon.supercell_matrix.tolist(),
+ primitive_matrix=phonon.primitive_matrix.tolist(),
+ code=code,
+ mp_id=mp_id,
+ thermal_displacement_data={
+ "temperatures_thermal_displacements": temperature_range_thermal_displacements.tolist(), # noqa: E501
+ "thermal_displacement_matrix_cif": tdisp_mat_cif,
+ "thermal_displacement_matrix": tdisp_mat,
+ "freq_min_thermal_displacements": freq_min_thermal_displacements,
+ }
+ if kwargs.get("create_thermal_displacements")
+ else None,
+ jobdirs={
+ "displacements_job_dirs": displacement_data["dirs"],
+ "static_run_job_dir": kwargs["static_run_job_dir"],
+ "born_run_job_dir": kwargs["born_run_job_dir"],
+ "optimization_run_job_dir": kwargs["optimization_run_job_dir"],
+ "taskdoc_run_job_dir": str(Path.cwd()),
+ },
+ uuids={
+ "displacements_uuids": displacement_data["uuids"],
+ "born_run_uuid": kwargs["born_run_uuid"],
+ "optimization_run_uuid": kwargs["optimization_run_uuid"],
+ "static_run_uuid": kwargs["static_run_uuid"],
+ },
+ phonopy_settings={
+ "npoints_band": npoints_band,
+ "kpath_scheme": kpath_scheme,
+ "kpoint_density_dos": kpoint_density_dos,
+ },
+ )
+
+ @staticmethod
+ def get_kpath(
+ structure: Structure, kpath_scheme: str, symprec: float, **kpath_kwargs
+ ) -> tuple:
+ """Get high-symmetry points in k-space in phonopy format.
+
+ Parameters
+ ----------
+ structure: Structure Object
+ kpath_scheme: str
+ string describing kpath
+ symprec: float
+ precision for symmetry determination
+ **kpath_kwargs:
+ additional parameters that can be passed to this method as a dict
+ """
+ if kpath_scheme in ("setyawan_curtarolo", "latimer_munro", "hinuma"):
+ high_symm_kpath = HighSymmKpath(
+ structure, path_type=kpath_scheme, symprec=symprec, **kpath_kwargs
+ )
+ kpath = high_symm_kpath.kpath
+ elif kpath_scheme == "seekpath":
+ high_symm_kpath = KPathSeek(structure, symprec=symprec, **kpath_kwargs)
+ kpath = high_symm_kpath._kpath # noqa: SLF001
+ else:
+ raise ValueError(f"Unexpected {kpath_scheme=}")
+
+ path = copy.deepcopy(kpath["path"])
+
+ for set_idx, label_set in enumerate(kpath["path"]):
+ for lbl_idx, label in enumerate(label_set):
+ path[set_idx][lbl_idx] = kpath["kpoints"][label]
+ return kpath["kpoints"], path
diff --git a/src/atomate2/common/schemas/phonons.py b/src/atomate2/common/schemas/phonons.py
index 73b53c9e26..99e95c4c10 100644
--- a/src/atomate2/common/schemas/phonons.py
+++ b/src/atomate2/common/schemas/phonons.py
@@ -201,7 +201,7 @@ class PhononBSDOSDoc(StructureMetadata, extra="allow"): # type: ignore[call-arg
born: Optional[list[Matrix3D]] = Field(
None,
- description="born charges as computed from phonopy. Only for symmetrically "
+ description="Born charges as computed from phonopy. Only for symmetrically "
"different atoms",
)
@@ -280,7 +280,7 @@ def from_forces_born(
epsilon_static: Matrix3D
The high-frequency dielectric constant
born: Matrix3D
- born charges
+ Born charges
**kwargs:
additional arguments
"""
@@ -329,7 +329,7 @@ def from_forces_born(
)
else:
raise ValueError(
- "Number of born charges does not agree with number of atoms"
+ "Number of Born charges does not agree with number of atoms"
)
if code == "vasp" and not np.all(np.isclose(borns, 0.0)):
phonon.nac_params = {
@@ -595,7 +595,8 @@ def get_kpath(
**kpath_kwargs:
additional parameters that can be passed to this method as a dict
"""
- if kpath_scheme in ("setyawan_curtarolo", "latimer_munro", "hinuma"):
+ valid_schemes = {"setyawan_curtarolo", "latimer_munro", "hinuma", "seekpath"}
+ if kpath_scheme in (valid_schemes - {"seekpath"}):
high_symm_kpath = HighSymmKpath(
structure, path_type=kpath_scheme, symprec=symprec, **kpath_kwargs
)
@@ -604,7 +605,9 @@ def get_kpath(
high_symm_kpath = KPathSeek(structure, symprec=symprec, **kpath_kwargs)
kpath = high_symm_kpath._kpath # noqa: SLF001
else:
- raise ValueError(f"Unexpected {kpath_scheme=}")
+ raise ValueError(
+ f"Unexpected {kpath_scheme=}, must be one of {valid_schemes}"
+ )
path = copy.deepcopy(kpath["path"])
diff --git a/src/atomate2/common/schemas/qha.py b/src/atomate2/common/schemas/qha.py
index b0257d2142..bda924fbb5 100644
--- a/src/atomate2/common/schemas/qha.py
+++ b/src/atomate2/common/schemas/qha.py
@@ -4,6 +4,7 @@
from typing import Optional, Union
import numpy as np
+from emmet.core.math import Matrix3D
from emmet.core.structure import StructureMetadata
from phonopy.api_qha import PhonopyQHA
from pydantic import Field
@@ -88,6 +89,8 @@ class PhononQHADoc(StructureMetadata, extra="allow"): # type: ignore[call-arg]
)
formula_units: Optional[int] = Field(None, description="Formula units")
+ supercell_matrix: Optional[Matrix3D] = Field(None, description="Supercell matrix")
+
@classmethod
def from_phonon_runs(
cls,
@@ -98,6 +101,7 @@ def from_phonon_runs(
free_energies: list[list[float]],
heat_capacities: list[list[float]],
entropies: list[list[float]],
+ supercell_matrix: list[list[float]],
t_max: float = None,
pressure: float = None,
formula_units: Union[int, None] = None,
@@ -115,6 +119,7 @@ def from_phonon_runs(
free_energies: list of list of floats
heat_capacities: list of list of floats
entropies: list of list of floats
+ supercell_matrix: list of list of floats
t_max: float
pressure: float
eos_type: string
@@ -232,4 +237,5 @@ def from_phonon_runs(
heat_capacities=heat_capacities,
entropies=entropies,
formula_units=formula_units,
+ supercell_matrix=supercell_matrix,
)
diff --git a/src/atomate2/common/utils.py b/src/atomate2/common/utils.py
index 9110a37f4b..ce7e0c4da8 100644
--- a/src/atomate2/common/utils.py
+++ b/src/atomate2/common/utils.py
@@ -7,10 +7,79 @@
from typing import TYPE_CHECKING, Any
from monty.serialization import loadfn
+from pymatgen.transformations.advanced_transformations import (
+ CubicSupercellTransformation,
+)
if TYPE_CHECKING:
from pathlib import Path
+ from pymatgen.core.structure import Structure
+
+
+def get_supercell_matrix(
+ structure: Structure,
+ min_length: float,
+ max_length: float,
+ prefer_90_degrees: bool,
+ allow_orthorhombic: bool = False,
+ **kwargs,
+) -> list[list[float]]:
+ """
+ Determine supercell size with given min_length and max_length.
+
+ Parameters
+ ----------
+ structure: Structure Object
+ Input structure that will be used to determine supercell
+ min_length: float
+ minimum length of cell in Angstrom
+ max_length: float
+ maximum length of cell in Angstrom
+ prefer_90_degrees: bool
+ if True, the algorithm will try to find a cell with 90 degree angles first
+ allow_orthorhombic: bool
+ if True, orthorhombic supercells are allowed
+ **kwargs:
+ Additional parameters that can be set.
+ """
+ kwargs.setdefault("force_diagonal", False)
+ common_kwds = dict(
+ min_length=min_length,
+ max_length=max_length,
+ min_atoms=kwargs.get("min_atoms"),
+ max_atoms=kwargs.get("max_atoms"),
+ step_size=kwargs.get("step_size", 0.1),
+ force_diagonal=kwargs["force_diagonal"],
+ )
+ if not prefer_90_degrees:
+ transformation = CubicSupercellTransformation(
+ **common_kwds,
+ force_90_degrees=False,
+ allow_orthorhombic=allow_orthorhombic,
+ )
+ transformation.apply_transformation(structure=structure)
+ else:
+ try:
+ common_kwds.update({"max_atoms": kwargs.get("max_atoms", 1200)})
+ transformation = CubicSupercellTransformation(
+ **common_kwds,
+ force_90_degrees=True,
+ angle_tolerance=kwargs.get("angle_tolerance", 1e-2),
+ allow_orthorhombic=allow_orthorhombic,
+ )
+ transformation.apply_transformation(structure=structure)
+
+ except AttributeError:
+ transformation = CubicSupercellTransformation(
+ **common_kwds,
+ force_90_degrees=False,
+ allow_orthorhombic=allow_orthorhombic,
+ )
+ transformation.apply_transformation(structure=structure)
+ # matrix from pymatgen has to be transposed
+ return transformation.transformation_matrix.transpose().tolist()
+
def get_transformations(
transformations: tuple[str, ...], params: tuple[dict, ...] | None
diff --git a/src/atomate2/forcefields/__init__.py b/src/atomate2/forcefields/__init__.py
index e16c088654..59e25c1beb 100644
--- a/src/atomate2/forcefields/__init__.py
+++ b/src/atomate2/forcefields/__init__.py
@@ -3,6 +3,10 @@
from __future__ import annotations
from enum import Enum
+from typing import TYPE_CHECKING
+
+if TYPE_CHECKING:
+ from typing import Any
class MLFF(Enum): # TODO inherit from StrEnum when 3.11+
@@ -17,6 +21,16 @@ class MLFF(Enum): # TODO inherit from StrEnum when 3.11+
Nequip = "Nequip"
SevenNet = "SevenNet"
+ @classmethod
+ def _missing_(cls, value: Any) -> Any:
+ """Allow input of str(MLFF) as valid enum."""
+ if isinstance(value, str):
+ value = value.split("MLFF.")[-1]
+ for member in cls:
+ if member.value == value:
+ return member
+ return None
+
def _get_formatted_ff_name(force_field_name: str | MLFF) -> str:
"""
diff --git a/src/atomate2/forcefields/flows/elastic.py b/src/atomate2/forcefields/flows/elastic.py
index 597f5be22e..df2cc945cf 100644
--- a/src/atomate2/forcefields/flows/elastic.py
+++ b/src/atomate2/forcefields/flows/elastic.py
@@ -3,11 +3,24 @@
from __future__ import annotations
from dataclasses import dataclass, field
+from typing import TYPE_CHECKING
from atomate2 import SETTINGS
from atomate2.common.flows.elastic import BaseElasticMaker
+from atomate2.forcefields import MLFF, _get_formatted_ff_name
from atomate2.forcefields.jobs import ForceFieldRelaxMaker
+if TYPE_CHECKING:
+ from typing import Any
+
+ from typing_extensions import Self
+
+# default options for the forcefield makers in ElasticMaker
+_DEFAULT_RELAX_KWARGS: dict[str, Any] = {
+ "force_field_name": "CHGNet",
+ "relax_kwargs": {"fmax": 0.00001},
+}
+
@dataclass
class ElasticMaker(BaseElasticMaker):
@@ -62,16 +75,12 @@ class ElasticMaker(BaseElasticMaker):
symprec: float = SETTINGS.SYMPREC
bulk_relax_maker: ForceFieldRelaxMaker | None = field(
default_factory=lambda: ForceFieldRelaxMaker(
- force_field_name="CHGNet",
- relax_cell=True,
- relax_kwargs={"fmax": 0.00001},
+ relax_cell=True, **_DEFAULT_RELAX_KWARGS
)
)
elastic_relax_maker: ForceFieldRelaxMaker | None = field(
default_factory=lambda: ForceFieldRelaxMaker(
- force_field_name="CHGNet",
- relax_cell=False,
- relax_kwargs={"fmax": 0.00001},
+ relax_cell=False, **_DEFAULT_RELAX_KWARGS
)
) # constant volume relaxation
max_failed_deformations: int | float | None = None
@@ -89,3 +98,44 @@ def prev_calc_dir_argname(self) -> str | None:
Note: this is only applicable if a relax_maker is specified; i.e., two
calculations are performed for each ordering (relax -> static)
"""
+
+ @classmethod
+ def from_force_field_name(
+ cls,
+ force_field_name: str | MLFF,
+ mlff_kwargs: dict | None = None,
+ **kwargs,
+ ) -> Self:
+ """
+ Create an elastic flow from a forcefield name.
+
+ Parameters
+ ----------
+ force_field_name : str or .MLFF
+ The name of the force field.
+ mlff_kwargs : dict or None (default)
+ kwargs to pass to `ForceFieldRelaxMaker`.
+ **kwargs
+ Additional kwargs to pass to ElasticMaker.
+
+ Returns
+ -------
+ ElasticMaker
+ """
+ default_kwargs: dict[str, Any] = {
+ **_DEFAULT_RELAX_KWARGS,
+ **(mlff_kwargs or {}),
+ "force_field_name": _get_formatted_ff_name(force_field_name),
+ }
+ return cls(
+ name=f"{str(force_field_name).split('MLFF.')[-1]} elastic",
+ bulk_relax_maker=ForceFieldRelaxMaker(
+ relax_cell=True,
+ **default_kwargs,
+ ),
+ elastic_relax_maker=ForceFieldRelaxMaker(
+ relax_cell=False,
+ **default_kwargs,
+ ),
+ **kwargs,
+ )
diff --git a/src/atomate2/forcefields/flows/eos.py b/src/atomate2/forcefields/flows/eos.py
index 184498a0d6..21b39a75ec 100644
--- a/src/atomate2/forcefields/flows/eos.py
+++ b/src/atomate2/forcefields/flows/eos.py
@@ -1,4 +1,4 @@
-"""Flows to generate EOS fits using CHGNet, M3GNet, or MACE."""
+"""Flows to generate EOS fits using machine learned interatomic potentials."""
from __future__ import annotations
@@ -62,6 +62,7 @@ def from_force_field_name(
cls,
force_field_name: str | MLFF,
relax_initial_structure: bool = True,
+ **kwargs,
) -> Self:
"""
Create an EOS flow from a forcefield name.
@@ -72,6 +73,9 @@ def from_force_field_name(
The name of the force field.
relax_initial_structure: bool = True
Whether to relax the initial structure before performing an EOS fit.
+ **kwargs
+ Additional kwargs to pass to ElasticMaker
+
Returns
-------
@@ -89,6 +93,7 @@ def from_force_field_name(
force_field_name=force_field_name, relax_cell=False
),
static_maker=None,
+ **kwargs,
)
diff --git a/src/atomate2/forcefields/flows/pheasy.py b/src/atomate2/forcefields/flows/pheasy.py
new file mode 100644
index 0000000000..d9db491809
--- /dev/null
+++ b/src/atomate2/forcefields/flows/pheasy.py
@@ -0,0 +1,145 @@
+"""Flows for calculating phonons."""
+
+from __future__ import annotations
+
+from dataclasses import dataclass, field
+from typing import Literal
+
+from atomate2 import SETTINGS
+from atomate2.common.flows.pheasy import BasePhononMaker
+from atomate2.forcefields.jobs import ForceFieldRelaxMaker, ForceFieldStaticMaker
+
+
+@dataclass
+class PhononMaker(BasePhononMaker):
+ """
+ Maker to calculate harmonic phonons with a force field.
+
+ Calculate the harmonic phonons of a material. Initially, a tight structural
+ relaxation is performed to obtain a structure without forces on the atoms.
+ Subsequently, supercells with one displaced atom are generated and accurate
+ forces are computed for these structures. With the help of phonopy, these
+ forces are then converted into a dynamical matrix. To correct for polarization
+ effects, a correction of the dynamical matrix based on BORN charges can
+ be performed. The BORN charges can be supplied manually.
+ Finally, phonon densities of states, phonon band structures
+ and thermodynamic properties are computed.
+
+ .. Note::
+ It is heavily recommended to symmetrize the structure before passing it to
+ this flow. Otherwise, a different space group might be detected and too
+ many displacement calculations will be generated.
+ It is recommended to check the convergence parameters here and
+ adjust them if necessary. The default might not be strict enough
+ for your specific case.
+
+ Parameters
+ ----------
+ name : str
+ Name of the flows produced by this maker.
+ sym_reduce : bool
+ Whether to reduce the number of deformations using symmetry.
+ symprec : float
+ Symmetry precision to use in the
+ reduction of symmetry to find the primitive/conventional cell
+ (use_primitive_standard_structure, use_conventional_standard_structure)
+ and to handle all symmetry-related tasks in phonopy
+ displacement: float
+ displacement distance for phonons
+ min_length: float
+ min length of the supercell that will be built
+ prefer_90_degrees: bool
+ if set to True, supercell algorithm will first try to find a supercell
+ with 3 90 degree angles
+ get_supercell_size_kwargs: dict
+ kwargs that will be passed to get_supercell_size to determine supercell size
+ use_symmetrized_structure: str
+ allowed strings: "primitive", "conventional", None
+
+ - "primitive" will enforce to start the phonon computation
+ from the primitive standard structure
+ according to Setyawan, W., & Curtarolo, S. (2010).
+ High-throughput electronic band structure calculations:
+ Challenges and tools. Computational Materials Science,
+ 49(2), 299-312. doi:10.1016/j.commatsci.2010.05.010.
+ This makes it possible to use certain k-path definitions
+ with this workflow. Otherwise, we must rely on seekpath
+ - "conventional" will enforce to start the phonon computation
+ from the conventional standard structure
+ according to Setyawan, W., & Curtarolo, S. (2010).
+ High-throughput electronic band structure calculations:
+ Challenges and tools. Computational Materials Science,
+ 49(2), 299-312. doi:10.1016/j.commatsci.2010.05.010.
+ We will however use seekpath and primitive structures
+ as determined by from phonopy to compute the phonon band structure
+ bulk_relax_maker : .ForceFieldRelaxMaker or None
+ A maker to perform a tight relaxation on the bulk.
+ Set to ``None`` to skip the
+ bulk relaxation
+ static_energy_maker : .ForceFieldRelaxMaker or None
+ A maker to perform the computation of the DFT energy on the bulk.
+ Set to ``None`` to skip the
+ static energy computation
+ born_maker: .ForceFieldStaticMaker or None
+ Maker to compute the BORN charges.
+ phonon_displacement_maker : .ForceFieldStaticMaker or None
+ Maker used to compute the forces for a supercell.
+ generate_frequencies_eigenvectors_kwargs : dict
+ Keyword arguments passed to :obj:`generate_frequencies_eigenvectors`.
+ create_thermal_displacements: bool
+ Bool that determines if thermal_displacement_matrices are computed
+ kpath_scheme: str
+ scheme to generate kpoints. Please be aware that
+ you can only use seekpath with any kind of cell
+ Otherwise, please use the standard primitive structure
+ Available schemes are:
+ "seekpath", "hinuma", "setyawan_curtarolo", "latimer_munro".
+ "seekpath" and "hinuma" are the same definition but
+ seekpath can be used with any kind of unit cell as
+ it relies on phonopy to handle the relationship
+ to the primitive cell and not pymatgen
+ code: str
+ determines the dft or force field code.
+ store_force_constants: bool
+ if True, force constants will be stored
+ socket: bool
+ If True, use the socket for the calculation
+ """
+
+ name: str = "phonon"
+ sym_reduce: bool = True
+ symprec: float = SETTINGS.PHONON_SYMPREC
+ displacement: float = 0.01
+ min_length: float | None = 20.0
+ prefer_90_degrees: bool = True
+ get_supercell_size_kwargs: dict = field(default_factory=dict)
+ use_symmetrized_structure: Literal["primitive", "conventional"] | None = None
+ bulk_relax_maker: ForceFieldRelaxMaker | None = field(
+ default_factory=lambda: ForceFieldRelaxMaker(
+ force_field_name="MACE", relax_kwargs={"fmax": 0.00001}
+ )
+ )
+ static_energy_maker: ForceFieldStaticMaker | None = field(
+ default_factory=lambda: ForceFieldStaticMaker(force_field_name="MACE")
+ )
+ phonon_displacement_maker: ForceFieldStaticMaker = field(
+ default_factory=lambda: ForceFieldStaticMaker(force_field_name="MACE")
+ )
+ create_thermal_displacements: bool = False
+ generate_frequencies_eigenvectors_kwargs: dict = field(default_factory=dict)
+ kpath_scheme: str = "seekpath"
+ store_force_constants: bool = True
+ code: str = "forcefields"
+ born_maker: ForceFieldStaticMaker | None = None
+
+ @property
+ def prev_calc_dir_argname(self) -> None:
+ """Name of argument informing static maker of previous calculation directory.
+
+ As this differs between different DFT codes (e.g., VASP, CP2K), it
+ has been left as a property to be implemented by the inheriting class.
+
+ Note: this is only applicable if a relax_maker is specified; i.e., two
+ calculations are performed for each ordering (relax -> static)
+ """
+ return
diff --git a/src/atomate2/forcefields/flows/phonons.py b/src/atomate2/forcefields/flows/phonons.py
index 7dac0626c0..24e0529880 100644
--- a/src/atomate2/forcefields/flows/phonons.py
+++ b/src/atomate2/forcefields/flows/phonons.py
@@ -48,6 +48,8 @@ class PhononMaker(BasePhononMaker):
displacement distance for phonons
min_length: float
min length of the supercell that will be built
+ max_length: float
+ max length of the supercell that will be built
prefer_90_degrees: bool
if set to True, supercell algorithm will first try to find a supercell
with 3 90 degree angles
@@ -112,6 +114,7 @@ class PhononMaker(BasePhononMaker):
displacement: float = 0.01
min_length: float | None = 20.0
prefer_90_degrees: bool = True
+ max_length: float | None = None
get_supercell_size_kwargs: dict = field(default_factory=dict)
use_symmetrized_structure: Literal["primitive", "conventional"] | None = None
bulk_relax_maker: ForceFieldRelaxMaker | None = field(
diff --git a/src/atomate2/forcefields/flows/qha.py b/src/atomate2/forcefields/flows/qha.py
index f742eafc06..da93c2e79c 100644
--- a/src/atomate2/forcefields/flows/qha.py
+++ b/src/atomate2/forcefields/flows/qha.py
@@ -47,7 +47,15 @@ class CHGNetQhaMaker(CommonQhaMaker):
will be ignored
eos_type: supported_eos
Equation of State type used for the fitting. Defaults to vinet.
-
+ min_length: float
+ min length of the supercell that will be built
+ max_length: float
+ max length of the supercell that will be built
+ prefer_90_degrees: bool
+ if set to True, supercell algorithm will first try to find a supercell
+ with 3 90 degree angles
+ get_supercell_size_kwargs: dict
+ kwargs that will be passed to get_supercell_size to determine supercell size
"""
diff --git a/src/atomate2/forcefields/schemas.py b/src/atomate2/forcefields/schemas.py
index a66785b8b8..2d448d92ee 100644
--- a/src/atomate2/forcefields/schemas.py
+++ b/src/atomate2/forcefields/schemas.py
@@ -22,7 +22,7 @@ class ForcefieldResult(AseResult):
None, description="The structure in the final trajectory frame."
)
- def model_post_init(self, __context: Any) -> None:
+ def model_post_init(self, _context: Any) -> None:
"""Populate final_structure attr."""
self.final_structure = getattr(
self, "final_structure", self.final_mol_or_struct
diff --git a/src/atomate2/vasp/flows/pheasy.py b/src/atomate2/vasp/flows/pheasy.py
new file mode 100644
index 0000000000..74841c6071
--- /dev/null
+++ b/src/atomate2/vasp/flows/pheasy.py
@@ -0,0 +1,158 @@
+"""Define the VASP PhononMaker."""
+
+from __future__ import annotations
+
+from dataclasses import dataclass, field
+from typing import TYPE_CHECKING
+
+from atomate2 import SETTINGS
+from atomate2.common.flows.pheasy import BasePhononMaker
+from atomate2.vasp.flows.core import DoubleRelaxMaker
+from atomate2.vasp.jobs.core import DielectricMaker, StaticMaker, TightRelaxMaker
+from atomate2.vasp.jobs.phonons import PhononDisplacementMaker
+from atomate2.vasp.sets.core import StaticSetGenerator
+
+if TYPE_CHECKING:
+ from atomate2.vasp.jobs.base import BaseVaspMaker
+
+
+@dataclass
+class PhononMaker(BasePhononMaker):
+ """
+ Maker to calculate harmonic phonons with VASP and Phonopy.
+
+ Calculate the harmonic phonons of a material. Initially, a tight structural
+ relaxation is performed to obtain a structure without forces on the atoms.
+ Subsequently, supercells with one displaced atom are generated and accurate
+ forces are computed for these structures. With the help of phonopy, these
+ forces are then converted into a dynamical matrix. To correct for polarization
+ effects, a correction of the dynamical matrix based on BORN charges can
+ be performed. Finally, phonon densities of states, phonon band structures
+ and thermodynamic properties are computed.
+
+ .. Note::
+ It is heavily recommended to symmetrize the structure before passing it to
+ this flow. Otherwise, a different space group might be detected and too
+ many displacement calculations will be generated.
+ It is recommended to check the convergence parameters here and
+ adjust them if necessary. The default might not be strict enough
+ for your specific case.
+
+ Parameters
+ ----------
+ name : str = "phonon"
+ Name of the flows produced by this maker.
+ sym_reduce : bool = True
+ Whether to reduce the number of deformations using symmetry.
+ symprec : float = 1e-4
+ Symmetry precision to use in the
+ reduction of symmetry to find the primitive/conventional cell
+ (use_primitive_standard_structure, use_conventional_standard_structure)
+ and to handle all symmetry-related tasks in phonopy
+ displacement: float = 0.01
+ displacement distance for phonons
+ min_length: float = 20.0
+ min length of the supercell that will be built
+ prefer_90_degrees: bool = True
+ if set to True, supercell algorithm will first try to find a supercell
+ with 3 90 degree angles
+ get_supercell_size_kwargs: dict = {}
+ kwargs that will be passed to get_supercell_size to determine supercell size
+ use_symmetrized_structure: str or None = None
+ allowed strings: "primitive", "conventional", None
+
+ - "primitive" will enforce to start the phonon computation
+ from the primitive standard structure
+ according to Setyawan, W., & Curtarolo, S. (2010).
+ High-throughput electronic band structure calculations:
+ Challenges and tools. Computational Materials Science,
+ 49(2), 299-312. doi:10.1016/j.commatsci.2010.05.010.
+ This makes it possible to use certain k-path definitions
+ with this workflow. Otherwise, we must rely on seekpath
+ - "conventional" will enforce to start the phonon computation
+ from the conventional standard structure
+ according to Setyawan, W., & Curtarolo, S. (2010).
+ High-throughput electronic band structure calculations:
+ Challenges and tools. Computational Materials Science,
+ 49(2), 299-312. doi:10.1016/j.commatsci.2010.05.010.
+ We will however use seekpath and primitive structures
+ as determined by from phonopy to compute the phonon band structure
+ bulk_relax_maker : .BaseVaspMaker or None
+ A maker to perform a tight relaxation on the bulk.
+ Set to ``None`` to skip the
+ bulk relaxation
+ static_energy_maker : .BaseVaspMaker or None
+ A maker to perform the computation of the DFT energy on the bulk.
+ Set to ``None`` to skip the
+ static energy computation
+ born_maker: .BaseVaspMaker or None
+ Maker to compute the BORN charges.
+ phonon_displacement_maker : .BaseVaspMaker or None
+ Maker used to compute the forces for a supercell.
+ generate_frequencies_eigenvectors_kwargs : dict
+ Keyword arguments passed to :obj:`generate_frequencies_eigenvectors`.
+ create_thermal_displacements: bool
+ Bool that determines if thermal_displacement_matrices are computed
+ kpath_scheme: str = "seekpath"
+ scheme to generate kpoints. Please be aware that
+ you can only use seekpath with any kind of cell
+ Otherwise, please use the standard primitive structure
+ Available schemes are:
+ "seekpath", "hinuma", "setyawan_curtarolo", "latimer_munro".
+ "seekpath" and "hinuma" are the same definition but
+ seekpath can be used with any kind of unit cell as
+ it relies on phonopy to handle the relationship
+ to the primitive cell and not pymatgen
+ code: str = "vasp"
+ determines the DFT code. currently only vasp is implemented.
+ This keyword might enable the implementation of other codes
+ in the future
+ store_force_constants: bool
+ if True, force constants will be stored
+ socket: bool
+ If True, use the socket for the calculation
+ """
+
+ name: str = "phonon"
+ sym_reduce: bool = True
+ symprec: float = SETTINGS.PHONON_SYMPREC
+ cal_anhar_fcs: bool = False
+ displacement: float = 0.01
+ displacement_anhar: float = 0.08
+ num_displaced_supercells: int = 0
+ num_disp_anhar: int = 0
+ fcs_cutoff_radius: list = field(default_factory=lambda: [-1, 12, 10])
+ min_length: float | None = 12.0
+ prefer_90_degrees: bool = True
+ get_supercell_size_kwargs: dict = field(default_factory=dict)
+ use_symmetrized_structure: str | None = None
+ create_thermal_displacements: bool = True
+ generate_frequencies_eigenvectors_kwargs: dict = field(default_factory=dict)
+ kpath_scheme: str = "seekpath"
+ store_force_constants: bool = True
+ socket: bool = False
+ code: str = "vasp"
+ bulk_relax_maker: BaseVaspMaker | None = field(
+ default_factory=lambda: DoubleRelaxMaker.from_relax_maker(TightRelaxMaker())
+ )
+ static_energy_maker: BaseVaspMaker | None = field(
+ default_factory=lambda: StaticMaker(
+ input_set_generator=StaticSetGenerator(auto_ispin=True)
+ )
+ )
+ born_maker: BaseVaspMaker | None = field(default_factory=DielectricMaker)
+ phonon_displacement_maker: BaseVaspMaker = field(
+ default_factory=PhononDisplacementMaker
+ )
+
+ @property
+ def prev_calc_dir_argname(self) -> str:
+ """Name of argument informing static maker of previous calculation directory.
+
+ As this differs between different DFT codes (e.g., VASP, CP2K), it
+ has been left as a property to be implemented by the inheriting class.
+
+ Note: this is only applicable if a relax_maker is specified; i.e., two
+ calculations are performed for each ordering (relax -> static)
+ """
+ return "prev_dir"
diff --git a/src/atomate2/vasp/flows/phonons.py b/src/atomate2/vasp/flows/phonons.py
index 7bfc5c0700..155547c043 100644
--- a/src/atomate2/vasp/flows/phonons.py
+++ b/src/atomate2/vasp/flows/phonons.py
@@ -55,6 +55,8 @@ class PhononMaker(BasePhononMaker):
displacement distance for phonons
min_length: float = 20.0
min length of the supercell that will be built
+ max_length: float
+ max length of the supercell that will be built
prefer_90_degrees: bool = True
if set to True, supercell algorithm will first try to find a supercell
with 3 90 degree angles
@@ -120,6 +122,7 @@ class PhononMaker(BasePhononMaker):
symprec: float = SETTINGS.PHONON_SYMPREC
displacement: float = 0.01
min_length: float | None = 20.0
+ max_length: float | None = None
prefer_90_degrees: bool = True
get_supercell_size_kwargs: dict = field(default_factory=dict)
use_symmetrized_structure: Literal["primitive", "conventional"] | None = None
diff --git a/src/atomate2/vasp/flows/qha.py b/src/atomate2/vasp/flows/qha.py
index 69d6edd17e..2b9742d454 100644
--- a/src/atomate2/vasp/flows/qha.py
+++ b/src/atomate2/vasp/flows/qha.py
@@ -49,7 +49,15 @@ class QhaMaker(CommonQhaMaker):
will be ignored
eos_type: supported_eos
Equation of State type used for the fitting. Defaults to vinet.
-
+ min_length: float
+ min length of the supercell that will be built
+ max_length: float
+ max length of the supercell that will be built
+ prefer_90_degrees: bool
+ if set to True, supercell algorithm will first try to find a supercell
+ with 3 90 degree angles
+ get_supercell_size_kwargs: dict
+ kwargs that will be passed to get_supercell_size to determine supercell size
"""
diff --git a/tests/aims/test_flows/test_eos.py b/tests/aims/test_flows/test_eos.py
index e35476f069..bf31152ad1 100644
--- a/tests/aims/test_flows/test_eos.py
+++ b/tests/aims/test_flows/test_eos.py
@@ -100,5 +100,5 @@ def test_eos_from_parameters(mock_aims, tmp_path, si, species_dir):
assert len(output["relax"]["energy"]) == 5
# the initial calculation also participates in the fit here
assert output["relax"]["EOS"]["birch_murnaghan"]["b0"] == pytest.approx(
- 0.5189578108402951
+ 0.5189578108402951, rel=1e-3
)
diff --git a/tests/common/jobs/test_gruneisen.py b/tests/common/jobs/test_gruneisen.py
index b93169968c..a4cbd95247 100644
--- a/tests/common/jobs/test_gruneisen.py
+++ b/tests/common/jobs/test_gruneisen.py
@@ -53,8 +53,8 @@ def test_compute_gruneisen_param(tmp_dir, test_dir):
"minus": False,
}
assert gp_doc.derived_properties.average_gruneisen == pytest.approx(
- 1.1882292157682082
+ 1.1203420586842452, abs=1e-2
)
assert gp_doc.derived_properties.thermal_conductivity_slack == pytest.approx(
- 38.861289530152796
+ 44.078885068152346, abs=1e-2
)
diff --git a/tests/common/jobs/test_phonons.py b/tests/common/jobs/test_phonons.py
new file mode 100644
index 0000000000..9b49364600
--- /dev/null
+++ b/tests/common/jobs/test_phonons.py
@@ -0,0 +1,37 @@
+from jobflow import run_locally
+from numpy.testing import assert_allclose
+
+from atomate2.common.jobs.phonons import get_supercell_size
+
+
+def test_supercell(si_structure, tmp_dir):
+ supercell = get_supercell_size(
+ si_structure, min_length=10, max_length=None, prefer_90_degrees=False
+ )
+
+ responses = run_locally(supercell, create_folders=True, ensure_success=True)
+ assert_allclose(
+ responses[supercell.output.uuid][1].output, [[3, -1, 0], [0, 4, 0], [-2, -1, 3]]
+ )
+
+
+def test_supercell1(si_structure, tmp_dir):
+ supercell = get_supercell_size(
+ si_structure, min_length=8, max_length=13, prefer_90_degrees=True
+ )
+
+ responses = run_locally(supercell, create_folders=True, ensure_success=True)
+ assert_allclose(
+ responses[supercell.output.uuid][1].output, [[3, -1, 0], [0, 3, 0], [-1, -1, 3]]
+ )
+
+
+def test_supercell2(si_structure, tmp_dir):
+ supercell = get_supercell_size(
+ si_structure, min_length=8, max_length=20, prefer_90_degrees=True
+ )
+
+ responses = run_locally(supercell, create_folders=True, ensure_success=True)
+ assert_allclose(
+ responses[supercell.output.uuid][1].output, [[6, -2, 0], [0, 6, 0], [-3, -2, 5]]
+ )
diff --git a/tests/common/schemas/test_pheasy.py b/tests/common/schemas/test_pheasy.py
new file mode 100644
index 0000000000..6b91914097
--- /dev/null
+++ b/tests/common/schemas/test_pheasy.py
@@ -0,0 +1,60 @@
+import json
+
+import numpy as np
+import pytest
+from monty.json import MontyEncoder
+from pydantic import ValidationError
+
+from atomate2.common.schemas.pheasy import (
+ PhononBSDOSDoc,
+ PhononComputationalSettings,
+ PhononJobDirs,
+ PhononUUIDs,
+ ThermalDisplacementData,
+)
+
+
+def test_thermal_displacement_data():
+ doc = ThermalDisplacementData(freq_min_thermal_displacements=0.0)
+ validated = ThermalDisplacementData.model_validate_json(
+ json.dumps(doc, cls=MontyEncoder)
+ )
+ assert isinstance(validated, ThermalDisplacementData)
+
+
+def test_phonon_bs_dos_doc():
+ kwargs = {
+ "total_dft_energy": None,
+ "supercell_matrix": np.eye(3),
+ "primitive_matrix": np.eye(3),
+ "code": "test",
+ "phonopy_settings": PhononComputationalSettings(
+ npoints_band=1, kpath_scheme="test", kpoint_density_dos=1
+ ),
+ "thermal_displacement_data": None,
+ "jobdirs": None,
+ "uuids": None,
+ }
+ doc = PhononBSDOSDoc(**kwargs)
+ # check validation raises no errors
+ validated = PhononBSDOSDoc.model_validate_json(json.dumps(doc, cls=MontyEncoder))
+ assert isinstance(validated, PhononBSDOSDoc)
+
+ # test invalid supercell_matrix type fails
+ with pytest.raises(ValidationError):
+ doc = PhononBSDOSDoc(**kwargs | {"supercell_matrix": (1, 1, 1)})
+
+ # test optional material_id
+ doc = PhononBSDOSDoc(**kwargs | {"material_id": 1234})
+ assert doc.material_id == 1234
+
+ # test extra="allow" option
+ doc = PhononBSDOSDoc(**kwargs | {"extra_field": "test"})
+ assert doc.extra_field == "test"
+
+
+# schemas where all fields have default values
+@pytest.mark.parametrize("model_cls", [PhononJobDirs, PhononUUIDs])
+def test_model_validate(model_cls):
+ validated = model_cls.model_validate_json(json.dumps(model_cls(), cls=MontyEncoder))
+ assert isinstance(validated, model_cls)
diff --git a/tests/forcefields/flows/test_elastic.py b/tests/forcefields/flows/test_elastic.py
index 1a64df2673..ac30000017 100644
--- a/tests/forcefields/flows/test_elastic.py
+++ b/tests/forcefields/flows/test_elastic.py
@@ -7,7 +7,10 @@
from atomate2.forcefields.jobs import ForceFieldRelaxMaker
-def test_elastic_wf_with_mace(clean_dir, si_structure, test_dir):
+@pytest.mark.parametrize("convenience_constructor", [True, False])
+def test_elastic_wf_with_mace(
+ clean_dir, si_structure, test_dir, convenience_constructor: bool
+):
si_prim = SpacegroupAnalyzer(si_structure).get_primitive_standard_structure()
model_path = f"{test_dir}/forcefields/mace/MACE.model"
common_kwds = {
@@ -16,10 +19,17 @@ def test_elastic_wf_with_mace(clean_dir, si_structure, test_dir):
"relax_kwargs": {"fmax": 0.00001},
}
- flow = ElasticMaker(
- bulk_relax_maker=ForceFieldRelaxMaker(**common_kwds, relax_cell=True),
- elastic_relax_maker=ForceFieldRelaxMaker(**common_kwds, relax_cell=False),
- ).make(si_prim)
+ if convenience_constructor:
+ common_kwds.pop("force_field_name")
+ flow = ElasticMaker.from_force_field_name(
+ force_field_name="MACE",
+ mlff_kwargs=common_kwds,
+ ).make(si_prim)
+ else:
+ flow = ElasticMaker(
+ bulk_relax_maker=ForceFieldRelaxMaker(**common_kwds, relax_cell=True),
+ elastic_relax_maker=ForceFieldRelaxMaker(**common_kwds, relax_cell=False),
+ ).make(si_prim)
# run the flow or job and ensure that it finished running successfully
responses = run_locally(flow, create_folders=True, ensure_success=True)
diff --git a/tests/forcefields/flows/test_qha.py b/tests/forcefields/flows/test_qha.py
index eeb361e837..ffa1037189 100644
--- a/tests/forcefields/flows/test_qha.py
+++ b/tests/forcefields/flows/test_qha.py
@@ -1,6 +1,8 @@
from pathlib import Path
+import pytest
from jobflow import run_locally
+from numpy.testing import assert_allclose
from pymatgen.core.structure import Structure
from atomate2.common.schemas.qha import PhononQHADoc
@@ -14,8 +16,8 @@ def test_qha_dir(clean_dir, si_structure: Structure, tmp_path: Path):
flow = CHGNetQhaMaker(
number_of_frames=5,
ignore_imaginary_modes=True,
+ min_length=10,
phonon_maker=PhononMaker(
- min_length=10,
store_force_constants=False,
bulk_relax_maker=None,
generate_frequencies_eigenvectors_kwargs={
@@ -33,3 +35,66 @@ def test_qha_dir(clean_dir, si_structure: Structure, tmp_path: Path):
# # validate the outputs
ph_bs_dos_doc = responses[flow[-1].uuid][1].output
assert isinstance(ph_bs_dos_doc, PhononQHADoc)
+
+
+def test_qha_dir_change_defaults(clean_dir, si_structure: Structure, tmp_path: Path):
+ # TODO brittle due to inability to adjust dtypes in CHGNetRelaxMaker
+
+ flow = CHGNetQhaMaker(
+ number_of_frames=4,
+ ignore_imaginary_modes=True,
+ linear_strain=(-0.03, 0.03),
+ min_length=10,
+ phonon_maker=PhononMaker(
+ store_force_constants=False,
+ bulk_relax_maker=None,
+ generate_frequencies_eigenvectors_kwargs={
+ "tol_imaginary_modes": 5e-1,
+ "tmin": 0,
+ "tmax": 1000,
+ "tstep": 10,
+ },
+ ),
+ ).make(si_structure)
+
+ # run the flow or job and ensure that it finished running successfully
+ responses = run_locally(flow, create_folders=True, ensure_success=True)
+
+ # print(responses)
+
+ # # validate the outputs
+ ph_bs_dos_doc = responses[flow[-1].uuid][1].output
+ assert isinstance(ph_bs_dos_doc, PhononQHADoc)
+ assert len(ph_bs_dos_doc.volumes) == 5
+ assert ph_bs_dos_doc.volumes[0] == pytest.approx(ph_bs_dos_doc.volumes[2] * 0.97**3)
+ assert ph_bs_dos_doc.volumes[4] == pytest.approx(ph_bs_dos_doc.volumes[2] * 1.03**3)
+
+
+def test_qha_dir_manual_supercell(clean_dir, si_structure: Structure, tmp_path: Path):
+ # TODO brittle due to inability to adjust dtypes in CHGNetRelaxMaker
+ matrix = [[2.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]]
+ flow = CHGNetQhaMaker(
+ number_of_frames=4,
+ ignore_imaginary_modes=True,
+ min_length=10,
+ phonon_maker=PhononMaker(
+ store_force_constants=False,
+ bulk_relax_maker=None,
+ generate_frequencies_eigenvectors_kwargs={
+ "tol_imaginary_modes": 5e-1,
+ "tmin": 0,
+ "tmax": 1000,
+ "tstep": 10,
+ },
+ ),
+ ).make(si_structure, supercell_matrix=matrix)
+
+ # run the flow or job and ensure that it finished running successfully
+ responses = run_locally(flow, create_folders=True, ensure_success=True)
+
+ # print(responses)
+
+ # # validate the outputs
+ ph_bs_dos_doc = responses[flow[-1].uuid][1].output
+ assert isinstance(ph_bs_dos_doc, PhononQHADoc)
+ assert_allclose(ph_bs_dos_doc.supercell_matrix, matrix)
diff --git a/tests/forcefields/test_utils.py b/tests/forcefields/test_utils.py
index 023482ea2d..b43eb6ff06 100644
--- a/tests/forcefields/test_utils.py
+++ b/tests/forcefields/test_utils.py
@@ -4,6 +4,12 @@
from atomate2.forcefields.utils import ase_calculator
+@pytest.mark.parametrize(("force_field"), [mlff.value for mlff in MLFF])
+def test_mlff(force_field: str):
+ mlff = MLFF(force_field)
+ assert mlff == MLFF(str(mlff)) == MLFF(str(mlff).split(".")[-1])
+
+
@pytest.mark.parametrize(("force_field"), ["CHGNet", "MACE"])
def test_ext_load(force_field: str):
decode_dict = {
diff --git a/tests/test_data/vasp/Si_pheasy/dielectric/inputs/INCAR.gz b/tests/test_data/vasp/Si_pheasy/dielectric/inputs/INCAR.gz
new file mode 100644
index 0000000000..df8dbd7760
Binary files /dev/null and b/tests/test_data/vasp/Si_pheasy/dielectric/inputs/INCAR.gz differ
diff --git a/tests/test_data/vasp/Si_pheasy/dielectric/inputs/INCAR.orig.gz b/tests/test_data/vasp/Si_pheasy/dielectric/inputs/INCAR.orig.gz
new file mode 100644
index 0000000000..9f1c1ad5e8
Binary files /dev/null and b/tests/test_data/vasp/Si_pheasy/dielectric/inputs/INCAR.orig.gz differ
diff --git a/tests/test_data/vasp/Si_pheasy/dielectric/inputs/POSCAR.gz b/tests/test_data/vasp/Si_pheasy/dielectric/inputs/POSCAR.gz
new file mode 100644
index 0000000000..a393c90ee2
Binary files /dev/null and b/tests/test_data/vasp/Si_pheasy/dielectric/inputs/POSCAR.gz differ
diff --git a/tests/test_data/vasp/Si_pheasy/dielectric/inputs/POSCAR.orig.gz b/tests/test_data/vasp/Si_pheasy/dielectric/inputs/POSCAR.orig.gz
new file mode 100644
index 0000000000..9b2f5f8175
Binary files /dev/null and b/tests/test_data/vasp/Si_pheasy/dielectric/inputs/POSCAR.orig.gz differ
diff --git a/tests/test_data/vasp/Si_phonons_4/dielectric/inputs/POTCAR.spec.gz b/tests/test_data/vasp/Si_pheasy/dielectric/inputs/POTCAR.spec.gz
similarity index 100%
rename from tests/test_data/vasp/Si_phonons_4/dielectric/inputs/POTCAR.spec.gz
rename to tests/test_data/vasp/Si_pheasy/dielectric/inputs/POTCAR.spec.gz
diff --git a/tests/test_data/vasp/Si_pheasy/dielectric/outputs/CONTCAR.gz b/tests/test_data/vasp/Si_pheasy/dielectric/outputs/CONTCAR.gz
new file mode 100644
index 0000000000..c84d13a711
Binary files /dev/null and b/tests/test_data/vasp/Si_pheasy/dielectric/outputs/CONTCAR.gz differ
diff --git a/tests/test_data/vasp/Si_pheasy/dielectric/outputs/INCAR.gz b/tests/test_data/vasp/Si_pheasy/dielectric/outputs/INCAR.gz
new file mode 100644
index 0000000000..df8dbd7760
Binary files /dev/null and b/tests/test_data/vasp/Si_pheasy/dielectric/outputs/INCAR.gz differ
diff --git a/tests/test_data/vasp/Si_pheasy/dielectric/outputs/INCAR.orig.gz b/tests/test_data/vasp/Si_pheasy/dielectric/outputs/INCAR.orig.gz
new file mode 100644
index 0000000000..9f1c1ad5e8
Binary files /dev/null and b/tests/test_data/vasp/Si_pheasy/dielectric/outputs/INCAR.orig.gz differ
diff --git a/tests/test_data/vasp/Si_pheasy/dielectric/outputs/OUTCAR.gz b/tests/test_data/vasp/Si_pheasy/dielectric/outputs/OUTCAR.gz
new file mode 100644
index 0000000000..8037e1d4fd
Binary files /dev/null and b/tests/test_data/vasp/Si_pheasy/dielectric/outputs/OUTCAR.gz differ
diff --git a/tests/test_data/vasp/Si_pheasy/dielectric/outputs/POSCAR.gz b/tests/test_data/vasp/Si_pheasy/dielectric/outputs/POSCAR.gz
new file mode 100644
index 0000000000..a393c90ee2
Binary files /dev/null and b/tests/test_data/vasp/Si_pheasy/dielectric/outputs/POSCAR.gz differ
diff --git a/tests/test_data/vasp/Si_pheasy/dielectric/outputs/POSCAR.orig.gz b/tests/test_data/vasp/Si_pheasy/dielectric/outputs/POSCAR.orig.gz
new file mode 100644
index 0000000000..9b2f5f8175
Binary files /dev/null and b/tests/test_data/vasp/Si_pheasy/dielectric/outputs/POSCAR.orig.gz differ
diff --git a/tests/test_data/vasp/Si_phonons_4/dielectric/outputs/POTCAR.spec.gz b/tests/test_data/vasp/Si_pheasy/dielectric/outputs/POTCAR.spec.gz
similarity index 100%
rename from tests/test_data/vasp/Si_phonons_4/dielectric/outputs/POTCAR.spec.gz
rename to tests/test_data/vasp/Si_pheasy/dielectric/outputs/POTCAR.spec.gz
diff --git a/tests/test_data/vasp/Si_pheasy/dielectric/outputs/vasprun.xml.gz b/tests/test_data/vasp/Si_pheasy/dielectric/outputs/vasprun.xml.gz
new file mode 100644
index 0000000000..652ee17466
Binary files /dev/null and b/tests/test_data/vasp/Si_pheasy/dielectric/outputs/vasprun.xml.gz differ
diff --git a/tests/test_data/vasp/Si_pheasy/phonon_static_1_2/inputs/INCAR.gz b/tests/test_data/vasp/Si_pheasy/phonon_static_1_2/inputs/INCAR.gz
new file mode 100644
index 0000000000..933bcccb96
Binary files /dev/null and b/tests/test_data/vasp/Si_pheasy/phonon_static_1_2/inputs/INCAR.gz differ
diff --git a/tests/test_data/vasp/Si_pheasy/phonon_static_1_2/inputs/INCAR.orig.gz b/tests/test_data/vasp/Si_pheasy/phonon_static_1_2/inputs/INCAR.orig.gz
new file mode 100644
index 0000000000..98ee85e2d8
Binary files /dev/null and b/tests/test_data/vasp/Si_pheasy/phonon_static_1_2/inputs/INCAR.orig.gz differ
diff --git a/tests/test_data/vasp/Si_pheasy/phonon_static_1_2/inputs/KPOINTS.gz b/tests/test_data/vasp/Si_pheasy/phonon_static_1_2/inputs/KPOINTS.gz
new file mode 100644
index 0000000000..6024584b1d
Binary files /dev/null and b/tests/test_data/vasp/Si_pheasy/phonon_static_1_2/inputs/KPOINTS.gz differ
diff --git a/tests/test_data/vasp/Si_pheasy/phonon_static_1_2/inputs/KPOINTS.orig.gz b/tests/test_data/vasp/Si_pheasy/phonon_static_1_2/inputs/KPOINTS.orig.gz
new file mode 100644
index 0000000000..2ef5ae4de7
Binary files /dev/null and b/tests/test_data/vasp/Si_pheasy/phonon_static_1_2/inputs/KPOINTS.orig.gz differ
diff --git a/tests/test_data/vasp/Si_pheasy/phonon_static_1_2/inputs/POSCAR.gz b/tests/test_data/vasp/Si_pheasy/phonon_static_1_2/inputs/POSCAR.gz
new file mode 100644
index 0000000000..bd6627e23d
Binary files /dev/null and b/tests/test_data/vasp/Si_pheasy/phonon_static_1_2/inputs/POSCAR.gz differ
diff --git a/tests/test_data/vasp/Si_pheasy/phonon_static_1_2/inputs/POSCAR.orig.gz b/tests/test_data/vasp/Si_pheasy/phonon_static_1_2/inputs/POSCAR.orig.gz
new file mode 100644
index 0000000000..0d45f1dab8
Binary files /dev/null and b/tests/test_data/vasp/Si_pheasy/phonon_static_1_2/inputs/POSCAR.orig.gz differ
diff --git a/tests/test_data/vasp/Si_phonons_4/phonon_static_1_1/inputs/POTCAR.spec.gz b/tests/test_data/vasp/Si_pheasy/phonon_static_1_2/inputs/POTCAR.spec.gz
similarity index 100%
rename from tests/test_data/vasp/Si_phonons_4/phonon_static_1_1/inputs/POTCAR.spec.gz
rename to tests/test_data/vasp/Si_pheasy/phonon_static_1_2/inputs/POTCAR.spec.gz
diff --git a/tests/test_data/vasp/Si_pheasy/phonon_static_1_2/outputs/CONTCAR.gz b/tests/test_data/vasp/Si_pheasy/phonon_static_1_2/outputs/CONTCAR.gz
new file mode 100644
index 0000000000..3c05a8c698
Binary files /dev/null and b/tests/test_data/vasp/Si_pheasy/phonon_static_1_2/outputs/CONTCAR.gz differ
diff --git a/tests/test_data/vasp/Si_pheasy/phonon_static_1_2/outputs/INCAR.gz b/tests/test_data/vasp/Si_pheasy/phonon_static_1_2/outputs/INCAR.gz
new file mode 100644
index 0000000000..933bcccb96
Binary files /dev/null and b/tests/test_data/vasp/Si_pheasy/phonon_static_1_2/outputs/INCAR.gz differ
diff --git a/tests/test_data/vasp/Si_pheasy/phonon_static_1_2/outputs/INCAR.orig.gz b/tests/test_data/vasp/Si_pheasy/phonon_static_1_2/outputs/INCAR.orig.gz
new file mode 100644
index 0000000000..98ee85e2d8
Binary files /dev/null and b/tests/test_data/vasp/Si_pheasy/phonon_static_1_2/outputs/INCAR.orig.gz differ
diff --git a/tests/test_data/vasp/Si_pheasy/phonon_static_1_2/outputs/KPOINTS.gz b/tests/test_data/vasp/Si_pheasy/phonon_static_1_2/outputs/KPOINTS.gz
new file mode 100644
index 0000000000..6024584b1d
Binary files /dev/null and b/tests/test_data/vasp/Si_pheasy/phonon_static_1_2/outputs/KPOINTS.gz differ
diff --git a/tests/test_data/vasp/Si_pheasy/phonon_static_1_2/outputs/KPOINTS.orig.gz b/tests/test_data/vasp/Si_pheasy/phonon_static_1_2/outputs/KPOINTS.orig.gz
new file mode 100644
index 0000000000..2ef5ae4de7
Binary files /dev/null and b/tests/test_data/vasp/Si_pheasy/phonon_static_1_2/outputs/KPOINTS.orig.gz differ
diff --git a/tests/test_data/vasp/Si_pheasy/phonon_static_1_2/outputs/OUTCAR.gz b/tests/test_data/vasp/Si_pheasy/phonon_static_1_2/outputs/OUTCAR.gz
new file mode 100644
index 0000000000..e030514420
Binary files /dev/null and b/tests/test_data/vasp/Si_pheasy/phonon_static_1_2/outputs/OUTCAR.gz differ
diff --git a/tests/test_data/vasp/Si_pheasy/phonon_static_1_2/outputs/POSCAR.gz b/tests/test_data/vasp/Si_pheasy/phonon_static_1_2/outputs/POSCAR.gz
new file mode 100644
index 0000000000..bd6627e23d
Binary files /dev/null and b/tests/test_data/vasp/Si_pheasy/phonon_static_1_2/outputs/POSCAR.gz differ
diff --git a/tests/test_data/vasp/Si_pheasy/phonon_static_1_2/outputs/POSCAR.orig.gz b/tests/test_data/vasp/Si_pheasy/phonon_static_1_2/outputs/POSCAR.orig.gz
new file mode 100644
index 0000000000..0d45f1dab8
Binary files /dev/null and b/tests/test_data/vasp/Si_pheasy/phonon_static_1_2/outputs/POSCAR.orig.gz differ
diff --git a/tests/test_data/vasp/Si_phonons_4/phonon_static_1_1/outputs/POTCAR.spec.gz b/tests/test_data/vasp/Si_pheasy/phonon_static_1_2/outputs/POTCAR.spec.gz
similarity index 100%
rename from tests/test_data/vasp/Si_phonons_4/phonon_static_1_1/outputs/POTCAR.spec.gz
rename to tests/test_data/vasp/Si_pheasy/phonon_static_1_2/outputs/POTCAR.spec.gz
diff --git a/tests/test_data/vasp/Si_pheasy/phonon_static_1_2/outputs/vasprun.xml.gz b/tests/test_data/vasp/Si_pheasy/phonon_static_1_2/outputs/vasprun.xml.gz
new file mode 100644
index 0000000000..2ce85dbb84
Binary files /dev/null and b/tests/test_data/vasp/Si_pheasy/phonon_static_1_2/outputs/vasprun.xml.gz differ
diff --git a/tests/test_data/vasp/Si_pheasy/phonon_static_2_2/inputs/INCAR.gz b/tests/test_data/vasp/Si_pheasy/phonon_static_2_2/inputs/INCAR.gz
new file mode 100644
index 0000000000..16e6f2ca36
Binary files /dev/null and b/tests/test_data/vasp/Si_pheasy/phonon_static_2_2/inputs/INCAR.gz differ
diff --git a/tests/test_data/vasp/Si_pheasy/phonon_static_2_2/inputs/INCAR.orig.gz b/tests/test_data/vasp/Si_pheasy/phonon_static_2_2/inputs/INCAR.orig.gz
new file mode 100644
index 0000000000..24b6a95d02
Binary files /dev/null and b/tests/test_data/vasp/Si_pheasy/phonon_static_2_2/inputs/INCAR.orig.gz differ
diff --git a/tests/test_data/vasp/Si_pheasy/phonon_static_2_2/inputs/KPOINTS.gz b/tests/test_data/vasp/Si_pheasy/phonon_static_2_2/inputs/KPOINTS.gz
new file mode 100644
index 0000000000..d6bf6427a4
Binary files /dev/null and b/tests/test_data/vasp/Si_pheasy/phonon_static_2_2/inputs/KPOINTS.gz differ
diff --git a/tests/test_data/vasp/Si_pheasy/phonon_static_2_2/inputs/KPOINTS.orig.gz b/tests/test_data/vasp/Si_pheasy/phonon_static_2_2/inputs/KPOINTS.orig.gz
new file mode 100644
index 0000000000..dc147b5962
Binary files /dev/null and b/tests/test_data/vasp/Si_pheasy/phonon_static_2_2/inputs/KPOINTS.orig.gz differ
diff --git a/tests/test_data/vasp/Si_pheasy/phonon_static_2_2/inputs/POSCAR.gz b/tests/test_data/vasp/Si_pheasy/phonon_static_2_2/inputs/POSCAR.gz
new file mode 100644
index 0000000000..053d239170
Binary files /dev/null and b/tests/test_data/vasp/Si_pheasy/phonon_static_2_2/inputs/POSCAR.gz differ
diff --git a/tests/test_data/vasp/Si_pheasy/phonon_static_2_2/inputs/POSCAR.orig.gz b/tests/test_data/vasp/Si_pheasy/phonon_static_2_2/inputs/POSCAR.orig.gz
new file mode 100644
index 0000000000..4923c8cf94
Binary files /dev/null and b/tests/test_data/vasp/Si_pheasy/phonon_static_2_2/inputs/POSCAR.orig.gz differ
diff --git a/tests/test_data/vasp/Si_phonons_4/static/inputs/POTCAR.spec.gz b/tests/test_data/vasp/Si_pheasy/phonon_static_2_2/inputs/POTCAR.spec.gz
similarity index 100%
rename from tests/test_data/vasp/Si_phonons_4/static/inputs/POTCAR.spec.gz
rename to tests/test_data/vasp/Si_pheasy/phonon_static_2_2/inputs/POTCAR.spec.gz
diff --git a/tests/test_data/vasp/Si_pheasy/phonon_static_2_2/outputs/CONTCAR.gz b/tests/test_data/vasp/Si_pheasy/phonon_static_2_2/outputs/CONTCAR.gz
new file mode 100644
index 0000000000..87d01fccb9
Binary files /dev/null and b/tests/test_data/vasp/Si_pheasy/phonon_static_2_2/outputs/CONTCAR.gz differ
diff --git a/tests/test_data/vasp/Si_pheasy/phonon_static_2_2/outputs/INCAR.gz b/tests/test_data/vasp/Si_pheasy/phonon_static_2_2/outputs/INCAR.gz
new file mode 100644
index 0000000000..16e6f2ca36
Binary files /dev/null and b/tests/test_data/vasp/Si_pheasy/phonon_static_2_2/outputs/INCAR.gz differ
diff --git a/tests/test_data/vasp/Si_pheasy/phonon_static_2_2/outputs/INCAR.orig.gz b/tests/test_data/vasp/Si_pheasy/phonon_static_2_2/outputs/INCAR.orig.gz
new file mode 100644
index 0000000000..24b6a95d02
Binary files /dev/null and b/tests/test_data/vasp/Si_pheasy/phonon_static_2_2/outputs/INCAR.orig.gz differ
diff --git a/tests/test_data/vasp/Si_pheasy/phonon_static_2_2/outputs/KPOINTS.gz b/tests/test_data/vasp/Si_pheasy/phonon_static_2_2/outputs/KPOINTS.gz
new file mode 100644
index 0000000000..d6bf6427a4
Binary files /dev/null and b/tests/test_data/vasp/Si_pheasy/phonon_static_2_2/outputs/KPOINTS.gz differ
diff --git a/tests/test_data/vasp/Si_pheasy/phonon_static_2_2/outputs/KPOINTS.orig.gz b/tests/test_data/vasp/Si_pheasy/phonon_static_2_2/outputs/KPOINTS.orig.gz
new file mode 100644
index 0000000000..dc147b5962
Binary files /dev/null and b/tests/test_data/vasp/Si_pheasy/phonon_static_2_2/outputs/KPOINTS.orig.gz differ
diff --git a/tests/test_data/vasp/Si_pheasy/phonon_static_2_2/outputs/OUTCAR.gz b/tests/test_data/vasp/Si_pheasy/phonon_static_2_2/outputs/OUTCAR.gz
new file mode 100644
index 0000000000..07b205a9df
Binary files /dev/null and b/tests/test_data/vasp/Si_pheasy/phonon_static_2_2/outputs/OUTCAR.gz differ
diff --git a/tests/test_data/vasp/Si_pheasy/phonon_static_2_2/outputs/POSCAR.gz b/tests/test_data/vasp/Si_pheasy/phonon_static_2_2/outputs/POSCAR.gz
new file mode 100644
index 0000000000..053d239170
Binary files /dev/null and b/tests/test_data/vasp/Si_pheasy/phonon_static_2_2/outputs/POSCAR.gz differ
diff --git a/tests/test_data/vasp/Si_pheasy/phonon_static_2_2/outputs/POSCAR.orig.gz b/tests/test_data/vasp/Si_pheasy/phonon_static_2_2/outputs/POSCAR.orig.gz
new file mode 100644
index 0000000000..4923c8cf94
Binary files /dev/null and b/tests/test_data/vasp/Si_pheasy/phonon_static_2_2/outputs/POSCAR.orig.gz differ
diff --git a/tests/test_data/vasp/Si_phonons_4/static/outputs/POTCAR.spec.gz b/tests/test_data/vasp/Si_pheasy/phonon_static_2_2/outputs/POTCAR.spec.gz
similarity index 100%
rename from tests/test_data/vasp/Si_phonons_4/static/outputs/POTCAR.spec.gz
rename to tests/test_data/vasp/Si_pheasy/phonon_static_2_2/outputs/POTCAR.spec.gz
diff --git a/tests/test_data/vasp/Si_pheasy/phonon_static_2_2/outputs/vasprun.xml.gz b/tests/test_data/vasp/Si_pheasy/phonon_static_2_2/outputs/vasprun.xml.gz
new file mode 100644
index 0000000000..f44a08d39b
Binary files /dev/null and b/tests/test_data/vasp/Si_pheasy/phonon_static_2_2/outputs/vasprun.xml.gz differ
diff --git a/tests/test_data/vasp/Si_pheasy/static/inputs/INCAR.gz b/tests/test_data/vasp/Si_pheasy/static/inputs/INCAR.gz
new file mode 100644
index 0000000000..de79338e0e
Binary files /dev/null and b/tests/test_data/vasp/Si_pheasy/static/inputs/INCAR.gz differ
diff --git a/tests/test_data/vasp/Si_pheasy/static/inputs/INCAR.orig.gz b/tests/test_data/vasp/Si_pheasy/static/inputs/INCAR.orig.gz
new file mode 100644
index 0000000000..18a00cc740
Binary files /dev/null and b/tests/test_data/vasp/Si_pheasy/static/inputs/INCAR.orig.gz differ
diff --git a/tests/test_data/vasp/Si_pheasy/static/inputs/POSCAR.gz b/tests/test_data/vasp/Si_pheasy/static/inputs/POSCAR.gz
new file mode 100644
index 0000000000..a5aea21035
Binary files /dev/null and b/tests/test_data/vasp/Si_pheasy/static/inputs/POSCAR.gz differ
diff --git a/tests/test_data/vasp/Si_pheasy/static/inputs/POSCAR.orig.gz b/tests/test_data/vasp/Si_pheasy/static/inputs/POSCAR.orig.gz
new file mode 100644
index 0000000000..8209f6e0aa
Binary files /dev/null and b/tests/test_data/vasp/Si_pheasy/static/inputs/POSCAR.orig.gz differ
diff --git a/tests/test_data/vasp/Si_phonons_4/tight_relax_1/inputs/POTCAR.spec.gz b/tests/test_data/vasp/Si_pheasy/static/inputs/POTCAR.spec.gz
similarity index 100%
rename from tests/test_data/vasp/Si_phonons_4/tight_relax_1/inputs/POTCAR.spec.gz
rename to tests/test_data/vasp/Si_pheasy/static/inputs/POTCAR.spec.gz
diff --git a/tests/test_data/vasp/Si_pheasy/static/outputs/CONTCAR.gz b/tests/test_data/vasp/Si_pheasy/static/outputs/CONTCAR.gz
new file mode 100644
index 0000000000..23797aae36
Binary files /dev/null and b/tests/test_data/vasp/Si_pheasy/static/outputs/CONTCAR.gz differ
diff --git a/tests/test_data/vasp/Si_pheasy/static/outputs/INCAR.gz b/tests/test_data/vasp/Si_pheasy/static/outputs/INCAR.gz
new file mode 100644
index 0000000000..de79338e0e
Binary files /dev/null and b/tests/test_data/vasp/Si_pheasy/static/outputs/INCAR.gz differ
diff --git a/tests/test_data/vasp/Si_pheasy/static/outputs/INCAR.orig.gz b/tests/test_data/vasp/Si_pheasy/static/outputs/INCAR.orig.gz
new file mode 100644
index 0000000000..18a00cc740
Binary files /dev/null and b/tests/test_data/vasp/Si_pheasy/static/outputs/INCAR.orig.gz differ
diff --git a/tests/test_data/vasp/Si_pheasy/static/outputs/OUTCAR.gz b/tests/test_data/vasp/Si_pheasy/static/outputs/OUTCAR.gz
new file mode 100644
index 0000000000..c6e4600a7c
Binary files /dev/null and b/tests/test_data/vasp/Si_pheasy/static/outputs/OUTCAR.gz differ
diff --git a/tests/test_data/vasp/Si_pheasy/static/outputs/POSCAR.gz b/tests/test_data/vasp/Si_pheasy/static/outputs/POSCAR.gz
new file mode 100644
index 0000000000..a5aea21035
Binary files /dev/null and b/tests/test_data/vasp/Si_pheasy/static/outputs/POSCAR.gz differ
diff --git a/tests/test_data/vasp/Si_pheasy/static/outputs/POSCAR.orig.gz b/tests/test_data/vasp/Si_pheasy/static/outputs/POSCAR.orig.gz
new file mode 100644
index 0000000000..8209f6e0aa
Binary files /dev/null and b/tests/test_data/vasp/Si_pheasy/static/outputs/POSCAR.orig.gz differ
diff --git a/tests/test_data/vasp/Si_phonons_4/tight_relax_1/outputs/POTCAR.spec.gz b/tests/test_data/vasp/Si_pheasy/static/outputs/POTCAR.spec.gz
similarity index 100%
rename from tests/test_data/vasp/Si_phonons_4/tight_relax_1/outputs/POTCAR.spec.gz
rename to tests/test_data/vasp/Si_pheasy/static/outputs/POTCAR.spec.gz
diff --git a/tests/test_data/vasp/Si_pheasy/static/outputs/vasprun.xml.gz b/tests/test_data/vasp/Si_pheasy/static/outputs/vasprun.xml.gz
new file mode 100644
index 0000000000..c4ddad2006
Binary files /dev/null and b/tests/test_data/vasp/Si_pheasy/static/outputs/vasprun.xml.gz differ
diff --git a/tests/test_data/vasp/Si_pheasy/tight_relax_1/inputs/INCAR.gz b/tests/test_data/vasp/Si_pheasy/tight_relax_1/inputs/INCAR.gz
new file mode 100644
index 0000000000..2699cdc1e4
Binary files /dev/null and b/tests/test_data/vasp/Si_pheasy/tight_relax_1/inputs/INCAR.gz differ
diff --git a/tests/test_data/vasp/Si_pheasy/tight_relax_1/inputs/INCAR.orig.gz b/tests/test_data/vasp/Si_pheasy/tight_relax_1/inputs/INCAR.orig.gz
new file mode 100644
index 0000000000..504bf3725d
Binary files /dev/null and b/tests/test_data/vasp/Si_pheasy/tight_relax_1/inputs/INCAR.orig.gz differ
diff --git a/tests/test_data/vasp/Si_pheasy/tight_relax_1/inputs/POSCAR.gz b/tests/test_data/vasp/Si_pheasy/tight_relax_1/inputs/POSCAR.gz
new file mode 100644
index 0000000000..a0d31abdfe
Binary files /dev/null and b/tests/test_data/vasp/Si_pheasy/tight_relax_1/inputs/POSCAR.gz differ
diff --git a/tests/test_data/vasp/Si_pheasy/tight_relax_1/inputs/POSCAR.orig.gz b/tests/test_data/vasp/Si_pheasy/tight_relax_1/inputs/POSCAR.orig.gz
new file mode 100644
index 0000000000..cf967cea39
Binary files /dev/null and b/tests/test_data/vasp/Si_pheasy/tight_relax_1/inputs/POSCAR.orig.gz differ
diff --git a/tests/test_data/vasp/Si_phonons_4/tight_relax_2/inputs/POTCAR.spec.gz b/tests/test_data/vasp/Si_pheasy/tight_relax_1/inputs/POTCAR.spec.gz
similarity index 100%
rename from tests/test_data/vasp/Si_phonons_4/tight_relax_2/inputs/POTCAR.spec.gz
rename to tests/test_data/vasp/Si_pheasy/tight_relax_1/inputs/POTCAR.spec.gz
diff --git a/tests/test_data/vasp/Si_pheasy/tight_relax_1/outputs/CONTCAR.gz b/tests/test_data/vasp/Si_pheasy/tight_relax_1/outputs/CONTCAR.gz
new file mode 100644
index 0000000000..f9b41a941d
Binary files /dev/null and b/tests/test_data/vasp/Si_pheasy/tight_relax_1/outputs/CONTCAR.gz differ
diff --git a/tests/test_data/vasp/Si_pheasy/tight_relax_1/outputs/INCAR.gz b/tests/test_data/vasp/Si_pheasy/tight_relax_1/outputs/INCAR.gz
new file mode 100644
index 0000000000..2699cdc1e4
Binary files /dev/null and b/tests/test_data/vasp/Si_pheasy/tight_relax_1/outputs/INCAR.gz differ
diff --git a/tests/test_data/vasp/Si_pheasy/tight_relax_1/outputs/INCAR.orig.gz b/tests/test_data/vasp/Si_pheasy/tight_relax_1/outputs/INCAR.orig.gz
new file mode 100644
index 0000000000..504bf3725d
Binary files /dev/null and b/tests/test_data/vasp/Si_pheasy/tight_relax_1/outputs/INCAR.orig.gz differ
diff --git a/tests/test_data/vasp/Si_pheasy/tight_relax_1/outputs/OUTCAR.gz b/tests/test_data/vasp/Si_pheasy/tight_relax_1/outputs/OUTCAR.gz
new file mode 100644
index 0000000000..47230006c9
Binary files /dev/null and b/tests/test_data/vasp/Si_pheasy/tight_relax_1/outputs/OUTCAR.gz differ
diff --git a/tests/test_data/vasp/Si_pheasy/tight_relax_1/outputs/POSCAR.gz b/tests/test_data/vasp/Si_pheasy/tight_relax_1/outputs/POSCAR.gz
new file mode 100644
index 0000000000..a0d31abdfe
Binary files /dev/null and b/tests/test_data/vasp/Si_pheasy/tight_relax_1/outputs/POSCAR.gz differ
diff --git a/tests/test_data/vasp/Si_pheasy/tight_relax_1/outputs/POSCAR.orig.gz b/tests/test_data/vasp/Si_pheasy/tight_relax_1/outputs/POSCAR.orig.gz
new file mode 100644
index 0000000000..cf967cea39
Binary files /dev/null and b/tests/test_data/vasp/Si_pheasy/tight_relax_1/outputs/POSCAR.orig.gz differ
diff --git a/tests/test_data/vasp/Si_phonons_4/tight_relax_2/outputs/POTCAR.spec.gz b/tests/test_data/vasp/Si_pheasy/tight_relax_1/outputs/POTCAR.spec.gz
similarity index 100%
rename from tests/test_data/vasp/Si_phonons_4/tight_relax_2/outputs/POTCAR.spec.gz
rename to tests/test_data/vasp/Si_pheasy/tight_relax_1/outputs/POTCAR.spec.gz
diff --git a/tests/test_data/vasp/Si_pheasy/tight_relax_1/outputs/vasprun.xml.gz b/tests/test_data/vasp/Si_pheasy/tight_relax_1/outputs/vasprun.xml.gz
new file mode 100644
index 0000000000..4c181d8ee7
Binary files /dev/null and b/tests/test_data/vasp/Si_pheasy/tight_relax_1/outputs/vasprun.xml.gz differ
diff --git a/tests/test_data/vasp/Si_pheasy/tight_relax_2/inputs/INCAR.gz b/tests/test_data/vasp/Si_pheasy/tight_relax_2/inputs/INCAR.gz
new file mode 100644
index 0000000000..48def910ba
Binary files /dev/null and b/tests/test_data/vasp/Si_pheasy/tight_relax_2/inputs/INCAR.gz differ
diff --git a/tests/test_data/vasp/Si_pheasy/tight_relax_2/inputs/INCAR.orig.gz b/tests/test_data/vasp/Si_pheasy/tight_relax_2/inputs/INCAR.orig.gz
new file mode 100644
index 0000000000..ac7a6dbe93
Binary files /dev/null and b/tests/test_data/vasp/Si_pheasy/tight_relax_2/inputs/INCAR.orig.gz differ
diff --git a/tests/test_data/vasp/Si_pheasy/tight_relax_2/inputs/POSCAR.gz b/tests/test_data/vasp/Si_pheasy/tight_relax_2/inputs/POSCAR.gz
new file mode 100644
index 0000000000..8b9450aac9
Binary files /dev/null and b/tests/test_data/vasp/Si_pheasy/tight_relax_2/inputs/POSCAR.gz differ
diff --git a/tests/test_data/vasp/Si_pheasy/tight_relax_2/inputs/POSCAR.orig.gz b/tests/test_data/vasp/Si_pheasy/tight_relax_2/inputs/POSCAR.orig.gz
new file mode 100644
index 0000000000..0a4823de33
Binary files /dev/null and b/tests/test_data/vasp/Si_pheasy/tight_relax_2/inputs/POSCAR.orig.gz differ
diff --git a/tests/test_data/vasp/Si_pheasy/tight_relax_2/inputs/POTCAR.spec.gz b/tests/test_data/vasp/Si_pheasy/tight_relax_2/inputs/POTCAR.spec.gz
new file mode 100644
index 0000000000..03d35c79c0
Binary files /dev/null and b/tests/test_data/vasp/Si_pheasy/tight_relax_2/inputs/POTCAR.spec.gz differ
diff --git a/tests/test_data/vasp/Si_pheasy/tight_relax_2/outputs/CONTCAR.gz b/tests/test_data/vasp/Si_pheasy/tight_relax_2/outputs/CONTCAR.gz
new file mode 100644
index 0000000000..fc60000106
Binary files /dev/null and b/tests/test_data/vasp/Si_pheasy/tight_relax_2/outputs/CONTCAR.gz differ
diff --git a/tests/test_data/vasp/Si_pheasy/tight_relax_2/outputs/INCAR.gz b/tests/test_data/vasp/Si_pheasy/tight_relax_2/outputs/INCAR.gz
new file mode 100644
index 0000000000..48def910ba
Binary files /dev/null and b/tests/test_data/vasp/Si_pheasy/tight_relax_2/outputs/INCAR.gz differ
diff --git a/tests/test_data/vasp/Si_pheasy/tight_relax_2/outputs/INCAR.orig.gz b/tests/test_data/vasp/Si_pheasy/tight_relax_2/outputs/INCAR.orig.gz
new file mode 100644
index 0000000000..ac7a6dbe93
Binary files /dev/null and b/tests/test_data/vasp/Si_pheasy/tight_relax_2/outputs/INCAR.orig.gz differ
diff --git a/tests/test_data/vasp/Si_pheasy/tight_relax_2/outputs/OUTCAR.gz b/tests/test_data/vasp/Si_pheasy/tight_relax_2/outputs/OUTCAR.gz
new file mode 100644
index 0000000000..6877560f70
Binary files /dev/null and b/tests/test_data/vasp/Si_pheasy/tight_relax_2/outputs/OUTCAR.gz differ
diff --git a/tests/test_data/vasp/Si_pheasy/tight_relax_2/outputs/POSCAR.gz b/tests/test_data/vasp/Si_pheasy/tight_relax_2/outputs/POSCAR.gz
new file mode 100644
index 0000000000..8b9450aac9
Binary files /dev/null and b/tests/test_data/vasp/Si_pheasy/tight_relax_2/outputs/POSCAR.gz differ
diff --git a/tests/test_data/vasp/Si_pheasy/tight_relax_2/outputs/POSCAR.orig.gz b/tests/test_data/vasp/Si_pheasy/tight_relax_2/outputs/POSCAR.orig.gz
new file mode 100644
index 0000000000..0a4823de33
Binary files /dev/null and b/tests/test_data/vasp/Si_pheasy/tight_relax_2/outputs/POSCAR.orig.gz differ
diff --git a/tests/test_data/vasp/Si_pheasy/tight_relax_2/outputs/POTCAR.spec.gz b/tests/test_data/vasp/Si_pheasy/tight_relax_2/outputs/POTCAR.spec.gz
new file mode 100644
index 0000000000..03d35c79c0
Binary files /dev/null and b/tests/test_data/vasp/Si_pheasy/tight_relax_2/outputs/POTCAR.spec.gz differ
diff --git a/tests/test_data/vasp/Si_pheasy/tight_relax_2/outputs/vasprun.xml.gz b/tests/test_data/vasp/Si_pheasy/tight_relax_2/outputs/vasprun.xml.gz
new file mode 100644
index 0000000000..b7a814eaac
Binary files /dev/null and b/tests/test_data/vasp/Si_pheasy/tight_relax_2/outputs/vasprun.xml.gz differ
diff --git a/tests/test_data/vasp/Si_phonons_4/dielectric/inputs/INCAR.gz b/tests/test_data/vasp/Si_phonons_3/dielectric/inputs/INCAR.gz
similarity index 100%
rename from tests/test_data/vasp/Si_phonons_4/dielectric/inputs/INCAR.gz
rename to tests/test_data/vasp/Si_phonons_3/dielectric/inputs/INCAR.gz
diff --git a/tests/test_data/vasp/Si_phonons_4/dielectric/inputs/KPOINTS.gz b/tests/test_data/vasp/Si_phonons_3/dielectric/inputs/KPOINTS.gz
similarity index 100%
rename from tests/test_data/vasp/Si_phonons_4/dielectric/inputs/KPOINTS.gz
rename to tests/test_data/vasp/Si_phonons_3/dielectric/inputs/KPOINTS.gz
diff --git a/tests/test_data/vasp/Si_phonons_4/dielectric/inputs/POSCAR.gz b/tests/test_data/vasp/Si_phonons_3/dielectric/inputs/POSCAR.gz
similarity index 100%
rename from tests/test_data/vasp/Si_phonons_4/dielectric/inputs/POSCAR.gz
rename to tests/test_data/vasp/Si_phonons_3/dielectric/inputs/POSCAR.gz
diff --git a/tests/test_data/vasp/Si_phonons_3/dielectric/inputs/POTCAR.spec.gz b/tests/test_data/vasp/Si_phonons_3/dielectric/inputs/POTCAR.spec.gz
new file mode 100644
index 0000000000..03d35c79c0
Binary files /dev/null and b/tests/test_data/vasp/Si_phonons_3/dielectric/inputs/POTCAR.spec.gz differ
diff --git a/tests/test_data/vasp/Si_phonons_4/dielectric/outputs/CONTCAR.gz b/tests/test_data/vasp/Si_phonons_3/dielectric/outputs/CONTCAR.gz
similarity index 100%
rename from tests/test_data/vasp/Si_phonons_4/dielectric/outputs/CONTCAR.gz
rename to tests/test_data/vasp/Si_phonons_3/dielectric/outputs/CONTCAR.gz
diff --git a/tests/test_data/vasp/Si_phonons_4/dielectric/outputs/INCAR.gz b/tests/test_data/vasp/Si_phonons_3/dielectric/outputs/INCAR.gz
similarity index 100%
rename from tests/test_data/vasp/Si_phonons_4/dielectric/outputs/INCAR.gz
rename to tests/test_data/vasp/Si_phonons_3/dielectric/outputs/INCAR.gz
diff --git a/tests/test_data/vasp/Si_phonons_4/dielectric/outputs/INCAR.orig.gz b/tests/test_data/vasp/Si_phonons_3/dielectric/outputs/INCAR.orig.gz
similarity index 100%
rename from tests/test_data/vasp/Si_phonons_4/dielectric/outputs/INCAR.orig.gz
rename to tests/test_data/vasp/Si_phonons_3/dielectric/outputs/INCAR.orig.gz
diff --git a/tests/test_data/vasp/Si_phonons_4/dielectric/outputs/OUTCAR.gz b/tests/test_data/vasp/Si_phonons_3/dielectric/outputs/OUTCAR.gz
similarity index 100%
rename from tests/test_data/vasp/Si_phonons_4/dielectric/outputs/OUTCAR.gz
rename to tests/test_data/vasp/Si_phonons_3/dielectric/outputs/OUTCAR.gz
diff --git a/tests/test_data/vasp/Si_phonons_4/dielectric/outputs/POSCAR.gz b/tests/test_data/vasp/Si_phonons_3/dielectric/outputs/POSCAR.gz
similarity index 100%
rename from tests/test_data/vasp/Si_phonons_4/dielectric/outputs/POSCAR.gz
rename to tests/test_data/vasp/Si_phonons_3/dielectric/outputs/POSCAR.gz
diff --git a/tests/test_data/vasp/Si_phonons_4/dielectric/outputs/POSCAR.orig.gz b/tests/test_data/vasp/Si_phonons_3/dielectric/outputs/POSCAR.orig.gz
similarity index 100%
rename from tests/test_data/vasp/Si_phonons_4/dielectric/outputs/POSCAR.orig.gz
rename to tests/test_data/vasp/Si_phonons_3/dielectric/outputs/POSCAR.orig.gz
diff --git a/tests/test_data/vasp/Si_phonons_3/dielectric/outputs/POTCAR.spec.gz b/tests/test_data/vasp/Si_phonons_3/dielectric/outputs/POTCAR.spec.gz
new file mode 100644
index 0000000000..03d35c79c0
Binary files /dev/null and b/tests/test_data/vasp/Si_phonons_3/dielectric/outputs/POTCAR.spec.gz differ
diff --git a/tests/test_data/vasp/Si_phonons_4/dielectric/outputs/custodian.json.gz b/tests/test_data/vasp/Si_phonons_3/dielectric/outputs/custodian.json.gz
similarity index 100%
rename from tests/test_data/vasp/Si_phonons_4/dielectric/outputs/custodian.json.gz
rename to tests/test_data/vasp/Si_phonons_3/dielectric/outputs/custodian.json.gz
diff --git a/tests/test_data/vasp/Si_phonons_4/dielectric/outputs/vasprun.xml.gz b/tests/test_data/vasp/Si_phonons_3/dielectric/outputs/vasprun.xml.gz
similarity index 100%
rename from tests/test_data/vasp/Si_phonons_4/dielectric/outputs/vasprun.xml.gz
rename to tests/test_data/vasp/Si_phonons_3/dielectric/outputs/vasprun.xml.gz
diff --git a/tests/test_data/vasp/Si_phonons_3/phonon_static_1_1/inputs/POSCAR.gz b/tests/test_data/vasp/Si_phonons_3/phonon_static_1_1/inputs/POSCAR.gz
index e2c4425c7b..a418409b0a 100644
Binary files a/tests/test_data/vasp/Si_phonons_3/phonon_static_1_1/inputs/POSCAR.gz and b/tests/test_data/vasp/Si_phonons_3/phonon_static_1_1/inputs/POSCAR.gz differ
diff --git a/tests/test_data/vasp/Si_phonons_3/phonon_static_1_1/outputs/CONTCAR.gz b/tests/test_data/vasp/Si_phonons_3/phonon_static_1_1/outputs/CONTCAR.gz
index 27c9e11ddd..80521abea0 100644
Binary files a/tests/test_data/vasp/Si_phonons_3/phonon_static_1_1/outputs/CONTCAR.gz and b/tests/test_data/vasp/Si_phonons_3/phonon_static_1_1/outputs/CONTCAR.gz differ
diff --git a/tests/test_data/vasp/Si_phonons_3/phonon_static_1_1/outputs/INCAR.gz b/tests/test_data/vasp/Si_phonons_3/phonon_static_1_1/outputs/INCAR.gz
index 079b96cacd..7c689ecda1 100644
Binary files a/tests/test_data/vasp/Si_phonons_3/phonon_static_1_1/outputs/INCAR.gz and b/tests/test_data/vasp/Si_phonons_3/phonon_static_1_1/outputs/INCAR.gz differ
diff --git a/tests/test_data/vasp/Si_phonons_3/phonon_static_1_1/outputs/INCAR.orig.gz b/tests/test_data/vasp/Si_phonons_3/phonon_static_1_1/outputs/INCAR.orig.gz
index aefe18fdcb..780d833dd9 100644
Binary files a/tests/test_data/vasp/Si_phonons_3/phonon_static_1_1/outputs/INCAR.orig.gz and b/tests/test_data/vasp/Si_phonons_3/phonon_static_1_1/outputs/INCAR.orig.gz differ
diff --git a/tests/test_data/vasp/Si_phonons_3/phonon_static_1_1/outputs/KPOINTS.gz b/tests/test_data/vasp/Si_phonons_3/phonon_static_1_1/outputs/KPOINTS.gz
index 570222f6f8..69c527ecfd 100644
Binary files a/tests/test_data/vasp/Si_phonons_3/phonon_static_1_1/outputs/KPOINTS.gz and b/tests/test_data/vasp/Si_phonons_3/phonon_static_1_1/outputs/KPOINTS.gz differ
diff --git a/tests/test_data/vasp/Si_phonons_3/phonon_static_1_1/outputs/KPOINTS.orig.gz b/tests/test_data/vasp/Si_phonons_3/phonon_static_1_1/outputs/KPOINTS.orig.gz
index 9480076401..59f63c1b2b 100644
Binary files a/tests/test_data/vasp/Si_phonons_3/phonon_static_1_1/outputs/KPOINTS.orig.gz and b/tests/test_data/vasp/Si_phonons_3/phonon_static_1_1/outputs/KPOINTS.orig.gz differ
diff --git a/tests/test_data/vasp/Si_phonons_3/phonon_static_1_1/outputs/OUTCAR.gz b/tests/test_data/vasp/Si_phonons_3/phonon_static_1_1/outputs/OUTCAR.gz
index 14ac94de4f..5dcfe167f0 100644
Binary files a/tests/test_data/vasp/Si_phonons_3/phonon_static_1_1/outputs/OUTCAR.gz and b/tests/test_data/vasp/Si_phonons_3/phonon_static_1_1/outputs/OUTCAR.gz differ
diff --git a/tests/test_data/vasp/Si_phonons_3/phonon_static_1_1/outputs/POSCAR.gz b/tests/test_data/vasp/Si_phonons_3/phonon_static_1_1/outputs/POSCAR.gz
index 83a3945fd5..aacc0b22d2 100644
Binary files a/tests/test_data/vasp/Si_phonons_3/phonon_static_1_1/outputs/POSCAR.gz and b/tests/test_data/vasp/Si_phonons_3/phonon_static_1_1/outputs/POSCAR.gz differ
diff --git a/tests/test_data/vasp/Si_phonons_3/phonon_static_1_1/outputs/POSCAR.orig.gz b/tests/test_data/vasp/Si_phonons_3/phonon_static_1_1/outputs/POSCAR.orig.gz
index 2d9aea5e5a..4f4154884b 100644
Binary files a/tests/test_data/vasp/Si_phonons_3/phonon_static_1_1/outputs/POSCAR.orig.gz and b/tests/test_data/vasp/Si_phonons_3/phonon_static_1_1/outputs/POSCAR.orig.gz differ
diff --git a/tests/test_data/vasp/Si_phonons_3/phonon_static_1_1/outputs/custodian.json.gz b/tests/test_data/vasp/Si_phonons_3/phonon_static_1_1/outputs/custodian.json.gz
index 9bf2ea5345..a270b0bded 100644
Binary files a/tests/test_data/vasp/Si_phonons_3/phonon_static_1_1/outputs/custodian.json.gz and b/tests/test_data/vasp/Si_phonons_3/phonon_static_1_1/outputs/custodian.json.gz differ
diff --git a/tests/test_data/vasp/Si_phonons_3/phonon_static_1_1/outputs/phonon_info.json.gz b/tests/test_data/vasp/Si_phonons_3/phonon_static_1_1/outputs/phonon_info.json.gz
index 1d3b2191a1..0195a4413d 100644
Binary files a/tests/test_data/vasp/Si_phonons_3/phonon_static_1_1/outputs/phonon_info.json.gz and b/tests/test_data/vasp/Si_phonons_3/phonon_static_1_1/outputs/phonon_info.json.gz differ
diff --git a/tests/test_data/vasp/Si_phonons_3/phonon_static_1_1/outputs/vasprun.xml.gz b/tests/test_data/vasp/Si_phonons_3/phonon_static_1_1/outputs/vasprun.xml.gz
index 6568863f87..669d6c5608 100644
Binary files a/tests/test_data/vasp/Si_phonons_3/phonon_static_1_1/outputs/vasprun.xml.gz and b/tests/test_data/vasp/Si_phonons_3/phonon_static_1_1/outputs/vasprun.xml.gz differ
diff --git a/tests/test_data/vasp/Si_phonons_3/static/inputs/INCAR.gz b/tests/test_data/vasp/Si_phonons_3/static/inputs/INCAR.gz
index 7f65d98af0..8e0e2ef449 100644
Binary files a/tests/test_data/vasp/Si_phonons_3/static/inputs/INCAR.gz and b/tests/test_data/vasp/Si_phonons_3/static/inputs/INCAR.gz differ
diff --git a/tests/test_data/vasp/Si_phonons_3/static/inputs/POSCAR.gz b/tests/test_data/vasp/Si_phonons_3/static/inputs/POSCAR.gz
index b6c25c6c9e..b090b378ce 100644
Binary files a/tests/test_data/vasp/Si_phonons_3/static/inputs/POSCAR.gz and b/tests/test_data/vasp/Si_phonons_3/static/inputs/POSCAR.gz differ
diff --git a/tests/test_data/vasp/Si_phonons_3/static/outputs/CONTCAR.gz b/tests/test_data/vasp/Si_phonons_3/static/outputs/CONTCAR.gz
index 4813b5c715..c432fa9e2c 100644
Binary files a/tests/test_data/vasp/Si_phonons_3/static/outputs/CONTCAR.gz and b/tests/test_data/vasp/Si_phonons_3/static/outputs/CONTCAR.gz differ
diff --git a/tests/test_data/vasp/Si_phonons_3/static/outputs/INCAR.gz b/tests/test_data/vasp/Si_phonons_3/static/outputs/INCAR.gz
index 9940377ae8..af710aeb54 100644
Binary files a/tests/test_data/vasp/Si_phonons_3/static/outputs/INCAR.gz and b/tests/test_data/vasp/Si_phonons_3/static/outputs/INCAR.gz differ
diff --git a/tests/test_data/vasp/Si_phonons_3/static/outputs/INCAR.orig.gz b/tests/test_data/vasp/Si_phonons_3/static/outputs/INCAR.orig.gz
index 0900966a30..0c0d530ed9 100644
Binary files a/tests/test_data/vasp/Si_phonons_3/static/outputs/INCAR.orig.gz and b/tests/test_data/vasp/Si_phonons_3/static/outputs/INCAR.orig.gz differ
diff --git a/tests/test_data/vasp/Si_phonons_3/static/outputs/OUTCAR.gz b/tests/test_data/vasp/Si_phonons_3/static/outputs/OUTCAR.gz
index f48b7e917f..90a5dec8e0 100644
Binary files a/tests/test_data/vasp/Si_phonons_3/static/outputs/OUTCAR.gz and b/tests/test_data/vasp/Si_phonons_3/static/outputs/OUTCAR.gz differ
diff --git a/tests/test_data/vasp/Si_phonons_3/static/outputs/POSCAR.gz b/tests/test_data/vasp/Si_phonons_3/static/outputs/POSCAR.gz
index 1b1c5032e4..d20115c775 100644
Binary files a/tests/test_data/vasp/Si_phonons_3/static/outputs/POSCAR.gz and b/tests/test_data/vasp/Si_phonons_3/static/outputs/POSCAR.gz differ
diff --git a/tests/test_data/vasp/Si_phonons_3/static/outputs/POSCAR.orig.gz b/tests/test_data/vasp/Si_phonons_3/static/outputs/POSCAR.orig.gz
index 275ca04265..6db7b23a32 100644
Binary files a/tests/test_data/vasp/Si_phonons_3/static/outputs/POSCAR.orig.gz and b/tests/test_data/vasp/Si_phonons_3/static/outputs/POSCAR.orig.gz differ
diff --git a/tests/test_data/vasp/Si_phonons_3/static/outputs/custodian.json.gz b/tests/test_data/vasp/Si_phonons_3/static/outputs/custodian.json.gz
index 740d45a39f..91c0b9bcc3 100644
Binary files a/tests/test_data/vasp/Si_phonons_3/static/outputs/custodian.json.gz and b/tests/test_data/vasp/Si_phonons_3/static/outputs/custodian.json.gz differ
diff --git a/tests/test_data/vasp/Si_phonons_3/static/outputs/vasprun.xml.gz b/tests/test_data/vasp/Si_phonons_3/static/outputs/vasprun.xml.gz
index a7356a553d..57f512600e 100644
Binary files a/tests/test_data/vasp/Si_phonons_3/static/outputs/vasprun.xml.gz and b/tests/test_data/vasp/Si_phonons_3/static/outputs/vasprun.xml.gz differ
diff --git a/tests/test_data/vasp/Si_phonons_4/tight_relax_1/inputs/INCAR.gz b/tests/test_data/vasp/Si_phonons_3/tight_relax_1/inputs/INCAR.gz
similarity index 100%
rename from tests/test_data/vasp/Si_phonons_4/tight_relax_1/inputs/INCAR.gz
rename to tests/test_data/vasp/Si_phonons_3/tight_relax_1/inputs/INCAR.gz
diff --git a/tests/test_data/vasp/Si_phonons_4/static/inputs/KPOINTS.gz b/tests/test_data/vasp/Si_phonons_3/tight_relax_1/inputs/KPOINTS.gz
similarity index 100%
rename from tests/test_data/vasp/Si_phonons_4/static/inputs/KPOINTS.gz
rename to tests/test_data/vasp/Si_phonons_3/tight_relax_1/inputs/KPOINTS.gz
diff --git a/tests/test_data/vasp/Si_phonons_4/tight_relax_1/inputs/POSCAR.gz b/tests/test_data/vasp/Si_phonons_3/tight_relax_1/inputs/POSCAR.gz
similarity index 100%
rename from tests/test_data/vasp/Si_phonons_4/tight_relax_1/inputs/POSCAR.gz
rename to tests/test_data/vasp/Si_phonons_3/tight_relax_1/inputs/POSCAR.gz
diff --git a/tests/test_data/vasp/Si_phonons_3/tight_relax_1/inputs/POTCAR.spec.gz b/tests/test_data/vasp/Si_phonons_3/tight_relax_1/inputs/POTCAR.spec.gz
new file mode 100644
index 0000000000..03d35c79c0
Binary files /dev/null and b/tests/test_data/vasp/Si_phonons_3/tight_relax_1/inputs/POTCAR.spec.gz differ
diff --git a/tests/test_data/vasp/Si_phonons_4/tight_relax_1/outputs/CONTCAR.gz b/tests/test_data/vasp/Si_phonons_3/tight_relax_1/outputs/CONTCAR.gz
similarity index 100%
rename from tests/test_data/vasp/Si_phonons_4/tight_relax_1/outputs/CONTCAR.gz
rename to tests/test_data/vasp/Si_phonons_3/tight_relax_1/outputs/CONTCAR.gz
diff --git a/tests/test_data/vasp/Si_phonons_4/tight_relax_1/outputs/INCAR.gz b/tests/test_data/vasp/Si_phonons_3/tight_relax_1/outputs/INCAR.gz
similarity index 100%
rename from tests/test_data/vasp/Si_phonons_4/tight_relax_1/outputs/INCAR.gz
rename to tests/test_data/vasp/Si_phonons_3/tight_relax_1/outputs/INCAR.gz
diff --git a/tests/test_data/vasp/Si_phonons_4/tight_relax_1/outputs/INCAR.orig.gz b/tests/test_data/vasp/Si_phonons_3/tight_relax_1/outputs/INCAR.orig.gz
similarity index 100%
rename from tests/test_data/vasp/Si_phonons_4/tight_relax_1/outputs/INCAR.orig.gz
rename to tests/test_data/vasp/Si_phonons_3/tight_relax_1/outputs/INCAR.orig.gz
diff --git a/tests/test_data/vasp/Si_phonons_4/tight_relax_1/outputs/OUTCAR.gz b/tests/test_data/vasp/Si_phonons_3/tight_relax_1/outputs/OUTCAR.gz
similarity index 100%
rename from tests/test_data/vasp/Si_phonons_4/tight_relax_1/outputs/OUTCAR.gz
rename to tests/test_data/vasp/Si_phonons_3/tight_relax_1/outputs/OUTCAR.gz
diff --git a/tests/test_data/vasp/Si_phonons_4/tight_relax_1/outputs/POSCAR.gz b/tests/test_data/vasp/Si_phonons_3/tight_relax_1/outputs/POSCAR.gz
similarity index 100%
rename from tests/test_data/vasp/Si_phonons_4/tight_relax_1/outputs/POSCAR.gz
rename to tests/test_data/vasp/Si_phonons_3/tight_relax_1/outputs/POSCAR.gz
diff --git a/tests/test_data/vasp/Si_phonons_4/tight_relax_1/outputs/POSCAR.orig.gz b/tests/test_data/vasp/Si_phonons_3/tight_relax_1/outputs/POSCAR.orig.gz
similarity index 100%
rename from tests/test_data/vasp/Si_phonons_4/tight_relax_1/outputs/POSCAR.orig.gz
rename to tests/test_data/vasp/Si_phonons_3/tight_relax_1/outputs/POSCAR.orig.gz
diff --git a/tests/test_data/vasp/Si_phonons_3/tight_relax_1/outputs/POTCAR.spec.gz b/tests/test_data/vasp/Si_phonons_3/tight_relax_1/outputs/POTCAR.spec.gz
new file mode 100644
index 0000000000..03d35c79c0
Binary files /dev/null and b/tests/test_data/vasp/Si_phonons_3/tight_relax_1/outputs/POTCAR.spec.gz differ
diff --git a/tests/test_data/vasp/Si_phonons_4/tight_relax_1/outputs/custodian.json.gz b/tests/test_data/vasp/Si_phonons_3/tight_relax_1/outputs/custodian.json.gz
similarity index 100%
rename from tests/test_data/vasp/Si_phonons_4/tight_relax_1/outputs/custodian.json.gz
rename to tests/test_data/vasp/Si_phonons_3/tight_relax_1/outputs/custodian.json.gz
diff --git a/tests/test_data/vasp/Si_phonons_4/tight_relax_1/outputs/vasprun.xml.gz b/tests/test_data/vasp/Si_phonons_3/tight_relax_1/outputs/vasprun.xml.gz
similarity index 100%
rename from tests/test_data/vasp/Si_phonons_4/tight_relax_1/outputs/vasprun.xml.gz
rename to tests/test_data/vasp/Si_phonons_3/tight_relax_1/outputs/vasprun.xml.gz
diff --git a/tests/test_data/vasp/Si_phonons_4/tight_relax_2/inputs/INCAR.gz b/tests/test_data/vasp/Si_phonons_3/tight_relax_2/inputs/INCAR.gz
similarity index 100%
rename from tests/test_data/vasp/Si_phonons_4/tight_relax_2/inputs/INCAR.gz
rename to tests/test_data/vasp/Si_phonons_3/tight_relax_2/inputs/INCAR.gz
diff --git a/tests/test_data/vasp/Si_phonons_4/tight_relax_1/inputs/KPOINTS.gz b/tests/test_data/vasp/Si_phonons_3/tight_relax_2/inputs/KPOINTS.gz
similarity index 100%
rename from tests/test_data/vasp/Si_phonons_4/tight_relax_1/inputs/KPOINTS.gz
rename to tests/test_data/vasp/Si_phonons_3/tight_relax_2/inputs/KPOINTS.gz
diff --git a/tests/test_data/vasp/Si_phonons_4/tight_relax_2/inputs/POSCAR.gz b/tests/test_data/vasp/Si_phonons_3/tight_relax_2/inputs/POSCAR.gz
similarity index 100%
rename from tests/test_data/vasp/Si_phonons_4/tight_relax_2/inputs/POSCAR.gz
rename to tests/test_data/vasp/Si_phonons_3/tight_relax_2/inputs/POSCAR.gz
diff --git a/tests/test_data/vasp/Si_phonons_3/tight_relax_2/inputs/POTCAR.spec.gz b/tests/test_data/vasp/Si_phonons_3/tight_relax_2/inputs/POTCAR.spec.gz
new file mode 100644
index 0000000000..03d35c79c0
Binary files /dev/null and b/tests/test_data/vasp/Si_phonons_3/tight_relax_2/inputs/POTCAR.spec.gz differ
diff --git a/tests/test_data/vasp/Si_phonons_4/tight_relax_2/outputs/CONTCAR.gz b/tests/test_data/vasp/Si_phonons_3/tight_relax_2/outputs/CONTCAR.gz
similarity index 100%
rename from tests/test_data/vasp/Si_phonons_4/tight_relax_2/outputs/CONTCAR.gz
rename to tests/test_data/vasp/Si_phonons_3/tight_relax_2/outputs/CONTCAR.gz
diff --git a/tests/test_data/vasp/Si_phonons_4/tight_relax_2/outputs/INCAR.gz b/tests/test_data/vasp/Si_phonons_3/tight_relax_2/outputs/INCAR.gz
similarity index 100%
rename from tests/test_data/vasp/Si_phonons_4/tight_relax_2/outputs/INCAR.gz
rename to tests/test_data/vasp/Si_phonons_3/tight_relax_2/outputs/INCAR.gz
diff --git a/tests/test_data/vasp/Si_phonons_4/tight_relax_2/outputs/INCAR.orig.gz b/tests/test_data/vasp/Si_phonons_3/tight_relax_2/outputs/INCAR.orig.gz
similarity index 100%
rename from tests/test_data/vasp/Si_phonons_4/tight_relax_2/outputs/INCAR.orig.gz
rename to tests/test_data/vasp/Si_phonons_3/tight_relax_2/outputs/INCAR.orig.gz
diff --git a/tests/test_data/vasp/Si_phonons_4/tight_relax_2/outputs/OUTCAR.gz b/tests/test_data/vasp/Si_phonons_3/tight_relax_2/outputs/OUTCAR.gz
similarity index 100%
rename from tests/test_data/vasp/Si_phonons_4/tight_relax_2/outputs/OUTCAR.gz
rename to tests/test_data/vasp/Si_phonons_3/tight_relax_2/outputs/OUTCAR.gz
diff --git a/tests/test_data/vasp/Si_phonons_4/tight_relax_2/outputs/POSCAR.gz b/tests/test_data/vasp/Si_phonons_3/tight_relax_2/outputs/POSCAR.gz
similarity index 100%
rename from tests/test_data/vasp/Si_phonons_4/tight_relax_2/outputs/POSCAR.gz
rename to tests/test_data/vasp/Si_phonons_3/tight_relax_2/outputs/POSCAR.gz
diff --git a/tests/test_data/vasp/Si_phonons_4/tight_relax_2/outputs/POSCAR.orig.gz b/tests/test_data/vasp/Si_phonons_3/tight_relax_2/outputs/POSCAR.orig.gz
similarity index 100%
rename from tests/test_data/vasp/Si_phonons_4/tight_relax_2/outputs/POSCAR.orig.gz
rename to tests/test_data/vasp/Si_phonons_3/tight_relax_2/outputs/POSCAR.orig.gz
diff --git a/tests/test_data/vasp/Si_phonons_3/tight_relax_2/outputs/POTCAR.spec.gz b/tests/test_data/vasp/Si_phonons_3/tight_relax_2/outputs/POTCAR.spec.gz
new file mode 100644
index 0000000000..03d35c79c0
Binary files /dev/null and b/tests/test_data/vasp/Si_phonons_3/tight_relax_2/outputs/POTCAR.spec.gz differ
diff --git a/tests/test_data/vasp/Si_phonons_4/tight_relax_2/outputs/custodian.json.gz b/tests/test_data/vasp/Si_phonons_3/tight_relax_2/outputs/custodian.json.gz
similarity index 100%
rename from tests/test_data/vasp/Si_phonons_4/tight_relax_2/outputs/custodian.json.gz
rename to tests/test_data/vasp/Si_phonons_3/tight_relax_2/outputs/custodian.json.gz
diff --git a/tests/test_data/vasp/Si_phonons_4/tight_relax_2/outputs/vasprun.xml.gz b/tests/test_data/vasp/Si_phonons_3/tight_relax_2/outputs/vasprun.xml.gz
similarity index 100%
rename from tests/test_data/vasp/Si_phonons_4/tight_relax_2/outputs/vasprun.xml.gz
rename to tests/test_data/vasp/Si_phonons_3/tight_relax_2/outputs/vasprun.xml.gz
diff --git a/tests/test_data/vasp/Si_phonons_4/phonon_static_1_1/inputs/INCAR.gz b/tests/test_data/vasp/Si_phonons_4/phonon_static_1_1/inputs/INCAR.gz
deleted file mode 100644
index e83c6fa560..0000000000
Binary files a/tests/test_data/vasp/Si_phonons_4/phonon_static_1_1/inputs/INCAR.gz and /dev/null differ
diff --git a/tests/test_data/vasp/Si_phonons_4/phonon_static_1_1/inputs/KPOINTS.gz b/tests/test_data/vasp/Si_phonons_4/phonon_static_1_1/inputs/KPOINTS.gz
deleted file mode 100644
index b8c83cd8ee..0000000000
Binary files a/tests/test_data/vasp/Si_phonons_4/phonon_static_1_1/inputs/KPOINTS.gz and /dev/null differ
diff --git a/tests/test_data/vasp/Si_phonons_4/phonon_static_1_1/inputs/POSCAR.gz b/tests/test_data/vasp/Si_phonons_4/phonon_static_1_1/inputs/POSCAR.gz
deleted file mode 100644
index a418409b0a..0000000000
Binary files a/tests/test_data/vasp/Si_phonons_4/phonon_static_1_1/inputs/POSCAR.gz and /dev/null differ
diff --git a/tests/test_data/vasp/Si_phonons_4/phonon_static_1_1/outputs/CONTCAR.gz b/tests/test_data/vasp/Si_phonons_4/phonon_static_1_1/outputs/CONTCAR.gz
deleted file mode 100644
index 80521abea0..0000000000
Binary files a/tests/test_data/vasp/Si_phonons_4/phonon_static_1_1/outputs/CONTCAR.gz and /dev/null differ
diff --git a/tests/test_data/vasp/Si_phonons_4/phonon_static_1_1/outputs/INCAR.gz b/tests/test_data/vasp/Si_phonons_4/phonon_static_1_1/outputs/INCAR.gz
deleted file mode 100644
index 7c689ecda1..0000000000
Binary files a/tests/test_data/vasp/Si_phonons_4/phonon_static_1_1/outputs/INCAR.gz and /dev/null differ
diff --git a/tests/test_data/vasp/Si_phonons_4/phonon_static_1_1/outputs/INCAR.orig.gz b/tests/test_data/vasp/Si_phonons_4/phonon_static_1_1/outputs/INCAR.orig.gz
deleted file mode 100644
index 780d833dd9..0000000000
Binary files a/tests/test_data/vasp/Si_phonons_4/phonon_static_1_1/outputs/INCAR.orig.gz and /dev/null differ
diff --git a/tests/test_data/vasp/Si_phonons_4/phonon_static_1_1/outputs/KPOINTS.gz b/tests/test_data/vasp/Si_phonons_4/phonon_static_1_1/outputs/KPOINTS.gz
deleted file mode 100644
index 69c527ecfd..0000000000
Binary files a/tests/test_data/vasp/Si_phonons_4/phonon_static_1_1/outputs/KPOINTS.gz and /dev/null differ
diff --git a/tests/test_data/vasp/Si_phonons_4/phonon_static_1_1/outputs/KPOINTS.orig.gz b/tests/test_data/vasp/Si_phonons_4/phonon_static_1_1/outputs/KPOINTS.orig.gz
deleted file mode 100644
index 59f63c1b2b..0000000000
Binary files a/tests/test_data/vasp/Si_phonons_4/phonon_static_1_1/outputs/KPOINTS.orig.gz and /dev/null differ
diff --git a/tests/test_data/vasp/Si_phonons_4/phonon_static_1_1/outputs/OUTCAR.gz b/tests/test_data/vasp/Si_phonons_4/phonon_static_1_1/outputs/OUTCAR.gz
deleted file mode 100644
index 5dcfe167f0..0000000000
Binary files a/tests/test_data/vasp/Si_phonons_4/phonon_static_1_1/outputs/OUTCAR.gz and /dev/null differ
diff --git a/tests/test_data/vasp/Si_phonons_4/phonon_static_1_1/outputs/POSCAR.gz b/tests/test_data/vasp/Si_phonons_4/phonon_static_1_1/outputs/POSCAR.gz
deleted file mode 100644
index aacc0b22d2..0000000000
Binary files a/tests/test_data/vasp/Si_phonons_4/phonon_static_1_1/outputs/POSCAR.gz and /dev/null differ
diff --git a/tests/test_data/vasp/Si_phonons_4/phonon_static_1_1/outputs/POSCAR.orig.gz b/tests/test_data/vasp/Si_phonons_4/phonon_static_1_1/outputs/POSCAR.orig.gz
deleted file mode 100644
index 4f4154884b..0000000000
Binary files a/tests/test_data/vasp/Si_phonons_4/phonon_static_1_1/outputs/POSCAR.orig.gz and /dev/null differ
diff --git a/tests/test_data/vasp/Si_phonons_4/phonon_static_1_1/outputs/custodian.json.gz b/tests/test_data/vasp/Si_phonons_4/phonon_static_1_1/outputs/custodian.json.gz
deleted file mode 100644
index a270b0bded..0000000000
Binary files a/tests/test_data/vasp/Si_phonons_4/phonon_static_1_1/outputs/custodian.json.gz and /dev/null differ
diff --git a/tests/test_data/vasp/Si_phonons_4/phonon_static_1_1/outputs/phonon_info.json.gz b/tests/test_data/vasp/Si_phonons_4/phonon_static_1_1/outputs/phonon_info.json.gz
deleted file mode 100644
index 0195a4413d..0000000000
Binary files a/tests/test_data/vasp/Si_phonons_4/phonon_static_1_1/outputs/phonon_info.json.gz and /dev/null differ
diff --git a/tests/test_data/vasp/Si_phonons_4/phonon_static_1_1/outputs/vasprun.xml.gz b/tests/test_data/vasp/Si_phonons_4/phonon_static_1_1/outputs/vasprun.xml.gz
deleted file mode 100644
index 669d6c5608..0000000000
Binary files a/tests/test_data/vasp/Si_phonons_4/phonon_static_1_1/outputs/vasprun.xml.gz and /dev/null differ
diff --git a/tests/test_data/vasp/Si_phonons_4/static/inputs/INCAR.gz b/tests/test_data/vasp/Si_phonons_4/static/inputs/INCAR.gz
deleted file mode 100644
index 8e0e2ef449..0000000000
Binary files a/tests/test_data/vasp/Si_phonons_4/static/inputs/INCAR.gz and /dev/null differ
diff --git a/tests/test_data/vasp/Si_phonons_4/static/inputs/POSCAR.gz b/tests/test_data/vasp/Si_phonons_4/static/inputs/POSCAR.gz
deleted file mode 100644
index b090b378ce..0000000000
Binary files a/tests/test_data/vasp/Si_phonons_4/static/inputs/POSCAR.gz and /dev/null differ
diff --git a/tests/test_data/vasp/Si_phonons_4/static/outputs/CONTCAR.gz b/tests/test_data/vasp/Si_phonons_4/static/outputs/CONTCAR.gz
deleted file mode 100644
index c432fa9e2c..0000000000
Binary files a/tests/test_data/vasp/Si_phonons_4/static/outputs/CONTCAR.gz and /dev/null differ
diff --git a/tests/test_data/vasp/Si_phonons_4/static/outputs/INCAR.gz b/tests/test_data/vasp/Si_phonons_4/static/outputs/INCAR.gz
deleted file mode 100644
index af710aeb54..0000000000
Binary files a/tests/test_data/vasp/Si_phonons_4/static/outputs/INCAR.gz and /dev/null differ
diff --git a/tests/test_data/vasp/Si_phonons_4/static/outputs/INCAR.orig.gz b/tests/test_data/vasp/Si_phonons_4/static/outputs/INCAR.orig.gz
deleted file mode 100644
index 0c0d530ed9..0000000000
Binary files a/tests/test_data/vasp/Si_phonons_4/static/outputs/INCAR.orig.gz and /dev/null differ
diff --git a/tests/test_data/vasp/Si_phonons_4/static/outputs/OUTCAR.gz b/tests/test_data/vasp/Si_phonons_4/static/outputs/OUTCAR.gz
deleted file mode 100644
index 90a5dec8e0..0000000000
Binary files a/tests/test_data/vasp/Si_phonons_4/static/outputs/OUTCAR.gz and /dev/null differ
diff --git a/tests/test_data/vasp/Si_phonons_4/static/outputs/POSCAR.gz b/tests/test_data/vasp/Si_phonons_4/static/outputs/POSCAR.gz
deleted file mode 100644
index d20115c775..0000000000
Binary files a/tests/test_data/vasp/Si_phonons_4/static/outputs/POSCAR.gz and /dev/null differ
diff --git a/tests/test_data/vasp/Si_phonons_4/static/outputs/POSCAR.orig.gz b/tests/test_data/vasp/Si_phonons_4/static/outputs/POSCAR.orig.gz
deleted file mode 100644
index 6db7b23a32..0000000000
Binary files a/tests/test_data/vasp/Si_phonons_4/static/outputs/POSCAR.orig.gz and /dev/null differ
diff --git a/tests/test_data/vasp/Si_phonons_4/static/outputs/custodian.json.gz b/tests/test_data/vasp/Si_phonons_4/static/outputs/custodian.json.gz
deleted file mode 100644
index 91c0b9bcc3..0000000000
Binary files a/tests/test_data/vasp/Si_phonons_4/static/outputs/custodian.json.gz and /dev/null differ
diff --git a/tests/test_data/vasp/Si_phonons_4/static/outputs/vasprun.xml.gz b/tests/test_data/vasp/Si_phonons_4/static/outputs/vasprun.xml.gz
deleted file mode 100644
index 57f512600e..0000000000
Binary files a/tests/test_data/vasp/Si_phonons_4/static/outputs/vasprun.xml.gz and /dev/null differ
diff --git a/tests/test_data/vasp/Si_phonons_4/tight_relax_2/inputs/KPOINTS.gz b/tests/test_data/vasp/Si_phonons_4/tight_relax_2/inputs/KPOINTS.gz
deleted file mode 100644
index fb18392ee7..0000000000
Binary files a/tests/test_data/vasp/Si_phonons_4/tight_relax_2/inputs/KPOINTS.gz and /dev/null differ
diff --git a/tests/vasp/flows/test_pheasy.py b/tests/vasp/flows/test_pheasy.py
new file mode 100644
index 0000000000..2896485501
--- /dev/null
+++ b/tests/vasp/flows/test_pheasy.py
@@ -0,0 +1,426 @@
+from jobflow import run_locally
+from numpy.testing import assert_allclose
+from pymatgen.core.structure import Structure
+from pymatgen.phonon.bandstructure import PhononBandStructureSymmLine
+from pymatgen.phonon.dos import PhononDos
+
+from atomate2.common.flows.pheasy import BasePhononMaker
+from atomate2.common.powerups import add_metadata_to_flow
+from atomate2.common.schemas.pheasy import (
+ Forceconstants,
+ PhononBSDOSDoc,
+ PhononComputationalSettings,
+ PhononJobDirs,
+ PhononUUIDs,
+ ThermalDisplacementData,
+)
+from atomate2.vasp.flows.pheasy import PhononMaker
+from atomate2.vasp.jobs.base import BaseVaspMaker
+from atomate2.vasp.powerups import update_user_incar_settings
+
+
+def test_pheasy_wf_vasp(mock_vasp, clean_dir, si_structure: Structure, test_dir):
+ # mapping from job name to directory containing test files
+ ref_paths = {
+ "tight relax 1": "Si_pheasy/tight_relax_1",
+ "tight relax 2": "Si_pheasy/tight_relax_2",
+ "phonon static 1/2": "Si_pheasy/phonon_static_1_2",
+ "phonon static 2/2": "Si_pheasy/phonon_static_2_2",
+ "static": "Si_pheasy/static",
+ "dielectric": "Si_pheasy/dielectric",
+ }
+
+ # settings passed to fake_run_vasp; adjust these to check for certain INCAR settings
+ fake_run_vasp_kwargs = {
+ "tight relax 1": {"incar_settings": ["NSW", "ISMEAR", "KSPACING"]},
+ "tight relax 2": {"incar_settings": ["NSW", "ISMEAR", "KSPACING"]},
+ "phonon static 1/2": {"incar_settings": ["NSW", "ISMEAR"]},
+ "phonon static 2/2": {"incar_settings": ["NSW", "ISMEAR"]},
+ "static": {"incar_settings": ["NSW", "ISMEAR"]},
+ "dielectric": {"incar_settings": ["NSW", "ISMEAR"]},
+ }
+
+ # automatically use fake VASP and write POTCAR.spec dulsring the test
+ mock_vasp(ref_paths, fake_run_vasp_kwargs)
+
+ si_struct = Structure.from_file(
+ test_dir / "vasp/Si_pheasy/tight_relax_1/inputs/POSCAR.gz"
+ )
+
+ job = PhononMaker(
+ get_supercell_size_kwargs={"force_diagonal": True},
+ min_length=12,
+ mp_id="mp-149",
+ cal_anhar_fcs=False,
+ # use_symmetrized_structure="primitive"
+ ).make(structure=si_struct)
+
+ job = update_user_incar_settings(
+ job,
+ {
+ "ENCUT": 600,
+ "ISMEAR": 0,
+ "SIGMA": 0.05,
+ "KSPACING": 0.15,
+ "ISPIN": 1,
+ "EDIFFG": -1e-04,
+ "EDIFF": 1e-07,
+ },
+ )
+ job = add_metadata_to_flow(
+ flow=job,
+ additional_fields={"mp_id": "mp-149", "unit_testing": "yes"},
+ class_filter=(BaseVaspMaker, BasePhononMaker, PhononMaker),
+ )
+
+ # run the flow or job and ensure that it finished running successfully
+ responses = run_locally(job, create_folders=True, ensure_success=True)
+
+ # validate the outputs
+ assert isinstance(responses[job.jobs[-1].uuid][1].output, PhononBSDOSDoc)
+ assert_allclose(
+ responses[job.jobs[-1].uuid][1].output.free_energies,
+ [
+ 5792.458116272716,
+ 5792.451271742757,
+ 5792.308574619996,
+ 5791.3893705576875,
+ 5788.26230090264,
+ 5781.251118319793,
+ 5768.997781886127,
+ 5750.565423632229,
+ 5725.332982293328,
+ 5692.876443331414,
+ 5652.890004107427,
+ 5605.143827748774,
+ 5549.4640389105325,
+ 5485.723569667365,
+ 5413.837265156259,
+ 5333.758132354205,
+ 5245.473570542215,
+ 5149.001346193473,
+ 5044.385428634828,
+ 4931.691884236595,
+ 4811.004999276292,
+ 4682.423743675511,
+ 4546.058632404462,
+ 4402.028999079057,
+ 4250.460668093644,
+ 4091.483995048405,
+ 3925.2322370663665,
+ 3751.840212042715,
+ 3571.4432067784546,
+ 3384.176096803778,
+ 3190.172644484756,
+ 2989.5649460944087,
+ 2782.483002538277,
+ 2569.054392149452,
+ 2349.4040273117657,
+ 2123.6539796025945,
+ 1891.9233606773441,
+ 1654.3282482754973,
+ 1410.9816485521276,
+ 1161.9934874706462,
+ 907.4706252726158,
+ 647.5168891063854,
+ 382.2331197810191,
+ 111.71722934513599,
+ -163.9357332035992,
+ -444.6335102719554,
+ -730.2865598681723,
+ -1020.8079770828365,
+ -1316.1134140135116,
+ -1616.1209990625455,
+ ],
+ rtol=1e-5, # relaxed relative tolerance
+ atol=1e-5, # add a small absolute tolerance
+ )
+
+ assert isinstance(
+ responses[job.jobs[-1].uuid][1].output.phonon_bandstructure,
+ PhononBandStructureSymmLine,
+ )
+ assert isinstance(responses[job.jobs[-1].uuid][1].output.phonon_dos, PhononDos)
+ assert isinstance(
+ responses[job.jobs[-1].uuid][1].output.thermal_displacement_data,
+ ThermalDisplacementData,
+ )
+ assert isinstance(responses[job.jobs[-1].uuid][1].output.structure, Structure)
+ assert_allclose(
+ responses[job.jobs[-1].uuid][1].output.temperatures,
+ [
+ 0,
+ 10,
+ 20,
+ 30,
+ 40,
+ 50,
+ 60,
+ 70,
+ 80,
+ 90,
+ 100,
+ 110,
+ 120,
+ 130,
+ 140,
+ 150,
+ 160,
+ 170,
+ 180,
+ 190,
+ 200,
+ 210,
+ 220,
+ 230,
+ 240,
+ 250,
+ 260,
+ 270,
+ 280,
+ 290,
+ 300,
+ 310,
+ 320,
+ 330,
+ 340,
+ 350,
+ 360,
+ 370,
+ 380,
+ 390,
+ 400,
+ 410,
+ 420,
+ 430,
+ 440,
+ 450,
+ 460,
+ 470,
+ 480,
+ 490,
+ ],
+ )
+ assert responses[job.jobs[-1].uuid][1].output.has_imaginary_modes is False
+ assert isinstance(
+ responses[job.jobs[-1].uuid][1].output.force_constants, Forceconstants
+ )
+ assert isinstance(responses[job.jobs[-1].uuid][1].output.jobdirs, PhononJobDirs)
+ assert isinstance(responses[job.jobs[-1].uuid][1].output.uuids, PhononUUIDs)
+ assert_allclose(responses[job.jobs[-1].uuid][1].output.total_dft_energy, -5.7466748)
+ assert_allclose(
+ responses[job.jobs[-1].uuid][1].output.born,
+ [
+ ((0.0, 0.0, 0.0), (0.0, 0.0, 0.0), (0.0, 0.0, 0.0)),
+ ((0.0, 0.0, 0.0), (0.0, 0.0, 0.0), (0.0, 0.0, 0.0)),
+ ],
+ )
+ assert_allclose(
+ responses[job.jobs[-1].uuid][1].output.epsilon_static,
+ (
+ (13.31020238, 0.0, -0.000000000000000000000000000000041086505480261033),
+ (0.000000000000000000000000000000032869204384208823, 13.31020238, 0.0),
+ (
+ 0.00000000000000000000000000000003697785493223493,
+ -0.00000000000000000000000000000005310360021821649,
+ 13.31020238,
+ ),
+ ),
+ atol=1e-8,
+ )
+ assert_allclose(
+ responses[job.jobs[-1].uuid][1].output.supercell_matrix,
+ [[4.0, 0.0, 0.0], [0.0, 4.0, 0.0], [0.0, 0.0, 4.0]],
+ )
+ assert_allclose(
+ responses[job.jobs[-1].uuid][1].output.primitive_matrix,
+ [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]],
+ rtol=1e-5,
+ atol=1e-10,
+ )
+ assert responses[job.jobs[-1].uuid][1].output.code == "vasp"
+ assert isinstance(
+ responses[job.jobs[-1].uuid][1].output.phonopy_settings,
+ PhononComputationalSettings,
+ )
+ assert responses[job.jobs[-1].uuid][1].output.phonopy_settings.npoints_band == 101
+ assert (
+ responses[job.jobs[-1].uuid][1].output.phonopy_settings.kpath_scheme
+ == "seekpath"
+ )
+ assert (
+ responses[job.jobs[-1].uuid][1].output.phonopy_settings.kpoint_density_dos
+ == 7000
+ )
+ assert_allclose(
+ responses[job.jobs[-1].uuid][1].output.entropies,
+ [
+ 0.0,
+ 0.0029053715285381693,
+ 0.03534406735813481,
+ 0.17371749757534496,
+ 0.4806355132850958,
+ 0.9443138235461701,
+ 1.5217487290037728,
+ 2.1748672785891556,
+ 2.8785588902287014,
+ 3.61777903114171,
+ 4.383308818024208,
+ 5.168810618964096,
+ 5.969257468971105,
+ 6.780249611796599,
+ 7.597788260092625,
+ 8.418241566262875,
+ 9.238367696860214,
+ 10.055337466350297,
+ 10.866738450845892,
+ 11.670559750796489,
+ 12.465162508699496,
+ 13.249242154996521,
+ 14.02178733336692,
+ 14.78203900188199,
+ 15.529451895610356,
+ 16.26365953641141,
+ 16.984443284189616,
+ 17.691705481526853,
+ 18.38544648297612,
+ 19.065745223657316,
+ 19.732742925263075,
+ 20.38662953003233,
+ 21.027632473643276,
+ 21.6560074426566,
+ 22.27203080258059,
+ 22.87599342375645,
+ 23.468195671240995,
+ 24.04894336028136,
+ 24.618544510282547,
+ 25.177306757323354,
+ 25.725535308514058,
+ 26.26353134118291,
+ 26.791590766448167,
+ 27.31000329059948,
+ 27.81905171927258,
+ 28.31901145900843,
+ 28.810150178756654,
+ 29.29272760048077,
+ 29.76699539348012,
+ 30.23319715155372,
+ ],
+ atol=1e-6,
+ )
+ assert_allclose(
+ responses[job.jobs[-1].uuid][1].output.heat_capacities,
+ [
+ 0.0,
+ 0.009726834044170778,
+ 0.13866101670569114,
+ 0.6571570545060572,
+ 1.5667423561930287,
+ 2.639591924628341,
+ 3.7231364670350806,
+ 4.771184569421581,
+ 5.786825421391361,
+ 6.782392815371432,
+ 7.764289299349057,
+ 8.731219108699392,
+ 9.676996516283827,
+ 10.593722262345167,
+ 11.473976773888314,
+ 12.311941879701372,
+ 13.103752616984227,
+ 13.847404360575291,
+ 14.542454296999962,
+ 15.189663439692012,
+ 15.790656197118794,
+ 16.347630811439014,
+ 16.86312946277534,
+ 17.33986465971092,
+ 17.780593548325292,
+ 18.188030602925156,
+ 18.564789807070603,
+ 18.913348785224382,
+ 19.23602883664659,
+ 19.53498619439316,
+ 19.812210987727507,
+ 20.069531311525665,
+ 20.30862052298697,
+ 20.531006428284563,
+ 20.738081424927493,
+ 20.931112960999076,
+ 21.111253886165763,
+ 21.279552422144512,
+ 21.436961588185195,
+ 21.584347992205277,
+ 21.722499949574136,
+ 21.852134925891033,
+ 21.97390632235039,
+ 22.088409636021186,
+ 22.19618803517433,
+ 22.29773739353601,
+ 22.393510828356888,
+ 22.483922786412425,
+ 22.56935272015279,
+ 22.65014839366568,
+ ],
+ rtol=1e-5, # relaxed relative tolerance
+ atol=1e-5, # add a small absolute tolerance
+ )
+
+ assert_allclose(
+ responses[job.jobs[-1].uuid][1].output.internal_energies,
+ [
+ 5792.458116272716,
+ 5792.480324532992,
+ 5793.015455042023,
+ 5796.60089455924,
+ 5807.487720506598,
+ 5828.466808566305,
+ 5860.302704690473,
+ 5902.806132190803,
+ 5955.617692560522,
+ 6018.476555173027,
+ 6091.22088493709,
+ 6173.712994848893,
+ 6265.774934186432,
+ 6367.156018184099,
+ 6477.527620534776,
+ 6596.4943662401865,
+ 6723.612400966099,
+ 6858.408714377748,
+ 7000.398348669136,
+ 7149.098235746229,
+ 7304.03749984975,
+ 7464.76459503267,
+ 7630.851844526549,
+ 7801.897968265965,
+ 7977.529121766132,
+ 8157.3988778485345,
+ 8341.187489623593,
+ 8528.600690692962,
+ 8719.3682206193,
+ 8913.242210240971,
+ 9109.995520608829,
+ 9309.420098917732,
+ 9511.32539258518,
+ 9715.536846674577,
+ 9921.894498604659,
+ 10130.251676299567,
+ 10340.473800672748,
+ 10552.437289894393,
+ 10766.028560740182,
+ 10981.143121073083,
+ 11197.684746889987,
+ 11415.564737168335,
+ 11634.701239831205,
+ 11855.018642409685,
+ 12076.447021347745,
+ 12298.92164431772,
+ 12522.382520360086,
+ 12746.773993107487,
+ 12972.044372785333,
+ 13198.145603091058,
+ ],
+ rtol=1e-5,
+ atol=1e-5,
+ )
+ assert responses[job.jobs[-1].uuid][1].output.chemsys == "Si"
diff --git a/tests/vasp/flows/test_phonons.py b/tests/vasp/flows/test_phonons.py
index ddb6ac14d4..057a803988 100644
--- a/tests/vasp/flows/test_phonons.py
+++ b/tests/vasp/flows/test_phonons.py
@@ -123,8 +123,8 @@ def test_phonon_wf_vasp_only_displacements_no_structural_transformation(
):
# mapping from job name to directory containing test files
ref_paths = {
- "phonon static 1/1": "Si_phonons_3/phonon_static_1_1",
- "static": "Si_phonons_3/static",
+ "phonon static 1/1": "Si_phonons_2/phonon_static_1_1",
+ "static": "Si_phonons_2/static",
}
# settings passed to fake_run_vasp; adjust these to check for certain INCAR settings
@@ -145,33 +145,27 @@ def test_phonon_wf_vasp_only_displacements_no_structural_transformation(
store_force_constants=False,
prefer_90_degrees=False,
generate_frequencies_eigenvectors_kwargs={"tstep": 100},
- ).make(si_structure)
+ ).make(si_structure.to_conventional())
# run the flow or job and ensure that it finished running successfully
responses = run_locally(job, create_folders=True, ensure_success=True)
- # validate the outputs
- assert isinstance(responses[job.jobs[-1].uuid][1].output, PhononBSDOSDoc)
-
- assert_allclose(
- responses[job.jobs[-1].uuid][1].output.free_energies,
- [5927.157337, 5905.309813, 5439.530414, 4207.379685, 2297.576147],
- )
- assert_allclose(
- responses[job.jobs[-1].uuid][1].output.entropies,
- [0.0, 1.256496, 8.511348, 15.928285, 22.063785],
- atol=1e-6,
- )
- assert_allclose(
- responses[job.jobs[-1].uuid][1].output.heat_capacities,
- [0.0, 4.958763, 15.893881, 20.311967, 22.196143],
+ # validate settings
+ assert responses[job.jobs[-1].uuid][1].output.code == "vasp"
+ assert isinstance(
+ responses[job.jobs[-1].uuid][1].output.phonopy_settings,
+ PhononComputationalSettings,
)
-
- assert_allclose(
- responses[job.jobs[-1].uuid][1].output.internal_energies,
- [5927.157337, 6030.959432, 7141.800004, 8985.865319, 11123.090225],
+ assert responses[job.jobs[-1].uuid][1].output.phonopy_settings.npoints_band == 101
+ assert (
+ responses[job.jobs[-1].uuid][1].output.phonopy_settings.kpath_scheme
+ == "seekpath"
)
+ phonopy_settings = responses[job.jobs[-1].uuid][1].output.phonopy_settings
+ assert phonopy_settings.kpoint_density_dos == 7_000
+ # validate the outputs
+ assert isinstance(responses[job.jobs[-1].uuid][1].output, PhononBSDOSDoc)
assert isinstance(
responses[job.jobs[-1].uuid][1].output.phonon_bandstructure,
PhononBandStructureSymmLine,
@@ -187,7 +181,7 @@ def test_phonon_wf_vasp_only_displacements_no_structural_transformation(
assert isinstance(responses[job.jobs[-1].uuid][1].output.jobdirs, PhononJobDirs)
assert isinstance(responses[job.jobs[-1].uuid][1].output.uuids, PhononUUIDs)
assert_allclose(
- responses[job.jobs[-1].uuid][1].output.total_dft_energy, -5.74525804
+ responses[job.jobs[-1].uuid][1].output.total_dft_energy, -5.74555232
)
assert responses[job.jobs[-1].uuid][1].output.born is None
assert responses[job.jobs[-1].uuid][1].output.epsilon_static is None
@@ -196,40 +190,25 @@ def test_phonon_wf_vasp_only_displacements_no_structural_transformation(
)
assert_allclose(
responses[job.jobs[-1].uuid][1].output.primitive_matrix,
- ((0, 1, 0), (0, 0, 1), (1, 0, 0)),
+ ((0, 0.5, 0.5), (0.5, 0, 0.5), (0.5, 0.5, 0)),
atol=1e-8,
)
assert_allclose(
- responses[job.jobs[-1].uuid][1].output.primitive_matrix,
- ((0, 1, 0), (0, 0, 1), (1, 0, 0)),
- )
- assert responses[job.jobs[-1].uuid][1].output.code == "vasp"
- assert isinstance(
- responses[job.jobs[-1].uuid][1].output.phonopy_settings,
- PhononComputationalSettings,
- )
- assert responses[job.jobs[-1].uuid][1].output.phonopy_settings.npoints_band == 101
- assert (
- responses[job.jobs[-1].uuid][1].output.phonopy_settings.kpath_scheme
- == "seekpath"
+ responses[job.jobs[-1].uuid][1].output.free_energies,
+ [6115.980051, 6059.749756, 5490.929122, 4173.234384, 2194.164562],
)
- phonopy_settings = responses[job.jobs[-1].uuid][1].output.phonopy_settings
- assert phonopy_settings.kpoint_density_dos == 7_000
assert_allclose(
responses[job.jobs[-1].uuid][1].output.entropies,
- [0.0, 1.256496, 8.511348, 15.928285, 22.063785],
+ [0.0, 2.194216, 9.478603, 16.687079, 22.702177],
atol=1e-6,
)
assert_allclose(
responses[job.jobs[-1].uuid][1].output.heat_capacities,
- [0.0, 4.958763, 15.893881, 20.311967, 22.196143],
- atol=1e-6,
+ [0.0, 5.750113, 15.408866, 19.832123, 21.842104],
)
-
assert_allclose(
responses[job.jobs[-1].uuid][1].output.internal_energies,
- [5927.157337, 6030.959432, 7141.800004, 8985.865319, 11123.090225],
- atol=1e-6,
+ [6115.980051, 6279.17132, 7386.649622, 9179.358187, 11275.035523],
)
@@ -265,66 +244,41 @@ def test_phonon_wf_vasp_only_displacements_kpath(
responses = run_locally(job, create_folders=True, ensure_success=True)
# validate the outputs
- # print(type(responses))
- assert isinstance(responses[job.jobs[-1].uuid][1].output, PhononBSDOSDoc)
+ ph_doc = responses[job.jobs[-1].uuid][1].output
+ assert isinstance(ph_doc, PhononBSDOSDoc)
assert_allclose(
- responses[job.jobs[-1].uuid][1].output.free_energies,
+ ph_doc.free_energies,
[5776.14995034, 5617.74737777, 4725.50269363, 3043.81827626, 694.49078355],
atol=1e-3,
)
+ assert isinstance(ph_doc.phonon_bandstructure, PhononBandStructureSymmLine)
+ assert isinstance(ph_doc.phonon_dos, PhononDos)
+ assert isinstance(ph_doc.thermal_displacement_data, ThermalDisplacementData)
+ assert isinstance(ph_doc.structure, Structure)
+ assert_allclose(ph_doc.temperatures, [0, 100, 200, 300, 400])
+ assert ph_doc.has_imaginary_modes is False
+ force_const = ph_doc.force_constants.force_constants[0][0][0][0]
+ assert force_const == pytest.approx(13.032324)
+ assert isinstance(ph_doc.jobdirs, PhononJobDirs)
+ assert isinstance(ph_doc.uuids, PhononUUIDs)
+ assert ph_doc.total_dft_energy is None
+ assert ph_doc.born is None
+ assert ph_doc.epsilon_static is None
+ assert ph_doc.supercell_matrix == ((-1, 1, 1), (1, -1, 1), (1, 1, -1))
+ assert ph_doc.primitive_matrix == ((1, 0, 0), (0, 1, 0), (0, 0, 1))
+ assert ph_doc.code == "vasp"
assert isinstance(
- responses[job.jobs[-1].uuid][1].output.phonon_bandstructure,
- PhononBandStructureSymmLine,
- )
- assert isinstance(responses[job.jobs[-1].uuid][1].output.phonon_dos, PhononDos)
- assert isinstance(
- responses[job.jobs[-1].uuid][1].output.thermal_displacement_data,
- ThermalDisplacementData,
- )
- assert isinstance(responses[job.jobs[-1].uuid][1].output.structure, Structure)
- assert_allclose(
- responses[job.jobs[-1].uuid][1].output.temperatures, [0, 100, 200, 300, 400]
- )
- assert responses[job.jobs[-1].uuid][1].output.has_imaginary_modes is False
- assert_allclose(
- responses[job.jobs[-1].uuid][1].output.force_constants.force_constants[0][0][0][
- 0
- ],
- 13.032324,
- )
- assert isinstance(responses[job.jobs[-1].uuid][1].output.jobdirs, PhononJobDirs)
- assert isinstance(responses[job.jobs[-1].uuid][1].output.uuids, PhononUUIDs)
- assert responses[job.jobs[-1].uuid][1].output.total_dft_energy is None
- assert responses[job.jobs[-1].uuid][1].output.born is None
- assert responses[job.jobs[-1].uuid][1].output.epsilon_static is None
- assert_allclose(
- responses[job.jobs[-1].uuid][1].output.supercell_matrix,
- [[-1.0, 1.0, 1.0], [1.0, -1.0, 1.0], [1.0, 1.0, -1.0]],
- )
- assert_allclose(
- responses[job.jobs[-1].uuid][1].output.primitive_matrix,
- [[1, 0, 0], [0, 1, 0], [0, 0, 1]],
- atol=1e-8,
- )
- assert responses[job.jobs[-1].uuid][1].output.code == "vasp"
- assert isinstance(
- responses[job.jobs[-1].uuid][1].output.phonopy_settings,
+ ph_doc.phonopy_settings,
PhononComputationalSettings,
)
- assert responses[job.jobs[-1].uuid][1].output.phonopy_settings.npoints_band == 101
- assert (
- responses[job.jobs[-1].uuid][1].output.phonopy_settings.kpath_scheme
- == kpath_scheme
- )
- assert (
- responses[job.jobs[-1].uuid][1].output.phonopy_settings.kpoint_density_dos
- == 7_000
- )
+ assert ph_doc.phonopy_settings.npoints_band == 101
+ assert ph_doc.phonopy_settings.kpath_scheme == kpath_scheme
+ assert ph_doc.phonopy_settings.kpoint_density_dos == 7_000
-# test supply of born charges, epsilon, DFT energy, supercell
+# test supply of Born charges, epsilon, DFT energy, supercell
def test_phonon_wf_vasp_only_displacements_add_inputs_raises(
mock_vasp, clean_dir, si_structure: Structure
):
@@ -337,16 +291,9 @@ def test_phonon_wf_vasp_only_displacements_add_inputs_raises(
# automatically use fake VASP and write POTCAR.spec during the test
mock_vasp(ref_paths, fake_run_vasp_kwargs)
- born = [
- [[0, 0, 0], [0, 0, 0], [0, 0, 0]],
- [[0, 0, 0], [0, 0, 0], [0, 0, 0]],
- [[0, 0, 0], [0, 0, 0], [0, 0, 0.1]],
- ]
- epsilon_static = [
- [5.25, 0, 0],
- [0, 5.25, 0],
- [0, 0, 5.25],
- ]
+ born = np.zeros((3, 3))
+ born[-1, -1] = 0.1
+ epsilon_static = 5.25 * np.eye(3)
total_dft_energy_per_formula_unit = -5
job = PhononMaker(
@@ -368,7 +315,7 @@ def test_phonon_wf_vasp_only_displacements_add_inputs_raises(
run_locally(job, create_folders=True, ensure_success=True)
-# test supply of born charges, epsilon, DFT energy, supercell
+# test supply of Born charges, epsilon, DFT energy, supercell
def test_phonon_wf_vasp_only_displacements_add_inputs(
mock_vasp, clean_dir, si_structure: Structure
):
@@ -574,11 +521,11 @@ def test_phonon_wf_vasp_only_displacements_optional_settings(
def test_phonon_wf_vasp_all_steps(mock_vasp, clean_dir, si_structure: Structure):
# mapping from job name to directory containing test files
ref_paths = {
- "phonon static 1/1": "Si_phonons_4/phonon_static_1_1",
- "static": "Si_phonons_4/static",
- "tight relax 1": "Si_phonons_4/tight_relax_1",
- "tight relax 2": "Si_phonons_4/tight_relax_2",
- "dielectric": "Si_phonons_4/dielectric",
+ "phonon static 1/1": "Si_phonons_3/phonon_static_1_1",
+ "static": "Si_phonons_3/static",
+ "tight relax 1": "Si_phonons_3/tight_relax_1",
+ "tight relax 2": "Si_phonons_3/tight_relax_2",
+ "dielectric": "Si_phonons_3/dielectric",
}
# settings passed to fake_run_vasp; adjust these to check for certain INCAR settings
diff --git a/tests/vasp/flows/test_qha.py b/tests/vasp/flows/test_qha.py
index 066304dc6b..9c8b4f6670 100644
--- a/tests/vasp/flows/test_qha.py
+++ b/tests/vasp/flows/test_qha.py
@@ -22,9 +22,10 @@ def test_qha(mock_vasp, clean_dir, si_diamond: Structure):
qha_maker = QhaMaker(
number_of_frames=0,
- phonon_maker=PhononMaker(min_length=8, born_maker=None, bulk_relax_maker=None),
+ phonon_maker=PhononMaker(born_maker=None, bulk_relax_maker=None),
ignore_imaginary_modes=True,
skip_analysis=True,
+ min_length=8,
).make(structure=si_diamond)
qha_maker = update_user_incar_settings(qha_maker, {"NPAR": 4, "ISMEAR": 0})
diff --git a/tests/vasp/test_sets.py b/tests/vasp/test_sets.py
index 18ea272972..30372ad250 100644
--- a/tests/vasp/test_sets.py
+++ b/tests/vasp/test_sets.py
@@ -52,7 +52,7 @@ def test_user_incar_settings():
# check to see if user incar settings (even when set to nonsensical values, as done
# below) are always preserved.
- uis = {
+ user_settings = {
"ALGO": "VeryFast",
"EDIFF": 1e-30,
"EDIFFG": -1e-10,
@@ -80,16 +80,16 @@ def test_user_incar_settings():
"LDAUTYPE": 2,
}
- static_set_generator = StaticSetGenerator(user_incar_settings=uis)
+ static_set_generator = StaticSetGenerator(user_incar_settings=user_settings)
incar = static_set_generator.get_input_set(structure, potcar_spec=True)["INCAR"]
- for key in uis:
+ for key, val in user_settings.items():
if isinstance(incar[key], str):
- assert incar[key].lower() == uis[key].lower()
- elif isinstance(uis[key], dict):
- assert incar[key] == [uis[key][str(site.specie)] for site in structure]
+ assert incar[key].lower() == val.lower()
+ elif isinstance(val, dict):
+ assert incar[key] == [val[str(site.specie)] for site in structure]
else:
- assert incar[key] == uis[key]
+ assert incar[key] == val
@pytest.mark.parametrize(