"""Modules handling input files for VASP calculations."""
import pprint
import json
import os
import numpy as np
from jarvis.core.atoms import Atoms
from collections import OrderedDict
from jarvis.core.kpoints import generate_kgrid, Kpoints3D
from jarvis.core.utils import get_counts
from jarvis.core.specie import Specie
from jarvis.core.utils import update_dict
from jarvis.db.jsonutils import loadjson
from collections import defaultdict
[docs]class Poscar(object):
"""
Class defining Poscar object.
Constructued from the Atoms object.
Args:
atoms : Atoms object
comment : Header of Poscar file
"""
def __init__(self, atoms, comment="System"):
"""Initialize the Poscar object."""
self.atoms = atoms
self.comment = comment
[docs] @staticmethod
def from_file(filename="POSCAR"):
"""Read simple POSCAR file from the path."""
with open(filename, "r") as f:
return Poscar.from_string(f.read())
[docs] def to_dict(self):
"""Convert Poscar object to a dictionary."""
d = OrderedDict()
d["atoms"] = self.atoms.to_dict()
d["comment"] = self.comment
return d
[docs] @classmethod
def from_dict(self, d={}):
"""Construct Poscar object from a dictionary."""
return Poscar(atoms=Atoms.from_dict(d["atoms"]), comment=d["comment"])
[docs] def to_string(self):
"""Make the Poscar object to a string."""
header = (
str(self.comment)
+ str("\n1.0\n")
+ str(self.atoms.lattice_mat[0][0])
+ " "
+ str(self.atoms.lattice_mat[0][1])
+ " "
+ str(self.atoms.lattice_mat[0][2])
+ "\n"
+ str(self.atoms.lattice_mat[1][0])
+ " "
+ str(self.atoms.lattice_mat[1][1])
+ " "
+ str(self.atoms.lattice_mat[1][2])
+ "\n"
+ str(self.atoms.lattice_mat[2][0])
+ " "
+ str(self.atoms.lattice_mat[2][1])
+ " "
+ str(self.atoms.lattice_mat[2][2])
+ "\n"
)
# order = np.argsort(self.atoms.elements)
coords = self.atoms.frac_coords
# DO NOT USE ORDER
coords_ordered = np.array(coords) # [order]
elements_ordered = np.array(self.atoms.elements) # [order]
props_ordered = np.array(self.atoms.props) # [order]
# check_selective_dynamics = False
info = defaultdict(list)
for i, j, k in zip(elements_ordered, coords_ordered, props_ordered):
info[i].append([j, k])
elname = ""
elcount = ""
elcoords = ""
for i, j in info.items():
# print(i, len(j))
elname += i + " "
elcount += str(len(j)) + " "
for k in j:
if k[1] == "":
elcoords += " ".join(map(str, k[0])) + " " + i + "\n"
elif isinstance(k[1], list):
elcoords += (
" ".join(map(str, k[0]))
+ " ".join(map(str, k[1]))
+ "\n"
)
else:
elcoords += " ".join(map(str, k[0])) + " " + k[1] + "\n"
if "T" in "".join(map(str, self.atoms.props[0])):
middle = (
elname + "\n" + elcount + "\nSelective dynamics\n" + "Direct\n"
)
else:
middle = elname + "\n" + elcount + "\ndirect\n"
rest = elcoords
# counts = get_counts(elements_ordered)
# if "T" in "".join(map(str, self.atoms.props[0])):
# middle = (
# " ".join(map(str, counts.keys()))
# + "\n"
# + " ".join(map(str, counts.values()))
# + "\nSelective dynamics\n"
# + "Direct\n"
# )
# else:
# middle = (
# " ".join(map(str, counts.keys()))
# + "\n"
# + " ".join(map(str, counts.values()))
# + "\ndirect\n"
# )
# rest = ""
# print ('repr',self.frac_coords, self.frac_coords.shape)
# for ii, i in enumerate(coords_ordered):
# p_ordered = str(props_ordered[ii])
# rest = (
# rest + " ".join(map(str, i)) + " " + str(p_ordered) + "\n"
# )
result = header + middle + rest
return result
[docs] def write_file(self, filename):
"""Write the Poscar object to a file."""
# TODO: Use to_string instead of re-writing the code here
with open(filename, "w") as f:
# f = open(filename, "w")
f.write(self.to_string())
# f.close()
[docs] @staticmethod
def from_string(lines):
"""Read Poscar from strings, useful in reading files/streams."""
text = lines.splitlines()
comment = text[0]
scale = float(text[1])
lattice_mat = []
lattice_mat.append([float(i) for i in text[2].split()])
lattice_mat.append([float(i) for i in text[3].split()])
lattice_mat.append([float(i) for i in text[4].split()])
lattice_mat = scale * np.array(lattice_mat)
begin = 5
if "S" in text[7] and "s" in text[7]:
begin = 6
uniq_elements = text[5].split()
element_count = np.array([int(i) for i in text[6].split()])
elements = []
for i, ii in enumerate(element_count):
for j in range(ii):
elements.append(uniq_elements[i])
cartesian = True
if "d" in text[begin + 2] or "D" in text[begin + 2]:
cartesian = False
num_atoms = int(np.sum(element_count))
coords = []
for i in range(num_atoms):
coords.append([float(i) for i in text[begin + 3 + i].split()[0:3]])
coords = np.array(coords)
atoms = Atoms(
lattice_mat=lattice_mat,
coords=coords,
elements=elements,
cartesian=cartesian,
)
# print (atoms)
# formula = atoms.composition.formula
return Poscar(atoms, comment=comment)
[docs] def __repr__(self):
"""Represent the Poscar class."""
header = (
str(self.comment)
+ str("\n1.0\n")
+ str(self.atoms.lattice_mat[0][0])
+ " "
+ str(self.atoms.lattice_mat[0][1])
+ " "
+ str(self.atoms.lattice_mat[0][2])
+ "\n"
+ str(self.atoms.lattice_mat[1][0])
+ " "
+ str(self.atoms.lattice_mat[1][1])
+ " "
+ str(self.atoms.lattice_mat[1][2])
+ "\n"
+ str(self.atoms.lattice_mat[2][0])
+ " "
+ str(self.atoms.lattice_mat[2][1])
+ " "
+ str(self.atoms.lattice_mat[2][2])
+ "\n"
)
order = np.argsort(self.atoms.elements)
coords = self.atoms.frac_coords
coords_ordered = np.array(coords)[order]
elements_ordered = np.array(self.atoms.elements)[order]
props_ordered = np.array(self.atoms.props)[order]
# check_selective_dynamics = False
counts = get_counts(elements_ordered)
if "T" in "".join(map(str, self.atoms.props[0])):
middle = (
" ".join(map(str, counts.keys()))
+ "\n"
+ " ".join(map(str, counts.values()))
+ "\nSelective dynamics\n"
+ "Direct\n"
)
else:
middle = (
" ".join(map(str, counts.keys()))
+ "\n"
+ " ".join(map(str, counts.values()))
+ "\ndirect\n"
)
rest = ""
# print ('repr',self.frac_coords, self.frac_coords.shape)
for ii, i in enumerate(coords_ordered):
p_ordered = str(props_ordered[ii])
rest = rest + " ".join(map(str, i)) + " " + str(p_ordered) + "\n"
result = header + middle + rest
return result
[docs]class Incar(object):
"""Make VASP INCAR files as python dictionary of keys and values."""
def __init__(self, tags={}):
"""Initialize with a dictionary."""
self._tags = tags
[docs] @staticmethod
def from_file(filename="INCAR"):
"""Read INCAR file."""
with open(filename, "r") as f:
return Incar.from_string(f.read())
[docs] def update(self, d={}):
"""Provide the new tags as a dictionary to update Incar object."""
# print("selftags1=", self._tags)
if self._tags != {}:
for i, j in self._tags.items():
self._tags[i] = j # .strip(' ')
for i, j in d.items():
self._tags[i] = j
# print("selftags2=", self._tags)
return Incar(self._tags)
# def get(self, key="POTIM", temp=0.5):
# """Get the value of a key."""
# if key in list(self._tags.keys()):
# return self._tags[key]
# else:
# self._tags[key] = temp
# print("Setting the temp value to the key", temp, key)
# return self._tags[key]
[docs] def to_dict(self):
"""Convert into dictionary."""
return self._tags
[docs] @classmethod
def from_dict(self, data={}):
"""Construct from dictionary."""
return Incar(tags=data)
[docs] @staticmethod
def from_string(lines):
"""Construct from string."""
text = lines.splitlines()
tags = OrderedDict()
for i in text:
if "=" in i:
tmp = i.split("=")
tags.setdefault(tmp[0].strip(" "), tmp[1].strip(" "))
return Incar(tags=tags)
[docs] def __repr__(self):
"""Representation of the class."""
return str(self._tags)
[docs] def write_file(self, filename):
"""Write Incar to a file."""
tags = self._tags
lines = ""
for i, j in tags.items():
lines = lines + str(i) + str("=") + str(j) + "\n"
f = open(filename, "w")
f.write(lines)
f.close()
[docs]class IndividualPotcarData(object):
"""Class for individual POTCAR file handling."""
def __init__(self, data={}):
"""Intialize with some key info."""
self._data = data
[docs] def from_string(lines):
"""Generate some of the contents in the POTCAR."""
text = lines.splitlines()
individual_data = OrderedDict()
individual_data["header1"] = text[0]
individual_data["header2"] = text[2]
individual_data["VRHFIN"] = text[3].split("=")[1]
individual_data["element"] = text[3].split("=")[1].split(":")[0]
individual_data["LEXCH"] = text[4].split("=")[1].replace(" ", "")
tmp = text[5].split("=")[1].split()[0].replace(" ", "")
individual_data["EATOM"] = tmp
individual_data["TITEL"] = text[7].split("=")[1].replace(" ", "")
return IndividualPotcarData(data=individual_data)
[docs] def from_file(filename="POTCAR"):
"""Read from file."""
with open(filename, "r") as f:
return IndividualPotcarData.from_string(f.read())
[docs] def __repr__(self):
"""Reprent the class."""
return pprint.pformat(self._data, indent=4)
[docs]class Potcar(object):
"""Construct muti-atoms Postcar."""
def __init__(
self,
elements=[],
pot_type="POT_GGA_PAW_PBE",
pot_json_path="",
potcar_strings=[],
):
"""
Initialize the Potcar class.
POTCAR file contains the pseudopotential for each
atomic species used in the calculation.
Args:
elements: atomic elements.
pot_type: Type of pseudopotential.
Look for VASP provided PSPs.
VASP_PSP_DIR should be in the path.
pot_json_path: Path to dictionary of chemical elements along with
their orbitals taken into conisideration, e.g. V_pv.
potcar_strings: One can directly provide
the above mentioned strings.
"""
self._elements = elements
self._pot_type = pot_type
self._potcar_strings = potcar_strings
self._pot_json_path = pot_json_path
if self._potcar_strings == []:
pot_json_file = str(
os.path.join(os.path.dirname(__file__), "default_potcars.json")
)
self._pot_json_path = pot_json_file
pot_json = open(pot_json_file, "r")
pot_json_selected = json.load(pot_json)
pot_json.close()
potcar_strings = [] # OrderedDict()
for i in self._elements:
potcar_strings.append(pot_json_selected[i])
# for j, k in pot_json_selected.items():
# if i == j:
# potcar_strings.setdefault(i, k)
self._potcar_strings = potcar_strings
# print("self._elements", self._elements)
# print("self._potcar_strings", self._potcar_strings)
if len(self._elements) != len(self._potcar_strings):
raise ValueError(
"Number of elements is not same as potcar_strings",
self._elements,
self._potcar_strings.keys(),
)
else:
pot_json = open(self._pot_json_path, "r")
pot_json_selected = json.load(pot_json)
pot_json.close()
self._potcar_strings = potcar_strings
if len(self._elements) != len(self._potcar_strings):
msg = "Number of elements not same as potcar_strings"
raise ValueError(msg)
[docs] @staticmethod
def from_atoms(atoms=None, pot_type=None):
"""Obtain POTCAR for atoms object."""
new_symb = []
for i in atoms.elements:
if i not in new_symb:
new_symb.append(i)
if pot_type is None:
pot_type = "POT_GGA_PAW_PBE"
potcar = Potcar(elements=new_symb, pot_type=pot_type)
return potcar
[docs] @classmethod
def from_dict(self, d={}):
"""Build class from a dictionary."""
return Potcar(
elements=d["elements"],
pot_type=d["pot_type"],
pot_json_path=d["pot_json_path"],
potcar_strings=d["potcar_strings"],
)
[docs] def to_dict(self):
"""Convert to a dictionary."""
d = OrderedDict()
d["elements"] = np.array(self._elements).tolist()
d["pot_type"] = self._pot_type
d["pot_json_path"] = self._pot_json_path
d["potcar_strings"] = np.array(self._potcar_strings).tolist()
return d
[docs] def catenate_potcar_files(
self, destination_filename="POTCAR", filenames=[]
):
"""Catenate potcars of sifferent elements."""
with open(destination_filename, "w") as outfile:
for fname in filenames:
with open(fname) as infile:
for line in infile:
outfile.write(line)
[docs] def list_potcar_files(self):
"""List POTCAR files."""
pot_files = []
vasp_dir = str(os.environ["VASP_PSP_DIR"])
vasp_psp_dir = str(os.path.join(vasp_dir, self._pot_type))
potcar_strings = self._potcar_strings
for j in potcar_strings:
tmp = os.path.join(vasp_psp_dir, j, "POTCAR")
pot_files.append(tmp)
return pot_files
[docs] def write_file(self, filename="POTCAR"):
"""Write POTCAR file."""
pot_files = self.list_potcar_files()
self.catenate_potcar_files(
destination_filename=filename, filenames=pot_files
)
[docs] def __repr__(self):
"""Represent Potcar."""
return str(str(self._pot_type) + "\n" + str(self._potcar_strings))
[docs]class Kpoints(object):
"""Make VASP KPOINTS as object."""
def __init__(self, filename=""):
"""Initialize Kpoints from filename else read from file-stream."""
self.filename = filename
if filename != "":
f = open(self.filename, "r")
self.lines = f.read().splitlines()
f.close()
self.kpoints = self.read(self.lines)
[docs] @classmethod
def read(self, lines):
"""Read from an open file."""
if lines[1] == "0":
return self.get_mesh_kp(lines=lines)
elif "Reciprocal" in lines[2]:
return self.get_ibz_kp(lines=lines)
else:
ValueError("K-point scheme is not implemented")
[docs] def get_mesh_kp(lines=""):
"""Read Kpoints as grid."""
grid = [int(i) for i in lines[3].split()]
kpts = generate_kgrid(grid)
kpts_cls = Kpoints3D(kpoints=np.array(kpts))
return kpts_cls
[docs] def get_ibz_kp(lines=""):
"""Read the Kpoints in the line-mode."""
kp_labels = []
all_kp = []
kp_labels_points = []
for ii, i in enumerate(lines):
if ii > 2:
tmp = i.split()
k = [tmp[0], tmp[1], tmp[2]]
all_kp.append(k)
if len(tmp) == 5:
tmp = str("$") + str(tmp[4]) + str("$")
if len(kp_labels) == 0:
kp_labels.append(tmp)
kp_labels_points.append(ii - 3)
elif tmp != kp_labels[-1]:
kp_labels.append(tmp)
kp_labels_points.append(ii - 3)
labels = []
for i in range(len(all_kp)):
labels.append("")
for i, j in zip(kp_labels, kp_labels_points):
labels[j] = i
all_kp = np.array(all_kp, dtype="float")
kpts_cls = Kpoints3D(
kpoints=all_kp, labels=labels, kpoint_mode="linemode"
)
return kpts_cls
[docs]def find_ldau_magmom(
atoms="",
default_magmom=True,
U=3.0,
mag=5.0,
amix=0.2,
bmix=0.00001,
amixmag=0.8,
bmixmag=0.00001,
lsorbit=False,
):
"""Get necessary INCAR tags for DFT+U calculations."""
sps = atoms.uniq_species
LDAUL = []
LDAUU = []
LDAUTYPE = 2
lmix = 4
for i in sps:
el = Specie(i)
el_u = 0
el_l = -1
if el.element_property("is_transition_metal"):
el_u = U
el_l = 2
if el.element_property("is_actinoid") or el.element_property(
"is_lanthanoid"
):
el_u = U
el_l = 3
lmix = 6
LDAUL.append(el_l)
LDAUU.append(el_u)
if 3 in LDAUL:
LDAUTYPE = 3
nat = atoms.num_atoms
magmom = str(nat) + str("*") + str(mag)
if lsorbit:
magmom = ""
tmp = " 0 0 " + str(mag)
for i in range(0, nat):
magmom = magmom + tmp
info = {}
info["LDAU"] = ".TRUE."
info["LDAUTYPE"] = LDAUTYPE
info["LDAUL"] = " ".join(str(m) for m in LDAUL)
info["LDAUU"] = " ".join(str(m) for m in LDAUU)
info["LDAUPRINT"] = 2
info["LMAMIX"] = lmix
if not default_magmom:
info["MAGMOM"] = magmom
info["AMIX"] = amix
info["BMIX"] = bmix
info["AMIX_MAG"] = amixmag
info["BMIX_MAG"] = bmixmag
return info
[docs]def get_nelect(atoms=None, default_pot=None):
"""Get number of electrons fro default POTCAR settings."""
if default_pot is None:
default_pot = loadjson(
os.path.join(
os.path.join(os.path.dirname(__file__)),
"..",
"wannier",
"default_semicore.json",
)
)
comp = atoms.composition.to_dict()
nelect = 0
for i, j in comp.items():
nelect = nelect + j * (default_pot[i][0] + default_pot[i][1])
return nelect
[docs]def add_ldau_incar(
use_incar_dict={}, atoms=None, Uval=2, lsorbit=False, default_magmom=True
):
"""Add LDAU in incase, especially made for spillage calcs."""
info_ldau = find_ldau_magmom(
U=Uval, atoms=atoms, lsorbit=lsorbit, default_magmom=default_magmom
)
tmp = update_dict(use_incar_dict, info_ldau)
use_incar_dict = tmp
return use_incar_dict