Source code for castep_outputs.parsers.phonon_file_parser

"""Parse castep .phonon files."""
from __future__ import annotations

from collections import defaultdict
from typing import TextIO

from ..utilities.datatypes import ComplexThreeVector, QData, StandardHeader, ThreeVector
from ..utilities.filewrapper import Block
from ..utilities.utility import file_or_path, log_factory, to_type
from .parse_utilities import parse_regular_header


[docs] class PhononFileQPoint(QData, total=False): """Q-point info from a phonon file.""" #: Direction to next Q-point dir: ThreeVector #: Eigenvalues of Q-point. eigenvalues: list[float] #: Eigenvectors of Q-point. eigenvectors: list[ComplexThreeVector]
[docs] class PhononFileInfo(StandardHeader, total=False): """Full phonon file information.""" #: Number of ions in system. ions: int #: Number of phonon branches. branches: int #: Number of wavevectors in calculation. wavevectors: int #: Per Q-point information. qpts: list[PhononFileQPoint]
[docs] @file_or_path(mode="r") def parse_phonon_file(phonon_file: TextIO) -> PhononFileInfo: """Parse castep .phonon file. Parameters ---------- phonon_file A handle to a CASTEP .phonon file. Returns ------- PhononFileInfo Parsed data. """ # pylint: disable=too-many-locals logger = log_factory(phonon_file) phonon_info: PhononFileInfo = defaultdict(list) for line in phonon_file: if block := Block.from_re(line, phonon_file, "BEGIN header", "END header"): data = parse_regular_header(block) phonon_info.update(data) del phonon_info[""] eigenvectors_endblock = (format(phonon_info["branches"], ">4") + format(phonon_info["ions"], ">4")) elif block := Block.from_re(line, phonon_file, "q-pt", "Phonon Eigenvectors"): logger("Found eigenvalue block") _, _, posx, posy, posz, weight, *qpoint_dir = line.split() qdata: PhononFileQPoint = defaultdict(list) qdata["qpt"] = to_type((posx, posy, posz), float) qdata["weight"] = float(weight) if qpoint_dir: qdata["dir"] = to_type(qpoint_dir, float) if len(block) - 2 != phonon_info["branches"]: raise ValueError("Malformed phonon frequencies. " f"Expected {phonon_info['branches']} branches, " f"received {len(block)-2}") block.remove_bounds(1, 1) for line in block: # Line columns taken from phonon.f90 eigenvalue, ir_intensity, raman_intensity = (line[8:23], line[31:46], line[55:70]) qdata["eigenvalues"].append(float(eigenvalue)) if ir_intensity.strip(): qdata["ir_intensity"].append(float(ir_intensity)) if raman_intensity.strip(): qdata["raman_intensity"].append(float(raman_intensity)) elif block := Block.from_re(line, phonon_file, "Mode Ion", eigenvectors_endblock): logger("Found eigenvector block") expected_n_eigenvec = phonon_info["branches"] * phonon_info["ions"] if len(block) - 1 != expected_n_eigenvec: raise ValueError("Malformed eigenvectors. " f"Expected {expected_n_eigenvec} branches, " f"received {len(block)-1}") block.remove_bounds(1, 0) for line in block: _, _, *eigenvectors = line.split() eigenvectors = to_type(eigenvectors, float) eigenvectors = map(complex, eigenvectors[::2], eigenvectors[1::2]) qdata["eigenvectors"].append(tuple(eigenvectors)) phonon_info["qpts"].append(qdata) return phonon_info