Source code for vasppy.summary

# Summary class and helper methods
# Used for summarising VASP calculations as YAML

from pymatgen.io.vasp.outputs import Vasprun
from pymatgen.analysis.transition_state import NEBAnalysis
from vasppy.vaspmeta import VASPMeta
from vasppy.outcar import final_energy_from_outcar, vasp_version_from_outcar, potcar_eatom_list_from_outcar
from vasppy.data.potcar_data import potcar_md5sum_data, potcar_nelect
from vasppy.utils import file_md5, md5sum, match_filename, cd
from xml.etree import ElementTree as ET 
import sys
import yaml
import glob
import re

potcar_sets = [ 'PBE', 'PBE_52', 'PBE_54' ]

[docs]def potcar_spec( filename ): """ Returns a dictionary specifying the pseudopotentials contained in a POTCAR file. Args: filename (Str): The name of the POTCAR file to process. Returns: (Dict): A dictionary of pseudopotential filename: dataset pairs, e.g. { 'Fe_pv': 'PBE_54', 'O', 'PBE_54' } """ p_spec = {} with open( filename, 'r' ) as f: potcars = re.split('(End of Dataset\n)', f.read() ) potcar_md5sums = [ md5sum( ''.join( pair ) ) for pair in zip( potcars[::2], potcars[1:-1:2] ) ] for this_md5sum in potcar_md5sums: for ps in potcar_sets: for p, p_md5sum in potcar_md5sum_data[ ps ].items(): if this_md5sum == p_md5sum: p_spec[ p ] = ps if len( p_spec ) != len( potcar_md5sums ): raise ValueError( 'One or more POTCARs did not have matching md5 hashes' ) return p_spec
[docs]def find_vasp_calculations(): """ Returns a list of all subdirectories that contain either a vasprun.xml file or a compressed vasprun.xml.gz file. Args: None Returns: (List): list of all VASP calculation subdirectories. """ dir_list = [ './' + re.sub( r'vasprun\.xml', '', path ) for path in glob.iglob( '**/vasprun.xml', recursive=True ) ] gz_dir_list = [ './' + re.sub( r'vasprun\.xml\.gz', '', path ) for path in glob.iglob( '**/vasprun.xml.gz', recursive=True ) ] return dir_list + gz_dir_list
[docs]class Summary: """ TODO Document Summary class """ supported_flags = { 'title': 'Title', 'description': 'Description', 'notes': 'Notes', 'type': 'Type', 'status': 'Status', 'stoichiometry': 'Stoichiometry', 'potcar': 'POTCAR', 'eatom': 'POTCAR EATOM values', 'plus_u': 'Dudarev +U parameters', 'energy': 'Energy', 'lreal': 'LREAL', 'k-points': 'k-points', 'functional': 'functional', 'encut': 'encut', 'ediffg': 'ediffg', 'ibrion': 'ibrion', 'converged': 'converged', 'md5': 'md5', 'directory': 'directory', 'vbm': 'Vasprun valence band maximum', 'cbm': 'Vasprun conduction band minimum', 'track': 'tracking for files', 'version': 'VASP executable version', 'nelect': 'NELECT' } def __init__( self, directory='.' ): self.directory = directory with cd( directory ): try: self.meta = VASPMeta.from_file( 'vaspmeta.yaml' ) except FileNotFoundError as e: raise type(e)( str(e) + ' in {}'.format( directory )).with_traceback( sys.exc_info()[2] ) self.parse_vasprun() self.print_methods = { 'title': self.print_title, 'description': self.print_description, 'notes': self.print_notes, 'type': self.print_type, 'status': self.print_status, 'stoichiometry': self.print_stoichiometry, 'potcar': self.print_potcar, 'eatom': self.print_eatom, 'energy': self.print_energy, 'k-points': self.print_kpoints, 'functional': self.print_functional, 'encut': self.print_encut, 'plus_u': self.print_plus_u, 'ediffg': self.print_ediffg, 'ibrion': self.print_ibrion, 'converged': self.print_converged, 'version': self.print_version, 'md5': self.print_vasprun_md5, 'directory': self.print_directory, 'lreal': self.print_lreal, 'vbm': self.print_vbm, 'cbm': self.print_cbm, 'track': self.print_file_tracking, 'nelect': self.print_nelect } if not set( self.print_methods.keys() ) == set( self.supported_flags ): print( set( self.print_methods.keys() ) ) print( '--------------' ) print( set( self.supported_flags.keys() ) ) raise( ValueError )
[docs] def parse_vasprun( self ): """ Read in `vasprun.xml` as a pymatgen Vasprun object. Args: None Returns: None None: If the vasprun.xml is not well formed this method will catch the ParseError and set self.vasprun = None. """ self.vasprun_filename = match_filename( 'vasprun.xml' ) if not self.vasprun_filename: raise FileNotFoundError( 'Could not find vasprun.xml or vasprun.xml.gz file' ) try: self.vasprun = Vasprun( self.vasprun_filename, parse_potcar_file=False ) except ET.ParseError: self.vasprun = None except: raise
@property def stoich( self ): return self.vasprun.final_structure.composition.get_el_amt_dict() @property def functional( self ): """ String description of the calculation functional. Recognises: - PBE - PBEsol - PBE-based hybrids: - PBE0 (alpha=0.25, no screening) - HSE06 (alpha=0.25, mu=0.2) - generic hybrids (alpha=?, no screening) - generic screened hybrids (alpha=?, mu=?) Returns: (Str): String describing the calculation functional. """ if self.potcars_are_pbe(): # PBE base funtional if 'LHFCALC' in self.vasprun.parameters: alpha = float( self.vasprun.parameters['AEXX'] ) else: alpha = 0.0 if 'HFSCREEN' in self.vasprun.parameters: mu = float( self.vasprun.parameters['HFSCREEN'] ) else: mu = 0 if alpha > 0: if mu > 0: # screened hybrid if ( mu == 0.2 ) and ( alpha == 0.25 ): f = 'HSE06' else: f = "screened hybrid. alpha={}, mu={}".format( alpha, mu ) else: # unscreened hybrid if alpha == 0.25: f = 'PBE0' else: f = "hybrid. alpha={}".format( alpha ) else: # not hybrid. Plain PBE or some variant. pbe_list = { 'PS': 'PBEsol', 'PE': 'PBE', '91': 'PW91', 'RP': 'rPBE', 'AM': 'AM05' } f = pbe_list[ self.vasprun.parameters['GGA'] ] else: f = 'not recognised' return f
[docs] def potcars_are_pbe( self ): return all( 'PBE' in s for s in self.vasprun.potcar_symbols )
[docs] def output( self, to_print ): if not self.vasprun: to_print = [ 'title', 'type', 'status' ] print( "---" ) for p in to_print: self.print_methods[ p ]() print( '', flush=True )
[docs] def print_type( self ): if self.meta.type: print( "type: {}".format( self.meta.type ) )
[docs] def print_title( self ): print( "title: {}".format( self.meta.title ) )
[docs] def print_description( self ): print( "description: {}".format( self.meta.description.strip() ) )
[docs] def print_notes( self ): print( "notes: {}".format( self.meta.notes.strip() ) )
[docs] def print_status( self ): print( "status: {}".format( self.meta.status ) )
[docs] def print_lreal( self ): print( "lreal: {}".format( self.vasprun.parameters['LREAL'] ) )
[docs] def print_stoichiometry( self ): print( "stoichiometry:" ) for element in self.stoich: print( " - {}: {}".format( element, int( self.stoich[ element ] ) ) )
[docs] def print_potcar( self ): print( "potcar:" ) for e, p in zip( self.stoich, self.vasprun.potcar_symbols ): print( " - {}: {}".format( e, p ) )
[docs] def print_energy( self ): # if this gets more options, it might be a good idea to set the # appropriate method using a dictionary? # or we could subclass Summary --> NEB_Summary ? if not self.meta.type: print( "energy: {}".format( self.vasprun.final_energy ) ) elif self.meta.type == 'neb': self.print_neb_energy() else: raise ValueError( "VASPMeta type not supported: {}".format( self.meta.type ) )
[docs] def print_neb_energy( self ): image_00_energy = final_energy_from_outcar( '00/OUTCAR' ) print( "reference energy: {} eV".format( image_00_energy ) ) neb = NEBAnalysis.from_dir( '.' ) print( "neb image energies:" ) for i, e in enumerate( neb.energies ): print( " - {:02d}: {:10.6f} eV".format( i, e ) )
[docs] def print_version( self ): version_string = vasp_version_from_outcar( '{}/OUTCAR'.format( self.directory ) ).split()[0] print( "version: {}".format( version_string ) )
[docs] def print_eatom( self ): # This is one way to try to uniquely identify the POTCARs used, because the # potcar_symbol (e.g. `Ti_pv 07Sep2000`) is not sufficient. print( "eatom:" ) for e, eatom in zip( self.stoich, potcar_eatom_list_from_outcar( '{}/OUTCAR'.format( self.directory ) ) ): print( " - {}: {} eV".format( e, eatom ) )
[docs] def print_kpoints( self ): print( "k-points:" ) print( " scheme: {}".format( self.vasprun.kpoints.style ) ) print( " grid: {}".format( " ".join( str( k ) for k in self.vasprun.kpoints.kpts[0] ) ) )
[docs] def print_functional( self ): print( "functional: {}".format( self.functional ) )
[docs] def print_ibrion( self ): print( "ibrion: {}".format( self.vasprun.incar['IBRION'] ) )
[docs] def print_ediffg( self ): print( "ediffg: {}".format( self.vasprun.incar['EDIFFG'] ) )
[docs] def print_encut( self ): if 'ENCUT' in self.vasprun.incar: print( "encut: {}".format( self.vasprun.incar['ENCUT'] ) ) elif 'ENMAX' in self.vasprun.incar: print( "encut: {}".format( self.vasprun.incar['ENMAX'] ) )
[docs] def print_converged( self ): print( "converged: {}".format( self.vasprun.converged ) )
[docs] def print_vasprun_md5( self ): print( "vasprun md5: {}".format( file_md5( "{}/{}".format( self.directory, self.vasprun_filename ) ) ) )
[docs] def print_file_tracking( self ): if self.meta.track: print( "file tracking:" ) for f, new_filename in self.meta.track.items(): print( " {}:".format( f ) ) if not new_filename: new_filename = f print( " filename: {}".format( new_filename ) ) filename = match_filename( self.directory + f ) if filename: md5 = file_md5( filename ) else: md5 = 'null' print( " md5: {}".format( md5 ) )
[docs] def print_directory( self ): print( "directory: {}".format( self.directory ) )
[docs] def print_plus_u( self ): if 'LDAUU' in self.vasprun.incar: lqn = { 0: 's', 1: 'p', 2: 'd', 3: 'f' } ldauu = self.vasprun.incar[ 'LDAUU' ] ldauj = self.vasprun.incar[ 'LDAUJ' ] ldaul = self.vasprun.incar[ 'LDAUL' ] if any( v != 0 for v in ldauu ): print( 'ldau:' ) for e, u, j, l in zip( self.stoich, ldauu, ldauj, ldaul ): if u != 0: print( " - {}: {} {} {}".format( e, lqn[l], u, j ) )
[docs] def print_cbm( self ): print( 'cbm: {}'.format( self.vasprun.eigenvalue_band_properties[1] ) )
[docs] def print_vbm( self ): print( 'vbm: {}'.format( self.vasprun.eigenvalue_band_properties[2] ) )
[docs] def print_nelect( self ): print( 'nelect: {}'.format( self.vasprun.parameters['NELECT'] ) )