Source code for pyprocar.io.abinit

import re
import glob
import collections
import os

from numpy import array
import numpy as np

from ..core import Structure, DensityOfStates, ElectronicBandStructure, KPath
from ..utils import elements
from .vasp import Procar, Kpoints

[docs]class Output(collections.abc.Mapping): """This class contains methods to parse the fermi energy, reciprocal lattice vectors and structure from the Abinit output file. """
[docs] def __init__(self, abinit_output=None): # variables self.abinit_output = abinit_output self.fermi = None self.reclat = None # reciprocal lattice vectors self.nspin = None # spin self.coordinates = None # reduced atomic coordinates self.variables = {} # call parsing functions self._readFermi() self._readRecLattice() self._readCoordinates() self._readLattice() self._readAtoms() # creating structure object self.structure = Structure( atoms=self.atoms, fractional_coordinates=self.coordinates, lattice=self.lattice, ) return
def _readFermi(self): """Reads the Fermi energy from the Abinit output file.""" with open(self.abinit_output, "r") as rf: data = rf.read() self.fermi = float( re.findall( r"Fermi\w*.\(\w*.HOMO\)\s*\w*\s*\(\w*\)\s*\=\s*([0-9.+-]*)", data )[0] ) # Converting from Hartree to eV self.fermi = 27.211396641308 * self.fermi # read spin (nsppol) self.nspin = re.findall(r"nsppol\s*=\s*([1-9]*)", data)[0] return def _readRecLattice(self): """Reads the reciprocal lattice vectors from the Abinit output file. This is used to calculate the k-path in cartesian coordinates if required.""" with open(self.abinit_output, "r") as rf: data = rf.read() lattice_block = re.findall(r"G\([1,2,3]\)=\s*([0-9.\s-]*)", data) lattice_block = lattice_block[3:] self.reclat = array( [lattice_block[0:3][i].split() for i in range(len(lattice_block))], dtype=float, ) return def _readCoordinates(self): """Reads the coordinates as given by the xred keyword.""" with open(self.abinit_output, "r") as rf: data = rf.read() coordinate_block = re.findall(r"xred\s*([+-.0-9E\s]*)", data)[-1].split() # for single atom at (0,0,0) xred is not printed out at the end. # So we use this workaround. if not coordinate_block: coordinate_block = re.findall( r"reduced\scoordinates\s\(array\sxred\)\sfor\s*[1-9]\satoms\n([+-.0-9E\s]*)\n", data, )[-1].split() coordinate_list = np.array([float(x) for x in coordinate_block]) self.coordinates = coordinate_list.reshape(len(coordinate_list) // 3, 3) return def _readLattice(self): """Reads the lattice vectors from rprim keyword and scales with acell.""" with open(self.abinit_output, "r") as rf: data = rf.read() # acell acell = re.findall(r"acell\s*([+-.0-9E\s]*)", data)[-1].split() acell = np.array([float(x) for x in acell]) # convert acell from Bohr to Angstrom acell = 0.529177 * acell # rprim rprim_block = re.findall(r"rprim\s*([+-.0-9E\s]*)", data)[-1].split() rprim_list = np.array([float(x) for x in rprim_block]) rprim = rprim_list.reshape(len(rprim_list) // 3, 3) # lattice self.lattice = np.zeros(shape=(3, 3)) for i in range(len(acell)): self.lattice[i, :] = acell[i] * rprim[i, :] return def _readAtoms(self): """Reads atomic elements used and puts them in an array according to their composition.""" with open(self.abinit_output, "r") as rf: data = rf.read() # Getting typat and znucl typat = re.findall(r"typat\s*([+-.0-9E\s]*)", data)[-1].split() typat = [int(x) for x in typat] znucl = re.findall(r"znucl\s*([+-.0-9E\s]*)", data)[-1].split() znucl = [int(float(x)) for x in znucl] self.atoms = [elements.atomic_symbol(znucl[x - 1]) for x in typat] def __contains__(self, x): return x in self.variables def __getitem__(self, x): return self.variables.__getitem__(x) def __iter__(self): return self.variables.__iter__() def __len__(self): return self.variables.__len__()
[docs]class AbinitKpoints(collections.abc.Mapping): """This class parses the k-point information."""
[docs] def __init__(self, filename="KPOINTS", has_time_reversal=True): self.variables = {} self.filename=filename # calling parser self._parse_kpoints() self.kpath=None if self.abinitkpointsobject.knames: self.kpath = KPath( knames=self.abinitkpointsobject.knames, special_kpoints=self.abinitkpointsobject.special_kpoints, ngrids=self.abinitkpointsobject.ngrids, has_time_reversal=has_time_reversal, )
def _parse_kpoints(self): """Reads KPOINTS file. Abinit has multiple way of defining the k-path. For the time being, we suggest to use a KPOINTS file similar to VASP to obtain the k-path for PyProcar from an Abinit calculation. """ self.abinitkpointsobject = Kpoints(filename=self.filename, has_time_reversal=True) def __contains__(self, x): return x in self.variables def __getitem__(self, x): return self.variables.__getitem__(x) def __iter__(self): return self.variables.__iter__() def __len__(self): return self.variables.__len__()
[docs]class AbinitProcar(collections.abc.Mapping): """This class has functions to parse the PROCAR file generated from Abinit. Unlike, VASP here the PROCAR files need to be merged and fixed for formatting issues prior to further processing. """
[docs] def __init__( self, dirname=None, inFiles=None, abinit_output=None, reciprocal_lattice=None, kpath=None, efermi=None, ): self.variables = {} self.inFiles = inFiles self.abinit_output = abinit_output self.reciprocal_lattice = reciprocal_lattice self.kpath = kpath self.efermi = efermi # Preparing files for merging # reading in all PROCAR_* files and putting it into a list if not provided. if inFiles is None: tmp_str = os.path.join(dirname, "PROCAR_*") inFiles = sorted(glob.glob(tmp_str)) else: inFiles = inFiles filename = os.path.join(dirname, "PROCAR") if isinstance(inFiles, list): self._mergeparallel( inputfiles=inFiles, outputfile=filename, abinit_output=self.abinit_output, ) else: pass # Use VASP Procar parser following PROCAR merge self.abinitprocarobject = Procar( filename=filename, structure=None, reciprocal_lattice=self.reciprocal_lattice, kpath=self.kpath, efermi=self.efermi, interpolation_factor=1, )
def _mergeparallel(self, inputfiles=None, outputfile=None, abinit_output=None): """This merges Procar files seperated between k-point ranges. Happens with parallel Abinit runs. """ print("Merging parallel files...") filenames = sorted(inputfiles) # creating an instance of the AbinitParser class if abinit_output: abinitparserobject = Output(abinit_output=abinit_output) nspin = int(abinitparserobject.nspin) else: raise IOError("Abinit output file not found.") if nspin != 2: with open(outputfile, "w") as outfile: for fname in filenames: with open(fname) as infile: for line in infile: outfile.write(line) elif nspin == 2: # for spin polarized calculations the spin down segments are saved in the # second half of the PROCAR's but in reverse k-point order. So we have to # fix the order and merge the second half of the PROCAR's. spinup_list = filenames[: int(len(filenames) / 2)] spindown_list = filenames[int(len(filenames) / 2) :] # reading the second line of the header to set as the separating line # in the colinear spin PROCAR. fp = open(spinup_list[0], "r") header1 = fp.readline() header2 = fp.readline() fp.close() # second half of PROCAR files in reverse order. spindown_list.reverse() # Writing new PROCAR with first spin up, header2 and then # spin down (reversed). with open(outputfile, "w") as outfile: for spinupfile in spinup_list: with open(spinupfile) as infile: for line in infile: outfile.write(line) outfile.write("\n") outfile.write(header2) outfile.write("\n") for spindownfile in spindown_list: with open(spindownfile) as infile: for line in infile: outfile.write(line) def _fixformat(self, inputfile=None, outputfile=None): """Fixes the formatting of Abinit's Procar when the tot projection is not summed and spin directions not seperated. """ print("Fixing formatting errors...") # removing existing temporary fixed file if os.path.exists(outputfile): os.remove(outputfile) ####### Fixing the parallel PROCARs from Abinit ########## rf = open(inputfile, "r") data = rf.read() rf.close() # reading headers rffl = open(inputfile, "r") first_line = rffl.readline() rffl.close() # header header = re.findall("#\sof\s.*", data)[0] # writing to PROCAR fp = open(outputfile, "a") fp.write(first_line) fp.write(str(header) + "\n\n") # get all the k-point line headers kpoints_raw = re.findall("k-point\s*[0-9]\s*:*.*", data) for kpoint_counter in range(len(kpoints_raw)): if kpoint_counter == (len(kpoints_raw) - 1): # get bands of last k point bands_raw = re.findall( kpoints_raw[kpoint_counter] + "([a-z0-9\s\n.+#-]*)", data )[0] else: # get bands between k point n and n+1 bands_raw = re.findall( kpoints_raw[kpoint_counter] + "([a-z0-9\s\n.+#-]*)" + kpoints_raw[kpoint_counter + 1], data, )[0] # get the bands headers for a certain k point raw_bands = re.findall("band\s*[0-9]*.*", bands_raw) # writing k point header to file fp.write(kpoints_raw[kpoint_counter] + "\n\n") for band_counter in range(len(raw_bands)): if band_counter == (len(raw_bands) - 1): # the last band single_band = re.findall( raw_bands[band_counter] + "([a-z0-9.+\s\n-]*)", bands_raw )[0] else: # get a single band single_band = re.findall( raw_bands[band_counter] + "([a-z0-9.+\s\n-]*)" + raw_bands[band_counter + 1], bands_raw, )[0] # get the column headers for ion, orbitals and total column_header = re.findall("ion\s.*tot", single_band)[0] # get number of ions using PROCAR file nion_raw = re.findall("#\s*of\s*ions:\s*[0-9]*", data)[0] nion = int(nion_raw.split(" ")[-1]) # the first column of the band. Same as ions first_column = [] for x in single_band.split("\n"): if x != "": if x != " ": if x.split()[0] != "ion": first_column.append(x.split()[0]) # number of spin orientations norient = int(len(first_column) / nion) # calculate the number of orbital headers (s,p,d etc.) for x in single_band.split("\n"): if x != "": if x != " ": if x.split()[0] == "ion": norbital = len(x.split()) - 2 # array to store a single band data as seperate lines single_band_lines = [] for x in single_band.split("\n"): if x != "": if x != " ": if x.split()[0] != "ion": single_band_lines.append(x) # create empty array to store data (the orbitals + tot) bands_orb = np.zeros(shape=(norient, nion, norbital + 1)) # enter data into bands_orb iion = 0 iorient = 0 for x in single_band.split("\n"): if x != "" and x != " " and x.split()[0] != "ion": iline = x.split() if iion > 1: iion = 0 iorient += 1 for iorb in range(0, norbital + 1): bands_orb[iorient, iion, iorb] = float(iline[iorb + 1]) iion += 1 # create an array to store the total values tot = np.zeros(shape=(norient, norbital + 1)) # entering data into tot array for iorient in range(norient): tot[iorient, :] = np.sum(bands_orb[iorient, :, :], axis=0) # writing data fp.write(raw_bands[band_counter] + "\n\n") fp.write(column_header + "\n") band_iterator = 0 total_count = 0 for orientations_count in range(norient): for ions_count in range(nion): fp.write(single_band_lines[band_iterator] + "\n") band_iterator += 1 fp.write("tot " + " ".join(map(str, tot[total_count, :])) + "\n\n") total_count += 1 fp.close() def __contains__(self, x): return x in self.variables def __getitem__(self, x): return self.variables.__getitem__(x) def __iter__(self): return self.variables.__iter__() def __len__(self): return self.variables.__len__()
class AbinitDOSParser: def __init__( self, dirname: str = "", ): self.dirname = dirname self.dos_total, self.energies, self.fermi = self._parse_total_dos() self.projected = self._parse_projected_dos_files() self.dos = DensityOfStates( energies=self.energies, total=self.dos_total, efermi=self.fermi, projected=self.projected, interpolation_factor=1, ) def _parse_total_dos(self): self.total_dos_filename = glob.glob(self.dirname + "/abinito_DOS_TOTAL*")[0] with open(self.total_dos_filename) as f: text_lines = f.readlines() header_text = "".join(text_lines[:13]) dos_text = text_lines[13:] nsppol = int(re.findall("nsppol\s=\s(\d)", header_text)[0]) fermi = float(re.findall("Fermi energy\s:\s*([-\d*.]*)", header_text)[0]) energy_details = re.findall( "between\s*([-\d*.]*)\s*and\s*([-\d*.]*)\s*Hartree\s*by\s*steps\s*of\s*([-\d*.]*)", header_text, )[0] energy_details = [float(x) for x in energy_details] e_min, e_max, e_step = energy_details energies = np.arange(e_min, e_max + e_step, e_step) n_energies = energies.shape[0] if nsppol == 2: n_spin_header = 3 n_up_start = n_spin_header n_up_end = n_up_start + n_energies dos_up = dos_text[n_up_start:n_up_end] n_block_spacing = 3 n_down_start = n_spin_header + n_energies + n_block_spacing n_down_end = n_down_start + n_energies dos_down = dos_text[n_down_start:n_down_end] # converting to floats dos_down = np.array( [[float(value) for value in line.split()] for line in dos_down] ) dos_up = np.array( [[float(value) for value in line.split()] for line in dos_up] ) # Keep dos column dos_down = dos_down[:, 1] dos_up = dos_up[:, 1] dos_total = np.vstack([dos_up, dos_down]) else: n_header = 2 n_up_start = n_header n_up_end = n_up_start + n_energies dos_up = dos_text[n_up_start:n_up_end] # converting to floats dos_up = np.array( [[float(value) for value in line.split()] for line in dos_up] ) # Keep dos column dos_up = dos_up[:, 1] dos_total = dos_up[None, :] energies -= fermi return dos_total, energies, fermi def _parse_projected_dos_files(self): self.projected_dos_filenames = glob.glob(self.dirname + "/abinito_DOS_AT*") n_atoms = len(self.projected_dos_filenames) projected = [0] * n_atoms for filename in self.projected_dos_filenames: dos_atom_projections, atom_index = self._parse_projected_dos_file(filename) projected[atom_index - 1] = dos_atom_projections projected.append(dos_atom_projections) projected = np.array(projected) # organizing the projection array in the appropiate formate # (n_atoms, n_principals, n_orbitals, n_spins, n_dos) projected = np.transpose(projected, (0, 2, 3, 1)) # This is adding for the principle quantum number. Throughout the code this is unecessary, but puting here for consitency projected = projected[:, None, :, :, :] return projected def _parse_projected_dos_file(self, filename): with open(filename) as f: text_lines = f.readlines() header_text = "".join(text_lines[:13]) dos_text = text_lines[13:] nsppol = int(re.findall("nsppol\s=\s(\d)", header_text)[0]) energy_details = re.findall( "between\s*([-\d*.]*)\s*and\s*([-\d*.]*)\s*Hartree\s*by\s*steps\s*of\s*([-\d*.]*)", header_text, )[0] energy_details = [float(x) for x in energy_details] e_min, e_max, e_step = energy_details atom_detail_text = "".join(dos_text[:4]) atom_index = int(re.findall("iatom=\s*(\d)", atom_detail_text)[0]) energies = np.arange(e_min, e_max + e_step, e_step) n_energies = energies.shape[0] if nsppol == 2: n_spin_header = 7 n_up_start = n_spin_header n_up_end = n_up_start + n_energies dos_up = dos_text[n_up_start:n_up_end] n_block_spacing = 7 n_down_start = n_up_end + n_block_spacing n_down_end = n_down_start + n_energies dos_down = dos_text[n_down_start:n_down_end] # converting to floats dos_down = np.array( [[float(value) for value in line.split()] for line in dos_down] ) dos_up = np.array( [[float(value) for value in line.split()] for line in dos_up] ) # Keep only s,p,d projections dos_down = dos_down[:, 11:20] dos_up = dos_up[:, 11:20] dos_atom_projections = np.dstack([dos_up, dos_down]) else: n_spin_header = 6 n_up_start = n_spin_header n_up_end = n_up_start + n_energies dos_up = dos_text[n_up_start:n_up_end] # converting to floats dos_up = np.array( [[float(value) for value in line.split()] for line in dos_up] ) # Keep only s,p,d projections dos_up = dos_up[:, 11:20] dos_atom_projections = dos_up[:, :, None] return dos_atom_projections, atom_index