Source code for cgexplore._internal.analysis.geom

# Distributed under the terms of the MIT License.

"""Module for geometry analysis."""

import typing
from collections import abc, defaultdict

import stk
import stko
from rdkit.Chem import AllChem

from cgexplore._internal.forcefields.forcefield import ForceField
from cgexplore._internal.terms.torsions import TargetTorsion
from cgexplore._internal.terms.utilities import find_torsions


[docs] class GeomMeasure: """Class to perform geometry calculations. Parameters: target_torsions: An iterable of `TargetTorsion` defining which torsions to capture if you run `calculate_torsions`. """ def __init__( self, target_torsions: abc.Iterable[TargetTorsion] | None = None, ) -> None: """Initialize GeomMeasure.""" self._stko_analyser = stko.molecule_analysis.GeometryAnalyser() if target_torsions is None: self._target_torsions = None else: self._target_torsions = tuple(target_torsions)
[docs] def calculate_min_distance( self, molecule: stk.Molecule, ) -> dict[str, float]: """Calculate the minimum distance between beads and centroid. Parameters: molecule: The molecule to analyse. Returns: The minimum distance. """ return { "min_distance": ( self._stko_analyser.get_min_centroid_distance(molecule) ), }
def _get_paths( self, molecule: stk.Molecule, path_length: int, ) -> tuple[abc.Sequence[int]]: return AllChem.FindAllPathsOfLengthN( mol=molecule.to_rdkit_mol(), length=path_length, useBonds=False, useHs=True, )
[docs] def calculate_minb2b(self, molecule: stk.Molecule) -> float: """Calculate the minimum distance between beads and beads. Parameters: molecule: The molecule to analyse. Returns: The minimum bead-to-bead distance. """ return self._stko_analyser.get_min_atom_atom_distance(molecule)
[docs] def calculate_bonds( self, molecule: stk.Molecule, ) -> dict[tuple[str, str], list[float]]: """Calculate the bond lengths. Uses `stko.molecule_analysis.GeometryAnalyser` Parameters: molecule: The molecule to analyse. Returns: A dictionary of bond lengths organised by element strings. """ return self._stko_analyser.calculate_bonds(molecule)
[docs] def calculate_angles( self, molecule: stk.Molecule, ) -> dict[tuple[str, str, str], list[float]]: """Calculate the angle values. Uses `stko.molecule_analysis.GeometryAnalyser` Parameters: molecule: The molecule to analyse. Returns: A dictionary of angle values organised by element strings. """ return self._stko_analyser.calculate_angles(molecule)
[docs] def calculate_torsions( self, molecule: stk.Molecule, absolute: bool, as_search_string: bool = False, ) -> dict[str, list[float]]: """Calculate the value of target torsions. Parameters: molecule: The molecule to analyse. absolute: `True` to get the abs(torsion) from 0 degrees to 180 degrees. as_search_string: Changes the key of the returned dictionary to be defined by the search string used in `target_torsions` or as the found atom element strings. Returns: A dictionary of torsion values organised by element strings. """ if self._target_torsions is None: return {} torsions = defaultdict(list) for target_torsion in self._target_torsions: for torsion in find_torsions( molecule, len(target_torsion.search_estring) ): estrings = tuple([i.__class__.__name__ for i in torsion.atoms]) if estrings not in ( target_torsion.search_estring, tuple(reversed(target_torsion.search_estring)), ): continue # Check if you want the search string as key, or only the # measured atoms. if as_search_string: torsion_type_option1 = "_".join(estrings) torsion_type_option2 = "_".join(reversed(estrings)) else: torsion_type_option1 = "_".join( tuple( estrings[i] for i in target_torsion.measured_atom_ids ) ) torsion_type_option2 = "_".join( tuple( estrings[i] for i in reversed(target_torsion.measured_atom_ids) ) ) if torsion_type_option1 in torsions: key_string = torsion_type_option1 new_ids = tuple( torsion.atom_ids[i] for i in target_torsion.measured_atom_ids ) elif torsion_type_option2 in torsions: key_string = torsion_type_option2 new_ids = tuple( torsion.atom_ids[i] for i in reversed(target_torsion.measured_atom_ids) ) else: key_string = torsion_type_option1 new_ids = tuple( torsion.atom_ids[i] for i in target_torsion.measured_atom_ids ) torsion_value = stko.calculate_dihedral( pt1=next(iter(molecule.get_atomic_positions(new_ids[0]))), pt2=next(iter(molecule.get_atomic_positions(new_ids[1]))), pt3=next(iter(molecule.get_atomic_positions(new_ids[2]))), pt4=next(iter(molecule.get_atomic_positions(new_ids[3]))), ) if absolute: torsion_value = abs(torsion_value) torsions[key_string].append(torsion_value) return torsions
[docs] def calculate_radius_gyration(self, molecule: stk.Molecule) -> float: """Calculate the radius of gyration. Uses `stko.molecule_analysis.GeometryAnalyser` Parameters: molecule: The molecule to analyse. """ return self._stko_analyser.get_radius_gyration(molecule)
[docs] def calculate_max_diameter(self, molecule: stk.Molecule) -> float: """Calculate the maximum diameter of the molecule. Uses `stko.molecule_analysis.GeometryAnalyser` Parameters: molecule: The molecule to analyse. """ return self._stko_analyser.get_max_diameter(molecule)
[docs] @classmethod def from_forcefield( cls, forcefield: ForceField, ) -> typing.Self: """Get the values in terms of forcefield terms.""" ff_targets = forcefield.get_targets() return cls(target_torsions=ff_targets["torsions"])