Source code for aiida_vasp.inputset.base
"""
Module for preparing standardised input for calculations
"""
import logging
from copy import deepcopy
from itertools import chain
from math import pi
from pathlib import Path
from typing import Any
import yaml
from aiida.orm import Dict, KpointsData, StructureData
logger = logging.getLogger(__name__)
FELEMS = [
'La',
'Ce',
'Pr',
'Nd',
'Pm',
'Sm',
'Eu',
'Gd',
'Tb',
'Dy',
'Ho',
'Er',
'Tm',
'Yb',
'Lu',
'Ac',
'Th',
'Pa',
'U',
'Np',
'Pu',
'Am',
'Cm',
'Bk',
'Cf',
'Es',
'Fm',
'Md',
'No',
'Lr',
]
[docs]
def get_library_path() -> Path:
"""Get the path where the YAML files are stored within this package"""
return Path(__file__).parent
[docs]
def list_inputsets() -> list[Path]:
"""
List all available input sets in the package.
"""
_load_paths = (get_library_path(), Path('~/.aiida-vasp').expanduser())
inputsets = []
for parent in _load_paths:
files = chain(parent.glob('*.yaml'), parent.glob('*.yml'))
for file in files:
with open(file) as fh:
data = yaml.safe_load(fh)
if 'global' in data:
inputsets.append(file.absolute())
return inputsets
[docs]
class InputSet:
"""
Base class representing an inputs set.
Not useful on its own, should be subclass for convenient definition of inputs
for high-throughput calculations.
"""
# path from which the set yaml files are read
_load_paths = (get_library_path(), Path('~/.aiida-vasp').expanduser())
def __init__(self, set_name, overrides=None, verbose=False):
"""
Initialise an InputSet
Args:
set_name: Name of the set to be loaded
overrides: A dictionary of overriding inputs, the keys should be in lower case.
"""
self.set_name = set_name
if overrides is None:
overrides = {}
self.overrides = convert_lowercase(overrides)
self._presets = None
self.verbose = verbose
self._load_data()
[docs]
def get_input_dict(self, structure: StructureData, raw_python: bool = True) -> Dict | dict[str, Any]:
"""
Get a input dictionary for VASP
"""
out_dict = deepcopy(self._presets['global'])
# Set-per atom properties
natoms = len(structure.sites)
for key, value in self._presets.get('per_atom', {}).items():
out_dict[key] = value * natoms
self.apply_overrides(out_dict)
if raw_python:
return out_dict
return Dict(dict=out_dict)
[docs]
def _load_data(self) -> None:
"""Load stored data"""
set_path = None
for parent in self._load_paths:
set_path = parent / (self.set_name + '.yaml')
if set_path.is_file():
break
if set_path is None:
raise RuntimeError(f'Cannot find input set definition for {self.set_name}')
if self.verbose:
print(f'Using input set file at: {set_path}')
with open(set_path, encoding='utf-8') as fhd:
self._presets = yaml.load(fhd, Loader=yaml.FullLoader)
[docs]
def apply_overrides(self, out_dict: dict[str, Any]) -> None:
"""Apply overrides stored in self.overrides to the dictionary passed"""
for name, value in self.overrides.items():
# Keys ends with '_mapping' are treated differently here
# Those valuse should have been applied already implemented in the `get_input_dict` method.
if '_mapping' in name or '_list' in name or '_family' in name:
continue
# Delete the key
if value is None:
out_dict.pop(name, None)
else:
out_dict[name] = value
[docs]
def get_kpoints(self, structure: StructureData, density: float | None = None) -> KpointsData:
"""
Return a kpoints object for a given density
Args:
density: kpoint density in 2pi Angstrom^-1 (CASTEP convention)
Returns:
An KpointsData object with the desired density
"""
if density is None:
density = self._presets['kpoints_spacing']
kpoints = KpointsData()
kpoints.set_cell(structure.cell)
kpoints.set_kpoints_mesh_from_density(density * 2 * pi)
return kpoints
[docs]
def convert_lowercase(indict: dict[str, Any]) -> dict[str, Any]:
"""Convert all keys in a dictionary to lowercase"""
has_uppercase = any(any(letter.isupper() for letter in c) for c in indict.keys())
if not has_uppercase:
return indict
logger.warning('Overrides uses lowercase keys - converting all keys to lowercase.')
return {k.lower(): v for k, v in indict.items()}