omgkit
A cheminformatics toolkit written in Rust, with Python bindings.
Every structure on this page was drawn by omgkit itself — no other
chemistry library is involved. The code that produced them is
docs/figures/make_figures.py.
What it does
| SMILES | Parsing and writing, with tetrahedral chirality, double-bond geometry, dative bonds and explicit hydrogens — plus canonical output |
| Sanitization | Valence, implicit hydrogens, ring perception, kekulization, aromaticity, conjugation, hybridization |
| SMARTS | Query parsing, substructure matching (optionally stereo-aware), and SMARTS writing for molecules and reactions |
| Reactions | Reaction templates, product generation, optional atom-atom mapping, intramolecular reactions |
| Byproducts | The water an esterification drops, rebuilt as a real molecule — or an explicit cannot tell when the record itself does not balance |
.mol / .sdf |
V2000 molblock and multi-record SDF, read and written, in 2D and 3D, with stereochemistry both ways |
| 2D depiction | Coordinates and SVG/PNG/JPEG output in two drawing styles — with an explicit report of anything it could not draw well |
| 3D structures | One deterministic conformer per molecule, no random seed and no retries, as a starting point for force-field refinement |
| Graph descriptors | The sixteen per-atom and per-bond values a graph neural network reads — including Gasteiger partial charges and Pauling electronegativity |
| Batches | A columnar MolBatch with zero-copy per-molecule views |
Status: under development. The API still changes between commits. Every layer is checked record by record against an external reference implementation (see Correctness), but the surface is not yet stable enough for production use. Bug reports are welcome.
Install
pip install omgkit
One wheel covers Python 3.9 and up (built against the stable ABI), and there
are no system dependencies — nothing to apt install, no shared library to
find at runtime.
For Rust, take only the layers you need; each depends only on the ones below it.
[]
= "0.0.4" # data structures
= "0.0.4" # SMILES, SMARTS, .mol/.sdf
= "0.0.4" # sanitization
= "0.0.4" # matching, reactions, byproducts
= "0.0.4" # 2D coordinates and drawing
= "0.0.4" # 3D structure generation
Getting started
=
# 'c1cccc(c1C(O)=O)N'
=
# [[1, 2, 0]] — molecule indices, in query order
=
, ,
Stereochemistry survives the round trip
A configuration written in a SMILES string comes back out of a drawing, a
.mol file, or a 3D structure as the same configuration.
= ;
= # a wedge bond carries the configuration
= .
== # True
# and the enantiomer does *not* collide with it, so that True means something:
= ;
== # False
Reaction templates
A template describes the reaction centre; everything else in the molecule comes along automatically.
=
=
# ['CCOC(c1ccccc1)=O']
# ['O']
# 'capped'
How many product molecules come out is decided by the graph, not by the template. A template rewrites one graph; the number of products is the number of connected components that graph has afterwards. This is why applying a template that cuts a ring bond does not silently duplicate atoms.
Byproducts, and an explicit cannot tell
Reaction records generally write only the main product. The atoms a template
drops are recorded as fact (discarded), and can be closed into balanced
molecules by an atom-and-charge budget.
The answer comes with a verdict saying how much to trust it: capped (closed
by adding hydrogens only — no choices), bonded(n) (n extra bonds, which is a
heuristic), or unresolved(reason). When it is unresolved, no molecule is
returned — a made-up one would be well-formed, sanitizable, and wrong.
What you get is the formal byproduct: the balanced molecule. For Boc deprotection that is tert-butyl carbonic acid, not the carbon dioxide and isobutylene you actually isolate. Decomposition needs a rule table, and mixing a proven result with a guessed one in the same output makes them indistinguishable.
3D structures, without the retries
Mol.conformer() generates one conformer per molecule for force-field
refinement to start from. There is no random seed and no retry loop — the
same molecule always gives the same coordinates.
=
# [(x, y, z), ...], Å, lined up with conf.mol
, # (1, 1) — every centre has the right sign
The usual approach samples one distance per atom pair independently from its
allowed interval, which routinely produces a table no 3D arrangement can
satisfy; the response is to discard the attempt and re-draw, up to 10×N times.
When the cause is structural, all 10×N retries fail the same way. omgkit keeps
the embedding and replaces that sampling step: after triangle smoothing, the
upper-bound matrix is itself a metric, so it is used directly as the reference
distance table.
On the same 8831-molecule corpus:
| failures | notes | |
|---|---|---|
| RDKit ETKDGv3 2025.09.2 | 36 (0.41%) | mostly metal complexes |
| omgkit | 1 (0.01%) | the one case has contradictory distance bounds |
Drawing
Two styles are built in, both with every number taken from the ChemDraw 17.1
manual rather than tuned by eye. Output is SVG with no dependencies; PNG and
JPEG are behind the raster feature.
The picture is decided by the molecule, not by how it was written. Any SMILES for the same structure gives point-for-point identical coordinates.
What it cannot draw well, it says so. Bridged and caged systems have no
good planar solution, so Depiction reports degraded, unresolved,
crossings and unwedged counts instead of quietly handing back a picture
whose configuration cannot be read.
Correctness
Every claim above has a judge behind it, and each judge had to be shown to go
red when the behaviour is broken. The full suite (TOTAL in harness/gates.sh, 40 as of this writing) runs
on every push; the gates compare
omgkit against an external implementation record by record on a
8831-molecule corpus, and each one carries a floor as well as a cap so that it
cannot pass by being fed nothing.
harness/README.md— how each judge is built, what it was measured at, and what it is known not to reachdocs/design.md— what each layer does and why
Documentation
- Documentation site — guides and the Python API
cargo doc --workspace --no-deps --open— Rust API
Contributing
Issues and pull requests are welcome. The full gate suite is one command:
$ bash harness/gates.sh
It needs a Python environment with the pinned RDKit
(harness/requirements.lock) because most gates compare against it. The five
Rust-only gates that CI also runs are:
$ cargo fmt --all --check
$ cargo clippy --workspace --all-targets -- -D warnings
$ cargo test --release
$ cargo test --workspace
$ cargo doc --workspace --no-deps --document-private-items
cargo test is green on a fresh clone: the smoke oracles are committed. The
large-corpus tier is marked #[ignore] and needs oracles you generate yourself
— see harness/README.md.
License
Code released under the BSD-3-Clause license.
Test corpora and the element table are redistributed from other projects and
carry their own terms; each file is traced to its origin in
THIRD-PARTY-NOTICES.md.