Skip to content

Commit cc932c8

Browse files
committed
split vasp sets into package with smaller more navigable submodules
matches atomate2 code structure with base.py, mp.py, matpes.py, etc.
1 parent d599611 commit cc932c8

File tree

11 files changed

+3681
-3189
lines changed

11 files changed

+3681
-3189
lines changed

dev_scripts/potcar_scrambler.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
from pymatgen.core import SETTINGS
1414
from pymatgen.io.vasp import Potcar, PotcarSingle
15-
from pymatgen.io.vasp.sets import _load_yaml_config
15+
from pymatgen.io.vasp.sets.base import _load_yaml_config
1616
from pymatgen.util.testing import VASP_IN_DIR
1717

1818
if TYPE_CHECKING:

pymatgen/io/vasp/sets.py

Lines changed: 0 additions & 3188 deletions
This file was deleted.

pymatgen/io/vasp/sets/__init__.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
"""
2+
This module defines the VaspInputSet abstract base class and a concrete implementation for the parameters developed
3+
and tested by the core team of pymatgen, including the Materials Virtual Lab, Materials Project and the MIT high
4+
throughput project. The basic concept behind an input set is to specify a scheme to generate a consistent set of VASP
5+
inputs from a structure without further user intervention. This ensures comparability across runs.
6+
7+
Read the following carefully before implementing new input sets:
8+
9+
1. 99% of what needs to be done can be done by specifying user_incar_settings to override some of the defaults of
10+
various input sets. Unless there is an extremely good reason to add a new set, **do not** add one. e.g. if you want
11+
to turn the Hubbard U off, just set "LDAU": False as a user_incar_setting.
12+
2. All derivative input sets should inherit appropriate configurations (e.g., from MPRelaxSet), and more often than
13+
not, VaspInputSet should be the superclass. Superclass delegation should be used where possible. In particular,
14+
you are not supposed to implement your own as_dict or from_dict for derivative sets unless you know what you are
15+
doing. Improper overriding the as_dict and from_dict protocols is the major cause of implementation headaches. If
16+
you need an example, look at how the MPStaticSet is initialized.
17+
18+
The above are recommendations. The following are **UNBREAKABLE** rules:
19+
20+
1. All input sets must take in a structure, list of structures or None as the first argument. If None, the input set
21+
should perform a stateless initialization and before any output can be written, a structure must be set.
22+
2. user_incar_settings, user_kpoints_settings and user_<whatever>_settings are ABSOLUTE. Any new sets you implement
23+
must obey this. If a user wants to override your settings, you assume he knows what he is doing. Do not
24+
magically override user supplied settings. You can issue a warning if you think the user is wrong.
25+
3. All input sets must save all supplied args and kwargs as instance variables. e.g. self.arg = arg and
26+
self.kwargs = kwargs in the __init__. This ensures the as_dict and from_dict work correctly.
27+
"""
28+
29+
from __future__ import annotations
30+
31+
from pymatgen.io.vasp.sets.base import DictSet, VaspInputGenerator, VaspInputSet
32+
from pymatgen.io.vasp.sets.lobster import LobsterSet
33+
from pymatgen.io.vasp.sets.matpes import MatPESStaticSet
34+
from pymatgen.io.vasp.sets.mit import MITMDSet, MITNEBSet, MITRelaxSet
35+
from pymatgen.io.vasp.sets.mp import (
36+
MPAbsorptionSet,
37+
MPHSEBSSet,
38+
MPHSERelaxSet,
39+
MPMDSet,
40+
MPMetalRelaxSet,
41+
MPNMRSet,
42+
MPNonSCFSet,
43+
MPRelaxSet,
44+
MPScanRelaxSet,
45+
MPScanStaticSet,
46+
MPSOCSet,
47+
MPStaticSet,
48+
)
49+
from pymatgen.io.vasp.sets.mvl import (
50+
MVLElasticSet,
51+
MVLGBSet,
52+
MVLGWSet,
53+
MVLNPTMDSet,
54+
MVLRelax52Set,
55+
MVLScanRelaxSet,
56+
MVLSlabSet,
57+
)
58+
from pymatgen.io.vasp.sets.utils import (
59+
MODULE_DIR,
60+
BadInputSetWarning,
61+
_apply_incar_updates,
62+
_combine_kpoints,
63+
_get_ispin,
64+
_get_recommended_lreal,
65+
_remove_unused_incar_params,
66+
auto_kspacing,
67+
batch_write_input,
68+
get_structure_from_prev_run,
69+
get_valid_magmom_struct,
70+
get_vasprun_outcar,
71+
standardize_structure,
72+
)

pymatgen/io/vasp/sets/base.py

Lines changed: 1191 additions & 0 deletions
Large diffs are not rendered by default.

pymatgen/io/vasp/sets/common.py

Lines changed: 292 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,292 @@
1+
from __future__ import annotations
2+
3+
from collections.abc import Sequence
4+
from glob import glob
5+
from pathlib import Path
6+
from typing import TYPE_CHECKING, cast
7+
8+
import numpy as np
9+
10+
from pymatgen.analysis.structure_matcher import StructureMatcher
11+
from pymatgen.io.vasp.inputs import Kpoints
12+
from pymatgen.io.vasp.outputs import Outcar, Vasprun
13+
from pymatgen.symmetry.analyzer import SpacegroupAnalyzer
14+
from pymatgen.util.typing import Kpoint
15+
16+
if TYPE_CHECKING:
17+
from typing import Literal, Union
18+
19+
from pymatgen.core import Structure
20+
21+
UserPotcarFunctional = Union[
22+
Literal["PBE", "PBE_52", "PBE_54", "LDA", "LDA_52", "LDA_54", "PW91", "LDA_US", "PW91_US"], None
23+
]
24+
25+
26+
class BadInputSetWarning(UserWarning):
27+
"""Warning class for bad but legal VASP inputs."""
28+
29+
30+
def _get_ispin(vasprun: Vasprun | None, outcar: Outcar | None) -> int:
31+
"""Get value of ISPIN depending on the magnetisation in the OUTCAR and vasprun."""
32+
if outcar is not None and outcar.magnetization is not None:
33+
# Turn off spin when magmom for every site is smaller than 0.02.
34+
site_magmom = np.array([i["tot"] for i in outcar.magnetization])
35+
return 2 if np.any(np.abs(site_magmom) > 0.02) else 1
36+
if vasprun is not None:
37+
return 2 if vasprun.is_spin else 1
38+
return 2
39+
40+
41+
def _get_recommended_lreal(structure: Structure) -> str | bool:
42+
"""Get recommended LREAL flag based on the structure."""
43+
return "Auto" if structure.num_sites > 16 else False
44+
45+
46+
def _combine_kpoints(*kpoints_objects: Kpoints) -> Kpoints:
47+
"""Combine multiple Kpoints objects."""
48+
_labels: list[list[str]] = []
49+
_kpoints: list[Sequence[Kpoint]] = []
50+
_weights = []
51+
52+
for kpoints_object in filter(None, kpoints_objects):
53+
if kpoints_object.style != Kpoints.supported_modes.Reciprocal:
54+
raise ValueError("Can only combine kpoints with style=Kpoints.supported_modes.Reciprocal")
55+
if kpoints_object.labels is None:
56+
_labels.append([""] * len(kpoints_object.kpts))
57+
else:
58+
_labels.append(kpoints_object.labels)
59+
60+
_kpoints.append(kpoints_object.kpts)
61+
_weights.append(kpoints_object.kpts_weights)
62+
63+
labels = np.concatenate(_labels).tolist()
64+
kpoints = np.concatenate(_kpoints).tolist()
65+
weights = np.concatenate(_weights).tolist()
66+
67+
return Kpoints(
68+
comment="Combined k-points",
69+
style=Kpoints.supported_modes.Reciprocal,
70+
num_kpts=len(kpoints),
71+
kpts=cast(Sequence[Kpoint], kpoints),
72+
labels=labels,
73+
kpts_weights=weights,
74+
)
75+
76+
77+
def _apply_incar_updates(incar, updates, skip: Sequence[str] = ()) -> None:
78+
"""
79+
Apply updates to an INCAR file.
80+
81+
Args:
82+
incar (dict): An incar.
83+
updates (dict): Updates to apply.
84+
skip (list of str): Keys to skip.
85+
"""
86+
for k, v in updates.items():
87+
if k in skip:
88+
continue
89+
90+
if v is None:
91+
incar.pop(k, None)
92+
else:
93+
incar[k] = v
94+
95+
96+
def _remove_unused_incar_params(incar, skip: Sequence[str] = ()) -> None:
97+
"""
98+
Remove INCAR parameters that are not actively used by VASP.
99+
100+
Args:
101+
incar (dict): An incar.
102+
skip (list of str): Keys to skip.
103+
"""
104+
# Turn off IBRION/ISIF/POTIM if NSW = 0
105+
opt_flags = ["EDIFFG", "IBRION", "ISIF", "POTIM"]
106+
if incar.get("NSW", 0) == 0:
107+
for opt_flag in opt_flags:
108+
if opt_flag not in skip:
109+
incar.pop(opt_flag, None)
110+
111+
# Remove MAGMOMs if they aren't used
112+
if incar.get("ISPIN", 1) == 1 and "MAGMOM" not in skip:
113+
incar.pop("MAGMOM", None)
114+
115+
# Turn off +U flags if +U is not even used
116+
ldau_flags = ["LDAUU", "LDAUJ", "LDAUL", "LDAUTYPE"]
117+
if incar.get("LDAU", False) is False:
118+
for ldau_flag in ldau_flags:
119+
if ldau_flag not in skip:
120+
incar.pop(ldau_flag, None)
121+
122+
123+
def _get_nedos(vasprun: Vasprun | None, dedos: float) -> int:
124+
"""Automatic setting of nedos using the energy range and the energy step."""
125+
if vasprun is None:
126+
return 2000
127+
128+
emax = max(eigs.max() for eigs in vasprun.eigenvalues.values())
129+
emin = min(eigs.min() for eigs in vasprun.eigenvalues.values())
130+
return int((emax - emin) / dedos)
131+
132+
133+
def auto_kspacing(bandgap, bandgap_tol):
134+
"""Automatically set kspacing based on the bandgap"""
135+
if bandgap is None or bandgap <= bandgap_tol: # metallic
136+
return 0.22
137+
138+
rmin = max(1.5, 25.22 - 2.87 * bandgap) # Eq. 25
139+
kspacing = 2 * np.pi * 1.0265 / (rmin - 1.0183) # Eq. 29
140+
141+
# cap kspacing at a max of 0.44, per internal benchmarking
142+
return min(kspacing, 0.44)
143+
144+
145+
def get_valid_magmom_struct(structure: Structure, inplace: bool = True, spin_mode: str = "auto") -> Structure:
146+
"""
147+
Make sure that the structure has valid magmoms based on the kind of calculation.
148+
149+
Fill in missing Magmom values.
150+
151+
Args:
152+
structure: The input structure
153+
inplace: True: edit magmoms of the input structure; False: return new structure
154+
spin_mode: "scalar"/"vector"/"none"/"auto" only first letter (s/v/n) is needed.
155+
dictates how the spin configuration will be determined.
156+
157+
- auto: read the existing magmom values and decide
158+
- scalar: use a single scalar value (for spin up/down)
159+
- vector: use a vector value for spin-orbit systems
160+
- none: Remove all the magmom information
161+
162+
Returns:
163+
New structure if inplace is False
164+
"""
165+
default_values = {"s": 1.0, "v": [1.0, 1.0, 1.0], "n": None}
166+
if spin_mode[0].lower() == "a":
167+
mode = "n"
168+
for site in structure:
169+
if "magmom" not in site.properties or site.properties["magmom"] is None:
170+
pass
171+
elif isinstance(site.properties["magmom"], (float, int)):
172+
if mode == "v":
173+
raise TypeError("Magmom type conflict")
174+
mode = "s"
175+
if isinstance(site.properties["magmom"], int):
176+
site.properties["magmom"] = float(site.properties["magmom"])
177+
elif len(site.properties["magmom"]) == 3:
178+
if mode == "s":
179+
raise TypeError("Magmom type conflict")
180+
mode = "v"
181+
else:
182+
raise TypeError("Unrecognized Magmom Value")
183+
else:
184+
mode = spin_mode[0].lower()
185+
186+
ret_struct = structure if inplace else structure.copy()
187+
for site in ret_struct:
188+
if mode == "n":
189+
if "magmom" in site.properties:
190+
site.properties.pop("magmom")
191+
elif "magmom" not in site.properties or site.properties["magmom"] is None:
192+
site.properties["magmom"] = default_values[mode]
193+
194+
return ret_struct
195+
196+
197+
def get_structure_from_prev_run(vasprun, outcar=None) -> Structure:
198+
"""
199+
Process structure from previous run.
200+
201+
Args:
202+
vasprun (Vasprun): Vasprun that contains the final structure from previous run.
203+
outcar (Outcar): Outcar that contains the magnetization info from previous run.
204+
205+
Returns:
206+
Structure: The magmom-decorated structure that can be passed to get VASP input files, e.g.
207+
get_kpoints().
208+
"""
209+
structure = vasprun.final_structure
210+
211+
site_properties = {}
212+
# magmom
213+
if vasprun.is_spin:
214+
if outcar and outcar.magnetization:
215+
site_properties["magmom"] = [i["tot"] for i in outcar.magnetization]
216+
else:
217+
site_properties["magmom"] = vasprun.parameters["MAGMOM"]
218+
# LDAU
219+
if vasprun.parameters.get("LDAU", False):
220+
for key in ("LDAUU", "LDAUJ", "LDAUL"):
221+
vals = vasprun.incar[key]
222+
m = {}
223+
l_val = []
224+
s = 0
225+
for site in structure:
226+
if site.specie.symbol not in m:
227+
m[site.specie.symbol] = vals[s]
228+
s += 1
229+
l_val.append(m[site.specie.symbol])
230+
if len(l_val) == len(structure):
231+
site_properties.update({key.lower(): l_val})
232+
else:
233+
raise ValueError(f"length of list {l_val} not the same as structure")
234+
235+
return structure.copy(site_properties=site_properties)
236+
237+
238+
def standardize_structure(structure, sym_prec=0.1, international_monoclinic=True):
239+
"""Get the symmetrically standardized structure.
240+
241+
Args:
242+
structure (Structure): The structure.
243+
sym_prec (float): Tolerance for symmetry finding for standardization.
244+
international_monoclinic (bool): Whether to use international
245+
convention (vs Curtarolo) for monoclinic. Defaults True.
246+
247+
Returns:
248+
The symmetrized structure.
249+
"""
250+
sym_finder = SpacegroupAnalyzer(structure, symprec=sym_prec)
251+
new_structure = sym_finder.get_primitive_standard_structure(international_monoclinic=international_monoclinic)
252+
253+
# the primitive structure finding has had several bugs in the past
254+
# defend through validation
255+
vpa_old = structure.volume / len(structure)
256+
vpa_new = new_structure.volume / len(new_structure)
257+
258+
if abs(vpa_old - vpa_new) / vpa_old > 0.02:
259+
raise ValueError(f"Standardizing cell failed! VPA old: {vpa_old}, VPA new: {vpa_new}")
260+
261+
matcher = StructureMatcher()
262+
if not matcher.fit(structure, new_structure):
263+
raise ValueError("Standardizing cell failed! Old structure doesn't match new.")
264+
265+
return new_structure
266+
267+
268+
def get_vasprun_outcar(path: str | Path, parse_dos: bool = True, parse_eigen: bool = True) -> tuple[Vasprun, Outcar]:
269+
"""Get a Vasprun and Outcar from a directory.
270+
271+
Args:
272+
path: Path to get the vasprun.xml and OUTCAR.
273+
parse_dos: Whether to parse dos. Defaults to True.
274+
parse_eigen: Whether to parse eigenvalue. Defaults to True.
275+
276+
Returns:
277+
Vasprun and Outcar files.
278+
"""
279+
path = Path(path)
280+
vruns = list(glob(str(path / "vasprun.xml*")))
281+
outcars = list(glob(str(path / "OUTCAR*")))
282+
283+
if len(vruns) == 0 or len(outcars) == 0:
284+
raise ValueError(f"Unable to get vasprun.xml/OUTCAR from prev calculation in {path}")
285+
vs_file_fullpath = str(path / "vasprun.xml")
286+
outcar_fullpath = str(path / "OUTCAR.gz")
287+
vs_file = vs_file_fullpath if vs_file_fullpath in vruns else max(vruns)
288+
outcar_file = outcar_fullpath if outcar_fullpath in outcars else max(outcars)
289+
return (
290+
Vasprun(vs_file, parse_dos=parse_dos, parse_eigen=parse_eigen),
291+
Outcar(outcar_file),
292+
)

0 commit comments

Comments
 (0)