Source code for qsarkit.representation.fingerprints._morgan

"""Morgan / circular (ECFP, FCFP) fingerprints."""

from __future__ import annotations

from typing import TYPE_CHECKING, Any, List, Optional

import numpy as np
import numpy.typing as npt

from qsarkit.representation.fingerprints._base import BaseFingerprintTransformer

if TYPE_CHECKING:  # pragma: no cover
    from rdkit.Chem import Mol


[docs] class MorganFingerprint(BaseFingerprintTransformer): """Morgan (circular) fingerprints, i.e. ECFP and FCFP. Iteratively hashes the circular atom environment of every atom up to ``radius`` bonds and folds the resulting identifiers into ``n_bits``. With ``use_features=False`` the atom invariants are connectivity based (ECFP flavour); with ``use_features=True`` they are the Gobbi-style pharmacophoric feature invariants (FCFP flavour). The ECFP diameter naming convention corresponds to ``2 * radius`` (``radius=2`` -> ECFP4). Parameters ---------- n_bits : int, default 2048 Width of the folded fingerprint. radius : int, default 2 Maximum circular environment radius, in bonds. use_counts : bool, default False If ``True`` return per-bit occurrence counts (``uint32``) instead of a binary vector (``uint8``). use_features : bool, default False Use pharmacophoric (FCFP) rather than connectivity (ECFP) atom invariants. use_chirality : bool, default False Include chiral tags in the atom invariants. use_bond_types : bool, default True Include bond orders when hashing environments. Attributes ---------- n_features_out : int Equal to ``n_bits``. Examples -------- >>> from rdkit import Chem >>> from qsarkit.representation.fingerprints import MorganFingerprint >>> mols = [Chem.MolFromSmiles("CCO"), Chem.MolFromSmiles("c1ccccc1")] >>> X = MorganFingerprint(n_bits=64, radius=2).fit_transform(mols) >>> X.shape (2, 64) References ---------- - Rogers, D. & Hahn, M. (2010). "Extended-Connectivity Fingerprints." J. Chem. Inf. Model., 50(5), 742-754. https://doi.org/10.1021/ci100050t - Morgan, H. L. (1965). "The Generation of a Unique Machine Description for Chemical Structures." J. Chem. Doc., 5(2), 107-113. https://doi.org/10.1021/c160017a018 - RDKit ``rdFingerprintGenerator`` documentation: https://www.rdkit.org/docs/source/rdkit.Chem.rdFingerprintGenerator.html """ def __init__( self, n_bits: int = 2048, radius: int = 2, use_counts: bool = False, use_features: bool = False, use_chirality: bool = False, use_bond_types: bool = True, ): self.n_bits = n_bits self.radius = radius self.use_counts = use_counts self.use_features = use_features self.use_chirality = use_chirality self.use_bond_types = use_bond_types @property def n_features_out(self) -> int: return int(self.n_bits) @property def _feature_prefix(self) -> str: # type: ignore[override] flavour = "FCFP" if self.use_features else "ECFP" return f"{flavour}{2 * int(self.radius)}" def _generator(self) -> Any: from rdkit.Chem import rdFingerprintGenerator invariants = ( rdFingerprintGenerator.GetMorganFeatureAtomInvGen() if self.use_features else None ) return rdFingerprintGenerator.GetMorganGenerator( radius=int(self.radius), fpSize=int(self.n_bits), includeChirality=bool(self.use_chirality), useBondTypes=bool(self.use_bond_types), atomInvariantsGenerator=invariants, ) def _fingerprint(self, mol: "Mol") -> npt.NDArray[np.float64]: generator = self._generator() if self.use_counts: return generator.GetCountFingerprintAsNumPy(mol) # type: ignore[no-any-return] return generator.GetFingerprintAsNumPy(mol) # type: ignore[no-any-return] def _transform(self, mols: List[Optional["Mol"]]) -> npt.NDArray[np.float64]: # Build the generator once per batch rather than once per molecule. generator = self._generator() encode = ( generator.GetCountFingerprintAsNumPy if self.use_counts else generator.GetFingerprintAsNumPy ) out = np.zeros((len(mols), self.n_features_out), dtype=np.float64) for i, mol in enumerate(mols): if mol is None: continue out[i] = encode(mol) return out
[docs] class FeatureMorganFingerprint(MorganFingerprint): """FCFP-flavoured Morgan fingerprint (pharmacophoric atom invariants). Thin convenience subclass of :class:`MorganFingerprint` with ``use_features=True``; feature types are donor, acceptor, aromatic, halogen, basic and acidic, as defined by the Gobbi & Poppinger pharmacophore typing rules used by RDKit. Parameters ---------- n_bits : int, default 2048 Width of the folded fingerprint. radius : int, default 2 Maximum circular environment radius, in bonds. use_counts : bool, default False Return counts rather than bits. use_chirality : bool, default False Include chiral tags in the atom invariants. use_bond_types : bool, default True Include bond orders when hashing environments. References ---------- - Rogers, D. & Hahn, M. (2010). "Extended-Connectivity Fingerprints." J. Chem. Inf. Model., 50(5), 742-754. https://doi.org/10.1021/ci100050t - Gobbi, A. & Poppinger, D. (1998). "Genetic Optimization of Combinatorial Libraries." Biotechnol. Bioeng., 61(1), 47-54. https://doi.org/10.1002/(SICI)1097-0290(199824)61:1<47::AID-BIT9>3.0.CO;2-Z - RDKit ``rdFingerprintGenerator`` documentation: https://www.rdkit.org/docs/source/rdkit.Chem.rdFingerprintGenerator.html """ def __init__( self, n_bits: int = 2048, radius: int = 2, use_counts: bool = False, use_chirality: bool = False, use_bond_types: bool = True, ): super().__init__( n_bits=n_bits, radius=radius, use_counts=use_counts, use_features=True, use_chirality=use_chirality, use_bond_types=use_bond_types, )