# coding: utf-8
# Copyright (c) Max-Planck-Institut für Eisenforschung GmbH - Computational Materials Design (CM) Department
# Distributed under the terms of "New BSD License", see the LICENSE file.
import os
import posixpath
import numpy as np
import pandas
from pyiron_base import GenericParameters, state
from pyiron_snippets.deprecate import deprecate
from pyiron_snippets.resources import ResourceResolver
from pyiron_atomistics.atomistics.job.potentials import (
PotentialAbstract,
)
__author__ = "Jan Janssen"
__copyright__ = (
"Copyright 2021, Max-Planck-Institut für Eisenforschung GmbH - "
"Computational Materials Design (CM) Department"
)
__version__ = "1.0"
__maintainer__ = "Jan Janssen"
__email__ = "janssen@mpie.de"
__status__ = "development"
__date__ = "Sep 1, 2017"
[docs]
class VaspPotentialAbstract(PotentialAbstract):
"""
Args:
potential_df:
default_df:
selected_atoms:
"""
resource_plugin_name = "vasp"
[docs]
def __init__(self, potential_df=None, default_df=None, selected_atoms=None):
if potential_df is None:
potential_df = self._get_potential_df(
file_name_lst={"potentials_vasp.csv"},
)
super(VaspPotentialAbstract, self).__init__(
potential_df=potential_df,
default_df=default_df,
selected_atoms=selected_atoms,
)
def default(self):
if self._default_df is not None:
return pandas.concat(
[
self._potential_df[
(
self._potential_df["Name"]
== self._default_df.loc[atom].values[0]
)
]
for atom in self._selected_atoms
]
)
return None
def find_default(self, element):
if isinstance(element, set):
element = element
elif isinstance(element, list):
element = set(element)
elif isinstance(element, str):
element = set([element])
else:
raise TypeError("Only, str, list and set supported!")
element_lst = list(element)
if self._default_df is not None:
merged_lst = list(set(self._selected_atoms + element_lst))
return pandas.concat(
[
self._potential_df[
(
self._potential_df["Name"]
== self._default_df.loc[atom].values[0]
)
]
for atom in merged_lst
]
)
return None
[docs]
def find(self, element):
if isinstance(element, set):
element = element
elif isinstance(element, list):
element = set(element)
elif isinstance(element, str):
element = set([element])
else:
raise TypeError("Only, str, list and set supported!")
element_lst = list(element)
merged_lst = list(set(self._selected_atoms + element_lst))
return pandas.concat(
[super(VaspPotentialAbstract, self).find({atom}) for atom in merged_lst]
)
[docs]
def list(self):
if len(self._selected_atoms) != 0:
return pandas.concat(
[
super(VaspPotentialAbstract, self).find({atom})
for atom in self._selected_atoms
]
)
else:
return pandas.DataFrame({})
def list_potential_names(self):
df = self.list()
if len(df) != 0:
return list(self.list()["Name"])
else:
return []
def __dir__(self):
return [val.replace("-", "_") for val in self.list_potential_names()]
def __getitem__(self, item):
item_replace = item.replace("_gga_pbe", "-gga-pbe").replace("_lda", "-lda")
if item_replace in self.list_potential_names():
df = self.list()
return return_potential_file(
file_name=list(df[df["Name"] == item_replace]["Filename"])[0][0]
)
selected_atoms = self._selected_atoms + [item]
return VaspPotentialAbstract(
potential_df=self._potential_df,
default_df=self._default_df,
selected_atoms=selected_atoms,
)
[docs]
class VaspPotentialFile(VaspPotentialAbstract):
"""
The Potential class is derived from the PotentialAbstract class, but instead of loading the potentials from a list,
the potentials are loaded from a file.
Args:
xc (str): Exchange correlation functional ['PBE', 'LDA']
"""
[docs]
def __init__(self, xc=None, selected_atoms=None):
potential_df = self._get_potential_df(
file_name_lst={"potentials_vasp.csv"},
)
if xc == "PBE":
default_df = self._get_potential_default_df(
file_name_lst={"potentials_vasp_pbe_default.csv"},
)
potential_df = potential_df[(potential_df["Model"] == "gga-pbe")]
elif xc == "GGA":
default_df = self._get_potential_default_df(
file_name_lst={"potentials_vasp_pbe_default.csv"},
)
potential_df = potential_df[(potential_df["Model"] == "gga-pbe")]
elif xc == "LDA":
default_df = self._get_potential_default_df(
file_name_lst={"potentials_vasp_lda_default.csv"},
)
potential_df = potential_df[(potential_df["Model"] == "lda")]
else:
raise ValueError(
'The exchange correlation functional has to be set and it can either be "LDA" or "PBE"'
)
super(VaspPotentialFile, self).__init__(
potential_df=potential_df,
default_df=default_df,
selected_atoms=selected_atoms,
)
[docs]
def add_new_element(self, parent_element, new_element):
"""
Adding a new user defined element with a different POTCAR file. It is assumed that the file exists
Args:
parent_element (str): Parent element
new_element (str): Name of the new element (the name of the folder where the new POTCAR file exists
"""
df = self.find_default(element=parent_element)
df["Species"].values[0][0] = new_element
path_list = df["Filename"].values[0][0].split("/")
path_list[-2] = new_element
name_list = df["Name"].values[0].split("-")
name_list[0] = new_element
df["Name"].values[0] = "-".join(name_list)
df["Filename"].values[0][0] = "/".join(path_list)
self._potential_df = pandas.concat([self._potential_df, df])
if new_element not in self._default_df.index.values:
ds = pandas.Series(
["-".join(name_list), "/".join(path_list)],
index=["Name", "Filename"],
dtype=str,
name=new_element,
)
self._default_df = pandas.concat([self._default_df, ds.to_frame().T])
else:
self._default_df.loc[new_element] = "-".join(name_list)
[docs]
class VaspPotential(object):
"""
The Potential class is derived from the PotentialAbstract class, but instead of loading the potentials from a list,
the potentials are loaded from a file.
Args:
path (str): path to the potential list
"""
[docs]
def __init__(self, selected_atoms=None):
self.pbe = VaspPotentialFile(xc="PBE", selected_atoms=selected_atoms)
self.lda = VaspPotentialFile(xc="LDA", selected_atoms=selected_atoms)
[docs]
class VaspPotentialSetter(object):
[docs]
def __init__(self, element_lst):
super(VaspPotentialSetter, self).__setattr__(
"_potential_dict", {el: None for el in element_lst}
)
def __getattr__(self, item):
if item in self._potential_dict.keys():
return item
else:
raise AttributeError
def __setitem__(self, key, value):
self.__setattr__(key=key, value=value)
def __setattr__(self, key, value):
if key in self._potential_dict.keys():
self._potential_dict[key] = value
else:
raise AttributeError
def to_dict(self):
return self._potential_dict
def from_dict(self, obj_dict):
for key, value in obj_dict.items():
self._potential_dict[key] = value
def __repr__(self):
return self._potential_dict.__repr__()
[docs]
@deprecate(
"use get_enmax_among_potentials and note the adjustment to the signature (*args instead of list)"
)
def get_enmax_among_species(symbol_lst, return_list=False, xc="PBE"):
"""
DEPRECATED: Please use `get_enmax_among_potentials`.
Given a list of species symbols, finds the largest applicable encut.
Args:
symbol_lst (list): The list of species symbols.
return_list (bool): Whether to return the list of all ENMAX values (in the same order as `species_lst` along with
the largest value). (Default is False.)
xc ("GGA"/"PBE"/"LDA"): The exchange correlation functional for which the POTCARs were generated. (Default is "PBE".)
Returns:
(float): The largest ENMAX among the POTCAR files for all the species.
[optional](list): The ENMAX value corresponding to each species.
"""
return get_enmax_among_potentials(*symbol_lst, return_list=return_list, xc=xc)
[docs]
def get_enmax_among_potentials(*names, return_list=False, xc="PBE"):
"""
Given potential names without XC information or elemental symbols, look over all the corresponding POTCAR files and
find the largest ENMAX value.
e.g. `get_enmax_among_potentials('Mg', 'Al_GW', 'Ca_pv', 'Ca_sv', xc='LDA')`
Args:
*names (str): Names of potentials or elemental symbols
return_list (bool): Whether to return the list of all ENMAX values (in the same order as `names` as a second
return value after providing the largest value). (Default is False.)
xc ("GGA"/"PBE"/"LDA"): The exchange correlation functional for which the POTCARs were generated.
(Default is "PBE".)
Returns:
(float): The largest ENMAX among the POTCAR files for all the requested names.
[optional](list): The ENMAX value corresponding to each species.
"""
def _get_just_element_from_name(name):
return name.split("_")[0]
def _get_index_of_exact_match(name, potential_names):
try:
return np.argwhere(
[name == strip_xc_from_potential_name(pn) for pn in potential_names]
)[0, 0]
except IndexError:
raise ValueError(
"Couldn't find {} among potential names for {}".format(
name, _get_just_element_from_name(name)
)
)
def _get_potcar_filename(name, exch_corr):
potcar_table = VaspPotentialFile(xc=exch_corr).find(
_get_just_element_from_name(name)
)
return potcar_table["Filename"].values[
_get_index_of_exact_match(name, potcar_table["Name"].values)
][0]
enmax_lst = []
for n in names:
with open(
VaspPotentialFile.find_potential_file(path=_get_potcar_filename(n, xc))
) as pf:
for i, line in enumerate(pf):
if i == 14:
encut_str = line.split()[2][:-1]
enmax_lst.append(float(encut_str))
break
if return_list:
return max(enmax_lst), enmax_lst
else:
return max(enmax_lst)
[docs]
def strip_xc_from_potential_name(name):
return name.split("-")[0]
[docs]
class Potcar(GenericParameters):
pot_path_dict = {"GGA": "paw-gga-pbe", "PBE": "paw-gga-pbe", "LDA": "paw-lda"}
[docs]
def __init__(self, input_file_name=None, table_name="potcar"):
GenericParameters.__init__(
self,
input_file_name=input_file_name,
table_name=table_name,
val_only=False,
comment_char="#",
)
self._structure = None
self.electrons_per_atom_lst = list()
self.max_cutoff_lst = list()
self.el_path_lst = list()
self.el_path_dict = dict()
self.modified_elements = dict()
self._vasp_potentials = None
@property
def vasp_potentials(self):
if self._vasp_potentials is None:
self._vasp_potentials = VaspPotentialFile(self.get("xc"))
return self._vasp_potentials
def potcar_set_structure(self, structure, modified_elements):
self._structure = structure
self._set_default_path_dict()
self._set_potential_paths()
self.modified_elements = modified_elements
[docs]
def modify(self, **modify):
if "xc" in modify:
xc_type = modify["xc"]
self._set_default_path_dict()
if xc_type not in self.pot_path_dict:
raise ValueError("xc type not implemented: " + xc_type)
GenericParameters.modify(self, **modify)
if self._structure is not None:
self._set_potential_paths()
def _set_default_path_dict(self):
if self._structure is None:
return
for i, el_obj in enumerate(self._structure.get_species_objects()):
if isinstance(el_obj.Parent, str):
el = el_obj.Parent
else:
el = el_obj.Abbreviation
if isinstance(el_obj.tags, dict):
if "pseudo_potcar_file" in el_obj.tags.keys():
new_element = el_obj.tags["pseudo_potcar_file"]
self.vasp_potentials.add_new_element(
parent_element=el, new_element=new_element
)
key = self.vasp_potentials.find_default(el).Species.values[0][0]
val = self.vasp_potentials.find_default(el).Name.values[0]
self[key] = val
def _set_potential_paths(self):
element_list = (
self._structure.get_species_symbols()
) # .ElementList.getSpecies()
object_list = self._structure.get_species_objects()
state.logger.debug("element list: {0}".format(element_list))
self.el_path_lst = list()
xc = self.get("xc")
state.logger.debug("XC: {0}".format(xc))
for i, el_obj in enumerate(object_list):
if isinstance(el_obj.Parent, str):
el = el_obj.Parent
else:
el = el_obj.Abbreviation
if (
isinstance(el_obj.tags, dict)
and "pseudo_potcar_file" in el_obj.tags.keys()
):
new_element = el_obj.tags["pseudo_potcar_file"]
self.vasp_potentials.add_new_element(
parent_element=el, new_element=new_element
)
el_path = self.vasp_potentials.find_potential_file(
path=self.vasp_potentials.find_default(new_element)[
"Filename"
].values[0][0]
)
if not (os.path.isfile(el_path)):
raise ValueError("such a file does not exist in the pp directory")
elif el in self.modified_elements.keys():
new_element = self.modified_elements[el]
if os.path.isabs(new_element):
el_path = new_element
else:
self.vasp_potentials.add_new_element(
parent_element=el, new_element=new_element
)
el_path = self.vasp_potentials.find_potential_file(
path=self.vasp_potentials.find_default(new_element)[
"Filename"
].values[0][0]
)
else:
el_path = self.vasp_potentials.find_potential_file(
path=self.vasp_potentials.find_default(el)["Filename"].values[0][0]
)
if not (os.path.isfile(el_path)):
raise AssertionError()
pot_name = "pot_" + str(i)
if pot_name in self._dataset["Parameter"]:
try:
ind = self._dataset["Parameter"].index(pot_name)
except (ValueError, IndexError):
indices = np.core.defchararray.find(
self._dataset["Parameter"], pot_name
)
ind = np.where(indices == 0)[0][0]
self._dataset["Value"][ind] = el_path
self._dataset["Comment"][ind] = ""
else:
self._dataset["Parameter"].append("pot_" + str(i))
self._dataset["Value"].append(el_path)
self._dataset["Comment"].append("")
self.el_path_lst.append(el_path)
def get_file_content(self):
self.electrons_per_atom_lst = list()
self.max_cutoff_lst = list()
self._set_potential_paths()
line_lst = []
for el_file in self.el_path_lst:
with open(el_file) as pot_file:
for i, line in enumerate(pot_file):
line_lst.append(line)
if i == 1:
self.electrons_per_atom_lst.append(int(float(line)))
elif i == 14:
mystr = line.split()[2][:-1]
self.max_cutoff_lst.append(float(mystr))
return line_lst
[docs]
def write_file(self, file_name, cwd=None):
"""
Args:
file_name:
cwd:
Returns:
"""
with open(file_name, "w") as f:
f.writelines("".join(self.get_file_content()))
[docs]
def load_default(self):
file_content = """\
xc GGA # LDA, GGA
"""
self.load_string(file_content)