Source code for gloryxr.reactions
"""
Core chemical reaction processing engine for GLORYxR.
This module handles both abstract reaction management and concrete reaction generation.
"""
import csv
import importlib.resources
import itertools
from rdkit.Chem.inchi import MolToInchi
from rdkit.Chem.rdchem import Mol
from rdkit.Chem.rdChemReactions import ChemicalReaction, ReactionFromSmarts
from rdkit.Chem.rdmolops import AddHs, GetMolFrags, RemoveHs, SanitizeMol
from rdkit.rdBase import BlockLogs
from gloryxr.som import annotate_educt_and_product_inplace
__all__ = ["Reactor"]
_rules_data = importlib.resources.files("gloryxr").joinpath("rules_data")
[docs]
class Reactor:
"""
Core chemical reaction processing engine for GLORYxR.
This class manages abstract reactions and provides methods to generate
concrete reactions from input molecules.
Args:
strict_soms: Whether to use strict SOM validation
"""
def __init__(self, strict_soms: bool = False) -> None:
self.strict_soms: bool = strict_soms
self.abstract_reactions: list[ChemicalReaction] = []
# Load reaction rules from CSV file
self._load_reaction_rules()
def _load_reaction_rules(self) -> None:
"""Load abstract reaction rules from the CSV file."""
with _rules_data.joinpath("gloryx_reactionrules.csv").open() as f:
for row in csv.DictReader(f):
reaction: ChemicalReaction = ReactionFromSmarts(row["SMIRKS"])
reaction.SetProp("_Name", row["Reaction name"])
reaction.SetProp("_Priority", row["Priority level"])
reaction.SetProp("_Subset", row["Name of rule subset"])
self.abstract_reactions.append(reaction)
[docs]
def react_one(self, mol: Mol) -> list[ChemicalReaction]:
"""
Applies abstract reactions to a molecule to generate concrete reactions.
Args:
mol: RDKit molecule object
Returns:
List of concrete reactions with SOM annotations
"""
concrete_reactions: list[ChemicalReaction] = list(
itertools.chain.from_iterable(
(
_to_concrete_reactions(reaction=abstract_reaction, educt=mol)
for abstract_reaction in self.abstract_reactions
)
)
)
# Annotate each concrete reaction with SOM information
for concrete_reaction in concrete_reactions:
annotate_educt_and_product_inplace(
educt=concrete_reaction.GetReactants()[0],
product=concrete_reaction.GetProducts()[0],
strict_soms=self.strict_soms,
)
return list(
itertools.chain.from_iterable(
_separate_reactions_for_products(concrete_reaction)
for concrete_reaction in concrete_reactions
)
)
def _to_concrete_reactions(
reaction: ChemicalReaction, educt: Mol
) -> list[ChemicalReaction]:
"""
Convert an abstract reaction to concrete reactions for a given educt.
This method applies the abstract reaction to the educt molecule and
generates all possible concrete reactions, filtering out duplicates
and invalid products. Importantly, the generated products may contain
multiple molecules.
Args:
reaction: Abstract chemical reaction
educt: Input molecule
Returns:
List of concrete reactions
"""
# Generate all possible products from the reaction
# TODO: ensure there is only one reactant!
products = itertools.chain.from_iterable(reaction.RunReactants([AddHs(educt)]))
known_products = set()
reactions = []
for product in products:
# Sanitize the product molecule
try:
block = BlockLogs()
SanitizeMol(product)
del block
except Exception:
# Skip invalid products
continue
# Check for duplicate products using InChI
if (inchi := MolToInchi(product)) not in known_products:
known_products.add(inchi)
else:
continue
# Create concrete reaction
product_ = AddHs(product)
educt_ = Mol(educt)
concrete_reaction = ChemicalReaction()
concrete_reaction.AddReactantTemplate(RemoveHs(educt_))
concrete_reaction.AddProductTemplate(RemoveHs(product_))
# Copy reaction name, priority, and subset if available
if reaction.HasProp("_Name"):
concrete_reaction.SetProp("_Name", reaction.GetProp("_Name"))
if reaction.HasProp("_Priority"):
concrete_reaction.SetProp("_Priority", reaction.GetProp("_Priority"))
if reaction.HasProp("_Subset"):
concrete_reaction.SetProp("_Subset", reaction.GetProp("_Subset"))
reactions.append(concrete_reaction)
return reactions
def _separate_reactions_for_products(
combined_reaction: ChemicalReaction,
) -> list[ChemicalReaction]:
product_or_products = combined_reaction.GetProducts()[0]
products = GetMolFrags(product_or_products, asMols=True, sanitizeFrags=False)
if len(products) == 1:
return [combined_reaction]
results = []
for product in products:
split_reaction = ChemicalReaction()
split_reaction.AddReactantTemplate(combined_reaction.GetReactants()[0])
split_reaction.AddProductTemplate(product)
if combined_reaction.HasProp("_Name"):
split_reaction.SetProp("_Name", combined_reaction.GetProp("_Name"))
if combined_reaction.HasProp("_Priority"):
split_reaction.SetProp("_Priority", combined_reaction.GetProp("_Priority"))
if combined_reaction.HasProp("_Subset"):
split_reaction.SetProp("_Subset", combined_reaction.GetProp("_Subset"))
results.append(split_reaction)
return results