3
0
mirror of https://github.com/triqs/dft_tools synced 2024-11-09 07:33:47 +01:00
dft_tools/python/vasp/vaspio.py
Oleg E. Peil 87b00f61b1 Added a format check for EIGENVAL to vaspio.py
Added a check to 'vaspio.py' testing that the number of columns
implies that the Fermi weights are present in EIGENVAL. This check ensures
that the new format (starting from VASP 5.4) of the file is used.

Corresponding test is added to the suite.
2015-10-16 11:59:02 +02:00

561 lines
18 KiB
Python

import numpy as np
import re
#import plocar_io.c_plocar_io as c_plocar_io
def read_lines(filename):
r"""
Generator of lines for a file
Parameters
----------
filename (str) : name of the file
"""
with open(filename, 'r') as f:
for line in f:
yield line
################################################################################
################################################################################
#
# class VaspData
#
################################################################################
################################################################################
class VaspData:
"""
Container class for all VASP data.
"""
def __init__(self, vasp_dir, read_all=True):
self.vasp_dir = vasp_dir
self.plocar = Plocar()
self.poscar = Poscar()
self.kpoints = Kpoints()
self.eigenval = Eigenval()
self.doscar = Doscar()
if read_all:
self.plocar.from_file(vasp_dir)
self.poscar.from_file(vasp_dir)
self.kpoints.from_file(vasp_dir)
self.eigenval.from_file(vasp_dir)
self.doscar.from_file(vasp_dir)
################################################################################
################################################################################
#
# class Plocar
#
################################################################################
################################################################################
class Plocar:
r"""
Class containing raw PLO data from VASP.
Properties
----------
- *plo* (numpy.array((nion, ns, nk, nb, nlmmax))) : raw projectors
- *params* (dict) : parameters read from PLOCAR
- *ferw* (array(nion, ns, nk, nb)) : Fermi weights from VASP
"""
def from_file(self, vasp_dir='./', plocar_filename='PLOCAR'):
r"""
Reads non-normalized projectors from a binary file (`PLOCAR' by default)
generated by VASP PLO interface.
Parameters
----------
vasp_dir (str) : path to the VASP working directory [default = `./']
plocar_filename (str) : filename [default = `PLOCAR']
"""
# Add a slash to the path name if necessary
if vasp_dir[-1] != '/':
vasp_dir += '/'
# self.params, self.plo, self.ferw = c_plocar_io.read_plocar(vasp_dir + plocar_filename)
self.proj_params, self.plo = self.temp_parser(projcar_filename=vasp_dir + "PROJCAR", locproj_filename=vasp_dir + "LOCPROJ")
def temp_parser(self, projcar_filename='PROJCAR', locproj_filename='LOCPROJ'):
r"""
Parses PROJCAR (and partially LOCPROJ) to get VASP projectors.
This is a prototype parser that should eventually be written in C for
better performance on large files.
Returns projector parameters (site/orbital indices etc.) and an array
with projectors.
"""
orb_labels = ["s", "pz", "px", "py", "dz2", "dxz", "dyz", "dx2-y2", "dxy",
"fz3", "fxz2", "fyz2", "fz(x2-y2)", "fxyz", "fx(x2-3y2)", "fy(3x2-y2)"]
def lm_to_l_m(lm):
l = int(np.sqrt(lm))
m = lm - l*l
return l, m
# Read the first line of LOCPROJ to get the dimensions
with open(locproj_filename, 'rt') as f:
line = f.readline()
nproj, nspin, nk, nband = map(int, line.split())
plo = np.zeros((nproj, nspin, nk, nband), dtype=np.complex128)
proj_params = [{} for i in xrange(nproj)]
iproj_site = 0
is_first_read = True
with open(projcar_filename, 'rt') as f:
line = self.search_for(f, "^ *ISITE")
while line:
isite = int(line.split()[1])
if not is_first_read:
for il in xrange(norb):
ip_new = iproj_site * norb + il
ip_prev = (iproj_site - 1) * norb + il
proj_params[ip_new]['isite'] = isite
proj_params[ip_new]['l'] = proj_params[ip_prev]['l']
proj_params[ip_new]['m'] = proj_params[ip_prev]['m']
for ispin in xrange(nspin):
for ik in xrange(nk):
# Parse the orbital labels and convert them to l,m-indices
line = self.search_for(f, "^ *band")
if is_first_read:
cpatt = re.compile("lm= *([^\s]+)")
labels = re.findall(cpatt, line)
norb = len(labels)
for il, label in enumerate(labels):
lm = orb_labels.index(label)
l, m = lm_to_l_m(lm)
# For the first read 'iproj_site = 0' and only orbital index 'il' is used
proj_params[il]['isite'] = isite
proj_params[il]['l'] = l
proj_params[il]['m'] = m
is_first_read = False
# Read the block of nk * ns * nband complex numbers
for ib in xrange(nband):
line = f.readline()
rtmp = map(float, line.split()[1:])
for il in xrange(norb):
ctmp = complex(rtmp[2 * il], rtmp[2 * il + 1])
plo[iproj_site * norb + il, ispin, ik, ib] = ctmp
# End of site-block
iproj_site += 1
line = self.search_for(f, "^ *ISITE")
print "Read parameters:"
for il, par in enumerate(proj_params):
print il, " -> ", par
return proj_params, plo
def search_for(self, f, patt):
r"""
Reads file 'f' until pattern 'patt' is encountered and returns
the corresponding line.
"""
cpatt = re.compile(patt)
line = "x"
while not re.match(cpatt, line) and line:
line = f.readline()
return line
################################################################################
################################################################################
#
# class Poscar
#
################################################################################
################################################################################
class Poscar:
"""
Class containing POSCAR data from VASP.
Properties
----------
nq (int) : total number of ions
ntypes ([int]) : number of ion types
nions (int) : a list of number of ions of each type
a_brav (numpy.array((3, 3), dtype=float)) : lattice vectors
q_types ([numpy.array((nions, 3), dtype=float)]) : a list of
arrays each containing fractional coordinates of ions of a given type
"""
def __init__(self):
self.q_cart = None
self.b_rec = None
def from_file(self, vasp_dir='./', poscar_filename='POSCAR'):
"""
Reads POSCAR and returns a dictionary.
Parameters
----------
vasp_dir (str) : path to the VASP working directory [default = `./']
plocar_filename (str) : filename [default = `PLOCAR']
"""
# Convenince local function
def readline_remove_comments():
return f.next().split('!')[0].strip()
# Add a slash to the path name if necessary
if vasp_dir[-1] != '/':
vasp_dir += '/'
f = read_lines(vasp_dir + poscar_filename)
# Comment line
comment = f.next().rstrip()
print " Found POSCAR, title line: %s"%(comment)
# Read scale
sline = readline_remove_comments()
ascale = float(sline[0])
# Read lattice vectors
self.a_brav = np.zeros((3, 3))
for ia in xrange(3):
sline = readline_remove_comments()
self.a_brav[ia, :] = map(float, sline.split())
# Negative scale means that it is a volume scale
if ascale < 0:
vscale = -ascale
vol = np.linalg.det(self.a_brav)
ascale = (vscale / vol)**(1.0/3)
self.a_brav *= ascale
# Depending on the version of VASP there could be
# an extra line with element names
sline = readline_remove_comments()
try:
# Old v4.6 format: no element names
self.nions = map(int, sline.split())
self.el_names = ['El%i'%(i) for i in xrange(len(nions))]
except ValueError:
# New v5.x format: read element names first
self.el_names = sline.split()
sline = readline_remove_comments()
self.nions = map(int, sline.split())
# Set the number of atom sorts (types) and the total
# number of atoms in the unit cell
self.ntypes = len(self.nions)
self.nq = sum(self.nions)
# Check for the line 'Selective dynamics' (and ignore it)
sline = readline_remove_comments()
if sline[0].lower() == 's':
sline = readline_remove_comments()
# Check whether coordinates are cartesian or fractional
cartesian = (sline[0].lower() in 'ck')
if cartesian:
brec = np.linalg.inv(self.a_brav.T)
# Read atomic positions
self.q_types = []
self.type_of_ion = []
for it in xrange(self.ntypes):
# Array mapping ion index to type
self.type_of_ion += self.nions[it] * [it]
q_at_it = np.zeros((self.nions[it], 3))
for iq in xrange(self.nions[it]):
sline = readline_remove_comments()
qcoord = map(float, sline.split()[:3])
if cartesian:
qcoord = np.dot(brec, qcoord)
q_at_it[iq, :] = qcoord
self.q_types.append(q_at_it)
print " Total number of ions:", self.nq
print " Number of types:", self.ntypes
print " Number of ions for each type:", self.nions
# print
# print " Coords:"
# for it in xrange(ntypes):
# print " Element:", el_names[it]
# print q_at[it]
################################################################################
################################################################################
#
# class Kpoints
#
################################################################################
################################################################################
class Kpoints:
"""
Class describing k-points and optionally tetrahedra.
Properties
----------
- nktot (int) : total number of k-points in the IBZ
- kpts (numpy.array((nktot, 3), dtype=float)) : k-point vectors (fractional coordinates)
- ntet (int) : total number of k-point tetrahedra
- itet (numpy.array((ntet, 5), dtype=float) : array of tetrahedra
- volt (float) : volume of a tetrahedron (the k-grid is assumed to
be uniform)
"""
#
# Reads IBZKPT file
#
def from_file(self, vasp_dir='./', ibz_filename='IBZKPT'):
"""
Reads from IBZKPT: k-points and optionally
tetrahedra topology (if present).
Parameters
----------
vasp_dir (str) : path to the VASP working directory [default = `./']
plocar_filename (str) : filename [default = `PLOCAR']
"""
# Add a slash to the path name if necessary
if vasp_dir[-1] != '/':
vasp_dir += '/'
ibz_file = read_lines(vasp_dir + ibz_filename)
# Skip comment line
line = ibz_file.next()
# Number of k-points
line = ibz_file.next()
self.nktot = int(line.strip().split()[0])
print
print " {0:>26} {1:d}".format("Total number of k-points:", self.nktot)
self.kpts = np.zeros((self.nktot, 3))
# Skip comment line
line = ibz_file.next()
for ik in xrange(self.nktot):
line = ibz_file.next()
self.kpts[ik, :] = map(float, line.strip().split()[:3])
# Attempt to read tetrahedra
# Skip comment line ("Tetrahedra")
try:
line = ibz_file.next()
# Number of tetrahedra and volume = 1/(6*nkx*nky*nkz)
line = ibz_file.next()
sline = line.split()
self.ntet = int(sline[0])
self.volt = float(sline[1])
print " {0:>26} {1:d}".format("Total number of tetrahedra:", self.ntet)
# Traditionally, itet[it, 0] contains multiplicity
self.itet = np.zeros((self.ntet, 5), dtype=int)
for it in xrange(self.ntet):
line = ibz_file.next()
self.itet[it, :] = map(int, line.split()[:5])
except StopIteration, ValueError:
print " No tetrahedron data found in %s. Skipping..."%(ibz_filename)
self.ntet = 0
# data = { 'nktot': nktot,
# 'kpts': kpts,
# 'ntet': ntet,
# 'itet': itet,
# 'volt': volt }
#
# return data
################################################################################
################################################################################
#
# class Eigenval
#
################################################################################
################################################################################
class Eigenval:
"""
Class containing Kohn-Sham-eigenvalues data from VASP (EIGENVAL file).
"""
def from_file(self, vasp_dir='./', eig_filename='EIGENVAL'):
"""
Reads eigenvalues from EIGENVAL. Note that the file also
contains k-points with weights. They are also stored and
then used to check the consistency of files read.
"""
# Add a slash to the path name if necessary
if vasp_dir[-1] != '/':
vasp_dir += '/'
f = read_lines(vasp_dir + eig_filename)
# First line: only the first and the last number out of four
# are used; these are 'nions' and 'ispin'
sline = f.next().split()
self.nq = int(sline[0])
self.ispin = int(sline[3])
# Second line: cell volume and lengths of lattice vectors (skip)
sline = f.next()
# Third line: temperature (skip)
sline = f.next()
# Fourth and fifth line: useless
sline = f.next()
sline = f.next()
# Sixth line: NELECT, NKTOT, NBTOT
sline = f.next().split()
self.nelect = int(sline[0])
self.nktot = int(sline[1])
self.nband = int(sline[2])
# Set of eigenvalues and k-points
self.kpts = np.zeros((self.nktot, 3))
self.kwghts = np.zeros((self.nktot,))
self.eigs = np.zeros((self.nktot, self.nband, self.ispin))
self.ferw = np.zeros((self.nktot, self.nband, self.ispin))
for ik in xrange(self.nktot):
sline = f.next() # Empty line
sline = f.next() # k-point info
tmp = map(float, sline.split())
self.kpts[ik, :] = tmp[:3]
self.kwghts[ik] = tmp[3]
for ib in xrange(self.nband):
sline = f.next().split()
tmp = map(float, sline)
assert len(tmp) == 2 * self.ispin + 1, "EIGENVAL file is incorrect (probably from old versions of VASP)"
self.eigs[ik, ib, :] = tmp[1:self.ispin+1]
self.ferw[ik, ib, :] = tmp[self.ispin+1:]
################################################################################
################################################################################
#
# class Doscar
#
################################################################################
################################################################################
class Doscar:
"""
Class containing some data from DOSCAR
"""
def from_file(self, vasp_dir='./', dos_filename='DOSCAR'):
"""
Reads only E_Fermi from DOSCAR.
"""
# Add a slash to the path name if necessary
if vasp_dir[-1] != '/':
vasp_dir += '/'
f = read_lines(vasp_dir + dos_filename)
# First line: NION, NION, JOBPAR, NCDIJ
sline = f.next().split()
self.ncdij = int(sline[3])
# Skip next 4 lines
for _ in xrange(4):
sline = f.next()
# Sixth line: EMAX, EMIN, NEDOS, EFERMI, 1.0
sline = f.next().split()
self.efermi = float(sline[3])
################################################################
#
# Reads SYMMCAR
#
################################################################
def read_symmcar(vasp_dir, symm_filename='SYMMCAR'):
"""
Reads SYMMCAR.
"""
# Shorthand for simple parsing
def extract_int_par(parname):
return int(re.findall(parname + '\s*=\s*(\d+)', line)[-1])
# Add a slash to the path name if necessary
if vasp_dir[-1] != '/':
vasp_dir += '/'
symmcar_exist = False
sym_file = read_lines(vasp_dir + symm_filename)
line = sym_file.next()
nrot = extract_int_par('NROT')
line = sym_file.next()
ntrans = extract_int_par('NPCELL')
# Lmax
line = sym_file.next()
lmax = extract_int_par('LMAX')
mmax = 2 * lmax + 1
# Nion
line = sym_file.next()
nion = extract_int_par('NION')
print " {0:>26} {1:d}".format("Number of rotations:", nrot)
print " {0:>26} {1:d}".format("Number of translations:", ntrans)
print " {0:>26} {1:d}".format("Number of ions:", nion)
print " {0:>26} {1:d}".format("L_max:", lmax)
rot_mats = np.zeros((nrot, lmax+1, mmax, mmax))
rot_map = np.zeros((nrot, ntrans, nion), dtype=np.int32)
for irot in xrange(nrot):
# Empty line
line = sym_file.next()
# IROT index (skip it)
line = sym_file.next()
# ISYMOP matrix (can be also skipped)
line = sym_file.next()
line = sym_file.next()
line = sym_file.next()
# Skip comment " Permutation map..."
line = sym_file.next()
# Permutations (in chunks of 20 indices per line)
for it in xrange(ntrans):
for ibl in xrange((nion - 1) / 20 + 1):
i1 = ibl * 20
i2 = (ibl + 1) * 20
line = sym_file.next()
rot_map[irot, it, i1:i2] = map(int, line.split())
for l in xrange(lmax + 1):
mmax = 2 * l + 1
# Comment: "L = ..."
line = sym_file.next()
for m in xrange(mmax):
line = sym_file.next()
rot_mats[irot, l, m, :mmax] = map(float, line.split()[:mmax])
data.update({ 'nrot': nrot, 'ntrans': ntrans,
'lmax': lmax, 'nion': nion,
'sym_rots': rot_mats, 'perm_map': rot_map })