🧬 uncorrupt-smiles
This project implements the approach described in:
Schoenmaker, L., Béquignon, O.J.M., Jespers, W. & Gerard J.P. van Westen
UnCorrupt SMILES: a novel approach to de novo design.
Journal of Cheminformatics 15, 22 (2023). https://doi.org/10.1186/s13321-023-00696-x
uncorrupt-smiles fixes broken SMILES. Give it a trained checkpoint and a SMILES string (or a CSV of them), and it hands back a valid molecule — from the CLI or from Python, one string or a streamed million-row file.
from uncorrupt_smiles import Uncorrupt
uncorrupt = Uncorrupt() # downloads and loads the bundled pretrained checkpoint, once
uncorrupt.fix_smiles("C1=CC=CC=C1(")
# -> ["c1ccccc1"]
Uncorrupt is the main entrypoint: no checkpoint path, vocab, or device to wire up — it
downloads the pretrained checkpoint bundled with this project the first time it's needed (see
Fetch example data below) and picks CUDA automatically if available.
Pass checkpoint="path/to/your.pkg" to use a self-trained checkpoint instead. Everything else —
standardizing datasets,
generating synthetic errors, training your own corrector — is secondary tooling for anyone who
wants to build a new model, built around the same three composable, argument-driven,
streaming-first building blocks: no config files, and no dataset is ever loaded fully into
memory (polars streams every CSV in and out).
📦 Installation
Requires Python 3.11+.
pip install uncorrupt-smiles
Optional extras:
pip install "uncorrupt-smiles[analysis]" # pandas/matplotlib/seaborn for the analysis/ scripts
pip install "uncorrupt-smiles[test]" # pytest, for running the test suite
To work from a clone instead (e.g. to run the test suite or modify the source):
git clone https://github.com/OlivierBeq/uncorrupt-smiles.git
cd uncorrupt-smiles
pip install -e ".[test]"
Core dependencies — torch >= 2.4, rdkit >= 2024.3.1, polars >= 1.0 — are resolved by pip
to whichever wheel (CPU or CUDA) matches your machine.
Note: checkpoints produced by earlier versions of this project (including the archive on Zenodo) use a different, incompatible checkpoint format and cannot be loaded by
Seq2Seq.load_checkpoint. Retrain to obtain a checkpoint this version can use.
🚀 Usage
Run uncorrupt-smiles <subcommand> -h for the full flag list of any subcommand. The CLI and the
Python API expose exactly the same operations, in the same order of importance: fixing SMILES
first, then the supporting tooling for anyone building their own corrector.
CLI
🩹 Fix
uncorrupt-smiles fix \
--checkpoint data/performance/transformer_multiple_12_PAPYRUS_200_16_3.pkg \
--input-csv generated/gan_ckpt100.csv --smiles-col SMILES \
--output-csv generated/gan_ckpt100_fixed.csv
📥 Fetch example data
The example datasets and pretrained checkpoint under data/, generated/, and rawdata/ are
not committed to this repository (to keep it small) — they're downloaded on demand from
Zenodo and this repo's release assets, streamed straight
to disk and checksum-verified.
uncorrupt-smiles fetch-data
Already-present files are skipped by default (pass --force to redownload). Use --only to
fetch specific files, e.g. uncorrupt-smiles fetch-data --only rawdata/gbd_8.csv.
🧹 Standardize
Canonicalizes SMILES via RDKit. Anything RDKit can't parse — or an empty string — is dropped, not silently kept.
uncorrupt-smiles standardize \
--input-csv rawdata/PAPYRUS.csv --smiles-col SMILES --separator ";" \
--output-csv data/papyrus_std.csv --threshold 200
--smiles-col and --separator are explicit arguments, so this works on any CSV — not just
PAPYRUS-shaped exports.
🧪 Generate errors
Introduces synthetic, RDKit-confirmed-invalid corruptions of valid SMILES, for training or evaluating a corrector.
uncorrupt-smiles generate-errors \
--input-csv data/papyrus_std.csv --smiles-col STD_SMILES \
--fragment-csv rawdata/gbd_8.csv --fragment-col FRAGMENT \
--train-csv data/errors/train.csv --dev-csv data/errors/dev.csv \
--invalid-type all --num-errors 1
--invalid-type selects which corruption(s) to introduce: all, multiple, or one of exists,
par, permut, ring, syntax, valence, arom.
🏋️ Train
Training, evaluation, and checkpointing all live as methods on the model itself.
uncorrupt-smiles train \
--train-csv data/errors/train.csv --dev-csv data/errors/dev.csv \
--checkpoint-out data/performance/model.pkg \
--hid-dim 128 --n-layers 2 --n-heads 4 --pf-dim 256 --batch-size 32 --epochs 20
The defaults above are sized for an 8GB GPU. For a full-scale run on more powerful hardware,
just pass larger values — e.g. --hid-dim 512 --n-heads 8 --pf-dim 1024 --batch-size 128.
--device defaults to CUDA if available, otherwise CPU. Pass --resume <checkpoint> to continue
training an existing checkpoint.
Python
🩹 Fix
from uncorrupt_smiles import Uncorrupt
uncorrupt = Uncorrupt() # or Uncorrupt(checkpoint="data/performance/model.pkg", device="cpu")
# a single SMILES, or a list
fixed = uncorrupt.fix_smiles(["C1=CC=CC=C1(", "CC(=O)Oc1ccccc1C(=O)O"])
# or stream an entire CSV in and out
uncorrupt.fix_smiles_csv(
"generated/gan_ckpt100.csv", "SMILES", "generated/gan_ckpt100_fixed.csv",
)
Uncorrupt is a thin wrapper around Seq2Seq, the model class itself — reach for it directly
if you're already holding a model (e.g. right after fit(), or loaded via
Seq2Seq.load_checkpoint(path, device)), since a checkpoint's src_vocab/trg_vocab are
attached to the model, not passed around separately:
from uncorrupt_smiles.transformer import Seq2Seq
model = Seq2Seq.load_checkpoint("data/performance/model.pkg", device="cpu")
model.fix_smiles(["C1=CC=CC=C1("])
fix_smiles/fix_smiles_csv work with a checkpoint trained by this model architecture, or any
future architecture exposing the same generate(...) method — nothing here is
Transformer-specific beyond Seq2Seq itself.
📥 Fetch example data
from uncorrupt_smiles.fetch_data import fetch_all
fetch_all()
Already-present files are skipped by default (pass force=True to redownload). Use
only=[...] to fetch specific files, e.g. fetch_all(only=["rawdata/gbd_8.csv"]).
🧹 Standardize
from uncorrupt_smiles.preprocess import standardize_smiles, standardize_stream
standardize_smiles("C(C)O") # -> "CCO"
standardize_stream(
"rawdata/PAPYRUS.csv", smiles_col="SMILES", output_csv="data/papyrus_std.csv",
length_threshold=200, separator=";",
)
🧪 Generate errors
from uncorrupt_smiles.invalid_smiles import generate_errors, reservoir_sample_fragments, write_errors_split
# stream straight to train/dev CSVs
write_errors_split(
"data/papyrus_std.csv", "STD_SMILES",
"rawdata/gbd_8.csv", "FRAGMENT",
"data/errors/train.csv", "data/errors/dev.csv",
seed=42, invalid_type="all",
)
# or generate pairs in memory, e.g. for a handful of molecules
fragments = reservoir_sample_fragments("rawdata/gbd_8.csv", "FRAGMENT", k=20_000, seed=42)
pairs = list(generate_errors(["CCO", "c1ccccc1"], fragments, seed=42))
🏋️ Train
from uncorrupt_smiles.data import iter_csv_column, make_loader
from uncorrupt_smiles.transformer import Seq2Seq
from uncorrupt_smiles.vocab import Vocab
src_vocab = Vocab.build_from_lines(iter_csv_column("data/errors/train.csv", "ERROR"))
trg_vocab = Vocab.build_from_lines(iter_csv_column("data/errors/train.csv", "STD_SMILES"))
model = Seq2Seq.build(
len(src_vocab), len(trg_vocab), max_length=202, device="cuda",
src_pad_idx=src_vocab.pad_idx, trg_pad_idx=trg_vocab.pad_idx,
hid_dim=128, n_layers=2, n_heads=4, pf_dim=256,
)
train_loader = make_loader("data/errors/train.csv", "ERROR", "STD_SMILES", src_vocab, trg_vocab, batch_size=32)
dev_loader = make_loader("data/errors/dev.csv", "ERROR", "STD_SMILES", src_vocab, trg_vocab, batch_size=32)
model.fit(train_loader, dev_loader, src_vocab, trg_vocab, epochs=20, checkpoint_path="data/performance/model.pkg")
The defaults above are sized for an 8GB GPU; pass larger hid_dim/n_heads/pf_dim/batch size
keyword arguments for a full-scale run on more powerful hardware. Use
Seq2Seq.load_checkpoint(...) to continue training an existing checkpoint.
🗂️ Package layout
| Module | Purpose |
|---|---|
src/uncorrupt_smiles/uncorrupt.py |
Uncorrupt — the main entrypoint. Wraps Seq2Seq.load_checkpoint, defaulting to the bundled pretrained checkpoint (downloaded on demand). |
src/uncorrupt_smiles/transformer.py |
The Seq2Seq model. Fixing SMILES (fix_smiles/fix_smiles_csv), training, evaluation, and checkpoint save/load are all methods on the model itself; src_vocab/trg_vocab are attached to the model, not passed around separately. |
src/uncorrupt_smiles/fetch_data.py |
Downloads example datasets / the pretrained checkpoint on demand. |
src/uncorrupt_smiles/invalid_smiles.py |
Per-SMILES error functions (exists_error, par_error, permutation, ring_error, syntax_error, valence_error, arom_error, introduce_error) plus streaming orchestration (generate_errors, write_errors_split, reservoir_sample_fragments). |
src/uncorrupt_smiles/preprocess.py |
standardize_smiles / standardize_stream. |
src/uncorrupt_smiles/vocab.py |
Vocab — character-level vocabulary, built by streaming. |
src/uncorrupt_smiles/data.py |
Streaming CSV helpers and the training IterableDataset. |
src/uncorrupt_smiles/cli.py |
The uncorrupt-smiles entrypoint. |
✅ Testing
pip install -e ".[test]"
pytest
The suite uses small synthetic fixtures and tiny model dimensions — it runs in a few seconds and comfortably fits an 8GB-VRAM/modest-CPU machine.
🙏 Acknowledgments
uncorrupt-smiles builds on the original SMILES-corrector research and codebase by Linde Schoenmaker.
📄 License
MIT — see LICENSE.
Metadata
Release files for uncorrupt-smiles 1.0.0.post0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| uncorrupt_smiles-1.0.0.post0.tar.gz | 48.2 kB | Without provenance | Details for uncorrupt_smiles-1.0.0.post0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| uncorrupt_smiles-1.0.0.post0-py3-none-any.whl | Python 3 | none | any | Details for uncorrupt_smiles-1.0.0.post0-py3-none-any.whl |
Total release size: 91.6 kB
Release files / uncorrupt_smiles-1.0.0.post0.tar.gz
| Download URL | uncorrupt_smiles-1.0.0.post0.tar.gz |
|---|---|
| Size | 48.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
2c2df4d1175ebf7136d0723043d7e0099f1f6950623ed6555f1e96acc1c98612
|
|
BLAKE2b-256 checksum How to use checksums |
43c33ceff1906b3b1c355247e9bcb995af65296928015b593fa06c8cb79cda43
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.13
|
Release files / uncorrupt_smiles-1.0.0.post0-py3-none-any.whl
| Download URL | uncorrupt_smiles-1.0.0.post0-py3-none-any.whl |
|---|---|
| Size | 43.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
02c080e5050d18e569efb8c835d08c17c000844b9582c53ed903f91db81f4b35
|
|
BLAKE2b-256 checksum How to use checksums |
80fa6aab6c567c5f9a348ef0e0662fd5d033367140347e31e247723ff1b33dc6
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.13
|
Security for uncorrupt-smiles 1.0.0.post0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.