Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/main' into neb
Browse files Browse the repository at this point in the history
  • Loading branch information
esoteric-ephemera committed Nov 27, 2024
2 parents 3b0e5a2 + 6349766 commit 8168097
Show file tree
Hide file tree
Showing 17 changed files with 196 additions and 121 deletions.
1 change: 1 addition & 0 deletions docs/user/codes/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ The section gives the instructions for codes supported by atomate2.

```{toctree}
vasp
openmm
```
39 changes: 34 additions & 5 deletions docs/user/codes/vasp.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
(codes.vasp)=

# VASP

At present, most workflows in atomate2 use the Vienna *ab initio* simulation package
Expand Down Expand Up @@ -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 r<sup>2</sup>SCAN 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 r<sup>2</sup>SCAN 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 r<sup>2</sup>SCAN 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` Å<sup>-1</sup>, 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

Expand Down
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -98,9 +98,9 @@ strict = [
"click==8.1.7",
"custodian==2024.10.16",
"dscribe==2.1.1",
"emmet-core==0.84.3rc3",
"emmet-core==0.84.3rc4",
"ijson==3.3.0",
"jobflow==0.1.18",
"jobflow==0.1.19",
"lobsterpy==0.4.9",
"mdanalysis==2.7.0",
"monty==2024.10.21",
Expand Down
2 changes: 1 addition & 1 deletion src/atomate2/common/flows/anharmonicity.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion src/atomate2/common/flows/gruneisen.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion src/atomate2/common/flows/phonons.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,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
Expand Down
27 changes: 9 additions & 18 deletions src/atomate2/common/flows/qha.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,28 +25,24 @@
from atomate2.common.flows.phonons import BasePhononMaker
from atomate2.forcefields.jobs import ForceFieldRelaxMaker
from atomate2.vasp.jobs.core import BaseVaspMaker

supported_eos = frozenset(("vinet", "birch_murnaghan", "murnaghan"))


@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 Gibbs 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
----------
Expand Down Expand Up @@ -84,7 +80,6 @@ class CommonQhaMaker(Maker, ABC):
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"
Expand Down Expand Up @@ -128,10 +123,7 @@ def make(
.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 = []

Expand All @@ -148,7 +140,7 @@ def make(
eos_job = self.eos.make(structure)
qha_jobs.append(eos_job)

# implement a supercell job to get matrix for just the equillibrium structure
# 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,
Expand All @@ -162,7 +154,6 @@ def make(
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,
Expand Down
18 changes: 8 additions & 10 deletions src/atomate2/common/jobs/anharmonicity.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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(
Expand Down
8 changes: 5 additions & 3 deletions src/atomate2/common/jobs/gruneisen.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand Down
4 changes: 1 addition & 3 deletions src/atomate2/common/jobs/qha.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,7 @@ def get_supercell_size(
)


@job(
data=[PhononBSDOSDoc],
)
@job(data=[PhononBSDOSDoc])
def get_phonon_jobs(
phonon_maker: BasePhononMaker, eos_output: dict, supercell_matrix: list[list[float]]
) -> Flow:
Expand Down
13 changes: 8 additions & 5 deletions src/atomate2/common/schemas/phonons.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
)

Expand Down Expand Up @@ -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
"""
Expand Down Expand Up @@ -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 = {
Expand Down Expand Up @@ -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
)
Expand All @@ -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"])

Expand Down
14 changes: 14 additions & 0 deletions src/atomate2/forcefields/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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+
Expand All @@ -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:
"""
Expand Down
Loading

0 comments on commit 8168097

Please sign in to comment.