Source code for aiida_vasp.inputset.pmgset
"""
Module for using pymatgen.io.vasp.sets based input sets.
"""
from typing import Dict, Optional, Union
import numpy as np
from aiida import orm
from .base import InputSet
try:
import pymatgen.io.vasp.sets as pmg_sets
from pymatgen.io.vasp.inputs import KpointsSupportedModes
except ImportError:
pmg_sets = None
[docs]
class PymatgenInputSet(InputSet):
"""
Input set using pymatgen.io.vasp.sets.
Provides basic compatibility with pymatgen sets for generating VASP input
parameters, k-point meshes, and pseudopotential mappings.
"""
# An none-exhaust list of supported pymatgen input sets
KNOWN_SETS = (
'MPRelaxSet',
'MITRelaxSet',
'MPScanRelaxSet',
'MP24RelaxSet',
'MPMetalRelaxSet',
'MPHSERelaxSet',
'MVLGWSet',
'MPAbsorptionSet',
'MatPESStaticSet',
'MPScanStaticSet',
'MP24StaticSet',
'MPHSEBSSet',
'MPNonSCFSet',
'MPSOCSet',
'MPNMRSet',
'MPStaticSet',
)
def __init__(
self,
set_name: str,
overrides: Optional[Dict] = None,
verbose: Optional[bool] = None,
pmg_kwargs: Optional[Dict] = None,
) -> None:
"""
Instantiate a PymatgenInputSet.
:param set_name: Name of the pymatgen input set to use
:type set_name: str
:param overrides: Dictionary of parameter overrides for the input set
:type overrides: dict or None
:param verbose: If True, print additional information during processing
:type verbose: bool or None
:param pmg_kwargs: Additional keyword arguments to pass to the pymatgen input set
:type pmg_kwargs: dict or None
:raises AssertionError: If set_name is not in KNOWN_SETS
"""
assert set_name in self.KNOWN_SETS, f'Unsupported set name: {set_name}'
super().__init__(set_name, overrides=overrides, verbose=verbose)
self._pmg_kwargs = pmg_kwargs or {}
[docs]
def _load_data(self) -> None:
"""
Load the pymatgen input set class.
Dynamically imports and stores the pymatgen input set class based on set_name.
:raises ImportError: If pymatgen is not installed or cannot be imported
"""
if pmg_sets is None:
raise ImportError('pymatgen is not installed. Please install it to use PymatgenInputSet.')
self._pmg_class = getattr(pmg_sets, self.set_name)
[docs]
def get_input_dict(self, structure: orm.StructureData, raw_python: bool = True) -> Union[Dict, orm.Dict]:
"""
Compute the input parameters for a VASP calculation using pymatgen.io.vasp.sets.
Generates INCAR parameters by instantiating the pymatgen input set with the
given structure and applying any specified overrides. Removes certain
parameters that conflict with aiida-vasp's input validation.
:param structure: Crystal structure for the calculation
:type structure: orm.StructureData
:param raw_python: If True, return a Python dict; if False, return orm.Dict
:type raw_python: bool
:returns: Dictionary of INCAR parameters
:rtype: dict or orm.Dict
"""
ps = structure.get_pymatgen()
pmgset = self._pmg_class(ps, **self._pmg_kwargs)
incar_dict = {key.lower(): value for key, value in pmgset.incar.items()}
# Apply the overrides
for key, value in self.overrides.items():
if value is None:
if key in incar_dict:
incar_dict.pop(key)
else:
incar_dict[key] = value
# pop icharg which conflicts with aiida-vasp's input checks
incar_dict.pop('icharg', None)
incar_dict.pop('istart', None)
incar_dict.pop('kspacing', None)
if raw_python:
return incar_dict
return orm.Dict(dict=incar_dict)
[docs]
def get_pp_mapping(self, structure: orm.StructureData) -> Dict[str, str]:
"""
Get the pseudopotential mapping used by the input set.
Returns a dictionary mapping element symbols to their corresponding
pseudopotential symbols as defined by the pymatgen input set.
:param structure: Crystal structure for the calculation
:type structure: orm.StructureData
:returns: Dictionary mapping element names to pseudopotential symbols
:rtype: dict
"""
ps = structure.get_pymatgen()
pmgset = self._pmg_class(ps, **self._pmg_kwargs)
return {p.element: p.symbol for p in pmgset.potcar}
[docs]
def get_potcar_family(self) -> str:
"""
Get the POTCAR family used by the input set.
Retrieves the pseudopotential functional family from the pymatgen
input set configuration. Converts underscore notation to dot notation
(e.g., PBE_54 becomes PBE.54) for aiida-vasp compatibility.
:returns: Name of the POTCAR family
:rtype: str
"""
return self._pmg_class.CONFIG['POTCAR_FUNCTIONAL'].replace('_', '.')
[docs]
def get_kpoints(self, structure: orm.StructureData) -> Optional[orm.KpointsData]:
"""
Return a KpointsData object for the given structure.
Converts the k-point specification from the pymatgen input set to an
aiida-vasp compatible KpointsData object. Supports Gamma-centered,
Monkhorst-Pack, and automatic k-point generation modes.
:param structure: Crystal structure for k-point generation
:type structure: orm.StructureData
:returns: K-points data object, or None if no k-points are specified
:rtype: orm.KpointsData or None
"""
ps = structure.get_pymatgen()
pmgset = self._pmg_class(ps, **self._pmg_kwargs)
if pmgset.kpoints is None:
return None
# Currently only supports Gamma and Monkhorst-Pack
kpoints_data = pmg_kpoints2kpointsdata(pmgset.kpoints, structure)
return kpoints_data
[docs]
def get_kpoints_spacing(self, structure: orm.StructureData) -> Optional[float]:
"""
Get the k-point spacing used by the input set.
Extracts the KSPACING parameter from the pymatgen input set and converts
it to the format expected by aiida-vasp (dividing by 2π).
:param structure: Crystal structure for the calculation
:type structure: orm.StructureData
:returns: K-point spacing value or None if not specified
:rtype: float or None
"""
ps = structure.get_pymatgen()
pmgset = self._pmg_class(ps, **self._pmg_kwargs)
incar_dict = {key.lower(): value for key, value in pmgset.incar.items()}
kspacing = incar_dict.pop('kspacing', None)
if kspacing is not None:
return kspacing / np.pi / 2
return None
[docs]
def pmg_kpoints2kpointsdata(pmg_kpoints, structure: orm.StructureData) -> orm.KpointsData:
"""
Convert a pymatgen Kpoints object to an AiiDA KpointsData object.
Handles conversion between different k-point generation modes:
- Gamma-centered grids
- Monkhorst-Pack grids (with appropriate shift corrections)
- Automatic k-point generation based on spacing
:param pmg_kpoints: Pymatgen Kpoints object to convert
:type pmg_kpoints: pymatgen.io.vasp.inputs.Kpoints
:param structure: AiiDA structure data for setting the unit cell
:type structure: orm.StructureData
:returns: Converted k-points data object
:rtype: orm.KpointsData
:raises ValueError: If the k-point style is not supported
"""
# Currently only supports Gamma and Monkhorst-Pack
style = pmg_kpoints.style
mesh = pmg_kpoints.kpts[0]
kpoints = orm.KpointsData()
if style == KpointsSupportedModes.Gamma:
# aiida-vasp defaults to use Gamma-centering mode when constructing the KPOINTS file
gamma_shifts = (0, 0, 0)
elif style == KpointsSupportedModes.Monkhorst:
# If a MP grid is supplied, we just add a -0.5 shift to the gamma-centering grid to make it
# equivalent to a MP centred grid
# See https://www.vasp.at/wiki/index.php/KPOINTS
gamma_shifts = []
for i in mesh:
if i % 2 == 1:
# Odd division - do nothing
gamma_shifts.append(0)
else:
# Even division - add shifts
gamma_shifts.append(-0.5)
elif style == KpointsSupportedModes.Automatic:
# The automatic mode is used with a length R_k
# We convert it to a mesh based on the structure's lattice
# See: https://www.vasp.at/wiki/index.php/KPOINTS#Automatic_k-point_mesh
kspacing = 2 * np.pi / pmg_kpoints.kpts[0][0]
kpoints.set_cell_from_structure(structure)
kpoints.set_kpoints_mesh_from_density(kspacing)
return kpoints
else:
raise ValueError(f'Unsupported kpoint style: {style}')
# Using explicit meshes
shifts = pmg_kpoints.kpts_shift
# Construct AiiDA KpointsData object
kpoints.set_kpoints_mesh(mesh, offset=[i + j for i, j in zip(shifts, gamma_shifts)])
kpoints.set_cell_from_structure(structure)
return kpoints