Skip to content

Sulfoxide (tetrahedral S) stereochemistry is not conditioned — generated configuration is independent of the input #4

Description

@isayev

Summary

LoQI reproduces carbon and phosphorus stereocenters essentially perfectly, but the configuration it generates at a sulfoxide (tetrahedral sulfur) stereocenter is independent of the input SMILES chirality — it samples a fixed, ~63/37 R-biased prior regardless of whether R or S is requested.

Surfaced while building a docking ligand-prep pipeline. Many drugs are chiral sulfoxides (omeprazole/esomeprazole, modafinil/armodafinil, sulindac, oxfendazole, …), where the wrong configuration is the wrong molecule.

Evidence — enantiomer-conditioning experiment

Each stereoisomer is fed separately (30 conformers, --seed 0); the CIP label is read back from each generated 3D structure. The ETKDG column is an RDKit embedding of the same SMILES — it confirms the target is achievable and that the 3D→CIP assignment is reliable.

input SMILES intended LoQI output (n=30) ETKDG
sulfoxide, R C[S@@](=O)c1ccccc1 R R:18, S:12 10/10 R
sulfoxide, S C[S@](=O)c1ccccc1 S R:20, S:10 10/10 S
phosphine oxide, R CC[P@](=O)(C)c1ccccc1 R R:30 10/10 R
phosphine oxide, S CC[P@@](=O)(C)c1ccccc1 S S:30 10/10 S
carbon control, R C[C@H](O)c1ccccc1 R R:30 10/10 R

Both sulfoxide inputs collapse to the same ~63/37 R-biased output → the S-center configuration is not conditioned on the input. Phosphorus — also a tetrahedral heteroatom stereocenter — and carbon are conditioned perfectly.

At the population level, across ~730 achievable, fully-defined chiral conformers from a 1000-molecule ChEMBL run, carbon centers reproduce at ~100% while the residual stereo errors are dominated by sulfoxide centers.

Reproduction

Standalone; needs only the shim + RDKit. Run from the repo root.

import json, subprocess, sys, tempfile
from collections import Counter
from pathlib import Path
from rdkit import Chem, RDLogger
RDLogger.DisableLog("rdApp.*")

SHIM = "scripts/loqi_inference_shim.py"
CASES = [("sulfoxide R", "C[S@@](=O)c1ccccc1"), ("sulfoxide S", "C[S@](=O)c1ccccc1"),
         ("P-oxide R", "CC[P@](=O)(C)c1ccccc1"), ("P-oxide S", "CC[P@@](=O)(C)c1ccccc1")]
def cip(s):
    m = Chem.MolFromSmiles(s)
    return dict(Chem.FindMolChiralCenters(m, includeUnassigned=True, useLegacyImplementation=False))
payload, meta = [], {}
for label, smi in CASES:
    ik = Chem.MolToInchiKey(Chem.MolFromSmiles(smi))
    payload.append({"inchikey": ik, "smiles": smi}); meta[ik] = (label, cip(smi))
with tempfile.TemporaryDirectory() as t:
    t = Path(t); (t/"in.json").write_text(json.dumps(payload)); o = t/"out"; o.mkdir()
    subprocess.run([sys.executable, SHIM, "--input", str(t/"in.json"), "--output_dir", str(o),
                    "--n_confs", "30", "--seed", "0"], check=True)
    for ik, (label, intended) in meta.items():
        want = ",".join(map(str, intended.values())); obs = Counter()
        for m in Chem.SDMolSupplier(str(o/f"{ik}.conformers.sdf"), removeHs=False):
            if m is None: continue
            mm = Chem.Mol(m); Chem.AssignStereochemistryFrom3D(mm)
            obs[",".join(dict(Chem.FindMolChiralCenters(mm, includeUnassigned=True,
                useLegacyImplementation=False)).get(i, "?") for i in intended)] += 1
        print(f"{label:14s} intended {want:3s} -> {dict(obs)}")

Ruled out

  • Not "chirality ignored globally": carbon and phosphorus centers condition at ~100% (input R→R, S→S).
  • Not a perception artifact: the sulfoxide CIP assigns cleanly as R/S from the 3D (never ?), and ETKDG of the same SMILES returns the intended label 10/10.
  • Not an impossible/degenerate geometry: ETKDG embeds both sulfoxide enantiomers cleanly; the generated S-center is a well-formed pyramid.

Likely cause

The graph featurization appears to carry chiral tags for tetrahedral carbon and phosphorus but not for the 3-coordinate sulfoxide sulfur (RDKit represents it as S(=O) / [S+][O-], a special stereo case). Worth checking whether atom.GetChiralTag() on the sulfoxide S survives graph construction / one-hot encoding.

Environment

LoQI main @ 3f1c3fe, data/loqi.ckpt, 25-step sampler, --seed 0; PyTorch 2.9.1, Lightning 2.6.1, NVIDIA L40S. Happy to share the full failing-set SMILES + per-center analysis from the ChEMBL run.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions