plinder.core.utils.sanitize#

Vendored copy of peppr’s sanitize with local over-valence fixes.

Warning

TEMPORARY. This is a local, hand-maintained copy of the modified sanitize from unreleased peppr version. It carries boron-cage / main-group over-valence tolerance and related valence/kekulization fixes that are not yet in a released peppr and are not pip-installable from the internal repo.

TODO(peppr): once these changes land in a published peppr release, DELETE this module and revert every import of the form from plinder.core.utils.sanitize import sanitize as peppr_sanitize back to from peppr import sanitize as peppr_sanitize (grep the tree for peppr_sanitize to find all call sites). Note that mol_from_smiles() is a plinder wrapper, not part of upstream peppr — RELOCATE it (e.g. to a core utils module) rather than deleting it.

Deliberate local deltas from the upstream peppr body: this warning docstring; the bool(...) wraps on the predicate returns (plinder’s minimal mypy env has no rdkit stubs, so Chem is Any and strict’s warn_return_any would flag them); and a trimmed _ORGANOMETALLIC_OMITTED_ELEMENTS comment (correctness, pending upstream).

plinder.core.utils.sanitize.mol_from_smiles(smiles: str | None) → Mol | None[source]#

Parse a SMILES into a sanitized RDKit mol via the tolerant peppr sanitizer.

This is the canonical way to re-parse a stored ligand SMILES anywhere in plinder. Plain Chem.MolFromSmiles applies strict valence sanitization and returns None for the over-valent main-group centres (boron cages, hypervalent metals) that the mol-building path accepts, so descriptors, classification, fingerprints and identity keys must reparse the same way or they silently drop those ligands. Here we parse without sanitizing and then run sanitize(), which accepts exactly what the build path accepts.

For a valid organic molecule the result is chemically identical to Chem.MolFromSmiles (sanitize finishes with a full SANITIZE_ALL pass), so fingerprints/canonical SMILES do not drift. It is a faithful tolerant drop-in: parsing with sanitize=False skips the parser’s assignStereochemistry finalisation that strict MolFromSmiles runs, so we re-run it here — otherwise double-bond E/Z (parsed as bond directions but never translated into bond stereo) would be left unperceived while atom @/@@ parity survived. Returns None for an empty/None input or a SMILES that cannot be parsed or repaired.

plinder.core.utils.sanitize.sanitize(mol: Mol, max_fix_iterations: int = 100) → None[source]#

Sanitize a molecule, repairing the problems that make RDKit’s SanitizeMol() fail on otherwise-valid heavy-atom input.

Parameters:
molChem.rdchem.Mol

The molecule to sanitize, modified in place.

max_fix_iterationsint, optional

The maximum number of fix passes per stage.

Notes

Runs in stages, in the order RDKit resolves them - valence, then kekulization, then the rest - fixing and re-checking each before moving on:

  • AtomValenceException: relieve over-valence via a zero-order bond (into a valence-unlimited metal or an over-valent partner), an “onium” charge (N/O), a terminal-ligand charge (hypervalent P/S), or a borate charge (B); metal centres and carborane cages are tolerated. See _fix_valence().

  • KekulizeException: kekulize a ring heteroatom (N/O/S) neutral-first - reduce its bond to a coordinated metal, or add a ring-N [nH] - and only as a last resort invent an onium +1 (pyridinium/pyrylium/thiopyrylium). See _fix_kekule().