Source code for aiida_crystal17.parsers.raw.main_out

"""
parse the main output file and create the required output nodes
"""
from collections import Mapping
import traceback
from aiida_crystal17.symmetry import convert_structure
from aiida.plugins import DataFactory
from aiida_crystal17 import __version__
from aiida_crystal17.calculations.cry_main import CryMainCalculation
from aiida_crystal17.parsers.raw import crystal_stdout


[docs]class OutputNodes(Mapping): """ a mapping of output nodes, with attribute access """ def __init__(self): self._dict = { "results": None, "structure": None, "symmetry": None } def _get_results(self): return self._dict["results"] def _set_results(self, value): assert isinstance(value, DataFactory('dict')) self._dict["results"] = value results = property(_get_results, _set_results) def _get_structure(self): return self._dict["structure"] def _set_structure(self, value): assert isinstance(value, DataFactory('structure')) self._dict["structure"] = value structure = property(_get_structure, _set_structure) def _get_symmetry(self): return self._dict["symmetry"] def _set_symmetry(self, value): assert isinstance(value, DataFactory('crystal17.symmetry')) self._dict["symmetry"] = value symmetry = property(_get_symmetry, _set_symmetry) def __getitem__(self, value): out = self._dict[value] if out is None: raise KeyError(value) return out def __iter__(self): for key, val in self._dict.items(): if val is not None: yield key def __len__(self): len([k for k, v in self._dict.items() if v is not None])
[docs]class ParserResult(object): def __init__(self): self.exit_code = None self.nodes = OutputNodes()
# pylint: disable=too-many-locals,too-many-statements
[docs]def parse_main_out(fileobj, parser_class, init_struct=None, init_settings=None): """ parse the main output file and create the required output nodes :param fileobj: handle to main output file :param parser_class: a string denoting the parser class :param init_struct: input structure :param init_settings: input structure settings :return parse_result """ parser_result = ParserResult() exit_codes = CryMainCalculation.exit_codes results_data = { "parser_version": str(__version__), "parser_class": str(parser_class), "parser_errors": [], "parser_warnings": [], "parser_exceptions": [], "errors": [], "warnings": [] } try: data = crystal_stdout.read_crystal_stdout(fileobj.read()) except IOError as err: # should never happen traceback.print_exc() parser_result.exit_code = exit_codes.ERROR_PARSING_STDOUT results_data["parser_exceptions"].append( "Error parsing CRYSTAL 17 main output: {0}".format(err)) parser_result.nodes.results = DataFactory("dict")(dict=results_data) return parser_result # TODO could also read .gui file for definitive final (primitive) geometry, # with symmetries # TODO could also read .SCFLOG, to get scf output for each opt step # TODO could also read files in .optstory folder, # to get (primitive) geometries (+ symmetries) for each opt step # Note the above files are only available for optimisation runs results_data.update(data) # TODO handle errors try: final_info = crystal_stdout.extract_final_info(data) except ValueError: traceback.print_exc() final_info = {} results_data.pop("initial_geometry", None) initial_scf = results_data.pop("initial_scf", None) optimisation = results_data.pop("optimisation", None) results_data.pop("final_geometry", None) mulliken_analysis = results_data.pop("mulliken", None) stdout_exit_code = results_data.pop("exit_code") if initial_scf is not None: results_data["scf_iterations"] = len(initial_scf.get("cycles", [])) if optimisation is not None: # the first optimisation step is the initial scf results_data["opt_iterations"] = len(optimisation) + 1 # TODO read separate energy contributions results_data["energy"] = final_info.get("energy", None) # we include this for back compatibility results_data["energy_units"] = results_data.get("units", {}).get("energy", "eV") # TODO read from fort.34 (initial and final) file and check consistency of final cell/symmops structure = _extract_structure(final_info, init_struct, results_data, parser_result, exit_codes) if structure is not None and (optimisation is not None or not init_struct): parser_result.nodes.structure = structure _extract_symmetry( final_info, init_settings, results_data, parser_result, exit_codes) if mulliken_analysis is not None: _extract_mulliken(mulliken_analysis, results_data) parser_result.nodes.results = DataFactory("dict")(dict=results_data) if stdout_exit_code: parser_result.exit_code = exit_codes[stdout_exit_code] return parser_result
def _extract_symmetry(final_data, init_settings, param_data, parser_result, exit_codes): """extract symmetry operations""" if "primitive_symmops" not in final_data: param_data["parser_errors"].append( "primitive symmops were not found in the output file") parser_result.exit_code = exit_codes.ERROR_SYMMETRY_NOT_FOUND return if init_settings: if init_settings.num_symops != len(final_data["primitive_symmops"]): param_data["parser_errors"].append( "number of symops different") parser_result.exit_code = exit_codes.ERROR_SYMMETRY_INCONSISTENCY # differences = init_settings.compare_operations( # final_data["primitive_symmops"]) # if differences: # param_data["parser_errors"].append( # "output symmetry operations were not the same as " # "those input: {}".format(differences)) # parser_result.success = False else: from aiida.plugins import DataFactory symmetry_data_cls = DataFactory('crystal17.symmetry') data_dict = { "operations": final_data["primitive_symmops"], "basis": "fractional", "hall_number": None } parser_result.nodes.symmetry = symmetry_data_cls(data=data_dict) def _extract_structure(final_data, init_struct, results_data, parser_result, exit_codes): """create a StructureData object of the final configuration""" if "primitive_cell" not in final_data: results_data["parser_errors"].append( "final primitive cell was not found in the output file") parser_result.exit_code = exit_codes.ERROR_PARSING_STDOUT return None cell_data = final_data["primitive_cell"] results_data["number_of_atoms"] = len(cell_data["atomic_numbers"]) results_data["number_of_assymetric"] = sum(cell_data["assymetric"]) cell_vectors = [] for n in "a b c".split(): cell_vectors.append(cell_data["cell_vectors"][n]) # we want to reuse the kinds from the input structure, if available if not init_struct: results_data["parser_warnings"].append( "no initial structure available, creating new kinds for atoms") kinds = None else: kinds = [ init_struct.get_kind(n) for n in init_struct.get_site_kindnames() ] structure = convert_structure({ "lattice": cell_vectors, "pbc": cell_data["pbc"], "symbols": cell_data["symbols"], "ccoords": cell_data["ccoords"], "kinds": kinds }, "aiida") results_data["volume"] = structure.get_cell_volume() return structure def _extract_mulliken(data, param_data): """extract mulliken electronic charge partition data""" if "alpha+beta_electrons" in data: electrons = data["alpha+beta_electrons"]["charges"] anum = data["alpha+beta_electrons"]["atomic_numbers"] param_data["mulliken_electrons"] = electrons param_data["mulliken_charges"] = [ a - e for a, e in zip(anum, electrons) ] if "alpha-beta_electrons" in data: param_data["mulliken_spins"] = data["alpha-beta_electrons"]["charges"] param_data["mulliken_spin_total"] = sum( param_data["mulliken_spins"])