Source code for tcmu.cli_scripts.geo

"""Module containing functions for calculating geometrical parameters for molecules"""

from typing import Dict, List, Tuple

import click
from strenum import StrEnum

from tcmu import geometry, molecule
from tcmu.results.read import read


[docs] class GeometricParameter(StrEnum): """Enum class for geometric parameters""" COORDINATE = "Coordinate" DISTANCE = "Distance" ANGLE = "Angle" DIHEDRAL = "Dihedral" PYRAMIDAL = "Pyramidal" SUMOFANGLES = "SumOfAngles"
geometric_character_to_info_mapping: Dict[GeometricParameter, Tuple[str, str, int]] = { GeometricParameter.COORDINATE: ("Coordinate", "Å", 6), GeometricParameter.DISTANCE: ("Distance", " Å", 3), GeometricParameter.ANGLE: ("Angle", "°", 2), GeometricParameter.DIHEDRAL: ("Dihedral", "°", 2), GeometricParameter.PYRAMIDAL: ("Pyramidal", "°", 2), GeometricParameter.SUMOFANGLES: ("SumOfAngles", "°", 2), } @click.command("geo") @click.argument("path", type=click.Path(exists=True)) @click.argument("atom_indices", type=str, nargs=-1) @click.option("-p", "--pyramidal", is_flag=True, default=False, help="Instead of calculating a dihedral angle, calculate pyramidalisation angle.") @click.option("-s", "--soa", is_flag=True, default=False, help="Instead of calculating a dihedral angle, calculate sum-of-angles.") def calculate_geometry_parameter(path: str, atom_indices: List[str], pyramidal: bool, soa: bool) -> None: """ Calculate geometrical parameters for atoms at the provided ``ATOM_INDICES``. ``PATH`` should be an ``.xyz``-file or a calculation directory. \b For 0 indices we return all coordinates of the molecule in ``PATH``. For 1 index this program returns the cartesian coordinate for this atom. For 2 indices return bond length between atoms. For 3 indices return bond angle, with the second index being the central atom. For 4 indices return dihedral angle by calculating the angle between normal vectors described by atoms at indices ``1-2-3`` and indices ``2-3-4``. \b If the ``-p``/``--pyramidal`` flag is turned on it calculates ``360° - ang1 - ang2 - ang3``, where ``ang1``, ``ang2`` and ``ang3`` are the angles described by indices ``2-1-3``, ``3-1-4`` and ``4-1-2`` respectively. If the ``-s``/``--soa`` flag is turned on it calculates ``ang1 + ang2 + ang3``. .. note:: Atom counting starts at 1. """ try: mol = molecule.load(path) except IsADirectoryError: mol = read(path).molecule.output assert 0 <= len(atom_indices) <= 4, f"Number of atom indices must be 1, 2, 3 or 4, not {len(atom_indices)}" if len(atom_indices) == 0: mol.delete_all_bonds() print(mol) return atom_indices = [int(i) - 1 for i in atom_indices] atoms = "-".join([f"{mol[i + 1].symbol}{i + 1}" for i in atom_indices]) param_value = geometry.parameter(mol, *atom_indices, pyramidal=pyramidal, sum_of_angles=soa) if len(atom_indices) == 1: param_type, unit, precision = geometric_character_to_info_mapping[GeometricParameter.COORDINATE] param_value = " ".join([f"{x: .{precision}f}" for x in mol[atom_indices[0] + 1].coords]) print(f"{param_type}({atoms}) = {param_value} {unit}") return elif len(atom_indices) == 2: param_type = GeometricParameter.DISTANCE elif len(atom_indices) == 3: param_type = GeometricParameter.ANGLE elif len(atom_indices) == 4: if pyramidal: param_type = GeometricParameter.PYRAMIDAL elif soa: param_type = GeometricParameter.SUMOFANGLES else: param_type = GeometricParameter.DIHEDRAL else: raise ValueError("Invalid number of atom indices") param_type, unit, precision = geometric_character_to_info_mapping[param_type] print(f"{param_type}({atoms}) = {param_value: .{precision}f}{unit}") if __name__ == "__main__": calculate_geometry_parameter()