Skills › Scientific Agent Skills › rdkit › _common.py
_common.py
62 lines · skills/rdkit/scripts/_common.py at 5f2bc03
1"""Shared, source-index-preserving molecular file reader (no standardization)."""
3import sys
4from pathlib import Path
5from rdkit import Chem
7SOURCE_INDEX = "_SkillRDKitSourceIndex"
10def read_molecule_records(file_path):
11 """Yield (one-based source record, sanitized nonempty Mol).
13 SMILES input has no header; first field is SMILES and optional remainder is
14 the name. CXSMILES extensions are parsed before names. Blank/comment lines
15 are ignored but physical line IDs are retained.
16 SDF IDs are supplier record numbers. Invalid records are logged to stderr.
17 """
18 path = Path(file_path)
19 if not path.is_file():
20 raise FileNotFoundError(f"File not found: {path}")
21 suffix = path.suffix.lower()
22 if suffix in {".smi", ".smiles", ".txt"}:
23 with path.open(encoding="utf-8") as stream:
24 for index, line in enumerate(stream, 1):
25 fields = line.strip().split(maxsplit=1)
26 if not fields or fields[0].startswith("#"):
27 continue
28 params = Chem.SmilesParserParams()
29 params.parseName = True
30 mol = Chem.MolFromSmiles(line.strip(), params)
31 if mol is None or mol.GetNumAtoms() == 0:
32 print(f"[FAIL] Invalid molecule at record {index}", file=sys.stderr)
33 continue
34 mol.SetIntProp(SOURCE_INDEX, index)
35 yield index, mol
36 elif suffix == ".mol":
37 mol = Chem.MolFromMolFile(str(path))
38 if mol is None or mol.GetNumAtoms() == 0:
39 print("[FAIL] Invalid molecule at record 1", file=sys.stderr)
40 return
41 mol.SetIntProp(SOURCE_INDEX, 1)
42 yield 1, mol
43 elif suffix == ".sdf":
44 with Chem.SDMolSupplier(str(path)) as supplier:
45 for index, mol in enumerate(supplier, 1):
46 if mol is None or mol.GetNumAtoms() == 0:
47 print(f"[FAIL] Invalid molecule at record {index}", file=sys.stderr)
48 continue
49 # Overwrite any input property with the actual source record.
50 mol.SetIntProp(SOURCE_INDEX, index)
51 yield index, mol
52 else:
53 raise ValueError(f"Unsupported file format: {suffix}")
56def molecule_smiles(mol):
57 """Preserve enhanced stereo groups when plain isomeric SMILES is insufficient."""
58 if mol.GetStereoGroups():
59 return Chem.MolToCXSmiles(
60 mol, Chem.SmilesWriteParams(), flags=Chem.CXSmilesFields.CX_ENHANCEDSTEREO
61 )
62 return Chem.MolToSmiles(mol, canonical=True, isomericSmiles=True)