Skip to content

Commit

Permalink
Merge branch 'materialsproject:main' into update_lobster_outputs
Browse files Browse the repository at this point in the history
  • Loading branch information
naik-aakash authored Dec 7, 2024
2 parents 3e6dbed + 44a1125 commit 47f486d
Show file tree
Hide file tree
Showing 124 changed files with 581 additions and 347 deletions.
4 changes: 2 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -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:
Expand Down
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
13 changes: 6 additions & 7 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,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",
Expand All @@ -89,7 +89,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",
Expand All @@ -98,17 +98,17 @@ 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",
"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",
Expand Down Expand Up @@ -207,7 +207,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
Expand Down
2 changes: 1 addition & 1 deletion src/atomate2/ase/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
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
4 changes: 3 additions & 1 deletion src/atomate2/common/flows/phonons.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -185,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
76 changes: 55 additions & 21 deletions src/atomate2/common/flows/qha.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
----------
Expand Down Expand Up @@ -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"
Expand All @@ -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.
Expand All @@ -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 = []

Expand All @@ -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:
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
18 changes: 9 additions & 9 deletions src/atomate2/common/jobs/electrode.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Loading

0 comments on commit 47f486d

Please sign in to comment.