Source code for dfttopif.parsers.base

import os
from collections import Counter
from pypif.obj.common import Value, Property


[docs]def Value_if_true(func): '''Returns: Value if x is True, else None''' return lambda x: Value() if func(x) == True else None
[docs]class DFTParser(object): '''Base class for all tools to parse a directory of output files from a DFT Calculation To use this class, provide the path to a directory to containing the output files from a DFT calculation. Once instantiated, call the methods provided by this class in order to retrieve the settings and results of the calculation. To get a list of names of the settings available for this a particular instance, call get_setting_functions(). These methods return a pypif Value object. To get a list of the names of results available via a particular instance, call get_result_functions(). These methods return a pypif Property object. Developer Notes --------------- Settings and properties should... return None if property or setting is not applicable, or if the test is a boolean and the value is false Raise exception if there is an error that would benefit from user intervention To add a new setting or value to the output, add a new entry to the dictionary returned by get_setting_functions() or get_result_functions(). The key for each entry is a 'human-friendly' name of the result and the value is the name of the function. This design was chosen because there is a single function for defining the human names of the results, which are what serve as the tags in the pif file. In this way, the same property will be ensured to have the same name in the pif. ''' _directory = None '''Path to directory containing calculation files''' _converged = None ''' Whether this calculation has converged '''
[docs] def __init__(self, directory): '''Initialize a parser. Input: directory - String, path to a directory of output files ''' # Sanity check: Make sure the format is correct if not self.test_if_from(directory): raise Exception('Files in directory inconsistent with this format') self._directory = directory
@classmethod
[docs] def test_if_from(self, directory): '''Test whether a directory of output files seems like it is from this DFT code. Input: directory - String, path to a directory of output files Returns: boolean, whether directory was created by this code ''' raise NotImplementedError
[docs] def get_setting_functions(self): '''Get a dictionary containing the names of methods that return settings of the calculation Returns: dict, where the key is the name of the setting, and the value is function name of this parser ''' return { 'XC Functional':'get_xc_functional', 'Relaxed':'is_relaxed', 'Cutoff Energy':'get_cutoff_energy', 'k-Points per Reciprocal Atom':'get_KPPRA', 'Spin-Orbit Coupling':'uses_SOC', 'DFT+U':'get_U_settings', 'vdW Interactions':'get_vdW_settings', 'Psuedopotentials':'get_pp_name', 'INCAR':'get_incar', 'POSCAR':'get_poscar', }
[docs] def get_result_functions(self): '''Get a dictionary describing the names of methods that return results of the calculation Returns: dict, where the key is the name of a property, and the value is the name of the function ''' return { 'Converged':'is_converged', 'Total Energy':'get_total_energy', 'Band Gap Energy':'get_band_gap', 'Pressure':'get_pressure', 'Density of States':'get_dos', 'Positions': 'get_positions', 'Forces': 'get_forces', 'OUTCAR': 'get_outcar', }
def _call_ase(self, func): '''Make a call to an ASE function. Handles changing directories Returns: Result of ASE function ''' # Change directories old_path = os.getcwd() os.chdir(self._directory) # Call function try: res = func() except: os.chdir(old_path) raise # Change back os.chdir(old_path) return res
[docs] def get_name(self): '''Get the name of this program''' raise NotImplementedError
[docs] def get_version_number(self): '''Get the version number of code that created these output files Returns: string, Version number ''' raise NotImplementedError
[docs] def get_output_structure(self): '''Get the output structure, if available Returns: ase.Atoms - Output structure from this calculation or None if output file not found ''' raise NotImplementedError
[docs] def get_composition(self): '''Get composition of output structure Returns: String - Composition based on output structure ''' strc = self.get_output_structure() counts = Counter(strc.get_chemical_symbols()) return ''.join(k if counts[k]==1 else '%s%d'%(k,counts[k]) \ for k in sorted(counts))
[docs] def get_positions(self): strc = self.get_output_structure() return Property(vectors=strc.positions.tolist())
[docs] def get_cutoff_energy(self): '''Read the cutoff energy from the output Returns: Value, cutoff energy (scalar) and units ''' raise NotImplementedError
[docs] def uses_SOC(self): '''Parse the output file to tell if spin-orbit coupling was used Returns: Blank Value if true, `None` otherwise ''' raise NotImplementedError
[docs] def is_relaxed(self): '''Parse the output file to tell if the structure was relaxed Returns: Blank Value if true, `None` otherwise ''' raise NotImplementedError
[docs] def get_xc_functional(self): '''Parse the output file to tell which exchange-correlation functional was used Returns: Value - where "scalars" is the name of the functional ''' raise NotImplementedError
[docs] def is_converged(self): '''Whether the calculation has converged Returns: Property where "scalar" is a boolean indicating ''' # Check for cached result if self._converged is None: self._converged = self._is_converged() return Property(scalars=self._converged)
[docs] def get_pp_name(self): '''Read output to get the pseudopotentials names used for each elements Returns: Value where the key "scalars" is the list of pseudopotentials names ''' raise NotImplementedError
[docs] def get_KPPRA(self): '''Read output and calculate the number of k-points per reciprocal atom Returns: Value, number of k-points per reciprocal atom ''' raise NotImplementedError
[docs] def get_U_settings(self): '''Get the DFT+U settings, if used Returns: Value, which could contain several keys 'Type' -> String, type of DFT+U employed 'Values' -> dict of Element -> (L, U, J) Note: Returns None if DFT+U was not used ''' raise NotImplementedError
[docs] def get_vdW_settings(self): '''Get the vdW settings, if applicable Returns: Value where `scalars` is the name of the vdW method. None if vdW was not used''' raise NotImplementedError
# Operations for retrieving results def _is_converged(self): '''Read output to see whether it is converged Hidden operation: self.is_converged() is the public interface, which may draw from a converged result Returns: boolean''' raise NotImplementedError
[docs] def get_total_energy(self): '''Get the total energy of the last ionic step Returns: Property ''' raise NotImplementedError
[docs] def get_band_gap(self): '''Get the band gap energy Returns: Property''' raise NotImplementedError
[docs] def get_pressure(self): '''Get the pressure acting on the system Returns: Property, where pressure is a scalar''' raise NotImplementedError
[docs] def get_dos(self): '''Get the total density of states Returns: Property where DOS is a vector, and the energy at which the DOS was evaluated is a condition''' raise NotImplementedError
[docs] def get_stresses(self): '''Get the stress tensor Returns: Property where stresses is a 2d matrix''' raise NotImplementedError