From f3da9a22a09795bbceb80448dcf6b52316ed4dbf Mon Sep 17 00:00:00 2001 From: Jacob Wilkins Date: Tue, 30 Apr 2024 11:42:47 +0100 Subject: [PATCH] Add delocalised coordinates parsers --- castep_outputs/parsers/castep_file_parser.py | 125 +++++++++++++++++-- castep_outputs/test/test_castep_parser.py | 40 ++++-- castep_outputs/utilities/datatypes.py | 20 +++ 3 files changed, 160 insertions(+), 25 deletions(-) diff --git a/castep_outputs/parsers/castep_file_parser.py b/castep_outputs/parsers/castep_file_parser.py index ac0972b..f2c010c 100644 --- a/castep_outputs/parsers/castep_file_parser.py +++ b/castep_outputs/parsers/castep_file_parser.py @@ -16,18 +16,22 @@ from ..utilities.castep_res import (gen_table_re, get_numbers, labelled_floats) from ..utilities.constants import SHELLS -from ..utilities.datatypes import (AtomIndex, AtomPropBlock, BandStructure, - BondData, CellInfo, CharTable, - ConstraintsReport, DipoleTable, - ElasticProperties, FinalConfig, GeomTable, - InitialSpin, KPointsList, KPointsSpec, - MDInfo, MemoryEst, MullikenInfo, - Occupancies, PhononSymmetryReport, - PSPotEnergy, PSPotReport, PSPotStrInfo, - QData, RamanReport, SCFReport, SixVector, - SymmetryReport, TDDFTData, Thermodynamics, - ThreeByThreeMatrix, ThreeVector, - WvfnLineMin) +from ..utilities.datatypes import (AtomIndex, AtomPropBlock, + BandStructure, BondData, CellInfo, + CharTable, ConstraintsReport, + DelocActiveSpace, + DelocInternalsTable, DipoleTable, + ElasticProperties, FinalConfig, + GeomTable, InitialSpin, + InternalConstraints, KPointsList, + KPointsSpec, MDInfo, MemoryEst, + MullikenInfo, Occupancies, + PhononSymmetryReport, PSPotEnergy, + PSPotReport, PSPotStrInfo, QData, + RamanReport, SCFReport, SixVector, + SymmetryReport, TDDFTData, + Thermodynamics, ThreeByThreeMatrix, + ThreeVector, WvfnLineMin) from ..utilities.filewrapper import Block, FileWrapper from ..utilities.utility import (add_aliases, atreg_to_index, determine_type, fix_data_types, log_factory, normalise_key, @@ -1024,6 +1028,38 @@ def parse_castep_file(castep_file_in: TextIO, curr_run["minimisation"].append(_process_geom_table(block)) + # GeomOpt Deloc + + elif block := Block.from_re(line, castep_file, + "INTERNAL CONSTRAINTS", REs.EMPTY, n_end=2): + + if Filters.GEOM_OPT not in to_parse: + continue + + curr_run["internal_constraints"] = _process_internal_constraints(block) + + elif block := Block.from_re(line, castep_file, + "Message: Generating deloc", "Message: Generation of deloc"): + + if Filters.GEOM_OPT not in to_parse: + continue + + if "delocalised_internal" not in curr_run: + curr_run["delocalised_internal"] = {} + + curr_run["delocalised_internal"].update(_process_deloc_table(block)) + + elif block := Block.from_re(line, castep_file, + "The size of active space", REs.EMPTY): + + if Filters.GEOM_OPT not in to_parse: + continue + + if "delocalised_internal" not in curr_run: + curr_run["delocalised_internal"] = {} + + curr_run["delocalised_internal"].update(_process_deloc_act_space_table(block)) + # TDDFT elif block := Block.from_re(line, castep_file, gen_table_re("TDDFT excitation energies", r"\+", post="TDDFT"), @@ -2326,3 +2362,68 @@ def _process_elastic_properties(block: Block) -> ElasticProperties: ) return accum + + +def _process_internal_constraints(block: TextIO) -> List[InternalConstraints]: + + # Skip table headers + for _ in zip(range(3), block): + pass + + accum = [] + for line in block: + if not line.strip(): + continue + + _, type_, target, actual, *definitions = line.split() + if actual == "Satisfied": + target, actual = actual, target + else: + target = float(target) + actual = float(actual) + + definitions = " ".join(definitions).split(")") + const = {val[0]: tuple(val[1:]) + for definition in definitions + if definition and + (val := to_type(get_numbers(definition), int))} + assert type_ in ("Bond", "Angle", "Torsion") + + elem: InternalConstraints = {"type": type_, + "target": target, + "actual": actual, + "constraints": const} + accum.append(elem) + + return accum + + +def _process_deloc_table(block: TextIO) -> DelocInternalsTable: + + accum: DelocInternalsTable = {"constraint_mapping": {}} + for line in block: + if line.startswith(" constraint"): + key, val = to_type(get_numbers(line), int) + accum["constraint_mapping"][key] = val + elif line.startswith("Total number of primitive"): + type_, val = line.split()[4:6] + type_ = "num_" + type_.strip(":") + val = int(val) + + accum[type_] = val + + return accum + + +def _process_deloc_act_space_table(block: TextIO) -> DelocActiveSpace: + + accum = {} + for line in block: + if "active space" in line: + accum["active_space_size"] = to_type(get_numbers(line)[0], int) + if "degrees" in line: + accum["num_dof"] = to_type(get_numbers(line)[0], int) + if "primitive" in line: + accum["num_primitive_internals"] = to_type(get_numbers(line)[0], int) + + return accum diff --git a/castep_outputs/test/test_castep_parser.py b/castep_outputs/test/test_castep_parser.py index 8497a81..020f46f 100644 --- a/castep_outputs/test/test_castep_parser.py +++ b/castep_outputs/test/test_castep_parser.py @@ -2720,10 +2720,29 @@ def test_get_deloc_info(self): Message: Generation of delocalized internals is successful """) - self.skipTest("Not implemented yet") test_dict = parse_castep_file(test_text)[0] - pprint.pprint(test_dict) - self.assertEqual(test_dict, {}) + + self.assertEqual(test_dict, {'delocalised_internal': + {'constraint_mapping': {1: 15, + 2: 43}, + 'num_angles': 48, + 'num_bonds': 16, + 'num_dihedrals': 144, + 'num_internals': 208}, + 'internal_constraints': [ + {'actual': 2.36, + 'constraints': {4: (0, 0, 0), + 5: (0, 0, -1)}, + 'target': 'Satisfied', + 'type': 'Bond'}, + {'actual': 109.47, + 'constraints': {2: (1, 0, 0), + 3: (0, 0, 0), + 5: (0, 0, 0)}, + 'target': 'Satisfied', + 'type': 'Angle'} + ] + }) def test_get_deloc_step(self): test_text = io.StringIO(""" @@ -2734,18 +2753,13 @@ def test_get_deloc_step(self): There are : 24 degrees of freedom There are : 67 primitive internals - - INTERNAL CONSTRAINTS - # Type Target Actual Definition - - 1 Bond 2.360 Satisfied 4 ( 0 0 0) 5 ( 0 0-1) - 2 Angle 109.47 Satisfied 3 ( 0 0 0) 5 ( 0 0 0) 2 ( 1 0 0) - """) - self.skipTest("Not implemented yet") + test_dict = parse_castep_file(test_text)[0] - pprint.pprint(test_dict) - self.assertEqual(test_dict, {}) + + self.assertEqual(test_dict, {'delocalised_internal': {'active_space_size': 19, + 'num_dof': 24, + 'num_primitive_internals': 67}}) def test_get_tss_structure(self): test_text = io.StringIO(""" diff --git a/castep_outputs/utilities/datatypes.py b/castep_outputs/utilities/datatypes.py index b15c9f3..2f79095 100644 --- a/castep_outputs/utilities/datatypes.py +++ b/castep_outputs/utilities/datatypes.py @@ -66,6 +66,26 @@ class GeomTable(TypedDict): dr_max: GeomTableElem +class InternalConstraints(TypedDict): + type: Literal["Bond", "Angle", "Torsion"] + actual: float + target: Union[Literal["Satisfied"], float] + constraints: Dict[int, ThreeVector] + + +class DelocInternalsTable(TypedDict): + num_bonds: int + num_angles: int + num_dihedrals: int + num_internals: int + contraints: Dict[int, int] + + +class DelocActiveSpace(TypedDict): + num_dof: int + num_primitive_internals: int + active_space_size: int + # Dipole class DipoleTable(TypedDict):