Skip to content

RDKit → chematic migration cheatsheet

Side-by-side API reference for users coming from RDKit. chematic is pure Rust by default — install with pip install chematic, no conda required. The optional native-inchi Cargo feature is the documented vendored C FFI exception.

See also: rdkit-migration.md for a feature-by-feature Supported/Partial/Not-supported breakdown.

Installation

# RDKit
conda install -c conda-forge rdkit   # or: pip install rdkit

# chematic
pip install chematic                  # no extra dependencies

Basic I/O

RDKit chematic Notes
Chem.MolFromSmiles(smi) chematic.from_smiles(smi) SMILES → Mol
Chem.MolToSmiles(mol) mol.smiles Mol → canonical SMILES
Chem.MolFromMolBlock(block) chematic.from_mol_block(block) MOL block → Mol
Chem.inchi.MolFromInchi(inchi) chematic.from_inchi(inchi) InChI → Mol
Chem.inchi.MolToInchi(mol) mol.inchi Mol → InChI
Chem.inchi.InchiToInchiKey(inchi) mol.inchikey InChIKey
Chem.MolToSmiles(mol) str(mol) string conversion

Descriptors

RDKit chematic
Descriptors.MolWt(mol) mol.mw
Descriptors.ExactMolWt(mol) mol.exact_mass
Descriptors.MolLogP(mol) mol.logp
Descriptors.TPSA(mol) mol.tpsa
Descriptors.NumHDonors(mol) mol.hbd
Descriptors.NumHAcceptors(mol) mol.hba
Descriptors.NumRotatableBonds(mol) mol.rotatable_bonds
Descriptors.FractionCSP3(mol) mol.fsp3
Descriptors.HeavyAtomCount(mol) mol.heavy_atoms
Descriptors.RingCount(mol) mol.ring_count
Descriptors.NumAromaticRings(mol) mol.aromatic_ring_count
Descriptors.MolMR(mol) mol.molar_refractivity
Descriptors.qed(mol) mol.qed
rdMolDescriptors.CalcTPSA(mol) mol.tpsa
rdMolDescriptors.CalcNumStereocenters(mol) mol.num_stereocenters
rdMolDescriptors.CalcMolFormula(mol) mol.formula
sascorer.calculateScore(mol) mol.sa_score
Descriptors.ESOL(mol) (rdkit-contrib) mol.esol

Drug-likeness filters

RDKit chematic
FilterCatalog.FilterCatalogParams(PAINS) mol.pains_passes
(manual implementation) mol.lipinski_passes
(manual implementation) mol.veber_passes
(manual implementation) mol.ghose_passes
(manual implementation) mol.egan_passes
(manual implementation) mol.reos_passes
(manual implementation) mol.brenk_passes

Fingerprints

RDKit chematic
AllChem.GetMorganFingerprintAsBitVect(mol, 2, 2048) mol.ecfp4()
AllChem.GetMorganFingerprintAsBitVect(mol, 3, 2048) mol.ecfp6()
AllChem.GetMorganFingerprintAsBitVect(mol, 2, 2048, useFeatures=True) mol.fcfp4()
AllChem.GetMACCSKeysFingerprint(mol) mol.maccs()
rdMolDescriptors.GetAtomPairFingerprintAsBitVect(mol) mol.atom_pair_fp()
rdMolDescriptors.GetTopologicalTorsionFingerprintAsBitVect(mol) mol.torsion_fp()
rdMolDescriptors.GetHashedTopologicalTorsionFingerprintAsBitVect(mol) mol.layered_fp()

Return values are bytes (chematic) or DataStructs.ExplicitBitVect (RDKit). Pass chematic bytes directly to chematic.tanimoto().

Similarity

# RDKit
from rdkit.DataStructs import TanimotoSimilarity
sim = TanimotoSimilarity(fp1, fp2)

# chematic
sim = chematic.tanimoto(mol1.ecfp4(), mol2.ecfp4())

Bulk processing

# RDKit (single-threaded)
mols = [Chem.MolFromSmiles(s) for s in smiles_list]
fps  = [AllChem.GetMorganFingerprintAsBitVect(m, 2, 2048) for m in mols]

# chematic (automatic multi-core parallelism via Rayon)
fps = chematic.bulk.ecfp4(smiles_list)  # numpy array (N, 2048)

Similarity matrix

# RDKit
from rdkit.DataStructs import BulkTanimotoSimilarity
matrix = [[TanimotoSimilarity(fp, fp2) for fp2 in fps] for fp in fps]

# chematic
matrix = chematic.bulk.tanimoto(smiles_a, smiles_b)  # numpy (M, N) float32
# RDKit
query = Chem.MolFromSmarts("[OH]")
matches = mol.GetSubstructMatches(query)

# chematic — bool
chematic.smarts_match("[OH]", mol)

# chematic — atom indices
chematic.smarts_find("[OH]", mol)   # [[3], [7], ...]

Reaction SMARTS

# RDKit
from rdkit.Chem import rdChemReactions
rxn = rdChemReactions.ReactionFromSmarts("[OH]>>[O-]")
matches = rxn.RunReactants((mol,))

# chematic
chematic.reaction_smarts_match("[OH]>>[O-]", "CCO>>CC[O-]")  # bool

Maximum Common Substructure (MCS)

# RDKit
from rdkit.Chem import rdFMCS
result = rdFMCS.FindMCS([mol1, mol2])
mcs_mol = Chem.MolFromSmarts(result.smartsString)

# chematic
mcs = chematic.find_mcs([mol1, mol2])
print(mcs.smiles)

Labelled R-group decomposition

# RDKit
from rdkit.Chem import rdRGroupDecomposition
rows, unmatched = rdRGroupDecomposition.RGroupDecompose(
    [core], mols, asSmiles=True
)

# chematic: bounded mapped-label subset, with row preservation
rows = chematic.rgroup_decompose_labeled(
    "c1cc([*:1])ccc1[*:2]", mols
)
columns = chematic.rgroup_decompose_columns(
    "c1cc([*:1])ccc1[*:2]", mols
)

CheMatic does not yet implement RDKit's multi-core MCS alignment, tautomer or core enumeration, non-terminal R-groups, or GA scoring.

SMIRKS reactions

# RDKit
from rdkit.Chem import AllChem
rxn = AllChem.ReactionFromSmarts("[OH:1]>>[O-:1]")
products = rxn.RunReactants((mol,))

# chematic
products = chematic.run_smirks("[OH:1]>>[O-:1]", [mol])

Standardisation

# RDKit
from rdkit.Chem.MolStandardize import rdMolStandardize
clean = rdMolStandardize.Cleanup(mol)
frags = rdMolStandardize.LargestFragmentChooser().choose(clean)
uncharge = rdMolStandardize.Uncharger().uncharge(frags)

# chematic (one-liner)
clean = mol.standardize()

# individual steps
mol.largest_fragment()
mol.neutralize()
mol.remove_stereo()
mol.remove_isotopes()

Tautomers

# RDKit
from rdkit.Chem.MolStandardize import rdMolStandardize
enumerator = rdMolStandardize.TautomerEnumerator()
canonical = enumerator.Canonicalize(mol)
all_tautomers = enumerator.Enumerate(mol)

# chematic
canonical = mol.canonical_tautomer()
all_tautomers = mol.enumerate_tautomers()

Murcko scaffold

# RDKit
from rdkit.Chem.Scaffolds import MurckoScaffold
scaffold = MurckoScaffold.GetScaffoldForMol(mol)
generic = MurckoScaffold.MakeScaffoldGeneric(scaffold)

# chematic
scaffold = mol.scaffold()
generic  = mol.generic_scaffold()

SDF file reading

# RDKit
from rdkit.Chem import SDMolSupplier
for mol in SDMolSupplier("library.sdf"):
    if mol: print(Descriptors.MolWt(mol))

# chematic
for record in chematic.iter_sdf("library.sdf"):
    print(record.mol.mw)

2D depiction

# RDKit (Jupyter, returns PIL image)
from rdkit.Chem import Draw
Draw.MolToImage(mol)

# chematic (Jupyter, SVG)
from IPython.display import SVG
SVG(mol.svg())

# with atom highlighting
SVG(mol.svg_highlighted([0, 1, 2], color="#FF6B6B"))

# grid
SVG(chematic.depict_grid([mol1, mol2, mol3], cols=3))

Conformer coordinate transforms

# RDKit mutates a Conformer in place
from rdkit.Chem import rdMolTransforms
rdMolTransforms.SetBondLength(conf, 0, 1, 1.52)
rdMolTransforms.SetAngleDeg(conf, 0, 1, 2, 109.5)
rdMolTransforms.SetDihedralDeg(conf, 0, 1, 2, 3, 180.0)
transform = rdMolTransforms.ComputeCanonicalTransform(conf)
rdMolTransforms.CanonicalizeConformer(conf)

# chematic returns a new coordinate array and leaves the input unchanged
coords = mol.set_bond_length(coords, 0, 1, 1.52)
coords = mol.set_bond_angle(coords, 0, 1, 2, 109.5)
coords = mol.set_dihedral(coords, 0, 1, 2, 3, 180.0)
transform = mol.compute_canonical_transform(coords)
coords = mol.canonicalize_conformer(coords)

The selected atom and its side of the molecular graph move as one rigid fragment. As in RDKit, setting a ring-bond length or ring-bond dihedral is rejected; setting an angle is rejected when both defining bonds are in rings. Python raises ValueError. WASM/Node provides matching coordinate-explicit *_from_coords_json functions so imported or previously edited conformers are not regenerated.

Canonicalization centers the selected atoms and aligns the largest covariance axis with x, the next with y, and the smallest with z. It preserves distances and handedness. Eigenvector signs and bases inside a repeated-eigenvalue subspace are not unique, so compare geometry rather than requiring identical coordinate signs across platforms.

SASA (3D solvent-accessible surface area)

# RDKit
from rdkit.Chem import rdFreeSASA
rdFreeSASA.CalcSASA(mol_with_3d_coords)

# chematic (generates 3D coords internally)
mol.sasa()           # total SASA in A^2
mol.sasa_per_atom()  # per-atom list

pKa prediction

Rule-based screening (23 SMARTS rules), not a trained predictive model — useful for early triage, not a substitute for experimental or clinical measurement.

# RDKit — not in the standard library (external tools required)

# chematic — built-in, rule-based screening
pka = mol.pka()
print(pka["most_acidic"])   # 3.49
print(pka["most_basic"])    # None

ADMET profile

Rule-based/empirical screening (BOILED-Egg-style heuristics), not validated against clinical endpoints — directional guidance for early triage, not a substitute for named tools like pkasolver or SwissADME.

# RDKit — not in the standard library (pkasolver, SwissADME, etc. required)

# chematic — built-in, rule-based screening
profile = mol.admet()
# {"bbb": False, "bbb_score": -1.2, "caco2": -5.1, "herg_risk": 0.3, "cyp3a4_risk": 0.4}

All descriptors to a DataFrame

# RDKit
from rdkit.Chem import Descriptors
import pandas as pd
desc_names = [x[0] for x in Descriptors._descList]
data = [{name: Descriptors.__dict__[name](mol) for name in desc_names} for mol in mols]
df = pd.DataFrame(data)

# chematic
df = pd.DataFrame(chematic.bulk.descriptors(smiles_list))

Features only in chematic

  • pKa prediction — built-in rule-based screening, no external tools (not for clinical use)
  • ADMET profile — built-in rule-based screening, BBB/Caco-2/hERG/CYP3A4 in a single call (not for clinical use)
  • WASM support — runs in the browser (published v1.0.15 WASM asset: 4,005,280 bytes raw / 1,460,499 bytes gzip)
  • MCP server — direct integration with AI agents
  • Pure Rust — no conda, works in Docker / serverless / CI without extra setup
  • Atropisomer detection — mol.atropisomers() detects biaryl and allene axes