Source code for src.bond_angles

#!/usr/bin/env python3
"""
The BondAngles class calculates or searches for and stores bond angle data from
an ORCA .out file.
"""
__author__ = "Peter Waddell"
__copyright__ = "Copyright 2024"
__credits__ = ["Peter Waddell"]
__version__ = "0.1.0"
__date__ = "2024/02/27"
__maintainer__ = "Peter Waddell"
__email__ = "pmwaddell9@gmail.com"
__status__ = "Prototype"

import math

from orca_data_extraction.src.data_section_with_inputs import DataSectionWithInputs
from orca_data_extraction.src.final_geom import FinalGeometry


[docs] class BondAngles(DataSectionWithInputs): """ Finds and stores bond angle data from an ORCA .out file. Methods ------- _search Search the .out file for bond angle data. __calc_atom_angle Manually calcs the "bond angle" of the 3 atoms in the input angle tuple. get_datum Gives the bond angle from _data for a certain bond. """ def __init__(self, out_filename, outfile_contents, inputs): """ Parameters ---------- out_filename : str Name of the ORCA .out file that will be searched. outfile_contents : str String containing the full text of the ORCA .out file. inputs : tuple Tuple of 'angle tuples' (i.e. tuples of three strings of atom labels, e.g. ('1 H', '0 O', '2 H')) for which data will be searched. """ super().__init__(out_filename, outfile_contents, inputs) self._section_name = 'Bond Angles' def _search(self, angle_tuple): return self.__calc_atom_angle(angle_tuple) def __calc_atom_angle(self, angle_tuple): """ Manually calcs the "bond angle" of the 3 atoms in the input angle_tuple. Of course, these can be any three atoms whose coordinates appear in the .out file, they do not have to be classified as having a "bond angle" by ORCA. Parameters ---------- angle_tuple : tuple Tuple containing three atom labels, representing the desired bond. Returns ------- str String containing the angle between the three atoms. Raises ------ TypeError This occurs when one of the atom labels in the input angle tuple does not have corresponding data in final_geom. """ final_geom = \ FinalGeometry(out_filename=self._out_filename, outfile_contents=self._outfile_contents, inputs=( angle_tuple[0], angle_tuple[1], angle_tuple[2] )) atom0, atom1, atom2 = angle_tuple[0], angle_tuple[1], angle_tuple[2] # I've decided DRY here is more trouble than its worth... try: atom0_x = float(final_geom.get_datum(atom0)['x']) atom0_y = float(final_geom.get_datum(atom0)['y']) atom0_z = float(final_geom.get_datum(atom0)['z']) except TypeError: print(f'Manual calculation failed for bond angle {atom0}-{atom1}-' f'{atom2}: geometry data for {atom0} not found.') return None try: atom1_x = float(final_geom.get_datum(atom1)['x']) atom1_y = float(final_geom.get_datum(atom1)['y']) atom1_z = float(final_geom.get_datum(atom1)['z']) except TypeError: print(f'Manual calculation failed for bond angle {atom0}-{atom1}-' f'{atom2}: geometry data for {atom1} not found.') return None try: atom2_x = float(final_geom.get_datum(atom2)['x']) atom2_y = float(final_geom.get_datum(atom2)['y']) atom2_z = float(final_geom.get_datum(atom2)['z']) except TypeError: print(f'Manual calculation failed for bond angle {atom0}-{atom1}-' f'{atom2}: geometry data for {atom2} not found.') return None vector_01 = [atom0_x - atom1_x, atom0_y - atom1_y, atom0_z - atom1_z] vector_12 = [atom1_x - atom2_x, atom1_y - atom2_y, atom1_z - atom2_z] numerator = (vector_01[0] * vector_12[0]) + \ (vector_01[1] * vector_12[1]) + \ (vector_01[2] * vector_12[2]) denominator = math.sqrt(vector_01[0] ** 2 + vector_01[1] ** 2 + vector_01[2] ** 2) * \ math.sqrt(vector_12[0] ** 2 + vector_12[1] ** 2 + vector_12[2] ** 2) return str( round(180 - math.degrees(math.acos(numerator / denominator)), 5) )
[docs] def get_datum(self, angle_tuple): """ Gives the bond angle from _data for a certain bond. Parameters ---------- angle_tuple : tuple Tuple containing three atom labels, representing the desired bond. Returns ------- str String consisting of the angle of the corresponding bond in degrees. Raises ------ KeyError This occurs when the input 'bond' tuple can't be found as a key in the dict containing the bond angle data. """ # In order to find the bond angle no matter which atom is listed first # in the bond tuple, this override is necessary. try: return self._data[angle_tuple] except KeyError: try: flipped_angle_tuple = (angle_tuple[2], angle_tuple[1], angle_tuple[0]) return self._data[flipped_angle_tuple] pass except KeyError: print(f'Error: {angle_tuple} not found in ' f'{self._out_filename} (Bond Angles).') return None