Source code for stk._internal.building_block

import logging
import os
import pathlib
import typing
from collections.abc import Collection, Iterable, Iterator
from functools import partial

import numpy as np
import rdkit.Chem.AllChem as rdkit
import vabene

from stk._internal.atom import Atom
from stk._internal.bond import Bond
from stk._internal.functional_group_factories.functional_group_factory import (
    FunctionalGroupFactory,
)
from stk._internal.functional_groups.functional_group import FunctionalGroup
from stk._internal.molecule import Molecule
from stk._internal.utilities.utilities import flatten, remake

logger = logging.getLogger(__name__)


[docs] class BuildingBlock(Molecule): """ Represents a building block of a :class:`.ConstructedMolecule`. A :class:`BuildingBlock` can represent either an entire molecule or a molecular fragments used to construct a :class:`.ConstructedMolecule`. The building block uses :class:`.FunctionalGroup` instances to identify which atoms are modified during construction. See Also: * :class:`.Atom`: Represents atoms of a building block. * :class:`.Bond`: Represents bonds of a building block. """ # Maps file extensions to functions which can be used to # create an rdkit molecule from that file type. _init_funcs = { ".mol": partial(rdkit.MolFromMolFile, sanitize=False, removeHs=False), ".sdf": partial(rdkit.MolFromMolFile, sanitize=False, removeHs=False), } _placer_ids: frozenset[int] _core_ids: frozenset[int] def __init__( self, smiles: str, functional_groups: ( FunctionalGroup | FunctionalGroupFactory | Iterable[FunctionalGroup | FunctionalGroupFactory] ) = (), placer_ids: Iterable[int] | None = None, position_matrix: np.ndarray | None = None, ) -> None: """ Parameters: smiles: A SMILES string of the molecule. functional_groups (FunctionalGroup \ | FunctionalGroupFactory \ | list[FunctionalGroup | FunctionalGroupFactory]): :class:`.FunctionalGroup` instances added to the building block and :class:`.FunctionalGroupFactory` instances used to create :class:`.FunctionalGroup` instances added to the building block. :class:`.FunctionalGroup` instances are used to identify which atoms are modified during :class:`.ConstructedMolecule` construction. placer_ids (list[int]): The ids of *placer* atoms. These are the atoms which should be used for calculating the position of the building block. Depending on the values passed to `placer_ids`, and the functional groups in the building block, different *placer* ids will be used by the building block. #. `placer_ids` is passed to the initializer: the passed *placer* ids will be used by the building block. #. `placer_ids` is ``None`` and the building block has functional groups: The *placer* ids of the functional groups will be used as the *placer* ids of the building block. #. `placer_ids` is ``None`` and `functional_groups` is empty. All atoms of the molecule will be used for *placer* ids. position_matrix: The position matrix the building block should use. If ``None``, :func:`rdkit.ETKDGv2` will be used to calculate it. Raises: :class:`RuntimeError` If embedding the molecule fails. """ molecule = rdkit.AddHs(rdkit.MolFromSmiles(smiles)) if position_matrix is None: params = rdkit.ETKDGv2() random_seed = 4 params.randomSeed = random_seed if rdkit.EmbedMolecule(molecule, params) == -1: raise RuntimeError( f"Embedding with seed value of {random_seed} " "failed." ) rdkit.Kekulize(molecule) else: # Make sure the position matrix always holds floats. position_matrix = np.array( position_matrix, dtype=np.float64, ) conformer = rdkit.Conformer(molecule.GetNumAtoms()) for atom_id, position in enumerate(position_matrix): conformer.SetAtomPosition(atom_id, position) molecule.AddConformer(conformer) self._init_from_rdkit_mol( molecule=molecule, functional_groups=functional_groups, placer_ids=placer_ids, )
[docs] @classmethod def init_from_molecule( cls, molecule: Molecule, functional_groups: ( FunctionalGroup | FunctionalGroupFactory | Iterable[FunctionalGroup | FunctionalGroupFactory] ) = (), placer_ids: Iterable[int] | None = None, ) -> typing.Self: """ Initialize from a :class:`.Molecule`. Parameters: molecule: The molecule to initialize from. functional_groups (FunctionalGroup \ | FunctionalGroupFactory \ | list[FunctionalGroup | FunctionalGroupFactory]): :class:`.FunctionalGroup` instances added to the building block and :class:`.FunctionalGroupFactory` instances used to create :class:`.FunctionalGroup` instances added to the building block. :class:`.FunctionalGroup` instances are used to identify which atoms are modified during :class:`.ConstructedMolecule` construction. placer_ids (list[int]): The ids of *placer* atoms. These are the atoms which should be used for calculating the position of the building block. Depending on the values passed to `placer_ids`, and the functional groups in the building block, different *placer* ids will be used by the building block. #. `placer_ids` is passed to the initializer: the passed *placer* ids will be used by the building block. #. `placer_ids` is ``None`` and the building block has functional groups: The *placer* ids of the functional groups will be used as the *placer* ids of the building block. #. `placer_ids` is ``None`` and `functional_groups` is empty. All atoms of the molecule will be used for *placer* ids. Returns: BuildingBlock: The building block. It will have the same atoms, bonds and atomic positions as `molecule`. """ return cls.init( atoms=tuple(molecule.get_atoms()), bonds=tuple(molecule.get_bonds()), position_matrix=molecule.get_position_matrix(), functional_groups=functional_groups, placer_ids=placer_ids, )
[docs] @classmethod def init_from_vabene_molecule( cls, molecule: vabene.Molecule, functional_groups: ( FunctionalGroup | FunctionalGroupFactory | Iterable[FunctionalGroup | FunctionalGroupFactory] ) = (), placer_ids: Iterable[int] | None = None, position_matrix: np.ndarray | None = None, ) -> typing.Self: """ Initialize from a :mod:`vabene.Molecule`. Notes: The molecule is given 3D coordinates with :func:`rdkit.ETKDGv2()`. Parameters: molecule: The :class:`vabene.Molecule` from which to initialize. functional_groups (FunctionalGroup \ | FunctionalGroupFactory \ | list[FunctionalGroup | FunctionalGroupFactory]): :class:`.FunctionalGroup` instances added to the building block and :class:`.FunctionalGroupFactory` instances used to create :class:`.FunctionalGroup` instances added to the building block. :class:`.FunctionalGroup` instances are used to identify which atoms are modified during :class:`.ConstructedMolecule` construction. placer_ids (list[int]): The ids of *placer* atoms. These are the atoms which should be used for calculating the position of the building block. Depending on the values passed to `placer_ids`, and the functional groups in the building block, different *placer* ids will be used by the building block. #. `placer_ids` is passed to the initializer: the passed *placer* ids will be used by the building block. #. `placer_ids` is ``None`` and the building block has functional groups: The *placer* ids of the functional groups will be used as the *placer* ids of the building block. #. `placer_ids` is ``None`` and `functional_groups` is empty. All atoms of the molecule will be used for *placer* ids. position_matrix: The position matrix the building block should use. If ``None``, :func:`rdkit.ETKDGv2` will be used to calculate it. Returns: BuildingBlock: The building block. Raises: :class:`RuntimeError` If embedding the molecule fails. """ editable = rdkit.EditableMol(rdkit.Mol()) for atom in molecule.get_atoms(): rdkit_atom = rdkit.Atom(atom.get_atomic_number()) rdkit_atom.SetFormalCharge(atom.get_charge()) editable.AddAtom(rdkit_atom) for bond in molecule.get_bonds(): editable.AddBond( beginAtomIdx=bond.get_atom1_id(), endAtomIdx=bond.get_atom2_id(), order=rdkit.BondType(bond.get_order()), ) rdkit_molecule = editable.GetMol() rdkit.SanitizeMol(rdkit_molecule) rdkit_molecule = rdkit.AddHs(rdkit_molecule) if position_matrix is None: params = rdkit.ETKDGv2() random_seed = 4 params.randomSeed = random_seed if rdkit.EmbedMolecule(rdkit_molecule, params) == -1: raise RuntimeError( f"Embedding with seed value of {random_seed} " "failed." ) else: # Make sure the position matrix always holds floats. position_matrix = np.array( position_matrix, dtype=np.float64, ) conformer = rdkit.Conformer(rdkit_molecule.GetNumAtoms()) for atom_id, position in enumerate(position_matrix): conformer.SetAtomPosition(atom_id, position) rdkit_molecule.AddConformer(conformer) rdkit.Kekulize(rdkit_molecule) return cls.init_from_rdkit_mol( molecule=rdkit_molecule, functional_groups=functional_groups, placer_ids=placer_ids, )
[docs] @classmethod def init( cls, atoms: Iterable[Atom], bonds: Iterable[Bond], position_matrix: np.ndarray, functional_groups: ( FunctionalGroup | FunctionalGroupFactory | Iterable[FunctionalGroup | FunctionalGroupFactory] ) = (), placer_ids: Iterable[int] | None = None, ) -> typing.Self: """ Initialize a :class:`.BuildingBlock` from its components. Parameters: atoms (list[Atom]): The atoms of the building block. bonds (list[Bond]): The bonds of the building block. position_matrix: An ``(n, 3)`` position matrix of the building block. functional_groups (FunctionalGroup \ | FunctionalGroupFactory \ | list[FunctionalGroup | FunctionalGroupFactory]): :class:`.FunctionalGroup` instances added to the building block and :class:`.FunctionalGroupFactory` instances used to create :class:`.FunctionalGroup` instances added to the building block. :class:`.FunctionalGroup` instances are used to identify which atoms are modified during :class:`.ConstructedMolecule` construction. placer_ids (list[int]): The ids of *placer* atoms. These are the atoms which should be used for calculating the position of the building block. Depending on the values passed to `placer_ids`, and the functional groups in the building block, different *placer* ids will be used by the building block. #. `placer_ids` is passed to the initializer: the passed *placer* ids will be used by the building block. #. `placer_ids` is ``None`` and the building block has functional groups: The *placer* ids of the functional groups will be used as the *placer* ids of the building block. #. `placer_ids` is ``None`` and `functional_groups` is empty. All atoms of the molecule will be used for *placer* ids. Returns: BuildingBlock: The building block. """ building_block = cls.__new__(cls) Molecule.__init__( self=building_block, atoms=atoms, bonds=bonds, position_matrix=position_matrix, ) functional_groups = building_block._extract_functional_groups( functional_groups=functional_groups, ) building_block._with_functional_groups(functional_groups) building_block._fg_repr = repr( # type: ignore[has-type] tuple(building_block.get_functional_groups()) ) building_block._placer_ids = building_block._normalize_placer_ids( placer_ids=placer_ids, functional_groups=building_block._functional_groups, ) building_block._core_ids = frozenset( building_block._get_core_ids( functional_groups=building_block._functional_groups, ) ) return building_block
[docs] @classmethod def init_from_file( cls, path: pathlib.Path | str, functional_groups: ( FunctionalGroup | FunctionalGroupFactory | Iterable[FunctionalGroup | FunctionalGroupFactory] ) = (), placer_ids: Iterable[int] | None = None, ) -> typing.Self: """ Initialize from a file. Parameters: path: The path to a molecular structure file. Supported file types are: #. ``.mol``, ``.sdf`` - MDL V3000 MOL file functional_groups (FunctionalGroup \ | FunctionalGroupFactory \ | list[FunctionalGroup | FunctionalGroupFactory]): :class:`.FunctionalGroup` instances added to the building block and :class:`.FunctionalGroupFactory` instances used to create :class:`.FunctionalGroup` instances added to the building block. :class:`.FunctionalGroup` instances are used to identify which atoms are modified during :class:`.ConstructedMolecule` construction. placer_ids (list[int]): The ids of *placer* atoms. These are the atoms which should be used for calculating the position of the building block. Depending on the values passed to `placer_ids`, and the functional groups in the building block, different *placer* ids will be used by the building block. #. `placer_ids` is passed to the initializer: the passed *placer* ids will be used by the building block. #. `placer_ids` is ``None`` and the building block has functional groups: The *placer* ids of the functional groups will be used as the *placer* ids of the building block. #. `placer_ids` is ``None`` and `functional_groups` is empty. All atoms of the molecule will be used for *placer* ids. Returns: BuildingBlock: The building block. Raises: :class:`ValueError` If the file type cannot be used for initialization. """ _, extension = os.path.splitext(path) if extension not in cls._init_funcs: raise ValueError(f'Unable to initialize from "{extension}" files.') # This remake needs to be here because molecules loaded # with rdkit often have issues, because rdkit tries to do # bits of structural analysis like stereocenters. Remake # gets rid of all this problematic metadata. molecule = remake(cls._init_funcs[extension](str(path))) return cls.init_from_rdkit_mol( molecule=molecule, functional_groups=functional_groups, placer_ids=placer_ids, )
[docs] @classmethod def init_from_rdkit_mol( cls, molecule: rdkit.Mol, functional_groups: ( FunctionalGroup | FunctionalGroupFactory | Iterable[FunctionalGroup | FunctionalGroupFactory] ) = (), placer_ids: Iterable[int] | None = None, ) -> typing.Self: """ Initialize from an :mod:`rdkit` molecule. Warnings: For :mod:`rdkit` molecules with non-integer bond orders, such as 1.5, the molecule should be kekulized prior to calling this method. Otherwise, all bond orders will be set to an integer value in the building block. Parameters: molecule: The molecule. functional_groups (FunctionalGroup \ | FunctionalGroupFactory \ | list[FunctionalGroup | FunctionalGroupFactory]): :class:`.FunctionalGroup` instances added to the building block and :class:`.FunctionalGroupFactory` instances used to create :class:`.FunctionalGroup` instances added to the building block. :class:`.FunctionalGroup` instances are used to identify which atoms are modified during :class:`.ConstructedMolecule` construction. placer_ids (list[int]): The ids of *placer* atoms. These are the atoms which should be used for calculating the position of the building block. Depending on the values passed to `placer_ids`, and the functional groups in the building block, different *placer* ids will be used by the building block. #. `placer_ids` is passed to the initializer: the passed *placer* ids will be used by the building block. #. `placer_ids` is ``None`` and the building block has functional groups: The *placer* ids of the functional groups will be used as the *placer* ids of the building block. #. `placer_ids` is ``None`` and `functional_groups` is empty. All atoms of the molecule will be used for *placer* ids. Returns: BuildingBlock: The building block. """ building_block = cls.__new__(cls) building_block._init_from_rdkit_mol( molecule=molecule, functional_groups=functional_groups, placer_ids=placer_ids, ) return building_block
def _init_from_rdkit_mol( self, molecule: rdkit.Mol, functional_groups: ( FunctionalGroup | FunctionalGroupFactory | Iterable[FunctionalGroup | FunctionalGroupFactory] ), placer_ids: Iterable[int] | None, ) -> None: """ Initialize from an :mod:`rdkit` molecule. Parameters: molecule: The molecule. functional_groups (FunctionalGroup \ | FunctionalGroupFactory \ | list[FunctionalGroup | FunctionalGroupFactory]): :class:`.FunctionalGroup` instances added to the building block and :class:`.FunctionalGroupFactory` instances used to create :class:`.FunctionalGroup` instances added to the building block. :class:`.FunctionalGroup` instances are used to identify which atoms are modified during :class:`.ConstructedMolecule` construction. placer_ids (list[int]): The ids of *placer* atoms. These are the atoms which should be used for calculating the position of the building block. Depending on the values passed to `placer_ids`, and the functional groups in the building block, different *placer* ids will be used by the building block. #. `placer_ids` is passed to the initializer: the passed *placer* ids will be used by the building block. #. `placer_ids` is ``None`` and the building block has functional groups: The *placer* ids of the functional groups will be used as the *placer* ids of the building block. #. `placer_ids` is ``None`` and `functional_groups` is empty. All atoms of the molecule will be used for *placer* ids. """ atoms = tuple( Atom(a.GetIdx(), a.GetAtomicNum(), a.GetFormalCharge()) for a in molecule.GetAtoms() ) bonds = tuple( Bond( atom1=atoms[b.GetBeginAtomIdx()], atom2=atoms[b.GetEndAtomIdx()], order=( 9 if b.GetBondType() == rdkit.BondType.DATIVE else b.GetBondTypeAsDouble() ), ) for b in molecule.GetBonds() ) position_matrix = molecule.GetConformer().GetPositions() super().__init__(atoms, bonds, position_matrix) self._with_functional_groups( self._extract_functional_groups( functional_groups=functional_groups, ) ) self._fg_repr = repr(self._functional_groups) self._placer_ids = self._normalize_placer_ids( placer_ids=placer_ids, functional_groups=self._functional_groups, ) self._core_ids = frozenset( self._get_core_ids( functional_groups=self._functional_groups, ) ) def _normalize_placer_ids( self, placer_ids: Iterable[int] | None, functional_groups: Collection[FunctionalGroup], ) -> frozenset[int]: """ Get the final *placer* ids. Parameters: placer_ids: The ids of *placer* atoms or ``None``. functional_groups: The :class:`.FunctionalGroup` instances of the building block. Returns: Depending on the input values, this function will return different things. #. `placer_ids` is a :class:`tuple` of :class`int`: the `placer_ids` will be returned. #. `placer_ids` is ``None`` and `functional_groups` is not empty: The *placer* ids of the functional groups will be returned. #. `placer_ids` is ``None`` and `functional_groups` is empty. The ids of all atoms in the building block will be returned. """ if placer_ids is not None: return frozenset(placer_ids) if functional_groups: return frozenset( flatten( functional_group.get_placer_ids() for functional_group in functional_groups ) ) return frozenset(atom.get_id() for atom in self._atoms) def _get_core_ids( self, functional_groups: Iterable[FunctionalGroup], ) -> Iterator[int]: """ Get the final *core* ids. This method may return duplicate ids. Parameters: functional_groups: The :class:`.FunctionalGroup` instances of the building block. Yields: The id of an atom defining the core of the molecule. """ functional_group_atom_ids = { atom_id for functional_group in functional_groups for atom_id in functional_group.get_atom_ids() } for atom in self._atoms: atom_id = atom.get_id() if atom_id not in functional_group_atom_ids: yield atom_id for functional_group in functional_groups: for atom_id in functional_group.get_core_atom_ids(): yield atom_id def _extract_functional_groups( self, functional_groups: ( FunctionalGroup | FunctionalGroupFactory | Iterable[FunctionalGroup | FunctionalGroupFactory] ), ) -> Iterator[FunctionalGroup]: """ Yield functional groups. The input can be a mixture of :class:`.FunctionalGroup` and :class:`.FunctionalGroupFactory`. The output yields :class:`.FunctionalGroup` instances only. Either those held directly in `functional_groups` or created by the factories in `functional_groups`. Parameters: functional_groups: Can be an :class:`iterable` of both :class:`.FunctionalGroup` and :class:`.FunctionalGroupFactory`. Yields: A functional group from `functional_groups`, or created by a factory in `functional_groups`. """ if isinstance( functional_groups, FunctionalGroup | FunctionalGroupFactory, ): functional_groups = (functional_groups,) for functional_group in functional_groups: if isinstance(functional_group, FunctionalGroup): yield functional_group else: # Else it's a factory. yield from functional_group.get_functional_groups(self) def _with_functional_groups( self, functional_groups: Iterable[FunctionalGroup], ) -> typing.Self: """ Modify the molecule. """ self._functional_groups = tuple(functional_groups) self._fg_repr = repr(self._functional_groups) return self
[docs] def with_functional_groups( self, functional_groups: Iterable[FunctionalGroup], ) -> typing.Self: """ Return a clone with specific functional groups. Parameters: functional_groups (list[FunctionalGroup]): Functional groups the clone should have. Returns: BuildingBlock: The clone. """ return self.clone()._with_functional_groups(functional_groups)
def _with_canonical_atom_ordering(self) -> typing.Self: ordering = rdkit.CanonicalRankAtoms(self.to_rdkit_mol()) super()._with_canonical_atom_ordering() id_map = {old_id: new_id for old_id, new_id in enumerate(ordering)} self._functional_groups = tuple( functional_group.with_ids(id_map) for functional_group in self._functional_groups ) self._fg_repr = repr(self._functional_groups) self._placer_ids = frozenset( id_map[placer_id] for placer_id in self._placer_ids ) self._core_ids = frozenset( id_map[core_id] for core_id in self._core_ids ) return self
[docs] def get_num_functional_groups(self) -> int: """ Return the number of functional groups. Returns: The number of functional groups in the building block. """ return len(self._functional_groups)
[docs] def get_functional_groups( self, fg_ids: int | Iterable[int] | None = None, ) -> Iterator[FunctionalGroup]: """ Yield the functional groups, ordered by id. Parameters: fg_ids (int | list[int] | None): The ids of functional groups yielded. If ``None``, then all functional groups are yielded. Yields: A functional group of the building block. """ if fg_ids is None: fg_ids = range(len(self._functional_groups)) elif isinstance(fg_ids, int): fg_ids = (fg_ids,) for fg_id in fg_ids: yield self._functional_groups[fg_id]
[docs] def clone(self) -> typing.Self: """ Return a clone. Returns: BuildingBlock: The clone. """ clone = super().clone() clone._fg_repr = self._fg_repr clone._functional_groups = self._functional_groups clone._placer_ids = self._placer_ids clone._core_ids = self._core_ids return clone
[docs] def get_num_placers(self) -> int: """ Return the number of *placer* atoms in the building block. Returns: The number of *placer* atoms in the building block. """ return len(self._placer_ids)
[docs] def get_placer_ids(self) -> Iterator[int]: """ Yield the ids of *placer* atoms. *Placer* atoms are those, which should be used to calculate the position of the building block. See Also: * :meth:`.FunctionalGroup.get_placer_ids`: For getting the placer ids of functional groups. Yields: The id of a *placer* atom. """ yield from self._placer_ids
[docs] def get_core_atom_ids(self) -> Iterator[int]: """ Yield ids of atoms which form the core of the building block. This includes all atoms in the building block not part of a functional group, as well as any atoms in a functional group, specifically labelled as core atoms. See Also: * :meth:`.FunctionalGroup.get_core_atom_ids`: For getting the core atom ids of functional groups. Yields: The id of a *core* atom. """ yield from self._core_ids
[docs] def with_canonical_atom_ordering(self) -> typing.Self: """ Return a clone, with canonically ordered atoms. Returns: BuildingBlock: The clone. """ return super().with_canonical_atom_ordering()
[docs] def with_centroid( self, position: np.ndarray, atom_ids: int | Iterable[int] | None = None, ) -> typing.Self: """ Return a clone with its centroid at `position`. Parameters: position: This array holds the position on which the centroid of the clone is going to be placed. atom_ids (int | list[int] | None): The ids of atoms which should have their centroid set to `position`. If ``None``, all atoms are used. Returns: BuildingBlock: A clone with its centroid at `position`. """ return super().with_centroid(position, atom_ids)
[docs] def with_displacement(self, displacement: np.ndarray) -> typing.Self: """ Return a displaced clone. Parameters: displacement: The displacement vector to be applied. Returns: BuildingBlock: A displaced clone. """ return super().with_displacement(displacement)
[docs] def with_position_matrix(self, position_matrix: np.ndarray) -> typing.Self: """ Return a clone with atomic positions set by `position_matrix`. Parameters: position_matrix: The position matrix of the clone. The shape of the matrix is ``(n, 3)``. Returns: BuildingBlock: The clone. """ return super().with_position_matrix(position_matrix)
[docs] def with_rotation_about_axis( self, angle: float, axis: np.ndarray, origin: np.ndarray, ) -> typing.Self: """ Return a rotated clone. The clone is rotated by `angle` about `axis` on the `origin`. Parameters: angle: The size of the rotation in radians. axis: The axis about which the rotation happens. Must have unit magnitude. origin: The origin about which the rotation happens. Returns: BuildingBlock: A rotated clone. """ return super().with_rotation_about_axis(angle, axis, origin)
[docs] def with_rotation_between_vectors( self, start: np.ndarray, target: np.ndarray, origin: np.ndarray, ) -> typing.Self: """ Return a rotated clone. The rotation is equal to a rotation from `start` to `target`. Given two direction vectors, `start` and `target`, this method applies the rotation required transform `start` to `target` onto the clone. The rotation occurs about the `origin`. For example, if the `start` and `target` vectors are 45 degrees apart, a 45 degree rotation will be applied to the clone. The rotation will be along the appropriate direction. The great thing about this method is that you as long as you can associate a geometric feature of the molecule with a vector, then the clone can be rotated so that this vector is aligned with `target`. The defined vector can be virtually anything. This means that any geometric feature of the molecule can be easily aligned with any arbitrary direction. Parameters: start: A vector which is to be rotated so that it transforms into the `target` vector. target: The vector onto which `start` is rotated. origin: The point about which the rotation occurs. Returns: BuildingBlock: A rotated clone. """ return super().with_rotation_between_vectors(start, target, origin)
[docs] def with_rotation_to_minimize_angle( self, start: np.ndarray, target: np.ndarray, axis: np.ndarray, origin: np.ndarray, ) -> typing.Self: """ Return a rotated clone. The clone is rotated by the rotation required to minimize the angle between `start` and `target`. Note that this function will not necessarily overlay the `start` and `target` vectors. This is because the possible rotation is restricted to the `axis`. Parameters: start: The vector which is rotated. target: The vector which is stationary. axis: The vector about which the rotation happens. Must have unit magnitude. origin: The origin about which the rotation happens. Returns: BuildingBlock: A rotated clone. Raises: :class:`ValueError` If `target` has a magnitude of 0. In this case it is not possible to calculate an angle between `start` and `target`. """ return super().with_rotation_to_minimize_angle( start=start, target=target, axis=axis, origin=origin, )
[docs] def with_structure_from_file( self, path: pathlib.Path | str, extension: str | None = None, ) -> typing.Self: """ Return a clone, with its structure taken from a file. Multiple file types are supported, namely: #. ``.mol``, ``.sdf`` - MDL V2000 and V3000 files #. ``.xyz`` - XYZ files #. ``.mae`` - Schrodinger Maestro files #. ``.coord`` - Turbomole files #. ``.pdb`` - PDB files Parameters: path: The path to a molecular structure file holding updated coordinates for the :class:`.Molecule`. extension: If you want to treat the file as though it has a particular extension, put it here. Include the dot. Returns: BuildingBlock: A clone with atomic positions found in `path`. """ return super().with_structure_from_file(str(path), extension)
[docs] def write( self, path: pathlib.Path | str, atom_ids: int | Iterable[int] | None = None, ) -> typing.Self: """ Write the structure to a file. This function will write the format based on the extension of `path`. #. ``.mol``, ``.sdf`` - MDL V3000 MOL file #. ``.xyz`` - XYZ file #. ``.pdb`` - PDB file Parameters: path: The `path` to which the molecule should be written. atom_ids (int | list[int] | None): The ids of atoms to write. If ``None``, all atoms are used. If you use this parameter, the atom ids in the file may not correspond to the atom ids in the molecule. Returns: BuildingBlock: The molecule. """ return super().write(path, atom_ids)
def __str__(self) -> str: smiles = rdkit.MolToSmiles( mol=rdkit.RemoveHs(self.to_rdkit_mol()), ) return f"{self.__class__.__name__}({smiles!r}, {self._fg_repr})" def __repr__(self) -> str: return str(self)