[Feature] Implemented SMARTS filtering for Rules (#246)

Reactant Filter SMARTS as well as Product Filter SMARTS are now reflected when applying rules.

Fixes #245

Co-authored-by: Tim Lorsbach <tim@lorsba.ch>
Reviewed-on: enviPath/enviPy#246
This commit is contained in:
2025-11-28 23:28:41 +13:00
parent fd2e2c2534
commit e8ae494c16
4 changed files with 90 additions and 3 deletions

View File

@ -279,6 +279,24 @@ class FormatConverter(object):
except Exception:
return False
@staticmethod
def smarts_matches(mol: str | Chem.Mol, smarts: str) -> bool:
"""
Returns True if the SMARTS pattern matches the given SMILES / Molecule.
"""
_mol = mol
if isinstance(mol, str):
_mol = Chem.MolFromSmiles(mol)
if _mol is None:
raise ValueError(f"Invalid Molecule: {mol}")
pattern = Chem.MolFromSmarts(smarts)
if pattern is None:
raise ValueError(f"Invalid SMARTS: {smarts}")
return _mol.HasSubstructMatch(pattern)
@staticmethod
def apply(
smiles: str,
@ -288,6 +306,8 @@ class FormatConverter(object):
standardize: bool = True,
kekulize: bool = True,
remove_stereo: bool = True,
reactant_filter_smarts: str | None = None,
product_filter_smarts: str | None = None,
) -> List["ProductSet"]:
logger.debug(f"Applying {smirks} on {smiles}")
@ -306,6 +326,15 @@ class FormatConverter(object):
Chem.SanitizeMol(mol)
mol = Chem.AddHs(mol)
# Check if reactant_filter_smarts matches and we shouldn't apply the rule
if reactant_filter_smarts and FormatConverter.smarts_matches(
mol, reactant_filter_smarts
):
logger.debug(
f"Reactant {FormatConverter.to_smiles(mol)} matches {reactant_filter_smarts}, skipping"
)
return list(pss)
# apply!
sites = rxn.RunReactants((mol,))
logger.debug(f"{len(sites)} products sets generated")
@ -321,6 +350,17 @@ class FormatConverter(object):
p = FormatConverter.standardize(
Chem.MolToSmiles(p), remove_stereo=remove_stereo
)
if product_filter_smarts and FormatConverter.smarts_matches(
p, product_filter_smarts
):
logger.debug(
f"Product {FormatConverter.to_smiles(mol)} matches {product_filter_smarts}, skipping"
)
# clear products we might have already collected
prods.clear()
break
prods.append(p)
# if kekulize:
@ -357,7 +397,7 @@ class FormatConverter(object):
except Exception as e:
logger.error(f"Applying {smirks} on {smiles} failed:\n{e}")
return pss
return list(pss)
@staticmethod
def MACCS(smiles):